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,209 @@
|
|
|
1
|
+
import type { RenderBox, TextPaintOptions, PageSize, Rect } from "./types.js";
|
|
2
|
+
import type { PagePainter } from "./page-painter.js";
|
|
3
|
+
import type { HeaderFooterVariant, HeaderFooterLayout } from "./header-footer-layout.js";
|
|
4
|
+
import type { FontRegistry } from "./font/font-registry.js";
|
|
5
|
+
import { applyPlaceholders } from "./header-footer-tokens.js";
|
|
6
|
+
import {
|
|
7
|
+
renderHeaderFooterHtml,
|
|
8
|
+
paintRenderedHeaderFooter,
|
|
9
|
+
type RenderedHeaderFooter,
|
|
10
|
+
} from "./header-footer-renderer.js";
|
|
11
|
+
import { paintBoxAtomic } from "./renderer/box-painter.js";
|
|
12
|
+
import { log } from "../logging/debug.js";
|
|
13
|
+
import type { Environment } from "../environment/environment.js";
|
|
14
|
+
|
|
15
|
+
export interface HeaderFooterPaintContext {
|
|
16
|
+
/** Page margins in pixels */
|
|
17
|
+
margins: { top: number; right: number; bottom: number; left: number };
|
|
18
|
+
/** Page width in pixels */
|
|
19
|
+
pageWidthPx: number;
|
|
20
|
+
/** Page height in pixels */
|
|
21
|
+
pageHeightPx: number;
|
|
22
|
+
/** Font registry for text rendering */
|
|
23
|
+
fontRegistry: FontRegistry;
|
|
24
|
+
/** Page offset Y for coordinate transformation */
|
|
25
|
+
pageOffsetY: number;
|
|
26
|
+
/** Optional CSS for header/footer styling */
|
|
27
|
+
css?: string;
|
|
28
|
+
/** Platform environment (Node/browser) */
|
|
29
|
+
environment?: Environment;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Renders and paints headers and footers as full HTML content.
|
|
34
|
+
* This provides Word/mPDF-like behavior where headers and footers
|
|
35
|
+
* are fully rendered HTML with complete styling support.
|
|
36
|
+
*/
|
|
37
|
+
export async function paintHeaderFooter(
|
|
38
|
+
painter: PagePainter,
|
|
39
|
+
header: HeaderFooterVariant | undefined,
|
|
40
|
+
footer: HeaderFooterVariant | undefined,
|
|
41
|
+
tokens: Map<string, string | ((page: number, total: number) => string)>,
|
|
42
|
+
pageIndex: number,
|
|
43
|
+
totalPages: number,
|
|
44
|
+
baseOptions: TextPaintOptions = { fontSizePt: 10 },
|
|
45
|
+
under = false,
|
|
46
|
+
context?: HeaderFooterPaintContext,
|
|
47
|
+
): Promise<void> {
|
|
48
|
+
void under;
|
|
49
|
+
|
|
50
|
+
// If we have full context, use the new HTML rendering path
|
|
51
|
+
if (context) {
|
|
52
|
+
log("layout", "debug", "Using HTML rendering path for headers/footers", {
|
|
53
|
+
hasHeader: !!header?.content,
|
|
54
|
+
hasFooter: !!footer?.content,
|
|
55
|
+
margins: context.margins,
|
|
56
|
+
});
|
|
57
|
+
await paintHeaderFooterWithContext(
|
|
58
|
+
painter,
|
|
59
|
+
header,
|
|
60
|
+
footer,
|
|
61
|
+
tokens,
|
|
62
|
+
pageIndex,
|
|
63
|
+
totalPages,
|
|
64
|
+
context,
|
|
65
|
+
);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
log("layout", "debug", "Using legacy text rendering for headers/footers (no context)");
|
|
70
|
+
// Fallback to legacy text-only rendering for backwards compatibility
|
|
71
|
+
await paintHeaderFooterLegacy(painter, header, footer, tokens, pageIndex, totalPages, baseOptions);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* New HTML rendering path for headers/footers.
|
|
76
|
+
* Renders headers and footers as full HTML through the layout pipeline.
|
|
77
|
+
*/
|
|
78
|
+
async function paintHeaderFooterWithContext(
|
|
79
|
+
painter: PagePainter,
|
|
80
|
+
header: HeaderFooterVariant | undefined,
|
|
81
|
+
footer: HeaderFooterVariant | undefined,
|
|
82
|
+
tokens: Map<string, string | ((page: number, total: number) => string)>,
|
|
83
|
+
pageIndex: number,
|
|
84
|
+
totalPages: number,
|
|
85
|
+
context: HeaderFooterPaintContext,
|
|
86
|
+
): Promise<void> {
|
|
87
|
+
const { margins, pageWidthPx, pageHeightPx, fontRegistry, pageOffsetY, css } = context;
|
|
88
|
+
|
|
89
|
+
// Calculate content width (page width minus left and right margins)
|
|
90
|
+
const contentWidthPx = pageWidthPx - margins.left - margins.right;
|
|
91
|
+
|
|
92
|
+
log("layout", "debug", "paintHeaderFooterWithContext", {
|
|
93
|
+
headerContent: header?.content ? String(header.content).slice(0, 100) : "none",
|
|
94
|
+
footerContent: footer?.content ? String(footer.content).slice(0, 100) : "none",
|
|
95
|
+
contentWidthPx,
|
|
96
|
+
pageWidthPx,
|
|
97
|
+
pageHeightPx,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Render and paint header
|
|
101
|
+
if (header?.content) {
|
|
102
|
+
const headerHtml = stringify(header.content);
|
|
103
|
+
log("layout", "debug", "Rendering header HTML", { headerHtml: headerHtml.slice(0, 200) });
|
|
104
|
+
if (headerHtml) {
|
|
105
|
+
try {
|
|
106
|
+
const rendered = await renderHeaderFooterHtml({
|
|
107
|
+
html: headerHtml,
|
|
108
|
+
css,
|
|
109
|
+
widthPx: contentWidthPx,
|
|
110
|
+
maxHeightPx: header.maxHeightPx,
|
|
111
|
+
tokens,
|
|
112
|
+
pageNumber: pageIndex,
|
|
113
|
+
totalPages,
|
|
114
|
+
environment: context.environment,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (rendered) {
|
|
118
|
+
log("layout", "debug", "Header rendered successfully", { heightPx: rendered.heightPx });
|
|
119
|
+
// Header is positioned at the top margin area
|
|
120
|
+
await paintRenderedHeaderFooter(
|
|
121
|
+
painter,
|
|
122
|
+
rendered,
|
|
123
|
+
margins.left,
|
|
124
|
+
margins.top,
|
|
125
|
+
fontRegistry,
|
|
126
|
+
pageOffsetY,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
} catch (err) {
|
|
130
|
+
log("layout", "warn", "Failed to render header HTML", { error: err });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Render and paint footer
|
|
136
|
+
if (footer?.content) {
|
|
137
|
+
const footerHtml = stringify(footer.content);
|
|
138
|
+
if (footerHtml) {
|
|
139
|
+
try {
|
|
140
|
+
const rendered = await renderHeaderFooterHtml({
|
|
141
|
+
html: footerHtml,
|
|
142
|
+
css,
|
|
143
|
+
widthPx: contentWidthPx,
|
|
144
|
+
maxHeightPx: footer.maxHeightPx,
|
|
145
|
+
tokens,
|
|
146
|
+
pageNumber: pageIndex,
|
|
147
|
+
totalPages,
|
|
148
|
+
environment: context.environment,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (rendered) {
|
|
152
|
+
// Footer is positioned at the bottom of the page
|
|
153
|
+
const footerY = pageHeightPx - margins.bottom - footer.maxHeightPx;
|
|
154
|
+
await paintRenderedHeaderFooter(
|
|
155
|
+
painter,
|
|
156
|
+
rendered,
|
|
157
|
+
margins.left,
|
|
158
|
+
footerY,
|
|
159
|
+
fontRegistry,
|
|
160
|
+
pageOffsetY,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
} catch (err) {
|
|
164
|
+
log("layout", "warn", "Failed to render footer HTML", { error: err });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Legacy text-only rendering for headers/footers.
|
|
172
|
+
* Used for backwards compatibility when context is not provided.
|
|
173
|
+
*/
|
|
174
|
+
async function paintHeaderFooterLegacy(
|
|
175
|
+
painter: PagePainter,
|
|
176
|
+
header: HeaderFooterVariant | undefined,
|
|
177
|
+
footer: HeaderFooterVariant | undefined,
|
|
178
|
+
tokens: Map<string, string | ((page: number, total: number) => string)>,
|
|
179
|
+
pageIndex: number,
|
|
180
|
+
totalPages: number,
|
|
181
|
+
baseOptions: TextPaintOptions,
|
|
182
|
+
): Promise<void> {
|
|
183
|
+
const headerText = header?.content ? stringify(header.content) : undefined;
|
|
184
|
+
const footerText = footer?.content ? stringify(footer.content) : undefined;
|
|
185
|
+
|
|
186
|
+
if (headerText) {
|
|
187
|
+
const rendered = applyPlaceholders(headerText, tokens, pageIndex, totalPages);
|
|
188
|
+
await painter.drawText(rendered, 16, header?.maxHeightPx ?? 24, { ...baseOptions, absolute: true });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (footerText) {
|
|
192
|
+
const rendered = applyPlaceholders(footerText, tokens, pageIndex, totalPages);
|
|
193
|
+
const yPx = painter.pageHeightPx ? painter.pageHeightPx - ((footer?.maxHeightPx ?? 24) + 16) : 16;
|
|
194
|
+
await painter.drawText(rendered, 16, yPx, { ...baseOptions, absolute: true });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function stringify(content: unknown): string {
|
|
199
|
+
if (content == null) {
|
|
200
|
+
return "";
|
|
201
|
+
}
|
|
202
|
+
if (typeof content === "string") {
|
|
203
|
+
return content;
|
|
204
|
+
}
|
|
205
|
+
if (typeof content === "function") {
|
|
206
|
+
return String(content());
|
|
207
|
+
}
|
|
208
|
+
return JSON.stringify(content);
|
|
209
|
+
}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Header/Footer Renderer
|
|
3
|
+
*
|
|
4
|
+
* This module provides Word/mPDF-like header and footer rendering.
|
|
5
|
+
* Headers and footers are rendered as full HTML through the same layout pipeline
|
|
6
|
+
* as the main content, giving them full styling capabilities.
|
|
7
|
+
*
|
|
8
|
+
* Key behaviors:
|
|
9
|
+
* - Headers are rendered at the top of each page (within top margin area)
|
|
10
|
+
* - Footers are rendered at the bottom of each page (within bottom margin area)
|
|
11
|
+
* - Main content area is automatically reduced to exclude header/footer space
|
|
12
|
+
* - Placeholders like {{pageNumber}}, {{totalPages}}, {{date}} are supported
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { parseHTML } from "linkedom";
|
|
16
|
+
import type { FontConfig } from "../types/fonts.js";
|
|
17
|
+
import { parseCss } from "../html/css/parse-css.js";
|
|
18
|
+
import { makeUnitParsers, type UnitCtx, pxToPt } from "../units/units.js";
|
|
19
|
+
import { LayoutNode } from "../dom/node.js";
|
|
20
|
+
import { ComputedStyle } from "../css/style.js";
|
|
21
|
+
import { layoutTree } from "../layout/pipeline/layout-tree.js";
|
|
22
|
+
import { buildRenderTree, type RenderTreeOptions } from "./layout-tree-builder.js";
|
|
23
|
+
import type { RenderBox, Rect, LayoutTree, RGBA, Run, TextPaintOptions } from "./types.js";
|
|
24
|
+
import { convertDomNode } from "../html/dom-converter.js";
|
|
25
|
+
import { computeStyleForElement } from "../css/compute-style.js";
|
|
26
|
+
import { Display } from "../css/enums.js";
|
|
27
|
+
import { log } from "../logging/debug.js";
|
|
28
|
+
import { offsetRenderTree } from "../render/offset.js";
|
|
29
|
+
import type { FontEmbedder } from "./font/embedder.js";
|
|
30
|
+
import { FontRegistryResolver } from "../fonts/font-registry-resolver.js";
|
|
31
|
+
import type { FontRegistry } from "./font/font-registry.js";
|
|
32
|
+
import { computeGlyphRun, applyWordSpacingToGlyphRun } from "./utils/node-text-run-factory.js";
|
|
33
|
+
import type { PagePainter } from "./page-painter.js";
|
|
34
|
+
import { paintBoxAtomic } from "./renderer/box-painter.js";
|
|
35
|
+
import { applyPlaceholders } from "./header-footer-tokens.js";
|
|
36
|
+
import type { Environment } from "../environment/environment.js";
|
|
37
|
+
|
|
38
|
+
export interface HeaderFooterRenderOptions {
|
|
39
|
+
/** The HTML content for the header/footer */
|
|
40
|
+
html: string;
|
|
41
|
+
/** Optional CSS to apply */
|
|
42
|
+
css?: string;
|
|
43
|
+
/** Width of the header/footer area in pixels */
|
|
44
|
+
widthPx: number;
|
|
45
|
+
/** Maximum height of the header/footer area in pixels */
|
|
46
|
+
maxHeightPx: number;
|
|
47
|
+
/** Font configuration for rendering */
|
|
48
|
+
fontConfig?: FontConfig;
|
|
49
|
+
/** Font embedder for PDF output */
|
|
50
|
+
fontEmbedder?: FontEmbedder | null;
|
|
51
|
+
/** Resource base directory for loading assets */
|
|
52
|
+
resourceBaseDir?: string;
|
|
53
|
+
/** Asset root directory */
|
|
54
|
+
assetRootDir?: string;
|
|
55
|
+
/** Tokens for placeholder replacement */
|
|
56
|
+
tokens?: Map<string, string | ((page: number, total: number) => string)>;
|
|
57
|
+
/** Current page number (1-indexed) */
|
|
58
|
+
pageNumber?: number;
|
|
59
|
+
/** Total number of pages */
|
|
60
|
+
totalPages?: number;
|
|
61
|
+
/** Platform environment (Node/browser) for resource loading */
|
|
62
|
+
environment?: Environment;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface RenderedHeaderFooter {
|
|
66
|
+
/** The render tree for the header/footer */
|
|
67
|
+
renderTree: LayoutTree;
|
|
68
|
+
/** The actual height of the rendered content in pixels */
|
|
69
|
+
heightPx: number;
|
|
70
|
+
/** The root render box */
|
|
71
|
+
root: RenderBox;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Renders header or footer HTML into a layout tree.
|
|
76
|
+
* This uses the same pipeline as the main content for full HTML/CSS support.
|
|
77
|
+
*/
|
|
78
|
+
export async function renderHeaderFooterHtml(
|
|
79
|
+
options: HeaderFooterRenderOptions
|
|
80
|
+
): Promise<RenderedHeaderFooter | null> {
|
|
81
|
+
const {
|
|
82
|
+
html,
|
|
83
|
+
css = "",
|
|
84
|
+
widthPx,
|
|
85
|
+
maxHeightPx,
|
|
86
|
+
fontEmbedder,
|
|
87
|
+
resourceBaseDir,
|
|
88
|
+
assetRootDir,
|
|
89
|
+
tokens,
|
|
90
|
+
pageNumber = 1,
|
|
91
|
+
totalPages = 1,
|
|
92
|
+
environment,
|
|
93
|
+
} = options;
|
|
94
|
+
|
|
95
|
+
const resolvedResourceBase = resourceBaseDir ?? "";
|
|
96
|
+
const resolvedAssetRoot = assetRootDir ?? resolvedResourceBase;
|
|
97
|
+
|
|
98
|
+
if (!html || !html.trim()) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Apply placeholder replacements
|
|
103
|
+
let processedHtml = html;
|
|
104
|
+
if (tokens) {
|
|
105
|
+
processedHtml = applyPlaceholders(html, tokens, pageNumber, totalPages);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Wrap in a container if not already a full document
|
|
109
|
+
const normalizedHtml = normalizeHtmlFragment(processedHtml);
|
|
110
|
+
|
|
111
|
+
const unitCtx: UnitCtx = { viewport: { width: widthPx, height: maxHeightPx } };
|
|
112
|
+
const units = makeUnitParsers(unitCtx);
|
|
113
|
+
|
|
114
|
+
const { document } = parseHTML(normalizedHtml);
|
|
115
|
+
|
|
116
|
+
// Parse CSS
|
|
117
|
+
const { styleRules: cssRules } = parseCss(css);
|
|
118
|
+
|
|
119
|
+
// Get the root element
|
|
120
|
+
const rootElement = document.body || document.documentElement;
|
|
121
|
+
|
|
122
|
+
const baseParentStyle = new ComputedStyle();
|
|
123
|
+
const rootFontSize = baseParentStyle.fontSize;
|
|
124
|
+
|
|
125
|
+
let rootStyle = computeStyleForElement(rootElement, cssRules, baseParentStyle, units, rootFontSize);
|
|
126
|
+
if (isInlineDisplay(rootStyle.display)) {
|
|
127
|
+
rootStyle.display = Display.Block;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const rootLayout = new LayoutNode(rootStyle, [], { tagName: rootElement?.tagName?.toLowerCase() });
|
|
131
|
+
|
|
132
|
+
const conversionContext = { resourceBaseDir: resolvedResourceBase, assetRootDir: resolvedAssetRoot, units, rootFontSize, environment };
|
|
133
|
+
|
|
134
|
+
if (rootElement) {
|
|
135
|
+
for (const child of Array.from(rootElement.childNodes)) {
|
|
136
|
+
if (child.nodeType === child.ELEMENT_NODE) {
|
|
137
|
+
const tagName = (child as HTMLElement).tagName.toLowerCase();
|
|
138
|
+
if (tagName === "head" || tagName === "meta" || tagName === "title" || tagName === "link" || tagName === "script") {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const layoutChild = await convertDomNode(child, cssRules, rootStyle, conversionContext);
|
|
143
|
+
if (layoutChild) rootLayout.appendChild(layoutChild);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Layout with constrained dimensions
|
|
148
|
+
layoutTree(rootLayout, { width: widthPx, height: maxHeightPx }, fontEmbedder ?? null);
|
|
149
|
+
|
|
150
|
+
const renderTree = buildRenderTree(rootLayout, {});
|
|
151
|
+
|
|
152
|
+
// Calculate actual height
|
|
153
|
+
const actualHeight = Math.min(calculateTreeHeight(renderTree.root), maxHeightPx);
|
|
154
|
+
|
|
155
|
+
log("layout", "debug", "Header/footer rendered", {
|
|
156
|
+
widthPx,
|
|
157
|
+
maxHeightPx,
|
|
158
|
+
actualHeight,
|
|
159
|
+
hasContent: renderTree.root.children.length > 0,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
renderTree,
|
|
164
|
+
heightPx: actualHeight,
|
|
165
|
+
root: renderTree.root,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Deep clones a render box tree for per-page rendering.
|
|
171
|
+
* This is needed because we apply offsets that are page-specific.
|
|
172
|
+
*/
|
|
173
|
+
function cloneRenderBox(box: RenderBox): RenderBox {
|
|
174
|
+
const clonedRuns = box.textRuns.map((run) => ({
|
|
175
|
+
...run,
|
|
176
|
+
lineMatrix: run.lineMatrix ? { ...run.lineMatrix } : undefined,
|
|
177
|
+
decorations: run.decorations ? { ...run.decorations } : undefined,
|
|
178
|
+
glyphs: run.glyphs ? { ...run.glyphs } : undefined,
|
|
179
|
+
})) as Run[];
|
|
180
|
+
|
|
181
|
+
const clonedBox: RenderBox = {
|
|
182
|
+
...box,
|
|
183
|
+
contentBox: { ...box.contentBox },
|
|
184
|
+
paddingBox: { ...box.paddingBox },
|
|
185
|
+
borderBox: { ...box.borderBox },
|
|
186
|
+
visualOverflow: { ...box.visualOverflow },
|
|
187
|
+
padding: { ...box.padding },
|
|
188
|
+
border: { ...box.border },
|
|
189
|
+
borderRadius: {
|
|
190
|
+
topLeft: { ...box.borderRadius.topLeft },
|
|
191
|
+
topRight: { ...box.borderRadius.topRight },
|
|
192
|
+
bottomRight: { ...box.borderRadius.bottomRight },
|
|
193
|
+
bottomLeft: { ...box.borderRadius.bottomLeft },
|
|
194
|
+
},
|
|
195
|
+
background: box.background
|
|
196
|
+
? {
|
|
197
|
+
color: box.background.color ? { ...box.background.color } : undefined,
|
|
198
|
+
image: box.background.image
|
|
199
|
+
? {
|
|
200
|
+
...box.background.image,
|
|
201
|
+
rect: { ...box.background.image.rect },
|
|
202
|
+
originRect: { ...box.background.image.originRect },
|
|
203
|
+
}
|
|
204
|
+
: undefined,
|
|
205
|
+
gradient: box.background.gradient
|
|
206
|
+
? {
|
|
207
|
+
...box.background.gradient,
|
|
208
|
+
rect: { ...box.background.gradient.rect },
|
|
209
|
+
originRect: { ...box.background.gradient.originRect },
|
|
210
|
+
}
|
|
211
|
+
: undefined,
|
|
212
|
+
}
|
|
213
|
+
: { color: undefined },
|
|
214
|
+
textRuns: clonedRuns,
|
|
215
|
+
markerRect: box.markerRect ? { ...box.markerRect } : undefined,
|
|
216
|
+
boxShadows: box.boxShadows.map((s) => ({ ...s, color: { ...s.color } })),
|
|
217
|
+
links: box.links.map((link) => ({
|
|
218
|
+
rect: { ...link.rect },
|
|
219
|
+
target: { ...link.target },
|
|
220
|
+
})),
|
|
221
|
+
children: box.children.map((child) => cloneRenderBox(child)),
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
return clonedBox;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Paints a rendered header/footer onto a page.
|
|
229
|
+
*/
|
|
230
|
+
export async function paintRenderedHeaderFooter(
|
|
231
|
+
painter: PagePainter,
|
|
232
|
+
rendered: RenderedHeaderFooter,
|
|
233
|
+
xOffsetPx: number,
|
|
234
|
+
yOffsetPx: number,
|
|
235
|
+
fontRegistry: FontRegistry,
|
|
236
|
+
pageOffsetY: number,
|
|
237
|
+
): Promise<void> {
|
|
238
|
+
// Clone the tree to avoid mutating the original
|
|
239
|
+
const clonedRoot = cloneRenderBox(rendered.root);
|
|
240
|
+
|
|
241
|
+
// Enrich text runs with glyph data
|
|
242
|
+
const fontResolver = new FontRegistryResolver(fontRegistry);
|
|
243
|
+
await enrichTreeWithGlyphRuns(clonedRoot, fontResolver);
|
|
244
|
+
|
|
245
|
+
// Offset the tree to the correct position
|
|
246
|
+
offsetRenderTree(clonedRoot, xOffsetPx, yOffsetPx - pageOffsetY, false);
|
|
247
|
+
|
|
248
|
+
// Paint all boxes
|
|
249
|
+
const stack: RenderBox[] = [clonedRoot];
|
|
250
|
+
while (stack.length > 0) {
|
|
251
|
+
const box = stack.pop()!;
|
|
252
|
+
await paintBoxAtomic(painter, box);
|
|
253
|
+
// Add children in reverse order to paint in correct order
|
|
254
|
+
for (let i = box.children.length - 1; i >= 0; i--) {
|
|
255
|
+
stack.push(box.children[i]);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Calculates the configuration for page content area when headers/footers are present.
|
|
262
|
+
*/
|
|
263
|
+
export interface PageContentAreaConfig {
|
|
264
|
+
/** Y offset where content should start (after header) */
|
|
265
|
+
contentStartY: number;
|
|
266
|
+
/** Available height for content (excluding header and footer) */
|
|
267
|
+
contentHeightPx: number;
|
|
268
|
+
/** Y position where footer should be placed */
|
|
269
|
+
footerY: number;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function calculatePageContentArea(
|
|
273
|
+
pageHeightPx: number,
|
|
274
|
+
marginTop: number,
|
|
275
|
+
marginBottom: number,
|
|
276
|
+
headerHeightPx: number,
|
|
277
|
+
footerHeightPx: number,
|
|
278
|
+
): PageContentAreaConfig {
|
|
279
|
+
// Header is placed at marginTop
|
|
280
|
+
// Content starts after header
|
|
281
|
+
const contentStartY = marginTop + headerHeightPx;
|
|
282
|
+
|
|
283
|
+
// Footer is placed at (pageHeight - marginBottom - footerHeight)
|
|
284
|
+
const footerY = pageHeightPx - marginBottom - footerHeightPx;
|
|
285
|
+
|
|
286
|
+
// Content height is the space between header and footer
|
|
287
|
+
const contentHeightPx = footerY - contentStartY;
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
contentStartY,
|
|
291
|
+
contentHeightPx: Math.max(0, contentHeightPx),
|
|
292
|
+
footerY,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function normalizeHtmlFragment(html: string): string {
|
|
297
|
+
const hasHtmlTag = /<\s*html[\s>]/i.test(html);
|
|
298
|
+
if (hasHtmlTag) {
|
|
299
|
+
return html;
|
|
300
|
+
}
|
|
301
|
+
return `<!doctype html><html><head></head><body>${html}</body></html>`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function isInlineDisplay(display: Display): boolean {
|
|
305
|
+
return (
|
|
306
|
+
display === Display.Inline ||
|
|
307
|
+
display === Display.InlineBlock ||
|
|
308
|
+
display === Display.InlineFlex ||
|
|
309
|
+
display === Display.InlineGrid ||
|
|
310
|
+
display === Display.InlineTable
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function calculateTreeHeight(root: RenderBox): number {
|
|
315
|
+
let maxBottom = 0;
|
|
316
|
+
|
|
317
|
+
function traverse(box: RenderBox): void {
|
|
318
|
+
const bottom = box.borderBox.y + box.borderBox.height;
|
|
319
|
+
maxBottom = Math.max(maxBottom, bottom);
|
|
320
|
+
for (const child of box.children) {
|
|
321
|
+
traverse(child);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
traverse(root);
|
|
326
|
+
return maxBottom;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function enrichTreeWithGlyphRuns(root: RenderBox, fontResolver: FontRegistryResolver): Promise<void> {
|
|
330
|
+
async function enrichRun(run: Run): Promise<void> {
|
|
331
|
+
if (run.glyphs) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
const font = await fontResolver.resolve(run.fontFamily, run.fontWeight, run.fontStyle);
|
|
336
|
+
const letterSpacing = run.letterSpacing ?? 0;
|
|
337
|
+
const glyphRun = computeGlyphRun(font, run.text, run.fontSize, letterSpacing);
|
|
338
|
+
applyWordSpacingToGlyphRun(glyphRun, run.text, run.wordSpacing);
|
|
339
|
+
run.glyphs = glyphRun;
|
|
340
|
+
} catch {
|
|
341
|
+
// Ignore font resolution errors for headers/footers
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function traverse(box: RenderBox): Promise<void> {
|
|
346
|
+
if (box.textRuns && box.textRuns.length > 0) {
|
|
347
|
+
for (const run of box.textRuns) {
|
|
348
|
+
await enrichRun(run);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
for (const child of box.children) {
|
|
352
|
+
await traverse(child);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
await traverse(root);
|
|
357
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { HeaderFooterHTML } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export function computeHfTokens(
|
|
4
|
+
placeholders: HeaderFooterHTML["placeholders"],
|
|
5
|
+
_totalPages: number,
|
|
6
|
+
meta: { title?: string } = {},
|
|
7
|
+
): Map<string, string | ((page: number, total: number) => string)> {
|
|
8
|
+
const tokens = new Map<string, string | ((page: number, total: number) => string)>();
|
|
9
|
+
for (const [key, value] of Object.entries(placeholders ?? {})) {
|
|
10
|
+
tokens.set(key, value);
|
|
11
|
+
}
|
|
12
|
+
// Support multiple naming conventions for page numbers
|
|
13
|
+
tokens.set("page", (page, _total) => String(page));
|
|
14
|
+
tokens.set("pageNumber", (page, _total) => String(page));
|
|
15
|
+
tokens.set("pages", (_page, total) => String(total));
|
|
16
|
+
tokens.set("totalPages", (_page, total) => String(total));
|
|
17
|
+
if (!tokens.has("title") && meta.title) {
|
|
18
|
+
tokens.set("title", meta.title);
|
|
19
|
+
}
|
|
20
|
+
tokens.set("date", () => new Date().toLocaleDateString());
|
|
21
|
+
return tokens;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function applyPlaceholders(
|
|
25
|
+
template: string,
|
|
26
|
+
tokens: Map<string, string | ((page: number, total: number) => string)>,
|
|
27
|
+
pageIndex: number,
|
|
28
|
+
totalPages: number,
|
|
29
|
+
): string {
|
|
30
|
+
// Support both {{key}} (double curly braces) and {key} (single curly braces) syntax
|
|
31
|
+
// First replace double braces, then single braces
|
|
32
|
+
let result = template.replace(/\{\{([^}]+)\}\}/g, (_, key) => {
|
|
33
|
+
const entry = tokens.get(key.trim());
|
|
34
|
+
if (entry === undefined) {
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
if (typeof entry === "string") {
|
|
38
|
+
return entry;
|
|
39
|
+
}
|
|
40
|
+
return entry(pageIndex, totalPages);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
result = result.replace(/\{([^}]+)\}/g, (_, key) => {
|
|
44
|
+
const entry = tokens.get(key.trim());
|
|
45
|
+
if (entry === undefined) {
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
if (typeof entry === "string") {
|
|
49
|
+
return entry;
|
|
50
|
+
}
|
|
51
|
+
return entry(pageIndex, totalPages);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export {
|
|
2
|
+
type HeaderFooterContext,
|
|
3
|
+
type HeaderFooterLayout,
|
|
4
|
+
type HeaderFooterVariant,
|
|
5
|
+
type HeaderFooterVariants,
|
|
6
|
+
initHeaderFooterContext,
|
|
7
|
+
layoutHeaderFooterTrees,
|
|
8
|
+
adjustPageBoxForHf,
|
|
9
|
+
pickHeaderVariant,
|
|
10
|
+
pickFooterVariant,
|
|
11
|
+
} from "./header-footer-layout.js";
|
|
12
|
+
|
|
13
|
+
export { computeHfTokens, applyPlaceholders } from "./header-footer-tokens.js";
|
|
14
|
+
|
|
15
|
+
export { paintHeaderFooter, type HeaderFooterPaintContext } from "./header-footer-painter.js";
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
renderHeaderFooterHtml,
|
|
19
|
+
paintRenderedHeaderFooter,
|
|
20
|
+
calculatePageContentArea,
|
|
21
|
+
type HeaderFooterRenderOptions,
|
|
22
|
+
type RenderedHeaderFooter,
|
|
23
|
+
type PageContentAreaConfig,
|
|
24
|
+
} from "./header-footer-renderer.js";
|
|
25
|
+
export type { Environment as HeaderFooterEnvironment } from "../environment/environment.js";
|