1 /*
2 For general Scribus (>=1.3.2) copyright and licensing information please refer
3 to the COPYING file provided with the program. Following this notice may exist
4 a copyright and/or license notice that predates the release of Scribus 1.3.2
5 for which a new license (GPL+exception) is in place.
6 */
7 /***************************************************************************
8 pageitem.cpp - description
9 -------------------
10 begin : Sat Apr 7 2001
11 copyright : (C) 2001 by Franz Schmid
12 email : Franz.Schmid@altmuehlnet.de
13 ***************************************************************************/
14
15 /***************************************************************************
16 * *
17 * This program is free software; you can redistribute it and/or modify *
18 * it under the terms of the GNU General Public License as published by *
19 * the Free Software Foundation; either version 2 of the License, or *
20 * (at your option) any later version. *
21 * *
22 ***************************************************************************/
23
24 #include "pageitem_pathtext.h"
25 #include <QGridLayout>
26 #include <QImage>
27 #include <QLabel>
28 #include <QList>
29 #include <QPainterPath>
30
31 #include <cassert>
32
33 #include "scconfig.h"
34
35 #include "commonstrings.h"
36 #include "pageitem.h"
37 #include "prefsmanager.h"
38 #include "scpage.h"
39 #include "scpainter.h"
40 #include "scpaths.h"
41 #include "scraction.h"
42
43 #include "scribusstructs.h"
44 #include "scribusdoc.h"
45 #include "undomanager.h"
46 #include "undostate.h"
47 #include "util.h"
48 #include "util_math.h"
49 #include "text/boxes.h"
50 #include "text/textlayoutpainter.h"
51 #include "text/textshaper.h"
52 #include "text/screenpainter.h"
53
54 using namespace std;
55
PageItem_PathText(ScribusDoc * pa,double x,double y,double w,double h,double w2,const QString & fill,const QString & outline)56 PageItem_PathText::PageItem_PathText(ScribusDoc *pa, double x, double y, double w, double h, double w2, const QString& fill, const QString& outline)
57 : PageItem(pa, PageItem::PathText, x, y, w, h, w2, fill, outline)
58 {
59 firstChar = 0;
60 m_maxChars = itemText.length();
61 }
62
63
layout()64 void PageItem_PathText::layout()
65 {
66 QImage pgPix(10, 10, QImage::Format_ARGB32_Premultiplied);
67 QRectF rd; // = QRect(0,0,9,9);
68 ScPainter *painter = new ScPainter(&pgPix, pgPix.width(), pgPix.height());
69 DrawObj(painter, rd);
70 painter->end();
71 delete painter;
72 updatePolyClip();
73 }
74
75
DrawObj_Item(ScPainter * p,QRectF cullingArea)76 void PageItem_PathText::DrawObj_Item(ScPainter *p, QRectF cullingArea)
77 {
78 itemText.invalidateAll();
79 firstChar = 0;
80 m_maxChars = 0;
81 FPoint point = FPoint(0, 0);
82 FPoint tangent = FPoint(0, 0);
83 CurX = m_textDistanceMargins.left();
84
85 if (!m_Doc->layerOutline(m_layerID))
86 {
87 if (PoShow)
88 {
89 p->setupPolygon(&PoLine, false);
90 if (NamedLStyle.isEmpty())
91 {
92 if ((!patternStrokeVal.isEmpty()) && (m_Doc->docPatterns.contains(patternStrokeVal)))
93 {
94 if (patternStrokePath)
95 {
96 QPainterPath guidePath = PoLine.toQPainterPath(false);
97 DrawStrokePattern(p, guidePath);
98 }
99 else
100 {
101 p->setPattern(&m_Doc->docPatterns[patternStrokeVal], patternStrokeScaleX, patternStrokeScaleY, patternStrokeOffsetX, patternStrokeOffsetY, patternStrokeRotation, patternStrokeSkewX, patternStrokeSkewY, patternStrokeMirrorX, patternStrokeMirrorY);
102 p->setStrokeMode(ScPainter::Pattern);
103 p->strokePath();
104 }
105 }
106 else if (GrTypeStroke > 0)
107 {
108 if ((!gradientStrokeVal.isEmpty()) && (!m_Doc->docGradients.contains(gradientStrokeVal)))
109 gradientStrokeVal.clear();
110 if (!(gradientStrokeVal.isEmpty()) && (m_Doc->docGradients.contains(gradientStrokeVal)))
111 stroke_gradient = m_Doc->docGradients[gradientStrokeVal];
112 if (stroke_gradient.stops() < 2) // fall back to solid stroking if there are not enough colorstops in the gradient.
113 {
114 if (lineColor() != CommonStrings::None)
115 {
116 p->setBrush(m_strokeQColor);
117 p->setStrokeMode(ScPainter::Solid);
118 }
119 else
120 p->setStrokeMode(ScPainter::None);
121 }
122 else
123 {
124 p->setStrokeMode(ScPainter::Gradient);
125 p->stroke_gradient = stroke_gradient;
126 if (GrTypeStroke == Gradient_Linear)
127 p->setGradient(VGradient::linear, FPoint(GrStrokeStartX, GrStrokeStartY), FPoint(GrStrokeEndX, GrStrokeEndY), FPoint(GrStrokeStartX, GrStrokeStartY), GrStrokeScale, GrStrokeSkew);
128 else
129 p->setGradient(VGradient::radial, FPoint(GrStrokeStartX, GrStrokeStartY), FPoint(GrStrokeEndX, GrStrokeEndY), FPoint(GrStrokeFocalX, GrStrokeFocalY), GrStrokeScale, GrStrokeSkew);
130 }
131 p->strokePath();
132 }
133 else if (lineColor() != CommonStrings::None)
134 {
135 p->setStrokeMode(ScPainter::Solid);
136 p->strokePath();
137 }
138 }
139 else
140 {
141 p->setStrokeMode(ScPainter::Solid);
142 multiLine ml = m_Doc->docLineStyles[NamedLStyle];
143 QColor tmp;
144 for (int it = ml.size()-1; it > -1; it--)
145 {
146 if (ml[it].Color != CommonStrings::None) // && (ml[it].Width != 0))
147 {
148 SetQColor(&tmp, ml[it].Color, ml[it].Shade);
149 p->setPen(tmp, ml[it].Width, static_cast<Qt::PenStyle>(ml[it].Dash), static_cast<Qt::PenCapStyle>(ml[it].LineEnd), static_cast<Qt::PenJoinStyle>(ml[it].LineJoin));
150 p->strokePath();
151 }
152 }
153 }
154 }
155 }
156
157 double totalTextLen = 0.0;
158 double totalCurveLen = 0.0;
159 double extraOffset = 0.0;
160 if (itemText.length() <= 0)
161 return;
162 CurX += itemText.charStyle(0).fontSize() * itemText.charStyle(0).tracking() / 10000.0;
163
164 textLayout.setStory(&itemText);
165 int spaceCount = 0;
166 double wordExtra = 0;
167
168 TextShaper textShaper(this, itemText, firstChar, true);
169 const QList<GlyphCluster> glyphRuns = textShaper.shape(0, itemText.length()).glyphs();
170 if (glyphRuns.isEmpty())
171 return;
172
173 // enable seamless crossing past the endpoint for closed curve
174 bool curveClosed = PoLine.isBezierClosed();
175 bool canWrapAround = curveClosed;
176
177 for (const GlyphCluster& run : glyphRuns)
178 {
179 totalTextLen += run.width();
180 if (run.hasFlag(ScLayout_ExpandingSpace))
181 spaceCount++;
182 }
183
184 for (int segs = 0; segs < PoLine.size()-3; segs += 4)
185 {
186 totalCurveLen += PoLine.lenPathSeg(segs);
187 }
188 if ((itemText.paragraphStyle(0).alignment() != ParagraphStyle::LeftAligned) && (totalCurveLen >= totalTextLen + m_textDistanceMargins.left()))
189 {
190 if (itemText.paragraphStyle(0).alignment() == ParagraphStyle::RightAligned)
191 {
192 CurX = totalCurveLen - totalTextLen;
193 CurX -= m_textDistanceMargins.left();
194 }
195 if (itemText.paragraphStyle(0).alignment() == ParagraphStyle::Centered)
196 CurX = ((totalCurveLen - totalTextLen) / 2.0) + m_textDistanceMargins.left();
197 if (itemText.paragraphStyle(0).alignment() == ParagraphStyle::Justified)
198 {
199 if (spaceCount != 0)
200 {
201 extraOffset = 0;
202 wordExtra = (totalCurveLen - m_textDistanceMargins.left() - totalTextLen) / static_cast<double>(spaceCount);
203 }
204 else
205 {
206 extraOffset = (totalCurveLen - m_textDistanceMargins.left() - totalTextLen) / static_cast<double>(itemText.length());
207 wordExtra = 0;
208 }
209 }
210 if (itemText.paragraphStyle(0).alignment() == ParagraphStyle::Extended)
211 extraOffset = (totalCurveLen - m_textDistanceMargins.left() - totalTextLen) / static_cast<double>(itemText.length());
212 }
213 int firstRun = 0;
214 if (!curveClosed && (totalTextLen + m_textDistanceMargins.left() > totalCurveLen) && (itemText.paragraphStyle(0).direction() == ParagraphStyle::RTL))
215 {
216 double totalLenDiff = totalTextLen + m_textDistanceMargins.left() - totalCurveLen;
217 while (firstRun < glyphRuns.count())
218 {
219 const GlyphCluster &run = glyphRuns.at(firstRun);
220 totalLenDiff -= run.width();
221 firstRun++;
222 if (totalLenDiff <= 0)
223 break;
224 }
225 }
226
227 int currPathIndex = 0;
228 QPainterPath guidePath = PoLine.toQPainterPath(false);
229 QList<QPainterPath> pathList = decomposePath(guidePath);
230 QPainterPath currPath = pathList[0];
231 PathLineBox* linebox = new PathLineBox();
232
233 double lastPathPerc = currPath.percentAtLength(CurX);
234 double totalPathPerc = 0.0;
235
236 for (int i = firstRun; i < glyphRuns.length(); i++)
237 {
238 const GlyphCluster &run = glyphRuns.at(i);
239 double dx = run.width() / 2.0;
240 CurX += dx;
241 CurY = 0;
242 double currPerc = currPath.percentAtLength(CurX);
243 if (currPerc >= 0.9999999)
244 {
245 // sticks out to the next segment
246 currPathIndex++;
247 if (currPathIndex == pathList.count())
248 {
249 if (!curveClosed || !canWrapAround)
250 {
251 m_maxChars = run.firstChar();
252 break;
253 }
254 canWrapAround = false;
255 currPathIndex = 0;
256 }
257 CurX -= currPath.length();
258 currPath = pathList[currPathIndex];
259 currPerc = currPath.percentAtLength(CurX);
260 lastPathPerc = 0.0;
261 }
262
263 // Prevent total text length to exceed the length of path
264 totalPathPerc += (currPerc - lastPathPerc);
265 if (lastPathPerc > currPerc)
266 totalPathPerc -= 1.0;
267 if (totalPathPerc >= pathList.count() - 1e-7)
268 {
269 m_maxChars = run.firstChar();
270 break;
271 }
272 lastPathPerc = currPerc;
273
274 double currAngle = currPath.angleAtPercent(currPerc);
275 if (currAngle <= 180.0)
276 currAngle *= -1.0;
277 else
278 currAngle = 360.0 - currAngle;
279 QPointF currPoint = currPath.pointAtPercent(currPerc);
280 tangent = FPoint(cos(currAngle * M_PI / 180.0), sin(currAngle * M_PI / 180.0));
281 point = FPoint(currPoint.x(), currPoint.y());
282 QTransform trafo = QTransform( 1, 0, 0, -1, -dx, 0 );
283 if (textPathFlipped)
284 trafo *= QTransform(1, 0, 0, -1, 0, 0);
285 if (textPathType == 0)
286 trafo *= QTransform( tangent.x(), tangent.y(), tangent.y(), -tangent.x(), point.x(), point.y() ); // ID's Rainbow mode
287 else if (textPathType == 1)
288 trafo *= QTransform( 1, 0, 0, -1, point.x(), point.y() ); // ID's Stair Step mode
289 else if (textPathType == 2)
290 {
291 double a = 1;
292 if (tangent.x() < 0)
293 a = -1;
294 if (fabs(tangent.x()) > 0.1)
295 trafo *= QTransform( a, (tangent.y() / tangent.x()) * a, 0, -1, point.x(), point.y() ); // ID's Skew mode
296 else
297 trafo *= QTransform( a, 4 * a, 0, -1, point.x(), point.y() );
298 }
299 trafo.translate(0, BaseOffs);
300
301 const CharStyle& cStyle(run.style());
302 double scaleV = cStyle.scaleV() / 1000.0;
303 double offset = (cStyle.fontSize() / 10) * (cStyle.baselineOffset() / 1000.0);
304 double ascent = cStyle.font().ascent(cStyle.fontSize()/10.00) * scaleV + offset;
305 double descent = cStyle.font().descent(cStyle.fontSize()/10.00) * scaleV - offset;
306 linebox->setAscent(ascent);
307 linebox->setDescent(descent);
308 Box* box;
309 if (run.object().getPageItem(m_Doc))
310 {
311 box = new ObjectBox(run, this);
312 box->setAscent(this->getVisualBoundingBox(run.object()).height());
313 box->setDescent(0);
314 }
315 else
316 {
317 box = new GlyphBox(run);
318 box->setAscent(linebox->ascent());
319 box->setDescent(linebox->descent());
320 }
321
322 box->setMatrix(trafo);
323 linebox->addBox(box);
324
325 m_maxChars = run.lastChar() + 1;
326 CurX -= dx;
327 CurX += run.width() + cStyle.fontSize() * cStyle.tracking() / 10000.0 + extraOffset;
328 if (run.hasFlag(ScLayout_ExpandingSpace))
329 CurX += wordExtra;
330 }
331
332 textLayout.addColumn(0, 0);
333 textLayout.appendLine(linebox);
334
335 if (!m_Doc->RePos)
336 {
337 int fm = p->fillMode();
338 p->setFillMode(ScPainter::Solid);
339 p->save();
340 ScreenPainter painter(p, this);
341 textLayout.render(&painter);
342 p->setFillMode(fm);
343 p->restore();
344 }
345 }
346
createInfoGroup(QFrame * infoGroup,QGridLayout * infoGroupLayout)347 bool PageItem_PathText::createInfoGroup(QFrame *infoGroup, QGridLayout *infoGroupLayout)
348 {
349 int Parag = 0;
350 int Words = 0;
351 int Chara = 0;
352 int ParagN = 0;
353 int WordsN = 0;
354 int CharaN = 0;
355
356 QLabel *infoCT = new QLabel(infoGroup);
357 QLabel *linesCT = new QLabel(infoGroup);
358 QLabel *linesT = new QLabel(infoGroup);
359 QLabel *parCT = new QLabel(infoGroup);
360 QLabel *parT = new QLabel(infoGroup);
361 QLabel *wordCT = new QLabel(infoGroup);
362 QLabel *wordT = new QLabel(infoGroup);
363 QLabel *charCT = new QLabel(infoGroup);
364 QLabel *charT = new QLabel(infoGroup);
365
366
367 infoCT->setText("Text on a Path");
368 infoGroupLayout->addWidget( infoCT, 0, 0, 1, 2, Qt::AlignCenter );
369
370 WordAndPara(this, &Words, &Parag, &Chara, &WordsN, &ParagN, &CharaN);
371 parCT->setText(tr("Paragraphs: "));
372 infoGroupLayout->addWidget( parCT, 1, 0, Qt::AlignRight );
373 if (ParagN != 0)
374 parT->setText(QString::number(Parag+ParagN)+" ("+QString::number(ParagN)+")");
375 else
376 parT->setText(QString::number(Parag));
377 infoGroupLayout->addWidget( parT, 1, 1 );
378
379 linesCT->setText(tr("Lines: "));
380 infoGroupLayout->addWidget( linesCT, 2, 0, Qt::AlignRight );
381 linesT->setText(QString::number(textLayout.lines()));
382 infoGroupLayout->addWidget( linesT, 2, 1 );
383
384
385 wordCT->setText(tr("Words: "));
386 infoGroupLayout->addWidget( wordCT, 3, 0, Qt::AlignRight );
387 if (WordsN != 0)
388 wordT->setText(QString::number(Words+WordsN)+" ("+QString::number(WordsN)+")");
389 else
390 wordT->setText(QString::number(Words));
391 infoGroupLayout->addWidget( wordT, 3, 1 );
392
393 charCT->setText(tr("Chars: "));
394 infoGroupLayout->addWidget(charCT, 4, 0, Qt::AlignRight );
395 if (CharaN != 0)
396 charT->setText(QString::number(Chara+CharaN)+" ("+QString::number(CharaN)+")");
397 else
398 charT->setText(QString::number(Chara));
399 infoGroupLayout->addWidget( charT, 4, 1 );
400 return true;
401 }
402
applicableActions(QStringList & actionList)403 void PageItem_PathText::applicableActions(QStringList & actionList)
404 {
405 actionList << "toolsEditWithStoryEditor";
406 actionList << "itemConvertToOutlines";
407 }
408
infoDescription() const409 QString PageItem_PathText::infoDescription() const
410 {
411 return QString();
412 }
413
getVisualBoundingRect(double * x1,double * y1,double * x2,double * y2) const414 void PageItem_PathText::getVisualBoundingRect(double * x1, double * y1, double * x2, double * y2) const
415 {
416 PageItem::getVisualBoundingRect(x1, y1, x2, y2);
417 QRectF totalRect(QPointF(*x1, *y1), QPointF(*x2, *y2));
418 QTransform clipTrans;
419 clipTrans.translate(m_xPos, m_yPos);
420 clipTrans.rotate(m_rotation);
421 totalRect = totalRect.united(QRectF(clipTrans.mapRect(Clip.boundingRect())));
422 totalRect.getCoords(x1, y1, x2, y2);
423 }
424
visualXPos() const425 double PageItem_PathText::visualXPos() const
426 {
427 double extraSpace = visualLineWidth() / 2.0;
428 return qMin(m_xPos + QRectF(Clip.boundingRect()).x(), m_xPos - extraSpace);
429 }
430
visualYPos() const431 double PageItem_PathText::visualYPos() const
432 {
433 double extraSpace = visualLineWidth() / 2.0;
434 return qMin(m_yPos + QRectF(Clip.boundingRect()).y(), m_yPos - extraSpace);
435 }
436
visualWidth() const437 double PageItem_PathText::visualWidth() const
438 {
439 double extraSpace = visualLineWidth();
440 return qMax(QRectF(Clip.boundingRect()).width(), m_width + extraSpace);
441 }
442
visualHeight() const443 double PageItem_PathText::visualHeight() const
444 {
445 double extraSpace = visualLineWidth();
446 return qMax(QRectF(Clip.boundingRect()).height(), m_height + extraSpace);
447 }
448