Add TrueType font support to SolveSpace. This uses a modified
version of the code from SketchFlat, with all arbitrary limits removed. The TTF text is its own entity, and that entity includes the font file basename and the text. That's an extra 128 bytes in the entity, which is around a 50% increase, kind of a shame. It was simple, though. [git-p4: depot-paths = "//depot/solvespace/": change = 1814]solver
parent
a31782e1ea
commit
273339d5c4
1
Makefile
1
Makefile
|
@ -32,6 +32,7 @@ SSOBJS = $(OBJDIR)\solvespace.obj \
|
|||
$(OBJDIR)\polygon.obj \
|
||||
$(OBJDIR)\mesh.obj \
|
||||
$(OBJDIR)\bsp.obj \
|
||||
$(OBJDIR)\ttf.obj \
|
||||
|
||||
|
||||
LIBS = user32.lib gdi32.lib comctl32.lib advapi32.lib opengl32.lib glu32.lib \
|
||||
|
|
22
draw.cpp
22
draw.cpp
|
@ -324,6 +324,7 @@ bool GraphicsWindow::ConstrainPointByHovered(hEntity pt) {
|
|||
|
||||
void GraphicsWindow::MouseLeftDown(double mx, double my) {
|
||||
if(GraphicsEditControlIsVisible()) return;
|
||||
HideTextEditControl();
|
||||
|
||||
// Make sure the hover is up to date.
|
||||
MouseMoved(mx, my, false, false, false, false, false);
|
||||
|
@ -453,6 +454,26 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
|
|||
ClearSuper();
|
||||
break;
|
||||
|
||||
case MNU_TTF_TEXT: {
|
||||
if(!SS.GW.LockedInWorkplane()) {
|
||||
Error("Can't draw text in 3d; select a workplane first.");
|
||||
ClearSuper();
|
||||
break;
|
||||
}
|
||||
hr = AddRequest(Request::TTF_TEXT);
|
||||
Request *r = SS.GetRequest(hr);
|
||||
r->str.strcpy("Abc");
|
||||
r->font.strcpy("arial.ttf");
|
||||
|
||||
SS.GetEntity(hr.entity(1))->PointForceTo(v);
|
||||
SS.GetEntity(hr.entity(2))->PointForceTo(v);
|
||||
|
||||
pending.operation = DRAGGING_NEW_POINT;
|
||||
pending.point = hr.entity(2);
|
||||
pending.description = "click to place bottom left of text";
|
||||
break;
|
||||
}
|
||||
|
||||
case DRAGGING_RADIUS:
|
||||
case DRAGGING_NEW_POINT:
|
||||
// The MouseMoved event has already dragged it as desired.
|
||||
|
@ -546,6 +567,7 @@ void GraphicsWindow::MouseLeftUp(double mx, double my) {
|
|||
|
||||
void GraphicsWindow::MouseLeftDoubleClick(double mx, double my) {
|
||||
if(GraphicsEditControlIsVisible()) return;
|
||||
HideTextEditControl();
|
||||
|
||||
if(hover.constraint.v) {
|
||||
constraintBeingEdited = hover.constraint;
|
||||
|
|
|
@ -132,7 +132,7 @@ bool Entity::IsVisible(void) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void Entity::DrawOrGetDistance(void) {
|
||||
void Entity::DrawOrGetDistance(void) {
|
||||
// If an entity is invisible, then it doesn't get shown, and it doesn't
|
||||
// contribute a distance for the selection, but it still generates edges.
|
||||
if(!dogd.edges) {
|
||||
|
@ -361,6 +361,17 @@ void Entity::DrawOrGetDistance(void) {
|
|||
break;
|
||||
}
|
||||
|
||||
case TTF_TEXT: {
|
||||
Vector topLeft = SS.GetEntity(point[0])->PointGetNum();
|
||||
Vector botLeft = SS.GetEntity(point[1])->PointGetNum();
|
||||
Vector n = Normal()->NormalN();
|
||||
Vector v = topLeft.Minus(botLeft);
|
||||
Vector u = (v.Cross(n)).WithMagnitude(v.Magnitude());
|
||||
|
||||
SS.fonts.PlotString(font.str, str.str, 0, h, botLeft, u, v);
|
||||
break;
|
||||
}
|
||||
|
||||
case FACE_NORMAL_PT:
|
||||
case FACE_XPROD:
|
||||
case FACE_N_ROT_TRANS:
|
||||
|
|
5
file.cpp
5
file.cpp
|
@ -96,10 +96,14 @@ const SolveSpace::SaveTable SolveSpace::SAVED[] = {
|
|||
{ 'r', "Request.workplane.v", 'x', &(SS.sv.r.workplane.v) },
|
||||
{ 'r', "Request.group.v", 'x', &(SS.sv.r.group.v) },
|
||||
{ 'r', "Request.construction", 'b', &(SS.sv.r.construction) },
|
||||
{ 'r', "Request.str", 'N', &(SS.sv.r.str) },
|
||||
{ 'r', "Request.font", 'N', &(SS.sv.r.font) },
|
||||
|
||||
{ 'e', "Entity.h.v", 'x', &(SS.sv.e.h.v) },
|
||||
{ 'e', "Entity.type", 'd', &(SS.sv.e.type) },
|
||||
{ 'e', "Entity.construction", 'b', &(SS.sv.e.construction) },
|
||||
{ 'e', "Entity.str", 'N', &(SS.sv.e.str) },
|
||||
{ 'e', "Entity.font", 'N', &(SS.sv.e.font) },
|
||||
{ 'e', "Entity.point[0].v", 'x', &(SS.sv.e.point[0].v) },
|
||||
{ 'e', "Entity.point[1].v", 'x', &(SS.sv.e.point[1].v) },
|
||||
{ 'e', "Entity.point[2].v", 'x', &(SS.sv.e.point[2].v) },
|
||||
|
@ -149,6 +153,7 @@ void SolveSpace::SaveUsingTable(int type) {
|
|||
if(fmt == 'd' && *((int *)p) == 0) continue;
|
||||
if(fmt == 'x' && *((DWORD *)p) == 0) continue;
|
||||
if(fmt == 'f' && *((double *)p) == 0.0) continue;
|
||||
if(fmt == 'N' && strlen(((NameStr *)p)->str) == 0) continue;
|
||||
|
||||
fprintf(fh, "%s=", SAVED[i].desc);
|
||||
switch(fmt) {
|
||||
|
|
|
@ -66,6 +66,8 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = {
|
|||
{ 1, "&Arc of a Circle\tA", MNU_ARC, 'A', mReq },
|
||||
{ 1, "&Cubic Segment\t3", MNU_CUBIC, '3', mReq },
|
||||
{ 1, NULL, 0, NULL },
|
||||
{ 1, "&Text in TrueType Font\tT", MNU_TTF_TEXT, 'T', mReq },
|
||||
{ 1, NULL, 0, NULL },
|
||||
{ 1, "To&ggle Construction\tG", MNU_CONSTRUCTION, 'G', mReq },
|
||||
|
||||
{ 0, "&Constrain", 0, NULL },
|
||||
|
@ -423,6 +425,8 @@ void GraphicsWindow::MenuEdit(int id) {
|
|||
break;
|
||||
|
||||
case MNU_DELETE: {
|
||||
SS.UndoRemember();
|
||||
|
||||
int i;
|
||||
SS.request.ClearTags();
|
||||
SS.constraint.ClearTags();
|
||||
|
@ -507,6 +511,7 @@ void GraphicsWindow::MenuRequest(int id) {
|
|||
case MNU_ARC: s = "click point on arc (draws anti-clockwise)"; goto c;
|
||||
case MNU_WORKPLANE: s = "click origin of workplane"; goto c;
|
||||
case MNU_RECTANGLE: s = "click one corner of rectangular"; goto c;
|
||||
case MNU_TTF_TEXT: s = "click top left of text"; goto c;
|
||||
c:
|
||||
SS.GW.pending.operation = id;
|
||||
SS.GW.pending.description = s;
|
||||
|
|
16
group.cpp
16
group.cpp
|
@ -70,7 +70,6 @@ void Group::MenuGroup(int id) {
|
|||
Error("Bad selection for new drawing in workplane.");
|
||||
return;
|
||||
}
|
||||
SS.GW.ClearSelection();
|
||||
break;
|
||||
|
||||
case GraphicsWindow::MNU_GROUP_EXTRUDE:
|
||||
|
@ -96,7 +95,6 @@ void Group::MenuGroup(int id) {
|
|||
g.type = LATHE;
|
||||
g.opA = SS.GW.activeGroup;
|
||||
g.name.strcpy("lathe");
|
||||
SS.GW.ClearSelection();
|
||||
break;
|
||||
|
||||
case GraphicsWindow::MNU_GROUP_SWEEP: {
|
||||
|
@ -146,7 +144,6 @@ void Group::MenuGroup(int id) {
|
|||
g.valC = 0; // pitch in radius
|
||||
g.opA = SS.GW.activeGroup;
|
||||
g.name.strcpy("helical-sweep");
|
||||
SS.GW.ClearSelection();
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -168,7 +165,6 @@ void Group::MenuGroup(int id) {
|
|||
g.valA = 3;
|
||||
g.subtype = ONE_SIDED;
|
||||
g.name.strcpy("rotate");
|
||||
SS.GW.ClearSelection();
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -194,7 +190,9 @@ void Group::MenuGroup(int id) {
|
|||
|
||||
default: oops();
|
||||
}
|
||||
SS.GW.ClearSelection();
|
||||
SS.UndoRemember();
|
||||
|
||||
SS.group.AddAndAssignId(&g);
|
||||
Group *gg = SS.GetGroup(g.h);
|
||||
|
||||
|
@ -205,6 +203,8 @@ void Group::MenuGroup(int id) {
|
|||
SS.GW.activeGroup = gg->h;
|
||||
SS.GenerateAll();
|
||||
if(gg->type == DRAWING_WORKPLANE) {
|
||||
// Can't set the active workplane for this one until after we've
|
||||
// regenerated, because the workplane doesn't exist until then.
|
||||
gg->activeWorkplane = gg->h.entity(0);
|
||||
}
|
||||
gg->Activate();
|
||||
|
@ -613,6 +613,14 @@ void Group::CopyEntity(IdList<Entity,hEntity> *el,
|
|||
en.normal = Remap(ep->normal, remap);
|
||||
break;
|
||||
|
||||
case Entity::TTF_TEXT:
|
||||
en.point[0] = Remap(ep->point[0], remap);
|
||||
en.point[1] = Remap(ep->point[1], remap);
|
||||
en.normal = Remap(ep->normal, remap);
|
||||
en.str.strcpy(ep->str.str);
|
||||
en.font.strcpy(ep->font.str);
|
||||
break;
|
||||
|
||||
case Entity::POINT_N_COPY:
|
||||
case Entity::POINT_N_TRANS:
|
||||
case Entity::POINT_N_ROT_TRANS:
|
||||
|
|
|
@ -53,6 +53,12 @@ void Request::Generate(IdList<Entity,hEntity> *entity,
|
|||
points = 4;
|
||||
break;
|
||||
|
||||
case Request::TTF_TEXT:
|
||||
et = Entity::TTF_TEXT;
|
||||
points = 2;
|
||||
hasNormal = true;
|
||||
break;
|
||||
|
||||
default: oops();
|
||||
}
|
||||
|
||||
|
@ -61,6 +67,8 @@ void Request::Generate(IdList<Entity,hEntity> *entity,
|
|||
e.group = group;
|
||||
e.workplane = workplane;
|
||||
e.construction = construction;
|
||||
e.str.strcpy(str.str);
|
||||
e.font.strcpy(font.str);
|
||||
e.h = h.entity(0);
|
||||
|
||||
// And generate entities for the points
|
||||
|
@ -147,6 +155,7 @@ char *Request::DescriptionString(void) {
|
|||
case CUBIC: s = "cubic-bezier"; break;
|
||||
case CIRCLE: s = "circle"; break;
|
||||
case ARC_OF_CIRCLE: s = "arc-of-circle"; break;
|
||||
case TTF_TEXT: s = "ttf-text"; break;
|
||||
default: s = "???"; break;
|
||||
}
|
||||
}
|
||||
|
|
6
sketch.h
6
sketch.h
|
@ -228,6 +228,7 @@ public:
|
|||
static const int CUBIC = 300;
|
||||
static const int CIRCLE = 400;
|
||||
static const int ARC_OF_CIRCLE = 500;
|
||||
static const int TTF_TEXT = 600;
|
||||
|
||||
int type;
|
||||
|
||||
|
@ -235,6 +236,8 @@ public:
|
|||
hGroup group;
|
||||
|
||||
bool construction;
|
||||
NameStr str;
|
||||
NameStr font;
|
||||
|
||||
static hParam AddParam(ParamList *param, hParam hp);
|
||||
void Generate(EntityList *entity, ParamList *param);
|
||||
|
@ -278,6 +281,7 @@ public:
|
|||
static const int CUBIC = 12000;
|
||||
static const int CIRCLE = 13000;
|
||||
static const int ARC_OF_CIRCLE = 14000;
|
||||
static const int TTF_TEXT = 15000;
|
||||
|
||||
int type;
|
||||
|
||||
|
@ -312,6 +316,8 @@ public:
|
|||
hEntity workplane; // or Entity::FREE_IN_3D
|
||||
|
||||
bool construction;
|
||||
NameStr str;
|
||||
NameStr font;
|
||||
|
||||
// For entities that are derived by a transformation, the number of
|
||||
// times to apply the transformation.
|
||||
|
|
91
solvespace.h
91
solvespace.h
|
@ -29,6 +29,7 @@ inline int WRAP(int v, int n) {
|
|||
#define isforname(c) (isalnum(c) || (c) == '_' || (c) == '-' || (c) == '#')
|
||||
|
||||
typedef signed long SDWORD;
|
||||
typedef signed short SWORD;
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
|
@ -44,6 +45,7 @@ class ExprVector;
|
|||
class ExprQuaternion;
|
||||
|
||||
|
||||
//================
|
||||
// From the platform-specific code.
|
||||
#define MAX_RECENT 8
|
||||
#define RECENT_OPEN (0xf000)
|
||||
|
@ -59,6 +61,7 @@ int SaveFileYesNoCancel(void);
|
|||
BOOL GetSaveFile(char *file, char *defExtension, char *selPattern);
|
||||
BOOL GetOpenFile(char *file, char *defExtension, char *selPattern);
|
||||
void GetAbsoluteFilename(char *file);
|
||||
void LoadAllFontFiles(void);
|
||||
|
||||
void CheckMenuById(int id, BOOL checked);
|
||||
void EnableMenuById(int id, BOOL checked);
|
||||
|
@ -94,7 +97,10 @@ void FreeAllTemporary(void);
|
|||
void *MemRealloc(void *p, int n);
|
||||
void *MemAlloc(int n);
|
||||
void MemFree(void *p);
|
||||
void vl(void); // debug function to validate
|
||||
void vl(void); // debug function to validate heaps
|
||||
|
||||
// End of platform-specific functions
|
||||
//================
|
||||
|
||||
|
||||
#include "dsc.h"
|
||||
|
@ -208,6 +214,86 @@ public:
|
|||
void Solve(Group *g);
|
||||
};
|
||||
|
||||
class TtfFont {
|
||||
public:
|
||||
typedef struct {
|
||||
bool onCurve;
|
||||
bool lastInContour;
|
||||
SWORD x;
|
||||
SWORD y;
|
||||
} FontPoint;
|
||||
|
||||
typedef struct {
|
||||
FontPoint *pt;
|
||||
int pts;
|
||||
|
||||
int xMax;
|
||||
int xMin;
|
||||
int leftSideBearing;
|
||||
int advanceWidth;
|
||||
} Glyph;
|
||||
|
||||
typedef struct {
|
||||
int x, y;
|
||||
} IntPoint;
|
||||
|
||||
char fontFile[MAX_PATH];
|
||||
NameStr name;
|
||||
bool loaded;
|
||||
|
||||
// The font itself, plus the mapping from ASCII codes to glyphs
|
||||
int useGlyph[256];
|
||||
Glyph *glyph;
|
||||
int glyphs;
|
||||
|
||||
int maxPoints;
|
||||
int scale;
|
||||
|
||||
// The filehandle, while loading
|
||||
FILE *fh;
|
||||
// Some state while rendering a character to curves
|
||||
static const int NOTHING = 0;
|
||||
static const int ON_CURVE = 1;
|
||||
static const int OFF_CURVE = 2;
|
||||
int lastWas;
|
||||
IntPoint lastOnCurve;
|
||||
IntPoint lastOffCurve;
|
||||
|
||||
// And the state that the caller must specify, determines where we
|
||||
// render to and how
|
||||
hEntity entity;
|
||||
Vector origin, u, v;
|
||||
|
||||
int Getc(void);
|
||||
int GetBYTE(void);
|
||||
int GetWORD(void);
|
||||
int GetDWORD(void);
|
||||
|
||||
void LoadGlyph(int index);
|
||||
bool LoadFontFromFile(bool nameOnly);
|
||||
char *FontFileBaseName(void);
|
||||
|
||||
void Flush(void);
|
||||
void Handle(int *dx, int x, int y, bool onCurve);
|
||||
void PlotCharacter(int *dx, int c, double spacing);
|
||||
void PlotString(char *str, double spacing,
|
||||
hEntity he, 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;
|
||||
SList<TtfFont> l;
|
||||
|
||||
void LoadAll(void);
|
||||
|
||||
void PlotString(char *font, char *str, double spacing,
|
||||
hEntity he, Vector origin, Vector u, Vector v);
|
||||
};
|
||||
|
||||
class SolveSpace {
|
||||
public:
|
||||
|
@ -337,6 +423,9 @@ public:
|
|||
// The system to be solved.
|
||||
System sys;
|
||||
|
||||
// All the TrueType fonts in memory
|
||||
TtfFontList fonts;
|
||||
|
||||
// Everything has been pruned, so we know there's no dangling references
|
||||
// to entities that don't exist. Before that, we mustn't try to display
|
||||
// the sketch!
|
||||
|
|
|
@ -684,7 +684,18 @@ void TextWindow::EditControlDone(char *s) {
|
|||
SS.later.generateAll = true;
|
||||
break;
|
||||
}
|
||||
case EDIT_TTF_TEXT: {
|
||||
SS.UndoRemember();
|
||||
Request *r = SS.request.FindByIdNoOops(edit.request);
|
||||
if(r) {
|
||||
r->str.strcpy(s);
|
||||
SS.MarkGroupDirty(r->group);
|
||||
SS.later.generateAll = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
InvalidateGraphics();
|
||||
SS.later.showTW = true;
|
||||
HideTextEditControl();
|
||||
edit.meaning = EDIT_NOTHING;
|
||||
|
|
64
textwin.cpp
64
textwin.cpp
|
@ -206,10 +206,11 @@ void TextWindow::Show(void) {
|
|||
Printf(false, "");
|
||||
Printf(false, "%s", SS.GW.pending.description);
|
||||
} else if(gs.n > 0) {
|
||||
HideTextEditControl();
|
||||
if(edit.meaning != EDIT_TTF_TEXT) HideTextEditControl();
|
||||
ShowHeader(false);
|
||||
DescribeSelection();
|
||||
} else {
|
||||
if(edit.meaning == EDIT_TTF_TEXT) HideTextEditControl();
|
||||
ShowHeader(true);
|
||||
switch(shown->screen) {
|
||||
default:
|
||||
|
@ -229,6 +230,36 @@ void TextWindow::ScreenUnselectAll(int link, DWORD v) {
|
|||
GraphicsWindow::MenuEdit(GraphicsWindow::MNU_UNSELECT_ALL);
|
||||
}
|
||||
|
||||
void TextWindow::ScreenEditTtfText(int link, DWORD v) {
|
||||
hRequest hr = { v };
|
||||
Request *r = SS.GetRequest(hr);
|
||||
|
||||
ShowTextEditControl(13, 10, r->str.str);
|
||||
SS.TW.edit.meaning = EDIT_TTF_TEXT;
|
||||
SS.TW.edit.request = hr;
|
||||
}
|
||||
|
||||
void TextWindow::ScreenSetTtfFont(int link, DWORD v) {
|
||||
int i = (int)v;
|
||||
if(i < 0) return;
|
||||
if(i >= SS.fonts.l.n) return;
|
||||
|
||||
SS.GW.GroupSelection();
|
||||
if(gs.entities != 1 || gs.n != 1) return;
|
||||
|
||||
Entity *e = SS.entity.FindByIdNoOops(gs.entity[0]);
|
||||
if(!e || e->type != Entity::TTF_TEXT || !e->h.isFromRequest()) return;
|
||||
|
||||
Request *r = SS.request.FindByIdNoOops(e->h.request());
|
||||
if(!r) return;
|
||||
|
||||
SS.UndoRemember();
|
||||
r->font.strcpy(SS.fonts.l.elem[i].FontFileBaseName());
|
||||
SS.MarkGroupDirty(r->group);
|
||||
SS.later.generateAll = true;
|
||||
SS.later.showTW = true;
|
||||
}
|
||||
|
||||
void TextWindow::DescribeSelection(void) {
|
||||
Entity *e;
|
||||
Vector p;
|
||||
|
@ -331,6 +362,35 @@ void TextWindow::DescribeSelection(void) {
|
|||
Printf(false, " thru = " PT_AS_STR, COSTR(p));
|
||||
break;
|
||||
|
||||
case Entity::TTF_TEXT: {
|
||||
Printf(false, "%FtTRUETYPE FONT TEXT%E");
|
||||
Printf(true, " font = '%Fi%s%E'", e->font.str);
|
||||
if(e->h.isFromRequest()) {
|
||||
Printf(false, " text = '%Fi%s%E' %Fl%Ll%f%D[change]%E",
|
||||
e->str.str, &ScreenEditTtfText, e->h.request());
|
||||
Printf(true, " select new font");
|
||||
SS.fonts.LoadAll();
|
||||
int i;
|
||||
for(i = 0; i < SS.fonts.l.n; i++) {
|
||||
TtfFont *tf = &(SS.fonts.l.elem[i]);
|
||||
if(strcmp(e->font.str, tf->FontFileBaseName())==0) {
|
||||
Printf(false, "%Bp %s",
|
||||
(i & 1) ? 'd' : 'a',
|
||||
tf->name.str);
|
||||
} else {
|
||||
Printf(false, "%Bp %f%D%Fl%Ll%s%E%Bp",
|
||||
(i & 1) ? 'd' : 'a',
|
||||
&ScreenSetTtfFont, i,
|
||||
tf->name.str,
|
||||
(i & 1) ? 'd' : 'a');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Printf(false, " text = '%Fi%s%E'", e->str.str);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
Printf(true, "%Ft?? ENTITY%E");
|
||||
break;
|
||||
|
@ -340,7 +400,7 @@ void TextWindow::DescribeSelection(void) {
|
|||
Printf(false, "");
|
||||
Printf(false, "%FtIN GROUP%E %s", g->DescriptionString());
|
||||
if(e->workplane.v == Entity::FREE_IN_3D.v) {
|
||||
Printf(false, "%FtNO WORKPLANE (FREE IN 3D)%E");
|
||||
Printf(false, "%FtNOT LOCKED IN WORKPLANE%E");
|
||||
} else {
|
||||
Entity *w = SS.GetEntity(e->workplane);
|
||||
Printf(false, "%FtIN WORKPLANE%E %s", w->DescriptionString());
|
||||
|
|
|
@ -0,0 +1,712 @@
|
|||
#include "solvespace.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Get the list of available font filenames, and load the name for each of
|
||||
// them. Only that, though, not the glyphs too.
|
||||
//-----------------------------------------------------------------------------
|
||||
void TtfFontList::LoadAll(void) {
|
||||
if(loaded) return;
|
||||
|
||||
// Get the list of font files from the platform-specific code.
|
||||
LoadAllFontFiles();
|
||||
|
||||
int i;
|
||||
for(i = 0; i < l.n; i++) {
|
||||
TtfFont *tf = &(l.elem[i]);
|
||||
tf->LoadFontFromFile(true);
|
||||
}
|
||||
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
void TtfFontList::PlotString(char *font, char *str, double spacing,
|
||||
hEntity he, Vector origin, Vector u, Vector v)
|
||||
{
|
||||
LoadAll();
|
||||
|
||||
int i;
|
||||
for(i = 0; i < l.n; i++) {
|
||||
TtfFont *tf = &(l.elem[i]);
|
||||
if(strcmp(tf->FontFileBaseName(), font)==0) {
|
||||
tf->LoadFontFromFile(false);
|
||||
tf->PlotString(str, spacing, he, origin, u, v);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Couldn't find the font; so draw a big X for an error marker.
|
||||
Entity *e = SS.GetEntity(he);
|
||||
e->LineDrawOrGetDistanceOrEdge(origin, origin.Plus(u).Plus(v));
|
||||
e->LineDrawOrGetDistanceOrEdge(origin.Plus(v), origin.Plus(u));
|
||||
}
|
||||
|
||||
|
||||
//=============================================================================
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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 < 0) {
|
||||
throw "EOF";
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers to get 1, 2, or 4 bytes from the .ttf file. Big endian.
|
||||
//-----------------------------------------------------------------------------
|
||||
int TtfFont::GetBYTE(void) {
|
||||
return Getc();
|
||||
}
|
||||
int TtfFont::GetWORD(void) {
|
||||
BYTE b0, b1;
|
||||
b1 = Getc();
|
||||
b0 = Getc();
|
||||
|
||||
return (b1 << 8) | b0;
|
||||
}
|
||||
int TtfFont::GetDWORD(void) {
|
||||
BYTE b0, b1, b2, b3;
|
||||
b3 = Getc();
|
||||
b2 = Getc();
|
||||
b1 = Getc();
|
||||
b0 = Getc();
|
||||
|
||||
return (b3 << 24) | (b2 << 16) | (b1 << 8) | b0;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Load a glyph from the .ttf file into memory. Assumes that the .ttf file
|
||||
// is already seeked to the correct location, and writes the result to
|
||||
// glyphs[index]
|
||||
//-----------------------------------------------------------------------------
|
||||
void TtfFont::LoadGlyph(int index) {
|
||||
if(index < 0 || index >= glyphs) return;
|
||||
|
||||
int i;
|
||||
|
||||
SWORD contours = GetWORD();
|
||||
SWORD xMin = GetWORD();
|
||||
SWORD yMin = GetWORD();
|
||||
SWORD xMax = GetWORD();
|
||||
SWORD yMax = GetWORD();
|
||||
|
||||
if(useGlyph['A'] == index) {
|
||||
scale = (1024*1024) / yMax;
|
||||
}
|
||||
|
||||
if(contours > 0) {
|
||||
WORD *endPointsOfContours =
|
||||
(WORD *)AllocTemporary(contours*sizeof(WORD));
|
||||
|
||||
for(i = 0; i < contours; i++) {
|
||||
endPointsOfContours[i] = GetWORD();
|
||||
}
|
||||
WORD totalPts = endPointsOfContours[i-1] + 1;
|
||||
|
||||
WORD instructionLength = GetWORD();
|
||||
for(i = 0; i < instructionLength; i++) {
|
||||
// We can ignore the instructions, since we're doing vector
|
||||
// output.
|
||||
(void)GetBYTE();
|
||||
}
|
||||
|
||||
BYTE *flags = (BYTE *)AllocTemporary(totalPts*sizeof(BYTE));
|
||||
SWORD *x = (SWORD *)AllocTemporary(totalPts*sizeof(SWORD));
|
||||
SWORD *y = (SWORD *)AllocTemporary(totalPts*sizeof(SWORD));
|
||||
|
||||
// Flags, that indicate format of the coordinates
|
||||
#define FLAG_ON_CURVE (1 << 0)
|
||||
#define FLAG_DX_IS_BYTE (1 << 1)
|
||||
#define FLAG_DY_IS_BYTE (1 << 2)
|
||||
#define FLAG_REPEAT (1 << 3)
|
||||
#define FLAG_X_IS_SAME (1 << 4)
|
||||
#define FLAG_X_IS_POSITIVE (1 << 4)
|
||||
#define FLAG_Y_IS_SAME (1 << 5)
|
||||
#define FLAG_Y_IS_POSITIVE (1 << 5)
|
||||
for(i = 0; i < totalPts; i++) {
|
||||
flags[i] = GetBYTE();
|
||||
if(flags[i] & FLAG_REPEAT) {
|
||||
int n = GetBYTE();
|
||||
BYTE f = flags[i];
|
||||
int j;
|
||||
for(j = 0; j < n; j++) {
|
||||
i++;
|
||||
if(i >= totalPts) {
|
||||
throw "too many points in glyph";
|
||||
}
|
||||
flags[i] = f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// x coordinates
|
||||
SWORD xa = 0;
|
||||
for(i = 0; i < totalPts; i++) {
|
||||
if(flags[i] & FLAG_DX_IS_BYTE) {
|
||||
BYTE v = GetBYTE();
|
||||
if(flags[i] & FLAG_X_IS_POSITIVE) {
|
||||
xa += v;
|
||||
} else {
|
||||
xa -= v;
|
||||
}
|
||||
} else {
|
||||
if(flags[i] & FLAG_X_IS_SAME) {
|
||||
// no change
|
||||
} else {
|
||||
SWORD d = GetWORD();
|
||||
xa += d;
|
||||
}
|
||||
}
|
||||
x[i] = xa;
|
||||
}
|
||||
|
||||
// y coordinates
|
||||
SWORD ya = 0;
|
||||
for(i = 0; i < totalPts; i++) {
|
||||
if(flags[i] & FLAG_DY_IS_BYTE) {
|
||||
BYTE v = GetBYTE();
|
||||
if(flags[i] & FLAG_Y_IS_POSITIVE) {
|
||||
ya += v;
|
||||
} else {
|
||||
ya -= v;
|
||||
}
|
||||
} else {
|
||||
if(flags[i] & FLAG_Y_IS_SAME) {
|
||||
// no change
|
||||
} else {
|
||||
SWORD d = GetWORD();
|
||||
ya += d;
|
||||
}
|
||||
}
|
||||
y[i] = ya;
|
||||
}
|
||||
|
||||
Glyph *g = &(glyph[index]);
|
||||
g->pt = (FontPoint *)MemAlloc(totalPts*sizeof(FontPoint));
|
||||
int contour = 0;
|
||||
for(i = 0; i < totalPts; i++) {
|
||||
g->pt[i].x = x[i];
|
||||
g->pt[i].y = y[i];
|
||||
g->pt[i].onCurve = (BYTE)(flags[i] & FLAG_ON_CURVE);
|
||||
|
||||
if(i == endPointsOfContours[contour]) {
|
||||
g->pt[i].lastInContour = true;
|
||||
contour++;
|
||||
} else {
|
||||
g->pt[i].lastInContour = false;
|
||||
}
|
||||
}
|
||||
g->pts = totalPts;
|
||||
g->xMax = xMax;
|
||||
g->xMin = xMin;
|
||||
|
||||
} else {
|
||||
// This is a composite glyph, TODO.
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Return the basename of our font filename; that's how the requests and
|
||||
// entities that reference us will store it.
|
||||
//-----------------------------------------------------------------------------
|
||||
char *TtfFont::FontFileBaseName(void) {
|
||||
char *sb = strrchr(fontFile, '\\');
|
||||
char *sf = strrchr(fontFile, '/');
|
||||
char *s = sf ? sf : sb;
|
||||
if(!s) return "";
|
||||
return s + 1;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Load a TrueType font into memory. We care about the curves that define
|
||||
// the letter shapes, and about the mappings that determine which glyph goes
|
||||
// with which character.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool TtfFont::LoadFontFromFile(bool nameOnly) {
|
||||
if(loaded) return true;
|
||||
|
||||
int i;
|
||||
|
||||
fh = fopen(fontFile, "rb");
|
||||
if(!fh) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// First, load the Offset Table
|
||||
DWORD version = GetDWORD();
|
||||
WORD numTables = GetWORD();
|
||||
WORD searchRange = GetWORD();
|
||||
WORD entrySelector = GetWORD();
|
||||
WORD rangeShift = GetWORD();
|
||||
|
||||
// Now load the Table Directory; our goal in doing this will be to
|
||||
// find the addresses of the tables that we will need.
|
||||
DWORD glyfAddr = -1, glyfLen;
|
||||
DWORD cmapAddr = -1, cmapLen;
|
||||
DWORD headAddr = -1, headLen;
|
||||
DWORD locaAddr = -1, locaLen;
|
||||
DWORD maxpAddr = -1, maxpLen;
|
||||
DWORD nameAddr = -1, nameLen;
|
||||
DWORD hmtxAddr = -1, hmtxLen;
|
||||
DWORD hheaAddr = -1, hheaLen;
|
||||
|
||||
for(i = 0; i < numTables; i++) {
|
||||
char tag[5] = "xxxx";
|
||||
tag[0] = GetBYTE();
|
||||
tag[1] = GetBYTE();
|
||||
tag[2] = GetBYTE();
|
||||
tag[3] = GetBYTE();
|
||||
DWORD checksum = GetDWORD();
|
||||
DWORD offset = GetDWORD();
|
||||
DWORD length = GetDWORD();
|
||||
|
||||
if(strcmp(tag, "glyf")==0) {
|
||||
glyfAddr = offset;
|
||||
glyfLen = length;
|
||||
} else if(strcmp(tag, "cmap")==0) {
|
||||
cmapAddr = offset;
|
||||
cmapLen = length;
|
||||
} else if(strcmp(tag, "head")==0) {
|
||||
headAddr = offset;
|
||||
headLen = length;
|
||||
} else if(strcmp(tag, "loca")==0) {
|
||||
locaAddr = offset;
|
||||
locaLen = length;
|
||||
} else if(strcmp(tag, "maxp")==0) {
|
||||
maxpAddr = offset;
|
||||
maxpLen = length;
|
||||
} else if(strcmp(tag, "name")==0) {
|
||||
nameAddr = offset;
|
||||
nameLen = length;
|
||||
} else if(strcmp(tag, "hhea")==0) {
|
||||
hheaAddr = offset;
|
||||
hheaLen = length;
|
||||
} else if(strcmp(tag, "hmtx")==0) {
|
||||
hmtxAddr = offset;
|
||||
hmtxLen = length;
|
||||
}
|
||||
}
|
||||
|
||||
if(glyfAddr == -1 || cmapAddr == -1 || headAddr == -1 ||
|
||||
locaAddr == -1 || maxpAddr == -1 || hmtxAddr == -1 ||
|
||||
nameAddr == -1 || hheaAddr == -1)
|
||||
{
|
||||
throw "missing table addr";
|
||||
}
|
||||
|
||||
// Load the name table. This gives us display names for the font, which
|
||||
// we need when we're giving the user a list to choose from.
|
||||
fseek(fh, nameAddr, SEEK_SET);
|
||||
|
||||
WORD nameFormat = GetWORD();
|
||||
WORD nameCount = GetWORD();
|
||||
WORD nameStringOffset = GetWORD();
|
||||
// And now we're at the name records. Go through those till we find
|
||||
// one that we want.
|
||||
int displayNameOffset, displayNameLength;
|
||||
for(i = 0; i < nameCount; i++) {
|
||||
WORD platformID = GetWORD();
|
||||
WORD encodingID = GetWORD();
|
||||
WORD languageID = GetWORD();
|
||||
WORD nameId = GetWORD();
|
||||
WORD length = GetWORD();
|
||||
WORD offset = GetWORD();
|
||||
|
||||
if(nameId == 4) {
|
||||
displayNameOffset = offset;
|
||||
displayNameLength = length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(nameOnly && i >= nameCount) {
|
||||
throw "no name";
|
||||
}
|
||||
|
||||
if(nameOnly) {
|
||||
// Find the display name, and store it in the provided buffer.
|
||||
fseek(fh, nameAddr+nameStringOffset+displayNameOffset, SEEK_SET);
|
||||
int c = 0;
|
||||
for(i = 0; i < displayNameLength; i++) {
|
||||
BYTE b = GetBYTE();
|
||||
if(b && c < (sizeof(name.str) - 2)) {
|
||||
name.str[c++] = b;
|
||||
}
|
||||
}
|
||||
name.str[c++] = '\0';
|
||||
|
||||
fclose(fh);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Load the head table; we need this to determine the format of the
|
||||
// loca table, 16- or 32-bit entries
|
||||
fseek(fh, headAddr, SEEK_SET);
|
||||
|
||||
DWORD headVersion = GetDWORD();
|
||||
DWORD headFontRevision = GetDWORD();
|
||||
DWORD headCheckSumAdj = GetDWORD();
|
||||
DWORD headMagicNumber = GetDWORD();
|
||||
WORD headFlags = GetWORD();
|
||||
WORD headUnitsPerEm = GetWORD();
|
||||
(void)GetDWORD(); // created time
|
||||
(void)GetDWORD();
|
||||
(void)GetDWORD(); // modified time
|
||||
(void)GetDWORD();
|
||||
WORD headXmin = GetWORD();
|
||||
WORD headYmin = GetWORD();
|
||||
WORD headXmax = GetWORD();
|
||||
WORD headYmax = GetWORD();
|
||||
WORD headMacStyle = GetWORD();
|
||||
WORD headLowestRecPPEM = GetWORD();
|
||||
WORD headFontDirectionHint = GetWORD();
|
||||
WORD headIndexToLocFormat = GetWORD();
|
||||
WORD headGlyphDataFormat = GetWORD();
|
||||
|
||||
if(headMagicNumber != 0x5F0F3CF5) {
|
||||
throw "bad magic number";
|
||||
}
|
||||
|
||||
// Load the hhea table, which contains the number of entries in the
|
||||
// horizontal metrics (hmtx) table.
|
||||
fseek(fh, hheaAddr, SEEK_SET);
|
||||
DWORD hheaVersion = GetDWORD();
|
||||
WORD hheaAscender = GetWORD();
|
||||
WORD hheaDescender = GetWORD();
|
||||
WORD hheaLineGap = GetWORD();
|
||||
WORD hheaAdvanceWidthMax = GetWORD();
|
||||
WORD hheaMinLsb = GetWORD();
|
||||
WORD hheaMinRsb = GetWORD();
|
||||
WORD hheaXMaxExtent = GetWORD();
|
||||
WORD hheaCaretSlopeRise = GetWORD();
|
||||
WORD hheaCaretSlopeRun = GetWORD();
|
||||
WORD hheaCaretOffset = GetWORD();
|
||||
(void)GetWORD();
|
||||
(void)GetWORD();
|
||||
(void)GetWORD();
|
||||
(void)GetWORD();
|
||||
WORD hheaMetricDataFormat = GetWORD();
|
||||
WORD hheaNumberOfMetrics = GetWORD();
|
||||
|
||||
// Load the maxp table, which determines (among other things) the number
|
||||
// of glyphs in the font
|
||||
fseek(fh, maxpAddr, SEEK_SET);
|
||||
|
||||
DWORD maxpVersion = GetDWORD();
|
||||
WORD maxpNumGlyphs = GetWORD();
|
||||
WORD maxpMaxPoints = GetWORD();
|
||||
WORD maxpMaxContours = GetWORD();
|
||||
WORD maxpMaxComponentPoints = GetWORD();
|
||||
WORD maxpMaxComponentContours = GetWORD();
|
||||
WORD maxpMaxZones = GetWORD();
|
||||
WORD maxpMaxTwilightPoints = GetWORD();
|
||||
WORD maxpMaxStorage = GetWORD();
|
||||
WORD maxpMaxFunctionDefs = GetWORD();
|
||||
WORD maxpMaxInstructionDefs = GetWORD();
|
||||
WORD maxpMaxStackElements = GetWORD();
|
||||
WORD maxpMaxSizeOfInstructions = GetWORD();
|
||||
WORD maxpMaxComponentElements = GetWORD();
|
||||
WORD maxpMaxComponentDepth = GetWORD();
|
||||
|
||||
glyphs = maxpNumGlyphs;
|
||||
glyph = (Glyph *)MemAlloc(glyphs*sizeof(glyph[0]));
|
||||
|
||||
// Load the hmtx table, which gives the horizontal metrics (spacing
|
||||
// and advance width) of the font.
|
||||
fseek(fh, hmtxAddr, SEEK_SET);
|
||||
|
||||
WORD hmtxAdvanceWidth;
|
||||
SWORD hmtxLsb;
|
||||
for(i = 0; i < min(glyphs, hheaNumberOfMetrics); i++) {
|
||||
hmtxAdvanceWidth = GetWORD();
|
||||
hmtxLsb = (SWORD)GetWORD();
|
||||
|
||||
glyph[i].leftSideBearing = hmtxLsb;
|
||||
glyph[i].advanceWidth = hmtxAdvanceWidth;
|
||||
}
|
||||
// The last entry in the table applies to all subsequent glyphs also.
|
||||
for(; i < glyphs; i++) {
|
||||
glyph[i].leftSideBearing = hmtxLsb;
|
||||
glyph[i].advanceWidth = hmtxAdvanceWidth;
|
||||
}
|
||||
|
||||
// Load the cmap table, which determines the mapping of characters to
|
||||
// glyphs.
|
||||
fseek(fh, cmapAddr, SEEK_SET);
|
||||
|
||||
DWORD usedTableAddr = -1;
|
||||
|
||||
WORD cmapVersion = GetWORD();
|
||||
WORD cmapTableCount = GetWORD();
|
||||
for(i = 0; i < cmapTableCount; i++) {
|
||||
WORD platformId = GetWORD();
|
||||
WORD encodingId = GetWORD();
|
||||
DWORD offset = GetDWORD();
|
||||
|
||||
if(platformId == 3 && encodingId == 1) {
|
||||
// The Windows Unicode mapping is our preference
|
||||
usedTableAddr = cmapAddr + offset;
|
||||
}
|
||||
}
|
||||
|
||||
if(usedTableAddr == -1) {
|
||||
throw "no used table addr";
|
||||
}
|
||||
|
||||
// So we can load the desired subtable; in this case, Windows Unicode,
|
||||
// which is us.
|
||||
fseek(fh, usedTableAddr, SEEK_SET);
|
||||
|
||||
WORD mapFormat = GetWORD();
|
||||
WORD mapLength = GetWORD();
|
||||
WORD mapVersion = GetWORD();
|
||||
WORD mapSegCountX2 = GetWORD();
|
||||
WORD mapSearchRange = GetWORD();
|
||||
WORD mapEntrySelector = GetWORD();
|
||||
WORD mapRangeShift = GetWORD();
|
||||
|
||||
if(mapFormat != 4) {
|
||||
// Required to use format 4 per spec
|
||||
throw "not format 4";
|
||||
}
|
||||
|
||||
int segCount = mapSegCountX2 / 2;
|
||||
WORD *endChar = (WORD *)AllocTemporary(segCount*sizeof(WORD));
|
||||
WORD *startChar = (WORD *)AllocTemporary(segCount*sizeof(WORD));
|
||||
WORD *idDelta = (WORD *)AllocTemporary(segCount*sizeof(WORD));
|
||||
WORD *idRangeOffset = (WORD *)AllocTemporary(segCount*sizeof(WORD));
|
||||
|
||||
DWORD *filePos = (DWORD *)AllocTemporary(segCount*sizeof(DWORD));
|
||||
|
||||
for(i = 0; i < segCount; i++) {
|
||||
endChar[i] = GetWORD();
|
||||
}
|
||||
WORD mapReservedPad = GetWORD();
|
||||
for(i = 0; i < segCount; i++) {
|
||||
startChar[i] = GetWORD();
|
||||
}
|
||||
for(i = 0; i < segCount; i++) {
|
||||
idDelta[i] = GetWORD();
|
||||
}
|
||||
for(i = 0; i < segCount; i++) {
|
||||
filePos[i] = ftell(fh);
|
||||
idRangeOffset[i] = GetWORD();
|
||||
}
|
||||
|
||||
// So first, null out the glyph table in our in-memory representation
|
||||
// of the font; any character for which cmap does not provide a glyph
|
||||
// corresponds to -1
|
||||
for(i = 0; i < arraylen(useGlyph); i++) {
|
||||
useGlyph[i] = 0;
|
||||
}
|
||||
|
||||
for(i = 0; i < segCount; i++) {
|
||||
WORD v = idDelta[i];
|
||||
if(idRangeOffset[i] == 0) {
|
||||
int j;
|
||||
for(j = startChar[i]; j <= endChar[i]; j++) {
|
||||
if(j > 0 && j < arraylen(useGlyph)) {
|
||||
// Don't create a reference to a glyph that we won't
|
||||
// store because it's bigger than the table.
|
||||
if((WORD)(j + v) < glyphs) {
|
||||
// Arithmetic is modulo 2^16
|
||||
useGlyph[j] = (WORD)(j + v);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int j;
|
||||
for(j = startChar[i]; j <= endChar[i]; j++) {
|
||||
if(j > 0 && j < arraylen(useGlyph)) {
|
||||
int fp = filePos[i];
|
||||
fp += (j - startChar[i])*sizeof(WORD);
|
||||
fp += idRangeOffset[i];
|
||||
fseek(fh, fp, SEEK_SET);
|
||||
|
||||
useGlyph[j] = GetWORD();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load the loca table. This contains the offsets of each glyph,
|
||||
// relative to the beginning of the glyf table.
|
||||
fseek(fh, locaAddr, SEEK_SET);
|
||||
|
||||
DWORD *glyphOffsets = (DWORD *)AllocTemporary(glyphs*sizeof(DWORD));
|
||||
|
||||
for(i = 0; i < glyphs; i++) {
|
||||
if(headIndexToLocFormat == 1) {
|
||||
// long offsets, 32 bits
|
||||
glyphOffsets[i] = GetDWORD();
|
||||
} else if(headIndexToLocFormat == 0) {
|
||||
// short offsets, 16 bits but divided by 2
|
||||
glyphOffsets[i] = GetWORD()*2;
|
||||
} else {
|
||||
throw "bad headIndexToLocFormat";
|
||||
}
|
||||
}
|
||||
|
||||
scale = 1024;
|
||||
// Load the glyf table. This contains the actual representations of the
|
||||
// letter forms, as piecewise linear or quadratic outlines.
|
||||
for(i = 0; i < glyphs; i++) {
|
||||
fseek(fh, glyfAddr + glyphOffsets[i], SEEK_SET);
|
||||
LoadGlyph(i);
|
||||
}
|
||||
} catch (char *s) {
|
||||
dbp("failed: '%s'", s);
|
||||
fclose(fh);
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(fh);
|
||||
loaded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void TtfFont::Flush(void) {
|
||||
lastWas = NOTHING;
|
||||
}
|
||||
|
||||
void TtfFont::Handle(int *dx, int x, int y, bool onCurve) {
|
||||
x = ((x + *dx)*scale + 512) >> 10;
|
||||
y = (y*scale + 512) >> 10;
|
||||
|
||||
if(lastWas == ON_CURVE && onCurve) {
|
||||
// This is a line segment.
|
||||
LineSegment(lastOnCurve.x, lastOnCurve.y, x, y);
|
||||
} else if(lastWas == ON_CURVE && !onCurve) {
|
||||
// We can't do the Bezier until we get the next on-curve point,
|
||||
// but we must store the off-curve point.
|
||||
} else if(lastWas == OFF_CURVE && onCurve) {
|
||||
// We are ready to do a Bezier.
|
||||
Bezier(lastOnCurve.x, lastOnCurve.y,
|
||||
lastOffCurve.x, lastOffCurve.y,
|
||||
x, y);
|
||||
} else if(lastWas == OFF_CURVE && !onCurve) {
|
||||
// Two consecutive off-curve points implicitly have an on-point
|
||||
// curve between them, and that should trigger us to generate a
|
||||
// Bezier.
|
||||
IntPoint fake;
|
||||
fake.x = (x + lastOffCurve.x) / 2;
|
||||
fake.y = (y + lastOffCurve.y) / 2;
|
||||
Bezier(lastOnCurve.x, lastOnCurve.y,
|
||||
lastOffCurve.x, lastOffCurve.y,
|
||||
fake.x, fake.y);
|
||||
|
||||
lastOnCurve.x = fake.x;
|
||||
lastOnCurve.y = fake.y;
|
||||
}
|
||||
|
||||
if(onCurve) {
|
||||
lastOnCurve.x = x;
|
||||
lastOnCurve.y = y;
|
||||
lastWas = ON_CURVE;
|
||||
} else {
|
||||
lastOffCurve.x = x;
|
||||
lastOffCurve.y = y;
|
||||
lastWas = OFF_CURVE;
|
||||
}
|
||||
}
|
||||
|
||||
void TtfFont::PlotCharacter(int *dx, int c, double spacing) {
|
||||
int gli = useGlyph[c];
|
||||
|
||||
if(gli < 0 || gli >= glyphs) return;
|
||||
Glyph *g = &(glyph[gli]);
|
||||
if(!g->pt) return;
|
||||
|
||||
if(c == ' ') {
|
||||
*dx += g->advanceWidth;
|
||||
return;
|
||||
}
|
||||
|
||||
int dx0 = *dx;
|
||||
|
||||
// A point that has x = xMin should be plotted at (dx0 + lsb); fix up
|
||||
// our x-position so that the curve-generating code will put stuff
|
||||
// at the right place.
|
||||
*dx = dx0 - g->xMin;
|
||||
*dx += g->leftSideBearing;
|
||||
|
||||
int i;
|
||||
int firstInContour = 0;
|
||||
for(i = 0; i < g->pts; i++) {
|
||||
Handle(dx, g->pt[i].x, g->pt[i].y, g->pt[i].onCurve);
|
||||
|
||||
if(g->pt[i].lastInContour) {
|
||||
int f = firstInContour;
|
||||
Handle(dx, g->pt[f].x, g->pt[f].y, g->pt[f].onCurve);
|
||||
firstInContour = i + 1;
|
||||
Flush();
|
||||
}
|
||||
}
|
||||
|
||||
// And we're done, so advance our position by the requested advance
|
||||
// width, plus the user-requested extra advance.
|
||||
*dx = dx0 + g->advanceWidth + (int)(spacing + 0.5);
|
||||
}
|
||||
|
||||
void TtfFont::PlotString(char *str, double spacing,
|
||||
hEntity he, Vector porigin, Vector pu, Vector pv)
|
||||
{
|
||||
entity = he;
|
||||
u = pu;
|
||||
v = pv;
|
||||
origin = porigin;
|
||||
|
||||
if(!loaded || !str || *str == '\0') {
|
||||
LineSegment(0, 0, 1024, 0);
|
||||
LineSegment(1024, 0, 1024, 1024);
|
||||
LineSegment(1024, 1024, 0, 1024);
|
||||
LineSegment(0, 1024, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
int dx = 0;
|
||||
|
||||
while(*str) {
|
||||
PlotCharacter(&dx, *str, spacing);
|
||||
str++;
|
||||
}
|
||||
}
|
||||
|
||||
Vector TtfFont::TransformIntPoint(int x, int y) {
|
||||
Vector r = origin;
|
||||
r = r.Plus(u.ScaledBy(x / 1024.0));
|
||||
r = r.Plus(v.ScaledBy(y / 1024.0));
|
||||
return r;
|
||||
}
|
||||
|
||||
void TtfFont::LineSegment(int x0, int y0, int x1, int y1) {
|
||||
Entity *e = SS.GetEntity(entity);
|
||||
e->LineDrawOrGetDistanceOrEdge(TransformIntPoint(x0, y0),
|
||||
TransformIntPoint(x1, y1));
|
||||
}
|
||||
|
||||
void TtfFont::Bezier(int x0, int y0, int x1, int y1, int x2, int y2) {
|
||||
Entity *e = SS.GetEntity(entity);
|
||||
|
||||
Vector p0 = TransformIntPoint(x0, y0),
|
||||
p1 = TransformIntPoint(x1, y1),
|
||||
p2 = TransformIntPoint(x2, y2);
|
||||
|
||||
int i, n = max(2, (int)(4/sqrt(SS.meshTol)));
|
||||
Vector prev = p0;
|
||||
for(i = 1; i <= n; i++) {
|
||||
double t = ((double)i)/n;
|
||||
Vector p =
|
||||
(p0.ScaledBy((1 - t)*(1 - t))).Plus(
|
||||
(p1.ScaledBy(2*t*(1 - t))).Plus(
|
||||
(p2.ScaledBy(t*t))));
|
||||
e->LineDrawOrGetDistanceOrEdge(prev, p);
|
||||
prev = p;
|
||||
}
|
||||
}
|
||||
|
20
ui.h
20
ui.h
|
@ -5,7 +5,7 @@
|
|||
class TextWindow {
|
||||
public:
|
||||
static const int MAX_COLS = 100;
|
||||
static const int MAX_ROWS = 200;
|
||||
static const int MAX_ROWS = 2000;
|
||||
|
||||
#ifndef RGB
|
||||
#define RGB(r, g, b) ((r) | ((g) << 8) | ((b) << 16))
|
||||
|
@ -67,10 +67,12 @@ public:
|
|||
static const int EDIT_HELIX_TURNS = 8;
|
||||
static const int EDIT_HELIX_PITCH = 9;
|
||||
static const int EDIT_HELIX_DRADIUS = 10;
|
||||
static const int EDIT_TTF_TEXT = 11;
|
||||
struct {
|
||||
int meaning;
|
||||
int i;
|
||||
hGroup group;
|
||||
int meaning;
|
||||
int i;
|
||||
hGroup group;
|
||||
hRequest request;
|
||||
} edit;
|
||||
|
||||
static void ReportHowGroupSolved(hGroup hg);
|
||||
|
@ -89,7 +91,13 @@ public:
|
|||
|
||||
void OneScreenForwardTo(int screen);
|
||||
|
||||
// All of these are callbacks from the GUI code.
|
||||
// All of these are callbacks from the GUI code; first from when
|
||||
// we're describing an entity
|
||||
static void ScreenEditTtfText(int link, DWORD v);
|
||||
static void ScreenSetTtfFont(int link, DWORD v);
|
||||
static void ScreenUnselectAll(int link, DWORD v);
|
||||
|
||||
// and the rest from the stuff in textscreens.cpp
|
||||
static void ScreenSelectGroup(int link, DWORD v);
|
||||
static void ScreenActivateGroup(int link, DWORD v);
|
||||
static void ScreenToggleGroupShown(int link, DWORD v);
|
||||
|
@ -101,7 +109,6 @@ public:
|
|||
static void ScreenHoverRequest(int link, DWORD v);
|
||||
static void ScreenSelectRequest(int link, DWORD v);
|
||||
static void ScreenSelectConstraint(int link, DWORD v);
|
||||
static void ScreenUnselectAll(int link, DWORD v);
|
||||
|
||||
static void ScreenChangeOneOrTwoSides(int link, DWORD v);
|
||||
static void ScreenChangeSkipFirst(int link, DWORD v);
|
||||
|
@ -162,6 +169,7 @@ public:
|
|||
MNU_ARC,
|
||||
MNU_RECTANGLE,
|
||||
MNU_CUBIC,
|
||||
MNU_TTF_TEXT,
|
||||
MNU_CONSTRUCTION,
|
||||
// Group
|
||||
MNU_GROUP_3D,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "solvespace.h"
|
||||
|
||||
void SolveSpace::UndoRemember(void) {
|
||||
unsaved = true;
|
||||
PushFromCurrentOnto(&undo);
|
||||
UndoClearStack(&redo);
|
||||
UndoEnableMenus();
|
||||
|
|
|
@ -754,6 +754,7 @@ int SaveFileYesNoCancel(void)
|
|||
|
||||
return r;
|
||||
}
|
||||
|
||||
void GetAbsoluteFilename(char *file)
|
||||
{
|
||||
char absoluteFile[MAX_PATH];
|
||||
|
@ -761,6 +762,31 @@ void GetAbsoluteFilename(char *file)
|
|||
strcpy(file, absoluteFile);
|
||||
}
|
||||
|
||||
void LoadAllFontFiles(void)
|
||||
{
|
||||
WIN32_FIND_DATA wfd;
|
||||
char dir[MAX_PATH];
|
||||
GetWindowsDirectory(dir, MAX_PATH - 30);
|
||||
strcat(dir, "\\fonts\\*.ttf");
|
||||
|
||||
HANDLE h = FindFirstFile(dir, &wfd);
|
||||
|
||||
while(h != INVALID_HANDLE_VALUE) {
|
||||
TtfFont tf;
|
||||
ZERO(&tf);
|
||||
|
||||
char fullPath[MAX_PATH];
|
||||
GetWindowsDirectory(fullPath, MAX_PATH - (30 + strlen(wfd.cFileName)));
|
||||
strcat(fullPath, "\\fonts\\");
|
||||
strcat(fullPath, wfd.cFileName);
|
||||
|
||||
strcpy(tf.fontFile, fullPath);
|
||||
SS.fonts.l.Add(&tf);
|
||||
|
||||
if(!FindNextFile(h, &wfd)) break;
|
||||
}
|
||||
}
|
||||
|
||||
static void MenuById(int id, BOOL yes, BOOL check)
|
||||
{
|
||||
int i;
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
STL check for meshes, and T intersection removal
|
||||
STL export
|
||||
DXF export
|
||||
TTF font text
|
||||
some kind of rounding / chamfer
|
||||
remove back button in browser?
|
||||
auto-generate circles and faces when lathing
|
||||
copy the section geometry to other end when sweeping
|
||||
cylindrical faces
|
||||
draw explicit edges
|
||||
|
||||
|
||||
long term
|
||||
|
|
Loading…
Reference in New Issue