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
parent
23feb4cf8f
commit
74cb1f589c
|
@ -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.
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
49
src/file.cpp
49
src/file.cpp
|
@ -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) },
|
||||
|
@ -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, ¶m);
|
||||
|
||||
// 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)
|
||||
{
|
||||
|
|
|
@ -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,
|
||||
|
@ -78,7 +78,7 @@ 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;
|
||||
|
@ -86,6 +86,26 @@ void Request::Generate(IdList<Entity,hEntity> *entity,
|
|||
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);
|
||||
|
||||
|
@ -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
|
||||
|
|
11
src/sketch.h
11
src/sketch.h
|
@ -316,11 +316,13 @@ public:
|
|||
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;
|
||||
|
@ -391,6 +393,7 @@ public:
|
|||
|
||||
std::string str;
|
||||
std::string font;
|
||||
double aspectRatio;
|
||||
|
||||
// For entities that are derived by a transformation, the number of
|
||||
// times to apply the transformation.
|
||||
|
@ -434,7 +437,9 @@ public:
|
|||
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);
|
||||
|
@ -463,6 +468,8 @@ public:
|
|||
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;
|
||||
|
||||
|
@ -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; }
|
||||
|
|
|
@ -808,6 +808,7 @@ public:
|
|||
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);
|
||||
|
|
52
src/ttf.cpp
52
src/ttf.cpp
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ public:
|
|||
|
||||
void PlotString(const std::string &str,
|
||||
SBezierList *sbl, Vector origin, Vector u, Vector v);
|
||||
double AspectRatio(const std::string &str);
|
||||
};
|
||||
|
||||
class TtfFontList {
|
||||
|
@ -33,9 +34,11 @@ public:
|
|||
~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 not shown.
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
|
@ -130,6 +130,22 @@ Param.h.v.=00040014
|
|||
Param.val=-5.00000000000000000000
|
||||
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.type=100
|
||||
Request.group.v=00000001
|
||||
|
@ -155,6 +171,7 @@ Request.group.v=00000002
|
|||
Request.construction=0
|
||||
Request.str=Text
|
||||
Request.font=Gentium-R.ttf
|
||||
Request.aspectRatio=2.89213176826959461607
|
||||
AddRequest
|
||||
|
||||
Entity.h.v=00010000
|
||||
|
@ -236,6 +253,8 @@ Entity.str=Text
|
|||
Entity.font=Gentium-R.ttf
|
||||
Entity.point[0].v=00040001
|
||||
Entity.point[1].v=00040002
|
||||
Entity.point[2].v=00040003
|
||||
Entity.point[3].v=00040004
|
||||
Entity.normal.v=00040020
|
||||
Entity.workplane.v=80020000
|
||||
Entity.actVisible=1
|
||||
|
@ -259,6 +278,24 @@ Entity.actPoint.y=-5.00000000000000000000
|
|||
Entity.actVisible=1
|
||||
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.type=3001
|
||||
Entity.construction=0
|
||||
|
|
Loading…
Reference in New Issue