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