Add two more points to the TTF text request.

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.
pull/97/head
whitequark 2016-10-11 01:58:04 +00:00
parent 23feb4cf8f
commit 74cb1f589c
10 changed files with 233 additions and 8 deletions

View File

@ -9,6 +9,8 @@ New sketch features:
boolean operation, to increase performance. boolean operation, to increase performance.
* Translate and rotate groups can create n-dimensional arrays using * Translate and rotate groups can create n-dimensional arrays using
the "difference" and "assembly" boolean operations. 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 * Irrelevant points (e.g. arc center point) are not counted when estimating
the bounding box used to compute chord tolerance. the bounding box used to compute chord tolerance.

View File

@ -367,6 +367,23 @@ ExprQuaternion EntityBase::NormalGetExprs() const {
return q; 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) { void EntityBase::PointForceTo(Vector p) {
switch(type) { switch(type) {
case Type::POINT_IN_3D: case Type::POINT_IN_3D:
@ -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) { void EntityBase::PointForceQuaternionTo(Quaternion q) {
ssassert(type == Type::POINT_N_ROT_TRANS, "Unexpected entity type"); ssassert(type == Type::POINT_N_ROT_TRANS, "Unexpected entity type");
@ -738,6 +762,23 @@ Vector EntityBase::EndpointFinish() const {
} else ssassert(false, "Unexpected entity type"); } 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 { void EntityBase::AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index) const {
Equation eq; Equation eq;
eq.e = expr; eq.e = expr;
@ -752,6 +793,7 @@ void EntityBase::GenerateEquations(IdList<Equation,hEquation> *l) const {
AddEq(l, (q.Magnitude())->Minus(Expr::From(1)), 0); AddEq(l, (q.Magnitude())->Minus(Expr::From(1)), 0);
break; break;
} }
case Type::ARC_OF_CIRCLE: { case Type::ARC_OF_CIRCLE: {
// If this is a copied entity, with its point already fixed // If this is a copied entity, with its point already fixed
// with respect to each other, then we don't want to generate // with respect to each other, then we don't want to generate
@ -780,6 +822,25 @@ void EntityBase::GenerateEquations(IdList<Equation,hEquation> *l) const {
AddEq(l, ra->Minus(rb), 0); AddEq(l, ra->Minus(rb), 0);
break; 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. default: // Most entities do not generate equations.
break; break;
} }

View File

@ -131,6 +131,7 @@ const SolveSpaceUI::SaveTable SolveSpaceUI::SAVED[] = {
{ 'r', "Request.style", 'x', &(SS.sv.r.style) }, { 'r', "Request.style", 'x', &(SS.sv.r.style) },
{ 'r', "Request.str", 'S', &(SS.sv.r.str) }, { 'r', "Request.str", 'S', &(SS.sv.r.str) },
{ 'r', "Request.font", 'S', &(SS.sv.r.font) }, { '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.h.v", 'x', &(SS.sv.e.h.v) },
{ 'e', "Entity.type", 'd', &(SS.sv.e.type) }, { 'e', "Entity.type", 'd', &(SS.sv.e.type) },
@ -512,9 +513,57 @@ bool SolveSpaceUI::LoadFromFile(const std::string &filename) {
} }
} }
UpgradeLegacyData();
return true; 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, bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList *le,
SMesh *m, SShell *sh) SMesh *m, SShell *sh)
{ {

View File

@ -29,7 +29,7 @@ static const EntReqMapping EntReqMap[] = {
{ Request::Type::CUBIC_PERIODIC, Entity::Type::CUBIC_PERIODIC, 3, true, false, false }, { Request::Type::CUBIC_PERIODIC, Entity::Type::CUBIC_PERIODIC, 3, true, false, false },
{ Request::Type::CIRCLE, Entity::Type::CIRCLE, 1, false, true, true }, { 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::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, static void CopyEntityInfo(const EntReqMapping *te, int extraPoints,
@ -78,7 +78,7 @@ Request::Type EntReqTable::GetRequestForEntity(Entity::Type ent) {
} }
void Request::Generate(IdList<Entity,hEntity> *entity, void Request::Generate(IdList<Entity,hEntity> *entity,
IdList<Param,hParam> *param) const IdList<Param,hParam> *param)
{ {
int points = 0; int points = 0;
Entity::Type et; Entity::Type et;
@ -86,6 +86,26 @@ void Request::Generate(IdList<Entity,hEntity> *entity,
bool hasDistance = false; bool hasDistance = false;
int i; 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 = {}; Entity e = {};
EntReqTable::GetRequestInfo(type, extraPoints, &et, &points, &hasNormal, &hasDistance); EntReqTable::GetRequestInfo(type, extraPoints, &et, &points, &hasNormal, &hasDistance);
@ -98,6 +118,7 @@ void Request::Generate(IdList<Entity,hEntity> *entity,
e.construction = construction; e.construction = construction;
e.str = str; e.str = str;
e.font = font; e.font = font;
e.aspectRatio = aspectRatio;
e.h = h.entity(0); e.h = h.entity(0);
// And generate entities for the points // And generate entities for the points

View File

@ -316,11 +316,13 @@ public:
hStyle style; hStyle style;
bool construction; bool construction;
std::string str; std::string str;
std::string font; std::string font;
double aspectRatio;
static hParam AddParam(ParamList *param, hParam hp); static hParam AddParam(ParamList *param, hParam hp);
void Generate(EntityList *entity, ParamList *param) const; void Generate(EntityList *entity, ParamList *param);
std::string DescriptionString() const; std::string DescriptionString() const;
int IndexOfPoint(hEntity he) const; int IndexOfPoint(hEntity he) const;
@ -391,6 +393,7 @@ public:
std::string str; std::string str;
std::string font; std::string font;
double aspectRatio;
// For entities that are derived by a transformation, the number of // For entities that are derived by a transformation, the number of
// times to apply the transformation. // times to apply the transformation.
@ -434,7 +437,9 @@ public:
Vector PointGetNum() const; Vector PointGetNum() const;
ExprVector PointGetExprs() const; ExprVector PointGetExprs() const;
void PointGetExprsInWorkplane(hEntity wrkpl, Expr **u, Expr **v) const; void PointGetExprsInWorkplane(hEntity wrkpl, Expr **u, Expr **v) const;
ExprVector PointGetExprsInWorkplane(hEntity wrkpl) const;
void PointForceTo(Vector v); void PointForceTo(Vector v);
void PointForceParamTo(Vector v);
// These apply only the POINT_N_ROT_TRANS, which has an assoc rotation // These apply only the POINT_N_ROT_TRANS, which has an assoc rotation
Quaternion PointGetQuaternion() const; Quaternion PointGetQuaternion() const;
void PointForceQuaternionTo(Quaternion q); void PointForceQuaternionTo(Quaternion q);
@ -463,6 +468,8 @@ public:
Vector EndpointStart() const; Vector EndpointStart() const;
Vector EndpointFinish() const; Vector EndpointFinish() const;
void TtfTextGetPointsExprs(ExprVector *eap, ExprVector *ebp) const;
void AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index) const; void AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index) const;
void GenerateEquations(IdList<Equation,hEquation> *l) const; void GenerateEquations(IdList<Equation,hEquation> *l) const;
@ -859,7 +866,7 @@ inline hRequest hEntity::request() const
inline hGroup hEntity::group() const inline hGroup hEntity::group() const
{ hGroup r; r.v = (v >> 16) & 0x3fff; return r; } { hGroup r; r.v = (v >> 16) & 0x3fff; return r; }
inline hEquation hEntity::equation(int i) const 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 inline hRequest hParam::request() const
{ hRequest r; r.v = (v >> 16); return r; } { hRequest r; r.v = (v >> 16); return r; }

View File

@ -808,6 +808,7 @@ public:
bool SaveToFile(const std::string &filename); bool SaveToFile(const std::string &filename);
bool LoadAutosaveFor(const std::string &filename); bool LoadAutosaveFor(const std::string &filename);
bool LoadFromFile(const std::string &filename); bool LoadFromFile(const std::string &filename);
void UpgradeLegacyData();
bool LoadEntitiesFromFile(const std::string &filename, EntityList *le, bool LoadEntitiesFromFile(const std::string &filename, EntityList *le,
SMesh *m, SShell *sh); SMesh *m, SShell *sh);
bool ReloadAllImported(bool canCancel=false); bool ReloadAllImported(bool canCancel=false);

View File

@ -79,18 +79,28 @@ void TtfFontList::LoadAll() {
loaded = true; loaded = true;
} }
void TtfFontList::PlotString(const std::string &font, const std::string &str, TtfFont *TtfFontList::LoadFont(const std::string &font)
SBezierList *sbl, Vector origin, Vector u, Vector v)
{ {
LoadAll(); 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; }); [&](const TtfFont &tf) { return tf.FontFileBaseName() == font; });
if(!str.empty() && tf != &l.elem[l.n]) { if(tf != l.end()) {
if(tf->fontFace == NULL) { if(tf->fontFace == NULL) {
tf->LoadFromFile(fontLibrary, /*nameOnly=*/false); 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); tf->PlotString(str, sbl, origin, u, v);
} else { } else {
// No text or no font; so draw a big X for an error marker. // No text or no font; so draw a big X for an error marker.
@ -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 // Return the basename of our font filename; that's how the requests and
// entities that reference us will store it. // entities that reference us will store it.
@ -328,3 +348,27 @@ void TtfFont::PlotString(const std::string &str,
dx += fontFace->glyph->advance.x; 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;
}

View File

@ -21,6 +21,7 @@ public:
void PlotString(const std::string &str, void PlotString(const std::string &str,
SBezierList *sbl, Vector origin, Vector u, Vector v); SBezierList *sbl, Vector origin, Vector u, Vector v);
double AspectRatio(const std::string &str);
}; };
class TtfFontList { class TtfFontList {
@ -33,9 +34,11 @@ public:
~TtfFontList(); ~TtfFontList();
void LoadAll(); void LoadAll();
TtfFont *LoadFont(const std::string &font);
void PlotString(const std::string &font, const std::string &str, void PlotString(const std::string &font, const std::string &str,
SBezierList *sbl, Vector origin, Vector u, Vector v); SBezierList *sbl, Vector origin, Vector u, Vector v);
double AspectRatio(const std::string &font, const std::string &str);
}; };
#endif #endif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -130,6 +130,22 @@ Param.h.v.=00040014
Param.val=-5.00000000000000000000 Param.val=-5.00000000000000000000
AddParam AddParam
Param.h.v.=00040016
Param.val=23.92131768269594616072
AddParam
Param.h.v.=00040017
Param.val=-5.00000000000000000000
AddParam
Param.h.v.=00040019
Param.val=23.92131768269594616072
AddParam
Param.h.v.=0004001a
Param.val=5.00000000000000000000
AddParam
Request.h.v=00000001 Request.h.v=00000001
Request.type=100 Request.type=100
Request.group.v=00000001 Request.group.v=00000001
@ -155,6 +171,7 @@ Request.group.v=00000002
Request.construction=0 Request.construction=0
Request.str=Text Request.str=Text
Request.font=Gentium-R.ttf Request.font=Gentium-R.ttf
Request.aspectRatio=2.89213176826959461607
AddRequest AddRequest
Entity.h.v=00010000 Entity.h.v=00010000
@ -236,6 +253,8 @@ Entity.str=Text
Entity.font=Gentium-R.ttf Entity.font=Gentium-R.ttf
Entity.point[0].v=00040001 Entity.point[0].v=00040001
Entity.point[1].v=00040002 Entity.point[1].v=00040002
Entity.point[2].v=00040003
Entity.point[3].v=00040004
Entity.normal.v=00040020 Entity.normal.v=00040020
Entity.workplane.v=80020000 Entity.workplane.v=80020000
Entity.actVisible=1 Entity.actVisible=1
@ -259,6 +278,24 @@ Entity.actPoint.y=-5.00000000000000000000
Entity.actVisible=1 Entity.actVisible=1
AddEntity AddEntity
Entity.h.v=00040003
Entity.type=2001
Entity.construction=0
Entity.workplane.v=80020000
Entity.actPoint.x=23.92131768269594616072
Entity.actPoint.y=-5.00000000000000000000
Entity.actVisible=1
AddEntity
Entity.h.v=00040004
Entity.type=2001
Entity.construction=0
Entity.workplane.v=80020000
Entity.actPoint.x=23.92131768269594616072
Entity.actPoint.y=5.00000000000000000000
Entity.actVisible=1
AddEntity
Entity.h.v=00040020 Entity.h.v=00040020
Entity.type=3001 Entity.type=3001
Entity.construction=0 Entity.construction=0