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