diff --git a/src/pixie/fontformats/opentype.nim b/src/pixie/fontformats/opentype.nim index e3e51e0..1cb5233 100644 --- a/src/pixie/fontformats/opentype.nim +++ b/src/pixie/fontformats/opentype.nim @@ -15,6 +15,7 @@ type numTables*: uint16 encodingRecords*: seq[EncodingRecord] runeToGlyphId*: Table[Rune, uint16] + glyphIdToRune*: Table[uint16, Rune] HeadTable* = ref object majorVersion*: uint16 @@ -295,6 +296,7 @@ proc parseCmapTable(buf: string, offset: int): CmapTable = if c != 65535: result.runeToGlyphId[Rune(c)] = glyphId.uint16 + result.glyphIdToRune[glyphId.uint16] = Rune(c) else: # TODO implement other Windows encodingIDs discard @@ -563,13 +565,13 @@ proc parseKernTable(buf: string, offset: int): KernTable = else: failUnsupported() -proc getGlyphId*(opentype: OpenType, rune: Rune): int = +proc getGlyphId*(opentype: OpenType, rune: Rune): uint16 = if rune in opentype.cmap.runeToGlyphId: - result = opentype.cmap.runeToGlyphId[rune].int + result = opentype.cmap.runeToGlyphId[rune] else: discard # Index 0 is the "missing character" glyph -proc parseGlyph(opentype: OpenType, glyphId: int): Path +proc parseGlyph(opentype: OpenType, glyphId: uint16): Path proc parseGlyphPath(buf: string, offset, numberOfContours: int): Path = if numberOfContours < 0: @@ -781,7 +783,7 @@ proc parseCompositeGlyph(opentype: OpenType, offset: int): Path = # elif (flags and 0b1000000000000) != 0: # UNSCALED_COMPONENT_OFFSET # discard - var subPath = opentype.parseGlyph(component.glyphId.int) + var subPath = opentype.parseGlyph(component.glyphId) subPath.transform(mat3( component.xScale, component.scale10, 0.0, component.scale01, component.yScale, 0.0, @@ -792,8 +794,8 @@ proc parseCompositeGlyph(opentype: OpenType, offset: int): Path = moreComponents = (flags and 0b100000) != 0 -proc parseGlyph(opentype: OpenType, glyphId: int): Path = - if glyphId < 0 or glyphId >= opentype.glyf.offsets.len: +proc parseGlyph(opentype: OpenType, glyphId: uint16): Path = + if glyphId.int >= opentype.glyf.offsets.len: raise newException(PixieError, "Invalid glyph ID " & $glyphId) let glyphOffset = opentype.glyf.offsets[glyphId] diff --git a/src/pixie/fonts.nim b/src/pixie/fonts.nim index af56b89..f695139 100644 --- a/src/pixie/fonts.nim +++ b/src/pixie/fonts.nim @@ -6,6 +6,7 @@ type Font* = ref object opentype: OpenType glyphPaths: Table[Rune, Path] + kerningPairs: Table[(Rune, Rune), float32] size*: float32 ## Font size in pixels. lineHeight*: float32 ## The line height in pixels or AutoLineHeight for the font's default line height. @@ -46,12 +47,17 @@ proc getGlyphPath*(font: Font, rune: Rune): Path = font.glyphPaths[rune] proc getGlyphAdvance*(font: Font, rune: Rune): float32 = - let glyphId = font.opentype.getGlyphId(rune) + let glyphId = font.opentype.getGlyphId(rune).int if glyphId < font.opentype.hmtx.hMetrics.len: font.opentype.hmtx.hMetrics[glyphId].advanceWidth.float32 else: font.opentype.hmtx.hMetrics[^1].advanceWidth.float32 +proc getKerningAdjustment*(font: Font, left, right: Rune): float32 = + let pair = (left, right) + if pair in font.kerningPairs: + result = font.kerningPairs[pair] + proc scale*(font: Font): float32 = ## The scale factor to transform font units into pixels. font.size / font.opentype.head.unitsPerEm.float32 @@ -104,6 +110,9 @@ proc typeset*( if rune.canWrap(): prevCanWrap = i + if i > 0: + at.x += font.getKerningAdjustment(runes[i - 1], rune) * font.scale + let advance = font.getGlyphAdvance(rune) * font.scale if bounds.x > 0 and at.x + advance > bounds.x: # Wrap to new line at.x = 0 @@ -112,6 +121,8 @@ 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]) * font.scale positions[j] = at at.x += font.getGlyphAdvance(runes[j]) * font.scale @@ -129,5 +140,16 @@ proc parseOtf*(buf: string): Font = result.size = 12 result.lineHeight = AutoLineHeight + if result.opentype.kern != nil: + for table in result.opentype.kern.subTables: + 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: + result.kerningPairs[( + result.opentype.cmap.glyphIdToRune[pair.left], + result.opentype.cmap.glyphIdToRune[pair.right] + )] = pair.value.float32 + proc parseTtf*(buf: string): Font = parseOtf(buf) diff --git a/tests/fonts/diffs/basic6.png b/tests/fonts/diffs/basic6.png index 88724da..8e468a6 100644 Binary files a/tests/fonts/diffs/basic6.png and b/tests/fonts/diffs/basic6.png differ diff --git a/tests/fonts/rendered/basic6.png b/tests/fonts/rendered/basic6.png index 51e6a5f..8388fd2 100644 Binary files a/tests/fonts/rendered/basic6.png and b/tests/fonts/rendered/basic6.png differ