reamkit 1.27.0 → 1.29.0
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 +6 -5
- package/dist/esm/core/converter/ream.d.ts +8 -9
- package/dist/esm/core/converter/ream.js +0 -1
- package/dist/esm/core/document-model/types.d.ts +5 -3
- package/dist/esm/core/font/index.d.ts +1 -0
- package/dist/esm/core/font/ligatures.d.ts +17 -0
- package/dist/esm/core/font/ligatures.js +49 -0
- package/dist/esm/core/font/ttf-parser.d.ts +2 -1
- package/dist/esm/core/font/ttf-parser.js +11 -2
- package/dist/esm/core/fonts/remote-fonts.d.ts +1 -1
- package/dist/esm/core/fonts/remote-fonts.js +47 -2
- package/dist/esm/core/fonts/scripts.js +10 -5
- package/dist/esm/excel/header-footer.js +53 -8
- package/dist/esm/layout/styled-layout.js +53 -3
- package/dist/esm/pdf/cid-font.js +35 -8
- package/dist/esm/pdf-reader/annot-draw.js +114 -1
- package/dist/esm/pdf-reader/annots.d.ts +0 -14
- package/dist/esm/pdf-reader/annots.js +13 -1
- package/dist/esm/pdf-reader/ccitt.d.ts +2 -3
- package/dist/esm/pdf-reader/ccitt.js +32 -4
- package/dist/esm/pdf-reader/cff-outline.d.ts +36 -0
- package/dist/esm/pdf-reader/cff-outline.js +1122 -0
- package/dist/esm/pdf-reader/cmap.js +16 -6
- package/dist/esm/pdf-reader/content.d.ts +91 -0
- package/dist/esm/pdf-reader/content.js +156 -60
- package/dist/esm/pdf-reader/dingbats.d.ts +11 -0
- package/dist/esm/pdf-reader/dingbats.js +1033 -0
- package/dist/esm/pdf-reader/display.js +41 -1
- package/dist/esm/pdf-reader/document.d.ts +8 -0
- package/dist/esm/pdf-reader/document.js +26 -9
- package/dist/esm/pdf-reader/embedded-fonts.d.ts +11 -0
- package/dist/esm/pdf-reader/embedded-fonts.js +14 -1
- package/dist/esm/pdf-reader/encodings.d.ts +25 -0
- package/dist/esm/pdf-reader/encodings.js +110 -0
- package/dist/esm/pdf-reader/flow-build.d.ts +15 -4
- package/dist/esm/pdf-reader/flow-build.js +24 -11
- package/dist/esm/pdf-reader/font.js +329 -8
- package/dist/esm/pdf-reader/glyf-outline.d.ts +43 -0
- package/dist/esm/pdf-reader/glyf-outline.js +351 -0
- package/dist/esm/pdf-reader/glyph-names.js +20 -0
- package/dist/esm/pdf-reader/icc.d.ts +10 -0
- package/dist/esm/pdf-reader/icc.js +210 -0
- package/dist/esm/pdf-reader/image-decode.d.ts +4 -3
- package/dist/esm/pdf-reader/image-decode.js +85 -64
- package/dist/esm/pdf-reader/images.d.ts +6 -0
- package/dist/esm/pdf-reader/images.js +50 -7
- package/dist/esm/pdf-reader/jbig2.js +104 -19
- package/dist/esm/pdf-reader/layout.js +1531 -55
- package/dist/esm/pdf-reader/math-rows.d.ts +23 -0
- package/dist/esm/pdf-reader/math-rows.js +198 -0
- package/dist/esm/pdf-reader/predefined-cmap.d.ts +21 -0
- package/dist/esm/pdf-reader/predefined-cmap.js +102 -0
- package/dist/esm/pdf-reader/reader.d.ts +6 -9
- package/dist/esm/pdf-reader/reader.js +34 -21
- package/dist/esm/pdf-reader/shading.d.ts +81 -2
- package/dist/esm/pdf-reader/shading.js +191 -25
- package/dist/esm/pdf-reader/stream-filters.d.ts +6 -0
- package/dist/esm/pdf-reader/stream-filters.js +67 -0
- package/dist/esm/pdf-reader/tagged.js +1 -1
- package/dist/esm/pdf-reader/text-rules.js +83 -10
- package/dist/esm/pdf-reader/text.js +82 -3
- package/dist/esm/pdf-reader/type1-outline.d.ts +21 -0
- package/dist/esm/pdf-reader/type1-outline.js +576 -0
- package/dist/esm/pdf-reader/vector.d.ts +2 -0
- package/dist/esm/pdf-reader/vector.js +44 -4
- package/dist/esm/word/document-parser.js +4 -1
- package/dist/esm/word/docx-writer.js +28 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -203,16 +203,17 @@ stencil image masks, constant alpha, and the Type 3 glyphs that are drawings
|
|
|
203
203
|
rather than letters. It honours the layers a file turns off (§8.11 optional
|
|
204
204
|
content) and the box it says to show (`/CropBox`), and it decides for itself
|
|
205
205
|
whether a file is a document to re-flow or a page to keep — a paper is mostly
|
|
206
|
-
lines, a form is mostly marks —
|
|
206
|
+
lines, a form is mostly marks — and there is nothing to configure: a caller
|
|
207
|
+
cannot know which of the two it was handed. It reads modern compressed files (cross-reference
|
|
207
208
|
+ object streams) and encrypted ones (RC4 / AES — the user password is passed to
|
|
208
209
|
`Ream.parse(bytes, { password })`, defaulting to the permissions-only case); a
|
|
209
210
|
filter it does not carry can be handed to it through
|
|
210
211
|
`Ream.parse(bytes, { filters })`.
|
|
211
212
|
|
|
212
|
-
A form or a drawing is not a reflowable document, so
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
A form or a drawing is not a reflowable document, so the reader keeps it as a
|
|
214
|
+
page instead: every line stands where its glyphs stand, beside the artwork, the
|
|
215
|
+
way up the page is shown. Which of the two readings a file gets it decides
|
|
216
|
+
itself and records in the losses.
|
|
216
217
|
|
|
217
218
|
**Reads PowerPoint, too.** `Ream.parse` accepts a `.pptx` and turns each slide
|
|
218
219
|
into a page at the deck size — text boxes (with run formatting, alignment,
|
|
@@ -21,15 +21,6 @@ export interface ReamParseOptions {
|
|
|
21
21
|
* encryption (EP14); an encrypted OOXML package always names a password.
|
|
22
22
|
*/
|
|
23
23
|
readonly password?: string;
|
|
24
|
-
/**
|
|
25
|
-
* PDF only: what to read out of the page. `'flow'` (the default) reconstructs
|
|
26
|
-
* a re-flowable document — paragraphs and tables in reading order, from the
|
|
27
|
-
* structure tree where the file has one. `'positional'` keeps the page as a
|
|
28
|
-
* page: every line stands where its glyphs stand, beside the rules and fills
|
|
29
|
-
* the page draws. A form or a drawing needs the second; a report to be edited
|
|
30
|
-
* or converted to markdown needs the first.
|
|
31
|
-
*/
|
|
32
|
-
readonly pdfLayout?: 'flow' | 'positional';
|
|
33
24
|
/**
|
|
34
25
|
* PDF only: decoders for `/Filter` names the reader does not implement
|
|
35
26
|
* (§7.4). A filter it cannot undo leaves that stream unread — and when the
|
|
@@ -38,6 +29,14 @@ export interface ReamParseOptions {
|
|
|
38
29
|
* carrying one for every filter anyone might write:
|
|
39
30
|
*
|
|
40
31
|
* ```ts
|
|
32
|
+
* // browser: any wasm/JS decoder you ship — brotli-dec-wasm is ~200 KB
|
|
33
|
+
* import brotliPromise from 'brotli-dec-wasm';
|
|
34
|
+
* const brotli = await brotliPromise;
|
|
35
|
+
* Ream.parse(pdf, { filters: { BrotliDecode: (b) => brotli.decompress(b) } });
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* ```ts
|
|
39
|
+
* // node, where the runtime already carries one
|
|
41
40
|
* import { brotliDecompressSync } from 'node:zlib';
|
|
42
41
|
* Ream.parse(pdf, { filters: { BrotliDecode: (b) => brotliDecompressSync(b) } });
|
|
43
42
|
* ```
|
|
@@ -88,7 +88,6 @@ var Ream = class Ream {
|
|
|
88
88
|
if (!reader) throw new Error(`Unrecognized document format (readers: ${readers.map((r) => r.id).join(", ")})`);
|
|
89
89
|
const { doc, losses } = reader.read(source, {
|
|
90
90
|
password: options.password,
|
|
91
|
-
...options.pdfLayout ? { pdfLayout: options.pdfLayout } : {},
|
|
92
91
|
...options.filters ? { filters: options.filters } : {}
|
|
93
92
|
});
|
|
94
93
|
const sheet = doc.kind === "sheet" ? doc : void 0;
|
|
@@ -1571,10 +1571,12 @@ export interface SectionProperties {
|
|
|
1571
1571
|
};
|
|
1572
1572
|
/**
|
|
1573
1573
|
* §17.6.22 `w:type` — where the section starts. `continuous` starts it on the
|
|
1574
|
-
* page already in hand rather than a fresh one;
|
|
1575
|
-
* and the
|
|
1574
|
+
* page already in hand rather than a fresh one; `nextPage` on the next sheet;
|
|
1575
|
+
* `oddPage` and `evenPage` on the next sheet of that PARITY, which means a
|
|
1576
|
+
* blank one in between when the count falls wrong. (`nextColumn` is not
|
|
1577
|
+
* distinguished from `nextPage`.)
|
|
1576
1578
|
*/
|
|
1577
|
-
readonly sectionStart?: 'continuous' | 'nextPage';
|
|
1579
|
+
readonly sectionStart?: 'continuous' | 'nextPage' | 'oddPage' | 'evenPage';
|
|
1578
1580
|
/**
|
|
1579
1581
|
* §17.6.5 `w:docGrid` — the line grid a `lines`/`linesAndChars` section rules
|
|
1580
1582
|
* its text onto, as the pitch in points. Every line of the section's text is
|
|
@@ -6,5 +6,6 @@ export type { FontVariant, FontBytesByVariant } from './font-registry.js';
|
|
|
6
6
|
export { pickVariant } from './font-registry.js';
|
|
7
7
|
export { parseGposKerning, parseGsubLigatures, shapeText } from './opentype-layout.js';
|
|
8
8
|
export type { KerningMap, LigatureMap, ShapedRun } from './opentype-layout.js';
|
|
9
|
+
export { hasSubstitutable, lettersForLigature, substituteLetters } from './ligatures.js';
|
|
9
10
|
export { createFontMeasure } from './measure.js';
|
|
10
11
|
export type { FontMeasure } from './measure.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The letters a character stands for, for a character nothing can draw.
|
|
3
|
+
*
|
|
4
|
+
* @param cp The code point.
|
|
5
|
+
* @returns The letters, or `undefined` when the character is a letter itself.
|
|
6
|
+
*/
|
|
7
|
+
export declare function substituteLetters(cp: number): string | undefined;
|
|
8
|
+
/** Whether any code point of `text` is one {@link substituteLetters} answers for. */
|
|
9
|
+
export declare function hasSubstitutable(text: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* The letters a LIGATURE stands for, for the one place that asks only about
|
|
12
|
+
* those: the `/ToUnicode` a drawn ligature glyph is stated by.
|
|
13
|
+
*
|
|
14
|
+
* @param cp The code point.
|
|
15
|
+
* @returns The letters, or `undefined` when it is not one of these ligatures.
|
|
16
|
+
*/
|
|
17
|
+
export declare function lettersForLigature(cp: number): string | undefined;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region src/core/font/ligatures.ts
|
|
2
|
+
/**
|
|
3
|
+
* The letters a character stands for, for a character nothing can draw.
|
|
4
|
+
*
|
|
5
|
+
* @param cp The code point.
|
|
6
|
+
* @returns The letters, or `undefined` when the character is a letter itself.
|
|
7
|
+
*/
|
|
8
|
+
function substituteLetters(cp) {
|
|
9
|
+
const ligature = LIGATURE_LETTERS.get(cp);
|
|
10
|
+
if (ligature !== void 0) return ligature;
|
|
11
|
+
if (cp >= 119808 && cp <= 120831) return decomposed(cp);
|
|
12
|
+
if (cp >= 8448 && cp <= 8527) {
|
|
13
|
+
const letters = decomposed(cp);
|
|
14
|
+
return letters?.length === 1 ? letters : void 0;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Whether any code point of `text` is one {@link substituteLetters} answers for. */
|
|
18
|
+
function hasSubstitutable(text) {
|
|
19
|
+
for (const ch of text) if (substituteLetters(ch.codePointAt(0)) !== void 0) return true;
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The letters a LIGATURE stands for, for the one place that asks only about
|
|
24
|
+
* those: the `/ToUnicode` a drawn ligature glyph is stated by.
|
|
25
|
+
*
|
|
26
|
+
* @param cp The code point.
|
|
27
|
+
* @returns The letters, or `undefined` when it is not one of these ligatures.
|
|
28
|
+
*/
|
|
29
|
+
function lettersForLigature(cp) {
|
|
30
|
+
return LIGATURE_LETTERS.get(cp);
|
|
31
|
+
}
|
|
32
|
+
/** U+FB00..FB06 — `ſt` opens with a long s, which is an s. */
|
|
33
|
+
var LIGATURE_LETTERS = new Map([
|
|
34
|
+
[64256, "ff"],
|
|
35
|
+
[64257, "fi"],
|
|
36
|
+
[64258, "fl"],
|
|
37
|
+
[64259, "ffi"],
|
|
38
|
+
[64260, "ffl"],
|
|
39
|
+
[64261, "st"],
|
|
40
|
+
[64262, "st"]
|
|
41
|
+
]);
|
|
42
|
+
/** What Unicode says the character is a form of, or `undefined` if nothing. */
|
|
43
|
+
function decomposed(cp) {
|
|
44
|
+
const ch = String.fromCodePoint(cp);
|
|
45
|
+
const base = ch.normalize("NFKD");
|
|
46
|
+
return base === ch ? void 0 : base;
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { hasSubstitutable, lettersForLigature, substituteLetters };
|
|
@@ -63,7 +63,8 @@ export interface ParsedTtf {
|
|
|
63
63
|
/**
|
|
64
64
|
* Parse TrueType / OpenType font bytes into a {@link ParsedTtf}.
|
|
65
65
|
*
|
|
66
|
-
* @param raw The font bytes (sfnt: TrueType `0x00010000` or
|
|
66
|
+
* @param raw The font bytes (sfnt: TrueType `0x00010000` or `'true'`, or
|
|
67
|
+
* OpenType-CFF `OTTO`).
|
|
67
68
|
* @returns The parsed metrics, mappings and layout tables.
|
|
68
69
|
* @throws Error when the bytes are not a TrueType or OpenType font.
|
|
69
70
|
*/
|
|
@@ -4,16 +4,25 @@ import { parseGposKerning, parseGsubArabicForms, parseGsubLigatures } from "./op
|
|
|
4
4
|
var SFNT_TRUETYPE = 65536;
|
|
5
5
|
var SFNT_OPENTYPE_CFF = 1330926671;
|
|
6
6
|
/**
|
|
7
|
+
* `'true'` — Apple's own sfnt tag, and as much a TrueType font as `0x00010000`.
|
|
8
|
+
* Rejected outright, the reader could not read the program a file embedded and
|
|
9
|
+
* so could not say what its glyphs were: complex_ttf_font.pdf,
|
|
10
|
+
* PDFJS-7562-reduced.pdf and arial_unicode_en_cidfont.pdf each carry one and
|
|
11
|
+
* each reconstructed to a blank page.
|
|
12
|
+
*/
|
|
13
|
+
var SFNT_APPLE_TRUE = 1953658213;
|
|
14
|
+
/**
|
|
7
15
|
* Parse TrueType / OpenType font bytes into a {@link ParsedTtf}.
|
|
8
16
|
*
|
|
9
|
-
* @param raw The font bytes (sfnt: TrueType `0x00010000` or
|
|
17
|
+
* @param raw The font bytes (sfnt: TrueType `0x00010000` or `'true'`, or
|
|
18
|
+
* OpenType-CFF `OTTO`).
|
|
10
19
|
* @returns The parsed metrics, mappings and layout tables.
|
|
11
20
|
* @throws Error when the bytes are not a TrueType or OpenType font.
|
|
12
21
|
*/
|
|
13
22
|
function parseTtf(raw) {
|
|
14
23
|
const r = new BigEndianReader(raw);
|
|
15
24
|
const sfntVersion = r.u32();
|
|
16
|
-
if (sfntVersion !== SFNT_TRUETYPE && sfntVersion !== SFNT_OPENTYPE_CFF) throw new Error(`Not a TrueType or OpenType font (sfntVersion=0x${sfntVersion.toString(16).padStart(8, "0")})`);
|
|
25
|
+
if (sfntVersion !== SFNT_TRUETYPE && sfntVersion !== SFNT_OPENTYPE_CFF && sfntVersion !== SFNT_APPLE_TRUE) throw new Error(`Not a TrueType or OpenType font (sfntVersion=0x${sfntVersion.toString(16).padStart(8, "0")})`);
|
|
17
26
|
const numTables = r.u16();
|
|
18
27
|
r.skip(6);
|
|
19
28
|
const tables = /* @__PURE__ */ new Map();
|
|
@@ -11,7 +11,7 @@ export type FamilyKey = 'arimo' | 'tinos' | 'cousine' | 'carlito' | 'caladea';
|
|
|
11
11
|
* only in the regular weight: Noto Sans SC is ten megabytes, and a bold run in
|
|
12
12
|
* it is better stroked (see `SyntheticFace`) than downloaded four times over.
|
|
13
13
|
*/
|
|
14
|
-
export type ScriptKey = 'jp' | 'kr' | 'sc' | 'arabic' | 'hebrew' | 'thai' | '
|
|
14
|
+
export type ScriptKey = 'jp' | 'kr' | 'sc' | 'arabic' | 'hebrew' | 'thai' | 'symbols1' | 'symbols2';
|
|
15
15
|
/** Either kind of substitute — a Latin family or a per-script face. */
|
|
16
16
|
export type SubstituteKey = FamilyKey | ScriptKey;
|
|
17
17
|
/** Whether a substitute key names a writing system rather than a Latin family. */
|
|
@@ -31,7 +31,11 @@ var SCRIPTS = {
|
|
|
31
31
|
pkg: "noto-sans-thai",
|
|
32
32
|
file: "NotoSansThai"
|
|
33
33
|
},
|
|
34
|
-
|
|
34
|
+
symbols1: {
|
|
35
|
+
pkg: "noto-sans-symbols",
|
|
36
|
+
file: "NotoSansSymbols"
|
|
37
|
+
},
|
|
38
|
+
symbols2: {
|
|
35
39
|
pkg: "noto-sans-symbols-2",
|
|
36
40
|
file: "NotoSansSymbols2"
|
|
37
41
|
}
|
|
@@ -102,6 +106,47 @@ var MONO = new Set([
|
|
|
102
106
|
"dejavu sans mono",
|
|
103
107
|
"monospace"
|
|
104
108
|
]);
|
|
109
|
+
var STEMS = [
|
|
110
|
+
["latinmodernroman", "tinos"],
|
|
111
|
+
["latinmodernmath", "tinos"],
|
|
112
|
+
["latinmodernmono", "cousine"],
|
|
113
|
+
["latinmodernsans", "arimo"],
|
|
114
|
+
["lmroman", "tinos"],
|
|
115
|
+
["lmmath", "tinos"],
|
|
116
|
+
["lmmono", "cousine"],
|
|
117
|
+
["lmsans", "arimo"],
|
|
118
|
+
["cmbx", "tinos"],
|
|
119
|
+
["cmcsc", "tinos"],
|
|
120
|
+
["cmti", "tinos"],
|
|
121
|
+
["cmsl", "tinos"],
|
|
122
|
+
["cmmi", "tinos"],
|
|
123
|
+
["cmsy", "tinos"],
|
|
124
|
+
["cmss", "arimo"],
|
|
125
|
+
["cmtt", "cousine"],
|
|
126
|
+
["cmr", "tinos"],
|
|
127
|
+
["nimbusrom", "tinos"],
|
|
128
|
+
["nimbussan", "arimo"],
|
|
129
|
+
["nimbusmon", "cousine"],
|
|
130
|
+
["urwpalladio", "tinos"],
|
|
131
|
+
["urwbookman", "tinos"],
|
|
132
|
+
["centuryschlbk", "tinos"],
|
|
133
|
+
["centuryschoolbook", "tinos"],
|
|
134
|
+
["newcenturyschlbk", "tinos"],
|
|
135
|
+
["bookman", "tinos"],
|
|
136
|
+
["palatino", "tinos"],
|
|
137
|
+
["utopia", "tinos"],
|
|
138
|
+
["charter", "tinos"],
|
|
139
|
+
["baskerville", "tinos"],
|
|
140
|
+
["caslon", "tinos"],
|
|
141
|
+
["minionpro", "tinos"],
|
|
142
|
+
["stoneserif", "tinos"],
|
|
143
|
+
["stonesans", "arimo"],
|
|
144
|
+
["myriadpro", "arimo"]
|
|
145
|
+
];
|
|
146
|
+
/** The family a name STARTS with, for the families named by stem and size. */
|
|
147
|
+
function familyFromStem(name) {
|
|
148
|
+
for (const [stem, key] of STEMS) if (name.startsWith(stem)) return key;
|
|
149
|
+
}
|
|
105
150
|
var WEIGHT_WORDS = new Map([
|
|
106
151
|
["black", "bold"],
|
|
107
152
|
["heavy", "bold"],
|
|
@@ -159,7 +204,7 @@ function resolveFamilyStyle(name) {
|
|
|
159
204
|
];
|
|
160
205
|
let key = "arimo";
|
|
161
206
|
for (const n of tries) {
|
|
162
|
-
const found = EXACT[n] ?? (MONO.has(n) ? "cousine" : SERIF.has(n) ? "tinos" : void 0);
|
|
207
|
+
const found = EXACT[n] ?? (MONO.has(n) ? "cousine" : SERIF.has(n) ? "tinos" : void 0) ?? familyFromStem(n.replace(/[^a-z]/gu, ""));
|
|
163
208
|
if (found) {
|
|
164
209
|
key = found;
|
|
165
210
|
break;
|
|
@@ -33,27 +33,27 @@ var RANGES = [
|
|
|
33
33
|
[
|
|
34
34
|
8592,
|
|
35
35
|
8703,
|
|
36
|
-
"
|
|
36
|
+
"symbols1"
|
|
37
37
|
],
|
|
38
38
|
[
|
|
39
39
|
8960,
|
|
40
40
|
9215,
|
|
41
|
-
"
|
|
41
|
+
"symbols1"
|
|
42
42
|
],
|
|
43
43
|
[
|
|
44
44
|
9312,
|
|
45
45
|
9471,
|
|
46
|
-
"
|
|
46
|
+
"symbols1"
|
|
47
47
|
],
|
|
48
48
|
[
|
|
49
49
|
9472,
|
|
50
50
|
10175,
|
|
51
|
-
"
|
|
51
|
+
"symbols2"
|
|
52
52
|
],
|
|
53
53
|
[
|
|
54
54
|
11008,
|
|
55
55
|
11263,
|
|
56
|
-
"
|
|
56
|
+
"symbols2"
|
|
57
57
|
],
|
|
58
58
|
[
|
|
59
59
|
12288,
|
|
@@ -149,6 +149,11 @@ function scriptsInFlow(flow) {
|
|
|
149
149
|
marks.add("han");
|
|
150
150
|
continue;
|
|
151
151
|
}
|
|
152
|
+
if (key === "symbols1" || key === "symbols2") {
|
|
153
|
+
scripts.add("symbols1");
|
|
154
|
+
scripts.add("symbols2");
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
152
157
|
scripts.add(key);
|
|
153
158
|
}
|
|
154
159
|
};
|
|
@@ -53,20 +53,65 @@ function applyHeaderTint(hex, amount, darken) {
|
|
|
53
53
|
function buildHeaderFooterContent(formatString, sheetName, scale = 1, basePt = DEFAULT_HEADER_PT, fileName, themePalette, now) {
|
|
54
54
|
const regions = parseHeaderFooterString(formatString, sheetName, scale, basePt, fileName, themePalette, now);
|
|
55
55
|
const out = [];
|
|
56
|
-
const
|
|
57
|
-
|
|
56
|
+
const lines = {
|
|
57
|
+
left: splitLines(regions.left),
|
|
58
|
+
center: splitLines(regions.center),
|
|
59
|
+
right: splitLines(regions.right)
|
|
60
|
+
};
|
|
61
|
+
const filled = (region) => lines[region].filter((line) => line.length > 0).length;
|
|
62
|
+
if ((filled("left") > 0 ? 1 : 0) + (filled("center") > 0 ? 1 : 0) + (filled("right") > 0 ? 1 : 0) <= 1) {
|
|
63
|
+
const para = (runs, alignment) => {
|
|
64
|
+
for (const line of splitLines(runs)) if (line.length > 0) out.push({
|
|
65
|
+
kind: "paragraph",
|
|
66
|
+
paragraph: {
|
|
67
|
+
properties: { alignment },
|
|
68
|
+
runs: line
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
para(regions.left, "left");
|
|
73
|
+
para(regions.center, "center");
|
|
74
|
+
para(regions.right, "right");
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
const deep = Math.max(lines.left.length, lines.center.length, lines.right.length);
|
|
78
|
+
for (let i = 0; i < deep; i++) {
|
|
79
|
+
const left = lines.left[i] ?? [];
|
|
80
|
+
const center = lines.center[i] ?? [];
|
|
81
|
+
const right = lines.right[i] ?? [];
|
|
82
|
+
if (left.length + center.length + right.length === 0) continue;
|
|
83
|
+
const runs = [...left];
|
|
84
|
+
if (center.length > 0) runs.push(TAB_RUN, ...center);
|
|
85
|
+
if (right.length > 0) runs.push(TAB_RUN, ...right);
|
|
86
|
+
out.push({
|
|
58
87
|
kind: "paragraph",
|
|
59
88
|
paragraph: {
|
|
60
|
-
properties: {
|
|
61
|
-
runs
|
|
89
|
+
properties: { tabs: HEADER_STOPS },
|
|
90
|
+
runs
|
|
62
91
|
}
|
|
63
92
|
});
|
|
64
|
-
}
|
|
65
|
-
para(regions.left, "left");
|
|
66
|
-
para(regions.center, "center");
|
|
67
|
-
para(regions.right, "right");
|
|
93
|
+
}
|
|
68
94
|
return out;
|
|
69
95
|
}
|
|
96
|
+
/** The tab a header's regions are separated by. */
|
|
97
|
+
var TAB_RUN = {
|
|
98
|
+
text: " ",
|
|
99
|
+
properties: {}
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* §17.3.1.38 — the two stops a header's regions stand on: the middle of the
|
|
103
|
+
* band and its far edge, both stated against the band itself rather than at a
|
|
104
|
+
* distance, since only the layout knows how wide the band is.
|
|
105
|
+
*/
|
|
106
|
+
var HEADER_STOPS = [{
|
|
107
|
+
positionPt: pt(0),
|
|
108
|
+
relativeTo: "center",
|
|
109
|
+
alignment: "center"
|
|
110
|
+
}, {
|
|
111
|
+
positionPt: pt(0),
|
|
112
|
+
relativeTo: "right",
|
|
113
|
+
alignment: "right"
|
|
114
|
+
}];
|
|
70
115
|
/**
|
|
71
116
|
* Break a region's runs at the line breaks Excel allows inside one — a header
|
|
72
117
|
* region is not necessarily one line.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveFamilyStyle } from "../core/fonts/remote-fonts.js";
|
|
2
2
|
import { scriptForCodepoint } from "../core/fonts/scripts.js";
|
|
3
3
|
import { shapeText } from "../core/font/opentype-layout.js";
|
|
4
|
+
import { hasSubstitutable, substituteLetters } from "../core/font/ligatures.js";
|
|
4
5
|
import { createFontMeasure } from "../core/font/measure.js";
|
|
5
6
|
import { halfPtToPt, pt } from "../core/ir/units.js";
|
|
6
7
|
import { ResourceStore } from "../core/ir/resources.js";
|
|
@@ -2434,6 +2435,40 @@ function fallbackFaceKey(options, cp, bold, italic) {
|
|
|
2434
2435
|
}
|
|
2435
2436
|
}
|
|
2436
2437
|
/**
|
|
2438
|
+
* A character no face on hand can draw, set as the letters it stands for.
|
|
2439
|
+
*
|
|
2440
|
+
* `ff` (U+FB00) is one glyph for two f's — a shape of the FACE, not a letter of
|
|
2441
|
+
* the alphabet, and most faces carry no such code point at all: they form the
|
|
2442
|
+
* ligature from `ff` through their own `liga` table. `𝑝` (U+1D45D) is a lower
|
|
2443
|
+
* case p set in italic, given a code point of its own so that mathematics can
|
|
2444
|
+
* name a variable in plain text; no ordinary face carries that either. So the
|
|
2445
|
+
* run's own face drew nothing, no other loaded face answered, and the character
|
|
2446
|
+
* went out as a box or as nothing at all: bug1873345.pdf reads "different" and
|
|
2447
|
+
* we set "di erent", bug1529502.pdf reads "p ← trim(p)" and we set "□ ← □".
|
|
2448
|
+
* The letters are the same text — the ligature the face is free to form again,
|
|
2449
|
+
* the slant is lost, and both beat a box.
|
|
2450
|
+
*
|
|
2451
|
+
* Only where nobody can draw the character itself: one a face DOES carry is
|
|
2452
|
+
* drawn as written.
|
|
2453
|
+
*
|
|
2454
|
+
* @param options The render options (the loaded registries).
|
|
2455
|
+
* @param primary The run's own face.
|
|
2456
|
+
* @param resolved Its resolved properties (weight and slant).
|
|
2457
|
+
* @param text The run's text.
|
|
2458
|
+
* @returns The text to set, which is `text` itself unless something was undrawable.
|
|
2459
|
+
*/
|
|
2460
|
+
function drawableText(options, primary, resolved, text) {
|
|
2461
|
+
if (!hasSubstitutable(text)) return text;
|
|
2462
|
+
let out = "";
|
|
2463
|
+
for (const ch of text) {
|
|
2464
|
+
const cp = ch.codePointAt(0);
|
|
2465
|
+
const letters = substituteLetters(cp);
|
|
2466
|
+
const drawable = letters === void 0 || primary.glyphForCodepoint(cp) !== 0 || fallbackFaceKey(options, cp, resolved.bold, resolved.italic) !== void 0;
|
|
2467
|
+
out += drawable ? ch : letters;
|
|
2468
|
+
}
|
|
2469
|
+
return out;
|
|
2470
|
+
}
|
|
2471
|
+
/**
|
|
2437
2472
|
* §9.2.2 — the face a request had to settle for, and what is left to draw.
|
|
2438
2473
|
*
|
|
2439
2474
|
* A registry may hold one face and be asked for four: `FontBytesByVariant`
|
|
@@ -2580,7 +2615,7 @@ function collectFontResources(body, headersFooters, options, imageResources) {
|
|
|
2580
2615
|
};
|
|
2581
2616
|
used.set(fontKey, bucket);
|
|
2582
2617
|
}
|
|
2583
|
-
const text = run.listMarker === true ? markerText(run.text, parsed) : run.text;
|
|
2618
|
+
const text = drawableText(options, parsed, resolved, run.listMarker === true ? markerText(run.text, parsed) : run.text);
|
|
2584
2619
|
for (const span of faceSpans(options, {
|
|
2585
2620
|
fontKey,
|
|
2586
2621
|
parsed
|
|
@@ -3008,9 +3043,16 @@ function tokenizeParagraph(paragraph, options, fontResources, imageResources, co
|
|
|
3008
3043
|
const { fontKey, synthetic } = runFontKeyAndParsed(options, resolvedRun.fontFamily.ascii, resolvedRun.bold, resolvedRun.italic);
|
|
3009
3044
|
const script = SCRIPT_OFFSET[resolvedRun.verticalAlign] ?? 0;
|
|
3010
3045
|
const own = lookupFont(fontResources, fontKey);
|
|
3011
|
-
const
|
|
3046
|
+
const drawn = (() => {
|
|
3047
|
+
const text = drawableText(options, own.parsed, resolvedRun, run.text);
|
|
3048
|
+
return text === run.text ? run : {
|
|
3049
|
+
...run,
|
|
3050
|
+
text
|
|
3051
|
+
};
|
|
3052
|
+
})();
|
|
3053
|
+
const faceFor = facesForRun(options, fontResources, own.parsed, resolvedRun, drawn.text);
|
|
3012
3054
|
return {
|
|
3013
|
-
run,
|
|
3055
|
+
run: drawn,
|
|
3014
3056
|
resolvedRun,
|
|
3015
3057
|
font: own,
|
|
3016
3058
|
...faceFor ? { faceFor } : {},
|
|
@@ -5114,6 +5156,14 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
5114
5156
|
}
|
|
5115
5157
|
if (asm.pageHasContent() || asm.pageInSection === 0) asm.flushPage(true);
|
|
5116
5158
|
else asm.dropPage();
|
|
5159
|
+
const opening = sectionCtxs[asm.secIdx + 1]?.properties;
|
|
5160
|
+
const wants = opening?.sectionStart;
|
|
5161
|
+
if (wants === "oddPage" || wants === "evenPage") {
|
|
5162
|
+
if ((opening?.pageNumberStart ?? asm.pageNumber) % 2 === 1 !== (wants === "oddPage")) {
|
|
5163
|
+
asm.cursorY = asm.ctx.pageHeight - asm.ctx.marginTop;
|
|
5164
|
+
asm.flushPage(true);
|
|
5165
|
+
}
|
|
5166
|
+
}
|
|
5117
5167
|
asm.secIdx++;
|
|
5118
5168
|
asm.ctx = sectionCtxs[asm.secIdx];
|
|
5119
5169
|
asm.restartPageNumbers(asm.ctx);
|
package/dist/esm/pdf/cid-font.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { glyphClosure, subsetTtf } from "../core/font/ttf-subset.js";
|
|
2
|
+
import { lettersForLigature } from "../core/font/ligatures.js";
|
|
2
3
|
import { createFontMeasure } from "../core/font/measure.js";
|
|
3
4
|
import { deflatedStream, dict, name, ref } from "./objects.js";
|
|
4
5
|
//#region src/pdf/cid-font.ts
|
|
@@ -109,6 +110,22 @@ function consecutiveRuns(ids) {
|
|
|
109
110
|
}
|
|
110
111
|
return runs;
|
|
111
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* How much a code point says about the character a glyph stands for.
|
|
115
|
+
*
|
|
116
|
+
* A presentation form is a shape — the ligature two letters make, the form a
|
|
117
|
+
* letter takes where it joins — but a shape that names its letters. A
|
|
118
|
+
* private-use code is a font's own numbering and names nothing at all. A glyph
|
|
119
|
+
* several codes reach keeps the one that says the most.
|
|
120
|
+
*
|
|
121
|
+
* @param cp The code point.
|
|
122
|
+
* @returns 0 for a character, 1 for a presentation form, 2 for private use.
|
|
123
|
+
*/
|
|
124
|
+
function codeRank(cp) {
|
|
125
|
+
if (cp >= 57344 && cp <= 63743) return 2;
|
|
126
|
+
if (cp >= 64256 && cp <= 65023 || cp >= 65136 && cp <= 65279) return 1;
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
112
129
|
function buildToUnicodeCMap(parsed, subsetGids) {
|
|
113
130
|
const inSubset = subsetGids ? new Set(subsetGids) : void 0;
|
|
114
131
|
const gidToCps = /* @__PURE__ */ new Map();
|
|
@@ -116,30 +133,40 @@ function buildToUnicodeCMap(parsed, subsetGids) {
|
|
|
116
133
|
for (let cp = 9; cp <= 65535; cp++) {
|
|
117
134
|
if (cp >= 55296 && cp <= 57343) continue;
|
|
118
135
|
const gid = parsed.glyphForCodepoint(cp);
|
|
119
|
-
if (gid === 0
|
|
136
|
+
if (gid === 0) continue;
|
|
137
|
+
const had = gidToCps.get(gid);
|
|
138
|
+
if (had) {
|
|
139
|
+
if (had.length === 1 && codeRank(cp) < codeRank(had[0])) gidToCps.set(gid, [cp]);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
120
142
|
gidToCps.set(gid, [cp]);
|
|
121
143
|
order.push(gid);
|
|
122
144
|
}
|
|
145
|
+
for (const [gid, cps] of gidToCps) {
|
|
146
|
+
const letters = cps.length === 1 ? lettersForLigature(cps[0]) : void 0;
|
|
147
|
+
if (letters !== void 0) gidToCps.set(gid, [...letters].map((c) => c.codePointAt(0)));
|
|
148
|
+
}
|
|
123
149
|
let changed = true;
|
|
124
150
|
while (changed) {
|
|
125
151
|
changed = false;
|
|
126
152
|
for (const [key, ligGid] of parsed.ligatures) {
|
|
127
|
-
|
|
153
|
+
const have = gidToCps.get(ligGid);
|
|
154
|
+
if (have && !(have.length === 1 && codeRank(have[0]) > 0)) continue;
|
|
128
155
|
const cps = [];
|
|
129
156
|
let ok = true;
|
|
130
157
|
for (const comp of key.split(",")) {
|
|
131
158
|
const c = gidToCps.get(Number(comp));
|
|
132
|
-
if (!c) {
|
|
159
|
+
if (!c || c === have) {
|
|
133
160
|
ok = false;
|
|
134
161
|
break;
|
|
135
162
|
}
|
|
136
163
|
cps.push(...c);
|
|
137
164
|
}
|
|
138
|
-
if (ok
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
165
|
+
if (!ok || cps.length === 0) continue;
|
|
166
|
+
if (have?.join(",") === cps.join(",")) continue;
|
|
167
|
+
gidToCps.set(ligGid, cps);
|
|
168
|
+
if (!have) order.push(ligGid);
|
|
169
|
+
changed = true;
|
|
143
170
|
}
|
|
144
171
|
}
|
|
145
172
|
const pairs = order.filter((g) => inSubset === void 0 || inSubset.has(g)).map((g) => [g, gidToCps.get(g)]).sort((a, b) => a[0] - b[0]);
|