diff --git a/src/cocoa/cocoamain.mm b/src/cocoa/cocoamain.mm index ea2debee..6a905ccf 100644 --- a/src/cocoa/cocoamain.mm +++ b/src/cocoa/cocoamain.mm @@ -1088,20 +1088,20 @@ void SolveSpace::OpenWebsite(const char *url) { [NSURL URLWithString:[NSString stringWithUTF8String:url]]]; } -void SolveSpace::LoadAllFontFiles(void) { +std::vector SolveSpace::GetFontFiles() { + std::vector fonts; + NSArray *fontNames = [[NSFontManager sharedFontManager] availableFonts]; for(NSString *fontName in fontNames) { CTFontDescriptorRef fontRef = CTFontDescriptorCreateWithNameAndSize ((__bridge CFStringRef)fontName, 10.0); CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fontRef, kCTFontURLAttribute); NSString *fontPath = [NSString stringWithString:[(NSURL *)CFBridgingRelease(url) path]]; - if([[fontPath pathExtension] isEqual:@"ttf"]) { - TtfFont tf = {}; - tf.fontFile = [[NSFileManager defaultManager] - fileSystemRepresentationWithPath:fontPath]; - SS.fonts.l.Add(&tf); - } + fonts.push_back([[NSFileManager defaultManager] + fileSystemRepresentationWithPath:fontPath]); } + + return fonts; } /* Application lifecycle */ diff --git a/src/drawentity.cpp b/src/drawentity.cpp index fbbea004..759c0359 100644 --- a/src/drawentity.cpp +++ b/src/drawentity.cpp @@ -447,7 +447,7 @@ void Entity::GenerateBezierCurves(SBezierList *sbl) { Vector v = topLeft.Minus(botLeft); 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; } diff --git a/src/gtk/gtkmain.cpp b/src/gtk/gtkmain.cpp index 738353d0..4ae4786c 100644 --- a/src/gtk/gtkmain.cpp +++ b/src/gtk/gtkmain.cpp @@ -1442,7 +1442,9 @@ void OpenWebsite(const char *url) { } /* fontconfig is already initialized by GTK */ -void LoadAllFontFiles(void) { +std::vector GetFontFiles() { + std::vector fonts; + FcPattern *pat = FcPatternCreate(); FcObjectSet *os = FcObjectSetBuild(FC_FILE, (char *)0); FcFontSet *fs = FcFontList(0, pat, os); @@ -1450,17 +1452,15 @@ void LoadAllFontFiles(void) { for(int i = 0; i < fs->nfont; i++) { FcChar8 *filenameFC = FcPatternFormat(fs->fonts[i], (const FcChar8*) "%{file}"); std::string filename = (char*) filenameFC; - if(FilenameHasExtension(filename, ".ttf")) { - TtfFont tf = {}; - tf.fontFile = filename; - SS.fonts.l.Add(&tf); - } + fonts.push_back(filename); FcStrFree(filenameFC); } FcFontSetDestroy(fs); FcObjectSetDestroy(os); FcPatternDestroy(pat); + + return fonts; } /* Space Navigator support */ diff --git a/src/solvespace.h b/src/solvespace.h index c7e251d8..c942b01a 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -35,6 +35,13 @@ # include #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 // carefully considered, so we disable the -Wfloat-equal warning for them #ifdef __clang__ @@ -218,7 +225,7 @@ bool GetSaveFile(std::string &filename, const std::string &defExtension, const char *selPattern); bool GetOpenFile(std::string &filename, const std::string &defExtension, const char *selPattern); -void LoadAllFontFiles(void); +std::vector GetFontFiles(); void OpenWebsite(const char *url); @@ -446,87 +453,7 @@ public: void Clear(void); }; -class TtfFont { -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 charMap; - std::vector 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 l; - - void LoadAll(void); - - void PlotString(const std::string &font, const char *str, double spacing, - SBezierList *sbl, Vector origin, Vector u, Vector v); -}; +#include "ttf.h" class StepFileWriter { public: @@ -939,7 +866,7 @@ public: GENERATE_REGEN, GENERATE_UNTIL_ACTIVE, }; - + void GenerateAll(GenerateType type, bool andFindFree = false); void GenerateAll(void); void GenerateAll(int first, int last, bool andFindFree = false, bool genForBBox = false); diff --git a/src/ttf.cpp b/src/ttf.cpp index 1f03727e..0a4fb233 100644 --- a/src/ttf.cpp +++ b/src/ttf.cpp @@ -3,228 +3,101 @@ // as entities, since they're always representable as either lines or // quadratic Bezier curves. // -// Copyright 2008-2013 Jonathan Westhues. +// Copyright 2016 whitequark, Peter Barfuss. //----------------------------------------------------------------------------- +#include +#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" //----------------------------------------------------------------------------- // 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) { +TtfFontList::TtfFontList() { + FT_Init_FreeType(&fontLibrary); +} + +TtfFontList::~TtfFontList() { + FT_Done_FreeType(fontLibrary); +} + +void TtfFontList::LoadAll() { 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); + for(const std::string &font : GetFontFiles()) { + TtfFont tf = {}; + tf.fontFile = font; + if(tf.LoadFromFile(fontLibrary)) + l.Add(&tf); } + // 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; } -void TtfFontList::PlotString(const std::string &font, const char *str, double spacing, - SBezierList *sbl, - Vector origin, Vector u, Vector v) +void TtfFontList::PlotString(const std::string &font, const std::string &str, + SBezierList *sbl, Vector origin, Vector u, Vector v) { LoadAll(); - int i; - for(i = 0; i < l.n; i++) { - 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; + TtfFont *tf = std::find_if(&l.elem[0], &l.elem[l.n], + [&](const TtfFont &tf) { return tf.FontFileBaseName() == font; }); + if(!str.empty() && tf != &l.elem[l.n]) { + tf->PlotString(str, sbl, origin, u, v); } 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 // entities that reference us will store it. //----------------------------------------------------------------------------- -std::string TtfFont::FontFileBaseName(void) { +std::string TtfFont::FontFileBaseName() const { std::string baseName = fontFile; size_t pos = baseName.rfind(PATH_SEP); 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 // with which character. //----------------------------------------------------------------------------- -bool TtfFont::LoadFontFromFile(bool nameOnly) { - if(loaded) return true; +bool TtfFont::LoadFromFile(FT_Library fontLibrary) { + FT_Open_Args args = {}; + args.flags = FT_OPEN_PATHNAME; + args.pathname = &fontFile[0]; // FT_String is char* for historical reasons - int i; - - fh = ssfopen(fontFile, "rb"); - if(!fh) { + // 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 + // we only look into C:\Windows\Fonts, which has a known short path. + 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; } - try { - // First, load the Offset Table - uint32_t version = GetULONG(); - uint16_t numTables = GetUSHORT(); - 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); + if(int fterr = FT_Select_Charmap(fontFace, FT_ENCODING_UNICODE)) { + dbp("freetype: loading unicode CMap for file '%s' failed: %s", + fontFile.c_str(), ft_error_string(fterr)); + FT_Done_Face(fontFace); return false; } - fclose(fh); - loaded = true; + name = std::string(fontFace->family_name) + + " (" + std::string(fontFace->style_name) + ")"; return true; } -void TtfFont::Flush(void) { - lastWas = NOTHING; -} +typedef struct OutlineData { + 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) { - 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, 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)); +static Vector Transform(OutlineData *data, FT_Pos x, FT_Pos y) { + Vector r = data->origin; + r = r.Plus(data->u.ScaledBy((float)(data->bx + x) * data->factor)); + r = r.Plus(data->v.ScaledBy((float)y * data->factor)); 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); +static int MoveTo(const FT_Vector *p, void *cc) +{ + OutlineData *data = (OutlineData *) cc; + 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) { - SBezier sb = SBezier::From(TransformIntPoint(x0, y0), - TransformIntPoint(x1, y1), - TransformIntPoint(x2, y2)); - beziers->l.Add(&sb); +static int LineTo(const FT_Vector *p, void *cc) +{ + OutlineData *data = (OutlineData *) cc; + SBezier sb = SBezier::From( + 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; + } +} diff --git a/src/ttf.h b/src/ttf.h new file mode 100644 index 00000000..e7234be0 --- /dev/null +++ b/src/ttf.h @@ -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 l; + + TtfFontList(); + ~TtfFontList(); + + void LoadAll(); + + void PlotString(const std::string &font, const std::string &str, + SBezierList *sbl, Vector origin, Vector u, Vector v); +}; + +#endif diff --git a/src/win32/w32main.cpp b/src/win32/w32main.cpp index 81c81d25..33def55c 100644 --- a/src/win32/w32main.cpp +++ b/src/win32/w32main.cpp @@ -1101,21 +1101,21 @@ DialogChoice SolveSpace::LocateImportedFileYesNoCancel(const std::string &filena } } -void SolveSpace::LoadAllFontFiles(void) -{ +std::vector SolveSpace::GetFontFiles() { + std::vector fonts; + std::wstring fontsDir(MAX_PATH, '\0'); fontsDir.resize(GetWindowsDirectoryW(&fontsDir[0], fontsDir.length())); fontsDir += L"\\fonts\\"; 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) { - TtfFont tf = {}; - tf.fontFile = Narrow(fontsDir) + Narrow(wfd.cFileName); - SS.fonts.l.Add(&tf); - + fonts.push_back(Narrow(fontsDir) + Narrow(wfd.cFileName)); if(!FindNextFileW(h, &wfd)) break; } + + return fonts; } static void MenuById(int id, bool yes, bool check)