@@ -23,6 +23,8 @@ interface Box {
2323
2424export interface BlockBox extends Box {
2525 readonly type : "block" ;
26+ readonly border : boolean ;
27+ readonly children : LayoutBox [ ] ;
2628}
2729export interface TextBox extends Box {
2830 readonly type : "text" ;
@@ -48,8 +50,8 @@ export type ResolveImageSize = (src: string) => {
4850} | null ;
4951
5052interface Options {
51- readonly contentTop : number ;
52- readonly contentWidth : number ;
53+ readonly top : number ;
54+ readonly width : number ;
5355 readonly spacing ?: number ;
5456 readonly textWidth : TextWidth ;
5557 readonly textHeight : TextHeight ;
@@ -62,148 +64,149 @@ export const layoutBlock = (
6264 startX : number ,
6365 startY : number ,
6466 options : Options ,
65- ) : LayoutBox [ ] => {
67+ ) : BlockBox [ ] => {
6668 const {
67- contentTop ,
68- contentWidth ,
69+ top ,
70+ width ,
6971 spacing,
7072 textWidth,
7173 textHeight,
7274 resolveFont,
7375 resolveImageSize,
7476 } = options ;
75- const result : LayoutBox [ ] = [ ] ;
76- const inlines = children . filter (
77- ( c ) => c . type === "text" || c . type === "void" ,
78- ) ;
79- let y = max ( startY , contentTop ) ;
80- if ( children . length !== inlines . length ) {
81- let afterPagebreak = false ;
82- for ( let i = 0 ; i < children . length ; i ++ ) {
83- const node = children [ i ] ! ;
84- switch ( node . type ) {
85- case "text" :
86- case "void" : {
87- if ( afterPagebreak ) {
88- y = contentTop ;
89- afterPagebreak = false ;
90- }
91- const childBoxes = layoutBlock (
92- {
93- type : "block" ,
94- style : { display : "block" } ,
95- children : [ node ] ,
96- } ,
97- startX ,
98- y ,
99- options ,
100- ) ;
101- if ( childBoxes . length > 0 ) {
102- result . push ( ...childBoxes ) ;
103- const lastBox = childBoxes [ childBoxes . length - 1 ] ! ;
104- if ( "height" in lastBox ) {
105- y = lastBox . y + lastBox . height ;
106- }
107- }
108- break ;
109- }
110- case "block" : {
111- if ( afterPagebreak ) {
112- y = contentTop ;
113- afterPagebreak = false ;
114- }
115- let childBoxes : LayoutBox [ ] = [ ] ;
116- if ( node . style && node . style . display === "table" ) {
117- const rows = node . children . filter ( ( c ) => c . type === "block" ) ;
118- if ( ! rows [ 0 ] ) break ;
119- const colCount = rows [ 0 ] . children . length ;
120- const cellWidth = contentWidth / colCount ;
121- const cellPadding = 2 ;
122- let tableY = y ;
123- for ( const row of rows ) {
124- const cells = row . children . filter ( ( c ) => c . type === "block" ) ;
125- let cellHeight = 0 ;
126- const cellBoxes : LayoutBox [ ] [ ] = [ ] ;
127- for ( let colIdx = 0 ; colIdx < cells . length ; colIdx ++ ) {
128- const cell = cells [ colIdx ] ! ;
129- const boxes = measureInlines (
130- cell . children . filter (
131- ( n ) => n . type === "text" || n . type === "void" ,
132- ) ,
133- {
134- x : startX + colIdx * cellWidth + cellPadding ,
135- y : tableY + cellPadding * 2 ,
136- align : cell . style . textAlign ,
137- width : cellWidth - cellPadding * 2 ,
138- } ,
139- textWidth ,
140- textHeight ,
141- resolveFont ,
142- resolveImageSize ,
143- ) ;
144- cellBoxes . push ( boxes ) ;
145- const maxCellBottom = boxes . reduce (
146- ( acc , b ) => max ( acc , b . y + b . height ) ,
147- tableY ,
148- ) ;
149- cellHeight = max ( cellHeight , maxCellBottom - tableY ) ;
150- }
151- for ( const boxes of cellBoxes ) {
152- childBoxes . push ( ...boxes ) ;
153- }
154- for ( let colIdx = 0 ; colIdx < cells . length ; colIdx ++ ) {
155- childBoxes . push ( {
156- type : "block" ,
157- x : startX + colIdx * cellWidth ,
158- y : tableY ,
159- width : cellWidth ,
160- height : cellHeight ,
161- } ) ;
162- }
163- tableY += cellHeight ;
164- }
165- y = tableY ;
166- } else {
167- childBoxes = layoutBlock ( node , startX , y , options ) ;
168- const lastBox = childBoxes [ childBoxes . length - 1 ] ! ;
169- if ( "height" in lastBox ) {
170- y = lastBox . y + lastBox . height ;
171- }
172- }
173- if ( childBoxes . length > 0 ) {
174- result . push ( ...childBoxes ) ;
175- }
176- if ( spacing ) {
177- y += spacing ;
178- }
179- break ;
180- }
181- case "pagebreak" : {
182- result . push ( { type : "pagebreak" } ) ;
183- afterPagebreak = true ;
184- break ;
185- }
186- default : {
187- node satisfies never ;
188- }
189- }
190- }
191- } else {
192- const boxes = measureInlines (
193- inlines ,
77+ let y = max ( startY , top ) ;
78+ let height = 0 ;
79+ let border = false ;
80+ const boxes : LayoutBox [ ] = [ ] ;
81+
82+ if ( style . display === "table-cell" ) {
83+ border = true ;
84+ const cellPadding = 2 ;
85+ const inlineBoxes = measureInlines (
86+ children . filter ( ( c ) => c . type === "text" || c . type === "void" ) ,
19487 {
195- x : startX + ( style . indent ?? 0 ) ,
196- y : y ,
197- width : contentWidth ,
88+ x : startX + cellPadding ,
89+ y : y + cellPadding ,
90+ align : style . textAlign ,
91+ width : width - cellPadding * 2 ,
19892 } ,
19993 textWidth ,
20094 textHeight ,
20195 resolveFont ,
20296 resolveImageSize ,
20397 ) ;
204- result . push ( ...boxes ) ;
98+ boxes . push ( ...inlineBoxes ) ;
99+ height =
100+ inlineBoxes . reduce ( ( acc , b ) => max ( acc , b . y + b . height - y ) , 0 ) +
101+ cellPadding ;
102+ } else if ( style . display === "table" ) {
103+ const rows = children . filter ( ( c ) => c . type === "block" ) ;
104+ if ( rows [ 0 ] ) {
105+ const colCount = rows [ 0 ] . children . length ;
106+ const cellWidth = width / colCount ;
107+ let tableY = y ;
108+ for ( const row of rows ) {
109+ const cells = row . children . filter ( ( c ) => c . type === "block" ) ;
110+ let rowHeight = 0 ;
111+ const rowChildren : BlockBox [ ] = [ ] ;
112+ for ( let colIdx = 0 ; colIdx < cells . length ; colIdx ++ ) {
113+ const cell = cells [ colIdx ] ! ;
114+ const cellBoxes = layoutBlock (
115+ cell ,
116+ startX + colIdx * cellWidth ,
117+ tableY ,
118+ { ...options , width : cellWidth } ,
119+ ) ;
120+ rowChildren . push ( ...cellBoxes ) ;
121+ rowHeight = max (
122+ rowHeight ,
123+ cellBoxes . reduce ( ( acc , b ) => max ( acc , b . height ) , 0 ) ,
124+ ) ;
125+ }
126+ boxes . push ( {
127+ type : "block" ,
128+ x : startX ,
129+ y : tableY ,
130+ width : width ,
131+ height : rowHeight ,
132+ border : false ,
133+ children : rowChildren ,
134+ } ) ;
135+ tableY += rowHeight ;
136+ }
137+ height = tableY - y ;
138+ }
139+ } else if ( style . display === "block" || style . display === "table-row" ) {
140+ let afterPagebreak = false ;
141+ const inlineBuffer : ( TextNode | VoidNode ) [ ] = [ ] ;
142+
143+ const flush = ( ) => {
144+ if ( inlineBuffer . length > 0 ) {
145+ const inlineBoxes = measureInlines (
146+ inlineBuffer ,
147+ {
148+ x : startX + ( style . indent ?? 0 ) ,
149+ y : y ,
150+ width : width ,
151+ } ,
152+ textWidth ,
153+ textHeight ,
154+ resolveFont ,
155+ resolveImageSize ,
156+ ) ;
157+ boxes . push ( ...inlineBoxes ) ;
158+ if ( inlineBoxes . length > 0 ) {
159+ const lastBox = inlineBoxes [ inlineBoxes . length - 1 ] ! ;
160+ y = lastBox . y + lastBox . height ;
161+ }
162+ inlineBuffer . splice ( 0 ) ;
163+ }
164+ } ;
165+
166+ for ( const node of children ) {
167+ if ( node . type === "pagebreak" ) {
168+ flush ( ) ;
169+ boxes . push ( { type : "pagebreak" } ) ;
170+ afterPagebreak = true ;
171+ continue ;
172+ }
173+ if ( afterPagebreak ) {
174+ y = top ;
175+ afterPagebreak = false ;
176+ }
177+ if ( node . type === "block" ) {
178+ flush ( ) ;
179+ const childBoxes = layoutBlock ( node , startX , y , options ) ;
180+ boxes . push ( ...childBoxes ) ;
181+ if ( childBoxes . length > 0 ) {
182+ const lastBox = childBoxes [ childBoxes . length - 1 ] ! ;
183+ y = lastBox . y + lastBox . height ;
184+ }
185+ } else if ( node . type === "text" || node . type === "void" ) {
186+ inlineBuffer . push ( node ) ;
187+ }
188+ if ( spacing ) {
189+ if ( node . type === "block" ) {
190+ y += spacing ;
191+ }
192+ }
193+ }
194+ flush ( ) ;
195+
196+ height = y - startY ;
205197 }
206- return result ;
198+
199+ return [
200+ {
201+ type : "block" ,
202+ x : startX ,
203+ y : startY ,
204+ width : width ,
205+ height,
206+ border,
207+ children : boxes ,
208+ } ,
209+ ] ;
207210} ;
208211
209212const measureInlines = (
0 commit comments