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
|
@@ -25,6 +25,7 @@ const image_utils_1 = require("../builder/image-utils.js");
|
|
|
25
25
|
const pdf_stream_1 = require("../core/pdf-stream.js");
|
|
26
26
|
const font_manager_1 = require("../font/font-manager.js");
|
|
27
27
|
const constants_1 = require("./constants.js");
|
|
28
|
+
const style_converter_1 = require("./style-converter.js");
|
|
28
29
|
function computeCellPadding(cell, scaleFactor = 1) {
|
|
29
30
|
return {
|
|
30
31
|
left: (constants_1.CELL_PADDING_H + cell.borderInsets.left) * scaleFactor,
|
|
@@ -113,6 +114,14 @@ function renderPage(page, options, fontManager, totalPages) {
|
|
|
113
114
|
drawCellText(stream, cell, fontManager, alphaValues, sf);
|
|
114
115
|
}
|
|
115
116
|
}
|
|
117
|
+
// --- Step 5b: Draw printed row/column headings ---
|
|
118
|
+
if (options.showRowColHeaders) {
|
|
119
|
+
drawRowColHeadings(stream, page, options, fontManager);
|
|
120
|
+
}
|
|
121
|
+
// --- Step 5c: Draw comment boxes over the grid ---
|
|
122
|
+
if (page.commentBoxes?.length) {
|
|
123
|
+
drawCommentBoxes(stream, page, options, fontManager);
|
|
124
|
+
}
|
|
116
125
|
// --- Step 5: Draw page header (sheet name) ---
|
|
117
126
|
const excelHeader = selectHeaderFooter(page, "header");
|
|
118
127
|
const excelFooter = selectHeaderFooter(page, "footer");
|
|
@@ -1355,7 +1364,11 @@ function drawHeaderFooterSection(stream, page, fontManager, runs, anchorX, basel
|
|
|
1355
1364
|
: run.subscript
|
|
1356
1365
|
? -fontSize * HEADER_FOOTER_METRICS.SUBSCRIPT_DROP
|
|
1357
1366
|
: 0;
|
|
1358
|
-
|
|
1367
|
+
// Header/footer runs carry their own `&K` colors, which bypass the cell
|
|
1368
|
+
// style pipeline — apply black-and-white here.
|
|
1369
|
+
const bw = page.options.blackAndWhite;
|
|
1370
|
+
const rawColor = run.color ?? { r: 0, g: 0, b: 0 };
|
|
1371
|
+
const color = bw ? (0, style_converter_1.toGrayscale)(rawColor) : rawColor;
|
|
1359
1372
|
stream.save();
|
|
1360
1373
|
stream.setFillColor(color);
|
|
1361
1374
|
stream.setStrokeColor(color);
|
|
@@ -1404,6 +1417,169 @@ function resolveLineRuns(line, page, fontManager) {
|
|
|
1404
1417
|
return { run, text, resourceName, fontSize, width };
|
|
1405
1418
|
});
|
|
1406
1419
|
}
|
|
1420
|
+
// =============================================================================
|
|
1421
|
+
// Row / Column Headings
|
|
1422
|
+
// =============================================================================
|
|
1423
|
+
/** Convert a 1-based column number to its Excel letters (1 → "A", 27 → "AA"). */
|
|
1424
|
+
function columnNumberToLetters(col) {
|
|
1425
|
+
let n = col;
|
|
1426
|
+
let out = "";
|
|
1427
|
+
while (n > 0) {
|
|
1428
|
+
const rem = (n - 1) % 26;
|
|
1429
|
+
out = String.fromCharCode(65 + rem) + out;
|
|
1430
|
+
n = Math.floor((n - 1) / 26);
|
|
1431
|
+
}
|
|
1432
|
+
return out;
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Draw Excel's printed row numbers and column letters around the grid.
|
|
1436
|
+
*
|
|
1437
|
+
* The bands occupy space reserved by the layout engine (see
|
|
1438
|
+
* `computeHeadingMetrics`), so the grid origin already accounts for them and
|
|
1439
|
+
* nothing here can push content off the page.
|
|
1440
|
+
*/
|
|
1441
|
+
function drawRowColHeadings(stream, page, options, fontManager) {
|
|
1442
|
+
const headings = page.headings;
|
|
1443
|
+
if (!headings || page.columnWidths.length === 0 || page.rowHeights.length === 0) {
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
const { gutterWidth, bandHeight, fontSize } = headings;
|
|
1447
|
+
const resourceName = fontManager.hasEmbeddedFont()
|
|
1448
|
+
? fontManager.getEmbeddedResourceName()
|
|
1449
|
+
: fontManager.ensureFont((0, font_manager_1.resolvePdfFontName)(options.defaultFontFamily, false, false));
|
|
1450
|
+
// The heading palette is already neutral gray, so black-and-white needs no
|
|
1451
|
+
// conversion here.
|
|
1452
|
+
const fill = constants_1.HEADING_FILL;
|
|
1453
|
+
const rule = constants_1.HEADING_RULE;
|
|
1454
|
+
const textColor = constants_1.HEADING_TEXT;
|
|
1455
|
+
const gridLeft = page.columnOffsets[0];
|
|
1456
|
+
const lastCol = page.columnWidths.length - 1;
|
|
1457
|
+
const gridRight = page.columnOffsets[lastCol] + page.columnWidths[lastCol];
|
|
1458
|
+
const gridTop = page.rowYPositions[0];
|
|
1459
|
+
const lastRow = page.rowHeights.length - 1;
|
|
1460
|
+
const gridBottom = page.rowYPositions[lastRow] - page.rowHeights[lastRow];
|
|
1461
|
+
stream.save();
|
|
1462
|
+
// Band backgrounds: column letters above the grid, row numbers to its left,
|
|
1463
|
+
// plus the corner square where the two meet.
|
|
1464
|
+
stream.fillRect(gridLeft, gridTop, gridRight - gridLeft, bandHeight, fill);
|
|
1465
|
+
stream.fillRect(gridLeft - gutterWidth, gridBottom, gutterWidth, gridTop - gridBottom, fill);
|
|
1466
|
+
stream.fillRect(gridLeft - gutterWidth, gridTop, gutterWidth, bandHeight, fill);
|
|
1467
|
+
// Rules: one per column boundary through the top band, one per row boundary
|
|
1468
|
+
// through the gutter, and the two outer edges.
|
|
1469
|
+
stream.setStrokeColor(rule);
|
|
1470
|
+
stream.setLineWidth(0.25);
|
|
1471
|
+
for (let i = 0; i <= page.columnWidths.length; i++) {
|
|
1472
|
+
const x = i < page.columnWidths.length
|
|
1473
|
+
? page.columnOffsets[i]
|
|
1474
|
+
: page.columnOffsets[i - 1] + page.columnWidths[i - 1];
|
|
1475
|
+
stream.moveTo(x, gridTop);
|
|
1476
|
+
stream.lineTo(x, gridTop + bandHeight);
|
|
1477
|
+
}
|
|
1478
|
+
for (let i = 0; i <= page.rowYPositions.length; i++) {
|
|
1479
|
+
const y = i < page.rowYPositions.length
|
|
1480
|
+
? page.rowYPositions[i]
|
|
1481
|
+
: page.rowYPositions[i - 1] - page.rowHeights[i - 1];
|
|
1482
|
+
stream.moveTo(gridLeft - gutterWidth, y);
|
|
1483
|
+
stream.lineTo(gridLeft, y);
|
|
1484
|
+
}
|
|
1485
|
+
stream.moveTo(gridLeft - gutterWidth, gridTop + bandHeight);
|
|
1486
|
+
stream.lineTo(gridRight, gridTop + bandHeight);
|
|
1487
|
+
stream.moveTo(gridLeft - gutterWidth, gridBottom);
|
|
1488
|
+
stream.lineTo(gridLeft - gutterWidth, gridTop + bandHeight);
|
|
1489
|
+
stream.stroke();
|
|
1490
|
+
// Labels, centered in their band cell.
|
|
1491
|
+
stream.setFillColor(textColor);
|
|
1492
|
+
const baselineOffset = (bandHeight - fontSize) / 2 + fontSize * 0.2;
|
|
1493
|
+
for (let i = 0; i < page.sheetCols.length; i++) {
|
|
1494
|
+
const label = columnNumberToLetters(page.sheetCols[i]);
|
|
1495
|
+
fontManager.trackText(label);
|
|
1496
|
+
const w = fontManager.measureText(label, resourceName, fontSize);
|
|
1497
|
+
const x = page.columnOffsets[i] + (page.columnWidths[i] - w) / 2;
|
|
1498
|
+
emitTextWithMatrix(stream, fontManager, {
|
|
1499
|
+
text: label,
|
|
1500
|
+
matrix: [1, 0, 0, 1, x, gridTop + baselineOffset],
|
|
1501
|
+
resourceName,
|
|
1502
|
+
fontSize
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
for (let i = 0; i < page.sheetRows.length; i++) {
|
|
1506
|
+
const label = String(page.sheetRows[i]);
|
|
1507
|
+
fontManager.trackText(label);
|
|
1508
|
+
const w = fontManager.measureText(label, resourceName, fontSize);
|
|
1509
|
+
const rowH = page.rowHeights[i];
|
|
1510
|
+
const x = gridLeft - gutterWidth + (gutterWidth - w) / 2;
|
|
1511
|
+
const y = page.rowYPositions[i] - rowH + (rowH - fontSize) / 2 + fontSize * 0.2;
|
|
1512
|
+
emitTextWithMatrix(stream, fontManager, {
|
|
1513
|
+
text: label,
|
|
1514
|
+
matrix: [1, 0, 0, 1, x, y],
|
|
1515
|
+
resourceName,
|
|
1516
|
+
fontSize
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
stream.restore();
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* Draw Excel's on-sheet comment boxes and their cell corner markers.
|
|
1523
|
+
*
|
|
1524
|
+
* Positioned by the layout engine, so this only has to paint: a pale note-yellow
|
|
1525
|
+
* box with a thin border, the wrapped body inside it, and the small red triangle
|
|
1526
|
+
* Excel puts in the commented cell's top-right corner.
|
|
1527
|
+
*/
|
|
1528
|
+
function drawCommentBoxes(stream, page, options, fontManager) {
|
|
1529
|
+
const boxes = page.commentBoxes;
|
|
1530
|
+
if (!boxes?.length) {
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
const bw = options.blackAndWhite;
|
|
1534
|
+
const fill = bw ? (0, style_converter_1.toGrayscale)(constants_1.COMMENT_FILL) : constants_1.COMMENT_FILL;
|
|
1535
|
+
const border = bw ? (0, style_converter_1.toGrayscale)(constants_1.COMMENT_BORDER) : constants_1.COMMENT_BORDER;
|
|
1536
|
+
const markerColor = bw ? (0, style_converter_1.toGrayscale)(constants_1.COMMENT_MARKER_COLOR) : constants_1.COMMENT_MARKER_COLOR;
|
|
1537
|
+
const resourceName = fontManager.hasEmbeddedFont()
|
|
1538
|
+
? fontManager.getEmbeddedResourceName()
|
|
1539
|
+
: fontManager.ensureFont((0, font_manager_1.resolvePdfFontName)(options.defaultFontFamily, false, false));
|
|
1540
|
+
for (const box of boxes) {
|
|
1541
|
+
const { rect } = box;
|
|
1542
|
+
stream.save();
|
|
1543
|
+
stream.fillRect(rect.x, rect.y, rect.width, rect.height, fill);
|
|
1544
|
+
stream.setStrokeColor(border);
|
|
1545
|
+
stream.setLineWidth(0.5);
|
|
1546
|
+
stream.rect(rect.x, rect.y, rect.width, rect.height);
|
|
1547
|
+
stream.stroke();
|
|
1548
|
+
// Body text, wrapped to the box and clipped by simply stopping once the
|
|
1549
|
+
// remaining height is used up.
|
|
1550
|
+
const lineHeight = box.fontSize * constants_1.LINE_HEIGHT_FACTOR;
|
|
1551
|
+
const innerWidth = Math.max(rect.width - 2 * constants_1.CELL_PADDING_H, 1);
|
|
1552
|
+
const measure = (t) => fontManager.measureText(t, resourceName, box.fontSize);
|
|
1553
|
+
const lines = box.text.split("\n").flatMap(part => wrapTextLines(part, measure, innerWidth));
|
|
1554
|
+
stream.setFillColor(bw ? (0, style_converter_1.toGrayscale)({ r: 0, g: 0, b: 0 }) : { r: 0, g: 0, b: 0 });
|
|
1555
|
+
let baseline = rect.y + rect.height - constants_1.CELL_PADDING_V - box.fontSize;
|
|
1556
|
+
for (const line of lines) {
|
|
1557
|
+
if (baseline < rect.y + constants_1.CELL_PADDING_V) {
|
|
1558
|
+
break;
|
|
1559
|
+
}
|
|
1560
|
+
fontManager.trackText(line);
|
|
1561
|
+
emitTextWithMatrix(stream, fontManager, {
|
|
1562
|
+
text: line,
|
|
1563
|
+
matrix: [1, 0, 0, 1, rect.x + constants_1.CELL_PADDING_H, baseline],
|
|
1564
|
+
resourceName,
|
|
1565
|
+
fontSize: box.fontSize
|
|
1566
|
+
});
|
|
1567
|
+
baseline -= lineHeight;
|
|
1568
|
+
}
|
|
1569
|
+
stream.restore();
|
|
1570
|
+
if (box.marker) {
|
|
1571
|
+
const { x, y, size } = box.marker;
|
|
1572
|
+
stream.save();
|
|
1573
|
+
stream.setFillColor(markerColor);
|
|
1574
|
+
stream.moveTo(x - size, y);
|
|
1575
|
+
stream.lineTo(x, y);
|
|
1576
|
+
stream.lineTo(x, y - size);
|
|
1577
|
+
stream.closePath();
|
|
1578
|
+
stream.fill();
|
|
1579
|
+
stream.restore();
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1407
1583
|
function drawPageHeader(stream, page, options, fontManager) {
|
|
1408
1584
|
const headerFontSize = 10;
|
|
1409
1585
|
const headerText = page.sheetName;
|
|
@@ -1507,7 +1683,8 @@ function clamp01(v) {
|
|
|
1507
1683
|
*/
|
|
1508
1684
|
function renderTextWatermark(stream, page, watermark, fontManager) {
|
|
1509
1685
|
const fontSize = watermark.fontSize ?? TEXT_WM_DEFAULTS.fontSize;
|
|
1510
|
-
const
|
|
1686
|
+
const rawColor = watermark.color ?? TEXT_WM_DEFAULTS.color;
|
|
1687
|
+
const color = page.options.blackAndWhite ? (0, style_converter_1.toGrayscale)(rawColor) : rawColor;
|
|
1511
1688
|
const opacity = watermark.opacity ?? TEXT_WM_DEFAULTS.opacity;
|
|
1512
1689
|
const rotation = watermark.rotation ?? TEXT_WM_DEFAULTS.rotation;
|
|
1513
1690
|
const fontFamily = watermark.fontFamily ?? TEXT_WM_DEFAULTS.fontFamily;
|
|
@@ -148,7 +148,9 @@ function preflightCharts(allPages, fontManager) {
|
|
|
148
148
|
}
|
|
149
149
|
const stream = new pdf_stream_1.PdfContentStream();
|
|
150
150
|
const alphaValues = new Set();
|
|
151
|
-
|
|
151
|
+
// Black-and-white is a per-sheet print option, and each page carries the
|
|
152
|
+
// options of the sheet that produced it.
|
|
153
|
+
const surface = (0, chart_surface_1.createChartSurface)(stream, fontManager, alphaValues, page.options.blackAndWhite);
|
|
152
154
|
chart.drawVector(surface, constrainChartAspectRatio(chart.rect));
|
|
153
155
|
cache.set(chart, { stream, alphaValues });
|
|
154
156
|
}
|
|
@@ -270,6 +272,10 @@ function renderSinglePage(page, fontManager, writer, fontObjectMap, chartCache,
|
|
|
270
272
|
const { stream: contentStream, alphaValues } = (0, page_renderer_1.renderPage)(page, page.options, fontManager, totalPages);
|
|
271
273
|
// Handle images: create XObject Image entries and draw them
|
|
272
274
|
const imageXObjects = new Map();
|
|
275
|
+
// Raster payloads are converted when the XObject is written — a real pixel
|
|
276
|
+
// conversion for PNG, a `/DeviceN` luma space for JPEG — so no overlay is
|
|
277
|
+
// painted and transparency is preserved.
|
|
278
|
+
const grayscaleRaster = page.options.blackAndWhite;
|
|
273
279
|
let headerImageContentObjNum;
|
|
274
280
|
const headerContent = (0, page_renderer_1.selectHeaderFooter)(page, "header");
|
|
275
281
|
const footerContent = (0, page_renderer_1.selectHeaderFooter)(page, "footer");
|
|
@@ -285,23 +291,28 @@ function renderSinglePage(page, fontManager, writer, fontObjectMap, chartCache,
|
|
|
285
291
|
const rightEdge = alignWithMargins
|
|
286
292
|
? page.width - page.options.margins.right
|
|
287
293
|
: page.width - 18;
|
|
294
|
+
// `scaleWithDoc` governs the whole header/footer, not just its text, so a
|
|
295
|
+
// logo has to shrink with the grid the same way the runs do.
|
|
296
|
+
const hfScale = page.headerFooter?.scaleWithDoc === false ? 1 : page.scaleFactor;
|
|
288
297
|
for (let index = 0; index < headerImages.length; index++) {
|
|
289
298
|
const headerImage = headerImages[index];
|
|
290
299
|
if (!activePositions.has(headerImage.position)) {
|
|
291
300
|
continue;
|
|
292
301
|
}
|
|
293
302
|
const imageName = `HfIm${index + 1}`;
|
|
294
|
-
imageXObjects.set(imageName, getOrWriteImageObject(writer, imageObjectCache, headerImage.data, headerImage.format));
|
|
303
|
+
imageXObjects.set(imageName, getOrWriteImageObject(writer, imageObjectCache, headerImage.data, headerImage.format, grayscaleRaster));
|
|
304
|
+
const drawWidth = headerImage.width * hfScale;
|
|
305
|
+
const drawHeight = headerImage.height * hfScale;
|
|
295
306
|
const placement = headerFooterImagePlacement(headerImage.position);
|
|
296
307
|
const imageX = placement.section === "left"
|
|
297
308
|
? leftEdge
|
|
298
309
|
: placement.section === "right"
|
|
299
|
-
? rightEdge -
|
|
300
|
-
: (page.width -
|
|
310
|
+
? rightEdge - drawWidth
|
|
311
|
+
: (page.width - drawWidth) / 2;
|
|
301
312
|
const imageY = placement.kind === "header"
|
|
302
|
-
? page.height - page.options.headerMargin -
|
|
313
|
+
? page.height - page.options.headerMargin - drawHeight
|
|
303
314
|
: page.options.footerMargin;
|
|
304
|
-
imageStream.drawImage(imageName, imageX, imageY,
|
|
315
|
+
imageStream.drawImage(imageName, imageX, imageY, drawWidth, drawHeight);
|
|
305
316
|
}
|
|
306
317
|
headerImageContentObjNum = writer.allocObject();
|
|
307
318
|
writer.addStreamObject(headerImageContentObjNum, new pdf_object_1.PdfDict(), imageStream);
|
|
@@ -310,7 +321,7 @@ function renderSinglePage(page, fontManager, writer, fontObjectMap, chartCache,
|
|
|
310
321
|
for (let imgIdx = 0; imgIdx < page.images.length; imgIdx++) {
|
|
311
322
|
const img = page.images[imgIdx];
|
|
312
323
|
const imgName = `Im${imgIdx + 1}`;
|
|
313
|
-
const imgObjNum = getOrWriteImageObject(writer, imageObjectCache, img.data, img.format);
|
|
324
|
+
const imgObjNum = getOrWriteImageObject(writer, imageObjectCache, img.data, img.format, grayscaleRaster);
|
|
314
325
|
imageXObjects.set(imgName, imgObjNum);
|
|
315
326
|
contentStream.drawImage(imgName, img.rect.x, img.rect.y, img.rect.width, img.rect.height);
|
|
316
327
|
}
|
|
@@ -339,7 +350,7 @@ function renderSinglePage(page, fontManager, writer, fontObjectMap, chartCache,
|
|
|
339
350
|
}
|
|
340
351
|
if (chart.raster) {
|
|
341
352
|
const imgName = `Im${++rasterCounter}`;
|
|
342
|
-
const imgObjNum = getOrWriteImageObject(writer, imageObjectCache, chart.raster.data, chart.raster.format);
|
|
353
|
+
const imgObjNum = getOrWriteImageObject(writer, imageObjectCache, chart.raster.data, chart.raster.format, grayscaleRaster);
|
|
343
354
|
imageXObjects.set(imgName, imgObjNum);
|
|
344
355
|
contentStream.drawImage(imgName, chart.rect.x, chart.rect.y, chart.rect.width, chart.rect.height);
|
|
345
356
|
}
|
|
@@ -359,7 +370,7 @@ function renderSinglePage(page, fontManager, writer, fontObjectMap, chartCache,
|
|
|
359
370
|
}
|
|
360
371
|
// Register watermark image XObjects
|
|
361
372
|
for (const wmImg of wmResult.imageXObjects) {
|
|
362
|
-
const imgObjNum = getOrWriteImageObject(writer, imageObjectCache, wmImg.data, wmImg.format);
|
|
373
|
+
const imgObjNum = getOrWriteImageObject(writer, imageObjectCache, wmImg.data, wmImg.format, grayscaleRaster);
|
|
363
374
|
imageXObjects.set(wmImg.name, imgObjNum);
|
|
364
375
|
}
|
|
365
376
|
// Write watermark content stream object
|
|
@@ -440,6 +451,8 @@ function renderSinglePage(page, fontManager, writer, fontObjectMap, chartCache,
|
|
|
440
451
|
height: page.height,
|
|
441
452
|
contentsRef: contentsRef,
|
|
442
453
|
resourcesRef: resourcesObjNum,
|
|
454
|
+
// Constant alpha needs a defined backdrop.
|
|
455
|
+
transparencyGroup: alphaValues.size > 0,
|
|
443
456
|
annotRefs: annotRefs.length > 0 ? annotRefs : undefined
|
|
444
457
|
});
|
|
445
458
|
pageObjNums.push(pageObjNum);
|
|
@@ -453,13 +466,18 @@ function renderSinglePage(page, fontManager, writer, fontObjectMap, chartCache,
|
|
|
453
466
|
});
|
|
454
467
|
}
|
|
455
468
|
}
|
|
456
|
-
function getOrWriteImageObject(writer, cache, data, format) {
|
|
457
|
-
|
|
469
|
+
function getOrWriteImageObject(writer, cache, data, format, grayscale = false) {
|
|
470
|
+
let variants = cache.get(data);
|
|
471
|
+
if (variants === undefined) {
|
|
472
|
+
variants = new Map();
|
|
473
|
+
cache.set(data, variants);
|
|
474
|
+
}
|
|
475
|
+
const existing = variants.get(grayscale);
|
|
458
476
|
if (existing !== undefined) {
|
|
459
477
|
return existing;
|
|
460
478
|
}
|
|
461
|
-
const objectNumber = (0, image_utils_1.writeImageXObject)(writer, data, format);
|
|
462
|
-
|
|
479
|
+
const objectNumber = (0, image_utils_1.writeImageXObject)(writer, data, format, grayscale);
|
|
480
|
+
variants.set(grayscale, objectNumber);
|
|
463
481
|
return objectNumber;
|
|
464
482
|
}
|
|
465
483
|
function headerFooterImagePositions(content, kind) {
|
|
@@ -543,6 +561,110 @@ function selectSheets(workbook, sheets) {
|
|
|
543
561
|
// =============================================================================
|
|
544
562
|
// Options Resolution
|
|
545
563
|
// =============================================================================
|
|
564
|
+
/**
|
|
565
|
+
* Parse an Excel "Columns to repeat at left" reference (`printTitlesColumn`)
|
|
566
|
+
* into an absolute column band, e.g. `"A:B"` → `{ first: 1, last: 2 }`.
|
|
567
|
+
*
|
|
568
|
+
* Returns `null` for anything that is not a column range.
|
|
569
|
+
*/
|
|
570
|
+
function parseTitlesColumnBand(ref) {
|
|
571
|
+
const match = ref
|
|
572
|
+
.replace(/\$/g, "")
|
|
573
|
+
.toUpperCase()
|
|
574
|
+
.match(/^([A-Z]+)(?::([A-Z]+))?$/);
|
|
575
|
+
if (!match) {
|
|
576
|
+
return null;
|
|
577
|
+
}
|
|
578
|
+
const first = columnLettersToNumber(match[1]);
|
|
579
|
+
const last = match[2] ? columnLettersToNumber(match[2]) : first;
|
|
580
|
+
if (last < first) {
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
return { first, last };
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Parse an Excel "Rows to repeat at top" reference (`printTitlesRow`) into an
|
|
587
|
+
* absolute row band, e.g. `"1:3"` → `{ first: 1, last: 3 }`.
|
|
588
|
+
*/
|
|
589
|
+
function parseTitlesRowBand(ref) {
|
|
590
|
+
const match = ref.replace(/\$/g, "").match(/^(\d+)(?::(\d+))?$/);
|
|
591
|
+
if (!match) {
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
const first = parseInt(match[1], 10);
|
|
595
|
+
const last = match[2] ? parseInt(match[2], 10) : first;
|
|
596
|
+
if (first < 1 || last < first) {
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
return { first, last };
|
|
600
|
+
}
|
|
601
|
+
function columnLettersToNumber(letters) {
|
|
602
|
+
let n = 0;
|
|
603
|
+
for (let i = 0; i < letters.length; i++) {
|
|
604
|
+
n = n * 26 + (letters.charCodeAt(i) - 64);
|
|
605
|
+
}
|
|
606
|
+
return n;
|
|
607
|
+
}
|
|
608
|
+
function normalizePageOrder(value) {
|
|
609
|
+
return value === "overThenDown" ? "overThenDown" : "downThenOver";
|
|
610
|
+
}
|
|
611
|
+
/** Excel writes `None` / `asDisplayed` / `atEnd`. */
|
|
612
|
+
function normalizeCommentMode(value) {
|
|
613
|
+
if (value === "atEnd" || value === "asDisplayed") {
|
|
614
|
+
return value;
|
|
615
|
+
}
|
|
616
|
+
return "none";
|
|
617
|
+
}
|
|
618
|
+
function normalizeErrorMode(value) {
|
|
619
|
+
switch (value) {
|
|
620
|
+
case "blank":
|
|
621
|
+
case "dash":
|
|
622
|
+
case "NA":
|
|
623
|
+
return value;
|
|
624
|
+
default:
|
|
625
|
+
return "displayed";
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Reconcile documonster's scaling options with Excel's two mutually exclusive
|
|
630
|
+
* scaling modes (Page Setup → Scaling):
|
|
631
|
+
*
|
|
632
|
+
* - **"Adjust to N %"** — `pageSetup.scale`, with `pageSetup.fitToPage` off.
|
|
633
|
+
* - **"Fit to N pages wide by M tall"** — `pageSetup.fitToWidth` /
|
|
634
|
+
* `fitToHeight`, with `pageSetup.fitToPage` on. Excel ignores `scale` here.
|
|
635
|
+
*
|
|
636
|
+
* Neither mode ever enlarges: Excel caps fit-to-page scaling at 100%.
|
|
637
|
+
*
|
|
638
|
+
* `PdfExportOptions.fitToPage` is documonster's own convenience default
|
|
639
|
+
* ("shrink to one page wide"). It only applies when neither the caller nor the
|
|
640
|
+
* sheet expresses a scaling intent, so a sheet that asks for 80% is not
|
|
641
|
+
* shrunk twice.
|
|
642
|
+
*/
|
|
643
|
+
function resolveScaling(options, ps) {
|
|
644
|
+
const clampPages = (n) => n === undefined || !Number.isFinite(n) || n <= 0 ? 0 : Math.floor(n);
|
|
645
|
+
const callerFit = options?.fitToWidth !== undefined || options?.fitToHeight !== undefined;
|
|
646
|
+
const sheetFitMode = ps?.fitToPage === true;
|
|
647
|
+
let fitToWidth = 0;
|
|
648
|
+
let fitToHeight = 0;
|
|
649
|
+
if (callerFit) {
|
|
650
|
+
fitToWidth = clampPages(options?.fitToWidth);
|
|
651
|
+
fitToHeight = clampPages(options?.fitToHeight);
|
|
652
|
+
}
|
|
653
|
+
else if (sheetFitMode && options?.scale === undefined && options?.fitToPage !== false) {
|
|
654
|
+
// `fitToPage: false` is the caller saying "do not auto-scale"; it has to
|
|
655
|
+
// beat the sheet's own fit-to-N mode, or an explicit option would lose to
|
|
656
|
+
// the workbook it is meant to override.
|
|
657
|
+
fitToWidth = clampPages(ps?.fitToWidth);
|
|
658
|
+
fitToHeight = clampPages(ps?.fitToHeight);
|
|
659
|
+
}
|
|
660
|
+
// A sheet in "Adjust to N %" mode with a non-default percentage.
|
|
661
|
+
const sheetScalePercent = !sheetFitMode && ps?.scale !== undefined && ps.scale !== 100 ? ps.scale / 100 : undefined;
|
|
662
|
+
// Excel's Page Setup allows 10–400%, so the ceiling is 4.0 rather than 3.0.
|
|
663
|
+
const scale = Math.max(0.1, Math.min(4.0, options?.scale ?? sheetScalePercent ?? 1.0));
|
|
664
|
+
const hasFitConstraint = fitToWidth > 0 || fitToHeight > 0;
|
|
665
|
+
const fitToPage = options?.fitToPage ?? !(hasFitConstraint || sheetScalePercent !== undefined || callerFit);
|
|
666
|
+
return { fitToPage, scale, fitToWidth, fitToHeight };
|
|
667
|
+
}
|
|
546
668
|
/**
|
|
547
669
|
* Resolve user options with defaults.
|
|
548
670
|
*/
|
|
@@ -563,40 +685,59 @@ function resolveOptions(options, sheet) {
|
|
|
563
685
|
chartsheetOrientation ??
|
|
564
686
|
(ps?.orientation === "landscape" ? "landscape" : "portrait");
|
|
565
687
|
const margins = resolveMargins(options?.margins, ps?.margins);
|
|
688
|
+
const blackAndWhite = options?.blackAndWhite ?? ps?.blackAndWhite ?? false;
|
|
566
689
|
const gridLineColorStr = options?.gridLineColor ?? "FFD0D0D0";
|
|
567
|
-
const
|
|
690
|
+
const gridLineColorRaw = (0, style_converter_1.argbToPdfColor)(gridLineColorStr) ?? {
|
|
568
691
|
r: 0.816,
|
|
569
692
|
g: 0.816,
|
|
570
693
|
b: 0.816
|
|
571
694
|
};
|
|
572
|
-
|
|
573
|
-
//
|
|
574
|
-
// repeated
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
695
|
+
const gridLineColor = blackAndWhite ? (0, style_converter_1.toGrayscale)(gridLineColorRaw) : gridLineColorRaw;
|
|
696
|
+
// Repeated title rows/columns fall back to the sheet's print titles. Only
|
|
697
|
+
// PdfSheetData carries them — chartsheets have no repeated bands. An
|
|
698
|
+
// explicitly passed `false` suppresses the sheet setting, so "not provided"
|
|
699
|
+
// (undefined) and "provided as off" must stay distinguishable.
|
|
700
|
+
const isWorksheet = Boolean(sheet) && !(0, types_1.isPdfChartsheet)(sheet);
|
|
701
|
+
// A caller-supplied count means "the first N rows/columns of the sheet"; the
|
|
702
|
+
// sheet's own print titles are absolute ranges that may sit outside the print
|
|
703
|
+
// area, so both normalise to an absolute band.
|
|
704
|
+
const countToBand = (n) => typeof n === "number" && n > 0 ? { first: 1, last: n } : false;
|
|
705
|
+
let repeatRows = false;
|
|
706
|
+
if (options?.repeatRows !== undefined) {
|
|
707
|
+
repeatRows = countToBand(options.repeatRows);
|
|
708
|
+
}
|
|
709
|
+
else if (isWorksheet && ps?.printTitlesRow) {
|
|
710
|
+
repeatRows = parseTitlesRowBand(ps.printTitlesRow) ?? false;
|
|
711
|
+
}
|
|
712
|
+
let repeatCols = false;
|
|
713
|
+
if (options?.repeatCols !== undefined) {
|
|
714
|
+
repeatCols = countToBand(options.repeatCols);
|
|
715
|
+
}
|
|
716
|
+
else if (isWorksheet && ps?.printTitlesColumn) {
|
|
717
|
+
repeatCols = parseTitlesColumnBand(ps.printTitlesColumn) ?? false;
|
|
718
|
+
}
|
|
719
|
+
const { fitToPage, scale, fitToWidth, fitToHeight } = resolveScaling(options, ps);
|
|
583
720
|
return {
|
|
584
721
|
pageSize,
|
|
585
722
|
orientation,
|
|
586
723
|
margins,
|
|
587
724
|
ignorePrintArea: options?.ignorePrintArea ?? false,
|
|
588
|
-
fitToPage
|
|
589
|
-
scale
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
((options?.fitToPage !== undefined ? options.fitToPage : true)
|
|
593
|
-
? 1.0
|
|
594
|
-
: ps?.scale
|
|
595
|
-
? ps.scale / 100
|
|
596
|
-
: 1.0))),
|
|
725
|
+
fitToPage,
|
|
726
|
+
scale,
|
|
727
|
+
fitToWidth,
|
|
728
|
+
fitToHeight,
|
|
597
729
|
showGridLines: options?.showGridLines ?? ps?.showGridLines ?? false,
|
|
598
730
|
gridLineColor,
|
|
731
|
+
showRowColHeaders: options?.showRowColHeaders ?? ps?.showRowColHeaders ?? false,
|
|
732
|
+
horizontalCentered: options?.horizontalCentered ?? ps?.horizontalCentered ?? false,
|
|
733
|
+
verticalCentered: options?.verticalCentered ?? ps?.verticalCentered ?? false,
|
|
734
|
+
pageOrder: options?.pageOrder ?? normalizePageOrder(ps?.pageOrder),
|
|
735
|
+
blackAndWhite,
|
|
736
|
+
draft: options?.draft ?? ps?.draft ?? false,
|
|
737
|
+
errors: options?.errors ?? normalizeErrorMode(ps?.errors),
|
|
738
|
+
cellComments: options?.cellComments ?? normalizeCommentMode(ps?.cellComments),
|
|
599
739
|
repeatRows,
|
|
740
|
+
repeatCols,
|
|
600
741
|
defaultFontFamily: options?.defaultFontFamily ?? "Helvetica",
|
|
601
742
|
defaultFontSize: options?.defaultFontSize ?? 11,
|
|
602
743
|
showSheetNames: options?.showSheetNames ?? false,
|
|
@@ -13,6 +13,8 @@ exports.DEFAULT_COLORS = void 0;
|
|
|
13
13
|
exports.argbToPdfColor = argbToPdfColor;
|
|
14
14
|
exports.excelColorToPdf = excelColorToPdf;
|
|
15
15
|
exports.applyTint = applyTint;
|
|
16
|
+
exports.toGrayscale = toGrayscale;
|
|
17
|
+
exports.grayscaleBorders = grayscaleBorders;
|
|
16
18
|
exports.extractFontProperties = extractFontProperties;
|
|
17
19
|
exports.excelFillToPdfColor = excelFillToPdfColor;
|
|
18
20
|
exports.borderStyleToLineWidth = borderStyleToLineWidth;
|
|
@@ -177,6 +179,30 @@ function indexedColorToPdf(index) {
|
|
|
177
179
|
function applyTint(color, tint) {
|
|
178
180
|
return (0, theme_colors_1.applyTintRgb01)(color, tint);
|
|
179
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Convert a color to its luminance-preserving grayscale equivalent, using the
|
|
184
|
+
* Rec. 601 luma weights. Backs Excel's "Black and white" print option: hues are
|
|
185
|
+
* discarded while relative lightness — and therefore contrast — is kept.
|
|
186
|
+
*
|
|
187
|
+
* Opacity is carried through untouched; dropping it would make transparent
|
|
188
|
+
* chart fills opaque.
|
|
189
|
+
*/
|
|
190
|
+
function toGrayscale(color) {
|
|
191
|
+
const luma = 0.299 * color.r + 0.587 * color.g + 0.114 * color.b;
|
|
192
|
+
return color.a === undefined
|
|
193
|
+
? { r: luma, g: luma, b: luma }
|
|
194
|
+
: { r: luma, g: luma, b: luma, a: color.a };
|
|
195
|
+
}
|
|
196
|
+
/** Grayscale every edge of a border set, preserving `null` edges. */
|
|
197
|
+
function grayscaleBorders(borders) {
|
|
198
|
+
const convert = (b) => b === null ? null : { ...b, color: toGrayscale(b.color) };
|
|
199
|
+
return {
|
|
200
|
+
top: convert(borders.top),
|
|
201
|
+
right: convert(borders.right),
|
|
202
|
+
bottom: convert(borders.bottom),
|
|
203
|
+
left: convert(borders.left)
|
|
204
|
+
};
|
|
205
|
+
}
|
|
180
206
|
/**
|
|
181
207
|
* Default colors used in PDF rendering.
|
|
182
208
|
*/
|
|
@@ -76,41 +76,90 @@ export function parseJpegDimensions(data) {
|
|
|
76
76
|
* Write an image XObject (JPEG or PNG) to the writer.
|
|
77
77
|
* Returns the allocated object number.
|
|
78
78
|
*/
|
|
79
|
-
export function writeImageXObject(writer, data, format) {
|
|
79
|
+
export function writeImageXObject(writer, data, format, grayscale = false) {
|
|
80
80
|
if (format === "png") {
|
|
81
|
-
return writePngImageXObject(writer, data);
|
|
81
|
+
return writePngImageXObject(writer, data, grayscale);
|
|
82
82
|
}
|
|
83
|
-
return writeJpegImageXObject(writer, data);
|
|
83
|
+
return writeJpegImageXObject(writer, data, grayscale);
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
|
-
* Write
|
|
86
|
+
* Write the PostScript calculator function that converts an RGB triple to its
|
|
87
|
+
* Rec. 601 luma, and return its object number.
|
|
88
|
+
*
|
|
89
|
+
* Used as the tint transform of a `/DeviceN` space so a JPEG can be recoloured
|
|
90
|
+
* without decoding it: the samples stay untouched and the consumer applies the
|
|
91
|
+
* transform per pixel. Unlike painting a blend on top, this cannot darken
|
|
92
|
+
* transparent areas, because it *is* the color space rather than an overlay.
|
|
87
93
|
*/
|
|
88
|
-
function
|
|
94
|
+
function writeLumaTintTransform(writer) {
|
|
89
95
|
const objNum = writer.allocObject();
|
|
96
|
+
const dict = new PdfDict()
|
|
97
|
+
.set("FunctionType", "4")
|
|
98
|
+
.set("Domain", "[0 1 0 1 0 1]")
|
|
99
|
+
.set("Range", "[0 1]");
|
|
100
|
+
// Stack on entry is R G B (B on top):
|
|
101
|
+
// 0.114 mul -> R G 0.114B
|
|
102
|
+
// exch -> R 0.114B G
|
|
103
|
+
// 0.587 mul -> R 0.114B 0.587G
|
|
104
|
+
// add -> R (0.114B + 0.587G)
|
|
105
|
+
// exch -> (0.114B + 0.587G) R
|
|
106
|
+
// 0.299 mul -> (0.114B + 0.587G) 0.299R
|
|
107
|
+
// add -> luma
|
|
108
|
+
const program = "{ 0.114 mul exch 0.587 mul add exch 0.299 mul add }";
|
|
109
|
+
writer.addStreamObject(objNum, dict, new TextEncoder().encode(program));
|
|
110
|
+
return objNum;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Write a JPEG image using DCTDecode (raw JPEG data embedded directly).
|
|
114
|
+
*/
|
|
115
|
+
function writeJpegImageXObject(writer, data, grayscale = false) {
|
|
90
116
|
const dims = parseJpegDimensions(data);
|
|
117
|
+
// Grayscale without a JPEG decoder: keep the DCTDecode samples and reinterpret
|
|
118
|
+
// the three components through a `/DeviceN` space whose tint transform is the
|
|
119
|
+
// luma formula. Component count still matches the JPEG, as PDF requires.
|
|
120
|
+
const tintRef = grayscale ? writeLumaTintTransform(writer) : undefined;
|
|
121
|
+
const objNum = writer.allocObject();
|
|
91
122
|
const dict = new PdfDict()
|
|
92
123
|
.set("Type", "/XObject")
|
|
93
124
|
.set("Subtype", "/Image")
|
|
94
125
|
.set("Width", pdfNumber(dims.width))
|
|
95
126
|
.set("Height", pdfNumber(dims.height))
|
|
96
|
-
.set("ColorSpace",
|
|
127
|
+
.set("ColorSpace", tintRef === undefined
|
|
128
|
+
? "/DeviceRGB"
|
|
129
|
+
: `[/DeviceN [/C1 /C2 /C3] /DeviceGray ${pdfRef(tintRef)}]`)
|
|
97
130
|
.set("BitsPerComponent", "8")
|
|
98
131
|
.set("Filter", "/DCTDecode");
|
|
99
132
|
writer.addStreamObject(objNum, dict, data);
|
|
100
133
|
return objNum;
|
|
101
134
|
}
|
|
135
|
+
/** Rec. 601 luma of an 8-bit RGB triple. */
|
|
136
|
+
function lumaByte(r, g, b) {
|
|
137
|
+
return Math.round(0.299 * r + 0.587 * g + 0.114 * b);
|
|
138
|
+
}
|
|
102
139
|
/**
|
|
103
140
|
* Write a PNG image: decode to raw RGB, create SMask for alpha if needed.
|
|
104
141
|
*/
|
|
105
|
-
function writePngImageXObject(writer, data) {
|
|
142
|
+
function writePngImageXObject(writer, data, grayscale = false) {
|
|
106
143
|
const png = decodePng(data);
|
|
107
144
|
const objNum = writer.allocObject();
|
|
145
|
+
// PNG is already decoded to raw samples here, so grayscale is a real pixel
|
|
146
|
+
// conversion: collapse RGB to one luma component and switch to DeviceGray.
|
|
147
|
+
// The alpha channel lives in a separate SMask and is untouched, so transparent
|
|
148
|
+
// regions stay transparent.
|
|
149
|
+
let pixels = png.pixels;
|
|
150
|
+
if (grayscale) {
|
|
151
|
+
const gray = new Uint8Array(png.width * png.height);
|
|
152
|
+
for (let i = 0, p = 0; i < gray.length; i++, p += 3) {
|
|
153
|
+
gray[i] = lumaByte(pixels[p], pixels[p + 1], pixels[p + 2]);
|
|
154
|
+
}
|
|
155
|
+
pixels = gray;
|
|
156
|
+
}
|
|
108
157
|
const dict = new PdfDict()
|
|
109
158
|
.set("Type", "/XObject")
|
|
110
159
|
.set("Subtype", "/Image")
|
|
111
160
|
.set("Width", pdfNumber(png.width))
|
|
112
161
|
.set("Height", pdfNumber(png.height))
|
|
113
|
-
.set("ColorSpace", "/DeviceRGB")
|
|
162
|
+
.set("ColorSpace", grayscale ? "/DeviceGray" : "/DeviceRGB")
|
|
114
163
|
.set("BitsPerComponent", pdfNumber(png.bitsPerComponent));
|
|
115
164
|
if (png.alpha) {
|
|
116
165
|
const smaskObjNum = writer.allocObject();
|
|
@@ -124,6 +173,6 @@ function writePngImageXObject(writer, data) {
|
|
|
124
173
|
writer.addStreamObject(smaskObjNum, smaskDict, png.alpha);
|
|
125
174
|
dict.set("SMask", pdfRef(smaskObjNum));
|
|
126
175
|
}
|
|
127
|
-
writer.addStreamObject(objNum, dict,
|
|
176
|
+
writer.addStreamObject(objNum, dict, pixels);
|
|
128
177
|
return objNum;
|
|
129
178
|
}
|