about summary refs log blame commit diff stats
path: root/src/css/cascade.nim
blob: abf8ba73f858a33f642a917c500bd4a4384fc37e (plain) (tree)
1
2
3
4
5
6
7
8
9
                
              
            
 
                     
                    
                 
                         
                

                 




                                  
                                                           







                                                                          
                           
                                  
       

                                                        
                                               

                        
 
































                                                  

                                                                 






                                                                      
                      
                                                                     
                                
 
                      





                                              


                                                                     
                          


                                                                        


                              
 



                                                                                                                                 

                                                



                                            

                                                








                                                            
                                                                                                                       

                       

                                

                                  
 

                                     



                                     
                                                                          




                                       






                                       
                                                        



                                               
 














                                                                                                
 

                                                                   
 

                                                              

                                    
                     
 
                         

                          
                                     
 


                          
                               
                   

                                     

              
                           

                               

                                       

                                       






                                                                      

                                              
 
                                                                         

                                       





                                   
import algorithm
import streams
import sugar

import css/mediaquery
import css/cssparser
import css/select
import css/selectorparser
import css/sheet
import css/values
import html/dom

type
  ApplyResult = object
    normal: seq[CSSDeclaration]
    important: seq[CSSDeclaration]
  DeclarationList* = array[PseudoElem, seq[CSSDeclaration]]

proc applyProperty(elem: Element, d: CSSDeclaration, pseudo: PseudoElem) =
  var parent: CSSSpecifiedValues
  if elem.parentElement != nil:
    parent = elem.parentElement.css
  else:
    parent = rootProperties()

  if pseudo == PSEUDO_NONE:
    elem.css.applyValue(parent, d)
  else:
    if elem.pseudo[pseudo] == nil:
      elem.pseudo[pseudo] = elem.css.inheritProperties()
    elem.pseudo[pseudo].applyValue(elem.css, d)

  elem.cssapplied = true

func applies(mq: MediaQuery): bool =
  case mq.t
  of CONDITION_MEDIA:
    case mq.media
    of MEDIA_TYPE_ALL: return true
    of MEDIA_TYPE_PRINT: return false
    of MEDIA_TYPE_SCREEN: return true
    of MEDIA_TYPE_SPEECH: return false
    of MEDIA_TYPE_TTY: return true
    of MEDIA_TYPE_UNKNOWN: return false
  of CONDITION_NOT:
    return not mq.n.applies()
  of CONDITION_AND:
    return mq.anda.applies() and mq.andb.applies()
  of CONDITION_OR:
    return mq.ora.applies() or mq.orb.applies()
  of CONDITION_FEATURE:
    case mq.feature.t
    of FEATURE_COLOR:
      return true #TODO
    of FEATURE_GRID:
      return mq.feature.b
    of FEATURE_HOVER:
      return mq.feature.b
    of FEATURE_PREFERS_COLOR_SCHEME:
      return mq.feature.b

func applies(mqlist: MediaQueryList): bool =
  for mq in mqlist:
    if mq.applies():
      return true
  return false

type ToSorts = array[PseudoElem, seq[(int, seq[CSSDeclaration])]]

proc calcRule(tosorts: var ToSorts, elem: Element, rule: CSSRuleDef) =
  for sel in rule.sels:
    if elem.selectorsMatch(sel):
      let spec = getSpecificity(sel)
      tosorts[sel.pseudo].add((spec,rule.decls))

func calcRules(elem: Element, sheet: CSSStylesheet): DeclarationList =
  var tosorts: ToSorts
  for rule in sheet.gen_rules(elem.tagType, elem.id, elem.classList):
    tosorts.calcRule(elem, rule)

  for i in PseudoElem:
    tosorts[i].sort((x, y) => cmp(x[0], y[0]))
    result[i] = collect(newSeq):
      for item in tosorts[i]:
        for dl in item[1]:
          dl

#TODO couldn't these two procedures be merged?
proc applyNormal(ares: var ApplyResult, decls: seq[CSSDeclaration]) =
  for decl in decls:
    if not decl.important:
      ares.normal.add(decl)

proc applyImportant(ares: var ApplyResult, decls: seq[CSSDeclaration]) =
  for decl in decls:
    if decl.important:
      ares.important.add(decl)

proc checkRendered(element: Element, prev: CSSSpecifiedValues, ppseudo: array[PSEUDO_BEFORE..PSEUDO_AFTER, CSSSpecifiedValues]) =
  if element.rendered:
    for p in PSEUDO_BEFORE..PSEUDO_AFTER:
      if ppseudo[p] != element.pseudo[p] and ppseudo[p] == nil:
        if element.parentElement != nil:
          element.parentElement.rendered = false
        element.rendered = false
        return
    for t in CSSPropertyType:
      if not element.css[t].equals(prev[t]):
        if element.parentElement != nil:
          element.parentElement.rendered = false
        element.rendered = false
        return
    for p in PSEUDO_BEFORE..PSEUDO_AFTER:
      if ppseudo[p] != nil:
        for t in CSSPropertyType:
          if not element.pseudo[p][t].equals(ppseudo[p][t]):
            element.rendered = false
            return

proc applyDeclarations(element: Element, ua, user: DeclarationList, author: seq[DeclarationList], pseudo: PseudoElem) =
  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])

  if pseudo == PSEUDO_NONE:
    let style = element.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])

  for rule in ares.normal:
    element.applyProperty(rule, pseudo)

  for rule in ares.important:
    element.applyProperty(rule, pseudo)

func applyMediaQuery(ss: CSSStylesheet): CSSStylesheet =
  result = ss
  for mq in ss.mq_list:
    if mq.query.applies():
      result.add(mq.children.applyMediaQuery())

proc resetRules(elem: Element) =
  elem.css = if elem.parentElement != nil:
    elem.parentElement.css.inheritProperties()
  else:
    rootProperties()

  for pseudo in PSEUDO_BEFORE..PSEUDO_AFTER:
    elem.pseudo[pseudo] = nil

proc applyRules(elem: Element, ua, user: CSSStylesheet, author: seq[CSSStylesheet]) {.inline.} =
  let uadecls = calcRules(elem, ua)
  let userdecls = calcRules(elem, user)
  var authordecls: seq[DeclarationList]
  for rule in author:
    authordecls.add(calcRules(elem, rule))

  for pseudo in PseudoElem:
    elem.applyDeclarations(uadecls, userdecls, authordecls, pseudo)

proc applyRules(document: Document, ua, user: CSSStylesheet) =
  var author: seq[CSSStylesheet]

  for sheet in document.head.sheets:
    author.add(sheet)

  var stack: seq[Element]

  stack.add(document.root)
  var lenstack = newSeqOfCap[int](15)

  while stack.len > 0:
    let elem = stack.pop()

    # Remove stylesheets on nil
    if elem == 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) =
  let uass = uass.applyMediaQuery()
  let userss = userss.applyMediaQuery()
  document.applyRules(uass, userss)

proc refreshStyle*(elem: Element) =
  elem.cssapplied = false
  for child in elem.children:
    child.refreshStyle()