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

                         
                
                  
                   
 
                                
 
    

                                                                       
                                              
 





                                                                     




                                                                             
                                                                        
                                                                            
                                                                            
                                                                
                                                                         
                                                                       
                                                                    

                                                                       



                                                                              
                                                                  
                                                                
                                                                           
                                    





                                                                           



                                                                              









                                                                              
                   










                                                                              
                                                             

                                     




                                                                               

                                                                        

                                                                 
 


                                                           




                                                                         


                                                      

                                                            

                                                                 
 










                                                                                 
    




                     


                               











                                 
  
                                
                       
                         
                   
                       




                              
                          
                     


                               
                        
                                


                             
                                             

                              

                                      

                                      

                              

                                              

                            

                                  



                                        

                                         

                        

                          
                                                                   
 














                                                                   
                                 
 

                                       



                               

                                     

           
                       
                          









                                              
                                    
                          
                            
                                              



                                            
                                        

                                            
                                    
                                                      
                                                



                                
                            

                                            

                                              



                                          
                                    


                                                

           
                     
                            
                              









                                                  
                                        
                               
                                
                                                  



                                        
                                      

                                                
                                        
                                                          
                                         



                                    
                                

                                            

                                                  




                                              


                                         



                                                            
                                                                      
                                                                        

                                                                           

 

                                                            






                                              


                                                       
                                               

                                                     
                                                     

                         






                                                                     
                                                          
                          
                        
                                                  

                                                                                
                                                              
                          

                      
                                                  

                                                     
                    
                                                                                         
 


                                          

                                                           
 

                                                           
 
                                   

                                                           

                                      

                                                           
 
                                                                          
             



                                               






                                    



                                                                        
 


                                                       


                                          
                                              

                                                                    

                                                                        










                                      
                                                                              
                             

                                                      
    
 
               

















                    
                                                       

                                          

                                                     
 









                                                          
                                                                











                                                                 




                                                               
 














                                                  




                                                                    
                                                          









                                                                  
                                                            















                                                                                       
              









                                                 
                 

                                                    
                                                                         
 






















                                                                                                                     
                                                     
 












                                                           


                                                         

                           
                                            
                       
                               


                                                           
 
                                                                 
 


                                                        
                                        
                    





                                        









































                                                                              
 


                                                      
                                        
                    

                                        
                                              
                                                    








                                                                
                                                      



                                                      


                                                          
                                        
                    





                                                                   


                                                            
                                        
                    







                                                         


                                                  
                                        
                    



                              

                                           

                                                          
                                                                   


                              






                                                                    
 


                                                          
                                        
                    



                                                              
 


                                                                  
                                        
                    




                                                  

                                                          


                                                                      


                                                                  
                                        
                    











                                                                   
                                                      


                                                             


                                                        



                                             
                               

                                    
                                             

                                                          


                                                          
                                        







                                                    

                                                         


                                                                          
                                        


                                                      

                                                                  
                                                        

                            
                                        





                                             


                                                                            
                                                              

                            











                                                           
                                                                    

                            





                                                              




















                                                                       







                                                                
                                                         

                 






                                                                            








                                                                  
                                                                                                              
           
                           


                                                    
       
            
                 
                              
                  

                             
                                       
                            
                                      






                                                                   


                                                                           
         


                                                         
                                               
                                                            

                                     
                                       



                                                
                                                                     






                                                                                  


                                                                        
                             




                                             











                                                                





                                                        

                        
                                                     

                    
                          

                                
       
                          
 

                                                      

                                                                       

                                                              

                                
                                                        
 







                                                 
                                                        
                      
                          

                 
                                                                
                   
                                                              
                      
                                                                   
                  
                                                                  

                                                                    

                                                                    
       
                                     

           
                                                                  
                                     
                              
 
                                    
 

                                                                                   
 
                                                                                                                               
                                                


                                         

                                                    



                            







                                                                                                                        
                                  






                                                      
                                   




                                                      
                                              


                                       
                                    




                                                      





                          



                                        
                                     











                                                                                        













                                                      
                                                       
                                                         
                                                          
                                                          
                       
                                                        
                                                          
                                                           
                                                           


                                                


                                                                                  

                                







                                            

                               

                                      

















                                                                                     
 
                                            



                          


                









                                                                       
                                                                    
                                                        
                                                                                 
                                                    
                                                              

                                                                       

                                                                 
                                           


                            





                                                                                                                



                                                                                                  



                                                                                                             



                                                                                               


                                                                                                       






                                                                                                  
 

                                                                                                                                                           



                                   
             




                                   
                   










                                     
                  



                                       
                    
                    
 







                                                                       
                                                                   

                              
                              





                                           






                                                          
 
                                                                                 
             



                                                          
                                                                     
                                                     




                                                                                  



                                                  
                                                                                             




                                                                                                
                                                                                             




                                                     
                                                                                             




                                                     
                                                                                             



                                                     
                                                                     

                                           
                              




                                                                                   
import tables
import macros
import options
import strutils
import unicode

import css/cssparser
import css/selectorparser
import io/window
import types/color
import utils/twtstr

export selectorparser.PseudoElem

type
  CSSShorthandType = enum
    SHORTHAND_NONE, SHORTHAND_ALL, SHORTHAND_MARGIN, SHORTHAND_PADDING,
    SHORTHAND_BACKGROUND, SHORTHAND_LIST_STYLE

  CSSUnit* = enum
    UNIT_CM, UNIT_MM, UNIT_IN, UNIT_PX, UNIT_PT, UNIT_PC,
    UNIT_EM, UNIT_EX, UNIT_CH, UNIT_REM, UNIT_VW, UNIT_VH, UNIT_VMIN,
    UNIT_VMAX, UNIT_PERC, UNIT_IC

  CSSPropertyType* = enum
    PROPERTY_NONE, PROPERTY_COLOR, PROPERTY_MARGIN_TOP, PROPERTY_MARGIN_LEFT,
    PROPERTY_MARGIN_RIGHT, PROPERTY_MARGIN_BOTTOM, PROPERTY_FONT_STYLE,
    PROPERTY_DISPLAY, PROPERTY_CONTENT, PROPERTY_WHITE_SPACE,
    PROPERTY_FONT_WEIGHT, PROPERTY_TEXT_DECORATION, PROPERTY_WORD_BREAK,
    PROPERTY_WIDTH, PROPERTY_HEIGHT, PROPERTY_LIST_STYLE_TYPE,
    PROPERTY_PADDING_TOP, PROPERTY_PADDING_LEFT, PROPERTY_PADDING_RIGHT,
    PROPERTY_PADDING_BOTTOM, PROPERTY_WORD_SPACING, PROPERTY_VERTICAL_ALIGN,
    PROPERTY_LINE_HEIGHT, PROPERTY_TEXT_ALIGN, PROPERTY_LIST_STYLE_POSITION,
    PROPERTY_BACKGROUND_COLOR, PROPERTY_POSITION, PROPERTY_LEFT,
    PROPERTY_RIGHT, PROPERTY_TOP, PROPERTY_BOTTOM, PROPERTY_CAPTION_SIDE,
    PROPERTY_BORDER_SPACING, PROPERTY_BORDER_COLLAPSE, PROPERTY_QUOTES,
    PROPERTY_COUNTER_RESET, PROPERTY_MAX_WIDTH, PROPERTY_MAX_HEIGHT,
    PROPERTY_MIN_WIDTH, PROPERTY_MIN_HEIGHT, PROPERTY_BACKGROUND_IMAGE,
    PROPERTY_CHA_COLSPAN, PROPERTY_CHA_ROWSPAN

  CSSValueType* = enum
    VALUE_NONE, VALUE_LENGTH, VALUE_COLOR, VALUE_CONTENT, VALUE_DISPLAY,
    VALUE_FONT_STYLE, VALUE_WHITE_SPACE, VALUE_INTEGER, VALUE_TEXT_DECORATION,
    VALUE_WORD_BREAK, VALUE_LIST_STYLE_TYPE, VALUE_VERTICAL_ALIGN,
    VALUE_TEXT_ALIGN, VALUE_LIST_STYLE_POSITION, VALUE_POSITION,
    VALUE_CAPTION_SIDE, VALUE_LENGTH2, VALUE_BORDER_COLLAPSE, VALUE_QUOTES,
    VALUE_COUNTER_RESET, VALUE_IMAGE

  CSSGlobalValueType* = enum
    VALUE_NOGLOBAL, VALUE_INITIAL, VALUE_INHERIT, VALUE_REVERT, VALUE_UNSET

  CSSDisplay* = enum
    DISPLAY_NONE, DISPLAY_INLINE, DISPLAY_BLOCK, DISPLAY_LIST_ITEM,
    DISPLAY_INLINE_BLOCK, DISPLAY_TABLE, DISPLAY_INLINE_TABLE,
    DISPLAY_TABLE_ROW_GROUP, DISPLAY_TABLE_HEADER_GROUP,
    DISPLAY_TABLE_FOOTER_GROUP, DISPLAY_TABLE_COLUMN_GROUP, DISPLAY_TABLE_ROW,
    DISPLAY_TABLE_COLUMN, DISPLAY_TABLE_CELL, DISPLAY_TABLE_CAPTION

  CSSWhitespace* = enum
    WHITESPACE_NORMAL, WHITESPACE_NOWRAP, WHITESPACE_PRE, WHITESPACE_PRE_LINE,
    WHITESPACE_PRE_WRAP

  CSSFontStyle* = enum
    FONTSTYLE_NORMAL, FONTSTYLE_ITALIC, FONTSTYLE_OBLIQUE

  CSSPosition* = enum
    POSITION_STATIC, POSITION_RELATIVE, POSITION_ABSOLUTE, POSITION_FIXED,
    POSITION_STICKY

  CSSTextDecoration* = enum
    TEXT_DECORATION_NONE, TEXT_DECORATION_UNDERLINE, TEXT_DECORATION_OVERLINE,
    TEXT_DECORATION_LINE_THROUGH, TEXT_DECORATION_BLINK

  CSSWordBreak* = enum
    WORD_BREAK_NORMAL, WORD_BREAK_BREAK_ALL, WORD_BREAK_KEEP_ALL

  CSSListStyleType* = enum
    LIST_STYLE_TYPE_NONE, LIST_STYLE_TYPE_DISC, LIST_STYLE_TYPE_CIRCLE,
    LIST_STYLE_TYPE_SQUARE, LIST_STYLE_TYPE_DECIMAL,
    LIST_STYLE_TYPE_LOWER_ROMAN, LIST_STYLE_TYPE_UPPER_ROMAN,
    LIST_STYLE_TYPE_JAPANESE_INFORMAL

  CSSVerticalAlign2* = enum
    VERTICAL_ALIGN_BASELINE, VERTICAL_ALIGN_SUB, VERTICAL_ALIGN_SUPER,
    VERTICAL_ALIGN_TEXT_TOP, VERTICAL_ALIGN_TEXT_BOTTOM, VERTICAL_ALIGN_MIDDLE,
    VERTICAL_ALIGN_TOP, VERTICAL_ALIGN_BOTTOM

  CSSTextAlign* = enum
    TEXT_ALIGN_START, TEXT_ALIGN_END, TEXT_ALIGN_LEFT, TEXT_ALIGN_RIGHT,
    TEXT_ALIGN_CENTER, TEXT_ALIGN_JUSTIFY, TEXT_ALIGN_CHA_CENTER,
    TEXT_ALIGN_CHA_LEFT, TEXT_ALIGN_CHA_RIGHT

  CSSListStylePosition* = enum
    LIST_STYLE_POSITION_OUTSIDE, LIST_STYLE_POSITION_INSIDE

  CSSCaptionSide* = enum
    CAPTION_SIDE_TOP, CAPTION_SIDE_BOTTOM, CAPTION_SIDE_LEFT,
    CAPTION_SIDE_RIGHT, CAPTION_SIDE_BLOCK_START, CAPTION_SIDE_BLOCK_END,
    CAPTION_SIDE_INLINE_START, CAPTION_SIDE_INLINE_END

  CSSBorderCollapse* = enum
    BORDER_COLLAPSE_SEPARATE, BORDER_COLLAPSE_COLLAPSE

  CSSContentType* = enum
    CONTENT_STRING, CONTENT_OPEN_QUOTE, CONTENT_CLOSE_QUOTE,
    CONTENT_NO_OPEN_QUOTE, CONTENT_NO_CLOSE_QUOTE, CONTENT_IMAGE,
    CONTENT_NEWLINE

const RowGroupBox* = {DISPLAY_TABLE_ROW_GROUP, DISPLAY_TABLE_HEADER_GROUP,
                      DISPLAY_TABLE_FOOTER_GROUP}
const ProperTableChild* = {DISPLAY_TABLE_ROW, DISPLAY_TABLE_COLUMN,
                           DISPLAY_TABLE_COLUMN_GROUP, DISPLAY_TABLE_CAPTION} +
                           RowGroupBox
const ProperTableRowParent* = {DISPLAY_TABLE, DISPLAY_INLINE_TABLE} + RowGroupBox
const InternalTableBox* = {DISPLAY_TABLE_CELL, DISPLAY_TABLE_ROW,
                           DISPLAY_TABLE_COLUMN, DISPLAY_TABLE_COLUMN_GROUP} +
                           RowGroupBox
const TabularContainer* = {DISPLAY_TABLE_ROW} + ProperTableRowParent

type
  CSSLength* = object
    num*: float64
    unit*: CSSUnit
    auto*: bool

  CSSVerticalAlign* = object
    length*: CSSLength
    keyword*: CSSVerticalAlign2

  CSSContent* = object
    t*: CSSContentType
    s*: string

  CSSQuotes* = object
    auto*: bool
    qs*: seq[tuple[s, e: string]]

  CSSCounterReset* = object
    name*: string
    num*: int
  
  CSSComputedValue* = ref object
    t*: CSSPropertyType
    case v*: CSSValueType
    of VALUE_COLOR:
      color*: RGBAColor
    of VALUE_LENGTH:
      length*: CSSLength
    of VALUE_FONT_STYLE:
      fontstyle*: CSSFontStyle
    of VALUE_DISPLAY:
      display*: CSSDisplay
    of VALUE_CONTENT:
      content*: seq[CSSContent]
    of VALUE_QUOTES:
      quotes*: CSSQuotes
    of VALUE_WHITESPACE:
      whitespace*: CSSWhitespace
    of VALUE_INTEGER:
      integer*: int
    of VALUE_TEXT_DECORATION:
      textdecoration*: set[CSSTextDecoration]
    of VALUE_WORD_BREAK:
      wordbreak*: CSSWordBreak
    of VALUE_LIST_STYLE_TYPE:
      liststyletype*: CSSListStyleType
    of VALUE_VERTICAL_ALIGN:
      verticalalign*: CSSVerticalAlign
    of VALUE_TEXT_ALIGN:
      textalign*: CSSTextAlign
    of VALUE_LIST_STYLE_POSITION:
      liststyleposition*: CSSListStylePosition
    of VALUE_POSITION:
      position*: CSSPosition
    of VALUE_CAPTION_SIDE:
      captionside*: CSSCaptionSide
    of VALUE_LENGTH2:
      length2*: tuple[a, b: CSSLength]
    of VALUE_BORDER_COLLAPSE:
      bordercollapse*: CSSBorderCollapse
    of VALUE_COUNTER_RESET:
      counterreset*: seq[CSSCounterReset]
    of VALUE_IMAGE:
      image*: CSSContent
    of VALUE_NONE: discard

  CSSComputedValues* = ref array[CSSPropertyType, CSSComputedValue]

  CSSOrigin* = enum
    ORIGIN_USER_AGENT
    ORIGIN_USER
    ORIGIN_AUTHOR

  CSSComputedValueBuilder = object
    global: CSSGlobalValueType
    val: CSSComputedValue

  CSSComputedValueBuilders = seq[CSSComputedValueBuilder]

  CSSComputedValuesBuilder* = object
    parent: CSSComputedValues
    normalProperties: array[CSSOrigin, CSSComputedValueBuilders]
    importantProperties: array[CSSOrigin, CSSComputedValueBuilders]
    preshints*: CSSComputedValues

  CSSValueError* = object of ValueError

const ShorthandNames = {
  "all": SHORTHAND_ALL,
  "margin": SHORTHAND_MARGIN,
  "padding": SHORTHAND_PADDING,
  "background": SHORTHAND_BACKGROUND,
  "list-style": SHORTHAND_LIST_STYLE
}.toTable()

const PropertyNames = {
  "color": PROPERTY_COLOR,
  "margin-top": PROPERTY_MARGIN_TOP,
  "margin-bottom": PROPERTY_MARGIN_BOTTOM,
  "margin-left": PROPERTY_MARGIN_LEFT,
  "margin-right": PROPERTY_MARGIN_RIGHT,
  "font-style": PROPERTY_FONT_STYLE,
  "display": PROPERTY_DISPLAY,
  "content": PROPERTY_CONTENT,
  "white-space": PROPERTY_WHITE_SPACE,
  "font-weight": PROPERTY_FONT_WEIGHT,
  "text-decoration": PROPERTY_TEXT_DECORATION,
  "word-break": PROPERTY_WORD_BREAK,
  "width": PROPERTY_WIDTH,
  "height": PROPERTY_HEIGHT,
  "list-style-type": PROPERTY_LIST_STYLE_TYPE,
  "padding-top": PROPERTY_PADDING_TOP,
  "padding-bottom": PROPERTY_PADDING_BOTTOM,
  "padding-left": PROPERTY_PADDING_LEFT,
  "padding-right": PROPERTY_PADDING_RIGHT,
  "word-spacing": PROPERTY_WORD_SPACING,
  "vertical-align": PROPERTY_VERTICAL_ALIGN,
  "line-height": PROPERTY_LINE_HEIGHT,
  "text-align": PROPERTY_TEXT_ALIGN,
  "list-style-position": PROPERTY_LIST_STYLE_POSITION,
  "background-color": PROPERTY_BACKGROUND_COLOR,
  "position": PROPERTY_POSITION,
  "left": PROPERTY_LEFT,
  "right": PROPERTY_RIGHT,
  "top": PROPERTY_TOP,
  "bottom": PROPERTY_BOTTOM,
  "caption-side": PROPERTY_CAPTION_SIDE,
  "border-spacing": PROPERTY_BORDER_SPACING,
  "border-collapse": PROPERTY_BORDER_COLLAPSE,
  "quotes": PROPERTY_QUOTES,
  "counter-reset": PROPERTY_COUNTER_RESET,
  "max-width": PROPERTY_MAX_WIDTH,
  "max-height": PROPERTY_MAX_HEIGHT,
  "min-width": PROPERTY_MIN_WIDTH,
  "min-height": PROPERTY_MIN_HEIGHT,
  "background-image": PROPERTY_BACKGROUND_IMAGE,
  "-cha-colspan": PROPERTY_CHA_COLSPAN,
  "-cha-rowspan": PROPERTY_CHA_ROWSPAN
}.toTable()

const ValueTypes* = [
  PROPERTY_NONE: VALUE_NONE,
  PROPERTY_COLOR: VALUE_COLOR,
  PROPERTY_MARGIN_TOP: VALUE_LENGTH,
  PROPERTY_MARGIN_LEFT: VALUE_LENGTH,
  PROPERTY_MARGIN_RIGHT: VALUE_LENGTH,
  PROPERTY_MARGIN_BOTTOM: VALUE_LENGTH,
  PROPERTY_FONT_STYLE: VALUE_FONT_STYLE,
  PROPERTY_DISPLAY: VALUE_DISPLAY,
  PROPERTY_CONTENT: VALUE_CONTENT,
  PROPERTY_WHITE_SPACE: VALUE_WHITE_SPACE,
  PROPERTY_FONT_WEIGHT: VALUE_INTEGER,
  PROPERTY_TEXT_DECORATION: VALUE_TEXT_DECORATION,
  PROPERTY_WORD_BREAK: VALUE_WORD_BREAK,
  PROPERTY_WIDTH: VALUE_LENGTH,
  PROPERTY_HEIGHT: VALUE_LENGTH,
  PROPERTY_LIST_STYLE_TYPE: VALUE_LIST_STYLE_TYPE,
  PROPERTY_PADDING_TOP: VALUE_LENGTH,
  PROPERTY_PADDING_LEFT: VALUE_LENGTH,
  PROPERTY_PADDING_RIGHT: VALUE_LENGTH,
  PROPERTY_PADDING_BOTTOM: VALUE_LENGTH,
  PROPERTY_WORD_SPACING: VALUE_LENGTH,
  PROPERTY_VERTICAL_ALIGN: VALUE_VERTICAL_ALIGN,
  PROPERTY_LINE_HEIGHT: VALUE_LENGTH,
  PROPERTY_TEXT_ALIGN: VALUE_TEXT_ALIGN,
  PROPERTY_LIST_STYLE_POSITION: VALUE_LIST_STYLE_POSITION,
  PROPERTY_BACKGROUND_COLOR: VALUE_COLOR,
  PROPERTY_POSITION: VALUE_POSITION,
  PROPERTY_LEFT: VALUE_LENGTH,
  PROPERTY_RIGHT: VALUE_LENGTH,
  PROPERTY_TOP: VALUE_LENGTH,
  PROPERTY_BOTTOM: VALUE_LENGTH,
  PROPERTY_CAPTION_SIDE: VALUE_CAPTION_SIDE,
  PROPERTY_BORDER_SPACING: VALUE_LENGTH2,
  PROPERTY_BORDER_COLLAPSE: VALUE_BORDER_COLLAPSE,
  PROPERTY_QUOTES: VALUE_QUOTES,
  PROPERTY_COUNTER_RESET: VALUE_COUNTER_RESET,
  PROPERTY_MAX_WIDTH: VALUE_LENGTH,
  PROPERTY_MAX_HEIGHT: VALUE_LENGTH,
  PROPERTY_MIN_WIDTH: VALUE_LENGTH,
  PROPERTY_MIN_HEIGHT: VALUE_LENGTH,
  PROPERTY_BACKGROUND_IMAGE: VALUE_IMAGE,
  PROPERTY_CHA_COLSPAN: VALUE_INTEGER,
  PROPERTY_CHA_ROWSPAN: VALUE_INTEGER
]

const InheritedProperties = {
  PROPERTY_COLOR, PROPERTY_FONT_STYLE, PROPERTY_WHITE_SPACE,
  PROPERTY_FONT_WEIGHT, PROPERTY_TEXT_DECORATION, PROPERTY_WORD_BREAK,
  PROPERTY_LIST_STYLE_TYPE, PROPERTY_WORD_SPACING, PROPERTY_LINE_HEIGHT,
  PROPERTY_TEXT_ALIGN, PROPERTY_LIST_STYLE_POSITION, PROPERTY_CAPTION_SIDE,
  PROPERTY_BORDER_SPACING, PROPERTY_BORDER_COLLAPSE, PROPERTY_QUOTES
}

func getPropInheritedArray(): array[CSSPropertyType, bool] =
  for prop in CSSPropertyType:
    if prop in InheritedProperties:
      result[prop] = true
    else:
      result[prop] = false

const InheritedArray = getPropInheritedArray()

func shorthandType(s: string): CSSShorthandType =
  return ShorthandNames.getOrDefault(s, SHORTHAND_NONE)

func propertyType(s: string): CSSPropertyType =
  return PropertyNames.getOrDefault(s, PROPERTY_NONE)

func valueType(prop: CSSPropertyType): CSSValueType =
  return ValueTypes[prop]

func `$`*(val: CSSComputedValue): string =
  result = ($val.t).toLowerAscii().split('_')[1..^1].join('-') & ": "
  case val.v
  of VALUE_COLOR:
    result &= $val.color
  else: discard

macro `{}`*(vals: CSSComputedValues, s: string): untyped =
  let t = propertyType($s)
  let vs = $valueType(t)
  let s = vs.split('_')[1..^1].join("_").tolower()
  result = newDotExpr(newTree(nnkBracketExpr, vals, newLit(t)), newIdentNode(s))

macro `{}=`*(vals: CSSComputedValues, s: string, val: typed) =
  let t = propertyType($s)
  let v = valueType(t)
  let vs = $v
  let s = vs.split('_')[1..^1].join("_").tolower()
  let id = ident(s)
  let expr = newTree(nnkBracketExpr, vals, newLit(t))
  result = quote do:
    `expr` = CSSComputedValue(t: CSSPropertyType(`t`), v: CSSValueType(`v`), `id`: `val`)

func inherited(t: CSSPropertyType): bool =
  return InheritedArray[t]

func em_to_px(em: float64, window: WindowAttributes): int =
  int(em * float64(window.ppl))

func ch_to_px(ch: float64, window: WindowAttributes): int =
  int(ch * float64(window.ppc))

# 水 width, we assume it's 2 chars
func ic_to_px(ic: float64, window: WindowAttributes): int =
  int(ic * float64(window.ppc) * 2)

# x-letter height, we assume it's em/2
func ex_to_px(ex: float64, window: WindowAttributes): int =
  int(ex * float64(window.ppc) / 2)

func px*(l: CSSLength, window: WindowAttributes, p: int): int {.inline.} =
  case l.unit
  of UNIT_EM, UNIT_REM: em_to_px(l.num, window)
  of UNIT_CH: ch_to_px(l.num, window)
  of UNIT_IC: ic_to_px(l.num, window)
  of UNIT_EX: ex_to_px(l.num, window)
  of UNIT_PERC: int(p / 100 * l.num)
  of UNIT_PX: int(l.num)
  of UNIT_CM: int(l.num * 37.8)
  of UNIT_MM: int(l.num * 3.78)
  of UNIT_IN: int(l.num * 96)
  of UNIT_PC: int(l.num * 96 / 6)
  of UNIT_PT: int(l.num * 96 / 72)
  of UNIT_VW: int(window.width_px / 100 * l.num)
  of UNIT_VH: int(window.height_px / 100 * l.num)
  of UNIT_VMIN: int(min(window.width_px, window.width_px) / 100 * l.num)
  of UNIT_VMAX: int(max(window.width_px, window.width_px) / 100 * l.num)

func listMarker*(t: CSSListStyleType, i: int): string =
  case t
  of LIST_STYLE_TYPE_NONE: return ""
  of LIST_STYLE_TYPE_DISC: return "• "
  of LIST_STYLE_TYPE_CIRCLE: return "○ "
  of LIST_STYLE_TYPE_SQUARE: return "□ "
  of LIST_STYLE_TYPE_DECIMAL: return $i & ". "
  of LIST_STYLE_TYPE_UPPER_ROMAN: return romanNumber(i) & ". "
  of LIST_STYLE_TYPE_LOWER_ROMAN: return romanNumber_lower(i) & ". "
  of LIST_STYLE_TYPE_JAPANESE_INFORMAL: return japaneseNumber(i) & "、"

#TODO this should change by language
func quoteStart*(level: int): string =
  if level == 0:
    return "“"
  return "‘"

func quoteEnd*(level: int): string =
  if level == 0:
    return "“"
  return "‘"

const Colors: Table[string, RGBAColor] = ((func (): Table[string, RGBAColor] =
  for name, rgb in ColorsRGB:
    result[name] = rgb
  result["transparent"] = rgba(0x00, 0x00, 0x00, 0x00)
)())

const Units = {
  "%": UNIT_PERC,
  "cm": UNIT_CM,
  "mm": UNIT_MM,
  "in": UNIT_IN,
  "px": UNIT_PX,
  "pt": UNIT_PT,
  "pc": UNIT_PC,
  "em": UNIT_EM,
  "ex": UNIT_EX,
  "ch": UNIT_CH,
  "ic": UNIT_CH,
  "rem": UNIT_REM,
  "vw": UNIT_VW,
  "vh": UNIT_VH,
  "vmin": UNIT_VMIN,
  "vmax": UNIT_VMAX,
}.toTable()

func cssLength(val: float64, unit: string): CSSLength =
  if unit in Units:
    CSSLength(num: val, unit: Units[unit])
  else:
    raise newException(CSSValueError, "Invalid unit")

func parseDimensionValues*(s: string): Option[CSSLength] =
  if s == "": return
  var i = 0
  while s[i] in AsciiWhitespace: inc i
  if i >= s.len or s[i] notin AsciiDigit: return
  var n: float64
  while s[i] in AsciiDigit:
    n *= 10
    n += float64(decValue(s[i]))
    inc i
    if i >= s.len: return some(CSSLength(num: n, unit: UNIT_PX))
  if s[i] == '.':
    inc i
    if i >= s.len: return some(CSSLength(num: n, unit: UNIT_PX))
    var d = 1
    while i < s.len and s[i] in AsciiDigit:
      n += float64(decValue(s[i])) / float64(d)
      inc d
      inc i
  if i >= s.len: return some(CSSLength(num: n, unit: UNIT_PX))
  if s[i] == '%': return some(CSSLength(num: n, unit: UNIT_PERC))
  return some(CSSLength(num: n, unit: UNIT_PX))

func skipWhitespace(vals: seq[CSSComponentValue], i: var int) =
  while i < vals.len:
    if vals[i] != CSS_WHITESPACE_TOKEN:
      break
    inc i

func cssColor(val: CSSComponentValue): RGBAColor =
  if val of CSSToken:
    let tok = CSSToken(val)
    case tok.tokenType
    of CSS_HASH_TOKEN:
      let c = parseHexColor(tok.value)
      if c.isSome:
        return c.get
    of CSS_IDENT_TOKEN:
      let s = tok.value
      if s in Colors:
        return Colors[s]
    else: discard
  elif val of CSSFunction:
    let f = CSSFunction(val)
    var i = 0
    var commaMode = false
    template check_err =
      #TODO calc, percentages, etc (cssnumber function or something)
      if i >= f.value.len or f.value[i] != CSS_NUMBER_TOKEN:
        raise newException(CSSValueError, "Invalid color")
    template next_value(first = false, slash = false) =
      inc i
      f.value.skipWhitespace(i)
      if i < f.value.len:
        if f.value[i] == CSS_COMMA_TOKEN and (commaMode or first):
          # legacy compatibility
          inc i
          f.value.skipWhitespace(i)
          commaMode = true
        elif commaMode:
          raise newException(CSSValueError, "Invalid color")
        if slash:
          if f.value[i] != CSS_DELIM_TOKEN or CSSToken(f.value[i]).rvalue != Rune('/'):
            raise newException(CSSValueError, "Invalid color")
          inc i
          f.value.skipWhitespace(i)
      check_err
    case f.name
    of "rgb":
      f.value.skipWhitespace(i)
      check_err
      let r = CSSToken(f.value[i]).nvalue
      next_value true
      let g = CSSToken(f.value[i]).nvalue
      next_value
      let b = CSSToken(f.value[i]).nvalue
      return rgba(int(r), int(g), int(b), 255)
    of "rgba":
      f.value.skipWhitespace(i)
      check_err
      let r = CSSToken(f.value[i]).nvalue
      next_value true
      let g = CSSToken(f.value[i]).nvalue
      next_value
      let b = CSSToken(f.value[i]).nvalue
      next_value false, true
      let a = CSSToken(f.value[i]).nvalue
      return rgba(int(r), int(g), int(b), int(a))
    else: discard
  raise newException(CSSValueError, "Invalid color")

func isToken(cval: CSSComponentValue): bool {.inline.} = cval of CSSToken

func cssLength(val: CSSComponentValue, has_auto: static bool = true, allow_negative: static bool = true): CSSLength =
  block nofail:
    if val of CSSToken:
      let tok = CSSToken(val)
      case tok.tokenType
      of CSS_NUMBER_TOKEN:
        if tok.nvalue == 0:
          return CSSLength(num: 0, unit: UNIT_PX)
      of CSS_PERCENTAGE_TOKEN:
        when not allow_negative:
          if tok.nvalue < 0:
            break nofail
        return cssLength(tok.nvalue, "%")
      of CSS_DIMENSION_TOKEN:
        when not allow_negative:
          if tok.nvalue < 0:
            break nofail
        return cssLength(tok.nvalue, tok.unit)
      of CSS_IDENT_TOKEN:
        when has_auto:
          if tok.value == "auto":
            return CSSLength(auto: true)
      else: discard
  raise newException(CSSValueError, "Invalid length")

func cssAbsoluteLength(val: CSSComponentValue): CSSLength =
  if val of CSSToken:
    let tok = CSSToken(val)
    case tok.tokenType
    of CSS_NUMBER_TOKEN:
      if tok.nvalue == 0:
        return CSSLength(num: 0, unit: UNIT_PX)
    of CSS_DIMENSION_TOKEN:
      if tok.nvalue >= 0:
        return cssLength(tok.nvalue, tok.unit)
    else: discard
  raise newException(CSSValueError, "Invalid length")

func cssWordSpacing(cval: CSSComponentValue): CSSLength =
  if cval of CSSToken:
    let tok = CSSToken(cval)
    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 getToken(cval: CSSComponentValue): CSSToken = (CSSToken)cval

func cssGlobal*(d: CSSDeclaration): CSSGlobalValueType =
  if d.value.len > 0 and isToken(d.value[0]):
    let tok = getToken(d.value[0])
    if tok.tokenType == CSS_IDENT_TOKEN:
      case tok.value
      of "inherit": return VALUE_INHERIT
      of "initial": return VALUE_INITIAL
      of "unset": return VALUE_UNSET
      of "revert": return VALUE_REVERT
  return VALUE_NOGLOBAL

func cssQuotes(d: CSSDeclaration): Option[CSSQuotes] =
  var res: CSSQuotes
  var sa = false
  var pair: tuple[s, e: string]
  for cval in d.value:
    if res.auto: return none(CSSQuotes)
    if isToken(cval):
      let tok = getToken(cval)
      case tok.tokenType
      of CSS_IDENT_TOKEN:
        if res.qs.len > 0: return none(CSSQuotes)
        case tok.value
        of "auto": res.auto = true
        of "none": return none(CSSQuotes)
      of CSS_STRING_TOKEN:
        if sa:
          pair.e = tok.value
          res.qs.add(pair)
          sa = false
        else:
          pair.s = tok.value
          sa = true
      of CSS_WHITESPACE_TOKEN: discard
      else: return none(CSSQuotes)
  if not sa:
    return some(res)

func cssContent(d: CSSDeclaration): seq[CSSContent] =
  for cval in d.value:
    if isToken(cval):
      let tok = getToken(cval)
      case tok.tokenType
      of CSS_IDENT_TOKEN:
        case tok.value
        of "/": break
        of "open-quote": result.add(CSSContent(t: CONTENT_OPEN_QUOTE))
        of "no-open-quote": result.add(CSSContent(t: CONTENT_NO_OPEN_QUOTE))
        of "close-quote": result.add(CSSContent(t: CONTENT_CLOSE_QUOTE))
        of "no-close-quote": result.add(CSSContent(t: CONTENT_NO_CLOSE_QUOTE))
      of CSS_STRING_TOKEN:
        result.add(CSSContent(t: CONTENT_STRING, s: tok.value))
      else: return

func cssDisplay(cval: CSSComponentValue): CSSDisplay =
  if isToken(cval):
    let tok = getToken(cval)
    if tok.tokenType == CSS_IDENT_TOKEN:
      case tok.value
      of "block": return DISPLAY_BLOCK
      of "inline": return DISPLAY_INLINE
      of "list-item": return DISPLAY_LIST_ITEM
      of "inline-block": return DISPLAY_INLINE_BLOCK
      of "table": return DISPLAY_TABLE
      of "table-row": return DISPLAY_TABLE_ROW
      of "table-cell": return DISPLAY_TABLE_CELL
      of "table-column": return DISPLAY_TABLE_COLUMN
      of "table-column-group": return DISPLAY_TABLE_COLUMN_GROUP
      of "inline-table": return DISPLAY_INLINE_TABLE
      of "table-row-group": return DISPLAY_TABLE_ROW_GROUP
      of "table-header-group": return DISPLAY_TABLE_HEADER_GROUP
      of "table-footer-group": return DISPLAY_TABLE_FOOTER_GROUP
      of "table-caption": return DISPLAY_TABLE_CAPTION
      of "none": return DISPLAY_NONE
      else: return DISPLAY_INLINE
  raise newException(CSSValueError, "Invalid display")

func cssFontStyle(cval: CSSComponentValue): CSSFontStyle =
  if isToken(cval):
    let tok = getToken(cval)
    if tok.tokenType == CSS_IDENT_TOKEN:
      case tok.value
      of "normal": return FONTSTYLE_NORMAL
      of "italic": return FONTSTYLE_ITALIC
      of "oblique": return FONTSTYLE_OBLIQUE
      else: raise newException(CSSValueError, "Invalid font style")
  raise newException(CSSValueError, "Invalid font style")

func cssWhiteSpace(cval: CSSComponentValue): CSSWhitespace =
  if isToken(cval):
    let tok = getToken(cval)
    if tok.tokenType == CSS_IDENT_TOKEN:
      case tok.value
      of "normal": return WHITESPACE_NORMAL
      of "nowrap": return WHITESPACE_NOWRAP
      of "pre": return WHITESPACE_PRE
      of "pre-line": return WHITESPACE_PRE_LINE
      of "pre-wrap": return WHITESPACE_PRE_WRAP
      else: return WHITESPACE_NORMAL
  raise newException(CSSValueError, "Invalid whitespace")

func cssFontWeight(cval: CSSComponentValue): int =
  if isToken(cval):
    let tok = getToken(cval)
    if tok.tokenType == CSS_IDENT_TOKEN:
      case tok.value
      of "normal": return 400
      of "bold": return 700
      of "lighter": return 400
      of "bolder": return 700
    elif tok.tokenType == CSS_NUMBER_TOKEN:
      return int(tok.nvalue)
  raise newException(CSSValueError, "Invalid font weight")

func cssTextDecoration(d: CSSDeclaration): set[CSSTextDecoration] =
  for cval in d.value:
    if isToken(cval):
      let tok = getToken(cval)
      if tok.tokenType == CSS_IDENT_TOKEN:
        case tok.value
        of "none": result.incl(TEXT_DECORATION_NONE)
        of "underline": result.incl(TEXT_DECORATION_UNDERLINE)
        of "overline": result.incl(TEXT_DECORATION_OVERLINE)
        of "line-through": result.incl(TEXT_DECORATION_LINE_THROUGH)
        of "blink": result.incl(TEXT_DECORATION_BLINK)

func cssWordBreak(cval: CSSComponentValue): CSSWordBreak =
  if isToken(cval):
    let tok = getToken(cval)
    if tok.tokenType == CSS_IDENT_TOKEN:
      case tok.value
      of "normal": return WORD_BREAK_NORMAL
      of "break-all": return WORD_BREAK_BREAK_ALL
      of "keep-all": return WORD_BREAK_KEEP_ALL
  raise newException(CSSValueError, "Invalid text decoration")

func cssListStyleType(cval: CSSComponentValue): CSSListStyleType =
  if isToken(cval):
    let tok = getToken(cval)
    if tok.tokenType == CSS_IDENT_TOKEN:
      case tok.value
      of "none": return LIST_STYLE_TYPE_NONE
      of "disc": return LIST_STYLE_TYPE_DISC
      of "circle": return LIST_STYLE_TYPE_CIRCLE
      of "square": return LIST_STYLE_TYPE_SQUARE
      of "decimal": return LIST_STYLE_TYPE_DECIMAL
      of "upper-roman": return LIST_STYLE_TYPE_UPPER_ROMAN
      of "lower-roman": return LIST_STYLE_TYPE_LOWER_ROMAN
      of "japanese-informal": return LIST_STYLE_TYPE_JAPANESE_INFORMAL
  raise newException(CSSValueError, "Invalid list style")

func cssVerticalAlign(cval: CSSComponentValue): CSSVerticalAlign =
  if isToken(cval):
    let tok = getToken(cval)
    if tok.tokenType == CSS_IDENT_TOKEN:
      case tok.value
      of "baseline": result.keyword = VERTICAL_ALIGN_BASELINE
      of "sub": result.keyword = VERTICAL_ALIGN_SUB
      of "super": result.keyword = VERTICAL_ALIGN_SUPER
      of "text-top": result.keyword = VERTICAL_ALIGN_TEXT_BOTTOM
      of "middle": result.keyword = VERTICAL_ALIGN_MIDDLE
      of "top": result.keyword = VERTICAL_ALIGN_TOP
      of "bottom": result.keyword = VERTICAL_ALIGN_BOTTOM
      else:
        raise newException(CSSValueError, "Invalid vertical align")
      return result
    else:
      result.keyword = VERTICAL_ALIGN_BASELINE
      result.length = cssLength(tok, has_auto = false)
      return result
  raise newException(CSSValueError, "Invalid vertical align")

func cssLineHeight(cval: CSSComponentValue): CSSLength =
  if cval of CSSToken:
    let tok = CSSToken(cval)
    case tok.tokenType
    of CSS_NUMBER_TOKEN:
      return cssLength(tok.nvalue * 100, "%")
    of CSS_IDENT_TOKEN:
      if tok.value == "normal":
        return CSSLength(auto: true)
    else:
      return cssLength(tok, has_auto = false)
  raise newException(CSSValueError, "Invalid line height")

func cssTextAlign(cval: CSSComponentValue): CSSTextAlign =
  if isToken(cval):
    let tok = getToken(cval)
    if tok.tokenType == CSS_IDENT_TOKEN:
      case tok.value
      of "start": return TEXT_ALIGN_START
      of "end": return TEXT_ALIGN_END
      of "left": return TEXT_ALIGN_LEFT
      of "right": return TEXT_ALIGN_RIGHT
      of "center": return TEXT_ALIGN_CENTER
      of "justify": return TEXT_ALIGN_JUSTIFY
      of "-cha-center": return TEXT_ALIGN_CHA_CENTER
  raise newException(CSSValueError, "Invalid text align")

func cssListStylePosition(cval: CSSComponentValue): CSSListStylePosition =
  if isToken(cval):
    let tok = getToken(cval)
    if tok.tokenType == CSS_IDENT_TOKEN:
      case tok.value
      of "outside": return LIST_STYLE_POSITION_OUTSIDE
      of "inside": return LIST_STYLE_POSITION_INSIDE
  raise newException(CSSValueError, "Invalid list style position")

func cssPosition(cval: CSSComponentValue): CSSPosition =
  if isToken(cval):
    let tok = getToken(cval)
    if tok.tokenType == CSS_IDENT_TOKEN:
      case tok.value
      of "static": return POSITION_STATIC
      of "relative": return POSITION_RELATIVE
      of "absolute": return POSITION_ABSOLUTE
      of "fixed": return POSITION_FIXED
      of "sticky": return POSITION_STICKY
      else: raise newException(CSSValueError, "Invalid list style position")
  raise newException(CSSValueError, "Invalid list style position")

func cssCaptionSide(cval: CSSComponentValue): CSSCaptionSide =
  if isToken(cval):
    let tok = getToken(cval)
    if tok.tokenType == CSS_IDENT_TOKEN:
      case tok.value
      of "top": return CAPTION_SIDE_TOP
      of "bottom": return CAPTION_SIDE_BOTTOM
      of "left": return CAPTION_SIDE_LEFT
      of "right": return CAPTION_SIDE_RIGHT
      of "block-start": return CAPTION_SIDE_BLOCK_START
      of "block-end": return CAPTION_SIDE_BLOCK_END
      of "inline-start": return CAPTION_SIDE_INLINE_START
      of "inline-end": return CAPTION_SIDE_INLINE_END
  raise newException(CSSValueError, "Invalid caption side")

func cssBorderCollapse(cval: CSSComponentValue): CSSBorderCollapse =
  if isToken(cval):
    let tok = getToken(cval)
    if tok.tokenType == CSS_IDENT_TOKEN:
      case tok.value
      of "collapse": return BORDER_COLLAPSE_COLLAPSE
      of "separate": return BORDER_COLLAPSE_SEPARATE
  raise newException(CSSValueError, "Invalid border collapse")

func cssCounterReset(d: CSSDeclaration): Option[seq[CSSCounterReset]] =
  var res: seq[CSSCounterReset]
  var r: CSSCounterReset
  var s = false
  for cval in d.value:
    if isToken(cval):
      let tok = getToken(cval)
      case tok.tokenType
      of CSS_WHITESPACE_TOKEN: discard
      of CSS_IDENT_TOKEN:
        if s: return
        r.name = tok.value
        s = true
      of CSS_NUMBER_TOKEN:
        if not s: return
        r.num = int(tok.nvalue)
        res.add(r)
        s = false
      else: return
  return some(res)

func cssMaxMinSize(cval: CSSComponentValue): Option[CSSLength] =
  if isToken(cval):
    let tok = getToken(cval)
    case tok.tokenType
    of CSS_IDENT_TOKEN:
      if tok.value == "none":
        return some(CSSLength(auto: true))
    of CSS_NUMBER_TOKEN, CSS_DIMENSION_TOKEN:
      return some(cssLength(tok, allow_negative = false))
    else: discard

#TODO this should be a separate type
func cssImage(cval: CSSComponentValue): Option[CSSContent] =
  if isToken(cval):
    let tok = getToken(cval)
    if tok.tokenType == CSS_URL_TOKEN or tok.tokenType == CSS_BAD_URL_TOKEN:
      return some(CSSContent(t: CONTENT_IMAGE, s: "[img]"))

func cssInteger(cval: CSSComponentValue, range: Slice[int]): int =
  if isToken(cval):
    let tok = getToken(cval)
    if tok.tokenType == CSS_NUMBER_TOKEN:
      let i = int(tok.nvalue)
      if float64(i) == tok.nvalue and i in range:
        return i
  raise newException(CSSValueError, "Invalid integer")

proc getValueFromDecl(val: CSSComputedValue, d: CSSDeclaration, vtype: CSSValueType, ptype: CSSPropertyType) =
  var i = 0
  d.value.skipWhitespace(i)
  if i >= d.value.len: 
    raise newException(CSSValueError, "Empty value")
  let cval = d.value[i]
  inc i
  case vtype
  of VALUE_COLOR:
    val.color = cssColor(cval)
  of VALUE_LENGTH:
    case ptype
    of PROPERTY_WORD_SPACING:
      val.length = cssWordSpacing(cval)
    of PROPERTY_LINE_HEIGHT:
      val.length = cssLineHeight(cval)
    of PROPERTY_MAX_WIDTH, PROPERTY_MAX_HEIGHT, PROPERTY_MIN_WIDTH,
       PROPERTY_MIN_HEIGHT:
      let res = cssMaxMinSize(cval)
      if res.isSome:
        val.length = res.get
      else:
        raise newException(CSSValueError, "Invalid length")
    of PROPERTY_PADDING_LEFT, PROPERTY_PADDING_RIGHT, PROPERTY_PADDING_TOP,
       PROPERTY_PADDING_BOTTOM:
      val.length = cssLength(cval, has_auto = false)
    else:
      val.length = cssLength(cval)
  of VALUE_FONT_STYLE: val.fontstyle = cssFontStyle(cval)
  of VALUE_DISPLAY: val.display = cssDisplay(cval)
  of VALUE_CONTENT: val.content = cssContent(d)
  of VALUE_WHITE_SPACE: val.whitespace = cssWhiteSpace(cval)
  of VALUE_INTEGER:
    if ptype == PROPERTY_FONT_WEIGHT:
      val.integer = cssFontWeight(cval)
    elif ptype == PROPERTY_CHA_COLSPAN:
      val.integer = cssInteger(cval, 1 .. 1000)
    elif ptype == PROPERTY_CHA_ROWSPAN:
      val.integer = cssInteger(cval, 0 .. 65534)
  of VALUE_TEXT_DECORATION: val.textdecoration = cssTextDecoration(d)
  of VALUE_WORD_BREAK: val.wordbreak = cssWordBreak(cval)
  of VALUE_LIST_STYLE_TYPE: val.liststyletype = cssListStyleType(cval)
  of VALUE_VERTICAL_ALIGN: val.verticalalign = cssVerticalAlign(cval)
  of VALUE_TEXT_ALIGN: val.textalign = cssTextAlign(cval)
  of VALUE_LIST_STYLE_POSITION: val.liststyleposition = cssListStylePosition(cval)
  of VALUE_POSITION: val.position = cssPosition(cval)
  of VALUE_CAPTION_SIDE: val.captionside = cssCaptionSide(cval)
  of VALUE_BORDER_COLLAPSE: val.bordercollapse = cssBorderCollapse(cval)
  of VALUE_LENGTH2:
    val.length2.a = cssAbsoluteLength(cval)
    d.value.skipWhitespace(i)
    if i >= d.value.len:
      val.length2.b = val.length2.a
    else:
      let cval = d.value[i]
      val.length2.b = cssAbsoluteLength(cval)
  of VALUE_QUOTES:
    let qs = cssQuotes(d)
    if qs.isSome:
      val.quotes = qs.get
    else:
      raise newException(CSSValueError, "Invalid quotes")
  of VALUE_COUNTER_RESET:
    let res = cssCounterReset(d)
    if res.isSome:
      val.counterreset = res.get
    else:
      raise newException(CSSValueError, "Invalid counter reset")
  of VALUE_IMAGE:
    let res = cssImage(d)
    if res.isSome:
      val.image = res.get
    else:
      raise newException(CSSValueError, "Invalid image")
  of VALUE_NONE: discard

func getInitialColor(t: CSSPropertyType): RGBAColor =
  case t
  of PROPERTY_COLOR:
    return Colors["white"]
  of PROPERTY_BACKGROUND_COLOR:
    return Colors["transparent"]
  else:
    return Colors["black"]

func getInitialLength(t: CSSPropertyType): CSSLength =
  case t
  of PROPERTY_WIDTH, PROPERTY_HEIGHT, PROPERTY_WORD_SPACING,
     PROPERTY_LINE_HEIGHT, PROPERTY_LEFT, PROPERTY_RIGHT, PROPERTY_TOP,
     PROPERTY_BOTTOM, PROPERTY_MAX_WIDTH, PROPERTY_MAX_HEIGHT,
     PROPERTY_MIN_WIDTH, PROPERTY_MIN_HEIGHT:
    return CSSLength(auto: true)
  else:
    return CSSLength(auto: false, unit: UNIT_PX, num: 0)

func getInitialInteger(t: CSSPropertyType): int =
  case t
  of PROPERTY_CHA_COLSPAN, PROPERTY_CHA_ROWSPAN:
    return 1
  of PROPERTY_FONT_WEIGHT:
    return 400 # normal
  else: discard

func calcInitial(t: CSSPropertyType): CSSComputedValue =
  let v = valueType(t)
  var nv: CSSComputedValue
  case v
  of VALUE_COLOR:
    nv = CSSComputedValue(t: t, v: v, color: getInitialColor(t))
  of VALUE_DISPLAY:
    nv = CSSComputedValue(t: t, v: v, display: DISPLAY_INLINE)
  of VALUE_WORD_BREAK:
    nv = CSSComputedValue(t: t, v: v, wordbreak: WORD_BREAK_NORMAL)
  of VALUE_LENGTH:
    nv = CSSComputedValue(t: t, v: v, length: getInitialLength(t))
  of VALUE_INTEGER:
    nv = CSSComputedValue(t: t, v: v, integer: getInitialInteger(t))
  of VALUE_QUOTES:
    nv = CSSComputedValue(t: t, v: v, quotes: CSSQuotes(auto: true))
  else:
    nv = CSSComputedValue(t: t, v: v)
  return nv

func getInitialTable(): array[CSSPropertyType, CSSComputedValue] =
  for i in low(result)..high(result):
    result[i] = calcInitial(i)

let defaultTable = getInitialTable()

template getDefault(t: CSSPropertyType): CSSComputedValue = {.cast(noSideEffect).}:
  defaultTable[t]

func getComputedValue(d: CSSDeclaration, ptype: CSSPropertyType, vtype: CSSValueType): (CSSComputedValue, CSSGlobalValueType) =
  var val = CSSComputedValue(t: ptype, v: vtype)
  try:
    val.getValueFromDecl(d, vtype, ptype)
  except CSSValueError:
    #TODO we should probably just do nothing instead
    #TODO also get rid of these ugly exceptions
    val = getDefault(ptype)

  return (val, cssGlobal(d))

func lengthShorthand(d: CSSDeclaration, props: array[4, CSSPropertyType]): seq[(CSSComputedValue, CSSGlobalValueType)] =
  var i = 0
  var cvals: seq[CSSComponentValue]
  while i < d.value.len:
    if d.value[i] != CSS_WHITESPACE_TOKEN:
      cvals.add(d.value[i])
    inc i
  case cvals.len
  of 1: # top, bottom, left, right
    try:
      for ptype in props:
        let vtype = valueType(ptype)
        let val = CSSComputedValue(t: ptype, v: vtype)
        val.getValueFromDecl(d, vtype, ptype)
        result.add((val, cssGlobal(d)))
    except CSSValueError: discard
  of 2: # top, bottom | left, right
    try:
      for i in 0 ..< props.len:
        let ptype = props[i]
        let vtype = valueType(ptype)
        let val = CSSComputedValue(t: ptype, v: vtype)
        val.length = cssLength(cvals[i mod 2])
        result.add((val, cssGlobal(d)))
    except CSSValueError:
      discard
  of 3: # top | left, right | bottom
    try:
      for i in 0 ..< props.len:
        let ptype = props[i]
        let vtype = valueType(ptype)
        let val = CSSComputedValue(t: ptype, v: vtype)
        let j = if i == 0:
          0 # top
        elif i == 3:
          2 # bottom
        else:
          1 # left, right
        val.length = cssLength(cvals[j])
        result.add((val, cssGlobal(d)))
    except CSSValueError:
      discard
  of 4: # top | right | bottom | left
    try:
      for i in 0 ..< props.len:
        let ptype = props[i]
        let vtype = valueType(ptype)
        let val = CSSComputedValue(t: ptype, v: vtype)
        val.length = cssLength(cvals[i])
        result.add((val, cssGlobal(d)))
    except CSSValueError:
      discard
  else: discard

proc getComputedValues(d: CSSDeclaration): seq[(CSSComputedValue, CSSGlobalValueType)] =
  let name = d.name
  case shorthandType(name)
  of SHORTHAND_NONE:
    let ptype = propertyType(name)
    let vtype = valueType(ptype)
    result.add(getComputedValue(d, ptype, vtype))
  of SHORTHAND_ALL:
    let global = cssGlobal(d)
    if global != VALUE_NOGLOBAL:
      for ptype in CSSPropertyType:
        let vtype = valueType(ptype)
        let val = CSSComputedValue(t: ptype, v: vtype)
        result.add((val, global))
  of SHORTHAND_MARGIN:
    result.add(lengthShorthand(d, [PROPERTY_MARGIN_TOP,
                                   PROPERTY_MARGIN_RIGHT,
                                   PROPERTY_MARGIN_BOTTOM,
                                   PROPERTY_MARGIN_LEFT]))
  of SHORTHAND_PADDING:
    result.add(lengthShorthand(d, [PROPERTY_PADDING_TOP,
                                   PROPERTY_PADDING_RIGHT,
                                   PROPERTY_PADDING_BOTTOM,
                                   PROPERTY_PADDING_LEFT]))
  of SHORTHAND_BACKGROUND:
    let global = cssGlobal(d)
    let bgcolorptype = PROPERTY_BACKGROUND_COLOR
    let bgcolorval = CSSComputedValue(t: bgcolorptype, v: valueType(bgcolorptype))
    let bgimageptype = PROPERTY_BACKGROUND_IMAGE
    let bgimageval = CSSComputedValue(t: bgimageptype, v: valueType(bgimageptype))
    if global == VALUE_NOGLOBAL:
      for tok in d.value:
        let img = cssImage(tok)
        if img.isSome:
          bgimageval.image = img.get
          result.add((bgimageval, global))
        else:
          try:
            bgcolorval.color = cssColor(tok)
            result.add((bgcolorval, global))
          except CSSValueError:
            discard
    else:
      result.add((bgcolorval, global))
  of SHORTHAND_LIST_STYLE:
    let global = cssGlobal(d)
    let positionptype = PROPERTY_LIST_STYLE_POSITION
    let positionval = CSSComputedValue(t: positionptype, v: valueType(positionptype))
    let typeptype = PROPERTY_LIST_STYLE_TYPE
    let typeval = CSSComputedValue(t: typeptype, v: valueType(typeptype))
    if global == VALUE_NOGLOBAL:
      for tok in d.value:
        try:
          positionval.liststyleposition = cssListStylePosition(tok)
          result.add((positionval, global))
        except CSSValueError:
          try:
            typeval.liststyletype = cssListStyleType(tok)
            result.add((typeval, global))
          except CSSValueError:
            #TODO list-style-image
            discard

func equals*(a, b: CSSComputedValue): bool =
  if a == b:
    return true
  if a == nil or b == nil:
    return false
  if a.v != b.v:
    return false
  case a.v
  of VALUE_COLOR: return a.color == b.color
  of VALUE_LENGTH: return a.length == b.length
  of VALUE_FONT_STYLE: return a.fontstyle == b.fontstyle
  of VALUE_DISPLAY: return a.display == b.display
  of VALUE_CONTENT: return a.content == b.content
  of VALUE_WHITESPACE: return a.whitespace == b.whitespace
  of VALUE_INTEGER: return a.integer == a.integer
  of VALUE_TEXT_DECORATION: return a.textdecoration == b.textdecoration
  of VALUE_WORD_BREAK: return a.wordbreak == b.wordbreak
  of VALUE_LIST_STYLE_TYPE: return a.liststyletype == b.liststyletype
  of VALUE_VERTICAL_ALIGN: return a.verticalalign == b.verticalalign
  of VALUE_TEXT_ALIGN: return a.textalign == b.textalign
  of VALUE_LIST_STYLE_POSITION: return a.liststyleposition == b.liststyleposition
  of VALUE_POSITION: return a.position == b.position
  of VALUE_CAPTION_SIDE: return a.captionside == b.captionside
  of VALUE_LENGTH2: return a.length2 == b.length2
  of VALUE_BORDER_COLLAPSE: return a.bordercollapse == b.bordercollapse
  of VALUE_QUOTES: return a.quotes == b.quotes
  of VALUE_COUNTER_RESET: return a.counterreset == b.counterreset
  of VALUE_IMAGE: return a.image == b.image
  of VALUE_NONE: return true
  return false

proc newComputedValueBuilder*(parent: CSSComputedValues): CSSComputedValuesBuilder =
  result.parent = parent

proc addValuesImportant*(builder: var CSSComputedValuesBuilder, decls: seq[CSSDeclaration], origin: CSSOrigin) =
  for decl in decls:
    if decl.important:
      let vals = getComputedValues(decl)
      for vg in vals:
        let (val, global) = vg
        builder.importantProperties[origin].add(CSSComputedValueBuilder(val: val, global: global))

proc addValuesNormal*(builder: var CSSComputedValuesBuilder, decls: seq[CSSDeclaration], origin: CSSOrigin) =
  for decl in decls:
    if not decl.important:
      let vals = getComputedValues(decl)
      for vg in vals:
        let (val, global) = vg
        builder.normalProperties[origin].add(CSSComputedValueBuilder(val: val, global: global))

proc addValues*(builder: var CSSComputedValuesBuilder, decls: seq[CSSDeclaration], origin: CSSOrigin) =
  for decl in decls:
    let vals = getComputedValues(decl)
    for vg in vals:
      let (val, global) = vg
      if decl.important:
        builder.importantProperties[origin].add(CSSComputedValueBuilder(val: val, global: global))
      else:
        builder.normalProperties[origin].add(CSSComputedValueBuilder(val: val, global: global))

proc applyValue(vals: CSSComputedValues, val: CSSComputedValue, global: CSSGlobalValueType, parent: CSSComputedValues, previousOrigin: CSSComputedValues) =
  let prop = val.t
  let parentVal = if parent != nil:
    parent[prop]
  else:
    nil
  case global
  of VALUE_INHERIT:
    if parentVal != nil:
      vals[prop] = parentVal
    else:
      vals[prop] = getDefault(prop)
  of VALUE_INITIAL:
    vals[prop] = getDefault(prop)
  of VALUE_UNSET:
    if inherited(prop):
      # inherit
      if parentVal != nil:
        vals[prop] = parentVal
      else:
        vals[prop] = getDefault(prop)
    else:
      # initial
      vals[prop] = getDefault(prop)
  of VALUE_REVERT:
    if previousOrigin != nil:
      vals[prop] = previousOrigin[prop]
    else:
      vals[prop] = getDefault(prop)
  of VALUE_NOGLOBAL:
    vals[prop] = val

func inheritProperties*(parent: CSSComputedValues): CSSComputedValues =
  new(result)
  for prop in CSSPropertyType:
    if inherited(prop) and parent[prop] != nil:
      result[prop] = parent[prop]
    else:
      result[prop] = getDefault(prop)

func copyProperties*(props: CSSComputedValues): CSSComputedValues =
  new(result)
  for prop in CSSPropertyType:
    result[prop] = props[prop]

func rootProperties*(): CSSComputedValues =
  new(result)
  for prop in CSSPropertyType:
    result[prop] = getDefault(prop)

func hasValues*(builder: CSSComputedValuesBuilder): bool =
  for origin in CSSOrigin:
    if builder.normalProperties[origin].len > 0:
      return true
    if builder.importantProperties[origin].len > 0:
      return true
  return false

func buildComputedValues*(builder: CSSComputedValuesBuilder): CSSComputedValues =
  new(result)
  var previousOrigins: array[CSSOrigin, CSSComputedValues]
  block:
    let origin = ORIGIN_USER_AGENT
    for build in builder.normalProperties[origin]:
      result.applyValue(build.val, build.global, builder.parent, nil)
    previousOrigins[origin] = result.copyProperties()
  # Presentational hints override user agent style, but respect user/author style.
  if builder.preshints != nil:
    for prop in CSSPropertyType:
      if builder.preshints[prop] != nil:
        result[prop] = builder.preshints[prop]
  block:
    let origin = ORIGIN_USER
    let prevOrigin = ORIGIN_USER_AGENT
    for build in builder.normalProperties[origin]:
      result.applyValue(build.val, build.global, builder.parent, previousOrigins[prevOrigin])
    previousOrigins[origin] = result.copyProperties() # save user origins so author can use them
  block:
    let origin = ORIGIN_AUTHOR
    let prevOrigin = ORIGIN_USER
    for build in builder.normalProperties[origin]:
      result.applyValue(build.val, build.global, builder.parent, previousOrigins[prevOrigin])
    # no need to save user origins
  block:
    let origin = ORIGIN_AUTHOR
    let prevOrigin = ORIGIN_USER
    for build in builder.importantProperties[origin]:
      result.applyValue(build.val, build.global, builder.parent, previousOrigins[prevOrigin])
    # important, so no need to save origins
  block:
    let origin = ORIGIN_USER
    let prevOrigin = ORIGIN_USER_AGENT
    for build in builder.importantProperties[origin]:
      result.applyValue(build.val, build.global, builder.parent, previousOrigins[prevOrigin])
    # important, so no need to save origins
  block:
    let origin = ORIGIN_USER_AGENT
    for build in builder.importantProperties[origin]:
      result.applyValue(build.val, build.global, builder.parent, nil)
    # important, so no need to save origins
  # set defaults
  for prop in CSSPropertyType:
    if result[prop] == nil:
      if inherited(prop) and builder.parent != nil and builder.parent[prop] != nil:
        result[prop] = builder.parent[prop]
      else:
        result[prop] = getDefault(prop)