Move vector font to res/fonts/; remove lff2c.
This commit integrates the vector font in the resource system, so that cross-compilation would be easier and a custom font could be used without recompilation. The font handling code was carefully written to lazily load glyphs; as possible; in practice this means that startup is less than 15ms slower after this commit, most of it spent in inflate(). This also reduces executable size and makes compilation of glhelper.cpp much faster.pull/10/head
parent
645c2d90ac
commit
fc79642788
|
@ -183,7 +183,6 @@ endif()
|
||||||
|
|
||||||
# components
|
# components
|
||||||
|
|
||||||
add_subdirectory(tools)
|
|
||||||
add_subdirectory(res)
|
add_subdirectory(res)
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
add_subdirectory(exposed)
|
add_subdirectory(exposed)
|
||||||
|
|
|
@ -176,7 +176,8 @@ add_resources(
|
||||||
fonts/private/4-stipple-dot.png
|
fonts/private/4-stipple-dot.png
|
||||||
fonts/private/5-stipple-dash-long.png
|
fonts/private/5-stipple-dash-long.png
|
||||||
fonts/private/6-stipple-dash.png
|
fonts/private/6-stipple-dash.png
|
||||||
fonts/private/7-stipple-zigzag.png)
|
fonts/private/7-stipple-zigzag.png
|
||||||
|
fonts/unicode.lff.gz)
|
||||||
|
|
||||||
# Third, distribute the resources.
|
# Third, distribute the resources.
|
||||||
add_custom_target(resources
|
add_custom_target(resources
|
||||||
|
|
Binary file not shown.
|
@ -75,27 +75,6 @@ if(NOT WIN32)
|
||||||
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# generated files
|
|
||||||
|
|
||||||
# `$<TARGET_FILE:tool>` allows us to use binfmt support on Linux
|
|
||||||
# without special-casing anything; running `tool.exe` would succeed
|
|
||||||
# but unlike Windows, Linux does not have the machinery to map
|
|
||||||
# an invocation of `tool` to an executable `tool.exe` in $PATH.
|
|
||||||
|
|
||||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/generated)
|
|
||||||
|
|
||||||
add_custom_command(
|
|
||||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated/vectorfont.table.h
|
|
||||||
COMMAND $<TARGET_FILE:lff2c>
|
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/generated/vectorfont.table.h
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/fonts/unicode.lff.gz
|
|
||||||
DEPENDS lff2c
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/fonts/unicode.lff.gz
|
|
||||||
VERBATIM)
|
|
||||||
|
|
||||||
set(generated_HEADERS
|
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/generated/vectorfont.table.h)
|
|
||||||
|
|
||||||
# platform dependencies
|
# platform dependencies
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
|
@ -212,7 +191,6 @@ add_executable(solvespace WIN32 MACOSX_BUNDLE
|
||||||
${libslvs_SOURCES}
|
${libslvs_SOURCES}
|
||||||
${util_SOURCES}
|
${util_SOURCES}
|
||||||
${platform_SOURCES}
|
${platform_SOURCES}
|
||||||
${generated_HEADERS}
|
|
||||||
${solvespace_HEADERS}
|
${solvespace_HEADERS}
|
||||||
${solvespace_SOURCES}
|
${solvespace_SOURCES}
|
||||||
$<TARGET_PROPERTY:resources,EXTRA_SOURCES>)
|
$<TARGET_PROPERTY:resources,EXTRA_SOURCES>)
|
||||||
|
|
|
@ -126,6 +126,7 @@ public:
|
||||||
double x, y;
|
double x, y;
|
||||||
|
|
||||||
static Point2d From(double x, double y);
|
static Point2d From(double x, double y);
|
||||||
|
static Point2d FromPolar(double r, double a);
|
||||||
|
|
||||||
Point2d Plus(const Point2d &b) const;
|
Point2d Plus(const Point2d &b) const;
|
||||||
Point2d Minus(const Point2d &b) const;
|
Point2d Minus(const Point2d &b) const;
|
||||||
|
@ -134,6 +135,8 @@ public:
|
||||||
double Dot(Point2d p) const;
|
double Dot(Point2d p) const;
|
||||||
double DistanceTo(const Point2d &p) const;
|
double DistanceTo(const Point2d &p) const;
|
||||||
double DistanceToLine(const Point2d &p0, const Point2d &dp, bool segment) const;
|
double DistanceToLine(const Point2d &p0, const Point2d &dp, bool segment) const;
|
||||||
|
double Angle() const;
|
||||||
|
double AngleTo(const Point2d &p) const;
|
||||||
double Magnitude(void) const;
|
double Magnitude(void) const;
|
||||||
double MagSquared(void) const;
|
double MagSquared(void) const;
|
||||||
Point2d WithMagnitude(double v) const;
|
Point2d WithMagnitude(double v) const;
|
||||||
|
|
Binary file not shown.
292
src/glhelper.cpp
292
src/glhelper.cpp
|
@ -7,74 +7,9 @@
|
||||||
|
|
||||||
namespace SolveSpace {
|
namespace SolveSpace {
|
||||||
|
|
||||||
// A vector font.
|
|
||||||
#include "generated/vectorfont.table.h"
|
|
||||||
|
|
||||||
static bool ColorLocked;
|
static bool ColorLocked;
|
||||||
static bool DepthOffsetLocked;
|
static bool DepthOffsetLocked;
|
||||||
|
|
||||||
static const VectorGlyph &GetVectorGlyph(char32_t chr) {
|
|
||||||
int first = 0;
|
|
||||||
int last = sizeof(VectorFont) / sizeof(VectorGlyph);
|
|
||||||
while(first <= last) {
|
|
||||||
int mid = (first + last) / 2;
|
|
||||||
char32_t midChr = VectorFont[mid].character;
|
|
||||||
if(midChr > chr) {
|
|
||||||
last = mid - 1; // and first stays the same
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(midChr < chr) {
|
|
||||||
first = mid + 1; // and last stays the same
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return VectorFont[mid];
|
|
||||||
}
|
|
||||||
return GetVectorGlyph(0xfffd); // replacement character
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internally and in the UI, the vector font is sized using cap height.
|
|
||||||
#define FONT_SCALE(h) ((h)/(double)FONT_CAP_HEIGHT)
|
|
||||||
double ssglStrCapHeight(double h)
|
|
||||||
{
|
|
||||||
return FONT_CAP_HEIGHT * FONT_SCALE(h) / SS.GW.scale;
|
|
||||||
}
|
|
||||||
double ssglStrFontSize(double h)
|
|
||||||
{
|
|
||||||
return FONT_SIZE * FONT_SCALE(h) / SS.GW.scale;
|
|
||||||
}
|
|
||||||
double ssglStrWidth(const std::string &str, double h)
|
|
||||||
{
|
|
||||||
int width = 0;
|
|
||||||
for(char32_t chr : ReadUTF8(str)) {
|
|
||||||
const VectorGlyph &glyph = GetVectorGlyph(chr);
|
|
||||||
if(glyph.baseCharacter != 0) {
|
|
||||||
const VectorGlyph &baseGlyph = GetVectorGlyph(glyph.baseCharacter);
|
|
||||||
width += max(glyph.advanceWidth, baseGlyph.advanceWidth);
|
|
||||||
} else {
|
|
||||||
width += glyph.advanceWidth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return width * FONT_SCALE(h) / SS.GW.scale;
|
|
||||||
}
|
|
||||||
void ssglWriteTextRefCenter(const std::string &str, double h, Vector t, Vector u, Vector v,
|
|
||||||
ssglLineFn *fn, void *fndata)
|
|
||||||
{
|
|
||||||
u = u.WithMagnitude(1);
|
|
||||||
v = v.WithMagnitude(1);
|
|
||||||
|
|
||||||
double scale = FONT_SCALE(h)/SS.GW.scale;
|
|
||||||
double fh = ssglStrCapHeight(h);
|
|
||||||
double fw = ssglStrWidth(str, h);
|
|
||||||
|
|
||||||
t = t.Plus(u.ScaledBy(-fw/2));
|
|
||||||
t = t.Plus(v.ScaledBy(-fh/2));
|
|
||||||
|
|
||||||
// Apply additional offset to get an exact center alignment.
|
|
||||||
t = t.Plus(v.ScaledBy(-4608*scale));
|
|
||||||
|
|
||||||
ssglWriteText(str, h, t, u, v, fn, fndata);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssglLineWidth(GLfloat width) {
|
void ssglLineWidth(GLfloat width) {
|
||||||
// Intel GPUs with Mesa on *nix render thin lines poorly.
|
// Intel GPUs with Mesa on *nix render thin lines poorly.
|
||||||
static bool workaroundChecked, workaroundEnabled;
|
static bool workaroundChecked, workaroundEnabled;
|
||||||
|
@ -102,98 +37,6 @@ static void LineDrawCallback(void *fndata, Vector a, Vector b)
|
||||||
glEnd();
|
glEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector pixelAlign(Vector v) {
|
|
||||||
v = SS.GW.ProjectPoint3(v);
|
|
||||||
v.x = floor(v.x) + 0.5;
|
|
||||||
v.y = floor(v.y) + 0.5;
|
|
||||||
v = SS.GW.UnProjectPoint3(v);
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ssglDrawCharacter(const VectorGlyph &glyph, Vector t, Vector o, Vector u, Vector v,
|
|
||||||
double scale, ssglLineFn *fn, void *fndata, bool gridFit) {
|
|
||||||
int advanceWidth = glyph.advanceWidth;
|
|
||||||
|
|
||||||
if(glyph.baseCharacter != 0) {
|
|
||||||
const VectorGlyph &baseGlyph = GetVectorGlyph(glyph.baseCharacter);
|
|
||||||
int baseWidth = ssglDrawCharacter(baseGlyph, t, o, u, v, scale, fn, fndata, gridFit);
|
|
||||||
advanceWidth = max(glyph.advanceWidth, baseWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
int actualWidth, offsetX;
|
|
||||||
if(gridFit) {
|
|
||||||
o.x += glyph.leftSideBearing;
|
|
||||||
offsetX = glyph.leftSideBearing;
|
|
||||||
actualWidth = glyph.boundingWidth;
|
|
||||||
if(actualWidth == 0) {
|
|
||||||
// Dot, "i", etc.
|
|
||||||
actualWidth = 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
offsetX = 0;
|
|
||||||
actualWidth = advanceWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector tt = t;
|
|
||||||
tt = tt.Plus(u.ScaledBy(o.x * scale));
|
|
||||||
tt = tt.Plus(v.ScaledBy(o.y * scale));
|
|
||||||
|
|
||||||
Vector tu = tt;
|
|
||||||
tu = tu.Plus(u.ScaledBy(actualWidth * scale));
|
|
||||||
|
|
||||||
Vector tv = tt;
|
|
||||||
tv = tv.Plus(v.ScaledBy(FONT_CAP_HEIGHT * scale));
|
|
||||||
|
|
||||||
if(gridFit) {
|
|
||||||
tt = pixelAlign(tt);
|
|
||||||
tu = pixelAlign(tu);
|
|
||||||
tv = pixelAlign(tv);
|
|
||||||
}
|
|
||||||
|
|
||||||
tu = tu.Minus(tt).ScaledBy(1.0 / actualWidth);
|
|
||||||
tv = tv.Minus(tt).ScaledBy(1.0 / FONT_CAP_HEIGHT);
|
|
||||||
|
|
||||||
const int16_t *data = glyph.data;
|
|
||||||
bool pen_up = true;
|
|
||||||
Vector prevp;
|
|
||||||
while(true) {
|
|
||||||
int16_t x = *data++;
|
|
||||||
int16_t y = *data++;
|
|
||||||
|
|
||||||
if(x == PEN_UP && y == PEN_UP) {
|
|
||||||
if(pen_up) break;
|
|
||||||
pen_up = true;
|
|
||||||
} else {
|
|
||||||
Vector p = tt;
|
|
||||||
p = p.Plus(tu.ScaledBy(x - offsetX));
|
|
||||||
p = p.Plus(tv.ScaledBy(y));
|
|
||||||
if(!pen_up) fn(fndata, prevp, p);
|
|
||||||
prevp = p;
|
|
||||||
pen_up = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return advanceWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssglWriteText(const std::string &str, double h, Vector t, Vector u, Vector v,
|
|
||||||
ssglLineFn *fn, void *fndata)
|
|
||||||
{
|
|
||||||
if(!fn) fn = LineDrawCallback;
|
|
||||||
u = u.WithMagnitude(1);
|
|
||||||
v = v.WithMagnitude(1);
|
|
||||||
|
|
||||||
// Perform grid-fitting only when the text is parallel to the view plane.
|
|
||||||
bool gridFit = !SS.exportMode && u.Equals(SS.GW.projRight) && v.Equals(SS.GW.projUp);
|
|
||||||
|
|
||||||
double scale = FONT_SCALE(h) / SS.GW.scale;
|
|
||||||
Vector o = { 3840.0, 3840.0, 0.0 };
|
|
||||||
for(char32_t chr : ReadUTF8(str)) {
|
|
||||||
const VectorGlyph &glyph = GetVectorGlyph(chr);
|
|
||||||
o.x += ssglDrawCharacter(glyph, t, o, u, v, scale, fn, fndata, gridFit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssglVertex3v(Vector u)
|
void ssglVertex3v(Vector u)
|
||||||
{
|
{
|
||||||
glVertex3f((GLfloat)u.x, (GLfloat)u.y, (GLfloat)u.z);
|
glVertex3f((GLfloat)u.x, (GLfloat)u.y, (GLfloat)u.z);
|
||||||
|
@ -841,4 +684,139 @@ void ssglBitmapText(const std::string &str, Vector p)
|
||||||
glDisable(GL_TEXTURE_2D);
|
glDisable(GL_TEXTURE_2D);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Bitmap font rendering
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static VectorFont BuiltinVectorFont;
|
||||||
|
static void LoadVectorFont() {
|
||||||
|
if(!BuiltinVectorFont.IsEmpty()) return;
|
||||||
|
|
||||||
|
BuiltinVectorFont = VectorFont::From(LoadStringFromGzip("fonts/unicode.lff.gz"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internally and in the UI, the vector font is sized using cap height.
|
||||||
|
#define FONT_SCALE(h) ((h)/(double)BuiltinVectorFont.capHeight)
|
||||||
|
|
||||||
|
double ssglStrCapHeight(double h)
|
||||||
|
{
|
||||||
|
return BuiltinVectorFont.capHeight *
|
||||||
|
FONT_SCALE(h) / SS.GW.scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
double ssglStrFontSize(double h)
|
||||||
|
{
|
||||||
|
return (BuiltinVectorFont.ascender - BuiltinVectorFont.descender) *
|
||||||
|
FONT_SCALE(h) / SS.GW.scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
double ssglStrWidth(const std::string &str, double h)
|
||||||
|
{
|
||||||
|
LoadVectorFont();
|
||||||
|
|
||||||
|
int width = 0;
|
||||||
|
for(char32_t codepoint : ReadUTF8(str)) {
|
||||||
|
width += BuiltinVectorFont.GetGlyph(codepoint).advanceWidth;
|
||||||
|
}
|
||||||
|
return width * FONT_SCALE(h) / SS.GW.scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector PixelAlign(Vector v) {
|
||||||
|
v = SS.GW.ProjectPoint3(v);
|
||||||
|
v.x = floor(v.x) + 0.5;
|
||||||
|
v.y = floor(v.y) + 0.5;
|
||||||
|
v = SS.GW.UnProjectPoint3(v);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int DrawCharacter(const VectorFont::Glyph &glyph, Vector t, Vector o, Vector u, Vector v,
|
||||||
|
double scale, ssglLineFn *fn, void *fndata, bool gridFit) {
|
||||||
|
int advanceWidth = glyph.advanceWidth;
|
||||||
|
|
||||||
|
int actualWidth, offsetX;
|
||||||
|
if(gridFit) {
|
||||||
|
o.x += glyph.leftSideBearing;
|
||||||
|
offsetX = glyph.leftSideBearing;
|
||||||
|
actualWidth = glyph.boundingWidth;
|
||||||
|
if(actualWidth == 0) {
|
||||||
|
// Dot, "i", etc.
|
||||||
|
actualWidth = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
offsetX = 0;
|
||||||
|
actualWidth = advanceWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector tt = t;
|
||||||
|
tt = tt.Plus(u.ScaledBy(o.x * scale));
|
||||||
|
tt = tt.Plus(v.ScaledBy(o.y * scale));
|
||||||
|
|
||||||
|
Vector tu = tt;
|
||||||
|
tu = tu.Plus(u.ScaledBy(actualWidth * scale));
|
||||||
|
|
||||||
|
Vector tv = tt;
|
||||||
|
tv = tv.Plus(v.ScaledBy(BuiltinVectorFont.capHeight * scale));
|
||||||
|
|
||||||
|
if(gridFit) {
|
||||||
|
tt = PixelAlign(tt);
|
||||||
|
tu = PixelAlign(tu);
|
||||||
|
tv = PixelAlign(tv);
|
||||||
|
}
|
||||||
|
|
||||||
|
tu = tu.Minus(tt).ScaledBy(1.0 / actualWidth);
|
||||||
|
tv = tv.Minus(tt).ScaledBy(1.0 / BuiltinVectorFont.capHeight);
|
||||||
|
|
||||||
|
for(const VectorFont::Contour &contour : glyph.contours) {
|
||||||
|
Vector prevp;
|
||||||
|
bool penUp = true;
|
||||||
|
for(const Point2d &pt : contour.points) {
|
||||||
|
Vector p = tt;
|
||||||
|
p = p.Plus(tu.ScaledBy(pt.x - offsetX));
|
||||||
|
p = p.Plus(tv.ScaledBy(pt.y));
|
||||||
|
if(!penUp) fn(fndata, prevp, p);
|
||||||
|
prevp = p;
|
||||||
|
penUp = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return advanceWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssglWriteText(const std::string &str, double h, Vector t, Vector u, Vector v,
|
||||||
|
ssglLineFn *fn, void *fndata)
|
||||||
|
{
|
||||||
|
LoadVectorFont();
|
||||||
|
|
||||||
|
if(!fn) fn = LineDrawCallback;
|
||||||
|
u = u.WithMagnitude(1);
|
||||||
|
v = v.WithMagnitude(1);
|
||||||
|
|
||||||
|
// Perform grid-fitting only when the text is parallel to the view plane.
|
||||||
|
bool gridFit = !SS.exportMode && u.Equals(SS.GW.projRight) && v.Equals(SS.GW.projUp);
|
||||||
|
|
||||||
|
double scale = FONT_SCALE(h) / SS.GW.scale;
|
||||||
|
Vector o = {};
|
||||||
|
for(char32_t codepoint : ReadUTF8(str)) {
|
||||||
|
o.x += DrawCharacter(BuiltinVectorFont.GetGlyph(codepoint),
|
||||||
|
t, o, u, v, scale, fn, fndata, gridFit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssglWriteTextRefCenter(const std::string &str, double h, Vector t, Vector u, Vector v,
|
||||||
|
ssglLineFn *fn, void *fndata)
|
||||||
|
{
|
||||||
|
LoadVectorFont();
|
||||||
|
|
||||||
|
u = u.WithMagnitude(1);
|
||||||
|
v = v.WithMagnitude(1);
|
||||||
|
|
||||||
|
double fh = ssglStrCapHeight(h);
|
||||||
|
double fw = ssglStrWidth(str, h);
|
||||||
|
|
||||||
|
t = t.Plus(u.ScaledBy(-fw/2));
|
||||||
|
t = t.Plus(v.ScaledBy(-fh/2));
|
||||||
|
|
||||||
|
ssglWriteText(str, h, t, u, v, fn, fndata);
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
254
src/resource.cpp
254
src/resource.cpp
|
@ -3,9 +3,10 @@
|
||||||
//
|
//
|
||||||
// Copyright 2016 whitequark
|
// Copyright 2016 whitequark
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
#include "solvespace.h"
|
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
#include <png.h>
|
#include <png.h>
|
||||||
|
#include <regex>
|
||||||
|
#include "solvespace.h"
|
||||||
|
|
||||||
namespace SolveSpace {
|
namespace SolveSpace {
|
||||||
|
|
||||||
|
@ -175,18 +176,39 @@ public:
|
||||||
return ASCIIReader({ str.cbegin(), str.cend() });
|
return ASCIIReader({ str.cbegin(), str.cend() });
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t LengthToEOL() {
|
bool AtEnd() const {
|
||||||
|
return pos == end;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t CountUntilEOL() const {
|
||||||
return std::find(pos, end, '\n') - pos;
|
return std::find(pos, end, '\n') - pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReadChar(char c) {
|
void SkipUntilEOL() {
|
||||||
if(pos == end) oops();
|
pos = std::find(pos, end, '\n');
|
||||||
if(*pos++ != c) oops();
|
}
|
||||||
|
|
||||||
|
char ReadChar() {
|
||||||
|
if(AtEnd()) oops();
|
||||||
|
return *pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TryChar(char c) {
|
||||||
|
if(AtEnd()) oops();
|
||||||
|
if(*pos == c) {
|
||||||
|
pos++;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExpectChar(char c) {
|
||||||
|
if(ReadChar() != c) oops();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t Read4HexBits() {
|
uint8_t Read4HexBits() {
|
||||||
if(pos == end) oops();
|
char c = ReadChar();
|
||||||
char c = *pos++;
|
|
||||||
if(c >= '0' && c <= '9') {
|
if(c >= '0' && c <= '9') {
|
||||||
return c - '0';
|
return c - '0';
|
||||||
} else if(c >= 'a' && c <= 'f') {
|
} else if(c >= 'a' && c <= 'f') {
|
||||||
|
@ -207,6 +229,23 @@ public:
|
||||||
l = Read8HexBits();
|
l = Read8HexBits();
|
||||||
return (h << 8) + l;
|
return (h << 8) + l;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double ReadDoubleString() {
|
||||||
|
char *endptr;
|
||||||
|
double d = strtod(&*pos, &endptr);
|
||||||
|
if(&*pos == endptr) oops();
|
||||||
|
pos += endptr - &*pos;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadRegex(const std::regex &re, std::smatch *m) {
|
||||||
|
if(std::regex_search(pos, end, *m, re, std::regex_constants::match_continuous)) {
|
||||||
|
pos = (*m)[0].second;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -273,7 +312,7 @@ const BitmapFont::Glyph &BitmapFont::GetGlyph(char32_t codepoint) {
|
||||||
// Read the codepoint.
|
// Read the codepoint.
|
||||||
ASCIIReader reader = { mid, unifontData.cend() };
|
ASCIIReader reader = { mid, unifontData.cend() };
|
||||||
char32_t foundCodepoint = reader.Read16HexBits();
|
char32_t foundCodepoint = reader.Read16HexBits();
|
||||||
reader.ReadChar(':');
|
reader.ExpectChar(':');
|
||||||
|
|
||||||
if(foundCodepoint > codepoint) {
|
if(foundCodepoint > codepoint) {
|
||||||
last = mid - 1;
|
last = mid - 1;
|
||||||
|
@ -294,7 +333,7 @@ const BitmapFont::Glyph &BitmapFont::GetGlyph(char32_t codepoint) {
|
||||||
|
|
||||||
// Read glyph bits.
|
// Read glyph bits.
|
||||||
unsigned short glyphBits[16];
|
unsigned short glyphBits[16];
|
||||||
int glyphLength = reader.LengthToEOL();
|
int glyphLength = reader.CountUntilEOL();
|
||||||
if(glyphLength == 4 * 16) {
|
if(glyphLength == 4 * 16) {
|
||||||
glyph.advanceCells = 2;
|
glyph.advanceCells = 2;
|
||||||
for(size_t i = 0; i < 16; i++) {
|
for(size_t i = 0; i < 16; i++) {
|
||||||
|
@ -340,4 +379,201 @@ bool BitmapFont::LocateGlyph(char32_t codepoint,
|
||||||
return textureUpdated;
|
return textureUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Vector font manipulation
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const static int ARC_POINTS = 8;
|
||||||
|
static void MakePwlArc(VectorFont::Contour *contour, bool isReversed,
|
||||||
|
const Point2d &cp, double radius, double a1, double a2) {
|
||||||
|
if (radius < LENGTH_EPS) return;
|
||||||
|
|
||||||
|
double aSign = 1.0;
|
||||||
|
if(isReversed) {
|
||||||
|
if(a1 <= a2 + LENGTH_EPS) a1 += 2.0 * M_PI;
|
||||||
|
aSign = -1.0;
|
||||||
|
} else {
|
||||||
|
if(a2 <= a1 + LENGTH_EPS) a2 += 2.0 * M_PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
double aStep = aSign * fabs(a2 - a1) / (double)ARC_POINTS;
|
||||||
|
for(int i = 0; i <= ARC_POINTS; i++) {
|
||||||
|
contour->points.emplace_back(cp.Plus(Point2d::FromPolar(radius, a1 + aStep * i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void MakePwlBulge(VectorFont::Contour *contour, const Point2d &v, double bulge) {
|
||||||
|
bool reversed = bulge < 0.0;
|
||||||
|
double alpha = atan(bulge) * 4.0;
|
||||||
|
const Point2d &point = contour->points.back();
|
||||||
|
|
||||||
|
Point2d middle = point.Plus(v).ScaledBy(0.5);
|
||||||
|
double dist = point.DistanceTo(v) / 2.0;
|
||||||
|
double angle = point.AngleTo(v);
|
||||||
|
|
||||||
|
// alpha can't be 0.0 at this point
|
||||||
|
double radius = fabs(dist / sin(alpha / 2.0));
|
||||||
|
double wu = fabs(radius*radius - dist*dist);
|
||||||
|
double h = sqrt(wu);
|
||||||
|
|
||||||
|
if(bulge > 0.0) {
|
||||||
|
angle += M_PI_2;
|
||||||
|
} else {
|
||||||
|
angle -= M_PI_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fabs(alpha) > M_PI) {
|
||||||
|
h = -h;
|
||||||
|
}
|
||||||
|
|
||||||
|
Point2d center = Point2d::FromPolar(h, angle).Plus(middle);
|
||||||
|
double a1 = center.AngleTo(point);
|
||||||
|
double a2 = center.AngleTo(v);
|
||||||
|
MakePwlArc(contour, reversed, center, radius, a1, a2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GetGlyphBBox(const VectorFont::Glyph &glyph,
|
||||||
|
double *rminx, double *rmaxx, double *rminy, double *rmaxy) {
|
||||||
|
double minx = 0.0, maxx = 0.0, miny = 0.0, maxy = 0.0;
|
||||||
|
if(!glyph.contours.empty()) {
|
||||||
|
const Point2d &start = glyph.contours[0].points[0];
|
||||||
|
minx = maxx = start.x;
|
||||||
|
miny = maxy = start.y;
|
||||||
|
for(const VectorFont::Contour &c : glyph.contours) {
|
||||||
|
for(const Point2d &p : c.points) {
|
||||||
|
maxx = std::max(maxx, p.x);
|
||||||
|
minx = std::min(minx, p.x);
|
||||||
|
maxy = std::max(maxy, p.y);
|
||||||
|
miny = std::min(miny, p.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(rminx) *rminx = minx;
|
||||||
|
if(rmaxx) *rmaxx = maxx;
|
||||||
|
if(rminy) *rminy = miny;
|
||||||
|
if(rmaxy) *rmaxy = maxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
VectorFont VectorFont::From(std::string &&lffData) {
|
||||||
|
VectorFont font = {};
|
||||||
|
font.lffData = std::move(lffData);
|
||||||
|
|
||||||
|
ASCIIReader reader = ASCIIReader::From(font.lffData);
|
||||||
|
std::smatch m;
|
||||||
|
while(reader.ReadRegex(std::regex("#\\s*(\\w+)\\s*:\\s*(.+?)\n"), &m)) {
|
||||||
|
std::string name = m.str(1),
|
||||||
|
value = m.str(2);
|
||||||
|
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
|
||||||
|
if (name == "letterspacing") {
|
||||||
|
font.rightSideBearing = std::stod(value);
|
||||||
|
} else if (name == "wordspacing") {
|
||||||
|
Glyph space = {};
|
||||||
|
space.advanceWidth = std::stod(value);
|
||||||
|
font.glyphs.emplace(' ', std::move(space));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GetGlyphBBox(font.GetGlyph('A'), nullptr, nullptr, nullptr, &font.capHeight);
|
||||||
|
GetGlyphBBox(font.GetGlyph('h'), nullptr, nullptr, nullptr, &font.ascender);
|
||||||
|
GetGlyphBBox(font.GetGlyph('p'), nullptr, nullptr, &font.descender, nullptr);
|
||||||
|
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VectorFont::Glyph &VectorFont::GetGlyph(char32_t codepoint) {
|
||||||
|
auto it = glyphs.find(codepoint);
|
||||||
|
if(it != glyphs.end()) {
|
||||||
|
return (*it).second;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto firstGlyph = std::find(lffData.cbegin(), lffData.cend(), '[');
|
||||||
|
if(firstGlyph == lffData.cend()) oops();
|
||||||
|
|
||||||
|
// Find the serialized representation in the (sorted) lff file.
|
||||||
|
auto first = firstGlyph,
|
||||||
|
last = lffData.cend();
|
||||||
|
while(first <= last) {
|
||||||
|
auto mid = first + (last - first) / 2;
|
||||||
|
while(mid > first) {
|
||||||
|
if(*mid == '[' && *(mid - 1) == '\n') break;
|
||||||
|
mid--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the codepoint.
|
||||||
|
ASCIIReader reader = { mid, lffData.cend() };
|
||||||
|
reader.ExpectChar('[');
|
||||||
|
char32_t foundCodepoint = reader.Read16HexBits();
|
||||||
|
reader.ExpectChar(']');
|
||||||
|
reader.SkipUntilEOL();
|
||||||
|
|
||||||
|
if(foundCodepoint > codepoint) {
|
||||||
|
last = mid - 1;
|
||||||
|
continue; // and first stays the same
|
||||||
|
}
|
||||||
|
if(foundCodepoint < codepoint) {
|
||||||
|
first = mid + 1;
|
||||||
|
while(first != lffData.cend()) {
|
||||||
|
if(*first == '[' && *(first - 1) == '\n') break;
|
||||||
|
first++;
|
||||||
|
}
|
||||||
|
continue; // and last stays the same
|
||||||
|
}
|
||||||
|
|
||||||
|
// Found the codepoint.
|
||||||
|
VectorFont::Glyph glyph = {};
|
||||||
|
|
||||||
|
// Read glyph contours.
|
||||||
|
while(!reader.AtEnd()) {
|
||||||
|
if(reader.TryChar('\n')) {
|
||||||
|
// Skip.
|
||||||
|
} else if(reader.TryChar('[')) {
|
||||||
|
// End of glyph.
|
||||||
|
if(glyph.contours.back().points.empty()) {
|
||||||
|
// Remove an useless empty contour, if any.
|
||||||
|
glyph.contours.pop_back();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else if(reader.TryChar('C')) {
|
||||||
|
// Another character is referenced in this one.
|
||||||
|
char32_t baseCodepoint = reader.Read16HexBits();
|
||||||
|
const VectorFont::Glyph &baseGlyph = GetGlyph(baseCodepoint);
|
||||||
|
std::copy(baseGlyph.contours.begin(), baseGlyph.contours.end(),
|
||||||
|
std::back_inserter(glyph.contours));
|
||||||
|
} else {
|
||||||
|
Contour contour;
|
||||||
|
do {
|
||||||
|
Point2d p;
|
||||||
|
p.x = reader.ReadDoubleString();
|
||||||
|
reader.ExpectChar(',');
|
||||||
|
p.y = reader.ReadDoubleString();
|
||||||
|
|
||||||
|
if(reader.TryChar(',')) {
|
||||||
|
// Point with a bulge.
|
||||||
|
reader.ExpectChar('A');
|
||||||
|
double bulge = reader.ReadDoubleString();
|
||||||
|
MakePwlBulge(&contour, p, bulge);
|
||||||
|
} else {
|
||||||
|
// Just a point.
|
||||||
|
contour.points.emplace_back(std::move(p));
|
||||||
|
}
|
||||||
|
} while(reader.TryChar(';'));
|
||||||
|
reader.ExpectChar('\n');
|
||||||
|
glyph.contours.emplace_back(std::move(contour));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate metrics.
|
||||||
|
GetGlyphBBox(glyph, &glyph.leftSideBearing, &glyph.boundingWidth, nullptr, nullptr);
|
||||||
|
glyph.advanceWidth = glyph.leftSideBearing + glyph.boundingWidth + rightSideBearing;
|
||||||
|
|
||||||
|
it = glyphs.emplace(codepoint, std::move(glyph)).first;
|
||||||
|
return (*it).second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Glyph doesn't exist; return replacement glyph instead.
|
||||||
|
if(codepoint == 0xfffd) oops();
|
||||||
|
return GetGlyph(0xfffd);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#ifndef __RESOURCE_H
|
#ifndef __RESOURCE_H
|
||||||
#define __RESOURCE_H
|
#define __RESOURCE_H
|
||||||
|
|
||||||
|
class Point2d;
|
||||||
class Pixmap;
|
class Pixmap;
|
||||||
|
|
||||||
// Only the following function is platform-specific.
|
// Only the following function is platform-specific.
|
||||||
|
@ -61,4 +62,31 @@ public:
|
||||||
void AddGlyph(char32_t codepoint, const Pixmap &pixmap);
|
void AddGlyph(char32_t codepoint, const Pixmap &pixmap);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class VectorFont {
|
||||||
|
public:
|
||||||
|
struct Contour {
|
||||||
|
std::vector<Point2d> points;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Glyph {
|
||||||
|
std::vector<Contour> contours;
|
||||||
|
double leftSideBearing;
|
||||||
|
double boundingWidth;
|
||||||
|
double advanceWidth;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string lffData;
|
||||||
|
std::map<char32_t, Glyph> glyphs;
|
||||||
|
double rightSideBearing;
|
||||||
|
double capHeight;
|
||||||
|
double ascender;
|
||||||
|
double descender;
|
||||||
|
|
||||||
|
static VectorFont From(std::string &&lffData);
|
||||||
|
|
||||||
|
bool IsEmpty() const { return lffData.empty(); }
|
||||||
|
|
||||||
|
const Glyph &GetGlyph(char32_t codepoint);
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
13
src/util.cpp
13
src/util.cpp
|
@ -938,6 +938,19 @@ Point2d Point2d::From(double x, double y) {
|
||||||
return { x, y };
|
return { x, y };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Point2d Point2d::FromPolar(double r, double a) {
|
||||||
|
return { r * cos(a), r * sin(a) };
|
||||||
|
}
|
||||||
|
|
||||||
|
double Point2d::Angle() const {
|
||||||
|
double a = atan2(y, x);
|
||||||
|
return M_PI + remainder(a - M_PI, 2 * M_PI);
|
||||||
|
}
|
||||||
|
|
||||||
|
double Point2d::AngleTo(const Point2d &p) const {
|
||||||
|
return p.Minus(*this).Angle();
|
||||||
|
}
|
||||||
|
|
||||||
Point2d Point2d::Plus(const Point2d &b) const {
|
Point2d Point2d::Plus(const Point2d &b) const {
|
||||||
return { x + b.x, y + b.y };
|
return { x + b.x, y + b.y };
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
add_executable(lff2c
|
|
||||||
lff2c.cpp)
|
|
||||||
|
|
||||||
target_link_libraries(lff2c
|
|
||||||
${ZLIB_LIBRARIES})
|
|
410
tools/lff2c.cpp
410
tools/lff2c.cpp
|
@ -1,410 +0,0 @@
|
||||||
#define _USE_MATH_DEFINES
|
|
||||||
#include <zlib.h>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cctype>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
#include <map>
|
|
||||||
#include <iostream>
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#define TOLERANCE 1e-6
|
|
||||||
|
|
||||||
double correctAngle(double a) {
|
|
||||||
return M_PI + remainder(a - M_PI, 2 * M_PI);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Point {
|
|
||||||
double x;
|
|
||||||
double y;
|
|
||||||
|
|
||||||
Point operator+(const Point &o) const { return { x + o.x, y + o.y }; }
|
|
||||||
Point operator-(const Point &o) const { return { x - o.x, y - o.y }; }
|
|
||||||
Point operator*(const Point &o) const { return { x * o.x, y * o.y }; }
|
|
||||||
Point operator/(const Point &o) const { return { x / o.x, y / o.y }; }
|
|
||||||
|
|
||||||
Point operator*(double k) const { return { x * k, y * k }; }
|
|
||||||
Point operator/(double k) const { return { x / k, y / k }; }
|
|
||||||
|
|
||||||
double length() const{
|
|
||||||
return sqrt(x * x + y * y);
|
|
||||||
}
|
|
||||||
|
|
||||||
double angle() const {
|
|
||||||
return correctAngle(atan2(y, x));
|
|
||||||
}
|
|
||||||
|
|
||||||
double distanceTo(const Point &v) const {
|
|
||||||
return (*this - v).length();
|
|
||||||
}
|
|
||||||
|
|
||||||
double angleTo(const Point &v) const {
|
|
||||||
return (v - *this).angle();
|
|
||||||
}
|
|
||||||
|
|
||||||
static Point polar(double radius, double angle) {
|
|
||||||
return { radius * cos(angle), radius * sin(angle) };
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const Point &o) const { return x == o.x && y == o.y; }
|
|
||||||
bool operator!=(const Point &o) const { return x != o.x || y != o.y; }
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Curve {
|
|
||||||
std::vector<Point> points;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Glyph {
|
|
||||||
char32_t character;
|
|
||||||
char32_t baseCharacter;
|
|
||||||
std::vector<Curve> curves;
|
|
||||||
|
|
||||||
void getHorizontalBounds(double *rminx, double *rmaxx) const {
|
|
||||||
double minx = 0;
|
|
||||||
double maxx = 0;
|
|
||||||
if(!curves.empty()) {
|
|
||||||
minx = curves[0].points[0].x;
|
|
||||||
maxx = minx;
|
|
||||||
for(const Curve &c : curves) {
|
|
||||||
for(const Point &p : c.points) {
|
|
||||||
maxx = std::max(maxx, p.x);
|
|
||||||
minx = std::min(minx, p.x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(rminx) *rminx = minx;
|
|
||||||
if(rmaxx) *rmaxx = maxx;
|
|
||||||
}
|
|
||||||
|
|
||||||
void getVerticalBounds(double *rminy, double *rmaxy) const {
|
|
||||||
double miny = 0;
|
|
||||||
double maxy = 0;
|
|
||||||
if(!curves.empty()) {
|
|
||||||
miny = curves[0].points[0].y;
|
|
||||||
maxy = miny;
|
|
||||||
for(const Curve &c : curves) {
|
|
||||||
for(const Point &p : c.points) {
|
|
||||||
maxy = std::max(maxy, p.y);
|
|
||||||
miny = std::min(miny, p.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(rminy) *rminy = miny;
|
|
||||||
if(rmaxy) *rmaxy = maxy;
|
|
||||||
}
|
|
||||||
|
|
||||||
void getHorizontalMetrics(double *leftSideBearing, double *boundingWidth) const {
|
|
||||||
double minx, maxx;
|
|
||||||
getHorizontalBounds(&minx, &maxx);
|
|
||||||
*leftSideBearing = minx;
|
|
||||||
*boundingWidth = maxx - minx;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator<(const Glyph &o) const { return character < o.character; }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Font {
|
|
||||||
double letterSpacing;
|
|
||||||
double wordSpacing;
|
|
||||||
std::vector<Glyph> glyphs;
|
|
||||||
|
|
||||||
const Glyph &findGlyph(char32_t character) {
|
|
||||||
return *std::find_if(glyphs.begin(), glyphs.end(),
|
|
||||||
[&](const Glyph &g) { return g.character == character; });
|
|
||||||
}
|
|
||||||
|
|
||||||
void getGlyphBound(double *rminw, double *rminh, double *rmaxw, double *rmaxh) {
|
|
||||||
if(glyphs.empty()) {
|
|
||||||
*rminw = 0.0;
|
|
||||||
*rmaxw = 0.0;
|
|
||||||
*rminh = 0.0;
|
|
||||||
*rmaxh = 0.0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
glyphs[0].getHorizontalBounds(rminw, rmaxw);
|
|
||||||
glyphs[0].getVerticalBounds(rminh, rmaxh);
|
|
||||||
for(const Glyph &g : glyphs) {
|
|
||||||
double minw, minh, maxw, maxh;
|
|
||||||
g.getHorizontalBounds(&minw, &maxw);
|
|
||||||
g.getVerticalBounds(&minh, &maxh);
|
|
||||||
*rminw = std::min(*rminw, minw);
|
|
||||||
*rminh = std::min(*rminh, minh);
|
|
||||||
*rmaxw = std::max(*rmaxw, maxw);
|
|
||||||
*rmaxh = std::max(*rmaxh, maxh);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void createArc(Curve &curve, const Point &cp, double radius,
|
|
||||||
double a1, double a2, bool reversed) {
|
|
||||||
if (radius < 1e-6) return;
|
|
||||||
|
|
||||||
double aSign = 1.0;
|
|
||||||
if(reversed) {
|
|
||||||
if(a1 <= a2 + TOLERANCE) a1 += 2.0 * M_PI;
|
|
||||||
aSign = -1.0;
|
|
||||||
} else {
|
|
||||||
if(a2 <= a1 + TOLERANCE) a2 += 2.0 * M_PI;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Angle Step (rad)
|
|
||||||
double da = fabs(a2 - a1);
|
|
||||||
int numPoints = 8;
|
|
||||||
double aStep = aSign * da / double(numPoints);
|
|
||||||
|
|
||||||
for(int i = 0; i <= numPoints; i++) {
|
|
||||||
curve.points.push_back(cp + Point::polar(radius, a1 + aStep * i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void createBulge(const Point &v, double bulge, Curve &curve) {
|
|
||||||
bool reversed = bulge < 0.0;
|
|
||||||
double alpha = atan(bulge) * 4.0;
|
|
||||||
Point &point = curve.points.back();
|
|
||||||
|
|
||||||
Point middle = (point + v) / 2.0;
|
|
||||||
double dist = point.distanceTo(v) / 2.0;
|
|
||||||
double angle = point.angleTo(v);
|
|
||||||
|
|
||||||
// alpha can't be 0.0 at this point
|
|
||||||
double radius = fabs(dist / sin(alpha / 2.0));
|
|
||||||
double wu = fabs(radius*radius - dist*dist);
|
|
||||||
double h = sqrt(wu);
|
|
||||||
|
|
||||||
if(bulge > 0.0) {
|
|
||||||
angle += M_PI_2;
|
|
||||||
} else {
|
|
||||||
angle -= M_PI_2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fabs(alpha) > M_PI) {
|
|
||||||
h *= -1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Point center = Point::polar(h, angle);
|
|
||||||
center = center + middle;
|
|
||||||
|
|
||||||
double a1 = center.angleTo(point);
|
|
||||||
double a2 = center.angleTo(v);
|
|
||||||
createArc(curve, center, radius, a1, a2, reversed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void readLff(const std::string &path) {
|
|
||||||
gzFile lfffont = gzopen(path.c_str(), "rb");
|
|
||||||
if(!lfffont) {
|
|
||||||
std::cerr << path << ": gzopen failed" << std::endl;
|
|
||||||
std::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read line by line until we find a new letter:
|
|
||||||
Glyph *currentGlyph = nullptr;
|
|
||||||
while(!gzeof(lfffont)) {
|
|
||||||
std::string line;
|
|
||||||
do {
|
|
||||||
char buf[128] = {0};
|
|
||||||
if(!gzgets(lfffont, buf, sizeof(buf)))
|
|
||||||
break;
|
|
||||||
line += buf;
|
|
||||||
} while(line.back() != '\n');
|
|
||||||
|
|
||||||
if(line.empty() || line[0] == '\n') {
|
|
||||||
continue;
|
|
||||||
} else if(line[0] == '#') {
|
|
||||||
// This is comment or metadata.
|
|
||||||
std::istringstream ss(line.substr(1));
|
|
||||||
|
|
||||||
std::vector<std::string> tokens;
|
|
||||||
while(!ss.eof()) {
|
|
||||||
std::string token;
|
|
||||||
std::getline(ss, token, ':');
|
|
||||||
std::istringstream(token) >> token; // trim
|
|
||||||
if(!token.empty())
|
|
||||||
tokens.push_back(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not in form of "a:b" then it's not metadata, just a comment.
|
|
||||||
if (tokens.size() != 2)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
std::string &identifier = tokens[0];
|
|
||||||
std::string &value = tokens[1];
|
|
||||||
|
|
||||||
std::transform(identifier.begin(), identifier.end(), identifier.begin(),
|
|
||||||
::tolower);
|
|
||||||
if (identifier == "letterspacing") {
|
|
||||||
std::istringstream(value) >> letterSpacing;
|
|
||||||
} else if (identifier == "wordspacing") {
|
|
||||||
std::istringstream(value) >> wordSpacing;
|
|
||||||
} else if (identifier == "linespacingfactor") {
|
|
||||||
/* don't care */
|
|
||||||
} else if (identifier == "author") {
|
|
||||||
/* don't care */
|
|
||||||
} else if (identifier == "name") {
|
|
||||||
/* don't care */
|
|
||||||
} else if (identifier == "license") {
|
|
||||||
/* don't care */
|
|
||||||
} else if (identifier == "encoding") {
|
|
||||||
/* don't care */
|
|
||||||
} else if (identifier == "created") {
|
|
||||||
/* don't care */
|
|
||||||
}
|
|
||||||
} else if(line[0] == '[') {
|
|
||||||
// This is a glyph.
|
|
||||||
size_t closingPos;
|
|
||||||
char32_t chr = std::stoi(line.substr(1), &closingPos, 16);;
|
|
||||||
if(line[closingPos + 1] != ']') {
|
|
||||||
std::cerr << "unrecognized character number: " << line << std::endl;
|
|
||||||
currentGlyph = nullptr;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
glyphs.emplace_back();
|
|
||||||
currentGlyph = &glyphs.back();
|
|
||||||
currentGlyph->character = chr;
|
|
||||||
currentGlyph->baseCharacter = 0;
|
|
||||||
} else if(currentGlyph != nullptr) {
|
|
||||||
if (line[0] == 'C') {
|
|
||||||
// This is a reference to another glyph.
|
|
||||||
currentGlyph->baseCharacter = std::stoi(line.substr(1), nullptr, 16);
|
|
||||||
} else {
|
|
||||||
// This is a series of curves.
|
|
||||||
currentGlyph->curves.emplace_back();
|
|
||||||
Curve &curve = currentGlyph->curves.back();
|
|
||||||
|
|
||||||
std::stringstream ss(line);
|
|
||||||
while (!ss.eof()) {
|
|
||||||
std::string vertex;
|
|
||||||
std::getline(ss, vertex, ';');
|
|
||||||
|
|
||||||
std::stringstream ssv(vertex);
|
|
||||||
std::string coord;
|
|
||||||
Point p;
|
|
||||||
|
|
||||||
if(!std::getline(ssv, coord, ',')) continue;
|
|
||||||
p.x = std::stod(coord);
|
|
||||||
|
|
||||||
if(!std::getline(ssv, coord, ',')) continue;
|
|
||||||
p.y = std::stod(coord);
|
|
||||||
|
|
||||||
if(!std::getline(ssv, coord, ',') || coord[0] != 'A') {
|
|
||||||
// This is just a point.
|
|
||||||
curve.points.push_back(p);
|
|
||||||
} else {
|
|
||||||
// This is a point with a bulge.
|
|
||||||
double bulge = std::stod(coord.substr(1));
|
|
||||||
createBulge(p, bulge, curve);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
std::cerr << "unrecognized line: " << line << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gzclose(lfffont);
|
|
||||||
}
|
|
||||||
|
|
||||||
void writeCppHeader(const std::string &hName) {
|
|
||||||
std::sort(glyphs.begin(), glyphs.end());
|
|
||||||
|
|
||||||
std::ofstream ts(hName, std::ios::out);
|
|
||||||
|
|
||||||
double minX, minY, maxX, maxY;
|
|
||||||
getGlyphBound(&minX, &minY, &maxX, &maxY);
|
|
||||||
|
|
||||||
double size = 32766.0;
|
|
||||||
double scale = size / std::max({ fabs(maxX), fabs(minX), fabs(maxY), fabs(minY) });
|
|
||||||
|
|
||||||
double capHeight, ascender, descender;
|
|
||||||
findGlyph('A').getVerticalBounds(nullptr, &capHeight);
|
|
||||||
findGlyph('h').getVerticalBounds(nullptr, &ascender);
|
|
||||||
findGlyph('p').getVerticalBounds(&descender, nullptr);
|
|
||||||
|
|
||||||
// We use tabs for indentation here to make compilation slightly faster
|
|
||||||
ts <<
|
|
||||||
"/**** This is a generated file - do not edit ****/\n\n"
|
|
||||||
"#ifndef __VECTORFONT_TABLE_H\n"
|
|
||||||
"#define __VECTORFONT_TABLE_H\n"
|
|
||||||
"\n"
|
|
||||||
"#define PEN_UP 32767\n"
|
|
||||||
"#define UP PEN_UP\n"
|
|
||||||
"\n"
|
|
||||||
"#define FONT_CAP_HEIGHT ((int16_t)" << (int)floor(capHeight * scale) << ")\n" <<
|
|
||||||
"#define FONT_ASCENDER ((int16_t)" << (int)floor(ascender * scale) << ")\n" <<
|
|
||||||
"#define FONT_DESCENDER ((int16_t)" << (int)floor(descender * scale) << ")\n" <<
|
|
||||||
"#define FONT_SIZE (FONT_ASCENDER-FONT_DESCENDER)\n"
|
|
||||||
"\n"
|
|
||||||
"struct VectorGlyph {\n"
|
|
||||||
"\tchar32_t character;\n"
|
|
||||||
"\tchar32_t baseCharacter;\n"
|
|
||||||
"\tint leftSideBearing;\n"
|
|
||||||
"\tint boundingWidth;\n"
|
|
||||||
"\tint advanceWidth;\n"
|
|
||||||
"\tconst int16_t *data;\n"
|
|
||||||
"};\n"
|
|
||||||
"\n"
|
|
||||||
"const int16_t VectorFontData[] = {\n"
|
|
||||||
"\tUP, UP,\n";
|
|
||||||
|
|
||||||
std::map<char32_t, size_t> glyphIndexes;
|
|
||||||
size_t index = 2;
|
|
||||||
for(const Glyph &g : glyphs) {
|
|
||||||
ts << "\t// U+" << std::hex << g.character << std::dec << "\n";
|
|
||||||
glyphIndexes[g.character] = index;
|
|
||||||
for(const Curve &c : g.curves) {
|
|
||||||
for(const Point &p : c.points) {
|
|
||||||
ts << "\t" << (int)floor(p.x * scale) << ", " <<
|
|
||||||
(int)floor(p.y * scale) << ",\n";
|
|
||||||
index += 2;
|
|
||||||
}
|
|
||||||
ts << "\tUP, UP,\n";
|
|
||||||
index += 2;
|
|
||||||
}
|
|
||||||
ts << "\tUP, UP,\n"; // end-of-glyph marker
|
|
||||||
index += 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
ts <<
|
|
||||||
"};\n"
|
|
||||||
"\n"
|
|
||||||
"const VectorGlyph VectorFont[] = {\n"
|
|
||||||
"\t// U+20\n"
|
|
||||||
"\t{ 32, 0, 0, 0, " << (int)floor(wordSpacing * scale) << ", &VectorFontData[0] },\n";
|
|
||||||
|
|
||||||
for(const Glyph &g : glyphs) {
|
|
||||||
double leftSideBearing, boundingWidth;
|
|
||||||
g.getHorizontalMetrics(&leftSideBearing, &boundingWidth);
|
|
||||||
|
|
||||||
ts << "\t// U+" << std::hex << g.character << std::dec << "\n";
|
|
||||||
ts << "\t{ " << g.character << ", "
|
|
||||||
<< g.baseCharacter << ", "
|
|
||||||
<< (int)floor(leftSideBearing * scale) << ", "
|
|
||||||
<< (int)floor(boundingWidth * scale) << ", "
|
|
||||||
<< (int)floor((leftSideBearing + boundingWidth +
|
|
||||||
letterSpacing) * scale) << ", ";
|
|
||||||
ts << "&VectorFontData[" << glyphIndexes[g.character] << "] },\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
ts <<
|
|
||||||
"};\n"
|
|
||||||
"\n"
|
|
||||||
"#undef UP\n"
|
|
||||||
"\n"
|
|
||||||
"#endif\n";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
|
||||||
if(argc != 3) {
|
|
||||||
std::cerr << "Usage: " << argv[0] << " <header out> <lff in>\n" << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Font font;
|
|
||||||
font.readLff(argv[2]);
|
|
||||||
font.writeCppHeader(argv[1]);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
Loading…
Reference in New Issue