documonster 0.7.0 → 0.8.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/browser/modules/pdf/builder/image-utils.d.ts +1 -1
- package/dist/browser/modules/pdf/builder/image-utils.js +58 -9
- package/dist/browser/modules/pdf/core/pdf-writer.d.ts +8 -0
- package/dist/browser/modules/pdf/core/pdf-writer.js +3 -0
- package/dist/browser/modules/pdf/excel-bridge.js +126 -19
- package/dist/browser/modules/pdf/index.d.ts +1 -1
- package/dist/browser/modules/pdf/render/chart-surface.d.ts +6 -1
- package/dist/browser/modules/pdf/render/chart-surface.js +86 -47
- package/dist/browser/modules/pdf/render/constants.d.ts +69 -0
- package/dist/browser/modules/pdf/render/constants.js +45 -0
- package/dist/browser/modules/pdf/render/layout-engine.d.ts +5 -1
- package/dist/browser/modules/pdf/render/layout-engine.js +586 -205
- package/dist/browser/modules/pdf/render/page-renderer.js +180 -3
- package/dist/browser/modules/pdf/render/pdf-exporter.js +176 -35
- package/dist/browser/modules/pdf/render/style-converter.d.ts +11 -0
- package/dist/browser/modules/pdf/render/style-converter.js +24 -0
- package/dist/browser/modules/pdf/types.d.ts +284 -5
- package/dist/cjs/modules/pdf/builder/image-utils.js +58 -9
- package/dist/cjs/modules/pdf/core/pdf-writer.js +3 -0
- package/dist/cjs/modules/pdf/excel-bridge.js +125 -18
- package/dist/cjs/modules/pdf/render/chart-surface.js +86 -47
- package/dist/cjs/modules/pdf/render/constants.js +46 -1
- package/dist/cjs/modules/pdf/render/layout-engine.js +583 -202
- package/dist/cjs/modules/pdf/render/page-renderer.js +179 -2
- package/dist/cjs/modules/pdf/render/pdf-exporter.js +175 -34
- package/dist/cjs/modules/pdf/render/style-converter.js +26 -0
- package/dist/esm/modules/pdf/builder/image-utils.js +58 -9
- package/dist/esm/modules/pdf/core/pdf-writer.js +3 -0
- package/dist/esm/modules/pdf/excel-bridge.js +126 -19
- package/dist/esm/modules/pdf/render/chart-surface.js +86 -47
- package/dist/esm/modules/pdf/render/constants.js +45 -0
- package/dist/esm/modules/pdf/render/layout-engine.js +586 -205
- package/dist/esm/modules/pdf/render/page-renderer.js +180 -3
- package/dist/esm/modules/pdf/render/pdf-exporter.js +176 -35
- package/dist/esm/modules/pdf/render/style-converter.js +24 -0
- package/dist/iife/documonster.archive.iife.js +1 -1
- package/dist/iife/documonster.archive.iife.min.js +1 -1
- package/dist/iife/documonster.csv.iife.js +1 -1
- package/dist/iife/documonster.csv.iife.min.js +1 -1
- package/dist/iife/documonster.excel.iife.js +1 -1
- package/dist/iife/documonster.excel.iife.min.js +1 -1
- package/dist/iife/documonster.formula.iife.js +1 -1
- package/dist/iife/documonster.formula.iife.min.js +1 -1
- package/dist/iife/documonster.markdown.iife.js +1 -1
- package/dist/iife/documonster.markdown.iife.min.js +1 -1
- package/dist/iife/documonster.pdf.iife.js +1457 -664
- package/dist/iife/documonster.pdf.iife.js.map +1 -1
- package/dist/iife/documonster.pdf.iife.min.js +33 -32
- package/dist/iife/documonster.stream.iife.js +1 -1
- package/dist/iife/documonster.stream.iife.min.js +1 -1
- package/dist/iife/documonster.word.iife.js +1 -1
- package/dist/iife/documonster.word.iife.min.js +1 -1
- package/dist/iife/documonster.xml.iife.js +1 -1
- package/dist/iife/documonster.xml.iife.min.js +1 -1
- package/dist/types/modules/pdf/builder/image-utils.d.ts +1 -1
- package/dist/types/modules/pdf/core/pdf-writer.d.ts +8 -0
- package/dist/types/modules/pdf/index.d.ts +1 -1
- package/dist/types/modules/pdf/render/chart-surface.d.ts +6 -1
- package/dist/types/modules/pdf/render/constants.d.ts +69 -0
- package/dist/types/modules/pdf/render/layout-engine.d.ts +5 -1
- package/dist/types/modules/pdf/render/style-converter.d.ts +11 -0
- package/dist/types/modules/pdf/types.d.ts +284 -5
- package/package.json +1 -1
|
@@ -66,7 +66,7 @@ async function excelToPdf(workbook, options) {
|
|
|
66
66
|
// shipped with. This keeps the ~200 KB engine out of bundles that only
|
|
67
67
|
// export already-computed workbooks — no host-registry needed.
|
|
68
68
|
options?.recalculate?.(workbook);
|
|
69
|
-
const pdfWorkbook = await excelWorkbookToPdf(workbook);
|
|
69
|
+
const pdfWorkbook = await excelWorkbookToPdf(workbook, options);
|
|
70
70
|
return (0, pdf_exporter_1.exportPdf)(pdfWorkbook, options);
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
@@ -178,9 +178,9 @@ async function chartToPdf(chart, options = {}) {
|
|
|
178
178
|
* Chartsheets without an orderNo fall to the end, mirroring how Excel
|
|
179
179
|
* treats sheets with missing tab positions.
|
|
180
180
|
*/
|
|
181
|
-
async function excelWorkbookToPdf(workbook) {
|
|
182
|
-
const worksheetResults = await Promise.all((0, workbook_1.getWorksheets)(workbook).map(ws => convertSheet(ws, workbook)));
|
|
183
|
-
const chartsheetResults = await Promise.all((0, workbook_1.getChartsheets)(workbook).map(cs => convertChartsheet(cs, workbook)));
|
|
181
|
+
async function excelWorkbookToPdf(workbook, options) {
|
|
182
|
+
const worksheetResults = await Promise.all((0, workbook_1.getWorksheets)(workbook).map(ws => convertSheet(ws, workbook, options)));
|
|
183
|
+
const chartsheetResults = await Promise.all((0, workbook_1.getChartsheets)(workbook).map(cs => convertChartsheet(cs, workbook, options)));
|
|
184
184
|
const combined = [...worksheetResults, ...chartsheetResults];
|
|
185
185
|
combined.sort((a, b) => (a.orderNo ?? Number.POSITIVE_INFINITY) - (b.orderNo ?? Number.POSITIVE_INFINITY));
|
|
186
186
|
return {
|
|
@@ -196,7 +196,7 @@ async function excelWorkbookToPdf(workbook) {
|
|
|
196
196
|
// =============================================================================
|
|
197
197
|
// Sheet Conversion
|
|
198
198
|
// =============================================================================
|
|
199
|
-
async function convertSheet(ws, workbook) {
|
|
199
|
+
async function convertSheet(ws, workbook, options) {
|
|
200
200
|
const dimensions = (0, worksheet_1.getSheetDimensions)(ws);
|
|
201
201
|
const hasData = dimensions && dimensions.top > 0 && dimensions.left > 0;
|
|
202
202
|
const bounds = hasData
|
|
@@ -316,10 +316,22 @@ async function convertSheet(ws, workbook) {
|
|
|
316
316
|
}
|
|
317
317
|
: undefined,
|
|
318
318
|
scale: ps.scale,
|
|
319
|
+
fitToPage: ps.fitToPage,
|
|
320
|
+
fitToWidth: ps.fitToWidth,
|
|
321
|
+
fitToHeight: ps.fitToHeight,
|
|
319
322
|
printTitlesRow: ps.printTitlesRow,
|
|
323
|
+
printTitlesColumn: ps.printTitlesColumn,
|
|
320
324
|
showGridLines: ps.showGridLines,
|
|
325
|
+
showRowColHeaders: ps.showRowColHeaders,
|
|
321
326
|
printArea: ps.printArea,
|
|
322
|
-
firstPageNumber: ps.useFirstPageNumber === false ? undefined : ps.firstPageNumber
|
|
327
|
+
firstPageNumber: ps.useFirstPageNumber === false ? undefined : ps.firstPageNumber,
|
|
328
|
+
pageOrder: ps.pageOrder,
|
|
329
|
+
blackAndWhite: ps.blackAndWhite,
|
|
330
|
+
draft: ps.draft,
|
|
331
|
+
errors: ps.errors,
|
|
332
|
+
cellComments: ps.cellComments,
|
|
333
|
+
horizontalCentered: ps.horizontalCentered,
|
|
334
|
+
verticalCentered: ps.verticalCentered
|
|
323
335
|
}
|
|
324
336
|
: undefined;
|
|
325
337
|
// Convert row/col breaks
|
|
@@ -328,9 +340,18 @@ async function convertSheet(ws, workbook) {
|
|
|
328
340
|
// Convert images and charts. Both are floating objects anchored to
|
|
329
341
|
// cells, and both need to participate in bounds expansion so the
|
|
330
342
|
// layout engine allocates pages that cover their anchor rows/cols.
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
343
|
+
//
|
|
344
|
+
// Draft quality prints no graphics, so skip collection entirely rather than
|
|
345
|
+
// gathering (and rasterizing ChartEx layouts) only to drop the result during
|
|
346
|
+
// layout: draft exists to be cheap, and a rasterizer failure must not sink an
|
|
347
|
+
// export that would not have shown the graphic anyway. The precedence here
|
|
348
|
+
// mirrors `resolveOptions`; the layout engine still gates on the resolved
|
|
349
|
+
// flag, so this stays a pure optimization.
|
|
350
|
+
const comments = collectComments(ws);
|
|
351
|
+
const draftQuality = options?.draft ?? ws.pageSetup?.draft ?? false;
|
|
352
|
+
const images = draftQuality ? undefined : collectImages(ws, workbook);
|
|
353
|
+
const charts = draftQuality ? undefined : await collectCharts(ws);
|
|
354
|
+
const sparklineCharts = draftQuality ? undefined : collectSparklineCharts(ws);
|
|
334
355
|
// Merge sparkline micro-charts with regular charts
|
|
335
356
|
const allCharts = charts
|
|
336
357
|
? sparklineCharts
|
|
@@ -402,14 +423,91 @@ async function convertSheet(ws, workbook) {
|
|
|
402
423
|
rows,
|
|
403
424
|
merges,
|
|
404
425
|
pageSetup,
|
|
405
|
-
headerFooter: convertHeaderFooter(ws, workbook),
|
|
426
|
+
headerFooter: convertHeaderFooter(ws, workbook, draftQuality),
|
|
406
427
|
rowBreaks,
|
|
407
428
|
colBreaks,
|
|
408
429
|
images,
|
|
409
|
-
charts: allCharts
|
|
430
|
+
charts: allCharts,
|
|
431
|
+
comments
|
|
410
432
|
};
|
|
411
433
|
}
|
|
412
|
-
|
|
434
|
+
/**
|
|
435
|
+
* Collect every comment on a worksheet, in row-major order.
|
|
436
|
+
*
|
|
437
|
+
* Two sources are merged: classic notes stored on the cell (`_comment`, the
|
|
438
|
+
* VML-backed kind) and Office 365 threaded comments held on the sheet. Only the
|
|
439
|
+
* text and author are needed — the printed form is an end-of-sheet list, not a
|
|
440
|
+
* positioned box — so the VML geometry is deliberately ignored.
|
|
441
|
+
*/
|
|
442
|
+
function collectComments(ws) {
|
|
443
|
+
const collected = [];
|
|
444
|
+
for (const row of ws._rows ?? []) {
|
|
445
|
+
if (!row) {
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
for (const cell of row.cells ?? []) {
|
|
449
|
+
if (!cell) {
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
const note = (0, cell_1.cellComment)(cell);
|
|
453
|
+
const text = noteText(note?.note);
|
|
454
|
+
if (text) {
|
|
455
|
+
collected.push({
|
|
456
|
+
ref: cell.address,
|
|
457
|
+
text,
|
|
458
|
+
author: note?.author,
|
|
459
|
+
anchor: parseVmlAnchor(note?.note)
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
for (const entry of ws.threadedComments ?? []) {
|
|
465
|
+
const text = entry.comment.text?.trim();
|
|
466
|
+
if (text) {
|
|
467
|
+
collected.push({ ref: entry.ref, text, author: entry.comment.personId });
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return collected.length > 0 ? collected : undefined;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Decode a VML comment anchor into fractional sheet coordinates.
|
|
474
|
+
*
|
|
475
|
+
* `<x:Anchor>` holds eight integers: for each edge a column/row index followed
|
|
476
|
+
* by an offset into it, measured in 1/68 of a column and 1/18 of a row (the
|
|
477
|
+
* reciprocals Excel uses when it writes the value). Returns `undefined` when the
|
|
478
|
+
* note has no anchor, so callers fall back to Excel's default placement.
|
|
479
|
+
*/
|
|
480
|
+
function parseVmlAnchor(note) {
|
|
481
|
+
if (!note || typeof note === "string" || typeof note.anchor !== "string") {
|
|
482
|
+
return undefined;
|
|
483
|
+
}
|
|
484
|
+
const parts = note.anchor.split(",").map(p => Number(p.trim()));
|
|
485
|
+
if (parts.length !== 8 || parts.some(n => !Number.isFinite(n))) {
|
|
486
|
+
return undefined;
|
|
487
|
+
}
|
|
488
|
+
const [l, lf, t, tf, r, rf, b, bf] = parts;
|
|
489
|
+
return {
|
|
490
|
+
left: l + lf / 68,
|
|
491
|
+
top: t + tf / 18,
|
|
492
|
+
right: r + rf / 68,
|
|
493
|
+
bottom: b + bf / 18
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
/** Flatten a note body, which may be plain text or a rich `NoteConfig`. */
|
|
497
|
+
function noteText(note) {
|
|
498
|
+
if (typeof note === "string") {
|
|
499
|
+
return note.trim() || undefined;
|
|
500
|
+
}
|
|
501
|
+
if (note && typeof note === "object" && Array.isArray(note.texts)) {
|
|
502
|
+
const joined = note.texts
|
|
503
|
+
.map(t => (typeof t === "string" ? t : (t.text ?? "")))
|
|
504
|
+
.join("")
|
|
505
|
+
.trim();
|
|
506
|
+
return joined || undefined;
|
|
507
|
+
}
|
|
508
|
+
return undefined;
|
|
509
|
+
}
|
|
510
|
+
function convertHeaderFooter(ws, workbook, draftQuality = false) {
|
|
413
511
|
const source = ws.headerFooter;
|
|
414
512
|
const fields = [
|
|
415
513
|
source.oddHeader,
|
|
@@ -419,7 +517,9 @@ function convertHeaderFooter(ws, workbook) {
|
|
|
419
517
|
source.firstHeader,
|
|
420
518
|
source.firstFooter
|
|
421
519
|
];
|
|
422
|
-
|
|
520
|
+
// Draft quality prints no graphics, header pictures included, so skip
|
|
521
|
+
// collecting them rather than embedding XObjects nothing will draw.
|
|
522
|
+
const images = draftQuality ? [] : convertHeaderFooterImages(ws, workbook);
|
|
423
523
|
if (!fields.some(Boolean) && images.length === 0) {
|
|
424
524
|
return undefined;
|
|
425
525
|
}
|
|
@@ -1258,7 +1358,7 @@ function chartAnchorRange(chart) {
|
|
|
1258
1358
|
: undefined,
|
|
1259
1359
|
// Chart anchors store ext as EMU (`cx`, `cy`). Pass the values
|
|
1260
1360
|
// through unchanged; the layout engine converts EMU→pt for charts
|
|
1261
|
-
// (
|
|
1361
|
+
// (÷12700) and px→pt for images (×0.75) based on `extUnit`.
|
|
1262
1362
|
ext: r.ext ? { width: r.ext.cx, height: r.ext.cy } : undefined,
|
|
1263
1363
|
extUnit: r.ext ? "emu" : undefined
|
|
1264
1364
|
};
|
|
@@ -1342,11 +1442,16 @@ const CHARTSHEET_RASTER_PX = { width: 1280, height: 720 };
|
|
|
1342
1442
|
* (matches what Excel prints for a chartsheet whose chart was deleted
|
|
1343
1443
|
* but the sheet kept).
|
|
1344
1444
|
*/
|
|
1345
|
-
async function convertChartsheet(cs, workbook) {
|
|
1445
|
+
async function convertChartsheet(cs, workbook, options) {
|
|
1346
1446
|
const classicModel = (0, chartsheet_1.chartsheetChartModel)(cs);
|
|
1347
1447
|
const chartExModel = (0, chartsheet_1.chartsheetChartExModel)(cs);
|
|
1448
|
+
// Resolve draft before touching the chart: a ChartEx outside the vector
|
|
1449
|
+
// whitelist would otherwise be rasterised at cost — and could fail — for a
|
|
1450
|
+
// page that prints no graphics at all. Precedence mirrors `resolveOptions`.
|
|
1451
|
+
const draftQuality = options?.draft ?? (0, chartsheet_1.chartsheetPageSetup)(cs)?.draft ?? false;
|
|
1452
|
+
// Draft quality prints no graphics, so the chart is simply never built.
|
|
1348
1453
|
let chart = {};
|
|
1349
|
-
if (classicModel) {
|
|
1454
|
+
if (!draftQuality && classicModel) {
|
|
1350
1455
|
const model = classicModel;
|
|
1351
1456
|
chart = {
|
|
1352
1457
|
drawVector: (surface, rect) => {
|
|
@@ -1359,7 +1464,7 @@ async function convertChartsheet(cs, workbook) {
|
|
|
1359
1464
|
}
|
|
1360
1465
|
};
|
|
1361
1466
|
}
|
|
1362
|
-
else if (chartExModel) {
|
|
1467
|
+
else if (!draftQuality && chartExModel) {
|
|
1363
1468
|
if ((0, chart_ex_renderer_1.canRenderChartExAsVectorPdf)(chartExModel)) {
|
|
1364
1469
|
const model = chartExModel;
|
|
1365
1470
|
chart = {
|
|
@@ -1400,6 +1505,8 @@ async function convertChartsheet(cs, workbook) {
|
|
|
1400
1505
|
orientation: ps?.orientation,
|
|
1401
1506
|
paperSize: ps?.paperSize,
|
|
1402
1507
|
showGridLines: false,
|
|
1508
|
+
blackAndWhite: ps?.blackAndWhite,
|
|
1509
|
+
draft: ps?.draft,
|
|
1403
1510
|
firstPageNumber: ps?.useFirstPageNumber === false ? undefined : ps?.firstPageNumber,
|
|
1404
1511
|
margins: margins
|
|
1405
1512
|
? {
|
|
@@ -1421,7 +1528,7 @@ async function convertChartsheet(cs, workbook) {
|
|
|
1421
1528
|
orientation,
|
|
1422
1529
|
chart,
|
|
1423
1530
|
pageSetup,
|
|
1424
|
-
headerFooter: convertHeaderFooterModel((0, chartsheet_1.chartsheetModel)(cs).headerFooter, convertChartsheetHeaderImages((0, chartsheet_1.chartsheetModel)(cs).headerImages, workbook))
|
|
1531
|
+
headerFooter: convertHeaderFooterModel((0, chartsheet_1.chartsheetModel)(cs).headerFooter, draftQuality ? [] : convertChartsheetHeaderImages((0, chartsheet_1.chartsheetModel)(cs).headerImages, workbook))
|
|
1425
1532
|
};
|
|
1426
1533
|
}
|
|
1427
1534
|
function convertChartsheetHeaderImages(images, workbook) {
|
|
@@ -21,6 +21,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
21
21
|
exports.createChartSurface = createChartSurface;
|
|
22
22
|
const font_manager_1 = require("../font/font-manager.js");
|
|
23
23
|
const page_renderer_1 = require("./page-renderer.js");
|
|
24
|
+
const style_converter_1 = require("./style-converter.js");
|
|
24
25
|
/**
|
|
25
26
|
* Build a {@link PdfChartDrawingSurface} that forwards to the given content
|
|
26
27
|
* stream. The returned surface is stateful — callers should not mix it with
|
|
@@ -31,38 +32,77 @@ const page_renderer_1 = require("./page-renderer.js");
|
|
|
31
32
|
* @param alphaValues Shared set that accumulates transparency values for
|
|
32
33
|
* later `ExtGState` registration. The surface adds any
|
|
33
34
|
* `color.a` values it observes to this set.
|
|
35
|
+
* @param grayscale When true, every color the chart renderer emits is
|
|
36
|
+
* converted to grayscale. Charts bypass the cell style
|
|
37
|
+
* pipeline entirely — the chart engine pushes colors
|
|
38
|
+
* straight into this surface — so Excel's "Black and
|
|
39
|
+
* white" print option has to be applied right here.
|
|
34
40
|
*/
|
|
35
|
-
function createChartSurface(stream, fontManager, alphaValues) {
|
|
41
|
+
function createChartSurface(stream, fontManager, alphaValues, grayscale = false) {
|
|
42
|
+
/** Single choke point for every color entering the surface. */
|
|
43
|
+
const mapColor = (color) => grayscale && color ? (0, style_converter_1.toGrayscale)(color) : color;
|
|
36
44
|
const applyAlpha = (color) => {
|
|
37
45
|
if (color?.a !== undefined && color.a < 1) {
|
|
38
46
|
alphaValues.add(color.a);
|
|
39
47
|
stream.setGraphicsState((0, page_renderer_1.alphaGsName)(color.a));
|
|
40
48
|
}
|
|
41
49
|
};
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Fill and/or stroke the path built by `buildPath`.
|
|
52
|
+
*
|
|
53
|
+
* `buildPath` is a closure rather than a pre-built path because a fill and a
|
|
54
|
+
* stroke with *different* opacities cannot share one graphics state: the
|
|
55
|
+
* alpha `ExtGState` sets `/ca` and `/CA` together, and `fillAndStroke()`
|
|
56
|
+
* paints both under whichever was applied last. In that case we paint in two
|
|
57
|
+
* passes and rebuild the path for the second one. (Within a single pass the
|
|
58
|
+
* unused half of the ExtGState is harmless — a fill-only pass never strokes.)
|
|
59
|
+
*/
|
|
60
|
+
const paintFillStroke = (buildPath, options) => {
|
|
61
|
+
const { lineWidth, dashPattern } = options;
|
|
62
|
+
const fill = mapColor(options.fill);
|
|
63
|
+
const stroke = mapColor(options.stroke);
|
|
64
|
+
const applyDash = () => {
|
|
65
|
+
if (dashPattern && dashPattern.length > 0) {
|
|
66
|
+
stream.setDashPattern(dashPattern);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
if (fill && stroke) {
|
|
70
|
+
const fillAlpha = fill.a ?? 1;
|
|
71
|
+
const strokeAlpha = stroke.a ?? 1;
|
|
72
|
+
if (fillAlpha !== strokeAlpha) {
|
|
73
|
+
stream.save();
|
|
74
|
+
buildPath();
|
|
75
|
+
stream.setFillColor(fill);
|
|
76
|
+
applyAlpha(fill);
|
|
77
|
+
stream.fill();
|
|
78
|
+
stream.restore();
|
|
79
|
+
stream.save();
|
|
80
|
+
buildPath();
|
|
81
|
+
stream.setStrokeColor(stroke);
|
|
82
|
+
applyAlpha(stroke);
|
|
83
|
+
applyDash();
|
|
84
|
+
if (lineWidth !== undefined) {
|
|
85
|
+
stream.setLineWidth(lineWidth);
|
|
86
|
+
}
|
|
87
|
+
stream.stroke();
|
|
88
|
+
stream.restore();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
buildPath();
|
|
93
|
+
applyDash();
|
|
94
|
+
// Reaching here means either a single operation, or a fill and stroke that
|
|
95
|
+
// share one opacity — so one ExtGState describes both and no reset is
|
|
96
|
+
// needed (the differing case returned above, each pass in its own q/Q).
|
|
44
97
|
if (fill) {
|
|
45
98
|
stream.setFillColor(fill);
|
|
46
99
|
applyAlpha(fill);
|
|
47
100
|
}
|
|
48
101
|
if (stroke) {
|
|
49
102
|
stream.setStrokeColor(stroke);
|
|
50
|
-
|
|
51
|
-
// `a` field or `a >= 1`), we must explicitly restore opaque alpha
|
|
52
|
-
// so the stroke is not drawn with the fill's transparency. The
|
|
53
|
-
// ExtGState set by `applyAlpha(fill)` applies to both fill and
|
|
54
|
-
// stroke operations in PDF; without this reset the stroke leaks
|
|
55
|
-
// the fill's alpha.
|
|
56
|
-
const strokeNeedsAlpha = stroke.a !== undefined && stroke.a < 1;
|
|
57
|
-
const fillAppliedAlpha = fill?.a !== undefined && fill.a < 1;
|
|
58
|
-
if (strokeNeedsAlpha) {
|
|
103
|
+
if (!fill) {
|
|
59
104
|
applyAlpha(stroke);
|
|
60
105
|
}
|
|
61
|
-
else if (fillAppliedAlpha) {
|
|
62
|
-
// Reset to fully opaque for the stroke
|
|
63
|
-
alphaValues.add(1);
|
|
64
|
-
stream.setGraphicsState((0, page_renderer_1.alphaGsName)(1));
|
|
65
|
-
}
|
|
66
106
|
if (lineWidth !== undefined) {
|
|
67
107
|
stream.setLineWidth(lineWidth);
|
|
68
108
|
}
|
|
@@ -84,13 +124,13 @@ function createChartSurface(stream, fontManager, alphaValues) {
|
|
|
84
124
|
drawRect(options) {
|
|
85
125
|
const { x, y, width, height, fill, stroke, lineWidth } = options;
|
|
86
126
|
stream.save();
|
|
87
|
-
stream.rect(x, y, width, height);
|
|
88
|
-
paintFillStroke({ fill, stroke, lineWidth });
|
|
127
|
+
paintFillStroke(() => stream.rect(x, y, width, height), { fill, stroke, lineWidth });
|
|
89
128
|
stream.restore();
|
|
90
129
|
return this;
|
|
91
130
|
},
|
|
92
131
|
drawLine(options) {
|
|
93
|
-
const { x1, y1, x2, y2,
|
|
132
|
+
const { x1, y1, x2, y2, lineWidth, dashPattern } = options;
|
|
133
|
+
const color = mapColor(options.color);
|
|
94
134
|
stream.save();
|
|
95
135
|
if (color) {
|
|
96
136
|
stream.setStrokeColor(color);
|
|
@@ -111,7 +151,7 @@ function createChartSurface(stream, fontManager, alphaValues) {
|
|
|
111
151
|
return this;
|
|
112
152
|
}
|
|
113
153
|
const fontSize = options.fontSize ?? 10;
|
|
114
|
-
const color = options.color ?? { r: 0, g: 0, b: 0 };
|
|
154
|
+
const color = mapColor(options.color ?? { r: 0, g: 0, b: 0 });
|
|
115
155
|
const bold = options.bold ?? false;
|
|
116
156
|
const italic = options.italic ?? false;
|
|
117
157
|
const fontFamily = options.fontFamily ?? "Helvetica";
|
|
@@ -143,39 +183,38 @@ function createChartSurface(stream, fontManager, alphaValues) {
|
|
|
143
183
|
drawCircle(options) {
|
|
144
184
|
const { cx, cy, r, fill, stroke, lineWidth } = options;
|
|
145
185
|
stream.save();
|
|
146
|
-
stream.circle(cx, cy, r);
|
|
147
|
-
paintFillStroke({ fill, stroke, lineWidth });
|
|
186
|
+
paintFillStroke(() => stream.circle(cx, cy, r), { fill, stroke, lineWidth });
|
|
148
187
|
stream.restore();
|
|
149
188
|
return this;
|
|
150
189
|
},
|
|
151
190
|
drawPath(ops, options) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
191
|
+
const buildPath = () => {
|
|
192
|
+
for (const op of ops) {
|
|
193
|
+
switch (op.op) {
|
|
194
|
+
case "move":
|
|
195
|
+
stream.moveTo(op.x, op.y);
|
|
196
|
+
break;
|
|
197
|
+
case "line":
|
|
198
|
+
stream.lineTo(op.x, op.y);
|
|
199
|
+
break;
|
|
200
|
+
case "curve":
|
|
201
|
+
stream.curveTo(op.x1, op.y1, op.x2, op.y2, op.x3, op.y3);
|
|
202
|
+
break;
|
|
203
|
+
case "close":
|
|
204
|
+
stream.closePath();
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
167
207
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
paintFillStroke({
|
|
208
|
+
if (options?.closePath) {
|
|
209
|
+
stream.closePath();
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
stream.save();
|
|
213
|
+
paintFillStroke(buildPath, {
|
|
176
214
|
fill: options?.fill,
|
|
177
215
|
stroke: options?.stroke,
|
|
178
|
-
lineWidth: options?.lineWidth
|
|
216
|
+
lineWidth: options?.lineWidth,
|
|
217
|
+
dashPattern: options?.dashPattern
|
|
179
218
|
});
|
|
180
219
|
stream.restore();
|
|
181
220
|
return this;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* use exactly the same values, preventing clipped or overlapping content.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.PX_TO_PT = exports.MAX_DIGIT_WIDTH_PX = exports.INDENT_WIDTH = exports.LINE_HEIGHT_FACTOR = exports.CELL_PADDING_V = exports.CELL_PADDING_H = void 0;
|
|
9
|
+
exports.PAGE_NUMBER_BAND_HEIGHT = exports.SHEET_NAME_BAND_HEIGHT = exports.COLUMN_FIT_EPSILON = exports.COMMENT_MARKER_COLOR = exports.COMMENT_BORDER = exports.COMMENT_FILL = exports.COMMENT_MARKER_SIZE = exports.FIT_MIN_SCALE = exports.HEADING_TEXT = exports.HEADING_RULE = exports.HEADING_FILL = exports.HEADING_PADDING = exports.HEADING_FONT_SIZE = exports.PX_TO_PT = exports.MAX_DIGIT_WIDTH_PX = exports.INDENT_WIDTH = exports.LINE_HEIGHT_FACTOR = exports.CELL_PADDING_V = exports.CELL_PADDING_H = void 0;
|
|
10
10
|
/** Horizontal cell padding in points (left + right = 2 × CELL_PADDING_H). */
|
|
11
11
|
exports.CELL_PADDING_H = 3;
|
|
12
12
|
/** Vertical cell padding in points (top + bottom = 2 × CELL_PADDING_V). */
|
|
@@ -31,3 +31,48 @@ exports.INDENT_WIDTH = 10;
|
|
|
31
31
|
*/
|
|
32
32
|
exports.MAX_DIGIT_WIDTH_PX = 7;
|
|
33
33
|
exports.PX_TO_PT = 72 / 96; // 0.75
|
|
34
|
+
/**
|
|
35
|
+
* Font size of the printed row-number / column-letter headings, in points.
|
|
36
|
+
*
|
|
37
|
+
* Deliberately independent of the grid's print scale: headings are a reading
|
|
38
|
+
* aid rather than content, so a fixed size stays legible even when the grid is
|
|
39
|
+
* shrunk to fit, and lets the layout reserve exactly the space it draws.
|
|
40
|
+
*/
|
|
41
|
+
exports.HEADING_FONT_SIZE = 8;
|
|
42
|
+
/** Padding around a heading label, in points (applied on both sides). */
|
|
43
|
+
exports.HEADING_PADDING = 3;
|
|
44
|
+
/** Background shade and rule color of the heading bands. */
|
|
45
|
+
exports.HEADING_FILL = { r: 0.94, g: 0.94, b: 0.94 };
|
|
46
|
+
exports.HEADING_RULE = { r: 0.6, g: 0.6, b: 0.6 };
|
|
47
|
+
exports.HEADING_TEXT = { r: 0.2, g: 0.2, b: 0.2 };
|
|
48
|
+
/**
|
|
49
|
+
* Lower bound when solving "fit to N pages", matching the 10% floor of Excel's
|
|
50
|
+
* Page Setup scaling. A target that is unreachable at this scale — typically
|
|
51
|
+
* because manual page breaks force more pages than requested — yields more than
|
|
52
|
+
* N pages rather than shrinking the grid into illegibility.
|
|
53
|
+
*/
|
|
54
|
+
exports.FIT_MIN_SCALE = 0.1;
|
|
55
|
+
/**
|
|
56
|
+
* Side length of the red corner marker Excel draws on a commented cell, in
|
|
57
|
+
* points at 100% scale.
|
|
58
|
+
*/
|
|
59
|
+
exports.COMMENT_MARKER_SIZE = 4;
|
|
60
|
+
/** Comment box fill, border and text colors, matching Excel's note styling. */
|
|
61
|
+
exports.COMMENT_FILL = { r: 1, g: 1, b: 0.88 };
|
|
62
|
+
exports.COMMENT_BORDER = { r: 0.4, g: 0.4, b: 0.4 };
|
|
63
|
+
exports.COMMENT_MARKER_COLOR = { r: 0.8, g: 0, b: 0 };
|
|
64
|
+
/**
|
|
65
|
+
* Overflow tolerance when packing columns into a page, in points.
|
|
66
|
+
*
|
|
67
|
+
* Scaled point widths accumulate rounding error, so a column that mathematically
|
|
68
|
+
* fits can measure a hair too wide. Rows need no such slack because their
|
|
69
|
+
* heights are not derived from a character-width conversion.
|
|
70
|
+
*/
|
|
71
|
+
exports.COLUMN_FIT_EPSILON = 0.01;
|
|
72
|
+
/**
|
|
73
|
+
* Height of the bands reserved for the fallback sheet-name header and
|
|
74
|
+
* page-number footer, in points. Excel header/footers use their own margins;
|
|
75
|
+
* these are the simple `showSheetNames` / `showPageNumbers` bands.
|
|
76
|
+
*/
|
|
77
|
+
exports.SHEET_NAME_BAND_HEIGHT = 20;
|
|
78
|
+
exports.PAGE_NUMBER_BAND_HEIGHT = 20;
|