Skip to content

Commit 313fd34

Browse files
committed
perf: add diverging BlockContainer types
Saves a bit of memory, since an OfInlines only ever has one child, which we can now represent without wrapping in an array. Soon the paragraph's properties will go on BlockContainerOfInlines.
1 parent 66659b3 commit 313fd34

10 files changed

Lines changed: 458 additions & 431 deletions

File tree

src/api.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,7 @@ export function staticLayoutContribution(box: BlockContainer): number {
218218
}
219219

220220
if (box.isBlockContainerOfInlines()) {
221-
const [ifc] = box.children;
222-
for (const line of ifc.paragraph.lineboxes) {
221+
for (const line of box.ifc.paragraph.lineboxes) {
223222
intrinsicSize = Math.max(intrinsicSize, line.width);
224223
}
225224
// TODO: floats

src/layout-box.ts

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,16 @@ import {id, Logger} from './util.ts';
22

33
import type {Style} from './style.ts';
44
import type {Run} from './layout-text.ts';
5-
import type {InlineLevel, Break, Inline, IfcInline, BlockContainer, ReplacedBox} from './layout-flow.ts';
5+
import type {
6+
InlineLevel,
7+
Break,
8+
Inline,
9+
IfcInline,
10+
BlockContainer,
11+
BlockContainerOfInlines,
12+
BlockContainerOfBlocks,
13+
ReplacedBox
14+
} from './layout-flow.ts';
615

716
export interface LogicalArea {
817
blockStart: number | undefined;
@@ -34,6 +43,14 @@ export abstract class RenderItem {
3443
return false;
3544
}
3645

46+
isBlockContainerOfInlines(): this is BlockContainerOfInlines {
47+
return false;
48+
}
49+
50+
isBlockContainerOfBlocks(): this is BlockContainerOfBlocks {
51+
return false;
52+
}
53+
3754
isFormattingBox(): this is FormattingBox {
3855
return false;
3956
}
@@ -94,11 +111,16 @@ export abstract class RenderItem {
94111

95112
log.text('\n');
96113

97-
if (this.isBlockContainer() || this.isInline()) {
114+
if (
115+
this.isBlockContainerOfInlines() ||
116+
this.isBlockContainerOfBlocks() ||
117+
this.isInline()
118+
) {
119+
const children = this.isBlockContainerOfInlines() ? [this.ifc] : this.children;
98120
log.pushIndent();
99121

100-
for (let i = 0; i < this.children.length; i++) {
101-
this.children[i].log(options, log);
122+
for (let i = 0; i < children.length; i++) {
123+
children[i].log(options, log);
102124
}
103125

104126
log.popIndent();
@@ -782,10 +804,12 @@ export function prelayout(root: BlockContainer, icb: BoxArea) {
782804
if (box.style.position !== 'static') pstack.push(box.getPaddingArea());
783805
}
784806

785-
if (box.isBlockContainer() || box.isInline()) {
807+
if (box.isBlockContainerOfBlocks() || box.isInline()) {
786808
for (let i = box.children.length - 1; i >= 0; i--) {
787809
stack.push(box.children[i]);
788810
}
811+
} else if (box.isBlockContainerOfInlines()) {
812+
stack.push(box.ifc);
789813
}
790814
} else if (box.isRun()) {
791815
box.propagate(parents.at(-1)!, ifcs.at(-1)!.paragraph.string);
@@ -809,14 +833,18 @@ export function postlayout(root: BlockContainer) {
809833
box.postlayoutPreorder();
810834
stack.push({sentinel: true});
811835
parents.push(box);
812-
for (let i = box.children.length - 1; i >= 0; i--) {
813-
const child = box.children[i];
814-
if (child.isBlockContainer() || child.isInline()) {
815-
stack.push(child);
816-
} else {
817-
child.postlayoutPreorder()
818-
child.postlayoutPostorder();
836+
if (box.isBlockContainerOfBlocks() || box.isInline()) {
837+
for (let i = box.children.length - 1; i >= 0; i--) {
838+
const child = box.children[i];
839+
if (child.isBlockContainer() || child.isInline()) {
840+
stack.push(child);
841+
} else {
842+
child.postlayoutPreorder()
843+
child.postlayoutPostorder();
844+
}
819845
}
846+
} else if (box.isBlockContainerOfInlines()) {
847+
stack.push(box.ifc);
820848
}
821849
}
822850
}

src/layout-flow.ts

Lines changed: 49 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -728,30 +728,13 @@ export class FloatContext {
728728
}
729729
}
730730

731-
export interface BlockContainerOfInlines extends BlockContainer {
732-
children: IfcInline[];
733-
}
734-
735-
export type BlockLevel = BlockContainer | ReplacedBox;
736-
737-
export interface BlockContainerOfBlocks extends BlockContainer {
738-
children: BlockLevel[];
739-
}
740-
741-
export class BlockContainer extends FormattingBox {
742-
public children: IfcInline[] | BlockLevel[];
743-
731+
export abstract class BlockContainer extends FormattingBox {
744732
static ATTRS = {
745733
...FormattingBox.ATTRS,
746734
isInline: Box.BITS.isInline,
747735
isBfcRoot: Box.BITS.isBfcRoot
748736
};
749737

750-
constructor(style: Style, children: IfcInline[] | BlockLevel[], attrs: number) {
751-
super(style, attrs);
752-
this.children = children;
753-
}
754-
755738
getLogSymbol() {
756739
if (this.isFloat()) {
757740
return '○︎';
@@ -802,27 +785,21 @@ export class BlockContainer extends FormattingBox {
802785
return Boolean(this.bitfield & Box.BITS.enableLogging);
803786
}
804787

805-
isBlockContainerOfInlines(): this is BlockContainerOfInlines {
806-
return Boolean(this.children.length && this.children[0].isIfcInline());
807-
}
808-
809788
canCollapseThrough(): boolean {
810789
const blockSize = this.style.getBlockSize(this.getContainingBlock());
811790

812791
if (blockSize !== 'auto' && blockSize !== 0) return false;
813792

814793
if (this.isBlockContainerOfInlines()) {
815-
const [ifc] = this.children;
816-
return !ifc.hasText();
817-
} else {
794+
return !this.ifc.hasText();
795+
} else if (this.isBlockContainerOfBlocks()) {
818796
return this.children.length === 0;
797+
} else {
798+
// TODO: this is a terrible situation, but where else does the method go?
799+
throw new Error('Unreachable');
819800
}
820801
}
821802

822-
isBlockContainerOfBlocks(): this is BlockContainerOfBlocks {
823-
return !this.isBlockContainerOfInlines();
824-
}
825-
826803
propagate(parent: Box) {
827804
super.propagate(parent);
828805

@@ -832,14 +809,6 @@ export class BlockContainer extends FormattingBox {
832809
}
833810
}
834811

835-
doTextLayout(ctx: LayoutContext) {
836-
if (!this.isBlockContainerOfInlines()) throw new Error('Children are block containers');
837-
const [ifc] = this.children;
838-
const blockSize = this.style.getBlockSize(this.getContainingBlock());
839-
ifc.doTextLayout(ctx);
840-
if (blockSize === 'auto') this.setBlockSize(ifc.paragraph.getHeight());
841-
}
842-
843812
hasBackground() {
844813
return this.style.hasPaint();
845814
}
@@ -849,6 +818,40 @@ export class BlockContainer extends FormattingBox {
849818
}
850819
}
851820

821+
export class BlockContainerOfInlines extends BlockContainer {
822+
ifc: IfcInline;
823+
824+
constructor(style: Style, ifc: IfcInline, attrs: number) {
825+
super(style, attrs);
826+
this.ifc = ifc;
827+
}
828+
829+
isBlockContainerOfInlines(): this is BlockContainerOfInlines {
830+
return true;
831+
}
832+
833+
doTextLayout(ctx: LayoutContext) {
834+
const blockSize = this.style.getBlockSize(this.getContainingBlock());
835+
this.ifc.doTextLayout(ctx);
836+
if (blockSize === 'auto') this.setBlockSize(this.ifc.paragraph.getHeight());
837+
}
838+
}
839+
840+
export type BlockLevel = BlockContainer | ReplacedBox;
841+
842+
export class BlockContainerOfBlocks extends BlockContainer {
843+
children: BlockLevel[];
844+
845+
constructor(style: Style, children: BlockLevel[], attrs: number) {
846+
super(style, attrs);
847+
this.children = children;
848+
}
849+
850+
isBlockContainerOfBlocks(): this is BlockContainerOfBlocks {
851+
return true;
852+
}
853+
}
854+
852855
// §10.3.3
853856
function doInlineBoxModelForBlockBox(box: FormattingBox) {
854857
const containingBlock = box.getContainingBlock();
@@ -921,7 +924,7 @@ function doBlockBoxModelForBlockBox(box: BlockContainer) {
921924
const blockSize = box.style.getBlockSize(containingBlock);
922925

923926
if (blockSize === 'auto') {
924-
if (box.children.length === 0) {
927+
if (box.canCollapseThrough()) {
925928
box.setBlockSize(0); // Case 4
926929
} else {
927930
// Cases 1-4 should be handled by doBoxPositioning, where margin
@@ -1036,9 +1039,8 @@ export function layoutContribution(
10361039
isize = Math.max(isize, layoutContribution(child, mode));
10371040
}
10381041
} else if (box.isBlockContainerOfInlines()) {
1039-
const [ifc] = box.children;
1040-
if (ifc.shouldLayoutContent()) {
1041-
isize = ifc.paragraph.contribution(mode);
1042+
if (box.ifc.shouldLayoutContent()) {
1043+
isize = box.ifc.paragraph.contribution(mode);
10421044
}
10431045
}
10441046
}
@@ -1610,7 +1612,7 @@ function wrapInBlockContainer(parentEl: HTMLElement, inlines: InlineLevel[], tex
16101612
let attrs = Box.ATTRS.isAnonymous;
16111613
if ('x-dropflow-log' in parentEl.attrs) attrs |= Box.ATTRS.enableLogging;
16121614
const ifc = new IfcInline(anonStyle, text.value, inlines, attrs);
1613-
return new BlockContainer(anonStyle, [ifc], attrs);
1615+
return new BlockContainerOfInlines(anonStyle, ifc, attrs);
16141616
}
16151617

16161618
function generateFormattingBox(el: HTMLElement): BlockLevel {
@@ -1705,22 +1707,22 @@ export function generateBlockContainer(el: HTMLElement): BlockContainer {
17051707
attrs |= BlockContainer.ATTRS.isInline;
17061708
}
17071709

1708-
let children: BlockLevel[] | IfcInline[];
1710+
let box;
17091711

17101712
if (inlines.length) {
17111713
if (blocks.length) {
17121714
blocks.push(wrapInBlockContainer(el, inlines, text));
1713-
children = blocks;
1715+
box = new BlockContainerOfBlocks(el.style, blocks, attrs);
17141716
} else {
17151717
const anonComputedStyle = createStyle(el.style, EMPTY_STYLE);
17161718
const ifcAttrs = Box.ATTRS.isAnonymous | (enableLogging ? Box.ATTRS.enableLogging : 0);
1717-
children = [new IfcInline(anonComputedStyle, text.value, inlines, ifcAttrs)];
1719+
const ifc = new IfcInline(anonComputedStyle, text.value, inlines, ifcAttrs);
1720+
box = new BlockContainerOfInlines(el.style, ifc, attrs);
17181721
}
17191722
} else {
1720-
children = blocks;
1723+
box = new BlockContainerOfBlocks(el.style, blocks, attrs);
17211724
}
17221725

1723-
const box = new BlockContainer(el.style, children, attrs);
17241726
el.boxes.push(box);
17251727
return box;
17261728
}

src/layout-text.ts

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,26 +1011,21 @@ function getLastBaseline(block: FormattingBox) {
10111011

10121012
if (block.isReplacedBox()) {
10131013
return undefined;
1014-
} else if (block.isBlockContainer()) {
1015-
if (block.isBlockContainerOfInlines()) {
1016-
const [ifc] = block.children;
1017-
const linebox = ifc.paragraph.lineboxes.at(-1);
1018-
if (linebox) return offset + linebox.blockOffset + linebox.ascender;
1019-
}
1020-
1021-
if (block.isBlockContainerOfBlocks()) {
1022-
const parentOffset = offset;
1023-
1024-
for (const child of block.children) {
1025-
if (child.isBlockContainer()) {
1026-
const containingBlock = child.getContainingBlock();
1027-
const offset = parentOffset
1028-
+ child.getBorderArea().blockStart
1029-
+ child.style.getBorderBlockStartWidth(containingBlock);
1030-
+ child.style.getPaddingBlockStart(containingBlock);
1031-
1032-
stack.push({block: child, offset});
1033-
}
1014+
} else if (block.isBlockContainerOfInlines()) {
1015+
const linebox = block.ifc.paragraph.lineboxes.at(-1);
1016+
if (linebox) return offset + linebox.blockOffset + linebox.ascender;
1017+
} else if (block.isBlockContainerOfBlocks()) {
1018+
const parentOffset = offset;
1019+
1020+
for (const child of block.children) {
1021+
if (child.isBlockContainer()) {
1022+
const containingBlock = child.getContainingBlock();
1023+
const offset = parentOffset
1024+
+ child.getBorderArea().blockStart
1025+
+ child.style.getBorderBlockStartWidth(containingBlock);
1026+
+ child.style.getPaddingBlockStart(containingBlock);
1027+
1028+
stack.push({block: child, offset});
10341029
}
10351030
}
10361031
}

src/paint.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,7 @@ function paintBackgroundDescendents(root: FormattingBox, b: PaintBackend) {
175175
paintFormattingBoxBackground(box, b);
176176
}
177177

178-
if (
179-
box.isBlockContainer() &&
180-
box.isBlockContainerOfBlocks() &&
181-
box.hasBackgroundInLayerRoot()
182-
) {
178+
if (box.isBlockContainerOfBlocks() && box.hasBackgroundInLayerRoot()) {
183179
stack.push({sentinel: true});
184180
parents.push(box);
185181

@@ -429,8 +425,12 @@ function paintBlockForeground(root: BlockLayerRoot, b: PaintBackend) {
429425
stack.push({sentinel: true});
430426
}
431427

432-
for (let i = box.children.length - 1; i >= 0; i--) {
433-
stack.push(box.children[i]);
428+
if (box.isBlockContainerOfBlocks()) {
429+
for (let i = box.children.length - 1; i >= 0; i--) {
430+
stack.push(box.children[i]);
431+
}
432+
} else if (box.isBlockContainerOfInlines()) {
433+
stack.push(box.ifc);
434434
}
435435
}
436436
}
@@ -643,7 +643,9 @@ function createLayerRoot(rootBox: BlockContainer) {
643643
stack.push({sentinel: true});
644644
parents.push(box);
645645
if (layerRoot) parentRoots.push(layerRoot);
646-
if (box.isBlockContainer() || box.isInline()) {
646+
if (box.isBlockContainerOfInlines()) {
647+
stack.push(box.ifc);
648+
} else if (box.isInline() || box.isBlockContainerOfBlocks()) {
647649
for (let i = box.children.length - 1; i >= 0; i--) {
648650
stack.push(box.children[i]);
649651
}

test/api.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ describe('Hyperscript API', function () {
114114
const box = flow.generate(tree);
115115
registerFontAsset('Arimo/Arimo-Regular.ttf');
116116
flow.layout(box, 100);
117-
const ifc = box.children[0].children[1].children[0];
117+
const ifc = box.children[0].children[1].ifc;
118118
expect(ifc.paragraph.lineboxes).to.have.lengthOf(4);
119119
expect(ifc.paragraph.lineboxes[0].blockOffset).to.equal(0);
120120
expect(ifc.paragraph.lineboxes[1].startOffset).to.equal(20);
@@ -144,7 +144,7 @@ describe('Hyperscript API', function () {
144144
registerFontAsset('Arimo/Arimo-Regular.ttf');
145145
flow.layout(box, 100);
146146
flow.layout(box, 100);
147-
const ifc = box.children[0].children[1].children[0];
147+
const ifc = box.children[0].children[1].ifc;
148148
expect(ifc.paragraph.lineboxes).to.have.lengthOf(4);
149149
unregisterFontAsset('Arimo/Arimo-Regular.ttf');
150150
});

0 commit comments

Comments
 (0)