From 3c548dd7b9cbf4670f276120ce4b9df121d1f886 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg <guzba8@gmail.com> Date: Sun, 23 May 2021 18:20:16 -0500 Subject: [PATCH 1/4] parse post table --- src/pixie/fontformats/opentype.nim | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/pixie/fontformats/opentype.nim b/src/pixie/fontformats/opentype.nim index bb6353d..477c181 100644 --- a/src/pixie/fontformats/opentype.nim +++ b/src/pixie/fontformats/opentype.nim @@ -303,6 +303,13 @@ type # featureList: FeatureList lookupList: LookupList + PostTable = ref object + version: float32 + italicAngle: float32 + underlinePosition: int16 + underlineThickness: int16 + isFixedPitch: uint32 + OpenType* = ref object buf*: string version*: uint32 @@ -322,6 +329,7 @@ type glyf*: GlyfTable kern*: KernTable gpos*: GposTable + post*: PostTable glyphPaths: Table[Rune, Path] when defined(release): @@ -1194,6 +1202,16 @@ proc parseGposTable(buf: string, offset: int): GPOSTable = result.lookupList = parseLookupList(buf, offset + result.lookupListOffset.int, result) +proc parsePostTable(buf: string, offset: int): PostTable = + buf.eofCheck(offset + 14) + + result = PostTable() + result.version = buf.readFixed32(offset + 0) + result.italicAngle = buf.readFixed32(offset + 4) + result.underlinePosition = buf.readInt16(offset + 8).swap() + result.underlineThickness = buf.readInt16(offset + 10).swap() + result.isFixedPitch = buf.readUint32(offset + 12).swap() + proc getGlyphId(opentype: OpenType, rune: Rune): uint16 {.inline.} = if rune in opentype.cmap.runeToGlyphId: result = opentype.cmap.runeToGlyphId[rune] @@ -1544,7 +1562,8 @@ proc parseOpenType*(buf: string): OpenType = i += 16 const requiredTables = [ - "cmap", "head", "hhea", "hmtx", "maxp", "name", "OS/2", "loca", "glyf" + "cmap", "head", "hhea", "hmtx", "maxp", "name", "OS/2", "loca", "glyf", + "post" ] for table in requiredTables: if table notin result.tableRecords: @@ -1571,5 +1590,7 @@ proc parseOpenType*(buf: string): OpenType = if "GPOS" in result.tableRecords: result.gpos = parseGposTable(buf, result.tableRecords["GPOS"].offset.int) + result.post = parsePostTable(buf, result.tableRecords["post"].offset.int) + when defined(release): {.pop.} From 145d54c7e6d21a64d7f02baa19cfa8191c6be1b5 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg <guzba8@gmail.com> Date: Sun, 23 May 2021 18:21:02 -0500 Subject: [PATCH 2/4] rm --- src/pixie/images.nim | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pixie/images.nim b/src/pixie/images.nim index 9a9d271..91de331 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -396,10 +396,8 @@ proc blur*( for xx in max(x - radius, image.width) .. x + radius: values += outOfBounds * kernel[xx - x + radius] + blurX.setRgbaUnsafe(y, x, rgbx(values)) - # this would also work (see: `limitations of method call syntax`) - # `mixin rgbx` - # blurX.setRgbaUnsafe(y, x, values.rgbx) # Blur in the Y direction. for y in 0 ..< image.height: From b73b88bb634ff3fc3c5e2f6883194fe5d581b2cd Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg <guzba8@gmail.com> Date: Sun, 23 May 2021 18:56:21 -0500 Subject: [PATCH 3/4] layers --- src/pixie/context.nim | 294 ++++++++++++++++------------- tests/images/context/clip_1d.png | Bin 0 -> 3664 bytes tests/images/context/clip_1e.png | Bin 0 -> 618 bytes tests/images/context/clip_text.png | Bin 0 -> 4569 bytes tests/test_context.nim | 73 +++++++ 5 files changed, 237 insertions(+), 130 deletions(-) create mode 100644 tests/images/context/clip_1d.png create mode 100644 tests/images/context/clip_1e.png create mode 100644 tests/images/context/clip_text.png diff --git a/src/pixie/context.nim b/src/pixie/context.nim index a081e57..1ed8788 100644 --- a/src/pixie/context.nim +++ b/src/pixie/context.nim @@ -18,6 +18,7 @@ type path: Path mat: Mat3 mask: Mask + layer: Image stateStack: seq[ContextState] ContextState = object @@ -29,6 +30,10 @@ type textAlign: HAlignMode mat: Mat3 mask: Mask + layer: Image + + TextMetrics* = object + width*: float32 proc newContext*(image: Image): Context = ## Create a new Context that will draw to the parameter image. @@ -43,6 +48,116 @@ proc newContext*(width, height: int): Context {.inline.} = ## Create a new Context that will draw to a new image of width and height. newContext(newImage(width, height)) +proc state(ctx: Context): ContextState = + result.fillStyle = ctx.fillStyle + result.strokeStyle = ctx.strokeStyle + result.lineWidth = ctx.lineWidth + result.lineCap = ctx.lineCap + result.lineJoin = ctx.lineJoin + result.font = ctx.font + result.textAlign = ctx.textAlign + result.mat = ctx.mat + result.mask = if ctx.mask != nil: ctx.mask.copy() else: nil + +proc save*(ctx: Context) {.inline.} = + ## Saves the entire state of the canvas by pushing the current state onto + ## a stack. + ctx.stateStack.add(ctx.state()) + +proc saveLayer*(ctx: Context) = + var state = ctx.state() + state.layer = ctx.layer + ctx.stateStack.add(state) + ctx.layer = newImage(ctx.image.width, ctx.image.height) + +proc restore*(ctx: Context) = + ## Restores the most recently saved canvas state by popping the top entry + ## in the drawing state stack. If there is no saved state, this method does + ## nothing. + if ctx.stateStack.len == 0: + return + + let + poppedLayer = ctx.layer + poppedMask = ctx.mask + + let state = ctx.stateStack.pop() + ctx.fillStyle = state.fillStyle + ctx.strokeStyle = state.strokeStyle + ctx.lineWidth = state.lineWidth + ctx.lineCap = state.lineCap + ctx.lineJoin = state.lineJoin + ctx.font = state.font + ctx.textAlign = state.textAlign + ctx.mat = state.mat + ctx.mask = state.mask + ctx.layer = state.layer + + if poppedLayer != nil: # If there is a layer being popped + if poppedMask != nil: # If there is a mask, apply it + poppedLayer.draw(poppedMask) + if ctx.layer != nil: # If we popped to another layer, draw to it + ctx.layer.draw(poppedLayer) + else: # Otherwise draw to the root image + ctx.image.draw(poppedLayer) + +proc fill( + ctx: Context, image: Image, path: Path, windingRule: WindingRule +) {.inline.} = + image.fillPath( + path, + ctx.fillStyle, + ctx.mat, + windingRule + ) + +proc stroke(ctx: Context, image: Image, path: Path) {.inline.} = + image.strokePath( + path, + ctx.strokeStyle, + ctx.mat, + ctx.lineWidth, + ctx.lineCap, + ctx.lineJoin + ) + +proc fillText(ctx: Context, image: Image, text: string, at: Vec2) {.inline.} = + if ctx.font.typeface == nil: + raise newException(PixieError, "No font has been set on this Context") + + # Canvas positions text relative to the alphabetic baseline by default + var at = at + at.y -= round(ctx.font.typeface.ascent * ctx.font.scale) + + ctx.font.paint = ctx.fillStyle + + image.fillText( + ctx.font, + text, + ctx.mat * translate(at), + hAlign = ctx.textAlign + ) + +proc strokeText(ctx: Context, image: Image, text: string, at: Vec2) {.inline.} = + if ctx.font.typeface == nil: + raise newException(PixieError, "No font has been set on this Context") + + # Canvas positions text relative to the alphabetic baseline by default + var at = at + at.y -= round(ctx.font.typeface.ascent * ctx.font.scale) + + ctx.font.paint = ctx.strokeStyle + + image.strokeText( + ctx.font, + text, + ctx.mat * translate(at), + ctx.lineWidth, + hAlign = ctx.textAlign, + lineCap = ctx.lineCap, + lineJoin = ctx.lineJoin + ) + proc beginPath*(ctx: Context) {.inline.} = ## Starts a new path by emptying the list of sub-paths. ctx.path = Path() @@ -121,23 +236,14 @@ proc ellipse*(ctx: Context, x, y, rx, ry: float32) {.inline.} = proc fill*(ctx: Context, path: Path, windingRule = wrNonZero) {.inline.} = ## Fills the path with the current fillStyle. - if ctx.mask != nil: - let tmp = newImage(ctx.image.width, ctx.image.height) - tmp.fillPath( - path, - ctx.fillStyle, - ctx.mat, - windingRule - ) - tmp.draw(ctx.mask) - ctx.image.draw(tmp) + if ctx.mask != nil and ctx.layer == nil: + ctx.saveLayer() + ctx.fill(ctx.layer, path, windingRule) + ctx.restore() + elif ctx.layer != nil: + ctx.fill(ctx.layer, path, windingRule) else: - ctx.image.fillPath( - path, - ctx.fillStyle, - ctx.mat, - windingRule - ) + ctx.fill(ctx.image, path, windingRule) proc fill*(ctx: Context, windingRule = wrNonZero) {.inline.} = ## Fills the current path with the current fillStyle. @@ -163,27 +269,14 @@ proc clip*(ctx: Context, windingRule = wrNonZero) {.inline.} = proc stroke*(ctx: Context, path: Path) {.inline.} = ## Strokes (outlines) the current or given path with the current strokeStyle. - if ctx.mask != nil: - let tmp = newImage(ctx.image.width, ctx.image.height) - tmp.strokePath( - path, - ctx.strokeStyle, - ctx.mat, - ctx.lineWidth, - ctx.lineCap, - ctx.lineJoin - ) - tmp.draw(ctx.mask) - ctx.image.draw(tmp) + if ctx.mask != nil and ctx.layer == nil: + ctx.saveLayer() + ctx.stroke(ctx.layer, path) + ctx.restore() + elif ctx.layer != nil: + ctx.stroke(ctx.layer, path) else: - ctx.image.strokePath( - path, - ctx.strokeStyle, - ctx.mat, - ctx.lineWidth, - ctx.lineCap, - ctx.lineJoin - ) + ctx.stroke(ctx.image, path) proc stroke*(ctx: Context) {.inline.} = ## Strokes (outlines) the current or given path with the current strokeStyle. @@ -193,11 +286,18 @@ proc clearRect*(ctx: Context, rect: Rect) = ## Erases the pixels in a rectangular area. var path: Path path.rect(rect) - ctx.image.fillPath( - path, - Paint(kind: pkSolid, color:rgbx(0, 0, 0, 0), blendMode: bmOverwrite), - ctx.mat - ) + if ctx.layer != nil: + ctx.layer.fillPath( + path, + Paint(kind: pkSolid, color:rgbx(0, 0, 0, 0), blendMode: bmOverwrite), + ctx.mat + ) + else: + ctx.image.fillPath( + path, + Paint(kind: pkSolid, color:rgbx(0, 0, 0, 0), blendMode: bmOverwrite), + ctx.mat + ) proc clearRect*(ctx: Context, x, y, width, height: float32) {.inline.} = ## Erases the pixels in a rectangular area. @@ -228,33 +328,14 @@ proc strokeRect*(ctx: Context, x, y, width, height: float32) {.inline.} = proc fillText*(ctx: Context, text: string, at: Vec2) = ## Draws a text string at the specified coordinates, filling the string's ## characters with the current fillStyle - - if ctx.font.typeface == nil: - raise newException(PixieError, "No font has been set on this Context") - - # Canvas positions text relative to the alphabetic baseline by default - var at = at - at.y -= round(ctx.font.typeface.ascent * ctx.font.scale) - - ctx.font.paint = ctx.fillStyle - - if ctx.mask != nil: - let tmp = newImage(ctx.image.width, ctx.image.height) - tmp.fillText( - ctx.font, - text, - ctx.mat * translate(at), - hAlign = ctx.textAlign - ) - tmp.draw(ctx.mask) - ctx.image.draw(tmp) + if ctx.mask != nil and ctx.layer == nil: + ctx.saveLayer() + ctx.fillText(ctx.layer, text, at) + ctx.restore() + elif ctx.layer != nil: + ctx.fillText(ctx.layer, text, at) else: - ctx.image.fillText( - ctx.font, - text, - ctx.mat * translate(at), - hAlign = ctx.textAlign - ) + ctx.fillText(ctx.image, text, at) proc fillText*(ctx: Context, text: string, x, y: float32) {.inline.} = ## Draws the outlines of the characters of a text string at the specified @@ -264,45 +345,29 @@ proc fillText*(ctx: Context, text: string, x, y: float32) {.inline.} = proc strokeText*(ctx: Context, text: string, at: Vec2) = ## Draws the outlines of the characters of a text string at the specified ## coordinates. - - if ctx.font.typeface == nil: - raise newException(PixieError, "No font has been set on this Context") - - # Canvas positions text relative to the alphabetic baseline by default - var at = at - at.y -= round(ctx.font.typeface.ascent * ctx.font.scale) - - ctx.font.paint = ctx.strokeStyle - - if ctx.mask != nil: - let tmp = newImage(ctx.image.width, ctx.image.height) - tmp.strokeText( - ctx.font, - text, - ctx.mat * translate(at), - ctx.lineWidth, - hAlign = ctx.textAlign, - lineCap = ctx.lineCap, - lineJoin = ctx.lineJoin - ) - tmp.draw(ctx.mask) - ctx.image.draw(tmp) + if ctx.mask != nil and ctx.layer == nil: + ctx.saveLayer() + ctx.strokeText(ctx.layer, text, at) + ctx.restore() + elif ctx.layer != nil: + ctx.strokeText(ctx.layer, text, at) else: - ctx.image.strokeText( - ctx.font, - text, - ctx.mat * translate(at), - ctx.lineWidth, - hAlign = ctx.textAlign, - lineCap = ctx.lineCap, - lineJoin = ctx.lineJoin - ) + ctx.strokeText(ctx.image, text, at) proc strokeText*(ctx: Context, text: string, x, y: float32) {.inline.} = ## Draws the outlines of the characters of a text string at the specified ## coordinates. ctx.strokeText(text, vec2(x, y)) +proc measureText*(ctx: Context, text: string): TextMetrics = + ## Returns a TextMetrics object that contains information about the measured + ## text (such as its width, for example). + if ctx.font.typeface == nil: + raise newException(PixieError, "No font has been set on this Context") + + let bounds = typeset(ctx.font, text).computeBounds() + result.width = bounds.x + proc getTransform*(ctx: Context): Mat3 {.inline.} = ## Retrieves the current transform matrix being applied to the context. ctx.mat @@ -351,37 +416,6 @@ proc resetTransform*(ctx: Context) {.inline.} = ## Resets the current transform to the identity matrix. ctx.mat = mat3() -proc save*(ctx: Context) = - ## Saves the entire state of the canvas by pushing the current state onto - ## a stack. - var state: ContextState - state.fillStyle = ctx.fillStyle - state.strokeStyle = ctx.strokeStyle - state.lineWidth = ctx.lineWidth - state.lineCap = ctx.lineCap - state.lineJoin = ctx.lineJoin - state.font = ctx.font - state.textAlign = ctx.textAlign - state.mat = ctx.mat - state.mask = if ctx.mask != nil: ctx.mask.copy() else: nil - ctx.stateStack.add(state) - -proc restore*(ctx: Context) = - ## Restores the most recently saved canvas state by popping the top entry - ## in the drawing state stack. If there is no saved state, this method does - ## nothing. - if ctx.stateStack.len > 0: - let state = ctx.stateStack.pop() - ctx.fillStyle = state.fillStyle - ctx.strokeStyle = state.strokeStyle - ctx.lineWidth = state.lineWidth - ctx.lineCap = state.lineCap - ctx.lineJoin = state.lineJoin - ctx.font = state.font - ctx.textAlign = state.textAlign - ctx.mat = state.mat - ctx.mask = state.mask - # Additional procs that are not part of the JS API proc roundedRect*(ctx: Context, x, y, w, h, nw, ne, se, sw: float32) {.inline.} = diff --git a/tests/images/context/clip_1d.png b/tests/images/context/clip_1d.png new file mode 100644 index 0000000000000000000000000000000000000000..bdee0a9680ee5fe888ed0fdace99749ce6a154c3 GIT binary patch literal 3664 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 literal 0 HcmV?d00001 diff --git a/tests/images/context/clip_1e.png b/tests/images/context/clip_1e.png new file mode 100644 index 0000000000000000000000000000000000000000..c7b514bfb5d35a76698a926f166e57f75428088d GIT binary patch literal 618 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!r*W_W$&~1=mlznB3_V>OLn;{GUI|>r;>hE0 zF+lqKx#x!Y+?T(EJ`ea3-rZ51W;cIk?0LRB4bm0AdFT9RWW49Up@HWhlMxH|2>~4i zXR2_j4lwjv81L@?Bl+MsHO+;(Qi?6@+?jQ*46IG8DI8)G6eAqk5*Q_^!->f*I(H^7 zFYAF9b<9I`ogl|GhMKuE@4gp5aGI0KE(h9FE~Q~*y!+_E#@|E+%OA#j4u>zOxEukc OAqG!ZKbLh*2~7a=hPXQb literal 0 HcmV?d00001 diff --git a/tests/images/context/clip_text.png b/tests/images/context/clip_text.png new file mode 100644 index 0000000000000000000000000000000000000000..b12da69b7c2721de831d1695b08dfcb1ec3626c6 GIT binary patch literal 4569 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 GY5xbZzmiV? literal 0 HcmV?d00001 diff --git a/tests/test_context.nim b/tests/test_context.nim index 605564c..9485de5 100644 --- a/tests/test_context.nim +++ b/tests/test_context.nim @@ -363,6 +363,51 @@ block: ctx.image.writeFile("tests/images/context/clip_1c.png") +block: + let ctx = newContext(newImage(300, 150)) + + ctx.fillStyle = "blue" + ctx.fillRect(0, 0, ctx.image.width.float32, ctx.image.height.float32) + + ctx.beginPath() + ctx.circle(100, 75, 50) + ctx.clip() + + ctx.saveLayer() + + ctx.fillStyle = "red" + ctx.fillRect(0, 0, ctx.image.width.float32, ctx.image.height.float32) + ctx.fillStyle = "orange" + ctx.fillRect(0, 0, 100, 100) + + ctx.restore() + + ctx.image.writeFile("tests/images/context/clip_1d.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.save() + + ctx.beginPath() + ctx.circle(100, 75, 50) + ctx.clip() + + ctx.saveLayer() + + ctx.fillStyle = "red" + ctx.fillRect(0, 0, ctx.image.width.float32, ctx.image.height.float32) + ctx.fillStyle = "orange" + ctx.fillRect(0, 0, 100, 100) + + ctx.restore() # Pop the layer + ctx.restore() # Pop the clip + + ctx.fillStyle = "blue" + ctx.fillRect(0, 0, ctx.image.width.float32, ctx.image.height.float32) + + ctx.image.writeFile("tests/images/context/clip_1e.png") + block: let ctx = newContext(newImage(300, 150)) @@ -393,3 +438,31 @@ block: ctx.fillRect(0, 0, ctx.image.width.float32, ctx.image.height.float32) image.writeFile("tests/images/context/clip_3.png") + +block: + let image = newImage(300, 150) + + let ctx = newContext(image) + ctx.font = readFont("tests/fonts/Roboto-Regular_1.ttf") + ctx.font.size = 50 + ctx.fillStyle = "blue" + + ctx.saveLayer() + + var circlePath: Path + circlePath.circle(150, 75, 75) + + ctx.clip(circlePath) + + ctx.fillText("Hello world", 50, 90) + + ctx.restore() + + image.writeFile("tests/images/context/clip_text.png") + +block: + let ctx = newContext(100, 100) + ctx.font = readFont("tests/fonts/Roboto-Regular_1.ttf") + + let metrics = ctx.measureText("Hello world") + doAssert metrics.width == 61 From 67c55b0422e37505ee500b6e6182f19659ac6462 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg <guzba8@gmail.com> Date: Sun, 23 May 2021 20:52:06 -0500 Subject: [PATCH 4/4] multiple layers test --- tests/images/context/clip_1f.png | Bin 0 -> 3368 bytes tests/test_context.nim | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 tests/images/context/clip_1f.png diff --git a/tests/images/context/clip_1f.png b/tests/images/context/clip_1f.png new file mode 100644 index 0000000000000000000000000000000000000000..844523eb76490106f5552b10d8edcbe8152963da GIT binary patch literal 3368 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 E7vEw)1poj5 literal 0 HcmV?d00001 diff --git a/tests/test_context.nim b/tests/test_context.nim index 9485de5..8c1f499 100644 --- a/tests/test_context.nim +++ b/tests/test_context.nim @@ -408,6 +408,31 @@ block: ctx.image.writeFile("tests/images/context/clip_1e.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.save() + + ctx.beginPath() + ctx.circle(100, 75, 50) + ctx.clip() + + ctx.saveLayer() + + ctx.fillStyle = "red" + ctx.fillRect(0, 0, ctx.image.width.float32, ctx.image.height.float32) + + ctx.restore() + ctx.saveLayer() + + ctx.fillStyle = "orange" + ctx.fillRect(0, 0, 100, 100) + + ctx.restore() # Pop the layer + + ctx.image.writeFile("tests/images/context/clip_1f.png") + block: let ctx = newContext(newImage(300, 150))