diff options
author | bptato <nincsnevem662@gmail.com> | 2023-06-02 00:36:54 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2023-06-05 03:58:21 +0200 |
commit | 8027e52cb221c432bed64517015ebf3182e6166d (patch) | |
tree | 18991f9e74c8dcfc0ed7439f3bc78a0cfec9b2d6 /src/types | |
parent | b3b97465805b7367df461a4b7b830fabaccf3a89 (diff) | |
download | chawan-8027e52cb221c432bed64517015ebf3182e6166d.tar.gz |
Add support for canvas and multipart
Quite incomplete canvas implementation. Crucially, the layout engine can't do much with whatever is drawn because it doesn't support images yet. I've re-introduced multipart as well, with the FormData API. For the append function I've also introduced a hack to the JS binding generator that allows requesting the JSContext pointer in nim procs. Really I should just fix the union generator thing and add support for overloading. In conclusion, for now the only thing canvas can be used for is exporting it as PNG and uploading it somewhere. Also, we now have PNG encoding and decoding too. (Now if only we had sixels as well...)
Diffstat (limited to 'src/types')
-rw-r--r-- | src/types/blob.nim | 61 | ||||
-rw-r--r-- | src/types/color.nim | 103 | ||||
-rw-r--r-- | src/types/cookie.nim | 4 | ||||
-rw-r--r-- | src/types/formdata.nim | 18 | ||||
-rw-r--r-- | src/types/line.nim | 66 | ||||
-rw-r--r-- | src/types/matrix.nim | 46 | ||||
-rw-r--r-- | src/types/mime.nim | 6 | ||||
-rw-r--r-- | src/types/url.nim | 10 | ||||
-rw-r--r-- | src/types/vector.nim | 53 |
9 files changed, 354 insertions, 13 deletions
diff --git a/src/types/blob.nim b/src/types/blob.nim new file mode 100644 index 00000000..677fb037 --- /dev/null +++ b/src/types/blob.nim @@ -0,0 +1,61 @@ +import js/javascript +import types/mime +import utils/twtstr + +type + DeallocFun = proc(buffer: pointer) {.noconv.} + + Blob* = ref object of RootObj + isfile*: bool + size* {.jsget.}: uint64 + ctype* {.jsget: "type".}: string + buffer*: pointer + deallocFun*: DeallocFun + + WebFile* = ref object of Blob + webkitRelativePath {.jsget.}: string + path*: string + file: File #TODO maybe use fd? + +proc newBlob*(buffer: pointer, size: int, ctype: string, + deallocFun: DeallocFun): Blob = + return Blob( + buffer: buffer, + size: uint64(size), + ctype: ctype, + deallocFun: deallocFun + ) + +proc finalize(blob: Blob) {.jsfin.} = + if blob.deallocFun != nil and blob.buffer != nil: + blob.deallocFun(blob.buffer) + +proc newWebFile*(path: string, webkitRelativePath = ""): WebFile = + var file: File + doAssert open(file, path, fmRead) #TODO bleh + return WebFile( + isfile: true, + path: path, + file: file, + webkitRelativePath: webkitRelativePath + ) + +#TODO File, Blob constructors + +func size*(this: WebFile): uint64 {.jsfget.} = + #TODO use stat instead + return uint64(this.file.getFileSize()) + +func ctype*(this: WebFile): string {.jsfget: "type".} = + return guessContentType(this.path) + +func name*(this: WebFile): string {.jsfget.} = + if this.path.len > 0 and this.path[^1] != '/': + return this.path.afterLast('/') + return this.path.afterLast('/', 2) + +#TODO lastModified + +proc addBlobModule*(ctx: JSContext) = + ctx.registerType(Blob) + ctx.registerType(WebFile, name = "File") diff --git a/src/types/color.nim b/src/types/color.nim index 02f56a56..818d4f5a 100644 --- a/src/types/color.nim +++ b/src/types/color.nim @@ -1,3 +1,4 @@ +import math import options import sequtils import strutils @@ -21,6 +22,8 @@ converter toRGBColor*(i: RGBAColor): RGBColor = converter toRGBAColor*(i: RGBColor): RGBAColor = return RGBAColor(uint32(i) or 0xFF000000u32) +func `==`*(a, b: RGBAColor): bool {.borrow.} + func rgbcolor*(color: CellColor): RGBColor = cast[RGBColor](color.n) @@ -220,10 +223,99 @@ func b*(c: RGBAColor): int = func a*(c: RGBAColor): int = return int(uint32(c) shr 24 and 0xff) +# https://html.spec.whatwg.org/#serialisation-of-a-color +func serialize*(color: RGBAColor): string = + if color.a == 255: + let r = toHex(cast[uint8](color.r)) + let g = toHex(cast[uint8](color.g)) + let b = toHex(cast[uint8](color.b)) + return "#" & r & g & b + let a = float64(color.a) / 255 + return "rgba(" & $color.r & ", " & $color.g & ", " & $color.b & ", " & $a & + ")" + +func `$`*(rgbacolor: RGBAColor): string = + return rgbacolor.serialize() + +# https://arxiv.org/pdf/2202.02864.pdf +func fastmul(c, ca: uint32): uint32 = + let u = c or 0xFF000000u32 + var rb = u and 0x00FF00FFu32 + rb *= ca + rb += 0x00800080 + rb += (rb shr 8) and 0x00FF00FFu32 + rb = rb and 0xFF00FF00u32 + var ga = (u shr 8) and 0x00FF00FFu32 + ga *= ca + ga += 0x00800080 + ga += (ga shr 8) and 0x00FF00FFu32 + ga = ga and 0xFF00FF00u32 + return ga or (rb shr 8) + +# fastmul, but preserves alpha +func fastmul1(c, ca: uint32): uint32 = + let u = c + var rb = u and 0x00FF00FFu32 + rb *= ca + rb += 0x00800080 + rb += (rb shr 8) and 0x00FF00FFu32 + rb = rb and 0xFF00FF00u32 + var ga = (u shr 8) and 0x00FF00FFu32 + ga *= ca + ga += 0x00800080 + ga += (ga shr 8) and 0x00FF00FFu32 + ga = ga and 0xFF00FF00u32 + return ga or (rb shr 8) + +func fastmul(c: RGBAColor, ca: uint32): uint32 = + return fastmul(uint32(c), ca) + +func fastmul1(c: RGBAColor, ca: uint32): uint32 = + return fastmul1(uint32(c), ca) + +func rgba*(r, g, b, a: int): RGBAColor + +func premul(c: RGBAColor): RGBAColor = + return RGBAColor(fastmul(c, uint32(c.a))) + +const straightAlphaTable = (func(): auto = + var table: array[256, array[256, uint8]] + for a in 0 ..< 256: + let multiplier = if a > 0: (255 / a.float32) else: 0 + for c in 0 ..< 256: + table[a][c] = min(round((c.float32 * multiplier)), 255).uint8 + return table)() + +proc straight*(c: RGBAColor): RGBAColor = + let r = straightAlphaTable[c.a][c.r] + let g = straightAlphaTable[c.a][c.g] + let b = straightAlphaTable[c.a][c.b] + return rgba(int(r), int(g), int(b), int(c.a)) + +func blend*(c0, c1: RGBAColor): RGBAColor = + let pc0 = c0.premul() + let pc1 = c1.premul() + let k = 255 - pc1.a + let mc = RGBAColor(fastmul1(pc0, uint32(k))) + let rr = pc1.r + mc.r + let rg = pc1.g + mc.g + let rb = pc1.b + mc.b + let ra = pc1.a + mc.a + let pres = rgba(rr, rg, rb, ra) + let res = straight(pres) + return res + +#func blend*(c0, c1: RGBAColor): RGBAColor = +# const norm = 1f64 / 255f64 +# let c0a = float64(c0.a) * norm +# let c1a = float64(c1.a) * norm +# let a0 = c0a + c1a * (1 - c0a) + func rgb*(r, g, b: int): RGBColor = return RGBColor((r shl 16) or (g shl 8) or b) -func `==`*(a, b: RGBAColor): bool {.borrow.} +func rgb*(r, g, b: uint8): RGBColor {.inline.} = + return rgb(int(r), int(g), int(b)) func r*(c: RGBColor): int = return int(uint32(c) shr 16 and 0xff) @@ -256,12 +348,15 @@ func YUV*(Y, U, V: int): RGBColor = func rgba*(r, g, b, a: int): RGBAColor = return RGBAColor((uint32(a) shl 24) or (uint32(r) shl 16) or (uint32(g) shl 8) or uint32(b)) +func gray*(n: int): RGBColor = + return rgb(n, n, n) #TODO use yuv instead? + +func gray*(n: uint8): RGBColor = + return gray(int(n)) + template `$`*(rgbcolor: RGBColor): string = "rgb(" & $rgbcolor.r & ", " & $rgbcolor.g & ", " & $rgbcolor.b & ")" -template `$`*(rgbacolor: RGBAColor): string = - "rgba(" & $rgbacolor.r & ", " & $rgbacolor.g & ", " & $rgbacolor.b & ", " & $rgbacolor.a & ")" - template `$`*(color: CellColor): string = if color.rgb: $color.rgbcolor diff --git a/src/types/cookie.nim b/src/types/cookie.nim index 41de0f4c..9dc50550 100644 --- a/src/types/cookie.nim +++ b/src/types/cookie.nim @@ -157,7 +157,9 @@ proc newCookie*(str: string): Cookie {.jsctor.} = if date.issome: cookie.expires = date.get.toTime().toUnix() of "max-age": - cookie.expires = now().toTime().toUnix() + parseInt64(val) + let x = parseInt64(val) + if x.isSome: + cookie.expires = now().toTime().toUnix() + x.get of "secure": cookie.secure = true of "httponly": cookie.httponly = true of "samesite": cookie.samesite = true diff --git a/src/types/formdata.nim b/src/types/formdata.nim new file mode 100644 index 00000000..983508bf --- /dev/null +++ b/src/types/formdata.nim @@ -0,0 +1,18 @@ +import types/blob + +type + FormDataEntry* = object + name*: string + filename*: string + case isstr*: bool + of true: + svalue*: string + of false: + value*: Blob + + FormData* = ref object + entries*: seq[FormDataEntry] + +iterator items*(this: FormData): FormDataEntry {.inline.} = + for entry in this.entries: + yield entry diff --git a/src/types/line.nim b/src/types/line.nim new file mode 100644 index 00000000..31a639f5 --- /dev/null +++ b/src/types/line.nim @@ -0,0 +1,66 @@ +import types/vector + +type + Line* = object + p0*: Vector2D + p1*: Vector2D + + LineSegment* = object + line: Line + miny*: float64 + maxy*: float64 + minyx*: float64 + islope*: float64 + +func minx*(line: Line): float64 = + return min(line.p0.x, line.p1.x) + +func maxx*(line: Line): float64 = + return max(line.p0.x, line.p1.x) + +func minyx*(line: Line): float64 = + if line.p0.y < line.p1.y: + return line.p0.x + return line.p1.x + +func maxyx*(line: Line): float64 = + if line.p0.y > line.p1.y: + return line.p0.x + return line.p1.x + +func miny*(line: Line): float64 = + return min(line.p0.y, line.p1.y) + +func maxy*(line: Line): float64 = + return max(line.p0.y, line.p1.y) + +func slope*(line: Line): float64 = + let xdiff = (line.p0.x - line.p1.x) + if xdiff == 0: + return 0 + return (line.p0.y - line.p1.y) / xdiff + +# inverse slope +func islope*(line: Line): float64 = + let ydiff = (line.p0.y - line.p1.y) + if ydiff == 0: + return 0 + return (line.p0.x - line.p1.x) / ydiff + +proc cmpLineSegmentY*(l1, l2: LineSegment): int = + return cmp(l1.miny, l2.miny) + +proc cmpLineSegmentX*(l1, l2: LineSegment): int = + return cmp(l1.minyx, l2.minyx) + +func p0*(ls: LineSegment): Vector2D {.inline.} = ls.line.p0 +func p1*(ls: LineSegment): Vector2D {.inline.} = ls.line.p1 + +converter toLineSegment*(line: Line): LineSegment = + LineSegment( + line: line, + miny: line.miny, + maxy: line.maxy, + minyx: line.minyx, + islope: line.islope + ) diff --git a/src/types/matrix.nim b/src/types/matrix.nim new file mode 100644 index 00000000..17ff3c0d --- /dev/null +++ b/src/types/matrix.nim @@ -0,0 +1,46 @@ +type Matrix* = object + me*: seq[float64] + w: int + h: int + +proc newMatrix*(me: seq[float64], w: int, h: int): Matrix = + return Matrix( + me: me, + w: w, + h: h + ) + +proc newIdentityMatrix*(n: int): Matrix = + var me = newSeq[float64](n * n) + for i in 0 ..< n: + me[n * i + i] = 1 + return Matrix( + me: me, + w: n, + h: n + ) + +proc newMatrixUninitialized*(w, h: int): Matrix = + return Matrix( + me: newSeqUninitialized[float64](w * h), + w: w, + h: h + ) + +#TODO this is extremely inefficient +proc `*`*(a: Matrix, b: Matrix): Matrix = + assert a.w == b.h + let h = a.h + let w = b.w + let n = a.w + var c = newMatrixUninitialized(w, h) + for x in 0 ..< w: + for y in 0 ..< h: + var val: float64 = 0 + for i in 0 ..< n: + val += a.me[y * a.w + i] * b.me[i * b.w + x] + c.me[y * c.w + x] = val + return c + +proc `*=`*(a: var Matrix, b: Matrix) = + a = a * b diff --git a/src/types/mime.nim b/src/types/mime.nim index 2db567ad..cd4c2d51 100644 --- a/src/types/mime.nim +++ b/src/types/mime.nim @@ -12,12 +12,12 @@ const DefaultGuess = [ ("", "text/plain") ].toTable() -proc guessContentType*(path: string): string = +proc guessContentType*(path: string, def = DefaultGuess[""]): string = var i = path.len - 1 var n = 0 while i > 0: if path[i] == '/': - return DefaultGuess[""] + return def if path[i] == '.': n = i break @@ -26,7 +26,7 @@ proc guessContentType*(path: string): string = let ext = path.substr(n + 1) if ext in DefaultGuess: return DefaultGuess[ext] - return DefaultGuess[""] + return def const JavaScriptTypes = [ "application/ecmascript", diff --git a/src/types/url.nim b/src/types/url.nim index 10b91fe5..cba83adb 100644 --- a/src/types/url.nim +++ b/src/types/url.nim @@ -6,6 +6,7 @@ import unicode import math import js/javascript +import types/blob import utils/twtstr type @@ -17,10 +18,8 @@ type RELATIVE_SLASH_STATE, QUERY_STATE, HOST_STATE, HOSTNAME_STATE, FILE_HOST_STATE, PORT_STATE, PATH_START_STATE, FILE_SLASH_STATE - Blob* = object - BlobUrlEntry* = object - obj: Blob #TODO + obj: Blob #TODO blob urls UrlPath* = object case opaque*: bool @@ -570,10 +569,11 @@ proc basicParseUrl*(input: string, base = none(Url), url: Url = Url(), stateOver (url.is_special and c == '\\') or override: if buffer != "": let i = parseInt32(buffer) - if i notin 0..65535: + if i.isNone or i.get notin 0..65535: + eprint "validation error???", i #TODO validation error return none(Url) - let port = cast[uint16](i).some + let port = cast[uint16](i.get).some url.port = if url.is_special and url.default_port == port: none(uint16) else: port buffer = "" if override: diff --git a/src/types/vector.nim b/src/types/vector.nim new file mode 100644 index 00000000..e200cc98 --- /dev/null +++ b/src/types/vector.nim @@ -0,0 +1,53 @@ +import math + +type Vector2D* = object + x*: float64 + y*: float64 + +func `-`*(v1, v2: Vector2D): Vector2D = + return Vector2D(x: v1.x - v2.x, y: v1.y - v2.y) + +func `+`*(v1, v2: Vector2D): Vector2D = + return Vector2D(x: v1.x + v2.x, y: v1.y + v2.y) + +proc `+=`*(v1: var Vector2D, v2: Vector2D) = + v1.x += v2.x + v1.y += v2.y + +proc `-=`*(v1: var Vector2D, v2: Vector2D) = + v1.x -= v2.x + v1.y -= v2.y + +# scalar multiplication +func `*`*(v: Vector2D, s: float64): Vector2D = + return Vector2D(x: v.x * s, y: v.y * s) + +func `/`*(v: Vector2D, s: float64): Vector2D = + return Vector2D(x: v.x / s, y: v.y / s) + +# dot product +func `*`*(v1, v2: Vector2D): float64 = + return v1.x * v2.x + v1.y * v2.y + +func norm*(v: Vector2D): float64 = + return sqrt(v.x * v.x + v.y * v.y) + +# kind of a cross product? +func cross*(v1, v2: Vector2D): float64 = + return v1.x * v2.y - v1.y * v2.x + +# https://en.wikipedia.org/wiki/Inner_product_space +func innerAngle*(v1, v2: Vector2D): float64 = + return arccos((v1 * v2) / (v1.norm() * v2.norm())) + +func rotate*(v: Vector2D, alpha: float64): Vector2D = + let sa = sin(alpha) + let ca = cos(alpha) + return Vector2D( + x: v.x * ca - v.y * sa, + y: v.x * sa + v.y * ca + ) + +func collinear*(v1, v2, v3: Vector2D): bool = + return almostEqual((v1.y - v2.y) * (v1.x - v3.x), + (v1.y - v3.y) * (v1.x - v2.x)) |