@bendyline/squisq-formats 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-532L4D5D.js +21 -0
- package/dist/chunk-532L4D5D.js.map +1 -0
- package/dist/chunk-67KIJHV2.js +21 -0
- package/dist/chunk-67KIJHV2.js.map +1 -0
- package/dist/chunk-KAK4V57E.js +387 -0
- package/dist/chunk-KAK4V57E.js.map +1 -0
- package/dist/chunk-MQHCXI56.js +1035 -0
- package/dist/chunk-MQHCXI56.js.map +1 -0
- package/dist/chunk-TBPD5PCU.js +1136 -0
- package/dist/chunk-TBPD5PCU.js.map +1 -0
- package/dist/chunk-ULLIPBEJ.js +271 -0
- package/dist/chunk-ULLIPBEJ.js.map +1 -0
- package/dist/docx/index.d.ts +108 -0
- package/dist/docx/index.js +14 -0
- package/dist/docx/index.js.map +1 -0
- package/dist/html/index.d.ts +166 -0
- package/dist/html/index.js +17 -0
- package/dist/html/index.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/dist/ooxml/index.d.ts +351 -0
- package/dist/ooxml/index.js +99 -0
- package/dist/ooxml/index.js.map +1 -0
- package/dist/pdf/index.d.ts +130 -0
- package/dist/pdf/index.js +15 -0
- package/dist/pdf/index.js.map +1 -0
- package/dist/pptx/index.d.ts +58 -0
- package/dist/pptx/index.js +13 -0
- package/dist/pptx/index.js.map +1 -0
- package/dist/xlsx/index.d.ts +58 -0
- package/dist/xlsx/index.js +13 -0
- package/dist/xlsx/index.js.map +1 -0
- package/package.json +85 -0
- package/src/__tests__/docxExport.test.ts +457 -0
- package/src/__tests__/docxImport.test.ts +410 -0
- package/src/__tests__/html.test.ts +295 -0
- package/src/__tests__/ooxml.test.ts +197 -0
- package/src/__tests__/pdfExport.test.ts +322 -0
- package/src/__tests__/pdfImport.test.ts +290 -0
- package/src/__tests__/roundTrip.test.ts +201 -0
- package/src/docx/export.ts +978 -0
- package/src/docx/import.ts +909 -0
- package/src/docx/index.ts +26 -0
- package/src/docx/styles.ts +145 -0
- package/src/html/htmlTemplate.ts +307 -0
- package/src/html/imageUtils.ts +66 -0
- package/src/html/index.ts +168 -0
- package/src/index.ts +51 -0
- package/src/ooxml/index.ts +87 -0
- package/src/ooxml/namespaces.ts +160 -0
- package/src/ooxml/reader.ts +218 -0
- package/src/ooxml/types.ts +104 -0
- package/src/ooxml/writer.ts +321 -0
- package/src/ooxml/xmlUtils.ts +123 -0
- package/src/pdf/export.ts +1029 -0
- package/src/pdf/import.ts +835 -0
- package/src/pdf/index.ts +29 -0
- package/src/pdf/styles.ts +180 -0
- package/src/pptx/index.ts +78 -0
- package/src/xlsx/index.ts +78 -0
|
@@ -0,0 +1,1136 @@
|
|
|
1
|
+
// src/pdf/export.ts
|
|
2
|
+
import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
|
|
3
|
+
import { docToMarkdown } from "@bendyline/squisq/doc";
|
|
4
|
+
|
|
5
|
+
// src/pdf/styles.ts
|
|
6
|
+
var PAGE_WIDTH_LETTER = 612;
|
|
7
|
+
var PAGE_HEIGHT_LETTER = 792;
|
|
8
|
+
var PAGE_WIDTH_A4 = 595.28;
|
|
9
|
+
var PAGE_HEIGHT_A4 = 841.89;
|
|
10
|
+
var DEFAULT_MARGIN = 72;
|
|
11
|
+
var DEFAULT_FONT_SIZE = 11;
|
|
12
|
+
var HEADING_SIZES = {
|
|
13
|
+
1: 24,
|
|
14
|
+
2: 20,
|
|
15
|
+
3: 16,
|
|
16
|
+
4: 14,
|
|
17
|
+
5: 13,
|
|
18
|
+
6: 12
|
|
19
|
+
};
|
|
20
|
+
var CODE_FONT_SIZE = 10;
|
|
21
|
+
var LINE_HEIGHT_FACTOR = 1.4;
|
|
22
|
+
var HEADING_SPACE_BEFORE = 18;
|
|
23
|
+
var HEADING_SPACE_AFTER = 6;
|
|
24
|
+
var PARAGRAPH_SPACING = 8;
|
|
25
|
+
var LIST_INDENT = 24;
|
|
26
|
+
var BULLET_CHAR = "\u2022";
|
|
27
|
+
var BLOCKQUOTE_INDENT = 24;
|
|
28
|
+
var BLOCKQUOTE_BAR_WIDTH = 3;
|
|
29
|
+
var COLOR_TEXT = { r: 0.12, g: 0.12, b: 0.14 };
|
|
30
|
+
var COLOR_HEADING = { r: 0.08, g: 0.08, b: 0.1 };
|
|
31
|
+
var COLOR_LINK = { r: 0.05, g: 0.27, b: 0.73 };
|
|
32
|
+
var COLOR_CODE_TEXT = { r: 0.2, g: 0.2, b: 0.22 };
|
|
33
|
+
var COLOR_BLOCKQUOTE_BAR = { r: 0.75, g: 0.75, b: 0.78 };
|
|
34
|
+
var COLOR_BLOCKQUOTE_TEXT = { r: 0.35, g: 0.35, b: 0.38 };
|
|
35
|
+
var COLOR_THEMATIC_BREAK = { r: 0.78, g: 0.78, b: 0.8 };
|
|
36
|
+
var COLOR_TABLE_BORDER = { r: 0.75, g: 0.75, b: 0.78 };
|
|
37
|
+
var COLOR_TABLE_HEADER_BG = { r: 0.93, g: 0.93, b: 0.95 };
|
|
38
|
+
var TABLE_CELL_PAD_X = 6;
|
|
39
|
+
var TABLE_CELL_PAD_Y = 4;
|
|
40
|
+
var TABLE_BORDER_WIDTH = 0.5;
|
|
41
|
+
var IMPORT_HEADING_MIN_SIZE = 13;
|
|
42
|
+
var IMPORT_HEADING_SIZE_RANGES = [
|
|
43
|
+
{ min: 22, depth: 1 },
|
|
44
|
+
{ min: 18, depth: 2 },
|
|
45
|
+
{ min: 15, depth: 3 },
|
|
46
|
+
{ min: 13.5, depth: 4 },
|
|
47
|
+
{ min: 12.5, depth: 5 },
|
|
48
|
+
{ min: 12, depth: 6 }
|
|
49
|
+
];
|
|
50
|
+
var IMPORT_PARAGRAPH_GAP = 4;
|
|
51
|
+
var IMPORT_BULLET_CHARS = /* @__PURE__ */ new Set([
|
|
52
|
+
"\u2022",
|
|
53
|
+
"\u2023",
|
|
54
|
+
"\u25E6",
|
|
55
|
+
"\u2043",
|
|
56
|
+
"-",
|
|
57
|
+
"*",
|
|
58
|
+
"\u2013",
|
|
59
|
+
"\u2014",
|
|
60
|
+
"\u25AA",
|
|
61
|
+
"\u25AB"
|
|
62
|
+
]);
|
|
63
|
+
var IMPORT_ORDERED_PREFIX = /^(\d+|[a-z]+|[ivxlcdm]+)[.)]\s/i;
|
|
64
|
+
var IMPORT_COLUMN_TOLERANCE = 8;
|
|
65
|
+
var IMPORT_TABLE_MIN_ROWS = 2;
|
|
66
|
+
var IMPORT_URL_PATTERN = /https?:\/\/[^\s)>\]]+/g;
|
|
67
|
+
|
|
68
|
+
// src/pdf/export.ts
|
|
69
|
+
async function markdownDocToPdf(doc, options = {}) {
|
|
70
|
+
const pdfDoc = await PDFDocument.create();
|
|
71
|
+
if (options.title) pdfDoc.setTitle(options.title);
|
|
72
|
+
if (options.author) pdfDoc.setAuthor(options.author);
|
|
73
|
+
pdfDoc.setCreationDate(/* @__PURE__ */ new Date());
|
|
74
|
+
pdfDoc.setModificationDate(/* @__PURE__ */ new Date());
|
|
75
|
+
const ctx = await createExportContext(pdfDoc, options);
|
|
76
|
+
renderBlocks(doc.children, ctx, 0);
|
|
77
|
+
const bytes = await pdfDoc.save();
|
|
78
|
+
return bytes.buffer;
|
|
79
|
+
}
|
|
80
|
+
async function docToPdf(doc, options = {}) {
|
|
81
|
+
const markdownDoc = docToMarkdown(doc);
|
|
82
|
+
return markdownDocToPdf(markdownDoc, options);
|
|
83
|
+
}
|
|
84
|
+
async function createExportContext(pdfDoc, options) {
|
|
85
|
+
const [regular, bold, italic, boldItalic, mono, monoBold] = await Promise.all([
|
|
86
|
+
pdfDoc.embedFont(StandardFonts.Helvetica),
|
|
87
|
+
pdfDoc.embedFont(StandardFonts.HelveticaBold),
|
|
88
|
+
pdfDoc.embedFont(StandardFonts.HelveticaOblique),
|
|
89
|
+
pdfDoc.embedFont(StandardFonts.HelveticaBoldOblique),
|
|
90
|
+
pdfDoc.embedFont(StandardFonts.Courier),
|
|
91
|
+
pdfDoc.embedFont(StandardFonts.CourierBold)
|
|
92
|
+
]);
|
|
93
|
+
const isA4 = options.pageSize === "a4";
|
|
94
|
+
const pageWidth = isA4 ? PAGE_WIDTH_A4 : PAGE_WIDTH_LETTER;
|
|
95
|
+
const pageHeight = isA4 ? PAGE_HEIGHT_A4 : PAGE_HEIGHT_LETTER;
|
|
96
|
+
const margin = options.margin ?? DEFAULT_MARGIN;
|
|
97
|
+
const fontSize = options.defaultFontSize ?? DEFAULT_FONT_SIZE;
|
|
98
|
+
const page = pdfDoc.addPage([pageWidth, pageHeight]);
|
|
99
|
+
return {
|
|
100
|
+
pdfDoc,
|
|
101
|
+
fonts: { regular, bold, italic, boldItalic, mono, monoBold },
|
|
102
|
+
pageWidth,
|
|
103
|
+
pageHeight,
|
|
104
|
+
margin,
|
|
105
|
+
fontSize,
|
|
106
|
+
page,
|
|
107
|
+
y: pageHeight - margin,
|
|
108
|
+
contentWidth: pageWidth - 2 * margin,
|
|
109
|
+
bottomY: margin
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function ensureSpace(ctx, needed) {
|
|
113
|
+
if (ctx.y - needed < ctx.bottomY) {
|
|
114
|
+
newPage(ctx);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function newPage(ctx) {
|
|
118
|
+
const page = ctx.pdfDoc.addPage([ctx.pageWidth, ctx.pageHeight]);
|
|
119
|
+
ctx.page = page;
|
|
120
|
+
ctx.y = ctx.pageHeight - ctx.margin;
|
|
121
|
+
}
|
|
122
|
+
function flattenInlines(nodes, ctx, state) {
|
|
123
|
+
const spans = [];
|
|
124
|
+
for (const node of nodes) {
|
|
125
|
+
switch (node.type) {
|
|
126
|
+
case "text": {
|
|
127
|
+
const font = state.code ? state.bold ? ctx.fonts.monoBold : ctx.fonts.mono : pickFont(ctx, state.bold, state.italic);
|
|
128
|
+
spans.push({
|
|
129
|
+
text: node.value,
|
|
130
|
+
font,
|
|
131
|
+
fontSize: state.code ? CODE_FONT_SIZE : ctx.fontSize,
|
|
132
|
+
color: state.code ? COLOR_CODE_TEXT : state.color ?? COLOR_TEXT,
|
|
133
|
+
link: state.link,
|
|
134
|
+
strikethrough: state.strikethrough
|
|
135
|
+
});
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case "strong":
|
|
139
|
+
spans.push(
|
|
140
|
+
...flattenInlines(node.children, ctx, { ...state, bold: true })
|
|
141
|
+
);
|
|
142
|
+
break;
|
|
143
|
+
case "emphasis":
|
|
144
|
+
spans.push(
|
|
145
|
+
...flattenInlines(node.children, ctx, { ...state, italic: true })
|
|
146
|
+
);
|
|
147
|
+
break;
|
|
148
|
+
case "delete":
|
|
149
|
+
spans.push(
|
|
150
|
+
...flattenInlines(node.children, ctx, {
|
|
151
|
+
...state,
|
|
152
|
+
strikethrough: true
|
|
153
|
+
})
|
|
154
|
+
);
|
|
155
|
+
break;
|
|
156
|
+
case "inlineCode": {
|
|
157
|
+
spans.push({
|
|
158
|
+
text: node.value,
|
|
159
|
+
font: ctx.fonts.mono,
|
|
160
|
+
fontSize: CODE_FONT_SIZE,
|
|
161
|
+
color: COLOR_CODE_TEXT,
|
|
162
|
+
link: state.link
|
|
163
|
+
});
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case "link": {
|
|
167
|
+
const linkNode = node;
|
|
168
|
+
spans.push(
|
|
169
|
+
...flattenInlines(linkNode.children, ctx, {
|
|
170
|
+
...state,
|
|
171
|
+
link: linkNode.url,
|
|
172
|
+
color: COLOR_LINK
|
|
173
|
+
})
|
|
174
|
+
);
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
case "image": {
|
|
178
|
+
const imgNode = node;
|
|
179
|
+
spans.push({
|
|
180
|
+
text: imgNode.alt ? `[Image: ${imgNode.alt}]` : "[Image]",
|
|
181
|
+
font: ctx.fonts.italic,
|
|
182
|
+
fontSize: ctx.fontSize,
|
|
183
|
+
color: COLOR_BLOCKQUOTE_TEXT
|
|
184
|
+
});
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case "break":
|
|
188
|
+
spans.push({
|
|
189
|
+
text: "\n",
|
|
190
|
+
font: ctx.fonts.regular,
|
|
191
|
+
fontSize: ctx.fontSize,
|
|
192
|
+
color: COLOR_TEXT
|
|
193
|
+
});
|
|
194
|
+
break;
|
|
195
|
+
case "htmlInline": {
|
|
196
|
+
const html = node.rawHtml;
|
|
197
|
+
if (html) {
|
|
198
|
+
spans.push({
|
|
199
|
+
text: html,
|
|
200
|
+
font: ctx.fonts.mono,
|
|
201
|
+
fontSize: CODE_FONT_SIZE,
|
|
202
|
+
color: COLOR_CODE_TEXT
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
case "inlineMath":
|
|
208
|
+
spans.push({
|
|
209
|
+
text: node.value,
|
|
210
|
+
font: ctx.fonts.mono,
|
|
211
|
+
fontSize: CODE_FONT_SIZE,
|
|
212
|
+
color: COLOR_CODE_TEXT
|
|
213
|
+
});
|
|
214
|
+
break;
|
|
215
|
+
case "footnoteReference": {
|
|
216
|
+
const ref = node;
|
|
217
|
+
spans.push({
|
|
218
|
+
text: `[${ref.identifier}]`,
|
|
219
|
+
font: ctx.fonts.regular,
|
|
220
|
+
fontSize: ctx.fontSize * 0.75,
|
|
221
|
+
color: COLOR_LINK
|
|
222
|
+
});
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
// linkReference, imageReference, textDirective — render children or identifier
|
|
226
|
+
default: {
|
|
227
|
+
const fallback = node;
|
|
228
|
+
if (fallback.children) {
|
|
229
|
+
spans.push(...flattenInlines(fallback.children, ctx, state));
|
|
230
|
+
} else if (fallback.value) {
|
|
231
|
+
spans.push({
|
|
232
|
+
text: fallback.value,
|
|
233
|
+
font: pickFont(ctx, state.bold, state.italic),
|
|
234
|
+
fontSize: ctx.fontSize,
|
|
235
|
+
color: state.color ?? COLOR_TEXT
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return spans;
|
|
243
|
+
}
|
|
244
|
+
function pickFont(ctx, bold, italic) {
|
|
245
|
+
if (bold && italic) return ctx.fonts.boldItalic;
|
|
246
|
+
if (bold) return ctx.fonts.bold;
|
|
247
|
+
if (italic) return ctx.fonts.italic;
|
|
248
|
+
return ctx.fonts.regular;
|
|
249
|
+
}
|
|
250
|
+
function drawSpans(spans, ctx, availableWidth, x0) {
|
|
251
|
+
if (spans.length === 0) return;
|
|
252
|
+
const lines = wrapSpans(spans, availableWidth);
|
|
253
|
+
for (const line of lines) {
|
|
254
|
+
const lineHeight = getLineHeight(line);
|
|
255
|
+
ensureSpace(ctx, lineHeight);
|
|
256
|
+
let x = x0;
|
|
257
|
+
for (const span of line) {
|
|
258
|
+
ctx.page.drawText(span.text, {
|
|
259
|
+
x,
|
|
260
|
+
y: ctx.y - span.fontSize,
|
|
261
|
+
// pdf-lib y is baseline
|
|
262
|
+
size: span.fontSize,
|
|
263
|
+
font: span.font,
|
|
264
|
+
color: rgb(span.color.r, span.color.g, span.color.b)
|
|
265
|
+
});
|
|
266
|
+
const textWidth = span.font.widthOfTextAtSize(span.text, span.fontSize);
|
|
267
|
+
if (span.link) {
|
|
268
|
+
ctx.page.drawLine({
|
|
269
|
+
start: { x, y: ctx.y - span.fontSize - 1 },
|
|
270
|
+
end: { x: x + textWidth, y: ctx.y - span.fontSize - 1 },
|
|
271
|
+
thickness: 0.5,
|
|
272
|
+
color: rgb(span.color.r, span.color.g, span.color.b)
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
if (span.strikethrough) {
|
|
276
|
+
const midY = ctx.y - span.fontSize * 0.6;
|
|
277
|
+
ctx.page.drawLine({
|
|
278
|
+
start: { x, y: midY },
|
|
279
|
+
end: { x: x + textWidth, y: midY },
|
|
280
|
+
thickness: 0.5,
|
|
281
|
+
color: rgb(span.color.r, span.color.g, span.color.b)
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
x += textWidth;
|
|
285
|
+
}
|
|
286
|
+
ctx.y -= lineHeight;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function wrapSpans(spans, maxWidth) {
|
|
290
|
+
const lines = [];
|
|
291
|
+
let currentLine = [];
|
|
292
|
+
let lineWidth = 0;
|
|
293
|
+
for (const span of spans) {
|
|
294
|
+
if (span.text === "\n") {
|
|
295
|
+
lines.push(currentLine);
|
|
296
|
+
currentLine = [];
|
|
297
|
+
lineWidth = 0;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const words = splitIntoWords(span.text);
|
|
301
|
+
for (const word of words) {
|
|
302
|
+
const wordWidth = span.font.widthOfTextAtSize(word, span.fontSize);
|
|
303
|
+
if (lineWidth + wordWidth > maxWidth && currentLine.length > 0 && word.trim().length > 0) {
|
|
304
|
+
lines.push(currentLine);
|
|
305
|
+
currentLine = [];
|
|
306
|
+
lineWidth = 0;
|
|
307
|
+
}
|
|
308
|
+
const trimmedWord = currentLine.length === 0 ? word.replace(/^\s+/, "") : word;
|
|
309
|
+
if (trimmedWord.length > 0) {
|
|
310
|
+
const tw = span.font.widthOfTextAtSize(trimmedWord, span.fontSize);
|
|
311
|
+
currentLine.push({ ...span, text: trimmedWord });
|
|
312
|
+
lineWidth += tw;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (currentLine.length > 0) {
|
|
317
|
+
lines.push(currentLine);
|
|
318
|
+
}
|
|
319
|
+
return lines.length > 0 ? lines : [[]];
|
|
320
|
+
}
|
|
321
|
+
function splitIntoWords(text) {
|
|
322
|
+
const tokens = [];
|
|
323
|
+
let current = "";
|
|
324
|
+
let inSpace = false;
|
|
325
|
+
for (const ch of text) {
|
|
326
|
+
const isSpace = ch === " " || ch === " ";
|
|
327
|
+
if (isSpace !== inSpace && current.length > 0) {
|
|
328
|
+
tokens.push(current);
|
|
329
|
+
current = "";
|
|
330
|
+
}
|
|
331
|
+
current += ch;
|
|
332
|
+
inSpace = isSpace;
|
|
333
|
+
}
|
|
334
|
+
if (current.length > 0) tokens.push(current);
|
|
335
|
+
return tokens;
|
|
336
|
+
}
|
|
337
|
+
function getLineHeight(line) {
|
|
338
|
+
let maxSize = 0;
|
|
339
|
+
for (const span of line) {
|
|
340
|
+
if (span.fontSize > maxSize) maxSize = span.fontSize;
|
|
341
|
+
}
|
|
342
|
+
return (maxSize || DEFAULT_FONT_SIZE) * LINE_HEIGHT_FACTOR;
|
|
343
|
+
}
|
|
344
|
+
function renderBlocks(nodes, ctx, extraIndent) {
|
|
345
|
+
for (const node of nodes) {
|
|
346
|
+
renderBlock(node, ctx, extraIndent);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function renderBlock(node, ctx, extraIndent) {
|
|
350
|
+
switch (node.type) {
|
|
351
|
+
case "heading":
|
|
352
|
+
renderHeading(node, ctx, extraIndent);
|
|
353
|
+
break;
|
|
354
|
+
case "paragraph":
|
|
355
|
+
renderParagraph(node, ctx, extraIndent);
|
|
356
|
+
break;
|
|
357
|
+
case "blockquote":
|
|
358
|
+
renderBlockquote(node, ctx, extraIndent);
|
|
359
|
+
break;
|
|
360
|
+
case "list":
|
|
361
|
+
renderList(node, ctx, extraIndent, 0);
|
|
362
|
+
break;
|
|
363
|
+
case "code":
|
|
364
|
+
renderCodeBlock(node, ctx, extraIndent);
|
|
365
|
+
break;
|
|
366
|
+
case "table":
|
|
367
|
+
renderTable(node, ctx, extraIndent);
|
|
368
|
+
break;
|
|
369
|
+
case "thematicBreak":
|
|
370
|
+
renderThematicBreak(ctx, extraIndent);
|
|
371
|
+
break;
|
|
372
|
+
case "htmlBlock":
|
|
373
|
+
renderHtmlBlock(node, ctx, extraIndent);
|
|
374
|
+
break;
|
|
375
|
+
case "math":
|
|
376
|
+
renderMathBlock(node, ctx, extraIndent);
|
|
377
|
+
break;
|
|
378
|
+
case "footnoteDefinition":
|
|
379
|
+
renderFootnoteDefinition(node, ctx, extraIndent);
|
|
380
|
+
break;
|
|
381
|
+
default:
|
|
382
|
+
renderFallbackBlock(node, ctx, extraIndent);
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
function renderHeading(node, ctx, extraIndent) {
|
|
387
|
+
const origFontSize = ctx.fontSize;
|
|
388
|
+
ctx.fontSize = HEADING_SIZES[node.depth] ?? DEFAULT_FONT_SIZE;
|
|
389
|
+
ctx.y -= HEADING_SPACE_BEFORE;
|
|
390
|
+
const x0 = ctx.margin + extraIndent;
|
|
391
|
+
const w = ctx.contentWidth - extraIndent;
|
|
392
|
+
const spans = flattenInlines(node.children, ctx, {
|
|
393
|
+
bold: true,
|
|
394
|
+
italic: false,
|
|
395
|
+
code: false,
|
|
396
|
+
color: COLOR_HEADING
|
|
397
|
+
});
|
|
398
|
+
drawSpans(spans, ctx, w, x0);
|
|
399
|
+
ctx.y -= HEADING_SPACE_AFTER;
|
|
400
|
+
ctx.fontSize = origFontSize;
|
|
401
|
+
}
|
|
402
|
+
function renderParagraph(node, ctx, extraIndent, colorOverride) {
|
|
403
|
+
const x0 = ctx.margin + extraIndent;
|
|
404
|
+
const w = ctx.contentWidth - extraIndent;
|
|
405
|
+
const spans = flattenInlines(node.children, ctx, {
|
|
406
|
+
bold: false,
|
|
407
|
+
italic: false,
|
|
408
|
+
code: false,
|
|
409
|
+
color: colorOverride
|
|
410
|
+
});
|
|
411
|
+
drawSpans(spans, ctx, w, x0);
|
|
412
|
+
ctx.y -= PARAGRAPH_SPACING;
|
|
413
|
+
}
|
|
414
|
+
function renderBlockquote(node, ctx, extraIndent) {
|
|
415
|
+
const barX = ctx.margin + extraIndent + 4;
|
|
416
|
+
const indent = extraIndent + BLOCKQUOTE_INDENT;
|
|
417
|
+
const startY = ctx.y;
|
|
418
|
+
for (const child of node.children) {
|
|
419
|
+
if (child.type === "paragraph") {
|
|
420
|
+
renderParagraph(child, ctx, indent, COLOR_BLOCKQUOTE_TEXT);
|
|
421
|
+
} else {
|
|
422
|
+
renderBlock(child, ctx, indent);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
const endY = ctx.y + PARAGRAPH_SPACING;
|
|
426
|
+
if (startY > endY) {
|
|
427
|
+
ctx.page.drawRectangle({
|
|
428
|
+
x: barX,
|
|
429
|
+
y: endY,
|
|
430
|
+
width: BLOCKQUOTE_BAR_WIDTH,
|
|
431
|
+
height: startY - endY,
|
|
432
|
+
color: rgb(COLOR_BLOCKQUOTE_BAR.r, COLOR_BLOCKQUOTE_BAR.g, COLOR_BLOCKQUOTE_BAR.b)
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
ctx.y -= PARAGRAPH_SPACING;
|
|
436
|
+
}
|
|
437
|
+
function renderList(node, ctx, extraIndent, depth) {
|
|
438
|
+
const ordered = node.ordered ?? false;
|
|
439
|
+
let counter = node.start ?? 1;
|
|
440
|
+
for (const child of node.children) {
|
|
441
|
+
if (child.type === "listItem") {
|
|
442
|
+
renderListItem(child, ctx, extraIndent, depth, ordered, counter);
|
|
443
|
+
if (ordered) counter++;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function renderListItem(item, ctx, extraIndent, depth, ordered, counter) {
|
|
448
|
+
const indent = extraIndent + depth * LIST_INDENT;
|
|
449
|
+
const bullet = ordered ? `${counter}.` : BULLET_CHAR;
|
|
450
|
+
const bulletFont = ctx.fonts.regular;
|
|
451
|
+
const bulletWidth = bulletFont.widthOfTextAtSize(bullet + " ", ctx.fontSize);
|
|
452
|
+
const lineHeight = ctx.fontSize * LINE_HEIGHT_FACTOR;
|
|
453
|
+
ensureSpace(ctx, lineHeight);
|
|
454
|
+
ctx.page.drawText(bullet, {
|
|
455
|
+
x: ctx.margin + indent,
|
|
456
|
+
y: ctx.y - ctx.fontSize,
|
|
457
|
+
size: ctx.fontSize,
|
|
458
|
+
font: bulletFont,
|
|
459
|
+
color: rgb(COLOR_TEXT.r, COLOR_TEXT.g, COLOR_TEXT.b)
|
|
460
|
+
});
|
|
461
|
+
const textIndent = indent + bulletWidth + 4;
|
|
462
|
+
const textWidth = ctx.contentWidth - textIndent;
|
|
463
|
+
let isFirstChild = true;
|
|
464
|
+
for (const child of item.children) {
|
|
465
|
+
if (child.type === "paragraph" && isFirstChild) {
|
|
466
|
+
const spans = flattenInlines(child.children, ctx, {
|
|
467
|
+
bold: false,
|
|
468
|
+
italic: false,
|
|
469
|
+
code: false
|
|
470
|
+
});
|
|
471
|
+
drawSpans(spans, ctx, textWidth, ctx.margin + textIndent);
|
|
472
|
+
ctx.y -= PARAGRAPH_SPACING / 2;
|
|
473
|
+
} else if (child.type === "list") {
|
|
474
|
+
renderList(child, ctx, extraIndent, depth + 1);
|
|
475
|
+
} else {
|
|
476
|
+
renderBlock(child, ctx, textIndent);
|
|
477
|
+
}
|
|
478
|
+
isFirstChild = false;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
function renderCodeBlock(node, ctx, extraIndent) {
|
|
482
|
+
const x0 = ctx.margin + extraIndent;
|
|
483
|
+
const _w = ctx.contentWidth - extraIndent;
|
|
484
|
+
const lines = node.value.split("\n");
|
|
485
|
+
const lineH = CODE_FONT_SIZE * LINE_HEIGHT_FACTOR;
|
|
486
|
+
const totalHeight = lines.length * lineH + 12;
|
|
487
|
+
ensureSpace(ctx, Math.min(totalHeight, ctx.y - ctx.bottomY));
|
|
488
|
+
const _bgTop = ctx.y;
|
|
489
|
+
const _bgStartY = ctx.y;
|
|
490
|
+
ctx.y -= 6;
|
|
491
|
+
for (const line of lines) {
|
|
492
|
+
ensureSpace(ctx, lineH);
|
|
493
|
+
if (line.length > 0) {
|
|
494
|
+
ctx.page.drawText(line, {
|
|
495
|
+
x: x0 + 8,
|
|
496
|
+
y: ctx.y - CODE_FONT_SIZE,
|
|
497
|
+
size: CODE_FONT_SIZE,
|
|
498
|
+
font: ctx.fonts.mono,
|
|
499
|
+
color: rgb(COLOR_CODE_TEXT.r, COLOR_CODE_TEXT.g, COLOR_CODE_TEXT.b)
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
ctx.y -= lineH;
|
|
503
|
+
}
|
|
504
|
+
ctx.y -= 6;
|
|
505
|
+
ctx.y -= PARAGRAPH_SPACING;
|
|
506
|
+
}
|
|
507
|
+
function renderTable(node, ctx, extraIndent) {
|
|
508
|
+
if (node.children.length === 0) return;
|
|
509
|
+
const x0 = ctx.margin + extraIndent;
|
|
510
|
+
const tableWidth = ctx.contentWidth - extraIndent;
|
|
511
|
+
const numCols = Math.max(
|
|
512
|
+
...node.children.map((row) => row.children.length)
|
|
513
|
+
);
|
|
514
|
+
if (numCols === 0) return;
|
|
515
|
+
const colWidth = tableWidth / numCols;
|
|
516
|
+
for (let rowIdx = 0; rowIdx < node.children.length; rowIdx++) {
|
|
517
|
+
const row = node.children[rowIdx];
|
|
518
|
+
const isHeader = rowIdx === 0 && row.children.some((c) => c.isHeader);
|
|
519
|
+
const rowCellHeights = row.children.map((cell) => {
|
|
520
|
+
const cellNode = cell;
|
|
521
|
+
const spans = flattenInlines(cellNode.children, ctx, {
|
|
522
|
+
bold: isHeader,
|
|
523
|
+
italic: false,
|
|
524
|
+
code: false
|
|
525
|
+
});
|
|
526
|
+
const lines = wrapSpans(spans, colWidth - 2 * TABLE_CELL_PAD_X);
|
|
527
|
+
return lines.length * ctx.fontSize * LINE_HEIGHT_FACTOR + 2 * TABLE_CELL_PAD_Y;
|
|
528
|
+
});
|
|
529
|
+
const rowHeight = Math.max(
|
|
530
|
+
...rowCellHeights,
|
|
531
|
+
ctx.fontSize * LINE_HEIGHT_FACTOR + 2 * TABLE_CELL_PAD_Y
|
|
532
|
+
);
|
|
533
|
+
ensureSpace(ctx, rowHeight);
|
|
534
|
+
if (isHeader) {
|
|
535
|
+
ctx.page.drawRectangle({
|
|
536
|
+
x: x0,
|
|
537
|
+
y: ctx.y - rowHeight,
|
|
538
|
+
width: tableWidth,
|
|
539
|
+
height: rowHeight,
|
|
540
|
+
color: rgb(COLOR_TABLE_HEADER_BG.r, COLOR_TABLE_HEADER_BG.g, COLOR_TABLE_HEADER_BG.b)
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
for (let colIdx = 0; colIdx < numCols; colIdx++) {
|
|
544
|
+
const cellX = x0 + colIdx * colWidth;
|
|
545
|
+
ctx.page.drawRectangle({
|
|
546
|
+
x: cellX,
|
|
547
|
+
y: ctx.y - rowHeight,
|
|
548
|
+
width: colWidth,
|
|
549
|
+
height: rowHeight,
|
|
550
|
+
borderColor: rgb(COLOR_TABLE_BORDER.r, COLOR_TABLE_BORDER.g, COLOR_TABLE_BORDER.b),
|
|
551
|
+
borderWidth: TABLE_BORDER_WIDTH
|
|
552
|
+
});
|
|
553
|
+
if (colIdx < row.children.length) {
|
|
554
|
+
const cellNode = row.children[colIdx];
|
|
555
|
+
const spans = flattenInlines(cellNode.children, ctx, {
|
|
556
|
+
bold: isHeader,
|
|
557
|
+
italic: false,
|
|
558
|
+
code: false
|
|
559
|
+
});
|
|
560
|
+
const savedY = ctx.y;
|
|
561
|
+
ctx.y = ctx.y - TABLE_CELL_PAD_Y;
|
|
562
|
+
const cellAvailableWidth = colWidth - 2 * TABLE_CELL_PAD_X;
|
|
563
|
+
const wrappedLines = wrapSpans(spans, cellAvailableWidth);
|
|
564
|
+
for (const line of wrappedLines) {
|
|
565
|
+
let lx = cellX + TABLE_CELL_PAD_X;
|
|
566
|
+
for (const span of line) {
|
|
567
|
+
ctx.page.drawText(span.text, {
|
|
568
|
+
x: lx,
|
|
569
|
+
y: ctx.y - span.fontSize,
|
|
570
|
+
size: span.fontSize,
|
|
571
|
+
font: span.font,
|
|
572
|
+
color: rgb(span.color.r, span.color.g, span.color.b)
|
|
573
|
+
});
|
|
574
|
+
lx += span.font.widthOfTextAtSize(span.text, span.fontSize);
|
|
575
|
+
}
|
|
576
|
+
ctx.y -= getLineHeight(line);
|
|
577
|
+
}
|
|
578
|
+
ctx.y = savedY;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
ctx.y -= rowHeight;
|
|
582
|
+
}
|
|
583
|
+
ctx.y -= PARAGRAPH_SPACING;
|
|
584
|
+
}
|
|
585
|
+
function renderThematicBreak(ctx, extraIndent) {
|
|
586
|
+
ctx.y -= PARAGRAPH_SPACING;
|
|
587
|
+
ensureSpace(ctx, 10);
|
|
588
|
+
const x0 = ctx.margin + extraIndent;
|
|
589
|
+
const x1 = ctx.margin + ctx.contentWidth;
|
|
590
|
+
ctx.page.drawLine({
|
|
591
|
+
start: { x: x0, y: ctx.y },
|
|
592
|
+
end: { x: x1, y: ctx.y },
|
|
593
|
+
thickness: 1,
|
|
594
|
+
color: rgb(COLOR_THEMATIC_BREAK.r, COLOR_THEMATIC_BREAK.g, COLOR_THEMATIC_BREAK.b)
|
|
595
|
+
});
|
|
596
|
+
ctx.y -= PARAGRAPH_SPACING;
|
|
597
|
+
}
|
|
598
|
+
function renderHtmlBlock(node, ctx, extraIndent) {
|
|
599
|
+
if (!node.rawHtml) return;
|
|
600
|
+
const lines = node.rawHtml.split("\n");
|
|
601
|
+
const x0 = ctx.margin + extraIndent;
|
|
602
|
+
for (const line of lines) {
|
|
603
|
+
if (line.trim().length === 0) continue;
|
|
604
|
+
const lineH = CODE_FONT_SIZE * LINE_HEIGHT_FACTOR;
|
|
605
|
+
ensureSpace(ctx, lineH);
|
|
606
|
+
ctx.page.drawText(line, {
|
|
607
|
+
x: x0,
|
|
608
|
+
y: ctx.y - CODE_FONT_SIZE,
|
|
609
|
+
size: CODE_FONT_SIZE,
|
|
610
|
+
font: ctx.fonts.mono,
|
|
611
|
+
color: rgb(COLOR_CODE_TEXT.r, COLOR_CODE_TEXT.g, COLOR_CODE_TEXT.b)
|
|
612
|
+
});
|
|
613
|
+
ctx.y -= lineH;
|
|
614
|
+
}
|
|
615
|
+
ctx.y -= PARAGRAPH_SPACING;
|
|
616
|
+
}
|
|
617
|
+
function renderMathBlock(node, ctx, extraIndent) {
|
|
618
|
+
const lines = node.value.split("\n");
|
|
619
|
+
const x0 = ctx.margin + extraIndent;
|
|
620
|
+
for (const line of lines) {
|
|
621
|
+
const lineH = CODE_FONT_SIZE * LINE_HEIGHT_FACTOR;
|
|
622
|
+
ensureSpace(ctx, lineH);
|
|
623
|
+
if (line.length > 0) {
|
|
624
|
+
ctx.page.drawText(line, {
|
|
625
|
+
x: x0,
|
|
626
|
+
y: ctx.y - CODE_FONT_SIZE,
|
|
627
|
+
size: CODE_FONT_SIZE,
|
|
628
|
+
font: ctx.fonts.mono,
|
|
629
|
+
color: rgb(COLOR_CODE_TEXT.r, COLOR_CODE_TEXT.g, COLOR_CODE_TEXT.b)
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
ctx.y -= lineH;
|
|
633
|
+
}
|
|
634
|
+
ctx.y -= PARAGRAPH_SPACING;
|
|
635
|
+
}
|
|
636
|
+
function renderFootnoteDefinition(node, ctx, extraIndent) {
|
|
637
|
+
const label = `[${node.identifier}]`;
|
|
638
|
+
const lineH = ctx.fontSize * LINE_HEIGHT_FACTOR;
|
|
639
|
+
ensureSpace(ctx, lineH);
|
|
640
|
+
ctx.page.drawText(label, {
|
|
641
|
+
x: ctx.margin + extraIndent,
|
|
642
|
+
y: ctx.y - ctx.fontSize * 0.75,
|
|
643
|
+
size: ctx.fontSize * 0.75,
|
|
644
|
+
font: ctx.fonts.bold,
|
|
645
|
+
color: rgb(COLOR_LINK.r, COLOR_LINK.g, COLOR_LINK.b)
|
|
646
|
+
});
|
|
647
|
+
renderBlocks(node.children, ctx, extraIndent + LIST_INDENT);
|
|
648
|
+
}
|
|
649
|
+
function renderFallbackBlock(node, ctx, extraIndent) {
|
|
650
|
+
const fallback = node;
|
|
651
|
+
if (fallback.children && Array.isArray(fallback.children)) {
|
|
652
|
+
if (fallback.children.length > 0 && typeof fallback.children[0]?.type === "string") {
|
|
653
|
+
const firstType = fallback.children[0].type;
|
|
654
|
+
const inlineTypes = /* @__PURE__ */ new Set([
|
|
655
|
+
"text",
|
|
656
|
+
"strong",
|
|
657
|
+
"emphasis",
|
|
658
|
+
"delete",
|
|
659
|
+
"inlineCode",
|
|
660
|
+
"link",
|
|
661
|
+
"image",
|
|
662
|
+
"break"
|
|
663
|
+
]);
|
|
664
|
+
if (inlineTypes.has(firstType)) {
|
|
665
|
+
const spans = flattenInlines(fallback.children, ctx, {
|
|
666
|
+
bold: false,
|
|
667
|
+
italic: false,
|
|
668
|
+
code: false
|
|
669
|
+
});
|
|
670
|
+
drawSpans(spans, ctx, ctx.contentWidth - extraIndent, ctx.margin + extraIndent);
|
|
671
|
+
ctx.y -= PARAGRAPH_SPACING;
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
renderBlocks(fallback.children, ctx, extraIndent);
|
|
676
|
+
} else if (fallback.value && typeof fallback.value === "string") {
|
|
677
|
+
const lineH = ctx.fontSize * LINE_HEIGHT_FACTOR;
|
|
678
|
+
ensureSpace(ctx, lineH);
|
|
679
|
+
ctx.page.drawText(fallback.value, {
|
|
680
|
+
x: ctx.margin + extraIndent,
|
|
681
|
+
y: ctx.y - ctx.fontSize,
|
|
682
|
+
size: ctx.fontSize,
|
|
683
|
+
font: ctx.fonts.regular,
|
|
684
|
+
color: rgb(COLOR_TEXT.r, COLOR_TEXT.g, COLOR_TEXT.b)
|
|
685
|
+
});
|
|
686
|
+
ctx.y -= lineH + PARAGRAPH_SPACING;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// src/pdf/import.ts
|
|
691
|
+
import { markdownToDoc } from "@bendyline/squisq/doc";
|
|
692
|
+
async function pdfToMarkdownDoc(data, options = {}) {
|
|
693
|
+
const bytes = data instanceof Blob ? new Uint8Array(await data.arrayBuffer()) : data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
694
|
+
const textLines = await extractTextLines(bytes);
|
|
695
|
+
if (textLines.length === 0) {
|
|
696
|
+
return { type: "document", children: [] };
|
|
697
|
+
}
|
|
698
|
+
const bodySize = options.bodyFontSize ?? detectBodyFontSize(textLines);
|
|
699
|
+
const blocks = classifyLines(textLines, bodySize, options);
|
|
700
|
+
return { type: "document", children: blocks };
|
|
701
|
+
}
|
|
702
|
+
async function pdfToDoc(data, options = {}) {
|
|
703
|
+
const markdownDoc = await pdfToMarkdownDoc(data, options);
|
|
704
|
+
return markdownToDoc(markdownDoc);
|
|
705
|
+
}
|
|
706
|
+
function configurePdfWorker(workerSrc) {
|
|
707
|
+
_workerSrc = workerSrc;
|
|
708
|
+
}
|
|
709
|
+
var _workerSrc;
|
|
710
|
+
async function applyWorkerConfig(pdfjsLib) {
|
|
711
|
+
if (!pdfjsLib.GlobalWorkerOptions) return;
|
|
712
|
+
if (pdfjsLib.GlobalWorkerOptions.workerSrc) return;
|
|
713
|
+
if (_workerSrc) {
|
|
714
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = _workerSrc;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
async function extractTextLines(data) {
|
|
718
|
+
let pdfjsLib;
|
|
719
|
+
try {
|
|
720
|
+
pdfjsLib = await import("pdfjs-dist/legacy/build/pdf.mjs");
|
|
721
|
+
} catch {
|
|
722
|
+
pdfjsLib = await import("pdfjs-dist");
|
|
723
|
+
}
|
|
724
|
+
await applyWorkerConfig(pdfjsLib);
|
|
725
|
+
const loadingTask = pdfjsLib.getDocument({
|
|
726
|
+
data,
|
|
727
|
+
isEvalSupported: false,
|
|
728
|
+
useSystemFonts: true
|
|
729
|
+
});
|
|
730
|
+
const pdf = await loadingTask.promise;
|
|
731
|
+
const allLines = [];
|
|
732
|
+
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
|
|
733
|
+
const page = await pdf.getPage(pageNum);
|
|
734
|
+
const content = await page.getTextContent();
|
|
735
|
+
const styleMap = content.styles || {};
|
|
736
|
+
const items = [];
|
|
737
|
+
for (const item of content.items) {
|
|
738
|
+
if (!item.str || item.str.trim().length === 0) continue;
|
|
739
|
+
const transform = item.transform || [1, 0, 0, 1, 0, 0];
|
|
740
|
+
const x = transform[4];
|
|
741
|
+
const y = transform[5];
|
|
742
|
+
const height = Math.abs(transform[3]) || item.height || 12;
|
|
743
|
+
const width = item.width || 0;
|
|
744
|
+
const fontName = item.fontName || "";
|
|
745
|
+
const fontFamily = styleMap[fontName]?.fontFamily || "";
|
|
746
|
+
items.push({ str: item.str, x, y, width, height, fontName, fontFamily });
|
|
747
|
+
}
|
|
748
|
+
const lineMap = /* @__PURE__ */ new Map();
|
|
749
|
+
for (const item of items) {
|
|
750
|
+
const roundedY = Math.round(item.y * 2) / 2;
|
|
751
|
+
let foundKey;
|
|
752
|
+
for (const key of lineMap.keys()) {
|
|
753
|
+
if (Math.abs(key - roundedY) < 2) {
|
|
754
|
+
foundKey = key;
|
|
755
|
+
break;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
if (foundKey !== void 0) {
|
|
759
|
+
lineMap.get(foundKey).push(item);
|
|
760
|
+
} else {
|
|
761
|
+
lineMap.set(roundedY, [item]);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
const sortedKeys = [...lineMap.keys()].sort((a, b) => b - a);
|
|
765
|
+
for (const key of sortedKeys) {
|
|
766
|
+
const lineItems = lineMap.get(key).sort((a, b) => a.x - b.x);
|
|
767
|
+
const fontSizes = lineItems.map((i) => i.height);
|
|
768
|
+
const fontSize = mode(fontSizes) || 12;
|
|
769
|
+
const fontFamilies = lineItems.map((i) => i.fontFamily);
|
|
770
|
+
const fontFamily = modeStr(fontFamilies) || "";
|
|
771
|
+
const fontNames = lineItems.map((i) => i.fontName);
|
|
772
|
+
const fontName = modeStr(fontNames) || "";
|
|
773
|
+
const minX = Math.min(...lineItems.map((i) => i.x));
|
|
774
|
+
const text = lineItems.map((i) => i.str).join(" ");
|
|
775
|
+
allLines.push({
|
|
776
|
+
items: lineItems,
|
|
777
|
+
y: key,
|
|
778
|
+
page: pageNum - 1,
|
|
779
|
+
fontSize,
|
|
780
|
+
fontFamily,
|
|
781
|
+
fontName,
|
|
782
|
+
minX,
|
|
783
|
+
text
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return allLines;
|
|
788
|
+
}
|
|
789
|
+
function detectBodyFontSize(lines) {
|
|
790
|
+
const sizes = lines.map((l) => Math.round(l.fontSize * 2) / 2);
|
|
791
|
+
return mode(sizes) || DEFAULT_FONT_SIZE;
|
|
792
|
+
}
|
|
793
|
+
function mode(arr) {
|
|
794
|
+
const freq = /* @__PURE__ */ new Map();
|
|
795
|
+
for (const v of arr) freq.set(v, (freq.get(v) || 0) + 1);
|
|
796
|
+
let maxCount = 0;
|
|
797
|
+
let maxVal = 0;
|
|
798
|
+
for (const [v, c] of freq) {
|
|
799
|
+
if (c > maxCount) {
|
|
800
|
+
maxCount = c;
|
|
801
|
+
maxVal = v;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return maxVal;
|
|
805
|
+
}
|
|
806
|
+
function modeStr(arr) {
|
|
807
|
+
const freq = /* @__PURE__ */ new Map();
|
|
808
|
+
for (const v of arr) freq.set(v, (freq.get(v) || 0) + 1);
|
|
809
|
+
let maxCount = 0;
|
|
810
|
+
let maxVal = "";
|
|
811
|
+
for (const [v, c] of freq) {
|
|
812
|
+
if (c > maxCount) {
|
|
813
|
+
maxCount = c;
|
|
814
|
+
maxVal = v;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
return maxVal;
|
|
818
|
+
}
|
|
819
|
+
function classifyLines(lines, bodySize, options) {
|
|
820
|
+
const blocks = [];
|
|
821
|
+
const detectTables = options.detectTables !== false;
|
|
822
|
+
const detectCodeBlocks = options.detectCodeBlocks !== false;
|
|
823
|
+
const detectBlockquotes = options.detectBlockquotes !== false;
|
|
824
|
+
const _detectLinks = options.detectLinks !== false;
|
|
825
|
+
const leftMargins = lines.map((l) => Math.round(l.minX));
|
|
826
|
+
const typicalLeftMargin = mode(leftMargins) || 72;
|
|
827
|
+
let i = 0;
|
|
828
|
+
while (i < lines.length) {
|
|
829
|
+
const line = lines[i];
|
|
830
|
+
if (line.fontSize >= IMPORT_HEADING_MIN_SIZE && line.fontSize > bodySize + 1) {
|
|
831
|
+
const depth = sizeToHeadingDepth(line.fontSize);
|
|
832
|
+
blocks.push({
|
|
833
|
+
type: "heading",
|
|
834
|
+
depth,
|
|
835
|
+
children: buildInlineNodes(line, options)
|
|
836
|
+
});
|
|
837
|
+
i++;
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
if (detectCodeBlocks && isMonospaceLine(line)) {
|
|
841
|
+
const codeLines = [];
|
|
842
|
+
while (i < lines.length && isMonospaceLine(lines[i])) {
|
|
843
|
+
codeLines.push(lines[i].text);
|
|
844
|
+
i++;
|
|
845
|
+
}
|
|
846
|
+
blocks.push({
|
|
847
|
+
type: "code",
|
|
848
|
+
value: codeLines.join("\n")
|
|
849
|
+
});
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
if (detectTables && i + 1 < lines.length) {
|
|
853
|
+
const tableLines = tryDetectTable(lines, i, typicalLeftMargin);
|
|
854
|
+
if (tableLines > 0) {
|
|
855
|
+
const table = buildTable(lines.slice(i, i + tableLines), options);
|
|
856
|
+
if (table) {
|
|
857
|
+
blocks.push(table);
|
|
858
|
+
i += tableLines;
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
const bulletMatch = tryMatchBullet(line.text);
|
|
864
|
+
const orderedMatch = line.text.match(IMPORT_ORDERED_PREFIX);
|
|
865
|
+
if (bulletMatch || orderedMatch) {
|
|
866
|
+
const listResult = consumeList(lines, i, typicalLeftMargin, bodySize, options);
|
|
867
|
+
blocks.push(listResult.list);
|
|
868
|
+
i = listResult.nextIndex;
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
if (detectBlockquotes && line.minX > typicalLeftMargin + 20) {
|
|
872
|
+
const quoteLines = [];
|
|
873
|
+
while (i < lines.length && lines[i].minX > typicalLeftMargin + 20 && !isMonospaceLine(lines[i]) && lines[i].fontSize <= bodySize + 1) {
|
|
874
|
+
quoteLines.push(lines[i]);
|
|
875
|
+
i++;
|
|
876
|
+
}
|
|
877
|
+
const quoteBlocks = quoteLines.map(
|
|
878
|
+
(ql) => ({
|
|
879
|
+
type: "paragraph",
|
|
880
|
+
children: buildInlineNodes(ql, options)
|
|
881
|
+
})
|
|
882
|
+
);
|
|
883
|
+
blocks.push({
|
|
884
|
+
type: "blockquote",
|
|
885
|
+
children: quoteBlocks
|
|
886
|
+
});
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
const paraLines = [line];
|
|
890
|
+
i++;
|
|
891
|
+
while (i < lines.length) {
|
|
892
|
+
const next = lines[i];
|
|
893
|
+
if (next.page === line.page && Math.abs(next.fontSize - bodySize) <= 1 && !isMonospaceLine(next) && next.minX <= typicalLeftMargin + 15 && !tryMatchBullet(next.text) && !next.text.match(IMPORT_ORDERED_PREFIX)) {
|
|
894
|
+
const yGap = paraLines[paraLines.length - 1].y - next.y;
|
|
895
|
+
const lineHeight = bodySize * 1.6;
|
|
896
|
+
if (yGap > 0 && yGap < lineHeight + IMPORT_PARAGRAPH_GAP) {
|
|
897
|
+
paraLines.push(next);
|
|
898
|
+
i++;
|
|
899
|
+
} else {
|
|
900
|
+
break;
|
|
901
|
+
}
|
|
902
|
+
} else {
|
|
903
|
+
break;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
const allInlines = [];
|
|
907
|
+
for (let j = 0; j < paraLines.length; j++) {
|
|
908
|
+
if (j > 0) {
|
|
909
|
+
allInlines.push({ type: "text", value: " " });
|
|
910
|
+
}
|
|
911
|
+
allInlines.push(...buildInlineNodes(paraLines[j], options));
|
|
912
|
+
}
|
|
913
|
+
if (allInlines.length > 0) {
|
|
914
|
+
blocks.push({
|
|
915
|
+
type: "paragraph",
|
|
916
|
+
children: mergeAdjacentText(allInlines)
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return blocks;
|
|
921
|
+
}
|
|
922
|
+
function sizeToHeadingDepth(fontSize) {
|
|
923
|
+
for (const range of IMPORT_HEADING_SIZE_RANGES) {
|
|
924
|
+
if (fontSize >= range.min) return range.depth;
|
|
925
|
+
}
|
|
926
|
+
return 6;
|
|
927
|
+
}
|
|
928
|
+
function isMonospaceLine(line) {
|
|
929
|
+
return isMonospaceFamily(line.fontFamily) || isMonospaceName(line.fontName);
|
|
930
|
+
}
|
|
931
|
+
function isMonospaceItem(item) {
|
|
932
|
+
return isMonospaceFamily(item.fontFamily) || isMonospaceName(item.fontName);
|
|
933
|
+
}
|
|
934
|
+
function isMonospaceFamily(fontFamily) {
|
|
935
|
+
const lower = fontFamily.toLowerCase();
|
|
936
|
+
return lower === "monospace" || lower.includes("monospace");
|
|
937
|
+
}
|
|
938
|
+
function isMonospaceName(fontName) {
|
|
939
|
+
const lower = fontName.toLowerCase();
|
|
940
|
+
return lower.includes("courier") || lower.includes("mono") || lower.includes("consolas") || lower.includes("menlo") || lower.includes("inconsolata") || lower.includes("firacode") || lower.includes("source code") || lower.includes("dejavu sans mono");
|
|
941
|
+
}
|
|
942
|
+
function isBoldFont(fontName) {
|
|
943
|
+
const lower = fontName.toLowerCase();
|
|
944
|
+
return lower.includes("bold") || lower.includes("black") || lower.includes("heavy");
|
|
945
|
+
}
|
|
946
|
+
function isItalicFont(fontName) {
|
|
947
|
+
const lower = fontName.toLowerCase();
|
|
948
|
+
return lower.includes("italic") || lower.includes("oblique") || lower.includes("slanted");
|
|
949
|
+
}
|
|
950
|
+
function buildInlineNodes(line, options) {
|
|
951
|
+
const nodes = [];
|
|
952
|
+
const detectLinksOpt = options.detectLinks !== false;
|
|
953
|
+
for (const item of line.items) {
|
|
954
|
+
const text = item.str;
|
|
955
|
+
if (!text || text.trim().length === 0) continue;
|
|
956
|
+
const bold = isBoldFont(item.fontName);
|
|
957
|
+
const italic = isItalicFont(item.fontName);
|
|
958
|
+
const mono = isMonospaceItem(item);
|
|
959
|
+
let inlineNodes;
|
|
960
|
+
if (mono) {
|
|
961
|
+
inlineNodes = [{ type: "inlineCode", value: text }];
|
|
962
|
+
} else if (detectLinksOpt) {
|
|
963
|
+
inlineNodes = splitTextWithLinks(text);
|
|
964
|
+
} else {
|
|
965
|
+
inlineNodes = [{ type: "text", value: text }];
|
|
966
|
+
}
|
|
967
|
+
for (const node of inlineNodes) {
|
|
968
|
+
let wrapped = node;
|
|
969
|
+
if (italic) {
|
|
970
|
+
wrapped = { type: "emphasis", children: [wrapped] };
|
|
971
|
+
}
|
|
972
|
+
if (bold) {
|
|
973
|
+
wrapped = { type: "strong", children: [wrapped] };
|
|
974
|
+
}
|
|
975
|
+
nodes.push(wrapped);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
return nodes;
|
|
979
|
+
}
|
|
980
|
+
function splitTextWithLinks(text) {
|
|
981
|
+
const nodes = [];
|
|
982
|
+
let lastIndex = 0;
|
|
983
|
+
IMPORT_URL_PATTERN.lastIndex = 0;
|
|
984
|
+
let match;
|
|
985
|
+
while ((match = IMPORT_URL_PATTERN.exec(text)) !== null) {
|
|
986
|
+
if (match.index > lastIndex) {
|
|
987
|
+
nodes.push({ type: "text", value: text.slice(lastIndex, match.index) });
|
|
988
|
+
}
|
|
989
|
+
const url = match[0];
|
|
990
|
+
nodes.push({
|
|
991
|
+
type: "link",
|
|
992
|
+
url,
|
|
993
|
+
children: [{ type: "text", value: url }]
|
|
994
|
+
});
|
|
995
|
+
lastIndex = match.index + url.length;
|
|
996
|
+
}
|
|
997
|
+
if (lastIndex < text.length) {
|
|
998
|
+
nodes.push({ type: "text", value: text.slice(lastIndex) });
|
|
999
|
+
}
|
|
1000
|
+
return nodes.length > 0 ? nodes : [{ type: "text", value: text }];
|
|
1001
|
+
}
|
|
1002
|
+
function tryMatchBullet(text) {
|
|
1003
|
+
if (text.length === 0) return false;
|
|
1004
|
+
return IMPORT_BULLET_CHARS.has(text[0]) || IMPORT_BULLET_CHARS.has(text.trimStart()[0]);
|
|
1005
|
+
}
|
|
1006
|
+
function stripBullet(text) {
|
|
1007
|
+
const trimmed = text.trimStart();
|
|
1008
|
+
if (IMPORT_BULLET_CHARS.has(trimmed[0])) {
|
|
1009
|
+
return trimmed.slice(1).trimStart();
|
|
1010
|
+
}
|
|
1011
|
+
return text;
|
|
1012
|
+
}
|
|
1013
|
+
function stripOrderedPrefix(text) {
|
|
1014
|
+
return text.replace(IMPORT_ORDERED_PREFIX, "");
|
|
1015
|
+
}
|
|
1016
|
+
function consumeList(lines, startIdx, _typicalLeftMargin, _bodySize, _options) {
|
|
1017
|
+
const firstLine = lines[startIdx];
|
|
1018
|
+
const isOrdered = !!firstLine.text.match(IMPORT_ORDERED_PREFIX);
|
|
1019
|
+
const items = [];
|
|
1020
|
+
let i = startIdx;
|
|
1021
|
+
while (i < lines.length) {
|
|
1022
|
+
const line = lines[i];
|
|
1023
|
+
const isBullet = tryMatchBullet(line.text);
|
|
1024
|
+
const isOrd = !!line.text.match(IMPORT_ORDERED_PREFIX);
|
|
1025
|
+
if (!isBullet && !isOrd) break;
|
|
1026
|
+
if (isOrdered && !isOrd) break;
|
|
1027
|
+
if (!isOrdered && !isBullet) break;
|
|
1028
|
+
const cleanText = isOrdered ? stripOrderedPrefix(line.text) : stripBullet(line.text);
|
|
1029
|
+
const para = {
|
|
1030
|
+
type: "paragraph",
|
|
1031
|
+
children: splitTextWithLinks(cleanText)
|
|
1032
|
+
};
|
|
1033
|
+
items.push({
|
|
1034
|
+
type: "listItem",
|
|
1035
|
+
children: [para]
|
|
1036
|
+
});
|
|
1037
|
+
i++;
|
|
1038
|
+
}
|
|
1039
|
+
return {
|
|
1040
|
+
list: {
|
|
1041
|
+
type: "list",
|
|
1042
|
+
ordered: isOrdered,
|
|
1043
|
+
children: items
|
|
1044
|
+
},
|
|
1045
|
+
nextIndex: i
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
function tryDetectTable(lines, start, _typicalLeftMargin) {
|
|
1049
|
+
const firstLine = lines[start];
|
|
1050
|
+
if (firstLine.items.length < 2) return 0;
|
|
1051
|
+
const cols = getColumnPositions(firstLine);
|
|
1052
|
+
if (cols.length < 2) return 0;
|
|
1053
|
+
let count = 1;
|
|
1054
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
1055
|
+
const line = lines[i];
|
|
1056
|
+
if (line.items.length < 2) break;
|
|
1057
|
+
const lineCols = getColumnPositions(line);
|
|
1058
|
+
if (lineCols.length !== cols.length) break;
|
|
1059
|
+
let aligned = true;
|
|
1060
|
+
for (let c = 0; c < cols.length; c++) {
|
|
1061
|
+
if (Math.abs(lineCols[c] - cols[c]) > IMPORT_COLUMN_TOLERANCE) {
|
|
1062
|
+
aligned = false;
|
|
1063
|
+
break;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
if (!aligned) break;
|
|
1067
|
+
count++;
|
|
1068
|
+
}
|
|
1069
|
+
return count >= IMPORT_TABLE_MIN_ROWS ? count : 0;
|
|
1070
|
+
}
|
|
1071
|
+
function getColumnPositions(line) {
|
|
1072
|
+
const positions = [];
|
|
1073
|
+
for (const item of line.items) {
|
|
1074
|
+
const x = Math.round(item.x);
|
|
1075
|
+
let found = false;
|
|
1076
|
+
for (const p of positions) {
|
|
1077
|
+
if (Math.abs(p - x) < IMPORT_COLUMN_TOLERANCE) {
|
|
1078
|
+
found = true;
|
|
1079
|
+
break;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
if (!found) positions.push(x);
|
|
1083
|
+
}
|
|
1084
|
+
return positions.sort((a, b) => a - b);
|
|
1085
|
+
}
|
|
1086
|
+
function buildTable(lines, _options) {
|
|
1087
|
+
if (lines.length === 0) return null;
|
|
1088
|
+
const cols = getColumnPositions(lines[0]);
|
|
1089
|
+
if (cols.length < 2) return null;
|
|
1090
|
+
const rows = [];
|
|
1091
|
+
for (let ri = 0; ri < lines.length; ri++) {
|
|
1092
|
+
const line = lines[ri];
|
|
1093
|
+
const cells = [];
|
|
1094
|
+
for (let ci = 0; ci < cols.length; ci++) {
|
|
1095
|
+
const colLeft = cols[ci] - IMPORT_COLUMN_TOLERANCE;
|
|
1096
|
+
const colRight = ci + 1 < cols.length ? cols[ci + 1] - IMPORT_COLUMN_TOLERANCE : Infinity;
|
|
1097
|
+
const cellItems = line.items.filter((item) => item.x >= colLeft && item.x < colRight);
|
|
1098
|
+
const text = cellItems.map((i) => i.str).join(" ").trim();
|
|
1099
|
+
cells.push({
|
|
1100
|
+
type: "tableCell",
|
|
1101
|
+
isHeader: ri === 0,
|
|
1102
|
+
children: text.length > 0 ? [{ type: "text", value: text }] : []
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
rows.push({
|
|
1106
|
+
type: "tableRow",
|
|
1107
|
+
children: cells
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
return {
|
|
1111
|
+
type: "table",
|
|
1112
|
+
children: rows
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
function mergeAdjacentText(nodes) {
|
|
1116
|
+
if (nodes.length <= 1) return nodes;
|
|
1117
|
+
const result = [];
|
|
1118
|
+
for (const node of nodes) {
|
|
1119
|
+
const prev = result[result.length - 1];
|
|
1120
|
+
if (prev && prev.type === "text" && node.type === "text") {
|
|
1121
|
+
prev.value += node.value;
|
|
1122
|
+
} else {
|
|
1123
|
+
result.push(node);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return result;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
export {
|
|
1130
|
+
markdownDocToPdf,
|
|
1131
|
+
docToPdf,
|
|
1132
|
+
pdfToMarkdownDoc,
|
|
1133
|
+
pdfToDoc,
|
|
1134
|
+
configurePdfWorker
|
|
1135
|
+
};
|
|
1136
|
+
//# sourceMappingURL=chunk-TBPD5PCU.js.map
|