diff --git a/CHANGELOG.md b/CHANGELOG.md index a0beb5a9..14c9ee56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/src/entity.cpp b/src/entity.cpp index 3a48ed3f..ea71806b 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -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 *l, Expr *expr, int index) const { Equation eq; eq.e = expr; @@ -752,6 +793,7 @@ void EntityBase::GenerateEquations(IdList *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 *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; } diff --git a/src/file.cpp b/src/file.cpp index 4b199d19..bff0c844 100644 --- a/src/file.cpp +++ b/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 = {}; + IdList 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) { diff --git a/src/request.cpp b/src/request.cpp index bfef176c..999cc945 100644 --- a/src/request.cpp +++ b/src/request.cpp @@ -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, - IdList *param) const + IdList *param) { int points = 0; Entity::Type et; @@ -86,6 +86,26 @@ void Request::Generate(IdList *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, e.construction = construction; e.str = str; e.font = font; + e.aspectRatio = aspectRatio; e.h = h.entity(0); // And generate entities for the points diff --git a/src/sketch.h b/src/sketch.h index 7316d0f8..582ad3a6 100644 --- a/src/sketch.h +++ b/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 *l, Expr *expr, int index) const; void GenerateEquations(IdList *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; } diff --git a/src/solvespace.h b/src/solvespace.h index 05d8a657..f2998224 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -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); diff --git a/src/ttf.cpp b/src/ttf.cpp index e83ff57f..c04cc5dd 100644 --- a/src/ttf.cpp +++ b/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; +} diff --git a/src/ttf.h b/src/ttf.h index ec911272..ff016c39 100644 --- a/src/ttf.h +++ b/src/ttf.h @@ -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 diff --git a/test/request/ttf_text/normal.png b/test/request/ttf_text/normal.png index 3397538f..39f789ef 100644 Binary files a/test/request/ttf_text/normal.png and b/test/request/ttf_text/normal.png differ diff --git a/test/request/ttf_text/normal.slvs b/test/request/ttf_text/normal.slvs index 8ea2d83e..229c63bc 100644 --- a/test/request/ttf_text/normal.slvs +++ b/test/request/ttf_text/normal.slvs @@ -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