File indexing completed on 2024-05-12 04:35:07
0001 /* This file is part of the TikZKit project. 0002 * 0003 * Copyright (C) 2013 Dominik Haumann <dhaumann@kde.org> 0004 * 0005 * This library is free software; you can redistribute it and/or modify 0006 * it under the terms of the GNU Library General Public License as published 0007 * by the Free Software Foundation, either version 2 of the License, or 0008 * (at your option) any later version. 0009 * 0010 * This library is distributed in the hope that it will be useful, 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0013 * GNU Library General Public License for more details. 0014 * 0015 * You should have received a copy of the GNU Library General Public License 0016 * along with this library; see the file COPYING.LIB. If not, see 0017 * <http://www.gnu.org/licenses/>. 0018 */ 0019 0020 #include "TikzExportVisitor.h" 0021 0022 #include "Document.h" 0023 #include "Node.h" 0024 #include "Path.h" 0025 #include "EllipsePath.h" 0026 #include "EdgePath.h" 0027 #include "Style.h" 0028 0029 #include <QStringList> 0030 #include <QTextStream> 0031 #include <QMetaProperty> 0032 #include <QFile> 0033 #include <QDebug> 0034 #include <QHash> 0035 0036 namespace tikz { 0037 namespace core { 0038 0039 static QColor mixColor(const QColor & c1, const QColor & c2, qreal interp) 0040 { 0041 if (! c1.isValid() || ! c2.isValid()) { 0042 return QColor(); 0043 } 0044 0045 const auto p = interp; 0046 const auto q = 1 - interp; 0047 return QColor(qRound(p * c1.red() + q * c2.red()), 0048 qRound(p * c1.green() + q * c2.green()), 0049 qRound(p * c1.blue() + q * c2.blue())); 0050 } 0051 0052 static QString colorToString(const QColor & color) 0053 { 0054 static QHash<QRgb, QString> colorMap; 0055 if (colorMap.isEmpty()) { 0056 for (int i = 10; i < 100; i += 10) { 0057 const qreal f = i / 100.0; 0058 colorMap.insert(mixColor(Qt::black, Qt::white, f).rgb(), QString("black!%1").arg(i)); 0059 colorMap.insert(mixColor(Qt::cyan, Qt::white, f).rgb(), QString("cyan!%1").arg(i)); 0060 colorMap.insert(mixColor(Qt::cyan, Qt::black, f).rgb(), QString("cyan!%1!black").arg(i)); 0061 colorMap.insert(mixColor(Qt::red, Qt::white, f).rgb(), QString("red!%1").arg(i)); 0062 colorMap.insert(mixColor(Qt::red, Qt::black, f).rgb(), QString("red!%1!black").arg(i)); 0063 colorMap.insert(mixColor(Qt::magenta, Qt::white, f).rgb(), QString("magenta!%1").arg(i)); 0064 colorMap.insert(mixColor(Qt::magenta, Qt::black, f).rgb(), QString("magenta!%1!black").arg(i)); 0065 colorMap.insert(mixColor(Qt::green, Qt::white, f).rgb(), QString("green!%1").arg(i)); 0066 colorMap.insert(mixColor(Qt::green, Qt::black, f).rgb(), QString("green!%1!black").arg(i)); 0067 colorMap.insert(mixColor(Qt::yellow, Qt::white, f).rgb(), QString("yellow!%1").arg(i)); 0068 colorMap.insert(mixColor(Qt::yellow, Qt::black, f).rgb(), QString("yellow!%1!black").arg(i)); 0069 colorMap.insert(mixColor(Qt::blue, Qt::white, f).rgb(), QString("blue!%1").arg(i)); 0070 colorMap.insert(mixColor(Qt::blue, Qt::black, f).rgb(), QString("blue!%1!black").arg(i)); 0071 colorMap.insert(mixColor(QColor(255, 128, 0), Qt::white, f).rgb(), QString("orange!%1").arg(i)); 0072 colorMap.insert(mixColor(QColor(255, 128, 0), Qt::black, f).rgb(), QString("orange!%1!black").arg(i)); 0073 } 0074 0075 colorMap.insert(QColor(Qt::black).rgb(), "black"); 0076 colorMap.insert(QColor(128, 128, 128).rgb(), "gray"); 0077 colorMap.insert(QColor(64, 64, 64).rgb(), "darkgray"); 0078 colorMap.insert(QColor(191, 191, 191).rgb(), "lightgray"); 0079 colorMap.insert(QColor(Qt::white).rgb(), "white"); 0080 colorMap.insert(QColor(Qt::cyan).rgb(), "cyan"); 0081 colorMap.insert(QColor(Qt::red).rgb(), "red"); 0082 colorMap.insert(QColor(Qt::magenta).rgb(), "magenta"); 0083 colorMap.insert(QColor(Qt::green).rgb(), "green"); 0084 colorMap.insert(QColor(Qt::yellow).rgb(), "yellow"); 0085 colorMap.insert(QColor(Qt::blue).rgb(), "blue"); 0086 colorMap.insert(QColor(191, 128, 64).rgb(), "brown"); 0087 colorMap.insert(QColor(191, 255, 0).rgb(), "lime"); 0088 colorMap.insert(QColor(255, 191, 191).rgb(), "pink"); 0089 colorMap.insert(QColor(191, 0, 64).rgb(), "purple"); 0090 colorMap.insert(QColor(0, 128, 128).rgb(), "teal"); 0091 colorMap.insert(QColor(128, 0, 128).rgb(), "violet"); 0092 colorMap.insert(QColor(128, 128, 0).rgb(), "olive"); 0093 } 0094 0095 // try to find a smart colorname 0096 auto it = colorMap.find(color.rgb()); 0097 if (it != colorMap.end()) { 0098 return it.value(); 0099 } 0100 0101 // use generic fallback color schema 0102 return QString("{rgb,255:red,%1; green,%2; blue,%3}").arg(color.red()).arg(color.green()).arg(color.blue()); 0103 } 0104 0105 static QString lineWidthToString(const tikz::Value & lw) 0106 { 0107 if (lw == Value::ultraThin()) { 0108 return "ultra thin"; 0109 } else if (lw == Value::veryThin()) { 0110 return "very thin"; 0111 } else if (lw == Value::thin()) { 0112 return "thin"; 0113 } else if (lw == Value::semiThick()) { 0114 return "semithick"; 0115 } else if (lw == Value::thick()) { 0116 return "thick"; 0117 } else if (lw == Value::veryThick()) { 0118 return "very thick"; 0119 } else if (lw == Value::ultraThick()) { 0120 return "ultra thick"; 0121 } 0122 0123 return "line width=" + lw.toString(); 0124 } 0125 0126 TikzExportVisitor::TikzExportVisitor() 0127 : Visitor() 0128 { 0129 } 0130 0131 TikzExportVisitor::~TikzExportVisitor() 0132 { 0133 } 0134 0135 QString TikzExportVisitor::tikzCode() 0136 { 0137 return m_tikzExport.tikzCode(); 0138 } 0139 0140 void TikzExportVisitor::visit(Document * doc) 0141 { 0142 // 0143 // export the global options for the tikzpicture 0144 // 0145 QStringList options = styleOptions(doc->style()); 0146 options << "align=center"; // FIXME: temporary hack to make text wrap in nodes work. 0147 m_tikzExport.setDocumentOptions(options.join(", ")); 0148 } 0149 0150 void TikzExportVisitor::visit(Node * node) 0151 { 0152 QString options = nodeStyleOptions(node->style()).join(", "); 0153 0154 QString cmd = QString("\\node[%1,draw] (%2) at %3 {%4};") 0155 .arg(options) 0156 .arg(node->uid().toString()) 0157 .arg(node->pos().toString()) 0158 .arg(node->text()); 0159 0160 // 0161 // finally add node to picture 0162 // 0163 TikzLine line; 0164 line.contents = cmd; 0165 m_tikzExport.addTikzLine(line); 0166 } 0167 0168 void TikzExportVisitor::visit(Path * path) 0169 { 0170 QString options = edgeStyleOptions(path->style()).join(", "); 0171 if (!options.isEmpty()) { 0172 options = "[" + options + "]"; 0173 } 0174 0175 QString cmd; 0176 0177 switch (path->type()) { 0178 case PathType::Line: 0179 case PathType::HVLine: 0180 case PathType::VHLine: { 0181 auto edge = static_cast<tikz::core::EdgePath*>(path); 0182 // 0183 // compute start & end 0184 // 0185 const QString startCoord = edge->startMetaPos().toString(); 0186 const QString endCoord = edge->endMetaPos().toString(); 0187 0188 // 0189 // build connection string 0190 // 0191 QString to; 0192 switch (edge->type()) { 0193 case PathType::Line: to = "--"; break; 0194 case PathType::HVLine: to = "-|"; break; 0195 case PathType::VHLine: to = "|-"; break; 0196 default: Q_ASSERT(false); break; 0197 } 0198 cmd = "\\draw" + options + " " + startCoord + " " + to + " " + endCoord + ";"; 0199 } 0200 case PathType::BendCurve: { 0201 break; 0202 } 0203 case PathType::InOutCurve: { 0204 break; 0205 } 0206 case PathType::BezierCurve: { 0207 break; 0208 } 0209 case PathType::Ellipse: { 0210 auto ellipsePath = static_cast<tikz::core::EllipsePath*>(path); 0211 0212 // 0213 // compute center 0214 // 0215 const QString center = ellipsePath->metaPos().toString(); 0216 0217 // 0218 // compute radius 0219 // 0220 QString radius; 0221 if (ellipsePath->style()->radiusX() == ellipsePath->style()->radiusY()) { 0222 radius = "radius=" + ellipsePath->style()->radiusX().toString(); 0223 } else { 0224 radius = "x radius=" + ellipsePath->style()->radiusX().toString(); 0225 radius += ", y radius=" + ellipsePath->style()->radiusY().toString(); 0226 } 0227 0228 // 0229 // export rotation 0230 // 0231 if (ellipsePath->style()->rotationSet()) { 0232 radius += QString(", rotate=%1").arg(ellipsePath->style()->rotation()); 0233 } 0234 0235 // build path 0236 cmd = "\\draw" + options + " " + center + " circle [" + radius + "];"; 0237 break; 0238 } 0239 case PathType::Rectangle: { 0240 break; 0241 } 0242 case PathType::Grid: { 0243 break; 0244 } 0245 default: break; 0246 } 0247 0248 // 0249 // finally add path to picture 0250 // 0251 TikzLine line; 0252 line.contents = cmd; 0253 m_tikzExport.addTikzLine(line); 0254 } 0255 0256 void TikzExportVisitor::visit(Style * style) 0257 { 0258 Q_UNUSED(style) 0259 } 0260 0261 QStringList TikzExportVisitor::styleOptions(Style * style) 0262 { 0263 QStringList options; 0264 0265 // 0266 // export pen style 0267 // 0268 if (style->penStyleSet()) { 0269 options << toString(style->penStyle()); 0270 } 0271 0272 // 0273 // export line width 0274 // 0275 if (style->lineWidthSet()) { 0276 options << lineWidthToString(style->lineWidth()); 0277 } 0278 0279 // 0280 // export double lines 0281 // 0282 if (style->doubleLineSet()) { 0283 options << "double" + (style->innerLineColorSet() 0284 ? QString("=" + colorToString(style->innerLineColor())) 0285 : QString()); 0286 0287 if (style->innerLineWidthSet()) { 0288 options << "double distance=" + style->innerLineWidth().toString(); 0289 } 0290 } 0291 0292 // 0293 // export draw & fill opacity 0294 // 0295 if (style->penOpacitySet() && style->fillOpacitySet() 0296 && style->penOpacity() == style->fillOpacity()) 0297 { 0298 options << QString("opacity=%1").arg(style->penOpacity()); 0299 } else { 0300 if (style->penOpacitySet()) { 0301 options << QString("draw opacity=%1").arg(style->penOpacity()); 0302 } 0303 if (style->fillOpacitySet()) { 0304 options << QString("fill opacity=%1").arg(style->fillOpacity()); 0305 } 0306 } 0307 0308 // 0309 // export draw & fill colors 0310 // 0311 if (style->penColorSet()) { 0312 options << "draw=" + colorToString(style->penColor()); 0313 } 0314 if (style->fillColorSet()) { 0315 options << "fill=" + colorToString(style->fillColor()); 0316 } 0317 0318 return options; 0319 } 0320 0321 QStringList TikzExportVisitor::edgeStyleOptions(Style * style) 0322 { 0323 QStringList options = styleOptions(style); 0324 0325 // 0326 // export arrow tail 0327 // 0328 QString arrowTail; 0329 if (style->arrowTailSet()) { 0330 arrowTail = toString(style->arrowTail()); 0331 } 0332 0333 QString arrowHead; 0334 if (style->arrowHeadSet()) { 0335 arrowHead = toString(style->arrowHead()); 0336 } 0337 0338 if (style->arrowTailSet() || style->arrowHeadSet()) { 0339 options << arrowTail + "-" + arrowHead; 0340 } 0341 0342 // 0343 // export shorten properties 0344 // 0345 if (style->shortenStartSet()) { 0346 options << "shorten <=" + style->shortenStart().toString(); 0347 } 0348 0349 if (style->shortenEndSet()) { 0350 options << "shorten >=" + style->shortenEnd().toString(); 0351 } 0352 0353 // FIXME 0354 if (style->bendAngleSet()) { 0355 const qreal angle = style->bendAngle(); 0356 if (angle > 0) options << QString("bend left=%1").arg(angle); 0357 if (angle < 0) options << QString("bend right=%1").arg(-angle); 0358 } 0359 0360 #if 0 0361 // 0362 // export curve mode 0363 // 0364 if (style->curveModeSet()) { 0365 const CurveMode cm = style->curveMode(); 0366 switch (cm) { 0367 case LineTo: break; 0368 case HVLineTo: break; 0369 case VHLineTo: break; 0370 case BendCurve: { 0371 if (style->bendAngleSet()) { 0372 const qreal angle = style->bendAngle(); 0373 if (angle > 0) options << QString("bend left=%1").arg(angle); 0374 if (angle < 0) options << QString("bend right=%1").arg(-angle); 0375 } 0376 break; 0377 } 0378 case InOutCurve: { 0379 if (style->outAngleSet()) { 0380 options << QString("out=%1").arg(style->outAngle()); 0381 } 0382 if (style->inAngleSet()) { 0383 options << QString("in=%1").arg(style->inAngle()); 0384 } 0385 break; 0386 } 0387 case BezierCurve: { 0388 // TODO ? 0389 } 0390 default: Q_ASSERT(false); break; 0391 } 0392 0393 } 0394 #endif 0395 // 0396 // export looseness 0397 // 0398 if (style->loosenessSet()) { 0399 // Q_ASSERT(cm == CurveMode::BendCurve || cm == CurveMode::InOutCurve || cm == CurveMode::BezierCurve); 0400 options << QString("looseness=%1").arg(style->looseness()); 0401 } 0402 0403 #if 0 0404 if (d->curveMode == BezierCurve) { 0405 vm.insert("control point 1", startControlPoint()); // FIXME: use d->cp1 ? 0406 vm.insert("control point 2", endControlPoint()); // FIXME: use d->cp2 ? 0407 } 0408 } 0409 #endif 0410 0411 return options; 0412 } 0413 0414 QStringList TikzExportVisitor::nodeStyleOptions(Style * style) 0415 { 0416 QStringList options = styleOptions(style); 0417 0418 // 0419 // export align 0420 // 0421 if (style->textAlignSet()) { 0422 options << "align=" + toString(style->textAlign()); 0423 } 0424 0425 // 0426 // export shape 0427 // 0428 if (style->shapeSet()) { 0429 options << toString(style->shape()); 0430 } 0431 0432 // 0433 // export inner sep and outer sep 0434 // 0435 if (style->innerSepSet()) { 0436 options << "inner sep=" + style->innerSep().toString(); 0437 } 0438 0439 if (style->outerSepSet()) { 0440 options << "outer sep=" + style->outerSep().toString(); 0441 } 0442 0443 // 0444 // export minimum width and minimum height 0445 // 0446 if (style->minimumWidthSet() && style->minimumHeightSet() 0447 && style->minimumWidth() == style->minimumHeight()) 0448 { 0449 options << "minimum size=" + style->minimumWidth().toString(); 0450 } else { 0451 if (style->minimumWidthSet()) { 0452 options << "minimum width=" + style->minimumWidth().toString(); 0453 } 0454 if (style->minimumHeightSet()) { 0455 options << "minimum height=" + style->minimumHeight().toString(); 0456 } 0457 } 0458 0459 // 0460 // export rotation 0461 // 0462 if (style->rotationSet()) { 0463 options << QString("rotate=%1").arg(style->rotation()); 0464 } 0465 0466 return options; 0467 } 0468 0469 } 0470 } 0471 0472 // kate: indent-width 4; replace-tabs on;