about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2021-12-14 21:05:32 +0100
committerbptato <nincsnevem662@gmail.com>2021-12-14 21:05:32 +0100
commit148d6ce5154c2e8c6126509f39ae0cd2f019a0c0 (patch)
tree3f9fa2cdde8ab090ce0168f15e7e84f3d49283e9
parentfac95085e1de75e99c12ec30b2d697cf4a77d3ba (diff)
downloadchawan-148d6ce5154c2e8c6126509f39ae0cd2f019a0c0.tar.gz
Support all css length units
-rw-r--r--res/ua.css12
-rw-r--r--src/css/style.nim7
-rw-r--r--src/css/values.nim86
-rw-r--r--src/io/buffer.nim2
-rw-r--r--src/layout/box.nim9
-rw-r--r--src/layout/engine.nim91
-rw-r--r--src/types/enums.nim4
7 files changed, 130 insertions, 81 deletions
diff --git a/res/ua.css b/res/ua.css
index 08107420..e045823f 100644
--- a/res/ua.css
+++ b/res/ua.css
@@ -73,20 +73,20 @@ u, ins {
 }
 
 h1, h2, h3, h4, h5, h6 {
-	margin-top: 1ch;
-	margin-bottom: 1ch;
+	margin-top: 1em;
+	margin-bottom: 1em;
 	font-weight: bold;
 }
 
 pre {
-	margin-top: 1ch;
-	margin-bottom: 1ch;
+	margin-top: 1em;
+	margin-bottom: 1em;
 	white-space: pre;
 }
 
 p {
-	margin-top: 1ch;
-	margin-bottom: 1ch;
+	margin-top: 1em;
+	margin-bottom: 1em;
 }
 
 a {
diff --git a/src/css/style.nim b/src/css/style.nim
index c1fba752..e5b8414d 100644
--- a/src/css/style.nim
+++ b/src/css/style.nim
@@ -195,7 +195,12 @@ proc querySelector*(document: Document, q: string): seq[Element] =
     result.add(document.selectElems(sel))
 
 proc applyProperty(elem: Element, s: CSSSpecifiedValue, pseudo: PseudoElem) =
-  let cval = getComputedValue(s, elem.cssvalues)
+  var parent: CSSComputedValues
+  if elem.parentElement != nil:
+    parent = elem.parentElement.cssvalues
+  else:
+    parent = rootProperties()
+  let cval = getComputedValue(s, elem.cssvalues, parent)
   if cval.t == PROPERTY_MARGIN:
     let left = CSSSpecifiedValue(t: PROPERTY_MARGIN_LEFT, v: VALUE_LENGTH, length: cval.length, globalValue: s.globalValue)
     let right = CSSSpecifiedValue(t: PROPERTY_MARGIN_RIGHT, v: VALUE_LENGTH, length: cval.length, globalValue: s.globalValue)
diff --git a/src/css/values.nim b/src/css/values.nim
index c7f7a5b7..93dd3855 100644
--- a/src/css/values.nim
+++ b/src/css/values.nim
@@ -2,6 +2,7 @@ import unicode
 import tables
 import sugar
 import sequtils
+import options
 
 import utils/twtstr
 import types/enums
@@ -116,17 +117,34 @@ func valueType*(prop: CSSPropertyType): CSSValueType =
 func inherited(t: CSSPropertyType): bool =
   return InheritedArray[t]
 
-func cells*(l: CSSLength): int =
+func px(n: float64, d: int): int =
+  return int(n / float(d))
+
+func cells*(l: CSSLength, d, w, h: int, p: Option[int], o: bool): int =
   case l.unit
-  of UNIT_EM:
-    return int(l.num)
+  of UNIT_EM, UNIT_REM:
+    if o: int(l.num * 2) #horizontal
+    else: int(l.num) #vertical
   of UNIT_CH:
-    return int(l.num)
-  of UNIT_REM:
-    return int(l.num)
-  else:
-    #TODO
-    return int(l.num / 8)
+    if o: int(l.num) #horizontal
+    else: int(l.num / 2) #vertical
+  of UNIT_IC:
+    if o: int(l.num * 2) #horizontal
+    else: int(l.num) #vertical
+  of UNIT_EX:
+    if o: int(l.num / 2) #horizontal
+    else: int(l.num / 4) #vertical
+  of UNIT_PERC: int(p.get / 100 * l.num)
+  of UNIT_PX: px(l.num, d)
+  of UNIT_CM: px(l.num * 37.8, d)
+  of UNIT_MM: px(l.num * 3.78, d)
+  of UNIT_IN: px(l.num * 96, d)
+  of UNIT_PC: px(l.num * 96 / 6, d)
+  of UNIT_PT: px(l.num * 96 / 72, d)
+  of UNIT_VW: px(w / 100 * l.num, d)
+  of UNIT_VH: px(h / 100 * l.num, d)
+  of UNIT_VMIN: px(min(w, h) / 100 * l.num, d)
+  of UNIT_VMAX: px(max(w, h) / 100 * l.num, d)
 
 func listMarker*(t: CSSListStyleType, i: int): string =
   case t
@@ -300,24 +318,30 @@ const colors = {
   "rebeccapurple": 0x663399,
 }.map((a) => (a[0], CSSColor(rgba: RGBAColor(a[1])))).toTable()
 
+const units = {
+  "%": UNIT_PERC,
+  "cm": UNIT_CM,
+  "mm": UNIT_MM,
+  "in": UNIT_IN,
+  "px": UNIT_PX,
+  "pt": UNIT_PT,
+  "pc": UNIT_PC,
+  "em": UNIT_EM,
+  "ex": UNIT_EX,
+  "ch": UNIT_CH,
+  "ic": UNIT_CH,
+  "rem": UNIT_REM,
+  "vw": UNIT_VW,
+  "vh": UNIT_VH,
+  "vmin": UNIT_VMIN,
+  "vmax": UNIT_VMAX,
+}.toTable()
+
 func cssLength(val: float64, unit: string): CSSLength =
-  case unit
-  of "%": return CSSLength(num: val, unit: UNIT_PERC)
-  of "cm": return CSSLength(num: val, unit: UNIT_CM)
-  of "mm": return CSSLength(num: val, unit: UNIT_MM)
-  of "in": return CSSLength(num: val, unit: UNIT_IN)
-  of "px": return CSSLength(num: val, unit: UNIT_PX)
-  of "pt": return CSSLength(num: val, unit: UNIT_PT)
-  of "pc": return CSSLength(num: val, unit: UNIT_PC)
-  of "em": return CSSLength(num: val, unit: UNIT_EM)
-  of "ex": return CSSLength(num: val, unit: UNIT_EX)
-  of "ch": return CSSLength(num: val, unit: UNIT_CH)
-  of "rem": return CSSLength(num: val, unit: UNIT_REM)
-  of "vw": return CSSLength(num: val, unit: UNIT_VW)
-  of "vh": return CSSLength(num: val, unit: UNIT_VH)
-  of "vmin": return CSSLength(num: val, unit: UNIT_VMIN)
-  of "vmax": return CSSLength(num: val, unit: UNIT_VMAX)
-  else: raise newException(CSSValueError, "Invalid unit")
+  if unit in units:
+    CSSLength(num: val, unit: units[unit])
+  else:
+    raise newException(CSSValueError, "Invalid unit")
 
 func color(r, g, b: int): CSSColor =
   return CSSColor(rgba: rgba(r, g, b, 256))
@@ -604,7 +628,7 @@ func getDefault(t: CSSPropertyType): CSSComputedValue = {.cast(noSideEffect).}:
   assert defaultTable[t] != nil
   return defaultTable[t]
 
-func getComputedValue*(prop: CSSSpecifiedValue, current: CSSComputedValues): CSSComputedValue =
+func getComputedValue*(prop: CSSSpecifiedValue, current, parent: CSSComputedValues): CSSComputedValue =
   case prop.globalValue
   of VALUE_INHERIT:
     if inherited(prop.t):
@@ -643,9 +667,6 @@ func getComputedValue*(prop: CSSSpecifiedValue, current: CSSComputedValues): CSS
     return CSSComputedValue(t: prop.t, v: VALUE_LIST_STYLE_TYPE, liststyletype: prop.liststyletype)
   of VALUE_NONE: return CSSComputedValue(t: prop.t, v: VALUE_NONE)
 
-func getComputedValue*(d: CSSDeclaration, current: CSSComputedValues): CSSComputedValue =
-  return getComputedValue(getSpecifiedValue(d), current)
-
 proc rootProperties*(vals: var CSSComputedValues) =
   new(vals)
   for prop in low(CSSPropertyType)..high(CSSPropertyType):
@@ -667,3 +688,8 @@ func inheritProperties*(parent: CSSComputedValues): CSSComputedValues =
       result[prop] = parent[prop]
     else:
       result[prop] = getDefault(prop)
+
+func rootProperties*(): CSSComputedValues =
+  new(result)
+  for prop in low(CSSPropertyType)..high(CSSPropertyType):
+    result[prop] = getDefault(prop)
diff --git a/src/io/buffer.nim b/src/io/buffer.nim
index a43223ef..cff179b6 100644
--- a/src/io/buffer.nim
+++ b/src/io/buffer.nim
@@ -733,7 +733,7 @@ proc renderDocument*(buffer: Buffer) =
     ss_init = true
 
   buffer.document.applyStylesheets(ua_stylesheet, user_stylesheet)
-  buffer.rootbox = buffer.document.alignBoxes(buffer.width, buffer.height)
+  buffer.rootbox = buffer.document.alignBoxes(buffer.attrs)
   if buffer.rootbox == nil:
     return
   var stack: seq[CSSBox]
diff --git a/src/layout/box.nim b/src/layout/box.nim
index 4e31b1e1..679e7b57 100644
--- a/src/layout/box.nim
+++ b/src/layout/box.nim
@@ -1,6 +1,9 @@
+import options
+
 import types/enums
 import css/values
 import html/dom
+import io/term
 
 type
   CSSRect* = object
@@ -33,11 +36,13 @@ type
     fromy*: int
     margin_done*: int
     margin_todo*: int
-    has_blocks*: bool
-    anon_block*: CSSBlockBox
+    #following are *specified* dimensions. actual dimensions are in CSSBox
+    width*: int
+    height*: Option[int]
 
   LayoutState* = object
     nodes*: seq[Node]
+    term*: TermAttributes
 
   CSSRowBox* = object
     x*: int
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index abd76a78..2e84003d 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -1,10 +1,24 @@
 import unicode
+import options
 
 import layout/box
 import types/enums
 import html/dom
 import css/values
 import utils/twtstr
+import io/term
+
+func cells_in(l: CSSLength, state: LayoutState, d: int, p: Option[int], o: bool): int =
+  return cells(l, d, state.term.width, state.term.height, p, o)
+
+func cells_w(l: CSSLength, state: LayoutState, p: int): int =
+  return l.cells_in(state, state.term.ppc, p.some, true)
+
+func cells_h(l: CSSLength, state: LayoutState, p: Option[int]): int =
+  return l.cells_in(state, state.term.ppl, p, false)
+
+func cells_h(l: CSSLength, state: LayoutState, p: int): int =
+  return l.cells_in(state, state.term.ppl, p.some, false)
 
 func newInlineContext*(box: CSSBox): InlineContext =
   new(result)
@@ -16,9 +30,9 @@ func newBlockContext(): BlockContext =
 
 proc flushLines(box: CSSBox) =
   if box.icontext.conty:
-    inc box.height
     inc box.icontext.fromy
     inc box.bcontext.fromy
+    inc box.height
     box.icontext.conty = false
   box.icontext.fromy += box.bcontext.margin_todo
   box.bcontext.margin_done += box.bcontext.margin_todo
@@ -27,12 +41,12 @@ proc flushLines(box: CSSBox) =
 func newBlockBox(state: var LayoutState, parent: CSSBox, vals: CSSComputedValues): CSSBlockBox =
   new(result)
   result.x = parent.x
-  result.x += vals[PROPERTY_MARGIN_LEFT].length.cells()
+  result.x += vals[PROPERTY_MARGIN_LEFT].length.cells_w(state, parent.bcontext.width)
   result.bcontext = newBlockContext()
 
   parent.flushLines()
 
-  let mtop = vals[PROPERTY_MARGIN_TOP].length.cells()
+  let mtop = vals[PROPERTY_MARGIN_TOP].length.cells_h(state, parent.bcontext.width)
   if mtop > parent.bcontext.margin_done:
     let diff = mtop - parent.bcontext.margin_done
     parent.icontext.fromy += diff
@@ -42,28 +56,32 @@ func newBlockBox(state: var LayoutState, parent: CSSBox, vals: CSSComputedValues
 
   result.bcontext.margin_done = parent.bcontext.margin_done
 
-  let pwidth = vals[PROPERTY_WIDTH]
-  if pwidth.length.auto:
-    result.width = parent.width
+  let pwidth = vals[PROPERTY_WIDTH].length
+  if pwidth.auto:
+    result.bcontext.width = parent.bcontext.width
   else:
-    result.width = pwidth.length.cells()
+    result.bcontext.width = pwidth.cells_w(state, parent.bcontext.width)
+
+  let pheight = vals[PROPERTY_HEIGHT].length
+  if not pheight.auto:
+    if pheight.unit != UNIT_PERC or parent.bcontext.height.issome:
+      result.bcontext.height = pheight.cells_h(state, parent.bcontext.height).some
 
   result.icontext = newInlineContext(parent)
   result.icontext.fromy = result.y
   result.icontext.fromx = result.x
   result.cssvalues = vals
 
-func newInlineBox*(parent: CSSBox, vals: CSSComputedValues): CSSInlineBox =
+func newInlineBox*(state: LayoutState, parent: CSSBox, vals: CSSComputedValues): CSSInlineBox =
   assert parent != nil
   new(result)
   result.x = parent.x
   result.y = parent.icontext.fromy
 
-  result.width = parent.width
   result.icontext = parent.icontext
   result.bcontext = parent.bcontext
   result.cssvalues = vals
-  result.icontext.fromx += vals[PROPERTY_MARGIN_LEFT].length.cells()
+  result.icontext.fromx += vals[PROPERTY_MARGIN_LEFT].length.cells_w(state, parent.bcontext.width)
 
 type InlineState = object
   ibox: CSSInlineBox
@@ -113,7 +131,7 @@ proc addWord(state: var InlineState) =
   state.ww = 0
 
 proc wrapNormal(state: var InlineState, r: Rune) =
-  if state.fromx + state.width + state.ww == state.ibox.width and r == Rune(' '):
+  if state.fromx + state.width + state.ww == state.ibox.bcontext.width and r == Rune(' '):
     state.addWord()
   if state.word.len == 0:
     if r == Rune(' '):
@@ -132,16 +150,16 @@ proc checkWrap(state: var InlineState, r: Rune) =
   case state.ibox.cssvalues[PROPERTY_WORD_BREAK].wordbreak
   of WORD_BREAK_NORMAL:
     if state.fromx + state.width > state.ibox.x and
-        state.fromx + state.width + state.ww + r.width() > state.ibox.width:
+        state.fromx + state.width + state.ww + r.width() > state.ibox.bcontext.width:
       state.wrapNormal(r)
   of WORD_BREAK_BREAK_ALL:
-    if state.fromx + state.width + state.ww + r.width() > state.ibox.width:
+    if state.fromx + state.width + state.ww + r.width() > state.ibox.bcontext.width:
       var pl: seq[Rune]
       var i = 0
       var w = 0
       while i < state.word.len and
           state.ibox.icontext.fromx + state.rowbox.width + w <
-            state.ibox.width:
+            state.ibox.bcontext.width:
         pl &= state.word[i]
         w += state.word[i].width()
         inc i
@@ -156,7 +174,7 @@ proc checkWrap(state: var InlineState, r: Rune) =
       state.inlineWrap()
   of WORD_BREAK_KEEP_ALL:
     if state.fromx + state.width > state.ibox.x and
-        state.fromx + state.width + state.ww + r.width() > state.ibox.width:
+        state.fromx + state.width + state.ww + r.width() > state.ibox.bcontext.width:
       state.wrapNormal(r)
 
 proc preWrap(state: var InlineState) =
@@ -180,9 +198,7 @@ proc processInlineBox(lstate: var LayoutState, parent: CSSBox, str: string): CSS
   else:
     # TODO TODO TODO I highly doubt this is correct but it's the only way it
     # makes sense...
-    var inherit: CSSComputedValues
-    inherit.inheritProperties(parent.cssvalues)
-    state.ibox = newInlineBox(parent, inherit)
+    state.ibox = lstate.newInlineBox(parent, parent.cssvalues.inheritProperties())
 
   if str.len == 0:
     return
@@ -225,7 +241,8 @@ proc processInlineBox(lstate: var LayoutState, parent: CSSBox, str: string): CSS
       fastRuneAt(str, i, r)
       rw = r.width()
 
-    #TODO a better line wrapping algorithm would be nice
+    # TODO a better line wrapping algorithm would be nice... especially because
+    # this one doesn't even work
     if rw > 1 or state.ibox.cssvalues[PROPERTY_WORD_BREAK].wordbreak == WORD_BREAK_BREAK_ALL:
       state.addWord()
 
@@ -265,26 +282,22 @@ proc add(state: var LayoutState, parent: CSSBox, box: CSSBox) =
 
     box.flushLines()
 
-    let mbot = box.cssvalues[PROPERTY_MARGIN_BOTTOM].length.cells()
+    let mbot = box.cssvalues[PROPERTY_MARGIN_BOTTOM].length.cells_h(state, parent.bcontext.width)
     parent.bcontext.margin_todo += mbot
 
     parent.bcontext.margin_done = box.bcontext.margin_done
     parent.bcontext.margin_todo = max(parent.bcontext.margin_todo - box.bcontext.margin_done, 0)
 
+    if box.bcontext.height.isnone:
+      parent.icontext.fromy = box.icontext.fromy
+    else:
+      parent.icontext.fromy += box.bcontext.height.get
     #eprint "END", CSSBlockBox(box).tag, box.icontext.fromy
   elif box of CSSInlineBox:
-    parent.icontext.fromx += box.cssvalues[PROPERTY_MARGIN_RIGHT].length.cells()
+    parent.icontext.fromx += box.cssvalues[PROPERTY_MARGIN_RIGHT].length.cells_w(state, parent.bcontext.width)
+    parent.icontext.fromy = box.icontext.fromy
 
-  let pheight = box.cssvalues[PROPERTY_HEIGHT]
   parent.height += box.height
-  parent.icontext.fromy = box.icontext.fromy
-
-  if not pheight.length.auto:
-    let max = pheight.length.cells()
-    let diff = box.height - max
-    if diff > 0:
-      parent.height -= diff
-      parent.icontext.fromy -= diff
 
   parent.children.add(box)
 
@@ -311,7 +324,7 @@ proc processComputedValueBox(state: var LayoutState, parent: CSSBox, values: CSS
     result = state.newBlockBox(parent, values)
     #CSSBlockBox(result).tag = $elem.tagType
   of DISPLAY_INLINE:
-    result = newInlineBox(parent, values)
+    result = state.newInlineBox(parent, values)
   of DISPLAY_LIST_ITEM:
     result = state.newBlockBox(parent, values)
   of DISPLAY_NONE:
@@ -373,14 +386,15 @@ proc processBeforePseudoElem(state: var LayoutState, parent: CSSBox, node: Node)
         inline.node = node
         state.add(box, inline)
 
+      state.add(parent, box)
+
 # same as before except it's after
 proc processAfterPseudoElem(state: var LayoutState, parent: CSSBox, node: Node) =
   if node.nodeType == ELEMENT_NODE:
     let elem = Element(node)
 
     if elem.cssvalues_after != nil:
-      var box: CSSBox
-      box = state.processComputedValueBox(parent, elem.cssvalues_after)
+      let box = state.processComputedValueBox(parent, elem.cssvalues_after)
       if box != nil:
         box.node = node
 
@@ -421,15 +435,14 @@ proc processNodes(state: var LayoutState, parent: CSSBox, node: Node) =
 
   discard state.nodes.pop()
 
-proc alignBoxes*(document: Document, width: int, height: int): CSSBox =
+proc alignBoxes*(document: Document, term: TermAttributes): CSSBox =
   var state: LayoutState
-  var rootbox = CSSBlockBox(x: 0, y: 0, width: width, height: 0)
-  rootbox.cssvalues = document.root.cssvalues
+  state.term = term
+  var rootbox = CSSBlockBox(x: 0, y: 0)
+  rootbox.cssvalues = rootProperties()
   rootbox.icontext = newInlineContext(rootbox)
   rootbox.bcontext = newBlockContext()
+  rootbox.bcontext.width = term.width
   state.nodes.add(document.root)
   state.processNodes(rootbox, document.root)
   return rootbox
-
-proc realignBoxes*(document: Document, width, height: int) =
-  var state: LayoutState
diff --git a/src/types/enums.nim b/src/types/enums.nim
index 028b7557..300744ba 100644
--- a/src/types/enums.nim
+++ b/src/types/enums.nim
@@ -52,8 +52,8 @@ type
 
   CSSUnit* = enum
     UNIT_CM, UNIT_MM, UNIT_IN, UNIT_PX, UNIT_PT, UNIT_PC,
-    UNIT_EM, UNIT_EX, UNIT_CH, UNIT_REM, UNIT_VW, UNIT_VH, UNIT_VMIN, UNIT_VMAX,
-    UNIT_PERC
+    UNIT_EM, UNIT_EX, UNIT_CH, UNIT_REM, UNIT_VW, UNIT_VH, UNIT_VMIN,
+    UNIT_VMAX, UNIT_PERC, UNIT_IC
 
   CSSPropertyType* = enum
     PROPERTY_NONE, PROPERTY_ALL, PROPERTY_COLOR, PROPERTY_MARGIN,