1 #include "s2s.h"
2 
3 #include "stdio.h"
4 
5 #include <QStringList>
6 #include <QTextStream>
7 #include <QFile>
8 #include <QDir>
9 #include <QFileInfo>
10 #include <QtDebug>
11 #include <QRegExp>
12 #include <qmath.h>
13 #include <QDomNodeList>
14 #include <QDomDocument>
15 #include <QDomElement>
16 #include <QSvgRenderer>
17 #include <QFontDatabase>
18 #include <QFont>
19 #include <QFontInfo>
20 #include <QResource>
21 #include <QImage>
22 #include <QPainter>
23 #include <QBitArray>
24 
25 #include "../../src/utils/textutils.h"
26 #include "../../src/utils/schematicrectconstants.h"
27 
28 #include <limits>
29 
30 /////////////////////////////////
31 
xLessThan(ConnectorLocation * cl1,ConnectorLocation * cl2)32 bool xLessThan(ConnectorLocation * cl1, ConnectorLocation * cl2)
33 {
34     return cl1->terminalPoint.x() < cl2->terminalPoint.x();
35 }
36 
yLessThan(ConnectorLocation * cl1,ConnectorLocation * cl2)37 bool yLessThan(ConnectorLocation * cl1, ConnectorLocation * cl2)
38 {
39     return cl1->terminalPoint.y() < cl2->terminalPoint.y();
40 }
41 
getY(const QPointF & p)42 double getY(const QPointF & p) {
43     return p.y();
44 }
45 
getX(const QPointF & p)46 double getX(const QPointF & p) {
47     return p.x();
48 }
49 
setHiddenAux(QList<ConnectorLocation * > & allConnectorLocations,QList<ConnectorLocation * > & sideConnectorLocations,double (* get)(const QPointF &),double fudge)50 void setHiddenAux(QList<ConnectorLocation *> & allConnectorLocations, QList<ConnectorLocation *> & sideConnectorLocations, double (*get)(const QPointF &), double fudge)
51 {
52     int i = 0;
53     while (i < sideConnectorLocations.count() - 1) {
54         QList<ConnectorLocation *> same;
55         ConnectorLocation * basis = sideConnectorLocations.at(i);
56         same << basis;
57         for (int j = i + 1; j < sideConnectorLocations.count(); j++) {
58             i = j;
59             ConnectorLocation * next = sideConnectorLocations.at(j);
60             if (qAbs(get(basis->terminalPoint )- get(next->terminalPoint)) < fudge) {
61                 same << next;
62             }
63             else {
64                 break;
65             }
66         }
67         if (same.count() > 1) {
68             foreach (ConnectorLocation * connectorLocation, same) {
69                 connectorLocation->hidden = true;
70             }
71             for (int ix = allConnectorLocations.count() - 1; ix >= 0; ix--) {
72                 ConnectorLocation * that = allConnectorLocations.at(ix);
73                 if (same.contains(that)) {
74                     // assume the topmost is the one at the end of allConnectorLocations
75                     // the rest are hidden
76                     that->hidden = false;
77                     sideConnectorLocations.removeOne(that);
78                     int maxIndex = -1;
79                     foreach (ConnectorLocation * s, same) {
80                         int ix = sideConnectorLocations.indexOf(s);
81                         if (ix > maxIndex) maxIndex = ix;
82                     }
83                     // visible one should be the topmost of the set
84                     sideConnectorLocations.insert(maxIndex + 1, that);
85                     break;
86                 }
87             }
88         }
89     }
90 
91 }
92 
93 /////////////////////////////////
94 
95 static QRegExp IntegerFinder("\\d+");
96 static const double ImageFactor = 5;
97 static const double FudgeDivisor = 300;
98 static const QRegExp VersionRegexp("[ -_][vV][\\d]+");
99 
100 ///////////////////////////////////////////////////////
101 
makePinNumber(const ConnectorLocation * connectorLocation,double x1,double y1,double x2,double y2)102 QString makePinNumber(const ConnectorLocation * connectorLocation, double x1, double y1, double x2, double y2) {
103 
104     if (connectorLocation->id < 0) return "";
105     if (connectorLocation->hidden) return "";
106     if (!connectorLocation->displayPinNumber) return "";
107 
108     QString text;
109     double tx = 0;
110     double ty = 0;
111     if (x1 == x2) {
112         text += QString("<g transform='translate(%1,%2)'><g transform='rotate(%3)'>\n")
113 			        .arg(x2 - SchematicRectConstants::PinWidth + SchematicRectConstants::PinSmallTextVert)
114 			        .arg((y2 + y1) / 2)
115 			        .arg(270)
116                     ;
117     }
118     else {
119         tx = (x2 + x1) / 2;
120         ty =  y2 - SchematicRectConstants::PinWidth + SchematicRectConstants::PinSmallTextVert;
121     }
122 
123     text += QString("<text class='text' font-family=\"%8\" stroke='none' stroke-width='%6' fill='%7' font-size='%1' x='%2' y='%3' text-anchor='%4'>%5</text>\n")
124 					.arg(SchematicRectConstants::PinSmallTextHeight)
125 					.arg(tx)
126 					.arg(ty)
127 					.arg("middle")
128 					.arg(connectorLocation->id)
129 					.arg(0)  // SW(width)
130 					.arg(SchematicRectConstants::PinTextColor)
131                     .arg(SchematicRectConstants::FontFamily)
132                     ;
133 
134     if (x1 == x2) {
135 		text += "</g></g>\n";
136     }
137 
138 
139     return text;
140 }
141 
makePinText(const ConnectorLocation * connectorLocation,double x1,double y1,double x2,double y2,bool anchorAtStart)142 QString makePinText(const ConnectorLocation * connectorLocation, double x1, double y1, double x2, double y2, bool anchorAtStart)
143 {
144     if (connectorLocation->hidden) return "";
145 
146     QString text;
147 
148     bool rotate = false;
149     double xOffset = 0, yOffset = 0;
150     if (x1 == x2) {
151         rotate = true;
152         yOffset = (anchorAtStart ? -SchematicRectConstants::PinTextIndent : SchematicRectConstants::PinTextIndent);
153         xOffset = SchematicRectConstants::PinTextVert;
154     }
155     else if (y1 == y2) {
156         // horizontal pin
157         xOffset = (anchorAtStart ? SchematicRectConstants::PinTextIndent : -SchematicRectConstants::PinTextIndent);
158         yOffset = SchematicRectConstants::PinTextVert;
159     }
160     else {
161         return "";
162     }
163 
164     if (rotate) {
165 		text += QString("<g transform='translate(%1,%2)'><g transform='rotate(%3)'>\n")
166 			.arg(x2 + xOffset)
167 			.arg(y2 + yOffset)
168 			.arg(270);
169 		x2 = 0;
170 		y2 = 0;
171         xOffset = yOffset = 0;
172 	}
173 
174 	text += QString("<text class='text' font-family=\"%8\" stroke='none' stroke-width='%6' fill='%7' font-size='%1' x='%2' y='%3' text-anchor='%4'>%5</text>\n")
175 						.arg(SchematicRectConstants::PinBigTextHeight)
176 						.arg(x2 + xOffset)
177 						.arg(y2 + yOffset)
178 						.arg(anchorAtStart ? "start" : "end")
179 						.arg(TextUtils::escapeAnd(connectorLocation->name))
180 						.arg(0)  // SW(width)
181 						.arg(SchematicRectConstants::PinTextColor)
182                         .arg(SchematicRectConstants::FontFamily)
183                         ;
184 
185     if (rotate) {
186 		text += "</g></g>\n";
187 	}
188 
189     return text;
190 }
191 
makePin(const ConnectorLocation * connectorLocation,double x1,double y1,double x2,double y2)192 QString makePin(const ConnectorLocation * connectorLocation, double x1, double y1, double x2, double y2) {
193     return QString("<line class='pin' x1='%1' y1='%2' x2='%3' y2='%4' fill='none' stroke='%5' stroke-width='%6' stroke-linecap='round' id='%7' />\n")
194             .arg(x1)
195             .arg(y1)
196             .arg(x2)
197             .arg(y2)
198             .arg(connectorLocation->hidden ? "none" : SchematicRectConstants::PinColor)
199             .arg(connectorLocation->hidden ? 0 : SchematicRectConstants::PinWidth)
200             .arg(connectorLocation->svgID)
201             ;
202 }
203 
makeTerminal(const ConnectorLocation * connectorLocation,double x,double y)204 QString makeTerminal(const ConnectorLocation * connectorLocation, double x, double y) {
205     return QString("<rect class='terminal' x='%1' y='%2' width='%3' height='%4' fill='none' stroke='none' stroke-width='0' id='%5' />\n")
206             .arg(x - (SchematicRectConstants::PinWidth / 2))
207             .arg(y - (SchematicRectConstants::PinWidth / 2))
208             .arg(SchematicRectConstants::PinWidth)
209             .arg(SchematicRectConstants::PinWidth)
210             .arg(connectorLocation->terminalID)
211             ;
212 }
213 
214 ///////////////////////////////////////////////////////
215 
216 
S2S(bool fzpzStyle)217 S2S::S2S(bool fzpzStyle) : QObject()
218 {
219     m_image = new QImage(50 * ImageFactor, 5 * ImageFactor, QImage::Format_Mono);
220     m_fzpzStyle = fzpzStyle;
221 }
222 
message(const QString & msg)223 void S2S::message(const QString & msg) {
224    // QTextStream cout(stdout);
225   //  cout << msg;
226    // cout.flush();
227 
228 	qDebug() << msg;
229     emit messageSignal(msg);
230 }
231 
saveFile(const QString & content,const QString & path)232 void S2S::saveFile(const QString & content, const QString & path)
233 {
234 	QFile file(path);
235 	if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
236 		QTextStream out(&file);
237 		out.setCodec("UTF-8");
238 		out << content;
239 		file.close();
240 	}
241 }
242 
onefzp(QString & fzpFilePath,QString & schematicFilePath)243 bool S2S::onefzp(QString & fzpFilePath, QString & schematicFilePath) {
244     if (!fzpFilePath.endsWith(".fzp")) {
245         return false;
246     }
247 
248     bool createSchematicFile = schematicFilePath.isEmpty();
249     QString newSchematicFilePath = schematicFilePath;
250 
251     m_lefts.clear();
252     m_rights.clear();
253     m_tops.clear();
254     m_bottoms.clear();
255 
256     QFile file(fzpFilePath);
257 
258 	QString errorStr;
259 	int errorLine;
260 	int errorColumn;
261 
262 	QDomDocument dom;
263 	if (!dom.setContent(&file, true, &errorStr, &errorLine, &errorColumn)) {
264 		message(tr("Failed loading '%1', %2 line:%3 col:%4").arg(fzpFilePath).arg(errorStr).arg(errorLine).arg(errorColumn));
265 		return false;
266 	}
267 
268     QDomElement root = dom.documentElement();
269     QDomElement titleElement = root.firstChildElement("title");
270     QString title;
271     TextUtils::findText(titleElement, title);
272     //int ix = VersionRegexp.lastIndexIn(title);
273     //if (ix > 0 && ix + VersionRegexp.cap(0).count() == title.count()) {
274     //    title.chop(VersionRegexp.cap(0).count());
275     //}
276     QStringList titles;
277     titles << title;
278     TextUtils::resplit(titles, " ");
279     TextUtils::resplit(titles, "_");
280     TextUtils::resplit(titles, "-");
281 
282 
283     if (createSchematicFile) {
284         QString schematicFileName;
285         QDomNodeList nodeList = root.elementsByTagName("schematicView");
286         for (int i = 0; i < nodeList.count(); i++) {
287             QDomElement schematicView = nodeList.at(i).toElement();
288             QDomElement layers = schematicView.firstChildElement("layers");
289             schematicFileName = layers.attribute("image");
290             if (!schematicFileName.isEmpty()) break;
291         }
292 
293         if (schematicFileName.isEmpty()) {
294 		    message(tr("Schematic not found for '%1'").arg(fzpFilePath));
295             return false;
296         }
297 
298         if (m_fzpzStyle) {
299              schematicFileName.replace("/", ".");
300              schematicFileName = "svg." + schematicFileName;
301         }
302 
303         schematicFilePath = m_oldSvgDir.absoluteFilePath(schematicFileName);
304         newSchematicFilePath = m_newSvgDir.absoluteFilePath(schematicFileName);
305     }
306 
307     if (!ensureTerminalPoints(fzpFilePath, schematicFilePath, root)) {
308         return false;
309     }
310 
311     qDebug() << schematicFilePath;
312 
313     QSvgRenderer renderer;
314     bool loaded = renderer.load(schematicFilePath);
315     if (!loaded) {
316 		message(tr("Unable to load schematic '%1' for '%2'").arg(schematicFilePath).arg(fzpFilePath));
317         return false;
318     }
319 
320     QList<ConnectorLocation *> connectorLocations = initConnectors(root, renderer, fzpFilePath, schematicFilePath);
321 
322     QRectF viewBox = renderer.viewBoxF();
323     if (viewBox.isEmpty()) {
324         qDebug() << "\tempty viewbox";
325     }
326     double oldUnit = lrtb(connectorLocations, viewBox);
327 
328     if (qAbs(oldUnit - SchematicRectConstants::NewUnit) < (SchematicRectConstants::NewUnit / 25)) {
329 		message(tr("Schematic '%1' is already using the 0.1inch standard.").arg(schematicFilePath));
330         return false;
331     }
332 
333     setHidden(connectorLocations);
334 
335     double minPinV = 0;
336     double maxPinV = 0;
337     if (m_lefts.count() && m_rights.count()) {
338         minPinV = qMin(m_lefts.first()->terminalPoint.y(), m_rights.first()->terminalPoint.y());
339         maxPinV = qMax(m_lefts.last()->terminalPoint.y(), m_rights.last()->terminalPoint.y());
340     }
341     else if (m_rights.count()) {
342         minPinV = m_rights.first()->terminalPoint.y();
343         maxPinV = m_rights.last()->terminalPoint.y();
344     }
345     else if (m_lefts.count()) {
346         minPinV = m_lefts.first()->terminalPoint.y();
347         maxPinV = m_lefts.last()->terminalPoint.y();
348     }
349     int vPinUnits = qRound((maxPinV - minPinV) / oldUnit) + 2;
350     int vUnits = vPinUnits;
351 
352     double minPinH = 0;
353     double maxPinH = 0;
354     if (m_bottoms.count() && m_tops.count()) {
355         minPinH = qMin(m_bottoms.first()->terminalPoint.x(), m_tops.first()->terminalPoint.x());
356         maxPinH = qMax(m_bottoms.last()->terminalPoint.x(), m_tops.last()->terminalPoint.x());
357     }
358     else if (m_bottoms.count()) {
359         minPinH = m_bottoms.first()->terminalPoint.x();
360         maxPinH = m_bottoms.last()->terminalPoint.x();
361     }
362     else if (m_tops.count()) {
363         minPinH = m_tops.first()->terminalPoint.x();
364         maxPinH = m_tops.last()->terminalPoint.x();
365     }
366     int hPinUnits = qRound((maxPinH - minPinH) / oldUnit) + 2;
367     int hUnits = hPinUnits;
368 
369 	double hPinTextMax = 0;
370 	for (int i = 0; i < m_lefts.count(); i++) {
371 		double w = stringWidthMM(SchematicRectConstants::PinBigTextHeight, m_lefts.at(i)->name);
372 		if (w > hPinTextMax) hPinTextMax = w;
373 	}
374 	for (int i = 0; i < m_rights.count(); i++) {
375 		double w = stringWidthMM(SchematicRectConstants::PinBigTextHeight, m_rights.at(i)->name);
376 		if (w > hPinTextMax) hPinTextMax = w;
377 	}
378     int leftTextUnits = qCeil(hPinTextMax / SchematicRectConstants::NewUnit);
379     int rightTextUnits = leftTextUnits;
380 
381 
382 	double vTopPinTextMax = 0;
383 	for (int i = 0; i < m_tops.count(); i++) {
384 		double w = stringWidthMM(SchematicRectConstants::PinBigTextHeight, m_tops.at(i)->name);
385 		if (w > vTopPinTextMax) vTopPinTextMax = w;
386 	}
387     int topTextUnits = qCeil(vTopPinTextMax / SchematicRectConstants::NewUnit);
388 	double vBottomPinTextMax = 0;
389 	for (int i = 0; i < m_bottoms.count(); i++) {
390 		double w = stringWidthMM(SchematicRectConstants::PinBigTextHeight, m_bottoms.at(i)->name);
391 		if (w > vBottomPinTextMax) vBottomPinTextMax = w;
392 	}
393     int bottomTextUnits = qCeil(vBottomPinTextMax / SchematicRectConstants::NewUnit);
394 
395     double labelTextMax = spaceTitle(titles, hUnits - leftTextUnits - rightTextUnits - 2);
396 
397     double textWidth = hPinTextMax + hPinTextMax + labelTextMax + (6 * SchematicRectConstants::PinTextIndent);   // PTI text PTI PTI title PTI PTI text PTI
398     double hTextUnits = qCeil(textWidth / SchematicRectConstants::NewUnit);
399     if (hTextUnits > hUnits) hUnits = hTextUnits;
400 
401     textWidth = titles.count() * (SchematicRectConstants::LabelTextHeight + SchematicRectConstants::LabelTextSpace);
402     double vTextUnits = qCeil((textWidth / SchematicRectConstants::NewUnit) + (2 * SchematicRectConstants::PinTextIndent));
403     if (vTextUnits > vUnits) vUnits = vTextUnits;
404 
405     vUnits += topTextUnits;
406     vUnits += bottomTextUnits;
407 
408     double fullWidth = hUnits;
409     if (m_lefts.count()) fullWidth += 2;
410     if (m_rights.count()) fullWidth += 2;
411     double fullHeight = vUnits;
412     if (m_tops.count()) fullHeight += 2;
413     if (m_bottoms.count()) fullHeight += 2;
414 
415 
416 
417     // construct svg
418 
419     QString svg = TextUtils::makeSVGHeader(25.4, 25.4, fullWidth * SchematicRectConstants::NewUnit, fullHeight * SchematicRectConstants::NewUnit);
420     double rectL = SchematicRectConstants::RectStrokeWidth / 2;
421     double rectT = rectL;
422     if (m_lefts.count()) rectL += 2 * SchematicRectConstants::NewUnit;
423     if (m_tops.count()) rectT += 2 * SchematicRectConstants::NewUnit;
424     svg += "<g id='schematic'>\n";
425     svg += QString("<rect class='interior rect' x='%1' y='%2' width='%3' height='%4' fill='%5' stroke='%6' stroke-width='%7' stroke-linecap='round' />\n")
426                 .arg(rectL)
427                 .arg(rectT)
428                 .arg((hUnits * SchematicRectConstants::NewUnit) - SchematicRectConstants::RectStrokeWidth)
429                 .arg((vUnits * SchematicRectConstants::NewUnit) - SchematicRectConstants::RectStrokeWidth)
430                 .arg(SchematicRectConstants::RectFillColor)
431                 .arg(SchematicRectConstants::RectStrokeColor)
432                 .arg(SchematicRectConstants::RectStrokeWidth)
433                 ;
434 
435     double vPinOffset = 0;
436     if (m_tops.count()) vPinOffset = 2 * SchematicRectConstants::NewUnit;
437     vPinOffset += topTextUnits * SchematicRectConstants::NewUnit;
438     int space = vUnits - vPinUnits - topTextUnits - bottomTextUnits;
439     if (space > 1) {
440         vPinOffset += (space / 2) * SchematicRectConstants::NewUnit;
441     }
442 
443     foreach (ConnectorLocation * connectorLocation, m_rights) {
444         int units = qRound((connectorLocation->terminalPoint.y() - minPinV) / oldUnit) + 1;
445         double y = units * SchematicRectConstants::NewUnit + vPinOffset;
446         double x1 = ((fullWidth - 2) * SchematicRectConstants::NewUnit) - (SchematicRectConstants::PinWidth / 2);
447         double x2 = (fullWidth * SchematicRectConstants::NewUnit) - (SchematicRectConstants::PinWidth / 2);
448         svg += makePin(connectorLocation, x1, y, x2, y);
449         svg += makePinNumber(connectorLocation, x1, y, x2, y);
450         svg += makePinText(connectorLocation, x2, y, x1, y, false);
451         svg += makeTerminal(connectorLocation, fullWidth * SchematicRectConstants::NewUnit, y);
452     }
453 
454     foreach (ConnectorLocation * connectorLocation, m_lefts) {
455         int units = qRound((connectorLocation->terminalPoint.y() - minPinV) / oldUnit) + 1;
456         double y = units * SchematicRectConstants::NewUnit + vPinOffset;
457         double x1 = SchematicRectConstants::PinWidth / 2;
458         double x2 = (2 * SchematicRectConstants::NewUnit) + (SchematicRectConstants::PinWidth / 2);
459         svg += makePin(connectorLocation, x1, y, x2, y);
460         svg += makePinNumber(connectorLocation, x1, y, x2, y);
461         svg += makePinText(connectorLocation, x1, y, x2, y, true);
462         svg += makeTerminal(connectorLocation, 0, y);
463     }
464 
465     double hPinOffset = 0;
466     if (m_lefts.count()) hPinOffset = 2 * SchematicRectConstants::NewUnit;
467     space = hUnits - hPinUnits;
468     if (space > 1) {
469         hPinOffset += (space / 2) * SchematicRectConstants::NewUnit;
470     }
471 
472     foreach (ConnectorLocation * connectorLocation, m_bottoms) {
473         int units = qRound((connectorLocation->terminalPoint.x() - minPinH) / oldUnit) + 1;
474         double x = units * SchematicRectConstants::NewUnit + hPinOffset;
475         double y1 = ((fullHeight - 2) * SchematicRectConstants::NewUnit) - (SchematicRectConstants::PinWidth / 2);
476         double y2 = (fullHeight * SchematicRectConstants::NewUnit) - (SchematicRectConstants::PinWidth / 2);
477         svg += makePin(connectorLocation, x, y1, x, y2);
478         svg += makePinNumber(connectorLocation, x, y1, x, y2);
479         svg += makePinText(connectorLocation, x, y2, x, y1, true);
480         svg += makeTerminal(connectorLocation, x, fullHeight * SchematicRectConstants::NewUnit);
481     }
482 
483     foreach (ConnectorLocation * connectorLocation, m_tops) {
484         int units = qRound((connectorLocation->terminalPoint.x() - minPinH) / oldUnit) + 1;
485         double x = units * SchematicRectConstants::NewUnit + hPinOffset;
486         double y1 =  SchematicRectConstants::PinWidth / 2;
487         double y2 = (2 * SchematicRectConstants::NewUnit) + (SchematicRectConstants::PinWidth / 2);
488         svg += makePin(connectorLocation, x, y1, x, y2);
489         svg += makePinNumber(connectorLocation, x, y1, x, y2);
490         svg += makePinText(connectorLocation, x, y1, x, y2, false);
491         svg += makeTerminal(connectorLocation, x, 0);
492     }
493 
494     double y = vUnits * SchematicRectConstants::NewUnit / 2;
495     y -= titles.count() * (SchematicRectConstants::LabelTextHeight + SchematicRectConstants::LabelTextSpace) / 2;
496     y += rectT;
497     y = qMax(y, rectT + (topTextUnits * SchematicRectConstants::NewUnit) + SchematicRectConstants::LabelTextHeight + SchematicRectConstants::LabelTextSpace + SchematicRectConstants::PinTextIndent);  // y seems to be the location of the baseline so add a line
498     foreach (QString subTitle, titles) {
499         svg += QString("<text class='text' id='label' font-family=\"%7\" stroke='none' stroke-width='%4' fill='%5' font-size='%1' x='%2' y='%3' text-anchor='middle'>%6</text>\n")
500 				    .arg(SchematicRectConstants::LabelTextHeight)
501 				    .arg(((hUnits * SchematicRectConstants::NewUnit) / 2) + rectL)
502 				    .arg(y)
503 				    .arg(0)  // SW(width)
504 				    .arg(SchematicRectConstants::TitleColor)
505                     .arg(TextUtils::escapeAnd(subTitle))
506                     .arg(SchematicRectConstants::FontFamily)
507                     ;
508             y += (SchematicRectConstants::LabelTextHeight + SchematicRectConstants::LabelTextSpace);
509         }
510 
511 
512     svg +="</g>\n";
513     svg +="</svg>\n";
514 
515     TextUtils::writeUtf8(newSchematicFilePath, svg);
516     qDebug() << newSchematicFilePath;
517     qDebug() << "";
518     return true;
519 }
520 
521 
stringWidthMM(double fontSize,const QString & string)522 double S2S::stringWidthMM(double fontSize, const QString & string) {
523 
524     /*
525     // tried using FontMetrics but the result is always too short
526     QFont font("Droid Sans");
527 	font.setPointSizeF(fontSize * 72 / 25.4);
528 	QFontMetricsF fontMetrics(font);
529     double pixels = fontMetrics.width(string);
530     double mm = 25.4 * pixels / 90;
531     */
532 
533     QString svg = TextUtils::makeSVGHeader(25.4, 25.4, 50, 5);
534     svg += QString("<text font=\"%1\" font-size='%2' stroke='none' stroke-width='0' fill='black' x='0' y='%4' text-anchor='start' >%3</text>")
535         .arg(SchematicRectConstants::FontFamily).arg(fontSize).arg(TextUtils::escapeAnd(string)).arg(2.5);
536     svg += "</svg>";
537 
538     QSvgRenderer renderer(svg.toUtf8());
539     m_image->fill(0xffffffff);
540 	QPainter painter;
541 	painter.begin(m_image);
542 	painter.setRenderHint(QPainter::Antialiasing, false);
543     painter.setRenderHint(QPainter::SmoothPixmapTransform, false);
544 	renderer.render(&painter);
545 	painter.end();
546     //QDir dir(QCoreApplication::applicationDirPath());
547     //dir.cdUp();
548     //image.save(dir.absoluteFilePath("bloody_font.png"));
549     int bestX = 0;
550     for (int y = 0; y < m_image->height(); y++) {
551         for (int x = bestX; x < m_image->width(); x++) {
552             if (m_image->pixel(x, y) == 0xff000000) {
553                 bestX = x;
554             }
555         }
556     }
557 
558     //if (mm < bestX / ImageFactor) {
559     //    qDebug() << "string width" << mm << (bestX / ImageFactor) << string;
560     //}
561 
562     return bestX / ImageFactor;
563 }
564 
initConnectors(const QDomElement & root,const QSvgRenderer & renderer,const QString & fzpFilename,const QString & svgFilename)565 QList<ConnectorLocation *> S2S::initConnectors(const QDomElement & root, const QSvgRenderer & renderer, const QString & fzpFilename, const QString & svgFilename)
566 {
567     QList<ConnectorLocation *> connectorLocations;
568     m_minLeft = std::numeric_limits<int>::max();
569     m_minTop = std::numeric_limits<int>::max();
570     m_maxRight = std::numeric_limits<int>::min();
571     m_maxBottom = std::numeric_limits<int>::min();
572 
573     QDomElement connectors = root.firstChildElement("connectors");
574     QDomElement connector = connectors.firstChildElement("connector");
575     QBitArray idlist;
576     int noIDCount = 0;
577     while (!connector.isNull()) {
578         QDomElement schematicView = connector.firstChildElement("views").firstChildElement("schematicView");
579         QString svgID = schematicView.firstChildElement("p").attribute("svgId");
580         if (svgID.isEmpty()) {
581             message(tr("Missing connector %1 in '%2' schematic of '%3'").arg(connector.attribute("id")).arg(svgFilename).arg(fzpFilename));
582         }
583         else {
584             ConnectorLocation * connectorLocation = new ConnectorLocation;
585             connectorLocation->id = -1;
586             connectorLocation->hidden = false;
587             connectorLocation->displayPinNumber = false;
588             QString id = connector.attribute("id");
589             int ix = IntegerFinder.indexIn(id);
590             if (ix > 0) {
591                 connectorLocation->id = IntegerFinder.cap(0).toInt();
592                 if (idlist.size() < connectorLocation->id + 1) {
593                     int oldsize = idlist.size();
594                     idlist.resize(connectorLocation->id + 1);
595                     for (int i = oldsize; i < idlist.size(); i++) idlist.setBit(i, false);
596                 }
597                 idlist.setBit(connectorLocation->id, true);
598             }
599             else {
600                 noIDCount++;
601             }
602             connectorLocation->name = connector.attribute("name");
603             connectorLocation->terminalID = schematicView.firstChildElement("p").attribute("terminalId");
604             connectorLocation->svgID = svgID;
605             QRectF bounds = renderer.boundsOnElement(svgID);
606 		    QMatrix m = renderer.matrixForElement(svgID);
607 		    connectorLocation->bounds = m.mapRect(bounds);
608             connectorLocation->terminalPoint = connectorLocation->bounds.center();
609 
610             if (!connectorLocation->terminalID.isEmpty()) {
611                 bounds = renderer.boundsOnElement(connectorLocation->terminalID);
612                 m = renderer.matrixForElement(connectorLocation->terminalID);
613                 connectorLocation->terminalPoint = m.mapRect(bounds).center();
614             }
615             connectorLocations.append(connectorLocation);
616             if (connectorLocation->terminalPoint.x() < m_minLeft) {
617                 m_minLeft = connectorLocation->terminalPoint.x();
618             }
619             if (connectorLocation->terminalPoint.x() > m_maxRight) {
620                 m_maxRight = connectorLocation->terminalPoint.x();
621             }
622             if (connectorLocation->terminalPoint.y() < m_minTop) {
623                 m_minTop = connectorLocation->terminalPoint.y();
624             }
625             if (connectorLocation->terminalPoint.y() > m_maxBottom) {
626                 m_maxBottom = connectorLocation->terminalPoint.y();
627             }
628         }
629 
630         connector = connector.nextSiblingElement("connector");
631     }
632 
633     if (noIDCount > 0) {
634         qDebug() << "no id count" << noIDCount;
635     }
636 
637     bool display = true;
638     if (!idlist.at(0) && !idlist.at(1)) {
639         display = false;
640     }
641     else {
642         int present = 0;
643         for (int i = 0; i < idlist.size(); i++) {
644             if (idlist.at(i)) present++;
645         }
646         display = (present >= .66 * idlist.size());
647     }
648 
649     foreach (ConnectorLocation * connectorLocation, connectorLocations) {
650         connectorLocation->displayPinNumber = display;
651     }
652 
653     if (display && idlist.at(0)) {
654         foreach (ConnectorLocation * connectorLocation, connectorLocations) {
655             connectorLocation->id++;
656         }
657     }
658 
659     return connectorLocations;
660 }
661 
lrtb(QList<ConnectorLocation * > & connectorLocations,const QRectF & viewBox)662 double S2S::lrtb(QList<ConnectorLocation *> & connectorLocations, const QRectF & viewBox)
663 {
664     m_fudge = qMax(m_maxRight - m_minLeft, m_maxBottom - m_minTop) / FudgeDivisor;
665 
666     foreach (ConnectorLocation * connectorLocation, connectorLocations) {
667         double d[4];
668         d[0] = connectorLocation->terminalPoint.x() - viewBox.left();
669         d[1] = connectorLocation->terminalPoint.y() - viewBox.top();
670         d[2] = viewBox.right() - connectorLocation->terminalPoint.x();
671         d[3] = viewBox.bottom() - connectorLocation->terminalPoint.y();
672         int ix = 0;
673         int dx = d[0];
674         for (int i = 1; i < 4; i++) {
675             if (d[i] < dx) {
676                 dx = d[i];
677                 ix = i;
678             }
679         }
680         switch (ix) {
681             case 0:
682                 m_lefts << connectorLocation;
683                 break;
684             case 1:
685                 m_tops << connectorLocation;
686                 break;
687             case 2:
688                 m_rights << connectorLocation;
689                 break;
690             case 3:
691                 m_bottoms << connectorLocation;
692                 break;
693             default:
694                 qDebug() << "shouldn't happen" << ix;
695         }
696 
697     }
698 
699     qSort(m_lefts.begin(), m_lefts.end(), yLessThan);
700     qSort(m_rights.begin(), m_rights.end(), yLessThan);
701     qSort(m_tops.begin(), m_tops.end(), xLessThan);
702     qSort(m_bottoms.begin(), m_bottoms.end(), xLessThan);
703 
704     QList<double> distances;
705     for (int i = 1; i < m_lefts.count(); i++) {
706         ConnectorLocation * l1 = m_lefts.at(i - 1);
707         ConnectorLocation * l2 = m_lefts.at(i);
708         distances << l2->terminalPoint.y() - l1->terminalPoint.y();
709     }
710     for (int i = 1; i < m_rights.count(); i++) {
711         ConnectorLocation * l1 = m_rights.at(i - 1);
712         ConnectorLocation * l2 = m_rights.at(i);
713         distances << l2->terminalPoint.y() - l1->terminalPoint.y();
714     }
715     for (int i = 1; i < m_tops.count(); i++) {
716         ConnectorLocation * l1 = m_tops.at(i - 1);
717         ConnectorLocation * l2 = m_tops.at(i);
718         distances << l2->terminalPoint.x() - l1->terminalPoint.x();
719     }
720     for (int i = 1; i < m_bottoms.count(); i++) {
721         ConnectorLocation * l1 = m_bottoms.at(i - 1);
722         ConnectorLocation * l2 = m_bottoms.at(i);
723         distances << l2->terminalPoint.x() - l1->terminalPoint.x();
724     }
725 
726     qSort(distances.begin(), distances.end());
727 
728     int totalPins = m_lefts.count() + m_rights.count() + m_bottoms.count() + m_tops.count();
729 
730     int most = 0;
731     foreach (double distance, distances) {
732         int d = qRound(distance / m_fudge);
733         if (d > most) most = d;
734     }
735 
736     QVector<int> dbins(most + 2, 0);
737     foreach (double distance, distances) {
738         int d = qRound(distance / m_fudge);
739         dbins[d] += 1;
740     }
741 
742     int biggest = dbins[1];
743     int biggestIndex = 1;
744     for (int i = 2; i < dbins.count(); i++) {
745         if (dbins[i] > biggest) {
746             biggest = dbins[i];
747             biggestIndex = i;
748         }
749     }
750 
751     if (biggest == 0) {
752         qDebug() << "\tbiggest 0" << totalPins;
753     }
754 
755     double oldUnit = biggestIndex * m_fudge;
756 
757     qDebug() << QString("\tunit is roughly %1mm, bin:%2 pins:%3 fudge:%4").arg(oldUnit).arg(biggest).arg(totalPins).arg(m_fudge);
758     qDebug() << QString("\tl:%1 t:%2 r:%3 b:%4").arg(m_lefts.count()).arg(m_tops.count()).arg(m_rights.count()).arg(m_bottoms.count());
759 
760     return oldUnit;
761 }
762 
763 
setHidden(QList<ConnectorLocation * > & connectorLocations)764 void S2S::setHidden(QList<ConnectorLocation *> & connectorLocations)
765 {
766     setHiddenAux(connectorLocations, m_lefts, getY, m_fudge);
767     setHiddenAux(connectorLocations, m_rights, getY, m_fudge);
768     setHiddenAux(connectorLocations, m_tops, getX, m_fudge);
769     setHiddenAux(connectorLocations, m_bottoms, getX, m_fudge);
770 }
771 
ensureTerminalPoints(const QString & fzpFilePath,const QString & svgFilePath,QDomElement & fzpRoot)772 bool S2S::ensureTerminalPoints(const QString & fzpFilePath, const QString & svgFilePath, QDomElement & fzpRoot) {
773     QList<QDomElement> missing;
774     QDomElement connectors = fzpRoot.firstChildElement("connectors");
775     QDomElement connector = connectors.firstChildElement("connector");
776     while (!connector.isNull()) {
777         QDomElement views = connector.firstChildElement("views");
778         QDomElement schematicView = views.firstChildElement("schematicView");
779         QDomElement p = schematicView.firstChildElement("p");
780         QString terminalID = p.attribute("terminalId");
781         if (terminalID.isEmpty()) {
782             missing << p;
783         }
784 
785         connector = connector.nextSiblingElement("connector");
786     }
787 
788     if (missing.count() == 0) return true;
789 
790     QSvgRenderer renderer;
791     bool loaded = renderer.load(svgFilePath);
792     if (!loaded) {
793 		message(tr("Uunable to load schematic '%1' for '%2'").arg(svgFilePath).arg(fzpFilePath));
794         return false;
795     }
796 
797 	QString errorStr;
798 	int errorLine;
799 	int errorColumn;
800 
801     QFile file(svgFilePath);
802 	QDomDocument dom;
803 	if (!dom.setContent(&file, true, &errorStr, &errorLine, &errorColumn)) {
804 		message(tr("Failed loading schematic '%1', %2 line:%3 col:%4").arg(svgFilePath).arg(errorStr).arg(errorLine).arg(errorColumn));
805 		return false;
806 	}
807 
808     QDomElement svgRoot = dom.documentElement();
809 
810     bool fzpChanged = false;
811     bool svgChanged = false;
812 
813     foreach (QDomElement p, missing) {
814         QDomElement connector = p.parentNode().parentNode().parentNode().toElement();
815         QString name = connector.attribute("id");
816         if (name.isEmpty()) {
817             qDebug() << "empty name in connector";
818         }
819         else {
820             // assumes the file is well behaved, and the terminalID isn't already in use
821             QString terminalName = name + "terminal";
822             p.setAttribute("terminalId", terminalName);
823             fzpChanged = true;
824 
825             QDomElement terminalElement = TextUtils::findElementWithAttribute(svgRoot, "id", terminalName);
826             if (terminalElement.isNull()) {
827                 QString svgID = p.attribute("svgId");
828                 QDomElement connectorElement = TextUtils::findElementWithAttribute(svgRoot, "id", svgID);
829                 QRectF bounds = renderer.boundsOnElement(svgID);
830                 QDomElement rect = svgRoot.ownerDocument().createElement("rect");
831                 connectorElement.parentNode().insertAfter(rect, connectorElement);
832                 rect.setAttribute("x", QString::number(bounds.left()));
833                 rect.setAttribute("y", QString::number(bounds.top()));
834                 rect.setAttribute("width", QString::number(bounds.width()));
835                 rect.setAttribute("height", QString::number(bounds.height()));
836                 rect.setAttribute("fill", "none");
837                 rect.setAttribute("stroke", "none");
838                 rect.setAttribute("stroke-width", 0);
839                 rect.setAttribute("id", terminalName);
840                 svgChanged = true;
841             }
842         }
843     }
844 
845     if (svgChanged) {
846         TextUtils::writeUtf8(svgFilePath, TextUtils::removeXMLEntities(dom.toString(4)));
847     }
848     if (fzpChanged) {
849         TextUtils::writeUtf8(fzpFilePath, TextUtils::removeXMLEntities(fzpRoot.ownerDocument().toString(4)));
850     }
851 
852     return true;
853 }
854 
spaceTitle(QStringList & titles,int openUnits)855 double S2S::spaceTitle(QStringList & titles, int openUnits)
856 {
857     double labelTextMax = 0;
858     foreach (QString title, titles) {
859 		double w = stringWidthMM(SchematicRectConstants::LabelTextHeight, title);
860 		if (w > labelTextMax) labelTextMax = w;
861     }
862 
863     double useMax = qMax(openUnits * SchematicRectConstants::NewUnit, labelTextMax);
864 
865     bool changed = false;
866     int ix = 0;
867     while (ix < titles.count() - 1) {
868         QString combined = titles.at(ix);
869         if (!(combined.endsWith("_") || combined.endsWith("-"))) {
870             combined.append(" ");
871         }
872         combined.append(titles.at(ix + 1));
873         double w = stringWidthMM(SchematicRectConstants::LabelTextHeight, combined);
874         if (w <= useMax) {
875             titles[ix] = combined;
876             titles.removeAt(ix + 1);
877             changed = true;
878             continue;
879         }
880 
881         ix++;
882     }
883 
884     if (!changed) return labelTextMax;
885 
886     labelTextMax = 0;
887     foreach (QString title, titles) {
888 		double w = stringWidthMM(SchematicRectConstants::LabelTextHeight, title);
889 		if (w > labelTextMax) labelTextMax = w;
890     }
891     return labelTextMax;
892 }
893 
setSvgDirs(QDir & oldDir,QDir & newDir)894 void S2S::setSvgDirs(QDir & oldDir, QDir & newDir) {
895     m_oldSvgDir = oldDir;
896     m_newSvgDir = newDir;
897 }
898 
899