diff options
author | bptato <nincsnevem662@gmail.com> | 2023-07-03 23:50:50 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2023-07-04 00:04:26 +0200 |
commit | 1c6ce0892f5ca63682fa2c4dfe9d6a4e81558eb4 (patch) | |
tree | 87a17f65545ad16543ecfe8b3bec675f9c6b3c51 | |
parent | ee8310c5490d3366559304e056bda164f7fae726 (diff) | |
download | chawan-1c6ce0892f5ca63682fa2c4dfe9d6a4e81558eb4.tar.gz |
Use LayoutUnit in layout
Reduces ugly rendering caused by rounding errors.
-rw-r--r-- | src/css/cascade.nim | 9 | ||||
-rw-r--r-- | src/css/values.nim | 43 | ||||
-rw-r--r-- | src/layout/box.nim | 104 | ||||
-rw-r--r-- | src/layout/engine.nim | 136 | ||||
-rw-r--r-- | src/layout/layoutunit.nim | 48 | ||||
-rw-r--r-- | src/render/renderdocument.nim | 54 |
6 files changed, 242 insertions, 152 deletions
diff --git a/src/css/cascade.nim b/src/css/cascade.nim index 2582763e..7996f96e 100644 --- a/src/css/cascade.nim +++ b/src/css/cascade.nim @@ -12,6 +12,7 @@ import css/stylednode import css/values import html/dom import html/tags +import layout/layoutunit import types/color type @@ -33,12 +34,12 @@ func applies(feature: MediaFeature, window: Window): bool = of FEATURE_PREFERS_COLOR_SCHEME: return feature.b of FEATURE_WIDTH: - let a = px(feature.lengthrange.a, window.attrs, 0) - let b = px(feature.lengthrange.b, window.attrs, 0) + let a = toInt(px(feature.lengthrange.a, window.attrs, 0)) + let b = toInt(px(feature.lengthrange.b, window.attrs, 0)) return window.attrs.ppc * window.attrs.width in a .. b of FEATURE_HEIGHT: - let a = px(feature.lengthrange.a, window.attrs, 0) - let b = px(feature.lengthrange.b, window.attrs, 0) + let a = toInt(px(feature.lengthrange.a, window.attrs, 0)) + let b = toInt(px(feature.lengthrange.b, window.attrs, 0)) return window.attrs.ppl * window.attrs.height in a .. b func applies(mq: MediaQuery, window: Window): bool = diff --git a/src/css/values.nim b/src/css/values.nim index 6d664a25..e988333d 100644 --- a/src/css/values.nim +++ b/src/css/values.nim @@ -8,6 +8,7 @@ import css/cssparser import css/selectorparser import img/bitmap import io/window +import layout/layoutunit import types/color import utils/opt import utils/twtstr @@ -387,37 +388,39 @@ macro `{}=`*(vals: CSSComputedValues, s: string, val: typed) = func inherited(t: CSSPropertyType): bool = return InheritedArray[t] -func em_to_px(em: float64, window: WindowAttributes): int = - int(em * float64(window.ppl)) +func em_to_px(em: float64, window: WindowAttributes): LayoutUnit = + em * toLayoutUnit(window.ppl) -func ch_to_px(ch: float64, window: WindowAttributes): int = - int(ch * float64(window.ppc)) +func ch_to_px(ch: float64, window: WindowAttributes): LayoutUnit = + ch * toLayoutUnit(window.ppc) # 水 width, we assume it's 2 chars -func ic_to_px(ic: float64, window: WindowAttributes): int = - int(ic * float64(window.ppc) * 2) +func ic_to_px(ic: float64, window: WindowAttributes): LayoutUnit = + ic * toLayoutUnit(window.ppc) * 2 # x-letter height, we assume it's em/2 -func ex_to_px(ex: float64, window: WindowAttributes): int = - int(ex * float64(window.ppc) / 2) +func ex_to_px(ex: float64, window: WindowAttributes): LayoutUnit = + ex * toLayoutUnit(window.ppc) / 2 -func px*(l: CSSLength, window: WindowAttributes, p: int): int {.inline.} = +func px*(l: CSSLength, window: WindowAttributes, p: LayoutUnit): LayoutUnit {.inline.} = case l.unit of UNIT_EM, UNIT_REM: em_to_px(l.num, window) of UNIT_CH: ch_to_px(l.num, window) of UNIT_IC: ic_to_px(l.num, window) of UNIT_EX: ex_to_px(l.num, window) - of UNIT_PERC: int(p / 100 * l.num) - of UNIT_PX: int(l.num) - of UNIT_CM: int(l.num * 37.8) - of UNIT_MM: int(l.num * 3.78) - of UNIT_IN: int(l.num * 96) - of UNIT_PC: int(l.num * 96 / 6) - of UNIT_PT: int(l.num * 96 / 72) - of UNIT_VW: int(window.width_px / 100 * l.num) - of UNIT_VH: int(window.height_px / 100 * l.num) - of UNIT_VMIN: int(min(window.width_px, window.width_px) / 100 * l.num) - of UNIT_VMAX: int(max(window.width_px, window.width_px) / 100 * l.num) + of UNIT_PERC: p * l.num / 100 + of UNIT_PX: toLayoutUnit(l.num) + of UNIT_CM: toLayoutUnit(l.num * 37.8) + of UNIT_MM: toLayoutUnit(l.num * 3.78) + of UNIT_IN: toLayoutUnit(l.num * 96) + of UNIT_PC: toLayoutUnit(l.num * 16) + of UNIT_PT: toLayoutUnit(l.num * 4 / 3) + of UNIT_VW: toLayoutUnit(float64(window.width_px) * l.num / 100) + of UNIT_VH: toLayoutUnit(float64(window.height_px) * l.num / 100) + of UNIT_VMIN: + toLayoutUnit(min(window.width_px, window.width_px) / 100 * l.num) + of UNIT_VMAX: + toLayoutUnit(max(window.width_px, window.width_px) / 100 * l.num) func listMarker*(t: CSSListStyleType, i: int): string = case t diff --git a/src/layout/box.nim b/src/layout/box.nim index 1167ca7b..87d61829 100644 --- a/src/layout/box.nim +++ b/src/layout/box.nim @@ -3,12 +3,10 @@ import options import css/stylednode import css/values import io/window +import layout/layoutunit import types/color type - #LayoutUnit* = distinct int32 - LayoutUnit* = int - Offset* = object x*: LayoutUnit y*: LayoutUnit @@ -18,8 +16,8 @@ type height*: LayoutUnit Strut* = object - pos*: int - neg*: int + pos*: LayoutUnit + neg*: LayoutUnit Viewport* = ref object window*: WindowAttributes @@ -58,12 +56,12 @@ type InlineAtom* = ref object of RootObj offset*: Offset - width*: int - height*: int + width*: LayoutUnit + height*: LayoutUnit vertalign*: CSSVerticalAlign - baseline*: int - top*: int - bottom*: int + baseline*: LayoutUnit + top*: LayoutUnit + bottom*: LayoutUnit ComputedFormat* = ref object fontstyle*: CSSFontStyle @@ -86,25 +84,25 @@ type LineBox* = ref object atoms*: seq[InlineAtom] offset*: Offset - width*: int - height*: int - baseline*: int - lineheight*: int #line-height property + width*: LayoutUnit + height*: LayoutUnit + baseline*: LayoutUnit + lineheight*: LayoutUnit #line-height property InlineContext* = ref object offset*: Offset - height*: int + height*: LayoutUnit lines*: seq[LineBox] currentLine*: LineBox - width*: int - contentWidth*: int - contentHeight*: Option[int] + width*: LayoutUnit + contentWidth*: LayoutUnit + contentHeight*: Option[LayoutUnit] contentWidthInfinite*: bool charwidth*: int whitespacenum*: int # this is actually xminwidth. - minwidth*: int + minwidth*: LayoutUnit viewport*: Viewport shrink*: bool format*: ComputedFormat @@ -118,25 +116,25 @@ type offset*: Offset # This is the padding width/height. - width*: int - height*: int - margin_top*: int - margin_bottom*: int - margin_left*: int - margin_right*: int - padding_top*: int - padding_bottom*: int - padding_left*: int - padding_right*: int - min_width*: Option[int] - max_width*: Option[int] - min_height*: Option[int] - max_height*: Option[int] + width*: LayoutUnit + height*: LayoutUnit + margin_top*: LayoutUnit + margin_bottom*: LayoutUnit + margin_left*: LayoutUnit + margin_right*: LayoutUnit + padding_top*: LayoutUnit + padding_bottom*: LayoutUnit + padding_left*: LayoutUnit + padding_right*: LayoutUnit + min_width*: Option[LayoutUnit] + max_width*: Option[LayoutUnit] + min_height*: Option[LayoutUnit] + max_height*: Option[LayoutUnit] # This is the (specified) content width/height. Actual dimensions may # differ (i.e. overflow) - contentWidth*: int - contentHeight*: Option[int] + contentWidth*: LayoutUnit + contentHeight*: Option[LayoutUnit] shrink*: bool # Whether to stretch content to infinity. contentWidthInfinite*: bool @@ -148,7 +146,7 @@ type # very bad name. basically the minimum content width after the contents # have been positioned (usually the width of the shortest word.) used # in table cells. - xminwidth*: int + xminwidth*: LayoutUnit ListItemBox* = ref object of BlockBox marker*: InlineContext @@ -166,14 +164,14 @@ type RowContext* = object cells*: seq[CellWrapper] reflow*: seq[bool] - width*: int + width*: LayoutUnit builder*: TableRowBoxBuilder ncols*: int ColumnContext* = object - minwidth*: int - maxwidth*: int - width*: int + minwidth*: LayoutUnit + maxwidth*: LayoutUnit + width*: LayoutUnit wspecified*: bool weight*: float64 @@ -182,33 +180,21 @@ type rows*: seq[RowContext] cols*: seq[ColumnContext] growing*: seq[CellWrapper] - maxwidth*: int - blockspacing*: int - inlinespacing*: int + maxwidth*: LayoutUnit + blockspacing*: LayoutUnit + inlinespacing*: LayoutUnit collapse*: bool InlineBlockBox* = ref object of InlineAtom innerbox*: BlockBox - margin_top*: int - margin_bottom*: int + margin_top*: LayoutUnit + margin_bottom*: LayoutUnit -proc append*(a: var Strut, b: int) = +proc append*(a: var Strut, b: LayoutUnit) = if b < 0: a.neg = min(b, a.neg) else: a.pos = max(b, a.pos) -func sum*(a: Strut): int = +func sum*(a: Strut): LayoutUnit = return a.pos + a.neg - -#proc `div`(a, b: LayoutUnit): LayoutUnit {.borrow.} -# -#func `+`*(a, b: LayoutUnit): LayoutUnit {.borrow.} -#func `-`*(a, b: LayoutUnit): LayoutUnit {.borrow.} -#func `*`*(a, b: LayoutUnit): LayoutUnit {.borrow.} -#func `/`*(a, b: LayoutUnit): LayoutUnit = a div b -# -#proc `+=`*(a: var LayoutUnit, b: LayoutUnit) {.borrow.} -#proc `-=`*(a: var LayoutUnit, b: LayoutUnit) {.borrow.} -#proc `*=`*(a: var LayoutUnit, b: LayoutUnit) {.borrow.} -#proc `/=`*(a: var LayoutUnit, b: LayoutUnit) = a = a div b diff --git a/src/layout/engine.nim b/src/layout/engine.nim index c3e6c3e0..7cc6731d 100644 --- a/src/layout/engine.nim +++ b/src/layout/engine.nim @@ -6,15 +6,18 @@ import css/stylednode import css/values import io/window import layout/box +import layout/layoutunit import utils/twtstr # Build phase -func px(l: CSSLength, viewport: Viewport, p = 0): int {.inline.} = +func px(l: CSSLength, viewport: Viewport, p: LayoutUnit = 0): + LayoutUnit {.inline.} = return px(l, viewport.window, p) -func px(l: CSSLength, viewport: Viewport, p: Option[int]): Option[int] {.inline.} = +func px(l: CSSLength, viewport: Viewport, p: Option[LayoutUnit]): + Option[LayoutUnit] {.inline.} = if l.unit == UNIT_PERC and p.isNone: - return none(int) + return none(LayoutUnit) return some(px(l, viewport.window, p.get(0))) type InlineState = object @@ -32,20 +35,21 @@ func whitespacepre(computed: CSSComputedValues): bool = func nowrap(computed: CSSComputedValues): bool = computed{"white-space"} in {WHITESPACE_NOWRAP, WHITESPACE_PRE} -func cellwidth(viewport: Viewport): int {.inline.} = +func cellwidth(viewport: Viewport): LayoutUnit = viewport.window.ppc -func cellwidth(ictx: InlineContext): int {.inline.} = +func cellwidth(ictx: InlineContext): LayoutUnit = ictx.viewport.cellwidth -func cellheight(viewport: Viewport): int {.inline.} = +func cellheight(viewport: Viewport): LayoutUnit = viewport.window.ppl -func cellheight(ictx: InlineContext): int {.inline.} = +func cellheight(ictx: InlineContext): LayoutUnit = ictx.viewport.cellheight # Whitespace between words -func computeShift(ictx: InlineContext, computed: CSSComputedValues): int = +func computeShift(ictx: InlineContext, computed: CSSComputedValues): + LayoutUnit = if ictx.whitespacenum > 0: if ictx.currentLine.atoms.len > 0 or computed.whitespacepre: let spacing = computed{"word-spacing"} @@ -102,7 +106,7 @@ proc horizontalAlignLine(ictx: InlineContext, line: LineBox, computed: CSSComput atom.offset.x += x of TEXT_ALIGN_JUSTIFY: if not computed.whitespacepre and not last: - var sumwidth = 0 + var sumwidth: LayoutUnit = 0 var spaces = 0 for atom in line.atoms: if atom of InlineSpacing: @@ -189,8 +193,8 @@ proc verticalAlignLine(ictx: InlineContext) = # Finally, find the inline block with the largest block margins, then apply # these to the line itself. - var margin_top = 0 - var margin_bottom = 0 + var margin_top: LayoutUnit = 0 + var margin_bottom: LayoutUnit = 0 for atom in line.atoms: if atom of InlineBlockBox: @@ -204,7 +208,8 @@ proc verticalAlignLine(ictx: InlineContext) = line.height += margin_top line.height += margin_bottom -proc addSpacing(line: LineBox, width, height: int, format: ComputedFormat, hang = false) = +proc addSpacing(line: LineBox, width, height: LayoutUnit, + format: ComputedFormat, hang = false) = let spacing = InlineSpacing(width: width, height: height, baseline: height, format: format) spacing.offset.x = line.width if not hang: @@ -243,7 +248,7 @@ proc finish(ictx: InlineContext, computed: CSSComputedValues) = for line in ictx.lines: ictx.horizontalAlignLine(line, computed, line == ictx.lines[^1]) -func minwidth(atom: InlineAtom): int = +func minwidth(atom: InlineAtom): LayoutUnit = if atom of InlineBlockBox: return cast[InlineBlockBox](atom).innerbox.xminwidth return atom.width @@ -391,7 +396,8 @@ proc layoutText(ictx: InlineContext, str: string, computed: CSSComputedValues, n func isOuterBlock(computed: CSSComputedValues): bool = return computed{"display"} in {DISPLAY_BLOCK, DISPLAY_TABLE} -proc resolveContentWidth(box: BlockBox, widthpx, availableWidth: int, isauto = false) = +proc resolveContentWidth(box: BlockBox, widthpx, availableWidth: LayoutUnit, + isauto = false) = if box.computed.isOuterBlock: let computed = box.computed let total = widthpx + box.margin_left + box.margin_right + @@ -416,8 +422,8 @@ proc resolveContentWidth(box: BlockBox, widthpx, availableWidth: int, isauto = f # Resolve percentage-based dimensions. # availableWidth: width of the containing box # availableHeight: ditto, but with height. -proc resolveDimensions(box: BlockBox, availableWidth: int, - availableHeight: Option[int]) = +proc resolveDimensions(box: BlockBox, availableWidth: LayoutUnit, + availableHeight: Option[LayoutUnit]) = let viewport = box.viewport let computed = box.computed @@ -471,7 +477,8 @@ proc resolveDimensions(box: BlockBox, availableWidth: int, min_height.get > box.contentHeight.get: box.contentHeight = min_height -proc resolveTableCellDimensions(box: BlockBox, availableWidth: int, availableHeight: Option[int]) = +proc resolveTableCellDimensions(box: BlockBox, availableWidth: LayoutUnit, + availableHeight: Option[LayoutUnit]) = let viewport = box.viewport let computed = box.computed @@ -537,7 +544,7 @@ func isShrink(box: BlockBox, parent: BlockBox = nil, override = false): bool = else: discard proc newTableCellBox(viewport: Viewport, builder: BoxBuilder, - parentWidth: int, parentHeight = none(int), shrink = true, + parentWidth: LayoutUnit, parentHeight = none(LayoutUnit), shrink = true, contentWidthInfinite = false): BlockBox = let box = BlockBox( viewport: viewport, @@ -550,7 +557,7 @@ proc newTableCellBox(viewport: Viewport, builder: BoxBuilder, return box proc newFlowRootBox(viewport: Viewport, builder: BoxBuilder, - parentWidth: int, parentHeight = none(int), shrink = true, + parentWidth: LayoutUnit, parentHeight = none(LayoutUnit), shrink = true, contentWidthInfinite = false): BlockBox = let box = BlockBox( viewport: viewport, @@ -603,7 +610,8 @@ proc newListItem(parent: BlockBox, builder: ListItemBoxBuilder): ListItemBox = box.resolveDimensions(parentWidth, parentHeight) return box -proc newInlineBlock(viewport: Viewport, builder: BoxBuilder, parentWidth: int, parentHeight = none(int)): InlineBlockBox = +proc newInlineBlock(viewport: Viewport, builder: BoxBuilder, + parentWidth: LayoutUnit, parentHeight = none(LayoutUnit)): InlineBlockBox = new(result) result.innerbox = newFlowRootBox(viewport, builder, parentWidth, parentHeight) result.vertalign = builder.computed{"vertical-align"} @@ -661,7 +669,7 @@ proc buildBlockLayout(box: BlockBox, children: seq[BoxBuilder], node: StyledNode discard box.viewport.positioned.pop() #TODO this is horribly inefficient, and should be inherited like xminwidth -func firstBaseline(box: BlockBox): int = +func firstBaseline(box: BlockBox): LayoutUnit = if box.inline != nil: if box.inline.lines.len > 0: return box.offset.y + box.inline.lines[0].baseline @@ -671,9 +679,9 @@ func firstBaseline(box: BlockBox): int = box.offset.y #TODO ditto -func baseline(box: BlockBox): int = +func baseline(box: BlockBox): LayoutUnit = if box.inline != nil: - var y = 0 + var y: LayoutUnit = 0 for line in box.inline.lines: if line == box.inline.lines[^1]: return box.offset.y + y + line.baseline @@ -690,7 +698,8 @@ proc buildLayout(box: BlockBox, builder: BlockBoxBuilder) = box.buildBlockLayout(builder.children, builder.node) # parentWidth, parentHeight: width/height of the containing block. -proc buildInlineBlock(builder: BlockBoxBuilder, parent: InlineContext, parentWidth: int, parentHeight = none(int)): InlineBlockBox = +proc buildInlineBlock(builder: BlockBoxBuilder, parent: InlineContext, + parentWidth: LayoutUnit, parentHeight = none(LayoutUnit)): InlineBlockBox = result = newInlineBlock(parent.viewport, builder, parentWidth) case builder.computed{"display"} @@ -824,7 +833,8 @@ proc positionRelative(parent, box: BlockBox) = elif not top.auto: box.offset.y -= parent.height - bottom.px(parent.viewport) - box.height -proc applyChildPosition(parent, child: BlockBox, spec: bool, x, y: var int, margin_todo: var Strut) = +proc applyChildPosition(parent, child: BlockBox, spec: bool, + x, y: var LayoutUnit, margin_todo: var Strut) = if child.computed{"position"} == POSITION_ABSOLUTE: #TODO sticky, fixed if child.computed{"left"}.auto and child.computed{"right"}.auto: child.offset.x = x @@ -845,7 +855,7 @@ proc applyChildPosition(parent, child: BlockBox, spec: bool, x, y: var int, marg margin_todo = Strut() margin_todo.append(child.margin_bottom) -proc postAlignChild(box, child: BlockBox, width: int, spec: bool) = +proc postAlignChild(box, child: BlockBox, width: LayoutUnit, spec: bool) = case box.computed{"text-align"} of TEXT_ALIGN_CHA_CENTER: child.offset.x += max(width div 2 - child.width div 2, 0) @@ -856,8 +866,8 @@ proc postAlignChild(box, child: BlockBox, width: int, spec: bool) = child.offset.x += child.margin_left proc positionBlocks(box: BlockBox) = - var y = 0 - var x = 0 + var y: LayoutUnit = 0 + var x: LayoutUnit = 0 var margin_todo: Strut # If content width has been specified, use it. @@ -928,18 +938,22 @@ proc positionBlocks(box: BlockBox) = box.width += box.padding_left box.width += box.padding_right -proc buildTableCaption(viewport: Viewport, builder: TableCaptionBoxBuilder, maxwidth: int, maxheight: Option[int], shrink = false): BlockBox = +proc buildTableCaption(viewport: Viewport, builder: TableCaptionBoxBuilder, + maxwidth: LayoutUnit, maxheight: Option[LayoutUnit], shrink = false): + BlockBox = result = viewport.newFlowRootBox(builder, maxwidth, maxheight, shrink) result.buildLayout(builder) proc buildTableCell(viewport: Viewport, builder: TableCellBoxBuilder, - parentWidth: int, parentHeight: Option[int], shrink: bool, + parentWidth: LayoutUnit, parentHeight: Option[LayoutUnit], shrink: bool, contentWidthInfinite = false): BlockBox = - result = viewport.newTableCellBox(builder, parentWidth, parentHeight, + let tableCell = viewport.newTableCellBox(builder, parentWidth, parentHeight, shrink, contentWidthInfinite) - result.buildLayout(builder) + tableCell.buildLayout(builder) + return tableCell -proc preBuildTableRow(pctx: var TableContext, box: TableRowBoxBuilder, parent: BlockBox, i: int): RowContext = +proc preBuildTableRow(pctx: var TableContext, box: TableRowBoxBuilder, + parent: BlockBox, i: int): RowContext = var ctx = RowContext(builder: box, cells: newSeq[CellWrapper](box.children.len)) var n = 0 var i = 0 @@ -952,7 +966,14 @@ proc preBuildTableRow(pctx: var TableContext, box: TableRowBoxBuilder, parent: B let spec = (not computedWidth.auto) and computedWidth.unit != UNIT_PERC let box = parent.viewport.buildTableCell(cellbuilder, parent.contentWidth, parent.contentHeight, not spec, not spec) - let wrapper = CellWrapper(box: box, builder: cellbuilder, colspan: colspan, rowspan: rowspan, rowi: i, coli: n) + let wrapper = CellWrapper( + box: box, + builder: cellbuilder, + colspan: colspan, + rowspan: rowspan, + rowi: i, + coli: n + ) ctx.cells[i] = wrapper if rowspan != 1: pctx.growing.add(wrapper) @@ -994,22 +1015,27 @@ proc preBuildTableRow(pctx: var TableContext, box: TableRowBoxBuilder, parent: B ctx.ncols = n return ctx -proc buildTableRow(pctx: TableContext, ctx: RowContext, parent: BlockBox, builder: TableRowBoxBuilder): BlockBox = - var x = 0 +proc buildTableRow(pctx: TableContext, ctx: RowContext, parent: BlockBox, + builder: TableRowBoxBuilder): BlockBox = + var x: LayoutUnit = 0 var n = 0 let row = newBlockBox(parent, builder) - var baseline = 0 + var baseline: LayoutUnit = 0 for cellw in ctx.cells: var cell = cellw.box - var w = 0 + var w: LayoutUnit = 0 for i in n ..< n + cellw.colspan: w += pctx.cols[i].width if cellw.reflow: #TODO TODO TODO this is a hack, and it doesn't even work properly let ocomputed = cellw.builder.computed cellw.builder.computed = ocomputed.copyProperties() - cellw.builder.computed{"width"} = CSSLength(num: float64(w), unit: UNIT_PX) - cell = parent.viewport.buildTableCell(cellw.builder, w, none(int), parent.shrink) + cellw.builder.computed{"width"} = CSSLength( + num: toFloat64(w), + unit: UNIT_PX + ) + cell = parent.viewport.buildTableCell(cellw.builder, w, none(LayoutUnit), + parent.shrink) cellw.builder.computed = ocomputed w = max(w, cell.width) x += pctx.inlinespacing @@ -1017,7 +1043,10 @@ proc buildTableRow(pctx: TableContext, ctx: RowContext, parent: BlockBox, builde x += pctx.inlinespacing x += w n += cellw.colspan - if cell.computed{"vertical-align"}.keyword notin {VERTICAL_ALIGN_TOP, VERTICAL_ALIGN_MIDDLE, VERTICAL_ALIGN_BOTTOM}: # baseline + const HasNoBaseline = { + VERTICAL_ALIGN_TOP, VERTICAL_ALIGN_MIDDLE, VERTICAL_ALIGN_BOTTOM + } + if cell.computed{"vertical-align"}.keyword notin HasNoBaseline: # baseline baseline = max(cell.firstBaseline, baseline) row.nested.add(cell) row.height = max(row.height, cell.height) @@ -1071,7 +1100,8 @@ iterator rows(builder: TableBoxBuilder): BoxBuilder {.inline.} = for child in footer: yield child -proc calcUnspecifiedColIndices(ctx: var TableContext, W: var int, weight: var float64): seq[int] = +proc calcUnspecifiedColIndices(ctx: var TableContext, W: var LayoutUnit, + weight: var float64): seq[int] = var avail = newSeqUninitialized[int](ctx.cols.len) var i = 0 var j = 0 @@ -1080,9 +1110,9 @@ proc calcUnspecifiedColIndices(ctx: var TableContext, W: var int, weight: var fl avail[j] = i let colw = ctx.cols[i].width let w = if colw < W: - float64(colw) + toFloat64(colw) else: - float64(W) * (ln(float64(colw) / float64(W)) + 1) + toFloat64(W) * (ln(toFloat64(colw) / toFloat64(W)) + 1) ctx.cols[i].weight = w weight += w inc j @@ -1132,11 +1162,11 @@ proc buildTableLayout(table: BlockBox, builder: TableBoxBuilder) = W = 0 redo = false # divide delta width by sum of sqrt(width) for all elem in avail - let unit = float64(W) / weight + let unit = toFloat64(W) / weight weight = 0 for i in countdown(avail.high, 0): let j = avail[i] - let x = int(unit * ctx.cols[j].weight) + let x = unit * ctx.cols[j].weight let mw = ctx.cols[j].minwidth ctx.cols[j].width = x if mw > x: @@ -1160,7 +1190,7 @@ proc buildTableLayout(table: BlockBox, builder: TableBoxBuilder) = if n < row.reflow.len and row.reflow[n]: reflow[n] = true dec n - var y = 0 + var y: LayoutUnit = 0 for roww in ctx.rows: if roww.builder.computed{"visibility"} == VISIBILITY_COLLAPSE: continue @@ -1178,27 +1208,31 @@ proc buildTableLayout(table: BlockBox, builder: TableBoxBuilder) = if ctx.caption != nil: case ctx.caption.computed{"caption-side"} of CAPTION_SIDE_TOP, CAPTION_SIDE_BLOCK_START: - let caption = table.viewport.buildTableCaption(ctx.caption, table.width, none(int), false) + let caption = table.viewport.buildTableCaption(ctx.caption, table.width, + none(LayoutUnit), false) for r in table.nested: r.offset.y += caption.height table.nested.insert(caption, 0) table.height += caption.height table.width = max(table.width, caption.width) of CAPTION_SIDE_BOTTOM, CAPTION_SIDE_BLOCK_END: - let caption = table.viewport.buildTableCaption(ctx.caption, table.width, none(int), false) + let caption = table.viewport.buildTableCaption(ctx.caption, table.width, + none(LayoutUnit), false) caption.offset.y += table.width table.nested.add(caption) table.height += caption.height table.width = max(table.width, caption.width) of CAPTION_SIDE_LEFT, CAPTION_SIDE_INLINE_START: - let caption = table.viewport.buildTableCaption(ctx.caption, table.contentWidth, some(table.height), true) + let caption = table.viewport.buildTableCaption(ctx.caption, + table.contentWidth, some(table.height), true) for r in table.nested: r.offset.x += caption.width table.nested.insert(caption, 0) table.width += caption.width table.height = max(table.height, caption.height) of CAPTION_SIDE_RIGHT, CAPTION_SIDE_INLINE_END: - let caption = table.viewport.buildTableCaption(ctx.caption, table.contentWidth, some(table.height), true) + let caption = table.viewport.buildTableCaption(ctx.caption, + table.contentWidth, some(table.height), true) caption.offset.x += table.width table.nested.add(caption) table.width += caption.width diff --git a/src/layout/layoutunit.nim b/src/layout/layoutunit.nim new file mode 100644 index 00000000..1038e5ed --- /dev/null +++ b/src/layout/layoutunit.nim @@ -0,0 +1,48 @@ +# 32-bit fixed-point number, with 6 bits of precision. + +type LayoutUnit* = distinct int32 + +func toInt*(a: LayoutUnit): int = + return int32(a) shr 6 + +converter toLayoutUnit*(a: int32): LayoutUnit = + return LayoutUnit(a shl 6) + +converter toLayoutUnit*(a: int64): LayoutUnit = + return toLayoutUnit(cast[int32](a)) + +converter toLayoutUnit*(a: int): LayoutUnit = + return toLayoutUnit(cast[int32](a)) + +converter toLayoutUnit*(a: float64): LayoutUnit = + return LayoutUnit(int32(a * 64)) + +func toFloat64*(a: LayoutUnit): float64 = + return float64(int32(a)) / 64 + +func `$`*(a: LayoutUnit): string = + $toFloat64(a) + +func `==`*(a, b: LayoutUnit): bool {.borrow.} +func `<`*(a, b: LayoutUnit): bool {.borrow.} +func `<=`*(a, b: LayoutUnit): bool {.borrow.} +func `+`*(a, b: LayoutUnit): LayoutUnit {.borrow.} +func `+=`*(a: var LayoutUnit, b: LayoutUnit) {.borrow.} +func `-`*(a, b: LayoutUnit): LayoutUnit {.borrow.} +func `-=`*(a: var LayoutUnit, b: LayoutUnit) {.borrow.} +func `*`*(a, b: LayoutUnit): LayoutUnit {.inline.} = + LayoutUnit((int32(a) * int32(b)) shr 6) +func `*=`*(a: var LayoutUnit, b: LayoutUnit) {.inline.} = + a = a * b +func `/`*(a, b: LayoutUnit): LayoutUnit {.inline.} = + let a64 = int64(a) + let b64 = int64(b) + LayoutUnit(cast[int32](((a64 shl 12) div b64) shr 6)) +func `/=`*(a: var LayoutUnit, b: LayoutUnit) {.inline.} = + a = a / b +func `div`*(a, b: LayoutUnit): LayoutUnit {.inline.} = + a / b + +func min*(a, b: LayoutUnit): LayoutUnit {.borrow.} +func max*(a, b: LayoutUnit): LayoutUnit {.borrow.} +func clamp*(x, a, b: LayoutUnit): LayoutUnit {.borrow.} diff --git a/src/render/renderdocument.nim b/src/render/renderdocument.nim index d18dbdd5..6e8b4223 100644 --- a/src/render/renderdocument.nim +++ b/src/render/renderdocument.nim @@ -10,6 +10,7 @@ import html/dom import io/window import layout/box import layout/engine +import layout/layoutunit import types/color import utils/twtstr @@ -31,7 +32,8 @@ func formatFromWord(computed: ComputedFormat): Format = result.blink = true else: discard -proc setText(lines: var FlexibleGrid, linestr: string, cformat: ComputedFormat, x, y: int) {.inline.} = +proc setText(lines: var FlexibleGrid, linestr: string, cformat: ComputedFormat, + x, y: int) {.inline.} = var i = 0 var r: Rune # make sure we have line y @@ -167,13 +169,14 @@ proc setText(lines: var FlexibleGrid, linestr: string, cformat: ComputedFormat, assert lines[y].formats[fi].pos <= nx # That's it! -proc setRowWord(lines: var FlexibleGrid, word: InlineWord, x, y: int, window: WindowAttributes) = +proc setRowWord(lines: var FlexibleGrid, word: InlineWord, x, y: LayoutUnit, + window: WindowAttributes) = var r: Rune - var y = (y + word.offset.y) div window.ppl # y cell + var y = toInt((y + word.offset.y) div window.ppl) # y cell if y < 0: return # y is outside the canvas, no need to draw - var x = (x + word.offset.x) div window.ppc # x cell + var x = toInt((x + word.offset.x) div window.ppc) # x cell var i = 0 while x < 0 and i < word.str.len: fastRuneAt(word.str, i, r) @@ -183,12 +186,13 @@ proc setRowWord(lines: var FlexibleGrid, word: InlineWord, x, y: int, window: Wi lines.setText(linestr, word.format, x, y) -proc setSpacing(lines: var FlexibleGrid, spacing: InlineSpacing, x, y: int, window: WindowAttributes) = - var y = (y + spacing.offset.y) div window.ppl # y cell +proc setSpacing(lines: var FlexibleGrid, spacing: InlineSpacing, x, y: LayoutUnit, + window: WindowAttributes) = + var y = toInt((y + spacing.offset.y) div window.ppl) # y cell if y < 0: return # y is outside the canvas, no need to draw - var x = (x + spacing.offset.x) div window.ppc # x cell - let width = spacing.width div window.ppc # cell width + var x = toInt((x + spacing.offset.x) div window.ppc) # x cell + let width = toInt(spacing.width div window.ppc) # cell width if x + width < 0: return # highest x is outside the canvas, no need to draw var i = 0 @@ -199,7 +203,8 @@ proc setSpacing(lines: var FlexibleGrid, spacing: InlineSpacing, x, y: int, wind lines.setText(linestr, spacing.format, x, y) -proc paintBackground(lines: var FlexibleGrid, color: RGBAColor, startx, starty, endx, endy: int, node: StyledNode, window: WindowAttributes) = +proc paintBackground(lines: var FlexibleGrid, color: RGBAColor, startx, + starty, endx, endy: int, node: StyledNode, window: WindowAttributes) = let color = color.cellColor() var starty = starty div window.ppl @@ -277,18 +282,22 @@ proc paintBackground(lines: var FlexibleGrid, color: RGBAColor, startx, starty, lines[y].formats[fi].format.bgcolor = color lines[y].formats[fi].node = node -func calculateErrorY(ctx: InlineContext, window: WindowAttributes): int = +func calculateErrorY(ctx: InlineContext, window: WindowAttributes): + LayoutUnit = if ctx.lines.len <= 1: return 0 - var error = 0 + var error: LayoutUnit = 0 for i in 0 ..< ctx.lines.len: if i < ctx.lines.high: let dy = ctx.lines[i + 1].offset.y - ctx.lines[i].offset.y error += dy - (dy div window.ppl) * window.ppl return error div (ctx.lines.len - 1) -proc renderBlockBox(grid: var FlexibleGrid, box: BlockBox, x, y: int, window: WindowAttributes, posx = 0, posy = 0) +proc renderBlockBox(grid: var FlexibleGrid, box: BlockBox, x, y: LayoutUnit, + window: WindowAttributes, posx: LayoutUnit = 0, posy: LayoutUnit = 0) -proc renderInlineContext(grid: var FlexibleGrid, ctx: InlineContext, x, y: int, window: WindowAttributes, posx = 0, posy = 0) = +proc renderInlineContext(grid: var FlexibleGrid, ctx: InlineContext, + x, y: LayoutUnit, window: WindowAttributes, posx: LayoutUnit = 0, + posy: LayoutUnit = 0) = let x = x + ctx.offset.x let y = y + ctx.offset.y let erry = ctx.calculateErrorY(window) @@ -316,8 +325,12 @@ proc renderInlineContext(grid: var FlexibleGrid, ctx: InlineContext, x, y: int, grid.setSpacing(spacing, x, y, window) inc i -proc renderBlockBox(grid: var FlexibleGrid, box: BlockBox, x, y: int, window: WindowAttributes, posx = 0, posy = 0) = - var stack = newSeqOfCap[(BlockBox, int, int, int, int)](100) +proc renderBlockBox(grid: var FlexibleGrid, box: BlockBox, x, y: LayoutUnit, + window: WindowAttributes, posx: LayoutUnit = 0, posy: LayoutUnit = 0) = + var stack = newSeqOfCap[tuple[ + box: BlockBox, + x, y, posx, posy: LayoutUnit + ]](100) stack.add((box, x, y, posx, posy)) while stack.len > 0: @@ -334,7 +347,12 @@ proc renderBlockBox(grid: var FlexibleGrid, box: BlockBox, x, y: int, window: Wi if box.computed{"visibility"} == VISIBILITY_VISIBLE: if box.computed{"background-color"}.a != 0: #TODO color blending - grid.paintBackground(box.computed{"background-color"}, x, y, x + box.width, y + box.height, box.node, window) + let ix = toInt(x) + let iy = toInt(y) + let iex = toInt(x + box.width) + let iey = toInt(y + box.height) + grid.paintBackground(box.computed{"background-color"}, ix, iy, iex, + iey, box.node, window) if box.computed{"background-image"}.t == CONTENT_IMAGE and box.computed{"background-image"}.s != "": # ugly hack for background-image display... TODO actually display images let s = "[img]" @@ -344,8 +362,8 @@ proc renderBlockBox(grid: var FlexibleGrid, box: BlockBox, x, y: int, window: Wi # text is larger than image; center it to minimize error ix -= w div 2 ix += box.width div 2 - let x = ix div window.ppc - let y = y div window.ppl + let x = toInt(ix div window.ppc) + let y = toInt(y div window.ppl) if y >= 0 and x + w >= 0: grid.setText(s, ComputedFormat(node: box.node), x, y) |