diff options
Diffstat (limited to 'src/css')
-rw-r--r-- | src/css/cascade.nim | 205 | ||||
-rw-r--r-- | src/css/stylednode.nim | 18 | ||||
-rw-r--r-- | src/css/values.nim | 6 |
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 = |