@cj-tech-master/excelts 8.1.2 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/README_zh.md +2 -2
- package/dist/browser/modules/excel/cell.js +11 -7
- package/dist/browser/modules/excel/column.js +7 -6
- package/dist/browser/modules/excel/row.js +5 -1
- package/dist/browser/modules/excel/stream/worksheet-reader.js +3 -2
- package/dist/browser/modules/excel/utils/cell-format.js +64 -2
- package/dist/browser/modules/pdf/excel-bridge.d.ts +4 -3
- package/dist/browser/modules/pdf/excel-bridge.js +18 -5
- package/dist/browser/modules/pdf/index.d.ts +3 -3
- package/dist/browser/modules/pdf/index.js +3 -3
- package/dist/browser/modules/pdf/pdf.d.ts +7 -6
- package/dist/browser/modules/pdf/pdf.js +7 -6
- package/dist/browser/modules/pdf/reader/pdf-reader.d.ts +8 -7
- package/dist/browser/modules/pdf/reader/pdf-reader.js +81 -74
- package/dist/browser/modules/pdf/render/constants.d.ts +30 -0
- package/dist/browser/modules/pdf/render/constants.js +30 -0
- package/dist/browser/modules/pdf/render/layout-engine.d.ts +2 -1
- package/dist/browser/modules/pdf/render/layout-engine.js +359 -156
- package/dist/browser/modules/pdf/render/page-renderer.d.ts +2 -2
- package/dist/browser/modules/pdf/render/page-renderer.js +245 -107
- package/dist/browser/modules/pdf/render/pdf-exporter.d.ts +3 -2
- package/dist/browser/modules/pdf/render/pdf-exporter.js +145 -105
- package/dist/browser/modules/pdf/render/style-converter.js +27 -26
- package/dist/browser/modules/pdf/types.d.ts +8 -0
- package/dist/browser/utils/utils.base.d.ts +5 -0
- package/dist/browser/utils/utils.base.js +10 -0
- package/dist/cjs/modules/excel/cell.js +11 -7
- package/dist/cjs/modules/excel/column.js +7 -6
- package/dist/cjs/modules/excel/row.js +5 -1
- package/dist/cjs/modules/excel/stream/worksheet-reader.js +3 -2
- package/dist/cjs/modules/excel/utils/cell-format.js +64 -2
- package/dist/cjs/modules/pdf/excel-bridge.js +18 -5
- package/dist/cjs/modules/pdf/index.js +3 -3
- package/dist/cjs/modules/pdf/pdf.js +7 -6
- package/dist/cjs/modules/pdf/reader/pdf-reader.js +81 -74
- package/dist/cjs/modules/pdf/render/constants.js +33 -0
- package/dist/cjs/modules/pdf/render/layout-engine.js +359 -156
- package/dist/cjs/modules/pdf/render/page-renderer.js +245 -107
- package/dist/cjs/modules/pdf/render/pdf-exporter.js +145 -105
- package/dist/cjs/modules/pdf/render/style-converter.js +27 -26
- package/dist/cjs/utils/utils.base.js +11 -0
- package/dist/esm/modules/excel/cell.js +11 -7
- package/dist/esm/modules/excel/column.js +7 -6
- package/dist/esm/modules/excel/row.js +5 -1
- package/dist/esm/modules/excel/stream/worksheet-reader.js +3 -2
- package/dist/esm/modules/excel/utils/cell-format.js +64 -2
- package/dist/esm/modules/pdf/excel-bridge.js +18 -5
- package/dist/esm/modules/pdf/index.js +3 -3
- package/dist/esm/modules/pdf/pdf.js +7 -6
- package/dist/esm/modules/pdf/reader/pdf-reader.js +81 -74
- package/dist/esm/modules/pdf/render/constants.js +30 -0
- package/dist/esm/modules/pdf/render/layout-engine.js +359 -156
- package/dist/esm/modules/pdf/render/page-renderer.js +245 -107
- package/dist/esm/modules/pdf/render/pdf-exporter.js +145 -105
- package/dist/esm/modules/pdf/render/style-converter.js +27 -26
- package/dist/esm/utils/utils.base.js +10 -0
- package/dist/iife/excelts.iife.js +1022 -677
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +48 -48
- package/dist/types/modules/pdf/excel-bridge.d.ts +4 -3
- package/dist/types/modules/pdf/index.d.ts +3 -3
- package/dist/types/modules/pdf/pdf.d.ts +7 -6
- package/dist/types/modules/pdf/reader/pdf-reader.d.ts +8 -7
- package/dist/types/modules/pdf/render/constants.d.ts +30 -0
- package/dist/types/modules/pdf/render/layout-engine.d.ts +2 -1
- package/dist/types/modules/pdf/render/page-renderer.d.ts +2 -2
- package/dist/types/modules/pdf/render/pdf-exporter.d.ts +3 -2
- package/dist/types/modules/pdf/types.d.ts +8 -0
- package/dist/types/utils/utils.base.d.ts +5 -0
- package/package.json +1 -1
|
@@ -18,12 +18,7 @@ exports.computeTextX = computeTextX;
|
|
|
18
18
|
exports.wrapTextLines = wrapTextLines;
|
|
19
19
|
const pdf_stream_1 = require("../core/pdf-stream");
|
|
20
20
|
const font_manager_1 = require("../font/font-manager");
|
|
21
|
-
|
|
22
|
-
// Constants
|
|
23
|
-
// =============================================================================
|
|
24
|
-
/** Internal cell padding in points */
|
|
25
|
-
const CELL_PADDING_H = 3;
|
|
26
|
-
const CELL_PADDING_V = 2;
|
|
21
|
+
const constants_1 = require("./constants");
|
|
27
22
|
/**
|
|
28
23
|
* Render a single page to a PDF content stream.
|
|
29
24
|
*/
|
|
@@ -45,9 +40,10 @@ function renderPage(page, options, fontManager, totalPages) {
|
|
|
45
40
|
drawCellBorders(stream, cell);
|
|
46
41
|
}
|
|
47
42
|
// --- Step 4: Draw cell text ---
|
|
43
|
+
const sf = page.scaleFactor;
|
|
48
44
|
for (const cell of page.cells) {
|
|
49
45
|
if (cell.text) {
|
|
50
|
-
drawCellText(stream, cell, fontManager, alphaValues);
|
|
46
|
+
drawCellText(stream, cell, fontManager, alphaValues, sf);
|
|
51
47
|
}
|
|
52
48
|
}
|
|
53
49
|
// --- Step 5: Draw page header (sheet name) ---
|
|
@@ -125,38 +121,56 @@ function drawCellBorders(stream, cell) {
|
|
|
125
121
|
const { rect, borders } = cell;
|
|
126
122
|
const { x, y, width, height } = rect;
|
|
127
123
|
if (borders.top) {
|
|
128
|
-
drawBorderLine(stream, borders.top, x, y + height, x + width, y + height);
|
|
124
|
+
drawBorderLine(stream, borders.top, x, y + height, x + width, y + height, true);
|
|
129
125
|
}
|
|
130
126
|
if (borders.bottom) {
|
|
131
|
-
drawBorderLine(stream, borders.bottom, x, y, x + width, y);
|
|
127
|
+
drawBorderLine(stream, borders.bottom, x, y, x + width, y, true);
|
|
132
128
|
}
|
|
133
129
|
if (borders.left) {
|
|
134
|
-
drawBorderLine(stream, borders.left, x, y, x, y + height);
|
|
130
|
+
drawBorderLine(stream, borders.left, x, y, x, y + height, false);
|
|
135
131
|
}
|
|
136
132
|
if (borders.right) {
|
|
137
|
-
drawBorderLine(stream, borders.right, x + width, y, x + width, y + height);
|
|
133
|
+
drawBorderLine(stream, borders.right, x + width, y, x + width, y + height, false);
|
|
138
134
|
}
|
|
139
135
|
}
|
|
140
|
-
function drawBorderLine(stream, border, x1, y1, x2, y2) {
|
|
141
|
-
|
|
136
|
+
function drawBorderLine(stream, border, x1, y1, x2, y2, isHorizontal) {
|
|
137
|
+
if (border.isDouble) {
|
|
138
|
+
// Draw two parallel thin lines with a small gap between them
|
|
139
|
+
const offset = 0.4;
|
|
140
|
+
const thinWidth = Math.min(border.width, 0.25);
|
|
141
|
+
if (isHorizontal) {
|
|
142
|
+
stream.drawLine(x1, y1 + offset, x2, y2 + offset, border.color, thinWidth, border.dashPattern);
|
|
143
|
+
stream.drawLine(x1, y1 - offset, x2, y2 - offset, border.color, thinWidth, border.dashPattern);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
stream.drawLine(x1 + offset, y1, x2 + offset, y2, border.color, thinWidth, border.dashPattern);
|
|
147
|
+
stream.drawLine(x1 - offset, y1, x2 - offset, y2, border.color, thinWidth, border.dashPattern);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
stream.drawLine(x1, y1, x2, y2, border.color, border.width, border.dashPattern);
|
|
152
|
+
}
|
|
142
153
|
}
|
|
143
154
|
// =============================================================================
|
|
144
155
|
// Cell Text
|
|
145
156
|
// =============================================================================
|
|
146
|
-
function drawCellText(stream, cell, fontManager, alphaValues) {
|
|
157
|
+
function drawCellText(stream, cell, fontManager, alphaValues, scaleFactor = 1) {
|
|
147
158
|
const { rect, text, fontSize, horizontalAlign, verticalAlign, wrapText } = cell;
|
|
148
159
|
if (!text && !cell.richText) {
|
|
149
160
|
return;
|
|
150
161
|
}
|
|
151
|
-
const
|
|
152
|
-
const
|
|
162
|
+
const padH = constants_1.CELL_PADDING_H * scaleFactor;
|
|
163
|
+
const padV = constants_1.CELL_PADDING_V * scaleFactor;
|
|
164
|
+
const availWidth = rect.width - padH * 2;
|
|
165
|
+
const availHeight = rect.height - padV * 2;
|
|
153
166
|
if (availWidth <= 0 || availHeight <= 0) {
|
|
154
167
|
return;
|
|
155
168
|
}
|
|
156
|
-
const indentPts = cell.indent * INDENT_WIDTH;
|
|
157
|
-
// Clip to cell bounds
|
|
169
|
+
const indentPts = cell.indent * constants_1.INDENT_WIDTH * scaleFactor;
|
|
170
|
+
// Clip to cell bounds (extend for text overflow into adjacent empty cells)
|
|
171
|
+
const clipWidth = rect.width + (cell.textOverflowWidth || 0);
|
|
158
172
|
stream.save();
|
|
159
|
-
stream.rect(rect.x, rect.y,
|
|
173
|
+
stream.rect(rect.x, rect.y, clipWidth, rect.height);
|
|
160
174
|
stream.clip();
|
|
161
175
|
stream.endPath();
|
|
162
176
|
// Apply text color alpha if needed
|
|
@@ -167,18 +181,18 @@ function drawCellText(stream, cell, fontManager, alphaValues) {
|
|
|
167
181
|
}
|
|
168
182
|
// Handle text rotation
|
|
169
183
|
if (cell.textRotation === "vertical") {
|
|
170
|
-
drawVerticalStackedText(stream, cell, fontManager, indentPts);
|
|
184
|
+
drawVerticalStackedText(stream, cell, fontManager, indentPts, scaleFactor);
|
|
171
185
|
stream.restore();
|
|
172
186
|
return;
|
|
173
187
|
}
|
|
174
188
|
if (typeof cell.textRotation === "number" && cell.textRotation !== 0) {
|
|
175
|
-
drawRotatedText(stream, cell, fontManager, indentPts);
|
|
189
|
+
drawRotatedText(stream, cell, fontManager, indentPts, scaleFactor);
|
|
176
190
|
stream.restore();
|
|
177
191
|
return;
|
|
178
192
|
}
|
|
179
193
|
// Handle rich text runs
|
|
180
194
|
if (cell.richText && cell.richText.length > 0) {
|
|
181
|
-
drawRichText(stream, cell, fontManager, indentPts);
|
|
195
|
+
drawRichText(stream, cell, fontManager, indentPts, scaleFactor);
|
|
182
196
|
stream.restore();
|
|
183
197
|
return;
|
|
184
198
|
}
|
|
@@ -188,13 +202,13 @@ function drawCellText(stream, cell, fontManager, alphaValues) {
|
|
|
188
202
|
? fontManager.getEmbeddedResourceName()
|
|
189
203
|
: fontManager.ensureFont((0, font_manager_1.resolvePdfFontName)(cell.fontFamily, cell.bold, cell.italic));
|
|
190
204
|
const measure = (s) => fontManager.measureText(s, resourceName, fontSize);
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const lines = wrapText ? wrapTextLines(text, measure, effectiveWidth) :
|
|
194
|
-
const lineHeight = fontSize *
|
|
205
|
+
const effectiveWidth = availWidth - indentPts;
|
|
206
|
+
// Always split on explicit newlines; additionally word-wrap if wrapText is set
|
|
207
|
+
const lines = wrapText ? wrapTextLines(text, measure, effectiveWidth) : text.split(/\r?\n/);
|
|
208
|
+
const lineHeight = fontSize * constants_1.LINE_HEIGHT_FACTOR;
|
|
195
209
|
const ascent = fontManager.getFontAscent(resourceName, fontSize);
|
|
196
210
|
const totalTextHeight = lines.length * lineHeight;
|
|
197
|
-
const textStartY = computeTextStartY(verticalAlign, rect, totalTextHeight, ascent);
|
|
211
|
+
const textStartY = computeTextStartY(verticalAlign, rect, totalTextHeight, ascent, padV);
|
|
198
212
|
stream.setFillColor(cell.textColor);
|
|
199
213
|
stream.beginText();
|
|
200
214
|
stream.setFont(resourceName, fontSize);
|
|
@@ -202,7 +216,7 @@ function drawCellText(stream, cell, fontManager, alphaValues) {
|
|
|
202
216
|
const line = lines[i];
|
|
203
217
|
const lineY = textStartY - i * lineHeight;
|
|
204
218
|
const textWidth = measure(line);
|
|
205
|
-
const textX = computeTextX(horizontalAlign, rect, textWidth, indentPts);
|
|
219
|
+
const textX = computeTextX(horizontalAlign, rect, textWidth, indentPts, padH);
|
|
206
220
|
stream.setTextMatrix(1, 0, 0, 1, textX, lineY);
|
|
207
221
|
const hexEncoded = fontManager.encodeText(line, resourceName);
|
|
208
222
|
if (hexEncoded) {
|
|
@@ -219,9 +233,11 @@ function drawCellText(stream, cell, fontManager, alphaValues) {
|
|
|
219
233
|
// =============================================================================
|
|
220
234
|
// Rich Text Rendering
|
|
221
235
|
// =============================================================================
|
|
222
|
-
function drawRichText(stream, cell, fontManager, indentPts) {
|
|
236
|
+
function drawRichText(stream, cell, fontManager, indentPts, scaleFactor = 1) {
|
|
223
237
|
const { rect, horizontalAlign, verticalAlign, wrapText } = cell;
|
|
224
238
|
const runs = cell.richText;
|
|
239
|
+
const padH = constants_1.CELL_PADDING_H * scaleFactor;
|
|
240
|
+
const padV = constants_1.CELL_PADDING_V * scaleFactor;
|
|
225
241
|
// Use the largest font size across all runs for line height calculation
|
|
226
242
|
let maxFontSize = cell.fontSize;
|
|
227
243
|
for (const run of runs) {
|
|
@@ -230,7 +246,7 @@ function drawRichText(stream, cell, fontManager, indentPts) {
|
|
|
230
246
|
}
|
|
231
247
|
}
|
|
232
248
|
const primaryFontSize = maxFontSize;
|
|
233
|
-
const lineHeight = primaryFontSize *
|
|
249
|
+
const lineHeight = primaryFontSize * constants_1.LINE_HEIGHT_FACTOR;
|
|
234
250
|
const isEmbedded = fontManager.hasEmbeddedFont();
|
|
235
251
|
// Helper: resolve resource name for a run
|
|
236
252
|
const runResource = (run) => isEmbedded
|
|
@@ -238,7 +254,7 @@ function drawRichText(stream, cell, fontManager, indentPts) {
|
|
|
238
254
|
: fontManager.ensureFont((0, font_manager_1.resolvePdfFontName)(run.fontFamily, run.bold, run.italic));
|
|
239
255
|
// --- Wrapping path ---
|
|
240
256
|
if (wrapText) {
|
|
241
|
-
const availWidth = rect.width -
|
|
257
|
+
const availWidth = rect.width - padH * 2 - indentPts;
|
|
242
258
|
if (availWidth <= 0) {
|
|
243
259
|
return;
|
|
244
260
|
}
|
|
@@ -257,7 +273,7 @@ function drawRichText(stream, cell, fontManager, indentPts) {
|
|
|
257
273
|
const primaryResourceName = runResource(runs[0]);
|
|
258
274
|
const ascent = fontManager.getFontAscent(primaryResourceName, primaryFontSize);
|
|
259
275
|
const totalTextHeight = lines.length * lineHeight;
|
|
260
|
-
const textStartY = computeTextStartY(verticalAlign, rect, totalTextHeight, ascent);
|
|
276
|
+
const textStartY = computeTextStartY(verticalAlign, rect, totalTextHeight, ascent, padV);
|
|
261
277
|
let charPos = 0;
|
|
262
278
|
for (let li = 0; li < lines.length; li++) {
|
|
263
279
|
const lineY = textStartY - li * lineHeight;
|
|
@@ -295,7 +311,7 @@ function drawRichText(stream, cell, fontManager, indentPts) {
|
|
|
295
311
|
for (const seg of segments) {
|
|
296
312
|
lineWidth += fontManager.measureText(seg.text, seg.resourceName, seg.run.fontSize);
|
|
297
313
|
}
|
|
298
|
-
let textX = computeTextX(horizontalAlign, rect, lineWidth, indentPts);
|
|
314
|
+
let textX = computeTextX(horizontalAlign, rect, lineWidth, indentPts, padH);
|
|
299
315
|
for (const seg of segments) {
|
|
300
316
|
const { run, text, resourceName } = seg;
|
|
301
317
|
const segWidth = fontManager.measureText(text, resourceName, run.fontSize);
|
|
@@ -338,8 +354,8 @@ function drawRichText(stream, cell, fontManager, indentPts) {
|
|
|
338
354
|
}
|
|
339
355
|
const primaryResourceName = runMetrics[0]?.resourceName ?? "F1";
|
|
340
356
|
const ascent = fontManager.getFontAscent(primaryResourceName, primaryFontSize);
|
|
341
|
-
const textStartY = computeTextStartY(verticalAlign, rect, lineHeight, ascent);
|
|
342
|
-
let textX = computeTextX(horizontalAlign, rect, totalWidth, indentPts);
|
|
357
|
+
const textStartY = computeTextStartY(verticalAlign, rect, lineHeight, ascent, padV);
|
|
358
|
+
let textX = computeTextX(horizontalAlign, rect, totalWidth, indentPts, padH);
|
|
343
359
|
for (let i = 0; i < runs.length; i++) {
|
|
344
360
|
const run = runs[i];
|
|
345
361
|
const { resourceName } = runMetrics[i];
|
|
@@ -373,23 +389,20 @@ function drawRichText(stream, cell, fontManager, indentPts) {
|
|
|
373
389
|
// =============================================================================
|
|
374
390
|
// Rotated Text
|
|
375
391
|
// =============================================================================
|
|
376
|
-
function drawRotatedText(stream, cell, fontManager, indentPts) {
|
|
377
|
-
const { rect,
|
|
392
|
+
function drawRotatedText(stream, cell, fontManager, indentPts, scaleFactor = 1) {
|
|
393
|
+
const { rect, wrapText } = cell;
|
|
378
394
|
let { fontSize } = cell;
|
|
395
|
+
const padH = constants_1.CELL_PADDING_H * scaleFactor;
|
|
396
|
+
const padV = constants_1.CELL_PADDING_V * scaleFactor;
|
|
379
397
|
const isEmbedded = fontManager.hasEmbeddedFont();
|
|
380
398
|
const resourceName = isEmbedded
|
|
381
399
|
? fontManager.getEmbeddedResourceName()
|
|
382
400
|
: fontManager.ensureFont((0, font_manager_1.resolvePdfFontName)(cell.fontFamily, cell.bold, cell.italic));
|
|
383
|
-
// Convert Excel rotation to
|
|
401
|
+
// Convert Excel rotation to degrees
|
|
384
402
|
// 1-90: counterclockwise, 91-180: clockwise (value-90 degrees)
|
|
385
403
|
let degrees;
|
|
386
404
|
if (typeof cell.textRotation === "number") {
|
|
387
|
-
|
|
388
|
-
degrees = cell.textRotation;
|
|
389
|
-
}
|
|
390
|
-
else {
|
|
391
|
-
degrees = -(cell.textRotation - 90);
|
|
392
|
-
}
|
|
405
|
+
degrees = cell.textRotation <= 90 ? cell.textRotation : -(cell.textRotation - 90);
|
|
393
406
|
}
|
|
394
407
|
else {
|
|
395
408
|
degrees = 0;
|
|
@@ -397,81 +410,208 @@ function drawRotatedText(stream, cell, fontManager, indentPts) {
|
|
|
397
410
|
const radians = (degrees * Math.PI) / 180;
|
|
398
411
|
const cos = Math.cos(radians);
|
|
399
412
|
const sin = Math.sin(radians);
|
|
400
|
-
// Scale font size down if rotated bounding box exceeds cell dimensions
|
|
401
|
-
const textWidth = fontManager.measureText(text, resourceName, fontSize);
|
|
402
413
|
const absSin = Math.abs(sin);
|
|
403
414
|
const absCos = Math.abs(cos);
|
|
404
|
-
const
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if (
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
415
|
+
const maxWidth = rect.width - padH * 2;
|
|
416
|
+
const maxHeight = rect.height - padV * 2;
|
|
417
|
+
// Available length along the text flow direction for wrapping
|
|
418
|
+
let availTextLength;
|
|
419
|
+
if (absSin > 0.01 && absCos > 0.01) {
|
|
420
|
+
availTextLength = Math.min(maxHeight / absSin, maxWidth / absCos);
|
|
421
|
+
}
|
|
422
|
+
else if (absSin > 0.01) {
|
|
423
|
+
availTextLength = maxHeight / absSin;
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
availTextLength = maxWidth;
|
|
427
|
+
}
|
|
428
|
+
const measure = (s) => fontManager.measureText(s, resourceName, fontSize);
|
|
429
|
+
// Split on explicit newlines first, then optionally word-wrap each paragraph
|
|
430
|
+
let lines;
|
|
431
|
+
if (wrapText) {
|
|
432
|
+
lines = wrapTextLines(cell.text, measure, Math.max(availTextLength - 1, 1));
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
lines = cell.text.split(/\r?\n/);
|
|
436
|
+
}
|
|
437
|
+
const lineHeight = fontSize * constants_1.LINE_HEIGHT_FACTOR;
|
|
438
|
+
const totalTextHeight = lines.length * lineHeight;
|
|
439
|
+
// For non-wrapping text: scale font down if the rotated bounding box exceeds cell
|
|
440
|
+
if (!wrapText) {
|
|
441
|
+
let maxLineWidth = 0;
|
|
442
|
+
for (const line of lines) {
|
|
443
|
+
const w = measure(line);
|
|
444
|
+
if (w > maxLineWidth) {
|
|
445
|
+
maxLineWidth = w;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
const rotatedWidth = maxLineWidth * absCos + totalTextHeight * absSin;
|
|
449
|
+
const rotatedHeight = maxLineWidth * absSin + totalTextHeight * absCos;
|
|
450
|
+
if (maxWidth > 0 && maxHeight > 0 && (rotatedWidth > maxWidth || rotatedHeight > maxHeight)) {
|
|
451
|
+
const fitScale = Math.min(maxWidth / rotatedWidth, maxHeight / rotatedHeight);
|
|
452
|
+
if (fitScale < 1) {
|
|
453
|
+
fontSize = fontSize * fitScale;
|
|
454
|
+
}
|
|
412
455
|
}
|
|
413
456
|
}
|
|
414
|
-
|
|
415
|
-
const indentOffset = cell.horizontalAlign === "left"
|
|
416
|
-
? indentPts / 2
|
|
417
|
-
: cell.horizontalAlign === "right"
|
|
418
|
-
? -indentPts / 2
|
|
419
|
-
: 0;
|
|
420
|
-
const cx = rect.x + rect.width / 2 + indentOffset;
|
|
421
|
-
const cy = rect.y + rect.height / 2;
|
|
422
|
-
const finalTextWidth = fontManager.measureText(text, resourceName, fontSize);
|
|
457
|
+
const scaledLineHeight = fontSize * constants_1.LINE_HEIGHT_FACTOR;
|
|
423
458
|
const ascent = fontManager.getFontAscent(resourceName, fontSize);
|
|
424
|
-
|
|
425
|
-
const
|
|
426
|
-
const offsetY = -ascent / 2;
|
|
427
|
-
const tx = cx + offsetX * cos - offsetY * sin;
|
|
428
|
-
const ty = cy + offsetX * sin + offsetY * cos;
|
|
459
|
+
const is90 = Math.abs(degrees - 90) < 0.01;
|
|
460
|
+
const isMinus90 = Math.abs(degrees + 90) < 0.01;
|
|
429
461
|
stream.setFillColor(cell.textColor);
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
if (
|
|
435
|
-
|
|
462
|
+
if (is90) {
|
|
463
|
+
// Text reads bottom-to-top. Each line becomes a column drawn left-to-right.
|
|
464
|
+
drawRotated90(stream, cell, lines, fontManager, resourceName, fontSize, scaledLineHeight, ascent, padH, padV);
|
|
465
|
+
}
|
|
466
|
+
else if (isMinus90) {
|
|
467
|
+
// Text reads top-to-bottom. Each line becomes a column drawn right-to-left.
|
|
468
|
+
drawRotatedMinus90(stream, cell, lines, fontManager, resourceName, fontSize, scaledLineHeight, ascent, padH, padV);
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
// General rotation — center multi-line text block in cell
|
|
472
|
+
drawRotatedGeneral(stream, cell, lines, fontManager, resourceName, fontSize, scaledLineHeight, ascent, cos, sin, indentPts);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/** 90° CCW: text reads bottom-to-top, lines stack left-to-right. */
|
|
476
|
+
function drawRotated90(stream, cell, lines, fontManager, resourceName, fontSize, lineHeight, ascent, padH, padV) {
|
|
477
|
+
const { rect, horizontalAlign, verticalAlign } = cell;
|
|
478
|
+
const totalColumnsWidth = lines.length * lineHeight;
|
|
479
|
+
// Horizontal centering of line columns
|
|
480
|
+
let startX;
|
|
481
|
+
if (horizontalAlign === "center" || lines.length === 1) {
|
|
482
|
+
startX = rect.x + rect.width / 2 - totalColumnsWidth / 2 + ascent;
|
|
483
|
+
}
|
|
484
|
+
else if (horizontalAlign === "right") {
|
|
485
|
+
startX = rect.x + rect.width - padH - totalColumnsWidth + ascent;
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
startX = rect.x + padH + ascent;
|
|
489
|
+
}
|
|
490
|
+
for (let i = 0; i < lines.length; i++) {
|
|
491
|
+
const line = lines[i];
|
|
492
|
+
const lineWidth = fontManager.measureText(line, resourceName, fontSize);
|
|
493
|
+
const colX = startX + i * lineHeight;
|
|
494
|
+
// Vertical positioning: text flows upward from bottom
|
|
495
|
+
let ty;
|
|
496
|
+
if (verticalAlign === "top") {
|
|
497
|
+
ty = rect.y + padV;
|
|
498
|
+
}
|
|
499
|
+
else if (verticalAlign === "middle") {
|
|
500
|
+
ty = rect.y + (rect.height - lineWidth) / 2;
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
ty = rect.y + rect.height - padV - lineWidth;
|
|
504
|
+
}
|
|
505
|
+
ty = Math.max(ty, rect.y + padV);
|
|
506
|
+
stream.beginText();
|
|
507
|
+
stream.setFont(resourceName, fontSize);
|
|
508
|
+
stream.setTextMatrix(0, 1, -1, 0, colX, ty);
|
|
509
|
+
emitText(stream, fontManager, line, resourceName);
|
|
510
|
+
stream.endText();
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
/** -90° (270° CW): text reads top-to-bottom, lines stack right-to-left. */
|
|
514
|
+
function drawRotatedMinus90(stream, cell, lines, fontManager, resourceName, fontSize, lineHeight, ascent, padH, padV) {
|
|
515
|
+
const { rect, horizontalAlign, verticalAlign } = cell;
|
|
516
|
+
const totalColumnsWidth = lines.length * lineHeight;
|
|
517
|
+
let startX;
|
|
518
|
+
if (horizontalAlign === "center" || lines.length === 1) {
|
|
519
|
+
startX = rect.x + rect.width / 2 + totalColumnsWidth / 2 - lineHeight + ascent;
|
|
520
|
+
}
|
|
521
|
+
else if (horizontalAlign === "right") {
|
|
522
|
+
startX = rect.x + rect.width - padH - lineHeight + ascent;
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
startX = rect.x + padH + totalColumnsWidth - lineHeight + ascent;
|
|
526
|
+
}
|
|
527
|
+
for (let i = 0; i < lines.length; i++) {
|
|
528
|
+
const line = lines[i];
|
|
529
|
+
const lineWidth = fontManager.measureText(line, resourceName, fontSize);
|
|
530
|
+
const colX = startX - i * lineHeight;
|
|
531
|
+
let ty;
|
|
532
|
+
if (verticalAlign === "top") {
|
|
533
|
+
ty = rect.y + rect.height - padV;
|
|
534
|
+
}
|
|
535
|
+
else if (verticalAlign === "middle") {
|
|
536
|
+
ty = rect.y + (rect.height + lineWidth) / 2;
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
ty = rect.y + padV + lineWidth;
|
|
540
|
+
}
|
|
541
|
+
ty = Math.min(ty, rect.y + rect.height - padV);
|
|
542
|
+
stream.beginText();
|
|
543
|
+
stream.setFont(resourceName, fontSize);
|
|
544
|
+
stream.setTextMatrix(0, -1, 1, 0, colX, ty);
|
|
545
|
+
emitText(stream, fontManager, line, resourceName);
|
|
546
|
+
stream.endText();
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
/** General rotation — center a multi-line text block in the cell. */
|
|
550
|
+
function drawRotatedGeneral(stream, cell, lines, fontManager, resourceName, fontSize, lineHeight, ascent, cos, sin, indentPts) {
|
|
551
|
+
const { rect, horizontalAlign } = cell;
|
|
552
|
+
const indentOffset = horizontalAlign === "left" ? indentPts / 2 : horizontalAlign === "right" ? -indentPts / 2 : 0;
|
|
553
|
+
const cx = rect.x + rect.width / 2 + indentOffset;
|
|
554
|
+
const cy = rect.y + rect.height / 2;
|
|
555
|
+
for (let i = 0; i < lines.length; i++) {
|
|
556
|
+
const line = lines[i];
|
|
557
|
+
const lineWidth = fontManager.measureText(line, resourceName, fontSize);
|
|
558
|
+
const lineOffset = (i - (lines.length - 1) / 2) * lineHeight;
|
|
559
|
+
const offsetX = -lineWidth / 2;
|
|
560
|
+
const offsetY = -ascent / 2 - lineOffset;
|
|
561
|
+
const tx = cx + offsetX * cos - offsetY * sin;
|
|
562
|
+
const ty = cy + offsetX * sin + offsetY * cos;
|
|
563
|
+
stream.beginText();
|
|
564
|
+
stream.setFont(resourceName, fontSize);
|
|
565
|
+
stream.setTextMatrix(cos, sin, -sin, cos, tx, ty);
|
|
566
|
+
emitText(stream, fontManager, line, resourceName);
|
|
567
|
+
stream.endText();
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
/** Emit a text string with hex encoding if available. */
|
|
571
|
+
function emitText(stream, fontManager, text, resourceName) {
|
|
572
|
+
const hex = fontManager.encodeText(text, resourceName);
|
|
573
|
+
if (hex) {
|
|
574
|
+
stream.showTextHex(hex);
|
|
436
575
|
}
|
|
437
576
|
else {
|
|
438
577
|
stream.showText(text);
|
|
439
578
|
}
|
|
440
|
-
stream.endText();
|
|
441
579
|
}
|
|
442
580
|
/**
|
|
443
581
|
* Draw vertical stacked text (each character top-to-bottom).
|
|
582
|
+
* Newlines (\n) start a new column to the right.
|
|
444
583
|
*/
|
|
445
|
-
function drawVerticalStackedText(stream, cell, fontManager, _indentPts) {
|
|
584
|
+
function drawVerticalStackedText(stream, cell, fontManager, _indentPts, scaleFactor = 1) {
|
|
446
585
|
const { rect, text, fontSize } = cell;
|
|
586
|
+
const padV = constants_1.CELL_PADDING_V * scaleFactor;
|
|
447
587
|
const isEmbedded = fontManager.hasEmbeddedFont();
|
|
448
588
|
const resourceName = isEmbedded
|
|
449
589
|
? fontManager.getEmbeddedResourceName()
|
|
450
590
|
: fontManager.ensureFont((0, font_manager_1.resolvePdfFontName)(cell.fontFamily, cell.bold, cell.italic));
|
|
451
591
|
const charHeight = fontSize * 1.3;
|
|
452
592
|
const ascent = fontManager.getFontAscent(resourceName, fontSize);
|
|
453
|
-
|
|
454
|
-
|
|
593
|
+
// Split on newlines — each segment becomes a new column
|
|
594
|
+
const columns = text.split(/\r?\n/);
|
|
595
|
+
const columnWidth = fontSize * 1.4;
|
|
596
|
+
const totalColumnsWidth = columns.length * columnWidth;
|
|
597
|
+
const startX = rect.x + rect.width / 2 - totalColumnsWidth / 2 + columnWidth / 2;
|
|
455
598
|
stream.setFillColor(cell.textColor);
|
|
456
|
-
for (let
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
stream
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
stream.showText(ch);
|
|
599
|
+
for (let colIdx = 0; colIdx < columns.length; colIdx++) {
|
|
600
|
+
const colText = columns[colIdx];
|
|
601
|
+
const colX = startX + colIdx * columnWidth;
|
|
602
|
+
let currentY = rect.y + rect.height - padV - ascent;
|
|
603
|
+
for (const ch of colText) {
|
|
604
|
+
if (currentY < rect.y + padV) {
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
const charWidth = fontManager.measureText(ch, resourceName, fontSize);
|
|
608
|
+
stream.beginText();
|
|
609
|
+
stream.setFont(resourceName, fontSize);
|
|
610
|
+
stream.setTextMatrix(1, 0, 0, 1, colX - charWidth / 2, currentY);
|
|
611
|
+
emitText(stream, fontManager, ch, resourceName);
|
|
612
|
+
stream.endText();
|
|
613
|
+
currentY -= charHeight;
|
|
472
614
|
}
|
|
473
|
-
stream.endText();
|
|
474
|
-
currentY -= charHeight;
|
|
475
615
|
}
|
|
476
616
|
}
|
|
477
617
|
// =============================================================================
|
|
@@ -488,49 +628,47 @@ function alphaGsName(alpha) {
|
|
|
488
628
|
// =============================================================================
|
|
489
629
|
// Text Layout Helpers
|
|
490
630
|
// =============================================================================
|
|
491
|
-
|
|
492
|
-
const INDENT_WIDTH = 10;
|
|
493
|
-
function computeTextStartY(verticalAlign, rect, totalTextHeight, ascent) {
|
|
631
|
+
function computeTextStartY(verticalAlign, rect, totalTextHeight, ascent, padV = constants_1.CELL_PADDING_V) {
|
|
494
632
|
let y;
|
|
495
633
|
switch (verticalAlign) {
|
|
496
634
|
case "top":
|
|
497
|
-
y = rect.y + rect.height -
|
|
635
|
+
y = rect.y + rect.height - padV - ascent;
|
|
498
636
|
break;
|
|
499
637
|
case "middle":
|
|
500
638
|
y = rect.y + rect.height / 2 + totalTextHeight / 2 - ascent;
|
|
501
639
|
break;
|
|
502
640
|
case "bottom":
|
|
503
641
|
default:
|
|
504
|
-
y = rect.y +
|
|
642
|
+
y = rect.y + padV + (totalTextHeight - ascent);
|
|
505
643
|
break;
|
|
506
644
|
}
|
|
507
645
|
// Clamp: ensure text ascent doesn't exceed the cell top
|
|
508
|
-
const maxY = rect.y + rect.height -
|
|
646
|
+
const maxY = rect.y + rect.height - padV - ascent;
|
|
509
647
|
if (y > maxY) {
|
|
510
648
|
y = maxY;
|
|
511
649
|
}
|
|
512
650
|
// Clamp: ensure text descent doesn't go below cell bottom
|
|
513
|
-
const minY = rect.y +
|
|
651
|
+
const minY = rect.y + padV;
|
|
514
652
|
if (y < minY) {
|
|
515
653
|
y = minY;
|
|
516
654
|
}
|
|
517
655
|
return y;
|
|
518
656
|
}
|
|
519
|
-
function computeTextX(align, rect, textWidth, indentPts = 0) {
|
|
657
|
+
function computeTextX(align, rect, textWidth, indentPts = 0, padH = constants_1.CELL_PADDING_H) {
|
|
520
658
|
let x;
|
|
521
659
|
switch (align) {
|
|
522
660
|
case "center":
|
|
523
661
|
x = rect.x + (rect.width - textWidth) / 2;
|
|
524
662
|
break;
|
|
525
663
|
case "right":
|
|
526
|
-
x = rect.x + rect.width -
|
|
664
|
+
x = rect.x + rect.width - padH - textWidth;
|
|
527
665
|
break;
|
|
528
666
|
default:
|
|
529
|
-
x = rect.x +
|
|
667
|
+
x = rect.x + padH + indentPts;
|
|
530
668
|
break;
|
|
531
669
|
}
|
|
532
670
|
// Clamp: don't start before cell left edge
|
|
533
|
-
const minX = rect.x +
|
|
671
|
+
const minX = rect.x + padH;
|
|
534
672
|
if (x < minX) {
|
|
535
673
|
x = minX;
|
|
536
674
|
}
|