711 lines
24 KiB
C++
711 lines
24 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 == EOF) {
|
|
throw "EOF";
|
|
}
|
|
return c;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Helpers to get 1, 2, or 4 bytes from the .ttf file. Big endian.
|
|
//-----------------------------------------------------------------------------
|
|
BYTE TtfFont::GetBYTE(void) {
|
|
return (BYTE)Getc();
|
|
}
|
|
WORD TtfFont::GetWORD(void) {
|
|
BYTE b0, b1;
|
|
b1 = (BYTE)Getc();
|
|
b0 = (BYTE)Getc();
|
|
|
|
return (b1 << 8) | b0;
|
|
}
|
|
DWORD TtfFont::GetDWORD(void) {
|
|
BYTE b0, b1, b2, b3;
|
|
b3 = (BYTE)Getc();
|
|
b2 = (BYTE)Getc();
|
|
b1 = (BYTE)Getc();
|
|
b0 = (BYTE)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 = (SWORD)GetWORD();
|
|
SWORD xMin = (SWORD)GetWORD();
|
|
SWORD yMin = (SWORD)GetWORD();
|
|
SWORD xMax = (SWORD)GetWORD();
|
|
SWORD yMax = (SWORD)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 = (SWORD)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 = (SWORD)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.
|
|
//-----------------------------------------------------------------------------
|
|
const 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 = (DWORD)-1, glyfLen;
|
|
DWORD cmapAddr = (DWORD)-1, cmapLen;
|
|
DWORD headAddr = (DWORD)-1, headLen;
|
|
DWORD locaAddr = (DWORD)-1, locaLen;
|
|
DWORD maxpAddr = (DWORD)-1, maxpLen;
|
|
DWORD nameAddr = (DWORD)-1, nameLen;
|
|
DWORD hmtxAddr = (DWORD)-1, hmtxLen;
|
|
DWORD hheaAddr = (DWORD)-1, hheaLen;
|
|
|
|
for(i = 0; i < numTables; i++) {
|
|
char tag[5] = "xxxx";
|
|
tag[0] = (char)GetBYTE();
|
|
tag[1] = (char)GetBYTE();
|
|
tag[2] = (char)GetBYTE();
|
|
tag[3] = (char)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 == (DWORD)-1 || cmapAddr == (DWORD)-1 || headAddr == (DWORD)-1 ||
|
|
locaAddr == (DWORD)-1 || maxpAddr == (DWORD)-1 || hmtxAddr == (DWORD)-1 ||
|
|
nameAddr == (DWORD)-1 || hheaAddr == (DWORD)-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 = 0, displayNameLength = 0;
|
|
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 < ((int)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 = 0;
|
|
SWORD hmtxLsb = 0;
|
|
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 = (DWORD)-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 == (DWORD)-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] = (DWORD)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 < (int)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 < (int)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 < (int)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);
|
|
}
|
|
|