typeface + cleaner
This commit is contained in:
parent
a076205882
commit
8574b12900
|
@ -3,10 +3,13 @@ import pixie/fontformats/opentype, pixie/paths, unicode, vmath
|
||||||
const AutoLineHeight* = -1.float32
|
const AutoLineHeight* = -1.float32
|
||||||
|
|
||||||
type
|
type
|
||||||
Font* = ref object
|
Typeface* = ref object
|
||||||
opentype: OpenType
|
opentype: OpenType
|
||||||
glyphPaths: Table[Rune, Path]
|
glyphPaths: Table[Rune, Path]
|
||||||
kerningPairs: Table[(Rune, Rune), float32]
|
kerningPairs: Table[(Rune, Rune), float32]
|
||||||
|
|
||||||
|
Font* = ref 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.
|
lineHeight*: float32 ## The line height in pixels or AutoLineHeight for the font's default line height.
|
||||||
|
|
||||||
|
@ -28,48 +31,46 @@ type
|
||||||
# tcSmallCaps
|
# tcSmallCaps
|
||||||
# tcSmallCapsForced
|
# tcSmallCapsForced
|
||||||
|
|
||||||
proc ascent*(font: Font): float32 {.inline.} =
|
proc ascent(typeface: Typeface): float32 {.inline.} =
|
||||||
## The font ascender value in font units.
|
## The font ascender value in font units.
|
||||||
font.opentype.hhea.ascender.float32
|
typeface.opentype.hhea.ascender.float32
|
||||||
|
|
||||||
proc descent*(font: Font): float32 {.inline.} =
|
proc descent(typeface: Typeface): float32 {.inline.} =
|
||||||
## The font descender value in font units.
|
## The font descender value in font units.
|
||||||
font.opentype.hhea.descender.float32
|
typeface.opentype.hhea.descender.float32
|
||||||
|
|
||||||
proc lineGap*(font: Font): float32 {.inline.} =
|
proc lineGap(typeface: Typeface): float32 {.inline.} =
|
||||||
## The font line gap value in font units.
|
## The font line gap value in font units.
|
||||||
font.opentype.hhea.lineGap.float32
|
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:
|
||||||
|
result = typeface.opentype.hmtx.hMetrics[^1].advanceWidth.float32
|
||||||
|
|
||||||
|
proc getKerningAdjustment(typeface: Typeface, left, right: Rune): float32 =
|
||||||
|
## The kerning adjustment for the rune pair, in pixels.
|
||||||
|
let pair = (left, right)
|
||||||
|
if pair in typeface.kerningPairs:
|
||||||
|
result = typeface.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.opentype.head.unitsPerEm.float32
|
font.size / font.typeface.opentype.head.unitsPerEm.float32
|
||||||
|
|
||||||
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.ascent + abs(font.descent) + font.lineGap) * font.scale)
|
round((font.typeface.ascent + abs(font.typeface.descent) + font.typeface.lineGap) * font.scale)
|
||||||
|
|
||||||
proc getGlyphPath*(font: Font, rune: Rune): Path =
|
|
||||||
## The glyph path for the rune.
|
|
||||||
if rune notin font.glyphPaths:
|
|
||||||
font.glyphPaths[rune] = font.opentype.parseGlyph(rune)
|
|
||||||
font.glyphPaths[rune].transform(scale(vec2(1, -1)))
|
|
||||||
font.glyphPaths[rune]
|
|
||||||
|
|
||||||
proc getGlyphAdvance*(font: Font, rune: Rune): float32 =
|
|
||||||
## The advance for the rune in pixels.
|
|
||||||
let glyphId = font.opentype.getGlyphId(rune).int
|
|
||||||
if glyphId < font.opentype.hmtx.hMetrics.len:
|
|
||||||
result = font.opentype.hmtx.hMetrics[glyphId].advanceWidth.float32
|
|
||||||
else:
|
|
||||||
result = font.opentype.hmtx.hMetrics[^1].advanceWidth.float32
|
|
||||||
result *= font.scale
|
|
||||||
|
|
||||||
proc getKerningAdjustment*(font: Font, left, right: Rune): float32 =
|
|
||||||
## The kerning adjustment for the rune pair, in pixels.
|
|
||||||
let pair = (left, right)
|
|
||||||
if pair in font.kerningPairs:
|
|
||||||
result = font.kerningPairs[pair]
|
|
||||||
result *= font.scale
|
|
||||||
|
|
||||||
proc convertTextCase(runes: var seq[Rune], textCase: TextCase) =
|
proc convertTextCase(runes: var seq[Rune], textCase: TextCase) =
|
||||||
case textCase:
|
case textCase:
|
||||||
|
@ -105,20 +106,23 @@ proc typeset*(
|
||||||
else:
|
else:
|
||||||
font.defaultLineHeight
|
font.defaultLineHeight
|
||||||
|
|
||||||
|
proc glyphAdvance(runes: seq[Rune], font: Font, i: int): float32 =
|
||||||
|
if i + 1 < runes.len:
|
||||||
|
result += font.typeface.getKerningAdjustment(runes[i], runes[i + 1])
|
||||||
|
result += font.typeface.getGlyphAdvance(runes[i])
|
||||||
|
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.ascent * font.scale)
|
at.y = round(font.typeface.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():
|
||||||
prevCanWrap = i
|
prevCanWrap = i
|
||||||
|
|
||||||
if i > 0:
|
let advance = glyphAdvance(runes, font, i)
|
||||||
at.x += font.getKerningAdjustment(runes[i - 1], rune)
|
|
||||||
|
|
||||||
let advance = font.getGlyphAdvance(rune)
|
|
||||||
if bounds.x > 0 and at.x + advance > bounds.x: # Wrap to new line
|
if 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
|
||||||
|
@ -126,43 +130,42 @@ proc typeset*(
|
||||||
# Go back and wrap glyphs after the wrap index down to the next line
|
# Go back and wrap glyphs after the wrap index down to the next line
|
||||||
if prevCanWrap > 0 and prevCanWrap != i:
|
if prevCanWrap > 0 and prevCanWrap != i:
|
||||||
for j in prevCanWrap + 1 ..< i:
|
for j in prevCanWrap + 1 ..< i:
|
||||||
if j > 0:
|
|
||||||
at.x += font.getKerningAdjustment(runes[j - 1], runes[j])
|
|
||||||
positions[j] = at
|
positions[j] = at
|
||||||
at.x += font.getGlyphAdvance(runes[j])
|
at.x += glyphAdvance(runes, font, j)
|
||||||
|
|
||||||
positions[i] = at
|
positions[i] = at
|
||||||
at.x += advance
|
at.x += advance
|
||||||
|
|
||||||
for i, rune in runes:
|
for i, rune in runes:
|
||||||
var path = font.getGlyphPath(rune)
|
var path = font.typeface.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.opentype = parseOpenType(buf)
|
result.typeface = Typeface()
|
||||||
|
result.typeface.opentype = parseOpenType(buf)
|
||||||
result.size = 12
|
result.size = 12
|
||||||
result.lineHeight = AutoLineHeight
|
result.lineHeight = AutoLineHeight
|
||||||
|
|
||||||
if result.opentype.kern != nil:
|
if result.typeface.opentype.kern != nil:
|
||||||
for table in result.opentype.kern.subTables:
|
for table in result.typeface.opentype.kern.subTables:
|
||||||
if (table.coverage and 1) != 0: # Horizontal data
|
if (table.coverage and 1) != 0: # Horizontal data
|
||||||
for pair in table.kernPairs:
|
for pair in table.kernPairs:
|
||||||
if pair.value != 0 and
|
if pair.value != 0 and
|
||||||
pair.left in result.opentype.cmap.glyphIdToRune and
|
pair.left in result.typeface.opentype.cmap.glyphIdToRune and
|
||||||
pair.right in result.opentype.cmap.glyphIdToRune:
|
pair.right in result.typeface.opentype.cmap.glyphIdToRune:
|
||||||
let key = (
|
let key = (
|
||||||
result.opentype.cmap.glyphIdToRune[pair.left],
|
result.typeface.opentype.cmap.glyphIdToRune[pair.left],
|
||||||
result.opentype.cmap.glyphIdToRune[pair.right]
|
result.typeface.opentype.cmap.glyphIdToRune[pair.right]
|
||||||
)
|
)
|
||||||
var value = pair.value.float32
|
var value = pair.value.float32
|
||||||
if key in result.kerningPairs:
|
if key in result.typeface.kerningPairs:
|
||||||
if (table.coverage and 0b1000) != 0: # Override
|
if (table.coverage and 0b1000) != 0: # Override
|
||||||
discard
|
discard
|
||||||
else: # Accumulate
|
else: # Accumulate
|
||||||
value += result.kerningPairs[key]
|
value += result.typeface.kerningPairs[key]
|
||||||
result.kerningPairs[key] = value
|
result.typeface.kerningPairs[key] = value
|
||||||
|
|
||||||
proc parseTtf*(buf: string): Font =
|
proc parseTtf*(buf: string): Font =
|
||||||
parseOtf(buf)
|
parseOtf(buf)
|
||||||
|
|
Loading…
Reference in a new issue