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];"); // 	 
 
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('\'', "'");
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