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                           util.cpp  -  description
9                              -------------------
10     begin                : Fri Sep 14 2001
11     copyright            : (C) 2001 by Franz Schmid
12     email                : Franz.Schmid@altmuehlnet.de
13  ***************************************************************************/
14 
15 /***************************************************************************
16  *                                                                         *
17  *   This program is free software; you can redistribute it and/or modify  *
18  *   it under the terms of the GNU General Public License as published by  *
19  *   the Free Software Foundation; either version 2 of the License, or     *
20  *   (at your option) any later version.                                   *
21  *                                                                         *
22  ***************************************************************************/
23 
24 #include <algorithm>
25 #include "util.h"
26 #include <zlib.h>
27 #include <qglobal.h>
28 #include <QApplication>
29 #include <QCryptographicHash>
30 #include <QDomElement>
31 #include <QMessageBox>
32 #include <QProcess>
33 #include <QSignalBlocker>
34 
35 #include "pageitem.h"
36 #include "pageitem_table.h"
37 #include "scribusview.h"
38 #include "scribusdoc.h"
39 #include "scpainter.h"
40 #include "ui/scmessagebox.h"
41 
42 #include <csignal>
43 
44 #if !defined(_WIN32) && !defined(Q_OS_MAC)
45 #include <execinfo.h>
46 #include <cxxabi.h>
47 #endif
48 #if defined(_WIN32)
49 #include <windows.h>
50 #endif
51 
52 using namespace std;
53 
cleanupLang(const QString & lang)54 QString cleanupLang(const QString& lang)
55 {
56 	int dotIndex = lang.indexOf(QChar('.'));
57 	if (dotIndex < 0)
58 		return lang;
59 
60 	return lang.left(dotIndex);
61 }
62 
System(const QString & exename,const QStringList & args,const QString & fileStdErr,const QString & fileStdOut,const bool * cancel)63 int System(const QString& exename, const QStringList & args, const QString& fileStdErr, const QString& fileStdOut, const bool* cancel)
64 {
65 	QProcess proc;
66 	if (!fileStdOut.isEmpty())
67 		proc.setStandardOutputFile(fileStdOut);
68 	if (!fileStdErr.isEmpty())
69 		proc.setStandardErrorFile(fileStdErr);
70 	proc.start(exename, args);
71 	if (proc.waitForStarted(15000))
72 	{
73 		while (!proc.waitForFinished(15000))
74 		{
75 			qApp->processEvents();
76 			if (cancel && (*cancel))
77 			{
78 				proc.kill();
79 				break;
80 			}
81 		}
82 	}
83 	if (cancel && (*cancel))
84 		return -1;
85 	return proc.exitCode();
86 }
87 
88 // On Windows, return short path name, else return longPath;
getShortPathName(const QString & longPath)89 QString getShortPathName(const QString & longPath)
90 {
91 	QString shortPath(longPath);
92 #if defined _WIN32
93 	QFileInfo fInfo(longPath);
94 	if (fInfo.exists())
95 	{
96 		WCHAR shortName[MAX_PATH + 1];
97 		// An error should not be blocking as ERROR_INVALID_PARAMETER can simply mean
98 		// that volume does not support 8.3 filenames, so return longPath in this case
99 		QString nativePath = QDir::toNativeSeparators(longPath);
100 		int ret = GetShortPathNameW((LPCWSTR) nativePath.utf16(), shortName, MAX_PATH);
101 		if (ret != ERROR_INVALID_PARAMETER && ret < MAX_PATH)
102 			shortPath = QString::fromUtf16((const ushort*) shortName);
103 	}
104 #endif
105 	return shortPath;
106 }
107 
108 // On Windows, return short path name, else return longPath;
getLongPathName(const QString & shortPath)109 QString getLongPathName(const QString & shortPath)
110 {
111 	QString longPath(shortPath);
112 #if defined _WIN32
113 	QFileInfo fInfo(longPath);
114 	if (fInfo.exists())
115 	{
116 		WCHAR longName[MAX_PATH + 1];
117 		// An error should not be blocking as ERROR_INVALID_PARAMETER can simply mean
118 		// that volume does not support long filenames, so return shortPath in this case
119 		QString nativePath = QDir::toNativeSeparators(shortPath);
120 		int ret = GetLongPathNameW((LPCWSTR) nativePath.utf16(), longName, MAX_PATH);
121 		if (ret != ERROR_INVALID_PARAMETER && ret < MAX_PATH)
122 			longPath = QString::fromUtf16((const ushort*) longName);
123 	}
124 #endif
125 	return longPath;
126 }
127 
128 // Legacy implementation of LoadText with incorrect
129 // handling of unicode data. This should be retired.
130 // Use loadRawText instead.
131 // FIXME XXX
132 //
loadText(const QString & filename,QString * buffer)133 bool loadText(const QString& filename, QString *buffer)
134 {
135 	QFile f(filename);
136 	QFileInfo fi(f);
137 	if (!fi.exists())
138 		return false;
139 	QByteArray bb(f.size(), ' ');
140 	if (!f.open(QIODevice::ReadOnly))
141 		return false;
142 	f.read(bb.data(), f.size());
143 	f.close();
144 	for (int i = 0; i < bb.size(); ++i)
145 		*buffer += QChar(bb[i]);
146 	/*
147 		int len = bb.size();
148 		int oldLen = Buffer->length();
149 		Buffer->setLength( oldLen + len + 1);
150 		// digged into Qt 3.3 sources to find that. Might break in Qt 4 -- AV
151 		unsigned short * ucsString = const_cast<unsigned short *>(Buffer->ucs2()) + oldLen;
152 		char * data = bb.data();
153 		for (uint posi = 0; posi < len; ++posi)
154 		*ucsString++ = *data++;
155 		*ucsString = 0;
156 		*/
157 	return true;
158 }
159 
loadRawText(const QString & filename,QByteArray & buf)160 bool loadRawText(const QString & filename, QByteArray & buf)
161 {
162 	bool ret = false;
163 	QFile f(filename);
164 	QFileInfo fi(f);
165 	if (fi.exists())
166 	{
167 		// Allocating one more bytes for null terminator is unneeded with Qt4
168 		// as QByteArray ensure null termination automatically
169 		// Triggers also QDomDocument parsing errors
170 		QByteArray tempBuf(f.size() /*+ 1*/, ' ');
171 		if (f.open(QIODevice::ReadOnly))
172 		{
173 			qint64 bytesRead = f.read(tempBuf.data(), f.size());
174 			ret = bytesRead == f.size();
175 			if (ret)
176 				buf = tempBuf; // sharing makes this efficient
177 		}
178 	}
179 	if (f.isOpen())
180 		f.close();
181 	return ret;
182 }
183 
loadRawBytes(const QString & filename,QByteArray & buf)184 bool loadRawBytes(const QString & filename, QByteArray & buf)
185 {
186 	bool ret = false;
187 	QFile f(filename);
188 	QFileInfo fi(f);
189 	if (fi.exists())
190 	{
191 		QByteArray tempBuf(f.size(), ' ');
192 		if (f.open(QIODevice::ReadOnly))
193 		{
194 			qint64 bytesRead = f.read(tempBuf.data(), f.size());
195 			ret = bytesRead == f.size();
196 			if (ret)
197 				buf = tempBuf; // sharing makes this efficient
198 		}
199 	}
200 	if (f.isOpen())
201 		f.close();
202 	return ret;
203 }
204 
loadRawBytes(const QString & filename,QByteArray & buf,int maxLength)205 bool loadRawBytes(const QString & filename, QByteArray & buf, int maxLength)
206 {
207 	if (maxLength < 0)
208 		return false;
209 	bool ret = false;
210 
211 	QFile f(filename);
212 	QFileInfo fi(f);
213 	if (fi.exists())
214 	{
215 		QByteArray tempBuf(maxLength, ' ');
216 		if (f.open(QIODevice::ReadOnly))
217 		{
218 			qint64 bytesRead = f.read(tempBuf.data(), (qint64) maxLength);
219 			ret = (bytesRead > 0);
220 			if (ret)
221 			{
222 				tempBuf.resize((int) bytesRead);
223 				buf = tempBuf; // sharing makes this efficient
224 			}
225 		}
226 	}
227 	if (f.isOpen())
228 		f.close();
229 	return ret;
230 }
231 
CompressStr(QString * in)232 QString CompressStr(QString *in)
233 {
234 	QString out;
235 	QByteArray bb(in->length(), ' ');
236 	if (bb.size() == in->length())
237 	{
238 		for (int i = 0; i < in->length(); ++i)
239 		{
240 			// bb.insert(ax, in->at(ax)); JG monstruously inefficient due to frequent memory reallocation
241 			bb[i] = in->at(i).cell();
242 			assert(in->at(i).row() == 0);
243 		}
244 		uLong exlen = (uLong)(bb.size() * 0.001 + 16) + bb.size();
245 		QByteArray bc(exlen, ' ');
246 		if( bc.size() == static_cast<qint32>(exlen) )
247 		{
248 			int errcode = compress2((Byte *)bc.data(), &exlen, (Byte *)bb.data(), uLong(bb.size()), 9);
249 			if (errcode != Z_OK)
250 			{
251 				qDebug("compress2 failed with code %i", errcode);
252 				out = *in;
253 			}
254 			else {
255 				for (uint cl = 0; cl < exlen; ++cl)
256 					out += QChar(bc[cl]);
257 			}
258 		}
259 		else
260 		{
261 			qDebug("insufficient memory to allocate %i bytes", in->length());
262 			out = *in;
263 		}
264 	}
265 	else
266 	{
267 		qDebug("insufficient memory to allocate %i bytes", in->length());
268 		out = *in;
269 	}
270 	return out;
271 }
272 
CompressArray(const QByteArray & in)273 QByteArray CompressArray(const QByteArray& in)
274 {
275 	QByteArray out;
276 	uLong exlen = uint(in.size() * 0.001 + 16) + in.size();
277 	QByteArray temp(exlen, ' ');
278 	int errcode = compress2((Byte *)temp.data(), &exlen, (Byte *)in.data(), uLong(in.size()), 9);
279 	if (errcode == Z_OK)
280 	{
281 		temp.resize(exlen);
282 		out = temp;
283 	}
284 	else
285 		qDebug("compress2 failed with code %i", errcode);
286 	return out;
287 }
288 
toAscii85(quint32 value,bool & allZero)289 char *toAscii85( quint32 value, bool& allZero )
290 {
291 	int digit;
292 	static char asciiVal[6];
293 	allZero = true;
294 	for (int i = 0; i < 5; ++i)
295 	{
296 		digit = value % 85;
297 		if (digit != 0)
298 			allZero = false;
299 		asciiVal[4-i] = digit + 33;
300 		value = (value - digit) / 85;
301 	}
302 	asciiVal[5] = 0;
303 	return asciiVal;
304 }
305 
toHex(uchar u)306 char *toHex( uchar u )
307 {
308 	static char hexVal[3];
309 	int i = 1;
310 	while ( i >= 0 )
311 	{
312 		ushort hex = (u & 0x000f);
313 		if ( hex < 0x0a )
314 			hexVal[i] = '0'+hex;
315 		else
316 			hexVal[i] = 'A'+(hex-0x0a);
317 		u = u >> 4;
318 		i--;
319 	}
320 	hexVal[2] = '\0';
321 	return hexVal;
322 }
323 
String2Hex(QString * in,bool lang)324 QString String2Hex(QString *in, bool lang)
325 {
326 	int i = 0;
327 	QString out;
328 	for (int j = 0; j < in->length(); ++j)
329 	{
330 		// Qt4 .cell() added ???
331 		out += toHex(QChar(in->at(j)).cell());
332 		++i;
333 		if ((i > 40) && (lang))
334 		{
335 			out += '\n';
336 			i=0;
337 		}
338 	}
339 	return out;
340 }
341 
Path2Relative(const QString & Path,const QString & baseDir)342 QString Path2Relative(const QString& Path, const QString& baseDir)
343 {
344 	QDir d(baseDir);
345 	return d.relativeFilePath(Path);
346 }
347 
Relative2Path(const QString & File,const QString & baseDir)348 QString Relative2Path(const QString& File, const QString& baseDir)
349 {
350 	QString absPath;
351 	QFileInfo fi(File);
352 	if (File.isEmpty())
353 		absPath = File;
354 	else if (fi.isRelative())
355 	{
356 		QDir d(baseDir);
357 		absPath = d.absoluteFilePath(File);
358 		absPath = QDir::cleanPath(absPath);
359 	}
360 	else
361 		absPath = File;
362 	return absPath;
363 }
364 
365 /***************************************************************************
366     begin                : Wed Oct 29 2003
367     copyright            : (C) 2003 The Scribus Team
368     email                : paul@all-the-johnsons.co.uk
369  ***************************************************************************/
370 // check if the file exists, if it does, ask if they're sure
371 // return true if they're sure, else return false;
372 
overwrite(QWidget * parent,const QString & filename)373 bool overwrite(QWidget *parent, const QString& filename)
374 {
375 	bool retval = true;
376 	QFileInfo fi(filename);
377 	if (fi.exists())
378 	{
379 		QString fn = QDir::toNativeSeparators(filename);
380 		int t = ScMessageBox::warning(parent, QObject::tr("File exists"),
381 									 "<qt>"+ QObject::tr("A file named '%1' already exists.<br/>Do you want to replace it with the file you are saving?").arg(fn) +"</qt>",
382 											 QMessageBox::Ok | QMessageBox::Cancel,
383 											 QMessageBox::Cancel,	// GUI default
384 											 QMessageBox::Ok);	// batch default
385 		if (t == QMessageBox::Cancel)
386 			retval = false;
387 	}
388 	return retval;
389 }
390 
WordAndPara(PageItem * currItem,int * w,int * p,int * c,int * wN,int * pN,int * cN)391 void WordAndPara(PageItem* currItem, int *w, int *p, int *c, int *wN, int *pN, int *cN)
392 {
393 	QChar Dat = QChar(32);
394 	int para = 0;
395 	int ww = 0;
396 	int cc = 0;
397 	int paraN = 0;
398 	int wwN = 0;
399 	int ccN = 0;
400 	bool first = true;
401 	PageItem *nextItem = currItem;
402 	PageItem *nbl = currItem;
403 	while (nextItem != nullptr)
404 	{
405 		if (nextItem->prevInChain() != nullptr)
406 			nextItem = nextItem->prevInChain();
407 		else
408 			break;
409 	}
410 	while (nextItem != nullptr)
411 	{
412 		for (int a = qMax(nextItem->firstInFrame(),0); a <= nextItem->lastInFrame() && a < nextItem->itemText.length(); ++a)
413 		{
414 			QChar b = nextItem->itemText.text(a);
415 			if (b == SpecialChars::PARSEP)
416 				para++;
417 			if ((!b.isLetterOrNumber()) && (Dat.isLetterOrNumber()) && (!first))
418 				ww++;
419 			if (b.isSurrogate())
420 				++a;
421 			cc++;
422 			Dat = b;
423 			first = false;
424 		}
425 		nbl = nextItem;
426 		nextItem = nextItem->nextInChain();
427 	}
428 	if (nbl->frameOverflows())
429 	{
430 		paraN++;
431 		for (int a = nbl->lastInFrame()+1; a < nbl->itemText.length(); ++a)
432 		{
433 			QChar b = nbl->itemText.text(a);
434 			if (b == SpecialChars::PARSEP)
435 				paraN++;
436 			if ((!b.isLetterOrNumber()) && (Dat.isLetterOrNumber()) && (!first))
437 				wwN++;
438 			if (b.isSurrogate())
439 				++a;
440 			ccN++;
441 			Dat = b;
442 			first = false;
443 		}
444 	}
445 	else {
446 		para++;
447 	}
448 	if (Dat.isLetterOrNumber())
449 	{
450 		if (nbl->frameOverflows())
451 			wwN++;
452 		else
453 			ww++;
454 	}
455 	*w = ww;
456 	*p = para;
457 	*c = cc;
458 	*wN = wwN;
459 	*pN = paraN;
460 	*cN = ccN;
461 }
462 
ReOrderText(ScribusDoc * currentDoc,ScribusView * view)463 void ReOrderText(ScribusDoc *currentDoc, ScribusView *view)
464 {
465 	double savScale = view->scale();
466 	view->setScale(1.0);
467 	currentDoc->RePos = true;
468 	QImage pgPix(10, 10, QImage::Format_ARGB32_Premultiplied);
469 	QRect rd; // = QRect(0,0,9,9);
470 	ScPainter *painter = new ScPainter(&pgPix, pgPix.width(), pgPix.height());
471 	for (auto it = currentDoc->MasterItems.begin(); it != currentDoc->MasterItems.end(); ++it)
472 	{
473 		PageItem* currItem = *it;
474 		if (currItem->itemType() == PageItem::PathText)
475 			currItem->DrawObj(painter, rd);
476 	}
477 	for (auto it = currentDoc->Items->begin(); it != currentDoc->Items->end(); ++it)
478 	{
479 		PageItem* currItem = *it;
480 		currItem->layout();
481 		if (currItem->itemType() == PageItem::PathText)
482 			currItem->DrawObj(painter, rd); //FIXME: this should be replaced by code in layout()
483 	}
484 	currentDoc->RePos = false;
485 	view->setScale(savScale);
486 	delete painter;
487 }
488 
489 /*! \brief Helper function for sorting in sortQStringList.
490 \author 10/06/2004 - pv
491 \param s1 first string
492 \param s2 second string
493 \retval bool t/f related s1>s2
494  */
compareQStrings(const QString & s1,const QString & s2)495 bool compareQStrings(const QString& s1, const QString& s2)
496 {
497 	return QString::localeAwareCompare(s1, s2) < 0;
498 }
499 
sortQStringList(QStringList aList)500 QStringList sortQStringList(QStringList aList)
501 {
502 	std::vector<QString> sortList;
503 	QStringList retList;
504 	QStringList::Iterator it;
505 	for (it = aList.begin(); it != aList.end(); ++it)
506 		sortList.push_back(*it);
507 	std::sort(sortList.begin(), sortList.end(), compareQStrings);
508 	for (uint i = 0; i < sortList.size(); i++)
509 		retList.append(sortList[i]);
510 	return retList;
511 }
512 
sortingQPairOfStrings(QPair<QString,QString> aP,QPair<QString,QString> bP)513 bool sortingQPairOfStrings( QPair<QString, QString> aP, QPair<QString, QString> bP)
514 {
515 	if (aP.first == bP.first)
516 		return (aP.second < bP.second);
517 	return (aP.first < bP.first);
518 }
519 
checkFileExtension(const QString & currName,const QString & extension)520 QString checkFileExtension(const QString &currName, const QString &extension)
521 {
522 	QString newName(currName);
523 	//If filename ends with a period, just add the extension
524 	if (newName.right(1)==".")
525 	{
526 		newName+=extension.toLower();
527 		return newName;
528 	}
529 	//If filename doesn't end with the period+extension, add it on
530 	QString dotExt("." + extension.toLower());
531 	if (!newName.endsWith(dotExt, Qt::CaseInsensitive))
532 		newName+=dotExt;
533 	return newName;
534 }
535 
getFileNameByPage(ScribusDoc * currDoc,uint pageNo,const QString & extension,const QString & prefix)536 QString getFileNameByPage(ScribusDoc* currDoc, uint pageNo, const QString& extension, const QString& prefix)
537 {
538 	uint number = pageNo + currDoc->FirstPnum;
539 	QString defaultName;
540 	if (!prefix.isNull())
541 		defaultName=prefix;
542 	else
543 		defaultName=currDoc->documentFileName();
544 	if (defaultName.isNull())
545 		defaultName = "export";
546 	else
547 	{
548 		QFileInfo fi(defaultName);
549 		defaultName = fi.completeBaseName();
550 	}
551 	return QString("%1-%2%3.%4").arg(defaultName, QObject::tr("page", "page export")).arg(number, 3, 10, QChar('0')).arg(extension);
552 }
553 
getStringFromSequence(NumFormat type,uint position,const QString & asterix)554 QString getStringFromSequence(NumFormat type, uint position, const QString& asterix)
555 {
556 	QString retVal;
557 
558 	const QString english("abcdefghijklmnopqrstuvwxyz");
559 	const QString arabic("أبتثجحخدذرزسشصضطظعغفقكلمنهوي");
560 	const QString abjad("أبجدهوزحطيكلمنسعفصقرشتثخذضظغ");
561 
562 	switch (type)
563 	{
564 		case Type_1_2_3:
565 			retVal=QString::number(position);
566 			break;
567 		case Type_1_2_3_ar:
568 			retVal=QLocale("ar").toString(position);
569 			break;
570 		case Type_A_B_C:
571 			retVal = numberToLetterSequence(english, position).toUpper();
572 			break;
573 		case Type_a_b_c:
574 			retVal = numberToLetterSequence(english, position);
575 			break;
576 		case Type_Alphabet_ar:
577 			retVal = numberToLetterSequence(arabic, position);
578 			break;
579 		case Type_Abjad_ar:
580 			retVal = numberToLetterSequence(abjad, position);
581 			break;
582 		case Type_Hebrew:
583 			retVal = numberToHebrew(position);
584 			break;
585 		case Type_I_II_III:
586 			retVal = numberToRoman(position);
587 			break;
588 		case Type_i_ii_iii:
589 			//well, for lower case people will want that, even if its "wrong"
590 			//ie, X=10, x=10000
591 			retVal = numberToRoman(position).toLower();
592 			break;
593 		case Type_asterix:
594 			for (uint a=1; a <= position; ++a)
595 				retVal.append(asterix);
596 			break;
597 		case Type_CJK:
598 			retVal = numberToCJK(position);
599 		case Type_None:
600 			break;
601 		default:
602 			break;
603 	}
604 	return retVal;
605 }
606 
numberToLetterSequence(const QString & letters,uint num)607 QString numberToLetterSequence(const QString& letters, uint num)
608 {
609 	QString retVal;
610 	unsigned digits = 1;
611 	unsigned offset = 0;
612 	uint column = num - 1;
613 
614 	// FIXME: what is the heck is this?
615 	if (column > 4058115285U)
616 		return  QString("@");
617 
618 	for (unsigned limit = 28; column >= limit+offset; limit *= letters.length(), digits++)
619 		offset += limit;
620 
621 	for (unsigned c = column - offset; digits; --digits, c /= letters.length())
622 	{
623 		uint i = c % letters.length();
624 		if (i < static_cast<uint>(letters.length()))
625 			retVal.prepend(letters.at(i));
626 		else
627 			retVal.prepend(QChar::Null);
628 	}
629 	return retVal;
630 }
631 
numberToRoman(uint i)632 QString numberToRoman(uint i)
633 {
634 	QString roman("");
635 	int arabic = i;
636 	while (arabic - 1000000 >= 0){
637 		roman += "m";
638 		arabic -= 1000000;
639 	}
640 	while (arabic - 900000 >= 0){
641 		roman += "cm";
642 		arabic -= 900000;
643 	}
644 	while (arabic - 500000 >= 0){
645 		roman += "d";
646 		arabic -= 500000;
647 	}
648 	while (arabic - 400000 >= 0){
649 		roman += "cd";
650 		arabic -= 400000;
651 	}
652 	while (arabic - 100000 >= 0){
653 		roman += "c";
654 		arabic -= 100000;
655 	}
656 	while (arabic - 90000 >= 0){
657 		roman += "xc";
658 		arabic -= 90000;
659 	}
660 	while (arabic - 50000 >= 0){
661 		roman += "l";
662 		arabic -= 50000;
663 	}
664 	while (arabic - 40000 >= 0){
665 		roman += "xl";
666 		arabic -= 40000;
667 	}
668 	while (arabic - 10000 >= 0){
669 		roman += "x";
670 		arabic -= 10000;
671 	}
672 	while (arabic - 9000 >= 0){
673 		roman += "Mx";
674 		arabic -= 9000;
675 	}
676 	while (arabic - 5000 >= 0){
677 		roman += "v";
678 		arabic -= 5000;
679 	}
680 	while (arabic - 4000 >= 0){
681 		roman += "Mv";
682 		arabic -= 4000;
683 	}
684 	while (arabic - 1000 >= 0){
685 		roman += "M";
686 		arabic -= 1000;
687 	}
688 	while (arabic - 900 >= 0){
689 		roman += "CM";
690 		arabic -= 900;
691 	}
692 	while (arabic - 500 >= 0){
693 		roman += "D";
694 		arabic -= 500;
695 	}
696 	while (arabic - 400 >= 0){
697 		roman += "CD";
698 		arabic -= 400;
699 	}
700 	while (arabic - 100 >= 0){
701 		roman += "C";
702 		arabic -= 100;
703 	}
704 	while (arabic - 90 >= 0){
705 		roman += "XC";
706 		arabic -= 90;
707 	}
708 	while (arabic - 50 >= 0){
709 		roman += "L";
710 		arabic -= 50;
711 	}
712 	while (arabic - 40 >= 0){
713 		roman += "XL";
714 		arabic -= 40;
715 	}
716 	while (arabic - 10 >= 0){
717 		roman += "X";
718 		arabic -= 10;
719 	}
720 	while (arabic - 9 >= 0){
721 		roman += "IX";
722 		arabic -= 9;
723 	}
724 	while (arabic - 5 >= 0){
725 		roman += "V";
726 		arabic -= 5;
727 	}
728 	while (arabic - 4 >= 0){
729 		roman += "IV";
730 		arabic -= 4;
731 	}
732 	while (arabic - 1 >= 0){
733 		roman += "I";
734 		arabic -= 1;
735 	}
736 	return roman;
737 }
738 
739 //CB Moved from scribus.cpp
parsePagesString(const QString & pages,std::vector<int> * pageNs,int sourcePageCount)740 void parsePagesString(const QString& pages, std::vector<int>* pageNs, int sourcePageCount)
741 {
742 	QString tmp(pages);
743 	QString token;
744 	do
745 	{
746 		if (tmp.indexOf(",") == -1)
747 		{
748 			token = tmp;
749 			tmp = "";
750 		}
751 		else
752 		{
753 			token = tmp.left(tmp.indexOf(","));
754 			tmp = tmp.right(tmp.length() - tmp.indexOf(",") - 1);
755 		}
756 
757 		token = token.trimmed();
758 		if (token == "*") // Import all source doc pages
759 		{
760 			for (int i = 1; i <= sourcePageCount; ++i)
761 				pageNs->push_back(i);
762 		}
763 		else if (token.indexOf("-") != -1) // import a range of source doc pages
764 		{
765 			int from = QStringRef(token.leftRef(token.indexOf("-"))).toInt();
766 			int to = QStringRef(token.rightRef(token.length() - token.indexOf("-") - 1)).toInt();
767 			if ((from != 0) && (to != 0))
768 			{
769 				if (from > sourcePageCount)
770 					from = sourcePageCount;
771 				if (to > sourcePageCount)
772 					to = sourcePageCount;
773 				if (from == to)
774 					pageNs->push_back(to);
775 				else if (from < to)
776 				{
777 					for (int i = from; i <= to; ++i)
778 						pageNs->push_back(i);
779 				}
780 				else
781 				{
782 					for (int i = from; i >= to; --i)
783 						pageNs->push_back(i);
784 				}
785 			}
786 		}
787 		else // import single source doc page
788 		{
789 			int pageNr = token.toInt();
790 			if ((pageNr > 0) && (pageNr <= sourcePageCount))
791 				pageNs->push_back(pageNr);
792 		}
793 	} while (!tmp.isEmpty());
794 }
795 
796 
readLineFromDataStream(QDataStream & s)797 QString readLineFromDataStream(QDataStream &s)
798 {
799 	QString ret;
800 	uchar charData;
801 	while (!s.atEnd())
802 	{
803 		s >> charData;
804 		if (charData == '\x0A')
805 			break;
806 		if (charData == '\x0D')
807 		{
808 			quint64 oldPos = s.device()->pos();
809 			s >> charData;
810 			if (charData != '\x0A')
811 				s.device()->seek(oldPos);
812 			break;
813 		}
814 		ret += QChar(charData);
815 	}
816 	return ret.trimmed();
817 }
818 
setCurrentComboItem(QComboBox * box,const QString & text)819 void setCurrentComboItem(QComboBox *box, const QString& text)
820 {
821 	QSignalBlocker signalBlocker(box);
822 	int ind = box->findText(text);
823 	if (ind > -1)
824 		box->setCurrentIndex(ind);
825 }
826 
setCurrentComboItemFromData(QComboBox * box,const QString & data)827 void setCurrentComboItemFromData(QComboBox *box, const QString& data)
828 {
829 	QSignalBlocker signalBlocker(box);
830 	int ind = box->findData(data);
831 	if (ind > -1)
832 		box->setCurrentIndex(ind);
833 }
834 
removeComboItem(QComboBox * box,const QString & text)835 void removeComboItem(QComboBox *box, const QString& text)
836 {
837 	QSignalBlocker signalBlocker(box);
838 	int ind = box->findText(text);
839 	if (ind > -1)
840 		box->removeItem(ind);
841 }
842 
readAdobeUniCodeString(QDataStream & s)843 QString readAdobeUniCodeString(QDataStream &s)
844 {
845 	QString ret;
846 	quint32 len;
847 	s >> len;
848 	for (quint32 i = 0; i < len; i++)
849 	{
850 		quint16 ch;
851 		s >> ch;
852 		if (ch != 0)
853 			ret.append(QChar(ch));
854 	}
855 	return ret;
856 }
857 
readAdobeUniCodeString16(QDataStream & s)858 QString readAdobeUniCodeString16(QDataStream &s)
859 {
860 	QString ret;
861 	quint16 len;
862 	s >> len;
863 	for (quint16 i = 0; i < len; i++)
864 	{
865 		quint16 ch;
866 		s >> ch;
867 		if (ch != 0)
868 			ret.append(QChar(ch));
869 	}
870 	return ret;
871 }
872 
getDashString(int dashtype,double linewidth)873 QString getDashString(int dashtype, double linewidth)
874 {
875 	QString dashString;
876 	QVector<double> dashArray;
877 	getDashArray(dashtype, linewidth, dashArray);
878 	for (int i = 0; i < dashArray.size(); ++i)
879 	{
880 		dashString += QString::number(dashArray.at(i));
881 		if (i < (dashArray.size() - 1))
882 			dashString += " ";
883 	}
884 	return dashString;
885 }
886 
getDashArray(int dashtype,double linewidth,QVector<float> & dashArray)887 void getDashArray(int dashtype, double linewidth, QVector<float> &dashArray)
888 {
889 	QVector<double> tmp;
890 	getDashArray(dashtype, linewidth, tmp);
891 	dashArray.clear();
892 	for (int i = 0; i < tmp.count(); ++i)
893 		dashArray << static_cast<float>(tmp[i]);
894 }
895 
getDashArray(int dashtype,double linewidth,QVector<double> & dashArray)896 void getDashArray(int dashtype, double linewidth, QVector<double> &dashArray)
897 {
898 	dashArray.clear();
899 	if ((dashtype == 1) || (dashtype == 0))
900 		return;
901 	double Dt = qMax(1.0*linewidth, 0.1);
902 	double Sp = qMax(2.0*linewidth, 0.1);
903 	double Da = qMax(4.0*linewidth, 0.1);
904 	switch (dashtype)
905 	{
906 		case 1:
907 			break;
908 		case 2:
909 			dashArray << Da << Sp;
910 			break;
911 		case 3:
912 			dashArray << Dt << Sp;
913 			break;
914 		case 4:
915 			dashArray << Da << Sp << Dt << Sp;
916 			break;
917 		case 5:
918 			dashArray << Da << Sp << Dt << Sp << Dt << Sp;
919 			break;
920 // Additional line styles taken from Inkscape
921 		case 6:
922 			dashArray << qMax(1.0 * linewidth, 0.01) << qMax(1.0 * linewidth, 0.01);
923 			break;
924 		case 7:
925 			dashArray << qMax(1.0 * linewidth, 0.01) << qMax(3.0 * linewidth, 0.01);
926 			break;
927 		case 8:
928 			dashArray << qMax(1.0 * linewidth, 0.01) << qMax(4.0 * linewidth, 0.01);
929 			break;
930 		case 9:
931 			dashArray << qMax(1.0 * linewidth, 0.01) << qMax(6.0 * linewidth, 0.01);
932 			break;
933 		case 10:
934 			dashArray << qMax(1.0 * linewidth, 0.01) << qMax(8.0 * linewidth, 0.01);
935 			break;
936 		case 11:
937 			dashArray << qMax(1.0 * linewidth, 0.01) << qMax(12.0 * linewidth, 0.01);
938 			break;
939 		case 12:
940 			dashArray << qMax(1.0 * linewidth, 0.01) << qMax(24.0 * linewidth, 0.01);
941 			break;
942 		case 13:
943 			dashArray << qMax(1.0 * linewidth, 0.01) << qMax(48.0 * linewidth, 0.01);
944 			break;
945 		case 14:
946 			dashArray << qMax(2.0 * linewidth, 0.01) << qMax(1.0 * linewidth, 0.01);
947 			break;
948 		case 15:
949 			dashArray << qMax(3.0 * linewidth, 0.01) << qMax(1.0 * linewidth, 0.01);
950 			break;
951 		case 16:
952 			dashArray << qMax(4.0 * linewidth, 0.01) << qMax(1.0 * linewidth, 0.01);
953 			break;
954 		case 17:
955 			dashArray << qMax(6.0 * linewidth, 0.01) << qMax(1.0 * linewidth, 0.01);
956 			break;
957 		case 18:
958 			dashArray << qMax(8.0 * linewidth, 0.01) << qMax(1.0 * linewidth, 0.01);
959 			break;
960 		case 19:
961 			dashArray << qMax(10.0 * linewidth, 0.01) << qMax(1.0 * linewidth, 0.01);
962 			break;
963 		case 20:
964 			dashArray << qMax(12.0 * linewidth, 0.01) << qMax(1.0 * linewidth, 0.01);
965 			break;
966 		case 21:
967 			dashArray << qMax(2.0 * linewidth, 0.01) << qMax(2.0 * linewidth, 0.01);
968 			break;
969 		case 22:
970 			dashArray << qMax(3.0 * linewidth, 0.01) << qMax(3.0 * linewidth, 0.01);
971 			break;
972 		case 23:
973 			dashArray << qMax(4.0 * linewidth, 0.01) << qMax(4.0 * linewidth, 0.01);
974 			break;
975 		case 24:
976 			dashArray << qMax(6.0 * linewidth, 0.01) << qMax(6.0 * linewidth, 0.01);
977 			break;
978 		case 25:
979 			dashArray << qMax(8.0 * linewidth, 0.01) << qMax(8.0 * linewidth, 0.01);
980 			break;
981 		case 26:
982 			dashArray << qMax(10.0 * linewidth, 0.01) << qMax(10.0 * linewidth, 0.01);
983 			break;
984 		case 27:
985 			dashArray << qMax(12.0 * linewidth, 0.01) << qMax(12.0 * linewidth, 0.01);
986 			break;
987 		case 28:
988 			dashArray << qMax(2.0 * linewidth, 0.01) << qMax(4.0 * linewidth, 0.01);
989 			break;
990 		case 29:
991 			dashArray << qMax(2.0 * linewidth, 0.01) << qMax(6.0 * linewidth, 0.01);
992 			break;
993 		case 30:
994 			dashArray << qMax(6.0 * linewidth, 0.01) << qMax(2.0 * linewidth, 0.01);
995 			break;
996 		case 31:
997 			dashArray << qMax(4.0 * linewidth, 0.01) << qMax(8.0 * linewidth, 0.01);
998 			break;
999 		case 32:
1000 			dashArray << qMax(8.0 * linewidth, 0.01) << qMax(4.0 * linewidth, 0.01);
1001 			break;
1002 		case 33:
1003 			dashArray << qMax(2.0 * linewidth, 0.01) << qMax(1.0 * linewidth, 0.01);
1004 			dashArray << qMax(0.5 * linewidth, 0.01) << qMax(1.0 * linewidth, 0.01);
1005 			break;
1006 		case 34:
1007 			dashArray << qMax(8.0 * linewidth, 0.01) << qMax(2.0 * linewidth, 0.01);
1008 			dashArray << qMax(1.0 * linewidth, 0.01) << qMax(2.0 * linewidth, 0.01);
1009 			break;
1010 		case 35:
1011 			dashArray << qMax(0.5 * linewidth, 0.01) << qMax(0.5 * linewidth, 0.01);
1012 			break;
1013 		case 36:
1014 			dashArray << qMax(0.25 * linewidth, 0.01) << qMax(0.25 * linewidth, 0.01);
1015 			break;
1016 		case 37:
1017 			dashArray << qMax(0.1 * linewidth, 0.01) << qMax(0.1 * linewidth, 0.01);
1018 			break;
1019 		default:
1020 			break;
1021 	}
1022 }
1023 
convertOldTable(ScribusDoc * m_Doc,PageItem * gItem,QList<PageItem * > & gpL,QStack<QList<PageItem * >> * groupStackT,QList<PageItem * > * target)1024 bool convertOldTable(ScribusDoc *m_Doc, PageItem* gItem, QList<PageItem*> &gpL, QStack<QList<PageItem *> > *groupStackT, QList<PageItem *> *target)
1025 {
1026 	QList<double> colWidths;
1027 	QList<double> rowHeights;
1028 
1029 	// 1. Although this was not intended, legacy tables allowed user to link frames together
1030 	// New table do not support that, so if one frame has any link, we stop the conversion
1031 	// here, those frame will be converted to a standard group.
1032 	// 2. Pre-1.4.3 versions had a bug where item TopLink/LeftLink/BottomLink/RightLink were
1033 	// lost when copy/pasting tables. Exit conversion too so these broken tables can be
1034 	// converted to standard groups (at least until we find a good way to process that case)
1035 	bool hasTableLinks = false;
1036 	bool hasTextLinks = false;
1037 	for (int i = 0; i < gpL.count(); i++)
1038 	{
1039 		PageItem* it = gpL[i];
1040 		it->isTableItem = false;
1041 		if (it->nextInChain() || it->prevInChain())
1042 			hasTextLinks = true;
1043 		if (it->m_leftLink || it->m_rightLink || it->m_bottomLink || it->m_topLink)
1044 			hasTableLinks = true;
1045 	}
1046 
1047 	if (!hasTableLinks || hasTextLinks)
1048 		return false;
1049 
1050 	PageItem *topLeft = nullptr;
1051 	for (int i = 0; i < gpL.count(); i++)
1052 	{
1053 		PageItem* it = gpL[i];
1054 		if ((it->m_topLink == nullptr) && (it->m_leftLink == nullptr))	// we got the topleft item
1055 		{
1056 			topLeft = it;
1057 			PageItem *tl = it;
1058 			while (tl->m_rightLink != nullptr)
1059 			{
1060 				colWidths.append(tl->width());
1061 				tl = tl->m_rightLink;
1062 			}
1063 			colWidths.append(tl->width());
1064 			while (tl->m_bottomLink != nullptr)
1065 			{
1066 				rowHeights.append(tl->height());
1067 				tl = tl->m_bottomLink;
1068 			}
1069 			rowHeights.append(tl->height());
1070 			break;
1071 		}
1072 	}
1073 
1074 	// Check we have found enough rows and columns so that no item will disappear
1075 	if ((colWidths.count() * rowHeights.count()) < gpL.count())
1076 		return false;
1077 
1078 	m_Doc->dontResize = true;
1079 	int z = m_Doc->itemAdd(PageItem::Table, PageItem::Unspecified, gItem->xPos(), gItem->yPos(), gItem->width(), gItem->height(), 0.0, CommonStrings::None, CommonStrings::None);
1080 	PageItem_Table* currItem = m_Doc->Items->takeAt(z)->asTable();
1081 
1082 	currItem->m_layerID = gItem->m_layerID;
1083 	currItem->OwnPage = gItem->OwnPage;
1084 	currItem->OnMasterPage = gItem->OnMasterPage;
1085 
1086 	currItem->insertRows(0, rowHeights.count()-1);
1087 	m_Doc->dontResize = true;
1088 	currItem->insertColumns(0, colWidths.count()-1);
1089 	m_Doc->dontResize = true;
1090 	for (int i = 0; i < rowHeights.count(); i++)
1091 	{
1092 		currItem->resizeRow(i, rowHeights[i]);
1093 	}
1094 	m_Doc->dontResize = true;
1095 	for (int i = 0; i < colWidths.count(); i++)
1096 	{
1097 		currItem->resizeColumn(i, colWidths[i]);
1098 	}
1099 	TableBorder border(0.0, Qt::SolidLine, CommonStrings::None, 100);
1100 	currItem->setLeftBorder(border);
1101 	currItem->setTopBorder(border);
1102 	currItem->setRightBorder(border);
1103 	currItem->setBottomBorder(border);
1104 	m_Doc->dontResize = true;
1105 	PageItem *tr = topLeft;
1106 	int rowCount = 0;
1107 	int colCount = 0;
1108 	while (rowCount < rowHeights.count())
1109 	{
1110 		PageItem *tl = tr;
1111 		while (colCount < colWidths.count())
1112 		{
1113 			currItem->cellAt(rowCount, colCount).textFrame()->itemText = tl->itemText.copy();
1114 			currItem->cellAt(rowCount, colCount).setFillColor(tl->fillColor());
1115 			currItem->cellAt(rowCount, colCount).setFillShade(tl->fillShade());
1116 			currItem->cellAt(rowCount, colCount).setLeftBorder(border);
1117 			currItem->cellAt(rowCount, colCount).setTopBorder(border);
1118 			currItem->cellAt(rowCount, colCount).setRightBorder(border);
1119 			currItem->cellAt(rowCount, colCount).setBottomBorder(border);
1120 			if ((tl->lineColor() != CommonStrings::None) && (tl->lineWidth() != 0.0))
1121 			{
1122 				TableBorder bb(tl->lineWidth(), tl->lineStyle(), tl->lineColor(), tl->lineShade());
1123 				if (tl->LeftLine)
1124 					currItem->cellAt(rowCount, colCount).setLeftBorder(bb);
1125 				if (tl->TopLine)
1126 					currItem->cellAt(rowCount, colCount).setTopBorder(bb);
1127 				if (tl->RightLine)
1128 					currItem->cellAt(rowCount, colCount).setRightBorder(bb);
1129 				if (tl->BottomLine)
1130 					currItem->cellAt(rowCount, colCount).setBottomBorder(bb);
1131 			}
1132 			if (colCount == colWidths.count()-1)
1133 				break;
1134 			colCount++;
1135 			tl = tl->m_rightLink;
1136 		}
1137 		if (rowCount == rowHeights.count()-1)
1138 			break;
1139 		colCount = 0;
1140 		rowCount++;
1141 		tr = tr->m_bottomLink;
1142 	}
1143 	m_Doc->dontResize = true;
1144 	currItem->setLayer(gItem->m_layerID);
1145 	currItem->setMasterPage(gItem->OwnPage, gItem->OnMasterPage);
1146 	currItem->adjustFrameToTable();
1147 	if (target != nullptr)
1148 	{
1149 		int ind = target->indexOf(gItem);
1150 		target->replace(ind, currItem);
1151 	}
1152 	else
1153 	{
1154 		int ind = m_Doc->FrameItems.key(gItem);
1155 		m_Doc->FrameItems.remove(ind);
1156 		m_Doc->FrameItems.insert(ind, currItem);
1157 	}
1158 	if (groupStackT != nullptr)
1159 	{
1160 		if (groupStackT->count() > 0)
1161 		{
1162 			int ii = groupStackT->top().indexOf(gItem);
1163 			if (ii >= 0)
1164 				groupStackT->top().replace(ii, currItem);
1165 		}
1166 	}
1167 	while (!gpL.isEmpty())
1168 	{
1169 		PageItem* item = gpL.takeFirst();
1170 		if (item->isTextFrame())
1171 			item->dropLinks();
1172 		delete item;
1173 	}
1174 	delete gItem;
1175 
1176 	return true;
1177 }
1178 
setWidgetBoldFont(QWidget * w,bool wantBold)1179 void setWidgetBoldFont(QWidget* w, bool wantBold)
1180 {
1181 	QFont f(w->font());
1182 	f.setBold(wantBold);
1183 	w->setFont(f);
1184 }
1185 
1186 
getUniqueName(QString & name,const QStringList & list,const QString & separator,bool prepend)1187 void getUniqueName(QString &name, const QStringList& list, const QString& separator, bool prepend)
1188 {
1189 	if (!list.contains(name))
1190 		return;
1191 	int token = -1;
1192 	QString newName;
1193 	do {
1194 		token++;
1195 		if (prepend)
1196 			newName = separator + QString::number(token) + " " + name;
1197 		else
1198 			newName = name + separator + QString::number(token);
1199 	} while (list.contains(newName));
1200 	name = newName;
1201 }
1202 
numberToHebrew(uint i)1203 QString numberToHebrew(uint i)
1204 {
1205 	const QString hebrew("אבגדהוזחטיכלמנסעפצקרשת");
1206 	QString result;
1207 
1208 	if (i > 999)
1209 	{
1210 		result.append(numberToHebrew(i / 1000));
1211 		result.append(QChar(0x05F3));
1212 		i %= 1000;
1213 	}
1214 
1215 	int hundreds = i / 100;
1216 	int tens = (i - hundreds * 100) / 10;
1217 	int ones = i % 10;
1218 
1219 	while (hundreds > 4)
1220 	{
1221 		result.append(hebrew.at(21));
1222 		hundreds -= 4;
1223 	}
1224 
1225 	if (hundreds)
1226 		result.append(hebrew.at(hundreds + 17));
1227 
1228 	if (tens == 1 && ones == 5)
1229 		result.append("טו");
1230 	else if (tens == 1 && ones == 6)
1231 		result.append("טז");
1232 	else
1233 	{
1234 		if (tens)
1235 			result.append(hebrew.at(tens + 8));
1236 		if (ones)
1237 			result.append(hebrew.at(ones - 1));
1238 	}
1239 
1240 	return result;
1241 }
1242 
numberToCJK(uint i)1243 QString numberToCJK(uint i)
1244 {
1245 	QString result;
1246 	if (i<10)
1247 		result = QString(cjkDigit(i));
1248 
1249 	if (i>9 && i<=99)
1250 	{
1251 		int tens=i/10;
1252 		int ones=i%10;
1253 		if (tens!=1)
1254 			result.append(cjkDigit(tens));
1255 		result.append(cjkDigit(10));
1256 		if (ones!=0)
1257 			result.append(cjkDigit(ones));
1258 	}
1259 
1260 	if (i>99 && i<=999)
1261 	{
1262 		int hundreds=i/100;
1263 		int tens=(i-hundreds*100)/10;
1264 		int ones=i%10;
1265 		result.append(cjkDigit(hundreds));
1266 		result.append(cjkDigit(100));
1267 		if (tens!=0)
1268 		{
1269 			result.append(cjkDigit(tens));
1270 			result.append(cjkDigit(10));
1271 		}
1272 		else if (ones!=0)
1273 			result.append(cjkDigit(0));
1274 		if (ones!=0)
1275 			result.append(cjkDigit(ones));
1276 	}
1277 	return result;
1278 }
1279 
1280 
cjkDigit(uint i)1281 QChar cjkDigit(uint i)
1282 {
1283 	switch (i)
1284 	{
1285 		case 0:
1286 			return QChar(0x96f6);
1287 			break;
1288 		case 1:
1289 			return QChar(0x4e00);
1290 			break;
1291 		case 2:
1292 			return QChar(0x4e8c);
1293 			break;
1294 		case 3:
1295 			return QChar(0x4e09);
1296 			break;
1297 		case 4:
1298 			return QChar(0x56db);
1299 			break;
1300 		case 5:
1301 			return QChar(0x4e94);
1302 			break;
1303 		case 6:
1304 			return QChar(0x516d);
1305 			break;
1306 		case 7:
1307 			return QChar(0x4e03);
1308 			break;
1309 		case 8:
1310 			return QChar(0x516b);
1311 			break;
1312 		case 9:
1313 			return QChar(0x4e5d);
1314 			break;
1315 		case 10:
1316 			return QChar(0x5341);
1317 			break;
1318 		case 100:
1319 			return QChar(0x767e);
1320 			break;
1321 		case 1000:
1322 			return QChar(0x5343);
1323 			break;
1324 		case 10000:
1325 			return QChar(0x842c);
1326 			break;
1327 		case 100000000:
1328 			return QChar(0x5104);
1329 			break;
1330 	}
1331 	return QChar::Null;
1332 }
1333