about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--src/layout/box.nim7
-rw-r--r--src/layout/engine.nim196
-rw-r--r--src/render/renderdocument.nim16
3 files changed, 123 insertions, 96 deletions
diff --git a/src/layout/box.nim b/src/layout/box.nim
index 913fa705..72b5c3be 100644
--- a/src/layout/box.nim
+++ b/src/layout/box.nim
@@ -30,11 +30,9 @@ type
 
   InlineBlockBoxBuilder* = ref object of BlockBoxBuilder
     iblock*: InlineBlock # iblock.bctx is equivalent to box.bctx
-    ictx*: InlineContext
 
   InlineAtom* = ref object of RootObj
-    relx*: int
-    rely*: int
+    offset*: Offset
     width*: int
     height*: int
     vertalign*: CSSVerticalAlign
@@ -58,8 +56,7 @@ type
 
   InlineRow* = ref object
     atoms*: seq[InlineAtom]
-    relx*: int
-    rely*: int
+    offset*: Offset
     width*: int
     height*: int
     lineheight*: int #line-height property
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index 7d622076..a6a4d582 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -9,6 +9,7 @@ import css/values
 import utils/twtstr
 import io/term
 
+# p is what to use for percentage values
 func cells_in(l: CSSLength, state: Viewport, d: int, p: Option[int], o: bool): int =
   return cells(l, d, state.term, p, o)
 
@@ -91,11 +92,11 @@ proc horizontalAlignRow(ictx: InlineContext, row: InlineRow, specified: CSSCompu
     # move everything
     let x = max(maxwidth, row.width) - row.width
     for atom in row.atoms:
-      atom.relx += x
+      atom.offset.x += x
   of TEXT_ALIGN_CENTER:
-    let x = max((max(maxwidth - row.relx, row.width)) div 2 - row.width div 2, 0)
+    let x = max((max(maxwidth - row.offset.x, row.width)) div 2 - row.width div 2, 0)
     for atom in row.atoms:
-      atom.relx += x
+      atom.offset.x += x
   of TEXT_ALIGN_JUSTIFY:
     if not specified.whitespacepre and not last:
       var sumwidth = 0
@@ -112,7 +113,7 @@ proc horizontalAlignRow(ictx: InlineContext, row: InlineRow, specified: CSSCompu
         let oldwidth = row.width
         row.width = 0
         for atom in row.atoms:
-          atom.relx = row.width
+          atom.offset.x = row.width
           if atom of InlineSpacing:
             let atom = InlineSpacing(atom)
             atom.width = spacingwidth
@@ -157,11 +158,11 @@ proc verticalAlignRow(ictx: InlineContext) =
       row.height - atom.height
     else:
       baseline - atom.height
-    atom.rely += diff
+    atom.offset.y += diff
 
 proc addSpacing(row: InlineRow, width, height: int, format: ComputedFormat) {.inline.} =
   let spacing = InlineSpacing(width: width, height: height, format: format)
-  spacing.relx = row.width
+  spacing.offset.x = row.width
   row.width += spacing.width
   row.atoms.add(spacing)
 
@@ -180,7 +181,7 @@ proc finishRow(ictx: InlineContext, specified: CSSComputedValues, maxwidth: int,
     ictx.rows.add(oldrow)
     ictx.height += oldrow.height
     ictx.maxwidth = max(ictx.maxwidth, oldrow.width)
-    ictx.thisrow = InlineRow(rely: oldrow.rely + oldrow.height)
+    ictx.thisrow = InlineRow(offset: Offset(y: oldrow.offset.y + oldrow.height))
 
 proc finish(ictx: InlineContext, specified: CSSComputedValues, maxwidth: int) =
   ictx.finishRow(specified, maxwidth)
@@ -203,7 +204,7 @@ proc addAtom(ictx: InlineContext, atom: InlineAtom, maxwidth: int, specified: CS
     if shift > 0:
       ictx.thisrow.addSpacing(shift, ictx.cellheight, ictx.format)
 
-    atom.relx += ictx.thisrow.width
+    atom.offset.x += ictx.thisrow.width
     ictx.thisrow.lineheight = max(ictx.thisrow.lineheight, computeLineHeight(ictx.viewport, specified))
     ictx.thisrow.width += atom.width
     ictx.thisrow.height = max(ictx.thisrow.height, atom.height)
@@ -277,69 +278,82 @@ proc renderText*(ictx: InlineContext, str: string, maxwidth: int, specified: CSS
 
   state.addWord()
 
-proc computedDimensions(bctx: BlockContext, width: int, height: Option[int]) =
-  let pwidth = bctx.specified{"width"}
+type PreferredDimensions = object
+  compwidth: int
+  compheight: Option[int]
+  margin_left: int
+  margin_right: int
+  padding_left: int
+  padding_right: int
+  padding_top: int
+  padding_bottom: int
+
+proc preferredDimensions(computed: CSSComputedValues, viewport: Viewport, width: int, height: Option[int]): PreferredDimensions =
+  let pwidth = computed{"width"}
   if pwidth.auto:
-    bctx.compwidth = width
+    result.compwidth = width
   else:
-    bctx.compwidth = pwidth.px(bctx.viewport, width)
+    result.compwidth = pwidth.px(viewport, width)
 
-  bctx.margin_left = bctx.specified{"margin-left"}.px(bctx.viewport, width)
-  bctx.margin_right = bctx.specified{"margin-right"}.px(bctx.viewport, width)
+  result.margin_left = computed{"margin-left"}.px(viewport, width)
+  result.margin_right = computed{"margin-right"}.px(viewport, width)
 
-  bctx.padding_top = bctx.specified{"padding-top"}.px(bctx.viewport, width)
-  bctx.padding_bottom = bctx.specified{"padding-bottom"}.px(bctx.viewport, width)
-  bctx.padding_left = bctx.specified{"padding-left"}.px(bctx.viewport, width)
-  bctx.padding_right = bctx.specified{"padding-right"}.px(bctx.viewport, width)
+  result.padding_top = computed{"padding-top"}.px(viewport, width)
+  result.padding_bottom = computed{"padding-bottom"}.px(viewport, width)
+  result.padding_left = computed{"padding-left"}.px(viewport, width)
+  result.padding_right = computed{"padding-right"}.px(viewport, width)
 
-  if bctx.compwidth >= width:
-    bctx.compwidth -= bctx.margin_left
-    bctx.compwidth -= bctx.margin_right
+  if result.compwidth >= width:
+    result.compwidth -= result.margin_left
+    result.compwidth -= result.margin_right
 
-    bctx.compwidth -= bctx.padding_left
-    bctx.compwidth -= bctx.padding_right
+    result.compwidth -= result.padding_left
+    result.compwidth -= result.padding_right
 
-  let pheight = bctx.specified{"height"}
+  let pheight = computed{"height"}
   if not pheight.auto:
     #bctx.compheight = pheight.cells_h(bctx.viewport, height).some
     if pheight.unit != UNIT_PERC:
-      bctx.compheight = pheight.px(bctx.viewport).some
+      result.compheight = pheight.px(viewport).some
     elif height.issome:
-      bctx.compheight = pheight.px(bctx.viewport, height.get).some
+      result.compheight = pheight.px(viewport, height.get).some
+
+proc setPreferredDimensions(bctx: BlockContext, width: int, height: Option[int]) =
+  let preferred = preferredDimensions(bctx.specified, bctx.viewport, width, height)
+  bctx.compwidth = preferred.compwidth
+  bctx.compheight = preferred.compheight
+  bctx.padding_top = preferred.padding_top
+  bctx.padding_bottom = preferred.padding_bottom
+  bctx.padding_left = preferred.padding_left
+  bctx.padding_right = preferred.padding_right
+  bctx.margin_left = preferred.margin_left
+  bctx.margin_right = preferred.margin_right
 
 proc newBlockContext_common(parent: BlockContext, box: BoxBuilder): BlockContext {.inline.} =
   new(result)
 
   result.viewport = parent.viewport
   result.specified = box.specified
-  result.computedDimensions(parent.compwidth, parent.compheight)
+  result.setPreferredDimensions(parent.compwidth, parent.compheight)
 
 proc newBlockContext(parent: BlockContext, box: BlockBoxBuilder): BlockContext =
   result = newBlockContext_common(parent, box)
   result.shrink = result.specified{"width"}.auto and parent.shrink
 
-proc newInlineBlockContext(parent: BlockContext, box: InlineBlockBoxBuilder): BlockContext =
-  result = newBlockContext_common(parent, box)
+proc newInlineBlockContext(parent: BlockContext, builder: InlineBlockBoxBuilder): BlockContext =
+  result = newBlockContext_common(parent, builder.children[0])
   result.shrink = result.specified{"width"}.auto
 
-proc newInlineBlock(parent: BlockContext, box: InlineBlockBoxBuilder): InlineBlock =
+proc newInlineBlock(parent: BlockContext, builder: InlineBlockBoxBuilder): InlineBlock =
   new(result)
-  result.bctx = parent.newInlineBlockContext(box)
+  result.bctx = parent.newInlineBlockContext(builder)
 
-# Anonymous block box.
-proc newBlockContext(parent: BlockContext): BlockContext =
+proc newBlockContext(viewport: Viewport, box: BlockBoxBuilder): BlockContext =
   new(result)
-  result.specified = parent.specified.inheritProperties()
-  result.viewport = parent.viewport
-  result.computedDimensions(parent.compwidth, parent.compheight)
-  result.shrink = result.specified{"width"}.auto and parent.shrink
-
-# Anonymous block box (root).
-proc newBlockContext(viewport: Viewport): BlockContext =
-  new(result)
-  result.specified = rootProperties()
   result.viewport = viewport
-  result.computedDimensions(viewport.term.width_px, none(int))
+  result.specified = box.specified
+  result.setPreferredDimensions(viewport.term.width_px, none(int))
+  result.shrink = result.specified{"width"}.auto
 
 proc newInlineContext(bctx: BlockContext): InlineContext =
   new(result)
@@ -441,32 +455,44 @@ proc positionInlines(bctx: BlockContext, selfcontained: bool) =
     bctx.width = bctx.compwidth
 
 proc buildBlock(box: BlockBoxBuilder, parent: BlockContext, selfcontained = false): BlockContext
+proc buildInlines(bctx: BlockContext, inlines: seq[BoxBuilder])
+proc buildBlocks(bctx: BlockContext, blocks: seq[BoxBuilder], node: Node)
 
-#TODO
 proc buildInlineBlock(builder: InlineBlockBoxBuilder, parent: InlineContext, parentblock: BlockContext): InlineBlock =
-  return
+  assert builder.children.len == 1 and builder.children[0] of BlockBoxBuilder
+  result = parentblock.newInlineBlock(builder)
 
-proc buildInlineBlock(bctx: BlockContext, box: InlineBlockBoxBuilder) =
-  assert box.children.len == 1 and box.children[0].specified{"display"} == DISPLAY_BLOCK
-  #TODO
-  
-  #let nestedblock = BlockBoxBuilder(box.children[0])
-  #nestedblock.bctx = bctx.newInlineBlockContext(box)
-  #nestedblock.buildBlock(bctx)
+  let blockbuilder = BlockBoxBuilder(builder.children[0])
+  if blockbuilder.inlinelayout:
+    # Builder only contains inline boxes.
+    result.bctx.buildInlines(blockbuilder.children)
+    result.bctx.positionInlines(false)
+  else:
+    # Builder only contains block boxes.
+    result.bctx.buildBlocks(blockbuilder.children, blockbuilder.node)
+    result.bctx.positionBlocks(false)
 
-  #let box = InlineBlockBoxBuilder(box)
-  #box.iblock = InlineBlock(bctx: nestedblock.bctx)
-  #box.bctx = box.iblock.bctx
+  let preferred = preferredDimensions(builder.specified, parentblock.viewport, parentblock.compwidth, parentblock.compheight)
+  let pwidth = builder.specified{"width"}
+  if pwidth.auto:
+    # Half-baked shrink-to-fit
+    result.bctx.width = min(max(result.bctx.width, parent.maxwidth), preferred.compwidth)
+  else:
+    result.bctx.width = preferred.compwidth
 
-  #buildBlock(box, bctx, true)
+  # Set inline block dimensions,
+  result.width = result.bctx.width
+  result.height = result.bctx.height
 
-  #let iblock = box.iblock
-  #iblock.relx = iblock.bctx.relx + iblock.bctx.margin_left
-  #iblock.width = iblock.bctx.width + iblock.bctx.margin_left + iblock.bctx.margin_right
-  #iblock.height = iblock.bctx.height
+  # Plus margins, for the final result.
+  result.width += result.bctx.margin_left
+  result.height += result.bctx.margin_top
+  result.width += result.bctx.margin_right
+  result.height += result.bctx.margin_bottom
 
-  #box.ictx.addAtom(box.iblock, bctx.compwidth, box.specified)
-  #box.ictx.whitespacenum = 0
+  # Set offset here because positionInlines will reset it.
+  result.bctx.offset.x = result.bctx.margin_left
+  result.bctx.offset.y = result.bctx.margin_top
 
 proc buildInline(bctx: BlockContext, box: InlineBoxBuilder) =
   assert box.ictx != nil
@@ -492,11 +518,9 @@ proc buildInline(bctx: BlockContext, box: InlineBoxBuilder) =
       child.ictx = box.ictx
       bctx.buildInline(child)
     of DISPLAY_INLINE_BLOCK:
-      #TODO
-      #let child = InlineBlockBoxBuilder(child)
-      #child.ictx = box.ictx
-      #bctx.buildInlineBlock(child)
-      discard
+      let child = InlineBlockBoxBuilder(child)
+      let iblock = child.buildInlineBlock(box.ictx, bctx)
+      box.ictx.addAtom(iblock, bctx.compwidth, child.specified)
     else:
       assert false, "child.t is " & $child.specified{"display"}
 
@@ -525,8 +549,9 @@ proc buildInlines(bctx: BlockContext, inlines: seq[BoxBuilder]) =
         bctx.buildInline(child)
       of DISPLAY_INLINE_BLOCK:
         let child = InlineBlockBoxBuilder(child)
-        child.ictx = ictx
-        bctx.buildInlineBlock(child)
+        let iblock = child.buildInlineBlock(ictx, bctx)
+        ictx.addAtom(iblock, bctx.compwidth, child.specified)
+        ictx.whitespacenum = 0
       else:
         assert false, "child.t is " & $child.specified{"display"}
     ictx.finish(bctx.specified, bctx.compwidth)
@@ -547,7 +572,7 @@ proc buildBlocks(bctx: BlockContext, blocks: seq[BoxBuilder], node: Node) =
 # Build a block box inside another block box, based on a builder.
 proc buildBlock(box: BlockBoxBuilder, parent: BlockContext, selfcontained = false): BlockContext =
   assert parent != nil
-  result = parent.newBlockContext()
+  result = parent.newBlockContext(box)
   if box.inlinelayout:
     # Builder only contains inline boxes.
     result.buildInlines(box.children)
@@ -559,7 +584,7 @@ proc buildBlock(box: BlockBoxBuilder, parent: BlockContext, selfcontained = fals
 
 # Build a block box whose parent is the viewport, based on a builder.
 proc buildBlock(box: BlockBoxBuilder, viewport: Viewport, selfcontained = false): BlockContext =
-  result = viewport.newBlockContext()
+  result = viewport.newBlockContext(box)
   if box.inlinelayout:
     # Builder only contains inline boxes.
     result.buildInlines(box.children)
@@ -571,11 +596,6 @@ proc buildBlock(box: BlockBoxBuilder, viewport: Viewport, selfcontained = false)
 
 # Generation phase
 
-proc getInlineBlockBox(specified: CSSComputedValues): InlineBlockBoxBuilder =
-  assert specified{"display"} == DISPLAY_INLINE_BLOCK
-  new(result)
-  result.specified = specified
-
 # Returns a block box, disregarding the specified value of display
 proc getBlockBox(specified: CSSComputedValues): BlockBoxBuilder =
   new(result)
@@ -585,6 +605,12 @@ proc getBlockBox(specified: CSSComputedValues): BlockBoxBuilder =
   #TODO figure something out here
   result.specified[PROPERTY_DISPLAY] = CSSComputedValue(t: PROPERTY_DISPLAY, v: VALUE_DISPLAY, display: DISPLAY_BLOCK)
 
+proc getInlineBlockBox(specified: CSSComputedValues): InlineBlockBoxBuilder =
+  new(result)
+  result.specified = specified.copyProperties()
+  #TODO ditto
+  result.specified[PROPERTY_DISPLAY] = CSSComputedValue(t: PROPERTY_DISPLAY, v: VALUE_DISPLAY, display: DISPLAY_INLINE_BLOCK)
+
 proc getTextBox(box: BoxBuilder): InlineBoxBuilder =
   new(result)
   result.inlinelayout = true
@@ -651,7 +677,7 @@ template generate_from_elem(child: Element) =
     box.generateInlineBoxes(child, blockgroup, viewport)
   of DISPLAY_INLINE_BLOCK:
     flush_ibox
-    let childbox = getInlineBlockBox(child.css)
+    let childbox = getInlineBlockBox(box.specified)
     let childblock = child.generateBlockBox(viewport)
     childbox.children.add(childblock)
     blockgroup.add(childbox)
@@ -680,7 +706,7 @@ proc generateBlockPseudoBox(specified: CSSComputedValues, viewport: Viewport): B
 
   return box
 
-template generate_pseudo(specified: CSSComputedValues) =
+proc generatePseudo(box: BlockBoxBuilder, elem: Element, blockgroup: var seq[BoxBuilder], viewport: Viewport, ibox: var InlineBoxBuilder, specified: CSSComputedValues) =
   case specified{"display"}
   of DISPLAY_BLOCK, DISPLAY_LIST_ITEM:
     flush_block_group3(elem.css)
@@ -691,7 +717,7 @@ template generate_pseudo(specified: CSSComputedValues) =
     box.generateInlinePseudoBox(specified, blockgroup, viewport)
   of DISPLAY_INLINE_BLOCK:
     flush_ibox
-    let childbox = getInlineBlockBox(specified)
+    let childbox = getInlineBlockBox(box.specified)
     let childblock = generateBlockPseudoBox(specified, viewport)
     childbox.children.add(childblock)
     blockgroup.add(childbox)
@@ -700,7 +726,7 @@ template generate_pseudo(specified: CSSComputedValues) =
 
 proc generateBoxBefore(box: BlockBoxBuilder, elem: Element, blockgroup: var seq[BoxBuilder], viewport: Viewport, ibox: var InlineBoxBuilder) =
   if elem.pseudo[PSEUDO_BEFORE] != nil:
-    generate_pseudo(elem.pseudo[PSEUDO_BEFORE])
+    box.generatePseudo(elem, blockgroup, viewport, ibox, elem.pseudo[PSEUDO_BEFORE])
 
   #TODO
   #if box.specified{"display"} == DISPLAY_LIST_ITEM:
@@ -722,7 +748,7 @@ proc generateBoxBefore(box: BlockBoxBuilder, elem: Element, blockgroup: var seq[
 
 proc generateBoxAfter(box: BlockBoxBuilder, elem: Element, blockgroup: var seq[BoxBuilder], viewport: Viewport, ibox: var InlineBoxBuilder) =
   if elem.pseudo[PSEUDO_AFTER] != nil:
-    generate_pseudo(elem.pseudo[PSEUDO_AFTER])
+    box.generatePseudo(elem, blockgroup, viewport, ibox, elem.pseudo[PSEUDO_AFTER])
 
 proc generateInlineBoxes(box: BlockBoxBuilder, elem: Element, blockgroup: var seq[BoxBuilder], viewport: Viewport) =
   var ibox: InlineBoxBuilder = nil
@@ -771,7 +797,13 @@ proc generateBlockBox(elem: Element, viewport: Viewport): BlockBoxBuilder =
   generateBoxAfter(box, elem, blockgroup, viewport, ibox)
 
   flush_ibox
-  flush_block_group3(elem.css)
+  if blockgroup.len > 0:
+    # Avoid unnecessary anonymous block boxes
+    if box.children.len == 0:
+      box.children = blockgroup
+      box.inlinelayout = true
+    else:
+      flush_block_group3(elem.css)
   return box
 
 proc generateBoxBuilders(elem: Element, viewport: Viewport): BlockBoxBuilder =
diff --git a/src/render/renderdocument.nim b/src/render/renderdocument.nim
index 3dc72c38..6227b4c7 100644
--- a/src/render/renderdocument.nim
+++ b/src/render/renderdocument.nim
@@ -101,10 +101,10 @@ proc setText(lines: var FlexibleGrid, linestr: string, format: ComputedFormat, x
 proc setRowWord(lines: var FlexibleGrid, word: InlineWord, x, y: int, term: TermAttributes) =
   var r: Rune
 
-  var y = (y + word.rely) div term.ppl
+  var y = (y + word.offset.y) div term.ppl
   if y < 0: y = 0
 
-  var x = (x + word.relx) div term.ppc
+  var x = (x + word.offset.x) div term.ppc
   var i = 0
   while x < 0 and i < word.str.len:
     fastRuneAt(word.str, i, r)
@@ -114,12 +114,10 @@ proc setRowWord(lines: var FlexibleGrid, word: InlineWord, x, y: int, term: Term
   lines.setText(linestr, word.format, x, y)
 
 proc setSpacing(lines: var FlexibleGrid, spacing: InlineSpacing, x, y: int, term: TermAttributes) =
-  var r: Rune
-
-  var y = (y + spacing.rely) div term.ppl
+  var y = (y + spacing.offset.y) div term.ppl
   if y < 0: y = 0
 
-  var x = (x + spacing.relx) div term.ppc
+  var x = (x + spacing.offset.x) div term.ppc
   let width = spacing.width div term.ppc
 
   var i = 0
@@ -208,8 +206,8 @@ proc renderInlineContext(grid: var FlexibleGrid, ctx: InlineContext, x, y: int,
   let x = x + ctx.offset.x
   let y = y + ctx.offset.y
   for row in ctx.rows:
-    let x = x + row.relx
-    let y = y + row.rely
+    let x = x + row.offset.x
+    let y = y + row.offset.y
 
     let r = y div term.ppl
     while grid.len <= r:
@@ -218,7 +216,7 @@ proc renderInlineContext(grid: var FlexibleGrid, ctx: InlineContext, x, y: int,
     for atom in row.atoms:
       if atom of InlineBlock:
         let iblock = InlineBlock(atom)
-        grid.renderBlockContext(iblock.bctx, x + iblock.relx, y + iblock.rely, term)
+        grid.renderBlockContext(iblock.bctx, x + iblock.offset.x, y + iblock.offset.y, term)
       elif atom of InlineWord:
         let word = InlineWord(atom)
         grid.setRowWord(word, x, y, term)