Skip to content

Commit

Permalink
Add two more points to the TTF text request.
Browse files Browse the repository at this point in the history
These points can be used for constraining the width of the text
(or to the width of the text).

The main parts of the commit are:
  * TtfFont is restructured to be able to return the aspect ratio
    for a given string.
  * This aspect ratio is written to the savefile, such that even if
    the font is missing, the sketch would still be solved correctly.
  * The two additional points are constrained via perpendicularly
    to the two main points (which form a v vector).

The compatibility features are as follows:
  * When the font is missing in old files, 1:1 aspect ratio is used,
    which works for the replacement symbol anyhow.
  * When the two additional points are missing in old files, their
    would-be positions are calculated and they are moved there,
    avoiding 'jumping' of underconstrained sketches.
  • Loading branch information
whitequark committed Nov 2, 2016
1 parent 23feb4c commit 74cb1f5
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 8 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ New sketch features:
boolean operation, to increase performance.
* Translate and rotate groups can create n-dimensional arrays using
the "difference" and "assembly" boolean operations.
* TTF text request has two additional points on the right side, which allow
constraining the width of text.
* Irrelevant points (e.g. arc center point) are not counted when estimating
the bounding box used to compute chord tolerance.

Expand Down
61 changes: 61 additions & 0 deletions src/entity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,23 @@ ExprQuaternion EntityBase::NormalGetExprs() const {
return q;
}

void EntityBase::PointForceParamTo(Vector p) {
switch(type) {
case Type::POINT_IN_3D:
SK.GetParam(param[0])->val = p.x;
SK.GetParam(param[1])->val = p.y;
SK.GetParam(param[2])->val = p.z;
break;

case Type::POINT_IN_2D:
SK.GetParam(param[0])->val = p.x;
SK.GetParam(param[1])->val = p.y;
break;

default: ssassert(false, "Unexpected entity type");
}
}

void EntityBase::PointForceTo(Vector p) {
switch(type) {
case Type::POINT_IN_3D:
Expand Down Expand Up @@ -550,6 +567,13 @@ void EntityBase::PointGetExprsInWorkplane(hEntity wrkpl, Expr **u, Expr **v) con
}
}

ExprVector EntityBase::PointGetExprsInWorkplane(hEntity wrkpl) const {
ExprVector r;
PointGetExprsInWorkplane(wrkpl, &r.x, &r.y);
r.z = Expr::From(0.0);
return r;
}

void EntityBase::PointForceQuaternionTo(Quaternion q) {
ssassert(type == Type::POINT_N_ROT_TRANS, "Unexpected entity type");

Expand Down Expand Up @@ -738,6 +762,23 @@ Vector EntityBase::EndpointFinish() const {
} else ssassert(false, "Unexpected entity type");
}

void EntityBase::TtfTextGetPointsExprs(ExprVector *eb, ExprVector *ec) const {
EntityBase *a = SK.GetEntity(point[0]);
EntityBase *o = SK.GetEntity(point[1]);

// Write equations for each point in the current workplane.
// This reduces the complexity of resulting equations.
ExprVector ea = a->PointGetExprsInWorkplane(workplane);
ExprVector eo = o->PointGetExprsInWorkplane(workplane);

// Take perpendicular vector and scale it by aspect ratio.
ExprVector eu = ea.Minus(eo);
ExprVector ev = ExprVector::From(eu.y, eu.x->Negate(), eu.z).ScaledBy(Expr::From(aspectRatio));

*eb = eo.Plus(ev);
*ec = eo.Plus(eu).Plus(ev);
}

void EntityBase::AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index) const {
Equation eq;
eq.e = expr;
Expand All @@ -752,6 +793,7 @@ void EntityBase::GenerateEquations(IdList<Equation,hEquation> *l) const {
AddEq(l, (q.Magnitude())->Minus(Expr::From(1)), 0);
break;
}

case Type::ARC_OF_CIRCLE: {
// If this is a copied entity, with its point already fixed
// with respect to each other, then we don't want to generate
Expand Down Expand Up @@ -780,6 +822,25 @@ void EntityBase::GenerateEquations(IdList<Equation,hEquation> *l) const {
AddEq(l, ra->Minus(rb), 0);
break;
}

case Type::TTF_TEXT: {
EntityBase *b = SK.GetEntity(point[2]);
EntityBase *c = SK.GetEntity(point[3]);
ExprVector eb = b->PointGetExprsInWorkplane(workplane);
ExprVector ec = c->PointGetExprsInWorkplane(workplane);

ExprVector ebp, ecp;
TtfTextGetPointsExprs(&ebp, &ecp);

ExprVector beq = eb.Minus(ebp);
AddEq(l, beq.x, 0);
AddEq(l, beq.y, 1);
ExprVector ceq = ec.Minus(ecp);
AddEq(l, ceq.x, 2);
AddEq(l, ceq.y, 3);
break;
}

default: // Most entities do not generate equations.
break;
}
Expand Down
49 changes: 49 additions & 0 deletions src/file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ const SolveSpaceUI::SaveTable SolveSpaceUI::SAVED[] = {
{ 'r', "Request.style", 'x', &(SS.sv.r.style) },
{ 'r', "Request.str", 'S', &(SS.sv.r.str) },
{ 'r', "Request.font", 'S', &(SS.sv.r.font) },
{ 'r', "Request.aspectRatio", 'f', &(SS.sv.r.aspectRatio) },

{ 'e', "Entity.h.v", 'x', &(SS.sv.e.h.v) },
{ 'e', "Entity.type", 'd', &(SS.sv.e.type) },
Expand Down Expand Up @@ -512,9 +513,57 @@ bool SolveSpaceUI::LoadFromFile(const std::string &filename) {
}
}

UpgradeLegacyData();

return true;
}

void SolveSpaceUI::UpgradeLegacyData() {
for(Request &r : SK.request) {
switch(r.type) {
// TTF text requests saved in versions prior to 3.0 only have two
// reference points (origin and origin plus v); version 3.0 adds two
// more points, and if we don't do anything, then they will appear
// at workplane origin, and the solver will mess up the sketch if
// it is not fully constrained.
case Request::Type::TTF_TEXT: {
IdList<Entity,hEntity> entity = {};
IdList<Param,hParam> param = {};
r.Generate(&entity, &param);

// If we didn't load all of the entities and params that this
// request would generate, then add them now, so that we can
// force them to their appropriate positions.
for(Param &p : param) {
if(SK.param.FindByIdNoOops(p.h) != NULL) continue;
SK.param.Add(&p);
}
bool allPointsExist = true;
for(Entity &e : entity) {
if(SK.entity.FindByIdNoOops(e.h) != NULL) continue;
SK.entity.Add(&e);
allPointsExist = false;
}

if(!allPointsExist) {
Entity *text = entity.FindById(r.h.entity(0));
Entity *b = entity.FindById(text->point[2]);
Entity *c = entity.FindById(text->point[3]);
ExprVector bex, cex;
text->TtfTextGetPointsExprs(&bex, &cex);
b->PointForceParamTo(bex.Eval());
c->PointForceParamTo(cex.Eval());
}
entity.Clear();
param.Clear();
}

default:
break;
}
}
}

bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList *le,
SMesh *m, SShell *sh)
{
Expand Down
25 changes: 23 additions & 2 deletions src/request.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ static const EntReqMapping EntReqMap[] = {
{ Request::Type::CUBIC_PERIODIC, Entity::Type::CUBIC_PERIODIC, 3, true, false, false },
{ Request::Type::CIRCLE, Entity::Type::CIRCLE, 1, false, true, true },
{ Request::Type::ARC_OF_CIRCLE, Entity::Type::ARC_OF_CIRCLE, 3, false, true, false },
{ Request::Type::TTF_TEXT, Entity::Type::TTF_TEXT, 2, false, true, false },
{ Request::Type::TTF_TEXT, Entity::Type::TTF_TEXT, 4, false, true, false },
};

static void CopyEntityInfo(const EntReqMapping *te, int extraPoints,
Expand Down Expand Up @@ -78,14 +78,34 @@ Request::Type EntReqTable::GetRequestForEntity(Entity::Type ent) {
}

void Request::Generate(IdList<Entity,hEntity> *entity,
IdList<Param,hParam> *param) const
IdList<Param,hParam> *param)
{
int points = 0;
Entity::Type et;
bool hasNormal = false;
bool hasDistance = false;
int i;

// Request-specific generation.
switch(type) {
case Type::TTF_TEXT: {
double actualAspectRatio = SS.fonts.AspectRatio(font, str);
if(EXACT(actualAspectRatio != 0.0)) {
// We could load the font, so use the actual value.
aspectRatio = actualAspectRatio;
}
if(EXACT(aspectRatio == 0.0)) {
// We couldn't load the font and we don't have anything saved,
// so just use 1:1, which is valid for the missing font symbol anyhow.
aspectRatio = 1.0;
}
break;
}

default: // most requests don't do anything else
break;
}

Entity e = {};
EntReqTable::GetRequestInfo(type, extraPoints, &et, &points, &hasNormal, &hasDistance);

Expand All @@ -98,6 +118,7 @@ void Request::Generate(IdList<Entity,hEntity> *entity,
e.construction = construction;
e.str = str;
e.font = font;
e.aspectRatio = aspectRatio;
e.h = h.entity(0);

// And generate entities for the points
Expand Down
11 changes: 9 additions & 2 deletions src/sketch.h
Original file line number Diff line number Diff line change
Expand Up @@ -316,11 +316,13 @@ class Request {
hStyle style;

bool construction;

std::string str;
std::string font;
double aspectRatio;

static hParam AddParam(ParamList *param, hParam hp);
void Generate(EntityList *entity, ParamList *param) const;
void Generate(EntityList *entity, ParamList *param);

std::string DescriptionString() const;
int IndexOfPoint(hEntity he) const;
Expand Down Expand Up @@ -391,6 +393,7 @@ class EntityBase {

std::string str;
std::string font;
double aspectRatio;

// For entities that are derived by a transformation, the number of
// times to apply the transformation.
Expand Down Expand Up @@ -434,7 +437,9 @@ class EntityBase {
Vector PointGetNum() const;
ExprVector PointGetExprs() const;
void PointGetExprsInWorkplane(hEntity wrkpl, Expr **u, Expr **v) const;
ExprVector PointGetExprsInWorkplane(hEntity wrkpl) const;
void PointForceTo(Vector v);
void PointForceParamTo(Vector v);
// These apply only the POINT_N_ROT_TRANS, which has an assoc rotation
Quaternion PointGetQuaternion() const;
void PointForceQuaternionTo(Quaternion q);
Expand Down Expand Up @@ -463,6 +468,8 @@ class EntityBase {
Vector EndpointStart() const;
Vector EndpointFinish() const;

void TtfTextGetPointsExprs(ExprVector *eap, ExprVector *ebp) const;

void AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index) const;
void GenerateEquations(IdList<Equation,hEquation> *l) const;

Expand Down Expand Up @@ -859,7 +866,7 @@ inline hRequest hEntity::request() const
inline hGroup hEntity::group() const
{ hGroup r; r.v = (v >> 16) & 0x3fff; return r; }
inline hEquation hEntity::equation(int i) const
{ hEquation r; r.v = v | 0x40000000; return r; }
{ hEquation r; r.v = v | 0x40000000 | (uint32_t)i; return r; }

inline hRequest hParam::request() const
{ hRequest r; r.v = (v >> 16); return r; }
Expand Down
1 change: 1 addition & 0 deletions src/solvespace.h
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,7 @@ class SolveSpaceUI {
bool SaveToFile(const std::string &filename);
bool LoadAutosaveFor(const std::string &filename);
bool LoadFromFile(const std::string &filename);
void UpgradeLegacyData();
bool LoadEntitiesFromFile(const std::string &filename, EntityList *le,
SMesh *m, SShell *sh);
bool ReloadAllImported(bool canCancel=false);
Expand Down
52 changes: 48 additions & 4 deletions src/ttf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,28 @@ void TtfFontList::LoadAll() {
loaded = true;
}

void TtfFontList::PlotString(const std::string &font, const std::string &str,
SBezierList *sbl, Vector origin, Vector u, Vector v)
TtfFont *TtfFontList::LoadFont(const std::string &font)
{
LoadAll();

TtfFont *tf = std::find_if(&l.elem[0], &l.elem[l.n],
TtfFont *tf = std::find_if(l.begin(), l.end(),
[&](const TtfFont &tf) { return tf.FontFileBaseName() == font; });

if(!str.empty() && tf != &l.elem[l.n]) {
if(tf != l.end()) {
if(tf->fontFace == NULL) {
tf->LoadFromFile(fontLibrary, /*nameOnly=*/false);
}
return tf;
} else {
return NULL;
}
}

void TtfFontList::PlotString(const std::string &font, const std::string &str,
SBezierList *sbl, Vector origin, Vector u, Vector v)
{
TtfFont *tf = LoadFont(font);
if(!str.empty() && tf != NULL) {
tf->PlotString(str, sbl, origin, u, v);
} else {
// No text or no font; so draw a big X for an error marker.
Expand All @@ -102,6 +112,16 @@ void TtfFontList::PlotString(const std::string &font, const std::string &str,
}
}

double TtfFontList::AspectRatio(const std::string &font, const std::string &str)
{
TtfFont *tf = LoadFont(font);
if(tf != NULL) {
return tf->AspectRatio(str);
}

return 0.0;
}

//-----------------------------------------------------------------------------
// Return the basename of our font filename; that's how the requests and
// entities that reference us will store it.
Expand Down Expand Up @@ -328,3 +348,27 @@ void TtfFont::PlotString(const std::string &str,
dx += fontFace->glyph->advance.x;
}
}

double TtfFont::AspectRatio(const std::string &str) {
ssassert(fontFace != NULL, "Expected font face to be loaded");

// We always request a unit size character, so the aspect ratio is the same as advance length.
double dx = 0;
for(char32_t chr : ReadUTF8(str)) {
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));
}

if(int fterr = FT_Load_Glyph(fontFace, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING)) {
dbp("freetype: cannot load glyph (GID 0x%04x): %s",
gid, ft_error_string(fterr));
break;
}

dx += (double)fontFace->glyph->advance.x / capHeight;
}

return dx;
}
3 changes: 3 additions & 0 deletions src/ttf.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class TtfFont {

void PlotString(const std::string &str,
SBezierList *sbl, Vector origin, Vector u, Vector v);
double AspectRatio(const std::string &str);
};

class TtfFontList {
Expand All @@ -33,9 +34,11 @@ class TtfFontList {
~TtfFontList();

void LoadAll();
TtfFont *LoadFont(const std::string &font);

void PlotString(const std::string &font, const std::string &str,
SBezierList *sbl, Vector origin, Vector u, Vector v);
double AspectRatio(const std::string &font, const std::string &str);
};

#endif
Binary file modified test/request/ttf_text/normal.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 74cb1f5

Please sign in to comment.