diff options
author | bptato <nincsnevem662@gmail.com> | 2021-12-14 21:05:32 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2021-12-14 21:05:32 +0100 |
commit | 148d6ce5154c2e8c6126509f39ae0cd2f019a0c0 (patch) | |
tree | 3f9fa2cdde8ab090ce0168f15e7e84f3d49283e9 | |
parent | fac95085e1de75e99c12ec30b2d697cf4a77d3ba (diff) | |
download | chawan-148d6ce5154c2e8c6126509f39ae0cd2f019a0c0.tar.gz |
Support all css length units
-rw-r--r-- | res/ua.css | 12 | ||||
-rw-r--r-- | src/css/style.nim | 7 | ||||
-rw-r--r-- | src/css/values.nim | 86 | ||||
-rw-r--r-- | src/io/buffer.nim | 2 | ||||
-rw-r--r-- | src/layout/box.nim | 9 | ||||
-rw-r--r-- | src/layout/engine.nim | 91 | ||||
-rw-r--r-- | src/types/enums.nim | 4 |
7 files changed, 130 insertions, 81 deletions
diff --git a/res/ua.css b/res/ua.css index 08107420..e045823f 100644 --- a/res/ua.css +++ b/res/ua.css @@ -73,20 +73,20 @@ u, ins { } h1, h2, h3, h4, h5, h6 { - margin-top: 1ch; - margin-bottom: 1ch; + margin-top: 1em; + margin-bottom: 1em; font-weight: bold; } pre { - margin-top: 1ch; - margin-bottom: 1ch; + margin-top: 1em; + margin-bottom: 1em; white-space: pre; } p { - margin-top: 1ch; - margin-bottom: 1ch; + margin-top: 1em; + margin-bottom: 1em; } a { diff --git a/src/css/style.nim b/src/css/style.nim index c1fba752..e5b8414d 100644 --- a/src/css/style.nim +++ b/src/css/style.nim @@ -195,7 +195,12 @@ proc querySelector*(document: Document, q: string): seq[Element] = result.add(document.selectElems(sel)) proc applyProperty(elem: Element, s: CSSSpecifiedValue, pseudo: PseudoElem) = - let cval = getComputedValue(s, elem.cssvalues) + var parent: CSSComputedValues + if elem.parentElement != nil: + parent = elem.parentElement.cssvalues + else: + parent = rootProperties() + let cval = getComputedValue(s, elem.cssvalues, parent) if cval.t == PROPERTY_MARGIN: let left = CSSSpecifiedValue(t: PROPERTY_MARGIN_LEFT, v: VALUE_LENGTH, length: cval.length, globalValue: s.globalValue) let right = CSSSpecifiedValue(t: PROPERTY_MARGIN_RIGHT, v: VALUE_LENGTH, length: cval.length, globalValue: s.globalValue) diff --git a/src/css/values.nim b/src/css/values.nim index c7f7a5b7..93dd3855 100644 --- a/src/css/values.nim +++ b/src/css/values.nim @@ -2,6 +2,7 @@ import unicode import tables import sugar import sequtils +import options import utils/twtstr import types/enums @@ -116,17 +117,34 @@ func valueType*(prop: CSSPropertyType): CSSValueType = func inherited(t: CSSPropertyType): bool = return InheritedArray[t] -func cells*(l: CSSLength): int = +func px(n: float64, d: int): int = + return int(n / float(d)) + +func cells*(l: CSSLength, d, w, h: int, p: Option[int], o: bool): int = case l.unit - of UNIT_EM: - return int(l.num) + of UNIT_EM, UNIT_REM: + if o: int(l.num * 2) #horizontal + else: int(l.num) #vertical of UNIT_CH: - return int(l.num) - of UNIT_REM: - return int(l.num) - else: - #TODO - return int(l.num / 8) + if o: int(l.num) #horizontal + else: int(l.num / 2) #vertical + of UNIT_IC: + if o: int(l.num * 2) #horizontal + else: int(l.num) #vertical + of UNIT_EX: + if o: int(l.num / 2) #horizontal + else: int(l.num / 4) #vertical + of UNIT_PERC: int(p.get / 100 * l.num) + of UNIT_PX: px(l.num, d) + of UNIT_CM: px(l.num * 37.8, d) + of UNIT_MM: px(l.num * 3.78, d) + of UNIT_IN: px(l.num * 96, d) + of UNIT_PC: px(l.num * 96 / 6, d) + of UNIT_PT: px(l.num * 96 / 72, d) + of UNIT_VW: px(w / 100 * l.num, d) + of UNIT_VH: px(h / 100 * l.num, d) + of UNIT_VMIN: px(min(w, h) / 100 * l.num, d) + of UNIT_VMAX: px(max(w, h) / 100 * l.num, d) func listMarker*(t: CSSListStyleType, i: int): string = case t @@ -300,24 +318,30 @@ const colors = { "rebeccapurple": 0x663399, }.map((a) => (a[0], CSSColor(rgba: RGBAColor(a[1])))).toTable() +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): CSSLength = - case unit - of "%": return CSSLength(num: val, unit: UNIT_PERC) - of "cm": return CSSLength(num: val, unit: UNIT_CM) - of "mm": return CSSLength(num: val, unit: UNIT_MM) - of "in": return CSSLength(num: val, unit: UNIT_IN) - of "px": return CSSLength(num: val, unit: UNIT_PX) - of "pt": return CSSLength(num: val, unit: UNIT_PT) - of "pc": return CSSLength(num: val, unit: UNIT_PC) - of "em": return CSSLength(num: val, unit: UNIT_EM) - of "ex": return CSSLength(num: val, unit: UNIT_EX) - of "ch": return CSSLength(num: val, unit: UNIT_CH) - of "rem": return CSSLength(num: val, unit: UNIT_REM) - of "vw": return CSSLength(num: val, unit: UNIT_VW) - of "vh": return CSSLength(num: val, unit: UNIT_VH) - of "vmin": return CSSLength(num: val, unit: UNIT_VMIN) - of "vmax": return CSSLength(num: val, unit: UNIT_VMAX) - else: raise newException(CSSValueError, "Invalid unit") + if unit in units: + CSSLength(num: val, unit: units[unit]) + else: + raise newException(CSSValueError, "Invalid unit") func color(r, g, b: int): CSSColor = return CSSColor(rgba: rgba(r, g, b, 256)) @@ -604,7 +628,7 @@ func getDefault(t: CSSPropertyType): CSSComputedValue = {.cast(noSideEffect).}: assert defaultTable[t] != nil return defaultTable[t] -func getComputedValue*(prop: CSSSpecifiedValue, current: CSSComputedValues): CSSComputedValue = +func getComputedValue*(prop: CSSSpecifiedValue, current, parent: CSSComputedValues): CSSComputedValue = case prop.globalValue of VALUE_INHERIT: if inherited(prop.t): @@ -643,9 +667,6 @@ func getComputedValue*(prop: CSSSpecifiedValue, current: CSSComputedValues): CSS return CSSComputedValue(t: prop.t, v: VALUE_LIST_STYLE_TYPE, liststyletype: prop.liststyletype) of VALUE_NONE: return CSSComputedValue(t: prop.t, v: VALUE_NONE) -func getComputedValue*(d: CSSDeclaration, current: CSSComputedValues): CSSComputedValue = - return getComputedValue(getSpecifiedValue(d), current) - proc rootProperties*(vals: var CSSComputedValues) = new(vals) for prop in low(CSSPropertyType)..high(CSSPropertyType): @@ -667,3 +688,8 @@ func inheritProperties*(parent: CSSComputedValues): CSSComputedValues = result[prop] = parent[prop] else: result[prop] = getDefault(prop) + +func rootProperties*(): CSSComputedValues = + new(result) + for prop in low(CSSPropertyType)..high(CSSPropertyType): + result[prop] = getDefault(prop) diff --git a/src/io/buffer.nim b/src/io/buffer.nim index a43223ef..cff179b6 100644 --- a/src/io/buffer.nim +++ b/src/io/buffer.nim @@ -733,7 +733,7 @@ proc renderDocument*(buffer: Buffer) = ss_init = true buffer.document.applyStylesheets(ua_stylesheet, user_stylesheet) - buffer.rootbox = buffer.document.alignBoxes(buffer.width, buffer.height) + buffer.rootbox = buffer.document.alignBoxes(buffer.attrs) if buffer.rootbox == nil: return var stack: seq[CSSBox] diff --git a/src/layout/box.nim b/src/layout/box.nim index 4e31b1e1..679e7b57 100644 --- a/src/layout/box.nim +++ b/src/layout/box.nim @@ -1,6 +1,9 @@ +import options + import types/enums import css/values import html/dom +import io/term type CSSRect* = object @@ -33,11 +36,13 @@ type fromy*: int margin_done*: int margin_todo*: int - has_blocks*: bool - anon_block*: CSSBlockBox + #following are *specified* dimensions. actual dimensions are in CSSBox + width*: int + height*: Option[int] LayoutState* = object nodes*: seq[Node] + term*: TermAttributes CSSRowBox* = object x*: int diff --git a/src/layout/engine.nim b/src/layout/engine.nim index abd76a78..2e84003d 100644 --- a/src/layout/engine.nim +++ b/src/layout/engine.nim @@ -1,10 +1,24 @@ import unicode +import options import layout/box import types/enums import html/dom import css/values import utils/twtstr +import io/term + +func cells_in(l: CSSLength, state: LayoutState, d: int, p: Option[int], o: bool): int = + return cells(l, d, state.term.width, state.term.height, p, o) + +func cells_w(l: CSSLength, state: LayoutState, p: int): int = + return l.cells_in(state, state.term.ppc, p.some, true) + +func cells_h(l: CSSLength, state: LayoutState, p: Option[int]): int = + return l.cells_in(state, state.term.ppl, p, false) + +func cells_h(l: CSSLength, state: LayoutState, p: int): int = + return l.cells_in(state, state.term.ppl, p.some, false) func newInlineContext*(box: CSSBox): InlineContext = new(result) @@ -16,9 +30,9 @@ func newBlockContext(): BlockContext = proc flushLines(box: CSSBox) = if box.icontext.conty: - inc box.height inc box.icontext.fromy inc box.bcontext.fromy + inc box.height box.icontext.conty = false box.icontext.fromy += box.bcontext.margin_todo box.bcontext.margin_done += box.bcontext.margin_todo @@ -27,12 +41,12 @@ proc flushLines(box: CSSBox) = func newBlockBox(state: var LayoutState, parent: CSSBox, vals: CSSComputedValues): CSSBlockBox = new(result) result.x = parent.x - result.x += vals[PROPERTY_MARGIN_LEFT].length.cells() + result.x += vals[PROPERTY_MARGIN_LEFT].length.cells_w(state, parent.bcontext.width) result.bcontext = newBlockContext() parent.flushLines() - let mtop = vals[PROPERTY_MARGIN_TOP].length.cells() + let mtop = vals[PROPERTY_MARGIN_TOP].length.cells_h(state, parent.bcontext.width) if mtop > parent.bcontext.margin_done: let diff = mtop - parent.bcontext.margin_done parent.icontext.fromy += diff @@ -42,28 +56,32 @@ func newBlockBox(state: var LayoutState, parent: CSSBox, vals: CSSComputedValues result.bcontext.margin_done = parent.bcontext.margin_done - let pwidth = vals[PROPERTY_WIDTH] - if pwidth.length.auto: - result.width = parent.width + let pwidth = vals[PROPERTY_WIDTH].length + if pwidth.auto: + result.bcontext.width = parent.bcontext.width else: - result.width = pwidth.length.cells() + result.bcontext.width = pwidth.cells_w(state, parent.bcontext.width) + + let pheight = vals[PROPERTY_HEIGHT].length + if not pheight.auto: + if pheight.unit != UNIT_PERC or parent.bcontext.height.issome: + result.bcontext.height = pheight.cells_h(state, parent.bcontext.height).some result.icontext = newInlineContext(parent) result.icontext.fromy = result.y result.icontext.fromx = result.x result.cssvalues = vals -func newInlineBox*(parent: CSSBox, vals: CSSComputedValues): CSSInlineBox = +func newInlineBox*(state: LayoutState, parent: CSSBox, vals: CSSComputedValues): CSSInlineBox = assert parent != nil new(result) result.x = parent.x result.y = parent.icontext.fromy - result.width = parent.width result.icontext = parent.icontext result.bcontext = parent.bcontext result.cssvalues = vals - result.icontext.fromx += vals[PROPERTY_MARGIN_LEFT].length.cells() + result.icontext.fromx += vals[PROPERTY_MARGIN_LEFT].length.cells_w(state, parent.bcontext.width) type InlineState = object ibox: CSSInlineBox @@ -113,7 +131,7 @@ proc addWord(state: var InlineState) = state.ww = 0 proc wrapNormal(state: var InlineState, r: Rune) = - if state.fromx + state.width + state.ww == state.ibox.width and r == Rune(' '): + if state.fromx + state.width + state.ww == state.ibox.bcontext.width and r == Rune(' '): state.addWord() if state.word.len == 0: if r == Rune(' '): @@ -132,16 +150,16 @@ proc checkWrap(state: var InlineState, r: Rune) = case state.ibox.cssvalues[PROPERTY_WORD_BREAK].wordbreak of WORD_BREAK_NORMAL: if state.fromx + state.width > state.ibox.x and - state.fromx + state.width + state.ww + r.width() > state.ibox.width: + state.fromx + state.width + state.ww + r.width() > state.ibox.bcontext.width: state.wrapNormal(r) of WORD_BREAK_BREAK_ALL: - if state.fromx + state.width + state.ww + r.width() > state.ibox.width: + if state.fromx + state.width + state.ww + r.width() > state.ibox.bcontext.width: var pl: seq[Rune] var i = 0 var w = 0 while i < state.word.len and state.ibox.icontext.fromx + state.rowbox.width + w < - state.ibox.width: + state.ibox.bcontext.width: pl &= state.word[i] w += state.word[i].width() inc i @@ -156,7 +174,7 @@ proc checkWrap(state: var InlineState, r: Rune) = state.inlineWrap() of WORD_BREAK_KEEP_ALL: if state.fromx + state.width > state.ibox.x and - state.fromx + state.width + state.ww + r.width() > state.ibox.width: + state.fromx + state.width + state.ww + r.width() > state.ibox.bcontext.width: state.wrapNormal(r) proc preWrap(state: var InlineState) = @@ -180,9 +198,7 @@ proc processInlineBox(lstate: var LayoutState, parent: CSSBox, str: string): CSS else: # TODO TODO TODO I highly doubt this is correct but it's the only way it # makes sense... - var inherit: CSSComputedValues - inherit.inheritProperties(parent.cssvalues) - state.ibox = newInlineBox(parent, inherit) + state.ibox = lstate.newInlineBox(parent, parent.cssvalues.inheritProperties()) if str.len == 0: return @@ -225,7 +241,8 @@ proc processInlineBox(lstate: var LayoutState, parent: CSSBox, str: string): CSS fastRuneAt(str, i, r) rw = r.width() - #TODO a better line wrapping algorithm would be nice + # TODO a better line wrapping algorithm would be nice... especially because + # this one doesn't even work if rw > 1 or state.ibox.cssvalues[PROPERTY_WORD_BREAK].wordbreak == WORD_BREAK_BREAK_ALL: state.addWord() @@ -265,26 +282,22 @@ proc add(state: var LayoutState, parent: CSSBox, box: CSSBox) = box.flushLines() - let mbot = box.cssvalues[PROPERTY_MARGIN_BOTTOM].length.cells() + let mbot = box.cssvalues[PROPERTY_MARGIN_BOTTOM].length.cells_h(state, parent.bcontext.width) parent.bcontext.margin_todo += mbot parent.bcontext.margin_done = box.bcontext.margin_done parent.bcontext.margin_todo = max(parent.bcontext.margin_todo - box.bcontext.margin_done, 0) + if box.bcontext.height.isnone: + parent.icontext.fromy = box.icontext.fromy + else: + parent.icontext.fromy += box.bcontext.height.get #eprint "END", CSSBlockBox(box).tag, box.icontext.fromy elif box of CSSInlineBox: - parent.icontext.fromx += box.cssvalues[PROPERTY_MARGIN_RIGHT].length.cells() + parent.icontext.fromx += box.cssvalues[PROPERTY_MARGIN_RIGHT].length.cells_w(state, parent.bcontext.width) + parent.icontext.fromy = box.icontext.fromy - let pheight = box.cssvalues[PROPERTY_HEIGHT] parent.height += box.height - parent.icontext.fromy = box.icontext.fromy - - if not pheight.length.auto: - let max = pheight.length.cells() - let diff = box.height - max - if diff > 0: - parent.height -= diff - parent.icontext.fromy -= diff parent.children.add(box) @@ -311,7 +324,7 @@ proc processComputedValueBox(state: var LayoutState, parent: CSSBox, values: CSS result = state.newBlockBox(parent, values) #CSSBlockBox(result).tag = $elem.tagType of DISPLAY_INLINE: - result = newInlineBox(parent, values) + result = state.newInlineBox(parent, values) of DISPLAY_LIST_ITEM: result = state.newBlockBox(parent, values) of DISPLAY_NONE: @@ -373,14 +386,15 @@ proc processBeforePseudoElem(state: var LayoutState, parent: CSSBox, node: Node) inline.node = node state.add(box, inline) + state.add(parent, box) + # same as before except it's after proc processAfterPseudoElem(state: var LayoutState, parent: CSSBox, node: Node) = if node.nodeType == ELEMENT_NODE: let elem = Element(node) if elem.cssvalues_after != nil: - var box: CSSBox - box = state.processComputedValueBox(parent, elem.cssvalues_after) + let box = state.processComputedValueBox(parent, elem.cssvalues_after) if box != nil: box.node = node @@ -421,15 +435,14 @@ proc processNodes(state: var LayoutState, parent: CSSBox, node: Node) = discard state.nodes.pop() -proc alignBoxes*(document: Document, width: int, height: int): CSSBox = +proc alignBoxes*(document: Document, term: TermAttributes): CSSBox = var state: LayoutState - var rootbox = CSSBlockBox(x: 0, y: 0, width: width, height: 0) - rootbox.cssvalues = document.root.cssvalues + state.term = term + var rootbox = CSSBlockBox(x: 0, y: 0) + rootbox.cssvalues = rootProperties() rootbox.icontext = newInlineContext(rootbox) rootbox.bcontext = newBlockContext() + rootbox.bcontext.width = term.width state.nodes.add(document.root) state.processNodes(rootbox, document.root) return rootbox - -proc realignBoxes*(document: Document, width, height: int) = - var state: LayoutState diff --git a/src/types/enums.nim b/src/types/enums.nim index 028b7557..300744ba 100644 --- a/src/types/enums.nim +++ b/src/types/enums.nim @@ -52,8 +52,8 @@ type 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_EM, UNIT_EX, UNIT_CH, UNIT_REM, UNIT_VW, UNIT_VH, UNIT_VMIN, + UNIT_VMAX, UNIT_PERC, UNIT_IC CSSPropertyType* = enum PROPERTY_NONE, PROPERTY_ALL, PROPERTY_COLOR, PROPERTY_MARGIN, |