Merge pull request #402 from treeform/dev
Seperate the idea layout and pixel bounds.
|
@ -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"
|
||||||
|
|
|
@ -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: [].} =
|
||||||
|
|
|
@ -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,
|
||||||
|
|
BIN
tests/fonts/PinyonScript.ttf
Normal file
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
BIN
tests/fonts/diffs/spans7.png
Normal file
After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 8 KiB |
BIN
tests/fonts/masters/spans7.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
BIN
tests/fonts/rendered/spans7.png
Normal file
After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 8 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
|
@ -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
|
||||||
|
|