about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2024-09-15 18:13:00 +0200
committerbptato <nincsnevem662@gmail.com>2024-09-15 19:13:00 +0200
commit9f453ca3997528252eb28268e38480f58fbce4f6 (patch)
tree3c4f5658e729052855cad5eea15c22a32d3f7b67
parentbf721f2e604974b5e40dc2dc099c899f0ded2798 (diff)
downloadchawan-9f453ca3997528252eb28268e38480f58fbce4f6.tar.gz
Refactor img/*
I've moved most image logic to adapter, so it doesn't really make
sense to have this subdir anymore.
-rw-r--r--Makefile6
-rw-r--r--adapter/img/canvas.nim230
-rw-r--r--doc/architecture.md2
-rw-r--r--src/css/cssvalues.nim2
-rw-r--r--src/html/dom.nim16
-rw-r--r--src/img/bitmap.nim39
-rw-r--r--src/img/painter.nim211
-rw-r--r--src/layout/box.nim2
-rw-r--r--src/layout/engine.nim2
-rw-r--r--src/layout/renderdocument.nim2
-rw-r--r--src/local/container.nim2
-rw-r--r--src/local/pager.nim2
-rw-r--r--src/types/bitmap.nim7
-rw-r--r--src/types/canvastypes.nim15
-rw-r--r--src/types/path.nim (renamed from src/img/path.nim)0
15 files changed, 264 insertions, 274 deletions
diff --git a/Makefile b/Makefile
index adcc30c8..eb9ddf08 100644
--- a/Makefile
+++ b/Makefile
@@ -112,9 +112,9 @@ $(OUTDIR_CGI_BIN)/stbi: adapter/img/stbi.nim adapter/img/stb_image.c \
 $(OUTDIR_CGI_BIN)/jebp: adapter/img/jebp.c adapter/img/jebp.h \
 		src/utils/sandbox.nim
 $(OUTDIR_CGI_BIN)/sixel: src/types/color.nim src/utils/sandbox.nim $(twtstr) $(dynstream)
-$(OUTDIR_CGI_BIN)/canvas: src/img/bitmap.nim src/img/painter.nim \
-	src/img/path.nim src/io/bufreader.nim src/types/color.nim \
-	src/types/line.nim src/utils/sandbox.nim $(dynstream) $(twtstr)
+$(OUTDIR_CGI_BIN)/canvas: src/types/canvastypes.nim src/types/path.nim \
+	src/io/bufreader.nim src/types/color.nim src/types/line.nim \
+	src/utils/sandbox.nim $(dynstream) $(twtstr)
 $(OUTDIR_LIBEXEC)/urlenc: $(twtstr)
 $(OUTDIR_LIBEXEC)/gopher2html: adapter/gophertypes.nim $(twtstr)
 $(OUTDIR_LIBEXEC)/ansi2html: src/types/color.nim $(twtstr)
diff --git a/adapter/img/canvas.nim b/adapter/img/canvas.nim
index 32d6d37a..cfa2cd32 100644
--- a/adapter/img/canvas.nim
+++ b/adapter/img/canvas.nim
@@ -6,18 +6,20 @@
 # and read it with stbi. (TODO: try switching to a more efficient format
 # like qemacs fbf.)
 
+import std/algorithm
 import std/os
 import std/posix
 import std/strutils
 
-import img/bitmap
-import img/painter
-import img/path
+import types/path
 import io/bufreader
 import io/dynstream
+import types/canvastypes
 import types/color
 import types/line
+import types/vector
 import utils/sandbox
+import utils/twtuni
 
 {.compile: "canvas.c".}
 
@@ -29,13 +31,48 @@ proc stbi_load_from_memory(buffer: ptr uint8; len: cint; x, y, comp: ptr cint;
 proc stbi_image_free(retval_from_stbi_load: pointer)
 {.pop.}
 
+type
+  GlyphCacheItem = object
+    u: uint32
+    bmp: Bitmap
+
+  Bitmap = ref object
+    px: seq[RGBAColorBE]
+    width: int
+    height: int
+
+proc newBitmap(width, height: int): Bitmap =
+  return Bitmap(
+    px: newSeq[RGBAColorBE](width * height),
+    width: width,
+    height: height
+  )
+
+proc setpx(bmp: Bitmap; x, y: int; color: RGBAColorBE) {.inline.} =
+  bmp.px[bmp.width * y + x] = color
+
+proc setpx(bmp: Bitmap; x, y: int; color: ARGBColor) {.inline.} =
+  bmp.px[bmp.width * y + x] = rgba_be(color.r, color.g, color.b, color.a)
+
+proc getpx(bmp: Bitmap; x, y: int): RGBAColorBE {.inline.} =
+  return bmp.px[bmp.width * y + x]
+
+proc setpxb(bmp: Bitmap; x, y: int; c: RGBAColorBE) {.inline.} =
+  if c.a == 255:
+    bmp.setpx(x, y, c)
+  else:
+    bmp.setpx(x, y, bmp.getpx(x, y).blend(c))
+
+proc setpxb(bmp: Bitmap; x, y: int; c: ARGBColor) {.inline.} =
+  bmp.setpxb(x, y, rgba_be(c.r, c.g, c.b, c.a))
+
 const unifont = readFile"res/unifont_jp-15.0.05.png"
-proc loadUnifont(unifont: string): ImageBitmap =
+proc loadUnifont(unifont: string): Bitmap =
   var width, height, comp: cint
   let p = stbi_load_from_memory(cast[ptr uint8](unsafeAddr unifont[0]),
     cint(unifont.len), addr width, addr height, addr comp, 4)
   let len = width * height
-  let bitmap = ImageBitmap(
+  let bitmap = Bitmap(
     px: cast[seq[RGBAColorBE]](newSeqUninitialized[uint32](len)),
     width: int(width),
     height: int(height)
@@ -44,6 +81,189 @@ proc loadUnifont(unifont: string): ImageBitmap =
   stbi_image_free(p)
   return bitmap
 
+# https://en.wikipedia.org/wiki/Bresenham's_line_algorithm#All_cases
+proc plotLineLow(bmp: Bitmap; x1, y1, x2, y2: int; color: ARGBColor) =
+  var dx = x2 - x1
+  var dy = y2 - y1
+  var yi = 1
+  if dy < 0:
+    yi = -1
+    dy = -dy
+  var D = 2 * dy - dx;
+  var y = y1;
+  for x in x1 ..< x2:
+    if x < 0 or y < 0 or x >= bmp.width or y >= bmp.height:
+      break
+    bmp.setpxb(x, y, color)
+    if D > 0:
+       y = y + yi;
+       D = D - 2 * dx;
+    D = D + 2 * dy;
+
+proc plotLineHigh(bmp: Bitmap; x1, y1, x2, y2: int; color: ARGBColor) =
+  var dx = x2 - x1
+  var dy = y2 - y1
+  var xi = 1
+  if dx < 0:
+    xi = -1
+    dx = -dx
+  var D = 2 * dx - dy
+  var x = x1
+  for y in y1 ..< y2:
+    if x < 0 or y < 0 or x >= bmp.width or y >= bmp.height:
+      break
+    bmp.setpxb(x, y, color)
+    if D > 0:
+       x = x + xi
+       D = D - 2 * dy
+    D = D + 2 * dx
+
+proc plotLine(bmp: Bitmap; x1, y1, x2, y2: int; color: ARGBColor) =
+  if abs(y2 - y1) < abs(x2 - x1):
+    if x1 > x2:
+      bmp.plotLineLow(x2, y2, x1, y1, color)
+    else:
+      bmp.plotLineLow(x1, y1, x2, y2, color)
+  else:
+    if y1 > y2:
+      bmp.plotLineHigh(x2, y2, x1, y1, color)
+    else:
+      bmp.plotLineHigh(x1, y1, x2, y2, color)
+
+proc plotLine(bmp: Bitmap; a, b: Vector2D; color: ARGBColor) =
+  bmp.plotLine(int(a.x), int(a.y), int(b.x), int(b.y), color)
+
+proc plotLine(bmp: Bitmap; line: Line; color: ARGBColor) =
+  bmp.plotLine(line.p0, line.p1, color)
+
+proc strokePath(bmp: Bitmap; lines: seq[Line]; color: ARGBColor) =
+  for line in lines:
+    bmp.plotLine(line, color)
+
+func isInside(windingNumber: int; fillRule: CanvasFillRule): bool =
+  return case fillRule
+  of cfrNonZero: windingNumber != 0
+  of cfrEvenOdd: windingNumber mod 2 == 0
+
+# Algorithm originally from SerenityOS.
+proc fillPath(bmp: Bitmap; lines: PathLines; color: ARGBColor;
+    fillRule: CanvasFillRule) =
+  var i = 0
+  var ylines: seq[LineSegment] = @[]
+  for y in int(lines.miny) .. int(lines.maxy):
+    for k in countdown(ylines.high, 0):
+      if ylines[k].maxy < float64(y):
+        ylines.del(k) # we'll sort anyways, so del is fine
+    for j in i ..< lines.len:
+      if lines[j].miny > float64(y):
+        break
+      if lines[j].maxy > float64(y):
+        ylines.add(lines[j])
+      inc i
+    ylines.sort(cmpLineSegmentX)
+    var w = if fillRule == cfrNonZero: 1 else: 0
+    for k in 0 ..< ylines.high:
+      let a = ylines[k]
+      let b = ylines[k + 1]
+      let sx = int(a.minyx)
+      let ex = int(b.minyx)
+      if w.isInside(fillRule) and y > 0:
+        for x in sx .. ex:
+          if x > 0:
+            bmp.setpxb(x, y, color)
+      if int(a.p0.y) != y and int(a.p1.y) != y and int(b.p0.y) != y and
+          int(b.p1.y) != y and sx != ex or a.islope * b.islope < 0:
+        case fillRule
+        of cfrEvenOdd: inc w
+        of cfrNonZero:
+          if a.p0.y < a.p1.y:
+            inc w
+          else:
+            dec w
+      ylines[k].minyx += ylines[k].islope
+    if ylines.len > 0:
+      ylines[^1].minyx += ylines[^1].islope
+
+proc fillRect(bmp: Bitmap; x1, y1, x2, y2: int; color: ARGBColor) =
+  for y in y1 ..< y2:
+    for x in x1 ..< x2:
+      bmp.setpxb(x, y, color)
+
+proc strokeRect(bmp: Bitmap; x1, y1, x2, y2: int; color: ARGBColor) =
+  for x in x1 ..< x2:
+    bmp.setpxb(x, y1, color)
+    bmp.setpxb(x, y2, color)
+  for y in y1 ..< y2:
+    bmp.setpxb(x1, y, color)
+    bmp.setpxb(x2, y, color)
+
+var unifontBitmap: Bitmap = nil
+var glyphCache: seq[GlyphCacheItem] = @[]
+var glyphCacheI = 0
+proc getCharBmp(u: uint32): Bitmap =
+  # We only have the BMP.
+  let u = if u <= 0xFFFF: u else: 0xFFFD
+  for it in glyphCache:
+    if it.u == u:
+      return it.bmp
+  # Unifont glyphs start at x: 32, y: 64, and are of 8x16/16x16 size
+  let gx = int(32 + 16 * (u mod 0x100))
+  let gy = int(64 + 16 * (u div 0x100))
+  var fullwidth = false
+  const white = rgba_be(255, 255, 255, 255)
+  block loop:
+    # hack to recognize full width characters
+    for y in 0 ..< 16:
+      for x in 8 ..< 16:
+        if unifontBitmap.getpx(gx + x, gy + y) != white:
+          fullwidth = true
+          break loop
+  let bmp = newBitmap(if fullwidth: 16 else: 8, 16)
+  for y in 0 ..< bmp.height:
+    for x in 0 ..< bmp.width:
+      let c = unifontBitmap.getpx(gx + x, gy + y)
+      if c != white:
+        bmp.setpx(x, y, c)
+  if glyphCache.len < 256:
+    glyphCache.add(GlyphCacheItem(u: u, bmp: bmp))
+  else:
+    glyphCache[glyphCacheI] = GlyphCacheItem(u: u, bmp: bmp)
+    inc glyphCacheI
+    if glyphCacheI >= glyphCache.len:
+      glyphCacheI = 0
+  return bmp
+
+proc drawBitmap(a, b: Bitmap; p: Vector2D) =
+  for y in 0 ..< b.height:
+    for x in 0 ..< b.width:
+      let ax = int(p.x) + x
+      let ay = int(p.y) + y
+      if ax >= 0 and ay >= y and ax < a.width and ay < a.height:
+        a.setpxb(ax, ay, b.getpx(x, y))
+
+proc fillText(bmp: Bitmap; text: string; x, y: float64; color: ARGBColor;
+    textAlign: CanvasTextAlign) =
+  var w = 0f64
+  var glyphs: seq[Bitmap] = @[]
+  for u in text.points:
+    let glyph = getCharBmp(u)
+    glyphs.add(glyph)
+    w += float64(glyph.width)
+  var x = x
+  #TODO rtl
+  case textAlign
+  of ctaLeft, ctaStart: discard
+  of ctaRight, ctaEnd: x -= w
+  of ctaCenter: x -= w / 2
+  for glyph in glyphs:
+    bmp.drawBitmap(glyph, Vector2D(x: x, y: y - 8))
+    x += float64(glyph.width)
+
+proc strokeText(bmp: Bitmap; text: string; x, y: float64; color: ARGBColor;
+    textAlign: CanvasTextAlign) =
+  #TODO
+  bmp.fillText(text, x, y, color, textAlign)
+
 proc main() =
   enterNetworkSandbox()
   let os = newPosixStream(STDOUT_FILENO)
diff --git a/doc/architecture.md b/doc/architecture.md
index 615fd304..7ad3a113 100644
--- a/doc/architecture.md
+++ b/doc/architecture.md
@@ -30,8 +30,6 @@ Explanation for the separate directories found in `src/`:
 * css: styling-related code; CSS parsing and cascading.
 * html: DOM building, DOM functions, the DOM itself, forms, etc. Note that it
   does not include the [HTML parser](https://git.sr.ht/~bptato/chame) itself.
-* img: image-related code. Mostly useless because we can't draw images to the
-  screen yet. (One day...)
 * io: code for IPC, interaction with the file system, etc.
 * js: modules mainly for use by JS code.
 * layout: the layout engine and its renderer.
diff --git a/src/css/cssvalues.nim b/src/css/cssvalues.nim
index a0bb1f1f..3faf13e5 100644
--- a/src/css/cssvalues.nim
+++ b/src/css/cssvalues.nim
@@ -6,7 +6,7 @@ import std/tables
 
 import css/cssparser
 import css/selectorparser
-import img/bitmap
+import types/bitmap
 import layout/layoutunit
 import types/color
 import types/opt
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 146056a9..9275538c 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -19,9 +19,8 @@ import html/catom
 import html/enums
 import html/event
 import html/script
-import img/bitmap
-import img/painter
-import img/path
+import types/bitmap
+import types/path
 import io/bufwriter
 import io/dynstream
 import io/promise
@@ -40,6 +39,7 @@ import monoucha/jsutils
 import monoucha/quickjs
 import monoucha/tojs
 import types/blob
+import types/canvastypes
 import types/color
 import types/line
 import types/matrix
@@ -63,10 +63,10 @@ type
     fetMultipart = "multipart/form-data",
     fetTextPlain = "text/plain"
 
-type DocumentReadyState* = enum
-  rsLoading = "loading"
-  rsInteractive = "interactive"
-  rsComplete = "complete"
+  DocumentReadyState* = enum
+    rsLoading = "loading"
+    rsInteractive = "interactive"
+    rsComplete = "complete"
 
 type
   DependencyType* = enum
@@ -368,7 +368,7 @@ type
 
   CanvasRenderingContext2D = ref object of RenderingContext
     canvas {.jsget.}: HTMLCanvasElement
-    bitmap: Bitmap
+    bitmap: NetworkBitmap
     state: DrawingState
     stateStack: seq[DrawingState]
     ps*: PosixStream
diff --git a/src/img/bitmap.nim b/src/img/bitmap.nim
deleted file mode 100644
index 343e9ecf..00000000
--- a/src/img/bitmap.nim
+++ /dev/null
@@ -1,39 +0,0 @@
-import types/color
-
-type
-  Bitmap* = ref object of RootObj
-    px*: seq[RGBAColorBE]
-    width*: int
-    height*: int
-
-  ImageBitmap* = ref object of Bitmap
-
-  NetworkBitmap* = ref object of Bitmap
-    cacheId*: int
-    imageId*: int
-    contentType*: string
-
-proc newBitmap*(width, height: int): ImageBitmap =
-  return ImageBitmap(
-    px: newSeq[RGBAColorBE](width * height),
-    width: width,
-    height: height
-  )
-
-proc setpx*(bmp: Bitmap; x, y: int; color: RGBAColorBE) {.inline.} =
-  bmp.px[bmp.width * y + x] = color
-
-proc setpx*(bmp: Bitmap; x, y: int; color: ARGBColor) {.inline.} =
-  bmp.px[bmp.width * y + x] = rgba_be(color.r, color.g, color.b, color.a)
-
-proc getpx*(bmp: Bitmap; x, y: int): RGBAColorBE {.inline.} =
-  return bmp.px[bmp.width * y + x]
-
-proc setpxb*(bmp: Bitmap; x, y: int; c: RGBAColorBE) {.inline.} =
-  if c.a == 255:
-    bmp.setpx(x, y, c)
-  else:
-    bmp.setpx(x, y, bmp.getpx(x, y).blend(c))
-
-proc setpxb*(bmp: Bitmap; x, y: int; c: ARGBColor) {.inline.} =
-  bmp.setpxb(x, y, rgba_be(c.r, c.g, c.b, c.a))
diff --git a/src/img/painter.nim b/src/img/painter.nim
deleted file mode 100644
index 40f2c79a..00000000
--- a/src/img/painter.nim
+++ /dev/null
@@ -1,211 +0,0 @@
-import std/algorithm
-
-import img/bitmap
-import img/path
-import types/color
-import types/line
-import types/vector
-import utils/twtuni
-
-type
-  CanvasFillRule* = enum
-    cfrNonZero = "nonzero"
-    cfrEvenOdd = "evenodd"
-
-  PaintCommand* = enum
-    pcSetDimensions, pcFillRect, pcStrokeRect, pcFillPath, pcStrokePath,
-    pcFillText, pcStrokeText
-
-  CanvasTextAlign* = enum
-    ctaStart = "start"
-    ctaEnd = "end"
-    ctaLeft = "left"
-    ctaRight = "right"
-    ctaCenter = "center"
-
-# https://en.wikipedia.org/wiki/Bresenham's_line_algorithm#All_cases
-proc plotLineLow(bmp: Bitmap; x1, y1, x2, y2: int; color: ARGBColor) =
-  var dx = x2 - x1
-  var dy = y2 - y1
-  var yi = 1
-  if dy < 0:
-    yi = -1
-    dy = -dy
-  var D = 2 * dy - dx;
-  var y = y1;
-  for x in x1 ..< x2:
-    if x < 0 or y < 0 or x >= bmp.width or y >= bmp.height:
-      break
-    bmp.setpxb(x, y, color)
-    if D > 0:
-       y = y + yi;
-       D = D - 2 * dx;
-    D = D + 2 * dy;
-
-proc plotLineHigh(bmp: Bitmap; x1, y1, x2, y2: int; color: ARGBColor) =
-  var dx = x2 - x1
-  var dy = y2 - y1
-  var xi = 1
-  if dx < 0:
-    xi = -1
-    dx = -dx
-  var D = 2 * dx - dy
-  var x = x1
-  for y in y1 ..< y2:
-    if x < 0 or y < 0 or x >= bmp.width or y >= bmp.height:
-      break
-    bmp.setpxb(x, y, color)
-    if D > 0:
-       x = x + xi
-       D = D - 2 * dy
-    D = D + 2 * dx
-
-proc plotLine(bmp: Bitmap; x1, y1, x2, y2: int; color: ARGBColor) =
-  if abs(y2 - y1) < abs(x2 - x1):
-    if x1 > x2:
-      bmp.plotLineLow(x2, y2, x1, y1, color)
-    else:
-      bmp.plotLineLow(x1, y1, x2, y2, color)
-  else:
-    if y1 > y2:
-      bmp.plotLineHigh(x2, y2, x1, y1, color)
-    else:
-      bmp.plotLineHigh(x1, y1, x2, y2, color)
-
-proc plotLine(bmp: Bitmap; a, b: Vector2D; color: ARGBColor) =
-  bmp.plotLine(int(a.x), int(a.y), int(b.x), int(b.y), color)
-
-proc plotLine(bmp: Bitmap; line: Line; color: ARGBColor) =
-  bmp.plotLine(line.p0, line.p1, color)
-
-proc strokePath*(bmp: Bitmap; lines: seq[Line]; color: ARGBColor) =
-  for line in lines:
-    bmp.plotLine(line, color)
-
-func isInside(windingNumber: int; fillRule: CanvasFillRule): bool =
-  return case fillRule
-  of cfrNonZero: windingNumber != 0
-  of cfrEvenOdd: windingNumber mod 2 == 0
-
-# Algorithm originally from SerenityOS.
-proc fillPath*(bmp: Bitmap; lines: PathLines; color: ARGBColor;
-    fillRule: CanvasFillRule) =
-  var i = 0
-  var ylines: seq[LineSegment] = @[]
-  for y in int(lines.miny) .. int(lines.maxy):
-    for k in countdown(ylines.high, 0):
-      if ylines[k].maxy < float64(y):
-        ylines.del(k) # we'll sort anyways, so del is fine
-    for j in i ..< lines.len:
-      if lines[j].miny > float64(y):
-        break
-      if lines[j].maxy > float64(y):
-        ylines.add(lines[j])
-      inc i
-    ylines.sort(cmpLineSegmentX)
-    var w = if fillRule == cfrNonZero: 1 else: 0
-    for k in 0 ..< ylines.high:
-      let a = ylines[k]
-      let b = ylines[k + 1]
-      let sx = int(a.minyx)
-      let ex = int(b.minyx)
-      if w.isInside(fillRule) and y > 0:
-        for x in sx .. ex:
-          if x > 0:
-            bmp.setpxb(x, y, color)
-      if int(a.p0.y) != y and int(a.p1.y) != y and int(b.p0.y) != y and
-          int(b.p1.y) != y and sx != ex or a.islope * b.islope < 0:
-        case fillRule
-        of cfrEvenOdd: inc w
-        of cfrNonZero:
-          if a.p0.y < a.p1.y:
-            inc w
-          else:
-            dec w
-      ylines[k].minyx += ylines[k].islope
-    if ylines.len > 0:
-      ylines[^1].minyx += ylines[^1].islope
-
-proc fillRect*(bmp: Bitmap; x1, y1, x2, y2: int; color: ARGBColor) =
-  for y in y1 ..< y2:
-    for x in x1 ..< x2:
-      bmp.setpxb(x, y, color)
-
-proc strokeRect*(bmp: Bitmap; x1, y1, x2, y2: int; color: ARGBColor) =
-  for x in x1 ..< x2:
-    bmp.setpxb(x, y1, color)
-    bmp.setpxb(x, y2, color)
-  for y in y1 ..< y2:
-    bmp.setpxb(x1, y, color)
-    bmp.setpxb(x2, y, color)
-
-type GlyphCacheItem = object
-  u: uint32
-  bmp: Bitmap
-
-var unifontBitmap*: Bitmap = nil
-var glyphCache: seq[GlyphCacheItem] = @[]
-var glyphCacheI = 0
-proc getCharBmp(u: uint32): Bitmap =
-  # We only have the BMP.
-  let u = if u <= 0xFFFF: u else: 0xFFFD
-  for it in glyphCache:
-    if it.u == u:
-      return it.bmp
-  # Unifont glyphs start at x: 32, y: 64, and are of 8x16/16x16 size
-  let gx = int(32 + 16 * (u mod 0x100))
-  let gy = int(64 + 16 * (u div 0x100))
-  var fullwidth = false
-  const white = rgba_be(255, 255, 255, 255)
-  block loop:
-    # hack to recognize full width characters
-    for y in 0 ..< 16:
-      for x in 8 ..< 16:
-        if unifontBitmap.getpx(gx + x, gy + y) != white:
-          fullwidth = true
-          break loop
-  let bmp = newBitmap(if fullwidth: 16 else: 8, 16)
-  for y in 0 ..< bmp.height:
-    for x in 0 ..< bmp.width:
-      let c = unifontBitmap.getpx(gx + x, gy + y)
-      if c != white:
-        bmp.setpx(x, y, c)
-  if glyphCache.len < 256:
-    glyphCache.add(GlyphCacheItem(u: u, bmp: bmp))
-  else:
-    glyphCache[glyphCacheI] = GlyphCacheItem(u: u, bmp: bmp)
-    inc glyphCacheI
-    if glyphCacheI >= glyphCache.len:
-      glyphCacheI = 0
-  return bmp
-
-proc drawBitmap(a, b: Bitmap; p: Vector2D) =
-  for y in 0 ..< b.height:
-    for x in 0 ..< b.width:
-      let ax = int(p.x) + x
-      let ay = int(p.y) + y
-      if ax >= 0 and ay >= y and ax < a.width and ay < a.height:
-        a.setpxb(ax, ay, b.getpx(x, y))
-
-proc fillText*(bmp: Bitmap; text: string; x, y: float64; color: ARGBColor;
-    textAlign: CanvasTextAlign) =
-  var w = 0f64
-  var glyphs: seq[Bitmap] = @[]
-  for u in text.points:
-    let glyph = getCharBmp(u)
-    glyphs.add(glyph)
-    w += float64(glyph.width)
-  var x = x
-  #TODO rtl
-  case textAlign
-  of ctaLeft, ctaStart: discard
-  of ctaRight, ctaEnd: x -= w
-  of ctaCenter: x -= w / 2
-  for glyph in glyphs:
-    bmp.drawBitmap(glyph, Vector2D(x: x, y: y - 8))
-    x += float64(glyph.width)
-
-proc strokeText*(bmp: Bitmap; text: string; x, y: float64; color: ARGBColor;
-    textAlign: CanvasTextAlign) =
-  #TODO
-  bmp.fillText(text, x, y, color, textAlign)
diff --git a/src/layout/box.nim b/src/layout/box.nim
index cd94ca49..b9d18f79 100644
--- a/src/layout/box.nim
+++ b/src/layout/box.nim
@@ -1,6 +1,6 @@
 import css/cssvalues
 import css/stylednode
-import img/bitmap
+import types/bitmap
 import layout/layoutunit
 
 type
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index 831df62e..8ba12729 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -3,7 +3,7 @@ import std/math
 
 import css/cssvalues
 import css/stylednode
-import img/bitmap
+import types/bitmap
 import layout/box
 import layout/layoutunit
 import types/winattrs
diff --git a/src/layout/renderdocument.nim b/src/layout/renderdocument.nim
index 2d1df349..9622d305 100644
--- a/src/layout/renderdocument.nim
+++ b/src/layout/renderdocument.nim
@@ -2,7 +2,7 @@ import std/strutils
 
 import css/cssvalues
 import css/stylednode
-import img/bitmap
+import types/bitmap
 import layout/box
 import layout/engine
 import layout/layoutunit
diff --git a/src/local/container.nim b/src/local/container.nim
index 54d9bedd..c6907982 100644
--- a/src/local/container.nim
+++ b/src/local/container.nim
@@ -7,7 +7,7 @@ import std/tables
 import chagashi/charset
 import config/config
 import config/mimetypes
-import img/bitmap
+import types/bitmap
 import io/dynstream
 import io/promise
 import io/serversocket
diff --git a/src/local/pager.nim b/src/local/pager.nim
index c0034a7f..8b449f24 100644
--- a/src/local/pager.nim
+++ b/src/local/pager.nim
@@ -12,7 +12,7 @@ import chagashi/charset
 import config/chapath
 import config/config
 import config/mailcap
-import img/bitmap
+import types/bitmap
 import io/bufreader
 import io/dynstream
 import io/promise
diff --git a/src/types/bitmap.nim b/src/types/bitmap.nim
new file mode 100644
index 00000000..55a7794f
--- /dev/null
+++ b/src/types/bitmap.nim
@@ -0,0 +1,7 @@
+type
+  NetworkBitmap* = ref object
+    width*: int
+    height*: int
+    cacheId*: int
+    imageId*: int
+    contentType*: string
diff --git a/src/types/canvastypes.nim b/src/types/canvastypes.nim
new file mode 100644
index 00000000..350d8cbb
--- /dev/null
+++ b/src/types/canvastypes.nim
@@ -0,0 +1,15 @@
+type
+  CanvasFillRule* = enum
+    cfrNonZero = "nonzero"
+    cfrEvenOdd = "evenodd"
+
+  PaintCommand* = enum
+    pcSetDimensions, pcFillRect, pcStrokeRect, pcFillPath, pcStrokePath,
+    pcFillText, pcStrokeText
+
+  CanvasTextAlign* = enum
+    ctaStart = "start"
+    ctaEnd = "end"
+    ctaLeft = "left"
+    ctaRight = "right"
+    ctaCenter = "center"
diff --git a/src/img/path.nim b/src/types/path.nim
index c5db0bea..c5db0bea 100644
--- a/src/img/path.nim
+++ b/src/types/path.nim