about summary refs log tree commit diff stats
path: root/src/layout
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2022-01-29 11:04:21 +0100
committerbptato <nincsnevem662@gmail.com>2022-01-29 11:04:21 +0100
commit5c19adf246650306eaee3605b7b9fc47a6ca73fb (patch)
treea16707ce3c121b255e255980c52b8141ad1d79bf /src/layout
parenta42f0b169f5f612b3b14026618bcb26a2afeedca (diff)
downloadchawan-5c19adf246650306eaee3605b7b9fc47a6ca73fb.tar.gz
Implement text-align and <center>
Diffstat (limited to 'src/layout')
-rw-r--r--src/layout/box.nim5
-rw-r--r--src/layout/engine.nim163
2 files changed, 120 insertions, 48 deletions
diff --git a/src/layout/box.nim b/src/layout/box.nim
index aa897ae6..64fe0741 100644
--- a/src/layout/box.nim
+++ b/src/layout/box.nim
@@ -50,7 +50,6 @@ type
   InlineContext* = ref object
     relx*: int
     rely*: int
-    width*: int
     height*: int
     rows*: seq[InlineRow]
     thisrow*: InlineRow
@@ -59,6 +58,7 @@ type
     maxwidth*: int
     viewport*: Viewport
     node*: Node
+    shrink*: bool
 
   BlockContext* = ref object of InlineAtom
     inline*: InlineContext
@@ -75,7 +75,10 @@ type
     padding_right*: int
 
     compwidth*: int
+    maxwidth*: int
+    nocenter*: bool
     compheight*: Option[int]
+    shrink*: bool
     done*: bool
 
   InlineBox* = ref object of CSSBox
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index 986d6723..18939a57 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -74,52 +74,102 @@ proc newWord(state: var InlineState) =
   word.format.node = state.node
   state.word = word
 
-proc finishRow(ictx: InlineContext, specified: CSSSpecifiedValues, force = false) =
-  if ictx.thisrow.atoms.len != 0 or force:
-    let oldrow = ictx.thisrow
+proc horizontalAlignRow(ictx: InlineContext, row: InlineRow, specified: CSSSpecifiedValues, maxwidth: int, last = false) =
+  let maxwidth = if ictx.shrink:
+    ictx.maxwidth
+  else:
+    maxwidth
+  # we don't support directions for now so left = start and right = end
+  case specified{"text-align"}
+  of TEXT_ALIGN_START, TEXT_ALIGN_LEFT:
+    discard
+  of TEXT_ALIGN_END, TEXT_ALIGN_RIGHT:
+    # move everything
+    let x = max(maxwidth, row.width) - row.width
+    for atom in row.atoms:
+      atom.relx += x
+  of TEXT_ALIGN_CENTER:
+    let x = max((max(maxwidth - row.relx, row.width)) div 2 - row.width div 2, 0)
+    for atom in row.atoms:
+      atom.relx += x
+  of TEXT_ALIGN_JUSTIFY:
+    if not specified.whitespacepre and not last:
+      var sumwidth = 0
+      var spaces = 0
+      for atom in row.atoms:
+        if atom of InlineSpacing:
+          discard
+        else:
+          inc spaces
+          sumwidth += atom.width
+      dec spaces
+      if spaces > 0:
+        let spacingwidth = (ictx.maxwidth - sumwidth) div spaces
+        let oldwidth = row.width
+        row.width = 0
+        for atom in row.atoms:
+          atom.relx = row.width
+          if atom of InlineSpacing:
+            let atom = InlineSpacing(atom)
+            atom.width = spacingwidth
+          row.width += atom.width
+  else:
+    discard
 
-    var baseline = if oldrow.height < oldrow.lineheight:
-      let lines = oldrow.lineheight div ictx.cellheight
-      ceilDiv(lines, 2) * ictx.cellheight
+proc verticalAlignRow(ictx: InlineContext) =
+  let row = ictx.thisrow
+  var baseline = if row.height < row.lineheight:
+    let lines = row.lineheight div ictx.cellheight
+    int(ceil(lines / 2)) * ictx.cellheight
+  else:
+    0
+
+  # line-height is the minimum line height
+  row.height = max(row.height, row.lineheight)
+
+  for atom in row.atoms:
+    case atom.vertalign.keyword
+    of VERTICAL_ALIGN_BASELINE:
+      let len = atom.vertalign.length.px(ictx.viewport, row.lineheight)
+      baseline = max(baseline, atom.height + len)
+    of VERTICAL_ALIGN_TOP, VERTICAL_ALIGN_BOTTOM:
+      row.height = max(atom.height, row.height)
+    of VERTICAL_ALIGN_MIDDLE:
+      baseline = max(baseline, atom.height div 2)
     else:
+      baseline = max(baseline, atom.height)
+  row.height = max(baseline, row.height)
+
+  for atom in row.atoms:
+    let diff = case atom.vertalign.keyword
+    of VERTICAL_ALIGN_BASELINE:
+      let len = atom.vertalign.length.px(ictx.viewport, row.lineheight)
+      baseline - atom.height - len
+    of VERTICAL_ALIGN_MIDDLE:
+      baseline - atom.height div 2
+    of VERTICAL_ALIGN_TOP:
       0
+    of VERTICAL_ALIGN_BOTTOM:
+      row.height - atom.height
+    else:
+      baseline - atom.height
+    atom.rely += diff
 
-    # line-height is the minimum line height
-    oldrow.height = max(oldrow.height, oldrow.lineheight)
-
-    for atom in oldrow.atoms:
-      case atom.vertalign.keyword
-      of VERTICAL_ALIGN_BASELINE:
-        let len = atom.vertalign.length.px(ictx.viewport, oldrow.lineheight)
-        baseline = max(baseline, atom.height + len)
-      of VERTICAL_ALIGN_TOP, VERTICAL_ALIGN_BOTTOM:
-        oldrow.height = max(atom.height, oldrow.height)
-      of VERTICAL_ALIGN_MIDDLE:
-        baseline = max(baseline, atom.height div 2)
-      else:
-        baseline = max(baseline, atom.height)
-    oldrow.height = max(baseline, oldrow.height)
-
-    for atom in oldrow.atoms:
-      let diff = case atom.vertalign.keyword
-      of VERTICAL_ALIGN_BASELINE:
-        let len = atom.vertalign.length.px(ictx.viewport, oldrow.lineheight)
-        baseline - atom.height - len
-      of VERTICAL_ALIGN_MIDDLE:
-        baseline - atom.height div 2
-      of VERTICAL_ALIGN_TOP:
-        0
-      of VERTICAL_ALIGN_BOTTOM:
-        oldrow.height - atom.height
-      else:
-        baseline - atom.height
-      atom.rely += diff
+proc finishRow(ictx: InlineContext, specified: CSSSpecifiedValues, maxwidth: int, force = false) =
+  if ictx.thisrow.atoms.len != 0 or force:
+    ictx.verticalAlignRow()
 
+    let oldrow = ictx.thisrow
     ictx.rows.add(oldrow)
     ictx.height += oldrow.height
-    ictx.width = max(ictx.width, oldrow.width)
+    ictx.maxwidth = max(ictx.maxwidth, oldrow.width)
     ictx.thisrow = InlineRow(rely: oldrow.rely + oldrow.height)
 
+proc finish(ictx: InlineContext, specified: CSSSpecifiedValues, maxwidth: int) =
+  ictx.finishRow(specified, maxwidth)
+  for row in ictx.rows:
+    ictx.horizontalAlignRow(row, specified, maxwidth, row == ictx.rows[^1])
+
 proc addSpacing(row: InlineRow, width, height: int, format: ComputedFormat) {.inline.} =
   let spacing = InlineSpacing(width: width, height: height, format: format)
   spacing.relx = row.width
@@ -132,7 +182,7 @@ proc addAtom(ictx: InlineContext, atom: InlineAtom, maxwidth: int, specified: CS
   # Line wrapping
   if specified{"white-space"} notin {WHITESPACE_NOWRAP, WHITESPACE_PRE}:
     if ictx.thisrow.width + atom.width + shift > maxwidth:
-      ictx.finishRow(specified)
+      ictx.finishRow(specified, maxwidth, false)
       # Recompute on newline
       shift = ictx.computeShift(specified)
       ictx.whitespace = false
@@ -161,9 +211,9 @@ proc addWord(state: var InlineState) =
     state.newWord()
 
 # Start a new line, even if the previous one is empty
-proc flushLine(ictx: InlineContext, specified: CSSSpecifiedValues) =
+proc flushLine(ictx: InlineContext, specified: CSSSpecifiedValues, maxwidth: int) =
   ictx.thisrow.lineheight = computeLineHeight(ictx.viewport, specified)
-  ictx.finishRow(specified, true)
+  ictx.finishRow(specified, maxwidth, true)
 
 proc checkWrap(state: var InlineState, r: Rune) =
   if state.specified{"white-space"} in {WHITESPACE_NOWRAP, WHITESPACE_PRE}:
@@ -173,11 +223,11 @@ proc checkWrap(state: var InlineState, r: Rune) =
   of WORD_BREAK_BREAK_ALL:
     if state.ictx.thisrow.width + state.word.width + shift + r.width() * state.ictx.cellwidth > state.maxwidth:
       state.addWord()
-      state.ictx.finishRow(state.specified)
+      state.ictx.finishRow(state.specified, state.maxwidth, false)
       state.ictx.whitespace = false
   of WORD_BREAK_KEEP_ALL:
     if state.ictx.thisrow.width + state.word.width + shift + r.width() * state.ictx.cellwidth > state.maxwidth:
-      state.ictx.finishRow(state.specified)
+      state.ictx.finishRow(state.specified, state.maxwidth, false)
       state.ictx.whitespace = false
   else: discard
 
@@ -188,7 +238,7 @@ proc processWhitespace(state: var InlineState, c: char) =
     state.ictx.whitespace = true
   of WHITESPACE_PRE_LINE, WHITESPACE_PRE, WHITESPACE_PRE_WRAP:
     if c == '\n':
-      state.ictx.flushLine(state.specified)
+      state.ictx.flushLine(state.specified, state.maxwidth)
     else:
       state.ictx.whitespace = true
 
@@ -255,9 +305,11 @@ proc newBlockContext_common(parent: BlockContext, box: CSSBox): BlockContext {.i
 
 proc newBlockContext(parent: BlockContext, box: BlockBox): BlockContext =
   result = newBlockContext_common(parent, box)
+  result.shrink = result.specified{"width"}.auto and parent.shrink
 
 proc newInlineBlockContext(parent: BlockContext, box: InlineBlockBox): BlockContext =
   result = newBlockContext_common(parent, box)
+  result.shrink = result.specified{"width"}.auto
 
 # Anonymous block box.
 proc newBlockContext(parent: BlockContext): BlockContext =
@@ -265,6 +317,7 @@ proc newBlockContext(parent: BlockContext): BlockContext =
   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 =
@@ -277,6 +330,7 @@ proc newInlineContext(bctx: BlockContext): InlineContext =
   new(result)
   result.thisrow = InlineRow()
   result.viewport = bctx.viewport
+  result.shrink = bctx.shrink
   bctx.inline = result
 
 # Blocks' positions do not have to be arranged if alignBlocks is called with
@@ -290,10 +344,14 @@ proc arrangeBlocks(bctx: BlockContext, selfcontained: bool) =
   bctx.height += bctx.padding_top
 
   x += bctx.padding_left
+  if bctx.specified{"text-align"} == TEXT_ALIGN_MOZ_CENTER:
+    x += bctx.compwidth div 2
 
   template apply_child(child: BlockContext) =
     child.rely = y
     child.relx = x + child.margin_left
+    if bctx.specified{"text-align"} == TEXT_ALIGN_MOZ_CENTER:
+      child.relx -= child.width div 2
     y += child.height
     bctx.height += child.height
     bctx.width = max(bctx.width, child.width)
@@ -381,7 +439,7 @@ proc alignInlineBlock(bctx: BlockContext, box: InlineBlockBox) =
 proc alignInline(bctx: BlockContext, box: InlineBox) =
   assert box.ictx != nil
   if box.newline:
-    box.ictx.flushLine(bctx.specified)
+    box.ictx.flushLine(bctx.specified, bctx.compwidth)
 
   let margin_left = box.specified{"margin-left"}.px(bctx.viewport, bctx.compwidth)
   box.ictx.thisrow.width += margin_left
@@ -430,12 +488,12 @@ proc alignInlines(bctx: BlockContext, inlines: seq[CSSBox]) =
         bctx.alignInlineBlock(child)
       else:
         assert false, "child.t is " & $child.t
-    ictx.finishRow(bctx.specified)
+    ictx.finish(bctx.specified, bctx.compwidth)
 
   bctx.height += ictx.height
   if bctx.compheight.issome:
     bctx.height = bctx.compheight.get
-  bctx.width = max(bctx.width, ictx.width)
+  bctx.width = max(bctx.width, ictx.maxwidth)
 
 template flush_group() =
   if blockgroup.len > 0:
@@ -538,13 +596,24 @@ proc generateBox(elem: Element, viewport: Viewport, bctx: BlockContext = nil): C
   if viewport.map[elem.uid] != nil:
     let box = viewport.map[elem.uid]
     var bctx = bctx
-    if box.specified{"display"} in {DISPLAY_BLOCK, DISPLAY_LIST_ITEM, DISPLAY_INLINE_BLOCK}:
+    case box.specified{"display"}
+    of DISPLAY_BLOCK, DISPLAY_LIST_ITEM:
       let box = BlockBox(box)
       if bctx == nil:
         box.bctx = viewport.newBlockContext()
       else:
         box.bctx = bctx.newBlockContext(box)
       bctx = box.bctx
+    of DISPLAY_INLINE_BLOCK:
+      let box = InlineBlockBox(box)
+      if bctx == nil:
+        assert false
+        box.bctx = viewport.newBlockContext()
+      else:
+        box.bctx = bctx.newInlineBlockContext(box)
+      bctx = box.bctx
+    else:
+      discard
 
     var i = 0
     while i < box.children.len: