From 8c9daadd04b776a61b409e6d60c6df66a8e45748 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Tue, 17 Aug 2021 23:11:48 -0500 Subject: [PATCH 1/2] morepretty --- src/pixie/contexts.nim | 2 +- src/pixie/paints.nim | 4 ++-- tests/test_fonts.nim | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pixie/contexts.nim b/src/pixie/contexts.nim index 36fb48c..2a0adb0 100644 --- a/src/pixie/contexts.nim +++ b/src/pixie/contexts.nim @@ -385,7 +385,7 @@ proc clearRect*(ctx: Context, rect: Rect) = let path = newPath() path.rect(rect) if ctx.layer != nil: - ctx.layer.fillPath( path, paint, ctx.mat) + ctx.layer.fillPath(path, paint, ctx.mat) else: ctx.image.fillPath(path, paint, ctx.mat) diff --git a/src/pixie/paints.nim b/src/pixie/paints.nim index 5565d4a..131701c 100644 --- a/src/pixie/paints.nim +++ b/src/pixie/paints.nim @@ -15,7 +15,7 @@ type blendMode*: BlendMode ## Blend mode. opacity*: float32 # pkSolid - color*: Color ## Color to fill with. + color*: Color ## Color to fill with. # pkImage, pkImageTiled: image*: Image ## Image to fill with. imageMat*: Mat3 ## Matrix of the filled image. @@ -25,7 +25,7 @@ type ColorStop* = object ## Color stop on a gradient curve. - color*: Color ## Color of the stop. + color*: Color ## Color of the stop. position*: float32 ## Gradient stop position 0..1. SomePaint* = string | Paint | SomeColor diff --git a/tests/test_fonts.nim b/tests/test_fonts.nim index 3ef471a..6674749 100644 --- a/tests/test_fonts.nim +++ b/tests/test_fonts.nim @@ -154,7 +154,9 @@ block: let image = newImage(200, 100) image.fill(rgba(255, 255, 255, 255)) image.fillText(font, "First line") - image.fillText(font, "Second line", translate(vec2(0, font.defaultLineHeight))) + image.fillText( + font, "Second line", translate(vec2(0, font.defaultLineHeight)) + ) doDiff(image, "basic7") From 94a0f3e151e8f3ed7885c64b69760c022b68ebb9 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Tue, 17 Aug 2021 23:14:30 -0500 Subject: [PATCH 2/2] raises --- src/pixie.nim | 44 +++-- src/pixie/blends.nim | 114 ++++++------ src/pixie/common.nim | 8 +- src/pixie/contexts.nim | 268 +++++++++++++++++++---------- src/pixie/fileformats/bmp.nim | 6 +- src/pixie/fileformats/gif.nim | 2 +- src/pixie/fileformats/jpg.nim | 6 +- src/pixie/fileformats/png.nim | 20 ++- src/pixie/fileformats/svg.nim | 46 +++-- src/pixie/fontformats/opentype.nim | 220 +++++++++++++---------- src/pixie/fontformats/svgfont.nim | 43 ++--- src/pixie/fonts.nim | 97 ++++++----- src/pixie/images.nim | 88 +++++----- src/pixie/masks.nim | 40 +++-- src/pixie/paints.nim | 25 +-- src/pixie/paths.nim | 144 +++++++++------- 16 files changed, 683 insertions(+), 488 deletions(-) diff --git a/src/pixie.nim b/src/pixie.nim index b7fb7e3..00b9190 100644 --- a/src/pixie.nim +++ b/src/pixie.nim @@ -10,15 +10,15 @@ type FileFormat* = enum ffPng, ffBmp, ffJpg, ffGif -converter autoStraightAlpha*(c: ColorRGBX): ColorRGBA {.inline.} = - ## Convert a paremultiplied alpha RGBA to a straight alpha RGBA. +converter autoStraightAlpha*(c: ColorRGBX): ColorRGBA {.inline, raises: [].} = + ## Convert a premultiplied alpha RGBA to a straight alpha RGBA. c.rgba() -converter autoPremultipliedAlpha*(c: ColorRGBA): ColorRGBX {.inline.} = +converter autoPremultipliedAlpha*(c: ColorRGBA): ColorRGBX {.inline, raises: [].} = ## Convert a straight alpha RGBA to a premultiplied alpha RGBA. c.rgbx() -proc decodeImage*(data: string | seq[uint8]): Image = +proc decodeImage*(data: string | seq[uint8]): Image {.raises: [PixieError].} = ## Loads an image from memory. if data.len > 8 and data.readUint64(0) == cast[uint64](pngSignature): decodePng(data) @@ -34,22 +34,28 @@ proc decodeImage*(data: string | seq[uint8]): Image = else: raise newException(PixieError, "Unsupported image file format") -proc decodeMask*(data: string | seq[uint8]): Mask = +proc decodeMask*(data: string | seq[uint8]): Mask {.raises: [PixieError].} = ## Loads a mask from memory. if data.len > 8 and data.readUint64(0) == cast[uint64](pngSignature): newMask(decodePng(data)) else: raise newException(PixieError, "Unsupported mask file format") -proc readImage*(filePath: string): Image = +proc readImage*(filePath: string): Image {.inline, raises: [PixieError].} = ## Loads an image from a file. - decodeImage(readFile(filePath)) + try: + decodeImage(readFile(filePath)) + except IOError as e: + raise newException(PixieError, e.msg, e) -proc readMask*(filePath: string): Mask = +proc readMask*(filePath: string): Mask {.raises: [PixieError].} = ## Loads a mask from a file. - decodeMask(readFile(filePath)) + try: + decodeMask(readFile(filePath)) + except IOError as e: + raise newException(PixieError, e.msg, e) -proc encodeImage*(image: Image, fileFormat: FileFormat): string = +proc encodeImage*(image: Image, fileFormat: FileFormat): string {.raises: [PixieError].} = ## Encodes an image into memory. case fileFormat: of ffPng: @@ -61,7 +67,7 @@ proc encodeImage*(image: Image, fileFormat: FileFormat): string = of ffGif: raise newException(PixieError, "Unsupported file format") -proc encodeMask*(mask: Mask, fileFormat: FileFormat): string = +proc encodeMask*(mask: Mask, fileFormat: FileFormat): string {.raises: [PixieError].} = ## Encodes a mask into memory. case fileFormat: of ffPng: @@ -69,7 +75,7 @@ proc encodeMask*(mask: Mask, fileFormat: FileFormat): string = else: raise newException(PixieError, "Unsupported file format") -proc writeFile*(image: Image, filePath: string) = +proc writeFile*(image: Image, filePath: string) {.raises: [PixieError].} = ## Writes an image to a file. let fileFormat = case splitFile(filePath).ext.toLowerAscii(): of ".png": ffPng @@ -77,9 +83,13 @@ proc writeFile*(image: Image, filePath: string) = of ".jpg", ".jpeg": ffJpg else: raise newException(PixieError, "Unsupported file extension") - writeFile(filePath, image.encodeImage(fileFormat)) -proc writeFile*(mask: Mask, filePath: string) = + try: + writeFile(filePath, image.encodeImage(fileFormat)) + except IOError as e: + raise newException(PixieError, e.msg, e) + +proc writeFile*(mask: Mask, filePath: string) {.raises: [PixieError].} = ## Writes a mask to a file. let fileFormat = case splitFile(filePath).ext.toLowerAscii(): of ".png": ffPng @@ -87,4 +97,8 @@ proc writeFile*(mask: Mask, filePath: string) = of ".jpg", ".jpeg": ffJpg else: raise newException(PixieError, "Unsupported file extension") - writeFile(filePath, mask.encodeMask(fileFormat)) + + try: + writeFile(filePath, mask.encodeMask(fileFormat)) + except IOError as e: + raise newException(PixieError, e.msg, e) diff --git a/src/pixie/blends.nim b/src/pixie/blends.nim index 5e5b2c7..8ef218c 100644 --- a/src/pixie/blends.nim +++ b/src/pixie/blends.nim @@ -34,18 +34,18 @@ type bmSubtractMask ## Inverse mask bmExcludeMask - Blender* = proc(backdrop, source: ColorRGBX): ColorRGBX + Blender* = proc(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} ## Function signature returned by blender. - Masker* = proc(backdrop, source: uint8): uint8 + Masker* = proc(backdrop, source: uint8): uint8 {.raises: [].} ## Function signature returned by masker. when defined(release): {.push checks: off.} -proc min(a, b: uint32): uint32 {.inline.} = +proc min(a, b: uint32): uint32 {.inline, raises: [].} = if a < b: a else: b -proc alphaFix(backdrop, source, mixed: ColorRGBA): ColorRGBA = +proc alphaFix(backdrop, source, mixed: ColorRGBA): ColorRGBA {.raises: [].} = ## After mixing an image, adjust its alpha value to be correct. let sa = source.a.uint32 @@ -68,7 +68,7 @@ proc alphaFix(backdrop, source, mixed: ColorRGBA): ColorRGBA = result.b = (b div a div 255).uint8 result.a = a.uint8 -proc alphaFix(backdrop, source, mixed: Color): Color = +proc alphaFix(backdrop, source, mixed: Color): Color {.raises: [].} = ## After mixing an image, adjust its alpha value to be correct. result.a = (source.a + backdrop.a * (1.0 - source.a)) if result.a == 0: @@ -87,16 +87,16 @@ proc alphaFix(backdrop, source, mixed: Color): Color = result.g /= result.a result.b /= result.a -proc blendAlpha*(backdrop, source: uint8): uint8 {.inline.} = +proc blendAlpha*(backdrop, source: uint8): uint8 {.inline, raises: [].} = ## Blends alphas of backdrop, source. source + ((backdrop.uint32 * (255 - source)) div 255).uint8 -proc screen(backdrop, source: uint32): uint8 {.inline.} = +proc screen(backdrop, source: uint32): uint8 {.inline, raises: [].} = ((backdrop + source).int32 - ((backdrop * source) div 255).int32).uint8 proc hardLight( backdropColor, backdropAlpha, sourceColor, sourceAlpha: uint32 -): uint8 {.inline.} = +): uint8 {.inline, raises: [].} = if sourceColor * 2 <= sourceAlpha: (( 2 * sourceColor * backdropColor + @@ -106,41 +106,41 @@ proc hardLight( else: screen(backdropColor, sourceColor) -proc softLight(backdrop, source: float32): float32 {.inline.} = +proc softLight(backdrop, source: float32): float32 {.inline, raises: [].} = ## Pegtop (1 - 2 * source) * backdrop ^ 2 + 2 * source * backdrop -proc `+`(c: Color, v: float32): Color {.inline.} = +proc `+`(c: Color, v: float32): Color {.inline, raises: [].} = result.r = c.r + v result.g = c.g + v result.b = c.b + v result.a = c.a + v -proc `+`(v: float32, c: Color): Color {.inline.} = +proc `+`(v: float32, c: Color): Color {.inline, raises: [].} = c + v -proc `*`(c: Color, v: float32): Color {.inline.} = +proc `*`(c: Color, v: float32): Color {.inline, raises: [].} = result.r = c.r * v result.g = c.g * v result.b = c.b * v result.a = c.a * v -proc `/`(c: Color, v: float32): Color {.inline.} = +proc `/`(c: Color, v: float32): Color {.inline, raises: [].} = result.r = c.r / v result.g = c.g / v result.b = c.b / v result.a = c.a / v -proc `-`(c: Color, v: float32): Color {.inline.} = +proc `-`(c: Color, v: float32): Color {.inline, raises: [].} = result.r = c.r - v result.g = c.g - v result.b = c.b - v result.a = c.a - v -proc Lum(C: Color): float32 {.inline.} = +proc Lum(C: Color): float32 {.inline, raises: [].} = 0.3 * C.r + 0.59 * C.g + 0.11 * C.b -proc ClipColor(C: var Color) {.inline.} = +proc ClipColor(C: var Color) {.inline, raises: [].} = let L = Lum(C) n = min([C.r, C.g, C.b]) @@ -150,22 +150,22 @@ proc ClipColor(C: var Color) {.inline.} = if x > 1: C = L + (((C - L) * (1 - L)) / (x - L)) -proc SetLum(C: Color, l: float32): Color {.inline.} = +proc SetLum(C: Color, l: float32): Color {.inline, raises: [].} = let d = l - Lum(C) result.r = C.r + d result.g = C.g + d result.b = C.b + d ClipColor(result) -proc Sat(C: Color): float32 {.inline.} = +proc Sat(C: Color): float32 {.inline, raises: [].} = max([C.r, C.g, C.b]) - min([C.r, C.g, C.b]) -proc SetSat(C: Color, s: float32): Color {.inline.} = +proc SetSat(C: Color, s: float32): Color {.inline, raises: [].} = let satC = Sat(C) if satC > 0: result = (C - min([C.r, C.g, C.b])) * s / satC -proc blendNormal(backdrop, source: ColorRGBX): ColorRGBX = +proc blendNormal(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} = if backdrop.a == 0: return source if source.a == 255: @@ -179,7 +179,7 @@ proc blendNormal(backdrop, source: ColorRGBX): ColorRGBX = result.b = source.b + ((backdrop.b.uint32 * k) div 255).uint8 result.a = blendAlpha(backdrop.a, source.a) -proc blendDarken(backdrop, source: ColorRGBX): ColorRGBX = +proc blendDarken(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} = proc blend( backdropColor, backdropAlpha, sourceColor, sourceAlpha: uint8 ): uint8 {.inline.} = @@ -193,7 +193,7 @@ proc blendDarken(backdrop, source: ColorRGBX): ColorRGBX = result.b = blend(backdrop.b, backdrop.a, source.b, source.a) result.a = blendAlpha(backdrop.a, source.a) -proc blendMultiply(backdrop, source: ColorRGBX): ColorRGBX = +proc blendMultiply(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} = proc blend( backdropColor, backdropAlpha, sourceColor, sourceAlpha: uint8 ): uint8 {.inline.} = @@ -218,7 +218,7 @@ proc blendMultiply(backdrop, source: ColorRGBX): ColorRGBX = # result = alphaFix(backdrop, source, result) # result = result.toPremultipliedAlpha() -proc blendColorBurn(backdrop, source: ColorRGBX): ColorRGBX = +proc blendColorBurn(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} = let backdrop = backdrop.rgba() source = source.rgba() @@ -235,7 +235,7 @@ proc blendColorBurn(backdrop, source: ColorRGBX): ColorRGBX = blended.b = blend(backdrop.b, source.b) result = alphaFix(backdrop, source, blended).rgbx() -proc blendLighten(backdrop, source: ColorRGBX): ColorRGBX = +proc blendLighten(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} = proc blend( backdropColor, backdropAlpha, sourceColor, sourceAlpha: uint8 ): uint8 {.inline.} = @@ -249,7 +249,7 @@ proc blendLighten(backdrop, source: ColorRGBX): ColorRGBX = result.b = blend(backdrop.b, backdrop.a, source.b, source.a) result.a = blendAlpha(backdrop.a, source.a) -proc blendScreen(backdrop, source: ColorRGBX): ColorRGBX = +proc blendScreen(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} = result.r = screen(backdrop.r, source.r) result.g = screen(backdrop.g, source.g) result.b = screen(backdrop.b, source.b) @@ -265,7 +265,7 @@ proc blendScreen(backdrop, source: ColorRGBX): ColorRGBX = # result = alphaFix(backdrop, source, result) # result = result.toPremultipliedAlpha() -proc blendColorDodge(backdrop, source: ColorRGBX): ColorRGBX = +proc blendColorDodge(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} = let backdrop = backdrop.rgba() source = source.rgba() @@ -282,13 +282,13 @@ proc blendColorDodge(backdrop, source: ColorRGBX): ColorRGBX = blended.b = blend(backdrop.b, source.b) result = alphaFix(backdrop, source, blended).rgbx() -proc blendOverlay(backdrop, source: ColorRGBX): ColorRGBX = +proc blendOverlay(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} = result.r = hardLight(source.r, source.a, backdrop.r, backdrop.a) result.g = hardLight(source.g, source.a, backdrop.g, backdrop.a) result.b = hardLight(source.b, source.a, backdrop.b, backdrop.a) result.a = blendAlpha(backdrop.a, source.a) -proc blendSoftLight(backdrop, source: ColorRGBX): ColorRGBX = +proc blendSoftLight(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} = # proc softLight(backdrop, source: int32): uint8 {.inline.} = # ## Pegtop # ( @@ -362,13 +362,13 @@ proc blendSoftLight(backdrop, source: ColorRGBX): ColorRGBX = result = rgba.rgbx() -proc blendHardLight(backdrop, source: ColorRGBX): ColorRGBX = +proc blendHardLight(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} = result.r = hardLight(backdrop.r, backdrop.a, source.r, source.a) result.g = hardLight(backdrop.g, backdrop.a, source.g, source.a) result.b = hardLight(backdrop.b, backdrop.a, source.b, source.a) result.a = blendAlpha(backdrop.a, source.a) -proc blendDifference(backdrop, source: ColorRGBX): ColorRGBX = +proc blendDifference(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} = proc blend( backdropColor, backdropAlpha, sourceColor, sourceAlpha: uint8 ): uint8 {.inline.} = @@ -384,7 +384,7 @@ proc blendDifference(backdrop, source: ColorRGBX): ColorRGBX = result.b = blend(backdrop.b, backdrop.a, source.b, source.a) result.a = blendAlpha(backdrop.a, source.a) -proc blendExclusion(backdrop, source: ColorRGBX): ColorRGBX = +proc blendExclusion(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} = proc blend(backdrop, source: uint32): uint8 {.inline.} = let v = (backdrop + source).int32 - ((2 * backdrop * source) div 255).int32 max(0, v).uint8 @@ -393,63 +393,63 @@ proc blendExclusion(backdrop, source: ColorRGBX): ColorRGBX = result.b = blend(backdrop.b.uint32, source.b.uint32) result.a = blendAlpha(backdrop.a, source.a) -proc blendColor(backdrop, source: ColorRGBX): ColorRGBX = +proc blendColor(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} = let backdrop = backdrop.rgba().color source = source.rgba().color blended = SetLum(source, Lum(backdrop)) result = alphaFix(backdrop, source, blended).rgba.rgbx() -proc blendLuminosity(backdrop, source: ColorRGBX): ColorRGBX = +proc blendLuminosity(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} = let backdrop = backdrop.rgba().color source = source.rgba().color blended = SetLum(backdrop, Lum(source)) result = alphaFix(backdrop, source, blended).rgba.rgbx() -proc blendHue(backdrop, source: ColorRGBX): ColorRGBX = +proc blendHue(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} = let backdrop = backdrop.rgba().color source = source.rgba().color blended = SetLum(SetSat(source, Sat(backdrop)), Lum(backdrop)) result = alphaFix(backdrop, source, blended).rgba.rgbx() -proc blendSaturation(backdrop, source: ColorRGBX): ColorRGBX = +proc blendSaturation(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} = let backdrop = backdrop.rgba().color source = source.rgba().color blended = SetLum(SetSat(backdrop, Sat(source)), Lum(backdrop)) result = alphaFix(backdrop, source, blended).rgba.rgbx() -proc blendMask(backdrop, source: ColorRGBX): ColorRGBX = +proc blendMask(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} = let k = source.a.uint32 result.r = ((backdrop.r * k) div 255).uint8 result.g = ((backdrop.g * k) div 255).uint8 result.b = ((backdrop.b * k) div 255).uint8 result.a = ((backdrop.a * k) div 255).uint8 -proc blendSubtractMask(backdrop, source: ColorRGBX): ColorRGBX = +proc blendSubtractMask(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} = let a = (backdrop.a.uint32 * (255 - source.a)) div 255 result.r = ((backdrop.r * a) div 255).uint8 result.g = ((backdrop.g * a) div 255).uint8 result.b = ((backdrop.b * a) div 255).uint8 result.a = a.uint8 -proc blendExcludeMask(backdrop, source: ColorRGBX): ColorRGBX = +proc blendExcludeMask(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} = let a = max(backdrop.a, source.a).uint32 - min(backdrop.a, source.a) result.r = ((source.r * a) div 255).uint8 result.g = ((source.g * a) div 255).uint8 result.b = ((source.b * a) div 255).uint8 result.a = a.uint8 -proc blendOverwrite(backdrop, source: ColorRGBX): ColorRGBX = +proc blendOverwrite(backdrop, source: ColorRGBX): ColorRGBX {.raises: [].} = source # proc blendWhite(backdrop, source: ColorRGBX): ColorRGBX = # ## For testing # rgbx(255, 255, 255, 255) -proc blender*(blendMode: BlendMode): Blender = +proc blender*(blendMode: BlendMode): Blender {.raises: [].} = ## Returns a blend function for a given blend mode. case blendMode: of bmNormal: blendNormal @@ -475,24 +475,24 @@ proc blender*(blendMode: BlendMode): Blender = of bmSubtractMask: blendSubtractMask of bmExcludeMask: blendExcludeMask -proc maskNormal(backdrop, source: uint8): uint8 = +proc maskNormal(backdrop, source: uint8): uint8 {.raises: [].} = ## Blending masks blendAlpha(backdrop, source) -proc maskMask(backdrop, source: uint8): uint8 = +proc maskMask(backdrop, source: uint8): uint8 {.raises: [].} = ## Masking masks ((backdrop.uint32 * source) div 255).uint8 -proc maskSubtract(backdrop, source: uint8): uint8 = +proc maskSubtract(backdrop, source: uint8): uint8 {.raises: [].} = ((backdrop.uint32 * (255 - source)) div 255).uint8 -proc maskExclude(backdrop, source: uint8): uint8 = +proc maskExclude(backdrop, source: uint8): uint8 {.raises: [].} = max(backdrop, source) - min(backdrop, source) -proc maskOverwrite(backdrop, source: uint8): uint8 = +proc maskOverwrite(backdrop, source: uint8): uint8 {.raises: [].} = source -proc masker*(blendMode: BlendMode): Masker = +proc masker*(blendMode: BlendMode): Masker {.raises: [PixieError].} = ## Returns a blend masking function for a given blend masking mode. case blendMode: of bmNormal: maskNormal @@ -507,12 +507,12 @@ when defined(amd64) and not defined(pixieNoSimd): import nimsimd/sse2 type - BlenderSimd* = proc(blackdrop, source: M128i): M128i + BlenderSimd* = proc(blackdrop, source: M128i): M128i {.raises: [].} ## Function signature returned by blenderSimd. - MaskerSimd* = proc(blackdrop, source: M128i): M128i + MaskerSimd* = proc(blackdrop, source: M128i): M128i {.raises: [].} ## Function signature returned by maskerSimd. - proc blendNormalSimd(backdrop, source: M128i): M128i = + proc blendNormalSimd(backdrop, source: M128i): M128i {.raises: [].} = let alphaMask = mm_set1_epi32(cast[int32](0xff000000)) oddMask = mm_set1_epi16(cast[int16](0xff00)) @@ -541,7 +541,7 @@ when defined(amd64) and not defined(pixieNoSimd): mm_or_si128(backdropEven, mm_slli_epi16(backdropOdd, 8)) ) - proc blendMaskSimd(backdrop, source: M128i): M128i = + proc blendMaskSimd(backdrop, source: M128i): M128i {.raises: [].} = let alphaMask = mm_set1_epi32(cast[int32](0xff000000)) oddMask = mm_set1_epi16(cast[int16](0xff00)) @@ -562,10 +562,10 @@ when defined(amd64) and not defined(pixieNoSimd): mm_or_si128(backdropEven, mm_slli_epi16(backdropOdd, 8)) - proc blendOverwriteSimd(backdrop, source: M128i): M128i = + proc blendOverwriteSimd(backdrop, source: M128i): M128i {.raises: [].} = source - proc blenderSimd*(blendMode: BlendMode): BlenderSimd = + proc blenderSimd*(blendMode: BlendMode): BlenderSimd {.raises: [PixieError].} = ## Returns a blend function for a given blend mode with SIMD support. case blendMode: of bmNormal: blendNormalSimd @@ -574,11 +574,11 @@ when defined(amd64) and not defined(pixieNoSimd): else: raise newException(PixieError, "No SIMD blender for " & $blendMode) - proc hasSimdBlender*(blendMode: BlendMode): bool = + proc hasSimdBlender*(blendMode: BlendMode): bool {.inline, raises: [].} = ## Is there a blend function for a given blend mode with SIMD support? blendMode in {bmNormal, bmMask, bmOverwrite} - proc maskNormalSimd(backdrop, source: M128i): M128i = + proc maskNormalSimd(backdrop, source: M128i): M128i {.raises: [].} = ## Blending masks let oddMask = mm_set1_epi16(cast[int16](0xff00)) @@ -615,7 +615,7 @@ when defined(amd64) and not defined(pixieNoSimd): mm_or_si128(blendedEven, mm_slli_epi16(blendedOdd, 8)) - proc maskMaskSimd(backdrop, source: M128i): M128i = + proc maskMaskSimd(backdrop, source: M128i): M128i {.raises: [].} = let oddMask = mm_set1_epi16(cast[int16](0xff00)) div255 = mm_set1_epi16(cast[int16](0x8081)) @@ -636,7 +636,7 @@ when defined(amd64) and not defined(pixieNoSimd): mm_or_si128(backdropEven, mm_slli_epi16(backdropOdd, 8)) - proc maskerSimd*(blendMode: BlendMode): MaskerSimd = + proc maskerSimd*(blendMode: BlendMode): MaskerSimd {.raises: [PixieError].} = ## Returns a blend masking function with SIMD support. case blendMode: of bmNormal: maskNormalSimd @@ -645,7 +645,7 @@ when defined(amd64) and not defined(pixieNoSimd): else: raise newException(PixieError, "No SIMD masker for " & $blendMode) - proc hasSimdMasker*(blendMode: BlendMode): bool = + proc hasSimdMasker*(blendMode: BlendMode): bool {.inline, raises: [].} = ## Is there a blend masking function with SIMD support? blendMode in {bmNormal, bmMask, bmOverwrite} diff --git a/src/pixie/common.nim b/src/pixie/common.nim index d2e7bbb..e596169 100644 --- a/src/pixie/common.nim +++ b/src/pixie/common.nim @@ -3,12 +3,12 @@ import bumpy, chroma, vmath type PixieError* = object of ValueError ## Raised if an operation fails. -proc lerp*(a, b: uint8, t: float32): uint8 {.inline.} = +proc lerp*(a, b: uint8, t: float32): uint8 {.inline, raises: [].} = ## Linearly interpolate between a and b using t. let t = round(t * 255).uint32 ((a * (255 - t) + b * t) div 255).uint8 -proc lerp*(a, b: ColorRGBX, t: float32): ColorRGBX {.inline.} = +proc lerp*(a, b: ColorRGBX, t: float32): ColorRGBX {.inline, raises: [].} = ## Linearly interpolate between a and b using t. let x = round(t * 255).uint32 result.r = ((a.r.uint32 * (255 - x) + b.r.uint32 * x) div 255).uint8 @@ -16,14 +16,14 @@ proc lerp*(a, b: ColorRGBX, t: float32): ColorRGBX {.inline.} = result.b = ((a.b.uint32 * (255 - x) + b.b.uint32 * x) div 255).uint8 result.a = ((a.a.uint32 * (255 - x) + b.a.uint32 * x) div 255).uint8 -proc lerp*(a, b: Color, v: float32): Color {.inline.} = +proc lerp*(a, b: Color, v: float32): Color {.inline, raises: [].} = ## Linearly interpolate between a and b using t. result.r = lerp(a.r, b.r, v) result.g = lerp(a.g, b.g, v) result.b = lerp(a.b, b.b, v) result.a = lerp(a.a, b.a, v) -proc snapToPixels*(rect: Rect): Rect = +proc snapToPixels*(rect: Rect): Rect {.raises: [].} = let xMin = rect.x xMax = rect.x + rect.w diff --git a/src/pixie/contexts.nim b/src/pixie/contexts.nim index 2a0adb0..3269d8f 100644 --- a/src/pixie/contexts.nim +++ b/src/pixie/contexts.nim @@ -44,7 +44,7 @@ type TextMetrics* = object width*: float32 -proc newContext*(image: Image): Context = +proc newContext*(image: Image): Context {.raises: [].} = ## Create a new Context that will draw to the parameter image. result = Context() result.image = image @@ -53,15 +53,17 @@ proc newContext*(image: Image): Context = result.globalAlpha = 1 result.lineWidth = 1 result.miterLimit = 10 - result.fillStyle = rgbx(0, 0, 0, 255) - result.strokeStyle = rgbx(0, 0, 0, 255) + result.fillStyle = newPaint(pkSolid) + result.fillStyle.color = color(0, 0, 0, 1) + result.strokeStyle = newPaint(pkSolid) + result.strokeStyle.color = color(0, 0, 0, 1) result.fontSize = 12 -proc newContext*(width, height: int): Context {.inline.} = +proc newContext*(width, height: int): Context {.inline, raises: [PixieError].} = ## Create a new Context that will draw to a new image of width and height. newContext(newImage(width, height)) -proc state(ctx: Context): ContextState = +proc state(ctx: Context): ContextState {.raises: [PixieError].} = result.fillStyle = ctx.fillStyle result.strokeStyle = ctx.strokeStyle result.globalAlpha = ctx.globalAlpha @@ -76,7 +78,7 @@ proc state(ctx: Context): ContextState = result.mat = ctx.mat result.mask = if ctx.mask != nil: ctx.mask.copy() else: nil -proc save*(ctx: Context) {.inline.} = +proc save*(ctx: Context) {.inline, raises: [PixieError].} = ## Saves the entire state of the context by pushing the current state onto ## a stack. ctx.stateStack.add(ctx.state()) @@ -84,7 +86,7 @@ proc save*(ctx: Context) {.inline.} = ctx.fillStyle = newPaint(ctx.fillStyle) ctx.strokeStyle = newPaint(ctx.strokeStyle) -proc saveLayer*(ctx: Context) = +proc saveLayer*(ctx: Context) {.raises: [PixieError].} = ## Saves the entire state of the context by pushing the current state onto ## a stack and allocates a new image layer for subsequent drawing. Calling ## restore blends the current layer image onto the prior layer or root image. @@ -93,7 +95,7 @@ proc saveLayer*(ctx: Context) = ctx.stateStack.add(state) ctx.layer = newImage(ctx.image.width, ctx.image.height) -proc restore*(ctx: Context) = +proc restore*(ctx: Context) {.raises: [PixieError].} = ## Restores the most recently saved context state by popping the top entry ## in the drawing state stack. If there is no saved state, this method does ## nothing. @@ -128,7 +130,9 @@ proc restore*(ctx: Context) = else: # Otherwise draw to the root image ctx.image.draw(poppedLayer) -proc fill(ctx: Context, image: Image, path: Path, windingRule: WindingRule) = +proc fill( + ctx: Context, image: Image, path: Path, windingRule: WindingRule +) {.raises: [PixieError].} = var image = image if ctx.globalAlpha != 1: @@ -146,7 +150,7 @@ proc fill(ctx: Context, image: Image, path: Path, windingRule: WindingRule) = ctx.layer.applyOpacity(ctx.globalAlpha) ctx.restore() -proc stroke(ctx: Context, image: Image, path: Path) = +proc stroke(ctx: Context, image: Image, path: Path) {.raises: [PixieError].} = var image = image if ctx.globalAlpha != 1: @@ -168,17 +172,19 @@ proc stroke(ctx: Context, image: Image, path: Path) = ctx.layer.applyOpacity(ctx.globalAlpha) ctx.restore() -proc newFont(ctx: Context): Font = +proc newFont(ctx: Context): Font {.raises: [PixieError].} = if ctx.font == "": raise newException(PixieError, "No font has been set on this Context") if ctx.font notin ctx.typefaces: ctx.typefaces[ctx.font] = readTypeface(ctx.font) - result = newFont(ctx.typefaces[ctx.font]) + result = newFont(ctx.typefaces.getOrDefault(ctx.font, nil)) result.size = ctx.fontSize -proc fillText(ctx: Context, image: Image, text: string, at: Vec2) = +proc fillText( + ctx: Context, image: Image, text: string, at: Vec2 +) {.raises: [PixieError].} = let font = newFont(ctx) # Canvas positions text relative to the alphabetic baseline by default @@ -204,7 +210,9 @@ proc fillText(ctx: Context, image: Image, text: string, at: Vec2) = ctx.layer.applyOpacity(ctx.globalAlpha) ctx.restore() -proc strokeText(ctx: Context, image: Image, text: string, at: Vec2) = +proc strokeText( + ctx: Context, image: Image, text: string, at: Vec2 +) {.raises: [PixieError].} = let font = newFont(ctx) # Canvas positions text relative to the alphabetic baseline by default @@ -235,29 +243,29 @@ proc strokeText(ctx: Context, image: Image, text: string, at: Vec2) = ctx.layer.applyOpacity(ctx.globalAlpha) ctx.restore() -proc beginPath*(ctx: Context) {.inline.} = +proc beginPath*(ctx: Context) {.inline, raises: [].} = ## Starts a new path by emptying the list of sub-paths. ctx.path = newPath() -proc moveTo*(ctx: Context, v: Vec2) {.inline.} = +proc moveTo*(ctx: Context, v: Vec2) {.inline, raises: [].} = ## Begins a new sub-path at the point (x, y). ctx.path.moveTo(v) -proc moveTo*(ctx: Context, x, y: float32) {.inline.} = +proc moveTo*(ctx: Context, x, y: float32) {.inline, raises: [].} = ## Begins a new sub-path at the point (x, y). ctx.moveTo(vec2(x, y)) -proc lineTo*(ctx: Context, v: Vec2) {.inline.} = +proc lineTo*(ctx: Context, v: Vec2) {.inline, raises: [].} = ## Adds a straight line to the current sub-path by connecting the sub-path's ## last point to the specified (x, y) coordinates. ctx.path.lineTo(v) -proc lineTo*(ctx: Context, x, y: float32) {.inline.} = +proc lineTo*(ctx: Context, x, y: float32) {.inline, raises: [].} = ## Adds a straight line to the current sub-path by connecting the sub-path's ## last point to the specified (x, y) coordinates. ctx.lineTo(vec2(x, y)) -proc bezierCurveTo*(ctx: Context, cp1, cp2, to: Vec2) {.inline.} = +proc bezierCurveTo*(ctx: Context, cp1, cp2, to: Vec2) {.inline, raises: [].} = ## Adds a cubic Bézier curve to the current sub-path. It requires three ## points: the first two are control points and the third one is the end ## point. The starting point is the latest point in the current path, @@ -266,14 +274,16 @@ proc bezierCurveTo*(ctx: Context, cp1, cp2, to: Vec2) {.inline.} = proc bezierCurveTo*( ctx: Context, cp1x, cp1y, cp2x, cp2y, x, y: float32 -) {.inline.} = +) {.inline, raises: [].} = ## Adds a cubic Bézier curve to the current sub-path. It requires three ## points: the first two are control points and the third one is the end ## point. The starting point is the latest point in the current path, ## which can be changed using moveTo() before creating the Bézier curve. ctx.bezierCurveTo(vec2(cp1x, cp1y), vec2(cp2x, cp2y), vec2(x, y)) -proc quadraticCurveTo*(ctx: Context, cpx, cpy, x, y: float32) {.inline.} = +proc quadraticCurveTo*( + ctx: Context, cpx, cpy, x, y: float32 +) {.inline, raises: [].} = ## Adds a quadratic Bézier curve to the current sub-path. It requires two ## points: the first one is a control point and the second one is the end ## point. The starting point is the latest point in the current path, @@ -281,7 +291,9 @@ proc quadraticCurveTo*(ctx: Context, cpx, cpy, x, y: float32) {.inline.} = ## Bézier curve. ctx.path.quadraticCurveTo(cpx, cpy, x, y) -proc quadraticCurveTo*(ctx: Context, ctrl, to: Vec2) {.inline.} = +proc quadraticCurveTo*( + ctx: Context, ctrl, to: Vec2 +) {.inline, raises: [].} = ## Adds a quadratic Bézier curve to the current sub-path. It requires two ## points: the first one is a control point and the second one is the end ## point. The starting point is the latest point in the current path, @@ -289,45 +301,55 @@ proc quadraticCurveTo*(ctx: Context, ctrl, to: Vec2) {.inline.} = ## Bézier curve. ctx.path.quadraticCurveTo(ctrl, to) -proc arc*(ctx: Context, x, y, r, a0, a1: float32, ccw: bool = false) = +proc arc*( + ctx: Context, x, y, r, a0, a1: float32, ccw: bool = false +) {.raises: [PixieError].} = ## Draws a circular arc. ctx.path.arc(x, y, r, a0, a1, ccw) -proc arc*(ctx: Context, pos: Vec2, r: float32, a: Vec2, ccw: bool = false) = +proc arc*( + ctx: Context, pos: Vec2, r: float32, a: Vec2, ccw: bool = false +) {.raises: [PixieError].} = ## Adds a circular arc to the current sub-path. ctx.path.arc(pos, r, a, ccw) -proc arcTo*(ctx: Context, x1, y1, x2, y2, radius: float32) = +proc arcTo*( + ctx: Context, x1, y1, x2, y2, radius: float32 +) {.raises: [PixieError].} = ## Draws a circular arc using the given control points and radius. ctx.path.arcTo(x1, y1, x2, y2, radius) -proc arcTo*(ctx: Context, a, b: Vec2, r: float32) = +proc arcTo*( + ctx: Context, a, b: Vec2, r: float32 +) {.raises: [PixieError].} = ## Adds a circular arc using the given control points and radius. ctx.path.arcTo(a, b, r) -proc closePath*(ctx: Context) {.inline.} = +proc closePath*(ctx: Context) {.inline, raises: [].} = ## Attempts to add a straight line from the current point to the start of ## the current sub-path. If the shape has already been closed or has only ## one point, this function does nothing. ctx.path.closePath() -proc rect*(ctx: Context, rect: Rect) {.inline.} = +proc rect*(ctx: Context, rect: Rect) {.inline, raises: [].} = ## Adds a rectangle to the current path. ctx.path.rect(rect) -proc rect*(ctx: Context, x, y, width, height: float32) {.inline.} = +proc rect*(ctx: Context, x, y, width, height: float32) {.inline, raises: [].} = ## Adds a rectangle to the current path. ctx.path.rect(x, y, width, height) -proc ellipse*(ctx: Context, center: Vec2, rx, ry: float32) {.inline.} = +proc ellipse*(ctx: Context, center: Vec2, rx, ry: float32) {.inline, raises: [].} = ## Adds an ellipse to the current sub-path. ctx.path.ellipse(center, rx, ry) -proc ellipse*(ctx: Context, x, y, rx, ry: float32) {.inline.} = +proc ellipse*(ctx: Context, x, y, rx, ry: float32) {.inline, raises: [].} = ## Adds an ellipse to the current sub-path. ctx.path.ellipse(x, y, rx, ry) -proc fill*(ctx: Context, path: Path, windingRule = wrNonZero) = +proc fill*( + ctx: Context, path: Path, windingRule = wrNonZero +) {.raises: [PixieError].} = ## Fills the path with the current fillStyle. if ctx.mask != nil and ctx.layer == nil: ctx.saveLayer() @@ -338,13 +360,17 @@ proc fill*(ctx: Context, path: Path, windingRule = wrNonZero) = else: ctx.fill(ctx.image, path, windingRule) -proc fill*(ctx: Context, windingRule = wrNonZero) {.inline.} = +proc fill*( + ctx: Context, windingRule = wrNonZero +) {.inline, raises: [PixieError].} = ## Fills the current path with the current fillStyle. ctx.fill(ctx.path, windingRule) -proc clip*(ctx: Context, windingRule = wrNonZero) {.inline.} +proc clip*(ctx: Context, windingRule = wrNonZero) {.inline, raises: [PixieError].} -proc clip*(ctx: Context, path: Path, windingRule = wrNonZero) = +proc clip*( + ctx: Context, path: Path, windingRule = wrNonZero +) {.raises: [PixieError].} = ## Turns the path into the current clipping region. The previous clipping ## region, if any, is intersected with the current or given path to create ## the new clipping region. @@ -354,15 +380,17 @@ proc clip*(ctx: Context, path: Path, windingRule = wrNonZero) = else: ctx.mask.fillPath(path, windingRule = windingRule, blendMode = bmMask) -proc clip*(ctx: Context, windingRule = wrNonZero) {.inline.} = +proc clip*( + ctx: Context, windingRule = wrNonZero +) {.inline, raises: [PixieError].} = ## Turns the current path into the current clipping region. The previous ## clipping region, if any, is intersected with the current or given path ## to create the new clipping region. ctx.clip(ctx.path, windingRule) -proc stroke*(ctx: Context) {.inline.} +proc stroke*(ctx: Context) {.inline, raises: [PixieError].} -proc stroke*(ctx: Context, path: Path) = +proc stroke*(ctx: Context, path: Path) {.raises: [PixieError].} = ## Strokes (outlines) the current or given path with the current strokeStyle. if ctx.mask != nil and ctx.layer == nil: ctx.saveLayer() @@ -373,11 +401,11 @@ proc stroke*(ctx: Context, path: Path) = else: ctx.stroke(ctx.image, path) -proc stroke*(ctx: Context) {.inline.} = +proc stroke*(ctx: Context) {.inline, raises: [PixieError].} = ## Strokes (outlines) the current or given path with the current strokeStyle. ctx.stroke(ctx.path) -proc clearRect*(ctx: Context, rect: Rect) = +proc clearRect*(ctx: Context, rect: Rect) {.raises: [PixieError].} = ## Erases the pixels in a rectangular area. let paint = newPaint(pkSolid) paint.blendMode = bmOverwrite @@ -389,33 +417,39 @@ proc clearRect*(ctx: Context, rect: Rect) = else: ctx.image.fillPath(path, paint, ctx.mat) -proc clearRect*(ctx: Context, x, y, width, height: float32) {.inline.} = +proc clearRect*( + ctx: Context, x, y, width, height: float32 +) {.inline, raises: [PixieError].} = ## Erases the pixels in a rectangular area. ctx.clearRect(rect(x, y, width, height)) -proc fillRect*(ctx: Context, rect: Rect) = +proc fillRect*(ctx: Context, rect: Rect) {.raises: [PixieError].} = ## Draws a rectangle that is filled according to the current fillStyle. let path = newPath() path.rect(rect) ctx.fill(path) -proc fillRect*(ctx: Context, x, y, width, height: float32) {.inline.} = +proc fillRect*( + ctx: Context, x, y, width, height: float32 +) {.inline, raises: [PixieError].} = ## Draws a rectangle that is filled according to the current fillStyle. ctx.fillRect(rect(x, y, width, height)) -proc strokeRect*(ctx: Context, rect: Rect) = +proc strokeRect*(ctx: Context, rect: Rect) {.raises: [PixieError].} = ## Draws a rectangle that is stroked (outlined) according to the current ## strokeStyle and other context settings. let path = newPath() path.rect(rect) ctx.stroke(path) -proc strokeRect*(ctx: Context, x, y, width, height: float32) {.inline.} = +proc strokeRect*( + ctx: Context, x, y, width, height: float32 +) {.inline, raises: [PixieError].} = ## Draws a rectangle that is stroked (outlined) according to the current ## strokeStyle and other context settings. ctx.strokeRect(rect(x, y, width, height)) -proc fillText*(ctx: Context, text: string, at: Vec2) = +proc fillText*(ctx: Context, text: string, at: Vec2) {.raises: [PixieError].} = ## Draws a text string at the specified coordinates, filling the string's ## characters with the current fillStyle if ctx.mask != nil and ctx.layer == nil: @@ -427,12 +461,14 @@ proc fillText*(ctx: Context, text: string, at: Vec2) = else: ctx.fillText(ctx.image, text, at) -proc fillText*(ctx: Context, text: string, x, y: float32) {.inline.} = +proc fillText*( + ctx: Context, text: string, x, y: float32 +) {.inline, raises: [PixieError].} = ## Draws the outlines of the characters of a text string at the specified ## coordinates. ctx.fillText(text, vec2(x, y)) -proc strokeText*(ctx: Context, text: string, at: Vec2) = +proc strokeText*(ctx: Context, text: string, at: Vec2) {.raises: [PixieError].} = ## Draws the outlines of the characters of a text string at the specified ## coordinates. if ctx.mask != nil and ctx.layer == nil: @@ -444,12 +480,14 @@ proc strokeText*(ctx: Context, text: string, at: Vec2) = else: ctx.strokeText(ctx.image, text, at) -proc strokeText*(ctx: Context, text: string, x, y: float32) {.inline.} = +proc strokeText*( + ctx: Context, text: string, x, y: float32 +) {.inline, raises: [PixieError].} = ## 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 = +proc measureText*(ctx: Context, text: string): TextMetrics {.raises: [PixieError].} = ## Returns a TextMetrics object that contains information about the measured ## text (such as its width, for example). let @@ -457,61 +495,63 @@ proc measureText*(ctx: Context, text: string): TextMetrics = bounds = typeset(font, text).computeBounds() result.width = bounds.x -proc getLineDash*(ctx: Context): seq[float32] {.inline.} = +proc getLineDash*(ctx: Context): seq[float32] {.inline, raises: [].} = ctx.lineDash -proc setLineDash*(ctx: Context, lineDash: seq[float32]) {.inline.} = +proc setLineDash*(ctx: Context, lineDash: seq[float32]) {.inline, raises: [].} = ctx.lineDash = lineDash -proc getTransform*(ctx: Context): Mat3 {.inline.} = +proc getTransform*(ctx: Context): Mat3 {.inline, raises: []} = ## Retrieves the current transform matrix being applied to the context. ctx.mat -proc setTransform*(ctx: Context, transform: Mat3) {.inline.} = +proc setTransform*(ctx: Context, transform: Mat3) {.inline, raises: [].} = ## Overrides the transform matrix being applied to the context. ctx.mat = transform -proc setTransform*(ctx: Context, a, b, c, d, e, f: float32) {.inline.} = +proc setTransform*(ctx: Context, a, b, c, d, e, f: float32) {.inline, raises: [].} = ## Overrides the transform matrix being applied to the context. ctx.mat = mat3(a, b, 0, c, d, 0, e, f, 1) -proc transform*(ctx: Context, transform: Mat3) {.inline.} = +proc transform*(ctx: Context, transform: Mat3) {.inline, raises: [].} = ## Multiplies the current transform with the matrix described by the ## arguments of this method. ctx.mat = ctx.mat * transform -proc transform*(ctx: Context, a, b, c, d, e, f: float32) {.inline.} = +proc transform*(ctx: Context, a, b, c, d, e, f: float32) {.inline, raises: [].} = ## Multiplies the current transform with the matrix described by the ## arguments of this method. ctx.transform(mat3(a, b, 0, c, d, 0, e, f, 1)) -proc translate*(ctx: Context, v: Vec2) {.inline.} = +proc translate*(ctx: Context, v: Vec2) {.inline, raises: [].} = ## Adds a translation transformation to the current matrix. ctx.mat = ctx.mat * translate(v) -proc translate*(ctx: Context, x, y: float32) {.inline.} = +proc translate*(ctx: Context, x, y: float32) {.inline, raises: [].} = ## Adds a translation transformation to the current matrix. ctx.mat = ctx.mat * translate(vec2(x, y)) -proc scale*(ctx: Context, v: Vec2) {.inline.} = +proc scale*(ctx: Context, v: Vec2) {.inline, raises: [].} = ## Adds a scaling transformation to the context units horizontally and/or ## vertically. ctx.mat = ctx.mat * scale(v) -proc scale*(ctx: Context, x, y: float32) {.inline.} = +proc scale*(ctx: Context, x, y: float32) {.inline, raises: [].} = ## Adds a scaling transformation to the context units horizontally and/or ## vertically. ctx.mat = ctx.mat * scale(vec2(x, y)) -proc rotate*(ctx: Context, angle: float32) {.inline.} = +proc rotate*(ctx: Context, angle: float32) {.inline, raises: [].} = ## Adds a rotation to the transformation matrix. ctx.mat = ctx.mat * rotate(-angle) -proc resetTransform*(ctx: Context) {.inline.} = +proc resetTransform*(ctx: Context) {.inline, raises: [].} = ## Resets the current transform to the identity matrix. ctx.mat = mat3() -proc drawImage*(ctx: Context, image: Image, dx, dy, dWidth, dHeight: float32) = +proc drawImage*( + ctx: Context, image: Image, dx, dy, dWidth, dHeight: float32 +) {.raises: [PixieError].} = ## Draws a source image onto the destination image. let imageMat = ctx.mat * translate(vec2(dx, dy)) * scale(vec2( @@ -530,15 +570,17 @@ proc drawImage*(ctx: Context, image: Image, dx, dy, dWidth, dHeight: float32) = ctx.fillStyle = savedFillStyle -proc drawImage*(ctx: Context, image: Image, dx, dy: float32) = +proc drawImage*( + ctx: Context, image: Image, dx, dy: float32 +) {.raises: [PixieError].} = ## Draws a source image onto the destination image. ctx.drawImage(image, dx, dx, image.width.float32, image.height.float32) -proc drawImage*(ctx: Context, image: Image, pos: Vec2) = +proc drawImage*(ctx: Context, image: Image, pos: Vec2) {.raises: [PixieError].} = ## Draws a source image onto the destination image. ctx.drawImage(image, pos.x, pos.y) -proc drawImage*(ctx: Context, image: Image, rect: Rect) = +proc drawImage*(ctx: Context, image: Image, rect: Rect) {.raises: [PixieError].} = ## Draws a source image onto the destination image. ctx.drawImage(image, rect.x, rect.y, rect.w, rect.h) @@ -547,12 +589,12 @@ proc drawImage*( image: Image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight: float32 -) = +) {.raises: [PixieError].} = ## Draws a source image onto the destination image. let image = image.subImage(sx.int, sy.int, sWidth.int, sHeight.int) ctx.drawImage(image, dx, dx, image.width.float32, image.height.float32) -proc drawImage*(ctx: Context, image: Image, src, dest: Rect) = +proc drawImage*(ctx: Context, image: Image, src, dest: Rect) {.raises: [PixieError].} = ## Draws a source image onto the destination image. ctx.drawImage( image, @@ -562,29 +604,31 @@ proc drawImage*(ctx: Context, image: Image, src, dest: Rect) = proc isPointInPath*( ctx: Context, path: Path, pos: Vec2, windingRule = wrNonZero -): bool = +): bool {.raises: [PixieError].} = ## Returns whether or not the specified point is contained in the current path. path.fillOverlaps(pos, ctx.mat, windingRule) proc isPointInPath*( ctx: Context, path: Path, x, y: float32, windingRule = wrNonZero -): bool {.inline.} = +): bool {.inline, raises: [PixieError].} = ## Returns whether or not the specified point is contained in the current path. ctx.isPointInPath(path, vec2(x, y), windingRule) proc isPointInPath*( ctx: Context, pos: Vec2, windingRule = wrNonZero -): bool {.inline.} = +): bool {.inline, raises: [PixieError].} = ## Returns whether or not the specified point is contained in the current path. ctx.isPointInPath(ctx.path, pos, windingRule) proc isPointInPath*( ctx: Context, x, y: float32, windingRule = wrNonZero -): bool {.inline.} = +): bool {.inline, raises: [PixieError].} = ## Returns whether or not the specified point is contained in the current path. ctx.isPointInPath(ctx.path, vec2(x, y), windingRule) -proc isPointInStroke*(ctx: Context, path: Path, pos: Vec2): bool = +proc isPointInStroke*( + ctx: Context, path: Path, pos: Vec2 +): bool {.raises: [PixieError].} = ## Returns whether or not the specified point is inside the area contained ## by the stroking of a path. path.strokeOverlaps( @@ -597,17 +641,23 @@ proc isPointInStroke*(ctx: Context, path: Path, pos: Vec2): bool = ctx.lineDash ) -proc isPointInStroke*(ctx: Context, path: Path, x, y: float32): bool {.inline.} = +proc isPointInStroke*( + ctx: Context, path: Path, x, y: float32 +): bool {.inline, raises: [PixieError].} = ## Returns whether or not the specified point is inside the area contained ## by the stroking of a path. ctx.isPointInStroke(path, vec2(x, y)) -proc isPointInStroke*(ctx: Context, pos: Vec2): bool {.inline.} = +proc isPointInStroke*( + ctx: Context, pos: Vec2 +): bool {.inline, raises: [PixieError].} = ## Returns whether or not the specified point is inside the area contained ## by the stroking of a path. ctx.isPointInStroke(ctx.path, pos) -proc isPointInStroke*(ctx: Context, x, y: float32): bool {.inline.} = +proc isPointInStroke*( + ctx: Context, x, y: float32 +): bool {.inline, raises: [PixieError].} = ## Returns whether or not the specified point is inside the area contained ## by the stroking of a path. ctx.isPointInStroke(ctx.path, vec2(x, y)) @@ -616,53 +666,69 @@ proc isPointInStroke*(ctx: Context, x, y: float32): bool {.inline.} = # Additional procs that are not part of the JS API # -proc roundedRect*(ctx: Context, x, y, w, h, nw, ne, se, sw: float32) {.inline.} = +proc roundedRect*( + ctx: Context, x, y, w, h, nw, ne, se, sw: float32 +) {.inline, raises: [].} = ## Adds a rounded rectangle to the current path. ctx.path.roundedRect(x, y, w, h, nw, ne, se, sw) -proc roundedRect*(ctx: Context, rect: Rect, nw, ne, se, sw: float32) {.inline.} = +proc roundedRect*( + ctx: Context, rect: Rect, nw, ne, se, sw: float32 +) {.inline, raises: [].} = ## Adds a rounded rectangle to the current path. ctx.path.roundedRect(rect, nw, ne, se, sw) -proc circle*(ctx: Context, cx, cy, r: float32) {.inline.} = +proc circle*(ctx: Context, cx, cy, r: float32) {.inline, raises: [].} = ## Adds a circle to the current path. ctx.path.circle(cx, cy, r) -proc circle*(ctx: Context, circle: Circle) {.inline.} = +proc circle*(ctx: Context, circle: Circle) {.inline, raises: [].} = ## Adds a circle to the current path. ctx.path.circle(circle) -proc polygon*(ctx: Context, x, y, size: float32, sides: int) {.inline.} = +proc polygon*( + ctx: Context, x, y, size: float32, sides: int +) {.inline, raises: [].} = ## Adds an n-sided regular polygon at (x, y) of size to the current path. ctx.path.polygon(x, y, size, sides) -proc polygon*(ctx: Context, pos: Vec2, size: float32, sides: int) {.inline.} = +proc polygon*( + ctx: Context, pos: Vec2, size: float32, sides: int +) {.inline, raises: [].} = ## Adds an n-sided regular polygon at (x, y) of size to the current path. ctx.path.polygon(pos, size, sides) -proc fillRoundedRect*(ctx: Context, rect: Rect, nw, ne, se, sw: float32) = +proc fillRoundedRect*( + ctx: Context, rect: Rect, nw, ne, se, sw: float32 +) {.raises: [PixieError].} = ## Draws a rounded rectangle that is filled according to the current fillStyle. let path = newPath() path.roundedRect(rect, nw, ne, se, sw) ctx.fill(path) -proc fillRoundedRect*(ctx: Context, rect: Rect, radius: float32) {.inline.} = +proc fillRoundedRect*( + ctx: Context, rect: Rect, radius: float32 +) {.inline, raises: [PixieError].} = ## Draws a rounded rectangle that is filled according to the current fillStyle. ctx.fillRoundedRect(rect, radius, radius, radius, radius) -proc strokeRoundedRect*(ctx: Context, rect: Rect, nw, ne, se, sw: float32) = +proc strokeRoundedRect*( + ctx: Context, rect: Rect, nw, ne, se, sw: float32 +) {.raises: [PixieError].} = ## Draws a rounded rectangle that is stroked (outlined) according to the ## current strokeStyle and other context settings. let path = newPath() path.roundedRect(rect, nw, ne, se, sw) ctx.stroke(path) -proc strokeRoundedRect*(ctx: Context, rect: Rect, radius: float32) {.inline.} = +proc strokeRoundedRect*( + ctx: Context, rect: Rect, radius: float32 +) {.inline, raises: [PixieError].} = ## Draws a rounded rectangle that is stroked (outlined) according to the ## current strokeStyle and other context settings. ctx.strokeRoundedRect(rect, radius, radius, radius, radius) -proc strokeSegment*(ctx: Context, segment: Segment) = +proc strokeSegment*(ctx: Context, segment: Segment) {.raises: [PixieError].} = ## Strokes a segment (draws a line from segment.at to segment.to) according ## to the current strokeStyle and other context settings. let path = newPath() @@ -670,40 +736,52 @@ proc strokeSegment*(ctx: Context, segment: Segment) = path.lineTo(segment.to) ctx.stroke(path) -proc fillEllipse*(ctx: Context, center: Vec2, rx, ry: float32) = +proc fillEllipse*( + ctx: Context, center: Vec2, rx, ry: float32 +) {.raises: [PixieError].} = ## Draws an ellipse that is filled according to the current fillStyle. let path = newPath() path.ellipse(center, rx, ry) ctx.fill(path) -proc strokeEllipse*(ctx: Context, center: Vec2, rx, ry: float32) = +proc strokeEllipse*( + ctx: Context, center: Vec2, rx, ry: float32 +) {.raises: [PixieError].} = ## Draws an ellipse that is stroked (outlined) according to the current ## strokeStyle and other context settings. let path = newPath() path.ellipse(center, rx, ry) ctx.stroke(path) -proc fillCircle*(ctx: Context, circle: Circle) = +proc fillCircle*( + ctx: Context, circle: Circle +) {.raises: [PixieError].} = ## Draws a circle that is filled according to the current fillStyle let path = newPath() path.circle(circle) ctx.fill(path) -proc strokeCircle*(ctx: Context, circle: Circle) = +proc strokeCircle*( + ctx: Context, circle: Circle +) {.raises: [PixieError].} = ## Draws a circle that is stroked (outlined) according to the current ## strokeStyle and other context settings. let path = newPath() path.circle(circle) ctx.stroke(path) -proc fillPolygon*(ctx: Context, pos: Vec2, size: float32, sides: int) = +proc fillPolygon*( + ctx: Context, pos: Vec2, size: float32, sides: int +) {.raises: [PixieError].} = ## Draws an n-sided regular polygon at (x, y) of size that is filled according ## to the current fillStyle. let path = newPath() path.polygon(pos, size, sides) ctx.fill(path) -proc strokePolygon*(ctx: Context, pos: Vec2, size: float32, sides: int) = +proc strokePolygon*( + ctx: Context, pos: Vec2, size: float32, sides: int +) {.raises: [PixieError].} = ## Draws an n-sided regular polygon at (x, y) of size that is stroked ## (outlined) according to the current strokeStyle and other context settings. let path = newPath() diff --git a/src/pixie/fileformats/bmp.nim b/src/pixie/fileformats/bmp.nim index 83c0b7d..9853d7f 100644 --- a/src/pixie/fileformats/bmp.nim +++ b/src/pixie/fileformats/bmp.nim @@ -4,7 +4,7 @@ import chroma, flatty/binny, pixie/common, pixie/images const bmpSignature* = "BM" -proc decodeBmp*(data: string): Image = +proc decodeBmp*(data: string): Image {.raises: [PixieError].} = ## Decodes bitmap data into an Image. # BMP Header @@ -48,11 +48,11 @@ proc decodeBmp*(data: string): Image = offset += 3 result[x, result.height - y - 1] = rgba.rgbx() -proc decodeBmp*(data: seq[uint8]): Image {.inline.} = +proc decodeBmp*(data: seq[uint8]): Image {.inline, raises: [PixieError].} = ## Decodes bitmap data into an Image. decodeBmp(cast[string](data)) -proc encodeBmp*(image: Image): string = +proc encodeBmp*(image: Image): string {.raises: [].} = ## Encodes an image into the BMP file format. # BMP Header diff --git a/src/pixie/fileformats/gif.nim b/src/pixie/fileformats/gif.nim index 27727c0..b2ee795 100644 --- a/src/pixie/fileformats/gif.nim +++ b/src/pixie/fileformats/gif.nim @@ -10,7 +10,7 @@ template failInvalid() = when defined(release): {.push checks: off.} -proc decodeGif*(data: string): Image = +proc decodeGif*(data: string): Image {.raises: [PixieError].} = ## Decodes GIF data into an Image. if data.len <= 13: failInvalid() diff --git a/src/pixie/fileformats/jpg.nim b/src/pixie/fileformats/jpg.nim index fddc06a..cc241df 100644 --- a/src/pixie/fileformats/jpg.nim +++ b/src/pixie/fileformats/jpg.nim @@ -6,7 +6,7 @@ when defined(pixieUseStb): const jpgStartOfImage* = [0xFF.uint8, 0xD8] -proc decodeJpg*(data: seq[uint8]): Image = +proc decodeJpg*(data: seq[uint8]): Image {.raises: [PixieError].} = ## Decodes the JPEG into an Image. when not defined(pixieUseStb): raise newException(PixieError, "Decoding JPG requires -d:pixieUseStb") @@ -19,10 +19,10 @@ proc decodeJpg*(data: seq[uint8]): Image = result = newImage(width, height) copyMem(result.data[0].addr, pixels[0].unsafeAddr, pixels.len) -proc decodeJpg*(data: string): Image {.inline.} = +proc decodeJpg*(data: string): Image {.inline, raises: [PixieError].} = ## Decodes the JPEG data into an Image. decodeJpg(cast[seq[uint8]](data)) -proc encodeJpg*(image: Image): string = +proc encodeJpg*(image: Image): string {.raises: [PixieError].} = ## Encodes Image into a JPEG data string. raise newException(PixieError, "Encoding JPG not supported yet") diff --git a/src/pixie/fileformats/png.nim b/src/pixie/fileformats/png.nim index c99d10a..6e376db 100644 --- a/src/pixie/fileformats/png.nim +++ b/src/pixie/fileformats/png.nim @@ -23,7 +23,7 @@ template failInvalid() = when defined(release): {.push checks: off.} -proc decodeHeader(data: string): PngHeader = +proc decodeHeader(data: string): PngHeader {.raises: [PixieError].} = result.width = data.readUint32(0).swap().int result.height = data.readUint32(4).swap().int result.bitDepth = data.readUint8(8) @@ -79,7 +79,7 @@ proc decodeHeader(data: string): PngHeader = if result.interlaceMethod != 0: raise newException(PixieError, "Interlaced PNG not yet supported") -proc decodePalette(data: string): seq[ColorRGB] = +proc decodePalette(data: string): seq[ColorRGB] {.raises: [PixieError].} = if data.len == 0 or data.len mod 3 != 0: failInvalid() @@ -88,7 +88,9 @@ proc decodePalette(data: string): seq[ColorRGB] = for i in 0 ..< data.len div 3: result[i] = cast[ptr ColorRGB](data[i * 3].unsafeAddr)[] -proc unfilter(uncompressed: string, height, rowBytes, bpp: int): string = +proc unfilter( + uncompressed: string, height, rowBytes, bpp: int +): string {.raises: [].} = result.setLen(uncompressed.len - height) template uncompressedIdx(x, y: int): int = @@ -162,7 +164,7 @@ proc decodeImageData( header: PngHeader, palette: seq[ColorRGB], transparency, data: string -): seq[ColorRGBA] = +): seq[ColorRGBA] {.raises: [PixieError].} = result.setLen(header.width * header.height) let @@ -314,7 +316,7 @@ proc decodeImageData( else: discard # Not possible, parseHeader validates -proc decodePng*(data: string): Image = +proc decodePng*(data: string): Image {.raises: [PixieError].} = ## Decodes the PNG data into an Image. if data.len < (8 + (8 + 13 + 4) + 4): # Magic bytes + IHDR + IEND @@ -424,7 +426,9 @@ proc decodePng*(data: string): Image = result = newImage(header.width, header.height) copyMem(result.data[0].addr, pixels[0].addr, pixels.len * 4) -proc encodePng*(width, height, channels: int, data: pointer, len: int): string = +proc encodePng*( + width, height, channels: int, data: pointer, len: int +): string {.raises: [PixieError].} = ## Encodes the image data into the PNG file format. ## If data points to RGBA data, it is assumed to be straight alpha. @@ -503,7 +507,7 @@ proc encodePng*(width, height, channels: int, data: pointer, len: int): string = result.add("IEND") result.addUint32(crc32(result[result.len - 4 ..< result.len]).swap()) -proc encodePng*(image: Image): string = +proc encodePng*(image: Image): string {.raises: [PixieError].} = ## Encodes the image data into the PNG file format. if image.data.len == 0: raise newException( @@ -514,7 +518,7 @@ proc encodePng*(image: Image): string = copy.toStraightAlpha() encodePng(image.width, image.height, 4, copy[0].addr, copy.len * 4) -proc encodePng*(mask: Mask): string = +proc encodePng*(mask: Mask): string {.raises: [PixieError].} = ## Encodes the mask data into the PNG file format. if mask.data.len == 0: raise newException( diff --git a/src/pixie/fileformats/svg.nim b/src/pixie/fileformats/svg.nim index 731bfc1..b13f96a 100644 --- a/src/pixie/fileformats/svg.nim +++ b/src/pixie/fileformats/svg.nim @@ -33,15 +33,19 @@ type template failInvalid() = raise newException(PixieError, "Invalid SVG data") -proc attrOrDefault(node: XmlNode, name, default: string): string = +proc attrOrDefault(node: XmlNode, name, default: string): string {.raises: [].} = result = node.attr(name) if result.len == 0: result = default -proc initCtx(): Ctx = +proc initCtx(): Ctx {.raises: [PixieError].} = result.display = true - result.fill = parseHtmlColor("black").rgbx - result.stroke = parseHtmlColor("black").rgbx + try: + result.fill = parseHtmlColor("black").rgbx + result.stroke = parseHtmlColor("black").rgbx + except: + let e = getCurrentException() + raise newException(PixieError, e.msg, e) result.strokeWidth = 1 result.transform = mat3() result.strokeMiterLimit = defaultMiterLimit @@ -49,7 +53,7 @@ proc initCtx(): Ctx = result.strokeOpacity = 1 result.linearGradients = newTable[string, LinearGradient]() -proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx = +proc decodeCtxInternal(inherited: Ctx, node: XmlNode): Ctx = result = inherited proc splitArgs(s: string): seq[string] = @@ -313,14 +317,23 @@ proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx = else: failInvalidTransform(transform) -proc fill(img: Image, ctx: Ctx, path: Path) {.inline.} = +proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx {.raises: [PixieError].} = + try: + decodeCtxInternal(inherited, node) + except PixieError as e: + raise e + except: + let e = getCurrentException() + raise newException(PixieError, e.msg, e) + +proc fill(img: Image, ctx: Ctx, path: Path) {.inline, raises: [PixieError].} = if ctx.display and ctx.opacity > 0: let paint = newPaint(ctx.fill) if ctx.opacity != 1: paint.opacity = paint.opacity * ctx.opacity img.fillPath(path, paint, ctx.transform, ctx.fillRule) -proc stroke(img: Image, ctx: Ctx, path: Path) {.inline.} = +proc stroke(img: Image, ctx: Ctx, path: Path) {.inline, raises: [PixieError].} = if ctx.display and ctx.opacity > 0: let paint = newPaint(ctx.stroke) if ctx.opacity != 1: @@ -336,7 +349,7 @@ proc stroke(img: Image, ctx: Ctx, path: Path) {.inline.} = dashes = ctx.strokeDashArray ) -proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) = +proc drawInternal(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) = if node.kind != xnElement: # Skip return @@ -353,7 +366,7 @@ proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) = let ctx = decodeCtx(ctxStack[^1], node) ctxStack.add(ctx) for child in node: - img.draw(child, ctxStack) + img.drawInternal(child, ctxStack) discard ctxStack.pop() of "path": @@ -543,7 +556,20 @@ proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) = else: raise newException(PixieError, "Unsupported SVG tag: " & node.tag) -proc decodeSvg*(data: string, width = 0, height = 0): Image = +proc draw( + img: Image, node: XmlNode, ctxStack: var seq[Ctx] +) {.raises: [PixieError].} = + try: + drawInternal(img, node, ctxStack) + except PixieError as e: + raise e + except: + let e = getCurrentException() + raise newException(PixieError, e.msg, e) + +proc decodeSvg*( + data: string, width = 0, height = 0 +): Image {.raises: [PixieError].} = ## Render SVG file and return the image. Defaults to the SVG's view box size. try: let root = parseXml(data) diff --git a/src/pixie/fontformats/opentype.nim b/src/pixie/fontformats/opentype.nim index a611eed..f6b4d33 100644 --- a/src/pixie/fontformats/opentype.nim +++ b/src/pixie/fontformats/opentype.nim @@ -334,32 +334,34 @@ type when defined(release): {.push checks: off.} -proc eofCheck(buf: string, readTo: int) {.inline.} = +template eofCheck(buf: string, readTo: int) = if readTo > buf.len: raise newException(PixieError, "Unexpected error reading font data, EOF") -proc failUnsupported() = +template failUnsupported() = raise newException(PixieError, "Unsupported font data") -proc readUint16Seq(buf: string, offset, len: int): seq[uint16] = +proc readUint16Seq(buf: string, offset, len: int): seq[uint16] {.raises: [].} = result = newSeq[uint16](len) for i in 0 ..< len: result[i] = buf.readUint16(offset + i * 2).swap() -proc readFixed32(buf: string, offset: int): float32 = +proc readFixed32(buf: string, offset: int): float32 {.raises: [].} = ## Packed 32-bit value with major and minor version numbers. ceil(buf.readInt32(offset).swap().float32 / 65536.0 * 100000.0) / 100000.0 -proc readFixed16(buf: string, offset: int): float32 = +proc readFixed16(buf: string, offset: int): float32 {.raises: [].} = ## Reads 16-bit signed fixed number with the low 14 bits of fraction (2.14). buf.readInt16(offset).swap().float32 / 16384.0 -proc readLongDateTime(buf: string, offset: int): float64 = +proc readLongDateTime(buf: string, offset: int): float64 {.raises: [].} = ## Date and time represented in number of seconds since 12:00 midnight, ## January 1, 1904, UTC. buf.readInt64(offset).swap().float64 - 2082844800 -proc parseCmapTable(buf: string, offset: int): CmapTable = +proc parseCmapTable( + buf: string, offset: int +): CmapTable {.raises: [PixieError].} = var i = offset buf.eofCheck(i + 4) @@ -454,7 +456,9 @@ proc parseCmapTable(buf: string, offset: int): CmapTable = # TODO implement other cmap platformIDs discard -proc parseHeadTable(buf: string, offset: int): HeadTable = +proc parseHeadTable( + buf: string, offset: int +): HeadTable {.raises: [PixieError].} = buf.eofCheck(offset + 54) result = HeadTable() @@ -483,7 +487,9 @@ proc parseHeadTable(buf: string, offset: int): HeadTable = if result.glyphDataFormat != 0: failUnsupported() -proc parseHheaTable(buf: string, offset: int): HheaTable = +proc parseHheaTable( + buf: string, offset: int +): HheaTable {.raises: [PixieError].} = buf.eofCheck(offset + 36) result = HheaTable() @@ -512,7 +518,9 @@ proc parseHheaTable(buf: string, offset: int): HheaTable = failUnsupported() result.numberOfHMetrics = buf.readUint16(offset + 34).swap() -proc parseMaxpTable(buf: string, offset: int): MaxpTable = +proc parseMaxpTable( + buf: string, offset: int +): MaxpTable {.raises: [PixieError].} = buf.eofCheck(offset + 32) result = MaxpTable() @@ -536,7 +544,7 @@ proc parseMaxpTable(buf: string, offset: int): MaxpTable = proc parseHmtxTable( buf: string, offset: int, hhea: HheaTable, maxp: MaxpTable -): HmtxTable = +): HmtxTable {.raises: [PixieError].} = var i = offset let @@ -557,7 +565,9 @@ proc parseHmtxTable( result.leftSideBearings.add(buf.readInt16(i).swap()) i += 2 -proc parseNameTable(buf: string, offset: int): NameTable = +proc parseNameTable( + buf: string, offset: int +): NameTable {.raises: [PixieError].} = var i = offset buf.eofCheck(i + 6) @@ -583,7 +593,9 @@ proc parseNameTable(buf: string, offset: int): NameTable = record.offset = buf.readUint16(i + 10).swap() i += 12 -proc parseOS2Table(buf: string, offset: int): OS2Table = +proc parseOS2Table( + buf: string, offset: int +): OS2Table {.raises: [PixieError].} = var i = offset buf.eofCheck(i + 78) @@ -647,7 +659,7 @@ proc parseOS2Table(buf: string, offset: int): OS2Table = proc parseLocaTable( buf: string, offset: int, head: HeadTable, maxp: MaxpTable -): LocaTable = +): LocaTable {.raises: [PixieError].} = var i = offset result = LocaTable() @@ -664,13 +676,17 @@ proc parseLocaTable( result.offsets.add(buf.readUint32(i).swap()) i += 4 -proc parseGlyfTable(buf: string, offset: int, loca: LocaTable): GlyfTable = +proc parseGlyfTable( + buf: string, offset: int, loca: LocaTable +): GlyfTable {.raises: [].} = result = GlyfTable() result.offsets.setLen(loca.offsets.len) for glyphId in 0 ..< loca.offsets.len: result.offsets[glyphId] = offset.uint32 + loca.offsets[glyphId] -proc parseKernTable(buf: string, offset: int): KernTable = +proc parseKernTable( + buf: string, offset: int +): KernTable {.raises: [PixieError].} = var i = offset buf.eofCheck(i + 2) @@ -721,11 +737,10 @@ proc parseKernTable(buf: string, offset: int): KernTable = if pair.value != 0: let key = (pair.left, pair.right) var value = pair.value.float32 - if key in result.kerningPairs: - if (table.coverage and 0b1000) != 0: # Override - discard - else: # Accumulate - value += result.kerningPairs[key] + if (table.coverage and 0b1000) != 0: # Override + discard + else: # Accumulate + value += result.kerningPairs.getOrDefault(key, 0) result.kerningPairs[key] = value elif version == 1: @@ -733,7 +748,9 @@ proc parseKernTable(buf: string, offset: int): KernTable = else: failUnsupported() -# proc parseLangSys(buf: string, offset: int): LangSys = +# proc parseLangSys( +# buf: string, offset: int +# ): LangSys {.raises: [PixieError].} = # var i = offset # buf.eofCheck(i + 6) @@ -821,14 +838,16 @@ proc parseKernTable(buf: string, offset: int): KernTable = # result.featureRecords.add(featureRecord) # i += 6 -proc parseRangeRecord(buf: string, offset: int): RangeRecord = +proc parseRangeRecord( + buf: string, offset: int +): RangeRecord {.raises: [PixieError].} = buf.eofCheck(offset + 6) result.startGlyphID = buf.readUint16(offset + 0).swap() result.endGlyphID = buf.readUint16(offset + 2).swap() result.startCoverageIndex = buf.readUint16(offset + 4).swap() -proc parseCoverage(buf: string, offset: int): Coverage = +proc parseCoverage(buf: string, offset: int): Coverage {.raises: [PixieError].} = var i = offset buf.eofCheck(i + 4) @@ -866,7 +885,7 @@ proc parseCoverage(buf: string, offset: int): Coverage = else: failUnsupported() -proc valueFormatSize(valueFormat: uint16): int = +proc valueFormatSize(valueFormat: uint16): int {.raises: [].} = # countSetBits(valueFormat) * 2 var n = valueFormat @@ -878,7 +897,7 @@ proc valueFormatSize(valueFormat: uint16): int = proc parseValueRecord( buf: string, offset: int, valueFormat: uint16 -): ValueRecord = +): ValueRecord {.raises: [PixieError].} = buf.eofCheck(offset + valueFormatSize(valueFormat)) var i = offset @@ -909,7 +928,7 @@ proc parseValueRecord( proc parsePairValueRecord( buf: string, offset: int, valueFormat1, valueFormat2: uint16 -): PairValueRecord = +): PairValueRecord {.raises: [PixieError].} = var i = offset buf.eofCheck(i + 2) @@ -923,7 +942,7 @@ proc parsePairValueRecord( proc parsePairSet( buf: string, offset: int, valueFormat1, valueFormat2: uint16 -): PairSet = +): PairSet {.raises: [PixieError].} = var i = offset buf.eofCheck(i + 2) @@ -942,7 +961,7 @@ proc parsePairSet( proc parseClass2Record( buf: string, offset: int, valueFormat1, valueFormat2: uint16 -): Class2Record = +): Class2Record {.raises: [PixieError].} = var i = offset buf.eofCheck( @@ -955,7 +974,7 @@ proc parseClass2Record( proc parseClass1Record( buf: string, offset: int, valueFormat1, valueFormat2, class2Count: uint16 -): Class1Record = +): Class1Record {.raises: [PixieError].} = var i = offset result.class2Records.setLen(class2Count.int) @@ -964,14 +983,16 @@ proc parseClass1Record( parseClass2Record(buf, i, valueFormat1, valueFormat2) i += valueFormatSize(valueFormat1) + valueFormatSize(valueFormat2) -proc parseClassRangeRecord(buf: string, offset: int): ClassRangeRecord = +proc parseClassRangeRecord( + buf: string, offset: int +): ClassRangeRecord {.raises: [PixieError].} = buf.eofCheck(offset + 6) result.startGlyphID = buf.readUint16(offset + 0).swap() result.endGlyphID = buf.readUint16(offset + 2).swap() result.class = buf.readUint16(offset + 4).swap() -proc parseClassDef(buf: string, offset: int): ClassDef = +proc parseClassDef(buf: string, offset: int): ClassDef {.raises: [PixieError].} = var i = offset buf.eofCheck(i + 2) @@ -1003,7 +1024,7 @@ proc parseClassDef(buf: string, offset: int): ClassDef = else: failUnsupported() -proc parsePairPos(buf: string, offset: int): PairPos = +proc parsePairPos(buf: string, offset: int): PairPos {.raises: [PixieError].} = var i = offset buf.eofCheck(i + 4) @@ -1136,7 +1157,9 @@ proc parsePairPos(buf: string, offset: int): PairPos = else: failUnsupported() -proc parseLookup(buf: string, offset: int, gpos: GposTable): Lookup = +proc parseLookup( + buf: string, offset: int, gpos: GposTable +): Lookup {.raises: [PixieError].} = var i = offset buf.eofCheck(i + 6) @@ -1162,7 +1185,9 @@ proc parseLookup(buf: string, offset: int, gpos: GposTable): Lookup = pairPos.classPairAdjustments.len > 0: gpos.lookupList.pairPosTables.add(pairPos) -proc parseLookupList(buf: string, offset: int, gpos: GposTable): LookupList = +proc parseLookupList( + buf: string, offset: int, gpos: GposTable +): LookupList {.raises: [PixieError].} = var i = offset buf.eofCheck(i + 2) @@ -1177,7 +1202,9 @@ proc parseLookupList(buf: string, offset: int, gpos: GposTable): LookupList = for lookupOffset in result.lookupoffsets: result.lookups.add(parseLookup(buf, offset + lookupOffset.int, gpos)) -proc parseGposTable(buf: string, offset: int): GPOSTable = +proc parseGposTable( + buf: string, offset: int +): GPOSTable {.raises: [PixieError].} = var i = offset buf.eofCheck(i + 10) @@ -1208,7 +1235,9 @@ proc parseGposTable(buf: string, offset: int): GPOSTable = result.lookupList = parseLookupList(buf, offset + result.lookupListOffset.int, result) -proc parsePostTable(buf: string, offset: int): PostTable = +proc parsePostTable( + buf: string, offset: int +): PostTable {.raises: [PixieError].} = buf.eofCheck(offset + 14) result = PostTable() @@ -1218,15 +1247,14 @@ proc parsePostTable(buf: string, offset: int): PostTable = 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] - else: - discard # Index 0 is the "missing character" glyph +proc getGlyphId(opentype: OpenType, rune: Rune): uint16 {.inline, raises: [].} = + result = opentype.cmap.runeToGlyphId.getOrDefault(rune, 0) -proc parseGlyph(opentype: OpenType, glyphId: uint16): Path +proc parseGlyph(opentype: OpenType, glyphId: uint16): Path {.raises: [PixieError].} -proc parseGlyphPath(buf: string, offset, numberOfContours: int): Path = +proc parseGlyphPath( + buf: string, offset, numberOfContours: int +): Path {.raises: [PixieError].} = if numberOfContours < 0: raise newException(PixieError, "Glyph numberOfContours must be >= 0") @@ -1359,7 +1387,9 @@ proc parseGlyphPath(buf: string, offset, numberOfContours: int): Path = result.closePath() -proc parseCompositeGlyph(opentype: OpenType, offset: int): Path = +proc parseCompositeGlyph( + opentype: OpenType, offset: int +): Path {.raises: [PixieError].} = result = newPath() var @@ -1454,7 +1484,9 @@ proc parseCompositeGlyph(opentype: OpenType, offset: int): Path = moreComponents = (flags and 0b100000) != 0 -proc parseGlyph(opentype: OpenType, glyphId: uint16): Path = +proc parseGlyph( + opentype: OpenType, glyphId: uint16 +): Path {.raises: [PixieError].} = if glyphId.int >= opentype.glyf.offsets.len: raise newException(PixieError, "Invalid glyph ID " & $glyphId) @@ -1482,16 +1514,21 @@ proc parseGlyph(opentype: OpenType, glyphId: uint16): Path = else: parseGlyphPath(opentype.buf, i, numberOfContours) -proc parseGlyph(opentype: OpenType, rune: Rune): Path {.inline.} = +proc parseGlyph( + opentype: OpenType, rune: Rune +): Path {.inline, raises: [PixieError].} = opentype.parseGlyph(opentype.getGlyphId(rune)) -proc getGlyphPath*(opentype: OpenType, rune: Rune): Path = +proc getGlyphPath*( + opentype: OpenType, rune: Rune +): Path {.raises: [PixieError].} = if rune notin opentype.glyphPaths: - opentype.glyphPaths[rune] = opentype.parseGlyph(rune) - opentype.glyphPaths[rune].transform(scale(vec2(1, -1))) - opentype.glyphPaths[rune] + let path = opentype.parseGlyph(rune) + path.transform(scale(vec2(1, -1))) + opentype.glyphPaths[rune] = path + opentype.glyphPaths.getOrDefault(rune, nil) # Never actually returns nil -proc getLeftSideBearing*(opentype: OpenType, rune: Rune): float32 = +proc getLeftSideBearing*(opentype: OpenType, rune: Rune): float32 {.raises: [].} = let glyphId = opentype.getGlyphId(rune).int if glyphId < opentype.hmtx.hMetrics.len: result = opentype.hmtx.hMetrics[glyphId].leftSideBearing.float32 @@ -1500,21 +1537,23 @@ proc getLeftSideBearing*(opentype: OpenType, rune: Rune): float32 = if index > 0 and index < opentype.hmtx.leftSideBearings.len: result = opentype.hmtx.leftSideBearings[index].float32 -proc getAdvance*(opentype: OpenType, rune: Rune): float32 = +proc getAdvance*(opentype: OpenType, rune: Rune): float32 {.raises: [].} = let glyphId = opentype.getGlyphId(rune).int if glyphId < opentype.hmtx.hMetrics.len: result = opentype.hmtx.hMetrics[glyphId].advanceWidth.float32 else: result = opentype.hmtx.hMetrics[^1].advanceWidth.float32 -proc getKerningAdjustment*(opentype: OpenType, left, right: Rune): float32 = +proc getKerningAdjustment*( + opentype: OpenType, left, right: Rune +): float32 {.raises: [].} = if left notin opentype.cmap.runeToGlyphId or right notin opentype.cmap.runeToGlyphId: return let - leftGlyphId = opentype.cmap.runeToGlyphId[left] - rightGlyphId = opentype.cmap.runeToGlyphId[right] + leftGlyphId = opentype.cmap.runeToGlyphId.getOrDefault(left, 0) + rightGlyphId = opentype.cmap.runeToGlyphId.getOrDefault(right, 0) glyphPair = (leftGlyphId, rightGlyphId) if opentype.gpos != nil: @@ -1525,27 +1564,23 @@ proc getKerningAdjustment*(opentype: OpenType, left, right: Rune): float32 = case pairPos.posFormat: of 1: if glyphPair in pairPos.glyphPairAdjustments: - result = pairPos.glyphPairAdjustments[glyphPair].float32 + result = pairPos.glyphPairAdjustments.getOrDefault(glyphPair, 0).float32 break of 2: - var leftClass, rightClass: uint16 - if leftGlyphId in pairPos.glyphIdToClass1: - leftClass = pairPos.glyphIdToClass1[leftGlyphId] - if rightGlyphId in pairPos.glyphIdToClass2: - rightClass = pairPos.glyphIdToClass2[rightGlyphId] - - let classPair = (leftClass, rightClass) + let + leftClass = pairPos.glyphIdToClass1.getOrDefault(leftGlyphId, 0) + rightClass = pairPos.glyphIdToClass2.getOrDefault(rightGlyphId, 0) + classPair = (leftClass, rightClass) if classPair in pairPos.classPairAdjustments: - result = pairPos.classPairAdjustments[classPair].float32 + result = pairPos.classPairAdjustments.getOrDefault(classPair, 0).float32 break else: discard elif opentype.kern != nil: - if glyphPair in opentype.kern.kerningPairs: - result = opentype.kern.kerningPairs[glyphPair] + result = opentype.kern.kerningPairs.getOrDefault(glyphPair, 0) -proc parseOpenType*(buf: string): OpenType = +proc parseOpenType*(buf: string): OpenType {.raises: [PixieError].} = result = OpenType() result.buf = buf @@ -1572,36 +1607,31 @@ proc parseOpenType*(buf: string): OpenType = result.tableRecords[tableRecord.tag] = tableRecord i += 16 - const requiredTables = [ - "cmap", "head", "hhea", "hmtx", "maxp", "name", "OS/2", "loca", "glyf", - "post" - ] - for table in requiredTables: - if table notin result.tableRecords: - raise newException(PixieError, "Missing required font table " & table) + try: + result.cmap = parseCmapTable(buf, result.tableRecords["cmap"].offset.int) + result.head = parseHeadTable(buf, result.tableRecords["head"].offset.int) + result.hhea = parseHheaTable(buf, result.tableRecords["hhea"].offset.int) + result.maxp = parseMaxpTable(buf, result.tableRecords["maxp"].offset.int) + result.hmtx = parseHmtxTable( + buf, result.tableRecords["hmtx"].offset.int, result.hhea, result.maxp + ) + result.name = parseNameTable(buf, result.tableRecords["name"].offset.int) + result.os2 = parseOS2Table(buf, result.tableRecords["OS/2"].offset.int) + result.loca = parseLocaTable( + buf, result.tableRecords["loca"].offset.int, result.head, result.maxp + ) + result.glyf = + parseGlyfTable(buf, result.tableRecords["glyf"].offset.int, result.loca) - result.cmap = parseCmapTable(buf, result.tableRecords["cmap"].offset.int) - result.head = parseHeadTable(buf, result.tableRecords["head"].offset.int) - result.hhea = parseHheaTable(buf, result.tableRecords["hhea"].offset.int) - result.maxp = parseMaxpTable(buf, result.tableRecords["maxp"].offset.int) - result.hmtx = parseHmtxTable( - buf, result.tableRecords["hmtx"].offset.int, result.hhea, result.maxp - ) - result.name = parseNameTable(buf, result.tableRecords["name"].offset.int) - result.os2 = parseOS2Table(buf, result.tableRecords["OS/2"].offset.int) - result.loca = parseLocaTable( - buf, result.tableRecords["loca"].offset.int, result.head, result.maxp - ) - result.glyf = - parseGlyfTable(buf, result.tableRecords["glyf"].offset.int, result.loca) + if "kern" in result.tableRecords: + result.kern = parseKernTable(buf, result.tableRecords["kern"].offset.int) - if "kern" in result.tableRecords: - result.kern = parseKernTable(buf, result.tableRecords["kern"].offset.int) + if "GPOS" in result.tableRecords: + result.gpos = parseGposTable(buf, result.tableRecords["GPOS"].offset.int) - if "GPOS" in result.tableRecords: - result.gpos = parseGposTable(buf, result.tableRecords["GPOS"].offset.int) - - result.post = parsePostTable(buf, result.tableRecords["post"].offset.int) + result.post = parsePostTable(buf, result.tableRecords["post"].offset.int) + except KeyError as e: + raise newException(PixieError, "Missing required font table: " & e.msg) when defined(release): {.pop.} diff --git a/src/pixie/fontformats/svgfont.nim b/src/pixie/fontformats/svgfont.nim index 62a73b8..add3666 100644 --- a/src/pixie/fontformats/svgfont.nim +++ b/src/pixie/fontformats/svgfont.nim @@ -8,27 +8,22 @@ type SvgFont* = ref object missingGlyphAdvance: float32 missingGlyphPath: Path -proc getGlyphPath*(svgFont: SvgFont, rune: Rune): Path = - if rune in svgFont.glyphPaths: - svgFont.glyphPaths[rune] - else: - svgFont.missingGlyphPath +proc getGlyphPath*(svgFont: SvgFont, rune: Rune): Path {.raises: [].} = + svgFont.glyphPaths.getOrDefault(rune, svgFont.missingGlyphPath) -proc getAdvance*(svgFont: SvgFont, rune: Rune): float32 = - if rune in svgFont.advances: - svgFont.advances[rune] - else: - svgFont.missingGlyphAdvance +proc getAdvance*(svgFont: SvgFont, rune: Rune): float32 {.raises: [].} = + svgFont.advances.getOrDefault(rune, svgFont.missingGlyphAdvance) -proc getKerningAdjustment*(svgFont: SvgFont, left, right: Rune): float32 = +proc getKerningAdjustment*( + svgFont: SvgFont, left, right: Rune +): float32 {.raises: [].} = let pair = (left, right) - if pair in svgFont.kerningPairs: - result = svgFont.kerningPairs[pair] + result = svgFont.kerningPairs.getOrDefault(pair, 0) -proc failInvalid() = +template failInvalid() = raise newException(PixieError, "Invalid SVG font data") -proc parseFloat(node: XmlNode, attr: string): float32 = +proc parseFloat(node: XmlNode, attr: string): float32 {.raises: [PixieError].} = let value = node.attr(attr) if value.len == 0: raise newException(PixieError, "SVG font missing attr " & attr) @@ -37,12 +32,17 @@ proc parseFloat(node: XmlNode, attr: string): float32 = except: failInvalid() -proc parseSvgFont*(buf: string): SvgFont = +proc parseSvgFont*(buf: string): SvgFont {.raises: [PixieError].} = result = SvgFont() - let - root = parseXml(buf) - defs = root.child("defs") + let root = + try: + parseXml(buf) + except: + let e = getCurrentException() + raise newException(PixieError, e.msg, e) + + let defs = root.child("defs") if defs == nil: failInvalid() @@ -75,8 +75,9 @@ proc parseSvgFont*(buf: string): SvgFont = if node.attr("horiz-adv-x").len > 0: advance = node.parseFloat("horiz-adv-x") result.advances[rune] = advance - result.glyphPaths[rune] = parsePath(node.attr("d")) - result.glyphPaths[rune].transform(scale(vec2(1, -1))) + let path = parsePath(node.attr("d")) + path.transform(scale(vec2(1, -1))) + result.glyphPaths[rune] = path else: discard # Multi-rune unicode? of "hkern": diff --git a/src/pixie/fonts.nim b/src/pixie/fonts.nim index 4271404..c171567 100644 --- a/src/pixie/fonts.nim +++ b/src/pixie/fonts.nim @@ -53,46 +53,48 @@ type # tcSmallCaps # tcSmallCapsForced -proc ascent*(typeface: Typeface): float32 {.inline.} = +proc ascent*(typeface: Typeface): float32 {.raises: [].} = ## The font ascender value in font units. if typeface.opentype != nil: typeface.opentype.hhea.ascender.float32 else: typeface.svgFont.ascent -proc descent*(typeface: Typeface): float32 {.inline.} = +proc descent*(typeface: Typeface): float32 {.raises: [].} = ## The font descender value in font units. if typeface.opentype != nil: typeface.opentype.hhea.descender.float32 else: typeface.svgFont.descent -proc lineGap*(typeface: Typeface): float32 {.inline.} = +proc lineGap*(typeface: Typeface): float32 {.raises: [].} = ## The font line gap value in font units. if typeface.opentype != nil: result = typeface.opentype.hhea.lineGap.float32 -proc lineHeight*(typeface: Typeface): float32 {.inline.} = +proc lineHeight*(typeface: Typeface): float32 {.inline, raises: [].} = ## The default line height in font units. typeface.ascent - typeface.descent + typeface.lineGap -proc underlinePosition(typeface: Typeface): float32 {.inline.} = +proc underlinePosition(typeface: Typeface): float32 {.raises: [].} = if typeface.opentype != nil: result = typeface.opentype.post.underlinePosition.float32 -proc underlineThickness(typeface: Typeface): float32 {.inline.} = +proc underlineThickness(typeface: Typeface): float32 {.raises: [].} = if typeface.opentype != nil: result = typeface.opentype.post.underlineThickness.float32 -proc strikeoutPosition(typeface: Typeface): float32 {.inline.} = +proc strikeoutPosition(typeface: Typeface): float32 {.raises: [].} = if typeface.opentype != nil: result = typeface.opentype.os2.yStrikeoutPosition.float32 -proc strikeoutThickness(typeface: Typeface): float32 {.inline.} = +proc strikeoutThickness(typeface: Typeface): float32 {.raises: [].} = if typeface.opentype != nil: result = typeface.opentype.os2.yStrikeoutSize.float32 -proc getGlyphPath*(typeface: Typeface, rune: Rune): Path {.inline.} = +proc getGlyphPath*( + typeface: Typeface, rune: Rune +): Path {.inline, raises: [PixieError].} = ## The glyph path for the rune. result = newPath() if rune.uint32 > SP.uint32: # Empty paths for control runes (not tofu) @@ -101,7 +103,7 @@ proc getGlyphPath*(typeface: Typeface, rune: Rune): Path {.inline.} = else: result.addPath(typeface.svgFont.getGlyphPath(rune)) -proc getAdvance*(typeface: Typeface, rune: Rune): float32 {.inline.} = +proc getAdvance*(typeface: Typeface, rune: Rune): float32 {.inline, raises: [].} = ## The advance for the rune in pixels. if typeface.opentype != nil: typeface.opentype.getAdvance(rune) @@ -110,46 +112,47 @@ proc getAdvance*(typeface: Typeface, rune: Rune): float32 {.inline.} = proc getKerningAdjustment*( typeface: Typeface, left, right: Rune -): float32 {.inline.} = +): float32 {.inline, raises: [].} = ## The kerning adjustment for the rune pair, in pixels. if typeface.opentype != nil: typeface.opentype.getKerningAdjustment(left, right) else: typeface.svgfont.getKerningAdjustment(left, right) -proc scale*(font: Font): float32 {.inline.} = +proc scale*(font: Font): float32 {.inline, raises: [].} = ## The scale factor to transform font units into pixels. if font.typeface.opentype != nil: font.size / font.typeface.opentype.head.unitsPerEm.float32 else: font.size / font.typeface.svgFont.unitsPerEm -proc defaultLineHeight*(font: Font): float32 {.inline.} = +proc defaultLineHeight*(font: Font): float32 {.inline, raises: [].} = ## The default line height in pixels for the current font size. let fontUnits = font.typeface.ascent - font.typeface.descent + font.typeface.lineGap round(fontUnits * font.scale) -proc paint*(font: Font): var Paint = +proc paint*(font: Font): var Paint {.inline, raises: [].} = font.paints[0] -proc `paint=`*(font: Font, paint: Paint) = +proc `paint=`*(font: Font, paint: Paint) {.inline, raises: [].} = font.paints = @[paint] -proc newFont*(typeface: Typeface): Font = +proc newFont*(typeface: Typeface): Font {.raises: [].} = result = Font() result.typeface = typeface result.size = 12 result.lineHeight = AutoLineHeight - result.paint = rgbx(0, 0, 0, 255) + result.paint = newPaint(pkSolid) + result.paint.color = color(0, 0, 0, 1) -proc newSpan*(text: string, font: Font): Span = +proc newSpan*(text: string, font: Font): Span {.raises: [].} = ## Creates a span, associating a font with the text. result = Span() result.text = text result.font = font -proc convertTextCase(runes: var seq[Rune], textCase: TextCase) = +proc convertTextCase(runes: var seq[Rune], textCase: TextCase) {.raises: [].} = case textCase: of tcNormal: discard @@ -166,7 +169,7 @@ proc convertTextCase(runes: var seq[Rune], textCase: TextCase) = rune = rune.toUpper() prevRune = rune -proc canWrap(rune: Rune): bool {.inline.} = +proc canWrap(rune: Rune): bool {.inline, raises: [].} = rune == Rune(32) or rune.isWhiteSpace() proc typeset*( @@ -175,7 +178,7 @@ proc typeset*( hAlign = haLeft, vAlign = vaTop, wrap = true -): Arrangement = +): Arrangement {.raises: [].} = ## Lays out the character glyphs and returns the arrangement. ## Optional parameters: ## bounds: width determines wrapping and hAlign, height for vAlign @@ -396,7 +399,7 @@ proc typeset*( hAlign = haLeft, vAlign = vaTop, wrap = true -): Arrangement {.inline.} = +): Arrangement {.inline, raises: [].} = ## Lays out the character glyphs and returns the arrangement. ## Optional parameters: ## bounds: width determines wrapping and hAlign, height for vAlign @@ -405,7 +408,7 @@ proc typeset*( ## wrap: enable/disable text wrapping typeset(@[newSpan(text, font)], bounds, hAlign, vAlign, wrap) -proc computeBounds*(arrangement: Arrangement): Vec2 = +proc computeBounds*(arrangement: Arrangement): Vec2 {.raises: [].} = ## Computes the width and height of the arrangement in pixels. if arrangement.runes.len > 0: for i in 0 ..< arrangement.runes.len: @@ -415,22 +418,22 @@ proc computeBounds*(arrangement: Arrangement): Vec2 = let finalRect = arrangement.selectionRects[^1] result.y = finalRect.y + finalRect.h -proc computeBounds*(font: Font, text: string): Vec2 {.inline.} = +proc computeBounds*(font: Font, text: string): Vec2 {.inline, raises: [].} = ## Computes the width and height of the text in pixels. font.typeset(text).computeBounds() -proc computeBounds*(spans: seq[Span]): Vec2 {.inline.} = +proc computeBounds*(spans: seq[Span]): Vec2 {.inline, raises: [].} = ## Computes the width and height of the spans in pixels. typeset(spans).computeBounds() -proc parseOtf*(buf: string): Typeface = +proc parseOtf*(buf: string): Typeface {.raises: [PixieError].} = result = Typeface() result.opentype = parseOpenType(buf) -proc parseTtf*(buf: string): Typeface = +proc parseTtf*(buf: string): Typeface {.raises: [PixieError].} = parseOtf(buf) -proc parseSvgFont*(buf: string): Typeface = +proc parseSvgFont*(buf: string): Typeface {.raises: [PixieError].} = result = Typeface() result.svgFont = svgfont.parseSvgFont(buf) @@ -444,7 +447,7 @@ proc textUber( miterLimit = defaultMiterLimit, dashes: seq[float32] = @[], stroke: static[bool] = false -) = +) {.raises: [PixieError].} = var line: int for spanIndex, (start, stop) in arrangement.spans: let @@ -519,7 +522,7 @@ proc fillText*( target: Image | Mask, arrangement: Arrangement, transform = mat3() -) {.inline.} = +) {.inline, raises: [PixieError].} = ## Fills the text arrangement. textUber( target, @@ -535,7 +538,7 @@ proc fillText*( bounds = vec2(0, 0), hAlign = haLeft, vAlign = vaTop -) {.inline.} = +) {.inline, raises: [PixieError].} = ## Typesets and fills the text. Optional parameters: ## transform: translation or matrix to apply ## bounds: width determines wrapping and hAlign, height for vAlign @@ -552,7 +555,7 @@ proc strokeText*( lineJoin = ljMiter, miterLimit = defaultMiterLimit, dashes: seq[float32] = @[] -) {.inline.} = +) {.inline, raises: [PixieError].} = ## Strokes the text arrangement. textUber( target, @@ -579,7 +582,7 @@ proc strokeText*( lineJoin = ljMiter, miterLimit = defaultMiterLimit, dashes: seq[float32] = @[] -) {.inline.} = +) {.inline, raises: [PixieError].} = ## Typesets and strokes the text. Optional parameters: ## transform: translation or matrix to apply ## bounds: width determines wrapping and hAlign, height for vAlign @@ -598,20 +601,24 @@ proc strokeText*( dashes ) -proc readTypeface*(filePath: string): Typeface = +proc readTypeface*(filePath: string): Typeface {.raises: [PixieError].} = ## Loads a typeface from a file. - result = - case splitFile(filePath).ext.toLowerAscii(): - of ".ttf": - parseTtf(readFile(filePath)) - of ".otf": - parseOtf(readFile(filePath)) - of ".svg": - parseSvgFont(readFile(filePath)) - else: - raise newException(PixieError, "Unsupported font format") + try: + result = + case splitFile(filePath).ext.toLowerAscii(): + of ".ttf": + parseTtf(readFile(filePath)) + of ".otf": + parseOtf(readFile(filePath)) + of ".svg": + parseSvgFont(readFile(filePath)) + else: + raise newException(PixieError, "Unsupported font format") + except IOError as e: + raise newException(PixieError, e.msg, e) + result.filePath = filePath -proc readFont*(filePath: string): Font = +proc readFont*(filePath: string): Font {.raises: [PixieError].} = ## Loads a font from a file. newFont(readTypeface(filePath)) diff --git a/src/pixie/images.nim b/src/pixie/images.nim index 754068d..e71b67e 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -14,7 +14,7 @@ type when defined(release): {.push checks: off.} -proc newImage*(width, height: int): Image = +proc newImage*(width, height: int): Image {.raises: [PixieError].} = ## Creates a new image with the parameter dimensions. if width <= 0 or height <= 0: raise newException(PixieError, "Image width and height must be > 0") @@ -24,7 +24,7 @@ proc newImage*(width, height: int): Image = result.height = height result.data = newSeq[ColorRGBX](width * height) -proc newImage*(mask: Mask): Image = +proc newImage*(mask: Mask): Image {.raises: [PixieError].} = result = newImage(mask.width, mask.height) var i: int when defined(amd64) and not defined(pixieNoSimd): @@ -39,59 +39,63 @@ proc newImage*(mask: Mask): Image = let v = mask.data[i] result.data[i] = rgbx(v, v, v, v) -proc wh*(image: Image): Vec2 {.inline.} = +proc wh*(image: Image): Vec2 {.inline, raises: [].} = ## Return with and height as a size vector. vec2(image.width.float32, image.height.float32) -proc copy*(image: Image): Image = +proc copy*(image: Image): Image {.raises: [PixieError].} = ## Copies the image data into a new image. result = newImage(image.width, image.height) result.data = image.data -proc `$`*(image: Image): string = +proc `$`*(image: Image): string {.raises: [].} = ## Prints the image size. "" -proc inside*(image: Image, x, y: int): bool {.inline.} = +proc inside*(image: Image, x, y: int): bool {.inline, raises: [].} = ## Returns true if (x, y) is inside the image. x >= 0 and x < image.width and y >= 0 and y < image.height -proc dataIndex*(image: Image, x, y: int): int {.inline.} = +proc dataIndex*(image: Image, x, y: int): int {.inline, raises: [].} = image.width * y + x -proc getRgbaUnsafe*(image: Image, x, y: int): ColorRGBX {.inline.} = +proc getRgbaUnsafe*(image: Image, x, y: int): ColorRGBX {.inline, raises: [].} = ## Gets a color from (x, y) coordinates. ## * No bounds checking * ## Make sure that x, y are in bounds. ## Failure in the assumptions will case unsafe memory reads. result = image.data[image.width * y + x] -proc `[]`*(image: Image, x, y: int): ColorRGBX {.inline.} = +proc `[]`*(image: Image, x, y: int): ColorRGBX {.inline, raises: [].} = ## Gets a pixel at (x, y) or returns transparent black if outside of bounds. if image.inside(x, y): return image.getRgbaUnsafe(x, y) -proc getColor*(image: Image, x, y: int): Color = +proc getColor*(image: Image, x, y: int): Color {.inline, raises: [].} = ## Gets a color at (x, y) or returns transparent black if outside of bounds. image[x, y].color() -proc setRgbaUnsafe*(image: Image, x, y: int, color: SomeColor) {.inline.} = +proc setRgbaUnsafe*( + image: Image, x, y: int, color: SomeColor +) {.inline, raises: [].} = ## Sets a color from (x, y) coordinates. ## * No bounds checking * ## Make sure that x, y are in bounds. ## Failure in the assumptions will case unsafe memory writes. image.data[image.dataIndex(x, y)] = color.asRgbx() -proc `[]=`*(image: Image, x, y: int, color: SomeColor) {.inline.} = +proc `[]=`*(image: Image, x, y: int, color: SomeColor) {.inline, raises: [].} = ## Sets a pixel at (x, y) or does nothing if outside of bounds. if image.inside(x, y): image.setRgbaUnsafe(x, y, color.asRgbx()) -proc setColor*(image: Image, x, y: int, color: Color) = +proc setColor*(image: Image, x, y: int, color: Color) {.inline, raises: [].} = ## Sets a color at (x, y) or does nothing if outside of bounds. image[x, y] = color.rgbx() -proc fillUnsafe*(data: var seq[ColorRGBX], color: SomeColor, start, len: int) = +proc fillUnsafe*( + data: var seq[ColorRGBX], color: SomeColor, start, len: int +) {.raises: [].} = ## Fills the image data with the parameter color starting at index start and ## continuing for len indices. @@ -122,11 +126,11 @@ proc fillUnsafe*(data: var seq[ColorRGBX], color: SomeColor, start, len: int) = for j in i ..< start + len: data[j] = rgbx -proc fill*(image: Image, color: SomeColor) {.inline.} = +proc fill*(image: Image, color: SomeColor) {.inline, raises: [].} = ## Fills the image with the parameter color. fillUnsafe(image.data, color, 0, image.data.len) -proc isOneColor*(image: Image): bool = +proc isOneColor*(image: Image): bool {.raises: [].} = ## Checks if the entire image is the same color. result = true @@ -149,7 +153,7 @@ proc isOneColor*(image: Image): bool = if image.data[j] != color: return false -proc isTransparent*(image: Image): bool = +proc isTransparent*(image: Image): bool {.raises: [].} = ## Checks if this image is fully transparent or not. result = true @@ -174,7 +178,7 @@ proc isTransparent*(image: Image): bool = if image.data[j].a != 0: return false -proc flipHorizontal*(image: Image) = +proc flipHorizontal*(image: Image) {.raises: [].} = ## Flips the image around the Y axis. let w = image.width div 2 for y in 0 ..< image.height: @@ -185,7 +189,7 @@ proc flipHorizontal*(image: Image) = image.setRgbaUnsafe(image.width - x - 1, y, rgba1) image.setRgbaUnsafe(x, y, rgba2) -proc flipVertical*(image: Image) = +proc flipVertical*(image: Image) {.raises: [].} = ## Flips the image around the X axis. let h = image.height div 2 for y in 0 ..< h: @@ -196,7 +200,7 @@ proc flipVertical*(image: Image) = image.setRgbaUnsafe(x, image.height - y - 1, rgba1) image.setRgbaUnsafe(x, y, rgba2) -proc subImage*(image: Image, x, y, w, h: int): Image = +proc subImage*(image: Image, x, y, w, h: int): Image {.raises: [PixieError].} = ## Gets a sub image from this image. if x < 0 or x + w > image.width: @@ -218,7 +222,7 @@ proc subImage*(image: Image, x, y, w, h: int): Image = w * 4 ) -proc diff*(master, image: Image): (float32, Image) = +proc diff*(master, image: Image): (float32, Image) {.raises: [PixieError].} = ## Compares the parameters and returns a score and image of the difference. let w = max(master.width, image.width) @@ -248,7 +252,7 @@ proc diff*(master, image: Image): (float32, Image) = (100 * diffScore.float32 / diffTotal.float32, diffImage) -proc minifyBy2*(image: Image, power = 1): Image = +proc minifyBy2*(image: Image, power = 1): Image {.raises: [PixieError].} = ## Scales the image down by an integer scale. if power < 0: raise newException(PixieError, "Cannot minifyBy2 with negative power") @@ -323,7 +327,7 @@ proc minifyBy2*(image: Image, power = 1): Image = # Set src as this result for if we do another power src = result -proc magnifyBy2*(image: Image, power = 1): Image = +proc magnifyBy2*(image: Image, power = 1): Image {.raises: [PixieError].} = ## Scales image up by 2 ^ power. if power < 0: raise newException(PixieError, "Cannot magnifyBy2 with negative power") @@ -339,7 +343,7 @@ proc magnifyBy2*(image: Image, power = 1): Image = for i in 0 ..< scale: result.data[idx + i] = rgba -proc applyOpacity*(target: Image | Mask, opacity: float32) = +proc applyOpacity*(target: Image | Mask, opacity: float32) {.raises: [].} = ## Multiplies alpha of the image by opacity. let opacity = round(255 * opacity).uint16 @@ -405,7 +409,7 @@ proc applyOpacity*(target: Image | Mask, opacity: float32) = for j in i ..< target.data.len: target.data[j] = ((target.data[j] * opacity) div 255).uint8 -proc invert*(target: Image | Mask) = +proc invert*(target: Image | Mask) {.raises: [].} = ## Inverts all of the colors and alpha. var i: int when defined(amd64) and not defined(pixieNoSimd): @@ -447,7 +451,7 @@ proc invert*(target: Image | Mask) = proc blur*( image: Image, radius: float32, outOfBounds: SomeColor = ColorRGBX() -) = +) {.raises: [PixieError].} = ## Applies Gaussian blur to the image given a radius. let radius = round(radius).int if radius == 0: @@ -512,7 +516,7 @@ proc blur*( image.setRgbaUnsafe(x, y, rgbx(values)) -proc newMask*(image: Image): Mask = +proc newMask*(image: Image): Mask {.raises: [PixieError].} = ## Returns a new mask using the alpha values of the parameter image. result = newMask(image.width, image.height) @@ -544,7 +548,9 @@ proc newMask*(image: Image): Mask = for j in i ..< image.data.len: result.data[j] = image.data[j].a -proc getRgbaSmooth*(image: Image, x, y: float32, wrapped = false): ColorRGBX = +proc getRgbaSmooth*( + image: Image, x, y: float32, wrapped = false +): ColorRGBX {.raises: [].} = ## Gets a interpolated color with float point coordinates. ## Pixes outside the image are transparent. let @@ -582,7 +588,7 @@ proc getRgbaSmooth*(image: Image, x, y: float32, wrapped = false): ColorRGBX = proc drawCorrect( a, b: Image | Mask, mat = mat3(), tiled = false, blendMode = bmNormal -) = +) {.raises: [PixieError].} = ## Draws one image onto another using matrix with color blending. when type(a) is Image: @@ -634,7 +640,9 @@ proc drawCorrect( let sample = b.getValueSmooth(xFloat, yFloat) a.setValueUnsafe(x, y, masker(backdrop, sample)) -proc drawUber(a, b: Image | Mask, mat = mat3(), blendMode = bmNormal) = +proc drawUber( + a, b: Image | Mask, mat = mat3(), blendMode = bmNormal +) {.raises: [PixieError].} = let corners = [ mat * vec2(0, 0), @@ -840,7 +848,7 @@ proc drawUber(a, b: Image | Mask, mat = mat3(), blendMode = bmNormal) = proc draw*( a, b: Image, transform = mat3(), blendMode = bmNormal -) {.inline.} = +) {.inline, raises: [PixieError].} = ## Draws one image onto another using matrix with color blending. when type(transform) is Vec2: a.drawUber(b, translate(transform), blendMode) @@ -849,7 +857,7 @@ proc draw*( proc draw*( a, b: Mask, transform = mat3(), blendMode = bmMask -) {.inline.} = +) {.inline, raises: [PixieError].} = ## Draws a mask onto a mask using a matrix with color blending. when type(transform) is Vec2: a.drawUber(b, translate(transform), blendMode) @@ -858,7 +866,7 @@ proc draw*( proc draw*( image: Image, mask: Mask, transform = mat3(), blendMode = bmMask -) {.inline.} = +) {.inline, raises: [PixieError].} = ## Draws a mask onto an image using a matrix with color blending. when type(transform) is Vec2: image.drawUber(mask, translate(transform), blendMode) @@ -867,17 +875,19 @@ proc draw*( proc draw*( mask: Mask, image: Image, transform = mat3(), blendMode = bmMask -) {.inline.} = +) {.inline, raises: [PixieError].} = ## Draws a image onto a mask using a matrix with color blending. when type(transform) is Vec2: mask.drawUber(image, translate(transform), blendMode) else: mask.drawUber(image, transform, blendMode) -proc drawTiled*(dest, src: Image, mat: Mat3, blendMode = bmNormal) = - dest.drawCorrect(src, mat, true, blendMode) +proc drawTiled*( + dst, src: Image, mat: Mat3, blendMode = bmNormal +) {.raises: [PixieError]} = + dst.drawCorrect(src, mat, true, blendMode) -proc resize*(srcImage: Image, width, height: int): Image = +proc resize*(srcImage: Image, width, height: int): Image {.raises: [PixieError]} = ## Resize an image to a given height and width. if width == srcImage.width and height == srcImage.height: result = srcImage.copy() @@ -894,7 +904,7 @@ proc resize*(srcImage: Image, width, height: int): Image = proc shadow*( image: Image, offset: Vec2, spread, blur: float32, color: SomeColor -): Image = +): Image {.raises: [PixieError]} = ## Create a shadow of the image with the offset, spread and blur. let mask = image.newMask() @@ -906,7 +916,7 @@ proc shadow*( result.fill(color) result.draw(shifted, blendMode = bmMask) -proc superImage*(image: Image, x, y, w, h: int): Image = +proc superImage*(image: Image, x, y, w, h: int): Image {.raises: [PixieError]} = ## Either cuts a sub image or returns a super image with padded transparency. if x >= 0 and x + w <= image.width and y >= 0 and y + h <= image.height: result = image.subImage(x, y, w, h) diff --git a/src/pixie/masks.nim b/src/pixie/masks.nim index b234032..5f5f20b 100644 --- a/src/pixie/masks.nim +++ b/src/pixie/masks.nim @@ -12,7 +12,7 @@ type when defined(release): {.push checks: off.} -proc newMask*(width, height: int): Mask = +proc newMask*(width, height: int): Mask {.raises: [PixieError].} = ## Creates a new mask with the parameter dimensions. if width <= 0 or height <= 0: raise newException(PixieError, "Mask width and height must be > 0") @@ -22,59 +22,59 @@ proc newMask*(width, height: int): Mask = result.height = height result.data = newSeq[uint8](width * height) -proc wh*(mask: Mask): Vec2 {.inline.} = +proc wh*(mask: Mask): Vec2 {.inline, raises: [].} = ## Return with and height as a size vector. vec2(mask.width.float32, mask.height.float32) -proc copy*(mask: Mask): Mask = +proc copy*(mask: Mask): Mask {.raises: [PixieError].} = ## Copies the image data into a new image. result = newMask(mask.width, mask.height) result.data = mask.data -proc `$`*(mask: Mask): string = +proc `$`*(mask: Mask): string {.raises: [].} = ## Prints the mask size. "" -proc inside*(mask: Mask, x, y: int): bool {.inline.} = +proc inside*(mask: Mask, x, y: int): bool {.inline, raises: [].} = ## Returns true if (x, y) is inside the mask. x >= 0 and x < mask.width and y >= 0 and y < mask.height -proc dataIndex*(mask: Mask, x, y: int): int {.inline.} = +proc dataIndex*(mask: Mask, x, y: int): int {.inline, raises: [].} = mask.width * y + x -proc getValueUnsafe*(mask: Mask, x, y: int): uint8 {.inline.} = +proc getValueUnsafe*(mask: Mask, x, y: int): uint8 {.inline, raises: [].} = ## Gets a value from (x, y) coordinates. ## * No bounds checking * ## Make sure that x, y are in bounds. ## Failure in the assumptions will case unsafe memory reads. result = mask.data[mask.width * y + x] -proc `[]`*(mask: Mask, x, y: int): uint8 {.inline.} = +proc `[]`*(mask: Mask, x, y: int): uint8 {.inline, raises: [].} = ## Gets a value at (x, y) or returns transparent black if outside of bounds. if mask.inside(x, y): return mask.getValueUnsafe(x, y) -proc getValue*(mask: Mask, x, y: int): uint8 {.inline.} = +proc getValue*(mask: Mask, x, y: int): uint8 {.inline, raises: [].} = ## Gets a value at (x, y) or returns transparent black if outside of bounds. mask[x, y] -proc setValueUnsafe*(mask: Mask, x, y: int, value: uint8) {.inline.} = +proc setValueUnsafe*(mask: Mask, x, y: int, value: uint8) {.inline, raises: [].} = ## Sets a value from (x, y) coordinates. ## * No bounds checking * ## Make sure that x, y are in bounds. ## Failure in the assumptions will case unsafe memory writes. mask.data[mask.dataIndex(x, y)] = value -proc `[]=`*(mask: Mask, x, y: int, value: uint8) {.inline.} = +proc `[]=`*(mask: Mask, x, y: int, value: uint8) {.inline, raises: [].} = ## Sets a value at (x, y) or does nothing if outside of bounds. if mask.inside(x, y): mask.setValueUnsafe(x, y, value) -proc setValue*(mask: Mask, x, y: int, value: uint8) {.inline.} = +proc setValue*(mask: Mask, x, y: int, value: uint8) {.inline, raises: [].} = ## Sets a value at (x, y) or does nothing if outside of bounds. mask[x, y] = value -proc minifyBy2*(mask: Mask, power = 1): Mask = +proc minifyBy2*(mask: Mask, power = 1): Mask {.raises: [PixieError].} = ## Scales the mask down by an integer scale. if power < 0: raise newException(PixieError, "Cannot minifyBy2 with negative power") @@ -157,16 +157,18 @@ proc minifyBy2*(mask: Mask, power = 1): Mask = # Set src as this result for if we do another power src = result -proc fillUnsafe*(data: var seq[uint8], value: uint8, start, len: int) = +proc fillUnsafe*( + data: var seq[uint8], value: uint8, start, len: int +) {.raises: [].} = ## Fills the mask data with the parameter value starting at index start and ## continuing for len indices. nimSetMem(data[start].addr, value.cint, len) -proc fill*(mask: Mask, value: uint8) {.inline.} = +proc fill*(mask: Mask, value: uint8) {.inline, raises: [].} = ## Fills the mask with the parameter value. fillUnsafe(mask.data, value, 0, mask.data.len) -proc getValueSmooth*(mask: Mask, x, y: float32): uint8 = +proc getValueSmooth*(mask: Mask, x, y: float32): uint8 {.raises: [].} = ## Gets a interpolated value with float point coordinates. let x0 = x.int @@ -194,7 +196,7 @@ proc getValueSmooth*(mask: Mask, x, y: float32): uint8 = else: topMix -proc spread*(mask: Mask, spread: float32) = +proc spread*(mask: Mask, spread: float32) {.raises: [PixieError].} = ## Grows the mask by spread. let spread = round(spread).int if spread == 0: @@ -227,7 +229,7 @@ proc spread*(mask: Mask, spread: float32) = break mask.setValueUnsafe(x, y, maxValue) -proc ceil*(mask: Mask) = +proc ceil*(mask: Mask) {.raises: [].} = ## A value of 0 stays 0. Anything else turns into 255. var i: int when defined(amd64) and not defined(pixieNoSimd): @@ -245,7 +247,7 @@ proc ceil*(mask: Mask) = if mask.data[j] != 0: mask.data[j] = 255 -proc blur*(mask: Mask, radius: float32, outOfBounds: uint8 = 0) = +proc blur*(mask: Mask, radius: float32, outOfBounds: uint8 = 0) {.raises: [PixieError].} = ## Applies Gaussian blur to the image given a radius. let radius = round(radius).int if radius == 0: diff --git a/src/pixie/paints.nim b/src/pixie/paints.nim index 131701c..7f3470a 100644 --- a/src/pixie/paints.nim +++ b/src/pixie/paints.nim @@ -30,11 +30,11 @@ type SomePaint* = string | Paint | SomeColor -proc newPaint*(kind: PaintKind): Paint = +proc newPaint*(kind: PaintKind): Paint {.raises: [].} = ## Create a new Paint. result = Paint(kind: kind, opacity: 1, imageMat: mat3()) -proc newPaint*(paint: Paint): Paint = +proc newPaint*(paint: Paint): Paint {.raises: [].} = ## Create a new Paint with the same properties. result = newPaint(paint.kind) result.blendMode = paint.blendMode @@ -45,11 +45,16 @@ proc newPaint*(paint: Paint): Paint = result.gradientHandlePositions = paint.gradientHandlePositions result.gradientStops = paint.gradientStops -converter parseSomePaint*(paint: SomePaint): Paint {.inline.} = +converter parseSomePaint*( + paint: SomePaint +): Paint {.inline, raises: [PixieError].} = ## Given SomePaint, parse it in different ways. when type(paint) is string: result = newPaint(pkSolid) - result.color = parseHtmlColor(paint) + try: + result.color = parseHtmlColor(paint) + except: + raise newException(PixieError, "Unable to parse color " & paint) elif type(paint) is SomeColor: result = newPaint(pkSolid) when type(paint) is Color: @@ -59,7 +64,7 @@ converter parseSomePaint*(paint: SomePaint): Paint {.inline.} = elif type(paint) is Paint: paint -proc toLineSpace(at, to, point: Vec2): float32 {.inline.} = +proc toLineSpace(at, to, point: Vec2): float32 {.inline, raises: [].} = ## Convert position on to where it would fall on a line between at and to. let d = to - at @@ -68,7 +73,7 @@ proc toLineSpace(at, to, point: Vec2): float32 {.inline.} = proc gradientPut( image: Image, paint: Paint, x, y: int, t: float32, stops: seq[ColorStop] -) = +) {.raises: [].} = ## Put an gradient color based on `t` - where are we related to a line. var index = -1 for i, stop in stops: @@ -95,7 +100,7 @@ proc gradientPut( color.a *= paint.opacity image.setRgbaUnsafe(x, y, color.rgbx()) -proc fillGradientLinear(image: Image, paint: Paint) = +proc fillGradientLinear(image: Image, paint: Paint) {.raises: [PixieError].} = ## Fills a linear gradient. if paint.gradientHandlePositions.len != 2: @@ -117,7 +122,7 @@ proc fillGradientLinear(image: Image, paint: Paint) = t = toLineSpace(at, to, xy) image.gradientPut(paint, x, y, t, paint.gradientStops) -proc fillGradientRadial(image: Image, paint: Paint) = +proc fillGradientRadial(image: Image, paint: Paint) {.raises: [PixieError].} = ## Fills a radial gradient. if paint.gradientHandlePositions.len != 3: @@ -148,7 +153,7 @@ proc fillGradientRadial(image: Image, paint: Paint) = t = (mat * xy).length() image.gradientPut(paint, x, y, t, paint.gradientStops) -proc fillGradientAngular(image: Image, paint: Paint) = +proc fillGradientAngular(image: Image, paint: Paint) {.raises: [PixieError].} = ## Fills an angular gradient. if paint.gradientHandlePositions.len != 3: @@ -173,7 +178,7 @@ proc fillGradientAngular(image: Image, paint: Paint) = t = (angle + gradientAngle + PI / 2).fixAngle() / 2 / PI + 0.5 image.gradientPut(paint, x, y, t, paint.gradientStops) -proc fillGradient*(image: Image, paint: Paint) = +proc fillGradient*(image: Image, paint: Paint) {.raises: [PixieError].} = ## Fills with the Paint gradient. case paint.kind: diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index ed312f8..e8bb547 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -46,23 +46,23 @@ const when defined(release): {.push checks: off.} -proc newPath*(): Path = +proc newPath*(): Path {.raises: [].} = ## Create a new Path. Path() -proc pixelScale(transform: Mat3): float32 = +proc pixelScale(transform: Mat3): float32 {.raises: [].} = ## What is the largest scale factor of this transform? max( vec2(transform[0, 0], transform[0, 1]).length, vec2(transform[1, 0], transform[1, 1]).length ) -proc isRelative(kind: PathCommandKind): bool = +proc isRelative(kind: PathCommandKind): bool {.inline, raises: [].} = kind in { RMove, RLine, TQuad, RTQuad, RHLine, RVLine, RCubic, RSCubic, RQuad, RArc } -proc parameterCount(kind: PathCommandKind): int = +proc parameterCount(kind: PathCommandKind): int {.raises: [].} = ## Returns number of parameters a path command has. case kind: of Close: 0 @@ -72,7 +72,7 @@ proc parameterCount(kind: PathCommandKind): int = of SCubic, RSCubic, Quad, RQuad: 4 of Arc, RArc: 7 -proc `$`*(path: Path): string = +proc `$`*(path: Path): string {.raises: [].} = ## Turn path int into a string. for i, command in path.commands: case command.kind @@ -103,7 +103,7 @@ proc `$`*(path: Path): string = if i != path.commands.len - 1 or j != command.numbers.len - 1: result.add " " -proc parsePath*(path: string): Path = +proc parsePath*(path: string): Path {.raises: [PixieError].} = ## Converts a SVG style path string into seq of commands. result = newPath() @@ -249,7 +249,7 @@ proc parsePath*(path: string): Path = finishCommand(result) -proc transform*(path: Path, mat: Mat3) = +proc transform*(path: Path, mat: Mat3) {.raises: [].} = ## Apply a matrix transform to a path. if mat == mat3(): return @@ -314,39 +314,39 @@ proc transform*(path: Path, mat: Mat3) = command.numbers[5] = to.x command.numbers[6] = to.y -proc addPath*(path: Path, other: Path) = +proc addPath*(path: Path, other: Path) {.raises: [].} = ## Adds a path to the current path. path.commands.add(other.commands) -proc closePath*(path: Path) = +proc closePath*(path: Path) {.raises: [].} = ## Attempts to add a straight line from the current point to the start of ## the current sub-path. If the shape has already been closed or has only ## one point, this function does nothing. path.commands.add(PathCommand(kind: Close)) path.at = path.start -proc moveTo*(path: Path, x, y: float32) = +proc moveTo*(path: Path, x, y: float32) {.raises: [].} = ## Begins a new sub-path at the point (x, y). path.commands.add(PathCommand(kind: Move, numbers: @[x, y])) path.start = vec2(x, y) path.at = path.start -proc moveTo*(path: Path, v: Vec2) {.inline.} = +proc moveTo*(path: Path, v: Vec2) {.inline, raises: [].} = ## Begins a new sub-path at the point (x, y). path.moveTo(v.x, v.y) -proc lineTo*(path: Path, x, y: float32) = +proc lineTo*(path: Path, x, y: float32) {.raises: [].} = ## Adds a straight line to the current sub-path by connecting the sub-path's ## last point to the specified (x, y) coordinates. path.commands.add(PathCommand(kind: Line, numbers: @[x, y])) path.at = vec2(x, y) -proc lineTo*(path: Path, v: Vec2) {.inline.} = +proc lineTo*(path: Path, v: Vec2) {.inline, raises: [].} = ## Adds a straight line to the current sub-path by connecting the sub-path's ## last point to the specified (x, y) coordinates. path.lineTo(v.x, v.y) -proc bezierCurveTo*(path: Path, x1, y1, x2, y2, x3, y3: float32) = +proc bezierCurveTo*(path: Path, x1, y1, x2, y2, x3, y3: float32) {.raises: [].} = ## Adds a cubic Bézier curve to the current sub-path. It requires three ## points: the first two are control points and the third one is the end ## point. The starting point is the latest point in the current path, @@ -357,14 +357,14 @@ proc bezierCurveTo*(path: Path, x1, y1, x2, y2, x3, y3: float32) = )) path.at = vec2(x3, y3) -proc bezierCurveTo*(path: Path, ctrl1, ctrl2, to: Vec2) {.inline.} = +proc bezierCurveTo*(path: Path, ctrl1, ctrl2, to: Vec2) {.inline, raises: [].} = ## Adds a cubic Bézier curve to the current sub-path. It requires three ## points: the first two are control points and the third one is the end ## point. The starting point is the latest point in the current path, ## which can be changed using moveTo() before creating the Bézier curve. path.bezierCurveTo(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, to.x, to.y) -proc quadraticCurveTo*(path: Path, x1, y1, x2, y2: float32) = +proc quadraticCurveTo*(path: Path, x1, y1, x2, y2: float32) {.raises: [].} = ## Adds a quadratic Bézier curve to the current sub-path. It requires two ## points: the first one is a control point and the second one is the end ## point. The starting point is the latest point in the current path, @@ -376,7 +376,7 @@ proc quadraticCurveTo*(path: Path, x1, y1, x2, y2: float32) = )) path.at = vec2(x2, y2) -proc quadraticCurveTo*(path: Path, ctrl, to: Vec2) {.inline.} = +proc quadraticCurveTo*(path: Path, ctrl, to: Vec2) {.inline, raises: [].} = ## Adds a quadratic Bézier curve to the current sub-path. It requires two ## points: the first one is a control point and the second one is the end ## point. The starting point is the latest point in the current path, @@ -390,7 +390,7 @@ proc ellipticalArcTo*( xAxisRotation: float32, largeArcFlag, sweepFlag: bool, x, y: float32 -) = +) {.raises: [].} = ## Adds an elliptical arc to the current sub-path, using the given radius ## ratios, sweep flags, and end position. path.commands.add(PathCommand( @@ -401,7 +401,9 @@ proc ellipticalArcTo*( )) path.at = vec2(x, y) -proc arc*(path: Path, x, y, r, a0, a1: float32, ccw: bool) = +proc arc*( + path: Path, x, y, r, a0, a1: float32, ccw: bool +) {.raises: [PixieError].} = ## Adds a circular arc to the current sub-path. if r == 0: # When radius is zero, do nothing. return @@ -438,11 +440,13 @@ proc arc*(path: Path, x, y, r, a0, a1: float32, ccw: bool) = path.at.y = y + r * sin(a1) path.ellipticalArcTo(r, r, 0, angle >= PI, cw, path.at.x, path.at.y) -proc arc*(path: Path, pos: Vec2, r: float32, a: Vec2, ccw: bool = false) = +proc arc*( + path: Path, pos: Vec2, r: float32, a: Vec2, ccw: bool = false +) {.inline, raises: [PixieError].} = ## Adds a circular arc to the current sub-path. path.arc(pos.x, pos.y, r, a.x, a.y, ccw) -proc arcTo*(path: Path, x1, y1, x2, y2, r: float32) = +proc arcTo*(path: Path, x1, y1, x2, y2, r: float32) {.raises: [PixieError].} = ## Adds a circular arc using the given control points and radius. ## Commonly used for making rounded corners. if r < 0: # When radius is negative, error. @@ -483,11 +487,11 @@ proc arcTo*(path: Path, x1, y1, x2, y2, r: float32) = path.at.y = y1 + t21 * y21 path.ellipticalArcTo(r, r, 0, false, y01 * x20 > x01 * y20, path.at.x, path.at.y) -proc arcTo*(path: Path, a, b: Vec2, r: float32) = +proc arcTo*(path: Path, a, b: Vec2, r: float32) {.inline, raises: [PixieError].} = ## Adds a circular arc using the given control points and radius. path.arcTo(a.x, a.y, b.x, b.y, r) -proc rect*(path: Path, x, y, w, h: float32, clockwise = true) = +proc rect*(path: Path, x, y, w, h: float32, clockwise = true) {.raises: [].} = ## Adds a rectangle. ## Clockwise param can be used to subtract a rect from a path when using ## even-odd winding rule. @@ -504,7 +508,7 @@ proc rect*(path: Path, x, y, w, h: float32, clockwise = true) = path.lineTo(x + w, y) path.closePath() -proc rect*(path: Path, rect: Rect, clockwise = true) {.inline.} = +proc rect*(path: Path, rect: Rect, clockwise = true) {.inline, raises: [].} = ## Adds a rectangle. ## Clockwise param can be used to subtract a rect from a path when using ## even-odd winding rule. @@ -516,7 +520,7 @@ const splineCircleK = 4.0 * (-1.0 + sqrt(2.0)) / 3 proc roundedRect*( path: Path, x, y, w, h, nw, ne, se, sw: float32, clockwise = true -) = +) {.raises: [].} = ## Adds a rounded rectangle. ## Clockwise param can be used to subtract a rect from a path when using ## even-odd winding rule. @@ -583,13 +587,13 @@ proc roundedRect*( proc roundedRect*( path: Path, rect: Rect, nw, ne, se, sw: float32, clockwise = true -) {.inline.} = +) {.inline, raises: [].} = ## Adds a rounded rectangle. ## Clockwise param can be used to subtract a rect from a path when using ## even-odd winding rule. path.roundedRect(rect.x, rect.y, rect.w, rect.h, nw, ne, se, sw, clockwise) -proc ellipse*(path: Path, cx, cy, rx, ry: float32) = +proc ellipse*(path: Path, cx, cy, rx, ry: float32) {.raises: [].} = ## Adds a ellipse. let magicX = splineCircleK * rx @@ -602,19 +606,19 @@ proc ellipse*(path: Path, cx, cy, rx, ry: float32) = path.bezierCurveTo(cx + magicX, cy - ry, cx + rx, cy - magicY, cx + rx, cy) path.closePath() -proc ellipse*(path: Path, center: Vec2, rx, ry: float32) {.inline.} = +proc ellipse*(path: Path, center: Vec2, rx, ry: float32) {.inline, raises: [].} = ## Adds a ellipse. path.ellipse(center.x, center.y, rx, ry) -proc circle*(path: Path, cx, cy, r: float32) {.inline.} = +proc circle*(path: Path, cx, cy, r: float32) {.inline, raises: [].} = ## Adds a circle. path.ellipse(cx, cy, r, r) -proc circle*(path: Path, circle: Circle) {.inline.} = +proc circle*(path: Path, circle: Circle) {.inline, raises: [].} = ## Adds a circle. path.ellipse(circle.pos.x, circle.pos.y, circle.radius, circle.radius) -proc polygon*(path: Path, x, y, size: float32, sides: int) = +proc polygon*(path: Path, x, y, size: float32, sides: int) {.raises: [].} = ## Adds an n-sided regular polygon at (x, y) with the parameter size. path.moveTo(x + size * cos(0.0), y + size * sin(0.0)) for side in 0 .. sides: @@ -623,13 +627,15 @@ proc polygon*(path: Path, x, y, size: float32, sides: int) = y + size * sin(side.float32 * 2.0 * PI / sides.float32) ) -proc polygon*(path: Path, pos: Vec2, size: float32, sides: int) {.inline.} = +proc polygon*( + path: Path, pos: Vec2, size: float32, sides: int +) {.inline, raises: [].} = ## Adds a n-sided regular polygon at (x, y) with the parameter size. path.polygon(pos.x, pos.y, size, sides) proc commandsToShapes( path: Path, closeSubpaths = false, pixelScale: float32 = 1.0 -): seq[seq[Vec2]] = +): seq[seq[Vec2]] {.raises: [PixieError].} = ## Converts SVG-like commands to sequences of vectors. var start, at: Vec2 @@ -991,7 +997,9 @@ proc commandsToShapes( shape.addSegment(at, start) result.add(shape) -proc shapesToSegments(shapes: seq[seq[Vec2]]): seq[(Segment, int16)] = +proc shapesToSegments( + shapes: seq[seq[Vec2]] +): seq[(Segment, int16)] {.raises: [].} = ## Converts the shapes into a set of filtered segments with winding value. for shape in shapes: for segment in shape.segments: @@ -1006,7 +1014,7 @@ proc shapesToSegments(shapes: seq[seq[Vec2]]): seq[(Segment, int16)] = result.add((segment, winding)) -proc requiresAntiAliasing(segments: seq[(Segment, int16)]): bool = +proc requiresAntiAliasing(segments: seq[(Segment, int16)]): bool {.raises: [].} = ## Returns true if the fill requires antialiasing. template hasFractional(v: float32): bool = @@ -1021,13 +1029,13 @@ proc requiresAntiAliasing(segments: seq[(Segment, int16)]): bool = # AA is required if all segments are not vertical or have fractional > 0 return true -proc transform(shapes: var seq[seq[Vec2]], transform: Mat3) = +proc transform(shapes: var seq[seq[Vec2]], transform: Mat3) {.raises: [].} = if transform != mat3(): for shape in shapes.mitems: for vec in shape.mitems: vec = transform * vec -proc computeBounds(segments: seq[(Segment, int16)]): Rect = +proc computeBounds(segments: seq[(Segment, int16)]): Rect {.raises: [].} = ## Compute the bounds of the segments. var xMin = float32.high @@ -1049,7 +1057,9 @@ proc computeBounds(segments: seq[(Segment, int16)]): Rect = result.w = xMax - xMin result.h = yMax - yMin -proc computeBounds*(path: Path, transform = mat3()): Rect = +proc computeBounds*( + path: Path, transform = mat3() +): Rect {.raises: [PixieError].} = ## Compute the bounds of the path. var shapes = path.commandsToShapes() shapes.transform(transform) @@ -1057,7 +1067,7 @@ proc computeBounds*(path: Path, transform = mat3()): Rect = proc partitionSegments( segments: seq[(Segment, int16)], top, height: int -): Partitioning = +): Partitioning {.raises: [].} = ## Puts segments into the height partitions they intersect with. let maxPartitions = max(1, height div 10).uint32 @@ -1081,7 +1091,9 @@ proc partitionSegments( for i in atPartition .. toPartition: result.partitions[i].add((segment, winding)) -proc getIndexForY(partitioning: Partitioning, y: int): uint32 {.inline.} = +proc getIndexForY( + partitioning: Partitioning, y: int +): uint32 {.inline, raises: [].} = if partitioning.partitionHeight == 0 or partitioning.partitions.len == 1: 0.uint32 else: @@ -1090,7 +1102,9 @@ proc getIndexForY(partitioning: Partitioning, y: int): uint32 {.inline.} = partitioning.partitions.high.uint32 ) -proc insertionSort(a: var seq[(float32, int16)], lo, hi: int) {.inline.} = +proc insertionSort( + a: var seq[(float32, int16)], lo, hi: int +) {.inline, raises: [].} = for i in lo + 1 .. hi: var j = i - 1 @@ -1100,7 +1114,7 @@ proc insertionSort(a: var seq[(float32, int16)], lo, hi: int) {.inline.} = dec j dec k -proc sort(a: var seq[(float32, int16)], inl, inr: int) = +proc sort(a: var seq[(float32, int16)], inl, inr: int) {.raises: [].} = ## Quicksort + insertion sort, in-place and faster than standard lib sort. let n = inr - inl + 1 if n < 32: @@ -1122,7 +1136,9 @@ proc sort(a: var seq[(float32, int16)], inl, inr: int) = sort(a, inl, r) sort(a, l, inr) -proc shouldFill(windingRule: WindingRule, count: int): bool {.inline.} = +proc shouldFill( + windingRule: WindingRule, count: int +): bool {.inline, raises: [].} = ## Should we fill based on the current winding rule and count? case windingRule: of wrNonZero: @@ -1136,7 +1152,7 @@ iterator walk( windingRule: WindingRule, y: int, size: Vec2 -): (float32, float32, int32) = +): (float32, float32, int32) {.raises: [].} = var prevAt: float32 count: int32 @@ -1168,7 +1184,7 @@ proc computeCoverages( aa: bool, partitioning: Partitioning, windingRule: WindingRule -) {.inline.} = +) {.inline, raises: [].} = let quality = if aa: 5 else: 1 # Must divide 255 cleanly (1, 3, 5, 15, 17, 51, 85) sampleCoverage = (255 div quality).uint8 @@ -1238,7 +1254,9 @@ proc computeCoverages( for j in i ..< fillStart + fillLen: coverages[j - startX] += sampleCoverage -proc clearUnsafe(target: Image | Mask, startX, startY, toX, toY: int) = +proc clearUnsafe( + target: Image | Mask, startX, startY, toX, toY: int +) {.raises: [].} = ## Clears data from [start, to). if startX == target.width or startY == target.height: return @@ -1256,7 +1274,7 @@ proc fillCoverage( startX, y: int, coverages: seq[uint8], blendMode: BlendMode -) = +) {.raises: [PixieError].} = var x = startX when defined(amd64) and not defined(pixieNoSimd): if blendMode.hasSimdBlender(): @@ -1343,7 +1361,7 @@ proc fillCoverage( startX, y: int, coverages: seq[uint8], blendMode: BlendMode -) = +) {.raises: [PixieError].} = var x = startX when defined(amd64) and not defined(pixieNoSimd): if blendMode.hasSimdMasker(): @@ -1386,7 +1404,7 @@ proc fillHits( numHits: int, windingRule: WindingRule, blendMode: BlendMode -) = +) {.raises: [PixieError].} = let blender = blendMode.blender() var filledTo: int for (prevAt, at, count) in hits.walk(numHits, windingRule, y, image.wh): @@ -1434,7 +1452,7 @@ proc fillHits( numHits: int, windingRule: WindingRule, blendMode: BlendMode -) = +) {.raises: [PixieError].} = let masker = blendMode.masker() var filledTo: int for (prevAt, at, count) in hits.walk(numHits, windingRule, y, mask.wh): @@ -1478,7 +1496,7 @@ proc fillShapes( color: SomeColor, windingRule: WindingRule, blendMode: BlendMode -) = +) {.raises: [PixieError].} = # Figure out the total bounds of all the shapes, # rasterize only within the total bounds let @@ -1536,7 +1554,7 @@ proc fillShapes( shapes: seq[seq[Vec2]], windingRule: WindingRule, blendMode: BlendMode -) = +) {.raises: [PixieError].} = # Figure out the total bounds of all the shapes, # rasterize only within the total bounds let @@ -1574,11 +1592,11 @@ proc fillShapes( mask.clearUnsafe(0, 0, 0, startY) mask.clearUnsafe(0, pathHeight, 0, mask.height) -proc miterLimitToAngle*(limit: float32): float32 = +proc miterLimitToAngle*(limit: float32): float32 {.inline, raises: [].} = ## Converts miter-limit-ratio to miter-limit-angle. arcsin(1 / limit) * 2 -proc angleToMiterLimit*(angle: float32): float32 = +proc angleToMiterLimit*(angle: float32): float32 {.inline, raises: [].} = ## Converts miter-limit-angle to miter-limit-ratio. 1 / sin(angle / 2) @@ -1589,7 +1607,7 @@ proc strokeShapes( lineJoin: LineJoin, miterLimit: float32, dashes: seq[float32] -): seq[seq[Vec2]] = +): seq[seq[Vec2]] {.raises: [PixieError].} = if strokeWidth <= 0: return @@ -1725,7 +1743,7 @@ proc strokeShapes( proc parseSomePath( path: SomePath, closeSubpaths: bool, pixelScale: float32 = 1.0 -): seq[seq[Vec2]] {.inline.} = +): seq[seq[Vec2]] {.inline, raises: [PixieError].} = ## Given SomePath, parse it in different ways. when type(path) is string: parsePath(path).commandsToShapes(closeSubpaths, pixelScale) @@ -1738,7 +1756,7 @@ proc fillPath*( transform = mat3(), windingRule = wrNonZero, blendMode = bmNormal -) = +) {.raises: [PixieError].} = ## Fills a path. var shapes = parseSomePath(path, true, transform.pixelScale()) shapes.transform(transform) @@ -1750,7 +1768,7 @@ proc fillPath*( paint: Paint, transform = mat3(), windingRule = wrNonZero -) = +) {.raises: [PixieError].} = ## Fills a path. if paint.opacity == 0: return @@ -1804,7 +1822,7 @@ proc strokePath*( miterLimit = defaultMiterLimit, dashes: seq[float32] = @[], blendMode = bmNormal -) = +) {.raises: [PixieError].} = ## Strokes a path. var strokeShapes = strokeShapes( parseSomePath(path, false, transform.pixelScale()), @@ -1827,7 +1845,7 @@ proc strokePath*( lineJoin = ljMiter, miterLimit = defaultMiterLimit, dashes: seq[float32] = @[] -) = +) {.raises: [PixieError].} = ## Strokes a path. if paint.opacity == 0: return @@ -1890,7 +1908,7 @@ proc overlaps( shapes: seq[seq[Vec2]], test: Vec2, windingRule: WindingRule -): bool = +): bool {.raises: [].} = var hits: seq[(float32, int16)] let @@ -1920,7 +1938,7 @@ proc fillOverlaps*( test: Vec2, transform = mat3(), ## Applied to the path, not the test point. windingRule = wrNonZero -): bool = +): bool {.raises: [PixieError].} = ## Returns whether or not the specified point is contained in the current path. var shapes = parseSomePath(path, true, transform.pixelScale()) shapes.transform(transform) @@ -1935,7 +1953,7 @@ proc strokeOverlaps*( lineJoin = ljMiter, miterLimit = defaultMiterLimit, dashes: seq[float32] = @[], -): bool = +): bool {.raises: [PixieError].} = ## Returns whether or not the specified point is inside the area contained ## by the stroking of a path. var strokeShapes = strokeShapes(