]> Shamusworld >> Repos - architektonas/blob - src/base/text.cpp
Bugfixes related to removing Snapper class.
[architektonas] / src / base / text.cpp
1 // text.cpp
2 //
3 // Part of the Architektonas Project
4 // Originally part of QCad Community Edition by Andrew Mustun
5 // Extensively rewritten and refactored by James L. Hammons
6 // Portions copyright (C) 2001-2003 RibbonSoft
7 // Copyright (C) 2010 Underground Software
8 // See the README and GPLv2 files for licensing and warranty information
9 //
10 // JLH = James L. Hammons <jlhamm@acm.org>
11 //
12 // Who  When        What
13 // ---  ----------  -----------------------------------------------------------
14 // JLH  05/27/2010  Added this text. :-)
15 //
16
17 #include "text.h"
18
19 #include "font.h"
20 #include "fontlist.h"
21 #include "insert.h"
22
23 /**
24  * Constructor.
25  */
26 Text::Text(EntityContainer * parent, const TextData & d): EntityContainer(parent), data(d)
27 {
28         usedTextHeight = 0.0;
29         usedTextWidth = 0.0;
30         setText(data.text);
31 }
32
33 /*virtual*/ Text::~Text()
34 {
35 }
36
37 /*virtual*/ Entity * Text::clone()
38 {
39         Text * t = new Text(*this);
40 #warning "!!! Need to deal with setAutoDelete() Qt3->Qt4 !!!"
41 //      t->entities.setAutoDelete(entities.autoDelete());
42         t->initId();
43         t->detach();
44         return t;
45 }
46
47 /**     @return RS2::EntityText */
48 /*virtual*/ RS2::EntityType Text::rtti() const
49 {
50         return RS2::EntityText;
51 }
52
53 /** @return Copy of data that defines the text. */
54 TextData Text::getData() const
55 {
56         return data;
57 }
58
59 /**
60  * @return Number of lines in this text entity.
61  */
62 int Text::getNumberOfLines()
63 {
64         int c = 1;
65
66         for(int i=0; i<(int)data.text.length(); i++)
67         {
68                 if (data.text.at(i).unicode() == 0x0A)
69                         c++;
70         }
71
72         return c;
73 }
74
75 /**
76  * Updates the Inserts (letters) of this text. Called when the
77  * text or it's data, position, alignment, .. changes.
78  * This method also updates the usedTextWidth / usedTextHeight property.
79  */
80 void Text::update()
81 {
82 #if 0
83 printf("Text::update(): ");
84 #endif
85         DEBUG->print("Text::update");
86         clear();
87
88 #if 0
89 printf("isUndone=%s, ", (isUndone() ? "true" : "false"));
90 #endif
91         if (isUndone())
92                 return;
93
94         usedTextWidth = 0.0;
95         usedTextHeight = 0.0;
96
97 #if 0
98 printf("data.style=\"%s\", ", data.style.toAscii().data());
99 #endif
100         Font * font = FONTLIST->requestFont(data.style);
101
102 #if 0
103 printf("font=%08X\n", (unsigned int)font);
104 #endif
105         if (!font)
106                 return;
107
108         Vector letterPos = Vector(0.0, -9.0);
109         Vector letterSpace = Vector(font->getLetterSpacing(), 0.0);
110         Vector space = Vector(font->getWordSpacing(), 0.0);
111         int lineCounter = 0;
112
113         // Every single text line gets stored in this entity container
114         //  so we can move the whole line around easely:
115         EntityContainer * oneLine = new EntityContainer(this);
116
117         // First every text line is created with
118         //   alignement: top left
119         //   angle: 0
120         //   height: 9.0
121         // Rotation, scaling and centering is done later
122
123         // For every letter:
124         for(int i=0; i<(int)data.text.length(); ++i)
125         {
126                 switch (data.text.at(i).unicode())
127                 {
128                 case 0x0A:
129                         // line feed:
130                         updateAddLine(oneLine, lineCounter++);
131                         oneLine = new EntityContainer(this);
132                         letterPos = Vector(0.0, -9.0);
133                         break;
134
135                 case 0x20:
136                         // Space:
137                         letterPos += space;
138                         break;
139
140                 case 0x5C:
141                 {
142                         // code (e.g. \S, \P, ..)
143                         i++;
144                         int ch = data.text.at(i).unicode();
145
146                         switch (ch)
147                         {
148                         case 'P':
149                                 updateAddLine(oneLine, lineCounter++);
150                                 oneLine = new EntityContainer(this);
151                                 letterPos = Vector(0.0, -9.0);
152                                 break;
153
154                         case 'S':
155                         {
156                                 QString up;
157                                 QString dw;
158                                 //letterPos += letterSpace;
159
160                                 // get upper string:
161                                 i++;
162                                 while (data.text.at(i).unicode() != '^'
163                                         && data.text.at(i).unicode() != '\\'
164                                         && i < (int)data.text.length())
165                                 {
166                                         up += data.text.at(i);
167                                         i++;
168                                 }
169
170                                 i++;
171
172                                 if (data.text.at(i - 1).unicode() == '^'
173                                         && data.text.at(i).unicode() == ' ')
174                                 {
175                                         i++;
176                                 }
177
178                                 // get lower string:
179                                 while (data.text.at(i).unicode() != ';' && i < (int)data.text.length())
180                                 {
181                                         dw += data.text.at(i);
182                                         i++;
183                                 }
184
185                                 // add texts:
186                                 Text * upper = new Text(oneLine,
187                                         TextData(letterPos + Vector(0.0, 9.0), 4.0, 100.0,
188                                                 RS2::VAlignTop, RS2::HAlignLeft, RS2::LeftToRight,
189                                                 RS2::Exact, 1.0, up, data.style, 0.0, RS2::Update));
190                                 upper->setLayer(NULL);
191                                 upper->setPen(Pen(RS2::FlagInvalid));
192                                 oneLine->addEntity(upper);
193
194                                 Text * lower = new Text(oneLine,
195                                         TextData(letterPos+Vector(0.0, 4.0), 4.0, 100.0,
196                                                 RS2::VAlignTop, RS2::HAlignLeft, RS2::LeftToRight,
197                                                 RS2::Exact, 1.0, dw, data.style, 0.0, RS2::Update));
198                                 lower->setLayer(NULL);
199                                 lower->setPen(Pen(RS2::FlagInvalid));
200                                 oneLine->addEntity(lower);
201
202                                 // move cursor:
203                                 upper->calculateBorders();
204                                 lower->calculateBorders();
205
206                                 double w1 = upper->getSize().x;
207                                 double w2 = lower->getSize().x;
208
209                                 if (w1 > w2)
210                                         letterPos += Vector(w1, 0.0);
211                                 else
212                                         letterPos += Vector(w2, 0.0);
213
214                                 letterPos += letterSpace;
215                         }
216                                 break;
217
218                         default:
219                                 break;
220                         }
221                 }
222                         break;
223
224                 default:
225                 {
226 // Regular text works because the .style is "normallatin1" while the dimensions are done
227 // using "standard"... So that's why it doesn't work.
228 // "standard" exists, but it seems that no characters are loaded from it...
229 //count() == 0...
230 //printf("--> default: pos=%d, char=%04X, font size=%d\n", i, data.text.at(i).unicode(), font->getLetterList()->count());
231                         // One Letter:
232 //#warning "!!! font->findLetter() not working correctly !!!"
233                         if (font->findLetter(QString(data.text.at(i))))
234                         {
235 //We ain't getting here:
236 /*
237 About to draw TEXT entity... TEXT="107.25"
238 Dimension::updateCreateDimensionLine()...
239 --> 107.25
240 Text::update(): isUndone=false, font=094C1020
241 --> default: pos=0, char=0031
242 --> default: pos=1, char=0030
243 --> default: pos=2, char=0037
244 --> default: pos=3, char=002E
245 --> default: pos=4, char=0032
246 --> default: pos=5, char=0035
247 */
248 //printf("             char=%s\n", QString(data.text.at(i)).toAscii().data());
249                                 DEBUG->print("Text::update: insert a letter at pos: %f/%f", letterPos.x, letterPos.y);
250                                 InsertData d(QString(data.text.at(i)), letterPos, Vector(1.0, 1.0),
251                                         0.0, 1, 1, Vector(0.0, 0.0), font->getLetterList(), RS2::NoUpdate);
252
253                                 Insert * letter = new Insert(this, d);
254                                 letter->setPen(Pen(RS2::FlagInvalid));
255                                 letter->setLayer(NULL);
256                                 letter->update();
257                                 letter->forcedCalculateBorders();
258
259                                 // until 2.0.4.5:
260                                 //letterWidth = Vector(letter->getSize().x, 0.0);
261                                 // from 2.0.4.6:
262                                 Vector letterWidth = Vector(letter->getMax().x - letterPos.x, 0.0);
263                                 oneLine->addEntity(letter);
264
265                                 // next letter position:
266                                 letterPos += letterWidth;
267                                 letterPos += letterSpace;
268                         }
269                 }
270                         break;
271                 }
272         }
273
274         updateAddLine(oneLine, lineCounter);
275         usedTextHeight -= data.height * data.lineSpacingFactor * 1.6 - data.height;
276         forcedCalculateBorders();
277
278         DEBUG->print("Text::update: OK");
279 }
280
281 /**
282  * Used internally by update() to add a text line created with
283  * default values and alignment to this text container.
284  *
285  * @param textLine The text line.
286  * @param lineCounter Line number.
287  */
288 void Text::updateAddLine(EntityContainer* textLine, int lineCounter)
289 {
290         DEBUG->print("Text::updateAddLine: width: %f", textLine->getSize().x);
291
292         //textLine->forcedCalculateBorders();
293         //DEBUG->print("Text::updateAddLine: width 2: %f", textLine->getSize().x);
294
295         // Move to correct line position:
296         textLine->move(Vector(0.0, -9.0 * lineCounter * data.lineSpacingFactor * 1.6));
297
298         textLine->forcedCalculateBorders();
299         Vector textSize = textLine->getSize();
300
301         DEBUG->print("Text::updateAddLine: width 2: %f", textSize.x);
302
303         // Horizontal Align:
304         switch (data.halign)
305         {
306         case RS2::HAlignCenter:
307                 DEBUG->print("Text::updateAddLine: move by: %f", -textSize.x / 2.0);
308                 textLine->move(Vector(-textSize.x / 2.0, 0.0));
309                 break;
310
311         case RS2::HAlignRight:
312                 textLine->move(Vector(-textSize.x, 0.0));
313                 break;
314
315         default:
316                 break;
317         }
318
319         // Vertical Align:
320         double vSize = getNumberOfLines() * 9.0 * data.lineSpacingFactor * 1.6 - (9.0 * data.lineSpacingFactor * 1.6 - 9.0);
321
322         switch (data.valign)
323         {
324         case RS2::VAlignMiddle:
325                 textLine->move(Vector(0.0, vSize / 2.0));
326                 break;
327
328         case RS2::VAlignBottom:
329                 textLine->move(Vector(0.0, vSize));
330                 break;
331
332         default:
333                 break;
334         }
335
336         // Scale:
337         textLine->scale(Vector(0.0, 0.0), Vector(data.height / 9.0, data.height / 9.0));
338
339         textLine->forcedCalculateBorders();
340
341         // Update actual text size (before rotating, after scaling!):
342         if (textLine->getSize().x > usedTextWidth)
343                 usedTextWidth = textLine->getSize().x;
344
345         usedTextHeight += data.height * data.lineSpacingFactor * 1.6;
346
347         // Rotate:
348         textLine->rotate(Vector(0.0, 0.0), data.angle);
349
350         // Move:
351         textLine->move(data.insertionPoint);
352         textLine->setPen(Pen(RS2::FlagInvalid));
353         textLine->setLayer(NULL);
354         textLine->forcedCalculateBorders();
355
356         addEntity(textLine);
357 }
358
359 Vector Text::getInsertionPoint()
360 {
361         return data.insertionPoint;
362 }
363
364 double Text::getHeight()
365 {
366         return data.height;
367 }
368
369 void Text::setHeight(double h)
370 {
371         data.height = h;
372 }
373
374 double Text::getWidth()
375 {
376         return data.width;
377 }
378
379 /**
380  * Sets the alignment from an int.
381  *
382  * @param a 1: top left ... 9: bottom right
383  */
384 void Text::setAlignment(int a)
385 {
386         switch (a % 3)
387         {
388         default:
389         case 1:
390                 data.halign = RS2::HAlignLeft;
391                 break;
392         case 2:
393                 data.halign = RS2::HAlignCenter;
394                 break;
395         case 0:
396                 data.halign = RS2::HAlignRight;
397                 break;
398         }
399
400         switch ((int)ceil(a / 3.0))
401         {
402         default:
403         case 1:
404                 data.valign = RS2::VAlignTop;
405                 break;
406         case 2:
407                 data.valign = RS2::VAlignMiddle;
408                 break;
409         case 3:
410                 data.valign = RS2::VAlignBottom;
411                 break;
412         }
413 }
414
415 /**
416  * Gets the alignment as an int.
417  *
418  * @return  1: top left ... 9: bottom right
419  */
420 int Text::getAlignment()
421 {
422         if (data.valign == RS2::VAlignTop)
423         {
424                 if (data.halign == RS2::HAlignLeft)
425                 {
426                         return 1;
427                 }
428                 else if (data.halign == RS2::HAlignCenter)
429                 {
430                         return 2;
431                 }
432                 else if (data.halign == RS2::HAlignRight)
433                 {
434                         return 3;
435                 }
436         }
437         else if (data.valign == RS2::VAlignMiddle)
438         {
439                 if (data.halign == RS2::HAlignLeft)
440                 {
441                         return 4;
442                 }
443                 else if (data.halign == RS2::HAlignCenter)
444                 {
445                         return 5;
446                 }
447                 else if (data.halign == RS2::HAlignRight)
448                 {
449                         return 6;
450                 }
451         }
452         else if (data.valign == RS2::VAlignBottom)
453         {
454                 if (data.halign == RS2::HAlignLeft)
455                 {
456                         return 7;
457                 }
458                 else if (data.halign == RS2::HAlignCenter)
459                 {
460                         return 8;
461                 }
462                 else if (data.halign == RS2::HAlignRight)
463                 {
464                         return 9;
465                 }
466         }
467
468         return 1;
469 }
470
471 RS2::VAlign Text::getVAlign()
472 {
473         return data.valign;
474 }
475
476 void Text::setVAlign(RS2::VAlign va)
477 {
478         data.valign = va;
479 }
480
481 RS2::HAlign Text::getHAlign()
482 {
483         return data.halign;
484 }
485
486 void Text::setHAlign(RS2::HAlign ha)
487 {
488         data.halign = ha;
489 }
490
491 RS2::TextDrawingDirection Text::getDrawingDirection()
492 {
493         return data.drawingDirection;
494 }
495
496 RS2::TextLineSpacingStyle Text::getLineSpacingStyle()
497 {
498         return data.lineSpacingStyle;
499 }
500
501 void Text::setLineSpacingFactor(double f)
502 {
503         data.lineSpacingFactor = f;
504 }
505
506 double Text::getLineSpacingFactor()
507 {
508         return data.lineSpacingFactor;
509 }
510
511 /**
512  * Sets a new text. The entities representing the
513  * text are updated.
514  */
515 void Text::setText(const QString & t)
516 {
517         data.text = t;
518
519         // handle some special flags embedded in the text:
520         if (data.text.left(4) == "\\A0;")
521         {
522                 data.text = data.text.mid(4);
523                 data.valign = RS2::VAlignBottom;
524         }
525         else if (data.text.left(4) == "\\A1;")
526         {
527                 data.text = data.text.mid(4);
528                 data.valign = RS2::VAlignMiddle;
529         }
530         else if (data.text.left(4) == "\\A2;")
531         {
532                 data.text = data.text.mid(4);
533                 data.valign = RS2::VAlignTop;
534         }
535
536         if (data.updateMode == RS2::Update)
537         {
538                 update();
539                 //calculateBorders();
540         }
541 }
542
543 QString Text::getText()
544 {
545         return data.text;
546 }
547
548 void Text::setStyle(const QString & s)
549 {
550         data.style = s;
551 }
552
553 QString Text::getStyle()
554 {
555         return data.style;
556 }
557
558 void Text::setAngle(double a)
559 {
560         data.angle = a;
561 }
562
563 double Text::getAngle()
564 {
565         return data.angle;
566 }
567
568 double Text::getUsedTextWidth()
569 {
570         return usedTextWidth;
571 }
572
573 double Text::getUsedTextHeight()
574 {
575         return usedTextHeight;
576 }
577
578 /*virtual*/ double Text::getLength()
579 {
580         return -1.0;
581 }
582
583 VectorSolutions Text::getRefPoints()
584 {
585         VectorSolutions ret(data.insertionPoint);
586         return ret;
587 }
588
589 Vector Text::getNearestRef(const Vector & coord, double * dist)
590 {
591         return Entity::getNearestRef(coord, dist);
592 }
593
594 void Text::move(Vector offset)
595 {
596         data.insertionPoint.move(offset);
597         update();
598 }
599
600 void Text::rotate(Vector center, double angle)
601 {
602         data.insertionPoint.rotate(center, angle);
603         data.angle = Math::correctAngle(data.angle + angle);
604         update();
605 }
606
607 void Text::scale(Vector center, Vector factor)
608 {
609         data.insertionPoint.scale(center, factor);
610         data.width *= factor.x;
611         data.height *= factor.x;
612         update();
613 }
614
615 void Text::mirror(Vector axisPoint1, Vector axisPoint2)
616 {
617         data.insertionPoint.mirror(axisPoint1, axisPoint2);
618         //double ang = axisPoint1.angleTo(axisPoint2);
619         bool readable = Math::isAngleReadable(data.angle);
620
621         Vector vec;
622         vec.setPolar(1.0, data.angle);
623         vec.mirror(Vector(0.0, 0.0), axisPoint2 - axisPoint1);
624         data.angle = vec.angle();
625
626         bool corr;
627         data.angle = Math::makeAngleReadable(data.angle, readable, &corr);
628
629         if (corr)
630         {
631                 if (data.halign == RS2::HAlignLeft)
632                 {
633                         data.halign = RS2::HAlignRight;
634                 }
635                 else if (data.halign == RS2::HAlignRight)
636                 {
637                         data.halign = RS2::HAlignLeft;
638                 }
639         }
640         else
641         {
642                 if (data.valign == RS2::VAlignTop)
643                 {
644                         data.valign = RS2::VAlignBottom;
645                 }
646                 else if (data.valign == RS2::VAlignBottom)
647                 {
648                         data.valign = RS2::VAlignTop;
649                 }
650         }
651
652         update();
653 }
654
655 bool Text::hasEndpointsWithinWindow(Vector /*v1*/, Vector /*v2*/)
656 {
657         return false;
658 }
659
660 /**
661  * Implementations must stretch the given range of the entity
662  * by the given offset.
663  */
664 void Text::stretch(Vector firstCorner, Vector secondCorner, Vector offset)
665 {
666         if (getMin().isInWindow(firstCorner, secondCorner)
667                 && getMax().isInWindow(firstCorner, secondCorner))
668                 move(offset);
669 }
670
671 /**
672  * Dumps the point's data to stdout.
673  */
674 std::ostream & operator<<(std::ostream & os, const Text & p)
675 {
676         os << " Text: " << p.getData() << "\n";
677         return os;
678 }