pagyra-js 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +30 -0
- package/CHANGELOG.md +13 -0
- package/README.md +275 -0
- package/UA_Styles_Chromium.md +93 -0
- package/_ext/woff2_conversion/brotli-decode.d.ts +139 -0
- package/_ext/woff2_conversion/brotli-encode.d.ts +129 -0
- package/_ext/woff2_conversion/brotli-port.d.ts +12 -0
- package/_ext/woff2_conversion/brotli-shared-dictionary.d.ts +25 -0
- package/_ext/woff2_conversion/brotli-types.d.ts +15 -0
- package/_ext/woff2_conversion/woff2-common.d.ts +37 -0
- package/_ext/woff2_conversion/woff2-decode.d.ts +32 -0
- package/_ext/woff2_conversion/woff2-encode.d.ts +31 -0
- package/_ext/woff2_conversion/woff2-output.d.ts +39 -0
- package/_ext/woff2_original_cpp/brotli/brotli.c +1559 -0
- package/_ext/woff2_original_cpp/brotli/brotli.md +116 -0
- package/_ext/woff2_original_cpp/brotli/decode.h +409 -0
- package/_ext/woff2_original_cpp/brotli/encode.h +505 -0
- package/_ext/woff2_original_cpp/brotli/port.h +302 -0
- package/_ext/woff2_original_cpp/brotli/shared_dictionary.h +100 -0
- package/_ext/woff2_original_cpp/brotli/types.h +83 -0
- package/_ext/woff2_original_cpp/cmake/FindBrotliDec.cmake +35 -0
- package/_ext/woff2_original_cpp/cmake/FindBrotliEnc.cmake +35 -0
- package/_ext/woff2_original_cpp/include/woff2/decode.h +36 -0
- package/_ext/woff2_original_cpp/include/woff2/encode.h +43 -0
- package/_ext/woff2_original_cpp/include/woff2/output.h +86 -0
- package/_ext/woff2_original_cpp/src/buffer.h +164 -0
- package/_ext/woff2_original_cpp/src/convert_woff2ttf_fuzzer.cc +13 -0
- package/_ext/woff2_original_cpp/src/convert_woff2ttf_fuzzer_new_entry.cc +12 -0
- package/_ext/woff2_original_cpp/src/file.h +30 -0
- package/_ext/woff2_original_cpp/src/font.cc +400 -0
- package/_ext/woff2_original_cpp/src/font.h +105 -0
- package/_ext/woff2_original_cpp/src/glyph.cc +383 -0
- package/_ext/woff2_original_cpp/src/glyph.h +71 -0
- package/_ext/woff2_original_cpp/src/normalize.cc +314 -0
- package/_ext/woff2_original_cpp/src/normalize.h +39 -0
- package/_ext/woff2_original_cpp/src/port.h +66 -0
- package/_ext/woff2_original_cpp/src/round.h +27 -0
- package/_ext/woff2_original_cpp/src/store_bytes.h +55 -0
- package/_ext/woff2_original_cpp/src/table_tags.cc +82 -0
- package/_ext/woff2_original_cpp/src/table_tags.h +30 -0
- package/_ext/woff2_original_cpp/src/transform.cc +430 -0
- package/_ext/woff2_original_cpp/src/transform.h +26 -0
- package/_ext/woff2_original_cpp/src/variable_length.cc +129 -0
- package/_ext/woff2_original_cpp/src/variable_length.h +30 -0
- package/_ext/woff2_original_cpp/src/woff2_common.cc +50 -0
- package/_ext/woff2_original_cpp/src/woff2_common.h +64 -0
- package/_ext/woff2_original_cpp/src/woff2_compress.cc +43 -0
- package/_ext/woff2_original_cpp/src/woff2_dec.cc +1398 -0
- package/_ext/woff2_original_cpp/src/woff2_decompress.cc +41 -0
- package/_ext/woff2_original_cpp/src/woff2_enc.cc +458 -0
- package/_ext/woff2_original_cpp/src/woff2_info.cc +142 -0
- package/_ext/woff2_original_cpp/src/woff2_out.cc +63 -0
- package/assets/fonts/ttf/arimo/Arimo-Bold.ttf +0 -0
- package/assets/fonts/ttf/arimo/Arimo-BoldItalic.ttf +0 -0
- package/assets/fonts/ttf/arimo/Arimo-Italic.ttf +0 -0
- package/assets/fonts/ttf/arimo/Arimo-Regular.ttf +0 -0
- package/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Black.ttf +0 -0
- package/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Bold.ttf +0 -0
- package/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Regular.ttf +0 -0
- package/assets/fonts/ttf/dejavu/DejaVuSans.ttf +0 -0
- package/assets/fonts/ttf/firecode/FiraCode-Bold.ttf +0 -0
- package/assets/fonts/ttf/firecode/FiraCode-Light.ttf +0 -0
- package/assets/fonts/ttf/firecode/FiraCode-Medium.ttf +0 -0
- package/assets/fonts/ttf/firecode/FiraCode-Regular.ttf +0 -0
- package/assets/fonts/ttf/firecode/FiraCode-SemiBold.ttf +0 -0
- package/assets/fonts/ttf/notoemoji/NotoEmoji-Bold.ttf +0 -0
- package/assets/fonts/ttf/notoemoji/NotoEmoji-Light.ttf +0 -0
- package/assets/fonts/ttf/notoemoji/NotoEmoji-Medium.ttf +0 -0
- package/assets/fonts/ttf/notoemoji/NotoEmoji-Regular.ttf +0 -0
- package/assets/fonts/ttf/notoemoji/NotoEmoji-SemiBold.ttf +0 -0
- package/assets/fonts/ttf/notosans/NotoSans-Regular.ttf +0 -0
- package/assets/fonts/ttf/roboto/Roboto-Bold.ttf +0 -0
- package/assets/fonts/ttf/roboto/Roboto-BoldItalic.ttf +0 -0
- package/assets/fonts/ttf/roboto/Roboto-Italic.ttf +0 -0
- package/assets/fonts/ttf/roboto/Roboto-Regular.ttf +0 -0
- package/assets/fonts/ttf/stixtwomath/STIXTwoMath-Regular.ttf +0 -0
- package/assets/fonts/ttf/tinos/Tinos-Bold.ttf +0 -0
- package/assets/fonts/ttf/tinos/Tinos-BoldItalic.ttf +0 -0
- package/assets/fonts/ttf/tinos/Tinos-Italic.ttf +0 -0
- package/assets/fonts/ttf/tinos/Tinos-Regular.ttf +0 -0
- package/assets/fonts/woff/lato/lato-latin-400-italic.woff +0 -0
- package/assets/fonts/woff/lato/lato-latin-400-normal.woff +0 -0
- package/assets/fonts/woff/lato/lato-latin-700-italic.woff +0 -0
- package/assets/fonts/woff/lato/lato-latin-700-normal.woff +0 -0
- package/assets/fonts/woff2/caveat/Caveat-Bold.woff2 +0 -0
- package/assets/fonts/woff2/caveat/Caveat-Regular.woff2 +0 -0
- package/assets/fonts/woff2/lato/lato-latin-400-italic.woff2 +0 -0
- package/assets/fonts/woff2/lato/lato-latin-400-normal.woff2 +0 -0
- package/assets/fonts/woff2/lato/lato-latin-700-italic.woff2 +0 -0
- package/assets/fonts/woff2/lato/lato-latin-700-normal.woff2 +0 -0
- package/docs/AGENTS.md +288 -0
- package/docs/BACKGROUND-REPEAT-IMPLEMENTATION.md +127 -0
- package/docs/BACKGROUND-REPEAT-REFERENCE.md +127 -0
- package/docs/BACKGROUND-REPEAT-SPACE-ROUND.md +164 -0
- package/docs/css-properties-support.md +256 -0
- package/docs/src_modules_table.md +172 -0
- package/docs/text-overlap-fix.md +85 -0
- package/docs/text-overlap-investigation.md +27 -0
- package/eslint.config.js +36 -0
- package/glyph_measure.htm +1458 -0
- package/package.json +50 -0
- package/playground/browser-entry.ts +2 -0
- package/playground/exports/background-text-debug.pdf +0 -0
- package/playground/public/app.js +875 -0
- package/playground/public/assets/1.webp +0 -0
- package/playground/public/examples/accents-test.html +24 -0
- package/playground/public/examples/advanced-selectors-demo.html +118 -0
- package/playground/public/examples/background-advanced-showcase.html +82 -0
- package/playground/public/examples/background-clip-text.html +36 -0
- package/playground/public/examples/background-origin-showcase.html +137 -0
- package/playground/public/examples/background-position-showcase.html +83 -0
- package/playground/public/examples/background-repeat-showcase.html +83 -0
- package/playground/public/examples/background-repeat-space-round.html +348 -0
- package/playground/public/examples/background-size-showcase.html +82 -0
- package/playground/public/examples/background-text-debug.html +18 -0
- package/playground/public/examples/baseline-test.html +24 -0
- package/playground/public/examples/bold-showcase.html +150 -0
- package/playground/public/examples/bold-strike-example.html +12 -0
- package/playground/public/examples/border-collapse-test.html +23 -0
- package/playground/public/examples/centered-shadow-div.html +72 -0
- package/playground/public/examples/css-variables.html +50 -0
- package/playground/public/examples/debug-accents.html +11 -0
- package/playground/public/examples/debug-text-overlap.html +46 -0
- package/playground/public/examples/flex-gap-column.html +130 -0
- package/playground/public/examples/flex-gap-row.html +137 -0
- package/playground/public/examples/flex-padding-test.html +29 -0
- package/playground/public/examples/flexbox-text-test.html +193 -0
- package/playground/public/examples/fonts-demo.html +126 -0
- package/playground/public/examples/footer-example.html +4 -0
- package/playground/public/examples/gradient-text.html +54 -0
- package/playground/public/examples/grid-gap-demo.html +156 -0
- package/playground/public/examples/header-example.html +4 -0
- package/playground/public/examples/header-footer-example.html +27 -0
- package/playground/public/examples/image-showcase.html +33 -0
- package/playground/public/examples/justify-text.html +22 -0
- package/playground/public/examples/linear-gradient-example.html +38 -0
- package/playground/public/examples/lorem-span.html +14 -0
- package/playground/public/examples/margin-block-showcase.html +21 -0
- package/playground/public/examples/margin-inline-showcase.html +21 -0
- package/playground/public/examples/monthly-summary.html +95 -0
- package/playground/public/examples/multi-page-lorem.html +190 -0
- package/playground/public/examples/opacity-debug.html +39 -0
- package/playground/public/examples/opacity-example.html +70 -0
- package/playground/public/examples/png-image-example.html +13 -0
- package/playground/public/examples/red-rectangle.html +18 -0
- package/playground/public/examples/repro.html +24 -0
- package/playground/public/examples/rounded-borders-test.html +24 -0
- package/playground/public/examples/simple-list.html +89 -0
- package/playground/public/examples/simple-svg.html +37 -0
- package/playground/public/examples/simple-table.html +52 -0
- package/playground/public/examples/skew-div.html +138 -0
- package/playground/public/examples/skew-text.html +21 -0
- package/playground/public/examples/starter-report.css +51 -0
- package/playground/public/examples/starter-report.html +23 -0
- package/playground/public/examples/svg-aspect-ratio-showcase.html +116 -0
- package/playground/public/examples/svg-gradients-linear.html +28 -0
- package/playground/public/examples/svg-gradients-radial.html +29 -0
- package/playground/public/examples/svg-gradients-showcase.html +66 -0
- package/playground/public/examples/svg-image-path-test.html +43 -0
- package/playground/public/examples/svg-images-clipping.html +27 -0
- package/playground/public/examples/svg-path-gallery.html +118 -0
- package/playground/public/examples/svg-radial-transform-demo.html +78 -0
- package/playground/public/examples/svg-transform-stack.html +103 -0
- package/playground/public/examples/svg-transforms-demo.html +127 -0
- package/playground/public/examples/table-merge-test.html +34 -0
- package/playground/public/examples/text-decoration-showcase.html +138 -0
- package/playground/public/examples/text-indent-showcase.html +137 -0
- package/playground/public/examples/text-shadow-example.html +29 -0
- package/playground/public/examples/very-complex-css.html +293 -0
- package/playground/public/examples/webp-example.html +13 -0
- package/playground/public/examples/z-index-demo.html +93 -0
- package/playground/public/examples.json +240 -0
- package/playground/public/images/dice.png +0 -0
- package/playground/public/images/duck.jpg +0 -0
- package/playground/public/index.html +149 -0
- package/playground/public/mode.js +1 -0
- package/playground/public/styles.css +382 -0
- package/playground/public/tmp-h2-debug.html +33 -0
- package/playground/public/tmp-italic-debug.html +32 -0
- package/playground/public/vendor/codemirror/codemirror.min.css +1 -0
- package/playground/public/vendor/codemirror/codemirror.min.js +1 -0
- package/playground/public/vendor/codemirror/css.min.js +1 -0
- package/playground/public/vendor/codemirror/darcula.min.css +1 -0
- package/playground/public/vendor/codemirror/htmlmixed.min.js +1 -0
- package/playground/public/vendor/codemirror/javascript.min.js +1 -0
- package/playground/public/vendor/codemirror/xml.min.js +1 -0
- package/playground/public/vendor/pagyra-playground-browser.js +165966 -0
- package/playground/public/vendor/pagyra-playground-browser.js.map +7 -0
- package/playground/server.d.ts +1 -0
- package/playground/server.js +68 -0
- package/playground/server.ts +128 -0
- package/scripts/browser-build.ts +101 -0
- package/scripts/build-browser-bundle.ts +52 -0
- package/scripts/glyph-comparison/simulate.ts +744 -0
- package/scripts/playground-browser-server.ts +57 -0
- package/scripts/probe-roboto.ts +6 -0
- package/scripts/render-playground-example.ts +121 -0
- package/scripts/run-glyph-atlas-tuner-runner.mjs +113 -0
- package/scripts/run-glyph-atlas-tuner.ts +141 -0
- package/scripts/top-ts-files.ps1 +39 -0
- package/scripts/top-ts-files.sh +37 -0
- package/scripts/woff2_info.ps1 +132 -0
- package/src/browser-entry.ts +14 -0
- package/src/compression/adler32.ts +45 -0
- package/src/compression/brotli/brotli.ts +463 -0
- package/src/compression/brotli/index.ts +15 -0
- package/src/compression/brotli/transform.ts +184 -0
- package/src/compression/brotli/types.ts +58 -0
- package/src/compression/brotli/utils.ts +157 -0
- package/src/compression/brotli/vendor/bit_reader.js +124 -0
- package/src/compression/brotli/vendor/context.js +250 -0
- package/src/compression/brotli/vendor/decode.d.ts +2 -0
- package/src/compression/brotli/vendor/decode.js +938 -0
- package/src/compression/brotli/vendor/dictionary-data.js +9469 -0
- package/src/compression/brotli/vendor/dictionary.js +36 -0
- package/src/compression/brotli/vendor/huffman.js +123 -0
- package/src/compression/brotli/vendor/package.json +3 -0
- package/src/compression/brotli/vendor/prefix.js +60 -0
- package/src/compression/brotli/vendor/streams.js +31 -0
- package/src/compression/brotli/vendor/transform.js +247 -0
- package/src/compression/brotli/vendor-decode.d.ts +4 -0
- package/src/compression/brotli/woff2-glyf-transform.ts +623 -0
- package/src/compression/decompress.ts +16 -0
- package/src/compression/deflate.ts +295 -0
- package/src/compression/index.ts +4 -0
- package/src/compression/types.ts +26 -0
- package/src/compression/utils.ts +107 -0
- package/src/core.ts +18 -0
- package/src/css/apply-declarations.ts +86 -0
- package/src/css/background-types.ts +65 -0
- package/src/css/browser-defaults.ts +16 -0
- package/src/css/clip-path-types.ts +13 -0
- package/src/css/compute-style.ts +494 -0
- package/src/css/css-unit-resolver.ts +65 -0
- package/src/css/custom-properties.ts +215 -0
- package/src/css/enums.ts +127 -0
- package/src/css/font-face-parser.ts +233 -0
- package/src/css/font-weight.ts +65 -0
- package/src/css/inline-style-parser.ts +27 -0
- package/src/css/layout-property-resolver.ts +75 -0
- package/src/css/length.ts +141 -0
- package/src/css/line-height.ts +96 -0
- package/src/css/named-colors.ts +150 -0
- package/src/css/parsers/background-parser-extended.ts +111 -0
- package/src/css/parsers/background-parser.ts +456 -0
- package/src/css/parsers/border-block-parser.ts +26 -0
- package/src/css/parsers/border-inline-parser.ts +26 -0
- package/src/css/parsers/border-parser-extended.ts +256 -0
- package/src/css/parsers/border-parser.ts +175 -0
- package/src/css/parsers/box-shadow-parser.ts +106 -0
- package/src/css/parsers/clip-path-parser.ts +92 -0
- package/src/css/parsers/color-parser.ts +14 -0
- package/src/css/parsers/dimension-parser.ts +117 -0
- package/src/css/parsers/display-flex-parser.ts +59 -0
- package/src/css/parsers/flex-parser.ts +144 -0
- package/src/css/parsers/font-parser.ts +40 -0
- package/src/css/parsers/gradient-parser.ts +366 -0
- package/src/css/parsers/grid-parser-extended.ts +55 -0
- package/src/css/parsers/grid-parser.ts +218 -0
- package/src/css/parsers/length-parser.ts +95 -0
- package/src/css/parsers/list-style-parser.ts +39 -0
- package/src/css/parsers/margin-block-parser.ts +12 -0
- package/src/css/parsers/margin-inline-parser.ts +12 -0
- package/src/css/parsers/margin-parser.ts +30 -0
- package/src/css/parsers/opacity-parser.ts +32 -0
- package/src/css/parsers/overflow-wrap-parser.ts +38 -0
- package/src/css/parsers/padding-block-parser.ts +12 -0
- package/src/css/parsers/padding-inline-parser.ts +12 -0
- package/src/css/parsers/padding-parser.ts +30 -0
- package/src/css/parsers/position-parser.ts +75 -0
- package/src/css/parsers/register-parsers.ts +302 -0
- package/src/css/parsers/registry.ts +18 -0
- package/src/css/parsers/text-parser-extended.ts +144 -0
- package/src/css/parsers/text-parser.ts +25 -0
- package/src/css/parsers/text-shadow-parser.ts +94 -0
- package/src/css/properties/box-model.ts +82 -0
- package/src/css/properties/flexbox.ts +44 -0
- package/src/css/properties/gap.ts +14 -0
- package/src/css/properties/grid.ts +94 -0
- package/src/css/properties/layout.ts +59 -0
- package/src/css/properties/misc.ts +44 -0
- package/src/css/properties/typography.ts +71 -0
- package/src/css/properties/visual.ts +68 -0
- package/src/css/selectors/matcher.ts +219 -0
- package/src/css/selectors/parser.ts +163 -0
- package/src/css/selectors/simple-key.ts +31 -0
- package/src/css/selectors/specificity.ts +41 -0
- package/src/css/selectors/types.ts +31 -0
- package/src/css/shorthands/border-shorthand.ts +68 -0
- package/src/css/shorthands/box-shorthand.ts +33 -0
- package/src/css/style-inheritance.ts +50 -0
- package/src/css/style.ts +402 -0
- package/src/css/ua-defaults/base-defaults.ts +266 -0
- package/src/css/ua-defaults/browser-defaults.ts +134 -0
- package/src/css/ua-defaults/element-defaults.ts +374 -0
- package/src/css/ua-defaults/types.ts +43 -0
- package/src/css/unit-conversion.ts +24 -0
- package/src/css/utils.ts +108 -0
- package/src/css/viewport.ts +17 -0
- package/src/debug/audit.ts +20 -0
- package/src/debug/ids.ts +13 -0
- package/src/debug/log.js +28 -0
- package/src/debug/log.ts +52 -0
- package/src/debug/tree.ts +57 -0
- package/src/dom/node.ts +133 -0
- package/src/environment/browser-environment.ts +78 -0
- package/src/environment/environment.ts +35 -0
- package/src/environment/global.ts +13 -0
- package/src/environment/node-environment.browser.ts +28 -0
- package/src/environment/node-environment.ts +64 -0
- package/src/fonts/detector.ts +28 -0
- package/src/fonts/engines/ttf-engine.ts +28 -0
- package/src/fonts/engines/woff-engine.ts +38 -0
- package/src/fonts/engines/woff2-engine.ts +41 -0
- package/src/fonts/extractors/metrics-extractor.ts +362 -0
- package/src/fonts/font-registry-resolver.ts +132 -0
- package/src/fonts/index.ts +3 -0
- package/src/fonts/orchestrator.ts +92 -0
- package/src/fonts/parsers/base-parser.ts +23 -0
- package/src/fonts/types.ts +85 -0
- package/src/fonts/utils/ttf-reconstructor.ts +120 -0
- package/src/fonts/woff/decoder.ts +105 -0
- package/src/fonts/woff2/buffer.ts +106 -0
- package/src/fonts/woff2/decoder.ts +981 -0
- package/src/geometry/box.ts +48 -0
- package/src/geometry/matrix.ts +59 -0
- package/src/html/css/parse-css.ts +85 -0
- package/src/html/dom-converter.ts +433 -0
- package/src/html/image-converter.ts +200 -0
- package/src/html-to-pdf.ts +410 -0
- package/src/image/base-decoder.ts +149 -0
- package/src/image/image-service.ts +188 -0
- package/src/image/jpeg-decoder.ts +73 -0
- package/src/image/png-decoder.ts +550 -0
- package/src/image/types.ts +20 -0
- package/src/image/webp-decoder.ts +242 -0
- package/src/image/webp-huffman.ts +218 -0
- package/src/image/webp-riff-parser.ts +54 -0
- package/src/image/webp-vp8l-decoder.ts +199 -0
- package/src/index.ts +35 -0
- package/src/layout/context/float-context.ts +62 -0
- package/src/layout/context/layout-environment.ts +29 -0
- package/src/layout/debug.ts +18 -0
- package/src/layout/inline/bounding-box-calculator.ts +132 -0
- package/src/layout/inline/font-baseline-calculator.ts +76 -0
- package/src/layout/inline/inline-utils.ts +94 -0
- package/src/layout/inline/layout.ts +285 -0
- package/src/layout/inline/line_breaker.ts +109 -0
- package/src/layout/inline/measurement.ts +144 -0
- package/src/layout/inline/run-placer.ts +139 -0
- package/src/layout/inline/text-alignment.ts +70 -0
- package/src/layout/inline/tokenizer.ts +195 -0
- package/src/layout/inline/types.ts +76 -0
- package/src/layout/pipeline/context-factory.ts +16 -0
- package/src/layout/pipeline/default-engine.ts +24 -0
- package/src/layout/pipeline/engine.ts +59 -0
- package/src/layout/pipeline/layout-tree.ts +13 -0
- package/src/layout/pipeline/out-of-flow-manager.ts +73 -0
- package/src/layout/pipeline/strategy.ts +12 -0
- package/src/layout/pipeline/text-metrics-initializer.ts +13 -0
- package/src/layout/strategies/block.ts +236 -0
- package/src/layout/strategies/display-none.ts +14 -0
- package/src/layout/strategies/fallback.ts +15 -0
- package/src/layout/strategies/flex.ts +477 -0
- package/src/layout/strategies/fragmentation.ts +17 -0
- package/src/layout/strategies/grid.ts +247 -0
- package/src/layout/strategies/image.ts +342 -0
- package/src/layout/strategies/inline.ts +128 -0
- package/src/layout/strategies/table.ts +595 -0
- package/src/layout/table/cell_layout.ts +31 -0
- package/src/layout/table/diagnostics.ts +19 -0
- package/src/layout/text-run.ts +42 -0
- package/src/layout/utils/content-measurer.ts +117 -0
- package/src/layout/utils/display-utils.ts +24 -0
- package/src/layout/utils/floats.ts +98 -0
- package/src/layout/utils/gap-calculator.ts +167 -0
- package/src/layout/utils/inline-formatter.ts +31 -0
- package/src/layout/utils/inline-formatting.ts +9 -0
- package/src/layout/utils/margin.ts +140 -0
- package/src/layout/utils/node-math.ts +237 -0
- package/src/layout/utils/overflow.ts +14 -0
- package/src/layout/utils/sizing.ts +12 -0
- package/src/layout/utils/text-metrics.ts +361 -0
- package/src/logging/debug.ts +58 -0
- package/src/pdf/font/base14/widths-courier-bold.ts +159 -0
- package/src/pdf/font/base14/widths-courier.ts +159 -0
- package/src/pdf/font/base14/widths-helvetica-bold.ts +158 -0
- package/src/pdf/font/base14/widths-helvetica.ts +158 -0
- package/src/pdf/font/base14/widths-times-bold.ts +158 -0
- package/src/pdf/font/base14/widths-times-roman.ts +158 -0
- package/src/pdf/font/base14/widths-types.ts +25 -0
- package/src/pdf/font/base14-widths.ts +32 -0
- package/src/pdf/font/blur.ts +81 -0
- package/src/pdf/font/builtin-fonts.browser.ts +262 -0
- package/src/pdf/font/builtin-fonts.ts +126 -0
- package/src/pdf/font/composite-glyph-parser.ts +242 -0
- package/src/pdf/font/embedder.ts +395 -0
- package/src/pdf/font/font-config.ts +190 -0
- package/src/pdf/font/font-registry.ts +263 -0
- package/src/pdf/font/font-subset.ts +258 -0
- package/src/pdf/font/glyph-atlas-maxrects.ts +305 -0
- package/src/pdf/font/glyph-atlas-tuner.ts +98 -0
- package/src/pdf/font/glyph-atlas.ts +226 -0
- package/src/pdf/font/glyph-cache.ts +127 -0
- package/src/pdf/font/loca-reader.ts +109 -0
- package/src/pdf/font/managers/font-resource-manager.ts +73 -0
- package/src/pdf/font/managers/subset-resource-manager.ts +164 -0
- package/src/pdf/font/rasterizer.ts +270 -0
- package/src/pdf/font/resolvers/base-font-mapper.ts +77 -0
- package/src/pdf/font/resolvers/family-resolver.ts +33 -0
- package/src/pdf/font/resolvers/weight-style-applicator.ts +63 -0
- package/src/pdf/font/simple-glyph-parser.ts +289 -0
- package/src/pdf/font/to-unicode.ts +109 -0
- package/src/pdf/font/transformation-matrix.ts +136 -0
- package/src/pdf/font/ttf-cmap.ts +180 -0
- package/src/pdf/font/ttf-global-metrics.ts +58 -0
- package/src/pdf/font/ttf-glyf.ts +125 -0
- package/src/pdf/font/ttf-glyph-metrics.ts +43 -0
- package/src/pdf/font/ttf-lite.ts +269 -0
- package/src/pdf/font/ttf-table-parser.ts +132 -0
- package/src/pdf/font/ttf-table-provider.ts +61 -0
- package/src/pdf/font/widths.ts +79 -0
- package/src/pdf/font-subset/font-registry.ts +127 -0
- package/src/pdf/header-footer-layout.ts +153 -0
- package/src/pdf/header-footer-painter.ts +209 -0
- package/src/pdf/header-footer-renderer.ts +357 -0
- package/src/pdf/header-footer-tokens.ts +55 -0
- package/src/pdf/header-footer.ts +25 -0
- package/src/pdf/layout-tree-builder.ts +261 -0
- package/src/pdf/page-painter.ts +241 -0
- package/src/pdf/pagination.ts +155 -0
- package/src/pdf/primitives/pdf-builder.ts +378 -0
- package/src/pdf/primitives/pdf-bytes.ts +40 -0
- package/src/pdf/primitives/pdf-document.ts +108 -0
- package/src/pdf/primitives/pdf-reference-manager.ts +47 -0
- package/src/pdf/primitives/pdf-resource-registries.ts +255 -0
- package/src/pdf/primitives/pdf-serializers.ts +194 -0
- package/src/pdf/primitives/pdf-types.ts +73 -0
- package/src/pdf/render.ts +210 -0
- package/src/pdf/renderer/box-painter.ts +236 -0
- package/src/pdf/renderer/page-paint.ts +102 -0
- package/src/pdf/renderer/paint-box-shadows.ts +218 -0
- package/src/pdf/renderer/radius.ts +58 -0
- package/src/pdf/renderers/graphics-state-manager.ts +40 -0
- package/src/pdf/renderers/image-renderer.ts +127 -0
- package/src/pdf/renderers/radius-utils.ts +80 -0
- package/src/pdf/renderers/rectangle-renderer.ts +129 -0
- package/src/pdf/renderers/rounded-rect-path.ts +120 -0
- package/src/pdf/renderers/shape-renderer.ts +563 -0
- package/src/pdf/renderers/shape-utils.ts +194 -0
- package/src/pdf/renderers/text-decoration-renderer.ts +313 -0
- package/src/pdf/renderers/text-encoder.ts +41 -0
- package/src/pdf/renderers/text-font-resolver.ts +75 -0
- package/src/pdf/renderers/text-renderer-utils.ts +28 -0
- package/src/pdf/renderers/text-renderer.ts +391 -0
- package/src/pdf/renderers/text-shadow-renderer.ts +300 -0
- package/src/pdf/shading/gradient-service.ts +525 -0
- package/src/pdf/shading/index.ts +1 -0
- package/src/pdf/stacking/build-stacking-contexts.ts +93 -0
- package/src/pdf/stacking/resolve-paint-order.ts +157 -0
- package/src/pdf/stacking/types.ts +40 -0
- package/src/pdf/svg/aspect-ratio.ts +81 -0
- package/src/pdf/svg/coordinate-mapper.ts +81 -0
- package/src/pdf/svg/geometry-builder.ts +45 -0
- package/src/pdf/svg/render-svg.ts +296 -0
- package/src/pdf/svg/shape-renderer.ts +463 -0
- package/src/pdf/svg/style-computer.ts +246 -0
- package/src/pdf/transform-adapter.ts +26 -0
- package/src/pdf/types.ts +377 -0
- package/src/pdf/utils/background-layer-resolver.ts +439 -0
- package/src/pdf/utils/background-tiles.ts +192 -0
- package/src/pdf/utils/border-dashes.ts +109 -0
- package/src/pdf/utils/border-radius-utils.ts +86 -0
- package/src/pdf/utils/box-dimensions-utils.ts +47 -0
- package/src/pdf/utils/clip-path-resolver.ts +50 -0
- package/src/pdf/utils/clipping-path-builder.ts +190 -0
- package/src/pdf/utils/color-utils.ts +102 -0
- package/src/pdf/utils/coordinate-transformer.ts +30 -0
- package/src/pdf/utils/drop-shadow-raster.ts +233 -0
- package/src/pdf/utils/encoding.ts +98 -0
- package/src/pdf/utils/glyph-atlas-registrar.ts +13 -0
- package/src/pdf/utils/glyph-run-renderer.ts +129 -0
- package/src/pdf/utils/image-command-partitioner.ts +28 -0
- package/src/pdf/utils/image-matrix-builder.ts +31 -0
- package/src/pdf/utils/image-utils.ts +26 -0
- package/src/pdf/utils/list-utils.ts +194 -0
- package/src/pdf/utils/node-text-run-factory.ts +202 -0
- package/src/pdf/utils/page-resource-registrar.ts +46 -0
- package/src/pdf/utils/result-combiner.ts +102 -0
- package/src/pdf/utils/shadow-utils.ts +127 -0
- package/src/pdf/utils/text-alignment-resolver.ts +76 -0
- package/src/pdf/utils/text-decoration-utils.ts +64 -0
- package/src/pdf/utils/text-layout-adjuster.ts +185 -0
- package/src/pdf/utils/text-utils.ts +193 -0
- package/src/pdf/utils/transform-scope-manager.ts +69 -0
- package/src/render/offset.ts +170 -0
- package/src/shim/empty.ts +2 -0
- package/src/shim/fs-empty.ts +5 -0
- package/src/shim/url-empty.ts +7 -0
- package/src/shim/zlib-empty.ts +9 -0
- package/src/style/shorthands/index.ts +19 -0
- package/src/style/ua/defaults.ts +69 -0
- package/src/svg/index.ts +4 -0
- package/src/svg/parser-registry.ts +71 -0
- package/src/svg/parser.ts +486 -0
- package/src/svg/path-data.ts +515 -0
- package/src/svg/types.ts +194 -0
- package/src/text/line-breaker.ts +321 -0
- package/src/text/text-transform.ts +43 -0
- package/src/text/text.ts +33 -0
- package/src/transform/css-parser.ts +95 -0
- package/src/types/fonts.ts +62 -0
- package/src/types/public.ts +19 -0
- package/src/units/page-utils.ts +58 -0
- package/src/units/units.ts +50 -0
- package/src/utils/base64.ts +24 -0
- package/test-output.txt +79 -0
- package/tests/css/background-parser.spec.ts +14 -0
- package/tests/css/clip-path-parser.spec.ts +66 -0
- package/tests/environment/path-resolution.spec.ts +104 -0
- package/tests/helpers/ai-layout-diagnostics.ts +141 -0
- package/tests/helpers/render-utils.ts +52 -0
- package/tests/helpers/text-geometry.ts +56 -0
- package/tests/layout/custom-properties.test.ts +38 -0
- package/tests/layout/gap-calculator.spec.ts +196 -0
- package/tests/layout/inline-background-alignment.spec.ts +93 -0
- package/tests/layout/inline-fragments.spec.ts +26 -0
- package/tests/layout/run-placer-baseline.spec.ts +108 -0
- package/tests/pdf/alignments.spec.ts +26 -0
- package/tests/pdf/background-clip.spec.ts +57 -0
- package/tests/pdf/background-repeat-space-round.spec.ts +35 -0
- package/tests/pdf/background-repeat.spec.ts +137 -0
- package/tests/pdf/border-radius.spec.ts +151 -0
- package/tests/pdf/clip-path.spec.ts +92 -0
- package/tests/pdf/radial-gradient.spec.ts +50 -0
- package/tests/pdf/svg-stroke-dash.spec.ts +81 -0
- package/tests/pdf/text-transform-matrix.spec.ts +43 -0
- package/tsconfig.json +17 -0
- package/types/fonts.js +10 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// src/html/image-converter.ts
|
|
2
|
+
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { type DomEl, type CssRuleEntry } from "./css/parse-css.js";
|
|
5
|
+
import { LayoutNode } from "../dom/node.js";
|
|
6
|
+
import { ComputedStyle } from "../css/style.js";
|
|
7
|
+
import { computeStyleForElement } from "../css/compute-style.js";
|
|
8
|
+
import { ImageService } from "../image/image-service.js";
|
|
9
|
+
import { ImageStrategy } from "../layout/strategies/image.js";
|
|
10
|
+
import type { ImageInfo } from "../image/types.js";
|
|
11
|
+
import type { UnitParsers } from "../units/units.js";
|
|
12
|
+
import { log } from "../logging/debug.js";
|
|
13
|
+
import { decodeBase64ToUint8Array } from "../utils/base64.js";
|
|
14
|
+
|
|
15
|
+
// The ConversionContext should be defined where it's used
|
|
16
|
+
export interface ConversionContext {
|
|
17
|
+
resourceBaseDir: string;
|
|
18
|
+
assetRootDir: string;
|
|
19
|
+
units: UnitParsers;
|
|
20
|
+
rootFontSize: number;
|
|
21
|
+
environment?: import("../environment/environment.js").Environment;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function resolveImageSource(src: string, context: ConversionContext): string {
|
|
25
|
+
const trimmed = src.trim();
|
|
26
|
+
if (!trimmed) {
|
|
27
|
+
return trimmed;
|
|
28
|
+
}
|
|
29
|
+
if (/^data:/i.test(trimmed)) {
|
|
30
|
+
return trimmed;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const url = new URL(trimmed);
|
|
34
|
+
if (url.protocol === "file:") {
|
|
35
|
+
return url.href;
|
|
36
|
+
}
|
|
37
|
+
return url.href;
|
|
38
|
+
} catch {
|
|
39
|
+
// Not an absolute URL, fall through to filesystem resolution
|
|
40
|
+
}
|
|
41
|
+
if (context.environment?.resolveLocal) {
|
|
42
|
+
return context.environment.resolveLocal(trimmed, context.resourceBaseDir || context.assetRootDir || undefined);
|
|
43
|
+
}
|
|
44
|
+
if (trimmed.startsWith("/")) {
|
|
45
|
+
const resolved = context.assetRootDir ? path.resolve(context.assetRootDir, `.${trimmed}`) : trimmed;
|
|
46
|
+
log('image-converter', 'debug', "resolveImageSource - resolving absolute path:", { src, trimmed, assetRootDir: context.assetRootDir, resolved });
|
|
47
|
+
return resolved;
|
|
48
|
+
}
|
|
49
|
+
if (path.isAbsolute(trimmed)) {
|
|
50
|
+
return trimmed;
|
|
51
|
+
}
|
|
52
|
+
return context.resourceBaseDir ? path.resolve(context.resourceBaseDir, trimmed) : trimmed;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isHttpUrl(value: string): boolean {
|
|
56
|
+
return /^https?:\/\//i.test(value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function parseHttpUrl(value?: string): URL | null {
|
|
60
|
+
if (!value) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const url = new URL(value);
|
|
65
|
+
if (url.protocol === "http:" || url.protocol === "https:") {
|
|
66
|
+
return url;
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// Ignore parse failures; only interested in HTTP(S) bases.
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function collectAllowedOrigins(context: ConversionContext): URL[] {
|
|
75
|
+
const origins = new Map<string, URL>();
|
|
76
|
+
const candidates = [context.assetRootDir, context.resourceBaseDir];
|
|
77
|
+
for (const candidate of candidates) {
|
|
78
|
+
const parsed = parseHttpUrl(candidate);
|
|
79
|
+
if (parsed) {
|
|
80
|
+
const key = parsed.origin;
|
|
81
|
+
if (!origins.has(key)) {
|
|
82
|
+
origins.set(key, parsed);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return Array.from(origins.values());
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function canLoadHttpResource(url: string, context: ConversionContext): boolean {
|
|
90
|
+
if (!isHttpUrl(url)) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const target = new URL(url);
|
|
95
|
+
if (target.protocol !== "http:" && target.protocol !== "https:") {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const allowed = collectAllowedOrigins(context);
|
|
99
|
+
return allowed.some((origin) => origin.origin === target.origin);
|
|
100
|
+
} catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function convertImageElement(
|
|
106
|
+
element: DomEl,
|
|
107
|
+
cssRules: CssRuleEntry[],
|
|
108
|
+
parentStyle: ComputedStyle,
|
|
109
|
+
context: ConversionContext,
|
|
110
|
+
): Promise<LayoutNode> {
|
|
111
|
+
const style = computeStyleForElement(element, cssRules, parentStyle, context.units, context.rootFontSize);
|
|
112
|
+
// SVG <image> elements commonly use 'href' or 'xlink:href'; HTML <img> uses 'src'.
|
|
113
|
+
const rawSrc = element.getAttribute("href") ?? element.getAttribute("xlink:href") ?? element.getAttribute("src") ?? "";
|
|
114
|
+
const srcAttr = rawSrc?.trim() ?? "";
|
|
115
|
+
|
|
116
|
+
const widthAttr = element.getAttribute("width");
|
|
117
|
+
const heightAttr = element.getAttribute("height");
|
|
118
|
+
const width = widthAttr ? Number.parseFloat(widthAttr) || undefined : undefined;
|
|
119
|
+
const height = heightAttr ? Number.parseFloat(heightAttr) || undefined : undefined;
|
|
120
|
+
|
|
121
|
+
if (!srcAttr) {
|
|
122
|
+
const placeholder = new LayoutNode(style, [], { tagName: "img" });
|
|
123
|
+
placeholder.intrinsicInlineSize = width ?? 100;
|
|
124
|
+
placeholder.intrinsicBlockSize = height ?? 100;
|
|
125
|
+
return placeholder;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const resolvedSrc = resolveImageSource(srcAttr, context);
|
|
129
|
+
|
|
130
|
+
let imageInfo: ImageInfo;
|
|
131
|
+
try {
|
|
132
|
+
const imageService = ImageService.getInstance(context.environment);
|
|
133
|
+
if (isHttpUrl(resolvedSrc) && !canLoadHttpResource(resolvedSrc, context)) {
|
|
134
|
+
throw new Error(`Remote images are not supported (${resolvedSrc})`);
|
|
135
|
+
}
|
|
136
|
+
if (resolvedSrc.startsWith("data:")) {
|
|
137
|
+
const match = resolvedSrc.match(/^data:image\/(.+);base64,(.+)$/);
|
|
138
|
+
if (!match) {
|
|
139
|
+
throw new Error("Invalid data URI");
|
|
140
|
+
}
|
|
141
|
+
const bytes = decodeBase64ToUint8Array(match[2]);
|
|
142
|
+
const copy = bytes.slice();
|
|
143
|
+
imageInfo = await imageService.decodeImage(copy.buffer, {
|
|
144
|
+
maxWidth: width,
|
|
145
|
+
maxHeight: height,
|
|
146
|
+
});
|
|
147
|
+
} else {
|
|
148
|
+
imageInfo = await imageService.loadImage(resolvedSrc, {
|
|
149
|
+
maxWidth: width,
|
|
150
|
+
maxHeight: height,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
log("render-tree", "debug", "Image loaded successfully", {
|
|
155
|
+
src: srcAttr,
|
|
156
|
+
resolvedSrc,
|
|
157
|
+
width: imageInfo.width,
|
|
158
|
+
height: imageInfo.height,
|
|
159
|
+
format: imageInfo.format,
|
|
160
|
+
});
|
|
161
|
+
} catch (error) {
|
|
162
|
+
log("render-tree", "warn", `Failed to load image: ${srcAttr}. Using placeholder.`, {
|
|
163
|
+
resolvedSrc,
|
|
164
|
+
error: error instanceof Error ? error.message : String(error),
|
|
165
|
+
});
|
|
166
|
+
const placeholder = new LayoutNode(style, [], { tagName: "img" });
|
|
167
|
+
placeholder.intrinsicInlineSize = width ?? 100;
|
|
168
|
+
placeholder.intrinsicBlockSize = height ?? 100;
|
|
169
|
+
return placeholder;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const layoutNode = new LayoutNode(style, [], {
|
|
173
|
+
tagName: "img",
|
|
174
|
+
customData: {
|
|
175
|
+
image: {
|
|
176
|
+
originalSrc: srcAttr,
|
|
177
|
+
resolvedSrc,
|
|
178
|
+
info: imageInfo,
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
layoutNode.intrinsicInlineSize = imageInfo.width;
|
|
184
|
+
layoutNode.intrinsicBlockSize = imageInfo.height;
|
|
185
|
+
|
|
186
|
+
ImageStrategy.processImage(layoutNode, imageInfo);
|
|
187
|
+
|
|
188
|
+
if (width && height) {
|
|
189
|
+
layoutNode.intrinsicInlineSize = width;
|
|
190
|
+
layoutNode.intrinsicBlockSize = height;
|
|
191
|
+
} else if (width) {
|
|
192
|
+
layoutNode.intrinsicInlineSize = width;
|
|
193
|
+
layoutNode.intrinsicBlockSize = Math.round((imageInfo.height / imageInfo.width) * width);
|
|
194
|
+
} else if (height) {
|
|
195
|
+
layoutNode.intrinsicBlockSize = height;
|
|
196
|
+
layoutNode.intrinsicInlineSize = Math.round((imageInfo.width / imageInfo.height) * height);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return layoutNode;
|
|
200
|
+
}
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
// src/html-to-pdf.ts
|
|
2
|
+
|
|
3
|
+
import { parseHTML } from "linkedom";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
+
import { type FontConfig } from "./types/fonts.js";
|
|
7
|
+
import { configureDebug, log, type LogLevel } from "./logging/debug.js";
|
|
8
|
+
import { parseCss } from "./html/css/parse-css.js";
|
|
9
|
+
import { makeUnitParsers, type UnitCtx, pxToPt } from "./units/units.js";
|
|
10
|
+
import { LayoutNode } from "./dom/node.js";
|
|
11
|
+
import { ComputedStyle } from "./css/style.js";
|
|
12
|
+
import { layoutTree } from "./layout/pipeline/layout-tree.js";
|
|
13
|
+
import { buildRenderTree } from "./pdf/layout-tree-builder.js";
|
|
14
|
+
import { renderPdf } from "./pdf/render.js";
|
|
15
|
+
import { convertDomNode } from "./html/dom-converter.js";
|
|
16
|
+
import { applyPageVerticalMarginsWithHf, offsetRenderTree } from "./render/offset.js";
|
|
17
|
+
import { applyTextLayoutAdjustments } from "./pdf/utils/text-layout-adjuster.js";
|
|
18
|
+
import { setViewportSize } from "./css/apply-declarations.js";
|
|
19
|
+
import { type PageMarginsPx } from "./units/page-utils.js";
|
|
20
|
+
import { computeStyleForElement } from "./css/compute-style.js";
|
|
21
|
+
import type { HeaderFooterHTML } from "./pdf/types.js";
|
|
22
|
+
import { FontEmbedder } from "./pdf/font/embedder.js";
|
|
23
|
+
import { PdfDocument } from "./pdf/primitives/pdf-document.js";
|
|
24
|
+
import { loadBuiltinFontConfig } from "./pdf/font/builtin-fonts.js";
|
|
25
|
+
import { Display } from "./css/enums.js";
|
|
26
|
+
import type { Environment } from "./environment/environment.js";
|
|
27
|
+
import { NodeEnvironment } from "./environment/node-environment.js";
|
|
28
|
+
import { decodeBase64ToUint8Array } from "./utils/base64.js";
|
|
29
|
+
|
|
30
|
+
export interface RenderHtmlOptions {
|
|
31
|
+
html: string;
|
|
32
|
+
css: string;
|
|
33
|
+
viewportWidth: number;
|
|
34
|
+
viewportHeight: number;
|
|
35
|
+
pageWidth: number;
|
|
36
|
+
pageHeight: number;
|
|
37
|
+
margins: PageMarginsPx;
|
|
38
|
+
debug?: boolean;
|
|
39
|
+
debugLevel?: LogLevel;
|
|
40
|
+
debugCats?: string[];
|
|
41
|
+
fontConfig?: FontConfig;
|
|
42
|
+
resourceBaseDir?: string;
|
|
43
|
+
assetRootDir?: string;
|
|
44
|
+
headerFooter?: Partial<HeaderFooterHTML>;
|
|
45
|
+
/** Environment abstraction (Node/browser). Defaults to Node implementation. */
|
|
46
|
+
environment?: Environment;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface PreparedRender {
|
|
50
|
+
layoutRoot: LayoutNode;
|
|
51
|
+
renderTree: ReturnType<typeof buildRenderTree>;
|
|
52
|
+
pageSize: { widthPt: number; heightPt: number };
|
|
53
|
+
margins: PageMarginsPx;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function renderHtmlToPdf(options: RenderHtmlOptions): Promise<Uint8Array> {
|
|
57
|
+
const environment = options.environment ?? new NodeEnvironment(options.assetRootDir ?? options.resourceBaseDir);
|
|
58
|
+
const resolvedFontConfig = options.fontConfig ?? (await loadBuiltinFontConfig(environment));
|
|
59
|
+
const preparedOptions = resolvedFontConfig ? { ...options, fontConfig: resolvedFontConfig, environment } : { ...options, environment };
|
|
60
|
+
const prepared = await prepareHtmlRender(preparedOptions);
|
|
61
|
+
return renderPdf(prepared.renderTree, {
|
|
62
|
+
pageSize: prepared.pageSize,
|
|
63
|
+
fontConfig: resolvedFontConfig ?? undefined,
|
|
64
|
+
margins: prepared.margins,
|
|
65
|
+
environment,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function prepareHtmlRender(options: RenderHtmlOptions): Promise<PreparedRender> {
|
|
70
|
+
const { html, css, viewportWidth, viewportHeight, pageWidth, pageHeight, margins, debug = false, debugLevel, debugCats, headerFooter } =
|
|
71
|
+
options;
|
|
72
|
+
const normalizedHtml = normalizeHtmlInput(html);
|
|
73
|
+
|
|
74
|
+
setViewportSize(viewportWidth, viewportHeight);
|
|
75
|
+
const isNode = typeof process !== "undefined" && !!process.versions?.node;
|
|
76
|
+
const resourceBaseDir = options.resourceBaseDir ?? options.assetRootDir ?? "";
|
|
77
|
+
const assetRootDir = options.assetRootDir ?? resourceBaseDir;
|
|
78
|
+
const environment = options.environment ?? new NodeEnvironment(assetRootDir);
|
|
79
|
+
|
|
80
|
+
if (debugLevel || debugCats) {
|
|
81
|
+
configureDebug({ level: debugLevel ?? (debug ? "debug" : "info"), cats: debugCats });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const unitCtx: UnitCtx = { viewport: { width: viewportWidth, height: viewportHeight } };
|
|
85
|
+
const units = makeUnitParsers(unitCtx);
|
|
86
|
+
|
|
87
|
+
log('html-to-pdf', 'debug', `prepareHtmlRender - input html: ${html}`);
|
|
88
|
+
let { document } = parseHTML(normalizedHtml);
|
|
89
|
+
if (needsReparse(document)) {
|
|
90
|
+
document = parseHTML(wrapHtml(html)).document;
|
|
91
|
+
}
|
|
92
|
+
log('html-to-pdf', 'debug', `prepareHtmlRender - parsed document body: ${document.body?.innerHTML || 'no body'}`);
|
|
93
|
+
log('html-to-pdf', 'debug', `prepareHtmlRender - document.documentElement tagName: ${document.documentElement?.tagName}`);
|
|
94
|
+
log('html-to-pdf', 'debug', `prepareHtmlRender - document.documentElement innerHTML: ${document.documentElement?.innerHTML}`);
|
|
95
|
+
log('html-to-pdf', 'debug', `prepareHtmlRender - document children count: ${document.childNodes.length}`);
|
|
96
|
+
for (let i = 0; i < document.childNodes.length; i++) {
|
|
97
|
+
const child = document.childNodes[i];
|
|
98
|
+
log('html-to-pdf', 'debug', `prepareHtmlRender - document child ${i}: ${child.nodeType}, ${(child as any).tagName || 'text node'}`);
|
|
99
|
+
}
|
|
100
|
+
log("parse", "debug", "DOM parsed", { hasBody: !!document.body });
|
|
101
|
+
|
|
102
|
+
let mergedCss = css || "";
|
|
103
|
+
const styleTags = Array.from(document.querySelectorAll("style"));
|
|
104
|
+
for (const styleTag of styleTags) {
|
|
105
|
+
if (styleTag.textContent) mergedCss += "\n" + styleTag.textContent;
|
|
106
|
+
}
|
|
107
|
+
const linkTags = Array.from(document.querySelectorAll("link")).filter((link) => (link.getAttribute("rel") || "").toLowerCase() === "stylesheet");
|
|
108
|
+
for (const linkTag of linkTags) {
|
|
109
|
+
const href = linkTag.getAttribute("href");
|
|
110
|
+
if (!href) continue;
|
|
111
|
+
const cssText = await loadStylesheetFromHref(href, resourceBaseDir, assetRootDir, environment);
|
|
112
|
+
if (cssText) mergedCss += "\n" + cssText;
|
|
113
|
+
}
|
|
114
|
+
const { styleRules: cssRules, fontFaceRules } = parseCss(mergedCss);
|
|
115
|
+
log("parse", "debug", "CSS rules", { count: cssRules.length, fontFaces: fontFaceRules.length });
|
|
116
|
+
|
|
117
|
+
// Determine the root element to process - prefer body, but fall back to documentElement if body is empty
|
|
118
|
+
let rootElement = document.body;
|
|
119
|
+
let processChildrenOf = rootElement;
|
|
120
|
+
|
|
121
|
+
// If body is empty but documentElement has children (like when HTML is just a fragment),
|
|
122
|
+
// process the documentElement's children instead
|
|
123
|
+
if (rootElement && rootElement.childNodes.length === 0) {
|
|
124
|
+
// Check if documentElement has meaningful children
|
|
125
|
+
const meaningfulChildren = Array.from(document.documentElement.childNodes).filter(node => {
|
|
126
|
+
return node.nodeType === node.ELEMENT_NODE && (node as HTMLElement).tagName !== 'HEAD';
|
|
127
|
+
});
|
|
128
|
+
if (meaningfulChildren.length > 0) {
|
|
129
|
+
processChildrenOf = document.documentElement;
|
|
130
|
+
}
|
|
131
|
+
} else if (!rootElement) {
|
|
132
|
+
// If there's no body element, process documentElement children directly
|
|
133
|
+
processChildrenOf = document.documentElement;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const baseParentStyle = new ComputedStyle();
|
|
137
|
+
const htmlElement = document.documentElement;
|
|
138
|
+
const documentElementStyle = htmlElement
|
|
139
|
+
? computeStyleForElement(htmlElement, cssRules, baseParentStyle, units, baseParentStyle.fontSize)
|
|
140
|
+
: baseParentStyle;
|
|
141
|
+
const rootFontSize = documentElementStyle.fontSize;
|
|
142
|
+
|
|
143
|
+
let rootStyle: ComputedStyle;
|
|
144
|
+
if (!processChildrenOf || processChildrenOf === htmlElement) {
|
|
145
|
+
rootStyle = documentElementStyle;
|
|
146
|
+
} else {
|
|
147
|
+
rootStyle = computeStyleForElement(processChildrenOf, cssRules, documentElementStyle, units, rootFontSize);
|
|
148
|
+
}
|
|
149
|
+
if (isInlineDisplay(rootStyle.display)) {
|
|
150
|
+
rootStyle.display = Display.Block;
|
|
151
|
+
}
|
|
152
|
+
const rootLayout = new LayoutNode(rootStyle, [], { tagName: processChildrenOf?.tagName?.toLowerCase() });
|
|
153
|
+
|
|
154
|
+
const conversionContext = { resourceBaseDir, assetRootDir, units, rootFontSize, environment };
|
|
155
|
+
|
|
156
|
+
if (processChildrenOf) {
|
|
157
|
+
log('html-to-pdf', 'debug', `prepareHtmlRender - processing children of: ${processChildrenOf.tagName}, count: ${processChildrenOf.childNodes.length}`);
|
|
158
|
+
for (const child of Array.from(processChildrenOf.childNodes)) {
|
|
159
|
+
log('html-to-pdf', 'debug', `prepareHtmlRender - processing child: ${(child as any).tagName || 'text node'}, type: ${child.nodeType}`);
|
|
160
|
+
// Skip head and other non-content elements
|
|
161
|
+
if (child.nodeType === child.ELEMENT_NODE) {
|
|
162
|
+
const tagName = (child as HTMLElement).tagName.toLowerCase();
|
|
163
|
+
if (tagName === 'head' || tagName === 'meta' || tagName === 'title' || tagName === 'link' || tagName === 'script') {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const layoutChild = await convertDomNode(child, cssRules, rootStyle, conversionContext);
|
|
168
|
+
if (layoutChild) rootLayout.appendChild(layoutChild);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const pdfDoc = new PdfDocument();
|
|
173
|
+
|
|
174
|
+
// Process @font-face rules
|
|
175
|
+
for (const fontFace of fontFaceRules) {
|
|
176
|
+
const fontFamily = fontFace.declarations['font-family']?.replace(/['"]/g, '');
|
|
177
|
+
const src = fontFace.declarations['src'];
|
|
178
|
+
if (fontFamily && src) {
|
|
179
|
+
const targetUrl = pickFontUrlFromSrc(src);
|
|
180
|
+
if (targetUrl && options.fontConfig) {
|
|
181
|
+
const fontData = await loadFontData(targetUrl, resourceBaseDir, assetRootDir, environment);
|
|
182
|
+
if (fontData) {
|
|
183
|
+
const weightStr = fontFace.declarations["font-weight"] || "400";
|
|
184
|
+
const styleStr = fontFace.declarations["font-style"] || "normal";
|
|
185
|
+
options.fontConfig.fontFaceDefs.push({
|
|
186
|
+
name: fontFamily,
|
|
187
|
+
family: fontFamily,
|
|
188
|
+
src: targetUrl,
|
|
189
|
+
data: fontData,
|
|
190
|
+
weight: parseFontWeight(weightStr),
|
|
191
|
+
style: normalizeFontStyle(styleStr),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// In a browser environment, the font data should be pre-loaded and passed in the fontConfig.
|
|
199
|
+
if (options.fontConfig) {
|
|
200
|
+
for (const face of options.fontConfig.fontFaceDefs) {
|
|
201
|
+
if (!face.data && face.src) {
|
|
202
|
+
const loaded = await loadFontData(face.src, resourceBaseDir, assetRootDir, environment);
|
|
203
|
+
if (loaded) {
|
|
204
|
+
(face as any).data = loaded;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const fontEmbedder = options.fontConfig ? new FontEmbedder(options.fontConfig, pdfDoc) : null;
|
|
210
|
+
if (fontEmbedder) {
|
|
211
|
+
await fontEmbedder.initialize();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
layoutTree(rootLayout, { width: viewportWidth, height: viewportHeight }, fontEmbedder);
|
|
215
|
+
log("layout", "debug", "Layout complete");
|
|
216
|
+
|
|
217
|
+
const renderTree = buildRenderTree(rootLayout, { headerFooter });
|
|
218
|
+
|
|
219
|
+
// Global justification pass: fix inline run positions for text-align: justify
|
|
220
|
+
applyTextLayoutAdjustments(renderTree.root);
|
|
221
|
+
|
|
222
|
+
// Get header/footer heights for proper content positioning
|
|
223
|
+
const headerHeightPx = headerFooter?.maxHeaderHeightPx ?? 0;
|
|
224
|
+
const footerHeightPx = headerFooter?.maxFooterHeightPx ?? 0;
|
|
225
|
+
|
|
226
|
+
// Apply vertical margins with header/footer space reserved
|
|
227
|
+
// This pushes content below the header and reserves space for the footer
|
|
228
|
+
applyPageVerticalMarginsWithHf(renderTree.root, {
|
|
229
|
+
pageHeight,
|
|
230
|
+
margins,
|
|
231
|
+
headerHeightPx,
|
|
232
|
+
footerHeightPx,
|
|
233
|
+
});
|
|
234
|
+
offsetRenderTree(renderTree.root, margins.left, 0, debug);
|
|
235
|
+
|
|
236
|
+
const pageSize = { widthPt: pxToPt(pageWidth), heightPt: pxToPt(pageHeight) };
|
|
237
|
+
return { layoutRoot: rootLayout, renderTree, pageSize, margins };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function loadStylesheetFromHref(href: string, resourceBaseDir: string, assetRootDir: string, environment: Environment): Promise<string> {
|
|
241
|
+
const trimmed = href.trim();
|
|
242
|
+
if (!trimmed) return "";
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
if (/^https?:\/\//i.test(trimmed) || trimmed.startsWith("//")) {
|
|
246
|
+
const absoluteHref = trimmed.startsWith("//") ? `https:${trimmed}` : trimmed;
|
|
247
|
+
const response = await fetch(absoluteHref);
|
|
248
|
+
if (!response.ok) {
|
|
249
|
+
throw new Error(`HTTP ${response.status}`);
|
|
250
|
+
}
|
|
251
|
+
const cssText = await response.text();
|
|
252
|
+
return rewriteCssUrls(cssText, absoluteHref);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const cssPath = environment.resolveLocal ? environment.resolveLocal(trimmed, resourceBaseDir) : resolveLocalPath(trimmed, resourceBaseDir, assetRootDir);
|
|
256
|
+
const cssBuffer = await environment.loader.load(cssPath);
|
|
257
|
+
const cssText = new TextDecoder("utf-8").decode(cssBuffer);
|
|
258
|
+
const baseHref = /^https?:\/\//i.test(cssPath) || cssPath.startsWith("file:")
|
|
259
|
+
? cssPath
|
|
260
|
+
: pathToFileURL(cssPath).toString();
|
|
261
|
+
return rewriteCssUrls(cssText, baseHref);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
log("parse", "warn", "Failed to load stylesheet", { href, error: error instanceof Error ? error.message : String(error) });
|
|
264
|
+
return "";
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function rewriteCssUrls(cssText: string, baseHref: string): string {
|
|
269
|
+
let base: URL;
|
|
270
|
+
try {
|
|
271
|
+
base = new URL(baseHref);
|
|
272
|
+
} catch {
|
|
273
|
+
return cssText;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const urlRegex = /url\(\s*(['"]?)([^'")]+)\1\s*\)/gi;
|
|
277
|
+
return cssText.replace(urlRegex, (match, quote: string, rawUrl: string) => {
|
|
278
|
+
const candidate = (rawUrl || "").trim();
|
|
279
|
+
if (!candidate || /^data:/i.test(candidate)) return match;
|
|
280
|
+
if (/^[a-z][a-z0-9+\-.]*:/i.test(candidate)) return match;
|
|
281
|
+
try {
|
|
282
|
+
const resolved = new URL(candidate, base).toString();
|
|
283
|
+
const q = quote || "";
|
|
284
|
+
return `url(${q}${resolved}${q})`;
|
|
285
|
+
} catch {
|
|
286
|
+
return match;
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function pickFontUrlFromSrc(src: string): string | null {
|
|
292
|
+
const urlRegex = /url\(\s*(['"]?)([^'")]+)\1\s*\)(?:\s*format\(\s*['"]?([^'")]+)['"]?\s*\))?/gi;
|
|
293
|
+
let fallback: string | null = null;
|
|
294
|
+
let preferred: string | null = null;
|
|
295
|
+
let match: RegExpExecArray | null;
|
|
296
|
+
|
|
297
|
+
while ((match = urlRegex.exec(src)) !== null) {
|
|
298
|
+
const url = match[2];
|
|
299
|
+
const format = (match[3] || "").toLowerCase();
|
|
300
|
+
if (!fallback) fallback = url;
|
|
301
|
+
if (format === "woff2") {
|
|
302
|
+
preferred = url;
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return preferred ?? fallback;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function parseFontWeight(weightStr: string): number {
|
|
311
|
+
const normalized = weightStr.trim().toLowerCase();
|
|
312
|
+
if (normalized === "bold") return 700;
|
|
313
|
+
if (normalized === "normal") return 400;
|
|
314
|
+
const parsed = Number.parseInt(normalized, 10);
|
|
315
|
+
return Number.isFinite(parsed) ? parsed : 400;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function normalizeFontStyle(styleStr: string): "normal" | "italic" {
|
|
319
|
+
const normalized = styleStr.trim().toLowerCase();
|
|
320
|
+
return normalized.includes("italic") || normalized.includes("oblique") ? "italic" : "normal";
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function resolveLocalPath(target: string, resourceBaseDir: string, assetRootDir: string): string {
|
|
324
|
+
let result = target;
|
|
325
|
+
if (result.startsWith("file://")) {
|
|
326
|
+
result = fileURLToPath(result);
|
|
327
|
+
} else if (result.startsWith("/")) {
|
|
328
|
+
result = path.resolve(assetRootDir, `.${result}`);
|
|
329
|
+
} else if (!path.isAbsolute(result)) {
|
|
330
|
+
result = path.resolve(resourceBaseDir, result);
|
|
331
|
+
}
|
|
332
|
+
return result;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function loadFontData(src: string, resourceBaseDir: string, assetRootDir: string, environment: Environment): Promise<ArrayBuffer | null> {
|
|
336
|
+
const trimmed = src.trim();
|
|
337
|
+
if (!trimmed) return null;
|
|
338
|
+
let target = trimmed;
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
if (target.startsWith("//")) {
|
|
342
|
+
target = `https:${target}`;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (/^data:/i.test(target)) {
|
|
346
|
+
const commaIdx = target.indexOf(",");
|
|
347
|
+
if (commaIdx === -1) {
|
|
348
|
+
throw new Error("Invalid data URI");
|
|
349
|
+
}
|
|
350
|
+
const meta = target.slice(5, commaIdx);
|
|
351
|
+
if (!/;base64/i.test(meta)) {
|
|
352
|
+
throw new Error("Only base64-encoded data URIs are supported for fonts");
|
|
353
|
+
}
|
|
354
|
+
const data = decodeBase64ToUint8Array(target.slice(commaIdx + 1));
|
|
355
|
+
const copy = data.slice();
|
|
356
|
+
return copy.buffer;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (/^https?:\/\//i.test(target)) {
|
|
360
|
+
const response = await fetch(target);
|
|
361
|
+
if (!response.ok) {
|
|
362
|
+
throw new Error(`HTTP ${response.status}`);
|
|
363
|
+
}
|
|
364
|
+
return await response.arrayBuffer();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (/^file:/i.test(target)) {
|
|
368
|
+
const localPath = fileURLToPath(target);
|
|
369
|
+
const fontDataBuffer = await environment.loader.load(localPath);
|
|
370
|
+
return fontDataBuffer;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const resolved = environment.resolveLocal ? environment.resolveLocal(target, resourceBaseDir) : resolveLocalPath(target, resourceBaseDir, assetRootDir);
|
|
374
|
+
const fontDataBuffer = await environment.loader.load(resolved);
|
|
375
|
+
return fontDataBuffer;
|
|
376
|
+
} catch (error) {
|
|
377
|
+
log("font", "warn", "Failed to load font data", { src, error: error instanceof Error ? error.message : String(error) });
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function isInlineDisplay(display: Display): boolean {
|
|
383
|
+
return (
|
|
384
|
+
display === Display.Inline ||
|
|
385
|
+
display === Display.InlineBlock ||
|
|
386
|
+
display === Display.InlineFlex ||
|
|
387
|
+
display === Display.InlineGrid ||
|
|
388
|
+
display === Display.InlineTable
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function normalizeHtmlInput(html: string): string {
|
|
393
|
+
const hasHtmlTag = /<\s*html[\s>]/i.test(html);
|
|
394
|
+
if (hasHtmlTag) {
|
|
395
|
+
return html;
|
|
396
|
+
}
|
|
397
|
+
return wrapHtml(html);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function wrapHtml(html: string): string {
|
|
401
|
+
return `<!doctype html><html><head></head><body>${html}</body></html>`;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function needsReparse(document: Document): boolean {
|
|
405
|
+
const docEl = document.documentElement?.tagName;
|
|
406
|
+
const docIsHtml = docEl?.toUpperCase() === "HTML";
|
|
407
|
+
if (!docIsHtml) return true;
|
|
408
|
+
if (!document.body) return true;
|
|
409
|
+
return false;
|
|
410
|
+
}
|