@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.cjs
CHANGED
|
@@ -30,9 +30,21 @@ module.exports = __toCommonJS(index_exports);
|
|
|
30
30
|
|
|
31
31
|
// packages/layout-engine/src/lib/layout-engine.ts
|
|
32
32
|
var import_bapbong_model = require("@shadow-garden/bapbong-model");
|
|
33
|
-
var DEFAULT_FONT = {
|
|
33
|
+
var DEFAULT_FONT = {
|
|
34
|
+
family: "Arial",
|
|
35
|
+
sizePt: 11,
|
|
36
|
+
bold: false,
|
|
37
|
+
italic: false
|
|
38
|
+
};
|
|
34
39
|
var PT_TO_PX = 96 / 72;
|
|
35
|
-
var HEADING_PT = {
|
|
40
|
+
var HEADING_PT = {
|
|
41
|
+
1: 24,
|
|
42
|
+
2: 18,
|
|
43
|
+
3: 14,
|
|
44
|
+
4: 12,
|
|
45
|
+
5: 11,
|
|
46
|
+
6: 11
|
|
47
|
+
};
|
|
36
48
|
var LINE_HEIGHT_FACTOR = 1.2;
|
|
37
49
|
var BASELINE_FACTOR = 0.8;
|
|
38
50
|
var DEFAULT_TAB_WIDTH = 48;
|
|
@@ -108,11 +120,13 @@ function resolveImage(node, pos) {
|
|
|
108
120
|
function paragraphToFlow(node, base, nodePos, allowFloats = false, marker) {
|
|
109
121
|
const contentStart = nodePos + 1;
|
|
110
122
|
const headingLevel = node.attrs["heading"];
|
|
111
|
-
const
|
|
123
|
+
const styleId = node.attrs["styleId"];
|
|
124
|
+
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;
|
|
112
125
|
const runs = [];
|
|
113
126
|
const floats = [];
|
|
114
127
|
node.forEach((child, offset) => {
|
|
115
|
-
if (child.isText)
|
|
128
|
+
if (child.isText)
|
|
129
|
+
runs.push(resolveRun(child, runBase, contentStart + offset));
|
|
116
130
|
else if (child.type.name === "image") {
|
|
117
131
|
const float = child.attrs["float"];
|
|
118
132
|
if (float && allowFloats) {
|
|
@@ -139,7 +153,8 @@ function paragraphToFlow(node, base, nodePos, allowFloats = false, marker) {
|
|
|
139
153
|
}
|
|
140
154
|
} else if (child.type.name === "page_field")
|
|
141
155
|
runs.push(resolveField(child, runBase, contentStart + offset));
|
|
142
|
-
else if (child.type.name === "hard_break")
|
|
156
|
+
else if (child.type.name === "hard_break")
|
|
157
|
+
runs.push({ break: true, pos: contentStart + offset });
|
|
143
158
|
});
|
|
144
159
|
const list = node.attrs["list"];
|
|
145
160
|
const align = node.attrs["align"];
|
|
@@ -175,8 +190,15 @@ function markerFor(node, counter) {
|
|
|
175
190
|
}
|
|
176
191
|
function nodeToBlock(node, base, nodePos, allowFloats = false, counter) {
|
|
177
192
|
if (node.type.name === "paragraph")
|
|
178
|
-
return paragraphToFlow(
|
|
179
|
-
|
|
193
|
+
return paragraphToFlow(
|
|
194
|
+
node,
|
|
195
|
+
base,
|
|
196
|
+
nodePos,
|
|
197
|
+
allowFloats,
|
|
198
|
+
markerFor(node, counter)
|
|
199
|
+
);
|
|
200
|
+
if (node.type.name === "table")
|
|
201
|
+
return tableToFlow(node, base, nodePos, counter);
|
|
180
202
|
return null;
|
|
181
203
|
}
|
|
182
204
|
function tableToFlow(node, base, nodePos, counter) {
|
|
@@ -188,7 +210,13 @@ function tableToFlow(node, base, nodePos, counter) {
|
|
|
188
210
|
const cellPos = rowPos + 1 + cellOffset;
|
|
189
211
|
const content = [];
|
|
190
212
|
cellNode.forEach((child, childOffset) => {
|
|
191
|
-
const block = nodeToBlock(
|
|
213
|
+
const block = nodeToBlock(
|
|
214
|
+
child,
|
|
215
|
+
base,
|
|
216
|
+
cellPos + 1 + childOffset,
|
|
217
|
+
true,
|
|
218
|
+
counter
|
|
219
|
+
);
|
|
192
220
|
if (block) content.push(block);
|
|
193
221
|
});
|
|
194
222
|
const a = cellNode.attrs;
|
|
@@ -220,7 +248,9 @@ function tableToFlow(node, base, nodePos, counter) {
|
|
|
220
248
|
}
|
|
221
249
|
function toFlowBlocks(doc, defaultFont = {}, allowFloats = true) {
|
|
222
250
|
const base = { ...DEFAULT_FONT, ...defaultFont };
|
|
223
|
-
const counter = (0, import_bapbong_model.createNumberingCounter)(
|
|
251
|
+
const counter = (0, import_bapbong_model.createNumberingCounter)(
|
|
252
|
+
doc.attrs["numbering"]
|
|
253
|
+
);
|
|
224
254
|
const blocks = [];
|
|
225
255
|
doc.forEach((node, offset) => {
|
|
226
256
|
const block = nodeToBlock(node, base, offset, allowFloats, counter);
|
|
@@ -230,7 +260,16 @@ function toFlowBlocks(doc, defaultFont = {}, allowFloats = true) {
|
|
|
230
260
|
}
|
|
231
261
|
function tokenizeInline(inline, ctx) {
|
|
232
262
|
if ("break" in inline) {
|
|
233
|
-
return [
|
|
263
|
+
return [
|
|
264
|
+
{
|
|
265
|
+
isBreak: true,
|
|
266
|
+
font: ctx.base,
|
|
267
|
+
width: 0,
|
|
268
|
+
isSpace: false,
|
|
269
|
+
pos: inline.pos,
|
|
270
|
+
size: 1
|
|
271
|
+
}
|
|
272
|
+
];
|
|
234
273
|
}
|
|
235
274
|
if ("src" in inline) {
|
|
236
275
|
return [
|
|
@@ -341,7 +380,12 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
341
380
|
return lineLeft + k * tabWidth - x;
|
|
342
381
|
};
|
|
343
382
|
const tabStops = block.tabs ? [...block.tabs].sort((a, b) => a.pos - b.pos) : [];
|
|
344
|
-
const LEADER_CHARS = {
|
|
383
|
+
const LEADER_CHARS = {
|
|
384
|
+
dot: ".",
|
|
385
|
+
hyphen: "-",
|
|
386
|
+
underscore: "_",
|
|
387
|
+
middleDot: "\xB7"
|
|
388
|
+
};
|
|
345
389
|
const MIN_TAB = 2;
|
|
346
390
|
const tabGroup = (ti) => {
|
|
347
391
|
let width = 0;
|
|
@@ -351,7 +395,8 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
351
395
|
if (t.isTab) break;
|
|
352
396
|
if (decimalPrefix === null && t.text != null && !t.field) {
|
|
353
397
|
const m = /[.,]/.exec(t.text);
|
|
354
|
-
if (m)
|
|
398
|
+
if (m)
|
|
399
|
+
decimalPrefix = width + measure(t.text.slice(0, m.index), t.font);
|
|
355
400
|
}
|
|
356
401
|
width += t.width;
|
|
357
402
|
}
|
|
@@ -474,7 +519,16 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
474
519
|
height = target;
|
|
475
520
|
}
|
|
476
521
|
const painted = firstLine && marker ? [marker, ...segments] : segments;
|
|
477
|
-
emit({
|
|
522
|
+
emit({
|
|
523
|
+
x: startX,
|
|
524
|
+
width: lineRight - startX,
|
|
525
|
+
height,
|
|
526
|
+
baseline,
|
|
527
|
+
segments: painted,
|
|
528
|
+
images,
|
|
529
|
+
from,
|
|
530
|
+
to
|
|
531
|
+
});
|
|
478
532
|
prevTo = to;
|
|
479
533
|
lineTokens = [];
|
|
480
534
|
lineWidth = 0;
|
|
@@ -508,10 +562,14 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
508
562
|
softWrapped = false;
|
|
509
563
|
continue;
|
|
510
564
|
}
|
|
511
|
-
if (token.isSpace && !token.isTab && lineTokens.length === 0 && softWrapped)
|
|
565
|
+
if (token.isSpace && !token.isTab && lineTokens.length === 0 && softWrapped)
|
|
566
|
+
continue;
|
|
512
567
|
if (token.isTab) resolveTab(token, lineStart() + lineWidth, ti);
|
|
513
568
|
if (clusterable(token) && clusterFont && sameFont(clusterFont, token.font)) {
|
|
514
|
-
token.width = Math.max(
|
|
569
|
+
token.width = Math.max(
|
|
570
|
+
0,
|
|
571
|
+
measure(clusterText + token.text, token.font) - clusterWidth
|
|
572
|
+
);
|
|
515
573
|
}
|
|
516
574
|
const cursor = lineStart() + lineWidth;
|
|
517
575
|
if (!token.isSpace && lineTokens.length > 0 && cursor + token.width > lineRight) {
|
|
@@ -519,14 +577,21 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
519
577
|
resetCluster();
|
|
520
578
|
softWrapped = true;
|
|
521
579
|
if (token.isTab) resolveTab(token, lineStart() + lineWidth, ti);
|
|
522
|
-
else if (clusterable(token))
|
|
580
|
+
else if (clusterable(token))
|
|
581
|
+
token.width = measure(token.text, token.font);
|
|
523
582
|
}
|
|
524
583
|
if (clusterable(token) && lineTokens.length === 0 && token.text.length > 1 && lineStart() + token.width > lineRight) {
|
|
525
584
|
const avail = lineRight - lineStart();
|
|
526
585
|
let n = 1;
|
|
527
|
-
while (n < token.text.length && measure(token.text.slice(0, n + 1), token.font) <= avail)
|
|
586
|
+
while (n < token.text.length && measure(token.text.slice(0, n + 1), token.font) <= avail)
|
|
587
|
+
n++;
|
|
528
588
|
const restText = token.text.slice(n);
|
|
529
|
-
const rest = {
|
|
589
|
+
const rest = {
|
|
590
|
+
...token,
|
|
591
|
+
text: restText,
|
|
592
|
+
width: measure(restText, token.font),
|
|
593
|
+
size: restText.length
|
|
594
|
+
};
|
|
530
595
|
if (token.pos != null) rest.pos = token.pos + n;
|
|
531
596
|
token.text = token.text.slice(0, n);
|
|
532
597
|
token.size = n;
|
|
@@ -547,7 +612,10 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
547
612
|
const rotH = rotatedBoxHeight(token.image);
|
|
548
613
|
maxImagePx = Math.max(maxImagePx, rotH);
|
|
549
614
|
maxAscent = Math.max(maxAscent, token.image.height / 2 + rotH / 2);
|
|
550
|
-
maxDescent = Math.max(
|
|
615
|
+
maxDescent = Math.max(
|
|
616
|
+
maxDescent,
|
|
617
|
+
Math.max(0, (rotH - token.image.height) / 2)
|
|
618
|
+
);
|
|
551
619
|
} else {
|
|
552
620
|
maxFontPx = Math.max(maxFontPx, sizePx(token.font));
|
|
553
621
|
if (metrics) {
|
|
@@ -600,7 +668,13 @@ function shiftTableX(table, dx) {
|
|
|
600
668
|
}
|
|
601
669
|
var TEXTBOX_INSET = { l: 10, t: 5, r: 10, b: 5 };
|
|
602
670
|
function resolveFloat(f, x, y, ctx) {
|
|
603
|
-
const rf = {
|
|
671
|
+
const rf = {
|
|
672
|
+
x,
|
|
673
|
+
y,
|
|
674
|
+
width: f.width,
|
|
675
|
+
height: f.height,
|
|
676
|
+
src: f.src
|
|
677
|
+
};
|
|
604
678
|
if (f.pos != null) rf.pos = f.pos;
|
|
605
679
|
if (f.rotation) rf.rotation = f.rotation;
|
|
606
680
|
if (f.shape) rf.shape = f.shape;
|
|
@@ -646,7 +720,8 @@ function eachCell(rows, ncols, visit) {
|
|
|
646
720
|
for (const cell of rows[r].cells) {
|
|
647
721
|
while (col < ncols && spanned[col] > 0) col++;
|
|
648
722
|
visit(r, cell, col);
|
|
649
|
-
for (let k = 0; k < cell.colspan && col + k < ncols; k++)
|
|
723
|
+
for (let k = 0; k < cell.colspan && col + k < ncols; k++)
|
|
724
|
+
spanned[col + k] = cell.rowspan;
|
|
650
725
|
col += cell.colspan;
|
|
651
726
|
}
|
|
652
727
|
for (let i = 0; i < ncols; i++) if (spanned[i] > 0) spanned[i]--;
|
|
@@ -656,14 +731,18 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
656
731
|
const avail = contentRight - contentLeft;
|
|
657
732
|
const nrows = table.rows.length;
|
|
658
733
|
const ncols = table.rows.reduce(
|
|
659
|
-
(m, r) => Math.max(
|
|
734
|
+
(m, r) => Math.max(
|
|
735
|
+
m,
|
|
736
|
+
r.cells.reduce((s, c) => s + c.colspan, 0)
|
|
737
|
+
),
|
|
660
738
|
0
|
|
661
739
|
);
|
|
662
740
|
const colWidths = new Array(ncols).fill(0);
|
|
663
741
|
eachCell(table.rows, ncols, (_r, cell, startCol) => {
|
|
664
742
|
if (cell.colwidth && cell.colwidth.length === cell.colspan) {
|
|
665
743
|
for (let k = 0; k < cell.colspan && startCol + k < ncols; k++) {
|
|
666
|
-
if (colWidths[startCol + k] === 0)
|
|
744
|
+
if (colWidths[startCol + k] === 0)
|
|
745
|
+
colWidths[startCol + k] = cell.colwidth[k];
|
|
667
746
|
}
|
|
668
747
|
}
|
|
669
748
|
});
|
|
@@ -671,7 +750,8 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
671
750
|
const unknown = colWidths.filter((w) => w === 0).length;
|
|
672
751
|
if (unknown > 0) {
|
|
673
752
|
const share = Math.max(0, (avail - known) / unknown);
|
|
674
|
-
for (let i = 0; i < ncols; i++)
|
|
753
|
+
for (let i = 0; i < ncols; i++)
|
|
754
|
+
if (colWidths[i] === 0) colWidths[i] = share;
|
|
675
755
|
}
|
|
676
756
|
const natural = colWidths.reduce((s, w) => s + w, 0);
|
|
677
757
|
if (natural > avail && natural > 0) {
|
|
@@ -691,9 +771,15 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
691
771
|
const cellDrafts = [];
|
|
692
772
|
eachCell(table.rows, ncols, (r, cell, col) => {
|
|
693
773
|
let cellWidth = 0;
|
|
694
|
-
for (let k = 0; k < cell.colspan && col + k < ncols; k++)
|
|
774
|
+
for (let k = 0; k < cell.colspan && col + k < ncols; k++)
|
|
775
|
+
cellWidth += colWidths[col + k];
|
|
695
776
|
const cellLeft = colX[col];
|
|
696
|
-
const flow = layoutFlow(
|
|
777
|
+
const flow = layoutFlow(
|
|
778
|
+
cell.content,
|
|
779
|
+
cellLeft + pad.left,
|
|
780
|
+
cellLeft + cellWidth - pad.right,
|
|
781
|
+
ctx
|
|
782
|
+
);
|
|
697
783
|
cellDrafts.push({
|
|
698
784
|
startRow: r,
|
|
699
785
|
startCol: col,
|
|
@@ -713,14 +799,18 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
713
799
|
const rowHeight = new Array(nrows).fill(0);
|
|
714
800
|
for (const c of cellDrafts) {
|
|
715
801
|
if (c.rowspan === 1) {
|
|
716
|
-
rowHeight[c.startRow] = Math.max(
|
|
802
|
+
rowHeight[c.startRow] = Math.max(
|
|
803
|
+
rowHeight[c.startRow],
|
|
804
|
+
c.contentHeight + pad.top + pad.bottom
|
|
805
|
+
);
|
|
717
806
|
}
|
|
718
807
|
}
|
|
719
808
|
for (const c of cellDrafts) {
|
|
720
809
|
if (c.rowspan > 1) {
|
|
721
810
|
const need = c.contentHeight + pad.top + pad.bottom;
|
|
722
811
|
let span = 0;
|
|
723
|
-
for (let r = c.startRow; r < c.startRow + c.rowspan && r < nrows; r++)
|
|
812
|
+
for (let r = c.startRow; r < c.startRow + c.rowspan && r < nrows; r++)
|
|
813
|
+
span += rowHeight[r];
|
|
724
814
|
if (need > span) {
|
|
725
815
|
const last = Math.min(c.startRow + c.rowspan - 1, nrows - 1);
|
|
726
816
|
rowHeight[last] += need - span;
|
|
@@ -735,7 +825,8 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
735
825
|
for (let r = 0; r < nrows; r++) rowY[r + 1] = rowY[r] + rowHeight[r];
|
|
736
826
|
const cells = cellDrafts.map((c) => {
|
|
737
827
|
let height = 0;
|
|
738
|
-
for (let r = c.startRow; r < c.startRow + c.rowspan && r < nrows; r++)
|
|
828
|
+
for (let r = c.startRow; r < c.startRow + c.rowspan && r < nrows; r++)
|
|
829
|
+
height += rowHeight[r];
|
|
739
830
|
const slack = Math.max(0, height - pad.top - pad.bottom - c.contentHeight);
|
|
740
831
|
const vOffset = c.vAlign === "bottom" ? slack : c.vAlign === "center" ? slack / 2 : 0;
|
|
741
832
|
const dy = rowY[c.startRow] + pad.top + vOffset;
|
|
@@ -753,19 +844,30 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
753
844
|
if (c.background) cell.background = c.background;
|
|
754
845
|
if (c.borders) cell.borders = c.borders;
|
|
755
846
|
if (c.tables.length > 0) cell.tables = c.tables;
|
|
756
|
-
if (c.floats.length > 0)
|
|
847
|
+
if (c.floats.length > 0)
|
|
848
|
+
cell.floats = c.floats.map((f) => ({ ...f, y: f.y + dy }));
|
|
757
849
|
return cell;
|
|
758
850
|
});
|
|
759
|
-
const resolved = {
|
|
851
|
+
const resolved = {
|
|
852
|
+
x: contentLeft + xShift,
|
|
853
|
+
y: 0,
|
|
854
|
+
width: tableWidth,
|
|
855
|
+
height: rowY[nrows],
|
|
856
|
+
cells
|
|
857
|
+
};
|
|
760
858
|
if (table.borders) resolved.borders = table.borders;
|
|
761
859
|
let headerRows = 0;
|
|
762
860
|
while (headerRows < nrows && table.rows[headerRows].header) headerRows++;
|
|
763
861
|
if (headerRows > 0 && headerRows < nrows) {
|
|
764
862
|
const headerBottom = rowY[headerRows];
|
|
765
|
-
const spansOut = cells.some(
|
|
863
|
+
const spansOut = cells.some(
|
|
864
|
+
(c) => c.y < headerBottom && c.y + c.height > headerBottom
|
|
865
|
+
);
|
|
766
866
|
if (!spansOut) resolved.headerBottom = headerBottom;
|
|
767
867
|
}
|
|
768
|
-
const cantSplitBands = table.rows.map(
|
|
868
|
+
const cantSplitBands = table.rows.map(
|
|
869
|
+
(row, r) => row.cantSplit ? { top: rowY[r], bottom: rowY[r + 1] } : null
|
|
870
|
+
).filter((b) => b !== null);
|
|
769
871
|
if (cantSplitBands.length > 0) resolved.cantSplitBands = cantSplitBands;
|
|
770
872
|
return resolved;
|
|
771
873
|
}
|
|
@@ -798,7 +900,10 @@ function cloneTable(t) {
|
|
|
798
900
|
return { ...t, cells: t.cells.map(cloneCell) };
|
|
799
901
|
}
|
|
800
902
|
function cloneCell(cell) {
|
|
801
|
-
const copy = {
|
|
903
|
+
const copy = {
|
|
904
|
+
...cell,
|
|
905
|
+
lines: cell.lines.map((l) => ({ ...l }))
|
|
906
|
+
};
|
|
802
907
|
if (cell.tables) copy.tables = cell.tables.map(cloneTable);
|
|
803
908
|
if (cell.floats) copy.floats = cell.floats.map((f) => ({ ...f }));
|
|
804
909
|
return copy;
|
|
@@ -834,11 +939,19 @@ function splitTableAt(table, cut) {
|
|
|
834
939
|
} else {
|
|
835
940
|
const c = straddlers.get(cell);
|
|
836
941
|
const topLines = cell.lines.filter((l) => l.y + l.height <= cut);
|
|
837
|
-
const topTables = (cell.tables ?? []).filter(
|
|
838
|
-
|
|
942
|
+
const topTables = (cell.tables ?? []).filter(
|
|
943
|
+
(t) => t.y + t.height <= cut
|
|
944
|
+
);
|
|
945
|
+
const topCell = {
|
|
946
|
+
...cell,
|
|
947
|
+
height: cut - cell.y,
|
|
948
|
+
lines: topLines
|
|
949
|
+
};
|
|
839
950
|
if (topTables.length > 0) topCell.tables = topTables;
|
|
840
951
|
else delete topCell.tables;
|
|
841
|
-
const topFloats = (cell.floats ?? []).filter(
|
|
952
|
+
const topFloats = (cell.floats ?? []).filter(
|
|
953
|
+
(f) => f.y + f.height <= cut
|
|
954
|
+
);
|
|
842
955
|
if (topFloats.length > 0) topCell.floats = topFloats;
|
|
843
956
|
else delete topCell.floats;
|
|
844
957
|
topCells.push(topCell);
|
|
@@ -861,7 +974,13 @@ function splitTableAt(table, cut) {
|
|
|
861
974
|
restCells.push(contCell);
|
|
862
975
|
}
|
|
863
976
|
}
|
|
864
|
-
const top = {
|
|
977
|
+
const top = {
|
|
978
|
+
x: table.x,
|
|
979
|
+
y: 0,
|
|
980
|
+
width: table.width,
|
|
981
|
+
height: cut,
|
|
982
|
+
cells: topCells
|
|
983
|
+
};
|
|
865
984
|
const rest = {
|
|
866
985
|
x: table.x,
|
|
867
986
|
y: 0,
|
|
@@ -883,10 +1002,14 @@ function cloneHeaderCells(table, headerBottom) {
|
|
|
883
1002
|
tables: void 0,
|
|
884
1003
|
floats: cell.floats?.map((f) => ({ ...f })),
|
|
885
1004
|
lines: cell.lines.map((l) => {
|
|
886
|
-
const line = {
|
|
1005
|
+
const line = {
|
|
1006
|
+
...l,
|
|
1007
|
+
segments: l.segments.map((s) => ({ ...s, pos: void 0 }))
|
|
1008
|
+
};
|
|
887
1009
|
delete line.from;
|
|
888
1010
|
delete line.to;
|
|
889
|
-
if (line.images)
|
|
1011
|
+
if (line.images)
|
|
1012
|
+
line.images = line.images.map((im) => ({ ...im, pos: void 0 }));
|
|
890
1013
|
return line;
|
|
891
1014
|
})
|
|
892
1015
|
}));
|
|
@@ -963,13 +1086,15 @@ function placeBlocks(items, config, ctx, band, footnotes) {
|
|
|
963
1086
|
for (const l of ls)
|
|
964
1087
|
for (const s of l.segments) {
|
|
965
1088
|
const n = s.footnoteRef;
|
|
966
|
-
if (n != null && footnotes?.has(n) && !pageFnSet.has(n) && !out.includes(n))
|
|
1089
|
+
if (n != null && footnotes?.has(n) && !pageFnSet.has(n) && !out.includes(n))
|
|
1090
|
+
out.push(n);
|
|
967
1091
|
}
|
|
968
1092
|
};
|
|
969
1093
|
for (const c of t.cells) {
|
|
970
1094
|
if (c.y >= maxY) continue;
|
|
971
1095
|
scan(c.lines);
|
|
972
|
-
if (c.tables)
|
|
1096
|
+
if (c.tables)
|
|
1097
|
+
for (const nt of c.tables) for (const nc of nt.cells) scan(nc.lines);
|
|
973
1098
|
}
|
|
974
1099
|
return out;
|
|
975
1100
|
};
|
|
@@ -998,10 +1123,16 @@ function placeBlocks(items, config, ctx, band, footnotes) {
|
|
|
998
1123
|
return { separatorY: areaTop + FOOTNOTE_AREA_GAP / 2, lines: out };
|
|
999
1124
|
};
|
|
1000
1125
|
const finalizePage = () => {
|
|
1001
|
-
const resolved = {
|
|
1126
|
+
const resolved = {
|
|
1127
|
+
index: pages.length,
|
|
1128
|
+
width: page.width,
|
|
1129
|
+
height: page.height,
|
|
1130
|
+
lines
|
|
1131
|
+
};
|
|
1002
1132
|
if (tables.length > 0) resolved.tables = tables;
|
|
1003
1133
|
if (pageFloats.length > 0) resolved.floats = pageFloats;
|
|
1004
|
-
if (pageFnNums.length > 0)
|
|
1134
|
+
if (pageFnNums.length > 0)
|
|
1135
|
+
resolved.footnotes = buildFootnoteArea(pageFnNums);
|
|
1005
1136
|
pages.push(resolved);
|
|
1006
1137
|
lines = [];
|
|
1007
1138
|
tables = [];
|
|
@@ -1091,7 +1222,9 @@ function placeBlocks(items, config, ctx, band, footnotes) {
|
|
|
1091
1222
|
}
|
|
1092
1223
|
const b = bandAt(y, estH);
|
|
1093
1224
|
if (b) return b;
|
|
1094
|
-
const blockers = exclusions.filter(
|
|
1225
|
+
const blockers = exclusions.filter(
|
|
1226
|
+
(ex) => ex.top < y + estH && ex.bottom > y
|
|
1227
|
+
);
|
|
1095
1228
|
if (blockers.length === 0) return { left: colX0(), right: colX1() };
|
|
1096
1229
|
y = Math.min(...blockers.map((ex) => ex.bottom));
|
|
1097
1230
|
}
|
|
@@ -1135,7 +1268,9 @@ function placeBlocks(items, config, ctx, band, footnotes) {
|
|
|
1135
1268
|
}
|
|
1136
1269
|
const drafts = item.para.drafts;
|
|
1137
1270
|
const draftsHeight = drafts?.reduce((s, d) => s + d.height, 0) ?? 0;
|
|
1138
|
-
const floatsAhead = exclusions.some(
|
|
1271
|
+
const floatsAhead = exclusions.some(
|
|
1272
|
+
(ex) => ex.bottom > y && ex.top < y + draftsHeight
|
|
1273
|
+
);
|
|
1139
1274
|
if (drafts && !floatsAhead) {
|
|
1140
1275
|
for (const d of drafts) emitLine(d);
|
|
1141
1276
|
} else {
|
|
@@ -1254,16 +1389,25 @@ function shiftDrafts(drafts, delta) {
|
|
|
1254
1389
|
...d,
|
|
1255
1390
|
from: d.from != null ? d.from + delta : d.from,
|
|
1256
1391
|
to: d.to != null ? d.to + delta : d.to,
|
|
1257
|
-
segments: d.segments.map(
|
|
1258
|
-
|
|
1392
|
+
segments: d.segments.map(
|
|
1393
|
+
(s) => s.pos != null ? { ...s, pos: s.pos + delta } : s
|
|
1394
|
+
),
|
|
1395
|
+
images: d.images.map(
|
|
1396
|
+
(im) => im.pos != null ? { ...im, pos: im.pos + delta } : im
|
|
1397
|
+
)
|
|
1259
1398
|
}));
|
|
1260
1399
|
}
|
|
1261
1400
|
function cloneLineShifted(l, delta) {
|
|
1262
1401
|
const out = {
|
|
1263
1402
|
...l,
|
|
1264
|
-
segments: l.segments.map(
|
|
1403
|
+
segments: l.segments.map(
|
|
1404
|
+
(s) => s.pos != null ? { ...s, pos: s.pos + delta } : { ...s }
|
|
1405
|
+
)
|
|
1265
1406
|
};
|
|
1266
|
-
if (l.images)
|
|
1407
|
+
if (l.images)
|
|
1408
|
+
out.images = l.images.map(
|
|
1409
|
+
(im) => im.pos != null ? { ...im, pos: im.pos + delta } : { ...im }
|
|
1410
|
+
);
|
|
1267
1411
|
if (l.from != null) out.from = l.from + delta;
|
|
1268
1412
|
if (l.to != null) out.to = l.to + delta;
|
|
1269
1413
|
return out;
|
|
@@ -1306,7 +1450,10 @@ function layVariants(docs, fn) {
|
|
|
1306
1450
|
return out;
|
|
1307
1451
|
}
|
|
1308
1452
|
function maxBandHeight(bands) {
|
|
1309
|
-
return Math.max(
|
|
1453
|
+
return Math.max(
|
|
1454
|
+
0,
|
|
1455
|
+
...[bands.default, bands.first, bands.even].filter(Boolean).map((b) => b.height)
|
|
1456
|
+
);
|
|
1310
1457
|
}
|
|
1311
1458
|
function anyBandHasFields(bands) {
|
|
1312
1459
|
return [bands.default, bands.first, bands.even].some(
|
|
@@ -1332,9 +1479,17 @@ function layoutChrome(doc, topY, left, right, ctx) {
|
|
|
1332
1479
|
const lines = flow.lines.map((l) => ({ ...l, y: l.y + topY }));
|
|
1333
1480
|
flow.tables.forEach((t) => offsetTable(t, topY));
|
|
1334
1481
|
stripPositions(lines, flow.tables);
|
|
1335
|
-
const band = {
|
|
1482
|
+
const band = {
|
|
1483
|
+
lines,
|
|
1484
|
+
tables: flow.tables,
|
|
1485
|
+
height: flow.height
|
|
1486
|
+
};
|
|
1336
1487
|
if (flow.floats.length > 0)
|
|
1337
|
-
band.floats = flow.floats.map((f) => ({
|
|
1488
|
+
band.floats = flow.floats.map((f) => ({
|
|
1489
|
+
...f,
|
|
1490
|
+
y: f.y + topY,
|
|
1491
|
+
pos: void 0
|
|
1492
|
+
}));
|
|
1338
1493
|
return band;
|
|
1339
1494
|
}
|
|
1340
1495
|
function layoutFooterChrome(doc, pageHeight, left, right, ctx) {
|
|
@@ -1346,12 +1501,21 @@ function layoutFooterChrome(doc, pageHeight, left, right, ctx) {
|
|
|
1346
1501
|
height: flow.height
|
|
1347
1502
|
};
|
|
1348
1503
|
band.tables.forEach((t) => offsetTable(t, topY));
|
|
1349
|
-
if (flow.floats)
|
|
1504
|
+
if (flow.floats)
|
|
1505
|
+
band.floats = flow.floats.map((f) => ({ ...f, y: f.y + topY }));
|
|
1350
1506
|
return band;
|
|
1351
1507
|
}
|
|
1352
1508
|
function layoutFootnoteBody(doc, left, right, ctx) {
|
|
1353
|
-
const fnCtx = {
|
|
1354
|
-
|
|
1509
|
+
const fnCtx = {
|
|
1510
|
+
...ctx,
|
|
1511
|
+
base: { ...ctx.base, sizePt: ctx.base.sizePt * FOOTNOTE_FONT_SCALE }
|
|
1512
|
+
};
|
|
1513
|
+
const flow = layoutFlow(
|
|
1514
|
+
toFlowBlocks(doc, fnCtx.base, false),
|
|
1515
|
+
left,
|
|
1516
|
+
right,
|
|
1517
|
+
fnCtx
|
|
1518
|
+
);
|
|
1355
1519
|
stripPositions(flow.lines, flow.tables);
|
|
1356
1520
|
return { lines: flow.lines, height: flow.height };
|
|
1357
1521
|
}
|
|
@@ -1360,10 +1524,24 @@ function layout(doc, config, cache, chrome, footnotes) {
|
|
|
1360
1524
|
const { page } = config;
|
|
1361
1525
|
const left = page.margin.left;
|
|
1362
1526
|
const right = page.width - page.margin.right;
|
|
1363
|
-
const headerDocs = {
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1527
|
+
const headerDocs = {
|
|
1528
|
+
default: chrome?.header,
|
|
1529
|
+
first: chrome?.headerFirst,
|
|
1530
|
+
even: chrome?.headerEven
|
|
1531
|
+
};
|
|
1532
|
+
const footerDocs = {
|
|
1533
|
+
default: chrome?.footer,
|
|
1534
|
+
first: chrome?.footerFirst,
|
|
1535
|
+
even: chrome?.footerEven
|
|
1536
|
+
};
|
|
1537
|
+
const layHeaders = (c) => layVariants(
|
|
1538
|
+
headerDocs,
|
|
1539
|
+
(d) => layoutChrome(d, CHROME_DISTANCE, left, right, c)
|
|
1540
|
+
);
|
|
1541
|
+
const layFooters = (c) => layVariants(
|
|
1542
|
+
footerDocs,
|
|
1543
|
+
(d) => layoutFooterChrome(d, page.height, left, right, c)
|
|
1544
|
+
);
|
|
1367
1545
|
let headers = layHeaders(ctx);
|
|
1368
1546
|
let footers = layFooters(ctx);
|
|
1369
1547
|
let top = page.margin.top;
|
|
@@ -1372,25 +1550,46 @@ function layout(doc, config, cache, chrome, footnotes) {
|
|
|
1372
1550
|
top = Math.max(top, CHROME_DISTANCE + maxBandHeight(headers));
|
|
1373
1551
|
}
|
|
1374
1552
|
if (footers.default || footers.first || footers.even) {
|
|
1375
|
-
bottom = Math.min(
|
|
1553
|
+
bottom = Math.min(
|
|
1554
|
+
bottom,
|
|
1555
|
+
page.height - CHROME_DISTANCE - maxBandHeight(footers)
|
|
1556
|
+
);
|
|
1376
1557
|
}
|
|
1377
|
-
const counter = (0, import_bapbong_model.createNumberingCounter)(
|
|
1558
|
+
const counter = (0, import_bapbong_model.createNumberingCounter)(
|
|
1559
|
+
doc.attrs["numbering"]
|
|
1560
|
+
);
|
|
1378
1561
|
const sections = doc.attrs["sections"] ?? [
|
|
1379
|
-
{
|
|
1562
|
+
{
|
|
1563
|
+
blockCount: doc.childCount,
|
|
1564
|
+
columns: { count: 1, gap: 0 },
|
|
1565
|
+
newPage: true
|
|
1566
|
+
}
|
|
1380
1567
|
];
|
|
1381
1568
|
const blockSection = [];
|
|
1382
1569
|
for (const sec of sections) {
|
|
1383
1570
|
for (let k = 0; k < sec.blockCount; k++) {
|
|
1384
|
-
blockSection.push({
|
|
1571
|
+
blockSection.push({
|
|
1572
|
+
columns: sec.columns,
|
|
1573
|
+
start: k === 0,
|
|
1574
|
+
newPage: sec.newPage
|
|
1575
|
+
});
|
|
1385
1576
|
}
|
|
1386
1577
|
}
|
|
1387
1578
|
const lastSec = sections[sections.length - 1];
|
|
1388
1579
|
while (blockSection.length < doc.childCount) {
|
|
1389
|
-
blockSection.push({
|
|
1580
|
+
blockSection.push({
|
|
1581
|
+
columns: lastSec.columns,
|
|
1582
|
+
start: false,
|
|
1583
|
+
newPage: lastSec.newPage
|
|
1584
|
+
});
|
|
1390
1585
|
}
|
|
1391
1586
|
const items = [];
|
|
1392
1587
|
doc.forEach((node, offset, index) => {
|
|
1393
|
-
const bs = blockSection[index] ?? {
|
|
1588
|
+
const bs = blockSection[index] ?? {
|
|
1589
|
+
columns: { count: 1, gap: 0 },
|
|
1590
|
+
start: false,
|
|
1591
|
+
newPage: true
|
|
1592
|
+
};
|
|
1394
1593
|
const colRight = left + columnWidth(right - left, bs.columns);
|
|
1395
1594
|
const tag = (item) => {
|
|
1396
1595
|
if (bs.start) item.section = { ...bs.columns, newPage: bs.newPage };
|
|
@@ -1423,17 +1622,35 @@ function layout(doc, config, cache, chrome, footnotes) {
|
|
|
1423
1622
|
}
|
|
1424
1623
|
const flow = paragraphToFlow(node, ctx.base, offset, true, marker);
|
|
1425
1624
|
const drafts = layoutParagraph(flow, left, colRight, ctx);
|
|
1426
|
-
cache?.paragraphs.set(node, {
|
|
1625
|
+
cache?.paragraphs.set(node, {
|
|
1626
|
+
left,
|
|
1627
|
+
right: colRight,
|
|
1628
|
+
basePos: contentStart,
|
|
1629
|
+
marker,
|
|
1630
|
+
drafts
|
|
1631
|
+
});
|
|
1427
1632
|
items.push(tag({ para: { ...para(drafts), getFlow: () => flow } }));
|
|
1428
1633
|
} else if (node.type.name === "table") {
|
|
1429
1634
|
const hit = cache?.tables.get(node);
|
|
1430
1635
|
if (hit && hit.left === left && hit.right === colRight) {
|
|
1431
|
-
items.push(
|
|
1636
|
+
items.push(
|
|
1637
|
+
tag({ table: cloneTableShifted(hit.table, offset - hit.basePos) })
|
|
1638
|
+
);
|
|
1432
1639
|
return;
|
|
1433
1640
|
}
|
|
1434
|
-
const table = layoutTable(
|
|
1641
|
+
const table = layoutTable(
|
|
1642
|
+
tableToFlow(node, ctx.base, offset, counter),
|
|
1643
|
+
left,
|
|
1644
|
+
colRight,
|
|
1645
|
+
ctx
|
|
1646
|
+
);
|
|
1435
1647
|
if (cache && !tableHasList(node)) {
|
|
1436
|
-
cache.tables.set(node, {
|
|
1648
|
+
cache.tables.set(node, {
|
|
1649
|
+
left,
|
|
1650
|
+
right: colRight,
|
|
1651
|
+
basePos: offset,
|
|
1652
|
+
table
|
|
1653
|
+
});
|
|
1437
1654
|
items.push(tag({ table: cloneTableShifted(table, 0) }));
|
|
1438
1655
|
} else {
|
|
1439
1656
|
items.push(tag({ table }));
|
|
@@ -1451,7 +1668,10 @@ function layout(doc, config, cache, chrome, footnotes) {
|
|
|
1451
1668
|
assignSectionHeights(items);
|
|
1452
1669
|
const resolved = placeBlocks(items, config, ctx, { top, bottom }, fnMap);
|
|
1453
1670
|
if (anyBandHasFields(headers) || anyBandHasFields(footers)) {
|
|
1454
|
-
const fieldCtx = {
|
|
1671
|
+
const fieldCtx = {
|
|
1672
|
+
...ctx,
|
|
1673
|
+
fieldPlaceholder: String(resolved.pages.length)
|
|
1674
|
+
};
|
|
1455
1675
|
headers = layHeaders(fieldCtx);
|
|
1456
1676
|
footers = layFooters(fieldCtx);
|
|
1457
1677
|
}
|
|
@@ -1462,7 +1682,10 @@ function layout(doc, config, cache, chrome, footnotes) {
|
|
|
1462
1682
|
if (footers.first) resolved.pageFooterFirst = footers.first;
|
|
1463
1683
|
if (footers.even) resolved.pageFooterEven = footers.even;
|
|
1464
1684
|
if (chrome?.titlePg || chrome?.evenAndOdd) {
|
|
1465
|
-
resolved.chromeSelect = {
|
|
1685
|
+
resolved.chromeSelect = {
|
|
1686
|
+
titlePg: !!chrome.titlePg,
|
|
1687
|
+
evenAndOdd: !!chrome.evenAndOdd
|
|
1688
|
+
};
|
|
1466
1689
|
}
|
|
1467
1690
|
return resolved;
|
|
1468
1691
|
}
|