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