about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2022-01-26 22:15:00 +0100
committerbptato <nincsnevem662@gmail.com>2022-01-26 22:16:15 +0100
commit876f5e9c64992ffef8630cd2bafa8c5ba9431f01 (patch)
tree33f318fd1f7f5371d08182a3c99536906f380a37
parent402f7595b6c63e668ae09c040410bfa13de5b588 (diff)
downloadchawan-876f5e9c64992ffef8630cd2bafa8c5ba9431f01.tar.gz
Implement padding
-rw-r--r--src/io/buffer.nim36
-rw-r--r--src/io/cell.nim118
-rw-r--r--src/layout/box.nim20
-rw-r--r--src/layout/engine.nim102
-rw-r--r--src/render/renderdocument.nim46
-rw-r--r--src/render/rendertext.nim4
6 files changed, 199 insertions, 127 deletions
diff --git a/src/io/buffer.nim b/src/io/buffer.nim
index 0b8d09a4..6663458f 100644
--- a/src/io/buffer.nim
+++ b/src/io/buffer.nim
@@ -65,7 +65,7 @@ proc newBuffer*(): Buffer =
 func generateFullOutput(buffer: Buffer): string =
   var x = 0
   var w = 0
-  var formatting = newFormatting()
+  var format = newFormat()
   result &= HVP(1, 1)
 
   for cell in buffer.display:
@@ -75,7 +75,7 @@ func generateFullOutput(buffer: Buffer): string =
       x = 0
       w = 0
 
-    result &= formatting.processFormatting(cell.formatting)
+    result &= format.processFormat(cell.format)
     result &= $cell.runes
 
     w += cell.width()
@@ -88,7 +88,7 @@ func generateFullOutput(buffer: Buffer): string =
 # current one. ideally should be used when small changes are made (e.g. hover
 # changes underlining)
 func generateSwapOutput(buffer: Buffer): string =
-  var formatting = newFormatting()
+  var format = newFormat()
   let curr = buffer.display
   let prev = buffer.prevdisplay
   var i = 0
@@ -107,7 +107,7 @@ func generateSwapOutput(buffer: Buffer): string =
       inc y
       line = ""
     lr = lr or (curr[i] != prev[i])
-    line &= formatting.processFormatting(curr[i].formatting)
+    line &= format.processFormat(curr[i].format)
     line &= $curr[i].runes
     inc i
     inc x
@@ -140,7 +140,7 @@ func generateSwapOutput(buffer: Buffer): string =
   #      cx = x
   #      cy = y
 
-  #    text &= formatting.processFormatting(curr[i].formatting)
+  #    text &= format.processFormat(curr[i].format)
 
   #    text &= $curr[i].runes
   #    if currwidth < prevwidth:
@@ -157,10 +157,10 @@ func generateSwapOutput(buffer: Buffer): string =
   #  result &= $text
 
 func generateStatusMessage*(buffer: Buffer): string =
-  var formatting = newFormatting()
+  var format = newFormat()
   var w = 0
   for cell in buffer.statusmsg:
-    result &= formatting.processFormatting(cell.formatting)
+    result &= format.processFormat(cell.format)
     result &= $cell.runes
     w += cell.width()
   if w < buffer.width:
@@ -309,7 +309,7 @@ proc refreshDisplay(buffer: Buffer) =
         nf = line.findNextFormat(j)
       buffer.display[dls + k].runes.add(r)
       if cf.pos != -1:
-        buffer.display[dls + k].formatting = cf.formatting
+        buffer.display[dls + k].format = cf.format
         buffer.display[dls + k].node = cf.node
       let tk = k + r.width()
       while k < tk and k < buffer.width - 1:
@@ -763,7 +763,7 @@ proc cursorBufferPos(buffer: Buffer) =
 proc clearStatusMessage(buffer: Buffer) =
   buffer.statusmsg = newFixedGrid(buffer.width)
 
-proc writeStatusMessage(buffer: Buffer, str: string, formatting: Formatting = Formatting()) =
+proc writeStatusMessage(buffer: Buffer, str: string, format: Format = Format()) =
   buffer.clearStatusMessage()
   var i = 0
   for r in str.runes:
@@ -773,16 +773,16 @@ proc writeStatusMessage(buffer: Buffer, str: string, formatting: Formatting = Fo
       buffer.statusmsg[^1].runes.add(Rune('$'))
       break
     buffer.statusmsg[i].runes.add(r)
-    buffer.statusmsg[i].formatting = formatting
+    buffer.statusmsg[i].format = format
 
 proc statusMsgForBuffer(buffer: Buffer) =
   var msg = $(buffer.cursory + 1) & "/" & $buffer.numLines & " (" &
             $buffer.atPercentOf() & "%) " & "<" & buffer.title & ">"
   if buffer.hovertext.len > 0:
     msg &= " " & buffer.hovertext
-  var formatting: Formatting
-  formatting.reverse = true
-  buffer.writeStatusMessage(msg, formatting)
+  var format: Format
+  format.reverse = true
+  buffer.writeStatusMessage(msg, format)
 
 proc setStatusMessage*(buffer: Buffer, str: string) =
   buffer.writeStatusMessage(str)
@@ -811,16 +811,16 @@ proc click*(buffer: Buffer): string =
   return ""
 
 proc drawBuffer*(buffer: Buffer) =
-  var formatting = newFormatting()
+  var format = newFormat()
   for line in buffer.lines:
     if line.formats.len == 0:
       print(line.str & '\n')
     else:
       var x = 0
-      for format in line.formats:
-        print(line.str.substr(x, format.pos - 1))
-        print(formatting.processFormatting(format.formatting))
-        x = format.pos
+      for f in line.formats:
+        print(line.str.substr(x, f.pos - 1))
+        print(format.processFormat(f.format))
+        x = f.pos
       print(line.str.substr(x, line.str.len) & '\n')
 
 proc refreshBuffer*(buffer: Buffer) =
diff --git a/src/io/cell.nim b/src/io/cell.nim
index b783d180..7009a12a 100644
--- a/src/io/cell.nim
+++ b/src/io/cell.nim
@@ -5,6 +5,7 @@ import sugar
 import unicode
 
 import html/dom
+import layout/box
 import types/color
 import utils/twtstr
 
@@ -17,21 +18,22 @@ type
     FLAG_STRIKE
     FLAG_OVERLINE
 
-  Formatting* = object
+  Format* = object
     fgcolor*: CellColor
     bgcolor*: CellColor
     flags: set[FormatFlags]
 
   Cell* = object of RootObj
-    formatting*: Formatting
+    format*: Format
     node*: Node
 
-  FormattingCell* = object of Cell
+  FormatCell* = object of Cell
     pos*: int
+    computed*: ComputedFormat
 
   FlexibleLine* = object
     str*: string
-    formats*: seq[FormattingCell]
+    formats*: seq[FormatCell]
 
   FlexibleGrid* = seq[FlexibleLine]
 
@@ -49,20 +51,20 @@ const FormatCodes: array[FormatFlags, tuple[s: int, e: int]] = [
   FLAG_OVERLINE: (53, 55),
 ]
 
-template flag_template(formatting: Formatting, val: bool, flag: FormatFlags) =
-  if val: formatting.flags.incl(flag)
-  else: formatting.flags.excl(flag)
+template flag_template(format: Format, val: bool, flag: FormatFlags) =
+  if val: format.flags.incl(flag)
+  else: format.flags.excl(flag)
 
-template `italic=`*(f: var Formatting, b: bool) = flag_template f, b, FLAG_ITALIC
-template `bold=`*(f: var Formatting, b: bool) = flag_template f, b, FLAG_BOLD
-template `underline=`*(f: var Formatting, b: bool) = flag_template f, b, FLAG_UNDERLINE
-template `reverse=`*(f: var Formatting, b: bool) = flag_template f, b, FLAG_REVERSE
-template `strike=`*(f: var Formatting, b: bool) = flag_template f, b, FLAG_STRIKE
-template `overline=`*(f: var Formatting, b: bool) = flag_template f, b, FLAG_OVERLINE
+template `italic=`*(f: var Format, b: bool) = flag_template f, b, FLAG_ITALIC
+template `bold=`*(f: var Format, b: bool) = flag_template f, b, FLAG_BOLD
+template `underline=`*(f: var Format, b: bool) = flag_template f, b, FLAG_UNDERLINE
+template `reverse=`*(f: var Format, b: bool) = flag_template f, b, FLAG_REVERSE
+template `strike=`*(f: var Format, b: bool) = flag_template f, b, FLAG_STRIKE
+template `overline=`*(f: var Format, b: bool) = flag_template f, b, FLAG_OVERLINE
 
 #TODO ?????
 func `==`*(a: FixedCell, b: FixedCell): bool =
-  return a.formatting == b.formatting and
+  return a.format == b.format and
     a.runes == b.runes and
     a.node == b.node
 
@@ -75,8 +77,8 @@ func width*(line: FlexibleLine): int =
 func width*(cell: FixedCell): int =
   return cell.runes.width()
 
-func newFormatting*(): Formatting =
-  return Formatting(fgcolor: defaultColor, bgcolor: defaultColor)
+func newFormat*(): Format =
+  return Format(fgcolor: defaultColor, bgcolor: defaultColor)
 
 func findFormatN*(line: FlexibleLine, pos: int): int =
   var i = 0
@@ -86,21 +88,21 @@ func findFormatN*(line: FlexibleLine, pos: int): int =
     inc i 
   return i
 
-func findFormat*(line: FlexibleLine, pos: int): FormattingCell =
+func findFormat*(line: FlexibleLine, pos: int): FormatCell =
   let i = line.findFormatN(pos) - 1
   if i != -1:
     result = line.formats[i]
   else:
     result.pos = -1
 
-func findNextFormat*(line: FlexibleLine, pos: int): FormattingCell =
+func findNextFormat*(line: FlexibleLine, pos: int): FormatCell =
   let i = line.findFormatN(pos)
   if i < line.formats.len:
     result = line.formats[i]
   else:
     result.pos = -1
 
-func subformats*(formats: seq[FormattingCell], pos: int): seq[FormattingCell] =
+func subformats*(formats: seq[FormatCell], pos: int): seq[FormatCell] =
   var i = 0
   while i < formats.len:
     if formats[i].pos >= pos:
@@ -128,20 +130,18 @@ proc setLen*(line: var FlexibleLine, len: int) =
 
 proc add*(a: var FlexibleLine, b: FlexibleLine) =
   let l = a.str.len
-  a.formats.add(b.formats.map((x) => FormattingCell(formatting: x.formatting, node: x.node, pos: l + x.pos)))
+  a.formats.add(b.formats.map((x) => FormatCell(format: x.format, node: x.node, pos: l + x.pos)))
   a.str &= b.str
 
 proc addLine*(grid: var FlexibleGrid) =
   grid.add(FlexibleLine())
 
-proc addFormat*(line: var FlexibleLine, pos: int, format: Formatting) =
-  line.formats.add(FormattingCell(formatting: format, pos: line.str.len))
+proc addFormat*(line: var FlexibleLine, pos: int, format: Format) =
+  line.formats.add(FormatCell(format: format, pos: line.str.len))
 
-proc addFormat*(grid: var FlexibleGrid, y, pos: int, format: Formatting) =
-  grid[y].formats.add(FormattingCell(formatting: format, pos: grid[y].str.len))
-
-proc addFormat*(grid: var FlexibleGrid, y, pos: int, format: Formatting, node: Node) =
-  grid[y].formats.add(FormattingCell(formatting: format, node: node, pos: pos))
+proc addFormat*(grid: var FlexibleGrid, y, pos: int, format: Format, computed: ComputedFormat = nil, node: Node = nil) =
+  if computed == nil or grid[y].formats.len == 0 or grid[y].formats[^1].computed != computed:
+    grid[y].formats.add(FormatCell(format: format, node: node, computed: computed, pos: pos))
 
 proc addCell*(grid: var FlexibleGrid, y: int, r: Rune) =
   grid[y].str &= $r
@@ -154,11 +154,11 @@ template inc_check(i: int) =
   if i >= buf.len:
     return i
 
-proc handleAnsiCode(formatting: var Formatting, final: char, params: string) =
+proc handleAnsiCode(format: var Format, final: char, params: string) =
   case final
   of 'm':
     if params.len == 0:
-      formatting = newFormatting()
+      format = newFormat()
     else:
       let sparams = params.split(';')
       try:
@@ -167,17 +167,17 @@ proc handleAnsiCode(formatting: var Formatting, final: char, params: string) =
         while pi < ip.len:
           case ip[pi]
           of 0:
-            formatting = newFormatting()
-          of 1: formatting.bold = true
-          of 3: formatting.italic = true
-          of 4: formatting.underline = true
-          of 7: formatting.reverse = true
-          of 9: formatting.strike = true
-          of 22: formatting.bold = false
-          of 23: formatting.italic = false
-          of 27: formatting.reverse = false
-          of 29: formatting.strike = false
-          of 30..37: formatting.fgcolor = CellColor(rgb: false, color: uint8(ip[pi]))
+            format = newFormat()
+          of 1: format.bold = true
+          of 3: format.italic = true
+          of 4: format.underline = true
+          of 7: format.reverse = true
+          of 9: format.strike = true
+          of 22: format.bold = false
+          of 23: format.italic = false
+          of 27: format.reverse = false
+          of 29: format.strike = false
+          of 30..37: format.fgcolor = CellColor(rgb: false, color: uint8(ip[pi]))
           of 38:
             inc pi
             if pi < ip.len:
@@ -189,7 +189,7 @@ proc handleAnsiCode(formatting: var Formatting, final: char, params: string) =
                   let g = ip[pi]
                   inc pi
                   let b = ip[pi]
-                  formatting.fgcolor = CellColor(rgb: true, rgbcolor: rgb(r, g, b))
+                  format.fgcolor = CellColor(rgb: true, rgbcolor: rgb(r, g, b))
               else:
                 #TODO
                 inc pi
@@ -197,9 +197,9 @@ proc handleAnsiCode(formatting: var Formatting, final: char, params: string) =
             else:
               break
           of 39:
-            formatting.fgcolor = defaultColor
+            format.fgcolor = defaultColor
           of 40..47:
-            formatting.bgcolor = CellColor(rgb: false, color: uint8(ip[0]))
+            format.bgcolor = CellColor(rgb: false, color: uint8(ip[0]))
           of 48:
             inc pi
             if pi < ip.len:
@@ -211,22 +211,22 @@ proc handleAnsiCode(formatting: var Formatting, final: char, params: string) =
                   let g = ip[pi]
                   inc pi
                   let b = ip[pi]
-                  formatting.bgcolor = CellColor(rgb: true, rgbcolor: rgb(r, g, b))
+                  format.bgcolor = CellColor(rgb: true, rgbcolor: rgb(r, g, b))
               else:
                 #TODO
                 inc pi
                 continue
             else:
               break
-          of 49: formatting.bgcolor = defaultColor
-          of 53: formatting.overline = true
-          of 55: formatting.overline = false
+          of 49: format.bgcolor = defaultColor
+          of 53: format.overline = true
+          of 55: format.overline = false
           else: discard
           inc pi
       except ValueError: discard
   else: discard
 
-proc parseAnsiCode*(formatting: var Formatting, buf: string, fi: int): int =
+proc parseAnsiCode*(format: var Format, buf: string, fi: int): int =
   var i = fi
   if buf[i] != '\e':
     return i
@@ -253,11 +253,11 @@ proc parseAnsiCode*(formatting: var Formatting, buf: string, fi: int): int =
   let final = buf[i]
   #final byte
   if 0x40 <= int(buf[i]) and int(buf[i]) <= 0x7E:
-    formatting.handleAnsiCode(final, params)
+    format.handleAnsiCode(final, params)
 
   return i
 
-proc parseAnsiCode*(formatting: var Formatting, stream: Stream) =
+proc parseAnsiCode*(format: var Format, stream: Stream) =
   if stream.atEnd(): return
   var c = stream.readChar()
   if 0x40 <= int(c) and int(c) <= 0x5F:
@@ -284,37 +284,37 @@ proc parseAnsiCode*(formatting: var Formatting, stream: Stream) =
   #final byte
   if 0x40 <= int(c) and int(c) <= 0x7E:
     let final = c
-    formatting.handleAnsiCode(final, params)
+    format.handleAnsiCode(final, params)
 
-proc processFormatting*(formatting: var Formatting, cellf: Formatting): string =
+proc processFormat*(format: var Format, cellf: Format): string =
   for flag in FormatFlags:
-    if flag in formatting.flags and flag notin cellf.flags:
+    if flag in format.flags and flag notin cellf.flags:
       result &= SGR(FormatCodes[flag].e)
 
-  if cellf.fgcolor != formatting.fgcolor:
+  if cellf.fgcolor != format.fgcolor:
     var color = cellf.fgcolor
     if color.rgb:
       let rgb = color.rgbcolor
       result &= SGR(38, 2, rgb.r, rgb.g, rgb.b)
     elif color == defaultColor:
       result &= SGR()
-      formatting = newFormatting()
+      format = newFormat()
     else:
       result &= SGR(color.color)
 
-  if cellf.bgcolor != formatting.bgcolor:
+  if cellf.bgcolor != format.bgcolor:
     var color = cellf.bgcolor
     if color.rgb:
       let rgb = color.rgbcolor
       result &= SGR(48, 2, rgb.r, rgb.g, rgb.b)
     elif color == defaultColor:
       result &= SGR()
-      formatting = newFormatting()
+      format = newFormat()
     else:
       result &= SGR(color.color)
 
   for flag in FormatFlags:
-    if flag notin formatting.flags and flag in cellf.flags:
+    if flag notin format.flags and flag in cellf.flags:
       result &= SGR(FormatCodes[flag].s)
 
-  formatting = cellf
+  format = cellf
diff --git a/src/layout/box.nim b/src/layout/box.nim
index d7ae3400..44096c9a 100644
--- a/src/layout/box.nim
+++ b/src/layout/box.nim
@@ -23,17 +23,20 @@ type
     width*: int
     height*: int
 
-  InlineSpacing* = ref object of InlineAtom
-    word*: InlineWord
-
-  InlineWord* = ref object of InlineAtom
-    str*: string
+  ComputedFormat* = ref object
     fontstyle*: CSSFontStyle
     fontweight*: int
     textdecoration*: CSSTextDecoration
     color*: CSSColor
     node*: Node
 
+  InlineSpacing* = ref object of InlineAtom
+    format*: ComputedFormat
+
+  InlineWord* = ref object of InlineAtom
+    str*: string
+    format*: ComputedFormat
+
   InlineRow* = ref object
     atoms*: seq[InlineAtom]
     relx*: int
@@ -43,6 +46,7 @@ type
 
   InlineContext* = ref object
     relx*: int
+    rely*: int
     width*: int
     height*: int
     rows*: seq[InlineRow]
@@ -61,6 +65,12 @@ type
     rely*: int
     margin_top*: int
     margin_bottom*: int
+    margin_left*: int
+    margin_right*: int
+    padding_top*: int
+    padding_bottom*: int
+    padding_left*: int
+    padding_right*: int
 
     compwidth*: int
     compheight*: Option[int]
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index 45aef6de..b5876b5c 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -53,12 +53,13 @@ func computeShift(ictx: InlineContext, specified: CSSSpecifiedValues): int =
 
 proc newWord(state: var InlineState) =
   let word = InlineWord()
+  word.format = ComputedFormat()
   let specified = state.specified
-  word.color = specified{"color"}
-  word.fontstyle = specified{"font-style"}
-  word.fontweight = specified{"font-weight"}
-  word.textdecoration = specified{"text-decoration"}
-  word.node = state.node
+  word.format.color = specified{"color"}
+  word.format.fontstyle = specified{"font-style"}
+  word.format.fontweight = specified{"font-weight"}
+  word.format.textdecoration = specified{"text-decoration"}
+  word.format.node = state.node
   state.word = word
 
 proc finishRow(ictx: InlineContext) =
@@ -69,6 +70,12 @@ proc finishRow(ictx: InlineContext) =
     ictx.width = max(ictx.width, oldrow.width)
     ictx.thisrow = InlineRow(rely: oldrow.rely + oldrow.height)
 
+proc addSpacing(row: InlineRow, width, height: int, format: ComputedFormat) {.inline.} =
+  let spacing = InlineSpacing(width: width, height: height, format: format)
+  spacing.relx = row.width
+  row.width += spacing.width
+  row.atoms.add(spacing)
+
 proc addAtom(ictx: InlineContext, atom: InlineAtom, maxwidth: int, specified: CSSSpecifiedValues) =
   var shift = ictx.computeShift(specified)
   ictx.whitespace = false
@@ -82,10 +89,11 @@ proc addAtom(ictx: InlineContext, atom: InlineAtom, maxwidth: int, specified: CS
 
   if atom.width > 0 and atom.height > 0:
     if shift > 0:
-      let spacing = InlineSpacing(width: shift, height: atom.height)
-      spacing.relx = ictx.thisrow.width
-      ictx.thisrow.width += spacing.width
-      ictx.thisrow.atoms.add(spacing)
+      let format = if atom of InlineWord:
+        InlineWord(atom).format
+      else:
+        nil
+      ictx.thisrow.addSpacing(shift, atom.height, format)
 
     atom.relx += ictx.thisrow.width
     ictx.thisrow.width += atom.width
@@ -163,16 +171,22 @@ proc computedDimensions(bctx: BlockContext, width: int, height: Option[int]) =
   if pwidth.auto:
     bctx.compwidth = width
   else:
-    #bctx.compwidth = pwidth.cells_w(bctx.viewport, width)
     bctx.compwidth = pwidth.px(bctx.viewport, width)
 
-  #let mlef = bctx.specified{"margin-left"}.cells_w(bctx.viewport, width)
-  #let mrig = bctx.specified{"margin-right"}.cells_w(bctx.viewport, width)
-  let mlef = bctx.specified{"margin-left"}.px(bctx.viewport, width)
-  let mrig = bctx.specified{"margin-right"}.px(bctx.viewport, width)
-  bctx.relx = mlef
-  bctx.compwidth -= mlef
-  bctx.compwidth -= mrig
+  bctx.margin_left = bctx.specified{"margin-left"}.px(bctx.viewport, width)
+  bctx.margin_right = bctx.specified{"margin-right"}.px(bctx.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)
+
+  if bctx.compwidth >= width:
+    bctx.compwidth -= bctx.margin_left
+    bctx.compwidth -= bctx.margin_right
+
+    bctx.compwidth -= bctx.padding_left
+    bctx.compwidth -= bctx.padding_right
 
   let pheight = bctx.specified{"height"}
   if not pheight.auto:
@@ -219,10 +233,17 @@ proc newInlineContext(bctx: BlockContext): InlineContext =
 # children, whence the separate procedure.
 proc arrangeBlocks(bctx: BlockContext) =
   var y = 0
+  var x = 0
   var margin_todo = 0
 
+  y += bctx.padding_top
+  bctx.height += bctx.padding_top
+
+  x += bctx.padding_left
+
   template apply_child(child: BlockContext) =
     child.rely = y
+    child.relx = x + child.margin_left
     y += child.height
     bctx.height += child.height
     bctx.width = max(bctx.width, child.width)
@@ -233,7 +254,6 @@ proc arrangeBlocks(bctx: BlockContext) =
     let child = bctx.nested[i]
 
     bctx.margin_top = child.margin_top
-    #let mtop = bctx.specified{"margin-top"}.cells_h(bctx.viewport, bctx.compwidth)
     let mtop = bctx.specified{"margin-top"}.px(bctx.viewport, bctx.compwidth)
     if mtop > bctx.margin_top or mtop < 0:
       bctx.margin_top = mtop - bctx.margin_top
@@ -253,23 +273,46 @@ proc arrangeBlocks(bctx: BlockContext) =
     inc i
 
   bctx.margin_bottom = margin_todo
-  #let mbot = bctx.specified{"margin-bottom"}.cells_h(bctx.viewport, bctx.compwidth)
   let mbot = bctx.specified{"margin-bottom"}.px(bctx.viewport, bctx.compwidth)
   if mbot > bctx.margin_bottom or mbot < 0:
     bctx.margin_bottom = mbot - bctx.margin_bottom
 
+  bctx.height += bctx.padding_bottom
+
   if bctx.compheight.issome:
     bctx.height = bctx.compheight.get
 
+  bctx.width += bctx.padding_left
+  bctx.width += bctx.padding_right
+
+proc arrangeInlines(bctx: BlockContext) =
+  bctx.width += bctx.padding_left
+  bctx.inline.relx += bctx.padding_left
+
+  bctx.height += bctx.padding_top
+  bctx.inline.rely += bctx.padding_top
+
+  bctx.height += bctx.padding_bottom
+
+  bctx.width += bctx.padding_right
+
+  bctx.width = min(bctx.width, bctx.compwidth)
+
 proc alignBlock(box: BlockBox)
 
 proc alignInlineBlock(bctx: BlockContext, box: InlineBlockBox, parentcss: CSSSpecifiedValues) =
   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
+
+  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.whitespace = false
 
@@ -277,6 +320,15 @@ proc alignInline(bctx: BlockContext, box: InlineBox) =
   assert box.ictx != nil
   if box.newline:
     box.ictx.flushLine()
+
+  let margin_left = box.specified{"margin-left"}.px(bctx.viewport, bctx.compwidth)
+  box.ictx.thisrow.width += margin_left
+
+  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)
+
   for text in box.text:
     assert box.children.len == 0
     box.ictx.renderText(text, bctx.compwidth, box.specified, box.node)
@@ -294,6 +346,13 @@ proc alignInline(bctx: BlockContext, box: InlineBox) =
     else:
       assert false, "child.t is " & $child.t
 
+  let padding_right = box.specified{"padding-right"}.px(bctx.viewport, bctx.compwidth)
+  if padding_right > 0:
+    box.ictx.thisrow.addSpacing(padding_right, max(box.ictx.thisrow.height, 1), paddingformat)
+
+  let margin_right = box.specified{"margin-right"}.px(bctx.viewport, bctx.compwidth)
+  box.ictx.thisrow.width += margin_right
+
 proc alignInlines(bctx: BlockContext, inlines: seq[CSSBox]) =
   let ictx = bctx.newInlineContext()
   for child in inlines:
@@ -312,9 +371,7 @@ proc alignInlines(bctx: BlockContext, inlines: seq[CSSBox]) =
   bctx.height += ictx.height
   if bctx.compheight.issome:
     bctx.height = bctx.compheight.get
-  bctx.width = max(ictx.width, ictx.width)
-  #bctx.margin_top = bctx.specified{"margin-top"}.cells_h(bctx.viewport, bctx.compwidth)
-  #bctx.margin_bottom = bctx.specified{"margin-bottom"}.cells_h(bctx.viewport, bctx.compwidth)
+  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)
 
@@ -351,6 +408,7 @@ proc alignBlock(box: BlockBox) =
   if box.inlinelayout:
     # Box only contains inline boxes.
     box.bctx.alignInlines(box.children)
+    box.bctx.arrangeInlines()
   else:
     var blockgroup: seq[CSSBox]
     box.bctx.alignBlocks(box.children, blockgroup, box.node)
diff --git a/src/render/renderdocument.nim b/src/render/renderdocument.nim
index 89ce4ae6..13cd7b0c 100644
--- a/src/render/renderdocument.nim
+++ b/src/render/renderdocument.nim
@@ -11,24 +11,24 @@ import layout/box
 import layout/engine
 import utils/twtstr
 
-func formatFromWord(word: InlineWord): Formatting =
-  result.fgcolor = word.color.cellColor()
-  if word.fontstyle in { FONT_STYLE_ITALIC, FONT_STYLE_OBLIQUE }:
+func formatFromWord(computed: ComputedFormat): Format =
+  result.fgcolor = computed.color.cellColor()
+  if computed.fontstyle in { FONT_STYLE_ITALIC, FONT_STYLE_OBLIQUE }:
     result.italic = true
-  if word.fontweight > 500:
+  if computed.fontweight > 500:
     result.bold = true
-  if word.textdecoration == TEXT_DECORATION_UNDERLINE:
+  if computed.textdecoration == TEXT_DECORATION_UNDERLINE:
     result.underline = true
-  if word.textdecoration == TEXT_DECORATION_OVERLINE:
+  if computed.textdecoration == TEXT_DECORATION_OVERLINE:
     result.overline = true
-  if word.textdecoration == TEXT_DECORATION_LINE_THROUGH:
+  if computed.textdecoration == TEXT_DECORATION_LINE_THROUGH:
     result.strike = true
 
 proc setRowWord(lines: var FlexibleGrid, word: InlineWord, x, y: int, term: TermAttributes) =
   var r: Rune
 
   let y = y div term.ppl
-  var x = x div term.ppc
+  var x = (x + word.relx) div term.ppc
   var i = 0
   while x < 0:
     fastRuneAt(word.str, i, r)
@@ -48,13 +48,15 @@ proc setRowWord(lines: var FlexibleGrid, word: InlineWord, x, y: int, term: Term
   let oformats = lines[y].formats.subformats(i)
   lines[y].setLen(i)
 
-  lines.addFormat(y, i, word.formatFromWord(), word.node)
-
   var nx = cx
   if nx < x:
+    lines.addFormat(y, i, newFormat())
     lines[y].str &= ' '.repeat(x - nx)
+    i += x - nx
     nx = x
 
+  lines.addFormat(y, i, word.format.formatFromWord(), word.format, word.format.node)
+
   lines[y].str &= linestr
   nx += linestr.width()
 
@@ -71,7 +73,7 @@ proc setSpacing(lines: var FlexibleGrid, spacing: InlineSpacing, x, y: int, term
   var r: Rune
 
   let y = y div term.ppl
-  var x = x div term.ppc
+  var x = (x + spacing.relx) div term.ppc
   let width = spacing.width div term.ppc
 
   var i = 0
@@ -94,16 +96,16 @@ proc setSpacing(lines: var FlexibleGrid, spacing: InlineSpacing, x, y: int, term
   let oformats = lines[y].formats.subformats(i)
   lines[y].setLen(i)
 
-  if spacing.word != nil:
-    lines.addFormat(y, i, spacing.word.formatFromWord(), spacing.word.node)
-
   var nx = cx
   if nx < x:
+    lines.addFormat(y, i, newFormat())
     lines[y].str &= ' '.repeat(x - nx)
     nx = x
 
   lines[y].str &= linestr
   nx += linestr.len
+  if spacing.format != nil:
+    lines.addFormat(y, i, spacing.format.formatFromWord(), spacing.format, spacing.format.node)
 
   i = 0
   while cx < nx and i < ostr.len:
@@ -117,6 +119,8 @@ proc setSpacing(lines: var FlexibleGrid, spacing: InlineSpacing, x, y: int, term
 proc renderBlockContext(grid: var FlexibleGrid, ctx: BlockContext, x, y: int, term: TermAttributes)
 
 proc renderInlineContext(grid: var FlexibleGrid, ctx: InlineContext, x, y: int, term: TermAttributes) =
+  let x = x + ctx.relx
+  let y = y + ctx.rely
   for row in ctx.rows:
     let x = x + row.relx
     let y = y + row.rely + row.height
@@ -126,23 +130,23 @@ proc renderInlineContext(grid: var FlexibleGrid, ctx: InlineContext, x, y: int,
       let y = y - atom.height
       if atom of BlockContext:
         let ctx = BlockContext(atom)
-        grid.renderBlockContext(ctx, x + ctx.relx, y + ctx.rely, term)
+        grid.renderBlockContext(ctx, x, y, term)
       elif atom of InlineWord:
         let word = InlineWord(atom)
-        grid.setRowWord(word, x + word.relx, y, term)
+        grid.setRowWord(word, x, y, term)
       elif atom of InlineSpacing:
         let spacing = InlineSpacing(atom)
-        grid.setSpacing(spacing, x + spacing.relx, y, term)
+        grid.setSpacing(spacing, x, y, term)
 
 proc renderBlockContext(grid: var FlexibleGrid, ctx: BlockContext, x, y: int, term: TermAttributes) =
-  var x = x
-  var y = y
+  let x = x + ctx.relx
+  let y = y + ctx.rely
   if ctx.inline != nil:
     assert ctx.nested.len == 0
-    grid.renderInlineContext(ctx.inline, x + ctx.inline.relx, y, term)
+    grid.renderInlineContext(ctx.inline, x, y, term)
   else:
     for ctx in ctx.nested:
-      grid.renderBlockContext(ctx, x + ctx.relx, y + ctx.rely, term)
+      grid.renderBlockContext(ctx, x, y, term)
 
 const css = staticRead"res/ua.css"
 let uastyle = css.parseStylesheet()
diff --git a/src/render/rendertext.nim b/src/render/rendertext.nim
index b5054e7b..15a58c4c 100644
--- a/src/render/rendertext.nim
+++ b/src/render/rendertext.nim
@@ -4,7 +4,7 @@ import io/cell
 import utils/twtstr
 
 proc renderPlainText*(text: string): FlexibleGrid =
-  var format = newFormatting()
+  var format = newFormat()
   template add_format() =
     if af:
       af = false
@@ -38,7 +38,7 @@ proc renderPlainText*(text: string): FlexibleGrid =
     discard result.pop()
 
 proc renderStream*(stream: Stream): FlexibleGrid =
-  var format = newFormatting()
+  var format = newFormat()
   template add_format() =
     if af:
       af = false