@jasy/pdf 1.0.0-alpha.6 → 1.0.0-alpha.8
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 +7 -7
- package/dist/api/descriptor.js +26 -1
- package/dist/api/forms.d.ts +347 -0
- package/dist/api/forms.js +189 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/layout.d.ts +47 -14
- package/dist/api/layout.js +47 -11
- package/dist/api/structure.d.ts +11 -0
- package/dist/api/structure.js +2 -0
- package/dist/api/text.d.ts +18 -2
- package/dist/api/text.js +15 -0
- package/dist/crypto/legacy-handler.d.ts +43 -0
- package/dist/crypto/legacy-handler.js +145 -0
- package/dist/crypto/security-handler.d.ts +16 -0
- package/dist/crypto/security-handler.js +26 -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 +135 -0
- package/dist/edit/document.js +649 -0
- package/dist/edit/fill.d.ts +48 -0
- package/dist/edit/fill.js +395 -0
- package/dist/edit/flatten.d.ts +42 -0
- package/dist/edit/flatten.js +437 -0
- package/dist/edit/index.d.ts +18 -0
- package/dist/edit/index.js +16 -0
- package/dist/edit/lexer.d.ts +30 -0
- package/dist/edit/lexer.js +285 -0
- package/dist/edit/objects.d.ts +63 -0
- package/dist/edit/objects.js +56 -0
- package/dist/edit/writer.d.ts +53 -0
- package/dist/edit/writer.js +261 -0
- package/dist/elements/container-element.d.ts +10 -1
- package/dist/elements/container-element.js +26 -8
- 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/index.d.ts +8 -0
- package/dist/elements/index.js +8 -0
- 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/padding-element.d.ts +3 -0
- package/dist/elements/layout/padding-element.js +12 -0
- 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/positioned-element.d.ts +10 -7
- package/dist/elements/layout/positioned-element.js +21 -9
- package/dist/elements/page-element.d.ts +11 -1
- package/dist/elements/page-element.js +41 -16
- package/dist/elements/pdf-element.d.ts +28 -0
- package/dist/elements/pdf-element.js +32 -0
- package/dist/elements/rectangle-element.d.ts +10 -1
- package/dist/elements/rectangle-element.js +27 -6
- package/dist/elements/row-element.d.ts +9 -1
- package/dist/elements/row-element.js +9 -1
- package/dist/elements/text-element.d.ts +30 -4
- package/dist/elements/text-element.js +40 -17
- 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/ir/display-list.d.ts +18 -1
- package/dist/layout/fragmentation.d.ts +12 -0
- package/dist/layout/fragmentation.js +45 -8
- package/dist/renderer/form-field-renderer.d.ts +20 -0
- package/dist/renderer/form-field-renderer.js +12 -0
- package/dist/renderer/keep-together-renderer.d.ts +10 -0
- package/dist/renderer/keep-together-renderer.js +12 -0
- package/dist/renderer/page-break-renderer.d.ts +14 -0
- package/dist/renderer/page-break-renderer.js +15 -0
- package/dist/renderer/page-renderer.js +8 -2
- package/dist/renderer/pdf-backend.d.ts +7 -0
- package/dist/renderer/pdf-backend.js +48 -6
- package/dist/renderer/pdf-document-class.js +3 -0
- package/dist/renderer/pdf-document-renderer.js +26 -7
- package/dist/renderer/pdf-renderer.js +25 -1
- package/dist/renderer/text-renderer.d.ts +1 -1
- package/dist/renderer/text-renderer.js +107 -59
- package/dist/text/advance.d.ts +34 -0
- package/dist/text/advance.js +42 -0
- package/dist/text/line-breaker.d.ts +9 -8
- package/dist/text/line-breaker.js +28 -21
- package/dist/text/line-metrics.d.ts +49 -0
- package/dist/text/line-metrics.js +57 -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 +13 -3
- package/dist/text/text-style.js +10 -3
- package/dist/utils/afm-parser.d.ts +34 -0
- package/dist/utils/afm-parser.js +67 -0
- package/dist/utils/dest-registry.d.ts +5 -2
- package/dist/utils/dest-registry.js +5 -5
- package/dist/utils/font-metrics.d.ts +14 -0
- package/dist/utils/outline.js +1 -2
- package/dist/utils/pdf-object-manager.d.ts +45 -1
- package/dist/utils/pdf-object-manager.js +193 -26
- package/dist/utils/struct-tree.js +2 -3
- package/dist/utils/ttf-parser.d.ts +53 -0
- package/dist/utils/ttf-parser.js +368 -0
- package/dist/validators/element-validator.js +7 -3
- package/package.json +9 -2
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// A PDF literal string escape (backslash first, then the string delimiters).
|
|
2
|
-
const esc = (s) => s.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
|
|
3
1
|
const num = (n) => (Number.isFinite(n) ? Number(n.toFixed(2)) : 0);
|
|
4
2
|
/**
|
|
5
3
|
* Collects named destinations - the jump targets an internal `Link({ to })` points at, declared with
|
|
@@ -19,13 +17,15 @@ export class DestRegistry {
|
|
|
19
17
|
return this.dests.size === 0;
|
|
20
18
|
}
|
|
21
19
|
/** The `/Dests << /Names [ ... ] >>` fragment for the catalog `/Names` dictionary (merged with any
|
|
22
|
-
* `/EmbeddedFiles`). Returns "" when empty. Names are sorted lexically - a name tree requires it.
|
|
23
|
-
|
|
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
24
|
if (this.dests.size === 0)
|
|
25
25
|
return "";
|
|
26
26
|
const entries = [...this.dests.keys()].sort().map((name) => {
|
|
27
27
|
const d = this.dests.get(name);
|
|
28
|
-
return
|
|
28
|
+
return `${om.pdfString(name)} [${d.pageObjNum} 0 R /XYZ null ${num(d.top)} null]`;
|
|
29
29
|
});
|
|
30
30
|
return `/Dests << /Names [ ${entries.join(" ")} ] >>`;
|
|
31
31
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { FontStyle } from "./pdf-object-manager.js";
|
|
2
|
+
import type { FontVerticals } from "../text/line-metrics.js";
|
|
3
|
+
import type { FontDecoration } from "../text/text-decoration.js";
|
|
2
4
|
/**
|
|
3
5
|
* Read-only font measurement - the slice of the object manager that the layout pass
|
|
4
6
|
* needs. Keeping layout (and, later, fragmentation) behind this interface means those
|
|
@@ -9,4 +11,16 @@ import type { FontStyle } from "./pdf-object-manager.js";
|
|
|
9
11
|
export interface FontMetrics {
|
|
10
12
|
getStringWidth(text: string, fontFamily: string, fontSize: number, fontStyle: FontStyle): number;
|
|
11
13
|
getCharWidth(char: string, fontSize: number, fullFontName?: string, fontName?: string, fontStyle?: FontStyle): number;
|
|
14
|
+
/** Ascent / descent / lineGap of a face, in em fractions - the vertical counterpart of the widths
|
|
15
|
+
* above. Drives the line box and the baseline (see `text/line-metrics.ts`). */
|
|
16
|
+
getFontVerticals(fontFamily: string, fontStyle: FontStyle): FontVerticals;
|
|
17
|
+
/** Underline / strikethrough geometry of a face, in em fractions. GLYPH metrics, deliberately kept
|
|
18
|
+
* apart from the LINE metrics above (see `text/text-decoration.ts`). */
|
|
19
|
+
getFontDecoration(fontFamily: string, fontStyle: FontStyle): FontDecoration;
|
|
20
|
+
/** Whether kerning is on for this document. `runAdvance` reads it so a measured advance matches the
|
|
21
|
+
* drawn one (the backend emits a `TJ` under the same flag). */
|
|
22
|
+
readonly kerningEnabled: boolean;
|
|
23
|
+
/** Per-adjacent-pair kerning of `text`, in em/1000 (negative tightens); length `codePoints - 1`,
|
|
24
|
+
* zero next to a space. Only meaningful when `kerningEnabled`. */
|
|
25
|
+
getKernPairs(text: string, fontFamily: string, fontStyle: FontStyle): number[];
|
|
12
26
|
}
|
package/dist/utils/outline.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// A PDF literal string escape (same rule as elsewhere): backslash first, then the string delimiters.
|
|
2
|
-
const esc = (s) => s.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
|
|
3
2
|
const num = (n) => Number(n.toFixed(2));
|
|
4
3
|
/**
|
|
5
4
|
* Builds the PDF document outline (`/Outlines`) - the viewer's bookmark panel. Entries are collected
|
|
@@ -52,7 +51,7 @@ export class OutlineBuilder {
|
|
|
52
51
|
// Emit each item dict, wired to its parent, siblings (prev/next) and first/last child.
|
|
53
52
|
const emit = (nodes, parentNum) => {
|
|
54
53
|
nodes.forEach((n, i) => {
|
|
55
|
-
const parts = [`/Title
|
|
54
|
+
const parts = [`/Title ${om.pdfString(n.title)}`, `/Parent ${parentNum} 0 R`];
|
|
56
55
|
if (i > 0)
|
|
57
56
|
parts.push(`/Prev ${objOf.get(nodes[i - 1])} 0 R`);
|
|
58
57
|
if (i < nodes.length - 1)
|
|
@@ -2,9 +2,12 @@ import type { OverflowPolicy } from "../layout/fragmentation.js";
|
|
|
2
2
|
import { TTFParser } from "./ttf-parser.js";
|
|
3
3
|
import type { SecurityHandler } from "../crypto/security-handler.js";
|
|
4
4
|
import type { Gradient } from "../ir/display-list.js";
|
|
5
|
+
import type { FontVerticals } from "../text/line-metrics.js";
|
|
6
|
+
import type { FontDecoration } from "../text/text-decoration.js";
|
|
5
7
|
import { StructTree } from "./struct-tree.js";
|
|
6
8
|
import { OutlineBuilder } from "./outline.js";
|
|
7
9
|
import { DestRegistry } from "./dest-registry.js";
|
|
10
|
+
import { AcroFormCollector } from "../forms/acroform.js";
|
|
8
11
|
import type { PDFConfig } from "../renderer/pdf-document-class.js";
|
|
9
12
|
import type { FontMetrics } from "./font-metrics.js";
|
|
10
13
|
interface FontIndexes {
|
|
@@ -33,6 +36,8 @@ export declare class PDFObjectManager implements FontMetrics {
|
|
|
33
36
|
pageFormat: [number, number];
|
|
34
37
|
private afmParsers;
|
|
35
38
|
private customFonts;
|
|
39
|
+
private verticalsCache;
|
|
40
|
+
private decorationCache;
|
|
36
41
|
private customFontEmit;
|
|
37
42
|
private attachments;
|
|
38
43
|
private xmpMetadata?;
|
|
@@ -43,6 +48,7 @@ export declare class PDFObjectManager implements FontMetrics {
|
|
|
43
48
|
private overflowPolicy;
|
|
44
49
|
private security?;
|
|
45
50
|
private encJobs;
|
|
51
|
+
private strJobs;
|
|
46
52
|
private encryptObjNum?;
|
|
47
53
|
private _struct;
|
|
48
54
|
get struct(): StructTree;
|
|
@@ -50,6 +56,8 @@ export declare class PDFObjectManager implements FontMetrics {
|
|
|
50
56
|
get outline(): OutlineBuilder;
|
|
51
57
|
private _dests;
|
|
52
58
|
get dests(): DestRegistry;
|
|
59
|
+
private _acroform;
|
|
60
|
+
get acroform(): AcroFormCollector;
|
|
53
61
|
constructor();
|
|
54
62
|
addObject(content: string): number;
|
|
55
63
|
replaceObject(objectNumber: number, content: string): void;
|
|
@@ -57,9 +65,23 @@ export declare class PDFObjectManager implements FontMetrics {
|
|
|
57
65
|
setOverflowPolicy(policy: OverflowPolicy): void;
|
|
58
66
|
getOverflowPolicy(): OverflowPolicy;
|
|
59
67
|
private encToken;
|
|
68
|
+
private strToken;
|
|
69
|
+
/**
|
|
70
|
+
* The single choke-point every literal string in an OBJECT goes through - a field's `/T` and `/V`, a
|
|
71
|
+
* bookmark `/Title`, a link `/URI`, an `/Alt` text, a destination name.
|
|
72
|
+
*
|
|
73
|
+
* Without a security handler this is the escaped literal we have always written, so output stays
|
|
74
|
+
* byte-identical. With one, the raw bytes are registered for the finalize pass and a placeholder takes
|
|
75
|
+
* their place: ISO 32000-1 7.6.2 requires ALL strings and streams to be encrypted, and the ones missed
|
|
76
|
+
* here were exactly the values a password is supposed to protect (ISSUE-7).
|
|
77
|
+
*
|
|
78
|
+
* A string INSIDE a content stream needs none of this - the stream is enciphered as a whole.
|
|
79
|
+
*/
|
|
80
|
+
pdfString(text: string): string;
|
|
60
81
|
private streamPayload;
|
|
61
82
|
private stream;
|
|
62
83
|
addContentStream(content: string): number;
|
|
84
|
+
addFormXObject(bbox: string, content: string, resources?: string): number;
|
|
63
85
|
changePDFConfig(config: PDFConfig): void;
|
|
64
86
|
getPDFConfig(): PDFConfig;
|
|
65
87
|
private fillConfigWithStandardValues;
|
|
@@ -119,10 +141,32 @@ export declare class PDFObjectManager implements FontMetrics {
|
|
|
119
141
|
private buildCIDFont;
|
|
120
142
|
private buildToUnicode;
|
|
121
143
|
private buildType0;
|
|
144
|
+
getFontVerticals(fontFamily: string, fontStyle: FontStyle): FontVerticals;
|
|
145
|
+
getFontDecoration(fontFamily: string, fontStyle: FontStyle): FontDecoration;
|
|
146
|
+
kerningEnabled: boolean;
|
|
147
|
+
setKerning(enabled: boolean): void;
|
|
148
|
+
/**
|
|
149
|
+
* The kerning adjustment for each adjacent code-point pair of `text`, in em/1000 (the sign the
|
|
150
|
+
* font declares: negative tightens). Length is `codePointCount - 1`. Zero at any pair next to a
|
|
151
|
+
* space - letters are never kerned against a space, which also keeps a per-word measure equal to a
|
|
152
|
+
* whole-line one (the space pairs contribute nothing either way).
|
|
153
|
+
*
|
|
154
|
+
* Standard-14 answers from the AFM `KPX` pairs. An embedded font has no kerning yet (its `kern` /
|
|
155
|
+
* `GPOS` tables are unparsed), so it returns zeros - never a wrong value, just no adjustment.
|
|
156
|
+
*/
|
|
157
|
+
getKernPairs(text: string, fontFamily: string, fontStyle: FontStyle): number[];
|
|
158
|
+
/**
|
|
159
|
+
* Where the glyphs of `text` put ink inside a horizontal band, as x-intervals in POINTS measured
|
|
160
|
+
* from the start of the run. This is what an underline steps around ("skip-ink").
|
|
161
|
+
*
|
|
162
|
+
* `bandTop` / `bandBottom` are in points relative to the baseline, y UP (so both negative under
|
|
163
|
+
* it). Only an EMBEDDED font can answer: the standard-14 outlines live in the viewer, not here.
|
|
164
|
+
* Callers must check `isCustomFont` first rather than treat an empty result as "no descenders".
|
|
165
|
+
*/
|
|
166
|
+
getInkSpansInBand(text: string, fontSize: number, fontFamily: string, fontStyle: FontStyle, bandTop: number, bandBottom: number, letterSpacing?: number): Array<[number, number]>;
|
|
122
167
|
getStringWidth(text: string, fontFamily: string, fontSize: number, fontStyle: FontStyle): number;
|
|
123
168
|
private getAVMParserByFont;
|
|
124
169
|
getCharWidth(char: string, fontSize: number, fullFontName?: string, fontName?: string, fontStyle?: FontStyle): number;
|
|
125
|
-
private getKerning;
|
|
126
170
|
getAllFontsRaw(): Map<string, FontIndexes>;
|
|
127
171
|
getAllImagesRaw(): Map<string, number>;
|
|
128
172
|
getRenderedObjects(): string;
|
|
@@ -4,13 +4,14 @@ import { md5, md5Hex } from "./md5.js";
|
|
|
4
4
|
import { zlibSync } from "fflate";
|
|
5
5
|
import { bytesFromLatin1, latin1FromBytes } from "./bytes.js";
|
|
6
6
|
import { AFMParser } from "./afm-parser.js";
|
|
7
|
-
import { TTFParser } from "./ttf-parser.js";
|
|
7
|
+
import { mergeSpans, TTFParser } from "./ttf-parser.js";
|
|
8
8
|
import { subsetTTF } from "./ttf-subsetter.js";
|
|
9
9
|
import { getArrayBuffer } from "./utf8-to-windows1252-encoder.js";
|
|
10
10
|
import { isEmojiCodePoint } from "../text/emoji-codepoints.js";
|
|
11
11
|
import { StructTree } from "./struct-tree.js";
|
|
12
12
|
import { OutlineBuilder } from "./outline.js";
|
|
13
13
|
import { DestRegistry } from "./dest-registry.js";
|
|
14
|
+
import { AcroFormCollector } from "../forms/acroform.js";
|
|
14
15
|
// Enums come from the leaf config module (never in a cycle); the config type is
|
|
15
16
|
// erased at runtime so it can come from the cyclic module safely.
|
|
16
17
|
import { ColorMode, Orientation } from "../renderer/pdf-config.js";
|
|
@@ -134,6 +135,9 @@ export class PDFObjectManager {
|
|
|
134
135
|
get dests() {
|
|
135
136
|
return this._dests;
|
|
136
137
|
}
|
|
138
|
+
get acroform() {
|
|
139
|
+
return this._acroform;
|
|
140
|
+
}
|
|
137
141
|
constructor(pageSize) {
|
|
138
142
|
this.objects = [];
|
|
139
143
|
this.objectPositions = [];
|
|
@@ -150,6 +154,10 @@ export class PDFObjectManager {
|
|
|
150
154
|
// every lookup, and the lookups happen once per character - the string allocation alone was a third
|
|
151
155
|
// of a custom-font render. `customFontEmit` below keeps the flat key: it is touched once per face.
|
|
152
156
|
this.customFonts = new Map();
|
|
157
|
+
// Memoised getFontVerticals answers, family -> style -> metrics. Cleared when a font registers.
|
|
158
|
+
this.verticalsCache = new Map();
|
|
159
|
+
// Same, for getFontDecoration.
|
|
160
|
+
this.decorationCache = new Map();
|
|
153
161
|
// Per-registered face: the reserved font-object numbers + the glyph ids actually used. The font
|
|
154
162
|
// program is subsetted and the objects filled in by finalizeCustomFonts(), after the render pass.
|
|
155
163
|
this.customFontEmit = new Map();
|
|
@@ -163,6 +171,7 @@ export class PDFObjectManager {
|
|
|
163
171
|
this.compress = false;
|
|
164
172
|
this.overflowPolicy = "error";
|
|
165
173
|
this.encJobs = [];
|
|
174
|
+
this.strJobs = [];
|
|
166
175
|
// Accessible (PDF/UA) tagging, off by default (byte-identical output); the API turns it on.
|
|
167
176
|
this._struct = new StructTree();
|
|
168
177
|
// Document outline (bookmarks). Empty unless a `Bookmark` was placed; then finalize() emits /Outlines.
|
|
@@ -170,6 +179,14 @@ export class PDFObjectManager {
|
|
|
170
179
|
// Named destinations (internal-link jump targets). Empty unless an `Anchor` was placed; then it emits
|
|
171
180
|
// a /Names /Dests tree.
|
|
172
181
|
this._dests = new DestRegistry();
|
|
182
|
+
// Interactive form fields (AcroForm). Empty unless a form field was placed; then finalize() emits the
|
|
183
|
+
// catalog /AcroForm dict and each field's widget lands in its page /Annots.
|
|
184
|
+
this._acroform = new AcroFormCollector();
|
|
185
|
+
// Whether to kern. Document-level: measuring (via `runAdvance`, which reads this off the metrics)
|
|
186
|
+
// and drawing (the backend emits a `TJ`) both consult it, so they can never disagree. OFF while the
|
|
187
|
+
// embedded-font path (kern/GPOS) is unbuilt - a standard-14-only kerning would set the 14 system
|
|
188
|
+
// fonts finer than a user's own font, which is worse than none. Flip to on when GPOS lands.
|
|
189
|
+
this.kerningEnabled = false;
|
|
173
190
|
if (pageSize)
|
|
174
191
|
this.pageFormat = pageFormats[pageSize];
|
|
175
192
|
this.fillConfigWithStandardValues();
|
|
@@ -204,6 +221,32 @@ export class PDFObjectManager {
|
|
|
204
221
|
encToken(id) {
|
|
205
222
|
return `JENC:${id}`;
|
|
206
223
|
}
|
|
224
|
+
// The same trick for a literal STRING. A separate marker because the two are swapped differently: a
|
|
225
|
+
// stream keeps its delimiters and only the payload changes, a string is replaced whole by a hex one.
|
|
226
|
+
strToken(id) {
|
|
227
|
+
return `\x00\x02JSTR:${id}\x02\x00`;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* The single choke-point every literal string in an OBJECT goes through - a field's `/T` and `/V`, a
|
|
231
|
+
* bookmark `/Title`, a link `/URI`, an `/Alt` text, a destination name.
|
|
232
|
+
*
|
|
233
|
+
* Without a security handler this is the escaped literal we have always written, so output stays
|
|
234
|
+
* byte-identical. With one, the raw bytes are registered for the finalize pass and a placeholder takes
|
|
235
|
+
* their place: ISO 32000-1 7.6.2 requires ALL strings and streams to be encrypted, and the ones missed
|
|
236
|
+
* here were exactly the values a password is supposed to protect (ISSUE-7).
|
|
237
|
+
*
|
|
238
|
+
* A string INSIDE a content stream needs none of this - the stream is enciphered as a whole.
|
|
239
|
+
*/
|
|
240
|
+
pdfString(text) {
|
|
241
|
+
if (!this.security) {
|
|
242
|
+
return `(${text.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)")})`;
|
|
243
|
+
}
|
|
244
|
+
// Encrypt the bytes the unencrypted path would have written, so a viewer decrypts to the same value.
|
|
245
|
+
// No escaping is applied: the result is emitted as a HEX string, which has no escapes to get wrong.
|
|
246
|
+
const bytes = new Uint8Array(getArrayBuffer(text));
|
|
247
|
+
const id = this.strJobs.push(bytes) - 1;
|
|
248
|
+
return this.strToken(id);
|
|
249
|
+
}
|
|
207
250
|
// The single encryption choke-point every stream emitter routes through (stream(), registerImage). Without
|
|
208
251
|
// a security handler: the bytes as a latin1 string + their length. With one: register the raw bytes for the
|
|
209
252
|
// finalize pass and return a placeholder + the pre-computable encrypted length (16-byte IV + PKCS#7 padding).
|
|
@@ -237,6 +280,14 @@ export class PDFObjectManager {
|
|
|
237
280
|
addContentStream(content) {
|
|
238
281
|
return this.addObject(this.stream("", new Uint8Array(getArrayBuffer(content))));
|
|
239
282
|
}
|
|
283
|
+
// A Form XObject: a self-contained content stream with its own /BBox (+ optional /Resources). Used for
|
|
284
|
+
// form-field appearance streams (/AP). Routes through the same choke-point as page content, so
|
|
285
|
+
// compression and encryption apply. Returns the object number.
|
|
286
|
+
addFormXObject(bbox, content, resources) {
|
|
287
|
+
const dict = `/Type /XObject /Subtype /Form /BBox ${bbox}` +
|
|
288
|
+
(resources ? ` /Resources << ${resources} >>` : "");
|
|
289
|
+
return this.addObject(this.stream(dict, new Uint8Array(getArrayBuffer(content))));
|
|
290
|
+
}
|
|
240
291
|
changePDFConfig(config) {
|
|
241
292
|
this.pdfConfig = { ...this.pdfConfig, ...config };
|
|
242
293
|
}
|
|
@@ -318,9 +369,8 @@ endstream`;
|
|
|
318
369
|
attachFile(name, data, opts = {}) {
|
|
319
370
|
const subtype = (opts.mimeType ?? "application/octet-stream").replace(/\//g, "#2F");
|
|
320
371
|
const embedded = this.addObject(this.stream(`/Type /EmbeddedFile /Subtype /${subtype} /Params << /Size ${data.length} >>`, data));
|
|
321
|
-
const
|
|
322
|
-
const
|
|
323
|
-
const filespec = this.addObject(`<< /Type /Filespec /F (${escaped(name)}) /UF (${escaped(name)}) ` +
|
|
372
|
+
const desc = opts.description ? ` /Desc ${this.pdfString(opts.description)}` : "";
|
|
373
|
+
const filespec = this.addObject(`<< /Type /Filespec /F ${this.pdfString(name)} /UF ${this.pdfString(name)} ` +
|
|
324
374
|
`/AFRelationship /${opts.relationship ?? "Unspecified"}${desc} ` +
|
|
325
375
|
`/EF << /F ${embedded} 0 R /UF ${embedded} 0 R >> >>`);
|
|
326
376
|
this.attachments.push({ name, filespec });
|
|
@@ -341,8 +391,8 @@ endstream`;
|
|
|
341
391
|
setOutputIntent(icc, opts = {}) {
|
|
342
392
|
const profile = this.addObject(this.stream("/N 3", icc));
|
|
343
393
|
this.outputIntent = this.addObject(`<< /Type /OutputIntent /S /GTS_PDFA1 ` +
|
|
344
|
-
`/OutputConditionIdentifier
|
|
345
|
-
`/Info
|
|
394
|
+
`/OutputConditionIdentifier ${this.pdfString(opts.identifier ?? "sRGB")} ` +
|
|
395
|
+
`/Info ${this.pdfString(opts.info ?? "sRGB IEC61966-2.1")} /DestOutputProfile ${profile} 0 R >>`);
|
|
346
396
|
}
|
|
347
397
|
getOutputIntent() {
|
|
348
398
|
return this.outputIntent;
|
|
@@ -390,6 +440,18 @@ endstream`;
|
|
|
390
440
|
if (idx >= 0)
|
|
391
441
|
this.objects[idx] = this.objects[idx].replace(token, () => ciphertext);
|
|
392
442
|
}
|
|
443
|
+
// Strings: the same idea, but a string is replaced WHOLE - `(text)` becomes `<ciphertext>`. Hex,
|
|
444
|
+
// because the ciphertext is binary and a hex string has no escape rules to get wrong.
|
|
445
|
+
for (let id = 0; id < this.strJobs.length; id++) {
|
|
446
|
+
const cipher = await this.security.encrypt(this.strJobs[id]);
|
|
447
|
+
const hex = Array.from(cipher, (b) => b.toString(16).padStart(2, "0").toUpperCase()).join("");
|
|
448
|
+
const token = this.strToken(id);
|
|
449
|
+
const idx = this.objects.findIndex((o) => o.includes(token));
|
|
450
|
+
if (idx >= 0)
|
|
451
|
+
this.objects[idx] = this.objects[idx].replace(token, () => `<${hex}>`);
|
|
452
|
+
}
|
|
453
|
+
// Added LAST on purpose: the /Encrypt dictionary's own strings must stay in the clear (7.6.2), and
|
|
454
|
+
// they would otherwise be swept up by the pass above.
|
|
393
455
|
this.encryptObjNum = this.addObject(`<< ${this.security.encryptDict()} >>`);
|
|
394
456
|
}
|
|
395
457
|
// Registers (or reuses) a transparency graphics state and returns its resource name
|
|
@@ -462,6 +524,8 @@ endstream`;
|
|
|
462
524
|
if (this.fonts.hasFont(fontName, fontStyle)) {
|
|
463
525
|
return this.fonts.getFont(fontName, fontStyle); // Already exists? Return it!
|
|
464
526
|
}
|
|
527
|
+
this.verticalsCache.delete(fontName);
|
|
528
|
+
this.decorationCache.delete(fontName);
|
|
465
529
|
const data = STANDARD_AFM[fullName];
|
|
466
530
|
if (data !== undefined) {
|
|
467
531
|
this.afmParsers.push({
|
|
@@ -495,6 +559,8 @@ endstream`;
|
|
|
495
559
|
if (byStyle.has(style))
|
|
496
560
|
return;
|
|
497
561
|
byStyle.set(style, new TTFParser(data));
|
|
562
|
+
this.verticalsCache.delete(name); // this family now answers from the .ttf, not from an AFM
|
|
563
|
+
this.decorationCache.delete(name);
|
|
498
564
|
// Emission (the PDF font objects + page resource) is DEFERRED to first use via ensureEmitted(),
|
|
499
565
|
// so a registered-but-never-rendered face - e.g. a bundled family the document doesn't touch -
|
|
500
566
|
// costs nothing in the output. The metrics above are still available for layout immediately.
|
|
@@ -660,7 +726,7 @@ endstream`;
|
|
|
660
726
|
.map((g) => `${g} [${all[g] ?? 1000}]`)
|
|
661
727
|
.join(" ");
|
|
662
728
|
return (`<< /Type /Font /Subtype /CIDFontType2 /BaseFont /${pdfName(name)} ` +
|
|
663
|
-
`/CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> ` +
|
|
729
|
+
`/CIDSystemInfo << /Registry ${this.pdfString("Adobe")} /Ordering ${this.pdfString("Identity")} /Supplement 0 >> ` +
|
|
664
730
|
`/FontDescriptor ${descriptor} 0 R /CIDToGIDMap /Identity ` +
|
|
665
731
|
`/DW 1000 /W [${w}] >>`);
|
|
666
732
|
}
|
|
@@ -690,24 +756,132 @@ endstream`;
|
|
|
690
756
|
return (`<< /Type /Font /Subtype /Type0 /BaseFont /${pdfName(name)} /Encoding /Identity-H ` +
|
|
691
757
|
`/DescendantFonts [${cidFont} 0 R] /ToUnicode ${toUnicode} 0 R >>`);
|
|
692
758
|
}
|
|
759
|
+
// The vertical metrics of a face, in em fractions - what seats the baseline and sizes the line
|
|
760
|
+
// box (see text/line-metrics.ts). An embedded font answers from its own `hhea`; a standard-14
|
|
761
|
+
// font from its AFM header. Nothing here is guessed: both numbers come out of the font data.
|
|
762
|
+
//
|
|
763
|
+
// Asked once per LINE (not per character), but a long document has a lot of lines and the
|
|
764
|
+
// standard-14 lookup below is a linear scan, so the answer is memoised per face. Registering a
|
|
765
|
+
// font clears the cache - a name can go from standard-14 to embedded.
|
|
766
|
+
getFontVerticals(fontFamily, fontStyle) {
|
|
767
|
+
let byStyle = this.verticalsCache.get(fontFamily);
|
|
768
|
+
const hit = byStyle?.get(fontStyle);
|
|
769
|
+
if (hit)
|
|
770
|
+
return hit;
|
|
771
|
+
const ttf = this.getCustomFont(fontFamily, fontStyle);
|
|
772
|
+
const verticals = ttf
|
|
773
|
+
? {
|
|
774
|
+
ascent: ttf.ascent / 1000,
|
|
775
|
+
descent: -ttf.descent / 1000, // hhea writes the descent negative
|
|
776
|
+
lineGap: ttf.lineGap / 1000,
|
|
777
|
+
}
|
|
778
|
+
: this.getAVMParserByFont(undefined, fontFamily, fontStyle).parser.verticals();
|
|
779
|
+
if (!byStyle) {
|
|
780
|
+
byStyle = new Map();
|
|
781
|
+
this.verticalsCache.set(fontFamily, byStyle);
|
|
782
|
+
}
|
|
783
|
+
byStyle.set(fontStyle, verticals);
|
|
784
|
+
return verticals;
|
|
785
|
+
}
|
|
786
|
+
// Underline / strikethrough geometry of a face, in em fractions. Same shape as getFontVerticals:
|
|
787
|
+
// an embedded font answers from its `post`/`OS/2` tables, a standard-14 one from its AFM header,
|
|
788
|
+
// and the answer is memoised per face because it is asked once per decorated run.
|
|
789
|
+
getFontDecoration(fontFamily, fontStyle) {
|
|
790
|
+
let byStyle = this.decorationCache.get(fontFamily);
|
|
791
|
+
const hit = byStyle?.get(fontStyle);
|
|
792
|
+
if (hit)
|
|
793
|
+
return hit;
|
|
794
|
+
const ttf = this.getCustomFont(fontFamily, fontStyle);
|
|
795
|
+
const decoration = ttf
|
|
796
|
+
? ttf.decoration()
|
|
797
|
+
: this.getAVMParserByFont(undefined, fontFamily, fontStyle).parser.decoration();
|
|
798
|
+
if (!byStyle) {
|
|
799
|
+
byStyle = new Map();
|
|
800
|
+
this.decorationCache.set(fontFamily, byStyle);
|
|
801
|
+
}
|
|
802
|
+
byStyle.set(fontStyle, decoration);
|
|
803
|
+
return decoration;
|
|
804
|
+
}
|
|
805
|
+
setKerning(enabled) {
|
|
806
|
+
this.kerningEnabled = enabled;
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* The kerning adjustment for each adjacent code-point pair of `text`, in em/1000 (the sign the
|
|
810
|
+
* font declares: negative tightens). Length is `codePointCount - 1`. Zero at any pair next to a
|
|
811
|
+
* space - letters are never kerned against a space, which also keeps a per-word measure equal to a
|
|
812
|
+
* whole-line one (the space pairs contribute nothing either way).
|
|
813
|
+
*
|
|
814
|
+
* Standard-14 answers from the AFM `KPX` pairs. An embedded font has no kerning yet (its `kern` /
|
|
815
|
+
* `GPOS` tables are unparsed), so it returns zeros - never a wrong value, just no adjustment.
|
|
816
|
+
*/
|
|
817
|
+
getKernPairs(text, fontFamily, fontStyle) {
|
|
818
|
+
const chars = [...text];
|
|
819
|
+
if (chars.length < 2)
|
|
820
|
+
return [];
|
|
821
|
+
const out = [];
|
|
822
|
+
// An embedded font kerns by GLYPH id (kern table / GPOS); a standard-14 one by character (AFM).
|
|
823
|
+
const ttf = this.getCustomFont(fontFamily, fontStyle);
|
|
824
|
+
const parser = ttf
|
|
825
|
+
? undefined
|
|
826
|
+
: this.getAVMParserByFont(undefined, fontFamily, fontStyle).parser;
|
|
827
|
+
for (let i = 0; i < chars.length - 1; i++) {
|
|
828
|
+
const a = chars[i];
|
|
829
|
+
const b = chars[i + 1];
|
|
830
|
+
if (a === " " || b === " ") {
|
|
831
|
+
out.push(0);
|
|
832
|
+
}
|
|
833
|
+
else if (ttf) {
|
|
834
|
+
out.push(ttf.getKerning(ttf.getGlyphIndex(a.codePointAt(0)), ttf.getGlyphIndex(b.codePointAt(0))));
|
|
835
|
+
}
|
|
836
|
+
else {
|
|
837
|
+
out.push(parser.getKerning(a, b));
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
return out;
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Where the glyphs of `text` put ink inside a horizontal band, as x-intervals in POINTS measured
|
|
844
|
+
* from the start of the run. This is what an underline steps around ("skip-ink").
|
|
845
|
+
*
|
|
846
|
+
* `bandTop` / `bandBottom` are in points relative to the baseline, y UP (so both negative under
|
|
847
|
+
* it). Only an EMBEDDED font can answer: the standard-14 outlines live in the viewer, not here.
|
|
848
|
+
* Callers must check `isCustomFont` first rather than treat an empty result as "no descenders".
|
|
849
|
+
*/
|
|
850
|
+
getInkSpansInBand(text, fontSize, fontFamily, fontStyle, bandTop, bandBottom, letterSpacing = 0) {
|
|
851
|
+
const ttf = this.getCustomFont(fontFamily, fontStyle);
|
|
852
|
+
if (!ttf)
|
|
853
|
+
return [];
|
|
854
|
+
const scale = fontSize / ttf.unitsPerEm;
|
|
855
|
+
const spans = [];
|
|
856
|
+
let pen = 0;
|
|
857
|
+
for (const ch of text) {
|
|
858
|
+
const codePoint = ch.codePointAt(0);
|
|
859
|
+
if (codePoint !== undefined) {
|
|
860
|
+
for (const [x0, x1] of ttf.inkSpansInBand(codePoint, bandTop / scale, bandBottom / scale)) {
|
|
861
|
+
spans.push([pen + x0 * scale, pen + x1 * scale]);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
// The pen advances by the glyph AND its letter-spacing, so the ink of the next glyph lands
|
|
865
|
+
// where `Tc` actually draws it - otherwise the skipped gaps drift under the wrong letters.
|
|
866
|
+
pen += this.getCharWidth(ch, fontSize, undefined, fontFamily, fontStyle) + letterSpacing;
|
|
867
|
+
}
|
|
868
|
+
return mergeSpans(spans);
|
|
869
|
+
}
|
|
693
870
|
// Returns the current width of a text, included kernings
|
|
694
871
|
getStringWidth(text, fontFamily, fontSize, fontStyle) {
|
|
695
872
|
let width = 0;
|
|
696
873
|
// Iterate code points, not UTF-16 units: an astral char (emoji, CJK-ext) is a surrogate pair,
|
|
697
874
|
// and indexing by unit would measure each half as its own (zero-width) "char". The spread splits
|
|
698
875
|
// on code points; for BMP text this is one unit each, so the result is unchanged.
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
const kerning = this.getKerning(char, nextChar, undefined, fontFamily, fontStyle);
|
|
709
|
-
width += kerning * fontSize; // Kerning must be scaled with the font size
|
|
710
|
-
}
|
|
876
|
+
//
|
|
877
|
+
// NO KERNING. We write a run as a single `Tj`, and a viewer advances that by the font's plain
|
|
878
|
+
// widths - PDF never kerns on its own; a producer has to say so with a `TJ` array. Folding the
|
|
879
|
+
// AFM's kerning into the MEASUREMENT while the OUTPUT ignores it made every kerned string draw
|
|
880
|
+
// wider than its box: "AVATAR Wave" at 40pt by 19pt, "Total" at 11pt by 5.7%. Measured must equal
|
|
881
|
+
// drawn. The AFM kerning pairs stay parsed (`getKerning`) for the day we emit `TJ` - see the
|
|
882
|
+
// "real kerning" item in todo.md, which must cover embedded fonts too (`kern`/`GPOS`).
|
|
883
|
+
for (const char of text) {
|
|
884
|
+
width += this.getCharWidth(char, fontSize, undefined, fontFamily, fontStyle);
|
|
711
885
|
}
|
|
712
886
|
return width;
|
|
713
887
|
}
|
|
@@ -767,13 +941,6 @@ endstream`;
|
|
|
767
941
|
return (advanceWidth / 1000) * fontSize;
|
|
768
942
|
}
|
|
769
943
|
// Method to get the kerning, if available, between two signs
|
|
770
|
-
getKerning(char, nextChar, fullFontName, fontName, fontStyle) {
|
|
771
|
-
// TrueType kerning (the kern/GPOS tables) is not wired up yet - no kerning for custom fonts.
|
|
772
|
-
if (this.getCustomFont(fullFontName ?? fontName, fontStyle))
|
|
773
|
-
return 0;
|
|
774
|
-
const currentParser = this.getAVMParserByFont(fullFontName, fontName, fontStyle);
|
|
775
|
-
return currentParser.parser.getKerning(char, nextChar) / 1000;
|
|
776
|
-
}
|
|
777
944
|
// Returns all fonts
|
|
778
945
|
getAllFontsRaw() {
|
|
779
946
|
return this.fonts.getAllFonts();
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// Escapes a PDF literal string ( ... ) - the language tag is safe ASCII, but /Alt can carry arbitrary text.
|
|
2
|
-
const esc = (s) => s.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
|
|
3
2
|
// A structure role reaches the PDF as a raw name (/H1, /S /Table). Roles come from a controlled set,
|
|
4
3
|
// but restrict them to a safe PDF-name token anyway (defence-in-depth) so a stray "/", whitespace, or
|
|
5
4
|
// operator can never corrupt the content stream / object dictionary. Empty after stripping -> /Span.
|
|
@@ -90,7 +89,7 @@ export class StructTree {
|
|
|
90
89
|
...childrenOf(e.key).map((c) => `${objNum.get(c.key)} 0 R`),
|
|
91
90
|
...e.mc.map((m) => `<< /Type /MCR /Pg ${pg(m.structParents)} 0 R /MCID ${m.mcid} >>`),
|
|
92
91
|
].join(" ");
|
|
93
|
-
const alt = e.alt ? ` /Alt
|
|
92
|
+
const alt = e.alt ? ` /Alt ${om.pdfString(e.alt)}` : "";
|
|
94
93
|
// PDF/UA 7.5: a header cell needs a Scope so a reader can associate data cells with it. Our headers
|
|
95
94
|
// are a header ROW (the Table `header` option), so each TH is the head of its Column.
|
|
96
95
|
const attr = e.role === "TH" ? " /A << /O /Table /Scope /Column >>" : "";
|
|
@@ -125,6 +124,6 @@ export class StructTree {
|
|
|
125
124
|
om.replaceObject(parentTreeNum, `<< /Nums [ ${nums.join(" ")} ] >>`);
|
|
126
125
|
om.replaceObject(rootNum, `<< /Type /StructTreeRoot /K ${docNum} 0 R /ParentTree ${parentTreeNum} 0 R ` +
|
|
127
126
|
`/ParentTreeNextKey ${this.structParentsCounter} >>`);
|
|
128
|
-
return `/MarkInfo << /Marked true >> /StructTreeRoot ${rootNum} 0 R /Lang
|
|
127
|
+
return `/MarkInfo << /Marked true >> /StructTreeRoot ${rootNum} 0 R /Lang ${om.pdfString(this.lang)}`;
|
|
129
128
|
}
|
|
130
129
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FontDecoration } from "../text/text-decoration.js";
|
|
1
2
|
export type GlyphPathCommand = {
|
|
2
3
|
type: "M";
|
|
3
4
|
x: number;
|
|
@@ -47,11 +48,19 @@ export interface ColorGlyphLayer {
|
|
|
47
48
|
paint: Paint;
|
|
48
49
|
transform?: Affine;
|
|
49
50
|
}
|
|
51
|
+
/** Sorts x-intervals and unions the overlapping ones. */
|
|
52
|
+
export declare function mergeSpans(spans: Array<[number, number]>): Array<[number, number]>;
|
|
50
53
|
export declare class TTFParser {
|
|
51
54
|
private data;
|
|
52
55
|
unitsPerEm: number;
|
|
53
56
|
ascent: number;
|
|
54
57
|
descent: number;
|
|
58
|
+
lineGap: number;
|
|
59
|
+
private underlinePositionRaw;
|
|
60
|
+
private underlineThicknessRaw;
|
|
61
|
+
private xHeightRaw;
|
|
62
|
+
private capHeightRaw;
|
|
63
|
+
private cachedDecoration?;
|
|
55
64
|
bbox: [number, number, number, number];
|
|
56
65
|
private numGlyphs;
|
|
57
66
|
private numHMetrics;
|
|
@@ -65,6 +74,9 @@ export declare class TTFParser {
|
|
|
65
74
|
private palette;
|
|
66
75
|
private colrBaseV1;
|
|
67
76
|
private colrLayerListOffset;
|
|
77
|
+
private kernPairs;
|
|
78
|
+
private hasKerning;
|
|
79
|
+
private gposPairPos;
|
|
68
80
|
constructor(data: Uint8Array);
|
|
69
81
|
getData(): Uint8Array;
|
|
70
82
|
glyphCount(): number;
|
|
@@ -75,6 +87,47 @@ export declare class TTFParser {
|
|
|
75
87
|
getAdvanceWidth(glyphIndex: number): number;
|
|
76
88
|
getCharWidth(char: string, fontSize: number): number;
|
|
77
89
|
getStringWidth(text: string, fontSize: number): number;
|
|
90
|
+
private readDecorationMetrics;
|
|
91
|
+
/**
|
|
92
|
+
* Underline / strikethrough geometry, as fractions of the em.
|
|
93
|
+
*
|
|
94
|
+
* A font without `post` (rare) gets the classic 10% below the baseline at 5% thickness - the same
|
|
95
|
+
* numbers all 14 standard AFMs happen to declare, so it is a measured convention rather than an
|
|
96
|
+
* invention. A font whose `OS/2` predates version 2 has no x- or cap-height, so we MEASURE them off
|
|
97
|
+
* the outlines of `x` and `H` instead of guessing.
|
|
98
|
+
*/
|
|
99
|
+
decoration(): FontDecoration;
|
|
100
|
+
/** The highest ON-CURVE point of a code point's outline, in font units; 0 for a missing glyph.
|
|
101
|
+
* A quadratic's control point may sit above the curve, so it is not a bound - and `x`/`H` have
|
|
102
|
+
* flat tops in any face where this fallback matters. */
|
|
103
|
+
private outlineTop;
|
|
104
|
+
/**
|
|
105
|
+
* Where a glyph's INK crosses a horizontal band, as x-intervals in font units (pen origin at 0).
|
|
106
|
+
* This is what lets an underline step around a descender ("skip-ink"), the way a browser does.
|
|
107
|
+
*
|
|
108
|
+
* Scanline fill: sample a few horizontal lines across the band, find every place the outline
|
|
109
|
+
* crosses them, and pair the crossings up into filled spans. A handful of scanlines is plenty -
|
|
110
|
+
* the band is a few percent of the em tall, and a descender stem does not wander inside it.
|
|
111
|
+
*
|
|
112
|
+
* `yTop` / `yBottom` are in font units, y UP from the baseline (so both are negative under it).
|
|
113
|
+
* Returns [] for a glyph with no outline (a space, an unmapped code point, a composite we cannot
|
|
114
|
+
* decompose) - the caller then leaves the line unbroken there, which is the safe direction.
|
|
115
|
+
*/
|
|
116
|
+
inkSpansInBand(codePoint: number, yTop: number, yBottom: number): Array<[number, number]>;
|
|
117
|
+
/** True if the font declares any horizontal kerning (kern table or GPOS). */
|
|
118
|
+
kerns(): boolean;
|
|
119
|
+
private kernCache;
|
|
120
|
+
/** Kerning between two glyphs, in em/1000 (the standard-14 unit); 0 if the pair is not kerned.
|
|
121
|
+
* Negative tightens, matching the AFM sign. GPOS wins over the legacy `kern` table when both
|
|
122
|
+
* exist (it is what a modern shaper uses, and can carry more pairs). */
|
|
123
|
+
getKerning(leftGid: number, rightGid: number): number;
|
|
124
|
+
private readKern;
|
|
125
|
+
private readGpos;
|
|
126
|
+
private gposKern;
|
|
127
|
+
private static valueRecordSize;
|
|
128
|
+
private xAdvance;
|
|
129
|
+
private coverageIndex;
|
|
130
|
+
private classOf;
|
|
78
131
|
private table;
|
|
79
132
|
private readTableDirectory;
|
|
80
133
|
private readHmtx;
|