1 //-----------------------------------------------------------------------------
2 // Implementation of a cosmetic line style, which determines the color and
3 // other appearance of a line or curve on-screen and in exported files. Some
4 // styles are predefined, and others can be created by the user.
5 //
6 // Copyright 2008-2013 Jonathan Westhues.
7 //-----------------------------------------------------------------------------
8 #include "solvespace.h"
9 #include <png.h>
10 
11 #define DEFAULT_TEXT_HEIGHT 11.5
12 
13 const Style::Default Style::Defaults[] = {
14     { { ACTIVE_GRP },   "ActiveGrp",    RGBf(1.0, 1.0, 1.0), 1.5, 4 },
15     { { CONSTRUCTION }, "Construction", RGBf(0.1, 0.7, 0.1), 1.5, 0 },
16     { { INACTIVE_GRP }, "InactiveGrp",  RGBf(0.5, 0.3, 0.0), 1.5, 3 },
17     { { DATUM },        "Datum",        RGBf(0.0, 0.8, 0.0), 1.5, 0 },
18     { { SOLID_EDGE },   "SolidEdge",    RGBf(0.8, 0.8, 0.8), 1.0, 2 },
19     { { CONSTRAINT },   "Constraint",   RGBf(1.0, 0.1, 1.0), 1.0, 0 },
20     { { SELECTED },     "Selected",     RGBf(1.0, 0.0, 0.0), 1.5, 0 },
21     { { HOVERED },      "Hovered",      RGBf(1.0, 1.0, 0.0), 1.5, 0 },
22     { { CONTOUR_FILL }, "ContourFill",  RGBf(0.0, 0.1, 0.1), 1.0, 0 },
23     { { NORMALS },      "Normals",      RGBf(0.0, 0.4, 0.4), 1.0, 0 },
24     { { ANALYZE },      "Analyze",      RGBf(0.0, 1.0, 1.0), 1.0, 0 },
25     { { DRAW_ERROR },   "DrawError",    RGBf(1.0, 0.0, 0.0), 8.0, 0 },
26     { { DIM_SOLID },    "DimSolid",     RGBf(0.1, 0.1, 0.1), 1.0, 0 },
27     { { HIDDEN_EDGE },  "HiddenEdge",   RGBf(0.8, 0.8, 0.8), 2.0, 1 },
28     { { OUTLINE },      "Outline",      RGBf(0.8, 0.8, 0.8), 3.0, 5 },
29     { { 0 },            NULL,           RGBf(0.0, 0.0, 0.0), 0.0, 0 }
30 };
31 
CnfColor(const std::string & prefix)32 std::string Style::CnfColor(const std::string &prefix) {
33     return "Style_" + prefix + "_Color";
34 }
CnfWidth(const std::string & prefix)35 std::string Style::CnfWidth(const std::string &prefix) {
36     return "Style_" + prefix + "_Width";
37 }
CnfTextHeight(const std::string & prefix)38 std::string Style::CnfTextHeight(const std::string &prefix) {
39     return "Style_" + prefix + "_TextHeight";
40 }
41 
CnfPrefixToName(const std::string & prefix)42 std::string Style::CnfPrefixToName(const std::string &prefix) {
43     std::string name = "#def-";
44 
45     for(size_t i = 0; i < prefix.length(); i++) {
46         if(isupper(prefix[i]) && i != 0)
47             name += '-';
48         name += tolower(prefix[i]);
49     }
50 
51     return name;
52 }
53 
CreateAllDefaultStyles(void)54 void Style::CreateAllDefaultStyles(void) {
55     const Default *d;
56     for(d = &(Defaults[0]); d->h.v; d++) {
57         (void)Get(d->h);
58     }
59 }
60 
CreateDefaultStyle(hStyle h)61 void Style::CreateDefaultStyle(hStyle h) {
62     bool isDefaultStyle = true;
63     const Default *d;
64     for(d = &(Defaults[0]); d->h.v; d++) {
65         if(d->h.v == h.v) break;
66     }
67     if(!d->h.v) {
68         // Not a default style; so just create it the same as our default
69         // active group entity style.
70         d = &(Defaults[0]);
71         isDefaultStyle = false;
72     }
73 
74     Style ns = {};
75     FillDefaultStyle(&ns, d);
76     ns.h = h;
77     if(isDefaultStyle) {
78         ns.name = CnfPrefixToName(d->cnfPrefix);
79     } else {
80         ns.name = "new-custom-style";
81     }
82 
83     SK.style.Add(&ns);
84 }
85 
FillDefaultStyle(Style * s,const Default * d,bool factory)86 void Style::FillDefaultStyle(Style *s, const Default *d, bool factory) {
87     if(d == NULL) d = &Defaults[0];
88     s->color         = (factory) ? d->color : CnfThawColor(d->color, CnfColor(d->cnfPrefix));
89     s->width         = (factory) ? d->width : CnfThawFloat((float)(d->width), CnfWidth(d->cnfPrefix));
90     s->widthAs       = UNITS_AS_PIXELS;
91     s->textHeight    = (factory) ? DEFAULT_TEXT_HEIGHT
92                                  : CnfThawFloat(DEFAULT_TEXT_HEIGHT, CnfTextHeight(d->cnfPrefix));
93     s->textHeightAs  = UNITS_AS_PIXELS;
94     s->textOrigin    = 0;
95     s->textAngle     = 0;
96     s->visible       = true;
97     s->exportable    = true;
98     s->filled        = false;
99     s->fillColor     = RGBf(0.3, 0.3, 0.3);
100     s->stippleType   = (d->h.v == Style::HIDDEN_EDGE) ? Style::STIPPLE_DASH
101                                                       : Style::STIPPLE_CONTINUOUS;
102     s->stippleScale  = 15.0;
103     s->zIndex        = d->zIndex;
104 }
105 
LoadFactoryDefaults(void)106 void Style::LoadFactoryDefaults(void) {
107     const Default *d;
108     for(d = &(Defaults[0]); d->h.v; d++) {
109         Style *s = Get(d->h);
110         FillDefaultStyle(s, d, /*factory=*/true);
111     }
112     SS.backgroundColor = RGBi(0, 0, 0);
113     if(SS.bgImage.fromFile) MemFree(SS.bgImage.fromFile);
114     SS.bgImage.fromFile = NULL;
115 }
116 
FreezeDefaultStyles(void)117 void Style::FreezeDefaultStyles(void) {
118     const Default *d;
119     for(d = &(Defaults[0]); d->h.v; d++) {
120         CnfFreezeColor(Color(d->h), CnfColor(d->cnfPrefix));
121         CnfFreezeFloat((float)Width(d->h), CnfWidth(d->cnfPrefix));
122         CnfFreezeFloat((float)TextHeight(d->h), CnfTextHeight(d->cnfPrefix));
123     }
124 }
125 
CreateCustomStyle(bool rememberForUndo)126 uint32_t Style::CreateCustomStyle(bool rememberForUndo) {
127     if(rememberForUndo) SS.UndoRemember();
128     uint32_t vs = max((uint32_t)Style::FIRST_CUSTOM, SK.style.MaximumId() + 1);
129     hStyle hs = { vs };
130     (void)Style::Get(hs);
131     return hs.v;
132 }
133 
AssignSelectionToStyle(uint32_t v)134 void Style::AssignSelectionToStyle(uint32_t v) {
135     bool showError = false;
136     SS.GW.GroupSelection();
137 
138     SS.UndoRemember();
139     int i;
140     for(i = 0; i < SS.GW.gs.entities; i++) {
141         hEntity he = SS.GW.gs.entity[i];
142         Entity *e = SK.GetEntity(he);
143         if(!e->IsStylable()) continue;
144 
145         if(!he.isFromRequest()) {
146             showError = true;
147             continue;
148         }
149 
150         hRequest hr = he.request();
151         Request *r = SK.GetRequest(hr);
152         r->style.v = v;
153         SS.MarkGroupDirty(r->group);
154     }
155     for(i = 0; i < SS.GW.gs.constraints; i++) {
156         hConstraint hc = SS.GW.gs.constraint[i];
157         Constraint *c = SK.GetConstraint(hc);
158         if(!c->IsStylable()) continue;
159 
160         c->disp.style.v = v;
161     }
162 
163     if(showError) {
164         Error("Can't assign style to an entity that's derived from another "
165               "entity; try assigning a style to this entity's parent.");
166     }
167 
168     SS.GW.ClearSelection();
169     InvalidateGraphics();
170     SS.ScheduleGenerateAll();
171 
172     // And show that style's info screen in the text window.
173     SS.TW.GoToScreen(TextWindow::SCREEN_STYLE_INFO);
174     SS.TW.shown.style.v = v;
175     SS.ScheduleShowTW();
176 }
177 
178 //-----------------------------------------------------------------------------
179 // Look up a style by its handle. If that style does not exist, then create
180 // the style, according to our table of default styles.
181 //-----------------------------------------------------------------------------
Get(hStyle h)182 Style *Style::Get(hStyle h) {
183     if(h.v == 0) h.v = ACTIVE_GRP;
184 
185     Style *s = SK.style.FindByIdNoOops(h);
186     if(s) {
187         // It exists, good.
188         return s;
189     } else {
190         // It doesn't exist; so we should create it and then return that.
191         CreateDefaultStyle(h);
192         return SK.style.FindById(h);
193     }
194 }
195 
196 //-----------------------------------------------------------------------------
197 // A couple of wrappers, so that I can call these functions with either an
198 // hStyle or with the integer corresponding to that hStyle.v.
199 //-----------------------------------------------------------------------------
Color(int s,bool forExport)200 RgbaColor Style::Color(int s, bool forExport) {
201     hStyle hs = { (uint32_t)s };
202     return Color(hs, forExport);
203 }
Width(int s)204 float Style::Width(int s) {
205     hStyle hs = { (uint32_t)s };
206     return Width(hs);
207 }
208 
209 //-----------------------------------------------------------------------------
210 // If a color is almost white, then we can rewrite it to black, just so that
211 // it won't disappear on file formats with a light background.
212 //-----------------------------------------------------------------------------
RewriteColor(RgbaColor rgbin)213 RgbaColor Style::RewriteColor(RgbaColor rgbin) {
214     Vector rgb = Vector::From(rgbin.redF(), rgbin.greenF(), rgbin.blueF());
215     rgb = rgb.Minus(Vector::From(1, 1, 1));
216     if(rgb.Magnitude() < 0.4 && SS.fixExportColors) {
217         // This is an almost-white color in a default style, which is
218         // good for the default on-screen view (black bg) but probably
219         // not desired in the exported files, which typically are shown
220         // against white backgrounds.
221         return RGBi(0, 0, 0);
222     } else {
223         return rgbin;
224     }
225 }
226 
227 //-----------------------------------------------------------------------------
228 // Return the stroke color associated with our style as 8-bit RGB.
229 //-----------------------------------------------------------------------------
Color(hStyle h,bool forExport)230 RgbaColor Style::Color(hStyle h, bool forExport) {
231     Style *s = Get(h);
232     if(forExport) {
233         return RewriteColor(s->color);
234     } else {
235         return s->color;
236     }
237 }
238 
239 //-----------------------------------------------------------------------------
240 // Return the fill color associated with our style as 8-bit RGB.
241 //-----------------------------------------------------------------------------
FillColor(hStyle h,bool forExport)242 RgbaColor Style::FillColor(hStyle h, bool forExport) {
243     Style *s = Get(h);
244     if(forExport) {
245         return RewriteColor(s->fillColor);
246     } else {
247         return s->fillColor;
248     }
249 }
250 
251 //-----------------------------------------------------------------------------
252 // Return the width associated with our style in pixels..
253 //-----------------------------------------------------------------------------
Width(hStyle h)254 float Style::Width(hStyle h) {
255     double r = 1.0;
256     Style *s = Get(h);
257     if(s->widthAs == UNITS_AS_MM) {
258         r = s->width * SS.GW.scale;
259     } else if(s->widthAs == UNITS_AS_PIXELS) {
260         r = s->width;
261     }
262     // This returns a float because ssglLineWidth expects a float, avoid casts.
263     return (float)r;
264 }
265 
266 //-----------------------------------------------------------------------------
267 // Return the width associated with our style in millimeters..
268 //-----------------------------------------------------------------------------
WidthMm(int hs)269 double Style::WidthMm(int hs) {
270     double widthpx = Width(hs);
271     return widthpx / SS.GW.scale;
272 }
273 
274 //-----------------------------------------------------------------------------
275 // Return the associated text height, in pixels.
276 //-----------------------------------------------------------------------------
TextHeight(hStyle hs)277 double Style::TextHeight(hStyle hs) {
278     Style *s = Get(hs);
279     if(s->textHeightAs == UNITS_AS_MM) {
280         return s->textHeight * SS.GW.scale;
281     } else /* s->textHeightAs == UNITS_AS_PIXELS */ {
282         return s->textHeight;
283     }
284 }
285 
DefaultTextHeight()286 double Style::DefaultTextHeight() {
287     hStyle hs { Style::CONSTRAINT };
288     return TextHeight(hs);
289 }
290 
291 //-----------------------------------------------------------------------------
292 // Should lines and curves from this style appear in the output file? Only
293 // if it's both shown and exportable.
294 //-----------------------------------------------------------------------------
Exportable(int si)295 bool Style::Exportable(int si) {
296     hStyle hs = { (uint32_t)si };
297     Style *s = Get(hs);
298     return (s->exportable) && (s->visible);
299 }
300 
301 //-----------------------------------------------------------------------------
302 // Return the appropriate style for our entity. If the entity has a style
303 // explicitly assigned, then it's that style. Otherwise it's the appropriate
304 // default style.
305 //-----------------------------------------------------------------------------
ForEntity(hEntity he)306 hStyle Style::ForEntity(hEntity he) {
307     Entity *e = SK.GetEntity(he);
308     // If the entity has a special style, use that. If that style doesn't
309     // exist yet, then it will get created automatically later.
310     if(e->style.v != 0) {
311         return e->style;
312     }
313 
314     // Otherwise, we use the default rules.
315     hStyle hs;
316     if(e->group.v != SS.GW.activeGroup.v) {
317         hs.v = INACTIVE_GRP;
318     } else if(e->construction) {
319         hs.v = CONSTRUCTION;
320     } else {
321         hs.v = ACTIVE_GRP;
322     }
323     return hs;
324 }
325 
PatternType(hStyle hs)326 int Style::PatternType(hStyle hs) {
327     Style *s = Get(hs);
328     return s->stippleType;
329 }
330 
StippleScaleMm(hStyle hs)331 double Style::StippleScaleMm(hStyle hs) {
332     Style *s = Get(hs);
333     if(s->widthAs == UNITS_AS_MM) {
334         return s->stippleScale;
335     } else if(s->widthAs == UNITS_AS_PIXELS) {
336         return s->stippleScale / SS.GW.scale;
337     }
338     return 1.0;
339 }
340 
DescriptionString(void)341 std::string Style::DescriptionString(void) {
342     if(name.empty()) {
343         return ssprintf("s%03x-(unnamed)", h.v);
344     } else {
345         return ssprintf("s%03x-%s", h.v, name.c_str());
346     }
347 }
348 
349 
ScreenShowListOfStyles(int link,uint32_t v)350 void TextWindow::ScreenShowListOfStyles(int link, uint32_t v) {
351     SS.TW.GoToScreen(SCREEN_LIST_OF_STYLES);
352 }
ScreenShowStyleInfo(int link,uint32_t v)353 void TextWindow::ScreenShowStyleInfo(int link, uint32_t v) {
354     SS.TW.GoToScreen(SCREEN_STYLE_INFO);
355     SS.TW.shown.style.v = v;
356 }
357 
ScreenLoadFactoryDefaultStyles(int link,uint32_t v)358 void TextWindow::ScreenLoadFactoryDefaultStyles(int link, uint32_t v) {
359     Style::LoadFactoryDefaults();
360     SS.TW.GoToScreen(SCREEN_LIST_OF_STYLES);
361 }
362 
ScreenCreateCustomStyle(int link,uint32_t v)363 void TextWindow::ScreenCreateCustomStyle(int link, uint32_t v) {
364     Style::CreateCustomStyle();
365 }
366 
ScreenChangeBackgroundColor(int link,uint32_t v)367 void TextWindow::ScreenChangeBackgroundColor(int link, uint32_t v) {
368     RgbaColor rgb = SS.backgroundColor;
369     SS.TW.ShowEditControlWithColorPicker(3, rgb);
370     SS.TW.edit.meaning = EDIT_BACKGROUND_COLOR;
371 }
372 
RoundUpToPowerOfTwo(int v)373 static int RoundUpToPowerOfTwo(int v)
374 {
375     int i;
376     for(i = 0; i < 31; i++) {
377         int vt = (1 << i);
378         if(vt >= v) {
379             return vt;
380         }
381     }
382     return 0;
383 }
384 
ScreenBackgroundImage(int link,uint32_t v)385 void TextWindow::ScreenBackgroundImage(int link, uint32_t v) {
386     if(SS.bgImage.fromFile) MemFree(SS.bgImage.fromFile);
387     SS.bgImage.fromFile = NULL;
388 
389     if(link == 'l') {
390         FILE *f = NULL;
391         png_struct *png_ptr = NULL;
392         png_info *info_ptr = NULL;
393 
394         std::string importFile;
395         if(!GetOpenFile(&importFile, "", PngFileFilter)) goto err;
396         f = ssfopen(importFile, "rb");
397         if(!f) goto err;
398 
399         uint8_t header[8];
400         if (fread(header, 1, 8, f) != 8)
401             goto err;
402         if(png_sig_cmp(header, 0, 8)) goto err;
403 
404         png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
405             NULL, NULL, NULL);
406         if(!png_ptr) goto err;
407 
408         info_ptr = png_create_info_struct(png_ptr);
409         if(!info_ptr) goto err;
410 
411         if(setjmp(png_jmpbuf(png_ptr))) goto err;
412 
413         png_init_io(png_ptr, f);
414         png_set_sig_bytes(png_ptr, 8);
415 
416         png_read_png(png_ptr, info_ptr,
417             PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_ALPHA, NULL);
418 
419         int w; w = (int)png_get_image_width(png_ptr, info_ptr);
420         int h; h = (int)png_get_image_height(png_ptr, info_ptr);
421         uint8_t **rows; rows = png_get_rows(png_ptr, info_ptr);
422 
423         // Round to next-highest powers of two, since the textures require
424         // that. And round up to 4, to guarantee 32-bit alignment.
425         int rw; rw = max(4, RoundUpToPowerOfTwo(w));
426         int rh; rh = max(4, RoundUpToPowerOfTwo(h));
427 
428         SS.bgImage.fromFile = (uint8_t *)MemAlloc(rw*rh*3);
429         {for(int i = 0; i < h; i++) {
430             memcpy(SS.bgImage.fromFile + ((h - 1) - i)*(rw*3), rows[i], w*3);
431         }}
432         SS.bgImage.w      = w;
433         SS.bgImage.h      = h;
434         SS.bgImage.rw     = rw;
435         SS.bgImage.rh     = rh;
436         SS.bgImage.scale  = SS.GW.scale;
437         SS.bgImage.origin = SS.GW.offset.ScaledBy(-1);
438 
439 err:
440         if(png_ptr) png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
441         if(f) fclose(f);
442     }
443     SS.ScheduleShowTW();
444 }
445 
ScreenChangeBackgroundImageScale(int link,uint32_t v)446 void TextWindow::ScreenChangeBackgroundImageScale(int link, uint32_t v) {
447     SS.TW.edit.meaning = EDIT_BACKGROUND_IMG_SCALE;
448     SS.TW.ShowEditControl(10, ssprintf("%.3f", SS.bgImage.scale * SS.MmPerUnit()));
449 }
450 
ShowListOfStyles(void)451 void TextWindow::ShowListOfStyles(void) {
452     Printf(true, "%Ft color  style-name");
453 
454     bool darkbg = false;
455     Style *s;
456     for(s = SK.style.First(); s; s = SK.style.NextAfter(s)) {
457         Printf(false, "%Bp  %Bz   %Bp   %Fl%Ll%f%D%s%E",
458             darkbg ? 'd' : 'a',
459             &s->color,
460             darkbg ? 'd' : 'a',
461             ScreenShowStyleInfo, s->h.v,
462             s->DescriptionString().c_str());
463 
464         darkbg = !darkbg;
465     }
466 
467     Printf(true, "  %Fl%Ll%fcreate a new custom style%E",
468         &ScreenCreateCustomStyle);
469 
470     Printf(false, "");
471 
472     RgbaColor rgb = SS.backgroundColor;
473     Printf(false, "%Ft background color (r, g, b)%E");
474     Printf(false, "%Ba   %@, %@, %@ %Fl%D%f%Ll[change]%E",
475         rgb.redF(), rgb.greenF(), rgb.blueF(),
476         top[rows-1] + 2, &ScreenChangeBackgroundColor);
477 
478     Printf(false, "");
479     Printf(false, "%Ft background bitmap image%E");
480     if(SS.bgImage.fromFile) {
481         Printf(false, "%Ba   %Ftwidth:%E %dpx   %Ftheight:%E %dpx",
482             SS.bgImage.w, SS.bgImage.h);
483 
484         Printf(false, "   %Ftscale:%E %# px/%s %Fl%Ll%f%D[change]%E",
485             SS.bgImage.scale*SS.MmPerUnit(),
486             SS.UnitName(),
487             &ScreenChangeBackgroundImageScale, top[rows-1] + 2);
488 
489         Printf(false, "%Ba   %Fl%Lc%fclear background image%E",
490             &ScreenBackgroundImage);
491     } else {
492         Printf(false, "%Ba   none - %Fl%Ll%fload background image%E",
493             &ScreenBackgroundImage);
494         Printf(false, "   (bottom left will be center of view)");
495     }
496 
497     Printf(false, "");
498     Printf(false, "  %Fl%Ll%fload factory defaults%E",
499         &ScreenLoadFactoryDefaultStyles);
500 }
501 
502 
ScreenChangeStyleName(int link,uint32_t v)503 void TextWindow::ScreenChangeStyleName(int link, uint32_t v) {
504     hStyle hs = { v };
505     Style *s = Style::Get(hs);
506     SS.TW.ShowEditControl(12, s->name);
507     SS.TW.edit.style = hs;
508     SS.TW.edit.meaning = EDIT_STYLE_NAME;
509 }
510 
ScreenDeleteStyle(int link,uint32_t v)511 void TextWindow::ScreenDeleteStyle(int link, uint32_t v) {
512     SS.UndoRemember();
513     hStyle hs = { v };
514     Style *s = SK.style.FindByIdNoOops(hs);
515     if(s) {
516         SK.style.RemoveById(hs);
517         // And it will get recreated automatically if something is still using
518         // the style, so no need to do anything else.
519     }
520     SS.TW.GoToScreen(SCREEN_LIST_OF_STYLES);
521     InvalidateGraphics();
522 }
523 
ScreenChangeStylePatternType(int link,uint32_t v)524 void TextWindow::ScreenChangeStylePatternType(int link, uint32_t v) {
525     hStyle hs = { v };
526     Style *s = Style::Get(hs);
527     s->stippleType = link - 1;
528 }
529 
ScreenChangeStyleMetric(int link,uint32_t v)530 void TextWindow::ScreenChangeStyleMetric(int link, uint32_t v) {
531     hStyle hs = { v };
532     Style *s = Style::Get(hs);
533     double val;
534     int units, meaning, col;
535     switch(link) {
536         case 't':
537             val = s->textHeight;
538             units = s->textHeightAs;
539             col = 10;
540             meaning = EDIT_STYLE_TEXT_HEIGHT;
541             break;
542 
543         case 's':
544             val = s->stippleScale;
545             units = s->widthAs;
546             col = 17;
547             meaning = EDIT_STYLE_STIPPLE_PERIOD;
548             break;
549 
550         case 'w':
551         case 'W':
552             val = s->width;
553             units = s->widthAs;
554             col = 9;
555             meaning = EDIT_STYLE_WIDTH;
556             break;
557 
558         default: oops();
559     }
560 
561     std::string edit_value;
562     if(units == Style::UNITS_AS_PIXELS) {
563         edit_value = ssprintf("%.2f", val);
564     } else {
565         edit_value = SS.MmToString(val);
566     }
567     SS.TW.ShowEditControl(col, edit_value);
568     SS.TW.edit.style = hs;
569     SS.TW.edit.meaning = meaning;
570 }
571 
ScreenChangeStyleTextAngle(int link,uint32_t v)572 void TextWindow::ScreenChangeStyleTextAngle(int link, uint32_t v) {
573     hStyle hs = { v };
574     Style *s = Style::Get(hs);
575     SS.TW.ShowEditControl(9, ssprintf("%.2f", s->textAngle));
576     SS.TW.edit.style = hs;
577     SS.TW.edit.meaning = EDIT_STYLE_TEXT_ANGLE;
578 }
579 
ScreenChangeStyleColor(int link,uint32_t v)580 void TextWindow::ScreenChangeStyleColor(int link, uint32_t v) {
581     hStyle hs = { v };
582     Style *s = Style::Get(hs);
583     // Same function used for stroke and fill colors
584     int em;
585     RgbaColor rgb;
586     if(link == 's') {
587         em = EDIT_STYLE_COLOR;
588         rgb = s->color;
589     } else if(link == 'f') {
590         em = EDIT_STYLE_FILL_COLOR;
591         rgb = s->fillColor;
592     } else {
593         oops();
594     }
595     SS.TW.ShowEditControlWithColorPicker(13, rgb);
596     SS.TW.edit.style = hs;
597     SS.TW.edit.meaning = em;
598 }
599 
ScreenChangeStyleYesNo(int link,uint32_t v)600 void TextWindow::ScreenChangeStyleYesNo(int link, uint32_t v) {
601     SS.UndoRemember();
602     hStyle hs = { v };
603     Style *s = Style::Get(hs);
604     switch(link) {
605         // Units for the width
606         case 'w':
607             if(s->widthAs != Style::UNITS_AS_MM) {
608                 s->widthAs = Style::UNITS_AS_MM;
609                 s->width /= SS.GW.scale;
610                 s->stippleScale /= SS.GW.scale;
611             }
612             break;
613         case 'W':
614             if(s->widthAs != Style::UNITS_AS_PIXELS) {
615                 s->widthAs = Style::UNITS_AS_PIXELS;
616                 s->width *= SS.GW.scale;
617                 s->stippleScale *= SS.GW.scale;
618             }
619             break;
620 
621         // Units for the height
622         case 'g':
623             if(s->textHeightAs != Style::UNITS_AS_MM) {
624                 s->textHeightAs = Style::UNITS_AS_MM;
625                 s->textHeight /= SS.GW.scale;
626             }
627             break;
628 
629         case 'G':
630             if(s->textHeightAs != Style::UNITS_AS_PIXELS) {
631                 s->textHeightAs = Style::UNITS_AS_PIXELS;
632                 s->textHeight *= SS.GW.scale;
633             }
634             break;
635 
636         case 'e':
637             s->exportable = !(s->exportable);
638             break;
639 
640         case 'v':
641             s->visible = !(s->visible);
642             break;
643 
644         case 'f':
645             s->filled = !(s->filled);
646             break;
647 
648         // Horizontal text alignment
649         case 'L':
650             s->textOrigin |=  Style::ORIGIN_LEFT;
651             s->textOrigin &= ~Style::ORIGIN_RIGHT;
652             break;
653         case 'H':
654             s->textOrigin &= ~Style::ORIGIN_LEFT;
655             s->textOrigin &= ~Style::ORIGIN_RIGHT;
656             break;
657         case 'R':
658             s->textOrigin &= ~Style::ORIGIN_LEFT;
659             s->textOrigin |=  Style::ORIGIN_RIGHT;
660             break;
661 
662         // Vertical text alignment
663         case 'B':
664             s->textOrigin |=  Style::ORIGIN_BOT;
665             s->textOrigin &= ~Style::ORIGIN_TOP;
666             break;
667         case 'V':
668             s->textOrigin &= ~Style::ORIGIN_BOT;
669             s->textOrigin &= ~Style::ORIGIN_TOP;
670             break;
671         case 'T':
672             s->textOrigin &= ~Style::ORIGIN_BOT;
673             s->textOrigin |=  Style::ORIGIN_TOP;
674             break;
675     }
676     InvalidateGraphics();
677 }
678 
EditControlDoneForStyles(const char * str)679 bool TextWindow::EditControlDoneForStyles(const char *str) {
680     Style *s;
681     switch(edit.meaning) {
682         case EDIT_STYLE_STIPPLE_PERIOD:
683         case EDIT_STYLE_TEXT_HEIGHT:
684         case EDIT_STYLE_WIDTH: {
685             SS.UndoRemember();
686             s = Style::Get(edit.style);
687 
688             double v;
689             int units = (edit.meaning == EDIT_STYLE_TEXT_HEIGHT) ?
690                             s->textHeightAs : s->widthAs;
691             if(units == Style::UNITS_AS_MM) {
692                 v = SS.StringToMm(str);
693             } else {
694                 v = atof(str);
695             }
696             v = max(0.0, v);
697             if(edit.meaning == EDIT_STYLE_TEXT_HEIGHT) {
698                 s->textHeight = v;
699             } else if(edit.meaning == EDIT_STYLE_STIPPLE_PERIOD) {
700                 s->stippleScale = v;
701             } else {
702                 s->width = v;
703             }
704             break;
705         }
706         case EDIT_STYLE_TEXT_ANGLE:
707             SS.UndoRemember();
708             s = Style::Get(edit.style);
709             s->textAngle = WRAP_SYMMETRIC(atof(str), 360);
710             break;
711 
712         case EDIT_BACKGROUND_COLOR:
713         case EDIT_STYLE_FILL_COLOR:
714         case EDIT_STYLE_COLOR: {
715             Vector rgb;
716             if(sscanf(str, "%lf, %lf, %lf", &rgb.x, &rgb.y, &rgb.z)==3) {
717                 rgb = rgb.ClampWithin(0, 1);
718                 if(edit.meaning == EDIT_STYLE_COLOR) {
719                     SS.UndoRemember();
720                     s = Style::Get(edit.style);
721                     s->color = RGBf(rgb.x, rgb.y, rgb.z);
722                 } else if(edit.meaning == EDIT_STYLE_FILL_COLOR) {
723                     SS.UndoRemember();
724                     s = Style::Get(edit.style);
725                     s->fillColor = RGBf(rgb.x, rgb.y, rgb.z);
726                 } else {
727                     SS.backgroundColor = RGBf(rgb.x, rgb.y, rgb.z);
728                 }
729             } else {
730                 Error("Bad format: specify color as r, g, b");
731             }
732             break;
733         }
734         case EDIT_STYLE_NAME:
735             if(!*str) {
736                 Error("Style name cannot be empty");
737             } else {
738                 SS.UndoRemember();
739                 s = Style::Get(edit.style);
740                 s->name = str;
741             }
742             break;
743 
744         case EDIT_BACKGROUND_IMG_SCALE: {
745             Expr *e = Expr::From(str, true);
746             if(e) {
747                 double ev = e->Eval();
748                 if(ev < 0.001 || isnan(ev)) {
749                     Error("Scale must not be zero or negative!");
750                 } else {
751                     SS.bgImage.scale = ev / SS.MmPerUnit();
752                 }
753             }
754             break;
755         }
756         default: return false;
757     }
758     return true;
759 }
760 
ShowStyleInfo(void)761 void TextWindow::ShowStyleInfo(void) {
762     Printf(true, "%Fl%f%Ll(back to list of styles)%E", &ScreenShowListOfStyles);
763 
764     Style *s = Style::Get(shown.style);
765 
766     if(s->h.v < Style::FIRST_CUSTOM) {
767         Printf(true, "%FtSTYLE  %E%s ", s->DescriptionString().c_str());
768     } else {
769         Printf(true, "%FtSTYLE  %E%s "
770                      "[%Fl%Ll%D%frename%E/%Fl%Ll%D%fdel%E]",
771             s->DescriptionString().c_str(),
772             s->h.v, &ScreenChangeStyleName,
773             s->h.v, &ScreenDeleteStyle);
774     }
775     Printf(true, "%Ft line stroke style%E");
776     Printf(false, "%Ba   %Ftcolor %E%Bz  %Ba (%@, %@, %@) %D%f%Ls%Fl[change]%E",
777         &s->color,
778         s->color.redF(), s->color.greenF(), s->color.blueF(),
779         s->h.v, ScreenChangeStyleColor);
780 
781     // The line width, and its units
782     if(s->widthAs == Style::UNITS_AS_PIXELS) {
783         Printf(false, "   %Ftwidth%E %@ %D%f%Lp%Fl[change]%E",
784             s->width,
785             s->h.v, &ScreenChangeStyleMetric,
786             (s->h.v < Style::FIRST_CUSTOM) ? 'w' : 'W');
787     } else {
788         Printf(false, "   %Ftwidth%E %s %D%f%Lp%Fl[change]%E",
789             SS.MmToString(s->width).c_str(),
790             s->h.v, &ScreenChangeStyleMetric,
791             (s->h.v < Style::FIRST_CUSTOM) ? 'w' : 'W');
792     }
793 
794     if(s->h.v >= Style::FIRST_CUSTOM) {
795         if(s->widthAs == Style::UNITS_AS_PIXELS) {
796             Printf(false, "%Ba   %Ftstipple width%E %@ %D%f%Lp%Fl[change]%E",
797                 s->stippleScale,
798                 s->h.v, &ScreenChangeStyleMetric, 's');
799         } else {
800             Printf(false, "%Ba   %Ftstipple width%E %s %D%f%Lp%Fl[change]%E",
801                 SS.MmToString(s->stippleScale).c_str(),
802                 s->h.v, &ScreenChangeStyleMetric, 's');
803         }
804     }
805 
806     bool widthpx = (s->widthAs == Style::UNITS_AS_PIXELS);
807     if(s->h.v < Style::FIRST_CUSTOM) {
808         Printf(false,"   %Ftin units of %Fdpixels%E");
809     } else {
810         Printf(false,"%Ba   %Ftin units of  %Fd"
811                             "%D%f%LW%s pixels%E  "
812                             "%D%f%Lw%s %s",
813             s->h.v, &ScreenChangeStyleYesNo,
814             widthpx ? RADIO_TRUE : RADIO_FALSE,
815             s->h.v, &ScreenChangeStyleYesNo,
816             !widthpx ? RADIO_TRUE : RADIO_FALSE,
817             SS.UnitName());
818     }
819 
820     Printf(false,"%Ba   %Ftstipple type:%E");
821 
822     const size_t patternCount = Style::LAST_STIPPLE + 1;
823     const char *patternsSource[patternCount] = {
824         "___________",
825         "- - - - - -",
826         "__ __ __ __",
827         "-.-.-.-.-.-",
828         "..-..-..-..",
829         "...........",
830         "~~~~~~~~~~~",
831         "__~__~__~__"
832     };
833     std::string patterns[patternCount];
834 
835     for(int i = 0; i <= Style::LAST_STIPPLE; i++) {
836         const char *str = patternsSource[i];
837         do {
838             switch(*str) {
839                 case ' ': patterns[i] += " "; break;
840                 case '.': patterns[i] += "\xEE\x80\x84"; break;
841                 case '_': patterns[i] += "\xEE\x80\x85"; break;
842                 case '-': patterns[i] += "\xEE\x80\x86"; break;
843                 case '~': patterns[i] += "\xEE\x80\x87"; break;
844                 default: oops();
845             }
846         } while(*(++str));
847     }
848 
849     for(int i = 0; i <= Style::LAST_STIPPLE; i++) {
850         const char *radio = s->stippleType == i ? RADIO_TRUE : RADIO_FALSE;
851         Printf(false, "%Bp     %D%f%Lp%s %s%E",
852             (i % 2 == 0) ? 'd' : 'a',
853             s->h.v, &ScreenChangeStylePatternType,
854             i + 1, radio, patterns[i].c_str());
855     }
856 
857     if(s->h.v >= Style::FIRST_CUSTOM) {
858         // The fill color, and whether contours are filled
859 
860         Printf(false, "");
861         Printf(false, "%Ft contour fill style%E");
862         Printf(false,
863             "%Ba   %Ftcolor %E%Bz  %Ba (%@, %@, %@) %D%f%Lf%Fl[change]%E",
864             &s->fillColor,
865             s->fillColor.redF(), s->fillColor.greenF(), s->fillColor.blueF(),
866             s->h.v, ScreenChangeStyleColor);
867 
868         Printf(false, "%Bd   %D%f%Lf%s  contours are filled%E",
869             s->h.v, &ScreenChangeStyleYesNo,
870             s->filled ? CHECK_TRUE : CHECK_FALSE);
871     }
872 
873     // The text height, and its units
874     Printf(false, "");
875     Printf(false, "%Ft text style%E");
876 
877     if(s->textHeightAs == Style::UNITS_AS_PIXELS) {
878         Printf(false, "%Ba   %Ftheight %E%@ %D%f%Lt%Fl%s%E",
879             s->textHeight,
880             s->h.v, &ScreenChangeStyleMetric,
881             "[change]");
882     } else {
883         Printf(false, "%Ba   %Ftheight %E%s %D%f%Lt%Fl%s%E",
884             SS.MmToString(s->textHeight).c_str(),
885             s->h.v, &ScreenChangeStyleMetric,
886             "[change]");
887     }
888 
889     bool textHeightpx = (s->textHeightAs == Style::UNITS_AS_PIXELS);
890     if(s->h.v < Style::FIRST_CUSTOM) {
891         Printf(false,"%Bd   %Ftin units of %Fdpixels");
892     } else {
893         Printf(false,"%Bd   %Ftin units of  %Fd"
894                             "%D%f%LG%s pixels%E  "
895                             "%D%f%Lg%s %s",
896             s->h.v, &ScreenChangeStyleYesNo,
897             textHeightpx ? RADIO_TRUE : RADIO_FALSE,
898             s->h.v, &ScreenChangeStyleYesNo,
899             !textHeightpx ? RADIO_TRUE : RADIO_FALSE,
900             SS.UnitName());
901     }
902 
903     if(s->h.v >= Style::FIRST_CUSTOM) {
904         Printf(false, "%Ba   %Ftangle %E%@ %D%f%Ll%Fl[change]%E",
905             s->textAngle,
906             s->h.v, &ScreenChangeStyleTextAngle);
907 
908         Printf(false, "");
909         Printf(false, "%Ft text comment alignment%E");
910         bool neither;
911         neither = !(s->textOrigin & (Style::ORIGIN_LEFT | Style::ORIGIN_RIGHT));
912         Printf(false, "%Ba   "
913                       "%D%f%LL%s left%E    "
914                       "%D%f%LH%s center%E  "
915                       "%D%f%LR%s right%E  ",
916             s->h.v, &ScreenChangeStyleYesNo,
917             (s->textOrigin & Style::ORIGIN_LEFT) ? RADIO_TRUE : RADIO_FALSE,
918             s->h.v, &ScreenChangeStyleYesNo,
919             neither ? RADIO_TRUE : RADIO_FALSE,
920             s->h.v, &ScreenChangeStyleYesNo,
921             (s->textOrigin & Style::ORIGIN_RIGHT) ? RADIO_TRUE : RADIO_FALSE);
922 
923         neither = !(s->textOrigin & (Style::ORIGIN_BOT | Style::ORIGIN_TOP));
924         Printf(false, "%Bd   "
925                       "%D%f%LB%s bottom%E  "
926                       "%D%f%LV%s center%E  "
927                       "%D%f%LT%s top%E  ",
928             s->h.v, &ScreenChangeStyleYesNo,
929             (s->textOrigin & Style::ORIGIN_BOT) ? RADIO_TRUE : RADIO_FALSE,
930             s->h.v, &ScreenChangeStyleYesNo,
931             neither ? RADIO_TRUE : RADIO_FALSE,
932             s->h.v, &ScreenChangeStyleYesNo,
933             (s->textOrigin & Style::ORIGIN_TOP) ? RADIO_TRUE : RADIO_FALSE);
934     }
935 
936     if(s->h.v >= Style::FIRST_CUSTOM) {
937         Printf(false, "");
938 
939         Printf(false, "  %Fd%D%f%Lv%s  show these objects on screen%E",
940                 s->h.v, &ScreenChangeStyleYesNo,
941                 s->visible ? CHECK_TRUE : CHECK_FALSE);
942 
943         Printf(false, "  %Fd%D%f%Le%s  export these objects%E",
944                 s->h.v, &ScreenChangeStyleYesNo,
945                 s->exportable ? CHECK_TRUE : CHECK_FALSE);
946 
947         Printf(false, "");
948         Printf(false, "To assign lines or curves to this style,");
949         Printf(false, "right-click them on the drawing.");
950     }
951 }
952 
ScreenAssignSelectionToStyle(int link,uint32_t v)953 void TextWindow::ScreenAssignSelectionToStyle(int link, uint32_t v) {
954     Style::AssignSelectionToStyle(v);
955 }
956 
957