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
CSSColor* = tuple[r: uint8, g: uint8, b: uint8, a: uint8]
CSSComputedValue* = ref 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*: CSSDisplay
of VALUE_CONTENT:
content*: seq[Rune]
of VALUE_WHITESPACE:
whitespace*: CSSWhitespace
of VALUE_INTEGER:
integer*: int
of VALUE_TEXT_DECORATION:
textdecoration*: CSSTextDecoration
of VALUE_WORD_BREAK:
wordbreak*: CSSWordBreak
of VALUE_NONE: discard
CSSComputedValues* = array[low(CSSPropertyType)..high(CSSPropertyType), CSSComputedValue]
CSSSpecifiedValue* = object of CSSComputedValue
globalValue: CSSGlobalValueType
CSSValueError* = object of ValueError
#TODO calculate this during compile time
const PropertyNames = {
"all": PROPERTY_ALL,
"color": PROPERTY_COLOR,
"margin": PROPERTY_MARGIN,
"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,
}.toTable()
const ValueTypes = [
PROPERTY_NONE: VALUE_NONE,
PROPERTY_ALL: VALUE_NONE,
PROPERTY_COLOR: VALUE_COLOR,
PROPERTY_MARGIN: VALUE_LENGTH,
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,
]
const InheritedProperties = {
PROPERTY_COLOR, PROPERTY_FONT_STYLE, PROPERTY_WHITE_SPACE,
PROPERTY_FONT_WEIGHT, PROPERTY_TEXT_DECORATION, PROPERTY_WORD_BREAK
}
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 propertyType*(s: string): CSSPropertyType =
return PropertyNames.getOrDefault(s, PROPERTY_NONE)
func valueType*(prop: CSSPropertyType): CSSValueType =
return ValueTypes[prop]
func inherited(t: CSSPropertyType): bool =
return InheritedArray[t]
func cells*(l: CSSLength): int =
case l.unit
of UNIT_EM:
return int(l.num)
of UNIT_CH:
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()
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: raise newException(CSSValueError, "Invalid unit")
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:
raise newException(CSSValueError, "Invalid color")
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:
raise newException(CSSValueError, "Invalid color")
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:
raise newException(CSSValueError, "Invalid color")
of CSS_IDENT_TOKEN:
let s = tok.value
if $s in colors:
return colors[$s]
else:
raise newException(CSSValueError, "Invalid color")
else:
raise newException(CSSValueError, "Invalid color")
elif d.value[0] of CSSFunction:
let f = CSSFunction(d.value[0])
#TODO calc etc (cssnumber function or something)
case $f.name
of "rgb":
if f.value.len != 3:
raise newException(CSSValueError, "Invalid color")
for c in f.value:
if c != CSS_NUMBER_TOKEN:
raise newException(CSSValueError, "Invalid color")
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:
raise newException(CSSValueError, "Invalid color")
for c in f.value:
if c != CSS_NUMBER_TOKEN:
raise newException(CSSValueError, "Invalid color")
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: discard
raise newException(CSSValueError, "Invalid color")
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 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 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): CSSDisplay =
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": return DISPLAY_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-column-group": return DISPLAY_TABLE_COLUMN_GROUP
of "table-row": return DISPLAY_TABLE_ROW
of "table-column": return DISPLAY_TABLE_COLUMN
of "table-cell": return DISPLAY_TABLE_CELL
of "none": return DISPLAY_NONE
else: return DISPLAY_INLINE
raise newException(CSSValueError, "Invalid display")
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: raise newException(CSSValueError, "Invalid font style")
raise newException(CSSValueError, "Invalid font style")
func cssWhiteSpace(d: CSSDeclaration): CSSWhitespace =
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
raise newException(CSSValueError, "Invalid whitespace")
#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
elif tok.tokenType == CSS_NUMBER_TOKEN:
return int(tok.nvalue)
raise newException(CSSValueError, "Invalid font weight")
func cssTextDecoration(d: CSSDeclaration): CSSTextDecoration =
if isToken(d):
let tok = getToken(d)
if tok.tokenType == CSS_IDENT_TOKEN:
case $tok.value
of "underline": return TEXT_DECORATION_UNDERLINE
of "overline": return TEXT_DECORATION_OVERLINE
of "line-through": return TEXT_DECORATION_LINE_THROUGH
of "blink": return TEXT_DECORATION_BLINK
raise newException(CSSValueError, "Invalid text decoration")
func cssWordBreak(d: CSSDeclaration): CSSWordBreak =
if isToken(d):
let tok = getToken(d)
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 getSpecifiedValue*(d: CSSDeclaration): CSSSpecifiedValue =
let name = $d.name
let ptype = propertyType(name)
let vtype = valueType(ptype)
result = CSSSpecifiedValue(t: ptype, v: vtype)
try:
case vtype
of VALUE_COLOR: result.color = cssColor(d)
of VALUE_LENGTH: result.length = cssLength(d)
of VALUE_FONT_STYLE: result.fontstyle = cssFontStyle(d)
of VALUE_DISPLAY: result.display = cssDisplay(d)
of VALUE_CONTENT: result.content = cssString(d)
of VALUE_WHITE_SPACE: result.whitespace = cssWhiteSpace(d)
of VALUE_INTEGER:
case ptype
of PROPERTY_FONT_WEIGHT:
result.integer = cssFontWeight(d)
else: discard #???
of VALUE_TEXT_DECORATION: result.textdecoration = cssTextDecoration(d)
of VALUE_WORD_BREAK: result.wordbreak = cssWordBreak(d)
of VALUE_NONE: discard
except CSSValueError:
result.globalValue = VALUE_UNSET
if result.globalValue == VALUE_NOGLOBAL:
result.globalValue = cssGlobal(d)
func getInitialColor*(t: CSSPropertyType): CSSColor =
case t
of PROPERTY_COLOR:
return (0xffu8, 0xffu8, 0xffu8, 0x00u8)
else:
return (0u8, 0u8, 0u8, 255u8)
func calcDefault(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)
else:
nv = CSSComputedValue(t: t, v: v)
return nv
func getDefaultTable(): array[low(CSSPropertyType)..high(CSSPropertyType), CSSComputedValue] =
for i in low(result)..high(result):
result[i] = calcDefault(i)
let defaultTable = getDefaultTable()
func getDefault(t: CSSPropertyType): CSSComputedValue = {.cast(noSideEffect).}:
assert defaultTable[t] != nil
return defaultTable[t]
func getComputedValue*(prop: CSSSpecifiedValue, current: CSSComputedValues): CSSComputedValue =
case prop.globalValue
of VALUE_INHERIT:
if inherited(prop.t):
return current[prop.t]
of VALUE_INITIAL:
return getDefault(prop.t)
of VALUE_UNSET:
if inherited(prop.t):
return current[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_TEXT_DECORATION:
return CSSComputedValue(t: prop.t, v: VALUE_TEXT_DECORATION, textdecoration: prop.textdecoration)
of VALUE_WORD_BREAK:
return CSSComputedValue(t: prop.t, v: VALUE_WORD_BREAK, wordbreak: prop.wordbreak)
of VALUE_NONE: return CSSComputedValue(t: prop.t, v: VALUE_NONE)
func getComputedValue*(d: CSSDeclaration, current: CSSComputedValues): CSSComputedValue =
return getComputedValue(getSpecifiedValue(d), current)
proc inheritProperties*(vals: var CSSComputedValues, parent: CSSComputedValues) =
for prop in low(CSSPropertyType)..high(CSSPropertyType):
if vals[prop] == nil:
vals[prop] = getDefault(prop)
if inherited(prop) and parent[prop] != nil and vals[prop] == getDefault(prop):
vals[prop] = parent[prop]
proc rootProperties*(vals: var CSSComputedValues) =
for prop in low(CSSPropertyType)..high(CSSPropertyType):
vals[prop] = getDefault(prop)