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