@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
|
@@ -5,6 +5,8 @@ interface ShapeModel {
|
|
|
5
5
|
insetmode?: string;
|
|
6
6
|
inset?: number[];
|
|
7
7
|
};
|
|
8
|
+
width?: number;
|
|
9
|
+
height?: number;
|
|
8
10
|
};
|
|
9
11
|
refAddress?: any;
|
|
10
12
|
}
|
|
@@ -20,6 +22,11 @@ declare class VmlShapeXform extends BaseXform {
|
|
|
20
22
|
parseOpen(node: any): boolean;
|
|
21
23
|
parseText(text: string): void;
|
|
22
24
|
parseClose(name: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Extract a points-valued length (e.g. `width:120pt`) from a VML style
|
|
27
|
+
* string. Returns `undefined` when the property is absent or not in `pt`.
|
|
28
|
+
*/
|
|
29
|
+
static parseStyleLength(style: string, prop: "width" | "height"): number | undefined;
|
|
23
30
|
static V_SHAPE_ATTRIBUTES: (model: ShapeModel, index: number) => any;
|
|
24
31
|
}
|
|
25
32
|
export { VmlShapeXform };
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { BaseXform } from "../base-xform.js";
|
|
2
2
|
import { VmlClientDataXform } from "./vml-client-data-xform.js";
|
|
3
3
|
import { VmlTextboxXform } from "./vml-textbox-xform.js";
|
|
4
|
+
/** Default comment box geometry in points (matches legacy Excel notes). */
|
|
5
|
+
const DEFAULT_NOTE_WIDTH_PT = 97.8;
|
|
6
|
+
const DEFAULT_NOTE_HEIGHT_PT = 59.1;
|
|
4
7
|
class VmlShapeXform extends BaseXform {
|
|
5
8
|
constructor() {
|
|
6
9
|
super();
|
|
@@ -37,6 +40,21 @@ class VmlShapeXform extends BaseXform {
|
|
|
37
40
|
editAs: "",
|
|
38
41
|
protection: {}
|
|
39
42
|
};
|
|
43
|
+
{
|
|
44
|
+
// Recover the comment box geometry from the VML style string
|
|
45
|
+
// (e.g. "...width:120pt;height:80pt;..."). Only surface width/height
|
|
46
|
+
// when they differ from the legacy defaults, so untouched notes keep
|
|
47
|
+
// a clean model (and stay byte-compatible with prior behaviour).
|
|
48
|
+
const style = node.attributes.style ?? "";
|
|
49
|
+
const width = VmlShapeXform.parseStyleLength(style, "width");
|
|
50
|
+
const height = VmlShapeXform.parseStyleLength(style, "height");
|
|
51
|
+
if (width !== undefined && width !== DEFAULT_NOTE_WIDTH_PT) {
|
|
52
|
+
this.model.width = width;
|
|
53
|
+
}
|
|
54
|
+
if (height !== undefined && height !== DEFAULT_NOTE_HEIGHT_PT) {
|
|
55
|
+
this.model.height = height;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
40
58
|
break;
|
|
41
59
|
default:
|
|
42
60
|
this.parser = this.map[node.name];
|
|
@@ -75,13 +93,29 @@ class VmlShapeXform extends BaseXform {
|
|
|
75
93
|
return true;
|
|
76
94
|
}
|
|
77
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Extract a points-valued length (e.g. `width:120pt`) from a VML style
|
|
98
|
+
* string. Returns `undefined` when the property is absent or not in `pt`.
|
|
99
|
+
*/
|
|
100
|
+
static parseStyleLength(style, prop) {
|
|
101
|
+
const match = new RegExp(`(?:^|;)\\s*${prop}\\s*:\\s*([0-9.]+)pt`, "i").exec(style);
|
|
102
|
+
if (!match) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
const value = parseFloat(match[1]);
|
|
106
|
+
return Number.isFinite(value) ? value : undefined;
|
|
107
|
+
}
|
|
78
108
|
}
|
|
79
|
-
VmlShapeXform.V_SHAPE_ATTRIBUTES = (model, index) =>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
109
|
+
VmlShapeXform.V_SHAPE_ATTRIBUTES = (model, index) => {
|
|
110
|
+
const width = model.note?.width ?? DEFAULT_NOTE_WIDTH_PT;
|
|
111
|
+
const height = model.note?.height ?? DEFAULT_NOTE_HEIGHT_PT;
|
|
112
|
+
return {
|
|
113
|
+
id: `_x0000_s${1025 + index}`,
|
|
114
|
+
type: "#_x0000_t202",
|
|
115
|
+
style: `position:absolute; margin-left:105.3pt;margin-top:10.5pt;width:${width}pt;height:${height}pt;z-index:1;visibility:hidden`,
|
|
116
|
+
fillcolor: "infoBackground [80]",
|
|
117
|
+
strokecolor: "none [81]",
|
|
118
|
+
"o:insetmode": model.note.margins && model.note.margins.insetmode
|
|
119
|
+
};
|
|
120
|
+
};
|
|
87
121
|
export { VmlShapeXform };
|
|
@@ -21,7 +21,9 @@ class ContentTypesXform extends BaseXform {
|
|
|
21
21
|
mediaHash[imageType] = true;
|
|
22
22
|
xmlStream.leafNode("Default", {
|
|
23
23
|
Extension: imageType,
|
|
24
|
-
|
|
24
|
+
// SVG's IANA media type is "image/svg+xml"; everything else follows
|
|
25
|
+
// the "image/<ext>" convention.
|
|
26
|
+
ContentType: imageType === "svg" ? "image/svg+xml" : `image/${imageType}`
|
|
25
27
|
});
|
|
26
28
|
}
|
|
27
29
|
}
|
|
@@ -3,6 +3,7 @@ import { BaseCellAnchorXform } from "./base-cell-anchor-xform.js";
|
|
|
3
3
|
import { ExtXform } from "./ext-xform.js";
|
|
4
4
|
import { GraphicFrameXform } from "./graphic-frame-xform.js";
|
|
5
5
|
import { PicXform } from "./pic-xform.js";
|
|
6
|
+
import { ShapeXform } from "./shape-xform.js";
|
|
6
7
|
import { StaticXform } from "../static-xform.js";
|
|
7
8
|
/** https://en.wikipedia.org/wiki/Office_Open_XML_file_formats#DrawingML */
|
|
8
9
|
const EMU_PER_PIXEL_AT_96_DPI = 9525;
|
|
@@ -70,6 +71,7 @@ class AbsoluteAnchorXform extends BaseCellAnchorXform {
|
|
|
70
71
|
// `<xdr:absoluteAnchor><xdr:pos/><xdr:ext/><xdr:clientData/></xdr:absoluteAnchor>`
|
|
71
72
|
// with no chart reference, so the anchor was ignored on open.
|
|
72
73
|
"xdr:graphicFrame": new GraphicFrameXform(),
|
|
74
|
+
"xdr:userShape": new ShapeXform(),
|
|
73
75
|
"xdr:clientData": new StaticXform({ tag: "xdr:clientData" })
|
|
74
76
|
};
|
|
75
77
|
}
|
|
@@ -94,6 +96,9 @@ class AbsoluteAnchorXform extends BaseCellAnchorXform {
|
|
|
94
96
|
else if (model.graphicFrame) {
|
|
95
97
|
this.map["xdr:graphicFrame"].render(xmlStream, model.graphicFrame);
|
|
96
98
|
}
|
|
99
|
+
else if (model.shape?.kind === "userShape") {
|
|
100
|
+
this.map["xdr:userShape"].render(xmlStream, model.shape);
|
|
101
|
+
}
|
|
97
102
|
this.map["xdr:clientData"].render(xmlStream, {});
|
|
98
103
|
xmlStream.closeNode();
|
|
99
104
|
}
|
|
@@ -47,8 +47,25 @@ class BaseCellAnchorXform extends BaseXform {
|
|
|
47
47
|
const name = match[1];
|
|
48
48
|
const mediaId = options.mediaIndex[name];
|
|
49
49
|
const medium = options.media[mediaId];
|
|
50
|
+
if (!medium) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
// Resolve an SVG companion (asvg:svgBlip extension) back to its media
|
|
54
|
+
// index and record it on the raster media entry itself, so callers that
|
|
55
|
+
// look the image up by id (e.g. Workbook.getImage) surface the vector
|
|
56
|
+
// companion alongside the raster fallback.
|
|
57
|
+
if (model.svgRId) {
|
|
58
|
+
const svgRel = options.rels[model.svgRId];
|
|
59
|
+
const svgMatch = svgRel && svgRel.Target.match(/.*\/media\/(.+[.][a-zA-Z]{3,4})/);
|
|
60
|
+
if (svgMatch) {
|
|
61
|
+
const svgMediaId = options.mediaIndex[svgMatch[1]];
|
|
62
|
+
if (svgMediaId !== undefined) {
|
|
63
|
+
medium.svgMediaId = svgMediaId;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
50
67
|
// Preserve alphaModFix (transparency) from the picture model if present
|
|
51
|
-
if (
|
|
68
|
+
if (model.alphaModFix !== undefined) {
|
|
52
69
|
return { ...medium, alphaModFix: model.alphaModFix };
|
|
53
70
|
}
|
|
54
71
|
return medium;
|
|
@@ -8,6 +8,12 @@ interface BlipModel {
|
|
|
8
8
|
* instead of an embedded one via `r:embed`.
|
|
9
9
|
*/
|
|
10
10
|
external?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Relationship id of an SVG companion. When set, the raster blip carries an
|
|
13
|
+
* `asvg:svgBlip` extension referencing the SVG media so Excel 2016+ renders
|
|
14
|
+
* the vector image while older consumers fall back to the raster blip.
|
|
15
|
+
*/
|
|
16
|
+
svgRId?: string;
|
|
11
17
|
}
|
|
12
18
|
declare class BlipXform extends BaseXform<BlipModel> {
|
|
13
19
|
constructor();
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { BaseXform } from "../base-xform.js";
|
|
2
|
+
/** OOXML extension URI for the SVG blip (Office 2016 SVG feature). */
|
|
3
|
+
const SVG_BLIP_EXT_URI = "{96DAC541-7B7A-43D3-8B79-37D633B846F1}";
|
|
4
|
+
/** Namespace for the asvg:svgBlip element. */
|
|
5
|
+
const SVG_BLIP_NS = "http://schemas.microsoft.com/office/drawing/2016/SVG/main";
|
|
6
|
+
const REL_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
2
7
|
class BlipXform extends BaseXform {
|
|
3
8
|
constructor() {
|
|
4
9
|
super();
|
|
@@ -10,23 +15,37 @@ class BlipXform extends BaseXform {
|
|
|
10
15
|
render(xmlStream, model) {
|
|
11
16
|
// External (linked) images use `r:link`; embedded images use `r:embed`.
|
|
12
17
|
const relAttr = model.external ? "r:link" : "r:embed";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
const hasAlpha = model.alphaModFix !== undefined && model.alphaModFix < 100000;
|
|
19
|
+
const hasSvg = model.svgRId !== undefined;
|
|
20
|
+
// A bare blip can be a leaf node; an alpha modulation or an SVG extension
|
|
21
|
+
// both require child elements, so switch to the open/close form.
|
|
22
|
+
if (!hasAlpha && !hasSvg) {
|
|
23
|
+
xmlStream.leafNode(this.tag, {
|
|
24
|
+
"xmlns:r": REL_NS,
|
|
17
25
|
[relAttr]: model.rId,
|
|
18
26
|
cstate: "print"
|
|
19
27
|
});
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
xmlStream.openNode(this.tag, {
|
|
31
|
+
"xmlns:r": REL_NS,
|
|
32
|
+
[relAttr]: model.rId,
|
|
33
|
+
cstate: "print"
|
|
34
|
+
});
|
|
35
|
+
if (hasAlpha) {
|
|
20
36
|
xmlStream.leafNode("a:alphaModFix", { amt: String(model.alphaModFix) });
|
|
21
|
-
xmlStream.closeNode();
|
|
22
37
|
}
|
|
23
|
-
|
|
24
|
-
xmlStream.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
38
|
+
if (hasSvg) {
|
|
39
|
+
xmlStream.openNode("a:extLst");
|
|
40
|
+
xmlStream.openNode("a:ext", { uri: SVG_BLIP_EXT_URI });
|
|
41
|
+
xmlStream.leafNode("asvg:svgBlip", {
|
|
42
|
+
"xmlns:asvg": SVG_BLIP_NS,
|
|
43
|
+
"r:embed": model.svgRId
|
|
28
44
|
});
|
|
45
|
+
xmlStream.closeNode(); // a:ext
|
|
46
|
+
xmlStream.closeNode(); // a:extLst
|
|
29
47
|
}
|
|
48
|
+
xmlStream.closeNode(); // a:blip
|
|
30
49
|
}
|
|
31
50
|
parseOpen(node) {
|
|
32
51
|
switch (node.name) {
|
|
@@ -46,6 +65,14 @@ class BlipXform extends BaseXform {
|
|
|
46
65
|
this.model.alphaModFix = parseInt(node.attributes.amt, 10);
|
|
47
66
|
}
|
|
48
67
|
return true;
|
|
68
|
+
case "asvg:svgBlip": {
|
|
69
|
+
// Capture the SVG companion's relationship id for round-trip.
|
|
70
|
+
const embed = node.attributes["r:embed"];
|
|
71
|
+
if (embed !== undefined && this.model) {
|
|
72
|
+
this.model.svgRId = embed;
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
49
76
|
default:
|
|
50
77
|
return true;
|
|
51
78
|
}
|
|
@@ -56,7 +83,7 @@ class BlipXform extends BaseXform {
|
|
|
56
83
|
case this.tag:
|
|
57
84
|
return false;
|
|
58
85
|
default:
|
|
59
|
-
// unprocessed internal nodes
|
|
86
|
+
// unprocessed internal nodes (a:extLst / a:ext / alphaModFix)
|
|
60
87
|
return true;
|
|
61
88
|
}
|
|
62
89
|
}
|
|
@@ -3,6 +3,7 @@ import { CellPositionXform } from "./cell-position-xform.js";
|
|
|
3
3
|
import { ExtXform } from "./ext-xform.js";
|
|
4
4
|
import { GraphicFrameXform } from "./graphic-frame-xform.js";
|
|
5
5
|
import { PicXform } from "./pic-xform.js";
|
|
6
|
+
import { ShapeXform } from "./shape-xform.js";
|
|
6
7
|
import { StaticXform } from "../static-xform.js";
|
|
7
8
|
class OneCellAnchorXform extends BaseCellAnchorXform {
|
|
8
9
|
constructor() {
|
|
@@ -11,6 +12,7 @@ class OneCellAnchorXform extends BaseCellAnchorXform {
|
|
|
11
12
|
"xdr:from": new CellPositionXform({ tag: "xdr:from" }),
|
|
12
13
|
"xdr:ext": new ExtXform({ tag: "xdr:ext" }),
|
|
13
14
|
"xdr:pic": new PicXform(),
|
|
15
|
+
"xdr:userShape": new ShapeXform(),
|
|
14
16
|
"xdr:graphicFrame": new GraphicFrameXform(),
|
|
15
17
|
"xdr:clientData": new StaticXform({ tag: "xdr:clientData" })
|
|
16
18
|
};
|
|
@@ -36,6 +38,9 @@ class OneCellAnchorXform extends BaseCellAnchorXform {
|
|
|
36
38
|
else if (model.graphicFrame) {
|
|
37
39
|
this.map["xdr:graphicFrame"].render(xmlStream, model.graphicFrame);
|
|
38
40
|
}
|
|
41
|
+
else if (model.shape?.kind === "userShape") {
|
|
42
|
+
this.map["xdr:userShape"].render(xmlStream, model.shape);
|
|
43
|
+
}
|
|
39
44
|
this.map["xdr:clientData"].render(xmlStream, {});
|
|
40
45
|
xmlStream.closeNode();
|
|
41
46
|
}
|
|
@@ -6,6 +6,8 @@ interface PicModel {
|
|
|
6
6
|
alphaModFix?: number;
|
|
7
7
|
/** When true, render the picture as an external linked image (`r:link`). */
|
|
8
8
|
external?: boolean;
|
|
9
|
+
/** Relationship id of an SVG companion (asvg:svgBlip extension). */
|
|
10
|
+
svgRId?: string;
|
|
9
11
|
[key: string]: any;
|
|
10
12
|
}
|
|
11
13
|
declare class PicXform extends BaseXform {
|
|
@@ -25,7 +25,8 @@ class PicXform extends BaseXform {
|
|
|
25
25
|
this.map["xdr:blipFill"].render(xmlStream, {
|
|
26
26
|
rId: model.rId,
|
|
27
27
|
alphaModFix: model.alphaModFix,
|
|
28
|
-
external: model.external
|
|
28
|
+
external: model.external,
|
|
29
|
+
svgRId: model.svgRId
|
|
29
30
|
});
|
|
30
31
|
this.map["xdr:spPr"].render(xmlStream, model);
|
|
31
32
|
xmlStream.closeNode();
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { BaseXform } from "../base-xform.js";
|
|
2
|
+
/** Fill specification for a drawing shape. */
|
|
3
|
+
export interface ShapeFill {
|
|
4
|
+
/** Solid fill colour as a hex RGB string (e.g. "FF0000"). Omit for no fill. */
|
|
5
|
+
color?: string;
|
|
6
|
+
}
|
|
7
|
+
/** Line (outline) specification for a drawing shape. */
|
|
8
|
+
export interface ShapeLine {
|
|
9
|
+
/** Line colour as a hex RGB string (e.g. "000000"). */
|
|
10
|
+
color?: string;
|
|
11
|
+
/** Line width in points. */
|
|
12
|
+
width?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Model for a user-visible drawing shape (`<xdr:sp>`).
|
|
16
|
+
*
|
|
17
|
+
* Distinct from the form-control shape rendered by `SpXform` — this one is
|
|
18
|
+
* visible, carries a configurable preset geometry, solid fill, outline and an
|
|
19
|
+
* optional text label, and is NOT wrapped in an `a14` AlternateContent block.
|
|
20
|
+
*/
|
|
21
|
+
export interface ShapeRenderModel {
|
|
22
|
+
/** Marks this as a user shape so the anchor routes to ShapeXform. */
|
|
23
|
+
kind: "userShape";
|
|
24
|
+
/** Unique drawing id. */
|
|
25
|
+
cNvPrId: number;
|
|
26
|
+
/** Display name (e.g. "Rectangle 1"). */
|
|
27
|
+
name: string;
|
|
28
|
+
/** Preset geometry name (e.g. "rect", "ellipse", "line", "roundRect"). */
|
|
29
|
+
shapeType: string;
|
|
30
|
+
fill?: ShapeFill;
|
|
31
|
+
line?: ShapeLine;
|
|
32
|
+
/** Optional text label centred in the shape. */
|
|
33
|
+
text?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Renders a user-visible drawing shape. Geometry/position is governed by the
|
|
37
|
+
* enclosing anchor (`xfrm` is written as zero, matching how Excel anchors a
|
|
38
|
+
* shape to a cell range), while preset geometry, fill, outline and text are
|
|
39
|
+
* taken from the model. Write-only: shapes are not parsed back on read (the
|
|
40
|
+
* same limitation that already applies to all non-chart drawing content).
|
|
41
|
+
*/
|
|
42
|
+
declare class ShapeXform extends BaseXform {
|
|
43
|
+
model: ShapeRenderModel;
|
|
44
|
+
get tag(): string;
|
|
45
|
+
render(xmlStream: any, model: ShapeRenderModel): void;
|
|
46
|
+
}
|
|
47
|
+
export { ShapeXform };
|
|
@@ -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
|
/**
|