diff --git a/src/pixie/fonts.nim b/src/pixie/fonts.nim index 4e34806..19f928f 100644 --- a/src/pixie/fonts.nim +++ b/src/pixie/fonts.nim @@ -3,10 +3,13 @@ import pixie/fontformats/opentype, pixie/paths, unicode, vmath const AutoLineHeight* = -1.float32 type - Font* = ref object + Typeface* = ref object opentype: OpenType glyphPaths: Table[Rune, Path] kerningPairs: Table[(Rune, Rune), float32] + + Font* = ref object + typeface*: Typeface size*: float32 ## Font size in pixels. lineHeight*: float32 ## The line height in pixels or AutoLineHeight for the font's default line height. @@ -28,48 +31,46 @@ type # tcSmallCaps # tcSmallCapsForced -proc ascent*(font: Font): float32 {.inline.} = +proc ascent(typeface: Typeface): float32 {.inline.} = ## 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. - 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. - 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 = ## 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 = ## The default line height in pixels for the current font size. - round((font.ascent + abs(font.descent) + font.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 + round((font.typeface.ascent + abs(font.typeface.descent) + font.typeface.lineGap) * font.scale) proc convertTextCase(runes: var seq[Rune], textCase: TextCase) = case textCase: @@ -105,20 +106,23 @@ proc typeset*( else: 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 positions = newSeq[Vec2](runes.len) at: Vec2 prevCanWrap: int - at.y = round(font.ascent * font.scale) + at.y = round(font.typeface.ascent * font.scale) at.y += (lineheight - font.defaultLineHeight) / 2 for i, rune in runes: if rune.canWrap(): prevCanWrap = i - if i > 0: - at.x += font.getKerningAdjustment(runes[i - 1], rune) - - let advance = font.getGlyphAdvance(rune) + let advance = glyphAdvance(runes, font, i) if bounds.x > 0 and at.x + advance > bounds.x: # Wrap to new line at.x = 0 at.y += lineHeight @@ -126,43 +130,42 @@ proc typeset*( # Go back and wrap glyphs after the wrap index down to the next line if prevCanWrap > 0 and prevCanWrap != i: for j in prevCanWrap + 1 ..< i: - if j > 0: - at.x += font.getKerningAdjustment(runes[j - 1], runes[j]) positions[j] = at - at.x += font.getGlyphAdvance(runes[j]) + at.x += glyphAdvance(runes, font, j) positions[i] = at at.x += advance 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))) result.add(path) proc parseOtf*(buf: string): Font = result = Font() - result.opentype = parseOpenType(buf) + result.typeface = Typeface() + result.typeface.opentype = parseOpenType(buf) result.size = 12 result.lineHeight = AutoLineHeight - if result.opentype.kern != nil: - for table in result.opentype.kern.subTables: + 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.opentype.cmap.glyphIdToRune and - pair.right in result.opentype.cmap.glyphIdToRune: + pair.left in result.typeface.opentype.cmap.glyphIdToRune and + pair.right in result.typeface.opentype.cmap.glyphIdToRune: let key = ( - result.opentype.cmap.glyphIdToRune[pair.left], - result.opentype.cmap.glyphIdToRune[pair.right] + result.typeface.opentype.cmap.glyphIdToRune[pair.left], + result.typeface.opentype.cmap.glyphIdToRune[pair.right] ) var value = pair.value.float32 - if key in result.kerningPairs: + if key in result.typeface.kerningPairs: if (table.coverage and 0b1000) != 0: # Override discard else: # Accumulate - value += result.kerningPairs[key] - result.kerningPairs[key] = value + value += result.typeface.kerningPairs[key] + result.typeface.kerningPairs[key] = value proc parseTtf*(buf: string): Font = parseOtf(buf)