documonster 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/modules/pdf/builder/image-utils.d.ts +1 -1
- package/dist/browser/modules/pdf/builder/image-utils.js +58 -9
- package/dist/browser/modules/pdf/core/pdf-writer.d.ts +8 -0
- package/dist/browser/modules/pdf/core/pdf-writer.js +3 -0
- package/dist/browser/modules/pdf/excel-bridge.js +126 -19
- package/dist/browser/modules/pdf/index.d.ts +1 -1
- package/dist/browser/modules/pdf/render/chart-surface.d.ts +6 -1
- package/dist/browser/modules/pdf/render/chart-surface.js +86 -47
- package/dist/browser/modules/pdf/render/constants.d.ts +69 -0
- package/dist/browser/modules/pdf/render/constants.js +45 -0
- package/dist/browser/modules/pdf/render/layout-engine.d.ts +5 -1
- package/dist/browser/modules/pdf/render/layout-engine.js +586 -205
- package/dist/browser/modules/pdf/render/page-renderer.js +180 -3
- package/dist/browser/modules/pdf/render/pdf-exporter.js +176 -35
- package/dist/browser/modules/pdf/render/style-converter.d.ts +11 -0
- package/dist/browser/modules/pdf/render/style-converter.js +24 -0
- package/dist/browser/modules/pdf/types.d.ts +284 -5
- package/dist/cjs/modules/pdf/builder/image-utils.js +58 -9
- package/dist/cjs/modules/pdf/core/pdf-writer.js +3 -0
- package/dist/cjs/modules/pdf/excel-bridge.js +125 -18
- package/dist/cjs/modules/pdf/render/chart-surface.js +86 -47
- package/dist/cjs/modules/pdf/render/constants.js +46 -1
- package/dist/cjs/modules/pdf/render/layout-engine.js +583 -202
- package/dist/cjs/modules/pdf/render/page-renderer.js +179 -2
- package/dist/cjs/modules/pdf/render/pdf-exporter.js +175 -34
- package/dist/cjs/modules/pdf/render/style-converter.js +26 -0
- package/dist/esm/modules/pdf/builder/image-utils.js +58 -9
- package/dist/esm/modules/pdf/core/pdf-writer.js +3 -0
- package/dist/esm/modules/pdf/excel-bridge.js +126 -19
- package/dist/esm/modules/pdf/render/chart-surface.js +86 -47
- package/dist/esm/modules/pdf/render/constants.js +45 -0
- package/dist/esm/modules/pdf/render/layout-engine.js +586 -205
- package/dist/esm/modules/pdf/render/page-renderer.js +180 -3
- package/dist/esm/modules/pdf/render/pdf-exporter.js +176 -35
- package/dist/esm/modules/pdf/render/style-converter.js +24 -0
- package/dist/iife/documonster.archive.iife.js +1 -1
- package/dist/iife/documonster.archive.iife.min.js +1 -1
- package/dist/iife/documonster.csv.iife.js +1 -1
- package/dist/iife/documonster.csv.iife.min.js +1 -1
- package/dist/iife/documonster.excel.iife.js +1 -1
- package/dist/iife/documonster.excel.iife.min.js +1 -1
- package/dist/iife/documonster.formula.iife.js +1 -1
- package/dist/iife/documonster.formula.iife.min.js +1 -1
- package/dist/iife/documonster.markdown.iife.js +1 -1
- package/dist/iife/documonster.markdown.iife.min.js +1 -1
- package/dist/iife/documonster.pdf.iife.js +1457 -664
- package/dist/iife/documonster.pdf.iife.js.map +1 -1
- package/dist/iife/documonster.pdf.iife.min.js +33 -32
- package/dist/iife/documonster.stream.iife.js +1 -1
- package/dist/iife/documonster.stream.iife.min.js +1 -1
- package/dist/iife/documonster.word.iife.js +1 -1
- package/dist/iife/documonster.word.iife.min.js +1 -1
- package/dist/iife/documonster.xml.iife.js +1 -1
- package/dist/iife/documonster.xml.iife.min.js +1 -1
- package/dist/types/modules/pdf/builder/image-utils.d.ts +1 -1
- package/dist/types/modules/pdf/core/pdf-writer.d.ts +8 -0
- package/dist/types/modules/pdf/index.d.ts +1 -1
- package/dist/types/modules/pdf/render/chart-surface.d.ts +6 -1
- package/dist/types/modules/pdf/render/constants.d.ts +69 -0
- package/dist/types/modules/pdf/render/layout-engine.d.ts +5 -1
- package/dist/types/modules/pdf/render/style-converter.d.ts +11 -0
- package/dist/types/modules/pdf/types.d.ts +284 -5
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* documonster v0.
|
|
2
|
+
* documonster v0.8.0
|
|
3
3
|
* Zero-dependency TypeScript toolkit for Excel, Word, PDF, CSV, Markdown, XML & ZIP — one API across Node.js, Bun & browsers.
|
|
4
4
|
* (c) 2026 cjnoname
|
|
5
5
|
* Released under the Apache-2.0 License
|
|
@@ -1814,34 +1814,60 @@ this.Documonster = this.Documonster || {};
|
|
|
1814
1814
|
* Write an image XObject (JPEG or PNG) to the writer.
|
|
1815
1815
|
* Returns the allocated object number.
|
|
1816
1816
|
*/
|
|
1817
|
-
function writeImageXObject(writer, data, format) {
|
|
1818
|
-
if (format === "png") return writePngImageXObject(writer, data);
|
|
1819
|
-
return writeJpegImageXObject(writer, data);
|
|
1817
|
+
function writeImageXObject(writer, data, format, grayscale = false) {
|
|
1818
|
+
if (format === "png") return writePngImageXObject(writer, data, grayscale);
|
|
1819
|
+
return writeJpegImageXObject(writer, data, grayscale);
|
|
1820
1820
|
}
|
|
1821
1821
|
/**
|
|
1822
|
-
* Write
|
|
1822
|
+
* Write the PostScript calculator function that converts an RGB triple to its
|
|
1823
|
+
* Rec. 601 luma, and return its object number.
|
|
1824
|
+
*
|
|
1825
|
+
* Used as the tint transform of a `/DeviceN` space so a JPEG can be recoloured
|
|
1826
|
+
* without decoding it: the samples stay untouched and the consumer applies the
|
|
1827
|
+
* transform per pixel. Unlike painting a blend on top, this cannot darken
|
|
1828
|
+
* transparent areas, because it *is* the color space rather than an overlay.
|
|
1823
1829
|
*/
|
|
1824
|
-
function
|
|
1830
|
+
function writeLumaTintTransform(writer) {
|
|
1825
1831
|
const objNum = writer.allocObject();
|
|
1832
|
+
const dict = new PdfDict().set("FunctionType", "4").set("Domain", "[0 1 0 1 0 1]").set("Range", "[0 1]");
|
|
1833
|
+
writer.addStreamObject(objNum, dict, new TextEncoder().encode("{ 0.114 mul exch 0.587 mul add exch 0.299 mul add }"));
|
|
1834
|
+
return objNum;
|
|
1835
|
+
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Write a JPEG image using DCTDecode (raw JPEG data embedded directly).
|
|
1838
|
+
*/
|
|
1839
|
+
function writeJpegImageXObject(writer, data, grayscale = false) {
|
|
1826
1840
|
const dims = parseJpegDimensions(data);
|
|
1827
|
-
const
|
|
1841
|
+
const tintRef = grayscale ? writeLumaTintTransform(writer) : void 0;
|
|
1842
|
+
const objNum = writer.allocObject();
|
|
1843
|
+
const dict = new PdfDict().set("Type", "/XObject").set("Subtype", "/Image").set("Width", pdfNumber(dims.width)).set("Height", pdfNumber(dims.height)).set("ColorSpace", tintRef === void 0 ? "/DeviceRGB" : `[/DeviceN [/C1 /C2 /C3] /DeviceGray ${pdfRef(tintRef)}]`).set("BitsPerComponent", "8").set("Filter", "/DCTDecode");
|
|
1828
1844
|
writer.addStreamObject(objNum, dict, data);
|
|
1829
1845
|
return objNum;
|
|
1830
1846
|
}
|
|
1847
|
+
/** Rec. 601 luma of an 8-bit RGB triple. */
|
|
1848
|
+
function lumaByte(r, g, b) {
|
|
1849
|
+
return Math.round(.299 * r + .587 * g + .114 * b);
|
|
1850
|
+
}
|
|
1831
1851
|
/**
|
|
1832
1852
|
* Write a PNG image: decode to raw RGB, create SMask for alpha if needed.
|
|
1833
1853
|
*/
|
|
1834
|
-
function writePngImageXObject(writer, data) {
|
|
1854
|
+
function writePngImageXObject(writer, data, grayscale = false) {
|
|
1835
1855
|
const png = decodePng(data);
|
|
1836
1856
|
const objNum = writer.allocObject();
|
|
1837
|
-
|
|
1857
|
+
let pixels = png.pixels;
|
|
1858
|
+
if (grayscale) {
|
|
1859
|
+
const gray = new Uint8Array(png.width * png.height);
|
|
1860
|
+
for (let i = 0, p = 0; i < gray.length; i++, p += 3) gray[i] = lumaByte(pixels[p], pixels[p + 1], pixels[p + 2]);
|
|
1861
|
+
pixels = gray;
|
|
1862
|
+
}
|
|
1863
|
+
const dict = new PdfDict().set("Type", "/XObject").set("Subtype", "/Image").set("Width", pdfNumber(png.width)).set("Height", pdfNumber(png.height)).set("ColorSpace", grayscale ? "/DeviceGray" : "/DeviceRGB").set("BitsPerComponent", pdfNumber(png.bitsPerComponent));
|
|
1838
1864
|
if (png.alpha) {
|
|
1839
1865
|
const smaskObjNum = writer.allocObject();
|
|
1840
1866
|
const smaskDict = new PdfDict().set("Type", "/XObject").set("Subtype", "/Image").set("Width", pdfNumber(png.width)).set("Height", pdfNumber(png.height)).set("ColorSpace", "/DeviceGray").set("BitsPerComponent", "8");
|
|
1841
1867
|
writer.addStreamObject(smaskObjNum, smaskDict, png.alpha);
|
|
1842
1868
|
dict.set("SMask", pdfRef(smaskObjNum));
|
|
1843
1869
|
}
|
|
1844
|
-
writer.addStreamObject(objNum, dict,
|
|
1870
|
+
writer.addStreamObject(objNum, dict, pixels);
|
|
1845
1871
|
return objNum;
|
|
1846
1872
|
}
|
|
1847
1873
|
var init_image_utils = __esmMin((() => {
|
|
@@ -3821,6 +3847,7 @@ this.Documonster = this.Documonster || {};
|
|
|
3821
3847
|
const mediaBox = `[0 0 ${pdfNumber(options.width)} ${pdfNumber(options.height)}]`;
|
|
3822
3848
|
const contentsValue = typeof options.contentsRef === "string" ? options.contentsRef : pdfRef(options.contentsRef);
|
|
3823
3849
|
const dict = new PdfDict().set("Type", "/Page").set("Parent", pdfRef(options.parentRef)).set("MediaBox", mediaBox).set("Contents", contentsValue).set("Resources", pdfRef(options.resourcesRef));
|
|
3850
|
+
if (options.transparencyGroup) dict.set("Group", "<< /S /Transparency /CS /DeviceRGB /I false /K false >>");
|
|
3824
3851
|
if (options.annotRefs && options.annotRefs.length > 0) dict.set("Annots", "[" + options.annotRefs.map((r) => pdfRef(r)).join(" ") + "]");
|
|
3825
3852
|
this.addObject(objNum, dict);
|
|
3826
3853
|
return objNum;
|
|
@@ -31020,10 +31047,488 @@ this.Documonster = this.Documonster || {};
|
|
|
31020
31047
|
}));
|
|
31021
31048
|
//#endregion
|
|
31022
31049
|
//#region src/modules/pdf/render/constants.ts
|
|
31023
|
-
var LINE_HEIGHT_FACTOR, PX_TO_PT;
|
|
31050
|
+
var LINE_HEIGHT_FACTOR, PX_TO_PT, HEADING_FILL, HEADING_RULE, HEADING_TEXT, FIT_MIN_SCALE, COMMENT_FILL, COMMENT_BORDER, COMMENT_MARKER_COLOR, COLUMN_FIT_EPSILON;
|
|
31024
31051
|
var init_constants = __esmMin((() => {
|
|
31025
31052
|
LINE_HEIGHT_FACTOR = 1.2;
|
|
31026
31053
|
PX_TO_PT = 72 / 96;
|
|
31054
|
+
HEADING_FILL = {
|
|
31055
|
+
r: .94,
|
|
31056
|
+
g: .94,
|
|
31057
|
+
b: .94
|
|
31058
|
+
};
|
|
31059
|
+
HEADING_RULE = {
|
|
31060
|
+
r: .6,
|
|
31061
|
+
g: .6,
|
|
31062
|
+
b: .6
|
|
31063
|
+
};
|
|
31064
|
+
HEADING_TEXT = {
|
|
31065
|
+
r: .2,
|
|
31066
|
+
g: .2,
|
|
31067
|
+
b: .2
|
|
31068
|
+
};
|
|
31069
|
+
FIT_MIN_SCALE = .1;
|
|
31070
|
+
COMMENT_FILL = {
|
|
31071
|
+
r: 1,
|
|
31072
|
+
g: 1,
|
|
31073
|
+
b: .88
|
|
31074
|
+
};
|
|
31075
|
+
COMMENT_BORDER = {
|
|
31076
|
+
r: .4,
|
|
31077
|
+
g: .4,
|
|
31078
|
+
b: .4
|
|
31079
|
+
};
|
|
31080
|
+
COMMENT_MARKER_COLOR = {
|
|
31081
|
+
r: .8,
|
|
31082
|
+
g: 0,
|
|
31083
|
+
b: 0
|
|
31084
|
+
};
|
|
31085
|
+
COLUMN_FIT_EPSILON = .01;
|
|
31086
|
+
}));
|
|
31087
|
+
//#endregion
|
|
31088
|
+
//#region src/utils/theme-colors.ts
|
|
31089
|
+
/**
|
|
31090
|
+
* Parse a hex colour string into 0–1 float RGB components.
|
|
31091
|
+
*
|
|
31092
|
+
* Accepts `RRGGBB` (6 digits), `AARRGGBB` (8 digits, alpha first — the form
|
|
31093
|
+
* Excel uses for cell fills), and `RGB` (3 digits, CSS shorthand). A leading
|
|
31094
|
+
* `#` is tolerated. Returns `null` for anything else.
|
|
31095
|
+
*/
|
|
31096
|
+
function hexToRgb01(hex) {
|
|
31097
|
+
if (!hex) return null;
|
|
31098
|
+
let h = hex.startsWith("#") ? hex.slice(1) : hex;
|
|
31099
|
+
if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
|
|
31100
|
+
let a = 255;
|
|
31101
|
+
let rHex;
|
|
31102
|
+
let gHex;
|
|
31103
|
+
let bHex;
|
|
31104
|
+
if (h.length === 8) {
|
|
31105
|
+
a = parseInt(h.slice(0, 2), 16);
|
|
31106
|
+
rHex = h.slice(2, 4);
|
|
31107
|
+
gHex = h.slice(4, 6);
|
|
31108
|
+
bHex = h.slice(6, 8);
|
|
31109
|
+
} else if (h.length === 6) {
|
|
31110
|
+
rHex = h.slice(0, 2);
|
|
31111
|
+
gHex = h.slice(2, 4);
|
|
31112
|
+
bHex = h.slice(4, 6);
|
|
31113
|
+
} else return null;
|
|
31114
|
+
const r = parseInt(rHex, 16);
|
|
31115
|
+
const g = parseInt(gHex, 16);
|
|
31116
|
+
const b = parseInt(bHex, 16);
|
|
31117
|
+
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b) || Number.isNaN(a)) return null;
|
|
31118
|
+
const alpha = a / 255;
|
|
31119
|
+
return alpha < 1 ? {
|
|
31120
|
+
r: r / 255,
|
|
31121
|
+
g: g / 255,
|
|
31122
|
+
b: b / 255,
|
|
31123
|
+
a: alpha
|
|
31124
|
+
} : {
|
|
31125
|
+
r: r / 255,
|
|
31126
|
+
g: g / 255,
|
|
31127
|
+
b: b / 255
|
|
31128
|
+
};
|
|
31129
|
+
}
|
|
31130
|
+
/**
|
|
31131
|
+
* Apply a tint to a 0–1 float RGB colour, per OOXML §18.8.19.
|
|
31132
|
+
*
|
|
31133
|
+
* Tint ∈ [-1, 1]: positive lightens toward white
|
|
31134
|
+
* (`c + (1 - c) * tint`), negative darkens toward black
|
|
31135
|
+
* (`c * (1 + tint)`). `tint = 0` returns the colour unchanged. Result
|
|
31136
|
+
* components are clamped to [0, 1]; alpha is preserved.
|
|
31137
|
+
*/
|
|
31138
|
+
function applyTintRgb01(color, tint) {
|
|
31139
|
+
const apply = (c) => {
|
|
31140
|
+
const v = tint < 0 ? c * (1 + tint) : c + (1 - c) * tint;
|
|
31141
|
+
return Math.max(0, Math.min(1, v));
|
|
31142
|
+
};
|
|
31143
|
+
const out = {
|
|
31144
|
+
r: apply(color.r),
|
|
31145
|
+
g: apply(color.g),
|
|
31146
|
+
b: apply(color.b)
|
|
31147
|
+
};
|
|
31148
|
+
if (color.a !== void 0) out.a = color.a;
|
|
31149
|
+
return out;
|
|
31150
|
+
}
|
|
31151
|
+
var DEFAULT_OFFICE_THEME, CHART_THEME_PALETTE, CELL_THEME_PALETTE;
|
|
31152
|
+
var init_theme_colors = __esmMin((() => {
|
|
31153
|
+
DEFAULT_OFFICE_THEME = Object.freeze({
|
|
31154
|
+
dk1: "000000",
|
|
31155
|
+
lt1: "FFFFFF",
|
|
31156
|
+
dk2: "44546A",
|
|
31157
|
+
lt2: "E7E6E6",
|
|
31158
|
+
accent1: "4472C4",
|
|
31159
|
+
accent2: "ED7D31",
|
|
31160
|
+
accent3: "A5A5A5",
|
|
31161
|
+
accent4: "FFC000",
|
|
31162
|
+
accent5: "5B9BD5",
|
|
31163
|
+
accent6: "70AD47",
|
|
31164
|
+
hlink: "0563C1",
|
|
31165
|
+
folHlink: "954F72"
|
|
31166
|
+
});
|
|
31167
|
+
CHART_THEME_PALETTE = Object.freeze([
|
|
31168
|
+
DEFAULT_OFFICE_THEME.dk1,
|
|
31169
|
+
DEFAULT_OFFICE_THEME.lt1,
|
|
31170
|
+
DEFAULT_OFFICE_THEME.dk2,
|
|
31171
|
+
DEFAULT_OFFICE_THEME.lt2,
|
|
31172
|
+
DEFAULT_OFFICE_THEME.accent1,
|
|
31173
|
+
DEFAULT_OFFICE_THEME.accent2,
|
|
31174
|
+
DEFAULT_OFFICE_THEME.accent3,
|
|
31175
|
+
DEFAULT_OFFICE_THEME.accent4,
|
|
31176
|
+
DEFAULT_OFFICE_THEME.accent5,
|
|
31177
|
+
DEFAULT_OFFICE_THEME.accent6,
|
|
31178
|
+
DEFAULT_OFFICE_THEME.hlink,
|
|
31179
|
+
DEFAULT_OFFICE_THEME.folHlink
|
|
31180
|
+
]);
|
|
31181
|
+
CELL_THEME_PALETTE = Object.freeze([
|
|
31182
|
+
DEFAULT_OFFICE_THEME.lt1,
|
|
31183
|
+
DEFAULT_OFFICE_THEME.dk1,
|
|
31184
|
+
DEFAULT_OFFICE_THEME.lt2,
|
|
31185
|
+
DEFAULT_OFFICE_THEME.dk2,
|
|
31186
|
+
DEFAULT_OFFICE_THEME.accent1,
|
|
31187
|
+
DEFAULT_OFFICE_THEME.accent2,
|
|
31188
|
+
DEFAULT_OFFICE_THEME.accent3,
|
|
31189
|
+
DEFAULT_OFFICE_THEME.accent4,
|
|
31190
|
+
DEFAULT_OFFICE_THEME.accent5,
|
|
31191
|
+
DEFAULT_OFFICE_THEME.accent6,
|
|
31192
|
+
DEFAULT_OFFICE_THEME.hlink,
|
|
31193
|
+
DEFAULT_OFFICE_THEME.folHlink
|
|
31194
|
+
]);
|
|
31195
|
+
}));
|
|
31196
|
+
//#endregion
|
|
31197
|
+
//#region src/modules/pdf/render/style-converter.ts
|
|
31198
|
+
/**
|
|
31199
|
+
* Convert an ARGB color string to PDF RGB color.
|
|
31200
|
+
* Handles "AARRGGBB" (8-char) and "RRGGBB" (6-char) formats.
|
|
31201
|
+
* PDF uses 0-1 floats for each component.
|
|
31202
|
+
*/
|
|
31203
|
+
function argbToPdfColor(argb) {
|
|
31204
|
+
if (!argb || argb.length < 6) return null;
|
|
31205
|
+
return hexToRgb01(argb);
|
|
31206
|
+
}
|
|
31207
|
+
/**
|
|
31208
|
+
* Convert a color data object to PDF color.
|
|
31209
|
+
* Handles ARGB, theme-based, and indexed colors.
|
|
31210
|
+
*/
|
|
31211
|
+
function excelColorToPdf(color) {
|
|
31212
|
+
if (!color) return null;
|
|
31213
|
+
if (color.argb) return argbToPdfColor(color.argb);
|
|
31214
|
+
if (color.theme !== void 0) {
|
|
31215
|
+
const base = themeColorToPdf(color.theme);
|
|
31216
|
+
if (!base) return null;
|
|
31217
|
+
const tint = color.tint;
|
|
31218
|
+
if (tint !== void 0 && tint !== 0) return applyTint(base, tint);
|
|
31219
|
+
return base;
|
|
31220
|
+
}
|
|
31221
|
+
if (color.indexed !== void 0) return indexedColorToPdf(color.indexed);
|
|
31222
|
+
return null;
|
|
31223
|
+
}
|
|
31224
|
+
/**
|
|
31225
|
+
* Map a cell-colour theme index to a PDF colour using the default Office
|
|
31226
|
+
* palette in SpreadsheetML cell order (0=lt1, 1=dk1, 2=lt2, 3=dk2, …).
|
|
31227
|
+
*/
|
|
31228
|
+
function themeColorToPdf(themeIndex) {
|
|
31229
|
+
if (themeIndex >= 0 && themeIndex < CELL_THEME_PALETTE.length) return hexToRgb01(CELL_THEME_PALETTE[themeIndex]);
|
|
31230
|
+
return null;
|
|
31231
|
+
}
|
|
31232
|
+
/**
|
|
31233
|
+
* Convert an indexed color to PDF color.
|
|
31234
|
+
* Index 64 = system foreground (black), 65 = system background (white).
|
|
31235
|
+
*/
|
|
31236
|
+
function indexedColorToPdf(index) {
|
|
31237
|
+
if (index === 64) return {
|
|
31238
|
+
r: 0,
|
|
31239
|
+
g: 0,
|
|
31240
|
+
b: 0
|
|
31241
|
+
};
|
|
31242
|
+
if (index === 65) return {
|
|
31243
|
+
r: 1,
|
|
31244
|
+
g: 1,
|
|
31245
|
+
b: 1
|
|
31246
|
+
};
|
|
31247
|
+
if (index >= 0 && index < INDEXED_COLORS.length) return argbToPdfColor(INDEXED_COLORS[index]) ?? null;
|
|
31248
|
+
return null;
|
|
31249
|
+
}
|
|
31250
|
+
/**
|
|
31251
|
+
* Apply a tint value to a color.
|
|
31252
|
+
* Tint range: -1.0 (fully dark) to +1.0 (fully light).
|
|
31253
|
+
* Negative tint darkens, positive tint lightens.
|
|
31254
|
+
*
|
|
31255
|
+
* @see OOXML §18.8.19 - tint formula
|
|
31256
|
+
*/
|
|
31257
|
+
function applyTint(color, tint) {
|
|
31258
|
+
return applyTintRgb01(color, tint);
|
|
31259
|
+
}
|
|
31260
|
+
/**
|
|
31261
|
+
* Convert a color to its luminance-preserving grayscale equivalent, using the
|
|
31262
|
+
* Rec. 601 luma weights. Backs Excel's "Black and white" print option: hues are
|
|
31263
|
+
* discarded while relative lightness — and therefore contrast — is kept.
|
|
31264
|
+
*
|
|
31265
|
+
* Opacity is carried through untouched; dropping it would make transparent
|
|
31266
|
+
* chart fills opaque.
|
|
31267
|
+
*/
|
|
31268
|
+
function toGrayscale(color) {
|
|
31269
|
+
const luma = .299 * color.r + .587 * color.g + .114 * color.b;
|
|
31270
|
+
return color.a === void 0 ? {
|
|
31271
|
+
r: luma,
|
|
31272
|
+
g: luma,
|
|
31273
|
+
b: luma
|
|
31274
|
+
} : {
|
|
31275
|
+
r: luma,
|
|
31276
|
+
g: luma,
|
|
31277
|
+
b: luma,
|
|
31278
|
+
a: color.a
|
|
31279
|
+
};
|
|
31280
|
+
}
|
|
31281
|
+
/** Grayscale every edge of a border set, preserving `null` edges. */
|
|
31282
|
+
function grayscaleBorders(borders) {
|
|
31283
|
+
const convert = (b) => b === null ? null : {
|
|
31284
|
+
...b,
|
|
31285
|
+
color: toGrayscale(b.color)
|
|
31286
|
+
};
|
|
31287
|
+
return {
|
|
31288
|
+
top: convert(borders.top),
|
|
31289
|
+
right: convert(borders.right),
|
|
31290
|
+
bottom: convert(borders.bottom),
|
|
31291
|
+
left: convert(borders.left)
|
|
31292
|
+
};
|
|
31293
|
+
}
|
|
31294
|
+
/**
|
|
31295
|
+
* Extract font properties for PDF rendering.
|
|
31296
|
+
*/
|
|
31297
|
+
function extractFontProperties(font, defaultFamily, defaultSize) {
|
|
31298
|
+
return {
|
|
31299
|
+
fontFamily: font?.name ?? defaultFamily,
|
|
31300
|
+
fontSize: font?.size ?? defaultSize,
|
|
31301
|
+
bold: font?.bold ?? false,
|
|
31302
|
+
italic: font?.italic ?? false,
|
|
31303
|
+
strike: font?.strike ?? false,
|
|
31304
|
+
underline: !!(font?.underline && font.underline !== "none"),
|
|
31305
|
+
textColor: excelColorToPdf(font?.color) ?? DEFAULT_COLORS.black
|
|
31306
|
+
};
|
|
31307
|
+
}
|
|
31308
|
+
/**
|
|
31309
|
+
* Convert a fill to a PDF background color.
|
|
31310
|
+
* Only pattern fills with "solid" pattern are supported as PDF backgrounds.
|
|
31311
|
+
* Other patterns are approximated or ignored.
|
|
31312
|
+
*/
|
|
31313
|
+
function excelFillToPdfColor(fill) {
|
|
31314
|
+
if (!fill) return null;
|
|
31315
|
+
if (fill.type === "pattern") {
|
|
31316
|
+
if (fill.pattern === "solid" && fill.fgColor) return excelColorToPdf(fill.fgColor);
|
|
31317
|
+
if (fill.pattern === "none") return null;
|
|
31318
|
+
if (fill.fgColor) return excelColorToPdf(fill.fgColor);
|
|
31319
|
+
}
|
|
31320
|
+
if (fill.type === "gradient") {
|
|
31321
|
+
if (fill.stops && fill.stops.length > 0) return excelColorToPdf(fill.stops[0].color);
|
|
31322
|
+
}
|
|
31323
|
+
return null;
|
|
31324
|
+
}
|
|
31325
|
+
/**
|
|
31326
|
+
* Map border styles to PDF line widths (in points).
|
|
31327
|
+
*
|
|
31328
|
+
* Values match Excel's actual border weights as used historically by this
|
|
31329
|
+
* library. An earlier change doubled every width (0.25 → 0.5, 0.5 → 1,
|
|
31330
|
+
* 1 → 2) to make `thin` and `medium` more visually distinct in PDF
|
|
31331
|
+
* viewers, but that change made all borders heavier than Excel itself.
|
|
31332
|
+
* The 2× ratio between thin/medium and the 4× ratio between
|
|
31333
|
+
* thin/thick are preserved with the lighter values, so styles remain
|
|
31334
|
+
* distinguishable while matching Excel.
|
|
31335
|
+
*
|
|
31336
|
+
* hair = 0.1 pt
|
|
31337
|
+
* thin = 0.25 pt (also dotted, dashed, dashDot, dashDotDot, slantDashDot, double)
|
|
31338
|
+
* medium = 0.5 pt (also mediumDashed, mediumDashDot, mediumDashDotDot)
|
|
31339
|
+
* thick = 1 pt
|
|
31340
|
+
*/
|
|
31341
|
+
function borderStyleToLineWidth(style) {
|
|
31342
|
+
switch (style) {
|
|
31343
|
+
case "hair": return .1;
|
|
31344
|
+
case "thin": return .25;
|
|
31345
|
+
case "medium": return .5;
|
|
31346
|
+
case "thick": return 1;
|
|
31347
|
+
case "double": return .25;
|
|
31348
|
+
case "dotted": return .25;
|
|
31349
|
+
case "dashed": return .25;
|
|
31350
|
+
case "dashDot": return .25;
|
|
31351
|
+
case "dashDotDot": return .25;
|
|
31352
|
+
case "slantDashDot": return .25;
|
|
31353
|
+
case "mediumDashed": return .5;
|
|
31354
|
+
case "mediumDashDot": return .5;
|
|
31355
|
+
case "mediumDashDotDot": return .5;
|
|
31356
|
+
default: return .25;
|
|
31357
|
+
}
|
|
31358
|
+
}
|
|
31359
|
+
/**
|
|
31360
|
+
* Map border styles to PDF dash patterns.
|
|
31361
|
+
* An empty array means a solid line.
|
|
31362
|
+
*/
|
|
31363
|
+
function borderStyleToDashPattern(style) {
|
|
31364
|
+
switch (style) {
|
|
31365
|
+
case "dotted": return [1, 1];
|
|
31366
|
+
case "dashed":
|
|
31367
|
+
case "mediumDashed": return [3, 2];
|
|
31368
|
+
case "dashDot":
|
|
31369
|
+
case "mediumDashDot": return [
|
|
31370
|
+
3,
|
|
31371
|
+
1,
|
|
31372
|
+
1,
|
|
31373
|
+
1
|
|
31374
|
+
];
|
|
31375
|
+
case "dashDotDot":
|
|
31376
|
+
case "mediumDashDotDot": return [
|
|
31377
|
+
3,
|
|
31378
|
+
1,
|
|
31379
|
+
1,
|
|
31380
|
+
1,
|
|
31381
|
+
1,
|
|
31382
|
+
1
|
|
31383
|
+
];
|
|
31384
|
+
case "slantDashDot": return [
|
|
31385
|
+
4,
|
|
31386
|
+
2,
|
|
31387
|
+
1,
|
|
31388
|
+
2
|
|
31389
|
+
];
|
|
31390
|
+
case "hair": return [.5, .5];
|
|
31391
|
+
default: return [];
|
|
31392
|
+
}
|
|
31393
|
+
}
|
|
31394
|
+
/**
|
|
31395
|
+
* Convert a single border side to a PDF LayoutBorder.
|
|
31396
|
+
*/
|
|
31397
|
+
function convertBorder(border) {
|
|
31398
|
+
if (!border || !border.style) return null;
|
|
31399
|
+
return {
|
|
31400
|
+
width: borderStyleToLineWidth(border.style),
|
|
31401
|
+
color: excelColorToPdf(border.color) ?? DEFAULT_COLORS.black,
|
|
31402
|
+
dashPattern: borderStyleToDashPattern(border.style),
|
|
31403
|
+
isDouble: border.style === "double"
|
|
31404
|
+
};
|
|
31405
|
+
}
|
|
31406
|
+
/**
|
|
31407
|
+
* Convert border data to PDF LayoutBorders.
|
|
31408
|
+
*/
|
|
31409
|
+
function excelBordersToPdf(borders) {
|
|
31410
|
+
if (!borders) return {
|
|
31411
|
+
top: null,
|
|
31412
|
+
right: null,
|
|
31413
|
+
bottom: null,
|
|
31414
|
+
left: null
|
|
31415
|
+
};
|
|
31416
|
+
return {
|
|
31417
|
+
top: convertBorder(borders.top),
|
|
31418
|
+
right: convertBorder(borders.right),
|
|
31419
|
+
bottom: convertBorder(borders.bottom),
|
|
31420
|
+
left: convertBorder(borders.left)
|
|
31421
|
+
};
|
|
31422
|
+
}
|
|
31423
|
+
/**
|
|
31424
|
+
* Convert horizontal alignment to PDF alignment.
|
|
31425
|
+
*/
|
|
31426
|
+
function excelHAlignToPdf(alignment) {
|
|
31427
|
+
if (!alignment?.horizontal) return "left";
|
|
31428
|
+
switch (alignment.horizontal) {
|
|
31429
|
+
case "center":
|
|
31430
|
+
case "centerContinuous": return "center";
|
|
31431
|
+
case "right": return "right";
|
|
31432
|
+
default: return "left";
|
|
31433
|
+
}
|
|
31434
|
+
}
|
|
31435
|
+
/**
|
|
31436
|
+
* Convert vertical alignment to PDF alignment.
|
|
31437
|
+
*/
|
|
31438
|
+
function excelVAlignToPdf(alignment) {
|
|
31439
|
+
if (!alignment?.vertical) return "bottom";
|
|
31440
|
+
switch (alignment.vertical) {
|
|
31441
|
+
case "top": return "top";
|
|
31442
|
+
case "middle": return "middle";
|
|
31443
|
+
default: return "bottom";
|
|
31444
|
+
}
|
|
31445
|
+
}
|
|
31446
|
+
var INDEXED_COLORS, DEFAULT_COLORS;
|
|
31447
|
+
var init_style_converter = __esmMin((() => {
|
|
31448
|
+
init_theme_colors();
|
|
31449
|
+
INDEXED_COLORS = [
|
|
31450
|
+
"000000",
|
|
31451
|
+
"FFFFFF",
|
|
31452
|
+
"FF0000",
|
|
31453
|
+
"00FF00",
|
|
31454
|
+
"0000FF",
|
|
31455
|
+
"FFFF00",
|
|
31456
|
+
"FF00FF",
|
|
31457
|
+
"00FFFF",
|
|
31458
|
+
"000000",
|
|
31459
|
+
"FFFFFF",
|
|
31460
|
+
"FF0000",
|
|
31461
|
+
"00FF00",
|
|
31462
|
+
"0000FF",
|
|
31463
|
+
"FFFF00",
|
|
31464
|
+
"FF00FF",
|
|
31465
|
+
"00FFFF",
|
|
31466
|
+
"800000",
|
|
31467
|
+
"008000",
|
|
31468
|
+
"000080",
|
|
31469
|
+
"808000",
|
|
31470
|
+
"800080",
|
|
31471
|
+
"008080",
|
|
31472
|
+
"C0C0C0",
|
|
31473
|
+
"808080",
|
|
31474
|
+
"9999FF",
|
|
31475
|
+
"993366",
|
|
31476
|
+
"FFFFCC",
|
|
31477
|
+
"CCFFFF",
|
|
31478
|
+
"660066",
|
|
31479
|
+
"FF8080",
|
|
31480
|
+
"0066CC",
|
|
31481
|
+
"CCCCFF",
|
|
31482
|
+
"000080",
|
|
31483
|
+
"FF00FF",
|
|
31484
|
+
"FFFF00",
|
|
31485
|
+
"00FFFF",
|
|
31486
|
+
"800080",
|
|
31487
|
+
"800000",
|
|
31488
|
+
"008080",
|
|
31489
|
+
"0000FF",
|
|
31490
|
+
"00CCFF",
|
|
31491
|
+
"CCFFFF",
|
|
31492
|
+
"CCFFCC",
|
|
31493
|
+
"FFFF99",
|
|
31494
|
+
"99CCFF",
|
|
31495
|
+
"FF99CC",
|
|
31496
|
+
"CC99FF",
|
|
31497
|
+
"FFCC99",
|
|
31498
|
+
"3366FF",
|
|
31499
|
+
"33CCCC",
|
|
31500
|
+
"99CC00",
|
|
31501
|
+
"FFCC00",
|
|
31502
|
+
"FF9900",
|
|
31503
|
+
"FF6600",
|
|
31504
|
+
"666699",
|
|
31505
|
+
"969696",
|
|
31506
|
+
"003366",
|
|
31507
|
+
"339966",
|
|
31508
|
+
"003300",
|
|
31509
|
+
"333300",
|
|
31510
|
+
"993300",
|
|
31511
|
+
"993366",
|
|
31512
|
+
"333399",
|
|
31513
|
+
"333333"
|
|
31514
|
+
];
|
|
31515
|
+
DEFAULT_COLORS = {
|
|
31516
|
+
black: {
|
|
31517
|
+
r: 0,
|
|
31518
|
+
g: 0,
|
|
31519
|
+
b: 0
|
|
31520
|
+
},
|
|
31521
|
+
white: {
|
|
31522
|
+
r: 1,
|
|
31523
|
+
g: 1,
|
|
31524
|
+
b: 1
|
|
31525
|
+
},
|
|
31526
|
+
gridLine: {
|
|
31527
|
+
r: .816,
|
|
31528
|
+
g: .816,
|
|
31529
|
+
b: .816
|
|
31530
|
+
}
|
|
31531
|
+
};
|
|
31027
31532
|
}));
|
|
31028
31533
|
//#endregion
|
|
31029
31534
|
//#region src/modules/pdf/render/page-renderer.ts
|
|
@@ -31086,6 +31591,8 @@ this.Documonster = this.Documonster || {};
|
|
|
31086
31591
|
for (const cell of page.cells) drawCellBorders(stream, cell);
|
|
31087
31592
|
const sf = page.scaleFactor;
|
|
31088
31593
|
for (const cell of page.cells) if (cell.text || cell.richText) drawCellText(stream, cell, fontManager, alphaValues, sf);
|
|
31594
|
+
if (options.showRowColHeaders) drawRowColHeadings(stream, page, options, fontManager);
|
|
31595
|
+
if (page.commentBoxes?.length) drawCommentBoxes(stream, page, options, fontManager);
|
|
31089
31596
|
const excelHeader = selectHeaderFooter(page, "header");
|
|
31090
31597
|
const excelFooter = selectHeaderFooter(page, "footer");
|
|
31091
31598
|
if (excelHeader !== void 0) {
|
|
@@ -31987,11 +32494,13 @@ this.Documonster = this.Documonster || {};
|
|
|
31987
32494
|
for (const item of resolved) {
|
|
31988
32495
|
const { run, text, resourceName, fontSize, width } = item;
|
|
31989
32496
|
const shift = run.superscript ? fontSize * HEADER_FOOTER_METRICS.SUPERSCRIPT_RISE : run.subscript ? -fontSize * HEADER_FOOTER_METRICS.SUBSCRIPT_DROP : 0;
|
|
31990
|
-
const
|
|
32497
|
+
const bw = page.options.blackAndWhite;
|
|
32498
|
+
const rawColor = run.color ?? {
|
|
31991
32499
|
r: 0,
|
|
31992
32500
|
g: 0,
|
|
31993
32501
|
b: 0
|
|
31994
32502
|
};
|
|
32503
|
+
const color = bw ? toGrayscale(rawColor) : rawColor;
|
|
31995
32504
|
stream.save();
|
|
31996
32505
|
stream.setFillColor(color);
|
|
31997
32506
|
stream.setStrokeColor(color);
|
|
@@ -32055,6 +32564,169 @@ this.Documonster = this.Documonster || {};
|
|
|
32055
32564
|
};
|
|
32056
32565
|
});
|
|
32057
32566
|
}
|
|
32567
|
+
/** Convert a 1-based column number to its Excel letters (1 → "A", 27 → "AA"). */
|
|
32568
|
+
function columnNumberToLetters(col) {
|
|
32569
|
+
let n = col;
|
|
32570
|
+
let out = "";
|
|
32571
|
+
while (n > 0) {
|
|
32572
|
+
const rem = (n - 1) % 26;
|
|
32573
|
+
out = String.fromCharCode(65 + rem) + out;
|
|
32574
|
+
n = Math.floor((n - 1) / 26);
|
|
32575
|
+
}
|
|
32576
|
+
return out;
|
|
32577
|
+
}
|
|
32578
|
+
/**
|
|
32579
|
+
* Draw Excel's printed row numbers and column letters around the grid.
|
|
32580
|
+
*
|
|
32581
|
+
* The bands occupy space reserved by the layout engine (see
|
|
32582
|
+
* `computeHeadingMetrics`), so the grid origin already accounts for them and
|
|
32583
|
+
* nothing here can push content off the page.
|
|
32584
|
+
*/
|
|
32585
|
+
function drawRowColHeadings(stream, page, options, fontManager) {
|
|
32586
|
+
const headings = page.headings;
|
|
32587
|
+
if (!headings || page.columnWidths.length === 0 || page.rowHeights.length === 0) return;
|
|
32588
|
+
const { gutterWidth, bandHeight, fontSize } = headings;
|
|
32589
|
+
const resourceName = fontManager.hasEmbeddedFont() ? fontManager.getEmbeddedResourceName() : fontManager.ensureFont(resolvePdfFontName(options.defaultFontFamily, false, false));
|
|
32590
|
+
const fill = HEADING_FILL;
|
|
32591
|
+
const rule = HEADING_RULE;
|
|
32592
|
+
const textColor = HEADING_TEXT;
|
|
32593
|
+
const gridLeft = page.columnOffsets[0];
|
|
32594
|
+
const lastCol = page.columnWidths.length - 1;
|
|
32595
|
+
const gridRight = page.columnOffsets[lastCol] + page.columnWidths[lastCol];
|
|
32596
|
+
const gridTop = page.rowYPositions[0];
|
|
32597
|
+
const lastRow = page.rowHeights.length - 1;
|
|
32598
|
+
const gridBottom = page.rowYPositions[lastRow] - page.rowHeights[lastRow];
|
|
32599
|
+
stream.save();
|
|
32600
|
+
stream.fillRect(gridLeft, gridTop, gridRight - gridLeft, bandHeight, fill);
|
|
32601
|
+
stream.fillRect(gridLeft - gutterWidth, gridBottom, gutterWidth, gridTop - gridBottom, fill);
|
|
32602
|
+
stream.fillRect(gridLeft - gutterWidth, gridTop, gutterWidth, bandHeight, fill);
|
|
32603
|
+
stream.setStrokeColor(rule);
|
|
32604
|
+
stream.setLineWidth(.25);
|
|
32605
|
+
for (let i = 0; i <= page.columnWidths.length; i++) {
|
|
32606
|
+
const x = i < page.columnWidths.length ? page.columnOffsets[i] : page.columnOffsets[i - 1] + page.columnWidths[i - 1];
|
|
32607
|
+
stream.moveTo(x, gridTop);
|
|
32608
|
+
stream.lineTo(x, gridTop + bandHeight);
|
|
32609
|
+
}
|
|
32610
|
+
for (let i = 0; i <= page.rowYPositions.length; i++) {
|
|
32611
|
+
const y = i < page.rowYPositions.length ? page.rowYPositions[i] : page.rowYPositions[i - 1] - page.rowHeights[i - 1];
|
|
32612
|
+
stream.moveTo(gridLeft - gutterWidth, y);
|
|
32613
|
+
stream.lineTo(gridLeft, y);
|
|
32614
|
+
}
|
|
32615
|
+
stream.moveTo(gridLeft - gutterWidth, gridTop + bandHeight);
|
|
32616
|
+
stream.lineTo(gridRight, gridTop + bandHeight);
|
|
32617
|
+
stream.moveTo(gridLeft - gutterWidth, gridBottom);
|
|
32618
|
+
stream.lineTo(gridLeft - gutterWidth, gridTop + bandHeight);
|
|
32619
|
+
stream.stroke();
|
|
32620
|
+
stream.setFillColor(textColor);
|
|
32621
|
+
const baselineOffset = (bandHeight - fontSize) / 2 + fontSize * .2;
|
|
32622
|
+
for (let i = 0; i < page.sheetCols.length; i++) {
|
|
32623
|
+
const label = columnNumberToLetters(page.sheetCols[i]);
|
|
32624
|
+
fontManager.trackText(label);
|
|
32625
|
+
const w = fontManager.measureText(label, resourceName, fontSize);
|
|
32626
|
+
emitTextWithMatrix(stream, fontManager, {
|
|
32627
|
+
text: label,
|
|
32628
|
+
matrix: [
|
|
32629
|
+
1,
|
|
32630
|
+
0,
|
|
32631
|
+
0,
|
|
32632
|
+
1,
|
|
32633
|
+
page.columnOffsets[i] + (page.columnWidths[i] - w) / 2,
|
|
32634
|
+
gridTop + baselineOffset
|
|
32635
|
+
],
|
|
32636
|
+
resourceName,
|
|
32637
|
+
fontSize
|
|
32638
|
+
});
|
|
32639
|
+
}
|
|
32640
|
+
for (let i = 0; i < page.sheetRows.length; i++) {
|
|
32641
|
+
const label = String(page.sheetRows[i]);
|
|
32642
|
+
fontManager.trackText(label);
|
|
32643
|
+
const w = fontManager.measureText(label, resourceName, fontSize);
|
|
32644
|
+
const rowH = page.rowHeights[i];
|
|
32645
|
+
emitTextWithMatrix(stream, fontManager, {
|
|
32646
|
+
text: label,
|
|
32647
|
+
matrix: [
|
|
32648
|
+
1,
|
|
32649
|
+
0,
|
|
32650
|
+
0,
|
|
32651
|
+
1,
|
|
32652
|
+
gridLeft - gutterWidth + (gutterWidth - w) / 2,
|
|
32653
|
+
page.rowYPositions[i] - rowH + (rowH - fontSize) / 2 + fontSize * .2
|
|
32654
|
+
],
|
|
32655
|
+
resourceName,
|
|
32656
|
+
fontSize
|
|
32657
|
+
});
|
|
32658
|
+
}
|
|
32659
|
+
stream.restore();
|
|
32660
|
+
}
|
|
32661
|
+
/**
|
|
32662
|
+
* Draw Excel's on-sheet comment boxes and their cell corner markers.
|
|
32663
|
+
*
|
|
32664
|
+
* Positioned by the layout engine, so this only has to paint: a pale note-yellow
|
|
32665
|
+
* box with a thin border, the wrapped body inside it, and the small red triangle
|
|
32666
|
+
* Excel puts in the commented cell's top-right corner.
|
|
32667
|
+
*/
|
|
32668
|
+
function drawCommentBoxes(stream, page, options, fontManager) {
|
|
32669
|
+
const boxes = page.commentBoxes;
|
|
32670
|
+
if (!boxes?.length) return;
|
|
32671
|
+
const bw = options.blackAndWhite;
|
|
32672
|
+
const fill = bw ? toGrayscale(COMMENT_FILL) : COMMENT_FILL;
|
|
32673
|
+
const border = bw ? toGrayscale(COMMENT_BORDER) : COMMENT_BORDER;
|
|
32674
|
+
const markerColor = bw ? toGrayscale(COMMENT_MARKER_COLOR) : COMMENT_MARKER_COLOR;
|
|
32675
|
+
const resourceName = fontManager.hasEmbeddedFont() ? fontManager.getEmbeddedResourceName() : fontManager.ensureFont(resolvePdfFontName(options.defaultFontFamily, false, false));
|
|
32676
|
+
for (const box of boxes) {
|
|
32677
|
+
const { rect } = box;
|
|
32678
|
+
stream.save();
|
|
32679
|
+
stream.fillRect(rect.x, rect.y, rect.width, rect.height, fill);
|
|
32680
|
+
stream.setStrokeColor(border);
|
|
32681
|
+
stream.setLineWidth(.5);
|
|
32682
|
+
stream.rect(rect.x, rect.y, rect.width, rect.height);
|
|
32683
|
+
stream.stroke();
|
|
32684
|
+
const lineHeight = box.fontSize * LINE_HEIGHT_FACTOR;
|
|
32685
|
+
const innerWidth = Math.max(rect.width - 6, 1);
|
|
32686
|
+
const measure = (t) => fontManager.measureText(t, resourceName, box.fontSize);
|
|
32687
|
+
const lines = box.text.split("\n").flatMap((part) => wrapTextLines(part, measure, innerWidth));
|
|
32688
|
+
stream.setFillColor(bw ? toGrayscale({
|
|
32689
|
+
r: 0,
|
|
32690
|
+
g: 0,
|
|
32691
|
+
b: 0
|
|
32692
|
+
}) : {
|
|
32693
|
+
r: 0,
|
|
32694
|
+
g: 0,
|
|
32695
|
+
b: 0
|
|
32696
|
+
});
|
|
32697
|
+
let baseline = rect.y + rect.height - 2 - box.fontSize;
|
|
32698
|
+
for (const line of lines) {
|
|
32699
|
+
if (baseline < rect.y + 2) break;
|
|
32700
|
+
fontManager.trackText(line);
|
|
32701
|
+
emitTextWithMatrix(stream, fontManager, {
|
|
32702
|
+
text: line,
|
|
32703
|
+
matrix: [
|
|
32704
|
+
1,
|
|
32705
|
+
0,
|
|
32706
|
+
0,
|
|
32707
|
+
1,
|
|
32708
|
+
rect.x + 3,
|
|
32709
|
+
baseline
|
|
32710
|
+
],
|
|
32711
|
+
resourceName,
|
|
32712
|
+
fontSize: box.fontSize
|
|
32713
|
+
});
|
|
32714
|
+
baseline -= lineHeight;
|
|
32715
|
+
}
|
|
32716
|
+
stream.restore();
|
|
32717
|
+
if (box.marker) {
|
|
32718
|
+
const { x, y, size } = box.marker;
|
|
32719
|
+
stream.save();
|
|
32720
|
+
stream.setFillColor(markerColor);
|
|
32721
|
+
stream.moveTo(x - size, y);
|
|
32722
|
+
stream.lineTo(x, y);
|
|
32723
|
+
stream.lineTo(x, y - size);
|
|
32724
|
+
stream.closePath();
|
|
32725
|
+
stream.fill();
|
|
32726
|
+
stream.restore();
|
|
32727
|
+
}
|
|
32728
|
+
}
|
|
32729
|
+
}
|
|
32058
32730
|
function drawPageHeader(stream, page, options, fontManager) {
|
|
32059
32731
|
const headerFontSize = 10;
|
|
32060
32732
|
const headerText = page.sheetName;
|
|
@@ -32143,7 +32815,8 @@ this.Documonster = this.Documonster || {};
|
|
|
32143
32815
|
*/
|
|
32144
32816
|
function renderTextWatermark(stream, page, watermark, fontManager) {
|
|
32145
32817
|
const fontSize = watermark.fontSize ?? TEXT_WM_DEFAULTS.fontSize;
|
|
32146
|
-
const
|
|
32818
|
+
const rawColor = watermark.color ?? TEXT_WM_DEFAULTS.color;
|
|
32819
|
+
const color = page.options.blackAndWhite ? toGrayscale(rawColor) : rawColor;
|
|
32147
32820
|
const opacity = watermark.opacity ?? TEXT_WM_DEFAULTS.opacity;
|
|
32148
32821
|
const rotation = watermark.rotation ?? TEXT_WM_DEFAULTS.rotation;
|
|
32149
32822
|
const fontFamily = watermark.fontFamily ?? TEXT_WM_DEFAULTS.fontFamily;
|
|
@@ -32282,6 +32955,7 @@ this.Documonster = this.Documonster || {};
|
|
|
32282
32955
|
init_pdf_stream();
|
|
32283
32956
|
init_font_manager();
|
|
32284
32957
|
init_constants();
|
|
32958
|
+
init_style_converter();
|
|
32285
32959
|
HEADER_FOOTER_DATE_OPTIONS = {
|
|
32286
32960
|
year: "numeric",
|
|
32287
32961
|
month: "numeric",
|
|
@@ -32337,115 +33011,6 @@ this.Documonster = this.Documonster || {};
|
|
|
32337
33011
|
MIN_REPEAT_SPACING = 10;
|
|
32338
33012
|
}));
|
|
32339
33013
|
//#endregion
|
|
32340
|
-
//#region src/utils/theme-colors.ts
|
|
32341
|
-
/**
|
|
32342
|
-
* Parse a hex colour string into 0–1 float RGB components.
|
|
32343
|
-
*
|
|
32344
|
-
* Accepts `RRGGBB` (6 digits), `AARRGGBB` (8 digits, alpha first — the form
|
|
32345
|
-
* Excel uses for cell fills), and `RGB` (3 digits, CSS shorthand). A leading
|
|
32346
|
-
* `#` is tolerated. Returns `null` for anything else.
|
|
32347
|
-
*/
|
|
32348
|
-
function hexToRgb01(hex) {
|
|
32349
|
-
if (!hex) return null;
|
|
32350
|
-
let h = hex.startsWith("#") ? hex.slice(1) : hex;
|
|
32351
|
-
if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
|
|
32352
|
-
let a = 255;
|
|
32353
|
-
let rHex;
|
|
32354
|
-
let gHex;
|
|
32355
|
-
let bHex;
|
|
32356
|
-
if (h.length === 8) {
|
|
32357
|
-
a = parseInt(h.slice(0, 2), 16);
|
|
32358
|
-
rHex = h.slice(2, 4);
|
|
32359
|
-
gHex = h.slice(4, 6);
|
|
32360
|
-
bHex = h.slice(6, 8);
|
|
32361
|
-
} else if (h.length === 6) {
|
|
32362
|
-
rHex = h.slice(0, 2);
|
|
32363
|
-
gHex = h.slice(2, 4);
|
|
32364
|
-
bHex = h.slice(4, 6);
|
|
32365
|
-
} else return null;
|
|
32366
|
-
const r = parseInt(rHex, 16);
|
|
32367
|
-
const g = parseInt(gHex, 16);
|
|
32368
|
-
const b = parseInt(bHex, 16);
|
|
32369
|
-
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b) || Number.isNaN(a)) return null;
|
|
32370
|
-
const alpha = a / 255;
|
|
32371
|
-
return alpha < 1 ? {
|
|
32372
|
-
r: r / 255,
|
|
32373
|
-
g: g / 255,
|
|
32374
|
-
b: b / 255,
|
|
32375
|
-
a: alpha
|
|
32376
|
-
} : {
|
|
32377
|
-
r: r / 255,
|
|
32378
|
-
g: g / 255,
|
|
32379
|
-
b: b / 255
|
|
32380
|
-
};
|
|
32381
|
-
}
|
|
32382
|
-
/**
|
|
32383
|
-
* Apply a tint to a 0–1 float RGB colour, per OOXML §18.8.19.
|
|
32384
|
-
*
|
|
32385
|
-
* Tint ∈ [-1, 1]: positive lightens toward white
|
|
32386
|
-
* (`c + (1 - c) * tint`), negative darkens toward black
|
|
32387
|
-
* (`c * (1 + tint)`). `tint = 0` returns the colour unchanged. Result
|
|
32388
|
-
* components are clamped to [0, 1]; alpha is preserved.
|
|
32389
|
-
*/
|
|
32390
|
-
function applyTintRgb01(color, tint) {
|
|
32391
|
-
const apply = (c) => {
|
|
32392
|
-
const v = tint < 0 ? c * (1 + tint) : c + (1 - c) * tint;
|
|
32393
|
-
return Math.max(0, Math.min(1, v));
|
|
32394
|
-
};
|
|
32395
|
-
const out = {
|
|
32396
|
-
r: apply(color.r),
|
|
32397
|
-
g: apply(color.g),
|
|
32398
|
-
b: apply(color.b)
|
|
32399
|
-
};
|
|
32400
|
-
if (color.a !== void 0) out.a = color.a;
|
|
32401
|
-
return out;
|
|
32402
|
-
}
|
|
32403
|
-
var DEFAULT_OFFICE_THEME, CHART_THEME_PALETTE, CELL_THEME_PALETTE;
|
|
32404
|
-
var init_theme_colors = __esmMin((() => {
|
|
32405
|
-
DEFAULT_OFFICE_THEME = Object.freeze({
|
|
32406
|
-
dk1: "000000",
|
|
32407
|
-
lt1: "FFFFFF",
|
|
32408
|
-
dk2: "44546A",
|
|
32409
|
-
lt2: "E7E6E6",
|
|
32410
|
-
accent1: "4472C4",
|
|
32411
|
-
accent2: "ED7D31",
|
|
32412
|
-
accent3: "A5A5A5",
|
|
32413
|
-
accent4: "FFC000",
|
|
32414
|
-
accent5: "5B9BD5",
|
|
32415
|
-
accent6: "70AD47",
|
|
32416
|
-
hlink: "0563C1",
|
|
32417
|
-
folHlink: "954F72"
|
|
32418
|
-
});
|
|
32419
|
-
CHART_THEME_PALETTE = Object.freeze([
|
|
32420
|
-
DEFAULT_OFFICE_THEME.dk1,
|
|
32421
|
-
DEFAULT_OFFICE_THEME.lt1,
|
|
32422
|
-
DEFAULT_OFFICE_THEME.dk2,
|
|
32423
|
-
DEFAULT_OFFICE_THEME.lt2,
|
|
32424
|
-
DEFAULT_OFFICE_THEME.accent1,
|
|
32425
|
-
DEFAULT_OFFICE_THEME.accent2,
|
|
32426
|
-
DEFAULT_OFFICE_THEME.accent3,
|
|
32427
|
-
DEFAULT_OFFICE_THEME.accent4,
|
|
32428
|
-
DEFAULT_OFFICE_THEME.accent5,
|
|
32429
|
-
DEFAULT_OFFICE_THEME.accent6,
|
|
32430
|
-
DEFAULT_OFFICE_THEME.hlink,
|
|
32431
|
-
DEFAULT_OFFICE_THEME.folHlink
|
|
32432
|
-
]);
|
|
32433
|
-
CELL_THEME_PALETTE = Object.freeze([
|
|
32434
|
-
DEFAULT_OFFICE_THEME.lt1,
|
|
32435
|
-
DEFAULT_OFFICE_THEME.dk1,
|
|
32436
|
-
DEFAULT_OFFICE_THEME.lt2,
|
|
32437
|
-
DEFAULT_OFFICE_THEME.dk2,
|
|
32438
|
-
DEFAULT_OFFICE_THEME.accent1,
|
|
32439
|
-
DEFAULT_OFFICE_THEME.accent2,
|
|
32440
|
-
DEFAULT_OFFICE_THEME.accent3,
|
|
32441
|
-
DEFAULT_OFFICE_THEME.accent4,
|
|
32442
|
-
DEFAULT_OFFICE_THEME.accent5,
|
|
32443
|
-
DEFAULT_OFFICE_THEME.accent6,
|
|
32444
|
-
DEFAULT_OFFICE_THEME.hlink,
|
|
32445
|
-
DEFAULT_OFFICE_THEME.folHlink
|
|
32446
|
-
]);
|
|
32447
|
-
}));
|
|
32448
|
-
//#endregion
|
|
32449
33014
|
//#region src/modules/pdf/core/digital-signature.ts
|
|
32450
33015
|
var digital_signature_exports = /* @__PURE__ */ __exportAll({
|
|
32451
33016
|
asn1Parse: () => asn1Parse,
|
|
@@ -34488,29 +35053,66 @@ this.Documonster = this.Documonster || {};
|
|
|
34488
35053
|
* @param alphaValues Shared set that accumulates transparency values for
|
|
34489
35054
|
* later `ExtGState` registration. The surface adds any
|
|
34490
35055
|
* `color.a` values it observes to this set.
|
|
34491
|
-
|
|
34492
|
-
|
|
35056
|
+
* @param grayscale When true, every color the chart renderer emits is
|
|
35057
|
+
* converted to grayscale. Charts bypass the cell style
|
|
35058
|
+
* pipeline entirely — the chart engine pushes colors
|
|
35059
|
+
* straight into this surface — so Excel's "Black and
|
|
35060
|
+
* white" print option has to be applied right here.
|
|
35061
|
+
*/
|
|
35062
|
+
function createChartSurface(stream, fontManager, alphaValues, grayscale = false) {
|
|
35063
|
+
/** Single choke point for every color entering the surface. */
|
|
35064
|
+
const mapColor = (color) => grayscale && color ? toGrayscale(color) : color;
|
|
34493
35065
|
const applyAlpha = (color) => {
|
|
34494
35066
|
if (color?.a !== void 0 && color.a < 1) {
|
|
34495
35067
|
alphaValues.add(color.a);
|
|
34496
35068
|
stream.setGraphicsState(alphaGsName(color.a));
|
|
34497
35069
|
}
|
|
34498
35070
|
};
|
|
34499
|
-
|
|
34500
|
-
|
|
35071
|
+
/**
|
|
35072
|
+
* Fill and/or stroke the path built by `buildPath`.
|
|
35073
|
+
*
|
|
35074
|
+
* `buildPath` is a closure rather than a pre-built path because a fill and a
|
|
35075
|
+
* stroke with *different* opacities cannot share one graphics state: the
|
|
35076
|
+
* alpha `ExtGState` sets `/ca` and `/CA` together, and `fillAndStroke()`
|
|
35077
|
+
* paints both under whichever was applied last. In that case we paint in two
|
|
35078
|
+
* passes and rebuild the path for the second one. (Within a single pass the
|
|
35079
|
+
* unused half of the ExtGState is harmless — a fill-only pass never strokes.)
|
|
35080
|
+
*/
|
|
35081
|
+
const paintFillStroke = (buildPath, options) => {
|
|
35082
|
+
const { lineWidth, dashPattern } = options;
|
|
35083
|
+
const fill = mapColor(options.fill);
|
|
35084
|
+
const stroke = mapColor(options.stroke);
|
|
35085
|
+
const applyDash = () => {
|
|
35086
|
+
if (dashPattern && dashPattern.length > 0) stream.setDashPattern(dashPattern);
|
|
35087
|
+
};
|
|
35088
|
+
if (fill && stroke) {
|
|
35089
|
+
if ((fill.a ?? 1) !== (stroke.a ?? 1)) {
|
|
35090
|
+
stream.save();
|
|
35091
|
+
buildPath();
|
|
35092
|
+
stream.setFillColor(fill);
|
|
35093
|
+
applyAlpha(fill);
|
|
35094
|
+
stream.fill();
|
|
35095
|
+
stream.restore();
|
|
35096
|
+
stream.save();
|
|
35097
|
+
buildPath();
|
|
35098
|
+
stream.setStrokeColor(stroke);
|
|
35099
|
+
applyAlpha(stroke);
|
|
35100
|
+
applyDash();
|
|
35101
|
+
if (lineWidth !== void 0) stream.setLineWidth(lineWidth);
|
|
35102
|
+
stream.stroke();
|
|
35103
|
+
stream.restore();
|
|
35104
|
+
return;
|
|
35105
|
+
}
|
|
35106
|
+
}
|
|
35107
|
+
buildPath();
|
|
35108
|
+
applyDash();
|
|
34501
35109
|
if (fill) {
|
|
34502
35110
|
stream.setFillColor(fill);
|
|
34503
35111
|
applyAlpha(fill);
|
|
34504
35112
|
}
|
|
34505
35113
|
if (stroke) {
|
|
34506
35114
|
stream.setStrokeColor(stroke);
|
|
34507
|
-
|
|
34508
|
-
const fillAppliedAlpha = fill?.a !== void 0 && fill.a < 1;
|
|
34509
|
-
if (strokeNeedsAlpha) applyAlpha(stroke);
|
|
34510
|
-
else if (fillAppliedAlpha) {
|
|
34511
|
-
alphaValues.add(1);
|
|
34512
|
-
stream.setGraphicsState(alphaGsName(1));
|
|
34513
|
-
}
|
|
35115
|
+
if (!fill) applyAlpha(stroke);
|
|
34514
35116
|
if (lineWidth !== void 0) stream.setLineWidth(lineWidth);
|
|
34515
35117
|
}
|
|
34516
35118
|
if (fill && stroke) stream.fillAndStroke();
|
|
@@ -34522,8 +35124,7 @@ this.Documonster = this.Documonster || {};
|
|
|
34522
35124
|
drawRect(options) {
|
|
34523
35125
|
const { x, y, width, height, fill, stroke, lineWidth } = options;
|
|
34524
35126
|
stream.save();
|
|
34525
|
-
stream.rect(x, y, width, height)
|
|
34526
|
-
paintFillStroke({
|
|
35127
|
+
paintFillStroke(() => stream.rect(x, y, width, height), {
|
|
34527
35128
|
fill,
|
|
34528
35129
|
stroke,
|
|
34529
35130
|
lineWidth
|
|
@@ -34532,7 +35133,8 @@ this.Documonster = this.Documonster || {};
|
|
|
34532
35133
|
return this;
|
|
34533
35134
|
},
|
|
34534
35135
|
drawLine(options) {
|
|
34535
|
-
const { x1, y1, x2, y2,
|
|
35136
|
+
const { x1, y1, x2, y2, lineWidth, dashPattern } = options;
|
|
35137
|
+
const color = mapColor(options.color);
|
|
34536
35138
|
stream.save();
|
|
34537
35139
|
if (color) {
|
|
34538
35140
|
stream.setStrokeColor(color);
|
|
@@ -34547,11 +35149,11 @@ this.Documonster = this.Documonster || {};
|
|
|
34547
35149
|
drawText(text, options) {
|
|
34548
35150
|
if (!text) return this;
|
|
34549
35151
|
const fontSize = options.fontSize ?? 10;
|
|
34550
|
-
const color = options.color ?? {
|
|
35152
|
+
const color = mapColor(options.color ?? {
|
|
34551
35153
|
r: 0,
|
|
34552
35154
|
g: 0,
|
|
34553
35155
|
b: 0
|
|
34554
|
-
};
|
|
35156
|
+
});
|
|
34555
35157
|
const bold = options.bold ?? false;
|
|
34556
35158
|
const italic = options.italic ?? false;
|
|
34557
35159
|
const fontFamily = options.fontFamily ?? "Helvetica";
|
|
@@ -34578,8 +35180,7 @@ this.Documonster = this.Documonster || {};
|
|
|
34578
35180
|
drawCircle(options) {
|
|
34579
35181
|
const { cx, cy, r, fill, stroke, lineWidth } = options;
|
|
34580
35182
|
stream.save();
|
|
34581
|
-
stream.circle(cx, cy, r)
|
|
34582
|
-
paintFillStroke({
|
|
35183
|
+
paintFillStroke(() => stream.circle(cx, cy, r), {
|
|
34583
35184
|
fill,
|
|
34584
35185
|
stroke,
|
|
34585
35186
|
lineWidth
|
|
@@ -34588,25 +35189,27 @@ this.Documonster = this.Documonster || {};
|
|
|
34588
35189
|
return this;
|
|
34589
35190
|
},
|
|
34590
35191
|
drawPath(ops, options) {
|
|
35192
|
+
const buildPath = () => {
|
|
35193
|
+
for (const op of ops) switch (op.op) {
|
|
35194
|
+
case "move":
|
|
35195
|
+
stream.moveTo(op.x, op.y);
|
|
35196
|
+
break;
|
|
35197
|
+
case "line":
|
|
35198
|
+
stream.lineTo(op.x, op.y);
|
|
35199
|
+
break;
|
|
35200
|
+
case "curve":
|
|
35201
|
+
stream.curveTo(op.x1, op.y1, op.x2, op.y2, op.x3, op.y3);
|
|
35202
|
+
break;
|
|
35203
|
+
case "close": stream.closePath();
|
|
35204
|
+
}
|
|
35205
|
+
if (options?.closePath) stream.closePath();
|
|
35206
|
+
};
|
|
34591
35207
|
stream.save();
|
|
34592
|
-
|
|
34593
|
-
case "move":
|
|
34594
|
-
stream.moveTo(op.x, op.y);
|
|
34595
|
-
break;
|
|
34596
|
-
case "line":
|
|
34597
|
-
stream.lineTo(op.x, op.y);
|
|
34598
|
-
break;
|
|
34599
|
-
case "curve":
|
|
34600
|
-
stream.curveTo(op.x1, op.y1, op.x2, op.y2, op.x3, op.y3);
|
|
34601
|
-
break;
|
|
34602
|
-
case "close": stream.closePath();
|
|
34603
|
-
}
|
|
34604
|
-
if (options?.closePath) stream.closePath();
|
|
34605
|
-
if (options?.dashPattern && options.dashPattern.length > 0) stream.setDashPattern(options.dashPattern);
|
|
34606
|
-
paintFillStroke({
|
|
35208
|
+
paintFillStroke(buildPath, {
|
|
34607
35209
|
fill: options?.fill,
|
|
34608
35210
|
stroke: options?.stroke,
|
|
34609
|
-
lineWidth: options?.lineWidth
|
|
35211
|
+
lineWidth: options?.lineWidth,
|
|
35212
|
+
dashPattern: options?.dashPattern
|
|
34610
35213
|
});
|
|
34611
35214
|
stream.restore();
|
|
34612
35215
|
return this;
|
|
@@ -34620,309 +35223,7 @@ this.Documonster = this.Documonster || {};
|
|
|
34620
35223
|
var init_chart_surface = __esmMin((() => {
|
|
34621
35224
|
init_font_manager();
|
|
34622
35225
|
init_page_renderer();
|
|
34623
|
-
|
|
34624
|
-
//#endregion
|
|
34625
|
-
//#region src/modules/pdf/render/style-converter.ts
|
|
34626
|
-
/**
|
|
34627
|
-
* Convert an ARGB color string to PDF RGB color.
|
|
34628
|
-
* Handles "AARRGGBB" (8-char) and "RRGGBB" (6-char) formats.
|
|
34629
|
-
* PDF uses 0-1 floats for each component.
|
|
34630
|
-
*/
|
|
34631
|
-
function argbToPdfColor(argb) {
|
|
34632
|
-
if (!argb || argb.length < 6) return null;
|
|
34633
|
-
return hexToRgb01(argb);
|
|
34634
|
-
}
|
|
34635
|
-
/**
|
|
34636
|
-
* Convert a color data object to PDF color.
|
|
34637
|
-
* Handles ARGB, theme-based, and indexed colors.
|
|
34638
|
-
*/
|
|
34639
|
-
function excelColorToPdf(color) {
|
|
34640
|
-
if (!color) return null;
|
|
34641
|
-
if (color.argb) return argbToPdfColor(color.argb);
|
|
34642
|
-
if (color.theme !== void 0) {
|
|
34643
|
-
const base = themeColorToPdf(color.theme);
|
|
34644
|
-
if (!base) return null;
|
|
34645
|
-
const tint = color.tint;
|
|
34646
|
-
if (tint !== void 0 && tint !== 0) return applyTint(base, tint);
|
|
34647
|
-
return base;
|
|
34648
|
-
}
|
|
34649
|
-
if (color.indexed !== void 0) return indexedColorToPdf(color.indexed);
|
|
34650
|
-
return null;
|
|
34651
|
-
}
|
|
34652
|
-
/**
|
|
34653
|
-
* Map a cell-colour theme index to a PDF colour using the default Office
|
|
34654
|
-
* palette in SpreadsheetML cell order (0=lt1, 1=dk1, 2=lt2, 3=dk2, …).
|
|
34655
|
-
*/
|
|
34656
|
-
function themeColorToPdf(themeIndex) {
|
|
34657
|
-
if (themeIndex >= 0 && themeIndex < CELL_THEME_PALETTE.length) return hexToRgb01(CELL_THEME_PALETTE[themeIndex]);
|
|
34658
|
-
return null;
|
|
34659
|
-
}
|
|
34660
|
-
/**
|
|
34661
|
-
* Convert an indexed color to PDF color.
|
|
34662
|
-
* Index 64 = system foreground (black), 65 = system background (white).
|
|
34663
|
-
*/
|
|
34664
|
-
function indexedColorToPdf(index) {
|
|
34665
|
-
if (index === 64) return {
|
|
34666
|
-
r: 0,
|
|
34667
|
-
g: 0,
|
|
34668
|
-
b: 0
|
|
34669
|
-
};
|
|
34670
|
-
if (index === 65) return {
|
|
34671
|
-
r: 1,
|
|
34672
|
-
g: 1,
|
|
34673
|
-
b: 1
|
|
34674
|
-
};
|
|
34675
|
-
if (index >= 0 && index < INDEXED_COLORS.length) return argbToPdfColor(INDEXED_COLORS[index]) ?? null;
|
|
34676
|
-
return null;
|
|
34677
|
-
}
|
|
34678
|
-
/**
|
|
34679
|
-
* Apply a tint value to a color.
|
|
34680
|
-
* Tint range: -1.0 (fully dark) to +1.0 (fully light).
|
|
34681
|
-
* Negative tint darkens, positive tint lightens.
|
|
34682
|
-
*
|
|
34683
|
-
* @see OOXML §18.8.19 - tint formula
|
|
34684
|
-
*/
|
|
34685
|
-
function applyTint(color, tint) {
|
|
34686
|
-
return applyTintRgb01(color, tint);
|
|
34687
|
-
}
|
|
34688
|
-
/**
|
|
34689
|
-
* Extract font properties for PDF rendering.
|
|
34690
|
-
*/
|
|
34691
|
-
function extractFontProperties(font, defaultFamily, defaultSize) {
|
|
34692
|
-
return {
|
|
34693
|
-
fontFamily: font?.name ?? defaultFamily,
|
|
34694
|
-
fontSize: font?.size ?? defaultSize,
|
|
34695
|
-
bold: font?.bold ?? false,
|
|
34696
|
-
italic: font?.italic ?? false,
|
|
34697
|
-
strike: font?.strike ?? false,
|
|
34698
|
-
underline: !!(font?.underline && font.underline !== "none"),
|
|
34699
|
-
textColor: excelColorToPdf(font?.color) ?? DEFAULT_COLORS.black
|
|
34700
|
-
};
|
|
34701
|
-
}
|
|
34702
|
-
/**
|
|
34703
|
-
* Convert a fill to a PDF background color.
|
|
34704
|
-
* Only pattern fills with "solid" pattern are supported as PDF backgrounds.
|
|
34705
|
-
* Other patterns are approximated or ignored.
|
|
34706
|
-
*/
|
|
34707
|
-
function excelFillToPdfColor(fill) {
|
|
34708
|
-
if (!fill) return null;
|
|
34709
|
-
if (fill.type === "pattern") {
|
|
34710
|
-
if (fill.pattern === "solid" && fill.fgColor) return excelColorToPdf(fill.fgColor);
|
|
34711
|
-
if (fill.pattern === "none") return null;
|
|
34712
|
-
if (fill.fgColor) return excelColorToPdf(fill.fgColor);
|
|
34713
|
-
}
|
|
34714
|
-
if (fill.type === "gradient") {
|
|
34715
|
-
if (fill.stops && fill.stops.length > 0) return excelColorToPdf(fill.stops[0].color);
|
|
34716
|
-
}
|
|
34717
|
-
return null;
|
|
34718
|
-
}
|
|
34719
|
-
/**
|
|
34720
|
-
* Map border styles to PDF line widths (in points).
|
|
34721
|
-
*
|
|
34722
|
-
* Values match Excel's actual border weights as used historically by this
|
|
34723
|
-
* library. An earlier change doubled every width (0.25 → 0.5, 0.5 → 1,
|
|
34724
|
-
* 1 → 2) to make `thin` and `medium` more visually distinct in PDF
|
|
34725
|
-
* viewers, but that change made all borders heavier than Excel itself.
|
|
34726
|
-
* The 2× ratio between thin/medium and the 4× ratio between
|
|
34727
|
-
* thin/thick are preserved with the lighter values, so styles remain
|
|
34728
|
-
* distinguishable while matching Excel.
|
|
34729
|
-
*
|
|
34730
|
-
* hair = 0.1 pt
|
|
34731
|
-
* thin = 0.25 pt (also dotted, dashed, dashDot, dashDotDot, slantDashDot, double)
|
|
34732
|
-
* medium = 0.5 pt (also mediumDashed, mediumDashDot, mediumDashDotDot)
|
|
34733
|
-
* thick = 1 pt
|
|
34734
|
-
*/
|
|
34735
|
-
function borderStyleToLineWidth(style) {
|
|
34736
|
-
switch (style) {
|
|
34737
|
-
case "hair": return .1;
|
|
34738
|
-
case "thin": return .25;
|
|
34739
|
-
case "medium": return .5;
|
|
34740
|
-
case "thick": return 1;
|
|
34741
|
-
case "double": return .25;
|
|
34742
|
-
case "dotted": return .25;
|
|
34743
|
-
case "dashed": return .25;
|
|
34744
|
-
case "dashDot": return .25;
|
|
34745
|
-
case "dashDotDot": return .25;
|
|
34746
|
-
case "slantDashDot": return .25;
|
|
34747
|
-
case "mediumDashed": return .5;
|
|
34748
|
-
case "mediumDashDot": return .5;
|
|
34749
|
-
case "mediumDashDotDot": return .5;
|
|
34750
|
-
default: return .25;
|
|
34751
|
-
}
|
|
34752
|
-
}
|
|
34753
|
-
/**
|
|
34754
|
-
* Map border styles to PDF dash patterns.
|
|
34755
|
-
* An empty array means a solid line.
|
|
34756
|
-
*/
|
|
34757
|
-
function borderStyleToDashPattern(style) {
|
|
34758
|
-
switch (style) {
|
|
34759
|
-
case "dotted": return [1, 1];
|
|
34760
|
-
case "dashed":
|
|
34761
|
-
case "mediumDashed": return [3, 2];
|
|
34762
|
-
case "dashDot":
|
|
34763
|
-
case "mediumDashDot": return [
|
|
34764
|
-
3,
|
|
34765
|
-
1,
|
|
34766
|
-
1,
|
|
34767
|
-
1
|
|
34768
|
-
];
|
|
34769
|
-
case "dashDotDot":
|
|
34770
|
-
case "mediumDashDotDot": return [
|
|
34771
|
-
3,
|
|
34772
|
-
1,
|
|
34773
|
-
1,
|
|
34774
|
-
1,
|
|
34775
|
-
1,
|
|
34776
|
-
1
|
|
34777
|
-
];
|
|
34778
|
-
case "slantDashDot": return [
|
|
34779
|
-
4,
|
|
34780
|
-
2,
|
|
34781
|
-
1,
|
|
34782
|
-
2
|
|
34783
|
-
];
|
|
34784
|
-
case "hair": return [.5, .5];
|
|
34785
|
-
default: return [];
|
|
34786
|
-
}
|
|
34787
|
-
}
|
|
34788
|
-
/**
|
|
34789
|
-
* Convert a single border side to a PDF LayoutBorder.
|
|
34790
|
-
*/
|
|
34791
|
-
function convertBorder(border) {
|
|
34792
|
-
if (!border || !border.style) return null;
|
|
34793
|
-
return {
|
|
34794
|
-
width: borderStyleToLineWidth(border.style),
|
|
34795
|
-
color: excelColorToPdf(border.color) ?? DEFAULT_COLORS.black,
|
|
34796
|
-
dashPattern: borderStyleToDashPattern(border.style),
|
|
34797
|
-
isDouble: border.style === "double"
|
|
34798
|
-
};
|
|
34799
|
-
}
|
|
34800
|
-
/**
|
|
34801
|
-
* Convert border data to PDF LayoutBorders.
|
|
34802
|
-
*/
|
|
34803
|
-
function excelBordersToPdf(borders) {
|
|
34804
|
-
if (!borders) return {
|
|
34805
|
-
top: null,
|
|
34806
|
-
right: null,
|
|
34807
|
-
bottom: null,
|
|
34808
|
-
left: null
|
|
34809
|
-
};
|
|
34810
|
-
return {
|
|
34811
|
-
top: convertBorder(borders.top),
|
|
34812
|
-
right: convertBorder(borders.right),
|
|
34813
|
-
bottom: convertBorder(borders.bottom),
|
|
34814
|
-
left: convertBorder(borders.left)
|
|
34815
|
-
};
|
|
34816
|
-
}
|
|
34817
|
-
/**
|
|
34818
|
-
* Convert horizontal alignment to PDF alignment.
|
|
34819
|
-
*/
|
|
34820
|
-
function excelHAlignToPdf(alignment) {
|
|
34821
|
-
if (!alignment?.horizontal) return "left";
|
|
34822
|
-
switch (alignment.horizontal) {
|
|
34823
|
-
case "center":
|
|
34824
|
-
case "centerContinuous": return "center";
|
|
34825
|
-
case "right": return "right";
|
|
34826
|
-
default: return "left";
|
|
34827
|
-
}
|
|
34828
|
-
}
|
|
34829
|
-
/**
|
|
34830
|
-
* Convert vertical alignment to PDF alignment.
|
|
34831
|
-
*/
|
|
34832
|
-
function excelVAlignToPdf(alignment) {
|
|
34833
|
-
if (!alignment?.vertical) return "bottom";
|
|
34834
|
-
switch (alignment.vertical) {
|
|
34835
|
-
case "top": return "top";
|
|
34836
|
-
case "middle": return "middle";
|
|
34837
|
-
default: return "bottom";
|
|
34838
|
-
}
|
|
34839
|
-
}
|
|
34840
|
-
var INDEXED_COLORS, DEFAULT_COLORS;
|
|
34841
|
-
var init_style_converter = __esmMin((() => {
|
|
34842
|
-
init_theme_colors();
|
|
34843
|
-
INDEXED_COLORS = [
|
|
34844
|
-
"000000",
|
|
34845
|
-
"FFFFFF",
|
|
34846
|
-
"FF0000",
|
|
34847
|
-
"00FF00",
|
|
34848
|
-
"0000FF",
|
|
34849
|
-
"FFFF00",
|
|
34850
|
-
"FF00FF",
|
|
34851
|
-
"00FFFF",
|
|
34852
|
-
"000000",
|
|
34853
|
-
"FFFFFF",
|
|
34854
|
-
"FF0000",
|
|
34855
|
-
"00FF00",
|
|
34856
|
-
"0000FF",
|
|
34857
|
-
"FFFF00",
|
|
34858
|
-
"FF00FF",
|
|
34859
|
-
"00FFFF",
|
|
34860
|
-
"800000",
|
|
34861
|
-
"008000",
|
|
34862
|
-
"000080",
|
|
34863
|
-
"808000",
|
|
34864
|
-
"800080",
|
|
34865
|
-
"008080",
|
|
34866
|
-
"C0C0C0",
|
|
34867
|
-
"808080",
|
|
34868
|
-
"9999FF",
|
|
34869
|
-
"993366",
|
|
34870
|
-
"FFFFCC",
|
|
34871
|
-
"CCFFFF",
|
|
34872
|
-
"660066",
|
|
34873
|
-
"FF8080",
|
|
34874
|
-
"0066CC",
|
|
34875
|
-
"CCCCFF",
|
|
34876
|
-
"000080",
|
|
34877
|
-
"FF00FF",
|
|
34878
|
-
"FFFF00",
|
|
34879
|
-
"00FFFF",
|
|
34880
|
-
"800080",
|
|
34881
|
-
"800000",
|
|
34882
|
-
"008080",
|
|
34883
|
-
"0000FF",
|
|
34884
|
-
"00CCFF",
|
|
34885
|
-
"CCFFFF",
|
|
34886
|
-
"CCFFCC",
|
|
34887
|
-
"FFFF99",
|
|
34888
|
-
"99CCFF",
|
|
34889
|
-
"FF99CC",
|
|
34890
|
-
"CC99FF",
|
|
34891
|
-
"FFCC99",
|
|
34892
|
-
"3366FF",
|
|
34893
|
-
"33CCCC",
|
|
34894
|
-
"99CC00",
|
|
34895
|
-
"FFCC00",
|
|
34896
|
-
"FF9900",
|
|
34897
|
-
"FF6600",
|
|
34898
|
-
"666699",
|
|
34899
|
-
"969696",
|
|
34900
|
-
"003366",
|
|
34901
|
-
"339966",
|
|
34902
|
-
"003300",
|
|
34903
|
-
"333300",
|
|
34904
|
-
"993300",
|
|
34905
|
-
"993366",
|
|
34906
|
-
"333399",
|
|
34907
|
-
"333333"
|
|
34908
|
-
];
|
|
34909
|
-
DEFAULT_COLORS = {
|
|
34910
|
-
black: {
|
|
34911
|
-
r: 0,
|
|
34912
|
-
g: 0,
|
|
34913
|
-
b: 0
|
|
34914
|
-
},
|
|
34915
|
-
white: {
|
|
34916
|
-
r: 1,
|
|
34917
|
-
g: 1,
|
|
34918
|
-
b: 1
|
|
34919
|
-
},
|
|
34920
|
-
gridLine: {
|
|
34921
|
-
r: .816,
|
|
34922
|
-
g: .816,
|
|
34923
|
-
b: .816
|
|
34924
|
-
}
|
|
34925
|
-
};
|
|
35226
|
+
init_style_converter();
|
|
34926
35227
|
}));
|
|
34927
35228
|
//#endregion
|
|
34928
35229
|
//#region src/modules/pdf/types.ts
|
|
@@ -35130,18 +35431,19 @@ this.Documonster = this.Documonster || {};
|
|
|
35130
35431
|
if (!ctx) return [createEmptyPage(sheet, options)];
|
|
35131
35432
|
const layoutPages = [];
|
|
35132
35433
|
const totalOutputPages = ctx.rowPages.length * ctx.colGroups.length;
|
|
35133
|
-
|
|
35434
|
+
const overThenDown = options.pageOrder === "overThenDown";
|
|
35435
|
+
const outer = overThenDown ? ctx.rowPages : ctx.colGroups;
|
|
35436
|
+
const inner = overThenDown ? ctx.colGroups : ctx.rowPages;
|
|
35437
|
+
for (const outerTracks of outer) for (const innerTracks of inner) {
|
|
35438
|
+
const rowPage = overThenDown ? outerTracks : innerTracks;
|
|
35439
|
+
const colGroup = overThenDown ? innerTracks : outerTracks;
|
|
35134
35440
|
layoutPages.push(buildPageLayout(ctx, rowPage, colGroup, layoutPages.length, sheet, options, fontManager));
|
|
35135
35441
|
if (layoutPages.length < totalOutputPages) await yieldToEventLoop();
|
|
35136
35442
|
}
|
|
35137
|
-
if (layoutPages.length > 0 && sheet.images) assignImagesToPages(sheet.images, layoutPages, ctx.scaleFactor);
|
|
35138
|
-
if (layoutPages.length > 0 && sheet.charts) assignChartsToPages(sheet.charts, layoutPages, ctx.scaleFactor);
|
|
35139
|
-
|
|
35140
|
-
for (let i = 0; i < layoutPages.length; i++)
|
|
35141
|
-
layoutPages[i].sheetPageNumber = firstPageNumber + i;
|
|
35142
|
-
layoutPages[i].sheetPageIndex = i + 1;
|
|
35143
|
-
layoutPages[i].sheetPageCount = layoutPages.length;
|
|
35144
|
-
}
|
|
35443
|
+
if (layoutPages.length > 0 && sheet.images && !options.draft) assignImagesToPages(sheet.images, layoutPages, ctx.scaleFactor);
|
|
35444
|
+
if (layoutPages.length > 0 && sheet.charts && !options.draft) assignChartsToPages(sheet.charts, layoutPages, ctx.scaleFactor);
|
|
35445
|
+
if (options.cellComments === "atEnd" && sheet.comments?.length) layoutPages.push(...buildCommentPages(sheet, sheet.comments, options, fontManager));
|
|
35446
|
+
for (let i = 0; i < layoutPages.length; i++) layoutPages[i].sheetPageIndex = i + 1;
|
|
35145
35447
|
return layoutPages;
|
|
35146
35448
|
}
|
|
35147
35449
|
/**
|
|
@@ -35170,9 +35472,7 @@ this.Documonster = this.Documonster || {};
|
|
|
35170
35472
|
...documentOptions,
|
|
35171
35473
|
orientation
|
|
35172
35474
|
};
|
|
35173
|
-
|
|
35174
|
-
let pageHeight = options.pageSize.height;
|
|
35175
|
-
if (options.orientation === "landscape") [pageWidth, pageHeight] = [pageHeight, pageWidth];
|
|
35475
|
+
const { width: pageWidth, height: pageHeight } = pageDimensions(options);
|
|
35176
35476
|
const margins = options.margins;
|
|
35177
35477
|
const headerHeight = options.showSheetNames ? 20 : 0;
|
|
35178
35478
|
const footerHeight = options.showPageNumbers ? 20 : 0;
|
|
@@ -35191,6 +35491,35 @@ this.Documonster = this.Documonster || {};
|
|
|
35191
35491
|
raster: sheet.chart.raster
|
|
35192
35492
|
};
|
|
35193
35493
|
return [{
|
|
35494
|
+
...blankPage(sheet, options),
|
|
35495
|
+
charts: options.draft ? [] : [chart]
|
|
35496
|
+
}];
|
|
35497
|
+
}
|
|
35498
|
+
/**
|
|
35499
|
+
* Page dimensions for the resolved options, with landscape applied.
|
|
35500
|
+
*
|
|
35501
|
+
* `pageSize` is always stored portrait-wise, so every consumer has to swap the
|
|
35502
|
+
* axes itself; centralised here so the four layout entry points cannot disagree.
|
|
35503
|
+
*/
|
|
35504
|
+
function pageDimensions(options) {
|
|
35505
|
+
const { width, height } = options.pageSize;
|
|
35506
|
+
return options.orientation === "landscape" ? {
|
|
35507
|
+
width: height,
|
|
35508
|
+
height: width
|
|
35509
|
+
} : {
|
|
35510
|
+
width,
|
|
35511
|
+
height
|
|
35512
|
+
};
|
|
35513
|
+
}
|
|
35514
|
+
/**
|
|
35515
|
+
* A `LayoutPage` with every field at its empty value.
|
|
35516
|
+
*
|
|
35517
|
+
* The shape has ~20 members and four construction sites; building them from one
|
|
35518
|
+
* skeleton means a new field cannot be forgotten in three of them.
|
|
35519
|
+
*/
|
|
35520
|
+
function blankPage(sheet, options) {
|
|
35521
|
+
const { width, height } = pageDimensions(options);
|
|
35522
|
+
return {
|
|
35194
35523
|
pageNumber: 1,
|
|
35195
35524
|
sheetPageNumber: sheet.pageSetup?.firstPageNumber ?? 1,
|
|
35196
35525
|
sheetPageIndex: 1,
|
|
@@ -35198,8 +35527,8 @@ this.Documonster = this.Documonster || {};
|
|
|
35198
35527
|
firstPageNumber: sheet.pageSetup?.firstPageNumber,
|
|
35199
35528
|
options,
|
|
35200
35529
|
cells: [],
|
|
35201
|
-
width
|
|
35202
|
-
height
|
|
35530
|
+
width,
|
|
35531
|
+
height,
|
|
35203
35532
|
sheetName: sheet.name,
|
|
35204
35533
|
sheetCols: [],
|
|
35205
35534
|
columnOffsets: [],
|
|
@@ -35208,10 +35537,10 @@ this.Documonster = this.Documonster || {};
|
|
|
35208
35537
|
rowYPositions: [],
|
|
35209
35538
|
rowHeights: [],
|
|
35210
35539
|
images: [],
|
|
35211
|
-
charts: [
|
|
35540
|
+
charts: [],
|
|
35212
35541
|
scaleFactor: 1,
|
|
35213
35542
|
headerFooter: options.includeHeadersFooters ? sheet.headerFooter : void 0
|
|
35214
|
-
}
|
|
35543
|
+
};
|
|
35215
35544
|
}
|
|
35216
35545
|
/**
|
|
35217
35546
|
* Steps 1–5: compute columns, scale, rows, merges, pagination.
|
|
@@ -35219,32 +35548,54 @@ this.Documonster = this.Documonster || {};
|
|
|
35219
35548
|
*/
|
|
35220
35549
|
function prepareLayout(sheet, options, fontManager) {
|
|
35221
35550
|
const { margins } = options;
|
|
35222
|
-
|
|
35223
|
-
let pageHeight = options.pageSize.height;
|
|
35224
|
-
if (options.orientation === "landscape") [pageWidth, pageHeight] = [pageHeight, pageWidth];
|
|
35225
|
-
const contentWidth = pageWidth - margins.left - margins.right;
|
|
35226
|
-
const contentHeight = pageHeight - margins.top - margins.bottom;
|
|
35551
|
+
const { width: pageWidth, height: pageHeight } = pageDimensions(options);
|
|
35227
35552
|
const headerHeight = options.showSheetNames ? 20 : 0;
|
|
35228
35553
|
const printRange = getPrintRange(sheet, options);
|
|
35229
|
-
const
|
|
35554
|
+
const headings = options.showRowColHeaders ? computeHeadingMetrics(sheet, printRange, fontManager, options) : void 0;
|
|
35555
|
+
const gutterWidth = headings?.gutterWidth ?? 0;
|
|
35556
|
+
const bandHeight = headings?.bandHeight ?? 0;
|
|
35557
|
+
const contentWidth = pageWidth - margins.left - margins.right - gutterWidth;
|
|
35558
|
+
const contentHeight = pageHeight - margins.top - margins.bottom - bandHeight;
|
|
35559
|
+
const { columnWidths, visibleCols, repeatColIndices } = computeColumnWidths(sheet, printRange, options.repeatCols);
|
|
35230
35560
|
if (visibleCols.length === 0) return null;
|
|
35561
|
+
const natural = computeRowHeights(sheet, printRange, fontManager, options, options.repeatRows);
|
|
35562
|
+
const rowBreaks = buildBreakSet(sheet.rowBreaks ?? [], natural.visibleRows);
|
|
35563
|
+
const colBreaks = buildBreakSet(sheet.colBreaks ?? [], visibleCols);
|
|
35231
35564
|
const totalTableWidth = columnWidths.reduce((sum, w) => sum + w, 0);
|
|
35565
|
+
const footerHeight = options.showPageNumbers ? 20 : 0;
|
|
35566
|
+
const availableHeight = contentHeight - headerHeight - footerHeight;
|
|
35232
35567
|
let scaleFactor = options.scale;
|
|
35233
|
-
if (options.
|
|
35234
|
-
|
|
35568
|
+
if (options.fitToWidth > 0 || options.fitToHeight > 0) {
|
|
35569
|
+
let fit = 1;
|
|
35570
|
+
const minFit = Math.min(1, FIT_MIN_SCALE / scaleFactor);
|
|
35571
|
+
if (options.fitToWidth > 0 && totalTableWidth > 0) {
|
|
35572
|
+
fit = Math.min(fit, contentWidth * options.fitToWidth / (totalTableWidth * scaleFactor));
|
|
35573
|
+
fit = tightenToPageCount((candidate) => paginateTracks(columnWidths.map((w) => w * scaleFactor * candidate), contentWidth, repeatColIndices, colBreaks, COLUMN_FIT_EPSILON).length, options.fitToWidth, fit, minFit);
|
|
35574
|
+
}
|
|
35575
|
+
if (options.fitToHeight > 0 && availableHeight > 0) {
|
|
35576
|
+
const totalTableHeight = natural.rowHeights.reduce((sum, h) => sum + h, 0);
|
|
35577
|
+
if (totalTableHeight > 0) {
|
|
35578
|
+
let heightFit = Math.min(fit, availableHeight * options.fitToHeight / (totalTableHeight * scaleFactor));
|
|
35579
|
+
heightFit = tightenToPageCount((candidate) => paginateTracks(natural.rowHeights.map((h) => h * scaleFactor * candidate), availableHeight, natural.repeatRowIndices, rowBreaks).length, options.fitToHeight, heightFit, minFit);
|
|
35580
|
+
fit = Math.min(fit, heightFit);
|
|
35581
|
+
}
|
|
35582
|
+
}
|
|
35583
|
+
if (fit < 1) scaleFactor = Math.max(scaleFactor * fit, FIT_MIN_SCALE);
|
|
35584
|
+
} else if (options.fitToPage && totalTableWidth > 0) {
|
|
35585
|
+
const fitScale = contentWidth / (totalTableWidth * scaleFactor);
|
|
35235
35586
|
if (fitScale < 1) scaleFactor *= fitScale;
|
|
35236
35587
|
}
|
|
35237
35588
|
const scaledColumnWidths = columnWidths.map((w) => w * scaleFactor);
|
|
35238
|
-
const
|
|
35239
|
-
const
|
|
35240
|
-
const { rowHeights, visibleRows } = computeRowHeights(sheet, scaleFactor, printRange, fontManager, options);
|
|
35589
|
+
const rowHeights = natural.rowHeights.map((h) => h * scaleFactor);
|
|
35590
|
+
const { visibleRows, repeatRowIndices } = natural;
|
|
35241
35591
|
const mergeMap = buildMergeMap(sheet);
|
|
35242
|
-
const rowPages =
|
|
35243
|
-
const colGroups =
|
|
35592
|
+
const rowPages = paginateTracks(rowHeights, availableHeight, repeatRowIndices, rowBreaks);
|
|
35593
|
+
const colGroups = paginateTracks(scaledColumnWidths, contentWidth, repeatColIndices, colBreaks, COLUMN_FIT_EPSILON);
|
|
35244
35594
|
return {
|
|
35245
35595
|
pageWidth,
|
|
35246
35596
|
pageHeight,
|
|
35247
35597
|
contentWidth,
|
|
35598
|
+
availableHeight,
|
|
35248
35599
|
headerHeight,
|
|
35249
35600
|
scaleFactor,
|
|
35250
35601
|
scaledColumnWidths,
|
|
@@ -35254,27 +35605,173 @@ this.Documonster = this.Documonster || {};
|
|
|
35254
35605
|
mergeMap,
|
|
35255
35606
|
rowPages,
|
|
35256
35607
|
colGroups,
|
|
35257
|
-
margins
|
|
35608
|
+
margins,
|
|
35609
|
+
headings
|
|
35258
35610
|
};
|
|
35259
35611
|
}
|
|
35260
35612
|
/**
|
|
35613
|
+
* Size the row-number gutter and column-letter band for Excel's "Row and
|
|
35614
|
+
* column headings" print option.
|
|
35615
|
+
*
|
|
35616
|
+
* The gutter is sized from the widest row label that can appear in the printed
|
|
35617
|
+
* range, so the grid origin is stable across pages instead of jittering as row
|
|
35618
|
+
* numbers gain digits.
|
|
35619
|
+
*/
|
|
35620
|
+
function computeHeadingMetrics(sheet, printRange, fontManager, options) {
|
|
35621
|
+
const fontSize = 8;
|
|
35622
|
+
const resourceName = fontManager.hasEmbeddedFont() ? fontManager.getEmbeddedResourceName() : fontManager.ensureFont(resolvePdfFontName(options.defaultFontFamily, false, false));
|
|
35623
|
+
const lastRow = printRange?.endRow ?? sheet.bounds.bottom;
|
|
35624
|
+
const widestLabel = String(Math.max(1, lastRow));
|
|
35625
|
+
fontManager.trackText(widestLabel);
|
|
35626
|
+
return {
|
|
35627
|
+
gutterWidth: fontManager.measureText(widestLabel, resourceName, fontSize) + 6,
|
|
35628
|
+
bandHeight: 14,
|
|
35629
|
+
fontSize
|
|
35630
|
+
};
|
|
35631
|
+
}
|
|
35632
|
+
/**
|
|
35633
|
+
* Largest scale ≤ `startScale` whose real pagination lands within `target`
|
|
35634
|
+
* pages, found by bisection.
|
|
35635
|
+
*
|
|
35636
|
+
* The caller passes a closure that re-paginates at a candidate scale, so the
|
|
35637
|
+
* answer respects indivisible columns/rows, manual breaks and repeated title
|
|
35638
|
+
* bands instead of trusting a total-size ratio. Returns `startScale` untouched
|
|
35639
|
+
* when it already fits, so nothing is shrunk needlessly.
|
|
35640
|
+
*
|
|
35641
|
+
* `minScale` is the floor the result may not go below. When the target is
|
|
35642
|
+
* unreachable even there — manual page breaks alone can force more pages than
|
|
35643
|
+
* requested — the floor is returned and the sheet simply spans more than
|
|
35644
|
+
* `target` pages, which is preferable to shrinking it into illegibility.
|
|
35645
|
+
*/
|
|
35646
|
+
function tightenToPageCount(pageCountAt, target, startScale, minScale) {
|
|
35647
|
+
const start = Math.max(startScale, minScale);
|
|
35648
|
+
if (pageCountAt(start) <= target || start <= minScale) return start;
|
|
35649
|
+
let lo = minScale;
|
|
35650
|
+
let hi = start;
|
|
35651
|
+
if (pageCountAt(lo) > target) return lo;
|
|
35652
|
+
for (let i = 0; i < 24; i++) {
|
|
35653
|
+
const mid = (lo + hi) / 2;
|
|
35654
|
+
if (pageCountAt(mid) <= target) lo = mid;
|
|
35655
|
+
else hi = mid;
|
|
35656
|
+
}
|
|
35657
|
+
return lo;
|
|
35658
|
+
}
|
|
35659
|
+
/**
|
|
35660
|
+
* Lay out Excel's "Comments at end of sheet" as extra pages.
|
|
35661
|
+
*
|
|
35662
|
+
* Each entry becomes an ordinary {@link LayoutCell} spanning the content width,
|
|
35663
|
+
* so the existing page renderer draws them with no special casing — including
|
|
35664
|
+
* word wrapping, which long comment bodies need. Pages are filled top to bottom
|
|
35665
|
+
* and a new one starts when the next entry would overflow.
|
|
35666
|
+
*/
|
|
35667
|
+
function buildCommentPages(sheet, comments, options, fontManager) {
|
|
35668
|
+
const { width: pageWidth, height: pageHeight } = pageDimensions(options);
|
|
35669
|
+
const { margins } = options;
|
|
35670
|
+
const headerHeight = options.showSheetNames ? 20 : 0;
|
|
35671
|
+
const footerHeight = options.showPageNumbers ? 20 : 0;
|
|
35672
|
+
const contentWidth = pageWidth - margins.left - margins.right;
|
|
35673
|
+
const top = pageHeight - margins.top - headerHeight;
|
|
35674
|
+
const bottom = margins.bottom + footerHeight;
|
|
35675
|
+
const fontSize = options.defaultFontSize;
|
|
35676
|
+
const resourceName = fontManager.hasEmbeddedFont() ? fontManager.getEmbeddedResourceName() : fontManager.ensureFont(resolvePdfFontName(options.defaultFontFamily, false, false));
|
|
35677
|
+
const measure = (text) => fontManager.measureText(text, resourceName, fontSize);
|
|
35678
|
+
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
35679
|
+
const textColor = {
|
|
35680
|
+
r: 0,
|
|
35681
|
+
g: 0,
|
|
35682
|
+
b: 0
|
|
35683
|
+
};
|
|
35684
|
+
const pages = [];
|
|
35685
|
+
let cells = [];
|
|
35686
|
+
let cursor = top;
|
|
35687
|
+
const flush = () => {
|
|
35688
|
+
if (cells.length > 0) {
|
|
35689
|
+
pages.push({
|
|
35690
|
+
...blankPage(sheet, options),
|
|
35691
|
+
cells
|
|
35692
|
+
});
|
|
35693
|
+
cells = [];
|
|
35694
|
+
cursor = top;
|
|
35695
|
+
}
|
|
35696
|
+
};
|
|
35697
|
+
const push = (text, bold) => {
|
|
35698
|
+
fontManager.trackText(text);
|
|
35699
|
+
const lines = wrapTextLines(text, measure, Math.max(contentWidth - 6, 1));
|
|
35700
|
+
const height = Math.max(lines.length, 1) * lineHeight + 4;
|
|
35701
|
+
if (cursor - height < bottom) flush();
|
|
35702
|
+
cells.push({
|
|
35703
|
+
text,
|
|
35704
|
+
rect: {
|
|
35705
|
+
x: margins.left,
|
|
35706
|
+
y: cursor - height,
|
|
35707
|
+
width: contentWidth,
|
|
35708
|
+
height
|
|
35709
|
+
},
|
|
35710
|
+
fontFamily: options.defaultFontFamily,
|
|
35711
|
+
fontSize,
|
|
35712
|
+
bold,
|
|
35713
|
+
italic: false,
|
|
35714
|
+
strike: false,
|
|
35715
|
+
underline: false,
|
|
35716
|
+
textColor,
|
|
35717
|
+
fillColor: null,
|
|
35718
|
+
horizontalAlign: "left",
|
|
35719
|
+
verticalAlign: "top",
|
|
35720
|
+
wrapText: true,
|
|
35721
|
+
borders: {
|
|
35722
|
+
top: null,
|
|
35723
|
+
right: null,
|
|
35724
|
+
bottom: null,
|
|
35725
|
+
left: null
|
|
35726
|
+
},
|
|
35727
|
+
borderInsets: {
|
|
35728
|
+
top: 0,
|
|
35729
|
+
right: 0,
|
|
35730
|
+
bottom: 0,
|
|
35731
|
+
left: 0
|
|
35732
|
+
},
|
|
35733
|
+
colSpan: 1,
|
|
35734
|
+
rowSpan: 1,
|
|
35735
|
+
hyperlink: null,
|
|
35736
|
+
richText: null,
|
|
35737
|
+
textRotation: 0,
|
|
35738
|
+
indent: 0,
|
|
35739
|
+
textOverflowWidth: 0
|
|
35740
|
+
});
|
|
35741
|
+
cursor -= height;
|
|
35742
|
+
};
|
|
35743
|
+
push(`${sheet.name} — comments`, true);
|
|
35744
|
+
for (const comment of comments) {
|
|
35745
|
+
const author = comment.author ? ` (${comment.author})` : "";
|
|
35746
|
+
push(`${comment.ref}${author}: ${comment.text}`, false);
|
|
35747
|
+
}
|
|
35748
|
+
flush();
|
|
35749
|
+
return pages;
|
|
35750
|
+
}
|
|
35751
|
+
/**
|
|
35261
35752
|
* Build the LayoutPage for a single rowPage × colGroup combination.
|
|
35262
35753
|
*/
|
|
35263
35754
|
function buildPageLayout(ctx, rowPage, colGroup, currentPageCount, sheet, options, fontManager) {
|
|
35264
|
-
const { scaledColumnWidths, rowHeights, visibleRows, visibleCols, mergeMap, pageWidth, pageHeight, contentWidth, headerHeight, scaleFactor, margins } = ctx;
|
|
35755
|
+
const { scaledColumnWidths, rowHeights, visibleRows, visibleCols, mergeMap, pageWidth, pageHeight, contentWidth, availableHeight, headerHeight, scaleFactor, margins, headings } = ctx;
|
|
35265
35756
|
const cells = [];
|
|
35757
|
+
const gridLeft = margins.left + (headings?.gutterWidth ?? 0);
|
|
35758
|
+
const gridTop = pageHeight - margins.top - headerHeight - (headings?.bandHeight ?? 0);
|
|
35266
35759
|
const groupColWidths = colGroup.map((ci) => scaledColumnWidths[ci]);
|
|
35267
35760
|
const groupTotalWidth = groupColWidths.reduce((s, w) => s + w, 0);
|
|
35268
35761
|
const groupColOffsets = [];
|
|
35269
|
-
let gx =
|
|
35270
|
-
if (groupTotalWidth < contentWidth) gx =
|
|
35762
|
+
let gx = gridLeft;
|
|
35763
|
+
if (options.horizontalCentered && groupTotalWidth < contentWidth) gx = gridLeft + (contentWidth - groupTotalWidth) / 2;
|
|
35271
35764
|
for (const w of groupColWidths) {
|
|
35272
35765
|
groupColOffsets.push(gx);
|
|
35273
35766
|
gx += w;
|
|
35274
35767
|
}
|
|
35275
35768
|
const rowYPositions = [];
|
|
35276
35769
|
const pageRowHeights = [];
|
|
35277
|
-
let currentY =
|
|
35770
|
+
let currentY = gridTop;
|
|
35771
|
+
if (options.verticalCentered) {
|
|
35772
|
+
const pageTotalHeight = rowPage.reduce((sum, rowIdx) => sum + (rowHeights[rowIdx] ?? DEFAULT_ROW_HEIGHT * scaleFactor), 0);
|
|
35773
|
+
if (pageTotalHeight < availableHeight) currentY -= (availableHeight - pageTotalHeight) / 2;
|
|
35774
|
+
}
|
|
35278
35775
|
for (const rowIdx of rowPage) {
|
|
35279
35776
|
const rowH = rowHeights[rowIdx] ?? DEFAULT_ROW_HEIGHT * scaleFactor;
|
|
35280
35777
|
rowYPositions.push(currentY);
|
|
@@ -35314,7 +35811,10 @@ this.Documonster = this.Documonster || {};
|
|
|
35314
35811
|
const rectY = cellY - cellHeight;
|
|
35315
35812
|
cells.push(buildLayoutCell(cell, cellX, rectY, cellWidth, cellHeight, colSpan, rowSpan, options, fontManager, scaleFactor));
|
|
35316
35813
|
const layoutCell = cells[cells.length - 1];
|
|
35317
|
-
if (mergeInfo?.isMaster)
|
|
35814
|
+
if (mergeInfo?.isMaster) {
|
|
35815
|
+
propagateMergeBorders(layoutCell, mergeInfo, wsRowNumber, wsColNumber, sheet);
|
|
35816
|
+
if (options.blackAndWhite) layoutCell.borders = grayscaleBorders(layoutCell.borders);
|
|
35817
|
+
}
|
|
35318
35818
|
cellGrid.set(`${ri}:${gci}`, layoutCell);
|
|
35319
35819
|
}
|
|
35320
35820
|
}
|
|
@@ -35340,36 +35840,94 @@ this.Documonster = this.Documonster || {};
|
|
|
35340
35840
|
images: [],
|
|
35341
35841
|
charts: [],
|
|
35342
35842
|
scaleFactor,
|
|
35843
|
+
headings,
|
|
35844
|
+
commentBoxes: options.cellComments === "asDisplayed" ? placeCommentBoxes(sheet, colGroup.map((ci) => visibleCols[ci]), groupColOffsets, groupColWidths, rowPage.map((ri) => visibleRows[ri]), rowYPositions, pageRowHeights, options, scaleFactor) : void 0,
|
|
35343
35845
|
headerFooter: options.includeHeadersFooters ? sheet.headerFooter : void 0
|
|
35344
35846
|
};
|
|
35345
35847
|
}
|
|
35346
|
-
|
|
35347
|
-
|
|
35348
|
-
|
|
35349
|
-
|
|
35848
|
+
/**
|
|
35849
|
+
* Position each comment box on a page, for `cellComments: "asDisplayed"`.
|
|
35850
|
+
*
|
|
35851
|
+
* A comment is placed when its box overlaps the page's tracks. Fractional VML
|
|
35852
|
+
* coordinates are interpolated against the page's own column offsets and row
|
|
35853
|
+
* positions, so a box lands correctly even when the scale, hidden tracks or
|
|
35854
|
+
* repeated title bands have moved the grid around. Comments whose box falls
|
|
35855
|
+
* entirely outside the page are skipped, which keeps each one on a single page
|
|
35856
|
+
* rather than slicing it across the seam.
|
|
35857
|
+
*/
|
|
35858
|
+
function placeCommentBoxes(sheet, sheetCols, columnOffsets, columnWidths, sheetRows, rowYPositions, rowHeights, options, scaleFactor) {
|
|
35859
|
+
const comments = sheet.comments;
|
|
35860
|
+
if (!comments?.length || sheetCols.length === 0 || sheetRows.length === 0) return;
|
|
35861
|
+
const xAt = (col) => {
|
|
35862
|
+
const index = sheetCols.indexOf(Math.floor(col) + 1);
|
|
35863
|
+
if (index < 0) return;
|
|
35864
|
+
return columnOffsets[index] + (col - Math.floor(col)) * columnWidths[index];
|
|
35865
|
+
};
|
|
35866
|
+
const yAt = (row) => {
|
|
35867
|
+
const index = sheetRows.indexOf(Math.floor(row) + 1);
|
|
35868
|
+
if (index < 0) return;
|
|
35869
|
+
return rowYPositions[index] - (row - Math.floor(row)) * rowHeights[index];
|
|
35870
|
+
};
|
|
35871
|
+
const boxes = [];
|
|
35872
|
+
for (const comment of comments) {
|
|
35873
|
+
const anchor = comment.anchor ?? defaultCommentAnchor(comment.ref);
|
|
35874
|
+
if (!anchor) continue;
|
|
35875
|
+
const left = xAt(anchor.left);
|
|
35876
|
+
const right = xAt(anchor.right);
|
|
35877
|
+
const top = yAt(anchor.top);
|
|
35878
|
+
const bottom = yAt(anchor.bottom);
|
|
35879
|
+
if (left === void 0 || right === void 0 || top === void 0 || bottom === void 0) continue;
|
|
35880
|
+
const width = right - left;
|
|
35881
|
+
const height = top - bottom;
|
|
35882
|
+
if (width <= 0 || height <= 0) continue;
|
|
35883
|
+
const cell = parseCellRef(comment.ref);
|
|
35884
|
+
const markerCol = sheetCols.indexOf(cell.c + 1);
|
|
35885
|
+
const markerRow = sheetRows.indexOf(cell.r + 1);
|
|
35886
|
+
const author = comment.author ? `${comment.author}:\n` : "";
|
|
35887
|
+
boxes.push({
|
|
35888
|
+
rect: {
|
|
35889
|
+
x: left,
|
|
35890
|
+
y: bottom,
|
|
35891
|
+
width,
|
|
35892
|
+
height
|
|
35893
|
+
},
|
|
35894
|
+
text: `${author}${comment.text}`,
|
|
35895
|
+
fontSize: options.defaultFontSize * scaleFactor,
|
|
35896
|
+
marker: markerCol >= 0 && markerRow >= 0 ? {
|
|
35897
|
+
x: columnOffsets[markerCol] + columnWidths[markerCol],
|
|
35898
|
+
y: rowYPositions[markerRow],
|
|
35899
|
+
size: 4 * scaleFactor
|
|
35900
|
+
} : void 0
|
|
35901
|
+
});
|
|
35902
|
+
}
|
|
35903
|
+
return boxes.length > 0 ? boxes : void 0;
|
|
35904
|
+
}
|
|
35905
|
+
/**
|
|
35906
|
+
* Excel's default comment placement, used when the note carries no VML anchor.
|
|
35907
|
+
*
|
|
35908
|
+
* Mirrors the geometry Excel writes for a fresh comment: the box starts at the
|
|
35909
|
+
* commented cell's column, two rows above it, and spans two columns by four
|
|
35910
|
+
* rows.
|
|
35911
|
+
*/
|
|
35912
|
+
function defaultCommentAnchor(ref) {
|
|
35913
|
+
let cell;
|
|
35914
|
+
try {
|
|
35915
|
+
cell = parseCellRef(ref);
|
|
35916
|
+
} catch {
|
|
35917
|
+
return;
|
|
35918
|
+
}
|
|
35919
|
+
const left = cell.c + 6 / 68;
|
|
35920
|
+
const top = Math.max(cell.r - 2, 0) + 14 / 18;
|
|
35350
35921
|
return {
|
|
35351
|
-
|
|
35352
|
-
|
|
35353
|
-
|
|
35354
|
-
|
|
35355
|
-
firstPageNumber: sheet.pageSetup?.firstPageNumber,
|
|
35356
|
-
options,
|
|
35357
|
-
cells: [],
|
|
35358
|
-
width: pageWidth,
|
|
35359
|
-
height: pageHeight,
|
|
35360
|
-
sheetName: sheet.name,
|
|
35361
|
-
sheetCols: [],
|
|
35362
|
-
columnOffsets: [],
|
|
35363
|
-
columnWidths: [],
|
|
35364
|
-
sheetRows: [],
|
|
35365
|
-
rowYPositions: [],
|
|
35366
|
-
rowHeights: [],
|
|
35367
|
-
images: [],
|
|
35368
|
-
charts: [],
|
|
35369
|
-
scaleFactor: 1,
|
|
35370
|
-
headerFooter: options.includeHeadersFooters ? sheet.headerFooter : void 0
|
|
35922
|
+
left,
|
|
35923
|
+
top,
|
|
35924
|
+
right: left + 2,
|
|
35925
|
+
bottom: top + 4
|
|
35371
35926
|
};
|
|
35372
35927
|
}
|
|
35928
|
+
function createEmptyPage(sheet, options) {
|
|
35929
|
+
return blankPage(sheet, options);
|
|
35930
|
+
}
|
|
35373
35931
|
/**
|
|
35374
35932
|
* Parse a cell reference like "A1" into 0-indexed { c, r }.
|
|
35375
35933
|
*/
|
|
@@ -35426,63 +35984,87 @@ this.Documonster = this.Documonster || {};
|
|
|
35426
35984
|
return null;
|
|
35427
35985
|
}
|
|
35428
35986
|
}
|
|
35429
|
-
function computeColumnWidths(sheet, printRange) {
|
|
35987
|
+
function computeColumnWidths(sheet, printRange, titleBand) {
|
|
35430
35988
|
const bounds = sheet.bounds;
|
|
35431
35989
|
if (!(bounds.top > 0 && bounds.left > 0)) return {
|
|
35432
35990
|
columnWidths: [],
|
|
35433
|
-
visibleCols: []
|
|
35991
|
+
visibleCols: [],
|
|
35992
|
+
repeatColIndices: []
|
|
35434
35993
|
};
|
|
35435
35994
|
const startCol = printRange?.startCol ?? bounds.left;
|
|
35436
35995
|
const endCol = printRange?.endCol ?? bounds.right;
|
|
35437
35996
|
const columnWidths = [];
|
|
35438
35997
|
const visibleCols = [];
|
|
35439
|
-
|
|
35998
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
35999
|
+
const push = (c) => {
|
|
36000
|
+
if (emitted.has(c)) return;
|
|
35440
36001
|
const col = sheet.columns.get(c);
|
|
35441
|
-
if (col?.hidden)
|
|
36002
|
+
if (col?.hidden) return;
|
|
36003
|
+
emitted.add(c);
|
|
35442
36004
|
const pixelWidth = charWidthToPixel(col?.width ?? DEFAULT_COLUMN_WIDTH$1, 7);
|
|
35443
36005
|
const pointWidth = Math.max(pixelWidth * PX_TO_PT, MIN_COLUMN_WIDTH);
|
|
35444
36006
|
columnWidths.push(pointWidth);
|
|
35445
36007
|
visibleCols.push(c);
|
|
35446
|
-
}
|
|
36008
|
+
};
|
|
36009
|
+
const titleFullyInside = titleBand && titleBand.first >= startCol && titleBand.last <= endCol;
|
|
36010
|
+
if (titleBand && !titleFullyInside) for (let c = titleBand.first; c <= titleBand.last; c++) push(c);
|
|
36011
|
+
for (let c = startCol; c <= endCol; c++) push(c);
|
|
35447
36012
|
return {
|
|
35448
36013
|
columnWidths,
|
|
35449
|
-
visibleCols
|
|
36014
|
+
visibleCols,
|
|
36015
|
+
repeatColIndices: titleBand ? visibleCols.flatMap((c, i) => c >= titleBand.first && c <= titleBand.last ? [i] : []) : []
|
|
35450
36016
|
};
|
|
35451
36017
|
}
|
|
35452
|
-
|
|
36018
|
+
/**
|
|
36019
|
+
* Measure every printable row at 100% scale.
|
|
36020
|
+
*
|
|
36021
|
+
* Heights are deliberately unscaled: `countWrapLines` derives the wrapped line
|
|
36022
|
+
* count from ratios that are independent of the print scale, so a scaled height
|
|
36023
|
+
* is exactly `unscaled * scale`. Measuring once and multiplying avoids re-running
|
|
36024
|
+
* the most expensive step of layout for every probe of the fit solver.
|
|
36025
|
+
*/
|
|
36026
|
+
function computeRowHeights(sheet, printRange, fontManager, options, titleBand) {
|
|
35453
36027
|
const bounds = sheet.bounds;
|
|
35454
36028
|
if (bounds.top <= 0) return {
|
|
35455
36029
|
rowHeights: [],
|
|
35456
|
-
visibleRows: []
|
|
36030
|
+
visibleRows: [],
|
|
36031
|
+
repeatRowIndices: []
|
|
35457
36032
|
};
|
|
35458
36033
|
const startRow = printRange?.startRow ?? bounds.top;
|
|
35459
36034
|
const endRow = printRange?.endRow ?? bounds.bottom;
|
|
35460
36035
|
const rowHeights = [];
|
|
35461
36036
|
const visibleRows = [];
|
|
35462
|
-
|
|
36037
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
36038
|
+
const push = (r) => {
|
|
36039
|
+
if (emitted.has(r)) return;
|
|
35463
36040
|
const row = sheet.rows.get(r);
|
|
35464
|
-
if (row?.hidden)
|
|
36041
|
+
if (row?.hidden) return;
|
|
36042
|
+
emitted.add(r);
|
|
35465
36043
|
let height;
|
|
35466
36044
|
if (row?.height && row.customHeight) height = row.height;
|
|
35467
|
-
else if (row?.height) height = Math.max(row.height, autoRowHeight(row,
|
|
35468
|
-
else height = autoRowHeight(row,
|
|
35469
|
-
rowHeights.push(height
|
|
36045
|
+
else if (row?.height) height = Math.max(row.height, autoRowHeight(row, sheet, fontManager, options));
|
|
36046
|
+
else height = autoRowHeight(row, sheet, fontManager, options);
|
|
36047
|
+
rowHeights.push(height);
|
|
35470
36048
|
visibleRows.push(r);
|
|
35471
|
-
}
|
|
36049
|
+
};
|
|
36050
|
+
const titleFullyInside = titleBand && titleBand.first >= startRow && titleBand.last <= endRow;
|
|
36051
|
+
if (titleBand && !titleFullyInside) for (let r = titleBand.first; r <= titleBand.last; r++) push(r);
|
|
36052
|
+
for (let r = startRow; r <= endRow; r++) push(r);
|
|
35472
36053
|
return {
|
|
35473
36054
|
rowHeights,
|
|
35474
|
-
visibleRows
|
|
36055
|
+
visibleRows,
|
|
36056
|
+
repeatRowIndices: titleBand ? visibleRows.flatMap((r, i) => r >= titleBand.first && r <= titleBand.last ? [i] : []) : []
|
|
35475
36057
|
};
|
|
35476
36058
|
}
|
|
35477
36059
|
/**
|
|
35478
36060
|
* Compute the minimum row height required to display wrapped cell content.
|
|
35479
36061
|
* Returns at least `DEFAULT_ROW_HEIGHT`.
|
|
35480
36062
|
*/
|
|
35481
|
-
function autoRowHeight(row,
|
|
36063
|
+
function autoRowHeight(row, sheet, fontManager, options) {
|
|
35482
36064
|
let height = DEFAULT_ROW_HEIGHT;
|
|
35483
36065
|
if (row) for (const cell of row.cells.values()) {
|
|
35484
36066
|
const fontSize = getCellFontSize(cell);
|
|
35485
|
-
const wrapLineCount = countWrapLines(cell, fontSize,
|
|
36067
|
+
const wrapLineCount = countWrapLines(cell, fontSize, sheet, fontManager, options);
|
|
35486
36068
|
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
35487
36069
|
const borderTop = cell.style?.border?.top?.style ? borderStyleToLineWidth(cell.style.border.top.style) / 2 : 0;
|
|
35488
36070
|
const borderBottom = cell.style?.border?.bottom?.style ? borderStyleToLineWidth(cell.style.border.bottom.style) / 2 : 0;
|
|
@@ -35512,31 +36094,30 @@ this.Documonster = this.Documonster || {};
|
|
|
35512
36094
|
* Count the wrap-line count for a cell, using actual font measurements
|
|
35513
36095
|
* so row heights match the page renderer exactly.
|
|
35514
36096
|
*/
|
|
35515
|
-
function countWrapLines(cell, fontSize,
|
|
36097
|
+
function countWrapLines(cell, fontSize, sheet, fontManager, options) {
|
|
35516
36098
|
const text = typeof cell.text === "string" ? cell.text : String(cell.text ?? "");
|
|
35517
36099
|
const lineCount = Math.max(1, (text.match(/\n/g) ?? []).length + 1);
|
|
35518
36100
|
if (!cell.style?.alignment?.wrapText || text.length === 0) return lineCount;
|
|
35519
|
-
const
|
|
36101
|
+
const colPts = charWidthToPixel(sheet.columns.get(cell.col)?.width ?? DEFAULT_COLUMN_WIDTH$1, 7) * PX_TO_PT;
|
|
35520
36102
|
const indent = cell.style.alignment.indent ?? 0;
|
|
35521
36103
|
const borderLeft = cell.style?.border?.left?.style ? borderStyleToLineWidth(cell.style.border.left.style) / 2 : 0;
|
|
35522
36104
|
const borderRight = cell.style?.border?.right?.style ? borderStyleToLineWidth(cell.style.border.right.style) / 2 : 0;
|
|
35523
36105
|
const padding = 3 + borderLeft + (3 + borderRight) + indent * 10;
|
|
35524
|
-
const effectiveWidth = Math.max(
|
|
36106
|
+
const effectiveWidth = Math.max(colPts - padding, 1);
|
|
35525
36107
|
if (cell.type === PdfCellType.RichText) {
|
|
35526
36108
|
const value = cell.value;
|
|
35527
36109
|
if (value && typeof value === "object" && "richText" in value) {
|
|
35528
36110
|
const runs = value.richText;
|
|
35529
36111
|
if (runs.length > 0) {
|
|
35530
|
-
const wrappedCount = countRichTextWrapLines(text, runs,
|
|
36112
|
+
const wrappedCount = countRichTextWrapLines(text, runs, effectiveWidth, fontManager, options, cell.style?.font);
|
|
35531
36113
|
return Math.max(lineCount, wrappedCount);
|
|
35532
36114
|
}
|
|
35533
36115
|
}
|
|
35534
36116
|
}
|
|
35535
|
-
const scaledFontSize = fontSize * scaleFactor;
|
|
35536
36117
|
const fontProps = extractFontProperties(cell.style.font, options.defaultFontFamily, options.defaultFontSize);
|
|
35537
36118
|
const pdfFontName = resolvePdfFontName(fontProps.fontFamily, fontProps.bold, fontProps.italic);
|
|
35538
36119
|
const resourceName = fontManager.hasEmbeddedFont() ? fontManager.getEmbeddedResourceName() : fontManager.ensureFont(pdfFontName);
|
|
35539
|
-
const measure = (s) => fontManager.measureText(s, resourceName,
|
|
36120
|
+
const measure = (s) => fontManager.measureText(s, resourceName, fontSize);
|
|
35540
36121
|
const wrappedLines = wrapTextLines(text, measure, effectiveWidth);
|
|
35541
36122
|
return Math.max(lineCount, wrappedLines.length);
|
|
35542
36123
|
}
|
|
@@ -35545,7 +36126,7 @@ this.Documonster = this.Documonster || {};
|
|
|
35545
36126
|
* This mirrors the logic in wrapRichTextLines (page-renderer) so that
|
|
35546
36127
|
* the row height calculation matches the actual rendering.
|
|
35547
36128
|
*/
|
|
35548
|
-
function countRichTextWrapLines(text, runs,
|
|
36129
|
+
function countRichTextWrapLines(text, runs, effectiveWidth, fontManager, options, cellFont) {
|
|
35549
36130
|
const defaultFamily = cellFont?.name ?? options.defaultFontFamily;
|
|
35550
36131
|
const defaultSize = cellFont?.size ?? options.defaultFontSize;
|
|
35551
36132
|
const runForChar = [];
|
|
@@ -35569,7 +36150,7 @@ this.Documonster = this.Documonster || {};
|
|
|
35569
36150
|
size: run.font.size ?? cellFont?.size,
|
|
35570
36151
|
bold: run.font.bold ?? false,
|
|
35571
36152
|
italic: run.font.italic ?? false
|
|
35572
|
-
} : cellFont, defaultFamily, defaultSize).fontSize
|
|
36153
|
+
} : cellFont, defaultFamily, defaultSize).fontSize;
|
|
35573
36154
|
});
|
|
35574
36155
|
const measureRange = (start, end) => {
|
|
35575
36156
|
let width = 0;
|
|
@@ -35638,17 +36219,21 @@ this.Documonster = this.Documonster || {};
|
|
|
35638
36219
|
return Math.max(1, totalLines);
|
|
35639
36220
|
}
|
|
35640
36221
|
/**
|
|
35641
|
-
*
|
|
36222
|
+
* Translate manual break positions from sheet track numbers into indices of the
|
|
36223
|
+
* printed track list.
|
|
36224
|
+
*
|
|
36225
|
+
* A break sits *after* its track, so the following index starts a new page.
|
|
36226
|
+
* Shared by both axes; computed once per sheet because the fit solver paginates
|
|
36227
|
+
* many times and must not rebuild the lookup on every probe.
|
|
35642
36228
|
*/
|
|
35643
|
-
function
|
|
36229
|
+
function buildBreakSet(breakTracks, visibleTracks) {
|
|
35644
36230
|
const breaks = /* @__PURE__ */ new Set();
|
|
35645
|
-
|
|
35646
|
-
|
|
35647
|
-
|
|
35648
|
-
for (
|
|
35649
|
-
|
|
35650
|
-
|
|
35651
|
-
if (idx !== void 0) breaks.add(idx + 1);
|
|
36231
|
+
if (breakTracks.length === 0) return breaks;
|
|
36232
|
+
const trackToIndex = /* @__PURE__ */ new Map();
|
|
36233
|
+
for (let i = 0; i < visibleTracks.length; i++) trackToIndex.set(visibleTracks[i], i);
|
|
36234
|
+
for (const track of breakTracks) {
|
|
36235
|
+
const index = trackToIndex.get(track);
|
|
36236
|
+
if (index !== void 0) breaks.add(index + 1);
|
|
35652
36237
|
}
|
|
35653
36238
|
return breaks;
|
|
35654
36239
|
}
|
|
@@ -35676,89 +36261,73 @@ this.Documonster = this.Documonster || {};
|
|
|
35676
36261
|
}
|
|
35677
36262
|
return map;
|
|
35678
36263
|
}
|
|
35679
|
-
|
|
35680
|
-
|
|
36264
|
+
/**
|
|
36265
|
+
* Split a track list (rows or columns) into pages.
|
|
36266
|
+
*
|
|
36267
|
+
* One implementation serves both axes: heights against the available page
|
|
36268
|
+
* height, widths against the content width. The axes differ only in the overflow
|
|
36269
|
+
* tolerance, so `epsilon` is the sole parameter that distinguishes them —
|
|
36270
|
+
* columns need a small slack because scaled point widths accumulate rounding.
|
|
36271
|
+
*
|
|
36272
|
+
* `repeatIndices` are the tracks of a print-title band. They are re-emitted at
|
|
36273
|
+
* the start of every page after the first, which is why they are absolute
|
|
36274
|
+
* indices rather than a count: a band may sit in the middle of the printed range
|
|
36275
|
+
* (Excel allows `printTitlesColumn = "C:D"`).
|
|
36276
|
+
*
|
|
36277
|
+
* Manual breaks are honoured via `breaks`, holding the index that must start a
|
|
36278
|
+
* new page.
|
|
36279
|
+
*/
|
|
36280
|
+
function paginateTracks(sizes, available, repeatIndices, breaks, epsilon = 0) {
|
|
36281
|
+
if (sizes.length === 0) return [[]];
|
|
35681
36282
|
const pages = [];
|
|
35682
|
-
let
|
|
35683
|
-
let
|
|
36283
|
+
let current = [];
|
|
36284
|
+
let used = 0;
|
|
35684
36285
|
let isFirstPage = true;
|
|
35685
36286
|
let repeatedPrefixCount = 0;
|
|
35686
|
-
const
|
|
36287
|
+
const repeatSet = new Set(repeatIndices);
|
|
36288
|
+
const addRepeats = () => {
|
|
35687
36289
|
repeatedPrefixCount = 0;
|
|
35688
|
-
for (
|
|
35689
|
-
if (
|
|
35690
|
-
|
|
35691
|
-
|
|
36290
|
+
for (const index of repeatIndices) {
|
|
36291
|
+
if (index >= sizes.length) continue;
|
|
36292
|
+
if (used + sizes[index] > available + epsilon && current.length > 0) break;
|
|
36293
|
+
current.push(index);
|
|
36294
|
+
used += sizes[index];
|
|
35692
36295
|
repeatedPrefixCount++;
|
|
35693
36296
|
}
|
|
35694
36297
|
};
|
|
35695
|
-
for (let i = 0; i <
|
|
35696
|
-
const
|
|
35697
|
-
|
|
35698
|
-
|
|
35699
|
-
|
|
35700
|
-
|
|
35701
|
-
|
|
35702
|
-
if (!forceBreak && !isFirstPage &&
|
|
35703
|
-
|
|
35704
|
-
|
|
36298
|
+
for (let i = 0; i < sizes.length; i++) {
|
|
36299
|
+
const size = sizes[i];
|
|
36300
|
+
let breakConsumed = false;
|
|
36301
|
+
for (;;) {
|
|
36302
|
+
const forceBreak = !breakConsumed && breaks.has(i) && current.length > 0;
|
|
36303
|
+
if ((forceBreak || used + size > available + epsilon) && current.length > 0) {
|
|
36304
|
+
if (forceBreak) breakConsumed = true;
|
|
36305
|
+
if (!forceBreak && !isFirstPage && current.length === repeatedPrefixCount) {
|
|
36306
|
+
current = [];
|
|
36307
|
+
used = 0;
|
|
35705
36308
|
repeatedPrefixCount = 0;
|
|
35706
36309
|
continue;
|
|
35707
36310
|
}
|
|
35708
|
-
pages.push(
|
|
35709
|
-
|
|
35710
|
-
|
|
36311
|
+
pages.push(current);
|
|
36312
|
+
current = [];
|
|
36313
|
+
used = 0;
|
|
35711
36314
|
repeatedPrefixCount = 0;
|
|
35712
36315
|
isFirstPage = false;
|
|
35713
|
-
|
|
36316
|
+
addRepeats();
|
|
35714
36317
|
continue;
|
|
35715
36318
|
}
|
|
35716
|
-
if (
|
|
35717
|
-
|
|
35718
|
-
|
|
36319
|
+
if (isFirstPage || !repeatSet.has(i) || !current.includes(i)) {
|
|
36320
|
+
current.push(i);
|
|
36321
|
+
used += size;
|
|
35719
36322
|
}
|
|
35720
|
-
currentPage.push(i);
|
|
35721
|
-
currentPageHeight += rowHeight;
|
|
35722
36323
|
break;
|
|
35723
36324
|
}
|
|
35724
|
-
if (skipRepeatedRow) continue;
|
|
35725
|
-
}
|
|
35726
|
-
if (currentPage.length > 0) pages.push(currentPage);
|
|
35727
|
-
return pages.length > 0 ? pages : [[]];
|
|
35728
|
-
}
|
|
35729
|
-
/**
|
|
35730
|
-
* Split columns into groups for horizontal pagination.
|
|
35731
|
-
*/
|
|
35732
|
-
function paginateColumns(columnWidths, contentWidth, sheet, visibleCols) {
|
|
35733
|
-
if (columnWidths.length === 0) return [[]];
|
|
35734
|
-
const colBreaks = /* @__PURE__ */ new Set();
|
|
35735
|
-
const wsColBreaks = sheet.colBreaks ?? [];
|
|
35736
|
-
if (wsColBreaks.length > 0) {
|
|
35737
|
-
const colToIndex = /* @__PURE__ */ new Map();
|
|
35738
|
-
for (let i = 0; i < visibleCols.length; i++) colToIndex.set(visibleCols[i], i);
|
|
35739
|
-
for (const brk of wsColBreaks) {
|
|
35740
|
-
const idx = colToIndex.get(brk);
|
|
35741
|
-
if (idx !== void 0) colBreaks.add(idx + 1);
|
|
35742
|
-
}
|
|
35743
|
-
}
|
|
35744
|
-
const groups = [];
|
|
35745
|
-
let currentGroup = [];
|
|
35746
|
-
let currentWidth = 0;
|
|
35747
|
-
for (let i = 0; i < columnWidths.length; i++) {
|
|
35748
|
-
const colWidth = columnWidths[i];
|
|
35749
|
-
if ((colBreaks.has(i) && currentGroup.length > 0 || currentWidth + colWidth > contentWidth + .01) && currentGroup.length > 0) {
|
|
35750
|
-
groups.push(currentGroup);
|
|
35751
|
-
currentGroup = [];
|
|
35752
|
-
currentWidth = 0;
|
|
35753
|
-
}
|
|
35754
|
-
currentGroup.push(i);
|
|
35755
|
-
currentWidth += colWidth;
|
|
35756
36325
|
}
|
|
35757
|
-
if (
|
|
35758
|
-
return
|
|
36326
|
+
if (current.length > 0) pages.push(current);
|
|
36327
|
+
return pages;
|
|
35759
36328
|
}
|
|
35760
36329
|
function buildLayoutCell(cell, x, y, width, height, colSpan, rowSpan, options, fontManager, scaleFactor) {
|
|
35761
|
-
const text = cell
|
|
36330
|
+
const text = resolveErrorText(cell, options.errors);
|
|
35762
36331
|
const style = cell?.style ?? {};
|
|
35763
36332
|
const fontProps = extractFontProperties(style.font, options.defaultFontFamily, options.defaultFontSize);
|
|
35764
36333
|
const scaledFontSize = fontProps.fontSize * scaleFactor;
|
|
@@ -35769,7 +36338,9 @@ this.Documonster = this.Documonster || {};
|
|
|
35769
36338
|
fontManager.trackText(text);
|
|
35770
36339
|
}
|
|
35771
36340
|
const richText = buildRichTextRuns(cell, options, fontManager, scaleFactor, style.font);
|
|
35772
|
-
const
|
|
36341
|
+
const rawBorders = excelBordersToPdf(style.border);
|
|
36342
|
+
const borders = options.blackAndWhite ? grayscaleBorders(rawBorders) : rawBorders;
|
|
36343
|
+
const rawFill = excelFillToPdfColor(style.fill);
|
|
35773
36344
|
return {
|
|
35774
36345
|
text,
|
|
35775
36346
|
rect: {
|
|
@@ -35784,8 +36355,8 @@ this.Documonster = this.Documonster || {};
|
|
|
35784
36355
|
italic: fontProps.italic,
|
|
35785
36356
|
strike: fontProps.strike,
|
|
35786
36357
|
underline: fontProps.underline,
|
|
35787
|
-
textColor: fontProps.textColor,
|
|
35788
|
-
fillColor:
|
|
36358
|
+
textColor: options.blackAndWhite ? toGrayscale(fontProps.textColor) : fontProps.textColor,
|
|
36359
|
+
fillColor: options.blackAndWhite && rawFill !== null ? toGrayscale(rawFill) : rawFill,
|
|
35789
36360
|
horizontalAlign: resolveHorizontalAlign(style.alignment, cell?.type, cell?.result),
|
|
35790
36361
|
verticalAlign: excelVAlignToPdf(style.alignment),
|
|
35791
36362
|
wrapText: style.alignment?.wrapText ?? false,
|
|
@@ -35873,7 +36444,7 @@ this.Documonster = this.Documonster || {};
|
|
|
35873
36444
|
* - `br` — when present — locates the opposite corner, so the rect size is
|
|
35874
36445
|
* the difference.
|
|
35875
36446
|
* - `ext` — when present — overrides the size directly. Images use pixels
|
|
35876
|
-
* (px × 0.75 = pt); charts use EMU (EMU /
|
|
36447
|
+
* (px × 0.75 = pt); charts use EMU (EMU / 12700 = pt). The `extUnit`
|
|
35877
36448
|
* field disambiguates. Historical callers that omit `extUnit` keep
|
|
35878
36449
|
* the legacy px behaviour.
|
|
35879
36450
|
*
|
|
@@ -35899,8 +36470,8 @@ this.Documonster = this.Documonster || {};
|
|
|
35899
36470
|
const extUnit = range.extUnit ?? "px";
|
|
35900
36471
|
if (range.ext) {
|
|
35901
36472
|
if (extUnit === "emu") {
|
|
35902
|
-
width =
|
|
35903
|
-
height =
|
|
36473
|
+
width = emuToPt$1(range.ext.width) * scaleFactor;
|
|
36474
|
+
height = emuToPt$1(range.ext.height) * scaleFactor;
|
|
35904
36475
|
} else {
|
|
35905
36476
|
width = range.ext.width * .75 * scaleFactor;
|
|
35906
36477
|
height = range.ext.height * .75 * scaleFactor;
|
|
@@ -36006,13 +36577,16 @@ this.Documonster = this.Documonster || {};
|
|
|
36006
36577
|
continue;
|
|
36007
36578
|
}
|
|
36008
36579
|
const tl = chart.range.tl;
|
|
36580
|
+
const tlCol = (tl.nativeCol ?? tl.col ?? 0) + 1;
|
|
36009
36581
|
const tlRow = (tl.nativeRow ?? tl.row ?? 0) + 1;
|
|
36582
|
+
const band = layoutPages.filter((page) => page.sheetCols.includes(tlCol));
|
|
36583
|
+
const ordered = (band.length > 0 ? band : [...layoutPages]).sort((a, b) => (a.sheetRows[0] ?? 0) - (b.sheetRows[0] ?? 0));
|
|
36010
36584
|
let targetPage;
|
|
36011
|
-
for (let pi = 0; pi <
|
|
36012
|
-
const page =
|
|
36585
|
+
for (let pi = 0; pi < ordered.length; pi++) {
|
|
36586
|
+
const page = ordered[pi];
|
|
36013
36587
|
const lastPageRow = page.sheetRows[page.sheetRows.length - 1] ?? 0;
|
|
36014
|
-
if (lastPageRow >= tlRow - 1 && pi + 1 <
|
|
36015
|
-
targetPage =
|
|
36588
|
+
if (lastPageRow >= tlRow - 1 && pi + 1 < ordered.length) {
|
|
36589
|
+
targetPage = ordered[pi + 1];
|
|
36016
36590
|
break;
|
|
36017
36591
|
}
|
|
36018
36592
|
if (lastPageRow >= tlRow) {
|
|
@@ -36020,7 +36594,7 @@ this.Documonster = this.Documonster || {};
|
|
|
36020
36594
|
break;
|
|
36021
36595
|
}
|
|
36022
36596
|
}
|
|
36023
|
-
if (!targetPage) targetPage =
|
|
36597
|
+
if (!targetPage) targetPage = ordered[ordered.length - 1];
|
|
36024
36598
|
if (targetPage) {
|
|
36025
36599
|
const margins = targetPage.options.margins;
|
|
36026
36600
|
const headerH = targetPage.options.showSheetNames ? 20 : 0;
|
|
@@ -36155,10 +36729,28 @@ this.Documonster = this.Documonster || {};
|
|
|
36155
36729
|
italic: fontProps.italic,
|
|
36156
36730
|
strike: fontProps.strike,
|
|
36157
36731
|
underline: fontProps.underline,
|
|
36158
|
-
textColor: fontProps.textColor
|
|
36732
|
+
textColor: options.blackAndWhite ? toGrayscale(fontProps.textColor) : fontProps.textColor
|
|
36159
36733
|
};
|
|
36160
36734
|
});
|
|
36161
36735
|
}
|
|
36736
|
+
/**
|
|
36737
|
+
* Apply Excel's "Cell errors as" print option to a cell's display text.
|
|
36738
|
+
*
|
|
36739
|
+
* Error values reach the PDF model in two shapes: a plain error cell
|
|
36740
|
+
* (`PdfCellType.Error`) and a formula whose computed result is an error
|
|
36741
|
+
* (`PdfCellType.Formula` with an `{ error }` result). Both must be substituted.
|
|
36742
|
+
*/
|
|
36743
|
+
function resolveErrorText(cell, mode) {
|
|
36744
|
+
const text = cell?.text ?? "";
|
|
36745
|
+
if (mode === "displayed" || !cell) return text;
|
|
36746
|
+
if (!(cell.type === PdfCellType.Error || cell.type === PdfCellType.Formula && typeof cell.result === "object" && cell.result !== null && "error" in cell.result)) return text;
|
|
36747
|
+
switch (mode) {
|
|
36748
|
+
case "blank": return "";
|
|
36749
|
+
case "dash": return "--";
|
|
36750
|
+
case "NA": return "#N/A";
|
|
36751
|
+
default: return text;
|
|
36752
|
+
}
|
|
36753
|
+
}
|
|
36162
36754
|
var DEFAULT_COLUMN_WIDTH$1, DEFAULT_ROW_HEIGHT, MIN_COLUMN_WIDTH;
|
|
36163
36755
|
var init_layout_engine = __esmMin((() => {
|
|
36164
36756
|
init_font_manager();
|
|
@@ -36269,7 +36861,7 @@ this.Documonster = this.Documonster || {};
|
|
|
36269
36861
|
if (!chart.drawVector) continue;
|
|
36270
36862
|
const stream = new PdfContentStream();
|
|
36271
36863
|
const alphaValues = /* @__PURE__ */ new Set();
|
|
36272
|
-
const surface = createChartSurface(stream, fontManager, alphaValues);
|
|
36864
|
+
const surface = createChartSurface(stream, fontManager, alphaValues, page.options.blackAndWhite);
|
|
36273
36865
|
chart.drawVector(surface, constrainChartAspectRatio(chart.rect));
|
|
36274
36866
|
cache.set(chart, {
|
|
36275
36867
|
stream,
|
|
@@ -36363,6 +36955,7 @@ this.Documonster = this.Documonster || {};
|
|
|
36363
36955
|
try {
|
|
36364
36956
|
const { stream: contentStream, alphaValues } = renderPage(page, page.options, fontManager, totalPages);
|
|
36365
36957
|
const imageXObjects = /* @__PURE__ */ new Map();
|
|
36958
|
+
const grayscaleRaster = page.options.blackAndWhite;
|
|
36366
36959
|
let headerImageContentObjNum;
|
|
36367
36960
|
const headerContent = selectHeaderFooter(page, "header");
|
|
36368
36961
|
const footerContent = selectHeaderFooter(page, "footer");
|
|
@@ -36373,15 +36966,18 @@ this.Documonster = this.Documonster || {};
|
|
|
36373
36966
|
const alignWithMargins = page.headerFooter?.alignWithMargins !== false;
|
|
36374
36967
|
const leftEdge = alignWithMargins ? page.options.margins.left : 18;
|
|
36375
36968
|
const rightEdge = alignWithMargins ? page.width - page.options.margins.right : page.width - 18;
|
|
36969
|
+
const hfScale = page.headerFooter?.scaleWithDoc === false ? 1 : page.scaleFactor;
|
|
36376
36970
|
for (let index = 0; index < headerImages.length; index++) {
|
|
36377
36971
|
const headerImage = headerImages[index];
|
|
36378
36972
|
if (!activePositions.has(headerImage.position)) continue;
|
|
36379
36973
|
const imageName = `HfIm${index + 1}`;
|
|
36380
|
-
imageXObjects.set(imageName, getOrWriteImageObject(writer, imageObjectCache, headerImage.data, headerImage.format));
|
|
36974
|
+
imageXObjects.set(imageName, getOrWriteImageObject(writer, imageObjectCache, headerImage.data, headerImage.format, grayscaleRaster));
|
|
36975
|
+
const drawWidth = headerImage.width * hfScale;
|
|
36976
|
+
const drawHeight = headerImage.height * hfScale;
|
|
36381
36977
|
const placement = headerFooterImagePlacement(headerImage.position);
|
|
36382
|
-
const imageX = placement.section === "left" ? leftEdge : placement.section === "right" ? rightEdge -
|
|
36383
|
-
const imageY = placement.kind === "header" ? page.height - page.options.headerMargin -
|
|
36384
|
-
imageStream.drawImage(imageName, imageX, imageY,
|
|
36978
|
+
const imageX = placement.section === "left" ? leftEdge : placement.section === "right" ? rightEdge - drawWidth : (page.width - drawWidth) / 2;
|
|
36979
|
+
const imageY = placement.kind === "header" ? page.height - page.options.headerMargin - drawHeight : page.options.footerMargin;
|
|
36980
|
+
imageStream.drawImage(imageName, imageX, imageY, drawWidth, drawHeight);
|
|
36385
36981
|
}
|
|
36386
36982
|
headerImageContentObjNum = writer.allocObject();
|
|
36387
36983
|
writer.addStreamObject(headerImageContentObjNum, new PdfDict(), imageStream);
|
|
@@ -36389,7 +36985,7 @@ this.Documonster = this.Documonster || {};
|
|
|
36389
36985
|
if (page.images.length > 0) for (let imgIdx = 0; imgIdx < page.images.length; imgIdx++) {
|
|
36390
36986
|
const img = page.images[imgIdx];
|
|
36391
36987
|
const imgName = `Im${imgIdx + 1}`;
|
|
36392
|
-
const imgObjNum = getOrWriteImageObject(writer, imageObjectCache, img.data, img.format);
|
|
36988
|
+
const imgObjNum = getOrWriteImageObject(writer, imageObjectCache, img.data, img.format, grayscaleRaster);
|
|
36393
36989
|
imageXObjects.set(imgName, imgObjNum);
|
|
36394
36990
|
contentStream.drawImage(imgName, img.rect.x, img.rect.y, img.rect.width, img.rect.height);
|
|
36395
36991
|
}
|
|
@@ -36405,7 +37001,7 @@ this.Documonster = this.Documonster || {};
|
|
|
36405
37001
|
}
|
|
36406
37002
|
if (chart.raster) {
|
|
36407
37003
|
const imgName = `Im${++rasterCounter}`;
|
|
36408
|
-
const imgObjNum = getOrWriteImageObject(writer, imageObjectCache, chart.raster.data, chart.raster.format);
|
|
37004
|
+
const imgObjNum = getOrWriteImageObject(writer, imageObjectCache, chart.raster.data, chart.raster.format, grayscaleRaster);
|
|
36409
37005
|
imageXObjects.set(imgName, imgObjNum);
|
|
36410
37006
|
contentStream.drawImage(imgName, chart.rect.x, chart.rect.y, chart.rect.width, chart.rect.height);
|
|
36411
37007
|
}
|
|
@@ -36417,7 +37013,7 @@ this.Documonster = this.Documonster || {};
|
|
|
36417
37013
|
const wmResult = renderWatermark(wmContentStream, page, watermark, fontManager);
|
|
36418
37014
|
for (const alpha of wmResult.alphaValues) alphaValues.add(alpha);
|
|
36419
37015
|
for (const wmImg of wmResult.imageXObjects) {
|
|
36420
|
-
const imgObjNum = getOrWriteImageObject(writer, imageObjectCache, wmImg.data, wmImg.format);
|
|
37016
|
+
const imgObjNum = getOrWriteImageObject(writer, imageObjectCache, wmImg.data, wmImg.format, grayscaleRaster);
|
|
36421
37017
|
imageXObjects.set(wmImg.name, imgObjNum);
|
|
36422
37018
|
}
|
|
36423
37019
|
watermarkContentObjNum = writer.allocObject();
|
|
@@ -36469,6 +37065,7 @@ this.Documonster = this.Documonster || {};
|
|
|
36469
37065
|
height: page.height,
|
|
36470
37066
|
contentsRef,
|
|
36471
37067
|
resourcesRef: resourcesObjNum,
|
|
37068
|
+
transparencyGroup: alphaValues.size > 0,
|
|
36472
37069
|
annotRefs: annotRefs.length > 0 ? annotRefs : void 0
|
|
36473
37070
|
});
|
|
36474
37071
|
pageObjNums.push(pageObjNum);
|
|
@@ -36477,11 +37074,16 @@ this.Documonster = this.Documonster || {};
|
|
|
36477
37074
|
throw new PdfRenderError(`Failed to render page ${page.pageNumber} of "${page.sheetName}"`, { cause: err });
|
|
36478
37075
|
}
|
|
36479
37076
|
}
|
|
36480
|
-
function getOrWriteImageObject(writer, cache, data, format) {
|
|
36481
|
-
|
|
37077
|
+
function getOrWriteImageObject(writer, cache, data, format, grayscale = false) {
|
|
37078
|
+
let variants = cache.get(data);
|
|
37079
|
+
if (variants === void 0) {
|
|
37080
|
+
variants = /* @__PURE__ */ new Map();
|
|
37081
|
+
cache.set(data, variants);
|
|
37082
|
+
}
|
|
37083
|
+
const existing = variants.get(grayscale);
|
|
36482
37084
|
if (existing !== void 0) return existing;
|
|
36483
|
-
const objectNumber = writeImageXObject(writer, data, format);
|
|
36484
|
-
|
|
37085
|
+
const objectNumber = writeImageXObject(writer, data, format, grayscale);
|
|
37086
|
+
variants.set(grayscale, objectNumber);
|
|
36485
37087
|
return objectNumber;
|
|
36486
37088
|
}
|
|
36487
37089
|
function headerFooterImagePositions(content, kind) {
|
|
@@ -36536,6 +37138,97 @@ this.Documonster = this.Documonster || {};
|
|
|
36536
37138
|
return result;
|
|
36537
37139
|
}
|
|
36538
37140
|
/**
|
|
37141
|
+
* Parse an Excel "Columns to repeat at left" reference (`printTitlesColumn`)
|
|
37142
|
+
* into an absolute column band, e.g. `"A:B"` → `{ first: 1, last: 2 }`.
|
|
37143
|
+
*
|
|
37144
|
+
* Returns `null` for anything that is not a column range.
|
|
37145
|
+
*/
|
|
37146
|
+
function parseTitlesColumnBand(ref) {
|
|
37147
|
+
const match = ref.replace(/\$/g, "").toUpperCase().match(/^([A-Z]+)(?::([A-Z]+))?$/);
|
|
37148
|
+
if (!match) return null;
|
|
37149
|
+
const first = columnLettersToNumber(match[1]);
|
|
37150
|
+
const last = match[2] ? columnLettersToNumber(match[2]) : first;
|
|
37151
|
+
if (last < first) return null;
|
|
37152
|
+
return {
|
|
37153
|
+
first,
|
|
37154
|
+
last
|
|
37155
|
+
};
|
|
37156
|
+
}
|
|
37157
|
+
/**
|
|
37158
|
+
* Parse an Excel "Rows to repeat at top" reference (`printTitlesRow`) into an
|
|
37159
|
+
* absolute row band, e.g. `"1:3"` → `{ first: 1, last: 3 }`.
|
|
37160
|
+
*/
|
|
37161
|
+
function parseTitlesRowBand(ref) {
|
|
37162
|
+
const match = ref.replace(/\$/g, "").match(/^(\d+)(?::(\d+))?$/);
|
|
37163
|
+
if (!match) return null;
|
|
37164
|
+
const first = parseInt(match[1], 10);
|
|
37165
|
+
const last = match[2] ? parseInt(match[2], 10) : first;
|
|
37166
|
+
if (first < 1 || last < first) return null;
|
|
37167
|
+
return {
|
|
37168
|
+
first,
|
|
37169
|
+
last
|
|
37170
|
+
};
|
|
37171
|
+
}
|
|
37172
|
+
function columnLettersToNumber(letters) {
|
|
37173
|
+
let n = 0;
|
|
37174
|
+
for (let i = 0; i < letters.length; i++) n = n * 26 + (letters.charCodeAt(i) - 64);
|
|
37175
|
+
return n;
|
|
37176
|
+
}
|
|
37177
|
+
function normalizePageOrder(value) {
|
|
37178
|
+
return value === "overThenDown" ? "overThenDown" : "downThenOver";
|
|
37179
|
+
}
|
|
37180
|
+
/** Excel writes `None` / `asDisplayed` / `atEnd`. */
|
|
37181
|
+
function normalizeCommentMode(value) {
|
|
37182
|
+
if (value === "atEnd" || value === "asDisplayed") return value;
|
|
37183
|
+
return "none";
|
|
37184
|
+
}
|
|
37185
|
+
function normalizeErrorMode(value) {
|
|
37186
|
+
switch (value) {
|
|
37187
|
+
case "blank":
|
|
37188
|
+
case "dash":
|
|
37189
|
+
case "NA": return value;
|
|
37190
|
+
default: return "displayed";
|
|
37191
|
+
}
|
|
37192
|
+
}
|
|
37193
|
+
/**
|
|
37194
|
+
* Reconcile documonster's scaling options with Excel's two mutually exclusive
|
|
37195
|
+
* scaling modes (Page Setup → Scaling):
|
|
37196
|
+
*
|
|
37197
|
+
* - **"Adjust to N %"** — `pageSetup.scale`, with `pageSetup.fitToPage` off.
|
|
37198
|
+
* - **"Fit to N pages wide by M tall"** — `pageSetup.fitToWidth` /
|
|
37199
|
+
* `fitToHeight`, with `pageSetup.fitToPage` on. Excel ignores `scale` here.
|
|
37200
|
+
*
|
|
37201
|
+
* Neither mode ever enlarges: Excel caps fit-to-page scaling at 100%.
|
|
37202
|
+
*
|
|
37203
|
+
* `PdfExportOptions.fitToPage` is documonster's own convenience default
|
|
37204
|
+
* ("shrink to one page wide"). It only applies when neither the caller nor the
|
|
37205
|
+
* sheet expresses a scaling intent, so a sheet that asks for 80% is not
|
|
37206
|
+
* shrunk twice.
|
|
37207
|
+
*/
|
|
37208
|
+
function resolveScaling(options, ps) {
|
|
37209
|
+
const clampPages = (n) => n === void 0 || !Number.isFinite(n) || n <= 0 ? 0 : Math.floor(n);
|
|
37210
|
+
const callerFit = options?.fitToWidth !== void 0 || options?.fitToHeight !== void 0;
|
|
37211
|
+
const sheetFitMode = ps?.fitToPage === true;
|
|
37212
|
+
let fitToWidth = 0;
|
|
37213
|
+
let fitToHeight = 0;
|
|
37214
|
+
if (callerFit) {
|
|
37215
|
+
fitToWidth = clampPages(options?.fitToWidth);
|
|
37216
|
+
fitToHeight = clampPages(options?.fitToHeight);
|
|
37217
|
+
} else if (sheetFitMode && options?.scale === void 0 && options?.fitToPage !== false) {
|
|
37218
|
+
fitToWidth = clampPages(ps?.fitToWidth);
|
|
37219
|
+
fitToHeight = clampPages(ps?.fitToHeight);
|
|
37220
|
+
}
|
|
37221
|
+
const sheetScalePercent = !sheetFitMode && ps?.scale !== void 0 && ps.scale !== 100 ? ps.scale / 100 : void 0;
|
|
37222
|
+
const scale = Math.max(.1, Math.min(4, options?.scale ?? sheetScalePercent ?? 1));
|
|
37223
|
+
const hasFitConstraint = fitToWidth > 0 || fitToHeight > 0;
|
|
37224
|
+
return {
|
|
37225
|
+
fitToPage: options?.fitToPage ?? !(hasFitConstraint || sheetScalePercent !== void 0 || callerFit),
|
|
37226
|
+
scale,
|
|
37227
|
+
fitToWidth,
|
|
37228
|
+
fitToHeight
|
|
37229
|
+
};
|
|
37230
|
+
}
|
|
37231
|
+
/**
|
|
36539
37232
|
* Resolve user options with defaults.
|
|
36540
37233
|
*/
|
|
36541
37234
|
function resolveOptions(options, sheet) {
|
|
@@ -36544,26 +37237,46 @@ this.Documonster = this.Documonster || {};
|
|
|
36544
37237
|
const chartsheetOrientation = sheet && isPdfChartsheet(sheet) ? sheet.orientation : void 0;
|
|
36545
37238
|
const orientation = options?.orientation ?? chartsheetOrientation ?? (ps?.orientation === "landscape" ? "landscape" : "portrait");
|
|
36546
37239
|
const margins = resolveMargins(options?.margins, ps?.margins);
|
|
36547
|
-
const
|
|
37240
|
+
const blackAndWhite = options?.blackAndWhite ?? ps?.blackAndWhite ?? false;
|
|
37241
|
+
const gridLineColorRaw = argbToPdfColor(options?.gridLineColor ?? "FFD0D0D0") ?? {
|
|
36548
37242
|
r: .816,
|
|
36549
37243
|
g: .816,
|
|
36550
37244
|
b: .816
|
|
36551
37245
|
};
|
|
36552
|
-
|
|
36553
|
-
|
|
36554
|
-
|
|
36555
|
-
|
|
36556
|
-
|
|
37246
|
+
const gridLineColor = blackAndWhite ? toGrayscale(gridLineColorRaw) : gridLineColorRaw;
|
|
37247
|
+
const isWorksheet = Boolean(sheet) && !isPdfChartsheet(sheet);
|
|
37248
|
+
const countToBand = (n) => typeof n === "number" && n > 0 ? {
|
|
37249
|
+
first: 1,
|
|
37250
|
+
last: n
|
|
37251
|
+
} : false;
|
|
37252
|
+
let repeatRows = false;
|
|
37253
|
+
if (options?.repeatRows !== void 0) repeatRows = countToBand(options.repeatRows);
|
|
37254
|
+
else if (isWorksheet && ps?.printTitlesRow) repeatRows = parseTitlesRowBand(ps.printTitlesRow) ?? false;
|
|
37255
|
+
let repeatCols = false;
|
|
37256
|
+
if (options?.repeatCols !== void 0) repeatCols = countToBand(options.repeatCols);
|
|
37257
|
+
else if (isWorksheet && ps?.printTitlesColumn) repeatCols = parseTitlesColumnBand(ps.printTitlesColumn) ?? false;
|
|
37258
|
+
const { fitToPage, scale, fitToWidth, fitToHeight } = resolveScaling(options, ps);
|
|
36557
37259
|
return {
|
|
36558
37260
|
pageSize,
|
|
36559
37261
|
orientation,
|
|
36560
37262
|
margins,
|
|
36561
37263
|
ignorePrintArea: options?.ignorePrintArea ?? false,
|
|
36562
|
-
fitToPage
|
|
36563
|
-
scale
|
|
37264
|
+
fitToPage,
|
|
37265
|
+
scale,
|
|
37266
|
+
fitToWidth,
|
|
37267
|
+
fitToHeight,
|
|
36564
37268
|
showGridLines: options?.showGridLines ?? ps?.showGridLines ?? false,
|
|
36565
37269
|
gridLineColor,
|
|
37270
|
+
showRowColHeaders: options?.showRowColHeaders ?? ps?.showRowColHeaders ?? false,
|
|
37271
|
+
horizontalCentered: options?.horizontalCentered ?? ps?.horizontalCentered ?? false,
|
|
37272
|
+
verticalCentered: options?.verticalCentered ?? ps?.verticalCentered ?? false,
|
|
37273
|
+
pageOrder: options?.pageOrder ?? normalizePageOrder(ps?.pageOrder),
|
|
37274
|
+
blackAndWhite,
|
|
37275
|
+
draft: options?.draft ?? ps?.draft ?? false,
|
|
37276
|
+
errors: options?.errors ?? normalizeErrorMode(ps?.errors),
|
|
37277
|
+
cellComments: options?.cellComments ?? normalizeCommentMode(ps?.cellComments),
|
|
36566
37278
|
repeatRows,
|
|
37279
|
+
repeatCols,
|
|
36567
37280
|
defaultFontFamily: options?.defaultFontFamily ?? "Helvetica",
|
|
36568
37281
|
defaultFontSize: options?.defaultFontSize ?? 11,
|
|
36569
37282
|
showSheetNames: options?.showSheetNames ?? false,
|
|
@@ -45944,6 +46657,9 @@ this.Documonster = this.Documonster || {};
|
|
|
45944
46657
|
function cellHyperlink(c) {
|
|
45945
46658
|
return c._value.hyperlink;
|
|
45946
46659
|
}
|
|
46660
|
+
function cellComment(c) {
|
|
46661
|
+
return c._comment;
|
|
46662
|
+
}
|
|
45947
46663
|
function cellText(c) {
|
|
45948
46664
|
return c._value.toString();
|
|
45949
46665
|
}
|
|
@@ -63776,7 +64492,7 @@ this.Documonster = this.Documonster || {};
|
|
|
63776
64492
|
*/
|
|
63777
64493
|
async function excelToPdf(workbook, options) {
|
|
63778
64494
|
options?.recalculate?.(workbook);
|
|
63779
|
-
return exportPdf(await excelWorkbookToPdf(workbook), options);
|
|
64495
|
+
return exportPdf(await excelWorkbookToPdf(workbook, options), options);
|
|
63780
64496
|
}
|
|
63781
64497
|
/**
|
|
63782
64498
|
* Render a single {@link Chart} to a standalone one-page PDF.
|
|
@@ -63879,9 +64595,9 @@ this.Documonster = this.Documonster || {};
|
|
|
63879
64595
|
* Chartsheets without an orderNo fall to the end, mirroring how Excel
|
|
63880
64596
|
* treats sheets with missing tab positions.
|
|
63881
64597
|
*/
|
|
63882
|
-
async function excelWorkbookToPdf(workbook) {
|
|
63883
|
-
const worksheetResults = await Promise.all(getWorksheets(workbook).map((ws) => convertSheet(ws, workbook)));
|
|
63884
|
-
const chartsheetResults = await Promise.all(getChartsheets(workbook).map((cs) => convertChartsheet(cs, workbook)));
|
|
64598
|
+
async function excelWorkbookToPdf(workbook, options) {
|
|
64599
|
+
const worksheetResults = await Promise.all(getWorksheets(workbook).map((ws) => convertSheet(ws, workbook, options)));
|
|
64600
|
+
const chartsheetResults = await Promise.all(getChartsheets(workbook).map((cs) => convertChartsheet(cs, workbook, options)));
|
|
63885
64601
|
const combined = [...worksheetResults, ...chartsheetResults];
|
|
63886
64602
|
combined.sort((a, b) => (a.orderNo ?? Number.POSITIVE_INFINITY) - (b.orderNo ?? Number.POSITIVE_INFINITY));
|
|
63887
64603
|
return {
|
|
@@ -63894,7 +64610,7 @@ this.Documonster = this.Documonster || {};
|
|
|
63894
64610
|
sheets: combined
|
|
63895
64611
|
};
|
|
63896
64612
|
}
|
|
63897
|
-
async function convertSheet(ws, workbook) {
|
|
64613
|
+
async function convertSheet(ws, workbook, options) {
|
|
63898
64614
|
const dimensions = getSheetDimensions(ws);
|
|
63899
64615
|
const hasData = dimensions && dimensions.top > 0 && dimensions.left > 0;
|
|
63900
64616
|
const bounds = hasData ? {
|
|
@@ -63971,16 +64687,30 @@ this.Documonster = this.Documonster || {};
|
|
|
63971
64687
|
footer: ps.margins.footer
|
|
63972
64688
|
} : void 0,
|
|
63973
64689
|
scale: ps.scale,
|
|
64690
|
+
fitToPage: ps.fitToPage,
|
|
64691
|
+
fitToWidth: ps.fitToWidth,
|
|
64692
|
+
fitToHeight: ps.fitToHeight,
|
|
63974
64693
|
printTitlesRow: ps.printTitlesRow,
|
|
64694
|
+
printTitlesColumn: ps.printTitlesColumn,
|
|
63975
64695
|
showGridLines: ps.showGridLines,
|
|
64696
|
+
showRowColHeaders: ps.showRowColHeaders,
|
|
63976
64697
|
printArea: ps.printArea,
|
|
63977
|
-
firstPageNumber: ps.useFirstPageNumber === false ? void 0 : ps.firstPageNumber
|
|
64698
|
+
firstPageNumber: ps.useFirstPageNumber === false ? void 0 : ps.firstPageNumber,
|
|
64699
|
+
pageOrder: ps.pageOrder,
|
|
64700
|
+
blackAndWhite: ps.blackAndWhite,
|
|
64701
|
+
draft: ps.draft,
|
|
64702
|
+
errors: ps.errors,
|
|
64703
|
+
cellComments: ps.cellComments,
|
|
64704
|
+
horizontalCentered: ps.horizontalCentered,
|
|
64705
|
+
verticalCentered: ps.verticalCentered
|
|
63978
64706
|
} : void 0;
|
|
63979
64707
|
const rowBreaks = ws.rowBreaks?.map((b) => b.id);
|
|
63980
64708
|
const colBreaks = ws.colBreaks?.map((b) => b.id);
|
|
63981
|
-
const
|
|
63982
|
-
const
|
|
63983
|
-
const
|
|
64709
|
+
const comments = collectComments(ws);
|
|
64710
|
+
const draftQuality = options?.draft ?? ws.pageSetup?.draft ?? false;
|
|
64711
|
+
const images = draftQuality ? void 0 : collectImages(ws, workbook);
|
|
64712
|
+
const charts = draftQuality ? void 0 : await collectCharts(ws);
|
|
64713
|
+
const sparklineCharts = draftQuality ? void 0 : collectSparklineCharts(ws);
|
|
63984
64714
|
const allCharts = charts ? sparklineCharts ? [...charts, ...sparklineCharts] : charts : sparklineCharts || void 0;
|
|
63985
64715
|
const anchoredRanges = [];
|
|
63986
64716
|
if (images) for (const img of images) anchoredRanges.push(img.range);
|
|
@@ -64023,14 +64753,74 @@ this.Documonster = this.Documonster || {};
|
|
|
64023
64753
|
rows,
|
|
64024
64754
|
merges,
|
|
64025
64755
|
pageSetup,
|
|
64026
|
-
headerFooter: convertHeaderFooter(ws, workbook),
|
|
64756
|
+
headerFooter: convertHeaderFooter(ws, workbook, draftQuality),
|
|
64027
64757
|
rowBreaks,
|
|
64028
64758
|
colBreaks,
|
|
64029
64759
|
images,
|
|
64030
|
-
charts: allCharts
|
|
64760
|
+
charts: allCharts,
|
|
64761
|
+
comments
|
|
64031
64762
|
};
|
|
64032
64763
|
}
|
|
64033
|
-
|
|
64764
|
+
/**
|
|
64765
|
+
* Collect every comment on a worksheet, in row-major order.
|
|
64766
|
+
*
|
|
64767
|
+
* Two sources are merged: classic notes stored on the cell (`_comment`, the
|
|
64768
|
+
* VML-backed kind) and Office 365 threaded comments held on the sheet. Only the
|
|
64769
|
+
* text and author are needed — the printed form is an end-of-sheet list, not a
|
|
64770
|
+
* positioned box — so the VML geometry is deliberately ignored.
|
|
64771
|
+
*/
|
|
64772
|
+
function collectComments(ws) {
|
|
64773
|
+
const collected = [];
|
|
64774
|
+
for (const row of ws._rows ?? []) {
|
|
64775
|
+
if (!row) continue;
|
|
64776
|
+
for (const cell of row.cells ?? []) {
|
|
64777
|
+
if (!cell) continue;
|
|
64778
|
+
const note = cellComment(cell);
|
|
64779
|
+
const text = noteText(note?.note);
|
|
64780
|
+
if (text) collected.push({
|
|
64781
|
+
ref: cell.address,
|
|
64782
|
+
text,
|
|
64783
|
+
author: note?.author,
|
|
64784
|
+
anchor: parseVmlAnchor(note?.note)
|
|
64785
|
+
});
|
|
64786
|
+
}
|
|
64787
|
+
}
|
|
64788
|
+
for (const entry of ws.threadedComments ?? []) {
|
|
64789
|
+
const text = entry.comment.text?.trim();
|
|
64790
|
+
if (text) collected.push({
|
|
64791
|
+
ref: entry.ref,
|
|
64792
|
+
text,
|
|
64793
|
+
author: entry.comment.personId
|
|
64794
|
+
});
|
|
64795
|
+
}
|
|
64796
|
+
return collected.length > 0 ? collected : void 0;
|
|
64797
|
+
}
|
|
64798
|
+
/**
|
|
64799
|
+
* Decode a VML comment anchor into fractional sheet coordinates.
|
|
64800
|
+
*
|
|
64801
|
+
* `<x:Anchor>` holds eight integers: for each edge a column/row index followed
|
|
64802
|
+
* by an offset into it, measured in 1/68 of a column and 1/18 of a row (the
|
|
64803
|
+
* reciprocals Excel uses when it writes the value). Returns `undefined` when the
|
|
64804
|
+
* note has no anchor, so callers fall back to Excel's default placement.
|
|
64805
|
+
*/
|
|
64806
|
+
function parseVmlAnchor(note) {
|
|
64807
|
+
if (!note || typeof note === "string" || typeof note.anchor !== "string") return;
|
|
64808
|
+
const parts = note.anchor.split(",").map((p) => Number(p.trim()));
|
|
64809
|
+
if (parts.length !== 8 || parts.some((n) => !Number.isFinite(n))) return;
|
|
64810
|
+
const [l, lf, t, tf, r, rf, b, bf] = parts;
|
|
64811
|
+
return {
|
|
64812
|
+
left: l + lf / 68,
|
|
64813
|
+
top: t + tf / 18,
|
|
64814
|
+
right: r + rf / 68,
|
|
64815
|
+
bottom: b + bf / 18
|
|
64816
|
+
};
|
|
64817
|
+
}
|
|
64818
|
+
/** Flatten a note body, which may be plain text or a rich `NoteConfig`. */
|
|
64819
|
+
function noteText(note) {
|
|
64820
|
+
if (typeof note === "string") return note.trim() || void 0;
|
|
64821
|
+
if (note && typeof note === "object" && Array.isArray(note.texts)) return note.texts.map((t) => typeof t === "string" ? t : t.text ?? "").join("").trim() || void 0;
|
|
64822
|
+
}
|
|
64823
|
+
function convertHeaderFooter(ws, workbook, draftQuality = false) {
|
|
64034
64824
|
const source = ws.headerFooter;
|
|
64035
64825
|
const fields = [
|
|
64036
64826
|
source.oddHeader,
|
|
@@ -64040,7 +64830,7 @@ this.Documonster = this.Documonster || {};
|
|
|
64040
64830
|
source.firstHeader,
|
|
64041
64831
|
source.firstFooter
|
|
64042
64832
|
];
|
|
64043
|
-
const images = convertHeaderFooterImages(ws, workbook);
|
|
64833
|
+
const images = draftQuality ? [] : convertHeaderFooterImages(ws, workbook);
|
|
64044
64834
|
if (!fields.some(Boolean) && images.length === 0) return;
|
|
64045
64835
|
return {
|
|
64046
64836
|
differentFirst: source.differentFirst,
|
|
@@ -64797,11 +65587,12 @@ this.Documonster = this.Documonster || {};
|
|
|
64797
65587
|
* (matches what Excel prints for a chartsheet whose chart was deleted
|
|
64798
65588
|
* but the sheet kept).
|
|
64799
65589
|
*/
|
|
64800
|
-
async function convertChartsheet(cs, workbook) {
|
|
65590
|
+
async function convertChartsheet(cs, workbook, options) {
|
|
64801
65591
|
const classicModel = chartsheetChartModel(cs);
|
|
64802
65592
|
const chartExModel = chartsheetChartExModel(cs);
|
|
65593
|
+
const draftQuality = options?.draft ?? chartsheetPageSetup(cs)?.draft ?? false;
|
|
64803
65594
|
let chart = {};
|
|
64804
|
-
if (classicModel) {
|
|
65595
|
+
if (!draftQuality && classicModel) {
|
|
64805
65596
|
const model = classicModel;
|
|
64806
65597
|
chart = { drawVector: (surface, rect) => {
|
|
64807
65598
|
drawChartPdf(surface, model, {
|
|
@@ -64811,7 +65602,7 @@ this.Documonster = this.Documonster || {};
|
|
|
64811
65602
|
height: rect.height
|
|
64812
65603
|
});
|
|
64813
65604
|
} };
|
|
64814
|
-
} else if (chartExModel) {
|
|
65605
|
+
} else if (!draftQuality && chartExModel) {
|
|
64815
65606
|
if (canRenderChartExAsVectorPdf(chartExModel)) {
|
|
64816
65607
|
const model = chartExModel;
|
|
64817
65608
|
chart = { drawVector: (surface, rect) => {
|
|
@@ -64839,6 +65630,8 @@ this.Documonster = this.Documonster || {};
|
|
|
64839
65630
|
orientation: ps?.orientation,
|
|
64840
65631
|
paperSize: ps?.paperSize,
|
|
64841
65632
|
showGridLines: false,
|
|
65633
|
+
blackAndWhite: ps?.blackAndWhite,
|
|
65634
|
+
draft: ps?.draft,
|
|
64842
65635
|
firstPageNumber: ps?.useFirstPageNumber === false ? void 0 : ps?.firstPageNumber,
|
|
64843
65636
|
margins: margins ? {
|
|
64844
65637
|
left: margins.left ?? margins.l ?? .7,
|
|
@@ -64857,7 +65650,7 @@ this.Documonster = this.Documonster || {};
|
|
|
64857
65650
|
orientation,
|
|
64858
65651
|
chart,
|
|
64859
65652
|
pageSetup,
|
|
64860
|
-
headerFooter: convertHeaderFooterModel(chartsheetModel(cs).headerFooter, convertChartsheetHeaderImages(chartsheetModel(cs).headerImages, workbook))
|
|
65653
|
+
headerFooter: convertHeaderFooterModel(chartsheetModel(cs).headerFooter, draftQuality ? [] : convertChartsheetHeaderImages(chartsheetModel(cs).headerImages, workbook))
|
|
64861
65654
|
};
|
|
64862
65655
|
}
|
|
64863
65656
|
function convertChartsheetHeaderImages(images, workbook) {
|