@jasy/pdf 1.0.0-alpha.1 → 1.0.0-alpha.11
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/LICENSE +21 -0
- package/README.md +35 -21
- package/dist/api/args.d.ts +1 -1
- package/dist/api/args.js +2 -5
- package/dist/api/color.d.ts +4 -4
- package/dist/api/color.js +11 -17
- package/dist/api/content.d.ts +20 -13
- package/dist/api/content.js +44 -29
- package/dist/api/descriptor.d.ts +2 -2
- package/dist/api/descriptor.js +121 -31
- package/dist/api/dimension.d.ts +76 -0
- package/dist/api/dimension.js +43 -0
- package/dist/api/font-url.d.ts +28 -0
- package/dist/api/font-url.js +134 -0
- package/dist/api/forms.d.ts +347 -0
- package/dist/api/forms.js +189 -0
- package/dist/api/gradient.d.ts +53 -0
- package/dist/api/gradient.js +97 -0
- package/dist/api/index.d.ts +13 -8
- package/dist/api/index.js +13 -24
- package/dist/api/insets.d.ts +19 -12
- package/dist/api/insets.js +19 -12
- package/dist/api/layout.d.ts +149 -25
- package/dist/api/layout.js +182 -51
- package/dist/api/page-builder.d.ts +40 -0
- package/dist/api/page-builder.js +44 -0
- package/dist/api/structure.d.ts +107 -13
- package/dist/api/structure.js +187 -88
- package/dist/api/table.d.ts +5 -5
- package/dist/api/table.js +38 -26
- package/dist/api/text.d.ts +90 -4
- package/dist/api/text.js +116 -31
- package/dist/assets/font-data.d.ts +2 -0
- package/dist/assets/font-data.js +21 -0
- package/dist/assets/font-data.ts +37 -0
- package/dist/common/color.js +1 -5
- package/dist/constants/page-sizes.js +3 -6
- package/dist/constants/pdf-parts.js +1 -4
- package/dist/crypto/legacy-handler.d.ts +43 -0
- package/dist/crypto/legacy-handler.js +145 -0
- package/dist/crypto/security-handler.d.ts +62 -0
- package/dist/crypto/security-handler.js +155 -0
- package/dist/crypto/webcrypto.d.ts +11 -0
- package/dist/crypto/webcrypto.js +62 -0
- package/dist/edit/acroform-reader.d.ts +70 -0
- package/dist/edit/acroform-reader.js +126 -0
- package/dist/edit/appearance.d.ts +47 -0
- package/dist/edit/appearance.js +171 -0
- package/dist/edit/document.d.ts +145 -0
- package/dist/edit/document.js +642 -0
- package/dist/edit/fill.d.ts +65 -0
- package/dist/edit/fill.js +424 -0
- package/dist/edit/flatten.d.ts +54 -0
- package/dist/edit/flatten.js +462 -0
- package/dist/edit/index.d.ts +20 -0
- package/dist/edit/index.js +18 -0
- package/dist/edit/lexer.d.ts +30 -0
- package/dist/edit/lexer.js +285 -0
- package/dist/edit/objects.d.ts +68 -0
- package/dist/edit/objects.js +83 -0
- package/dist/edit/writer.d.ts +60 -0
- package/dist/edit/writer.js +283 -0
- package/dist/elements/container-element.d.ts +50 -5
- package/dist/elements/container-element.js +100 -39
- package/dist/elements/forms/checkbox-element.d.ts +51 -0
- package/dist/elements/forms/checkbox-element.js +48 -0
- package/dist/elements/forms/choice-element.d.ts +53 -0
- package/dist/elements/forms/choice-element.js +59 -0
- package/dist/elements/forms/push-button-element.d.ts +56 -0
- package/dist/elements/forms/push-button-element.js +49 -0
- package/dist/elements/forms/radio-element.d.ts +52 -0
- package/dist/elements/forms/radio-element.js +49 -0
- package/dist/elements/forms/signature-element.d.ts +54 -0
- package/dist/elements/forms/signature-element.js +47 -0
- package/dist/elements/forms/text-field-element.d.ts +61 -0
- package/dist/elements/forms/text-field-element.js +54 -0
- package/dist/elements/image-element.d.ts +58 -6
- package/dist/elements/image-element.js +145 -111
- package/dist/elements/index.d.ts +26 -10
- package/dist/elements/index.js +26 -28
- package/dist/elements/layout/anchor-element.d.ts +23 -0
- package/dist/elements/layout/anchor-element.js +27 -0
- package/dist/elements/layout/bookmark-element.d.ts +27 -0
- package/dist/elements/layout/bookmark-element.js +30 -0
- package/dist/elements/layout/default-text-style-element.d.ts +30 -0
- package/dist/elements/layout/default-text-style-element.js +47 -0
- package/dist/elements/layout/deferred-element.d.ts +3 -3
- package/dist/elements/layout/deferred-element.js +4 -8
- package/dist/elements/layout/expanded-element.d.ts +6 -4
- package/dist/elements/layout/expanded-element.js +22 -17
- package/dist/elements/layout/keep-together-element.d.ts +42 -0
- package/dist/elements/layout/keep-together-element.js +85 -0
- package/dist/elements/layout/link-element.d.ts +33 -0
- package/dist/elements/layout/link-element.js +39 -0
- package/dist/elements/layout/padding-element.d.ts +10 -5
- package/dist/elements/layout/padding-element.js +25 -16
- package/dist/elements/layout/page-break-element.d.ts +17 -0
- package/dist/elements/layout/page-break-element.js +24 -0
- package/dist/elements/layout/page-builder-element.d.ts +32 -0
- package/dist/elements/layout/page-builder-element.js +45 -0
- package/dist/elements/layout/positioned-element.d.ts +47 -0
- package/dist/elements/layout/positioned-element.js +73 -0
- package/dist/elements/layout/repeating-header-element.d.ts +3 -3
- package/dist/elements/layout/repeating-header-element.js +8 -12
- package/dist/elements/layout/rotated-box-element.d.ts +35 -0
- package/dist/elements/layout/rotated-box-element.js +54 -0
- package/dist/elements/layout/rotated-element.d.ts +32 -0
- package/dist/elements/layout/rotated-element.js +41 -0
- package/dist/elements/layout/sized-container-element.d.ts +2 -2
- package/dist/elements/layout/sized-container-element.js +6 -11
- package/dist/elements/layout/struct-group.d.ts +28 -0
- package/dist/elements/layout/struct-group.js +42 -0
- package/dist/elements/line-element.d.ts +3 -3
- package/dist/elements/line-element.js +5 -10
- package/dist/elements/page-element.d.ts +26 -6
- package/dist/elements/page-element.js +72 -27
- package/dist/elements/pdf-document-element.d.ts +10 -4
- package/dist/elements/pdf-document-element.js +11 -10
- package/dist/elements/pdf-element.d.ts +150 -4
- package/dist/elements/pdf-element.js +138 -20
- package/dist/elements/rectangle-element.d.ts +52 -11
- package/dist/elements/rectangle-element.js +129 -36
- package/dist/elements/row-element.d.ts +45 -4
- package/dist/elements/row-element.js +66 -18
- package/dist/elements/text-element.d.ts +139 -11
- package/dist/elements/text-element.js +234 -57
- package/dist/forms/acroform.d.ts +63 -0
- package/dist/forms/acroform.js +427 -0
- package/dist/forms/appearance.d.ts +58 -0
- package/dist/forms/appearance.js +204 -0
- package/dist/forms/field.d.ts +164 -0
- package/dist/forms/field.js +1 -0
- package/dist/forms/pdf.d.ts +19 -0
- package/dist/forms/pdf.js +28 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -19
- package/dist/ir/display-list.d.ts +197 -8
- package/dist/ir/display-list.js +8 -2
- package/dist/layout/alignment.d.ts +10 -0
- package/dist/layout/alignment.js +6 -0
- package/dist/layout/box-constraints.d.ts +61 -0
- package/dist/layout/box-constraints.js +88 -6
- package/dist/layout/collect-images.d.ts +9 -0
- package/dist/layout/collect-images.js +34 -0
- package/dist/layout/fragmentation.d.ts +20 -1
- package/dist/layout/fragmentation.js +74 -18
- package/dist/layout/insets.d.ts +28 -0
- package/dist/layout/insets.js +20 -0
- package/dist/platform/browser-fs.d.ts +2 -0
- package/dist/platform/browser-fs.js +9 -0
- package/dist/platform/browser-image.d.ts +5 -0
- package/dist/platform/browser-image.js +13 -0
- package/dist/platform/node-fs.d.ts +2 -0
- package/dist/platform/node-fs.js +10 -0
- package/dist/platform/node-image.d.ts +5 -0
- package/dist/platform/node-image.js +9 -0
- package/dist/renderer/anchor-renderer.d.ts +6 -0
- package/dist/renderer/anchor-renderer.js +11 -0
- package/dist/renderer/bookmark-renderer.d.ts +6 -0
- package/dist/renderer/bookmark-renderer.js +11 -0
- package/dist/renderer/container-renderer.d.ts +3 -3
- package/dist/renderer/container-renderer.js +11 -28
- package/dist/renderer/default-text-style-renderer.d.ts +6 -0
- package/dist/renderer/default-text-style-renderer.js +10 -0
- package/dist/renderer/deferred-renderer.d.ts +3 -3
- package/dist/renderer/deferred-renderer.js +8 -23
- package/dist/renderer/emoji-image.d.ts +9 -0
- package/dist/renderer/emoji-image.js +68 -0
- package/dist/renderer/expanded-renderer.d.ts +3 -3
- package/dist/renderer/expanded-renderer.js +6 -21
- package/dist/renderer/form-field-renderer.d.ts +20 -0
- package/dist/renderer/form-field-renderer.js +12 -0
- package/dist/renderer/image-renderer.d.ts +4 -4
- package/dist/renderer/image-renderer.js +86 -75
- package/dist/renderer/index.d.ts +10 -10
- package/dist/renderer/index.js +10 -26
- package/dist/renderer/keep-together-renderer.d.ts +10 -0
- package/dist/renderer/keep-together-renderer.js +12 -0
- package/dist/renderer/line-renderer.d.ts +3 -3
- package/dist/renderer/line-renderer.js +13 -28
- package/dist/renderer/link-renderer.d.ts +6 -0
- package/dist/renderer/link-renderer.js +11 -0
- package/dist/renderer/padding-renderer.d.ts +3 -3
- package/dist/renderer/padding-renderer.js +6 -21
- package/dist/renderer/page-break-renderer.d.ts +14 -0
- package/dist/renderer/page-break-renderer.js +15 -0
- package/dist/renderer/page-builder-renderer.d.ts +6 -0
- package/dist/renderer/page-builder-renderer.js +11 -0
- package/dist/renderer/page-renderer.d.ts +2 -2
- package/dist/renderer/page-renderer.js +115 -77
- package/dist/renderer/pdf-backend.d.ts +31 -4
- package/dist/renderer/pdf-backend.js +287 -49
- package/dist/renderer/pdf-config.js +4 -7
- package/dist/renderer/pdf-document-class.d.ts +5 -5
- package/dist/renderer/pdf-document-class.js +27 -41
- package/dist/renderer/pdf-document-renderer.d.ts +19 -10
- package/dist/renderer/pdf-document-renderer.js +135 -91
- package/dist/renderer/pdf-renderer.d.ts +2 -2
- package/dist/renderer/pdf-renderer.js +165 -90
- package/dist/renderer/positioned-renderer.d.ts +6 -0
- package/dist/renderer/positioned-renderer.js +10 -0
- package/dist/renderer/rectangle-renderer.d.ts +3 -3
- package/dist/renderer/rectangle-renderer.js +49 -44
- package/dist/renderer/repeating-header-renderer.d.ts +3 -3
- package/dist/renderer/repeating-header-renderer.js +10 -26
- package/dist/renderer/rotated-renderer.d.ts +7 -0
- package/dist/renderer/rotated-renderer.js +29 -0
- package/dist/renderer/row-renderer.d.ts +3 -3
- package/dist/renderer/row-renderer.js +11 -28
- package/dist/renderer/struct-group-renderer.d.ts +10 -0
- package/dist/renderer/struct-group-renderer.js +24 -0
- package/dist/renderer/text-renderer.d.ts +9 -5
- package/dist/renderer/text-renderer.js +437 -89
- package/dist/text/advance.d.ts +34 -0
- package/dist/text/advance.js +45 -0
- package/dist/text/arabic.d.ts +20 -0
- package/dist/text/arabic.js +101 -0
- package/dist/text/bidi.d.ts +33 -0
- package/dist/text/bidi.js +94 -0
- package/dist/text/emoji-codepoints.d.ts +1 -0
- package/dist/text/emoji-codepoints.js +29 -0
- package/dist/text/font-fallback.d.ts +25 -0
- package/dist/text/font-fallback.js +38 -0
- package/dist/text/glyph-coverage.d.ts +15 -0
- package/dist/text/glyph-coverage.js +77 -0
- package/dist/text/line-breaker.d.ts +50 -12
- package/dist/text/line-breaker.js +200 -54
- package/dist/text/line-metrics.d.ts +49 -0
- package/dist/text/line-metrics.js +57 -0
- package/dist/text/orphans-widows.d.ts +27 -0
- package/dist/text/orphans-widows.js +53 -0
- package/dist/text/shape.d.ts +29 -0
- package/dist/text/shape.js +67 -0
- package/dist/text/text-decoration.d.ts +50 -0
- package/dist/text/text-decoration.js +62 -0
- package/dist/text/text-style.d.ts +60 -0
- package/dist/text/text-style.js +79 -0
- package/dist/text/whitespace.d.ts +4 -0
- package/dist/text/whitespace.js +43 -0
- package/dist/utils/afm-parser.d.ts +34 -0
- package/dist/utils/afm-parser.js +76 -17
- package/dist/utils/bytes.d.ts +24 -0
- package/dist/utils/bytes.js +76 -0
- package/dist/utils/dest-registry.d.ts +22 -0
- package/dist/utils/dest-registry.js +32 -0
- package/dist/utils/flex-layout.d.ts +36 -9
- package/dist/utils/flex-layout.js +207 -30
- package/dist/utils/font-metrics.d.ts +26 -1
- package/dist/utils/font-metrics.js +1 -2
- package/dist/utils/font-path.js +3 -6
- package/dist/utils/gsub.d.ts +36 -0
- package/dist/utils/gsub.js +178 -0
- package/dist/utils/image-helper.d.ts +6 -5
- package/dist/utils/image-helper.js +101 -111
- package/dist/utils/inflate.d.ts +16 -0
- package/dist/utils/inflate.js +52 -0
- package/dist/utils/md5.d.ts +4 -0
- package/dist/utils/md5.js +79 -0
- package/dist/utils/outline.d.ts +21 -0
- package/dist/utils/outline.js +79 -0
- package/dist/utils/pdf-object-manager.d.ts +127 -8
- package/dist/utils/pdf-object-manager.js +615 -141
- package/dist/utils/renderer-registry.d.ts +13 -1
- package/dist/utils/renderer-registry.js +23 -6
- package/dist/utils/struct-tree.d.ts +44 -0
- package/dist/utils/struct-tree.js +129 -0
- package/dist/utils/ttf-parser.d.ts +134 -2
- package/dist/utils/ttf-parser.js +873 -50
- package/dist/utils/ttf-subsetter.d.ts +1 -1
- package/dist/utils/ttf-subsetter.js +40 -42
- package/dist/utils/ua-xmp.d.ts +9 -0
- package/dist/utils/ua-xmp.js +32 -0
- package/dist/utils/utf8-to-windows1252-encoder.d.ts +6 -0
- package/dist/utils/utf8-to-windows1252-encoder.js +11 -4
- package/dist/utils/woff.d.ts +10 -0
- package/dist/utils/woff.js +116 -0
- package/dist/validators/element-validator.d.ts +2 -2
- package/dist/validators/element-validator.js +31 -26
- package/package.json +22 -2
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where an underline and a strikethrough sit, and how thick they are.
|
|
3
|
+
*
|
|
4
|
+
* These are GLYPH metrics, and they are deliberately NOT part of `FontVerticals` in
|
|
5
|
+
* `line-metrics.ts`, which holds LINE metrics. Confusing the two is exactly what produced ISSUE-5:
|
|
6
|
+
* the AFM's `Ascender` (the height of a `b`) was used to seat a baseline, a job that belongs to the
|
|
7
|
+
* `FontBBox`. Keep the two kinds of number in two places so they cannot be mixed up again.
|
|
8
|
+
*
|
|
9
|
+
* Every number here comes out of the font: the standard-14 AFM header, or a TrueType `post` +
|
|
10
|
+
* `OS/2` table. Nothing is invented. Verified against `google-chrome --headless --print-to-pdf` on
|
|
11
|
+
* the same font: Chrome's strikethrough centre lands on `xHeight / 2` (measured 0.260 em vs the
|
|
12
|
+
* font's 0.262), and its underline stroke with `text-decoration-thickness: from-font` is the font's
|
|
13
|
+
* `underlineThickness` (0.048 em vs 0.050). Chrome does NOT use the font's underline POSITION - it
|
|
14
|
+
* has its own offset - so we follow the font there and land within 0.02 em of it.
|
|
15
|
+
*/
|
|
16
|
+
/** The underline for a run whose baseline sits at `baselineY` (engine coords, y grows down). */
|
|
17
|
+
export function underlineStroke(decoration, fontSize, baselineY) {
|
|
18
|
+
return {
|
|
19
|
+
y: baselineY + decoration.underlinePosition * fontSize,
|
|
20
|
+
thickness: decoration.underlineThickness * fontSize,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The strikethrough for a run whose baseline sits at `baselineY`. It crosses at half the x-height,
|
|
25
|
+
* which is what a browser does and what looks right: through the middle of the lowercase letters,
|
|
26
|
+
* not through the middle of the capitals.
|
|
27
|
+
*/
|
|
28
|
+
export function strikethroughStroke(decoration, fontSize, baselineY) {
|
|
29
|
+
return {
|
|
30
|
+
y: baselineY - (decoration.xHeight / 2) * fontSize,
|
|
31
|
+
thickness: decoration.underlineThickness * fontSize,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Horizontal breathing room left on each side of a descender that interrupts an underline. Unlike
|
|
36
|
+
* everything else in this file this is a DESIGN choice, not a number read from the font - so it is
|
|
37
|
+
* expressed in the one quantity the font does give us, the stroke thickness.
|
|
38
|
+
*/
|
|
39
|
+
const SKIP_INK_PADDING = 1.0;
|
|
40
|
+
/**
|
|
41
|
+
* Cuts `[0, runWidth]` at every place the glyphs put ink, leaving the segments an underline should
|
|
42
|
+
* actually draw. `inkSpans` are x-intervals in points from the start of the run.
|
|
43
|
+
*
|
|
44
|
+
* This is CSS `text-decoration-skip-ink`, and it needs the real glyph outlines - so it only works
|
|
45
|
+
* for an embedded font. Segments narrower than the padding are dropped: a sliver of a line between
|
|
46
|
+
* two descenders reads as dirt, not as an underline.
|
|
47
|
+
*/
|
|
48
|
+
export function skipInkSegments(runWidth, inkSpans, thickness) {
|
|
49
|
+
const pad = SKIP_INK_PADDING * thickness;
|
|
50
|
+
const segments = [];
|
|
51
|
+
let cursor = 0;
|
|
52
|
+
for (const [start, end] of inkSpans) {
|
|
53
|
+
const gapStart = Math.max(0, start - pad);
|
|
54
|
+
const gapEnd = Math.min(runWidth, end + pad);
|
|
55
|
+
if (gapStart > cursor + pad)
|
|
56
|
+
segments.push([cursor, gapStart]);
|
|
57
|
+
cursor = Math.max(cursor, gapEnd);
|
|
58
|
+
}
|
|
59
|
+
if (runWidth > cursor + pad)
|
|
60
|
+
segments.push([cursor, runWidth]);
|
|
61
|
+
return segments;
|
|
62
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Color } from "../common/color.js";
|
|
2
|
+
import { FontStyle } from "../utils/pdf-object-manager.js";
|
|
3
|
+
import { HorizontalAlignment } from "../elements/pdf-element.js";
|
|
4
|
+
import type { Direction } from "./bidi.js";
|
|
5
|
+
/** CSS `text-transform`. `capitalize` upper-cases the first letter of every word, as CSS does. */
|
|
6
|
+
export type TextTransform = "none" | "uppercase" | "lowercase" | "capitalize";
|
|
7
|
+
/**
|
|
8
|
+
* Apply a `text-transform`. One place, so the measured text and the drawn text are the same string.
|
|
9
|
+
*
|
|
10
|
+
* `atWordStart` carries the capitalisation state ACROSS pieces: a word split over two spans
|
|
11
|
+
* (`span("hel") + span("lo")`) is still one word, and only its first letter is raised.
|
|
12
|
+
*/
|
|
13
|
+
export declare function applyTextTransform(text: string, transform: TextTransform, atWordStart?: boolean): {
|
|
14
|
+
text: string;
|
|
15
|
+
atWordStart: boolean;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The inheritable text properties - the same set CSS and Flutter cascade. A `Text` resolves each of
|
|
19
|
+
* its own (possibly unset) properties against the nearest cascaded style: explicit > inherited >
|
|
20
|
+
* built-in default. Box/layout properties (padding, border, width, ...) are deliberately NOT here -
|
|
21
|
+
* they never inherit, exactly as in CSS.
|
|
22
|
+
*/
|
|
23
|
+
export interface ResolvedTextStyle {
|
|
24
|
+
fontSize: number;
|
|
25
|
+
fontFamily: string;
|
|
26
|
+
/** The rest of the family stack: tried, in order, for a code point `fontFamily` cannot draw. */
|
|
27
|
+
fontFallback: string[];
|
|
28
|
+
fontStyle: FontStyle;
|
|
29
|
+
color: Color;
|
|
30
|
+
textAlignment: HorizontalAlignment;
|
|
31
|
+
/** Multiplier of the font size. `undefined` means the font's natural line height
|
|
32
|
+
* (`ascent + descent + lineGap`), i.e. CSS `line-height: normal`. */
|
|
33
|
+
lineHeight?: number;
|
|
34
|
+
/** Draw a line under the text, at the position and thickness the font declares. */
|
|
35
|
+
underline: boolean;
|
|
36
|
+
/** Draw a line through the text, at half its x-height. */
|
|
37
|
+
strikethrough: boolean;
|
|
38
|
+
/** Let the underline step around descenders (CSS `text-decoration-skip-ink`). Needs an EMBEDDED
|
|
39
|
+
* font: the standard-14 outlines live in the viewer, not in the AFM. */
|
|
40
|
+
skipInk: boolean;
|
|
41
|
+
/** Extra space after every glyph, in points (CSS `letter-spacing`). Default 0. */
|
|
42
|
+
letterSpacing: number;
|
|
43
|
+
/** Base writing direction (CSS `direction`). It decides where a line STARTS and how neutral
|
|
44
|
+
* characters between two scripts resolve; the reordering itself follows Unicode UAX #9 either
|
|
45
|
+
* way, so Hebrew inside an `ltr` paragraph still comes out right. */
|
|
46
|
+
direction: Direction;
|
|
47
|
+
/** CSS `text-transform`: recase the text before it is measured or drawn. */
|
|
48
|
+
textTransform: TextTransform;
|
|
49
|
+
/** CSS `word-spacing`, in points: extra advance at every space. Negative tightens. */
|
|
50
|
+
wordSpacing: number;
|
|
51
|
+
/** CSS `text-indent`, in points: how far the FIRST line of a paragraph starts in. */
|
|
52
|
+
textIndent: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The root of the cascade: what a `Text` falls back to when neither it nor any ancestor sets a
|
|
56
|
+
* property.
|
|
57
|
+
*/
|
|
58
|
+
export declare const DEFAULT_TEXT_STYLE: ResolvedTextStyle;
|
|
59
|
+
/** Layers a partial override onto a complete style; an unset (undefined) field keeps the base. */
|
|
60
|
+
export declare function mergeTextStyle(base: ResolvedTextStyle, override?: Partial<ResolvedTextStyle>): ResolvedTextStyle;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Color } from "../common/color.js";
|
|
2
|
+
import { FontStyle } from "../utils/pdf-object-manager.js";
|
|
3
|
+
import { HorizontalAlignment } from "../elements/pdf-element.js";
|
|
4
|
+
/**
|
|
5
|
+
* Apply a `text-transform`. One place, so the measured text and the drawn text are the same string.
|
|
6
|
+
*
|
|
7
|
+
* `atWordStart` carries the capitalisation state ACROSS pieces: a word split over two spans
|
|
8
|
+
* (`span("hel") + span("lo")`) is still one word, and only its first letter is raised.
|
|
9
|
+
*/
|
|
10
|
+
export function applyTextTransform(text, transform, atWordStart = true) {
|
|
11
|
+
const ends = text === "" ? atWordStart : /\s$/u.test(text);
|
|
12
|
+
if (transform === "none")
|
|
13
|
+
return { text, atWordStart: ends };
|
|
14
|
+
if (transform === "uppercase")
|
|
15
|
+
return { text: text.toUpperCase(), atWordStart: ends };
|
|
16
|
+
if (transform === "lowercase")
|
|
17
|
+
return { text: text.toLowerCase(), atWordStart: ends };
|
|
18
|
+
// CSS raises the first typographic LETTER of every word. Punctuation is not one, so a leading
|
|
19
|
+
// bracket or quote does not consume the word start - `(hello)` becomes `(Hello)`, as in a browser.
|
|
20
|
+
let start = atWordStart;
|
|
21
|
+
let out = "";
|
|
22
|
+
for (const ch of text) {
|
|
23
|
+
if (/\s/u.test(ch)) {
|
|
24
|
+
out += ch;
|
|
25
|
+
start = true;
|
|
26
|
+
}
|
|
27
|
+
else if (start && /\p{L}|\p{N}/u.test(ch)) {
|
|
28
|
+
out += ch.toUpperCase();
|
|
29
|
+
start = false;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
out += ch;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { text: out, atWordStart: start };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The root of the cascade: what a `Text` falls back to when neither it nor any ancestor sets a
|
|
39
|
+
* property.
|
|
40
|
+
*/
|
|
41
|
+
export const DEFAULT_TEXT_STYLE = {
|
|
42
|
+
fontSize: 12,
|
|
43
|
+
fontFamily: "Helvetica",
|
|
44
|
+
fontFallback: [],
|
|
45
|
+
fontStyle: FontStyle.Normal,
|
|
46
|
+
color: new Color(0, 0, 0),
|
|
47
|
+
textAlignment: HorizontalAlignment.start,
|
|
48
|
+
lineHeight: undefined,
|
|
49
|
+
underline: false,
|
|
50
|
+
strikethrough: false,
|
|
51
|
+
skipInk: false,
|
|
52
|
+
letterSpacing: 0,
|
|
53
|
+
direction: "ltr",
|
|
54
|
+
textTransform: "none",
|
|
55
|
+
wordSpacing: 0,
|
|
56
|
+
textIndent: 0,
|
|
57
|
+
};
|
|
58
|
+
/** Layers a partial override onto a complete style; an unset (undefined) field keeps the base. */
|
|
59
|
+
export function mergeTextStyle(base, override) {
|
|
60
|
+
if (!override)
|
|
61
|
+
return base;
|
|
62
|
+
return {
|
|
63
|
+
fontSize: override.fontSize ?? base.fontSize,
|
|
64
|
+
fontFamily: override.fontFamily ?? base.fontFamily,
|
|
65
|
+
fontFallback: override.fontFallback ?? base.fontFallback,
|
|
66
|
+
fontStyle: override.fontStyle ?? base.fontStyle,
|
|
67
|
+
color: override.color ?? base.color,
|
|
68
|
+
textAlignment: override.textAlignment ?? base.textAlignment,
|
|
69
|
+
lineHeight: override.lineHeight ?? base.lineHeight,
|
|
70
|
+
underline: override.underline ?? base.underline,
|
|
71
|
+
strikethrough: override.strikethrough ?? base.strikethrough,
|
|
72
|
+
skipInk: override.skipInk ?? base.skipInk,
|
|
73
|
+
letterSpacing: override.letterSpacing ?? base.letterSpacing,
|
|
74
|
+
direction: override.direction ?? base.direction,
|
|
75
|
+
textTransform: override.textTransform ?? base.textTransform,
|
|
76
|
+
wordSpacing: override.wordSpacing ?? base.wordSpacing,
|
|
77
|
+
textIndent: override.textIndent ?? base.textIndent,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { TextSegment } from "../elements/text-element.js";
|
|
2
|
+
export declare function normalizeWhitespace(text: string): string;
|
|
3
|
+
/** The same, for content that may already be split into styled spans. */
|
|
4
|
+
export declare function normalizeContent(content: string | TextSegment[]): string | TextSegment[];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A character with no glyph is still ENCODED and DRAWN: it comes out as the font's .notdef box, and
|
|
3
|
+
* veraPDF rejects the file for referencing .notdef (ISO 19005-3, 6.2.11.8). A `\n` in an invoice
|
|
4
|
+
* description was the first case we hit; zero-width characters are the same fault and arrive far more
|
|
5
|
+
* quietly - a paste out of Word or a web page carries them routinely.
|
|
6
|
+
*
|
|
7
|
+
* So nothing invisible survives into the breaker except `\n`, and that only because the breaker treats
|
|
8
|
+
* it as a hard break. The mapping follows a browser's `white-space: normal` apart from `\n`: CSS
|
|
9
|
+
* collapses that to a space, while a text COMPONENT breaks on it - Flutter's `Text` and react-pdf both
|
|
10
|
+
* do, and it is what anyone writing `"a\nb"` expects.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Everything that MEANS a line break, matched as a WHOLE so a two-character ending never becomes two
|
|
14
|
+
* breaks. `\n\r` is in here beside `\r\n` because it is what a mistyped CRLF looks like, and a stray
|
|
15
|
+
* blank line is a worse answer than reading it as the one break it was meant to be. U+2028/U+2029 are
|
|
16
|
+
* Unicode's own line and paragraph separators.
|
|
17
|
+
*/
|
|
18
|
+
const LINE_BREAKS = /\r\n|\n\r|\r|\u2028|\u2029/g;
|
|
19
|
+
/**
|
|
20
|
+
* Invisible and inert: a zero-width space and a byte-order mark carry no meaning we act on, so they are
|
|
21
|
+
* dropped rather than drawn. C0, C1 and DEL likewise.
|
|
22
|
+
*
|
|
23
|
+
* Deliberately NOT here: U+200C/U+200D (zero-width non-joiner and joiner) drive Arabic shaping and
|
|
24
|
+
* emoji sequences, and U+200E/U+200F (the bidi marks) drive `text/bidi.ts`. Dropping those would
|
|
25
|
+
* silently change how correct text is laid out - worse than the box they can still leave behind in a
|
|
26
|
+
* run that neither shapes nor reorders.
|
|
27
|
+
*/
|
|
28
|
+
// oxlint-disable-next-line no-control-regex -- matching control characters is the whole point here.
|
|
29
|
+
const UNPRINTABLE = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F\u200B\uFEFF]/g;
|
|
30
|
+
export function normalizeWhitespace(text) {
|
|
31
|
+
return (text
|
|
32
|
+
// Every spelling of a break becomes `\n` first, so the breaker only ever sees one form.
|
|
33
|
+
.replace(LINE_BREAKS, "\n")
|
|
34
|
+
// A tab is collapsible whitespace; a PDF text operator has no tab stops to advance to.
|
|
35
|
+
.replace(/\t/g, " ")
|
|
36
|
+
.replace(UNPRINTABLE, ""));
|
|
37
|
+
}
|
|
38
|
+
/** The same, for content that may already be split into styled spans. */
|
|
39
|
+
export function normalizeContent(content) {
|
|
40
|
+
if (typeof content === "string")
|
|
41
|
+
return normalizeWhitespace(content);
|
|
42
|
+
return content.map((seg) => ({ ...seg, content: normalizeWhitespace(seg.content) }));
|
|
43
|
+
}
|
|
@@ -1,12 +1,46 @@
|
|
|
1
|
+
import type { FontVerticals } from "../text/line-metrics.js";
|
|
2
|
+
import type { FontDecoration } from "../text/text-decoration.js";
|
|
1
3
|
export declare class AFMParser {
|
|
2
4
|
private advanceWidths;
|
|
3
5
|
private kerningPairs;
|
|
4
6
|
private glyphMap;
|
|
7
|
+
private bbox;
|
|
8
|
+
private cachedVerticals?;
|
|
9
|
+
private underlinePosition;
|
|
10
|
+
private underlineThickness;
|
|
11
|
+
private capHeight;
|
|
12
|
+
private xHeight;
|
|
13
|
+
private cachedDecoration?;
|
|
5
14
|
constructor(afmData: string);
|
|
6
15
|
private loadGlyphList;
|
|
7
16
|
private getGlyphName;
|
|
8
17
|
private parseAFMData;
|
|
9
18
|
private parseCharMetrics;
|
|
19
|
+
/**
|
|
20
|
+
* The font's LINE metrics, as fractions of the em: how far a line box must reach above and below
|
|
21
|
+
* the baseline. Taken from the `FontBBox`.
|
|
22
|
+
*
|
|
23
|
+
* Not from the AFM's `Ascender` / `Descender` lines, and that distinction is the whole point.
|
|
24
|
+
* `Ascender 718` is a GLYPH metric - the height of `b`, `d`, `h` - and for Helvetica it happens to
|
|
25
|
+
* equal the cap height. The line-metric equivalent is TrueType's `hhea.ascent`, which is much
|
|
26
|
+
* taller because it has to clear the accented capitals: Arial declares 0.905 where its caps reach
|
|
27
|
+
* 0.716. That surplus above the caps (0.189) is about as large as the descent below the baseline
|
|
28
|
+
* (0.212), which is exactly why capitals look vertically centred in a browser.
|
|
29
|
+
*
|
|
30
|
+
* Helvetica's `FontBBox` is `-166 -225 1000 931`: 0.931 up, 0.225 down - within a hair of Arial's
|
|
31
|
+
* hhea numbers. So the bbox IS the standard-14 line metric, and there is no leftover slack to
|
|
32
|
+
* call a lineGap. (Seating the baseline at `Ascender` instead put every capital too high; see
|
|
33
|
+
* ISSUE-5.)
|
|
34
|
+
*/
|
|
35
|
+
verticals(): FontVerticals;
|
|
36
|
+
/**
|
|
37
|
+
* The font's decoration metrics (underline / strikethrough), as fractions of the em.
|
|
38
|
+
*
|
|
39
|
+
* Symbol and ZapfDingbats declare no `CapHeight`/`XHeight` - they have no lowercase letters. For
|
|
40
|
+
* those the bbox top is the only height the font gives us, so the capitals reach it and a
|
|
41
|
+
* strikethrough crosses at half of that. Still read from the font, not guessed.
|
|
42
|
+
*/
|
|
43
|
+
decoration(): FontDecoration;
|
|
10
44
|
getAdvanceWidth(charName: string): number;
|
|
11
45
|
getKerning(firstChar: string, secondChar: string): number;
|
|
12
46
|
}
|
package/dist/utils/afm-parser.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.AFMParser = void 0;
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
|
-
class AFMParser {
|
|
1
|
+
import { AGL } from "../assets/font-data.js";
|
|
2
|
+
export class AFMParser {
|
|
10
3
|
constructor(afmData) {
|
|
11
4
|
this.advanceWidths = {};
|
|
12
5
|
this.kerningPairs = {};
|
|
13
6
|
this.glyphMap = {};
|
|
7
|
+
// The font's bounding box in glyph space (1000 units / em), from the AFM header. This - not the
|
|
8
|
+
// `Ascender` line - is what a line box is built from; see `verticals()`.
|
|
9
|
+
this.bbox = [0, 0, 0, 0];
|
|
10
|
+
// Glyph metrics from the AFM header, for underline / strikethrough. All 14 standard fonts carry
|
|
11
|
+
// the underline pair; Symbol and ZapfDingbats have no CapHeight/XHeight (they have no lowercase).
|
|
12
|
+
this.underlinePosition = 0; // negative, as the AFM writes it: below the baseline
|
|
13
|
+
this.underlineThickness = 0;
|
|
14
|
+
this.capHeight = 0;
|
|
15
|
+
this.xHeight = 0;
|
|
14
16
|
this.parseAFMData(afmData);
|
|
15
17
|
this.loadGlyphList();
|
|
16
18
|
}
|
|
17
19
|
loadGlyphList() {
|
|
18
|
-
const
|
|
19
|
-
const fileContent = fs_1.default.readFileSync(afmFilePath, "utf-8");
|
|
20
|
-
const lines = fileContent.split("\n");
|
|
20
|
+
const lines = AGL.split("\n");
|
|
21
21
|
for (const line of lines) {
|
|
22
22
|
const parts = line.trim().split(";");
|
|
23
23
|
if (parts.length >= 2) {
|
|
@@ -34,6 +34,7 @@ class AFMParser {
|
|
|
34
34
|
return this.glyphMap[char] || char;
|
|
35
35
|
}
|
|
36
36
|
parseAFMData(afmData) {
|
|
37
|
+
var _a;
|
|
37
38
|
const lines = afmData.split("\n");
|
|
38
39
|
let inCharMetrics = false;
|
|
39
40
|
for (let line of lines) {
|
|
@@ -53,13 +54,31 @@ class AFMParser {
|
|
|
53
54
|
this.advanceWidths[charName] = wx;
|
|
54
55
|
}
|
|
55
56
|
}
|
|
57
|
+
if (line.startsWith("UnderlinePosition ")) {
|
|
58
|
+
this.underlinePosition = parseFloat(line.slice(18));
|
|
59
|
+
}
|
|
60
|
+
if (line.startsWith("UnderlineThickness ")) {
|
|
61
|
+
this.underlineThickness = parseFloat(line.slice(19));
|
|
62
|
+
}
|
|
63
|
+
if (line.startsWith("CapHeight ")) {
|
|
64
|
+
this.capHeight = parseFloat(line.slice(10));
|
|
65
|
+
}
|
|
66
|
+
if (line.startsWith("XHeight ")) {
|
|
67
|
+
this.xHeight = parseFloat(line.slice(8));
|
|
68
|
+
}
|
|
69
|
+
if (line.startsWith("FontBBox ")) {
|
|
70
|
+
const [x0, y0, x1, y1] = line.slice(9).trim().split(/\s+/).map(parseFloat);
|
|
71
|
+
this.bbox = [x0, y0, x1, y1];
|
|
72
|
+
}
|
|
56
73
|
if (line.startsWith("KPX")) {
|
|
57
74
|
const parts = line.split(/\s+/);
|
|
58
75
|
const firstChar = parts[1];
|
|
59
76
|
const secondChar = parts[2];
|
|
60
77
|
const kerning = parseFloat(parts[3]);
|
|
61
|
-
|
|
62
|
-
|
|
78
|
+
// Nested by first glyph, then second. A flat `${a}-${b}` key would have to be BUILT on every
|
|
79
|
+
// lookup, and getKerning runs once per adjacent character pair - that string allocation was a
|
|
80
|
+
// quarter of a standard-font render.
|
|
81
|
+
((_a = this.kerningPairs)[firstChar] ?? (_a[firstChar] = {}))[secondChar] = kerning;
|
|
63
82
|
}
|
|
64
83
|
}
|
|
65
84
|
}
|
|
@@ -80,12 +99,52 @@ class AFMParser {
|
|
|
80
99
|
}
|
|
81
100
|
return null;
|
|
82
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* The font's LINE metrics, as fractions of the em: how far a line box must reach above and below
|
|
104
|
+
* the baseline. Taken from the `FontBBox`.
|
|
105
|
+
*
|
|
106
|
+
* Not from the AFM's `Ascender` / `Descender` lines, and that distinction is the whole point.
|
|
107
|
+
* `Ascender 718` is a GLYPH metric - the height of `b`, `d`, `h` - and for Helvetica it happens to
|
|
108
|
+
* equal the cap height. The line-metric equivalent is TrueType's `hhea.ascent`, which is much
|
|
109
|
+
* taller because it has to clear the accented capitals: Arial declares 0.905 where its caps reach
|
|
110
|
+
* 0.716. That surplus above the caps (0.189) is about as large as the descent below the baseline
|
|
111
|
+
* (0.212), which is exactly why capitals look vertically centred in a browser.
|
|
112
|
+
*
|
|
113
|
+
* Helvetica's `FontBBox` is `-166 -225 1000 931`: 0.931 up, 0.225 down - within a hair of Arial's
|
|
114
|
+
* hhea numbers. So the bbox IS the standard-14 line metric, and there is no leftover slack to
|
|
115
|
+
* call a lineGap. (Seating the baseline at `Ascender` instead put every capital too high; see
|
|
116
|
+
* ISSUE-5.)
|
|
117
|
+
*/
|
|
118
|
+
verticals() {
|
|
119
|
+
if (this.cachedVerticals)
|
|
120
|
+
return this.cachedVerticals;
|
|
121
|
+
const [, yMin, , yMax] = this.bbox;
|
|
122
|
+
this.cachedVerticals = { ascent: yMax / 1000, descent: -yMin / 1000, lineGap: 0 };
|
|
123
|
+
return this.cachedVerticals;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The font's decoration metrics (underline / strikethrough), as fractions of the em.
|
|
127
|
+
*
|
|
128
|
+
* Symbol and ZapfDingbats declare no `CapHeight`/`XHeight` - they have no lowercase letters. For
|
|
129
|
+
* those the bbox top is the only height the font gives us, so the capitals reach it and a
|
|
130
|
+
* strikethrough crosses at half of that. Still read from the font, not guessed.
|
|
131
|
+
*/
|
|
132
|
+
decoration() {
|
|
133
|
+
if (this.cachedDecoration)
|
|
134
|
+
return this.cachedDecoration;
|
|
135
|
+
const capHeight = this.capHeight || this.bbox[3];
|
|
136
|
+
this.cachedDecoration = {
|
|
137
|
+
underlinePosition: -this.underlinePosition / 1000, // AFM writes it negative (below the baseline)
|
|
138
|
+
underlineThickness: this.underlineThickness / 1000,
|
|
139
|
+
capHeight: capHeight / 1000,
|
|
140
|
+
xHeight: (this.xHeight || capHeight / 2) / 1000,
|
|
141
|
+
};
|
|
142
|
+
return this.cachedDecoration;
|
|
143
|
+
}
|
|
83
144
|
getAdvanceWidth(charName) {
|
|
84
145
|
return this.advanceWidths[this.getGlyphName(charName)] || 0;
|
|
85
146
|
}
|
|
86
147
|
getKerning(firstChar, secondChar) {
|
|
87
|
-
|
|
88
|
-
return this.kerningPairs[pair] || 0;
|
|
148
|
+
return this.kerningPairs[firstChar]?.[secondChar] ?? 0;
|
|
89
149
|
}
|
|
90
150
|
}
|
|
91
|
-
exports.AFMParser = AFMParser;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `Uint8Array` → a latin1 (ISO-8859-1) string: each byte 0x00-0xFF maps 1:1 to the same code point.
|
|
3
|
+
* This matches `Buffer.toString("latin1")` exactly. NOTE: do NOT use `TextDecoder("latin1")` here - per
|
|
4
|
+
* the encoding spec that label is really windows-1252, which mangles 0x80-0x9F; we need a true 1:1 pass
|
|
5
|
+
* so arbitrary binary (e.g. a compressed stream) rides through unchanged. The PDF body is assembled as
|
|
6
|
+
* such a binary string and the final encoder passes 0x00-0xFF through.
|
|
7
|
+
*/
|
|
8
|
+
export declare function latin1FromBytes(bytes: Uint8Array): string;
|
|
9
|
+
/**
|
|
10
|
+
* A latin1 (ISO-8859-1) string → a `Uint8Array`: each char's low byte. Matches `Buffer.from(str,
|
|
11
|
+
* "latin1")` / `"binary"`. Use for the binary strings the engine assembles char-by-char (CMaps, object
|
|
12
|
+
* bodies) before they become a stream.
|
|
13
|
+
*/
|
|
14
|
+
export declare function bytesFromLatin1(str: string): Uint8Array;
|
|
15
|
+
export declare function u8(b: Uint8Array, o: number): number;
|
|
16
|
+
export declare function u16(b: Uint8Array, o: number): number;
|
|
17
|
+
export declare function u32(b: Uint8Array, o: number): number;
|
|
18
|
+
export declare function i16(b: Uint8Array, o: number): number;
|
|
19
|
+
export declare function wu16(b: Uint8Array, value: number, offset: number): void;
|
|
20
|
+
export declare function wu32(b: Uint8Array, value: number, offset: number): void;
|
|
21
|
+
export declare function wi16(b: Uint8Array, value: number, offset: number): void;
|
|
22
|
+
export declare function writeLatin1(b: Uint8Array, str: string, offset: number): void;
|
|
23
|
+
/** Concatenate Uint8Arrays into one (mirrors Buffer.concat). */
|
|
24
|
+
export declare function concatBytes(parts: Uint8Array[]): Uint8Array;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Byte helpers for an isomorphic engine (Node + browser). Kept tiny + dependency-free; as the engine
|
|
2
|
+
// moves off Node-only APIs, the Node `Buffer` conveniences are replaced by these.
|
|
3
|
+
/**
|
|
4
|
+
* A `Uint8Array` → a latin1 (ISO-8859-1) string: each byte 0x00-0xFF maps 1:1 to the same code point.
|
|
5
|
+
* This matches `Buffer.toString("latin1")` exactly. NOTE: do NOT use `TextDecoder("latin1")` here - per
|
|
6
|
+
* the encoding spec that label is really windows-1252, which mangles 0x80-0x9F; we need a true 1:1 pass
|
|
7
|
+
* so arbitrary binary (e.g. a compressed stream) rides through unchanged. The PDF body is assembled as
|
|
8
|
+
* such a binary string and the final encoder passes 0x00-0xFF through.
|
|
9
|
+
*/
|
|
10
|
+
export function latin1FromBytes(bytes) {
|
|
11
|
+
let out = "";
|
|
12
|
+
const CHUNK = 0x8000; // chunk the apply() to stay under the argument-count limit on big streams
|
|
13
|
+
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
14
|
+
out += String.fromCharCode.apply(null, bytes.subarray(i, i + CHUNK));
|
|
15
|
+
}
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A latin1 (ISO-8859-1) string → a `Uint8Array`: each char's low byte. Matches `Buffer.from(str,
|
|
20
|
+
* "latin1")` / `"binary"`. Use for the binary strings the engine assembles char-by-char (CMaps, object
|
|
21
|
+
* bodies) before they become a stream.
|
|
22
|
+
*/
|
|
23
|
+
export function bytesFromLatin1(str) {
|
|
24
|
+
const u8a = new Uint8Array(str.length);
|
|
25
|
+
for (let i = 0; i < str.length; i++)
|
|
26
|
+
u8a[i] = str.charCodeAt(i) & 0xff;
|
|
27
|
+
return u8a;
|
|
28
|
+
}
|
|
29
|
+
// Big-endian integer reads from a Uint8Array (TrueType + PDF are big-endian), mirroring
|
|
30
|
+
// Buffer.readUInt8 / readUInt16BE / readUInt32BE / readInt16BE without needing a Buffer.
|
|
31
|
+
export function u8(b, o) {
|
|
32
|
+
return b[o];
|
|
33
|
+
}
|
|
34
|
+
export function u16(b, o) {
|
|
35
|
+
return (b[o] << 8) | b[o + 1];
|
|
36
|
+
}
|
|
37
|
+
export function u32(b, o) {
|
|
38
|
+
// Multiply the top byte (a left shift by 24 would go negative in 32-bit signed math).
|
|
39
|
+
return b[o] * 0x1000000 + ((b[o + 1] << 16) | (b[o + 2] << 8) | b[o + 3]);
|
|
40
|
+
}
|
|
41
|
+
export function i16(b, o) {
|
|
42
|
+
const v = (b[o] << 8) | b[o + 1];
|
|
43
|
+
return v & 0x8000 ? v - 0x10000 : v;
|
|
44
|
+
}
|
|
45
|
+
// Big-endian integer + latin1-string writes into a Uint8Array, mirroring Buffer.writeUInt16BE /
|
|
46
|
+
// writeUInt32BE / writeInt16BE / write(str, off, len, "latin1"). Arg order matches Buffer: (value, offset).
|
|
47
|
+
export function wu16(b, value, offset) {
|
|
48
|
+
b[offset] = (value >>> 8) & 0xff;
|
|
49
|
+
b[offset + 1] = value & 0xff;
|
|
50
|
+
}
|
|
51
|
+
export function wu32(b, value, offset) {
|
|
52
|
+
b[offset] = (value >>> 24) & 0xff;
|
|
53
|
+
b[offset + 1] = (value >>> 16) & 0xff;
|
|
54
|
+
b[offset + 2] = (value >>> 8) & 0xff;
|
|
55
|
+
b[offset + 3] = value & 0xff;
|
|
56
|
+
}
|
|
57
|
+
export function wi16(b, value, offset) {
|
|
58
|
+
wu16(b, value & 0xffff, offset);
|
|
59
|
+
}
|
|
60
|
+
export function writeLatin1(b, str, offset) {
|
|
61
|
+
for (let i = 0; i < str.length; i++)
|
|
62
|
+
b[offset + i] = str.charCodeAt(i) & 0xff;
|
|
63
|
+
}
|
|
64
|
+
/** Concatenate Uint8Arrays into one (mirrors Buffer.concat). */
|
|
65
|
+
export function concatBytes(parts) {
|
|
66
|
+
let len = 0;
|
|
67
|
+
for (const p of parts)
|
|
68
|
+
len += p.length;
|
|
69
|
+
const out = new Uint8Array(len);
|
|
70
|
+
let off = 0;
|
|
71
|
+
for (const p of parts) {
|
|
72
|
+
out.set(p, off);
|
|
73
|
+
off += p.length;
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PDFObjectManager } from "./pdf-object-manager.js";
|
|
2
|
+
interface Dest {
|
|
3
|
+
pageObjNum: number;
|
|
4
|
+
top: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Collects named destinations - the jump targets an internal `Link({ to })` points at, declared with
|
|
8
|
+
* `Anchor({ name })`. `finalize` emits the `/Dests` name-tree fragment for the catalog `/Names` dict, or
|
|
9
|
+
* "" when no anchor was placed. Names are strings the viewer resolves at click time, so a link can point
|
|
10
|
+
* at an anchor on a later (not-yet-rendered) page without any forward-reference bookkeeping.
|
|
11
|
+
*/
|
|
12
|
+
export declare class DestRegistry {
|
|
13
|
+
private dests;
|
|
14
|
+
add(name: string, dest: Dest): void;
|
|
15
|
+
get isEmpty(): boolean;
|
|
16
|
+
/** The `/Dests << /Names [ ... ] >>` fragment for the catalog `/Names` dictionary (merged with any
|
|
17
|
+
* `/EmbeddedFiles`). Returns "" when empty. Names are sorted lexically - a name tree requires it.
|
|
18
|
+
* Takes the object manager only so the names go through its string choke-point and get encrypted
|
|
19
|
+
* with everything else. */
|
|
20
|
+
finalize(om: PDFObjectManager): string;
|
|
21
|
+
}
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const num = (n) => (Number.isFinite(n) ? Number(n.toFixed(2)) : 0);
|
|
2
|
+
/**
|
|
3
|
+
* Collects named destinations - the jump targets an internal `Link({ to })` points at, declared with
|
|
4
|
+
* `Anchor({ name })`. `finalize` emits the `/Dests` name-tree fragment for the catalog `/Names` dict, or
|
|
5
|
+
* "" when no anchor was placed. Names are strings the viewer resolves at click time, so a link can point
|
|
6
|
+
* at an anchor on a later (not-yet-rendered) page without any forward-reference bookkeeping.
|
|
7
|
+
*/
|
|
8
|
+
export class DestRegistry {
|
|
9
|
+
constructor() {
|
|
10
|
+
// Keyed by name so a duplicate name resolves to its last definition (last anchor wins).
|
|
11
|
+
this.dests = new Map();
|
|
12
|
+
}
|
|
13
|
+
add(name, dest) {
|
|
14
|
+
this.dests.set(name, dest);
|
|
15
|
+
}
|
|
16
|
+
get isEmpty() {
|
|
17
|
+
return this.dests.size === 0;
|
|
18
|
+
}
|
|
19
|
+
/** The `/Dests << /Names [ ... ] >>` fragment for the catalog `/Names` dictionary (merged with any
|
|
20
|
+
* `/EmbeddedFiles`). Returns "" when empty. Names are sorted lexically - a name tree requires it.
|
|
21
|
+
* Takes the object manager only so the names go through its string choke-point and get encrypted
|
|
22
|
+
* with everything else. */
|
|
23
|
+
finalize(om) {
|
|
24
|
+
if (this.dests.size === 0)
|
|
25
|
+
return "";
|
|
26
|
+
const entries = [...this.dests.keys()].sort().map((name) => {
|
|
27
|
+
const d = this.dests.get(name);
|
|
28
|
+
return `${om.pdfString(name)} [${d.pageObjNum} 0 R /XYZ null ${num(d.top)} null]`;
|
|
29
|
+
});
|
|
30
|
+
return `/Dests << /Names [ ${entries.join(" ")} ] >>`;
|
|
31
|
+
}
|
|
32
|
+
}
|