about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/css/values.nim73
-rw-r--r--src/layout/box.nim4
-rw-r--r--src/layout/engine.nim163
-rw-r--r--src/render/renderdocument.nim13
4 files changed, 188 insertions, 65 deletions
diff --git a/src/css/values.nim b/src/css/values.nim
index fb1d6d63..926a9392 100644
--- a/src/css/values.nim
+++ b/src/css/values.nim
@@ -28,12 +28,13 @@ type
     PROPERTY_TEXT_DECORATION, PROPERTY_WORD_BREAK, PROPERTY_WIDTH,
     PROPERTY_HEIGHT, PROPERTY_LIST_STYLE_TYPE, PROPERTY_PADDING,
     PROPERTY_PADDING_TOP, PROPERTY_PADDING_LEFT, PROPERTY_PADDING_RIGHT,
-    PROPERTY_PADDING_BOTTOM, PROPERTY_WORD_SPACING
+    PROPERTY_PADDING_BOTTOM, PROPERTY_WORD_SPACING, PROPERTY_VERTICAL_ALIGN,
+    PROPERTY_LINE_HEIGHT
 
   CSSValueType* = enum
     VALUE_NONE, VALUE_LENGTH, VALUE_COLOR, VALUE_CONTENT, VALUE_DISPLAY,
     VALUE_FONT_STYLE, VALUE_WHITE_SPACE, VALUE_INTEGER, VALUE_TEXT_DECORATION,
-    VALUE_WORD_BREAK, VALUE_LIST_STYLE_TYPE
+    VALUE_WORD_BREAK, VALUE_LIST_STYLE_TYPE, VALUE_VERTICAL_ALIGN
 
   CSSGlobalValueType* = enum
     VALUE_NOGLOBAL, VALUE_INITIAL, VALUE_INHERIT, VALUE_REVERT, VALUE_UNSET
@@ -69,6 +70,11 @@ type
     LIST_STYLE_TYPE_LOWER_ROMAN, LIST_STYLE_TYPE_UPPER_ROMAN,
     LIST_STYLE_TYPE_JAPANESE_INFORMAL
 
+  CSSVerticalAlign2* = enum
+    VERTICAL_ALIGN_BASELINE, VERTICAL_ALIGN_SUB, VERTICAL_ALIGN_SUPER,
+    VERTICAL_ALIGN_TEXT_TOP, VERTICAL_ALIGN_TEXT_BOTTOM, VERTICAL_ALIGN_MIDDLE,
+    VERTICAL_ALIGN_TOP, VERTICAL_ALIGN_BOTTOM
+
 type
   CSSLength* = object
     num*: float64
@@ -78,6 +84,10 @@ type
   CSSColor* = object
     rgba: RGBAColor
     termcolor: int
+
+  CSSVerticalAlign* = object
+    length*: CSSLength
+    keyword*: CSSVerticalAlign2
   
   CSSSpecifiedValue* = ref object
     t*: CSSPropertyType
@@ -102,6 +112,8 @@ type
       wordbreak*: CSSWordBreak
     of VALUE_LIST_STYLE_TYPE:
       liststyletype*: CSSListStyleType
+    of VALUE_VERTICAL_ALIGN:
+      verticalalign*: CSSVerticalAlign
     of VALUE_NONE: discard
 
   CSSSpecifiedValues* = ref array[CSSPropertyType, CSSSpecifiedValue]
@@ -132,6 +144,8 @@ const PropertyNames = {
   "padding-left": PROPERTY_PADDING_LEFT,
   "padding-right": PROPERTY_PADDING_RIGHT,
   "word-spacing": PROPERTY_WORD_SPACING,
+  "vertical-align": PROPERTY_VERTICAL_ALIGN,
+  "line-height": PROPERTY_LINE_HEIGHT,
 }.toTable()
 
 const ValueTypes = [
@@ -159,12 +173,14 @@ const ValueTypes = [
   PROPERTY_PADDING_RIGHT: VALUE_LENGTH,
   PROPERTY_PADDING_BOTTOM: VALUE_LENGTH,
   PROPERTY_WORD_SPACING: VALUE_LENGTH,
+  PROPERTY_VERTICAL_ALIGN: VALUE_VERTICAL_ALIGN,
+  PROPERTY_LINE_HEIGHT: VALUE_LENGTH,
 ]
 
 const InheritedProperties = {
   PROPERTY_COLOR, PROPERTY_FONT_STYLE, PROPERTY_WHITE_SPACE,
   PROPERTY_FONT_WEIGHT, PROPERTY_TEXT_DECORATION, PROPERTY_WORD_BREAK,
-  PROPERTY_LIST_STYLE_TYPE, PROPERTY_WORD_SPACING
+  PROPERTY_LIST_STYLE_TYPE, PROPERTY_WORD_SPACING, PROPERTY_LINE_HEIGHT
 }
 
 func getPropInheritedArray(): array[CSSPropertyType, bool] =
@@ -520,8 +536,10 @@ func cssColor(d: CSSDeclaration): CSSColor =
 func cellColor*(color: CSSColor): CellColor =
   return CellColor(rgb: true, rgbcolor: RGBColor(color.rgba))
 
+func isToken(d: CSSDeclaration): bool {.inline.} = d.value.len > 0 and d.value[0] of CSSToken
+
 func cssLength(d: CSSDeclaration): CSSLength =
-  if d.value.len > 0 and d.value[0] of CSSToken:
+  if isToken(d):
     let tok = CSSToken(d.value[0])
     case tok.tokenType
     of CSS_PERCENTAGE_TOKEN:
@@ -535,7 +553,7 @@ func cssLength(d: CSSDeclaration): CSSLength =
   raise newException(CSSValueError, "Invalid length")
 
 func cssWordSpacing(d: CSSDeclaration): CSSLength =
-  if d.value.len > 0 and d.value[0] of CSSToken:
+  if isToken(d):
     let tok = CSSToken(d.value[0])
     case tok.tokenType
     of CSS_DIMENSION_TOKEN:
@@ -546,8 +564,6 @@ func cssWordSpacing(d: CSSDeclaration): CSSLength =
     else: discard
   raise newException(CSSValueError, "Invalid word spacing")
 
-func isToken(d: CSSDeclaration): bool = d.value.len > 0 and d.value[0] of CSSToken
-
 func getToken(d: CSSDeclaration): CSSToken = (CSSToken)d.value[0]
 
 func cssGlobal*(d: CSSDeclaration): CSSGlobalValueType =
@@ -667,12 +683,49 @@ func cssListStyleType(d: CSSDeclaration): CSSListStyleType =
       of "japanese-informal": return LIST_STYLE_TYPE_JAPANESE_INFORMAL
   raise newException(CSSValueError, "Invalid list style")
 
+func cssVerticalAlign(d: CSSDeclaration): CSSVerticalAlign =
+  if isToken(d):
+    let tok = getToken(d)
+    if tok.tokenType == CSS_IDENT_TOKEN:
+      case $tok.value
+      of "baseline": result.keyword = VERTICAL_ALIGN_BASELINE
+      of "sub": result.keyword = VERTICAL_ALIGN_SUB
+      of "super": result.keyword = VERTICAL_ALIGN_SUPER
+      of "text-top": result.keyword = VERTICAL_ALIGN_TEXT_BOTTOM
+      of "middle": result.keyword = VERTICAL_ALIGN_MIDDLE
+      of "top": result.keyword = VERTICAL_ALIGN_TOP
+      of "bottom": result.keyword = VERTICAL_ALIGN_BOTTOM
+      else:
+        raise newException(CSSValueError, "Invalid vertical align")
+      return result
+    else:
+      result.keyword = VERTICAL_ALIGN_BASELINE
+      result.length = cssLength(d)
+      return result
+  raise newException(CSSValueError, "Invalid vertical align")
+
+func cssLineHeight(d: CSSDeclaration): CSSLength =
+  if isToken(d):
+    let tok = CSSToken(d.value[0])
+    case tok.tokenType
+    of CSS_NUMBER_TOKEN:
+      return cssLength(tok.nvalue * 100, "%")
+    of CSS_IDENT_TOKEN:
+      if $tok.value == "normal":
+        return CSSLength(auto: true)
+    else:
+      return cssLength(d)
+  raise newException(CSSValueError, "Invalid line height")
+
 proc getValueFromDecl(val: CSSSpecifiedValue, d: CSSDeclaration, vtype: CSSValueType, ptype: CSSPropertyType) =
   case vtype
   of VALUE_COLOR: val.color = cssColor(d)
   of VALUE_LENGTH:
-    if ptype == PROPERTY_WORD_SPACING:
+    case ptype
+    of PROPERTY_WORD_SPACING:
       val.length = cssWordSpacing(d)
+    of PROPERTY_LINE_HEIGHT:
+      val.length = cssLineHeight(d)
     else:
       val.length = cssLength(d)
   of VALUE_FONT_STYLE: val.fontstyle = cssFontStyle(d)
@@ -685,6 +738,7 @@ proc getValueFromDecl(val: CSSSpecifiedValue, d: CSSDeclaration, vtype: CSSValue
   of VALUE_TEXT_DECORATION: val.textdecoration = cssTextDecoration(d)
   of VALUE_WORD_BREAK: val.wordbreak = cssWordBreak(d)
   of VALUE_LIST_STYLE_TYPE: val.liststyletype = cssListStyleType(d)
+  of VALUE_VERTICAL_ALIGN: val.verticalalign = cssVerticalAlign(d)
   of VALUE_NONE: discard
 
 func getInitialColor(t: CSSPropertyType): CSSColor =
@@ -696,7 +750,7 @@ func getInitialColor(t: CSSPropertyType): CSSColor =
 
 func getInitialLength(t: CSSPropertyType): CSSLength =
   case t
-  of PROPERTY_WIDTH, PROPERTY_HEIGHT, PROPERTY_WORD_SPACING:
+  of PROPERTY_WIDTH, PROPERTY_HEIGHT, PROPERTY_WORD_SPACING, PROPERTY_LINE_HEIGHT:
     return CSSLength(auto: true)
   else:
     return CSSLength()
@@ -756,6 +810,7 @@ func equals*(a, b: CSSSpecifiedValue): bool =
   of VALUE_TEXT_DECORATION: return a.textdecoration == b.textdecoration
   of VALUE_WORD_BREAK: return a.wordbreak == b.wordbreak
   of VALUE_LIST_STYLE_TYPE: return a.liststyletype == b.liststyletype
+  of VALUE_VERTICAL_ALIGN: return a.verticalalign == b.verticalalign
   of VALUE_NONE: return true
   return false
 
diff --git a/src/layout/box.nim b/src/layout/box.nim
index 44096c9a..aa897ae6 100644
--- a/src/layout/box.nim
+++ b/src/layout/box.nim
@@ -20,8 +20,10 @@ type
 
   InlineAtom* = ref object of RootObj
     relx*: int
+    rely*: int
     width*: int
     height*: int
+    vertalign*: CSSVerticalAlign
 
   ComputedFormat* = ref object
     fontstyle*: CSSFontStyle
@@ -43,6 +45,7 @@ type
     rely*: int
     width*: int
     height*: int
+    lineheight*: int #line-height property
 
   InlineContext* = ref object
     relx*: int
@@ -62,7 +65,6 @@ type
     nested*: seq[BlockContext]
     specified*: CSSSpecifiedValues
     viewport*: Viewport
-    rely*: int
     margin_top*: int
     margin_bottom*: int
     margin_left*: int
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index b5876b5c..986d6723 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -1,5 +1,6 @@
-import unicode
+import math
 import options
+import unicode
 
 import layout/box
 import html/tags
@@ -34,11 +35,17 @@ type InlineState = object
 func whitespacepre(specified: CSSSpecifiedValues): bool {.inline.} =
   specified{"white-space"} in {WHITESPACE_PRE, WHITESPACE_PRE_WRAP}
 
+func cellwidth(viewport: Viewport): int {.inline.} =
+  viewport.term.ppc
+
 func cellwidth(ictx: InlineContext): int {.inline.} =
-  ictx.viewport.term.ppc
+  ictx.viewport.cellwidth
+
+func cellheight(viewport: Viewport): int {.inline.} =
+  viewport.term.ppl
 
 func cellheight(ictx: InlineContext): int {.inline.} =
-  ictx.viewport.term.ppl
+  ictx.viewport.cellheight
 
 # Whitespace between words
 func computeShift(ictx: InlineContext, specified: CSSSpecifiedValues): int =
@@ -51,6 +58,11 @@ func computeShift(ictx: InlineContext, specified: CSSSpecifiedValues): int =
       return spacing.px(ictx.viewport)
   return 0
 
+func computeLineHeight(viewport: Viewport, specified: CSSSpecifiedValues): int =
+  if specified{"line-height"}.auto:
+    return viewport.cellheight
+  return specified{"line-height"}.px(viewport, viewport.cellheight)
+
 proc newWord(state: var InlineState) =
   let word = InlineWord()
   word.format = ComputedFormat()
@@ -62,9 +74,47 @@ proc newWord(state: var InlineState) =
   word.format.node = state.node
   state.word = word
 
-proc finishRow(ictx: InlineContext) =
-  if ictx.thisrow.height != 0:
+proc finishRow(ictx: InlineContext, specified: CSSSpecifiedValues, force = false) =
+  if ictx.thisrow.atoms.len != 0 or force:
     let oldrow = ictx.thisrow
+
+    var baseline = if oldrow.height < oldrow.lineheight:
+      let lines = oldrow.lineheight div ictx.cellheight
+      ceilDiv(lines, 2) * ictx.cellheight
+    else:
+      0
+
+    # 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
+
     ictx.rows.add(oldrow)
     ictx.height += oldrow.height
     ictx.width = max(ictx.width, oldrow.width)
@@ -82,20 +132,23 @@ 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()
+      ictx.finishRow(specified)
       # Recompute on newline
       shift = ictx.computeShift(specified)
       ictx.whitespace = false
 
   if atom.width > 0 and atom.height > 0:
+    atom.vertalign = specified{"vertical-align"}
+
     if shift > 0:
       let format = if atom of InlineWord:
         InlineWord(atom).format
       else:
         nil
-      ictx.thisrow.addSpacing(shift, atom.height, format)
+      ictx.thisrow.addSpacing(shift, ictx.cellheight, format)
 
     atom.relx += 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)
     ictx.thisrow.atoms.add(atom)
@@ -108,9 +161,9 @@ proc addWord(state: var InlineState) =
     state.newWord()
 
 # Start a new line, even if the previous one is empty
-proc flushLine(ictx: InlineContext) =
-  ictx.thisrow.height = max(ictx.thisrow.height, ictx.cellheight)
-  ictx.finishRow()
+proc flushLine(ictx: InlineContext, specified: CSSSpecifiedValues) =
+  ictx.thisrow.lineheight = computeLineHeight(ictx.viewport, specified)
+  ictx.finishRow(specified, true)
 
 proc checkWrap(state: var InlineState, r: Rune) =
   if state.specified{"white-space"} in {WHITESPACE_NOWRAP, WHITESPACE_PRE}:
@@ -120,11 +173,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.ictx.finishRow(state.specified)
       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.ictx.finishRow(state.specified)
       state.ictx.whitespace = false
   else: discard
 
@@ -135,7 +188,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.ictx.flushLine(state.specified)
     else:
       state.ictx.whitespace = true
 
@@ -163,9 +216,6 @@ proc renderText*(ictx: InlineContext, str: string, maxwidth: int, specified: CSS
 
   state.addWord()
 
-proc finish(ictx: InlineContext) =
-  ictx.finishRow()
-
 proc computedDimensions(bctx: BlockContext, width: int, height: Option[int]) =
   let pwidth = bctx.specified{"width"}
   if pwidth.auto:
@@ -207,7 +257,7 @@ proc newBlockContext(parent: BlockContext, box: BlockBox): BlockContext =
   result = newBlockContext_common(parent, box)
 
 proc newInlineBlockContext(parent: BlockContext, box: InlineBlockBox): BlockContext =
-  newBlockContext_common(parent, box)
+  result = newBlockContext_common(parent, box)
 
 # Anonymous block box.
 proc newBlockContext(parent: BlockContext): BlockContext =
@@ -231,7 +281,7 @@ proc newInlineContext(bctx: BlockContext): InlineContext =
 
 # Blocks' positions do not have to be arranged if alignBlocks is called with
 # children, whence the separate procedure.
-proc arrangeBlocks(bctx: BlockContext) =
+proc arrangeBlocks(bctx: BlockContext, selfcontained: bool) =
   var y = 0
   var x = 0
   var margin_todo = 0
@@ -258,6 +308,11 @@ proc arrangeBlocks(bctx: BlockContext) =
     if mtop > bctx.margin_top or mtop < 0:
       bctx.margin_top = mtop - bctx.margin_top
 
+    if selfcontained:
+      margin_todo = bctx.margin_top
+      bctx.height += margin_todo
+      y += margin_todo
+
     apply_child(child)
     inc i
 
@@ -277,6 +332,9 @@ proc arrangeBlocks(bctx: BlockContext) =
   if mbot > bctx.margin_bottom or mbot < 0:
     bctx.margin_bottom = mbot - bctx.margin_bottom
 
+  if selfcontained:
+    bctx.height += bctx.margin_bottom
+
   bctx.height += bctx.padding_bottom
 
   if bctx.compheight.issome:
@@ -285,10 +343,18 @@ proc arrangeBlocks(bctx: BlockContext) =
   bctx.width += bctx.padding_left
   bctx.width += bctx.padding_right
 
-proc arrangeInlines(bctx: BlockContext) =
+proc arrangeInlines(bctx: BlockContext, selfcontained: bool) =
+  bctx.margin_top = bctx.specified{"margin-top"}.px(bctx.viewport, bctx.compwidth)
+  bctx.margin_bottom = bctx.specified{"margin-bottom"}.px(bctx.viewport, bctx.compwidth)
+
   bctx.width += bctx.padding_left
   bctx.inline.relx += bctx.padding_left
 
+  if selfcontained:
+    bctx.inline.rely += bctx.margin_top
+    bctx.height += bctx.margin_top
+    bctx.height += bctx.margin_bottom
+
   bctx.height += bctx.padding_top
   bctx.inline.rely += bctx.padding_top
 
@@ -298,28 +364,24 @@ proc arrangeInlines(bctx: BlockContext) =
 
   bctx.width = min(bctx.width, bctx.compwidth)
 
-proc alignBlock(box: BlockBox)
+proc alignBlock(box: BlockBox, selfcontained = false)
 
-proc alignInlineBlock(bctx: BlockContext, box: InlineBlockBox, parentcss: CSSSpecifiedValues) =
+proc alignInlineBlock(bctx: BlockContext, box: InlineBlockBox) =
   if box.bctx.done:
     return
-  alignBlock(box)
-
-  box.bctx.rely += box.bctx.margin_top
-  box.bctx.height += box.bctx.margin_top
-  box.bctx.height += box.bctx.margin_bottom
+  alignBlock(box, true)
 
   box.bctx.relx += box.bctx.margin_left
   box.bctx.width += box.bctx.margin_left
   box.bctx.width += box.bctx.margin_right
 
-  box.ictx.addAtom(box.bctx, bctx.compwidth, parentcss)
+  box.ictx.addAtom(box.bctx, bctx.compwidth, box.specified)
   box.ictx.whitespace = false
 
 proc alignInline(bctx: BlockContext, box: InlineBox) =
   assert box.ictx != nil
   if box.newline:
-    box.ictx.flushLine()
+    box.ictx.flushLine(bctx.specified)
 
   let margin_left = box.specified{"margin-left"}.px(bctx.viewport, bctx.compwidth)
   box.ictx.thisrow.width += margin_left
@@ -327,7 +389,7 @@ proc alignInline(bctx: BlockContext, box: InlineBox) =
   let paddingformat = ComputedFormat(node: box.node)
   let padding_left = box.specified{"padding-left"}.px(bctx.viewport, bctx.compwidth)
   if padding_left > 0:
-    box.ictx.thisrow.addSpacing(padding_left, max(box.ictx.thisrow.height, 1), paddingformat)
+    box.ictx.thisrow.addSpacing(padding_left, box.ictx.cellheight, paddingformat)
 
   for text in box.text:
     assert box.children.len == 0
@@ -342,7 +404,7 @@ proc alignInline(bctx: BlockContext, box: InlineBox) =
     of DISPLAY_INLINE_BLOCK:
       let child = InlineBlockBox(child)
       child.ictx = box.ictx
-      bctx.alignInlineBlock(child, box.specified)
+      bctx.alignInlineBlock(child)
     else:
       assert false, "child.t is " & $child.t
 
@@ -355,25 +417,25 @@ proc alignInline(bctx: BlockContext, box: InlineBox) =
 
 proc alignInlines(bctx: BlockContext, inlines: seq[CSSBox]) =
   let ictx = bctx.newInlineContext()
-  for child in inlines:
-    case child.t
-    of DISPLAY_INLINE:
-      let child = InlineBox(child)
-      child.ictx = ictx
-      bctx.alignInline(child)
-    of DISPLAY_INLINE_BLOCK:
-      let child = InlineBlockBox(child)
-      child.ictx = ictx
-      bctx.alignInlineBlock(child, bctx.specified)
-    else:
-      assert false, "child.t is " & $child.t
-  ictx.finish()
+  if inlines.len > 0:
+    for child in inlines:
+      case child.t
+      of DISPLAY_INLINE:
+        let child = InlineBox(child)
+        child.ictx = ictx
+        bctx.alignInline(child)
+      of DISPLAY_INLINE_BLOCK:
+        let child = InlineBlockBox(child)
+        child.ictx = ictx
+        bctx.alignInlineBlock(child)
+      else:
+        assert false, "child.t is " & $child.t
+    ictx.finishRow(bctx.specified)
+
   bctx.height += ictx.height
   if bctx.compheight.issome:
     bctx.height = bctx.compheight.get
   bctx.width = max(bctx.width, ictx.width)
-  bctx.margin_top = bctx.specified{"margin-top"}.px(bctx.viewport, bctx.compwidth)
-  bctx.margin_bottom = bctx.specified{"margin-bottom"}.px(bctx.viewport, bctx.compwidth)
 
 template flush_group() =
   if blockgroup.len > 0:
@@ -402,19 +464,19 @@ proc alignBlocks(bctx: BlockContext, blocks: seq[CSSBox], blockgroup: var seq[CS
       blockgroup.add(child)
     else: discard #TODO
 
-proc alignBlock(box: BlockBox) =
+proc alignBlock(box: BlockBox, selfcontained = false) =
   if box.bctx.done:
     return
   if box.inlinelayout:
     # Box only contains inline boxes.
     box.bctx.alignInlines(box.children)
-    box.bctx.arrangeInlines()
+    box.bctx.arrangeInlines(selfcontained)
   else:
     var blockgroup: seq[CSSBox]
     box.bctx.alignBlocks(box.children, blockgroup, box.node)
     let bctx = box.bctx
     flush_group()
-    box.bctx.arrangeBlocks()
+    box.bctx.arrangeBlocks(selfcontained)
   box.bctx.done = true
 
 proc getBox(specified: CSSSpecifiedValues): CSSBox =
@@ -443,7 +505,10 @@ proc getTextBox(box: CSSBox): InlineBox =
   new(result)
   result.t = DISPLAY_INLINE
   result.inlinelayout = true
-  result.specified = box.specified.inheritProperties()
+  if box.specified{"display"} == DISPLAY_INLINE:
+    result.specified = box.specified
+  else:
+    result.specified = box.specified.inheritProperties()
 
 proc getPseudoBox(bctx: BlockContext, specified: CSSSpecifiedValues): CSSBox =
   let box = getBox(specified)
diff --git a/src/render/renderdocument.nim b/src/render/renderdocument.nim
index 13cd7b0c..732bcf47 100644
--- a/src/render/renderdocument.nim
+++ b/src/render/renderdocument.nim
@@ -27,7 +27,9 @@ func formatFromWord(computed: ComputedFormat): Format =
 proc setRowWord(lines: var FlexibleGrid, word: InlineWord, x, y: int, term: TermAttributes) =
   var r: Rune
 
-  let y = y div term.ppl
+  var y = (y + word.rely) div term.ppl
+  if y < 0: y = 0
+
   var x = (x + word.relx) div term.ppc
   var i = 0
   while x < 0:
@@ -72,7 +74,9 @@ proc setRowWord(lines: var FlexibleGrid, word: InlineWord, x, y: int, term: Term
 proc setSpacing(lines: var FlexibleGrid, spacing: InlineSpacing, x, y: int, term: TermAttributes) =
   var r: Rune
 
-  let y = y div term.ppl
+  var y = (y + spacing.rely) div term.ppl
+  if y < 0: y = 0
+
   var x = (x + spacing.relx) div term.ppc
   let width = spacing.width div term.ppc
 
@@ -123,11 +127,8 @@ proc renderInlineContext(grid: var FlexibleGrid, ctx: InlineContext, x, y: int,
   let y = y + ctx.rely
   for row in ctx.rows:
     let x = x + row.relx
-    let y = y + row.rely + row.height
+    let y = y + row.rely
     for atom in row.atoms:
-      # This aligns atoms with the baseline.
-      # (other alignment types in progress)
-      let y = y - atom.height
       if atom of BlockContext:
         let ctx = BlockContext(atom)
         grid.renderBlockContext(ctx, x, y, term)