import std/macros
import std/options
import std/strutils
import std/tables
import std/unicode
import css/cssparser
import css/selectorparser
import display/winattrs
import img/bitmap
import layout/layoutunit
import types/color
import types/opt
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, PROPERTY_FLOAT,
PROPERTY_VISIBILITY, PROPERTY_BOX_SIZING, PROPERTY_CLEAR,
PROPERTY_TEXT_TRANSFORM
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, VALUE_FLOAT, VALUE_VISIBILITY,
VALUE_BOX_SIZING, VALUE_CLEAR, VALUE_TEXT_TRANSFORM
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,
DISPLAY_FLOW_ROOT
CSSWhitespace* = enum
WHITESPACE_NORMAL, WHITESPACE_NOWRAP, WHITESPACE_PRE, WHITESPACE_PRE_LINE,
WHITESPACE_PRE_WRAP
CSSFontStyle* = enum
FONT_STYLE_NORMAL, FONT_STYLE_ITALIC, FONT_STYLE_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_DISCLOSURE_CLOSED, LIST_STYLE_TYPE_DISCLOSURE_OPEN,
LIST_STYLE_TYPE_CJK_EARTHLY_BRANCH, LIST_STYLE_TYPE_CJK_HEAVENLY_STEM,
LIST_STYLE_TYPE_LOWER_ROMAN, LIST_STYLE_TYPE_UPPER_ROMAN,
LIST_STYLE_TYPE_LOWER_ALPHA, LIST_STYLE_TYPE_UPPER_ALPHA,
LIST_STYLE_TYPE_LOWER_GREEK,
LIST_STYLE_TYPE_HIRAGANA, LIST_STYLE_TYPE_HIRAGANA_IROHA,
LIST_STYLE_TYPE_KATAKANA, LIST_STYLE_TYPE_KATAKANA_IROHA,
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
CSSFloat* = enum
FLOAT_NONE, FLOAT_LEFT, FLOAT_RIGHT
CSSVisibility* = enum
VISIBILITY_VISIBLE, VISIBILITY_HIDDEN, VISIBILITY_COLLAPSE
CSSBoxSizing* = enum
BOX_SIZING_CONTENT_BOX, BOX_SIZING_BORDER_BOX
CSSClear* = enum
CLEAR_NONE, CLEAR_LEFT, CLEAR_RIGHT, CLEAR_BOTH, CLEAR_INLINE_START,
CLEAR_INLINE_END
CSSTextTransform* = enum
TEXT_TRANSFORM_NONE, TEXT_TRANSFORM_CAPITALIZE, TEXT_TRANSFORM_UPPERCASE,
TEXT_TRANSFORM_LOWERCASE, TEXT_TRANSFORM_FULL_WIDTH,
TEXT_TRANSFORM_FULL_SIZE_KANA, TEXT_TRANSFORM_CHA_HALF_WIDTH
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
bmp*: Bitmap
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_WHITE_SPACE:
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_FLOAT:
float*: CSSFloat
of VALUE_VISIBILITY:
visibility*: CSSVisibility
of VALUE_BOX_SIZING:
boxsizing*: CSSBoxSizing
of VALUE_CLEAR:
clear*: CSSClear
of VALUE_TEXT_TRANSFORM:
texttransform*: CSSTextTransform
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
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,
"float": PROPERTY_FLOAT,
"visibility": PROPERTY_VISIBILITY,
"box-sizing": PROPERTY_BOX_SIZING,
"clear": PROPERTY_CLEAR,
"text-transform": PROPERTY_TEXT_TRANSFORM
}.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,
PROPERTY_FLOAT: VALUE_FLOAT,
PROPERTY_VISIBILITY: VALUE_VISIBILITY,
PROPERTY_BOX_SIZING: VALUE_BOX_SIZING,
PROPERTY_CLEAR: VALUE_CLEAR,
PROPERTY_TEXT_TRANSFORM: VALUE_TEXT_TRANSFORM
]
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,
PROPERTY_VISIBILITY, PROPERTY_TEXT_TRANSFORM
}
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.toLowerAscii(), SHORTHAND_NONE)
func propertyType(s: string): CSSPropertyType =
return PropertyNames.getOrDefault(s.toLowerAscii(), PROPERTY_NONE)
func valueType(prop: CSSPropertyType): CSSValueType =
return ValueTypes[prop]
func isSupportedProperty*(s: string): bool =
return s in PropertyNames
func `$`*(length: CSSLength): string =
if length.auto:
return "auto"
let us = ($length.unit).split('_')[1..^1].join('_').toLowerAscii()
return $length.num & us
func `$`*(content: CSSContent): string =
if content.s != "":
return "url(" & content.s & ")"
return "none"
func `$`*(val: CSSComputedValue): string =
result = ($val.t).toLowerAscii().split('_')[1..^1].join('-') & ": "
case val.v
of VALUE_COLOR:
result &= $val.color
of VALUE_IMAGE:
result &= $val.image
of VALUE_LENGTH:
result &= $val.length
else: discard
macro `{}`*(vals: CSSComputedValues, s: string): untyped =
let t = propertyType($s)
let vs = $valueType(t)
let s = vs.split('_')[1..^1].join('_').toLowerAscii()
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('_').toLowerAscii()
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): LayoutUnit =
em * float64(window.ppl)
func ch_to_px(ch: float64, window: WindowAttributes): LayoutUnit =
ch * float64(window.ppc)
# 水 width, we assume it's 2 chars
func ic_to_px(ic: float64, window: WindowAttributes): LayoutUnit =
ic * float64(window.ppc) * 2
# x-letter height, we assume it's em/2
func ex_to_px(ex: float64, window: WindowAttributes): LayoutUnit =
ex * float64(window.ppc) / 2
func px*(l: CSSLength, window: WindowAttributes, p: LayoutUnit): LayoutUnit {.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: toLayoutUnit(toFloat64(p) * l.num / 100)
of UNIT_PX: toLayoutUnit(l.num)
of UNIT_CM: toLayoutUnit(l.num * 37.8)
of UNIT_MM: toLayoutUnit(l.num * 3.78)
of UNIT_IN: toLayoutUnit(l.num * 96)
of UNIT_PC: toLayoutUnit(l.num * 16)
of UNIT_PT: toLayoutUnit(l.num * 4 / 3)
of UNIT_VW: toLayoutUnit(float64(window.width_px) * l.num / 100)
of UNIT_VH: toLayoutUnit(float64(window.height_px) * l.num / 100)
of UNIT_VMIN:
toLayoutUnit(min(window.width_px, window.width_px) / 100 * l.num)
of UNIT_VMAX:
toLayoutUnit(max(window.width_px, window.width_px) / 100 * l.num)
const UpperAlphaMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toRunes()
const LowerAlphaMap = "abcdefghijklmnopqrstuvwxyz".toRunes()
const LowerGreekMap = "αβγδεζηθικλμνξοπρστυφχψω".toRunes()
const HiraganaMap = ("あいうえおかきくけこさしすせそたちつてとなにぬねの" &
"はひふへほまみむめもやゆよらりるれろわゐゑをん").toRunes()
const HiraganaIrohaMap = ("いろはにほへとちりぬるをわかよたれそつねならむ" &
"うゐのおくやまけふこえてあさきゆめみしゑひもせす").toRunes()
const KatakanaMap = ("アイウエオカキクケコサシスセソタチツテトナニヌネノ" &
"ハヒフヘホマミムメモヤユヨラリルレロワヰヱヲン").toRunes()
const KatakanaIrohaMap = ("イロハニホヘトチリヌルヲワカヨタレソツネナラム" &
"ウヰノオクヤマケフコエテアサキユメミシヱヒモセス").toRunes()
const EarthlyBranchMap = "子丑寅卯辰巳午未申酉戌亥".toRunes()
const HeavenlyStemMap = "甲乙丙丁戊己庚辛壬癸".toRunes()
func numToBase(n: int, map: openArray[Rune]): string =
if n <= 0:
return $n
var tmp: seq[Rune]
var n = n
while n != 0:
n -= 1
tmp &= map[n mod map.len]
n = n div map.len
result = ""
for i in countdown(tmp.high, 0):
result &= $tmp[i]
func numToFixed(n: int, map: openArray[Rune]): string =
let n = n - 1
if n notin 0 .. map.high:
return $n
return $map[n]
func listMarker*(t: CSSListStyleType, i: int): string =
case t
of LIST_STYLE_TYPE_NONE: return ""
of LIST_STYLE_TYPE_DISC: return "• " # U+2022
of LIST_STYLE_TYPE_CIRCLE: return "○ " # U+25CB
of LIST_STYLE_TYPE_SQUARE: return "□ " # U+25A1
of LIST_STYLE_TYPE_DISCLOSURE_OPEN: return "▶ " # U+25B6
of LIST_STYLE_TYPE_DISCLOSURE_CLOSED: return "▼ " # U+25BC
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_UPPER_ALPHA: return numToBase(i, UpperAlphaMap) & ". "
of LIST_STYLE_TYPE_LOWER_ALPHA: return numToBase(i, LowerAlphaMap) & ". "
of LIST_STYLE_TYPE_LOWER_GREEK: return numToBase(i, LowerGreekMap) & ". "
of LIST_STYLE_TYPE_HIRAGANA: return numToBase(i, HiraganaMap) & "、"
of LIST_STYLE_TYPE_HIRAGANA_IROHA:
return numToBase(i, HiraganaIrohaMap) & "、"
of LIST_STYLE_TYPE_KATAKANA: return numToBase(i, KatakanaMap) & "、"
of LIST_STYLE_TYPE_KATAKANA_IROHA:
return numToBase(i, KatakanaIrohaMap) & "、"
of LIST_STYLE_TYPE_CJK_EARTHLY_BRANCH:
return numToFixed(i, EarthlyBranchMap) & "、"
of LIST_STYLE_TYPE_CJK_HEAVENLY_STEM:
return numToFixed(i, HeavenlyStemMap) & "、"
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): Opt[CSSLength] =
if unit in Units:
return ok(CSSLength(num: val, unit: Units[unit]))
return err()
const CSSLengthAuto* = CSSLength(auto: true)
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): Opt[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 ok(c.get)
of CSS_IDENT_TOKEN:
let s = tok.value
if s in Colors:
return ok(Colors[s])
else: discard
elif val of CSSFunction:
let f = CSSFunction(val)
var i = 0
var commaMode = false
template check_err(slash: bool) =
#TODO calc, percentages, etc (cssnumber function or something)
if not slash and i >= f.value.len or i < f.value.len and
f.value[i] != CSS_NUMBER_TOKEN:
return err()
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:
return err()
elif slash:
let tok = f.value[i]
if tok != CSS_DELIM_TOKEN or CSSToken(tok).cvalue != '/':
return err()
inc i
f.value.skipWhitespace(i)
check_err slash
if f.name.equalsIgnoreCase("rgb") or f.name.equalsIgnoreCase("rgba"):
f.value.skipWhitespace(i)
check_err false
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 = if i < f.value.len:
CSSToken(f.value[i]).nvalue
else:
1
return ok(rgba(int(r), int(g), int(b), int(a * 255)))
return err()
func isToken(cval: CSSComponentValue): bool {.inline.} = cval of CSSToken
func getToken(cval: CSSComponentValue): CSSToken = (CSSToken)cval
func cssIdent[T](map: static openArray[(string, T)], cval: CSSComponentValue):
Opt[T] =
if isToken(cval):
let tok = getToken(cval)
if tok.tokenType == CSS_IDENT_TOKEN:
# cmp when len is small enough, otherwise lowercase & hashmap
when map.len <= 4:
for (k, v) in map:
if k.equalsIgnoreCase(tok.value):
return ok(v)
else:
const MapTable = map.toTable()
let val = tok.value.toLowerAscii()
if val in MapTable:
return ok(MapTable[val])
return err()
func cssIdentFirst[T](map: static openArray[(string, T)], d: CSSDeclaration):
Opt[T] =
if d.value.len == 1:
return cssIdent(map, d.value[0])
return err()
func cssLength*(val: CSSComponentValue, has_auto: static bool = true,
allow_negative: static bool = true): Opt[CSSLength] =
block nofail:
if val of CSSToken:
let tok = CSSToken(val)
case tok.tokenType
of CSS_NUMBER_TOKEN:
if tok.nvalue == 0:
return ok(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.equalsIgnoreCase("auto"):
return ok(CSSLengthAuto)
else: discard
return err()
func cssAbsoluteLength(val: CSSComponentValue): Opt[CSSLength] =
if val of CSSToken:
let tok = CSSToken(val)
case tok.tokenType
of CSS_NUMBER_TOKEN:
if tok.nvalue == 0:
return ok(CSSLength(num: 0, unit: UNIT_PX))
of CSS_DIMENSION_TOKEN:
if tok.nvalue >= 0:
return cssLength(tok.nvalue, tok.unit)
else: discard
return err()
func cssWordSpacing(cval: CSSComponentValue): Opt[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.equalsIgnoreCase("normal"):
return ok(CSSLengthAuto)
else: discard
return err()
func cssGlobal(d: CSSDeclaration): CSSGlobalValueType =
const GlobalMap = {
"inherit": VALUE_INHERIT,
"initial": VALUE_INITIAL,
"unset": VALUE_UNSET,
"revert": VALUE_REVERT
}
return cssIdentFirst(GlobalMap, d).get(VALUE_NOGLOBAL)
func cssQuotes(d: CSSDeclaration): Opt[CSSQuotes] =
template die =
return err()
if d.value.len == 0:
die
var res: CSSQuotes
var sa = false
var pair: tuple[s, e: string]
for cval in d.value:
if res.auto: die
if isToken(cval):
let tok = getToken(cval)
case tok.tokenType
of CSS_IDENT_TOKEN:
if res.qs.len > 0: die
if tok.value.equalsIgnoreCase("auto"):
res.auto = true
elif tok.value.equalsIgnoreCase("none"):
if d.value.len != 1:
die
die
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: die
if sa:
die
return ok(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:
if tok.value == "/":
break
elif tok.value.equalsIgnoreCase("open-quote"):
result.add(CSSContent(t: CONTENT_OPEN_QUOTE))
elif tok.value.equalsIgnoreCase("no-open-quote"):
result.add(CSSContent(t: CONTENT_NO_OPEN_QUOTE))
elif tok.value.equalsIgnoreCase("close-quote"):
result.add(CSSContent(t: CONTENT_CLOSE_QUOTE))
elif tok.value.equalsIgnoreCase("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): Opt[CSSDisplay] =
const DisplayMap = {
"block": DISPLAY_BLOCK,
"inline": DISPLAY_INLINE,
"list-item": DISPLAY_LIST_ITEM,
"inline-block": DISPLAY_INLINE_BLOCK,
"table": DISPLAY_TABLE,
"table-row": DISPLAY_TABLE_ROW,
"table-cell": DISPLAY_TABLE_CELL,
"table-column": DISPLAY_TABLE_COLUMN,
"table-column-group": DISPLAY_TABLE_COLUMN_GROUP,
"inline-table": DISPLAY_INLINE_TABLE,
"table-row-group": DISPLAY_TABLE_ROW_GROUP,
"table-header-group": DISPLAY_TABLE_HEADER_GROUP,
"table-footer-group": DISPLAY_TABLE_FOOTER_GROUP,
"table-caption": DISPLAY_TABLE_CAPTION,
"flow-root": DISPLAY_FLOW_ROOT,
"none": DISPLAY_NONE
}
return cssIdent(DisplayMap, cval)
func cssFontStyle(cval: CSSComponentValue): Opt[CSSFontStyle] =
const FontStyleMap = {
"normal": FONT_STYLE_NORMAL,
"italic": FONT_STYLE_ITALIC,
"oblique": FONT_STYLE_OBLIQUE
}
return cssIdent(FontStyleMap, cval)
func cssWhiteSpace(cval: CSSComponentValue): Opt[CSSWhitespace] =
const WhiteSpaceMap = {
"normal": WHITESPACE_NORMAL,
"nowrap": WHITESPACE_NOWRAP,
"pre": WHITESPACE_PRE,
"pre-line": WHITESPACE_PRE_LINE,
"pre-wrap": WHITESPACE_PRE_WRAP
}
return cssIdent(WhiteSpaceMap, cval)
func cssFontWeight(cval: CSSComponentValue): Opt[int] =
if isToken(cval):
let tok = getToken(cval)
if tok.tokenType == CSS_IDENT_TOKEN:
const FontWeightMap = {
"normal": 400,
"bold": 700,
"lighter": 400,
"bolder": 700
}
return cssIdent(FontWeightMap, cval)
elif tok.tokenType == CSS_NUMBER_TOKEN:
if tok.nvalue in 1f64..1000f64:
return ok(int(tok.nvalue))
return err()
func cssTextDecoration(d: CSSDeclaration): Opt[set[CSSTextDecoration]] =
var s: set[CSSTextDecoration]
for cval in d.value:
if isToken(cval):
let tok = getToken(cval)
if tok.tokenType == CSS_IDENT_TOKEN:
if tok.value.equalsIgnoreCase("none"):
if d.value.len != 1:
return err()
return ok(s)
elif tok.value.equalsIgnoreCase("underline"):
s.incl(TEXT_DECORATION_UNDERLINE)
elif tok.value.equalsIgnoreCase("overline"):
s.incl(TEXT_DECORATION_OVERLINE)
elif tok.value.equalsIgnoreCase("line-through"):
s.incl(TEXT_DECORATION_LINE_THROUGH)
elif tok.value.equalsIgnoreCase("blink"):
s.incl(TEXT_DECORATION_BLINK)
else:
return err()
return ok(s)
func cssWordBreak(cval: CSSComponentValue): Opt[CSSWordBreak] =
const WordBreakMap = {
"normal": WORD_BREAK_NORMAL,
"break-all": WORD_BREAK_BREAK_ALL,
"keep-all": WORD_BREAK_KEEP_ALL
}
return cssIdent(WordBreakMap, cval)
func cssListStyleType(cval: CSSComponentValue):
Opt[CSSListStyleType] =
const ListStyleMap = {
"none": LIST_STYLE_TYPE_NONE,
"disc": LIST_STYLE_TYPE_DISC,
"circle": LIST_STYLE_TYPE_CIRCLE,
"square": LIST_STYLE_TYPE_SQUARE,
"decimal": LIST_STYLE_TYPE_DECIMAL,
"disclosure-open": LIST_STYLE_TYPE_DISCLOSURE_OPEN,
"disclosure-closed": LIST_STYLE_TYPE_DISCLOSURE_CLOSED,
"cjk-earthly-branch": LIST_STYLE_TYPE_CJK_EARTHLY_BRANCH,
"cjk-heavenly-stem": LIST_STYLE_TYPE_CJK_HEAVENLY_STEM,
"upper-roman": LIST_STYLE_TYPE_UPPER_ROMAN,
"lower-roman": LIST_STYLE_TYPE_LOWER_ROMAN,
"upper-latin": LIST_STYLE_TYPE_UPPER_ALPHA,
"lower-latin": LIST_STYLE_TYPE_LOWER_ALPHA,
"upper-alpha": LIST_STYLE_TYPE_UPPER_ALPHA,
"lower-alpha": LIST_STYLE_TYPE_UPPER_ALPHA,
"lower-greek": LIST_STYLE_TYPE_LOWER_GREEK,
"hiragana": LIST_STYLE_TYPE_HIRAGANA,
"hiragana-iroha": LIST_STYLE_TYPE_HIRAGANA_IROHA,
"katakana": LIST_STYLE_TYPE_KATAKANA,
"katakana-iroha": LIST_STYLE_TYPE_KATAKANA_IROHA,
"japanese-informal": LIST_STYLE_TYPE_JAPANESE_INFORMAL
}
return cssIdent(ListStyleMap, cval)
func cssVerticalAlign(cval: CSSComponentValue):
Opt[CSSVerticalAlign] =
if isToken(cval):
let tok = getToken(cval)
if tok.tokenType == CSS_IDENT_TOKEN:
const VerticalAlignMap = {
"baseline": VERTICAL_ALIGN_BASELINE,
"sub": VERTICAL_ALIGN_SUB,
"super": VERTICAL_ALIGN_SUPER,
"text-top": VERTICAL_ALIGN_TEXT_BOTTOM,
"middle": VERTICAL_ALIGN_MIDDLE,
"top": VERTICAL_ALIGN_TOP,
"bottom": VERTICAL_ALIGN_BOTTOM
}
let va2 = ?cssIdent(VerticalAlignMap, cval)
return ok(CSSVerticalAlign(
keyword: va2
))
else:
return ok(CSSVerticalAlign(
keyword: VERTICAL_ALIGN_BASELINE,
length: ?cssLength(tok, has_auto = false)
))
return err()
func cssLineHeight(cval: CSSComponentValue): Opt[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 ok(CSSLengthAuto)
else:
return cssLength(tok, has_auto = false)
return err()
func cssTextAlign(cval: CSSComponentValue): Opt[CSSTextAlign] =
const TextAlignMap = {
"start": TEXT_ALIGN_START,
"end": TEXT_ALIGN_END,
"left": TEXT_ALIGN_LEFT,
"right": TEXT_ALIGN_RIGHT,
"center": TEXT_ALIGN_CENTER,
"justify": TEXT_ALIGN_JUSTIFY,
"-cha-center": TEXT_ALIGN_CHA_CENTER
}
return cssIdent(TextAlignMap, cval)
func cssListStylePosition(cval: CSSComponentValue):
Opt[CSSListStylePosition] =
const ListStylePositionMap = {
"inside": LIST_STYLE_POSITION_INSIDE,
"outside": LIST_STYLE_POSITION_OUTSIDE
}
return cssIdent(ListStylePositionMap, cval)
func cssPosition(cval: CSSComponentValue): Opt[CSSPosition] =
const PositionMap = {
"static": POSITION_STATIC,
"relative": POSITION_RELATIVE,
"absolute": POSITION_ABSOLUTE,
"fixed": POSITION_FIXED,
"sticky": POSITION_STICKY
}
return cssIdent(PositionMap, cval)
func cssCaptionSide(cval: CSSComponentValue): Opt[CSSCaptionSide] =
const CaptionSideMap = {
"top": CAPTION_SIDE_TOP,
"bottom": CAPTION_SIDE_BOTTOM,
"left": CAPTION_SIDE_LEFT,
"right": CAPTION_SIDE_RIGHT,
"block-start": CAPTION_SIDE_BLOCK_START,
"block-end": CAPTION_SIDE_BLOCK_END,
"inline-start": CAPTION_SIDE_INLINE_START,
"inline-end": CAPTION_SIDE_INLINE_END
}
return cssIdent(CaptionSideMap, cval)
func cssBorderCollapse(cval: CSSComponentValue):
Opt[CSSBorderCollapse] =
const BorderCollapseMap = {
"collapse": BORDER_COLLAPSE_COLLAPSE,
"separate": BORDER_COLLAPSE_SEPARATE
}
return cssIdent(BorderCollapseMap, cval)
func cssCounterReset(d: CSSDeclaration): Opt[seq[CSSCounterReset]] =
template die =
return err()
var r: CSSCounterReset
var s = false
var res: seq[CSSCounterReset]
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:
die
r.name = tok.value
s = true
of CSS_NUMBER_TOKEN:
if not s:
die
r.num = int(tok.nvalue)
res.add(r)
s = false
else:
die
return ok(res)
func cssMaxMinSize(cval: CSSComponentValue): Opt[CSSLength] =
if isToken(cval):
let tok = getToken(cval)
case tok.tokenType
of CSS_IDENT_TOKEN:
if tok.value.equalsIgnoreCase("none"):
return ok(CSSLengthAuto)
of CSS_NUMBER_TOKEN, CSS_DIMENSION_TOKEN, CSS_PERCENTAGE_TOKEN:
return cssLength(tok, allow_negative = false)
else: discard
return err()
#TODO should be URL (parsed with baseurl of document...)
func cssURL(cval: CSSComponentValue): Option[string] =
if isToken(cval):
let tok = getToken(cval)
if tok == CSS_URL_TOKEN:
return some(tok.value)
elif cval of CSSFunction:
let fun = CSSFunction(cval)
if fun.name.equalsIgnoreCase("url") or fun.name.equalsIgnoreCase("src"):
for x in fun.value:
if not isToken(x):
break
let x = getToken(x)
if x == CSS_WHITESPACE_TOKEN:
discard
elif x == CSS_STRING_TOKEN:
return some(x.value)
else:
break
#TODO this should be bg-image, add gradient, etc etc
func cssImage(cval: CSSComponentValue): Opt[CSSContent] =
if isToken(cval):
#TODO bg-image only
let tok = getToken(cval)
if tok.tokenType == CSS_IDENT_TOKEN and tok.value == "none":
return ok(CSSContent(t: CONTENT_IMAGE, s: ""))
let url = cssURL(cval)
if url.isSome:
return ok(CSSContent(t: CONTENT_IMAGE, s: url.get))
return err()
func cssInteger(cval: CSSComponentValue, range: Slice[int]):
Opt[int] =
if isToken(cval):
let tok = getToken(cval)
if tok.tokenType == CSS_NUMBER_TOKEN:
if tok.nvalue in float64(range.a)..float64(range.b):
return ok(int(tok.nvalue))
return err()
func cssFloat(cval: CSSComponentValue): Opt[CSSFloat] =
const FloatMap = {
"none": FLOAT_NONE,
"left": FLOAT_LEFT,
"right": FLOAT_RIGHT
}
return cssIdent(FloatMap, cval)
func cssVisibility(cval: CSSComponentValue): Opt[CSSVisibility] =
const VisibilityMap = {
"visible": VISIBILITY_VISIBLE,
"hidden": VISIBILITY_HIDDEN,
"collapse": VISIBILITY_COLLAPSE
}
return cssIdent(VisibilityMap, cval)
func cssBoxSizing(cval: CSSComponentValue): Opt[CSSBoxSizing] =
const BoxSizingMap = {
"border-box": BOX_SIZING_BORDER_BOX,
"content-box": BOX_SIZING_CONTENT_BOX
}
return cssIdent(BoxSizingMap, cval)
func cssClear(cval: CSSComponentValue): Opt[CSSClear] =
const ClearMap = {
"none": CLEAR_NONE,
"left": CLEAR_LEFT,
"right": CLEAR_RIGHT,
"both": CLEAR_BOTH,
"inline-start": CLEAR_INLINE_START,
"inline-end": CLEAR_INLINE_END
}
return cssIdent(ClearMap, cval)
func cssTextTransform(cval: CSSComponentValue): Opt[CSSTextTransform] =
const TextTransformMap = {
"none": TEXT_TRANSFORM_NONE,
"capitalize": TEXT_TRANSFORM_CAPITALIZE,
"uppercase": TEXT_TRANSFORM_UPPERCASE,
"lowercase": TEXT_TRANSFORM_LOWERCASE,
"full-width": TEXT_TRANSFORM_FULL_WIDTH,
"full-size-kana": TEXT_TRANSFORM_FULL_SIZE_KANA,
"-cha-half-width": TEXT_TRANSFORM_CHA_HALF_WIDTH
}
return cssIdent(TextTransformMap, cval)
proc getValueFromDecl(val: CSSComputedValue, d: CSSDeclaration,
vtype: CSSValueType, ptype: CSSPropertyType): Err[void] =
var i = 0
d.value.skipWhitespace(i)
if i >= d.value.len:
return err()
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:
val.length = ?cssMaxMinSize(cval)
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:
val.quotes = ?cssQuotes(d)
of VALUE_COUNTER_RESET:
val.counterreset = ?cssCounterReset(d)
of VALUE_IMAGE:
val.image = ?cssImage(cval)
of VALUE_FLOAT:
val.float = ?cssFloat(cval)
of VALUE_VISIBILITY:
val.visibility = ?cssVisibility(cval)
of VALUE_BOX_SIZING:
val.boxsizing = ?cssBoxSizing(cval)
of VALUE_CLEAR:
val.clear = ?cssClear(cval)
of VALUE_TEXT_TRANSFORM:
val.texttransform = ?cssTextTransform(cval)
of VALUE_NONE:
discard
return ok()
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 CSSLengthAuto
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]
type CSSComputedValueMaybeGlobal = (CSSComputedValue, CSSGlobalValueType)
func getComputedValue(d: CSSDeclaration, ptype: CSSPropertyType,
vtype: CSSValueType): Opt[CSSComputedValueMaybeGlobal] =
let global = cssGlobal(d)
let val = CSSComputedValue(t: ptype, v: vtype)
if global != VALUE_NOGLOBAL:
return ok((val, global))
?val.getValueFromDecl(d, vtype, ptype)
return ok((val, global))
func lengthShorthand(d: CSSDeclaration, props: array[4, CSSPropertyType]):
Opt[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
var res: seq[(CSSComputedValue, CSSGlobalValueType)]
case cvals.len
of 1: # top, bottom, left, right
for ptype in props:
let vtype = valueType(ptype)
let val = CSSComputedValue(t: ptype, v: vtype)
?val.getValueFromDecl(d, vtype, ptype)
res.add((val, cssGlobal(d)))
of 2: # top, bottom | left, right
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])
res.add((val, cssGlobal(d)))
of 3: # top | left, right | bottom
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])
res.add((val, cssGlobal(d)))
of 4: # top | right | bottom | left
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])
res.add((val, cssGlobal(d)))
else: discard
return ok(res)
const PropertyMarginSpec = [
PROPERTY_MARGIN_TOP, PROPERTY_MARGIN_RIGHT, PROPERTY_MARGIN_BOTTOM,
PROPERTY_MARGIN_LEFT
]
const PropertyPaddingSpec = [
PROPERTY_PADDING_TOP, PROPERTY_PADDING_RIGHT, PROPERTY_PADDING_BOTTOM,
PROPERTY_PADDING_LEFT
]
proc getComputedValues0(d: CSSDeclaration):
Opt[seq[(CSSComputedValue, CSSGlobalValueType)]] =
let name = d.name
var res: seq[(CSSComputedValue, CSSGlobalValueType)]
case shorthandType(name)
of SHORTHAND_NONE:
let ptype = propertyType(name)
let vtype = valueType(ptype)
res.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)
res.add((val, global))
of SHORTHAND_MARGIN:
res.add(?lengthShorthand(d, PropertyMarginSpec))
of SHORTHAND_PADDING:
res.add(?lengthShorthand(d, PropertyPaddingSpec))
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:
if tok == CSS_WHITESPACE_TOKEN:
continue
let r = cssImage(tok)
if r.isOk:
bgimageval.image = r.get
res.add((bgimageval, global))
else:
let r = cssColor(tok)
if r.isOk:
bgcolorval.color = r.get
res.add((bgcolorval, global))
else:
res.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:
let r = cssListStylePosition(tok)
if r.isOk:
positionval.liststyleposition = r.get
res.add((positionval, global))
else:
let r = cssListStyleType(tok)
if r.isOk:
typeval.liststyletype = r.get
res.add((typeval, global))
else:
#TODO list-style-image
discard
return ok(res)
proc getComputedValues(d: CSSDeclaration):
seq[(CSSComputedValue, CSSGlobalValueType)] =
return getComputedValues0(d).get(@[])
proc newComputedValueBuilder*(parent: CSSComputedValues): CSSComputedValuesBuilder =
return CSSComputedValuesBuilder(
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)
if result{"float"} != FLOAT_NONE:
case result{"display"}
of DISPLAY_BLOCK, DISPLAY_TABLE, DISPLAY_LIST_ITEM, DISPLAY_NONE,
DISPLAY_FLOW_ROOT:
#TODO flex, grid
discard
{.linearScanEnd.}
of DISPLAY_INLINE, DISPLAY_INLINE_BLOCK, DISPLAY_TABLE_ROW,
DISPLAY_TABLE_ROW_GROUP, DISPLAY_TABLE_COLUMN,
DISPLAY_TABLE_COLUMN_GROUP, DISPLAY_TABLE_CELL, DISPLAY_TABLE_CAPTION,
DISPLAY_TABLE_HEADER_GROUP, DISPLAY_TABLE_FOOTER_GROUP:
result{"display"} = DISPLAY_BLOCK
of DISPLAY_INLINE_TABLE:
result{"display"} = DISPLAY_TABLE