@boxpdf/html-writer 0.1.17 → 0.1.18
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/index.cjs +344 -160
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +344 -160
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { structurePage as structurePage2, tableToHtml } from "@boxpdf/reader/structure";
|
|
3
3
|
|
|
4
|
+
// src/semantic-caption.ts
|
|
5
|
+
function isClearMediaCaption(media, block, pageWidth, pageHeight, pageLines) {
|
|
6
|
+
if (block.type !== "paragraph" || block.lines.length === 0) return false;
|
|
7
|
+
const bounds2 = unionLines(block.lines);
|
|
8
|
+
const lineHeight = median(block.lines.map((line) => line.bounds.height));
|
|
9
|
+
if (media.bounds.width < pageWidth * 0.2 || media.bounds.height < lineHeight * 10) return false;
|
|
10
|
+
if (media.bounds.x < -2 || media.bounds.y < -2 || media.bounds.x + media.bounds.width > pageWidth + 2 || media.bounds.y + media.bounds.height > pageHeight + 2)
|
|
11
|
+
return false;
|
|
12
|
+
const gap = media.bounds.y - (bounds2.y + bounds2.height);
|
|
13
|
+
if (gap < -lineHeight * 0.15 || gap > lineHeight * 1.25) return false;
|
|
14
|
+
const mediaCenter = media.bounds.x + media.bounds.width / 2;
|
|
15
|
+
const captionCenter = bounds2.x + bounds2.width / 2;
|
|
16
|
+
if (Math.abs(mediaCenter - captionCenter) > Math.max(3, media.bounds.width * 0.03)) return false;
|
|
17
|
+
if (bounds2.width < media.bounds.width * 0.45 || bounds2.width > media.bounds.width * 1.06) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const first = block.lines.flatMap((line) => line.spans).find((span) => /\S/u.test(span.text));
|
|
21
|
+
if (!first) return false;
|
|
22
|
+
const otherLines = pageLines.filter((line) => !block.lines.includes(line));
|
|
23
|
+
return fontSignature(first) !== dominantFontSignature(otherLines);
|
|
24
|
+
}
|
|
25
|
+
function clearMediaCaptionAssociations(media, blocks, pageWidth, pageHeight, pageLines) {
|
|
26
|
+
const associations = /* @__PURE__ */ new Map();
|
|
27
|
+
for (const item of media) {
|
|
28
|
+
const candidates = blocks.filter((block) => isClearMediaCaption(item, block, pageWidth, pageHeight, pageLines)).filter((block) => !associations.has(block)).sort((left, right) => captionGap(item, left) - captionGap(item, right));
|
|
29
|
+
const caption = candidates[0];
|
|
30
|
+
if (caption) associations.set(caption, item);
|
|
31
|
+
}
|
|
32
|
+
return associations;
|
|
33
|
+
}
|
|
34
|
+
function captionGap(media, block) {
|
|
35
|
+
if (block.type !== "paragraph") return Number.POSITIVE_INFINITY;
|
|
36
|
+
const bounds2 = unionLines(block.lines);
|
|
37
|
+
return Math.abs(media.bounds.y - bounds2.y - bounds2.height);
|
|
38
|
+
}
|
|
39
|
+
function unionLines(lines) {
|
|
40
|
+
const x = Math.min(...lines.map((line) => line.bounds.x));
|
|
41
|
+
const y = Math.min(...lines.map((line) => line.bounds.y));
|
|
42
|
+
const right = Math.max(...lines.map((line) => line.bounds.x + line.bounds.width));
|
|
43
|
+
const top = Math.max(...lines.map((line) => line.bounds.y + line.bounds.height));
|
|
44
|
+
return { x, y, width: right - x, height: top - y };
|
|
45
|
+
}
|
|
46
|
+
function dominantFontSignature(lines) {
|
|
47
|
+
const counts = /* @__PURE__ */ new Map();
|
|
48
|
+
for (const span of lines.flatMap((line) => line.spans)) {
|
|
49
|
+
const signature = fontSignature(span);
|
|
50
|
+
counts.set(signature, (counts.get(signature) ?? 0) + Math.max(1, [...span.text].length));
|
|
51
|
+
}
|
|
52
|
+
return [...counts].sort((left, right) => right[1] - left[1])[0]?.[0] ?? "";
|
|
53
|
+
}
|
|
54
|
+
function fontSignature(span) {
|
|
55
|
+
return `${(span.fontFamily ?? span.fontName ?? "").toLocaleLowerCase("en")}|${Math.round(span.fontSize * 2) / 2}|${span.color ?? ""}`;
|
|
56
|
+
}
|
|
57
|
+
function median(values) {
|
|
58
|
+
const ordered = [...values].sort((left, right) => left - right);
|
|
59
|
+
return ordered[Math.floor(ordered.length / 2)] ?? 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
4
62
|
// src/semantic-document.ts
|
|
5
63
|
import {
|
|
6
64
|
structurePage,
|
|
@@ -76,6 +134,85 @@ function escapeHtml(value) {
|
|
|
76
134
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
77
135
|
}
|
|
78
136
|
|
|
137
|
+
// src/vector-svg.ts
|
|
138
|
+
function vectorFillSvg(fill) {
|
|
139
|
+
if (!isCssHexColor(fill.color)) return "";
|
|
140
|
+
const points = fill.points.map(([x, y]) => `${number(x)},${number(y)}`).join(" ");
|
|
141
|
+
const opacity = isUnitInterval(fill.opacity) ? ` fill-opacity="${number(fill.opacity)}"` : "";
|
|
142
|
+
return `<polygon points="${points}" fill="${fill.color}"${opacity}/>`;
|
|
143
|
+
}
|
|
144
|
+
function vectorPathSvg(path, pageNumber, pathIndex) {
|
|
145
|
+
if (!isSvgPath(path.d)) return "";
|
|
146
|
+
const fill = isCssHexColor(path.fill) ? path.fill : "none";
|
|
147
|
+
const stroke = isCssHexColor(path.stroke) ? path.stroke : "none";
|
|
148
|
+
const width = finiteNonnegative(path.strokeWidth) ? ` stroke-width="${number(path.strokeWidth)}"` : "";
|
|
149
|
+
const fillOpacity = isUnitInterval(path.fillOpacity) ? ` fill-opacity="${number(path.fillOpacity)}"` : "";
|
|
150
|
+
const strokeOpacity = isUnitInterval(path.strokeOpacity) ? ` stroke-opacity="${number(path.strokeOpacity)}"` : "";
|
|
151
|
+
const dash = path.strokeDasharray?.every(finiteNonnegative) ? ` stroke-dasharray="${path.strokeDasharray.map(number).join(" ")}"` : "";
|
|
152
|
+
const dashoffset = Number.isFinite(path.strokeDashoffset) ? ` stroke-dashoffset="${number(path.strokeDashoffset ?? 0)}"` : "";
|
|
153
|
+
const linecap = path.strokeLinecap ? ` stroke-linecap="${path.strokeLinecap}"` : "";
|
|
154
|
+
const linejoin = path.strokeLinejoin ? ` stroke-linejoin="${path.strokeLinejoin}"` : "";
|
|
155
|
+
const rule = path.fillRule ? ` fill-rule="${path.fillRule}"` : "";
|
|
156
|
+
let output = `<path d="${path.d}" fill="${fill}" stroke="${stroke}"${width}${fillOpacity}${strokeOpacity}${dash}${dashoffset}${linecap}${linejoin}${rule}/>`;
|
|
157
|
+
for (let index = (path.clips?.length ?? 0) - 1; index >= 0; index -= 1) {
|
|
158
|
+
output = `<g clip-path="url(#${vectorPathClipId(pageNumber, pathIndex, index)})">${output}</g>`;
|
|
159
|
+
}
|
|
160
|
+
return output;
|
|
161
|
+
}
|
|
162
|
+
function vectorPathClipDefinitions(paths, pageNumber) {
|
|
163
|
+
return paths.flatMap(
|
|
164
|
+
({ path, index: pathIndex }) => (path.clips ?? []).map((clip, clipIndex) => {
|
|
165
|
+
if (!isSvgPath(clip.d)) return "";
|
|
166
|
+
const rule = clip.fillRule ? ` clip-rule="${clip.fillRule}"` : "";
|
|
167
|
+
return `<clipPath id="${vectorPathClipId(pageNumber, pathIndex, clipIndex)}" clipPathUnits="userSpaceOnUse"><path d="${clip.d}"${rule}/></clipPath>`;
|
|
168
|
+
})
|
|
169
|
+
).join("");
|
|
170
|
+
}
|
|
171
|
+
function vectorPathBounds(path) {
|
|
172
|
+
if (!isSvgPath(path.d)) return void 0;
|
|
173
|
+
const values = [...path.d.matchAll(/[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?/gi)].map(
|
|
174
|
+
(match) => Number(match[0])
|
|
175
|
+
);
|
|
176
|
+
if (values.length < 2) return void 0;
|
|
177
|
+
const xs = [];
|
|
178
|
+
const ys = [];
|
|
179
|
+
for (let index = 0; index + 1 < values.length; index += 2) {
|
|
180
|
+
xs.push(values[index] ?? 0);
|
|
181
|
+
ys.push(values[index + 1] ?? 0);
|
|
182
|
+
}
|
|
183
|
+
return bounds(xs, ys);
|
|
184
|
+
}
|
|
185
|
+
function vectorFillBounds(fill) {
|
|
186
|
+
if (fill.points.length === 0) return void 0;
|
|
187
|
+
return bounds(
|
|
188
|
+
fill.points.map(([x]) => x),
|
|
189
|
+
fill.points.map(([, y]) => y)
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
function isSvgPath(value) {
|
|
193
|
+
return value.length <= 1e6 && /^[\d\s.,+\-eEMmLlCcZz]+$/.test(value);
|
|
194
|
+
}
|
|
195
|
+
function bounds(xs, ys) {
|
|
196
|
+
const x = Math.min(...xs);
|
|
197
|
+
const y = Math.min(...ys);
|
|
198
|
+
return { x, y, width: Math.max(...xs) - x, height: Math.max(...ys) - y };
|
|
199
|
+
}
|
|
200
|
+
function vectorPathClipId(pageNumber, pathIndex, clipIndex) {
|
|
201
|
+
return `boxpdf-path-clip-${pageNumber}-${pathIndex}-${clipIndex}`;
|
|
202
|
+
}
|
|
203
|
+
function isCssHexColor(value) {
|
|
204
|
+
return typeof value === "string" && /^#[0-9a-f]{6}$/i.test(value);
|
|
205
|
+
}
|
|
206
|
+
function finiteNonnegative(value) {
|
|
207
|
+
return value !== void 0 && Number.isFinite(value) && value >= 0;
|
|
208
|
+
}
|
|
209
|
+
function isUnitInterval(value) {
|
|
210
|
+
return value !== void 0 && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
211
|
+
}
|
|
212
|
+
function number(value) {
|
|
213
|
+
return Number(value.toFixed(4)).toString();
|
|
214
|
+
}
|
|
215
|
+
|
|
79
216
|
// src/visual-font.ts
|
|
80
217
|
function visualFontAliases(pageNumber, fonts) {
|
|
81
218
|
return new Map(
|
|
@@ -125,46 +262,90 @@ function base64(bytes) {
|
|
|
125
262
|
// src/semantic-media.ts
|
|
126
263
|
function semanticMedia(page) {
|
|
127
264
|
const output = (page.images ?? []).map((image) => rasterMedia(image));
|
|
128
|
-
|
|
129
|
-
if (vector) output.push(vector);
|
|
265
|
+
output.push(...vectorMedia(page));
|
|
130
266
|
return output.sort((left, right) => right.bounds.y - left.bounds.y);
|
|
131
267
|
}
|
|
132
268
|
function rasterMedia(image) {
|
|
133
|
-
const
|
|
269
|
+
const bounds2 = transformedUnitBounds(image.transform);
|
|
134
270
|
const mime = image.format === "jpeg" ? "image/jpeg" : "image/bmp";
|
|
135
271
|
const data = image.format === "jpeg" ? image.data : rgbBmp(image);
|
|
136
|
-
const opacity = unitInterval(image.opacity) ? `;opacity:${
|
|
272
|
+
const opacity = unitInterval(image.opacity) ? `;opacity:${number2(image.opacity)}` : "";
|
|
137
273
|
return {
|
|
138
|
-
bounds,
|
|
139
|
-
html: `<img class="pdf-semantic-media" src="data:${mime};base64,${base64(data)}" width="${
|
|
274
|
+
bounds: bounds2,
|
|
275
|
+
html: `<img class="pdf-semantic-media" src="data:${mime};base64,${base64(data)}" width="${number2(bounds2.width)}" height="${number2(bounds2.height)}" alt="" style="max-width:100%;height:auto${opacity}">`
|
|
140
276
|
};
|
|
141
277
|
}
|
|
142
278
|
function vectorMedia(page) {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
279
|
+
const primitives = [
|
|
280
|
+
...(page.paths ?? []).flatMap((path, index) => {
|
|
281
|
+
const bounds2 = vectorPathBounds(path);
|
|
282
|
+
return bounds2 ? [{ type: "path", value: path, index, bounds: bounds2 }] : [];
|
|
283
|
+
}),
|
|
284
|
+
...(page.fills ?? []).flatMap((fill) => {
|
|
285
|
+
const bounds2 = vectorFillBounds(fill);
|
|
286
|
+
return bounds2 && !isPageBackground(fill, bounds2, page) ? [{ type: "fill", value: fill, bounds: bounds2 }] : [];
|
|
287
|
+
})
|
|
288
|
+
];
|
|
289
|
+
const components = vectorComponents(primitives, Math.min(36, page.width * 0.06)).filter(
|
|
290
|
+
(component) => component.primitives.length >= 2 || component.bounds.width * component.bounds.height >= page.width * page.height * 2e-3
|
|
291
|
+
);
|
|
150
292
|
const aliases = visualFontAliases(page.number, page.fonts ?? []);
|
|
151
293
|
const visualCodeFonts = new Set(
|
|
152
294
|
(page.fonts ?? []).filter((font) => font.format === "truetype" && font.visualCodeMapping).map((font) => font.id)
|
|
153
295
|
);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
296
|
+
return components.map((component) => {
|
|
297
|
+
const bounds2 = component.bounds;
|
|
298
|
+
const paths = component.primitives.flatMap(
|
|
299
|
+
(primitive) => primitive.type === "path" ? [{ path: primitive.value, index: primitive.index }] : []
|
|
300
|
+
);
|
|
301
|
+
const fills = component.primitives.flatMap(
|
|
302
|
+
(primitive) => primitive.type === "fill" ? [primitive.value] : []
|
|
303
|
+
);
|
|
304
|
+
const visualSpans = page.visualSpans ?? page.spans;
|
|
305
|
+
const overlay = visualSpans.filter(
|
|
306
|
+
(span) => span.fontAssetId && visualCodeFonts.has(span.fontAssetId) && centerInside(span.bounds, bounds2)
|
|
307
|
+
);
|
|
308
|
+
const consumedSpans = page.spans.filter(
|
|
309
|
+
(span) => span.fontAssetId && visualCodeFonts.has(span.fontAssetId) && centerInside(span.bounds, bounds2)
|
|
310
|
+
);
|
|
311
|
+
const fontIds = new Set(overlay.map((span) => span.fontAssetId));
|
|
312
|
+
const fontFaces = (page.fonts ?? []).filter((font) => fontIds.has(font.id)).map((font) => visualFontFace(font, aliases)).join("");
|
|
313
|
+
return {
|
|
314
|
+
bounds: bounds2,
|
|
315
|
+
html: `<svg class="pdf-semantic-media" xmlns="http://www.w3.org/2000/svg" viewBox="${number2(bounds2.x)} ${number2(page.height - bounds2.y - bounds2.height)} ${number2(bounds2.width)} ${number2(bounds2.height)}" style="display:block;max-width:100%;height:auto" aria-hidden="true">${fontFaces ? `<style>${fontFaces}</style>` : ""}${paths.length ? `<defs>${vectorPathClipDefinitions(paths, page.number)}</defs>` : ""}<g transform="translate(0 ${number2(page.height)}) scale(1 -1)">${fills.map(vectorFillSvg).join("") + paths.map(({ path, index }) => vectorPathSvg(path, page.number, index)).join("")}</g>${overlay.map((span) => vectorText(span, page.height, aliases)).join("")}</svg>`,
|
|
316
|
+
...consumedSpans.length > 0 ? { consumedSpans } : {}
|
|
317
|
+
};
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
function vectorComponents(primitives, padding) {
|
|
321
|
+
const components = [];
|
|
322
|
+
for (const primitive of primitives) {
|
|
323
|
+
const matches = components.filter(
|
|
324
|
+
(component) => nearby(component.bounds, primitive.bounds, padding)
|
|
325
|
+
);
|
|
326
|
+
if (matches.length === 0) {
|
|
327
|
+
components.push({ bounds: primitive.bounds, primitives: [primitive] });
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
const target = matches[0];
|
|
331
|
+
target.primitives.push(primitive);
|
|
332
|
+
target.bounds = unionBounds([target.bounds, primitive.bounds]);
|
|
333
|
+
for (const component of matches.slice(1)) {
|
|
334
|
+
target.primitives.push(...component.primitives);
|
|
335
|
+
target.bounds = unionBounds([target.bounds, component.bounds]);
|
|
336
|
+
components.splice(components.indexOf(component), 1);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return components;
|
|
340
|
+
}
|
|
341
|
+
function nearby(left, right, padding) {
|
|
342
|
+
return !(left.x + left.width + padding < right.x || right.x + right.width + padding < left.x || left.y + left.height + padding < right.y || right.y + right.height + padding < left.y);
|
|
343
|
+
}
|
|
344
|
+
function isPageBackground(fill, bounds2, page) {
|
|
345
|
+
if (!/^#f{6}$/i.test(fill.color)) return false;
|
|
346
|
+
const outside = bounds2.x < 0 || bounds2.y < 0 || bounds2.x + bounds2.width > page.width || bounds2.y + bounds2.height > page.height;
|
|
347
|
+
const large = bounds2.width * bounds2.height > page.width * page.height * 0.2;
|
|
348
|
+
return outside || large;
|
|
168
349
|
}
|
|
169
350
|
function withoutSemanticMediaSpans(page, media) {
|
|
170
351
|
const consumed = new Set(media.flatMap((item) => item.consumedSpans ?? []));
|
|
@@ -174,7 +355,7 @@ function vectorText(span, pageHeight, aliases) {
|
|
|
174
355
|
if (span.renderingMode === 3 || span.renderingMode === 7) return "";
|
|
175
356
|
const styles2 = [
|
|
176
357
|
cssColor(span.color) ? `fill:${span.color}` : "",
|
|
177
|
-
unitInterval(span.fillOpacity) ? `fill-opacity:${
|
|
358
|
+
unitInterval(span.fillOpacity) ? `fill-opacity:${number2(span.fillOpacity)}` : "",
|
|
178
359
|
...visualFontStyles(
|
|
179
360
|
span.fontFamily,
|
|
180
361
|
span.fontAssetId ? aliases.get(span.fontAssetId) : void 0
|
|
@@ -182,33 +363,16 @@ function vectorText(span, pageHeight, aliases) {
|
|
|
182
363
|
].filter(Boolean).join(";");
|
|
183
364
|
const anchorY = pageHeight - span.bounds.y;
|
|
184
365
|
const transform = span.transform;
|
|
185
|
-
const position = transform ? ` x="0" y="0" transform="matrix(${transform.map(
|
|
366
|
+
const position = transform ? ` x="0" y="0" transform="matrix(${transform.map(number2).join(" ")} ${number2(span.bounds.x)} ${number2(anchorY)})"` : ` x="${number2(span.bounds.x)}" y="${number2(anchorY)}"`;
|
|
186
367
|
const extent = span.direction === "ttb" ? span.bounds.height : span.bounds.width;
|
|
187
|
-
const length = extent > 0 ? ` textLength="${
|
|
188
|
-
return `<text${position} font-size="${
|
|
368
|
+
const length = extent > 0 ? ` textLength="${number2(extent)}" lengthAdjust="spacingAndGlyphs"` : "";
|
|
369
|
+
return `<text${position} font-size="${number2(span.fontSize)}"${length}${styles2 ? ` style="${styles2}"` : ""}>${escapeHtml2(span.text)}</text>`;
|
|
189
370
|
}
|
|
190
371
|
function centerInside(inner, outer) {
|
|
191
372
|
const x = inner.x + inner.width / 2;
|
|
192
373
|
const y = inner.y + inner.height / 2;
|
|
193
374
|
return x >= outer.x && x <= outer.x + outer.width && y >= outer.y && y <= outer.y + outer.height;
|
|
194
375
|
}
|
|
195
|
-
function vectorFill(fill) {
|
|
196
|
-
const points = fill.points.map(([x, y]) => `${number(x)},${number(y)}`).join(" ");
|
|
197
|
-
const opacity = unitInterval(fill.opacity) ? ` fill-opacity="${number(fill.opacity)}"` : "";
|
|
198
|
-
return cssColor(fill.color) ? `<polygon points="${points}" fill="${fill.color}"${opacity}/>` : "";
|
|
199
|
-
}
|
|
200
|
-
function vectorPath(path) {
|
|
201
|
-
const fill = cssColor(path.fill) ? path.fill : "none";
|
|
202
|
-
const stroke = cssColor(path.stroke) ? path.stroke : "none";
|
|
203
|
-
const width = finiteNonnegative(path.strokeWidth) ? ` stroke-width="${number(path.strokeWidth)}"` : "";
|
|
204
|
-
const fillOpacity = unitInterval(path.fillOpacity) ? ` fill-opacity="${number(path.fillOpacity)}"` : "";
|
|
205
|
-
const strokeOpacity = unitInterval(path.strokeOpacity) ? ` stroke-opacity="${number(path.strokeOpacity)}"` : "";
|
|
206
|
-
const dash = path.strokeDasharray?.every(finiteNonnegative) ? ` stroke-dasharray="${path.strokeDasharray.map(number).join(" ")}"` : "";
|
|
207
|
-
const linecap = path.strokeLinecap ? ` stroke-linecap="${path.strokeLinecap}"` : "";
|
|
208
|
-
const linejoin = path.strokeLinejoin ? ` stroke-linejoin="${path.strokeLinejoin}"` : "";
|
|
209
|
-
const rule = path.fillRule ? ` fill-rule="${path.fillRule}"` : "";
|
|
210
|
-
return `<path d="${path.d}" fill="${fill}" stroke="${stroke}"${width}${fillOpacity}${strokeOpacity}${dash}${linecap}${linejoin}${rule}/>`;
|
|
211
|
-
}
|
|
212
376
|
function transformedUnitBounds([a, b, c, d, e, f]) {
|
|
213
377
|
const points = [
|
|
214
378
|
[e, f],
|
|
@@ -222,31 +386,8 @@ function transformedUnitBounds([a, b, c, d, e, f]) {
|
|
|
222
386
|
const minY = Math.min(...ys);
|
|
223
387
|
return { x: minX, y: minY, width: Math.max(...xs) - minX, height: Math.max(...ys) - minY };
|
|
224
388
|
}
|
|
225
|
-
function
|
|
226
|
-
const values =
|
|
227
|
-
(match) => Number(match[0])
|
|
228
|
-
);
|
|
229
|
-
if (values.length < 2) return void 0;
|
|
230
|
-
const xs = [];
|
|
231
|
-
const ys = [];
|
|
232
|
-
for (let index = 0; index + 1 < values.length; index += 2) {
|
|
233
|
-
xs.push(values[index] ?? 0);
|
|
234
|
-
ys.push(values[index + 1] ?? 0);
|
|
235
|
-
}
|
|
236
|
-
const minX = Math.min(...xs);
|
|
237
|
-
const minY = Math.min(...ys);
|
|
238
|
-
return { x: minX, y: minY, width: Math.max(...xs) - minX, height: Math.max(...ys) - minY };
|
|
239
|
-
}
|
|
240
|
-
function fillBounds(fill) {
|
|
241
|
-
if (fill.points.length === 0) return void 0;
|
|
242
|
-
const xs = fill.points.map(([x]) => x);
|
|
243
|
-
const ys = fill.points.map(([, y]) => y);
|
|
244
|
-
const minX = Math.min(...xs);
|
|
245
|
-
const minY = Math.min(...ys);
|
|
246
|
-
return { x: minX, y: minY, width: Math.max(...xs) - minX, height: Math.max(...ys) - minY };
|
|
247
|
-
}
|
|
248
|
-
function unionBounds(bounds) {
|
|
249
|
-
const values = bounds.filter((value) => Boolean(value));
|
|
389
|
+
function unionBounds(bounds2) {
|
|
390
|
+
const values = bounds2.filter((value) => Boolean(value));
|
|
250
391
|
if (values.length === 0) return void 0;
|
|
251
392
|
const x = Math.min(...values.map((value) => value.x));
|
|
252
393
|
const y = Math.min(...values.map((value) => value.y));
|
|
@@ -277,19 +418,16 @@ function rgbBmp(image) {
|
|
|
277
418
|
}
|
|
278
419
|
return output;
|
|
279
420
|
}
|
|
280
|
-
function safePath(value) {
|
|
281
|
-
return value.length <= 1e6 && /^[\d\s.,+\-eEMmLlCcZz]+$/.test(value);
|
|
282
|
-
}
|
|
283
421
|
function cssColor(value) {
|
|
284
422
|
return /^#[\da-f]{6}$/i.test(value ?? "");
|
|
285
423
|
}
|
|
286
|
-
function
|
|
424
|
+
function finiteNonnegative2(value) {
|
|
287
425
|
return Number.isFinite(value) && (value ?? -1) >= 0;
|
|
288
426
|
}
|
|
289
427
|
function unitInterval(value) {
|
|
290
|
-
return
|
|
428
|
+
return finiteNonnegative2(value) && value <= 1;
|
|
291
429
|
}
|
|
292
|
-
function
|
|
430
|
+
function number2(value) {
|
|
293
431
|
return Number.isFinite(value) ? String(Math.round(value * 1e3) / 1e3) : "0";
|
|
294
432
|
}
|
|
295
433
|
function escapeHtml2(value) {
|
|
@@ -341,18 +479,38 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
341
479
|
const emitPage = async (page, future) => {
|
|
342
480
|
const defaultColor = dominantTextColor(page.structured.lines);
|
|
343
481
|
let mediaIndex = 0;
|
|
482
|
+
const captions = clearMediaCaptionAssociations(
|
|
483
|
+
page.media,
|
|
484
|
+
page.structured.blocks,
|
|
485
|
+
page.width,
|
|
486
|
+
page.height,
|
|
487
|
+
page.structured.lines
|
|
488
|
+
);
|
|
489
|
+
const captionedMedia = new Set(captions.values());
|
|
344
490
|
const futureFurniture = new Set(future.flatMap((candidate) => marginSignatures(candidate)));
|
|
345
491
|
const repeatedFurniture = /* @__PURE__ */ new Set([...seenFurniture, ...futureFurniture]);
|
|
346
492
|
for (const [blockIndex, block] of page.structured.blocks.entries()) {
|
|
347
493
|
const nextBlock = page.structured.blocks[blockIndex + 1];
|
|
348
494
|
const blockY = semanticBlockY(block);
|
|
495
|
+
let emittedAsCaption = false;
|
|
349
496
|
while ((page.media[mediaIndex]?.bounds.y ?? -Infinity) >= blockY) {
|
|
350
497
|
await flushPendingParagraph();
|
|
351
|
-
const
|
|
498
|
+
const item = page.media[mediaIndex];
|
|
499
|
+
if (item && captions.get(block) === item && block.type === "paragraph") {
|
|
500
|
+
const html2 = `<figure class="pdf-semantic-figure">${item.html}<figcaption>${semanticTextHtml(block.text, block.lines, defaultColor)}</figcaption></figure>`;
|
|
501
|
+
if (activeTable) pendingMedia.push(html2);
|
|
502
|
+
else await write(html2);
|
|
503
|
+
mediaIndex += 1;
|
|
504
|
+
emittedAsCaption = true;
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
if (item && captionedMedia.has(item)) break;
|
|
508
|
+
const html = `<div class="pdf-semantic-visual">${item?.html}</div>`;
|
|
352
509
|
if (activeTable) pendingMedia.push(html);
|
|
353
510
|
else await write(html);
|
|
354
511
|
mediaIndex += 1;
|
|
355
512
|
}
|
|
513
|
+
if (emittedAsCaption) continue;
|
|
356
514
|
if (isRepeatedFurniture(block, page, repeatedFurniture)) {
|
|
357
515
|
stats.suppressedFurniture += 1;
|
|
358
516
|
continue;
|
|
@@ -583,6 +741,14 @@ function financialSummaryRow(entry, columns) {
|
|
|
583
741
|
return `<tr><th scope="row"${colspan}>${escapeHtml3(entry.term)}</th><td>${escapeHtml3(entry.description)}</td></tr>`;
|
|
584
742
|
}
|
|
585
743
|
function semanticBlockHtml(block, defaultColor = "#000000") {
|
|
744
|
+
if (block.type === "insetGroup") {
|
|
745
|
+
return `<div class="pdf-semantic-inset" style="margin-inline-start:${block.indentEm}em">${block.blocks.map((item) => semanticBlockHtml(item, defaultColor)).join("")}</div>`;
|
|
746
|
+
}
|
|
747
|
+
if (block.type === "table") {
|
|
748
|
+
const rows = tableToRows(block.table);
|
|
749
|
+
const header = tableHeader(rows);
|
|
750
|
+
return `<table>${rows.map((row, index) => tableRow(row, Boolean(header && index === 0))).join("")}</table>`;
|
|
751
|
+
}
|
|
586
752
|
if (block.type === "heading")
|
|
587
753
|
return `<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor, false)}</h${block.level}>`;
|
|
588
754
|
if (block.type === "paragraph")
|
|
@@ -698,7 +864,7 @@ async function writePositionedPage(page, write, options) {
|
|
|
698
864
|
const displayWidth = quarterTurn ? page.height : page.width;
|
|
699
865
|
const displayHeight = quarterTurn ? page.width : page.height;
|
|
700
866
|
await write(
|
|
701
|
-
`<section class="pdf-page pdf-page--visual pdf-page--positioned" data-page="${page.number}" data-rotate="${page.rotate}" style="width:${
|
|
867
|
+
`<section class="pdf-page pdf-page--visual pdf-page--positioned" data-page="${page.number}" data-rotate="${page.rotate}" style="width:${number3(displayWidth)}pt;height:${number3(displayHeight)}pt">`
|
|
702
868
|
);
|
|
703
869
|
const fontAliases = visualFontAliases(page.number, page.fonts ?? []);
|
|
704
870
|
const type3Fonts = new Map(
|
|
@@ -710,44 +876,26 @@ async function writePositionedPage(page, write, options) {
|
|
|
710
876
|
);
|
|
711
877
|
}
|
|
712
878
|
await write(
|
|
713
|
-
`<div class="pdf-page-content pdf-page-content--${page.rotate}" style="width:${
|
|
879
|
+
`<div class="pdf-page-content pdf-page-content--${page.rotate}" style="width:${number3(page.width)}pt;height:${number3(page.height)}pt${rotationTransform(page)}">`
|
|
714
880
|
);
|
|
715
881
|
await write(
|
|
716
|
-
`<svg class="pdf-visual-text" xmlns="http://www.w3.org/2000/svg" width="${
|
|
882
|
+
`<svg class="pdf-visual-text" xmlns="http://www.w3.org/2000/svg" width="${number3(page.width)}pt" height="${number3(page.height)}pt" viewBox="0 0 ${number3(page.width)} ${number3(page.height)}">`
|
|
883
|
+
);
|
|
884
|
+
const clipDefinitions = imageClipDefinitions(page.images ?? [], page.number, page.height) + vectorPathClipDefinitions(
|
|
885
|
+
(page.paths ?? []).map((path, index) => ({ path, index })),
|
|
886
|
+
page.number
|
|
717
887
|
);
|
|
718
|
-
const clipDefinitions = imageClipDefinitions(page.images ?? [], page.number, page.height) + pathClipDefinitions(page.paths ?? [], page.number);
|
|
719
888
|
if (clipDefinitions) await write(`<defs>${clipDefinitions}</defs>`);
|
|
720
889
|
if (reflectedOverlay) {
|
|
721
890
|
for (const [index, image] of (page.images ?? []).entries()) {
|
|
722
891
|
await write(visualImage(image, page.height, page.number, index));
|
|
723
892
|
}
|
|
724
893
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
await write(
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
if (page.paths?.length) {
|
|
733
|
-
await write(`<g transform="translate(0 ${number2(page.height)}) scale(1 -1)">`);
|
|
734
|
-
for (const [pathIndex, path] of page.paths.entries()) {
|
|
735
|
-
if (!isSvgPath(path.d)) continue;
|
|
736
|
-
const fill = isCssHexColor(path.fill) ? path.fill : "none";
|
|
737
|
-
const stroke = isCssHexColor(path.stroke) ? path.stroke : "none";
|
|
738
|
-
const strokeWidth = path.strokeWidth !== void 0 && Number.isFinite(path.strokeWidth) && path.strokeWidth >= 0 ? ` stroke-width="${number2(path.strokeWidth)}"` : "";
|
|
739
|
-
const fillRule = path.fillRule ? ` fill-rule="${path.fillRule}"` : "";
|
|
740
|
-
const fillOpacity = isUnitInterval(path.fillOpacity) ? ` fill-opacity="${number2(path.fillOpacity)}"` : "";
|
|
741
|
-
const strokeOpacity = isUnitInterval(path.strokeOpacity) ? ` stroke-opacity="${number2(path.strokeOpacity)}"` : "";
|
|
742
|
-
const dasharray = path.strokeDasharray?.every((value) => Number.isFinite(value) && value >= 0) ? ` stroke-dasharray="${path.strokeDasharray.map(number2).join(" ")}"` : "";
|
|
743
|
-
const dashoffset = Number.isFinite(path.strokeDashoffset) ? ` stroke-dashoffset="${number2(path.strokeDashoffset ?? 0)}"` : "";
|
|
744
|
-
const linecap = path.strokeLinecap ? ` stroke-linecap="${path.strokeLinecap}"` : "";
|
|
745
|
-
const linejoin = path.strokeLinejoin ? ` stroke-linejoin="${path.strokeLinejoin}"` : "";
|
|
746
|
-
let output = `<path d="${path.d}" fill="${fill}" stroke="${stroke}"${strokeWidth}${fillOpacity}${strokeOpacity}${dasharray}${dashoffset}${linecap}${linejoin}${fillRule}/>`;
|
|
747
|
-
for (let index = (path.clips?.length ?? 0) - 1; index >= 0; index -= 1) {
|
|
748
|
-
output = `<g clip-path="url(#${pathClipId(page.number, pathIndex, index)})">${output}</g>`;
|
|
749
|
-
}
|
|
750
|
-
await write(output);
|
|
894
|
+
if (page.fills?.length || page.paths?.length) {
|
|
895
|
+
await write(`<g transform="translate(0 ${number3(page.height)}) scale(1 -1)">`);
|
|
896
|
+
for (const fill of page.fills ?? []) await write(vectorFillSvg(fill));
|
|
897
|
+
for (const [pathIndex, path] of (page.paths ?? []).entries()) {
|
|
898
|
+
await write(vectorPathSvg(path, page.number, pathIndex));
|
|
751
899
|
}
|
|
752
900
|
await write("</g>");
|
|
753
901
|
}
|
|
@@ -777,8 +925,8 @@ function usesReflectedVisualOverlay(page, spans) {
|
|
|
777
925
|
}
|
|
778
926
|
function visualImage(image, pageHeight, pageNumber, imageIndex) {
|
|
779
927
|
const [a, b, c, d, e, f] = image.transform;
|
|
780
|
-
const transform = [a, -b, -c, d, c + e, pageHeight - d - f].map(
|
|
781
|
-
const opacity =
|
|
928
|
+
const transform = [a, -b, -c, d, c + e, pageHeight - d - f].map(number3).join(" ");
|
|
929
|
+
const opacity = isUnitInterval2(image.opacity) ? ` opacity="${number3(image.opacity)}"` : "";
|
|
782
930
|
const mime = image.format === "jpeg" ? "image/jpeg" : "image/bmp";
|
|
783
931
|
const data = image.format === "jpeg" ? image.data : rgbBmp2(image);
|
|
784
932
|
let output = `<image width="1" height="1" preserveAspectRatio="none" transform="matrix(${transform})" href="data:${mime};base64,${base64(data)}"${opacity}/>`;
|
|
@@ -792,25 +940,13 @@ function imageClipDefinitions(images, pageNumber, pageHeight) {
|
|
|
792
940
|
(image, imageIndex) => (image.clips ?? []).map((clip, clipIndex) => {
|
|
793
941
|
if (!isSvgPath(clip.d)) return "";
|
|
794
942
|
const fillRule = clip.fillRule ? ` clip-rule="${clip.fillRule}"` : "";
|
|
795
|
-
return `<clipPath id="${imageClipId(pageNumber, imageIndex, clipIndex)}" clipPathUnits="userSpaceOnUse"><path d="${clip.d}" transform="translate(0 ${
|
|
943
|
+
return `<clipPath id="${imageClipId(pageNumber, imageIndex, clipIndex)}" clipPathUnits="userSpaceOnUse"><path d="${clip.d}" transform="translate(0 ${number3(pageHeight)}) scale(1 -1)"${fillRule}/></clipPath>`;
|
|
796
944
|
})
|
|
797
945
|
).join("");
|
|
798
946
|
}
|
|
799
947
|
function imageClipId(pageNumber, imageIndex, clipIndex) {
|
|
800
948
|
return `boxpdf-clip-${pageNumber}-${imageIndex}-${clipIndex}`;
|
|
801
949
|
}
|
|
802
|
-
function pathClipDefinitions(paths, pageNumber) {
|
|
803
|
-
return paths.flatMap(
|
|
804
|
-
(path, pathIndex) => (path.clips ?? []).map((clip, clipIndex) => {
|
|
805
|
-
if (!isSvgPath(clip.d)) return "";
|
|
806
|
-
const fillRule = clip.fillRule ? ` clip-rule="${clip.fillRule}"` : "";
|
|
807
|
-
return `<clipPath id="${pathClipId(pageNumber, pathIndex, clipIndex)}" clipPathUnits="userSpaceOnUse"><path d="${clip.d}"${fillRule}/></clipPath>`;
|
|
808
|
-
})
|
|
809
|
-
).join("");
|
|
810
|
-
}
|
|
811
|
-
function pathClipId(pageNumber, pathIndex, clipIndex) {
|
|
812
|
-
return `boxpdf-path-clip-${pageNumber}-${pathIndex}-${clipIndex}`;
|
|
813
|
-
}
|
|
814
950
|
function rgbBmp2(image) {
|
|
815
951
|
const stride = Math.ceil(image.width * 3 / 4) * 4;
|
|
816
952
|
const output = new Uint8Array(54 + stride * image.height);
|
|
@@ -839,11 +975,11 @@ function rgbBmp2(image) {
|
|
|
839
975
|
function rotationTransform(page) {
|
|
840
976
|
switch (page.rotate) {
|
|
841
977
|
case 90:
|
|
842
|
-
return `;transform:translate(${
|
|
978
|
+
return `;transform:translate(${number3(page.height)}pt,0) rotate(90deg)`;
|
|
843
979
|
case 180:
|
|
844
|
-
return `;transform:translate(${
|
|
980
|
+
return `;transform:translate(${number3(page.width)}pt,${number3(page.height)}pt) rotate(180deg)`;
|
|
845
981
|
case 270:
|
|
846
|
-
return `;transform:translate(0,${
|
|
982
|
+
return `;transform:translate(0,${number3(page.width)}pt) rotate(270deg)`;
|
|
847
983
|
default:
|
|
848
984
|
return "";
|
|
849
985
|
}
|
|
@@ -851,13 +987,13 @@ function rotationTransform(page) {
|
|
|
851
987
|
function positionedSpan(span, fontAliases) {
|
|
852
988
|
const direction = directionAttribute([span]);
|
|
853
989
|
const style = [
|
|
854
|
-
`left:${
|
|
855
|
-
`bottom:${
|
|
856
|
-
`width:${
|
|
857
|
-
`height:${
|
|
858
|
-
`font-size:${
|
|
859
|
-
...
|
|
860
|
-
...
|
|
990
|
+
`left:${number3(span.bounds.x)}pt`,
|
|
991
|
+
`bottom:${number3(span.bounds.y)}pt`,
|
|
992
|
+
`width:${number3(span.bounds.width)}pt`,
|
|
993
|
+
`height:${number3(span.bounds.height)}pt`,
|
|
994
|
+
`font-size:${number3(span.fontSize)}pt`,
|
|
995
|
+
...isCssHexColor2(span.color) ? [`color:${span.color}`] : [],
|
|
996
|
+
...isUnitInterval2(span.fillOpacity) ? [`opacity:${number3(span.fillOpacity)}`] : [],
|
|
861
997
|
...visualFontStyles(
|
|
862
998
|
span.fontFamily,
|
|
863
999
|
span.fontAssetId ? fontAliases.get(span.fontAssetId) : void 0
|
|
@@ -870,15 +1006,35 @@ async function writeFlowPage(page, write) {
|
|
|
870
1006
|
const structured = structurePage2(withoutSemanticMediaSpans(page, media));
|
|
871
1007
|
const defaultColor = dominantTextColor(structured.lines);
|
|
872
1008
|
let mediaIndex = 0;
|
|
1009
|
+
const captions = clearMediaCaptionAssociations(
|
|
1010
|
+
media,
|
|
1011
|
+
structured.blocks,
|
|
1012
|
+
page.width,
|
|
1013
|
+
page.height,
|
|
1014
|
+
structured.lines
|
|
1015
|
+
);
|
|
1016
|
+
const captionedMedia = new Set(captions.values());
|
|
873
1017
|
await write(
|
|
874
1018
|
`<section class="pdf-page pdf-page--semantic pdf-page--flow" data-page="${page.number}">`
|
|
875
1019
|
);
|
|
876
1020
|
for (const block of structured.blocks) {
|
|
877
1021
|
const blockY = semanticBlockY2(block);
|
|
1022
|
+
let emittedAsCaption = false;
|
|
878
1023
|
while ((media[mediaIndex]?.bounds.y ?? -Infinity) >= blockY) {
|
|
879
|
-
|
|
1024
|
+
const item = media[mediaIndex];
|
|
1025
|
+
if (item && captions.get(block) === item && block.type === "paragraph") {
|
|
1026
|
+
await write(
|
|
1027
|
+
`<figure class="pdf-semantic-figure">${item.html}<figcaption>${semanticTextHtml(block.text, block.lines, defaultColor)}</figcaption></figure>`
|
|
1028
|
+
);
|
|
1029
|
+
mediaIndex += 1;
|
|
1030
|
+
emittedAsCaption = true;
|
|
1031
|
+
break;
|
|
1032
|
+
}
|
|
1033
|
+
if (item && captionedMedia.has(item)) break;
|
|
1034
|
+
await write(`<div class="pdf-semantic-visual">${item?.html}</div>`);
|
|
880
1035
|
mediaIndex += 1;
|
|
881
1036
|
}
|
|
1037
|
+
if (emittedAsCaption) continue;
|
|
882
1038
|
if (block.type === "table") await write(tableToHtml(block.table));
|
|
883
1039
|
else if (block.type === "heading") {
|
|
884
1040
|
await write(
|
|
@@ -918,6 +1074,10 @@ async function writeFlowPage(page, write) {
|
|
|
918
1074
|
await write(
|
|
919
1075
|
`<section><h3>${escapeHtml4(block.role)}</h3><p>${escapeHtml4(block.organization)}</p><p>${escapeHtml4(block.date)}</p></section>`
|
|
920
1076
|
);
|
|
1077
|
+
} else if (block.type === "insetGroup") {
|
|
1078
|
+
await write(
|
|
1079
|
+
`<div class="pdf-semantic-inset" style="margin-inline-start:${number3(block.indentEm)}em">${block.blocks.map((item) => nestedSemanticBlockHtml(item, defaultColor)).join("")}</div>`
|
|
1080
|
+
);
|
|
921
1081
|
} else {
|
|
922
1082
|
const tag = block.ordered ? "ol" : "ul";
|
|
923
1083
|
await write(`<${tag}>`);
|
|
@@ -933,6 +1093,33 @@ async function writeFlowPage(page, write) {
|
|
|
933
1093
|
}
|
|
934
1094
|
await write("</section>");
|
|
935
1095
|
}
|
|
1096
|
+
function nestedSemanticBlockHtml(block, defaultColor) {
|
|
1097
|
+
if (block.type === "insetGroup") {
|
|
1098
|
+
return `<div class="pdf-semantic-inset" style="margin-inline-start:${number3(block.indentEm)}em">${block.blocks.map((item) => nestedSemanticBlockHtml(item, defaultColor)).join("")}</div>`;
|
|
1099
|
+
}
|
|
1100
|
+
if (block.type === "table") return tableToHtml(block.table);
|
|
1101
|
+
if (block.type === "heading") {
|
|
1102
|
+
return `<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor, false)}</h${block.level}>`;
|
|
1103
|
+
}
|
|
1104
|
+
if (block.type === "paragraph") {
|
|
1105
|
+
return `<p>${semanticTextHtml(block.text, block.lines, defaultColor)}</p>`;
|
|
1106
|
+
}
|
|
1107
|
+
if (block.type === "preformatted") return `<pre>${escapeHtml4(block.text)}</pre>`;
|
|
1108
|
+
if (block.type === "definitionList") {
|
|
1109
|
+
return `<dl>${block.entries.map((entry) => `<div><dt>${escapeHtml4(entry.term)}</dt><dd>${escapeHtml4(entry.description)}</dd></div>`).join("")}</dl>`;
|
|
1110
|
+
}
|
|
1111
|
+
if (block.type === "cardList") {
|
|
1112
|
+
return `<div class="pdf-semantic-cards">${block.items.map((item) => `<article><h3>${escapeHtml4(item.title)}</h3>${item.details.map((detail) => `<p>${escapeHtml4(detail)}</p>`).join("")}</article>`).join("")}</div>`;
|
|
1113
|
+
}
|
|
1114
|
+
if (block.type === "sectionGroup") {
|
|
1115
|
+
return `<div class="pdf-semantic-sections">${block.items.map((item) => `<section><h3>${escapeHtml4(item.label)}</h3>${item.content.map((content) => `<p>${escapeHtml4(content)}</p>`).join("")}</section>`).join("")}</div>`;
|
|
1116
|
+
}
|
|
1117
|
+
if (block.type === "employment") {
|
|
1118
|
+
return `<section><h3>${escapeHtml4(block.role)}</h3><p>${escapeHtml4(block.organization)}</p><p>${escapeHtml4(block.date)}</p></section>`;
|
|
1119
|
+
}
|
|
1120
|
+
const tag = block.ordered ? "ol" : "ul";
|
|
1121
|
+
return `<${tag}>${block.items.map((item) => `<li>${semanticTextHtml(item.text, item.lines, defaultColor)}</li>`).join("")}</${tag}>`;
|
|
1122
|
+
}
|
|
936
1123
|
function semanticBlockY2(block) {
|
|
937
1124
|
const lines = block.type === "list" ? block.items.flatMap((item) => item.lines) : block.lines;
|
|
938
1125
|
return Math.max(...lines.map((line) => line.bounds.y + line.bounds.height));
|
|
@@ -945,15 +1132,15 @@ function visualText(span, pageHeight, fontAliases, counterRotateReflectedText =
|
|
|
945
1132
|
span.fontFamily,
|
|
946
1133
|
span.fontAssetId ? fontAliases.get(span.fontAssetId) : void 0
|
|
947
1134
|
).join(";");
|
|
948
|
-
const stroke =
|
|
949
|
-
const strokeWidth = stroke && Number.isFinite(span.strokeWidth) && (span.strokeWidth ?? -1) >= 0 ? `stroke-width:${
|
|
1135
|
+
const stroke = isCssHexColor2(span.strokeColor) ? `stroke:${span.strokeColor}` : "";
|
|
1136
|
+
const strokeWidth = stroke && Number.isFinite(span.strokeWidth) && (span.strokeWidth ?? -1) >= 0 ? `stroke-width:${number3(span.strokeWidth ?? 0)}` : "";
|
|
950
1137
|
const strokeOnly = span.renderingMode === 1 || span.renderingMode === 5;
|
|
951
|
-
const fillOpacity =
|
|
952
|
-
const strokeOpacity =
|
|
1138
|
+
const fillOpacity = isUnitInterval2(span.fillOpacity) ? `fill-opacity:${number3(span.fillOpacity)}` : "";
|
|
1139
|
+
const strokeOpacity = isUnitInterval2(span.strokeOpacity) ? `stroke-opacity:${number3(span.strokeOpacity)}` : "";
|
|
953
1140
|
const style = [
|
|
954
1141
|
isHebrewPaintOrder(span) ? "unicode-bidi:bidi-override;direction:ltr" : "",
|
|
955
1142
|
span.direction === "ttb" ? "writing-mode:vertical-rl" : "",
|
|
956
|
-
strokeOnly ? "fill:none" :
|
|
1143
|
+
strokeOnly ? "fill:none" : isCssHexColor2(span.color) ? `fill:${span.color}` : "",
|
|
957
1144
|
stroke,
|
|
958
1145
|
strokeWidth,
|
|
959
1146
|
fillOpacity,
|
|
@@ -961,7 +1148,7 @@ function visualText(span, pageHeight, fontAliases, counterRotateReflectedText =
|
|
|
961
1148
|
font
|
|
962
1149
|
].filter(Boolean).join(";");
|
|
963
1150
|
const textExtent = span.direction === "ttb" ? span.bounds.height : span.bounds.width;
|
|
964
|
-
const textLength = textExtent > 0 && !isHebrewPaintOrder(span) ? ` textLength="${
|
|
1151
|
+
const textLength = textExtent > 0 && !isHebrewPaintOrder(span) ? ` textLength="${number3(textExtent)}" lengthAdjust="${span.direction === "ttb" || usesSpacingAdjustment(span) ? "spacing" : "spacingAndGlyphs"}"` : "";
|
|
965
1152
|
const transform = counterRotateReflectedText && span.transform ? [
|
|
966
1153
|
span.transform[0],
|
|
967
1154
|
span.transform[1],
|
|
@@ -974,8 +1161,8 @@ function visualText(span, pageHeight, fontAliases, counterRotateReflectedText =
|
|
|
974
1161
|
const basisY = transform?.[1] ?? 0;
|
|
975
1162
|
const anchorX = span.bounds.x + basisX * rtlOffset;
|
|
976
1163
|
const anchorY = pageHeight - span.bounds.y + basisY * rtlOffset;
|
|
977
|
-
const position = transformed ? ` x="0" y="0" transform="matrix(${transform?.map(
|
|
978
|
-
return `<text${direction}${position} font-size="${
|
|
1164
|
+
const position = transformed ? ` x="0" y="0" transform="matrix(${transform?.map(number3).join(" ")} ${number3(anchorX)} ${number3(anchorY)})"` : ` x="${number3(anchorX)}" y="${number3(anchorY)}"`;
|
|
1165
|
+
return `<text${direction}${position} font-size="${number3(span.fontSize)}"${textLength}${style ? ` style="${style}"` : ""}>${escapeHtml4(span.text)}</text>`;
|
|
979
1166
|
}
|
|
980
1167
|
function isAdobeCjkFont(fontFamily) {
|
|
981
1168
|
return /^Adobe(?:Heiti|Song|Kaiti|Ming|Gothic|Mincho)Std-/i.test(fontFamily ?? "");
|
|
@@ -987,16 +1174,16 @@ function visualType3Text(span, font, pageHeight) {
|
|
|
987
1174
|
const totalAdvance = sequence.reduce((total, glyph) => total + (glyph?.advance ?? 0), 0);
|
|
988
1175
|
if (totalAdvance <= 0 || span.bounds.width <= 0 || span.fontSize <= 0) return "";
|
|
989
1176
|
const transform = span.transform ?? [1, 0, 0, 1];
|
|
990
|
-
const outer = `matrix(${transform.map(
|
|
1177
|
+
const outer = `matrix(${transform.map(number3).join(" ")} ${number3(span.bounds.x)} ${number3(pageHeight - span.bounds.y)})`;
|
|
991
1178
|
const xScale = span.bounds.width / totalAdvance;
|
|
992
1179
|
let offset = 0;
|
|
993
1180
|
let content = "";
|
|
994
1181
|
for (const glyph of sequence) {
|
|
995
1182
|
if (!glyph) continue;
|
|
996
|
-
content += `<g transform="translate(${
|
|
1183
|
+
content += `<g transform="translate(${number3(offset)} 0)">${type3Glyph(glyph, span.color)}</g>`;
|
|
997
1184
|
offset += glyph.advance;
|
|
998
1185
|
}
|
|
999
|
-
return `<g transform="${outer}"><g transform="scale(${
|
|
1186
|
+
return `<g transform="${outer}"><g transform="scale(${number3(xScale)} ${number3(-span.fontSize)})">${content}</g></g>`;
|
|
1000
1187
|
}
|
|
1001
1188
|
function isHebrewPaintOrder(span) {
|
|
1002
1189
|
return span.direction === "ltr" && /[\u0590-\u05ff]/u.test(span.text);
|
|
@@ -1007,30 +1194,27 @@ function usesSpacingAdjustment(span) {
|
|
|
1007
1194
|
function type3Glyph(glyph, textColor) {
|
|
1008
1195
|
let output = "";
|
|
1009
1196
|
for (const fill of glyph.fills ?? []) {
|
|
1010
|
-
const color = glyph.usesTextColor &&
|
|
1011
|
-
if (!
|
|
1012
|
-
const points = fill.points.map(([x, y]) => `${
|
|
1013
|
-
const opacity =
|
|
1197
|
+
const color = glyph.usesTextColor && isCssHexColor2(textColor) ? textColor : fill.color;
|
|
1198
|
+
if (!isCssHexColor2(color)) continue;
|
|
1199
|
+
const points = fill.points.map(([x, y]) => `${number3(x)},${number3(y)}`).join(" ");
|
|
1200
|
+
const opacity = isUnitInterval2(fill.opacity) ? ` fill-opacity="${number3(fill.opacity)}"` : "";
|
|
1014
1201
|
output += `<polygon points="${points}" fill="${color}"${opacity}/>`;
|
|
1015
1202
|
}
|
|
1016
1203
|
for (const path of glyph.paths ?? []) {
|
|
1017
1204
|
if (!isSvgPath(path.d)) continue;
|
|
1018
|
-
const fill = glyph.usesTextColor &&
|
|
1019
|
-
const stroke = glyph.usesTextColor &&
|
|
1020
|
-
const width = path.strokeWidth !== void 0 && Number.isFinite(path.strokeWidth) && path.strokeWidth >= 0 ? ` stroke-width="${
|
|
1205
|
+
const fill = glyph.usesTextColor && isCssHexColor2(textColor) ? textColor : isCssHexColor2(path.fill) ? path.fill : "none";
|
|
1206
|
+
const stroke = glyph.usesTextColor && isCssHexColor2(textColor) && path.stroke ? textColor : isCssHexColor2(path.stroke) ? path.stroke : "none";
|
|
1207
|
+
const width = path.strokeWidth !== void 0 && Number.isFinite(path.strokeWidth) && path.strokeWidth >= 0 ? ` stroke-width="${number3(path.strokeWidth)}"` : "";
|
|
1021
1208
|
output += `<path d="${path.d}" fill="${fill}" stroke="${stroke}"${width}/>`;
|
|
1022
1209
|
}
|
|
1023
1210
|
return (glyph.fills?.length ?? 0) > 64 && glyph.advance > 2 ? `<g shape-rendering="crispEdges">${output}</g>` : output;
|
|
1024
1211
|
}
|
|
1025
|
-
function
|
|
1212
|
+
function isCssHexColor2(value) {
|
|
1026
1213
|
return /^#[\da-f]{6}$/i.test(value ?? "");
|
|
1027
1214
|
}
|
|
1028
|
-
function
|
|
1215
|
+
function isUnitInterval2(value) {
|
|
1029
1216
|
return Number.isFinite(value) && (value ?? -1) >= 0 && (value ?? 2) <= 1;
|
|
1030
1217
|
}
|
|
1031
|
-
function isSvgPath(value) {
|
|
1032
|
-
return value.length <= 1e6 && /^[\d\s.,+\-eEMmLlCcZz]+$/.test(value);
|
|
1033
|
-
}
|
|
1034
1218
|
function isMonospace(fontFamily) {
|
|
1035
1219
|
return /courier|mono/i.test(fontFamily ?? "");
|
|
1036
1220
|
}
|
|
@@ -1048,7 +1232,7 @@ function directionAttribute(spans) {
|
|
|
1048
1232
|
if (vertical > rtl && vertical * 2 >= spans.length) return ' data-direction="ttb"';
|
|
1049
1233
|
return rtl * 2 >= spans.length && spans.length > 0 ? ' dir="rtl"' : "";
|
|
1050
1234
|
}
|
|
1051
|
-
function
|
|
1235
|
+
function number3(value) {
|
|
1052
1236
|
return Number.isFinite(value) ? String(Math.round(value * 1e3) / 1e3) : "0";
|
|
1053
1237
|
}
|
|
1054
1238
|
function escapeAttribute(value) {
|