@shadow-garden/bapbong-layout-engine 0.1.0 → 0.2.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/index.cjs +295 -72
- package/dist/index.js +295 -72
- package/dist/lib/layout-engine.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -2,9 +2,21 @@
|
|
|
2
2
|
import {
|
|
3
3
|
createNumberingCounter
|
|
4
4
|
} from "@shadow-garden/bapbong-model";
|
|
5
|
-
var DEFAULT_FONT = {
|
|
5
|
+
var DEFAULT_FONT = {
|
|
6
|
+
family: "Arial",
|
|
7
|
+
sizePt: 11,
|
|
8
|
+
bold: false,
|
|
9
|
+
italic: false
|
|
10
|
+
};
|
|
6
11
|
var PT_TO_PX = 96 / 72;
|
|
7
|
-
var HEADING_PT = {
|
|
12
|
+
var HEADING_PT = {
|
|
13
|
+
1: 24,
|
|
14
|
+
2: 18,
|
|
15
|
+
3: 14,
|
|
16
|
+
4: 12,
|
|
17
|
+
5: 11,
|
|
18
|
+
6: 11
|
|
19
|
+
};
|
|
8
20
|
var LINE_HEIGHT_FACTOR = 1.2;
|
|
9
21
|
var BASELINE_FACTOR = 0.8;
|
|
10
22
|
var DEFAULT_TAB_WIDTH = 48;
|
|
@@ -80,11 +92,13 @@ function resolveImage(node, pos) {
|
|
|
80
92
|
function paragraphToFlow(node, base, nodePos, allowFloats = false, marker) {
|
|
81
93
|
const contentStart = nodePos + 1;
|
|
82
94
|
const headingLevel = node.attrs["heading"];
|
|
83
|
-
const
|
|
95
|
+
const styleId = node.attrs["styleId"];
|
|
96
|
+
const runBase = headingLevel ? { ...base, sizePt: HEADING_PT[headingLevel] ?? base.sizePt, bold: true } : styleId === "Title" ? { ...base, sizePt: 28 } : styleId === "Subtitle" ? { ...base, sizePt: 14, italic: true } : base;
|
|
84
97
|
const runs = [];
|
|
85
98
|
const floats = [];
|
|
86
99
|
node.forEach((child, offset) => {
|
|
87
|
-
if (child.isText)
|
|
100
|
+
if (child.isText)
|
|
101
|
+
runs.push(resolveRun(child, runBase, contentStart + offset));
|
|
88
102
|
else if (child.type.name === "image") {
|
|
89
103
|
const float = child.attrs["float"];
|
|
90
104
|
if (float && allowFloats) {
|
|
@@ -111,7 +125,8 @@ function paragraphToFlow(node, base, nodePos, allowFloats = false, marker) {
|
|
|
111
125
|
}
|
|
112
126
|
} else if (child.type.name === "page_field")
|
|
113
127
|
runs.push(resolveField(child, runBase, contentStart + offset));
|
|
114
|
-
else if (child.type.name === "hard_break")
|
|
128
|
+
else if (child.type.name === "hard_break")
|
|
129
|
+
runs.push({ break: true, pos: contentStart + offset });
|
|
115
130
|
});
|
|
116
131
|
const list = node.attrs["list"];
|
|
117
132
|
const align = node.attrs["align"];
|
|
@@ -147,8 +162,15 @@ function markerFor(node, counter) {
|
|
|
147
162
|
}
|
|
148
163
|
function nodeToBlock(node, base, nodePos, allowFloats = false, counter) {
|
|
149
164
|
if (node.type.name === "paragraph")
|
|
150
|
-
return paragraphToFlow(
|
|
151
|
-
|
|
165
|
+
return paragraphToFlow(
|
|
166
|
+
node,
|
|
167
|
+
base,
|
|
168
|
+
nodePos,
|
|
169
|
+
allowFloats,
|
|
170
|
+
markerFor(node, counter)
|
|
171
|
+
);
|
|
172
|
+
if (node.type.name === "table")
|
|
173
|
+
return tableToFlow(node, base, nodePos, counter);
|
|
152
174
|
return null;
|
|
153
175
|
}
|
|
154
176
|
function tableToFlow(node, base, nodePos, counter) {
|
|
@@ -160,7 +182,13 @@ function tableToFlow(node, base, nodePos, counter) {
|
|
|
160
182
|
const cellPos = rowPos + 1 + cellOffset;
|
|
161
183
|
const content = [];
|
|
162
184
|
cellNode.forEach((child, childOffset) => {
|
|
163
|
-
const block = nodeToBlock(
|
|
185
|
+
const block = nodeToBlock(
|
|
186
|
+
child,
|
|
187
|
+
base,
|
|
188
|
+
cellPos + 1 + childOffset,
|
|
189
|
+
true,
|
|
190
|
+
counter
|
|
191
|
+
);
|
|
164
192
|
if (block) content.push(block);
|
|
165
193
|
});
|
|
166
194
|
const a = cellNode.attrs;
|
|
@@ -192,7 +220,9 @@ function tableToFlow(node, base, nodePos, counter) {
|
|
|
192
220
|
}
|
|
193
221
|
function toFlowBlocks(doc, defaultFont = {}, allowFloats = true) {
|
|
194
222
|
const base = { ...DEFAULT_FONT, ...defaultFont };
|
|
195
|
-
const counter = createNumberingCounter(
|
|
223
|
+
const counter = createNumberingCounter(
|
|
224
|
+
doc.attrs["numbering"]
|
|
225
|
+
);
|
|
196
226
|
const blocks = [];
|
|
197
227
|
doc.forEach((node, offset) => {
|
|
198
228
|
const block = nodeToBlock(node, base, offset, allowFloats, counter);
|
|
@@ -202,7 +232,16 @@ function toFlowBlocks(doc, defaultFont = {}, allowFloats = true) {
|
|
|
202
232
|
}
|
|
203
233
|
function tokenizeInline(inline, ctx) {
|
|
204
234
|
if ("break" in inline) {
|
|
205
|
-
return [
|
|
235
|
+
return [
|
|
236
|
+
{
|
|
237
|
+
isBreak: true,
|
|
238
|
+
font: ctx.base,
|
|
239
|
+
width: 0,
|
|
240
|
+
isSpace: false,
|
|
241
|
+
pos: inline.pos,
|
|
242
|
+
size: 1
|
|
243
|
+
}
|
|
244
|
+
];
|
|
206
245
|
}
|
|
207
246
|
if ("src" in inline) {
|
|
208
247
|
return [
|
|
@@ -313,7 +352,12 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
313
352
|
return lineLeft + k * tabWidth - x;
|
|
314
353
|
};
|
|
315
354
|
const tabStops = block.tabs ? [...block.tabs].sort((a, b) => a.pos - b.pos) : [];
|
|
316
|
-
const LEADER_CHARS = {
|
|
355
|
+
const LEADER_CHARS = {
|
|
356
|
+
dot: ".",
|
|
357
|
+
hyphen: "-",
|
|
358
|
+
underscore: "_",
|
|
359
|
+
middleDot: "\xB7"
|
|
360
|
+
};
|
|
317
361
|
const MIN_TAB = 2;
|
|
318
362
|
const tabGroup = (ti) => {
|
|
319
363
|
let width = 0;
|
|
@@ -323,7 +367,8 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
323
367
|
if (t.isTab) break;
|
|
324
368
|
if (decimalPrefix === null && t.text != null && !t.field) {
|
|
325
369
|
const m = /[.,]/.exec(t.text);
|
|
326
|
-
if (m)
|
|
370
|
+
if (m)
|
|
371
|
+
decimalPrefix = width + measure(t.text.slice(0, m.index), t.font);
|
|
327
372
|
}
|
|
328
373
|
width += t.width;
|
|
329
374
|
}
|
|
@@ -446,7 +491,16 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
446
491
|
height = target;
|
|
447
492
|
}
|
|
448
493
|
const painted = firstLine && marker ? [marker, ...segments] : segments;
|
|
449
|
-
emit({
|
|
494
|
+
emit({
|
|
495
|
+
x: startX,
|
|
496
|
+
width: lineRight - startX,
|
|
497
|
+
height,
|
|
498
|
+
baseline,
|
|
499
|
+
segments: painted,
|
|
500
|
+
images,
|
|
501
|
+
from,
|
|
502
|
+
to
|
|
503
|
+
});
|
|
450
504
|
prevTo = to;
|
|
451
505
|
lineTokens = [];
|
|
452
506
|
lineWidth = 0;
|
|
@@ -480,10 +534,14 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
480
534
|
softWrapped = false;
|
|
481
535
|
continue;
|
|
482
536
|
}
|
|
483
|
-
if (token.isSpace && !token.isTab && lineTokens.length === 0 && softWrapped)
|
|
537
|
+
if (token.isSpace && !token.isTab && lineTokens.length === 0 && softWrapped)
|
|
538
|
+
continue;
|
|
484
539
|
if (token.isTab) resolveTab(token, lineStart() + lineWidth, ti);
|
|
485
540
|
if (clusterable(token) && clusterFont && sameFont(clusterFont, token.font)) {
|
|
486
|
-
token.width = Math.max(
|
|
541
|
+
token.width = Math.max(
|
|
542
|
+
0,
|
|
543
|
+
measure(clusterText + token.text, token.font) - clusterWidth
|
|
544
|
+
);
|
|
487
545
|
}
|
|
488
546
|
const cursor = lineStart() + lineWidth;
|
|
489
547
|
if (!token.isSpace && lineTokens.length > 0 && cursor + token.width > lineRight) {
|
|
@@ -491,14 +549,21 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
491
549
|
resetCluster();
|
|
492
550
|
softWrapped = true;
|
|
493
551
|
if (token.isTab) resolveTab(token, lineStart() + lineWidth, ti);
|
|
494
|
-
else if (clusterable(token))
|
|
552
|
+
else if (clusterable(token))
|
|
553
|
+
token.width = measure(token.text, token.font);
|
|
495
554
|
}
|
|
496
555
|
if (clusterable(token) && lineTokens.length === 0 && token.text.length > 1 && lineStart() + token.width > lineRight) {
|
|
497
556
|
const avail = lineRight - lineStart();
|
|
498
557
|
let n = 1;
|
|
499
|
-
while (n < token.text.length && measure(token.text.slice(0, n + 1), token.font) <= avail)
|
|
558
|
+
while (n < token.text.length && measure(token.text.slice(0, n + 1), token.font) <= avail)
|
|
559
|
+
n++;
|
|
500
560
|
const restText = token.text.slice(n);
|
|
501
|
-
const rest = {
|
|
561
|
+
const rest = {
|
|
562
|
+
...token,
|
|
563
|
+
text: restText,
|
|
564
|
+
width: measure(restText, token.font),
|
|
565
|
+
size: restText.length
|
|
566
|
+
};
|
|
502
567
|
if (token.pos != null) rest.pos = token.pos + n;
|
|
503
568
|
token.text = token.text.slice(0, n);
|
|
504
569
|
token.size = n;
|
|
@@ -519,7 +584,10 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
519
584
|
const rotH = rotatedBoxHeight(token.image);
|
|
520
585
|
maxImagePx = Math.max(maxImagePx, rotH);
|
|
521
586
|
maxAscent = Math.max(maxAscent, token.image.height / 2 + rotH / 2);
|
|
522
|
-
maxDescent = Math.max(
|
|
587
|
+
maxDescent = Math.max(
|
|
588
|
+
maxDescent,
|
|
589
|
+
Math.max(0, (rotH - token.image.height) / 2)
|
|
590
|
+
);
|
|
523
591
|
} else {
|
|
524
592
|
maxFontPx = Math.max(maxFontPx, sizePx(token.font));
|
|
525
593
|
if (metrics) {
|
|
@@ -572,7 +640,13 @@ function shiftTableX(table, dx) {
|
|
|
572
640
|
}
|
|
573
641
|
var TEXTBOX_INSET = { l: 10, t: 5, r: 10, b: 5 };
|
|
574
642
|
function resolveFloat(f, x, y, ctx) {
|
|
575
|
-
const rf = {
|
|
643
|
+
const rf = {
|
|
644
|
+
x,
|
|
645
|
+
y,
|
|
646
|
+
width: f.width,
|
|
647
|
+
height: f.height,
|
|
648
|
+
src: f.src
|
|
649
|
+
};
|
|
576
650
|
if (f.pos != null) rf.pos = f.pos;
|
|
577
651
|
if (f.rotation) rf.rotation = f.rotation;
|
|
578
652
|
if (f.shape) rf.shape = f.shape;
|
|
@@ -618,7 +692,8 @@ function eachCell(rows, ncols, visit) {
|
|
|
618
692
|
for (const cell of rows[r].cells) {
|
|
619
693
|
while (col < ncols && spanned[col] > 0) col++;
|
|
620
694
|
visit(r, cell, col);
|
|
621
|
-
for (let k = 0; k < cell.colspan && col + k < ncols; k++)
|
|
695
|
+
for (let k = 0; k < cell.colspan && col + k < ncols; k++)
|
|
696
|
+
spanned[col + k] = cell.rowspan;
|
|
622
697
|
col += cell.colspan;
|
|
623
698
|
}
|
|
624
699
|
for (let i = 0; i < ncols; i++) if (spanned[i] > 0) spanned[i]--;
|
|
@@ -628,14 +703,18 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
628
703
|
const avail = contentRight - contentLeft;
|
|
629
704
|
const nrows = table.rows.length;
|
|
630
705
|
const ncols = table.rows.reduce(
|
|
631
|
-
(m, r) => Math.max(
|
|
706
|
+
(m, r) => Math.max(
|
|
707
|
+
m,
|
|
708
|
+
r.cells.reduce((s, c) => s + c.colspan, 0)
|
|
709
|
+
),
|
|
632
710
|
0
|
|
633
711
|
);
|
|
634
712
|
const colWidths = new Array(ncols).fill(0);
|
|
635
713
|
eachCell(table.rows, ncols, (_r, cell, startCol) => {
|
|
636
714
|
if (cell.colwidth && cell.colwidth.length === cell.colspan) {
|
|
637
715
|
for (let k = 0; k < cell.colspan && startCol + k < ncols; k++) {
|
|
638
|
-
if (colWidths[startCol + k] === 0)
|
|
716
|
+
if (colWidths[startCol + k] === 0)
|
|
717
|
+
colWidths[startCol + k] = cell.colwidth[k];
|
|
639
718
|
}
|
|
640
719
|
}
|
|
641
720
|
});
|
|
@@ -643,7 +722,8 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
643
722
|
const unknown = colWidths.filter((w) => w === 0).length;
|
|
644
723
|
if (unknown > 0) {
|
|
645
724
|
const share = Math.max(0, (avail - known) / unknown);
|
|
646
|
-
for (let i = 0; i < ncols; i++)
|
|
725
|
+
for (let i = 0; i < ncols; i++)
|
|
726
|
+
if (colWidths[i] === 0) colWidths[i] = share;
|
|
647
727
|
}
|
|
648
728
|
const natural = colWidths.reduce((s, w) => s + w, 0);
|
|
649
729
|
if (natural > avail && natural > 0) {
|
|
@@ -663,9 +743,15 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
663
743
|
const cellDrafts = [];
|
|
664
744
|
eachCell(table.rows, ncols, (r, cell, col) => {
|
|
665
745
|
let cellWidth = 0;
|
|
666
|
-
for (let k = 0; k < cell.colspan && col + k < ncols; k++)
|
|
746
|
+
for (let k = 0; k < cell.colspan && col + k < ncols; k++)
|
|
747
|
+
cellWidth += colWidths[col + k];
|
|
667
748
|
const cellLeft = colX[col];
|
|
668
|
-
const flow = layoutFlow(
|
|
749
|
+
const flow = layoutFlow(
|
|
750
|
+
cell.content,
|
|
751
|
+
cellLeft + pad.left,
|
|
752
|
+
cellLeft + cellWidth - pad.right,
|
|
753
|
+
ctx
|
|
754
|
+
);
|
|
669
755
|
cellDrafts.push({
|
|
670
756
|
startRow: r,
|
|
671
757
|
startCol: col,
|
|
@@ -685,14 +771,18 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
685
771
|
const rowHeight = new Array(nrows).fill(0);
|
|
686
772
|
for (const c of cellDrafts) {
|
|
687
773
|
if (c.rowspan === 1) {
|
|
688
|
-
rowHeight[c.startRow] = Math.max(
|
|
774
|
+
rowHeight[c.startRow] = Math.max(
|
|
775
|
+
rowHeight[c.startRow],
|
|
776
|
+
c.contentHeight + pad.top + pad.bottom
|
|
777
|
+
);
|
|
689
778
|
}
|
|
690
779
|
}
|
|
691
780
|
for (const c of cellDrafts) {
|
|
692
781
|
if (c.rowspan > 1) {
|
|
693
782
|
const need = c.contentHeight + pad.top + pad.bottom;
|
|
694
783
|
let span = 0;
|
|
695
|
-
for (let r = c.startRow; r < c.startRow + c.rowspan && r < nrows; r++)
|
|
784
|
+
for (let r = c.startRow; r < c.startRow + c.rowspan && r < nrows; r++)
|
|
785
|
+
span += rowHeight[r];
|
|
696
786
|
if (need > span) {
|
|
697
787
|
const last = Math.min(c.startRow + c.rowspan - 1, nrows - 1);
|
|
698
788
|
rowHeight[last] += need - span;
|
|
@@ -707,7 +797,8 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
707
797
|
for (let r = 0; r < nrows; r++) rowY[r + 1] = rowY[r] + rowHeight[r];
|
|
708
798
|
const cells = cellDrafts.map((c) => {
|
|
709
799
|
let height = 0;
|
|
710
|
-
for (let r = c.startRow; r < c.startRow + c.rowspan && r < nrows; r++)
|
|
800
|
+
for (let r = c.startRow; r < c.startRow + c.rowspan && r < nrows; r++)
|
|
801
|
+
height += rowHeight[r];
|
|
711
802
|
const slack = Math.max(0, height - pad.top - pad.bottom - c.contentHeight);
|
|
712
803
|
const vOffset = c.vAlign === "bottom" ? slack : c.vAlign === "center" ? slack / 2 : 0;
|
|
713
804
|
const dy = rowY[c.startRow] + pad.top + vOffset;
|
|
@@ -725,19 +816,30 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
725
816
|
if (c.background) cell.background = c.background;
|
|
726
817
|
if (c.borders) cell.borders = c.borders;
|
|
727
818
|
if (c.tables.length > 0) cell.tables = c.tables;
|
|
728
|
-
if (c.floats.length > 0)
|
|
819
|
+
if (c.floats.length > 0)
|
|
820
|
+
cell.floats = c.floats.map((f) => ({ ...f, y: f.y + dy }));
|
|
729
821
|
return cell;
|
|
730
822
|
});
|
|
731
|
-
const resolved = {
|
|
823
|
+
const resolved = {
|
|
824
|
+
x: contentLeft + xShift,
|
|
825
|
+
y: 0,
|
|
826
|
+
width: tableWidth,
|
|
827
|
+
height: rowY[nrows],
|
|
828
|
+
cells
|
|
829
|
+
};
|
|
732
830
|
if (table.borders) resolved.borders = table.borders;
|
|
733
831
|
let headerRows = 0;
|
|
734
832
|
while (headerRows < nrows && table.rows[headerRows].header) headerRows++;
|
|
735
833
|
if (headerRows > 0 && headerRows < nrows) {
|
|
736
834
|
const headerBottom = rowY[headerRows];
|
|
737
|
-
const spansOut = cells.some(
|
|
835
|
+
const spansOut = cells.some(
|
|
836
|
+
(c) => c.y < headerBottom && c.y + c.height > headerBottom
|
|
837
|
+
);
|
|
738
838
|
if (!spansOut) resolved.headerBottom = headerBottom;
|
|
739
839
|
}
|
|
740
|
-
const cantSplitBands = table.rows.map(
|
|
840
|
+
const cantSplitBands = table.rows.map(
|
|
841
|
+
(row, r) => row.cantSplit ? { top: rowY[r], bottom: rowY[r + 1] } : null
|
|
842
|
+
).filter((b) => b !== null);
|
|
741
843
|
if (cantSplitBands.length > 0) resolved.cantSplitBands = cantSplitBands;
|
|
742
844
|
return resolved;
|
|
743
845
|
}
|
|
@@ -770,7 +872,10 @@ function cloneTable(t) {
|
|
|
770
872
|
return { ...t, cells: t.cells.map(cloneCell) };
|
|
771
873
|
}
|
|
772
874
|
function cloneCell(cell) {
|
|
773
|
-
const copy = {
|
|
875
|
+
const copy = {
|
|
876
|
+
...cell,
|
|
877
|
+
lines: cell.lines.map((l) => ({ ...l }))
|
|
878
|
+
};
|
|
774
879
|
if (cell.tables) copy.tables = cell.tables.map(cloneTable);
|
|
775
880
|
if (cell.floats) copy.floats = cell.floats.map((f) => ({ ...f }));
|
|
776
881
|
return copy;
|
|
@@ -806,11 +911,19 @@ function splitTableAt(table, cut) {
|
|
|
806
911
|
} else {
|
|
807
912
|
const c = straddlers.get(cell);
|
|
808
913
|
const topLines = cell.lines.filter((l) => l.y + l.height <= cut);
|
|
809
|
-
const topTables = (cell.tables ?? []).filter(
|
|
810
|
-
|
|
914
|
+
const topTables = (cell.tables ?? []).filter(
|
|
915
|
+
(t) => t.y + t.height <= cut
|
|
916
|
+
);
|
|
917
|
+
const topCell = {
|
|
918
|
+
...cell,
|
|
919
|
+
height: cut - cell.y,
|
|
920
|
+
lines: topLines
|
|
921
|
+
};
|
|
811
922
|
if (topTables.length > 0) topCell.tables = topTables;
|
|
812
923
|
else delete topCell.tables;
|
|
813
|
-
const topFloats = (cell.floats ?? []).filter(
|
|
924
|
+
const topFloats = (cell.floats ?? []).filter(
|
|
925
|
+
(f) => f.y + f.height <= cut
|
|
926
|
+
);
|
|
814
927
|
if (topFloats.length > 0) topCell.floats = topFloats;
|
|
815
928
|
else delete topCell.floats;
|
|
816
929
|
topCells.push(topCell);
|
|
@@ -833,7 +946,13 @@ function splitTableAt(table, cut) {
|
|
|
833
946
|
restCells.push(contCell);
|
|
834
947
|
}
|
|
835
948
|
}
|
|
836
|
-
const top = {
|
|
949
|
+
const top = {
|
|
950
|
+
x: table.x,
|
|
951
|
+
y: 0,
|
|
952
|
+
width: table.width,
|
|
953
|
+
height: cut,
|
|
954
|
+
cells: topCells
|
|
955
|
+
};
|
|
837
956
|
const rest = {
|
|
838
957
|
x: table.x,
|
|
839
958
|
y: 0,
|
|
@@ -855,10 +974,14 @@ function cloneHeaderCells(table, headerBottom) {
|
|
|
855
974
|
tables: void 0,
|
|
856
975
|
floats: cell.floats?.map((f) => ({ ...f })),
|
|
857
976
|
lines: cell.lines.map((l) => {
|
|
858
|
-
const line = {
|
|
977
|
+
const line = {
|
|
978
|
+
...l,
|
|
979
|
+
segments: l.segments.map((s) => ({ ...s, pos: void 0 }))
|
|
980
|
+
};
|
|
859
981
|
delete line.from;
|
|
860
982
|
delete line.to;
|
|
861
|
-
if (line.images)
|
|
983
|
+
if (line.images)
|
|
984
|
+
line.images = line.images.map((im) => ({ ...im, pos: void 0 }));
|
|
862
985
|
return line;
|
|
863
986
|
})
|
|
864
987
|
}));
|
|
@@ -935,13 +1058,15 @@ function placeBlocks(items, config, ctx, band, footnotes) {
|
|
|
935
1058
|
for (const l of ls)
|
|
936
1059
|
for (const s of l.segments) {
|
|
937
1060
|
const n = s.footnoteRef;
|
|
938
|
-
if (n != null && footnotes?.has(n) && !pageFnSet.has(n) && !out.includes(n))
|
|
1061
|
+
if (n != null && footnotes?.has(n) && !pageFnSet.has(n) && !out.includes(n))
|
|
1062
|
+
out.push(n);
|
|
939
1063
|
}
|
|
940
1064
|
};
|
|
941
1065
|
for (const c of t.cells) {
|
|
942
1066
|
if (c.y >= maxY) continue;
|
|
943
1067
|
scan(c.lines);
|
|
944
|
-
if (c.tables)
|
|
1068
|
+
if (c.tables)
|
|
1069
|
+
for (const nt of c.tables) for (const nc of nt.cells) scan(nc.lines);
|
|
945
1070
|
}
|
|
946
1071
|
return out;
|
|
947
1072
|
};
|
|
@@ -970,10 +1095,16 @@ function placeBlocks(items, config, ctx, band, footnotes) {
|
|
|
970
1095
|
return { separatorY: areaTop + FOOTNOTE_AREA_GAP / 2, lines: out };
|
|
971
1096
|
};
|
|
972
1097
|
const finalizePage = () => {
|
|
973
|
-
const resolved = {
|
|
1098
|
+
const resolved = {
|
|
1099
|
+
index: pages.length,
|
|
1100
|
+
width: page.width,
|
|
1101
|
+
height: page.height,
|
|
1102
|
+
lines
|
|
1103
|
+
};
|
|
974
1104
|
if (tables.length > 0) resolved.tables = tables;
|
|
975
1105
|
if (pageFloats.length > 0) resolved.floats = pageFloats;
|
|
976
|
-
if (pageFnNums.length > 0)
|
|
1106
|
+
if (pageFnNums.length > 0)
|
|
1107
|
+
resolved.footnotes = buildFootnoteArea(pageFnNums);
|
|
977
1108
|
pages.push(resolved);
|
|
978
1109
|
lines = [];
|
|
979
1110
|
tables = [];
|
|
@@ -1063,7 +1194,9 @@ function placeBlocks(items, config, ctx, band, footnotes) {
|
|
|
1063
1194
|
}
|
|
1064
1195
|
const b = bandAt(y, estH);
|
|
1065
1196
|
if (b) return b;
|
|
1066
|
-
const blockers = exclusions.filter(
|
|
1197
|
+
const blockers = exclusions.filter(
|
|
1198
|
+
(ex) => ex.top < y + estH && ex.bottom > y
|
|
1199
|
+
);
|
|
1067
1200
|
if (blockers.length === 0) return { left: colX0(), right: colX1() };
|
|
1068
1201
|
y = Math.min(...blockers.map((ex) => ex.bottom));
|
|
1069
1202
|
}
|
|
@@ -1107,7 +1240,9 @@ function placeBlocks(items, config, ctx, band, footnotes) {
|
|
|
1107
1240
|
}
|
|
1108
1241
|
const drafts = item.para.drafts;
|
|
1109
1242
|
const draftsHeight = drafts?.reduce((s, d) => s + d.height, 0) ?? 0;
|
|
1110
|
-
const floatsAhead = exclusions.some(
|
|
1243
|
+
const floatsAhead = exclusions.some(
|
|
1244
|
+
(ex) => ex.bottom > y && ex.top < y + draftsHeight
|
|
1245
|
+
);
|
|
1111
1246
|
if (drafts && !floatsAhead) {
|
|
1112
1247
|
for (const d of drafts) emitLine(d);
|
|
1113
1248
|
} else {
|
|
@@ -1226,16 +1361,25 @@ function shiftDrafts(drafts, delta) {
|
|
|
1226
1361
|
...d,
|
|
1227
1362
|
from: d.from != null ? d.from + delta : d.from,
|
|
1228
1363
|
to: d.to != null ? d.to + delta : d.to,
|
|
1229
|
-
segments: d.segments.map(
|
|
1230
|
-
|
|
1364
|
+
segments: d.segments.map(
|
|
1365
|
+
(s) => s.pos != null ? { ...s, pos: s.pos + delta } : s
|
|
1366
|
+
),
|
|
1367
|
+
images: d.images.map(
|
|
1368
|
+
(im) => im.pos != null ? { ...im, pos: im.pos + delta } : im
|
|
1369
|
+
)
|
|
1231
1370
|
}));
|
|
1232
1371
|
}
|
|
1233
1372
|
function cloneLineShifted(l, delta) {
|
|
1234
1373
|
const out = {
|
|
1235
1374
|
...l,
|
|
1236
|
-
segments: l.segments.map(
|
|
1375
|
+
segments: l.segments.map(
|
|
1376
|
+
(s) => s.pos != null ? { ...s, pos: s.pos + delta } : { ...s }
|
|
1377
|
+
)
|
|
1237
1378
|
};
|
|
1238
|
-
if (l.images)
|
|
1379
|
+
if (l.images)
|
|
1380
|
+
out.images = l.images.map(
|
|
1381
|
+
(im) => im.pos != null ? { ...im, pos: im.pos + delta } : { ...im }
|
|
1382
|
+
);
|
|
1239
1383
|
if (l.from != null) out.from = l.from + delta;
|
|
1240
1384
|
if (l.to != null) out.to = l.to + delta;
|
|
1241
1385
|
return out;
|
|
@@ -1278,7 +1422,10 @@ function layVariants(docs, fn) {
|
|
|
1278
1422
|
return out;
|
|
1279
1423
|
}
|
|
1280
1424
|
function maxBandHeight(bands) {
|
|
1281
|
-
return Math.max(
|
|
1425
|
+
return Math.max(
|
|
1426
|
+
0,
|
|
1427
|
+
...[bands.default, bands.first, bands.even].filter(Boolean).map((b) => b.height)
|
|
1428
|
+
);
|
|
1282
1429
|
}
|
|
1283
1430
|
function anyBandHasFields(bands) {
|
|
1284
1431
|
return [bands.default, bands.first, bands.even].some(
|
|
@@ -1304,9 +1451,17 @@ function layoutChrome(doc, topY, left, right, ctx) {
|
|
|
1304
1451
|
const lines = flow.lines.map((l) => ({ ...l, y: l.y + topY }));
|
|
1305
1452
|
flow.tables.forEach((t) => offsetTable(t, topY));
|
|
1306
1453
|
stripPositions(lines, flow.tables);
|
|
1307
|
-
const band = {
|
|
1454
|
+
const band = {
|
|
1455
|
+
lines,
|
|
1456
|
+
tables: flow.tables,
|
|
1457
|
+
height: flow.height
|
|
1458
|
+
};
|
|
1308
1459
|
if (flow.floats.length > 0)
|
|
1309
|
-
band.floats = flow.floats.map((f) => ({
|
|
1460
|
+
band.floats = flow.floats.map((f) => ({
|
|
1461
|
+
...f,
|
|
1462
|
+
y: f.y + topY,
|
|
1463
|
+
pos: void 0
|
|
1464
|
+
}));
|
|
1310
1465
|
return band;
|
|
1311
1466
|
}
|
|
1312
1467
|
function layoutFooterChrome(doc, pageHeight, left, right, ctx) {
|
|
@@ -1318,12 +1473,21 @@ function layoutFooterChrome(doc, pageHeight, left, right, ctx) {
|
|
|
1318
1473
|
height: flow.height
|
|
1319
1474
|
};
|
|
1320
1475
|
band.tables.forEach((t) => offsetTable(t, topY));
|
|
1321
|
-
if (flow.floats)
|
|
1476
|
+
if (flow.floats)
|
|
1477
|
+
band.floats = flow.floats.map((f) => ({ ...f, y: f.y + topY }));
|
|
1322
1478
|
return band;
|
|
1323
1479
|
}
|
|
1324
1480
|
function layoutFootnoteBody(doc, left, right, ctx) {
|
|
1325
|
-
const fnCtx = {
|
|
1326
|
-
|
|
1481
|
+
const fnCtx = {
|
|
1482
|
+
...ctx,
|
|
1483
|
+
base: { ...ctx.base, sizePt: ctx.base.sizePt * FOOTNOTE_FONT_SCALE }
|
|
1484
|
+
};
|
|
1485
|
+
const flow = layoutFlow(
|
|
1486
|
+
toFlowBlocks(doc, fnCtx.base, false),
|
|
1487
|
+
left,
|
|
1488
|
+
right,
|
|
1489
|
+
fnCtx
|
|
1490
|
+
);
|
|
1327
1491
|
stripPositions(flow.lines, flow.tables);
|
|
1328
1492
|
return { lines: flow.lines, height: flow.height };
|
|
1329
1493
|
}
|
|
@@ -1332,10 +1496,24 @@ function layout(doc, config, cache, chrome, footnotes) {
|
|
|
1332
1496
|
const { page } = config;
|
|
1333
1497
|
const left = page.margin.left;
|
|
1334
1498
|
const right = page.width - page.margin.right;
|
|
1335
|
-
const headerDocs = {
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1499
|
+
const headerDocs = {
|
|
1500
|
+
default: chrome?.header,
|
|
1501
|
+
first: chrome?.headerFirst,
|
|
1502
|
+
even: chrome?.headerEven
|
|
1503
|
+
};
|
|
1504
|
+
const footerDocs = {
|
|
1505
|
+
default: chrome?.footer,
|
|
1506
|
+
first: chrome?.footerFirst,
|
|
1507
|
+
even: chrome?.footerEven
|
|
1508
|
+
};
|
|
1509
|
+
const layHeaders = (c) => layVariants(
|
|
1510
|
+
headerDocs,
|
|
1511
|
+
(d) => layoutChrome(d, CHROME_DISTANCE, left, right, c)
|
|
1512
|
+
);
|
|
1513
|
+
const layFooters = (c) => layVariants(
|
|
1514
|
+
footerDocs,
|
|
1515
|
+
(d) => layoutFooterChrome(d, page.height, left, right, c)
|
|
1516
|
+
);
|
|
1339
1517
|
let headers = layHeaders(ctx);
|
|
1340
1518
|
let footers = layFooters(ctx);
|
|
1341
1519
|
let top = page.margin.top;
|
|
@@ -1344,25 +1522,46 @@ function layout(doc, config, cache, chrome, footnotes) {
|
|
|
1344
1522
|
top = Math.max(top, CHROME_DISTANCE + maxBandHeight(headers));
|
|
1345
1523
|
}
|
|
1346
1524
|
if (footers.default || footers.first || footers.even) {
|
|
1347
|
-
bottom = Math.min(
|
|
1525
|
+
bottom = Math.min(
|
|
1526
|
+
bottom,
|
|
1527
|
+
page.height - CHROME_DISTANCE - maxBandHeight(footers)
|
|
1528
|
+
);
|
|
1348
1529
|
}
|
|
1349
|
-
const counter = createNumberingCounter(
|
|
1530
|
+
const counter = createNumberingCounter(
|
|
1531
|
+
doc.attrs["numbering"]
|
|
1532
|
+
);
|
|
1350
1533
|
const sections = doc.attrs["sections"] ?? [
|
|
1351
|
-
{
|
|
1534
|
+
{
|
|
1535
|
+
blockCount: doc.childCount,
|
|
1536
|
+
columns: { count: 1, gap: 0 },
|
|
1537
|
+
newPage: true
|
|
1538
|
+
}
|
|
1352
1539
|
];
|
|
1353
1540
|
const blockSection = [];
|
|
1354
1541
|
for (const sec of sections) {
|
|
1355
1542
|
for (let k = 0; k < sec.blockCount; k++) {
|
|
1356
|
-
blockSection.push({
|
|
1543
|
+
blockSection.push({
|
|
1544
|
+
columns: sec.columns,
|
|
1545
|
+
start: k === 0,
|
|
1546
|
+
newPage: sec.newPage
|
|
1547
|
+
});
|
|
1357
1548
|
}
|
|
1358
1549
|
}
|
|
1359
1550
|
const lastSec = sections[sections.length - 1];
|
|
1360
1551
|
while (blockSection.length < doc.childCount) {
|
|
1361
|
-
blockSection.push({
|
|
1552
|
+
blockSection.push({
|
|
1553
|
+
columns: lastSec.columns,
|
|
1554
|
+
start: false,
|
|
1555
|
+
newPage: lastSec.newPage
|
|
1556
|
+
});
|
|
1362
1557
|
}
|
|
1363
1558
|
const items = [];
|
|
1364
1559
|
doc.forEach((node, offset, index) => {
|
|
1365
|
-
const bs = blockSection[index] ?? {
|
|
1560
|
+
const bs = blockSection[index] ?? {
|
|
1561
|
+
columns: { count: 1, gap: 0 },
|
|
1562
|
+
start: false,
|
|
1563
|
+
newPage: true
|
|
1564
|
+
};
|
|
1366
1565
|
const colRight = left + columnWidth(right - left, bs.columns);
|
|
1367
1566
|
const tag = (item) => {
|
|
1368
1567
|
if (bs.start) item.section = { ...bs.columns, newPage: bs.newPage };
|
|
@@ -1395,17 +1594,35 @@ function layout(doc, config, cache, chrome, footnotes) {
|
|
|
1395
1594
|
}
|
|
1396
1595
|
const flow = paragraphToFlow(node, ctx.base, offset, true, marker);
|
|
1397
1596
|
const drafts = layoutParagraph(flow, left, colRight, ctx);
|
|
1398
|
-
cache?.paragraphs.set(node, {
|
|
1597
|
+
cache?.paragraphs.set(node, {
|
|
1598
|
+
left,
|
|
1599
|
+
right: colRight,
|
|
1600
|
+
basePos: contentStart,
|
|
1601
|
+
marker,
|
|
1602
|
+
drafts
|
|
1603
|
+
});
|
|
1399
1604
|
items.push(tag({ para: { ...para(drafts), getFlow: () => flow } }));
|
|
1400
1605
|
} else if (node.type.name === "table") {
|
|
1401
1606
|
const hit = cache?.tables.get(node);
|
|
1402
1607
|
if (hit && hit.left === left && hit.right === colRight) {
|
|
1403
|
-
items.push(
|
|
1608
|
+
items.push(
|
|
1609
|
+
tag({ table: cloneTableShifted(hit.table, offset - hit.basePos) })
|
|
1610
|
+
);
|
|
1404
1611
|
return;
|
|
1405
1612
|
}
|
|
1406
|
-
const table = layoutTable(
|
|
1613
|
+
const table = layoutTable(
|
|
1614
|
+
tableToFlow(node, ctx.base, offset, counter),
|
|
1615
|
+
left,
|
|
1616
|
+
colRight,
|
|
1617
|
+
ctx
|
|
1618
|
+
);
|
|
1407
1619
|
if (cache && !tableHasList(node)) {
|
|
1408
|
-
cache.tables.set(node, {
|
|
1620
|
+
cache.tables.set(node, {
|
|
1621
|
+
left,
|
|
1622
|
+
right: colRight,
|
|
1623
|
+
basePos: offset,
|
|
1624
|
+
table
|
|
1625
|
+
});
|
|
1409
1626
|
items.push(tag({ table: cloneTableShifted(table, 0) }));
|
|
1410
1627
|
} else {
|
|
1411
1628
|
items.push(tag({ table }));
|
|
@@ -1423,7 +1640,10 @@ function layout(doc, config, cache, chrome, footnotes) {
|
|
|
1423
1640
|
assignSectionHeights(items);
|
|
1424
1641
|
const resolved = placeBlocks(items, config, ctx, { top, bottom }, fnMap);
|
|
1425
1642
|
if (anyBandHasFields(headers) || anyBandHasFields(footers)) {
|
|
1426
|
-
const fieldCtx = {
|
|
1643
|
+
const fieldCtx = {
|
|
1644
|
+
...ctx,
|
|
1645
|
+
fieldPlaceholder: String(resolved.pages.length)
|
|
1646
|
+
};
|
|
1427
1647
|
headers = layHeaders(fieldCtx);
|
|
1428
1648
|
footers = layFooters(fieldCtx);
|
|
1429
1649
|
}
|
|
@@ -1434,7 +1654,10 @@ function layout(doc, config, cache, chrome, footnotes) {
|
|
|
1434
1654
|
if (footers.first) resolved.pageFooterFirst = footers.first;
|
|
1435
1655
|
if (footers.even) resolved.pageFooterEven = footers.even;
|
|
1436
1656
|
if (chrome?.titlePg || chrome?.evenAndOdd) {
|
|
1437
|
-
resolved.chromeSelect = {
|
|
1657
|
+
resolved.chromeSelect = {
|
|
1658
|
+
titlePg: !!chrome.titlePg,
|
|
1659
|
+
evenAndOdd: !!chrome.evenAndOdd
|
|
1660
|
+
};
|
|
1438
1661
|
}
|
|
1439
1662
|
return resolved;
|
|
1440
1663
|
}
|