diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/buffer/buffer.nim | 70 | ||||
-rw-r--r-- | src/config/config.nim | 12 | ||||
-rw-r--r-- | src/css/cascade.nim | 3 | ||||
-rw-r--r-- | src/css/values.nim | 12 | ||||
-rw-r--r-- | src/display/pager.nim | 3 | ||||
-rw-r--r-- | src/html/dom.nim | 34 | ||||
-rw-r--r-- | src/img/png.nim | 6 | ||||
-rw-r--r-- | src/layout/box.nim | 13 | ||||
-rw-r--r-- | src/layout/engine.nim | 168 | ||||
-rw-r--r-- | src/render/renderdocument.nim | 26 | ||||
-rw-r--r-- | src/types/mime.nim | 1 |
11 files changed, 212 insertions, 136 deletions
diff --git a/src/buffer/buffer.nim b/src/buffer/buffer.nim index acfbad19..f587c8ee 100644 --- a/src/buffer/buffer.nim +++ b/src/buffer/buffer.nim @@ -12,29 +12,30 @@ when defined(posix): import posix import buffer/cell +import config/config import css/cascade import css/cssparser import css/mediaquery import css/sheet import css/stylednode import css/values -import config/config import data/charset import html/dom import html/env import html/htmlparser import html/tags +import img/png import io/loader -import io/request import io/posixstream import io/promise +import io/request import io/teestream +import io/window import ips/serialize import ips/serversocket import ips/socketstream import js/regex import js/timeout -import io/window import layout/box import render/renderdocument import render/rendertext @@ -491,9 +492,10 @@ proc do_reshape(buffer: Buffer) = of "text/html": if buffer.viewport == nil: buffer.viewport = Viewport(window: buffer.attrs) - let ret = renderDocument(buffer.document, buffer.attrs, buffer.userstyle, buffer.viewport, buffer.prevstyled) - buffer.lines = ret[0] - buffer.prevstyled = ret[1] + let ret = renderDocument(buffer.document, buffer.userstyle, + buffer.viewport, buffer.prevstyled) + buffer.lines = ret.grid + buffer.prevstyled = ret.styledRoot else: buffer.lines.renderStream(buffer.srenderer, buffer.available) buffer.available = 0 @@ -545,30 +547,50 @@ proc updateHover*(buffer: Buffer, cursorx, cursory: int): UpdateHoverResult {.pr buffer.prevnode = thisnode -proc loadResource(buffer: Buffer, document: Document, elem: HTMLLinkElement): EmptyPromise = +proc loadResource(buffer: Buffer, elem: HTMLLinkElement): EmptyPromise = + let document = buffer.document let href = elem.attr("href") if href == "": return let url = parseURL(href, document.url.some) if url.isSome: let url = url.get - if url.scheme == buffer.url.scheme: - let media = elem.media - if media != "": - let media = parseMediaQueryList(parseListOfComponentValues(newStringStream(media))) - if not media.applies(document.window): return - return buffer.loader.fetch(newRequest(url)).then(proc(res: Response) = - if res.contenttype == "text/css": - elem.sheet = parseStylesheet(res.body)) - -proc loadResources(buffer: Buffer, document: Document): EmptyPromise = + let media = elem.media + if media != "": + let media = parseMediaQueryList(parseListOfComponentValues(newStringStream(media))) + if not media.applies(document.window): return + return buffer.loader.fetch(newRequest(url)).then(proc(res: Response) = + if res.contenttype == "text/css": + elem.sheet = parseStylesheet(res.body)) + +proc loadResource(buffer: Buffer, elem: HTMLImageElement): EmptyPromise = + let document = buffer.document + let src = elem.attr("src") + if src == "": return + let url = parseURL(src, document.url.some) + if url.isSome: + let url = url.get + return buffer.loader.fetch(newRequest(url)).then(proc(res: Response) = + if res.contenttype == "image/png": + let pngData = res.body.readAll() + elem.bitmap = fromPNG(toOpenArrayByte(pngData, 0, pngData.high))) + +proc loadResources(buffer: Buffer): EmptyPromise = + let document = buffer.document var promises: seq[EmptyPromise] if document.html != nil: - for elem in document.html.elements(TAG_LINK): - let elem = HTMLLinkElement(elem) - if elem.rel == "stylesheet": - let p = buffer.loadResource(document, elem) - if p != nil: - promises.add(p) + for elem in document.html.elements({TAG_LINK, TAG_IMG}): + var p: EmptyPromise = nil + case elem.tagType + of TAG_LINK: + let elem = HTMLLinkElement(elem) + if elem.rel == "stylesheet": + p = buffer.loadResource(elem) + of TAG_IMG: + let elem = HTMLImageElement(elem) + p = buffer.loadResource(elem) + else: discard + if p != nil: + promises.add(p) return all(promises) type ConnectResult* = object @@ -655,7 +677,7 @@ proc finishLoad(buffer: Buffer): EmptyPromise = window = buffer.window, url = buffer.url) buffer.document = doc buffer.state = LOADING_RESOURCES - p = buffer.loadResources(buffer.document) + p = buffer.loadResources() else: p = EmptyPromise() p.resolve() diff --git a/src/config/config.nim b/src/config/config.nim index d242c37d..2b81460c 100644 --- a/src/config/config.nim +++ b/src/config/config.nim @@ -35,6 +35,7 @@ type referer_from*: Option[bool] scripting: Option[bool] document_charset: seq[Charset] + images: Option[bool] StaticOmniRule = object match: string @@ -50,6 +51,7 @@ type referer_from*: Option[bool] scripting*: Option[bool] document_charset*: seq[Charset] + images*: Option[bool] OmniRule* = object match*: Regex @@ -115,6 +117,7 @@ type referrerpolicy*: ReferrerPolicy scripting*: bool charsets*: seq[Charset] + images*: bool ForkServerConfig* = object tmpdir*: string @@ -136,7 +139,8 @@ func getForkServerConfig*(config: Config): ForkServerConfig = proc getBufferConfig*(config: Config, location: URL, cookiejar: CookieJar = nil, headers: Headers = nil, referer_from = false, scripting = false, - charsets = config.encoding.document_charset): BufferConfig = + charsets = config.encoding.document_charset, + images = false): BufferConfig = result = BufferConfig( userstyle: config.css.stylesheet, filter: newURLFilter(scheme = some(location.scheme), default = true), @@ -144,7 +148,8 @@ proc getBufferConfig*(config: Config, location: URL, cookiejar: CookieJar = nil, headers: headers, referer_from: referer_from, scripting: scripting, - charsets: charsets + charsets: charsets, + images: images ) new(result.headers) result.headers[] = DefaultHeaders @@ -156,7 +161,8 @@ proc getSiteConfig*(config: Config, jsctx: JSContext): seq[SiteConfig] = scripting: sc.scripting, share_cookie_jar: sc.share_cookie_jar, referer_from: sc.referer_from, - document_charset: sc.document_charset + document_charset: sc.document_charset, + images: sc.images ) if sc.url.isSome: conf.url = compileRegex(sc.url.get, 0) diff --git a/src/css/cascade.nim b/src/css/cascade.nim index 397dda90..c4bdc0c0 100644 --- a/src/css/cascade.nim +++ b/src/css/cascade.nim @@ -314,7 +314,8 @@ proc applyRules(document: Document, ua, user: CSSStylesheet, cachedTree: StyledN styledText.pseudo = pseudo styledParent.children.add(styledText) of PSEUDO_IMAGE: - let content = CSSContent(t: CONTENT_IMAGE, s: "[img]") + let src = Element(styledParent.node).attr("src") + let content = CSSContent(t: CONTENT_IMAGE, s: src) let styledText = styledParent.newStyledReplacement(content) styledText.pseudo = pseudo styledParent.children.add(styledText) diff --git a/src/css/values.nim b/src/css/values.nim index 99d1bc76..dd479509 100644 --- a/src/css/values.nim +++ b/src/css/values.nim @@ -6,6 +6,7 @@ import unicode import css/cssparser import css/selectorparser +import img/bitmap import io/window import types/color import utils/twtstr @@ -137,6 +138,7 @@ type CSSContent* = object t*: CSSContentType s*: string + bmp*: Bitmap CSSQuotes* = object auto*: bool @@ -519,9 +521,10 @@ func cssColor*(val: CSSComponentValue): RGBAColor = let f = CSSFunction(val) var i = 0 var commaMode = false - template check_err = + template check_err(slash: bool) = #TODO calc, percentages, etc (cssnumber function or something) - if i >= f.value.len or f.value[i] != CSS_NUMBER_TOKEN: + if not slash and i >= f.value.len or i < f.value.len and + f.value[i] != CSS_NUMBER_TOKEN: raise newException(CSSValueError, "Invalid color") template next_value(first = false, slash = false) = inc i @@ -539,12 +542,11 @@ func cssColor*(val: CSSComponentValue): RGBAColor = raise newException(CSSValueError, "Invalid color") inc i f.value.skipWhitespace(i) - if not slash: - check_err + check_err slash case f.name of "rgb", "rgba": f.value.skipWhitespace(i) - check_err + check_err false let r = CSSToken(f.value[i]).nvalue next_value true let g = CSSToken(f.value[i]).nvalue diff --git a/src/display/pager.nim b/src/display/pager.nim index adfe2f87..bed2b4b5 100644 --- a/src/display/pager.nim +++ b/src/display/pager.nim @@ -557,6 +557,7 @@ proc applySiteconf(pager: Pager, request: Request): BufferConfig = var cookiejar: CookieJar var headers: Headers var scripting: bool + var images: bool var charsets = pager.config.encoding.document_charset for sc in pager.siteconf: if sc.url.isSome and not sc.url.get.match(url): @@ -583,6 +584,8 @@ proc applySiteconf(pager: Pager, request: Request): BufferConfig = referer_from = sc.referer_from.get if sc.document_charset.len > 0: charsets = sc.document_charset + if sc.images.isSome: + images = sc.images.get return pager.config.getBufferConfig(request.url, cookiejar, headers, referer_from, scripting, charsets) diff --git a/src/html/dom.nim b/src/html/dom.nim index b022d858..3d901eb9 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -359,6 +359,9 @@ type alphabeticBaseline {.jsget.}: float64 ideographicBaseline {.jsget.}: float64 + HTMLImageElement* = ref object of HTMLElement + bitmap*: Bitmap + proc parseColor(element: Element, s: string): RGBAColor proc resetTransform(state: var DrawingState) = @@ -671,7 +674,8 @@ func makes(name: string, ts: set[TagType]): ReflectEntry = tags: ts ) -func makes(attrname: string, funcname: string, ts: set[TagType]): ReflectEntry = +func makes(attrname: string, funcname: string, ts: set[TagType]): + ReflectEntry = ReflectEntry( attrname: attrname, funcname: funcname, @@ -685,14 +689,18 @@ func makes(name: string, ts: varargs[TagType]): ReflectEntry = func makes(attrname, funcname: string, ts: varargs[TagType]): ReflectEntry = makes(attrname, funcname, toset(ts)) -template makeb(name: string, ts: varargs[TagType]): ReflectEntry = +func makeb(attrname, funcname: string, ts: varargs[TagType]): + ReflectEntry = ReflectEntry( - attrname: name, - funcname: name, + attrname: attrname, + funcname: funcname, t: REFLECT_BOOL, tags: toset(ts) ) +func makeb(name: string, ts: varargs[TagType]): ReflectEntry = + makeb(name, name, ts) + template makeul(name: string, ts: varargs[TagType], default = 0u32): ReflectEntry = ReflectEntry( attrname: name, @@ -729,6 +737,13 @@ const ReflectTable0 = [ makeulgz("size", TAG_INPUT, 20u32), makeul("width", TAG_CANVAS, 300u32), makeul("height", TAG_CANVAS, 150u32), + makes("alt", TAG_IMG), + makes("src", TAG_IMG, TAG_SCRIPT), + makes("srcset", TAG_IMG), + makes("sizes", TAG_IMG), + #TODO can we add crossOrigin here? + makes("usemap", "useMap", TAG_IMG), + makeb("ismap", "isMap", TAG_IMG), # "super-global" attributes makes("slot", AllTagTypes), makes("class", "className", AllTagTypes) @@ -1429,7 +1444,7 @@ func innerHTML*(element: Element): string {.jsfget.} = func outerHTML*(element: Element): string {.jsfget.} = return $element -func crossorigin(element: HTMLScriptElement): CORSAttribute = +func crossOrigin0(element: HTMLElement): CORSAttribute = if not element.attrb("crossorigin"): return NO_CORS case element.attr("crossorigin") @@ -1439,6 +1454,12 @@ func crossorigin(element: HTMLScriptElement): CORSAttribute = return USE_CREDENTIALS return ANONYMOUS +func crossOrigin(element: HTMLScriptElement): CORSAttribute {.jsfget.} = + return element.crossOrigin0 + +func crossOrigin(element: HTMLImageElement): CORSAttribute {.jsfget.} = + return element.crossOrigin0 + func referrerpolicy(element: HTMLScriptElement): Option[ReferrerPolicy] = getReferrerPolicy(element.attr("referrerpolicy")) @@ -1739,6 +1760,8 @@ func newHTMLElement*(document: Document, tagType: TagType, result = new(HTMLLabelElement) of TAG_CANVAS: result = new(HTMLCanvasElement) + of TAG_IMG: + result = new(HTMLImageElement) else: result = new(HTMLElement) result.nodeType = ELEMENT_NODE @@ -2778,6 +2801,7 @@ proc registerElements(ctx: JSContext, nodeCID: JSClassID) = register(HTMLTextAreaElement, TAG_TEXTAREA) register(HTMLLabelElement, TAG_LABEL) register(HTMLCanvasElement, TAG_CANVAS) + register(HTMLImageElement, TAG_IMG) proc addDOMModule*(ctx: JSContext) = let eventTargetCID = ctx.registerType(EventTarget) diff --git a/src/img/png.nim b/src/img/png.nim index 50b0e980..3de395f2 100644 --- a/src/img/png.nim +++ b/src/img/png.nim @@ -138,6 +138,7 @@ func scanlen(reader: PNGReader): int {.inline.} = return (w * reader.spp * int(reader.bitDepth) + 7) div 8 proc handleError(reader: var PNGReader, msg: string) = + eprint msg reader.bmp = nil if reader.hasstrm: discard inflateEnd(addr reader.strm) @@ -281,7 +282,6 @@ proc unfilter(reader: var PNGReader, irow: openArray[uint8], bpp: int) = of 4u8: # paeth reader.err "paeth not implemented yet" else: - eprint fil reader.err "got invalid filter" proc writepxs(reader: var PNGReader, crow: var openArray[RGBAColor]) = @@ -414,8 +414,8 @@ proc zlibFree(opaque: pointer, address: pointer) {.cdecl.} = proc initZStream(reader: var PNGReader) = let bps = max(int(reader.bitDepth) div 8, 1) - reader.idatBuf = newSeq[uint8](reader.scanlen * reader.height * bps) - reader.uprow = newSeq[uint8](reader.width * bps) + reader.idatBuf = newSeq[uint8](reader.scanlen * reader.height) + reader.uprow = newSeq[uint8](reader.scanlen) reader.strm = z_stream( zalloc: zlibAlloc, zfree: zlibFree diff --git a/src/layout/box.nim b/src/layout/box.nim index 5acc7ef6..1167ca7b 100644 --- a/src/layout/box.nim +++ b/src/layout/box.nim @@ -23,7 +23,6 @@ type Viewport* = ref object window*: WindowAttributes - root*: seq[BlockBox] positioned*: seq[BlockBox] BoxBuilder* = ref object of RootObj @@ -100,7 +99,7 @@ type width*: int contentWidth*: int contentHeight*: Option[int] - maxContentWidth*: int + contentWidthInfinite*: bool charwidth*: int whitespacenum*: int @@ -139,14 +138,8 @@ type contentWidth*: int contentHeight*: Option[int] shrink*: bool - # The sole purpose of maxContentWidth is to stretch children of table - # cells to infinity in its maximum width calculation pass. - # For blocks with a specified width, maxContentWidth does nothing. - # This should never be used for anything other than setting width to - # min(maxContentWidth, width)! Failure to do so will almost certainly - # result in overflow errors (because maxContentWidth may be set to - # high(int).) - maxContentWidth*: int + # Whether to stretch content to infinity. + contentWidthInfinite*: bool positioned*: bool x_positioned*: bool diff --git a/src/layout/engine.nim b/src/layout/engine.nim index b5da155e..c3e6c3e0 100644 --- a/src/layout/engine.nim +++ b/src/layout/engine.nim @@ -255,8 +255,8 @@ proc addAtom(ictx: InlineContext, atom: InlineAtom, pcomputed: CSSComputedValues var shift = ictx.computeShift(pcomputed) ictx.whitespacenum = 0 # Line wrapping - if not pcomputed.nowrap: - if ictx.currentLine.width + atom.width + shift > ictx.maxContentWidth: + if not pcomputed.nowrap and not ictx.contentWidthInfinite: + if ictx.currentLine.width + atom.width + shift > ictx.contentWidth: ictx.finishLine(pcomputed, false) # Recompute on newline shift = ictx.computeShift(pcomputed) @@ -307,26 +307,30 @@ proc flushLine(ictx: InlineContext, computed: CSSComputedValues) = proc checkWrap(state: var InlineState, r: Rune) = if state.computed{"white-space"} in {WHITESPACE_NOWRAP, WHITESPACE_PRE}: return + if state.ictx.contentWidthInfinite: + return let shift = state.ictx.computeShift(state.computed) let rw = r.width() + let currWidth = state.ictx.currentLine.width + state.word.width + shift + + rw * state.ictx.cellwidth case state.computed{"word-break"} of WORD_BREAK_NORMAL: if rw == 2 or state.wrappos != -1: # break on cjk and wrap opportunities - if state.ictx.currentLine.width + state.word.width + shift + rw * state.ictx.cellwidth > state.ictx.maxContentWidth: + if currWidth > state.ictx.contentWidth: let l = state.ictx.currentLine state.addWordEOL() if l == state.ictx.currentLine: # no line wrapping occured in addAtom state.ictx.finishLine(state.computed) state.ictx.whitespacenum = 0 of WORD_BREAK_BREAK_ALL: - if state.ictx.currentLine.width + state.word.width + shift + rw * state.ictx.cellwidth > state.ictx.maxContentWidth: + if currWidth > state.ictx.contentWidth: let l = state.ictx.currentLine state.addWordEOL() if l == state.ictx.currentLine: # no line wrapping occured in addAtom state.ictx.finishLine(state.computed) state.ictx.whitespacenum = 0 of WORD_BREAK_KEEP_ALL: - if state.ictx.currentLine.width + state.word.width + shift + rw * state.ictx.cellwidth > state.ictx.maxContentWidth: + if currWidth > state.ictx.contentWidth: state.ictx.finishLine(state.computed) state.ictx.whitespacenum = 0 @@ -410,8 +414,10 @@ proc resolveContentWidth(box: BlockBox, widthpx, availableWidth: int, isauto = f box.margin_right = underflow div 2 # Resolve percentage-based dimensions. -# availableWidth: width of the containing box. availableHeight: ditto, but with height. -proc resolveDimensions(box: BlockBox, availableWidth: int, availableHeight: Option[int], maxContentWidth: Option[int]) = +# availableWidth: width of the containing box +# availableHeight: ditto, but with height. +proc resolveDimensions(box: BlockBox, availableWidth: int, + availableHeight: Option[int]) = let viewport = box.viewport let computed = box.computed @@ -464,10 +470,8 @@ proc resolveDimensions(box: BlockBox, availableWidth: int, availableHeight: Opti if min_height.isSome and box.contentHeight.isSome and min_height.get > box.contentHeight.get: box.contentHeight = min_height - # if no max content width is supplied, just use regular content width. - box.maxContentWidth = maxContentWidth.get(box.contentWidth) -proc resolveTableCellDimensions(box: BlockBox, availableWidth: int, availableHeight: Option[int], maxContentWidth: Option[int]) = +proc resolveTableCellDimensions(box: BlockBox, availableWidth: int, availableHeight: Option[int]) = let viewport = box.viewport let computed = box.computed @@ -490,9 +494,6 @@ proc resolveTableCellDimensions(box: BlockBox, availableWidth: int, availableHei let pheight = computed{"height"} if not pheight.auto and pheight.unit != UNIT_PERC: box.contentHeight = some(pheight.px(viewport)) - # if no max content width is supplied, just use regular content width. - box.maxContentWidth = maxContentWidth.get(box.contentWidth) - # Whether a width was specified on this block box. func isWidthSpecified(box: BlockBox): bool = @@ -535,50 +536,72 @@ func isShrink(box: BlockBox, parent: BlockBox = nil, override = false): bool = return parent.shrink and not box.isWidthSpecified() else: discard -proc newTableCellBox(viewport: Viewport, builder: BoxBuilder, parentWidth: int, parentHeight = none(int), shrink = true, maxContentWidth = none(int)): BlockBox {.inline.} = +proc newTableCellBox(viewport: Viewport, builder: BoxBuilder, + parentWidth: int, parentHeight = none(int), shrink = true, + contentWidthInfinite = false): BlockBox = let box = BlockBox( viewport: viewport, computed: builder.computed, node: builder.node ) box.shrink = box.isShrink(nil, shrink) - box.resolveTableCellDimensions(parentWidth, parentHeight, maxContentWidth) + box.contentWidthInfinite = contentWidthInfinite + box.resolveTableCellDimensions(parentWidth, parentHeight) return box -proc newFlowRootBox(viewport: Viewport, builder: BoxBuilder, parentWidth: int, parentHeight = none(int), shrink = true, maxContentWidth = none(int)): BlockBox {.inline.} = - new(result) - result.viewport = viewport - result.computed = builder.computed - result.node = builder.node - result.positioned = builder.computed{"position"} != POSITION_STATIC - result.shrink = result.isShrink(nil, shrink) - result.resolveDimensions(parentWidth, parentHeight, maxContentWidth) +proc newFlowRootBox(viewport: Viewport, builder: BoxBuilder, + parentWidth: int, parentHeight = none(int), shrink = true, + contentWidthInfinite = false): BlockBox = + let box = BlockBox( + viewport: viewport, + computed: builder.computed, + node: builder.node, + positioned: builder.computed{"position"} != POSITION_STATIC, + contentWidthInfinite: contentWidthInfinite + ) + box.shrink = box.isShrink(nil, shrink) + box.resolveDimensions(parentWidth, parentHeight) + return box proc newBlockBox(parent: BlockBox, builder: BoxBuilder): BlockBox = - new(result) - result.viewport = parent.viewport - result.computed = builder.computed - result.shrink = result.isShrink(parent) - result.positioned = builder.computed{"position"} != POSITION_STATIC - let maxContentWidth = if result.shrink: - some(parent.maxContentWidth) + let box = BlockBox( + viewport: parent.viewport, + computed: builder.computed, + positioned: builder.computed{"position"} != POSITION_STATIC, + node: builder.node + ) + box.shrink = box.isShrink(parent) + box.contentWidthInfinite = parent.contentWidthInfinite + let parentWidth = if box.positioned: + parent.viewport.positioned[^1].contentWidth + else: + parent.contentWidth + let parentHeight = if box.positioned: + parent.viewport.positioned[^1].contentHeight else: - none(int) - result.node = builder.node - result.resolveDimensions(parent.contentWidth, parent.contentHeight, maxContentWidth) + parent.contentHeight + box.resolveDimensions(parentWidth, parentHeight) + return box proc newListItem(parent: BlockBox, builder: ListItemBoxBuilder): ListItemBox = - new(result) - result.viewport = parent.viewport - result.computed = builder.content.computed - result.positioned = builder.computed{"position"} != POSITION_STATIC - result.shrink = result.isShrink(parent) - let maxContentWidth = if result.shrink: - some(parent.maxContentWidth) + let box = ListItemBox( + viewport: parent.viewport, + computed: builder.computed, + positioned: builder.computed{"position"} != POSITION_STATIC, + node: builder.node + ) + box.shrink = box.isShrink(parent) + box.contentWidthInfinite = parent.contentWidthInfinite + let parentWidth = if box.positioned: + parent.viewport.positioned[^1].contentWidth else: - none(int) - result.node = builder.node - result.resolveDimensions(parent.contentWidth, parent.contentHeight, maxContentWidth) + parent.contentWidth + let parentHeight = if box.positioned: + parent.viewport.positioned[^1].contentHeight + else: + parent.contentHeight + box.resolveDimensions(parentWidth, parentHeight) + return box proc newInlineBlock(viewport: Viewport, builder: BoxBuilder, parentWidth: int, parentHeight = none(int)): InlineBlockBox = new(result) @@ -592,7 +615,7 @@ proc newInlineContext(parent: BlockBox): InlineContext = shrink: parent.shrink, contentHeight: parent.contentHeight, contentWidth: parent.contentWidth, - maxContentWidth: parent.maxContentWidth + contentWidthInfinite: parent.contentWidthInfinite ) proc buildBlock(builder: BlockBoxBuilder, parent: BlockBox): BlockBox @@ -614,7 +637,10 @@ proc applyInlineDimensions(box: BlockBox) = box.width = if not box.isWidthSpecified(): # We can make the box as small/large as the content's width. if box.shrink: - min(box.width, box.maxContentWidth) + if box.contentWidthInfinite: + box.width + else: + min(box.width, box.contentWidth) else: max(box.width, box.contentWidth) else: @@ -761,27 +787,14 @@ proc buildListItem(builder: ListItemBoxBuilder, parent: BlockBox): ListItemBox = result.marker = buildMarker(builder.marker, result) result.buildLayout(builder.content) -proc positionAbsolute(box: BlockBox, last: BlockBox = box.viewport.root[0]) = - # we use the viewport's dimensions if not parentPositioned. - let parentPositioned = box.viewport.positioned.len > 0 - let last = if parentPositioned: - box.viewport.positioned[^1] - else: - box.viewport.root[0] +proc positionAbsolute(box: BlockBox) = + let last = box.viewport.positioned[^1] let left = box.computed{"left"} let right = box.computed{"right"} let top = box.computed{"top"} let bottom = box.computed{"bottom"} - let parentHeight = if parentPositioned: - last.height - else: - box.viewport.window.height_px - let parentWidth = if parentPositioned: - last.width - else: - box.viewport.window.width_px - #TODO TODO TODO we should use parentWidth/parentHeight for size calculations - # too + let parentHeight = last.contentHeight.get(box.viewport.window.height_px) + let parentWidth = last.contentWidth box.x_positioned = not (left.auto and right.auto) box.y_positioned = not (top.auto and bottom.auto) if not left.auto: @@ -824,7 +837,10 @@ proc applyChildPosition(parent, child: BlockBox, spec: bool, x, y: var int, marg y += child.height parent.height += child.height if not spec: - parent.width = min(parent.maxContentWidth, max(child.width, parent.width)) + parent.width = if parent.contentWidthInfinite: + max(child.width, parent.width) + else: + min(parent.contentWidth, max(child.width, parent.width)) parent.xminwidth = max(parent.xminwidth, child.xminwidth) margin_todo = Strut() margin_todo.append(child.margin_bottom) @@ -846,7 +862,7 @@ proc positionBlocks(box: BlockBox) = # If content width has been specified, use it. # Otherwise, contentWidth is just the maximum width we can take up, so - # set width to min(maxContentWidth, box.contentWidth) + # set width to box.contentWidth let spec = box.isWidthSpecified() if spec: box.width = box.contentWidth @@ -916,8 +932,11 @@ proc buildTableCaption(viewport: Viewport, builder: TableCaptionBoxBuilder, maxw result = viewport.newFlowRootBox(builder, maxwidth, maxheight, shrink) result.buildLayout(builder) -proc buildTableCell(viewport: Viewport, builder: TableCellBoxBuilder, parentWidth: int, parentHeight: Option[int], shrink: bool, max_width = none(int)): BlockBox = - result = viewport.newTableCellBox(builder, parentWidth, parentHeight, shrink, max_width) +proc buildTableCell(viewport: Viewport, builder: TableCellBoxBuilder, + parentWidth: int, parentHeight: Option[int], shrink: bool, + contentWidthInfinite = false): BlockBox = + result = viewport.newTableCellBox(builder, parentWidth, parentHeight, + shrink, contentWidthInfinite) result.buildLayout(builder) proc preBuildTableRow(pctx: var TableContext, box: TableRowBoxBuilder, parent: BlockBox, i: int): RowContext = @@ -931,11 +950,8 @@ proc preBuildTableRow(pctx: var TableContext, box: TableRowBoxBuilder, parent: B let rowspan = cellbuilder.computed{"-cha-rowspan"} let computedWidth = cellbuilder.computed{"width"} let spec = (not computedWidth.auto) and computedWidth.unit != UNIT_PERC - let max_width = if spec: - none(int) - else: - some(high(int)) - let box = parent.viewport.buildTableCell(cellbuilder, parent.contentWidth, parent.contentHeight, not spec, max_width) + 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) ctx.cells[i] = wrapper if rowspan != 1: @@ -1211,14 +1227,15 @@ proc buildBlock(builder: BlockBoxBuilder, parent: BlockBox): BlockBox = result.buildLayout(builder) # Establish a new flow-root context and build a block box. -proc buildRootBlock(viewport: Viewport, builder: BlockBoxBuilder) = +proc buildRootBlock(viewport: Viewport, builder: BlockBoxBuilder): BlockBox = let box = viewport.newFlowRootBox(builder, viewport.window.width_px, shrink = false) - viewport.root.add(box) + viewport.positioned.add(box) box.buildLayout(builder) # Normally margin-top would be used by positionBlock, but the root block # doesn't get positioned by the parent, so we have to do it manually here. #TODO this is kind of ugly. box.offset.y += box.margin_top + return box # Generation phase @@ -1656,8 +1673,7 @@ proc generateTableBox(styledNode: StyledNode, viewport: Viewport, parent: var In box.generateTableChildWrappers() return box -proc renderLayout*(viewport: var Viewport, root: StyledNode) = - viewport.root.setLen(0) +proc renderLayout*(viewport: var Viewport, root: StyledNode): BlockBox = viewport.positioned.setLen(0) let builder = root.generateBlockBox(viewport) - viewport.buildRootBlock(builder) + return viewport.buildRootBlock(builder) diff --git a/src/render/renderdocument.nim b/src/render/renderdocument.nim index 0cdd8075..d18dbdd5 100644 --- a/src/render/renderdocument.nim +++ b/src/render/renderdocument.nim @@ -366,15 +366,23 @@ const css = staticRead"res/ua.css" let uastyle = css.parseStylesheet() const quirk = css & staticRead"res/quirk.css" let quirkstyle = quirk.parseStylesheet() -proc renderDocument*(document: Document, window: WindowAttributes, userstyle: CSSStylesheet, layout: var Viewport, previousStyled: StyledNode): (FlexibleGrid, StyledNode) = +type RenderedDocument* = object + grid*: FlexibleGrid + styledRoot*: StyledNode + images*: seq[StyledNode] + +proc renderDocument*(document: Document, userstyle: CSSStylesheet, + layout: var Viewport, previousStyled: StyledNode): RenderedDocument = + var grid: FlexibleGrid var uastyle = uastyle if document.mode == QUIRKS: uastyle = quirkstyle - let styledNode = document.applyStylesheets(uastyle, userstyle, previousStyled) - result[1] = styledNode - layout.renderLayout(styledNode) - result[0].setLen(0) - for root in layout.root: - result[0].renderBlockBox(root, 0, 0, window) - if result[0].len == 0: - result[0].addLine() + let styledRoot = document.applyStylesheets(uastyle, userstyle, previousStyled) + let rootBox = layout.renderLayout(styledRoot) + grid.renderBlockBox(rootBox, 0, 0, document.window.attrs) + if grid.len == 0: + grid.addLine() + return RenderedDocument( + grid: grid, + styledRoot: styledRoot + ) diff --git a/src/types/mime.nim b/src/types/mime.nim index cd4c2d51..96742337 100644 --- a/src/types/mime.nim +++ b/src/types/mime.nim @@ -9,6 +9,7 @@ const DefaultGuess = [ ("xht", "application/xhtml+xml"), ("txt", "text/plain"), ("css", "text/css"), + ("png", "image/png"), ("", "text/plain") ].toTable() |