Merge pull request #402 from treeform/dev

Seperate the idea layout and pixel bounds.
This commit is contained in:
treeform 2022-03-21 20:06:26 -07:00 committed by GitHub
commit c887910285
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 103 additions and 44 deletions

View file

@ -1,4 +1,4 @@
version = "4.0.5" version = "4.1.0"
author = "Andre von Houck and Ryan Oldenburg" author = "Andre von Houck and Ryan Oldenburg"
description = "Full-featured 2d graphics library for Nim." description = "Full-featured 2d graphics library for Nim."
license = "MIT" license = "MIT"

View file

@ -482,7 +482,7 @@ proc measureText*(ctx: Context, text: string): TextMetrics {.raises: [PixieError
## text (such as its width, for example). ## text (such as its width, for example).
let let
font = newFont(ctx) font = newFont(ctx)
bounds = typeset(font, text).computeBounds() bounds = typeset(font, text).layoutBounds()
result.width = bounds.x result.width = bounds.x
proc getLineDash*(ctx: Context): seq[float32] {.inline, raises: [].} = proc getLineDash*(ctx: Context): seq[float32] {.inline, raises: [].} =

View file

@ -467,7 +467,7 @@ proc typeset*(
## wrap: enable/disable text wrapping ## wrap: enable/disable text wrapping
typeset(@[newSpan(text, font)], bounds, hAlign, vAlign, wrap) typeset(@[newSpan(text, font)], bounds, hAlign, vAlign, wrap)
proc computeBounds*(arrangement: Arrangement): Vec2 {.raises: [].} = proc layoutBounds*(arrangement: Arrangement): Vec2 {.raises: [].} =
## Computes the width and height of the arrangement in pixels. ## Computes the width and height of the arrangement in pixels.
if arrangement.runes.len > 0: if arrangement.runes.len > 0:
for i in 0 ..< arrangement.runes.len: for i in 0 ..< arrangement.runes.len:
@ -481,13 +481,13 @@ proc computeBounds*(arrangement: Arrangement): Vec2 {.raises: [].} =
# If the text ends with a new line, we need add another line height. # If the text ends with a new line, we need add another line height.
result.y += finalRect.h result.y += finalRect.h
proc computeBounds*(font: Font, text: string): Vec2 {.inline, raises: [].} = proc layoutBounds*(font: Font, text: string): Vec2 {.inline, raises: [].} =
## Computes the width and height of the text in pixels. ## Computes the width and height of the text in pixels.
font.typeset(text).computeBounds() font.typeset(text).layoutBounds()
proc computeBounds*(spans: seq[Span]): Vec2 {.inline, raises: [].} = proc layoutBounds*(spans: seq[Span]): Vec2 {.inline, raises: [].} =
## Computes the width and height of the spans in pixels. ## Computes the width and height of the spans in pixels.
typeset(spans).computeBounds() typeset(spans).layoutBounds()
proc parseOtf*(buf: string): Typeface {.raises: [PixieError].} = proc parseOtf*(buf: string): Typeface {.raises: [PixieError].} =
result = Typeface() result = Typeface()
@ -500,34 +500,27 @@ proc parseSvgFont*(buf: string): Typeface {.raises: [PixieError].} =
result = Typeface() result = Typeface()
result.svgFont = svgfont.parseSvgFont(buf) result.svgFont = svgfont.parseSvgFont(buf)
proc textUber( proc computePaths(arrangement: Arrangement): seq[Path] =
target: Image | Mask, ## Takes an Arrangement and computes Paths for drawing.
arrangement: Arrangement, ## Returns a seq of paths that match the seq of Spans in the arrangement.
transform = mat3(), ## If you only have one Span you will only get one Path.
strokeWidth: float32 = 1.0,
lineCap = ButtCap,
lineJoin = MiterJoin,
miterLimit = defaultMiterLimit,
dashes: seq[float32] = @[],
stroke: static[bool] = false
) =
var line: int var line: int
for spanIndex, (start, stop) in arrangement.spans: for spanIndex, (start, stop) in arrangement.spans:
let let
spanPath = newPath()
font = arrangement.fonts[spanIndex] font = arrangement.fonts[spanIndex]
underlineThickness = font.typeface.underlineThickness * font.scale underlineThickness = font.typeface.underlineThickness * font.scale
underlinePosition = font.typeface.underlinePosition * font.scale underlinePosition = font.typeface.underlinePosition * font.scale
strikeoutThickness = font.typeface.strikeoutThickness * font.scale strikeoutThickness = font.typeface.strikeoutThickness * font.scale
strikeoutPosition = font.typeface.strikeoutPosition * font.scale strikeoutPosition = font.typeface.strikeoutPosition * font.scale
for runeIndex in start .. stop: for runeIndex in start .. stop:
let position = arrangement.positions[runeIndex] let
position = arrangement.positions[runeIndex]
let path = font.typeface.getGlyphPath(arrangement.runes[runeIndex]) path = font.typeface.getGlyphPath(arrangement.runes[runeIndex])
path.transform( path.transform(
translate(position) * translate(position) *
scale(vec2(font.scale)) scale(vec2(font.scale))
) )
var applyDecoration = true var applyDecoration = true
if runeIndex == arrangement.lines[line][1]: if runeIndex == arrangement.lines[line][1]:
inc line inc line
@ -553,22 +546,30 @@ proc textUber(
font.typeface.isCCW() font.typeface.isCCW()
) )
when stroke: spanPath.addPath(path)
when type(target) is Image: result.add(spanPath)
for paint in font.paints:
target.strokePath( proc textUber(
path, target: Image | Mask,
paint, arrangement: Arrangement,
transform, transform = mat3(),
strokeWidth, strokeWidth: float32 = 1.0,
lineCap, lineCap = ButtCap,
lineJoin, lineJoin = MiterJoin,
miterLimit, miterLimit = defaultMiterLimit,
dashes dashes: seq[float32] = @[],
) stroke: static[bool] = false
else: # target is Mask ) =
let spanPaths = arrangement.computePaths()
for spanIndex in 0 ..< arrangement.spans.len:
let path = spanPaths[spanIndex]
when stroke:
when type(target) is Image:
let font = arrangement.fonts[spanIndex]
for paint in font.paints:
target.strokePath( target.strokePath(
path, path,
paint,
transform, transform,
strokeWidth, strokeWidth,
lineCap, lineCap,
@ -576,12 +577,33 @@ proc textUber(
miterLimit, miterLimit,
dashes dashes
) )
else: else: # target is Mask
when type(target) is Image: target.strokePath(
for paint in font.paints: path,
target.fillPath(path, paint, transform) transform,
else: # target is Mask strokeWidth,
target.fillPath(path, transform) lineCap,
lineJoin,
miterLimit,
dashes
)
else:
when type(target) is Image:
let font = arrangement.fonts[spanIndex]
for paint in font.paints:
target.fillPath(path, paint, transform)
else: # target is Mask
target.fillPath(path, transform)
proc computeBounds*(
arrangement: Arrangement,
transform = mat3()
): Rect =
let fullPath = newPath()
for path in arrangement.computePaths():
fullPath.addPath(path)
fullPath.transform(transform)
fullPath.computeBounds()
proc fillText*( proc fillText*(
target: Image | Mask, target: Image | Mask,

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8 KiB

After

Width:  |  Height:  |  Size: 8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8 KiB

After

Width:  |  Height:  |  Size: 8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View file

@ -16,14 +16,14 @@ block:
var font = readFont("tests/fonts/Roboto-Regular_1.ttf") var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 24 font.size = 24
let bounds = font.computeBounds("Word") let bounds = font.layoutBounds("Word")
doAssert bounds == vec2(56, 28) doAssert bounds == vec2(56, 28)
block: block:
var font = readFont("tests/fonts/Roboto-Regular_1.ttf") var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 24 font.size = 24
let bounds = font.computeBounds("Word\n") let bounds = font.layoutBounds("Word\n")
doAssert bounds == vec2(56, 56) doAssert bounds == vec2(56, 56)
block: block:
@ -996,6 +996,43 @@ block:
doDiff(image, "spans6") doDiff(image, "spans6")
block:
let typeface1 = readTypeface("tests/fonts/PinyonScript.ttf")
var font1 = newFont(typeface1)
font1.size = 82
font1.lineHeight = 60
font1.paint = "#000000"
let spans = @[
newSpan("Fancy text", font1),
]
let image = newImage(400, 400)
image.fill(rgba(255, 255, 255, 255))
let ctx = newContext(image)
ctx.fillStyle = "#FFD6D6"
ctx.fillRect(rect(40, 170, 320, 60))
let
arrangement = typeset(spans, bounds = vec2(320, 60))
snappedBounds = arrangement.computeBounds().snapToPixels()
textImage = newImage(snappedBounds.w.int, snappedBounds.h.int)
textImage.fillText(arrangement, translate(-snappedBounds.xy))
image.draw(textImage, translate(snappedBounds.xy + vec2(40, 170)))
# Enable this to show bounds
# ctx.strokeStyle = "#FF0000"
# ctx.translate(vec2(40, 170))
# ctx.strokeRect(arrangement.computeBounds())
# Enable this to show how text is drawing directly
# image.fillText(arrangement, translate(vec2(40, 170)))
doDiff(image, "spans7")
block: block:
var font = readFont("tests/fonts/Roboto-Regular_1.ttf") var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 36 font.size = 36