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 #include <QByteArray>
9 #include <QCursor>
10 #include <QDebug>
11 #include <QDrag>
12 #include <QFile>
13 #include <QList>
14 #include <QMessageBox>
15 #include <QMimeData>
16 #include <QRegExp>
17 #include <QStack>
18 
19 #include <cstdlib>
20 
21 #include "importps.h"
22 
23 
24 #include "commonstrings.h"
25 #include "loadsaveplugin.h"
26 #include "prefscontext.h"
27 #include "prefsfile.h"
28 #include "prefsmanager.h"
29 #include "prefstable.h"
30 #include "scclocale.h"
31 #include "scconfig.h"
32 #include "scmimedata.h"
33 #include "scpaths.h"
34 #include "scribusXml.h"
35 #include "scribuscore.h"
36 #include "scribusdoc.h"
37 #include "scribusview.h"
38 #include "sctextstream.h"
39 #include "selection.h"
40 #include "ui/customfdialog.h"
41 #include "ui/multiprogressdialog.h"
42 #include "ui/propertiespalette.h"
43 #include "ui/scmessagebox.h"
44 #include "undomanager.h"
45 #include "util.h"
46 #include "util_color.h"
47 #include "util_formats.h"
48 #include "util_math.h"
49 
50 #ifdef HAVE_PODOFO
51 	#include <podofo/podofo.h>
52 #endif
53 
54 
55 extern SCRIBUS_API ScribusQApp * ScQApp;
56 
EPSPlug(ScribusDoc * doc,int flags)57 EPSPlug::EPSPlug(ScribusDoc* doc, int flags)
58 {
59 	tmpSel = new Selection(this, false);
60 	m_Doc  = doc;
61 	progressDialog = nullptr;
62 	interactive = (flags & LoadSavePlugin::lfInteractive);
63 }
64 
import(QString fName,const TransactionSettings & trSettings,int flags,bool showProgress)65 bool EPSPlug::import(QString fName, const TransactionSettings &trSettings, int flags, bool showProgress)
66 {
67 #ifdef Q_OS_MACOS
68 	#if QT_VERSION >= 0x050300
69 		showProgress = false;
70 	#endif
71 #endif
72 
73 	bool success = false;
74 	interactive = (flags & LoadSavePlugin::lfInteractive);
75 	cancel = false;
76 	double x, y, b, h;
77 	bool ret = false;
78 	bool found = false;
79 	CustColors.clear();
80 	QFileInfo fi = QFileInfo(fName);
81 	QString ext = fi.suffix().toLower();
82 	if ( !ScCore->usingGUI() ) {
83 		interactive = false;
84 		showProgress = false;
85 	}
86 	if ( showProgress )
87 	{
88 		ScribusMainWindow* mw=(m_Doc==0) ? ScCore->primaryMainWindow() : m_Doc->scMW();
89 		progressDialog = new MultiProgressDialog( tr("Importing: %1").arg(fi.fileName()), CommonStrings::tr_Cancel, mw);
90 		QStringList barNames, barTexts;
91 		barNames << "GI";
92 		barTexts << tr("Analyzing PostScript:");
93 		QList<bool> barsNumeric;
94 		barsNumeric << false;
95 		progressDialog->addExtraProgressBars(barNames, barTexts, barsNumeric);
96 		progressDialog->setOverallTotalSteps(3);
97 		progressDialog->setOverallProgress(0);
98 		progressDialog->setProgress("GI", 0);
99 		progressDialog->show();
100 		connect(progressDialog, SIGNAL(canceled()), this, SLOT(cancelRequested()));
101 		qApp->processEvents();
102 	}
103 	else {
104 		progressDialog = nullptr;
105 	}
106 
107 /* Set default Page to size defined in Preferences */
108 	x = 0.0;
109 	y = 0.0;
110 	b = PrefsManager::instance()->appPrefs.docSetupPrefs.pageWidth;
111 	h = PrefsManager::instance()->appPrefs.docSetupPrefs.pageHeight;
112 	if (extensionIndicatesEPSorPS(ext))
113 	{
114 		QString tmp, BBox, tmp2, FarNam;
115 		ScColor cc;
116 		QFile f(fName);
117 		if (f.open(QIODevice::ReadOnly))
118 		{
119 /* Try to find Bounding Box */
120 			QDataStream ts(&f);
121 			while (!ts.atEnd())
122 			{
123 				tmp = readLinefromDataStream(ts);
124 				if (tmp.startsWith("%%BoundingBox:"))
125 				{
126 					found = true;
127 					BBox = tmp.remove("%%BoundingBox:");
128 				}
129 				if (!found)
130 				{
131 					if (tmp.startsWith("%%BoundingBox"))
132 					{
133 						found = true;
134 						BBox = tmp.remove("%%BoundingBox");
135 					}
136 				}
137 				if (tmp.startsWith("%%EndComments"))
138 					break;
139 			}
140 			f.close();
141 			if (found)
142 			{
143 				QStringList bb = BBox.split(" ", QString::SkipEmptyParts);
144 				if (bb.count() == 4)
145 				{
146 					x = ScCLocale::toDoubleC(bb[0]);
147 					y = ScCLocale::toDoubleC(bb[1]);
148 					b = ScCLocale::toDoubleC(bb[2]);
149 					h = ScCLocale::toDoubleC(bb[3]);
150 				}
151 			}
152 		}
153 		importColorsFromFile(fName, CustColors);
154 	}
155 #ifdef HAVE_PODOFO
156 	else if (extensionIndicatesPDF(ext))
157 	{
158 		try
159 		{
160 			PoDoFo::PdfError::EnableDebug( false );
161 #if (PODOFO_VERSION == 0 && PODOFO_MINOR > 6)
162 		PoDoFo::PdfError::EnableLogging( false );
163 #endif
164 #if (PODOFO_VERSION == 0 && PODOFO_MINOR == 5 && PODOFO_REVISION == 99) || PODOFO_MINOR > 5
165 			PoDoFo::PdfMemDocument doc( fName.toLocal8Bit().data() );
166 #else
167 			PoDoFo::PdfDocument doc( fName.toLocal8Bit().data() );
168 #endif
169 			PoDoFo::PdfPage *curPage = doc.GetPage(0);
170 			if (curPage != nullptr)
171 			{
172 				PoDoFo::PdfRect rect = curPage->GetMediaBox();
173 				b = rect.GetWidth() - rect.GetLeft();
174 				h = rect.GetHeight() - rect.GetBottom();
175 			}
176 		}
177 		catch(PoDoFo::PdfError& e)
178 		{
179 			qDebug("%s", "PoDoFo error while reading page size!");
180 			e.PrintErrorMsg();
181 		}
182 	}
183 #endif
184 	baseX = 0;
185 	baseY = 0;
186 	if (!interactive || (flags & LoadSavePlugin::lfInsertPage))
187 	{
188 		m_Doc->setPage(b-x, h-y, 0, 0, 0, 0, 0, 0, false, false);
189 		m_Doc->addPage(0);
190 		m_Doc->view()->addPage(0, true);
191 		baseX = 0;
192 		baseY = 0;
193 	}
194 	else
195 	{
196 		if (!m_Doc || (flags & LoadSavePlugin::lfCreateDoc))
197 		{
198 			m_Doc=ScCore->primaryMainWindow()->doFileNew(b-x, h-y, 0, 0, 0, 0, 0, 0, false, false, 0, false, 0, 1, "Custom", true);
199 			ScCore->primaryMainWindow()->HaveNewDoc();
200 			ret = true;
201 			baseX = 0;
202 			baseY = 0;
203 		}
204 	}
205 	if ((!ret) && (interactive))
206 	{
207 		baseX = m_Doc->currentPage()->xOffset();
208 		baseY = m_Doc->currentPage()->yOffset();
209 	}
210 	if ((ret) || (!interactive))
211 	{
212 		if (b-x > h-y)
213 			m_Doc->setPageOrientation(1);
214 		else
215 			m_Doc->setPageOrientation(0);
216 		m_Doc->setPageSize("Custom");
217 	}
218 	ColorList::Iterator it;
219 	for (it = CustColors.begin(); it != CustColors.end(); ++it)
220 	{
221 		if (!m_Doc->PageColors.contains(it.key()))
222 			m_Doc->PageColors.insert(it.key(), it.value());
223 	}
224 	boundingBoxRect.addRect(0, 0, b-x, h-y);
225 	Elements.clear();
226 	m_Doc->setLoading(true);
227 	m_Doc->DoDrawing = false;
228 	if (!(flags & LoadSavePlugin::lfLoadAsPattern))
229 		m_Doc->view()->updatesOn(false);
230 	m_Doc->scMW()->setScriptRunning(true);
231 	qApp->changeOverrideCursor(QCursor(Qt::WaitCursor));
232 	QString CurDirP = QDir::currentPath();
233 	QDir::setCurrent(fi.path());
234 	if (convert(fName, x, y, b, h))
235 	{
236 // 		m_Doc->m_Selection->clear();
237 		tmpSel->clear();
238 		QDir::setCurrent(CurDirP);
239 //		if ((Elements.count() > 1) && (interactive))
240 		if (Elements.count() > 1)
241 			m_Doc->groupObjectsList(Elements);
242 		m_Doc->DoDrawing = true;
243 		m_Doc->scMW()->setScriptRunning(false);
244 		m_Doc->setLoading(false);
245 		qApp->changeOverrideCursor(QCursor(Qt::ArrowCursor));
246 		if ((Elements.count() > 0) && (!ret) && (interactive))
247 		{
248 			if (flags & LoadSavePlugin::lfScripted)
249 			{
250 				bool loadF = m_Doc->isLoading();
251 				m_Doc->setLoading(false);
252 				m_Doc->changed();
253 				m_Doc->setLoading(loadF);
254 				if (!(flags & LoadSavePlugin::lfLoadAsPattern))
255 				{
256 					m_Doc->m_Selection->delaySignalsOn();
257 					for (int dre=0; dre<Elements.count(); ++dre)
258 					{
259 						m_Doc->m_Selection->addItem(Elements.at(dre), true);
260 					}
261 					m_Doc->m_Selection->delaySignalsOff();
262 					m_Doc->m_Selection->setGroupRect();
263 					m_Doc->view()->updatesOn(true);
264 				}
265 			}
266 			else
267 			{
268 				m_Doc->DragP = true;
269 				m_Doc->DraggedElem = 0;
270 				m_Doc->DragElements.clear();
271 				m_Doc->m_Selection->delaySignalsOn();
272 				for (int dre=0; dre<Elements.count(); ++dre)
273 				{
274 					tmpSel->addItem(Elements.at(dre), true);
275 				}
276 				tmpSel->setGroupRect();
277 				ScriXmlDoc *ss = new ScriXmlDoc();
278 				ScElemMimeData* md = new ScElemMimeData();
279 				md->setScribusElem(ss->WriteElem(m_Doc, tmpSel));
280 				delete ss;
281 /*#ifndef Q_OS_MACOS */
282 // see #2196
283 				m_Doc->itemSelection_DeleteItem(tmpSel);
284 /*#else
285 				qDebug() << "psimport: leaving items on page";
286 #endif*/
287 				m_Doc->view()->updatesOn(true);
288 				m_Doc->m_Selection->delaySignalsOff();
289 				// We must copy the TransationSettings object as it is owned
290 				// by handleObjectImport method afterwards
291 				TransactionSettings* transacSettings = new TransactionSettings(trSettings);
292 				m_Doc->view()->handleObjectImport(md, transacSettings);
293 				m_Doc->DragP = false;
294 				m_Doc->DraggedElem = 0;
295 				m_Doc->DragElements.clear();
296 			}
297 		}
298 		else
299 		{
300 			m_Doc->changed();
301 			m_Doc->reformPages();
302 			if (!(flags & LoadSavePlugin::lfLoadAsPattern))
303 				m_Doc->view()->updatesOn(true);
304 		}
305 		success = true;
306 	}
307 	else
308 	{
309 		QDir::setCurrent(CurDirP);
310 		m_Doc->DoDrawing = true;
311 		m_Doc->scMW()->setScriptRunning(false);
312 		m_Doc->view()->updatesOn(true);
313 		qApp->changeOverrideCursor(QCursor(Qt::ArrowCursor));
314 	}
315 	if (interactive)
316 		m_Doc->setLoading(false);
317 	//CB If we have a gui we must refresh it if we have used the progressbar
318 	if (!(flags & LoadSavePlugin::lfLoadAsPattern))
319 	{
320 		if ((showProgress) && (!interactive))
321 			m_Doc->view()->DrawNew();
322 	}
323 	return success;
324 }
325 
~EPSPlug()326 EPSPlug::~EPSPlug()
327 {
328 	if (progressDialog)
329 		delete progressDialog;
330 	delete tmpSel;
331 }
332 
333 
convert(QString fn,double x,double y,double b,double h)334 bool EPSPlug::convert(QString fn, double x, double y, double b, double h)
335 {
336 	QStringList args;
337 	QString cmd, cmd1, cmd2, cmd3, tmp, tmp2, tmp3, tmp4;
338 	// import.prolog do not cope with filenames containing blank spaces
339 	// so take care that output filename does not (win32 compatibility)
340 	QString tmpFile = getShortPathName(ScPaths::tempFileDir())+ "/ps.out";
341 	QString errFile = getShortPathName(ScPaths::tempFileDir())+ "/ps.err";
342 	QString pfad = ScPaths::instance().libDir();
343 	QString pfad2 = QDir::toNativeSeparators(pfad + "import.prolog");
344 	QFileInfo fi = QFileInfo(fn);
345 	QString ext = fi.suffix().toLower();
346 
347 	if (progressDialog) {
348 		progressDialog->setOverallProgress(1);
349 		qApp->processEvents();
350 	}
351 /*
352 // Destill the eps with ghostscript to get a clean eps file
353 	QString cleanFile = getShortPathName(ScPaths::tempFileDir())+ "/clean.eps";
354 	args.append( "-q" );
355 	args.append( "-dNOPAUSE" );
356 	args.append( "-sDEVICE=epswrite" );
357 	args.append( "-dBATCH" );
358 	args.append( "-dSAFER" );
359 	args.append( "-dDEVICEWIDTH=250000" );
360 	args.append( "-dDEVICEHEIGHT=250000" );
361 	args.append( QString("-sOutputFile=%1").arg(QDir::toNativeSeparators(cleanFile)) );
362 	args.append( QDir::toNativeSeparators(fn) );
363 	System(getShortPathName(PrefsManager::instance()->ghostscriptExecutable()), args, errFile, errFile, &cancel);
364 	args.clear();
365 */
366 	args.append( "-q" );
367 	args.append( "-dNOPAUSE" );
368 	args.append( "-dNODISPLAY" );
369 	args.append( "-dBATCH" );
370 	args.append( "-dDELAYBIND" );
371 	// Add any extra font paths being used by Scribus to gs's font search
372 	// path We have to use Scribus's prefs context, not a plugin context, to
373 	// get to the required information.
374 	PrefsContext *pc = PrefsManager::instance()->prefsFile->getContext("Fonts");
375 	PrefsTable *extraFonts = pc->getTable("ExtraFontDirs");
376 	const char sep = ScPaths::envPathSeparator;
377 	if (extraFonts->getRowCount() >= 1)
378 		cmd = QString("-sFONTPATH=%1").arg(extraFonts->get(0,0));
379 	for (int i = 1; i < extraFonts->getRowCount(); ++i)
380 		cmd += QString("%1%2").arg(sep).arg(extraFonts->get(i,0));
381 	if( !cmd.isEmpty() )
382 		args.append( cmd );
383 	// then finish building the command and call gs
384 	args.append( QString("-g%1x%2").arg(tmp2.setNum(qRound((b-x)*4))).arg(tmp3.setNum(qRound((h-y)*4))) );
385 	args.append( "-r288");
386 	args.append( "-dTextAlphaBits=4" );
387 	args.append( "-dGraphicsAlphaBits=4" );
388 	args.append( "-c" );
389 	args.append( tmp.setNum(-x) );
390 	args.append( tmp.setNum(-y) );
391 	args.append( "translate" );
392 	args.append( QString("-sTraceFile=%1").arg(QDir::toNativeSeparators(tmpFile)) );
393 	QString exportPath = m_Doc->DocName + "-" + fi.baseName();
394 	QFileInfo exportFi(exportPath);
395 	if ( !exportFi.isWritable() ) {
396 		PrefsContext* docContext = PrefsManager::instance()->prefsFile->getContext("docdirs", false);
397 		QString docDir = ".";
398 		QString prefsDocDir=PrefsManager::instance()->documentDir();
399 		if (!prefsDocDir.isEmpty())
400 			docDir = docContext->get("docsopen", prefsDocDir);
401 		else
402 			docDir = docContext->get("docsopen", ".");
403 		exportFi.setFile(docDir + "/" + exportFi.baseName());
404 	}
405 	//qDebug() << QString("using export path %1").arg(exportFi.absFilePath());
406 	args.append( QString("-sExportFiles=%1").arg(QDir::toNativeSeparators(exportFi.absoluteFilePath())) );
407 	args.append( pfad2 );
408 	args.append( QDir::toNativeSeparators(fn) );
409 	args.append( "-c" );
410 	args.append( "flush" );
411 	args.append( "cfile" );
412 	args.append( "closefile" );
413 	args.append( "quit" );
414 	QByteArray finalCmd = args.join(" ").toLocal8Bit();
415 	int ret = System(getShortPathName(PrefsManager::instance()->ghostscriptExecutable()), args, errFile, errFile, &cancel);
416 	if (ret != 0 && !cancel)
417 	{
418 		qDebug("PostScript import failed when calling gs as: \n%s\n", finalCmd.data());
419 		qDebug("%s", "Ghostscript diagnostics:\n");
420 		QFile diag(errFile);
421 		if (diag.open(QIODevice::ReadOnly) && !diag.atEnd() ) {
422 			char buf[121];
423 			while (diag.readLine(buf, 120) > 0) {
424 				qDebug("\t%s", buf);
425 			}
426 			diag.close();
427 		}
428 		else {
429 			qDebug("%s", "-- no output --");
430 		}
431 		if (progressDialog)
432 			progressDialog->close();
433 		QString mess = tr("Importing File:\n%1\nfailed!").arg(fn);
434 		ScMessageBox::critical(0, tr("Fatal Error"), mess);
435 		return false;
436 	}
437 	if(progressDialog && !cancel) {
438 		progressDialog->setOverallProgress(2);
439 		progressDialog->setLabel("GI", tr("Generating Items"));
440 		qApp->processEvents();
441 	}
442 	if (!cancel) {
443 		parseOutput(tmpFile, extensionIndicatesEPSorPS(ext));
444 	}
445 	QFile::remove(tmpFile);
446 //	QFile::remove(cleanFile);
447 	if (progressDialog)
448 		progressDialog->close();
449 	return true;
450 }
451 
parseOutput(QString fn,bool eps)452 void EPSPlug::parseOutput(QString fn, bool eps)
453 {
454 	QString tmp, token, params, lasttoken, lastPath, currPath;
455 	int z, lcap, ljoin, dc, pagecount;
456 	int failedImages = 0;
457 	double dcp;
458 	bool fillRuleEvenOdd = true;
459 	PageItem* ite;
460 	QStack<PageItem*> groupStack;
461 	QStack< QList<PageItem*> > groupStackP;
462 	QStack<int>  gsStack;
463 	QStack<uint> gsStackMarks;
464 	QFile f(fn);
465 	lasttoken = "";
466 	pagecount = 1;
467 	if (f.open(QIODevice::ReadOnly))
468 	{
469 		int fProgress = 0;
470 		int fSize = (int) f.size();
471 		if (progressDialog) {
472 			progressDialog->setTotalSteps("GI", fSize);
473 			qApp->processEvents();
474 		}
475 		lastPath = "";
476 		currPath = "";
477 		LineW = 0;
478 		Opacity = 1;
479 		CurrColor = CommonStrings::None;
480 		JoinStyle = Qt::MiterJoin;
481 		CapStyle = Qt::FlatCap;
482 		DashPattern.clear();
483 		ScTextStream ts(&f);
484 		int line_cnt = 0;
485 		while (!ts.atEnd() && !cancel)
486 		{
487 			tmp = "";
488 			tmp = ts.readLine();
489 			if (progressDialog && (++line_cnt % 100 == 0)) {
490 				int fPos = f.pos();
491 				int progress = static_cast<int>(ceil(fPos / (double) fSize * 100));
492 				if (progress > fProgress)
493 				{
494 					progressDialog->setProgress("GI", fPos);
495 					qApp->processEvents();
496 					fProgress = progress;
497 				}
498 			}
499 			token = tmp.section(' ', 0, 0);
500 			params = tmp.section(' ', 1, -1, QString::SectionIncludeTrailingSep);
501 			if (lasttoken == "sp"  && !eps && token != "sp" ) //av: messes up anyway: && (!interactive))
502 			{
503 				m_Doc->addPage(pagecount);
504 				m_Doc->view()->addPage(pagecount, true);
505 				pagecount++;
506 			}
507 			if (token == "n")
508 			{
509 				Coords.resize(0);
510 				FirstM = true;
511 				WasM = false;
512 				ClosedPath = false;
513 			}
514 			else if (token == "m")
515 				WasM = true;
516 			else if (token == "c")
517 			{
518 				Curve(&Coords, params);
519 				currPath += params;
520 			}
521 			else if (token == "l")
522 			{
523 				LineTo(&Coords, params);
524 				currPath += params;
525 			}
526 			else if (token == "fill-winding")
527 			{
528 				fillRuleEvenOdd = false;
529 			}
530 			else if (token == "fill-evenodd")
531 			{
532 				fillRuleEvenOdd = true;
533 			}
534 			else if (token == "f")
535 			{
536 				//TODO: pattern -> Imageframe + Clip
537 				if (Coords.size() != 0)
538 				{
539 					if ((Elements.count() != 0) && (lastPath == currPath))
540 					{
541 						ite = Elements.last();
542 						ite->setFillColor(CurrColor);
543 						ite->setFillTransparency(1.0 - Opacity);
544 						lastPath = "";
545 					}
546 					else
547 					{
548 						if (ClosedPath)
549 							z = m_Doc->itemAdd(PageItem::Polygon, PageItem::Unspecified, baseX, baseY, 10, 10, LineW, CurrColor, CommonStrings::None);
550 						else
551 							z = m_Doc->itemAdd(PageItem::PolyLine, PageItem::Unspecified, baseX, baseY, 10, 10, LineW, CurrColor, CommonStrings::None);
552 						ite = m_Doc->Items->at(z);
553 						ite->PoLine = Coords.copy();  //FIXME: try to avoid copy if FPointArray when properly shared
554 						ite->PoLine.translate(m_Doc->currentPage()->xOffset(), m_Doc->currentPage()->yOffset());
555 						ite->ClipEdited = true;
556 						ite->FrameType = 3;
557 						ite->fillRule = (fillRuleEvenOdd);
558 						FPoint wh = getMaxClipF(&ite->PoLine);
559 						ite->setWidthHeight(wh.x(),wh.y());
560 						ite->Clip = FlattenPath(ite->PoLine, ite->Segments);
561 						ite->setFillTransparency(1.0 - Opacity);
562 						ite->setTextFlowMode(PageItem::TextFlowDisabled);
563 						m_Doc->adjustItemSize(ite);
564 						if (ite->itemType() == PageItem::Polygon)
565 							ite->ContourLine = ite->PoLine.copy();
566 						if ((groupStack.count() != 0) && (groupStackP.count() != 0))
567 							groupStackP.top().append(ite);
568 						Elements.append(ite);
569 						lastPath = currPath;
570 					}
571 					currPath = "";
572 				}
573 			}
574 			else if (token == "s")
575 			{
576 				if (Coords.size() != 0)
577 				{
578 				//	LineW = qMax(LineW, 0.01); // Set Linewidth to be a least 0.01 pts, a Stroke without a Linewidth makes no sense
579 					if ((Elements.count() != 0) && (lastPath == currPath))
580 					{
581 						ite = Elements.last();
582 						ite->setLineColor(CurrColor);
583 						ite->setLineWidth(LineW);
584 						ite->PLineEnd = CapStyle;
585 						ite->PLineJoin = JoinStyle;
586 						ite->setLineTransparency(1.0 - Opacity);
587 						ite->DashOffset = DashOffset;
588 						ite->DashValues = DashPattern;
589 					}
590 					else
591 					{
592 						if (ClosedPath)
593 							z = m_Doc->itemAdd(PageItem::Polygon, PageItem::Unspecified, baseX, baseY, 10, 10, LineW, CommonStrings::None, CurrColor);
594 						else
595 							z = m_Doc->itemAdd(PageItem::PolyLine, PageItem::Unspecified, baseX, baseY, 10, 10, LineW, CommonStrings::None, CurrColor);
596 						ite = m_Doc->Items->at(z);
597 						ite->PoLine = Coords.copy(); //FIXME: try to avoid copy when FPointArray is properly shared
598 						ite->PoLine.translate(m_Doc->currentPage()->xOffset(), m_Doc->currentPage()->yOffset());
599 						ite->ClipEdited = true;
600 						ite->FrameType = 3;
601 						ite->PLineEnd = CapStyle;
602 						ite->PLineJoin = JoinStyle;
603 						ite->DashOffset = DashOffset;
604 						ite->DashValues = DashPattern;
605 						FPoint wh = getMaxClipF(&ite->PoLine);
606 						ite->setWidthHeight(wh.x(), wh.y());
607 						ite->Clip = FlattenPath(ite->PoLine, ite->Segments);
608 						ite->setLineTransparency(1.0 - Opacity);
609 						m_Doc->adjustItemSize(ite);
610 						if (ite->itemType() == PageItem::Polygon)
611 							ite->ContourLine = ite->PoLine.copy();
612 						ite->setLineWidth(LineW);
613 						ite->setTextFlowMode(PageItem::TextFlowDisabled);
614 						if ((groupStack.count() != 0) && (groupStackP.count() != 0))
615 							groupStackP.top().append(ite);
616 						Elements.append(ite);
617 					}
618 					lastPath = "";
619 					currPath = "";
620 				}
621 			}
622 			else if (token == "co")
623 				CurrColor = parseColor(params, eps);
624 			else if (token == "corgb")
625 				CurrColor = parseColor(params, eps, colorModelRGB);
626 			else if (token == "ci")
627 			{
628 				if (Coords.size() != 0)
629 				{
630 					QPainterPath tmpPath = Coords.toQPainterPath(true);
631 					tmpPath = boundingBoxRect.intersected(tmpPath);
632 					if ((tmpPath.boundingRect().width() != 0) && (tmpPath.boundingRect().height() != 0))
633 					{
634 						clipCoords.fromQPainterPath(tmpPath);
635 						z = m_Doc->itemAdd(PageItem::Group, PageItem::Rectangle, baseX, baseY, 10, 10, 0, CommonStrings::None, CommonStrings::None);
636 						ite = m_Doc->Items->at(z);
637 						ite->PoLine = clipCoords.copy();  //FIXME: try to avoid copy if FPointArray when properly shared
638 						ite->PoLine.translate(m_Doc->currentPage()->xOffset(), m_Doc->currentPage()->yOffset());
639 						ite->ClipEdited = true;
640 						ite->FrameType = 3;
641 						FPoint wh = getMaxClipF(&ite->PoLine);
642 						ite->setWidthHeight(wh.x(),wh.y());
643 						ite->Clip = FlattenPath(ite->PoLine, ite->Segments);
644 						m_Doc->adjustItemSize(ite, true);
645 						ite->ContourLine = ite->PoLine.copy();
646 						ite->setItemName( tr("Group%1").arg(m_Doc->GroupCounter));
647 						ite->setTextFlowMode(PageItem::TextFlowDisabled);
648 						Elements.append(ite);
649 						if ((groupStack.count() != 0) && (groupStackP.count() != 0))
650 							groupStackP.top().append(ite);
651 						groupStack.push(ite);
652 						QList<PageItem*> gElements;
653 						groupStackP.push(gElements);
654 						gsStackMarks.push(gsStack.count());
655 						m_Doc->GroupCounter++;
656 					}
657 				}
658 				Coords   = FPointArray(0);
659 				lastPath = "";
660 				currPath = "";
661 			}
662 			else if (token == "gs")
663 			{
664 				gsStack.push(1);
665 			}
666 			else if (token == "gr")
667 			{
668 				// #6834 : self defense against incorrectly balanced save/restore
669 				if (gsStack.count() > 0)
670 					gsStack.pop();
671 				if ((groupStack.count() != 0) && (groupStackP.count() != 0))
672 				{
673 					if (gsStack.count() < static_cast<int>(gsStackMarks.top()))
674 					{
675 						PageItem *ite = groupStack.pop();
676 						QList<PageItem*> gList = groupStackP.pop();
677 						for (int d = 0; d < gList.count(); d++)
678 						{
679 							Elements.removeAll(gList.at(d));
680 						}
681 						m_Doc->groupObjectsToItem(ite, gList);
682 						gsStackMarks.pop();
683 					}
684 				}
685 			}
686 			else if (token == "w")
687 			{
688 				ScTextStream Lw(&params, QIODevice::ReadOnly);
689 				Lw >> LineW;
690 			}
691 			else if (token == "ld")
692 			{
693 				ScTextStream Lw(&params, QIODevice::ReadOnly);
694 				Lw >> dc;
695 				Lw >> DashOffset;
696 				DashPattern.clear();
697 				if (dc != 0)
698 				{
699 					for (int dcc = 0; dcc < dc; ++dcc)
700 					{
701 						Lw >> dcp;
702 						DashPattern.append(dcp);
703 					}
704 				}
705 			}
706 			else if (token == "lc")
707 			{
708 				ScTextStream Lw(&params, QIODevice::ReadOnly);
709 				Lw >> lcap;
710 				switch (lcap)
711 				{
712 					case 0:
713 						CapStyle = Qt::FlatCap;
714 						break;
715 					case 1:
716 						CapStyle = Qt::RoundCap;
717 						break;
718 					case 2:
719 						CapStyle = Qt::SquareCap;
720 						break;
721 					default:
722 						CapStyle = Qt::FlatCap;
723 						break;
724 				}
725 			}
726 			else if (token == "lj")
727 			{
728 				ScTextStream Lw(&params, QIODevice::ReadOnly);
729 				Lw >> ljoin;
730 				switch (ljoin)
731 				{
732 					case 0:
733 						JoinStyle = Qt::MiterJoin;
734 						break;
735 					case 1:
736 						JoinStyle = Qt::RoundJoin;
737 						break;
738 					case 2:
739 						JoinStyle = Qt::BevelJoin;
740 						break;
741 					default:
742 						JoinStyle = Qt::MiterJoin;
743 						break;
744 				}
745 			}
746 			else if (token == "cp") {
747 				ClosedPath = true;
748 			}
749 			else if (token == "im") {
750 				if ( !Image(params) )
751 					++failedImages;
752 			}
753 			lasttoken = token;
754 		}
755 		f.close();
756 		if (groupStack.count() != 0)
757 		{
758 			while (!groupStack.isEmpty())
759 			{
760 				PageItem *ite = groupStack.pop();
761 				QList<PageItem*> gList = groupStackP.pop();
762 				for (int d = 0; d < gList.count(); d++)
763 				{
764 					Elements.removeAll(gList.at(d));
765 				}
766 				m_Doc->groupObjectsToItem(ite, gList);
767 			}
768 		}
769 	}
770 	if (failedImages > 0)
771 	{
772 		QString mess = tr("Converting of %1 images failed!").arg(failedImages);
773 		ScMessageBox::critical(0, tr("Error"), mess);
774 	}
775 }
776 
Image(QString vals)777 bool EPSPlug::Image(QString vals)
778 {
779 	double x, y, w, h, angle;
780 	int horpix, verpix;
781 	QString filename, device;
782 	ScTextStream Code(&vals, QIODevice::ReadOnly);
783 	Code >> x;
784 	Code >> y;
785 	Code >> w;
786 	Code >> h;
787 	Code >> angle;
788 	Code >> horpix;
789 	Code >> verpix;
790 	Code >> device;
791 	filename = Code.readAll().trimmed();
792 	if (device.startsWith("psd")) {
793 		filename = filename.mid(0, filename.length()-3) + "psd";
794 	}
795 
796 	qDebug("%s", QString("import %7 image %1: %2x%3 @ (%4,%5) °%6").arg(filename).arg(w).arg(h).arg(x).arg(y).arg(angle).arg(device).toLocal8Bit().data());
797 	QString rawfile = filename.mid(0, filename.length()-3) + "dat";
798 	QStringList args;
799 	args.append( "-q" );
800 	args.append( "-dNOPAUSE" );
801 	args.append( QString("-sDEVICE=%1").arg(device) );
802 	args.append( "-dBATCH" );
803 	args.append( QString("-g%1x%2").arg(horpix).arg(verpix) );
804 	args.append( QString("-sOutputFile=%1").arg(QDir::toNativeSeparators(filename)) );
805 	args.append( QDir::toNativeSeparators(rawfile) );
806 	args.append( "-c" );
807 	args.append( "showpage" );
808 	args.append( "quit" );
809 	QByteArray finalCmd = args.join(" ").toLocal8Bit();
810 	int ret = System(getShortPathName(PrefsManager::instance()->ghostscriptExecutable()), args);
811 	if (ret != 0)
812 	{
813 		qDebug("PostScript image conversion failed when calling gs as: \n%s\n", finalCmd.data());
814 		qDebug("Ghostscript diagnostics: %d\n", ret);
815 		QFile diag(filename);
816 		if (diag.open(QIODevice::ReadOnly)) {
817 			char buf[121];
818 			long int len;
819 			bool gs_error = false;
820 			do {
821 				len = diag.readLine(buf, 120);
822 				gs_error |= (strstr(buf,"Error")==nullptr);
823 				if (gs_error)
824 					qDebug("\t%s", buf);
825 			}
826 			while (len > 0);
827 			diag.close();
828 			}
829 		else {
830 			qDebug("%s", "-- no output --");
831 		}
832 		qDebug("%s", "Failed file was:\n");
833 		QFile dat(rawfile);
834 		if (dat.open(QIODevice::ReadOnly)) {
835 			char buf[121];
836 			long int len;
837 			do {
838 				len = dat.readLine(buf, 120);
839 				qDebug("\t%s", buf);
840 			}
841 			while ( len > 0 && !(strstr(buf, "image")==nullptr) );
842 			dat.close();
843 		}
844 		else {
845 			qDebug("%s", "-- empty --");
846 		}
847 	}
848 	QFile::remove(rawfile);
849 	int z = m_Doc->itemAdd(PageItem::ImageFrame, PageItem::Unspecified, m_Doc->currentPage()->xOffset(), m_Doc->currentPage()->yOffset(), w, h, LineW, CommonStrings::None, CurrColor);
850 	PageItem * ite = m_Doc->Items->at(z);
851 	ite->setXYPos(m_Doc->currentPage()->xOffset() + x, m_Doc->currentPage()->yOffset() + y);
852 	ite->setWidthHeight(w, h);
853 	ite->clearContents();
854 /*	FPoint a(x, y);
855 	FPoint b(x+w, y);
856 	FPoint c(x+w, y-h);
857 	FPoint d(x, y-h);
858 	ite->PoLine.resize(0);
859 	ite->PoLine.addQuadPoint(a, a, b, b);
860 	ite->PoLine.addQuadPoint(b, b, c, c);
861 	ite->PoLine.addQuadPoint(c, c, d, d);
862 	ite->PoLine.addQuadPoint(d, d, a, a);
863 	ite->PoLine.translate(m_Doc->currentPage->xOffset() - x, m_Doc->currentPage->yOffset() - y);
864 	ite->ClipEdited = true;
865 	ite->Clip = FlattenPath(ite->PoLine, ite->Segments);
866 */
867 	m_Doc->loadPict(filename, ite, -1);
868 	ite->setRotation(angle);
869 	ite->setImageScalingMode(false, true); // fit to frame, keep ratio
870 //	m_Doc->view()->adjustItemSize(ite);
871 	Elements.append(ite);
872 	return ret == 0;
873 }
874 
875 
LineTo(FPointArray * i,QString vals)876 void EPSPlug::LineTo(FPointArray *i, QString vals)
877 {
878 	if (vals.isEmpty())
879 		return;
880 	double x1, x2, y1, y2;
881 	x1 = ScCLocale::toDoubleC(vals.section(' ', 0, 0, QString::SectionSkipEmpty));
882 	y1 = ScCLocale::toDoubleC(vals.section(' ', 1, 1, QString::SectionSkipEmpty));
883 	x2 = ScCLocale::toDoubleC(vals.section(' ', 2, 2, QString::SectionSkipEmpty));
884 	y2 = ScCLocale::toDoubleC(vals.section(' ', 3, 3, QString::SectionSkipEmpty));
885 	if ((!FirstM) && (WasM))
886 		i->setMarker();
887 	FirstM = false;
888 	WasM = false;
889 	i->addPoint(FPoint(x1, y1));
890 	i->addPoint(FPoint(x1, y1));
891 	i->addPoint(FPoint(x2, y2));
892 	i->addPoint(FPoint(x2, y2));
893 }
894 
Curve(FPointArray * i,QString vals)895 void EPSPlug::Curve(FPointArray *i, QString vals)
896 {
897 	if (vals.isEmpty())
898 		return;
899 	double x1, x2, y1, y2, x3, y3, x4, y4;
900 	x1 = ScCLocale::toDoubleC(vals.section(' ', 0, 0, QString::SectionSkipEmpty));
901 	y1 = ScCLocale::toDoubleC(vals.section(' ', 1, 1, QString::SectionSkipEmpty));
902 	x2 = ScCLocale::toDoubleC(vals.section(' ', 2, 2, QString::SectionSkipEmpty));
903 	y2 = ScCLocale::toDoubleC(vals.section(' ', 3, 3, QString::SectionSkipEmpty));
904 	x3 = ScCLocale::toDoubleC(vals.section(' ', 4, 4, QString::SectionSkipEmpty));
905 	y3 = ScCLocale::toDoubleC(vals.section(' ', 5, 5, QString::SectionSkipEmpty));
906 	x4 = ScCLocale::toDoubleC(vals.section(' ', 6, 6, QString::SectionSkipEmpty));
907 	y4 = ScCLocale::toDoubleC(vals.section(' ', 7, 7, QString::SectionSkipEmpty));
908 	if ((!FirstM) && (WasM))
909 		i->setMarker();
910 	FirstM = false;
911 	WasM = false;
912 	i->addPoint(FPoint(x1, y1));
913 	i->addPoint(FPoint(x2, y2));
914 	i->addPoint(FPoint(x4, y4));
915 	i->addPoint(FPoint(x3, y3));
916 }
917 
parseColor(QString vals,bool eps,colorModel model)918 QString EPSPlug::parseColor(QString vals, bool eps, colorModel model)
919 {
920 	QString ret = CommonStrings::None;
921 	if (vals.isEmpty())
922 		return ret;
923 	double c, m, y, k, r, g, b;
924 	ScColor tmp;
925 	ScTextStream Code(&vals, QIODevice::ReadOnly);
926 	if (model == colorModelRGB)
927 	{
928 		Code >> r;
929 		Code >> g;
930 		Code >> b;
931 		Code >> Opacity;
932 // Why adding 0.5 here color values range from 0 to 255 not 1 to 256 ??
933 /*		int Rc = static_cast<int>(r * 255 + 0.5);
934 		int Gc = static_cast<int>(g * 255 + 0.5);
935 		int Bc = static_cast<int>(b * 255 + 0.5); */
936 		int Rc = qRound(r * 255);
937 		int Gc = qRound(g * 255);
938 		int Bc = qRound(b * 255);
939 		tmp.setColorRGB(Rc, Gc, Bc);
940 	}
941 	else
942 	{
943 		Code >> c;
944 		Code >> m;
945 		Code >> y;
946 		Code >> k;
947 		Code >> Opacity;
948 		int Cc = qRound(c * 255);
949 		int Mc = qRound(m * 255);
950 		int Yc = qRound(y * 255);
951 		int Kc = qRound(k * 255);
952 		tmp.setColor(Cc, Mc, Yc, Kc);
953 	}
954 	tmp.setSpotColor(false);
955 	tmp.setRegistrationColor(false);
956 	QString namPrefix = "FromEPS";
957 	if (!eps)
958 		namPrefix = "FromPS";
959 	QString fNam = m_Doc->PageColors.tryAddColor(namPrefix+tmp.name(), tmp);
960 	ret = fNam;
961 	return ret;
962 }
963