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,180 @@
|
|
|
1
|
+
import type { CmapData } from "../../types/fonts.js";
|
|
2
|
+
import { TtfTableParser } from "./ttf-table-parser.js";
|
|
3
|
+
|
|
4
|
+
// cmap table tags / helpers could live here if needed
|
|
5
|
+
|
|
6
|
+
export class CmapParser implements CmapData {
|
|
7
|
+
public readonly unicodeMap = new Map<number, number>();
|
|
8
|
+
|
|
9
|
+
constructor(parser: TtfTableParser, cmapTable: DataView) {
|
|
10
|
+
this.parseCmapTable(parser, cmapTable);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
private parseCmapTable(parser: TtfTableParser, table: DataView): void {
|
|
14
|
+
if (table.byteLength < 4) throw new Error("Truncated cmap header");
|
|
15
|
+
|
|
16
|
+
const version = table.getUint16(0, false);
|
|
17
|
+
if (version !== 0) return; // only support version 0 for now
|
|
18
|
+
|
|
19
|
+
const numSubtables = table.getUint16(2, false);
|
|
20
|
+
const subtables: { platformId: number; encodingId: number; offset: number }[] = [];
|
|
21
|
+
|
|
22
|
+
// collect subtables with bounds checks
|
|
23
|
+
const expectedDirLen = 4 + numSubtables * 8;
|
|
24
|
+
if (expectedDirLen > table.byteLength) throw new Error("Truncated cmap subtables directory");
|
|
25
|
+
|
|
26
|
+
for (let i = 0; i < numSubtables; i++) {
|
|
27
|
+
const subtableOffset = 4 + i * 8;
|
|
28
|
+
const platformId = table.getUint16(subtableOffset, false);
|
|
29
|
+
const encodingId = table.getUint16(subtableOffset + 2, false);
|
|
30
|
+
const offset = table.getUint32(subtableOffset + 4, false);
|
|
31
|
+
|
|
32
|
+
if (offset >= table.byteLength) {
|
|
33
|
+
throw new Error("Invalid subtable offset in cmap");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
subtables.push({ platformId, encodingId, offset });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Selection strategy: prefer Platform 0 (Unicode) format 12/4, then Platform 3 encIDs 10/1 (format 12/4),
|
|
40
|
+
// otherwise fall back to any format 4 available.
|
|
41
|
+
const pickSubtable = (): { platformId: number; encodingId: number; offset: number } | null => {
|
|
42
|
+
const findPreferred = (platforms: { pid: number; eids: number[] }[]) => {
|
|
43
|
+
for (const p of platforms) {
|
|
44
|
+
for (const st of subtables) {
|
|
45
|
+
if (st.platformId !== p.pid) continue;
|
|
46
|
+
if (p.eids.length > 0 && !p.eids.includes(st.encodingId)) continue;
|
|
47
|
+
|
|
48
|
+
if (st.offset + 2 <= table.byteLength) {
|
|
49
|
+
const fmt = table.getUint16(st.offset, false);
|
|
50
|
+
if (fmt === 12) return st;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const p of platforms) {
|
|
55
|
+
for (const st of subtables) {
|
|
56
|
+
if (st.platformId !== p.pid) continue;
|
|
57
|
+
if (p.eids.length > 0 && !p.eids.includes(st.encodingId)) continue;
|
|
58
|
+
if (st.offset + 2 <= table.byteLength) {
|
|
59
|
+
const fmt = table.getUint16(st.offset, false);
|
|
60
|
+
if (fmt === 4) return st;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
let chosen = findPreferred([{ pid: 0, eids: [] }]);
|
|
68
|
+
if (chosen) return chosen;
|
|
69
|
+
|
|
70
|
+
chosen = findPreferred([{ pid: 3, eids: [10] }, { pid: 3, eids: [1] }]);
|
|
71
|
+
if (chosen) return chosen;
|
|
72
|
+
|
|
73
|
+
for (const st of subtables) {
|
|
74
|
+
if (st.offset + 2 <= table.byteLength && table.getUint16(st.offset, false) === 4) return st;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return null;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const selected = pickSubtable();
|
|
81
|
+
if (!selected) {
|
|
82
|
+
// No suitable cmap found — leave unicodeMap empty.
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const format = table.getUint16(selected.offset, false);
|
|
87
|
+
if (format === 4) {
|
|
88
|
+
this.parseFormat4Table(parser, table, selected.offset);
|
|
89
|
+
} else if (format === 12) {
|
|
90
|
+
this.parseFormat12Table(parser, table, selected.offset);
|
|
91
|
+
} else {
|
|
92
|
+
// unsupported format for now; leave empty
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private parseFormat4Table(_parser: TtfTableParser, table: DataView, offset: number): void {
|
|
98
|
+
if (offset + 8 > table.byteLength) throw new Error("Truncated cmap format 4 header");
|
|
99
|
+
const format = table.getUint16(offset, false);
|
|
100
|
+
if (format !== 4) throw new Error("Unexpected cmap format (not 4)");
|
|
101
|
+
|
|
102
|
+
const length = table.getUint16(offset + 2, false);
|
|
103
|
+
if (offset + length > table.byteLength) throw new Error("Truncated cmap format 4 table");
|
|
104
|
+
|
|
105
|
+
const segCountX2 = table.getUint16(offset + 6, false);
|
|
106
|
+
const segCount = segCountX2 / 2;
|
|
107
|
+
|
|
108
|
+
const endCodeOffset = offset + 14;
|
|
109
|
+
const startCodeOffset = endCodeOffset + segCount * 2 + 2;
|
|
110
|
+
const idDeltaOffset = startCodeOffset + segCount * 2;
|
|
111
|
+
const idRangeOffsetOffset = idDeltaOffset + segCount * 2;
|
|
112
|
+
|
|
113
|
+
if (idRangeOffsetOffset > offset + length) throw new Error("Truncated cmap format 4 arrays");
|
|
114
|
+
|
|
115
|
+
for (let i = 0; i < segCount; i++) {
|
|
116
|
+
const endCode = table.getUint16(endCodeOffset + i * 2, false);
|
|
117
|
+
const startCode = table.getUint16(startCodeOffset + i * 2, false);
|
|
118
|
+
const idDelta = table.getInt16(idDeltaOffset + i * 2, false); // signed
|
|
119
|
+
const idRangeOffset = table.getUint16(idRangeOffsetOffset + i * 2, false);
|
|
120
|
+
|
|
121
|
+
if (startCode === 0xffff) break;
|
|
122
|
+
|
|
123
|
+
if (startCode > endCode) throw new Error("Invalid cmap segment (startCode > endCode)");
|
|
124
|
+
|
|
125
|
+
for (let code = startCode; code <= endCode; code++) {
|
|
126
|
+
let glyphId = 0;
|
|
127
|
+
|
|
128
|
+
if (idRangeOffset === 0) {
|
|
129
|
+
glyphId = (code + idDelta) & 0xffff;
|
|
130
|
+
} else {
|
|
131
|
+
const idRangeWordOffset = idRangeOffsetOffset + i * 2;
|
|
132
|
+
const glyphIndexOffset = idRangeWordOffset + idRangeOffset + (code - startCode) * 2;
|
|
133
|
+
|
|
134
|
+
if (glyphIndexOffset + 2 > offset + length) {
|
|
135
|
+
throw new Error("Truncated cmap glyphIndexArray");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
glyphId = table.getUint16(glyphIndexOffset, false);
|
|
139
|
+
if (glyphId !== 0) {
|
|
140
|
+
glyphId = (glyphId + idDelta) & 0xffff;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this.unicodeMap.set(code, glyphId);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private parseFormat12Table(_parser: TtfTableParser, table: DataView, offset: number): void {
|
|
150
|
+
// Format 12 uses 32-bit fields
|
|
151
|
+
if (offset + 16 > table.byteLength) throw new Error("Truncated cmap format 12 header");
|
|
152
|
+
const format = table.getUint16(offset, false);
|
|
153
|
+
if (format !== 12) throw new Error("Unexpected cmap format (not 12)");
|
|
154
|
+
const nGroups = table.getUint32(offset + 12, false);
|
|
155
|
+
const groupsOffset = offset + 16;
|
|
156
|
+
const required = groupsOffset + nGroups * 12;
|
|
157
|
+
if (required > table.byteLength) throw new Error("Truncated cmap format 12 groups");
|
|
158
|
+
|
|
159
|
+
let p = groupsOffset;
|
|
160
|
+
for (let g = 0; g < nGroups; g++, p += 12) {
|
|
161
|
+
const startCode = table.getUint32(p, false);
|
|
162
|
+
const endCode = table.getUint32(p + 4, false);
|
|
163
|
+
const startGID = table.getUint32(p + 8, false);
|
|
164
|
+
|
|
165
|
+
if (startCode > endCode) throw new Error("Invalid cmap format 12 group (startCode > endCode)");
|
|
166
|
+
|
|
167
|
+
for (let cp = startCode; cp <= endCode; cp++) {
|
|
168
|
+
this.unicodeMap.set(cp, startGID + (cp - startCode));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
getGlyphId(codePoint: number): number {
|
|
174
|
+
return this.unicodeMap.get(codePoint) ?? 0;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
hasCodePoint(codePoint: number): boolean {
|
|
178
|
+
return this.unicodeMap.has(codePoint);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { TtfMetrics } from "../../types/fonts.js";
|
|
2
|
+
import { TtfTableParser } from "./ttf-table-parser.js";
|
|
3
|
+
|
|
4
|
+
// Table tags
|
|
5
|
+
const HEAD = 0x68656164; // 'head'
|
|
6
|
+
const HHEA = 0x68686561; // 'hhea'
|
|
7
|
+
const OS_2 = 0x4f532f32; // 'OS/2'
|
|
8
|
+
|
|
9
|
+
export function parseGlobalMetrics(parser: TtfTableParser): { metrics: TtfMetrics; numberOfHMetricsRaw: number; headBBox?: readonly [number, number, number, number] } {
|
|
10
|
+
const headTable = parser.getTable(HEAD);
|
|
11
|
+
if (!headTable) throw new Error("Missing head table");
|
|
12
|
+
|
|
13
|
+
const unitsPerEm = parser.getUint16(headTable, 18);
|
|
14
|
+
|
|
15
|
+
const hheaTable = parser.getTable(HHEA);
|
|
16
|
+
if (!hheaTable) throw new Error("Missing hhea table");
|
|
17
|
+
|
|
18
|
+
const ascender = parser.getInt16(hheaTable, 4);
|
|
19
|
+
const descender = parser.getInt16(hheaTable, 6);
|
|
20
|
+
const lineGap = parser.getInt16(hheaTable, 8);
|
|
21
|
+
const numberOfHMetricsRaw = parser.getUint16(hheaTable, 34);
|
|
22
|
+
|
|
23
|
+
// Parse OS/2 table for optional cap/x heights
|
|
24
|
+
const os2Table = parser.getTable(OS_2);
|
|
25
|
+
let capHeight = ascender;
|
|
26
|
+
let xHeight = Math.round(ascender * 0.5);
|
|
27
|
+
|
|
28
|
+
// Only use OS/2 capHeight/xHeight if the table exists and version >= 2 (fields present)
|
|
29
|
+
if (os2Table && os2Table.byteLength >= 4) {
|
|
30
|
+
const os2Version = parser.getUint16(os2Table, 0);
|
|
31
|
+
if (os2Version >= 2 && os2Table.byteLength >= 96) {
|
|
32
|
+
capHeight = parser.getInt16(os2Table, 88);
|
|
33
|
+
xHeight = parser.getInt16(os2Table, 86);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Read head bbox (xMin, yMin, xMax, yMax) if present
|
|
38
|
+
let headBBox: readonly [number, number, number, number] | undefined = undefined;
|
|
39
|
+
// head table contains bbox at offsets 36..42 (int16)
|
|
40
|
+
if (headTable.byteLength >= 44) {
|
|
41
|
+
const xMin = parser.getInt16(headTable, 36);
|
|
42
|
+
const yMin = parser.getInt16(headTable, 38);
|
|
43
|
+
const xMax = parser.getInt16(headTable, 40);
|
|
44
|
+
const yMax = parser.getInt16(headTable, 42);
|
|
45
|
+
headBBox = [xMin, yMin, xMax, yMax];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const metrics: TtfMetrics = {
|
|
49
|
+
unitsPerEm,
|
|
50
|
+
ascender,
|
|
51
|
+
descender,
|
|
52
|
+
lineGap,
|
|
53
|
+
capHeight,
|
|
54
|
+
xHeight
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return { metrics, numberOfHMetricsRaw, headBBox };
|
|
58
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { GlyphOutlineCmd } from "../../types/fonts.js";
|
|
2
|
+
import type { GlyphTableProvider } from "./ttf-table-provider.js";
|
|
3
|
+
import type { GlyphOutlineProvider } from "./composite-glyph-parser.js";
|
|
4
|
+
import { LocaTableReader } from "./loca-reader.js";
|
|
5
|
+
import { SimpleGlyphParser } from "./simple-glyph-parser.js";
|
|
6
|
+
import { CompositeGlyphParser } from "./composite-glyph-parser.js";
|
|
7
|
+
|
|
8
|
+
// Table tags
|
|
9
|
+
const LOCA = 0x6c6f6361; // 'loca'
|
|
10
|
+
const GLYF = 0x676c7966; // 'glyf'
|
|
11
|
+
const HEAD = 0x68656164; // 'head'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Minimal TrueType glyf/loca parser.
|
|
15
|
+
*
|
|
16
|
+
* - Supports simple glyphs (contours with on-curve + off-curve quadratic points).
|
|
17
|
+
* - Supports composite glyphs (components with transformations).
|
|
18
|
+
* - Provides robust bounds checks and defensive behavior.
|
|
19
|
+
*
|
|
20
|
+
* Produces a function getGlyphOutline(gid) => GlyphOutlineCmd[] | null
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates a glyph outline provider function for a TrueType font.
|
|
25
|
+
* @param parser - Provider for accessing font tables and reading binary data
|
|
26
|
+
* @returns Function that returns glyph outline commands for a given glyph ID
|
|
27
|
+
*/
|
|
28
|
+
export function createGlyfOutlineProvider(
|
|
29
|
+
parser: GlyphTableProvider
|
|
30
|
+
): (gid: number) => GlyphOutlineCmd[] | null {
|
|
31
|
+
|
|
32
|
+
// Read head table to determine loca format
|
|
33
|
+
const headTable = parser.getTable(HEAD);
|
|
34
|
+
if (!headTable) {
|
|
35
|
+
return () => null; // Cannot determine loca format
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const indexToLocFormat = readIndexToLocFormat(headTable, parser);
|
|
39
|
+
|
|
40
|
+
// Get required tables
|
|
41
|
+
const locaTable = parser.getTable(LOCA);
|
|
42
|
+
const glyfTable = parser.getTable(GLYF);
|
|
43
|
+
|
|
44
|
+
if (!locaTable || !glyfTable) {
|
|
45
|
+
return () => null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Initialize parsers and readers
|
|
49
|
+
const locaReader = new LocaTableReader(locaTable, indexToLocFormat, parser);
|
|
50
|
+
const simpleParser = new SimpleGlyphParser(parser);
|
|
51
|
+
const compositeParser = new CompositeGlyphParser(parser);
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Internal recursive function for getting glyph outlines.
|
|
55
|
+
* @param gid - Glyph ID
|
|
56
|
+
* @param depth - Current recursion depth
|
|
57
|
+
* @returns Glyph outline commands or null
|
|
58
|
+
*/
|
|
59
|
+
const getOutlineInternal = (gid: number, depth: number = 0): GlyphOutlineCmd[] | null => {
|
|
60
|
+
// Get glyph offset range from loca table
|
|
61
|
+
const range = locaReader.getGlyphOffset(gid);
|
|
62
|
+
if (!range) return null;
|
|
63
|
+
|
|
64
|
+
// Check for empty glyph
|
|
65
|
+
if (range.start === range.end) return null;
|
|
66
|
+
|
|
67
|
+
// Validate range against glyf table size
|
|
68
|
+
if (!locaReader.validateRange(range, glyfTable.byteLength)) return null;
|
|
69
|
+
|
|
70
|
+
// Create DataView for this glyph's data
|
|
71
|
+
const view = createGlyphView(glyfTable, range.start, range.end);
|
|
72
|
+
if (!view || view.byteLength < 10) return null;
|
|
73
|
+
|
|
74
|
+
// Read numberOfContours to determine glyph type
|
|
75
|
+
const numberOfContours = view.getInt16(0, false);
|
|
76
|
+
|
|
77
|
+
if (numberOfContours >= 0) {
|
|
78
|
+
// Simple glyph
|
|
79
|
+
return simpleParser.parse(view);
|
|
80
|
+
} else {
|
|
81
|
+
// Composite glyph
|
|
82
|
+
const provider: GlyphOutlineProvider = {
|
|
83
|
+
getOutline: (componentGid: number, componentDepth?: number) =>
|
|
84
|
+
getOutlineInternal(componentGid, componentDepth ?? 0)
|
|
85
|
+
};
|
|
86
|
+
return compositeParser.parse(view, provider, depth);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Return the public provider function
|
|
91
|
+
return (gid: number) => getOutlineInternal(gid, 0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Reads the indexToLocFormat field from the head table.
|
|
96
|
+
* @param headTable - DataView of the head table
|
|
97
|
+
* @param reader - Binary data reader
|
|
98
|
+
* @returns Format value (0 = short, 1 = long)
|
|
99
|
+
*/
|
|
100
|
+
function readIndexToLocFormat(headTable: DataView, reader: GlyphTableProvider): number {
|
|
101
|
+
try {
|
|
102
|
+
// indexToLocFormat is at offset 50 in the head table
|
|
103
|
+
return reader.getUint16(headTable, 50);
|
|
104
|
+
} catch {
|
|
105
|
+
return 0; // Default to short format on error
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Creates a DataView for a specific glyph's data within the glyf table.
|
|
111
|
+
* @param glyfTable - DataView of the glyf table
|
|
112
|
+
* @param start - Start offset
|
|
113
|
+
* @param end - End offset
|
|
114
|
+
* @returns DataView for the glyph data, or null if invalid
|
|
115
|
+
*/
|
|
116
|
+
function createGlyphView(glyfTable: DataView, start: number, end: number): DataView | null {
|
|
117
|
+
try {
|
|
118
|
+
return new DataView(glyfTable.buffer, glyfTable.byteOffset + start, end - start);
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Re-export types for convenience
|
|
125
|
+
export type { GlyphTableProvider } from "./ttf-table-provider.js";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { GlyphMetrics } from "../../types/fonts.js";
|
|
2
|
+
import { TtfTableParser } from "./ttf-table-parser.js";
|
|
3
|
+
|
|
4
|
+
// Table tags
|
|
5
|
+
const HMTX = 0x686d7478; // 'hmtx'
|
|
6
|
+
|
|
7
|
+
export function parseGlyphMetrics(
|
|
8
|
+
parser: TtfTableParser,
|
|
9
|
+
numberOfHMetrics: number,
|
|
10
|
+
numGlyphs: number
|
|
11
|
+
): Map<number, GlyphMetrics> {
|
|
12
|
+
const hmtxTable = parser.getTable(HMTX);
|
|
13
|
+
if (!hmtxTable) throw new Error("Missing hmtx table");
|
|
14
|
+
|
|
15
|
+
const glyphMetrics = new Map<number, GlyphMetrics>();
|
|
16
|
+
|
|
17
|
+
// Validate long metrics length
|
|
18
|
+
const longBytes = numberOfHMetrics * 4;
|
|
19
|
+
if (hmtxTable.byteLength < longBytes) {
|
|
20
|
+
throw new Error("Truncated hmtx long metrics");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Read first numberOfHMetrics entries (advanceWidth + lsb)
|
|
24
|
+
let lastAdvanceWidth = 0;
|
|
25
|
+
for (let gid = 0; gid < numGlyphs; gid++) {
|
|
26
|
+
if (gid < numberOfHMetrics) {
|
|
27
|
+
const off = gid * 4;
|
|
28
|
+
if (off + 4 > hmtxTable.byteLength) throw new Error("Truncated hmtx entry");
|
|
29
|
+
const advanceWidth = parser.getUint16(hmtxTable, off);
|
|
30
|
+
const leftSideBearing = parser.getInt16(hmtxTable, off + 2);
|
|
31
|
+
glyphMetrics.set(gid, { advanceWidth, leftSideBearing });
|
|
32
|
+
lastAdvanceWidth = advanceWidth;
|
|
33
|
+
} else {
|
|
34
|
+
// LSBs follow after long metrics
|
|
35
|
+
const lsbOff = longBytes + (gid - numberOfHMetrics) * 2;
|
|
36
|
+
if (lsbOff + 2 > hmtxTable.byteLength) throw new Error("Truncated hmtx LSB array");
|
|
37
|
+
const leftSideBearing = parser.getInt16(hmtxTable, lsbOff);
|
|
38
|
+
glyphMetrics.set(gid, { advanceWidth: lastAdvanceWidth, leftSideBearing });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return glyphMetrics;
|
|
43
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { TtfFontMetrics } from "../../types/fonts.js";
|
|
3
|
+
import { TtfTableParser } from "./ttf-table-parser.js";
|
|
4
|
+
import { parseGlobalMetrics } from "./ttf-global-metrics.js";
|
|
5
|
+
import { parseGlyphMetrics } from "./ttf-glyph-metrics.js";
|
|
6
|
+
import { CmapParser } from "./ttf-cmap.js";
|
|
7
|
+
import { createGlyfOutlineProvider } from "./ttf-glyf.js";
|
|
8
|
+
import type { KerningMap } from "../../types/fonts.js";
|
|
9
|
+
|
|
10
|
+
export function parseTtfBuffer(buffer: ArrayBuffer): TtfFontMetrics {
|
|
11
|
+
const parser = new TtfTableParser(buffer);
|
|
12
|
+
|
|
13
|
+
// Parse global metrics (head, hhea, OS/2)
|
|
14
|
+
const { metrics, numberOfHMetricsRaw, headBBox } = parseGlobalMetrics(parser);
|
|
15
|
+
|
|
16
|
+
// Parse maxp for numGlyphs
|
|
17
|
+
const MAXP = 0x6d617870; // 'maxp'
|
|
18
|
+
const maxpTable = parser.getTable(MAXP);
|
|
19
|
+
if (!maxpTable) throw new Error("Missing maxp table");
|
|
20
|
+
|
|
21
|
+
const numGlyphs = parser.getUint16(maxpTable, 4);
|
|
22
|
+
|
|
23
|
+
// Clamp numberOfHMetrics to glyph count
|
|
24
|
+
const numberOfHMetrics = Math.min(numberOfHMetricsRaw, numGlyphs);
|
|
25
|
+
|
|
26
|
+
// Parse glyph metrics
|
|
27
|
+
const glyphMetrics = parseGlyphMetrics(parser, numberOfHMetrics, numGlyphs);
|
|
28
|
+
|
|
29
|
+
// Parse cmap
|
|
30
|
+
const CMAP = 0x636d6170; // 'cmap'
|
|
31
|
+
const cmapTable = parser.getTable(CMAP);
|
|
32
|
+
if (!cmapTable) throw new Error("Missing cmap table");
|
|
33
|
+
|
|
34
|
+
const cmap = new CmapParser(parser, cmapTable);
|
|
35
|
+
const kerning = mergeKerningMaps(parseKerningTable(parser), parseGposKerning(parser));
|
|
36
|
+
|
|
37
|
+
const glyfProvider = createGlyfOutlineProvider(parser);
|
|
38
|
+
return new TtfFontMetrics(metrics, glyphMetrics, cmap, headBBox, glyfProvider, kerning);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function parseTtfFont(filePath: string): TtfFontMetrics {
|
|
42
|
+
const buffer = readFileSync(filePath);
|
|
43
|
+
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
44
|
+
return parseTtfBuffer(arrayBuffer);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parse the 'kern' table (format 0) into a nested map of adjustments in font units.
|
|
49
|
+
* Returns undefined when the table is missing or unsupported.
|
|
50
|
+
*/
|
|
51
|
+
function parseKerningTable(parser: TtfTableParser): KerningMap | undefined {
|
|
52
|
+
const KERN = 0x6b65726e; // 'kern'
|
|
53
|
+
const table = parser.getTable(KERN);
|
|
54
|
+
if (!table) return undefined;
|
|
55
|
+
|
|
56
|
+
// kern header: version (uint16), nTables (uint16)
|
|
57
|
+
if (table.byteLength < 4) return undefined;
|
|
58
|
+
const nTables = table.getUint16(2, false);
|
|
59
|
+
let offset = 4;
|
|
60
|
+
const result: KerningMap = new Map();
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < nTables; i++) {
|
|
63
|
+
if (offset + 6 > table.byteLength) break;
|
|
64
|
+
const subtableVersion = table.getUint16(offset, false);
|
|
65
|
+
const length = table.getUint16(offset + 2, false);
|
|
66
|
+
const coverage = table.getUint16(offset + 4, false);
|
|
67
|
+
const format = coverage >> 8;
|
|
68
|
+
// Only support format 0 (uni-directional kerning pairs)
|
|
69
|
+
if (subtableVersion === 0 && format === 0) {
|
|
70
|
+
if (offset + length > table.byteLength) break;
|
|
71
|
+
const stOffset = offset + 6;
|
|
72
|
+
if (stOffset + 8 > table.byteLength) break;
|
|
73
|
+
const pairCount = table.getUint16(stOffset, false);
|
|
74
|
+
// skip searchRange, entrySelector, rangeShift (next 6 bytes)
|
|
75
|
+
let cursor = stOffset + 8;
|
|
76
|
+
for (let p = 0; p < pairCount; p++) {
|
|
77
|
+
if (cursor + 6 > table.byteLength) break;
|
|
78
|
+
const left = table.getUint16(cursor, false);
|
|
79
|
+
const right = table.getUint16(cursor + 2, false);
|
|
80
|
+
const value = table.getInt16(cursor + 4, false);
|
|
81
|
+
if (value !== 0) {
|
|
82
|
+
const rightMap = result.get(left) ?? new Map<number, number>();
|
|
83
|
+
rightMap.set(right, value);
|
|
84
|
+
result.set(left, rightMap);
|
|
85
|
+
}
|
|
86
|
+
cursor += 6;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
offset += length;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return result.size > 0 ? result : undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Parse GPOS PairPos (format 1) kerning into a nested map.
|
|
97
|
+
* Supports value1 xAdvance/xPlacement adjustments; ignores class-based and other lookup types.
|
|
98
|
+
*/
|
|
99
|
+
function parseGposKerning(parser: TtfTableParser): KerningMap | undefined {
|
|
100
|
+
const GPOS = 0x47504f53; // 'GPOS'
|
|
101
|
+
const table = parser.getTable(GPOS);
|
|
102
|
+
if (!table) return undefined;
|
|
103
|
+
|
|
104
|
+
// Header
|
|
105
|
+
if (table.byteLength < 10) return undefined;
|
|
106
|
+
const gposStart = 0;
|
|
107
|
+
const lookupListOffset = table.getUint16(gposStart + 8, false);
|
|
108
|
+
if (lookupListOffset === 0 || lookupListOffset >= table.byteLength) return undefined;
|
|
109
|
+
|
|
110
|
+
const kerning: KerningMap = new Map();
|
|
111
|
+
|
|
112
|
+
// Lookup list
|
|
113
|
+
const lookupCount = table.getUint16(gposStart + lookupListOffset, false);
|
|
114
|
+
let lookupOffsetsPos = gposStart + lookupListOffset + 2;
|
|
115
|
+
for (let li = 0; li < lookupCount; li++) {
|
|
116
|
+
if (lookupOffsetsPos + 2 > table.byteLength) break;
|
|
117
|
+
const lookupOffset = table.getUint16(lookupOffsetsPos, false);
|
|
118
|
+
lookupOffsetsPos += 2;
|
|
119
|
+
if (lookupOffset === 0 || gposStart + lookupOffset >= table.byteLength) continue;
|
|
120
|
+
|
|
121
|
+
const lookupStart = gposStart + lookupOffset;
|
|
122
|
+
if (lookupStart + 6 > table.byteLength) continue;
|
|
123
|
+
const lookupType = table.getUint16(lookupStart, false);
|
|
124
|
+
const subTableCount = table.getUint16(lookupStart + 4, false);
|
|
125
|
+
let subTableOffsetsPos = lookupStart + 6;
|
|
126
|
+
|
|
127
|
+
if (lookupType !== 2) {
|
|
128
|
+
// Only Pair Adjustment lookups
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (let si = 0; si < subTableCount; si++) {
|
|
133
|
+
if (subTableOffsetsPos + 2 > table.byteLength) break;
|
|
134
|
+
const stOffset = table.getUint16(subTableOffsetsPos, false);
|
|
135
|
+
subTableOffsetsPos += 2;
|
|
136
|
+
if (stOffset === 0 || lookupStart + stOffset >= table.byteLength) continue;
|
|
137
|
+
const stStart = lookupStart + stOffset;
|
|
138
|
+
if (stStart + 10 > table.byteLength) continue;
|
|
139
|
+
|
|
140
|
+
const format = table.getUint16(stStart, false);
|
|
141
|
+
if (format !== 1) {
|
|
142
|
+
// Only handle PairPos format 1 (per-glyph pair sets)
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const coverageOffset = table.getUint16(stStart + 2, false);
|
|
147
|
+
const valueFormat1 = table.getUint16(stStart + 4, false);
|
|
148
|
+
const valueFormat2 = table.getUint16(stStart + 6, false);
|
|
149
|
+
const pairSetCount = table.getUint16(stStart + 8, false);
|
|
150
|
+
|
|
151
|
+
const coverage = parseCoverageTable(table, stStart + coverageOffset);
|
|
152
|
+
const pairSetOffsetsStart = stStart + 10;
|
|
153
|
+
|
|
154
|
+
for (let pi = 0; pi < pairSetCount; pi++) {
|
|
155
|
+
const leftGlyph = coverage[pi];
|
|
156
|
+
if (leftGlyph === undefined) continue;
|
|
157
|
+
|
|
158
|
+
const pairSetOffsetPos = pairSetOffsetsStart + pi * 2;
|
|
159
|
+
if (pairSetOffsetPos + 2 > table.byteLength) break;
|
|
160
|
+
const pairSetOffset = table.getUint16(pairSetOffsetPos, false);
|
|
161
|
+
if (pairSetOffset === 0 || stStart + pairSetOffset >= table.byteLength) continue;
|
|
162
|
+
|
|
163
|
+
const pairSetStart = stStart + pairSetOffset;
|
|
164
|
+
if (pairSetStart + 2 > table.byteLength) continue;
|
|
165
|
+
const pairValueCount = table.getUint16(pairSetStart, false);
|
|
166
|
+
let recPos = pairSetStart + 2;
|
|
167
|
+
|
|
168
|
+
for (let r = 0; r < pairValueCount; r++) {
|
|
169
|
+
if (recPos + 2 > table.byteLength) break;
|
|
170
|
+
const rightGlyph = table.getUint16(recPos, false);
|
|
171
|
+
recPos += 2;
|
|
172
|
+
|
|
173
|
+
const val1 = readValueRecord(table, recPos, valueFormat1);
|
|
174
|
+
recPos += val1.length;
|
|
175
|
+
|
|
176
|
+
const val2 = readValueRecord(table, recPos, valueFormat2);
|
|
177
|
+
recPos += val2.length;
|
|
178
|
+
|
|
179
|
+
const adjust = (val1.xAdvance ?? 0) + (val1.xPlacement ?? 0) + (val2.xPlacement ?? 0);
|
|
180
|
+
if (adjust !== 0) {
|
|
181
|
+
const rightMap = kerning.get(leftGlyph) ?? new Map<number, number>();
|
|
182
|
+
rightMap.set(rightGlyph, adjust);
|
|
183
|
+
kerning.set(leftGlyph, rightMap);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return kerning.size > 0 ? kerning : undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function parseCoverageTable(table: DataView, offset: number): number[] {
|
|
194
|
+
if (offset + 4 > table.byteLength) return [];
|
|
195
|
+
const format = table.getUint16(offset, false);
|
|
196
|
+
if (format === 1) {
|
|
197
|
+
const count = table.getUint16(offset + 2, false);
|
|
198
|
+
const glyphs: number[] = [];
|
|
199
|
+
let pos = offset + 4;
|
|
200
|
+
for (let i = 0; i < count; i++) {
|
|
201
|
+
if (pos + 2 > table.byteLength) break;
|
|
202
|
+
glyphs.push(table.getUint16(pos, false));
|
|
203
|
+
pos += 2;
|
|
204
|
+
}
|
|
205
|
+
return glyphs;
|
|
206
|
+
}
|
|
207
|
+
if (format === 2) {
|
|
208
|
+
const rangeCount = table.getUint16(offset + 2, false);
|
|
209
|
+
const glyphs: number[] = [];
|
|
210
|
+
let pos = offset + 4;
|
|
211
|
+
for (let i = 0; i < rangeCount; i++) {
|
|
212
|
+
if (pos + 6 > table.byteLength) break;
|
|
213
|
+
const start = table.getUint16(pos, false);
|
|
214
|
+
const end = table.getUint16(pos + 2, false);
|
|
215
|
+
const startCoverage = table.getUint16(pos + 4, false);
|
|
216
|
+
for (let g = 0; g <= end - start; g++) {
|
|
217
|
+
glyphs[startCoverage + g] = start + g;
|
|
218
|
+
}
|
|
219
|
+
pos += 6;
|
|
220
|
+
}
|
|
221
|
+
return glyphs;
|
|
222
|
+
}
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function readValueRecord(table: DataView, offset: number, valueFormat: number): { xAdvance?: number; xPlacement?: number; length: number } {
|
|
227
|
+
let pos = offset;
|
|
228
|
+
let xPlacement: number | undefined;
|
|
229
|
+
let xAdvance: number | undefined;
|
|
230
|
+
|
|
231
|
+
const consume = (flag: number) => {
|
|
232
|
+
const v = table.getInt16(pos, false);
|
|
233
|
+
pos += 2;
|
|
234
|
+
return v;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
if (valueFormat & 0x0001) xPlacement = consume(0x0001);
|
|
238
|
+
if (valueFormat & 0x0002) consume(0x0002); // yPlacement (ignored)
|
|
239
|
+
if (valueFormat & 0x0004) xAdvance = consume(0x0004);
|
|
240
|
+
if (valueFormat & 0x0008) consume(0x0008); // yAdvance (ignored)
|
|
241
|
+
|
|
242
|
+
// Skip Device/Variation tables if present (we don't apply them; consume offsets)
|
|
243
|
+
const deviceFlags = [0x0010, 0x0020, 0x0040, 0x0080];
|
|
244
|
+
for (const flag of deviceFlags) {
|
|
245
|
+
if (valueFormat & flag) {
|
|
246
|
+
pos += 2;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return { xAdvance, xPlacement, length: pos - offset };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function mergeKerningMaps(a?: KerningMap, b?: KerningMap): KerningMap | undefined {
|
|
254
|
+
if (!a && !b) return undefined;
|
|
255
|
+
const result: KerningMap = new Map();
|
|
256
|
+
const add = (map?: KerningMap) => {
|
|
257
|
+
if (!map) return;
|
|
258
|
+
for (const [left, rights] of map.entries()) {
|
|
259
|
+
const target = result.get(left) ?? new Map<number, number>();
|
|
260
|
+
for (const [right, val] of rights.entries()) {
|
|
261
|
+
target.set(right, (target.get(right) ?? 0) + val);
|
|
262
|
+
}
|
|
263
|
+
result.set(left, target);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
add(a);
|
|
267
|
+
add(b);
|
|
268
|
+
return result;
|
|
269
|
+
}
|