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

             

                   
                 
                  






                     
                                                                                           
 
                                                           

                                       
                       

                         
                      
                    
                        


                              
                           
                     
                         

                                 

                     
                          

                                                 

                                   
                    











                                        

           

















                                                                                        
 
                                
             
             




























                                                       















                                                        




























                                                        






                                    
                                   















                                                      


                                   






                                                       



                           




                                                                             









                                                
                                    
         
                                             
 
                                         
 











                                                                                              
 



































                                                                                  












                                                       
















                                            










                                                       


                                                               
                                                                                     
              
                                                                                         
                  
                                                                                             
                   
                                                                                              
                     
                                                                                                
                    
                                                                                               
                  
                                                                                                       
               
                                                                                             
               
                                                                                            
                   
                                                                                                         
                   


                                                                                                    
 
                                                     
        
                    



                                                   




























                                                                                              
                 
                                                                         
                  
                                                                            
                   
                                                                               
                      
                                                                                      
                   
                                                                               
                      
                                                                                        
                   

                                                                               
 
                                                                                        
                                                       
 



                                                                       
         





                                                          
import unicode
import tables

import utils/twtstr
import types/enums
import css/parser
import types/color

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

  CSSComputedValues* = array[low(CSSPropertyType)..high(CSSPropertyType), CSSComputedValue]

  CSSColor* = tuple[r: uint8, g: uint8, b: uint8, a: uint8]
  
  CSSComputedValue* = object of RootObj
    t*: CSSPropertyType
    case v*: CSSValueType
    of VALUE_COLOR:
      color*: CSSColor
    of VALUE_LENGTH:
      length*: CSSLength
    of VALUE_FONT_STYLE:
      fontstyle*: CSSFontStyle
    of VALUE_DISPLAY:
      display*: DisplayType
    of VALUE_CONTENT:
      content*: seq[Rune]
    of VALUE_WHITESPACE:
      whitespace*: WhitespaceType
    of VALUE_INTEGER:
      integer*: int
    of VALUE_NONE: discard

  CSSSpecifiedValue* = object of CSSComputedValue
    globalValue: CSSGlobalValueType

const ValueTypes = {
  PROPERTY_ALL: VALUE_NONE,
  PROPERTY_COLOR: VALUE_COLOR,
  PROPERTY_MARGIN: VALUE_LENGTH,
  PROPERTY_MARGIN_TOP: VALUE_LENGTH,
  PROPERTY_MARGIN_BOTTOM: VALUE_LENGTH,
  PROPERTY_MARGIN_LEFT: VALUE_LENGTH,
  PROPERTY_MARGIN_RIGHT: VALUE_LENGTH,
  PROPERTY_FONT_STYLE: VALUE_FONT_STYLE,
  PROPERTY_DISPLAY: VALUE_DISPLAY,
  PROPERTY_CONTENT: VALUE_CONTENT,
  PROPERTY_WHITESPACE: VALUE_WHITESPACE,
  PROPERTY_FONT_WEIGHT: VALUE_INTEGER,
}.toTable()

const InheritedProperties = {
  PROPERTY_COLOR, PROPERTY_FONT_STYLE, PROPERTY_WHITESPACE, PROPERTY_FONT_WEIGHT
}

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

const InheritedArray = getPropInheritedArray()

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

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

func cells*(l: CSSLength): int =
  case l.unit
  of UNIT_EM:
    return int(l.num)
  else:
    #TODO
    return int(l.num / 8)

const colors = {
  "maroon":  (0x80u8, 0x00u8, 0x00u8, 0x00u8),
  "red":     (0xffu8, 0x00u8, 0x00u8, 0x00u8),
  "orange":  (0xffu8, 0xa5u8, 0x00u8, 0x00u8),
  "yellow":  (0xffu8, 0xffu8, 0x00u8, 0x00u8),
  "olive":   (0x80u8, 0x80u8, 0x00u8, 0x00u8),
  "purple":  (0x80u8, 0x00u8, 0x80u8, 0x00u8),
  "fuchsia": (0xffu8, 0x00u8, 0x00u8, 0x00u8),
  "white":   (0xffu8, 0xffu8, 0xffu8, 0x00u8),
  "lime":    (0x00u8, 0xffu8, 0x00u8, 0x00u8),
  "green":   (0x00u8, 0x80u8, 0x00u8, 0x00u8),
  "navy":    (0x00u8, 0x00u8, 0x80u8, 0x00u8),
  "blue":    (0x00u8, 0x00u8, 0xffu8, 0x00u8),
  "aqua":    (0x00u8, 0xffu8, 0xffu8, 0x00u8),
  "teal":    (0x00u8, 0x80u8, 0x80u8, 0x00u8),
  "black":   (0x00u8, 0x00u8, 0x00u8, 0x00u8),
  "silver":  (0xc0u8, 0xc0u8, 0xc0u8, 0x00u8),
  "gray":    (0x80u8, 0x80u8, 0x80u8, 0x00u8),
}.toTable()

const defaultColor = (0xffu8, 0xffu8, 0xffu8, 0x00u8)

func cssLength(val: float64, unit: string): CSSLength =
  case unit
  of "%": return CSSLength(num: val, unit: UNIT_PERC)
  of "cm": return CSSLength(num: val, unit: UNIT_CM)
  of "mm": return CSSLength(num: val, unit: UNIT_MM)
  of "in": return CSSLength(num: val, unit: UNIT_IN)
  of "px": return CSSLength(num: val, unit: UNIT_PX)
  of "pt": return CSSLength(num: val, unit: UNIT_PT)
  of "pc": return CSSLength(num: val, unit: UNIT_PC)
  of "em": return CSSLength(num: val, unit: UNIT_EM)
  of "ex": return CSSLength(num: val, unit: UNIT_EX)
  of "ch": return CSSLength(num: val, unit: UNIT_CH)
  of "rem": return CSSLength(num: val, unit: UNIT_REM)
  of "vw": return CSSLength(num: val, unit: UNIT_VW)
  of "vh": return CSSLength(num: val, unit: UNIT_VH)
  of "vmin": return CSSLength(num: val, unit: UNIT_VMIN)
  of "vmax": return CSSLength(num: val, unit: UNIT_VMAX)
  else: return CSSLength(num: 0, unit: UNIT_EM)

func cssColor*(d: CSSDeclaration): CSSColor =
  if d.value.len > 0:
    if d.value[0] of CSSToken:
      let tok = CSSToken(d.value[0])
      case tok.tokenType
      of CSS_HASH_TOKEN:
        let s = tok.value
        if s.len == 3:
          for r in s:
            if hexValue(r) == -1:
              return
          let r = hexValue(s[0]) * 0x10 + hexValue(s[0])
          let g = hexValue(s[1]) * 0x10 + hexValue(s[1])
          let b = hexValue(s[2]) * 0x10 + hexValue(s[2])

          return (uint8(r), uint8(g), uint8(b), 0x00u8)
        elif s.len == 6:
          for r in s:
            if hexValue(r) == -1:
              return
          let r = hexValue(s[0]) * 0x10 + hexValue(s[1])
          let g = hexValue(s[2]) * 0x10 + hexValue(s[3])
          let b = hexValue(s[4]) * 0x10 + hexValue(s[5])
          return (uint8(r), uint8(g), uint8(b), 0x00u8)
        else:
          return defaultColor
      of CSS_IDENT_TOKEN:
        let s = tok.value
        if $s in colors:
          return colors[$s]
        else:
          return defaultColor
      else:
        eprint "else", tok.tokenType
        return defaultColor
    elif d.value[0] of CSSFunction:
      let f = CSSFunction(d.value[0])
      eprint "func", f.name
      #todo calc etc (cssnumber function or something)
      case $f.name
      of "rgb":
        if f.value.len != 3:
          return defaultColor
        for c in f.value:
          if c != CSS_NUMBER_TOKEN:
            return defaultColor
        let r = CSSToken(f.value[0]).nvalue
        let g = CSSToken(f.value[1]).nvalue
        let b = CSSToken(f.value[2]).nvalue
        return (uint8(r), uint8(g), uint8(b), 0x00u8)
      of "rgba":
        if f.value.len != 4:
          return defaultColor
        for c in f.value:
          if c != CSS_NUMBER_TOKEN:
            return defaultColor
        let r = CSSToken(f.value[0]).nvalue
        let g = CSSToken(f.value[1]).nvalue
        let b = CSSToken(f.value[2]).nvalue
        let a = CSSToken(f.value[3]).nvalue
        return (uint8(r), uint8(g), uint8(b), uint8(a))
      else:
        return defaultColor

  return defaultColor

func cellColor*(color: CSSColor): CellColor =
  #TODO better would be to store color names and return term colors on demand
  #option)
  return CellColor(rgb: true, rgbcolor: (r: color.r, g: color.g, b: color.b))

func cssLength(d: CSSDeclaration): CSSLength =
  if d.value.len > 0 and d.value[0] of CSSToken:
    let tok = CSSToken(d.value[0])
    case tok.tokenType
    of CSS_PERCENTAGE_TOKEN:
      return cssLength(tok.nvalue, "%")
    of CSS_DIMENSION_TOKEN:
      return cssLength(tok.nvalue, $tok.unit)
    of CSS_IDENT_TOKEN:
      if $tok.value == "auto":
        return CSSLength(auto: true)
    else:
      return CSSLength(num: 0, unit: UNIT_EM)

  return CSSLength(num: 0, unit: UNIT_EM)

#func hasColor*(style: CSS2Properties): bool =
#  return style.color.r != 0 or style.color.b != 0 or style.color.g != 0 or style.color.a != 0
#
#func termColor*(style: CSS2Properties): ForegroundColor =
#  if style.color.r > 120:
#    return fgRed
#  elif style.color.b > 120:
#    return fgBlue
#  elif style.color.g > 120:
#    return fgGreen
#  else:
#    return fgWhite

func isToken(d: CSSDeclaration): bool = d.value.len > 0 and d.value[0] of CSSToken
func getToken(d: CSSDeclaration): CSSToken = (CSSToken)d.value[0]

func cssString(d: CSSDeclaration): seq[Rune] =
  if isToken(d):
    let tok = getToken(d)
    case tok.tokenType
    of CSS_IDENT_TOKEN, CSS_STRING_TOKEN:
      return tok.value
    else: return

func cssDisplay(d: CSSDeclaration): DisplayType =
  if isToken(d):
    let tok = getToken(d)
    if tok.tokenType == CSS_IDENT_TOKEN:
      case $tok.value
      of "block": return DISPLAY_BLOCK
      of "inline": return DISPLAY_INLINE
      of "inline-block": return DISPLAY_INLINE_BLOCK
      of "list-item": return DISPLAY_LIST_ITEM
      of "table-column": return DISPLAY_TABLE_COLUMN
      of "none": return DISPLAY_NONE
      else: return DISPLAY_INLINE
  return DISPLAY_INLINE

func cssFontStyle(d: CSSDeclaration): CSSFontStyle =
  if isToken(d):
    let tok = getToken(d)
    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: return FONTSTYLE_NORMAL
  return FONTSTYLE_NORMAL

func cssWhiteSpace(d: CSSDeclaration): WhitespaceType =
  if isToken(d):
    let tok = getToken(d)
    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
  return WHITESPACE_NORMAL

#TODO
func cssFontWeight(d: CSSDeclaration): int =
  if isToken(d):
    let tok = getToken(d)
    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
      else: return 400

    elif tok.tokenType == CSS_NUMBER_TOKEN:
      return int(tok.nvalue)

  return 400

proc cssGlobal(d: CSSDeclaration): CSSGlobalValueType =
  if isToken(d):
    let tok = getToken(d)
    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 getSpecifiedValue*(d: CSSDeclaration): CSSSpecifiedValue =
  case $d.name
  of "color":
    result = CSSSpecifiedValue(t: PROPERTY_COLOR, v: VALUE_COLOR, color: cssColor(d))
  of "margin":
    result = CSSSpecifiedValue(t: PROPERTY_MARGIN, v: VALUE_LENGTH, length: cssLength(d))
  of "margin-top":
    result = CSSSpecifiedValue(t: PROPERTY_MARGIN_TOP, v: VALUE_LENGTH, length: cssLength(d))
  of "margin-left":
    result = CSSSpecifiedValue(t: PROPERTY_MARGIN_LEFT, v: VALUE_LENGTH, length: cssLength(d))
  of "margin-bottom":
    result = CSSSpecifiedValue(t: PROPERTY_MARGIN_BOTTOM, v: VALUE_LENGTH, length: cssLength(d))
  of "margin-right":
    result = CSSSpecifiedValue(t: PROPERTY_MARGIN_RIGHT, v: VALUE_LENGTH, length: cssLength(d))
  of "font-style":
    result = CSSSpecifiedValue(t: PROPERTY_FONT_STYLE, v: VALUE_FONT_STYLE, fontstyle: cssFontStyle(d))
  of "display":
    result = CSSSpecifiedValue(t: PROPERTY_DISPLAY, v: VALUE_DISPLAY, display: cssDisplay(d))
  of "content":
    result = CSSSpecifiedValue(t: PROPERTY_CONTENT, v: VALUE_CONTENT, content: cssString(d))
  of "white-space":
    result = CSSSpecifiedValue(t: PROPERTY_WHITESPACE, v: VALUE_WHITESPACE, whitespace: cssWhiteSpace(d))
  of "font-weight":
    result = CSSSpecifiedValue(t: PROPERTY_FONT_WEIGHT, v: VALUE_INTEGER, integer: cssFontWeight(d))

  result.globalValue = cssGlobal(d)

func getInitialColor*(t: CSSPropertyType): CSSColor =
  case t
  of PROPERTY_COLOR:
    return (r: 255u8, g: 255u8, b: 255u8, a: 255u8)
  else:
    return (r: 0u8, g: 0u8, b: 0u8, a: 255u8)

func getDefault(t: CSSPropertyType): CSSComputedValue =
  let v = getValueType(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)
  else:
    nv = CSSComputedValue(t: t, v: v)
  return nv

func getComputedValue*(prop: CSSSpecifiedValue, parent: CSSComputedValues): CSSComputedValue =
  case prop.globalValue
  of VALUE_INHERIT:
    if inherited(prop.t):
      return parent[prop.t]
  of VALUE_INITIAL:
    return getDefault(prop.t)
  of VALUE_UNSET:
    if inherited(prop.t):
      return parent[prop.t]
    return getDefault(prop.t)
  of VALUE_REVERT:
    #TODO
    discard
  of VALUE_NOGLOBAL: discard

  case prop.v
  of VALUE_COLOR:
    return CSSComputedValue(t: prop.t, v: VALUE_COLOR, color: prop.color)
  of VALUE_LENGTH:
    return CSSComputedValue(t: prop.t, v: VALUE_LENGTH, length: prop.length)
  of VALUE_DISPLAY:
    return CSSComputedValue(t: prop.t, v: VALUE_DISPLAY, display: prop.display)
  of VALUE_FONT_STYLE:
    return CSSComputedValue(t: prop.t, v: VALUE_FONT_STYLE, fontstyle: prop.fontstyle)
  of VALUE_CONTENT:
    return CSSComputedValue(t: prop.t, v: VALUE_CONTENT, content: prop.content)
  of VALUE_WHITESPACE:
    return CSSComputedValue(t: prop.t, v: VALUE_WHITESPACE, whitespace: prop.whitespace)
  of VALUE_INTEGER:
    return CSSComputedValue(t: prop.t, v: VALUE_INTEGER, integer: prop.integer)
  of VALUE_NONE: return CSSComputedValue(t: prop.t, v: VALUE_NONE)

func getComputedValue*(d: CSSDeclaration, parent: CSSComputedValues): CSSComputedValue =
  return getComputedValue(getSpecifiedValue(d), parent)

func inheritProperties*(parent: CSSComputedValues): CSSComputedValues =
  for prop in low(CSSPropertyType)..high(CSSPropertyType):
    if inherited(prop):
      result[prop] = parent[prop]
    else:
      result[prop] = getDefault(prop)

func rootProperties*(): CSSComputedValues =
  for prop in low(CSSPropertyType)..high(CSSPropertyType):
    result[prop] = getDefault(prop)
    eprint result[prop]