svg fonts + move stuff around
This commit is contained in:
parent
89225bad6f
commit
b42b9d649a
12 changed files with 244 additions and 64 deletions
|
@ -6,7 +6,7 @@ license = "MIT"
|
||||||
srcDir = "src"
|
srcDir = "src"
|
||||||
|
|
||||||
requires "nim >= 1.2.6"
|
requires "nim >= 1.2.6"
|
||||||
requires "vmath >= 1.0.3"
|
requires "vmath >= 1.0.4"
|
||||||
requires "chroma >= 0.2.5"
|
requires "chroma >= 0.2.5"
|
||||||
requires "zippy >= 0.3.5"
|
requires "zippy >= 0.3.5"
|
||||||
requires "flatty >= 0.1.3"
|
requires "flatty >= 0.1.3"
|
||||||
|
|
|
@ -16,6 +16,8 @@ proc readFont*(filePath: string): Font =
|
||||||
parseTtf(readFile(filePath))
|
parseTtf(readFile(filePath))
|
||||||
of ".otf":
|
of ".otf":
|
||||||
parseOtf(readFile(filePath))
|
parseOtf(readFile(filePath))
|
||||||
|
of ".svg":
|
||||||
|
parseSvgFont(readFile(filePath))
|
||||||
else:
|
else:
|
||||||
raise newException(PixieError, "Unsupported font format")
|
raise newException(PixieError, "Unsupported font format")
|
||||||
|
|
||||||
|
|
|
@ -183,6 +183,11 @@ type
|
||||||
loca*: LocaTable
|
loca*: LocaTable
|
||||||
glyf*: GlyfTable
|
glyf*: GlyfTable
|
||||||
kern*: KernTable
|
kern*: KernTable
|
||||||
|
glyphPaths: Table[Rune, Path]
|
||||||
|
kerningPairs: Table[(Rune, Rune), float32]
|
||||||
|
|
||||||
|
when defined(release):
|
||||||
|
{.push checks: off.}
|
||||||
|
|
||||||
proc eofCheck(buf: string, readTo: int) {.inline.} =
|
proc eofCheck(buf: string, readTo: int) {.inline.} =
|
||||||
if readTo > buf.len:
|
if readTo > buf.len:
|
||||||
|
@ -569,7 +574,7 @@ proc parseKernTable(buf: string, offset: int): KernTable =
|
||||||
else:
|
else:
|
||||||
failUnsupported()
|
failUnsupported()
|
||||||
|
|
||||||
proc getGlyphId*(opentype: OpenType, rune: Rune): uint16 =
|
proc getGlyphId(opentype: OpenType, rune: Rune): uint16 =
|
||||||
if rune in opentype.cmap.runeToGlyphId:
|
if rune in opentype.cmap.runeToGlyphId:
|
||||||
result = opentype.cmap.runeToGlyphId[rune]
|
result = opentype.cmap.runeToGlyphId[rune]
|
||||||
else:
|
else:
|
||||||
|
@ -826,9 +831,27 @@ proc parseGlyph(opentype: OpenType, glyphId: uint16): Path =
|
||||||
else:
|
else:
|
||||||
parseGlyphPath(opentype.buf, i, numberOfContours)
|
parseGlyphPath(opentype.buf, i, numberOfContours)
|
||||||
|
|
||||||
proc parseGlyph*(opentype: OpenType, rune: Rune): Path =
|
proc parseGlyph(opentype: OpenType, rune: Rune): Path =
|
||||||
opentype.parseGlyph(opentype.getGlyphId(rune))
|
opentype.parseGlyph(opentype.getGlyphId(rune))
|
||||||
|
|
||||||
|
proc getGlyphPath*(opentype: OpenType, rune: Rune): Path =
|
||||||
|
if rune notin opentype.glyphPaths:
|
||||||
|
opentype.glyphPaths[rune] = opentype.parseGlyph(rune)
|
||||||
|
opentype.glyphPaths[rune].transform(scale(vec2(1, -1)))
|
||||||
|
opentype.glyphPaths[rune]
|
||||||
|
|
||||||
|
proc getGlyphAdvance*(opentype: OpenType, rune: Rune): float32 =
|
||||||
|
let glyphId = opentype.getGlyphId(rune).int
|
||||||
|
if glyphId < opentype.hmtx.hMetrics.len:
|
||||||
|
opentype.hmtx.hMetrics[glyphId].advanceWidth.float32
|
||||||
|
else:
|
||||||
|
opentype.hmtx.hMetrics[^1].advanceWidth.float32
|
||||||
|
|
||||||
|
proc getKerningAdjustment*(opentype: OpenType, left, right: Rune): float32 =
|
||||||
|
let pair = (left, right)
|
||||||
|
if pair in opentype.kerningPairs:
|
||||||
|
result = opentype.kerningPairs[pair]
|
||||||
|
|
||||||
proc parseOpenType*(buf: string): OpenType =
|
proc parseOpenType*(buf: string): OpenType =
|
||||||
result = OpenType()
|
result = OpenType()
|
||||||
result.buf = buf
|
result.buf = buf
|
||||||
|
@ -881,3 +904,24 @@ proc parseOpenType*(buf: string): OpenType =
|
||||||
|
|
||||||
if "kern" in result.tableRecords:
|
if "kern" in result.tableRecords:
|
||||||
result.kern = parseKernTable(buf, result.tableRecords["kern"].offset.int)
|
result.kern = parseKernTable(buf, result.tableRecords["kern"].offset.int)
|
||||||
|
|
||||||
|
for table in result.kern.subTables:
|
||||||
|
if (table.coverage and 1) != 0: # Horizontal data
|
||||||
|
for pair in table.kernPairs:
|
||||||
|
if pair.value != 0 and
|
||||||
|
pair.left in result.cmap.glyphIdToRune and
|
||||||
|
pair.right in result.cmap.glyphIdToRune:
|
||||||
|
let key = (
|
||||||
|
result.cmap.glyphIdToRune[pair.left],
|
||||||
|
result.cmap.glyphIdToRune[pair.right]
|
||||||
|
)
|
||||||
|
var value = pair.value.float32
|
||||||
|
if key in result.kerningPairs:
|
||||||
|
if (table.coverage and 0b1000) != 0: # Override
|
||||||
|
discard
|
||||||
|
else: # Accumulate
|
||||||
|
value += result.kerningPairs[key]
|
||||||
|
result.kerningPairs[key] = value
|
||||||
|
|
||||||
|
when defined(release):
|
||||||
|
{.pop.}
|
||||||
|
|
88
src/pixie/fontformats/svgfont.nim
Normal file
88
src/pixie/fontformats/svgfont.nim
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import pixie/common, pixie/paths, strutils, tables, unicode, vmath, xmlparser, xmltree
|
||||||
|
|
||||||
|
type SvgFont* = ref object
|
||||||
|
unitsPerEm*, ascent*, descent*: float32
|
||||||
|
glyphAdvances*: Table[Rune, float32]
|
||||||
|
glyphPaths*: Table[Rune, Path]
|
||||||
|
kerningPairs*: Table[(Rune, Rune), float32]
|
||||||
|
missingGlyphAdvance*: float32
|
||||||
|
missingGlyphPath*: Path
|
||||||
|
|
||||||
|
proc failInvalid() =
|
||||||
|
raise newException(PixieError, "Invalid SVG font data")
|
||||||
|
|
||||||
|
proc parseFloat(node: XmlNode, attr: string): float32 =
|
||||||
|
let value = node.attr(attr)
|
||||||
|
if value.len == 0:
|
||||||
|
raise newException(PixieError, "SVG font missing attr " & attr)
|
||||||
|
try:
|
||||||
|
result = parseFloat(value)
|
||||||
|
except:
|
||||||
|
failInvalid()
|
||||||
|
|
||||||
|
proc parseSvgFont*(buf: string): SvgFont =
|
||||||
|
result = SvgFont()
|
||||||
|
|
||||||
|
let
|
||||||
|
root = parseXml(buf)
|
||||||
|
defs = root.child("defs")
|
||||||
|
if defs == nil:
|
||||||
|
failInvalid()
|
||||||
|
|
||||||
|
let font = defs.child("font")
|
||||||
|
if font == nil:
|
||||||
|
failInvalid()
|
||||||
|
|
||||||
|
let defaultAdvance = font.parseFloat("horiz-adv-x")
|
||||||
|
|
||||||
|
for node in font.items:
|
||||||
|
case node.tag:
|
||||||
|
of "font-face":
|
||||||
|
result.unitsPerEm = node.parseFloat("units-per-em")
|
||||||
|
result.ascent = node.parseFloat("ascent")
|
||||||
|
result.descent = node.parseFloat("descent")
|
||||||
|
of "glyph":
|
||||||
|
let
|
||||||
|
name = node.attr("glyph-name")
|
||||||
|
unicode = node.attr("unicode")
|
||||||
|
if unicode.len > 0 or name == "space":
|
||||||
|
var
|
||||||
|
i: int
|
||||||
|
rune: Rune
|
||||||
|
if name == "space":
|
||||||
|
rune = Rune(32)
|
||||||
|
else:
|
||||||
|
fastRuneAt(unicode, i, rune, true)
|
||||||
|
if i == unicode.len:
|
||||||
|
var advance = defaultAdvance
|
||||||
|
if node.attr("horiz-adv-x").len > 0:
|
||||||
|
advance = node.parseFloat("horiz-adv-x")
|
||||||
|
result.glyphAdvances[rune] = advance
|
||||||
|
result.glyphPaths[rune] = parsePath(node.attr("d"))
|
||||||
|
result.glyphPaths[rune].transform(scale(vec2(1, -1)))
|
||||||
|
else:
|
||||||
|
discard # Multi-rune unicode?
|
||||||
|
of "hkern":
|
||||||
|
let
|
||||||
|
u1 = node.attr("u1")
|
||||||
|
u2 = node.attr("u2")
|
||||||
|
if u1.len > 0 and u2.len > 0:
|
||||||
|
var
|
||||||
|
i1, i2: int
|
||||||
|
left, right: Rune
|
||||||
|
fastRuneAt(u1, i1, left, true)
|
||||||
|
fastRuneAt(u2, i2, right, true)
|
||||||
|
if i1 == u1.len and i2 == u2.len:
|
||||||
|
let adjustment = -node.parseFloat("k")
|
||||||
|
result.kerningPairs[(left, right)] = adjustment
|
||||||
|
else:
|
||||||
|
discard # Multi-rune unicode?
|
||||||
|
of "missing-glyph":
|
||||||
|
var advance = defaultAdvance
|
||||||
|
if node.attr("horiz-adv-x").len > 0:
|
||||||
|
advance = node.parseFloat("horiz-adv-x")
|
||||||
|
result.missingGlyphAdvance = advance
|
||||||
|
result.missingGlyphPath = parsePath(node.attr("d"))
|
||||||
|
result.missingGlyphPath.transform(scale(vec2(1, -1)))
|
||||||
|
else:
|
||||||
|
discard # Unrecognized font node child
|
|
@ -1,15 +1,12 @@
|
||||||
import pixie/fontformats/opentype, pixie/paths, unicode, vmath
|
import pixie/fontformats/opentype, pixie/fontformats/svgfont, pixie/paths,
|
||||||
|
unicode, vmath
|
||||||
|
|
||||||
const AutoLineHeight* = -1.float32
|
const AutoLineHeight* = -1.float32
|
||||||
|
|
||||||
type
|
type
|
||||||
Typeface* = ref object
|
|
||||||
opentype: OpenType
|
|
||||||
glyphPaths: Table[Rune, Path]
|
|
||||||
kerningPairs: Table[(Rune, Rune), float32]
|
|
||||||
|
|
||||||
Font* = ref object
|
Font* = ref object
|
||||||
typeface*: Typeface
|
opentype: OpenType
|
||||||
|
svgFont: SvgFont
|
||||||
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.
|
lineHeight*: float32 ## The line height in pixels or AutoLineHeight for the font's default line height.
|
||||||
|
|
||||||
|
@ -31,46 +28,65 @@ type
|
||||||
# tcSmallCaps
|
# tcSmallCaps
|
||||||
# tcSmallCapsForced
|
# tcSmallCapsForced
|
||||||
|
|
||||||
proc ascent(typeface: Typeface): float32 {.inline.} =
|
proc ascent(font: Font): float32 {.inline.} =
|
||||||
## The font ascender value in font units.
|
## The font ascender value in font units.
|
||||||
typeface.opentype.hhea.ascender.float32
|
if font.opentype != nil:
|
||||||
|
font.opentype.hhea.ascender.float32
|
||||||
proc descent(typeface: Typeface): float32 {.inline.} =
|
|
||||||
## The font descender value in font units.
|
|
||||||
typeface.opentype.hhea.descender.float32
|
|
||||||
|
|
||||||
proc lineGap(typeface: Typeface): float32 {.inline.} =
|
|
||||||
## The font line gap value in font units.
|
|
||||||
typeface.opentype.hhea.lineGap.float32
|
|
||||||
|
|
||||||
proc getGlyphPath*(typeface: Typeface, rune: Rune): Path =
|
|
||||||
## The glyph path for the rune.
|
|
||||||
if rune notin typeface.glyphPaths:
|
|
||||||
typeface.glyphPaths[rune] = typeface.opentype.parseGlyph(rune)
|
|
||||||
typeface.glyphPaths[rune].transform(scale(vec2(1, -1)))
|
|
||||||
typeface.glyphPaths[rune]
|
|
||||||
|
|
||||||
proc getGlyphAdvance(typeface: Typeface, rune: Rune): float32 =
|
|
||||||
## The advance for the rune in pixels.
|
|
||||||
let glyphId = typeface.opentype.getGlyphId(rune).int
|
|
||||||
if glyphId < typeface.opentype.hmtx.hMetrics.len:
|
|
||||||
result = typeface.opentype.hmtx.hMetrics[glyphId].advanceWidth.float32
|
|
||||||
else:
|
else:
|
||||||
result = typeface.opentype.hmtx.hMetrics[^1].advanceWidth.float32
|
font.svgFont.ascent
|
||||||
|
|
||||||
proc getKerningAdjustment(typeface: Typeface, left, right: Rune): float32 =
|
proc descent(font: Font): float32 {.inline.} =
|
||||||
|
## The font descender value in font units.
|
||||||
|
if font.opentype != nil:
|
||||||
|
font.opentype.hhea.descender.float32
|
||||||
|
else:
|
||||||
|
font.svgFont.descent
|
||||||
|
|
||||||
|
proc lineGap(font: Font): float32 {.inline.} =
|
||||||
|
## The font line gap value in font units.
|
||||||
|
if font.opentype != nil:
|
||||||
|
result = font.opentype.hhea.lineGap.float32
|
||||||
|
|
||||||
|
proc getGlyphPath*(font: Font, rune: Rune): Path =
|
||||||
|
## The glyph path for the rune.
|
||||||
|
if font.opentype != nil:
|
||||||
|
font.opentype.getGlyphPath(rune)
|
||||||
|
else:
|
||||||
|
if rune in font.svgFont.glyphPaths:
|
||||||
|
font.svgFont.glyphPaths[rune]
|
||||||
|
else:
|
||||||
|
font.svgFont.missingGlyphPath
|
||||||
|
|
||||||
|
proc getGlyphAdvance(font: Font, rune: Rune): float32 =
|
||||||
|
## The advance for the rune in pixels.
|
||||||
|
if font.opentype != nil:
|
||||||
|
font.opentype.getGlyphAdvance(rune)
|
||||||
|
else:
|
||||||
|
if rune in font.svgFont.glyphAdvances:
|
||||||
|
font.svgFont.glyphAdvances[rune]
|
||||||
|
else:
|
||||||
|
font.svgFont.missingGlyphAdvance
|
||||||
|
|
||||||
|
proc getKerningAdjustment(font: Font, left, right: Rune): float32 =
|
||||||
## The kerning adjustment for the rune pair, in pixels.
|
## The kerning adjustment for the rune pair, in pixels.
|
||||||
let pair = (left, right)
|
let pair = (left, right)
|
||||||
if pair in typeface.kerningPairs:
|
if font.opentype != nil:
|
||||||
result = typeface.kerningPairs[pair]
|
result = font.opentype.getKerningAdjustment(left, right)
|
||||||
|
else:
|
||||||
|
if pair in font.svgFont.kerningPairs:
|
||||||
|
result = font.svgFont.kerningPairs[pair]
|
||||||
|
|
||||||
proc scale*(font: Font): float32 =
|
proc scale*(font: Font): float32 =
|
||||||
## The scale factor to transform font units into pixels.
|
## The scale factor to transform font units into pixels.
|
||||||
font.size / font.typeface.opentype.head.unitsPerEm.float32
|
if font.opentype != nil:
|
||||||
|
font.size / font.opentype.head.unitsPerEm.float32
|
||||||
|
else:
|
||||||
|
font.size / font.svgFont.unitsPerEm
|
||||||
|
|
||||||
proc defaultLineHeight*(font: Font): float32 =
|
proc defaultLineHeight*(font: Font): float32 =
|
||||||
## The default line height in pixels for the current font size.
|
## The default line height in pixels for the current font size.
|
||||||
round((font.typeface.ascent + abs(font.typeface.descent) + font.typeface.lineGap) * font.scale)
|
let fontUnits = (font.ascent + abs(font.descent) + font.lineGap)
|
||||||
|
round(fontUnits * font.scale)
|
||||||
|
|
||||||
proc convertTextCase(runes: var seq[Rune], textCase: TextCase) =
|
proc convertTextCase(runes: var seq[Rune], textCase: TextCase) =
|
||||||
case textCase:
|
case textCase:
|
||||||
|
@ -108,15 +124,15 @@ proc typeset*(
|
||||||
|
|
||||||
proc glyphAdvance(runes: seq[Rune], font: Font, i: int): float32 =
|
proc glyphAdvance(runes: seq[Rune], font: Font, i: int): float32 =
|
||||||
if i + 1 < runes.len:
|
if i + 1 < runes.len:
|
||||||
result += font.typeface.getKerningAdjustment(runes[i], runes[i + 1])
|
result += font.getKerningAdjustment(runes[i], runes[i + 1])
|
||||||
result += font.typeface.getGlyphAdvance(runes[i])
|
result += font.getGlyphAdvance(runes[i])
|
||||||
result *= font.scale
|
result *= font.scale
|
||||||
|
|
||||||
var
|
var
|
||||||
positions = newSeq[Vec2](runes.len)
|
positions = newSeq[Vec2](runes.len)
|
||||||
at: Vec2
|
at: Vec2
|
||||||
prevCanWrap: int
|
prevCanWrap: int
|
||||||
at.y = round(font.typeface.ascent * font.scale)
|
at.y = round(font.ascent * font.scale)
|
||||||
at.y += (lineheight - font.defaultLineHeight) / 2
|
at.y += (lineheight - font.defaultLineHeight) / 2
|
||||||
for i, rune in runes:
|
for i, rune in runes:
|
||||||
if rune.canWrap():
|
if rune.canWrap():
|
||||||
|
@ -137,35 +153,21 @@ proc typeset*(
|
||||||
at.x += advance
|
at.x += advance
|
||||||
|
|
||||||
for i, rune in runes:
|
for i, rune in runes:
|
||||||
var path = font.typeface.getGlyphPath(rune)
|
var path = font.getGlyphPath(rune)
|
||||||
path.transform(translate(positions[i]) * scale(vec2(font.scale)))
|
path.transform(translate(positions[i]) * scale(vec2(font.scale)))
|
||||||
result.add(path)
|
result.add(path)
|
||||||
|
|
||||||
proc parseOtf*(buf: string): Font =
|
proc parseOtf*(buf: string): Font =
|
||||||
result = Font()
|
result = Font()
|
||||||
result.typeface = Typeface()
|
result.opentype = parseOpenType(buf)
|
||||||
result.typeface.opentype = parseOpenType(buf)
|
|
||||||
result.size = 12
|
result.size = 12
|
||||||
result.lineHeight = AutoLineHeight
|
result.lineHeight = AutoLineHeight
|
||||||
|
|
||||||
if result.typeface.opentype.kern != nil:
|
|
||||||
for table in result.typeface.opentype.kern.subTables:
|
|
||||||
if (table.coverage and 1) != 0: # Horizontal data
|
|
||||||
for pair in table.kernPairs:
|
|
||||||
if pair.value != 0 and
|
|
||||||
pair.left in result.typeface.opentype.cmap.glyphIdToRune and
|
|
||||||
pair.right in result.typeface.opentype.cmap.glyphIdToRune:
|
|
||||||
let key = (
|
|
||||||
result.typeface.opentype.cmap.glyphIdToRune[pair.left],
|
|
||||||
result.typeface.opentype.cmap.glyphIdToRune[pair.right]
|
|
||||||
)
|
|
||||||
var value = pair.value.float32
|
|
||||||
if key in result.typeface.kerningPairs:
|
|
||||||
if (table.coverage and 0b1000) != 0: # Override
|
|
||||||
discard
|
|
||||||
else: # Accumulate
|
|
||||||
value += result.typeface.kerningPairs[key]
|
|
||||||
result.typeface.kerningPairs[key] = value
|
|
||||||
|
|
||||||
proc parseTtf*(buf: string): Font =
|
proc parseTtf*(buf: string): Font =
|
||||||
parseOtf(buf)
|
parseOtf(buf)
|
||||||
|
|
||||||
|
proc parseSvgFont*(buf: string): Font =
|
||||||
|
result = Font()
|
||||||
|
result.svgFont = svgfont.parseSvgFont(buf)
|
||||||
|
result.size = 12
|
||||||
|
result.lineHeight = AutoLineHeight
|
||||||
|
|
|
@ -48,6 +48,11 @@ proc maxScale(m: Mat3): float32 =
|
||||||
vec2(m[1, 0], m[1, 1]).length
|
vec2(m[1, 0], m[1, 1]).length
|
||||||
)
|
)
|
||||||
|
|
||||||
|
proc isRelative(kind: PathCommandKind): bool =
|
||||||
|
kind in {
|
||||||
|
RMove, RLine, TQuad, RTQuad, RHLine, RVLine, RCubic, RSCubic, RQuad, RArc
|
||||||
|
}
|
||||||
|
|
||||||
proc parameterCount(kind: PathCommandKind): int =
|
proc parameterCount(kind: PathCommandKind): int =
|
||||||
## Returns number of parameters a path command has.
|
## Returns number of parameters a path command has.
|
||||||
case kind:
|
case kind:
|
||||||
|
@ -232,6 +237,10 @@ proc parsePath*(path: string): Path =
|
||||||
proc transform*(path: var Path, mat: Mat3) =
|
proc transform*(path: var Path, mat: Mat3) =
|
||||||
## Apply a matrix transform to a path.
|
## Apply a matrix transform to a path.
|
||||||
for command in path.commands.mitems:
|
for command in path.commands.mitems:
|
||||||
|
var mat = mat
|
||||||
|
if command.kind.isRelative():
|
||||||
|
mat.pos = vec2(0)
|
||||||
|
|
||||||
case command.kind:
|
case command.kind:
|
||||||
of Close:
|
of Close:
|
||||||
discard
|
discard
|
||||||
|
|
BIN
tests/fonts/svg_changa.png
Normal file
BIN
tests/fonts/svg_changa.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
BIN
tests/fonts/svg_dejavu.png
Normal file
BIN
tests/fonts/svg_dejavu.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
BIN
tests/fonts/svg_ibm.png
Normal file
BIN
tests/fonts/svg_ibm.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
tests/fonts/svg_moon.png
Normal file
BIN
tests/fonts/svg_moon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
BIN
tests/fonts/svg_ubuntu.png
Normal file
BIN
tests/fonts/svg_ubuntu.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
|
@ -30,6 +30,41 @@ block:
|
||||||
mask.strokeText(font, "stroke")
|
mask.strokeText(font, "stroke")
|
||||||
writeFile("tests/fonts/mask_stroke.png", mask.encodePng())
|
writeFile("tests/fonts/mask_stroke.png", mask.encodePng())
|
||||||
|
|
||||||
|
block:
|
||||||
|
let font = readFont("tests/fonts/Changa-Bold.svg")
|
||||||
|
font.size = 48
|
||||||
|
let mask = newMask(200, 100)
|
||||||
|
mask.fillText(font, "Changa")
|
||||||
|
writeFile("tests/fonts/svg_changa.png", mask.encodePng())
|
||||||
|
|
||||||
|
block:
|
||||||
|
let font = readFont("tests/fonts/DejaVuSans.svg")
|
||||||
|
font.size = 48
|
||||||
|
let mask = newMask(200, 100)
|
||||||
|
mask.fillText(font, "Deja vu ")
|
||||||
|
writeFile("tests/fonts/svg_dejavu.png", mask.encodePng())
|
||||||
|
|
||||||
|
block:
|
||||||
|
let font = readFont("tests/fonts/IBMPlexSans-Regular.svg")
|
||||||
|
font.size = 48
|
||||||
|
let mask = newMask(200, 100)
|
||||||
|
mask.fillText(font, "IBM ")
|
||||||
|
writeFile("tests/fonts/svg_ibm.png", mask.encodePng())
|
||||||
|
|
||||||
|
block:
|
||||||
|
let font = readFont("tests/fonts/Moon-Bold.svg")
|
||||||
|
font.size = 48
|
||||||
|
let mask = newMask(200, 100)
|
||||||
|
mask.fillText(font, "Moon ")
|
||||||
|
writeFile("tests/fonts/svg_moon.png", mask.encodePng())
|
||||||
|
|
||||||
|
block:
|
||||||
|
let font = readFont("tests/fonts/Ubuntu.svg")
|
||||||
|
font.size = 48
|
||||||
|
let mask = newMask(200, 100)
|
||||||
|
mask.fillText(font, "Ubuntu ")
|
||||||
|
writeFile("tests/fonts/svg_ubuntu.png", mask.encodePng())
|
||||||
|
|
||||||
proc doDiff(rendered: Image, name: string) =
|
proc doDiff(rendered: Image, name: string) =
|
||||||
let
|
let
|
||||||
master = readImage(&"tests/fonts/masters/{name}.png")
|
master = readImage(&"tests/fonts/masters/{name}.png")
|
||||||
|
|
Loading…
Reference in a new issue