Rewrite TTF to Bezier conversion using Freetype.

Benefits:
  * Much simpler code.
  * Handles the entire TTF spec, not just a small subset that
    only really worked well on Windows fonts.
  * Handles all character sets as well as accented characters.
  * Much faster parsing, since Freetype lazily loads and
    caches glyphs.
  * Support for basically every kind of font that was invented,
    not just TTF.

Note that OpenType features, e.g. ligatures, are not yet supported.
This means that Arabic and Devanagari scripts, among others, will
not be rendered in their proper form.

RTL scripts are not supported either, neither in TTF nor in
the text window. Adding RTL support is comparatively easy, but
given that Arabic would not be legibly rendered anyway, this is not
done so far.
pull/4/head
Peter Barfuss 2016-01-29 20:42:44 -05:00 committed by whitequark
parent e5294eef9d
commit 784f3e5548
7 changed files with 295 additions and 755 deletions

View File

@ -1088,20 +1088,20 @@ void SolveSpace::OpenWebsite(const char *url) {
[NSURL URLWithString:[NSString stringWithUTF8String:url]]]; [NSURL URLWithString:[NSString stringWithUTF8String:url]]];
} }
void SolveSpace::LoadAllFontFiles(void) { std::vector<std::string> SolveSpace::GetFontFiles() {
std::vector<std::string> fonts;
NSArray *fontNames = [[NSFontManager sharedFontManager] availableFonts]; NSArray *fontNames = [[NSFontManager sharedFontManager] availableFonts];
for(NSString *fontName in fontNames) { for(NSString *fontName in fontNames) {
CTFontDescriptorRef fontRef = CTFontDescriptorRef fontRef =
CTFontDescriptorCreateWithNameAndSize ((__bridge CFStringRef)fontName, 10.0); CTFontDescriptorCreateWithNameAndSize ((__bridge CFStringRef)fontName, 10.0);
CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fontRef, kCTFontURLAttribute); CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fontRef, kCTFontURLAttribute);
NSString *fontPath = [NSString stringWithString:[(NSURL *)CFBridgingRelease(url) path]]; NSString *fontPath = [NSString stringWithString:[(NSURL *)CFBridgingRelease(url) path]];
if([[fontPath pathExtension] isEqual:@"ttf"]) { fonts.push_back([[NSFileManager defaultManager]
TtfFont tf = {}; fileSystemRepresentationWithPath:fontPath]);
tf.fontFile = [[NSFileManager defaultManager]
fileSystemRepresentationWithPath:fontPath];
SS.fonts.l.Add(&tf);
}
} }
return fonts;
} }
/* Application lifecycle */ /* Application lifecycle */

View File

@ -447,7 +447,7 @@ void Entity::GenerateBezierCurves(SBezierList *sbl) {
Vector v = topLeft.Minus(botLeft); Vector v = topLeft.Minus(botLeft);
Vector u = (v.Cross(n)).WithMagnitude(v.Magnitude()); Vector u = (v.Cross(n)).WithMagnitude(v.Magnitude());
SS.fonts.PlotString(font.c_str(), str.c_str(), 0, sbl, botLeft, u, v); SS.fonts.PlotString(font.c_str(), str.c_str(), sbl, botLeft, u, v);
break; break;
} }

View File

@ -1442,7 +1442,9 @@ void OpenWebsite(const char *url) {
} }
/* fontconfig is already initialized by GTK */ /* fontconfig is already initialized by GTK */
void LoadAllFontFiles(void) { std::vector<std::string> GetFontFiles() {
std::vector<std::string> fonts;
FcPattern *pat = FcPatternCreate(); FcPattern *pat = FcPatternCreate();
FcObjectSet *os = FcObjectSetBuild(FC_FILE, (char *)0); FcObjectSet *os = FcObjectSetBuild(FC_FILE, (char *)0);
FcFontSet *fs = FcFontList(0, pat, os); FcFontSet *fs = FcFontList(0, pat, os);
@ -1450,17 +1452,15 @@ void LoadAllFontFiles(void) {
for(int i = 0; i < fs->nfont; i++) { for(int i = 0; i < fs->nfont; i++) {
FcChar8 *filenameFC = FcPatternFormat(fs->fonts[i], (const FcChar8*) "%{file}"); FcChar8 *filenameFC = FcPatternFormat(fs->fonts[i], (const FcChar8*) "%{file}");
std::string filename = (char*) filenameFC; std::string filename = (char*) filenameFC;
if(FilenameHasExtension(filename, ".ttf")) { fonts.push_back(filename);
TtfFont tf = {};
tf.fontFile = filename;
SS.fonts.l.Add(&tf);
}
FcStrFree(filenameFC); FcStrFree(filenameFC);
} }
FcFontSetDestroy(fs); FcFontSetDestroy(fs);
FcObjectSetDestroy(os); FcObjectSetDestroy(os);
FcPatternDestroy(pat); FcPatternDestroy(pat);
return fonts;
} }
/* Space Navigator support */ /* Space Navigator support */

View File

@ -35,6 +35,13 @@
# include <GL/glu.h> # include <GL/glu.h>
#endif #endif
// We declare these in advance instead of simply using FT_Library
// (defined as typedef FT_LibraryRec_* FT_Library) because including
// freetype.h invokes indescribable horrors and we would like to avoid
// doing that every time we include solvespace.h.
struct FT_LibraryRec_;
struct FT_FaceRec_;
// The few floating-point equality comparisons in SolveSpace have been // The few floating-point equality comparisons in SolveSpace have been
// carefully considered, so we disable the -Wfloat-equal warning for them // carefully considered, so we disable the -Wfloat-equal warning for them
#ifdef __clang__ #ifdef __clang__
@ -218,7 +225,7 @@ bool GetSaveFile(std::string &filename, const std::string &defExtension,
const char *selPattern); const char *selPattern);
bool GetOpenFile(std::string &filename, const std::string &defExtension, bool GetOpenFile(std::string &filename, const std::string &defExtension,
const char *selPattern); const char *selPattern);
void LoadAllFontFiles(void); std::vector<std::string> GetFontFiles();
void OpenWebsite(const char *url); void OpenWebsite(const char *url);
@ -446,87 +453,7 @@ public:
void Clear(void); void Clear(void);
}; };
class TtfFont { #include "ttf.h"
public:
typedef struct {
bool onCurve;
bool lastInContour;
int16_t x;
int16_t y;
} FontPoint;
typedef struct {
FontPoint *pt;
int pts;
int xMax;
int xMin;
int leftSideBearing;
int advanceWidth;
} Glyph;
typedef struct {
int x, y;
} IntPoint;
std::string fontFile;
std::string name;
bool loaded;
// The font itself, plus the mapping from ASCII codes to glyphs
std::vector<uint16_t> charMap;
std::vector<Glyph> glyph;
int maxPoints;
int scale;
// The filehandle, while loading
FILE *fh;
// Some state while rendering a character to curves
enum {
NOTHING = 0,
ON_CURVE = 1,
OFF_CURVE = 2
};
int lastWas;
IntPoint lastOnCurve;
IntPoint lastOffCurve;
// And the state that the caller must specify, determines where we
// render to and how
SBezierList *beziers;
Vector origin, u, v;
int Getc(void);
uint8_t GetBYTE(void);
uint16_t GetUSHORT(void);
uint32_t GetULONG(void);
void LoadGlyph(int index);
bool LoadFontFromFile(bool nameOnly);
std::string FontFileBaseName(void);
void Flush(void);
void Handle(int *dx, int x, int y, bool onCurve);
void PlotCharacter(int *dx, char32_t c, double spacing);
void PlotString(const char *str, double spacing,
SBezierList *sbl, Vector origin, Vector u, Vector v);
Vector TransformIntPoint(int x, int y);
void LineSegment(int x0, int y0, int x1, int y1);
void Bezier(int x0, int y0, int x1, int y1, int x2, int y2);
};
class TtfFontList {
public:
bool loaded;
List<TtfFont> l;
void LoadAll(void);
void PlotString(const std::string &font, const char *str, double spacing,
SBezierList *sbl, Vector origin, Vector u, Vector v);
};
class StepFileWriter { class StepFileWriter {
public: public:
@ -939,7 +866,7 @@ public:
GENERATE_REGEN, GENERATE_REGEN,
GENERATE_UNTIL_ACTIVE, GENERATE_UNTIL_ACTIVE,
}; };
void GenerateAll(GenerateType type, bool andFindFree = false); void GenerateAll(GenerateType type, bool andFindFree = false);
void GenerateAll(void); void GenerateAll(void);
void GenerateAll(int first, int last, bool andFindFree = false, bool genForBBox = false); void GenerateAll(int first, int last, bool andFindFree = false, bool genForBBox = false);

View File

@ -3,228 +3,101 @@
// as entities, since they're always representable as either lines or // as entities, since they're always representable as either lines or
// quadratic Bezier curves. // quadratic Bezier curves.
// //
// Copyright 2008-2013 Jonathan Westhues. // Copyright 2016 whitequark, Peter Barfuss.
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_OUTLINE_H
#include FT_ADVANCES_H
/* Yecch. Irritatingly, you need to do this nonsense to get the error string table,
since nobody thought to put this exact function into FreeType itsself. */
#undef __FTERRORS_H__
#define FT_ERRORDEF(e, v, s) { (e), (s) },
#define FT_ERROR_START_LIST
#define FT_ERROR_END_LIST { 0, NULL }
struct ft_error {
int err;
const char *str;
};
static const struct ft_error ft_errors[] = {
#include FT_ERRORS_H
};
extern "C" const char *ft_error_string(int err) {
const struct ft_error *e;
for(e = ft_errors; e->str; e++)
if(e->err == err)
return e->str;
return "Unknown error";
}
/* Okay, we're done with that. */
#undef FT_ERRORDEF
#undef FT_ERROR_START_LIST
#undef FT_ERROR_END_LIST
#undef HAVE_STDINT_H /* no thanks, we have our own config.h */
#include "solvespace.h" #include "solvespace.h"
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Get the list of available font filenames, and load the name for each of // Get the list of available font filenames, and load the name for each of
// them. Only that, though, not the glyphs too. // them. Only that, though, not the glyphs too.
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void TtfFontList::LoadAll(void) { TtfFontList::TtfFontList() {
FT_Init_FreeType(&fontLibrary);
}
TtfFontList::~TtfFontList() {
FT_Done_FreeType(fontLibrary);
}
void TtfFontList::LoadAll() {
if(loaded) return; if(loaded) return;
// Get the list of font files from the platform-specific code. for(const std::string &font : GetFontFiles()) {
LoadAllFontFiles(); TtfFont tf = {};
tf.fontFile = font;
int i; if(tf.LoadFromFile(fontLibrary))
for(i = 0; i < l.n; i++) { l.Add(&tf);
TtfFont *tf = &(l.elem[i]);
tf->LoadFontFromFile(true);
} }
// Sort fonts according to their actual name, not filename.
std::sort(&l.elem[0], &l.elem[l.n],
[](const TtfFont &a, const TtfFont &b) { return a.name < b.name; });
// Filter out fonts with the same family and style name. This is not
// strictly necessarily the exact same font, but it will almost always be.
TtfFont *it = std::unique(&l.elem[0], &l.elem[l.n],
[](const TtfFont &a, const TtfFont &b) { return a.name == b.name; });
l.RemoveLast(&l.elem[l.n] - it);
// TODO: identify fonts by their name and not filename, which may change
// between OSes.
loaded = true; loaded = true;
} }
void TtfFontList::PlotString(const std::string &font, const char *str, double spacing, void TtfFontList::PlotString(const std::string &font, const std::string &str,
SBezierList *sbl, SBezierList *sbl, Vector origin, Vector u, Vector v)
Vector origin, Vector u, Vector v)
{ {
LoadAll(); LoadAll();
int i; TtfFont *tf = std::find_if(&l.elem[0], &l.elem[l.n],
for(i = 0; i < l.n; i++) { [&](const TtfFont &tf) { return tf.FontFileBaseName() == font; });
TtfFont *tf = &(l.elem[i]);
if(tf->FontFileBaseName() == font) {
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.
// The BYTE, USHORT and ULONG nomenclature comes from the OpenType spec.
//-----------------------------------------------------------------------------
uint8_t TtfFont::GetBYTE(void) {
return (uint8_t)Getc();
}
uint16_t TtfFont::GetUSHORT(void) {
uint8_t b0, b1;
b1 = (uint8_t)Getc();
b0 = (uint8_t)Getc();
return (uint16_t)(b1 << 8) | b0;
}
uint32_t TtfFont::GetULONG(void) {
uint8_t b0, b1, b2, b3;
b3 = (uint8_t)Getc();
b2 = (uint8_t)Getc();
b1 = (uint8_t)Getc();
b0 = (uint8_t)Getc();
return
(uint32_t)(b3 << 24) |
(uint32_t)(b2 << 16) |
(uint32_t)(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 >= glyph.size()) return;
int i;
int16_t contours = (int16_t)GetUSHORT();
int16_t xMin = (int16_t)GetUSHORT();
int16_t yMin = (int16_t)GetUSHORT();
int16_t xMax = (int16_t)GetUSHORT();
int16_t yMax = (int16_t)GetUSHORT();
if(charMap.size() > 'A' && charMap[(int)'A'] == index) {
if(yMax > 0) {
scale = (1024*1024) / yMax;
} else {
scale = 1;
}
}
if(contours > 0) {
uint16_t *endPointsOfContours =
(uint16_t *)AllocTemporary(contours*sizeof(uint16_t));
for(i = 0; i < contours; i++) {
endPointsOfContours[i] = GetUSHORT();
}
uint16_t totalPts = endPointsOfContours[i-1] + 1;
uint16_t instructionLength = GetUSHORT();
for(i = 0; i < instructionLength; i++) {
// We can ignore the instructions, since we're doing vector
// output.
(void)GetBYTE();
}
uint8_t *flags = (uint8_t *)AllocTemporary(totalPts*sizeof(uint8_t));
int16_t *x = (int16_t *)AllocTemporary(totalPts*sizeof(int16_t));
int16_t *y = (int16_t *)AllocTemporary(totalPts*sizeof(int16_t));
// 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();
uint8_t 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
int16_t xa = 0;
for(i = 0; i < totalPts; i++) {
if(flags[i] & FLAG_DX_IS_BYTE) {
uint8_t 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 {
int16_t d = (int16_t)GetUSHORT();
xa += d;
}
}
x[i] = xa;
}
// y coordinates
int16_t ya = 0;
for(i = 0; i < totalPts; i++) {
if(flags[i] & FLAG_DY_IS_BYTE) {
uint8_t 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 {
int16_t d = (int16_t)GetUSHORT();
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 = (uint8_t)(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;
if(!str.empty() && tf != &l.elem[l.n]) {
tf->PlotString(str, sbl, origin, u, v);
} else { } else {
// This is a composite glyph, TODO. // No text or no 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);
} }
} }
@ -232,7 +105,7 @@ void TtfFont::LoadGlyph(int index) {
// Return the basename of our font filename; that's how the requests and // Return the basename of our font filename; that's how the requests and
// entities that reference us will store it. // entities that reference us will store it.
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
std::string TtfFont::FontFileBaseName(void) { std::string TtfFont::FontFileBaseName() const {
std::string baseName = fontFile; std::string baseName = fontFile;
size_t pos = baseName.rfind(PATH_SEP); size_t pos = baseName.rfind(PATH_SEP);
if(pos != std::string::npos) if(pos != std::string::npos)
@ -245,468 +118,168 @@ std::string TtfFont::FontFileBaseName(void) {
// the letter shapes, and about the mappings that determine which glyph goes // the letter shapes, and about the mappings that determine which glyph goes
// with which character. // with which character.
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
bool TtfFont::LoadFontFromFile(bool nameOnly) { bool TtfFont::LoadFromFile(FT_Library fontLibrary) {
if(loaded) return true; FT_Open_Args args = {};
args.flags = FT_OPEN_PATHNAME;
args.pathname = &fontFile[0]; // FT_String is char* for historical reasons
int i; // We don't use ssfopen() here to let freetype do its own memory management.
// This is OK because on Linux/OS X we just delegate to fopen and on Windows
fh = ssfopen(fontFile, "rb"); // we only look into C:\Windows\Fonts, which has a known short path.
if(!fh) { if(int fterr = FT_Open_Face(fontLibrary, &args, 0, &fontFace)) {
dbp("freetype: loading font from file '%s' failed: %s",
fontFile.c_str(), ft_error_string(fterr));
return false; return false;
} }
try { if(int fterr = FT_Select_Charmap(fontFace, FT_ENCODING_UNICODE)) {
// First, load the Offset Table dbp("freetype: loading unicode CMap for file '%s' failed: %s",
uint32_t version = GetULONG(); fontFile.c_str(), ft_error_string(fterr));
uint16_t numTables = GetUSHORT(); FT_Done_Face(fontFace);
uint16_t searchRange = GetUSHORT();
uint16_t entrySelector = GetUSHORT();
uint16_t rangeShift = GetUSHORT();
// Now load the Table Directory; our goal in doing this will be to
// find the addresses of the tables that we will need.
uint32_t glyfAddr = (uint32_t)-1, glyfLen;
uint32_t cmapAddr = (uint32_t)-1, cmapLen;
uint32_t headAddr = (uint32_t)-1, headLen;
uint32_t locaAddr = (uint32_t)-1, locaLen;
uint32_t maxpAddr = (uint32_t)-1, maxpLen;
uint32_t nameAddr = (uint32_t)-1, nameLen;
uint32_t hmtxAddr = (uint32_t)-1, hmtxLen;
uint32_t hheaAddr = (uint32_t)-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();
uint32_t checksum = GetULONG();
uint32_t offset = GetULONG();
uint32_t length = GetULONG();
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 == (uint32_t)-1 ||
cmapAddr == (uint32_t)-1 ||
headAddr == (uint32_t)-1 ||
locaAddr == (uint32_t)-1 ||
maxpAddr == (uint32_t)-1 ||
hmtxAddr == (uint32_t)-1 ||
nameAddr == (uint32_t)-1 ||
hheaAddr == (uint32_t)-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);
uint16_t nameFormat = GetUSHORT();
uint16_t nameCount = GetUSHORT();
uint16_t nameStringOffset = GetUSHORT();
// 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++) {
uint16_t platformID = GetUSHORT();
uint16_t encodingID = GetUSHORT();
uint16_t languageID = GetUSHORT();
uint16_t nameId = GetUSHORT();
uint16_t length = GetUSHORT();
uint16_t offset = GetUSHORT();
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);
name.clear();
for(i = 0; i < displayNameLength; i++) {
char b = (char)GetBYTE();
if(b) name += b;
}
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);
uint32_t headVersion = GetULONG();
uint32_t headFontRevision = GetULONG();
uint32_t headCheckSumAdj = GetULONG();
uint32_t headMagicNumber = GetULONG();
uint16_t headFlags = GetUSHORT();
uint16_t headUnitsPerEm = GetUSHORT();
(void)GetULONG(); // created time
(void)GetULONG();
(void)GetULONG(); // modified time
(void)GetULONG();
uint16_t headXmin = GetUSHORT();
uint16_t headYmin = GetUSHORT();
uint16_t headXmax = GetUSHORT();
uint16_t headYmax = GetUSHORT();
uint16_t headMacStyle = GetUSHORT();
uint16_t headLowestRecPPEM = GetUSHORT();
uint16_t headFontDirectionHint = GetUSHORT();
uint16_t headIndexToLocFormat = GetUSHORT();
uint16_t headGlyphDataFormat = GetUSHORT();
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);
uint32_t hheaVersion = GetULONG();
uint16_t hheaAscender = GetUSHORT();
uint16_t hheaDescender = GetUSHORT();
uint16_t hheaLineGap = GetUSHORT();
uint16_t hheaAdvanceWidthMax = GetUSHORT();
uint16_t hheaMinLsb = GetUSHORT();
uint16_t hheaMinRsb = GetUSHORT();
uint16_t hheaXMaxExtent = GetUSHORT();
uint16_t hheaCaretSlopeRise = GetUSHORT();
uint16_t hheaCaretSlopeRun = GetUSHORT();
uint16_t hheaCaretOffset = GetUSHORT();
(void)GetUSHORT();
(void)GetUSHORT();
(void)GetUSHORT();
(void)GetUSHORT();
uint16_t hheaMetricDataFormat = GetUSHORT();
uint16_t hheaNumberOfMetrics = GetUSHORT();
// Load the maxp table, which determines (among other things) the number
// of glyphs in the font
fseek(fh, maxpAddr, SEEK_SET);
uint32_t maxpVersion = GetULONG();
uint16_t maxpNumGlyphs = GetUSHORT();
uint16_t maxpMaxPoints = GetUSHORT();
uint16_t maxpMaxContours = GetUSHORT();
uint16_t maxpMaxComponentPoints = GetUSHORT();
uint16_t maxpMaxComponentContours = GetUSHORT();
uint16_t maxpMaxZones = GetUSHORT();
uint16_t maxpMaxTwilightPoints = GetUSHORT();
uint16_t maxpMaxStorage = GetUSHORT();
uint16_t maxpMaxFunctionDefs = GetUSHORT();
uint16_t maxpMaxInstructionDefs = GetUSHORT();
uint16_t maxpMaxStackElements = GetUSHORT();
uint16_t maxpMaxSizeOfInstructions = GetUSHORT();
uint16_t maxpMaxComponentElements = GetUSHORT();
uint16_t maxpMaxComponentDepth = GetUSHORT();
glyph.resize(maxpNumGlyphs);
// Load the hmtx table, which gives the horizontal metrics (spacing
// and advance width) of the font.
fseek(fh, hmtxAddr, SEEK_SET);
uint16_t hmtxAdvanceWidth = 0;
int16_t hmtxLsb = 0;
for(i = 0; i < min(glyph.size(), (size_t)hheaNumberOfMetrics); i++) {
hmtxAdvanceWidth = GetUSHORT();
hmtxLsb = (int16_t)GetUSHORT();
glyph[i].leftSideBearing = hmtxLsb;
glyph[i].advanceWidth = hmtxAdvanceWidth;
}
// The last entry in the table applies to all subsequent glyphs also.
for(; i < glyph.size(); 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);
uint32_t usedTableAddr = (uint32_t)-1;
uint16_t cmapVersion = GetUSHORT();
uint16_t cmapTableCount = GetUSHORT();
for(i = 0; i < cmapTableCount; i++) {
uint16_t platformId = GetUSHORT();
uint16_t encodingId = GetUSHORT();
uint32_t offset = GetULONG();
if(platformId == 3 && encodingId == 1) {
// The Windows Unicode mapping is our preference
usedTableAddr = cmapAddr + offset;
}
}
if(usedTableAddr == (uint32_t)-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);
uint16_t mapFormat = GetUSHORT();
uint16_t mapLength = GetUSHORT();
uint16_t mapVersion = GetUSHORT();
uint16_t mapSegCountX2 = GetUSHORT();
uint16_t mapSearchRange = GetUSHORT();
uint16_t mapEntrySelector = GetUSHORT();
uint16_t mapRangeShift = GetUSHORT();
if(mapFormat != 4) {
// Required to use format 4 per spec
throw "not format 4";
}
int segCount = mapSegCountX2 / 2;
uint16_t *endChar = (uint16_t *)AllocTemporary(segCount*sizeof(uint16_t));
uint16_t *startChar = (uint16_t *)AllocTemporary(segCount*sizeof(uint16_t));
uint16_t *idDelta = (uint16_t *)AllocTemporary(segCount*sizeof(uint16_t));
uint16_t *idRangeOffset = (uint16_t *)AllocTemporary(segCount*sizeof(uint16_t));
uint32_t *filePos = (uint32_t *)AllocTemporary(segCount*sizeof(uint32_t));
for(i = 0; i < segCount; i++) {
endChar[i] = GetUSHORT();
}
uint16_t mapReservedPad = GetUSHORT();
for(i = 0; i < segCount; i++) {
startChar[i] = GetUSHORT();
}
for(i = 0; i < segCount; i++) {
idDelta[i] = GetUSHORT();
}
for(i = 0; i < segCount; i++) {
filePos[i] = (uint32_t)ftell(fh);
idRangeOffset[i] = GetUSHORT();
}
for(i = 0; i < segCount; i++) {
if(charMap.size() < endChar[i] + 1)
charMap.resize(endChar[i] + 1);
uint16_t v = idDelta[i];
if(idRangeOffset[i] == 0) {
for(int j = startChar[i]; j <= endChar[i]; j++) {
// Arithmetic is modulo 2^16
charMap[j] = (uint16_t)(j + v);
}
} else {
for(int j = startChar[i]; j <= endChar[i]; j++) {
int fp = filePos[i];
fp += (j - startChar[i])*sizeof(uint16_t);
fp += idRangeOffset[i];
fseek(fh, fp, SEEK_SET);
charMap[j] = GetUSHORT();
}
}
}
// Load the loca table. This contains the offsets of each glyph,
// relative to the beginning of the glyf table.
fseek(fh, locaAddr, SEEK_SET);
uint32_t *glyphOffsets = (uint32_t *)AllocTemporary(glyph.size()*sizeof(uint32_t));
for(i = 0; i < glyph.size(); i++) {
if(headIndexToLocFormat == 1) {
// long offsets, 32 bits
glyphOffsets[i] = GetULONG();
} else if(headIndexToLocFormat == 0) {
// short offsets, 16 bits but divided by 2
glyphOffsets[i] = GetUSHORT()*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 < glyph.size(); i++) {
fseek(fh, glyfAddr + glyphOffsets[i], SEEK_SET);
LoadGlyph(i);
}
} catch (const char *s) {
dbp("ttf: file %s failed: '%s'", fontFile.c_str(), s);
fclose(fh);
return false; return false;
} }
fclose(fh); name = std::string(fontFace->family_name) +
loaded = true; " (" + std::string(fontFace->style_name) + ")";
return true; return true;
} }
void TtfFont::Flush(void) { typedef struct OutlineData {
lastWas = NOTHING; Vector origin, u, v; // input parameters
} SBezierList *beziers; // output bezier list
float factor; // ratio between freetype and solvespace coordinates
FT_Pos bx; // x offset of the current glyph
FT_Pos px, py; // current point
} OutlineData;
void TtfFont::Handle(int *dx, int x, int y, bool onCurve) { static Vector Transform(OutlineData *data, FT_Pos x, FT_Pos y) {
x = ((x + *dx)*scale + 512) >> 10; Vector r = data->origin;
y = (y*scale + 512) >> 10; r = r.Plus(data->u.ScaledBy((float)(data->bx + x) * data->factor));
r = r.Plus(data->v.ScaledBy((float)y * data->factor));
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, char32_t c, double spacing) {
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;
}
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(const 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) {
char32_t chr;
str = ReadUTF8(str, &chr);
PlotCharacter(&dx, chr, spacing);
}
}
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; return r;
} }
void TtfFont::LineSegment(int x0, int y0, int x1, int y1) { static int MoveTo(const FT_Vector *p, void *cc)
SBezier sb = SBezier::From(TransformIntPoint(x0, y0), {
TransformIntPoint(x1, y1)); OutlineData *data = (OutlineData *) cc;
beziers->l.Add(&sb); data->px = p->x;
data->py = p->y;
return 0;
} }
void TtfFont::Bezier(int x0, int y0, int x1, int y1, int x2, int y2) { static int LineTo(const FT_Vector *p, void *cc)
SBezier sb = SBezier::From(TransformIntPoint(x0, y0), {
TransformIntPoint(x1, y1), OutlineData *data = (OutlineData *) cc;
TransformIntPoint(x2, y2)); SBezier sb = SBezier::From(
beziers->l.Add(&sb); Transform(data, data->px, data->py),
Transform(data, p->x, p->y));
data->beziers->l.Add(&sb);
data->px = p->x;
data->py = p->y;
return 0;
} }
static int ConicTo(const FT_Vector *c, const FT_Vector *p, void *cc)
{
OutlineData *data = (OutlineData *) cc;
SBezier sb = SBezier::From(
Transform(data, data->px, data->py),
Transform(data, c->x, c->y),
Transform(data, p->x, p->y));
data->beziers->l.Add(&sb);
data->px = p->x;
data->py = p->y;
return 0;
}
static int CubicTo(const FT_Vector *c1, const FT_Vector *c2, const FT_Vector *p, void *cc)
{
OutlineData *data = (OutlineData *) cc;
SBezier sb = SBezier::From(
Transform(data, data->px, data->py),
Transform(data, c1->x, c1->y),
Transform(data, c2->x, c2->y),
Transform(data, p->x, p->y));
data->beziers->l.Add(&sb);
data->px = p->x;
data->py = p->y;
return 0;
}
static const FT_Outline_Funcs outline_funcs = {
MoveTo, LineTo, ConicTo, CubicTo, 0, 0
};
void TtfFont::PlotString(const std::string &str,
SBezierList *sbl, Vector origin, Vector u, Vector v)
{
const char *cstr = str.c_str();
FT_Pos dx = 0;
while(*cstr) {
char32_t chr;
cstr = ReadUTF8(cstr, &chr);
uint32_t gid = FT_Get_Char_Index(fontFace, chr);
if (gid == 0) {
dbp("freetype: CID-to-GID mapping for CID 0x%04x failed: %s; using CID as GID",
chr, ft_error_string(gid));
}
FT_F26Dot6 scale = fontFace->units_per_EM;
if(int fterr = FT_Set_Char_Size(fontFace, scale, scale, 72, 72)) {
dbp("freetype: cannot set character size: %s",
ft_error_string(fterr));
return;
}
/*
* Stupid hacks:
* - if we want fake-bold, use FT_Outline_Embolden(). This actually looks
* quite good.
* - if we want fake-italic, apply a shear transform [1 s s 1 0 0] here using
* FT_Set_Transform. This looks decent at small font sizes and bad at larger
* ones, antialiasing mitigates this considerably though.
*/
if(int fterr = FT_Load_Glyph(fontFace, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING)) {
dbp("freetype: cannot load glyph (gid %d): %s",
gid, ft_error_string(fterr));
return;
}
/* 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.
*
* There's no point in getting the glyph BBox here - not only can it be
* needlessly slow sometimes, but because we're about to render a single glyph,
* what we want actually *is* the CBox.
*
* This is notwithstanding that this makes extremely little sense, this
* looks like a workaround for either mishandling the start glyph on a line,
* or as a really hacky pseudo-track-kerning (in which case it works better than
* one would expect! especially since most fonts don't set track kerning).
*/
FT_BBox cbox;
FT_Outline_Get_CBox(&fontFace->glyph->outline, &cbox);
FT_Pos bx = dx - cbox.xMin;
// Yes, this is what FreeType calls left-side bearing.
// Then interchangeably uses that with "left-side bearing". Sigh.
bx += fontFace->glyph->metrics.horiBearingX;
OutlineData data = {};
data.origin = origin;
data.u = u;
data.v = v;
data.beziers = sbl;
data.factor = 1.0f/(float)scale;
data.bx = bx;
if(int fterr = FT_Outline_Decompose(&fontFace->glyph->outline, &outline_funcs, &data)) {
dbp("freetype: bezier decomposition failed (gid %d): %s",
gid, ft_error_string(fterr));
}
// And we're done, so advance our position by the requested advance
// width, plus the user-requested extra advance.
dx += fontFace->glyph->advance.x;
}
}

40
src/ttf.h Normal file
View File

@ -0,0 +1,40 @@
//-----------------------------------------------------------------------------
// 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 2016 whitequark, Peter Barfuss.
//-----------------------------------------------------------------------------
#ifndef __TTF_H
#define __TTF_H
class TtfFont {
public:
std::string fontFile;
std::string name;
FT_FaceRec_ *fontFace;
std::string FontFileBaseName() const;
bool LoadFromFile(FT_LibraryRec_ *fontLibrary);
void PlotString(const std::string &str,
SBezierList *sbl, Vector origin, Vector u, Vector v);
};
class TtfFontList {
public:
FT_LibraryRec_ *fontLibrary;
bool loaded;
List<TtfFont> l;
TtfFontList();
~TtfFontList();
void LoadAll();
void PlotString(const std::string &font, const std::string &str,
SBezierList *sbl, Vector origin, Vector u, Vector v);
};
#endif

View File

@ -1101,21 +1101,21 @@ DialogChoice SolveSpace::LocateImportedFileYesNoCancel(const std::string &filena
} }
} }
void SolveSpace::LoadAllFontFiles(void) std::vector<std::string> SolveSpace::GetFontFiles() {
{ std::vector<std::string> fonts;
std::wstring fontsDir(MAX_PATH, '\0'); std::wstring fontsDir(MAX_PATH, '\0');
fontsDir.resize(GetWindowsDirectoryW(&fontsDir[0], fontsDir.length())); fontsDir.resize(GetWindowsDirectoryW(&fontsDir[0], fontsDir.length()));
fontsDir += L"\\fonts\\"; fontsDir += L"\\fonts\\";
WIN32_FIND_DATA wfd; WIN32_FIND_DATA wfd;
HANDLE h = FindFirstFileW((fontsDir + L"*.ttf").c_str(), &wfd); HANDLE h = FindFirstFileW((fontsDir + L"*").c_str(), &wfd);
while(h != INVALID_HANDLE_VALUE) { while(h != INVALID_HANDLE_VALUE) {
TtfFont tf = {}; fonts.push_back(Narrow(fontsDir) + Narrow(wfd.cFileName));
tf.fontFile = Narrow(fontsDir) + Narrow(wfd.cFileName);
SS.fonts.l.Add(&tf);
if(!FindNextFileW(h, &wfd)) break; if(!FindNextFileW(h, &wfd)) break;
} }
return fonts;
} }
static void MenuById(int id, bool yes, bool check) static void MenuById(int id, bool yes, bool check)