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                           svgexplugin.cpp  -  description
9                              -------------------
10     begin                : Sun Aug 3 08:00:00 CEST 2002
11     copyright            : (C) 2002 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 <QBuffer>
25 #include <QByteArray>
26 #include <QCheckBox>
27 #include <QDataStream>
28 #include <QFile>
29 #include <QList>
30 #include <QMessageBox>
31 #include <QScopedPointer>
32 #include <QTextStream>
33 
34 #include "svgexplugin.h"
35 
36 #include "scconfig.h"
37 #include "canvas.h"
38 #include "cmsettings.h"
39 #include "commonstrings.h"
40 #include "pageitem_table.h"
41 #include "prefsmanager.h"
42 #include "prefsfile.h"
43 #include "prefscontext.h"
44 #include "qtiocompressor.h"
45 #include "scpage.h"
46 #include "scpattern.h"
47 #include "scribuscore.h"
48 #include "sctextstruct.h"
49 #include "tableutils.h"
50 #include "util.h"
51 #include "ui/customfdialog.h"
52 #include "ui/guidemanager.h"
53 #include "ui/scmessagebox.h"
54 #include "sccolorengine.h"
55 #include "util_formats.h"
56 #include "util_math.h"
57 #include "text/textlayout.h"
58 #include "text/textlayoutpainter.h"
59 #include "text/boxes.h"
60 
svgexplugin_getPluginAPIVersion()61 int svgexplugin_getPluginAPIVersion()
62 {
63 	return PLUGIN_API_VERSION;
64 }
65 
svgexplugin_getPlugin()66 ScPlugin* svgexplugin_getPlugin()
67 {
68 	SVGExportPlugin* plug = new SVGExportPlugin();
69 	Q_CHECK_PTR(plug);
70 	return plug;
71 }
72 
svgexplugin_freePlugin(ScPlugin * plugin)73 void svgexplugin_freePlugin(ScPlugin* plugin)
74 {
75 	SVGExportPlugin* plug = qobject_cast<SVGExportPlugin*>(plugin);
76 	Q_ASSERT(plug);
77 	delete plug;
78 }
79 
80 using namespace TableUtils;
81 
SVGExportPlugin()82 SVGExportPlugin::SVGExportPlugin()
83 {
84 	// Set action info in languageChange, so we only have to do
85 	// it in one place.
86 	languageChange();
87 }
88 
~SVGExportPlugin()89 SVGExportPlugin::~SVGExportPlugin() {};
90 
languageChange()91 void SVGExportPlugin::languageChange()
92 {
93 	// Note that we leave the unused members unset. They'll be initialised
94 	// with their default ctors during construction.
95 	// Action name
96 	m_actionInfo.name = "ExportAsSVG";
97 	// Action text for menu, including accel
98 	m_actionInfo.text = tr("Save as &SVG...");
99 	// Menu
100 	m_actionInfo.menu = "FileExport";
101 	m_actionInfo.enabledOnStartup = false;
102 	m_actionInfo.needsNumObjects = -1;
103 }
104 
fullTrName() const105 QString SVGExportPlugin::fullTrName() const
106 {
107 	return QObject::tr("SVG Export");
108 }
109 
getAboutData() const110 const ScActionPlugin::AboutData* SVGExportPlugin::getAboutData() const
111 {
112 	AboutData* about = new AboutData;
113 	about->authors = "Franz Schmid <franz@scribus.info>";
114 	about->shortDescription = tr("Exports SVG Files");
115 	about->description = tr("Exports the current page into an SVG file.");
116 	about->license = "GPL";
117 	Q_CHECK_PTR(about);
118 	return about;
119 }
120 
deleteAboutData(const AboutData * about) const121 void SVGExportPlugin::deleteAboutData(const AboutData* about) const
122 {
123 	Q_ASSERT(about);
124 	delete about;
125 }
126 
run(ScribusDoc * doc,const QString & filename)127 bool SVGExportPlugin::run(ScribusDoc* doc, const QString& filename)
128 {
129 	Q_ASSERT(filename.isEmpty());
130 	QString fileName;
131 	if (doc!=nullptr)
132 	{
133 		PrefsContext* prefs = PrefsManager::instance().prefsFile->getPluginContext("svgex");
134 		QString wdir = prefs->get("wdir", ".");
135 		QScopedPointer<CustomFDialog> openDia( new CustomFDialog(doc->scMW(), wdir, QObject::tr("Save as"), QObject::tr("%1;;All Files (*)").arg(FormatsManager::instance()->extensionsForFormat(FormatsManager::SVG)), fdHidePreviewCheckBox) );
136 		openDia->setSelection(getFileNameByPage(doc, doc->currentPage()->pageNr(), "svg"));
137 		openDia->setExtension("svg");
138 		openDia->setZipExtension("svgz");
139 		QCheckBox* compress = new QCheckBox(openDia.data());
140 		compress->setText( tr("Compress File"));
141 		compress->setChecked(false);
142 		openDia->addWidgets(compress);
143 		QCheckBox* inlineImages = new QCheckBox(openDia.data());
144 		inlineImages->setText( tr("Save Images inline"));
145 		inlineImages->setToolTip( tr("Adds all Images on the Page inline to the SVG.\nCaution: this will increase the file size!"));
146 		inlineImages->setChecked(true);
147 		openDia->addWidgets(inlineImages);
148 		QCheckBox* exportBack = new QCheckBox(openDia.data());
149 		exportBack->setText( tr("Export Page background"));
150 		exportBack->setToolTip( tr("Adds the Page itself as background to the SVG"));
151 		exportBack->setChecked(false);
152 		openDia->addWidgets(exportBack);
153 
154 		if (!openDia->exec())
155 			return true;
156 		fileName = openDia->selectedFile();
157 		QFileInfo fi(fileName);
158 		QString m_baseDir = fi.absolutePath();
159 		if (compress->isChecked())
160 			fileName = m_baseDir + "/" + fi.baseName() + ".svgz";
161 		else
162 			fileName = m_baseDir + "/" + fi.baseName() + ".svg";
163 
164 		SVGOptions Options;
165 		Options.inlineImages = inlineImages->isChecked();
166 		Options.exportPageBackground = exportBack->isChecked();
167 		Options.compressFile = compress->isChecked();
168 
169 		if (fileName.isEmpty())
170 			return true;
171 		prefs->set("wdir", fileName.left(fileName.lastIndexOf("/")));
172 		QFile f(fileName);
173 		if (f.exists())
174 		{
175 			int exit = ScMessageBox::warning(doc->scMW(), CommonStrings::trWarning,
176 				QObject::tr("Do you really want to overwrite the file:\n%1 ?").arg(fileName),
177 				QMessageBox::Yes | QMessageBox::No,
178 				QMessageBox::NoButton,	// GUI default
179 				QMessageBox::Yes);	// batch default
180 			if (exit == QMessageBox::No)
181 				return true;
182 		}
183 		SVGExPlug *dia = new SVGExPlug(doc);
184 		dia->doExport(fileName, Options);
185 		delete dia;
186 	}
187 	return true;
188 }
189 
SVGExPlug(ScribusDoc * doc)190 SVGExPlug::SVGExPlug( ScribusDoc* doc )
191 {
192 	m_Doc = doc;
193 	Options.inlineImages = true;
194 	Options.exportPageBackground = false;
195 	Options.compressFile = false;
196 	m_glyphNames.clear();
197 }
198 
doExport(const QString & fName,SVGOptions & Opts)199 bool SVGExPlug::doExport( const QString& fName, SVGOptions &Opts )
200 {
201 	Options = Opts;
202 	QFileInfo fiBase(fName);
203 
204 	m_baseDir = fiBase.absolutePath();
205 	m_gradCount = 0;
206 	m_clipCount = 0;
207 	m_pattCount = 0;
208 	m_maskCount = 0;
209 	m_filterCount = 0;
210 
211 	m_domDoc = QDomDocument("svgdoc");
212 	QString vo = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
213 	QString st = "<svg></svg>";
214 	m_domDoc.setContent(st);
215 
216 	ScPage *page = m_Doc->currentPage();
217 	double pageWidth  = page->width();
218 	double pageHeight = page->height();
219 	m_domElem = m_domDoc.documentElement();
220 	m_domElem.setAttribute("width", FToStr(pageWidth) + "pt");
221 	m_domElem.setAttribute("height", FToStr(pageHeight) + "pt");
222 	m_domElem.setAttribute("viewBox", QString("0 0 %1 %2").arg(pageWidth).arg(pageHeight));
223 	m_domElem.setAttribute("xmlns", "http://www.w3.org/2000/svg");
224 	m_domElem.setAttribute("xmlns:inkscape","http://www.inkscape.org/namespaces/inkscape");
225 	m_domElem.setAttribute("xmlns:xlink","http://www.w3.org/1999/xlink");
226 	m_domElem.setAttribute("version","1.1");
227 	if (!m_Doc->documentInfo().title().isEmpty())
228 	{
229 		QDomText title = m_domDoc.createTextNode(m_Doc->documentInfo().title());
230 		QDomElement titleElem = m_domDoc.createElement("title");
231 		titleElem.appendChild(title);
232 		m_domElem.appendChild(titleElem);
233 	}
234 	if (!m_Doc->documentInfo().comments().isEmpty())
235 	{
236 		QDomText desc = m_domDoc.createTextNode(m_Doc->documentInfo().comments());
237 		QDomElement descElem = m_domDoc.createElement("desc");
238 		descElem.appendChild(desc);
239 		m_domElem.appendChild(descElem);
240 	}
241 	m_globalDefs = m_domDoc.createElement("defs");
242 	writeBasePatterns();
243 	writeBaseSymbols();
244 	m_domElem.appendChild(m_globalDefs);
245 	if (Options.exportPageBackground)
246 	{
247 		QDomElement backG = m_domDoc.createElement("rect");
248 		backG.setAttribute("x", "0");
249 		backG.setAttribute("y", "0");
250 		backG.setAttribute("width", FToStr(pageWidth));
251 		backG.setAttribute("height", FToStr(pageHeight));
252 		backG.setAttribute("style", "fill:" + m_Doc->paperColor().name() + ";" + "stroke:none;");
253 		m_domElem.appendChild(backG);
254 	}
255 	ScLayer ll;
256 	ll.isPrintable = false;
257 	for (int la = 0; la < m_Doc->Layers.count(); la++)
258 	{
259 		m_Doc->Layers.levelToLayer(ll, la);
260 		if (ll.isPrintable)
261 		{
262 			page = m_Doc->MasterPages.at(m_Doc->MasterNames[m_Doc->currentPage()->masterPageName()]);
263 			processPageLayer(page, ll);
264 			page = m_Doc->currentPage();
265 			processPageLayer(page, ll);
266 		}
267 	}
268 	if(Options.compressFile)
269 	{
270 		// zipped saving
271 		QString wr = vo;
272 		wr += m_domDoc.toString();
273 		QByteArray utf8wr = wr.toUtf8();
274 		QFile file(fName);
275 		QtIOCompressor compressor(&file);
276 		compressor.setStreamFormat(QtIOCompressor::GzipFormat);
277 		compressor.open(QIODevice::WriteOnly);
278 		compressor.write(utf8wr);
279 		compressor.close();
280 	}
281 	else
282 	{
283 		QFile f(fName);
284 		if(!f.open(QIODevice::WriteOnly))
285 			return false;
286 		QDataStream s(&f);
287 		QString wr = vo;
288 		wr += m_domDoc.toString();
289 		QByteArray utf8wr = wr.toUtf8();
290 		s.writeRawData(utf8wr.data(), utf8wr.length());
291 		f.close();
292 	}
293 	return true;
294 }
295 
processPageLayer(ScPage * page,ScLayer & layer)296 void SVGExPlug::processPageLayer(ScPage *page, ScLayer& layer)
297 {
298 	QDomElement layerGroup;
299 	PageItem *item;
300 	QList<PageItem*> items;
301 	ScPage* SavedAct = m_Doc->currentPage();
302 	if (page->pageNameEmpty())
303 		items = m_Doc->DocItems;
304 	else
305 		items = m_Doc->MasterItems;
306 	if (items.count() == 0)
307 		return;
308 	if (!layer.isPrintable)
309 		return;
310 	m_Doc->setCurrentPage(page);
311 
312 	layerGroup = m_domDoc.createElement("g");
313 	layerGroup.setAttribute("id", layer.Name);
314 	layerGroup.setAttribute("inkscape:label", layer.Name);
315 	layerGroup.setAttribute("inkscape:groupmode", "layer");
316 	if (layer.transparency != 1.0)
317 		layerGroup.setAttribute("opacity", FToStr(layer.transparency));
318 	for (int j = 0; j < items.count(); ++j)
319 	{
320 		item = items.at(j);
321 		if (item->m_layerID != layer.ID)
322 			continue;
323 		if (!item->printEnabled())
324 			continue;
325 		double x = page->xOffset();
326 		double y = page->yOffset();
327 		double w = page->width();
328 		double h = page->height();
329 		double x2 = item->BoundingX;
330 		double y2 = item->BoundingY;
331 		double w2 = item->BoundingW;
332 		double h2 = item->BoundingH;
333 		if (!( qMax( x, x2 ) <= qMin( x+w, x2+w2 ) && qMax( y, y2 ) <= qMin( y+h, y2+h2 )))
334 			continue;
335 		if ((!page->pageNameEmpty()) && (item->OwnPage != static_cast<int>(page->pageNr())) && (item->OwnPage != -1))
336 			continue;
337 		processItemOnPage(item->xPos()-page->xOffset(), item->yPos()-page->yOffset(), item, &layerGroup);
338 	}
339 	m_domElem.appendChild(layerGroup);
340 
341 	m_Doc->setCurrentPage(SavedAct);
342 }
343 
processItemOnPage(double xOffset,double yOffset,PageItem * item,QDomElement * parentElem)344 void SVGExPlug::processItemOnPage(double xOffset, double yOffset, PageItem *item, QDomElement *parentElem)
345 {
346 	QDomElement ob;
347 	QString trans = "translate(" + FToStr(xOffset) + ", " + FToStr(yOffset) + ")";
348 	if (item->rotation() != 0)
349 		trans += " rotate(" + FToStr(item->rotation()) + ")";
350 	QString fill = getFillStyle(item);
351 	fill += processDropShadow(item);
352 	QString stroke = "stroke:none";
353 	stroke = getStrokeStyle(item);
354 	switch (item->itemType())
355 	{
356 		case PageItem::Arc:
357 		case PageItem::Polygon:
358 		case PageItem::PolyLine:
359 		case PageItem::RegularPolygon:
360 		case PageItem::Spiral:
361 			ob = processPolyItem(item, trans, fill, stroke);
362 			if ((item->lineColor() != CommonStrings::None) && ((item->startArrowIndex() != 0) || (item->endArrowIndex() != 0)))
363 				ob = processArrows(item, ob, trans);
364 			break;
365 		case PageItem::Line:
366 			ob = processLineItem(item, trans, stroke);
367 			if ((item->lineColor() != CommonStrings::None) && ((item->startArrowIndex() != 0) || (item->endArrowIndex() != 0)))
368 				ob = processArrows(item, ob, trans);
369 			break;
370 		case PageItem::ImageFrame:
371 		case PageItem::LatexFrame:
372 			ob = processImageItem(item, trans, fill, stroke);
373 			break;
374 		case PageItem::TextFrame:
375 		case PageItem::PathText:
376 			ob = processTextItem(item, trans, fill, stroke);
377 			break;
378 		case PageItem::Symbol:
379 			ob = processSymbolItem(item, trans);
380 			break;
381 		case PageItem::Group:
382 			if (item->groupItemList.count() > 0)
383 			{
384 				ob = m_domDoc.createElement("g");
385 				if (!item->AutoName)
386 					ob.setAttribute("id", item->itemName());
387 				if (item->GrMask > 0)
388 					ob.setAttribute("mask", handleMask(item, xOffset, yOffset));
389 				else
390 				{
391 					if (item->fillTransparency() != 0)
392 						ob.setAttribute("opacity", FToStr(1.0 - item->fillTransparency()));
393 				}
394 				QString tr = trans;
395 				if (item->imageFlippedH())
396 				{
397 					tr += QString(" translate(%1, 0.0)").arg(item->width());
398 					tr += QString(" scale(-1.0, 1.0)");
399 				}
400 				if (item->imageFlippedV())
401 				{
402 					tr += QString(" translate(0.0, %1)").arg(item->height());
403 					tr += QString(" scale(1.0, -1.0)");
404 				}
405 				tr += QString(" scale(%1, %2)").arg(item->width() / item->groupWidth).arg(item->height() / item->groupHeight);
406 				ob.setAttribute("transform", tr);
407 				ob.setAttribute("style", "fill:none; stroke:none");
408 				if (item->groupClipping())
409 				{
410 					FPointArray clipPath = item->PoLine;
411 					QTransform transform;
412 					transform.scale(item->width() / item->groupWidth, item->height() / item->groupHeight);
413 					transform = transform.inverted();
414 					clipPath.map(transform);
415 					QDomElement obc = createClipPathElement(&clipPath);
416 					if (!obc.isNull())
417 						ob.setAttribute("clip-path", "url(#" + obc.attribute("id") + ")");
418 					if (item->fillRule)
419 						ob.setAttribute("clip-rule", "evenodd");
420 					else
421 						ob.setAttribute("clip-rule", "nonzero");
422 				}
423 				for (int em = 0; em < item->groupItemList.count(); ++em)
424 				{
425 					PageItem* embed = item->groupItemList.at(em);
426 					processItemOnPage(embed->gXpos, embed->gYpos, embed, &ob);
427 				}
428 			}
429 			break;
430 		case PageItem::Table:
431 			ob = m_domDoc.createElement("g");
432 			ob.setAttribute("transform", trans + QString("translate(%1, %2)").arg(item->asTable()->gridOffset().x()).arg(item->asTable()->gridOffset().y()));
433 			// Paint table fill.
434 			if (item->asTable()->fillColor() != CommonStrings::None)
435 			{
436 				int lastCol = item->asTable()->columns() - 1;
437 				int lastRow = item->asTable()->rows() - 1;
438 				double x = item->asTable()->columnPosition(0);
439 				double y = item->asTable()->rowPosition(0);
440 				double width = item->asTable()->columnPosition(lastCol) + item->asTable()->columnWidth(lastCol) - x;
441 				double height = item->asTable()->rowPosition(lastRow) + item->asTable()->rowHeight(lastRow) - y;
442 				QDomElement cl = m_domDoc.createElement("rect");
443 				cl.setAttribute("fill", setColor(item->asTable()->fillColor(), item->asTable()->fillShade()));
444 				cl.setAttribute("x", "0");
445 				cl.setAttribute("y", "0");
446 				cl.setAttribute("width", FToStr(width));
447 				cl.setAttribute("height", FToStr(height));
448 				ob.appendChild(cl);
449 			}
450 			// Pass 1: Paint cell fills.
451 			for (int row = 0; row < item->asTable()->rows(); ++row)
452 			{
453 				int colSpan = 0;
454 				for (int col = 0; col < item->asTable()->columns(); col += colSpan)
455 				{
456 					TableCell cell = item->asTable()->cellAt(row, col);
457 					if (row == cell.row())
458 					{
459 						QString colorName = cell.fillColor();
460 						if (colorName != CommonStrings::None)
461 						{
462 							int row = cell.row();
463 							int col = cell.column();
464 							int lastRow = row + cell.rowSpan() - 1;
465 							int lastCol = col + cell.columnSpan() - 1;
466 							double x = item->asTable()->columnPosition(col);
467 							double y = item->asTable()->rowPosition(row);
468 							double width = item->asTable()->columnPosition(lastCol) + item->asTable()->columnWidth(lastCol) - x;
469 							double height = item->asTable()->rowPosition(lastRow) + item->asTable()->rowHeight(lastRow) - y;
470 							QDomElement cl = m_domDoc.createElement("rect");
471 							cl.setAttribute("fill", setColor(colorName, cell.fillShade()));
472 							cl.setAttribute("x", FToStr(x));
473 							cl.setAttribute("y", FToStr(y));
474 							cl.setAttribute("width", FToStr(width));
475 							cl.setAttribute("height", FToStr(height));
476 							ob.appendChild(cl);
477 						}
478 					}
479 					colSpan = cell.columnSpan();
480 				}
481 			}
482 			// Pass 2: Paint vertical borders.
483 			for (int row = 0; row < item->asTable()->rows(); ++row)
484 			{
485 				int colSpan = 0;
486 				for (int col = 0; col < item->asTable()->columns(); col += colSpan)
487 				{
488 					TableCell cell = item->asTable()->cellAt(row, col);
489 					if (row == cell.row())
490 					{
491 						const int lastRow = cell.row() + cell.rowSpan() - 1;
492 						const int lastCol = cell.column() + cell.columnSpan() - 1;
493 						const double borderX = item->asTable()->columnPosition(lastCol) + item->asTable()->columnWidth(lastCol);
494 						QPointF start(borderX, 0.0);
495 						QPointF end(borderX, 0.0);
496 						QPointF startOffsetFactors, endOffsetFactors;
497 						int startRow, endRow;
498 						for (int row = cell.row(); row <= lastRow; row += endRow - startRow + 1)
499 						{
500 							TableCell rightCell = item->asTable()->cellAt(row, lastCol + 1);
501 							startRow = qMax(cell.row(), rightCell.row());
502 							endRow = qMin(lastRow, rightCell.isValid() ? rightCell.row() + rightCell.rowSpan() - 1 : lastRow);
503 							TableCell topLeftCell = item->asTable()->cellAt(startRow - 1, lastCol);
504 							TableCell topRightCell = item->asTable()->cellAt(startRow - 1, lastCol + 1);
505 							TableCell bottomRightCell = item->asTable()->cellAt(endRow + 1, lastCol + 1);
506 							TableCell bottomLeftCell = item->asTable()->cellAt(endRow + 1, lastCol);
507 							TableBorder topLeft, top, topRight, border, bottomLeft, bottom, bottomRight;
508 							resolveBordersVertical(topLeftCell, topRightCell, cell, rightCell, bottomLeftCell, bottomRightCell,
509 												   &topLeft, &top, &topRight, &border, &bottomLeft, &bottom, &bottomRight, item->asTable());
510 							if (border.isNull())
511 								continue; // Quit early if the border to paint is null.
512 							start.setY(item->asTable()->rowPosition(startRow));
513 							end.setY((item->asTable()->rowPosition(endRow) + item->asTable()->rowHeight(endRow)));
514 							joinVertical(border, topLeft, top, topRight, bottomLeft, bottom, bottomRight, &start, &end, &startOffsetFactors, &endOffsetFactors);
515 							paintBorder(border, start, end, startOffsetFactors, endOffsetFactors, ob);
516 						}
517 						if (col == 0)
518 						{
519 							const int lastRow = cell.row() + cell.rowSpan() - 1;
520 							const int firstCol = cell.column();
521 							const double borderX = item->asTable()->columnPosition(firstCol);
522 							QPointF start(borderX, 0.0);
523 							QPointF end(borderX, 0.0);
524 							QPointF startOffsetFactors, endOffsetFactors;
525 							int startRow, endRow;
526 							for (int row = cell.row(); row <= lastRow; row += endRow - startRow + 1)
527 							{
528 								TableCell leftCell = item->asTable()->cellAt(row, firstCol - 1);
529 								startRow = qMax(cell.row(), leftCell.row());
530 								endRow = qMin(lastRow, leftCell.isValid() ? leftCell.row() + leftCell.rowSpan() - 1 : lastRow);
531 								TableCell topLeftCell = item->asTable()->cellAt(startRow - 1, firstCol - 1);
532 								TableCell topRightCell = item->asTable()->cellAt(startRow - 1, firstCol);
533 								TableCell bottomRightCell = item->asTable()->cellAt(lastRow + 1, firstCol);
534 								TableCell bottomLeftCell = item->asTable()->cellAt(lastRow + 1, firstCol - 1);
535 								TableBorder topLeft, top, topRight, border, bottomLeft, bottom, bottomRight;
536 								resolveBordersVertical(topLeftCell, topRightCell, leftCell, cell, bottomLeftCell, bottomRightCell,
537 													   &topLeft, &top, &topRight, &border, &bottomLeft, &bottom, &bottomRight, item->asTable());
538 								if (border.isNull())
539 									continue; // Quit early if the border to paint is null.
540 								start.setY(item->asTable()->rowPosition(startRow));
541 								end.setY((item->asTable()->rowPosition(endRow) + item->asTable()->rowHeight(endRow)));
542 								joinVertical(border, topLeft, top, topRight, bottomLeft, bottom, bottomRight, &start, &end, &startOffsetFactors, &endOffsetFactors);
543 								paintBorder(border, start, end, startOffsetFactors, endOffsetFactors, ob);
544 							}
545 						}
546 					}
547 					colSpan = cell.columnSpan();
548 				}
549 			}
550 			// Pass 3: Paint horizontal borders.
551 			for (int row = 0; row < item->asTable()->rows(); ++row)
552 			{
553 				int colSpan = 0;
554 				for (int col = 0; col < item->asTable()->columns(); col += colSpan)
555 				{
556 					TableCell cell = item->asTable()->cellAt(row, col);
557 					if (row == cell.row())
558 					{
559 						const int lastRow = cell.row() + cell.rowSpan() - 1;
560 						const int lastCol = cell.column() + cell.columnSpan() - 1;
561 						const double borderY = (item->asTable()->rowPosition(lastRow) + item->asTable()->rowHeight(lastRow));
562 						QPointF start(0.0, borderY);
563 						QPointF end(0.0, borderY);
564 						QPointF startOffsetFactors, endOffsetFactors;
565 						int startCol, endCol;
566 						for (int col = cell.column(); col <= lastCol; col += endCol - startCol + 1)
567 						{
568 							TableCell bottomCell = item->asTable()->cellAt(lastRow + 1, col);
569 							startCol = qMax(cell.column(), bottomCell.column());
570 							endCol = qMin(lastCol, bottomCell.isValid() ? bottomCell.column() + bottomCell.columnSpan() - 1 : lastCol);
571 							TableCell topLeftCell = item->asTable()->cellAt(lastRow, startCol - 1);
572 							TableCell topRightCell = item->asTable()->cellAt(lastRow, endCol + 1);
573 							TableCell bottomRightCell = item->asTable()->cellAt(lastRow + 1, endCol + 1);
574 							TableCell bottomLeftCell = item->asTable()->cellAt(lastRow + 1, startCol - 1);
575 							TableBorder topLeft, left, bottomLeft, border, topRight, right, bottomRight;
576 							resolveBordersHorizontal(topLeftCell, cell, topRightCell, bottomLeftCell, bottomCell,
577 													 bottomRightCell, &topLeft, &left, &bottomLeft, &border, &topRight, &right, &bottomRight, item->asTable());
578 							if (border.isNull())
579 								continue; // Quit early if the border is null.
580 							start.setX(item->asTable()->columnPosition(startCol));
581 							end.setX(item->asTable()->columnPosition(endCol) + item->asTable()->columnWidth(endCol));
582 							joinHorizontal(border, topLeft, left, bottomLeft, topRight, right, bottomRight, &start, &end, &startOffsetFactors, &endOffsetFactors);
583 							paintBorder(border, start, end, startOffsetFactors, endOffsetFactors, ob);
584 						}
585 						if (row == 0)
586 						{
587 							const int firstRow = cell.row();
588 							const int lastCol = cell.column() + cell.columnSpan() - 1;
589 							const double borderY = item->asTable()->rowPosition(firstRow);
590 							QPointF start(0.0, borderY);
591 							QPointF end(0.0, borderY);
592 							QPointF startOffsetFactors, endOffsetFactors;
593 							int startCol, endCol;
594 							for (int col = cell.column(); col <= lastCol; col += endCol - startCol + 1)
595 							{
596 								TableCell topCell = item->asTable()->cellAt(firstRow - 1, col);
597 								startCol = qMax(cell.column(), topCell.column());
598 								endCol = qMin(lastCol, topCell.isValid() ? topCell.column() + topCell.columnSpan() - 1 : lastCol);
599 								TableCell topLeftCell = item->asTable()->cellAt(firstRow - 1, startCol - 1);
600 								TableCell topRightCell = item->asTable()->cellAt(firstRow - 1, endCol + 1);
601 								TableCell bottomRightCell = item->asTable()->cellAt(firstRow, endCol + 1);
602 								TableCell bottomLeftCell = item->asTable()->cellAt(firstRow, startCol - 1);
603 								TableBorder topLeft, left, bottomLeft, border, topRight, right, bottomRight;
604 								resolveBordersHorizontal(topLeftCell, topCell, topRightCell, bottomLeftCell, cell,
605 														 bottomRightCell, &topLeft, &left, &bottomLeft, &border, &topRight, &right, &bottomRight, item->asTable());
606 								if (border.isNull())
607 									continue; // Quit early if the border is null.
608 								start.setX(item->asTable()->columnPosition(startCol));
609 								end.setX(item->asTable()->columnPosition(endCol) + item->asTable()->columnWidth(endCol));
610 								joinHorizontal(border, topLeft, left, bottomLeft, topRight, right, bottomRight, &start, &end, &startOffsetFactors, &endOffsetFactors);
611 								paintBorder(border, start, end, startOffsetFactors, endOffsetFactors, ob);
612 							}
613 						}
614 					}
615 					colSpan = cell.columnSpan();
616 				}
617 			}
618 			// Pass 4: Paint cell content.
619 			for (int row = 0; row < item->asTable()->rows(); ++row)
620 			{
621 				for (int col = 0; col < item->asTable()->columns(); col ++)
622 				{
623 					TableCell cell = item->asTable()->cellAt(row, col);
624 					if (cell.row() == row && cell.column() == col)
625 					{
626 						PageItem* textFrame = cell.textFrame();
627 						processItemOnPage(cell.contentRect().x(), cell.contentRect().y(), textFrame, &ob);
628 					}
629 				}
630 			}
631 			break;
632 		default:
633 			break;
634 	}
635 	if (item->GrMask > 0)
636 		ob.setAttribute("mask", handleMask(item, xOffset, yOffset));
637 	if (!item->AutoName)
638 		ob.setAttribute("id", item->itemName());
639 	parentElem->appendChild(ob);
640 }
641 
paintBorder(const TableBorder & border,const QPointF & start,const QPointF & end,const QPointF & startOffsetFactors,const QPointF & endOffsetFactors,QDomElement & ob)642 void SVGExPlug::paintBorder(const TableBorder& border, const QPointF& start, const QPointF& end, const QPointF& startOffsetFactors, const QPointF& endOffsetFactors, QDomElement &ob)
643 {
644 	QPointF lineStart, lineEnd;
645 	for (const TableBorderLine& line : border.borderLines())
646 	{
647 		lineStart.setX(start.x() + line.width() * startOffsetFactors.x());
648 		lineStart.setY(start.y() + line.width() * startOffsetFactors.y());
649 		lineEnd.setX(end.x() + line.width() * endOffsetFactors.x());
650 		lineEnd.setY(end.y() + line.width() * endOffsetFactors.y());
651 		QDomElement cl = m_domDoc.createElement("path");
652 		cl.setAttribute("d", "M " + FToStr(lineStart.x()) + " " + FToStr(lineStart.y()) + " L " + FToStr(lineEnd.x()) + " " + FToStr(lineEnd.y()));
653 		QString stroke = "";
654 		if (line.color() != CommonStrings::None)
655 			cl.setAttribute("stroke", setColor(line.color(), line.shade()));
656 		if (line.width() != 0.0)
657 			stroke = "stroke-width:" + FToStr(line.width()) + ";";
658 		else
659 			stroke = "stroke-width:1px;";
660 		stroke += " stroke-linecap:butt;";
661 		stroke += " stroke-linejoin:miter;";
662 		stroke += " stroke-dasharray:";
663 		if (line.style() == Qt::SolidLine)
664 			stroke += "none;";
665 		else
666 		{
667 			QString Da = getDashString(line.style(), qMax(line.width(), 1.0));
668 			if (Da.isEmpty())
669 				stroke += "none;";
670 			else
671 				stroke += Da.replace(" ", ", ") + ";";
672 		}
673 		cl.setAttribute("style", stroke);
674 		ob.appendChild(cl);
675 	}
676 }
677 
processDropShadow(PageItem * item)678 QString SVGExPlug::processDropShadow(PageItem *item)
679 {
680 	if (!item->hasSoftShadow())
681 		return "";
682 	QString ID = "Filter" + IToStr(m_filterCount);
683 	QDomElement filter = m_domDoc.createElement("filter");
684 	filter.setAttribute("id", ID);
685 	filter.setAttribute("inkscape:label", "Drop shadow");
686 	QDomElement ob = m_domDoc.createElement("feGaussianBlur");
687 	ob.setAttribute("id", "feGaussianBlur" + IToStr(m_filterCount));
688 	ob.setAttribute("in", "SourceAlpha");
689 	ob.setAttribute("stdDeviation", FToStr(item->softShadowBlurRadius()));
690 	ob.setAttribute("result", "blur");
691 	filter.appendChild(ob);
692 	QDomElement ob2 = m_domDoc.createElement("feColorMatrix");
693 	ob2.setAttribute("id", "feColorMatrix" + IToStr(m_filterCount));
694 	const ScColor& col = m_Doc->PageColors[item->softShadowColor()];
695 	QColor color = ScColorEngine::getShadeColorProof(col, m_Doc, item->softShadowShade());
696 	ob2.setAttribute("type", "matrix");
697 	ob2.setAttribute("values", QString("1 0 0 %1 0 0 1 0 %2 0 0 0 1 %3 0 0 0 0 %4 0").arg(color.redF()).arg(color.greenF()).arg(color.blueF()).arg(1.0 - item->softShadowOpacity()));
698 	ob2.setAttribute("result", "bluralpha");
699 	filter.appendChild(ob2);
700 	QDomElement ob3 = m_domDoc.createElement("feOffset");
701 	ob3.setAttribute("id", "feOffset" + IToStr(m_filterCount));
702 	ob3.setAttribute("in", "bluralpha");
703 	ob3.setAttribute("dx", FToStr(item->softShadowXOffset()));
704 	ob3.setAttribute("dy", FToStr(item->softShadowYOffset()));
705 	ob3.setAttribute("result", "offsetBlur");
706 	filter.appendChild(ob3);
707 	QDomElement ob4 = m_domDoc.createElement("feMerge");
708 	ob4.setAttribute("id", "feMerge" + IToStr(m_filterCount));
709 	QDomElement ob5 = m_domDoc.createElement("feMergeNode");
710 	ob5.setAttribute("id", "feMergeNode1" + IToStr(m_filterCount));
711 	ob5.setAttribute("in", "offsetBlur");
712 	ob4.appendChild(ob5);
713 	QDomElement ob6 = m_domDoc.createElement("feMergeNode");
714 	ob6.setAttribute("id", "feMergeNode2" + IToStr(m_filterCount));
715 	ob6.setAttribute("in", "SourceGraphic");
716 	ob4.appendChild(ob6);
717 	filter.appendChild(ob4);
718 	m_globalDefs.appendChild(filter);
719 	m_filterCount++;
720 	return "filter:url(#" + ID + ");";
721 }
722 
processHatchFill(PageItem * item,const QString & transl)723 QDomElement SVGExPlug::processHatchFill(PageItem *item, const QString& transl)
724 {
725 	QDomElement ob;
726 	ob = m_domDoc.createElement("g");
727 	if (!transl.isEmpty())
728 		ob.setAttribute("transform", transl);
729 	QDomElement obc = createClipPathElement(&item->PoLine);
730 	if (!obc.isNull())
731 		ob.setAttribute("clip-path", "url(#" + obc.attribute("id") + ")");
732 	if (item->fillRule)
733 		ob.setAttribute("clip-rule", "evenodd");
734 	else
735 		ob.setAttribute("clip-rule", "nonzero");
736 	if (item->hatchUseBackground)
737 	{
738 		QDomElement ob2 = m_domDoc.createElement("path");
739 		ob2.setAttribute("d", setClipPath(&item->PoLine, true));
740 		ob2.setAttribute("fill", setColor(item->hatchBackground, 100));
741 		ob.appendChild(ob2);
742 	}
743 	QString stroke = "";
744 	stroke += "stroke-width:1;";
745 	stroke += " stroke-linecap:butt;";
746 	stroke += " stroke-linejoin:miter;";
747 	double lineLen = sqrt((item->width() / 2.0) * (item->width() / 2.0) + (item->height() / 2.0) * (item->height() / 2.0));
748 	double dist = 0.0;
749 	while (dist < lineLen)
750 	{
751 		QTransform mpx;
752 		mpx.translate(item->width() / 2.0, item->height() / 2.0);
753 		if (item->hatchAngle != 0.0)
754 			mpx.rotate(-item->hatchAngle);
755 		QDomElement ob3 = m_domDoc.createElement("path");
756 		ob3.setAttribute("transform", matrixToStr(mpx));
757 		ob3.setAttribute("d", QString("M %1, %2 L %3, %4").arg(-lineLen).arg(dist).arg(lineLen).arg(dist));
758 		ob3.setAttribute("stroke", setColor(item->hatchForeground, 100));
759 		ob3.setAttribute("style", stroke);
760 		ob.appendChild(ob3);
761 		if (dist > 0)
762 		{
763 			QDomElement ob4 = m_domDoc.createElement("path");
764 			ob4.setAttribute("transform", matrixToStr(mpx));
765 			ob4.setAttribute("d", QString("M %1, %2 L %3, %4").arg(-lineLen).arg(-dist).arg(lineLen).arg(-dist));
766 			ob4.setAttribute("stroke", setColor(item->hatchForeground, 100));
767 			ob4.setAttribute("style", stroke);
768 			ob.appendChild(ob4);
769 		}
770 		dist += item->hatchDistance;
771 	}
772 	if ((item->hatchType == 1) || (item->hatchType == 2))
773 	{
774 		dist = 0.0;
775 		while (dist < lineLen)
776 		{
777 			QTransform mpx;
778 			mpx.translate(item->width() / 2.0, item->height() / 2.0);
779 			if (item->hatchAngle != 0.0)
780 				mpx.rotate(-item->hatchAngle + 90);
781 			QDomElement ob3 = m_domDoc.createElement("path");
782 			ob3.setAttribute("transform", matrixToStr(mpx));
783 			ob3.setAttribute("d", QString("M %1, %2 L %3, %4").arg(-lineLen).arg(dist).arg(lineLen).arg(dist));
784 			ob3.setAttribute("stroke", setColor(item->hatchForeground, 100));
785 			ob3.setAttribute("style", stroke);
786 			ob.appendChild(ob3);
787 			if (dist > 0)
788 			{
789 				QDomElement ob4 = m_domDoc.createElement("path");
790 				ob4.setAttribute("transform", matrixToStr(mpx));
791 				ob4.setAttribute("d", QString("M %1, %2 L %3, %4").arg(-lineLen).arg(-dist).arg(lineLen).arg(-dist));
792 				ob4.setAttribute("stroke", setColor(item->hatchForeground, 100));
793 				ob4.setAttribute("style", stroke);
794 				ob.appendChild(ob4);
795 			}
796 			dist += item->hatchDistance;
797 		}
798 	}
799 	if (item->hatchType == 2)
800 	{
801 		dist = 0.0;
802 		while (dist < lineLen)
803 		{
804 			double dDist = dist * sqrt(2.0);
805 			QTransform mpx;
806 			mpx.translate(item->width() / 2.0, item->height() / 2.0);
807 			if (item->hatchAngle != 0.0)
808 				mpx.rotate(-item->hatchAngle + 45);
809 			QDomElement ob3 = m_domDoc.createElement("path");
810 			ob3.setAttribute("transform", matrixToStr(mpx));
811 			ob3.setAttribute("d", QString("M %1, %2 L %3, %4").arg(-lineLen).arg(dDist).arg(lineLen).arg(dDist));
812 			ob3.setAttribute("stroke", setColor(item->hatchForeground, 100));
813 			ob3.setAttribute("style", stroke);
814 			ob.appendChild(ob3);
815 			if (dist > 0)
816 			{
817 				QDomElement ob4 = m_domDoc.createElement("path");
818 				ob4.setAttribute("transform", matrixToStr(mpx));
819 				ob4.setAttribute("d", QString("M %1, %2 L %3, %4").arg(-lineLen).arg(-dDist).arg(lineLen).arg(-dDist));
820 				ob4.setAttribute("stroke", setColor(item->hatchForeground, 100));
821 				ob4.setAttribute("style", stroke);
822 				ob.appendChild(ob4);
823 			}
824 			dist += item->hatchDistance;
825 		}
826 	}
827 	return ob;
828 }
829 
processSymbolStroke(PageItem * item,const QString & trans)830 QDomElement SVGExPlug::processSymbolStroke(PageItem *item, const QString& trans)
831 {
832 	QDomElement ob;
833 	ob = m_domDoc.createElement("g");
834 	ob.setAttribute("transform", trans);
835 	QPainterPath path = item->PoLine.toQPainterPath(false);
836 	ScPattern pat = m_Doc->docPatterns[item->strokePattern()];
837 	double pLen = path.length() - ((pat.width / 2.0) * (item->patternStrokeScaleX / 100.0));
838 	double adv = pat.width * item->patternStrokeScaleX / 100.0 * item->patternStrokeSpace;
839 	double xpos = item->patternStrokeOffsetX * item->patternStrokeScaleX / 100.0;
840 	while (xpos < pLen)
841 	{
842 		double currPerc = path.percentAtLength(xpos);
843 		double currAngle = path.angleAtPercent(currPerc);
844 		if (currAngle <= 180.0)
845 			currAngle *= -1.0;
846 		else
847 			currAngle = 360.0 - currAngle;
848 		QPointF currPoint = path.pointAtPercent(currPerc);
849 		QTransform trans;
850 		trans.translate(currPoint.x(), currPoint.y());
851 		trans.rotate(-currAngle);
852 		trans.translate(0.0, item->patternStrokeOffsetY);
853 		trans.rotate(-item->patternStrokeRotation);
854 		trans.shear(item->patternStrokeSkewX, -item->patternStrokeSkewY);
855 		trans.scale(item->patternStrokeScaleX / 100.0, item->patternStrokeScaleY / 100.0);
856 		trans.translate(-pat.width / 2.0, -pat.height / 2.0);
857 		QDomElement obS;
858 		obS = m_domDoc.createElement("use");
859 		obS.setAttribute("transform", matrixToStr(trans));
860 		if (item->patternStrokeMirrorX)
861 		{
862 			trans.translate(pat.width, 0);
863 			trans.scale(-1, 1);
864 		}
865 		if (item->patternStrokeMirrorY)
866 		{
867 			trans.translate(0, pat.height);
868 			trans.scale(1, -1);
869 		}
870 		obS.setAttribute("x", "0");
871 		obS.setAttribute("y", "0");
872 		obS.setAttribute("width", FToStr(pat.width));
873 		obS.setAttribute("height", FToStr(pat.height));
874 		obS.setAttribute("xlink:href", "#S" + item->strokePattern());
875 		ob.appendChild(obS);
876 		xpos += adv;
877 	}
878 	return ob;
879 }
880 
processSymbolItem(PageItem * item,const QString & trans)881 QDomElement SVGExPlug::processSymbolItem(PageItem *item, const QString& trans)
882 {
883 	QDomElement ob;
884 	ScPattern pat = m_Doc->docPatterns[item->pattern()];
885 	ob = m_domDoc.createElement("use");
886 	ob.setAttribute("x", "0");
887 	ob.setAttribute("y", "0");
888 	ob.setAttribute("width", FToStr(pat.width));
889 	ob.setAttribute("height", FToStr(pat.height));
890 	ob.setAttribute("xlink:href", "#S" + item->pattern());
891 	QString tr = trans + QString(" scale(%1, %2)").arg(item->width() / pat.width).arg(item->height() / pat.height);
892 	ob.setAttribute("transform", tr);
893 	return ob;
894 }
895 
processPolyItem(PageItem * item,const QString & trans,const QString & fill,const QString & stroke)896 QDomElement SVGExPlug::processPolyItem(PageItem *item, const QString& trans, const QString& fill, const QString& stroke)
897 {
898 	bool closedPath;
899 	QDomElement ob;
900 	closedPath = (item->itemType() == PageItem::Polygon) || (item->itemType() == PageItem::RegularPolygon) || (item->itemType() == PageItem::Arc);
901 	if (item->NamedLStyle.isEmpty())
902 	{
903 		if ((!item->strokePattern().isEmpty()) && (item->patternStrokePath))
904 		{
905 			ob = m_domDoc.createElement("g");
906 			if (item->GrType == Gradient_Hatch)
907 			{
908 				QDomElement ob1 = processHatchFill(item, trans);
909 				ob.appendChild(ob1);
910 			}
911 			QDomElement ob2 = m_domDoc.createElement("path");
912 			ob2.setAttribute("d", setClipPath(&item->PoLine, closedPath));
913 			ob2.setAttribute("transform", trans);
914 			if (item->GrType != Gradient_Hatch)
915 				ob2.setAttribute("style", fill);
916 			else
917 			{
918 				QString drS = processDropShadow(item);
919 				if (!drS.isEmpty())
920 					ob2.setAttribute("style", "fill:none;" + drS);
921 			}
922 			ob.appendChild(ob2);
923 			ob.appendChild(processSymbolStroke(item, trans));
924 		}
925 		else
926 		{
927 			if (item->GrType == Gradient_Hatch)
928 			{
929 				ob = m_domDoc.createElement("g");
930 				ob.setAttribute("transform", trans);
931 				QDomElement ob1 = processHatchFill(item);
932 				ob.appendChild(ob1);
933 				QDomElement ob2 = m_domDoc.createElement("path");
934 				ob2.setAttribute("d", setClipPath(&item->PoLine, closedPath));
935 				ob2.setAttribute("style", stroke + "fill:none;" + processDropShadow(item));
936 				ob.appendChild(ob2);
937 			}
938 			else
939 			{
940 				ob = m_domDoc.createElement("path");
941 				ob.setAttribute("d", setClipPath(&item->PoLine, closedPath));
942 				ob.setAttribute("transform", trans);
943 				ob.setAttribute("style", fill + stroke);
944 			}
945 		}
946 	}
947 	else
948 	{
949 		ob = m_domDoc.createElement("g");
950 		ob.setAttribute("transform", trans);
951 		if (item->GrType == Gradient_Hatch)
952 		{
953 			QDomElement ob1 = processHatchFill(item);
954 			ob.appendChild(ob1);
955 		}
956 		QDomElement ob2 = m_domDoc.createElement("path");
957 		ob2.setAttribute("d", setClipPath(&item->PoLine, closedPath));
958 		if (item->GrType != Gradient_Hatch)
959 			ob2.setAttribute("style", fill);
960 		else
961 		{
962 			QString drS = processDropShadow(item);
963 			if (!drS.isEmpty())
964 				ob2.setAttribute("style", "fill:none;" + drS);
965 		}
966 		ob.appendChild(ob2);
967 		multiLine ml = m_Doc->docLineStyles[item->NamedLStyle];
968 		for (int it = ml.size()-1; it > -1; it--)
969 		{
970 			if ((ml[it].Color != CommonStrings::None) && (ml[it].Width != 0))
971 			{
972 				QDomElement ob3 = m_domDoc.createElement("path");
973 				ob3.setAttribute("d", setClipPath(&item->PoLine, closedPath));
974 				ob3.setAttribute("style", getMultiStroke(&ml[it], item));
975 				ob.appendChild(ob3);
976 			}
977 		}
978 	}
979 	return ob;
980 }
981 
processLineItem(PageItem * item,const QString & trans,const QString & stroke)982 QDomElement SVGExPlug::processLineItem(PageItem *item, const QString& trans, const QString& stroke)
983 {
984 	QDomElement ob;
985 	if (item->NamedLStyle.isEmpty())
986 	{
987 		ob = m_domDoc.createElement("path");
988 		ob.setAttribute("d", "M 0 0 L " + FToStr(item->width()) + " 0");
989 		ob.setAttribute("transform", trans);
990 		ob.setAttribute("style", stroke);
991 	}
992 	else
993 	{
994 		ob = m_domDoc.createElement("g");
995 		ob.setAttribute("transform", trans);
996 		multiLine ml = m_Doc->docLineStyles[item->NamedLStyle];
997 		for (int i = ml.size()-1; i > -1; i--)
998 		{
999 			if ((ml[i].Color != CommonStrings::None) && (ml[i].Width != 0))
1000 			{
1001 				QDomElement ob2 = m_domDoc.createElement("path");
1002 				ob2.setAttribute("d", "M 0 0 L " + FToStr(item->width()) + " 0");
1003 				ob2.setAttribute("style", getMultiStroke(&ml[i], item));
1004 				ob.appendChild(ob2);
1005 			}
1006 		}
1007 	}
1008 	return ob;
1009 }
1010 
processImageItem(PageItem * item,const QString & trans,const QString & fill,const QString & stroke)1011 QDomElement SVGExPlug::processImageItem(PageItem *item, const QString& trans, const QString& fill, const QString& stroke)
1012 {
1013 	QDomElement ob;
1014 	ob = m_domDoc.createElement("g");
1015 	ob.setAttribute("transform", trans);
1016 	if ((item->fillColor() != CommonStrings::None) || (item->GrType != 0))
1017 	{
1018 		if (item->GrType == Gradient_Hatch)
1019 		{
1020 			QDomElement ob1 = processHatchFill(item);
1021 			ob.appendChild(ob1);
1022 			QString drS = processDropShadow(item);
1023 			if (!drS.isEmpty())
1024 				ob.setAttribute("style", "fill:none;" + drS);
1025 		}
1026 		else
1027 		{
1028 			QDomElement ob1 = m_domDoc.createElement("path");
1029 			ob1.setAttribute("d", setClipPath(&item->PoLine, true));
1030 			ob1.setAttribute("style", fill);
1031 			ob.appendChild(ob1);
1032 		}
1033 	}
1034 	if ((item->imageIsAvailable) && (!item->Pfile.isEmpty()))
1035 	{
1036 		QDomElement cl, ob2;
1037 		if (!item->imageClip.empty())
1038 			ob2 = createClipPathElement(&item->imageClip, &cl);
1039 		else
1040 			ob2 = createClipPathElement(&item->PoLine, &cl);
1041 		if (!ob2.isNull())
1042 		{
1043 			ob2.setAttribute("clipPathUnits", "userSpaceOnUse");
1044 			ob2.setAttribute("clip-rule", "evenodd");
1045 			QTransform mpc;
1046 			if (item->imageFlippedH())
1047 			{
1048 				mpc.translate(item->width(), 0);
1049 				mpc.scale(-1, 1);
1050 			}
1051 			if (item->imageFlippedV())
1052 			{
1053 				mpc.translate(0, item->height());
1054 				mpc.scale(1, -1);
1055 			}
1056 			cl.setAttribute("transform", matrixToStr(mpc));
1057 		}
1058 		QDomElement ob6 = m_domDoc.createElement("g");
1059 		if (!ob2.isNull())
1060 			ob6.setAttribute("clip-path", "url(#" + ob2.attribute("id") + ")");
1061 		QDomElement ob3 = m_domDoc.createElement("image");
1062 		ScImage img;
1063 		CMSettings cms(m_Doc, item->ImageProfile, item->ImageIntent);
1064 		cms.setUseEmbeddedProfile(item->UseEmbedded);
1065 		cms.allowSoftProofing(true);
1066 		img.loadPicture(item->Pfile, item->pixm.imgInfo.actualPageNumber, cms, ScImage::RGBData, 72);
1067 		img.applyEffect(item->effectsInUse, m_Doc->PageColors, true);
1068 		if (Options.inlineImages)
1069 		{
1070 			QBuffer buffer;
1071 			buffer.open(QIODevice::WriteOnly);
1072 			img.qImage().save(&buffer, "PNG");
1073 			QByteArray ba = buffer.buffer().toBase64();
1074 			buffer.close();
1075 			ob3.setAttribute("xlink:href", "data:image/png;base64," + QString(ba));
1076 		}
1077 		else
1078 		{
1079 			QFileInfo fi = QFileInfo(item->Pfile);
1080 			QString imgFileName = m_baseDir + "/" + fi.baseName() + ".png";
1081 			QFileInfo im = QFileInfo(imgFileName);
1082 			if (im.exists())
1083 				imgFileName = m_baseDir + "/" + fi.baseName() + "_copy.png";
1084 			img.qImage().save(imgFileName, "PNG");
1085 			QFileInfo fi2 = QFileInfo(imgFileName);
1086 			ob3.setAttribute("xlink:href", fi2.baseName() + ".png");
1087 		}
1088 		ob3.setAttribute("x", FToStr(item->imageXOffset() * item->imageXScale()));
1089 		ob3.setAttribute("y", FToStr(item->imageYOffset() * item->imageYScale()));
1090 		ob3.setAttribute("width", FToStr(img.width() * item->imageXScale()));
1091 		ob3.setAttribute("height", FToStr(img.height() * item->imageYScale()));
1092 		QTransform mpa;
1093 		if (item->imageFlippedH())
1094 		{
1095 			mpa.translate(item->width(), 0);
1096 			mpa.scale(-1, 1);
1097 		}
1098 		if (item->imageFlippedV())
1099 		{
1100 			mpa.translate(0, item->height());
1101 			mpa.scale(1, -1);
1102 		}
1103 		mpa.rotate(item->imageRotation());
1104 		ob3.setAttribute("transform", matrixToStr(mpa));
1105 		ob6.appendChild(ob3);
1106 		ob.appendChild(ob6);
1107 	}
1108 	if (item->NamedLStyle.isEmpty())
1109 	{
1110 		if ((!item->strokePattern().isEmpty()) && (item->patternStrokePath))
1111 		{
1112 			QDomElement ob4 = m_domDoc.createElement("g");
1113 			QDomElement ob2 = m_domDoc.createElement("path");
1114 			ob2.setAttribute("d", setClipPath(&item->PoLine, true));
1115 			ob2.setAttribute("transform", trans);
1116 			ob2.setAttribute("style", fill);
1117 			ob4.appendChild(ob2);
1118 			ob4.appendChild(processSymbolStroke(item, trans));
1119 			ob.appendChild(ob4);
1120 		}
1121 		else
1122 		{
1123 			QDomElement ob4 = m_domDoc.createElement("path");
1124 			ob4.setAttribute("d", setClipPath(&item->PoLine, true));
1125 			ob4.setAttribute("style", "fill:none; " + stroke);
1126 			ob.appendChild(ob4);
1127 		}
1128 	}
1129 	else
1130 	{
1131 		multiLine ml = m_Doc->docLineStyles[item->NamedLStyle];
1132 		for (int it = ml.size()-1; it > -1; it--)
1133 		{
1134 			if ((ml[it].Color != CommonStrings::None) && (ml[it].Width != 0))
1135 			{
1136 				QDomElement ob5 = m_domDoc.createElement("path");
1137 				ob5.setAttribute("d", setClipPath(&item->PoLine, true));
1138 				ob5.setAttribute("style", "fill:none; " + getMultiStroke(&ml[it], item));
1139 				ob.appendChild(ob5);
1140 			}
1141 		}
1142 	}
1143 	return ob;
1144 }
1145 
1146 class SvgPainter: public TextLayoutPainter
1147 {
1148 	QDomElement m_elem;
1149 	SVGExPlug *m_svg;
1150 	QString m_trans;
1151 
1152 public:
SvgPainter(const QString & trans,SVGExPlug * svg,QDomElement & elem)1153 	SvgPainter(const QString& trans, SVGExPlug *svg, QDomElement &elem)
1154 		: m_elem(elem)
1155 		, m_svg(svg)
1156 		, m_trans(trans)
1157 	{}
1158 
drawGlyph(const GlyphCluster & gc)1159 	void drawGlyph(const GlyphCluster& gc) override
1160 	{
1161 		if (gc.isControlGlyphs() || gc.isEmpty())
1162 			return;
1163 		double current_x = 0.0;
1164 		for (const GlyphLayout& gl : gc.glyphs())
1165 		{
1166 			if (gl.glyph >= ScFace::CONTROL_GLYPHS)
1167 			{
1168 				current_x += gl.xadvance * gl.scaleH;
1169 				continue;
1170 			}
1171 
1172 			QTransform transform = matrix();
1173 			transform.translate(x() + gl.xoffset + current_x, y() - (fontSize() * gc.scaleV()) + gl.yoffset);
1174 			transform.scale(gc.scaleH() * fontSize() / 10.0, gc.scaleV() * fontSize() / 10.0);
1175 			QDomElement glyph = m_svg->m_domDoc.createElement("use");
1176 			glyph.setAttribute("xlink:href", "#" + m_svg->handleGlyph(gl.glyph, font()));
1177 			glyph.setAttribute("transform", m_svg->matrixToStr(transform));
1178 			QString fill = "fill:" + m_svg->setColor(fillColor().color, fillColor().shade) + ";";
1179 			QString stroke = "stroke:none;";
1180 			glyph.setAttribute("style", fill + stroke);
1181 			m_elem.appendChild(glyph);
1182 
1183 			current_x += gl.xadvance * gl.scaleH;
1184 		}
1185 	}
1186 
drawGlyphOutline(const GlyphCluster & gc,bool hasFill)1187 	void drawGlyphOutline(const GlyphCluster& gc, bool hasFill) override
1188 	{
1189 		if (gc.isControlGlyphs() | gc.isEmpty())
1190 			return;
1191 
1192 		double current_x = 0.0;
1193 		for (const GlyphLayout& gl : gc.glyphs())
1194 		{
1195 			if (gl.glyph >= ScFace::CONTROL_GLYPHS)
1196 			{
1197 				current_x += gl.xadvance * gl.scaleH;
1198 				continue;
1199 			}
1200 
1201 			QTransform transform = matrix();
1202 			transform.translate(x() + gl.xoffset + current_x, y() - (fontSize() * gc.scaleV()) + gl.yoffset);
1203 			transform.scale(gc.scaleH() * fontSize() / 10.0, gc.scaleV() * fontSize() / 10.0);
1204 			QDomElement glyph = m_svg->m_domDoc.createElement("use");
1205 			glyph.setAttribute("xlink:href", "#" + m_svg->handleGlyph(gl.glyph, font()));
1206 			glyph.setAttribute("transform", m_svg->matrixToStr(transform));
1207 			QString fill = "fill:none;";
1208 			if (hasFill)
1209 				fill = "fill:" + m_svg->setColor(fillColor().color, fillColor().shade) + ";";
1210 			QString stroke ="stroke:" + m_svg->setColor(strokeColor().color, strokeColor().shade) + ";";
1211 			stroke += " stroke-width:" + m_svg->FToStr(strokeWidth() / (gc.scaleV() * fontSize() / 10.0)) + ";";
1212 			glyph.setAttribute("style", fill + stroke);
1213 			m_elem.appendChild(glyph);
1214 
1215 			current_x += gl.xadvance * gl.scaleH;
1216 		}
1217 	}
1218 
drawLine(QPointF start,QPointF end)1219 	void drawLine(QPointF start, QPointF end) override
1220 	{
1221 		QTransform transform = matrix();
1222 		transform.translate(x(), y());
1223 		QDomElement path = m_svg-> m_domDoc.createElement("path");
1224 		path.setAttribute("d", QString("M %1 %2 L%3 %4").arg(start.x()).arg(start.y()).arg(end.x()).arg(end.y()));
1225 		QString stroke = "stroke:none;";
1226 		if (fillColor().color != CommonStrings::None)
1227 		{
1228 			stroke = "stroke:" + m_svg->setColor(fillColor().color, fillColor().shade) + ";";
1229 			stroke += " stroke-width:" + m_svg->FToStr(strokeWidth()) + ";";
1230 		}
1231 		path.setAttribute("style", "fill:none;" + stroke);
1232 		path.setAttribute("transform", m_svg->matrixToStr(transform));
1233 		m_elem.appendChild(path);
1234 	}
1235 
drawRect(QRectF rect)1236 	void drawRect(QRectF rect) override
1237 	{
1238 		QTransform transform = matrix();
1239 		transform.translate(x(), y());
1240 		QString paS = QString("M %1 %2 ").arg(rect.x()).arg(rect.y());
1241 		paS += QString("L %1 %2 ").arg(rect.x() + rect.width()).arg(rect.y());
1242 		paS += QString("L %1 %2 ").arg(rect.x() + rect.width()).arg(rect.y() + rect.height());
1243 		paS += QString("L %1 %2 ").arg(rect.x()).arg(rect.y() + rect.height());
1244 		paS += "Z";
1245 		QDomElement path = m_svg->m_domDoc.createElement("path");
1246 		path.setAttribute("d", paS);
1247 		path.setAttribute("transform", m_svg->matrixToStr(transform));
1248 		path.setAttribute("style", "fill:" + m_svg->setColor(fillColor().color, fillColor().shade) + ";" + "stroke:none;");
1249 		m_elem.appendChild(path);
1250 	}
1251 
drawObject(PageItem * item)1252 	void drawObject(PageItem* item) override
1253 	{
1254 		QTransform transform = matrix();
1255 		transform.translate(x() + item->gXpos, y() + item->gYpos);
1256 		transform.rotate(item->rotation());
1257 		transform.scale(scaleH(), scaleV());
1258 		QDomElement Group = m_svg->m_domDoc.createElement("g");
1259 		QDomElement layerGroup = m_svg->processInlineItem(item, m_trans, scaleH(), scaleV());
1260 		Group.appendChild(layerGroup);
1261 		Group.setAttribute("transform", m_svg->matrixToStr(transform));
1262 		m_elem.appendChild(Group);
1263 	}
1264 };
1265 
processTextItem(PageItem * item,const QString & trans,const QString & fill,const QString & stroke)1266 QDomElement SVGExPlug::processTextItem(PageItem *item, const QString& trans, const QString& fill, const QString& stroke)
1267 {
1268 	QDomElement ob;
1269 	ob = m_domDoc.createElement("g");
1270 	ob.setAttribute("transform", trans);
1271 	if ((item->fillColor() != CommonStrings::None) || (item->GrType != 0))
1272 	{
1273 		if (item->GrType == Gradient_Hatch)
1274 		{
1275 			QDomElement ob1 = processHatchFill(item);
1276 			ob.appendChild(ob1);
1277 			QString drS = processDropShadow(item);
1278 			if (!drS.isEmpty())
1279 				ob.setAttribute("style", "fill:none;" + drS);
1280 		}
1281 		else
1282 		{
1283 			QDomElement ob1 = m_domDoc.createElement("path");
1284 			ob1.setAttribute("d", setClipPath(&item->PoLine, true));
1285 			ob1.setAttribute("style", fill);
1286 			ob.appendChild(ob1);
1287 		}
1288 	}
1289 
1290 	if (item->itemText.length() != 0)
1291 	{
1292 		SvgPainter p(trans, this, ob);
1293 		item->textLayout.renderBackground(&p);
1294 		item->textLayout.render(&p);
1295 	}
1296 	if (item->isTextFrame())
1297 	{
1298 		if (item->NamedLStyle.isEmpty())
1299 		{
1300 			if ((!item->strokePattern().isEmpty()) && (item->patternStrokePath))
1301 			{
1302 				QDomElement ob4 = m_domDoc.createElement("g");
1303 				QDomElement ob2 = m_domDoc.createElement("path");
1304 				ob2.setAttribute("d", setClipPath(&item->PoLine, true));
1305 				ob2.setAttribute("transform", trans);
1306 				ob2.setAttribute("style", fill);
1307 				ob4.appendChild(ob2);
1308 				ob4.appendChild(processSymbolStroke(item, trans));
1309 				ob.appendChild(ob4);
1310 			}
1311 			else
1312 			{
1313 				QDomElement ob4 = m_domDoc.createElement("path");
1314 				ob4.setAttribute("d", setClipPath(&item->PoLine, true));
1315 				ob4.setAttribute("style", "fill:none; " + stroke);
1316 				ob.appendChild(ob4);
1317 			}
1318 		}
1319 		else
1320 		{
1321 			multiLine ml = m_Doc->docLineStyles[item->NamedLStyle];
1322 			for (int it = ml.size()-1; it > -1; it--)
1323 			{
1324 				if ((ml[it].Color != CommonStrings::None) && (ml[it].Width != 0))
1325 				{
1326 					QDomElement ob5 = m_domDoc.createElement("path");
1327 					ob5.setAttribute("d", setClipPath(&item->PoLine, true));
1328 					ob5.setAttribute("style", "fill:none; " + getMultiStroke(&ml[it], item));
1329 					ob.appendChild(ob5);
1330 				}
1331 			}
1332 		}
1333 	}
1334 	else if (item->isPathText() && item->PoShow)
1335 	{
1336 		if (item->NamedLStyle.isEmpty())
1337 		{
1338 			if ((!item->strokePattern().isEmpty()) && (item->patternStrokePath))
1339 			{
1340 				QDomElement ob4 = m_domDoc.createElement("g");
1341 				QDomElement ob2 = m_domDoc.createElement("path");
1342 				ob2.setAttribute("d", setClipPath(&item->PoLine, false));
1343 				ob2.setAttribute("transform", trans);
1344 				ob2.setAttribute("style", fill);
1345 				ob4.appendChild(ob2);
1346 				ob4.appendChild(processSymbolStroke(item, trans));
1347 				ob.appendChild(ob4);
1348 			}
1349 			else
1350 			{
1351 				QDomElement ob4 = m_domDoc.createElement("path");
1352 				ob4.setAttribute("d", setClipPath(&item->PoLine, false));
1353 				ob4.setAttribute("style", "fill:none; " + stroke);
1354 				ob.appendChild(ob4);
1355 			}
1356 		}
1357 		else
1358 		{
1359 			multiLine ml = m_Doc->docLineStyles[item->NamedLStyle];
1360 			for (int it = ml.size()-1; it > -1; it--)
1361 			{
1362 				if ((ml[it].Color != CommonStrings::None) && (ml[it].Width != 0))
1363 				{
1364 					QDomElement ob5 = m_domDoc.createElement("path");
1365 					ob5.setAttribute("d", setClipPath(&item->PoLine, false));
1366 					ob5.setAttribute("style", "fill:none; " + getMultiStroke(&ml[it], item));
1367 					ob.appendChild(ob5);
1368 				}
1369 			}
1370 		}
1371 	}
1372 	return ob;
1373 }
1374 
processInlineItem(PageItem * embItem,const QString & trans,double scaleH,double scaleV)1375 QDomElement SVGExPlug::processInlineItem(PageItem* embItem, const QString& trans, double scaleH, double scaleV)
1376 {
1377 	QList<PageItem*> emG;
1378 	if (embItem->isGroup())
1379 		emG = embItem->groupItemList;
1380 	else
1381 		emG.append(embItem);
1382 
1383 	QDomElement layerGroup = m_domDoc.createElement("g");
1384 	for (int em = 0; em < emG.count(); ++em)
1385 	{
1386 		PageItem* embedded = emG.at(em);
1387 		QDomElement obE;
1388 		QString fill = getFillStyle(embedded);
1389 		QString stroke = "stroke:none";
1390 		stroke = getStrokeStyle(embedded);
1391 		QString transE = "";
1392 		if (embItem->isGroup())
1393 		{
1394 			transE = "translate(" + FToStr(embedded->gXpos) + ", " + FToStr(embedded->gYpos) + ")";
1395 			if (embedded->rotation() != 0)
1396 				transE += " rotate(" + FToStr(embedded->rotation()) + ")";
1397 		}
1398 		switch (embedded->itemType())
1399 		{
1400 			case PageItem::Arc:
1401 			case PageItem::Polygon:
1402 			case PageItem::PolyLine:
1403 			case PageItem::RegularPolygon:
1404 			case PageItem::Spiral:
1405 				obE = processPolyItem(embedded, transE, fill, stroke);
1406 				if ((embedded->lineColor() != CommonStrings::None) && ((embedded->startArrowIndex() != 0) || (embedded->endArrowIndex() != 0)))
1407 					obE = processArrows(embedded, obE, transE);
1408 				break;
1409 			case PageItem::Line:
1410 				obE = processLineItem(embedded, transE, stroke);
1411 				if ((embedded->lineColor() != CommonStrings::None) && ((embedded->startArrowIndex() != 0) || (embedded->endArrowIndex() != 0)))
1412 					obE = processArrows(embedded, obE, transE);
1413 				break;
1414 			case PageItem::ImageFrame:
1415 			case PageItem::LatexFrame:
1416 				obE = processImageItem(embedded, transE, fill, stroke);
1417 				break;
1418 			case PageItem::TextFrame:
1419 			case PageItem::PathText:
1420 				obE = processTextItem(embedded, transE, fill, stroke);
1421 				break;
1422 			case PageItem::Symbol:
1423 				obE = processSymbolItem(embedded, transE);
1424 				break;
1425 			case PageItem::Group:
1426 				if (embedded->groupItemList.count() > 0)
1427 				{
1428 					obE = m_domDoc.createElement("g");
1429 					if (!embedded->AutoName)
1430 						obE.setAttribute("id", embedded->itemName());
1431 					if (embedded->GrMask > 0)
1432 						obE.setAttribute("mask", handleMask(embedded, embedded->xPos() - m_Doc->currentPage()->xOffset(), embedded->yPos() - m_Doc->currentPage()->yOffset()));
1433 					else
1434 					{
1435 						if (embedded->fillTransparency() != 0)
1436 							obE.setAttribute("opacity", FToStr(1.0 - embedded->fillTransparency()));
1437 					}
1438 					QString tr = trans;
1439 					if (embedded->imageFlippedH())
1440 					{
1441 						tr += QString(" translate(%1, 0.0)").arg(embedded->width());
1442 						tr += QString(" scale(-1.0, 1.0)");
1443 					}
1444 					if (embedded->imageFlippedV())
1445 					{
1446 						tr += QString(" translate(0.0, %1)").arg(embedded->height());
1447 						tr += QString(" scale(1.0, -1.0)");
1448 					}
1449 					tr += QString(" scale(%1, %2)").arg(embedded->width() / embedded->groupWidth).arg(embedded->height() / embedded->groupHeight);
1450 					obE.setAttribute("transform", tr);
1451 					obE.setAttribute("style", "fill:none; stroke:none");
1452 					if (embedded->groupClipping())
1453 					{
1454 						FPointArray clipPath = embedded->PoLine;
1455 						QTransform transform;
1456 						transform.scale(embedded->width() / embedded->groupWidth, embedded->height() / embedded->groupHeight);
1457 						transform = transform.inverted();
1458 						clipPath.map(transform);
1459 						QDomElement obc = createClipPathElement(&clipPath);
1460 						if (!obc.isNull())
1461 							obE.setAttribute("clip-path", "url(#" + obc.attribute("id") + ")");
1462 						if (embedded->fillRule)
1463 							obE.setAttribute("clip-rule", "evenodd");
1464 						else
1465 							obE.setAttribute("clip-rule", "nonzero");
1466 					}
1467 					for (int em = 0; em < embedded->groupItemList.count(); ++em)
1468 					{
1469 						PageItem* embed = embedded->groupItemList.at(em);
1470 						processItemOnPage(embed->gXpos, embed->gYpos, embed, &obE);
1471 					}
1472 				}
1473 				break;
1474 			default:
1475 				break;
1476 		}
1477 		layerGroup.appendChild(obE);
1478 	}
1479 	QTransform mm;
1480 	if (embItem->isGroup())
1481 		mm.scale(embItem->width() / embItem->groupWidth, embItem->height() / embItem->groupHeight);
1482 	layerGroup.setAttribute("transform", matrixToStr(mm));
1483 	return layerGroup;
1484 }
1485 
handleGlyph(uint gid,const ScFace & font)1486 QString SVGExPlug::handleGlyph(uint gid, const ScFace& font)
1487 {
1488 	QString glName = QString("Gl%1%2").arg(font.psName().simplified().replace(QRegExp("[\\s\\/\\{\\[\\]\\}\\<\\>\\(\\)\\%]"), "_" )).arg(gid);
1489 	if (m_glyphNames.contains(glName))
1490 		return glName;
1491 	FPointArray pts = font.glyphOutline(gid);
1492 	QDomElement ob = m_domDoc.createElement("path");
1493 	ob.setAttribute("d", setClipPath(&pts, true));
1494 	ob.setAttribute("id", glName);
1495 	m_globalDefs.appendChild(ob);
1496 	m_glyphNames.append(glName);
1497 	return glName;
1498 }
1499 
processArrows(PageItem * item,const QDomElement & line,const QString & trans)1500 QDomElement SVGExPlug::processArrows(PageItem *item, const QDomElement& line, const QString& trans)
1501 {
1502 	QDomElement ob, gr;
1503 	gr = m_domDoc.createElement("g");
1504 	gr.appendChild(line);
1505 	if (item->startArrowIndex() != 0)
1506 	{
1507 		QTransform arrowTrans;
1508 		FPointArray arrow = m_Doc->arrowStyles().at(item->startArrowIndex()-1).points.copy();
1509 		if (item->itemType() == PageItem::Line)
1510 		{
1511 			arrowTrans.translate(0, 0);
1512 			arrowTrans.scale(item->startArrowScale() / 100.0, item->startArrowScale() / 100.0);
1513 			if (item->NamedLStyle.isEmpty())
1514 			{
1515 				if (item->lineWidth() != 0.0)
1516 					arrowTrans.scale(item->lineWidth(), item->lineWidth());
1517 			}
1518 			else
1519 			{
1520 				multiLine ml = m_Doc->docLineStyles[item->NamedLStyle];
1521 				if (ml[ml.size()-1].Width != 0.0)
1522 					arrowTrans.scale(ml[ml.size()-1].Width, ml[ml.size()-1].Width);
1523 			}
1524 			arrowTrans.scale(-1,1);
1525 		}
1526 		else
1527 		{
1528 			FPoint Start = item->PoLine.point(0);
1529 			for (int xx = 1; xx < item->PoLine.size(); xx += 2)
1530 			{
1531 				FPoint Vector = item->PoLine.point(xx);
1532 				if ((Start.x() != Vector.x()) || (Start.y() != Vector.y()))
1533 				{
1534 					double r = atan2(Start.y()-Vector.y(),Start.x()-Vector.x())*(180.0/M_PI);
1535 					arrowTrans.translate(Start.x(), Start.y());
1536 					arrowTrans.rotate(r);
1537 					arrowTrans.scale(item->startArrowScale() / 100.0, item->startArrowScale() / 100.0);
1538 					if (item->NamedLStyle.isEmpty())
1539 					{
1540 						if (item->lineWidth() != 0.0)
1541 							arrowTrans.scale(item->lineWidth(), item->lineWidth());
1542 					}
1543 					else
1544 					{
1545 						multiLine ml = m_Doc->docLineStyles[item->NamedLStyle];
1546 						if (ml[ml.size()-1].Width != 0.0)
1547 							arrowTrans.scale(ml[ml.size()-1].Width, ml[ml.size()-1].Width);
1548 					}
1549 					break;
1550 				}
1551 			}
1552 		}
1553 		arrow.map(arrowTrans);
1554 		if (item->NamedLStyle.isEmpty())
1555 		{
1556 			ob = m_domDoc.createElement("path");
1557 			ob.setAttribute("d", setClipPath(&arrow, true));
1558 			ob.setAttribute("transform", trans);
1559 			QString aFill;
1560 			if (!item->strokePattern().isEmpty())
1561 			{
1562 				QString pattID = item->strokePattern()+IToStr(m_pattCount);
1563 				m_pattCount++;
1564 				ScPattern pa = m_Doc->docPatterns[item->strokePattern()];
1565 				QDomElement patt = m_domDoc.createElement("pattern");
1566 				patt.setAttribute("id", pattID);
1567 				patt.setAttribute("height", pa.height);
1568 				patt.setAttribute("width", pa.width);
1569 				patt.setAttribute("patternUnits", "userSpaceOnUse");
1570 				double patternScaleX, patternScaleY, patternOffsetX, patternOffsetY, patternRotation, patternSkewX, patternSkewY, patternSpace;
1571 				item->strokePatternTransform(patternScaleX, patternScaleY, patternOffsetX, patternOffsetY, patternRotation, patternSkewX, patternSkewY, patternSpace);
1572 				bool mirrorX, mirrorY;
1573 				item->strokePatternFlip(mirrorX, mirrorY);
1574 				QTransform mpa;
1575 				mpa.translate(-item->lineWidth() / 2.0, -item->lineWidth() / 2.0);
1576 				mpa.translate(patternOffsetX, patternOffsetY);
1577 				mpa.rotate(patternRotation);
1578 				mpa.shear(-patternSkewX, patternSkewY);
1579 				mpa.scale(pa.scaleX, pa.scaleY);
1580 				mpa.scale(patternScaleX / 100.0 , patternScaleY / 100.0);
1581 				if (mirrorX)
1582 					mpa.scale(-1, 1);
1583 				if (mirrorY)
1584 					mpa.scale(1, -1);
1585 				patt.setAttribute("patternTransform", matrixToStr(mpa));
1586 				patt.setAttribute("xlink:href", "#" + item->strokePattern());
1587 				m_globalDefs.appendChild(patt);
1588 				aFill += "fill:url(#" + pattID + ");";
1589 			}
1590 			else if (item->GrTypeStroke > 0)
1591 			{
1592 				QDomElement grad;
1593 				if (item->GrTypeStroke == Gradient_Radial)
1594 				{
1595 					grad = m_domDoc.createElement("radialGradient");
1596 					grad.setAttribute("r", FToStr(sqrt(pow(item->GrStrokeEndX - item->GrStrokeStartX, 2) + pow(item->GrStrokeEndY - item->GrStrokeStartY,2))));
1597 					grad.setAttribute("cx", FToStr(item->GrStrokeStartX));
1598 					grad.setAttribute("cy", FToStr(item->GrStrokeStartY));
1599 				}
1600 				else
1601 				{
1602 					grad = m_domDoc.createElement("linearGradient");
1603 					grad.setAttribute("x1", FToStr(item->GrStrokeStartX));
1604 					grad.setAttribute("y1", FToStr(item->GrStrokeStartY));
1605 					grad.setAttribute("x2", FToStr(item->GrStrokeEndX));
1606 					grad.setAttribute("y2", FToStr(item->GrStrokeEndY));
1607 				}
1608 				bool   isFirst = true;
1609 				double actualStop = 0.0, lastStop = 0.0;
1610 				QList<VColorStop*> cstops = item->stroke_gradient.colorStops();
1611 				for (int cst = 0; cst < item->stroke_gradient.stops(); ++cst)
1612 				{
1613 					actualStop = cstops.at(cst)->rampPoint;
1614 					if ((actualStop != lastStop) || (isFirst))
1615 					{
1616 						QDomElement itcl = m_domDoc.createElement("stop");
1617 						itcl.setAttribute("offset", FToStr(cstops.at(cst)->rampPoint*100) + "%");
1618 						if (cstops.at(cst)->name == CommonStrings::None)
1619 							itcl.setAttribute("stop-opacity", FToStr(0));
1620 						else
1621 							itcl.setAttribute("stop-opacity", FToStr(cstops.at(cst)->opacity));
1622 						itcl.setAttribute("stop-color", setColor(cstops.at(cst)->name, cstops.at(cst)->shade));
1623 						grad.appendChild(itcl);
1624 						lastStop = actualStop;
1625 						isFirst  = false;
1626 					}
1627 				}
1628 				grad.setAttribute("id", "Grad" + IToStr(m_gradCount));
1629 				grad.setAttribute("gradientUnits", "userSpaceOnUse");
1630 				m_globalDefs.appendChild(grad);
1631 				aFill = " fill:url(#Grad" + IToStr(m_gradCount) + ");";
1632 				m_gradCount++;
1633 			}
1634 			else
1635 				aFill = "fill:" + setColor(item->lineColor(), item->lineShade()) + ";";
1636 			if (item->lineTransparency() != 0)
1637 				aFill += " fill-opacity:" + FToStr(1.0 - item->lineTransparency()) + ";";
1638 			ob.setAttribute("style", aFill + " stroke:none;");
1639 			gr.appendChild(ob);
1640 		}
1641 		else
1642 		{
1643 			multiLine ml = m_Doc->docLineStyles[item->NamedLStyle];
1644 			if (ml[0].Color != CommonStrings::None)
1645 			{
1646 				ob = m_domDoc.createElement("path");
1647 				ob.setAttribute("d", setClipPath(&arrow, true));
1648 				ob.setAttribute("transform", trans);
1649 				QString aFill = "fill:" + setColor(ml[0].Color, ml[0].Shade) + ";";
1650 				ob.setAttribute("style", aFill + " stroke:none;");
1651 				gr.appendChild(ob);
1652 			}
1653 			for (int it = ml.size()-1; it > 0; it--)
1654 			{
1655 				if (ml[it].Color != CommonStrings::None)
1656 				{
1657 					QDomElement ob5 = m_domDoc.createElement("path");
1658 					ob5.setAttribute("d", setClipPath(&arrow, true));
1659 					ob5.setAttribute("transform", trans);
1660 					QString stroke = "fill:none; stroke:" + setColor(ml[it].Color, ml[it].Shade) + "; stroke-linecap:butt; stroke-linejoin:miter; stroke-dasharray:none;";
1661 					if (ml[it].Width != 0.0)
1662 						stroke += " stroke-width:" + FToStr(ml[it].Width) + ";";
1663 					else
1664 						stroke += " stroke-width:1px;";
1665 					ob5.setAttribute("style", stroke);
1666 					gr.appendChild(ob5);
1667 				}
1668 			}
1669 		}
1670 	}
1671 	if (item->endArrowIndex() != 0)
1672 	{
1673 		QTransform arrowTrans;
1674 		FPointArray arrow = m_Doc->arrowStyles().at(item->endArrowIndex()-1).points.copy();
1675 		if (item->itemType() == PageItem::Line)
1676 		{
1677 			arrowTrans.translate(item->width(), 0);
1678 			arrowTrans.scale(item->endArrowScale() / 100.0, item->endArrowScale() / 100.0);
1679 			if (item->NamedLStyle.isEmpty())
1680 			{
1681 				if (item->lineWidth() != 0.0)
1682 					arrowTrans.scale(item->lineWidth(), item->lineWidth());
1683 			}
1684 			else
1685 			{
1686 				multiLine ml = m_Doc->docLineStyles[item->NamedLStyle];
1687 				if (ml[ml.size()-1].Width != 0.0)
1688 					arrowTrans.scale(ml[ml.size() - 1].Width, ml[ml.size() - 1].Width);
1689 			}
1690 		}
1691 		else
1692 		{
1693 			FPoint End = item->PoLine.point(item->PoLine.size() - 2);
1694 			for (uint xx = item->PoLine.size() - 1; xx > 0; xx -= 2)
1695 			{
1696 				FPoint Vector = item->PoLine.point(xx);
1697 				if ((End.x() != Vector.x()) || (End.y() != Vector.y()))
1698 				{
1699 					double r = atan2(End.y() - Vector.y(), End.x() - Vector.x()) * (180.0 / M_PI);
1700 					arrowTrans.translate(End.x(), End.y());
1701 					arrowTrans.rotate(r);
1702 					arrowTrans.scale(item->endArrowScale() / 100.0, item->endArrowScale() / 100.0);
1703 					if (item->NamedLStyle.isEmpty())
1704 					{
1705 						if (item->lineWidth() != 0.0)
1706 							arrowTrans.scale(item->lineWidth(), item->lineWidth());
1707 					}
1708 					else
1709 					{
1710 						multiLine ml = m_Doc->docLineStyles[item->NamedLStyle];
1711 						if (ml[ml.size() - 1].Width != 0.0)
1712 							arrowTrans.scale(ml[ml.size() - 1].Width, ml[ml.size() - 1].Width);
1713 					}
1714 					break;
1715 				}
1716 			}
1717 		}
1718 		arrow.map(arrowTrans);
1719 		if (item->NamedLStyle.isEmpty())
1720 		{
1721 			ob = m_domDoc.createElement("path");
1722 			ob.setAttribute("d", setClipPath(&arrow, true));
1723 			ob.setAttribute("transform", trans);
1724 			QString aFill;
1725 			if (!item->strokePattern().isEmpty())
1726 			{
1727 				QString pattID = item->strokePattern() + IToStr(m_pattCount);
1728 				m_pattCount++;
1729 				ScPattern pa = m_Doc->docPatterns[item->strokePattern()];
1730 				QDomElement patt = m_domDoc.createElement("pattern");
1731 				patt.setAttribute("id", pattID);
1732 				patt.setAttribute("height", pa.height);
1733 				patt.setAttribute("width", pa.width);
1734 				patt.setAttribute("patternUnits", "userSpaceOnUse");
1735 				double patternScaleX, patternScaleY, patternOffsetX, patternOffsetY, patternRotation, patternSkewX, patternSkewY, patternSpace;
1736 				item->strokePatternTransform(patternScaleX, patternScaleY, patternOffsetX, patternOffsetY, patternRotation, patternSkewX, patternSkewY, patternSpace);
1737 				bool mirrorX, mirrorY;
1738 				item->strokePatternFlip(mirrorX, mirrorY);
1739 				QTransform mpa;
1740 				mpa.translate(-item->lineWidth() / 2.0, -item->lineWidth() / 2.0);
1741 				mpa.translate(patternOffsetX, patternOffsetY);
1742 				mpa.rotate(patternRotation);
1743 				mpa.shear(-patternSkewX, patternSkewY);
1744 				mpa.scale(pa.scaleX, pa.scaleY);
1745 				mpa.scale(patternScaleX / 100.0 , patternScaleY / 100.0);
1746 				if (mirrorX)
1747 					mpa.scale(-1, 1);
1748 				if (mirrorY)
1749 					mpa.scale(1, -1);
1750 				patt.setAttribute("patternTransform", matrixToStr(mpa));
1751 				patt.setAttribute("xlink:href", "#" + item->strokePattern());
1752 				m_globalDefs.appendChild(patt);
1753 				aFill += "fill:url(#" + pattID + ");";
1754 			}
1755 			else if (item->GrTypeStroke > 0)
1756 			{
1757 				QDomElement grad;
1758 				if (item->GrTypeStroke == Gradient_Radial)
1759 				{
1760 					grad = m_domDoc.createElement("radialGradient");
1761 					grad.setAttribute("r", FToStr(sqrt(pow(item->GrStrokeEndX - item->GrStrokeStartX, 2) + pow(item->GrStrokeEndY - item->GrStrokeStartY,2))));
1762 					grad.setAttribute("cx", FToStr(item->GrStrokeStartX));
1763 					grad.setAttribute("cy", FToStr(item->GrStrokeStartY));
1764 				}
1765 				else
1766 				{
1767 					grad = m_domDoc.createElement("linearGradient");
1768 					grad.setAttribute("x1", FToStr(item->GrStrokeStartX));
1769 					grad.setAttribute("y1", FToStr(item->GrStrokeStartY));
1770 					grad.setAttribute("x2", FToStr(item->GrStrokeEndX));
1771 					grad.setAttribute("y2", FToStr(item->GrStrokeEndY));
1772 				}
1773 				bool   isFirst = true;
1774 				double actualStop = 0.0, lastStop = 0.0;
1775 				QList<VColorStop*> cstops = item->stroke_gradient.colorStops();
1776 				for (int cst = 0; cst < item->stroke_gradient.stops(); ++cst)
1777 				{
1778 					actualStop = cstops.at(cst)->rampPoint;
1779 					if ((actualStop != lastStop) || (isFirst))
1780 					{
1781 						QDomElement itcl = m_domDoc.createElement("stop");
1782 						itcl.setAttribute("offset", FToStr(cstops.at(cst)->rampPoint*100) + "%");
1783 						if (cstops.at(cst)->name == CommonStrings::None)
1784 							itcl.setAttribute("stop-opacity", FToStr(0));
1785 						else
1786 							itcl.setAttribute("stop-opacity", FToStr(cstops.at(cst)->opacity));
1787 						itcl.setAttribute("stop-color", setColor(cstops.at(cst)->name, cstops.at(cst)->shade));
1788 						grad.appendChild(itcl);
1789 						lastStop = actualStop;
1790 						isFirst  = false;
1791 					}
1792 				}
1793 				grad.setAttribute("id", "Grad" + IToStr(m_gradCount));
1794 				grad.setAttribute("gradientUnits", "userSpaceOnUse");
1795 				m_globalDefs.appendChild(grad);
1796 				aFill = " fill:url(#Grad" + IToStr(m_gradCount) + ");";
1797 				m_gradCount++;
1798 			}
1799 			else
1800 				aFill = "fill:" + setColor(item->lineColor(), item->lineShade()) + ";";
1801 			if (item->lineTransparency() != 0)
1802 				aFill += " fill-opacity:" + FToStr(1.0 - item->lineTransparency()) + ";";
1803 			ob.setAttribute("style", aFill + " stroke:none;");
1804 			gr.appendChild(ob);
1805 		}
1806 		else
1807 		{
1808 			multiLine ml = m_Doc->docLineStyles[item->NamedLStyle];
1809 			if (ml[0].Color != CommonStrings::None)
1810 			{
1811 				ob = m_domDoc.createElement("path");
1812 				ob.setAttribute("d", setClipPath(&arrow, true));
1813 				ob.setAttribute("transform", trans);
1814 				QString aFill = "fill:" + setColor(ml[0].Color, ml[0].Shade) + ";";
1815 				ob.setAttribute("style", aFill + " stroke:none;");
1816 				gr.appendChild(ob);
1817 			}
1818 			for (int it = ml.size()-1; it > 0; it--)
1819 			{
1820 				if (ml[it].Color != CommonStrings::None)
1821 				{
1822 					QDomElement ob5 = m_domDoc.createElement("path");
1823 					ob5.setAttribute("d", setClipPath(&arrow, true));
1824 					ob5.setAttribute("transform", trans);
1825 					QString stroke = "fill:none; stroke:" + setColor(ml[it].Color, ml[it].Shade) + "; stroke-linecap:butt; stroke-linejoin:miter; stroke-dasharray:none;";
1826 					if (ml[it].Width != 0.0)
1827 						stroke += " stroke-width:" + FToStr(ml[it].Width) + ";";
1828 					else
1829 						stroke += " stroke-width:1px;";
1830 					ob5.setAttribute("style", stroke);
1831 					gr.appendChild(ob5);
1832 				}
1833 			}
1834 		}
1835 	}
1836 	return gr;
1837 }
1838 
handleMask(PageItem * item,double xOffset,double yOffset)1839 QString SVGExPlug::handleMask(PageItem *item, double xOffset, double yOffset)
1840 {
1841 	QDomElement grad;
1842 	QString retVal = "";
1843 	if (item->GrMask == 0)
1844 		return retVal;
1845 
1846 	QString maskID = "Mask" + IToStr(m_maskCount);
1847 	m_maskCount++;
1848 	QDomElement mask = m_domDoc.createElement("mask");
1849 	mask.setAttribute("id", maskID);
1850 	QDomElement ob = m_domDoc.createElement("path");
1851 	ob.setAttribute("d", "M 0 0 L " + FToStr(item->width()) + " 0 L " + FToStr(item->width()) + " " + FToStr(item->height()) + " L 0 " + FToStr(item->height()) + " Z");
1852 	if (item->isGroup())
1853 	{
1854 		QString trans = "translate(" + FToStr(xOffset) + ", " + FToStr(yOffset) + ")";
1855 		if (item->rotation() != 0)
1856 			trans += " rotate(" + FToStr(item->rotation()) + ")";
1857 		ob.setAttribute("transform", trans);
1858 	}
1859 	if ((item->GrMask == GradMask_Pattern) || (item->GrMask == GradMask_PatternLumAlpha))
1860 	{
1861 		QString pattID = item->patternMask() + IToStr(m_pattCount);
1862 		m_pattCount++;
1863 		ScPattern pa = m_Doc->docPatterns[item->patternMask()];
1864 		QDomElement patt = m_domDoc.createElement("pattern");
1865 		patt.setAttribute("id", pattID);
1866 		patt.setAttribute("height", FToStr(pa.height));
1867 		patt.setAttribute("width", FToStr(pa.width));
1868 		patt.setAttribute("patternUnits", "userSpaceOnUse");
1869 		double patternScaleX, patternScaleY, patternOffsetX, patternOffsetY, patternRotation, patternSkewX, patternSkewY;
1870 		item->maskTransform(patternScaleX, patternScaleY, patternOffsetX, patternOffsetY, patternRotation, patternSkewX, patternSkewY);
1871 		bool mirrorX, mirrorY;
1872 		item->maskFlip(mirrorX, mirrorY);
1873 		QTransform mpa;
1874 		mpa.translate(patternOffsetX, patternOffsetY);
1875 		mpa.rotate(patternRotation);
1876 		mpa.shear(-patternSkewX, patternSkewY);
1877 		mpa.scale(pa.scaleX, pa.scaleY);
1878 		mpa.scale(patternScaleX / 100.0 , patternScaleY / 100.0);
1879 		if (mirrorX)
1880 			mpa.scale(-1, 1);
1881 		if (mirrorY)
1882 			mpa.scale(1, -1);
1883 		patt.setAttribute("patternTransform", matrixToStr(mpa));
1884 		patt.setAttribute("xlink:href", "#" + item->patternMask());
1885 		m_globalDefs.appendChild(patt);
1886 		ob.setAttribute("fill", "url(#" + pattID + ")");
1887 	}
1888 	else if ((item->GrMask == GradMask_Linear) || (item->GrMask == GradMask_Radial) || (item->GrMask == GradMask_LinearLumAlpha) || (item->GrMask == GradMask_RadialLumAlpha))
1889 	{
1890 		if ((item->GrMask == GradMask_Linear) || (item->GrMask == GradMask_LinearLumAlpha))
1891 		{
1892 			grad = m_domDoc.createElement("linearGradient");
1893 			grad.setAttribute("x1", FToStr(item->GrMaskStartX));
1894 			grad.setAttribute("y1", FToStr(item->GrMaskStartY));
1895 			grad.setAttribute("x2", FToStr(item->GrMaskEndX));
1896 			grad.setAttribute("y2", FToStr(item->GrMaskEndY));
1897 		}
1898 		else
1899 		{
1900 			grad = m_domDoc.createElement("radialGradient");
1901 			grad.setAttribute("r", FToStr(sqrt(pow(item->GrMaskEndX - item->GrMaskStartX, 2) + pow(item->GrMaskEndY - item->GrMaskStartY,2))));
1902 			grad.setAttribute("cx", FToStr(item->GrMaskStartX));
1903 			grad.setAttribute("cy", FToStr(item->GrMaskStartY));
1904 			grad.setAttribute("fx", FToStr(item->GrMaskFocalX));
1905 			grad.setAttribute("fy", FToStr(item->GrMaskFocalY));
1906 		}
1907 		double gradientSkew;
1908 		if (item->GrMaskSkew == 90)
1909 			gradientSkew = 1;
1910 		else if (item->GrMaskSkew == 180)
1911 			gradientSkew = 0;
1912 		else if (item->GrMaskSkew == 270)
1913 			gradientSkew = -1;
1914 		else if (item->GrMaskSkew == 390)
1915 			gradientSkew = 0;
1916 		else
1917 			gradientSkew = tan(M_PI / 180.0 * item->GrMaskSkew);
1918 		QTransform qmatrix;
1919 		if (item->GrType == Gradient_Linear)
1920 		{
1921 			qmatrix.translate(item->GrMaskStartX, item->GrMaskStartY);
1922 			qmatrix.shear(-gradientSkew, 0);
1923 			qmatrix.translate(-item->GrMaskStartX, -item->GrMaskStartY);
1924 		}
1925 		else
1926 		{
1927 			double rotEnd = xy2Deg(item->GrMaskEndX - item->GrMaskStartX, item->GrMaskEndY - item->GrMaskStartY);
1928 			qmatrix.translate(item->GrMaskStartX, item->GrMaskStartY);
1929 			qmatrix.rotate(rotEnd);
1930 			qmatrix.shear(gradientSkew, 0);
1931 			qmatrix.translate(0, item->GrMaskStartY * (1.0 - item->GrMaskScale));
1932 			qmatrix.translate(-item->GrMaskStartX, -item->GrMaskStartY);
1933 			qmatrix.scale(1, item->GrMaskScale);
1934 		}
1935 		grad.setAttribute("gradientTransform", matrixToStr(qmatrix));
1936 		grad.setAttribute("id", "Grad" + IToStr(m_gradCount));
1937 		grad.setAttribute("gradientUnits", "userSpaceOnUse");
1938 		QList<VColorStop*> cstops = item->mask_gradient.colorStops();
1939 		for (int cst = 0; cst < item->mask_gradient.stops(); ++cst)
1940 		{
1941 			QDomElement itcl = m_domDoc.createElement("stop");
1942 			itcl.setAttribute("offset", FToStr(cstops.at(cst)->rampPoint*100) + "%");
1943 			if (cstops.at(cst)->name == CommonStrings::None)
1944 				itcl.setAttribute("stop-opacity", FToStr(0));
1945 			else
1946 				itcl.setAttribute("stop-opacity", FToStr(cstops.at(cst)->opacity));
1947 			itcl.setAttribute("stop-color", setColor(cstops.at(cst)->name, cstops.at(cst)->shade));
1948 			grad.appendChild(itcl);
1949 		}
1950 		m_globalDefs.appendChild(grad);
1951 		ob.setAttribute("fill", "url(#Grad" + IToStr(m_gradCount) + ")");
1952 		m_gradCount++;
1953 	}
1954 	if ((item->lineColor() != CommonStrings::None) && (!item->isGroup()))
1955 	{
1956 		ob.setAttribute("stroke", "white");
1957 		if (item->lineWidth() != 0.0)
1958 			ob.setAttribute("stroke-width", FToStr(item->lineWidth()));
1959 		else
1960 			ob.setAttribute("stroke-width", "1px");
1961 	}
1962 	else
1963 		ob.setAttribute("stroke", "none");
1964 	mask.appendChild(ob);
1965 	m_globalDefs.appendChild(mask);
1966 
1967 	retVal = "url(#" + maskID + ")";
1968 	return retVal;
1969 }
1970 
getFillStyle(PageItem * item)1971 QString SVGExPlug::getFillStyle(PageItem *item)
1972 {
1973 	QDomElement grad;
1974 	QString fill;
1975 	if (item->asPathText())
1976 		return "fill:none;";
1977 	if ((item->fillColor() != CommonStrings::None) || (item->GrType != 0))
1978 	{
1979 		fill = "fill:" + setColor(item->fillColor(), item->fillShade()) + ";";
1980 		if (item->GrType != 0)
1981 		{
1982 			if (item->GrType == Gradient_Pattern)
1983 			{
1984 				QString pattID = item->pattern() + IToStr(m_pattCount);
1985 				m_pattCount++;
1986 				ScPattern pa = m_Doc->docPatterns[item->pattern()];
1987 				QDomElement patt = m_domDoc.createElement("pattern");
1988 				patt.setAttribute("id", pattID);
1989 				patt.setAttribute("height", FToStr(pa.height));
1990 				patt.setAttribute("width", FToStr(pa.width));
1991 				patt.setAttribute("patternUnits", "userSpaceOnUse");
1992 				double patternScaleX, patternScaleY, patternOffsetX, patternOffsetY, patternRotation, patternSkewX, patternSkewY;
1993 				item->patternTransform(patternScaleX, patternScaleY, patternOffsetX, patternOffsetY, patternRotation, patternSkewX, patternSkewY);
1994 				bool mirrorX, mirrorY;
1995 				item->patternFlip(mirrorX, mirrorY);
1996 				QTransform mpa;
1997 				mpa.translate(patternOffsetX, patternOffsetY);
1998 				mpa.rotate(patternRotation);
1999 				mpa.shear(-patternSkewX, patternSkewY);
2000 				mpa.scale(pa.scaleX, pa.scaleY);
2001 				mpa.scale(patternScaleX / 100.0 , patternScaleY / 100.0);
2002 				if (mirrorX)
2003 					mpa.scale(-1, 1);
2004 				if (mirrorY)
2005 					mpa.scale(1, -1);
2006 				patt.setAttribute("patternTransform", matrixToStr(mpa));
2007 				patt.setAttribute("xlink:href", "#" + item->pattern());
2008 				m_globalDefs.appendChild(patt);
2009 				fill = "fill:url(#" + pattID + ");";
2010 			}
2011 			else
2012 			{
2013 				if (item->GrType == Gradient_Linear)
2014 				{
2015 					grad = m_domDoc.createElement("linearGradient");
2016 					grad.setAttribute("x1", FToStr(item->GrStartX));
2017 					grad.setAttribute("y1", FToStr(item->GrStartY));
2018 					grad.setAttribute("x2", FToStr(item->GrEndX));
2019 					grad.setAttribute("y2", FToStr(item->GrEndY));
2020 				}
2021 				else
2022 				{
2023 					grad = m_domDoc.createElement("radialGradient");
2024 					grad.setAttribute("r", FToStr(sqrt(pow(item->GrEndX - item->GrStartX, 2) + pow(item->GrEndY - item->GrStartY,2))));
2025 					grad.setAttribute("cx", FToStr(item->GrStartX));
2026 					grad.setAttribute("cy", FToStr(item->GrStartY));
2027 					grad.setAttribute("fx", FToStr(item->GrFocalX));
2028 					grad.setAttribute("fy", FToStr(item->GrFocalY));
2029 				}
2030 				double gradientSkew;
2031 				if (item->GrSkew == 90)
2032 					gradientSkew = 1;
2033 				else if (item->GrSkew == 180)
2034 					gradientSkew = 0;
2035 				else if (item->GrSkew == 270)
2036 					gradientSkew = -1;
2037 				else if (item->GrSkew == 390)
2038 					gradientSkew = 0;
2039 				else
2040 					gradientSkew = tan(M_PI / 180.0 * item->GrSkew);
2041 				QTransform qmatrix;
2042 				if (item->GrType == Gradient_Linear)
2043 				{
2044 					qmatrix.translate(item->GrStartX, item->GrStartY);
2045 					qmatrix.shear(-gradientSkew, 0);
2046 					qmatrix.translate(-item->GrStartX, -item->GrStartY);
2047 				}
2048 				else
2049 				{
2050 					double rotEnd = xy2Deg(item->GrEndX - item->GrStartX, item->GrEndY - item->GrStartY);
2051 					qmatrix.translate(item->GrStartX, item->GrStartY);
2052 					qmatrix.rotate(rotEnd);
2053 					qmatrix.shear(gradientSkew, 0);
2054 					qmatrix.translate(0, item->GrStartY * (1.0 - item->GrScale));
2055 					qmatrix.translate(-item->GrStartX, -item->GrStartY);
2056 					qmatrix.scale(1, item->GrScale);
2057 				}
2058 				grad.setAttribute("gradientTransform", matrixToStr(qmatrix));
2059 				grad.setAttribute("id", "Grad" + IToStr(m_gradCount));
2060 				grad.setAttribute("gradientUnits", "userSpaceOnUse");
2061 				bool   isFirst = true;
2062 				double actualStop = 0.0, lastStop = 0.0;
2063 				QList<VColorStop*> cstops = item->fill_gradient.colorStops();
2064 				for (int cst = 0; cst < item->fill_gradient.stops(); ++cst)
2065 				{
2066 					actualStop = cstops.at(cst)->rampPoint;
2067 					if ((actualStop != lastStop) || (isFirst))
2068 					{
2069 						QDomElement itcl = m_domDoc.createElement("stop");
2070 						itcl.setAttribute("offset", FToStr(cstops.at(cst)->rampPoint * 100) + "%");
2071 						if (cstops.at(cst)->name == CommonStrings::None)
2072 							itcl.setAttribute("stop-opacity", FToStr(0));
2073 						else
2074 							itcl.setAttribute("stop-opacity", FToStr(cstops.at(cst)->opacity));
2075 						itcl.setAttribute("stop-color", setColor(cstops.at(cst)->name, cstops.at(cst)->shade));
2076 						grad.appendChild(itcl);
2077 						lastStop = actualStop;
2078 						isFirst  = false;
2079 					}
2080 				}
2081 				m_globalDefs.appendChild(grad);
2082 				fill = "fill:url(#Grad" + IToStr(m_gradCount) + ");";
2083 				m_gradCount++;
2084 			}
2085 		}
2086 		if (item->fillRule)
2087 			fill += " fill-rule:evenodd;";
2088 		else
2089 			fill += " fill-rule:nonzero;";
2090 		if (item->fillTransparency() != 0)
2091 			fill += " fill-opacity:" + FToStr(1.0 - item->fillTransparency()) + ";";
2092 	}
2093 	else
2094 		fill = "fill:none;";
2095 	return fill;
2096 }
2097 
writeBasePatterns()2098 void SVGExPlug::writeBasePatterns()
2099 {
2100 	QStringList patterns = m_Doc->getPatternDependencyList(m_Doc->getUsedPatterns());
2101 	for (int c = 0; c < patterns.count(); ++c)
2102 	{
2103 		ScPattern pa = m_Doc->docPatterns[patterns[c]];
2104 		QDomElement patt = m_domDoc.createElement("pattern");
2105 		patt.setAttribute("id", patterns[c]);
2106 		patt.setAttribute("height", FToStr(pa.height));
2107 		patt.setAttribute("width", FToStr(pa.width));
2108 		for (int em = 0; em < pa.items.count(); ++em)
2109 		{
2110 			PageItem* item = pa.items.at(em);
2111 			processItemOnPage(item->gXpos, item->gYpos, item, &patt);
2112 		}
2113 		m_globalDefs.appendChild(patt);
2114 	}
2115 }
2116 
writeBaseSymbols()2117 void SVGExPlug::writeBaseSymbols()
2118 {
2119 	QStringList patterns = m_Doc->getUsedSymbols();
2120 	for (int c = 0; c < patterns.count(); ++c)
2121 	{
2122 		ScPattern pa = m_Doc->docPatterns[patterns[c]];
2123 		QDomElement patt = m_domDoc.createElement("symbol");
2124 		patt.setAttribute("id", "S" + patterns[c]);
2125 		patt.setAttribute("viewbox", "0 0 " +  FToStr(pa.height) + " " + FToStr(pa.width));
2126 		for (int em = 0; em < pa.items.count(); ++em)
2127 		{
2128 			PageItem* item = pa.items.at(em);
2129 			processItemOnPage(item->gXpos, item->gYpos, item, &patt);
2130 		}
2131 		m_globalDefs.appendChild(patt);
2132 	}
2133 }
2134 
getStrokeStyle(PageItem * item)2135 QString SVGExPlug::getStrokeStyle(PageItem *item)
2136 {
2137 	QString stroke = "";
2138 	if (item->lineTransparency() != 0)
2139 		stroke = "stroke-opacity:" + FToStr(1.0 - item->lineTransparency()) + ";";
2140 	if (item->lineWidth() != 0.0)
2141 		stroke = "stroke-width:" + FToStr(item->lineWidth()) + ";";
2142 	else
2143 		stroke = "stroke-width:1px;";
2144 	stroke += " stroke-linecap:";
2145 	switch (item->PLineEnd)
2146 	{
2147 		case Qt::FlatCap:
2148 			stroke += "butt;";
2149 			break;
2150 		case Qt::SquareCap:
2151 			stroke += "square;";
2152 			break;
2153 		case Qt::RoundCap:
2154 			stroke += "round;";
2155 			break;
2156 		default:
2157 			stroke += "butt;";
2158 			break;
2159 	}
2160 	stroke += " stroke-linejoin:";
2161 	switch (item->PLineJoin)
2162 	{
2163 		case Qt::MiterJoin:
2164 			stroke += "miter;";
2165 			break;
2166 		case Qt::BevelJoin:
2167 			stroke += "bevel;";
2168 			break;
2169 		case Qt::RoundJoin:
2170 			stroke += "round;";
2171 			break;
2172 		default:
2173 			stroke += "miter;";
2174 			break;
2175 	}
2176 	stroke += " stroke-dasharray:";
2177 	if (item->DashValues.count() != 0)
2178 	{
2179 		for (auto it = item->DashValues.cbegin(); it != item->DashValues.cend(); ++it )
2180 		{
2181 			stroke += IToStr(static_cast<int>(*it)) + " ";
2182 		}
2183 		stroke += "; stroke-dashoffset:" + IToStr(static_cast<int>(item->DashOffset)) + ";";
2184 	}
2185 	else
2186 	{
2187 		if (item->PLineArt == Qt::SolidLine)
2188 			stroke += "none;";
2189 		else
2190 		{
2191 			QString Da = getDashString(item->PLineArt, item->lineWidth());
2192 			if (Da.isEmpty())
2193 				stroke += "none;";
2194 			else
2195 				stroke += Da.replace(" ", ", ") + ";";
2196 		}
2197 	}
2198 	if ((!item->strokePattern().isEmpty()) && (!item->patternStrokePath))
2199 	{
2200 		QString pattID = item->strokePattern() + IToStr(m_pattCount);
2201 		m_pattCount++;
2202 		ScPattern pa = m_Doc->docPatterns[item->strokePattern()];
2203 		QDomElement patt = m_domDoc.createElement("pattern");
2204 		patt.setAttribute("id", pattID);
2205 		patt.setAttribute("height", FToStr(pa.height));
2206 		patt.setAttribute("width", FToStr(pa.width));
2207 		patt.setAttribute("patternUnits", "userSpaceOnUse");
2208 		double patternScaleX, patternScaleY, patternOffsetX, patternOffsetY, patternRotation, patternSkewX, patternSkewY, patternSpace;
2209 		item->strokePatternTransform(patternScaleX, patternScaleY, patternOffsetX, patternOffsetY, patternRotation, patternSkewX, patternSkewY, patternSpace);
2210 		bool mirrorX, mirrorY;
2211 		item->strokePatternFlip(mirrorX, mirrorY);
2212 		QTransform mpa;
2213 		mpa.translate(-item->lineWidth() / 2.0, -item->lineWidth() / 2.0);
2214 		mpa.translate(patternOffsetX, patternOffsetY);
2215 		mpa.rotate(patternRotation);
2216 		mpa.shear(-patternSkewX, patternSkewY);
2217 		mpa.scale(pa.scaleX, pa.scaleY);
2218 		mpa.scale(patternScaleX / 100.0 , patternScaleY / 100.0);
2219 		if (mirrorX)
2220 			mpa.scale(-1, 1);
2221 		if (mirrorY)
2222 			mpa.scale(1, -1);
2223 		patt.setAttribute("patternTransform", matrixToStr(mpa));
2224 		patt.setAttribute("xlink:href", "#" + item->strokePattern());
2225 		m_globalDefs.appendChild(patt);
2226 		stroke += " stroke:url(#" + pattID + ");";
2227 	}
2228 	else if (item->GrTypeStroke > 0)
2229 	{
2230 		QDomElement grad;
2231 		if (item->GrTypeStroke == Gradient_Radial)
2232 		{
2233 			grad = m_domDoc.createElement("radialGradient");
2234 			grad.setAttribute("r", FToStr(sqrt(pow(item->GrStrokeEndX - item->GrStrokeStartX, 2) + pow(item->GrStrokeEndY - item->GrStrokeStartY,2))));
2235 			grad.setAttribute("cx", FToStr(item->GrStrokeStartX));
2236 			grad.setAttribute("cy", FToStr(item->GrStrokeStartY));
2237 			grad.setAttribute("fx", FToStr(item->GrStrokeFocalX));
2238 			grad.setAttribute("fy", FToStr(item->GrStrokeFocalY));
2239 		}
2240 		else
2241 		{
2242 			grad = m_domDoc.createElement("linearGradient");
2243 			grad.setAttribute("x1", FToStr(item->GrStrokeStartX));
2244 			grad.setAttribute("y1", FToStr(item->GrStrokeStartY));
2245 			grad.setAttribute("x2", FToStr(item->GrStrokeEndX));
2246 			grad.setAttribute("y2", FToStr(item->GrStrokeEndY));
2247 		}
2248 		bool   isFirst = true;
2249 		double actualStop = 0.0, lastStop = 0.0;
2250 		QList<VColorStop*> cstops = item->stroke_gradient.colorStops();
2251 		for (int cst = 0; cst < item->stroke_gradient.stops(); ++cst)
2252 		{
2253 			actualStop = cstops.at(cst)->rampPoint;
2254 			if ((actualStop != lastStop) || (isFirst))
2255 			{
2256 				QDomElement itcl = m_domDoc.createElement("stop");
2257 				itcl.setAttribute("offset", FToStr(cstops.at(cst)->rampPoint*100) + "%");
2258 				if (cstops.at(cst)->name == CommonStrings::None)
2259 					itcl.setAttribute("stop-opacity", FToStr(0));
2260 				else
2261 					itcl.setAttribute("stop-opacity", FToStr(cstops.at(cst)->opacity));
2262 				itcl.setAttribute("stop-color", setColor(cstops.at(cst)->name, cstops.at(cst)->shade));
2263 				grad.appendChild(itcl);
2264 				lastStop = actualStop;
2265 				isFirst  = false;
2266 			}
2267 		}
2268 		double gradientSkew;
2269 		if (item->GrStrokeSkew == 90)
2270 			gradientSkew = 1;
2271 		else if (item->GrStrokeSkew == 180)
2272 			gradientSkew = 0;
2273 		else if (item->GrStrokeSkew == 270)
2274 			gradientSkew = -1;
2275 		else if (item->GrStrokeSkew == 390)
2276 			gradientSkew = 0;
2277 		else
2278 			gradientSkew = tan(M_PI / 180.0 * item->GrStrokeSkew);
2279 		QTransform qmatrix;
2280 		if (item->GrType == Gradient_Linear)
2281 		{
2282 			qmatrix.translate(item->GrStrokeStartX, item->GrStrokeStartY);
2283 			qmatrix.shear(-gradientSkew, 0);
2284 			qmatrix.translate(-item->GrStrokeStartX, -item->GrStrokeStartY);
2285 		}
2286 		else
2287 		{
2288 			double rotEnd = xy2Deg(item->GrStrokeEndX - item->GrStrokeStartX, item->GrStrokeEndY - item->GrStrokeStartY);
2289 			qmatrix.translate(item->GrStrokeStartX, item->GrStrokeStartY);
2290 			qmatrix.rotate(rotEnd);
2291 			qmatrix.shear(gradientSkew, 0);
2292 			qmatrix.translate(0, item->GrStrokeStartY * (1.0 - item->GrStrokeScale));
2293 			qmatrix.translate(-item->GrStrokeStartX, -item->GrStrokeStartY);
2294 			qmatrix.scale(1, item->GrStrokeScale);
2295 		}
2296 		grad.setAttribute("gradientTransform", matrixToStr(qmatrix));
2297 		grad.setAttribute("id", "Grad" + IToStr(m_gradCount));
2298 		grad.setAttribute("gradientUnits", "userSpaceOnUse");
2299 		m_globalDefs.appendChild(grad);
2300 		stroke += " stroke:url(#Grad" + IToStr(m_gradCount) + ");";
2301 		m_gradCount++;
2302 	}
2303 	else if (item->lineColor() != CommonStrings::None)
2304 	{
2305 		stroke += " stroke:" + setColor(item->lineColor(), item->lineShade()) + ";";
2306 	}
2307 	else
2308 		stroke = "stroke:none;";
2309 	return stroke;
2310 }
2311 
createClipPathElement(FPointArray * ite,QDomElement * pathElem)2312 QDomElement SVGExPlug::createClipPathElement(FPointArray *ite, QDomElement* pathElem)
2313 {
2314 	QString clipPathStr = setClipPath(ite, true);
2315 	if (clipPathStr.isEmpty())
2316 		return QDomElement();
2317 	QDomElement clipPathElem = m_domDoc.createElement("clipPath");
2318 	clipPathElem.setAttribute("id", "Clip" + IToStr(m_clipCount));
2319 	QDomElement cl = m_domDoc.createElement("path");
2320 	if (pathElem)
2321 		*pathElem = cl;
2322 	cl.setAttribute("d", clipPathStr);
2323 	clipPathElem.appendChild(cl);
2324 	m_globalDefs.appendChild(clipPathElem);
2325 	m_clipCount++;
2326 	return clipPathElem;
2327 }
2328 
setClipPath(FPointArray * ite,bool closed)2329 QString SVGExPlug::setClipPath(FPointArray *ite, bool closed)
2330 {
2331 	QString tmp;
2332 	FPoint np, np1, np2, np3, np4, firstP;
2333 	bool nPath = true;
2334 	bool first = true;
2335 
2336 	if (ite->size() <= 3)
2337 		return tmp;
2338 
2339 	for (int poi=0; poi<ite->size()-3; poi += 4)
2340 	{
2341 		if (ite->isMarker(poi))
2342 		{
2343 			nPath = true;
2344 			continue;
2345 		}
2346 		if (nPath)
2347 		{
2348 			np = ite->point(poi);
2349 			if ((!first) && (closed) && (np4 == firstP))
2350 				tmp += "Z ";
2351 			tmp += QString("M%1 %2 ").arg(np.x()).arg(np.y());
2352 			nPath = false;
2353 			first = false;
2354 			firstP = np;
2355 			np4 = np;
2356 		}
2357 		np = ite->point(poi);
2358 		np1 = ite->point(poi+1);
2359 		np2 = ite->point(poi+3);
2360 		np3 = ite->point(poi+2);
2361 		if ((np == np1) && (np2 == np3))
2362 			tmp += QString("L%1 %2 ").arg(np3.x()).arg(np3.y());
2363 		else
2364 			tmp += QString("C%1 %2 %3 %4 %5 %6 ").arg(np1.x()).arg(np1.y()).arg(np2.x()).arg(np2.y()).arg(np3.x()).arg(np3.y());
2365 		np4 = np3;
2366 	}
2367 	if (closed)
2368 		tmp += "Z";
2369 	return tmp;
2370 }
2371 
FToStr(double c)2372 QString SVGExPlug::FToStr(double c)
2373 {
2374 	QString cc;
2375 	return cc.setNum(c);
2376 }
2377 
IToStr(int c)2378 QString SVGExPlug::IToStr(int c)
2379 {
2380 	QString cc;
2381 	return cc.setNum(c);
2382 }
2383 
matrixToStr(QTransform & mat)2384 QString SVGExPlug::matrixToStr(QTransform &mat)
2385 {
2386 	QString cc("matrix(%1 %2 %3 %4 %5 %6)");
2387 	return  cc.arg(mat.m11()).arg(mat.m12()).arg(mat.m21()).arg(mat.m22()).arg(mat.dx()).arg(mat.dy());
2388 }
2389 
setColor(const QString & farbe,int shad)2390 QString SVGExPlug::setColor(const QString& farbe, int shad)
2391 {
2392 	if (farbe == CommonStrings::None)
2393 		return "#FFFFFF";
2394 	const ScColor& col = m_Doc->PageColors[farbe];
2395 	return ScColorEngine::getShadeColorProof(col, m_Doc, shad).name();
2396 }
2397 
getMultiStroke(struct SingleLine * sl,PageItem * item)2398 QString SVGExPlug::getMultiStroke(struct SingleLine *sl, PageItem *item)
2399 {
2400 	QString tmp = "fill:none; ";
2401 	tmp += "stroke:" + setColor(sl->Color, sl->Shade) + "; ";
2402 	if (item->fillTransparency() != 0)
2403 		tmp += QString(" stroke-opacity:%1; ").arg(1.0 - item->fillTransparency());
2404 	tmp += QString("stroke-width:%1; ").arg(sl->Width);
2405 	tmp += "stroke-linecap:";
2406 	switch (static_cast<Qt::PenCapStyle>(sl->LineEnd))
2407 	{
2408 		case Qt::FlatCap:
2409 			tmp += "butt;";
2410 			break;
2411 		case Qt::SquareCap:
2412 			tmp += "square;";
2413 			break;
2414 		case Qt::RoundCap:
2415 			tmp += "round;";
2416 			break;
2417 		default:
2418 			tmp += "butt;";
2419 			break;
2420 	}
2421 	tmp += " stroke-linejoin:";
2422 	switch (static_cast<Qt::PenJoinStyle>(sl->LineJoin))
2423 	{
2424 		case Qt::MiterJoin:
2425 			tmp += "miter;";
2426 			break;
2427 		case Qt::BevelJoin:
2428 			tmp += "bevel;";
2429 			break;
2430 		case Qt::RoundJoin:
2431 			tmp += "round;";
2432 			break;
2433 		default:
2434 			tmp += "miter;";
2435 			break;
2436 	}
2437 	tmp += " stroke-dasharray:";
2438 	if (static_cast<Qt::PenStyle>(sl->Dash) == Qt::SolidLine)
2439 		tmp += "none;";
2440 	else
2441 	{
2442 		QString Da = getDashString(sl->Dash, sl->Width);
2443 		if (Da.isEmpty())
2444 			tmp += "none;";
2445 		else
2446 			tmp += Da.replace(" ", ", ") + ";";
2447 	}
2448 	return tmp;
2449 }
2450 
~SVGExPlug()2451 SVGExPlug::~SVGExPlug()
2452 {
2453 }
2454