about summary refs log tree commit diff stats
path: root/src/css
diff options
context:
space:
mode:
Diffstat (limited to 'src/css')
-rw-r--r--src/css/cascade.nim205
-rw-r--r--src/css/stylednode.nim18
-rw-r--r--src/css/values.nim6
3 files changed, 196 insertions, 33 deletions
diff --git a/src/css/cascade.nim b/src/css/cascade.nim
index 8a52cc58..d71a3dab 100644
--- a/src/css/cascade.nim
+++ b/src/css/cascade.nim
@@ -2,13 +2,15 @@ import algorithm
 import streams
 import sugar
 
-import css/mediaquery
 import css/cssparser
+import css/mediaquery
 import css/select
 import css/selectorparser
 import css/sheet
+import css/stylednode
 import css/values
 import html/dom
+import html/tags
 
 type
   ApplyResult = object
@@ -32,6 +34,17 @@ proc applyProperty(elem: Element, d: CSSDeclaration, pseudo: PseudoElem) =
 
   elem.cssapplied = true
 
+proc applyProperty(styledNode: StyledNode, parent: CSSComputedValues, d: CSSDeclaration) =
+  
+  styledNode.computed.applyValue(parent, d)
+  #else:
+    #if styled.pseudo[pseudo] == nil:
+    #  elem.pseudo[pseudo] = elem.css.inheritProperties()
+    #elem.pseudo[pseudo].applyValue(elem.css, d)
+
+  if styledNode.node != nil:
+    Element(styledNode.node).cssapplied = true
+
 func applies(mq: MediaQuery): bool =
   case mq.t
   of CONDITION_MEDIA:
@@ -144,6 +157,58 @@ proc applyDeclarations(element: Element, ua, user: DeclarationList, author: seq[
   for rule in ares.important:
     element.applyProperty(rule, pseudo)
 
+# Always returns a new styled node, with the passed declarations applied.
+proc applyDeclarations(elem: Element, parent: CSSComputedValues, ua, user: DeclarationList, author: seq[DeclarationList]): StyledNode =
+  let pseudo = PSEUDO_NONE
+  var ares: ApplyResult
+
+  ares.applyNormal(ua[pseudo])
+  ares.applyNormal(user[pseudo])
+  for rule in author:
+    ares.applyNormal(rule[pseudo])
+
+  for rule in author:
+    ares.applyImportant(rule[pseudo])
+
+  let style = Element(elem).attr("style")
+  if style.len > 0:
+    let inline_rules = newStringStream(style).parseListOfDeclarations2()
+    ares.applyNormal(inline_rules)
+    ares.applyImportant(inline_rules)
+
+  ares.applyImportant(user[pseudo])
+  ares.applyImportant(ua[pseudo])
+
+  result = StyledNode(t: STYLED_ELEMENT, node: elem, computed: parent.inheritProperties())
+  for rule in ares.normal:
+    result.applyProperty(parent, rule)
+
+  for rule in ares.important:
+    result.applyProperty(parent, rule)
+
+# Either returns a new styled node or nil.
+proc applyDeclarations(pseudo: PseudoElem, parent: CSSComputedValues, ua, user: DeclarationList, author: seq[DeclarationList]): StyledNode =
+  var ares: ApplyResult
+
+  ares.applyNormal(ua[pseudo])
+  ares.applyNormal(user[pseudo])
+  for rule in author:
+    ares.applyNormal(rule[pseudo])
+
+  for rule in author:
+    ares.applyImportant(rule[pseudo])
+
+  ares.applyImportant(user[pseudo])
+  ares.applyImportant(ua[pseudo])
+
+  if ares.normal.len > 0 or ares.important.len > 0:
+    result = StyledNode(t: STYLED_ELEMENT, node: nil, computed: parent.inheritProperties(), pseudo: pseudo)
+    for rule in ares.normal:
+      result.applyProperty(parent, rule)
+
+    for rule in ares.important:
+      result.applyProperty(parent, rule)
+
 func applyMediaQuery(ss: CSSStylesheet): CSSStylesheet =
   result = ss
   for mq in ss.mq_list:
@@ -159,7 +224,7 @@ proc resetRules(elem: Element) =
   for pseudo in PSEUDO_BEFORE..PSEUDO_AFTER:
     elem.pseudo[pseudo] = nil
 
-proc applyRules(elem: Element, ua, user: CSSStylesheet, author: seq[CSSStylesheet]) {.inline.} =
+proc applyRules(elem: Element, ua, user: CSSStylesheet, author: seq[CSSStylesheet]) =
   let uadecls = calcRules(elem, ua)
   let userdecls = calcRules(elem, user)
   var authordecls: seq[DeclarationList]
@@ -169,49 +234,129 @@ proc applyRules(elem: Element, ua, user: CSSStylesheet, author: seq[CSSStyleshee
   for pseudo in PseudoElem:
     elem.applyDeclarations(uadecls, userdecls, authordecls, pseudo)
 
-proc applyRules(document: Document, ua, user: CSSStylesheet) =
+func calcRules(elem: Element, ua, user: CSSStylesheet, author: seq[CSSStylesheet]): tuple[uadecls, userdecls: DeclarationList, authordecls: seq[DeclarationList]] =
+  result.uadecls = calcRules(elem, ua)
+  result.userdecls = calcRules(elem, user)
+  for rule in author:
+    result.authordecls.add(calcRules(elem, rule))
+
+proc applyStyle(parent: StyledNode, elem: Element, uadecls, userdecls: DeclarationList, authordecls: seq[DeclarationList]): StyledNode =
+  let parentComputed = if parent != nil:
+    parent.computed
+  else:
+    rootProperties()
+
+  result = elem.applyDeclarations(parentComputed, uadecls, userdecls, authordecls)
+  assert result != nil
+
+proc applyRules(document: Document, ua, user: CSSStylesheet, previousStyled: StyledNode): StyledNode =
+  if document.html == nil:
+    return
+
   var author: seq[CSSStylesheet]
 
   if document.head != nil:
     for sheet in document.head.sheets:
       author.add(sheet)
 
-  var stack: seq[Element]
-
-  if document.html != nil:
-    stack.add(document.html)
-  var lenstack = newSeqOfCap[int](15)
+  var lenstack = newSeqOfCap[int](256)
+  var styledStack: seq[(StyledNode, Node, PseudoElem, StyledNode)]
+  if previousStyled != nil:
+    styledStack.add((nil, document.html, PSEUDO_NONE, previousStyled))
+  else:
+    styledStack.add((nil, document.html, PSEUDO_NONE, nil))
 
-  while stack.len > 0:
-    let elem = stack.pop()
+  #TODO TODO TODO this can't work as we currently store cached children in the
+  # same seq we use for storing new children...
+  # For now we just reset previous children which effectively disables caching.
+  while styledStack.len > 0:
+    let (styledParent, child, pseudo, cachedChild) = styledStack.pop()
 
     # Remove stylesheets on nil
-    if elem == nil:
+    if pseudo == PSEUDO_NONE and child == nil:
       let len = lenstack.pop()
       author.setLen(author.len - len)
       continue
 
-    if not elem.cssapplied:
-      let prev = elem.css
-      let ppseudo = elem.pseudo
-      elem.resetRules()
-      elem.applyRules(ua, user, author)
-      elem.checkRendered(prev, ppseudo)
-
-    # Add nil before the last element (in-stack), so we can remove the
-    # stylesheets
-    if elem.sheets.len > 0:
-      author.add(elem.sheets)
-      lenstack.add(elem.sheets.len)
-      stack.add(nil)
-
-    for i in countdown(elem.children.high, 0):
-      stack.add(elem.children[i])
-
-proc applyStylesheets*(document: Document, uass, userss: CSSStylesheet) =
+    template stack_append(styledParent: StyledNode, child: Node) =
+      if child.nodeType != ELEMENT_NODE or Element(child).cssapplied:
+        var cachedChild: StyledNode
+        for it in styledParent.children:
+          if it.node == child:
+            cachedChild = it
+            break
+        styledStack.add((styledParent, child, PSEUDO_NONE, cachedChild))
+      else:
+        eprint "else branch"
+        styledStack.add((styledParent, child, PSEUDO_NONE, nil))
+
+    template stack_append(styledParent: StyledNode, ps: PseudoElem) =
+      if Element(styledParent.node).cssapplied:
+        var cachedChild: StyledNode
+        for it in styledParent.children:
+          if it.t == STYLED_ELEMENT and it.pseudo == ps:
+            cachedChild = it
+            break
+        styledStack.add((styledParent, nil, ps, cachedChild))
+      else:
+        eprint "else branch 2"
+        styledStack.add((styledParent, nil, ps, nil))
+
+    var styledChild: StyledNode
+    if cachedChild != nil:
+      styledChild = cachedChild
+      if styledParent == nil:
+        result = styledChild
+      else:
+        styledParent.children.add(styledChild)
+      styledChild.children.setLen(0)
+    else:
+      if pseudo != PSEUDO_NONE:
+        let (ua, user, authordecls) = Element(styledParent.node).calcRules(ua, user, author)
+        let styledPseudo = pseudo.applyDeclarations(styledParent.computed, ua, user, authordecls)
+        if styledPseudo != nil:
+          styledParent.children.add(styledPseudo)
+          let content = styledPseudo.computed{"content"}
+          if content.len > 0:
+            styledPseudo.children.add(StyledNode(t: STYLED_TEXT, text: content))
+      else:
+        assert child != nil
+        if styledParent != nil:
+          if child.nodeType == ELEMENT_NODE:
+            let (ua, user, authordecls) = Element(child).calcRules(ua, user, author)
+            styledChild = applyStyle(styledParent, Element(child), ua, user, authordecls)
+            styledParent.children.add(styledChild)
+          elif child.nodeType == TEXT_NODE:
+            let text = Text(child)
+            styledChild = StyledNode(t: STYLED_TEXT, node: child, text: text.data)
+            styledParent.children.add(styledChild)
+        else:
+          # Root element
+          assert result == nil
+          let (ua, user, authordecls) = Element(child).calcRules(ua, user, author)
+          styledChild = applyStyle(styledParent, Element(child), ua, user, authordecls)
+          result = styledChild
+
+    if styledChild != nil and styledChild.node != nil and styledChild.node.nodeType == ELEMENT_NODE:
+      let elem = Element(styledChild.node)
+      # Add a nil before the last element (in-stack), so we can remove the
+      # stylesheets
+      if elem.sheets.len > 0:
+        author.add(elem.sheets)
+        lenstack.add(elem.sheets.len)
+        styledStack.add((nil, nil, PSEUDO_NONE, nil))
+
+      stack_append styledChild, PSEUDO_AFTER
+
+      for i in countdown(elem.childNodes.high, 0):
+        stack_append styledChild, elem.childNodes[i]
+
+      stack_append styledChild, PSEUDO_BEFORE
+
+proc applyStylesheets*(document: Document, uass, userss: CSSStylesheet, previousStyled: StyledNode): StyledNode =
   let uass = uass.applyMediaQuery()
   let userss = userss.applyMediaQuery()
-  document.applyRules(uass, userss)
+  return document.applyRules(uass, userss, previousStyled)
 
 proc refreshStyle*(elem: Element) =
   elem.cssapplied = false
diff --git a/src/css/stylednode.nim b/src/css/stylednode.nim
new file mode 100644
index 00000000..d6293187
--- /dev/null
+++ b/src/css/stylednode.nim
@@ -0,0 +1,18 @@
+import css/values
+import html/dom
+
+# Container to hold a style and a node.
+# Pseudo elements are implemented using StyledNode objects without nodes.
+type
+  StyledType* = enum
+    STYLED_ELEMENT, STYLED_TEXT
+
+  StyledNode* = ref object
+    case t*: StyledType
+    of STYLED_ELEMENT:
+      pseudo*: PseudoElem
+      computed*: CSSComputedValues
+    of STYLED_TEXT:
+      text*: string
+    node*: Node
+    children*: seq[StyledNode]
diff --git a/src/css/values.nim b/src/css/values.nim
index 73299f9e..e78af603 100644
--- a/src/css/values.nim
+++ b/src/css/values.nim
@@ -110,7 +110,7 @@ type
     of VALUE_DISPLAY:
       display*: CSSDisplay
     of VALUE_CONTENT:
-      content*: seq[Rune]
+      content*: string
     of VALUE_WHITESPACE:
       whitespace*: CSSWhitespace
     of VALUE_INTEGER:
@@ -603,12 +603,12 @@ func cssGlobal*(d: CSSDeclaration): CSSGlobalValueType =
       of "revert": return VALUE_REVERT
   return VALUE_NOGLOBAL
 
-func cssString(d: CSSDeclaration): seq[Rune] =
+func cssString(d: CSSDeclaration): string =
   if isToken(d):
     let tok = getToken(d)
     case tok.tokenType
     of CSS_IDENT_TOKEN, CSS_STRING_TOKEN:
-      return tok.value
+      return $tok.value
     else: return
 
 func cssDisplay(d: CSSDeclaration): CSSDisplay =