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
parent
e5294eef9d
commit
784f3e5548
|
@ -1088,20 +1088,20 @@ void SolveSpace::OpenWebsite(const char *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];
|
||||
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 */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1442,7 +1442,9 @@ void OpenWebsite(const char *url) {
|
|||
}
|
||||
|
||||
/* fontconfig is already initialized by GTK */
|
||||
void LoadAllFontFiles(void) {
|
||||
std::vector<std::string> GetFontFiles() {
|
||||
std::vector<std::string> 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 */
|
||||
|
|
|
@ -35,6 +35,13 @@
|
|||
# include <GL/glu.h>
|
||||
#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<std::string> 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<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);
|
||||
};
|
||||
#include "ttf.h"
|
||||
|
||||
class StepFileWriter {
|
||||
public:
|
||||
|
|
845
src/ttf.cpp
845
src/ttf.cpp
|
@ -3,236 +3,109 @@
|
|||
// 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 <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"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
TtfFont *tf = std::find_if(&l.elem[0], &l.elem[l.n],
|
||||
[&](const TtfFont &tf) { return tf.FontFileBaseName() == font; });
|
||||
|
||||
// Couldn't find the font; so draw a big X for an error marker.
|
||||
if(!str.empty() && tf != &l.elem[l.n]) {
|
||||
tf->PlotString(str, sbl, origin, u, v);
|
||||
} else {
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
||||
//=============================================================================
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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;
|
||||
|
||||
} 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
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(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;
|
||||
}
|
||||
|
||||
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)
|
||||
name = std::string(fontFace->family_name) +
|
||||
" (" + std::string(fontFace->style_name) + ")";
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static int MoveTo(const FT_Vector *p, void *cc)
|
||||
{
|
||||
throw "missing table addr";
|
||||
OutlineData *data = (OutlineData *) cc;
|
||||
data->px = p->x;
|
||||
data->py = p->y;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 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";
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
fclose(fh);
|
||||
return true;
|
||||
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
|
||||
};
|
||||
|
||||
// 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);
|
||||
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 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";
|
||||
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));
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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, 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;
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
/*
|
||||
* 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 = 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);
|
||||
dx += fontFace->glyph->advance.x;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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');
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue