@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.cjs
CHANGED
|
@@ -27,6 +27,64 @@ __export(index_exports, {
|
|
|
27
27
|
module.exports = __toCommonJS(index_exports);
|
|
28
28
|
var import_structure2 = require("@boxpdf/reader/structure");
|
|
29
29
|
|
|
30
|
+
// src/semantic-caption.ts
|
|
31
|
+
function isClearMediaCaption(media, block, pageWidth, pageHeight, pageLines) {
|
|
32
|
+
if (block.type !== "paragraph" || block.lines.length === 0) return false;
|
|
33
|
+
const bounds2 = unionLines(block.lines);
|
|
34
|
+
const lineHeight = median(block.lines.map((line) => line.bounds.height));
|
|
35
|
+
if (media.bounds.width < pageWidth * 0.2 || media.bounds.height < lineHeight * 10) return false;
|
|
36
|
+
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)
|
|
37
|
+
return false;
|
|
38
|
+
const gap = media.bounds.y - (bounds2.y + bounds2.height);
|
|
39
|
+
if (gap < -lineHeight * 0.15 || gap > lineHeight * 1.25) return false;
|
|
40
|
+
const mediaCenter = media.bounds.x + media.bounds.width / 2;
|
|
41
|
+
const captionCenter = bounds2.x + bounds2.width / 2;
|
|
42
|
+
if (Math.abs(mediaCenter - captionCenter) > Math.max(3, media.bounds.width * 0.03)) return false;
|
|
43
|
+
if (bounds2.width < media.bounds.width * 0.45 || bounds2.width > media.bounds.width * 1.06) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const first = block.lines.flatMap((line) => line.spans).find((span) => /\S/u.test(span.text));
|
|
47
|
+
if (!first) return false;
|
|
48
|
+
const otherLines = pageLines.filter((line) => !block.lines.includes(line));
|
|
49
|
+
return fontSignature(first) !== dominantFontSignature(otherLines);
|
|
50
|
+
}
|
|
51
|
+
function clearMediaCaptionAssociations(media, blocks, pageWidth, pageHeight, pageLines) {
|
|
52
|
+
const associations = /* @__PURE__ */ new Map();
|
|
53
|
+
for (const item of media) {
|
|
54
|
+
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));
|
|
55
|
+
const caption = candidates[0];
|
|
56
|
+
if (caption) associations.set(caption, item);
|
|
57
|
+
}
|
|
58
|
+
return associations;
|
|
59
|
+
}
|
|
60
|
+
function captionGap(media, block) {
|
|
61
|
+
if (block.type !== "paragraph") return Number.POSITIVE_INFINITY;
|
|
62
|
+
const bounds2 = unionLines(block.lines);
|
|
63
|
+
return Math.abs(media.bounds.y - bounds2.y - bounds2.height);
|
|
64
|
+
}
|
|
65
|
+
function unionLines(lines) {
|
|
66
|
+
const x = Math.min(...lines.map((line) => line.bounds.x));
|
|
67
|
+
const y = Math.min(...lines.map((line) => line.bounds.y));
|
|
68
|
+
const right = Math.max(...lines.map((line) => line.bounds.x + line.bounds.width));
|
|
69
|
+
const top = Math.max(...lines.map((line) => line.bounds.y + line.bounds.height));
|
|
70
|
+
return { x, y, width: right - x, height: top - y };
|
|
71
|
+
}
|
|
72
|
+
function dominantFontSignature(lines) {
|
|
73
|
+
const counts = /* @__PURE__ */ new Map();
|
|
74
|
+
for (const span of lines.flatMap((line) => line.spans)) {
|
|
75
|
+
const signature = fontSignature(span);
|
|
76
|
+
counts.set(signature, (counts.get(signature) ?? 0) + Math.max(1, [...span.text].length));
|
|
77
|
+
}
|
|
78
|
+
return [...counts].sort((left, right) => right[1] - left[1])[0]?.[0] ?? "";
|
|
79
|
+
}
|
|
80
|
+
function fontSignature(span) {
|
|
81
|
+
return `${(span.fontFamily ?? span.fontName ?? "").toLocaleLowerCase("en")}|${Math.round(span.fontSize * 2) / 2}|${span.color ?? ""}`;
|
|
82
|
+
}
|
|
83
|
+
function median(values) {
|
|
84
|
+
const ordered = [...values].sort((left, right) => left - right);
|
|
85
|
+
return ordered[Math.floor(ordered.length / 2)] ?? 1;
|
|
86
|
+
}
|
|
87
|
+
|
|
30
88
|
// src/semantic-document.ts
|
|
31
89
|
var import_structure = require("@boxpdf/reader/structure");
|
|
32
90
|
|
|
@@ -99,6 +157,85 @@ function escapeHtml(value) {
|
|
|
99
157
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
100
158
|
}
|
|
101
159
|
|
|
160
|
+
// src/vector-svg.ts
|
|
161
|
+
function vectorFillSvg(fill) {
|
|
162
|
+
if (!isCssHexColor(fill.color)) return "";
|
|
163
|
+
const points = fill.points.map(([x, y]) => `${number(x)},${number(y)}`).join(" ");
|
|
164
|
+
const opacity = isUnitInterval(fill.opacity) ? ` fill-opacity="${number(fill.opacity)}"` : "";
|
|
165
|
+
return `<polygon points="${points}" fill="${fill.color}"${opacity}/>`;
|
|
166
|
+
}
|
|
167
|
+
function vectorPathSvg(path, pageNumber, pathIndex) {
|
|
168
|
+
if (!isSvgPath(path.d)) return "";
|
|
169
|
+
const fill = isCssHexColor(path.fill) ? path.fill : "none";
|
|
170
|
+
const stroke = isCssHexColor(path.stroke) ? path.stroke : "none";
|
|
171
|
+
const width = finiteNonnegative(path.strokeWidth) ? ` stroke-width="${number(path.strokeWidth)}"` : "";
|
|
172
|
+
const fillOpacity = isUnitInterval(path.fillOpacity) ? ` fill-opacity="${number(path.fillOpacity)}"` : "";
|
|
173
|
+
const strokeOpacity = isUnitInterval(path.strokeOpacity) ? ` stroke-opacity="${number(path.strokeOpacity)}"` : "";
|
|
174
|
+
const dash = path.strokeDasharray?.every(finiteNonnegative) ? ` stroke-dasharray="${path.strokeDasharray.map(number).join(" ")}"` : "";
|
|
175
|
+
const dashoffset = Number.isFinite(path.strokeDashoffset) ? ` stroke-dashoffset="${number(path.strokeDashoffset ?? 0)}"` : "";
|
|
176
|
+
const linecap = path.strokeLinecap ? ` stroke-linecap="${path.strokeLinecap}"` : "";
|
|
177
|
+
const linejoin = path.strokeLinejoin ? ` stroke-linejoin="${path.strokeLinejoin}"` : "";
|
|
178
|
+
const rule = path.fillRule ? ` fill-rule="${path.fillRule}"` : "";
|
|
179
|
+
let output = `<path d="${path.d}" fill="${fill}" stroke="${stroke}"${width}${fillOpacity}${strokeOpacity}${dash}${dashoffset}${linecap}${linejoin}${rule}/>`;
|
|
180
|
+
for (let index = (path.clips?.length ?? 0) - 1; index >= 0; index -= 1) {
|
|
181
|
+
output = `<g clip-path="url(#${vectorPathClipId(pageNumber, pathIndex, index)})">${output}</g>`;
|
|
182
|
+
}
|
|
183
|
+
return output;
|
|
184
|
+
}
|
|
185
|
+
function vectorPathClipDefinitions(paths, pageNumber) {
|
|
186
|
+
return paths.flatMap(
|
|
187
|
+
({ path, index: pathIndex }) => (path.clips ?? []).map((clip, clipIndex) => {
|
|
188
|
+
if (!isSvgPath(clip.d)) return "";
|
|
189
|
+
const rule = clip.fillRule ? ` clip-rule="${clip.fillRule}"` : "";
|
|
190
|
+
return `<clipPath id="${vectorPathClipId(pageNumber, pathIndex, clipIndex)}" clipPathUnits="userSpaceOnUse"><path d="${clip.d}"${rule}/></clipPath>`;
|
|
191
|
+
})
|
|
192
|
+
).join("");
|
|
193
|
+
}
|
|
194
|
+
function vectorPathBounds(path) {
|
|
195
|
+
if (!isSvgPath(path.d)) return void 0;
|
|
196
|
+
const values = [...path.d.matchAll(/[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?/gi)].map(
|
|
197
|
+
(match) => Number(match[0])
|
|
198
|
+
);
|
|
199
|
+
if (values.length < 2) return void 0;
|
|
200
|
+
const xs = [];
|
|
201
|
+
const ys = [];
|
|
202
|
+
for (let index = 0; index + 1 < values.length; index += 2) {
|
|
203
|
+
xs.push(values[index] ?? 0);
|
|
204
|
+
ys.push(values[index + 1] ?? 0);
|
|
205
|
+
}
|
|
206
|
+
return bounds(xs, ys);
|
|
207
|
+
}
|
|
208
|
+
function vectorFillBounds(fill) {
|
|
209
|
+
if (fill.points.length === 0) return void 0;
|
|
210
|
+
return bounds(
|
|
211
|
+
fill.points.map(([x]) => x),
|
|
212
|
+
fill.points.map(([, y]) => y)
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
function isSvgPath(value) {
|
|
216
|
+
return value.length <= 1e6 && /^[\d\s.,+\-eEMmLlCcZz]+$/.test(value);
|
|
217
|
+
}
|
|
218
|
+
function bounds(xs, ys) {
|
|
219
|
+
const x = Math.min(...xs);
|
|
220
|
+
const y = Math.min(...ys);
|
|
221
|
+
return { x, y, width: Math.max(...xs) - x, height: Math.max(...ys) - y };
|
|
222
|
+
}
|
|
223
|
+
function vectorPathClipId(pageNumber, pathIndex, clipIndex) {
|
|
224
|
+
return `boxpdf-path-clip-${pageNumber}-${pathIndex}-${clipIndex}`;
|
|
225
|
+
}
|
|
226
|
+
function isCssHexColor(value) {
|
|
227
|
+
return typeof value === "string" && /^#[0-9a-f]{6}$/i.test(value);
|
|
228
|
+
}
|
|
229
|
+
function finiteNonnegative(value) {
|
|
230
|
+
return value !== void 0 && Number.isFinite(value) && value >= 0;
|
|
231
|
+
}
|
|
232
|
+
function isUnitInterval(value) {
|
|
233
|
+
return value !== void 0 && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
234
|
+
}
|
|
235
|
+
function number(value) {
|
|
236
|
+
return Number(value.toFixed(4)).toString();
|
|
237
|
+
}
|
|
238
|
+
|
|
102
239
|
// src/visual-font.ts
|
|
103
240
|
function visualFontAliases(pageNumber, fonts) {
|
|
104
241
|
return new Map(
|
|
@@ -148,46 +285,90 @@ function base64(bytes) {
|
|
|
148
285
|
// src/semantic-media.ts
|
|
149
286
|
function semanticMedia(page) {
|
|
150
287
|
const output = (page.images ?? []).map((image) => rasterMedia(image));
|
|
151
|
-
|
|
152
|
-
if (vector) output.push(vector);
|
|
288
|
+
output.push(...vectorMedia(page));
|
|
153
289
|
return output.sort((left, right) => right.bounds.y - left.bounds.y);
|
|
154
290
|
}
|
|
155
291
|
function rasterMedia(image) {
|
|
156
|
-
const
|
|
292
|
+
const bounds2 = transformedUnitBounds(image.transform);
|
|
157
293
|
const mime = image.format === "jpeg" ? "image/jpeg" : "image/bmp";
|
|
158
294
|
const data = image.format === "jpeg" ? image.data : rgbBmp(image);
|
|
159
|
-
const opacity = unitInterval(image.opacity) ? `;opacity:${
|
|
295
|
+
const opacity = unitInterval(image.opacity) ? `;opacity:${number2(image.opacity)}` : "";
|
|
160
296
|
return {
|
|
161
|
-
bounds,
|
|
162
|
-
html: `<img class="pdf-semantic-media" src="data:${mime};base64,${base64(data)}" width="${
|
|
297
|
+
bounds: bounds2,
|
|
298
|
+
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}">`
|
|
163
299
|
};
|
|
164
300
|
}
|
|
165
301
|
function vectorMedia(page) {
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
302
|
+
const primitives = [
|
|
303
|
+
...(page.paths ?? []).flatMap((path, index) => {
|
|
304
|
+
const bounds2 = vectorPathBounds(path);
|
|
305
|
+
return bounds2 ? [{ type: "path", value: path, index, bounds: bounds2 }] : [];
|
|
306
|
+
}),
|
|
307
|
+
...(page.fills ?? []).flatMap((fill) => {
|
|
308
|
+
const bounds2 = vectorFillBounds(fill);
|
|
309
|
+
return bounds2 && !isPageBackground(fill, bounds2, page) ? [{ type: "fill", value: fill, bounds: bounds2 }] : [];
|
|
310
|
+
})
|
|
311
|
+
];
|
|
312
|
+
const components = vectorComponents(primitives, Math.min(36, page.width * 0.06)).filter(
|
|
313
|
+
(component) => component.primitives.length >= 2 || component.bounds.width * component.bounds.height >= page.width * page.height * 2e-3
|
|
314
|
+
);
|
|
173
315
|
const aliases = visualFontAliases(page.number, page.fonts ?? []);
|
|
174
316
|
const visualCodeFonts = new Set(
|
|
175
317
|
(page.fonts ?? []).filter((font) => font.format === "truetype" && font.visualCodeMapping).map((font) => font.id)
|
|
176
318
|
);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
319
|
+
return components.map((component) => {
|
|
320
|
+
const bounds2 = component.bounds;
|
|
321
|
+
const paths = component.primitives.flatMap(
|
|
322
|
+
(primitive) => primitive.type === "path" ? [{ path: primitive.value, index: primitive.index }] : []
|
|
323
|
+
);
|
|
324
|
+
const fills = component.primitives.flatMap(
|
|
325
|
+
(primitive) => primitive.type === "fill" ? [primitive.value] : []
|
|
326
|
+
);
|
|
327
|
+
const visualSpans = page.visualSpans ?? page.spans;
|
|
328
|
+
const overlay = visualSpans.filter(
|
|
329
|
+
(span) => span.fontAssetId && visualCodeFonts.has(span.fontAssetId) && centerInside(span.bounds, bounds2)
|
|
330
|
+
);
|
|
331
|
+
const consumedSpans = page.spans.filter(
|
|
332
|
+
(span) => span.fontAssetId && visualCodeFonts.has(span.fontAssetId) && centerInside(span.bounds, bounds2)
|
|
333
|
+
);
|
|
334
|
+
const fontIds = new Set(overlay.map((span) => span.fontAssetId));
|
|
335
|
+
const fontFaces = (page.fonts ?? []).filter((font) => fontIds.has(font.id)).map((font) => visualFontFace(font, aliases)).join("");
|
|
336
|
+
return {
|
|
337
|
+
bounds: bounds2,
|
|
338
|
+
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>`,
|
|
339
|
+
...consumedSpans.length > 0 ? { consumedSpans } : {}
|
|
340
|
+
};
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
function vectorComponents(primitives, padding) {
|
|
344
|
+
const components = [];
|
|
345
|
+
for (const primitive of primitives) {
|
|
346
|
+
const matches = components.filter(
|
|
347
|
+
(component) => nearby(component.bounds, primitive.bounds, padding)
|
|
348
|
+
);
|
|
349
|
+
if (matches.length === 0) {
|
|
350
|
+
components.push({ bounds: primitive.bounds, primitives: [primitive] });
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
const target = matches[0];
|
|
354
|
+
target.primitives.push(primitive);
|
|
355
|
+
target.bounds = unionBounds([target.bounds, primitive.bounds]);
|
|
356
|
+
for (const component of matches.slice(1)) {
|
|
357
|
+
target.primitives.push(...component.primitives);
|
|
358
|
+
target.bounds = unionBounds([target.bounds, component.bounds]);
|
|
359
|
+
components.splice(components.indexOf(component), 1);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return components;
|
|
363
|
+
}
|
|
364
|
+
function nearby(left, right, padding) {
|
|
365
|
+
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);
|
|
366
|
+
}
|
|
367
|
+
function isPageBackground(fill, bounds2, page) {
|
|
368
|
+
if (!/^#f{6}$/i.test(fill.color)) return false;
|
|
369
|
+
const outside = bounds2.x < 0 || bounds2.y < 0 || bounds2.x + bounds2.width > page.width || bounds2.y + bounds2.height > page.height;
|
|
370
|
+
const large = bounds2.width * bounds2.height > page.width * page.height * 0.2;
|
|
371
|
+
return outside || large;
|
|
191
372
|
}
|
|
192
373
|
function withoutSemanticMediaSpans(page, media) {
|
|
193
374
|
const consumed = new Set(media.flatMap((item) => item.consumedSpans ?? []));
|
|
@@ -197,7 +378,7 @@ function vectorText(span, pageHeight, aliases) {
|
|
|
197
378
|
if (span.renderingMode === 3 || span.renderingMode === 7) return "";
|
|
198
379
|
const styles2 = [
|
|
199
380
|
cssColor(span.color) ? `fill:${span.color}` : "",
|
|
200
|
-
unitInterval(span.fillOpacity) ? `fill-opacity:${
|
|
381
|
+
unitInterval(span.fillOpacity) ? `fill-opacity:${number2(span.fillOpacity)}` : "",
|
|
201
382
|
...visualFontStyles(
|
|
202
383
|
span.fontFamily,
|
|
203
384
|
span.fontAssetId ? aliases.get(span.fontAssetId) : void 0
|
|
@@ -205,33 +386,16 @@ function vectorText(span, pageHeight, aliases) {
|
|
|
205
386
|
].filter(Boolean).join(";");
|
|
206
387
|
const anchorY = pageHeight - span.bounds.y;
|
|
207
388
|
const transform = span.transform;
|
|
208
|
-
const position = transform ? ` x="0" y="0" transform="matrix(${transform.map(
|
|
389
|
+
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)}"`;
|
|
209
390
|
const extent = span.direction === "ttb" ? span.bounds.height : span.bounds.width;
|
|
210
|
-
const length = extent > 0 ? ` textLength="${
|
|
211
|
-
return `<text${position} font-size="${
|
|
391
|
+
const length = extent > 0 ? ` textLength="${number2(extent)}" lengthAdjust="spacingAndGlyphs"` : "";
|
|
392
|
+
return `<text${position} font-size="${number2(span.fontSize)}"${length}${styles2 ? ` style="${styles2}"` : ""}>${escapeHtml2(span.text)}</text>`;
|
|
212
393
|
}
|
|
213
394
|
function centerInside(inner, outer) {
|
|
214
395
|
const x = inner.x + inner.width / 2;
|
|
215
396
|
const y = inner.y + inner.height / 2;
|
|
216
397
|
return x >= outer.x && x <= outer.x + outer.width && y >= outer.y && y <= outer.y + outer.height;
|
|
217
398
|
}
|
|
218
|
-
function vectorFill(fill) {
|
|
219
|
-
const points = fill.points.map(([x, y]) => `${number(x)},${number(y)}`).join(" ");
|
|
220
|
-
const opacity = unitInterval(fill.opacity) ? ` fill-opacity="${number(fill.opacity)}"` : "";
|
|
221
|
-
return cssColor(fill.color) ? `<polygon points="${points}" fill="${fill.color}"${opacity}/>` : "";
|
|
222
|
-
}
|
|
223
|
-
function vectorPath(path) {
|
|
224
|
-
const fill = cssColor(path.fill) ? path.fill : "none";
|
|
225
|
-
const stroke = cssColor(path.stroke) ? path.stroke : "none";
|
|
226
|
-
const width = finiteNonnegative(path.strokeWidth) ? ` stroke-width="${number(path.strokeWidth)}"` : "";
|
|
227
|
-
const fillOpacity = unitInterval(path.fillOpacity) ? ` fill-opacity="${number(path.fillOpacity)}"` : "";
|
|
228
|
-
const strokeOpacity = unitInterval(path.strokeOpacity) ? ` stroke-opacity="${number(path.strokeOpacity)}"` : "";
|
|
229
|
-
const dash = path.strokeDasharray?.every(finiteNonnegative) ? ` stroke-dasharray="${path.strokeDasharray.map(number).join(" ")}"` : "";
|
|
230
|
-
const linecap = path.strokeLinecap ? ` stroke-linecap="${path.strokeLinecap}"` : "";
|
|
231
|
-
const linejoin = path.strokeLinejoin ? ` stroke-linejoin="${path.strokeLinejoin}"` : "";
|
|
232
|
-
const rule = path.fillRule ? ` fill-rule="${path.fillRule}"` : "";
|
|
233
|
-
return `<path d="${path.d}" fill="${fill}" stroke="${stroke}"${width}${fillOpacity}${strokeOpacity}${dash}${linecap}${linejoin}${rule}/>`;
|
|
234
|
-
}
|
|
235
399
|
function transformedUnitBounds([a, b, c, d, e, f]) {
|
|
236
400
|
const points = [
|
|
237
401
|
[e, f],
|
|
@@ -245,31 +409,8 @@ function transformedUnitBounds([a, b, c, d, e, f]) {
|
|
|
245
409
|
const minY = Math.min(...ys);
|
|
246
410
|
return { x: minX, y: minY, width: Math.max(...xs) - minX, height: Math.max(...ys) - minY };
|
|
247
411
|
}
|
|
248
|
-
function
|
|
249
|
-
const values =
|
|
250
|
-
(match) => Number(match[0])
|
|
251
|
-
);
|
|
252
|
-
if (values.length < 2) return void 0;
|
|
253
|
-
const xs = [];
|
|
254
|
-
const ys = [];
|
|
255
|
-
for (let index = 0; index + 1 < values.length; index += 2) {
|
|
256
|
-
xs.push(values[index] ?? 0);
|
|
257
|
-
ys.push(values[index + 1] ?? 0);
|
|
258
|
-
}
|
|
259
|
-
const minX = Math.min(...xs);
|
|
260
|
-
const minY = Math.min(...ys);
|
|
261
|
-
return { x: minX, y: minY, width: Math.max(...xs) - minX, height: Math.max(...ys) - minY };
|
|
262
|
-
}
|
|
263
|
-
function fillBounds(fill) {
|
|
264
|
-
if (fill.points.length === 0) return void 0;
|
|
265
|
-
const xs = fill.points.map(([x]) => x);
|
|
266
|
-
const ys = fill.points.map(([, y]) => y);
|
|
267
|
-
const minX = Math.min(...xs);
|
|
268
|
-
const minY = Math.min(...ys);
|
|
269
|
-
return { x: minX, y: minY, width: Math.max(...xs) - minX, height: Math.max(...ys) - minY };
|
|
270
|
-
}
|
|
271
|
-
function unionBounds(bounds) {
|
|
272
|
-
const values = bounds.filter((value) => Boolean(value));
|
|
412
|
+
function unionBounds(bounds2) {
|
|
413
|
+
const values = bounds2.filter((value) => Boolean(value));
|
|
273
414
|
if (values.length === 0) return void 0;
|
|
274
415
|
const x = Math.min(...values.map((value) => value.x));
|
|
275
416
|
const y = Math.min(...values.map((value) => value.y));
|
|
@@ -300,19 +441,16 @@ function rgbBmp(image) {
|
|
|
300
441
|
}
|
|
301
442
|
return output;
|
|
302
443
|
}
|
|
303
|
-
function safePath(value) {
|
|
304
|
-
return value.length <= 1e6 && /^[\d\s.,+\-eEMmLlCcZz]+$/.test(value);
|
|
305
|
-
}
|
|
306
444
|
function cssColor(value) {
|
|
307
445
|
return /^#[\da-f]{6}$/i.test(value ?? "");
|
|
308
446
|
}
|
|
309
|
-
function
|
|
447
|
+
function finiteNonnegative2(value) {
|
|
310
448
|
return Number.isFinite(value) && (value ?? -1) >= 0;
|
|
311
449
|
}
|
|
312
450
|
function unitInterval(value) {
|
|
313
|
-
return
|
|
451
|
+
return finiteNonnegative2(value) && value <= 1;
|
|
314
452
|
}
|
|
315
|
-
function
|
|
453
|
+
function number2(value) {
|
|
316
454
|
return Number.isFinite(value) ? String(Math.round(value * 1e3) / 1e3) : "0";
|
|
317
455
|
}
|
|
318
456
|
function escapeHtml2(value) {
|
|
@@ -364,18 +502,38 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
364
502
|
const emitPage = async (page, future) => {
|
|
365
503
|
const defaultColor = dominantTextColor(page.structured.lines);
|
|
366
504
|
let mediaIndex = 0;
|
|
505
|
+
const captions = clearMediaCaptionAssociations(
|
|
506
|
+
page.media,
|
|
507
|
+
page.structured.blocks,
|
|
508
|
+
page.width,
|
|
509
|
+
page.height,
|
|
510
|
+
page.structured.lines
|
|
511
|
+
);
|
|
512
|
+
const captionedMedia = new Set(captions.values());
|
|
367
513
|
const futureFurniture = new Set(future.flatMap((candidate) => marginSignatures(candidate)));
|
|
368
514
|
const repeatedFurniture = /* @__PURE__ */ new Set([...seenFurniture, ...futureFurniture]);
|
|
369
515
|
for (const [blockIndex, block] of page.structured.blocks.entries()) {
|
|
370
516
|
const nextBlock = page.structured.blocks[blockIndex + 1];
|
|
371
517
|
const blockY = semanticBlockY(block);
|
|
518
|
+
let emittedAsCaption = false;
|
|
372
519
|
while ((page.media[mediaIndex]?.bounds.y ?? -Infinity) >= blockY) {
|
|
373
520
|
await flushPendingParagraph();
|
|
374
|
-
const
|
|
521
|
+
const item = page.media[mediaIndex];
|
|
522
|
+
if (item && captions.get(block) === item && block.type === "paragraph") {
|
|
523
|
+
const html2 = `<figure class="pdf-semantic-figure">${item.html}<figcaption>${semanticTextHtml(block.text, block.lines, defaultColor)}</figcaption></figure>`;
|
|
524
|
+
if (activeTable) pendingMedia.push(html2);
|
|
525
|
+
else await write(html2);
|
|
526
|
+
mediaIndex += 1;
|
|
527
|
+
emittedAsCaption = true;
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
if (item && captionedMedia.has(item)) break;
|
|
531
|
+
const html = `<div class="pdf-semantic-visual">${item?.html}</div>`;
|
|
375
532
|
if (activeTable) pendingMedia.push(html);
|
|
376
533
|
else await write(html);
|
|
377
534
|
mediaIndex += 1;
|
|
378
535
|
}
|
|
536
|
+
if (emittedAsCaption) continue;
|
|
379
537
|
if (isRepeatedFurniture(block, page, repeatedFurniture)) {
|
|
380
538
|
stats.suppressedFurniture += 1;
|
|
381
539
|
continue;
|
|
@@ -606,6 +764,14 @@ function financialSummaryRow(entry, columns) {
|
|
|
606
764
|
return `<tr><th scope="row"${colspan}>${escapeHtml3(entry.term)}</th><td>${escapeHtml3(entry.description)}</td></tr>`;
|
|
607
765
|
}
|
|
608
766
|
function semanticBlockHtml(block, defaultColor = "#000000") {
|
|
767
|
+
if (block.type === "insetGroup") {
|
|
768
|
+
return `<div class="pdf-semantic-inset" style="margin-inline-start:${block.indentEm}em">${block.blocks.map((item) => semanticBlockHtml(item, defaultColor)).join("")}</div>`;
|
|
769
|
+
}
|
|
770
|
+
if (block.type === "table") {
|
|
771
|
+
const rows = (0, import_structure.tableToRows)(block.table);
|
|
772
|
+
const header = tableHeader(rows);
|
|
773
|
+
return `<table>${rows.map((row, index) => tableRow(row, Boolean(header && index === 0))).join("")}</table>`;
|
|
774
|
+
}
|
|
609
775
|
if (block.type === "heading")
|
|
610
776
|
return `<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor, false)}</h${block.level}>`;
|
|
611
777
|
if (block.type === "paragraph")
|
|
@@ -721,7 +887,7 @@ async function writePositionedPage(page, write, options) {
|
|
|
721
887
|
const displayWidth = quarterTurn ? page.height : page.width;
|
|
722
888
|
const displayHeight = quarterTurn ? page.width : page.height;
|
|
723
889
|
await write(
|
|
724
|
-
`<section class="pdf-page pdf-page--visual pdf-page--positioned" data-page="${page.number}" data-rotate="${page.rotate}" style="width:${
|
|
890
|
+
`<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">`
|
|
725
891
|
);
|
|
726
892
|
const fontAliases = visualFontAliases(page.number, page.fonts ?? []);
|
|
727
893
|
const type3Fonts = new Map(
|
|
@@ -733,44 +899,26 @@ async function writePositionedPage(page, write, options) {
|
|
|
733
899
|
);
|
|
734
900
|
}
|
|
735
901
|
await write(
|
|
736
|
-
`<div class="pdf-page-content pdf-page-content--${page.rotate}" style="width:${
|
|
902
|
+
`<div class="pdf-page-content pdf-page-content--${page.rotate}" style="width:${number3(page.width)}pt;height:${number3(page.height)}pt${rotationTransform(page)}">`
|
|
737
903
|
);
|
|
738
904
|
await write(
|
|
739
|
-
`<svg class="pdf-visual-text" xmlns="http://www.w3.org/2000/svg" width="${
|
|
905
|
+
`<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)}">`
|
|
906
|
+
);
|
|
907
|
+
const clipDefinitions = imageClipDefinitions(page.images ?? [], page.number, page.height) + vectorPathClipDefinitions(
|
|
908
|
+
(page.paths ?? []).map((path, index) => ({ path, index })),
|
|
909
|
+
page.number
|
|
740
910
|
);
|
|
741
|
-
const clipDefinitions = imageClipDefinitions(page.images ?? [], page.number, page.height) + pathClipDefinitions(page.paths ?? [], page.number);
|
|
742
911
|
if (clipDefinitions) await write(`<defs>${clipDefinitions}</defs>`);
|
|
743
912
|
if (reflectedOverlay) {
|
|
744
913
|
for (const [index, image] of (page.images ?? []).entries()) {
|
|
745
914
|
await write(visualImage(image, page.height, page.number, index));
|
|
746
915
|
}
|
|
747
916
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
await write(
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
if (page.paths?.length) {
|
|
756
|
-
await write(`<g transform="translate(0 ${number2(page.height)}) scale(1 -1)">`);
|
|
757
|
-
for (const [pathIndex, path] of page.paths.entries()) {
|
|
758
|
-
if (!isSvgPath(path.d)) continue;
|
|
759
|
-
const fill = isCssHexColor(path.fill) ? path.fill : "none";
|
|
760
|
-
const stroke = isCssHexColor(path.stroke) ? path.stroke : "none";
|
|
761
|
-
const strokeWidth = path.strokeWidth !== void 0 && Number.isFinite(path.strokeWidth) && path.strokeWidth >= 0 ? ` stroke-width="${number2(path.strokeWidth)}"` : "";
|
|
762
|
-
const fillRule = path.fillRule ? ` fill-rule="${path.fillRule}"` : "";
|
|
763
|
-
const fillOpacity = isUnitInterval(path.fillOpacity) ? ` fill-opacity="${number2(path.fillOpacity)}"` : "";
|
|
764
|
-
const strokeOpacity = isUnitInterval(path.strokeOpacity) ? ` stroke-opacity="${number2(path.strokeOpacity)}"` : "";
|
|
765
|
-
const dasharray = path.strokeDasharray?.every((value) => Number.isFinite(value) && value >= 0) ? ` stroke-dasharray="${path.strokeDasharray.map(number2).join(" ")}"` : "";
|
|
766
|
-
const dashoffset = Number.isFinite(path.strokeDashoffset) ? ` stroke-dashoffset="${number2(path.strokeDashoffset ?? 0)}"` : "";
|
|
767
|
-
const linecap = path.strokeLinecap ? ` stroke-linecap="${path.strokeLinecap}"` : "";
|
|
768
|
-
const linejoin = path.strokeLinejoin ? ` stroke-linejoin="${path.strokeLinejoin}"` : "";
|
|
769
|
-
let output = `<path d="${path.d}" fill="${fill}" stroke="${stroke}"${strokeWidth}${fillOpacity}${strokeOpacity}${dasharray}${dashoffset}${linecap}${linejoin}${fillRule}/>`;
|
|
770
|
-
for (let index = (path.clips?.length ?? 0) - 1; index >= 0; index -= 1) {
|
|
771
|
-
output = `<g clip-path="url(#${pathClipId(page.number, pathIndex, index)})">${output}</g>`;
|
|
772
|
-
}
|
|
773
|
-
await write(output);
|
|
917
|
+
if (page.fills?.length || page.paths?.length) {
|
|
918
|
+
await write(`<g transform="translate(0 ${number3(page.height)}) scale(1 -1)">`);
|
|
919
|
+
for (const fill of page.fills ?? []) await write(vectorFillSvg(fill));
|
|
920
|
+
for (const [pathIndex, path] of (page.paths ?? []).entries()) {
|
|
921
|
+
await write(vectorPathSvg(path, page.number, pathIndex));
|
|
774
922
|
}
|
|
775
923
|
await write("</g>");
|
|
776
924
|
}
|
|
@@ -800,8 +948,8 @@ function usesReflectedVisualOverlay(page, spans) {
|
|
|
800
948
|
}
|
|
801
949
|
function visualImage(image, pageHeight, pageNumber, imageIndex) {
|
|
802
950
|
const [a, b, c, d, e, f] = image.transform;
|
|
803
|
-
const transform = [a, -b, -c, d, c + e, pageHeight - d - f].map(
|
|
804
|
-
const opacity =
|
|
951
|
+
const transform = [a, -b, -c, d, c + e, pageHeight - d - f].map(number3).join(" ");
|
|
952
|
+
const opacity = isUnitInterval2(image.opacity) ? ` opacity="${number3(image.opacity)}"` : "";
|
|
805
953
|
const mime = image.format === "jpeg" ? "image/jpeg" : "image/bmp";
|
|
806
954
|
const data = image.format === "jpeg" ? image.data : rgbBmp2(image);
|
|
807
955
|
let output = `<image width="1" height="1" preserveAspectRatio="none" transform="matrix(${transform})" href="data:${mime};base64,${base64(data)}"${opacity}/>`;
|
|
@@ -815,25 +963,13 @@ function imageClipDefinitions(images, pageNumber, pageHeight) {
|
|
|
815
963
|
(image, imageIndex) => (image.clips ?? []).map((clip, clipIndex) => {
|
|
816
964
|
if (!isSvgPath(clip.d)) return "";
|
|
817
965
|
const fillRule = clip.fillRule ? ` clip-rule="${clip.fillRule}"` : "";
|
|
818
|
-
return `<clipPath id="${imageClipId(pageNumber, imageIndex, clipIndex)}" clipPathUnits="userSpaceOnUse"><path d="${clip.d}" transform="translate(0 ${
|
|
966
|
+
return `<clipPath id="${imageClipId(pageNumber, imageIndex, clipIndex)}" clipPathUnits="userSpaceOnUse"><path d="${clip.d}" transform="translate(0 ${number3(pageHeight)}) scale(1 -1)"${fillRule}/></clipPath>`;
|
|
819
967
|
})
|
|
820
968
|
).join("");
|
|
821
969
|
}
|
|
822
970
|
function imageClipId(pageNumber, imageIndex, clipIndex) {
|
|
823
971
|
return `boxpdf-clip-${pageNumber}-${imageIndex}-${clipIndex}`;
|
|
824
972
|
}
|
|
825
|
-
function pathClipDefinitions(paths, pageNumber) {
|
|
826
|
-
return paths.flatMap(
|
|
827
|
-
(path, pathIndex) => (path.clips ?? []).map((clip, clipIndex) => {
|
|
828
|
-
if (!isSvgPath(clip.d)) return "";
|
|
829
|
-
const fillRule = clip.fillRule ? ` clip-rule="${clip.fillRule}"` : "";
|
|
830
|
-
return `<clipPath id="${pathClipId(pageNumber, pathIndex, clipIndex)}" clipPathUnits="userSpaceOnUse"><path d="${clip.d}"${fillRule}/></clipPath>`;
|
|
831
|
-
})
|
|
832
|
-
).join("");
|
|
833
|
-
}
|
|
834
|
-
function pathClipId(pageNumber, pathIndex, clipIndex) {
|
|
835
|
-
return `boxpdf-path-clip-${pageNumber}-${pathIndex}-${clipIndex}`;
|
|
836
|
-
}
|
|
837
973
|
function rgbBmp2(image) {
|
|
838
974
|
const stride = Math.ceil(image.width * 3 / 4) * 4;
|
|
839
975
|
const output = new Uint8Array(54 + stride * image.height);
|
|
@@ -862,11 +998,11 @@ function rgbBmp2(image) {
|
|
|
862
998
|
function rotationTransform(page) {
|
|
863
999
|
switch (page.rotate) {
|
|
864
1000
|
case 90:
|
|
865
|
-
return `;transform:translate(${
|
|
1001
|
+
return `;transform:translate(${number3(page.height)}pt,0) rotate(90deg)`;
|
|
866
1002
|
case 180:
|
|
867
|
-
return `;transform:translate(${
|
|
1003
|
+
return `;transform:translate(${number3(page.width)}pt,${number3(page.height)}pt) rotate(180deg)`;
|
|
868
1004
|
case 270:
|
|
869
|
-
return `;transform:translate(0,${
|
|
1005
|
+
return `;transform:translate(0,${number3(page.width)}pt) rotate(270deg)`;
|
|
870
1006
|
default:
|
|
871
1007
|
return "";
|
|
872
1008
|
}
|
|
@@ -874,13 +1010,13 @@ function rotationTransform(page) {
|
|
|
874
1010
|
function positionedSpan(span, fontAliases) {
|
|
875
1011
|
const direction = directionAttribute([span]);
|
|
876
1012
|
const style = [
|
|
877
|
-
`left:${
|
|
878
|
-
`bottom:${
|
|
879
|
-
`width:${
|
|
880
|
-
`height:${
|
|
881
|
-
`font-size:${
|
|
882
|
-
...
|
|
883
|
-
...
|
|
1013
|
+
`left:${number3(span.bounds.x)}pt`,
|
|
1014
|
+
`bottom:${number3(span.bounds.y)}pt`,
|
|
1015
|
+
`width:${number3(span.bounds.width)}pt`,
|
|
1016
|
+
`height:${number3(span.bounds.height)}pt`,
|
|
1017
|
+
`font-size:${number3(span.fontSize)}pt`,
|
|
1018
|
+
...isCssHexColor2(span.color) ? [`color:${span.color}`] : [],
|
|
1019
|
+
...isUnitInterval2(span.fillOpacity) ? [`opacity:${number3(span.fillOpacity)}`] : [],
|
|
884
1020
|
...visualFontStyles(
|
|
885
1021
|
span.fontFamily,
|
|
886
1022
|
span.fontAssetId ? fontAliases.get(span.fontAssetId) : void 0
|
|
@@ -893,15 +1029,35 @@ async function writeFlowPage(page, write) {
|
|
|
893
1029
|
const structured = (0, import_structure2.structurePage)(withoutSemanticMediaSpans(page, media));
|
|
894
1030
|
const defaultColor = dominantTextColor(structured.lines);
|
|
895
1031
|
let mediaIndex = 0;
|
|
1032
|
+
const captions = clearMediaCaptionAssociations(
|
|
1033
|
+
media,
|
|
1034
|
+
structured.blocks,
|
|
1035
|
+
page.width,
|
|
1036
|
+
page.height,
|
|
1037
|
+
structured.lines
|
|
1038
|
+
);
|
|
1039
|
+
const captionedMedia = new Set(captions.values());
|
|
896
1040
|
await write(
|
|
897
1041
|
`<section class="pdf-page pdf-page--semantic pdf-page--flow" data-page="${page.number}">`
|
|
898
1042
|
);
|
|
899
1043
|
for (const block of structured.blocks) {
|
|
900
1044
|
const blockY = semanticBlockY2(block);
|
|
1045
|
+
let emittedAsCaption = false;
|
|
901
1046
|
while ((media[mediaIndex]?.bounds.y ?? -Infinity) >= blockY) {
|
|
902
|
-
|
|
1047
|
+
const item = media[mediaIndex];
|
|
1048
|
+
if (item && captions.get(block) === item && block.type === "paragraph") {
|
|
1049
|
+
await write(
|
|
1050
|
+
`<figure class="pdf-semantic-figure">${item.html}<figcaption>${semanticTextHtml(block.text, block.lines, defaultColor)}</figcaption></figure>`
|
|
1051
|
+
);
|
|
1052
|
+
mediaIndex += 1;
|
|
1053
|
+
emittedAsCaption = true;
|
|
1054
|
+
break;
|
|
1055
|
+
}
|
|
1056
|
+
if (item && captionedMedia.has(item)) break;
|
|
1057
|
+
await write(`<div class="pdf-semantic-visual">${item?.html}</div>`);
|
|
903
1058
|
mediaIndex += 1;
|
|
904
1059
|
}
|
|
1060
|
+
if (emittedAsCaption) continue;
|
|
905
1061
|
if (block.type === "table") await write((0, import_structure2.tableToHtml)(block.table));
|
|
906
1062
|
else if (block.type === "heading") {
|
|
907
1063
|
await write(
|
|
@@ -941,6 +1097,10 @@ async function writeFlowPage(page, write) {
|
|
|
941
1097
|
await write(
|
|
942
1098
|
`<section><h3>${escapeHtml4(block.role)}</h3><p>${escapeHtml4(block.organization)}</p><p>${escapeHtml4(block.date)}</p></section>`
|
|
943
1099
|
);
|
|
1100
|
+
} else if (block.type === "insetGroup") {
|
|
1101
|
+
await write(
|
|
1102
|
+
`<div class="pdf-semantic-inset" style="margin-inline-start:${number3(block.indentEm)}em">${block.blocks.map((item) => nestedSemanticBlockHtml(item, defaultColor)).join("")}</div>`
|
|
1103
|
+
);
|
|
944
1104
|
} else {
|
|
945
1105
|
const tag = block.ordered ? "ol" : "ul";
|
|
946
1106
|
await write(`<${tag}>`);
|
|
@@ -956,6 +1116,33 @@ async function writeFlowPage(page, write) {
|
|
|
956
1116
|
}
|
|
957
1117
|
await write("</section>");
|
|
958
1118
|
}
|
|
1119
|
+
function nestedSemanticBlockHtml(block, defaultColor) {
|
|
1120
|
+
if (block.type === "insetGroup") {
|
|
1121
|
+
return `<div class="pdf-semantic-inset" style="margin-inline-start:${number3(block.indentEm)}em">${block.blocks.map((item) => nestedSemanticBlockHtml(item, defaultColor)).join("")}</div>`;
|
|
1122
|
+
}
|
|
1123
|
+
if (block.type === "table") return (0, import_structure2.tableToHtml)(block.table);
|
|
1124
|
+
if (block.type === "heading") {
|
|
1125
|
+
return `<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor, false)}</h${block.level}>`;
|
|
1126
|
+
}
|
|
1127
|
+
if (block.type === "paragraph") {
|
|
1128
|
+
return `<p>${semanticTextHtml(block.text, block.lines, defaultColor)}</p>`;
|
|
1129
|
+
}
|
|
1130
|
+
if (block.type === "preformatted") return `<pre>${escapeHtml4(block.text)}</pre>`;
|
|
1131
|
+
if (block.type === "definitionList") {
|
|
1132
|
+
return `<dl>${block.entries.map((entry) => `<div><dt>${escapeHtml4(entry.term)}</dt><dd>${escapeHtml4(entry.description)}</dd></div>`).join("")}</dl>`;
|
|
1133
|
+
}
|
|
1134
|
+
if (block.type === "cardList") {
|
|
1135
|
+
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>`;
|
|
1136
|
+
}
|
|
1137
|
+
if (block.type === "sectionGroup") {
|
|
1138
|
+
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>`;
|
|
1139
|
+
}
|
|
1140
|
+
if (block.type === "employment") {
|
|
1141
|
+
return `<section><h3>${escapeHtml4(block.role)}</h3><p>${escapeHtml4(block.organization)}</p><p>${escapeHtml4(block.date)}</p></section>`;
|
|
1142
|
+
}
|
|
1143
|
+
const tag = block.ordered ? "ol" : "ul";
|
|
1144
|
+
return `<${tag}>${block.items.map((item) => `<li>${semanticTextHtml(item.text, item.lines, defaultColor)}</li>`).join("")}</${tag}>`;
|
|
1145
|
+
}
|
|
959
1146
|
function semanticBlockY2(block) {
|
|
960
1147
|
const lines = block.type === "list" ? block.items.flatMap((item) => item.lines) : block.lines;
|
|
961
1148
|
return Math.max(...lines.map((line) => line.bounds.y + line.bounds.height));
|
|
@@ -968,15 +1155,15 @@ function visualText(span, pageHeight, fontAliases, counterRotateReflectedText =
|
|
|
968
1155
|
span.fontFamily,
|
|
969
1156
|
span.fontAssetId ? fontAliases.get(span.fontAssetId) : void 0
|
|
970
1157
|
).join(";");
|
|
971
|
-
const stroke =
|
|
972
|
-
const strokeWidth = stroke && Number.isFinite(span.strokeWidth) && (span.strokeWidth ?? -1) >= 0 ? `stroke-width:${
|
|
1158
|
+
const stroke = isCssHexColor2(span.strokeColor) ? `stroke:${span.strokeColor}` : "";
|
|
1159
|
+
const strokeWidth = stroke && Number.isFinite(span.strokeWidth) && (span.strokeWidth ?? -1) >= 0 ? `stroke-width:${number3(span.strokeWidth ?? 0)}` : "";
|
|
973
1160
|
const strokeOnly = span.renderingMode === 1 || span.renderingMode === 5;
|
|
974
|
-
const fillOpacity =
|
|
975
|
-
const strokeOpacity =
|
|
1161
|
+
const fillOpacity = isUnitInterval2(span.fillOpacity) ? `fill-opacity:${number3(span.fillOpacity)}` : "";
|
|
1162
|
+
const strokeOpacity = isUnitInterval2(span.strokeOpacity) ? `stroke-opacity:${number3(span.strokeOpacity)}` : "";
|
|
976
1163
|
const style = [
|
|
977
1164
|
isHebrewPaintOrder(span) ? "unicode-bidi:bidi-override;direction:ltr" : "",
|
|
978
1165
|
span.direction === "ttb" ? "writing-mode:vertical-rl" : "",
|
|
979
|
-
strokeOnly ? "fill:none" :
|
|
1166
|
+
strokeOnly ? "fill:none" : isCssHexColor2(span.color) ? `fill:${span.color}` : "",
|
|
980
1167
|
stroke,
|
|
981
1168
|
strokeWidth,
|
|
982
1169
|
fillOpacity,
|
|
@@ -984,7 +1171,7 @@ function visualText(span, pageHeight, fontAliases, counterRotateReflectedText =
|
|
|
984
1171
|
font
|
|
985
1172
|
].filter(Boolean).join(";");
|
|
986
1173
|
const textExtent = span.direction === "ttb" ? span.bounds.height : span.bounds.width;
|
|
987
|
-
const textLength = textExtent > 0 && !isHebrewPaintOrder(span) ? ` textLength="${
|
|
1174
|
+
const textLength = textExtent > 0 && !isHebrewPaintOrder(span) ? ` textLength="${number3(textExtent)}" lengthAdjust="${span.direction === "ttb" || usesSpacingAdjustment(span) ? "spacing" : "spacingAndGlyphs"}"` : "";
|
|
988
1175
|
const transform = counterRotateReflectedText && span.transform ? [
|
|
989
1176
|
span.transform[0],
|
|
990
1177
|
span.transform[1],
|
|
@@ -997,8 +1184,8 @@ function visualText(span, pageHeight, fontAliases, counterRotateReflectedText =
|
|
|
997
1184
|
const basisY = transform?.[1] ?? 0;
|
|
998
1185
|
const anchorX = span.bounds.x + basisX * rtlOffset;
|
|
999
1186
|
const anchorY = pageHeight - span.bounds.y + basisY * rtlOffset;
|
|
1000
|
-
const position = transformed ? ` x="0" y="0" transform="matrix(${transform?.map(
|
|
1001
|
-
return `<text${direction}${position} font-size="${
|
|
1187
|
+
const position = transformed ? ` x="0" y="0" transform="matrix(${transform?.map(number3).join(" ")} ${number3(anchorX)} ${number3(anchorY)})"` : ` x="${number3(anchorX)}" y="${number3(anchorY)}"`;
|
|
1188
|
+
return `<text${direction}${position} font-size="${number3(span.fontSize)}"${textLength}${style ? ` style="${style}"` : ""}>${escapeHtml4(span.text)}</text>`;
|
|
1002
1189
|
}
|
|
1003
1190
|
function isAdobeCjkFont(fontFamily) {
|
|
1004
1191
|
return /^Adobe(?:Heiti|Song|Kaiti|Ming|Gothic|Mincho)Std-/i.test(fontFamily ?? "");
|
|
@@ -1010,16 +1197,16 @@ function visualType3Text(span, font, pageHeight) {
|
|
|
1010
1197
|
const totalAdvance = sequence.reduce((total, glyph) => total + (glyph?.advance ?? 0), 0);
|
|
1011
1198
|
if (totalAdvance <= 0 || span.bounds.width <= 0 || span.fontSize <= 0) return "";
|
|
1012
1199
|
const transform = span.transform ?? [1, 0, 0, 1];
|
|
1013
|
-
const outer = `matrix(${transform.map(
|
|
1200
|
+
const outer = `matrix(${transform.map(number3).join(" ")} ${number3(span.bounds.x)} ${number3(pageHeight - span.bounds.y)})`;
|
|
1014
1201
|
const xScale = span.bounds.width / totalAdvance;
|
|
1015
1202
|
let offset = 0;
|
|
1016
1203
|
let content = "";
|
|
1017
1204
|
for (const glyph of sequence) {
|
|
1018
1205
|
if (!glyph) continue;
|
|
1019
|
-
content += `<g transform="translate(${
|
|
1206
|
+
content += `<g transform="translate(${number3(offset)} 0)">${type3Glyph(glyph, span.color)}</g>`;
|
|
1020
1207
|
offset += glyph.advance;
|
|
1021
1208
|
}
|
|
1022
|
-
return `<g transform="${outer}"><g transform="scale(${
|
|
1209
|
+
return `<g transform="${outer}"><g transform="scale(${number3(xScale)} ${number3(-span.fontSize)})">${content}</g></g>`;
|
|
1023
1210
|
}
|
|
1024
1211
|
function isHebrewPaintOrder(span) {
|
|
1025
1212
|
return span.direction === "ltr" && /[\u0590-\u05ff]/u.test(span.text);
|
|
@@ -1030,30 +1217,27 @@ function usesSpacingAdjustment(span) {
|
|
|
1030
1217
|
function type3Glyph(glyph, textColor) {
|
|
1031
1218
|
let output = "";
|
|
1032
1219
|
for (const fill of glyph.fills ?? []) {
|
|
1033
|
-
const color = glyph.usesTextColor &&
|
|
1034
|
-
if (!
|
|
1035
|
-
const points = fill.points.map(([x, y]) => `${
|
|
1036
|
-
const opacity =
|
|
1220
|
+
const color = glyph.usesTextColor && isCssHexColor2(textColor) ? textColor : fill.color;
|
|
1221
|
+
if (!isCssHexColor2(color)) continue;
|
|
1222
|
+
const points = fill.points.map(([x, y]) => `${number3(x)},${number3(y)}`).join(" ");
|
|
1223
|
+
const opacity = isUnitInterval2(fill.opacity) ? ` fill-opacity="${number3(fill.opacity)}"` : "";
|
|
1037
1224
|
output += `<polygon points="${points}" fill="${color}"${opacity}/>`;
|
|
1038
1225
|
}
|
|
1039
1226
|
for (const path of glyph.paths ?? []) {
|
|
1040
1227
|
if (!isSvgPath(path.d)) continue;
|
|
1041
|
-
const fill = glyph.usesTextColor &&
|
|
1042
|
-
const stroke = glyph.usesTextColor &&
|
|
1043
|
-
const width = path.strokeWidth !== void 0 && Number.isFinite(path.strokeWidth) && path.strokeWidth >= 0 ? ` stroke-width="${
|
|
1228
|
+
const fill = glyph.usesTextColor && isCssHexColor2(textColor) ? textColor : isCssHexColor2(path.fill) ? path.fill : "none";
|
|
1229
|
+
const stroke = glyph.usesTextColor && isCssHexColor2(textColor) && path.stroke ? textColor : isCssHexColor2(path.stroke) ? path.stroke : "none";
|
|
1230
|
+
const width = path.strokeWidth !== void 0 && Number.isFinite(path.strokeWidth) && path.strokeWidth >= 0 ? ` stroke-width="${number3(path.strokeWidth)}"` : "";
|
|
1044
1231
|
output += `<path d="${path.d}" fill="${fill}" stroke="${stroke}"${width}/>`;
|
|
1045
1232
|
}
|
|
1046
1233
|
return (glyph.fills?.length ?? 0) > 64 && glyph.advance > 2 ? `<g shape-rendering="crispEdges">${output}</g>` : output;
|
|
1047
1234
|
}
|
|
1048
|
-
function
|
|
1235
|
+
function isCssHexColor2(value) {
|
|
1049
1236
|
return /^#[\da-f]{6}$/i.test(value ?? "");
|
|
1050
1237
|
}
|
|
1051
|
-
function
|
|
1238
|
+
function isUnitInterval2(value) {
|
|
1052
1239
|
return Number.isFinite(value) && (value ?? -1) >= 0 && (value ?? 2) <= 1;
|
|
1053
1240
|
}
|
|
1054
|
-
function isSvgPath(value) {
|
|
1055
|
-
return value.length <= 1e6 && /^[\d\s.,+\-eEMmLlCcZz]+$/.test(value);
|
|
1056
|
-
}
|
|
1057
1241
|
function isMonospace(fontFamily) {
|
|
1058
1242
|
return /courier|mono/i.test(fontFamily ?? "");
|
|
1059
1243
|
}
|
|
@@ -1071,7 +1255,7 @@ function directionAttribute(spans) {
|
|
|
1071
1255
|
if (vertical > rtl && vertical * 2 >= spans.length) return ' data-direction="ttb"';
|
|
1072
1256
|
return rtl * 2 >= spans.length && spans.length > 0 ? ' dir="rtl"' : "";
|
|
1073
1257
|
}
|
|
1074
|
-
function
|
|
1258
|
+
function number3(value) {
|
|
1075
1259
|
return Number.isFinite(value) ? String(Math.round(value * 1e3) / 1e3) : "0";
|
|
1076
1260
|
}
|
|
1077
1261
|
function escapeAttribute(value) {
|