solvespace/ttf.cpp
Jonathan Westhues f904c0fbee Entities now generate rational polynomial curves instead of
piecwise linear segments. These are piecewise linear approximated
for display, and currently for the mesh too, but that's the first
step to replace the mesh with exact curved surfaces.

[git-p4: depot-paths = "//depot/solvespace/": change = 1895]
2009-01-14 19:55:42 -08:00

704 lines
23 KiB
C++

#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,
SPolyCurveList *spcl,
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, spcl, origin, u, v);
return;
}
}
// Couldn't find the font; so draw a big X for an error marker.
SPolyCurve spc;
spc = SPolyCurve::From(origin, origin.Plus(u).Plus(v));
spcl->l.Add(&spc);
spc = SPolyCurve::From(origin.Plus(v), origin.Plus(u));
spcl->l.Add(&spc);
}
//=============================================================================
//-----------------------------------------------------------------------------
// 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,
SPolyCurveList *spcl,
Vector porigin, Vector pu, Vector pv)
{
polyCurves = spcl;
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) {
SPolyCurve spc = SPolyCurve::From(TransformIntPoint(x0, y0),
TransformIntPoint(x1, y1));
polyCurves->l.Add(&spc);
}
void TtfFont::Bezier(int x0, int y0, int x1, int y1, int x2, int y2) {
SPolyCurve spc = SPolyCurve::From(TransformIntPoint(x0, y0),
TransformIntPoint(x1, y1),
TransformIntPoint(x2, y2));
polyCurves->l.Add(&spc);
}