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