about summary refs log tree commit diff stats
path: root/src/types
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2023-06-02 00:36:54 +0200
committerbptato <nincsnevem662@gmail.com>2023-06-05 03:58:21 +0200
commit8027e52cb221c432bed64517015ebf3182e6166d (patch)
tree18991f9e74c8dcfc0ed7439f3bc78a0cfec9b2d6 /src/types
parentb3b97465805b7367df461a4b7b830fabaccf3a89 (diff)
downloadchawan-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.nim61
-rw-r--r--src/types/color.nim103
-rw-r--r--src/types/cookie.nim4
-rw-r--r--src/types/formdata.nim18
-rw-r--r--src/types/line.nim66
-rw-r--r--src/types/matrix.nim46
-rw-r--r--src/types/mime.nim6
-rw-r--r--src/types/url.nim10
-rw-r--r--src/types/vector.nim53
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))