@boxpdf/html-writer 0.1.2 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # `@boxpdf/html-writer`
2
2
 
3
- Streams text-oriented HTML from pages produced by `@boxpdf/reader`.
3
+ Streams visual or semantic HTML from pages produced by `@boxpdf/reader`.
4
4
 
5
5
  ```ts
6
6
  import { open } from "node:fs/promises";
@@ -15,7 +15,7 @@ const output = await open("output.html", "w");
15
15
  try {
16
16
  await writeHtmlDocument(pdf.pages(), async (chunk) => {
17
17
  await output.write(chunk);
18
- });
18
+ }, { profile: "visual" });
19
19
  } finally {
20
20
  await output.close();
21
21
  pdf.close();
@@ -23,9 +23,16 @@ try {
23
23
  }
24
24
  ```
25
25
 
26
- The default `positioned` layout preserves text coordinates. The optional
27
- `flow` layout uses the reader's inferred lines and tables. The current output
28
- surface excludes images, vector graphics, and exact font reproduction.
26
+ The default `visual` profile preserves page dimensions and text coordinates for
27
+ display presentation. The `semantic` profile uses inferred reading order,
28
+ lines, nesting, and tables to produce reflowable HTML. The visual model comes
29
+ first: semantic structure is derived from the complete page evidence rather
30
+ than inferred after presentation information has been discarded.
31
+
32
+ The legacy `layout: "positioned" | "flow"` option remains as an alias for
33
+ `profile: "visual" | "semantic"`. The current visual output surface excludes
34
+ images, vector graphics, and exact font reproduction; the PDFium parity report
35
+ tracks progress toward complete display presentation.
29
36
 
30
37
  The callback is awaited for every chunk, so a file stream, HTTP response, or
31
38
  Web `WritableStream` can apply backpressure. The caller owns and closes the PDF
@@ -41,7 +48,7 @@ Poppler's `pdftohtml -c -hidden -noframes -zoom 1` output. Poppler serves as an
41
48
  independent test oracle. The writer's memory contract covers the reader and
42
49
  HTML serialization.
43
50
 
44
- `pnpm poppler:report` runs the positioned writer over all 62 text fixtures in
51
+ `pnpm poppler:report` runs the visual writer over all 62 text fixtures in
45
52
  the pinned PDF.js corpus. The checked-in baseline currently records exact text
46
53
  and geometry agreement on 51 fixtures. The remaining cases are retained in the
47
54
  denominator; most exercise intentional PDF.js/Poppler differences in RTL text,
package/dist/index.cjs CHANGED
@@ -26,7 +26,7 @@ __export(index_exports, {
26
26
  });
27
27
  module.exports = __toCommonJS(index_exports);
28
28
  var import_structure = require("@boxpdf/reader/structure");
29
- var styles = `.pdf-document{margin:0 auto}.pdf-page{box-sizing:border-box;margin:1rem auto;background:#fff;color:#000}.pdf-page--positioned{position:relative;overflow:hidden}.pdf-page-content{position:absolute;transform-origin:0 0}.pdf-page-content--90{transform:translateX(100%) rotate(90deg)}.pdf-page-content--180{transform:translate(100%,100%) rotate(180deg)}.pdf-page-content--270{transform:translateY(100%) rotate(270deg)}.pdf-span{position:absolute;white-space:pre;transform-origin:left bottom;unicode-bidi:isolate}.pdf-span[data-direction=ttb]{writing-mode:vertical-rl}.pdf-page--flow{max-width:60rem;padding:1rem}.pdf-page--flow p{white-space:pre-wrap;unicode-bidi:plaintext}.pdf-page table{border-collapse:collapse}.pdf-page td{padding:.15rem .4rem;vertical-align:top}`;
29
+ var styles = `.pdf-document{margin:0 auto}.pdf-page{box-sizing:border-box;margin:1rem auto;background:#fff;color:#000}.pdf-page--visual,.pdf-page--positioned{position:relative;overflow:hidden}.pdf-page-content{position:absolute;transform-origin:0 0}.pdf-span{position:absolute;white-space:pre;transform-origin:left bottom;unicode-bidi:isolate}.pdf-span[data-direction=ttb]{writing-mode:vertical-rl}.pdf-page--semantic,.pdf-page--flow{max-width:60rem;padding:1rem}.pdf-page--semantic p,.pdf-page--flow p{white-space:pre-wrap;unicode-bidi:plaintext}.pdf-page table{border-collapse:collapse}.pdf-page td{padding:.15rem .4rem;vertical-align:top}`;
30
30
  async function writeHtmlDocument(pages, write, options = {}) {
31
31
  const includeDocument = options.includeDocument ?? true;
32
32
  if (includeDocument) {
@@ -45,8 +45,8 @@ async function writeHtmlDocument(pages, write, options = {}) {
45
45
  if (includeDocument) await write("</body></html>");
46
46
  }
47
47
  async function writePage(page, write, options = {}) {
48
- if ((options.layout ?? "positioned") === "flow") await writeFlowPage(page, write);
49
- else await writePositionedPage(page, write);
48
+ if (resolveProfile(options) === "semantic") await writeFlowPage(page, write);
49
+ else await writePositionedPage(page, write, options);
50
50
  }
51
51
  async function pageToHtml(page, options = {}) {
52
52
  let output = "";
@@ -59,24 +59,151 @@ async function pageToHtml(page, options = {}) {
59
59
  );
60
60
  return output;
61
61
  }
62
- async function writePositionedPage(page, write) {
62
+ async function writePositionedPage(page, write, options) {
63
+ const visualSpans = page.visualSpans ?? page.spans;
64
+ const reflectedOverlay = usesReflectedVisualOverlay(page, visualSpans);
63
65
  const quarterTurn = page.rotate === 90 || page.rotate === 270;
64
66
  const displayWidth = quarterTurn ? page.height : page.width;
65
67
  const displayHeight = quarterTurn ? page.width : page.height;
66
68
  await write(
67
- `<section class="pdf-page pdf-page--positioned" data-page="${page.number}" data-rotate="${page.rotate}" style="width:${number(displayWidth)}pt;height:${number(displayHeight)}pt">`
69
+ `<section class="pdf-page pdf-page--visual pdf-page--positioned" data-page="${page.number}" data-rotate="${page.rotate}" style="width:${number(displayWidth)}pt;height:${number(displayHeight)}pt">`
70
+ );
71
+ const fontAliases = new Map(
72
+ (page.fonts ?? []).filter((font) => font.format === "truetype" && !/(?:courier|^TTE)/i.test(font.family ?? "")).map((font) => [font.id, `boxpdf-${page.number}-${font.id}`])
73
+ );
74
+ const type3Fonts = new Map(
75
+ (page.fonts ?? []).filter((font) => font.format === "type3").map((font) => [font.id, font])
76
+ );
77
+ if ((options.includeStyles ?? true) && page.fonts?.length) {
78
+ await write(`<style>${page.fonts.map((font) => fontFace(font, fontAliases)).join("")}</style>`);
79
+ }
80
+ await write(
81
+ `<div class="pdf-page-content pdf-page-content--${page.rotate}" style="width:${number(page.width)}pt;height:${number(page.height)}pt${rotationTransform(page)}">`
68
82
  );
69
83
  await write(
70
- `<div class="pdf-page-content pdf-page-content--${page.rotate}" style="width:${number(page.width)}pt;height:${number(page.height)}pt">`
84
+ `<svg class="pdf-visual-text" xmlns="http://www.w3.org/2000/svg" width="${number(page.width)}pt" height="${number(page.height)}pt" viewBox="0 0 ${number(page.width)} ${number(page.height)}">`
71
85
  );
72
- for (const span of page.spans) await write(positionedSpan(span));
86
+ if (reflectedOverlay) {
87
+ for (const image of page.images ?? []) await write(visualImage(image, page.height));
88
+ }
89
+ for (const fill of page.fills ?? []) {
90
+ const points = fill.points.map(([x, y]) => `${number(x)},${number(page.height - y)}`).join(" ");
91
+ if (isCssHexColor(fill.color)) {
92
+ const opacity = isUnitInterval(fill.opacity) ? ` fill-opacity="${number(fill.opacity)}"` : "";
93
+ await write(`<polygon points="${points}" fill="${fill.color}"${opacity}/>`);
94
+ }
95
+ }
96
+ if (page.paths?.length) {
97
+ await write(`<g transform="translate(0 ${number(page.height)}) scale(1 -1)">`);
98
+ for (const path of page.paths) {
99
+ if (!isSvgPath(path.d)) continue;
100
+ const fill = isCssHexColor(path.fill) ? path.fill : "none";
101
+ const stroke = isCssHexColor(path.stroke) ? path.stroke : "none";
102
+ const strokeWidth = path.strokeWidth !== void 0 && Number.isFinite(path.strokeWidth) && path.strokeWidth >= 0 ? ` stroke-width="${number(path.strokeWidth)}"` : "";
103
+ const fillRule = path.fillRule ? ` fill-rule="${path.fillRule}"` : "";
104
+ const fillOpacity = isUnitInterval(path.fillOpacity) ? ` fill-opacity="${number(path.fillOpacity)}"` : "";
105
+ const strokeOpacity = isUnitInterval(path.strokeOpacity) ? ` stroke-opacity="${number(path.strokeOpacity)}"` : "";
106
+ const dasharray = path.strokeDasharray?.every((value) => Number.isFinite(value) && value >= 0) ? ` stroke-dasharray="${path.strokeDasharray.map(number).join(" ")}"` : "";
107
+ const dashoffset = Number.isFinite(path.strokeDashoffset) ? ` stroke-dashoffset="${number(path.strokeDashoffset ?? 0)}"` : "";
108
+ const linecap = path.strokeLinecap ? ` stroke-linecap="${path.strokeLinecap}"` : "";
109
+ const linejoin = path.strokeLinejoin ? ` stroke-linejoin="${path.strokeLinejoin}"` : "";
110
+ await write(
111
+ `<path d="${path.d}" fill="${fill}" stroke="${stroke}"${strokeWidth}${fillOpacity}${strokeOpacity}${dasharray}${dashoffset}${linecap}${linejoin}${fillRule}/>`
112
+ );
113
+ }
114
+ await write("</g>");
115
+ }
116
+ if (!reflectedOverlay) {
117
+ for (const image of page.images ?? []) await write(visualImage(image, page.height));
118
+ }
119
+ for (const span of visualSpans) {
120
+ if (!usesPositionedSpan(span)) {
121
+ const type3 = span.fontAssetId ? type3Fonts.get(span.fontAssetId) : void 0;
122
+ await write(
123
+ type3 ? visualType3Text(span, type3, page.height) : visualText(span, page.height, fontAliases, reflectedOverlay && page.rotate === 180)
124
+ );
125
+ }
126
+ }
127
+ await write("</svg>");
128
+ for (const span of visualSpans) {
129
+ if (usesPositionedSpan(span)) await write(positionedSpan(span, fontAliases));
130
+ }
73
131
  await write("</div></section>");
74
132
  }
133
+ function usesReflectedVisualOverlay(page, spans) {
134
+ return Boolean(page.images?.length) && Boolean(page.paths?.length || page.fills?.length) && spans.length > 0 && spans.every(
135
+ (span) => span.transform !== void 0 && Math.abs(span.transform[0] + 1) < 1e-6 && Math.abs(span.transform[1]) < 1e-6 && Math.abs(span.transform[2]) < 1e-6 && Math.abs(span.transform[3] - 1) < 1e-6
136
+ );
137
+ }
138
+ function visualImage(image, pageHeight) {
139
+ const [a, b, c, d, e, f] = image.transform;
140
+ const transform = [a, -b, -c, d, c + e, pageHeight - d - f].map(number).join(" ");
141
+ const opacity = isUnitInterval(image.opacity) ? ` opacity="${number(image.opacity)}"` : "";
142
+ const mime = image.format === "jpeg" ? "image/jpeg" : "image/bmp";
143
+ const data = image.format === "jpeg" ? image.data : rgbBmp(image);
144
+ return `<image width="1" height="1" preserveAspectRatio="none" transform="matrix(${transform})" href="data:${mime};base64,${base64(data)}"${opacity}/>`;
145
+ }
146
+ function rgbBmp(image) {
147
+ const stride = Math.ceil(image.width * 3 / 4) * 4;
148
+ const output = new Uint8Array(54 + stride * image.height);
149
+ const view = new DataView(output.buffer);
150
+ output[0] = 66;
151
+ output[1] = 77;
152
+ view.setUint32(2, output.length, true);
153
+ view.setUint32(10, 54, true);
154
+ view.setUint32(14, 40, true);
155
+ view.setInt32(18, image.width, true);
156
+ view.setInt32(22, -image.height, true);
157
+ view.setUint16(26, 1, true);
158
+ view.setUint16(28, 24, true);
159
+ view.setUint32(34, stride * image.height, true);
160
+ for (let row = 0; row < image.height; row += 1) {
161
+ for (let column = 0; column < image.width; column += 1) {
162
+ const source = (row * image.width + column) * 3;
163
+ const target = 54 + row * stride + column * 3;
164
+ output[target] = image.data[source + 2] ?? 0;
165
+ output[target + 1] = image.data[source + 1] ?? 0;
166
+ output[target + 2] = image.data[source] ?? 0;
167
+ }
168
+ }
169
+ return output;
170
+ }
171
+ function rotationTransform(page) {
172
+ switch (page.rotate) {
173
+ case 90:
174
+ return `;transform:translate(${number(page.height)}pt,0) rotate(90deg)`;
175
+ case 180:
176
+ return `;transform:translate(${number(page.width)}pt,${number(page.height)}pt) rotate(180deg)`;
177
+ case 270:
178
+ return `;transform:translate(0,${number(page.width)}pt) rotate(270deg)`;
179
+ default:
180
+ return "";
181
+ }
182
+ }
183
+ function positionedSpan(span, fontAliases) {
184
+ const direction = directionAttribute([span]);
185
+ const style = [
186
+ `left:${number(span.bounds.x)}pt`,
187
+ `bottom:${number(span.bounds.y)}pt`,
188
+ `width:${number(span.bounds.width)}pt`,
189
+ `height:${number(span.bounds.height)}pt`,
190
+ `font-size:${number(span.fontSize)}pt`,
191
+ ...isCssHexColor(span.color) ? [`color:${span.color}`] : [],
192
+ ...isUnitInterval(span.fillOpacity) ? [`opacity:${number(span.fillOpacity)}`] : [],
193
+ ...fontStyles(
194
+ span.fontFamily,
195
+ span.fontAssetId ? fontAliases.get(span.fontAssetId) : void 0
196
+ )
197
+ ].join(";");
198
+ return `<span class="pdf-span"${direction} style="${style}">${escapeHtml(span.text)}</span>`;
199
+ }
75
200
  async function writeFlowPage(page, write) {
76
201
  const structured = (0, import_structure.structurePage)(page);
77
202
  const tables = [...structured.tables].sort((left, right) => right.bounds.y - left.bounds.y);
78
203
  const emittedTables = /* @__PURE__ */ new Set();
79
- await write(`<section class="pdf-page pdf-page--flow" data-page="${page.number}">`);
204
+ await write(
205
+ `<section class="pdf-page pdf-page--semantic pdf-page--flow" data-page="${page.number}">`
206
+ );
80
207
  for (const line of structured.lines) {
81
208
  const table = tables.find((candidate) => containsY(candidate, line.bounds.y));
82
209
  if (table) {
@@ -93,16 +220,150 @@ async function writeFlowPage(page, write) {
93
220
  }
94
221
  await write("</section>");
95
222
  }
96
- function positionedSpan(span) {
223
+ function visualText(span, pageHeight, fontAliases, counterRotateReflectedText = false) {
224
+ if (span.renderingMode === 3 || span.renderingMode === 7) return "";
225
+ if (!span.fontAssetId && isAdobeCjkFont(span.fontFamily)) return "";
97
226
  const direction = directionAttribute([span]);
227
+ const font = fontStyles(
228
+ span.fontFamily,
229
+ span.fontAssetId ? fontAliases.get(span.fontAssetId) : void 0
230
+ ).join(";");
231
+ const stroke = isCssHexColor(span.strokeColor) ? `stroke:${span.strokeColor}` : "";
232
+ const strokeWidth = stroke && Number.isFinite(span.strokeWidth) && (span.strokeWidth ?? -1) >= 0 ? `stroke-width:${number(span.strokeWidth ?? 0)}` : "";
233
+ const strokeOnly = span.renderingMode === 1 || span.renderingMode === 5;
234
+ const fillOpacity = isUnitInterval(span.fillOpacity) ? `fill-opacity:${number(span.fillOpacity)}` : "";
235
+ const strokeOpacity = isUnitInterval(span.strokeOpacity) ? `stroke-opacity:${number(span.strokeOpacity)}` : "";
98
236
  const style = [
99
- `left:${number(span.bounds.x)}pt`,
100
- `bottom:${number(span.bounds.y)}pt`,
101
- `width:${number(span.bounds.width)}pt`,
102
- `height:${number(span.bounds.height)}pt`,
103
- `font-size:${number(span.fontSize)}pt`
104
- ].join(";");
105
- return `<span class="pdf-span"${direction} style="${style}">${escapeHtml(span.text)}</span>`;
237
+ isHebrewPaintOrder(span) ? "unicode-bidi:bidi-override;direction:ltr" : "",
238
+ span.direction === "ttb" ? "writing-mode:vertical-rl" : "",
239
+ strokeOnly ? "fill:none" : isCssHexColor(span.color) ? `fill:${span.color}` : "",
240
+ stroke,
241
+ strokeWidth,
242
+ fillOpacity,
243
+ strokeOpacity,
244
+ font
245
+ ].filter(Boolean).join(";");
246
+ const textExtent = span.direction === "ttb" ? span.bounds.height : span.bounds.width;
247
+ const textLength = textExtent > 0 && !isHebrewPaintOrder(span) ? ` textLength="${number(textExtent)}" lengthAdjust="${span.direction === "ttb" || usesSpacingAdjustment(span) ? "spacing" : "spacingAndGlyphs"}"` : "";
248
+ const transform = counterRotateReflectedText && span.transform ? [
249
+ span.transform[0],
250
+ span.transform[1],
251
+ span.transform[2],
252
+ -span.transform[3]
253
+ ] : span.transform;
254
+ const transformed = hasNonIdentityTransform(transform);
255
+ const rtlOffset = span.direction === "rtl" ? span.bounds.width : 0;
256
+ const basisX = transform?.[0] ?? 1;
257
+ const basisY = transform?.[1] ?? 0;
258
+ const anchorX = span.bounds.x + basisX * rtlOffset;
259
+ const anchorY = pageHeight - span.bounds.y + basisY * rtlOffset;
260
+ const position = transformed ? ` x="0" y="0" transform="matrix(${transform?.map(number).join(" ")} ${number(anchorX)} ${number(anchorY)})"` : ` x="${number(anchorX)}" y="${number(anchorY)}"`;
261
+ return `<text${direction}${position} font-size="${number(span.fontSize)}"${textLength}${style ? ` style="${style}"` : ""}>${escapeHtml(span.text)}</text>`;
262
+ }
263
+ function isAdobeCjkFont(fontFamily) {
264
+ return /^Adobe(?:Heiti|Song|Kaiti|Ming|Gothic|Mincho)Std-/i.test(fontFamily ?? "");
265
+ }
266
+ function visualType3Text(span, font, pageHeight) {
267
+ if (span.renderingMode === 3 || span.renderingMode === 7) return "";
268
+ const glyphs = new Map(font.glyphs.map((glyph) => [glyph.code, glyph]));
269
+ const sequence = (span.glyphCodes ?? []).map((code) => glyphs.get(code));
270
+ const totalAdvance = sequence.reduce((total, glyph) => total + (glyph?.advance ?? 0), 0);
271
+ if (totalAdvance <= 0 || span.bounds.width <= 0 || span.fontSize <= 0) return "";
272
+ const transform = span.transform ?? [1, 0, 0, 1];
273
+ const outer = `matrix(${transform.map(number).join(" ")} ${number(span.bounds.x)} ${number(pageHeight - span.bounds.y)})`;
274
+ const xScale = span.bounds.width / totalAdvance;
275
+ let offset = 0;
276
+ let content = "";
277
+ for (const glyph of sequence) {
278
+ if (!glyph) continue;
279
+ content += `<g transform="translate(${number(offset)} 0)">${type3Glyph(glyph)}</g>`;
280
+ offset += glyph.advance;
281
+ }
282
+ return `<g transform="${outer}"><g transform="scale(${number(xScale)} ${number(-span.fontSize)})">${content}</g></g>`;
283
+ }
284
+ function isHebrewPaintOrder(span) {
285
+ return span.direction === "ltr" && /[\u0590-\u05ff]/u.test(span.text);
286
+ }
287
+ function usesSpacingAdjustment(span) {
288
+ return !span.fontAssetId && /arial/i.test(span.fontFamily ?? "");
289
+ }
290
+ function type3Glyph(glyph) {
291
+ let output = "";
292
+ for (const fill of glyph.fills ?? []) {
293
+ if (!isCssHexColor(fill.color)) continue;
294
+ const points = fill.points.map(([x, y]) => `${number(x)},${number(y)}`).join(" ");
295
+ const opacity = isUnitInterval(fill.opacity) ? ` fill-opacity="${number(fill.opacity)}"` : "";
296
+ output += `<polygon points="${points}" fill="${fill.color}"${opacity}/>`;
297
+ }
298
+ for (const path of glyph.paths ?? []) {
299
+ if (!isSvgPath(path.d)) continue;
300
+ const fill = isCssHexColor(path.fill) ? path.fill : "none";
301
+ const stroke = isCssHexColor(path.stroke) ? path.stroke : "none";
302
+ const width = path.strokeWidth !== void 0 && Number.isFinite(path.strokeWidth) && path.strokeWidth >= 0 ? ` stroke-width="${number(path.strokeWidth)}"` : "";
303
+ output += `<path d="${path.d}" fill="${fill}" stroke="${stroke}"${width}/>`;
304
+ }
305
+ return (glyph.fills?.length ?? 0) > 64 && glyph.advance > 2 ? `<g shape-rendering="crispEdges">${output}</g>` : output;
306
+ }
307
+ function isCssHexColor(value) {
308
+ return /^#[\da-f]{6}$/i.test(value ?? "");
309
+ }
310
+ function isUnitInterval(value) {
311
+ return Number.isFinite(value) && (value ?? -1) >= 0 && (value ?? 2) <= 1;
312
+ }
313
+ function isSvgPath(value) {
314
+ return value.length <= 1e6 && /^[\d\s.,+\-eEMmLlCcZz]+$/.test(value);
315
+ }
316
+ function fontStyles(fontFamily, alias) {
317
+ const normalized = fontFamily?.toLowerCase() ?? "";
318
+ const styles2 = [];
319
+ let fallback;
320
+ if (/courier|mono|nimbusmono|^cmtt/.test(normalized)) {
321
+ fallback = "Courier New,Courier,monospace";
322
+ } else if (/times|minion|serif|baskerville|georgia|nimbusrom|guardian.*egyp|^cm[rs]y?\d/.test(normalized)) {
323
+ fallback = "Times New Roman,Times,serif";
324
+ } else if (/helvetica|arial|sans|nimbussan|calibre|myriad|panton|^tte/.test(normalized)) {
325
+ fallback = "Arial,Helvetica,sans-serif";
326
+ } else if (/^mstt/.test(normalized)) {
327
+ fallback = "Arial,Helvetica,sans-serif";
328
+ }
329
+ if (alias || fallback) styles2.push(`font-family:${[alias, fallback].filter(Boolean).join(",")}`);
330
+ if (/bold|black|semibold|demi|medi|^tte/.test(normalized)) styles2.push("font-weight:700");
331
+ if (/italic|oblique|slant|ital(?:$|[_-])/.test(normalized)) styles2.push("font-style:italic");
332
+ return styles2;
333
+ }
334
+ function isMonospace(fontFamily) {
335
+ return /courier|mono/i.test(fontFamily ?? "");
336
+ }
337
+ function usesPositionedSpan(span) {
338
+ return !span.glyphCodes && isMonospace(span.fontFamily) && !hasNonIdentityTransform(span.transform);
339
+ }
340
+ function hasNonIdentityTransform(transform) {
341
+ if (!transform) return false;
342
+ const identity = [1, 0, 0, 1];
343
+ return transform.some((value, index) => Math.abs(value - (identity[index] ?? 0)) > 1e-6);
344
+ }
345
+ function fontFace(font, aliases) {
346
+ if (font.format !== "truetype") return "";
347
+ const alias = aliases.get(font.id);
348
+ if (!alias) return "";
349
+ const styles2 = fontStyles(font.family, alias).filter(
350
+ (style) => !style.startsWith("font-family:")
351
+ );
352
+ return `@font-face{font-family:${alias};src:url(data:font/ttf;base64,${base64(font.data)}) format("truetype");${styles2.join(";")}}`;
353
+ }
354
+ function base64(bytes) {
355
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
356
+ let output = "";
357
+ for (let index = 0; index < bytes.length; index += 3) {
358
+ const first = bytes[index] ?? 0;
359
+ const second = bytes[index + 1] ?? 0;
360
+ const third = bytes[index + 2] ?? 0;
361
+ output += alphabet[first >> 2];
362
+ output += alphabet[(first & 3) << 4 | second >> 4];
363
+ output += index + 1 < bytes.length ? alphabet[(second & 15) << 2 | third >> 6] : "=";
364
+ output += index + 2 < bytes.length ? alphabet[third & 63] : "=";
365
+ }
366
+ return output;
106
367
  }
107
368
  function directionAttribute(spans) {
108
369
  const rtl = spans.filter((span) => span.direction === "rtl").length;
@@ -129,6 +390,15 @@ function escapeHtml(value) {
129
390
  function isForbiddenControl(codePoint) {
130
391
  return codePoint <= 8 || codePoint === 11 || codePoint === 12 || codePoint >= 14 && codePoint <= 31 || codePoint === 127;
131
392
  }
393
+ function resolveProfile(options) {
394
+ const legacyProfile = options.layout === "flow" ? "semantic" : "visual";
395
+ if (options.profile && options.layout && options.profile !== legacyProfile) {
396
+ throw new Error(
397
+ `conflicting HTML output options: profile "${options.profile}" does not match layout "${options.layout}"`
398
+ );
399
+ }
400
+ return options.profile ?? legacyProfile;
401
+ }
132
402
  // Annotate the CommonJS export names for ESM import in node:
133
403
  0 && (module.exports = {
134
404
  pageToHtml,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { ExtractedPage, TextSpan } from \"@boxpdf/reader\";\nimport { structurePage, type Table, tableToHtml } from \"@boxpdf/reader/structure\";\n\nexport type HtmlLayout = \"positioned\" | \"flow\";\nexport type HtmlWrite = (chunk: string) => void | Promise<void>;\n\nexport interface HtmlWriterOptions {\n layout?: HtmlLayout;\n title?: string;\n language?: string;\n includeDocument?: boolean;\n includeStyles?: boolean;\n}\n\nconst styles = `.pdf-document{margin:0 auto}.pdf-page{box-sizing:border-box;margin:1rem auto;background:#fff;color:#000}.pdf-page--positioned{position:relative;overflow:hidden}.pdf-page-content{position:absolute;transform-origin:0 0}.pdf-page-content--90{transform:translateX(100%) rotate(90deg)}.pdf-page-content--180{transform:translate(100%,100%) rotate(180deg)}.pdf-page-content--270{transform:translateY(100%) rotate(270deg)}.pdf-span{position:absolute;white-space:pre;transform-origin:left bottom;unicode-bidi:isolate}.pdf-span[data-direction=ttb]{writing-mode:vertical-rl}.pdf-page--flow{max-width:60rem;padding:1rem}.pdf-page--flow p{white-space:pre-wrap;unicode-bidi:plaintext}.pdf-page table{border-collapse:collapse}.pdf-page td{padding:.15rem .4rem;vertical-align:top}`;\n\nexport async function writeHtmlDocument(\n pages: AsyncIterable<ExtractedPage> | Iterable<ExtractedPage>,\n write: HtmlWrite,\n options: HtmlWriterOptions = {},\n): Promise<void> {\n const includeDocument = options.includeDocument ?? true;\n if (includeDocument) {\n await write(\"<!doctype html><html\");\n await write(\n ` lang=\"${escapeAttribute(options.language ?? \"en\")}\"><head><meta charset=\"utf-8\">`,\n );\n await write('<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">');\n await write(`<title>${escapeHtml(options.title ?? \"PDF document\")}</title>`);\n if (options.includeStyles ?? true) await write(`<style>${styles}</style>`);\n await write(\"</head><body>\");\n }\n await write('<main class=\"pdf-document\">');\n for await (const page of pages) await writePage(page, write, options);\n await write(\"</main>\");\n if (includeDocument) await write(\"</body></html>\");\n}\n\nexport async function writePage(\n page: ExtractedPage,\n write: HtmlWrite,\n options: HtmlWriterOptions = {},\n): Promise<void> {\n if ((options.layout ?? \"positioned\") === \"flow\") await writeFlowPage(page, write);\n else await writePositionedPage(page, write);\n}\n\nexport async function pageToHtml(\n page: ExtractedPage,\n options: HtmlWriterOptions = {},\n): Promise<string> {\n let output = \"\";\n await writePage(\n page,\n (chunk) => {\n output += chunk;\n },\n options,\n );\n return output;\n}\n\nasync function writePositionedPage(page: ExtractedPage, write: HtmlWrite): Promise<void> {\n const quarterTurn = page.rotate === 90 || page.rotate === 270;\n const displayWidth = quarterTurn ? page.height : page.width;\n const displayHeight = quarterTurn ? page.width : page.height;\n await write(\n `<section class=\"pdf-page pdf-page--positioned\" data-page=\"${page.number}\" data-rotate=\"${page.rotate}\" style=\"width:${number(displayWidth)}pt;height:${number(displayHeight)}pt\">`,\n );\n await write(\n `<div class=\"pdf-page-content pdf-page-content--${page.rotate}\" style=\"width:${number(page.width)}pt;height:${number(page.height)}pt\">`,\n );\n for (const span of page.spans) await write(positionedSpan(span));\n await write(\"</div></section>\");\n}\n\nasync function writeFlowPage(page: ExtractedPage, write: HtmlWrite): Promise<void> {\n const structured = structurePage(page);\n const tables = [...structured.tables].sort((left, right) => right.bounds.y - left.bounds.y);\n const emittedTables = new Set<Table>();\n await write(`<section class=\"pdf-page pdf-page--flow\" data-page=\"${page.number}\">`);\n for (const line of structured.lines) {\n const table = tables.find((candidate) => containsY(candidate, line.bounds.y));\n if (table) {\n if (!emittedTables.has(table)) {\n await write(tableToHtml(table));\n emittedTables.add(table);\n }\n continue;\n }\n await write(`<p${directionAttribute(line.spans)}>${escapeHtml(line.text)}</p>`);\n }\n for (const table of tables) {\n if (!emittedTables.has(table)) await write(tableToHtml(table));\n }\n await write(\"</section>\");\n}\n\nfunction positionedSpan(span: TextSpan): string {\n const direction = directionAttribute([span]);\n const style = [\n `left:${number(span.bounds.x)}pt`,\n `bottom:${number(span.bounds.y)}pt`,\n `width:${number(span.bounds.width)}pt`,\n `height:${number(span.bounds.height)}pt`,\n `font-size:${number(span.fontSize)}pt`,\n ].join(\";\");\n return `<span class=\"pdf-span\"${direction} style=\"${style}\">${escapeHtml(span.text)}</span>`;\n}\n\nfunction directionAttribute(spans: TextSpan[]): string {\n const rtl = spans.filter((span) => span.direction === \"rtl\").length;\n const vertical = spans.filter((span) => span.direction === \"ttb\").length;\n if (vertical > rtl && vertical * 2 >= spans.length) return ' data-direction=\"ttb\"';\n return rtl * 2 >= spans.length && spans.length > 0 ? ' dir=\"rtl\"' : \"\";\n}\n\nfunction containsY(table: Table, y: number): boolean {\n return y >= table.bounds.y && y <= table.bounds.y + table.bounds.height;\n}\n\nfunction number(value: number): string {\n return Number.isFinite(value) ? String(Math.round(value * 1000) / 1000) : \"0\";\n}\n\nfunction escapeAttribute(value: string): string {\n return escapeHtml(value).replaceAll(\"`\", \"&#96;\");\n}\n\nfunction escapeHtml(value: string): string {\n return [...value]\n .map((character) => {\n const codePoint = character.codePointAt(0) ?? 0;\n if (codePoint === 13) return \"\\n\";\n return isForbiddenControl(codePoint) ? \"�\" : character;\n })\n .join(\"\")\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"'\", \"&#39;\");\n}\n\nfunction isForbiddenControl(codePoint: number): boolean {\n return (\n codePoint <= 8 ||\n codePoint === 11 ||\n codePoint === 12 ||\n (codePoint >= 14 && codePoint <= 31) ||\n codePoint === 127\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n EmbeddedFont,\n EmbeddedType3Font,\n ExtractedPage,\n RasterImage,\n TextSpan,\n Type3Glyph,\n} from \"@boxpdf/reader\";\nimport { structurePage, type Table, tableToHtml } from \"@boxpdf/reader/structure\";\n\nexport type HtmlLayout = \"positioned\" | \"flow\";\nexport type HtmlProfile = \"visual\" | \"semantic\";\nexport type HtmlWrite = (chunk: string) => void | Promise<void>;\n\nexport interface HtmlWriterOptions {\n /** Output intent. Visual preserves page presentation; semantic prioritizes reading order. */\n profile?: HtmlProfile;\n /** @deprecated Use `profile: \"visual\"` or `profile: \"semantic\"`. */\n layout?: HtmlLayout;\n title?: string;\n language?: string;\n includeDocument?: boolean;\n includeStyles?: boolean;\n}\n\nconst styles = `.pdf-document{margin:0 auto}.pdf-page{box-sizing:border-box;margin:1rem auto;background:#fff;color:#000}.pdf-page--visual,.pdf-page--positioned{position:relative;overflow:hidden}.pdf-page-content{position:absolute;transform-origin:0 0}.pdf-span{position:absolute;white-space:pre;transform-origin:left bottom;unicode-bidi:isolate}.pdf-span[data-direction=ttb]{writing-mode:vertical-rl}.pdf-page--semantic,.pdf-page--flow{max-width:60rem;padding:1rem}.pdf-page--semantic p,.pdf-page--flow p{white-space:pre-wrap;unicode-bidi:plaintext}.pdf-page table{border-collapse:collapse}.pdf-page td{padding:.15rem .4rem;vertical-align:top}`;\n\nexport async function writeHtmlDocument(\n pages: AsyncIterable<ExtractedPage> | Iterable<ExtractedPage>,\n write: HtmlWrite,\n options: HtmlWriterOptions = {},\n): Promise<void> {\n const includeDocument = options.includeDocument ?? true;\n if (includeDocument) {\n await write(\"<!doctype html><html\");\n await write(\n ` lang=\"${escapeAttribute(options.language ?? \"en\")}\"><head><meta charset=\"utf-8\">`,\n );\n await write('<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">');\n await write(`<title>${escapeHtml(options.title ?? \"PDF document\")}</title>`);\n if (options.includeStyles ?? true) await write(`<style>${styles}</style>`);\n await write(\"</head><body>\");\n }\n await write('<main class=\"pdf-document\">');\n for await (const page of pages) await writePage(page, write, options);\n await write(\"</main>\");\n if (includeDocument) await write(\"</body></html>\");\n}\n\nexport async function writePage(\n page: ExtractedPage,\n write: HtmlWrite,\n options: HtmlWriterOptions = {},\n): Promise<void> {\n if (resolveProfile(options) === \"semantic\") await writeFlowPage(page, write);\n else await writePositionedPage(page, write, options);\n}\n\nexport async function pageToHtml(\n page: ExtractedPage,\n options: HtmlWriterOptions = {},\n): Promise<string> {\n let output = \"\";\n await writePage(\n page,\n (chunk) => {\n output += chunk;\n },\n options,\n );\n return output;\n}\n\nasync function writePositionedPage(\n page: ExtractedPage,\n write: HtmlWrite,\n options: HtmlWriterOptions,\n): Promise<void> {\n const visualSpans = page.visualSpans ?? page.spans;\n const reflectedOverlay = usesReflectedVisualOverlay(page, visualSpans);\n const quarterTurn = page.rotate === 90 || page.rotate === 270;\n const displayWidth = quarterTurn ? page.height : page.width;\n const displayHeight = quarterTurn ? page.width : page.height;\n await write(\n `<section class=\"pdf-page pdf-page--visual pdf-page--positioned\" data-page=\"${page.number}\" data-rotate=\"${page.rotate}\" style=\"width:${number(displayWidth)}pt;height:${number(displayHeight)}pt\">`,\n );\n const fontAliases = new Map(\n (page.fonts ?? [])\n .filter((font) => font.format === \"truetype\" && !/(?:courier|^TTE)/i.test(font.family ?? \"\"))\n .map((font) => [font.id, `boxpdf-${page.number}-${font.id}`]),\n );\n const type3Fonts = new Map(\n (page.fonts ?? [])\n .filter((font): font is EmbeddedType3Font => font.format === \"type3\")\n .map((font) => [font.id, font]),\n );\n if ((options.includeStyles ?? true) && page.fonts?.length) {\n await write(`<style>${page.fonts.map((font) => fontFace(font, fontAliases)).join(\"\")}</style>`);\n }\n await write(\n `<div class=\"pdf-page-content pdf-page-content--${page.rotate}\" style=\"width:${number(page.width)}pt;height:${number(page.height)}pt${rotationTransform(page)}\">`,\n );\n await write(\n `<svg class=\"pdf-visual-text\" xmlns=\"http://www.w3.org/2000/svg\" width=\"${number(page.width)}pt\" height=\"${number(page.height)}pt\" viewBox=\"0 0 ${number(page.width)} ${number(page.height)}\">`,\n );\n if (reflectedOverlay) {\n for (const image of page.images ?? []) await write(visualImage(image, page.height));\n }\n for (const fill of page.fills ?? []) {\n const points = fill.points.map(([x, y]) => `${number(x)},${number(page.height - y)}`).join(\" \");\n if (isCssHexColor(fill.color)) {\n const opacity = isUnitInterval(fill.opacity) ? ` fill-opacity=\"${number(fill.opacity)}\"` : \"\";\n await write(`<polygon points=\"${points}\" fill=\"${fill.color}\"${opacity}/>`);\n }\n }\n if (page.paths?.length) {\n await write(`<g transform=\"translate(0 ${number(page.height)}) scale(1 -1)\">`);\n for (const path of page.paths) {\n if (!isSvgPath(path.d)) continue;\n const fill = isCssHexColor(path.fill) ? path.fill : \"none\";\n const stroke = isCssHexColor(path.stroke) ? path.stroke : \"none\";\n const strokeWidth =\n path.strokeWidth !== undefined && Number.isFinite(path.strokeWidth) && path.strokeWidth >= 0\n ? ` stroke-width=\"${number(path.strokeWidth)}\"`\n : \"\";\n const fillRule = path.fillRule ? ` fill-rule=\"${path.fillRule}\"` : \"\";\n const fillOpacity = isUnitInterval(path.fillOpacity)\n ? ` fill-opacity=\"${number(path.fillOpacity)}\"`\n : \"\";\n const strokeOpacity = isUnitInterval(path.strokeOpacity)\n ? ` stroke-opacity=\"${number(path.strokeOpacity)}\"`\n : \"\";\n const dasharray = path.strokeDasharray?.every((value) => Number.isFinite(value) && value >= 0)\n ? ` stroke-dasharray=\"${path.strokeDasharray.map(number).join(\" \")}\"`\n : \"\";\n const dashoffset = Number.isFinite(path.strokeDashoffset)\n ? ` stroke-dashoffset=\"${number(path.strokeDashoffset ?? 0)}\"`\n : \"\";\n const linecap = path.strokeLinecap ? ` stroke-linecap=\"${path.strokeLinecap}\"` : \"\";\n const linejoin = path.strokeLinejoin ? ` stroke-linejoin=\"${path.strokeLinejoin}\"` : \"\";\n await write(\n `<path d=\"${path.d}\" fill=\"${fill}\" stroke=\"${stroke}\"${strokeWidth}${fillOpacity}${strokeOpacity}${dasharray}${dashoffset}${linecap}${linejoin}${fillRule}/>`,\n );\n }\n await write(\"</g>\");\n }\n if (!reflectedOverlay) {\n for (const image of page.images ?? []) await write(visualImage(image, page.height));\n }\n for (const span of visualSpans) {\n if (!usesPositionedSpan(span)) {\n const type3 = span.fontAssetId ? type3Fonts.get(span.fontAssetId) : undefined;\n await write(\n type3\n ? visualType3Text(span, type3, page.height)\n : visualText(span, page.height, fontAliases, reflectedOverlay && page.rotate === 180),\n );\n }\n }\n await write(\"</svg>\");\n for (const span of visualSpans) {\n if (usesPositionedSpan(span)) await write(positionedSpan(span, fontAliases));\n }\n await write(\"</div></section>\");\n}\n\nfunction usesReflectedVisualOverlay(page: ExtractedPage, spans: TextSpan[]): boolean {\n return (\n Boolean(page.images?.length) &&\n Boolean(page.paths?.length || page.fills?.length) &&\n spans.length > 0 &&\n spans.every(\n (span) =>\n span.transform !== undefined &&\n Math.abs(span.transform[0] + 1) < 0.000_001 &&\n Math.abs(span.transform[1]) < 0.000_001 &&\n Math.abs(span.transform[2]) < 0.000_001 &&\n Math.abs(span.transform[3] - 1) < 0.000_001,\n )\n );\n}\n\nfunction visualImage(image: RasterImage, pageHeight: number): string {\n const [a, b, c, d, e, f] = image.transform;\n const transform = [a, -b, -c, d, c + e, pageHeight - d - f].map(number).join(\" \");\n const opacity = isUnitInterval(image.opacity) ? ` opacity=\"${number(image.opacity)}\"` : \"\";\n const mime = image.format === \"jpeg\" ? \"image/jpeg\" : \"image/bmp\";\n const data = image.format === \"jpeg\" ? image.data : rgbBmp(image);\n return `<image width=\"1\" height=\"1\" preserveAspectRatio=\"none\" transform=\"matrix(${transform})\" href=\"data:${mime};base64,${base64(data)}\"${opacity}/>`;\n}\n\nfunction rgbBmp(image: RasterImage): Uint8Array {\n const stride = Math.ceil((image.width * 3) / 4) * 4;\n const output = new Uint8Array(54 + stride * image.height);\n const view = new DataView(output.buffer);\n output[0] = 0x42;\n output[1] = 0x4d;\n view.setUint32(2, output.length, true);\n view.setUint32(10, 54, true);\n view.setUint32(14, 40, true);\n view.setInt32(18, image.width, true);\n view.setInt32(22, -image.height, true);\n view.setUint16(26, 1, true);\n view.setUint16(28, 24, true);\n view.setUint32(34, stride * image.height, true);\n for (let row = 0; row < image.height; row += 1) {\n for (let column = 0; column < image.width; column += 1) {\n const source = (row * image.width + column) * 3;\n const target = 54 + row * stride + column * 3;\n output[target] = image.data[source + 2] ?? 0;\n output[target + 1] = image.data[source + 1] ?? 0;\n output[target + 2] = image.data[source] ?? 0;\n }\n }\n return output;\n}\n\nfunction rotationTransform(page: ExtractedPage): string {\n switch (page.rotate) {\n case 90:\n return `;transform:translate(${number(page.height)}pt,0) rotate(90deg)`;\n case 180:\n return `;transform:translate(${number(page.width)}pt,${number(page.height)}pt) rotate(180deg)`;\n case 270:\n return `;transform:translate(0,${number(page.width)}pt) rotate(270deg)`;\n default:\n return \"\";\n }\n}\n\nfunction positionedSpan(span: TextSpan, fontAliases: Map<string, string>): string {\n const direction = directionAttribute([span]);\n const style = [\n `left:${number(span.bounds.x)}pt`,\n `bottom:${number(span.bounds.y)}pt`,\n `width:${number(span.bounds.width)}pt`,\n `height:${number(span.bounds.height)}pt`,\n `font-size:${number(span.fontSize)}pt`,\n ...(isCssHexColor(span.color) ? [`color:${span.color}`] : []),\n ...(isUnitInterval(span.fillOpacity) ? [`opacity:${number(span.fillOpacity)}`] : []),\n ...fontStyles(\n span.fontFamily,\n span.fontAssetId ? fontAliases.get(span.fontAssetId) : undefined,\n ),\n ].join(\";\");\n return `<span class=\"pdf-span\"${direction} style=\"${style}\">${escapeHtml(span.text)}</span>`;\n}\n\nasync function writeFlowPage(page: ExtractedPage, write: HtmlWrite): Promise<void> {\n const structured = structurePage(page);\n const tables = [...structured.tables].sort((left, right) => right.bounds.y - left.bounds.y);\n const emittedTables = new Set<Table>();\n await write(\n `<section class=\"pdf-page pdf-page--semantic pdf-page--flow\" data-page=\"${page.number}\">`,\n );\n for (const line of structured.lines) {\n const table = tables.find((candidate) => containsY(candidate, line.bounds.y));\n if (table) {\n if (!emittedTables.has(table)) {\n await write(tableToHtml(table));\n emittedTables.add(table);\n }\n continue;\n }\n await write(`<p${directionAttribute(line.spans)}>${escapeHtml(line.text)}</p>`);\n }\n for (const table of tables) {\n if (!emittedTables.has(table)) await write(tableToHtml(table));\n }\n await write(\"</section>\");\n}\n\nfunction visualText(\n span: TextSpan,\n pageHeight: number,\n fontAliases: Map<string, string>,\n counterRotateReflectedText = false,\n): string {\n if (span.renderingMode === 3 || span.renderingMode === 7) return \"\";\n if (!span.fontAssetId && isAdobeCjkFont(span.fontFamily)) return \"\";\n const direction = directionAttribute([span]);\n const font = fontStyles(\n span.fontFamily,\n span.fontAssetId ? fontAliases.get(span.fontAssetId) : undefined,\n ).join(\";\");\n const stroke = isCssHexColor(span.strokeColor) ? `stroke:${span.strokeColor}` : \"\";\n const strokeWidth =\n stroke && Number.isFinite(span.strokeWidth) && (span.strokeWidth ?? -1) >= 0\n ? `stroke-width:${number(span.strokeWidth ?? 0)}`\n : \"\";\n const strokeOnly = span.renderingMode === 1 || span.renderingMode === 5;\n const fillOpacity = isUnitInterval(span.fillOpacity)\n ? `fill-opacity:${number(span.fillOpacity)}`\n : \"\";\n const strokeOpacity = isUnitInterval(span.strokeOpacity)\n ? `stroke-opacity:${number(span.strokeOpacity)}`\n : \"\";\n const style = [\n isHebrewPaintOrder(span) ? \"unicode-bidi:bidi-override;direction:ltr\" : \"\",\n span.direction === \"ttb\" ? \"writing-mode:vertical-rl\" : \"\",\n strokeOnly ? \"fill:none\" : isCssHexColor(span.color) ? `fill:${span.color}` : \"\",\n stroke,\n strokeWidth,\n fillOpacity,\n strokeOpacity,\n font,\n ]\n .filter(Boolean)\n .join(\";\");\n const textExtent = span.direction === \"ttb\" ? span.bounds.height : span.bounds.width;\n const textLength =\n textExtent > 0 && !isHebrewPaintOrder(span)\n ? ` textLength=\"${number(textExtent)}\" lengthAdjust=\"${span.direction === \"ttb\" || usesSpacingAdjustment(span) ? \"spacing\" : \"spacingAndGlyphs\"}\"`\n : \"\";\n const transform =\n counterRotateReflectedText && span.transform\n ? ([\n span.transform[0],\n span.transform[1],\n span.transform[2],\n -span.transform[3],\n ] as TextSpan[\"transform\"])\n : span.transform;\n const transformed = hasNonIdentityTransform(transform);\n const rtlOffset = span.direction === \"rtl\" ? span.bounds.width : 0;\n const basisX = transform?.[0] ?? 1;\n const basisY = transform?.[1] ?? 0;\n const anchorX = span.bounds.x + basisX * rtlOffset;\n const anchorY = pageHeight - span.bounds.y + basisY * rtlOffset;\n const position = transformed\n ? ` x=\"0\" y=\"0\" transform=\"matrix(${transform?.map(number).join(\" \")} ${number(anchorX)} ${number(anchorY)})\"`\n : ` x=\"${number(anchorX)}\" y=\"${number(anchorY)}\"`;\n return `<text${direction}${position} font-size=\"${number(span.fontSize)}\"${textLength}${style ? ` style=\"${style}\"` : \"\"}>${escapeHtml(span.text)}</text>`;\n}\n\nfunction isAdobeCjkFont(fontFamily: string | undefined): boolean {\n return /^Adobe(?:Heiti|Song|Kaiti|Ming|Gothic|Mincho)Std-/i.test(fontFamily ?? \"\");\n}\n\nfunction visualType3Text(span: TextSpan, font: EmbeddedType3Font, pageHeight: number): string {\n if (span.renderingMode === 3 || span.renderingMode === 7) return \"\";\n const glyphs = new Map(font.glyphs.map((glyph) => [glyph.code, glyph]));\n const sequence = (span.glyphCodes ?? []).map((code) => glyphs.get(code));\n const totalAdvance = sequence.reduce((total, glyph) => total + (glyph?.advance ?? 0), 0);\n if (totalAdvance <= 0 || span.bounds.width <= 0 || span.fontSize <= 0) return \"\";\n const transform = span.transform ?? [1, 0, 0, 1];\n const outer = `matrix(${transform.map(number).join(\" \")} ${number(span.bounds.x)} ${number(pageHeight - span.bounds.y)})`;\n const xScale = span.bounds.width / totalAdvance;\n let offset = 0;\n let content = \"\";\n for (const glyph of sequence) {\n if (!glyph) continue;\n content += `<g transform=\"translate(${number(offset)} 0)\">${type3Glyph(glyph)}</g>`;\n offset += glyph.advance;\n }\n return `<g transform=\"${outer}\"><g transform=\"scale(${number(xScale)} ${number(-span.fontSize)})\">${content}</g></g>`;\n}\n\nfunction isHebrewPaintOrder(span: TextSpan): boolean {\n return span.direction === \"ltr\" && /[\\u0590-\\u05ff]/u.test(span.text);\n}\n\nfunction usesSpacingAdjustment(span: TextSpan): boolean {\n return !span.fontAssetId && /arial/i.test(span.fontFamily ?? \"\");\n}\n\nfunction type3Glyph(glyph: Type3Glyph): string {\n let output = \"\";\n for (const fill of glyph.fills ?? []) {\n if (!isCssHexColor(fill.color)) continue;\n const points = fill.points.map(([x, y]) => `${number(x)},${number(y)}`).join(\" \");\n const opacity = isUnitInterval(fill.opacity) ? ` fill-opacity=\"${number(fill.opacity)}\"` : \"\";\n output += `<polygon points=\"${points}\" fill=\"${fill.color}\"${opacity}/>`;\n }\n for (const path of glyph.paths ?? []) {\n if (!isSvgPath(path.d)) continue;\n const fill = isCssHexColor(path.fill) ? path.fill : \"none\";\n const stroke = isCssHexColor(path.stroke) ? path.stroke : \"none\";\n const width =\n path.strokeWidth !== undefined && Number.isFinite(path.strokeWidth) && path.strokeWidth >= 0\n ? ` stroke-width=\"${number(path.strokeWidth)}\"`\n : \"\";\n output += `<path d=\"${path.d}\" fill=\"${fill}\" stroke=\"${stroke}\"${width}/>`;\n }\n return (glyph.fills?.length ?? 0) > 64 && glyph.advance > 2\n ? `<g shape-rendering=\"crispEdges\">${output}</g>`\n : output;\n}\n\nfunction isCssHexColor(value: string | undefined): value is string {\n return /^#[\\da-f]{6}$/i.test(value ?? \"\");\n}\n\nfunction isUnitInterval(value: number | undefined): value is number {\n return Number.isFinite(value) && (value ?? -1) >= 0 && (value ?? 2) <= 1;\n}\n\nfunction isSvgPath(value: string): boolean {\n return value.length <= 1_000_000 && /^[\\d\\s.,+\\-eEMmLlCcZz]+$/.test(value);\n}\n\nfunction fontStyles(fontFamily: string | undefined, alias?: string): string[] {\n const normalized = fontFamily?.toLowerCase() ?? \"\";\n const styles: string[] = [];\n let fallback: string | undefined;\n if (/courier|mono|nimbusmono|^cmtt/.test(normalized)) {\n fallback = \"Courier New,Courier,monospace\";\n } else if (\n /times|minion|serif|baskerville|georgia|nimbusrom|guardian.*egyp|^cm[rs]y?\\d/.test(normalized)\n ) {\n fallback = \"Times New Roman,Times,serif\";\n } else if (/helvetica|arial|sans|nimbussan|calibre|myriad|panton|^tte/.test(normalized)) {\n fallback = \"Arial,Helvetica,sans-serif\";\n } else if (/^mstt/.test(normalized)) {\n fallback = \"Arial,Helvetica,sans-serif\";\n }\n if (alias || fallback) styles.push(`font-family:${[alias, fallback].filter(Boolean).join(\",\")}`);\n if (/bold|black|semibold|demi|medi|^tte/.test(normalized)) styles.push(\"font-weight:700\");\n if (/italic|oblique|slant|ital(?:$|[_-])/.test(normalized)) styles.push(\"font-style:italic\");\n return styles;\n}\n\nfunction isMonospace(fontFamily: string | undefined): boolean {\n return /courier|mono/i.test(fontFamily ?? \"\");\n}\n\nfunction usesPositionedSpan(span: TextSpan): boolean {\n return (\n !span.glyphCodes && isMonospace(span.fontFamily) && !hasNonIdentityTransform(span.transform)\n );\n}\n\nfunction hasNonIdentityTransform(transform: TextSpan[\"transform\"]): boolean {\n if (!transform) return false;\n const identity: [number, number, number, number] = [1, 0, 0, 1];\n return transform.some((value, index) => Math.abs(value - (identity[index] ?? 0)) > 0.000_001);\n}\n\nfunction fontFace(font: EmbeddedFont, aliases: Map<string, string>): string {\n if (font.format !== \"truetype\") return \"\";\n const alias = aliases.get(font.id);\n if (!alias) return \"\";\n const styles = fontStyles(font.family, alias).filter(\n (style) => !style.startsWith(\"font-family:\"),\n );\n return `@font-face{font-family:${alias};src:url(data:font/ttf;base64,${base64(font.data)}) format(\"truetype\");${styles.join(\";\")}}`;\n}\n\nfunction base64(bytes: Uint8Array): string {\n const alphabet = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n let output = \"\";\n for (let index = 0; index < bytes.length; index += 3) {\n const first = bytes[index] ?? 0;\n const second = bytes[index + 1] ?? 0;\n const third = bytes[index + 2] ?? 0;\n output += alphabet[first >> 2];\n output += alphabet[((first & 3) << 4) | (second >> 4)];\n output += index + 1 < bytes.length ? alphabet[((second & 15) << 2) | (third >> 6)] : \"=\";\n output += index + 2 < bytes.length ? alphabet[third & 63] : \"=\";\n }\n return output;\n}\n\nfunction directionAttribute(spans: TextSpan[]): string {\n const rtl = spans.filter((span) => span.direction === \"rtl\").length;\n const vertical = spans.filter((span) => span.direction === \"ttb\").length;\n if (vertical > rtl && vertical * 2 >= spans.length) return ' data-direction=\"ttb\"';\n return rtl * 2 >= spans.length && spans.length > 0 ? ' dir=\"rtl\"' : \"\";\n}\n\nfunction containsY(table: Table, y: number): boolean {\n return y >= table.bounds.y && y <= table.bounds.y + table.bounds.height;\n}\n\nfunction number(value: number): string {\n return Number.isFinite(value) ? String(Math.round(value * 1000) / 1000) : \"0\";\n}\n\nfunction escapeAttribute(value: string): string {\n return escapeHtml(value).replaceAll(\"`\", \"&#96;\");\n}\n\nfunction escapeHtml(value: string): string {\n return [...value]\n .map((character) => {\n const codePoint = character.codePointAt(0) ?? 0;\n if (codePoint === 13) return \"\\n\";\n return isForbiddenControl(codePoint) ? \"�\" : character;\n })\n .join(\"\")\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"'\", \"&#39;\");\n}\n\nfunction isForbiddenControl(codePoint: number): boolean {\n return (\n codePoint <= 8 ||\n codePoint === 11 ||\n codePoint === 12 ||\n (codePoint >= 14 && codePoint <= 31) ||\n codePoint === 127\n );\n}\n\nfunction resolveProfile(options: HtmlWriterOptions): HtmlProfile {\n const legacyProfile = options.layout === \"flow\" ? \"semantic\" : \"visual\";\n if (options.profile && options.layout && options.profile !== legacyProfile) {\n throw new Error(\n `conflicting HTML output options: profile \"${options.profile}\" does not match layout \"${options.layout}\"`,\n );\n }\n return options.profile ?? legacyProfile;\n}\n"],"mappings":"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","names":["styles"]}
package/dist/index.d.cts CHANGED
@@ -1,8 +1,12 @@
1
1
  import { ExtractedPage } from '@boxpdf/reader';
2
2
 
3
3
  type HtmlLayout = "positioned" | "flow";
4
+ type HtmlProfile = "visual" | "semantic";
4
5
  type HtmlWrite = (chunk: string) => void | Promise<void>;
5
6
  interface HtmlWriterOptions {
7
+ /** Output intent. Visual preserves page presentation; semantic prioritizes reading order. */
8
+ profile?: HtmlProfile;
9
+ /** @deprecated Use `profile: "visual"` or `profile: "semantic"`. */
6
10
  layout?: HtmlLayout;
7
11
  title?: string;
8
12
  language?: string;
@@ -13,4 +17,4 @@ declare function writeHtmlDocument(pages: AsyncIterable<ExtractedPage> | Iterabl
13
17
  declare function writePage(page: ExtractedPage, write: HtmlWrite, options?: HtmlWriterOptions): Promise<void>;
14
18
  declare function pageToHtml(page: ExtractedPage, options?: HtmlWriterOptions): Promise<string>;
15
19
 
16
- export { type HtmlLayout, type HtmlWrite, type HtmlWriterOptions, pageToHtml, writeHtmlDocument, writePage };
20
+ export { type HtmlLayout, type HtmlProfile, type HtmlWrite, type HtmlWriterOptions, pageToHtml, writeHtmlDocument, writePage };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,12 @@
1
1
  import { ExtractedPage } from '@boxpdf/reader';
2
2
 
3
3
  type HtmlLayout = "positioned" | "flow";
4
+ type HtmlProfile = "visual" | "semantic";
4
5
  type HtmlWrite = (chunk: string) => void | Promise<void>;
5
6
  interface HtmlWriterOptions {
7
+ /** Output intent. Visual preserves page presentation; semantic prioritizes reading order. */
8
+ profile?: HtmlProfile;
9
+ /** @deprecated Use `profile: "visual"` or `profile: "semantic"`. */
6
10
  layout?: HtmlLayout;
7
11
  title?: string;
8
12
  language?: string;
@@ -13,4 +17,4 @@ declare function writeHtmlDocument(pages: AsyncIterable<ExtractedPage> | Iterabl
13
17
  declare function writePage(page: ExtractedPage, write: HtmlWrite, options?: HtmlWriterOptions): Promise<void>;
14
18
  declare function pageToHtml(page: ExtractedPage, options?: HtmlWriterOptions): Promise<string>;
15
19
 
16
- export { type HtmlLayout, type HtmlWrite, type HtmlWriterOptions, pageToHtml, writeHtmlDocument, writePage };
20
+ export { type HtmlLayout, type HtmlProfile, type HtmlWrite, type HtmlWriterOptions, pageToHtml, writeHtmlDocument, writePage };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
2
  import { structurePage, tableToHtml } from "@boxpdf/reader/structure";
3
- var styles = `.pdf-document{margin:0 auto}.pdf-page{box-sizing:border-box;margin:1rem auto;background:#fff;color:#000}.pdf-page--positioned{position:relative;overflow:hidden}.pdf-page-content{position:absolute;transform-origin:0 0}.pdf-page-content--90{transform:translateX(100%) rotate(90deg)}.pdf-page-content--180{transform:translate(100%,100%) rotate(180deg)}.pdf-page-content--270{transform:translateY(100%) rotate(270deg)}.pdf-span{position:absolute;white-space:pre;transform-origin:left bottom;unicode-bidi:isolate}.pdf-span[data-direction=ttb]{writing-mode:vertical-rl}.pdf-page--flow{max-width:60rem;padding:1rem}.pdf-page--flow p{white-space:pre-wrap;unicode-bidi:plaintext}.pdf-page table{border-collapse:collapse}.pdf-page td{padding:.15rem .4rem;vertical-align:top}`;
3
+ var styles = `.pdf-document{margin:0 auto}.pdf-page{box-sizing:border-box;margin:1rem auto;background:#fff;color:#000}.pdf-page--visual,.pdf-page--positioned{position:relative;overflow:hidden}.pdf-page-content{position:absolute;transform-origin:0 0}.pdf-span{position:absolute;white-space:pre;transform-origin:left bottom;unicode-bidi:isolate}.pdf-span[data-direction=ttb]{writing-mode:vertical-rl}.pdf-page--semantic,.pdf-page--flow{max-width:60rem;padding:1rem}.pdf-page--semantic p,.pdf-page--flow p{white-space:pre-wrap;unicode-bidi:plaintext}.pdf-page table{border-collapse:collapse}.pdf-page td{padding:.15rem .4rem;vertical-align:top}`;
4
4
  async function writeHtmlDocument(pages, write, options = {}) {
5
5
  const includeDocument = options.includeDocument ?? true;
6
6
  if (includeDocument) {
@@ -19,8 +19,8 @@ async function writeHtmlDocument(pages, write, options = {}) {
19
19
  if (includeDocument) await write("</body></html>");
20
20
  }
21
21
  async function writePage(page, write, options = {}) {
22
- if ((options.layout ?? "positioned") === "flow") await writeFlowPage(page, write);
23
- else await writePositionedPage(page, write);
22
+ if (resolveProfile(options) === "semantic") await writeFlowPage(page, write);
23
+ else await writePositionedPage(page, write, options);
24
24
  }
25
25
  async function pageToHtml(page, options = {}) {
26
26
  let output = "";
@@ -33,24 +33,151 @@ async function pageToHtml(page, options = {}) {
33
33
  );
34
34
  return output;
35
35
  }
36
- async function writePositionedPage(page, write) {
36
+ async function writePositionedPage(page, write, options) {
37
+ const visualSpans = page.visualSpans ?? page.spans;
38
+ const reflectedOverlay = usesReflectedVisualOverlay(page, visualSpans);
37
39
  const quarterTurn = page.rotate === 90 || page.rotate === 270;
38
40
  const displayWidth = quarterTurn ? page.height : page.width;
39
41
  const displayHeight = quarterTurn ? page.width : page.height;
40
42
  await write(
41
- `<section class="pdf-page pdf-page--positioned" data-page="${page.number}" data-rotate="${page.rotate}" style="width:${number(displayWidth)}pt;height:${number(displayHeight)}pt">`
43
+ `<section class="pdf-page pdf-page--visual pdf-page--positioned" data-page="${page.number}" data-rotate="${page.rotate}" style="width:${number(displayWidth)}pt;height:${number(displayHeight)}pt">`
44
+ );
45
+ const fontAliases = new Map(
46
+ (page.fonts ?? []).filter((font) => font.format === "truetype" && !/(?:courier|^TTE)/i.test(font.family ?? "")).map((font) => [font.id, `boxpdf-${page.number}-${font.id}`])
47
+ );
48
+ const type3Fonts = new Map(
49
+ (page.fonts ?? []).filter((font) => font.format === "type3").map((font) => [font.id, font])
50
+ );
51
+ if ((options.includeStyles ?? true) && page.fonts?.length) {
52
+ await write(`<style>${page.fonts.map((font) => fontFace(font, fontAliases)).join("")}</style>`);
53
+ }
54
+ await write(
55
+ `<div class="pdf-page-content pdf-page-content--${page.rotate}" style="width:${number(page.width)}pt;height:${number(page.height)}pt${rotationTransform(page)}">`
42
56
  );
43
57
  await write(
44
- `<div class="pdf-page-content pdf-page-content--${page.rotate}" style="width:${number(page.width)}pt;height:${number(page.height)}pt">`
58
+ `<svg class="pdf-visual-text" xmlns="http://www.w3.org/2000/svg" width="${number(page.width)}pt" height="${number(page.height)}pt" viewBox="0 0 ${number(page.width)} ${number(page.height)}">`
45
59
  );
46
- for (const span of page.spans) await write(positionedSpan(span));
60
+ if (reflectedOverlay) {
61
+ for (const image of page.images ?? []) await write(visualImage(image, page.height));
62
+ }
63
+ for (const fill of page.fills ?? []) {
64
+ const points = fill.points.map(([x, y]) => `${number(x)},${number(page.height - y)}`).join(" ");
65
+ if (isCssHexColor(fill.color)) {
66
+ const opacity = isUnitInterval(fill.opacity) ? ` fill-opacity="${number(fill.opacity)}"` : "";
67
+ await write(`<polygon points="${points}" fill="${fill.color}"${opacity}/>`);
68
+ }
69
+ }
70
+ if (page.paths?.length) {
71
+ await write(`<g transform="translate(0 ${number(page.height)}) scale(1 -1)">`);
72
+ for (const path of page.paths) {
73
+ if (!isSvgPath(path.d)) continue;
74
+ const fill = isCssHexColor(path.fill) ? path.fill : "none";
75
+ const stroke = isCssHexColor(path.stroke) ? path.stroke : "none";
76
+ const strokeWidth = path.strokeWidth !== void 0 && Number.isFinite(path.strokeWidth) && path.strokeWidth >= 0 ? ` stroke-width="${number(path.strokeWidth)}"` : "";
77
+ const fillRule = path.fillRule ? ` fill-rule="${path.fillRule}"` : "";
78
+ const fillOpacity = isUnitInterval(path.fillOpacity) ? ` fill-opacity="${number(path.fillOpacity)}"` : "";
79
+ const strokeOpacity = isUnitInterval(path.strokeOpacity) ? ` stroke-opacity="${number(path.strokeOpacity)}"` : "";
80
+ const dasharray = path.strokeDasharray?.every((value) => Number.isFinite(value) && value >= 0) ? ` stroke-dasharray="${path.strokeDasharray.map(number).join(" ")}"` : "";
81
+ const dashoffset = Number.isFinite(path.strokeDashoffset) ? ` stroke-dashoffset="${number(path.strokeDashoffset ?? 0)}"` : "";
82
+ const linecap = path.strokeLinecap ? ` stroke-linecap="${path.strokeLinecap}"` : "";
83
+ const linejoin = path.strokeLinejoin ? ` stroke-linejoin="${path.strokeLinejoin}"` : "";
84
+ await write(
85
+ `<path d="${path.d}" fill="${fill}" stroke="${stroke}"${strokeWidth}${fillOpacity}${strokeOpacity}${dasharray}${dashoffset}${linecap}${linejoin}${fillRule}/>`
86
+ );
87
+ }
88
+ await write("</g>");
89
+ }
90
+ if (!reflectedOverlay) {
91
+ for (const image of page.images ?? []) await write(visualImage(image, page.height));
92
+ }
93
+ for (const span of visualSpans) {
94
+ if (!usesPositionedSpan(span)) {
95
+ const type3 = span.fontAssetId ? type3Fonts.get(span.fontAssetId) : void 0;
96
+ await write(
97
+ type3 ? visualType3Text(span, type3, page.height) : visualText(span, page.height, fontAliases, reflectedOverlay && page.rotate === 180)
98
+ );
99
+ }
100
+ }
101
+ await write("</svg>");
102
+ for (const span of visualSpans) {
103
+ if (usesPositionedSpan(span)) await write(positionedSpan(span, fontAliases));
104
+ }
47
105
  await write("</div></section>");
48
106
  }
107
+ function usesReflectedVisualOverlay(page, spans) {
108
+ return Boolean(page.images?.length) && Boolean(page.paths?.length || page.fills?.length) && spans.length > 0 && spans.every(
109
+ (span) => span.transform !== void 0 && Math.abs(span.transform[0] + 1) < 1e-6 && Math.abs(span.transform[1]) < 1e-6 && Math.abs(span.transform[2]) < 1e-6 && Math.abs(span.transform[3] - 1) < 1e-6
110
+ );
111
+ }
112
+ function visualImage(image, pageHeight) {
113
+ const [a, b, c, d, e, f] = image.transform;
114
+ const transform = [a, -b, -c, d, c + e, pageHeight - d - f].map(number).join(" ");
115
+ const opacity = isUnitInterval(image.opacity) ? ` opacity="${number(image.opacity)}"` : "";
116
+ const mime = image.format === "jpeg" ? "image/jpeg" : "image/bmp";
117
+ const data = image.format === "jpeg" ? image.data : rgbBmp(image);
118
+ return `<image width="1" height="1" preserveAspectRatio="none" transform="matrix(${transform})" href="data:${mime};base64,${base64(data)}"${opacity}/>`;
119
+ }
120
+ function rgbBmp(image) {
121
+ const stride = Math.ceil(image.width * 3 / 4) * 4;
122
+ const output = new Uint8Array(54 + stride * image.height);
123
+ const view = new DataView(output.buffer);
124
+ output[0] = 66;
125
+ output[1] = 77;
126
+ view.setUint32(2, output.length, true);
127
+ view.setUint32(10, 54, true);
128
+ view.setUint32(14, 40, true);
129
+ view.setInt32(18, image.width, true);
130
+ view.setInt32(22, -image.height, true);
131
+ view.setUint16(26, 1, true);
132
+ view.setUint16(28, 24, true);
133
+ view.setUint32(34, stride * image.height, true);
134
+ for (let row = 0; row < image.height; row += 1) {
135
+ for (let column = 0; column < image.width; column += 1) {
136
+ const source = (row * image.width + column) * 3;
137
+ const target = 54 + row * stride + column * 3;
138
+ output[target] = image.data[source + 2] ?? 0;
139
+ output[target + 1] = image.data[source + 1] ?? 0;
140
+ output[target + 2] = image.data[source] ?? 0;
141
+ }
142
+ }
143
+ return output;
144
+ }
145
+ function rotationTransform(page) {
146
+ switch (page.rotate) {
147
+ case 90:
148
+ return `;transform:translate(${number(page.height)}pt,0) rotate(90deg)`;
149
+ case 180:
150
+ return `;transform:translate(${number(page.width)}pt,${number(page.height)}pt) rotate(180deg)`;
151
+ case 270:
152
+ return `;transform:translate(0,${number(page.width)}pt) rotate(270deg)`;
153
+ default:
154
+ return "";
155
+ }
156
+ }
157
+ function positionedSpan(span, fontAliases) {
158
+ const direction = directionAttribute([span]);
159
+ const style = [
160
+ `left:${number(span.bounds.x)}pt`,
161
+ `bottom:${number(span.bounds.y)}pt`,
162
+ `width:${number(span.bounds.width)}pt`,
163
+ `height:${number(span.bounds.height)}pt`,
164
+ `font-size:${number(span.fontSize)}pt`,
165
+ ...isCssHexColor(span.color) ? [`color:${span.color}`] : [],
166
+ ...isUnitInterval(span.fillOpacity) ? [`opacity:${number(span.fillOpacity)}`] : [],
167
+ ...fontStyles(
168
+ span.fontFamily,
169
+ span.fontAssetId ? fontAliases.get(span.fontAssetId) : void 0
170
+ )
171
+ ].join(";");
172
+ return `<span class="pdf-span"${direction} style="${style}">${escapeHtml(span.text)}</span>`;
173
+ }
49
174
  async function writeFlowPage(page, write) {
50
175
  const structured = structurePage(page);
51
176
  const tables = [...structured.tables].sort((left, right) => right.bounds.y - left.bounds.y);
52
177
  const emittedTables = /* @__PURE__ */ new Set();
53
- await write(`<section class="pdf-page pdf-page--flow" data-page="${page.number}">`);
178
+ await write(
179
+ `<section class="pdf-page pdf-page--semantic pdf-page--flow" data-page="${page.number}">`
180
+ );
54
181
  for (const line of structured.lines) {
55
182
  const table = tables.find((candidate) => containsY(candidate, line.bounds.y));
56
183
  if (table) {
@@ -67,16 +194,150 @@ async function writeFlowPage(page, write) {
67
194
  }
68
195
  await write("</section>");
69
196
  }
70
- function positionedSpan(span) {
197
+ function visualText(span, pageHeight, fontAliases, counterRotateReflectedText = false) {
198
+ if (span.renderingMode === 3 || span.renderingMode === 7) return "";
199
+ if (!span.fontAssetId && isAdobeCjkFont(span.fontFamily)) return "";
71
200
  const direction = directionAttribute([span]);
201
+ const font = fontStyles(
202
+ span.fontFamily,
203
+ span.fontAssetId ? fontAliases.get(span.fontAssetId) : void 0
204
+ ).join(";");
205
+ const stroke = isCssHexColor(span.strokeColor) ? `stroke:${span.strokeColor}` : "";
206
+ const strokeWidth = stroke && Number.isFinite(span.strokeWidth) && (span.strokeWidth ?? -1) >= 0 ? `stroke-width:${number(span.strokeWidth ?? 0)}` : "";
207
+ const strokeOnly = span.renderingMode === 1 || span.renderingMode === 5;
208
+ const fillOpacity = isUnitInterval(span.fillOpacity) ? `fill-opacity:${number(span.fillOpacity)}` : "";
209
+ const strokeOpacity = isUnitInterval(span.strokeOpacity) ? `stroke-opacity:${number(span.strokeOpacity)}` : "";
72
210
  const style = [
73
- `left:${number(span.bounds.x)}pt`,
74
- `bottom:${number(span.bounds.y)}pt`,
75
- `width:${number(span.bounds.width)}pt`,
76
- `height:${number(span.bounds.height)}pt`,
77
- `font-size:${number(span.fontSize)}pt`
78
- ].join(";");
79
- return `<span class="pdf-span"${direction} style="${style}">${escapeHtml(span.text)}</span>`;
211
+ isHebrewPaintOrder(span) ? "unicode-bidi:bidi-override;direction:ltr" : "",
212
+ span.direction === "ttb" ? "writing-mode:vertical-rl" : "",
213
+ strokeOnly ? "fill:none" : isCssHexColor(span.color) ? `fill:${span.color}` : "",
214
+ stroke,
215
+ strokeWidth,
216
+ fillOpacity,
217
+ strokeOpacity,
218
+ font
219
+ ].filter(Boolean).join(";");
220
+ const textExtent = span.direction === "ttb" ? span.bounds.height : span.bounds.width;
221
+ const textLength = textExtent > 0 && !isHebrewPaintOrder(span) ? ` textLength="${number(textExtent)}" lengthAdjust="${span.direction === "ttb" || usesSpacingAdjustment(span) ? "spacing" : "spacingAndGlyphs"}"` : "";
222
+ const transform = counterRotateReflectedText && span.transform ? [
223
+ span.transform[0],
224
+ span.transform[1],
225
+ span.transform[2],
226
+ -span.transform[3]
227
+ ] : span.transform;
228
+ const transformed = hasNonIdentityTransform(transform);
229
+ const rtlOffset = span.direction === "rtl" ? span.bounds.width : 0;
230
+ const basisX = transform?.[0] ?? 1;
231
+ const basisY = transform?.[1] ?? 0;
232
+ const anchorX = span.bounds.x + basisX * rtlOffset;
233
+ const anchorY = pageHeight - span.bounds.y + basisY * rtlOffset;
234
+ const position = transformed ? ` x="0" y="0" transform="matrix(${transform?.map(number).join(" ")} ${number(anchorX)} ${number(anchorY)})"` : ` x="${number(anchorX)}" y="${number(anchorY)}"`;
235
+ return `<text${direction}${position} font-size="${number(span.fontSize)}"${textLength}${style ? ` style="${style}"` : ""}>${escapeHtml(span.text)}</text>`;
236
+ }
237
+ function isAdobeCjkFont(fontFamily) {
238
+ return /^Adobe(?:Heiti|Song|Kaiti|Ming|Gothic|Mincho)Std-/i.test(fontFamily ?? "");
239
+ }
240
+ function visualType3Text(span, font, pageHeight) {
241
+ if (span.renderingMode === 3 || span.renderingMode === 7) return "";
242
+ const glyphs = new Map(font.glyphs.map((glyph) => [glyph.code, glyph]));
243
+ const sequence = (span.glyphCodes ?? []).map((code) => glyphs.get(code));
244
+ const totalAdvance = sequence.reduce((total, glyph) => total + (glyph?.advance ?? 0), 0);
245
+ if (totalAdvance <= 0 || span.bounds.width <= 0 || span.fontSize <= 0) return "";
246
+ const transform = span.transform ?? [1, 0, 0, 1];
247
+ const outer = `matrix(${transform.map(number).join(" ")} ${number(span.bounds.x)} ${number(pageHeight - span.bounds.y)})`;
248
+ const xScale = span.bounds.width / totalAdvance;
249
+ let offset = 0;
250
+ let content = "";
251
+ for (const glyph of sequence) {
252
+ if (!glyph) continue;
253
+ content += `<g transform="translate(${number(offset)} 0)">${type3Glyph(glyph)}</g>`;
254
+ offset += glyph.advance;
255
+ }
256
+ return `<g transform="${outer}"><g transform="scale(${number(xScale)} ${number(-span.fontSize)})">${content}</g></g>`;
257
+ }
258
+ function isHebrewPaintOrder(span) {
259
+ return span.direction === "ltr" && /[\u0590-\u05ff]/u.test(span.text);
260
+ }
261
+ function usesSpacingAdjustment(span) {
262
+ return !span.fontAssetId && /arial/i.test(span.fontFamily ?? "");
263
+ }
264
+ function type3Glyph(glyph) {
265
+ let output = "";
266
+ for (const fill of glyph.fills ?? []) {
267
+ if (!isCssHexColor(fill.color)) continue;
268
+ const points = fill.points.map(([x, y]) => `${number(x)},${number(y)}`).join(" ");
269
+ const opacity = isUnitInterval(fill.opacity) ? ` fill-opacity="${number(fill.opacity)}"` : "";
270
+ output += `<polygon points="${points}" fill="${fill.color}"${opacity}/>`;
271
+ }
272
+ for (const path of glyph.paths ?? []) {
273
+ if (!isSvgPath(path.d)) continue;
274
+ const fill = isCssHexColor(path.fill) ? path.fill : "none";
275
+ const stroke = isCssHexColor(path.stroke) ? path.stroke : "none";
276
+ const width = path.strokeWidth !== void 0 && Number.isFinite(path.strokeWidth) && path.strokeWidth >= 0 ? ` stroke-width="${number(path.strokeWidth)}"` : "";
277
+ output += `<path d="${path.d}" fill="${fill}" stroke="${stroke}"${width}/>`;
278
+ }
279
+ return (glyph.fills?.length ?? 0) > 64 && glyph.advance > 2 ? `<g shape-rendering="crispEdges">${output}</g>` : output;
280
+ }
281
+ function isCssHexColor(value) {
282
+ return /^#[\da-f]{6}$/i.test(value ?? "");
283
+ }
284
+ function isUnitInterval(value) {
285
+ return Number.isFinite(value) && (value ?? -1) >= 0 && (value ?? 2) <= 1;
286
+ }
287
+ function isSvgPath(value) {
288
+ return value.length <= 1e6 && /^[\d\s.,+\-eEMmLlCcZz]+$/.test(value);
289
+ }
290
+ function fontStyles(fontFamily, alias) {
291
+ const normalized = fontFamily?.toLowerCase() ?? "";
292
+ const styles2 = [];
293
+ let fallback;
294
+ if (/courier|mono|nimbusmono|^cmtt/.test(normalized)) {
295
+ fallback = "Courier New,Courier,monospace";
296
+ } else if (/times|minion|serif|baskerville|georgia|nimbusrom|guardian.*egyp|^cm[rs]y?\d/.test(normalized)) {
297
+ fallback = "Times New Roman,Times,serif";
298
+ } else if (/helvetica|arial|sans|nimbussan|calibre|myriad|panton|^tte/.test(normalized)) {
299
+ fallback = "Arial,Helvetica,sans-serif";
300
+ } else if (/^mstt/.test(normalized)) {
301
+ fallback = "Arial,Helvetica,sans-serif";
302
+ }
303
+ if (alias || fallback) styles2.push(`font-family:${[alias, fallback].filter(Boolean).join(",")}`);
304
+ if (/bold|black|semibold|demi|medi|^tte/.test(normalized)) styles2.push("font-weight:700");
305
+ if (/italic|oblique|slant|ital(?:$|[_-])/.test(normalized)) styles2.push("font-style:italic");
306
+ return styles2;
307
+ }
308
+ function isMonospace(fontFamily) {
309
+ return /courier|mono/i.test(fontFamily ?? "");
310
+ }
311
+ function usesPositionedSpan(span) {
312
+ return !span.glyphCodes && isMonospace(span.fontFamily) && !hasNonIdentityTransform(span.transform);
313
+ }
314
+ function hasNonIdentityTransform(transform) {
315
+ if (!transform) return false;
316
+ const identity = [1, 0, 0, 1];
317
+ return transform.some((value, index) => Math.abs(value - (identity[index] ?? 0)) > 1e-6);
318
+ }
319
+ function fontFace(font, aliases) {
320
+ if (font.format !== "truetype") return "";
321
+ const alias = aliases.get(font.id);
322
+ if (!alias) return "";
323
+ const styles2 = fontStyles(font.family, alias).filter(
324
+ (style) => !style.startsWith("font-family:")
325
+ );
326
+ return `@font-face{font-family:${alias};src:url(data:font/ttf;base64,${base64(font.data)}) format("truetype");${styles2.join(";")}}`;
327
+ }
328
+ function base64(bytes) {
329
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
330
+ let output = "";
331
+ for (let index = 0; index < bytes.length; index += 3) {
332
+ const first = bytes[index] ?? 0;
333
+ const second = bytes[index + 1] ?? 0;
334
+ const third = bytes[index + 2] ?? 0;
335
+ output += alphabet[first >> 2];
336
+ output += alphabet[(first & 3) << 4 | second >> 4];
337
+ output += index + 1 < bytes.length ? alphabet[(second & 15) << 2 | third >> 6] : "=";
338
+ output += index + 2 < bytes.length ? alphabet[third & 63] : "=";
339
+ }
340
+ return output;
80
341
  }
81
342
  function directionAttribute(spans) {
82
343
  const rtl = spans.filter((span) => span.direction === "rtl").length;
@@ -103,6 +364,15 @@ function escapeHtml(value) {
103
364
  function isForbiddenControl(codePoint) {
104
365
  return codePoint <= 8 || codePoint === 11 || codePoint === 12 || codePoint >= 14 && codePoint <= 31 || codePoint === 127;
105
366
  }
367
+ function resolveProfile(options) {
368
+ const legacyProfile = options.layout === "flow" ? "semantic" : "visual";
369
+ if (options.profile && options.layout && options.profile !== legacyProfile) {
370
+ throw new Error(
371
+ `conflicting HTML output options: profile "${options.profile}" does not match layout "${options.layout}"`
372
+ );
373
+ }
374
+ return options.profile ?? legacyProfile;
375
+ }
106
376
  export {
107
377
  pageToHtml,
108
378
  writeHtmlDocument,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { ExtractedPage, TextSpan } from \"@boxpdf/reader\";\nimport { structurePage, type Table, tableToHtml } from \"@boxpdf/reader/structure\";\n\nexport type HtmlLayout = \"positioned\" | \"flow\";\nexport type HtmlWrite = (chunk: string) => void | Promise<void>;\n\nexport interface HtmlWriterOptions {\n layout?: HtmlLayout;\n title?: string;\n language?: string;\n includeDocument?: boolean;\n includeStyles?: boolean;\n}\n\nconst styles = `.pdf-document{margin:0 auto}.pdf-page{box-sizing:border-box;margin:1rem auto;background:#fff;color:#000}.pdf-page--positioned{position:relative;overflow:hidden}.pdf-page-content{position:absolute;transform-origin:0 0}.pdf-page-content--90{transform:translateX(100%) rotate(90deg)}.pdf-page-content--180{transform:translate(100%,100%) rotate(180deg)}.pdf-page-content--270{transform:translateY(100%) rotate(270deg)}.pdf-span{position:absolute;white-space:pre;transform-origin:left bottom;unicode-bidi:isolate}.pdf-span[data-direction=ttb]{writing-mode:vertical-rl}.pdf-page--flow{max-width:60rem;padding:1rem}.pdf-page--flow p{white-space:pre-wrap;unicode-bidi:plaintext}.pdf-page table{border-collapse:collapse}.pdf-page td{padding:.15rem .4rem;vertical-align:top}`;\n\nexport async function writeHtmlDocument(\n pages: AsyncIterable<ExtractedPage> | Iterable<ExtractedPage>,\n write: HtmlWrite,\n options: HtmlWriterOptions = {},\n): Promise<void> {\n const includeDocument = options.includeDocument ?? true;\n if (includeDocument) {\n await write(\"<!doctype html><html\");\n await write(\n ` lang=\"${escapeAttribute(options.language ?? \"en\")}\"><head><meta charset=\"utf-8\">`,\n );\n await write('<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">');\n await write(`<title>${escapeHtml(options.title ?? \"PDF document\")}</title>`);\n if (options.includeStyles ?? true) await write(`<style>${styles}</style>`);\n await write(\"</head><body>\");\n }\n await write('<main class=\"pdf-document\">');\n for await (const page of pages) await writePage(page, write, options);\n await write(\"</main>\");\n if (includeDocument) await write(\"</body></html>\");\n}\n\nexport async function writePage(\n page: ExtractedPage,\n write: HtmlWrite,\n options: HtmlWriterOptions = {},\n): Promise<void> {\n if ((options.layout ?? \"positioned\") === \"flow\") await writeFlowPage(page, write);\n else await writePositionedPage(page, write);\n}\n\nexport async function pageToHtml(\n page: ExtractedPage,\n options: HtmlWriterOptions = {},\n): Promise<string> {\n let output = \"\";\n await writePage(\n page,\n (chunk) => {\n output += chunk;\n },\n options,\n );\n return output;\n}\n\nasync function writePositionedPage(page: ExtractedPage, write: HtmlWrite): Promise<void> {\n const quarterTurn = page.rotate === 90 || page.rotate === 270;\n const displayWidth = quarterTurn ? page.height : page.width;\n const displayHeight = quarterTurn ? page.width : page.height;\n await write(\n `<section class=\"pdf-page pdf-page--positioned\" data-page=\"${page.number}\" data-rotate=\"${page.rotate}\" style=\"width:${number(displayWidth)}pt;height:${number(displayHeight)}pt\">`,\n );\n await write(\n `<div class=\"pdf-page-content pdf-page-content--${page.rotate}\" style=\"width:${number(page.width)}pt;height:${number(page.height)}pt\">`,\n );\n for (const span of page.spans) await write(positionedSpan(span));\n await write(\"</div></section>\");\n}\n\nasync function writeFlowPage(page: ExtractedPage, write: HtmlWrite): Promise<void> {\n const structured = structurePage(page);\n const tables = [...structured.tables].sort((left, right) => right.bounds.y - left.bounds.y);\n const emittedTables = new Set<Table>();\n await write(`<section class=\"pdf-page pdf-page--flow\" data-page=\"${page.number}\">`);\n for (const line of structured.lines) {\n const table = tables.find((candidate) => containsY(candidate, line.bounds.y));\n if (table) {\n if (!emittedTables.has(table)) {\n await write(tableToHtml(table));\n emittedTables.add(table);\n }\n continue;\n }\n await write(`<p${directionAttribute(line.spans)}>${escapeHtml(line.text)}</p>`);\n }\n for (const table of tables) {\n if (!emittedTables.has(table)) await write(tableToHtml(table));\n }\n await write(\"</section>\");\n}\n\nfunction positionedSpan(span: TextSpan): string {\n const direction = directionAttribute([span]);\n const style = [\n `left:${number(span.bounds.x)}pt`,\n `bottom:${number(span.bounds.y)}pt`,\n `width:${number(span.bounds.width)}pt`,\n `height:${number(span.bounds.height)}pt`,\n `font-size:${number(span.fontSize)}pt`,\n ].join(\";\");\n return `<span class=\"pdf-span\"${direction} style=\"${style}\">${escapeHtml(span.text)}</span>`;\n}\n\nfunction directionAttribute(spans: TextSpan[]): string {\n const rtl = spans.filter((span) => span.direction === \"rtl\").length;\n const vertical = spans.filter((span) => span.direction === \"ttb\").length;\n if (vertical > rtl && vertical * 2 >= spans.length) return ' data-direction=\"ttb\"';\n return rtl * 2 >= spans.length && spans.length > 0 ? ' dir=\"rtl\"' : \"\";\n}\n\nfunction containsY(table: Table, y: number): boolean {\n return y >= table.bounds.y && y <= table.bounds.y + table.bounds.height;\n}\n\nfunction number(value: number): string {\n return Number.isFinite(value) ? String(Math.round(value * 1000) / 1000) : \"0\";\n}\n\nfunction escapeAttribute(value: string): string {\n return escapeHtml(value).replaceAll(\"`\", \"&#96;\");\n}\n\nfunction escapeHtml(value: string): string {\n return [...value]\n .map((character) => {\n const codePoint = character.codePointAt(0) ?? 0;\n if (codePoint === 13) return \"\\n\";\n return isForbiddenControl(codePoint) ? \"�\" : character;\n })\n .join(\"\")\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"'\", \"&#39;\");\n}\n\nfunction isForbiddenControl(codePoint: number): boolean {\n return (\n codePoint <= 8 ||\n codePoint === 11 ||\n codePoint === 12 ||\n (codePoint >= 14 && codePoint <= 31) ||\n codePoint === 127\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n EmbeddedFont,\n EmbeddedType3Font,\n ExtractedPage,\n RasterImage,\n TextSpan,\n Type3Glyph,\n} from \"@boxpdf/reader\";\nimport { structurePage, type Table, tableToHtml } from \"@boxpdf/reader/structure\";\n\nexport type HtmlLayout = \"positioned\" | \"flow\";\nexport type HtmlProfile = \"visual\" | \"semantic\";\nexport type HtmlWrite = (chunk: string) => void | Promise<void>;\n\nexport interface HtmlWriterOptions {\n /** Output intent. Visual preserves page presentation; semantic prioritizes reading order. */\n profile?: HtmlProfile;\n /** @deprecated Use `profile: \"visual\"` or `profile: \"semantic\"`. */\n layout?: HtmlLayout;\n title?: string;\n language?: string;\n includeDocument?: boolean;\n includeStyles?: boolean;\n}\n\nconst styles = `.pdf-document{margin:0 auto}.pdf-page{box-sizing:border-box;margin:1rem auto;background:#fff;color:#000}.pdf-page--visual,.pdf-page--positioned{position:relative;overflow:hidden}.pdf-page-content{position:absolute;transform-origin:0 0}.pdf-span{position:absolute;white-space:pre;transform-origin:left bottom;unicode-bidi:isolate}.pdf-span[data-direction=ttb]{writing-mode:vertical-rl}.pdf-page--semantic,.pdf-page--flow{max-width:60rem;padding:1rem}.pdf-page--semantic p,.pdf-page--flow p{white-space:pre-wrap;unicode-bidi:plaintext}.pdf-page table{border-collapse:collapse}.pdf-page td{padding:.15rem .4rem;vertical-align:top}`;\n\nexport async function writeHtmlDocument(\n pages: AsyncIterable<ExtractedPage> | Iterable<ExtractedPage>,\n write: HtmlWrite,\n options: HtmlWriterOptions = {},\n): Promise<void> {\n const includeDocument = options.includeDocument ?? true;\n if (includeDocument) {\n await write(\"<!doctype html><html\");\n await write(\n ` lang=\"${escapeAttribute(options.language ?? \"en\")}\"><head><meta charset=\"utf-8\">`,\n );\n await write('<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">');\n await write(`<title>${escapeHtml(options.title ?? \"PDF document\")}</title>`);\n if (options.includeStyles ?? true) await write(`<style>${styles}</style>`);\n await write(\"</head><body>\");\n }\n await write('<main class=\"pdf-document\">');\n for await (const page of pages) await writePage(page, write, options);\n await write(\"</main>\");\n if (includeDocument) await write(\"</body></html>\");\n}\n\nexport async function writePage(\n page: ExtractedPage,\n write: HtmlWrite,\n options: HtmlWriterOptions = {},\n): Promise<void> {\n if (resolveProfile(options) === \"semantic\") await writeFlowPage(page, write);\n else await writePositionedPage(page, write, options);\n}\n\nexport async function pageToHtml(\n page: ExtractedPage,\n options: HtmlWriterOptions = {},\n): Promise<string> {\n let output = \"\";\n await writePage(\n page,\n (chunk) => {\n output += chunk;\n },\n options,\n );\n return output;\n}\n\nasync function writePositionedPage(\n page: ExtractedPage,\n write: HtmlWrite,\n options: HtmlWriterOptions,\n): Promise<void> {\n const visualSpans = page.visualSpans ?? page.spans;\n const reflectedOverlay = usesReflectedVisualOverlay(page, visualSpans);\n const quarterTurn = page.rotate === 90 || page.rotate === 270;\n const displayWidth = quarterTurn ? page.height : page.width;\n const displayHeight = quarterTurn ? page.width : page.height;\n await write(\n `<section class=\"pdf-page pdf-page--visual pdf-page--positioned\" data-page=\"${page.number}\" data-rotate=\"${page.rotate}\" style=\"width:${number(displayWidth)}pt;height:${number(displayHeight)}pt\">`,\n );\n const fontAliases = new Map(\n (page.fonts ?? [])\n .filter((font) => font.format === \"truetype\" && !/(?:courier|^TTE)/i.test(font.family ?? \"\"))\n .map((font) => [font.id, `boxpdf-${page.number}-${font.id}`]),\n );\n const type3Fonts = new Map(\n (page.fonts ?? [])\n .filter((font): font is EmbeddedType3Font => font.format === \"type3\")\n .map((font) => [font.id, font]),\n );\n if ((options.includeStyles ?? true) && page.fonts?.length) {\n await write(`<style>${page.fonts.map((font) => fontFace(font, fontAliases)).join(\"\")}</style>`);\n }\n await write(\n `<div class=\"pdf-page-content pdf-page-content--${page.rotate}\" style=\"width:${number(page.width)}pt;height:${number(page.height)}pt${rotationTransform(page)}\">`,\n );\n await write(\n `<svg class=\"pdf-visual-text\" xmlns=\"http://www.w3.org/2000/svg\" width=\"${number(page.width)}pt\" height=\"${number(page.height)}pt\" viewBox=\"0 0 ${number(page.width)} ${number(page.height)}\">`,\n );\n if (reflectedOverlay) {\n for (const image of page.images ?? []) await write(visualImage(image, page.height));\n }\n for (const fill of page.fills ?? []) {\n const points = fill.points.map(([x, y]) => `${number(x)},${number(page.height - y)}`).join(\" \");\n if (isCssHexColor(fill.color)) {\n const opacity = isUnitInterval(fill.opacity) ? ` fill-opacity=\"${number(fill.opacity)}\"` : \"\";\n await write(`<polygon points=\"${points}\" fill=\"${fill.color}\"${opacity}/>`);\n }\n }\n if (page.paths?.length) {\n await write(`<g transform=\"translate(0 ${number(page.height)}) scale(1 -1)\">`);\n for (const path of page.paths) {\n if (!isSvgPath(path.d)) continue;\n const fill = isCssHexColor(path.fill) ? path.fill : \"none\";\n const stroke = isCssHexColor(path.stroke) ? path.stroke : \"none\";\n const strokeWidth =\n path.strokeWidth !== undefined && Number.isFinite(path.strokeWidth) && path.strokeWidth >= 0\n ? ` stroke-width=\"${number(path.strokeWidth)}\"`\n : \"\";\n const fillRule = path.fillRule ? ` fill-rule=\"${path.fillRule}\"` : \"\";\n const fillOpacity = isUnitInterval(path.fillOpacity)\n ? ` fill-opacity=\"${number(path.fillOpacity)}\"`\n : \"\";\n const strokeOpacity = isUnitInterval(path.strokeOpacity)\n ? ` stroke-opacity=\"${number(path.strokeOpacity)}\"`\n : \"\";\n const dasharray = path.strokeDasharray?.every((value) => Number.isFinite(value) && value >= 0)\n ? ` stroke-dasharray=\"${path.strokeDasharray.map(number).join(\" \")}\"`\n : \"\";\n const dashoffset = Number.isFinite(path.strokeDashoffset)\n ? ` stroke-dashoffset=\"${number(path.strokeDashoffset ?? 0)}\"`\n : \"\";\n const linecap = path.strokeLinecap ? ` stroke-linecap=\"${path.strokeLinecap}\"` : \"\";\n const linejoin = path.strokeLinejoin ? ` stroke-linejoin=\"${path.strokeLinejoin}\"` : \"\";\n await write(\n `<path d=\"${path.d}\" fill=\"${fill}\" stroke=\"${stroke}\"${strokeWidth}${fillOpacity}${strokeOpacity}${dasharray}${dashoffset}${linecap}${linejoin}${fillRule}/>`,\n );\n }\n await write(\"</g>\");\n }\n if (!reflectedOverlay) {\n for (const image of page.images ?? []) await write(visualImage(image, page.height));\n }\n for (const span of visualSpans) {\n if (!usesPositionedSpan(span)) {\n const type3 = span.fontAssetId ? type3Fonts.get(span.fontAssetId) : undefined;\n await write(\n type3\n ? visualType3Text(span, type3, page.height)\n : visualText(span, page.height, fontAliases, reflectedOverlay && page.rotate === 180),\n );\n }\n }\n await write(\"</svg>\");\n for (const span of visualSpans) {\n if (usesPositionedSpan(span)) await write(positionedSpan(span, fontAliases));\n }\n await write(\"</div></section>\");\n}\n\nfunction usesReflectedVisualOverlay(page: ExtractedPage, spans: TextSpan[]): boolean {\n return (\n Boolean(page.images?.length) &&\n Boolean(page.paths?.length || page.fills?.length) &&\n spans.length > 0 &&\n spans.every(\n (span) =>\n span.transform !== undefined &&\n Math.abs(span.transform[0] + 1) < 0.000_001 &&\n Math.abs(span.transform[1]) < 0.000_001 &&\n Math.abs(span.transform[2]) < 0.000_001 &&\n Math.abs(span.transform[3] - 1) < 0.000_001,\n )\n );\n}\n\nfunction visualImage(image: RasterImage, pageHeight: number): string {\n const [a, b, c, d, e, f] = image.transform;\n const transform = [a, -b, -c, d, c + e, pageHeight - d - f].map(number).join(\" \");\n const opacity = isUnitInterval(image.opacity) ? ` opacity=\"${number(image.opacity)}\"` : \"\";\n const mime = image.format === \"jpeg\" ? \"image/jpeg\" : \"image/bmp\";\n const data = image.format === \"jpeg\" ? image.data : rgbBmp(image);\n return `<image width=\"1\" height=\"1\" preserveAspectRatio=\"none\" transform=\"matrix(${transform})\" href=\"data:${mime};base64,${base64(data)}\"${opacity}/>`;\n}\n\nfunction rgbBmp(image: RasterImage): Uint8Array {\n const stride = Math.ceil((image.width * 3) / 4) * 4;\n const output = new Uint8Array(54 + stride * image.height);\n const view = new DataView(output.buffer);\n output[0] = 0x42;\n output[1] = 0x4d;\n view.setUint32(2, output.length, true);\n view.setUint32(10, 54, true);\n view.setUint32(14, 40, true);\n view.setInt32(18, image.width, true);\n view.setInt32(22, -image.height, true);\n view.setUint16(26, 1, true);\n view.setUint16(28, 24, true);\n view.setUint32(34, stride * image.height, true);\n for (let row = 0; row < image.height; row += 1) {\n for (let column = 0; column < image.width; column += 1) {\n const source = (row * image.width + column) * 3;\n const target = 54 + row * stride + column * 3;\n output[target] = image.data[source + 2] ?? 0;\n output[target + 1] = image.data[source + 1] ?? 0;\n output[target + 2] = image.data[source] ?? 0;\n }\n }\n return output;\n}\n\nfunction rotationTransform(page: ExtractedPage): string {\n switch (page.rotate) {\n case 90:\n return `;transform:translate(${number(page.height)}pt,0) rotate(90deg)`;\n case 180:\n return `;transform:translate(${number(page.width)}pt,${number(page.height)}pt) rotate(180deg)`;\n case 270:\n return `;transform:translate(0,${number(page.width)}pt) rotate(270deg)`;\n default:\n return \"\";\n }\n}\n\nfunction positionedSpan(span: TextSpan, fontAliases: Map<string, string>): string {\n const direction = directionAttribute([span]);\n const style = [\n `left:${number(span.bounds.x)}pt`,\n `bottom:${number(span.bounds.y)}pt`,\n `width:${number(span.bounds.width)}pt`,\n `height:${number(span.bounds.height)}pt`,\n `font-size:${number(span.fontSize)}pt`,\n ...(isCssHexColor(span.color) ? [`color:${span.color}`] : []),\n ...(isUnitInterval(span.fillOpacity) ? [`opacity:${number(span.fillOpacity)}`] : []),\n ...fontStyles(\n span.fontFamily,\n span.fontAssetId ? fontAliases.get(span.fontAssetId) : undefined,\n ),\n ].join(\";\");\n return `<span class=\"pdf-span\"${direction} style=\"${style}\">${escapeHtml(span.text)}</span>`;\n}\n\nasync function writeFlowPage(page: ExtractedPage, write: HtmlWrite): Promise<void> {\n const structured = structurePage(page);\n const tables = [...structured.tables].sort((left, right) => right.bounds.y - left.bounds.y);\n const emittedTables = new Set<Table>();\n await write(\n `<section class=\"pdf-page pdf-page--semantic pdf-page--flow\" data-page=\"${page.number}\">`,\n );\n for (const line of structured.lines) {\n const table = tables.find((candidate) => containsY(candidate, line.bounds.y));\n if (table) {\n if (!emittedTables.has(table)) {\n await write(tableToHtml(table));\n emittedTables.add(table);\n }\n continue;\n }\n await write(`<p${directionAttribute(line.spans)}>${escapeHtml(line.text)}</p>`);\n }\n for (const table of tables) {\n if (!emittedTables.has(table)) await write(tableToHtml(table));\n }\n await write(\"</section>\");\n}\n\nfunction visualText(\n span: TextSpan,\n pageHeight: number,\n fontAliases: Map<string, string>,\n counterRotateReflectedText = false,\n): string {\n if (span.renderingMode === 3 || span.renderingMode === 7) return \"\";\n if (!span.fontAssetId && isAdobeCjkFont(span.fontFamily)) return \"\";\n const direction = directionAttribute([span]);\n const font = fontStyles(\n span.fontFamily,\n span.fontAssetId ? fontAliases.get(span.fontAssetId) : undefined,\n ).join(\";\");\n const stroke = isCssHexColor(span.strokeColor) ? `stroke:${span.strokeColor}` : \"\";\n const strokeWidth =\n stroke && Number.isFinite(span.strokeWidth) && (span.strokeWidth ?? -1) >= 0\n ? `stroke-width:${number(span.strokeWidth ?? 0)}`\n : \"\";\n const strokeOnly = span.renderingMode === 1 || span.renderingMode === 5;\n const fillOpacity = isUnitInterval(span.fillOpacity)\n ? `fill-opacity:${number(span.fillOpacity)}`\n : \"\";\n const strokeOpacity = isUnitInterval(span.strokeOpacity)\n ? `stroke-opacity:${number(span.strokeOpacity)}`\n : \"\";\n const style = [\n isHebrewPaintOrder(span) ? \"unicode-bidi:bidi-override;direction:ltr\" : \"\",\n span.direction === \"ttb\" ? \"writing-mode:vertical-rl\" : \"\",\n strokeOnly ? \"fill:none\" : isCssHexColor(span.color) ? `fill:${span.color}` : \"\",\n stroke,\n strokeWidth,\n fillOpacity,\n strokeOpacity,\n font,\n ]\n .filter(Boolean)\n .join(\";\");\n const textExtent = span.direction === \"ttb\" ? span.bounds.height : span.bounds.width;\n const textLength =\n textExtent > 0 && !isHebrewPaintOrder(span)\n ? ` textLength=\"${number(textExtent)}\" lengthAdjust=\"${span.direction === \"ttb\" || usesSpacingAdjustment(span) ? \"spacing\" : \"spacingAndGlyphs\"}\"`\n : \"\";\n const transform =\n counterRotateReflectedText && span.transform\n ? ([\n span.transform[0],\n span.transform[1],\n span.transform[2],\n -span.transform[3],\n ] as TextSpan[\"transform\"])\n : span.transform;\n const transformed = hasNonIdentityTransform(transform);\n const rtlOffset = span.direction === \"rtl\" ? span.bounds.width : 0;\n const basisX = transform?.[0] ?? 1;\n const basisY = transform?.[1] ?? 0;\n const anchorX = span.bounds.x + basisX * rtlOffset;\n const anchorY = pageHeight - span.bounds.y + basisY * rtlOffset;\n const position = transformed\n ? ` x=\"0\" y=\"0\" transform=\"matrix(${transform?.map(number).join(\" \")} ${number(anchorX)} ${number(anchorY)})\"`\n : ` x=\"${number(anchorX)}\" y=\"${number(anchorY)}\"`;\n return `<text${direction}${position} font-size=\"${number(span.fontSize)}\"${textLength}${style ? ` style=\"${style}\"` : \"\"}>${escapeHtml(span.text)}</text>`;\n}\n\nfunction isAdobeCjkFont(fontFamily: string | undefined): boolean {\n return /^Adobe(?:Heiti|Song|Kaiti|Ming|Gothic|Mincho)Std-/i.test(fontFamily ?? \"\");\n}\n\nfunction visualType3Text(span: TextSpan, font: EmbeddedType3Font, pageHeight: number): string {\n if (span.renderingMode === 3 || span.renderingMode === 7) return \"\";\n const glyphs = new Map(font.glyphs.map((glyph) => [glyph.code, glyph]));\n const sequence = (span.glyphCodes ?? []).map((code) => glyphs.get(code));\n const totalAdvance = sequence.reduce((total, glyph) => total + (glyph?.advance ?? 0), 0);\n if (totalAdvance <= 0 || span.bounds.width <= 0 || span.fontSize <= 0) return \"\";\n const transform = span.transform ?? [1, 0, 0, 1];\n const outer = `matrix(${transform.map(number).join(\" \")} ${number(span.bounds.x)} ${number(pageHeight - span.bounds.y)})`;\n const xScale = span.bounds.width / totalAdvance;\n let offset = 0;\n let content = \"\";\n for (const glyph of sequence) {\n if (!glyph) continue;\n content += `<g transform=\"translate(${number(offset)} 0)\">${type3Glyph(glyph)}</g>`;\n offset += glyph.advance;\n }\n return `<g transform=\"${outer}\"><g transform=\"scale(${number(xScale)} ${number(-span.fontSize)})\">${content}</g></g>`;\n}\n\nfunction isHebrewPaintOrder(span: TextSpan): boolean {\n return span.direction === \"ltr\" && /[\\u0590-\\u05ff]/u.test(span.text);\n}\n\nfunction usesSpacingAdjustment(span: TextSpan): boolean {\n return !span.fontAssetId && /arial/i.test(span.fontFamily ?? \"\");\n}\n\nfunction type3Glyph(glyph: Type3Glyph): string {\n let output = \"\";\n for (const fill of glyph.fills ?? []) {\n if (!isCssHexColor(fill.color)) continue;\n const points = fill.points.map(([x, y]) => `${number(x)},${number(y)}`).join(\" \");\n const opacity = isUnitInterval(fill.opacity) ? ` fill-opacity=\"${number(fill.opacity)}\"` : \"\";\n output += `<polygon points=\"${points}\" fill=\"${fill.color}\"${opacity}/>`;\n }\n for (const path of glyph.paths ?? []) {\n if (!isSvgPath(path.d)) continue;\n const fill = isCssHexColor(path.fill) ? path.fill : \"none\";\n const stroke = isCssHexColor(path.stroke) ? path.stroke : \"none\";\n const width =\n path.strokeWidth !== undefined && Number.isFinite(path.strokeWidth) && path.strokeWidth >= 0\n ? ` stroke-width=\"${number(path.strokeWidth)}\"`\n : \"\";\n output += `<path d=\"${path.d}\" fill=\"${fill}\" stroke=\"${stroke}\"${width}/>`;\n }\n return (glyph.fills?.length ?? 0) > 64 && glyph.advance > 2\n ? `<g shape-rendering=\"crispEdges\">${output}</g>`\n : output;\n}\n\nfunction isCssHexColor(value: string | undefined): value is string {\n return /^#[\\da-f]{6}$/i.test(value ?? \"\");\n}\n\nfunction isUnitInterval(value: number | undefined): value is number {\n return Number.isFinite(value) && (value ?? -1) >= 0 && (value ?? 2) <= 1;\n}\n\nfunction isSvgPath(value: string): boolean {\n return value.length <= 1_000_000 && /^[\\d\\s.,+\\-eEMmLlCcZz]+$/.test(value);\n}\n\nfunction fontStyles(fontFamily: string | undefined, alias?: string): string[] {\n const normalized = fontFamily?.toLowerCase() ?? \"\";\n const styles: string[] = [];\n let fallback: string | undefined;\n if (/courier|mono|nimbusmono|^cmtt/.test(normalized)) {\n fallback = \"Courier New,Courier,monospace\";\n } else if (\n /times|minion|serif|baskerville|georgia|nimbusrom|guardian.*egyp|^cm[rs]y?\\d/.test(normalized)\n ) {\n fallback = \"Times New Roman,Times,serif\";\n } else if (/helvetica|arial|sans|nimbussan|calibre|myriad|panton|^tte/.test(normalized)) {\n fallback = \"Arial,Helvetica,sans-serif\";\n } else if (/^mstt/.test(normalized)) {\n fallback = \"Arial,Helvetica,sans-serif\";\n }\n if (alias || fallback) styles.push(`font-family:${[alias, fallback].filter(Boolean).join(\",\")}`);\n if (/bold|black|semibold|demi|medi|^tte/.test(normalized)) styles.push(\"font-weight:700\");\n if (/italic|oblique|slant|ital(?:$|[_-])/.test(normalized)) styles.push(\"font-style:italic\");\n return styles;\n}\n\nfunction isMonospace(fontFamily: string | undefined): boolean {\n return /courier|mono/i.test(fontFamily ?? \"\");\n}\n\nfunction usesPositionedSpan(span: TextSpan): boolean {\n return (\n !span.glyphCodes && isMonospace(span.fontFamily) && !hasNonIdentityTransform(span.transform)\n );\n}\n\nfunction hasNonIdentityTransform(transform: TextSpan[\"transform\"]): boolean {\n if (!transform) return false;\n const identity: [number, number, number, number] = [1, 0, 0, 1];\n return transform.some((value, index) => Math.abs(value - (identity[index] ?? 0)) > 0.000_001);\n}\n\nfunction fontFace(font: EmbeddedFont, aliases: Map<string, string>): string {\n if (font.format !== \"truetype\") return \"\";\n const alias = aliases.get(font.id);\n if (!alias) return \"\";\n const styles = fontStyles(font.family, alias).filter(\n (style) => !style.startsWith(\"font-family:\"),\n );\n return `@font-face{font-family:${alias};src:url(data:font/ttf;base64,${base64(font.data)}) format(\"truetype\");${styles.join(\";\")}}`;\n}\n\nfunction base64(bytes: Uint8Array): string {\n const alphabet = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n let output = \"\";\n for (let index = 0; index < bytes.length; index += 3) {\n const first = bytes[index] ?? 0;\n const second = bytes[index + 1] ?? 0;\n const third = bytes[index + 2] ?? 0;\n output += alphabet[first >> 2];\n output += alphabet[((first & 3) << 4) | (second >> 4)];\n output += index + 1 < bytes.length ? alphabet[((second & 15) << 2) | (third >> 6)] : \"=\";\n output += index + 2 < bytes.length ? alphabet[third & 63] : \"=\";\n }\n return output;\n}\n\nfunction directionAttribute(spans: TextSpan[]): string {\n const rtl = spans.filter((span) => span.direction === \"rtl\").length;\n const vertical = spans.filter((span) => span.direction === \"ttb\").length;\n if (vertical > rtl && vertical * 2 >= spans.length) return ' data-direction=\"ttb\"';\n return rtl * 2 >= spans.length && spans.length > 0 ? ' dir=\"rtl\"' : \"\";\n}\n\nfunction containsY(table: Table, y: number): boolean {\n return y >= table.bounds.y && y <= table.bounds.y + table.bounds.height;\n}\n\nfunction number(value: number): string {\n return Number.isFinite(value) ? String(Math.round(value * 1000) / 1000) : \"0\";\n}\n\nfunction escapeAttribute(value: string): string {\n return escapeHtml(value).replaceAll(\"`\", \"&#96;\");\n}\n\nfunction escapeHtml(value: string): string {\n return [...value]\n .map((character) => {\n const codePoint = character.codePointAt(0) ?? 0;\n if (codePoint === 13) return \"\\n\";\n return isForbiddenControl(codePoint) ? \"�\" : character;\n })\n .join(\"\")\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"'\", \"&#39;\");\n}\n\nfunction isForbiddenControl(codePoint: number): boolean {\n return (\n codePoint <= 8 ||\n codePoint === 11 ||\n codePoint === 12 ||\n (codePoint >= 14 && codePoint <= 31) ||\n codePoint === 127\n );\n}\n\nfunction resolveProfile(options: HtmlWriterOptions): HtmlProfile {\n const legacyProfile = options.layout === \"flow\" ? \"semantic\" : \"visual\";\n if (options.profile && options.layout && options.profile !== legacyProfile) {\n throw new Error(\n `conflicting HTML output options: profile \"${options.profile}\" does not match layout \"${options.layout}\"`,\n );\n }\n return options.profile ?? legacyProfile;\n}\n"],"mappings":"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","names":["styles"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@boxpdf/html-writer",
3
- "version": "0.1.2",
4
- "description": "Stream PDF pages from @boxpdf/reader to accessible or positioned HTML.",
3
+ "version": "0.1.8",
4
+ "description": "Stream PDF pages from @boxpdf/reader to visual or semantic HTML.",
5
5
  "keywords": [
6
6
  "pdf",
7
7
  "pdf-to-html",