@xignature/docx-editor 1.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/dist/FindReplaceDialog-QIGO4OJT.mjs +35 -0
- package/dist/FootnotePropertiesDialog-JPXQGUW6.mjs +248 -0
- package/dist/HyperlinkDialog-FKJNYDPU.mjs +33 -0
- package/dist/ImagePositionDialog-GNWPNWDW.mjs +384 -0
- package/dist/ImagePropertiesDialog-X444B4VT.mjs +255 -0
- package/dist/PageSetupDialog-VVADE54Z.mjs +218 -0
- package/dist/SplitCellDialog-7LP6IJIJ.mjs +199 -0
- package/dist/TablePropertiesDialog-OKWY22PF.mjs +194 -0
- package/dist/chunk-625UL7ND.mjs +928 -0
- package/dist/chunk-IAU6OTCV.mjs +969 -0
- package/dist/chunk-K4JMXHBE.mjs +61 -0
- package/dist/chunk-OG6X5JJA.mjs +825 -0
- package/dist/chunk-QGDML3KL.mjs +258 -0
- package/dist/chunk-TSNYN4SV.mjs +589 -0
- package/dist/chunk-VOCY4R5S.mjs +1642 -0
- package/dist/executor-WLWTO7VJ.mjs +8 -0
- package/dist/index.css +483 -0
- package/dist/index.d.mts +8891 -0
- package/dist/index.d.ts +8891 -0
- package/dist/index.js +61000 -0
- package/dist/index.mjs +53887 -0
- package/dist/processTemplate-3HXKJ3EC.mjs +26 -0
- package/dist/selectionRects-I6KGVVI7.mjs +12 -0
- package/package.json +70 -0
|
@@ -0,0 +1,1642 @@
|
|
|
1
|
+
// src/core/layout-engine/paginator.ts
|
|
2
|
+
function calculateColumnWidth(pageWidth, leftMargin, rightMargin, columns) {
|
|
3
|
+
const contentWidth = pageWidth - leftMargin - rightMargin;
|
|
4
|
+
const totalGaps = (columns.count - 1) * columns.gap;
|
|
5
|
+
return (contentWidth - totalGaps) / columns.count;
|
|
6
|
+
}
|
|
7
|
+
function createPaginator(options) {
|
|
8
|
+
const { pageSize, margins } = options;
|
|
9
|
+
let columns = options.columns ?? { count: 1, gap: 0 };
|
|
10
|
+
const pages = [];
|
|
11
|
+
const states = [];
|
|
12
|
+
const topMargin = margins.top;
|
|
13
|
+
const contentBottom = pageSize.h - margins.bottom;
|
|
14
|
+
const contentHeight = contentBottom - topMargin;
|
|
15
|
+
if (contentHeight <= 0) {
|
|
16
|
+
throw new Error("Paginator: page size and margins yield no content area");
|
|
17
|
+
}
|
|
18
|
+
let columnWidth = calculateColumnWidth(pageSize.w, margins.left, margins.right, columns);
|
|
19
|
+
let columnRegionTop = topMargin;
|
|
20
|
+
function getColumnX(columnIndex) {
|
|
21
|
+
return margins.left + columnIndex * (columnWidth + columns.gap);
|
|
22
|
+
}
|
|
23
|
+
function createNewPage() {
|
|
24
|
+
const pageNumber = pages.length + 1;
|
|
25
|
+
const footnoteHeight = options.footnoteReservedHeights?.get(pageNumber) ?? 0;
|
|
26
|
+
const pageContentBottom = contentBottom - footnoteHeight;
|
|
27
|
+
const page = {
|
|
28
|
+
number: pageNumber,
|
|
29
|
+
fragments: [],
|
|
30
|
+
margins: { ...margins },
|
|
31
|
+
size: { ...pageSize },
|
|
32
|
+
footnoteReservedHeight: footnoteHeight > 0 ? footnoteHeight : void 0,
|
|
33
|
+
// Set initial columns; may be overwritten by updateColumns() for continuous section breaks
|
|
34
|
+
columns: columns.count > 1 ? { ...columns } : void 0
|
|
35
|
+
};
|
|
36
|
+
const state = {
|
|
37
|
+
page,
|
|
38
|
+
cursorY: topMargin,
|
|
39
|
+
columnIndex: 0,
|
|
40
|
+
topMargin,
|
|
41
|
+
contentBottom: pageContentBottom,
|
|
42
|
+
trailingSpacing: 0
|
|
43
|
+
};
|
|
44
|
+
pages.push(page);
|
|
45
|
+
states.push(state);
|
|
46
|
+
columnRegionTop = topMargin;
|
|
47
|
+
if (options.onNewPage) {
|
|
48
|
+
options.onNewPage(state);
|
|
49
|
+
}
|
|
50
|
+
return state;
|
|
51
|
+
}
|
|
52
|
+
function getCurrentState() {
|
|
53
|
+
if (states.length === 0) {
|
|
54
|
+
return createNewPage();
|
|
55
|
+
}
|
|
56
|
+
return states[states.length - 1];
|
|
57
|
+
}
|
|
58
|
+
function getAvailableHeight(state) {
|
|
59
|
+
return state.contentBottom - state.cursorY;
|
|
60
|
+
}
|
|
61
|
+
function fits(height, state) {
|
|
62
|
+
const s = state || getCurrentState();
|
|
63
|
+
return getAvailableHeight(s) >= height;
|
|
64
|
+
}
|
|
65
|
+
function advanceColumn(state) {
|
|
66
|
+
if (state.columnIndex < columns.count - 1) {
|
|
67
|
+
state.columnIndex += 1;
|
|
68
|
+
state.cursorY = columnRegionTop;
|
|
69
|
+
state.trailingSpacing = 0;
|
|
70
|
+
return state;
|
|
71
|
+
}
|
|
72
|
+
return createNewPage();
|
|
73
|
+
}
|
|
74
|
+
function ensureFits(height) {
|
|
75
|
+
let state = getCurrentState();
|
|
76
|
+
const safeHeight = Number.isFinite(height) && height > 0 ? height : 0;
|
|
77
|
+
const columnCapacity = state.contentBottom - state.topMargin;
|
|
78
|
+
if (safeHeight > columnCapacity) {
|
|
79
|
+
if (state.cursorY !== state.topMargin) {
|
|
80
|
+
state = advanceColumn(state);
|
|
81
|
+
}
|
|
82
|
+
return state;
|
|
83
|
+
}
|
|
84
|
+
while (!fits(safeHeight, state)) {
|
|
85
|
+
state = advanceColumn(state);
|
|
86
|
+
}
|
|
87
|
+
return state;
|
|
88
|
+
}
|
|
89
|
+
function addFragment(fragment, height, spaceBefore = 0, spaceAfter = 0) {
|
|
90
|
+
const effectiveSpaceBefore = Math.max(spaceBefore, getCurrentState().trailingSpacing);
|
|
91
|
+
const totalHeight = effectiveSpaceBefore + height;
|
|
92
|
+
const state = ensureFits(totalHeight);
|
|
93
|
+
const isAtTop = state.cursorY === state.topMargin;
|
|
94
|
+
const actualSpaceBefore = isAtTop ? 0 : effectiveSpaceBefore;
|
|
95
|
+
const x = getColumnX(state.columnIndex);
|
|
96
|
+
const y = state.cursorY + actualSpaceBefore;
|
|
97
|
+
fragment.x = x;
|
|
98
|
+
fragment.y = y;
|
|
99
|
+
state.page.fragments.push(fragment);
|
|
100
|
+
state.cursorY = y + height;
|
|
101
|
+
state.trailingSpacing = spaceAfter;
|
|
102
|
+
return { state, x, y };
|
|
103
|
+
}
|
|
104
|
+
function forcePageBreak() {
|
|
105
|
+
return createNewPage();
|
|
106
|
+
}
|
|
107
|
+
function forceColumnBreak() {
|
|
108
|
+
const state = getCurrentState();
|
|
109
|
+
return advanceColumn(state);
|
|
110
|
+
}
|
|
111
|
+
function updateColumns(newColumns) {
|
|
112
|
+
columns = newColumns;
|
|
113
|
+
columnWidth = calculateColumnWidth(pageSize.w, margins.left, margins.right, columns);
|
|
114
|
+
const state = getCurrentState();
|
|
115
|
+
state.page.columns = columns.count > 1 ? { ...columns } : void 0;
|
|
116
|
+
columnRegionTop = state.cursorY;
|
|
117
|
+
state.columnIndex = 0;
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
/** All pages created so far. */
|
|
121
|
+
pages,
|
|
122
|
+
/** All page states. */
|
|
123
|
+
states,
|
|
124
|
+
/** Column width in pixels (use getColumnWidth() for current value after updates). */
|
|
125
|
+
get columnWidth() {
|
|
126
|
+
return columnWidth;
|
|
127
|
+
},
|
|
128
|
+
/** Get current column layout (returns copy to prevent external mutation). */
|
|
129
|
+
get columns() {
|
|
130
|
+
return { ...columns };
|
|
131
|
+
},
|
|
132
|
+
/** Get current state. */
|
|
133
|
+
getCurrentState,
|
|
134
|
+
/** Get available height in current column. */
|
|
135
|
+
getAvailableHeight: () => getAvailableHeight(getCurrentState()),
|
|
136
|
+
/** Check if height fits in current column. */
|
|
137
|
+
fits: (height) => fits(height),
|
|
138
|
+
/** Ensure height fits, advancing if needed. */
|
|
139
|
+
ensureFits,
|
|
140
|
+
/** Add a fragment to current page. */
|
|
141
|
+
addFragment,
|
|
142
|
+
/** Force a page break. */
|
|
143
|
+
forcePageBreak,
|
|
144
|
+
/** Force a column break. */
|
|
145
|
+
forceColumnBreak,
|
|
146
|
+
/** Get X position for column. */
|
|
147
|
+
getColumnX,
|
|
148
|
+
/** Update column layout (for section breaks). */
|
|
149
|
+
updateColumns
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/core/layout-engine/keep-together.ts
|
|
154
|
+
function computeKeepNextChains(blocks) {
|
|
155
|
+
const chains = /* @__PURE__ */ new Map();
|
|
156
|
+
const processed = /* @__PURE__ */ new Set();
|
|
157
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
158
|
+
if (processed.has(i)) continue;
|
|
159
|
+
const block = blocks[i];
|
|
160
|
+
if (block.kind !== "paragraph") continue;
|
|
161
|
+
const para = block;
|
|
162
|
+
if (!para.attrs?.keepNext) continue;
|
|
163
|
+
const memberIndices = [i];
|
|
164
|
+
let endIndex = i;
|
|
165
|
+
for (let j = i + 1; j < blocks.length; j++) {
|
|
166
|
+
const nextBlock = blocks[j];
|
|
167
|
+
if (nextBlock.kind === "sectionBreak" || nextBlock.kind === "pageBreak" || nextBlock.kind === "columnBreak") {
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
if (nextBlock.kind !== "paragraph") {
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
const nextPara = nextBlock;
|
|
174
|
+
if (nextPara.attrs?.keepNext) {
|
|
175
|
+
memberIndices.push(j);
|
|
176
|
+
endIndex = j;
|
|
177
|
+
processed.add(j);
|
|
178
|
+
} else {
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const potentialAnchor = endIndex + 1;
|
|
183
|
+
let anchorIndex = -1;
|
|
184
|
+
if (potentialAnchor < blocks.length) {
|
|
185
|
+
const anchorBlock = blocks[potentialAnchor];
|
|
186
|
+
if (anchorBlock.kind !== "sectionBreak" && anchorBlock.kind !== "pageBreak" && anchorBlock.kind !== "columnBreak") {
|
|
187
|
+
anchorIndex = potentialAnchor;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
chains.set(i, {
|
|
191
|
+
startIndex: i,
|
|
192
|
+
endIndex,
|
|
193
|
+
memberIndices,
|
|
194
|
+
anchorIndex
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return chains;
|
|
198
|
+
}
|
|
199
|
+
function calculateChainHeight(chain, blocks, measures) {
|
|
200
|
+
let totalHeight = 0;
|
|
201
|
+
for (const memberIndex of chain.memberIndices) {
|
|
202
|
+
const block = blocks[memberIndex];
|
|
203
|
+
const measure = measures[memberIndex];
|
|
204
|
+
if (block.kind !== "paragraph" || measure.kind !== "paragraph") continue;
|
|
205
|
+
const para = block;
|
|
206
|
+
const paraMeasure = measure;
|
|
207
|
+
const spacingBefore = para.attrs?.spacing?.before ?? 0;
|
|
208
|
+
totalHeight += spacingBefore;
|
|
209
|
+
totalHeight += paraMeasure.totalHeight;
|
|
210
|
+
const spacingAfter = para.attrs?.spacing?.after ?? 0;
|
|
211
|
+
totalHeight += spacingAfter;
|
|
212
|
+
}
|
|
213
|
+
if (chain.anchorIndex !== -1) {
|
|
214
|
+
const anchorMeasure = measures[chain.anchorIndex];
|
|
215
|
+
if (anchorMeasure?.kind === "paragraph") {
|
|
216
|
+
const anchorPara = anchorMeasure;
|
|
217
|
+
if (anchorPara.lines.length > 0) {
|
|
218
|
+
totalHeight += anchorPara.lines[0].lineHeight;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return totalHeight;
|
|
223
|
+
}
|
|
224
|
+
function getMidChainIndices(chains) {
|
|
225
|
+
const midChain = /* @__PURE__ */ new Set();
|
|
226
|
+
for (const chain of chains.values()) {
|
|
227
|
+
for (let i = 1; i < chain.memberIndices.length; i++) {
|
|
228
|
+
midChain.add(chain.memberIndices[i]);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return midChain;
|
|
232
|
+
}
|
|
233
|
+
function hasPageBreakBefore(block) {
|
|
234
|
+
if (block.kind !== "paragraph") return false;
|
|
235
|
+
const para = block;
|
|
236
|
+
return para.attrs?.pageBreakBefore === true;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/core/layout-engine/types.ts
|
|
240
|
+
var DEFAULT_TEXTBOX_MARGINS = { top: 4, bottom: 4, left: 7, right: 7 };
|
|
241
|
+
var DEFAULT_TEXTBOX_WIDTH = 200;
|
|
242
|
+
|
|
243
|
+
// src/core/layout-engine/findPageIndexContainingPmPos.ts
|
|
244
|
+
function findPageIndexContainingPmPos(layout, pmPos) {
|
|
245
|
+
for (let pi = 0; pi < layout.pages.length; pi++) {
|
|
246
|
+
for (const frag of layout.pages[pi].fragments) {
|
|
247
|
+
if (frag.pmStart == null) continue;
|
|
248
|
+
const start = frag.pmStart;
|
|
249
|
+
const end = frag.pmEnd ?? start + 1;
|
|
250
|
+
if (pmPos >= start && pmPos < end) {
|
|
251
|
+
return pi;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/core/layout-engine/index.ts
|
|
259
|
+
var DEFAULT_PAGE_SIZE = { w: 816, h: 1056 };
|
|
260
|
+
var DEFAULT_MARGINS = {
|
|
261
|
+
top: 96,
|
|
262
|
+
right: 96,
|
|
263
|
+
bottom: 96,
|
|
264
|
+
left: 96
|
|
265
|
+
};
|
|
266
|
+
function getSpacingBefore(block) {
|
|
267
|
+
return block.attrs?.spacing?.before ?? 0;
|
|
268
|
+
}
|
|
269
|
+
function getSpacingAfter(block) {
|
|
270
|
+
return block.attrs?.spacing?.after ?? 0;
|
|
271
|
+
}
|
|
272
|
+
function applyContextualSpacing(blocks) {
|
|
273
|
+
for (let i = 0; i < blocks.length - 1; i++) {
|
|
274
|
+
const curr = blocks[i];
|
|
275
|
+
const next = blocks[i + 1];
|
|
276
|
+
if (curr.kind !== "paragraph" || next.kind !== "paragraph") continue;
|
|
277
|
+
const currAttrs = curr.attrs;
|
|
278
|
+
const nextAttrs = next.attrs;
|
|
279
|
+
if (currAttrs?.contextualSpacing && nextAttrs?.contextualSpacing && currAttrs.styleId && currAttrs.styleId === nextAttrs.styleId) {
|
|
280
|
+
if (currAttrs.spacing) {
|
|
281
|
+
currAttrs.spacing = { ...currAttrs.spacing, after: 0 };
|
|
282
|
+
}
|
|
283
|
+
if (nextAttrs.spacing) {
|
|
284
|
+
nextAttrs.spacing = { ...nextAttrs.spacing, before: 0 };
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function layoutDocument(blocks, measures, options = {}) {
|
|
290
|
+
if (blocks.length !== measures.length) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
`layoutDocument: expected one measure per block (blocks=${blocks.length}, measures=${measures.length})`
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
const pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
296
|
+
const baseMargins = {
|
|
297
|
+
top: options.margins?.top ?? DEFAULT_MARGINS.top,
|
|
298
|
+
right: options.margins?.right ?? DEFAULT_MARGINS.right,
|
|
299
|
+
bottom: options.margins?.bottom ?? DEFAULT_MARGINS.bottom,
|
|
300
|
+
left: options.margins?.left ?? DEFAULT_MARGINS.left,
|
|
301
|
+
header: options.margins?.header ?? options.margins?.top ?? DEFAULT_MARGINS.top,
|
|
302
|
+
footer: options.margins?.footer ?? options.margins?.bottom ?? DEFAULT_MARGINS.bottom
|
|
303
|
+
};
|
|
304
|
+
void options.headerContentHeights;
|
|
305
|
+
void options.footerContentHeights;
|
|
306
|
+
void options.titlePage;
|
|
307
|
+
void options.evenAndOddHeaders;
|
|
308
|
+
const margins = { ...baseMargins };
|
|
309
|
+
const contentWidth = pageSize.w - margins.left - margins.right;
|
|
310
|
+
if (contentWidth <= 0) {
|
|
311
|
+
throw new Error("layoutDocument: page size and margins yield no content area");
|
|
312
|
+
}
|
|
313
|
+
const defaultColumns = { count: 1, gap: 0 };
|
|
314
|
+
const sectionColumnConfigs = [];
|
|
315
|
+
const sectionBreakTypes = [];
|
|
316
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
317
|
+
if (blocks[i].kind === "sectionBreak") {
|
|
318
|
+
const sb = blocks[i];
|
|
319
|
+
sectionColumnConfigs.push(sb.columns ?? defaultColumns);
|
|
320
|
+
sectionBreakTypes.push(sb.type);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
sectionColumnConfigs.push(options.columns ?? defaultColumns);
|
|
324
|
+
sectionBreakTypes.push(options.bodyBreakType);
|
|
325
|
+
const initialColumns = sectionColumnConfigs.length > 0 ? sectionColumnConfigs[0] : options.columns;
|
|
326
|
+
const paginator = createPaginator({
|
|
327
|
+
pageSize,
|
|
328
|
+
margins,
|
|
329
|
+
columns: initialColumns,
|
|
330
|
+
footnoteReservedHeights: options.footnoteReservedHeights
|
|
331
|
+
});
|
|
332
|
+
applyContextualSpacing(blocks);
|
|
333
|
+
const keepNextChains = computeKeepNextChains(blocks);
|
|
334
|
+
const midChainIndices = getMidChainIndices(keepNextChains);
|
|
335
|
+
let sectionIdx = 0;
|
|
336
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
337
|
+
const block = blocks[i];
|
|
338
|
+
const measure = measures[i];
|
|
339
|
+
if (hasPageBreakBefore(block)) {
|
|
340
|
+
paginator.forcePageBreak();
|
|
341
|
+
}
|
|
342
|
+
const chain = keepNextChains.get(i);
|
|
343
|
+
if (chain && !midChainIndices.has(i)) {
|
|
344
|
+
const chainHeight = calculateChainHeight(chain, blocks, measures);
|
|
345
|
+
const state = paginator.getCurrentState();
|
|
346
|
+
const availableHeight = paginator.getAvailableHeight();
|
|
347
|
+
const pageContentHeight = state.contentBottom - state.topMargin;
|
|
348
|
+
if (chainHeight <= pageContentHeight && chainHeight > availableHeight && state.page.fragments.length > 0) {
|
|
349
|
+
paginator.forcePageBreak();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
switch (block.kind) {
|
|
353
|
+
case "paragraph":
|
|
354
|
+
layoutParagraph(block, measure, paginator, contentWidth);
|
|
355
|
+
break;
|
|
356
|
+
case "table":
|
|
357
|
+
if (block.floating) {
|
|
358
|
+
layoutFloatingTable(block, measure, paginator, contentWidth);
|
|
359
|
+
} else {
|
|
360
|
+
layoutTable(block, measure, paginator);
|
|
361
|
+
}
|
|
362
|
+
break;
|
|
363
|
+
case "image":
|
|
364
|
+
layoutImage(block, measure, paginator);
|
|
365
|
+
break;
|
|
366
|
+
case "textBox":
|
|
367
|
+
layoutTextBox(block, measure, paginator);
|
|
368
|
+
break;
|
|
369
|
+
case "pageBreak":
|
|
370
|
+
paginator.forcePageBreak();
|
|
371
|
+
break;
|
|
372
|
+
case "columnBreak":
|
|
373
|
+
paginator.forceColumnBreak();
|
|
374
|
+
break;
|
|
375
|
+
case "sectionBreak": {
|
|
376
|
+
const nextType = sectionBreakTypes[sectionIdx + 1] ?? sectionBreakTypes[sectionIdx];
|
|
377
|
+
handleSectionBreak(
|
|
378
|
+
block,
|
|
379
|
+
paginator,
|
|
380
|
+
sectionColumnConfigs[sectionIdx + 1] ?? defaultColumns,
|
|
381
|
+
nextType
|
|
382
|
+
);
|
|
383
|
+
sectionIdx++;
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
if (paginator.pages.length === 0) {
|
|
389
|
+
paginator.getCurrentState();
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
pageSize,
|
|
393
|
+
pages: paginator.pages,
|
|
394
|
+
columns: options.columns,
|
|
395
|
+
pageGap: options.pageGap
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
function layoutParagraph(block, measure, paginator, contentWidth) {
|
|
399
|
+
if (measure.kind !== "paragraph") {
|
|
400
|
+
throw new Error(`layoutParagraph: expected paragraph measure`);
|
|
401
|
+
}
|
|
402
|
+
const lines = measure.lines;
|
|
403
|
+
if (lines.length === 0) {
|
|
404
|
+
const spaceBefore2 = getSpacingBefore(block);
|
|
405
|
+
const spaceAfter2 = getSpacingAfter(block);
|
|
406
|
+
const state = paginator.getCurrentState();
|
|
407
|
+
const fragment = {
|
|
408
|
+
kind: "paragraph",
|
|
409
|
+
blockId: block.id,
|
|
410
|
+
x: paginator.getColumnX(state.columnIndex),
|
|
411
|
+
y: state.cursorY + spaceBefore2,
|
|
412
|
+
width: contentWidth,
|
|
413
|
+
height: 0,
|
|
414
|
+
fromLine: 0,
|
|
415
|
+
toLine: 0,
|
|
416
|
+
pmStart: block.pmStart,
|
|
417
|
+
pmEnd: block.pmEnd
|
|
418
|
+
};
|
|
419
|
+
paginator.addFragment(fragment, 0, spaceBefore2, spaceAfter2);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const spaceBefore = getSpacingBefore(block);
|
|
423
|
+
const spaceAfter = getSpacingAfter(block);
|
|
424
|
+
let currentLineIndex = 0;
|
|
425
|
+
while (currentLineIndex < lines.length) {
|
|
426
|
+
const state = paginator.getCurrentState();
|
|
427
|
+
const availableHeight = paginator.getAvailableHeight();
|
|
428
|
+
let linesHeight = 0;
|
|
429
|
+
let fittingLines = 0;
|
|
430
|
+
for (let j = currentLineIndex; j < lines.length; j++) {
|
|
431
|
+
const lineHeight = lines[j].lineHeight;
|
|
432
|
+
const totalWithLine = linesHeight + lineHeight;
|
|
433
|
+
const withSpacing = currentLineIndex === 0 && j === currentLineIndex ? totalWithLine + spaceBefore : totalWithLine;
|
|
434
|
+
if (withSpacing <= availableHeight || fittingLines === 0) {
|
|
435
|
+
linesHeight = totalWithLine;
|
|
436
|
+
fittingLines++;
|
|
437
|
+
} else {
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
const isFirstFragment = currentLineIndex === 0;
|
|
442
|
+
const isLastFragment = currentLineIndex + fittingLines >= lines.length;
|
|
443
|
+
const effectiveSpaceBefore = isFirstFragment ? spaceBefore : 0;
|
|
444
|
+
const effectiveSpaceAfter = isLastFragment ? spaceAfter : 0;
|
|
445
|
+
const fragment = {
|
|
446
|
+
kind: "paragraph",
|
|
447
|
+
blockId: block.id,
|
|
448
|
+
x: paginator.getColumnX(state.columnIndex),
|
|
449
|
+
y: 0,
|
|
450
|
+
// Will be set by addFragment
|
|
451
|
+
width: contentWidth,
|
|
452
|
+
height: linesHeight,
|
|
453
|
+
fromLine: currentLineIndex,
|
|
454
|
+
toLine: currentLineIndex + fittingLines,
|
|
455
|
+
pmStart: block.pmStart,
|
|
456
|
+
pmEnd: block.pmEnd,
|
|
457
|
+
continuesFromPrev: !isFirstFragment,
|
|
458
|
+
continuesOnNext: !isLastFragment
|
|
459
|
+
};
|
|
460
|
+
const result = paginator.addFragment(
|
|
461
|
+
fragment,
|
|
462
|
+
linesHeight,
|
|
463
|
+
effectiveSpaceBefore,
|
|
464
|
+
effectiveSpaceAfter
|
|
465
|
+
);
|
|
466
|
+
fragment.y = result.y;
|
|
467
|
+
currentLineIndex += fittingLines;
|
|
468
|
+
if (currentLineIndex < lines.length) {
|
|
469
|
+
paginator.ensureFits(lines[currentLineIndex].lineHeight);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
function countHeaderRows(block) {
|
|
474
|
+
let count = 0;
|
|
475
|
+
for (const row of block.rows) {
|
|
476
|
+
if (row.isHeader) {
|
|
477
|
+
count++;
|
|
478
|
+
} else {
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return count;
|
|
483
|
+
}
|
|
484
|
+
function getHeaderRowsHeight(measure, headerRowCount) {
|
|
485
|
+
let height = 0;
|
|
486
|
+
for (let i = 0; i < headerRowCount && i < measure.rows.length; i++) {
|
|
487
|
+
height += measure.rows[i].height;
|
|
488
|
+
}
|
|
489
|
+
return height;
|
|
490
|
+
}
|
|
491
|
+
function layoutTable(block, measure, paginator) {
|
|
492
|
+
if (measure.kind !== "table") {
|
|
493
|
+
throw new Error(`layoutTable: expected table measure`);
|
|
494
|
+
}
|
|
495
|
+
const rows = measure.rows;
|
|
496
|
+
if (rows.length === 0) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const headerRowCount = countHeaderRows(block);
|
|
500
|
+
const headerRowsHeight = getHeaderRowsHeight(measure, headerRowCount);
|
|
501
|
+
let currentRowIndex = 0;
|
|
502
|
+
while (currentRowIndex < rows.length) {
|
|
503
|
+
const state = paginator.getCurrentState();
|
|
504
|
+
const rawAvailableHeight = paginator.getAvailableHeight();
|
|
505
|
+
const isFirstFragment = currentRowIndex === 0;
|
|
506
|
+
const pendingSpacing = isFirstFragment ? state.trailingSpacing : 0;
|
|
507
|
+
const availableHeight = rawAvailableHeight - pendingSpacing;
|
|
508
|
+
const headerOverhead = !isFirstFragment && headerRowCount > 0 ? headerRowsHeight : 0;
|
|
509
|
+
let rowsHeight = 0;
|
|
510
|
+
let fittingRows = 0;
|
|
511
|
+
for (let j = currentRowIndex; j < rows.length; j++) {
|
|
512
|
+
const rowHeight = rows[j].height;
|
|
513
|
+
const totalWithRow = rowsHeight + rowHeight + headerOverhead;
|
|
514
|
+
if (totalWithRow <= availableHeight || fittingRows === 0) {
|
|
515
|
+
rowsHeight += rowHeight;
|
|
516
|
+
fittingRows++;
|
|
517
|
+
} else {
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
const fragmentHeight = rowsHeight + headerOverhead;
|
|
522
|
+
const isLastFragment = currentRowIndex + fittingRows >= rows.length;
|
|
523
|
+
let desiredX = paginator.getColumnX(state.columnIndex);
|
|
524
|
+
if (block.justification === "center") {
|
|
525
|
+
desiredX = desiredX + (paginator.columnWidth - measure.totalWidth) / 2;
|
|
526
|
+
} else if (block.justification === "right") {
|
|
527
|
+
desiredX = desiredX + paginator.columnWidth - measure.totalWidth;
|
|
528
|
+
} else if (block.indent) {
|
|
529
|
+
desiredX += block.indent;
|
|
530
|
+
}
|
|
531
|
+
const fragment = {
|
|
532
|
+
kind: "table",
|
|
533
|
+
blockId: block.id,
|
|
534
|
+
x: desiredX,
|
|
535
|
+
y: 0,
|
|
536
|
+
// Will be set by addFragment
|
|
537
|
+
width: measure.totalWidth,
|
|
538
|
+
height: fragmentHeight,
|
|
539
|
+
fromRow: currentRowIndex,
|
|
540
|
+
toRow: currentRowIndex + fittingRows,
|
|
541
|
+
pmStart: block.pmStart,
|
|
542
|
+
pmEnd: block.pmEnd,
|
|
543
|
+
continuesFromPrev: !isFirstFragment,
|
|
544
|
+
continuesOnNext: !isLastFragment,
|
|
545
|
+
headerRowCount: !isFirstFragment && headerRowCount > 0 ? headerRowCount : void 0
|
|
546
|
+
};
|
|
547
|
+
const result = paginator.addFragment(fragment, fragmentHeight, 0, 0);
|
|
548
|
+
fragment.y = result.y;
|
|
549
|
+
fragment.x = desiredX;
|
|
550
|
+
currentRowIndex += fittingRows;
|
|
551
|
+
if (currentRowIndex < rows.length) {
|
|
552
|
+
const nextRowHeight = rows[currentRowIndex].height + (headerRowCount > 0 ? headerRowsHeight : 0);
|
|
553
|
+
paginator.ensureFits(nextRowHeight);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function layoutFloatingTable(block, measure, paginator, contentWidth) {
|
|
558
|
+
if (measure.kind !== "table") {
|
|
559
|
+
throw new Error(`layoutFloatingTable: expected table measure`);
|
|
560
|
+
}
|
|
561
|
+
const state = paginator.getCurrentState();
|
|
562
|
+
const floating = block.floating;
|
|
563
|
+
const page = state.page;
|
|
564
|
+
const margins = page.margins;
|
|
565
|
+
const tableWidth = measure.totalWidth;
|
|
566
|
+
const tableHeight = measure.totalHeight;
|
|
567
|
+
const contentHeight = page.size.h - margins.top - margins.bottom;
|
|
568
|
+
let baseX = margins.left;
|
|
569
|
+
let baseY = margins.top;
|
|
570
|
+
if (floating?.horzAnchor === "page") baseX = 0;
|
|
571
|
+
if (floating?.vertAnchor === "page") baseY = 0;
|
|
572
|
+
let x = paginator.getColumnX(state.columnIndex);
|
|
573
|
+
if (floating?.tblpX !== void 0) {
|
|
574
|
+
x = baseX + floating.tblpX;
|
|
575
|
+
} else if (floating?.tblpXSpec) {
|
|
576
|
+
const spec = floating.tblpXSpec;
|
|
577
|
+
if (spec === "left" || spec === "inside") {
|
|
578
|
+
x = baseX;
|
|
579
|
+
} else if (spec === "right" || spec === "outside") {
|
|
580
|
+
x = baseX + contentWidth - tableWidth;
|
|
581
|
+
} else if (spec === "center") {
|
|
582
|
+
x = baseX + (contentWidth - tableWidth) / 2;
|
|
583
|
+
}
|
|
584
|
+
} else if (block.justification === "center") {
|
|
585
|
+
x = baseX + (contentWidth - tableWidth) / 2;
|
|
586
|
+
} else if (block.justification === "right") {
|
|
587
|
+
x = baseX + contentWidth - tableWidth;
|
|
588
|
+
}
|
|
589
|
+
let y = state.cursorY;
|
|
590
|
+
let usedExplicitY = false;
|
|
591
|
+
if (floating?.tblpY !== void 0) {
|
|
592
|
+
y = baseY + floating.tblpY;
|
|
593
|
+
usedExplicitY = true;
|
|
594
|
+
} else if (floating?.tblpYSpec) {
|
|
595
|
+
usedExplicitY = true;
|
|
596
|
+
const spec = floating.tblpYSpec;
|
|
597
|
+
if (spec === "top") {
|
|
598
|
+
y = baseY;
|
|
599
|
+
} else if (spec === "bottom") {
|
|
600
|
+
y = baseY + contentHeight - tableHeight;
|
|
601
|
+
} else if (spec === "center") {
|
|
602
|
+
y = baseY + (contentHeight - tableHeight) / 2;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (!usedExplicitY) {
|
|
606
|
+
const fitState = paginator.ensureFits(tableHeight);
|
|
607
|
+
y = fitState.cursorY;
|
|
608
|
+
}
|
|
609
|
+
const minX = margins.left;
|
|
610
|
+
const maxX = margins.left + contentWidth - tableWidth;
|
|
611
|
+
if (Number.isFinite(maxX)) {
|
|
612
|
+
x = Math.max(minX, Math.min(x, maxX));
|
|
613
|
+
}
|
|
614
|
+
const fragment = {
|
|
615
|
+
kind: "table",
|
|
616
|
+
blockId: block.id,
|
|
617
|
+
x,
|
|
618
|
+
y,
|
|
619
|
+
width: tableWidth,
|
|
620
|
+
height: tableHeight,
|
|
621
|
+
fromRow: 0,
|
|
622
|
+
toRow: block.rows.length,
|
|
623
|
+
pmStart: block.pmStart,
|
|
624
|
+
pmEnd: block.pmEnd,
|
|
625
|
+
isFloating: true
|
|
626
|
+
};
|
|
627
|
+
state.page.fragments.push(fragment);
|
|
628
|
+
}
|
|
629
|
+
function layoutImage(block, measure, paginator) {
|
|
630
|
+
if (measure.kind !== "image") {
|
|
631
|
+
throw new Error(`layoutImage: expected image measure`);
|
|
632
|
+
}
|
|
633
|
+
if (block.anchor?.isAnchored) {
|
|
634
|
+
layoutAnchoredImage(block, measure, paginator);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
const state = paginator.ensureFits(measure.height);
|
|
638
|
+
const fragment = {
|
|
639
|
+
kind: "image",
|
|
640
|
+
blockId: block.id,
|
|
641
|
+
x: paginator.getColumnX(state.columnIndex),
|
|
642
|
+
y: 0,
|
|
643
|
+
// Will be set by addFragment
|
|
644
|
+
width: measure.width,
|
|
645
|
+
height: measure.height,
|
|
646
|
+
pmStart: block.pmStart,
|
|
647
|
+
pmEnd: block.pmEnd
|
|
648
|
+
};
|
|
649
|
+
const result = paginator.addFragment(fragment, measure.height, 0, 0);
|
|
650
|
+
fragment.y = result.y;
|
|
651
|
+
}
|
|
652
|
+
function layoutAnchoredImage(block, measure, paginator) {
|
|
653
|
+
const state = paginator.getCurrentState();
|
|
654
|
+
const anchor = block.anchor;
|
|
655
|
+
const x = anchor.offsetH ?? paginator.getColumnX(state.columnIndex);
|
|
656
|
+
const y = anchor.offsetV ?? state.cursorY;
|
|
657
|
+
const fragment = {
|
|
658
|
+
kind: "image",
|
|
659
|
+
blockId: block.id,
|
|
660
|
+
x,
|
|
661
|
+
y,
|
|
662
|
+
width: measure.width,
|
|
663
|
+
height: measure.height,
|
|
664
|
+
pmStart: block.pmStart,
|
|
665
|
+
pmEnd: block.pmEnd,
|
|
666
|
+
isAnchored: true,
|
|
667
|
+
zIndex: anchor.behindDoc ? -1 : 1
|
|
668
|
+
};
|
|
669
|
+
state.page.fragments.push(fragment);
|
|
670
|
+
}
|
|
671
|
+
function layoutTextBox(block, measure, paginator) {
|
|
672
|
+
if (measure.kind !== "textBox") {
|
|
673
|
+
throw new Error(`layoutTextBox: expected textBox measure`);
|
|
674
|
+
}
|
|
675
|
+
const state = paginator.ensureFits(measure.height);
|
|
676
|
+
const fragment = {
|
|
677
|
+
kind: "textBox",
|
|
678
|
+
blockId: block.id,
|
|
679
|
+
x: paginator.getColumnX(state.columnIndex),
|
|
680
|
+
y: 0,
|
|
681
|
+
width: measure.width,
|
|
682
|
+
height: measure.height,
|
|
683
|
+
pmStart: block.pmStart,
|
|
684
|
+
pmEnd: block.pmEnd
|
|
685
|
+
};
|
|
686
|
+
const result = paginator.addFragment(fragment, measure.height, 0, 0);
|
|
687
|
+
fragment.y = result.y;
|
|
688
|
+
}
|
|
689
|
+
function handleSectionBreak(_block, paginator, nextSectionColumns, nextSectionType) {
|
|
690
|
+
const breakType = nextSectionType ?? "nextPage";
|
|
691
|
+
switch (breakType) {
|
|
692
|
+
case "nextPage":
|
|
693
|
+
paginator.forcePageBreak();
|
|
694
|
+
break;
|
|
695
|
+
case "evenPage": {
|
|
696
|
+
const state = paginator.forcePageBreak();
|
|
697
|
+
if (state.page.number % 2 !== 0) {
|
|
698
|
+
paginator.forcePageBreak();
|
|
699
|
+
}
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
case "oddPage": {
|
|
703
|
+
const state = paginator.forcePageBreak();
|
|
704
|
+
if (state.page.number % 2 === 0) {
|
|
705
|
+
paginator.forcePageBreak();
|
|
706
|
+
}
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
case "continuous":
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
paginator.updateColumns(nextSectionColumns);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// src/core/utils/fontResolver.ts
|
|
716
|
+
var DEFAULT_SINGLE_LINE_RATIO = 1.15;
|
|
717
|
+
var FONT_MAPPINGS = {
|
|
718
|
+
// Microsoft Office fonts -> Google equivalents (via Croscore)
|
|
719
|
+
calibri: {
|
|
720
|
+
googleFont: "Carlito",
|
|
721
|
+
category: "sans-serif",
|
|
722
|
+
fallbackStack: ["Calibri", "Carlito", "Arial", "Helvetica", "sans-serif"],
|
|
723
|
+
singleLineRatio: 1.2207
|
|
724
|
+
// 2500/2048
|
|
725
|
+
},
|
|
726
|
+
cambria: {
|
|
727
|
+
googleFont: "Caladea",
|
|
728
|
+
category: "serif",
|
|
729
|
+
fallbackStack: ["Cambria", "Caladea", "Georgia", "serif"],
|
|
730
|
+
singleLineRatio: 1.2676
|
|
731
|
+
// 2596/2048
|
|
732
|
+
},
|
|
733
|
+
arial: {
|
|
734
|
+
googleFont: "Arimo",
|
|
735
|
+
category: "sans-serif",
|
|
736
|
+
fallbackStack: ["Arial", "Arimo", "Helvetica", "sans-serif"],
|
|
737
|
+
singleLineRatio: 1.1172
|
|
738
|
+
// (1854+434)/2048 — no sTypoLineGap
|
|
739
|
+
},
|
|
740
|
+
"times new roman": {
|
|
741
|
+
googleFont: "Tinos",
|
|
742
|
+
category: "serif",
|
|
743
|
+
fallbackStack: ["Times New Roman", "Tinos", "Times", "serif"],
|
|
744
|
+
singleLineRatio: 1.1074
|
|
745
|
+
// (1825+443)/2048 — no sTypoLineGap
|
|
746
|
+
},
|
|
747
|
+
"courier new": {
|
|
748
|
+
googleFont: "Cousine",
|
|
749
|
+
category: "monospace",
|
|
750
|
+
fallbackStack: ["Courier New", "Cousine", "Courier", "monospace"],
|
|
751
|
+
singleLineRatio: 1.1328
|
|
752
|
+
// 2320/2048
|
|
753
|
+
},
|
|
754
|
+
// Additional common fonts
|
|
755
|
+
georgia: {
|
|
756
|
+
googleFont: "Tinos",
|
|
757
|
+
// Similar but not perfect match
|
|
758
|
+
category: "serif",
|
|
759
|
+
fallbackStack: ["Georgia", "Tinos", "Times New Roman", "serif"],
|
|
760
|
+
singleLineRatio: 1.1362
|
|
761
|
+
// 2327/2048
|
|
762
|
+
},
|
|
763
|
+
verdana: {
|
|
764
|
+
googleFont: "Open Sans",
|
|
765
|
+
// Similar sans-serif
|
|
766
|
+
category: "sans-serif",
|
|
767
|
+
fallbackStack: ["Verdana", "Open Sans", "Arial", "sans-serif"],
|
|
768
|
+
singleLineRatio: 1.2153
|
|
769
|
+
// 2489/2048
|
|
770
|
+
},
|
|
771
|
+
tahoma: {
|
|
772
|
+
googleFont: "Open Sans",
|
|
773
|
+
category: "sans-serif",
|
|
774
|
+
fallbackStack: ["Tahoma", "Open Sans", "Arial", "sans-serif"],
|
|
775
|
+
singleLineRatio: 1.2075
|
|
776
|
+
// 2472/2048
|
|
777
|
+
},
|
|
778
|
+
"trebuchet ms": {
|
|
779
|
+
googleFont: "Fira Sans",
|
|
780
|
+
category: "sans-serif",
|
|
781
|
+
fallbackStack: ["Trebuchet MS", "Fira Sans", "Arial", "sans-serif"],
|
|
782
|
+
singleLineRatio: 1.1431
|
|
783
|
+
// 2341/2048
|
|
784
|
+
},
|
|
785
|
+
"comic sans ms": {
|
|
786
|
+
googleFont: "Comic Neue",
|
|
787
|
+
category: "cursive",
|
|
788
|
+
fallbackStack: ["Comic Sans MS", "Comic Neue", "cursive"],
|
|
789
|
+
singleLineRatio: 1.3936
|
|
790
|
+
// 2854/2048
|
|
791
|
+
},
|
|
792
|
+
impact: {
|
|
793
|
+
googleFont: "Anton",
|
|
794
|
+
category: "sans-serif",
|
|
795
|
+
fallbackStack: ["Impact", "Anton", "Arial Black", "sans-serif"],
|
|
796
|
+
singleLineRatio: 1.2197
|
|
797
|
+
// 2498/2048
|
|
798
|
+
},
|
|
799
|
+
"palatino linotype": {
|
|
800
|
+
googleFont: "EB Garamond",
|
|
801
|
+
category: "serif",
|
|
802
|
+
fallbackStack: ["Palatino Linotype", "EB Garamond", "Palatino", "Georgia", "serif"],
|
|
803
|
+
singleLineRatio: 1.0259
|
|
804
|
+
// 2101/2048
|
|
805
|
+
},
|
|
806
|
+
"book antiqua": {
|
|
807
|
+
googleFont: "EB Garamond",
|
|
808
|
+
category: "serif",
|
|
809
|
+
fallbackStack: ["Book Antiqua", "EB Garamond", "Palatino", "Georgia", "serif"],
|
|
810
|
+
singleLineRatio: 1.0259
|
|
811
|
+
// 2101/2048
|
|
812
|
+
},
|
|
813
|
+
garamond: {
|
|
814
|
+
googleFont: "EB Garamond",
|
|
815
|
+
category: "serif",
|
|
816
|
+
fallbackStack: ["Garamond", "EB Garamond", "Georgia", "serif"],
|
|
817
|
+
singleLineRatio: 1.068
|
|
818
|
+
// 1068/1000
|
|
819
|
+
},
|
|
820
|
+
"century gothic": {
|
|
821
|
+
googleFont: "Questrial",
|
|
822
|
+
category: "sans-serif",
|
|
823
|
+
fallbackStack: ["Century Gothic", "Questrial", "Arial", "sans-serif"],
|
|
824
|
+
singleLineRatio: 1.1611
|
|
825
|
+
// 2378/2048
|
|
826
|
+
},
|
|
827
|
+
"lucida sans": {
|
|
828
|
+
googleFont: "Open Sans",
|
|
829
|
+
category: "sans-serif",
|
|
830
|
+
fallbackStack: ["Lucida Sans", "Open Sans", "Arial", "sans-serif"],
|
|
831
|
+
singleLineRatio: 1.1655
|
|
832
|
+
// 2387/2048
|
|
833
|
+
},
|
|
834
|
+
"lucida console": {
|
|
835
|
+
googleFont: "Cousine",
|
|
836
|
+
category: "monospace",
|
|
837
|
+
fallbackStack: ["Lucida Console", "Cousine", "Courier New", "monospace"],
|
|
838
|
+
singleLineRatio: 1.1387
|
|
839
|
+
// 2332/2048
|
|
840
|
+
},
|
|
841
|
+
consolas: {
|
|
842
|
+
googleFont: "Inconsolata",
|
|
843
|
+
category: "monospace",
|
|
844
|
+
fallbackStack: ["Consolas", "Inconsolata", "Cousine", "Courier New", "monospace"],
|
|
845
|
+
singleLineRatio: 1.1626
|
|
846
|
+
// 2381/2048
|
|
847
|
+
},
|
|
848
|
+
// CJK fonts
|
|
849
|
+
"ms mincho": {
|
|
850
|
+
googleFont: "Noto Serif JP",
|
|
851
|
+
category: "serif",
|
|
852
|
+
fallbackStack: ["MS Mincho", "Noto Serif JP", "serif"],
|
|
853
|
+
singleLineRatio: DEFAULT_SINGLE_LINE_RATIO
|
|
854
|
+
},
|
|
855
|
+
"ms gothic": {
|
|
856
|
+
googleFont: "Noto Sans JP",
|
|
857
|
+
category: "sans-serif",
|
|
858
|
+
fallbackStack: ["MS Gothic", "Noto Sans JP", "sans-serif"],
|
|
859
|
+
singleLineRatio: DEFAULT_SINGLE_LINE_RATIO
|
|
860
|
+
},
|
|
861
|
+
simhei: {
|
|
862
|
+
googleFont: "Noto Sans SC",
|
|
863
|
+
category: "sans-serif",
|
|
864
|
+
fallbackStack: ["SimHei", "Noto Sans SC", "sans-serif"],
|
|
865
|
+
singleLineRatio: DEFAULT_SINGLE_LINE_RATIO
|
|
866
|
+
},
|
|
867
|
+
simsun: {
|
|
868
|
+
googleFont: "Noto Serif SC",
|
|
869
|
+
category: "serif",
|
|
870
|
+
fallbackStack: ["SimSun", "Noto Serif SC", "serif"],
|
|
871
|
+
singleLineRatio: DEFAULT_SINGLE_LINE_RATIO
|
|
872
|
+
},
|
|
873
|
+
"malgun gothic": {
|
|
874
|
+
googleFont: "Noto Sans KR",
|
|
875
|
+
category: "sans-serif",
|
|
876
|
+
fallbackStack: ["Malgun Gothic", "Noto Sans KR", "sans-serif"],
|
|
877
|
+
singleLineRatio: DEFAULT_SINGLE_LINE_RATIO
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
var DEFAULT_FALLBACKS = {
|
|
881
|
+
"sans-serif": "Arial, Helvetica, sans-serif",
|
|
882
|
+
serif: "Times New Roman, Times, serif",
|
|
883
|
+
monospace: "Courier New, Courier, monospace",
|
|
884
|
+
cursive: "cursive",
|
|
885
|
+
fantasy: "fantasy",
|
|
886
|
+
"system-ui": "system-ui, sans-serif"
|
|
887
|
+
};
|
|
888
|
+
function detectFontCategory(fontName) {
|
|
889
|
+
const lower = fontName.toLowerCase();
|
|
890
|
+
if (lower.includes("mono") || lower.includes("courier") || lower.includes("consolas") || lower.includes("console") || lower.includes("code") || lower.includes("terminal")) {
|
|
891
|
+
return "monospace";
|
|
892
|
+
}
|
|
893
|
+
if (lower.includes("times") || lower.includes("georgia") || lower.includes("garamond") || lower.includes("palatino") || lower.includes("baskerville") || lower.includes("bodoni") || lower.includes("cambria") || lower.includes("mincho") || lower.includes("ming") || lower.includes("song") || lower.includes("serif")) {
|
|
894
|
+
return "serif";
|
|
895
|
+
}
|
|
896
|
+
if (lower.includes("script") || lower.includes("cursive") || lower.includes("comic") || lower.includes("brush") || lower.includes("hand")) {
|
|
897
|
+
return "cursive";
|
|
898
|
+
}
|
|
899
|
+
return "sans-serif";
|
|
900
|
+
}
|
|
901
|
+
function resolveFontFamily(docxFontName) {
|
|
902
|
+
const normalizedName = docxFontName.trim().toLowerCase();
|
|
903
|
+
const mapping = FONT_MAPPINGS[normalizedName];
|
|
904
|
+
if (mapping) {
|
|
905
|
+
return {
|
|
906
|
+
googleFont: mapping.googleFont,
|
|
907
|
+
cssFallback: mapping.fallbackStack.map(quoteFontName).join(", "),
|
|
908
|
+
originalFont: docxFontName,
|
|
909
|
+
hasGoogleEquivalent: true,
|
|
910
|
+
singleLineRatio: mapping.singleLineRatio
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
const category = detectFontCategory(docxFontName);
|
|
914
|
+
const defaultFallback = DEFAULT_FALLBACKS[category];
|
|
915
|
+
return {
|
|
916
|
+
googleFont: null,
|
|
917
|
+
cssFallback: `${quoteFontName(docxFontName)}, ${defaultFallback}`,
|
|
918
|
+
originalFont: docxFontName,
|
|
919
|
+
hasGoogleEquivalent: false,
|
|
920
|
+
singleLineRatio: DEFAULT_SINGLE_LINE_RATIO
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
function quoteFontName(fontName) {
|
|
924
|
+
if (["serif", "sans-serif", "monospace", "cursive", "fantasy", "system-ui"].includes(
|
|
925
|
+
fontName.toLowerCase()
|
|
926
|
+
)) {
|
|
927
|
+
return fontName;
|
|
928
|
+
}
|
|
929
|
+
if (/[\s,'"()]/.test(fontName)) {
|
|
930
|
+
return `"${fontName.replace(/"/g, '\\"')}"`;
|
|
931
|
+
}
|
|
932
|
+
return fontName;
|
|
933
|
+
}
|
|
934
|
+
function resolveThemeFont(themeRef, fontScheme) {
|
|
935
|
+
if (!fontScheme) {
|
|
936
|
+
return null;
|
|
937
|
+
}
|
|
938
|
+
const isMajor = themeRef.toLowerCase().startsWith("major");
|
|
939
|
+
const themeFont = isMajor ? fontScheme.majorFont : fontScheme.minorFont;
|
|
940
|
+
if (!themeFont) {
|
|
941
|
+
return null;
|
|
942
|
+
}
|
|
943
|
+
const ref = themeRef.toLowerCase();
|
|
944
|
+
if (ref.includes("eastasia") || ref.includes("ea")) {
|
|
945
|
+
return themeFont.ea ?? themeFont.latin ?? null;
|
|
946
|
+
}
|
|
947
|
+
if (ref.includes("cs") || ref.includes("bidi")) {
|
|
948
|
+
return themeFont.cs ?? themeFont.latin ?? null;
|
|
949
|
+
}
|
|
950
|
+
return themeFont.latin ?? null;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// src/core/layout-bridge/measuring/measureContainer.ts
|
|
954
|
+
var TWIPS_PER_INCH = 1440;
|
|
955
|
+
var PX_PER_INCH = 96;
|
|
956
|
+
var TWIPS_PER_PX = TWIPS_PER_INCH / PX_PER_INCH;
|
|
957
|
+
var DEFAULT_FONT_SIZE = 11;
|
|
958
|
+
var DEFAULT_FONT_FAMILY = "Calibri";
|
|
959
|
+
var DEFAULT_LINE_HEIGHT_MULTIPLIER = 1;
|
|
960
|
+
var DEFAULT_ASCENT_RATIO = 0.8;
|
|
961
|
+
var DEFAULT_DESCENT_RATIO = 0.2;
|
|
962
|
+
var canvasContext = null;
|
|
963
|
+
function getCanvasContext() {
|
|
964
|
+
if (!canvasContext) {
|
|
965
|
+
const canvas = typeof document !== "undefined" ? document.createElement("canvas") : null;
|
|
966
|
+
if (!canvas) {
|
|
967
|
+
throw new Error("Canvas not available. Ensure this runs in a DOM environment.");
|
|
968
|
+
}
|
|
969
|
+
canvasContext = canvas.getContext("2d");
|
|
970
|
+
if (!canvasContext) {
|
|
971
|
+
throw new Error("Failed to get 2D context from canvas");
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return canvasContext;
|
|
975
|
+
}
|
|
976
|
+
function resetCanvasContext() {
|
|
977
|
+
canvasContext = null;
|
|
978
|
+
}
|
|
979
|
+
var fontResolvedCache = /* @__PURE__ */ new Map();
|
|
980
|
+
function getResolvedData(fontFamily) {
|
|
981
|
+
let cached = fontResolvedCache.get(fontFamily);
|
|
982
|
+
if (cached === void 0) {
|
|
983
|
+
const resolved = resolveFontFamily(fontFamily);
|
|
984
|
+
cached = { cssFallback: resolved.cssFallback, singleLineRatio: resolved.singleLineRatio };
|
|
985
|
+
fontResolvedCache.set(fontFamily, cached);
|
|
986
|
+
}
|
|
987
|
+
return cached;
|
|
988
|
+
}
|
|
989
|
+
function getResolvedFallback(fontFamily) {
|
|
990
|
+
return getResolvedData(fontFamily).cssFallback;
|
|
991
|
+
}
|
|
992
|
+
function buildFontString(style) {
|
|
993
|
+
const parts = [];
|
|
994
|
+
if (style.italic) parts.push("italic");
|
|
995
|
+
if (style.bold) parts.push("bold");
|
|
996
|
+
const fontSizePt = style.fontSize ?? DEFAULT_FONT_SIZE;
|
|
997
|
+
const fontSizePx = ptToPx(fontSizePt);
|
|
998
|
+
parts.push(`${fontSizePx}px`);
|
|
999
|
+
const fontFamily = style.fontFamily ?? DEFAULT_FONT_FAMILY;
|
|
1000
|
+
parts.push(getResolvedFallback(fontFamily));
|
|
1001
|
+
return parts.join(" ");
|
|
1002
|
+
}
|
|
1003
|
+
function getFontMetrics(style) {
|
|
1004
|
+
const fontSize = style.fontSize ?? DEFAULT_FONT_SIZE;
|
|
1005
|
+
const fontFamily = style.fontFamily ?? DEFAULT_FONT_FAMILY;
|
|
1006
|
+
const fontSizePx = ptToPx(fontSize);
|
|
1007
|
+
let ascent = fontSizePx * DEFAULT_ASCENT_RATIO;
|
|
1008
|
+
let descent = fontSizePx * DEFAULT_DESCENT_RATIO;
|
|
1009
|
+
let lineHeight = fontSizePx * DEFAULT_LINE_HEIGHT_MULTIPLIER;
|
|
1010
|
+
try {
|
|
1011
|
+
const ctx = getCanvasContext();
|
|
1012
|
+
ctx.font = buildFontString(style);
|
|
1013
|
+
const metrics = ctx.measureText("Hg");
|
|
1014
|
+
if (typeof metrics.actualBoundingBoxAscent === "number" && typeof metrics.actualBoundingBoxDescent === "number") {
|
|
1015
|
+
ascent = metrics.actualBoundingBoxAscent;
|
|
1016
|
+
descent = metrics.actualBoundingBoxDescent;
|
|
1017
|
+
}
|
|
1018
|
+
} catch {
|
|
1019
|
+
}
|
|
1020
|
+
lineHeight = Math.max(lineHeight, ascent + descent);
|
|
1021
|
+
const singleLineRatio = getResolvedData(fontFamily).singleLineRatio;
|
|
1022
|
+
return {
|
|
1023
|
+
fontSize,
|
|
1024
|
+
// Keep in points for reference
|
|
1025
|
+
ascent,
|
|
1026
|
+
descent,
|
|
1027
|
+
lineHeight,
|
|
1028
|
+
fontFamily,
|
|
1029
|
+
singleLineRatio
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
function measureTextWidth(text, style) {
|
|
1033
|
+
if (!text) return 0;
|
|
1034
|
+
const ctx = getCanvasContext();
|
|
1035
|
+
ctx.font = buildFontString(style);
|
|
1036
|
+
const metrics = ctx.measureText(text);
|
|
1037
|
+
let width = metrics.width;
|
|
1038
|
+
if (style.letterSpacing && text.length > 1) {
|
|
1039
|
+
width += style.letterSpacing * (text.length - 1);
|
|
1040
|
+
}
|
|
1041
|
+
return width;
|
|
1042
|
+
}
|
|
1043
|
+
function measureRun(text, style) {
|
|
1044
|
+
const metrics = getFontMetrics(style);
|
|
1045
|
+
if (!text) {
|
|
1046
|
+
return {
|
|
1047
|
+
width: 0,
|
|
1048
|
+
charWidths: [],
|
|
1049
|
+
metrics
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
const ctx = getCanvasContext();
|
|
1053
|
+
ctx.font = buildFontString(style);
|
|
1054
|
+
const letterSpacing = style.letterSpacing ?? 0;
|
|
1055
|
+
const charWidths = [];
|
|
1056
|
+
let totalWidth = 0;
|
|
1057
|
+
for (let i = 0; i < text.length; i++) {
|
|
1058
|
+
const char = text[i];
|
|
1059
|
+
const charMetrics = ctx.measureText(char);
|
|
1060
|
+
let charWidth = charMetrics.width;
|
|
1061
|
+
if (letterSpacing && i < text.length - 1) {
|
|
1062
|
+
charWidth += letterSpacing;
|
|
1063
|
+
}
|
|
1064
|
+
charWidths.push(charWidth);
|
|
1065
|
+
totalWidth += charWidth;
|
|
1066
|
+
}
|
|
1067
|
+
return {
|
|
1068
|
+
width: totalWidth,
|
|
1069
|
+
charWidths,
|
|
1070
|
+
metrics
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
function findCharacterAtX(x, charWidths) {
|
|
1074
|
+
if (charWidths.length === 0) return 0;
|
|
1075
|
+
if (x <= 0) return 0;
|
|
1076
|
+
let accumulatedWidth = 0;
|
|
1077
|
+
for (let i = 0; i < charWidths.length; i++) {
|
|
1078
|
+
const charWidth = charWidths[i];
|
|
1079
|
+
const charMidpoint = accumulatedWidth + charWidth / 2;
|
|
1080
|
+
if (x <= charMidpoint) {
|
|
1081
|
+
return i;
|
|
1082
|
+
}
|
|
1083
|
+
accumulatedWidth += charWidth;
|
|
1084
|
+
}
|
|
1085
|
+
return charWidths.length;
|
|
1086
|
+
}
|
|
1087
|
+
function twipsToPx(twips) {
|
|
1088
|
+
return twips / TWIPS_PER_PX;
|
|
1089
|
+
}
|
|
1090
|
+
function ptToPx(pt) {
|
|
1091
|
+
return pt * PX_PER_INCH / 72;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// src/core/layout-bridge/hitTest.ts
|
|
1095
|
+
function getPageTop(layout, pageIndex) {
|
|
1096
|
+
const pageGap = layout.pageGap ?? 0;
|
|
1097
|
+
let y = 0;
|
|
1098
|
+
for (let i = 0; i < pageIndex && i < layout.pages.length; i++) {
|
|
1099
|
+
const page = layout.pages[i];
|
|
1100
|
+
const pageHeight = page.size?.h ?? layout.pageSize.h;
|
|
1101
|
+
y += pageHeight + pageGap;
|
|
1102
|
+
}
|
|
1103
|
+
return y;
|
|
1104
|
+
}
|
|
1105
|
+
function findBlockIndexById(blocks, blockId) {
|
|
1106
|
+
return blocks.findIndex((block) => block.id === blockId);
|
|
1107
|
+
}
|
|
1108
|
+
function calculateParagraphFragmentHeight(fragment, measure) {
|
|
1109
|
+
let height = 0;
|
|
1110
|
+
for (let i = fragment.fromLine; i < fragment.toLine && i < measure.lines.length; i++) {
|
|
1111
|
+
height += measure.lines[i].lineHeight;
|
|
1112
|
+
}
|
|
1113
|
+
return height;
|
|
1114
|
+
}
|
|
1115
|
+
function hitTestFragment(pageHit, blocks, measures, pagePoint) {
|
|
1116
|
+
const sortedFragments = [...pageHit.page.fragments].sort((a, b) => {
|
|
1117
|
+
const dy = a.y - b.y;
|
|
1118
|
+
if (Math.abs(dy) > 0.5) return dy;
|
|
1119
|
+
return a.x - b.x;
|
|
1120
|
+
});
|
|
1121
|
+
for (const fragment of sortedFragments) {
|
|
1122
|
+
const blockIndex = findBlockIndexById(blocks, fragment.blockId);
|
|
1123
|
+
if (blockIndex === -1) continue;
|
|
1124
|
+
const block = blocks[blockIndex];
|
|
1125
|
+
const measure = measures[blockIndex];
|
|
1126
|
+
if (!block || !measure) continue;
|
|
1127
|
+
let fragmentHeight;
|
|
1128
|
+
if (fragment.kind === "paragraph") {
|
|
1129
|
+
if (block.kind !== "paragraph" || measure.kind !== "paragraph") continue;
|
|
1130
|
+
fragmentHeight = calculateParagraphFragmentHeight(fragment, measure);
|
|
1131
|
+
} else if (fragment.kind === "table") {
|
|
1132
|
+
fragmentHeight = fragment.height;
|
|
1133
|
+
} else if (fragment.kind === "image") {
|
|
1134
|
+
fragmentHeight = fragment.height;
|
|
1135
|
+
} else {
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
const withinX = pagePoint.x >= fragment.x && pagePoint.x <= fragment.x + fragment.width;
|
|
1139
|
+
const withinY = pagePoint.y >= fragment.y && pagePoint.y <= fragment.y + fragmentHeight;
|
|
1140
|
+
if (withinX && withinY) {
|
|
1141
|
+
return {
|
|
1142
|
+
fragment,
|
|
1143
|
+
block,
|
|
1144
|
+
measure,
|
|
1145
|
+
pageIndex: pageHit.pageIndex,
|
|
1146
|
+
localX: pagePoint.x - fragment.x,
|
|
1147
|
+
localY: pagePoint.y - fragment.y
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
return null;
|
|
1152
|
+
}
|
|
1153
|
+
function hitTestTableCell(pageHit, blocks, measures, pagePoint) {
|
|
1154
|
+
for (const fragment of pageHit.page.fragments) {
|
|
1155
|
+
if (fragment.kind !== "table") continue;
|
|
1156
|
+
const tableFragment = fragment;
|
|
1157
|
+
const withinX = pagePoint.x >= tableFragment.x && pagePoint.x <= tableFragment.x + tableFragment.width;
|
|
1158
|
+
const withinY = pagePoint.y >= tableFragment.y && pagePoint.y <= tableFragment.y + tableFragment.height;
|
|
1159
|
+
if (!withinX || !withinY) continue;
|
|
1160
|
+
const blockIndex = findBlockIndexById(blocks, tableFragment.blockId);
|
|
1161
|
+
if (blockIndex === -1) continue;
|
|
1162
|
+
const block = blocks[blockIndex];
|
|
1163
|
+
const measure = measures[blockIndex];
|
|
1164
|
+
if (!block || block.kind !== "table" || !measure || measure.kind !== "table") continue;
|
|
1165
|
+
const tableBlock = block;
|
|
1166
|
+
const tableMeasure = measure;
|
|
1167
|
+
const localX = pagePoint.x - tableFragment.x;
|
|
1168
|
+
const localY = pagePoint.y - tableFragment.y;
|
|
1169
|
+
const headerRowCount = tableFragment.headerRowCount ?? 0;
|
|
1170
|
+
const headerHeight = headerRowCount > 0 && tableFragment.continuesFromPrev ? getHeaderRowsHeight(tableMeasure, headerRowCount) : 0;
|
|
1171
|
+
let rowY = 0;
|
|
1172
|
+
let rowIndex = -1;
|
|
1173
|
+
if (tableMeasure.rows.length === 0 || tableBlock.rows.length === 0) continue;
|
|
1174
|
+
if (headerHeight > 0 && localY < headerHeight) {
|
|
1175
|
+
let hdrY = 0;
|
|
1176
|
+
for (let h = 0; h < headerRowCount && h < tableMeasure.rows.length; h++) {
|
|
1177
|
+
const hdrRowMeasure = tableMeasure.rows[h];
|
|
1178
|
+
if (localY >= hdrY && localY < hdrY + hdrRowMeasure.height) {
|
|
1179
|
+
rowIndex = h;
|
|
1180
|
+
break;
|
|
1181
|
+
}
|
|
1182
|
+
hdrY += hdrRowMeasure.height;
|
|
1183
|
+
}
|
|
1184
|
+
if (rowIndex === -1) rowIndex = 0;
|
|
1185
|
+
} else {
|
|
1186
|
+
const adjustedLocalY = localY - headerHeight;
|
|
1187
|
+
for (let r = tableFragment.fromRow; r < tableFragment.toRow && r < tableMeasure.rows.length; r++) {
|
|
1188
|
+
const rowMeasure2 = tableMeasure.rows[r];
|
|
1189
|
+
if (adjustedLocalY >= rowY && adjustedLocalY < rowY + rowMeasure2.height) {
|
|
1190
|
+
rowIndex = r;
|
|
1191
|
+
break;
|
|
1192
|
+
}
|
|
1193
|
+
rowY += rowMeasure2.height;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
if (rowIndex === -1) {
|
|
1197
|
+
rowIndex = Math.min(tableFragment.toRow - 1, tableMeasure.rows.length - 1);
|
|
1198
|
+
if (rowIndex < tableFragment.fromRow) continue;
|
|
1199
|
+
}
|
|
1200
|
+
const rowMeasure = tableMeasure.rows[rowIndex];
|
|
1201
|
+
const row = tableBlock.rows[rowIndex];
|
|
1202
|
+
if (!rowMeasure || !row) continue;
|
|
1203
|
+
let colX = 0;
|
|
1204
|
+
let colIndex = -1;
|
|
1205
|
+
if (rowMeasure.cells.length === 0 || row.cells.length === 0) continue;
|
|
1206
|
+
for (let c = 0; c < rowMeasure.cells.length; c++) {
|
|
1207
|
+
const cellMeasure2 = rowMeasure.cells[c];
|
|
1208
|
+
if (localX >= colX && localX < colX + cellMeasure2.width) {
|
|
1209
|
+
colIndex = c;
|
|
1210
|
+
break;
|
|
1211
|
+
}
|
|
1212
|
+
colX += cellMeasure2.width;
|
|
1213
|
+
}
|
|
1214
|
+
if (colIndex === -1) {
|
|
1215
|
+
colIndex = rowMeasure.cells.length - 1;
|
|
1216
|
+
if (colIndex < 0) continue;
|
|
1217
|
+
}
|
|
1218
|
+
const cellMeasure = rowMeasure.cells[colIndex];
|
|
1219
|
+
const cell = row.cells[colIndex];
|
|
1220
|
+
if (!cellMeasure || !cell) continue;
|
|
1221
|
+
let rowTop = 0;
|
|
1222
|
+
const isClickOnHeader = headerHeight > 0 && rowIndex < headerRowCount;
|
|
1223
|
+
if (isClickOnHeader) {
|
|
1224
|
+
for (let r = 0; r < rowIndex; r++) {
|
|
1225
|
+
rowTop += tableMeasure.rows[r]?.height ?? 0;
|
|
1226
|
+
}
|
|
1227
|
+
} else {
|
|
1228
|
+
rowTop = headerHeight;
|
|
1229
|
+
for (let r = tableFragment.fromRow; r < rowIndex; r++) {
|
|
1230
|
+
rowTop += tableMeasure.rows[r]?.height ?? 0;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
let colLeft = 0;
|
|
1234
|
+
for (let c = 0; c < colIndex; c++) {
|
|
1235
|
+
colLeft += rowMeasure.cells[c]?.width ?? 0;
|
|
1236
|
+
}
|
|
1237
|
+
let cellBlock;
|
|
1238
|
+
let cellBlockMeasure;
|
|
1239
|
+
if (cell.blocks && cell.blocks.length > 0) {
|
|
1240
|
+
const firstBlock = cell.blocks[0];
|
|
1241
|
+
const firstMeasure = cellMeasure.blocks[0];
|
|
1242
|
+
if (firstBlock.kind === "paragraph" && firstMeasure?.kind === "paragraph") {
|
|
1243
|
+
cellBlock = firstBlock;
|
|
1244
|
+
cellBlockMeasure = firstMeasure;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
const cellLocalX = localX - colLeft;
|
|
1248
|
+
const cellLocalY = localY - rowTop;
|
|
1249
|
+
return {
|
|
1250
|
+
fragment: tableFragment,
|
|
1251
|
+
block: tableBlock,
|
|
1252
|
+
measure: tableMeasure,
|
|
1253
|
+
pageIndex: pageHit.pageIndex,
|
|
1254
|
+
rowIndex,
|
|
1255
|
+
colIndex,
|
|
1256
|
+
cellBlock,
|
|
1257
|
+
cellMeasure: cellBlockMeasure,
|
|
1258
|
+
cellLocalX: Math.max(0, cellLocalX),
|
|
1259
|
+
cellLocalY: Math.max(0, cellLocalY)
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
return null;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// src/core/layout-bridge/selectionRects.ts
|
|
1266
|
+
function runToFontStyle(run) {
|
|
1267
|
+
return {
|
|
1268
|
+
fontFamily: run.fontFamily ?? "Arial",
|
|
1269
|
+
fontSize: run.fontSize ?? 12,
|
|
1270
|
+
bold: run.bold,
|
|
1271
|
+
italic: run.italic,
|
|
1272
|
+
letterSpacing: run.letterSpacing
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
function findBlockById(blocks, blockId) {
|
|
1276
|
+
return blocks.findIndex((block) => block.id === blockId);
|
|
1277
|
+
}
|
|
1278
|
+
function computeLinePmRange(block, line) {
|
|
1279
|
+
const blockPmStart = block.pmStart ?? 0;
|
|
1280
|
+
const contentStart = blockPmStart + 1;
|
|
1281
|
+
let charOffset = 0;
|
|
1282
|
+
let pmStart;
|
|
1283
|
+
for (let runIndex = 0; runIndex < block.runs.length && runIndex <= line.toRun; runIndex++) {
|
|
1284
|
+
const run = block.runs[runIndex];
|
|
1285
|
+
if (!run) continue;
|
|
1286
|
+
if (runIndex < line.fromRun) {
|
|
1287
|
+
if (run.kind === "text") {
|
|
1288
|
+
charOffset += (run.text ?? "").length;
|
|
1289
|
+
} else {
|
|
1290
|
+
charOffset += 1;
|
|
1291
|
+
}
|
|
1292
|
+
} else if (runIndex === line.fromRun) {
|
|
1293
|
+
charOffset += line.fromChar;
|
|
1294
|
+
pmStart = contentStart + charOffset;
|
|
1295
|
+
break;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
if (pmStart === void 0) {
|
|
1299
|
+
pmStart = contentStart;
|
|
1300
|
+
}
|
|
1301
|
+
let lineLength = 0;
|
|
1302
|
+
for (let runIndex = line.fromRun; runIndex <= line.toRun && runIndex < block.runs.length; runIndex++) {
|
|
1303
|
+
const run = block.runs[runIndex];
|
|
1304
|
+
if (!run) continue;
|
|
1305
|
+
if (run.kind === "text") {
|
|
1306
|
+
const text = run.text ?? "";
|
|
1307
|
+
const start = runIndex === line.fromRun ? line.fromChar : 0;
|
|
1308
|
+
const end = runIndex === line.toRun ? line.toChar : text.length;
|
|
1309
|
+
lineLength += end - start;
|
|
1310
|
+
} else {
|
|
1311
|
+
lineLength += 1;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
const pmEnd = pmStart + lineLength;
|
|
1315
|
+
return { pmStart, pmEnd };
|
|
1316
|
+
}
|
|
1317
|
+
function findLinesInRange(block, measure, from, to) {
|
|
1318
|
+
const result = [];
|
|
1319
|
+
for (let i = 0; i < measure.lines.length; i++) {
|
|
1320
|
+
const line = measure.lines[i];
|
|
1321
|
+
const range = computeLinePmRange(block, line);
|
|
1322
|
+
if (range.pmStart === void 0 || range.pmEnd === void 0) continue;
|
|
1323
|
+
if (range.pmEnd > from && range.pmStart < to) {
|
|
1324
|
+
result.push({ line, index: i });
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
return result;
|
|
1328
|
+
}
|
|
1329
|
+
function pmPosToCharOffset(block, line, pmPos) {
|
|
1330
|
+
const range = computeLinePmRange(block, line);
|
|
1331
|
+
if (range.pmStart === void 0) return 0;
|
|
1332
|
+
return Math.max(0, pmPos - range.pmStart);
|
|
1333
|
+
}
|
|
1334
|
+
function charOffsetToX(block, line, charOffset, _availableWidth) {
|
|
1335
|
+
let x = 0;
|
|
1336
|
+
let charsProcessed = 0;
|
|
1337
|
+
for (let runIndex = line.fromRun; runIndex <= line.toRun && runIndex < block.runs.length; runIndex++) {
|
|
1338
|
+
const run = block.runs[runIndex];
|
|
1339
|
+
if (!run) continue;
|
|
1340
|
+
if (run.kind === "tab") {
|
|
1341
|
+
const tabWidth = run.width ?? 48;
|
|
1342
|
+
if (charsProcessed + 1 >= charOffset) {
|
|
1343
|
+
if (charOffset <= charsProcessed) return x;
|
|
1344
|
+
return x + tabWidth;
|
|
1345
|
+
}
|
|
1346
|
+
x += tabWidth;
|
|
1347
|
+
charsProcessed += 1;
|
|
1348
|
+
continue;
|
|
1349
|
+
}
|
|
1350
|
+
if (run.kind === "image") {
|
|
1351
|
+
const imageWidth = run.width;
|
|
1352
|
+
if (charsProcessed + 1 >= charOffset) {
|
|
1353
|
+
if (charOffset <= charsProcessed) return x;
|
|
1354
|
+
return x + imageWidth;
|
|
1355
|
+
}
|
|
1356
|
+
x += imageWidth;
|
|
1357
|
+
charsProcessed += 1;
|
|
1358
|
+
continue;
|
|
1359
|
+
}
|
|
1360
|
+
if (run.kind === "lineBreak") {
|
|
1361
|
+
if (charOffset <= charsProcessed) return x;
|
|
1362
|
+
charsProcessed += 1;
|
|
1363
|
+
continue;
|
|
1364
|
+
}
|
|
1365
|
+
if (run.kind === "text") {
|
|
1366
|
+
const text = run.text ?? "";
|
|
1367
|
+
const isFirstRun = runIndex === line.fromRun;
|
|
1368
|
+
const isLastRun = runIndex === line.toRun;
|
|
1369
|
+
const start = isFirstRun ? line.fromChar : 0;
|
|
1370
|
+
const end = isLastRun ? line.toChar : text.length;
|
|
1371
|
+
const lineText = text.slice(start, end);
|
|
1372
|
+
if (charsProcessed + lineText.length >= charOffset) {
|
|
1373
|
+
const charInRun = charOffset - charsProcessed;
|
|
1374
|
+
const style2 = runToFontStyle(run);
|
|
1375
|
+
const measurement2 = measureRun(lineText.slice(0, charInRun), style2);
|
|
1376
|
+
return x + measurement2.width;
|
|
1377
|
+
}
|
|
1378
|
+
const style = runToFontStyle(run);
|
|
1379
|
+
const measurement = measureRun(lineText, style);
|
|
1380
|
+
x += measurement.width;
|
|
1381
|
+
charsProcessed += lineText.length;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
return x;
|
|
1385
|
+
}
|
|
1386
|
+
function lineHeightBefore(measure, lineIndex) {
|
|
1387
|
+
let height = 0;
|
|
1388
|
+
for (let i = 0; i < lineIndex && i < measure.lines.length; i++) {
|
|
1389
|
+
height += measure.lines[i].lineHeight;
|
|
1390
|
+
}
|
|
1391
|
+
return height;
|
|
1392
|
+
}
|
|
1393
|
+
function selectionToRects(layout, blocks, measures, from, to) {
|
|
1394
|
+
if (from === to) {
|
|
1395
|
+
return [];
|
|
1396
|
+
}
|
|
1397
|
+
const selFrom = Math.min(from, to);
|
|
1398
|
+
const selTo = Math.max(from, to);
|
|
1399
|
+
const rects = [];
|
|
1400
|
+
for (let pageIndex = 0; pageIndex < layout.pages.length; pageIndex++) {
|
|
1401
|
+
const page = layout.pages[pageIndex];
|
|
1402
|
+
const pageTopY = getPageTop(layout, pageIndex);
|
|
1403
|
+
for (const fragment of page.fragments) {
|
|
1404
|
+
if (fragment.kind === "paragraph") {
|
|
1405
|
+
const blockIndex = findBlockById(blocks, fragment.blockId);
|
|
1406
|
+
if (blockIndex === -1) continue;
|
|
1407
|
+
const block = blocks[blockIndex];
|
|
1408
|
+
const measure = measures[blockIndex];
|
|
1409
|
+
if (!block || block.kind !== "paragraph") continue;
|
|
1410
|
+
if (!measure || measure.kind !== "paragraph") continue;
|
|
1411
|
+
const paragraphBlock = block;
|
|
1412
|
+
const paragraphMeasure = measure;
|
|
1413
|
+
const paragraphFragment = fragment;
|
|
1414
|
+
const intersectingLines = findLinesInRange(
|
|
1415
|
+
paragraphBlock,
|
|
1416
|
+
paragraphMeasure,
|
|
1417
|
+
selFrom,
|
|
1418
|
+
selTo
|
|
1419
|
+
);
|
|
1420
|
+
for (const { line, index } of intersectingLines) {
|
|
1421
|
+
if (index < paragraphFragment.fromLine || index >= paragraphFragment.toLine) {
|
|
1422
|
+
continue;
|
|
1423
|
+
}
|
|
1424
|
+
const range = computeLinePmRange(paragraphBlock, line);
|
|
1425
|
+
if (range.pmStart === void 0 || range.pmEnd === void 0) continue;
|
|
1426
|
+
const sliceFrom = Math.max(range.pmStart, selFrom);
|
|
1427
|
+
const sliceTo = Math.min(range.pmEnd, selTo);
|
|
1428
|
+
if (sliceFrom >= sliceTo) continue;
|
|
1429
|
+
const charOffsetFrom = pmPosToCharOffset(paragraphBlock, line, sliceFrom);
|
|
1430
|
+
const charOffsetTo = pmPosToCharOffset(paragraphBlock, line, sliceTo);
|
|
1431
|
+
const indent = paragraphBlock.attrs?.indent;
|
|
1432
|
+
const indentLeft = indent?.left ?? 0;
|
|
1433
|
+
const indentRight = indent?.right ?? 0;
|
|
1434
|
+
const availableWidth = Math.max(0, fragment.width - indentLeft - indentRight);
|
|
1435
|
+
const startX = charOffsetToX(paragraphBlock, line, charOffsetFrom, availableWidth);
|
|
1436
|
+
const endX = charOffsetToX(paragraphBlock, line, charOffsetTo, availableWidth);
|
|
1437
|
+
const alignment = paragraphBlock.attrs?.alignment ?? "left";
|
|
1438
|
+
let alignmentOffset = 0;
|
|
1439
|
+
if (alignment === "center") {
|
|
1440
|
+
alignmentOffset = Math.max(0, (availableWidth - line.width) / 2);
|
|
1441
|
+
} else if (alignment === "right") {
|
|
1442
|
+
alignmentOffset = Math.max(0, availableWidth - line.width);
|
|
1443
|
+
}
|
|
1444
|
+
const lineOffset = lineHeightBefore(paragraphMeasure, index) - lineHeightBefore(paragraphMeasure, paragraphFragment.fromLine);
|
|
1445
|
+
const rectX = fragment.x + indentLeft + alignmentOffset + Math.min(startX, endX);
|
|
1446
|
+
const rectWidth = Math.max(1, Math.abs(endX - startX));
|
|
1447
|
+
const rectY = fragment.y + lineOffset;
|
|
1448
|
+
rects.push({
|
|
1449
|
+
x: rectX,
|
|
1450
|
+
y: rectY + pageTopY,
|
|
1451
|
+
width: rectWidth,
|
|
1452
|
+
height: line.lineHeight,
|
|
1453
|
+
pageIndex
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
if (fragment.kind === "table") {
|
|
1458
|
+
const blockIndex = findBlockById(blocks, fragment.blockId);
|
|
1459
|
+
if (blockIndex === -1) continue;
|
|
1460
|
+
const block = blocks[blockIndex];
|
|
1461
|
+
const measure = measures[blockIndex];
|
|
1462
|
+
if (!block || block.kind !== "table") continue;
|
|
1463
|
+
if (!measure || measure.kind !== "table") continue;
|
|
1464
|
+
const tableBlock = block;
|
|
1465
|
+
const tableMeasure = measure;
|
|
1466
|
+
const tableFragment = fragment;
|
|
1467
|
+
const hdrCount = tableFragment.headerRowCount ?? 0;
|
|
1468
|
+
const headerOffset = hdrCount > 0 && tableFragment.continuesFromPrev ? getHeaderRowsHeight(tableMeasure, hdrCount) : 0;
|
|
1469
|
+
let rowY = headerOffset;
|
|
1470
|
+
for (let rowIndex = tableFragment.fromRow; rowIndex < tableFragment.toRow && rowIndex < tableBlock.rows.length; rowIndex++) {
|
|
1471
|
+
const row = tableBlock.rows[rowIndex];
|
|
1472
|
+
const rowMeasure = tableMeasure.rows[rowIndex];
|
|
1473
|
+
if (!row || !rowMeasure) continue;
|
|
1474
|
+
let cellX = 0;
|
|
1475
|
+
for (let cellIndex = 0; cellIndex < row.cells.length; cellIndex++) {
|
|
1476
|
+
const cell = row.cells[cellIndex];
|
|
1477
|
+
const cellMeasure = rowMeasure.cells[cellIndex];
|
|
1478
|
+
if (!cell || !cellMeasure) continue;
|
|
1479
|
+
for (let blockIdx = 0; blockIdx < cell.blocks.length; blockIdx++) {
|
|
1480
|
+
const cellBlock = cell.blocks[blockIdx];
|
|
1481
|
+
const cellBlockMeasure = cellMeasure.blocks[blockIdx];
|
|
1482
|
+
if (!cellBlock || cellBlock.kind !== "paragraph") continue;
|
|
1483
|
+
if (!cellBlockMeasure || cellBlockMeasure.kind !== "paragraph") continue;
|
|
1484
|
+
const paragraphBlock = cellBlock;
|
|
1485
|
+
const paragraphMeasure = cellBlockMeasure;
|
|
1486
|
+
const intersectingLines = findLinesInRange(
|
|
1487
|
+
paragraphBlock,
|
|
1488
|
+
paragraphMeasure,
|
|
1489
|
+
selFrom,
|
|
1490
|
+
selTo
|
|
1491
|
+
);
|
|
1492
|
+
let blockY = 0;
|
|
1493
|
+
for (const { line, index } of intersectingLines) {
|
|
1494
|
+
const range = computeLinePmRange(paragraphBlock, line);
|
|
1495
|
+
if (range.pmStart === void 0 || range.pmEnd === void 0) continue;
|
|
1496
|
+
const sliceFrom = Math.max(range.pmStart, selFrom);
|
|
1497
|
+
const sliceTo = Math.min(range.pmEnd, selTo);
|
|
1498
|
+
if (sliceFrom >= sliceTo) continue;
|
|
1499
|
+
const charOffsetFrom = pmPosToCharOffset(paragraphBlock, line, sliceFrom);
|
|
1500
|
+
const charOffsetTo = pmPosToCharOffset(paragraphBlock, line, sliceTo);
|
|
1501
|
+
const startX = charOffsetToX(
|
|
1502
|
+
paragraphBlock,
|
|
1503
|
+
line,
|
|
1504
|
+
charOffsetFrom,
|
|
1505
|
+
cellMeasure.width
|
|
1506
|
+
);
|
|
1507
|
+
const endX = charOffsetToX(paragraphBlock, line, charOffsetTo, cellMeasure.width);
|
|
1508
|
+
const lineY = lineHeightBefore(paragraphMeasure, index);
|
|
1509
|
+
rects.push({
|
|
1510
|
+
x: tableFragment.x + cellX + Math.min(startX, endX),
|
|
1511
|
+
y: tableFragment.y + rowY + blockY + lineY + pageTopY,
|
|
1512
|
+
width: Math.max(1, Math.abs(endX - startX)),
|
|
1513
|
+
height: line.lineHeight,
|
|
1514
|
+
pageIndex
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
blockY += paragraphMeasure.totalHeight;
|
|
1518
|
+
}
|
|
1519
|
+
cellX += cellMeasure.width;
|
|
1520
|
+
}
|
|
1521
|
+
rowY += rowMeasure.height;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
if (fragment.kind === "image") {
|
|
1525
|
+
const blockPmStart = fragment.pmStart ?? 0;
|
|
1526
|
+
const blockPmEnd = fragment.pmEnd ?? blockPmStart + 1;
|
|
1527
|
+
if (blockPmEnd > selFrom && blockPmStart < selTo) {
|
|
1528
|
+
rects.push({
|
|
1529
|
+
x: fragment.x,
|
|
1530
|
+
y: fragment.y + pageTopY,
|
|
1531
|
+
width: fragment.width,
|
|
1532
|
+
height: fragment.height,
|
|
1533
|
+
pageIndex
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
return rects;
|
|
1540
|
+
}
|
|
1541
|
+
function getCaretPosition(layout, blocks, measures, pmPosition) {
|
|
1542
|
+
for (let pageIndex = 0; pageIndex < layout.pages.length; pageIndex++) {
|
|
1543
|
+
const page = layout.pages[pageIndex];
|
|
1544
|
+
const pageTopY = getPageTop(layout, pageIndex);
|
|
1545
|
+
for (const fragment of page.fragments) {
|
|
1546
|
+
if (fragment.kind === "paragraph") {
|
|
1547
|
+
const blockIndex = findBlockById(blocks, fragment.blockId);
|
|
1548
|
+
if (blockIndex === -1) continue;
|
|
1549
|
+
const block = blocks[blockIndex];
|
|
1550
|
+
const measure = measures[blockIndex];
|
|
1551
|
+
if (!block || block.kind !== "paragraph") continue;
|
|
1552
|
+
if (!measure || measure.kind !== "paragraph") continue;
|
|
1553
|
+
const paragraphBlock = block;
|
|
1554
|
+
const paragraphMeasure = measure;
|
|
1555
|
+
const paragraphFragment = fragment;
|
|
1556
|
+
const blockPmStart = paragraphBlock.pmStart ?? 0;
|
|
1557
|
+
const blockPmEnd = paragraphBlock.pmEnd ?? blockPmStart;
|
|
1558
|
+
if (pmPosition < blockPmStart || pmPosition > blockPmEnd) continue;
|
|
1559
|
+
for (let lineIndex = paragraphFragment.fromLine; lineIndex < paragraphFragment.toLine; lineIndex++) {
|
|
1560
|
+
const line = paragraphMeasure.lines[lineIndex];
|
|
1561
|
+
if (!line) continue;
|
|
1562
|
+
const range = computeLinePmRange(paragraphBlock, line);
|
|
1563
|
+
if (range.pmStart === void 0 || range.pmEnd === void 0) continue;
|
|
1564
|
+
if (pmPosition >= range.pmStart && pmPosition <= range.pmEnd) {
|
|
1565
|
+
const charOffset = pmPosToCharOffset(paragraphBlock, line, pmPosition);
|
|
1566
|
+
const indent = paragraphBlock.attrs?.indent;
|
|
1567
|
+
const indentLeft = indent?.left ?? 0;
|
|
1568
|
+
const indentRight = indent?.right ?? 0;
|
|
1569
|
+
const availableWidth = Math.max(0, fragment.width - indentLeft - indentRight);
|
|
1570
|
+
const x = charOffsetToX(paragraphBlock, line, charOffset, availableWidth);
|
|
1571
|
+
const alignment = paragraphBlock.attrs?.alignment ?? "left";
|
|
1572
|
+
let alignmentOffset = 0;
|
|
1573
|
+
if (alignment === "center") {
|
|
1574
|
+
alignmentOffset = Math.max(0, (availableWidth - line.width) / 2);
|
|
1575
|
+
} else if (alignment === "right") {
|
|
1576
|
+
alignmentOffset = Math.max(0, availableWidth - line.width);
|
|
1577
|
+
}
|
|
1578
|
+
const lineOffset = lineHeightBefore(paragraphMeasure, lineIndex) - lineHeightBefore(paragraphMeasure, paragraphFragment.fromLine);
|
|
1579
|
+
return {
|
|
1580
|
+
x: fragment.x + indentLeft + alignmentOffset + x,
|
|
1581
|
+
y: fragment.y + lineOffset + pageTopY,
|
|
1582
|
+
height: line.lineHeight,
|
|
1583
|
+
pageIndex
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
if (fragment.kind === "image") {
|
|
1589
|
+
const fragPmStart = fragment.pmStart ?? 0;
|
|
1590
|
+
const fragPmEnd = fragment.pmEnd ?? fragPmStart + 1;
|
|
1591
|
+
if (pmPosition >= fragPmStart && pmPosition <= fragPmEnd) {
|
|
1592
|
+
const xOffset = pmPosition === fragPmStart ? 0 : fragment.width;
|
|
1593
|
+
return {
|
|
1594
|
+
x: fragment.x + xOffset,
|
|
1595
|
+
y: fragment.y + pageTopY,
|
|
1596
|
+
height: fragment.height,
|
|
1597
|
+
pageIndex
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
return null;
|
|
1604
|
+
}
|
|
1605
|
+
function isMultiPageSelection(rects) {
|
|
1606
|
+
if (rects.length <= 1) return false;
|
|
1607
|
+
const pageIndices = new Set(rects.map((r) => r.pageIndex));
|
|
1608
|
+
return pageIndices.size > 1;
|
|
1609
|
+
}
|
|
1610
|
+
function groupRectsByPage(rects) {
|
|
1611
|
+
const map = /* @__PURE__ */ new Map();
|
|
1612
|
+
for (const rect of rects) {
|
|
1613
|
+
const pageRects = map.get(rect.pageIndex) ?? [];
|
|
1614
|
+
pageRects.push(rect);
|
|
1615
|
+
map.set(rect.pageIndex, pageRects);
|
|
1616
|
+
}
|
|
1617
|
+
return map;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
export {
|
|
1621
|
+
DEFAULT_SINGLE_LINE_RATIO,
|
|
1622
|
+
resolveFontFamily,
|
|
1623
|
+
resolveThemeFont,
|
|
1624
|
+
DEFAULT_TEXTBOX_MARGINS,
|
|
1625
|
+
DEFAULT_TEXTBOX_WIDTH,
|
|
1626
|
+
findPageIndexContainingPmPos,
|
|
1627
|
+
layoutDocument,
|
|
1628
|
+
resetCanvasContext,
|
|
1629
|
+
getFontMetrics,
|
|
1630
|
+
measureTextWidth,
|
|
1631
|
+
measureRun,
|
|
1632
|
+
findCharacterAtX,
|
|
1633
|
+
twipsToPx,
|
|
1634
|
+
ptToPx,
|
|
1635
|
+
getPageTop,
|
|
1636
|
+
hitTestFragment,
|
|
1637
|
+
hitTestTableCell,
|
|
1638
|
+
selectionToRects,
|
|
1639
|
+
getCaretPosition,
|
|
1640
|
+
isMultiPageSelection,
|
|
1641
|
+
groupRectsByPage
|
|
1642
|
+
};
|