@cj-tech-master/excelts 9.6.1 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -3
- package/README_zh.md +18 -3
- package/dist/browser/modules/excel/cell.d.ts +4 -0
- package/dist/browser/modules/excel/note.js +5 -1
- package/dist/browser/modules/excel/row.js +35 -2
- package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +8 -1
- package/dist/browser/modules/excel/stream/workbook-writer.browser.js +22 -2
- package/dist/browser/modules/excel/types.d.ts +81 -0
- package/dist/browser/modules/excel/utils/drawing-utils.d.ts +8 -0
- package/dist/browser/modules/excel/utils/drawing-utils.js +19 -2
- package/dist/browser/modules/excel/workbook.browser.d.ts +16 -0
- package/dist/browser/modules/excel/workbook.browser.js +32 -2
- package/dist/browser/modules/excel/worksheet.d.ts +31 -1
- package/dist/browser/modules/excel/worksheet.js +83 -0
- package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.d.ts +7 -0
- package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
- package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +6 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
- package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +1 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +2 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/shape-xform.d.ts +47 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/shape-xform.js +109 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
- package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
- package/dist/browser/modules/pdf/builder/document-builder.js +22 -49
- package/dist/browser/modules/pdf/builder/pdf-editor.js +1 -1
- package/dist/browser/modules/pdf/core/pdf-stream.d.ts +28 -1
- package/dist/browser/modules/pdf/core/pdf-stream.js +38 -2
- package/dist/browser/modules/pdf/font/font-manager.d.ts +26 -0
- package/dist/browser/modules/pdf/font/font-manager.js +35 -18
- package/dist/browser/modules/pdf/render/page-renderer.d.ts +51 -3
- package/dist/browser/modules/pdf/render/page-renderer.js +111 -18
- package/dist/browser/modules/word/advanced/field-engine.js +45 -20
- package/dist/browser/modules/word/advanced/glossary.d.ts +10 -36
- package/dist/browser/modules/word/advanced/glossary.js +8 -9
- package/dist/browser/modules/word/advanced/math-convert.js +94 -12
- package/dist/browser/modules/word/advanced/ole-objects.d.ts +28 -0
- package/dist/browser/modules/word/advanced/ole-objects.js +122 -19
- package/dist/browser/modules/word/advanced/style-map.js +31 -10
- package/dist/browser/modules/word/builder/run-builders.d.ts +7 -1
- package/dist/browser/modules/word/builder/run-builders.js +7 -1
- package/dist/browser/modules/word/constants.d.ts +4 -0
- package/dist/browser/modules/word/constants.js +5 -1
- package/dist/browser/modules/word/convert/docx-to-semantic.d.ts +2 -1
- package/dist/browser/modules/word/convert/docx-to-semantic.js +135 -1
- package/dist/browser/modules/word/convert/html/html-import.d.ts +32 -1
- package/dist/browser/modules/word/convert/html/html-import.js +167 -14
- package/dist/browser/modules/word/convert/html/html.d.ts +2 -2
- package/dist/browser/modules/word/convert/html/html.js +1 -1
- package/dist/browser/modules/word/convert/markdown/markdown-import.d.ts +48 -18
- package/dist/browser/modules/word/convert/markdown/markdown-import.js +279 -69
- package/dist/browser/modules/word/convert/markdown/markdown.d.ts +1 -1
- package/dist/browser/modules/word/convert/odt/odt.js +407 -56
- package/dist/browser/modules/word/html.d.ts +2 -2
- package/dist/browser/modules/word/html.js +1 -1
- package/dist/browser/modules/word/index.base.d.ts +3 -3
- package/dist/browser/modules/word/index.base.js +1 -1
- package/dist/browser/modules/word/layout/layout-full.js +326 -19
- package/dist/browser/modules/word/layout/render-page.js +35 -8
- package/dist/browser/modules/word/markdown.d.ts +1 -1
- package/dist/browser/modules/word/query/compat.d.ts +10 -2
- package/dist/browser/modules/word/query/compat.js +29 -21
- package/dist/browser/modules/word/reader/docx-reader.js +105 -2
- package/dist/browser/modules/word/reader/math-parser.js +8 -2
- package/dist/browser/modules/word/security/cfb-reader.js +5 -5
- package/dist/browser/modules/word/types.d.ts +96 -1
- package/dist/browser/modules/word/writer/docx-packager.js +108 -2
- package/dist/browser/modules/word/writer/glossary-writer.d.ts +28 -0
- package/dist/browser/modules/word/writer/glossary-writer.js +121 -0
- package/dist/browser/modules/word/writer/header-footer-writer.js +105 -20
- package/dist/browser/modules/word/writer/math-writer.js +7 -2
- package/dist/browser/utils/font-metrics.d.ts +8 -0
- package/dist/browser/utils/font-metrics.js +43 -0
- package/dist/browser/utils/theme-colors.js +4 -1
- package/dist/cjs/modules/excel/note.js +5 -1
- package/dist/cjs/modules/excel/row.js +35 -2
- package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +22 -2
- package/dist/cjs/modules/excel/utils/drawing-utils.js +19 -2
- package/dist/cjs/modules/excel/workbook.browser.js +31 -1
- package/dist/cjs/modules/excel/worksheet.js +83 -0
- package/dist/cjs/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
- package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
- package/dist/cjs/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
- package/dist/cjs/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
- package/dist/cjs/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
- package/dist/cjs/modules/excel/xlsx/xform/drawing/shape-xform.js +112 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
- package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
- package/dist/cjs/modules/pdf/builder/document-builder.js +21 -48
- package/dist/cjs/modules/pdf/builder/pdf-editor.js +1 -1
- package/dist/cjs/modules/pdf/core/pdf-stream.js +38 -2
- package/dist/cjs/modules/pdf/font/font-manager.js +35 -18
- package/dist/cjs/modules/pdf/render/page-renderer.js +112 -18
- package/dist/cjs/modules/word/advanced/field-engine.js +45 -20
- package/dist/cjs/modules/word/advanced/glossary.js +8 -9
- package/dist/cjs/modules/word/advanced/math-convert.js +94 -12
- package/dist/cjs/modules/word/advanced/ole-objects.js +123 -19
- package/dist/cjs/modules/word/advanced/style-map.js +31 -10
- package/dist/cjs/modules/word/builder/run-builders.js +7 -1
- package/dist/cjs/modules/word/constants.js +5 -1
- package/dist/cjs/modules/word/convert/docx-to-semantic.js +135 -1
- package/dist/cjs/modules/word/convert/html/html-import.js +168 -14
- package/dist/cjs/modules/word/convert/html/html.js +2 -1
- package/dist/cjs/modules/word/convert/markdown/markdown-import.js +279 -69
- package/dist/cjs/modules/word/convert/odt/odt.js +407 -56
- package/dist/cjs/modules/word/html.js +2 -1
- package/dist/cjs/modules/word/index.base.js +4 -3
- package/dist/cjs/modules/word/layout/layout-full.js +325 -18
- package/dist/cjs/modules/word/layout/render-page.js +35 -8
- package/dist/cjs/modules/word/query/compat.js +29 -21
- package/dist/cjs/modules/word/reader/docx-reader.js +104 -1
- package/dist/cjs/modules/word/reader/math-parser.js +8 -2
- package/dist/cjs/modules/word/security/cfb-reader.js +5 -5
- package/dist/cjs/modules/word/writer/docx-packager.js +108 -2
- package/dist/cjs/modules/word/writer/glossary-writer.js +124 -0
- package/dist/cjs/modules/word/writer/header-footer-writer.js +105 -20
- package/dist/cjs/modules/word/writer/math-writer.js +7 -2
- package/dist/cjs/utils/font-metrics.js +44 -0
- package/dist/cjs/utils/theme-colors.js +4 -1
- package/dist/esm/modules/excel/note.js +5 -1
- package/dist/esm/modules/excel/row.js +35 -2
- package/dist/esm/modules/excel/stream/workbook-writer.browser.js +22 -2
- package/dist/esm/modules/excel/utils/drawing-utils.js +19 -2
- package/dist/esm/modules/excel/workbook.browser.js +32 -2
- package/dist/esm/modules/excel/worksheet.js +83 -0
- package/dist/esm/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
- package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
- package/dist/esm/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/shape-xform.js +109 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
- package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
- package/dist/esm/modules/pdf/builder/document-builder.js +22 -49
- package/dist/esm/modules/pdf/builder/pdf-editor.js +1 -1
- package/dist/esm/modules/pdf/core/pdf-stream.js +38 -2
- package/dist/esm/modules/pdf/font/font-manager.js +35 -18
- package/dist/esm/modules/pdf/render/page-renderer.js +111 -18
- package/dist/esm/modules/word/advanced/field-engine.js +45 -20
- package/dist/esm/modules/word/advanced/glossary.js +8 -9
- package/dist/esm/modules/word/advanced/math-convert.js +94 -12
- package/dist/esm/modules/word/advanced/ole-objects.js +122 -19
- package/dist/esm/modules/word/advanced/style-map.js +31 -10
- package/dist/esm/modules/word/builder/run-builders.js +7 -1
- package/dist/esm/modules/word/constants.js +5 -1
- package/dist/esm/modules/word/convert/docx-to-semantic.js +135 -1
- package/dist/esm/modules/word/convert/html/html-import.js +167 -14
- package/dist/esm/modules/word/convert/html/html.js +1 -1
- package/dist/esm/modules/word/convert/markdown/markdown-import.js +279 -69
- package/dist/esm/modules/word/convert/odt/odt.js +407 -56
- package/dist/esm/modules/word/html.js +1 -1
- package/dist/esm/modules/word/index.base.js +1 -1
- package/dist/esm/modules/word/layout/layout-full.js +326 -19
- package/dist/esm/modules/word/layout/render-page.js +35 -8
- package/dist/esm/modules/word/query/compat.js +29 -21
- package/dist/esm/modules/word/reader/docx-reader.js +105 -2
- package/dist/esm/modules/word/reader/math-parser.js +8 -2
- package/dist/esm/modules/word/security/cfb-reader.js +5 -5
- package/dist/esm/modules/word/writer/docx-packager.js +108 -2
- package/dist/esm/modules/word/writer/glossary-writer.js +121 -0
- package/dist/esm/modules/word/writer/header-footer-writer.js +105 -20
- package/dist/esm/modules/word/writer/math-writer.js +7 -2
- package/dist/esm/utils/font-metrics.js +43 -0
- package/dist/esm/utils/theme-colors.js +4 -1
- package/dist/iife/excelts.iife.js +496 -59
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +39 -39
- package/dist/types/modules/excel/cell.d.ts +4 -0
- package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +8 -1
- package/dist/types/modules/excel/types.d.ts +81 -0
- package/dist/types/modules/excel/utils/drawing-utils.d.ts +8 -0
- package/dist/types/modules/excel/workbook.browser.d.ts +16 -0
- package/dist/types/modules/excel/worksheet.d.ts +31 -1
- package/dist/types/modules/excel/xlsx/xform/comment/vml-shape-xform.d.ts +7 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +6 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +1 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +2 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/shape-xform.d.ts +47 -0
- package/dist/types/modules/pdf/core/pdf-stream.d.ts +28 -1
- package/dist/types/modules/pdf/font/font-manager.d.ts +26 -0
- package/dist/types/modules/pdf/render/page-renderer.d.ts +51 -3
- package/dist/types/modules/word/advanced/glossary.d.ts +10 -36
- package/dist/types/modules/word/advanced/ole-objects.d.ts +28 -0
- package/dist/types/modules/word/builder/run-builders.d.ts +7 -1
- package/dist/types/modules/word/constants.d.ts +4 -0
- package/dist/types/modules/word/convert/docx-to-semantic.d.ts +2 -1
- package/dist/types/modules/word/convert/html/html-import.d.ts +32 -1
- package/dist/types/modules/word/convert/html/html.d.ts +2 -2
- package/dist/types/modules/word/convert/markdown/markdown-import.d.ts +48 -18
- package/dist/types/modules/word/convert/markdown/markdown.d.ts +1 -1
- package/dist/types/modules/word/html.d.ts +2 -2
- package/dist/types/modules/word/index.base.d.ts +3 -3
- package/dist/types/modules/word/markdown.d.ts +1 -1
- package/dist/types/modules/word/query/compat.d.ts +10 -2
- package/dist/types/modules/word/types.d.ts +96 -1
- package/dist/types/modules/word/writer/glossary-writer.d.ts +28 -0
- package/dist/types/utils/font-metrics.d.ts +8 -0
- package/package.json +3 -1
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { BaseXform } from "../base-xform.js";
|
|
2
|
+
const EMU_PER_POINT = 12700;
|
|
3
|
+
/**
|
|
4
|
+
* Normalize a user-supplied colour to the bare 6-digit RGB hex that OOXML's
|
|
5
|
+
* `<a:srgbClr val="...">` requires:
|
|
6
|
+
* - strips a leading `#`
|
|
7
|
+
* - accepts 8-digit ARGB (the form excelts uses for cell fills) and drops the
|
|
8
|
+
* leading alpha byte, since `srgbClr` carries no alpha channel
|
|
9
|
+
* - upper-cases
|
|
10
|
+
*
|
|
11
|
+
* Anything that isn't a 6- or 8-digit hex string is passed through unchanged so
|
|
12
|
+
* a caller using a less common form is not silently broken.
|
|
13
|
+
*/
|
|
14
|
+
function normalizeColor(color) {
|
|
15
|
+
const hex = color.startsWith("#") ? color.slice(1) : color;
|
|
16
|
+
if (/^[0-9a-fA-F]{8}$/.test(hex)) {
|
|
17
|
+
// ARGB → RGB: drop the alpha byte (srgbClr has no alpha component).
|
|
18
|
+
return hex.slice(2).toUpperCase();
|
|
19
|
+
}
|
|
20
|
+
if (/^[0-9a-fA-F]{6}$/.test(hex)) {
|
|
21
|
+
return hex.toUpperCase();
|
|
22
|
+
}
|
|
23
|
+
return hex;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Renders a user-visible drawing shape. Geometry/position is governed by the
|
|
27
|
+
* enclosing anchor (`xfrm` is written as zero, matching how Excel anchors a
|
|
28
|
+
* shape to a cell range), while preset geometry, fill, outline and text are
|
|
29
|
+
* taken from the model. Write-only: shapes are not parsed back on read (the
|
|
30
|
+
* same limitation that already applies to all non-chart drawing content).
|
|
31
|
+
*/
|
|
32
|
+
class ShapeXform extends BaseXform {
|
|
33
|
+
get tag() {
|
|
34
|
+
return "xdr:sp";
|
|
35
|
+
}
|
|
36
|
+
render(xmlStream, model) {
|
|
37
|
+
xmlStream.openNode("xdr:sp", { macro: "", textlink: "" });
|
|
38
|
+
// --- Non-visual shape properties ---
|
|
39
|
+
xmlStream.openNode("xdr:nvSpPr");
|
|
40
|
+
xmlStream.leafNode("xdr:cNvPr", { id: model.cNvPrId, name: model.name });
|
|
41
|
+
xmlStream.leafNode("xdr:cNvSpPr", {});
|
|
42
|
+
xmlStream.closeNode(); // xdr:nvSpPr
|
|
43
|
+
// --- Shape properties ---
|
|
44
|
+
xmlStream.openNode("xdr:spPr");
|
|
45
|
+
// Position/size is driven by the anchor; emit a zero xfrm placeholder.
|
|
46
|
+
xmlStream.openNode("a:xfrm");
|
|
47
|
+
xmlStream.leafNode("a:off", { x: 0, y: 0 });
|
|
48
|
+
xmlStream.leafNode("a:ext", { cx: 0, cy: 0 });
|
|
49
|
+
xmlStream.closeNode(); // a:xfrm
|
|
50
|
+
xmlStream.openNode("a:prstGeom", { prst: model.shapeType });
|
|
51
|
+
xmlStream.leafNode("a:avLst");
|
|
52
|
+
xmlStream.closeNode(); // a:prstGeom
|
|
53
|
+
// Fill: a colour produces a solidFill, otherwise an explicit noFill.
|
|
54
|
+
if (model.fill && model.fill.color) {
|
|
55
|
+
xmlStream.openNode("a:solidFill");
|
|
56
|
+
xmlStream.leafNode("a:srgbClr", { val: normalizeColor(model.fill.color) });
|
|
57
|
+
xmlStream.closeNode(); // a:solidFill
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
xmlStream.leafNode("a:noFill");
|
|
61
|
+
}
|
|
62
|
+
// Line: an `a:ln` with width (pt → EMU) and/or a solid colour. When width
|
|
63
|
+
// is given without a colour, Excel applies its default outline colour at
|
|
64
|
+
// that width. When neither colour nor width is supplied, emit an explicit
|
|
65
|
+
// noFill line (no visible outline).
|
|
66
|
+
if (model.line && (model.line.color || model.line.width !== undefined)) {
|
|
67
|
+
const lnAttrs = {};
|
|
68
|
+
if (model.line.width !== undefined) {
|
|
69
|
+
lnAttrs.w = Math.round(model.line.width * EMU_PER_POINT);
|
|
70
|
+
}
|
|
71
|
+
xmlStream.openNode("a:ln", lnAttrs);
|
|
72
|
+
if (model.line.color) {
|
|
73
|
+
xmlStream.openNode("a:solidFill");
|
|
74
|
+
xmlStream.leafNode("a:srgbClr", { val: normalizeColor(model.line.color) });
|
|
75
|
+
xmlStream.closeNode(); // a:solidFill
|
|
76
|
+
}
|
|
77
|
+
xmlStream.closeNode(); // a:ln
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
xmlStream.openNode("a:ln");
|
|
81
|
+
xmlStream.leafNode("a:noFill");
|
|
82
|
+
xmlStream.closeNode(); // a:ln
|
|
83
|
+
}
|
|
84
|
+
xmlStream.closeNode(); // xdr:spPr
|
|
85
|
+
// --- Text body ---
|
|
86
|
+
xmlStream.openNode("xdr:txBody");
|
|
87
|
+
xmlStream.leafNode("a:bodyPr", { vertOverflow: "clip", wrap: "square", anchor: "ctr" });
|
|
88
|
+
xmlStream.leafNode("a:lstStyle");
|
|
89
|
+
xmlStream.openNode("a:p");
|
|
90
|
+
xmlStream.openNode("a:pPr", { algn: "ctr" });
|
|
91
|
+
xmlStream.closeNode(); // a:pPr
|
|
92
|
+
if (model.text) {
|
|
93
|
+
xmlStream.openNode("a:r");
|
|
94
|
+
xmlStream.openNode("a:rPr", { lang: "en-US" });
|
|
95
|
+
xmlStream.closeNode(); // a:rPr
|
|
96
|
+
xmlStream.openNode("a:t");
|
|
97
|
+
xmlStream.writeText(model.text);
|
|
98
|
+
xmlStream.closeNode(); // a:t
|
|
99
|
+
xmlStream.closeNode(); // a:r
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
xmlStream.leafNode("a:endParaRPr", { lang: "en-US" });
|
|
103
|
+
}
|
|
104
|
+
xmlStream.closeNode(); // a:p
|
|
105
|
+
xmlStream.closeNode(); // xdr:txBody
|
|
106
|
+
xmlStream.closeNode(); // xdr:sp
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export { ShapeXform };
|
|
@@ -2,6 +2,7 @@ import { BaseCellAnchorXform } from "./base-cell-anchor-xform.js";
|
|
|
2
2
|
import { CellPositionXform } from "./cell-position-xform.js";
|
|
3
3
|
import { GraphicFrameXform } from "./graphic-frame-xform.js";
|
|
4
4
|
import { PicXform } from "./pic-xform.js";
|
|
5
|
+
import { ShapeXform } from "./shape-xform.js";
|
|
5
6
|
import { SpXform } from "./sp-xform.js";
|
|
6
7
|
import { StaticXform } from "../static-xform.js";
|
|
7
8
|
class TwoCellAnchorXform extends BaseCellAnchorXform {
|
|
@@ -24,6 +25,7 @@ class TwoCellAnchorXform extends BaseCellAnchorXform {
|
|
|
24
25
|
"xdr:to": new CellPositionXform({ tag: "xdr:to" }),
|
|
25
26
|
"xdr:pic": new PicXform(),
|
|
26
27
|
"xdr:sp": new SpXform(),
|
|
28
|
+
"xdr:userShape": new ShapeXform(),
|
|
27
29
|
"xdr:graphicFrame": new GraphicFrameXform(),
|
|
28
30
|
"xdr:clientData": new StaticXform({ tag: "xdr:clientData" })
|
|
29
31
|
};
|
|
@@ -125,7 +127,14 @@ class TwoCellAnchorXform extends BaseCellAnchorXform {
|
|
|
125
127
|
this.map["xdr:graphicFrame"].render(xmlStream, model.graphicFrame);
|
|
126
128
|
}
|
|
127
129
|
else if (model.shape) {
|
|
128
|
-
|
|
130
|
+
// A user-visible shape routes to the dedicated ShapeXform; the legacy
|
|
131
|
+
// form-control shape (no `kind`) stays on the SpXform path.
|
|
132
|
+
if (model.shape.kind === "userShape") {
|
|
133
|
+
this.map["xdr:userShape"].render(xmlStream, model.shape);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
this.map["xdr:sp"].render(xmlStream, model.shape);
|
|
137
|
+
}
|
|
129
138
|
}
|
|
130
139
|
this.map["xdr:clientData"].render(xmlStream, {});
|
|
131
140
|
xmlStream.closeNode(); // xdr:twoCellAnchor
|
|
@@ -498,7 +498,70 @@ class WorkSheetXform extends BaseXform {
|
|
|
498
498
|
});
|
|
499
499
|
}
|
|
500
500
|
}
|
|
501
|
-
// Handle
|
|
501
|
+
// Handle user-drawn shapes — anchored drawing parts with no media/rel.
|
|
502
|
+
const shapes = model.shapes ?? [];
|
|
503
|
+
if (shapes.length > 0) {
|
|
504
|
+
let { drawing } = model;
|
|
505
|
+
if (!drawing) {
|
|
506
|
+
drawing = model.drawing = {
|
|
507
|
+
rId: nextRid(rels),
|
|
508
|
+
name: `drawing${++options.drawingsCount}`,
|
|
509
|
+
anchors: [],
|
|
510
|
+
rels: []
|
|
511
|
+
};
|
|
512
|
+
options.drawings.push(drawing);
|
|
513
|
+
rels.push({
|
|
514
|
+
Id: drawing.rId,
|
|
515
|
+
Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
|
|
516
|
+
Target: drawingRelTargetFromWorksheet(drawing.name)
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
for (const shape of shapes) {
|
|
520
|
+
const anchorRange = shape.anchorRange;
|
|
521
|
+
if (!anchorRange) {
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
// Mirror the three image anchoring modes. `getAnchorType` (drawing
|
|
525
|
+
// xform) dispatches on `pos`/`br`: absolute when `pos` is present,
|
|
526
|
+
// two-cell when `br` is present, one-cell otherwise (needs `ext`).
|
|
527
|
+
let range;
|
|
528
|
+
if (anchorRange.pos) {
|
|
529
|
+
range = { pos: anchorRange.pos, ext: anchorRange.ext, editAs: "absolute" };
|
|
530
|
+
}
|
|
531
|
+
else if (anchorRange.br) {
|
|
532
|
+
range = {
|
|
533
|
+
tl: anchorRange.tl,
|
|
534
|
+
br: anchorRange.br,
|
|
535
|
+
editAs: anchorRange.editAs ?? "oneCell"
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
range = {
|
|
540
|
+
tl: anchorRange.tl,
|
|
541
|
+
ext: anchorRange.ext,
|
|
542
|
+
editAs: anchorRange.editAs ?? "oneCell"
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
// Allocate a cNvPr id from the same monotonic space as the anchor's
|
|
546
|
+
// position in the drawing so it never collides with image/chart ids
|
|
547
|
+
// (which derive from the anchor index).
|
|
548
|
+
const cNvPrId = drawing.anchors.length + 1;
|
|
549
|
+
drawing.anchors.push({
|
|
550
|
+
range,
|
|
551
|
+
shape: {
|
|
552
|
+
kind: "userShape",
|
|
553
|
+
cNvPrId,
|
|
554
|
+
name: shape.name ?? `Shape ${cNvPrId}`,
|
|
555
|
+
shapeType: shape.shapeType,
|
|
556
|
+
fill: shape.fillColor ? { color: shape.fillColor } : undefined,
|
|
557
|
+
line: shape.lineColor !== undefined || shape.lineWidth !== undefined
|
|
558
|
+
? { color: shape.lineColor, width: shape.lineWidth }
|
|
559
|
+
: undefined,
|
|
560
|
+
text: shape.text
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}
|
|
502
565
|
if (headerImageMedia.length > 0) {
|
|
503
566
|
const medium = headerImageMedia[0]; // Only one header image per sheet
|
|
504
567
|
const bookImage = options.media[medium.imageId];
|
|
@@ -26,7 +26,7 @@ import { writePdfAMetadata, writePdfAOutputIntent } from "../core/pdfa.js";
|
|
|
26
26
|
import { FontManager } from "../font/font-manager.js";
|
|
27
27
|
import { iterateSystemFontCandidates } from "../font/system-fonts.js";
|
|
28
28
|
import { parseTtf } from "../font/ttf-parser.js";
|
|
29
|
-
import {
|
|
29
|
+
import { emitTextBlock, alphaGsName } from "../render/page-renderer.js";
|
|
30
30
|
import { writeImageXObject } from "./image-utils.js";
|
|
31
31
|
// =============================================================================
|
|
32
32
|
// Constants
|
|
@@ -95,56 +95,29 @@ export class PdfPageBuilder {
|
|
|
95
95
|
const bold = options.bold ?? false;
|
|
96
96
|
const italic = options.italic ?? false;
|
|
97
97
|
const fontFamily = options.fontFamily ?? "Helvetica";
|
|
98
|
-
// Resolve
|
|
98
|
+
// Resolve the provisional Type1 resource and record the run's code
|
|
99
|
+
// points. The text is emitted as a deferred block so anchor alignment,
|
|
100
|
+
// word wrapping, and glyph encoding are all computed at build time —
|
|
101
|
+
// after fonts are finalised (a non-WinAnsi run may trigger a build-time
|
|
102
|
+
// auto-embed of a system CIDFont). Measuring against the provisional
|
|
103
|
+
// metrics here would misplace anchored text and break lines wrongly.
|
|
99
104
|
const resourceName = this._fontManager.resolveFont(fontFamily, bold, italic);
|
|
100
105
|
this._fontManager.trackText(text);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
: options.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const lines = wrapTextLines(text, measure, options.maxWidth);
|
|
117
|
-
const leading = fontSize * lineHeightFactor;
|
|
118
|
-
this._stream.save();
|
|
119
|
-
this._applyAlpha(color.a);
|
|
120
|
-
this._stream.setFillColor(color);
|
|
121
|
-
for (let i = 0; i < lines.length; i++) {
|
|
122
|
-
const lineY = options.y - i * leading;
|
|
123
|
-
emitTextWithMatrix(this._stream, lines[i], 1, 0, 0, 1, options.x, lineY, resourceName, fontSize, this._fontManager, useType3);
|
|
124
|
-
}
|
|
125
|
-
this._stream.restore();
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
// Single line
|
|
129
|
-
this._stream.save();
|
|
130
|
-
this._applyAlpha(color.a);
|
|
131
|
-
this._stream.setFillColor(color);
|
|
132
|
-
const rotation = options.rotation ?? 0;
|
|
133
|
-
if (rotation === 0) {
|
|
134
|
-
emitTextWithMatrix(this._stream, text, 1, 0, 0, 1, resolvedX, options.y, resourceName, fontSize, this._fontManager, useType3);
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
// Build the rotation matrix around (x, y). `emitTextWithMatrix`
|
|
138
|
-
// accepts the full 2×3 text matrix, so we pre-multiply the
|
|
139
|
-
// rotation with the translation so one call positions and
|
|
140
|
-
// rotates the glyph sequence in a single Tm op.
|
|
141
|
-
const theta = (rotation * Math.PI) / 180;
|
|
142
|
-
const cos = Math.cos(theta);
|
|
143
|
-
const sin = Math.sin(theta);
|
|
144
|
-
emitTextWithMatrix(this._stream, text, cos, sin, -sin, cos, resolvedX, options.y, resourceName, fontSize, this._fontManager, useType3);
|
|
145
|
-
}
|
|
146
|
-
this._stream.restore();
|
|
147
|
-
}
|
|
106
|
+
this._stream.save();
|
|
107
|
+
this._applyAlpha(color.a);
|
|
108
|
+
this._stream.setFillColor(color);
|
|
109
|
+
emitTextBlock(this._stream, {
|
|
110
|
+
text,
|
|
111
|
+
x: options.x,
|
|
112
|
+
y: options.y,
|
|
113
|
+
type1ResourceName: resourceName,
|
|
114
|
+
fontSize,
|
|
115
|
+
anchor: options.anchor ?? "start",
|
|
116
|
+
maxWidth: options.maxWidth,
|
|
117
|
+
lineHeightFactor,
|
|
118
|
+
rotation: options.rotation ?? 0
|
|
119
|
+
}, this._fontManager);
|
|
120
|
+
this._stream.restore();
|
|
148
121
|
return this;
|
|
149
122
|
}
|
|
150
123
|
/**
|
|
@@ -144,7 +144,7 @@ export class PdfEditorPage {
|
|
|
144
144
|
}
|
|
145
145
|
/** @internal */
|
|
146
146
|
_hasOverlay() {
|
|
147
|
-
return (this._overlay._stream.
|
|
147
|
+
return (this._overlay._stream.hasContent() ||
|
|
148
148
|
this._overlay._images.length > 0 ||
|
|
149
149
|
this._overlay._builderAnnotations.length > 0 ||
|
|
150
150
|
this._overlay._formFields.length > 0);
|
|
@@ -23,6 +23,16 @@ import { pdfNumber } from "./pdf-object.js";
|
|
|
23
23
|
*/
|
|
24
24
|
export class PdfContentStream {
|
|
25
25
|
constructor() {
|
|
26
|
+
/**
|
|
27
|
+
* Content stream fragments in draw order. Most entries are plain operator
|
|
28
|
+
* strings produced eagerly. A function entry is a *deferred* fragment:
|
|
29
|
+
* its body is only evaluated at serialization time. This is required for
|
|
30
|
+
* text whose final byte encoding depends on font decisions (embedded
|
|
31
|
+
* CIDFont vs. Type1/WinAnsi vs. Type3 fallback) that are not finalised
|
|
32
|
+
* until `PdfDocumentBuilder.build()`. Deferring keeps the fragment at its
|
|
33
|
+
* exact draw-order position (preserving z-order) while letting the actual
|
|
34
|
+
* encoding run after the font manager's state is settled.
|
|
35
|
+
*/
|
|
26
36
|
this.parts = [];
|
|
27
37
|
}
|
|
28
38
|
// ===========================================================================
|
|
@@ -36,6 +46,17 @@ export class PdfContentStream {
|
|
|
36
46
|
this.parts.push(operator);
|
|
37
47
|
return this;
|
|
38
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Append a deferred fragment whose body is evaluated only at serialization
|
|
51
|
+
* time. Used by text drawing so the final byte encoding can be chosen after
|
|
52
|
+
* the document's fonts are resolved at build time. The fragment occupies its
|
|
53
|
+
* draw-order slot immediately, so z-order relative to other operators is
|
|
54
|
+
* preserved.
|
|
55
|
+
*/
|
|
56
|
+
deferred(produce) {
|
|
57
|
+
this.parts.push(produce);
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
39
60
|
// ===========================================================================
|
|
40
61
|
// Graphics State
|
|
41
62
|
// ===========================================================================
|
|
@@ -435,10 +456,25 @@ export class PdfContentStream {
|
|
|
435
456
|
// Serialization
|
|
436
457
|
// ===========================================================================
|
|
437
458
|
/**
|
|
438
|
-
*
|
|
459
|
+
* Whether any fragment has been appended. Unlike `toString().length > 0`,
|
|
460
|
+
* this does NOT evaluate deferred fragments, so it is safe to call before
|
|
461
|
+
* fonts are resolved (e.g. when probing for overlay content during an
|
|
462
|
+
* editor save, prior to `writeFontResources`). A deferred text fragment
|
|
463
|
+
* counts as content even though its bytes are not produced yet.
|
|
464
|
+
*/
|
|
465
|
+
hasContent() {
|
|
466
|
+
return this.parts.length > 0;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Get the content stream as a string. Deferred fragments (see `deferred`)
|
|
470
|
+
* are evaluated here, after font resolution has completed at build time.
|
|
439
471
|
*/
|
|
440
472
|
toString() {
|
|
441
|
-
|
|
473
|
+
const out = [];
|
|
474
|
+
for (const part of this.parts) {
|
|
475
|
+
out.push(typeof part === "function" ? part() : part);
|
|
476
|
+
}
|
|
477
|
+
return out.join("\n");
|
|
442
478
|
}
|
|
443
479
|
/**
|
|
444
480
|
* Get the content stream as a Uint8Array (UTF-8 encoded).
|
|
@@ -182,30 +182,47 @@ export class FontManager {
|
|
|
182
182
|
getEmbeddedResourceName() {
|
|
183
183
|
return this.embeddedResourceName;
|
|
184
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Resolve the resource name a draw-time-resolved Type1 resource should
|
|
187
|
+
* actually render (and be measured) with, given the font manager's
|
|
188
|
+
* *current* state. If an embedded font exists (possibly auto-discovered
|
|
189
|
+
* at build time, after the text was drawn against a Type1 resource), the
|
|
190
|
+
* embedded resource name is returned so both measurement and encoding go
|
|
191
|
+
* through the CIDFont. Otherwise the original Type1 resource name is kept;
|
|
192
|
+
* `measureText` handles Type3-fallback widths internally from that name.
|
|
193
|
+
*
|
|
194
|
+
* Centralises the routing rule shared by the deferred text renderer and
|
|
195
|
+
* any deferred measurement (anchor alignment, word wrapping) so the two
|
|
196
|
+
* never disagree.
|
|
197
|
+
*/
|
|
198
|
+
resolveRenderResourceName(type1ResourceName) {
|
|
199
|
+
return this.embeddedFont ? this.embeddedResourceName : type1ResourceName;
|
|
200
|
+
}
|
|
185
201
|
/**
|
|
186
202
|
* Record that a text string will be rendered, tracking its code points.
|
|
187
203
|
* Must be called for every text string before writing the PDF.
|
|
204
|
+
*
|
|
205
|
+
* Two sets are maintained because font selection may be decided *after*
|
|
206
|
+
* drawing (e.g. `PdfDocumentBuilder.build()` auto-discovers and embeds a
|
|
207
|
+
* system font once it sees the accumulated non-WinAnsi code points):
|
|
208
|
+
*
|
|
209
|
+
* - `usedCodePoints` — every code point seen, always. If an embedded
|
|
210
|
+
* font ends up being used (whether registered up front or
|
|
211
|
+
* auto-discovered at build time), the subset must cover all of these,
|
|
212
|
+
* including plain ASCII, so the CIDFont can encode the full run.
|
|
213
|
+
* - `type3CodePoints` — non-WinAnsi code points only. Drives the
|
|
214
|
+
* build-time decision to auto-embed a system font, and the Type3
|
|
215
|
+
* fallback when none is available.
|
|
188
216
|
*/
|
|
189
217
|
trackText(text) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (cp > 0xffff) {
|
|
195
|
-
i++; // skip low surrogate
|
|
196
|
-
}
|
|
218
|
+
for (let i = 0; i < text.length; i++) {
|
|
219
|
+
const cp = text.codePointAt(i);
|
|
220
|
+
if (cp > 0xffff) {
|
|
221
|
+
i++; // skip low surrogate
|
|
197
222
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
for (let i = 0; i < text.length; i++) {
|
|
202
|
-
const cp = text.codePointAt(i);
|
|
203
|
-
if (cp > 0xffff) {
|
|
204
|
-
i++;
|
|
205
|
-
}
|
|
206
|
-
if (!isWinAnsiCodePoint(cp)) {
|
|
207
|
-
this.type3CodePoints.add(cp);
|
|
208
|
-
}
|
|
223
|
+
this.usedCodePoints.add(cp);
|
|
224
|
+
if (!isWinAnsiCodePoint(cp)) {
|
|
225
|
+
this.type3CodePoints.add(cp);
|
|
209
226
|
}
|
|
210
227
|
}
|
|
211
228
|
}
|
|
@@ -675,7 +675,7 @@ function drawRotatedGeneral(stream, cell, lines, fontManager, resourceName, font
|
|
|
675
675
|
emitTextWithMatrix(stream, line, cos, sin, -sin, cos, tx, ty, resourceName, fontSize, fontManager, useType3);
|
|
676
676
|
}
|
|
677
677
|
}
|
|
678
|
-
/** Emit a text string with hex encoding if available. */
|
|
678
|
+
/** Emit a text string with hex encoding if available, onto a sink stream. */
|
|
679
679
|
function emitText(stream, fontManager, text, resourceName) {
|
|
680
680
|
const hex = fontManager.encodeText(text, resourceName);
|
|
681
681
|
if (hex) {
|
|
@@ -690,40 +690,133 @@ function emitText(stream, fontManager, text, resourceName) {
|
|
|
690
690
|
* when needed. For each sub-run the matrix origin is advanced along the
|
|
691
691
|
* text direction (cos, sin) by the rendered width.
|
|
692
692
|
*
|
|
693
|
-
*
|
|
694
|
-
*
|
|
693
|
+
* The emitted operators are written as a *deferred* fragment (see
|
|
694
|
+
* `PdfContentStream.deferred`). The fragment is only evaluated at
|
|
695
|
+
* serialization time, by which point `PdfDocumentBuilder.build()` has
|
|
696
|
+
* finalised the document's fonts (auto-discovered embedded CIDFont,
|
|
697
|
+
* Type3 fallback, or plain Type1). This is essential: at draw time the
|
|
698
|
+
* font manager has not yet decided whether a non-WinAnsi code point (e.g.
|
|
699
|
+
* U+2192 →) will be served by an embedded font or a Type3 glyph, so eager
|
|
700
|
+
* encoding would irreversibly degrade those characters to spaces via the
|
|
701
|
+
* WinAnsi fallback. Deferring the encode keeps the fragment at its exact
|
|
702
|
+
* draw-order slot (preserving z-order) while choosing the correct bytes
|
|
703
|
+
* once fonts are known.
|
|
704
|
+
*
|
|
705
|
+
* The `useType3` argument is the caller's *draw-time* guess and is ignored;
|
|
706
|
+
* the deferred body recomputes the routing from the now-settled font
|
|
707
|
+
* manager state.
|
|
708
|
+
*/
|
|
709
|
+
export function emitTextWithMatrix(stream, text, a, b, c, d, tx, ty, type1ResourceName, fontSize, fontManager, _useType3) {
|
|
710
|
+
stream.deferred(() => renderTextBlock(text, a, b, c, d, tx, ty, type1ResourceName, fontSize, fontManager));
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Emit a text block as a single *deferred* fragment so that anchor
|
|
714
|
+
* alignment, word wrapping, and glyph encoding are all computed at
|
|
715
|
+
* serialization time — after `PdfDocumentBuilder.build()` has finalised the
|
|
716
|
+
* document's fonts.
|
|
717
|
+
*
|
|
718
|
+
* This matters because text measurement (anchor offset, line breaking) must
|
|
719
|
+
* use the *same* font that ultimately renders the glyphs. At draw time the
|
|
720
|
+
* font may still be unresolved (a non-WinAnsi run can trigger a build-time
|
|
721
|
+
* auto-embed of a system CIDFont), so measuring against the provisional
|
|
722
|
+
* Type1/Helvetica metrics would misplace centred/right-aligned text and
|
|
723
|
+
* break lines at the wrong points. Deferring keeps measurement and encoding
|
|
724
|
+
* consistent while preserving the fragment's draw-order slot (z-order).
|
|
725
|
+
*/
|
|
726
|
+
export function emitTextBlock(stream, options, fontManager) {
|
|
727
|
+
stream.deferred(() => renderTextBlockLayout(options, fontManager));
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Lay out and render a text block from the font manager's *current*
|
|
731
|
+
* (build-time) state. Resolves the render resource name once and uses it for
|
|
732
|
+
* both measurement and encoding so the two never disagree.
|
|
733
|
+
*
|
|
734
|
+
* Layout is computed in the text's *local* coordinate frame — x grows along
|
|
735
|
+
* the baseline, y grows upward — then mapped to page space through the
|
|
736
|
+
* rotation matrix. This makes anchor alignment, multi-line word wrapping, and
|
|
737
|
+
* rotation compose correctly together: each line is offset by its anchor
|
|
738
|
+
* shift (along local x) and its line index (down local y), and a single
|
|
739
|
+
* rotation maps the whole block into place. Upright text (rotation 0) reduces
|
|
740
|
+
* to the identity mapping.
|
|
741
|
+
*/
|
|
742
|
+
function renderTextBlockLayout(options, fontManager) {
|
|
743
|
+
const { text, x, y, type1ResourceName, fontSize, anchor, maxWidth, lineHeightFactor, rotation } = options;
|
|
744
|
+
// Resolve the resource name once; measurement and rendering share it so a
|
|
745
|
+
// build-time auto-embedded CIDFont (or Type3 fallback) is measured with the
|
|
746
|
+
// metrics that will actually render the glyphs.
|
|
747
|
+
const measureResource = fontManager.resolveRenderResourceName(type1ResourceName);
|
|
748
|
+
const measure = (s) => fontManager.measureText(s, measureResource, fontSize);
|
|
749
|
+
const lines = maxWidth ? wrapTextLines(text, measure, maxWidth) : [text];
|
|
750
|
+
const leading = fontSize * lineHeightFactor;
|
|
751
|
+
// Rotation matrix [a b; c d] = [cos sin; -sin cos]; identity when upright.
|
|
752
|
+
const theta = (rotation * Math.PI) / 180;
|
|
753
|
+
const cos = rotation === 0 ? 1 : Math.cos(theta);
|
|
754
|
+
const sin = rotation === 0 ? 0 : Math.sin(theta);
|
|
755
|
+
const anchorFactor = anchor === "middle" ? 0.5 : anchor === "end" ? 1 : 0;
|
|
756
|
+
const parts = [];
|
|
757
|
+
for (let i = 0; i < lines.length; i++) {
|
|
758
|
+
// Local-frame origin of this line: anchor shift along x, line index down y.
|
|
759
|
+
const localX = anchorFactor === 0 ? 0 : -measure(lines[i]) * anchorFactor;
|
|
760
|
+
const localY = -i * leading;
|
|
761
|
+
// Map local origin into page space through the rotation matrix.
|
|
762
|
+
const tx = x + localX * cos + localY * -sin;
|
|
763
|
+
const ty = y + localX * sin + localY * cos;
|
|
764
|
+
parts.push(renderTextBlock(lines[i], cos, sin, -sin, cos, tx, ty, type1ResourceName, fontSize, fontManager));
|
|
765
|
+
}
|
|
766
|
+
return parts.join("\n");
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Produce the PDF operator string for a positioned text run, choosing the
|
|
770
|
+
* encoding from the font manager's *current* (build-time) state:
|
|
771
|
+
* - embedded font → single BT/ET with CIDFont hex encoding
|
|
772
|
+
* - Type3 fallback → split into WinAnsi (Type1) and per-glyph Type3 runs
|
|
773
|
+
* - neither → single BT/ET with Type1/WinAnsi encoding
|
|
774
|
+
*
|
|
775
|
+
* Must only be called after font resolution (i.e. from a deferred fragment).
|
|
695
776
|
*/
|
|
696
|
-
|
|
777
|
+
function renderTextBlock(text, a, b, c, d, tx, ty, type1ResourceName, fontSize, fontManager) {
|
|
778
|
+
const sink = new PdfContentStream();
|
|
779
|
+
// Type3 splitting only applies when there is no embedded font but Type3
|
|
780
|
+
// fallback glyphs were generated. Otherwise the run renders as a single
|
|
781
|
+
// BT/ET pair, choosing the resource name from the now-settled state:
|
|
782
|
+
// if an embedded font exists (possibly auto-discovered at build time,
|
|
783
|
+
// after this run was drawn against a Type1 resource), the run must use it
|
|
784
|
+
// so `emitText` → `encodeText` produces CIDFont hex; without that switch
|
|
785
|
+
// the stale Type1 resource name would make `encodeText` return null and
|
|
786
|
+
// non-WinAnsi characters would degrade to spaces via the WinAnsi fallback.
|
|
787
|
+
const useType3 = fontManager.hasType3Fonts() && !fontManager.hasEmbeddedFont();
|
|
697
788
|
if (!useType3) {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
789
|
+
const resourceName = fontManager.resolveRenderResourceName(type1ResourceName);
|
|
790
|
+
sink.beginText();
|
|
791
|
+
sink.setFont(resourceName, fontSize);
|
|
792
|
+
sink.setTextMatrix(a, b, c, d, tx, ty);
|
|
793
|
+
emitText(sink, fontManager, text, resourceName);
|
|
794
|
+
sink.endText();
|
|
795
|
+
return sink.toString();
|
|
704
796
|
}
|
|
705
797
|
// Type3 path: split into runs and advance origin along text direction
|
|
706
798
|
const runs = splitTextRuns(text, fontManager);
|
|
707
799
|
let curTx = tx;
|
|
708
800
|
let curTy = ty;
|
|
709
801
|
for (const run of runs) {
|
|
710
|
-
|
|
802
|
+
sink.beginText();
|
|
711
803
|
if (run.type3) {
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
804
|
+
sink.setFont(run.type3.resourceName, fontSize);
|
|
805
|
+
sink.setTextMatrix(a, b, c, d, curTx, curTy);
|
|
806
|
+
sink.showTextHex(run.type3.hex);
|
|
715
807
|
}
|
|
716
808
|
else {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
emitText(
|
|
809
|
+
sink.setFont(type1ResourceName, fontSize);
|
|
810
|
+
sink.setTextMatrix(a, b, c, d, curTx, curTy);
|
|
811
|
+
emitText(sink, fontManager, run.text, type1ResourceName);
|
|
720
812
|
}
|
|
721
|
-
|
|
813
|
+
sink.endText();
|
|
722
814
|
const w = fontManager.measureText(run.text, run.type3?.resourceName ?? type1ResourceName, fontSize);
|
|
723
815
|
// Advance along the text direction (first column of the matrix)
|
|
724
816
|
curTx += a * w;
|
|
725
817
|
curTy += b * w;
|
|
726
818
|
}
|
|
819
|
+
return sink.toString();
|
|
727
820
|
}
|
|
728
821
|
/**
|
|
729
822
|
* Split a line of text into runs of consecutive WinAnsi and non-WinAnsi
|