From daaa71b2b1199dd63c1b0f69dd4b38a76801ae85 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Fri, 6 Aug 2021 01:26:20 -0500 Subject: [PATCH] font as ref object --- src/pixie.nim | 14 ---------- src/pixie/contexts.nim | 49 +++++++++++++++++++++------------- src/pixie/fonts.nim | 58 ++++++++++++++++++++++++++--------------- tests/test_contexts.nim | 17 +++++++----- tests/test_fonts.nim | 54 +++++++++++++++++++------------------- 5 files changed, 105 insertions(+), 87 deletions(-) diff --git a/src/pixie.nim b/src/pixie.nim index 688a4f2..0a0959c 100644 --- a/src/pixie.nim +++ b/src/pixie.nim @@ -10,20 +10,6 @@ type FileFormat* = enum ffPng, ffBmp, ffJpg, ffGif -proc readFont*(filePath: string): Font = - ## Loads a font 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") - result.typeface.filePath = filePath - converter autoStraightAlpha*(c: ColorRGBX): ColorRGBA {.inline.} = ## Convert a paremultiplied alpha RGBA to a straight alpha RGBA. c.rgba() diff --git a/src/pixie/contexts.nim b/src/pixie/contexts.nim index c0a050b..2cf06d6 100644 --- a/src/pixie/contexts.nim +++ b/src/pixie/contexts.nim @@ -1,5 +1,5 @@ -import bumpy, chroma, pixie/blends, pixie/common, pixie/fonts, pixie/images, - pixie/masks, pixie/paints, pixie/paths, vmath +import bumpy, chroma, tables, pixie/blends, pixie/common, pixie/fonts, + pixie/images, pixie/masks, pixie/paints, pixie/paths, vmath ## This file provides a Nim version of the Canvas 2D API commonly used on the ## web. The goal is to make picking up Pixie easy for developers familiar with @@ -15,7 +15,8 @@ type miterLimit*: float32 lineCap*: LineCap lineJoin*: LineJoin - font*: Font + font*: string ## File path to a .ttf or .otf file. + fontSize*: float32 textAlign*: HAlignMode path: Path lineDash: seq[float32] @@ -23,6 +24,7 @@ type mask: Mask layer: Image stateStack: seq[ContextState] + typefaces: Table[string, Typeface] ContextState = object fillStyle, strokeStyle: Paint @@ -31,7 +33,8 @@ type miterLimit: float32 lineCap: LineCap lineJoin: LineJoin - font: Font + font: string + fontSize*: float32 textAlign: HAlignMode lineDash: seq[float32] mat: Mat3 @@ -51,6 +54,7 @@ proc newContext*(image: Image): Context = result.miterLimit = 10 result.fillStyle = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255)) result.strokeStyle = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255)) + result.fontSize = 12 proc newContext*(width, height: int): Context {.inline.} = ## Create a new Context that will draw to a new image of width and height. @@ -65,6 +69,7 @@ proc state(ctx: Context): ContextState = result.lineCap = ctx.lineCap result.lineJoin = ctx.lineJoin result.font = ctx.font + result.fontSize = ctx.fontSize result.textAlign = ctx.textAlign result.lineDash = ctx.lineDash result.mat = ctx.mat @@ -104,6 +109,7 @@ proc restore*(ctx: Context) = ctx.lineCap = state.lineCap ctx.lineJoin = state.lineJoin ctx.font = state.font + ctx.fontSize = state.fontSize ctx.textAlign = state.textAlign ctx.lineDash = state.lineDash ctx.mat = state.mat @@ -158,15 +164,24 @@ proc stroke(ctx: Context, image: Image, path: Path) = ctx.layer.applyOpacity(ctx.globalAlpha) ctx.restore() -proc fillText(ctx: Context, image: Image, text: string, at: Vec2) = - if ctx.font.typeface == nil: +proc createFont(ctx: Context): Font = + 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.size = ctx.fontSize + +proc fillText(ctx: Context, image: Image, text: string, at: Vec2) = + let font = ctx.createFont() + # Canvas positions text relative to the alphabetic baseline by default var at = at - at.y -= round(ctx.font.typeface.ascent * ctx.font.scale) + at.y -= round(font.typeface.ascent * font.scale) - ctx.font.paint = ctx.fillStyle + font.paint = ctx.fillStyle var image = image @@ -175,7 +190,7 @@ proc fillText(ctx: Context, image: Image, text: string, at: Vec2) = image = ctx.layer image.fillText( - ctx.font, + font, text, ctx.mat * translate(at), hAlign = ctx.textAlign @@ -186,14 +201,13 @@ proc fillText(ctx: Context, image: Image, text: string, at: Vec2) = ctx.restore() proc strokeText(ctx: Context, image: Image, text: string, at: Vec2) = - if ctx.font.typeface == nil: - raise newException(PixieError, "No font has been set on this Context") + let font = ctx.createFont() # Canvas positions text relative to the alphabetic baseline by default var at = at - at.y -= round(ctx.font.typeface.ascent * ctx.font.scale) + at.y -= round(font.typeface.ascent * font.scale) - ctx.font.paint = ctx.strokeStyle + font.paint = ctx.strokeStyle var image = image @@ -202,7 +216,7 @@ proc strokeText(ctx: Context, image: Image, text: string, at: Vec2) = image = ctx.layer image.strokeText( - ctx.font, + font, text, ctx.mat * translate(at), ctx.lineWidth, @@ -419,10 +433,9 @@ proc strokeText*(ctx: Context, text: string, x, y: float32) {.inline.} = proc measureText*(ctx: Context, text: string): TextMetrics = ## Returns a TextMetrics object that contains information about the measured ## text (such as its width, for example). - if ctx.font.typeface == nil: - raise newException(PixieError, "No font has been set on this Context") - - let bounds = typeset(ctx.font, text).computeBounds() + let + font = ctx.createFont() + bounds = typeset(font, text).computeBounds() result.width = bounds.x proc getLineDash*(ctx: Context): seq[float32] {.inline.} = diff --git a/src/pixie/fonts.nim b/src/pixie/fonts.nim index 20813a7..ace9795 100644 --- a/src/pixie/fonts.nim +++ b/src/pixie/fonts.nim @@ -1,5 +1,5 @@ -import bumpy, chroma, pixie/fontformats/opentype, pixie/fontformats/svgfont, - pixie/images, pixie/masks, pixie/paints, pixie/paths, unicode, vmath +import bumpy, chroma, common, os, pixie/fontformats/opentype, pixie/fontformats/svgfont, + pixie/images, pixie/masks, pixie/paints, pixie/paths, strutils, unicode, vmath const AutoLineHeight* = -1.float32 ## Use default line height for the font size @@ -12,7 +12,7 @@ type svgFont: SvgFont filePath*: string - Font* = object + Font* = ref object typeface*: Typeface size*: float32 ## Font size in pixels. lineHeight*: float32 ## The line height in pixels or AutoLineHeight for the font's default line height. @@ -128,15 +128,19 @@ proc defaultLineHeight*(font: Font): float32 {.inline.} = font.typeface.ascent - font.typeface.descent + font.typeface.lineGap round(fontUnits * font.scale) -proc paint*(font: var Font): var Paint = +proc paint*(font: Font): var Paint = font.paints[0] -proc paint*(font: Font): lent Paint = - font.paints[0] - -proc `paint=`*(font: var Font, paint: Paint) = +proc `paint=`*(font: Font, paint: Paint) = font.paints = @[paint] +proc newFont*(typeface: Typeface): Font = + result = Font() + result.typeface = typeface + result.size = 12 + result.lineHeight = AutoLineHeight + result.paint = rgbx(0, 0, 0, 255) + proc newSpan*(text: string, font: Font): Span = ## Creates a span, associating a font with the text. result = Span() @@ -417,22 +421,16 @@ proc computeBounds*(spans: seq[Span]): Vec2 {.inline.} = ## Computes the width and height of the spans in pixels. typeset(spans).computeBounds() -proc parseOtf*(buf: string): Font = - result.typeface = Typeface() - result.typeface.opentype = parseOpenType(buf) - result.size = 12 - result.lineHeight = AutoLineHeight - result.paint = rgbx(0, 0, 0, 255) +proc parseOtf*(buf: string): Typeface = + result = Typeface() + result.opentype = parseOpenType(buf) -proc parseTtf*(buf: string): Font = +proc parseTtf*(buf: string): Typeface = parseOtf(buf) -proc parseSvgFont*(buf: string): Font = - result.typeface = Typeface() - result.typeface.svgFont = svgfont.parseSvgFont(buf) - result.size = 12 - result.lineHeight = AutoLineHeight - result.paint = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255)) +proc parseSvgFont*(buf: string): Typeface = + result = Typeface() + result.svgFont = svgfont.parseSvgFont(buf) proc textUber( target: Image | Mask, @@ -597,3 +595,21 @@ proc strokeText*( miterLimit, dashes ) + +proc readTypeface*(filePath: string): Typeface = + ## 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") + result.filePath = filePath + +proc readFont*(filePath: string): Font = + ## Loads a font from a file. + newFont(readTypeface(filePath)) diff --git a/tests/test_contexts.nim b/tests/test_contexts.nim index 79ab2b7..7fde728 100644 --- a/tests/test_contexts.nim +++ b/tests/test_contexts.nim @@ -280,8 +280,11 @@ block: block: let ctx = newContext(newImage(300, 150)) - ctx.font = readFont("tests/fonts/Roboto-Regular_1.ttf") - ctx.font.size = 50 + ctx.font = "tests/fonts/Roboto-Regular_1.ttf" + ctx.fontSize = 50 + ctx.save() + ctx.fontSize = 30 + ctx.restore() ctx.fillText("Hello world", 50, 90) @@ -290,8 +293,8 @@ block: block: let ctx = newContext(newImage(300, 150)) - ctx.font = readFont("tests/fonts/Roboto-Regular_1.ttf") - ctx.font.size = 50 + ctx.font = "tests/fonts/Roboto-Regular_1.ttf" + ctx.fontSize = 50 ctx.strokeText("Hello world", 50, 90) @@ -467,8 +470,8 @@ block: let image = newImage(300, 150) let ctx = newContext(image) - ctx.font = readFont("tests/fonts/Roboto-Regular_1.ttf") - ctx.font.size = 50 + ctx.font = "tests/fonts/Roboto-Regular_1.ttf" + ctx.fontSize = 50 ctx.fillStyle = "blue" ctx.saveLayer() @@ -486,7 +489,7 @@ block: block: let ctx = newContext(100, 100) - ctx.font = readFont("tests/fonts/Roboto-Regular_1.ttf") + ctx.font = "tests/fonts/Roboto-Regular_1.ttf" let metrics = ctx.measureText("Hello world") doAssert metrics.width == 61 diff --git a/tests/test_fonts.nim b/tests/test_fonts.nim index d1f34cc..1a30e10 100644 --- a/tests/test_fonts.nim +++ b/tests/test_fonts.nim @@ -738,41 +738,41 @@ block: block: let - roboto = readFont("tests/fonts/Roboto-Regular_1.ttf") - aclonica = readFont("tests/fonts/Aclonica-Regular_1.ttf") - ubuntu = readFont("tests/fonts/Ubuntu-Regular_1.ttf") - ibm = readFont("tests/fonts/IBMPlexSans-Regular_2.ttf") - noto = readFont("tests/fonts/NotoSans-Regular_4.ttf") + roboto = readTypeface("tests/fonts/Roboto-Regular_1.ttf") + aclonica = readTypeface("tests/fonts/Aclonica-Regular_1.ttf") + ubuntu = readTypeface("tests/fonts/Ubuntu-Regular_1.ttf") + ibm = readTypeface("tests/fonts/IBMPlexSans-Regular_2.ttf") + noto = readTypeface("tests/fonts/NotoSans-Regular_4.ttf") - var font1 = roboto + var font1 = newFont(roboto) font1.size = 64 - var font2 = aclonica + var font2 = newFont(aclonica) font2.size = 80 - var font3 = ibm + var font3 = newFont(ibm) font3.size = 40 - var font4 = ubuntu + var font4 = newFont(ubuntu) font4.size = 56 - var font5 = noto + var font5 = newFont(noto) font5.size = 72 - var font6 = roboto + var font6 = newFont(roboto) font6.size = 48 - var font7 = noto + var font7 = newFont(noto) font7.size = 64 - var font8 = ubuntu + var font8 = newFont(ubuntu) font8.size = 54 font8.paint.color = rgba(255, 0, 0, 255) - var font9 = roboto + var font9 = newFont(roboto) font9.size = 48 - var font10 = aclonica + var font10 = newFont(aclonica) font10.size = 48 font10.lineHeight = 120 @@ -799,20 +799,20 @@ block: doDiff(image, "spans4") block: - let ubuntu = readFont("tests/fonts/Ubuntu-Regular_1.ttf") + let ubuntu = readTypeface("tests/fonts/Ubuntu-Regular_1.ttf") - var font1 = ubuntu + var font1 = newFont(ubuntu) font1.size = 15 font1.paint = "#CACACA" - var font2 = ubuntu + var font2 = newFont(ubuntu) font2.size = 84 - var font3 = ubuntu + var font3 = newFont(ubuntu) font3.size = 18 font3.paint = "#007FF4" - var font4 = ubuntu + var font4 = newFont(ubuntu) font4.size = 20 font4.paint = "#4F4F4F" @@ -925,29 +925,29 @@ block: doDiff(image, "strikethrough3") block: - let ubuntu = readFont("tests/fonts/Ubuntu-Regular_1.ttf") + let ubuntu = readTypeface("tests/fonts/Ubuntu-Regular_1.ttf") - var font1 = ubuntu + var font1 = newFont(ubuntu) font1.size = 15 font1.paint = "#CACACA" - var font2 = ubuntu + var font2 = newFont(ubuntu) font2.size = 84 - var font3 = ubuntu + var font3 = newFont(ubuntu) font3.size = 18 font3.paint = "#007FF4" - var font4 = ubuntu + var font4 = newFont(ubuntu) font4.size = 20 font4.paint = "#4F4F4F" - var font5 = ubuntu + var font5 = newFont(ubuntu) font5.size = 20 font5.paint = "#4F4F4F" font5.underline = true - var font6 = ubuntu + var font6 = newFont(ubuntu) font6.size = 20 font6.paint = "#4F4F4F" font6.strikethrough = true