diff options
author | bptato <nincsnevem662@gmail.com> | 2022-01-29 11:04:21 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2022-01-29 11:04:21 +0100 |
commit | 5c19adf246650306eaee3605b7b9fc47a6ca73fb (patch) | |
tree | a16707ce3c121b255e255980c52b8141ad1d79bf | |
parent | a42f0b169f5f612b3b14026618bcb26a2afeedca (diff) | |
download | chawan-5c19adf246650306eaee3605b7b9fc47a6ca73fb.tar.gz |
Implement text-align and <center>
-rw-r--r-- | res/ua.css | 4 | ||||
-rw-r--r-- | src/css/values.nim | 33 | ||||
-rw-r--r-- | src/html/parser.nim | 4 | ||||
-rw-r--r-- | src/layout/box.nim | 5 | ||||
-rw-r--r-- | src/layout/engine.nim | 163 | ||||
-rw-r--r-- | src/utils/radixtree.nim | 11 |
6 files changed, 161 insertions, 59 deletions
diff --git a/res/ua.css b/res/ua.css index a618baf5..3a98099b 100644 --- a/res/ua.css +++ b/res/ua.css @@ -162,3 +162,7 @@ dd { dl[compact] dt + br { display: none; } + +center { + text-align: -moz-center; +} diff --git a/src/css/values.nim b/src/css/values.nim index 926a9392..ff3244cb 100644 --- a/src/css/values.nim +++ b/src/css/values.nim @@ -29,12 +29,13 @@ type PROPERTY_HEIGHT, PROPERTY_LIST_STYLE_TYPE, PROPERTY_PADDING, PROPERTY_PADDING_TOP, PROPERTY_PADDING_LEFT, PROPERTY_PADDING_RIGHT, PROPERTY_PADDING_BOTTOM, PROPERTY_WORD_SPACING, PROPERTY_VERTICAL_ALIGN, - PROPERTY_LINE_HEIGHT + PROPERTY_LINE_HEIGHT, PROPERTY_TEXT_ALIGN CSSValueType* = enum VALUE_NONE, VALUE_LENGTH, VALUE_COLOR, VALUE_CONTENT, VALUE_DISPLAY, VALUE_FONT_STYLE, VALUE_WHITE_SPACE, VALUE_INTEGER, VALUE_TEXT_DECORATION, - VALUE_WORD_BREAK, VALUE_LIST_STYLE_TYPE, VALUE_VERTICAL_ALIGN + VALUE_WORD_BREAK, VALUE_LIST_STYLE_TYPE, VALUE_VERTICAL_ALIGN, + VALUE_TEXT_ALIGN CSSGlobalValueType* = enum VALUE_NOGLOBAL, VALUE_INITIAL, VALUE_INHERIT, VALUE_REVERT, VALUE_UNSET @@ -75,6 +76,10 @@ type VERTICAL_ALIGN_TEXT_TOP, VERTICAL_ALIGN_TEXT_BOTTOM, VERTICAL_ALIGN_MIDDLE, VERTICAL_ALIGN_TOP, VERTICAL_ALIGN_BOTTOM + CSSTextAlign* = enum + TEXT_ALIGN_START, TEXT_ALIGN_END, TEXT_ALIGN_LEFT, TEXT_ALIGN_RIGHT, + TEXT_ALIGN_CENTER, TEXT_ALIGN_JUSTIFY, TEXT_ALIGN_MOZ_CENTER + type CSSLength* = object num*: float64 @@ -114,6 +119,8 @@ type liststyletype*: CSSListStyleType of VALUE_VERTICAL_ALIGN: verticalalign*: CSSVerticalAlign + of VALUE_TEXT_ALIGN: + textalign*: CSSTextAlign of VALUE_NONE: discard CSSSpecifiedValues* = ref array[CSSPropertyType, CSSSpecifiedValue] @@ -146,6 +153,7 @@ const PropertyNames = { "word-spacing": PROPERTY_WORD_SPACING, "vertical-align": PROPERTY_VERTICAL_ALIGN, "line-height": PROPERTY_LINE_HEIGHT, + "text-align": PROPERTY_TEXT_ALIGN, }.toTable() const ValueTypes = [ @@ -175,12 +183,14 @@ const ValueTypes = [ PROPERTY_WORD_SPACING: VALUE_LENGTH, PROPERTY_VERTICAL_ALIGN: VALUE_VERTICAL_ALIGN, PROPERTY_LINE_HEIGHT: VALUE_LENGTH, + PROPERTY_TEXT_ALIGN: VALUE_TEXT_ALIGN, ] const InheritedProperties = { PROPERTY_COLOR, PROPERTY_FONT_STYLE, PROPERTY_WHITE_SPACE, PROPERTY_FONT_WEIGHT, PROPERTY_TEXT_DECORATION, PROPERTY_WORD_BREAK, - PROPERTY_LIST_STYLE_TYPE, PROPERTY_WORD_SPACING, PROPERTY_LINE_HEIGHT + PROPERTY_LIST_STYLE_TYPE, PROPERTY_WORD_SPACING, PROPERTY_LINE_HEIGHT, + PROPERTY_TEXT_ALIGN } func getPropInheritedArray(): array[CSSPropertyType, bool] = @@ -717,6 +727,21 @@ func cssLineHeight(d: CSSDeclaration): CSSLength = return cssLength(d) raise newException(CSSValueError, "Invalid line height") +func cssTextAlign(d: CSSDeclaration): CSSTextAlign = + if isToken(d): + let tok = getToken(d) + if tok.tokenType == CSS_IDENT_TOKEN: + return case $tok.value + of "start": TEXT_ALIGN_START + of "end": TEXT_ALIGN_END + of "left": TEXT_ALIGN_LEFT + of "right": TEXT_ALIGN_RIGHT + of "center": TEXT_ALIGN_CENTER + of "justify": TEXT_ALIGN_JUSTIFY + of "-moz-center": TEXT_ALIGN_MOZ_CENTER + else: raise newException(CSSValueError, "Invalid text align") + raise newException(CSSValueError, "Invalid text align") + proc getValueFromDecl(val: CSSSpecifiedValue, d: CSSDeclaration, vtype: CSSValueType, ptype: CSSPropertyType) = case vtype of VALUE_COLOR: val.color = cssColor(d) @@ -739,6 +764,7 @@ proc getValueFromDecl(val: CSSSpecifiedValue, d: CSSDeclaration, vtype: CSSValue of VALUE_WORD_BREAK: val.wordbreak = cssWordBreak(d) of VALUE_LIST_STYLE_TYPE: val.liststyletype = cssListStyleType(d) of VALUE_VERTICAL_ALIGN: val.verticalalign = cssVerticalAlign(d) + of VALUE_TEXT_ALIGN: val.textalign = cssTextAlign(d) of VALUE_NONE: discard func getInitialColor(t: CSSPropertyType): CSSColor = @@ -811,6 +837,7 @@ func equals*(a, b: CSSSpecifiedValue): bool = of VALUE_WORD_BREAK: return a.wordbreak == b.wordbreak of VALUE_LIST_STYLE_TYPE: return a.liststyletype == b.liststyletype of VALUE_VERTICAL_ALIGN: return a.verticalalign == b.verticalalign + of VALUE_TEXT_ALIGN: return a.textalign == b.textalign of VALUE_NONE: return true return false diff --git a/src/html/parser.nim b/src/html/parser.nim index d28be97b..305d1afa 100644 --- a/src/html/parser.nim +++ b/src/html/parser.nim @@ -94,9 +94,9 @@ proc getescapecmd(buf: string, at: var int): string = s = "" inc i - if n.leaf: + if n.value.issome: at = i - return n.value + return n.value.get return "&" diff --git a/src/layout/box.nim b/src/layout/box.nim index aa897ae6..64fe0741 100644 --- a/src/layout/box.nim +++ b/src/layout/box.nim @@ -50,7 +50,6 @@ type InlineContext* = ref object relx*: int rely*: int - width*: int height*: int rows*: seq[InlineRow] thisrow*: InlineRow @@ -59,6 +58,7 @@ type maxwidth*: int viewport*: Viewport node*: Node + shrink*: bool BlockContext* = ref object of InlineAtom inline*: InlineContext @@ -75,7 +75,10 @@ type padding_right*: int compwidth*: int + maxwidth*: int + nocenter*: bool compheight*: Option[int] + shrink*: bool done*: bool InlineBox* = ref object of CSSBox diff --git a/src/layout/engine.nim b/src/layout/engine.nim index 986d6723..18939a57 100644 --- a/src/layout/engine.nim +++ b/src/layout/engine.nim @@ -74,52 +74,102 @@ proc newWord(state: var InlineState) = word.format.node = state.node state.word = word -proc finishRow(ictx: InlineContext, specified: CSSSpecifiedValues, force = false) = - if ictx.thisrow.atoms.len != 0 or force: - let oldrow = ictx.thisrow +proc horizontalAlignRow(ictx: InlineContext, row: InlineRow, specified: CSSSpecifiedValues, maxwidth: int, last = false) = + let maxwidth = if ictx.shrink: + ictx.maxwidth + else: + maxwidth + # we don't support directions for now so left = start and right = end + case specified{"text-align"} + of TEXT_ALIGN_START, TEXT_ALIGN_LEFT: + discard + of TEXT_ALIGN_END, TEXT_ALIGN_RIGHT: + # move everything + let x = max(maxwidth, row.width) - row.width + for atom in row.atoms: + atom.relx += x + of TEXT_ALIGN_CENTER: + let x = max((max(maxwidth - row.relx, row.width)) div 2 - row.width div 2, 0) + for atom in row.atoms: + atom.relx += x + of TEXT_ALIGN_JUSTIFY: + if not specified.whitespacepre and not last: + var sumwidth = 0 + var spaces = 0 + for atom in row.atoms: + if atom of InlineSpacing: + discard + else: + inc spaces + sumwidth += atom.width + dec spaces + if spaces > 0: + let spacingwidth = (ictx.maxwidth - sumwidth) div spaces + let oldwidth = row.width + row.width = 0 + for atom in row.atoms: + atom.relx = row.width + if atom of InlineSpacing: + let atom = InlineSpacing(atom) + atom.width = spacingwidth + row.width += atom.width + else: + discard - var baseline = if oldrow.height < oldrow.lineheight: - let lines = oldrow.lineheight div ictx.cellheight - ceilDiv(lines, 2) * ictx.cellheight +proc verticalAlignRow(ictx: InlineContext) = + let row = ictx.thisrow + var baseline = if row.height < row.lineheight: + let lines = row.lineheight div ictx.cellheight + int(ceil(lines / 2)) * ictx.cellheight + else: + 0 + + # line-height is the minimum line height + row.height = max(row.height, row.lineheight) + + for atom in row.atoms: + case atom.vertalign.keyword + of VERTICAL_ALIGN_BASELINE: + let len = atom.vertalign.length.px(ictx.viewport, row.lineheight) + baseline = max(baseline, atom.height + len) + of VERTICAL_ALIGN_TOP, VERTICAL_ALIGN_BOTTOM: + row.height = max(atom.height, row.height) + of VERTICAL_ALIGN_MIDDLE: + baseline = max(baseline, atom.height div 2) else: + baseline = max(baseline, atom.height) + row.height = max(baseline, row.height) + + for atom in row.atoms: + let diff = case atom.vertalign.keyword + of VERTICAL_ALIGN_BASELINE: + let len = atom.vertalign.length.px(ictx.viewport, row.lineheight) + baseline - atom.height - len + of VERTICAL_ALIGN_MIDDLE: + baseline - atom.height div 2 + of VERTICAL_ALIGN_TOP: 0 + of VERTICAL_ALIGN_BOTTOM: + row.height - atom.height + else: + baseline - atom.height + atom.rely += diff - # line-height is the minimum line height - oldrow.height = max(oldrow.height, oldrow.lineheight) - - for atom in oldrow.atoms: - case atom.vertalign.keyword - of VERTICAL_ALIGN_BASELINE: - let len = atom.vertalign.length.px(ictx.viewport, oldrow.lineheight) - baseline = max(baseline, atom.height + len) - of VERTICAL_ALIGN_TOP, VERTICAL_ALIGN_BOTTOM: - oldrow.height = max(atom.height, oldrow.height) - of VERTICAL_ALIGN_MIDDLE: - baseline = max(baseline, atom.height div 2) - else: - baseline = max(baseline, atom.height) - oldrow.height = max(baseline, oldrow.height) - - for atom in oldrow.atoms: - let diff = case atom.vertalign.keyword - of VERTICAL_ALIGN_BASELINE: - let len = atom.vertalign.length.px(ictx.viewport, oldrow.lineheight) - baseline - atom.height - len - of VERTICAL_ALIGN_MIDDLE: - baseline - atom.height div 2 - of VERTICAL_ALIGN_TOP: - 0 - of VERTICAL_ALIGN_BOTTOM: - oldrow.height - atom.height - else: - baseline - atom.height - atom.rely += diff +proc finishRow(ictx: InlineContext, specified: CSSSpecifiedValues, maxwidth: int, force = false) = + if ictx.thisrow.atoms.len != 0 or force: + ictx.verticalAlignRow() + let oldrow = ictx.thisrow ictx.rows.add(oldrow) ictx.height += oldrow.height - ictx.width = max(ictx.width, oldrow.width) + ictx.maxwidth = max(ictx.maxwidth, oldrow.width) ictx.thisrow = InlineRow(rely: oldrow.rely + oldrow.height) +proc finish(ictx: InlineContext, specified: CSSSpecifiedValues, maxwidth: int) = + ictx.finishRow(specified, maxwidth) + for row in ictx.rows: + ictx.horizontalAlignRow(row, specified, maxwidth, row == ictx.rows[^1]) + proc addSpacing(row: InlineRow, width, height: int, format: ComputedFormat) {.inline.} = let spacing = InlineSpacing(width: width, height: height, format: format) spacing.relx = row.width @@ -132,7 +182,7 @@ proc addAtom(ictx: InlineContext, atom: InlineAtom, maxwidth: int, specified: CS # Line wrapping if specified{"white-space"} notin {WHITESPACE_NOWRAP, WHITESPACE_PRE}: if ictx.thisrow.width + atom.width + shift > maxwidth: - ictx.finishRow(specified) + ictx.finishRow(specified, maxwidth, false) # Recompute on newline shift = ictx.computeShift(specified) ictx.whitespace = false @@ -161,9 +211,9 @@ proc addWord(state: var InlineState) = state.newWord() # Start a new line, even if the previous one is empty -proc flushLine(ictx: InlineContext, specified: CSSSpecifiedValues) = +proc flushLine(ictx: InlineContext, specified: CSSSpecifiedValues, maxwidth: int) = ictx.thisrow.lineheight = computeLineHeight(ictx.viewport, specified) - ictx.finishRow(specified, true) + ictx.finishRow(specified, maxwidth, true) proc checkWrap(state: var InlineState, r: Rune) = if state.specified{"white-space"} in {WHITESPACE_NOWRAP, WHITESPACE_PRE}: @@ -173,11 +223,11 @@ proc checkWrap(state: var InlineState, r: Rune) = of WORD_BREAK_BREAK_ALL: if state.ictx.thisrow.width + state.word.width + shift + r.width() * state.ictx.cellwidth > state.maxwidth: state.addWord() - state.ictx.finishRow(state.specified) + state.ictx.finishRow(state.specified, state.maxwidth, false) state.ictx.whitespace = false of WORD_BREAK_KEEP_ALL: if state.ictx.thisrow.width + state.word.width + shift + r.width() * state.ictx.cellwidth > state.maxwidth: - state.ictx.finishRow(state.specified) + state.ictx.finishRow(state.specified, state.maxwidth, false) state.ictx.whitespace = false else: discard @@ -188,7 +238,7 @@ proc processWhitespace(state: var InlineState, c: char) = state.ictx.whitespace = true of WHITESPACE_PRE_LINE, WHITESPACE_PRE, WHITESPACE_PRE_WRAP: if c == '\n': - state.ictx.flushLine(state.specified) + state.ictx.flushLine(state.specified, state.maxwidth) else: state.ictx.whitespace = true @@ -255,9 +305,11 @@ proc newBlockContext_common(parent: BlockContext, box: CSSBox): BlockContext {.i proc newBlockContext(parent: BlockContext, box: BlockBox): BlockContext = result = newBlockContext_common(parent, box) + result.shrink = result.specified{"width"}.auto and parent.shrink proc newInlineBlockContext(parent: BlockContext, box: InlineBlockBox): BlockContext = result = newBlockContext_common(parent, box) + result.shrink = result.specified{"width"}.auto # Anonymous block box. proc newBlockContext(parent: BlockContext): BlockContext = @@ -265,6 +317,7 @@ proc newBlockContext(parent: BlockContext): BlockContext = result.specified = parent.specified.inheritProperties() result.viewport = parent.viewport result.computedDimensions(parent.compwidth, parent.compheight) + result.shrink = result.specified{"width"}.auto and parent.shrink # Anonymous block box (root). proc newBlockContext(viewport: Viewport): BlockContext = @@ -277,6 +330,7 @@ proc newInlineContext(bctx: BlockContext): InlineContext = new(result) result.thisrow = InlineRow() result.viewport = bctx.viewport + result.shrink = bctx.shrink bctx.inline = result # Blocks' positions do not have to be arranged if alignBlocks is called with @@ -290,10 +344,14 @@ proc arrangeBlocks(bctx: BlockContext, selfcontained: bool) = bctx.height += bctx.padding_top x += bctx.padding_left + if bctx.specified{"text-align"} == TEXT_ALIGN_MOZ_CENTER: + x += bctx.compwidth div 2 template apply_child(child: BlockContext) = child.rely = y child.relx = x + child.margin_left + if bctx.specified{"text-align"} == TEXT_ALIGN_MOZ_CENTER: + child.relx -= child.width div 2 y += child.height bctx.height += child.height bctx.width = max(bctx.width, child.width) @@ -381,7 +439,7 @@ proc alignInlineBlock(bctx: BlockContext, box: InlineBlockBox) = proc alignInline(bctx: BlockContext, box: InlineBox) = assert box.ictx != nil if box.newline: - box.ictx.flushLine(bctx.specified) + box.ictx.flushLine(bctx.specified, bctx.compwidth) let margin_left = box.specified{"margin-left"}.px(bctx.viewport, bctx.compwidth) box.ictx.thisrow.width += margin_left @@ -430,12 +488,12 @@ proc alignInlines(bctx: BlockContext, inlines: seq[CSSBox]) = bctx.alignInlineBlock(child) else: assert false, "child.t is " & $child.t - ictx.finishRow(bctx.specified) + ictx.finish(bctx.specified, bctx.compwidth) bctx.height += ictx.height if bctx.compheight.issome: bctx.height = bctx.compheight.get - bctx.width = max(bctx.width, ictx.width) + bctx.width = max(bctx.width, ictx.maxwidth) template flush_group() = if blockgroup.len > 0: @@ -538,13 +596,24 @@ proc generateBox(elem: Element, viewport: Viewport, bctx: BlockContext = nil): C if viewport.map[elem.uid] != nil: let box = viewport.map[elem.uid] var bctx = bctx - if box.specified{"display"} in {DISPLAY_BLOCK, DISPLAY_LIST_ITEM, DISPLAY_INLINE_BLOCK}: + case box.specified{"display"} + of DISPLAY_BLOCK, DISPLAY_LIST_ITEM: let box = BlockBox(box) if bctx == nil: box.bctx = viewport.newBlockContext() else: box.bctx = bctx.newBlockContext(box) bctx = box.bctx + of DISPLAY_INLINE_BLOCK: + let box = InlineBlockBox(box) + if bctx == nil: + assert false + box.bctx = viewport.newBlockContext() + else: + box.bctx = bctx.newInlineBlockContext(box) + bctx = box.bctx + else: + discard var i = 0 while i < box.children.len: diff --git a/src/utils/radixtree.nim b/src/utils/radixtree.nim index b361d367..49072d65 100644 --- a/src/utils/radixtree.nim +++ b/src/utils/radixtree.nim @@ -2,6 +2,7 @@ # however it *is* faster. import json +import options import tables type @@ -9,9 +10,7 @@ type RadixNode*[T] = ref object children*: seq[RadixPair[T]] - case leaf*: bool - of true: value*: T - of false: discard + value*: Option[T] func newRadixTree*[T](): RadixNode[T] = new(result) @@ -61,10 +60,10 @@ func contains[T](node: RadixNode[T], k: string): bool = # O(1) add procedures for insert proc add[T](node: RadixNode[T], k: string, v: T) = - node.children.add((k, RadixNode[T](leaf: true, value: v))) + node.children.add((k, RadixNode[T](value: v.some))) proc add[T](node: RadixNode[T], k: string) = - node.children.add((k, RadixNode[T](leaf: false))) + node.children.add((k, RadixNode[T]())) proc add[T](node: RadixNode[T], k: string, v: RadixNode[T]) = node.children.add((k, v)) @@ -124,7 +123,7 @@ proc `[]=`*[T](tree: RadixNode[T], key: string, value: T) = p.children.del(k) elif key.len == t.len: # new matches a node, so replace - p.children[k].v = RadixNode[T](leaf: true, value: value, children: n.children) + p.children[k].v = RadixNode[T](value: value.some, children: n.children) elif key.len > t.len: # new is longer than the old, so add child to old n.add(key.substr(i), value) |