@cj-tech-master/excelts 9.4.1 → 9.4.2
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/document-builder.js +4 -23
- package/dist/browser/modules/pdf/core/pdf-stream.d.ts +15 -0
- package/dist/browser/modules/pdf/core/pdf-stream.js +47 -3
- package/dist/browser/modules/pdf/font/font-manager.d.ts +37 -6
- package/dist/browser/modules/pdf/font/font-manager.js +129 -17
- package/dist/browser/modules/pdf/font/system-fonts.d.ts +41 -0
- package/dist/browser/modules/pdf/font/system-fonts.js +188 -0
- package/dist/browser/modules/pdf/font/ttf-parser.js +29 -1
- package/dist/browser/modules/pdf/font/type3-font.d.ts +35 -0
- package/dist/browser/modules/pdf/font/type3-font.js +228 -0
- package/dist/browser/modules/pdf/font/type3-glyphs-extended.d.ts +33 -0
- package/dist/browser/modules/pdf/font/type3-glyphs-extended.js +4164 -0
- package/dist/browser/modules/pdf/font/type3-glyphs-extended2.d.ts +16 -0
- package/dist/browser/modules/pdf/font/type3-glyphs-extended2.js +9649 -0
- package/dist/browser/modules/pdf/font/type3-glyphs-fill.d.ts +17 -0
- package/dist/browser/modules/pdf/font/type3-glyphs-fill.js +5438 -0
- package/dist/browser/modules/pdf/font/type3-glyphs-quality.d.ts +28 -0
- package/dist/browser/modules/pdf/font/type3-glyphs-quality.js +5345 -0
- package/dist/browser/modules/pdf/font/type3-glyphs.d.ts +79 -0
- package/dist/browser/modules/pdf/font/type3-glyphs.js +2567 -0
- package/dist/browser/modules/pdf/render/layout-engine.js +36 -23
- package/dist/browser/modules/pdf/render/page-renderer.d.ts +9 -0
- package/dist/browser/modules/pdf/render/page-renderer.js +110 -78
- package/dist/browser/modules/pdf/render/pdf-exporter.js +73 -5
- package/dist/cjs/modules/pdf/builder/document-builder.js +3 -22
- package/dist/cjs/modules/pdf/core/pdf-stream.js +49 -3
- package/dist/cjs/modules/pdf/font/font-manager.js +129 -17
- package/dist/cjs/modules/pdf/font/system-fonts.js +194 -0
- package/dist/cjs/modules/pdf/font/ttf-parser.js +29 -1
- package/dist/cjs/modules/pdf/font/type3-font.js +231 -0
- package/dist/cjs/modules/pdf/font/type3-glyphs-extended.js +4167 -0
- package/dist/cjs/modules/pdf/font/type3-glyphs-extended2.js +9652 -0
- package/dist/cjs/modules/pdf/font/type3-glyphs-fill.js +5441 -0
- package/dist/cjs/modules/pdf/font/type3-glyphs-quality.js +5348 -0
- package/dist/cjs/modules/pdf/font/type3-glyphs.js +2573 -0
- package/dist/cjs/modules/pdf/render/layout-engine.js +36 -23
- package/dist/cjs/modules/pdf/render/page-renderer.js +111 -78
- package/dist/cjs/modules/pdf/render/pdf-exporter.js +71 -3
- package/dist/esm/modules/pdf/builder/document-builder.js +4 -23
- package/dist/esm/modules/pdf/core/pdf-stream.js +47 -3
- package/dist/esm/modules/pdf/font/font-manager.js +129 -17
- package/dist/esm/modules/pdf/font/system-fonts.js +188 -0
- package/dist/esm/modules/pdf/font/ttf-parser.js +29 -1
- package/dist/esm/modules/pdf/font/type3-font.js +228 -0
- package/dist/esm/modules/pdf/font/type3-glyphs-extended.js +4164 -0
- package/dist/esm/modules/pdf/font/type3-glyphs-extended2.js +9649 -0
- package/dist/esm/modules/pdf/font/type3-glyphs-fill.js +5438 -0
- package/dist/esm/modules/pdf/font/type3-glyphs-quality.js +5345 -0
- package/dist/esm/modules/pdf/font/type3-glyphs.js +2567 -0
- package/dist/esm/modules/pdf/render/layout-engine.js +36 -23
- package/dist/esm/modules/pdf/render/page-renderer.js +110 -78
- package/dist/esm/modules/pdf/render/pdf-exporter.js +73 -5
- package/dist/iife/excelts.iife.js +25445 -344
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +48 -46
- package/dist/types/modules/pdf/core/pdf-stream.d.ts +15 -0
- package/dist/types/modules/pdf/font/font-manager.d.ts +37 -6
- package/dist/types/modules/pdf/font/system-fonts.d.ts +41 -0
- package/dist/types/modules/pdf/font/type3-font.d.ts +35 -0
- package/dist/types/modules/pdf/font/type3-glyphs-extended.d.ts +33 -0
- package/dist/types/modules/pdf/font/type3-glyphs-extended2.d.ts +16 -0
- package/dist/types/modules/pdf/font/type3-glyphs-fill.d.ts +17 -0
- package/dist/types/modules/pdf/font/type3-glyphs-quality.d.ts +28 -0
- package/dist/types/modules/pdf/font/type3-glyphs.d.ts +79 -0
- package/dist/types/modules/pdf/render/page-renderer.d.ts +9 -0
- package/package.json +1 -1
|
@@ -390,38 +390,49 @@ function computeRowHeights(sheet, scaleFactor, printRange, fontManager, options)
|
|
|
390
390
|
height = row.height;
|
|
391
391
|
}
|
|
392
392
|
else if (row?.height) {
|
|
393
|
-
// Excel auto-calculated height —
|
|
394
|
-
|
|
393
|
+
// Excel auto-calculated height — use it as a baseline, but ensure
|
|
394
|
+
// the row is tall enough for wrapped text. The stored height may be
|
|
395
|
+
// stale when columns are narrower in the PDF layout or when the PDF
|
|
396
|
+
// uses different font metrics than the original Excel file.
|
|
397
|
+
height = Math.max(row.height, autoRowHeight(row, scaleFactor, sheet, fontManager, options));
|
|
395
398
|
}
|
|
396
399
|
else {
|
|
397
400
|
// No height info: auto-size based on cell content
|
|
398
|
-
height =
|
|
399
|
-
if (row) {
|
|
400
|
-
for (const cell of row.cells.values()) {
|
|
401
|
-
const fontSize = getCellFontSize(cell);
|
|
402
|
-
const wrapLineCount = countWrapLines(cell, fontSize, scaleFactor, sheet, fontManager, options);
|
|
403
|
-
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
404
|
-
// Account for border width: half of each border extends inward
|
|
405
|
-
const borderTop = cell.style?.border?.top?.style
|
|
406
|
-
? borderStyleToLineWidth(cell.style.border.top.style) / 2
|
|
407
|
-
: 0;
|
|
408
|
-
const borderBottom = cell.style?.border?.bottom?.style
|
|
409
|
-
? borderStyleToLineWidth(cell.style.border.bottom.style) / 2
|
|
410
|
-
: 0;
|
|
411
|
-
const neededHeight = fontSize +
|
|
412
|
-
(wrapLineCount - 1) * lineHeight +
|
|
413
|
-
(CELL_PADDING_V + borderTop + borderBottom) * 2;
|
|
414
|
-
if (neededHeight > height) {
|
|
415
|
-
height = neededHeight;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
401
|
+
height = autoRowHeight(row, scaleFactor, sheet, fontManager, options);
|
|
419
402
|
}
|
|
420
403
|
rowHeights.push(height * scaleFactor);
|
|
421
404
|
visibleRows.push(r);
|
|
422
405
|
}
|
|
423
406
|
return { rowHeights, visibleRows };
|
|
424
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* Compute the minimum row height required to display wrapped cell content.
|
|
410
|
+
* Returns at least `DEFAULT_ROW_HEIGHT`.
|
|
411
|
+
*/
|
|
412
|
+
function autoRowHeight(row, scaleFactor, sheet, fontManager, options) {
|
|
413
|
+
let height = DEFAULT_ROW_HEIGHT;
|
|
414
|
+
if (row) {
|
|
415
|
+
for (const cell of row.cells.values()) {
|
|
416
|
+
const fontSize = getCellFontSize(cell);
|
|
417
|
+
const wrapLineCount = countWrapLines(cell, fontSize, scaleFactor, sheet, fontManager, options);
|
|
418
|
+
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
419
|
+
// Account for border width: half of each border extends inward
|
|
420
|
+
const borderTop = cell.style?.border?.top?.style
|
|
421
|
+
? borderStyleToLineWidth(cell.style.border.top.style) / 2
|
|
422
|
+
: 0;
|
|
423
|
+
const borderBottom = cell.style?.border?.bottom?.style
|
|
424
|
+
? borderStyleToLineWidth(cell.style.border.bottom.style) / 2
|
|
425
|
+
: 0;
|
|
426
|
+
const neededHeight = fontSize +
|
|
427
|
+
(wrapLineCount - 1) * lineHeight +
|
|
428
|
+
(CELL_PADDING_V + borderTop + borderBottom) * 2;
|
|
429
|
+
if (neededHeight > height) {
|
|
430
|
+
height = neededHeight;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return height;
|
|
435
|
+
}
|
|
425
436
|
/**
|
|
426
437
|
* Get the largest font size for a cell, checking rich text runs.
|
|
427
438
|
*/
|
|
@@ -652,6 +663,8 @@ function buildLayoutCell(cell, x, y, width, height, colSpan, rowSpan, options, f
|
|
|
652
663
|
else {
|
|
653
664
|
const pdfFontName = resolvePdfFontName(fontProps.fontFamily, fontProps.bold, fontProps.italic);
|
|
654
665
|
fontManager.ensureFont(pdfFontName);
|
|
666
|
+
// Track non-WinAnsi code points for Type3 fallback font generation
|
|
667
|
+
fontManager.trackText(text);
|
|
655
668
|
}
|
|
656
669
|
// Rich text runs
|
|
657
670
|
const richText = buildRichTextRuns(cell, options, fontManager, scaleFactor);
|
|
@@ -258,23 +258,14 @@ function drawCellText(stream, cell, fontManager, alphaValues, scaleFactor = 1) {
|
|
|
258
258
|
const totalTextHeight = lines.length * lineHeight;
|
|
259
259
|
const textStartY = computeTextStartY(verticalAlign, rect, totalTextHeight, ascent, pad.top, pad.bottom);
|
|
260
260
|
stream.setFillColor(cell.textColor);
|
|
261
|
-
|
|
262
|
-
stream.setFont(resourceName, fontSize);
|
|
261
|
+
const useType3 = fontManager.hasType3Fonts() && !isEmbedded;
|
|
263
262
|
for (let i = 0; i < lines.length; i++) {
|
|
264
263
|
const line = lines[i];
|
|
265
264
|
const lineY = textStartY - i * lineHeight;
|
|
266
265
|
const textWidth = measure(line);
|
|
267
266
|
const textX = computeTextX(horizontalAlign, rect, textWidth, indentPts, pad.left, pad.right);
|
|
268
|
-
stream
|
|
269
|
-
const hexEncoded = fontManager.encodeText(line, resourceName);
|
|
270
|
-
if (hexEncoded) {
|
|
271
|
-
stream.showTextHex(hexEncoded);
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
stream.showText(line);
|
|
275
|
-
}
|
|
267
|
+
emitTextWithType3(stream, line, textX, lineY, resourceName, fontSize, fontManager, useType3);
|
|
276
268
|
}
|
|
277
|
-
stream.endText();
|
|
278
269
|
drawTextDecorations(stream, cell, lines, lineHeight, textStartY, measure, resourceName, fontManager, indentPts, pad);
|
|
279
270
|
stream.restore();
|
|
280
271
|
}
|
|
@@ -359,21 +350,11 @@ function drawRichText(stream, cell, fontManager, indentPts, scaleFactor = 1) {
|
|
|
359
350
|
lineWidth += fontManager.measureText(seg.text, seg.resourceName, seg.run.fontSize);
|
|
360
351
|
}
|
|
361
352
|
let textX = computeTextX(horizontalAlign, rect, lineWidth, indentPts, pad.left, pad.right);
|
|
353
|
+
const useType3 = fontManager.hasType3Fonts() && !isEmbedded;
|
|
362
354
|
for (const seg of segments) {
|
|
363
355
|
const { run, text, resourceName } = seg;
|
|
364
|
-
const segWidth = fontManager.measureText(text, resourceName, run.fontSize);
|
|
365
356
|
stream.setFillColor(run.textColor);
|
|
366
|
-
stream.
|
|
367
|
-
stream.setFont(resourceName, run.fontSize);
|
|
368
|
-
stream.setTextMatrix(1, 0, 0, 1, textX, lineY);
|
|
369
|
-
const hex = fontManager.encodeText(text, resourceName);
|
|
370
|
-
if (hex) {
|
|
371
|
-
stream.showTextHex(hex);
|
|
372
|
-
}
|
|
373
|
-
else {
|
|
374
|
-
stream.showText(text);
|
|
375
|
-
}
|
|
376
|
-
stream.endText();
|
|
357
|
+
const segWidth = emitTextWithType3(stream, text, textX, lineY, resourceName, run.fontSize, fontManager, useType3);
|
|
377
358
|
if (run.strike) {
|
|
378
359
|
const descent = fontManager.getFontDescent(resourceName, run.fontSize);
|
|
379
360
|
const y = lineY + descent + run.fontSize * 0.3;
|
|
@@ -403,23 +384,13 @@ function drawRichText(stream, cell, fontManager, indentPts, scaleFactor = 1) {
|
|
|
403
384
|
const ascent = fontManager.getFontAscent(primaryResourceName, primaryFontSize);
|
|
404
385
|
const textStartY = computeTextStartY(verticalAlign, rect, lineHeight, ascent, pad.top, pad.bottom);
|
|
405
386
|
let textX = computeTextX(horizontalAlign, rect, totalWidth, indentPts, pad.left, pad.right);
|
|
387
|
+
const useType3 = fontManager.hasType3Fonts() && !isEmbedded;
|
|
406
388
|
for (let i = 0; i < runs.length; i++) {
|
|
407
389
|
const run = runs[i];
|
|
408
390
|
const { resourceName } = runMetrics[i];
|
|
409
391
|
stream.setFillColor(run.textColor);
|
|
410
|
-
stream.
|
|
411
|
-
stream.setFont(resourceName, run.fontSize);
|
|
412
|
-
stream.setTextMatrix(1, 0, 0, 1, textX, textStartY);
|
|
413
|
-
const hexEncoded = fontManager.encodeText(run.text, resourceName);
|
|
414
|
-
if (hexEncoded) {
|
|
415
|
-
stream.showTextHex(hexEncoded);
|
|
416
|
-
}
|
|
417
|
-
else {
|
|
418
|
-
stream.showText(run.text);
|
|
419
|
-
}
|
|
420
|
-
stream.endText();
|
|
392
|
+
const runWidth = emitTextWithType3(stream, run.text, textX, textStartY, resourceName, run.fontSize, fontManager, useType3);
|
|
421
393
|
// Draw per-run decorations (strikethrough, underline)
|
|
422
|
-
const runWidth = runMetrics[i].width;
|
|
423
394
|
if (run.strike) {
|
|
424
395
|
const descent = fontManager.getFontDescent(resourceName, run.fontSize);
|
|
425
396
|
const y = textStartY + descent + run.fontSize * 0.3;
|
|
@@ -430,7 +401,7 @@ function drawRichText(stream, cell, fontManager, indentPts, scaleFactor = 1) {
|
|
|
430
401
|
const y = textStartY + descent * 0.5;
|
|
431
402
|
stream.drawLine(textX, y, textX + runWidth, y, run.textColor, 0.5);
|
|
432
403
|
}
|
|
433
|
-
textX +=
|
|
404
|
+
textX += runWidth;
|
|
434
405
|
}
|
|
435
406
|
}
|
|
436
407
|
// =============================================================================
|
|
@@ -527,6 +498,7 @@ function drawRotated90(stream, cell, lines, fontManager, resourceName, fontSize,
|
|
|
527
498
|
// left (default)
|
|
528
499
|
startX = rect.x + pad.left + ascent;
|
|
529
500
|
}
|
|
501
|
+
const useType3 = fontManager.hasType3Fonts() && !fontManager.hasEmbeddedFont();
|
|
530
502
|
for (let i = 0; i < lines.length; i++) {
|
|
531
503
|
const line = lines[i];
|
|
532
504
|
const lineWidth = fontManager.measureText(line, resourceName, fontSize);
|
|
@@ -546,11 +518,7 @@ function drawRotated90(stream, cell, lines, fontManager, resourceName, fontSize,
|
|
|
546
518
|
ty = rect.y + pad.bottom;
|
|
547
519
|
}
|
|
548
520
|
ty = Math.max(ty, rect.y + pad.bottom);
|
|
549
|
-
stream
|
|
550
|
-
stream.setFont(resourceName, fontSize);
|
|
551
|
-
stream.setTextMatrix(0, 1, -1, 0, colX, ty);
|
|
552
|
-
emitText(stream, fontManager, line, resourceName);
|
|
553
|
-
stream.endText();
|
|
521
|
+
emitTextWithMatrix(stream, line, 0, 1, -1, 0, colX, ty, resourceName, fontSize, fontManager, useType3);
|
|
554
522
|
}
|
|
555
523
|
}
|
|
556
524
|
/** -90° (270° CW): text reads top-to-bottom, lines stack right-to-left. */
|
|
@@ -569,6 +537,7 @@ function drawRotatedMinus90(stream, cell, lines, fontManager, resourceName, font
|
|
|
569
537
|
// left (default)
|
|
570
538
|
startX = rect.x + pad.left + totalColumnsWidth - lineHeight + ascent;
|
|
571
539
|
}
|
|
540
|
+
const useType3 = fontManager.hasType3Fonts() && !fontManager.hasEmbeddedFont();
|
|
572
541
|
for (let i = 0; i < lines.length; i++) {
|
|
573
542
|
const line = lines[i];
|
|
574
543
|
const lineWidth = fontManager.measureText(line, resourceName, fontSize);
|
|
@@ -588,11 +557,7 @@ function drawRotatedMinus90(stream, cell, lines, fontManager, resourceName, font
|
|
|
588
557
|
ty = rect.y + pad.bottom + lineWidth;
|
|
589
558
|
}
|
|
590
559
|
ty = Math.min(ty, rect.y + rect.height - pad.top);
|
|
591
|
-
stream
|
|
592
|
-
stream.setFont(resourceName, fontSize);
|
|
593
|
-
stream.setTextMatrix(0, -1, 1, 0, colX, ty);
|
|
594
|
-
emitText(stream, fontManager, line, resourceName);
|
|
595
|
-
stream.endText();
|
|
560
|
+
emitTextWithMatrix(stream, line, 0, -1, 1, 0, colX, ty, resourceName, fontSize, fontManager, useType3);
|
|
596
561
|
}
|
|
597
562
|
}
|
|
598
563
|
/** General rotation — center a multi-line text block in the cell. */
|
|
@@ -644,6 +609,7 @@ function drawRotatedGeneral(stream, cell, lines, fontManager, resourceName, font
|
|
|
644
609
|
// center (default for rotated)
|
|
645
610
|
cx = rect.x + rect.width / 2 + indentOffset + slantAtCy;
|
|
646
611
|
}
|
|
612
|
+
const useType3 = fontManager.hasType3Fonts() && !fontManager.hasEmbeddedFont();
|
|
647
613
|
for (let i = 0; i < lines.length; i++) {
|
|
648
614
|
const line = lines[i];
|
|
649
615
|
const lineWidth = fontManager.measureText(line, resourceName, fontSize);
|
|
@@ -652,11 +618,7 @@ function drawRotatedGeneral(stream, cell, lines, fontManager, resourceName, font
|
|
|
652
618
|
const offsetY = -ascent / 2 - lineOffset;
|
|
653
619
|
const tx = cx + offsetX * cos - offsetY * sin;
|
|
654
620
|
const ty = cy + offsetX * sin + offsetY * cos;
|
|
655
|
-
stream
|
|
656
|
-
stream.setFont(resourceName, fontSize);
|
|
657
|
-
stream.setTextMatrix(cos, sin, -sin, cos, tx, ty);
|
|
658
|
-
emitText(stream, fontManager, line, resourceName);
|
|
659
|
-
stream.endText();
|
|
621
|
+
emitTextWithMatrix(stream, line, cos, sin, -sin, cos, tx, ty, resourceName, fontSize, fontManager, useType3);
|
|
660
622
|
}
|
|
661
623
|
}
|
|
662
624
|
/** Emit a text string with hex encoding if available. */
|
|
@@ -669,6 +631,97 @@ function emitText(stream, fontManager, text, resourceName) {
|
|
|
669
631
|
stream.showText(text);
|
|
670
632
|
}
|
|
671
633
|
}
|
|
634
|
+
/**
|
|
635
|
+
* Render a text string with a custom text matrix, using Type3-aware splitting
|
|
636
|
+
* when needed. For each sub-run the matrix origin is advanced along the
|
|
637
|
+
* text direction (cos, sin) by the rendered width.
|
|
638
|
+
*
|
|
639
|
+
* When `useType3` is false this collapses to a single BT/ET pair — identical
|
|
640
|
+
* to the old `emitText()` path but wrapped in begin/end for convenience.
|
|
641
|
+
*/
|
|
642
|
+
export function emitTextWithMatrix(stream, text, a, b, c, d, tx, ty, type1ResourceName, fontSize, fontManager, useType3) {
|
|
643
|
+
if (!useType3) {
|
|
644
|
+
stream.beginText();
|
|
645
|
+
stream.setFont(type1ResourceName, fontSize);
|
|
646
|
+
stream.setTextMatrix(a, b, c, d, tx, ty);
|
|
647
|
+
emitText(stream, fontManager, text, type1ResourceName);
|
|
648
|
+
stream.endText();
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
// Type3 path: split into runs and advance origin along text direction
|
|
652
|
+
const runs = splitTextRuns(text, fontManager);
|
|
653
|
+
let curTx = tx;
|
|
654
|
+
let curTy = ty;
|
|
655
|
+
for (const run of runs) {
|
|
656
|
+
stream.beginText();
|
|
657
|
+
if (run.type3) {
|
|
658
|
+
stream.setFont(run.type3.resourceName, fontSize);
|
|
659
|
+
stream.setTextMatrix(a, b, c, d, curTx, curTy);
|
|
660
|
+
stream.showTextHex(run.type3.hex);
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
stream.setFont(type1ResourceName, fontSize);
|
|
664
|
+
stream.setTextMatrix(a, b, c, d, curTx, curTy);
|
|
665
|
+
emitText(stream, fontManager, run.text, type1ResourceName);
|
|
666
|
+
}
|
|
667
|
+
stream.endText();
|
|
668
|
+
const w = fontManager.measureText(run.text, run.type3?.resourceName ?? type1ResourceName, fontSize);
|
|
669
|
+
// Advance along the text direction (first column of the matrix)
|
|
670
|
+
curTx += a * w;
|
|
671
|
+
curTy += b * w;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Split a line of text into runs of consecutive WinAnsi and non-WinAnsi
|
|
676
|
+
* characters. Each non-WinAnsi character becomes its own Type3 run (since
|
|
677
|
+
* different code points may map to different Type3 fonts). Consecutive
|
|
678
|
+
* WinAnsi characters are merged into a single Type1 run.
|
|
679
|
+
*/
|
|
680
|
+
function splitTextRuns(text, fontManager) {
|
|
681
|
+
const runs = [];
|
|
682
|
+
let winAnsiBuffer = "";
|
|
683
|
+
const flushWinAnsi = () => {
|
|
684
|
+
if (winAnsiBuffer) {
|
|
685
|
+
runs.push({ text: winAnsiBuffer, type3: null });
|
|
686
|
+
winAnsiBuffer = "";
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
for (let i = 0; i < text.length; i++) {
|
|
690
|
+
const cp = text.codePointAt(i);
|
|
691
|
+
const ch = String.fromCodePoint(cp);
|
|
692
|
+
if (cp > 0xffff) {
|
|
693
|
+
i++; // skip low surrogate
|
|
694
|
+
}
|
|
695
|
+
if (fontManager.needsType3(cp)) {
|
|
696
|
+
flushWinAnsi();
|
|
697
|
+
const t3 = fontManager.resolveType3(cp);
|
|
698
|
+
if (t3) {
|
|
699
|
+
const hex = fontManager.encodeType3Char(cp);
|
|
700
|
+
runs.push({
|
|
701
|
+
text: ch,
|
|
702
|
+
type3: { resourceName: t3.resourceName, hex: hex ?? "<00>" }
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
// Character tracked but Type3 not yet written — render as space
|
|
707
|
+
runs.push({ text: ch, type3: null });
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
winAnsiBuffer += ch;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
flushWinAnsi();
|
|
715
|
+
return runs;
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Render a text string at (textX, textY) using Type3-aware splitting when needed.
|
|
719
|
+
* Returns the rendered width so the caller can advance textX.
|
|
720
|
+
*/
|
|
721
|
+
function emitTextWithType3(stream, text, textX, textY, type1ResourceName, fontSize, fontManager, useType3) {
|
|
722
|
+
emitTextWithMatrix(stream, text, 1, 0, 0, 1, textX, textY, type1ResourceName, fontSize, fontManager, useType3);
|
|
723
|
+
return fontManager.measureText(text, type1ResourceName, fontSize);
|
|
724
|
+
}
|
|
672
725
|
/**
|
|
673
726
|
* Draw vertical stacked text (each character top-to-bottom).
|
|
674
727
|
* Newlines (\n) start a new column to the right.
|
|
@@ -699,6 +752,7 @@ function drawVerticalStackedText(stream, cell, fontManager, _indentPts, scaleFac
|
|
|
699
752
|
startX = rect.x + pad.left + columnWidth / 2;
|
|
700
753
|
}
|
|
701
754
|
stream.setFillColor(cell.textColor);
|
|
755
|
+
const useType3 = fontManager.hasType3Fonts() && !isEmbedded;
|
|
702
756
|
for (let colIdx = 0; colIdx < columns.length; colIdx++) {
|
|
703
757
|
const colText = columns[colIdx];
|
|
704
758
|
const colX = startX + colIdx * columnWidth;
|
|
@@ -720,11 +774,7 @@ function drawVerticalStackedText(stream, cell, fontManager, _indentPts, scaleFac
|
|
|
720
774
|
break;
|
|
721
775
|
}
|
|
722
776
|
const charWidth = fontManager.measureText(ch, resourceName, fontSize);
|
|
723
|
-
stream
|
|
724
|
-
stream.setFont(resourceName, fontSize);
|
|
725
|
-
stream.setTextMatrix(1, 0, 0, 1, colX - charWidth / 2, currentY);
|
|
726
|
-
emitText(stream, fontManager, ch, resourceName);
|
|
727
|
-
stream.endText();
|
|
777
|
+
emitTextWithMatrix(stream, ch, 1, 0, 0, 1, colX - charWidth / 2, currentY, resourceName, fontSize, fontManager, useType3);
|
|
728
778
|
currentY -= charHeight;
|
|
729
779
|
}
|
|
730
780
|
}
|
|
@@ -865,17 +915,8 @@ function drawPageHeader(stream, page, options, fontManager) {
|
|
|
865
915
|
const y = page.height - options.margins.top + 5;
|
|
866
916
|
stream.save();
|
|
867
917
|
stream.setFillColor({ r: 0.3, g: 0.3, b: 0.3 });
|
|
868
|
-
|
|
869
|
-
stream
|
|
870
|
-
stream.setTextMatrix(1, 0, 0, 1, x, y);
|
|
871
|
-
const hex = fontManager.encodeText(headerText, resourceName);
|
|
872
|
-
if (hex) {
|
|
873
|
-
stream.showTextHex(hex);
|
|
874
|
-
}
|
|
875
|
-
else {
|
|
876
|
-
stream.showText(headerText);
|
|
877
|
-
}
|
|
878
|
-
stream.endText();
|
|
918
|
+
const useType3 = fontManager.hasType3Fonts() && !fontManager.hasEmbeddedFont();
|
|
919
|
+
emitTextWithMatrix(stream, headerText, 1, 0, 0, 1, x, y, resourceName, headerFontSize, fontManager, useType3);
|
|
879
920
|
stream.restore();
|
|
880
921
|
}
|
|
881
922
|
function drawPageFooter(stream, page, options, fontManager, totalPages) {
|
|
@@ -980,6 +1021,7 @@ function renderTextWatermark(stream, page, watermark, fontManager) {
|
|
|
980
1021
|
const sin = Math.sin(radians);
|
|
981
1022
|
const needsAlpha = opacity < 1;
|
|
982
1023
|
const gsName = needsAlpha ? alphaGsName(opacity) : "";
|
|
1024
|
+
const useType3 = fontManager.hasType3Fonts() && !isEmbedded;
|
|
983
1025
|
const drawSingleWatermark = (cx, cy) => {
|
|
984
1026
|
// Center the text at (cx, cy), compensating for both width and ascent height
|
|
985
1027
|
const halfW = textWidth / 2;
|
|
@@ -991,17 +1033,7 @@ function renderTextWatermark(stream, page, watermark, fontManager) {
|
|
|
991
1033
|
stream.setGraphicsState(gsName);
|
|
992
1034
|
}
|
|
993
1035
|
stream.setFillColor(color);
|
|
994
|
-
stream.
|
|
995
|
-
stream.setFont(resourceName, fontSize);
|
|
996
|
-
stream.setTextMatrix(cos, sin, -sin, cos, tx, ty);
|
|
997
|
-
const hex = fontManager.encodeText(watermark.text, resourceName);
|
|
998
|
-
if (hex) {
|
|
999
|
-
stream.showTextHex(hex);
|
|
1000
|
-
}
|
|
1001
|
-
else {
|
|
1002
|
-
stream.showText(watermark.text);
|
|
1003
|
-
}
|
|
1004
|
-
stream.endText();
|
|
1036
|
+
emitTextWithMatrix(stream, watermark.text, cos, sin, -sin, cos, tx, ty, resourceName, fontSize, fontManager, useType3);
|
|
1005
1037
|
stream.restore();
|
|
1006
1038
|
};
|
|
1007
1039
|
if (watermark.repeat) {
|
|
@@ -11,12 +11,13 @@ import { yieldToEventLoop } from "../../../utils/utils.base.js";
|
|
|
11
11
|
import { writeImageXObject } from "../builder/image-utils.js";
|
|
12
12
|
import { initEncryption } from "../core/encryption.js";
|
|
13
13
|
import { PdfDict, pdfRef, pdfNumber, pdfString as pdfStr } from "../core/pdf-object.js";
|
|
14
|
-
import { PdfContentStream } from "../core/pdf-stream.js";
|
|
14
|
+
import { PdfContentStream, isWinAnsiCodePoint } from "../core/pdf-stream.js";
|
|
15
15
|
import { PdfWriter } from "../core/pdf-writer.js";
|
|
16
16
|
import { PdfError, PdfRenderError } from "../errors.js";
|
|
17
17
|
import { FontManager, resolvePdfFontName } from "../font/font-manager.js";
|
|
18
|
+
import { discoverSystemFontCandidates } from "../font/system-fonts.js";
|
|
18
19
|
import { parseTtf } from "../font/ttf-parser.js";
|
|
19
|
-
import { PageSizes } from "../types.js";
|
|
20
|
+
import { PageSizes, PdfCellType } from "../types.js";
|
|
20
21
|
import { layoutSheet } from "./layout-engine.js";
|
|
21
22
|
import { renderPage, alphaGsName, renderWatermark } from "./page-renderer.js";
|
|
22
23
|
import { argbToPdfColor } from "./style-converter.js";
|
|
@@ -50,13 +51,39 @@ function prepareExport(workbook, options) {
|
|
|
50
51
|
}
|
|
51
52
|
const fontManager = new FontManager();
|
|
52
53
|
const writer = new PdfWriter();
|
|
53
|
-
|
|
54
|
+
// Determine font data: user-provided > auto-discovered system font
|
|
55
|
+
let fontData = options?.font ?? null;
|
|
56
|
+
if (!fontData) {
|
|
57
|
+
// Collect non-WinAnsi code points from the document (single pass)
|
|
58
|
+
const nonWinAnsi = collectNonWinAnsiCodePoints(sheets);
|
|
59
|
+
if (nonWinAnsi.size > 0) {
|
|
60
|
+
// Try system font candidates in preference order until one covers all chars
|
|
61
|
+
for (const candidate of discoverSystemFontCandidates()) {
|
|
62
|
+
try {
|
|
63
|
+
const testTtf = parseTtf(candidate);
|
|
64
|
+
const allCovered = [...nonWinAnsi].every(cp => testTtf.cmap.has(cp));
|
|
65
|
+
if (allCovered) {
|
|
66
|
+
fontData = candidate;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Parse failed — try next candidate
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (fontData) {
|
|
54
77
|
try {
|
|
55
|
-
const ttf = parseTtf(
|
|
78
|
+
const ttf = parseTtf(fontData);
|
|
56
79
|
fontManager.registerEmbeddedFont(ttf);
|
|
57
80
|
}
|
|
58
81
|
catch (err) {
|
|
59
|
-
|
|
82
|
+
if (options?.font) {
|
|
83
|
+
// Only throw if the user explicitly provided a font
|
|
84
|
+
throw new PdfRenderError("Failed to parse TrueType font", { cause: err });
|
|
85
|
+
}
|
|
86
|
+
// Auto-discovered font failed to parse — silently fall back to Type1 + Type3
|
|
60
87
|
}
|
|
61
88
|
}
|
|
62
89
|
return { sheets, fontManager, writer, allPages: [] };
|
|
@@ -500,3 +527,44 @@ function isWatermarkApplicable(watermark, page) {
|
|
|
500
527
|
}
|
|
501
528
|
return true;
|
|
502
529
|
}
|
|
530
|
+
// =============================================================================
|
|
531
|
+
// Non-WinAnsi Detection
|
|
532
|
+
// =============================================================================
|
|
533
|
+
/**
|
|
534
|
+
* Collect all non-WinAnsi code points from sheet text (single pass).
|
|
535
|
+
* Returns an empty set if all text is WinAnsi-representable.
|
|
536
|
+
*/
|
|
537
|
+
function collectNonWinAnsiCodePoints(sheets) {
|
|
538
|
+
const result = new Set();
|
|
539
|
+
for (const sheet of sheets) {
|
|
540
|
+
for (const row of sheet.rows.values()) {
|
|
541
|
+
for (const cell of row.cells.values()) {
|
|
542
|
+
collectFromText(cell.text, result);
|
|
543
|
+
if (cell.type === PdfCellType.RichText &&
|
|
544
|
+
cell.value &&
|
|
545
|
+
typeof cell.value === "object" &&
|
|
546
|
+
"richText" in cell.value) {
|
|
547
|
+
const runs = cell.value.richText;
|
|
548
|
+
for (const run of runs) {
|
|
549
|
+
collectFromText(run.text, result);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return result;
|
|
556
|
+
}
|
|
557
|
+
function collectFromText(text, out) {
|
|
558
|
+
if (!text) {
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
for (let i = 0; i < text.length; i++) {
|
|
562
|
+
const cp = text.codePointAt(i);
|
|
563
|
+
if (cp > 0xffff) {
|
|
564
|
+
i++;
|
|
565
|
+
}
|
|
566
|
+
if (!isWinAnsiCodePoint(cp)) {
|
|
567
|
+
out.add(cp);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|