handle control runes, newline
This commit is contained in:
parent
e5a463aaa3
commit
5c6725c4e3
5 changed files with 75 additions and 32 deletions
|
@ -1,7 +1,10 @@
|
||||||
import bumpy, pixie/fontformats/opentype, pixie/fontformats/svgfont, pixie/paths,
|
import bumpy, pixie/fontformats/opentype, pixie/fontformats/svgfont, pixie/paths,
|
||||||
unicode, vmath
|
unicode, vmath
|
||||||
|
|
||||||
const AutoLineHeight* = -1.float32 ## Use default line height for the font size
|
const
|
||||||
|
AutoLineHeight* = -1.float32 ## Use default line height for the font size
|
||||||
|
LF = Rune(10)
|
||||||
|
SP = Rune(32)
|
||||||
|
|
||||||
type
|
type
|
||||||
Typeface* = ref object
|
Typeface* = ref object
|
||||||
|
@ -58,10 +61,11 @@ proc lineGap*(typeface: Typeface): float32 {.inline.} =
|
||||||
|
|
||||||
proc getGlyphPath*(typeface: Typeface, rune: Rune): Path {.inline.} =
|
proc getGlyphPath*(typeface: Typeface, rune: Rune): Path {.inline.} =
|
||||||
## The glyph path for the rune.
|
## The glyph path for the rune.
|
||||||
if typeface.opentype != nil:
|
if rune.uint32 > SP.uint32: # Empty paths for control runes (not tofu)
|
||||||
typeface.opentype.getGlyphPath(rune)
|
if typeface.opentype != nil:
|
||||||
else:
|
result = typeface.opentype.getGlyphPath(rune)
|
||||||
typeface.svgFont.getGlyphPath(rune)
|
else:
|
||||||
|
result = typeface.svgFont.getGlyphPath(rune)
|
||||||
|
|
||||||
proc getAdvance*(typeface: Typeface, rune: Rune): float32 {.inline.} =
|
proc getAdvance*(typeface: Typeface, rune: Rune): float32 {.inline.} =
|
||||||
## The advance for the rune in pixels.
|
## The advance for the rune in pixels.
|
||||||
|
@ -121,9 +125,20 @@ proc typeset*(
|
||||||
): Arrangement =
|
): Arrangement =
|
||||||
result = Arrangement()
|
result = Arrangement()
|
||||||
result.font = font
|
result.font = font
|
||||||
result.runes = toRunes(text)
|
|
||||||
|
block: # Walk and filter runes
|
||||||
|
var
|
||||||
|
i = 0
|
||||||
|
rune: Rune
|
||||||
|
while i < text.len:
|
||||||
|
fastRuneAt(text, i, rune, true)
|
||||||
|
# Ignore control runes (0 - 31) except LF for now
|
||||||
|
if rune.uint32 >= SP.uint32 or rune.uint32 == LF.uint32:
|
||||||
|
result.runes.add(rune)
|
||||||
|
|
||||||
result.runes.convertTextCase(textCase)
|
result.runes.convertTextCase(textCase)
|
||||||
result.positions.setLen(result.runes.len)
|
result.positions.setLen(result.runes.len)
|
||||||
|
result.selectionRects.setLen(result.runes.len)
|
||||||
|
|
||||||
let lineHeight =
|
let lineHeight =
|
||||||
if font.lineheight >= 0:
|
if font.lineheight >= 0:
|
||||||
|
@ -145,31 +160,38 @@ proc typeset*(
|
||||||
at.y = round((font.typeface.ascent + font.typeface.lineGap / 2) * font.scale)
|
at.y = round((font.typeface.ascent + font.typeface.lineGap / 2) * font.scale)
|
||||||
at.y += (lineheight - font.defaultLineHeight) / 2
|
at.y += (lineheight - font.defaultLineHeight) / 2
|
||||||
for i, rune in result.runes:
|
for i, rune in result.runes:
|
||||||
if rune.canWrap():
|
if rune == LF:
|
||||||
prevCanWrap = i
|
result.positions[i] = at
|
||||||
|
|
||||||
let advance = advance(font, result.runes, i, kerning)
|
|
||||||
if rune != Rune(32) and bounds.x > 0 and at.x + advance > bounds.x:
|
|
||||||
# Wrap to new line
|
|
||||||
at.x = 0
|
at.x = 0
|
||||||
at.y += lineHeight
|
at.y += lineHeight
|
||||||
|
prevCanWrap = 0
|
||||||
|
else:
|
||||||
|
if rune.canWrap():
|
||||||
|
prevCanWrap = i
|
||||||
|
|
||||||
# Go back and wrap glyphs after the wrap index down to the next line
|
let advance = advance(font, result.runes, i, kerning)
|
||||||
if prevCanWrap > 0 and prevCanWrap != i:
|
if rune != SP and bounds.x > 0 and at.x + advance > bounds.x:
|
||||||
for j in prevCanWrap + 1 ..< i:
|
# Wrap to new line
|
||||||
result.positions[j] = at
|
at.x = 0
|
||||||
at.x += advance(font, result.runes, j, kerning)
|
at.y += lineHeight
|
||||||
|
|
||||||
result.positions[i] = at
|
# Go back and wrap glyphs after the wrap index down to the next line
|
||||||
at.x += advance
|
if prevCanWrap > 0 and prevCanWrap != i:
|
||||||
|
for j in prevCanWrap + 1 ..< i:
|
||||||
|
result.positions[j] = at
|
||||||
|
at.x += advance(font, result.runes, j, kerning)
|
||||||
|
|
||||||
|
result.positions[i] = at
|
||||||
|
at.x += advance
|
||||||
|
|
||||||
iterator paths*(arrangement: Arrangement): Path =
|
iterator paths*(arrangement: Arrangement): Path =
|
||||||
for i in 0 ..< arrangement.runes.len:
|
for i in 0 ..< arrangement.runes.len:
|
||||||
var path = arrangement.font.typeface.getGlyphPath(arrangement.runes[i])
|
if arrangement.runes[i].uint32 > SP.uint32: # Don't draw control runes
|
||||||
path.transform(
|
var path = arrangement.font.typeface.getGlyphPath(arrangement.runes[i])
|
||||||
translate(arrangement.positions[i]) * scale(vec2(arrangement.font.scale))
|
path.transform(
|
||||||
)
|
translate(arrangement.positions[i]) * scale(vec2(arrangement.font.scale))
|
||||||
yield path
|
)
|
||||||
|
yield path
|
||||||
|
|
||||||
proc parseOtf*(buf: string): Font =
|
proc parseOtf*(buf: string): Font =
|
||||||
result.typeface = Typeface()
|
result.typeface = Typeface()
|
||||||
|
|
BIN
tests/fonts/diffs/lines1.png
Normal file
BIN
tests/fonts/diffs/lines1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.3 KiB |
BIN
tests/fonts/masters/lines1.png
Normal file
BIN
tests/fonts/masters/lines1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
BIN
tests/fonts/rendered/lines1.png
Normal file
BIN
tests/fonts/rendered/lines1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
|
@ -1,5 +1,13 @@
|
||||||
import pixie, pixie/fileformats/png, strformat
|
import pixie, pixie/fileformats/png, strformat
|
||||||
|
|
||||||
|
proc doDiff(rendered: Image, name: string) =
|
||||||
|
rendered.writeFile(&"tests/fonts/rendered/{name}.png")
|
||||||
|
let
|
||||||
|
master = readImage(&"tests/fonts/masters/{name}.png")
|
||||||
|
(diffScore, diffImage) = diff(master, rendered)
|
||||||
|
echo &"{name} score: {diffScore}"
|
||||||
|
diffImage.writeFile(&"tests/fonts/diffs/{name}.png")
|
||||||
|
|
||||||
block:
|
block:
|
||||||
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||||
font.size = 64
|
font.size = 64
|
||||||
|
@ -65,14 +73,6 @@ block:
|
||||||
mask.fillText(font, "Ubuntu ")
|
mask.fillText(font, "Ubuntu ")
|
||||||
writeFile("tests/fonts/svg_ubuntu.png", mask.encodePng())
|
writeFile("tests/fonts/svg_ubuntu.png", mask.encodePng())
|
||||||
|
|
||||||
proc doDiff(rendered: Image, name: string) =
|
|
||||||
rendered.writeFile(&"tests/fonts/rendered/{name}.png")
|
|
||||||
let
|
|
||||||
master = readImage(&"tests/fonts/masters/{name}.png")
|
|
||||||
(diffScore, diffImage) = diff(master, rendered)
|
|
||||||
echo &"{name} score: {diffScore}"
|
|
||||||
diffImage.writeFile(&"tests/fonts/diffs/{name}.png")
|
|
||||||
|
|
||||||
block:
|
block:
|
||||||
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||||
font.size = 72
|
font.size = 72
|
||||||
|
@ -512,3 +512,24 @@ block:
|
||||||
)
|
)
|
||||||
|
|
||||||
doDiff(image, "pairs3")
|
doDiff(image, "pairs3")
|
||||||
|
|
||||||
|
block:
|
||||||
|
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||||
|
font.size = 18
|
||||||
|
|
||||||
|
let image = newImage(200, 150)
|
||||||
|
image.fill(rgba(255, 255, 255, 255))
|
||||||
|
image.fillText(
|
||||||
|
font,
|
||||||
|
"""First line
|
||||||
|
Second line
|
||||||
|
Third line
|
||||||
|
Fourth line
|
||||||
|
Fifth line
|
||||||
|
Sixth line
|
||||||
|
Seventh line""",
|
||||||
|
rgba(0, 0, 0, 255),
|
||||||
|
bounds = image.wh
|
||||||
|
)
|
||||||
|
|
||||||
|
doDiff(image, "lines1")
|
||||||
|
|
Loading…
Reference in a new issue