diff options
Diffstat (limited to 'src/layout')
-rw-r--r-- | src/layout/box.nim | 14 | ||||
-rw-r--r-- | src/layout/layout.nim | 340 |
2 files changed, 129 insertions, 225 deletions
diff --git a/src/layout/box.nim b/src/layout/box.nim index a36bb424..bacaa1b1 100644 --- a/src/layout/box.nim +++ b/src/layout/box.nim @@ -2,6 +2,8 @@ import unicode import utils/twtstr import io/cell +import types/enums +import css/style type CSSRect* = object @@ -14,10 +16,20 @@ type CSSBoxObj = object of RootObj x*: int y*: int - fromx*: int width*: int height*: int + bh*: int + bw*: int children*: seq[CSSBox] + context*: FormatContext + + FormatContext* = ref object + context*: FormatContextType + fromx*: int + fromy*: int + conty*: bool + whitespace*: bool + cssvalues*: CSSComputedValues CSSRowBox* = object x*: int diff --git a/src/layout/layout.nim b/src/layout/layout.nim index 96f27c9c..fe96b416 100644 --- a/src/layout/layout.nim +++ b/src/layout/layout.nim @@ -1,4 +1,5 @@ import unicode +import options import layout/box import types/enums @@ -8,252 +9,143 @@ import io/buffer import io/cell import utils/twtstr -#type -# Frame = object -# node: Node -# maxwidth: int -# maxheight: int -# box: CSSBox -# context: FormatContext +func newContext*(box: CSSBox): FormatContext = + new(result) + result.fromx = box.x + result.whitespace = true +func newBlockBox*(parent: CSSBox): CSSBlockBox = + new(result) + result.x = parent.x + if parent.context.conty: + inc parent.height + parent.context.conty = false + result.y = parent.y + parent.height -#proc addText(state: var LayoutState, frame: var Frame, text: Text) = -# let maxwidth = frame.maxwidth -# let fromx = state.fromx -# let x = state.x -# if maxwidth == 0: return -# if not (frame.box of CSSInlineBox): return -# var box = CSSInlineBox(frame.box) -# var r: Rune -# var i = 0 -# var rowbox: CSSRowBox -# if fromx > x: -# rowbox = CSSRowBox(x: state.fromx, y: state.y) -# let m = maxwidth - fromx + x -# var w = 0 -# var lf = false -# while i < text.data.len: -# let pi = i -# fastRuneAt(text.data, i, r) -# let rw = r.width() -# if rw + w > m: -# i = pi -# inc state.y -# lf = true -# break -# else: -# if r.isWhitespace(): -# if not state.whitespace: -# state.whitespace = true -# rowbox.runes.add(Rune(' ')) -# inc rowbox.width -# w += rw -# else: -# if state.whitespace: -# state.whitespace = false -# rowbox.runes.add(r) -# inc rowbox.width -# w += rw -# -# box.content.add(rowbox) -# if lf: -# state.fromx = 0 -# else: -# state.fromx += rowbox.width -# -# if i < text.data.len: -# rowbox = CSSRowBox(x: state.x, y: state.y) -# var w = 0 -# while i < text.data.len: -# let pi = i -# fastRuneAt(text.data, i, r) -# let rw = r.width() -# if rw + w > maxwidth: -# i = pi -# w = 0 -# box.content.add(rowbox) -# state.fromx += rowbox.width -# inc state.y -# rowbox = CSSRowBox(x: state.x, y: state.y) -# else: -# rowbox.runes.add(r) -# inc rowbox.width -# w += rw -# -# if rowbox.width > 0: -# box.content.add(rowbox) -# state.fromx += rowbox.width + result.width = parent.width + result.context = newContext(parent) -#proc alignBoxes*(buffer: Buffer) = -# #buffer.rootbox = buffer.document.root.genBox(buffer.width, buffer.height) -# buffer.rootbox = CSSBlockBox(x: 0, y: 0, width: buffer.width, height: buffer.height) -# buffer.rootbox.children.add(CSSInlineBox(x: 0, y: 0, width: buffer.width, height: buffer.height)) -# var x = 0 -# var stack: seq[Frame] -# var state: LayoutState -# stack.add(Frame(node: buffer.document.root, box: buffer.rootbox, maxwidth: 80, context: CONTEXT_BLOCK)) -# while stack.len > 0: -# var frame = stack.pop() -# let node = frame.node -# -# case frame.context -# of CONTEXT_BLOCK: -# case node.nodeType -# of TEXT_NODE: #anonymous -# discard -# of ELEMENT_NODE: #new formatting context -# let elem = Element(node) -# case elem.cssvalues[RULE_DISPLAY].display -# of DISPLAY_BLOCK: -# let parent = frame.box -# state.whitespace = false -# frame.box = CSSBlockBox(x: state.x, y: state.y, width: frame.maxwidth) -# parent.children.add(frame.box) -# frame.context = CONTEXT_BLOCK -# of DISPLAY_INLINE: -# let parent = frame.box -# frame.box = CSSInlineBox(x: state.x, y: state.y, width: frame.maxwidth) -# parent.children.add(frame.box) -# frame.context = CONTEXT_INLINE -# of DISPLAY_NONE: continue -# else: discard #TODO -# else: discard -# of CONTEXT_INLINE: -# case node.nodeType -# of TEXT_NODE: #just add to existing inline box no problem -# let text = Text(node) -# state.addText(frame, text) -# of ELEMENT_NODE: -# let elem = Element(node) -# case elem.cssvalues[RULE_DISPLAY].display -# of DISPLAY_NONE: continue -# else: -# #ok this is the difficult part (TODO) -# #NOTE we're assuming the parent isn't inline, if it is we're screwed -# #basically what we have to do is: -# #* create a new anonymous BLOCK box -# #* for every previous INLINE box in parent (BLOCK) box, do: -# #* copy INLINE box into new anonymous BLOCK box -# #* delete INLINE box -# #* create a new BLOCK box (this one) -# #* NOTE after our BLOCK there's a continuation of the last INLINE box -# -# eprint "?????" -# else: discard -# -# var i = node.childNodes.len - 1 -# while i >= 0: -# let child = node.childNodes[i] -# stack.add(Frame(node: child, box: frame.box, maxwidth: frame.maxwidth, context: frame.context)) -# dec i +func newInlineBox*(parent: CSSBox): CSSInlineBox = + assert parent != nil + new(result) + result.x = parent.x + result.y = parent.y + parent.height -type - LayoutState = object - x: int - y: int - fromx: int - whitespace: bool - context: FormatContext + result.width = parent.width + result.context = parent.context + if result.context == nil: + result.context = newContext(parent) - FormatContext = enum - CONTEXT_BLOCK, CONTEXT_INLINE +proc processInlineBox(parent: CSSBox, str: string): CSSBox = + var ibox: CSSInlineBox + var use_parent = false + if parent of CSSInlineBox: + ibox = CSSInlineBox(parent) + use_parent = true + else: + ibox = newInlineBox(parent) -proc addChild(parent: var CSSBox, box: CSSBox) = + if str.len == 0: + return + + var i = 0 + var rowi = 0 + var fromx = ibox.context.fromx + var rowbox = CSSRowBox(x: fromx, y: ibox.y + rowi) + var r: Rune + while i < str.len: + fastRuneAt(str, i, r) + if rowbox.width + r.width() > ibox.width: + ibox.content.add(rowbox) + inc rowi + fromx = ibox.x + ibox.context.whitespace = true + ibox.context.conty = false + rowbox = CSSRowBox(x: ibox.x, y: ibox.y + rowi) + if r.isWhitespace(): + if ibox.context.whitespace: + continue + else: + ibox.context.whitespace = true + else: + ibox.context.whitespace = false + rowbox.width += r.width() + rowbox.runes.add(r) + if rowbox.runes.len > 0: + ibox.content.add(rowbox) + ibox.context.fromx = fromx + rowbox.width + ibox.context.conty = true + + ibox.height += rowi + if use_parent: + return nil + return ibox + +proc processElemBox(parent: CSSBox, elem: Element): CSSBox = + case elem.cssvalues[RULE_DISPLAY].display + of DISPLAY_BLOCK: + result = newBlockBox(parent) + result.context.cssvalues = elem.cssvalues + of DISPLAY_INLINE: + result = newInlineBox(parent) + result.context.cssvalues = elem.cssvalues + of DISPLAY_NONE: + return nil + else: + return nil + +proc add(parent: var CSSBox, box: CSSBox) = if box == nil: return + if box of CSSBlockBox: + parent.context.fromx = 0 + parent.context.whitespace = true + if box.context.conty: + inc box.height parent.height += box.height parent.children.add(box) +proc processPseudoBox(parent: CSSBox, cssvalues: CSSComputedValues): CSSBox = + case cssvalues[RULE_DISPLAY].display + of DISPLAY_BLOCK: + result = newBlockBox(parent) + result.context.cssvalues = cssvalues + result.add(processInlineBox(parent, $cssvalues[RULE_CONTENT].content)) + of DISPLAY_INLINE: + result = processInlineBox(parent, $cssvalues[RULE_CONTENT].content) + of DISPLAY_NONE: + return nil + else: + return nil + proc processNode(parent: CSSBox, node: Node): CSSBox = case node.nodeType of ELEMENT_NODE: let elem = Element(node) - var box: CSSBox - case elem.cssvalues[RULE_DISPLAY].display - of DISPLAY_BLOCK: - box = CSSBlockBox(x: parent.x, y: parent.y + parent.height, width: parent.width) - of DISPLAY_INLINE: - #TODO split this into its own thing - #TODO also rethink this bc it isn't very great - #TODO like, it doesn't work - var fromx = parent.x - if parent.children.len > 0 and parent.children[^1] of CSSInlineBox: - let sib = CSSInlineBox(parent.children[^1]) - if sib.content.len > 0: - fromx = sib.content[^1].x + sib.content[^1].width - else: - eprint "???" - elif parent of CSSInlineBox: - let sib = CSSInlineBox(parent) - if sib.content.len > 0: - fromx = sib.content[^1].x + sib.content[^1].width - else: - eprint "???" - box = CSSInlineBox(x: parent.x, y: parent.y + parent.height, width: parent.width) - CSSInlineBox(box).content.add(CSSRowBox(x: fromx, y: box.y)) - of DISPLAY_NONE: - return nil - else: - return nil - for child in elem.childNodes: - CSSBox(box).addChild(processNode(box, child)) - return box - of TEXT_NODE: - let text = Text(node) - #TODO not always anonymous - var ibox = CSSInlineBox(x: parent.x, y: parent.y + parent.height, width: parent.width) - var ws = true #TODO doesn't always start with newline - let str = text.data - - if text.data.len == 0: + result = processElemBox(parent, elem) + if result == nil: return - #TODO ok we'll have to rethink this methinks - var fromx = ibox.x - if parent.children.len > 0 and parent.children[^1] of CSSInlineBox: - let sib = CSSInlineBox(parent.children[^1]) - if sib.content.len > 0: - fromx = sib.content[^1].x + sib.content[^1].width - else: - eprint "???" - elif parent of CSSInlineBox: - let sib = CSSInlineBox(parent) - if sib.content.len > 0: - fromx = sib.content[^1].x + sib.content[^1].width - else: - eprint "???" + if elem.cssvalues_before.isSome: + let bbox = processPseudoBox(parent, elem.cssvalues_before.get) + if bbox != nil: + result.add(bbox) - var i = 0 - var w = 0 - var rowi = 0 - var rowbox = CSSRowBox(x: fromx, y: ibox.y + rowi) - var r: Rune - while i < text.data.len: - fastRuneAt(text.data, i, r) - if w + r.width() > ibox.width: - ibox.content.add(rowbox) - inc rowi - rowbox = CSSRowBox(x: ibox.x, y: ibox.y + rowi) - if r.isWhitespace(): - if ws: - continue - else: - ws = true - else: - ws = false - rowbox.width += r.width() - rowbox.runes.add(r) - if rowbox.runes.len > 0: - ibox.content.add(rowbox) - inc rowi + for child in elem.childNodes: + result.add(processNode(result, child)) - ibox.height += rowi - return ibox + if elem.cssvalues_after.isSome: + let abox = processPseudoBox(parent, elem.cssvalues_after.get) + if abox != nil: + result.add(abox) + of TEXT_NODE: + let text = Text(node) + return processInlineBox(parent, text.data) else: discard - return nil proc alignBoxes*(buffer: Buffer) = buffer.rootbox = CSSBlockBox(x: 0, y: 0, width: buffer.width, height: 0) + buffer.rootbox.context = newContext(buffer.rootbox) for child in buffer.document.root.childNodes: - buffer.rootbox.addChild(processNode(buffer.rootbox, child)) + buffer.rootbox.add(processNode(buffer.rootbox, child)) |