diff options
author | bptato <nincsnevem662@gmail.com> | 2021-11-10 18:26:18 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2021-11-10 18:32:25 +0100 |
commit | fcd3a5b204e15fdfc739fd04975977d288e892e0 (patch) | |
tree | 363f3bfd60570ce5fb1f4fbc6c62557609ccc6ea | |
parent | e6f7cc72ba3343fb81c4f8196446c58eca59191e (diff) | |
download | chawan-fcd3a5b204e15fdfc739fd04975977d288e892e0.tar.gz |
Layout engine improvements, use author style sheet
-rw-r--r-- | readme.md | 7 | ||||
-rw-r--r-- | res/default.css | 2 | ||||
-rw-r--r-- | src/css/parser.nim | 2 | ||||
-rw-r--r-- | src/css/style.nim | 20 | ||||
-rw-r--r-- | src/html/dom.nim | 128 | ||||
-rw-r--r-- | src/html/parser.nim | 14 | ||||
-rw-r--r-- | src/io/buffer.nim | 123 | ||||
-rw-r--r-- | src/layout/box.nim | 15 | ||||
-rw-r--r-- | src/layout/layout.nim | 77 | ||||
-rw-r--r-- | src/main.nim | 2 | ||||
-rw-r--r-- | src/types/enums.nim | 4 | ||||
-rw-r--r-- | src/utils/eprint.nim | 36 |
12 files changed, 279 insertions, 151 deletions
diff --git a/readme.md b/readme.md index 3136ea55..9f14f854 100644 --- a/readme.md +++ b/readme.md @@ -30,19 +30,20 @@ most important APIs. Plus some other things. Currently implemented features are: -* basic html rendering (very much WIP) +* basic html rendering with CSS (very much WIP) * fully functioning 2d pager with custom keybindings Planned features (roughly in order of importance): -* improved html rendering (i.e. actually functioning) +* refactored and improved layout engine (with colors, inline blocks etc) * anchor * html generator (for source view) -* loading author stylesheets (i.e. ones in web pages) +* load external resources (e.g. css) * markdown (with built-in parser) * form (w/ input etc) * JavaScript * table +* config editor * cookie * SOCKS proxy * HTTP proxy diff --git a/res/default.css b/res/default.css index 5bb5526c..c0a62f90 100644 --- a/res/default.css +++ b/res/default.css @@ -10,6 +10,8 @@ menu, noframes, body { pre { margin-top: 1em; + margin-bottom: 1em; + white-space: pre; } a, abbr, b, bdo, button, cite, code, del, dfn, em, font, i, img, ins, diff --git a/src/css/parser.nim b/src/css/parser.nim index 25a0e21d..08ffeb02 100644 --- a/src/css/parser.nim +++ b/src/css/parser.nim @@ -789,6 +789,8 @@ proc printc*(c: CSSComponentValue) = else: discard proc parseCSS*(inputStream: Stream): CSSStylesheet = + if inputStream.atEnd(): + return CSSStylesheet() return inputstream.parseStylesheet() proc debugparseCSS*(inputStream: Stream) = diff --git a/src/css/style.nim b/src/css/style.nim index 5f426e58..9db9813e 100644 --- a/src/css/style.nim +++ b/src/css/style.nim @@ -29,6 +29,8 @@ type display*: DisplayType of VALUE_CONTENT: content*: seq[Rune] + of VALUE_WHITESPACE: + whitespace*: WhitespaceType of VALUE_NONE: discard CSSComputedValues* = array[low(CSSRuleType)..high(CSSRuleType), CSSComputedValue] @@ -48,6 +50,7 @@ const ValueTypes = { RULE_FONT_STYLE: VALUE_FONT_STYLE, RULE_DISPLAY: VALUE_DISPLAY, RULE_CONTENT: VALUE_CONTENT, + RULE_WHITESPACE: VALUE_WHITESPACE, }.toTable() func getValueType*(rule: CSSRuleType): CSSValueType = @@ -237,6 +240,19 @@ func cssFontStyle(d: CSSDeclaration): CSSFontStyle = else: return FONTSTYLE_NORMAL return FONTSTYLE_NORMAL +func cssWhiteSpace(d: CSSDeclaration): WhitespaceType = + if isToken(d): + let tok = getToken(d) + if tok.tokenType == CSS_IDENT_TOKEN: + case $tok.value + of "normal": return WHITESPACE_NORMAL + of "nowrap": return WHITESPACE_NOWRAP + of "pre": return WHITESPACE_PRE + of "pre-line": return WHITESPACE_PRE_LINE + of "pre-wrap": return WHITESPACE_PRE_WRAP + else: return WHITESPACE_NORMAL + return WHITESPACE_NORMAL + func getSpecifiedValue*(d: CSSDeclaration): CSSSpecifiedValue = case $d.name of "color": @@ -257,6 +273,8 @@ func getSpecifiedValue*(d: CSSDeclaration): CSSSpecifiedValue = return CSSSpecifiedValue(t: RULE_DISPLAY, v: VALUE_DISPLAY, display: cssDisplay(d)) of "content": return CSSSpecifiedValue(t: RULE_CONTENT, v: VALUE_CONTENT, content: cssString(d)) + of "white-space": + return CSSSpecifiedValue(t: RULE_WHITESPACE, v: VALUE_WHITESPACE, whitespace: cssWhiteSpace(d)) func getInitialColor*(t: CSSRuleType): CSSColor = case t @@ -292,6 +310,8 @@ func getComputedValue*(rule: CSSSpecifiedValue, parent: CSSValues): CSSComputedV return CSSComputedValue(t: rule.t, v: VALUE_FONT_STYLE, fontstyle: rule.fontstyle) of VALUE_CONTENT: return CSSComputedValue(t: rule.t, v: VALUE_CONTENT, content: rule.content) + of VALUE_WHITESPACE: + return CSSComputedValue(t: rule.t, v: VALUE_WHITESPACE, whitespace: rule.whitespace) of VALUE_NONE: return CSSComputedValue(t: rule.t, v: VALUE_NONE) func getComputedValue*(d: CSSDeclaration, parent: CSSValues): CSSComputedValue = diff --git a/src/html/dom.nim b/src/html/dom.nim index 647b1f7c..92d8cf1b 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -268,6 +268,18 @@ func getAttrValue*(element: Element, s: string): string = return "" #TODO case sensitivity + + +type SelectResult = object + success: bool + pseudo: PseudoElem + +func selectres(s: bool, p: PseudoElem = PSEUDO_NONE): SelectResult = + return SelectResult(success: s, pseudo: p) + +func psuccess(s: SelectResult): bool = + return s.pseudo == PSEUDO_NONE and s.success + func attrSelectorMatches(elem: Element, sel: Selector): bool = case sel.rel of ' ': return sel.attr in elem.attributes @@ -287,36 +299,41 @@ func pseudoSelectorMatches(elem: Element, sel: Selector): bool = of "last-child": return elem.parentNode.lastElementChild == elem else: return false -func pseudoElemSelectorMatches(elem: Element, sel: Selector, pseudo: PseudoElem = PSEUDO_NONE): bool = +func pseudoElemSelectorMatches(elem: Element, sel: Selector, pseudo: PseudoElem = PSEUDO_NONE): SelectResult = case sel.elem - of "after": return pseudo == PSEUDO_AFTER - of "before": return pseudo == PSEUDO_BEFORE - else: return false + of "after": return selectres(true, PSEUDO_AFTER) + of "before": return selectres(true, PSEUDO_AFTER) + else: return selectres(false) -func selectorMatches(elem: Element, sel: Selector, pseudo: PseudoElem = PSEUDO_NONE): bool = +func selectorMatches(elem: Element, sel: Selector): SelectResult = case sel.t of TYPE_SELECTOR: - return elem.tagType == sel.tag + return selectres(elem.tagType == sel.tag) of CLASS_SELECTOR: - return sel.class in elem.classList + return selectres(sel.class in elem.classList) of ID_SELECTOR: - return sel.id == elem.id + return selectres(sel.id == elem.id) of ATTR_SELECTOR: - return elem.attrSelectorMatches(sel) + return selectres(elem.attrSelectorMatches(sel)) of PSEUDO_SELECTOR: - return pseudoSelectorMatches(elem, sel) + return selectres(pseudoSelectorMatches(elem, sel)) of PSELEM_SELECTOR: - return pseudoElemSelectorMatches(elem, sel, pseudo) + return pseudoElemSelectorMatches(elem, sel) of UNIVERSAL_SELECTOR: - return true + return selectres(true) of FUNC_SELECTOR: - return false + return selectres(false) -func selectorsMatch(elem: Element, selectors: SelectorList, pseudo: PseudoElem = PSEUDO_NONE): bool = +func selectorsMatch(elem: Element, selectors: SelectorList): SelectResult = for sel in selectors.sels: - if not selectorMatches(elem, sel, pseudo): - return false - return true + let res = selectorMatches(elem, sel) + if not res.success: + return selectres(false) + if res.pseudo != PSEUDO_NONE: + if result.pseudo != PSEUDO_NONE: + return selectres(false) + result.pseudo = res.pseudo + result.success = true func selectElems(document: Document, sel: Selector): seq[Element] = case sel.t @@ -337,9 +354,9 @@ func selectElems(document: Document, sel: Selector): seq[Element] = of FUNC_SELECTOR: case sel.name of "not": - return document.all_elements.filter((elem) => not selectorsMatch(elem, sel.selectors)) + return document.all_elements.filter((elem) => not selectorsMatch(elem, sel.selectors).psuccess) of "is", "where": - return document.all_elements.filter((elem) => selectorsMatch(elem, sel.selectors)) + return document.all_elements.filter((elem) => selectorsMatch(elem, sel.selectors).psuccess) return newSeq[Element]() func selectElems(document: Document, selectors: SelectorList): seq[Element] = @@ -352,12 +369,12 @@ func selectElems(document: Document, selectors: SelectorList): seq[Element] = if sellist[i].t == FUNC_SELECTOR: case sellist[i].name of "not": - result = result.filter((elem) => not selectorsMatch(elem, sellist[i].selectors)) + result = result.filter((elem) => not selectorsMatch(elem, sellist[i].selectors).psuccess) of "is", "where": - result = result.filter((elem) => selectorsMatch(elem, sellist[i].selectors)) + result = result.filter((elem) => selectorsMatch(elem, sellist[i].selectors).psuccess) else: discard else: - result = result.filter((elem) => selectorMatches(elem, sellist[i])) + result = result.filter((elem) => selectorMatches(elem, sellist[i]).psuccess) inc i proc querySelector*(document: Document, q: string): seq[Element] = @@ -389,17 +406,17 @@ proc applyProperty(elem: Element, decl: CSSDeclaration, pseudo: PseudoElem = PSE elem.cssvalues_after.get[cval.t] = cval type ParsedRule = tuple[sels: seq[SelectorList], oblock: CSSSimpleBlock] +type ParsedStylesheet = seq[ParsedRule] -func calcRules(elem: Element, rules: seq[ParsedRule]): +func calcRules(elem: Element, rules: ParsedStylesheet): array[low(PseudoElem)..high(PseudoElem), seq[CSSSimpleBlock]] = var tosorts: array[low(PseudoElem)..high(PseudoElem), seq[tuple[s:int,b:CSSSimpleBlock]]] for rule in rules: for sel in rule.sels: - #TODO: optimize, like rewrite selector match algorithm output or something - for pseudo in low(PseudoElem)..high(PseudoElem): - if elem.selectorsMatch(sel, pseudo): - let spec = getSpecificity(sel) - tosorts[pseudo].add((spec,rule.oblock)) + let match = elem.selectorsMatch(sel) + if match.success: + let spec = getSpecificity(sel) + tosorts[match.pseudo].add((spec,rule.oblock)) for i in low(PseudoElem)..high(PseudoElem): tosorts[i].sort((x, y) => cmp(x.s,y.s)) @@ -413,9 +430,6 @@ proc applyRules*(document: Document, rules: CSSStylesheet): seq[tuple[e:Element, let parsed = rules.value.map((x) => (sels: parseSelectors(x.prelude), oblock: x.oblock)) while stack.len > 0: let elem = stack.pop() - #TODO: optimize - #ok this whole idea was stupid, what I should've done is to just check for - #pseudo elem selectors, this is way too slow let rules_pseudo = calcRules(elem, parsed) for pseudo in low(PseudoElem)..high(PseudoElem): let rules = rules_pseudo[pseudo] @@ -429,11 +443,53 @@ proc applyRules*(document: Document, rules: CSSStylesheet): seq[tuple[e:Element, else: elem.applyProperty(decl, pseudo) - for child in elem.children: - stack.add(child) + for child in elem.children: + stack.add(child) + +proc applyAuthorRules*(document: Document): seq[tuple[e:Element,d:CSSDeclaration]] = + var stack: seq[Element] + var embedded_rules: seq[ParsedStylesheet] + + stack.add(document.root) + + #let parsed = rules.value.map((x) => (sels: parseSelectors(x.prelude), oblock: x.oblock)) + + while stack.len > 0: + let elem = stack.pop() + var rules_local = "" + for child in elem.children: + if child.tagType == TAG_STYLE: + for ct in child.childNodes: + if ct.nodeType == TEXT_NODE: + rules_local &= Text(ct).data + + if rules_local.len > 0: + let parsed = parseCSS(newStringStream(rules_local)).value.map((x) => (sels: parseSelectors(x.prelude), oblock: x.oblock)) + embedded_rules.add(parsed) + let this_rules = embedded_rules.concat() + let rules_pseudo = calcRules(elem, this_rules) + + for pseudo in low(PseudoElem)..high(PseudoElem): + let rules = rules_pseudo[pseudo] + for rule in rules: + let decls = parseCSSListOfDeclarations(rule.value) + for item in decls: + if item of CSSDeclaration: + let decl = CSSDeclaration(item) + if decl.important: + result.add((elem, decl)) + else: + elem.applyProperty(decl, pseudo) + + for child in elem.children: + stack.add(child) + + if rules_local.len > 0: + discard embedded_rules.pop() -proc applyDefaultStylesheet*(document: Document) = - let important = document.applyRules(stylesheet) - for rule in important: +proc applyStylesheets*(document: Document) = + let important_ua = document.applyRules(stylesheet) + let important_author = document.applyAuthorRules() + for rule in important_ua: rule.e.applyProperty(rule.d) diff --git a/src/html/parser.nim b/src/html/parser.nim index 536ba434..eed5baa7 100644 --- a/src/html/parser.nim +++ b/src/html/parser.nim @@ -202,12 +202,21 @@ proc processDocumentBody(state: var HTMLParseState) = state.elementNode = state.elementNode.ownerDocument.body proc processDocumentAddNode(state: var HTMLParseState, newNode: Node) = - if state.elementNode.nodeType == ELEMENT_NODE and state.elementNode.tagType == TAG_HTML: + if state.elementNode.tagType == TAG_HTML: if state.in_body: state.elementNode = state.elementNode.ownerDocument.body else: state.elementNode = state.elementNode.ownerDocument.head + #> If the next token is a U+000A LINE FEED (LF) character token, then ignore + #> that token and move on to the next one. (Newlines at the start of pre + #> blocks are ignored as an authoring convenience.) + elif state.elementNode.tagType == TAG_PRE: + if state.elementNode.childNodes.len == 1 and + state.elementNode.childNodes[0].nodeType == TEXT_NODE and + Text(state.elementNode.childNodes[0]).data == "\n": + discard state.elementNode.childNodes.pop() + insertNode(state.elementNode, newNode) proc processDocumentEndNode(state: var HTMLParseState) = @@ -220,7 +229,6 @@ proc processDocumentText(state: var HTMLParseState) = processDocumentBody(state) if state.textNode == nil: state.textNode = newText() - processDocumentAddNode(state, state.textNode) proc processDocumentStartElement(state: var HTMLParseState, element: Element, tag: DOMParsedTag) = @@ -426,7 +434,7 @@ proc parseHtml*(inputStream: Stream): Document = var buf = "" var lineBuf: string while not inputStream.atEnd(): - lineBuf = inputStream.readLine() + lineBuf = inputStream.readLine() & '\n' buf &= lineBuf var at = 0 diff --git a/src/io/buffer.nim b/src/io/buffer.nim index a3dde1b7..9775a267 100644 --- a/src/io/buffer.nim +++ b/src/io/buffer.nim @@ -1,7 +1,6 @@ import options import terminal import uri -import tables import strutils import unicode @@ -47,11 +46,6 @@ type xend*: int fromx*: int fromy*: int - nodes*: seq[Node] - links*: seq[Node] - elements*: seq[Element] - idelements*: Table[string, Element] - selectedlink*: Node attrs*: TermAttributes document*: Document displaycontrols*: bool @@ -182,6 +176,12 @@ func width(line: seq[FlexibleCell]): int = for c in line: result += c.rune.width() +func acursorx(buffer: Buffer): int = + return max(0, buffer.cursorx - buffer.fromx) + +func acursory(buffer: Buffer): int = + return buffer.cursory - buffer.fromy + func cellOrigin(buffer: Buffer, x: int, y: int): int = let row = y * buffer.width var ox = x @@ -190,7 +190,11 @@ func cellOrigin(buffer: Buffer, x: int, y: int): int = return ox func currentCellOrigin(buffer: Buffer): int = - return buffer.cellOrigin(buffer.cursorx - buffer.fromx, buffer.cursory - buffer.fromy) + return buffer.cellOrigin(buffer.acursorx, buffer.acursory) + +func currentRune(buffer: Buffer): Rune = + let row = (buffer.cursory - buffer.fromy) * buffer.width + return buffer.display[row + buffer.currentCellOrigin()].runes[0] func cellWidthOverlap*(buffer: Buffer, x: int, y: int): int = let ox = buffer.cellOrigin(x, y) @@ -213,9 +217,6 @@ func atPercentOf*(buffer: Buffer): int = if buffer.lines.len == 0: return 100 return (100 * (buffer.cursory + 1)) div buffer.numLines -func lastNode*(buffer: Buffer): Node = - return buffer.nodes[^1] - func cursorOnNode*(buffer: Buffer, node: Node): bool = if node.y == node.ey and node.y == buffer.cursory: return buffer.cursorx >= node.x and buffer.cursorx < node.ex @@ -230,14 +231,6 @@ func findSelectedElement*(buffer: Buffer): Option[HtmlElement] = func canScroll*(buffer: Buffer): bool = return buffer.numLines >= buffer.height -func getElementById*(buffer: Buffer, id: string): Element = - if buffer.idelements.hasKey(id): - return buffer.idelements[id] - return nil - -proc findSelectedNode*(buffer: Buffer): Option[Node] = - discard #TODO - proc addLine(buffer: Buffer) = buffer.lines.add(newSeq[FlexibleCell]()) @@ -245,21 +238,13 @@ proc clearText*(buffer: Buffer) = buffer.lines.setLen(0) buffer.addLine() -proc clearNodes*(buffer: Buffer) = - buffer.nodes.setLen(0) - buffer.links.setLen(0) - buffer.elements.setLen(0) - buffer.idelements.clear() - proc clearBuffer*(buffer: Buffer) = buffer.clearText() - buffer.clearNodes() buffer.cursorx = 0 buffer.cursory = 0 buffer.fromx = 0 buffer.fromy = 0 buffer.hovertext = "" - buffer.selectedlink = nil proc restoreCursorX(buffer: Buffer) = buffer.cursorx = max(min(buffer.currentLineWidth() - 1, buffer.xend), 0) @@ -308,18 +293,24 @@ proc cursorUp*(buffer: Buffer) = proc cursorRight*(buffer: Buffer) = let cellwidth = buffer.currentCellWidth() - let cellorigin = buffer.currentCellOrigin() + let cellorigin = buffer.fromx + buffer.currentCellOrigin() let lw = buffer.currentLineWidth() if buffer.cursorx < lw - 1: buffer.cursorx = min(lw - 1, cellorigin + cellwidth) + assert buffer.cursorx >= 0 buffer.xend = buffer.cursorx if buffer.cursorx - buffer.width >= buffer.fromx: inc buffer.fromx buffer.redraw = true + if buffer.cursorx == buffer.fromx: + inc buffer.cursorx proc cursorLeft*(buffer: Buffer) = - let cellorigin = buffer.currentCellOrigin() + eprint "??", buffer.currentCellOrigin(), buffer.fromx + let cellorigin = buffer.fromx + buffer.currentCellOrigin() + let lw = buffer.currentLineWidth() if buffer.fromx > buffer.cursorx: + buffer.cursorx = min(max(lw - 1, 0), cellorigin - 1) buffer.fromx = buffer.cursorx buffer.redraw = true elif buffer.cursorx > 0: @@ -338,58 +329,46 @@ proc cursorLineBegin*(buffer: Buffer) = buffer.redraw = true proc cursorLineEnd*(buffer: Buffer) = - buffer.cursorx = buffer.currentLineWidth() - 1 + buffer.cursorx = max(buffer.currentLineWidth() - 1, 0) buffer.xend = buffer.cursorx buffer.fromx = max(buffer.cursorx - buffer.width + 1, 0) buffer.redraw = buffer.fromx > 0 -iterator revnodes*(buffer: Buffer): Node {.inline.} = - var i = buffer.nodes.len - 1 - while i >= 0: - yield buffer.nodes[i] - dec i - proc cursorNextWord*(buffer: Buffer) = let llen = buffer.currentLineWidth() - 1 - var x = buffer.cursorx - var y = buffer.cursory if llen >= 0: - while not buffer.lines[y][x].rune.breaksWord(): - if x >= llen: + while not buffer.currentRune().breaksWord(): + if buffer.cursorx >= llen: break - inc x + buffer.cursorRight() - while buffer.lines[y][x].rune.breaksWord(): - if x >= llen: + while buffer.currentRune().breaksWord(): + if buffer.cursorx >= llen: break - inc x + buffer.cursorRight() - if x >= llen: - if y < buffer.numLines: - inc y - x = 0 - buffer.cursorTo(x, y) + if buffer.cursorx >= buffer.currentLineWidth() - 1: + if buffer.cursory < buffer.numLines - 1: + buffer.cursorDown() + buffer.cursorLineBegin() proc cursorPrevWord*(buffer: Buffer) = - var x = buffer.cursorx - var y = buffer.cursory if buffer.currentLineWidth() > 0: - while not buffer.lines[y][x].rune.breaksWord(): - if x == 0: + while not buffer.currentRune().breaksWord(): + if buffer.cursorx == 0: break - dec x + buffer.cursorLeft() - while buffer.lines[y][x].rune.breaksWord(): - if x == 0: + while buffer.currentRune().breaksWord(): + if buffer.cursorx == 0: break - dec x + buffer.cursorLeft() - if x == 0: - if y > 0: - dec y - x = buffer.lines[y].len - 1 - buffer.cursorTo(x, y) + if buffer.cursorx == 0: + if buffer.cursory > 0: + buffer.cursorUp() + buffer.cursorLineEnd() proc cursorNextLink*(buffer: Buffer) = #TODO @@ -425,7 +404,7 @@ proc cursorMiddle*(buffer: Buffer) = buffer.restoreCursorX() proc cursorBottom*(buffer: Buffer) = - buffer.cursory = min(buffer.fromy + buffer.height - 1, buffer.numLines) + buffer.cursory = min(buffer.fromy + buffer.height - 1, buffer.numLines - 1) buffer.restoreCursorX() proc centerLine*(buffer: Buffer) = @@ -505,10 +484,12 @@ proc scrollLeft*(buffer: Buffer) = buffer.redraw = true proc gotoAnchor*(buffer: Buffer): bool = - if buffer.location.anchor != "": - let node = buffer.getElementById(buffer.location.anchor) - if node != nil: - buffer.scrollTo(max(node.y - buffer.height div 2, 0)) + discard + #TODO + #if buffer.location.anchor != "": + # let node = buffer.getElementById(buffer.location.anchor) + # if node != nil: + # buffer.scrollTo(max(node.y - buffer.height div 2, 0)) proc setLocation*(buffer: Buffer, uri: Uri) = buffer.location = uri @@ -587,9 +568,11 @@ proc updateCursor(buffer: Buffer) = buffer.fromy = 0 buffer.cursory = buffer.lastVisibleLine - 1 + if buffer.cursorx >= buffer.currentLineWidth() - 1: + buffer.cursorLineEnd() + if buffer.lines.len == 0: buffer.cursory = 0 - return proc clearDisplay*(buffer: Buffer) = var i = 0 @@ -602,7 +585,8 @@ proc refreshDisplay*(buffer: Buffer) = buffer.prevdisplay = buffer.display buffer.clearDisplay() - for line in buffer.lines[buffer.fromy..buffer.lastVisibleLine - 1]: + for line in buffer.lines[buffer.fromy.. + buffer.lastVisibleLine - 1]: var w = 0 var i = 0 while w < buffer.fromx and i < line.len: @@ -641,6 +625,7 @@ proc renderPlainText*(buffer: Buffer, text: string) = inc i if line.len > 0: buffer.setText(0, y, line.toRunes()) + buffer.updateCursor() proc renderDocument*(buffer: Buffer) = buffer.clearText() @@ -653,7 +638,6 @@ proc renderDocument*(buffer: Buffer) = let box = stack.pop() if box of CSSInlineBox: let inline = CSSInlineBox(box) - var i = 0 eprint "NEW BOX", inline.context.conty for line in inline.content: eprint line @@ -665,6 +649,7 @@ proc renderDocument*(buffer: Buffer) = while i >= 0: stack.add(box.children[i]) dec i + buffer.updateCursor() proc cursorBufferPos(buffer: Buffer) = let x = max(buffer.cursorx - buffer.fromx, 0) diff --git a/src/layout/box.nim b/src/layout/box.nim index bacaa1b1..f36823ae 100644 --- a/src/layout/box.nim +++ b/src/layout/box.nim @@ -18,19 +18,25 @@ type y*: int width*: int height*: int - bh*: int - bw*: int children*: seq[CSSBox] - context*: FormatContext + context*: InlineContext + bcontext*: BlockContext - FormatContext* = ref object + InlineContext* = ref object context*: FormatContextType fromx*: int fromy*: int + marginx*: int + marginy*: int conty*: bool whitespace*: bool cssvalues*: CSSComputedValues + BlockContext* = ref object + context*: FormatContextType + marginx*: int + marginy*: int + CSSRowBox* = object x*: int y*: int @@ -44,6 +50,7 @@ type CSSBlockBox* = ref CSSBlockBoxObj CSSBlockBoxObj = object of CSSBox + tag*: string func `+`(a: CSSRect, b: CSSRect): CSSRect = result.x1 = a.x1 + b.x1 diff --git a/src/layout/layout.nim b/src/layout/layout.nim index fe96b416..4f73f53c 100644 --- a/src/layout/layout.nim +++ b/src/layout/layout.nim @@ -9,33 +9,53 @@ import io/buffer import io/cell import utils/twtstr -func newContext*(box: CSSBox): FormatContext = +func newContext*(box: CSSBox): InlineContext = new(result) result.fromx = box.x result.whitespace = true -func newBlockBox*(parent: CSSBox): CSSBlockBox = +func newBlockBox*(parent: CSSBox, vals: CSSComputedValues): CSSBlockBox = new(result) + result.bcontext = parent.bcontext #TODO make this something like state or something result.x = parent.x if parent.context.conty: inc parent.height + eprint "inc n" + inc parent.context.fromy parent.context.conty = false - result.y = parent.y + parent.height + result.y = parent.context.fromy + let mtop = vals[RULE_MARGIN_TOP].length.cells() + if mtop > parent.bcontext.marginy: + result.y += mtop - parent.bcontext.marginy + eprint "my", mtop, parent.bcontext.marginy + parent.bcontext.marginy = mtop result.width = parent.width result.context = newContext(parent) + eprint "inc to", result.y + result.context.fromy = result.y + result.context.cssvalues = vals func newInlineBox*(parent: CSSBox): CSSInlineBox = assert parent != nil new(result) result.x = parent.x - result.y = parent.y + parent.height + result.y = parent.context.fromy result.width = parent.width result.context = parent.context + result.bcontext = parent.bcontext if result.context == nil: result.context = newContext(parent) +proc inlineWrap(ibox: var CSSInlineBox, rowi: var int, fromx: var int, rowbox: var CSSRowBox) = + ibox.content.add(rowbox) + inc rowi + fromx = ibox.x + ibox.context.whitespace = true + ibox.context.conty = true + rowbox = CSSRowBox(x: ibox.x, y: ibox.y + rowi) + proc processInlineBox(parent: CSSBox, str: string): CSSBox = var ibox: CSSInlineBox var use_parent = false @@ -51,22 +71,32 @@ proc processInlineBox(parent: CSSBox, str: string): CSSBox = var i = 0 var rowi = 0 var fromx = ibox.context.fromx - var rowbox = CSSRowBox(x: fromx, y: ibox.y + rowi) + var rowbox = CSSRowBox(x: fromx, y: ibox.context.fromy) 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) + inlineWrap(ibox, rowi, fromx, rowbox) if r.isWhitespace(): if ibox.context.whitespace: continue else: - ibox.context.whitespace = true + let wsr = ibox.context.cssvalues[RULE_WHITESPACE].whitespace + + case wsr + of WHITESPACE_NORMAL, WHITESPACE_NOWRAP: + r = Rune(' ') + of WHITESPACE_PRE, WHITESPACE_PRE_LINE, WHITESPACE_PRE_WRAP: + if r == Rune('\n'): + inlineWrap(ibox, rowi, fromx, rowbox) + ibox.context.whitespace = false + continue + + case wsr + of WHITESPACE_NORMAL, WHITESPACE_NOWRAP, WHITESPACE_PRE_LINE: + ibox.context.whitespace = true + else: + ibox.context.whitespace = false else: ibox.context.whitespace = false rowbox.width += r.width() @@ -77,6 +107,10 @@ proc processInlineBox(parent: CSSBox, str: string): CSSBox = ibox.context.conty = true ibox.height += rowi + eprint "inc i", rowi, rowbox.runes + if rowi > 0 or rowbox.width > 0: + parent.bcontext.marginy = 0 + ibox.context.fromy += rowi if use_parent: return nil return ibox @@ -84,8 +118,9 @@ proc processInlineBox(parent: CSSBox, str: string): CSSBox = proc processElemBox(parent: CSSBox, elem: Element): CSSBox = case elem.cssvalues[RULE_DISPLAY].display of DISPLAY_BLOCK: - result = newBlockBox(parent) - result.context.cssvalues = elem.cssvalues + eprint "START", elem.tagType + result = newBlockBox(parent, elem.cssvalues) + CSSBlockBox(result).tag = $elem.tagType of DISPLAY_INLINE: result = newInlineBox(parent) result.context.cssvalues = elem.cssvalues @@ -102,14 +137,23 @@ proc add(parent: var CSSBox, box: CSSBox) = parent.context.whitespace = true if box.context.conty: inc box.height + eprint "inc a" + inc box.context.fromy + box.context.conty = false + let mbot = box.context.cssvalues[RULE_MARGIN_BOTTOM].length.cells() + eprint "inc b", mbot + box.context.fromy += mbot + box.bcontext.marginy = mbot + eprint "END", CSSBlockBox(box).tag parent.height += box.height + eprint "parent to", box.context.fromy + parent.context.fromy = box.context.fromy 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 = newBlockBox(parent, cssvalues) result.add(processInlineBox(parent, $cssvalues[RULE_CONTENT].content)) of DISPLAY_INLINE: result = processInlineBox(parent, $cssvalues[RULE_CONTENT].content) @@ -147,5 +191,6 @@ proc processNode(parent: CSSBox, node: Node): CSSBox = proc alignBoxes*(buffer: Buffer) = buffer.rootbox = CSSBlockBox(x: 0, y: 0, width: buffer.width, height: 0) buffer.rootbox.context = newContext(buffer.rootbox) + buffer.rootbox.bcontext = new(BlockContext) for child in buffer.document.root.childNodes: buffer.rootbox.add(processNode(buffer.rootbox, child)) diff --git a/src/main.nim b/src/main.nim index 9661ba10..4515147f 100644 --- a/src/main.nim +++ b/src/main.nim @@ -45,7 +45,7 @@ proc main*() = buffer.source = getPageUri(uri).readAll() #TODO get rid of this buffer.document = parseHtml(newStringStream(buffer.source)) buffer.setLocation(uri) - buffer.document.applyDefaultStylesheet() + buffer.document.applyStylesheets() buffer.alignBoxes() buffer.renderDocument() var lastUri = uri diff --git a/src/types/enums.nim b/src/types/enums.nim index 3d79091b..914a4dc2 100644 --- a/src/types/enums.nim +++ b/src/types/enums.nim @@ -73,13 +73,13 @@ type CSSRuleType* = enum RULE_ALL, RULE_COLOR, RULE_MARGIN, RULE_MARGIN_TOP, RULE_MARGIN_LEFT, RULE_MARGIN_RIGHT, RULE_MARGIN_BOTTOM, RULE_FONT_STYLE, RULE_DISPLAY, - RULE_CONTENT + RULE_CONTENT, RULE_WHITESPACE CSSGlobalValueType* = enum VALUE_INITIAL, VALUE_INHERIT, VALUE_REVERT, VALUE_UNSET CSSValueType* = enum - VALUE_NONE, VALUE_LENGTH, VALUE_COLOR, VALUE_CONTENT, VALUE_DISPLAY, VALUE_FONT_STYLE + VALUE_NONE, VALUE_LENGTH, VALUE_COLOR, VALUE_CONTENT, VALUE_DISPLAY, VALUE_FONT_STYLE, VALUE_WHITESPACE DrawInstructionType* = enum DRAW_TEXT, DRAW_GOTO, DRAW_FGCOLOR, DRAW_BGCOLOR, DRAW_STYLE, DRAW_RESET diff --git a/src/utils/eprint.nim b/src/utils/eprint.nim index d13fcf91..eba5f51f 100644 --- a/src/utils/eprint.nim +++ b/src/utils/eprint.nim @@ -1,25 +1,27 @@ {.used.} template eprint*(s: varargs[string, `$`]) = {.cast(noSideEffect).}: - var a = false - for x in s: - if not a: - a = true - else: - stderr.write(' ') - stderr.write(x) - stderr.write('\n') + if not defined(release): + var a = false + for x in s: + if not a: + a = true + else: + stderr.write(' ') + stderr.write(x) + stderr.write('\n') template eecho*(s: varargs[string, `$`]) = {.cast(noSideEffect).}: - var a = false - var o = "" - for x in s: - if not a: - a = true - else: - o &= ' ' - o &= x - echo o + if not defined(release): + var a = false + var o = "" + for x in s: + if not a: + a = true + else: + o &= ' ' + o &= x + echo o template print*(s: varargs[string, `$`]) = for x in s: |