reamkit 1.30.0 → 1.31.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 +60 -24
- package/dist/esm/core/converter/project.js +3 -1
- package/dist/esm/core/crypto/offcrypto.js +1 -1
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +63 -0
- package/dist/esm/core/font/index.d.ts +2 -0
- package/dist/esm/core/font/ttf-build.d.ts +113 -0
- package/dist/esm/core/font/ttf-build.js +1224 -0
- package/dist/esm/core/font/ttf-subset.d.ts +19 -0
- package/dist/esm/core/font/ttf-subset.js +14 -1
- package/dist/esm/core/fonts/provider.d.ts +17 -0
- package/dist/esm/core/fonts/provider.js +27 -2
- package/dist/esm/core/fonts/remote-fonts.js +8 -1
- package/dist/esm/core/ir/flow.d.ts +71 -1
- package/dist/esm/core/numbering/index.d.ts +1 -1
- package/dist/esm/core/numbering/state.d.ts +11 -1
- package/dist/esm/core/numbering/state.js +10 -1
- package/dist/esm/core/style-cascade/resolver.js +35 -4
- package/dist/esm/core/style-cascade/types.d.ts +19 -1
- package/dist/esm/core/style-cascade/types.js +3 -0
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/layout/page-doc.d.ts +10 -3
- package/dist/esm/layout/styled-layout.d.ts +48 -7
- package/dist/esm/layout/styled-layout.js +881 -119
- package/dist/esm/layout/turned-section.d.ts +38 -0
- package/dist/esm/layout/turned-section.js +193 -0
- package/dist/esm/pdf/styled-page-emitter.js +78 -2
- package/dist/esm/pdf-reader/cff-outline.d.ts +27 -0
- package/dist/esm/pdf-reader/cff-outline.js +169 -21
- package/dist/esm/pdf-reader/content.d.ts +53 -0
- package/dist/esm/pdf-reader/content.js +9 -1
- package/dist/esm/pdf-reader/display.d.ts +36 -0
- package/dist/esm/pdf-reader/display.js +66 -1
- package/dist/esm/pdf-reader/document.js +5 -1
- package/dist/esm/pdf-reader/embedded-fonts.d.ts +2 -1
- package/dist/esm/pdf-reader/embedded-fonts.js +32 -2
- package/dist/esm/pdf-reader/encodings.d.ts +8 -0
- package/dist/esm/pdf-reader/encodings.js +25 -3
- package/dist/esm/pdf-reader/face-outlines.d.ts +78 -0
- package/dist/esm/pdf-reader/face-outlines.js +362 -0
- package/dist/esm/pdf-reader/figures.d.ts +52 -0
- package/dist/esm/pdf-reader/figures.js +433 -0
- package/dist/esm/pdf-reader/flow-build.d.ts +131 -5
- package/dist/esm/pdf-reader/flow-build.js +345 -26
- package/dist/esm/pdf-reader/font.js +321 -36
- package/dist/esm/pdf-reader/glyf-outline.d.ts +33 -0
- package/dist/esm/pdf-reader/glyf-outline.js +135 -1
- package/dist/esm/pdf-reader/glyph-names.js +154 -1
- package/dist/esm/pdf-reader/layout.d.ts +93 -0
- package/dist/esm/pdf-reader/layout.js +1703 -214
- package/dist/esm/pdf-reader/page-numbers.d.ts +53 -0
- package/dist/esm/pdf-reader/page-numbers.js +167 -0
- package/dist/esm/pdf-reader/tagged.js +182 -23
- package/dist/esm/pdf-reader/text.d.ts +6 -3
- package/dist/esm/pdf-reader/text.js +98 -9
- package/dist/esm/pdf-reader/type1-outline.d.ts +11 -0
- package/dist/esm/pdf-reader/type1-outline.js +63 -8
- package/dist/esm/pdf-reader/vector.js +71 -1
- package/dist/esm/word/doc/doc-reader.js +6 -2
- package/dist/esm/word/doc/doc-text.d.ts +6 -0
- package/dist/esm/word/doc/doc-text.js +19 -1
- package/dist/esm/word/document-parser.d.ts +2 -2
- package/dist/esm/word/document-parser.js +11 -1
- package/dist/esm/word/docx-reader.js +5 -3
- package/dist/esm/word/docx-writer.js +207 -21
- package/dist/esm/word/drawing-parser.d.ts +5 -3
- package/dist/esm/word/drawing-parser.js +49 -8
- package/dist/esm/word/font-embed.d.ts +30 -0
- package/dist/esm/word/font-embed.js +173 -0
- package/dist/esm/word/font-table.d.ts +10 -0
- package/dist/esm/word/font-table.js +13 -1
- package/dist/esm/word/index.js +1 -1
- package/dist/esm/word/numbering-parser.d.ts +3 -1
- package/dist/esm/word/numbering-parser.js +2 -1
- package/dist/esm/word/paragraph-properties.d.ts +7 -6
- package/dist/esm/word/paragraph-properties.js +14 -2
- package/dist/esm/word/run-properties.js +26 -0
- package/package.json +8 -3
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
//#region src/pdf-reader/figures.ts
|
|
2
|
+
/** No thicker than this, a path is a line. */
|
|
3
|
+
var THIN_PT = 1.5;
|
|
4
|
+
/**
|
|
5
|
+
* A line longer than this many ems RULES the page: the rule over a caption,
|
|
6
|
+
* the line under a heading, a table's border. A drawing's own strokes are
|
|
7
|
+
* shorter, and a long one inside a drawing — a chart's axis — is taken with
|
|
8
|
+
* the drawing it stands in once that is found.
|
|
9
|
+
*/
|
|
10
|
+
var RULE_EM = 3;
|
|
11
|
+
/** How close, in ems, two paths stand to be parts of one drawing. */
|
|
12
|
+
var JOIN_EM = .5;
|
|
13
|
+
/** The fewest paths a figure is drawn with: a box and a frame are not one. */
|
|
14
|
+
var MIN_PATHS = 6;
|
|
15
|
+
/** Past this share of the sheet, a drawing is the page's ground or frame. */
|
|
16
|
+
var MOST_OF_PAGE = .5;
|
|
17
|
+
/** How far across a figure a line of its words may run and still be a label. */
|
|
18
|
+
var LABEL_FILL = .6;
|
|
19
|
+
/** Past this many lines that run across it, what a drawing holds is prose. */
|
|
20
|
+
var PROSE_LINES = 2;
|
|
21
|
+
/**
|
|
22
|
+
* How far apart, in ems of the text, two parts of one figure may stand. The
|
|
23
|
+
* parts of comments.pdf's Figure 6 stand twenty-six points apart at 9pt.
|
|
24
|
+
*/
|
|
25
|
+
var NEAR_EM = 3;
|
|
26
|
+
/** Type smaller than this share of the text's is a drawing's, not the text's. */
|
|
27
|
+
var SMALL_SHARE = .85;
|
|
28
|
+
/** How much larger than the text a figure's words may be set. */
|
|
29
|
+
var LARGEST_LABEL = 1.2;
|
|
30
|
+
/** How far, in its own ems, small type may stand off a drawing and label it. */
|
|
31
|
+
var CAPTION_EM = 2;
|
|
32
|
+
/** What a glyph mapped to no character reads as (see `UNMAPPED` in ./layout). */
|
|
33
|
+
var UNMAPPED = "�";
|
|
34
|
+
/** Where an annotation's appearance stands in the page's painting order. */
|
|
35
|
+
var ANNOTATION_ORDER = Number.MAX_SAFE_INTEGER;
|
|
36
|
+
/**
|
|
37
|
+
* How many ems of its own type a line of words on a drawing may run before it
|
|
38
|
+
* is a line of prose, however wide the drawing: a label is a word or three.
|
|
39
|
+
*/
|
|
40
|
+
var LABEL_EM = 15;
|
|
41
|
+
/** Past this many paths a page is artwork through and through. */
|
|
42
|
+
var MOST_PATHS = 4e3;
|
|
43
|
+
/**
|
|
44
|
+
* The figures a page draws.
|
|
45
|
+
*
|
|
46
|
+
* A figure is paths that stand together — each within half an em of the next —
|
|
47
|
+
* and DRAW something: a curve or a slanted line among them. Straight lines
|
|
48
|
+
* and boxes alone are a ruling, which is a table's or a form's, and a page
|
|
49
|
+
* reads those as text. It has words set on it, or it is only artwork, which
|
|
50
|
+
* the page anchors as it always has; and none of its words run across it
|
|
51
|
+
* line after line, which is prose set on a ground, not a label.
|
|
52
|
+
*
|
|
53
|
+
* @param vectors The page's paths, where the page shows them.
|
|
54
|
+
* @param images The page's pictures, likewise.
|
|
55
|
+
* @param runs The page's text, likewise.
|
|
56
|
+
* @param sheet The page's size.
|
|
57
|
+
* @returns The figures, top of the page first.
|
|
58
|
+
*/
|
|
59
|
+
function pageFigures(vectors, images, runs, sheet) {
|
|
60
|
+
const words = runs.filter((r) => r.text.replaceAll(UNMAPPED, "").trim().length > 0);
|
|
61
|
+
const traced = runs.filter((r) => r.type3 !== true && tracedRun(r));
|
|
62
|
+
if (words.length === 0 || vectors.length === 0 || vectors.length > MOST_PATHS) return [];
|
|
63
|
+
const size = median(words.map((r) => r.fontSizePt).filter((s) => s > 0)) || 10;
|
|
64
|
+
const gap = size * JOIN_EM;
|
|
65
|
+
const pageArea = Math.max(1, sheet.width * sheet.height);
|
|
66
|
+
const huge = (b) => (b.maxX - b.minX) * (b.maxY - b.minY) > pageArea * MOST_OF_PAGE;
|
|
67
|
+
const thin = (v) => Math.min(v.maxX - v.minX, v.maxY - v.minY) <= THIN_PT;
|
|
68
|
+
const rule = (v) => thin(v) && Math.max(v.maxX - v.minX, v.maxY - v.minY) > size * RULE_EM;
|
|
69
|
+
const lettering = words.filter((r) => r.type3 === true).map(inkOf);
|
|
70
|
+
const letter = (v) => v.glyph === true || lettering.some((b) => (v.minX + v.maxX) / 2 >= b.minX - 1 && (v.minX + v.maxX) / 2 <= b.maxX + 1 && (v.minY + v.maxY) / 2 >= b.minY - 1 && (v.minY + v.maxY) / 2 <= b.maxY + 1);
|
|
71
|
+
const own = vectors.filter((v) => v.orderKey[0] !== ANNOTATION_ORDER);
|
|
72
|
+
const seeds = own.filter((v) => !rule(v) && !huge(v) && !letter(v));
|
|
73
|
+
const parent = seeds.map((_, k) => k);
|
|
74
|
+
const find = (k) => {
|
|
75
|
+
while (parent[k] !== k) {
|
|
76
|
+
parent[k] = parent[parent[k]];
|
|
77
|
+
k = parent[k];
|
|
78
|
+
}
|
|
79
|
+
return k;
|
|
80
|
+
};
|
|
81
|
+
const order = seeds.map((_, k) => k).sort((a, b) => seeds[a].minX - seeds[b].minX);
|
|
82
|
+
for (let i = 0; i < order.length; i++) {
|
|
83
|
+
const a = seeds[order[i]];
|
|
84
|
+
for (let j = i + 1; j < order.length; j++) {
|
|
85
|
+
const b = seeds[order[j]];
|
|
86
|
+
if (b.minX > a.maxX + gap) break;
|
|
87
|
+
if (b.minY <= a.maxY + gap && b.maxY >= a.minY - gap) parent[find(order[i])] = find(order[j]);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const clusters = /* @__PURE__ */ new Map();
|
|
91
|
+
seeds.forEach((v, k) => {
|
|
92
|
+
const root = find(k);
|
|
93
|
+
const members = clusters.get(root);
|
|
94
|
+
if (members) members.push(v);
|
|
95
|
+
else clusters.set(root, [v]);
|
|
96
|
+
});
|
|
97
|
+
const found = [];
|
|
98
|
+
const pieces = [];
|
|
99
|
+
for (const members of clusters.values()) {
|
|
100
|
+
const box = boxOf(members);
|
|
101
|
+
if (huge(box)) continue;
|
|
102
|
+
if (members.length < MIN_PATHS || !(members.some(draws) || barChart(members))) pieces.push({
|
|
103
|
+
box,
|
|
104
|
+
vectors: members
|
|
105
|
+
});
|
|
106
|
+
else found.push({
|
|
107
|
+
box,
|
|
108
|
+
vectors: members
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (found.length === 0) return [];
|
|
112
|
+
const near = size * NEAR_EM;
|
|
113
|
+
const text = words.filter((r) => r.type3 !== true && r.fontSizePt >= size * SMALL_SHARE);
|
|
114
|
+
const textBetween = (a, b) => {
|
|
115
|
+
const both = union(a, b);
|
|
116
|
+
return text.some((r) => {
|
|
117
|
+
const ink = inkOf(r);
|
|
118
|
+
return touches(both, ink, 0) && !touches(a, ink, 0) && !touches(b, ink, 0);
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
const rules = own.filter(rule);
|
|
122
|
+
const taken = /* @__PURE__ */ new Set();
|
|
123
|
+
let changed = true;
|
|
124
|
+
while (changed) {
|
|
125
|
+
changed = false;
|
|
126
|
+
for (let i = 0; i < found.length; i++) {
|
|
127
|
+
for (let j = found.length - 1; j > i; j--) {
|
|
128
|
+
const [a, b] = [found[i].box, found[j].box];
|
|
129
|
+
if (!touches(a, b, gap) && !(touches(a, b, near) && !textBetween(a, b))) continue;
|
|
130
|
+
found[i].vectors.push(...found[j].vectors);
|
|
131
|
+
found[i].box = union(a, b);
|
|
132
|
+
found.splice(j, 1);
|
|
133
|
+
changed = true;
|
|
134
|
+
}
|
|
135
|
+
for (let j = pieces.length - 1; j >= 0; j--) {
|
|
136
|
+
const [a, b] = [found[i].box, pieces[j].box];
|
|
137
|
+
if (!touches(a, b, gap) && !(touches(a, b, near) && !textBetween(a, b))) continue;
|
|
138
|
+
found[i].vectors.push(...pieces[j].vectors);
|
|
139
|
+
found[i].box = union(a, b);
|
|
140
|
+
pieces.splice(j, 1);
|
|
141
|
+
changed = true;
|
|
142
|
+
}
|
|
143
|
+
for (const r of rules) {
|
|
144
|
+
if (taken.has(r) || !mostlyWithin(r, found[i].box)) continue;
|
|
145
|
+
taken.add(r);
|
|
146
|
+
found[i].vectors.push(r);
|
|
147
|
+
found[i].box = union(found[i].box, r);
|
|
148
|
+
changed = true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const figures = [];
|
|
153
|
+
const labelled = /* @__PURE__ */ new Set();
|
|
154
|
+
const lettered = /* @__PURE__ */ new Set();
|
|
155
|
+
for (const f of found) {
|
|
156
|
+
const free = [...words, ...traced].filter((r) => r.type3 !== true && !labelled.has(r));
|
|
157
|
+
const labels = free.filter((r) => touches(f.box, inkOf(r), 0));
|
|
158
|
+
let box = labels.reduce((b, r) => union(b, inkOf(r)), f.box);
|
|
159
|
+
for (let grown = true; grown;) {
|
|
160
|
+
grown = false;
|
|
161
|
+
for (const r of free) {
|
|
162
|
+
if (labels.includes(r) || r.fontSizePt >= size * SMALL_SHARE) continue;
|
|
163
|
+
const ink = inkOf(r);
|
|
164
|
+
if (!touches(box, ink, r.fontSizePt * CAPTION_EM) || ink.minX < box.minX - r.fontSizePt) continue;
|
|
165
|
+
if (ink.maxX > box.maxX + r.fontSizePt) continue;
|
|
166
|
+
labels.push(r);
|
|
167
|
+
box = union(box, ink);
|
|
168
|
+
grown = true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (labels.length === 0 || runsAcross(labels, box, size)) continue;
|
|
172
|
+
if (labels.some((r) => r.fontSizePt > size * LARGEST_LABEL)) continue;
|
|
173
|
+
const spaces = runs.filter((r) => r.text.trim().length === 0 && r.text.length > 0 && touches(box, inkOf(r), 0));
|
|
174
|
+
for (const r of [...labels, ...spaces]) labelled.add(r);
|
|
175
|
+
const held = images.filter((img) => mostlyInside(img, box));
|
|
176
|
+
const letters = lettersOf(labels.filter(tracedRun), vectors).filter((v) => !lettered.has(v));
|
|
177
|
+
for (const v of letters) lettered.add(v);
|
|
178
|
+
figures.push({
|
|
179
|
+
...box,
|
|
180
|
+
vectors: [...f.vectors, ...letters],
|
|
181
|
+
images: held,
|
|
182
|
+
labels: [...labels, ...spaces]
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return figures.sort((a, b) => b.maxY - a.maxY);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Whether a run is lettering the file states no character for: glyphs traced
|
|
189
|
+
* as paths (see `PdfVector.glyph`), and a label only by where they stand.
|
|
190
|
+
*
|
|
191
|
+
* @param run A run of the page's text.
|
|
192
|
+
*/
|
|
193
|
+
function tracedRun(run) {
|
|
194
|
+
return run.text.includes(UNMAPPED) && run.text.replaceAll(UNMAPPED, "").trim() === "";
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* The glyphs traced for some runs: the paths traced as glyphs whose middles
|
|
198
|
+
* stand in the runs' ink.
|
|
199
|
+
*
|
|
200
|
+
* @param runs The traced runs.
|
|
201
|
+
* @param paths The page's paths.
|
|
202
|
+
*/
|
|
203
|
+
function lettersOf(runs, paths) {
|
|
204
|
+
const inks = runs.map(inkOf);
|
|
205
|
+
return paths.filter((v) => {
|
|
206
|
+
if (v.glyph !== true) return false;
|
|
207
|
+
const x = (v.minX + v.maxX) / 2;
|
|
208
|
+
const y = (v.minY + v.maxY) / 2;
|
|
209
|
+
return inks.some((b) => x >= b.minX - 1 && x <= b.maxX + 1 && y >= b.minY - 1 && y <= b.maxY + 1);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* A figure's paths with the traced letters painted one after another joined
|
|
214
|
+
* into one: the letters of a label are one shape, where a shape a letter made
|
|
215
|
+
* a chart of six hundred.
|
|
216
|
+
*
|
|
217
|
+
* @param paths The figure's paths, in the order the page painted them.
|
|
218
|
+
* @returns The paths to draw, in that order.
|
|
219
|
+
*/
|
|
220
|
+
function joinLetters(paths) {
|
|
221
|
+
const out = [];
|
|
222
|
+
let letters = [];
|
|
223
|
+
const flush = () => {
|
|
224
|
+
const [first, ...rest] = letters;
|
|
225
|
+
if (first !== void 0) out.push(rest.length === 0 ? first : {
|
|
226
|
+
...first,
|
|
227
|
+
...boxOf(letters),
|
|
228
|
+
segs: letters.flatMap((v) => v.segs)
|
|
229
|
+
});
|
|
230
|
+
letters = [];
|
|
231
|
+
};
|
|
232
|
+
for (const v of paths) {
|
|
233
|
+
const last = letters[letters.length - 1];
|
|
234
|
+
if (last !== void 0 && (!v.glyph || last.fillHex !== v.fillHex || last.alpha !== v.alpha)) flush();
|
|
235
|
+
if (v.glyph === true) letters.push(v);
|
|
236
|
+
else out.push(v);
|
|
237
|
+
}
|
|
238
|
+
flush();
|
|
239
|
+
return out;
|
|
240
|
+
}
|
|
241
|
+
/** Whether a path draws: a curve, or a line that is neither level nor upright. */
|
|
242
|
+
function draws(v) {
|
|
243
|
+
let x = 0;
|
|
244
|
+
let y = 0;
|
|
245
|
+
for (const s of v.segs) {
|
|
246
|
+
if (s.op === "cubic") return true;
|
|
247
|
+
if (s.op === "line" && Math.abs(s.x - x) > THIN_PT && Math.abs(s.y - y) > THIN_PT) return true;
|
|
248
|
+
if (s.op !== "close") {
|
|
249
|
+
x = s.x;
|
|
250
|
+
y = s.y;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Whether paths draw a BAR CHART: filled rectangles, six and more, standing on
|
|
257
|
+
* one line and as long as each other's figures make them — the one drawing
|
|
258
|
+
* made of nothing but straight lines and boxes that a ruling is not. A
|
|
259
|
+
* table's shading stands its boxes on one line too, but each as long as the
|
|
260
|
+
* last.
|
|
261
|
+
*
|
|
262
|
+
* comments.pdf sets Figures 10 and 12 so, bars of gradient and of flat grey.
|
|
263
|
+
* With no curve among them they were taken for a ruling: their labels were
|
|
264
|
+
* read as lines of the text, Figure 12 and the column beside it came back as
|
|
265
|
+
* one table, and Figure 10's caption, the first line left on its page, set
|
|
266
|
+
* the page's top margin 352 points down.
|
|
267
|
+
*
|
|
268
|
+
* @param paths The paths of one cluster.
|
|
269
|
+
*/
|
|
270
|
+
function barChart(paths) {
|
|
271
|
+
const bars = paths.filter((v) => v.fillHex !== void 0 || v.gradient !== void 0).flatMap(rectanglesOf);
|
|
272
|
+
const standing = (base, length) => {
|
|
273
|
+
const lengths = /* @__PURE__ */ new Map();
|
|
274
|
+
for (const b of bars) {
|
|
275
|
+
const at = Math.round(base(b) / BAR_LINE_PT);
|
|
276
|
+
const long = length(b);
|
|
277
|
+
if (long > THIN_PT) lengths.set(at, [...lengths.get(at) ?? [], long]);
|
|
278
|
+
}
|
|
279
|
+
return [...lengths.values()].some((all) => all.length >= MIN_PATHS && Math.max(...all) >= Math.min(...all) * BAR_SPREAD);
|
|
280
|
+
};
|
|
281
|
+
return standing((b) => b.minY, (b) => b.maxY - b.minY) || standing((b) => b.minX, (b) => b.maxX - b.minX);
|
|
282
|
+
}
|
|
283
|
+
/** How near, in points, bars' feet stand to stand on one line. */
|
|
284
|
+
var BAR_LINE_PT = .5;
|
|
285
|
+
/** How much longer the longest bar of a chart is than the shortest, at the least. */
|
|
286
|
+
var BAR_SPREAD = 2;
|
|
287
|
+
/** The upright rectangles a path is drawn with, one per subpath that is one. */
|
|
288
|
+
function rectanglesOf(v) {
|
|
289
|
+
const out = [];
|
|
290
|
+
let corners = [];
|
|
291
|
+
let curved = false;
|
|
292
|
+
const close = () => {
|
|
293
|
+
const xs = new Set(corners.map((c) => Math.round(c.x * 10)));
|
|
294
|
+
const ys = new Set(corners.map((c) => Math.round(c.y * 10)));
|
|
295
|
+
if (!curved && corners.length >= 4 && corners.length <= 5 && xs.size === 2 && ys.size === 2) out.push({
|
|
296
|
+
minX: Math.min(...corners.map((c) => c.x)),
|
|
297
|
+
maxX: Math.max(...corners.map((c) => c.x)),
|
|
298
|
+
minY: Math.min(...corners.map((c) => c.y)),
|
|
299
|
+
maxY: Math.max(...corners.map((c) => c.y))
|
|
300
|
+
});
|
|
301
|
+
corners = [];
|
|
302
|
+
curved = false;
|
|
303
|
+
};
|
|
304
|
+
for (const s of v.segs) if (s.op === "move") {
|
|
305
|
+
close();
|
|
306
|
+
corners.push({
|
|
307
|
+
x: s.x,
|
|
308
|
+
y: s.y
|
|
309
|
+
});
|
|
310
|
+
} else if (s.op === "line") corners.push({
|
|
311
|
+
x: s.x,
|
|
312
|
+
y: s.y
|
|
313
|
+
});
|
|
314
|
+
else if (s.op === "cubic") curved = true;
|
|
315
|
+
close();
|
|
316
|
+
return out;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Whether the words on a drawing are prose: lines that run across most of it,
|
|
320
|
+
* more than a caption's worth. A label is short, a paragraph set on a ground
|
|
321
|
+
* fills it.
|
|
322
|
+
*
|
|
323
|
+
* A line is its words as far as the next gap wider than an em. Two boxes side
|
|
324
|
+
* by side put their labels on one baseline — comments.pdf's "Record" and
|
|
325
|
+
* "Enter" a hundred and twenty points apart — and taken as one line they ran
|
|
326
|
+
* across the figure like a line of its text.
|
|
327
|
+
*/
|
|
328
|
+
function runsAcross(labels, box, size) {
|
|
329
|
+
const byLine = [];
|
|
330
|
+
for (const r of labels) {
|
|
331
|
+
const line = byLine.find((l) => Math.abs(l.y - r.y) <= size * .3);
|
|
332
|
+
if (line) {
|
|
333
|
+
line.inks.push(inkOf(r));
|
|
334
|
+
line.em = Math.max(line.em, r.fontSizePt);
|
|
335
|
+
} else byLine.push({
|
|
336
|
+
y: r.y,
|
|
337
|
+
em: r.fontSizePt,
|
|
338
|
+
inks: [inkOf(r)]
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
const width = box.maxX - box.minX;
|
|
342
|
+
let across = 0;
|
|
343
|
+
for (const { em, inks } of byLine) {
|
|
344
|
+
inks.sort((a, b) => a.minX - b.minX);
|
|
345
|
+
let from = inks[0].minX;
|
|
346
|
+
let to = inks[0].maxX;
|
|
347
|
+
for (const ink of [...inks.slice(1), void 0]) {
|
|
348
|
+
if (ink !== void 0 && ink.minX - to <= size) {
|
|
349
|
+
to = Math.max(to, ink.maxX);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
if (to - from >= Math.min(width * LABEL_FILL, em * LABEL_EM)) across++;
|
|
353
|
+
if (ink !== void 0) {
|
|
354
|
+
from = ink.minX;
|
|
355
|
+
to = ink.maxX;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return across >= PROSE_LINES;
|
|
360
|
+
}
|
|
361
|
+
/** The box a run's glyphs stand in: a fifth of the size below the baseline, the rest above. */
|
|
362
|
+
function inkOf(r) {
|
|
363
|
+
const s = r.fontSizePt;
|
|
364
|
+
if (Math.abs(r.angleDeg ?? 0) < .5) return {
|
|
365
|
+
minX: Math.min(r.x, r.endX),
|
|
366
|
+
minY: r.y - s * .2,
|
|
367
|
+
maxX: Math.max(r.x, r.endX),
|
|
368
|
+
maxY: r.y + s * .8
|
|
369
|
+
};
|
|
370
|
+
return {
|
|
371
|
+
minX: Math.min(r.x, r.endX) - s * .8,
|
|
372
|
+
minY: Math.min(r.y, r.endY) - s * .8,
|
|
373
|
+
maxX: Math.max(r.x, r.endX) + s * .8,
|
|
374
|
+
maxY: Math.max(r.y, r.endY) + s * .8
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
function boxOf(members) {
|
|
378
|
+
return members.reduce(union, { ...members[0] });
|
|
379
|
+
}
|
|
380
|
+
function union(a, b) {
|
|
381
|
+
return {
|
|
382
|
+
minX: Math.min(a.minX, b.minX),
|
|
383
|
+
minY: Math.min(a.minY, b.minY),
|
|
384
|
+
maxX: Math.max(a.maxX, b.maxX),
|
|
385
|
+
maxY: Math.max(a.maxY, b.maxY)
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function touches(a, b, slack) {
|
|
389
|
+
return b.minX <= a.maxX + slack && b.maxX >= a.minX - slack && b.minY <= a.maxY + slack && b.maxY >= a.minY - slack;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Whether a rule lies along a drawing rather than meeting it: across it within
|
|
393
|
+
* a point, and at least half its length inside it. A table's border that ends
|
|
394
|
+
* where a figure begins is not the figure's.
|
|
395
|
+
*/
|
|
396
|
+
function mostlyWithin(r, box) {
|
|
397
|
+
const across = r.maxX - r.minX >= r.maxY - r.minY;
|
|
398
|
+
const [from, to, lo, hi] = across ? [
|
|
399
|
+
r.minX,
|
|
400
|
+
r.maxX,
|
|
401
|
+
box.minX,
|
|
402
|
+
box.maxX
|
|
403
|
+
] : [
|
|
404
|
+
r.minY,
|
|
405
|
+
r.maxY,
|
|
406
|
+
box.minY,
|
|
407
|
+
box.maxY
|
|
408
|
+
];
|
|
409
|
+
const [side, sideEnd, sideLo, sideHi] = across ? [
|
|
410
|
+
r.minY,
|
|
411
|
+
r.maxY,
|
|
412
|
+
box.minY,
|
|
413
|
+
box.maxY
|
|
414
|
+
] : [
|
|
415
|
+
r.minX,
|
|
416
|
+
r.maxX,
|
|
417
|
+
box.minX,
|
|
418
|
+
box.maxX
|
|
419
|
+
];
|
|
420
|
+
if (sideEnd < sideLo - 1 || side > sideHi + 1) return false;
|
|
421
|
+
return Math.min(to, hi) - Math.max(from, lo) >= (to - from) / 2;
|
|
422
|
+
}
|
|
423
|
+
/** Whether most of a picture lies inside a box. */
|
|
424
|
+
function mostlyInside(img, box) {
|
|
425
|
+
return Math.max(0, Math.min(img.x + img.widthPt, box.maxX) - Math.max(img.x, box.minX)) * Math.max(0, Math.min(img.y + img.heightPt, box.maxY) - Math.max(img.y, box.minY)) > img.widthPt * img.heightPt / 2;
|
|
426
|
+
}
|
|
427
|
+
function median(values) {
|
|
428
|
+
if (values.length === 0) return 0;
|
|
429
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
430
|
+
return sorted[Math.floor(sorted.length / 2)];
|
|
431
|
+
}
|
|
432
|
+
//#endregion
|
|
433
|
+
export { joinLetters, pageFigures, tracedRun };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { BodyElement, ParagraphProperties, Section, SectionProperties, TextOutline } from '../core/document-model/index.js';
|
|
2
|
-
import { FaceFamily, FlowDoc } from '../core/ir/flow.js';
|
|
1
|
+
import { BodyElement, Ligatures, ParagraphProperties, Section, SectionProperties, ShapeBlock, TextOutline } from '../core/document-model/index.js';
|
|
2
|
+
import { FaceFamily, FaceOutlines, FlowDoc } from '../core/ir/flow.js';
|
|
3
3
|
import { FontRegistry } from '../core/font/index.js';
|
|
4
4
|
import { Loss, Pt, ResourceStore } from '../core/ir/index.js';
|
|
5
|
+
import { Display } from './display.js';
|
|
5
6
|
import { PdfImage } from './images.js';
|
|
6
7
|
import { PdfPage } from './document.js';
|
|
7
8
|
import { PdfVector } from './vector.js';
|
|
@@ -46,10 +47,18 @@ export declare function paragraphBlock(text: string, outlineLevel?: number): Bod
|
|
|
46
47
|
* them up to "78110"; in 7-point footnotes the same space is half as wide
|
|
47
48
|
* again as the page's, and pushes their lines over.
|
|
48
49
|
*
|
|
50
|
+
* A space between two words one text markup marks is marked with them: the
|
|
51
|
+
* band a highlighter lays runs across the gap. comments.pdf highlights two
|
|
52
|
+
* lines on its sixth page, and with its spaces left bare the band came back
|
|
53
|
+
* broken at every word.
|
|
54
|
+
*
|
|
49
55
|
* @param before The span the space follows, where there is one.
|
|
56
|
+
* @param after What follows the space, where anything does.
|
|
50
57
|
* @returns The space.
|
|
51
58
|
*/
|
|
52
|
-
export declare function spaceAfter(before: TextSpan | undefined
|
|
59
|
+
export declare function spaceAfter(before: TextSpan | undefined, after?: {
|
|
60
|
+
readonly markup?: TextMarkup;
|
|
61
|
+
}): TextSpan;
|
|
53
62
|
export interface TextSpan {
|
|
54
63
|
readonly text: string;
|
|
55
64
|
readonly href?: string;
|
|
@@ -73,6 +82,12 @@ export interface TextSpan {
|
|
|
73
82
|
readonly script?: 'superscript' | 'subscript';
|
|
74
83
|
/** §12.5.6.10 — a text-markup annotation marks these words. */
|
|
75
84
|
readonly markup?: TextMarkup;
|
|
85
|
+
/**
|
|
86
|
+
* §17.3.2.43 — the share of their own width the characters are set at: a
|
|
87
|
+
* word space the page set narrower than the face's own (see `fittedSpace` in
|
|
88
|
+
* ./layout).
|
|
89
|
+
*/
|
|
90
|
+
readonly widthScale?: number;
|
|
76
91
|
}
|
|
77
92
|
/**
|
|
78
93
|
* Build a paragraph {@link BodyElement} from positioned {@link TextSpan}s,
|
|
@@ -125,6 +140,53 @@ export declare function positionedText(spans: ReadonlyArray<TextSpan>, box: {
|
|
|
125
140
|
width: number;
|
|
126
141
|
height: number;
|
|
127
142
|
}, frame: PageFrame, zOrder: number, rotation60k?: number): BodyElement;
|
|
143
|
+
/**
|
|
144
|
+
* One part of a figure (see `./figures`): the shape it is drawn as, and where
|
|
145
|
+
* its box stands on the page — its left edge and its top, y-up.
|
|
146
|
+
*/
|
|
147
|
+
export interface FigureMember {
|
|
148
|
+
readonly shape: ShapeBlock;
|
|
149
|
+
readonly left: number;
|
|
150
|
+
readonly top: number;
|
|
151
|
+
}
|
|
152
|
+
/** A path of a figure, drawn where the page drew it. */
|
|
153
|
+
export declare function vectorMember(v: PdfVector): FigureMember;
|
|
154
|
+
/**
|
|
155
|
+
* A picture of a figure: a box filled with it (§20.1.8.14), which is what a
|
|
156
|
+
* `pic:pic` inside a group is — the shadows comments.pdf lays under each of
|
|
157
|
+
* its boxes.
|
|
158
|
+
*/
|
|
159
|
+
export declare function imageMember(image: PdfImage, resources: ResourceStore): FigureMember;
|
|
160
|
+
/**
|
|
161
|
+
* A label of a figure: a line of its words, in a box as wide as they are.
|
|
162
|
+
* The box never wraps — set in a face a little wider than the page's, one
|
|
163
|
+
* word would break in two.
|
|
164
|
+
*/
|
|
165
|
+
export declare function labelMember(spans: ReadonlyArray<TextSpan>, box: {
|
|
166
|
+
x: number;
|
|
167
|
+
y: number;
|
|
168
|
+
width: number;
|
|
169
|
+
height: number;
|
|
170
|
+
}, rotation60k?: number): FigureMember;
|
|
171
|
+
/** The box a figure's parts stand in, on the page (y-up). */
|
|
172
|
+
export declare function membersBox(members: ReadonlyArray<FigureMember>): {
|
|
173
|
+
left: number;
|
|
174
|
+
top: number;
|
|
175
|
+
right: number;
|
|
176
|
+
bottom: number;
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* §20.5.2.17 — a figure as the one drawing it is: a group holding its paths,
|
|
180
|
+
* its pictures and its labels at their places in it, set in a paragraph of
|
|
181
|
+
* its own. An inline drawing takes the room it is tall (a line holding
|
|
182
|
+
* nothing but a picture is the picture's height in Word), so the text after
|
|
183
|
+
* it follows it, and the figure goes with its paragraph wherever the text
|
|
184
|
+
* puts that.
|
|
185
|
+
*
|
|
186
|
+
* @param members Its parts, in the order they are painted.
|
|
187
|
+
* @param properties The paragraph it stands in.
|
|
188
|
+
*/
|
|
189
|
+
export declare function figureBlock(members: ReadonlyArray<FigureMember>, properties: ParagraphProperties): BodyElement;
|
|
128
190
|
/** Collapse losses sharing a `detail` message (the same colour space dropped on many pages). */
|
|
129
191
|
export declare function dedupeLosses(losses: ReadonlyArray<Loss>): Array<Loss>;
|
|
130
192
|
/**
|
|
@@ -150,7 +212,36 @@ export declare function shapeBlock(v: PdfVector, frame?: PageFrame, zOrder?: num
|
|
|
150
212
|
* this single geometry — a known approximation, still far better than a fixed
|
|
151
213
|
* `A4`. Returns `undefined` when there is no usable first-page box.
|
|
152
214
|
*/
|
|
153
|
-
export declare function sectionFromPdfPages(pages: ReadonlyArray<PdfPage
|
|
215
|
+
export declare function sectionFromPdfPages(pages: ReadonlyArray<PdfPage>, display?: Display): SectionProperties | undefined;
|
|
216
|
+
/**
|
|
217
|
+
* §17.6.20 — a section read in a page's text frame (see `textFrameOf`), set
|
|
218
|
+
* back on the sheet the page is shown on, its lines running down it (`tbRl`).
|
|
219
|
+
*
|
|
220
|
+
* The frame is the sheet turned back a quarter, so its measurements carry
|
|
221
|
+
* over edge for edge: the frame's left margin is where each line starts, the
|
|
222
|
+
* sheet's top one; its top margin is where the first line stands, the
|
|
223
|
+
* sheet's right one.
|
|
224
|
+
*
|
|
225
|
+
* @param section The section as the frame measured it.
|
|
226
|
+
* @param display The geometry its pages were read in.
|
|
227
|
+
* @returns The section on the sheet, or the section unchanged for a page read
|
|
228
|
+
* on its sheet.
|
|
229
|
+
*/
|
|
230
|
+
export declare function sectionOnSheet(section: SectionProperties | undefined, display: Display | undefined): SectionProperties | undefined;
|
|
231
|
+
/**
|
|
232
|
+
* A mark anchored to a page read in its text frame, set on the SHEET the page
|
|
233
|
+
* is shown on: where the page drew it, turned with the page.
|
|
234
|
+
*
|
|
235
|
+
* Word lays a `tbRl` section's text down the sheet but stands a drawing
|
|
236
|
+
* anchored to the page at its offsets on the sheet, upright (§17.6.20) — so a
|
|
237
|
+
* picture the frame placed is carried onto the sheet, and turned a quarter
|
|
238
|
+
* clockwise as the words around it are.
|
|
239
|
+
*
|
|
240
|
+
* @param el A body element, anchored or not.
|
|
241
|
+
* @param sheetWidth The sheet's width, which is the frame's height.
|
|
242
|
+
* @returns The element, anchored on the sheet.
|
|
243
|
+
*/
|
|
244
|
+
export declare function floatOntoSheet(el: BodyElement, sheetWidth: number): BodyElement;
|
|
154
245
|
/** How many lines a sheet must hold before where they end says where its measure does. */
|
|
155
246
|
export declare const MEASURE_LINES = 3;
|
|
156
247
|
/**
|
|
@@ -168,6 +259,13 @@ export declare const GUESSED_MARGIN: number;
|
|
|
168
259
|
export declare const BASELINE_AT = 0.8;
|
|
169
260
|
/** A line's box where no pitch was measured: the ordinary single spacing, in ems. */
|
|
170
261
|
export declare const NATURAL_LINE_EM = 1.2;
|
|
262
|
+
/**
|
|
263
|
+
* How far above its baseline the first line's BOX reaches, as a fraction of the
|
|
264
|
+
* size — which is what the top margin has to leave room for. The lines are set
|
|
265
|
+
* in exact boxes (see `groupIntoParagraphs`), so this is where the box's top
|
|
266
|
+
* stands, not where a face's ascender happens to.
|
|
267
|
+
*/
|
|
268
|
+
export declare const ASCENDER: number;
|
|
171
269
|
/**
|
|
172
270
|
* The size a document's text is set in: the middle of every size its runs
|
|
173
271
|
* carry.
|
|
@@ -223,6 +321,34 @@ export declare function withMeasuredMargins(section: SectionProperties | undefin
|
|
|
223
321
|
widthPt: number;
|
|
224
322
|
heightPt: number;
|
|
225
323
|
}>>, foot?: ReadonlyArray<TextRun>): SectionProperties | undefined;
|
|
324
|
+
/** What a face's runs ask of a word processor to be set as the page set them. */
|
|
325
|
+
export interface Typesetting {
|
|
326
|
+
/** §17.3.2.19 — kerned from this size up. */
|
|
327
|
+
readonly kerningMinPt?: Pt;
|
|
328
|
+
/** [MS-DOCX] `w14:ligatures` — set with these ligatures. */
|
|
329
|
+
readonly ligatures?: Ligatures;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* The runs set in a face the page KERNED or LIGATED, marked so (see
|
|
333
|
+
* `pageSpacing`, `FaceOutlines.ligatures`): a word processor kerns nothing a
|
|
334
|
+
* run does not ask it to, and set unkerned, a line kerned on the page runs
|
|
335
|
+
* longer than it did there; a ligature the embedded face carries is formed
|
|
336
|
+
* where the run asks for its face's ligatures.
|
|
337
|
+
*
|
|
338
|
+
* @param blocks The blocks to mark, tables and text boxes within them too.
|
|
339
|
+
* @param typeset Run font name → what its runs ask for.
|
|
340
|
+
* @returns The blocks, their runs in those faces asking for it.
|
|
341
|
+
*/
|
|
342
|
+
export declare function typesetRuns(blocks: ReadonlyArray<BodyElement>, typeset: ReadonlyMap<string, Typesetting>): Array<BodyElement>;
|
|
343
|
+
/**
|
|
344
|
+
* What each face's runs ask for: kerning where the page kerned the face, and
|
|
345
|
+
* its standard ligatures where the page drew one of them (the ligatures the
|
|
346
|
+
* face's embedded font then forms are exactly the ones the page drew).
|
|
347
|
+
*
|
|
348
|
+
* @param kerned The run font names of the faces the page kerned.
|
|
349
|
+
* @param faceOutlines Run font name → the face's outlines, ligatures among them.
|
|
350
|
+
*/
|
|
351
|
+
export declare function typesetting(kerned: ReadonlySet<string>, faceOutlines: ReadonlyMap<string, FaceOutlines> | undefined): Map<string, Typesetting>;
|
|
226
352
|
/**
|
|
227
353
|
* Assemble the final {@link FlowDoc} for a reconstruction: the body elements
|
|
228
354
|
* with their styles resolved against the empty style sheet, the lifted-image
|
|
@@ -230,4 +356,4 @@ export declare function withMeasuredMargins(section: SectionProperties | undefin
|
|
|
230
356
|
* both reconstruction paths (the tagged fast-path EP3 and the heuristic layout
|
|
231
357
|
* path EP4).
|
|
232
358
|
*/
|
|
233
|
-
export declare function buildFlowDoc(body: ReadonlyArray<BodyElement>, resources?: ResourceStore, section?: SectionProperties, embeddedFonts?: ReadonlyMap<string, FontRegistry>, sections?: ReadonlyArray<Section>, headersFooters?: ReadonlyMap<string, ReadonlyArray<BodyElement>>, faceFamilies?: ReadonlyMap<string, FaceFamily>): FlowDoc;
|
|
359
|
+
export declare function buildFlowDoc(body: ReadonlyArray<BodyElement>, resources?: ResourceStore, section?: SectionProperties, embeddedFonts?: ReadonlyMap<string, FontRegistry>, sections?: ReadonlyArray<Section>, headersFooters?: ReadonlyMap<string, ReadonlyArray<BodyElement>>, faceFamilies?: ReadonlyMap<string, FaceFamily>, faceOutlines?: ReadonlyMap<string, FaceOutlines>, kerned?: ReadonlySet<string>): FlowDoc;
|