Add Unicode support to TTF renderer.

It works. Mostly. Sort of. Only on Windows fonts. Sometimes it
randomly refuses to render glyphs (try `х`, that's not a latin ex).
I'm not really sure why, the logic seems right.

Why do we have a homegrown TTF parser anyway? It's kind of awful.
It breaks on any slightly unusual input. It plows through UTF-16BE
font names like a nuclear-powered steamroller. It outright ignores
composite glyphs (is that why it's broken this time?). The kerning
is seizure-inducing. It ignores any characters outside BMP by design.

Maybe we should just replace it with freetype.
This commit is contained in:
whitequark 2015-12-29 00:18:58 +08:00
parent 11f29b1231
commit b8e8da0c0b
2 changed files with 48 additions and 54 deletions

View File

@ -471,9 +471,8 @@ public:
bool loaded; bool loaded;
// The font itself, plus the mapping from ASCII codes to glyphs // The font itself, plus the mapping from ASCII codes to glyphs
int useGlyph[256]; std::vector<uint16_t> charMap;
Glyph *glyph; std::vector<Glyph> glyph;
int glyphs;
int maxPoints; int maxPoints;
int scale; int scale;
@ -506,7 +505,7 @@ public:
void Flush(void); void Flush(void);
void Handle(int *dx, int x, int y, bool onCurve); void Handle(int *dx, int x, int y, bool onCurve);
void PlotCharacter(int *dx, int c, double spacing); void PlotCharacter(int *dx, char32_t c, double spacing);
void PlotString(const char *str, double spacing, void PlotString(const char *str, double spacing,
SBezierList *sbl, Vector origin, Vector u, Vector v); SBezierList *sbl, Vector origin, Vector u, Vector v);

View File

@ -99,7 +99,7 @@ uint32_t TtfFont::GetULONG(void) {
// glyphs[index] // glyphs[index]
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void TtfFont::LoadGlyph(int index) { void TtfFont::LoadGlyph(int index) {
if(index < 0 || index >= glyphs) return; if(index < 0 || index >= glyph.size()) return;
int i; int i;
@ -109,8 +109,12 @@ void TtfFont::LoadGlyph(int index) {
int16_t xMax = (int16_t)GetUSHORT(); int16_t xMax = (int16_t)GetUSHORT();
int16_t yMax = (int16_t)GetUSHORT(); int16_t yMax = (int16_t)GetUSHORT();
if(useGlyph[(int)'A'] == index) { if(charMap.size() > 'A' && charMap[(int)'A'] == index) {
if(yMax > 0) {
scale = (1024*1024) / yMax; scale = (1024*1024) / yMax;
} else {
scale = 1;
}
} }
if(contours > 0) { if(contours > 0) {
@ -430,8 +434,7 @@ bool TtfFont::LoadFontFromFile(bool nameOnly) {
uint16_t maxpMaxComponentElements = GetUSHORT(); uint16_t maxpMaxComponentElements = GetUSHORT();
uint16_t maxpMaxComponentDepth = GetUSHORT(); uint16_t maxpMaxComponentDepth = GetUSHORT();
glyphs = maxpNumGlyphs; glyph.resize(maxpNumGlyphs);
glyph = (Glyph *)MemAlloc(glyphs*sizeof(glyph[0]));
// Load the hmtx table, which gives the horizontal metrics (spacing // Load the hmtx table, which gives the horizontal metrics (spacing
// and advance width) of the font. // and advance width) of the font.
@ -439,7 +442,7 @@ bool TtfFont::LoadFontFromFile(bool nameOnly) {
uint16_t hmtxAdvanceWidth = 0; uint16_t hmtxAdvanceWidth = 0;
int16_t hmtxLsb = 0; int16_t hmtxLsb = 0;
for(i = 0; i < min(glyphs, (int)hheaNumberOfMetrics); i++) { for(i = 0; i < min(glyph.size(), (size_t)hheaNumberOfMetrics); i++) {
hmtxAdvanceWidth = GetUSHORT(); hmtxAdvanceWidth = GetUSHORT();
hmtxLsb = (int16_t)GetUSHORT(); hmtxLsb = (int16_t)GetUSHORT();
@ -447,7 +450,7 @@ bool TtfFont::LoadFontFromFile(bool nameOnly) {
glyph[i].advanceWidth = hmtxAdvanceWidth; glyph[i].advanceWidth = hmtxAdvanceWidth;
} }
// The last entry in the table applies to all subsequent glyphs also. // The last entry in the table applies to all subsequent glyphs also.
for(; i < glyphs; i++) { for(; i < glyph.size(); i++) {
glyph[i].leftSideBearing = hmtxLsb; glyph[i].leftSideBearing = hmtxLsb;
glyph[i].advanceWidth = hmtxAdvanceWidth; glyph[i].advanceWidth = hmtxAdvanceWidth;
} }
@ -515,38 +518,24 @@ bool TtfFont::LoadFontFromFile(bool nameOnly) {
idRangeOffset[i] = GetUSHORT(); idRangeOffset[i] = GetUSHORT();
} }
// So first, null out the glyph table in our in-memory representation
// of the font; any character for which cmap does not provide a glyph
// corresponds to -1
for(i = 0; i < (int)arraylen(useGlyph); i++) {
useGlyph[i] = 0;
}
for(i = 0; i < segCount; i++) { for(i = 0; i < segCount; i++) {
if(charMap.size() < endChar[i] + 1)
charMap.resize(endChar[i] + 1);
uint16_t v = idDelta[i]; uint16_t v = idDelta[i];
if(idRangeOffset[i] == 0) { if(idRangeOffset[i] == 0) {
int j; for(int j = startChar[i]; j <= endChar[i]; j++) {
for(j = startChar[i]; j <= endChar[i]; j++) {
if(j > 0 && j < (int)arraylen(useGlyph)) {
// Don't create a reference to a glyph that we won't
// store because it's bigger than the table.
if((uint16_t)(j + v) < glyphs) {
// Arithmetic is modulo 2^16 // Arithmetic is modulo 2^16
useGlyph[j] = (uint16_t)(j + v); charMap[j] = (uint16_t)(j + v);
}
}
} }
} else { } else {
int j; for(int j = startChar[i]; j <= endChar[i]; j++) {
for(j = startChar[i]; j <= endChar[i]; j++) {
if(j > 0 && j < (int)arraylen(useGlyph)) {
int fp = filePos[i]; int fp = filePos[i];
fp += (j - startChar[i])*sizeof(uint16_t); fp += (j - startChar[i])*sizeof(uint16_t);
fp += idRangeOffset[i]; fp += idRangeOffset[i];
fseek(fh, fp, SEEK_SET); fseek(fh, fp, SEEK_SET);
useGlyph[j] = GetUSHORT(); charMap[j] = GetUSHORT();
}
} }
} }
} }
@ -555,9 +544,9 @@ bool TtfFont::LoadFontFromFile(bool nameOnly) {
// relative to the beginning of the glyf table. // relative to the beginning of the glyf table.
fseek(fh, locaAddr, SEEK_SET); fseek(fh, locaAddr, SEEK_SET);
uint32_t *glyphOffsets = (uint32_t *)AllocTemporary(glyphs*sizeof(uint32_t)); uint32_t *glyphOffsets = (uint32_t *)AllocTemporary(glyph.size()*sizeof(uint32_t));
for(i = 0; i < glyphs; i++) { for(i = 0; i < glyph.size(); i++) {
if(headIndexToLocFormat == 1) { if(headIndexToLocFormat == 1) {
// long offsets, 32 bits // long offsets, 32 bits
glyphOffsets[i] = GetULONG(); glyphOffsets[i] = GetULONG();
@ -572,7 +561,7 @@ bool TtfFont::LoadFontFromFile(bool nameOnly) {
scale = 1024; scale = 1024;
// Load the glyf table. This contains the actual representations of the // Load the glyf table. This contains the actual representations of the
// letter forms, as piecewise linear or quadratic outlines. // letter forms, as piecewise linear or quadratic outlines.
for(i = 0; i < glyphs; i++) { for(i = 0; i < glyph.size(); i++) {
fseek(fh, glyfAddr + glyphOffsets[i], SEEK_SET); fseek(fh, glyfAddr + glyphOffsets[i], SEEK_SET);
LoadGlyph(i); LoadGlyph(i);
} }
@ -632,10 +621,16 @@ void TtfFont::Handle(int *dx, int x, int y, bool onCurve) {
} }
} }
void TtfFont::PlotCharacter(int *dx, int c, double spacing) { void TtfFont::PlotCharacter(int *dx, char32_t c, double spacing) {
int gli = useGlyph[c]; int gli;
if(c < charMap.size()) {
gli = charMap[c];
if(gli < 0 || gli >= glyph.size())
gli = 0; // 0, by convention, is the unknown glyph
} else {
gli = 0;
}
if(gli < 0 || gli >= glyphs) return;
Glyph *g = &(glyph[gli]); Glyph *g = &(glyph[gli]);
if(!g->pt) return; if(!g->pt) return;
@ -688,10 +683,10 @@ void TtfFont::PlotString(const char *str, double spacing,
} }
int dx = 0; int dx = 0;
while(*str) { while(*str) {
PlotCharacter(&dx, *str, spacing); char32_t chr;
str++; str = ReadUTF8(str, &chr);
PlotCharacter(&dx, chr, spacing);
} }
} }