Compare commits
159 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
a4c3415838 | ||
f535eacd97 | |||
9908897aa2 | |||
29d3cbb0b6 | |||
babc20649a | |||
1188643c4b | |||
6b74301588 | |||
168527573c | |||
3d7bb37849 | |||
12a581ced9 | |||
857e1717a5 | |||
186bfb9d12 | |||
c5bc354f65 | |||
c48bb0428f | |||
46fbb615a0 | |||
3df4e103f9 | |||
addff418c6 | |||
14d653e701 | |||
040bac5256 | |||
bf44901a0a | |||
b4d0f4e949 | |||
b2b47b1f6a | |||
1b1398653b | |||
1f61dcb115 | |||
1476fc9174 | |||
d157a3acd9 | |||
fe6be928a9 | |||
8e537be454 | |||
6947529e02 | |||
b6d78929b9 | |||
e997189a64 | |||
31940deb7a | |||
922c6da234 | |||
aa8fb165eb | |||
4cd4431565 | |||
826183be8c | |||
6d9b7ee0e4 | |||
3c66acb653 | |||
aa0ad1dce5 | |||
65756457aa | |||
67f21d2500 | |||
1cf095a5cc | |||
ffeaec7fe9 | |||
a31e196e5b | |||
57851d90a5 | |||
1e4b16b734 | |||
876466b18d | |||
82b1c187ee | |||
4f93d258b8 | |||
60993fc005 | |||
db4965c8f5 | |||
36a964d931 | |||
6d90f4c9b4 | |||
839eafd73f | |||
f4141bf201 | |||
5a46028053 | |||
18d26647e1 | |||
0b5ec86780 | |||
5e15729045 | |||
c98a5f1ac3 | |||
7f02146a0e | |||
5dab8c7351 | |||
505e0e9a30 | |||
9e3510955a | |||
c006d4fbc7 | |||
5e02c44647 | |||
47f7cb18b5 | |||
3faf065c66 | |||
dfffd03790 | |||
796aa905d2 | |||
253fb95143 | |||
f3ea075b72 | |||
af725a7f78 | |||
016e0db797 | |||
4cf8b2e1f8 | |||
67b0aa9d47 | |||
567c6eafea | |||
ff890fb2af | |||
a512fd64b5 | |||
377318a62a | |||
671c871304 | |||
e0cc6b5655 | |||
e74b44b49c | |||
d6f0d88d4a | |||
9674e5b8dc | |||
ea4cf777a9 | |||
8a308fa9e3 | |||
56fa53b701 | |||
2a4ddd4e41 | |||
3d8a63fddd | |||
9d9b1d0399 | |||
739d60d410 | |||
9003034d0d | |||
5002513d21 | |||
12ede2be02 | |||
163ec2bd50 | |||
7744839613 | |||
df803d90cb | |||
5fc22585bf | |||
b8387458db | |||
04aed2556b | |||
4cde3a7fe0 | |||
466d6d47ba | |||
fa99b7f068 | |||
3e865b6c92 | |||
94dd576d3b | |||
9c19b4a3e4 | |||
1155656c2b | |||
5cf23c1134 | |||
67e9fdd10a | |||
b0e388c50d | |||
f37c9d8375 | |||
18c8ef9606 | |||
6766a3d0dc | |||
d16f447048 | |||
37c752c2ae | |||
b088d26e6f | |||
425f11a334 | |||
|
5ddbea1a5a | ||
|
42c5121784 | ||
|
d960d85539 | ||
|
ca60217a18 | ||
16f2829785 | |||
0381022b68 | |||
aae8ec0ee1 | |||
ec4768e1cc | |||
3308895b3b | |||
37e47fc33f | |||
b4a3a36b07 | |||
baf26dc492 | |||
30e4b4665c | |||
74a0a27fc1 | |||
59eed53644 | |||
473aaa004a | |||
ecfd4115a1 | |||
0fbbfaac7c | |||
86d2fc2c5b | |||
f97866fe82 | |||
5c0b8c4df0 | |||
8da88be5e8 | |||
4f0164965c | |||
63f4321b04 | |||
e4287e9943 | |||
337c299a5e | |||
4ac4d8d049 | |||
5e8abaa5b4 | |||
d8fd7f9956 | |||
1711aadb6b | |||
06b9385e97 | |||
94386b0e02 | |||
36fea0b0f2 | |||
437d56e54d | |||
1537705cde | |||
bb7cb4a6ab | |||
bda43bf5e7 | |||
58d923b14c | |||
e981d61a54 | |||
20847362a1 | |||
cc6bc209e8 |
66
.gitea/workflows/default_nottags.yaml
Normal file
66
.gitea/workflows/default_nottags.yaml
Normal file
@@ -0,0 +1,66 @@
|
||||
name: Default (not tags)
|
||||
|
||||
on:
|
||||
push:
|
||||
tags-ignore:
|
||||
- '**'
|
||||
|
||||
env:
|
||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
|
||||
|
||||
jobs:
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install pnpm and npmci
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
|
||||
- name: Run npm prepare
|
||||
run: npmci npm prepare
|
||||
|
||||
- name: Audit production dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --prod
|
||||
continue-on-error: true
|
||||
|
||||
- name: Audit development dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --dev
|
||||
continue-on-error: true
|
||||
|
||||
test:
|
||||
if: ${{ always() }}
|
||||
needs: security
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Test stable
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm test
|
||||
|
||||
- name: Test build
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm build
|
124
.gitea/workflows/default_tags.yaml
Normal file
124
.gitea/workflows/default_tags.yaml
Normal file
@@ -0,0 +1,124 @@
|
||||
name: Default (tags)
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
|
||||
|
||||
jobs:
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Audit production dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --prod
|
||||
continue-on-error: true
|
||||
|
||||
- name: Audit development dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --dev
|
||||
continue-on-error: true
|
||||
|
||||
test:
|
||||
if: ${{ always() }}
|
||||
needs: security
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Test stable
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm test
|
||||
|
||||
- name: Test build
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm build
|
||||
|
||||
release:
|
||||
needs: test
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Release
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm publish
|
||||
|
||||
metadata:
|
||||
needs: test
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
continue-on-error: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Code quality
|
||||
run: |
|
||||
npmci command npm install -g typescript
|
||||
npmci npm install
|
||||
|
||||
- name: Trigger
|
||||
run: npmci trigger
|
||||
|
||||
- name: Build docs and upload artifacts
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
pnpm install -g @git.zone/tsdoc
|
||||
npmci command tsdoc
|
||||
continue-on-error: true
|
17
.gitignore
vendored
17
.gitignore
vendored
@@ -1,7 +1,20 @@
|
||||
.nogit/
|
||||
node_modules/
|
||||
assets/pdfdir/
|
||||
|
||||
# artifacts
|
||||
coverage/
|
||||
public/
|
||||
pages/
|
||||
|
||||
# installs
|
||||
node_modules/
|
||||
|
||||
# caches
|
||||
.yarn/
|
||||
.cache/
|
||||
.rpt2_cache
|
||||
|
||||
# builds
|
||||
dist/
|
||||
dist_*/
|
||||
|
||||
# custom
|
147
.gitlab-ci.yml
147
.gitlab-ci.yml
@@ -1,147 +0,0 @@
|
||||
# gitzone standard
|
||||
image: hosttoday/ht-docker-node:npmci
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- .npmci_cache/
|
||||
key: "$CI_BUILD_STAGE"
|
||||
|
||||
stages:
|
||||
- security
|
||||
- test
|
||||
- release
|
||||
- metadata
|
||||
|
||||
# ====================
|
||||
# security stage
|
||||
# ====================
|
||||
mirror:
|
||||
stage: security
|
||||
script:
|
||||
- npmci git mirror
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
snyk:
|
||||
stage: security
|
||||
script:
|
||||
- npmci npm prepare
|
||||
- npmci command npm install -g snyk
|
||||
- npmci command npm install --ignore-scripts
|
||||
- npmci command snyk test
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
# ====================
|
||||
# test stage
|
||||
# ====================
|
||||
testLEGACY:
|
||||
stage: test
|
||||
script:
|
||||
- npmci npm prepare
|
||||
- npmci node install legacy
|
||||
- npmci npm install
|
||||
- npmci npm test
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
allow_failure: true
|
||||
|
||||
testLTS:
|
||||
stage: test
|
||||
script:
|
||||
- npmci npm prepare
|
||||
- npmci node install lts
|
||||
- npmci npm install
|
||||
- npmci npm test
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
testSTABLE:
|
||||
stage: test
|
||||
script:
|
||||
- npmci npm prepare
|
||||
- npmci node install stable
|
||||
- npmci npm install
|
||||
- npmci npm test
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
release:
|
||||
stage: release
|
||||
script:
|
||||
- npmci node install stable
|
||||
- npmci npm publish
|
||||
only:
|
||||
- tags
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
# ====================
|
||||
# metadata stage
|
||||
# ====================
|
||||
codequality:
|
||||
stage: metadata
|
||||
image: docker:stable
|
||||
allow_failure: true
|
||||
services:
|
||||
- docker:stable-dind
|
||||
script:
|
||||
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
|
||||
- docker run
|
||||
--env SOURCE_CODE="$PWD"
|
||||
--volume "$PWD":/code
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock
|
||||
"registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
|
||||
artifacts:
|
||||
paths: [codeclimate.json]
|
||||
tags:
|
||||
- docker
|
||||
- priv
|
||||
|
||||
trigger:
|
||||
stage: metadata
|
||||
script:
|
||||
- npmci trigger
|
||||
only:
|
||||
- tags
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
pages:
|
||||
image: hosttoday/ht-docker-node:npmci
|
||||
stage: metadata
|
||||
script:
|
||||
- npmci command npm install -g typedoc typescript
|
||||
- npmci npm prepare
|
||||
- npmci npm install
|
||||
- npmci command typedoc --module "commonjs" --target "ES2016" --out public/ ts/
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
only:
|
||||
- tags
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
paths:
|
||||
- public
|
||||
allow_failure: true
|
||||
|
||||
windowsCompatibility:
|
||||
image: stefanscherer/node-windows:10-build-tools
|
||||
stage: metadata
|
||||
script:
|
||||
- npm install & npm test
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- windows
|
||||
allow_failure: true
|
11
.vscode/launch.json
vendored
Normal file
11
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "npm test",
|
||||
"name": "Run npm test",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
26
.vscode/settings.json
vendored
Normal file
26
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": ["/npmextra.json"],
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"npmci": {
|
||||
"type": "object",
|
||||
"description": "settings for npmci"
|
||||
},
|
||||
"gitzone": {
|
||||
"type": "object",
|
||||
"description": "settings for gitzone",
|
||||
"properties": {
|
||||
"projectType": {
|
||||
"type": "string",
|
||||
"enum": ["website", "element", "service", "npm", "wcc"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
142
changelog.md
Normal file
142
changelog.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-08-01 - 3.3.0 - feat(smartpdf)
|
||||
Add automatic port allocation and multi-instance support
|
||||
|
||||
- Added ISmartPdfOptions interface with port configuration options
|
||||
- Implemented automatic port allocation between 20000-30000 by default
|
||||
- Added support for custom port ranges via portRangeStart/portRangeEnd options
|
||||
- Added support for specific port assignment via port option
|
||||
- Fixed resource cleanup when port allocation fails
|
||||
- Multiple SmartPdf instances can now run simultaneously without port conflicts
|
||||
- Updated readme with comprehensive documentation for all features
|
||||
|
||||
## 2025-02-25 - 3.2.2 - fix(SmartPdf)
|
||||
Fix buffer handling for PDF conversion and text extraction
|
||||
|
||||
- Ensure Uint8Array is converted to Node Buffer for PDF conversion.
|
||||
- Correct the PDF page viewport handling by using document dimensions.
|
||||
- Fix extractTextFromPdfBuffer argument type from Uint8Array to Buffer.
|
||||
|
||||
## 2025-02-25 - 3.2.1 - fix(SmartPdf)
|
||||
Fix type for extractTextFromPdfBuffer function
|
||||
|
||||
- Corrected the parameter type from Buffer to Uint8Array for extractTextFromPdfBuffer function.
|
||||
|
||||
## 2025-02-25 - 3.2.0 - feat(smartpdf)
|
||||
Improve dependency versions and optimize PDF to PNG conversion.
|
||||
|
||||
- Update several dependencies to newer versions for better stability and performance.
|
||||
- Refactor tests to enhance readability and add directory creation validations.
|
||||
- Optimize PDF to PNG conversion by switching to a more efficient Puppeteer and PDF.js-based method.
|
||||
- Add checks for presence of required dependencies (GraphicsMagick and Ghostscript).
|
||||
- Fix media emulation issue by properly awaiting the emulateMediaType function.
|
||||
|
||||
## 2024-11-30 - 3.1.8 - fix(core)
|
||||
Fix candidate handling in PDF generation
|
||||
|
||||
- Added error handling for missing PDF candidates in server requests.
|
||||
- Updated devDependencies and dependencies to latest versions for better stability and new features.
|
||||
- Patched header retrieval logic during PDF generation for security check.
|
||||
|
||||
## 2024-09-27 - 3.1.7 - fix(dependencies)
|
||||
Update dependencies to latest versions
|
||||
|
||||
- Updated @git.zone/tsbuild to version ^2.1.84
|
||||
- Updated @git.zone/tsdoc to version ^1.3.12
|
||||
- Updated @git.zone/tsrun to version ^1.2.49
|
||||
- Updated @push.rocks/tapbundle to version ^5.3.0
|
||||
- Updated @types/node to version ^22.7.4
|
||||
- Updated @push.rocks/smartfile to version ^11.0.21
|
||||
- Updated @push.rocks/smartpromise to version ^4.0.4
|
||||
- Updated @tsclass/tsclass to version ^4.1.2
|
||||
- Updated express to version ^4.21.0
|
||||
- Updated pdf2pic to version ^3.1.3
|
||||
|
||||
## 2024-05-29 - 3.1.6 - Core
|
||||
Updated description
|
||||
|
||||
- Minor changes to documentation and internal text.
|
||||
|
||||
## 2024-04-25 to 2024-04-30 - 3.1.0 to 3.1.5 - Core
|
||||
Fix updates in core functionality
|
||||
|
||||
- Fixes and updates in core function in versions 3.1.0 to 3.1.5.
|
||||
|
||||
## 2024-04-25 - 3.0.17 - Feature
|
||||
Now supports PDF to JPG conversion
|
||||
|
||||
- Added support for converting PDF files to JPG format.
|
||||
|
||||
## 2024-03-19 to 2024-04-14 - 3.0.17 - Maintenance
|
||||
Various updates to project configuration files
|
||||
|
||||
- Updated `tsconfig`.
|
||||
- Updated `npmextra.json`.
|
||||
|
||||
## 2023-07-11 to 2024-03-19 - 3.0.15 to 3.0.16 - Organization
|
||||
Switch to new organization scheme and core updates
|
||||
|
||||
- Switched to new organization scheme.
|
||||
- Applied core updates and bug fixes.
|
||||
|
||||
## 2022-11-07 to 2023-07-10 - 3.0.13 to 3.0.14 - Core
|
||||
Fixes and updates to core functionality
|
||||
|
||||
- Various minor bug fixes and updates to core components.
|
||||
|
||||
## 2022-09-13 to 2022-11-07 - 3.0.10 to 3.0.12 - Core
|
||||
Ongoing core updates and maintenance
|
||||
|
||||
- Regular fixes and operational improvements in core functionalities.
|
||||
|
||||
## 2022-06-12 to 2022-09-13 - 3.0.7 to 3.0.9 - Core
|
||||
Continued focus on high-priority bug fixes and core functionalities
|
||||
|
||||
- Regular fixes for critical bugs and enhancements.
|
||||
|
||||
## 2022-03-24 to 2022-06-29 - 3.0.3 to 3.0.6 - Core
|
||||
Further optimization and maintenance releases
|
||||
|
||||
- Further improvements and refinements of issues in core functionalities.
|
||||
|
||||
## 2022-01-05 to 2022-03-25 - 3.0.0 to 3.0.2 - Major Version Release
|
||||
Major release for version 3.0.x, including core fixes
|
||||
|
||||
- Increased version from 2.x to 3.0. New significant changes and fixes.
|
||||
|
||||
## 2022-01-05 to 2022-03-24 - 2.0.13 to 2.0.19 - Core
|
||||
Routine core updates and bug fixes
|
||||
|
||||
- Regular bug fixes in core components.
|
||||
|
||||
## 2019-11-19 to 2022-01-06 - 2.0.0 to 2.0.11 - Core
|
||||
Multiple core updates and a few performance improvements
|
||||
|
||||
- Some performance enhancements and multiple bug fixes.
|
||||
|
||||
## 2019-11-16 to 2019-11-19 - 1.0.27 to 1.0.29 - API
|
||||
Breaking change in API
|
||||
|
||||
- Naming PDF results to better represent their content.
|
||||
|
||||
## 2019-05-29 to 2019-11-15 - 1.0.13 to 1.0.26 - Core
|
||||
Core functional updates and some major restructuring
|
||||
|
||||
- Introduced multiple updates to the core, addressing bugs and improving stability.
|
||||
|
||||
## 2019-04-10 to 2019-05-28 - 1.0.4 to 1.0.12 - Core
|
||||
Fixes and updates in the core
|
||||
|
||||
- Implementation of multiple essential fixes for core components.
|
||||
|
||||
## 2018-10-06 - 1.0.1 to 1.0.3 - Core and Typings
|
||||
Initial implementation and core fixes
|
||||
|
||||
- Initial implementation of the project.
|
||||
- Fixed compilation problems in typings.
|
||||
|
||||
## 2016-01-29 - unknown - Initial
|
||||
Initial commit
|
||||
|
||||
- Initial commit for the project setup.
|
@@ -2,5 +2,33 @@
|
||||
"npmci": {
|
||||
"npmGlobalTools": [],
|
||||
"npmAccessLevel": "public"
|
||||
},
|
||||
"gitzone": {
|
||||
"projectType": "npm",
|
||||
"module": {
|
||||
"githost": "code.foss.global",
|
||||
"gitscope": "push.rocks",
|
||||
"gitrepo": "smartpdf",
|
||||
"description": "A library for creating PDFs dynamically from HTML or websites with additional features like merging PDFs.",
|
||||
"npmPackagename": "@push.rocks/smartpdf",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"PDF generation",
|
||||
"HTML to PDF",
|
||||
"website to PDF",
|
||||
"PDF manipulation",
|
||||
"puppeteer",
|
||||
"express",
|
||||
"node.js",
|
||||
"typescript",
|
||||
"automation",
|
||||
"PDF merging",
|
||||
"text extraction",
|
||||
"PDF management"
|
||||
]
|
||||
}
|
||||
},
|
||||
"tsdoc": {
|
||||
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
||||
}
|
||||
}
|
2236
package-lock.json
generated
2236
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
85
package.json
85
package.json
@@ -1,32 +1,73 @@
|
||||
{
|
||||
"name": "@pushrocks/smartpdf",
|
||||
"version": "1.0.4",
|
||||
"name": "@push.rocks/smartpdf",
|
||||
"version": "3.3.0",
|
||||
"private": false,
|
||||
"description": "create pdfs on the fly",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"description": "A library for creating PDFs dynamically from HTML or websites with additional features like merging PDFs.",
|
||||
"main": "dist_ts/index.js",
|
||||
"typings": "dist_ts/index.d.ts",
|
||||
"type": "module",
|
||||
"author": "Lossless GmbH",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test": "(tstest test/)",
|
||||
"format": "(gitzone format)",
|
||||
"build": "(tsbuild)"
|
||||
"test": "(tstest test/ --verbose --timeout 60)",
|
||||
"build": "(tsbuild tsfolders --allowimplicitany)",
|
||||
"buildDocs": "tsdoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@gitzone/tsbuild": "^2.1.8",
|
||||
"@gitzone/tsrun": "^1.2.5",
|
||||
"@gitzone/tstest": "^1.0.20",
|
||||
"@pushrocks/tapbundle": "^3.0.9",
|
||||
"@types/node": "^11.13.2"
|
||||
"@git.zone/tsbuild": "^2.6.4",
|
||||
"@git.zone/tsdoc": "^1.5.0",
|
||||
"@git.zone/tsrun": "^1.3.3",
|
||||
"@git.zone/tstest": "^2.3.2",
|
||||
"@types/node": "^24.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pushrocks/smartfile": "^7.0.2",
|
||||
"@pushrocks/smartnetwork": "^1.1.0",
|
||||
"@pushrocks/smartpromise": "^3.0.2",
|
||||
"@pushrocks/smartunique": "^3.0.1",
|
||||
"@types/express": "^4.16.1",
|
||||
"@types/puppeteer": "^1.12.3",
|
||||
"express": "^4.16.4",
|
||||
"puppeteer": "^1.14.0"
|
||||
}
|
||||
"@push.rocks/smartbuffer": "^3.0.5",
|
||||
"@push.rocks/smartdelay": "^3.0.5",
|
||||
"@push.rocks/smartfile": "^11.2.5",
|
||||
"@push.rocks/smartnetwork": "^4.1.2",
|
||||
"@push.rocks/smartpath": "^6.0.0",
|
||||
"@push.rocks/smartpromise": "^4.2.3",
|
||||
"@push.rocks/smartpuppeteer": "^2.0.5",
|
||||
"@push.rocks/smartunique": "^3.0.9",
|
||||
"@tsclass/tsclass": "^9.2.0",
|
||||
"@types/express": "^5.0.3",
|
||||
"express": "^5.1.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pdf2json": "3.2.0"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
"ts_web/**/*",
|
||||
"dist/**/*",
|
||||
"dist_*/**/*",
|
||||
"dist_ts/**/*",
|
||||
"dist_ts_web/**/*",
|
||||
"assets/**/*",
|
||||
"cli.js",
|
||||
"npmextra.json",
|
||||
"readme.md"
|
||||
],
|
||||
"browserslist": [
|
||||
"last 1 chrome versions"
|
||||
],
|
||||
"keywords": [
|
||||
"PDF generation",
|
||||
"HTML to PDF",
|
||||
"website to PDF",
|
||||
"PDF manipulation",
|
||||
"puppeteer",
|
||||
"express",
|
||||
"node.js",
|
||||
"typescript",
|
||||
"automation",
|
||||
"PDF merging",
|
||||
"text extraction",
|
||||
"PDF management"
|
||||
],
|
||||
"homepage": "https://code.foss.global/push.rocks/smartpdf",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://code.foss.global/push.rocks/smartpdf.git"
|
||||
},
|
||||
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977"
|
||||
}
|
||||
|
10380
pnpm-lock.yaml
generated
Normal file
10380
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
1
readme.hints.md
Normal file
1
readme.hints.md
Normal file
@@ -0,0 +1 @@
|
||||
|
333
readme.md
Normal file
333
readme.md
Normal file
@@ -0,0 +1,333 @@
|
||||
# @push.rocks/smartpdf
|
||||
Create PDFs on the fly from HTML, websites, or existing PDFs with advanced features like text extraction, PDF merging, and PNG conversion.
|
||||
|
||||
## Install
|
||||
To install `@push.rocks/smartpdf`, use npm or yarn:
|
||||
|
||||
```bash
|
||||
npm install @push.rocks/smartpdf --save
|
||||
```
|
||||
|
||||
Or with yarn:
|
||||
|
||||
```bash
|
||||
yarn add @push.rocks/smartpdf
|
||||
```
|
||||
|
||||
## Requirements
|
||||
This package requires a Chrome or Chromium installation to be available on the system, as it uses Puppeteer for rendering. The package will automatically detect and use the appropriate executable.
|
||||
|
||||
## Usage
|
||||
`@push.rocks/smartpdf` provides a powerful interface for PDF generation and manipulation. All examples use ESM syntax and TypeScript.
|
||||
|
||||
### Getting Started
|
||||
First, import the necessary classes:
|
||||
|
||||
```typescript
|
||||
import { SmartPdf, IPdf } from '@push.rocks/smartpdf';
|
||||
```
|
||||
|
||||
### Basic Setup with Automatic Port Allocation
|
||||
SmartPdf automatically finds an available port between 20000-30000 for its internal server:
|
||||
|
||||
```typescript
|
||||
async function setupSmartPdf() {
|
||||
const smartPdf = await SmartPdf.create();
|
||||
await smartPdf.start();
|
||||
|
||||
// Your PDF operations here
|
||||
|
||||
await smartPdf.stop();
|
||||
}
|
||||
```
|
||||
|
||||
### Advanced Setup with Custom Port Configuration
|
||||
You can specify custom port settings to avoid conflicts or meet specific requirements:
|
||||
|
||||
```typescript
|
||||
// Use a specific port
|
||||
const smartPdf = await SmartPdf.create({ port: 3000 });
|
||||
|
||||
// Use a custom port range
|
||||
const smartPdf = await SmartPdf.create({
|
||||
portRangeStart: 4000,
|
||||
portRangeEnd: 5000
|
||||
});
|
||||
|
||||
// The server will find an available port in your specified range
|
||||
await smartPdf.start();
|
||||
console.log(`Server running on port: ${smartPdf.serverPort}`);
|
||||
```
|
||||
|
||||
### Creating PDFs from HTML Strings
|
||||
Generate PDFs from HTML content with full CSS support:
|
||||
|
||||
```typescript
|
||||
async function createPdfFromHtml() {
|
||||
const smartPdf = await SmartPdf.create();
|
||||
await smartPdf.start();
|
||||
|
||||
const htmlString = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; }
|
||||
h1 { color: #333; }
|
||||
.highlight { background-color: yellow; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Professional PDF Document</h1>
|
||||
<p>This PDF was generated from <span class="highlight">HTML content</span>.</p>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
const pdf: IPdf = await smartPdf.getA4PdfResultForHtmlString(htmlString);
|
||||
|
||||
// pdf.buffer contains the PDF data
|
||||
// pdf.id contains a unique identifier
|
||||
// pdf.name contains the filename
|
||||
// pdf.metadata contains additional information like extracted text
|
||||
|
||||
await smartPdf.stop();
|
||||
}
|
||||
```
|
||||
|
||||
### Generating PDFs from Websites
|
||||
Capture web pages as PDFs with two different approaches:
|
||||
|
||||
#### A4 Format PDF from Website
|
||||
Captures the viewable area formatted for A4 paper:
|
||||
|
||||
```typescript
|
||||
async function createA4PdfFromWebsite() {
|
||||
const smartPdf = await SmartPdf.create();
|
||||
await smartPdf.start();
|
||||
|
||||
const pdf: IPdf = await smartPdf.getPdfResultForWebsite('https://example.com');
|
||||
|
||||
// Save to file
|
||||
await fs.writeFile('website-a4.pdf', pdf.buffer);
|
||||
|
||||
await smartPdf.stop();
|
||||
}
|
||||
```
|
||||
|
||||
#### Full Webpage as Single PDF
|
||||
Captures the entire webpage in a single PDF, regardless of length:
|
||||
|
||||
```typescript
|
||||
async function createFullPdfFromWebsite() {
|
||||
const smartPdf = await SmartPdf.create();
|
||||
await smartPdf.start();
|
||||
|
||||
const pdf: IPdf = await smartPdf.getFullWebsiteAsSinglePdf('https://example.com');
|
||||
|
||||
// This captures the entire scrollable area
|
||||
await fs.writeFile('website-full.pdf', pdf.buffer);
|
||||
|
||||
await smartPdf.stop();
|
||||
}
|
||||
```
|
||||
|
||||
### Merging Multiple PDFs
|
||||
Combine multiple PDF files into a single document:
|
||||
|
||||
```typescript
|
||||
async function mergePdfs() {
|
||||
const smartPdf = await SmartPdf.create();
|
||||
await smartPdf.start();
|
||||
|
||||
// Create or load your PDFs
|
||||
const pdf1 = await smartPdf.getA4PdfResultForHtmlString('<h1>Document 1</h1>');
|
||||
const pdf2 = await smartPdf.getA4PdfResultForHtmlString('<h1>Document 2</h1>');
|
||||
const pdf3 = await smartPdf.readFileToPdfObject('./existing-document.pdf');
|
||||
|
||||
// Merge PDFs - order matters!
|
||||
const mergedPdf: Uint8Array = await smartPdf.mergePdfs([
|
||||
pdf1.buffer,
|
||||
pdf2.buffer,
|
||||
pdf3.buffer
|
||||
]);
|
||||
|
||||
// Save the merged PDF
|
||||
await fs.writeFile('merged-document.pdf', mergedPdf);
|
||||
|
||||
await smartPdf.stop();
|
||||
}
|
||||
```
|
||||
|
||||
### Reading PDFs and Extracting Text
|
||||
Extract text content from existing PDFs:
|
||||
|
||||
```typescript
|
||||
async function extractTextFromPdf() {
|
||||
const smartPdf = await SmartPdf.create();
|
||||
|
||||
// Read PDF from disk
|
||||
const pdf: IPdf = await smartPdf.readFileToPdfObject('/path/to/document.pdf');
|
||||
|
||||
// Extract all text
|
||||
const extractedText = await smartPdf.extractTextFromPdfBuffer(pdf.buffer);
|
||||
console.log('Extracted text:', extractedText);
|
||||
|
||||
// The pdf object also contains metadata with text extraction
|
||||
console.log('Metadata:', pdf.metadata);
|
||||
}
|
||||
```
|
||||
|
||||
### Converting PDF to PNG Images
|
||||
Convert each page of a PDF into PNG images:
|
||||
|
||||
```typescript
|
||||
async function convertPdfToPng() {
|
||||
const smartPdf = await SmartPdf.create();
|
||||
await smartPdf.start();
|
||||
|
||||
// Load a PDF
|
||||
const pdf = await smartPdf.readFileToPdfObject('./document.pdf');
|
||||
|
||||
// Convert to PNG images (one per page)
|
||||
const pngImages: Uint8Array[] = await smartPdf.convertPDFToPngBytes(pdf.buffer);
|
||||
|
||||
// Save each page as a PNG
|
||||
pngImages.forEach((pngBuffer, index) => {
|
||||
fs.writeFileSync(`page-${index + 1}.png`, pngBuffer);
|
||||
});
|
||||
|
||||
await smartPdf.stop();
|
||||
}
|
||||
```
|
||||
|
||||
### Using External Browser Instance
|
||||
For advanced use cases, you can provide your own Puppeteer browser instance:
|
||||
|
||||
```typescript
|
||||
import puppeteer from 'puppeteer';
|
||||
|
||||
async function useExternalBrowser() {
|
||||
// Create your own browser instance with custom options
|
||||
const browser = await puppeteer.launch({
|
||||
headless: true,
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
||||
});
|
||||
|
||||
const smartPdf = await SmartPdf.create();
|
||||
await smartPdf.start(browser);
|
||||
|
||||
// Use SmartPdf normally
|
||||
const pdf = await smartPdf.getA4PdfResultForHtmlString('<h1>Hello</h1>');
|
||||
|
||||
// SmartPdf will not close the browser when stopping
|
||||
await smartPdf.stop();
|
||||
|
||||
// You control the browser lifecycle
|
||||
await browser.close();
|
||||
}
|
||||
```
|
||||
|
||||
### Running Multiple Instances
|
||||
Thanks to automatic port allocation, you can run multiple SmartPdf instances simultaneously:
|
||||
|
||||
```typescript
|
||||
async function runMultipleInstances() {
|
||||
// Each instance automatically finds its own free port
|
||||
const instance1 = await SmartPdf.create();
|
||||
const instance2 = await SmartPdf.create();
|
||||
const instance3 = await SmartPdf.create();
|
||||
|
||||
// Start all instances
|
||||
await Promise.all([
|
||||
instance1.start(),
|
||||
instance2.start(),
|
||||
instance3.start()
|
||||
]);
|
||||
|
||||
console.log(`Instance 1 running on port: ${instance1.serverPort}`);
|
||||
console.log(`Instance 2 running on port: ${instance2.serverPort}`);
|
||||
console.log(`Instance 3 running on port: ${instance3.serverPort}`);
|
||||
|
||||
// Use instances independently
|
||||
const pdfs = await Promise.all([
|
||||
instance1.getA4PdfResultForHtmlString('<h1>PDF 1</h1>'),
|
||||
instance2.getA4PdfResultForHtmlString('<h1>PDF 2</h1>'),
|
||||
instance3.getA4PdfResultForHtmlString('<h1>PDF 3</h1>')
|
||||
]);
|
||||
|
||||
// Clean up all instances
|
||||
await Promise.all([
|
||||
instance1.stop(),
|
||||
instance2.stop(),
|
||||
instance3.stop()
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
Always wrap SmartPdf operations in try-catch blocks and ensure proper cleanup:
|
||||
|
||||
```typescript
|
||||
async function safePdfGeneration() {
|
||||
let smartPdf: SmartPdf;
|
||||
|
||||
try {
|
||||
smartPdf = await SmartPdf.create();
|
||||
await smartPdf.start();
|
||||
|
||||
const pdf = await smartPdf.getA4PdfResultForHtmlString('<h1>Hello</h1>');
|
||||
// Process PDF...
|
||||
|
||||
} catch (error) {
|
||||
console.error('PDF generation failed:', error);
|
||||
// Handle error appropriately
|
||||
} finally {
|
||||
// Always cleanup
|
||||
if (smartPdf) {
|
||||
await smartPdf.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### IPdf Interface
|
||||
The `IPdf` interface represents a PDF with its metadata:
|
||||
|
||||
```typescript
|
||||
interface IPdf {
|
||||
name: string; // Filename of the PDF
|
||||
buffer: Buffer; // PDF content as buffer
|
||||
id: string | null; // Unique identifier
|
||||
metadata?: {
|
||||
textExtraction?: string; // Extracted text content
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always start and stop**: Initialize with `start()` and cleanup with `stop()` to properly manage resources.
|
||||
2. **Port management**: Use the automatic port allocation feature to avoid conflicts when running multiple instances.
|
||||
3. **Error handling**: Always implement proper error handling as PDF generation can fail due to various reasons.
|
||||
4. **Resource cleanup**: Ensure `stop()` is called even if an error occurs to prevent memory leaks.
|
||||
5. **HTML optimization**: When creating PDFs from HTML, ensure your HTML is well-formed and CSS is embedded or inlined.
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
|
||||
|
||||
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||
|
||||
### Trademarks
|
||||
|
||||
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
|
||||
|
||||
### Company Information
|
||||
|
||||
Task Venture Capital GmbH
|
||||
Registered at District court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
||||
|
||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
97
test/test.port.ts
Normal file
97
test/test.port.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import * as smartpdf from '../ts/index.js';
|
||||
|
||||
tap.test('should create multiple SmartPdf instances with automatic port allocation', async () => {
|
||||
const instance1 = new smartpdf.SmartPdf();
|
||||
const instance2 = new smartpdf.SmartPdf();
|
||||
const instance3 = new smartpdf.SmartPdf();
|
||||
|
||||
// Start all instances
|
||||
await instance1.start();
|
||||
await instance2.start();
|
||||
await instance3.start();
|
||||
|
||||
// Verify all instances have different ports
|
||||
expect(instance1.serverPort).toBeGreaterThanOrEqual(20000);
|
||||
expect(instance1.serverPort).toBeLessThanOrEqual(30000);
|
||||
expect(instance2.serverPort).toBeGreaterThanOrEqual(20000);
|
||||
expect(instance2.serverPort).toBeLessThanOrEqual(30000);
|
||||
expect(instance3.serverPort).toBeGreaterThanOrEqual(20000);
|
||||
expect(instance3.serverPort).toBeLessThanOrEqual(30000);
|
||||
|
||||
// Ensure all ports are different
|
||||
expect(instance1.serverPort).not.toEqual(instance2.serverPort);
|
||||
expect(instance1.serverPort).not.toEqual(instance3.serverPort);
|
||||
expect(instance2.serverPort).not.toEqual(instance3.serverPort);
|
||||
|
||||
console.log(`Instance 1 port: ${instance1.serverPort}`);
|
||||
console.log(`Instance 2 port: ${instance2.serverPort}`);
|
||||
console.log(`Instance 3 port: ${instance3.serverPort}`);
|
||||
|
||||
// Test that all instances work correctly
|
||||
const pdf1 = await instance1.getA4PdfResultForHtmlString('<h1>Instance 1</h1>');
|
||||
const pdf2 = await instance2.getA4PdfResultForHtmlString('<h1>Instance 2</h1>');
|
||||
const pdf3 = await instance3.getA4PdfResultForHtmlString('<h1>Instance 3</h1>');
|
||||
|
||||
expect(pdf1.buffer).toBeInstanceOf(Buffer);
|
||||
expect(pdf2.buffer).toBeInstanceOf(Buffer);
|
||||
expect(pdf3.buffer).toBeInstanceOf(Buffer);
|
||||
|
||||
// Clean up
|
||||
await instance1.stop();
|
||||
await instance2.stop();
|
||||
await instance3.stop();
|
||||
});
|
||||
|
||||
tap.test('should create SmartPdf instance with custom port range', async () => {
|
||||
const customInstance = new smartpdf.SmartPdf({
|
||||
portRangeStart: 25000,
|
||||
portRangeEnd: 26000
|
||||
});
|
||||
|
||||
await customInstance.start();
|
||||
|
||||
expect(customInstance.serverPort).toBeGreaterThanOrEqual(25000);
|
||||
expect(customInstance.serverPort).toBeLessThanOrEqual(26000);
|
||||
|
||||
console.log(`Custom range instance port: ${customInstance.serverPort}`);
|
||||
|
||||
await customInstance.stop();
|
||||
});
|
||||
|
||||
tap.test('should create SmartPdf instance with specific port', async () => {
|
||||
const specificPortInstance = new smartpdf.SmartPdf({
|
||||
port: 28888
|
||||
});
|
||||
|
||||
await specificPortInstance.start();
|
||||
|
||||
expect(specificPortInstance.serverPort).toEqual(28888);
|
||||
|
||||
console.log(`Specific port instance: ${specificPortInstance.serverPort}`);
|
||||
|
||||
await specificPortInstance.stop();
|
||||
});
|
||||
|
||||
tap.test('should throw error when specific port is already in use', async () => {
|
||||
const instance1 = new smartpdf.SmartPdf({ port: 29999 });
|
||||
await instance1.start();
|
||||
|
||||
const instance2 = new smartpdf.SmartPdf({ port: 29999 });
|
||||
|
||||
let errorThrown = false;
|
||||
try {
|
||||
await instance2.start();
|
||||
} catch (error) {
|
||||
errorThrown = true;
|
||||
expect(error.message).toInclude('already in use');
|
||||
}
|
||||
|
||||
expect(errorThrown).toBeTrue();
|
||||
|
||||
await instance1.stop();
|
||||
});
|
||||
|
||||
|
||||
|
||||
export default tap.start();
|
82
test/test.ts
82
test/test.ts
@@ -1,23 +1,85 @@
|
||||
import { expect, tap } from '@pushrocks/tapbundle';
|
||||
import * as smartpdf from '../ts/index';
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import * as smartpdf from '../ts/index.js';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
let testSmartPdf: smartpdf.SmartPdf;
|
||||
|
||||
tap.test('should create a valid instance of smartpdf', async () => {
|
||||
/**
|
||||
* Ensures that a directory exists.
|
||||
* @param dirPath - The directory path to ensure.
|
||||
*/
|
||||
function ensureDir(dirPath: string): void {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
tap.test('should create a valid instance of SmartPdf', async () => {
|
||||
testSmartPdf = new smartpdf.SmartPdf();
|
||||
expect(testSmartPdf).to.be.instanceof(smartpdf.SmartPdf);
|
||||
expect(testSmartPdf).toBeInstanceOf(smartpdf.SmartPdf);
|
||||
});
|
||||
|
||||
tap.test('should create a pdf from html string', async () => {
|
||||
await testSmartPdf.getPdfForHtmlString('hi');
|
||||
tap.test('should start the SmartPdf instance', async () => {
|
||||
await testSmartPdf.start();
|
||||
});
|
||||
|
||||
tap.test('should create a pdf from website', async () => {
|
||||
await testSmartPdf.getPdfForWebsite('https://maintainedby.lossless.com');
|
||||
tap.test('should create PDFs from HTML string', async () => {
|
||||
const pdf1 = await testSmartPdf.getA4PdfResultForHtmlString('hi');
|
||||
const pdf2 = await testSmartPdf.getA4PdfResultForHtmlString('hello');
|
||||
expect(pdf1.buffer).toBeInstanceOf(Buffer);
|
||||
expect(pdf2.buffer).toBeInstanceOf(Buffer);
|
||||
});
|
||||
|
||||
tap.test('should be able to close properly', async () => {
|
||||
await testSmartPdf.close();
|
||||
tap.test('should create PDFs from websites', async () => {
|
||||
const pdfA4 = await testSmartPdf.getPdfResultForWebsite('https://www.wikipedia.org');
|
||||
const pdfSingle = await testSmartPdf.getFullWebsiteAsSinglePdf('https://www.wikipedia.org');
|
||||
expect(pdfA4.buffer).toBeInstanceOf(Buffer);
|
||||
expect(pdfSingle.buffer).toBeInstanceOf(Buffer);
|
||||
});
|
||||
|
||||
tap.test('should create valid PDF results and write them to disk', async () => {
|
||||
const writePdfToDisk = async (urlArg: string, fileName: string) => {
|
||||
const pdfResult = await testSmartPdf.getFullWebsiteAsSinglePdf(urlArg);
|
||||
expect(pdfResult.buffer).toBeInstanceOf(Buffer);
|
||||
ensureDir('.nogit');
|
||||
fs.writeFileSync(path.join('.nogit', fileName), pdfResult.buffer as Buffer);
|
||||
};
|
||||
await writePdfToDisk('https://lossless.com/', '1.pdf');
|
||||
await writePdfToDisk('https://layer.io', '2.pdf');
|
||||
});
|
||||
|
||||
tap.test('should merge PDFs into a combined PDF', async () => {
|
||||
const pdf1 = await testSmartPdf.readFileToPdfObject('.nogit/1.pdf');
|
||||
const pdf2 = await testSmartPdf.readFileToPdfObject('.nogit/2.pdf');
|
||||
const mergedBuffer = await testSmartPdf.mergePdfs([pdf1.buffer, pdf2.buffer]);
|
||||
ensureDir('.nogit');
|
||||
fs.writeFileSync(path.join('.nogit', 'combined.pdf'), mergedBuffer);
|
||||
});
|
||||
|
||||
tap.test('should create PNG images from combined PDF using Puppeteer conversion', async () => {
|
||||
const pdfObject = await testSmartPdf.readFileToPdfObject('.nogit/combined.pdf');
|
||||
const images = await testSmartPdf.convertPDFToPngBytes(pdfObject.buffer);
|
||||
expect(images.length).toBeGreaterThan(0);
|
||||
console.log('Puppeteer-based conversion image sizes:', images.map(img => img.length));
|
||||
});
|
||||
|
||||
tap.test('should store PNG results from both conversion functions in .nogit/testresults', async () => {
|
||||
const testResultsDir = path.join('.nogit', 'testresults');
|
||||
ensureDir(testResultsDir);
|
||||
|
||||
const pdfObject = await testSmartPdf.readFileToPdfObject('.nogit/combined.pdf');
|
||||
|
||||
// Convert using Puppeteer-based function and store images
|
||||
const imagesPuppeteer = await testSmartPdf.convertPDFToPngBytes(pdfObject.buffer);
|
||||
imagesPuppeteer.forEach((img, index) => {
|
||||
const filePath = path.join(testResultsDir, `puppeteer_method_page_${index + 1}.png`);
|
||||
fs.writeFileSync(filePath, Buffer.from(img));
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('should close the SmartPdf instance properly', async () => {
|
||||
await testSmartPdf.stop();
|
||||
});
|
||||
|
||||
tap.start();
|
8
ts/00_commitinfo_data.ts
Normal file
8
ts/00_commitinfo_data.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* autocreated commitinfo by @push.rocks/commitinfo
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartpdf',
|
||||
version: '3.2.2',
|
||||
description: 'A library for creating PDFs dynamically from HTML or websites with additional features like merging PDFs.'
|
||||
}
|
14
ts/index.ts
14
ts/index.ts
@@ -1,9 +1,15 @@
|
||||
// compatibility
|
||||
declare global {
|
||||
interface Element { }
|
||||
interface Node { }
|
||||
interface NodeListOf<TNode = Node> { }
|
||||
interface Element {}
|
||||
interface Node {}
|
||||
interface NodeListOf<TNode = Node> {}
|
||||
}
|
||||
|
||||
// normal
|
||||
export * from './smartpdf.classes.smartpdf';
|
||||
export * from './smartpdf.classes.smartpdf.js';
|
||||
|
||||
// additional types
|
||||
import type * as tsclassTypes from '@tsclass/tsclass';
|
||||
type IPdf = tsclassTypes.business.IPdf;
|
||||
|
||||
export type { IPdf };
|
||||
|
@@ -1,8 +1,15 @@
|
||||
import * as plugins from './smartpdf.plugins';
|
||||
/*
|
||||
* pdf candidate is a construct used internally for mapping html
|
||||
* to pdf buffers delivered by puppeteer
|
||||
*/
|
||||
import * as plugins from './smartpdf.plugins.js';
|
||||
|
||||
export class PdfCandidate {
|
||||
pdfId = plugins.smartunique.shortId();
|
||||
doneDeferred = plugins.smartpromise.defer();
|
||||
public htmlString: string;
|
||||
public pdfId = plugins.smartunique.shortId();
|
||||
public doneDeferred = plugins.smartpromise.defer();
|
||||
|
||||
constructor(public htmlString) {}
|
||||
constructor(htmlStringArg: string) {
|
||||
this.htmlString = htmlStringArg;
|
||||
}
|
||||
}
|
||||
|
@@ -1 +0,0 @@
|
||||
import * as plugins from './smartpdf.plugins';
|
@@ -1,58 +1,136 @@
|
||||
import * as plugins from './smartpdf.plugins';
|
||||
import * as paths from './smartpdf.paths';
|
||||
import * as plugins from './smartpdf.plugins.js';
|
||||
import * as paths from './smartpdf.paths.js';
|
||||
import { Server } from 'http';
|
||||
import { PdfCandidate } from './smartpdf.classes.pdfcandidate';
|
||||
import { PdfCandidate } from './smartpdf.classes.pdfcandidate.js';
|
||||
import { type IPdf } from '@tsclass/tsclass/dist_ts/business/pdf.js';
|
||||
import { execFile } from 'child_process';
|
||||
|
||||
declare const document: any;
|
||||
|
||||
export interface ISmartPdfOptions {
|
||||
port?: number;
|
||||
portRangeStart?: number;
|
||||
portRangeEnd?: number;
|
||||
}
|
||||
|
||||
export class SmartPdf {
|
||||
// STATIC
|
||||
public static async create(optionsArg?: ISmartPdfOptions) {
|
||||
const smartpdfInstance = new SmartPdf(optionsArg);
|
||||
return smartpdfInstance;
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
htmlServerInstance: Server;
|
||||
serverPort: number;
|
||||
headlessBrowser: plugins.puppeteer.Browser;
|
||||
headlessBrowser: plugins.smartpuppeteer.puppeteer.Browser;
|
||||
externalBrowserBool: boolean = false;
|
||||
private _readyDeferred: plugins.smartpromise.Deferred<void>;
|
||||
private _candidates: { [key: string]: PdfCandidate } = {};
|
||||
private _options: ISmartPdfOptions;
|
||||
|
||||
constructor() {
|
||||
constructor(optionsArg?: ISmartPdfOptions) {
|
||||
this._readyDeferred = new plugins.smartpromise.Deferred();
|
||||
this.init();
|
||||
this._options = {
|
||||
portRangeStart: 20000,
|
||||
portRangeEnd: 30000,
|
||||
...optionsArg
|
||||
};
|
||||
}
|
||||
|
||||
async init() {
|
||||
async start(headlessBrowserArg?: plugins.smartpuppeteer.puppeteer.Browser) {
|
||||
const done = plugins.smartpromise.defer();
|
||||
// lets set the external browser in case one is provided
|
||||
this.headlessBrowser = headlessBrowserArg;
|
||||
// setup puppeteer
|
||||
this.headlessBrowser = await plugins.puppeteer.launch();
|
||||
if (this.headlessBrowser) {
|
||||
this.externalBrowserBool = true;
|
||||
} else {
|
||||
this.headlessBrowser = await plugins.smartpuppeteer.getEnvAwareBrowserInstance({
|
||||
forceNoSandbox: false,
|
||||
});
|
||||
}
|
||||
|
||||
// setup server
|
||||
// Find an available port BEFORE creating server
|
||||
const smartnetworkInstance = new plugins.smartnetwork.SmartNetwork();
|
||||
|
||||
if (this._options.port) {
|
||||
// If a specific port is requested, check if it's available
|
||||
const isPortAvailable = await smartnetworkInstance.isLocalPortUnused(this._options.port);
|
||||
if (isPortAvailable) {
|
||||
this.serverPort = this._options.port;
|
||||
} else {
|
||||
// Clean up browser if we created one
|
||||
if (!this.externalBrowserBool && this.headlessBrowser) {
|
||||
await this.headlessBrowser.close();
|
||||
}
|
||||
throw new Error(`Requested port ${this._options.port} is already in use`);
|
||||
}
|
||||
} else {
|
||||
// Find a free port in the specified range
|
||||
this.serverPort = await smartnetworkInstance.findFreePort(
|
||||
this._options.portRangeStart,
|
||||
this._options.portRangeEnd
|
||||
);
|
||||
if (!this.serverPort) {
|
||||
// Clean up browser if we created one
|
||||
if (!this.externalBrowserBool && this.headlessBrowser) {
|
||||
await this.headlessBrowser.close();
|
||||
}
|
||||
throw new Error(`No free ports available in range ${this._options.portRangeStart}-${this._options.portRangeEnd}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Now setup server after we know we have a valid port
|
||||
const app = plugins.express();
|
||||
app.get('/:pdfId', (req, res) => {
|
||||
res.setHeader('PDF-ID', this._candidates[req.params.pdfId].pdfId);
|
||||
res.send(this._candidates[req.params.pdfId].htmlString);
|
||||
const wantedCandidate = this._candidates[req.params.pdfId];
|
||||
if (!wantedCandidate) {
|
||||
console.log(`${req.url} not attached to a candidate`);
|
||||
return;
|
||||
}
|
||||
res.setHeader('pdf-id', wantedCandidate.pdfId);
|
||||
res.send(wantedCandidate.htmlString);
|
||||
});
|
||||
this.htmlServerInstance = plugins.http.createServer(app);
|
||||
const smartnetworkInstance = new plugins.smartnetwork.SmartNetwork();
|
||||
const portAvailable = smartnetworkInstance.isLocalPortAvailable(3210);
|
||||
this.htmlServerInstance.listen(3210, 'localhost');
|
||||
|
||||
this.htmlServerInstance.listen(this.serverPort, 'localhost');
|
||||
this.htmlServerInstance.on('listening', () => {
|
||||
console.log(`SmartPdf server listening on port ${this.serverPort}`);
|
||||
this._readyDeferred.resolve();
|
||||
done.resolve();
|
||||
});
|
||||
await done.promise;
|
||||
}
|
||||
|
||||
async close () {
|
||||
// stop
|
||||
async stop() {
|
||||
const done = plugins.smartpromise.defer<void>();
|
||||
this.htmlServerInstance.close(() => {
|
||||
done.resolve();
|
||||
});
|
||||
|
||||
if (!this.externalBrowserBool) {
|
||||
await this.headlessBrowser.close();
|
||||
}
|
||||
|
||||
await done.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a pdf for a given html string;
|
||||
* Returns a PDF for a given HTML string.
|
||||
*/
|
||||
async getPdfForHtmlString(htmlStringArg: string) {
|
||||
async getA4PdfResultForHtmlString(htmlStringArg: string): Promise<plugins.tsclass.business.IPdf> {
|
||||
await this._readyDeferred.promise;
|
||||
const pdfCandidate = new PdfCandidate(htmlStringArg);
|
||||
this._candidates[pdfCandidate.pdfId] = pdfCandidate;
|
||||
const page = await this.headlessBrowser.newPage();
|
||||
const response = await page.goto(`http://localhost:3210/${pdfCandidate.pdfId}`, {
|
||||
waitUntil: 'networkidle2'
|
||||
await page.setViewport({
|
||||
width: 794,
|
||||
height: 1122,
|
||||
});
|
||||
const response = await page.goto(`http://localhost:${this.serverPort}/${pdfCandidate.pdfId}`, {
|
||||
waitUntil: 'networkidle2',
|
||||
});
|
||||
const headers = response.headers();
|
||||
if (headers['pdf-id'] !== pdfCandidate.pdfId) {
|
||||
@@ -62,27 +140,255 @@ export class SmartPdf {
|
||||
console.log(`id security check passed for ${pdfCandidate.pdfId}`);
|
||||
}
|
||||
|
||||
await page.pdf({
|
||||
path: plugins.path.join(paths.pdfDir, `${pdfCandidate.pdfId}.pdf`),
|
||||
format: 'A4'
|
||||
const pdfBuffer = await page.pdf({
|
||||
width: 794,
|
||||
height: 1122,
|
||||
printBackground: true,
|
||||
displayHeaderFooter: false,
|
||||
});
|
||||
// Convert Uint8Array to Node Buffer
|
||||
const nodePdfBuffer = Buffer.from(pdfBuffer);
|
||||
await page.close();
|
||||
delete this._candidates[pdfCandidate.pdfId];
|
||||
pdfCandidate.doneDeferred.resolve();
|
||||
await pdfCandidate.doneDeferred.promise;
|
||||
return {
|
||||
id: pdfCandidate.pdfId,
|
||||
name: `${pdfCandidate.pdfId}.js`,
|
||||
metadata: {
|
||||
textExtraction: await this.extractTextFromPdfBuffer(nodePdfBuffer),
|
||||
},
|
||||
buffer: nodePdfBuffer,
|
||||
};
|
||||
}
|
||||
|
||||
async getPdfForWebsite(websiteUrl: string) {
|
||||
async getPdfResultForWebsite(websiteUrl: string): Promise<plugins.tsclass.business.IPdf> {
|
||||
const page = await this.headlessBrowser.newPage();
|
||||
await page.setViewport({
|
||||
width: 1980,
|
||||
height: 1200,
|
||||
});
|
||||
await page.emulateMediaType('screen');
|
||||
const response = await page.goto(websiteUrl, { waitUntil: 'networkidle2' });
|
||||
const pdfId = plugins.smartunique.shortId();
|
||||
await page.pdf({
|
||||
path: plugins.path.join(paths.pdfDir, `${pdfId}.pdf`),
|
||||
format: 'A4',
|
||||
const { documentHeight, documentWidth } = await page.evaluate(() => {
|
||||
return {
|
||||
documentHeight: document.height,
|
||||
documentWidth: document.width,
|
||||
};
|
||||
});
|
||||
const pdfBuffer = await page.pdf({
|
||||
height: documentHeight,
|
||||
width: documentWidth,
|
||||
printBackground: true,
|
||||
displayHeaderFooter: false,
|
||||
preferCSSPageSize: true
|
||||
});
|
||||
// Convert Uint8Array to Node Buffer
|
||||
const nodePdfBuffer = Buffer.from(pdfBuffer);
|
||||
await page.close();
|
||||
return {
|
||||
id: pdfId,
|
||||
name: `${pdfId}.js`,
|
||||
metadata: {
|
||||
textExtraction: await this.extractTextFromPdfBuffer(nodePdfBuffer),
|
||||
},
|
||||
buffer: nodePdfBuffer,
|
||||
};
|
||||
}
|
||||
|
||||
async getFullWebsiteAsSinglePdf(websiteUrl: string): Promise<plugins.tsclass.business.IPdf> {
|
||||
const page = await this.headlessBrowser.newPage();
|
||||
await page.setViewport({
|
||||
width: 1920,
|
||||
height: 1200,
|
||||
});
|
||||
await page.emulateMediaType('screen');
|
||||
const response = await page.goto(websiteUrl, { waitUntil: 'networkidle2' });
|
||||
const pdfId = plugins.smartunique.shortId();
|
||||
// Use both document.body and document.documentElement to ensure we have a valid height and width.
|
||||
const { documentHeight, documentWidth } = await page.evaluate(() => {
|
||||
return {
|
||||
documentHeight: Math.max(
|
||||
document.body.scrollHeight,
|
||||
document.documentElement.scrollHeight
|
||||
) || 1200,
|
||||
documentWidth: Math.max(
|
||||
document.body.clientWidth,
|
||||
document.documentElement.clientWidth
|
||||
) || 1920,
|
||||
};
|
||||
});
|
||||
// Update viewport height to the full document height.
|
||||
await page.setViewport({
|
||||
width: 1920,
|
||||
height: documentHeight,
|
||||
});
|
||||
const pdfBuffer = await page.pdf({
|
||||
height: documentHeight,
|
||||
width: 1920,
|
||||
printBackground: true,
|
||||
displayHeaderFooter: false,
|
||||
scale: 1,
|
||||
pageRanges: '1',
|
||||
});
|
||||
// Convert Uint8Array to Node Buffer
|
||||
const nodePdfBuffer = Buffer.from(pdfBuffer);
|
||||
await page.close();
|
||||
return {
|
||||
id: pdfId,
|
||||
name: `${pdfId}.js`,
|
||||
metadata: {
|
||||
textExtraction: await this.extractTextFromPdfBuffer(nodePdfBuffer),
|
||||
},
|
||||
buffer: nodePdfBuffer,
|
||||
};
|
||||
}
|
||||
|
||||
public async mergePdfs(inputPdfBuffers: Uint8Array[]): Promise<Uint8Array> {
|
||||
const mergedPdf = await plugins.pdfLib.PDFDocument.create();
|
||||
for (const pdfBytes of inputPdfBuffers) {
|
||||
const pdfDoc = await plugins.pdfLib.PDFDocument.load(pdfBytes);
|
||||
const pages = await mergedPdf.copyPages(pdfDoc, pdfDoc.getPageIndices());
|
||||
pages.forEach((page) => mergedPdf.addPage(page));
|
||||
}
|
||||
|
||||
const mergedPdfBytes = await mergedPdf.save();
|
||||
return mergedPdfBytes;
|
||||
}
|
||||
|
||||
public async readFileToPdfObject(pathArg: string): Promise<plugins.tsclass.business.IPdf> {
|
||||
const absolutePath = plugins.smartpath.transform.makeAbsolute(pathArg);
|
||||
const parsedPath = plugins.path.parse(absolutePath);
|
||||
const buffer = await plugins.smartfile.fs.toBuffer(absolutePath);
|
||||
return {
|
||||
name: parsedPath.base,
|
||||
buffer,
|
||||
id: null,
|
||||
metadata: null,
|
||||
};
|
||||
}
|
||||
|
||||
public async extractTextFromPdfBuffer(pdfBufferArg: Buffer): Promise<string> {
|
||||
const deferred = plugins.smartpromise.defer<string>();
|
||||
const pdfParser: any = new plugins.pdf2json();
|
||||
pdfParser.on('pdfParser_dataReady', (pdfData: any) => {
|
||||
let finalText = '';
|
||||
for (const page of pdfData.Pages) {
|
||||
for (const text of page.Texts) {
|
||||
for (const letter of text.R) {
|
||||
finalText = finalText + letter.T;
|
||||
}
|
||||
}
|
||||
}
|
||||
deferred.resolve(finalText);
|
||||
});
|
||||
pdfParser.parseBuffer(pdfBufferArg);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for the presence of required dependencies: GraphicsMagick and Ghostscript.
|
||||
*/
|
||||
private async checkDependencies(): Promise<void> {
|
||||
await Promise.all([
|
||||
this.checkCommandExists('gm', ['version']),
|
||||
this.checkCommandExists('gs', ['--version']),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given command exists by trying to execute it.
|
||||
*/
|
||||
private checkCommandExists(command: string, args: string[]): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile(command, args, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(new Error(`Dependency check failed: ${command} is not installed or not in the PATH. ${error.message}`));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a PDF to PNG bytes for each page using Puppeteer and PDF.js.
|
||||
* This method creates a temporary HTML page that loads PDF.js from a CDN,
|
||||
* renders each PDF page to a canvas, and then screenshots each canvas element.
|
||||
*/
|
||||
public async convertPDFToPngBytes(
|
||||
pdfBytes: Uint8Array,
|
||||
options: { width?: number; height?: number; quality?: number } = {}
|
||||
): Promise<Uint8Array[]> {
|
||||
// Note: options.width, options.height, and options.quality are not applied here,
|
||||
// as the rendered canvas size is determined by the PDF page dimensions.
|
||||
|
||||
// Create a new page using the headless browser.
|
||||
const page = await this.headlessBrowser.newPage();
|
||||
|
||||
// Prepare PDF data as a base64 string.
|
||||
const base64Pdf: string = Buffer.from(pdfBytes).toString('base64');
|
||||
|
||||
// HTML template that loads PDF.js and renders the PDF.
|
||||
const htmlTemplate: string = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>PDF to PNG Converter</title>
|
||||
<style>
|
||||
body { margin: 0; }
|
||||
canvas { display: block; margin: 10px auto; }
|
||||
</style>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
(async function() {
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.worker.min.js';
|
||||
const pdfData = "__PDF_DATA__";
|
||||
const raw = atob(pdfData);
|
||||
const pdfArray = new Uint8Array([...raw].map(c => c.charCodeAt(0)));
|
||||
const loadingTask = pdfjsLib.getDocument({data: pdfArray});
|
||||
const pdf = await loadingTask.promise;
|
||||
const numPages = pdf.numPages;
|
||||
for (let pageNum = 1; pageNum <= numPages; pageNum++) {
|
||||
const page = await pdf.getPage(pageNum);
|
||||
const viewport = page.getViewport({ scale: 1.0 });
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
canvas.width = viewport.width;
|
||||
canvas.height = viewport.height;
|
||||
await page.render({ canvasContext: context, viewport: viewport }).promise;
|
||||
document.body.appendChild(canvas);
|
||||
}
|
||||
window.renderComplete = true;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
// Replace the placeholder with the actual base64 PDF data.
|
||||
const htmlContent: string = htmlTemplate.replace("__PDF_DATA__", base64Pdf);
|
||||
|
||||
// Set the page content.
|
||||
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
|
||||
|
||||
// Wait until the PDF.js rendering is complete.
|
||||
await page.waitForFunction(() => (window as any).renderComplete === true, { timeout: 30000 });
|
||||
|
||||
// Query all canvas elements (each representing a rendered PDF page).
|
||||
const canvasElements = await page.$$('canvas');
|
||||
const pngBuffers: Uint8Array[] = [];
|
||||
|
||||
for (const canvasElement of canvasElements) {
|
||||
// Screenshot the canvas element. The screenshot will be a PNG buffer.
|
||||
const screenshotBuffer = (await canvasElement.screenshot({ encoding: 'binary' })) as Buffer;
|
||||
pngBuffers.push(new Uint8Array(screenshotBuffer));
|
||||
}
|
||||
|
||||
await page.close();
|
||||
return pngBuffers;
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
import * as plugins from './smartpdf.plugins';
|
||||
import * as plugins from './smartpdf.plugins.js';
|
||||
|
||||
export const packageDir = plugins.path.join(__dirname, '../');
|
||||
export const pdfDir = plugins.path.join(packageDir, 'assets/pdfdir');
|
||||
|
||||
plugins.smartfile.fs.ensureDirSync(pdfDir);
|
||||
export const packageDir = plugins.path.join(
|
||||
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
||||
'../'
|
||||
);
|
||||
|
@@ -5,15 +5,34 @@ import * as path from 'path';
|
||||
export { http, path };
|
||||
|
||||
// @pushrocks
|
||||
import * as smartfile from '@pushrocks/smartfile';
|
||||
import * as smartpromise from '@pushrocks/smartpromise';
|
||||
import * as smartnetwork from '@pushrocks/smartnetwork';
|
||||
import * as smartunique from '@pushrocks/smartunique';
|
||||
import * as smartbuffer from '@push.rocks/smartbuffer';
|
||||
import * as smartfile from '@push.rocks/smartfile';
|
||||
import * as smartdelay from '@push.rocks/smartdelay';
|
||||
import * as smartpromise from '@push.rocks/smartpromise';
|
||||
import * as smartpath from '@push.rocks/smartpath';
|
||||
import * as smartpuppeteer from '@push.rocks/smartpuppeteer';
|
||||
import * as smartnetwork from '@push.rocks/smartnetwork';
|
||||
import * as smartunique from '@push.rocks/smartunique';
|
||||
|
||||
export { smartfile, smartpromise, smartunique, smartnetwork };
|
||||
export {
|
||||
smartbuffer,
|
||||
smartfile,
|
||||
smartdelay,
|
||||
smartpromise,
|
||||
smartpath,
|
||||
smartpuppeteer,
|
||||
smartunique,
|
||||
smartnetwork,
|
||||
};
|
||||
|
||||
// tsclass scope
|
||||
import * as tsclass from '@tsclass/tsclass';
|
||||
|
||||
export { tsclass };
|
||||
|
||||
// thirdparty
|
||||
import * as express from 'express';
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import express from 'express';
|
||||
import pdf2json from 'pdf2json';
|
||||
import pdfLib from 'pdf-lib';
|
||||
|
||||
export { express, puppeteer };
|
||||
export { express, pdf2json, pdfLib, };
|
||||
|
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"useDefineForClassFields": false,
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true
|
||||
},
|
||||
"exclude": [
|
||||
"dist_*/**/*.d.ts"
|
||||
]
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"extends": ["tslint:latest", "tslint-config-prettier"],
|
||||
"rules": {
|
||||
"semicolon": [true, "always"]
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user