@boxpdf/html-writer 0.1.17 → 0.1.19
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 +560 -164
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +560 -164
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -27,6 +27,184 @@ __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
|
+
var minimumCaptionScore = 0.72;
|
|
32
|
+
function clearMediaCaptionAssociations(media, blocks, pageWidth, pageHeight, pageLines) {
|
|
33
|
+
const candidates = blocks.flatMap((block) => {
|
|
34
|
+
const candidate = captionCandidate(block);
|
|
35
|
+
return candidate ? [candidate] : [];
|
|
36
|
+
});
|
|
37
|
+
const preliminary = media.flatMap(
|
|
38
|
+
(item) => candidates.flatMap((candidate) => {
|
|
39
|
+
const evidence = scoreCaption(item, candidate, pageWidth, pageHeight, pageLines, 0);
|
|
40
|
+
return evidence ? [{ media: item, candidate, evidence }] : [];
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
const patterns = repeatedPatterns(preliminary);
|
|
44
|
+
const edges = preliminary.map((edge) => {
|
|
45
|
+
const evidence = scoreCaption(
|
|
46
|
+
edge.media,
|
|
47
|
+
edge.candidate,
|
|
48
|
+
pageWidth,
|
|
49
|
+
pageHeight,
|
|
50
|
+
pageLines,
|
|
51
|
+
patterns.get(patternKey(edge)) ?? 0
|
|
52
|
+
);
|
|
53
|
+
return evidence ? { ...edge, evidence } : void 0;
|
|
54
|
+
}).filter((edge) => Boolean(edge)).filter((edge) => edge.evidence.score >= minimumCaptionScore);
|
|
55
|
+
const bestForMedia = bestEdges(edges, (edge) => edge.media);
|
|
56
|
+
const bestForCaption = bestEdges(edges, (edge) => edge.candidate.block);
|
|
57
|
+
const associations = /* @__PURE__ */ new Map();
|
|
58
|
+
for (const edge of edges) {
|
|
59
|
+
if (bestForMedia.get(edge.media) === edge && bestForCaption.get(edge.candidate.block) === edge) {
|
|
60
|
+
associations.set(edge.candidate.block, edge.media);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return associations;
|
|
64
|
+
}
|
|
65
|
+
function scoreCaption(media, candidate, pageWidth, pageHeight, pageLines, repeatedAlignment) {
|
|
66
|
+
if (!insidePage(media.bounds, pageWidth, pageHeight)) return void 0;
|
|
67
|
+
if (media.bounds.width < pageWidth * 0.06 || media.bounds.height < candidate.lineHeight * 1.5)
|
|
68
|
+
return void 0;
|
|
69
|
+
const relation = verticalRelation(media.bounds, candidate.bounds);
|
|
70
|
+
if (!relation) return void 0;
|
|
71
|
+
const maximumGap = Math.max(candidate.lineHeight * 3, pageHeight * 0.035);
|
|
72
|
+
if (relation.gap > maximumGap) return void 0;
|
|
73
|
+
const overlapWidth = overlap(
|
|
74
|
+
media.bounds.x,
|
|
75
|
+
media.bounds.width,
|
|
76
|
+
candidate.bounds.x,
|
|
77
|
+
candidate.bounds.width
|
|
78
|
+
);
|
|
79
|
+
const horizontalOverlap = overlapWidth / Math.max(1, Math.min(media.bounds.width, candidate.bounds.width));
|
|
80
|
+
const centerDistance = Math.abs(center(media.bounds) - center(candidate.bounds));
|
|
81
|
+
const centerAlignment = clamp01(
|
|
82
|
+
1 - centerDistance / Math.max(media.bounds.width, candidate.bounds.width)
|
|
83
|
+
);
|
|
84
|
+
if (horizontalOverlap < 0.45 && centerAlignment < 0.82) return void 0;
|
|
85
|
+
const relativeWidth = Math.min(media.bounds.width, candidate.bounds.width) / Math.max(media.bounds.width, candidate.bounds.width);
|
|
86
|
+
const gapRatio = clamp01(1 - relation.gap / maximumGap);
|
|
87
|
+
const interveningContent = interveningScore(media.bounds, candidate, relation.side, pageLines);
|
|
88
|
+
if (interveningContent === 0) return void 0;
|
|
89
|
+
if (relation.side === "above" && (gapRatio < 0.6 || interveningContent < 1)) return void 0;
|
|
90
|
+
const fontContrast = captionFontContrast(candidate, pageLines);
|
|
91
|
+
const surroundingWhitespace = whitespaceScore(candidate, relation.side, relation.gap, pageLines);
|
|
92
|
+
const score = gapRatio * 0.22 + horizontalOverlap * 0.17 + centerAlignment * 0.13 + relativeWidth * 0.09 + fontContrast * 0.13 + surroundingWhitespace * 0.09 + interveningContent * 0.09 + repeatedAlignment * 0.08;
|
|
93
|
+
return {
|
|
94
|
+
score,
|
|
95
|
+
side: relation.side,
|
|
96
|
+
gapRatio,
|
|
97
|
+
horizontalOverlap,
|
|
98
|
+
centerAlignment,
|
|
99
|
+
relativeWidth,
|
|
100
|
+
fontContrast,
|
|
101
|
+
surroundingWhitespace,
|
|
102
|
+
interveningContent,
|
|
103
|
+
repeatedAlignment
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function captionCandidate(block) {
|
|
107
|
+
if (block.type !== "paragraph" || block.lines.length === 0) return void 0;
|
|
108
|
+
const first = block.lines.flatMap((line) => line.spans).find((span) => /\S/u.test(span.text));
|
|
109
|
+
if (!first) return void 0;
|
|
110
|
+
return {
|
|
111
|
+
block,
|
|
112
|
+
bounds: unionLines(block.lines),
|
|
113
|
+
lineHeight: median(block.lines.map((line) => line.bounds.height)),
|
|
114
|
+
font: fontSignature(first)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function verticalRelation(media, caption) {
|
|
118
|
+
const belowGap = media.y - (caption.y + caption.height);
|
|
119
|
+
if (belowGap >= -caption.height * 0.15) return { side: "below", gap: Math.max(0, belowGap) };
|
|
120
|
+
const aboveGap = caption.y - (media.y + media.height);
|
|
121
|
+
if (aboveGap >= -caption.height * 0.15) return { side: "above", gap: Math.max(0, aboveGap) };
|
|
122
|
+
return void 0;
|
|
123
|
+
}
|
|
124
|
+
function interveningScore(media, candidate, side, pageLines) {
|
|
125
|
+
const lower = side === "below" ? candidate.bounds.y + candidate.bounds.height : media.y + media.height;
|
|
126
|
+
const upper = side === "below" ? media.y : candidate.bounds.y;
|
|
127
|
+
const blockers = pageLines.filter(
|
|
128
|
+
(line) => !candidate.block.lines.includes(line) && line.bounds.y < upper && line.bounds.y + line.bounds.height > lower && overlap(line.bounds.x, line.bounds.width, media.x, media.width) / Math.max(1, Math.min(line.bounds.width, media.width)) >= 0.25
|
|
129
|
+
);
|
|
130
|
+
return blockers.length === 0 ? 1 : blockers.length === 1 ? 0.35 : 0;
|
|
131
|
+
}
|
|
132
|
+
function captionFontContrast(candidate, pageLines) {
|
|
133
|
+
const otherLines = pageLines.filter((line) => !candidate.block.lines.includes(line));
|
|
134
|
+
if (candidate.font !== dominantFontSignature(otherLines)) return 1;
|
|
135
|
+
const candidateSize = median(
|
|
136
|
+
candidate.block.lines.flatMap((line) => line.spans.map((span) => span.fontSize))
|
|
137
|
+
);
|
|
138
|
+
const bodySize = median(otherLines.flatMap((line) => line.spans.map((span) => span.fontSize)));
|
|
139
|
+
return Math.abs(candidateSize - bodySize) >= 0.75 ? 0.65 : 0.15;
|
|
140
|
+
}
|
|
141
|
+
function whitespaceScore(candidate, side, mediaGap, pageLines) {
|
|
142
|
+
const awayGaps = pageLines.filter((line) => !candidate.block.lines.includes(line)).filter(
|
|
143
|
+
(line) => overlap(line.bounds.x, line.bounds.width, candidate.bounds.x, candidate.bounds.width) > 0
|
|
144
|
+
).flatMap((line) => {
|
|
145
|
+
if (side === "below" && line.bounds.y + line.bounds.height <= candidate.bounds.y)
|
|
146
|
+
return [candidate.bounds.y - line.bounds.y - line.bounds.height];
|
|
147
|
+
if (side === "above" && line.bounds.y >= candidate.bounds.y + candidate.bounds.height)
|
|
148
|
+
return [line.bounds.y - candidate.bounds.y - candidate.bounds.height];
|
|
149
|
+
return [];
|
|
150
|
+
});
|
|
151
|
+
const awayGap = Math.min(...awayGaps, Number.POSITIVE_INFINITY);
|
|
152
|
+
return Number.isFinite(awayGap) ? clamp01((awayGap + candidate.lineHeight * 0.25) / (mediaGap + candidate.lineHeight)) : 1;
|
|
153
|
+
}
|
|
154
|
+
function repeatedPatterns(edges) {
|
|
155
|
+
const counts = /* @__PURE__ */ new Map();
|
|
156
|
+
for (const edge of edges.filter((item) => item.evidence.score >= minimumCaptionScore - 0.08)) {
|
|
157
|
+
counts.set(patternKey(edge), (counts.get(patternKey(edge)) ?? 0) + 1);
|
|
158
|
+
}
|
|
159
|
+
return new Map([...counts].map(([key, count]) => [key, count >= 2 ? 1 : 0]));
|
|
160
|
+
}
|
|
161
|
+
function patternKey(edge) {
|
|
162
|
+
const widthRatio = edge.candidate.bounds.width / Math.max(1, edge.media.bounds.width);
|
|
163
|
+
return `${edge.candidate.font}|${edge.evidence.side}|${Math.round(widthRatio * 4) / 4}`;
|
|
164
|
+
}
|
|
165
|
+
function bestEdges(edges, key) {
|
|
166
|
+
const output = /* @__PURE__ */ new Map();
|
|
167
|
+
for (const edge of edges) {
|
|
168
|
+
const existing = output.get(key(edge));
|
|
169
|
+
if (!existing || edge.evidence.score > existing.evidence.score) output.set(key(edge), edge);
|
|
170
|
+
}
|
|
171
|
+
return output;
|
|
172
|
+
}
|
|
173
|
+
function insidePage(bounds2, pageWidth, pageHeight) {
|
|
174
|
+
return bounds2.x >= -2 && bounds2.y >= -2 && bounds2.x + bounds2.width <= pageWidth + 2 && bounds2.y + bounds2.height <= pageHeight + 2;
|
|
175
|
+
}
|
|
176
|
+
function unionLines(lines) {
|
|
177
|
+
const x = Math.min(...lines.map((line) => line.bounds.x));
|
|
178
|
+
const y = Math.min(...lines.map((line) => line.bounds.y));
|
|
179
|
+
const right = Math.max(...lines.map((line) => line.bounds.x + line.bounds.width));
|
|
180
|
+
const top = Math.max(...lines.map((line) => line.bounds.y + line.bounds.height));
|
|
181
|
+
return { x, y, width: right - x, height: top - y };
|
|
182
|
+
}
|
|
183
|
+
function dominantFontSignature(lines) {
|
|
184
|
+
const counts = /* @__PURE__ */ new Map();
|
|
185
|
+
for (const span of lines.flatMap((line) => line.spans)) {
|
|
186
|
+
const signature = fontSignature(span);
|
|
187
|
+
counts.set(signature, (counts.get(signature) ?? 0) + Math.max(1, [...span.text].length));
|
|
188
|
+
}
|
|
189
|
+
return [...counts].sort((left, right) => right[1] - left[1])[0]?.[0] ?? "";
|
|
190
|
+
}
|
|
191
|
+
function fontSignature(span) {
|
|
192
|
+
return `${(span.fontFamily ?? span.fontName ?? "").toLocaleLowerCase("en")}|${Math.round(span.fontSize * 2) / 2}|${span.color ?? ""}`;
|
|
193
|
+
}
|
|
194
|
+
function center(bounds2) {
|
|
195
|
+
return bounds2.x + bounds2.width / 2;
|
|
196
|
+
}
|
|
197
|
+
function overlap(left, leftSize, right, rightSize) {
|
|
198
|
+
return Math.max(0, Math.min(left + leftSize, right + rightSize) - Math.max(left, right));
|
|
199
|
+
}
|
|
200
|
+
function clamp01(value) {
|
|
201
|
+
return Math.max(0, Math.min(1, value));
|
|
202
|
+
}
|
|
203
|
+
function median(values) {
|
|
204
|
+
const ordered = [...values].sort((left, right) => left - right);
|
|
205
|
+
return ordered[Math.floor(ordered.length / 2)] ?? 1;
|
|
206
|
+
}
|
|
207
|
+
|
|
30
208
|
// src/semantic-document.ts
|
|
31
209
|
var import_structure = require("@boxpdf/reader/structure");
|
|
32
210
|
|
|
@@ -99,6 +277,85 @@ function escapeHtml(value) {
|
|
|
99
277
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
100
278
|
}
|
|
101
279
|
|
|
280
|
+
// src/vector-svg.ts
|
|
281
|
+
function vectorFillSvg(fill) {
|
|
282
|
+
if (!isCssHexColor(fill.color)) return "";
|
|
283
|
+
const points = fill.points.map(([x, y]) => `${number(x)},${number(y)}`).join(" ");
|
|
284
|
+
const opacity = isUnitInterval(fill.opacity) ? ` fill-opacity="${number(fill.opacity)}"` : "";
|
|
285
|
+
return `<polygon points="${points}" fill="${fill.color}"${opacity}/>`;
|
|
286
|
+
}
|
|
287
|
+
function vectorPathSvg(path, pageNumber, pathIndex) {
|
|
288
|
+
if (!isSvgPath(path.d)) return "";
|
|
289
|
+
const fill = isCssHexColor(path.fill) ? path.fill : "none";
|
|
290
|
+
const stroke = isCssHexColor(path.stroke) ? path.stroke : "none";
|
|
291
|
+
const width = finiteNonnegative(path.strokeWidth) ? ` stroke-width="${number(path.strokeWidth)}"` : "";
|
|
292
|
+
const fillOpacity = isUnitInterval(path.fillOpacity) ? ` fill-opacity="${number(path.fillOpacity)}"` : "";
|
|
293
|
+
const strokeOpacity = isUnitInterval(path.strokeOpacity) ? ` stroke-opacity="${number(path.strokeOpacity)}"` : "";
|
|
294
|
+
const dash = path.strokeDasharray?.every(finiteNonnegative) ? ` stroke-dasharray="${path.strokeDasharray.map(number).join(" ")}"` : "";
|
|
295
|
+
const dashoffset = Number.isFinite(path.strokeDashoffset) ? ` stroke-dashoffset="${number(path.strokeDashoffset ?? 0)}"` : "";
|
|
296
|
+
const linecap = path.strokeLinecap ? ` stroke-linecap="${path.strokeLinecap}"` : "";
|
|
297
|
+
const linejoin = path.strokeLinejoin ? ` stroke-linejoin="${path.strokeLinejoin}"` : "";
|
|
298
|
+
const rule = path.fillRule ? ` fill-rule="${path.fillRule}"` : "";
|
|
299
|
+
let output = `<path d="${path.d}" fill="${fill}" stroke="${stroke}"${width}${fillOpacity}${strokeOpacity}${dash}${dashoffset}${linecap}${linejoin}${rule}/>`;
|
|
300
|
+
for (let index = (path.clips?.length ?? 0) - 1; index >= 0; index -= 1) {
|
|
301
|
+
output = `<g clip-path="url(#${vectorPathClipId(pageNumber, pathIndex, index)})">${output}</g>`;
|
|
302
|
+
}
|
|
303
|
+
return output;
|
|
304
|
+
}
|
|
305
|
+
function vectorPathClipDefinitions(paths, pageNumber) {
|
|
306
|
+
return paths.flatMap(
|
|
307
|
+
({ path, index: pathIndex }) => (path.clips ?? []).map((clip, clipIndex) => {
|
|
308
|
+
if (!isSvgPath(clip.d)) return "";
|
|
309
|
+
const rule = clip.fillRule ? ` clip-rule="${clip.fillRule}"` : "";
|
|
310
|
+
return `<clipPath id="${vectorPathClipId(pageNumber, pathIndex, clipIndex)}" clipPathUnits="userSpaceOnUse"><path d="${clip.d}"${rule}/></clipPath>`;
|
|
311
|
+
})
|
|
312
|
+
).join("");
|
|
313
|
+
}
|
|
314
|
+
function vectorPathBounds(path) {
|
|
315
|
+
if (!isSvgPath(path.d)) return void 0;
|
|
316
|
+
const values = [...path.d.matchAll(/[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?/gi)].map(
|
|
317
|
+
(match) => Number(match[0])
|
|
318
|
+
);
|
|
319
|
+
if (values.length < 2) return void 0;
|
|
320
|
+
const xs = [];
|
|
321
|
+
const ys = [];
|
|
322
|
+
for (let index = 0; index + 1 < values.length; index += 2) {
|
|
323
|
+
xs.push(values[index] ?? 0);
|
|
324
|
+
ys.push(values[index + 1] ?? 0);
|
|
325
|
+
}
|
|
326
|
+
return bounds(xs, ys);
|
|
327
|
+
}
|
|
328
|
+
function vectorFillBounds(fill) {
|
|
329
|
+
if (fill.points.length === 0) return void 0;
|
|
330
|
+
return bounds(
|
|
331
|
+
fill.points.map(([x]) => x),
|
|
332
|
+
fill.points.map(([, y]) => y)
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
function isSvgPath(value) {
|
|
336
|
+
return value.length <= 1e6 && /^[\d\s.,+\-eEMmLlCcZz]+$/.test(value);
|
|
337
|
+
}
|
|
338
|
+
function bounds(xs, ys) {
|
|
339
|
+
const x = Math.min(...xs);
|
|
340
|
+
const y = Math.min(...ys);
|
|
341
|
+
return { x, y, width: Math.max(...xs) - x, height: Math.max(...ys) - y };
|
|
342
|
+
}
|
|
343
|
+
function vectorPathClipId(pageNumber, pathIndex, clipIndex) {
|
|
344
|
+
return `boxpdf-path-clip-${pageNumber}-${pathIndex}-${clipIndex}`;
|
|
345
|
+
}
|
|
346
|
+
function isCssHexColor(value) {
|
|
347
|
+
return typeof value === "string" && /^#[0-9a-f]{6}$/i.test(value);
|
|
348
|
+
}
|
|
349
|
+
function finiteNonnegative(value) {
|
|
350
|
+
return value !== void 0 && Number.isFinite(value) && value >= 0;
|
|
351
|
+
}
|
|
352
|
+
function isUnitInterval(value) {
|
|
353
|
+
return value !== void 0 && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
354
|
+
}
|
|
355
|
+
function number(value) {
|
|
356
|
+
return Number(value.toFixed(4)).toString();
|
|
357
|
+
}
|
|
358
|
+
|
|
102
359
|
// src/visual-font.ts
|
|
103
360
|
function visualFontAliases(pageNumber, fonts) {
|
|
104
361
|
return new Map(
|
|
@@ -148,47 +405,151 @@ function base64(bytes) {
|
|
|
148
405
|
// src/semantic-media.ts
|
|
149
406
|
function semanticMedia(page) {
|
|
150
407
|
const output = (page.images ?? []).map((image) => rasterMedia(image));
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return output.sort((left, right) => right.bounds.y - left.bounds.y);
|
|
408
|
+
output.push(...vectorMedia(page));
|
|
409
|
+
return mediaComponents(output, page).sort((left, right) => right.bounds.y - left.bounds.y);
|
|
154
410
|
}
|
|
155
411
|
function rasterMedia(image) {
|
|
156
|
-
const
|
|
412
|
+
const bounds2 = transformedUnitBounds(image.transform);
|
|
157
413
|
const mime = image.format === "jpeg" ? "image/jpeg" : "image/bmp";
|
|
158
414
|
const data = image.format === "jpeg" ? image.data : rgbBmp(image);
|
|
159
|
-
const opacity = unitInterval(image.opacity) ? `;opacity:${
|
|
415
|
+
const opacity = unitInterval(image.opacity) ? `;opacity:${number2(image.opacity)}` : "";
|
|
160
416
|
return {
|
|
161
|
-
bounds,
|
|
162
|
-
|
|
417
|
+
bounds: bounds2,
|
|
418
|
+
kind: "raster",
|
|
419
|
+
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
420
|
};
|
|
164
421
|
}
|
|
165
422
|
function vectorMedia(page) {
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
423
|
+
const primitives = [
|
|
424
|
+
...(page.paths ?? []).flatMap((path, index) => {
|
|
425
|
+
const bounds2 = vectorPathBounds(path);
|
|
426
|
+
return bounds2 ? [{ type: "path", value: path, index, bounds: bounds2 }] : [];
|
|
427
|
+
}),
|
|
428
|
+
...(page.fills ?? []).flatMap((fill) => {
|
|
429
|
+
const bounds2 = vectorFillBounds(fill);
|
|
430
|
+
return bounds2 && !isPageBackground(fill, bounds2, page) ? [{ type: "fill", value: fill, bounds: bounds2 }] : [];
|
|
431
|
+
})
|
|
432
|
+
];
|
|
433
|
+
const components = vectorComponents(primitives, Math.min(36, page.width * 0.06)).filter(
|
|
434
|
+
(component) => component.primitives.length >= 2 || component.bounds.width * component.bounds.height >= page.width * page.height * 2e-3
|
|
435
|
+
);
|
|
173
436
|
const aliases = visualFontAliases(page.number, page.fonts ?? []);
|
|
174
437
|
const visualCodeFonts = new Set(
|
|
175
438
|
(page.fonts ?? []).filter((font) => font.format === "truetype" && font.visualCodeMapping).map((font) => font.id)
|
|
176
439
|
);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
440
|
+
return components.map((component) => {
|
|
441
|
+
const bounds2 = component.bounds;
|
|
442
|
+
const paths = component.primitives.flatMap(
|
|
443
|
+
(primitive) => primitive.type === "path" ? [{ path: primitive.value, index: primitive.index }] : []
|
|
444
|
+
);
|
|
445
|
+
const fills = component.primitives.flatMap(
|
|
446
|
+
(primitive) => primitive.type === "fill" ? [primitive.value] : []
|
|
447
|
+
);
|
|
448
|
+
const visualSpans = page.visualSpans ?? page.spans;
|
|
449
|
+
const overlay = visualSpans.filter(
|
|
450
|
+
(span) => span.fontAssetId && visualCodeFonts.has(span.fontAssetId) && centerInside(span.bounds, bounds2)
|
|
451
|
+
);
|
|
452
|
+
const consumedSpans = page.spans.filter(
|
|
453
|
+
(span) => span.fontAssetId && visualCodeFonts.has(span.fontAssetId) && centerInside(span.bounds, bounds2)
|
|
454
|
+
);
|
|
455
|
+
const fontIds = new Set(overlay.map((span) => span.fontAssetId));
|
|
456
|
+
const fontFaces = (page.fonts ?? []).filter((font) => fontIds.has(font.id)).map((font) => visualFontFace(font, aliases)).join("");
|
|
457
|
+
return {
|
|
458
|
+
bounds: bounds2,
|
|
459
|
+
kind: "vector",
|
|
460
|
+
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>`,
|
|
461
|
+
...consumedSpans.length > 0 ? { consumedSpans } : {}
|
|
462
|
+
};
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
function mediaComponents(media, page) {
|
|
466
|
+
const components = [];
|
|
467
|
+
for (const item of media) {
|
|
468
|
+
if (isPageBackdrop(item.bounds, page)) {
|
|
469
|
+
components.push([item]);
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
const matches = components.filter(
|
|
473
|
+
(component) => !component.some((member) => isPageBackdrop(member.bounds, page)) && component.some((member) => mediaPiecesTouch(member.bounds, item.bounds))
|
|
474
|
+
);
|
|
475
|
+
if (matches.length === 0) {
|
|
476
|
+
components.push([item]);
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
const target = matches[0];
|
|
480
|
+
target.push(item);
|
|
481
|
+
for (const component of matches.slice(1)) {
|
|
482
|
+
target.push(...component);
|
|
483
|
+
components.splice(components.indexOf(component), 1);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return components.map((component) => compositeMedia(component));
|
|
487
|
+
}
|
|
488
|
+
function compositeMedia(items) {
|
|
489
|
+
if (items.length === 1) return items[0];
|
|
490
|
+
const bounds2 = unionBounds(items.map((item) => item.bounds));
|
|
491
|
+
const layers = items.map((item) => {
|
|
492
|
+
const left = (item.bounds.x - bounds2.x) / bounds2.width * 100;
|
|
493
|
+
const top = (bounds2.y + bounds2.height - item.bounds.y - item.bounds.height) / bounds2.height * 100;
|
|
494
|
+
const width = item.bounds.width / bounds2.width * 100;
|
|
495
|
+
const height = item.bounds.height / bounds2.height * 100;
|
|
496
|
+
return `<div style="position:absolute;left:${number2(left)}%;top:${number2(top)}%;width:${number2(width)}%;height:${number2(height)}%;overflow:hidden">${item.html}</div>`;
|
|
497
|
+
}).join("");
|
|
186
498
|
return {
|
|
187
|
-
bounds,
|
|
188
|
-
|
|
189
|
-
|
|
499
|
+
bounds: bounds2,
|
|
500
|
+
kind: "composite",
|
|
501
|
+
html: `<div class="pdf-semantic-media pdf-semantic-media-composite" style="position:relative;max-width:100%;width:${number2(bounds2.width)}px;aspect-ratio:${number2(bounds2.width)}/${number2(bounds2.height)}">${layers}</div>`,
|
|
502
|
+
consumedSpans: items.flatMap((item) => item.consumedSpans ?? [])
|
|
190
503
|
};
|
|
191
504
|
}
|
|
505
|
+
function mediaPiecesTouch(left, right) {
|
|
506
|
+
const xOverlap = overlap2(left.x, left.width, right.x, right.width);
|
|
507
|
+
const yOverlap = overlap2(left.y, left.height, right.y, right.height);
|
|
508
|
+
if (xOverlap > 0 && yOverlap > 0) return true;
|
|
509
|
+
const horizontalGap = axisGap(left.x, left.width, right.x, right.width);
|
|
510
|
+
const verticalGap = axisGap(left.y, left.height, right.y, right.height);
|
|
511
|
+
if (horizontalGap <= 2 && yOverlap / Math.min(left.height, right.height) >= 0.65) return true;
|
|
512
|
+
return verticalGap <= 2 && xOverlap / Math.min(left.width, right.width) >= 0.65;
|
|
513
|
+
}
|
|
514
|
+
function isPageBackdrop(bounds2, page) {
|
|
515
|
+
return bounds2.width * bounds2.height >= page.width * page.height * 0.7;
|
|
516
|
+
}
|
|
517
|
+
function overlap2(left, leftSize, right, rightSize) {
|
|
518
|
+
return Math.max(0, Math.min(left + leftSize, right + rightSize) - Math.max(left, right));
|
|
519
|
+
}
|
|
520
|
+
function axisGap(left, leftSize, right, rightSize) {
|
|
521
|
+
return Math.max(0, right - left - leftSize, left - right - rightSize);
|
|
522
|
+
}
|
|
523
|
+
function vectorComponents(primitives, padding) {
|
|
524
|
+
const components = [];
|
|
525
|
+
for (const primitive of primitives) {
|
|
526
|
+
const matches = components.filter(
|
|
527
|
+
(component) => nearby(component.bounds, primitive.bounds, padding)
|
|
528
|
+
);
|
|
529
|
+
if (matches.length === 0) {
|
|
530
|
+
components.push({ bounds: primitive.bounds, primitives: [primitive] });
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
const target = matches[0];
|
|
534
|
+
target.primitives.push(primitive);
|
|
535
|
+
target.bounds = unionBounds([target.bounds, primitive.bounds]);
|
|
536
|
+
for (const component of matches.slice(1)) {
|
|
537
|
+
target.primitives.push(...component.primitives);
|
|
538
|
+
target.bounds = unionBounds([target.bounds, component.bounds]);
|
|
539
|
+
components.splice(components.indexOf(component), 1);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return components;
|
|
543
|
+
}
|
|
544
|
+
function nearby(left, right, padding) {
|
|
545
|
+
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);
|
|
546
|
+
}
|
|
547
|
+
function isPageBackground(fill, bounds2, page) {
|
|
548
|
+
if (!/^#f{6}$/i.test(fill.color)) return false;
|
|
549
|
+
const outside = bounds2.x < 0 || bounds2.y < 0 || bounds2.x + bounds2.width > page.width || bounds2.y + bounds2.height > page.height;
|
|
550
|
+
const large = bounds2.width * bounds2.height > page.width * page.height * 0.2;
|
|
551
|
+
return outside || large;
|
|
552
|
+
}
|
|
192
553
|
function withoutSemanticMediaSpans(page, media) {
|
|
193
554
|
const consumed = new Set(media.flatMap((item) => item.consumedSpans ?? []));
|
|
194
555
|
return consumed.size > 0 ? { ...page, spans: page.spans.filter((span) => !consumed.has(span)) } : page;
|
|
@@ -197,7 +558,7 @@ function vectorText(span, pageHeight, aliases) {
|
|
|
197
558
|
if (span.renderingMode === 3 || span.renderingMode === 7) return "";
|
|
198
559
|
const styles2 = [
|
|
199
560
|
cssColor(span.color) ? `fill:${span.color}` : "",
|
|
200
|
-
unitInterval(span.fillOpacity) ? `fill-opacity:${
|
|
561
|
+
unitInterval(span.fillOpacity) ? `fill-opacity:${number2(span.fillOpacity)}` : "",
|
|
201
562
|
...visualFontStyles(
|
|
202
563
|
span.fontFamily,
|
|
203
564
|
span.fontAssetId ? aliases.get(span.fontAssetId) : void 0
|
|
@@ -205,33 +566,16 @@ function vectorText(span, pageHeight, aliases) {
|
|
|
205
566
|
].filter(Boolean).join(";");
|
|
206
567
|
const anchorY = pageHeight - span.bounds.y;
|
|
207
568
|
const transform = span.transform;
|
|
208
|
-
const position = transform ? ` x="0" y="0" transform="matrix(${transform.map(
|
|
569
|
+
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
570
|
const extent = span.direction === "ttb" ? span.bounds.height : span.bounds.width;
|
|
210
|
-
const length = extent > 0 ? ` textLength="${
|
|
211
|
-
return `<text${position} font-size="${
|
|
571
|
+
const length = extent > 0 ? ` textLength="${number2(extent)}" lengthAdjust="spacingAndGlyphs"` : "";
|
|
572
|
+
return `<text${position} font-size="${number2(span.fontSize)}"${length}${styles2 ? ` style="${styles2}"` : ""}>${escapeHtml2(span.text)}</text>`;
|
|
212
573
|
}
|
|
213
574
|
function centerInside(inner, outer) {
|
|
214
575
|
const x = inner.x + inner.width / 2;
|
|
215
576
|
const y = inner.y + inner.height / 2;
|
|
216
577
|
return x >= outer.x && x <= outer.x + outer.width && y >= outer.y && y <= outer.y + outer.height;
|
|
217
578
|
}
|
|
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
579
|
function transformedUnitBounds([a, b, c, d, e, f]) {
|
|
236
580
|
const points = [
|
|
237
581
|
[e, f],
|
|
@@ -245,31 +589,8 @@ function transformedUnitBounds([a, b, c, d, e, f]) {
|
|
|
245
589
|
const minY = Math.min(...ys);
|
|
246
590
|
return { x: minX, y: minY, width: Math.max(...xs) - minX, height: Math.max(...ys) - minY };
|
|
247
591
|
}
|
|
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));
|
|
592
|
+
function unionBounds(bounds2) {
|
|
593
|
+
const values = bounds2.filter((value) => Boolean(value));
|
|
273
594
|
if (values.length === 0) return void 0;
|
|
274
595
|
const x = Math.min(...values.map((value) => value.x));
|
|
275
596
|
const y = Math.min(...values.map((value) => value.y));
|
|
@@ -300,19 +621,16 @@ function rgbBmp(image) {
|
|
|
300
621
|
}
|
|
301
622
|
return output;
|
|
302
623
|
}
|
|
303
|
-
function safePath(value) {
|
|
304
|
-
return value.length <= 1e6 && /^[\d\s.,+\-eEMmLlCcZz]+$/.test(value);
|
|
305
|
-
}
|
|
306
624
|
function cssColor(value) {
|
|
307
625
|
return /^#[\da-f]{6}$/i.test(value ?? "");
|
|
308
626
|
}
|
|
309
|
-
function
|
|
627
|
+
function finiteNonnegative2(value) {
|
|
310
628
|
return Number.isFinite(value) && (value ?? -1) >= 0;
|
|
311
629
|
}
|
|
312
630
|
function unitInterval(value) {
|
|
313
|
-
return
|
|
631
|
+
return finiteNonnegative2(value) && value <= 1;
|
|
314
632
|
}
|
|
315
|
-
function
|
|
633
|
+
function number2(value) {
|
|
316
634
|
return Number.isFinite(value) ? String(Math.round(value * 1e3) / 1e3) : "0";
|
|
317
635
|
}
|
|
318
636
|
function escapeHtml2(value) {
|
|
@@ -364,18 +682,52 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
364
682
|
const emitPage = async (page, future) => {
|
|
365
683
|
const defaultColor = dominantTextColor(page.structured.lines);
|
|
366
684
|
let mediaIndex = 0;
|
|
685
|
+
const captions = clearMediaCaptionAssociations(
|
|
686
|
+
page.media,
|
|
687
|
+
page.structured.blocks,
|
|
688
|
+
page.width,
|
|
689
|
+
page.height,
|
|
690
|
+
page.structured.lines
|
|
691
|
+
);
|
|
692
|
+
const captionedMedia = new Set(captions.values());
|
|
693
|
+
const emittedMedia = /* @__PURE__ */ new Set();
|
|
367
694
|
const futureFurniture = new Set(future.flatMap((candidate) => marginSignatures(candidate)));
|
|
368
695
|
const repeatedFurniture = /* @__PURE__ */ new Set([...seenFurniture, ...futureFurniture]);
|
|
369
696
|
for (const [blockIndex, block] of page.structured.blocks.entries()) {
|
|
370
697
|
const nextBlock = page.structured.blocks[blockIndex + 1];
|
|
371
698
|
const blockY = semanticBlockY(block);
|
|
699
|
+
let emittedAsCaption = false;
|
|
700
|
+
while (page.media[mediaIndex] && emittedMedia.has(page.media[mediaIndex])) {
|
|
701
|
+
mediaIndex += 1;
|
|
702
|
+
}
|
|
372
703
|
while ((page.media[mediaIndex]?.bounds.y ?? -Infinity) >= blockY) {
|
|
373
704
|
await flushPendingParagraph();
|
|
374
|
-
const
|
|
705
|
+
const item = page.media[mediaIndex];
|
|
706
|
+
if (item && captions.get(block) === item && block.type === "paragraph") {
|
|
707
|
+
const html2 = `<figure class="pdf-semantic-figure">${item.html}<figcaption>${semanticTextHtml(block.text, block.lines, defaultColor)}</figcaption></figure>`;
|
|
708
|
+
if (activeTable) pendingMedia.push(html2);
|
|
709
|
+
else await write(html2);
|
|
710
|
+
emittedMedia.add(item);
|
|
711
|
+
mediaIndex += 1;
|
|
712
|
+
emittedAsCaption = true;
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
if (item && captionedMedia.has(item)) break;
|
|
716
|
+
const html = `<div class="pdf-semantic-visual">${item?.html}</div>`;
|
|
375
717
|
if (activeTable) pendingMedia.push(html);
|
|
376
718
|
else await write(html);
|
|
377
719
|
mediaIndex += 1;
|
|
378
720
|
}
|
|
721
|
+
const associatedMedia = captions.get(block);
|
|
722
|
+
if (!emittedAsCaption && associatedMedia && block.type === "paragraph") {
|
|
723
|
+
await flushPendingParagraph();
|
|
724
|
+
const html = `<figure class="pdf-semantic-figure">${associatedMedia.html}<figcaption>${semanticTextHtml(block.text, block.lines, defaultColor)}</figcaption></figure>`;
|
|
725
|
+
if (activeTable) pendingMedia.push(html);
|
|
726
|
+
else await write(html);
|
|
727
|
+
emittedMedia.add(associatedMedia);
|
|
728
|
+
emittedAsCaption = true;
|
|
729
|
+
}
|
|
730
|
+
if (emittedAsCaption) continue;
|
|
379
731
|
if (isRepeatedFurniture(block, page, repeatedFurniture)) {
|
|
380
732
|
stats.suppressedFurniture += 1;
|
|
381
733
|
continue;
|
|
@@ -469,10 +821,13 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
469
821
|
await write(semanticBlockHtml(block, defaultColor));
|
|
470
822
|
}
|
|
471
823
|
while (mediaIndex < page.media.length) {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
824
|
+
const item = page.media[mediaIndex];
|
|
825
|
+
if (item && !emittedMedia.has(item)) {
|
|
826
|
+
await flushPendingParagraph();
|
|
827
|
+
const html = `<div class="pdf-semantic-visual">${item.html}</div>`;
|
|
828
|
+
if (activeTable) pendingMedia.push(html);
|
|
829
|
+
else await write(html);
|
|
830
|
+
}
|
|
476
831
|
mediaIndex += 1;
|
|
477
832
|
}
|
|
478
833
|
for (const signature of marginSignatures(page)) seenFurniture.add(signature);
|
|
@@ -606,6 +961,14 @@ function financialSummaryRow(entry, columns) {
|
|
|
606
961
|
return `<tr><th scope="row"${colspan}>${escapeHtml3(entry.term)}</th><td>${escapeHtml3(entry.description)}</td></tr>`;
|
|
607
962
|
}
|
|
608
963
|
function semanticBlockHtml(block, defaultColor = "#000000") {
|
|
964
|
+
if (block.type === "insetGroup") {
|
|
965
|
+
return `<div class="pdf-semantic-inset" style="margin-inline-start:${block.indentEm}em">${block.blocks.map((item) => semanticBlockHtml(item, defaultColor)).join("")}</div>`;
|
|
966
|
+
}
|
|
967
|
+
if (block.type === "table") {
|
|
968
|
+
const rows = (0, import_structure.tableToRows)(block.table);
|
|
969
|
+
const header = tableHeader(rows);
|
|
970
|
+
return `<table>${rows.map((row, index) => tableRow(row, Boolean(header && index === 0))).join("")}</table>`;
|
|
971
|
+
}
|
|
609
972
|
if (block.type === "heading")
|
|
610
973
|
return `<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor, false)}</h${block.level}>`;
|
|
611
974
|
if (block.type === "paragraph")
|
|
@@ -721,7 +1084,7 @@ async function writePositionedPage(page, write, options) {
|
|
|
721
1084
|
const displayWidth = quarterTurn ? page.height : page.width;
|
|
722
1085
|
const displayHeight = quarterTurn ? page.width : page.height;
|
|
723
1086
|
await write(
|
|
724
|
-
`<section class="pdf-page pdf-page--visual pdf-page--positioned" data-page="${page.number}" data-rotate="${page.rotate}" style="width:${
|
|
1087
|
+
`<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
1088
|
);
|
|
726
1089
|
const fontAliases = visualFontAliases(page.number, page.fonts ?? []);
|
|
727
1090
|
const type3Fonts = new Map(
|
|
@@ -733,44 +1096,26 @@ async function writePositionedPage(page, write, options) {
|
|
|
733
1096
|
);
|
|
734
1097
|
}
|
|
735
1098
|
await write(
|
|
736
|
-
`<div class="pdf-page-content pdf-page-content--${page.rotate}" style="width:${
|
|
1099
|
+
`<div class="pdf-page-content pdf-page-content--${page.rotate}" style="width:${number3(page.width)}pt;height:${number3(page.height)}pt${rotationTransform(page)}">`
|
|
737
1100
|
);
|
|
738
1101
|
await write(
|
|
739
|
-
`<svg class="pdf-visual-text" xmlns="http://www.w3.org/2000/svg" width="${
|
|
1102
|
+
`<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)}">`
|
|
1103
|
+
);
|
|
1104
|
+
const clipDefinitions = imageClipDefinitions(page.images ?? [], page.number, page.height) + vectorPathClipDefinitions(
|
|
1105
|
+
(page.paths ?? []).map((path, index) => ({ path, index })),
|
|
1106
|
+
page.number
|
|
740
1107
|
);
|
|
741
|
-
const clipDefinitions = imageClipDefinitions(page.images ?? [], page.number, page.height) + pathClipDefinitions(page.paths ?? [], page.number);
|
|
742
1108
|
if (clipDefinitions) await write(`<defs>${clipDefinitions}</defs>`);
|
|
743
1109
|
if (reflectedOverlay) {
|
|
744
1110
|
for (const [index, image] of (page.images ?? []).entries()) {
|
|
745
1111
|
await write(visualImage(image, page.height, page.number, index));
|
|
746
1112
|
}
|
|
747
1113
|
}
|
|
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);
|
|
1114
|
+
if (page.fills?.length || page.paths?.length) {
|
|
1115
|
+
await write(`<g transform="translate(0 ${number3(page.height)}) scale(1 -1)">`);
|
|
1116
|
+
for (const fill of page.fills ?? []) await write(vectorFillSvg(fill));
|
|
1117
|
+
for (const [pathIndex, path] of (page.paths ?? []).entries()) {
|
|
1118
|
+
await write(vectorPathSvg(path, page.number, pathIndex));
|
|
774
1119
|
}
|
|
775
1120
|
await write("</g>");
|
|
776
1121
|
}
|
|
@@ -800,8 +1145,8 @@ function usesReflectedVisualOverlay(page, spans) {
|
|
|
800
1145
|
}
|
|
801
1146
|
function visualImage(image, pageHeight, pageNumber, imageIndex) {
|
|
802
1147
|
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 =
|
|
1148
|
+
const transform = [a, -b, -c, d, c + e, pageHeight - d - f].map(number3).join(" ");
|
|
1149
|
+
const opacity = isUnitInterval2(image.opacity) ? ` opacity="${number3(image.opacity)}"` : "";
|
|
805
1150
|
const mime = image.format === "jpeg" ? "image/jpeg" : "image/bmp";
|
|
806
1151
|
const data = image.format === "jpeg" ? image.data : rgbBmp2(image);
|
|
807
1152
|
let output = `<image width="1" height="1" preserveAspectRatio="none" transform="matrix(${transform})" href="data:${mime};base64,${base64(data)}"${opacity}/>`;
|
|
@@ -815,25 +1160,13 @@ function imageClipDefinitions(images, pageNumber, pageHeight) {
|
|
|
815
1160
|
(image, imageIndex) => (image.clips ?? []).map((clip, clipIndex) => {
|
|
816
1161
|
if (!isSvgPath(clip.d)) return "";
|
|
817
1162
|
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 ${
|
|
1163
|
+
return `<clipPath id="${imageClipId(pageNumber, imageIndex, clipIndex)}" clipPathUnits="userSpaceOnUse"><path d="${clip.d}" transform="translate(0 ${number3(pageHeight)}) scale(1 -1)"${fillRule}/></clipPath>`;
|
|
819
1164
|
})
|
|
820
1165
|
).join("");
|
|
821
1166
|
}
|
|
822
1167
|
function imageClipId(pageNumber, imageIndex, clipIndex) {
|
|
823
1168
|
return `boxpdf-clip-${pageNumber}-${imageIndex}-${clipIndex}`;
|
|
824
1169
|
}
|
|
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
1170
|
function rgbBmp2(image) {
|
|
838
1171
|
const stride = Math.ceil(image.width * 3 / 4) * 4;
|
|
839
1172
|
const output = new Uint8Array(54 + stride * image.height);
|
|
@@ -862,11 +1195,11 @@ function rgbBmp2(image) {
|
|
|
862
1195
|
function rotationTransform(page) {
|
|
863
1196
|
switch (page.rotate) {
|
|
864
1197
|
case 90:
|
|
865
|
-
return `;transform:translate(${
|
|
1198
|
+
return `;transform:translate(${number3(page.height)}pt,0) rotate(90deg)`;
|
|
866
1199
|
case 180:
|
|
867
|
-
return `;transform:translate(${
|
|
1200
|
+
return `;transform:translate(${number3(page.width)}pt,${number3(page.height)}pt) rotate(180deg)`;
|
|
868
1201
|
case 270:
|
|
869
|
-
return `;transform:translate(0,${
|
|
1202
|
+
return `;transform:translate(0,${number3(page.width)}pt) rotate(270deg)`;
|
|
870
1203
|
default:
|
|
871
1204
|
return "";
|
|
872
1205
|
}
|
|
@@ -874,13 +1207,13 @@ function rotationTransform(page) {
|
|
|
874
1207
|
function positionedSpan(span, fontAliases) {
|
|
875
1208
|
const direction = directionAttribute([span]);
|
|
876
1209
|
const style = [
|
|
877
|
-
`left:${
|
|
878
|
-
`bottom:${
|
|
879
|
-
`width:${
|
|
880
|
-
`height:${
|
|
881
|
-
`font-size:${
|
|
882
|
-
...
|
|
883
|
-
...
|
|
1210
|
+
`left:${number3(span.bounds.x)}pt`,
|
|
1211
|
+
`bottom:${number3(span.bounds.y)}pt`,
|
|
1212
|
+
`width:${number3(span.bounds.width)}pt`,
|
|
1213
|
+
`height:${number3(span.bounds.height)}pt`,
|
|
1214
|
+
`font-size:${number3(span.fontSize)}pt`,
|
|
1215
|
+
...isCssHexColor2(span.color) ? [`color:${span.color}`] : [],
|
|
1216
|
+
...isUnitInterval2(span.fillOpacity) ? [`opacity:${number3(span.fillOpacity)}`] : [],
|
|
884
1217
|
...visualFontStyles(
|
|
885
1218
|
span.fontFamily,
|
|
886
1219
|
span.fontAssetId ? fontAliases.get(span.fontAssetId) : void 0
|
|
@@ -893,15 +1226,47 @@ async function writeFlowPage(page, write) {
|
|
|
893
1226
|
const structured = (0, import_structure2.structurePage)(withoutSemanticMediaSpans(page, media));
|
|
894
1227
|
const defaultColor = dominantTextColor(structured.lines);
|
|
895
1228
|
let mediaIndex = 0;
|
|
1229
|
+
const captions = clearMediaCaptionAssociations(
|
|
1230
|
+
media,
|
|
1231
|
+
structured.blocks,
|
|
1232
|
+
page.width,
|
|
1233
|
+
page.height,
|
|
1234
|
+
structured.lines
|
|
1235
|
+
);
|
|
1236
|
+
const captionedMedia = new Set(captions.values());
|
|
1237
|
+
const emittedMedia = /* @__PURE__ */ new Set();
|
|
896
1238
|
await write(
|
|
897
1239
|
`<section class="pdf-page pdf-page--semantic pdf-page--flow" data-page="${page.number}">`
|
|
898
1240
|
);
|
|
899
1241
|
for (const block of structured.blocks) {
|
|
900
1242
|
const blockY = semanticBlockY2(block);
|
|
1243
|
+
let emittedAsCaption = false;
|
|
1244
|
+
while (media[mediaIndex] && emittedMedia.has(media[mediaIndex]))
|
|
1245
|
+
mediaIndex += 1;
|
|
901
1246
|
while ((media[mediaIndex]?.bounds.y ?? -Infinity) >= blockY) {
|
|
902
|
-
|
|
1247
|
+
const item = media[mediaIndex];
|
|
1248
|
+
if (item && captions.get(block) === item && block.type === "paragraph") {
|
|
1249
|
+
await write(
|
|
1250
|
+
`<figure class="pdf-semantic-figure">${item.html}<figcaption>${semanticTextHtml(block.text, block.lines, defaultColor)}</figcaption></figure>`
|
|
1251
|
+
);
|
|
1252
|
+
emittedMedia.add(item);
|
|
1253
|
+
mediaIndex += 1;
|
|
1254
|
+
emittedAsCaption = true;
|
|
1255
|
+
break;
|
|
1256
|
+
}
|
|
1257
|
+
if (item && captionedMedia.has(item)) break;
|
|
1258
|
+
await write(`<div class="pdf-semantic-visual">${item?.html}</div>`);
|
|
903
1259
|
mediaIndex += 1;
|
|
904
1260
|
}
|
|
1261
|
+
const associatedMedia = captions.get(block);
|
|
1262
|
+
if (!emittedAsCaption && associatedMedia && block.type === "paragraph") {
|
|
1263
|
+
await write(
|
|
1264
|
+
`<figure class="pdf-semantic-figure">${associatedMedia.html}<figcaption>${semanticTextHtml(block.text, block.lines, defaultColor)}</figcaption></figure>`
|
|
1265
|
+
);
|
|
1266
|
+
emittedMedia.add(associatedMedia);
|
|
1267
|
+
emittedAsCaption = true;
|
|
1268
|
+
}
|
|
1269
|
+
if (emittedAsCaption) continue;
|
|
905
1270
|
if (block.type === "table") await write((0, import_structure2.tableToHtml)(block.table));
|
|
906
1271
|
else if (block.type === "heading") {
|
|
907
1272
|
await write(
|
|
@@ -941,6 +1306,10 @@ async function writeFlowPage(page, write) {
|
|
|
941
1306
|
await write(
|
|
942
1307
|
`<section><h3>${escapeHtml4(block.role)}</h3><p>${escapeHtml4(block.organization)}</p><p>${escapeHtml4(block.date)}</p></section>`
|
|
943
1308
|
);
|
|
1309
|
+
} else if (block.type === "insetGroup") {
|
|
1310
|
+
await write(
|
|
1311
|
+
`<div class="pdf-semantic-inset" style="margin-inline-start:${number3(block.indentEm)}em">${block.blocks.map((item) => nestedSemanticBlockHtml(item, defaultColor)).join("")}</div>`
|
|
1312
|
+
);
|
|
944
1313
|
} else {
|
|
945
1314
|
const tag = block.ordered ? "ol" : "ul";
|
|
946
1315
|
await write(`<${tag}>`);
|
|
@@ -951,11 +1320,41 @@ async function writeFlowPage(page, write) {
|
|
|
951
1320
|
}
|
|
952
1321
|
}
|
|
953
1322
|
while (mediaIndex < media.length) {
|
|
954
|
-
|
|
1323
|
+
const item = media[mediaIndex];
|
|
1324
|
+
if (item && !emittedMedia.has(item)) {
|
|
1325
|
+
await write(`<div class="pdf-semantic-visual">${item.html}</div>`);
|
|
1326
|
+
}
|
|
955
1327
|
mediaIndex += 1;
|
|
956
1328
|
}
|
|
957
1329
|
await write("</section>");
|
|
958
1330
|
}
|
|
1331
|
+
function nestedSemanticBlockHtml(block, defaultColor) {
|
|
1332
|
+
if (block.type === "insetGroup") {
|
|
1333
|
+
return `<div class="pdf-semantic-inset" style="margin-inline-start:${number3(block.indentEm)}em">${block.blocks.map((item) => nestedSemanticBlockHtml(item, defaultColor)).join("")}</div>`;
|
|
1334
|
+
}
|
|
1335
|
+
if (block.type === "table") return (0, import_structure2.tableToHtml)(block.table);
|
|
1336
|
+
if (block.type === "heading") {
|
|
1337
|
+
return `<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor, false)}</h${block.level}>`;
|
|
1338
|
+
}
|
|
1339
|
+
if (block.type === "paragraph") {
|
|
1340
|
+
return `<p>${semanticTextHtml(block.text, block.lines, defaultColor)}</p>`;
|
|
1341
|
+
}
|
|
1342
|
+
if (block.type === "preformatted") return `<pre>${escapeHtml4(block.text)}</pre>`;
|
|
1343
|
+
if (block.type === "definitionList") {
|
|
1344
|
+
return `<dl>${block.entries.map((entry) => `<div><dt>${escapeHtml4(entry.term)}</dt><dd>${escapeHtml4(entry.description)}</dd></div>`).join("")}</dl>`;
|
|
1345
|
+
}
|
|
1346
|
+
if (block.type === "cardList") {
|
|
1347
|
+
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>`;
|
|
1348
|
+
}
|
|
1349
|
+
if (block.type === "sectionGroup") {
|
|
1350
|
+
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>`;
|
|
1351
|
+
}
|
|
1352
|
+
if (block.type === "employment") {
|
|
1353
|
+
return `<section><h3>${escapeHtml4(block.role)}</h3><p>${escapeHtml4(block.organization)}</p><p>${escapeHtml4(block.date)}</p></section>`;
|
|
1354
|
+
}
|
|
1355
|
+
const tag = block.ordered ? "ol" : "ul";
|
|
1356
|
+
return `<${tag}>${block.items.map((item) => `<li>${semanticTextHtml(item.text, item.lines, defaultColor)}</li>`).join("")}</${tag}>`;
|
|
1357
|
+
}
|
|
959
1358
|
function semanticBlockY2(block) {
|
|
960
1359
|
const lines = block.type === "list" ? block.items.flatMap((item) => item.lines) : block.lines;
|
|
961
1360
|
return Math.max(...lines.map((line) => line.bounds.y + line.bounds.height));
|
|
@@ -968,15 +1367,15 @@ function visualText(span, pageHeight, fontAliases, counterRotateReflectedText =
|
|
|
968
1367
|
span.fontFamily,
|
|
969
1368
|
span.fontAssetId ? fontAliases.get(span.fontAssetId) : void 0
|
|
970
1369
|
).join(";");
|
|
971
|
-
const stroke =
|
|
972
|
-
const strokeWidth = stroke && Number.isFinite(span.strokeWidth) && (span.strokeWidth ?? -1) >= 0 ? `stroke-width:${
|
|
1370
|
+
const stroke = isCssHexColor2(span.strokeColor) ? `stroke:${span.strokeColor}` : "";
|
|
1371
|
+
const strokeWidth = stroke && Number.isFinite(span.strokeWidth) && (span.strokeWidth ?? -1) >= 0 ? `stroke-width:${number3(span.strokeWidth ?? 0)}` : "";
|
|
973
1372
|
const strokeOnly = span.renderingMode === 1 || span.renderingMode === 5;
|
|
974
|
-
const fillOpacity =
|
|
975
|
-
const strokeOpacity =
|
|
1373
|
+
const fillOpacity = isUnitInterval2(span.fillOpacity) ? `fill-opacity:${number3(span.fillOpacity)}` : "";
|
|
1374
|
+
const strokeOpacity = isUnitInterval2(span.strokeOpacity) ? `stroke-opacity:${number3(span.strokeOpacity)}` : "";
|
|
976
1375
|
const style = [
|
|
977
1376
|
isHebrewPaintOrder(span) ? "unicode-bidi:bidi-override;direction:ltr" : "",
|
|
978
1377
|
span.direction === "ttb" ? "writing-mode:vertical-rl" : "",
|
|
979
|
-
strokeOnly ? "fill:none" :
|
|
1378
|
+
strokeOnly ? "fill:none" : isCssHexColor2(span.color) ? `fill:${span.color}` : "",
|
|
980
1379
|
stroke,
|
|
981
1380
|
strokeWidth,
|
|
982
1381
|
fillOpacity,
|
|
@@ -984,7 +1383,7 @@ function visualText(span, pageHeight, fontAliases, counterRotateReflectedText =
|
|
|
984
1383
|
font
|
|
985
1384
|
].filter(Boolean).join(";");
|
|
986
1385
|
const textExtent = span.direction === "ttb" ? span.bounds.height : span.bounds.width;
|
|
987
|
-
const textLength = textExtent > 0 && !isHebrewPaintOrder(span) ? ` textLength="${
|
|
1386
|
+
const textLength = textExtent > 0 && !isHebrewPaintOrder(span) ? ` textLength="${number3(textExtent)}" lengthAdjust="${span.direction === "ttb" || usesSpacingAdjustment(span) ? "spacing" : "spacingAndGlyphs"}"` : "";
|
|
988
1387
|
const transform = counterRotateReflectedText && span.transform ? [
|
|
989
1388
|
span.transform[0],
|
|
990
1389
|
span.transform[1],
|
|
@@ -997,8 +1396,8 @@ function visualText(span, pageHeight, fontAliases, counterRotateReflectedText =
|
|
|
997
1396
|
const basisY = transform?.[1] ?? 0;
|
|
998
1397
|
const anchorX = span.bounds.x + basisX * rtlOffset;
|
|
999
1398
|
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="${
|
|
1399
|
+
const position = transformed ? ` x="0" y="0" transform="matrix(${transform?.map(number3).join(" ")} ${number3(anchorX)} ${number3(anchorY)})"` : ` x="${number3(anchorX)}" y="${number3(anchorY)}"`;
|
|
1400
|
+
return `<text${direction}${position} font-size="${number3(span.fontSize)}"${textLength}${style ? ` style="${style}"` : ""}>${escapeHtml4(span.text)}</text>`;
|
|
1002
1401
|
}
|
|
1003
1402
|
function isAdobeCjkFont(fontFamily) {
|
|
1004
1403
|
return /^Adobe(?:Heiti|Song|Kaiti|Ming|Gothic|Mincho)Std-/i.test(fontFamily ?? "");
|
|
@@ -1010,16 +1409,16 @@ function visualType3Text(span, font, pageHeight) {
|
|
|
1010
1409
|
const totalAdvance = sequence.reduce((total, glyph) => total + (glyph?.advance ?? 0), 0);
|
|
1011
1410
|
if (totalAdvance <= 0 || span.bounds.width <= 0 || span.fontSize <= 0) return "";
|
|
1012
1411
|
const transform = span.transform ?? [1, 0, 0, 1];
|
|
1013
|
-
const outer = `matrix(${transform.map(
|
|
1412
|
+
const outer = `matrix(${transform.map(number3).join(" ")} ${number3(span.bounds.x)} ${number3(pageHeight - span.bounds.y)})`;
|
|
1014
1413
|
const xScale = span.bounds.width / totalAdvance;
|
|
1015
1414
|
let offset = 0;
|
|
1016
1415
|
let content = "";
|
|
1017
1416
|
for (const glyph of sequence) {
|
|
1018
1417
|
if (!glyph) continue;
|
|
1019
|
-
content += `<g transform="translate(${
|
|
1418
|
+
content += `<g transform="translate(${number3(offset)} 0)">${type3Glyph(glyph, span.color)}</g>`;
|
|
1020
1419
|
offset += glyph.advance;
|
|
1021
1420
|
}
|
|
1022
|
-
return `<g transform="${outer}"><g transform="scale(${
|
|
1421
|
+
return `<g transform="${outer}"><g transform="scale(${number3(xScale)} ${number3(-span.fontSize)})">${content}</g></g>`;
|
|
1023
1422
|
}
|
|
1024
1423
|
function isHebrewPaintOrder(span) {
|
|
1025
1424
|
return span.direction === "ltr" && /[\u0590-\u05ff]/u.test(span.text);
|
|
@@ -1030,30 +1429,27 @@ function usesSpacingAdjustment(span) {
|
|
|
1030
1429
|
function type3Glyph(glyph, textColor) {
|
|
1031
1430
|
let output = "";
|
|
1032
1431
|
for (const fill of glyph.fills ?? []) {
|
|
1033
|
-
const color = glyph.usesTextColor &&
|
|
1034
|
-
if (!
|
|
1035
|
-
const points = fill.points.map(([x, y]) => `${
|
|
1036
|
-
const opacity =
|
|
1432
|
+
const color = glyph.usesTextColor && isCssHexColor2(textColor) ? textColor : fill.color;
|
|
1433
|
+
if (!isCssHexColor2(color)) continue;
|
|
1434
|
+
const points = fill.points.map(([x, y]) => `${number3(x)},${number3(y)}`).join(" ");
|
|
1435
|
+
const opacity = isUnitInterval2(fill.opacity) ? ` fill-opacity="${number3(fill.opacity)}"` : "";
|
|
1037
1436
|
output += `<polygon points="${points}" fill="${color}"${opacity}/>`;
|
|
1038
1437
|
}
|
|
1039
1438
|
for (const path of glyph.paths ?? []) {
|
|
1040
1439
|
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="${
|
|
1440
|
+
const fill = glyph.usesTextColor && isCssHexColor2(textColor) ? textColor : isCssHexColor2(path.fill) ? path.fill : "none";
|
|
1441
|
+
const stroke = glyph.usesTextColor && isCssHexColor2(textColor) && path.stroke ? textColor : isCssHexColor2(path.stroke) ? path.stroke : "none";
|
|
1442
|
+
const width = path.strokeWidth !== void 0 && Number.isFinite(path.strokeWidth) && path.strokeWidth >= 0 ? ` stroke-width="${number3(path.strokeWidth)}"` : "";
|
|
1044
1443
|
output += `<path d="${path.d}" fill="${fill}" stroke="${stroke}"${width}/>`;
|
|
1045
1444
|
}
|
|
1046
1445
|
return (glyph.fills?.length ?? 0) > 64 && glyph.advance > 2 ? `<g shape-rendering="crispEdges">${output}</g>` : output;
|
|
1047
1446
|
}
|
|
1048
|
-
function
|
|
1447
|
+
function isCssHexColor2(value) {
|
|
1049
1448
|
return /^#[\da-f]{6}$/i.test(value ?? "");
|
|
1050
1449
|
}
|
|
1051
|
-
function
|
|
1450
|
+
function isUnitInterval2(value) {
|
|
1052
1451
|
return Number.isFinite(value) && (value ?? -1) >= 0 && (value ?? 2) <= 1;
|
|
1053
1452
|
}
|
|
1054
|
-
function isSvgPath(value) {
|
|
1055
|
-
return value.length <= 1e6 && /^[\d\s.,+\-eEMmLlCcZz]+$/.test(value);
|
|
1056
|
-
}
|
|
1057
1453
|
function isMonospace(fontFamily) {
|
|
1058
1454
|
return /courier|mono/i.test(fontFamily ?? "");
|
|
1059
1455
|
}
|
|
@@ -1071,7 +1467,7 @@ function directionAttribute(spans) {
|
|
|
1071
1467
|
if (vertical > rtl && vertical * 2 >= spans.length) return ' data-direction="ttb"';
|
|
1072
1468
|
return rtl * 2 >= spans.length && spans.length > 0 ? ' dir="rtl"' : "";
|
|
1073
1469
|
}
|
|
1074
|
-
function
|
|
1470
|
+
function number3(value) {
|
|
1075
1471
|
return Number.isFinite(value) ? String(Math.round(value * 1e3) / 1e3) : "0";
|
|
1076
1472
|
}
|
|
1077
1473
|
function escapeAttribute(value) {
|