pagyra-js 0.0.19 → 0.0.21
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/README.md +55 -0
- package/dist/assets/fonts/licenses/selawik/SIL Open Font License.txt +43 -0
- package/dist/assets/fonts/ttf/arimo/Arimo-Bold.ttf +0 -0
- package/dist/assets/fonts/ttf/arimo/Arimo-BoldItalic.ttf +0 -0
- package/dist/assets/fonts/ttf/arimo/Arimo-Italic.ttf +0 -0
- package/dist/assets/fonts/ttf/arimo/Arimo-Regular.ttf +0 -0
- package/dist/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Black.ttf +0 -0
- package/dist/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Bold.ttf +0 -0
- package/dist/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Regular.ttf +0 -0
- package/dist/assets/fonts/ttf/dejavu/DejaVuSans.ttf +0 -0
- package/dist/assets/fonts/ttf/firecode/FiraCode-Bold.ttf +0 -0
- package/dist/assets/fonts/ttf/firecode/FiraCode-Light.ttf +0 -0
- package/dist/assets/fonts/ttf/firecode/FiraCode-Medium.ttf +0 -0
- package/dist/assets/fonts/ttf/firecode/FiraCode-Regular.ttf +0 -0
- package/dist/assets/fonts/ttf/firecode/FiraCode-SemiBold.ttf +0 -0
- package/dist/assets/fonts/ttf/notoemoji/NotoEmoji-Bold.ttf +0 -0
- package/dist/assets/fonts/ttf/notoemoji/NotoEmoji-Light.ttf +0 -0
- package/dist/assets/fonts/ttf/notoemoji/NotoEmoji-Medium.ttf +0 -0
- package/dist/assets/fonts/ttf/notoemoji/NotoEmoji-Regular.ttf +0 -0
- package/dist/assets/fonts/ttf/notoemoji/NotoEmoji-SemiBold.ttf +0 -0
- package/dist/assets/fonts/ttf/notosans/NotoSans-Regular.ttf +0 -0
- package/dist/assets/fonts/ttf/roboto/Roboto-Bold.ttf +0 -0
- package/dist/assets/fonts/ttf/roboto/Roboto-BoldItalic.ttf +0 -0
- package/dist/assets/fonts/ttf/roboto/Roboto-Italic.ttf +0 -0
- package/dist/assets/fonts/ttf/roboto/Roboto-Regular.ttf +0 -0
- package/dist/assets/fonts/ttf/selawik/selawk.ttf +0 -0
- package/dist/assets/fonts/ttf/selawik/selawkb.ttf +0 -0
- package/dist/assets/fonts/ttf/selawik/selawkl.ttf +0 -0
- package/dist/assets/fonts/ttf/selawik/selawksb.ttf +0 -0
- package/dist/assets/fonts/ttf/selawik/selawksl.ttf +0 -0
- package/dist/assets/fonts/ttf/stixtwomath/STIXTwoMath-Regular.ttf +0 -0
- package/dist/assets/fonts/ttf/tinos/Tinos-Bold.ttf +0 -0
- package/dist/assets/fonts/ttf/tinos/Tinos-BoldItalic.ttf +0 -0
- package/dist/assets/fonts/ttf/tinos/Tinos-Italic.ttf +0 -0
- package/dist/assets/fonts/ttf/tinos/Tinos-Regular.ttf +0 -0
- package/dist/assets/fonts/woff/lato/lato-latin-400-italic.woff +0 -0
- package/dist/assets/fonts/woff/lato/lato-latin-400-normal.woff +0 -0
- package/dist/assets/fonts/woff/lato/lato-latin-700-italic.woff +0 -0
- package/dist/assets/fonts/woff/lato/lato-latin-700-normal.woff +0 -0
- package/dist/assets/fonts/woff2/caveat/Caveat-Bold.woff2 +0 -0
- package/dist/assets/fonts/woff2/caveat/Caveat-Regular.woff2 +0 -0
- package/dist/assets/fonts/woff2/lato/lato-latin-400-italic.woff2 +0 -0
- package/dist/assets/fonts/woff2/lato/lato-latin-400-normal.woff2 +0 -0
- package/dist/assets/fonts/woff2/lato/lato-latin-700-italic.woff2 +0 -0
- package/dist/assets/fonts/woff2/lato/lato-latin-700-normal.woff2 +0 -0
- package/dist/browser/pagyra.min.js +34 -34
- package/dist/browser/pagyra.min.js.map +4 -4
- package/dist/playground/server.js +2 -0
- package/dist/src/css/compute-style/base-options.d.ts +7 -0
- package/dist/src/css/compute-style/base-options.js +24 -0
- package/dist/src/css/compute-style/declarations.d.ts +10 -0
- package/dist/src/css/compute-style/declarations.js +77 -0
- package/dist/src/css/compute-style/decoration.d.ts +8 -0
- package/dist/src/css/compute-style/decoration.js +55 -0
- package/dist/src/css/compute-style/defaults.d.ts +3 -0
- package/dist/src/css/compute-style/defaults.js +34 -0
- package/dist/src/css/compute-style/display.d.ts +3 -0
- package/dist/src/css/compute-style/display.js +85 -0
- package/dist/src/css/compute-style/float.d.ts +2 -0
- package/dist/src/css/compute-style/float.js +13 -0
- package/dist/src/css/compute-style/font.d.ts +12 -0
- package/dist/src/css/compute-style/font.js +57 -0
- package/dist/src/css/compute-style/overrides.d.ts +3 -0
- package/dist/src/css/compute-style/overrides.js +241 -0
- package/dist/src/css/compute-style.d.ts +2 -0
- package/dist/src/css/compute-style.js +34 -487
- package/dist/src/css/enums.d.ts +4 -0
- package/dist/src/css/enums.js +5 -0
- package/dist/src/css/layout-property-resolver.js +30 -18
- package/dist/src/css/length.d.ts +26 -2
- package/dist/src/css/length.js +48 -0
- package/dist/src/css/parsers/background-parser.js +1 -1
- package/dist/src/css/parsers/calc-parser.d.ts +2 -0
- package/dist/src/css/parsers/calc-parser.js +310 -0
- package/dist/src/css/parsers/content-parser.d.ts +2 -1
- package/dist/src/css/parsers/content-parser.js +7 -2
- package/dist/src/css/parsers/dimension-parser.js +37 -18
- package/dist/src/css/parsers/display-flex-parser.d.ts +4 -0
- package/dist/src/css/parsers/display-flex-parser.js +97 -0
- package/dist/src/css/parsers/filter-parser.d.ts +14 -0
- package/dist/src/css/parsers/filter-parser.js +255 -0
- package/dist/src/css/parsers/grid-parser-extended.d.ts +1 -0
- package/dist/src/css/parsers/grid-parser-extended.js +40 -1
- package/dist/src/css/parsers/grid-parser.d.ts +5 -2
- package/dist/src/css/parsers/grid-parser.js +71 -7
- package/dist/src/css/parsers/length-parser.d.ts +8 -3
- package/dist/src/css/parsers/length-parser.js +45 -2
- package/dist/src/css/parsers/margin-block-parser.js +3 -3
- package/dist/src/css/parsers/margin-parser.js +3 -3
- package/dist/src/css/parsers/padding-block-parser.js +3 -3
- package/dist/src/css/parsers/padding-inline-parser.js +3 -3
- package/dist/src/css/parsers/padding-parser.js +6 -6
- package/dist/src/css/parsers/position-parser.js +2 -22
- package/dist/src/css/parsers/register-parsers.js +29 -2
- package/dist/src/css/parsers/word-break-parser.d.ts +2 -0
- package/dist/src/css/parsers/word-break-parser.js +23 -0
- package/dist/src/css/properties/grid.d.ts +16 -2
- package/dist/src/css/properties/layout.d.ts +3 -1
- package/dist/src/css/properties/layout.js +1 -1
- package/dist/src/css/properties/misc.d.ts +5 -0
- package/dist/src/css/properties/typography.d.ts +3 -0
- package/dist/src/css/properties/visual.d.ts +36 -0
- package/dist/src/css/shorthands/box-shorthand.d.ts +2 -2
- package/dist/src/css/style-inheritance.d.ts +2 -1
- package/dist/src/css/style-inheritance.js +1 -0
- package/dist/src/css/style.d.ts +30 -10
- package/dist/src/css/style.js +8 -1
- package/dist/src/css/ua-defaults/base-defaults.d.ts +1 -0
- package/dist/src/css/ua-defaults/base-defaults.js +10 -1
- package/dist/src/css/ua-defaults/element-defaults.js +0 -2
- package/dist/src/html/css/parse-css.d.ts +2 -0
- package/dist/src/html/css/parse-css.js +32 -3
- package/dist/src/html/dom-converter/background-images.d.ts +3 -0
- package/dist/src/html/dom-converter/background-images.js +88 -0
- package/dist/src/html/dom-converter/convert-dom-node.d.ts +5 -0
- package/dist/src/html/dom-converter/convert-dom-node.js +81 -0
- package/dist/src/html/dom-converter/handlers/br-handler.d.ts +2 -0
- package/dist/src/html/dom-converter/handlers/br-handler.js +20 -0
- package/dist/src/html/dom-converter/handlers/form-control-handler.d.ts +2 -0
- package/dist/src/html/dom-converter/handlers/form-control-handler.js +28 -0
- package/dist/src/html/dom-converter/handlers/img-handler.d.ts +2 -0
- package/dist/src/html/dom-converter/handlers/img-handler.js +4 -0
- package/dist/src/html/dom-converter/handlers/index.d.ts +4 -0
- package/dist/src/html/dom-converter/handlers/index.js +19 -0
- package/dist/src/html/dom-converter/handlers/svg-handler.d.ts +2 -0
- package/dist/src/html/dom-converter/handlers/svg-handler.js +32 -0
- package/dist/src/html/dom-converter/handlers/types.d.ts +12 -0
- package/dist/src/html/dom-converter/handlers/types.js +2 -0
- package/dist/src/html/dom-converter/helpers.d.ts +7 -0
- package/dist/src/html/dom-converter/helpers.js +35 -0
- package/dist/src/html/dom-converter/index.d.ts +1 -0
- package/dist/src/html/dom-converter/index.js +1 -0
- package/dist/src/html/dom-converter/pseudo-elements.d.ts +6 -0
- package/dist/src/html/dom-converter/pseudo-elements.js +48 -0
- package/dist/src/html/dom-converter/text.d.ts +15 -0
- package/dist/src/html/dom-converter/text.js +170 -0
- package/dist/src/html/dom-converter.d.ts +1 -5
- package/dist/src/html/dom-converter.js +1 -412
- package/dist/src/html/image-converter.d.ts +5 -0
- package/dist/src/html/image-converter.js +8 -3
- package/dist/src/html-to-pdf/document-css.d.ts +14 -0
- package/dist/src/html-to-pdf/document-css.js +45 -0
- package/dist/src/html-to-pdf/fonts.d.ts +16 -0
- package/dist/src/html-to-pdf/fonts.js +74 -0
- package/dist/src/html-to-pdf/header-footer.d.ts +14 -0
- package/dist/src/html-to-pdf/header-footer.js +101 -0
- package/dist/src/html-to-pdf/html-parser.d.ts +6 -0
- package/dist/src/html-to-pdf/html-parser.js +81 -0
- package/dist/src/html-to-pdf/index.d.ts +3 -0
- package/dist/src/html-to-pdf/index.js +2 -0
- package/dist/src/html-to-pdf/layout-build.d.ts +37 -0
- package/dist/src/html-to-pdf/layout-build.js +73 -0
- package/dist/src/html-to-pdf/prepare-html-render.d.ts +2 -0
- package/dist/src/html-to-pdf/prepare-html-render.js +121 -0
- package/dist/src/html-to-pdf/render-finalize.d.ts +15 -0
- package/dist/src/html-to-pdf/render-finalize.js +27 -0
- package/dist/src/html-to-pdf/render-html-to-pdf.d.ts +3 -0
- package/dist/src/html-to-pdf/render-html-to-pdf.js +25 -0
- package/dist/src/html-to-pdf/resource-loader.d.ts +6 -0
- package/dist/src/html-to-pdf/resource-loader.js +120 -0
- package/dist/src/html-to-pdf/types.d.ts +38 -0
- package/dist/src/html-to-pdf/types.js +2 -0
- package/dist/src/html-to-pdf.d.ts +1 -37
- package/dist/src/html-to-pdf.js +1 -537
- package/dist/src/image/js-png-backend.d.ts +7 -0
- package/dist/src/image/js-png-backend.js +9 -0
- package/dist/src/image/png-backend.d.ts +5 -0
- package/dist/src/image/png-backend.js +1 -0
- package/dist/src/image/png-wasm-loader.d.ts +5 -0
- package/dist/src/image/png-wasm-loader.js +59 -0
- package/dist/src/image/wasm/png_decoder_wasm.d.ts +8 -0
- package/dist/src/image/wasm/png_decoder_wasm.js +24 -0
- package/dist/src/image/wasm/png_decoder_wasm_bg.js +16 -0
- package/dist/src/image/wasm-png-backend.d.ts +6 -0
- package/dist/src/image/wasm-png-backend.js +17 -0
- package/dist/src/layout/counter.d.ts +1 -2
- package/dist/src/layout/counter.js +18 -18
- package/dist/src/layout/inline/inline-utils.d.ts +1 -1
- package/dist/src/layout/inline/inline-utils.js +8 -7
- package/dist/src/layout/inline/layout.js +16 -3
- package/dist/src/layout/inline/run-placer.d.ts +1 -0
- package/dist/src/layout/inline/run-placer.js +2 -10
- package/dist/src/layout/pipeline/out-of-flow-manager.js +25 -1
- package/dist/src/layout/strategies/block.js +35 -24
- package/dist/src/layout/strategies/flex.js +305 -61
- package/dist/src/layout/strategies/form.d.ts +2 -0
- package/dist/src/layout/strategies/form.js +38 -13
- package/dist/src/layout/strategies/grid.js +166 -29
- package/dist/src/layout/strategies/image.js +53 -27
- package/dist/src/layout/strategies/inline.js +26 -21
- package/dist/src/layout/strategies/table.js +26 -18
- package/dist/src/layout/utils/content-measurer.d.ts +1 -1
- package/dist/src/layout/utils/content-measurer.js +8 -7
- package/dist/src/layout/utils/floats.d.ts +1 -0
- package/dist/src/layout/utils/floats.js +14 -12
- package/dist/src/layout/utils/margin.d.ts +4 -4
- package/dist/src/layout/utils/margin.js +20 -16
- package/dist/src/layout/utils/node-math.d.ts +12 -6
- package/dist/src/layout/utils/node-math.js +71 -41
- package/dist/src/layout/utils/sizing.js +2 -1
- package/dist/src/pdf/font-subset/font-registry.d.ts +6 -0
- package/dist/src/pdf/font-subset/font-registry.js +30 -2
- package/dist/src/pdf/header-footer-painter.d.ts +2 -0
- package/dist/src/pdf/header-footer-painter.js +52 -4
- package/dist/src/pdf/header-footer-renderer.js +12 -1
- package/dist/src/pdf/layout-tree-builder.js +5 -1
- package/dist/src/pdf/page-painter.js +13 -0
- package/dist/src/pdf/pagination.js +2 -2
- package/dist/src/pdf/renderer/box-painter.js +28 -3
- package/dist/src/pdf/renderer/page-paint.js +12 -3
- package/dist/src/pdf/renderers/radius-utils.js +31 -38
- package/dist/src/pdf/renderers/shape-renderer.js +1 -1
- package/dist/src/pdf/renderers/shape-utils.js +1 -1
- package/dist/src/pdf/renderers/text-renderer.d.ts +9 -1
- package/dist/src/pdf/renderers/text-renderer.js +36 -2
- package/dist/src/pdf/stacking/build-stacking-contexts.js +1 -2
- package/dist/src/pdf/stacking/resolve-paint-order.d.ts +5 -6
- package/dist/src/pdf/stacking/resolve-paint-order.js +29 -9
- package/dist/src/pdf/stacking/types.d.ts +14 -0
- package/dist/src/pdf/svg/shape-renderer.js +47 -20
- package/dist/src/pdf/types.d.ts +7 -1
- package/dist/src/pdf/utils/border-radius-utils.js +31 -38
- package/dist/src/pdf/utils/color-utils.js +17 -2
- package/dist/src/pdf/utils/filter-utils.d.ts +29 -0
- package/dist/src/pdf/utils/filter-utils.js +85 -0
- package/dist/src/pdf/utils/node-text-run-factory.js +1 -1
- package/dist/src/pdf/utils/text-layout-adjuster.d.ts +0 -8
- package/dist/src/pdf/utils/text-layout-adjuster.js +12 -9
- package/dist/src/shim/css-browser.d.ts +14 -9
- package/dist/src/shim/css-browser.js +50 -39
- package/dist/src/units/units.d.ts +1 -1
- package/dist/tests/css/box-sizing.spec.d.ts +1 -0
- package/dist/tests/css/box-sizing.spec.js +46 -0
- package/dist/tests/css/calc-parser.spec.d.ts +1 -0
- package/dist/tests/css/calc-parser.spec.js +68 -0
- package/dist/tests/css/container-query-units.spec.d.ts +1 -0
- package/dist/tests/css/container-query-units.spec.js +64 -0
- package/dist/tests/css/content-parser.spec.js +13 -0
- package/dist/tests/css/filter-parser.spec.d.ts +1 -0
- package/dist/tests/css/filter-parser.spec.js +116 -0
- package/dist/tests/css/flex-shorthand.spec.d.ts +1 -0
- package/dist/tests/css/flex-shorthand.spec.js +45 -0
- package/dist/tests/css/grid-clamp.spec.d.ts +1 -0
- package/dist/tests/css/grid-clamp.spec.js +82 -0
- package/dist/tests/css/parse-css-pseudo.spec.d.ts +1 -0
- package/dist/tests/css/parse-css-pseudo.spec.js +26 -0
- package/dist/tests/helpers/render-utils.d.ts +18 -2
- package/dist/tests/helpers/render-utils.js +25 -12
- package/dist/tests/html/dom-converter-pseudo-elements.spec.d.ts +1 -0
- package/dist/tests/html/dom-converter-pseudo-elements.spec.js +33 -0
- package/dist/tests/html/dom-converter-text.spec.d.ts +1 -0
- package/dist/tests/html/dom-converter-text.spec.js +67 -0
- package/dist/tests/image/png-backend.spec.d.ts +1 -0
- package/dist/tests/image/png-backend.spec.js +34 -0
- package/dist/tests/layout/box-sizing.spec.d.ts +1 -0
- package/dist/tests/layout/box-sizing.spec.js +75 -0
- package/dist/tests/layout/calc-padding.spec.d.ts +1 -0
- package/dist/tests/layout/calc-padding.spec.js +19 -0
- package/dist/tests/layout/container-query-units.spec.d.ts +1 -0
- package/dist/tests/layout/container-query-units.spec.js +24 -0
- package/dist/tests/layout/flex-auto-height.spec.d.ts +1 -0
- package/dist/tests/layout/flex-auto-height.spec.js +35 -0
- package/dist/tests/layout/flex-wrap-cards.spec.d.ts +1 -0
- package/dist/tests/layout/flex-wrap-cards.spec.js +16 -0
- package/dist/tests/layout/flex-wrap-grow-align-content.spec.d.ts +1 -0
- package/dist/tests/layout/flex-wrap-grow-align-content.spec.js +20 -0
- package/dist/tests/layout/grid-clamp-gap.spec.d.ts +1 -0
- package/dist/tests/layout/grid-clamp-gap.spec.js +22 -0
- package/dist/tests/layout/inline-fragments.spec.js +38 -0
- package/dist/tests/layout/paged-body-margin.spec.d.ts +1 -0
- package/dist/tests/layout/paged-body-margin.spec.js +92 -0
- package/dist/tests/layout/pseudo-counters-generated-content.spec.d.ts +1 -0
- package/dist/tests/layout/pseudo-counters-generated-content.spec.js +51 -0
- package/dist/tests/layout/responsive-clamp-grid-parity.spec.d.ts +1 -0
- package/dist/tests/layout/responsive-clamp-grid-parity.spec.js +75 -0
- package/dist/tests/layout/run-placer-baseline.spec.js +13 -11
- package/dist/tests/pdf/backdrop-filter-noop.spec.d.ts +1 -0
- package/dist/tests/pdf/backdrop-filter-noop.spec.js +140 -0
- package/dist/tests/pdf/filter-drop-shadow.spec.d.ts +1 -0
- package/dist/tests/pdf/filter-drop-shadow.spec.js +74 -0
- package/dist/tests/pdf/filter-opacity.spec.d.ts +1 -0
- package/dist/tests/pdf/filter-opacity.spec.js +30 -0
- package/dist/tests/pdf/font-subset-registry-key.spec.d.ts +1 -0
- package/dist/tests/pdf/font-subset-registry-key.spec.js +66 -0
- package/dist/tests/pdf/header-footer-clip-overflow.spec.d.ts +1 -0
- package/dist/tests/pdf/header-footer-clip-overflow.spec.js +45 -0
- package/dist/tests/pdf/selawik-opt-in.spec.d.ts +1 -0
- package/dist/tests/pdf/selawik-opt-in.spec.js +106 -0
- package/dist/tests/pdf/system-ui-fallback-subset-regression.spec.d.ts +1 -0
- package/dist/tests/pdf/system-ui-fallback-subset-regression.spec.js +39 -0
- package/dist/tests/pdf/text-renderer-fallback.spec.js +55 -0
- package/dist/tests/pdf/text-transform-matrix.spec.js +8 -7
- package/package.json +2 -2
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { FontConfig } from "../types/fonts.js";
|
|
2
|
+
import type { LogLevel } from "../logging/debug.js";
|
|
3
|
+
import { LayoutNode } from "../dom/node.js";
|
|
4
|
+
import { buildRenderTree } from "../pdf/layout-tree-builder.js";
|
|
5
|
+
import type { PageMarginsPx } from "../units/page-utils.js";
|
|
6
|
+
import type { HeaderFooterHTML } from "../pdf/types.js";
|
|
7
|
+
import type { Environment } from "../environment/environment.js";
|
|
8
|
+
export type PagedBodyMarginMode = "auto" | "zero";
|
|
9
|
+
export type InterBlockWhitespaceMode = "collapse" | "preserve";
|
|
10
|
+
export interface RenderHtmlOptions {
|
|
11
|
+
html: string;
|
|
12
|
+
css?: string;
|
|
13
|
+
viewportWidth?: number;
|
|
14
|
+
viewportHeight?: number;
|
|
15
|
+
pageWidth?: number;
|
|
16
|
+
pageHeight?: number;
|
|
17
|
+
margins?: Partial<PageMarginsPx>;
|
|
18
|
+
debug?: boolean;
|
|
19
|
+
debugLevel?: LogLevel;
|
|
20
|
+
debugCats?: string[];
|
|
21
|
+
fontConfig?: FontConfig;
|
|
22
|
+
resourceBaseDir?: string;
|
|
23
|
+
assetRootDir?: string;
|
|
24
|
+
headerFooter?: Partial<HeaderFooterHTML>;
|
|
25
|
+
pagedBodyMargin?: PagedBodyMarginMode;
|
|
26
|
+
interBlockWhitespace?: InterBlockWhitespaceMode;
|
|
27
|
+
/** Environment abstraction (Node/browser). Defaults to Node implementation. */
|
|
28
|
+
environment?: Environment;
|
|
29
|
+
}
|
|
30
|
+
export interface PreparedRender {
|
|
31
|
+
layoutRoot: LayoutNode;
|
|
32
|
+
renderTree: ReturnType<typeof buildRenderTree>;
|
|
33
|
+
pageSize: {
|
|
34
|
+
widthPt: number;
|
|
35
|
+
heightPt: number;
|
|
36
|
+
};
|
|
37
|
+
margins: PageMarginsPx;
|
|
38
|
+
}
|
|
@@ -1,37 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { type LogLevel } from "./logging/debug.js";
|
|
3
|
-
import { LayoutNode } from "./dom/node.js";
|
|
4
|
-
import { buildRenderTree } from "./pdf/layout-tree-builder.js";
|
|
5
|
-
import { type PageMarginsPx } from "./units/page-utils.js";
|
|
6
|
-
import type { HeaderFooterHTML } from "./pdf/types.js";
|
|
7
|
-
import type { Environment } from "./environment/environment.js";
|
|
8
|
-
export interface RenderHtmlOptions {
|
|
9
|
-
html: string;
|
|
10
|
-
css?: string;
|
|
11
|
-
viewportWidth?: number;
|
|
12
|
-
viewportHeight?: number;
|
|
13
|
-
pageWidth?: number;
|
|
14
|
-
pageHeight?: number;
|
|
15
|
-
margins?: Partial<PageMarginsPx>;
|
|
16
|
-
debug?: boolean;
|
|
17
|
-
debugLevel?: LogLevel;
|
|
18
|
-
debugCats?: string[];
|
|
19
|
-
fontConfig?: FontConfig;
|
|
20
|
-
resourceBaseDir?: string;
|
|
21
|
-
assetRootDir?: string;
|
|
22
|
-
headerFooter?: Partial<HeaderFooterHTML>;
|
|
23
|
-
/** Environment abstraction (Node/browser). Defaults to Node implementation. */
|
|
24
|
-
environment?: Environment;
|
|
25
|
-
}
|
|
26
|
-
export interface PreparedRender {
|
|
27
|
-
layoutRoot: LayoutNode;
|
|
28
|
-
renderTree: ReturnType<typeof buildRenderTree>;
|
|
29
|
-
pageSize: {
|
|
30
|
-
widthPt: number;
|
|
31
|
-
heightPt: number;
|
|
32
|
-
};
|
|
33
|
-
margins: PageMarginsPx;
|
|
34
|
-
}
|
|
35
|
-
export declare function renderHtmlToPdf(options: RenderHtmlOptions): Promise<Uint8Array>;
|
|
36
|
-
export declare function renderHtmlToPdfBase64(options: RenderHtmlOptions): Promise<string>;
|
|
37
|
-
export declare function prepareHtmlRender(options: RenderHtmlOptions): Promise<PreparedRender>;
|
|
1
|
+
export * from "./html-to-pdf/index.js";
|
package/dist/src/html-to-pdf.js
CHANGED
|
@@ -1,537 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { parseHTML } from "linkedom";
|
|
3
|
-
import {} from "./types/fonts.js";
|
|
4
|
-
import { configureDebug, log } from "./logging/debug.js";
|
|
5
|
-
import { parseCss } from "./html/css/parse-css.js";
|
|
6
|
-
import { makeUnitParsers, pxToPt } from "./units/units.js";
|
|
7
|
-
import { LayoutNode } from "./dom/node.js";
|
|
8
|
-
import { ComputedStyle } from "./css/style.js";
|
|
9
|
-
import { layoutTree } from "./layout/pipeline/layout-tree.js";
|
|
10
|
-
import { buildRenderTree } from "./pdf/layout-tree-builder.js";
|
|
11
|
-
import { renderPdf } from "./pdf/render.js";
|
|
12
|
-
import { convertDomNode } from "./html/dom-converter.js";
|
|
13
|
-
import { applyPageVerticalMarginsWithHf, offsetRenderTree } from "./render/offset.js";
|
|
14
|
-
import { applyTextLayoutAdjustments } from "./pdf/utils/text-layout-adjuster.js";
|
|
15
|
-
import { setViewportSize } from "./css/apply-declarations.js";
|
|
16
|
-
import { DEFAULT_PAGE_WIDTH_PX, DEFAULT_PAGE_HEIGHT_PX, resolvePageMarginsPx, sanitizeDimension, maxContentDimension, } from "./units/page-utils.js";
|
|
17
|
-
import { computeStyleForElement } from "./css/compute-style.js";
|
|
18
|
-
import { FontEmbedder } from "./pdf/font/embedder.js";
|
|
19
|
-
import { PdfDocument } from "./pdf/primitives/pdf-document.js";
|
|
20
|
-
import { loadBuiltinFontConfig } from "./pdf/font/builtin-fonts.js";
|
|
21
|
-
import { renderHeaderFooterHtml } from "./pdf/header-footer-renderer.js";
|
|
22
|
-
import { Display } from "./css/enums.js";
|
|
23
|
-
import { NodeEnvironment } from "./environment/node-environment.js";
|
|
24
|
-
import { decodeBase64ToUint8Array, encodeUint8ArrayToBase64 } from "./utils/base64.js";
|
|
25
|
-
const AUTO_HEADER_FOOTER_FALLBACK_PX = 64;
|
|
26
|
-
export async function renderHtmlToPdf(options) {
|
|
27
|
-
const environment = options.environment ?? new NodeEnvironment(options.assetRootDir ?? options.resourceBaseDir);
|
|
28
|
-
const resolvedResourceBaseDir = options.resourceBaseDir ?? options.assetRootDir ?? "";
|
|
29
|
-
const resolvedAssetRootDir = options.assetRootDir ?? resolvedResourceBaseDir;
|
|
30
|
-
const resolvedFontConfig = options.fontConfig ?? (await loadBuiltinFontConfig(environment));
|
|
31
|
-
const preparedOptions = resolvedFontConfig ? { ...options, fontConfig: resolvedFontConfig, environment } : { ...options, environment };
|
|
32
|
-
const prepared = await prepareHtmlRender(preparedOptions);
|
|
33
|
-
return renderPdf(prepared.renderTree, {
|
|
34
|
-
pageSize: prepared.pageSize,
|
|
35
|
-
fontConfig: resolvedFontConfig ?? undefined,
|
|
36
|
-
margins: prepared.margins,
|
|
37
|
-
environment,
|
|
38
|
-
resourceBaseDir: resolvedResourceBaseDir,
|
|
39
|
-
assetRootDir: resolvedAssetRootDir,
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
export async function renderHtmlToPdfBase64(options) {
|
|
43
|
-
const pdfBuffer = await renderHtmlToPdf(options);
|
|
44
|
-
return encodeUint8ArrayToBase64(pdfBuffer);
|
|
45
|
-
}
|
|
46
|
-
export async function prepareHtmlRender(options) {
|
|
47
|
-
// Apply defaults for optional parameters
|
|
48
|
-
const pageWidth = sanitizeDimension(options.pageWidth, DEFAULT_PAGE_WIDTH_PX);
|
|
49
|
-
const pageHeight = sanitizeDimension(options.pageHeight, DEFAULT_PAGE_HEIGHT_PX);
|
|
50
|
-
const marginsPx = resolvePageMarginsPx(pageWidth, pageHeight);
|
|
51
|
-
const maxContentWidth = maxContentDimension(pageWidth, marginsPx.left + marginsPx.right);
|
|
52
|
-
const maxContentHeight = maxContentDimension(pageHeight, marginsPx.top + marginsPx.bottom);
|
|
53
|
-
const viewportWidth = Math.min(sanitizeDimension(options.viewportWidth, maxContentWidth), maxContentWidth);
|
|
54
|
-
const viewportHeight = Math.min(sanitizeDimension(options.viewportHeight, maxContentHeight), maxContentHeight);
|
|
55
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
56
|
-
const { html, css, pageWidth: _, pageHeight: __, margins: ___, ...restOptions } = options;
|
|
57
|
-
const { html: htmlInput, css: cssInput = "" } = { html, css: css, ...restOptions };
|
|
58
|
-
const { debug = false, debugLevel, debugCats, headerFooter, resourceBaseDir, assetRootDir, environment: envOverride } = options;
|
|
59
|
-
const normalizedHtml = normalizeHtmlInput(htmlInput);
|
|
60
|
-
setViewportSize(viewportWidth, viewportHeight);
|
|
61
|
-
const resourceBaseDirVal = resourceBaseDir ?? assetRootDir ?? "";
|
|
62
|
-
const assetRootDirVal = assetRootDir ?? resourceBaseDirVal;
|
|
63
|
-
const environment = envOverride ?? new NodeEnvironment(assetRootDirVal);
|
|
64
|
-
const resolvedHeaderFooter = await resolveHeaderFooterMaxHeights({
|
|
65
|
-
headerFooter,
|
|
66
|
-
pageWidthPx: pageWidth,
|
|
67
|
-
pageHeightPx: pageHeight,
|
|
68
|
-
margins: marginsPx,
|
|
69
|
-
resourceBaseDir: resourceBaseDirVal,
|
|
70
|
-
assetRootDir: assetRootDirVal,
|
|
71
|
-
environment,
|
|
72
|
-
});
|
|
73
|
-
if (debugLevel || debugCats) {
|
|
74
|
-
configureDebug({ level: debugLevel ?? (debug ? "debug" : "info"), cats: debugCats });
|
|
75
|
-
}
|
|
76
|
-
const unitCtx = { viewport: { width: viewportWidth, height: viewportHeight } };
|
|
77
|
-
const units = makeUnitParsers(unitCtx);
|
|
78
|
-
log('html-to-pdf', 'debug', `prepareHtmlRender - input html: ${htmlInput}`);
|
|
79
|
-
let document = parseDocument(normalizedHtml);
|
|
80
|
-
if (needsReparse(document)) {
|
|
81
|
-
document = parseDocument(wrapHtml(htmlInput));
|
|
82
|
-
}
|
|
83
|
-
if (!document) {
|
|
84
|
-
throw new Error("Failed to parse HTML into a document");
|
|
85
|
-
}
|
|
86
|
-
log('html-to-pdf', 'debug', `prepareHtmlRender - parsed document body: ${document.body?.innerHTML || 'no body'}`);
|
|
87
|
-
log('html-to-pdf', 'debug', `prepareHtmlRender - document.documentElement tagName: ${document.documentElement?.tagName}`);
|
|
88
|
-
log('html-to-pdf', 'debug', `prepareHtmlRender - document.documentElement innerHTML: ${document.documentElement?.innerHTML}`);
|
|
89
|
-
log('html-to-pdf', 'debug', `prepareHtmlRender - document children count: ${document.childNodes.length}`);
|
|
90
|
-
for (let i = 0; i < document.childNodes.length; i++) {
|
|
91
|
-
const child = document.childNodes[i];
|
|
92
|
-
const tagName = child.nodeType === child.ELEMENT_NODE ? child.tagName : 'text node';
|
|
93
|
-
log('html-to-pdf', 'debug', `prepareHtmlRender - document child ${i}: ${child.nodeType}, ${tagName}`);
|
|
94
|
-
}
|
|
95
|
-
log("parse", "debug", "DOM parsed", { hasBody: !!document.body });
|
|
96
|
-
let mergedCss = cssInput || "";
|
|
97
|
-
const styleTags = Array.from(document.querySelectorAll("style"));
|
|
98
|
-
for (const styleTag of styleTags) {
|
|
99
|
-
if (styleTag.textContent)
|
|
100
|
-
mergedCss += "\n" + styleTag.textContent;
|
|
101
|
-
}
|
|
102
|
-
const linkTags = Array.from(document.querySelectorAll("link")).filter((link) => (link.getAttribute("rel") || "").toLowerCase() === "stylesheet");
|
|
103
|
-
for (const linkTag of linkTags) {
|
|
104
|
-
const href = linkTag.getAttribute("href");
|
|
105
|
-
if (!href)
|
|
106
|
-
continue;
|
|
107
|
-
const cssText = await loadStylesheetFromHref(href, resourceBaseDirVal, assetRootDirVal, environment);
|
|
108
|
-
if (cssText)
|
|
109
|
-
mergedCss += "\n" + cssText;
|
|
110
|
-
}
|
|
111
|
-
const { styleRules: cssRules, fontFaceRules } = parseCss(mergedCss);
|
|
112
|
-
log("parse", "debug", "CSS rules", { count: cssRules.length, fontFaces: fontFaceRules.length });
|
|
113
|
-
// Determine the root element to process - prefer body, but fall back to documentElement if body is empty
|
|
114
|
-
let rootElement = document.body;
|
|
115
|
-
let processChildrenOf = rootElement;
|
|
116
|
-
// If body is empty but documentElement has children (like when HTML is just a fragment),
|
|
117
|
-
// process the documentElement's children instead
|
|
118
|
-
if (rootElement && rootElement.childNodes.length === 0) {
|
|
119
|
-
// Check if documentElement has meaningful children
|
|
120
|
-
const meaningfulChildren = Array.from(document.documentElement.childNodes).filter(node => {
|
|
121
|
-
return node.nodeType === node.ELEMENT_NODE && node.tagName !== 'HEAD';
|
|
122
|
-
});
|
|
123
|
-
if (meaningfulChildren.length > 0) {
|
|
124
|
-
processChildrenOf = document.documentElement;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
else if (!rootElement) {
|
|
128
|
-
// If there's no body element, process documentElement children directly
|
|
129
|
-
processChildrenOf = document.documentElement;
|
|
130
|
-
}
|
|
131
|
-
const baseParentStyle = new ComputedStyle();
|
|
132
|
-
const htmlElement = document.documentElement;
|
|
133
|
-
const documentElementStyle = htmlElement
|
|
134
|
-
? computeStyleForElement(htmlElement, cssRules, baseParentStyle, units, baseParentStyle.fontSize)
|
|
135
|
-
: baseParentStyle;
|
|
136
|
-
const rootFontSize = documentElementStyle.fontSize;
|
|
137
|
-
let rootStyle;
|
|
138
|
-
if (!processChildrenOf || processChildrenOf === htmlElement) {
|
|
139
|
-
rootStyle = documentElementStyle;
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
rootStyle = computeStyleForElement(processChildrenOf, cssRules, documentElementStyle, units, rootFontSize);
|
|
143
|
-
}
|
|
144
|
-
if (isInlineDisplay(rootStyle.display)) {
|
|
145
|
-
rootStyle.display = Display.Block;
|
|
146
|
-
}
|
|
147
|
-
const rootLayout = new LayoutNode(rootStyle, [], { tagName: processChildrenOf?.tagName?.toLowerCase() });
|
|
148
|
-
const conversionContext = { resourceBaseDir: resourceBaseDirVal, assetRootDir: assetRootDirVal, units, rootFontSize, environment };
|
|
149
|
-
if (processChildrenOf) {
|
|
150
|
-
log('html-to-pdf', 'debug', `prepareHtmlRender - processing children of: ${processChildrenOf.tagName}, count: ${processChildrenOf.childNodes.length}`);
|
|
151
|
-
for (const child of Array.from(processChildrenOf.childNodes)) {
|
|
152
|
-
const childTagName = child.nodeType === child.ELEMENT_NODE ? child.tagName : 'text node';
|
|
153
|
-
log('html-to-pdf', 'debug', `prepareHtmlRender - processing child: ${childTagName}, type: ${child.nodeType}`);
|
|
154
|
-
// Skip head and other non-content elements
|
|
155
|
-
if (child.nodeType === child.ELEMENT_NODE) {
|
|
156
|
-
const tagName = child.tagName.toLowerCase();
|
|
157
|
-
if (tagName === 'head' || tagName === 'meta' || tagName === 'title' || tagName === 'link' || tagName === 'script') {
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
const layoutChild = await convertDomNode(child, cssRules, rootStyle, conversionContext);
|
|
162
|
-
if (layoutChild)
|
|
163
|
-
rootLayout.appendChild(layoutChild);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
const pdfDoc = new PdfDocument();
|
|
167
|
-
// Process @font-face rules
|
|
168
|
-
for (const fontFace of fontFaceRules) {
|
|
169
|
-
const fontFamily = fontFace.declarations['font-family']?.replace(/['"]/g, '');
|
|
170
|
-
const src = fontFace.declarations['src'];
|
|
171
|
-
if (fontFamily && src) {
|
|
172
|
-
const targetUrl = pickFontUrlFromSrc(src);
|
|
173
|
-
if (targetUrl && options.fontConfig) {
|
|
174
|
-
const fontData = await loadFontData(targetUrl, resourceBaseDirVal, assetRootDirVal, environment);
|
|
175
|
-
if (fontData) {
|
|
176
|
-
const weightStr = fontFace.declarations["font-weight"] || "400";
|
|
177
|
-
const styleStr = fontFace.declarations["font-style"] || "normal";
|
|
178
|
-
options.fontConfig.fontFaceDefs.push({
|
|
179
|
-
name: fontFamily,
|
|
180
|
-
family: fontFamily,
|
|
181
|
-
src: targetUrl,
|
|
182
|
-
data: fontData,
|
|
183
|
-
weight: parseFontWeight(weightStr),
|
|
184
|
-
style: normalizeFontStyle(styleStr),
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
// In a browser environment, the font data should be pre-loaded and passed in the fontConfig.
|
|
191
|
-
if (options.fontConfig) {
|
|
192
|
-
for (const face of options.fontConfig.fontFaceDefs) {
|
|
193
|
-
if (!face.data && face.src) {
|
|
194
|
-
const loaded = await loadFontData(face.src, resourceBaseDirVal, assetRootDirVal, environment);
|
|
195
|
-
if (loaded) {
|
|
196
|
-
// Type assertion needed because face is readonly from fontFaceDefs
|
|
197
|
-
face.data = loaded;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
const fontEmbedder = options.fontConfig ? new FontEmbedder(options.fontConfig, pdfDoc) : null;
|
|
203
|
-
if (fontEmbedder) {
|
|
204
|
-
await fontEmbedder.initialize();
|
|
205
|
-
}
|
|
206
|
-
layoutTree(rootLayout, { width: viewportWidth, height: viewportHeight }, fontEmbedder);
|
|
207
|
-
log("layout", "debug", "Layout complete");
|
|
208
|
-
const renderTree = buildRenderTree(rootLayout, { headerFooter: resolvedHeaderFooter });
|
|
209
|
-
// Global justification pass: fix inline run positions for text-align: justify
|
|
210
|
-
applyTextLayoutAdjustments(renderTree.root);
|
|
211
|
-
// Get header/footer heights for proper content positioning
|
|
212
|
-
const headerHeightPx = resolvedHeaderFooter?.maxHeaderHeightPx ?? 0;
|
|
213
|
-
const footerHeightPx = resolvedHeaderFooter?.maxFooterHeightPx ?? 0;
|
|
214
|
-
// Apply vertical margins with header/footer space reserved
|
|
215
|
-
// This pushes content below the header and reserves space for the footer
|
|
216
|
-
applyPageVerticalMarginsWithHf(renderTree.root, {
|
|
217
|
-
pageHeight,
|
|
218
|
-
margins: marginsPx,
|
|
219
|
-
headerHeightPx,
|
|
220
|
-
footerHeightPx,
|
|
221
|
-
});
|
|
222
|
-
offsetRenderTree(renderTree.root, marginsPx.left, 0, debug);
|
|
223
|
-
const pageSize = { widthPt: pxToPt(pageWidth), heightPt: pxToPt(pageHeight) };
|
|
224
|
-
return { layoutRoot: rootLayout, renderTree, pageSize, margins: marginsPx };
|
|
225
|
-
}
|
|
226
|
-
function resolveLocalPath(target, resourceBaseDir, assetRootDir, environment) {
|
|
227
|
-
let result = target;
|
|
228
|
-
if (result.startsWith("file://")) {
|
|
229
|
-
result = environment.fileURLToPath ? environment.fileURLToPath(result) : result;
|
|
230
|
-
}
|
|
231
|
-
else if (result.startsWith("/")) {
|
|
232
|
-
result = environment.pathResolve ? environment.pathResolve(assetRootDir, `.${result}`) : result;
|
|
233
|
-
}
|
|
234
|
-
else if (!(environment.pathIsAbsolute ? environment.pathIsAbsolute(result) : result.includes(":"))) {
|
|
235
|
-
result = environment.pathResolve ? environment.pathResolve(resourceBaseDir, result) : result;
|
|
236
|
-
}
|
|
237
|
-
return result;
|
|
238
|
-
}
|
|
239
|
-
function selectLocalBase(target, resourceBaseDir, assetRootDir) {
|
|
240
|
-
if (target.trim().startsWith("/")) {
|
|
241
|
-
return assetRootDir || resourceBaseDir;
|
|
242
|
-
}
|
|
243
|
-
return resourceBaseDir || assetRootDir;
|
|
244
|
-
}
|
|
245
|
-
async function loadStylesheetFromHref(href, resourceBaseDir, assetRootDir, environment) {
|
|
246
|
-
const trimmed = href.trim();
|
|
247
|
-
if (!trimmed)
|
|
248
|
-
return "";
|
|
249
|
-
try {
|
|
250
|
-
if (/^https?:\/\//i.test(trimmed) || trimmed.startsWith("//")) {
|
|
251
|
-
const absoluteHref = trimmed.startsWith("//") ? `https:${trimmed}` : trimmed;
|
|
252
|
-
const response = await fetch(absoluteHref);
|
|
253
|
-
if (!response.ok) {
|
|
254
|
-
throw new Error(`HTTP ${response.status}`);
|
|
255
|
-
}
|
|
256
|
-
const cssText = await response.text();
|
|
257
|
-
return rewriteCssUrls(cssText, absoluteHref);
|
|
258
|
-
}
|
|
259
|
-
const localBase = selectLocalBase(trimmed, resourceBaseDir, assetRootDir);
|
|
260
|
-
const cssPath = environment.resolveLocal
|
|
261
|
-
? environment.resolveLocal(trimmed, localBase || undefined)
|
|
262
|
-
: resolveLocalPath(trimmed, resourceBaseDir, assetRootDir, environment);
|
|
263
|
-
const cssBuffer = await environment.loader.load(cssPath);
|
|
264
|
-
const cssText = new TextDecoder("utf-8").decode(cssBuffer);
|
|
265
|
-
const baseHref = /^https?:\/\//i.test(cssPath) || cssPath.startsWith("file:")
|
|
266
|
-
? cssPath
|
|
267
|
-
: (environment.pathToFileURL ? environment.pathToFileURL(cssPath) : cssPath);
|
|
268
|
-
return rewriteCssUrls(cssText, baseHref);
|
|
269
|
-
}
|
|
270
|
-
catch (error) {
|
|
271
|
-
log("parse", "warn", "Failed to load stylesheet", { href, error: error instanceof Error ? error.message : String(error) });
|
|
272
|
-
return "";
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
function rewriteCssUrls(cssText, baseHref) {
|
|
276
|
-
let base;
|
|
277
|
-
try {
|
|
278
|
-
base = new URL(baseHref);
|
|
279
|
-
}
|
|
280
|
-
catch {
|
|
281
|
-
return cssText;
|
|
282
|
-
}
|
|
283
|
-
const urlRegex = /url\(\s*(['"]?)([^'")]+)\1\s*\)/gi;
|
|
284
|
-
return cssText.replace(urlRegex, (match, quote, rawUrl) => {
|
|
285
|
-
const candidate = (rawUrl || "").trim();
|
|
286
|
-
if (!candidate || /^data:/i.test(candidate))
|
|
287
|
-
return match;
|
|
288
|
-
if (/^[a-z][a-z0-9+\-.]*:/i.test(candidate))
|
|
289
|
-
return match;
|
|
290
|
-
try {
|
|
291
|
-
const resolved = new URL(candidate, base).toString();
|
|
292
|
-
const q = quote || "";
|
|
293
|
-
return `url(${q}${resolved}${q})`;
|
|
294
|
-
}
|
|
295
|
-
catch {
|
|
296
|
-
return match;
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
function pickFontUrlFromSrc(src) {
|
|
301
|
-
const urlRegex = /url\(\s*(['"]?)([^'")]+)\1\s*\)(?:\s*format\(\s*['"]?([^'")]+)['"]?\s*\))?/gi;
|
|
302
|
-
let fallback = null;
|
|
303
|
-
let preferred = null;
|
|
304
|
-
let match;
|
|
305
|
-
while ((match = urlRegex.exec(src)) !== null) {
|
|
306
|
-
const url = match[2];
|
|
307
|
-
const format = (match[3] || "").toLowerCase();
|
|
308
|
-
if (!fallback)
|
|
309
|
-
fallback = url;
|
|
310
|
-
if (format === "woff2") {
|
|
311
|
-
preferred = url;
|
|
312
|
-
break;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
return preferred ?? fallback;
|
|
316
|
-
}
|
|
317
|
-
function parseFontWeight(weightStr) {
|
|
318
|
-
const normalized = weightStr.trim().toLowerCase();
|
|
319
|
-
if (normalized === "bold")
|
|
320
|
-
return 700;
|
|
321
|
-
if (normalized === "normal")
|
|
322
|
-
return 400;
|
|
323
|
-
const parsed = Number.parseInt(normalized, 10);
|
|
324
|
-
return Number.isFinite(parsed) ? parsed : 400;
|
|
325
|
-
}
|
|
326
|
-
function normalizeFontStyle(styleStr) {
|
|
327
|
-
const normalized = styleStr.trim().toLowerCase();
|
|
328
|
-
return normalized.includes("italic") || normalized.includes("oblique") ? "italic" : "normal";
|
|
329
|
-
}
|
|
330
|
-
async function loadFontData(src, resourceBaseDir, assetRootDir, environment) {
|
|
331
|
-
const trimmed = src.trim();
|
|
332
|
-
if (!trimmed)
|
|
333
|
-
return null;
|
|
334
|
-
let target = trimmed;
|
|
335
|
-
try {
|
|
336
|
-
if (target.startsWith("//")) {
|
|
337
|
-
target = `https:${target}`;
|
|
338
|
-
}
|
|
339
|
-
if (/^data:/i.test(target)) {
|
|
340
|
-
const commaIdx = target.indexOf(",");
|
|
341
|
-
if (commaIdx === -1) {
|
|
342
|
-
throw new Error("Invalid data URI");
|
|
343
|
-
}
|
|
344
|
-
const meta = target.slice(5, commaIdx);
|
|
345
|
-
if (!/;base64/i.test(meta)) {
|
|
346
|
-
throw new Error("Only base64-encoded data URIs are supported for fonts");
|
|
347
|
-
}
|
|
348
|
-
const data = decodeBase64ToUint8Array(target.slice(commaIdx + 1));
|
|
349
|
-
const copy = data.slice();
|
|
350
|
-
return copy.buffer;
|
|
351
|
-
}
|
|
352
|
-
if (/^https?:\/\//i.test(target)) {
|
|
353
|
-
const response = await fetch(target);
|
|
354
|
-
if (!response.ok) {
|
|
355
|
-
throw new Error(`HTTP ${response.status}`);
|
|
356
|
-
}
|
|
357
|
-
return await response.arrayBuffer();
|
|
358
|
-
}
|
|
359
|
-
if (/^file:/i.test(target)) {
|
|
360
|
-
const localPath = environment.fileURLToPath ? environment.fileURLToPath(target) : target;
|
|
361
|
-
const fontDataBuffer = await environment.loader.load(localPath);
|
|
362
|
-
return fontDataBuffer;
|
|
363
|
-
}
|
|
364
|
-
const localBase = selectLocalBase(target, resourceBaseDir, assetRootDir);
|
|
365
|
-
const resolved = environment.resolveLocal
|
|
366
|
-
? environment.resolveLocal(target, localBase || undefined)
|
|
367
|
-
: resolveLocalPath(target, resourceBaseDir, assetRootDir, environment);
|
|
368
|
-
const fontDataBuffer = await environment.loader.load(resolved);
|
|
369
|
-
return fontDataBuffer;
|
|
370
|
-
}
|
|
371
|
-
catch (error) {
|
|
372
|
-
log("font", "warn", "Failed to load font data", { src, error: error instanceof Error ? error.message : String(error) });
|
|
373
|
-
return null;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
function isInlineDisplay(display) {
|
|
377
|
-
return (display === Display.Inline ||
|
|
378
|
-
display === Display.InlineBlock ||
|
|
379
|
-
display === Display.InlineFlex ||
|
|
380
|
-
display === Display.InlineGrid ||
|
|
381
|
-
display === Display.InlineTable);
|
|
382
|
-
}
|
|
383
|
-
async function resolveHeaderFooterMaxHeights(options) {
|
|
384
|
-
const { headerFooter } = options;
|
|
385
|
-
if (!headerFooter) {
|
|
386
|
-
return undefined;
|
|
387
|
-
}
|
|
388
|
-
const resolved = { ...headerFooter };
|
|
389
|
-
const contentWidthPx = Math.max(1, options.pageWidthPx - options.margins.left - options.margins.right);
|
|
390
|
-
const measurementMaxHeightPx = Math.max(1, options.pageHeightPx - options.margins.top - options.margins.bottom);
|
|
391
|
-
if (!hasOwnProperty(headerFooter, "maxHeaderHeightPx")) {
|
|
392
|
-
const headerCandidates = collectHeaderVariants(headerFooter);
|
|
393
|
-
if (headerCandidates.length > 0) {
|
|
394
|
-
const measured = await measureMaxHeaderFooterVariantHeight({
|
|
395
|
-
variants: headerCandidates,
|
|
396
|
-
widthPx: contentWidthPx,
|
|
397
|
-
maxHeightPx: measurementMaxHeightPx,
|
|
398
|
-
resourceBaseDir: options.resourceBaseDir,
|
|
399
|
-
assetRootDir: options.assetRootDir,
|
|
400
|
-
environment: options.environment,
|
|
401
|
-
});
|
|
402
|
-
resolved.maxHeaderHeightPx = measured > 0 ? measured : AUTO_HEADER_FOOTER_FALLBACK_PX;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
if (!hasOwnProperty(headerFooter, "maxFooterHeightPx")) {
|
|
406
|
-
const footerCandidates = collectFooterVariants(headerFooter);
|
|
407
|
-
if (footerCandidates.length > 0) {
|
|
408
|
-
const measured = await measureMaxHeaderFooterVariantHeight({
|
|
409
|
-
variants: footerCandidates,
|
|
410
|
-
widthPx: contentWidthPx,
|
|
411
|
-
maxHeightPx: measurementMaxHeightPx,
|
|
412
|
-
resourceBaseDir: options.resourceBaseDir,
|
|
413
|
-
assetRootDir: options.assetRootDir,
|
|
414
|
-
environment: options.environment,
|
|
415
|
-
});
|
|
416
|
-
resolved.maxFooterHeightPx = measured > 0 ? measured : AUTO_HEADER_FOOTER_FALLBACK_PX;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
return resolved;
|
|
420
|
-
}
|
|
421
|
-
async function measureMaxHeaderFooterVariantHeight(options) {
|
|
422
|
-
const heights = await Promise.all(options.variants.map(async (html) => {
|
|
423
|
-
try {
|
|
424
|
-
const rendered = await renderHeaderFooterHtml({
|
|
425
|
-
html,
|
|
426
|
-
widthPx: options.widthPx,
|
|
427
|
-
maxHeightPx: options.maxHeightPx,
|
|
428
|
-
resourceBaseDir: options.resourceBaseDir,
|
|
429
|
-
assetRootDir: options.assetRootDir,
|
|
430
|
-
environment: options.environment,
|
|
431
|
-
});
|
|
432
|
-
return rendered ? Math.ceil(rendered.heightPx) : 0;
|
|
433
|
-
}
|
|
434
|
-
catch (error) {
|
|
435
|
-
log("layout", "warn", "Failed to auto-measure header/footer variant height", {
|
|
436
|
-
error: error instanceof Error ? error.message : String(error),
|
|
437
|
-
});
|
|
438
|
-
return 0;
|
|
439
|
-
}
|
|
440
|
-
}));
|
|
441
|
-
return Math.max(0, ...heights);
|
|
442
|
-
}
|
|
443
|
-
function collectHeaderVariants(headerFooter) {
|
|
444
|
-
const values = [
|
|
445
|
-
headerFooter.headerHtml,
|
|
446
|
-
headerFooter.headerFirstHtml,
|
|
447
|
-
headerFooter.headerEvenHtml,
|
|
448
|
-
headerFooter.headerOddHtml,
|
|
449
|
-
];
|
|
450
|
-
return values.map(stringifyHeaderFooterContent).filter((value) => value.length > 0);
|
|
451
|
-
}
|
|
452
|
-
function collectFooterVariants(headerFooter) {
|
|
453
|
-
const values = [
|
|
454
|
-
headerFooter.footerHtml,
|
|
455
|
-
headerFooter.footerFirstHtml,
|
|
456
|
-
headerFooter.footerEvenHtml,
|
|
457
|
-
headerFooter.footerOddHtml,
|
|
458
|
-
];
|
|
459
|
-
return values.map(stringifyHeaderFooterContent).filter((value) => value.length > 0);
|
|
460
|
-
}
|
|
461
|
-
function stringifyHeaderFooterContent(content) {
|
|
462
|
-
if (content == null) {
|
|
463
|
-
return "";
|
|
464
|
-
}
|
|
465
|
-
if (typeof content === "string") {
|
|
466
|
-
return content;
|
|
467
|
-
}
|
|
468
|
-
if (typeof content === "function") {
|
|
469
|
-
try {
|
|
470
|
-
return String(content());
|
|
471
|
-
}
|
|
472
|
-
catch {
|
|
473
|
-
return "";
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
return String(content);
|
|
477
|
-
}
|
|
478
|
-
function hasOwnProperty(target, key) {
|
|
479
|
-
return Object.prototype.hasOwnProperty.call(target, key);
|
|
480
|
-
}
|
|
481
|
-
function normalizeHtmlInput(html) {
|
|
482
|
-
const hasHtmlTag = /<\s*html[\s>]/i.test(html);
|
|
483
|
-
if (hasHtmlTag) {
|
|
484
|
-
return html;
|
|
485
|
-
}
|
|
486
|
-
return wrapHtml(html);
|
|
487
|
-
}
|
|
488
|
-
function wrapHtml(html) {
|
|
489
|
-
return `<!doctype html><html><head></head><body>${html}</body></html>`;
|
|
490
|
-
}
|
|
491
|
-
function parseDocument(html) {
|
|
492
|
-
const parsed = parseHTML(html);
|
|
493
|
-
if (!parsed || typeof parsed !== "object") {
|
|
494
|
-
return undefined;
|
|
495
|
-
}
|
|
496
|
-
if (isDocumentLike(parsed)) {
|
|
497
|
-
return parsed;
|
|
498
|
-
}
|
|
499
|
-
let maybeDocument;
|
|
500
|
-
try {
|
|
501
|
-
maybeDocument = parsed.document;
|
|
502
|
-
}
|
|
503
|
-
catch {
|
|
504
|
-
maybeDocument = undefined;
|
|
505
|
-
}
|
|
506
|
-
if (isDocumentLike(maybeDocument)) {
|
|
507
|
-
return maybeDocument;
|
|
508
|
-
}
|
|
509
|
-
return undefined;
|
|
510
|
-
}
|
|
511
|
-
function isDocumentLike(value) {
|
|
512
|
-
if (!value || typeof value !== "object")
|
|
513
|
-
return false;
|
|
514
|
-
return "querySelectorAll" in value && "childNodes" in value;
|
|
515
|
-
}
|
|
516
|
-
function needsReparse(document) {
|
|
517
|
-
if (!document)
|
|
518
|
-
return true;
|
|
519
|
-
let docEl;
|
|
520
|
-
try {
|
|
521
|
-
docEl = document.documentElement?.tagName;
|
|
522
|
-
}
|
|
523
|
-
catch {
|
|
524
|
-
return true;
|
|
525
|
-
}
|
|
526
|
-
const docIsHtml = docEl?.toUpperCase() === "HTML";
|
|
527
|
-
if (!docIsHtml)
|
|
528
|
-
return true;
|
|
529
|
-
try {
|
|
530
|
-
if (!document.body)
|
|
531
|
-
return true;
|
|
532
|
-
}
|
|
533
|
-
catch {
|
|
534
|
-
return true;
|
|
535
|
-
}
|
|
536
|
-
return false;
|
|
537
|
-
}
|
|
1
|
+
export * from "./html-to-pdf/index.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ImageInfo } from "./types.js";
|
|
2
|
+
import type { ImageDecodeOptions } from "./types.js";
|
|
3
|
+
import type { PngBackend } from "./png-backend.js";
|
|
4
|
+
export declare class JsPngBackend implements PngBackend {
|
|
5
|
+
private readonly pngDecoder;
|
|
6
|
+
decode(buffer: ArrayBuffer, options?: ImageDecodeOptions): Promise<ImageInfo>;
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|