reamkit 1.29.0 → 1.30.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/dist/esm/core/document-model/types.d.ts +2 -0
- package/dist/esm/core/drawingml/shape-render.js +13 -1
- package/dist/esm/core/fonts/index.d.ts +1 -1
- package/dist/esm/core/fonts/remote-fonts.d.ts +8 -0
- package/dist/esm/core/fonts/remote-fonts.js +100 -17
- package/dist/esm/core/ir/flow.d.ts +17 -0
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/pdf-reader/annot-draw.js +93 -1
- package/dist/esm/pdf-reader/annots.d.ts +18 -0
- package/dist/esm/pdf-reader/annots.js +86 -9
- package/dist/esm/pdf-reader/cmap.js +5 -2
- package/dist/esm/pdf-reader/content.d.ts +17 -4
- package/dist/esm/pdf-reader/content.js +163 -11
- package/dist/esm/pdf-reader/display.js +16 -0
- package/dist/esm/pdf-reader/embedded-fonts.d.ts +24 -0
- package/dist/esm/pdf-reader/embedded-fonts.js +48 -9
- package/dist/esm/pdf-reader/flow-build.d.ts +96 -5
- package/dist/esm/pdf-reader/flow-build.js +210 -23
- package/dist/esm/pdf-reader/font.d.ts +27 -1
- package/dist/esm/pdf-reader/font.js +313 -29
- package/dist/esm/pdf-reader/glyf-outline.js +13 -2
- package/dist/esm/pdf-reader/glyph-shapes.d.ts +18 -0
- package/dist/esm/pdf-reader/glyph-shapes.js +57 -0
- package/dist/esm/pdf-reader/image-decode.js +70 -4
- package/dist/esm/pdf-reader/images.d.ts +5 -0
- package/dist/esm/pdf-reader/images.js +4 -2
- package/dist/esm/pdf-reader/jbig2.d.ts +40 -1
- package/dist/esm/pdf-reader/jbig2.js +78 -16
- package/dist/esm/pdf-reader/jpeg.d.ts +6 -3
- package/dist/esm/pdf-reader/jpeg.js +21 -1
- package/dist/esm/pdf-reader/layout.d.ts +26 -2
- package/dist/esm/pdf-reader/layout.js +638 -92
- package/dist/esm/pdf-reader/lexer.d.ts +10 -0
- package/dist/esm/pdf-reader/lexer.js +17 -0
- package/dist/esm/pdf-reader/pattern-tint.d.ts +11 -1
- package/dist/esm/pdf-reader/pattern-tint.js +21 -3
- package/dist/esm/pdf-reader/regions.d.ts +25 -0
- package/dist/esm/pdf-reader/regions.js +167 -0
- package/dist/esm/pdf-reader/shading.d.ts +58 -2
- package/dist/esm/pdf-reader/shading.js +181 -11
- package/dist/esm/pdf-reader/struct-tree.js +112 -8
- package/dist/esm/pdf-reader/tagged.js +27 -7
- package/dist/esm/pdf-reader/text-rules.js +1 -1
- package/dist/esm/pdf-reader/text.js +9 -14
- package/dist/esm/pdf-reader/vector.d.ts +8 -2
- package/dist/esm/pdf-reader/vector.js +34 -5
- package/dist/esm/word/docx-writer.js +137 -36
- package/dist/esm/word/drawing-parser.js +7 -2
- package/dist/esm/word/paragraph-properties.js +2 -0
- package/package.json +5 -3
|
@@ -1127,6 +1127,8 @@ export interface ShapeLine {
|
|
|
1127
1127
|
readonly customDash?: ReadonlyArray<number>;
|
|
1128
1128
|
readonly cap?: 'flat' | 'round' | 'square';
|
|
1129
1129
|
readonly fill?: 'solid' | 'none';
|
|
1130
|
+
/** §20.1.2.3.1 `a:alpha` on the line's colour — how opaque the stroke is, `0..1`. */
|
|
1131
|
+
readonly alpha?: number;
|
|
1130
1132
|
/** §20.1.8.24 `a:headEnd` — the decoration at the line's first point. */
|
|
1131
1133
|
readonly headEnd?: LineEnd;
|
|
1132
1134
|
/** §20.1.8.42 `a:tailEnd` — the decoration at the line's last point. */
|
|
@@ -192,13 +192,25 @@ function buildStroke(line) {
|
|
|
192
192
|
const widthPt = line.width ?? DEFAULT_LINE_WIDTH_EMU / 12700;
|
|
193
193
|
const dash = line.customDash?.map((n) => Math.max(.01, n * widthPt)) ?? (line.dash && line.dash !== "solid" ? dashPattern(line.dash, widthPt) : void 0);
|
|
194
194
|
const cap = line.cap === "flat" ? "butt" : line.cap;
|
|
195
|
+
const colorHex = line.colorHex ?? "000000";
|
|
195
196
|
return {
|
|
196
|
-
colorHex: line.colorHex
|
|
197
|
+
colorHex: line.alpha !== void 0 && line.alpha < 1 ? overWhite(colorHex, line.alpha) : colorHex,
|
|
197
198
|
widthPt,
|
|
198
199
|
...dash ? { dash } : {},
|
|
199
200
|
...cap ? { cap } : {}
|
|
200
201
|
};
|
|
201
202
|
}
|
|
203
|
+
/** A 6-hex colour drawn at `alpha` over white, as the opaque colour it comes to. */
|
|
204
|
+
function overWhite(hex, alpha) {
|
|
205
|
+
const n = parseInt(hex, 16);
|
|
206
|
+
if (Number.isNaN(n)) return hex;
|
|
207
|
+
const a = Math.max(0, Math.min(1, alpha));
|
|
208
|
+
return [
|
|
209
|
+
16,
|
|
210
|
+
8,
|
|
211
|
+
0
|
|
212
|
+
].map((shift) => Math.round(255 - (255 - (n >> shift & 255)) * a).toString(16).padStart(2, "0")).join("").toUpperCase();
|
|
213
|
+
}
|
|
202
214
|
function dashPattern(dash, w) {
|
|
203
215
|
const u = Math.max(w, .1);
|
|
204
216
|
switch (dash) {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { fetchFontSet, fetchScriptFont, isScriptKey, resolveFamilyKey, resolveFamilyStyle, clearFontCache, } from './remote-fonts.js';
|
|
1
|
+
export { fetchFontSet, fetchScriptFont, isScriptKey, knowsFamily, resolveFamilyKey, resolveFamilyStyle, clearFontCache, } from './remote-fonts.js';
|
|
2
2
|
export { scriptForCodepoint, scriptsInFlow } from './scripts.js';
|
|
3
3
|
export type { FamilyKey, FamilyStyle, FetchFontSetOptions, FetchLike, ScriptKey, SubstituteKey, } from './remote-fonts.js';
|
|
@@ -52,6 +52,14 @@ export interface FamilyStyle {
|
|
|
52
52
|
* @returns The chosen family and what the name said about the face.
|
|
53
53
|
*/
|
|
54
54
|
export declare function resolveFamilyStyle(name: string | undefined): FamilyStyle;
|
|
55
|
+
/**
|
|
56
|
+
* Whether the tables KNOW a family name — so the substitute it maps to is the
|
|
57
|
+
* name's own answer, and not the sans that every name nobody knows falls to.
|
|
58
|
+
*
|
|
59
|
+
* @param name The referenced family or face name.
|
|
60
|
+
* @returns `true` where the name matched a twin, a class or a stem.
|
|
61
|
+
*/
|
|
62
|
+
export declare function knowsFamily(name: string | undefined): boolean;
|
|
55
63
|
/**
|
|
56
64
|
* The curated substitute for a family name — {@link resolveFamilyStyle} without
|
|
57
65
|
* the face it also carries.
|
|
@@ -98,7 +98,24 @@ var SERIF = new Set([
|
|
|
98
98
|
"minion pro",
|
|
99
99
|
"serif"
|
|
100
100
|
]);
|
|
101
|
+
var SANS = new Set([
|
|
102
|
+
"roboto",
|
|
103
|
+
"noto sans",
|
|
104
|
+
"notosans",
|
|
105
|
+
"dejavu sans",
|
|
106
|
+
"dejavusans",
|
|
107
|
+
"lucida sans",
|
|
108
|
+
"lucidasans",
|
|
109
|
+
"ubuntu",
|
|
110
|
+
"lato",
|
|
111
|
+
"inter",
|
|
112
|
+
"din",
|
|
113
|
+
"sans",
|
|
114
|
+
"sans serif"
|
|
115
|
+
]);
|
|
101
116
|
var MONO = new Set([
|
|
117
|
+
"mono",
|
|
118
|
+
"monospaced",
|
|
102
119
|
"consolas",
|
|
103
120
|
"monaco",
|
|
104
121
|
"menlo",
|
|
@@ -138,14 +155,59 @@ var STEMS = [
|
|
|
138
155
|
["charter", "tinos"],
|
|
139
156
|
["baskerville", "tinos"],
|
|
140
157
|
["caslon", "tinos"],
|
|
141
|
-
["
|
|
158
|
+
["minion", "tinos"],
|
|
159
|
+
["stix", "tinos"],
|
|
142
160
|
["stoneserif", "tinos"],
|
|
143
161
|
["stonesans", "arimo"],
|
|
144
|
-
["
|
|
162
|
+
["myriad", "arimo"],
|
|
163
|
+
["arial", "arimo"],
|
|
164
|
+
["helvetica", "arimo"],
|
|
165
|
+
["frutiger", "arimo"],
|
|
166
|
+
["univers", "arimo"],
|
|
167
|
+
["futura", "arimo"],
|
|
168
|
+
["gillsans", "arimo"],
|
|
169
|
+
["optima", "arimo"],
|
|
170
|
+
["avenir", "arimo"],
|
|
171
|
+
["verdana", "arimo"],
|
|
172
|
+
["tahoma", "arimo"],
|
|
173
|
+
["trebuchet", "arimo"],
|
|
174
|
+
["segoeui", "arimo"],
|
|
175
|
+
["franklingothic", "arimo"],
|
|
176
|
+
["newsgothic", "arimo"],
|
|
177
|
+
["tradegothic", "arimo"],
|
|
178
|
+
["centurygothic", "arimo"],
|
|
179
|
+
["akzidenz", "arimo"],
|
|
180
|
+
["eurostile", "arimo"],
|
|
181
|
+
["avantgarde", "arimo"],
|
|
182
|
+
["gotham", "arimo"],
|
|
183
|
+
["proximanova", "arimo"],
|
|
184
|
+
["opensans", "arimo"],
|
|
185
|
+
["sourcesans", "arimo"],
|
|
186
|
+
["ptsans", "arimo"],
|
|
187
|
+
["firasans", "arimo"],
|
|
188
|
+
["montserrat", "arimo"],
|
|
189
|
+
["candara", "arimo"],
|
|
190
|
+
["corbel", "arimo"],
|
|
191
|
+
["lucidagrande", "arimo"],
|
|
192
|
+
["mssansserif", "arimo"],
|
|
193
|
+
["microsoftsansserif", "arimo"],
|
|
194
|
+
["dinpro", "arimo"],
|
|
195
|
+
["dinnext", "arimo"],
|
|
196
|
+
["monospace", "cousine"],
|
|
197
|
+
["lucidatypewriter", "cousine"],
|
|
198
|
+
["lucidasanstypewriter", "cousine"]
|
|
145
199
|
];
|
|
146
200
|
/** The family a name STARTS with, for the families named by stem and size. */
|
|
147
201
|
function familyFromStem(name) {
|
|
148
|
-
for (const [stem, key] of STEMS)
|
|
202
|
+
for (const [stem, key] of STEMS) {
|
|
203
|
+
if (!name.startsWith(stem)) continue;
|
|
204
|
+
if (key === "arimo") {
|
|
205
|
+
const cut = name.slice(stem.length);
|
|
206
|
+
if (/mono|typewriter/u.test(cut)) return "cousine";
|
|
207
|
+
if (/serif|slab/u.test(cut)) return "tinos";
|
|
208
|
+
}
|
|
209
|
+
return key;
|
|
210
|
+
}
|
|
149
211
|
}
|
|
150
212
|
var WEIGHT_WORDS = new Map([
|
|
151
213
|
["black", "bold"],
|
|
@@ -185,6 +247,26 @@ var NARROW_SCALE = .82;
|
|
|
185
247
|
*/
|
|
186
248
|
function resolveFamilyStyle(name) {
|
|
187
249
|
if (!name) return { key: "arimo" };
|
|
250
|
+
const { words, bold, italic, narrow } = readName(name);
|
|
251
|
+
return {
|
|
252
|
+
key: familyOfWords(words) ?? "arimo",
|
|
253
|
+
...bold ? { bold } : {},
|
|
254
|
+
...italic ? { italic } : {},
|
|
255
|
+
...narrow ? { widthScale: NARROW_SCALE } : {}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Whether the tables KNOW a family name — so the substitute it maps to is the
|
|
260
|
+
* name's own answer, and not the sans that every name nobody knows falls to.
|
|
261
|
+
*
|
|
262
|
+
* @param name The referenced family or face name.
|
|
263
|
+
* @returns `true` where the name matched a twin, a class or a stem.
|
|
264
|
+
*/
|
|
265
|
+
function knowsFamily(name) {
|
|
266
|
+
return name !== void 0 && name !== "" && familyOfWords(readName(name).words) !== void 0;
|
|
267
|
+
}
|
|
268
|
+
/** A name's family words, with the face words that ended it taken off and read. */
|
|
269
|
+
function readName(name) {
|
|
188
270
|
const words = name.trim().toLowerCase().split(/[\s\-_,]+/u).filter((w) => w !== "");
|
|
189
271
|
let bold = false;
|
|
190
272
|
let italic = false;
|
|
@@ -197,25 +279,26 @@ function resolveFamilyStyle(name) {
|
|
|
197
279
|
if (word === "narrow") narrow = true;
|
|
198
280
|
words.pop();
|
|
199
281
|
}
|
|
282
|
+
return {
|
|
283
|
+
words,
|
|
284
|
+
bold,
|
|
285
|
+
italic,
|
|
286
|
+
narrow
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
/** The substitute a family's words name, or `undefined` where no table knows them. */
|
|
290
|
+
function familyOfWords(words) {
|
|
291
|
+
if (words.length === 0) return void 0;
|
|
292
|
+
const whole = words.join(" ");
|
|
200
293
|
const tries = [
|
|
201
|
-
|
|
294
|
+
whole,
|
|
202
295
|
words[words.length - 1],
|
|
203
296
|
words[0]
|
|
204
297
|
];
|
|
205
|
-
let key = "arimo";
|
|
206
298
|
for (const n of tries) {
|
|
207
|
-
const found = EXACT[n] ?? (MONO.has(n) ? "cousine" : SERIF.has(n) ? "tinos" : void 0) ?? familyFromStem(n.replace(/[^a-z]/gu, ""));
|
|
208
|
-
if (found)
|
|
209
|
-
key = found;
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
299
|
+
const found = EXACT[n] ?? (MONO.has(n) ? "cousine" : SERIF.has(n) ? "tinos" : void 0) ?? (n === whole && SANS.has(n) ? "arimo" : void 0) ?? familyFromStem(n.replace(/[^a-z]/gu, ""));
|
|
300
|
+
if (found) return found;
|
|
212
301
|
}
|
|
213
|
-
return {
|
|
214
|
-
key,
|
|
215
|
-
...bold ? { bold } : {},
|
|
216
|
-
...italic ? { italic } : {},
|
|
217
|
-
...narrow ? { widthScale: NARROW_SCALE } : {}
|
|
218
|
-
};
|
|
219
302
|
}
|
|
220
303
|
/**
|
|
221
304
|
* The curated substitute for a family name — {@link resolveFamilyStyle} without
|
|
@@ -279,4 +362,4 @@ async function fetchFontSet(options = {}) {
|
|
|
279
362
|
};
|
|
280
363
|
}
|
|
281
364
|
//#endregion
|
|
282
|
-
export { fetchFontSet, fetchScriptFont, resolveFamilyKey, resolveFamilyStyle };
|
|
365
|
+
export { fetchFontSet, fetchScriptFont, knowsFamily, resolveFamilyKey, resolveFamilyStyle };
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { BodyElement, Chart, Comment, DocumentInfo, Numbering, Section, SectionProperties, ShapeFill, StyleSheet } from '../document-model/index.js';
|
|
2
2
|
import { FontRegistry } from '../font/index.js';
|
|
3
3
|
import { ResourceStore } from './resources.js';
|
|
4
|
+
/**
|
|
5
|
+
* A face's family as a word processor names it (ECMA-376 §17.8.3.9 `w:font`).
|
|
6
|
+
*/
|
|
7
|
+
export interface FaceFamily {
|
|
8
|
+
/** The family's own name: `Inter` for `Inter-SemiBold`, `Arial` for `ArialMT`. */
|
|
9
|
+
readonly family: string;
|
|
10
|
+
/** §17.8.3.10 `w:family` — the kind of face, for a reader that has to substitute. */
|
|
11
|
+
readonly generic: 'roman' | 'swiss' | 'modern';
|
|
12
|
+
}
|
|
4
13
|
/**
|
|
5
14
|
* The semantic IR tree (ir-design §5): everything a reader extracts from the
|
|
6
15
|
* document bytes, format-neutrally — the flow `body` plus its document-scoped
|
|
@@ -42,6 +51,14 @@ export interface FlowDoc {
|
|
|
42
51
|
readonly resources: ResourceStore;
|
|
43
52
|
/** Fonts embedded in the source document itself (docx fontTable), by name. */
|
|
44
53
|
readonly embeddedFonts?: ReadonlyMap<string, FontRegistry>;
|
|
54
|
+
/**
|
|
55
|
+
* The family each face a run names belongs to, where the two are not the
|
|
56
|
+
* same name. A PDF names a FACE — `Inter-SemiBold` — where a word processor
|
|
57
|
+
* names a family and states the weight beside it (`Inter`, bold). The layout
|
|
58
|
+
* finds a document's own program by the face; a writer that hands the text
|
|
59
|
+
* to another program names the family, which is the name that program knows.
|
|
60
|
+
*/
|
|
61
|
+
readonly faceFamilies?: ReadonlyMap<string, FaceFamily>;
|
|
45
62
|
/** Document metadata from docProps/core.xml. */
|
|
46
63
|
readonly info?: DocumentInfo;
|
|
47
64
|
/** Document natural language hint (BCP-47), e.g. for tagged-PDF /Lang. */
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export { getHyphenator, createLanguageHyphenator, createHyphenator, splitPattern
|
|
|
34
34
|
export type { Hyphenator, HyphenatorOptions, SupportedLanguage } from './core/hyphenation/index.js';
|
|
35
35
|
export type { Pt, ResourceId, Feature, KnownFeature, Loss, LossReport, LossSeverity, NativeBag, } from './core/ir/index.js';
|
|
36
36
|
export { FEATURES, ResourceStore, ConversionLossError, formatLoss, pt, twipsToPt, halfPtToPt, eighthPtToPt, emuToPt, pxToPt, inchToPt, mmToPt, } from './core/ir/index.js';
|
|
37
|
-
export type { FlowDoc } from './core/ir/flow.js';
|
|
37
|
+
export type { FaceFamily, FlowDoc } from './core/ir/flow.js';
|
|
38
38
|
export type { DocumentReader, DocumentWriter, ReadOptions, ReadResult, WriteOptions, WriteResult, } from './core/ir/adapters.js';
|
|
39
39
|
export { docxReader, readDocx } from './word/docx-reader.js';
|
|
40
40
|
export { xlsxReader, readXlsx } from './excel/xlsx-reader.js';
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { PDF_NULL, PdfName, PdfStream } from "../pdf/objects.js";
|
|
1
|
+
import { PDF_NULL, PdfHexString, PdfName, PdfStream } from "../pdf/objects.js";
|
|
2
|
+
import { reorderVisual } from "../core/bidi/algorithm.js";
|
|
3
|
+
import { analyzeString, hasBidiCharacters } from "../core/bidi/index.js";
|
|
2
4
|
//#region src/pdf-reader/annot-draw.ts
|
|
3
5
|
/** The circle constant: how far a Bézier handle reaches to round a quarter. */
|
|
4
6
|
var KAPPA = .5523;
|
|
@@ -20,6 +22,7 @@ function drawnAppearance(file, annot) {
|
|
|
20
22
|
const subtype = file.get(annot, "Subtype");
|
|
21
23
|
if (!(subtype instanceof PdfName)) return void 0;
|
|
22
24
|
if (subtype.value === "Widget") return typedValue(file, annot) ?? checkedBox(file, annot);
|
|
25
|
+
if (subtype.value === "FreeText") return freeText(file, annot);
|
|
23
26
|
const markup = markupShape(file, annot, subtype.value);
|
|
24
27
|
if (markup) return markup;
|
|
25
28
|
const pen = borderWidth(file, annot);
|
|
@@ -162,6 +165,10 @@ var SQUIGGLE_AMP_EM = .07;
|
|
|
162
165
|
*/
|
|
163
166
|
function drawnResources(file, annot) {
|
|
164
167
|
const subtype = file.get(annot, "Subtype");
|
|
168
|
+
if (subtype instanceof PdfName && subtype.value === "FreeText") {
|
|
169
|
+
const text = freeTextContents(file, annot);
|
|
170
|
+
return text === void 0 ? void 0 : new Map([["Font", new Map([[FREE_TEXT_FONT, spelledFace(text)]])]]);
|
|
171
|
+
}
|
|
165
172
|
if (!(subtype instanceof PdfName) || subtype.value !== "Widget") return void 0;
|
|
166
173
|
const acro = file.get(file.catalog, "AcroForm");
|
|
167
174
|
if (!(acro instanceof Map)) return void 0;
|
|
@@ -211,6 +218,91 @@ function typedValue(file, annot) {
|
|
|
211
218
|
ops.push("ET", "Q");
|
|
212
219
|
return new PdfStream(/* @__PURE__ */ new Map(), new TextEncoder().encode(ops.join("\n")));
|
|
213
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* §12.5.6.6 — a free-text annotation with no appearance: its contents, a line
|
|
223
|
+
* to each line break, from the top of its `/Rect` in the size and colour its
|
|
224
|
+
* `/DA` asks. Left undrawn the words were simply gone: bug1865341.pdf's one
|
|
225
|
+
* note, "Załącznik", and nothing else on the page.
|
|
226
|
+
*/
|
|
227
|
+
function freeText(file, annot) {
|
|
228
|
+
const text = freeTextContents(file, annot);
|
|
229
|
+
const rect = rectangle(file.get(annot, "Rect"));
|
|
230
|
+
if (text === void 0 || !rect) return void 0;
|
|
231
|
+
const da = defaultAppearance(file, annot);
|
|
232
|
+
const box = inset(rect, 2);
|
|
233
|
+
const size = da.size > 0 ? da.size : 12;
|
|
234
|
+
const codes = codesOf(text);
|
|
235
|
+
const ops = [
|
|
236
|
+
"q",
|
|
237
|
+
"BT",
|
|
238
|
+
`/${FREE_TEXT_FONT} ${num(size)} Tf`,
|
|
239
|
+
`${da.color} rg`
|
|
240
|
+
];
|
|
241
|
+
text.split(/\r\n|[\r\n]/u).forEach((line, i) => {
|
|
242
|
+
const y = box[3] - size * (.8 + i * 1.16);
|
|
243
|
+
const hex = visualOrder(line).map((ch) => (codes.get(ch) ?? 0).toString(16).padStart(2, "0")).join("");
|
|
244
|
+
ops.push(`1 0 0 1 ${num(box[0])} ${num(y)} Tm`, `<${hex}> Tj`);
|
|
245
|
+
});
|
|
246
|
+
ops.push("ET", "Q");
|
|
247
|
+
return new PdfStream(/* @__PURE__ */ new Map(), new TextEncoder().encode(ops.join("\n")));
|
|
248
|
+
}
|
|
249
|
+
/** UAX #9 — a line's characters in the order they stand on the page, left to right. */
|
|
250
|
+
function visualOrder(line) {
|
|
251
|
+
const chars = [...line];
|
|
252
|
+
if (!hasBidiCharacters(line)) return chars;
|
|
253
|
+
const { levels } = analyzeString(line);
|
|
254
|
+
return reorderVisual(levels).map((i) => chars[i] ?? "");
|
|
255
|
+
}
|
|
256
|
+
/** The resource name the face of a drawn free-text annotation goes by. */
|
|
257
|
+
var FREE_TEXT_FONT = "FreeText";
|
|
258
|
+
/** §7.9.2.2 — an annotation's `/Contents`, decoded as the text string it is. */
|
|
259
|
+
function freeTextContents(file, annot) {
|
|
260
|
+
const found = file.get(annot, "Contents");
|
|
261
|
+
const raw = found instanceof PdfHexString ? String.fromCharCode(...found.bytes) : typeof found === "string" ? found : void 0;
|
|
262
|
+
if (raw === void 0) return void 0;
|
|
263
|
+
const text = textString(raw).replace(/\s+$/u, "");
|
|
264
|
+
return text.length > 0 ? text : void 0;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* §7.9.2.2 — a text string's characters: UTF-16BE behind its byte-order mark,
|
|
268
|
+
* UTF-8 behind its own, and otherwise one byte to a character.
|
|
269
|
+
*/
|
|
270
|
+
function textString(raw) {
|
|
271
|
+
if (raw.startsWith("þÿ")) {
|
|
272
|
+
let out = "";
|
|
273
|
+
for (let i = 2; i + 1 < raw.length; i += 2) out += String.fromCharCode(raw.charCodeAt(i) << 8 | raw.charCodeAt(i + 1));
|
|
274
|
+
return out;
|
|
275
|
+
}
|
|
276
|
+
if (raw.startsWith("")) return new TextDecoder("utf-8").decode(Uint8Array.from([...raw.slice(3)].map((c) => c.charCodeAt(0))));
|
|
277
|
+
return raw;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* A code for each character a text shows, in the order it first shows them —
|
|
281
|
+
* so a simple face can set a text no standard encoding covers.
|
|
282
|
+
*/
|
|
283
|
+
function codesOf(text) {
|
|
284
|
+
const codes = /* @__PURE__ */ new Map();
|
|
285
|
+
for (const ch of text.replace(/[\r\n]/gu, "")) if (!codes.has(ch) && codes.size < 255) codes.set(ch, codes.size + 1);
|
|
286
|
+
return codes;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* The Helvetica a drawn free-text annotation is set in, its encoding spelling
|
|
290
|
+
* each code's character out as a glyph name (`uni0142` for ł) — which is how
|
|
291
|
+
* the text reads back as what it is.
|
|
292
|
+
*/
|
|
293
|
+
function spelledFace(text) {
|
|
294
|
+
const differences = [1];
|
|
295
|
+
for (const ch of codesOf(text).keys()) {
|
|
296
|
+
const cp = ch.codePointAt(0) ?? 32;
|
|
297
|
+
differences.push(new PdfName(`uni${cp.toString(16).toUpperCase().padStart(4, "0")}`));
|
|
298
|
+
}
|
|
299
|
+
return new Map([
|
|
300
|
+
["Type", new PdfName("Font")],
|
|
301
|
+
["Subtype", new PdfName("Type1")],
|
|
302
|
+
["BaseFont", new PdfName("Helvetica")],
|
|
303
|
+
["Encoding", new Map([["Differences", differences]])]
|
|
304
|
+
]);
|
|
305
|
+
}
|
|
214
306
|
/** §12.7.4.3 — the field is a multi-line text box (bit 13 of `/Ff`). */
|
|
215
307
|
var MULTILINE = 4096;
|
|
216
308
|
/**
|
|
@@ -8,5 +8,23 @@ export interface Appearance {
|
|
|
8
8
|
readonly ctm: Matrix;
|
|
9
9
|
/** The appearance's `/Resources`, when it states its own. */
|
|
10
10
|
readonly resources: PdfDict | undefined;
|
|
11
|
+
/** §8.10.1 — the form's `/BBox`, in its own space, which clips what it paints. */
|
|
12
|
+
readonly bbox?: readonly [number, number, number, number];
|
|
11
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* §8.10.1 — an appearance's content as it paints: clipped to its `/BBox`.
|
|
16
|
+
*
|
|
17
|
+
* The box is the form's clip, not a hint. A form-filling tool that wrote its
|
|
18
|
+
* field backgrounds in PAGE coordinates inside a box forty points wide paints
|
|
19
|
+
* them nowhere any viewer shows — and painted anyway, with the fill colour it
|
|
20
|
+
* never set, bug1669099.pdf's form came back with three black slabs over its
|
|
21
|
+
* letterhead and its terms of payment, each twice as far across the sheet as
|
|
22
|
+
* the field it belonged to. The clip is installed the way the content would
|
|
23
|
+
* install one, so every pass that reads the appearance sees the same page.
|
|
24
|
+
*
|
|
25
|
+
* @param file The owning file.
|
|
26
|
+
* @param appearance The appearance.
|
|
27
|
+
* @returns Its content, preceded by the clip to its box where it has one.
|
|
28
|
+
*/
|
|
29
|
+
export declare function appearanceContent(file: PdfFile, appearance: Appearance): Uint8Array;
|
|
12
30
|
export declare function collectPageAppearances(file: PdfFile, page: PdfPage): Array<Appearance>;
|
|
@@ -2,6 +2,32 @@ import { PDF_NULL, PdfName, PdfStream } from "../pdf/objects.js";
|
|
|
2
2
|
import { IDENTITY, multiply } from "./content.js";
|
|
3
3
|
import { drawnAppearance, drawnResources, textMarkupOf } from "./annot-draw.js";
|
|
4
4
|
//#region src/pdf-reader/annots.ts
|
|
5
|
+
/**
|
|
6
|
+
* §8.10.1 — an appearance's content as it paints: clipped to its `/BBox`.
|
|
7
|
+
*
|
|
8
|
+
* The box is the form's clip, not a hint. A form-filling tool that wrote its
|
|
9
|
+
* field backgrounds in PAGE coordinates inside a box forty points wide paints
|
|
10
|
+
* them nowhere any viewer shows — and painted anyway, with the fill colour it
|
|
11
|
+
* never set, bug1669099.pdf's form came back with three black slabs over its
|
|
12
|
+
* letterhead and its terms of payment, each twice as far across the sheet as
|
|
13
|
+
* the field it belonged to. The clip is installed the way the content would
|
|
14
|
+
* install one, so every pass that reads the appearance sees the same page.
|
|
15
|
+
*
|
|
16
|
+
* @param file The owning file.
|
|
17
|
+
* @param appearance The appearance.
|
|
18
|
+
* @returns Its content, preceded by the clip to its box where it has one.
|
|
19
|
+
*/
|
|
20
|
+
function appearanceContent(file, appearance) {
|
|
21
|
+
const data = file.streamData(appearance.stream);
|
|
22
|
+
const box = appearance.bbox;
|
|
23
|
+
if (!box) return data;
|
|
24
|
+
const [x0, y0, x1, y1] = box;
|
|
25
|
+
const clip = new TextEncoder().encode(`${String(x0)} ${String(y0)} ${String(x1 - x0)} ${String(y1 - y0)} re W n\n`);
|
|
26
|
+
const out = new Uint8Array(clip.length + data.length);
|
|
27
|
+
out.set(clip, 0);
|
|
28
|
+
out.set(data, clip.length);
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
5
31
|
/** §12.5.3 `/F` — the annotation is not painted at all. */
|
|
6
32
|
var FLAG_HIDDEN = 2;
|
|
7
33
|
var FLAG_NOVIEW = 32;
|
|
@@ -35,6 +61,7 @@ function collectPageAppearances(file, page) {
|
|
|
35
61
|
const annots = file.get(page.dict, "Annots");
|
|
36
62
|
if (!Array.isArray(annots)) return [];
|
|
37
63
|
const out = [];
|
|
64
|
+
const regenerate = needsAppearances(file);
|
|
38
65
|
for (const entry of annots) {
|
|
39
66
|
const annot = file.resolve(entry);
|
|
40
67
|
if (!(annot instanceof Map)) continue;
|
|
@@ -44,6 +71,18 @@ function collectPageAppearances(file, page) {
|
|
|
44
71
|
const flags = file.get(annot, "F");
|
|
45
72
|
if (typeof flags === "number" && (flags & FLAG_HIDDEN || flags & FLAG_NOVIEW)) continue;
|
|
46
73
|
const stream = normalAppearance(file, annot);
|
|
74
|
+
const typed = regenerate && isWidget(file, annot) ? drawnAppearance(file, annot) : void 0;
|
|
75
|
+
if (typed && typedField(file, annot)) {
|
|
76
|
+
const kept = stream ? withoutVariableText(file, stream) : void 0;
|
|
77
|
+
const rect = rectangle(file.get(annot, "Rect"));
|
|
78
|
+
if (kept && rect) out.push(fitted(file, kept, rect));
|
|
79
|
+
out.push({
|
|
80
|
+
stream: typed,
|
|
81
|
+
ctm: IDENTITY,
|
|
82
|
+
resources: drawnResources(file, annot)
|
|
83
|
+
});
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
47
86
|
if (!stream) {
|
|
48
87
|
const drawn = drawnAppearance(file, annot);
|
|
49
88
|
if (drawn) out.push({
|
|
@@ -55,17 +94,55 @@ function collectPageAppearances(file, page) {
|
|
|
55
94
|
}
|
|
56
95
|
const rect = rectangle(file.get(annot, "Rect"));
|
|
57
96
|
if (!rect) continue;
|
|
58
|
-
|
|
59
|
-
const bbox = rectangle(file.get(stream.dict, "BBox"));
|
|
60
|
-
const resources = file.get(stream.dict, "Resources");
|
|
61
|
-
out.push({
|
|
62
|
-
stream,
|
|
63
|
-
ctm: multiply(matrix, fitToRect(bbox, matrix, rect)),
|
|
64
|
-
resources: resources instanceof Map ? resources : void 0
|
|
65
|
-
});
|
|
97
|
+
out.push(fitted(file, stream, rect));
|
|
66
98
|
}
|
|
67
99
|
return out;
|
|
68
100
|
}
|
|
101
|
+
/** An appearance stream placed on its annotation's `/Rect` (§12.5.5). */
|
|
102
|
+
function fitted(file, stream, rect) {
|
|
103
|
+
const matrix = matrixOf(file, stream.dict);
|
|
104
|
+
const bbox = rectangle(file.get(stream.dict, "BBox"));
|
|
105
|
+
const resources = file.get(stream.dict, "Resources");
|
|
106
|
+
return {
|
|
107
|
+
stream,
|
|
108
|
+
ctm: multiply(matrix, fitToRect(bbox, matrix, rect)),
|
|
109
|
+
resources: resources instanceof Map ? resources : void 0,
|
|
110
|
+
...bbox ? { bbox } : {}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/** §12.7.2 — whether the form asks the viewer to build its fields' appearances. */
|
|
114
|
+
function needsAppearances(file) {
|
|
115
|
+
const acro = file.get(file.catalog, "AcroForm");
|
|
116
|
+
return acro instanceof Map && file.get(acro, "NeedAppearances") === true;
|
|
117
|
+
}
|
|
118
|
+
function isWidget(file, annot) {
|
|
119
|
+
const subtype = file.get(annot, "Subtype");
|
|
120
|
+
return subtype instanceof PdfName && subtype.value === "Widget";
|
|
121
|
+
}
|
|
122
|
+
/** Whether a widget belongs to a text field — `/FT /Tx`, on it or up its field tree. */
|
|
123
|
+
function typedField(file, annot) {
|
|
124
|
+
let node = annot;
|
|
125
|
+
for (let depth = 0; node && depth < 32; depth++) {
|
|
126
|
+
const type = file.get(node, "FT");
|
|
127
|
+
if (type instanceof PdfName) return type.value === "Tx";
|
|
128
|
+
const parent = file.get(node, "Parent");
|
|
129
|
+
node = parent instanceof Map ? parent : void 0;
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* §12.7.3.3 — an appearance with its variable text taken out: the part it
|
|
135
|
+
* marks `/Tx BMC … EMC`, which is what a viewer rebuilds from the value.
|
|
136
|
+
* bug1844583.pdf's stale appearance still reads "Dlrow Olleh" there, over a
|
|
137
|
+
* field whose value is "Hello World".
|
|
138
|
+
*/
|
|
139
|
+
function withoutVariableText(file, stream) {
|
|
140
|
+
const stripped = new TextDecoder("latin1").decode(file.streamData(stream)).replace(/\/Tx\s+BMC[\s\S]*?EMC/gu, "");
|
|
141
|
+
const dict = new Map(stream.dict);
|
|
142
|
+
dict.delete("Filter");
|
|
143
|
+
dict.delete("DecodeParms");
|
|
144
|
+
return new PdfStream(dict, Uint8Array.from([...stripped].map((c) => c.charCodeAt(0))));
|
|
145
|
+
}
|
|
69
146
|
/** §12.5.5 `/AP` `/N` — the normal appearance, through `/AS` when it is a set. */
|
|
70
147
|
function normalAppearance(file, annot) {
|
|
71
148
|
const ap = file.get(annot, "AP");
|
|
@@ -149,4 +226,4 @@ function rectangle(v) {
|
|
|
149
226
|
];
|
|
150
227
|
}
|
|
151
228
|
//#endregion
|
|
152
|
-
export { collectPageAppearances };
|
|
229
|
+
export { appearanceContent, collectPageAppearances };
|
|
@@ -45,8 +45,11 @@ function parseToUnicodeCMap(bytes) {
|
|
|
45
45
|
const hiN = bytesToInt(hi.bytes);
|
|
46
46
|
const dst = lexer.nextToken();
|
|
47
47
|
if (dst.kind === "hexstr") {
|
|
48
|
-
const base =
|
|
49
|
-
|
|
48
|
+
const base = utf16be(dst.bytes);
|
|
49
|
+
for (let i = 0; loN + i <= hiN && i < MAX_RANGE; i++) {
|
|
50
|
+
const text = stated(incString(base, i));
|
|
51
|
+
if (text !== void 0) map.set(loN + i, text);
|
|
52
|
+
}
|
|
50
53
|
} else if (dst.kind === "arrayOpen") {
|
|
51
54
|
let i = 0;
|
|
52
55
|
for (;;) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { ColorSpaceInfo, GsPaint } from './shading.js';
|
|
1
|
+
import { ColorSpaceInfo, GsPaint, PageGradient } from './shading.js';
|
|
2
2
|
import { TextMarkup } from './annot-draw.js';
|
|
3
|
-
import { ShapeGradient } from '../core/vector.js';
|
|
4
3
|
import { PdfDict, PdfStream } from '../pdf/objects.js';
|
|
5
4
|
/**
|
|
6
5
|
* A page font as the interpreter needs it (built from the font dictionaries in
|
|
@@ -136,6 +135,12 @@ export interface TextRun {
|
|
|
136
135
|
* leaves it to the picture.
|
|
137
136
|
*/
|
|
138
137
|
readonly invisible?: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* §12.5.5 — drawn by an annotation's appearance rather than by the page: a
|
|
140
|
+
* field's value, a button's caption, a tick. It stands in the annotation's
|
|
141
|
+
* box, which is placed where the page has it, not in the page's reading.
|
|
142
|
+
*/
|
|
143
|
+
readonly annotation?: boolean;
|
|
139
144
|
/**
|
|
140
145
|
* §9.3.6 — the colour the glyphs are STROKED in, when the rendering mode
|
|
141
146
|
* asks for a stroke, and how wide the pen is.
|
|
@@ -245,7 +250,7 @@ export interface VectorPlacement {
|
|
|
245
250
|
/** Fill colour (6-hex), present iff the path is filled (`f` / `F` / `f*` / `B` / `b`). */
|
|
246
251
|
readonly fillHex?: string;
|
|
247
252
|
/** Shading pattern, present iff filled with one (EP16c). */
|
|
248
|
-
readonly gradient?:
|
|
253
|
+
readonly gradient?: PageGradient;
|
|
249
254
|
/** §11.6.4.4 `/ca` — how opaque the fill is, when the page asked for less. */
|
|
250
255
|
readonly alpha?: number;
|
|
251
256
|
/**
|
|
@@ -271,10 +276,18 @@ export interface VectorPlacement {
|
|
|
271
276
|
* known by walking into it; the `fillHex` beside this is not the fill.
|
|
272
277
|
*/
|
|
273
278
|
readonly patternName?: string;
|
|
279
|
+
/** §8.7.3.3 — the colour `scn` gave an uncoloured pattern, where it gave one. */
|
|
280
|
+
readonly patternPaint?: string;
|
|
274
281
|
/** Stroke colour (6-hex), present iff the path is stroked (`S` / `s` / `B` / `b`) — EP11. */
|
|
275
282
|
readonly strokeHex?: string;
|
|
276
283
|
/** Stroke width in page-space points — EP11. */
|
|
277
284
|
readonly lineWidth?: number;
|
|
285
|
+
/** §8.4.3.6 — the stroke's dash lengths in page-space points, where it is dashed. */
|
|
286
|
+
readonly dash?: ReadonlyArray<number>;
|
|
287
|
+
/** §8.4.3.3 — the stroke's cap, where it is not the butt cap every line starts with. */
|
|
288
|
+
readonly cap?: 'round' | 'square';
|
|
289
|
+
/** §11.6.4.4 `/CA` — how opaque the stroke is, when the page asked for less than all. */
|
|
290
|
+
readonly strokeAlpha?: number;
|
|
278
291
|
readonly mcid?: number;
|
|
279
292
|
}
|
|
280
293
|
/** Everything {@link interpretContent} extracts from one page's content stream. */
|
|
@@ -357,7 +370,7 @@ export type Matrix = readonly [number, number, number, number, number, number];
|
|
|
357
370
|
export declare const IDENTITY: Matrix;
|
|
358
371
|
/** Compose two {@link Matrix matrices}: `a` applied first, then `b`. */
|
|
359
372
|
export declare function multiply(a: Matrix, b: Matrix): Matrix;
|
|
360
|
-
export declare function interpretContent(bytes: Uint8Array, fonts: ReadonlyMap<string, ContentFont>, initialCtm?: Matrix, shadings?: ReadonlyMap<string,
|
|
373
|
+
export declare function interpretContent(bytes: Uint8Array, fonts: ReadonlyMap<string, ContentFont>, initialCtm?: Matrix, shadings?: ReadonlyMap<string, PageGradient>, alphas?: ReadonlyMap<string, GsPaint>, spaces?: ReadonlyMap<string, ColorSpaceInfo>, hiddenOc?: ReadonlySet<string>): InterpretResult;
|
|
361
374
|
/**
|
|
362
375
|
* Whether a string is wholly right-to-left: at least one letter of an RTL
|
|
363
376
|
* script and nothing of any other, spaces and joiners aside.
|