@bendyline/squisq-formats 1.1.0 → 1.2.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/README.md +23 -10
- package/dist/{chunk-ULLIPBEJ.js → chunk-743COJWQ.js} +6 -2
- package/dist/chunk-743COJWQ.js.map +1 -0
- package/dist/{chunk-KAK4V57E.js → chunk-BHHEDPRB.js} +16 -112
- package/dist/chunk-BHHEDPRB.js.map +1 -0
- package/dist/{chunk-MQHCXI56.js → chunk-CLGUOVYR.js} +193 -23
- package/dist/chunk-CLGUOVYR.js.map +1 -0
- package/dist/chunk-FIFCQN3W.js +630 -0
- package/dist/chunk-FIFCQN3W.js.map +1 -0
- package/dist/chunk-KJ4NS4DX.js +120 -0
- package/dist/chunk-KJ4NS4DX.js.map +1 -0
- package/dist/{chunk-TBPD5PCU.js → chunk-S3Y7H2BK.js} +161 -11
- package/dist/chunk-S3Y7H2BK.js.map +1 -0
- package/dist/docx/export.d.ts +15 -0
- package/dist/docx/export.d.ts.map +1 -1
- package/dist/docx/export.js +144 -19
- package/dist/docx/export.js.map +1 -1
- package/dist/docx/import.d.ts +13 -0
- package/dist/docx/import.d.ts.map +1 -1
- package/dist/docx/import.js +102 -10
- package/dist/docx/import.js.map +1 -1
- package/dist/docx/index.d.ts +1 -1
- package/dist/docx/index.d.ts.map +1 -1
- package/dist/docx/index.js +1 -1
- package/dist/docx/index.js.map +1 -1
- package/dist/html/htmlTemplate.d.ts +6 -0
- package/dist/html/htmlTemplate.d.ts.map +1 -1
- package/dist/html/htmlTemplate.js +5 -1
- package/dist/html/htmlTemplate.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/ooxml/index.d.ts +1 -1
- package/dist/ooxml/index.d.ts.map +1 -1
- package/dist/ooxml/index.js +1 -1
- package/dist/ooxml/index.js.map +1 -1
- package/dist/ooxml/namespaces.d.ts +10 -0
- package/dist/ooxml/namespaces.d.ts.map +1 -1
- package/dist/ooxml/namespaces.js +10 -0
- package/dist/ooxml/namespaces.js.map +1 -1
- package/dist/pdf/export.d.ts +6 -0
- package/dist/pdf/export.d.ts.map +1 -1
- package/dist/pdf/export.js +37 -9
- package/dist/pdf/export.js.map +1 -1
- package/dist/pdf/import.d.ts +16 -0
- package/dist/pdf/import.d.ts.map +1 -1
- package/dist/pdf/import.js +177 -0
- package/dist/pdf/import.js.map +1 -1
- package/dist/pdf/index.d.ts +1 -1
- package/dist/pdf/index.d.ts.map +1 -1
- package/dist/pdf/index.js +1 -1
- package/dist/pdf/index.js.map +1 -1
- package/dist/pptx/export.d.ts +75 -0
- package/dist/pptx/export.d.ts.map +1 -0
- package/dist/pptx/export.js +722 -0
- package/dist/pptx/export.js.map +1 -0
- package/dist/pptx/index.d.ts +12 -27
- package/dist/pptx/index.d.ts.map +1 -1
- package/dist/pptx/index.js +12 -22
- package/dist/pptx/index.js.map +1 -1
- package/dist/pptx/styles.d.ts +54 -0
- package/dist/pptx/styles.d.ts.map +1 -0
- package/dist/pptx/styles.js +73 -0
- package/dist/pptx/styles.js.map +1 -0
- package/dist/pptx/templates.d.ts +24 -0
- package/dist/pptx/templates.d.ts.map +1 -0
- package/dist/pptx/templates.js +158 -0
- package/dist/pptx/templates.js.map +1 -0
- package/package.json +2 -2
- package/src/docx/export.ts +183 -20
- package/src/docx/import.ts +127 -10
- package/src/docx/index.ts +1 -1
- package/src/html/htmlTemplate.ts +13 -0
- package/src/index.ts +2 -2
- package/src/ooxml/index.ts +7 -0
- package/src/ooxml/namespaces.ts +23 -0
- package/src/pdf/export.ts +57 -9
- package/src/pdf/import.ts +228 -0
- package/src/pdf/index.ts +1 -1
- package/src/pptx/export.ts +1041 -0
- package/src/pptx/index.ts +15 -37
- package/src/pptx/styles.ts +96 -0
- package/src/pptx/templates.ts +187 -0
- package/dist/chunk-532L4D5D.js +0 -21
- package/dist/chunk-532L4D5D.js.map +0 -1
- package/dist/chunk-KAK4V57E.js.map +0 -1
- package/dist/chunk-MQHCXI56.js.map +0 -1
- package/dist/chunk-TBPD5PCU.js.map +0 -1
- package/dist/chunk-ULLIPBEJ.js.map +0 -1
package/src/pdf/export.ts
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import { PDFDocument, StandardFonts, rgb, PDFFont, PDFPage } from 'pdf-lib';
|
|
23
23
|
|
|
24
24
|
import type { Doc } from '@bendyline/squisq/schemas';
|
|
25
|
+
import { resolveTheme } from '@bendyline/squisq/schemas';
|
|
25
26
|
import { docToMarkdown } from '@bendyline/squisq/doc';
|
|
26
27
|
import type {
|
|
27
28
|
MarkdownDocument,
|
|
@@ -103,6 +104,12 @@ export interface PdfExportOptions {
|
|
|
103
104
|
margin?: number;
|
|
104
105
|
/** Default body font size in points. Default: 11. */
|
|
105
106
|
defaultFontSize?: number;
|
|
107
|
+
/**
|
|
108
|
+
* Squisq theme ID to apply (e.g., 'documentary', 'cinematic').
|
|
109
|
+
* When set, overrides heading, text, and link colors with the theme palette.
|
|
110
|
+
* Font changes are not supported (pdf-lib uses standard 14 PDF fonts only).
|
|
111
|
+
*/
|
|
112
|
+
themeId?: string;
|
|
106
113
|
}
|
|
107
114
|
|
|
108
115
|
/**
|
|
@@ -151,6 +158,12 @@ interface FontSet {
|
|
|
151
158
|
monoBold: PDFFont;
|
|
152
159
|
}
|
|
153
160
|
|
|
161
|
+
interface RgbColor {
|
|
162
|
+
r: number;
|
|
163
|
+
g: number;
|
|
164
|
+
b: number;
|
|
165
|
+
}
|
|
166
|
+
|
|
154
167
|
interface ExportContext {
|
|
155
168
|
pdfDoc: PDFDocument;
|
|
156
169
|
fonts: FontSet;
|
|
@@ -166,6 +179,12 @@ interface ExportContext {
|
|
|
166
179
|
contentWidth: number;
|
|
167
180
|
/** Bottom margin y position. */
|
|
168
181
|
bottomY: number;
|
|
182
|
+
/** Resolved colors (may be overridden by theme). */
|
|
183
|
+
colors: {
|
|
184
|
+
text: RgbColor;
|
|
185
|
+
heading: RgbColor;
|
|
186
|
+
link: RgbColor;
|
|
187
|
+
};
|
|
169
188
|
}
|
|
170
189
|
|
|
171
190
|
async function createExportContext(
|
|
@@ -189,6 +208,20 @@ async function createExportContext(
|
|
|
189
208
|
|
|
190
209
|
const page = pdfDoc.addPage([pageWidth, pageHeight]);
|
|
191
210
|
|
|
211
|
+
// Resolve theme colors if themeId is set
|
|
212
|
+
let colorText = COLOR_TEXT;
|
|
213
|
+
let colorHeading = COLOR_HEADING;
|
|
214
|
+
let colorLink = COLOR_LINK;
|
|
215
|
+
|
|
216
|
+
if (options.themeId) {
|
|
217
|
+
const theme = resolveTheme(options.themeId);
|
|
218
|
+
if (theme.colors) {
|
|
219
|
+
colorText = hexToRgb(theme.colors.text) ?? COLOR_TEXT;
|
|
220
|
+
colorHeading = hexToRgb(theme.colors.primary) ?? COLOR_HEADING;
|
|
221
|
+
colorLink = hexToRgb(theme.colors.highlight || theme.colors.secondary) ?? COLOR_LINK;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
192
225
|
return {
|
|
193
226
|
pdfDoc,
|
|
194
227
|
fonts: { regular, bold, italic, boldItalic, mono, monoBold },
|
|
@@ -200,6 +233,7 @@ async function createExportContext(
|
|
|
200
233
|
y: pageHeight - margin,
|
|
201
234
|
contentWidth: pageWidth - 2 * margin,
|
|
202
235
|
bottomY: margin,
|
|
236
|
+
colors: { text: colorText, heading: colorHeading, link: colorLink },
|
|
203
237
|
};
|
|
204
238
|
}
|
|
205
239
|
|
|
@@ -262,7 +296,7 @@ function flattenInlines(
|
|
|
262
296
|
text: (node as MarkdownText).value,
|
|
263
297
|
font,
|
|
264
298
|
fontSize: state.code ? CODE_FONT_SIZE : ctx.fontSize,
|
|
265
|
-
color: state.code ? COLOR_CODE_TEXT : (state.color ??
|
|
299
|
+
color: state.code ? COLOR_CODE_TEXT : (state.color ?? ctx.colors.text),
|
|
266
300
|
link: state.link,
|
|
267
301
|
strikethrough: state.strikethrough,
|
|
268
302
|
});
|
|
@@ -307,7 +341,7 @@ function flattenInlines(
|
|
|
307
341
|
...flattenInlines(linkNode.children, ctx, {
|
|
308
342
|
...state,
|
|
309
343
|
link: linkNode.url,
|
|
310
|
-
color:
|
|
344
|
+
color: ctx.colors.link,
|
|
311
345
|
}),
|
|
312
346
|
);
|
|
313
347
|
break;
|
|
@@ -329,7 +363,7 @@ function flattenInlines(
|
|
|
329
363
|
text: '\n',
|
|
330
364
|
font: ctx.fonts.regular,
|
|
331
365
|
fontSize: ctx.fontSize,
|
|
332
|
-
color:
|
|
366
|
+
color: ctx.colors.text,
|
|
333
367
|
});
|
|
334
368
|
break;
|
|
335
369
|
|
|
@@ -361,7 +395,7 @@ function flattenInlines(
|
|
|
361
395
|
text: `[${ref.identifier}]`,
|
|
362
396
|
font: ctx.fonts.regular,
|
|
363
397
|
fontSize: ctx.fontSize * 0.75,
|
|
364
|
-
color:
|
|
398
|
+
color: ctx.colors.link,
|
|
365
399
|
});
|
|
366
400
|
break;
|
|
367
401
|
}
|
|
@@ -376,7 +410,7 @@ function flattenInlines(
|
|
|
376
410
|
text: fallback.value,
|
|
377
411
|
font: pickFont(ctx, state.bold, state.italic),
|
|
378
412
|
fontSize: ctx.fontSize,
|
|
379
|
-
color: state.color ??
|
|
413
|
+
color: state.color ?? ctx.colors.text,
|
|
380
414
|
});
|
|
381
415
|
}
|
|
382
416
|
break;
|
|
@@ -601,7 +635,7 @@ function renderHeading(node: MarkdownHeading, ctx: ExportContext, extraIndent: n
|
|
|
601
635
|
bold: true,
|
|
602
636
|
italic: false,
|
|
603
637
|
code: false,
|
|
604
|
-
color:
|
|
638
|
+
color: ctx.colors.heading,
|
|
605
639
|
});
|
|
606
640
|
|
|
607
641
|
drawSpans(spans, ctx, w, x0);
|
|
@@ -704,7 +738,7 @@ function renderListItem(
|
|
|
704
738
|
y: ctx.y - ctx.fontSize,
|
|
705
739
|
size: ctx.fontSize,
|
|
706
740
|
font: bulletFont,
|
|
707
|
-
color: rgb(
|
|
741
|
+
color: rgb(ctx.colors.text.r, ctx.colors.text.g, ctx.colors.text.b),
|
|
708
742
|
});
|
|
709
743
|
|
|
710
744
|
const textIndent = indent + bulletWidth + 4;
|
|
@@ -969,7 +1003,7 @@ function renderFootnoteDefinition(
|
|
|
969
1003
|
y: ctx.y - ctx.fontSize * 0.75,
|
|
970
1004
|
size: ctx.fontSize * 0.75,
|
|
971
1005
|
font: ctx.fonts.bold,
|
|
972
|
-
color: rgb(
|
|
1006
|
+
color: rgb(ctx.colors.link.r, ctx.colors.link.g, ctx.colors.link.b),
|
|
973
1007
|
});
|
|
974
1008
|
|
|
975
1009
|
// Render children indented
|
|
@@ -1022,8 +1056,22 @@ function renderFallbackBlock(
|
|
|
1022
1056
|
y: ctx.y - ctx.fontSize,
|
|
1023
1057
|
size: ctx.fontSize,
|
|
1024
1058
|
font: ctx.fonts.regular,
|
|
1025
|
-
color: rgb(
|
|
1059
|
+
color: rgb(ctx.colors.text.r, ctx.colors.text.g, ctx.colors.text.b),
|
|
1026
1060
|
});
|
|
1027
1061
|
ctx.y -= lineH + PARAGRAPH_SPACING;
|
|
1028
1062
|
}
|
|
1029
1063
|
}
|
|
1064
|
+
|
|
1065
|
+
// ============================================
|
|
1066
|
+
// Color Helpers
|
|
1067
|
+
// ============================================
|
|
1068
|
+
|
|
1069
|
+
function hexToRgb(hex: string): RgbColor | undefined {
|
|
1070
|
+
const h = hex.startsWith('#') ? hex.slice(1) : hex;
|
|
1071
|
+
if (h.length !== 6) return undefined;
|
|
1072
|
+
const r = parseInt(h.slice(0, 2), 16);
|
|
1073
|
+
const g = parseInt(h.slice(2, 4), 16);
|
|
1074
|
+
const b = parseInt(h.slice(4, 6), 16);
|
|
1075
|
+
if (isNaN(r) || isNaN(g) || isNaN(b)) return undefined;
|
|
1076
|
+
return { r: r / 255, g: g / 255, b: b / 255 };
|
|
1077
|
+
}
|
package/src/pdf/import.ts
CHANGED
|
@@ -40,8 +40,12 @@ import type {
|
|
|
40
40
|
MarkdownStrong,
|
|
41
41
|
MarkdownInlineCode,
|
|
42
42
|
MarkdownLink,
|
|
43
|
+
MarkdownImage,
|
|
43
44
|
} from '@bendyline/squisq/markdown';
|
|
44
45
|
|
|
46
|
+
import { MemoryContentContainer } from '@bendyline/squisq/storage';
|
|
47
|
+
import type { ContentContainer } from '@bendyline/squisq/storage';
|
|
48
|
+
|
|
45
49
|
import {
|
|
46
50
|
DEFAULT_FONT_SIZE,
|
|
47
51
|
IMPORT_HEADING_MIN_SIZE,
|
|
@@ -127,6 +131,230 @@ export async function pdfToDoc(
|
|
|
127
131
|
return markdownToDoc(markdownDoc);
|
|
128
132
|
}
|
|
129
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Convert a PDF file to a ContentContainer with markdown + extracted images.
|
|
136
|
+
*
|
|
137
|
+
* The container will contain:
|
|
138
|
+
* - The primary markdown document (index.md)
|
|
139
|
+
* - Any embedded images under images/ (e.g., images/image1.png)
|
|
140
|
+
*
|
|
141
|
+
* Image extraction uses pdfjs-dist's operator list API and requires a browser
|
|
142
|
+
* environment (canvas is used to encode pixel data to PNG).
|
|
143
|
+
*
|
|
144
|
+
* @param data - The raw PDF file as ArrayBuffer, Uint8Array, or Blob
|
|
145
|
+
* @param options - Import options
|
|
146
|
+
* @returns A ContentContainer with the document and its media
|
|
147
|
+
*/
|
|
148
|
+
export async function pdfToContainer(
|
|
149
|
+
data: ArrayBuffer | Uint8Array | Blob,
|
|
150
|
+
options: PdfImportOptions = {},
|
|
151
|
+
): Promise<ContentContainer> {
|
|
152
|
+
const bytes =
|
|
153
|
+
data instanceof Blob
|
|
154
|
+
? new Uint8Array(await data.arrayBuffer())
|
|
155
|
+
: data instanceof ArrayBuffer
|
|
156
|
+
? new Uint8Array(data)
|
|
157
|
+
: data;
|
|
158
|
+
|
|
159
|
+
const textLines = await extractTextLines(bytes);
|
|
160
|
+
const images = await extractImages(bytes);
|
|
161
|
+
|
|
162
|
+
const bodySize = options.bodyFontSize ?? detectBodyFontSize(textLines);
|
|
163
|
+
|
|
164
|
+
// If we have images, insert image references into the block list
|
|
165
|
+
let blocks = classifyLines(textLines, bodySize, options);
|
|
166
|
+
if (images.length > 0) {
|
|
167
|
+
blocks = insertImageBlocks(blocks, images);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const markdownDoc: MarkdownDocument = { type: 'document', children: blocks };
|
|
171
|
+
|
|
172
|
+
const { stringifyMarkdown } = await import('@bendyline/squisq/markdown');
|
|
173
|
+
const markdown = stringifyMarkdown(markdownDoc);
|
|
174
|
+
|
|
175
|
+
const container = new MemoryContentContainer();
|
|
176
|
+
await container.writeDocument(markdown);
|
|
177
|
+
|
|
178
|
+
for (const img of images) {
|
|
179
|
+
await container.writeFile(img.path, new Uint8Array(img.data), 'image/png');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return container;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Extracted image with position info for placement. */
|
|
186
|
+
interface ExtractedImage {
|
|
187
|
+
path: string;
|
|
188
|
+
data: ArrayBuffer;
|
|
189
|
+
page: number;
|
|
190
|
+
y: number;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Extract embedded images from a PDF using pdfjs-dist operator list API.
|
|
195
|
+
* Requires browser canvas for PNG encoding.
|
|
196
|
+
*/
|
|
197
|
+
async function extractImages(data: Uint8Array): Promise<ExtractedImage[]> {
|
|
198
|
+
// Canvas is required for PNG encoding — skip in non-browser environments
|
|
199
|
+
if (typeof document === 'undefined') return [];
|
|
200
|
+
|
|
201
|
+
let pdfjsLib: PdfjsLib & { OPS?: Record<string, number> };
|
|
202
|
+
try {
|
|
203
|
+
pdfjsLib = (await import('pdfjs-dist/legacy/build/pdf.mjs')) as unknown as PdfjsLib & {
|
|
204
|
+
OPS?: Record<string, number>;
|
|
205
|
+
};
|
|
206
|
+
} catch {
|
|
207
|
+
pdfjsLib = (await import('pdfjs-dist')) as unknown as PdfjsLib & {
|
|
208
|
+
OPS?: Record<string, number>;
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
await applyWorkerConfig(pdfjsLib);
|
|
213
|
+
|
|
214
|
+
const OPS_paintImageXObject = pdfjsLib.OPS?.paintImageXObject ?? 85;
|
|
215
|
+
|
|
216
|
+
const loadingTask = pdfjsLib.getDocument({
|
|
217
|
+
data,
|
|
218
|
+
isEvalSupported: false,
|
|
219
|
+
useSystemFonts: true,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const pdf = await loadingTask.promise;
|
|
223
|
+
const images: ExtractedImage[] = [];
|
|
224
|
+
let counter = 0;
|
|
225
|
+
|
|
226
|
+
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
|
|
227
|
+
const page = (await pdf.getPage(pageNum)) as PdfjsPageFull;
|
|
228
|
+
if (!page.getOperatorList) continue;
|
|
229
|
+
|
|
230
|
+
const opList = await page.getOperatorList();
|
|
231
|
+
const seen = new Set<string>();
|
|
232
|
+
|
|
233
|
+
for (let i = 0; i < opList.fnArray.length; i++) {
|
|
234
|
+
if (opList.fnArray[i] !== OPS_paintImageXObject) continue;
|
|
235
|
+
|
|
236
|
+
const imgName = opList.argsArray[i]?.[0];
|
|
237
|
+
if (!imgName || typeof imgName !== 'string' || seen.has(imgName)) continue;
|
|
238
|
+
seen.add(imgName);
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
const imgData = page.objs?.get(imgName) as PdfjsImageData | null;
|
|
242
|
+
if (!imgData?.data || !imgData.width || !imgData.height) continue;
|
|
243
|
+
|
|
244
|
+
const pngData = imageDataToPng(imgData);
|
|
245
|
+
if (!pngData) continue;
|
|
246
|
+
|
|
247
|
+
counter++;
|
|
248
|
+
images.push({
|
|
249
|
+
path: `images/image${counter}.png`,
|
|
250
|
+
data: pngData,
|
|
251
|
+
page: pageNum - 1,
|
|
252
|
+
y: 0,
|
|
253
|
+
});
|
|
254
|
+
} catch {
|
|
255
|
+
// Skip images that fail to extract
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return images;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Minimal pdfjs image data shape. */
|
|
264
|
+
interface PdfjsImageData {
|
|
265
|
+
width: number;
|
|
266
|
+
height: number;
|
|
267
|
+
data: Uint8ClampedArray;
|
|
268
|
+
kind?: number;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Extended PdfjsPage with operator list and objs access. */
|
|
272
|
+
interface PdfjsPageFull extends PdfjsPage {
|
|
273
|
+
getOperatorList(): Promise<{ fnArray: number[]; argsArray: unknown[][] }>;
|
|
274
|
+
objs?: { get(name: string): unknown; has?(name: string): boolean };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Encode pdfjs image data to PNG using a canvas element. */
|
|
278
|
+
function imageDataToPng(img: PdfjsImageData): ArrayBuffer | null {
|
|
279
|
+
try {
|
|
280
|
+
const canvas = document.createElement('canvas');
|
|
281
|
+
canvas.width = img.width;
|
|
282
|
+
canvas.height = img.height;
|
|
283
|
+
const ctx = canvas.getContext('2d');
|
|
284
|
+
if (!ctx) return null;
|
|
285
|
+
|
|
286
|
+
// pdfjs kind=1 is GRAYSCALE, kind=2 is RGB, kind=3 is RGBA
|
|
287
|
+
let imageData: ImageData;
|
|
288
|
+
if (img.kind === 3 || img.data.length === img.width * img.height * 4) {
|
|
289
|
+
// RGBA — use directly
|
|
290
|
+
imageData = new ImageData(new Uint8ClampedArray(img.data), img.width, img.height);
|
|
291
|
+
} else if (img.kind === 2 || img.data.length === img.width * img.height * 3) {
|
|
292
|
+
// RGB — expand to RGBA
|
|
293
|
+
const rgba = new Uint8ClampedArray(img.width * img.height * 4);
|
|
294
|
+
for (let j = 0, k = 0; j < img.data.length; j += 3, k += 4) {
|
|
295
|
+
rgba[k] = img.data[j];
|
|
296
|
+
rgba[k + 1] = img.data[j + 1];
|
|
297
|
+
rgba[k + 2] = img.data[j + 2];
|
|
298
|
+
rgba[k + 3] = 255;
|
|
299
|
+
}
|
|
300
|
+
imageData = new ImageData(rgba, img.width, img.height);
|
|
301
|
+
} else if (img.kind === 1 || img.data.length === img.width * img.height) {
|
|
302
|
+
// Grayscale — expand to RGBA
|
|
303
|
+
const rgba = new Uint8ClampedArray(img.width * img.height * 4);
|
|
304
|
+
for (let j = 0, k = 0; j < img.data.length; j++, k += 4) {
|
|
305
|
+
rgba[k] = img.data[j];
|
|
306
|
+
rgba[k + 1] = img.data[j];
|
|
307
|
+
rgba[k + 2] = img.data[j];
|
|
308
|
+
rgba[k + 3] = 255;
|
|
309
|
+
}
|
|
310
|
+
imageData = new ImageData(rgba, img.width, img.height);
|
|
311
|
+
} else {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
ctx.putImageData(imageData, 0, 0);
|
|
316
|
+
|
|
317
|
+
// Convert canvas to PNG ArrayBuffer
|
|
318
|
+
const dataUrl = canvas.toDataURL('image/png');
|
|
319
|
+
const base64 = dataUrl.split(',')[1];
|
|
320
|
+
const binaryStr = atob(base64);
|
|
321
|
+
const bytes = new Uint8Array(binaryStr.length);
|
|
322
|
+
for (let i = 0; i < binaryStr.length; i++) {
|
|
323
|
+
bytes[i] = binaryStr.charCodeAt(i);
|
|
324
|
+
}
|
|
325
|
+
return bytes.buffer;
|
|
326
|
+
} catch {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Insert image reference blocks between text blocks.
|
|
333
|
+
* Places each image after the last text block on the same page (or at the end).
|
|
334
|
+
*/
|
|
335
|
+
function insertImageBlocks(
|
|
336
|
+
blocks: MarkdownBlockNode[],
|
|
337
|
+
images: ExtractedImage[],
|
|
338
|
+
): MarkdownBlockNode[] {
|
|
339
|
+
if (images.length === 0) return blocks;
|
|
340
|
+
|
|
341
|
+
// Simple strategy: append all images at the end as paragraphs
|
|
342
|
+
const result = [...blocks];
|
|
343
|
+
for (const img of images) {
|
|
344
|
+
const imgNode: MarkdownImage = {
|
|
345
|
+
type: 'image',
|
|
346
|
+
url: img.path,
|
|
347
|
+
alt: `Image ${img.path.replace('images/image', '').replace('.png', '')}`,
|
|
348
|
+
};
|
|
349
|
+
const para: MarkdownParagraph = {
|
|
350
|
+
type: 'paragraph',
|
|
351
|
+
children: [imgNode],
|
|
352
|
+
};
|
|
353
|
+
result.push(para);
|
|
354
|
+
}
|
|
355
|
+
return result;
|
|
356
|
+
}
|
|
357
|
+
|
|
130
358
|
// ============================================
|
|
131
359
|
// Internal Types
|
|
132
360
|
// ============================================
|
package/src/pdf/index.ts
CHANGED
|
@@ -25,5 +25,5 @@ export { markdownDocToPdf, docToPdf } from './export.js';
|
|
|
25
25
|
export type { PdfExportOptions } from './export.js';
|
|
26
26
|
|
|
27
27
|
// Import
|
|
28
|
-
export { pdfToMarkdownDoc, pdfToDoc, configurePdfWorker } from './import.js';
|
|
28
|
+
export { pdfToMarkdownDoc, pdfToDoc, pdfToContainer, configurePdfWorker } from './import.js';
|
|
29
29
|
export type { PdfImportOptions } from './import.js';
|