paint on font

This commit is contained in:
Ryan Oldenburg 2021-05-10 11:51:51 -05:00
parent 207608df04
commit 11fdc30399
6 changed files with 108 additions and 147 deletions

View file

@ -327,31 +327,7 @@ proc strokePolygon*(
mask.strokePath(path, strokeWidth)
proc fillText*(
image: Image,
font: Font,
text: string,
color: SomeColor,
transform: Vec2 | Mat3 = vec2(0, 0),
bounds = vec2(0, 0),
hAlign = haLeft,
vAlign = vaTop
) =
## Typesets and fills the text. Optional parameters:
## transform: translation or matrix to apply
## bounds: width determines wrapping and hAlign, height for vAlign
## hAlign: horizontal alignment of the text
## vAlign: vertical alignment of the text
let arrangement = font.typeset(
text,
bounds,
hAlign,
vAlign
)
for i in 0 ..< arrangement.runes.len:
image.fillPath(arrangement.getPath(i), color, transform)
proc fillText*(
mask: Mask,
target: Image | Mask,
font: Font,
text: string,
transform: Vec2 | Mat3 = vec2(0, 0),
@ -364,42 +340,15 @@ proc fillText*(
## bounds: width determines wrapping and hAlign, height for vAlign
## hAlign: horizontal alignment of the text
## vAlign: vertical alignment of the text
let arrangement = font.typeset(
text,
bounds,
hAlign,
vAlign
)
let arrangement = font.typeset(text, bounds, hAlign, vAlign)
for i in 0 ..< arrangement.runes.len:
mask.fillPath(arrangement.getPath(i), transform)
when type(target) is Image:
target.fillPath(arrangement.getPath(i), font.paint, transform)
else: # target is Mask
target.fillPath(arrangement.getPath(i), transform)
proc strokeText*(
image: Image,
font: Font,
text: string,
color: SomeColor,
transform: Vec2 | Mat3 = vec2(0, 0),
strokeWidth = 1.0,
bounds = vec2(0, 0),
hAlign = haLeft,
vAlign = vaTop
) =
## Typesets and strokes the text. Optional parameters:
## transform: translation or matrix to apply
## bounds: width determines wrapping and hAlign, height for vAlign
## hAlign: horizontal alignment of the text
## vAlign: vertical alignment of the text
let arrangement = font.typeset(
text,
bounds,
hAlign,
vAlign
)
for i in 0 ..< arrangement.runes.len:
image.strokePath(arrangement.getPath(i), color, transform, strokeWidth)
proc strokeText*(
mask: Mask,
target: Image | Mask,
font: Font,
text: string,
transform: Vec2 | Mat3 = vec2(0, 0),
@ -413,11 +362,11 @@ proc strokeText*(
## bounds: width determines wrapping and hAlign, height for vAlign
## hAlign: horizontal alignment of the text
## vAlign: vertical alignment of the text
let arrangement = font.typeset(
text,
bounds,
hAlign,
vAlign
)
let arrangement = font.typeset(text, bounds, hAlign, vAlign)
for i in 0 ..< arrangement.runes.len:
mask.strokePath(arrangement.getPath(i), transform, strokeWidth)
when type(target) is Image:
target.strokePath(
arrangement.getPath(i), font.paint, transform, strokeWidth
)
else: # target is Mask
target.strokePath(arrangement.getPath(i), transform, strokeWidth)

View file

@ -1,5 +1,5 @@
import bumpy, pixie/fontformats/opentype, pixie/fontformats/svgfont,
pixie/paths, unicode, vmath
import bumpy, chroma, pixie/fontformats/opentype, pixie/fontformats/svgfont,
pixie/paints, pixie/paths, unicode, vmath
const
AutoLineHeight* = -1.float32 ## Use default line height for the font size
@ -13,8 +13,9 @@ type
Font* = object
typeface*: Typeface
size*: float32 ## Font size in pixels.
size*: float32 ## Font size in pixels.
lineHeight*: float32 ## The line height in pixels or AutoLineHeight for the font's default line height.
paint*: Paint
textCase*: TextCase
noKerningAdjustments*: bool ## Optionally disable kerning pair adjustments
@ -115,7 +116,7 @@ proc convertTextCase(runes: var seq[Rune], textCase: TextCase) =
rune = rune.toUpper()
prevRune = rune
proc canWrap(rune: Rune): bool =
proc canWrap(rune: Rune): bool {.inline.} =
rune == Rune(32) or rune.isWhiteSpace()
proc typeset*(
@ -260,28 +261,31 @@ proc typeset*(
result.positions[i].y += yAdjustment
result.selectionRects[i].y += yAdjustment
proc getPath*(arrangement: Arrangement, index: int): Path =
## Returns the path for index.
result = arrangement.font.typeface.getGlyphPath(arrangement.runes[index])
result.transform(
translate(arrangement.positions[index]) *
scale(vec2(arrangement.font.scale))
)
proc computeBounds*(font: Font, text: string): Vec2 =
## Computes the width and height of the text in pixels.
let arrangement = font.typeset(text)
proc computeBounds*(arrangement: Arrangement): Vec2 =
if arrangement.runes.len > 0:
for rect in arrangement.selectionRects:
result.x = max(result.x, rect.x + rect.w)
let finalRect = arrangement.selectionRects[^1]
result.y = finalRect.y + finalRect.h
proc computeBounds*(font: Font, text: string): Vec2 {.inline.} =
## Computes the width and height of the text in pixels.
font.typeset(text).computeBounds()
proc getPath*(arrangement: Arrangement, index: int): Path =
## Returns the path for the rune index.
result = arrangement.font.typeface.getGlyphPath(arrangement.runes[index])
result.transform(
translate(arrangement.positions[index]) *
scale(vec2(arrangement.font.scale))
)
proc parseOtf*(buf: string): Font =
result.typeface = Typeface()
result.typeface.opentype = parseOpenType(buf)
result.size = 12
result.lineHeight = AutoLineHeight
result.paint = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255))
proc parseTtf*(buf: string): Font =
parseOtf(buf)
@ -291,3 +295,4 @@ proc parseSvgFont*(buf: string): Font =
result.typeface.svgFont = svgfont.parseSvgFont(buf)
result.size = 12
result.lineHeight = AutoLineHeight
result.paint = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255))

View file

@ -9,7 +9,7 @@ type
pkGradientRadial
pkGradientAngular
Paint* = ref object
Paint* = object
## Paint used to fill paths.
case kind*: PaintKind
of pkSolid:

View file

@ -1494,18 +1494,19 @@ proc fillPath*(
image: Image,
path: SomePath,
paint: Paint,
windingRule = wrNonZero,
transform: Vec2 | Mat3 = vec2(),
windingRule = wrNonZero
) =
## Fills a path.
if paint.kind == pkSolid:
image.fillPath(path, paint.color)
image.fillPath(path, paint.color, transform)
return
let
mask = newMask(image.width, image.height)
fill = newImage(image.width, image.height)
mask.fillPath(parseSomePath(path), windingRule)
mask.fillPath(parseSomePath(path), transform, windingRule)
case paint.kind:
of pkSolid:
@ -1606,5 +1607,58 @@ proc strokePath*(
strokeShapes.transform(transform)
mask.fillShapes(strokeShapes, wrNonZero)
proc strokePath*(
image: Image,
path: SomePath,
paint: Paint,
transform: Vec2 | Mat3 = vec2(),
strokeWidth = 1.0,
lineCap = lcButt,
lineJoin = ljMiter
) =
## Fills a path.
if paint.kind == pkSolid:
image.strokePath(
path, paint.color, transform, strokeWidth, lineCap, lineJoin
)
return
let
mask = newMask(image.width, image.height)
fill = newImage(image.width, image.height)
mask.strokePath(parseSomePath(path), transform)
case paint.kind:
of pkSolid:
discard # Handled above
of pkImage:
fill.draw(paint.image, paint.imageMat)
of pkImageTiled:
fill.drawTiled(paint.image, paint.imageMat)
of pkGradientLinear:
fill.fillLinearGradient(
paint.gradientHandlePositions[0],
paint.gradientHandlePositions[1],
paint.gradientStops
)
of pkGradientRadial:
fill.fillRadialGradient(
paint.gradientHandlePositions[0],
paint.gradientHandlePositions[1],
paint.gradientHandlePositions[2],
paint.gradientStops
)
of pkGradientAngular:
fill.fillAngularGradient(
paint.gradientHandlePositions[0],
paint.gradientHandlePositions[1],
paint.gradientHandlePositions[2],
paint.gradientStops
)
fill.draw(mask)
image.draw(fill, blendMode = paint.blendMode)
when defined(release):
{.pop.}

View file

@ -20,7 +20,7 @@ block:
font.size = 64
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.fillText(font, "fill", rgba(0, 0, 0, 255))
image.fillText(font, "fill")
image.writeFile("tests/fonts/image_fill.png")
block:
@ -28,7 +28,7 @@ block:
font.size = 64
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.strokeText(font, "stroke", rgba(0, 0, 0, 255))
image.strokeText(font, "stroke")
image.writeFile("tests/fonts/image_stroke.png")
block:
@ -86,7 +86,7 @@ block:
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.fillText(font, "asdf", rgba(0, 0, 0, 255))
image.fillText(font, "asdf")
doDiff(image, "basic1")
@ -96,7 +96,7 @@ block:
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.fillText(font, "A cow", rgba(0, 0, 0, 255))
image.fillText(font, "A cow")
doDiff(image, "basic2")
@ -106,7 +106,7 @@ block:
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.fillText(font, "A bit of text HERE", rgba(0, 0, 0, 255))
image.fillText(font, "A bit of text HERE")
doDiff(image, "basic3")
@ -117,7 +117,7 @@ block:
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.fillText(font, "Line height", rgba(0, 0, 0, 255))
image.fillText(font, "Line height")
doDiff(image, "basic4")
@ -127,7 +127,7 @@ block:
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.fillText(font, "Another font", rgba(0, 0, 0, 255))
image.fillText(font, "Another font")
doDiff(image, "basic5")
@ -137,7 +137,7 @@ block:
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.fillText(font, "Different font", rgba(0, 0, 0, 255))
image.fillText(font, "Different font")
doDiff(image, "basic6")
@ -147,17 +147,8 @@ block:
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.fillText(
font,
"First line",
rgba(0, 0, 0, 255)
)
image.fillText(
font,
"Second line",
rgba(0, 0, 0, 255),
vec2(0, font.defaultLineHeight)
)
image.fillText(font, "First line")
image.fillText(font, "Second line", vec2(0, font.defaultLineHeight))
doDiff(image, "basic7")
@ -170,7 +161,6 @@ block:
image.fillText(
font,
"Wrapping text to new line",
rgba(0, 0, 0, 255),
bounds = vec2(200, 0)
)
@ -185,7 +175,6 @@ block:
image.fillText(
font,
"Supercalifragilisticexpialidocious",
rgba(0, 0, 0, 255),
bounds = vec2(200, 0)
)
@ -208,7 +197,6 @@ block:
image.fillText(
font,
text,
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -227,7 +215,6 @@ block:
image.fillText(
font,
text,
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -245,7 +232,6 @@ block:
image.fillText(
font,
text,
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -264,7 +250,6 @@ block:
image.fillText(
font,
text,
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -282,7 +267,6 @@ block:
image.fillText(
font,
text,
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -301,7 +285,6 @@ block:
image.fillText(
font,
text,
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -319,7 +302,6 @@ block:
image.fillText(
font,
text,
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -338,7 +320,6 @@ block:
image.fillText(
font,
text,
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -356,7 +337,6 @@ block:
image.fillText(
font,
text,
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -375,7 +355,6 @@ block:
image.fillText(
font,
text,
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -391,7 +370,6 @@ block:
image.fillText(
font,
"Shehadcometotheconclusion",
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -407,7 +385,6 @@ block:
image.fillText(
font,
"Shehadcometotheconclusion",
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -422,7 +399,6 @@ block:
image.fillText(
font,
"Shehadcometotheconclusion",
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -438,7 +414,6 @@ block:
image.fillText(
font,
"Shehadcometotheconclusion",
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -453,7 +428,6 @@ block:
image.fillText(
font,
"Wrapping text to the next line",
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -469,7 +443,6 @@ block:
image.fillText(
font,
"Wrapping text to the next line",
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -484,7 +457,6 @@ block:
image.fillText(
font,
"HA HT HX HY IA IT IX IY MA MT MX MY NA NT NX NY",
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -499,7 +471,6 @@ block:
image.fillText(
font,
"V( V) V- V/ V: v; v? v@ VT VV VW VX VY V] Vu Vz V{",
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -514,7 +485,6 @@ block:
image.fillText(
font,
"B, B. BA BJ BT BW BY Bf Bg Bt bw By",
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -535,7 +505,6 @@ Fourth line
Fifth line
Sixth line
Seventh line""",
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -555,7 +524,6 @@ Second line
Third line
Fourth line
Fifth line""",
rgba(0, 0, 0, 255),
bounds = image.wh
)
@ -571,7 +539,6 @@ block:
image.fillText(
font,
"TopLeft",
rgba(0, 0, 0, 255),
bounds = image.wh,
hAlign = haLeft,
vAlign = vaTop
@ -580,7 +547,6 @@ block:
image.fillText(
font,
"TopCenter",
rgba(0, 0, 0, 255),
bounds = image.wh,
hAlign = haCenter,
vAlign = vaTop
@ -589,7 +555,6 @@ block:
image.fillText(
font,
"TopRight",
rgba(0, 0, 0, 255),
bounds = image.wh,
hAlign = haRight,
vAlign = vaTop
@ -598,7 +563,6 @@ block:
image.fillText(
font,
"MiddleLeft",
rgba(0, 0, 0, 255),
bounds = image.wh,
hAlign = haLeft,
vAlign = vaMiddle
@ -607,7 +571,6 @@ block:
image.fillText(
font,
"MiddleCenter",
rgba(0, 0, 0, 255),
bounds = image.wh,
hAlign = haCenter,
vAlign = vaMiddle
@ -616,7 +579,6 @@ block:
image.fillText(
font,
"MiddleRight",
rgba(0, 0, 0, 255),
bounds = image.wh,
hAlign = haRight,
vAlign = vaMiddle
@ -625,7 +587,6 @@ block:
image.fillText(
font,
"BottomLeft",
rgba(0, 0, 0, 255),
bounds = image.wh,
hAlign = haLeft,
vAlign = vaBottom
@ -634,7 +595,6 @@ block:
image.fillText(
font,
"BottomCenter",
rgba(0, 0, 0, 255),
bounds = image.wh,
hAlign = haCenter,
vAlign = vaBottom
@ -643,7 +603,6 @@ block:
image.fillText(
font,
"BottomRight",
rgba(0, 0, 0, 255),
bounds = image.wh,
hAlign = haRight,
vAlign = vaBottom

View file

@ -9,8 +9,7 @@ const heartShape = """
"""
block:
let
image = newImage(100, 100)
let image = newImage(100, 100)
image.fillPath(
heartShape,
Paint(
@ -21,8 +20,7 @@ block:
image.writeFile("tests/images/paths/paintSolid.png")
block:
let
image = newImage(100, 100)
let image = newImage(100, 100)
image.fillPath(
heartShape,
Paint(
@ -34,8 +32,7 @@ block:
image.writeFile("tests/images/paths/paintImage.png")
block:
let
image = newImage(100, 100)
let image = newImage(100, 100)
image.fillPath(
heartShape,
Paint(
@ -47,8 +44,7 @@ block:
image.writeFile("tests/images/paths/paintImageTiled.png")
block:
let
image = newImage(100, 100)
let image = newImage(100, 100)
image.fillPath(
heartShape,
Paint(
@ -66,8 +62,7 @@ block:
image.writeFile("tests/images/paths/gradientLinear.png")
block:
let
image = newImage(100, 100)
let image = newImage(100, 100)
image.fillPath(
heartShape,
Paint(
@ -87,8 +82,7 @@ block:
image.writeFile("tests/images/paths/gradientRadial.png")
block:
let
image = newImage(100, 100)
let image = newImage(100, 100)
image.fillPath(
heartShape,
Paint(