about summary refs log tree commit diff stats
path: root/src/layout
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2021-12-13 11:52:13 +0100
committerbptato <nincsnevem662@gmail.com>2021-12-13 11:59:54 +0100
commite1194507b4f6240cb15c1783240f8a21d359bc16 (patch)
tree0076a000e8a00cb3605ef8b275bdcc66e9768d51 /src/layout
parente46f0a4cb9b6a843e900dbb3abd5ce9684f47016 (diff)
downloadchawan-e1194507b4f6240cb15c1783240f8a21d359bc16.tar.gz
Support ::before, ::after pseudo elements
Diffstat (limited to 'src/layout')
-rw-r--r--src/layout/box.nim1
-rw-r--r--src/layout/engine.nim137
2 files changed, 119 insertions, 19 deletions
diff --git a/src/layout/box.nim b/src/layout/box.nim
index cfbf8506..4e31b1e1 100644
--- a/src/layout/box.nim
+++ b/src/layout/box.nim
@@ -19,6 +19,7 @@ type
     icontext*: InlineContext
     bcontext*: BlockContext
     cssvalues*: CSSComputedValues
+    node*: Node
 
   #TODO move fromy
   InlineContext* = ref object
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index 81ae1420..277bc70d 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -280,6 +280,19 @@ func isInline(node: Node): bool =
             elem.cssvalues[PROPERTY_DISPLAY].display == DISPLAY_INLINE_BLOCK
   return false
 
+proc processComputedValueBox(state: var LayoutState, parent: CSSBox, values: CSSComputedValues): CSSBox =
+  case values[PROPERTY_DISPLAY].display
+  of DISPLAY_BLOCK:
+    #eprint "START", elem.tagType, parent.icontext.fromy
+    result = state.newBlockBox(parent, values)
+    #CSSBlockBox(result).tag = $elem.tagType
+  of DISPLAY_INLINE:
+    result = newInlineBox(parent, values)
+  of DISPLAY_NONE:
+    return nil
+  else:
+    return nil
+
 proc processElemBox(state: var LayoutState, parent: CSSBox, elem: Element): CSSBox =
   if elem.tagType == TAG_BR:
     if parent.icontext.conty:
@@ -289,17 +302,10 @@ proc processElemBox(state: var LayoutState, parent: CSSBox, elem: Element): CSSB
     else:
       inc parent.icontext.fromy
     parent.icontext.fromx = parent.x
-  case elem.cssvalues[PROPERTY_DISPLAY].display
-  of DISPLAY_BLOCK:
-    #eprint "START", elem.tagType, parent.icontext.fromy
-    result = state.newBlockBox(parent, elem.cssvalues)
-    #CSSBlockBox(result).tag = $elem.tagType
-  of DISPLAY_INLINE:
-    result = newInlineBox(parent, elem.cssvalues)
-  of DISPLAY_NONE:
-    return nil
-  else:
-    return nil
+
+  result = state.processComputedValueBox(parent, elem.cssvalues)
+  if result != nil:
+    result.node = elem
 
 proc processNodes(state: var LayoutState, parent: CSSBox, node: Node)
 
@@ -309,13 +315,30 @@ proc processNode(state: var LayoutState, parent: CSSBox, node: Node): CSSBox =
     result = state.processElemBox(parent, Element(node))
     if result == nil:
       return
+
     state.processNodes(result, node)
   of TEXT_NODE:
     let text = Text(node)
     result = state.processInlineBox(parent, text.data)
+    if result != nil:
+      result.node = node
   else: discard
 
-template processAnonBlock(state: var LayoutState, parent: CSSBox, c: Node) =
+proc processAnonComputedValues(state: var LayoutState, parent: CSSBox, c: CSSComputedValues): bool =
+  if parent.bcontext.has_blocks:
+    if c[PROPERTY_DISPLAY].display == DISPLAY_INLINE:
+      if parent.bcontext.anon_block == nil:
+        var cssvals: CSSComputedValues
+        cssvals.inheritProperties(parent.cssvalues)
+        parent.bcontext.anon_block = state.newBlockBox(parent, cssvals)
+      state.add(parent.bcontext.anon_block, state.processComputedValueBox(parent.bcontext.anon_block, c))
+      return true
+    elif parent.bcontext.anon_block != nil:
+      state.add(parent, parent.bcontext.anon_block)
+      parent.bcontext.anon_block = nil
+  return false
+
+proc processAnonBlock(state: var LayoutState, parent: CSSBox, c: Node): bool =
   if parent.bcontext.has_blocks:
     if c.isInline():
       if parent.bcontext.anon_block == nil:
@@ -323,22 +346,94 @@ template processAnonBlock(state: var LayoutState, parent: CSSBox, c: Node) =
         cssvals.inheritProperties(parent.cssvalues)
         parent.bcontext.anon_block = state.newBlockBox(parent, cssvals)
       state.add(parent.bcontext.anon_block, state.processNode(parent.bcontext.anon_block, c))
-      continue
+      return true
     elif parent.bcontext.anon_block != nil:
       state.add(parent, parent.bcontext.anon_block)
       parent.bcontext.anon_block = nil
+  return false
 
-proc processNodes(state: var LayoutState, parent: CSSBox, node: Node) =
-  state.nodes.add(node)
+func needsAnonymousBlockBoxes(node: Node): bool =
+  if node.nodeType == ELEMENT_NODE:
+    let elem = Element(node)
+    if elem.cssvalues_before != nil:
+      if elem.cssvalues_before[PROPERTY_DISPLAY].display == DISPLAY_BLOCK:
+        return true
+    if elem.cssvalues_after != nil:
+      if elem.cssvalues_after[PROPERTY_DISPLAY].display == DISPLAY_BLOCK:
+        return true
 
   for c in node.childNodes:
     if c.isBlock():
-      parent.bcontext.has_blocks = true
+      return true
+
+  return false
+
+# ugh this is ugly, but it works...
+# basically this
+# * checks if there's a ::before pseudo element
+# * checks if we need to wrap things in anonymous block boxes
+# * in case we do, it adds the text to the anonymous box
+# * in case we don't, it tries to add the text to a new parent box
+# * but only if a new parent box is needed.
+proc processBeforePseudoElem(state: var LayoutState, parent: CSSBox, node: Node) =
+  if node.nodeType == ELEMENT_NODE:
+    let elem = Element(node)
+
+    if elem.cssvalues_before != nil:
+      var box: CSSBox
+      if not state.processAnonComputedValues(parent, elem.cssvalues_before):
+        box = state.processComputedValueBox(parent, elem.cssvalues_before)
+        if box != nil:
+          box.node = node
+      else:
+        box = parent.bcontext.anon_block
+
+      let text = elem.cssvalues_before[PROPERTY_CONTENT].content
+      var inline = state.processInlineBox(box, $text)
+      if inline != nil:
+        inline.node = node
+        state.add(box, inline)
+
+      if box != parent.bcontext.anon_block:
+        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
+      if not state.processAnonComputedValues(parent, elem.cssvalues_after):
+        box = state.processComputedValueBox(parent, elem.cssvalues_after)
+        if box != nil:
+          box.node = node
+      else:
+        box = parent.bcontext.anon_block
+
+      let text = elem.cssvalues_after[PROPERTY_CONTENT].content
+      var inline = state.processInlineBox(box, $text)
+      if inline != nil:
+        inline.node = node
+        state.add(box, inline)
+
+      if box != parent.bcontext.anon_block:
+        state.add(parent, box)
+
+proc processNodes(state: var LayoutState, parent: CSSBox, node: Node) =
+  state.nodes.add(node)
+
+  parent.bcontext.has_blocks = node.needsAnonymousBlockBoxes()
+
+  state.processBeforePseudoElem(parent, node)
 
   for c in node.childNodes:
-    state.processAnonBlock(parent, c)
-    let box = state.processNode(parent, c)
-    state.add(parent, box)
+    let isanon = state.processAnonBlock(parent, c)
+    if not isanon:
+      let box = state.processNode(parent, c)
+      state.add(parent, box)
+
+  state.processAfterPseudoElem(parent, node)
 
   if parent.bcontext.anon_block != nil:
     state.add(parent, parent.bcontext.anon_block)
@@ -349,8 +444,12 @@ proc processNodes(state: var LayoutState, parent: CSSBox, node: Node) =
 proc alignBoxes*(document: Document, width: int, height: int): CSSBox =
   var state: LayoutState
   var rootbox = CSSBlockBox(x: 0, y: 0, width: width, height: 0)
+  rootbox.cssvalues = document.root.cssvalues
   rootbox.icontext = newInlineContext(rootbox)
   rootbox.bcontext = newBlockContext()
   state.nodes.add(document.root)
   state.processNodes(rootbox, document.root)
   return rootbox
+
+proc realignBoxes*(document: Document, width, height: int) =
+  var state: LayoutState