solvespace/ttf.cpp

711 lines
23 KiB
C++

//-----------------------------------------------------------------------------
// Routines to read a TrueType font as vector outlines, and generate them
// as entities, since they're always representable as either lines or
// quadratic Bezier curves.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
//-----------------------------------------------------------------------------
// Get the list of available font filenames, and load the name for each of
// them. Only that, though, not the glyphs too.
//-----------------------------------------------------------------------------
void TtfFontList::LoadAll(void) {
if(loaded) return;
// Get the list of font files from the platform-specific code.
LoadAllFontFiles();
int i;
for(i = 0; i < l.n; i++) {
TtfFont *tf = &(l.elem[i]);
tf->LoadFontFromFile(true);
}
loaded = true;
}
void TtfFontList::PlotString(char *font, char *str, double spacing,
SBezierList *sbl,
Vector origin, Vector u, Vector v)
{
LoadAll();
int i;
for(i = 0; i < l.n; i++) {
TtfFont *tf = &(l.elem[i]);
if(strcmp(tf->FontFileBaseName(), font)==0) {
tf->LoadFontFromFile(false);
tf->PlotString(str, spacing, sbl, origin, u, v);
return;
}
}
// Couldn't find the font; so draw a big X for an error marker.
SBezier sb;
sb = SBezier::From(origin, origin.Plus(u).Plus(v));
sbl->l.Add(&sb);
sb = SBezier::From(origin.Plus(v), origin.Plus(u));
sbl->l.Add(&sb);
}
//=============================================================================
//-----------------------------------------------------------------------------
// Get a single character from the open .ttf file; EOF is an error, since
// we can always see that coming.
//-----------------------------------------------------------------------------
int TtfFont::Getc(void) {
int c = fgetc(fh);
if(c < 0) {
throw "EOF";
}
return c;
}
//-----------------------------------------------------------------------------
// Helpers to get 1, 2, or 4 bytes from the .ttf file. Big endian.
//-----------------------------------------------------------------------------
int TtfFont::GetBYTE(void) {
return Getc();
}
int TtfFont::GetWORD(void) {
BYTE b0, b1;
b1 = Getc();
b0 = Getc();
return (b1 << 8) | b0;
}
int TtfFont::GetDWORD(void) {
BYTE b0, b1, b2, b3;
b3 = Getc();
b2 = Getc();
b1 = Getc();
b0 = Getc();
return (b3 << 24) | (b2 << 16) | (b1 << 8) | b0;
}
//-----------------------------------------------------------------------------
// Load a glyph from the .ttf file into memory. Assumes that the .ttf file
// is already seeked to the correct location, and writes the result to
// glyphs[index]
//-----------------------------------------------------------------------------
void TtfFont::LoadGlyph(int index) {
if(index < 0 || index >= glyphs) return;
int i;
SWORD contours = GetWORD();
SWORD xMin = GetWORD();
SWORD yMin = GetWORD();
SWORD xMax = GetWORD();
SWORD yMax = GetWORD();
if(useGlyph['A'] == index) {
scale = (1024*1024) / yMax;
}
if(contours > 0) {
WORD *endPointsOfContours =
(WORD *)AllocTemporary(contours*sizeof(WORD));
for(i = 0; i < contours; i++) {
endPointsOfContours[i] = GetWORD();
}
WORD totalPts = endPointsOfContours[i-1] + 1;
WORD instructionLength = GetWORD();
for(i = 0; i < instructionLength; i++) {
// We can ignore the instructions, since we're doing vector
// output.
(void)GetBYTE();
}
BYTE *flags = (BYTE *)AllocTemporary(totalPts*sizeof(BYTE));
SWORD *x = (SWORD *)AllocTemporary(totalPts*sizeof(SWORD));
SWORD *y = (SWORD *)AllocTemporary(totalPts*sizeof(SWORD));
// Flags, that indicate format of the coordinates
#define FLAG_ON_CURVE (1 << 0)
#define FLAG_DX_IS_BYTE (1 << 1)
#define FLAG_DY_IS_BYTE (1 << 2)
#define FLAG_REPEAT (1 << 3)
#define FLAG_X_IS_SAME (1 << 4)
#define FLAG_X_IS_POSITIVE (1 << 4)
#define FLAG_Y_IS_SAME (1 << 5)
#define FLAG_Y_IS_POSITIVE (1 << 5)
for(i = 0; i < totalPts; i++) {
flags[i] = GetBYTE();
if(flags[i] & FLAG_REPEAT) {
int n = GetBYTE();
BYTE f = flags[i];
int j;
for(j = 0; j < n; j++) {
i++;
if(i >= totalPts) {
throw "too many points in glyph";
}
flags[i] = f;
}
}
}
// x coordinates
SWORD xa = 0;
for(i = 0; i < totalPts; i++) {
if(flags[i] & FLAG_DX_IS_BYTE) {
BYTE v = GetBYTE();
if(flags[i] & FLAG_X_IS_POSITIVE) {
xa += v;
} else {
xa -= v;
}
} else {
if(flags[i] & FLAG_X_IS_SAME) {
// no change
} else {
SWORD d = GetWORD();
xa += d;
}
}
x[i] = xa;
}
// y coordinates
SWORD ya = 0;
for(i = 0; i < totalPts; i++) {
if(flags[i] & FLAG_DY_IS_BYTE) {
BYTE v = GetBYTE();
if(flags[i] & FLAG_Y_IS_POSITIVE) {
ya += v;
} else {
ya -= v;
}
} else {
if(flags[i] & FLAG_Y_IS_SAME) {
// no change
} else {
SWORD d = GetWORD();
ya += d;
}
}
y[i] = ya;
}
Glyph *g = &(glyph[index]);
g->pt = (FontPoint *)MemAlloc(totalPts*sizeof(FontPoint));
int contour = 0;
for(i = 0; i < totalPts; i++) {
g->pt[i].x = x[i];
g->pt[i].y = y[i];
g->pt[i].onCurve = (BYTE)(flags[i] & FLAG_ON_CURVE);
if(i == endPointsOfContours[contour]) {
g->pt[i].lastInContour = true;
contour++;
} else {
g->pt[i].lastInContour = false;
}
}
g->pts = totalPts;
g->xMax = xMax;
g->xMin = xMin;
} else {
// This is a composite glyph, TODO.
}
}
//-----------------------------------------------------------------------------
// Return the basename of our font filename; that's how the requests and
// entities that reference us will store it.
//-----------------------------------------------------------------------------
char *TtfFont::FontFileBaseName(void) {
char *sb = strrchr(fontFile, '\\');
char *sf = strrchr(fontFile, '/');
char *s = sf ? sf : sb;
if(!s) return "";
return s + 1;
}
//-----------------------------------------------------------------------------
// Load a TrueType font into memory. We care about the curves that define
// the letter shapes, and about the mappings that determine which glyph goes
// with which character.
//-----------------------------------------------------------------------------
bool TtfFont::LoadFontFromFile(bool nameOnly) {
if(loaded) return true;
int i;
fh = fopen(fontFile, "rb");
if(!fh) {
return false;
}
try {
// First, load the Offset Table
DWORD version = GetDWORD();
WORD numTables = GetWORD();
WORD searchRange = GetWORD();
WORD entrySelector = GetWORD();
WORD rangeShift = GetWORD();
// Now load the Table Directory; our goal in doing this will be to
// find the addresses of the tables that we will need.
DWORD glyfAddr = -1, glyfLen;
DWORD cmapAddr = -1, cmapLen;
DWORD headAddr = -1, headLen;
DWORD locaAddr = -1, locaLen;
DWORD maxpAddr = -1, maxpLen;
DWORD nameAddr = -1, nameLen;
DWORD hmtxAddr = -1, hmtxLen;
DWORD hheaAddr = -1, hheaLen;
for(i = 0; i < numTables; i++) {
char tag[5] = "xxxx";
tag[0] = GetBYTE();
tag[1] = GetBYTE();
tag[2] = GetBYTE();
tag[3] = GetBYTE();
DWORD checksum = GetDWORD();
DWORD offset = GetDWORD();
DWORD length = GetDWORD();
if(strcmp(tag, "glyf")==0) {
glyfAddr = offset;
glyfLen = length;
} else if(strcmp(tag, "cmap")==0) {
cmapAddr = offset;
cmapLen = length;
} else if(strcmp(tag, "head")==0) {
headAddr = offset;
headLen = length;
} else if(strcmp(tag, "loca")==0) {
locaAddr = offset;
locaLen = length;
} else if(strcmp(tag, "maxp")==0) {
maxpAddr = offset;
maxpLen = length;
} else if(strcmp(tag, "name")==0) {
nameAddr = offset;
nameLen = length;
} else if(strcmp(tag, "hhea")==0) {
hheaAddr = offset;
hheaLen = length;
} else if(strcmp(tag, "hmtx")==0) {
hmtxAddr = offset;
hmtxLen = length;
}
}
if(glyfAddr == -1 || cmapAddr == -1 || headAddr == -1 ||
locaAddr == -1 || maxpAddr == -1 || hmtxAddr == -1 ||
nameAddr == -1 || hheaAddr == -1)
{
throw "missing table addr";
}
// Load the name table. This gives us display names for the font, which
// we need when we're giving the user a list to choose from.
fseek(fh, nameAddr, SEEK_SET);
WORD nameFormat = GetWORD();
WORD nameCount = GetWORD();
WORD nameStringOffset = GetWORD();
// And now we're at the name records. Go through those till we find
// one that we want.
int displayNameOffset, displayNameLength;
for(i = 0; i < nameCount; i++) {
WORD platformID = GetWORD();
WORD encodingID = GetWORD();
WORD languageID = GetWORD();
WORD nameId = GetWORD();
WORD length = GetWORD();
WORD offset = GetWORD();
if(nameId == 4) {
displayNameOffset = offset;
displayNameLength = length;
break;
}
}
if(nameOnly && i >= nameCount) {
throw "no name";
}
if(nameOnly) {
// Find the display name, and store it in the provided buffer.
fseek(fh, nameAddr+nameStringOffset+displayNameOffset, SEEK_SET);
int c = 0;
for(i = 0; i < displayNameLength; i++) {
BYTE b = GetBYTE();
if(b && c < (sizeof(name.str) - 2)) {
name.str[c++] = b;
}
}
name.str[c++] = '\0';
fclose(fh);
return true;
}
// Load the head table; we need this to determine the format of the
// loca table, 16- or 32-bit entries
fseek(fh, headAddr, SEEK_SET);
DWORD headVersion = GetDWORD();
DWORD headFontRevision = GetDWORD();
DWORD headCheckSumAdj = GetDWORD();
DWORD headMagicNumber = GetDWORD();
WORD headFlags = GetWORD();
WORD headUnitsPerEm = GetWORD();
(void)GetDWORD(); // created time
(void)GetDWORD();
(void)GetDWORD(); // modified time
(void)GetDWORD();
WORD headXmin = GetWORD();
WORD headYmin = GetWORD();
WORD headXmax = GetWORD();
WORD headYmax = GetWORD();
WORD headMacStyle = GetWORD();
WORD headLowestRecPPEM = GetWORD();
WORD headFontDirectionHint = GetWORD();
WORD headIndexToLocFormat = GetWORD();
WORD headGlyphDataFormat = GetWORD();
if(headMagicNumber != 0x5F0F3CF5) {
throw "bad magic number";
}
// Load the hhea table, which contains the number of entries in the
// horizontal metrics (hmtx) table.
fseek(fh, hheaAddr, SEEK_SET);
DWORD hheaVersion = GetDWORD();
WORD hheaAscender = GetWORD();
WORD hheaDescender = GetWORD();
WORD hheaLineGap = GetWORD();
WORD hheaAdvanceWidthMax = GetWORD();
WORD hheaMinLsb = GetWORD();
WORD hheaMinRsb = GetWORD();
WORD hheaXMaxExtent = GetWORD();
WORD hheaCaretSlopeRise = GetWORD();
WORD hheaCaretSlopeRun = GetWORD();
WORD hheaCaretOffset = GetWORD();
(void)GetWORD();
(void)GetWORD();
(void)GetWORD();
(void)GetWORD();
WORD hheaMetricDataFormat = GetWORD();
WORD hheaNumberOfMetrics = GetWORD();
// Load the maxp table, which determines (among other things) the number
// of glyphs in the font
fseek(fh, maxpAddr, SEEK_SET);
DWORD maxpVersion = GetDWORD();
WORD maxpNumGlyphs = GetWORD();
WORD maxpMaxPoints = GetWORD();
WORD maxpMaxContours = GetWORD();
WORD maxpMaxComponentPoints = GetWORD();
WORD maxpMaxComponentContours = GetWORD();
WORD maxpMaxZones = GetWORD();
WORD maxpMaxTwilightPoints = GetWORD();
WORD maxpMaxStorage = GetWORD();
WORD maxpMaxFunctionDefs = GetWORD();
WORD maxpMaxInstructionDefs = GetWORD();
WORD maxpMaxStackElements = GetWORD();
WORD maxpMaxSizeOfInstructions = GetWORD();
WORD maxpMaxComponentElements = GetWORD();
WORD maxpMaxComponentDepth = GetWORD();
glyphs = maxpNumGlyphs;
glyph = (Glyph *)MemAlloc(glyphs*sizeof(glyph[0]));
// Load the hmtx table, which gives the horizontal metrics (spacing
// and advance width) of the font.
fseek(fh, hmtxAddr, SEEK_SET);
WORD hmtxAdvanceWidth;
SWORD hmtxLsb;
for(i = 0; i < min(glyphs, hheaNumberOfMetrics); i++) {
hmtxAdvanceWidth = GetWORD();
hmtxLsb = (SWORD)GetWORD();
glyph[i].leftSideBearing = hmtxLsb;
glyph[i].advanceWidth = hmtxAdvanceWidth;
}
// The last entry in the table applies to all subsequent glyphs also.
for(; i < glyphs; i++) {
glyph[i].leftSideBearing = hmtxLsb;
glyph[i].advanceWidth = hmtxAdvanceWidth;
}
// Load the cmap table, which determines the mapping of characters to
// glyphs.
fseek(fh, cmapAddr, SEEK_SET);
DWORD usedTableAddr = -1;
WORD cmapVersion = GetWORD();
WORD cmapTableCount = GetWORD();
for(i = 0; i < cmapTableCount; i++) {
WORD platformId = GetWORD();
WORD encodingId = GetWORD();
DWORD offset = GetDWORD();
if(platformId == 3 && encodingId == 1) {
// The Windows Unicode mapping is our preference
usedTableAddr = cmapAddr + offset;
}
}
if(usedTableAddr == -1) {
throw "no used table addr";
}
// So we can load the desired subtable; in this case, Windows Unicode,
// which is us.
fseek(fh, usedTableAddr, SEEK_SET);
WORD mapFormat = GetWORD();
WORD mapLength = GetWORD();
WORD mapVersion = GetWORD();
WORD mapSegCountX2 = GetWORD();
WORD mapSearchRange = GetWORD();
WORD mapEntrySelector = GetWORD();
WORD mapRangeShift = GetWORD();
if(mapFormat != 4) {
// Required to use format 4 per spec
throw "not format 4";
}
int segCount = mapSegCountX2 / 2;
WORD *endChar = (WORD *)AllocTemporary(segCount*sizeof(WORD));
WORD *startChar = (WORD *)AllocTemporary(segCount*sizeof(WORD));
WORD *idDelta = (WORD *)AllocTemporary(segCount*sizeof(WORD));
WORD *idRangeOffset = (WORD *)AllocTemporary(segCount*sizeof(WORD));
DWORD *filePos = (DWORD *)AllocTemporary(segCount*sizeof(DWORD));
for(i = 0; i < segCount; i++) {
endChar[i] = GetWORD();
}
WORD mapReservedPad = GetWORD();
for(i = 0; i < segCount; i++) {
startChar[i] = GetWORD();
}
for(i = 0; i < segCount; i++) {
idDelta[i] = GetWORD();
}
for(i = 0; i < segCount; i++) {
filePos[i] = ftell(fh);
idRangeOffset[i] = GetWORD();
}
// 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 < arraylen(useGlyph); i++) {
useGlyph[i] = 0;
}
for(i = 0; i < segCount; i++) {
WORD v = idDelta[i];
if(idRangeOffset[i] == 0) {
int j;
for(j = startChar[i]; j <= endChar[i]; j++) {
if(j > 0 && j < arraylen(useGlyph)) {
// Don't create a reference to a glyph that we won't
// store because it's bigger than the table.
if((WORD)(j + v) < glyphs) {
// Arithmetic is modulo 2^16
useGlyph[j] = (WORD)(j + v);
}
}
}
} else {
int j;
for(j = startChar[i]; j <= endChar[i]; j++) {
if(j > 0 && j < arraylen(useGlyph)) {
int fp = filePos[i];
fp += (j - startChar[i])*sizeof(WORD);
fp += idRangeOffset[i];
fseek(fh, fp, SEEK_SET);
useGlyph[j] = GetWORD();
}
}
}
}
// Load the loca table. This contains the offsets of each glyph,
// relative to the beginning of the glyf table.
fseek(fh, locaAddr, SEEK_SET);
DWORD *glyphOffsets = (DWORD *)AllocTemporary(glyphs*sizeof(DWORD));
for(i = 0; i < glyphs; i++) {
if(headIndexToLocFormat == 1) {
// long offsets, 32 bits
glyphOffsets[i] = GetDWORD();
} else if(headIndexToLocFormat == 0) {
// short offsets, 16 bits but divided by 2
glyphOffsets[i] = GetWORD()*2;
} else {
throw "bad headIndexToLocFormat";
}
}
scale = 1024;
// Load the glyf table. This contains the actual representations of the
// letter forms, as piecewise linear or quadratic outlines.
for(i = 0; i < glyphs; i++) {
fseek(fh, glyfAddr + glyphOffsets[i], SEEK_SET);
LoadGlyph(i);
}
} catch (char *s) {
dbp("failed: '%s'", s);
fclose(fh);
return false;
}
fclose(fh);
loaded = true;
return true;
}
void TtfFont::Flush(void) {
lastWas = NOTHING;
}
void TtfFont::Handle(int *dx, int x, int y, bool onCurve) {
x = ((x + *dx)*scale + 512) >> 10;
y = (y*scale + 512) >> 10;
if(lastWas == ON_CURVE && onCurve) {
// This is a line segment.
LineSegment(lastOnCurve.x, lastOnCurve.y, x, y);
} else if(lastWas == ON_CURVE && !onCurve) {
// We can't do the Bezier until we get the next on-curve point,
// but we must store the off-curve point.
} else if(lastWas == OFF_CURVE && onCurve) {
// We are ready to do a Bezier.
Bezier(lastOnCurve.x, lastOnCurve.y,
lastOffCurve.x, lastOffCurve.y,
x, y);
} else if(lastWas == OFF_CURVE && !onCurve) {
// Two consecutive off-curve points implicitly have an on-point
// curve between them, and that should trigger us to generate a
// Bezier.
IntPoint fake;
fake.x = (x + lastOffCurve.x) / 2;
fake.y = (y + lastOffCurve.y) / 2;
Bezier(lastOnCurve.x, lastOnCurve.y,
lastOffCurve.x, lastOffCurve.y,
fake.x, fake.y);
lastOnCurve.x = fake.x;
lastOnCurve.y = fake.y;
}
if(onCurve) {
lastOnCurve.x = x;
lastOnCurve.y = y;
lastWas = ON_CURVE;
} else {
lastOffCurve.x = x;
lastOffCurve.y = y;
lastWas = OFF_CURVE;
}
}
void TtfFont::PlotCharacter(int *dx, int c, double spacing) {
int gli = useGlyph[c];
if(gli < 0 || gli >= glyphs) return;
Glyph *g = &(glyph[gli]);
if(!g->pt) return;
if(c == ' ') {
*dx += g->advanceWidth;
return;
}
int dx0 = *dx;
// A point that has x = xMin should be plotted at (dx0 + lsb); fix up
// our x-position so that the curve-generating code will put stuff
// at the right place.
*dx = dx0 - g->xMin;
*dx += g->leftSideBearing;
int i;
int firstInContour = 0;
for(i = 0; i < g->pts; i++) {
Handle(dx, g->pt[i].x, g->pt[i].y, g->pt[i].onCurve);
if(g->pt[i].lastInContour) {
int f = firstInContour;
Handle(dx, g->pt[f].x, g->pt[f].y, g->pt[f].onCurve);
firstInContour = i + 1;
Flush();
}
}
// And we're done, so advance our position by the requested advance
// width, plus the user-requested extra advance.
*dx = dx0 + g->advanceWidth + (int)(spacing + 0.5);
}
void TtfFont::PlotString(char *str, double spacing,
SBezierList *sbl,
Vector porigin, Vector pu, Vector pv)
{
beziers = sbl;
u = pu;
v = pv;
origin = porigin;
if(!loaded || !str || *str == '\0') {
LineSegment(0, 0, 1024, 0);
LineSegment(1024, 0, 1024, 1024);
LineSegment(1024, 1024, 0, 1024);
LineSegment(0, 1024, 0, 0);
return;
}
int dx = 0;
while(*str) {
PlotCharacter(&dx, *str, spacing);
str++;
}
}
Vector TtfFont::TransformIntPoint(int x, int y) {
Vector r = origin;
r = r.Plus(u.ScaledBy(x / 1024.0));
r = r.Plus(v.ScaledBy(y / 1024.0));
return r;
}
void TtfFont::LineSegment(int x0, int y0, int x1, int y1) {
SBezier sb = SBezier::From(TransformIntPoint(x0, y0),
TransformIntPoint(x1, y1));
beziers->l.Add(&sb);
}
void TtfFont::Bezier(int x0, int y0, int x1, int y1, int x2, int y2) {
SBezier sb = SBezier::From(TransformIntPoint(x0, y0),
TransformIntPoint(x1, y1),
TransformIntPoint(x2, y2));
beziers->l.Add(&sb);
}