Skip to content

Commit dac0f53

Browse files
committed
remove linebox array, inline arrays
Significantly reduces a layout's retained memory. Lineboxes are actually not needed to paint because walking the tree and the fragmented items together in order produces the same paint instructions. Another fun fact is that you don't need a list of lineboxes, you can get by with a singleton! This new approach also fixes a few bugs with inline fragmenting, which would have been harder with the old code. I've always wanted to remove ShapedItem.inlines; this is not needed since it's available if you walk the tree. v8 says performance is worse, but that's all gc. When I take the .gc('inner') mitata calls out, perf-2 is faster. perf-3 is not. Firefox says no difference on perf-3. I give up trying to figure out why v8 is slower to run perf-3, as I'm sure this is a tighter solution.
1 parent 5e8e735 commit dac0f53

6 files changed

Lines changed: 990 additions & 798 deletions

File tree

src/layout-flow.ts

Lines changed: 34 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
getIfcContribution,
1111
createIfcShapedItems,
1212
createIfcLineboxes,
13-
positionIfcItems,
1413
sliceIfcRenderText,
1514
collapseWhitespace
1615
} from './layout-text.ts';
@@ -832,16 +831,14 @@ export class BlockContainerOfInlines extends BlockContainerBase {
832831
text: string;
833832
buffer: AllocatedUint16Array;
834833
items: ShapedItem[];
835-
lineboxes: Linebox[];
836-
fragments: Map<Inline, InlineFragment[]>;
834+
fragments: InlineFragment[];
837835

838836
constructor(style: Style, attrs: number) {
839837
super(style, attrs);
840838
this.text = '';
841839
this.buffer = EmptyBuffer;
842840
this.items = [];
843-
this.lineboxes = [];
844-
this.fragments = new Map();
841+
this.fragments = [];
845842
}
846843

847844
prelayoutPostorder(layout: Layout, ctx: PrelayoutContext) {
@@ -851,12 +848,12 @@ export class BlockContainerOfInlines extends BlockContainerBase {
851848
this.buffer.destroy();
852849
this.buffer = createIfcBuffer(this.text);
853850
this.items = createIfcShapedItems(layout, this, inline);
854-
this.fragments.clear();
851+
this.fragments = [];
855852
}
856853
}
857854

858855
positionItemsPostlayout(layout: Layout) {
859-
const inlineShifts: Map<Inline, {dx: number; dy: number}> = new Map();
856+
const inlineShifts: Map<number, {dx: number; dy: number}> = new Map();
860857
const parents: Inline[] = [];
861858
const contentArea = this.getContentArea();
862859
const rootInline = layout.tree[this.treeStart + 1];
@@ -876,7 +873,7 @@ export class BlockContainerOfInlines extends BlockContainerBase {
876873
dy += box.getRelativeVerticalShift(containingBlock);
877874
}
878875

879-
inlineShifts.set(box, {dx, dy});
876+
inlineShifts.set(box.treeStart, {dx, dy});
880877
parents.push(box);
881878
} else {
882879
if (box.isBox()) i = box.treeFinal;
@@ -912,14 +909,11 @@ export class BlockContainerOfInlines extends BlockContainerBase {
912909
}
913910
}
914911

915-
for (const [inline, fragments] of this.fragments) {
916-
const {dx, dy} = inlineShifts.get(inline)!;
917-
918-
for (const fragment of fragments) {
919-
fragment.blockOffset += contentArea.y + dy;
920-
fragment.start += contentArea.x + dx;
921-
fragment.end += contentArea.x + dx;
922-
}
912+
for (const fragment of this.fragments) {
913+
const {dx, dy} = inlineShifts.get(fragment.treeIndex)!;
914+
fragment.blockOffset += contentArea.y + dy;
915+
fragment.left += contentArea.x + dx;
916+
fragment.right += contentArea.x + dx;
923917
}
924918
}
925919

@@ -955,15 +949,6 @@ export class BlockContainerOfInlines extends BlockContainerBase {
955949
return sliceIfcRenderText(layout, this, item, start, end);
956950
}
957951

958-
getLineboxHeight() {
959-
if (this.lineboxes.length) {
960-
const line = this.lineboxes.at(-1)!;
961-
return line.blockOffset + line.height();
962-
} else {
963-
return 0;
964-
}
965-
}
966-
967952
shouldLayoutContent(layout: Layout) {
968953
const inline = layout.tree[this.treeStart + 1];
969954
if (!inline.isInline()) throw new Error('Assertion failed');
@@ -977,11 +962,10 @@ export class BlockContainerOfInlines extends BlockContainerBase {
977962
const containingBlock = this.getContainingBlock();
978963
const blockSize = this.style.getBlockSize(containingBlock);
979964
if (this.shouldLayoutContent(layout)) {
980-
this.lineboxes = createIfcLineboxes(layout, this, ctx);
981-
positionIfcItems(layout, this);
982-
}
983-
if (blockSize === 'auto') {
984-
this.setBlockSize(containingBlock, this.getLineboxHeight());
965+
const ifc = createIfcLineboxes(layout, this, ctx);
966+
if (blockSize === 'auto') {
967+
this.setBlockSize(containingBlock, ifc.blockOffset - ifc.bfc.cbBlockStart);
968+
}
985969
}
986970
}
987971
}
@@ -1276,7 +1260,6 @@ export class Break extends TreeNode {
12761260
}
12771261

12781262
export class Inline extends Box {
1279-
public nshaped: number;
12801263
public metrics: InlineMetrics;
12811264
public textStart: number;
12821265
public textEnd: number;
@@ -1287,13 +1270,11 @@ export class Inline extends Box {
12871270
this.textEnd = 0;
12881271
this.treeStart = 0;
12891272
this.treeFinal = 0;
1290-
this.nshaped = 0;
12911273
this.metrics = EmptyInlineMetrics;
12921274
}
12931275

12941276
prelayoutPreorder(ctx: PrelayoutContext) {
12951277
super.prelayoutPreorder(ctx);
1296-
this.nshaped = 0;
12971278
this.metrics = getFontMetrics(this);
12981279
}
12991280

@@ -1372,22 +1353,20 @@ export class Inline extends Box {
13721353
return this.style.hasLineRightGap(containingBlock);
13731354
}
13741355

1375-
getInlineSideSize(containingBlock: BoxArea, side: 'pre' | 'post') {
1356+
getInlineStartSize(containingBlock: BoxArea) {
13761357
const direction = this.getDirectionAsParticipant(containingBlock);
1377-
if (
1378-
direction === 'ltr' && side === 'pre' ||
1379-
direction === 'rtl' && side === 'post'
1380-
) {
1381-
const marginLineLeft = this.style.getMarginLineLeft(containingBlock);
1382-
return (marginLineLeft === 'auto' ? 0 : marginLineLeft)
1383-
+ this.style.getBorderLineLeftWidth(containingBlock)
1384-
+ this.style.getPaddingLineLeft(containingBlock);
1385-
} else {
1386-
const marginLineRight = this.style.getMarginLineRight(containingBlock);
1387-
return (marginLineRight === 'auto' ? 0 : marginLineRight)
1388-
+ this.style.getBorderLineRightWidth(containingBlock)
1389-
+ this.style.getPaddingLineRight(containingBlock);
1390-
}
1358+
const marginStart = this.style.getMarginInlineStart(containingBlock, direction);
1359+
return (marginStart === 'auto' ? 0 : marginStart)
1360+
+ this.style.getBorderInlineStartWidth(containingBlock, direction)
1361+
+ this.style.getPaddingInlineStart(containingBlock, direction);
1362+
}
1363+
1364+
getInlineEndSize(containingBlock: BoxArea) {
1365+
const direction = this.getDirectionAsParticipant(containingBlock);
1366+
const marginEnd = this.style.getMarginInlineEnd(containingBlock, direction);
1367+
return (marginEnd === 'auto' ? 0 : marginEnd)
1368+
+ this.style.getBorderInlineEndWidth(containingBlock, direction)
1369+
+ this.style.getPaddingInlineEnd(containingBlock, direction);
13911370
}
13921371

13931372
isInline(): this is Inline {
@@ -1521,9 +1500,9 @@ export class ReplacedBox extends FormattingBox {
15211500
export type InlineLevel = Inline | Run | Break | BlockContainer | ReplacedBox;
15221501

15231502
type InlineIteratorBuffered = {state: 'pre' | 'post', item: Inline}
1524-
| {state: 'text', item: Run}
1503+
| {state: 'text', item: Run, index: number}
15251504
| {state: 'box', item: BlockLevel}
1526-
| {state: 'break'}
1505+
| {state: 'break', index: number}
15271506
| {state: 'breakop'};
15281507

15291508
type InlineIteratorValue = InlineIteratorBuffered | {state: 'breakspot'};
@@ -1566,6 +1545,9 @@ export function inlineIteratorStateNext(state: InlineIteratorState) {
15661545
state.breakspotIndex = 0;
15671546
state.isInlineBlock = false;
15681547

1548+
// This body of this loop consumes (pre | post)* atomic? post*,
1549+
// where atomic is a <br>, an inline-block, or a float, and pre/post
1550+
// are sides of an inline.
15691551
while (state.index <= state.block.treeFinal && !foundAtomic) {
15701552
const item = state.layout.tree[state.index];
15711553

@@ -1577,9 +1559,9 @@ export function inlineIteratorStateNext(state: InlineIteratorState) {
15771559
state.minlevel = state.parents.length;
15781560

15791561
if (item.isRun()) {
1580-
state.buffered.push({state: 'text', item});
1562+
state.buffered.push({state: 'text', item, index: state.index});
15811563
} else if (item.isBreak()) {
1582-
state.buffered.push({state: 'break'});
1564+
state.buffered.push({state: 'break', index: state.index});
15831565
} else {
15841566
if (item.isFloat()) {
15851567
state.buffered.push({state: 'box', item});

0 commit comments

Comments
 (0)