KDStateMachineEditor API Documentation 2.1
Loading...
Searching...
No Matches
svgexporter.cpp
Go to the documentation of this file.
1/*
2 This file is part of the KDAB State Machine Editor Library.
3
4 SPDX-FileCopyrightText: 2015 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
5 Author: Volker Krause <volker.krause@kdab.com>
6
7 SPDX-License-Identifier: LGPL-2.1-only OR LicenseRef-KDAB-KDStateMachineEditor
8
9 Licensees holding valid commercial KDAB State Machine Editor Library
10 licenses may use this file in accordance with the KDAB State Machine Editor
11 Library License Agreement provided with the Software.
12
13 Contact info@kdab.com if any conditions of this licensing are not clear to you.
14*/
15
16#include "svgexporter.h"
17
18#include "elementutil.h"
19#include "objecthelper.h"
20#include "state.h"
21#include "transition.h"
22
23#include <QDebug>
24#include <QFontMetricsF>
25#include <QGuiApplication>
26#include <QPainterPath>
27#include <QRectF>
28#include <QTransform>
29#include <QXmlStreamWriter>
30
31using namespace KDSME;
32
33namespace KDSME {
34
35class SvgExporterPrivate
36{
37public:
38 explicit SvgExporterPrivate(SvgExporter *qq)
39 : q(qq)
40 {
41 }
42
43 void writeSvgIntro(const QRectF &viewBox);
44 void writeSvgRect(const QRectF &rect, double radius = 0.0);
45 void writeSvgEllipse(const QRectF &rect, bool fill);
46 void writeSvgLine(const QLineF &line);
47 void writeSvgPath(const QPainterPath &path, bool fill = false);
48 void writeSvgText(const QString &text, QPointF pos, bool center = false);
49
50 bool writeStateMachine(const StateMachine *machine);
51 bool writeState(State *state);
52 bool writeStateInner(const State *state);
53 bool writeTransition(const Transition *transition);
54
55 [[nodiscard]] static double headerHeight();
56 [[nodiscard]] static double margin();
57 [[nodiscard]] static double arrowSize();
58
59 SvgExporter *q;
60 QXmlStreamWriter writer;
61};
62
63}
64
65void SvgExporterPrivate::writeSvgIntro(const QRectF &viewBox)
66{
67 writer.writeStartElement(QStringLiteral("svg"));
68 writer.writeAttribute(QStringLiteral("width"), QString::number(viewBox.width()));
69 writer.writeAttribute(QStringLiteral("height"), QString::number(viewBox.height()));
70 writer.writeAttribute(QStringLiteral("viewBox"), QStringLiteral("%1 %2 %3 %4").arg(viewBox.x()).arg(viewBox.y()).arg(viewBox.width()).arg(viewBox.height()));
71 writer.writeAttribute(QStringLiteral("xmlns"), QStringLiteral("http://www.w3.org/2000/svg"));
72 writer.writeAttribute(QStringLiteral("xmlns:svg"), QStringLiteral("http://www.w3.org/2000/svg"));
73 writer.writeAttribute(QStringLiteral("version"), QStringLiteral("1.2"));
74 writer.writeAttribute(QStringLiteral("baseProfile"), QStringLiteral("tiny"));
75}
76
77void SvgExporterPrivate::writeSvgRect(const QRectF &rect, double radius)
78{
79 writer.writeStartElement(QStringLiteral("rect"));
80 writer.writeAttribute(QStringLiteral("x"), QString::number(rect.x()));
81 writer.writeAttribute(QStringLiteral("y"), QString::number(rect.y()));
82 writer.writeAttribute(QStringLiteral("width"), QString::number(rect.width()));
83 writer.writeAttribute(QStringLiteral("height"), QString::number(rect.height()));
84 writer.writeAttribute(QStringLiteral("rx"), QString::number(radius));
85 writer.writeAttribute(QStringLiteral("ry"), QString::number(radius));
86 writer.writeAttribute(QStringLiteral("fill"), QStringLiteral("none"));
87 writer.writeAttribute(QStringLiteral("stroke"), QStringLiteral("black"));
88 writer.writeEndElement();
89}
90
91void SvgExporterPrivate::writeSvgEllipse(const QRectF &rect, bool fill)
92{
93 writer.writeStartElement(QStringLiteral("ellipse"));
94 writer.writeAttribute(QStringLiteral("cx"), QString::number(rect.center().x()));
95 writer.writeAttribute(QStringLiteral("cy"), QString::number(rect.center().y()));
96 writer.writeAttribute(QStringLiteral("rx"), QString::number(rect.width() / 2.0));
97 writer.writeAttribute(QStringLiteral("ry"), QString::number(rect.height() / 2.0));
98 writer.writeAttribute(QStringLiteral("fill"), fill ? QStringLiteral("black") : QStringLiteral("none"));
99 writer.writeAttribute(QStringLiteral("stroke"), QStringLiteral("black"));
100 writer.writeEndElement();
101}
102
103void SvgExporterPrivate::writeSvgLine(const QLineF &line)
104{
105 writer.writeStartElement(QStringLiteral("line"));
106 writer.writeAttribute(QStringLiteral("x1"), QString::number(line.x1()));
107 writer.writeAttribute(QStringLiteral("y1"), QString::number(line.y1()));
108 writer.writeAttribute(QStringLiteral("x2"), QString::number(line.x2()));
109 writer.writeAttribute(QStringLiteral("y2"), QString::number(line.y2()));
110 writer.writeAttribute(QStringLiteral("stroke"), QStringLiteral("black"));
111 writer.writeEndElement();
112}
113
114void SvgExporterPrivate::writeSvgPath(const QPainterPath &path, bool fill)
115{
116 writer.writeStartElement(QStringLiteral("path"));
117 QString pathData;
118 for (int i = 0; i < path.elementCount(); ++i) {
119 auto pathElement = path.elementAt(i);
120 switch (pathElement.type) {
121 case QPainterPath::MoveToElement:
122 pathData += QStringLiteral(" M") + QString::number(pathElement.x) + u',' + QString::number(pathElement.y);
123 break;
124 case QPainterPath::LineToElement:
125 pathData += QStringLiteral(" L") + QString::number(pathElement.x) + u',' + QString::number(pathElement.y);
126 break;
127 case QPainterPath::CurveToElement:
128 pathData += QStringLiteral(" C") + QString::number(pathElement.x) + u',' + QString::number(pathElement.y);
129 ++i;
130 while (i < path.elementCount()) {
131 const auto curveElement = path.elementAt(i);
132 if (curveElement.type != QPainterPath::CurveToDataElement) {
133 --i;
134 break;
135 }
136 pathData += u' ' + QString::number(curveElement.x) + u',' + QString::number(curveElement.y);
137 ++i;
138 }
139 break;
140 default:
141 break;
142 }
143 }
144 writer.writeAttribute(QStringLiteral("d"), pathData.trimmed());
145 writer.writeAttribute(QStringLiteral("fill"), fill ? QStringLiteral("black") : QStringLiteral("none"));
146 writer.writeAttribute(QStringLiteral("stroke"), QStringLiteral("black"));
147 writer.writeEndElement();
148}
149
150void SvgExporterPrivate::writeSvgText(const QString &text, QPointF pos, bool center)
151{
152 writer.writeStartElement(QStringLiteral("text"));
153 writer.writeAttribute(QStringLiteral("x"), QString::number(pos.x()));
154 writer.writeAttribute(QStringLiteral("y"), QString::number(pos.y()));
155 writer.writeAttribute(QStringLiteral("fill"), QStringLiteral("black"));
156 writer.writeAttribute(QStringLiteral("font-size"), QString::number(QGuiApplication::font().pointSizeF()));
157 writer.writeAttribute(QStringLiteral("font-family"), QGuiApplication::font().family());
158 if (center)
159 writer.writeAttribute(QStringLiteral("text-anchor"), QStringLiteral("middle"));
160 writer.writeCharacters(text);
161 writer.writeEndElement();
162}
163
164
165bool SvgExporterPrivate::writeStateMachine(const StateMachine *machine)
166{
167 writeSvgRect(machine->boundingRect());
168 const QFontMetricsF metrics(QGuiApplication::font());
169 const QRectF headerBox(machine->pos().x(), machine->pos().y(),
170 metrics.horizontalAdvance(machine->label()) + 2 * margin(), headerHeight());
171
172 writeSvgRect(headerBox);
173 writeSvgText(machine->label(), machine->pos() + QPointF(margin(), headerHeight() / 2.0));
174 return writeStateInner(machine);
175}
176
177bool SvgExporterPrivate::writeState(State *state)
178{
179#if !defined(NDEBUG)
180 if (auto pseudo = qobject_cast<PseudoState *>(state)) {
181 Q_ASSERT(pseudo->kind() == PseudoState::InitialState);
182#else
183 if (qobject_cast<PseudoState *>(state)) {
184#endif
185 writeSvgEllipse(state->boundingRect(), true);
186 } else if (qobject_cast<FinalState *>(state)) {
187 writeSvgEllipse(state->boundingRect(), false);
188 const auto dx = state->boundingRect().width() * 0.15;
189 const auto dy = state->boundingRect().height() * 0.15;
190 writeSvgEllipse(state->boundingRect().adjusted(dx, dy, -dx, -dy), true);
191 } else if (auto history = qobject_cast<HistoryState *>(state)) {
192 writeSvgEllipse(state->boundingRect(), false);
193 if (history->historyType() == HistoryState::DeepHistory) {
194 const auto dx = state->boundingRect().width() * 0.1;
195 const auto dy = state->boundingRect().height() * 0.1;
196 writeSvgEllipse(state->boundingRect().adjusted(dx, dy, -dx, -dy), false);
197 }
198 writeSvgText(QStringLiteral("H"), history->boundingRect().center(), true);
199 } else {
200 writeSvgRect(state->boundingRect(), 3.0);
201 if (state->isComposite()) {
202 writeSvgText(state->label(), state->pos() + QPointF(margin(), headerHeight() / 2.0));
203 writeSvgLine(QLineF(state->boundingRect().left(), state->boundingRect().top() + headerHeight(),
204 state->boundingRect().right(), state->boundingRect().top() + headerHeight()));
205 } else {
206 writeSvgText(state->label(), state->boundingRect().center(), true);
207 }
208 }
209 if (!writeStateInner(state))
210 return false;
211 return true;
212}
213
214bool SvgExporterPrivate::writeStateInner(const State *state)
215{
216 if (state->transitions().isEmpty() && state->childStates().isEmpty())
217 return true;
218
219 writer.writeStartElement(QStringLiteral("g"));
220 writer.writeAttribute(QStringLiteral("transform"), QStringLiteral("translate(%1,%2)").arg(state->boundingRect().x()).arg(state->boundingRect().y()));
221 const auto stateTransitions = state->transitions();
222 for (const Transition *transition : stateTransitions) {
223 writeTransition(transition);
224 }
225
226 if (state->isExpanded()) {
227 const auto childStates = state->childStates();
228 for (State *child : childStates) {
229 if (auto machine = qobject_cast<StateMachine *>(child)) {
230 if (!writeStateMachine(machine))
231 return false;
232 } else if (!writeState(child)) {
233 return false;
234 }
235 }
236 } else {
237 const QPointF p(state->boundingRect().width() / 2.0, state->boundingRect().height() / 2.0 + headerHeight() / 2.0);
238 writeSvgText(QStringLiteral("..."), p, true);
239 }
240
241 writer.writeEndElement();
242 return true;
243}
244
245bool SvgExporterPrivate::writeTransition(const Transition *transition)
246{
247 const auto path = transition->shape().translated(transition->pos());
248 writeSvgPath(path);
249
250 QPainterPath arrowHead;
251 arrowHead.moveTo(0, 0);
252 arrowHead.lineTo(arrowSize(), arrowSize() / 2.0);
253 arrowHead.lineTo(arrowSize(), -arrowSize() / 2.0);
254 arrowHead.closeSubpath();
255
256 QTransform t;
257 t.rotate(180 - path.angleAtPercent(1.0));
258 arrowHead = t.map(arrowHead);
259 writeSvgPath(arrowHead.translated(path.pointAtPercent(1.0)), true);
260
261 writeSvgText(transition->label(), transition->labelBoundingRect().translated(transition->pos()).topLeft());
262 return true;
263}
264
265
266double SvgExporterPrivate::headerHeight()
267{
268 const QFontMetricsF metrics(QGuiApplication::font());
269 return metrics.height() * 2.0;
270}
271
272double SvgExporterPrivate::margin()
273{
274 const QFontMetricsF metrics(QGuiApplication::font());
275 return metrics.horizontalAdvance(u'x');
276}
277
278double SvgExporterPrivate::arrowSize()
279{
280 return margin();
281}
282
283
284SvgExporter::SvgExporter(QIODevice *ioDevice)
285 : d(new SvgExporterPrivate(this))
286{
287 d->writer.setDevice(ioDevice);
288 d->writer.setAutoFormatting(true);
289}
290
294
296{
297 setErrorString({});
298
299 if (!machine) {
300 setErrorString(QStringLiteral("Null machine instance passed"));
301 return false;
302 }
303
304 if (d->writer.hasError()) {
305 setErrorString(QStringLiteral("Setting up XML writer failed"));
306 return false;
307 }
308
309 d->writer.writeStartDocument();
310 d->writeSvgIntro(machine->boundingRect().adjusted(-d->margin(), -d->margin(), d->margin(), d->margin()));
311 if (!d->writeStateMachine(machine))
312 return false;
313 d->writer.writeEndElement();
314 d->writer.writeEndDocument();
315 return !d->writer.hasError();
316}
void setErrorString(const QString &errorString)
QPointF pos
The position of the element from the top-left corner.
Definition element.h:42
virtual QRectF boundingRect() const
Definition element.cpp:199
QString label
Definition element.h:40
QList< Transition * > transitions() const
Definition state.cpp:98
bool isExpanded() const
Definition state.cpp:181
QList< State * > childStates() const
Definition state.cpp:93
bool isComposite
Definition state.h:34
bool exportMachine(StateMachine *machine) override
SvgExporter(QIODevice *ioDevice)
QRectF labelBoundingRect
Definition transition.h:31
QPainterPath shape
The exact shape of this transition.
Definition transition.h:30

© Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/
KDStateMachineEditor
Create Qt State Machine metacode using a graphical user interface
https://github.com/KDAB/KDStateMachineEditor
Generated on Tue Jul 15 2025 15:21:47 for KDStateMachineEditor API Documentation by doxygen 1.9.8