diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/config/config.nim | 4 | ||||
-rw-r--r-- | src/css/cascade.nim | 6 | ||||
-rw-r--r-- | src/html/dom.nim | 2 | ||||
-rw-r--r-- | src/img/png.nim | 67 | ||||
-rw-r--r-- | src/io/bufreader.nim | 5 | ||||
-rw-r--r-- | src/io/bufwriter.nim | 8 | ||||
-rw-r--r-- | src/layout/box.nim | 5 | ||||
-rw-r--r-- | src/layout/engine.nim | 26 | ||||
-rw-r--r-- | src/layout/renderdocument.nim | 39 | ||||
-rw-r--r-- | src/local/container.nim | 9 | ||||
-rw-r--r-- | src/local/pager.nim | 5 | ||||
-rw-r--r-- | src/local/term.nim | 97 | ||||
-rw-r--r-- | src/server/buffer.nim | 22 |
13 files changed, 233 insertions, 62 deletions
diff --git a/src/config/config.nim b/src/config/config.nim index 1ac0393e..6611b4ef 100644 --- a/src/config/config.nim +++ b/src/config/config.nim @@ -33,6 +33,9 @@ type FormatMode* = set[FormatFlags] + ImageMode* = enum + imNone = "none", imSixel = "sixel", imKitty = "kitty" + ChaPathResolved* = distinct string ActionMap = object @@ -104,6 +107,7 @@ type color_mode* {.jsgetset.}: Option[ColorMode] format_mode* {.jsgetset.}: Option[FormatMode] no_format_mode* {.jsgetset.}: FormatMode + image_mode* {.jsgetset.}: Option[ImageMode] emulate_overline* {.jsgetset.}: bool alt_screen* {.jsgetset.}: Option[bool] highlight_color* {.jsgetset.}: RGBAColor diff --git a/src/css/cascade.nim b/src/css/cascade.nim index 9c3e5803..b2d007f2 100644 --- a/src/css/cascade.nim +++ b/src/css/cascade.nim @@ -380,7 +380,11 @@ proc applyRulesFrameInvalid(frame: CascadeFrame; ua, user: CSSStylesheet; styledParent.children.add(styledText) of peImage: let src = Element(styledParent.node).attr(satSrc) - let content = CSSContent(t: ContentImage, s: src) + let content = CSSContent( + t: ContentImage, + s: src, + bmp: HTMLImageElement(styledParent.node).bitmap + ) let styledText = styledParent.newStyledReplacement(content) styledText.pseudo = pseudo styledParent.children.add(styledText) diff --git a/src/html/dom.nim b/src/html/dom.nim index 32e98824..76ed9b70 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -2849,7 +2849,7 @@ proc loadResource(window: Window; image: HTMLImageElement) = return let pngData = pngData.get let buffer = cast[ptr UncheckedArray[uint8]](pngData.buffer) - let high = int(pngData.size - 1) + let high = int(pngData.size) - 1 image.bitmap = fromPNG(toOpenArray(buffer, 0, high)) ) window.loadingResourcePromises.add(p) diff --git a/src/img/png.nim b/src/img/png.nim index 7f4d8493..bd7e6206 100644 --- a/src/img/png.nim +++ b/src/img/png.nim @@ -132,8 +132,7 @@ func spp(reader: PNGReader): int = of pcTrueColorWithAlpha: return 4 func scanlen(reader: PNGReader): int {.inline.} = - let w = reader.width + 1 - return (w * reader.spp * int(reader.bitDepth) + 7) div 8 + return 1 + (reader.width * reader.spp * int(reader.bitDepth) + 7) div 8 proc handleError(reader: var PNGReader; msg: string) = #TODO proper error handling? @@ -258,28 +257,55 @@ proc readtRNS(reader: var PNGReader) = for i in 0 ..< reader.palette.len: reader.palette[i].a = reader.readU8() +func paethPredictor(a, b, c: uint16): uint16 = + let pa0 = int(b) - int(c) + let pb0 = int(a) - int(c) + let pa = abs(pa0) + let pb = abs(pb0) + let pc = abs(pa0 + pb0) + return if pa <= pb and pa <= pc: a elif pb <= pc: b else: c + proc unfilter(reader: var PNGReader; irow: openArray[uint8]; bpp: int) = # none, sub, up -> replace uprow directly # average, paeth -> copy to temp array, then replace uprow - let fil = irow[0] - let w = reader.width - case fil + case irow[0] of 0u8: # none - copyMem(addr reader.uprow[0], unsafeAddr irow[1], w) + copyMem(addr reader.uprow[0], unsafeAddr irow[1], irow.len) of 1u8: # sub for i in 1 ..< irow.len: let j = i - 1 # skip filter byte - reader.uprow[j] = irow[i] - if j - bpp >= 0: - reader.uprow[j] += irow[j - bpp] + let aidx = j - bpp + let x = uint16(irow[i]) + let a = if aidx >= 0: uint16(reader.uprow[aidx]) else: 0u16 + reader.uprow[j] = uint8((x + a) and 0xFF) of 2u8: # up for i in 1 ..< irow.len: let j = i - 1 # skip filter byte - reader.uprow[j] += irow[i] + let x = uint16(irow[i]) + let b = uint16(reader.uprow[j]) + reader.uprow[j] = uint8((x + b) and 0xFF) of 3u8: # average - reader.err "average not implemented yet" + for i in 1 ..< irow.len: + let j = i - 1 # skip filter byte + let aidx = j - bpp + let x = uint16(irow[i]) + let a = if aidx >= 0: uint16(reader.uprow[aidx]) else: 0u16 + let b = uint16(reader.uprow[j]) + reader.uprow[j] = uint8((x + (a + b) div 2) and 0xFF) of 4u8: # paeth - reader.err "paeth not implemented yet" + var cmap: array[8, uint16] # max bpp is 16 bit with true color = 4 * 2 + var k = 0 + for i in 1 ..< irow.len: + let j = i - 1 # skip filter byte + let aidx = j - bpp + let x = uint16(irow[i]) + let a = if aidx >= 0: uint16(reader.uprow[aidx]) else: 0u16 + let b = uint16(reader.uprow[j]) + let kk = k mod bpp + let c = cmap[kk] + cmap[kk] = b + reader.uprow[j] = uint8((x + paethPredictor(a, b, c)) and 0xFF) + inc k else: reader.err "got invalid filter" @@ -340,11 +366,16 @@ proc writepxs(reader: var PNGReader; crow: var openArray[RGBAColor]) = crow[x] = rgba(n, n, n, a) of pcTrueColorWithAlpha: let step = int(reader.bitDepth) div 8 + var i = 0 for x in 0 ..< crow.len: - let r = reader.uprow[x * step] - let g = reader.uprow[(x + 1) * step] - let b = reader.uprow[(x + 2) * step] - let a = reader.uprow[(x + 3) * step] + let r = reader.uprow[i] + i += step + let g = reader.uprow[i] + i += step + let b = reader.uprow[i] + i += step + let a = reader.uprow[i] + i += step crow[x] = rgba(r, g, b, a) proc readPLTE(reader: var PNGReader) = @@ -404,10 +435,10 @@ proc readIDAT(reader: var PNGReader) = for y in reader.atline ..< maxline: let yi = y * sl assert yi + sl - 1 < reader.idatAt - reader.unfilter(toOpenArray(reader.idatBuf, yi, yi + sl - 1), bpp) + reader.unfilter(reader.idatBuf.toOpenArray(yi, yi + sl - 1), bpp) if unlikely(reader.bmp == nil): return let yj = y * reader.width - reader.writepxs(toOpenArray(bmp.px, yj, yj + reader.width - 1)) + reader.writepxs(bmp.px.toOpenArray(yj, yj + reader.width - 1)) proc readIEND(reader: var PNGReader) = if reader.i < reader.limit: diff --git a/src/io/bufreader.nim b/src/io/bufreader.nim index 6de269ac..3ee36208 100644 --- a/src/io/bufreader.nim +++ b/src/io/bufreader.nim @@ -6,6 +6,7 @@ import std/tables import io/dynstream import types/blob +import types/color import types/formdata import types/opt import types/url @@ -53,6 +54,7 @@ proc sread*(reader: var BufferedReader; part: var FormDataEntry) proc sread*(reader: var BufferedReader; blob: var Blob) proc sread*[T](reader: var BufferedReader; o: var Option[T]) proc sread*[T, E](reader: var BufferedReader; o: var Result[T, E]) +proc sread*(reader: var BufferedReader; c: var RGBAColor) {.inline.} proc readData(reader: var BufferedReader; buffer: pointer; len: int) = assert reader.bufIdx + len <= reader.buffer.len @@ -198,3 +200,6 @@ proc sread*[T, E](reader: var BufferedReader; o: var Result[T, E]) = o.err(e) else: o.err() + +proc sread*(reader: var BufferedReader; c: var RGBAColor) = + reader.sread(uint32(c)) diff --git a/src/io/bufwriter.nim b/src/io/bufwriter.nim index 75da4190..0957d3e8 100644 --- a/src/io/bufwriter.nim +++ b/src/io/bufwriter.nim @@ -6,11 +6,11 @@ import std/sets import std/tables import io/dynstream - import types/blob +import types/color import types/formdata -import types/url import types/opt +import types/url type BufferedWriter* = object stream: DynStream @@ -77,6 +77,7 @@ proc swrite*(writer: var BufferedWriter; part: FormDataEntry) proc swrite*(writer: var BufferedWriter; blob: Blob) proc swrite*[T](writer: var BufferedWriter; o: Option[T]) proc swrite*[T, E](writer: var BufferedWriter; o: Result[T, E]) +proc swrite*(writer: var BufferedWriter; c: RGBAColor) {.inline.} proc writeData(writer: var BufferedWriter; buffer: pointer; len: int) = let targetLen = writer.bufLen + len @@ -181,3 +182,6 @@ proc swrite*[T, E](writer: var BufferedWriter; o: Result[T, E]) = else: when not (E is void): writer.swrite(o.error) + +proc swrite*(writer: var BufferedWriter; c: RGBAColor) = + writer.swrite(uint32(c)) diff --git a/src/layout/box.nim b/src/layout/box.nim index 79f927ef..40628204 100644 --- a/src/layout/box.nim +++ b/src/layout/box.nim @@ -1,5 +1,6 @@ import css/stylednode import css/values +import img/bitmap import layout/layoutunit type @@ -12,7 +13,7 @@ type h*: LayoutUnit InlineAtomType* = enum - iatSpacing, iatWord, iatInlineBlock + iatSpacing, iatWord, iatInlineBlock, iatImage InlineAtom* = ref object offset*: Offset @@ -24,6 +25,8 @@ type str*: string of iatInlineBlock: innerbox*: BlockBox + of iatImage: + bmp*: Bitmap RootInlineFragment* = ref object # offset relative to parent diff --git a/src/layout/engine.nim b/src/layout/engine.nim index 99d205f5..30afb066 100644 --- a/src/layout/engine.nim +++ b/src/layout/engine.nim @@ -5,6 +5,7 @@ import std/unicode import css/stylednode import css/values +import img/bitmap import layout/box import layout/layoutunit import types/winattrs @@ -68,6 +69,7 @@ type text: seq[string] newline: bool splitType: set[SplitType] + bmp: Bitmap BlockBoxBuilder = ref object of BoxBuilder inlinelayout: bool @@ -1500,8 +1502,20 @@ proc layoutInline(ictx: var InlineContext; box: InlineBoxBuilder): if ictx.firstTextFragment == nil: ictx.firstTextFragment = fragment ictx.lastTextFragment = fragment - ictx.layoutText(state, box.text) - ictx.layoutChildren(state, box.children) + if box.bmp != nil: + let iastate = InlineAtomState( + vertalign: state.computed{"vertical-align"}, + baseline: ictx.cellheight + ) + let atom = InlineAtom( + t: iatImage, + bmp: box.bmp, + size: Size(w: int(box.bmp.width), h: int(box.bmp.height)) #TODO overflow + ) + discard ictx.addAtom(state, iastate, atom) + else: + ictx.layoutText(state, box.text) + ictx.layoutChildren(state, box.children) if stSplitEnd in box.splitType: let paddingRight = box.computed{"padding-right"}.px(lctx, ictx.space.w) ictx.currentLine.size.w += paddingRight @@ -2856,10 +2870,10 @@ proc generateFromElem(ctx: var InnerBlockContext; styledNode: StyledNode) = of DisplayNone: discard proc generateAnonymousInlineText(ctx: var InnerBlockContext; text: string; - styledNode: StyledNode) = + styledNode: StyledNode; bmp: Bitmap = nil) = if ctx.iroot == nil: let computed = styledNode.computed.inheritProperties() - ctx.ibox = InlineBoxBuilder(computed: computed, node: styledNode) + ctx.ibox = InlineBoxBuilder(computed: computed, node: styledNode, bmp: bmp) if ctx.inlineStack.len > 0: let iparent = ctx.reconstructInlineParents() iparent.children.add(ctx.ibox) @@ -2900,7 +2914,7 @@ proc generateReplacement(ctx: var InnerBlockContext; ctx.generateAnonymousInlineText(child.content.s, parent) of ContentImage: #TODO idk - ctx.generateAnonymousInlineText("[img]", parent) + ctx.generateAnonymousInlineText("[img]", parent, child.content.bmp) of ContentVideo: ctx.generateAnonymousInlineText("[video]", parent) of ContentAudio: @@ -3116,7 +3130,7 @@ proc generateTableBox(styledNode: StyledNode; lctx: LayoutState; box.generateTableChildWrappers() return box -proc renderLayout*(root: StyledNode; attrsp: ptr WindowAttributes): BlockBox = +proc layout*(root: StyledNode; attrsp: ptr WindowAttributes): BlockBox = let space = AvailableSpace( w: stretch(attrsp[].width_px), h: stretch(attrsp[].height_px) diff --git a/src/layout/renderdocument.nim b/src/layout/renderdocument.nim index fe7b92cb..e57a6d2c 100644 --- a/src/layout/renderdocument.nim +++ b/src/layout/renderdocument.nim @@ -3,12 +3,13 @@ import std/unicode import css/stylednode import css/values +import img/bitmap import layout/box import layout/engine import layout/layoutunit -import types/winattrs import types/cell import types/color +import types/winattrs import utils/strwidth type @@ -208,12 +209,19 @@ proc setText(grid: var FlexibleGrid; linestr: string; x, y: int; format: Format; assert grid[y].formats[fi].pos <= nx # That's it! -type RenderState = object - # Position of the absolute positioning containing block: - # https://drafts.csswg.org/css-position/#absolute-positioning-containing-block - absolutePos: seq[Offset] - bgcolor: CellColor - attrsp: ptr WindowAttributes +type + PosBitmap* = ref object + x*: int + y*: int + bmp*: Bitmap + + RenderState = object + # Position of the absolute positioning containing block: + # https://drafts.csswg.org/css-position/#absolute-positioning-containing-block + absolutePos: seq[Offset] + bgcolor: CellColor + attrsp: ptr WindowAttributes + images: seq[PosBitmap] template attrs(state: RenderState): WindowAttributes = state.attrsp[] @@ -361,6 +369,15 @@ proc renderInlineFragment(grid: var FlexibleGrid; state: var RenderState; grid.setRowWord(state, atom, offset, format, fragment.node) of iatSpacing: grid.setSpacing(state, atom, offset, format, fragment.node) + of iatImage: + state.images.add(PosBitmap( + x: (offset.x div state.attrs.ppc).toInt, + y: (offset.y div state.attrs.ppl).toInt, + bmp: atom.bmp + )) + else: + for child in fragment.children: + grid.renderInlineFragment(state, child, offset) if fragment.computed{"position"} != PositionStatic: if fragment.splitType != {stSplitStart, stSplitEnd}: if stSplitStart in fragment.splitType: @@ -370,8 +387,6 @@ proc renderInlineFragment(grid: var FlexibleGrid; state: var RenderState; )) if stSplitEnd in fragment.splitType: discard state.absolutePos.pop() - for child in fragment.children: - grid.renderInlineFragment(state, child, offset) proc renderRootInlineFragment(grid: var FlexibleGrid; state: var RenderState; root: RootInlineFragment; offset: Offset) = @@ -440,7 +455,8 @@ proc renderBlockBox(grid: var FlexibleGrid; state: var RenderState; stack.add((box.nested[i], offset)) proc renderDocument*(grid: var FlexibleGrid; bgcolor: var CellColor; - styledRoot: StyledNode; attrsp: ptr WindowAttributes) = + styledRoot: StyledNode; attrsp: ptr WindowAttributes; + images: var seq[PosBitmap]) = grid.setLen(0) if styledRoot == nil: # no HTML element when we run cascade; just clear all lines. @@ -449,8 +465,9 @@ proc renderDocument*(grid: var FlexibleGrid; bgcolor: var CellColor; absolutePos: @[Offset(x: 0, y: 0)], attrsp: attrsp ) - let rootBox = renderLayout(styledRoot, attrsp) + let rootBox = styledRoot.layout(attrsp) grid.renderBlockBox(state, rootBox, Offset(x: 0, y: 0)) if grid.len == 0: grid.addLines(1) bgcolor = state.bgcolor + images = state.images diff --git a/src/local/container.nim b/src/local/container.nim index b88161b8..3a0f1daf 100644 --- a/src/local/container.nim +++ b/src/local/container.nim @@ -14,6 +14,7 @@ import io/socketstream import js/javascript import js/jstypes import js/regex +import layout/renderdocument import loader/headers import loader/loader import loader/request @@ -25,7 +26,6 @@ import types/cookie import types/referrer import types/url import types/winattrs -import utils/luwrap import utils/mimeguess import utils/strwidth import utils/twtstr @@ -152,6 +152,7 @@ type cacheFile* {.jsget.}: string mainConfig*: Config flags*: set[ContainerFlag] + images*: seq[PosBitmap] jsDestructor(Highlight) jsDestructor(Container) @@ -458,7 +459,6 @@ proc requestLines(container: Container): EmptyPromise {.discardable.} = container.lineshift = w.a for y in 0 ..< min(res.lines.len, w.len): container.lines[y] = res.lines[y] - container.lines[y].str.mnormalize() var isBgNew = container.bgcolor != res.bgcolor if isBgNew: container.bgcolor = res.bgcolor @@ -474,6 +474,7 @@ proc requestLines(container: Container): EmptyPromise {.discardable.} = let cw = container.fromy ..< container.fromy + container.height if w.a in cw or w.b in cw or cw.a in w or cw.b in w or isBgNew: container.triggerEvent(cetUpdate) + container.images = res.images ) proc redraw(container: Container) {.jsfunc.} = @@ -1387,9 +1388,9 @@ proc onload(container: Container; res: int) = container.triggerEvent(cetStatus) container.triggerEvent(cetLoaded) if cfHasStart notin container.flags and container.url.anchor != "": - container.requestLines().then(proc(): Promise[Opt[tuple[x, y: int]]] = + container.requestLines().then(proc(): Promise[GotoAnchorResult] = return container.iface.gotoAnchor() - ).then(proc(res: Opt[tuple[x, y: int]]) = + ).then(proc(res: GotoAnchorResult) = if res.isSome: let res = res.get container.setCursorXYCenter(res.x, res.y) diff --git a/src/local/pager.nim b/src/local/pager.nim index 28c1face..8e828190 100644 --- a/src/local/pager.nim +++ b/src/local/pager.nim @@ -449,6 +449,11 @@ proc draw*(pager: Pager) = else: pager.term.writeGrid(pager.statusgrid, 0, pager.attrs.height - 1) pager.term.outputGrid() + if container != nil: + pager.term.clearImages() + for image in container.images: + pager.term.outputImage(image.bmp, image.x - container.fromx, + image.y - container.fromy, pager.attrs.width, pager.attrs.height - 1) if pager.askpromise != nil: pager.term.setCursor(pager.askcursor, pager.attrs.height - 1) elif pager.lineedit.isSome: diff --git a/src/local/term.nim b/src/local/term.nim index eee53039..0da700f5 100644 --- a/src/local/term.nim +++ b/src/local/term.nim @@ -8,7 +8,9 @@ import std/unicode import bindings/termcap import config/config +import img/bitmap import io/posixstream +import js/base64 import types/cell import types/color import types/opt @@ -64,6 +66,7 @@ type attrs*: WindowAttributes colormode: ColorMode formatmode: FormatMode + imagemode: ImageMode smcup: bool tc: Termcap tname: string @@ -128,6 +131,15 @@ const RMCUP = DECRST(1049) const SGRMOUSEBTNON = DECSET(1002, 1006) const SGRMOUSEBTNOFF = DECRST(1002, 1006) +# application program command + +# This is only used in kitty images, and join()'ing kilobytes of base64 +# is rather inefficient so we don't use a template. +const APC = "\e_" +const ST = "\e\\" + +const KITTYQUERY = APC & "Gi=1,a=q;" & ST + when not termcap_found: const CNORM = DECSET(25) const CIVIS = DECRST(25) @@ -555,15 +567,13 @@ proc applyConfig(term: Terminal) = # colors, formatting if term.config.display.color_mode.isSome: term.colormode = term.config.display.color_mode.get - elif term.isatty(): - let colorterm = getEnv("COLORTERM") - if colorterm in ["24bit", "truecolor"]: - term.colormode = cmTrueColor if term.config.display.format_mode.isSome: term.formatmode = term.config.display.format_mode.get for fm in FormatFlags: if fm in term.config.display.no_format_mode: term.formatmode.excl(fm) + if term.config.display.image_mode.isSome: + term.imagemode = term.config.display.image_mode.get if term.isatty(): if term.config.display.alt_screen.isSome: term.smcup = term.config.display.alt_screen.get @@ -603,6 +613,50 @@ proc outputGrid*(term: Terminal) = for i in 0 ..< term.canvas.cells.len: term.pcanvas[i] = term.canvas[i] +proc clearImages*(term: Terminal) = + if term.imagemode == imKitty: + term.write(APC & "Ga=d" & ST) + +proc outputImage*(term: Terminal; bmp: Bitmap; x, y, maxw, maxh: int) = + case term.imagemode + of imNone: discard + of imSixel: + discard #TODO + of imKitty: + # max 4096 bytes, base encoded + const MaxPixels = ((4096 div 4) * 3) div 3 + let offx = if x < 0: -(x * term.attrs.ppc) else: 0 + let offy = if y < 0: -(y * term.attrs.ppl) else: 0 + let w = int(bmp.width) + let h = int(bmp.height) + var dispw = w + if x + dispw div term.attrs.ppc > maxw: + dispw = (maxw - x) * term.attrs.ppc + var disph = h + if y + disph div term.attrs.ppl > maxh: + disph = (maxh - y) * term.attrs.ppl + var outs = term.cursorGoto(max(x, 0), max(y, 0)) + outs &= APC & "Gf=24,m=1,a=T,C=1,s=" & $w & ",v=" & $h & + ",x=" & $offx & ",y=" & $offy & ",w=" & $dispw & ",h=" & $disph & ';' + var buf = newStringOfCap(MaxPixels * 4) + var i = 0 + # transcode to RGB + while i < bmp.px.len: # max is 4096 + if i > 0 and i mod MaxPixels == 0: + outs &= btoa(buf) + outs &= ST + term.write(outs) + buf.setLen(0) + outs = APC & "Gm=1;" + buf &= char(bmp.px[i].r) + buf &= char(bmp.px[i].g) + buf &= char(bmp.px[i].b) + inc i + outs = APC & "Gm=0;" + outs &= btoa(buf) + outs &= ST + term.write(outs) + proc clearCanvas*(term: Terminal) = term.cleared = false @@ -665,7 +719,7 @@ when termcap_found: type QueryAttrs = enum - qaAnsiColor, qaRGB, qaSixel + qaAnsiColor, qaRGB, qaSixel, qaKittyImage QueryResult = object success: bool @@ -683,6 +737,7 @@ proc queryAttrs(term: Terminal; windowOnly: bool): QueryResult = const outs = XTGETFG & XTGETBG & + KITTYQUERY & GEOMPIXEL & GEOMCELL & XTGETTCAP("524742") & @@ -764,12 +819,15 @@ proc queryAttrs(term: Terminal; windowOnly: bool): QueryResult = term.expect ';' if term.consume == 'r' and term.consume == 'g' and term.consume == 'b': term.expect ':' - template eat_color(tc: char): uint8 = + var was_esc = false + template eat_color(tc: set[char]): uint8 = var val = 0u8 var i = 0 - while (let c = term.consume; c != tc): + var c = char(0) + while (c = term.consume; c notin tc): let v0 = hexValue(c) - if i > 4 or v0 == -1: fail # wat + if i > 4 or v0 == -1: + fail # wat let v = uint8(v0) if i == 0: # 1st place val = (v shl 4) or v @@ -777,10 +835,14 @@ proc queryAttrs(term: Terminal; windowOnly: bool): QueryResult = val = (val xor 0xF) or v # all other places are irrelevant inc i + was_esc = c == '\e' val - let r = eat_color '/' - let g = eat_color '/' - let b = eat_color '\a' + let r = eat_color {'/'} + let g = eat_color {'/'} + let b = eat_color {'\a', '\e'} + if was_esc: + # we got ST, not BEL; at least kitty does this + term.expect '\\' if c == '0': result.fgcolor = some(rgb(r, g, b)) else: @@ -805,7 +867,14 @@ proc queryAttrs(term: Terminal; windowOnly: bool): QueryResult = if id == tcapRGB: result.attrs.incl(qaRGB) else: # 0 - term.expect '\e' # ST (1) + # pure insanity: kitty returns P0, but also +r524742 after. please + # make up your mind! + term.skip_until '\e' # ST (1) + term.expect '\\' # ST (2) + of '_': # APC + term.expect 'G' + result.attrs.incl(qaKittyImage) + term.skip_until '\e' # ST (1) term.expect '\\' # ST (2) else: fail @@ -844,6 +913,10 @@ proc detectTermAttributes(term: Terminal; windowOnly: bool): TermStartResult = term.colormode = cmANSI if qaRGB in r.attrs: term.colormode = cmTrueColor + if qaSixel in r.attrs: + term.imagemode = imSixel + if qaKittyImage in r.attrs: + term.imagemode = imKitty # just assume the terminal doesn't choke on these. term.formatmode = {ffStrike, ffOverline} if r.bgcolor.isSome: diff --git a/src/server/buffer.nim b/src/server/buffer.nim index 10d1cce0..56ad946f 100644 --- a/src/server/buffer.nim +++ b/src/server/buffer.nim @@ -26,6 +26,7 @@ import html/enums import html/env import html/event import html/formdata as formdata_impl +import img/bitmap import io/bufreader import io/bufstream import io/bufwriter @@ -91,6 +92,7 @@ type ishtml: bool firstBufferRead: bool lines: FlexibleGrid + images: seq[PosBitmap] request: Request # source request attrs: WindowAttributes window: Window @@ -648,7 +650,9 @@ proc findNextMatch*(buffer: Buffer; regex: Regex; cursorx, cursory: int; break inc y -proc gotoAnchor*(buffer: Buffer): Opt[tuple[x, y: int]] {.proxy.} = +type GotoAnchorResult* = Opt[tuple[x, y: int]] + +proc gotoAnchor*(buffer: Buffer): GotoAnchorResult {.proxy.} = if buffer.document == nil: return err() let anchor = buffer.document.findAnchor(buffer.url.anchor) @@ -673,7 +677,8 @@ proc do_reshape(buffer: Buffer) = buffer.prevStyled = nil let styledRoot = buffer.document.applyStylesheets(uastyle, buffer.userstyle, buffer.prevStyled) - buffer.lines.renderDocument(buffer.bgcolor, styledRoot, addr buffer.attrs) + buffer.lines.renderDocument(buffer.bgcolor, styledRoot, addr buffer.attrs, + buffer.images) buffer.prevStyled = styledRoot proc processData0(buffer: Buffer; data: openArray[char]): bool = @@ -1718,11 +1723,11 @@ proc readCanceled*(buffer: Buffer): bool {.proxy.} = proc findAnchor*(buffer: Buffer; anchor: string): bool {.proxy.} = return buffer.document != nil and buffer.document.findAnchor(anchor) != nil -type GetLinesResult* = tuple[ - numLines: int, - lines: seq[SimpleFlexibleLine], +type GetLinesResult* = tuple + numLines: int + lines: seq[SimpleFlexibleLine] bgcolor: CellColor -] + images: seq[PosBitmap] proc getLines*(buffer: Buffer; w: Slice[int]): GetLinesResult {.proxy.} = var w = w @@ -1736,6 +1741,11 @@ proc getLines*(buffer: Buffer; w: Slice[int]): GetLinesResult {.proxy.} = result.lines.add(line) result.numLines = buffer.lines.len result.bgcolor = buffer.bgcolor + if buffer.config.images: + for image in buffer.images: + if image.y <= w.b and + image.y + int(image.bmp.height) div buffer.attrs.ppl >= w.a: + result.images.add(image) proc markURL*(buffer: Buffer; schemes: seq[string]) {.proxy.} = if buffer.document == nil or buffer.document.body == nil: |