diff options
author | bptato <nincsnevem662@gmail.com> | 2024-12-16 23:39:46 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-12-16 23:39:46 +0100 |
commit | c514f8c8b2e11557eed512d55321f9c8382ac2cc (patch) | |
tree | 35b27f23895bf3f609748a81ee8762d91e88ef7b /src | |
parent | 3a2652101021105952332036c178fc2a0fb7a41d (diff) | |
download | chawan-c514f8c8b2e11557eed512d55321f9c8382ac2cc.tar.gz |
css: resolve units to px before layout
Lets us skip a couple pointless multiplications/divisions during layout.
Diffstat (limited to 'src')
-rw-r--r-- | src/css/cascade.nim | 45 | ||||
-rw-r--r-- | src/css/cssvalues.nim | 156 | ||||
-rw-r--r-- | src/css/layout.nim | 167 | ||||
-rw-r--r-- | src/css/mediaquery.nim | 22 | ||||
-rw-r--r-- | src/css/render.nim | 4 | ||||
-rw-r--r-- | src/css/sheet.nim | 42 | ||||
-rw-r--r-- | src/html/dom.nim | 21 | ||||
-rw-r--r-- | src/server/buffer.nim | 8 |
8 files changed, 237 insertions, 228 deletions
diff --git a/src/css/cascade.nim b/src/css/cascade.nim index c2ac7f9a..9a49cf48 100644 --- a/src/css/cascade.nim +++ b/src/css/cascade.nim @@ -17,6 +17,7 @@ import html/enums import types/color import types/jscolor import types/opt +import types/winattrs type RuleList* = array[PseudoElem, seq[CSSRuleDef]] @@ -26,9 +27,9 @@ type user: RuleList author: seq[RuleList] -func appliesLR(feature: MediaFeature; window: Window; n: LayoutUnit): bool = - let a = feature.lengthrange.s.a.px(window.attrs, 0) - let b = feature.lengthrange.s.b.px(window.attrs, 0) +func appliesLR(feature: MediaFeature; window: Window; n: float64): bool = + let a = feature.lengthrange.s.a.num + let b = feature.lengthrange.s.b.num return (feature.lengthrange.aeq and a == n or a < n) and (feature.lengthrange.beq and b == n or n < b) @@ -43,9 +44,9 @@ func applies(feature: MediaFeature; window: Window): bool = of mftPrefersColorScheme: return feature.b == window.attrs.prefersDark of mftWidth: - return feature.appliesLR(window, window.attrs.widthPx.toLayoutUnit) + return feature.appliesLR(window, float64(window.attrs.widthPx)) of mftHeight: - return feature.appliesLR(window, window.attrs.heightPx.toLayoutUnit) + return feature.appliesLR(window, float64(window.attrs.heightPx)) of mftScripting: return feature.b == window.settings.scripting @@ -111,7 +112,8 @@ func calcRules(styledNode: StyledNode; sheet: CSSStylesheet): RuleList = for item in tosorts[i]: result[i].add(item[1]) -func calcPresHints(element: Element): seq[CSSComputedEntry] = +func calcPresHints(element: Element; attrs: WindowAttributes): + seq[CSSComputedEntry] = result = @[] template set_cv(t, x, b: untyped) = const v = valueType(t) @@ -141,7 +143,7 @@ func calcPresHints(element: Element): seq[CSSComputedEntry] = template map_size = let s = element.attrul(satSize) if s.isSome: - set_cv cptWidth, length, CSSLength(num: float64(s.get), u: cuCh) + set_cv cptWidth, length, resolveLength(cuCh, float64(s.get), attrs) template map_text = let s = element.attr(satText) if s != "": @@ -173,9 +175,8 @@ func calcPresHints(element: Element): seq[CSSComputedEntry] = template map_cellspacing = let s = element.attrul(satCellspacing) if s.isSome: - set_cv cptBorderSpacing, length2, CSSLength2( - a: CSSLength(num: float64(s.get), u: cuPx) - ) + let n = float64(s.get) + set_cv cptBorderSpacing, length2, CSSLength2(a: cssLength(n)) case element.tagType of TAG_TABLE: @@ -210,8 +211,8 @@ func calcPresHints(element: Element): seq[CSSComputedEntry] = let textarea = HTMLTextAreaElement(element) let cols = textarea.attrul(satCols).get(20) let rows = textarea.attrul(satRows).get(1) - set_cv cptWidth, length, CSSLength(u: cuCh, num: float64(cols)) - set_cv cptHeight, length, CSSLength(u: cuEm, num: float64(rows)) + set_cv cptWidth, length, resolveLength(cuCh, float64(cols), attrs) + set_cv cptHeight, length, resolveLength(cuEm, float64(rows), attrs) of TAG_FONT: map_color of TAG_INPUT: @@ -280,7 +281,7 @@ proc add(map: var CSSValueEntryObj; rules: seq[CSSRuleDef]) = map.important.add(rule.importantVals) proc applyDeclarations(styledNode: StyledNode; parent: CSSValues; - map: RuleListMap; styling: bool) = + map: RuleListMap; window: Window) = var rules: CSSValueEntryMap var presHints: seq[CSSComputedEntry] = @[] rules[coUserAgent].add(map.ua[peNone]) @@ -290,14 +291,14 @@ proc applyDeclarations(styledNode: StyledNode; parent: CSSValues; if styledNode.node != nil: let element = Element(styledNode.node) let style = element.cachedStyle - if styling and style != nil: + if window.styling and style != nil: for decl in style.decls: - let vals = parseComputedValues(decl.name, decl.value) + let vals = parseComputedValues(decl.name, decl.value, window.attrs) if decl.important: rules[coAuthor].important.add(vals) else: rules[coAuthor].normal.add(vals) - presHints = element.calcPresHints() + presHints = element.calcPresHints(window.attrs) styledNode.computed = rules.buildComputedValues(presHints, parent) func hasValues(rules: CSSValueEntryMap): bool = @@ -345,12 +346,12 @@ func calcRules(styledNode: StyledNode; ua, user: CSSStylesheet; ) proc applyStyle(parent, styledNode: StyledNode; map: RuleListMap; - styling: bool) = + window: Window) = let parentComputed = if parent != nil: parent.computed else: rootProperties() - styledNode.applyDeclarations(parentComputed, map, styling) + styledNode.applyDeclarations(parentComputed, map, window) type CascadeFrame = object styledParent: StyledNode @@ -382,7 +383,7 @@ proc applyRulesFrameValid(frame: var CascadeFrame): StyledNode = return cachedChild proc applyRulesFrameInvalid(frame: CascadeFrame; ua, user: CSSStylesheet; - author: seq[CSSStylesheet]; declmap: var RuleListMap; styling: bool): + author: seq[CSSStylesheet]; declmap: var RuleListMap; window: Window): StyledNode = var styledChild: StyledNode = nil let pseudo = frame.pseudo @@ -451,7 +452,7 @@ proc applyRulesFrameInvalid(frame: CascadeFrame; ua, user: CSSStylesheet; styledChild = styledParent.newStyledElement(element) styledParent.children.add(styledChild) declmap = styledChild.calcRules(ua, user, author) - styledParent.applyStyle(styledChild, declmap, styling) + styledParent.applyStyle(styledChild, declmap, window) elif child of Text: let text = Text(child) styledChild = styledParent.newStyledText(text) @@ -461,7 +462,7 @@ proc applyRulesFrameInvalid(frame: CascadeFrame; ua, user: CSSStylesheet; let element = Element(child) styledChild = newStyledElement(element) declmap = styledChild.calcRules(ua, user, author) - styledParent.applyStyle(styledChild, declmap, styling) + styledParent.applyStyle(styledChild, declmap, window) return styledChild proc stackAppend(styledStack: var seq[CascadeFrame]; frame: CascadeFrame; @@ -566,7 +567,7 @@ proc applyRules(document: Document; ua, user: CSSStylesheet; # because of property inheritance. frame.cachedChild = nil frame.applyRulesFrameInvalid(ua, user, author, declmap, - document.window.styling) + document.window) if styledChild != nil: if styledParent == nil: # Root element diff --git a/src/css/cssvalues.nim b/src/css/cssvalues.nim index 581c008a..d22fd414 100644 --- a/src/css/cssvalues.nim +++ b/src/css/cssvalues.nim @@ -311,14 +311,19 @@ type OverflowAuto = "auto" type + CSSLengthType* = enum + clPx = "px" + clAuto = "auto" + clPerc = "%" + CSSLength* = object - u*: CSSUnit + u*: CSSLengthType num*: float64 CSSVerticalAlign* = object keyword*: CSSVerticalAlign2 # inlined CSSLength so that this object fits into 2 words - u*: CSSUnit + u*: CSSLengthType num*: float64 CSSContent* = object @@ -489,7 +494,7 @@ func isSupportedProperty*(s: string): bool = when defined(debug): func `$`*(length: CSSLength): string = - if length.u == cuAuto: + if length.u == clAuto: return "auto" return $length.num & $length.u @@ -553,41 +558,6 @@ macro `{}=`*(vals: CSSValues; s: static string, val: typed) = func inherited*(t: CSSPropertyType): bool = return t in InheritedProperties -func em_to_px(em: float64; window: WindowAttributes): LayoutUnit = - (em * float64(window.ppl)).toLayoutUnit() - -func ch_to_px(ch: float64; window: WindowAttributes): LayoutUnit = - (ch * float64(window.ppc)).toLayoutUnit() - -# 水 width, we assume it's 2 chars -func ic_to_px(ic: float64; window: WindowAttributes): LayoutUnit = - (ic * float64(window.ppc) * 2).toLayoutUnit() - -# x-letter height, we assume it's em/2 -func ex_to_px(ex: float64; window: WindowAttributes): LayoutUnit = - (ex * float64(window.ppc) / 2).toLayoutUnit() - -func px*(l: CSSLength; window: WindowAttributes; p: LayoutUnit): LayoutUnit = - return case l.u - of cuAuto: LayoutUnit(0) - of cuEm, cuRem: em_to_px(l.num, window) - of cuCh: ch_to_px(l.num, window) - of cuIc: ic_to_px(l.num, window) - of cuEx: ex_to_px(l.num, window) - of cuPerc: toLayoutUnit(toFloat64(p) * l.num / 100) - of cuPx: toLayoutUnit(l.num) - of cuCm: toLayoutUnit(l.num * 37.8) - of cuMm: toLayoutUnit(l.num * 3.78) - of cuIn: toLayoutUnit(l.num * 96) - of cuPc: toLayoutUnit(l.num * 16) - of cuPt: toLayoutUnit(l.num * 4 / 3) - of cuVw: toLayoutUnit(float64(window.widthPx) * l.num / 100) - of cuVh: toLayoutUnit(float64(window.heightPx) * l.num / 100) - of cuVmin: - toLayoutUnit(min(window.widthPx, window.heightPx) / 100 * l.num) - of cuVmax: - toLayoutUnit(max(window.widthPx, window.heightPx) / 100 * l.num) - func blockify*(display: CSSDisplay): CSSDisplay = case display of DisplayBlock, DisplayTable, DisplayListItem, DisplayNone, DisplayFlowRoot, @@ -775,11 +745,35 @@ func parseIdent[T: enum](cval: CSSComponentValue): Opt[T] = return ok(T(i)) return err() -func cssLength(val: float64; u: string): Opt[CSSLength] = +template cssLength*(n: float64): CSSLength = + CSSLength(u: clPx, num: n) + +func resolveLength*(u: CSSUnit; val: float64; attrs: WindowAttributes): + CSSLength = + return case u + of cuAuto: CSSLength(u: clAuto) + of cuEm, cuRem: cssLength(val * float64(attrs.ppl)) + of cuCh: cssLength(val * float64(attrs.ppc)) + of cuIc: cssLength(val * float64(attrs.ppc) * 2) + of cuEx: cssLength(val * float64(attrs.ppc) / 2) + of cuPerc: CSSLength(u: clPerc, num: val) + of cuPx: cssLength(val) + of cuCm: cssLength(val * 37.8) + of cuMm: cssLength(val * 3.78) + of cuIn: cssLength(val * 96) + of cuPc: cssLength(val * 16) + of cuPt: cssLength(val * 4 / 3) + of cuVw: cssLength(float64(attrs.widthPx) * val / 100) + of cuVh: cssLength(float64(attrs.heightPx) * val / 100) + of cuVmin: cssLength(min(attrs.widthPx, attrs.heightPx) / 100 * val) + of cuVmax: cssLength(max(attrs.widthPx, attrs.heightPx) / 100 * val) + +func parseLength(val: float64; u: string; attrs: WindowAttributes): + Opt[CSSLength] = let u = ?parseEnumNoCase[CSSUnit](u) - return ok(CSSLength(num: val, u: u)) + return ok(resolveLength(u, val, attrs)) -const CSSLengthAuto* = CSSLength(u: cuAuto) +const CSSLengthAuto* = CSSLength(u: clAuto) func parseDimensionValues*(s: string): Option[CSSLength] = var i = s.skipBlanks(0) @@ -791,19 +785,19 @@ func parseDimensionValues*(s: string): Option[CSSLength] = n += float64(decValue(s[i])) inc i if i >= s.len: - return some(CSSLength(num: n, u: cuPx)) + return some(cssLength(n)) if s[i] == '.': inc i if i >= s.len: - return some(CSSLength(num: n, u: cuPx)) + return some(cssLength(n)) 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 and s[i] == '%': - return some(CSSLength(num: n, u: cuPerc)) - return some(CSSLength(num: n, u: cuPx)) + return some(CSSLength(num: n, u: clPerc)) + return some(cssLength(n)) func skipWhitespace*(vals: openArray[CSSComponentValue]; i: var int) = while i < vals.len: @@ -917,38 +911,39 @@ func cssColor*(val: CSSComponentValue): Opt[CSSColor] = return parseANSI(f.value) return err() -func cssLength*(val: CSSComponentValue; hasAuto = true; allowNegative = true): - Opt[CSSLength] = +func parseLength*(val: CSSComponentValue; attrs: WindowAttributes; + hasAuto = true; allowNegative = true): Opt[CSSLength] = if val of CSSToken: let tok = CSSToken(val) case tok.t of cttNumber: if tok.nvalue == 0: - return ok(CSSLength(num: 0, u: cuPx)) + return ok(cssLength(0)) of cttPercentage: if not allowNegative and tok.nvalue < 0: return err() - return cssLength(tok.nvalue, "%") + return parseLength(tok.nvalue, "%", attrs) of cttDimension: if not allowNegative and tok.nvalue < 0: return err() - return cssLength(tok.nvalue, tok.unit) + return parseLength(tok.nvalue, tok.unit, attrs) of cttIdent: if hasAuto and tok.value.equalsIgnoreCase("auto"): return ok(CSSLengthAuto) else: discard return err() -func cssAbsoluteLength(val: CSSComponentValue): Opt[CSSLength] = +func cssAbsoluteLength(val: CSSComponentValue; attrs: WindowAttributes): + Opt[CSSLength] = if val of CSSToken: let tok = CSSToken(val) case tok.t of cttNumber: if tok.nvalue == 0: - return ok(CSSLength(num: 0, u: cuPx)) + return ok(cssLength(0)) of cttDimension: if tok.nvalue >= 0: - return cssLength(tok.nvalue, tok.unit) + return parseLength(tok.nvalue, tok.unit, attrs) else: discard return err() @@ -1044,14 +1039,15 @@ func cssTextDecoration(cvals: openArray[CSSComponentValue]): s.incl(td) return ok(s) -func cssVerticalAlign(cval: CSSComponentValue): Opt[CSSVerticalAlign] = +func cssVerticalAlign(cval: CSSComponentValue; attrs: WindowAttributes): + Opt[CSSVerticalAlign] = if cval of CSSToken: let tok = CSSToken(cval) if tok.t == cttIdent: let va2 = ?parseIdent[CSSVerticalAlign2](cval) return ok(CSSVerticalAlign(keyword: va2)) else: - let length = ?cssLength(tok, hasAuto = false) + let length = ?parseLength(tok, attrs, hasAuto = false) return ok(CSSVerticalAlign( keyword: VerticalAlignBaseline, u: length.u, @@ -1086,7 +1082,8 @@ func cssCounterReset(cvals: openArray[CSSComponentValue]): die return ok(res) -func cssMaxSize(cval: CSSComponentValue): Opt[CSSLength] = +func cssMaxSize(cval: CSSComponentValue; attrs: WindowAttributes): + Opt[CSSLength] = if cval of CSSToken: let tok = CSSToken(cval) case tok.t @@ -1094,7 +1091,7 @@ func cssMaxSize(cval: CSSComponentValue): Opt[CSSLength] = if tok.value.equalsIgnoreCase("none"): return ok(CSSLengthAuto) of cttNumber, cttDimension, cttPercentage: - return cssLength(tok, allowNegative = false) + return parseLength(tok, attrs, allowNegative = false) else: discard return err() @@ -1163,7 +1160,7 @@ proc makeEntry*(t: CSSPropertyType; global: CSSGlobalType): CSSComputedEntry = return CSSComputedEntry(t: t, global: global) proc parseValue(cvals: openArray[CSSComponentValue]; - entry: var CSSComputedEntry): Opt[void] = + entry: var CSSComputedEntry; attrs: WindowAttributes): Opt[void] = var i = 0 cvals.skipWhitespace(i) if i >= cvals.len: @@ -1187,14 +1184,14 @@ proc parseValue(cvals: openArray[CSSComponentValue]; of cvtLength: case t of cptMinWidth, cptMinHeight: - set_new length, ?cssLength(cval, allowNegative = false) + set_new length, ?parseLength(cval, attrs, allowNegative = false) of cptMaxWidth, cptMaxHeight: - set_new length, ?cssMaxSize(cval) + set_new length, ?cssMaxSize(cval, attrs) of cptPaddingLeft, cptPaddingRight, cptPaddingTop, cptPaddingBottom: - set_new length, ?cssLength(cval, hasAuto = false) + set_new length, ?parseLength(cval, attrs, hasAuto = false) #TODO content for flex-basis else: - set_new length, ?cssLength(cval) + set_new length, ?parseLength(cval, attrs) of cvtContent: set_new content, cssContent(cvals) of cvtInteger: case t @@ -1204,7 +1201,7 @@ proc parseValue(cvals: openArray[CSSComponentValue]; of cptZIndex: set_new integer, ?cssInteger(cval, -65534 .. 65534) else: assert false of cvtTextDecoration: set_new textdecoration, ?cssTextDecoration(cvals) - of cvtVerticalAlign: set_new verticalAlign, ?cssVerticalAlign(cval) + of cvtVerticalAlign: set_new verticalAlign, ?cssVerticalAlign(cval, attrs) of cvtTextAlign: set_bit textAlign, ?parseIdent[CSSTextAlign](cval) of cvtListStylePosition: set_bit listStylePosition, ?parseIdent[CSSListStylePosition](cval) @@ -1213,9 +1210,9 @@ proc parseValue(cvals: openArray[CSSComponentValue]; of cvtBorderCollapse: set_bit borderCollapse, ?parseIdent[CSSBorderCollapse](cval) of cvtLength2: - let a = ?cssAbsoluteLength(cval) + let a = ?cssAbsoluteLength(cval, attrs) cvals.skipWhitespace(i) - let b = if i >= cvals.len: a else: ?cssAbsoluteLength(cvals[i]) + let b = if i >= cvals.len: a else: ?cssAbsoluteLength(cvals[i], attrs) set_new length2, CSSLength2(a: a, b: b) of cvtQuotes: set_new quotes, ?cssQuotes(cvals) of cvtCounterReset: set_new counterReset, ?cssCounterReset(cvals) @@ -1246,7 +1243,7 @@ func getInitialLength(t: CSSPropertyType): CSSLength = cptMaxHeight, cptMinWidth, cptMinHeight, cptFlexBasis: return CSSLengthAuto else: - return CSSLength(u: cuPx, num: 0) + return cssLength(0) func getInitialInteger(t: CSSPropertyType): int = case t @@ -1283,8 +1280,8 @@ template getDefault*(t: CSSPropertyType): CSSValue = defaultTable[t] func lengthShorthand(cvals: openArray[CSSComponentValue]; - props: array[4, CSSPropertyType]; global: CSSGlobalType; hasAuto = true): - Opt[seq[CSSComputedEntry]] = + props: array[4, CSSPropertyType]; global: CSSGlobalType; + attrs: WindowAttributes; hasAuto = true): Opt[seq[CSSComputedEntry]] = var res: seq[CSSComputedEntry] = @[] if global != cgtNone: for t in props: @@ -1294,7 +1291,7 @@ func lengthShorthand(cvals: openArray[CSSComponentValue]; var i = 0 while i < cvals.len: cvals.skipWhitespace(i) - let length = ?cssLength(cvals[i], hasAuto = hasAuto) + let length = ?parseLength(cvals[i], attrs, hasAuto = hasAuto) let val = CSSValue(v: cvtLength, length: length) lengths.add(val) inc i @@ -1330,7 +1327,7 @@ const PropertyPaddingSpec = [ ] proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string; - cvals: openArray[CSSComponentValue]): Err[void] = + cvals: openArray[CSSComponentValue]; attrs: WindowAttributes): Err[void] = var i = 0 cvals.skipWhitespace(i) if i >= cvals.len: @@ -1343,7 +1340,7 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string; res.add(makeEntry(t, global)) else: var entry = CSSComputedEntry(t: t) - ?cvals.parseValue(entry) + ?cvals.parseValue(entry, attrs) res.add(entry) of cstAll: if global == cgtNone: @@ -1351,9 +1348,9 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string; for t in CSSPropertyType: res.add(makeEntry(t, global)) of cstMargin: - res.add(?lengthShorthand(cvals, PropertyMarginSpec, global)) + res.add(?lengthShorthand(cvals, PropertyMarginSpec, global, attrs)) of cstPadding: - res.add(?lengthShorthand(cvals, PropertyPaddingSpec, global, + res.add(?lengthShorthand(cvals, PropertyPaddingSpec, global, attrs, hasAuto = false)) of cstBackground: var bgcolorval = getDefault(cptBackgroundColor) @@ -1422,13 +1419,10 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string; res.add(makeEntry(cptFlexShrink, val)) if i < cvals.len: # flex-basis - let val = CSSValue(v: cvtLength, length: ?cssLength(cvals[i])) + let val = CSSValue(v: cvtLength, length: ?parseLength(cvals[i], attrs)) res.add(makeEntry(cptFlexBasis, val)) else: # omitted, default to 0px - let val = CSSValue( - v: cvtLength, - length: CSSLength(u: cuPx, num: 0) - ) + let val = CSSValue(v: cvtLength, length: cssLength(0)) res.add(makeEntry(cptFlexBasis, val, global)) else: res.add(makeEntry(cptFlexGrow, global)) @@ -1457,10 +1451,10 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string; res.add(makeEntry(cptFlexWrap, global)) return ok() -proc parseComputedValues*(name: string; value: seq[CSSComponentValue]): - seq[CSSComputedEntry] = +proc parseComputedValues*(name: string; value: seq[CSSComponentValue]; + attrs: WindowAttributes): seq[CSSComputedEntry] = var res: seq[CSSComputedEntry] = @[] - if res.parseComputedValues(name, value).isSome: + if res.parseComputedValues(name, value, attrs).isSome: return res return @[] diff --git a/src/css/layout.nim b/src/css/layout.nim index c737f9f8..ef712b8a 100644 --- a/src/css/layout.nim +++ b/src/css/layout.nim @@ -139,29 +139,24 @@ func isDefinite(sc: SizeConstraint): bool = return sc.t in {scStretch, scFitContent} # 2nd pass: layout -func px(l: CSSLength; lctx: LayoutContext; p: LayoutUnit = 0): - LayoutUnit {.inline.} = - return px(l, lctx.attrs, p) - func canpx(l: CSSLength; sc: SizeConstraint): bool = - return l.u != cuAuto and (l.u != cuPerc or sc.t == scStretch) - -# Note: for margins only -# For percentages, use 0 for indefinite, and containing box's size for -# definite. -func px(l: CSSLength; lctx: LayoutContext; p: SizeConstraint): LayoutUnit = - if l.u == cuPerc: - case p.t - of scMinContent, scMaxContent: - return 0 - of scStretch, scFitContent: - return l.px(lctx, p.u) - return px(l, lctx.attrs, 0) + return l.u != clAuto and (l.u != clPerc or sc.t == scStretch) + +func px(l: CSSLength; p: LayoutUnit): LayoutUnit {.inline.} = + if l.u != clPerc: + return l.num.toLayoutUnit() + return (p.toFloat64() * l.num / 100).toLayoutUnit() -func stretchOrMaxContent(l: CSSLength; lctx: LayoutContext; sc: SizeConstraint): - SizeConstraint = +func px(l: CSSLength; p: SizeConstraint): LayoutUnit {.inline.} = + if l.u != clPerc: + return l.num.toLayoutUnit() + if p.t in {scStretch, scFitContent}: + return (p.u.toFloat64() * l.num / 100).toLayoutUnit() + return 0 + +func stretchOrMaxContent(l: CSSLength; sc: SizeConstraint): SizeConstraint = if l.canpx(sc): - return stretch(l.px(lctx, sc)) + return stretch(l.px(sc)) return maxContent() func applySizeConstraint(u: LayoutUnit; availableSize: SizeConstraint): @@ -664,7 +659,7 @@ func getBaseline(ictx: InlineContext; iastate: InlineAtomState; return case iastate.vertalign.keyword of VerticalAlignBaseline: let length = CSSLength(u: iastate.vertalign.u, num: iastate.vertalign.num) - let len = length.px(ictx.lctx, ictx.cellHeight) + let len = length.px(ictx.cellHeight) iastate.baseline + len of VerticalAlignTop, VerticalAlignBottom: atom.size.h @@ -899,9 +894,9 @@ proc layoutText(ictx: var InlineContext; state: var InlineState; s: string) = else: "" ictx.layoutTextLoop(state, s) -func spx(l: CSSLength; lctx: LayoutContext; p: SizeConstraint; - computed: CSSValues; padding: LayoutUnit): LayoutUnit = - let u = l.px(lctx, p) +func spx(l: CSSLength; p: SizeConstraint; computed: CSSValues; + padding: LayoutUnit): LayoutUnit = + let u = l.px(p) if computed{"box-sizing"} == BoxSizingBorderBox: return max(u - padding, 0) return max(u, 0) @@ -921,14 +916,14 @@ proc resolveContentWidth(sizes: var ResolvedSizes; widthpx: LayoutUnit; else: sizes.margin[dtHorizontal].send += underflow elif underflow > 0: - if computed{"margin-left"}.u != cuAuto and - computed{"margin-right"}.u != cuAuto: + if computed{"margin-left"}.u != clAuto and + computed{"margin-right"}.u != clAuto: sizes.margin[dtHorizontal].send += underflow - elif computed{"margin-left"}.u != cuAuto and - computed{"margin-right"}.u == cuAuto: + elif computed{"margin-left"}.u != clAuto and + computed{"margin-right"}.u == clAuto: sizes.margin[dtHorizontal].send = underflow - elif computed{"margin-left"}.u == cuAuto and - computed{"margin-right"}.u != cuAuto: + elif computed{"margin-left"}.u == clAuto and + computed{"margin-right"}.u != clAuto: sizes.margin[dtHorizontal].start = underflow else: sizes.margin[dtHorizontal].start = underflow div 2 @@ -939,12 +934,12 @@ proc resolveMargins(lctx: LayoutContext; availableWidth: SizeConstraint; # Note: we use availableWidth for percentage resolution intentionally. return [ dtHorizontal: Span( - start: computed{"margin-left"}.px(lctx, availableWidth), - send: computed{"margin-right"}.px(lctx, availableWidth), + start: computed{"margin-left"}.px(availableWidth), + send: computed{"margin-right"}.px(availableWidth), ), dtVertical: Span( - start: computed{"margin-top"}.px(lctx, availableWidth), - send: computed{"margin-bottom"}.px(lctx, availableWidth), + start: computed{"margin-top"}.px(availableWidth), + send: computed{"margin-bottom"}.px(availableWidth), ) ] @@ -953,12 +948,12 @@ proc resolvePadding(lctx: LayoutContext; availableWidth: SizeConstraint; # Note: we use availableWidth for percentage resolution intentionally. return [ dtHorizontal: Span( - start: computed{"padding-left"}.px(lctx, availableWidth), - send: computed{"padding-right"}.px(lctx, availableWidth) + start: computed{"padding-left"}.px(availableWidth), + send: computed{"padding-right"}.px(availableWidth) ), dtVertical: Span( - start: computed{"padding-top"}.px(lctx, availableWidth), - send: computed{"padding-bottom"}.px(lctx, availableWidth), + start: computed{"padding-top"}.px(availableWidth), + send: computed{"padding-bottom"}.px(availableWidth), ) ] @@ -968,12 +963,12 @@ func resolvePositioned(lctx: LayoutContext; size: Size; # (unlike with margin/padding) return [ dtHorizontal: Span( - start: computed{"left"}.px(lctx, size.w), - send: computed{"right"}.px(lctx, size.w) + start: computed{"left"}.px(size.w), + send: computed{"right"}.px(size.w) ), dtVertical: Span( - start: computed{"top"}.px(lctx, size.h), - send: computed{"bottom"}.px(lctx, size.h), + start: computed{"top"}.px(size.h), + send: computed{"bottom"}.px(size.h), ) ] @@ -989,21 +984,21 @@ func resolveBounds(lctx: LayoutContext; space: AvailableSpace; padding: Size; let sc = space.w let padding = padding[dtHorizontal] if computed{"min-width"}.canpx(sc): - let px = computed{"min-width"}.spx(lctx, sc, computed, padding) + let px = computed{"min-width"}.spx(sc, computed, padding) res.a[dtHorizontal].start = px res.minClamp[dtHorizontal] = px if computed{"max-width"}.canpx(sc): - let px = computed{"max-width"}.spx(lctx, sc, computed, padding) + let px = computed{"max-width"}.spx(sc, computed, padding) res.a[dtHorizontal].send = px block: let sc = space.h let padding = padding[dtHorizontal] if computed{"min-height"}.canpx(sc): - let px = computed{"min-height"}.spx(lctx, sc, computed, padding) + let px = computed{"min-height"}.spx(sc, computed, padding) res.a[dtVertical].start = px res.minClamp[dtVertical] = px if computed{"max-height"}.canpx(sc): - let px = computed{"max-height"}.spx(lctx, sc, computed, padding) + let px = computed{"max-height"}.spx(sc, computed, padding) res.a[dtVertical].send = px return res @@ -1012,9 +1007,9 @@ const CvalSizeMap = [dtHorizontal: cptWidth, dtVertical: cptHeight] proc resolveAbsoluteWidth(sizes: var ResolvedSizes; size: Size; positioned: RelativeRect; computed: CSSValues; lctx: LayoutContext) = - if computed{"width"}.u == cuAuto: + if computed{"width"}.u == clAuto: let u = max(size.w - positioned[dtHorizontal].sum(), 0) - if computed{"left"}.u != cuAuto and computed{"right"}.u != cuAuto: + if computed{"left"}.u != clAuto and computed{"right"}.u != clAuto: # Both left and right are known, so we can calculate the width. sizes.space.w = stretch(u) else: @@ -1022,15 +1017,15 @@ proc resolveAbsoluteWidth(sizes: var ResolvedSizes; size: Size; sizes.space.w = fitContent(u) else: let padding = sizes.padding[dtHorizontal].sum() - let sizepx = computed{"width"}.spx(lctx, stretch(size.w), computed, padding) + let sizepx = computed{"width"}.spx(stretch(size.w), computed, padding) sizes.space.w = stretch(sizepx) proc resolveAbsoluteHeight(sizes: var ResolvedSizes; size: Size; positioned: RelativeRect; computed: CSSValues; lctx: LayoutContext) = - if computed{"height"}.u == cuAuto: + if computed{"height"}.u == clAuto: let u = max(size.w - positioned[dtVertical].sum(), 0) - if computed{"top"}.u != cuAuto and computed{"bottom"}.u != cuAuto: + if computed{"top"}.u != clAuto and computed{"bottom"}.u != clAuto: # Both top and bottom are known, so we can calculate the height. sizes.space.h = stretch(u) else: @@ -1038,7 +1033,7 @@ proc resolveAbsoluteHeight(sizes: var ResolvedSizes; size: Size; sizes.space.h = maxContent() else: let padding = sizes.padding[dtVertical].sum() - let sizepx = computed{"height"}.spx(lctx, stretch(size.h), computed, + let sizepx = computed{"height"}.spx(stretch(size.h), computed, padding) sizes.space.h = stretch(sizepx) @@ -1071,7 +1066,7 @@ proc resolveFloatSizes(lctx: LayoutContext; space: AvailableSpace; for dim in DimensionType: let length = computed.objs[CvalSizeMap[dim]].length if length.canpx(space[dim]): - let u = length.spx(lctx, space[dim], computed, paddingSum[dim]) + let u = length.spx(space[dim], computed, paddingSum[dim]) sizes.space[dim] = stretch(minClamp(u, sizes.bounds.a[dim])) elif sizes.space[dim].isDefinite(): let u = sizes.space[dim].u - sizes.margin[dim].sum() - paddingSum[dim] @@ -1092,7 +1087,7 @@ proc resolveFlexItemSizes(lctx: LayoutContext; space: AvailableSpace; sizes.space.h = maxContent() let length = computed.objs[CvalSizeMap[dim]].length if length.canpx(space[dim]): - let u = length.spx(lctx, space[dim], computed, paddingSum[dim]) + let u = length.spx(space[dim], computed, paddingSum[dim]) sizes.space[dim] = SizeConstraint( t: sizes.space[dim].t, u: minClamp(u, sizes.bounds.a[dim]) @@ -1108,7 +1103,7 @@ proc resolveFlexItemSizes(lctx: LayoutContext; space: AvailableSpace; let odim = dim.opposite() let olength = computed.objs[CvalSizeMap[odim]].length if olength.canpx(space[odim]): - let u = olength.spx(lctx, space[odim], computed, paddingSum[odim]) + let u = olength.spx(space[odim], computed, paddingSum[odim]) sizes.space[odim] = stretch(minClamp(u, sizes.bounds.a[odim])) sizes.bounds.minClamp[odim] = min(sizes.space[odim].u, sizes.bounds.minClamp[odim]) @@ -1128,10 +1123,10 @@ proc resolveBlockWidth(sizes: var ResolvedSizes; parentWidth: SizeConstraint; let width = computed{"width"} var widthpx: LayoutUnit = 0 if width.canpx(parentWidth): - widthpx = width.spx(lctx, parentWidth, computed, inlinePadding) + widthpx = width.spx(parentWidth, computed, inlinePadding) sizes.space.w = stretch(widthpx) sizes.bounds.minClamp[dtHorizontal] = widthpx - sizes.resolveContentWidth(widthpx, parentWidth, computed, width.u == cuAuto) + sizes.resolveContentWidth(widthpx, parentWidth, computed, width.u == clAuto) if sizes.space.w.isDefinite() and sizes.maxWidth < sizes.space.w.u or sizes.maxWidth < LayoutUnit.high and sizes.space.w.t == scMaxContent: if sizes.space.w.t == scStretch: @@ -1156,7 +1151,7 @@ proc resolveBlockHeight(sizes: var ResolvedSizes; parentHeight: SizeConstraint; lctx: LayoutContext) = let height = computed{"height"} if height.canpx(parentHeight): - let heightpx = height.spx(lctx, parentHeight, computed, blockPadding) + let heightpx = height.spx(parentHeight, computed, blockPadding) sizes.space.h = stretch(heightpx) sizes.bounds.minClamp[dtVertical] = heightpx if sizes.space.h.isDefinite() and sizes.maxHeight < sizes.space.h.u or @@ -1314,15 +1309,15 @@ proc popPositioned(lctx: LayoutContext; overflow: var Overflow; size: Size) = # the available width, and we must re-layout. sizes.space.w = stretch(child.state.xminwidth) marginBottom = lctx.layoutRootBlock(child, it.offset, sizes) - if child.computed{"left"}.u != cuAuto: + if child.computed{"left"}.u != clAuto: child.state.offset.x = positioned.left + sizes.margin.left - elif child.computed{"right"}.u != cuAuto: + elif child.computed{"right"}.u != clAuto: child.state.offset.x = size.w - positioned.right - child.state.size.w - sizes.margin.right # margin.left is added in layoutRootBlock - if child.computed{"top"}.u != cuAuto: + if child.computed{"top"}.u != clAuto: child.state.offset.y = positioned.top + sizes.margin.top - elif child.computed{"bottom"}.u != cuAuto: + elif child.computed{"bottom"}.u != clAuto: child.state.offset.y = size.h - positioned.bottom - child.state.size.h - sizes.margin.bottom else: @@ -1617,25 +1612,24 @@ proc addInlineImage(ictx: var InlineContext; state: var InlineState; size: size(w = bmp.width, h = bmp.height) #TODO overflow ) let computed = state.fragment.computed - let lctx = ictx.lctx let hasWidth = computed{"width"}.canpx(ictx.space.w) let hasHeight = computed{"height"}.canpx(ictx.space.h) let osize = atom.size if hasWidth: - atom.size.w = computed{"width"}.spx(lctx, ictx.space.w, computed, padding) + atom.size.w = computed{"width"}.spx(ictx.space.w, computed, padding) if hasHeight: - atom.size.h = computed{"height"}.spx(lctx, ictx.space.h, computed, padding) + atom.size.h = computed{"height"}.spx(ictx.space.h, computed, padding) if computed{"max-width"}.canpx(ictx.space.w): - let w = computed{"max-width"}.spx(lctx, ictx.space.w, computed, padding) + let w = computed{"max-width"}.spx(ictx.space.w, computed, padding) atom.size.w = min(atom.size.w, w) if computed{"min-width"}.canpx(ictx.space.w): - let w = computed{"min-width"}.spx(lctx, ictx.space.w, computed, padding) + let w = computed{"min-width"}.spx(ictx.space.w, computed, padding) atom.size.w = max(atom.size.w, w) if computed{"max-height"}.canpx(ictx.space.h): - let h = computed{"max-height"}.spx(lctx, ictx.space.h, computed, padding) + let h = computed{"max-height"}.spx(ictx.space.h, computed, padding) atom.size.h = min(atom.size.h, h) if computed{"min-height"}.canpx(ictx.space.h): - let h = computed{"min-height"}.spx(lctx, ictx.space.h, computed, padding) + let h = computed{"min-height"}.spx(ictx.space.h, computed, padding) atom.size.h = max(atom.size.h, h) if not hasWidth and ictx.space.w.isDefinite(): atom.size.w = min(ictx.space.w.u, atom.size.w) @@ -1663,8 +1657,8 @@ proc addInlineImage(ictx: var InlineContext; state: var InlineState; # parent size yet. e.g. <img width=100% ...> with an indefinite containing # size (i.e. the first table cell pass) would resolve to an xminwidth of # image.width, stretching out the table to an uncomfortably large size. - if ictx.space.w.isDefinite() or computed{"width"}.u != cuPerc and - computed{"min-width"}.u != cuPerc: + if ictx.space.w.isDefinite() or computed{"width"}.u != clPerc and + computed{"min-width"}.u != clPerc: ictx.state.xminwidth = max(ictx.state.xminwidth, atom.size.w) proc layoutInline(ictx: var InlineContext; fragment: InlineFragment) = @@ -1672,12 +1666,12 @@ proc layoutInline(ictx: var InlineContext; fragment: InlineFragment) = let computed = fragment.computed var padding = Span() if stSplitStart in fragment.splitType: - let w = computed{"margin-left"}.px(lctx, ictx.space.w) + let w = computed{"margin-left"}.px(ictx.space.w) ictx.lbstate.size.w += w ictx.lbstate.widthAfterWhitespace += w padding = Span( - start: computed{"padding-left"}.px(lctx, ictx.space.w), - send: computed{"padding-right"}.px(lctx, ictx.space.w) + start: computed{"padding-left"}.px(ictx.space.w), + send: computed{"padding-right"}.px(ictx.space.w) ) fragment.state = InlineFragmentState() if padding.start != 0: @@ -1713,7 +1707,7 @@ proc layoutInline(ictx: var InlineContext; fragment: InlineFragment) = ictx.lbstate.paddingTodo.add((fragment, fragment.state.areas.high)) if stSplitEnd in fragment.splitType: ictx.lbstate.size.w += padding.send - ictx.lbstate.size.w += computed{"margin-right"}.px(lctx, ictx.space.w) + ictx.lbstate.size.w += computed{"margin-right"}.px(ictx.space.w) if fragment.t != iftParent: if not ictx.textFragmentSeen: ictx.textFragmentSeen = true @@ -1732,14 +1726,14 @@ proc layoutInline(ictx: var InlineContext; fragment: InlineFragment) = proc positionRelative(lctx: LayoutContext; parent, box: BlockBox) = let positioned = lctx.resolvePositioned(parent.state.size, box.computed) - if box.computed{"left"}.u != cuAuto: + if box.computed{"left"}.u != clAuto: box.state.offset.x += positioned.left - elif box.computed{"right"}.u != cuAuto: + elif box.computed{"right"}.u != clAuto: box.state.offset.x += parent.state.size.w - box.state.size.w - positioned.right - if box.computed{"top"}.u != cuAuto: + if box.computed{"top"}.u != clAuto: box.state.offset.y += positioned.top - elif box.computed{"bottom"}.u != cuAuto: + elif box.computed{"bottom"}.u != clAuto: box.state.offset.y += parent.state.size.h - box.state.size.h - positioned.bottom @@ -1871,8 +1865,8 @@ proc preLayoutTableRow(pctx: var TableContext; row, parent: BlockBox; let cw = box.computed{"width"} let ch = box.computed{"height"} let space = availableSpace( - w = cw.stretchOrMaxContent(pctx.lctx, pctx.space.w), - h = ch.stretchOrMaxContent(pctx.lctx, pctx.space.h) + w = cw.stretchOrMaxContent(pctx.space.w), + h = ch.stretchOrMaxContent(pctx.space.h) ) #TODO specified table height should be distributed among rows. # Allow the table cell to use its specified width. @@ -2091,7 +2085,7 @@ func needsRedistribution(tctx: TableContext; computed: CSSValues): of scStretch: return tctx.space.w.u != tctx.maxwidth of scFitContent: - return tctx.space.w.u > tctx.maxwidth and computed{"width"}.u != cuAuto or + return tctx.space.w.u > tctx.maxwidth and computed{"width"}.u != clAuto or tctx.space.w.u < tctx.maxwidth proc redistributeWidth(tctx: var TableContext) = @@ -2194,12 +2188,11 @@ proc layoutTable(tctx: var TableContext; table: BlockBox; sizes: ResolvedSizes) = if tctx.space.w.t == scStretch: table.state.xminwidth = tctx.space.w.u - let lctx = tctx.lctx if table.computed{"border-collapse"} != BorderCollapseCollapse: let spc = table.computed{"border-spacing"} if spc != nil: - tctx.inlineSpacing = table.computed{"border-spacing"}.a.px(lctx) - tctx.blockSpacing = table.computed{"border-spacing"}.b.px(lctx) + tctx.inlineSpacing = table.computed{"border-spacing"}.a.px(0) + tctx.blockSpacing = table.computed{"border-spacing"}.b.px(0) tctx.preLayoutTableRows(table) # first pass if tctx.needsRedistribution(table.computed): tctx.redistributeWidth() @@ -2412,11 +2405,11 @@ proc layoutFlex(bctx: var BlockContext; box: BlockBox; sizes: ResolvedSizes) = var childSizes = lctx.resolveFlexItemSizes(sizes.space, dim, child.computed) let flexBasis = child.computed{"flex-basis"} lctx.layoutFlexChild(child, childSizes) - if flexBasis.u != cuAuto and sizes.space[dim].isDefinite: + if flexBasis.u != clAuto and sizes.space[dim].isDefinite: # we can't skip this pass; it is needed to calculate the minimum # height. let minu = child.state.minFlexItemSize(dim) - childSizes.space[dim] = stretch(flexBasis.spx(lctx, sizes.space[dim], + childSizes.space[dim] = stretch(flexBasis.spx(sizes.space[dim], child.computed, childSizes.padding[dim].sum())) if minu > childSizes.space[dim].u: # First pass gave us a box that is thinner than the minimum diff --git a/src/css/mediaquery.nim b/src/css/mediaquery.nim index 756a67e4..5cb1ad98 100644 --- a/src/css/mediaquery.nim +++ b/src/css/mediaquery.nim @@ -3,12 +3,14 @@ import std/options import css/cssparser import css/cssvalues import types/opt +import types/winattrs import utils/twtstr type MediaQueryParser = object at: int cvals: seq[CSSComponentValue] + attrs: ptr WindowAttributes MediaType* = enum mtAll = "all" @@ -241,16 +243,19 @@ proc parseIntRange(parser: var MediaQueryParser; ismin, ismax: bool): proc parseLength(parser: var MediaQueryParser): Opt[CSSLength] = let cval = parser.consume() - return cssLength(cval) + let len = ?parseLength(cval, parser.attrs[]) + if len.u != clPx: + return err() + return ok(len) proc parseLengthRange(parser: var MediaQueryParser; ismin, ismax: bool): Opt[LengthRange] = if ismin: let a = ?parser.parseLength() - let b = CSSLength(num: Inf, u: cuPx) + let b = cssLength(Inf) return ok(LengthRange(s: a .. b, aeq: true, beq: false)) if ismax: - let a = CSSLength(num: 0, u: cuPx) + let a = cssLength(0) let b = ?parser.parseLength() return ok(LengthRange(s: a .. b, aeq: false, beq: true)) let comparison = ?parser.parseComparison() @@ -260,10 +265,10 @@ proc parseLengthRange(parser: var MediaQueryParser; ismin, ismax: bool): of mqcEq: return ok(LengthRange(s: len .. len, aeq: true, beq: true)) of mqcGt, mqcGe: - let b = CSSLength(num: Inf, u: cuPx) + let b = cssLength(Inf) return ok(LengthRange(s: len .. b, aeq: comparison == mqcGe, beq: false)) of mqcLt, mqcLe: - let a = CSSLength(num: 0, u: cuPx) + let a = cssLength(0) return ok(LengthRange(s: a .. len, aeq: false, beq: comparison == mqcLe)) proc parseFeature0(parser: var MediaQueryParser; t: MediaFeatureType; @@ -312,7 +317,7 @@ proc parseMediaInParens(parser: var MediaQueryParser): Opt[MediaQuery] = let sb = ?parser.consumeSimpleBlock() if sb.token.t != cttLparen: return err() - var fparser = MediaQueryParser(cvals: sb.value) + var fparser = MediaQueryParser(cvals: sb.value, attrs: parser.attrs) fparser.skipBlanks() let tok = ?fparser.consumeIdent() fparser.skipBlanks() @@ -397,11 +402,12 @@ proc parseMediaQuery(parser: var MediaQueryParser): Opt[MediaQuery] = else: return err() -proc parseMediaQueryList*(cvals: seq[CSSComponentValue]): MediaQueryList = +proc parseMediaQueryList*(cvals: seq[CSSComponentValue]; + attrs: ptr WindowAttributes): MediaQueryList = result = @[] let cseplist = cvals.parseCommaSepComponentValues() for list in cseplist: - var parser = MediaQueryParser(cvals: list) + var parser = MediaQueryParser(cvals: list, attrs: attrs) let query = parser.parseMediaQuery() if query.isSome: result.add(query.get) diff --git a/src/css/render.nim b/src/css/render.nim index 10045683..06fa02a7 100644 --- a/src/css/render.nim +++ b/src/css/render.nim @@ -402,9 +402,9 @@ proc renderBlockBox(grid: var FlexibleGrid; state: var RenderState; return var offset = offset if position in {PositionAbsolute, PositionFixed}: - if box.computed{"left"}.u != cuAuto or box.computed{"right"}.u != cuAuto: + if box.computed{"left"}.u != clAuto or box.computed{"right"}.u != clAuto: offset.x = state.absolutePos[^1].x - if box.computed{"top"}.u != cuAuto or box.computed{"bottom"}.u != cuAuto: + if box.computed{"top"}.u != clAuto or box.computed{"bottom"}.u != clAuto: offset.y = state.absolutePos[^1].y offset += box.state.offset box.render.offset = offset diff --git a/src/css/sheet.nim b/src/css/sheet.nim index 0793e903..818e30f8 100644 --- a/src/css/sheet.nim +++ b/src/css/sheet.nim @@ -7,6 +7,7 @@ import css/mediaquery import css/selectorparser import html/catom import types/url +import types/winattrs import utils/twtstr type @@ -36,6 +37,7 @@ type importList*: seq[URL] len: int factory*: CAtomFactory + attrs: ptr WindowAttributes type SelectorHashes = object tag: CAtom @@ -43,7 +45,8 @@ type SelectorHashes = object class: CAtom attr: CAtom -func newStylesheet*(cap: int; factory: CAtomFactory): CSSStylesheet = +func newStylesheet*(cap: int; factory: CAtomFactory; + attrs: ptr WindowAttributes): CSSStylesheet = let bucketsize = cap div 2 return CSSStylesheet( tagTable: initTable[CAtom, seq[CSSRuleDef]](bucketsize), @@ -51,7 +54,8 @@ func newStylesheet*(cap: int; factory: CAtomFactory): CSSStylesheet = classTable: initTable[CAtom, seq[CSSRuleDef]](bucketsize), attrTable: initTable[CAtom, seq[CSSRuleDef]](bucketsize), generalList: newSeqOfCap[CSSRuleDef](bucketsize), - factory: factory + factory: factory, + attrs: attrs ) proc getSelectorIds(hashes: var SelectorHashes; sel: Selector): bool @@ -176,29 +180,29 @@ proc add*(sheet, sheet2: CSSStylesheet) = do: sheet.attrTable[key] = value -proc addRule(stylesheet: CSSStylesheet; rule: CSSQualifiedRule) = - let sels = parseSelectors(rule.prelude, stylesheet.factory) +proc addRule(sheet: CSSStylesheet; rule: CSSQualifiedRule) = + let sels = parseSelectors(rule.prelude, sheet.factory) if sels.len > 0: var normalVals: seq[CSSComputedEntry] = @[] var importantVals: seq[CSSComputedEntry] = @[] let decls = rule.oblock.value.parseDeclarations() for decl in decls: - let vals = parseComputedValues(decl.name, decl.value) + let vals = parseComputedValues(decl.name, decl.value, sheet.attrs[]) if decl.important: importantVals.add(vals) else: normalVals.add(vals) - stylesheet.add(CSSRuleDef( + sheet.add(CSSRuleDef( sels: sels, normalVals: normalVals, importantVals: importantVals, - idx: stylesheet.len + idx: sheet.len )) - inc stylesheet.len + inc sheet.len -proc addAtRule(stylesheet: CSSStylesheet; atrule: CSSAtRule; base: URL) = +proc addAtRule(sheet: CSSStylesheet; atrule: CSSAtRule; base: URL) = if atrule.name.equalsIgnoreCase("import"): - if stylesheet.len == 0 and base != nil: + if sheet.len == 0 and base != nil: var i = 0 atrule.prelude.skipWhitespace(i) # Warning: this is a tracking vector minefield. If you implement @@ -212,28 +216,28 @@ proc addAtRule(stylesheet: CSSStylesheet; atrule: CSSAtRule; base: URL) = atrule.prelude.skipWhitespace(i) # check if there are really no media queries/layers/etc if i == atrule.prelude.len: - stylesheet.importList.add(url.get) + sheet.importList.add(url.get) elif atrule.name.equalsIgnoreCase("media"): if atrule.oblock != nil: - let query = parseMediaQueryList(atrule.prelude) + let query = parseMediaQueryList(atrule.prelude, sheet.attrs) let rules = atrule.oblock.value.parseListOfRules() if rules.len > 0: var media = CSSMediaQueryDef() - media.children = newStylesheet(rules.len, stylesheet.factory) - media.children.len = stylesheet.len + media.children = newStylesheet(rules.len, sheet.factory, sheet.attrs) + media.children.len = sheet.len media.query = query for rule in rules: if rule of CSSAtRule: media.children.addAtRule(CSSAtRule(rule), nil) else: media.children.addRule(CSSQualifiedRule(rule)) - stylesheet.mqList.add(media) - stylesheet.len = media.children.len + sheet.mqList.add(media) + sheet.len = media.children.len -proc parseStylesheet*(ibuf: string; factory: CAtomFactory; base: URL): - CSSStylesheet = +proc parseStylesheet*(ibuf: string; factory: CAtomFactory; base: URL; + attrs: ptr WindowAttributes): CSSStylesheet = let raw = parseStylesheet(ibuf) - let sheet = newStylesheet(raw.value.len, factory) + let sheet = newStylesheet(raw.value.len, factory, attrs) for v in raw.value: if v of CSSAtRule: sheet.addAtRule(CSSAtRule(v), base) diff --git a/src/html/dom.nim b/src/html/dom.nim index 41f37744..fdadd5f7 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -2543,8 +2543,8 @@ proc sheets*(document: Document): seq[CSSStylesheet] = for elem in document.documentElement.descendants: if elem of HTMLStyleElement: let style = HTMLStyleElement(elem) - style.sheet = parseStylesheet(style.textContent, document.factory, - document.baseURL) + style.sheet = style.textContent.parseStylesheet(document.factory, + document.baseURL, addr document.window.attrs) document.cachedSheets.add(style.sheet) elif elem of HTMLLinkElement: let link = HTMLLinkElement(elem) @@ -3288,12 +3288,21 @@ proc getter(ctx: JSContext; this: CSSStyleDeclaration; atom: JSAtom): return ctx.toJS(this.getPropertyValue(s)) return JS_UNINITIALIZED +template dummyWindow(): WindowAttributes = WindowAttributes( + width: 80, + height: 24, + ppc: 9, + ppl: 18, + widthPx: 80 * 9, + heightPx: 24 * 18 +) + proc setValue(this: CSSStyleDeclaration; i: int; cvals: seq[CSSComponentValue]): Err[void] = if i notin 0 .. this.decls.high: return err() var dummy: seq[CSSComputedEntry] - ?parseComputedValues(dummy, this.decls[i].name, cvals) + ?parseComputedValues(dummy, this.decls[i].name, cvals, dummyWindow()) this.decls[i].value = cvals return ok() @@ -3313,7 +3322,7 @@ proc setter(ctx: JSContext; this: CSSStyleDeclaration; atom: JSAtom; return ok() else: var dummy: seq[CSSComputedEntry] - let val0 = parseComputedValues(dummy, s, cvals) + let val0 = parseComputedValues(dummy, s, cvals, dummyWindow()) if val0.isNone: return ok() this.decls.add(CSSDeclaration(name: s, value: cvals)) @@ -3343,7 +3352,7 @@ proc loadSheet(window: Window; link: HTMLLinkElement; url: URL; applies: bool) = ).then(proc(s: JSResult[string]) = # Check applies here, to avoid leaking the window size. if s.isSome: - let sheet = s.get.parseStylesheet(window.factory, url) + let sheet = s.get.parseStylesheet(window.factory, url, addr window.attrs) if applies: # Note: we intentionally load all sheets to prevent media query # based tracking. @@ -3372,7 +3381,7 @@ proc loadResource(window: Window; link: HTMLLinkElement) = var applies = true if media != "": let cvals = parseComponentValues(media) - let media = parseMediaQueryList(cvals) + let media = parseMediaQueryList(cvals, addr window.attrs) applies = media.appliesImpl(window) window.loadSheet(link, url, applies) diff --git a/src/server/buffer.nim b/src/server/buffer.nim index f57a9184..da9b8bb5 100644 --- a/src/server/buffer.nim +++ b/src/server/buffer.nim @@ -1949,9 +1949,11 @@ proc launchBuffer*(config: BufferConfig; url: URL; attrs: WindowAttributes; const css = staticRead"res/ua.css" const quirk = css & staticRead"res/quirk.css" buffer.initDecoder() - buffer.uastyle = css.parseStylesheet(factory, nil) - buffer.quirkstyle = quirk.parseStylesheet(factory, nil) - buffer.userstyle = buffer.config.userstyle.parseStylesheet(factory, nil) + let attrsp = addr buffer.attrs + buffer.uastyle = css.parseStylesheet(factory, nil, attrsp) + buffer.quirkstyle = quirk.parseStylesheet(factory, nil, attrsp) + buffer.userstyle = buffer.config.userstyle.parseStylesheet(factory, nil, + attrsp) buffer.htmlParser = newHTML5ParserWrapper( buffer.window, buffer.url, |