font as ref object

This commit is contained in:
Ryan Oldenburg 2021-08-06 01:26:20 -05:00
parent 43d08f9ddf
commit daaa71b2b1
5 changed files with 105 additions and 87 deletions

View file

@ -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()

View file

@ -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.} =

View file

@ -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))

View file

@ -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

View file

@ -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