diff options
author | bptato <nincsnevem662@gmail.com> | 2025-01-15 23:43:45 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2025-01-16 01:58:42 +0100 |
commit | e76118ea080646cff4ed3017a9423ca299806e05 (patch) | |
tree | ce1edad9782ff2d9b235add33e6e53ce175f87dc | |
parent | 5209268ce43e78dabdddeec761f431fb0f23fe5b (diff) | |
download | chawan-e76118ea080646cff4ed3017a9423ca299806e05.tar.gz |
cascade: basic CSS variable support
and once again, there was light... Well, it barely works, but it's enough to get colors back on most sites. Nested variables aren't supported for now, and shorthand expansion only "works" for `background' (it's a hack). Props to the W3C for introducing the C preprocessor to CSS - I was starting to confuse it with JSSS after calc().
-rw-r--r-- | doc/troubleshooting.md | 2 | ||||
-rw-r--r-- | src/css/cascade.nim | 177 | ||||
-rw-r--r-- | src/css/cssvalues.nim | 247 | ||||
-rw-r--r-- | src/css/sheet.nim | 25 | ||||
-rw-r--r-- | test/layout/variables.color.expected | 2 | ||||
-rw-r--r-- | test/layout/variables.html | 7 |
6 files changed, 296 insertions, 164 deletions
diff --git a/doc/troubleshooting.md b/doc/troubleshooting.md index 6fce6fe7..d5ed7401 100644 --- a/doc/troubleshooting.md +++ b/doc/troubleshooting.md @@ -83,7 +83,7 @@ To do the same for HTML and ANSI text, use `plaintext, pre`. ## Why does `$WEBSITE` look awful? Usually, this is because it uses some CSS features that are not yet implemented -in Chawan. The most common offenders are grid and CSS variables. +in Chawan. The most common offenders are grid and calc(). There are three ways of dealing with this: diff --git a/src/css/cascade.nim b/src/css/cascade.nim index 0bdd4116..0b23a9a3 100644 --- a/src/css/cascade.nim +++ b/src/css/cascade.nim @@ -24,6 +24,8 @@ type RuleListEntry = object normal: seq[CSSComputedEntry] important: seq[CSSComputedEntry] + normalVars: seq[CSSVariable] + importantVars: seq[CSSVariable] RuleList = array[CSSOrigin, RuleListEntry] @@ -36,6 +38,16 @@ type ToSorts = array[PseudoElement, seq[RulePair]] + InitType = enum + itUserAgent, itUser, itOther + + InitMap = array[CSSPropertyType, set[InitType]] + +# Forward declarations +proc applyValue(vals: CSSValues; entry: CSSComputedEntry; + parentComputed, previousOrigin: CSSValues; initMap: var InitMap; + initType: InitType; nextInitType: set[InitType]; window: Window) + proc calcRule(tosorts: var ToSorts; element: Element; depends: var DependencyInfo; rule: CSSRuleDef) = for sel in rule.sels: @@ -46,9 +58,14 @@ proc calcRule(tosorts: var ToSorts; element: Element; else: tosorts[sel.pseudo].add((sel.specificity, rule)) -func calcRules0(map: RuleListMap; styledNode: StyledNode; sheet: CSSStylesheet; +proc add(entry: var RuleListEntry; rule: CSSRuleDef) = + entry.normal.add(rule.normalVals) + entry.important.add(rule.importantVals) + entry.normalVars.add(rule.normalVars) + entry.importantVars.add(rule.importantVars) + +proc calcRules0(map: RuleListMap; styledNode: StyledNode; sheet: CSSStylesheet; origin: CSSOrigin) = - var tosorts = ToSorts.default let element = styledNode.element var rules: seq[CSSRuleDef] = @[] sheet.tagTable.withValue(element.localName, v): @@ -64,29 +81,95 @@ func calcRules0(map: RuleListMap; styledNode: StyledNode; sheet: CSSStylesheet; rules.add(v[]) for rule in sheet.generalList: rules.add(rule) + var tosorts = ToSorts.default for rule in rules: tosorts.calcRule(element, styledNode.depends, rule) for pseudo, it in tosorts.mpairs: - it.sort(proc(x, y: (int, CSSRuleDef)): int = - let n = cmp(x[0], y[0]) + it.sort(proc(x, y: RulePair): int = + let n = cmp(x.specificity, y.specificity) if n != 0: return n - return cmp(x[1].idx, y[1].idx), order = Ascending) + return cmp(x.rule.idx, y.rule.idx), order = Ascending) for item in it: - map.rules[pseudo][origin].normal.add(item[1].normalVals) - map.rules[pseudo][origin].important.add(item[1].importantVals) + map.rules[pseudo][origin].add(item.rule) + +proc findVariable(computed: CSSValues; varName: CAtom): CSSVariable = + var vars = computed.vars + while vars != nil: + let cvar = vars.table.getOrDefault(varName) + if cvar != nil: + return cvar + vars = vars.parent + return nil + +proc applyVariable(vals: CSSValues; t: CSSPropertyType; varName: CAtom; + fallback: ref CSSComputedEntry; parentComputed, previousOrigin: CSSValues; + initMap: var InitMap; initType: InitType; nextInitType: set[InitType]; + window: Window) = + let v = t.valueType + let cvar = vals.findVariable(varName) + if cvar == nil: + if fallback != nil: + vals.applyValue(fallback[], parentComputed, previousOrigin, initMap, + initType, nextInitType, window) + return + for (iv, entry) in cvar.resolved.mitems: + if iv == v: + entry.t = t # must override, same var can be used for different props + vals.applyValue(entry, parentComputed, previousOrigin, initMap, initType, + nextInitType, window) + return + var entries: seq[CSSComputedEntry] = @[] + assert window != nil + if entries.parseComputedValues($t, cvar.cvals, window.attrsp[], + window.factory).isSome: + if entries[0].et != ceVar: + cvar.resolved.add((v, entries[0])) + vals.applyValue(entries[0], parentComputed, previousOrigin, initMap, + initType, nextInitType, window) + +proc applyGlobal(vals: CSSValues; t: CSSPropertyType; global: CSSGlobalType; + parentComputed, previousOrigin: CSSValues; initMap: InitMap; + initType: InitType) = + case global + of cgtInherit: + vals.initialOrCopyFrom(parentComputed, t) + of cgtInitial: + vals.setInitial(t) + of cgtUnset: + vals.initialOrInheritFrom(parentComputed, t) + of cgtRevert: + if previousOrigin != nil and initType in initMap[t]: + vals.copyFrom(previousOrigin, t) + else: + vals.initialOrInheritFrom(parentComputed, t) + +proc applyValue(vals: CSSValues; entry: CSSComputedEntry; + parentComputed, previousOrigin: CSSValues; initMap: var InitMap; + initType: InitType; nextInitType: set[InitType]; window: Window) = + case entry.et + of ceBit: vals.bits[entry.t].dummy = entry.bit + of ceWord: vals.words[entry.t] = entry.word + of ceObject: vals.objs[entry.t] = entry.obj + of ceGlobal: + vals.applyGlobal(entry.t, entry.global, parentComputed, previousOrigin, + initMap, initType) + of ceVar: + vals.applyVariable(entry.t, entry.cvar, entry.fallback, parentComputed, + previousOrigin, initMap, initType, nextInitType, window) + return # maybe it applies, maybe it doesn't... + initMap[entry.t] = initMap[entry.t] + nextInitType proc applyPresHints(computed: CSSValues; element: Element; - attrs: WindowAttributes; initMap: var InitMap) = + attrs: WindowAttributes; initMap: var InitMap, window: Window) = template set_cv(t, x, b: untyped) = - computed.applyValue(makeEntry(t, CSSValueWord(x: b)), nil, nil, nil, - initMap, itUserAgent) - initMap[t].incl(itUser) + computed.applyValue(makeEntry(t, CSSValueWord(x: b)), nil, nil, initMap, + itUserAgent, {itUser}, window) template set_cv_new(t, x, b: untyped) = const v = valueType(t) let val = CSSValue(v: v, x: b) - computed.applyValue(makeEntry(t, val), nil, nil, nil, initMap, itUserAgent) - initMap[t].incl(itUser) + computed.applyValue(makeEntry(t, val), nil, nil, initMap, itUserAgent, + {itUser}, window) template map_width = let s = parseDimensionValues(element.attr(satWidth)) if s.isSome: @@ -140,8 +223,8 @@ proc applyPresHints(computed: CSSValues; element: Element; template set_bgcolor_is_canvas = let t = cptBgcolorIsCanvas let val = CSSValueBit(bgcolorIsCanvas: true) - computed.applyValue(makeEntry(t, val), nil, nil, nil, initMap, itUserAgent) - initMap[t].incl(itUser) + computed.applyValue(makeEntry(t, val), nil, nil, initMap, itUserAgent, + {itUser}, window) template map_cellspacing = let s = element.attrul(satCellspacing) if s.isSome: @@ -196,39 +279,60 @@ proc applyPresHints(computed: CSSValues; element: Element; set_cv cptHeight, length, resolveLength(cuEm, float32(size), attrs) else: discard -func applyDeclarations0(rules: RuleList; parent: CSSValues; element: Element; +proc applyDeclarations0(rules: RuleList; parent, element: Element; window: Window): CSSValues = result = CSSValues() + var parentComputed: CSSValues = nil + var parentVars: CSSVariableMap = nil + if parent != nil: + parentComputed = parent.computed + parentVars = parentComputed.vars + for origin in CSSOrigin: + if rules[origin].importantVars.len > 0: + if result.vars == nil: + result.vars = newCSSVariableMap(parentVars) + for i in countdown(rules[origin].importantVars.high, 0): + let cvar = rules[origin].importantVars[i] + result.vars.putIfAbsent(cvar.name, cvar) + for origin in countdown(CSSOrigin.high, CSSOrigin.low): + if rules[origin].normalVars.len > 0: + if result.vars == nil: + result.vars = newCSSVariableMap(parentVars) + for i in countdown(rules[origin].normalVars.high, 0): + let cvar = rules[origin].normalVars[i] + result.vars.putIfAbsent(cvar.name, cvar) + if result.vars == nil: + result.vars = parentVars # inherit parent var initMap = InitMap.default for entry in rules[coUserAgent].normal: # user agent - result.applyValue(entry, nil, parent, nil, initMap, itOther) - initMap[entry.t] = {itUserAgent, itUser} + result.applyValue(entry, parentComputed, nil, initMap, itOther, + {itUserAgent, itUser}, window) let uaProperties = result.copyProperties() # Presentational hints override user agent style, but respect user/author # style. if element != nil: - result.applyPresHints(element, window.attrsp[], initMap) + result.applyPresHints(element, window.attrsp[], initMap, window) for entry in rules[coUser].normal: # user - result.applyValue(entry, nil, parent, uaProperties, initMap, itUserAgent) - initMap[entry.t].incl(itUser) + result.applyValue(entry, parentComputed, uaProperties, initMap, itUserAgent, + {itUser}, window) # save user properties so author can use them let userProperties = result.copyProperties() for entry in rules[coAuthor].normal: # author - result.applyValue(entry, nil, parent, userProperties, initMap, itUser) - initMap[entry.t].incl(itOther) + result.applyValue(entry, parentComputed, userProperties, initMap, itUser, + {itOther}, window) for entry in rules[coAuthor].important: # author important - result.applyValue(entry, nil, parent, userProperties, initMap, itUser) - initMap[entry.t].incl(itOther) + result.applyValue(entry, parentComputed, userProperties, initMap, itUser, + {itOther}, window) for entry in rules[coUser].important: # user important - result.applyValue(entry, nil, parent, uaProperties, initMap, itUserAgent) - initMap[entry.t].incl(itOther) + result.applyValue(entry, parentComputed, uaProperties, initMap, itUserAgent, + {itOther}, window) for entry in rules[coUserAgent].important: # user agent important - result.applyValue(entry, nil, parent, nil, initMap, itUserAgent) - initMap[entry.t].incl(itOther) + result.applyValue(entry, parentComputed, nil, initMap, itUserAgent, + {itOther}, window) # set defaults for t in CSSPropertyType: if initMap[t] == {}: - result.initialOrInheritFrom(parent, t) + result.initialOrInheritFrom(parentComputed, t) # Quirk: it seems others aren't implementing what the spec says about # blockification. # Well, neither will I, because the spec breaks on actual websites. @@ -243,12 +347,12 @@ func applyDeclarations0(rules: RuleList; parent: CSSValues; element: Element; result{"overflow-x"} = result{"overflow-x"}.bfcify() result{"overflow-y"} = result{"overflow-y"}.bfcify() -proc applyDeclarations(styledNode: StyledNode; parent: CSSValues; +proc applyDeclarations(styledNode: StyledNode; parent: Element; map: RuleListMap; window: Window; pseudo = peNone) = let element = if styledNode.pseudo == peNone: styledNode.element else: nil styledNode.computed = map.rules[pseudo].applyDeclarations0(parent, element, window) - if element != nil and window.settings.scripting == smApp: + if element != nil: element.computed = styledNode.computed func hasValues(rules: RuleList): bool = @@ -267,7 +371,7 @@ func applyMediaQuery(ss: CSSStylesheet; window: Window): CSSStylesheet = res.add(mq.children.applyMediaQuery(window)) return res -func calcRules(styledNode: StyledNode; ua, user: CSSStylesheet; +proc calcRules(styledNode: StyledNode; ua, user: CSSStylesheet; author: seq[CSSStylesheet]; window: Window): RuleListMap = let map = RuleListMap() map.calcRules0(styledNode, ua, coUserAgent) @@ -278,6 +382,7 @@ func calcRules(styledNode: StyledNode; ua, user: CSSStylesheet; let style = styledNode.element.cachedStyle if window.styling and style != nil: for decl in style.decls: + #TODO variables let vals = parseComputedValues(decl.name, decl.value, window.attrsp[], window.factory) if decl.important: @@ -328,10 +433,10 @@ proc applyRulesFrameInvalid(frame: CascadeFrame; ua, user: CSSStylesheet; let styledChild = newStyledElement(element) map = styledChild.calcRules(ua, user, author, window) if styledParent == nil: # root element - styledChild.applyDeclarations(rootProperties(), map, window) + styledChild.applyDeclarations(nil, map, window) else: styledParent.children.add(styledChild) - styledChild.applyDeclarations(styledParent.computed, map, window) + styledChild.applyDeclarations(styledParent.element, map, window) return styledChild elif child of Text: let text = Text(child) @@ -342,7 +447,7 @@ proc applyRulesFrameInvalid(frame: CascadeFrame; ua, user: CSSStylesheet; let map = frame.parentMap if map.rules[pseudo].hasValues(): let styledPseudo = styledParent.newStyledElement(pseudo) - styledPseudo.applyDeclarations(styledParent.computed, map, nil, pseudo) + styledPseudo.applyDeclarations(styledParent.element, map, window, pseudo) if styledPseudo.computed{"content"}.len > 0: for content in styledPseudo.computed{"content"}: let child = styledPseudo.newStyledReplacement(content, peNone) diff --git a/src/css/cssvalues.nim b/src/css/cssvalues.nim index 8fba787b..d6763372 100644 --- a/src/css/cssvalues.nim +++ b/src/css/cssvalues.nim @@ -146,8 +146,7 @@ type cvtNumber = "number" cvtOverflow = "overflow" - CSSGlobalType = enum - cgtNone = "" + CSSGlobalType* = enum cgtInitial = "initial" cgtInherit = "inherit" cgtRevert = "revert" @@ -355,7 +354,7 @@ type b*: CSSLength CSSValueBit* {.union.} = object - dummy: uint8 + dummy*: uint8 bgcolorIsCanvas*: bool borderCollapse*: CSSBorderCollapse boxSizing*: CSSBoxSizing @@ -399,10 +398,17 @@ type image*: NetworkBitmap else: discard + # Linked list of variable maps, except empty maps are skipped. + # Must not be nil for CSSValues, at least during cascade. + CSSVariableMap* = ref object + parent*: CSSVariableMap + table*: Table[CAtom, CSSVariable] + CSSValues* = ref object bits*: array[CSSPropertyType.low..LastBitPropType, CSSValueBit] words*: array[FirstWordPropType..LastWordPropType, CSSValueWord] objs*: array[FirstObjPropType..CSSPropertyType.high, CSSValue] + vars*: CSSVariableMap CSSOrigin* = enum coUserAgent @@ -410,11 +416,12 @@ type coAuthor CSSEntryType* = enum - ceBit, ceObject, ceWord, ceVar + ceBit, ceObject, ceWord, ceVar, ceGlobal CSSComputedEntry* = object + # put it here, so ComputedEntry remains 2 words wide + cvar*: CAtom t*: CSSPropertyType - global*: CSSGlobalType case et*: CSSEntryType of ceBit: bit*: uint8 @@ -423,17 +430,21 @@ type of ceObject: obj*: CSSValue of ceVar: - cvar*: CAtom + fallback*: ref CSSComputedEntry + of ceGlobal: + global*: CSSGlobalType CSSVariable* = ref object name*: CAtom + important*: bool cvals*: seq[CSSComponentValue] - resolved: seq[tuple[v: CSSValueType; entry: CSSComputedEntry]] + resolved*: seq[tuple[v: CSSValueType; entry: CSSComputedEntry]] static: doAssert sizeof(CSSValueBit) == 1 doAssert sizeof(CSSValueWord) <= 8 doAssert sizeof(CSSValue()[]) <= 16 + doAssert sizeof(CSSComputedEntry()) <= 16 const ValueTypes = [ # bits @@ -508,6 +519,17 @@ const OverflowScrollLike* = {OverflowScroll, OverflowAuto, OverflowOverlay} const OverflowHiddenLike* = {OverflowHidden, OverflowClip} const FlexReverse* = {FlexDirectionRowReverse, FlexDirectionColumnReverse} +# Forward declarations +proc parseValue(cvals: openArray[CSSComponentValue]; t: CSSPropertyType; + entry: var CSSComputedEntry; attrs: WindowAttributes; factory: CAtomFactory): + Opt[void] + +proc newCSSVariableMap*(parent: CSSVariableMap): CSSVariableMap = + return CSSVariableMap(parent: parent) + +proc putIfAbsent*(map: CSSVariableMap; name: CAtom; cvar: CSSVariable) = + discard map.table.hasKeyOrPut(name, cvar) + type CSSPropertyReprType* = enum cprtBit, cprtWord, cprtObject @@ -904,7 +926,15 @@ func parseDimensionValues*(s: string): Option[CSSLength] = func skipBlanks*(vals: openArray[CSSComponentValue]; i: int): int = var i = i while i < vals.len: - if not (vals[i] of CSSToken) or CSSToken(vals[i]).t != cttWhitespace: + if vals[i] != cttWhitespace: + break + inc i + return i + +func findBlank(vals: openArray[CSSComponentValue]; i: int): int = + var i = i + while i < vals.len: + if vals[i] == cttWhitespace: break inc i return i @@ -1055,8 +1085,8 @@ func cssAbsoluteLength(val: CSSComponentValue; attrs: WindowAttributes): else: discard return err() -func cssGlobal(cval: CSSComponentValue): CSSGlobalType = - return parseIdent[CSSGlobalType](cval).get(cgtNone) +func parseGlobal(cval: CSSComponentValue): Opt[CSSGlobalType] = + return parseIdent[CSSGlobalType](cval) func parseQuotes(cvals: openArray[CSSComponentValue]): Opt[CSSQuotes] = var i = cvals.skipBlanks(0) @@ -1256,25 +1286,23 @@ func cssNumber(cval: CSSComponentValue; positive: bool): Opt[float32] = return ok(tok.nvalue) return err() -proc makeEntry*(t: CSSPropertyType; obj: CSSValue; global = cgtNone): - CSSComputedEntry = - return CSSComputedEntry(et: ceObject, t: t, obj: obj, global: global) +proc makeEntry*(t: CSSPropertyType; obj: CSSValue): CSSComputedEntry = + return CSSComputedEntry(et: ceObject, t: t, obj: obj) -proc makeEntry*(t: CSSPropertyType; word: CSSValueWord; global = cgtNone): - CSSComputedEntry = - return CSSComputedEntry(et: ceWord, t: t, word: word, global: global) +proc makeEntry*(t: CSSPropertyType; word: CSSValueWord): CSSComputedEntry = + return CSSComputedEntry(et: ceWord, t: t, word: word) -proc makeEntry*(t: CSSPropertyType; bit: CSSValueBit; global = cgtNone): - CSSComputedEntry = - return CSSComputedEntry(et: ceBit, t: t, bit: bit.dummy, global: global) +proc makeEntry*(t: CSSPropertyType; bit: CSSValueBit): CSSComputedEntry = + return CSSComputedEntry(et: ceBit, t: t, bit: bit.dummy) proc makeEntry*(t: CSSPropertyType; global: CSSGlobalType): CSSComputedEntry = - return CSSComputedEntry(et: ceObject, t: t, global: global) + return CSSComputedEntry(et: ceGlobal, t: t, global: global) -proc parseVar(fun: CSSFunction; entry: var CSSComputedEntry; - attrs: WindowAttributes; factory: CAtomFactory): Opt[void] = - let i = fun.value.skipBlanks(0) - if i >= fun.value.len or fun.value.skipBlanks(i + 1) < fun.value.len: +proc parseVariable(fun: CSSFunction; t: CSSPropertyType; + entry: var CSSComputedEntry; attrs: WindowAttributes; + factory: CAtomFactory): Opt[void] = + var i = fun.value.skipBlanks(0) + if i >= fun.value.len: return err() let cval = fun.value[i] if not (cval of CSSToken): @@ -1284,11 +1312,22 @@ proc parseVar(fun: CSSFunction; entry: var CSSComputedEntry; return err() entry = CSSComputedEntry( et: ceVar, + t: t, cvar: factory.toAtom(tok.value.substr(2)) ) + i = fun.value.skipBlanks(i + 1) + if i < fun.value.len: + if fun.value[i] != cttComma: + return err() + i = fun.value.skipBlanks(i + 1) + if i < fun.value.len: + entry.fallback = (ref CSSComputedEntry)() + if fun.value.toOpenArray(i, fun.value.high).parseValue(t, + entry.fallback[], attrs, factory).isNone: + entry.fallback = nil return ok() -proc parseValue(cvals: openArray[CSSComponentValue]; +proc parseValue(cvals: openArray[CSSComponentValue]; t: CSSPropertyType; entry: var CSSComputedEntry; attrs: WindowAttributes; factory: CAtomFactory): Opt[void] = var i = cvals.skipBlanks(0) @@ -1301,23 +1340,22 @@ proc parseValue(cvals: openArray[CSSComponentValue]; if fun.name == cftVar: if cvals.skipBlanks(i) < cvals.len: return err() - return fun.parseVar(entry, attrs, factory) - let t = entry.t + return fun.parseVariable(t, entry, attrs, factory) let v = valueType(t) template set_new(prop, val: untyped) = entry = CSSComputedEntry( - t: entry.t, + t: t, et: ceObject, obj: CSSValue(v: v, prop: val) ) template set_word(prop, val: untyped) = entry = CSSComputedEntry( - t: entry.t, + t: t, et: ceWord, word: CSSValueWord(prop: val) ) template set_bit(prop, val: untyped) = - entry = CSSComputedEntry(t: entry.t, et: ceBit, bit: cast[uint8](val)) + entry = CSSComputedEntry(t: t, et: ceBit, bit: cast[uint8](val)) case v of cvtDisplay: set_bit display, ?parseIdent[CSSDisplay](cval) of cvtWhiteSpace: set_bit whiteSpace, ?parseIdent[CSSWhiteSpace](cval) @@ -1422,10 +1460,11 @@ proc getDefaultWord(t: CSSPropertyType): CSSValueWord = else: return CSSValueWord(dummy: 0) func lengthShorthand(cvals: openArray[CSSComponentValue]; - props: array[4, CSSPropertyType]; global: CSSGlobalType; + props: array[4, CSSPropertyType]; global: Opt[CSSGlobalType]; attrs: WindowAttributes; hasAuto = true): Opt[seq[CSSComputedEntry]] = var res: seq[CSSComputedEntry] = @[] - if global != cgtNone: + if global.isSome: + let global = global.get for t in props: res.add(makeEntry(t, global)) return ok(res) @@ -1474,25 +1513,20 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string; var i = cvals.skipBlanks(0) if i >= cvals.len: return err() - let global = cssGlobal(cvals[i]) + let global = parseGlobal(cvals[i]) case shorthandType(name) of cstNone: let t = propertyType(name) if t.isSome: let t = t.get - if global != cgtNone: - res.add(makeEntry(t, global)) + if global.isSome: + res.add(makeEntry(t, global.get)) else: - let et = case t.reprType - of cprtBit: ceBit - of cprtObject: ceObject - of cprtWord: ceWord - var entry = CSSComputedEntry(t: t, et: et) - ?cvals.parseValue(entry, attrs, factory) + var entry = CSSComputedEntry() + ?cvals.parseValue(t, entry, attrs, factory) res.add(entry) of cstAll: - if global == cgtNone: - return err() + let global = ?global for t in CSSPropertyType: res.add(makeEntry(t, global)) of cstMargin: @@ -1501,29 +1535,42 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string; res.add(?lengthShorthand(cvals, PropertyPaddingSpec, global, attrs, hasAuto = false)) of cstBackground: - var bgcolorval = getDefaultWord(cptBackgroundColor) - var bgimageval = getDefault(cptBackgroundImage) - var valid = true - if global == cgtNone: - for tok in cvals: - if tok == cttWhitespace: - continue - if (let r = parseImage(tok); r.isSome): - bgimageval = CSSValue(v: cvtImage, image: r.get) - elif (let r = cssColor(tok); r.isSome): - bgcolorval.color = r.get + if global.isSome: + let global = global.get + res.add(makeEntry(cptBackgroundColor, global)) + res.add(makeEntry(cptBackgroundImage, global)) + else: + var bgcolor = makeEntry(cptBackgroundColor, + getDefaultWord(cptBackgroundColor)) + var bgimage = makeEntry(cptBackgroundImage, + getDefault(cptBackgroundImage)) + var valid = true + var i = cvals.skipBlanks(0) + while i < cvals.len: + let j = cvals.findBlank(i) + if cvals.toOpenArray(i, j - 1).parseValue(bgcolor.t, bgcolor, attrs, + factory).isSome: + discard + elif cvals.toOpenArray(i, j - 1).parseValue(bgimage.t, bgimage, attrs, + factory).isSome: + discard else: #TODO when we implement the other shorthands too #valid = false discard - if valid: - res.add(makeEntry(cptBackgroundColor, bgcolorval, global)) - res.add(makeEntry(cptBackgroundImage, bgimageval, global)) + i = cvals.skipBlanks(j) + if valid: + res.add(bgcolor) + res.add(bgimage) of cstListStyle: - var positionVal = CSSValueBit() var typeVal = CSSValueBit() - var valid = true - if global == cgtNone: + if global.isSome: + let global = global.get + res.add(makeEntry(cptListStylePosition, global)) + res.add(makeEntry(cptListStyleType, global)) + else: + var valid = true + var positionVal = CSSValueBit() for tok in cvals: if tok == cttWhitespace: continue @@ -1535,11 +1582,16 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string; #TODO list-style-image #valid = false discard - if valid: - res.add(makeEntry(cptListStylePosition, positionVal, global)) - res.add(makeEntry(cptListStyleType, typeVal, global)) + if valid: + res.add(makeEntry(cptListStylePosition, positionVal)) + res.add(makeEntry(cptListStyleType, typeVal)) of cstFlex: - if global == cgtNone: + if global.isSome: + let global = global.get + res.add(makeEntry(cptFlexGrow, global)) + res.add(makeEntry(cptFlexShrink, global)) + res.add(makeEntry(cptFlexBasis, global)) + else: var i = cvals.skipBlanks(0) if i >= cvals.len: return err() @@ -1568,13 +1620,13 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string; res.add(makeEntry(cptFlexBasis, val)) else: # omitted, default to 0px let val = CSSValueWord(length: cssLength(0)) - res.add(makeEntry(cptFlexBasis, val, global)) - else: - res.add(makeEntry(cptFlexGrow, global)) - res.add(makeEntry(cptFlexShrink, global)) - res.add(makeEntry(cptFlexBasis, global)) + res.add(makeEntry(cptFlexBasis, val)) of cstFlexFlow: - if global == cgtNone: + if global.isSome: + let global = global.get + res.add(makeEntry(cptFlexDirection, global)) + res.add(makeEntry(cptFlexWrap, global)) + else: var i = cvals.skipBlanks(0) if i >= cvals.len: return err() @@ -1587,11 +1639,12 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string; let wrap = ?parseIdent[CSSFlexWrap](cvals[i]) var val = CSSValueBit(flexWrap: wrap) res.add(makeEntry(cptFlexWrap, val)) - else: - res.add(makeEntry(cptFlexDirection, global)) - res.add(makeEntry(cptFlexWrap, global)) of cstOverflow: - if global == cgtNone: + if global.isSome: + let global = global.get + res.add(makeEntry(cptOverflowX, global)) + res.add(makeEntry(cptOverflowY, global)) + else: var i = cvals.skipBlanks(0) if i >= cvals.len: return err() @@ -1603,9 +1656,6 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string; y.overflow = ?parseIdent[CSSOverflow](cvals[i]) res.add(makeEntry(cptOverflowX, x)) res.add(makeEntry(cptOverflowY, y)) - else: - res.add(makeEntry(cptOverflowX, global)) - res.add(makeEntry(cptOverflowY, global)) return ok() proc parseComputedValues*(name: string; value: seq[CSSComponentValue]; @@ -1615,13 +1665,13 @@ proc parseComputedValues*(name: string; value: seq[CSSComponentValue]; return res return @[] -proc copyFrom(a, b: CSSValues; t: CSSPropertyType) = +proc copyFrom*(a, b: CSSValues; t: CSSPropertyType) = case t.reprType of cprtBit: a.bits[t] = b.bits[t] of cprtWord: a.words[t] = b.words[t] of cprtObject: a.objs[t] = b.objs[t] -proc setInitial(a: CSSValues; t: CSSPropertyType) = +proc setInitial*(a: CSSValues; t: CSSPropertyType) = case t.reprType of cprtBit: a.bits[t].dummy = 0 of cprtWord: a.words[t] = getDefaultWord(t) @@ -1633,40 +1683,11 @@ proc initialOrInheritFrom*(a, b: CSSValues; t: CSSPropertyType) = else: a.setInitial(t) -type - InitType* = enum - itUserAgent, itUser, itOther - - InitMap* = array[CSSPropertyType, set[InitType]] - - CSSVariableMap* = ref object - parent*: CSSVariableMap - vars*: seq[CSSVariable] - -proc applyValue*(vals: CSSValues; entry: CSSComputedEntry; - varMap: CSSVariableMap; parent, previousOrigin: CSSValues; inited: InitMap; - initType: InitType) = - case entry.global - of cgtInherit: - if parent != nil: - vals.copyFrom(parent, entry.t) - else: - vals.setInitial(entry.t) - of cgtInitial: - vals.setInitial(entry.t) - of cgtUnset: - vals.initialOrInheritFrom(parent, entry.t) - of cgtRevert: - if previousOrigin != nil and initType in inited[entry.t]: - vals.copyFrom(previousOrigin, entry.t) - else: - vals.initialOrInheritFrom(parent, entry.t) - of cgtNone: - case entry.et - of ceBit: vals.bits[entry.t].dummy = entry.bit - of ceWord: vals.words[entry.t] = entry.word - of ceObject: vals.objs[entry.t] = entry.obj - of ceVar: discard #eprint "TODO" +proc initialOrCopyFrom*(a, b: CSSValues; t: CSSPropertyType) = + if b != nil: + a.copyFrom(b, t) + else: + a.setInitial(t) func inheritProperties*(parent: CSSValues): CSSValues = result = CSSValues() diff --git a/src/css/sheet.nim b/src/css/sheet.nim index c0963d2b..f574bd4d 100644 --- a/src/css/sheet.nim +++ b/src/css/sheet.nim @@ -185,31 +185,28 @@ proc addRule(sheet: CSSStylesheet; rule: CSSQualifiedRule) = var sels = parseSelectors(rule.prelude, sheet.factory) if sels.len > 0: let decls = rule.oblock.value.parseDeclarations() - var pass2 = newSeqOfCap[CSSDeclaration](decls.len) let rule = CSSRuleDef(sels: move(sels), idx: sheet.len) for decl in decls: if decl.name.startsWith("--"): let cvar = CSSVariable( - # case sensitive, it seems name: sheet.factory.toAtom(decl.name.substr(2)), - cvals: move(decl.value) + cvals: decl.value ) if decl.important: rule.importantVars.add(cvar) else: rule.normalVars.add(cvar) - pass2.add(decl) - for decl in pass2: - if decl.important: - let olen = rule.importantVals.len - if rule.importantVals.parseComputedValues(decl.name, decl.value, - sheet.attrs[], sheet.factory).isNone: - rule.importantVals.setLen(olen) else: - let olen = rule.normalVals.len - if rule.normalVals.parseComputedValues(decl.name, decl.value, - sheet.attrs[], sheet.factory).isNone: - rule.normalVals.setLen(olen) + if decl.important: + let olen = rule.importantVals.len + if rule.importantVals.parseComputedValues(decl.name, decl.value, + sheet.attrs[], sheet.factory).isNone: + rule.importantVals.setLen(olen) + else: + let olen = rule.normalVals.len + if rule.normalVals.parseComputedValues(decl.name, decl.value, + sheet.attrs[], sheet.factory).isNone: + rule.normalVals.setLen(olen) sheet.add(rule) inc sheet.len diff --git a/test/layout/variables.color.expected b/test/layout/variables.color.expected new file mode 100644 index 00000000..59986144 --- /dev/null +++ b/test/layout/variables.color.expected @@ -0,0 +1,2 @@ +[38;2;255;255;255m[48;2;128;0;128mpurple[39m [49m + diff --git a/test/layout/variables.html b/test/layout/variables.html new file mode 100644 index 00000000..cd6d3696 --- /dev/null +++ b/test/layout/variables.html @@ -0,0 +1,7 @@ +<style> +a { --purple: purple; color: var(--purple) } +div { color: white; --pink: yellow !important; background-color: var(--purple) } +</style> +<a> +<div>purple</div> +</a> |