@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.js
CHANGED
|
@@ -1,6 +1,184 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { structurePage as structurePage2, tableToHtml } from "@boxpdf/reader/structure";
|
|
3
3
|
|
|
4
|
+
// src/semantic-caption.ts
|
|
5
|
+
var minimumCaptionScore = 0.72;
|
|
6
|
+
function clearMediaCaptionAssociations(media, blocks, pageWidth, pageHeight, pageLines) {
|
|
7
|
+
const candidates = blocks.flatMap((block) => {
|
|
8
|
+
const candidate = captionCandidate(block);
|
|
9
|
+
return candidate ? [candidate] : [];
|
|
10
|
+
});
|
|
11
|
+
const preliminary = media.flatMap(
|
|
12
|
+
(item) => candidates.flatMap((candidate) => {
|
|
13
|
+
const evidence = scoreCaption(item, candidate, pageWidth, pageHeight, pageLines, 0);
|
|
14
|
+
return evidence ? [{ media: item, candidate, evidence }] : [];
|
|
15
|
+
})
|
|
16
|
+
);
|
|
17
|
+
const patterns = repeatedPatterns(preliminary);
|
|
18
|
+
const edges = preliminary.map((edge) => {
|
|
19
|
+
const evidence = scoreCaption(
|
|
20
|
+
edge.media,
|
|
21
|
+
edge.candidate,
|
|
22
|
+
pageWidth,
|
|
23
|
+
pageHeight,
|
|
24
|
+
pageLines,
|
|
25
|
+
patterns.get(patternKey(edge)) ?? 0
|
|
26
|
+
);
|
|
27
|
+
return evidence ? { ...edge, evidence } : void 0;
|
|
28
|
+
}).filter((edge) => Boolean(edge)).filter((edge) => edge.evidence.score >= minimumCaptionScore);
|
|
29
|
+
const bestForMedia = bestEdges(edges, (edge) => edge.media);
|
|
30
|
+
const bestForCaption = bestEdges(edges, (edge) => edge.candidate.block);
|
|
31
|
+
const associations = /* @__PURE__ */ new Map();
|
|
32
|
+
for (const edge of edges) {
|
|
33
|
+
if (bestForMedia.get(edge.media) === edge && bestForCaption.get(edge.candidate.block) === edge) {
|
|
34
|
+
associations.set(edge.candidate.block, edge.media);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return associations;
|
|
38
|
+
}
|
|
39
|
+
function scoreCaption(media, candidate, pageWidth, pageHeight, pageLines, repeatedAlignment) {
|
|
40
|
+
if (!insidePage(media.bounds, pageWidth, pageHeight)) return void 0;
|
|
41
|
+
if (media.bounds.width < pageWidth * 0.06 || media.bounds.height < candidate.lineHeight * 1.5)
|
|
42
|
+
return void 0;
|
|
43
|
+
const relation = verticalRelation(media.bounds, candidate.bounds);
|
|
44
|
+
if (!relation) return void 0;
|
|
45
|
+
const maximumGap = Math.max(candidate.lineHeight * 3, pageHeight * 0.035);
|
|
46
|
+
if (relation.gap > maximumGap) return void 0;
|
|
47
|
+
const overlapWidth = overlap(
|
|
48
|
+
media.bounds.x,
|
|
49
|
+
media.bounds.width,
|
|
50
|
+
candidate.bounds.x,
|
|
51
|
+
candidate.bounds.width
|
|
52
|
+
);
|
|
53
|
+
const horizontalOverlap = overlapWidth / Math.max(1, Math.min(media.bounds.width, candidate.bounds.width));
|
|
54
|
+
const centerDistance = Math.abs(center(media.bounds) - center(candidate.bounds));
|
|
55
|
+
const centerAlignment = clamp01(
|
|
56
|
+
1 - centerDistance / Math.max(media.bounds.width, candidate.bounds.width)
|
|
57
|
+
);
|
|
58
|
+
if (horizontalOverlap < 0.45 && centerAlignment < 0.82) return void 0;
|
|
59
|
+
const relativeWidth = Math.min(media.bounds.width, candidate.bounds.width) / Math.max(media.bounds.width, candidate.bounds.width);
|
|
60
|
+
const gapRatio = clamp01(1 - relation.gap / maximumGap);
|
|
61
|
+
const interveningContent = interveningScore(media.bounds, candidate, relation.side, pageLines);
|
|
62
|
+
if (interveningContent === 0) return void 0;
|
|
63
|
+
if (relation.side === "above" && (gapRatio < 0.6 || interveningContent < 1)) return void 0;
|
|
64
|
+
const fontContrast = captionFontContrast(candidate, pageLines);
|
|
65
|
+
const surroundingWhitespace = whitespaceScore(candidate, relation.side, relation.gap, pageLines);
|
|
66
|
+
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;
|
|
67
|
+
return {
|
|
68
|
+
score,
|
|
69
|
+
side: relation.side,
|
|
70
|
+
gapRatio,
|
|
71
|
+
horizontalOverlap,
|
|
72
|
+
centerAlignment,
|
|
73
|
+
relativeWidth,
|
|
74
|
+
fontContrast,
|
|
75
|
+
surroundingWhitespace,
|
|
76
|
+
interveningContent,
|
|
77
|
+
repeatedAlignment
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function captionCandidate(block) {
|
|
81
|
+
if (block.type !== "paragraph" || block.lines.length === 0) return void 0;
|
|
82
|
+
const first = block.lines.flatMap((line) => line.spans).find((span) => /\S/u.test(span.text));
|
|
83
|
+
if (!first) return void 0;
|
|
84
|
+
return {
|
|
85
|
+
block,
|
|
86
|
+
bounds: unionLines(block.lines),
|
|
87
|
+
lineHeight: median(block.lines.map((line) => line.bounds.height)),
|
|
88
|
+
font: fontSignature(first)
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function verticalRelation(media, caption) {
|
|
92
|
+
const belowGap = media.y - (caption.y + caption.height);
|
|
93
|
+
if (belowGap >= -caption.height * 0.15) return { side: "below", gap: Math.max(0, belowGap) };
|
|
94
|
+
const aboveGap = caption.y - (media.y + media.height);
|
|
95
|
+
if (aboveGap >= -caption.height * 0.15) return { side: "above", gap: Math.max(0, aboveGap) };
|
|
96
|
+
return void 0;
|
|
97
|
+
}
|
|
98
|
+
function interveningScore(media, candidate, side, pageLines) {
|
|
99
|
+
const lower = side === "below" ? candidate.bounds.y + candidate.bounds.height : media.y + media.height;
|
|
100
|
+
const upper = side === "below" ? media.y : candidate.bounds.y;
|
|
101
|
+
const blockers = pageLines.filter(
|
|
102
|
+
(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
|
|
103
|
+
);
|
|
104
|
+
return blockers.length === 0 ? 1 : blockers.length === 1 ? 0.35 : 0;
|
|
105
|
+
}
|
|
106
|
+
function captionFontContrast(candidate, pageLines) {
|
|
107
|
+
const otherLines = pageLines.filter((line) => !candidate.block.lines.includes(line));
|
|
108
|
+
if (candidate.font !== dominantFontSignature(otherLines)) return 1;
|
|
109
|
+
const candidateSize = median(
|
|
110
|
+
candidate.block.lines.flatMap((line) => line.spans.map((span) => span.fontSize))
|
|
111
|
+
);
|
|
112
|
+
const bodySize = median(otherLines.flatMap((line) => line.spans.map((span) => span.fontSize)));
|
|
113
|
+
return Math.abs(candidateSize - bodySize) >= 0.75 ? 0.65 : 0.15;
|
|
114
|
+
}
|
|
115
|
+
function whitespaceScore(candidate, side, mediaGap, pageLines) {
|
|
116
|
+
const awayGaps = pageLines.filter((line) => !candidate.block.lines.includes(line)).filter(
|
|
117
|
+
(line) => overlap(line.bounds.x, line.bounds.width, candidate.bounds.x, candidate.bounds.width) > 0
|
|
118
|
+
).flatMap((line) => {
|
|
119
|
+
if (side === "below" && line.bounds.y + line.bounds.height <= candidate.bounds.y)
|
|
120
|
+
return [candidate.bounds.y - line.bounds.y - line.bounds.height];
|
|
121
|
+
if (side === "above" && line.bounds.y >= candidate.bounds.y + candidate.bounds.height)
|
|
122
|
+
return [line.bounds.y - candidate.bounds.y - candidate.bounds.height];
|
|
123
|
+
return [];
|
|
124
|
+
});
|
|
125
|
+
const awayGap = Math.min(...awayGaps, Number.POSITIVE_INFINITY);
|
|
126
|
+
return Number.isFinite(awayGap) ? clamp01((awayGap + candidate.lineHeight * 0.25) / (mediaGap + candidate.lineHeight)) : 1;
|
|
127
|
+
}
|
|
128
|
+
function repeatedPatterns(edges) {
|
|
129
|
+
const counts = /* @__PURE__ */ new Map();
|
|
130
|
+
for (const edge of edges.filter((item) => item.evidence.score >= minimumCaptionScore - 0.08)) {
|
|
131
|
+
counts.set(patternKey(edge), (counts.get(patternKey(edge)) ?? 0) + 1);
|
|
132
|
+
}
|
|
133
|
+
return new Map([...counts].map(([key, count]) => [key, count >= 2 ? 1 : 0]));
|
|
134
|
+
}
|
|
135
|
+
function patternKey(edge) {
|
|
136
|
+
const widthRatio = edge.candidate.bounds.width / Math.max(1, edge.media.bounds.width);
|
|
137
|
+
return `${edge.candidate.font}|${edge.evidence.side}|${Math.round(widthRatio * 4) / 4}`;
|
|
138
|
+
}
|
|
139
|
+
function bestEdges(edges, key) {
|
|
140
|
+
const output = /* @__PURE__ */ new Map();
|
|
141
|
+
for (const edge of edges) {
|
|
142
|
+
const existing = output.get(key(edge));
|
|
143
|
+
if (!existing || edge.evidence.score > existing.evidence.score) output.set(key(edge), edge);
|
|
144
|
+
}
|
|
145
|
+
return output;
|
|
146
|
+
}
|
|
147
|
+
function insidePage(bounds2, pageWidth, pageHeight) {
|
|
148
|
+
return bounds2.x >= -2 && bounds2.y >= -2 && bounds2.x + bounds2.width <= pageWidth + 2 && bounds2.y + bounds2.height <= pageHeight + 2;
|
|
149
|
+
}
|
|
150
|
+
function unionLines(lines) {
|
|
151
|
+
const x = Math.min(...lines.map((line) => line.bounds.x));
|
|
152
|
+
const y = Math.min(...lines.map((line) => line.bounds.y));
|
|
153
|
+
const right = Math.max(...lines.map((line) => line.bounds.x + line.bounds.width));
|
|
154
|
+
const top = Math.max(...lines.map((line) => line.bounds.y + line.bounds.height));
|
|
155
|
+
return { x, y, width: right - x, height: top - y };
|
|
156
|
+
}
|
|
157
|
+
function dominantFontSignature(lines) {
|
|
158
|
+
const counts = /* @__PURE__ */ new Map();
|
|
159
|
+
for (const span of lines.flatMap((line) => line.spans)) {
|
|
160
|
+
const signature = fontSignature(span);
|
|
161
|
+
counts.set(signature, (counts.get(signature) ?? 0) + Math.max(1, [...span.text].length));
|
|
162
|
+
}
|
|
163
|
+
return [...counts].sort((left, right) => right[1] - left[1])[0]?.[0] ?? "";
|
|
164
|
+
}
|
|
165
|
+
function fontSignature(span) {
|
|
166
|
+
return `${(span.fontFamily ?? span.fontName ?? "").toLocaleLowerCase("en")}|${Math.round(span.fontSize * 2) / 2}|${span.color ?? ""}`;
|
|
167
|
+
}
|
|
168
|
+
function center(bounds2) {
|
|
169
|
+
return bounds2.x + bounds2.width / 2;
|
|
170
|
+
}
|
|
171
|
+
function overlap(left, leftSize, right, rightSize) {
|
|
172
|
+
return Math.max(0, Math.min(left + leftSize, right + rightSize) - Math.max(left, right));
|
|
173
|
+
}
|
|
174
|
+
function clamp01(value) {
|
|
175
|
+
return Math.max(0, Math.min(1, value));
|
|
176
|
+
}
|
|
177
|
+
function median(values) {
|
|
178
|
+
const ordered = [...values].sort((left, right) => left - right);
|
|
179
|
+
return ordered[Math.floor(ordered.length / 2)] ?? 1;
|
|
180
|
+
}
|
|
181
|
+
|
|
4
182
|
// src/semantic-document.ts
|
|
5
183
|
import {
|
|
6
184
|
structurePage,
|
|
@@ -76,6 +254,85 @@ function escapeHtml(value) {
|
|
|
76
254
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
77
255
|
}
|
|
78
256
|
|
|
257
|
+
// src/vector-svg.ts
|
|
258
|
+
function vectorFillSvg(fill) {
|
|
259
|
+
if (!isCssHexColor(fill.color)) return "";
|
|
260
|
+
const points = fill.points.map(([x, y]) => `${number(x)},${number(y)}`).join(" ");
|
|
261
|
+
const opacity = isUnitInterval(fill.opacity) ? ` fill-opacity="${number(fill.opacity)}"` : "";
|
|
262
|
+
return `<polygon points="${points}" fill="${fill.color}"${opacity}/>`;
|
|
263
|
+
}
|
|
264
|
+
function vectorPathSvg(path, pageNumber, pathIndex) {
|
|
265
|
+
if (!isSvgPath(path.d)) return "";
|
|
266
|
+
const fill = isCssHexColor(path.fill) ? path.fill : "none";
|
|
267
|
+
const stroke = isCssHexColor(path.stroke) ? path.stroke : "none";
|
|
268
|
+
const width = finiteNonnegative(path.strokeWidth) ? ` stroke-width="${number(path.strokeWidth)}"` : "";
|
|
269
|
+
const fillOpacity = isUnitInterval(path.fillOpacity) ? ` fill-opacity="${number(path.fillOpacity)}"` : "";
|
|
270
|
+
const strokeOpacity = isUnitInterval(path.strokeOpacity) ? ` stroke-opacity="${number(path.strokeOpacity)}"` : "";
|
|
271
|
+
const dash = path.strokeDasharray?.every(finiteNonnegative) ? ` stroke-dasharray="${path.strokeDasharray.map(number).join(" ")}"` : "";
|
|
272
|
+
const dashoffset = Number.isFinite(path.strokeDashoffset) ? ` stroke-dashoffset="${number(path.strokeDashoffset ?? 0)}"` : "";
|
|
273
|
+
const linecap = path.strokeLinecap ? ` stroke-linecap="${path.strokeLinecap}"` : "";
|
|
274
|
+
const linejoin = path.strokeLinejoin ? ` stroke-linejoin="${path.strokeLinejoin}"` : "";
|
|
275
|
+
const rule = path.fillRule ? ` fill-rule="${path.fillRule}"` : "";
|
|
276
|
+
let output = `<path d="${path.d}" fill="${fill}" stroke="${stroke}"${width}${fillOpacity}${strokeOpacity}${dash}${dashoffset}${linecap}${linejoin}${rule}/>`;
|
|
277
|
+
for (let index = (path.clips?.length ?? 0) - 1; index >= 0; index -= 1) {
|
|
278
|
+
output = `<g clip-path="url(#${vectorPathClipId(pageNumber, pathIndex, index)})">${output}</g>`;
|
|
279
|
+
}
|
|
280
|
+
return output;
|
|
281
|
+
}
|
|
282
|
+
function vectorPathClipDefinitions(paths, pageNumber) {
|
|
283
|
+
return paths.flatMap(
|
|
284
|
+
({ path, index: pathIndex }) => (path.clips ?? []).map((clip, clipIndex) => {
|
|
285
|
+
if (!isSvgPath(clip.d)) return "";
|
|
286
|
+
const rule = clip.fillRule ? ` clip-rule="${clip.fillRule}"` : "";
|
|
287
|
+
return `<clipPath id="${vectorPathClipId(pageNumber, pathIndex, clipIndex)}" clipPathUnits="userSpaceOnUse"><path d="${clip.d}"${rule}/></clipPath>`;
|
|
288
|
+
})
|
|
289
|
+
).join("");
|
|
290
|
+
}
|
|
291
|
+
function vectorPathBounds(path) {
|
|
292
|
+
if (!isSvgPath(path.d)) return void 0;
|
|
293
|
+
const values = [...path.d.matchAll(/[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?/gi)].map(
|
|
294
|
+
(match) => Number(match[0])
|
|
295
|
+
);
|
|
296
|
+
if (values.length < 2) return void 0;
|
|
297
|
+
const xs = [];
|
|
298
|
+
const ys = [];
|
|
299
|
+
for (let index = 0; index + 1 < values.length; index += 2) {
|
|
300
|
+
xs.push(values[index] ?? 0);
|
|
301
|
+
ys.push(values[index + 1] ?? 0);
|
|
302
|
+
}
|
|
303
|
+
return bounds(xs, ys);
|
|
304
|
+
}
|
|
305
|
+
function vectorFillBounds(fill) {
|
|
306
|
+
if (fill.points.length === 0) return void 0;
|
|
307
|
+
return bounds(
|
|
308
|
+
fill.points.map(([x]) => x),
|
|
309
|
+
fill.points.map(([, y]) => y)
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
function isSvgPath(value) {
|
|
313
|
+
return value.length <= 1e6 && /^[\d\s.,+\-eEMmLlCcZz]+$/.test(value);
|
|
314
|
+
}
|
|
315
|
+
function bounds(xs, ys) {
|
|
316
|
+
const x = Math.min(...xs);
|
|
317
|
+
const y = Math.min(...ys);
|
|
318
|
+
return { x, y, width: Math.max(...xs) - x, height: Math.max(...ys) - y };
|
|
319
|
+
}
|
|
320
|
+
function vectorPathClipId(pageNumber, pathIndex, clipIndex) {
|
|
321
|
+
return `boxpdf-path-clip-${pageNumber}-${pathIndex}-${clipIndex}`;
|
|
322
|
+
}
|
|
323
|
+
function isCssHexColor(value) {
|
|
324
|
+
return typeof value === "string" && /^#[0-9a-f]{6}$/i.test(value);
|
|
325
|
+
}
|
|
326
|
+
function finiteNonnegative(value) {
|
|
327
|
+
return value !== void 0 && Number.isFinite(value) && value >= 0;
|
|
328
|
+
}
|
|
329
|
+
function isUnitInterval(value) {
|
|
330
|
+
return value !== void 0 && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
331
|
+
}
|
|
332
|
+
function number(value) {
|
|
333
|
+
return Number(value.toFixed(4)).toString();
|
|
334
|
+
}
|
|
335
|
+
|
|
79
336
|
// src/visual-font.ts
|
|
80
337
|
function visualFontAliases(pageNumber, fonts) {
|
|
81
338
|
return new Map(
|
|
@@ -125,47 +382,151 @@ function base64(bytes) {
|
|
|
125
382
|
// src/semantic-media.ts
|
|
126
383
|
function semanticMedia(page) {
|
|
127
384
|
const output = (page.images ?? []).map((image) => rasterMedia(image));
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return output.sort((left, right) => right.bounds.y - left.bounds.y);
|
|
385
|
+
output.push(...vectorMedia(page));
|
|
386
|
+
return mediaComponents(output, page).sort((left, right) => right.bounds.y - left.bounds.y);
|
|
131
387
|
}
|
|
132
388
|
function rasterMedia(image) {
|
|
133
|
-
const
|
|
389
|
+
const bounds2 = transformedUnitBounds(image.transform);
|
|
134
390
|
const mime = image.format === "jpeg" ? "image/jpeg" : "image/bmp";
|
|
135
391
|
const data = image.format === "jpeg" ? image.data : rgbBmp(image);
|
|
136
|
-
const opacity = unitInterval(image.opacity) ? `;opacity:${
|
|
392
|
+
const opacity = unitInterval(image.opacity) ? `;opacity:${number2(image.opacity)}` : "";
|
|
137
393
|
return {
|
|
138
|
-
bounds,
|
|
139
|
-
|
|
394
|
+
bounds: bounds2,
|
|
395
|
+
kind: "raster",
|
|
396
|
+
html: `<img class="pdf-semantic-media" src="data:${mime};base64,${base64(data)}" width="${number2(bounds2.width)}" height="${number2(bounds2.height)}" alt="" style="max-width:100%;height:auto${opacity}">`
|
|
140
397
|
};
|
|
141
398
|
}
|
|
142
399
|
function vectorMedia(page) {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
400
|
+
const primitives = [
|
|
401
|
+
...(page.paths ?? []).flatMap((path, index) => {
|
|
402
|
+
const bounds2 = vectorPathBounds(path);
|
|
403
|
+
return bounds2 ? [{ type: "path", value: path, index, bounds: bounds2 }] : [];
|
|
404
|
+
}),
|
|
405
|
+
...(page.fills ?? []).flatMap((fill) => {
|
|
406
|
+
const bounds2 = vectorFillBounds(fill);
|
|
407
|
+
return bounds2 && !isPageBackground(fill, bounds2, page) ? [{ type: "fill", value: fill, bounds: bounds2 }] : [];
|
|
408
|
+
})
|
|
409
|
+
];
|
|
410
|
+
const components = vectorComponents(primitives, Math.min(36, page.width * 0.06)).filter(
|
|
411
|
+
(component) => component.primitives.length >= 2 || component.bounds.width * component.bounds.height >= page.width * page.height * 2e-3
|
|
412
|
+
);
|
|
150
413
|
const aliases = visualFontAliases(page.number, page.fonts ?? []);
|
|
151
414
|
const visualCodeFonts = new Set(
|
|
152
415
|
(page.fonts ?? []).filter((font) => font.format === "truetype" && font.visualCodeMapping).map((font) => font.id)
|
|
153
416
|
);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
417
|
+
return components.map((component) => {
|
|
418
|
+
const bounds2 = component.bounds;
|
|
419
|
+
const paths = component.primitives.flatMap(
|
|
420
|
+
(primitive) => primitive.type === "path" ? [{ path: primitive.value, index: primitive.index }] : []
|
|
421
|
+
);
|
|
422
|
+
const fills = component.primitives.flatMap(
|
|
423
|
+
(primitive) => primitive.type === "fill" ? [primitive.value] : []
|
|
424
|
+
);
|
|
425
|
+
const visualSpans = page.visualSpans ?? page.spans;
|
|
426
|
+
const overlay = visualSpans.filter(
|
|
427
|
+
(span) => span.fontAssetId && visualCodeFonts.has(span.fontAssetId) && centerInside(span.bounds, bounds2)
|
|
428
|
+
);
|
|
429
|
+
const consumedSpans = page.spans.filter(
|
|
430
|
+
(span) => span.fontAssetId && visualCodeFonts.has(span.fontAssetId) && centerInside(span.bounds, bounds2)
|
|
431
|
+
);
|
|
432
|
+
const fontIds = new Set(overlay.map((span) => span.fontAssetId));
|
|
433
|
+
const fontFaces = (page.fonts ?? []).filter((font) => fontIds.has(font.id)).map((font) => visualFontFace(font, aliases)).join("");
|
|
434
|
+
return {
|
|
435
|
+
bounds: bounds2,
|
|
436
|
+
kind: "vector",
|
|
437
|
+
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>`,
|
|
438
|
+
...consumedSpans.length > 0 ? { consumedSpans } : {}
|
|
439
|
+
};
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
function mediaComponents(media, page) {
|
|
443
|
+
const components = [];
|
|
444
|
+
for (const item of media) {
|
|
445
|
+
if (isPageBackdrop(item.bounds, page)) {
|
|
446
|
+
components.push([item]);
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
const matches = components.filter(
|
|
450
|
+
(component) => !component.some((member) => isPageBackdrop(member.bounds, page)) && component.some((member) => mediaPiecesTouch(member.bounds, item.bounds))
|
|
451
|
+
);
|
|
452
|
+
if (matches.length === 0) {
|
|
453
|
+
components.push([item]);
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
const target = matches[0];
|
|
457
|
+
target.push(item);
|
|
458
|
+
for (const component of matches.slice(1)) {
|
|
459
|
+
target.push(...component);
|
|
460
|
+
components.splice(components.indexOf(component), 1);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return components.map((component) => compositeMedia(component));
|
|
464
|
+
}
|
|
465
|
+
function compositeMedia(items) {
|
|
466
|
+
if (items.length === 1) return items[0];
|
|
467
|
+
const bounds2 = unionBounds(items.map((item) => item.bounds));
|
|
468
|
+
const layers = items.map((item) => {
|
|
469
|
+
const left = (item.bounds.x - bounds2.x) / bounds2.width * 100;
|
|
470
|
+
const top = (bounds2.y + bounds2.height - item.bounds.y - item.bounds.height) / bounds2.height * 100;
|
|
471
|
+
const width = item.bounds.width / bounds2.width * 100;
|
|
472
|
+
const height = item.bounds.height / bounds2.height * 100;
|
|
473
|
+
return `<div style="position:absolute;left:${number2(left)}%;top:${number2(top)}%;width:${number2(width)}%;height:${number2(height)}%;overflow:hidden">${item.html}</div>`;
|
|
474
|
+
}).join("");
|
|
163
475
|
return {
|
|
164
|
-
bounds,
|
|
165
|
-
|
|
166
|
-
|
|
476
|
+
bounds: bounds2,
|
|
477
|
+
kind: "composite",
|
|
478
|
+
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>`,
|
|
479
|
+
consumedSpans: items.flatMap((item) => item.consumedSpans ?? [])
|
|
167
480
|
};
|
|
168
481
|
}
|
|
482
|
+
function mediaPiecesTouch(left, right) {
|
|
483
|
+
const xOverlap = overlap2(left.x, left.width, right.x, right.width);
|
|
484
|
+
const yOverlap = overlap2(left.y, left.height, right.y, right.height);
|
|
485
|
+
if (xOverlap > 0 && yOverlap > 0) return true;
|
|
486
|
+
const horizontalGap = axisGap(left.x, left.width, right.x, right.width);
|
|
487
|
+
const verticalGap = axisGap(left.y, left.height, right.y, right.height);
|
|
488
|
+
if (horizontalGap <= 2 && yOverlap / Math.min(left.height, right.height) >= 0.65) return true;
|
|
489
|
+
return verticalGap <= 2 && xOverlap / Math.min(left.width, right.width) >= 0.65;
|
|
490
|
+
}
|
|
491
|
+
function isPageBackdrop(bounds2, page) {
|
|
492
|
+
return bounds2.width * bounds2.height >= page.width * page.height * 0.7;
|
|
493
|
+
}
|
|
494
|
+
function overlap2(left, leftSize, right, rightSize) {
|
|
495
|
+
return Math.max(0, Math.min(left + leftSize, right + rightSize) - Math.max(left, right));
|
|
496
|
+
}
|
|
497
|
+
function axisGap(left, leftSize, right, rightSize) {
|
|
498
|
+
return Math.max(0, right - left - leftSize, left - right - rightSize);
|
|
499
|
+
}
|
|
500
|
+
function vectorComponents(primitives, padding) {
|
|
501
|
+
const components = [];
|
|
502
|
+
for (const primitive of primitives) {
|
|
503
|
+
const matches = components.filter(
|
|
504
|
+
(component) => nearby(component.bounds, primitive.bounds, padding)
|
|
505
|
+
);
|
|
506
|
+
if (matches.length === 0) {
|
|
507
|
+
components.push({ bounds: primitive.bounds, primitives: [primitive] });
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
const target = matches[0];
|
|
511
|
+
target.primitives.push(primitive);
|
|
512
|
+
target.bounds = unionBounds([target.bounds, primitive.bounds]);
|
|
513
|
+
for (const component of matches.slice(1)) {
|
|
514
|
+
target.primitives.push(...component.primitives);
|
|
515
|
+
target.bounds = unionBounds([target.bounds, component.bounds]);
|
|
516
|
+
components.splice(components.indexOf(component), 1);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return components;
|
|
520
|
+
}
|
|
521
|
+
function nearby(left, right, padding) {
|
|
522
|
+
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);
|
|
523
|
+
}
|
|
524
|
+
function isPageBackground(fill, bounds2, page) {
|
|
525
|
+
if (!/^#f{6}$/i.test(fill.color)) return false;
|
|
526
|
+
const outside = bounds2.x < 0 || bounds2.y < 0 || bounds2.x + bounds2.width > page.width || bounds2.y + bounds2.height > page.height;
|
|
527
|
+
const large = bounds2.width * bounds2.height > page.width * page.height * 0.2;
|
|
528
|
+
return outside || large;
|
|
529
|
+
}
|
|
169
530
|
function withoutSemanticMediaSpans(page, media) {
|
|
170
531
|
const consumed = new Set(media.flatMap((item) => item.consumedSpans ?? []));
|
|
171
532
|
return consumed.size > 0 ? { ...page, spans: page.spans.filter((span) => !consumed.has(span)) } : page;
|
|
@@ -174,7 +535,7 @@ function vectorText(span, pageHeight, aliases) {
|
|
|
174
535
|
if (span.renderingMode === 3 || span.renderingMode === 7) return "";
|
|
175
536
|
const styles2 = [
|
|
176
537
|
cssColor(span.color) ? `fill:${span.color}` : "",
|
|
177
|
-
unitInterval(span.fillOpacity) ? `fill-opacity:${
|
|
538
|
+
unitInterval(span.fillOpacity) ? `fill-opacity:${number2(span.fillOpacity)}` : "",
|
|
178
539
|
...visualFontStyles(
|
|
179
540
|
span.fontFamily,
|
|
180
541
|
span.fontAssetId ? aliases.get(span.fontAssetId) : void 0
|
|
@@ -182,33 +543,16 @@ function vectorText(span, pageHeight, aliases) {
|
|
|
182
543
|
].filter(Boolean).join(";");
|
|
183
544
|
const anchorY = pageHeight - span.bounds.y;
|
|
184
545
|
const transform = span.transform;
|
|
185
|
-
const position = transform ? ` x="0" y="0" transform="matrix(${transform.map(
|
|
546
|
+
const position = transform ? ` x="0" y="0" transform="matrix(${transform.map(number2).join(" ")} ${number2(span.bounds.x)} ${number2(anchorY)})"` : ` x="${number2(span.bounds.x)}" y="${number2(anchorY)}"`;
|
|
186
547
|
const extent = span.direction === "ttb" ? span.bounds.height : span.bounds.width;
|
|
187
|
-
const length = extent > 0 ? ` textLength="${
|
|
188
|
-
return `<text${position} font-size="${
|
|
548
|
+
const length = extent > 0 ? ` textLength="${number2(extent)}" lengthAdjust="spacingAndGlyphs"` : "";
|
|
549
|
+
return `<text${position} font-size="${number2(span.fontSize)}"${length}${styles2 ? ` style="${styles2}"` : ""}>${escapeHtml2(span.text)}</text>`;
|
|
189
550
|
}
|
|
190
551
|
function centerInside(inner, outer) {
|
|
191
552
|
const x = inner.x + inner.width / 2;
|
|
192
553
|
const y = inner.y + inner.height / 2;
|
|
193
554
|
return x >= outer.x && x <= outer.x + outer.width && y >= outer.y && y <= outer.y + outer.height;
|
|
194
555
|
}
|
|
195
|
-
function vectorFill(fill) {
|
|
196
|
-
const points = fill.points.map(([x, y]) => `${number(x)},${number(y)}`).join(" ");
|
|
197
|
-
const opacity = unitInterval(fill.opacity) ? ` fill-opacity="${number(fill.opacity)}"` : "";
|
|
198
|
-
return cssColor(fill.color) ? `<polygon points="${points}" fill="${fill.color}"${opacity}/>` : "";
|
|
199
|
-
}
|
|
200
|
-
function vectorPath(path) {
|
|
201
|
-
const fill = cssColor(path.fill) ? path.fill : "none";
|
|
202
|
-
const stroke = cssColor(path.stroke) ? path.stroke : "none";
|
|
203
|
-
const width = finiteNonnegative(path.strokeWidth) ? ` stroke-width="${number(path.strokeWidth)}"` : "";
|
|
204
|
-
const fillOpacity = unitInterval(path.fillOpacity) ? ` fill-opacity="${number(path.fillOpacity)}"` : "";
|
|
205
|
-
const strokeOpacity = unitInterval(path.strokeOpacity) ? ` stroke-opacity="${number(path.strokeOpacity)}"` : "";
|
|
206
|
-
const dash = path.strokeDasharray?.every(finiteNonnegative) ? ` stroke-dasharray="${path.strokeDasharray.map(number).join(" ")}"` : "";
|
|
207
|
-
const linecap = path.strokeLinecap ? ` stroke-linecap="${path.strokeLinecap}"` : "";
|
|
208
|
-
const linejoin = path.strokeLinejoin ? ` stroke-linejoin="${path.strokeLinejoin}"` : "";
|
|
209
|
-
const rule = path.fillRule ? ` fill-rule="${path.fillRule}"` : "";
|
|
210
|
-
return `<path d="${path.d}" fill="${fill}" stroke="${stroke}"${width}${fillOpacity}${strokeOpacity}${dash}${linecap}${linejoin}${rule}/>`;
|
|
211
|
-
}
|
|
212
556
|
function transformedUnitBounds([a, b, c, d, e, f]) {
|
|
213
557
|
const points = [
|
|
214
558
|
[e, f],
|
|
@@ -222,31 +566,8 @@ function transformedUnitBounds([a, b, c, d, e, f]) {
|
|
|
222
566
|
const minY = Math.min(...ys);
|
|
223
567
|
return { x: minX, y: minY, width: Math.max(...xs) - minX, height: Math.max(...ys) - minY };
|
|
224
568
|
}
|
|
225
|
-
function
|
|
226
|
-
const values =
|
|
227
|
-
(match) => Number(match[0])
|
|
228
|
-
);
|
|
229
|
-
if (values.length < 2) return void 0;
|
|
230
|
-
const xs = [];
|
|
231
|
-
const ys = [];
|
|
232
|
-
for (let index = 0; index + 1 < values.length; index += 2) {
|
|
233
|
-
xs.push(values[index] ?? 0);
|
|
234
|
-
ys.push(values[index + 1] ?? 0);
|
|
235
|
-
}
|
|
236
|
-
const minX = Math.min(...xs);
|
|
237
|
-
const minY = Math.min(...ys);
|
|
238
|
-
return { x: minX, y: minY, width: Math.max(...xs) - minX, height: Math.max(...ys) - minY };
|
|
239
|
-
}
|
|
240
|
-
function fillBounds(fill) {
|
|
241
|
-
if (fill.points.length === 0) return void 0;
|
|
242
|
-
const xs = fill.points.map(([x]) => x);
|
|
243
|
-
const ys = fill.points.map(([, y]) => y);
|
|
244
|
-
const minX = Math.min(...xs);
|
|
245
|
-
const minY = Math.min(...ys);
|
|
246
|
-
return { x: minX, y: minY, width: Math.max(...xs) - minX, height: Math.max(...ys) - minY };
|
|
247
|
-
}
|
|
248
|
-
function unionBounds(bounds) {
|
|
249
|
-
const values = bounds.filter((value) => Boolean(value));
|
|
569
|
+
function unionBounds(bounds2) {
|
|
570
|
+
const values = bounds2.filter((value) => Boolean(value));
|
|
250
571
|
if (values.length === 0) return void 0;
|
|
251
572
|
const x = Math.min(...values.map((value) => value.x));
|
|
252
573
|
const y = Math.min(...values.map((value) => value.y));
|
|
@@ -277,19 +598,16 @@ function rgbBmp(image) {
|
|
|
277
598
|
}
|
|
278
599
|
return output;
|
|
279
600
|
}
|
|
280
|
-
function safePath(value) {
|
|
281
|
-
return value.length <= 1e6 && /^[\d\s.,+\-eEMmLlCcZz]+$/.test(value);
|
|
282
|
-
}
|
|
283
601
|
function cssColor(value) {
|
|
284
602
|
return /^#[\da-f]{6}$/i.test(value ?? "");
|
|
285
603
|
}
|
|
286
|
-
function
|
|
604
|
+
function finiteNonnegative2(value) {
|
|
287
605
|
return Number.isFinite(value) && (value ?? -1) >= 0;
|
|
288
606
|
}
|
|
289
607
|
function unitInterval(value) {
|
|
290
|
-
return
|
|
608
|
+
return finiteNonnegative2(value) && value <= 1;
|
|
291
609
|
}
|
|
292
|
-
function
|
|
610
|
+
function number2(value) {
|
|
293
611
|
return Number.isFinite(value) ? String(Math.round(value * 1e3) / 1e3) : "0";
|
|
294
612
|
}
|
|
295
613
|
function escapeHtml2(value) {
|
|
@@ -341,18 +659,52 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
341
659
|
const emitPage = async (page, future) => {
|
|
342
660
|
const defaultColor = dominantTextColor(page.structured.lines);
|
|
343
661
|
let mediaIndex = 0;
|
|
662
|
+
const captions = clearMediaCaptionAssociations(
|
|
663
|
+
page.media,
|
|
664
|
+
page.structured.blocks,
|
|
665
|
+
page.width,
|
|
666
|
+
page.height,
|
|
667
|
+
page.structured.lines
|
|
668
|
+
);
|
|
669
|
+
const captionedMedia = new Set(captions.values());
|
|
670
|
+
const emittedMedia = /* @__PURE__ */ new Set();
|
|
344
671
|
const futureFurniture = new Set(future.flatMap((candidate) => marginSignatures(candidate)));
|
|
345
672
|
const repeatedFurniture = /* @__PURE__ */ new Set([...seenFurniture, ...futureFurniture]);
|
|
346
673
|
for (const [blockIndex, block] of page.structured.blocks.entries()) {
|
|
347
674
|
const nextBlock = page.structured.blocks[blockIndex + 1];
|
|
348
675
|
const blockY = semanticBlockY(block);
|
|
676
|
+
let emittedAsCaption = false;
|
|
677
|
+
while (page.media[mediaIndex] && emittedMedia.has(page.media[mediaIndex])) {
|
|
678
|
+
mediaIndex += 1;
|
|
679
|
+
}
|
|
349
680
|
while ((page.media[mediaIndex]?.bounds.y ?? -Infinity) >= blockY) {
|
|
350
681
|
await flushPendingParagraph();
|
|
351
|
-
const
|
|
682
|
+
const item = page.media[mediaIndex];
|
|
683
|
+
if (item && captions.get(block) === item && block.type === "paragraph") {
|
|
684
|
+
const html2 = `<figure class="pdf-semantic-figure">${item.html}<figcaption>${semanticTextHtml(block.text, block.lines, defaultColor)}</figcaption></figure>`;
|
|
685
|
+
if (activeTable) pendingMedia.push(html2);
|
|
686
|
+
else await write(html2);
|
|
687
|
+
emittedMedia.add(item);
|
|
688
|
+
mediaIndex += 1;
|
|
689
|
+
emittedAsCaption = true;
|
|
690
|
+
break;
|
|
691
|
+
}
|
|
692
|
+
if (item && captionedMedia.has(item)) break;
|
|
693
|
+
const html = `<div class="pdf-semantic-visual">${item?.html}</div>`;
|
|
352
694
|
if (activeTable) pendingMedia.push(html);
|
|
353
695
|
else await write(html);
|
|
354
696
|
mediaIndex += 1;
|
|
355
697
|
}
|
|
698
|
+
const associatedMedia = captions.get(block);
|
|
699
|
+
if (!emittedAsCaption && associatedMedia && block.type === "paragraph") {
|
|
700
|
+
await flushPendingParagraph();
|
|
701
|
+
const html = `<figure class="pdf-semantic-figure">${associatedMedia.html}<figcaption>${semanticTextHtml(block.text, block.lines, defaultColor)}</figcaption></figure>`;
|
|
702
|
+
if (activeTable) pendingMedia.push(html);
|
|
703
|
+
else await write(html);
|
|
704
|
+
emittedMedia.add(associatedMedia);
|
|
705
|
+
emittedAsCaption = true;
|
|
706
|
+
}
|
|
707
|
+
if (emittedAsCaption) continue;
|
|
356
708
|
if (isRepeatedFurniture(block, page, repeatedFurniture)) {
|
|
357
709
|
stats.suppressedFurniture += 1;
|
|
358
710
|
continue;
|
|
@@ -446,10 +798,13 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
446
798
|
await write(semanticBlockHtml(block, defaultColor));
|
|
447
799
|
}
|
|
448
800
|
while (mediaIndex < page.media.length) {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
801
|
+
const item = page.media[mediaIndex];
|
|
802
|
+
if (item && !emittedMedia.has(item)) {
|
|
803
|
+
await flushPendingParagraph();
|
|
804
|
+
const html = `<div class="pdf-semantic-visual">${item.html}</div>`;
|
|
805
|
+
if (activeTable) pendingMedia.push(html);
|
|
806
|
+
else await write(html);
|
|
807
|
+
}
|
|
453
808
|
mediaIndex += 1;
|
|
454
809
|
}
|
|
455
810
|
for (const signature of marginSignatures(page)) seenFurniture.add(signature);
|
|
@@ -583,6 +938,14 @@ function financialSummaryRow(entry, columns) {
|
|
|
583
938
|
return `<tr><th scope="row"${colspan}>${escapeHtml3(entry.term)}</th><td>${escapeHtml3(entry.description)}</td></tr>`;
|
|
584
939
|
}
|
|
585
940
|
function semanticBlockHtml(block, defaultColor = "#000000") {
|
|
941
|
+
if (block.type === "insetGroup") {
|
|
942
|
+
return `<div class="pdf-semantic-inset" style="margin-inline-start:${block.indentEm}em">${block.blocks.map((item) => semanticBlockHtml(item, defaultColor)).join("")}</div>`;
|
|
943
|
+
}
|
|
944
|
+
if (block.type === "table") {
|
|
945
|
+
const rows = tableToRows(block.table);
|
|
946
|
+
const header = tableHeader(rows);
|
|
947
|
+
return `<table>${rows.map((row, index) => tableRow(row, Boolean(header && index === 0))).join("")}</table>`;
|
|
948
|
+
}
|
|
586
949
|
if (block.type === "heading")
|
|
587
950
|
return `<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor, false)}</h${block.level}>`;
|
|
588
951
|
if (block.type === "paragraph")
|
|
@@ -698,7 +1061,7 @@ async function writePositionedPage(page, write, options) {
|
|
|
698
1061
|
const displayWidth = quarterTurn ? page.height : page.width;
|
|
699
1062
|
const displayHeight = quarterTurn ? page.width : page.height;
|
|
700
1063
|
await write(
|
|
701
|
-
`<section class="pdf-page pdf-page--visual pdf-page--positioned" data-page="${page.number}" data-rotate="${page.rotate}" style="width:${
|
|
1064
|
+
`<section class="pdf-page pdf-page--visual pdf-page--positioned" data-page="${page.number}" data-rotate="${page.rotate}" style="width:${number3(displayWidth)}pt;height:${number3(displayHeight)}pt">`
|
|
702
1065
|
);
|
|
703
1066
|
const fontAliases = visualFontAliases(page.number, page.fonts ?? []);
|
|
704
1067
|
const type3Fonts = new Map(
|
|
@@ -710,44 +1073,26 @@ async function writePositionedPage(page, write, options) {
|
|
|
710
1073
|
);
|
|
711
1074
|
}
|
|
712
1075
|
await write(
|
|
713
|
-
`<div class="pdf-page-content pdf-page-content--${page.rotate}" style="width:${
|
|
1076
|
+
`<div class="pdf-page-content pdf-page-content--${page.rotate}" style="width:${number3(page.width)}pt;height:${number3(page.height)}pt${rotationTransform(page)}">`
|
|
714
1077
|
);
|
|
715
1078
|
await write(
|
|
716
|
-
`<svg class="pdf-visual-text" xmlns="http://www.w3.org/2000/svg" width="${
|
|
1079
|
+
`<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)}">`
|
|
1080
|
+
);
|
|
1081
|
+
const clipDefinitions = imageClipDefinitions(page.images ?? [], page.number, page.height) + vectorPathClipDefinitions(
|
|
1082
|
+
(page.paths ?? []).map((path, index) => ({ path, index })),
|
|
1083
|
+
page.number
|
|
717
1084
|
);
|
|
718
|
-
const clipDefinitions = imageClipDefinitions(page.images ?? [], page.number, page.height) + pathClipDefinitions(page.paths ?? [], page.number);
|
|
719
1085
|
if (clipDefinitions) await write(`<defs>${clipDefinitions}</defs>`);
|
|
720
1086
|
if (reflectedOverlay) {
|
|
721
1087
|
for (const [index, image] of (page.images ?? []).entries()) {
|
|
722
1088
|
await write(visualImage(image, page.height, page.number, index));
|
|
723
1089
|
}
|
|
724
1090
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
await write(
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
if (page.paths?.length) {
|
|
733
|
-
await write(`<g transform="translate(0 ${number2(page.height)}) scale(1 -1)">`);
|
|
734
|
-
for (const [pathIndex, path] of page.paths.entries()) {
|
|
735
|
-
if (!isSvgPath(path.d)) continue;
|
|
736
|
-
const fill = isCssHexColor(path.fill) ? path.fill : "none";
|
|
737
|
-
const stroke = isCssHexColor(path.stroke) ? path.stroke : "none";
|
|
738
|
-
const strokeWidth = path.strokeWidth !== void 0 && Number.isFinite(path.strokeWidth) && path.strokeWidth >= 0 ? ` stroke-width="${number2(path.strokeWidth)}"` : "";
|
|
739
|
-
const fillRule = path.fillRule ? ` fill-rule="${path.fillRule}"` : "";
|
|
740
|
-
const fillOpacity = isUnitInterval(path.fillOpacity) ? ` fill-opacity="${number2(path.fillOpacity)}"` : "";
|
|
741
|
-
const strokeOpacity = isUnitInterval(path.strokeOpacity) ? ` stroke-opacity="${number2(path.strokeOpacity)}"` : "";
|
|
742
|
-
const dasharray = path.strokeDasharray?.every((value) => Number.isFinite(value) && value >= 0) ? ` stroke-dasharray="${path.strokeDasharray.map(number2).join(" ")}"` : "";
|
|
743
|
-
const dashoffset = Number.isFinite(path.strokeDashoffset) ? ` stroke-dashoffset="${number2(path.strokeDashoffset ?? 0)}"` : "";
|
|
744
|
-
const linecap = path.strokeLinecap ? ` stroke-linecap="${path.strokeLinecap}"` : "";
|
|
745
|
-
const linejoin = path.strokeLinejoin ? ` stroke-linejoin="${path.strokeLinejoin}"` : "";
|
|
746
|
-
let output = `<path d="${path.d}" fill="${fill}" stroke="${stroke}"${strokeWidth}${fillOpacity}${strokeOpacity}${dasharray}${dashoffset}${linecap}${linejoin}${fillRule}/>`;
|
|
747
|
-
for (let index = (path.clips?.length ?? 0) - 1; index >= 0; index -= 1) {
|
|
748
|
-
output = `<g clip-path="url(#${pathClipId(page.number, pathIndex, index)})">${output}</g>`;
|
|
749
|
-
}
|
|
750
|
-
await write(output);
|
|
1091
|
+
if (page.fills?.length || page.paths?.length) {
|
|
1092
|
+
await write(`<g transform="translate(0 ${number3(page.height)}) scale(1 -1)">`);
|
|
1093
|
+
for (const fill of page.fills ?? []) await write(vectorFillSvg(fill));
|
|
1094
|
+
for (const [pathIndex, path] of (page.paths ?? []).entries()) {
|
|
1095
|
+
await write(vectorPathSvg(path, page.number, pathIndex));
|
|
751
1096
|
}
|
|
752
1097
|
await write("</g>");
|
|
753
1098
|
}
|
|
@@ -777,8 +1122,8 @@ function usesReflectedVisualOverlay(page, spans) {
|
|
|
777
1122
|
}
|
|
778
1123
|
function visualImage(image, pageHeight, pageNumber, imageIndex) {
|
|
779
1124
|
const [a, b, c, d, e, f] = image.transform;
|
|
780
|
-
const transform = [a, -b, -c, d, c + e, pageHeight - d - f].map(
|
|
781
|
-
const opacity =
|
|
1125
|
+
const transform = [a, -b, -c, d, c + e, pageHeight - d - f].map(number3).join(" ");
|
|
1126
|
+
const opacity = isUnitInterval2(image.opacity) ? ` opacity="${number3(image.opacity)}"` : "";
|
|
782
1127
|
const mime = image.format === "jpeg" ? "image/jpeg" : "image/bmp";
|
|
783
1128
|
const data = image.format === "jpeg" ? image.data : rgbBmp2(image);
|
|
784
1129
|
let output = `<image width="1" height="1" preserveAspectRatio="none" transform="matrix(${transform})" href="data:${mime};base64,${base64(data)}"${opacity}/>`;
|
|
@@ -792,25 +1137,13 @@ function imageClipDefinitions(images, pageNumber, pageHeight) {
|
|
|
792
1137
|
(image, imageIndex) => (image.clips ?? []).map((clip, clipIndex) => {
|
|
793
1138
|
if (!isSvgPath(clip.d)) return "";
|
|
794
1139
|
const fillRule = clip.fillRule ? ` clip-rule="${clip.fillRule}"` : "";
|
|
795
|
-
return `<clipPath id="${imageClipId(pageNumber, imageIndex, clipIndex)}" clipPathUnits="userSpaceOnUse"><path d="${clip.d}" transform="translate(0 ${
|
|
1140
|
+
return `<clipPath id="${imageClipId(pageNumber, imageIndex, clipIndex)}" clipPathUnits="userSpaceOnUse"><path d="${clip.d}" transform="translate(0 ${number3(pageHeight)}) scale(1 -1)"${fillRule}/></clipPath>`;
|
|
796
1141
|
})
|
|
797
1142
|
).join("");
|
|
798
1143
|
}
|
|
799
1144
|
function imageClipId(pageNumber, imageIndex, clipIndex) {
|
|
800
1145
|
return `boxpdf-clip-${pageNumber}-${imageIndex}-${clipIndex}`;
|
|
801
1146
|
}
|
|
802
|
-
function pathClipDefinitions(paths, pageNumber) {
|
|
803
|
-
return paths.flatMap(
|
|
804
|
-
(path, pathIndex) => (path.clips ?? []).map((clip, clipIndex) => {
|
|
805
|
-
if (!isSvgPath(clip.d)) return "";
|
|
806
|
-
const fillRule = clip.fillRule ? ` clip-rule="${clip.fillRule}"` : "";
|
|
807
|
-
return `<clipPath id="${pathClipId(pageNumber, pathIndex, clipIndex)}" clipPathUnits="userSpaceOnUse"><path d="${clip.d}"${fillRule}/></clipPath>`;
|
|
808
|
-
})
|
|
809
|
-
).join("");
|
|
810
|
-
}
|
|
811
|
-
function pathClipId(pageNumber, pathIndex, clipIndex) {
|
|
812
|
-
return `boxpdf-path-clip-${pageNumber}-${pathIndex}-${clipIndex}`;
|
|
813
|
-
}
|
|
814
1147
|
function rgbBmp2(image) {
|
|
815
1148
|
const stride = Math.ceil(image.width * 3 / 4) * 4;
|
|
816
1149
|
const output = new Uint8Array(54 + stride * image.height);
|
|
@@ -839,11 +1172,11 @@ function rgbBmp2(image) {
|
|
|
839
1172
|
function rotationTransform(page) {
|
|
840
1173
|
switch (page.rotate) {
|
|
841
1174
|
case 90:
|
|
842
|
-
return `;transform:translate(${
|
|
1175
|
+
return `;transform:translate(${number3(page.height)}pt,0) rotate(90deg)`;
|
|
843
1176
|
case 180:
|
|
844
|
-
return `;transform:translate(${
|
|
1177
|
+
return `;transform:translate(${number3(page.width)}pt,${number3(page.height)}pt) rotate(180deg)`;
|
|
845
1178
|
case 270:
|
|
846
|
-
return `;transform:translate(0,${
|
|
1179
|
+
return `;transform:translate(0,${number3(page.width)}pt) rotate(270deg)`;
|
|
847
1180
|
default:
|
|
848
1181
|
return "";
|
|
849
1182
|
}
|
|
@@ -851,13 +1184,13 @@ function rotationTransform(page) {
|
|
|
851
1184
|
function positionedSpan(span, fontAliases) {
|
|
852
1185
|
const direction = directionAttribute([span]);
|
|
853
1186
|
const style = [
|
|
854
|
-
`left:${
|
|
855
|
-
`bottom:${
|
|
856
|
-
`width:${
|
|
857
|
-
`height:${
|
|
858
|
-
`font-size:${
|
|
859
|
-
...
|
|
860
|
-
...
|
|
1187
|
+
`left:${number3(span.bounds.x)}pt`,
|
|
1188
|
+
`bottom:${number3(span.bounds.y)}pt`,
|
|
1189
|
+
`width:${number3(span.bounds.width)}pt`,
|
|
1190
|
+
`height:${number3(span.bounds.height)}pt`,
|
|
1191
|
+
`font-size:${number3(span.fontSize)}pt`,
|
|
1192
|
+
...isCssHexColor2(span.color) ? [`color:${span.color}`] : [],
|
|
1193
|
+
...isUnitInterval2(span.fillOpacity) ? [`opacity:${number3(span.fillOpacity)}`] : [],
|
|
861
1194
|
...visualFontStyles(
|
|
862
1195
|
span.fontFamily,
|
|
863
1196
|
span.fontAssetId ? fontAliases.get(span.fontAssetId) : void 0
|
|
@@ -870,15 +1203,47 @@ async function writeFlowPage(page, write) {
|
|
|
870
1203
|
const structured = structurePage2(withoutSemanticMediaSpans(page, media));
|
|
871
1204
|
const defaultColor = dominantTextColor(structured.lines);
|
|
872
1205
|
let mediaIndex = 0;
|
|
1206
|
+
const captions = clearMediaCaptionAssociations(
|
|
1207
|
+
media,
|
|
1208
|
+
structured.blocks,
|
|
1209
|
+
page.width,
|
|
1210
|
+
page.height,
|
|
1211
|
+
structured.lines
|
|
1212
|
+
);
|
|
1213
|
+
const captionedMedia = new Set(captions.values());
|
|
1214
|
+
const emittedMedia = /* @__PURE__ */ new Set();
|
|
873
1215
|
await write(
|
|
874
1216
|
`<section class="pdf-page pdf-page--semantic pdf-page--flow" data-page="${page.number}">`
|
|
875
1217
|
);
|
|
876
1218
|
for (const block of structured.blocks) {
|
|
877
1219
|
const blockY = semanticBlockY2(block);
|
|
1220
|
+
let emittedAsCaption = false;
|
|
1221
|
+
while (media[mediaIndex] && emittedMedia.has(media[mediaIndex]))
|
|
1222
|
+
mediaIndex += 1;
|
|
878
1223
|
while ((media[mediaIndex]?.bounds.y ?? -Infinity) >= blockY) {
|
|
879
|
-
|
|
1224
|
+
const item = media[mediaIndex];
|
|
1225
|
+
if (item && captions.get(block) === item && block.type === "paragraph") {
|
|
1226
|
+
await write(
|
|
1227
|
+
`<figure class="pdf-semantic-figure">${item.html}<figcaption>${semanticTextHtml(block.text, block.lines, defaultColor)}</figcaption></figure>`
|
|
1228
|
+
);
|
|
1229
|
+
emittedMedia.add(item);
|
|
1230
|
+
mediaIndex += 1;
|
|
1231
|
+
emittedAsCaption = true;
|
|
1232
|
+
break;
|
|
1233
|
+
}
|
|
1234
|
+
if (item && captionedMedia.has(item)) break;
|
|
1235
|
+
await write(`<div class="pdf-semantic-visual">${item?.html}</div>`);
|
|
880
1236
|
mediaIndex += 1;
|
|
881
1237
|
}
|
|
1238
|
+
const associatedMedia = captions.get(block);
|
|
1239
|
+
if (!emittedAsCaption && associatedMedia && block.type === "paragraph") {
|
|
1240
|
+
await write(
|
|
1241
|
+
`<figure class="pdf-semantic-figure">${associatedMedia.html}<figcaption>${semanticTextHtml(block.text, block.lines, defaultColor)}</figcaption></figure>`
|
|
1242
|
+
);
|
|
1243
|
+
emittedMedia.add(associatedMedia);
|
|
1244
|
+
emittedAsCaption = true;
|
|
1245
|
+
}
|
|
1246
|
+
if (emittedAsCaption) continue;
|
|
882
1247
|
if (block.type === "table") await write(tableToHtml(block.table));
|
|
883
1248
|
else if (block.type === "heading") {
|
|
884
1249
|
await write(
|
|
@@ -918,6 +1283,10 @@ async function writeFlowPage(page, write) {
|
|
|
918
1283
|
await write(
|
|
919
1284
|
`<section><h3>${escapeHtml4(block.role)}</h3><p>${escapeHtml4(block.organization)}</p><p>${escapeHtml4(block.date)}</p></section>`
|
|
920
1285
|
);
|
|
1286
|
+
} else if (block.type === "insetGroup") {
|
|
1287
|
+
await write(
|
|
1288
|
+
`<div class="pdf-semantic-inset" style="margin-inline-start:${number3(block.indentEm)}em">${block.blocks.map((item) => nestedSemanticBlockHtml(item, defaultColor)).join("")}</div>`
|
|
1289
|
+
);
|
|
921
1290
|
} else {
|
|
922
1291
|
const tag = block.ordered ? "ol" : "ul";
|
|
923
1292
|
await write(`<${tag}>`);
|
|
@@ -928,11 +1297,41 @@ async function writeFlowPage(page, write) {
|
|
|
928
1297
|
}
|
|
929
1298
|
}
|
|
930
1299
|
while (mediaIndex < media.length) {
|
|
931
|
-
|
|
1300
|
+
const item = media[mediaIndex];
|
|
1301
|
+
if (item && !emittedMedia.has(item)) {
|
|
1302
|
+
await write(`<div class="pdf-semantic-visual">${item.html}</div>`);
|
|
1303
|
+
}
|
|
932
1304
|
mediaIndex += 1;
|
|
933
1305
|
}
|
|
934
1306
|
await write("</section>");
|
|
935
1307
|
}
|
|
1308
|
+
function nestedSemanticBlockHtml(block, defaultColor) {
|
|
1309
|
+
if (block.type === "insetGroup") {
|
|
1310
|
+
return `<div class="pdf-semantic-inset" style="margin-inline-start:${number3(block.indentEm)}em">${block.blocks.map((item) => nestedSemanticBlockHtml(item, defaultColor)).join("")}</div>`;
|
|
1311
|
+
}
|
|
1312
|
+
if (block.type === "table") return tableToHtml(block.table);
|
|
1313
|
+
if (block.type === "heading") {
|
|
1314
|
+
return `<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor, false)}</h${block.level}>`;
|
|
1315
|
+
}
|
|
1316
|
+
if (block.type === "paragraph") {
|
|
1317
|
+
return `<p>${semanticTextHtml(block.text, block.lines, defaultColor)}</p>`;
|
|
1318
|
+
}
|
|
1319
|
+
if (block.type === "preformatted") return `<pre>${escapeHtml4(block.text)}</pre>`;
|
|
1320
|
+
if (block.type === "definitionList") {
|
|
1321
|
+
return `<dl>${block.entries.map((entry) => `<div><dt>${escapeHtml4(entry.term)}</dt><dd>${escapeHtml4(entry.description)}</dd></div>`).join("")}</dl>`;
|
|
1322
|
+
}
|
|
1323
|
+
if (block.type === "cardList") {
|
|
1324
|
+
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>`;
|
|
1325
|
+
}
|
|
1326
|
+
if (block.type === "sectionGroup") {
|
|
1327
|
+
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>`;
|
|
1328
|
+
}
|
|
1329
|
+
if (block.type === "employment") {
|
|
1330
|
+
return `<section><h3>${escapeHtml4(block.role)}</h3><p>${escapeHtml4(block.organization)}</p><p>${escapeHtml4(block.date)}</p></section>`;
|
|
1331
|
+
}
|
|
1332
|
+
const tag = block.ordered ? "ol" : "ul";
|
|
1333
|
+
return `<${tag}>${block.items.map((item) => `<li>${semanticTextHtml(item.text, item.lines, defaultColor)}</li>`).join("")}</${tag}>`;
|
|
1334
|
+
}
|
|
936
1335
|
function semanticBlockY2(block) {
|
|
937
1336
|
const lines = block.type === "list" ? block.items.flatMap((item) => item.lines) : block.lines;
|
|
938
1337
|
return Math.max(...lines.map((line) => line.bounds.y + line.bounds.height));
|
|
@@ -945,15 +1344,15 @@ function visualText(span, pageHeight, fontAliases, counterRotateReflectedText =
|
|
|
945
1344
|
span.fontFamily,
|
|
946
1345
|
span.fontAssetId ? fontAliases.get(span.fontAssetId) : void 0
|
|
947
1346
|
).join(";");
|
|
948
|
-
const stroke =
|
|
949
|
-
const strokeWidth = stroke && Number.isFinite(span.strokeWidth) && (span.strokeWidth ?? -1) >= 0 ? `stroke-width:${
|
|
1347
|
+
const stroke = isCssHexColor2(span.strokeColor) ? `stroke:${span.strokeColor}` : "";
|
|
1348
|
+
const strokeWidth = stroke && Number.isFinite(span.strokeWidth) && (span.strokeWidth ?? -1) >= 0 ? `stroke-width:${number3(span.strokeWidth ?? 0)}` : "";
|
|
950
1349
|
const strokeOnly = span.renderingMode === 1 || span.renderingMode === 5;
|
|
951
|
-
const fillOpacity =
|
|
952
|
-
const strokeOpacity =
|
|
1350
|
+
const fillOpacity = isUnitInterval2(span.fillOpacity) ? `fill-opacity:${number3(span.fillOpacity)}` : "";
|
|
1351
|
+
const strokeOpacity = isUnitInterval2(span.strokeOpacity) ? `stroke-opacity:${number3(span.strokeOpacity)}` : "";
|
|
953
1352
|
const style = [
|
|
954
1353
|
isHebrewPaintOrder(span) ? "unicode-bidi:bidi-override;direction:ltr" : "",
|
|
955
1354
|
span.direction === "ttb" ? "writing-mode:vertical-rl" : "",
|
|
956
|
-
strokeOnly ? "fill:none" :
|
|
1355
|
+
strokeOnly ? "fill:none" : isCssHexColor2(span.color) ? `fill:${span.color}` : "",
|
|
957
1356
|
stroke,
|
|
958
1357
|
strokeWidth,
|
|
959
1358
|
fillOpacity,
|
|
@@ -961,7 +1360,7 @@ function visualText(span, pageHeight, fontAliases, counterRotateReflectedText =
|
|
|
961
1360
|
font
|
|
962
1361
|
].filter(Boolean).join(";");
|
|
963
1362
|
const textExtent = span.direction === "ttb" ? span.bounds.height : span.bounds.width;
|
|
964
|
-
const textLength = textExtent > 0 && !isHebrewPaintOrder(span) ? ` textLength="${
|
|
1363
|
+
const textLength = textExtent > 0 && !isHebrewPaintOrder(span) ? ` textLength="${number3(textExtent)}" lengthAdjust="${span.direction === "ttb" || usesSpacingAdjustment(span) ? "spacing" : "spacingAndGlyphs"}"` : "";
|
|
965
1364
|
const transform = counterRotateReflectedText && span.transform ? [
|
|
966
1365
|
span.transform[0],
|
|
967
1366
|
span.transform[1],
|
|
@@ -974,8 +1373,8 @@ function visualText(span, pageHeight, fontAliases, counterRotateReflectedText =
|
|
|
974
1373
|
const basisY = transform?.[1] ?? 0;
|
|
975
1374
|
const anchorX = span.bounds.x + basisX * rtlOffset;
|
|
976
1375
|
const anchorY = pageHeight - span.bounds.y + basisY * rtlOffset;
|
|
977
|
-
const position = transformed ? ` x="0" y="0" transform="matrix(${transform?.map(
|
|
978
|
-
return `<text${direction}${position} font-size="${
|
|
1376
|
+
const position = transformed ? ` x="0" y="0" transform="matrix(${transform?.map(number3).join(" ")} ${number3(anchorX)} ${number3(anchorY)})"` : ` x="${number3(anchorX)}" y="${number3(anchorY)}"`;
|
|
1377
|
+
return `<text${direction}${position} font-size="${number3(span.fontSize)}"${textLength}${style ? ` style="${style}"` : ""}>${escapeHtml4(span.text)}</text>`;
|
|
979
1378
|
}
|
|
980
1379
|
function isAdobeCjkFont(fontFamily) {
|
|
981
1380
|
return /^Adobe(?:Heiti|Song|Kaiti|Ming|Gothic|Mincho)Std-/i.test(fontFamily ?? "");
|
|
@@ -987,16 +1386,16 @@ function visualType3Text(span, font, pageHeight) {
|
|
|
987
1386
|
const totalAdvance = sequence.reduce((total, glyph) => total + (glyph?.advance ?? 0), 0);
|
|
988
1387
|
if (totalAdvance <= 0 || span.bounds.width <= 0 || span.fontSize <= 0) return "";
|
|
989
1388
|
const transform = span.transform ?? [1, 0, 0, 1];
|
|
990
|
-
const outer = `matrix(${transform.map(
|
|
1389
|
+
const outer = `matrix(${transform.map(number3).join(" ")} ${number3(span.bounds.x)} ${number3(pageHeight - span.bounds.y)})`;
|
|
991
1390
|
const xScale = span.bounds.width / totalAdvance;
|
|
992
1391
|
let offset = 0;
|
|
993
1392
|
let content = "";
|
|
994
1393
|
for (const glyph of sequence) {
|
|
995
1394
|
if (!glyph) continue;
|
|
996
|
-
content += `<g transform="translate(${
|
|
1395
|
+
content += `<g transform="translate(${number3(offset)} 0)">${type3Glyph(glyph, span.color)}</g>`;
|
|
997
1396
|
offset += glyph.advance;
|
|
998
1397
|
}
|
|
999
|
-
return `<g transform="${outer}"><g transform="scale(${
|
|
1398
|
+
return `<g transform="${outer}"><g transform="scale(${number3(xScale)} ${number3(-span.fontSize)})">${content}</g></g>`;
|
|
1000
1399
|
}
|
|
1001
1400
|
function isHebrewPaintOrder(span) {
|
|
1002
1401
|
return span.direction === "ltr" && /[\u0590-\u05ff]/u.test(span.text);
|
|
@@ -1007,30 +1406,27 @@ function usesSpacingAdjustment(span) {
|
|
|
1007
1406
|
function type3Glyph(glyph, textColor) {
|
|
1008
1407
|
let output = "";
|
|
1009
1408
|
for (const fill of glyph.fills ?? []) {
|
|
1010
|
-
const color = glyph.usesTextColor &&
|
|
1011
|
-
if (!
|
|
1012
|
-
const points = fill.points.map(([x, y]) => `${
|
|
1013
|
-
const opacity =
|
|
1409
|
+
const color = glyph.usesTextColor && isCssHexColor2(textColor) ? textColor : fill.color;
|
|
1410
|
+
if (!isCssHexColor2(color)) continue;
|
|
1411
|
+
const points = fill.points.map(([x, y]) => `${number3(x)},${number3(y)}`).join(" ");
|
|
1412
|
+
const opacity = isUnitInterval2(fill.opacity) ? ` fill-opacity="${number3(fill.opacity)}"` : "";
|
|
1014
1413
|
output += `<polygon points="${points}" fill="${color}"${opacity}/>`;
|
|
1015
1414
|
}
|
|
1016
1415
|
for (const path of glyph.paths ?? []) {
|
|
1017
1416
|
if (!isSvgPath(path.d)) continue;
|
|
1018
|
-
const fill = glyph.usesTextColor &&
|
|
1019
|
-
const stroke = glyph.usesTextColor &&
|
|
1020
|
-
const width = path.strokeWidth !== void 0 && Number.isFinite(path.strokeWidth) && path.strokeWidth >= 0 ? ` stroke-width="${
|
|
1417
|
+
const fill = glyph.usesTextColor && isCssHexColor2(textColor) ? textColor : isCssHexColor2(path.fill) ? path.fill : "none";
|
|
1418
|
+
const stroke = glyph.usesTextColor && isCssHexColor2(textColor) && path.stroke ? textColor : isCssHexColor2(path.stroke) ? path.stroke : "none";
|
|
1419
|
+
const width = path.strokeWidth !== void 0 && Number.isFinite(path.strokeWidth) && path.strokeWidth >= 0 ? ` stroke-width="${number3(path.strokeWidth)}"` : "";
|
|
1021
1420
|
output += `<path d="${path.d}" fill="${fill}" stroke="${stroke}"${width}/>`;
|
|
1022
1421
|
}
|
|
1023
1422
|
return (glyph.fills?.length ?? 0) > 64 && glyph.advance > 2 ? `<g shape-rendering="crispEdges">${output}</g>` : output;
|
|
1024
1423
|
}
|
|
1025
|
-
function
|
|
1424
|
+
function isCssHexColor2(value) {
|
|
1026
1425
|
return /^#[\da-f]{6}$/i.test(value ?? "");
|
|
1027
1426
|
}
|
|
1028
|
-
function
|
|
1427
|
+
function isUnitInterval2(value) {
|
|
1029
1428
|
return Number.isFinite(value) && (value ?? -1) >= 0 && (value ?? 2) <= 1;
|
|
1030
1429
|
}
|
|
1031
|
-
function isSvgPath(value) {
|
|
1032
|
-
return value.length <= 1e6 && /^[\d\s.,+\-eEMmLlCcZz]+$/.test(value);
|
|
1033
|
-
}
|
|
1034
1430
|
function isMonospace(fontFamily) {
|
|
1035
1431
|
return /courier|mono/i.test(fontFamily ?? "");
|
|
1036
1432
|
}
|
|
@@ -1048,7 +1444,7 @@ function directionAttribute(spans) {
|
|
|
1048
1444
|
if (vertical > rtl && vertical * 2 >= spans.length) return ' data-direction="ttb"';
|
|
1049
1445
|
return rtl * 2 >= spans.length && spans.length > 0 ? ' dir="rtl"' : "";
|
|
1050
1446
|
}
|
|
1051
|
-
function
|
|
1447
|
+
function number3(value) {
|
|
1052
1448
|
return Number.isFinite(value) ? String(Math.round(value * 1e3) / 1e3) : "0";
|
|
1053
1449
|
}
|
|
1054
1450
|
function escapeAttribute(value) {
|