1 /*******************************************************************
2 
3 Part of the Fritzing project - http://fritzing.org
4 Copyright (c) 2007-2014 Fachhochschule Potsdam - http://fh-potsdam.de
5 
6 Fritzing is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10 
11 Fritzing is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with Fritzing.  If not, see <http://www.gnu.org/licenses/>.
18 
19 ********************************************************************
20 
21 $Revision: 6984 $:
22 $Author: irascibl@gmail.com $:
23 $Date: 2013-04-22 23:44:56 +0200 (Mo, 22. Apr 2013) $
24 
25 ********************************************************************/
26 
27 //#include <QNetworkInterface>
28 #include <QTextDocument>
29 #include <QTextStream>
30 
31 #include "textutils.h"
32 #include "misc.h"
33 #include "../installedfonts.h"
34 
35 //#include "../debugdialog.h"
36 
37 #include <QRegExp>
38 #include <QBuffer>
39 #include <QFile>
40 #include <QSettings>
41 #include <QUrl>
42 #include <QUuid>
43 #include <QCryptographicHash>
44 
45 #include <qmath.h>
46 #include <qnumeric.h>
47 
48 QSet<QString> InstalledFonts::InstalledFontsList;
49 QMultiHash<QString, QString> InstalledFonts::InstalledFontsNameMapper;   // family name to filename; SVG files seem to have to use filename
50 
51 const QString TextUtils::CreatedWithFritzingString("Created with Fritzing (http://www.fritzing.org/)");
52 const QString TextUtils::CreatedWithFritzingXmlComment("<!-- " + CreatedWithFritzingString + " -->\n");
53 
54 const QRegExp TextUtils::FindWhitespace("[\\s]+");
55 static const QRegExp SodipodiAttributeDetector("(inkscape|sodipodi):[^=\\s]+=\"([^\"\\\\]*(\\\\.[^\"\\\\]*)*)\"");
56 static const QRegExp SodipodiElementDetector("</{0,1}(inkscape|sodipodi):[^>]+>");
57 const QString TextUtils::SMDFlipSuffix("___");
58 
59 const QString TextUtils::RegexFloatDetector = "[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?";
60 const QRegExp TextUtils::floatingPointMatcher(RegexFloatDetector);
61 
62 static const QRegExp HexExpr("&#x[0-9a-fA-F];");   // &#x9; &#xa; &#xd;
63 static const QRegExp Xmlns("xmlns=([\"|'])[^\"']*\\1");
64 
65 const ushort TextUtils::MicroSymbolCode = 181;
66 const QString TextUtils::MicroSymbol = QString::fromUtf16(&MicroSymbolCode, 1);
67 
68 const QString TextUtils::AdobeIllustratorIdentifier = "Generator: Adobe Illustrator";
69 
70 QList<QString> PowerPrefixes;
71 QList<double> PowerPrefixValues;
72 const QString TextUtils::PowerPrefixesString = QString("pnmkMGTu\\x%1").arg(MicroSymbolCode, 4, 16, QChar('0'));
73 
74 typedef QHash<QString /*brokenFont*/, QString /*replacementFont*/> FixedFontsHash;
75 
76 //////////////////////////////////////////////
77 
getAttrFontFamilies(const QString & fileContent)78 QSet<QString> getAttrFontFamilies(const QString &fileContent) {
79 	/*
80 	 * font-family defined as attr example:
81 
82 <text xmlns="http://www.w3.org/2000/svg" font-family="Droid Sans"
83 id="text2732" transform="matrix(1 0 0 1 32.2012 236.969)"
84 font-size="9.9771" >A0</text>
85 
86 	 */
87 
88 	static QString pattern = "font-family\\s*=\\s*\"([^\"]*)\"";
89 	return TextUtils::getRegexpCaptures(pattern,fileContent);
90 }
91 
getFontFamiliesInsideStyleTag(const QString & fileContent)92 QSet<QString> getFontFamiliesInsideStyleTag(const QString &fileContent) {
93 	/*
94 	 * regexp: font-family\s*:\s*(.|[^;"]*).*"
95 	 * font-family defined in a style attr example:
96 
97 style="font-size:9;-inkscape-font-specification:Droid Sans;font-family:Droid Sans;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
98 
99 style="font-size:144px;font-style:normal;font-weight:normal;line-height:100%;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" x="18.000002"
100 
101 	 */
102 
103 	static QString pattern = "font-family\\s*:\\s*([^;\"]*)";
104 	return TextUtils::getRegexpCaptures(pattern,fileContent);
105 }
106 
107 
fixFontsMapping(const QSet<QString> fontsTofix,const QString & destFont)108 FixedFontsHash fixFontsMapping(const QSet<QString> fontsTofix, const QString & destFont) {
109 	FixedFontsHash retval;
110 
111 	QSet<QString> tempFontsToFix;
112 	tempFontsToFix = fontsTofix - InstalledFonts::InstalledFontsList;
113 	foreach (QString fontFileName, InstalledFonts::InstalledFontsNameMapper.values()) {
114 		// SVG uses filename which may not match family name (e.g. "Droid Sans" and "Droid Sans")
115 		tempFontsToFix.remove(fontFileName);
116 	}
117 
118 	foreach (QString broken, tempFontsToFix) {
119 		retval.insert(broken, destFont);
120 	}
121 
122 	return retval;
123 }
124 
removeFontFamilySingleQuotes(QString & fileContent)125 bool removeFontFamilySingleQuotes(QString &fileContent) {
126 	static QString pattern = "font-family=\"('[^']*')\"";
127 	QSet<QString> wrongFontFamilies = TextUtils::getRegexpCaptures(pattern, fileContent);
128 
129 	foreach(QString ff, wrongFontFamilies) {
130 		QString wrongFF = ff;
131 		QString fixedFF = ff.remove('\'');
132 		fileContent.replace(wrongFF,fixedFF);
133 	}
134 
135 	return wrongFontFamilies.size() > 0;
136 }
137 
fixUnavailableFontFamilies(QString & fileContent,const QString & destFont)138 bool fixUnavailableFontFamilies(QString &fileContent, const QString & destFont) {
139 	QSet<QString> definedFFs;
140 	definedFFs.unite(getAttrFontFamilies(fileContent));
141 	definedFFs.unite(getFontFamiliesInsideStyleTag(fileContent));
142 
143 	FixedFontsHash fixedFonts = fixFontsMapping(definedFFs, destFont);
144 	foreach(QString oldF, fixedFonts.keys()) {
145 		QString newF = fixedFonts[oldF];
146 		fileContent.replace(oldF,newF);
147 	}
148 
149 	return fixedFonts.size() > 0;
150 }
151 
makeDashString(bool dashed,const QVector<qreal> & pattern,double dpi,double printerScale)152 QString  makeDashString(bool dashed, const QVector<qreal> & pattern, double dpi, double printerScale)
153 {
154     QString dash;
155     if (dashed && pattern.count() > 0) {
156         dash = "stroke-dasharray='";
157         foreach (qreal p, pattern) {
158             dash += QString::number(p * dpi / printerScale);
159             dash += ",";
160         }
161         dash.chop(1);
162         dash.append("'");
163     }
164 
165     return dash;
166 }
167 
168 ///////////////////////////////////////
169 
170 
findElementsWithAttribute(QDomElement & element,const QString & att,QList<QDomElement> & elements)171 void TextUtils::findElementsWithAttribute(QDomElement & element, const QString & att, QList<QDomElement> & elements) {
172 	if (!element.attribute(att).isEmpty()) {
173 		elements.append(element);
174 	}
175 
176 	QDomElement child = element.firstChildElement();
177 	while (!child.isNull()) {
178 		findElementsWithAttribute(child, att, elements);
179 		child = child.nextSiblingElement();
180 	}
181 }
182 
findElementWithAttribute(QDomElement element,const QString & attributeName,const QString & attributeValue)183 QDomElement TextUtils::findElementWithAttribute(QDomElement element, const QString & attributeName, const QString & attributeValue) {
184 	if (element.hasAttribute(attributeName)) {
185 		if (element.attribute(attributeName).compare(attributeValue) == 0) return element;
186 	}
187 
188      for(QDomElement e = element.firstChildElement(); !e.isNull(); e = e.nextSiblingElement())
189      {
190 		 QDomElement result = findElementWithAttribute(e, attributeName, attributeValue);
191 		 if (!result.isNull()) return result;
192      }
193 
194 	return ___emptyElement___;
195 }
196 
197 
getRegexpCaptures(const QString & pattern,const QString & textToSearchIn)198 QSet<QString> TextUtils::getRegexpCaptures(const QString &pattern, const QString &textToSearchIn) {
199 	QRegExp re(pattern);
200 	QSet<QString> captures;
201 	int pos = 0;
202 
203 	while ((pos = re.indexIn(textToSearchIn, pos)) != -1) {
204 		captures << re.cap(1);
205 		pos += re.matchedLength();
206 	}
207 
208 	return captures;
209 }
210 
convertToInches(const QString & s,bool * ok,bool isIllustrator)211 double TextUtils::convertToInches(const QString & s, bool * ok, bool isIllustrator) {
212 	QString string = s;
213 	double divisor = 1.0;
214 	int chop = 2;
215 	if (string.endsWith("cm", Qt::CaseInsensitive)) {
216 		divisor = 2.54;
217 	}
218 	else if (string.endsWith("mm", Qt::CaseInsensitive)) {
219 		divisor = 25.4;
220 	}
221 	else if (string.endsWith("in", Qt::CaseInsensitive)) {
222 		divisor = 1.0;
223 	}
224 	else if (string.endsWith("px", Qt::CaseInsensitive)) {
225 		divisor = isIllustrator? 72.0: 90.0;
226 	}
227 	else if (string.endsWith("mil", Qt::CaseInsensitive)) {
228 		divisor = 1000.0;
229 		chop = 3;
230 	}
231 	else if (string.endsWith("pt", Qt::CaseInsensitive)) {
232 		divisor = 72.0;
233 	}
234 	else if (string.endsWith("pc", Qt::CaseInsensitive)) {
235 		divisor = 6.0;
236 	}
237 	else {
238         chopNotDigits(string);
239 		divisor = 90.0;			// default to Qt's standard internal units if all else fails
240 		chop = 0;
241 	}
242 
243 	if (chop) string.chop(chop);
244 
245 	bool fine;
246 	double result = string.toDouble(&fine);
247 	if (!fine) {
248 		if (ok) *ok = false;
249 		return 0;
250 	}
251 
252 	if (ok) *ok = true;
253 	return result / divisor;
254 }
255 
chopNotDigits(QString & string)256 void TextUtils::chopNotDigits(QString & string) {
257 	for (int ix = string.count() - 1; ix >= 0; ix--) {
258         QChar ch = string.at(ix);
259 		if (ch.isDigit()) return;
260         if (ch == '.') return;
261 
262 		string.chop(1);
263 	}
264 }
265 
squashElement(QDomDocument & doc,const QString & elementName,const QString & attName,const QRegExp & matchContent)266 bool TextUtils::squashElement(QDomDocument & doc, const QString & elementName, const QString &attName, const QRegExp &matchContent) {
267     bool result = false;
268     QDomElement root = doc.documentElement();
269     QDomNodeList domNodeList = root.elementsByTagName(elementName);
270     for (int i = 0; i < domNodeList.count(); i++) {
271         QDomElement node = domNodeList.item(i).toElement();
272         if (node.isNull()) continue;
273 
274         if (!attName.isEmpty()) {
275             QString att = node.attribute(attName);
276             if (att.isEmpty()) continue;
277 
278             if (!matchContent.isEmpty()) {
279                 if (matchContent.indexIn(att) < 0) continue;
280             }
281         }
282 
283         node.setTagName("g");
284         result = true;
285     }
286 
287     return result;
288 }
289 
replaceTextElement(const QString & svg,const QString & id,const QString & newValue)290 QString TextUtils::replaceTextElement(const QString & svg, const QString & id, const QString & newValue) {
291 	QString errorStr;
292 	int errorLine;
293 	int errorColumn;
294 	QDomDocument doc;
295 	if (!doc.setContent(svg, &errorStr, &errorLine, &errorColumn)) return svg;
296 
297 	QDomElement root = doc.documentElement();
298 	QDomNodeList domNodeList = root.elementsByTagName("text");
299 	for (int i = 0; i < domNodeList.count(); i++) {
300 		QDomElement node = domNodeList.item(i).toElement();
301 		if (node.isNull()) continue;
302 
303 		if (node.attribute("id").compare(id) != 0) continue;
304 
305 		replaceChildText(node, newValue);
306 		return doc.toString();
307 	}
308 
309 	return svg;
310 }
311 
replaceTextElement(const QByteArray & svg,const QString & id,const QString & newValue)312 QByteArray TextUtils::replaceTextElement(const QByteArray & svg, const QString & id, const QString & newValue) {
313 	QString errorStr;
314 	int errorLine;
315 	int errorColumn;
316 	QDomDocument doc;
317 	if (!doc.setContent(svg, &errorStr, &errorLine, &errorColumn)) return svg;
318 
319 	QDomElement root = doc.documentElement();
320 	QDomNodeList domNodeList = root.elementsByTagName("text");
321 	for (int i = 0; i < domNodeList.count(); i++) {
322 		QDomElement node = domNodeList.item(i).toElement();
323 		if (node.isNull()) continue;
324 
325 		if (node.attribute("id").compare(id) != 0) continue;
326 
327 		replaceChildText(node, newValue);
328 		return doc.toByteArray();
329 	}
330 
331 	return svg;
332 }
333 
replaceTextElements(const QString & svg,const QHash<QString,QString> & hash)334 QString TextUtils::replaceTextElements(const QString & svg, const QHash<QString, QString> & hash) {
335 	QString errorStr;
336 	int errorLine;
337 	int errorColumn;
338 	QDomDocument doc;
339 	if (!doc.setContent(svg, &errorStr, &errorLine, &errorColumn)) return svg;
340 
341 	bool changed = false;
342 	QDomElement root = doc.documentElement();
343 	QDomNodeList domNodeList = root.elementsByTagName("text");
344 	for (int i = 0; i < domNodeList.count(); i++) {
345 		QDomElement node = domNodeList.item(i).toElement();
346 		if (node.isNull()) continue;
347 		foreach (QString id, hash.keys()) {
348 			if (node.attribute("id").compare(id) != 0) continue;
349 
350 			replaceChildText(node, hash.value(id));
351 			changed = true;
352 			break;
353 		}
354 	}
355 
356 	if (!changed) return svg;
357 
358 	return doc.toString();
359 }
360 
361 
replaceElementChildText(QDomElement & root,const QString & elementName,const QString & text)362 void TextUtils::replaceElementChildText(QDomElement & root, const QString & elementName, const QString & text) {
363 	QDomElement element = root.firstChildElement(elementName);
364 	if (element.isNull()) {
365 		element = root.ownerDocument().createElement(elementName);
366 		root.appendChild(element);
367 	}
368 	replaceChildText(element, text);
369 }
370 
replaceChildText(QDomNode & node,const QString & text)371 void TextUtils::replaceChildText(QDomNode & node, const QString & text) {
372 	node.normalize();
373 
374 
375 	QDomNodeList childList = node.childNodes();
376 	for (int j = 0; j < childList.count(); j++) {
377 		QDomNode child = childList.item(j);
378 		if (child.isText()) {
379 			child.setNodeValue(text);
380 			return;
381 		}
382 	}
383 
384 	QDomText t = node.ownerDocument().createTextNode(text);
385 	node.appendChild(t);
386 }
387 
mergeSvg(QDomDocument & doc1,const QString & svg,const QString & id)388 bool TextUtils::mergeSvg(QDomDocument & doc1, const QString & svg, const QString & id)
389 {
390 	QString errorStr;
391 	int errorLine;
392 	int errorColumn;
393 	if (doc1.isNull()) {
394 		return doc1.setContent(svg, &errorStr, &errorLine, &errorColumn);
395 	}
396 
397 	QDomDocument doc2;
398 	if (!doc2.setContent(svg, &errorStr, &errorLine, &errorColumn)) return false;
399 
400 	QDomElement root1 = doc1.documentElement();
401 	if (root1.tagName() != "svg") return false;
402 
403 	QDomElement root2 = doc2.documentElement();
404 	if (root2.tagName() != "svg") return false;
405 
406 	QDomElement id1;
407 	if (!id.isEmpty()) {
408 		id1 = findElementWithAttribute(root1, "id", id);
409 	}
410 	if (id1.isNull()) id1 = root1;
411 
412 	QDomElement id2;
413 	if (!id.isEmpty()) {
414 		id2 = findElementWithAttribute(root2, "id", id);
415 	}
416 	if (id2.isNull()) id2 = root2;
417 
418 	QDomNode node = id2.firstChild();
419 	while (!node.isNull()) {
420 		QDomNode nextNode = node.nextSibling();
421 		id1.appendChild(node);
422 		node = nextNode;
423 	}
424 
425 	return true;
426 }
427 
mergeSvgFinish(QDomDocument & doc)428 QString TextUtils::mergeSvgFinish(QDomDocument & doc) {
429 	return removeXMLEntities(doc.toString());
430 }
431 
mergeSvg(const QString & svg1,const QString & svg2,const QString & id,bool flip)432 QString TextUtils::mergeSvg(const QString & svg1, const QString & svg2, const QString & id, bool flip) {
433 
434 	QDomDocument doc1;
435     if (!svg1.isEmpty()) {
436 	    if (!TextUtils::mergeSvg(doc1, svg1, id)) return ___emptyString___;
437     }
438 
439 	if (!TextUtils::mergeSvg(doc1, svg2, id)) return ___emptyString___;
440 
441 	if (flip) {
442 		QDomElement svg = doc1.documentElement();
443 		QString viewBox = svg.attribute("viewBox");
444 		QStringList coords = viewBox.split(" ", QString::SkipEmptyParts);
445 		double width = coords[2].toDouble();
446 		QMatrix matrix;
447 		matrix.translate(width / 2, 0);
448 		matrix.scale(-1, 1);
449 		matrix.translate(-width / 2, 0);
450 		QHash<QString, QString> attributes;
451 		attributes.insert("transform", svgMatrix(matrix));
452 		gWrap(doc1, attributes);
453 	}
454 
455 	return mergeSvgFinish(doc1);
456 }
457 
makeSVGHeader(double printerScale,double dpi,double width,double height)458 QString TextUtils::makeSVGHeader(double printerScale, double dpi, double width, double height) {
459 
460 	double trueWidth = width / printerScale;
461 	double trueHeight = height / printerScale;
462 
463 	return
464 		QString("<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n%5"
465 							 "<svg xmlns:svg='http://www.w3.org/2000/svg' xmlns='http://www.w3.org/2000/svg' "
466 							 "version='1.2' baseProfile='tiny' "
467 							 "x='0in' y='0in' width='%1in' height='%2in' "
468 							 "viewBox='0 0 %3 %4' >\n"
469 							 )
470 						.arg(trueWidth)
471 						.arg(trueHeight)
472 						.arg(trueWidth * dpi)
473 						.arg(trueHeight * dpi)
474 						.arg(TextUtils::CreatedWithFritzingXmlComment);
475 }
476 
isIllustratorFile(const QString & fileContent)477 bool TextUtils::isIllustratorFile(const QString &fileContent) {
478 	return fileContent.contains(AdobeIllustratorIdentifier, Qt::CaseInsensitive);
479 }
480 
isIllustratorFile(const QByteArray & fileContent)481 bool TextUtils::isIllustratorFile(const QByteArray & fileContent) {
482 	return fileContent.contains(AdobeIllustratorIdentifier.toUtf8());
483 }
484 
isIllustratorDoc(const QDomDocument & doc)485 bool TextUtils::isIllustratorDoc(const QDomDocument & doc) {
486 	QDomNodeList children = doc.childNodes();
487 	for ( int i=0; i < children.count(); ++i ) {
488 		QDomNode child = children.at(i);
489 		if (child.nodeType() == QDomNode::CommentNode) {
490 			if (isIllustratorFile(child.nodeValue())) {
491 				return true;
492 			}
493 		}
494 	}
495 
496 	return false;
497 }
498 
removeXMLEntities(QString svgContent)499 QString TextUtils::removeXMLEntities(QString svgContent) {
500 	return svgNSOnly(svgContent.remove(HexExpr));
501 }
502 
killXMLNS(QString svgContent)503 QString TextUtils::killXMLNS(QString svgContent) {
504 	// TODO: this is a bug in Qt, it would be nice to fix it there
505 
506     svgContent.remove(Xmlns);
507     return svgContent;
508 }
509 
svgNSOnly(QString svgContent)510 QString TextUtils::svgNSOnly(QString svgContent) {
511     svgContent.remove(Xmlns);
512     int ix = svgContent.indexOf("<svg");
513     if (ix >= 0) {
514         svgContent.insert(ix + 5, "xmlns=\"http://www.w3.org/2000/svg\" ");
515     }
516     return svgContent;
517 }
518 
cleanSodipodi(QString & content)519 bool TextUtils::cleanSodipodi(QString &content)
520 {
521 	// clean out sodipodi stuff
522 	// TODO: don't bother with the core parts
523 	int l1 = content.length();
524 	content.remove(SodipodiAttributeDetector);          // remove attributes first
525 	content.remove(SodipodiElementDetector);
526 	if (content.length() != l1) {
527 		return true;
528 	}
529 	return false;
530 
531 
532 	/*
533 	QString errorStr;
534 	int errorLine;
535 	int errorColumn;
536 	QDomDocument doc;
537 	bool result = doc.setContent(bytes, &errorStr, &errorLine, &errorColumn);
538 	m_svgXml.clear();
539 	if (!result) {
540 		return false;
541 	}
542 
543 	SvgFlattener flattener;
544 	QDomElement root = doc.documentElement();
545 	flattener.flattenChildren(root);
546 	SvgFileSplitter::fixStyleAttributeRecurse(root);
547 	return doc.toByteArray();
548 	*/
549 }
550 
fixPixelDimensionsIn(QString & fileContent)551 bool TextUtils::fixPixelDimensionsIn(QString &fileContent) {
552 	bool isIllustrator = isIllustratorFile(fileContent);
553 	if (!isIllustrator) return false;
554 
555 	QDomDocument svgDom;
556 
557 	QString errorMsg;
558 	int errorLine;
559 	int errorCol;
560 	if(!svgDom.setContent(fileContent, true, &errorMsg, &errorLine, &errorCol)) {
561 		return false;
562 	}
563 
564 	bool fileHasChanged = false;
565 
566 	if(isIllustrator) {
567 		QDomElement elem = svgDom.firstChildElement("svg");
568 		fileHasChanged = pxToInches(elem,"width",isIllustrator);
569 		fileHasChanged |= pxToInches(elem,"height",isIllustrator);
570 	}
571 
572 	if (fileHasChanged) {
573 		fileContent = removeXMLEntities(svgDom.toString());
574 	}
575 
576 	return fileHasChanged;
577 }
578 
pxToInches(QDomElement & elem,const QString & attrName,bool isIllustrator)579 bool TextUtils::pxToInches(QDomElement &elem, const QString &attrName, bool isIllustrator) {
580 	if (!isIllustrator) return false;
581 
582 	QString attrValue = elem.attribute(attrName);
583 	if(attrValue.endsWith("px")) {
584 		bool ok;
585 		double value = TextUtils::convertToInches(attrValue, &ok, isIllustrator);
586 		if(ok) {
587 			QString newValue = QString("%1in").arg(value);
588 			elem.setAttribute(attrName,newValue);
589 
590 			return true;
591 		}
592 	}
593 	return false;
594 }
595 
svgMatrix(const QMatrix & matrix)596 QString TextUtils::svgMatrix(const QMatrix & matrix) {
597 	return QString("matrix(%1, %2, %3, %4, %5, %6)").arg(matrix.m11()).arg(matrix.m12()).arg(matrix.m21()).arg(matrix.m22()).arg(matrix.dx()).arg(matrix.dy());
598 }
599 
svgMatrix(const QTransform & matrix)600 QString TextUtils::svgMatrix(const QTransform & matrix) {
601 	return QString("matrix(%1, %2, %3, %4, %5, %6)").arg(matrix.m11()).arg(matrix.m12()).arg(matrix.m21()).arg(matrix.m22()).arg(matrix.dx()).arg(matrix.dy());
602 }
603 
setSVGTransform(QDomElement & element,QMatrix & matrix)604 void TextUtils::setSVGTransform(QDomElement & element, QMatrix & matrix)
605 {
606 	element.setAttribute("transform", svgMatrix(matrix));
607 }
608 
svgTransform(const QString & svg,QTransform & transform,bool translate,const QString extras)609 QString TextUtils::svgTransform(const QString & svg, QTransform & transform, bool translate, const QString extras) {
610 	if (transform.isIdentity()) return svg;
611 
612 	return QString("<g transform='matrix(%1,%2,%3,%4,%5,%6)' %8 >%7</g>")
613 			.arg(transform.m11())
614 			.arg(transform.m12())
615 			.arg(transform.m21())
616 			.arg(transform.m22())
617 			.arg(translate ? transform.dx() : 0.0)
618 			.arg(translate ? transform.dy() : 0.0)
619 			.arg(svg)
620 			.arg(extras);
621 }
622 
getSvgSizes(QDomDocument & doc,double & sWidth,double & sHeight,double & vbWidth,double & vbHeight)623 bool TextUtils::getSvgSizes(QDomDocument & doc, double & sWidth, double & sHeight, double & vbWidth, double & vbHeight)
624 {
625 	bool isIllustrator = isIllustratorDoc(doc);
626 
627 	QDomElement root = doc.documentElement();
628 	QString swidthStr = root.attribute("width");
629 	if (swidthStr.isEmpty()) return false;
630 
631 	QString sheightStr = root.attribute("height");
632 	if (sheightStr.isEmpty()) return false;
633 
634 	bool ok;
635 	sWidth = TextUtils::convertToInches(swidthStr, &ok, isIllustrator);
636 	if (!ok) return false;
637 
638 	sHeight = TextUtils::convertToInches(sheightStr, &ok, isIllustrator);
639 	if (!ok) return false;
640 
641 	bool vbWidthOK = false;
642 	bool vbHeightOK = false;
643 	QString sviewboxStr = root.attribute("viewBox");
644 	if (!sviewboxStr.isEmpty()) {
645 		QStringList strings = sviewboxStr.split(" ");
646 		if (strings.size() == 4) {
647 			double tempWidth = strings[2].toDouble(&vbWidthOK);
648 			if (vbWidthOK) {
649 				vbWidth = tempWidth;
650 			}
651 
652 			double tempHeight= strings[3].toDouble(&vbHeightOK);
653 			if (vbHeightOK) {
654 				vbHeight = tempHeight;
655 			}
656 		}
657 	}
658 
659 	if (vbWidthOK && vbHeightOK) return true;
660 
661 	// assume that if there's no viewBox, the viewbox is at the right dpi?
662 	// or should the assumption be 90 or 100?  Illustrator would be 72...
663 	int multiplier = 90;
664 	if (isIllustratorFile(doc.toString())) {
665 		multiplier = 72;
666 	}
667 
668 	vbWidth = sWidth * multiplier;
669 	vbHeight = sHeight * multiplier;
670 	return true;
671 }
672 
findText(const QDomNode & node,QString & text)673 bool TextUtils::findText(const QDomNode & node, QString & text) {
674 	if (node.isText()) {
675 		text = node.nodeValue();
676 		return true;
677 	}
678 
679 	QDomNode cnode = node.firstChild();
680 	while (!cnode.isNull()) {
681 		if (findText(cnode, text)) return true;
682 
683 		cnode = cnode.nextSibling();
684 	}
685 
686 	return false;
687 }
688 
convertToInches(const QString & string)689 double TextUtils::convertToInches(const QString & string) {
690 	bool ok;
691 	double retval = TextUtils::convertToInches(string, &ok, false);
692 	if (!ok) return 0;
693 
694 	return retval;
695 }
696 
escapeAnd(const QString & string)697 QString TextUtils::escapeAnd(const QString & string) {
698 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
699 	QString s = Qt::escape(string);
700 #else
701     QString s = string.toHtmlEscaped();
702 #endif
703 	s.replace('\'', "&apos;");
704 	return s;
705 }
706 
convertExtendedChars(const QString & str)707 QString TextUtils::convertExtendedChars(const QString & str)
708 {
709 	QString result;
710     foreach (QChar c, str) {
711 		if (c < 128) {
712 			result.append(c);
713 		}
714 		else {
715 			result.append(QString("&#x%1;").arg(c.unicode(), 0, 16));
716 		}
717 	}
718 
719 	return result;
720 }
721 
stripNonValidXMLCharacters(const QString & str)722 QString TextUtils::stripNonValidXMLCharacters(const QString & str)
723 {
724 	QString result;
725 	QChar hs;
726 	bool in_hs = false;
727     foreach (QChar c, str) {
728 		if (c.isHighSurrogate()) {
729 			hs = c;
730 			in_hs = true;
731 			continue;
732 		}
733 
734         if ((c == 0x9) ||
735             (c == 0xA) ||
736             (c == 0xD) ||
737             ((c >= 0x20) && (c <= 0xD7FF)) ||
738             ((c >= 0xE000) && (c <= 0xFFFD)))
739 		{
740 			if (in_hs) {
741 				result.append(hs);
742 				in_hs = false;
743 			}
744 			if (c > 255) {
745 				result.append(QString("&#%1").arg(c, 0, 16));
746 			}
747 			else {
748 				result.append(c);
749 			}
750 		}
751 		else {
752 			in_hs = false;
753 		}
754     }
755     return result;
756 }
757 
addCopper1(const QString & filename,QDomDocument & domDocument,const QString & srcAtt,const QString & destAtt)758 bool TextUtils::addCopper1(const QString & filename, QDomDocument & domDocument, const QString & srcAtt, const QString & destAtt) {
759 	QString errorStr;
760 	int errorLine;
761 	int errorColumn;
762 	QFile file(filename);
763 	bool result = domDocument.setContent(&file, &errorStr, &errorLine, &errorColumn);
764 	if (!result) {
765 		domDocument.clear();			// probably redundant
766 		return false;
767 	}
768 
769     QDomElement root = domDocument.documentElement();
770 	QList<QDomElement> elements;
771 	findElementsWithAttribute(root, "id", elements);
772     for (int i = 0; i < elements.count(); i++) {
773         QDomElement node = elements.at(i);
774         if (node.isNull()) continue;
775 
776         QString att = node.attribute("id");
777 		if (att == destAtt) {
778 			// already have copper1
779 			domDocument.clear();
780 			return false;
781 		}
782     }
783 
784 	result = false;
785     for (int i = 0; i < elements.count(); i++) {
786         QDomElement node = elements.at(i);
787         if (node.isNull()) continue;
788 
789         QString att = node.attribute("id");
790 		if (att == srcAtt) {
791 			QDomElement g = domDocument.createElement("g");
792 			g.setAttribute("id", destAtt);
793 			node.parentNode().insertAfter(g, node);
794 			g.appendChild(node);
795 			result = true;
796 		}
797     }
798 
799 	if (!result) {
800 		domDocument.clear();
801 	}
802 	//else {
803 		//QString test = domDocument.toString();
804 		//DebugDialog::debug("test " + test);
805 	//}
806 
807 	return result;
808 
809 }
810 
convertToPowerPrefix(double q)811 QString TextUtils::convertToPowerPrefix(double q) {
812 	initPowerPrefixes();
813 
814 	for (int i = 0; i < PowerPrefixes.count(); i++) {
815 		if (q < 100 * PowerPrefixValues[i]) {
816 			q /= PowerPrefixValues[i];
817 			return QString::number(q) + PowerPrefixes[i];
818 		}
819 	}
820 
821 	return QString::number(q);
822 }
823 
convertFromPowerPrefixU(QString & val,const QString & symbol)824 double TextUtils::convertFromPowerPrefixU(QString & val, const QString & symbol)
825 {
826 	val.replace('u', MicroSymbol);
827 	return convertFromPowerPrefix(val, symbol);
828 }
829 
convertFromPowerPrefix(const QString & val,const QString & symbol)830 double TextUtils::convertFromPowerPrefix(const QString & val, const QString & symbol)
831 {
832 	initPowerPrefixes();
833 
834 	double multiplier = 1;
835 	QString temp = val;
836 	if (temp.endsWith(symbol)) {
837 		temp.chop(symbol.length());
838 	}
839 
840 	for (int i = 0; i < PowerPrefixes.count(); i++) {
841 		if (PowerPrefixes[i].isEmpty()) continue;
842 
843 		if (temp.endsWith(PowerPrefixes[i], Qt::CaseSensitive)) {
844 			multiplier = PowerPrefixValues[i];
845 			temp.chop(PowerPrefixes[i].length());
846 			break;
847 		}
848 	}
849 	temp = temp.trimmed();
850 	return temp.toDouble() * multiplier;
851 }
852 
initPowerPrefixes()853 void TextUtils::initPowerPrefixes() {
854 	if (PowerPrefixes.count() == 0) {
855 		PowerPrefixes << "p" << "n" << MicroSymbol  << "u" << "m" << "" << "k" << "M" << "G" << "T";
856         PowerPrefixValues << 0.000000000001 << 0.000000001 << 0.000001 << 0.000001 << 0.001 << 1 << 1000 << 1000000 << 1000000000. << 1000000000000.;
857 	}
858 }
859 
collectLeaves(QDomElement & element,int & index,QVector<QDomElement> & leaves)860 void TextUtils::collectLeaves(QDomElement & element, int & index, QVector<QDomElement> & leaves) {
861 	if (element.hasChildNodes()) {
862 		element.removeAttribute("id");
863 		QDomElement c = element.firstChildElement();
864 		while (!c.isNull()) {
865 			collectLeaves(c, index, leaves);
866 			c = c.nextSiblingElement();
867 		}
868 	}
869 	else {
870 		leaves.insert(index, element);
871 		element.setAttribute("id", QString::number(index++));
872 	}
873 }
874 
collectLeaves(QDomElement & element,QList<QDomElement> & leaves)875 void TextUtils::collectLeaves(QDomElement & element, QList<QDomElement> & leaves) {
876 	if (element.hasChildNodes()) {
877 		QDomElement c = element.firstChildElement();
878 		while (!c.isNull()) {
879 			collectLeaves(c, leaves);
880 			c = c.nextSiblingElement();
881 		}
882 	}
883 	else {
884 		leaves.append(element);
885 	}
886 }
887 
elementToMatrix(QDomElement & element)888 QMatrix TextUtils::elementToMatrix(QDomElement & element) {
889 	QString transform = element.attribute("transform");
890 	if (transform.isEmpty()) return QMatrix();
891 
892 	return transformStringToMatrix(transform);
893 }
894 
transformStringToMatrix(const QString & transform)895 QMatrix TextUtils::transformStringToMatrix(const QString & transform) {
896 
897 	// doesn't handle multiple transform attributes
898 	QList<double> floats = getTransformFloats(transform);
899 
900 	if (transform.startsWith("translate")) {
901 		return QMatrix().translate(floats[0], (floats.length() > 1) ? floats[1] : 0);
902 	}
903 	else if (transform.startsWith("rotate")) {
904 		if (floats.length() == 1) {
905 			return QMatrix().rotate(floats[0]);
906 		}
907 		else if (floats.length() == 3) {
908 			return  QMatrix().translate(-floats[1], -floats[2]) * QMatrix().rotate(floats[0]) * QMatrix().translate(floats[1], floats[2]);
909 		}
910 	}
911 	else if (transform.startsWith("matrix")) {
912         return QMatrix(floats[0], floats[1], floats[2], floats[3], floats[4], floats[5]);
913 	}
914 	else if (transform.startsWith("scale")) {
915 		return QMatrix().scale(floats[0], floats[1]);
916 	}
917 	else if (transform.startsWith("skewX")) {
918 		return QMatrix().shear(floats[0], 0);
919 	}
920 	else if (transform.startsWith("skewY")) {
921 		return QMatrix().shear(0, floats[0]);
922 	}
923 
924 	return QMatrix();
925 }
926 
getTransformFloats(QDomElement & element)927 QList<double> TextUtils::getTransformFloats(QDomElement & element){
928 	return getTransformFloats(element.attribute("transform"));
929 }
930 
getTransformFloats(const QString & transform)931 QList<double> TextUtils::getTransformFloats(const QString & transform){
932     QList<double> list;
933     int pos = 0;
934 
935 	while ((pos = TextUtils::floatingPointMatcher.indexIn(transform, pos)) != -1) {
936 		list << transform.mid(pos, TextUtils::floatingPointMatcher.matchedLength()).toDouble();
937         pos += TextUtils::floatingPointMatcher.matchedLength();
938     }
939 
940 #ifndef QT_NO_DEBUG
941    // QString dbg = "got transform params: \n";
942     //dbg += transform + "\n";
943     //for(int i=0; i < list.size(); i++){
944         //dbg += QString::number(list.at(i)) + " ";
945     // }
946     //DebugDialog::debug(dbg);
947 #endif
948 
949     return list;
950 }
951 
gWrap(QDomDocument & domDocument,const QHash<QString,QString> & attributes)952 void TextUtils::gWrap(QDomDocument & domDocument, const QHash<QString, QString> & attributes)
953 {
954 	QDomElement g = domDocument.createElement("g");
955 	foreach (QString key, attributes.keys()) {
956 		g.setAttribute(key, attributes.value(key, ""));
957 	}
958 
959 	QDomNodeList nodeList = domDocument.documentElement().childNodes();
960 	QList<QDomNode> nodes;
961 	for (int i = 0; i < nodeList.count(); i++) {
962 		nodes.append(nodeList.item(i));
963 	}
964 
965 	domDocument.documentElement().appendChild(g);
966 	foreach (QDomNode node, nodes) {
967 		g.appendChild(node);
968 	}
969 }
970 
fixInternalUnits(QString & svg)971 bool TextUtils::fixInternalUnits(QString & svg)
972 {
973 	// float detector is a little weak
974 	static QRegExp findInternalUnits("[\"']([\\d,\\.]+)(px|mm|cm|in|pt|pc)[\"']");
975 	static QRegExp findStrokeWidth("stroke-width:([\\d,\\.]+)(px|mm|cm|in|pt|pc)");
976 
977 	int sw = 0;
978 	int iu = 0;
979 	int jx = svg.indexOf("<svg");
980 	if (jx >= 0) {
981 		sw = iu = svg.indexOf(">", jx + 4);
982 	}
983 
984 	bool firstTime = true;
985 	QSizeF size;
986 	QRectF viewBox;
987 	bool result = false;
988 	while (true) {
989 		iu = findInternalUnits.indexIn(svg, iu);
990 		if (iu < 0) break;
991 
992 		result = true;
993 		if (firstTime) {
994 			// assumes width dpi = height dpi
995 			size = parseForWidthAndHeight(svg, viewBox, true);
996 			if (size.width() == 0) {
997 				// svg is messed up
998 				return false;
999 			}
1000             firstTime = false;
1001 		}
1002 
1003 		QString old = findInternalUnits.cap(1) + findInternalUnits.cap(2);
1004 		double in = convertToInches(old);
1005 		double replacement = in * viewBox.width() / size.width();
1006 		svg.replace(iu + 1, old.length(), QString::number(replacement));
1007 	}
1008 
1009 	while (true) {
1010 		sw = findStrokeWidth.indexIn(svg, sw);
1011 		if (sw < 0) break;
1012 
1013 		result = true;
1014 		if (firstTime) {
1015 			// assumes width dpi = height dpi
1016 			size = parseForWidthAndHeight(svg, viewBox, true);
1017 			if (size.width() == 0) {
1018 				// svg is messed up
1019 				return false;
1020 			}
1021 		}
1022 
1023 		QString old = findStrokeWidth.cap(1) + findStrokeWidth.cap(2);
1024 		double in = convertToInches(old);
1025 		double replacement = in * viewBox.width() / size.width();
1026 		svg.replace(sw + 13, old.length(), QString::number(replacement));
1027 	}
1028 
1029 	return result;
1030 }
1031 
fixMuch(QString & svg,bool fixStrokeWidthFlag)1032 bool TextUtils::fixMuch(QString &svg, bool fixStrokeWidthFlag)
1033 {
1034     bool result = cleanSodipodi(svg);
1035 	result |= fixInternalUnits(svg);
1036 
1037 	QDomDocument svgDom;
1038 	QString errorMsg;
1039 	int errorLine;
1040 	int errorCol;
1041 	if(!svgDom.setContent(svg, true, &errorMsg, &errorLine, &errorCol)) {
1042 		return result;
1043 	}
1044 
1045     QDomElement root = svgDom.documentElement();
1046     result |= fixViewBox(root);
1047 
1048     QStringList strings;
1049     strings << "pattern" << "marker" << "clipPath";
1050     foreach (QString string, strings) {
1051         if (svg.contains("<" + string)) {
1052             result |= noPatternAux(svgDom, string);
1053         }
1054     }
1055 
1056     if (svg.contains("<use")) {
1057         result |= noUseAux(svgDom);
1058     }
1059 
1060     if (svg.contains("<tspan")) {
1061         result |= tspanRemoveAux(svgDom);
1062     }
1063 
1064     if (fixStrokeWidthFlag) {
1065         result |= fixStrokeWidth(svgDom);
1066     }
1067 
1068     result |= elevateTransform(root);
1069 
1070     if (result) {
1071         svg = removeXMLEntities(svgDom.toString());
1072     }
1073 
1074     return result;
1075 }
1076 
fixViewBox(QDomElement & root)1077 bool TextUtils::fixViewBox(QDomElement & root) {
1078 	QString viewBox = root.attribute("viewBox");
1079     if (viewBox.isEmpty()) return false;
1080 
1081 	QStringList coords = viewBox.split(QRegExp(" |,"));
1082 	if (coords.length() != 4) return false;
1083 
1084     if (coords[0] == "0" && coords[1] == "0") return false;
1085 
1086     bool ok;
1087     double x = coords.at(0).toDouble(&ok);
1088     if (!ok) return false;
1089 
1090     double y = coords.at(1).toDouble(&ok);
1091     if (!ok) return false;
1092 
1093 	QString newValue = QString("0 0 %1 %2").arg(coords[2]).arg(coords[3]);
1094 	root.setAttribute("viewBox", newValue);
1095 
1096     QDomElement transformElement = root.ownerDocument().createElement("g");
1097     transformElement.setAttribute("transform", QString("translate(%1,%2)").arg(-x).arg(-y));
1098     transformElement = root.insertBefore(transformElement, QDomElement()).toElement();
1099     QDomElement nextElement = transformElement.nextSiblingElement();
1100     while (!nextElement.isNull()) {
1101         transformElement.appendChild(nextElement);
1102         nextElement = transformElement.nextSiblingElement();
1103     }
1104 
1105 	return true;
1106 }
1107 
getStrokeWidth(QDomElement & element,double defaultValue)1108 double TextUtils::getStrokeWidth(QDomElement & element, double defaultValue)
1109 {
1110     bool ok;
1111 	double sw = element.attribute("stroke-width").toDouble(&ok);
1112     if (ok) return sw;
1113 
1114     QString stroke = element.attribute("stroke");
1115     if (stroke == "none") return 0;
1116 
1117     QDomElement parent = element.parentNode().toElement();
1118     while (!parent.isNull()) {
1119         sw = parent.attribute("stroke-width").toDouble(&ok);
1120         if (ok) return sw;
1121 
1122         stroke = parent.attribute("stroke");
1123         if (stroke == "none") return 0;
1124 
1125         parent = parent.parentNode().toElement();
1126     }
1127 
1128     //QString text;
1129     //QTextStream stream(&text);
1130     //element.save(stream, 0);
1131     //DebugDialog::debug(QString("no circle stroke width set in %1: %2").arg(filename).arg(text));
1132 
1133     // default if there is no value to inherit
1134     element.setAttribute("stroke-width", defaultValue);
1135     return defaultValue;
1136 
1137 	//QString strokewidth("stroke-width");
1138 	//QString s = element.attribute("style");
1139 	//SvgFileSplitter::fixStyleAttribute(connectorElement, s, strokewidth);
1140 	//sw = connectorElement.attribute("stroke-width").toDouble(&ok);
1141 	//if (!ok) {
1142 		//return false;
1143 	//}
1144 }
1145 
fixStrokeWidth(QDomDocument & svgDoc)1146 bool TextUtils::fixStrokeWidth(QDomDocument & svgDoc) {
1147     bool result = false;
1148 
1149     QList<QDomElement> todo;
1150     todo << svgDoc.documentElement();
1151     while (todo.count() > 0) {
1152         QDomElement element = todo.takeFirst();
1153         QDomElement child = element.firstChildElement();
1154         while (!child.isNull()) {
1155             todo.append(child);
1156             child = child.nextSiblingElement();
1157         }
1158 
1159         QString stroke, strokeWidth;
1160         stroke = element.attribute("stroke");
1161         if (stroke.isEmpty()) {
1162             QString style = element.attribute("style");
1163             if (style.contains("stroke")) {
1164                 fixStyleAttribute(element);
1165                 stroke = element.attribute("stroke");
1166             }
1167         }
1168         strokeWidth = element.attribute("stroke-width");
1169 
1170         if (stroke.isEmpty()) continue;
1171         if (stroke == "none") continue;
1172         if (!strokeWidth.isEmpty()) continue;
1173 
1174         getStrokeWidth(element, 1);
1175         result = true;
1176     }
1177 
1178     return result;
1179 }
1180 
1181 
noPatternAux(QDomDocument & svgDom,const QString & tag)1182 bool TextUtils::noPatternAux(QDomDocument & svgDom, const QString & tag)
1183 {
1184     bool result = false;
1185 	QDomNodeList nodeList = svgDom.elementsByTagName(tag);
1186     for (int i = 0; i < nodeList.count(); i++) {
1187         QDomNode pattern = nodeList.at(i);
1188         pattern.parentNode().removeChild(pattern);
1189         result = true;
1190 	}
1191     return result;
1192 }
1193 
1194 
tspanRemoveAux(QDomDocument & svgDom)1195 bool TextUtils::tspanRemoveAux(QDomDocument & svgDom)
1196 {
1197 	QList<QDomElement> texts;
1198 	QDomNodeList textNodeList = svgDom.elementsByTagName("text");
1199     for (int i = 0; i < textNodeList.count(); i++) {
1200         QDomElement text = textNodeList.item(i).toElement();
1201 		QDomElement tspan = text.firstChildElement("tspan");
1202 		if (tspan.isNull()) continue;
1203 
1204 		texts.append(text);
1205 	}
1206 
1207     if (texts.count() == 0) return false;
1208 
1209 	foreach (QDomElement text, texts) {
1210 		QDomElement g = svgDom.createElement("g");
1211 		text.parentNode().replaceChild(g, text);
1212 		QDomNamedNodeMap attributes = text.attributes();
1213 		for (int i = 0; i < attributes.count(); i++) {
1214 			QDomNode attribute = attributes.item(i);
1215 			g.setAttribute(attribute.nodeName(), attribute.nodeValue());
1216 		}
1217 		QString defaultX = g.attribute("x");
1218 		QString defaultY = g.attribute("y");
1219 		g.removeAttribute("x");
1220 		g.removeAttribute("y");
1221 
1222 		copyText(svgDom, g, text, defaultX, defaultY, false);
1223 
1224 		QDomElement tspan = text.firstChildElement("tspan");
1225 		while (!tspan.isNull()) {
1226 			copyText(svgDom, g, tspan, defaultX, defaultY, true);
1227 			tspan = tspan.nextSiblingElement("tspan");
1228 		}
1229 	}
1230 
1231 	return true;
1232 }
1233 
noUseAux(QDomDocument & svgDom)1234 bool TextUtils::noUseAux(QDomDocument & svgDom)
1235 {
1236 
1237     QDomElement root = svgDom.documentElement();
1238     if (root.isNull()) return false;
1239 
1240 	QList<QDomElement> uses;
1241 	QDomNodeList useNodeList = svgDom.elementsByTagName("use");
1242     for (int i = 0; i < useNodeList.count(); i++) {
1243         QDomElement use = useNodeList.item(i).toElement();
1244         QString refid = use.attribute("href");
1245         if (refid.isEmpty()) continue;
1246 
1247         QString id = use.attribute("id");
1248         if (id.isEmpty()) continue;
1249 
1250 		uses.append(use);
1251 	}
1252 
1253     if (uses.count() == 0) return false;
1254 
1255 	foreach (QDomElement use, uses) {
1256 		QString transform = use.attribute("transform");
1257         QString refid = use.attribute("href");
1258         QString id = use.attribute("id");
1259 
1260         QDomElement g = svgDom.createElement("g");
1261 		use.parentNode().replaceChild(g, use);
1262         g.setAttribute("transform", transform);
1263 
1264         if (refid.startsWith("#")) {
1265             refid.remove(0, 1);
1266             if (refid.isEmpty()) continue;
1267         }
1268 
1269         QDomElement toCopy = findElementWithAttribute(root, "id", refid);
1270         if (toCopy.isNull()) continue;
1271 
1272         QDomElement copy = toCopy.cloneNode(true).toElement();
1273         g.appendChild(copy);
1274         copy.setAttribute("id", id);
1275 	}
1276 
1277 	return true;
1278 }
1279 
1280 
copyText(QDomDocument & svgDom,QDomElement & parent,QDomElement & text,const QString & defaultX,const QString & defaultY,bool copyAttributes)1281 QDomElement TextUtils::copyText(QDomDocument & svgDom, QDomElement & parent, QDomElement & text, const QString & defaultX, const QString & defaultY, bool copyAttributes)
1282 {
1283 	QDomNode cnode = text.firstChild();
1284 	while (!cnode.isNull()) {
1285 		if (cnode.isText()) {
1286 			QDomElement newText = svgDom.createElement("text");
1287 			parent.appendChild(newText);
1288 			newText.setAttribute("x", defaultX);
1289 			newText.setAttribute("y", defaultY);
1290 			QDomNode textValue = svgDom.createTextNode(cnode.nodeValue());
1291 			newText.appendChild(textValue);
1292 
1293 			if (copyAttributes) {
1294 				QDomNamedNodeMap attributes = text.attributes();
1295 				for (int i = 0; i < attributes.count(); i++) {
1296 					QDomNode attribute = attributes.item(i);
1297 					newText.setAttribute(attribute.nodeName(), attribute.nodeValue());
1298 				}
1299 			}
1300 
1301 			// normalize means there's only one text child node, so we can return now
1302 			return newText;
1303 		}
1304 
1305 		cnode = cnode.nextSibling();
1306 	}
1307 
1308 	return ___emptyElement___;
1309 }
1310 
slamStrokeAndFill(const QString & svg,const QString & stroke,const QString & strokeWidth,const QString & fill)1311 QString TextUtils::slamStrokeAndFill(const QString & svg, const QString & stroke, const QString & strokeWidth, const QString & fill)
1312 {
1313 	QDomDocument doc;
1314     if (doc.setContent(svg)) {
1315         QDomElement root = doc.documentElement();
1316         slamStrokeAndFill(root, stroke, strokeWidth, fill);
1317         return doc.toString();
1318     }
1319 
1320     return svg;
1321 }
1322 
slamStrokeAndFill(QDomElement & element,const QString & stroke,const QString & strokeWidth,const QString & fill)1323 void TextUtils::slamStrokeAndFill(QDomElement & element, const QString & stroke, const QString & strokeWidth, const QString & fill)
1324 {
1325 	// assumes style elements have been normalized already
1326 	QString strokeAtt = element.attribute("stroke");
1327 	QString fillAtt = element.attribute("fill");
1328 	QString strokeWidthAtt = element.attribute("stroke-width");
1329 	if (!strokeAtt.isEmpty() || !fillAtt.isEmpty() || !strokeWidthAtt.isEmpty()) {
1330 		element.setAttribute("stroke", stroke);
1331 		element.setAttribute("fill", fill);
1332 		element.setAttribute("stroke-width", strokeWidth);
1333 	}
1334 
1335 	QDomElement child = element.firstChildElement();
1336 	while (!child.isNull()) {
1337 		slamStrokeAndFill(child, stroke, strokeWidth, fill);
1338 		child = child.nextSiblingElement();
1339 	}
1340 }
1341 
1342 
1343 struct MatchThing
1344 {
1345 	int pos;
1346 	int len;
1347 	double val;
1348 };
1349 
incrementTemplate(const QString & filename,int pins,double increment,MultiplyPinFunction multiFun,CopyPinFunction copyFun,void * userData)1350 QString TextUtils::incrementTemplate(const QString & filename, int pins, double increment, MultiplyPinFunction multiFun, CopyPinFunction copyFun, void * userData)
1351 {
1352 	QFile file(filename);
1353 	file.open(QFile::ReadOnly);
1354 	QString templateString = file.readAll();
1355 	file.close();
1356 
1357 	return incrementTemplateString(templateString, pins, increment, multiFun, copyFun, userData);
1358 }
1359 
incrementTemplateString(const QString & templateString,int pins,double increment,MultiplyPinFunction multiFun,CopyPinFunction copyFun,void * userData)1360 QString TextUtils::incrementTemplateString(const QString & templateString, int pins, double increment, MultiplyPinFunction multiFun, CopyPinFunction copyFun, void * userData)
1361 {
1362 	QString string;
1363 
1364 	QRegExp uMatcher("\\[([\\.\\d]+)\\]");
1365 	MatchThing matchThings[32];
1366 	int pos = 0;
1367 	unsigned int matchThingIndex = 0;
1368 	while ((pos = uMatcher.indexIn(templateString, pos)) != -1) {
1369 		MatchThing * mt = &matchThings[matchThingIndex++];
1370 		mt->pos = pos;
1371 		mt->len = uMatcher.matchedLength();
1372 		mt->val = uMatcher.cap(1).toDouble();
1373 		pos += uMatcher.matchedLength();
1374 		if (matchThingIndex >= sizeof(matchThings) / sizeof(MatchThing)) break;
1375 	}
1376 
1377 	for (int i = 0; i < pins; i++) {
1378 		QString argCopy(templateString);
1379 		for (int j = matchThingIndex - 1; j >= 0; j--) {
1380 			MatchThing * mt = &matchThings[j];
1381 			argCopy.replace(mt->pos, mt->len, (*multiFun)(i, increment, mt->val));
1382 		}
1383 		string += (*copyFun)(i, argCopy, userData);
1384 	}
1385 
1386 	return string;
1387 }
1388 
standardCopyPinFunction(int pin,const QString & argString,void *)1389 QString TextUtils::standardCopyPinFunction(int pin, const QString & argString, void *)
1390 {
1391 	return argString.arg(pin);
1392 }
1393 
standardMultiplyPinFunction(int pin,double increment,double value)1394 QString TextUtils::standardMultiplyPinFunction(int pin, double increment, double value)
1395 {
1396 	return QString::number(value + (pin * increment));
1397 }
1398 
1399 
incMultiplyPinFunction(int pin,double increment,double value)1400 QString TextUtils::incMultiplyPinFunction(int pin, double increment, double value)
1401 {
1402 	return QString::number(value + ((pin + 1) * increment));
1403 }
1404 
incCopyPinFunction(int pin,const QString & argString,void *)1405 QString TextUtils::incCopyPinFunction(int pin, const QString & argString, void *)
1406 {
1407 	return argString.arg(pin + 1);
1408 }
1409 
noCopyPinFunction(int,const QString & argString,void *)1410 QString TextUtils::noCopyPinFunction(int, const QString & argString, void *)
1411 {
1412 	return argString;
1413 }
1414 
negIncCopyPinFunction(int pin,const QString & argString,void * userData)1415 QString TextUtils::negIncCopyPinFunction(int pin, const QString & argString, void * userData)
1416 {
1417 	int pins = *((int *) userData);
1418 	int offset = *(((int *) userData) + 1);
1419 	return argString.arg(pins - (pin + offset));
1420 }
1421 
getViewBoxCoord(const QString & svg,int coord)1422 double TextUtils::getViewBoxCoord(const QString & svg, int coord)
1423 {
1424 	QRegExp re("viewBox=['\\\"]([^'\\\"]+)['\\\"]");
1425 	int ix = re.indexIn(svg);
1426 	if (ix < 0) return 0;
1427 
1428 	QString vb = re.cap(1);
1429 	QStringList coords = vb.split(" ");
1430 	QString c = coords.at(coord);
1431 	return c.toDouble();
1432 }
1433 
makeLineSVG(QPointF p1,QPointF p2,double width,QString colorString,double dpi,double printerScale,bool blackOnly,bool dashed,const QVector<qreal> & pattern)1434 QString TextUtils::makeLineSVG(QPointF p1, QPointF p2, double width, QString colorString, double dpi, double printerScale, bool blackOnly, bool dashed, const QVector<qreal> & pattern)
1435 {
1436 	p1.setX(p1.x() * dpi / printerScale);
1437 	p1.setY(p1.y() * dpi / printerScale);
1438 	p2.setX(p2.x() * dpi / printerScale);
1439 	p2.setY(p2.y() * dpi / printerScale);
1440 
1441     QString dash = makeDashString(dashed, pattern, dpi, printerScale);
1442 
1443 	QString stroke = (blackOnly) ? "black" : colorString;
1444 	return QString("<line stroke-linecap='round' stroke='%6' x1='%1' y1='%2' x2='%3' y2='%4' stroke-width='%5' %7/>")
1445 					.arg(p1.x())
1446 					.arg(p1.y())
1447 					.arg(p2.x())
1448 					.arg(p2.y())
1449 					.arg(width * dpi / printerScale)
1450 					.arg(stroke)
1451                     .arg(dash)
1452                    ;
1453 }
1454 
makeCubicBezierSVG(const QPolygonF & poly,double width,QString colorString,double dpi,double printerScale,bool blackOnly,bool dashed,const QVector<qreal> & pattern)1455 QString TextUtils::makeCubicBezierSVG(const QPolygonF & poly, double width, QString colorString, double dpi, double printerScale, bool blackOnly, bool dashed, const QVector<qreal> & pattern)
1456 {
1457     QString dash = makeDashString(dashed, pattern, dpi, printerScale);
1458     QString stroke = (blackOnly) ? "black" : colorString;
1459 	return QString("<path stroke-linecap='round' fill='none' stroke-width='%1' stroke='%2' d='M%3,%4C%5,%6, %7,%8 %9,%10' %11/>")
1460 					.arg(width * dpi / printerScale)
1461 					.arg(stroke)
1462 					.arg(poly.at(0).x() * dpi / printerScale)
1463 					.arg(poly.at(0).y() * dpi / printerScale)
1464 					.arg(poly.at(1).x() * dpi / printerScale)
1465 					.arg(poly.at(1).y() * dpi / printerScale)
1466 					.arg(poly.at(2).x() * dpi / printerScale)
1467 					.arg(poly.at(2).y() * dpi / printerScale)
1468 					.arg(poly.at(3).x() * dpi / printerScale)
1469 					.arg(poly.at(3).y() * dpi / printerScale)
1470                     .arg(dash)
1471 					;
1472 }
1473 
1474 
makeRectSVG(QRectF r,QPointF offset,double dpi,double printerScale)1475 QString TextUtils::makeRectSVG(QRectF r, QPointF offset, double dpi, double printerScale)
1476 {
1477 	r.translate(-offset.x(), -offset.y());
1478 	double l = r.left() * dpi / printerScale;
1479 	double t = r.top() * dpi / printerScale;
1480 	double w = r.width() * dpi / printerScale;
1481 	double h = r.height() * dpi / printerScale;
1482 
1483 	QString stroke = "black";
1484 	return QString("<rect stroke-linecap='round' stroke='%6' x='%1' y='%2' width='%3' height='%4' stroke-width='%5' fill='none' />")
1485 			.arg(l)
1486 			.arg(t)
1487 			.arg(w)
1488 			.arg(h)
1489 			.arg(1 * dpi / printerScale)
1490 			.arg(stroke);
1491 }
1492 
1493 
makePolySVG(const QPolygonF & poly,QPointF offset,double width,QString colorString,double dpi,double printerScale,bool blackOnly)1494 QString TextUtils::makePolySVG(const QPolygonF & poly, QPointF offset, double width, QString colorString, double dpi, double printerScale, bool blackOnly)
1495 {
1496 	QString polyString = QString("<polyline stroke-linecap='round' stroke-linejoin='round' fill='none' stroke='%1' stroke-width='%2' points='\n").arg(blackOnly ? "black" : colorString).arg(width);
1497 	int space = 0;
1498 	foreach (QPointF p, poly) {
1499 		polyString += QString("%1,%2 %3")
1500 			.arg((p.x() - offset.x()) * dpi / printerScale)
1501 			.arg((p.y() - offset.y()) * dpi / printerScale)
1502 			.arg((++space % 8 == 0) ?  "\n" : "");
1503 	}
1504 	polyString += "'/>\n";
1505 	return polyString;
1506 }
1507 
polygonFromElement(QDomElement & element)1508 QPolygonF TextUtils::polygonFromElement(QDomElement & element)
1509 {
1510 	QPolygonF poly;
1511 	QDomElement point = element.firstChildElement("point");
1512 	while (!point.isNull()) {
1513 		double x = point.attribute("x").toDouble();
1514 		double y = point.attribute("y").toDouble();
1515 		poly.append(QPointF(x, y));
1516 		point = point.nextSiblingElement("point");
1517 	}
1518 	return poly;
1519 }
1520 
pointToSvgString(QPointF p,QPointF offset,double dpi,double printerScale)1521 QString TextUtils::pointToSvgString(QPointF p, QPointF offset, double dpi, double printerScale)
1522 {
1523 	QString point;
1524 	point += QString::number((p.x() - offset.x()) * dpi / printerScale);
1525 	point += ",";
1526 	point += QString::number((p.y() - offset.y()) * dpi / printerScale);
1527 	return point;
1528 }
1529 
removeSVGHeader(QString & string)1530 QString TextUtils::removeSVGHeader(QString & string) {
1531 	int ix = string.indexOf("<svg");
1532 	if (ix < 0) return string;
1533 
1534 	ix = string.indexOf(">", ix);
1535 	if (ix < 0) return string;
1536 
1537 	int jx = string.indexOf("</svg>");
1538 	if (jx < 0) return string;
1539 
1540 	string.remove(jx, 6);
1541 	string.remove(0, ix + 1);
1542 	return string;
1543 }
1544 
1545 /*
1546 QString TextUtils::getMacAddress()
1547 {
1548 	// http://stackoverflow.com/questions/7609953/obtaining-mac-address-on-windows-in-qt
1549     foreach (QNetworkInterface interface, QNetworkInterface::allInterfaces())
1550     {
1551         // Return only the first non-loopback MAC Address
1552         if (!(interface.flags() & QNetworkInterface::IsLoopBack))
1553             return interface.hardwareAddress();
1554     }
1555     return QString();
1556 }
1557 */
1558 
expandAndFill(const QString & svg,const QString & color,double expandBy)1559 QString TextUtils::expandAndFill(const QString & svg, const QString & color, double expandBy)
1560 {
1561 	QDomDocument domDocument;
1562 	QString errorStr;
1563 	int errorLine;
1564 	int errorColumn;
1565 	bool result = domDocument.setContent(svg, &errorStr, &errorLine, &errorColumn);
1566 	if (!result) {
1567 		return "";
1568 	}
1569 
1570 	QDomElement root = domDocument.documentElement();
1571 	expandAndFillAux(root, color, expandBy);
1572 
1573 	return domDocument.toString();
1574 }
1575 
expandAndFillAux(QDomElement & element,const QString & color,double expandBy)1576 void TextUtils::expandAndFillAux(QDomElement & element, const QString & color, double expandBy)
1577 {
1578 	bool gotChildren = false;
1579 	QDomElement child = element.firstChildElement();
1580 	while (!child.isNull()) {
1581 		gotChildren = true;
1582 		expandAndFillAux(child, color, expandBy);
1583 		child = child.nextSiblingElement();
1584 	}
1585 
1586 	if (gotChildren) return;
1587 
1588 	QString fill = element.attribute("fill");
1589 	QString stroke = element.attribute("stroke");
1590 	QString strokeWidth = element.attribute("stroke-width");
1591 	if (stroke.isEmpty() && fill.isEmpty()) {
1592 		return;
1593 	}
1594 
1595 	element.setAttribute("fill", color);
1596 	element.setAttribute("stroke", color);
1597 
1598 	if (strokeWidth.isEmpty()) {
1599 		strokeWidth = "0";
1600 	}
1601 
1602 	double sw = strokeWidth.toDouble();
1603 	sw += expandBy;
1604 	element.setAttribute("stroke-width", QString::number(sw));
1605 
1606 }
1607 
writeUtf8(const QString & fileName,const QString & text)1608 bool TextUtils::writeUtf8(const QString & fileName, const QString & text)
1609 {
1610 	QFile file(fileName);
1611 	if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
1612 		QTextStream out(&file);
1613 		out.setCodec("UTF-8");
1614 		out << text;
1615 		file.close();
1616         return true;
1617     }
1618 
1619     return false;
1620 }
1621 
getPinsAndSpacing(const QString & expectedFileName,QString & spacingString)1622 int TextUtils::getPinsAndSpacing(const QString & expectedFileName, QString & spacingString)
1623 {
1624     QStringList pieces = expectedFileName.split("_");
1625     int pix = 0;
1626     foreach (QString piece, pieces) {
1627         bool ok;
1628         piece.toInt(&ok);
1629         if (ok) break;
1630 
1631         pix++;
1632     }
1633     if (pix >= pieces.count()) return 0;
1634 
1635 	int pins = pieces.at(pix).toInt();
1636 
1637 	spacingString = "100mil";
1638     for (++pix; pix < pieces.count(); pix++) {
1639         if (QRegExp("\\d").indexIn(pieces.at(pix)) == 0) {
1640             spacingString = pieces.at(pix);
1641             return pins;
1642         }
1643     }
1644 
1645     return pins;
1646 }
1647 
parseForWidthAndHeight(const QString & svg)1648 QSizeF TextUtils::parseForWidthAndHeight(const QString & svg)
1649 {
1650 	QRectF viewBox;
1651     return parseForWidthAndHeight(svg, viewBox, false);
1652 }
1653 
parseForModuleID(const QString & fzp)1654 QString TextUtils::parseForModuleID(const QString & fzp)
1655 {
1656 	QXmlStreamReader streamReader(fzp);
1657     streamReader.setNamespaceProcessing(false);
1658 
1659 	while (!streamReader.atEnd()) {
1660         switch (streamReader.readNext()) {
1661             case QXmlStreamReader::StartElement:
1662 			    if (streamReader.name().toString().compare("module") == 0) {
1663 				    return streamReader.attributes().value("moduleId").toString();
1664                 }
1665                 break;
1666             default:
1667                 break;
1668         }
1669     }
1670 
1671     return "";
1672 }
1673 
parseForWidthAndHeight(const QString & svg,QRectF & viewBox,bool getViewBox)1674 QSizeF TextUtils::parseForWidthAndHeight(const QString & svg, QRectF & viewBox, bool getViewBox)
1675 {
1676 	QXmlStreamReader streamReader(svg);
1677     return parseForWidthAndHeight(streamReader, viewBox, getViewBox);
1678 }
1679 
parseForWidthAndHeight(QXmlStreamReader & svg)1680 QSizeF TextUtils::parseForWidthAndHeight(QXmlStreamReader & svg)
1681 {
1682 	QRectF viewBox;
1683     return parseForWidthAndHeight(svg, viewBox, false);
1684 }
1685 
parseForWidthAndHeight(QXmlStreamReader & svg,QRectF & viewBox,bool getViewBox)1686 QSizeF TextUtils::parseForWidthAndHeight(QXmlStreamReader & svg, QRectF & viewBox, bool getViewBox)
1687 {
1688     svg.setNamespaceProcessing(false);
1689 
1690 	QSizeF size(0, 0);
1691 	viewBox.setRect(0, 0, 0, 0);
1692 
1693 	bool isIllustrator = false;
1694 	bool bad = false;
1695 
1696 	while (!svg.atEnd() && !bad) {
1697         switch (svg.readNext()) {
1698 		case QXmlStreamReader::Comment:
1699 			if (!isIllustrator) {
1700 				isIllustrator = TextUtils::isIllustratorFile(svg.text().toString());
1701 			}
1702 			break;
1703         case QXmlStreamReader::StartElement:
1704 			if (svg.name().toString().compare("svg") == 0) {
1705 				QString ws = svg.attributes().value("width").toString();
1706 				QString hs = svg.attributes().value("height").toString();
1707 				bool okw, okh;
1708 				double w = TextUtils::convertToInches(ws, &okw, isIllustrator);
1709 				double h = TextUtils::convertToInches(hs, &okh, isIllustrator);
1710 				if (!okw || qIsNaN(w) || qIsInf(w) || !okh || qIsNaN(h) || qIsInf(h)) {
1711 					bad = true;
1712 					break;
1713 				}
1714 
1715 				size.setWidth(w);
1716 				size.setHeight(h);
1717 
1718                 if (getViewBox) {
1719                     bool gotViewBox = false;
1720 				    QString vb = svg.attributes().value("viewBox").toString();
1721 				    QStringList vbs = vb.split(QRegExp(",| "));
1722 				    if (vbs.count() == 4) {
1723 					    bool ok = false;
1724 					    double d[4];
1725 					    for (int i = 0; i < 4; i++) {
1726 						    d[i] = vbs.at(i).toDouble(&ok);
1727 						    if (!ok) break;
1728 					    }
1729 					    if (ok) {
1730                             gotViewBox = true;
1731 						    viewBox.setRect(d[0], d[1], d[2], d[3]);
1732 					    }
1733 				    }
1734 
1735                     if (!gotViewBox) {
1736                         chopNotDigits(hs);
1737                         chopNotDigits(ws);
1738                         viewBox.setRect(0, 0, ws.toDouble(), hs.toDouble());
1739                     }
1740                 }
1741 
1742 				return size;
1743 			}
1744 			break;
1745 		default:
1746 			break;
1747 		}
1748 	}
1749 
1750     return size;
1751 }
1752 
gornTree(QDomDocument & doc)1753 void TextUtils::gornTree(QDomDocument & doc)
1754 {
1755     QDomElement root = doc.documentElement();
1756     QString oldid = root.attribute("id");
1757     root.setAttribute("id", "0");
1758     root.setAttribute("gorn", "0");
1759     root.setAttribute("oldid", oldid);
1760     gornTreeAux(root);
1761 }
1762 
gornTreeAux(QDomElement & root)1763 void TextUtils::gornTreeAux(QDomElement & root) {
1764     QString prefix = root.attribute("id");
1765     QDomElement child = root.firstChildElement();
1766     int index = 0;
1767     while (!child.isNull()) {
1768         QString oldid = child.attribute("id");
1769         QString newid = QString("%1.%2").arg(prefix).arg(index++);
1770         child.setAttribute("id", newid);
1771         child.setAttribute("gorn", newid);
1772         child.setAttribute("oldid", oldid);
1773         gornTreeAux(child);
1774         child = child.nextSiblingElement();
1775     }
1776 }
1777 
elevateTransform(QDomElement & root)1778 bool TextUtils::elevateTransform(QDomElement & root) {
1779     QList<QDomElement> transforms;
1780     collectTransforms(root, transforms);
1781     if (transforms.length() == 0) return false;
1782 
1783     foreach (QDomElement element, transforms) {
1784         QString transform = element.attribute("transform");
1785         element.removeAttribute("transform");
1786         QDomElement g = element.ownerDocument().createElement("g");
1787         g.setAttribute("transform", transform);
1788         element.parentNode().insertBefore(g, element);
1789         g.appendChild(element);
1790     }
1791 
1792     return true;
1793 }
1794 
collectTransforms(QDomElement & root,QList<QDomElement> & transforms)1795 void TextUtils::collectTransforms(QDomElement & root, QList<QDomElement> & transforms) {
1796     QString transform = root.attribute("transform");
1797     if (!transform.isEmpty()) {
1798         transforms.append(root);
1799     }
1800 
1801     QDomElement child = root.firstChildElement();
1802     while (!child.isNull()) {
1803         collectTransforms(child, transforms);
1804         child = child.nextSiblingElement();
1805     }
1806 }
1807 
fixFonts(QString & svg,const QString & destFont,bool & reallyFixed)1808 bool TextUtils::fixFonts(QString & svg, const QString & destFont, bool & reallyFixed) {
1809 	bool changed = removeFontFamilySingleQuotes(svg);
1810     reallyFixed = fixUnavailableFontFamilies(svg, destFont);
1811 	changed |= reallyFixed;
1812 	return changed;
1813 }
1814 
fixStyleAttribute(QDomElement & element)1815 void TextUtils::fixStyleAttribute(QDomElement & element)
1816 {
1817 	QString style = element.attribute("style");
1818 	if (style.isEmpty()) return;
1819 
1820 	fixStyleAttribute(element, style, "stroke-width");
1821 	fixStyleAttribute(element, style, "stroke");
1822 	fixStyleAttribute(element, style, "fill");
1823 	fixStyleAttribute(element, style, "fill-opacity");
1824 	fixStyleAttribute(element, style, "stroke-opacity");
1825 	fixStyleAttribute(element, style, "font-size");
1826 
1827 	if (style.trimmed().isEmpty()) {
1828 		element.removeAttribute("style");
1829 	}
1830 	else {
1831 		element.setAttribute("style", style);
1832 	}
1833 
1834 	//QString deleteMe;
1835 	//QTextStream stream(&deleteMe);
1836 	//stream << element;
1837 	//DebugDialog::debug(deleteMe);
1838 }
1839 
fixStyleAttribute(QDomElement & element,QString & style,const QString & attributeName)1840 void TextUtils::fixStyleAttribute(QDomElement & element, QString & style, const QString & attributeName)
1841 {
1842     static const QString findStyle("%1[\\s]*:[\\s]*([^;]*)[;]?");
1843 
1844 	QString str = findStyle.arg(attributeName);
1845 	QRegExp sw(str);
1846 	if (sw.indexIn(style) >= 0) {
1847 		QString value = sw.cap(1);
1848 		style.remove(sw);
1849 		element.setAttribute(attributeName, value);
1850 	}
1851 }
1852 
getRandText()1853 QString TextUtils::getRandText() {
1854 	QString rand = QUuid::createUuid().toString();
1855 	QString randext = QCryptographicHash::hash(rand.toLatin1(),QCryptographicHash::Md4).toHex();
1856 	return randext;
1857 }
1858 
1859 /*QString TextUtils::getBase64RandText() {
1860 	QString rand = QUuid::createUuid().toString();
1861 	QString randext = QCryptographicHash::hash(rand.toLatin1(),QCryptographicHash::Md4).toHex();
1862 	return randext;
1863 }*/
1864 
1865 
ensureViewBox(QDomDocument doc,double dpi,QRectF & rect,bool convert,double & w,double & h,bool getwh)1866 bool TextUtils::ensureViewBox(QDomDocument doc, double dpi, QRectF & rect, bool convert, double & w, double & h, bool getwh) {
1867     bool isIllustrator = TextUtils::isIllustratorDoc(doc);
1868 
1869     QDomElement root = doc.documentElement();
1870 	QString viewBox = root.attribute("viewBox");
1871 	if (viewBox.isEmpty() || getwh) {
1872 		bool ok1, ok2;
1873         if (convert) {
1874 		    w = TextUtils::convertToInches(root.attribute("width"), &ok1, isIllustrator) * dpi;
1875 		    h = TextUtils::convertToInches(root.attribute("height"), &ok2, isIllustrator) * dpi;
1876         }
1877         else {
1878 		    w = root.attribute("width").toDouble(&ok1);
1879 		    h = root.attribute("height").toDouble(&ok2);
1880         }
1881 		if (!ok1 || !ok2) {
1882 			return false;
1883 		}
1884     }
1885 
1886 	if (viewBox.isEmpty()) {
1887 		root.setAttribute("viewBox", QString("0 0 %1 %2").arg(w).arg(h));
1888         rect.setRect(0, 0, w, h);
1889         return true;
1890 	}
1891 
1892     QStringList coords = viewBox.split(QRegExp(" |,"));
1893     if (coords.count() != 4) return false;
1894 
1895     rect.setRect(coords.at(0).toDouble(), coords.at(1).toDouble(), coords.at(2).toDouble(), coords.at(3).toDouble());
1896     return true;
1897 }
1898 
findAnchor(const QDomElement & text)1899 QString TextUtils::findAnchor(const QDomElement & text) {
1900     if (text.isNull()) return "start";
1901 
1902     QString anchor = text.attribute("text-anchor");
1903     if (!anchor.isEmpty()) return anchor;
1904 
1905     return findAnchor(text.parentNode().toElement());
1906 }
1907 
resplit(QStringList & names,const QString & split)1908 void TextUtils::resplit(QStringList & names, const QString & split) {
1909     QStringList result;
1910     QString appender = split;
1911     if (appender == " ") appender = "";
1912     foreach (QString name, names) {
1913         QStringList sub = name.split(split, QString::SkipEmptyParts);
1914         for (int i = 0; i < sub.count(); i++) {
1915             QString s = sub.at(i);
1916             if (i < sub.count() - 1) s.append(appender);
1917             result.append(s);
1918         }
1919     }
1920     names.clear();
1921     names.append(result);
1922 }
1923 
1924