about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2022-01-23 12:41:23 +0100
committerbptato <nincsnevem662@gmail.com>2022-01-23 12:41:23 +0100
commit7a2cda0e992da40684c193791b5865bb643df95e (patch)
tree21a08211c69afd14bc84fe8ba666402c071d1117 /src
parent806e38f140b377b308ed41622d795c21a497bd44 (diff)
downloadchawan-7a2cda0e992da40684c193791b5865bb643df95e.tar.gz
Implement word-spacing
Diffstat (limited to 'src')
-rw-r--r--src/css/values.nim30
-rw-r--r--src/layout/box.nim1
-rw-r--r--src/layout/engine.nim54
3 files changed, 57 insertions, 28 deletions
diff --git a/src/css/values.nim b/src/css/values.nim
index 638dc2be..93f40cd1 100644
--- a/src/css/values.nim
+++ b/src/css/values.nim
@@ -27,7 +27,7 @@ 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_PADDING_BOTTOM, PROPERTY_WORD_SPACING
 
   CSSValueType* = enum
     VALUE_NONE, VALUE_LENGTH, VALUE_COLOR, VALUE_CONTENT, VALUE_DISPLAY,
@@ -132,6 +132,7 @@ const PropertyNames = {
   "padding-bottom": PROPERTY_PADDING_BOTTOM,
   "padding-left": PROPERTY_PADDING_LEFT,
   "padding-right": PROPERTY_PADDING_RIGHT,
+  "word-spacing": PROPERTY_WORD_SPACING,
 }.toTable()
 
 const ValueTypes = [
@@ -158,12 +159,13 @@ const ValueTypes = [
   PROPERTY_PADDING_LEFT: VALUE_LENGTH,
   PROPERTY_PADDING_RIGHT: VALUE_LENGTH,
   PROPERTY_PADDING_BOTTOM: VALUE_LENGTH,
+  PROPERTY_WORD_SPACING: 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_LIST_STYLE_TYPE, PROPERTY_WORD_SPACING
 }
 
 func getPropInheritedArray(): array[CSSPropertyType, bool] =
@@ -496,10 +498,20 @@ func cssLength(d: CSSDeclaration): CSSLength =
     of CSS_IDENT_TOKEN:
       if $tok.value == "auto":
         return CSSLength(auto: true)
-    else:
-      return CSSLength(num: 0, unit: UNIT_EM)
+    else: discard
+  raise newException(CSSValueError, "Invalid length")
 
-  return CSSLength(num: 0, unit: UNIT_EM)
+func cssWordSpacing(d: CSSDeclaration): CSSLength =
+  if d.value.len > 0 and d.value[0] of CSSToken:
+    let tok = CSSToken(d.value[0])
+    case tok.tokenType
+    of CSS_DIMENSION_TOKEN:
+      return cssLength(tok.nvalue, $tok.unit)
+    of CSS_IDENT_TOKEN:
+      if $tok.value == "normal":
+        return CSSLength(auto: true)
+    else: discard
+  raise newException(CSSValueError, "Invalid word spacing")
 
 func isToken(d: CSSDeclaration): bool = d.value.len > 0 and d.value[0] of CSSToken
 
@@ -625,7 +637,11 @@ func cssListStyleType(d: CSSDeclaration): CSSListStyleType =
 proc getValueFromDecl(val: CSSSpecifiedValue, d: CSSDeclaration, vtype: CSSValueType, ptype: CSSPropertyType) =
   case vtype
   of VALUE_COLOR: val.color = cssColor(d)
-  of VALUE_LENGTH: val.length = cssLength(d)
+  of VALUE_LENGTH:
+    if ptype == PROPERTY_WORD_SPACING:
+      val.length = cssWordSpacing(d)
+    else:
+      val.length = cssLength(d)
   of VALUE_FONT_STYLE: val.fontstyle = cssFontStyle(d)
   of VALUE_DISPLAY: val.display = cssDisplay(d)
   of VALUE_CONTENT: val.content = cssString(d)
@@ -647,7 +663,7 @@ func getInitialColor(t: CSSPropertyType): CSSColor =
 
 func getInitialLength(t: CSSPropertyType): CSSLength =
   case t
-  of PROPERTY_WIDTH, PROPERTY_HEIGHT:
+  of PROPERTY_WIDTH, PROPERTY_HEIGHT, PROPERTY_WORD_SPACING:
     return CSSLength(auto: true)
   else:
     return CSSLength()
diff --git a/src/layout/box.nim b/src/layout/box.nim
index a49bde7f..e5e3f2fa 100644
--- a/src/layout/box.nim
+++ b/src/layout/box.nim
@@ -48,6 +48,7 @@ type
 
     whitespace*: bool
     maxwidth*: int
+    viewport*: Viewport
 
   BlockContext* = ref object of InlineAtom
     inline*: InlineContext
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index d11f1093..cfd2a1dd 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -46,22 +46,28 @@ proc finishRow(ictx: InlineContext) =
     ictx.width = max(ictx.width, oldrow.width)
     ictx.thisrow = InlineRow(rely: oldrow.rely + oldrow.height)
 
-proc addAtom(ictx: InlineContext, atom: InlineAtom, maxwidth: int, specified: CSSSpecifiedValues) =
-  # Whitespace between words
-  var shift = 0
-  let whitespacepre = specified{"white-space"} in {WHITESPACE_PRE, WHITESPACE_PRE_WRAP}
+func whitespacepre(specified: CSSSpecifiedValues): bool {.inline.} =
+  specified{"white-space"} in {WHITESPACE_PRE, WHITESPACE_PRE_WRAP}
+
+# Whitespace between words
+func computeShift(ictx: InlineContext, specified: CSSSpecifiedValues): int =
   if ictx.whitespace:
-    if ictx.thisrow.atoms.len > 0 or whitespacepre:
-      shift = 1
+    if ictx.thisrow.atoms.len > 0 or specified.whitespacepre:
+      let spacing = specified{"word-spacing"}
+      if spacing.auto:
+        return 1
+      return spacing.cells_w(ictx.viewport, 0)
     ictx.whitespace = false
+  return 0
 
+proc addAtom(ictx: InlineContext, atom: InlineAtom, maxwidth: int, specified: CSSSpecifiedValues) =
+  var shift = ictx.computeShift(specified)
   # Line wrapping
   if specified{"white-space"} notin {WHITESPACE_NOWRAP, WHITESPACE_PRE}:
-    if specified{"word-break"} == WORD_BREAK_NORMAL and ictx.thisrow.width + atom.width + shift > maxwidth:
+    if ictx.thisrow.width + atom.width + shift > maxwidth:
       ictx.finishRow()
-      if not whitespacepre:
-        # No whitespace on newline
-        shift = 0
+      # Recompute on newline
+      shift = ictx.computeShift(specified)
 
   ictx.thisrow.width += shift
 
@@ -87,13 +93,14 @@ proc flushLine(ictx: InlineContext) =
 proc checkWrap(state: var InlineState, r: Rune) =
   if state.specified{"white-space"} in {WHITESPACE_NOWRAP, WHITESPACE_PRE}:
     return
+  let shift = state.ictx.computeShift(state.specified)
   case state.specified{"word-break"}
   of WORD_BREAK_BREAK_ALL:
-    if state.ictx.thisrow.width + state.word.width + r.width() > state.maxwidth:
+    if state.ictx.thisrow.width + state.word.width + shift + r.width() > state.maxwidth:
       state.addWord()
       state.ictx.finishRow()
   of WORD_BREAK_KEEP_ALL:
-    if state.ictx.thisrow.width + state.word.width + r.width() > state.maxwidth:
+    if state.ictx.thisrow.width + state.word.width + shift + r.width() > state.maxwidth:
       state.ictx.finishRow()
   else: discard
 
@@ -120,18 +127,15 @@ proc renderText*(ictx: InlineContext, str: string, maxwidth: int, specified: CSS
     #eprint "start", str.strip()
   var i = 0
   while i < str.len:
-    var rw = 0
-    case str[i]
-    of ' ', '\n', '\t':
+    if str[i].isWhitespace():
       state.processWhitespace(str[i])
       inc i
     else:
       var r: Rune
       fastRuneAt(str, i, r)
-      rw = r.width()
       state.checkWrap(r)
       state.word.str &= r
-      state.word.width += rw
+      state.word.width += r.width()
 
   state.addWord()
 
@@ -186,6 +190,7 @@ proc newBlockContext(viewport: Viewport): BlockContext =
 proc newInlineContext(bctx: BlockContext): InlineContext =
   new(result)
   result.thisrow = InlineRow()
+  result.viewport = bctx.viewport
   bctx.inline = result
 
 # Blocks' positions do not have to be arranged if alignBlocks is called with
@@ -235,7 +240,8 @@ proc arrangeBlocks(bctx: BlockContext) =
 proc alignBlock(box: BlockBox)
 
 proc alignInlineBlock(bctx: BlockContext, box: InlineBlockBox, parentcss: CSSSpecifiedValues) =
-  box.bctx = bctx.newInlineBlockContext(box)
+  if box.bctx.done:
+    return
   alignBlock(box)
   box.bctx.rely += box.bctx.margin_top
   box.bctx.height += box.bctx.margin_top
@@ -328,7 +334,6 @@ proc alignBlocks(bctx: BlockContext, blocks: seq[CSSBox]) =
 proc alignBlock(box: BlockBox) =
   if box.bctx.done:
     return
-  box.bctx.done = true
   if box.node != nil:
     box.bctx.viewport.nodes.add(box.node)
   if box.inlinelayout:
@@ -339,6 +344,7 @@ proc alignBlock(box: BlockBox) =
     box.bctx.arrangeBlocks()
   if box.node != nil:
     discard box.bctx.viewport.nodes.pop()
+  box.bctx.done = true
 
 proc getBox(specified: CSSSpecifiedValues): CSSBox =
   case specified{"display"}
@@ -390,7 +396,7 @@ 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}:
+    if box.specified{"display"} in {DISPLAY_BLOCK, DISPLAY_LIST_ITEM, DISPLAY_INLINE_BLOCK}:
       let box = BlockBox(box)
       if bctx == nil:
         box.bctx = viewport.newBlockContext()
@@ -418,13 +424,19 @@ proc generateBox(elem: Element, viewport: Viewport, bctx: BlockContext = nil): C
   box.element = elem
 
   var bctx = bctx
-  if box.specified{"display"} in {DISPLAY_BLOCK, DISPLAY_LIST_ITEM}:
+  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)
+    box.bctx = bctx.newInlineBlockContext(box)
+    bctx = box.bctx
+  else: discard
 
   var ibox: InlineBox
   template add_ibox() =