24#include <QFontMetricsF>
25#include <QGuiApplication>
26#include <QPainterPath>
29#include <QXmlStreamWriter>
35class SvgExporterPrivate
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);
51 bool writeState(
State *state);
52 bool writeStateInner(
const State *state);
53 bool writeTransition(
const Transition *transition);
55 [[nodiscard]]
static double headerHeight();
56 [[nodiscard]]
static double margin();
57 [[nodiscard]]
static double arrowSize();
60 QXmlStreamWriter writer;
65void SvgExporterPrivate::writeSvgIntro(
const QRectF &viewBox)
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"));
77void SvgExporterPrivate::writeSvgRect(
const QRectF &rect,
double radius)
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();
91void SvgExporterPrivate::writeSvgEllipse(
const QRectF &rect,
bool fill)
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();
103void SvgExporterPrivate::writeSvgLine(
const QLineF &line)
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();
114void SvgExporterPrivate::writeSvgPath(
const QPainterPath &path,
bool fill)
116 writer.writeStartElement(QStringLiteral(
"path"));
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);
124 case QPainterPath::LineToElement:
125 pathData += QStringLiteral(
" L") + QString::number(pathElement.x) + u
',' + QString::number(pathElement.y);
127 case QPainterPath::CurveToElement:
128 pathData += QStringLiteral(
" C") + QString::number(pathElement.x) + u
',' + QString::number(pathElement.y);
130 while (i < path.elementCount()) {
131 const auto curveElement = path.elementAt(i);
132 if (curveElement.type != QPainterPath::CurveToDataElement) {
136 pathData += u
' ' + QString::number(curveElement.x) + u
',' + QString::number(curveElement.y);
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();
150void SvgExporterPrivate::writeSvgText(
const QString &text, QPointF pos,
bool center)
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());
159 writer.writeAttribute(QStringLiteral(
"text-anchor"), QStringLiteral(
"middle"));
160 writer.writeCharacters(text);
161 writer.writeEndElement();
165bool SvgExporterPrivate::writeStateMachine(
const StateMachine *machine)
168 const QFontMetricsF metrics(QGuiApplication::font());
169 const QRectF headerBox(machine->
pos().x(), machine->
pos().y(),
170 metrics.horizontalAdvance(machine->
label()) + 2 * margin(), headerHeight());
172 writeSvgRect(headerBox);
173 writeSvgText(machine->
label(), machine->
pos() + QPointF(margin(), headerHeight() / 2.0));
174 return writeStateInner(machine);
177bool SvgExporterPrivate::writeState(
State *state)
180 if (
auto pseudo = qobject_cast<PseudoState *>(state)) {
183 if (qobject_cast<PseudoState *>(state)) {
186 }
else if (qobject_cast<FinalState *>(state)) {
190 writeSvgEllipse(state->
boundingRect().adjusted(dx, dy, -dx, -dy),
true);
191 }
else if (
auto history = qobject_cast<HistoryState *>(state)) {
196 writeSvgEllipse(state->
boundingRect().adjusted(dx, dy, -dx, -dy),
false);
198 writeSvgText(QStringLiteral(
"H"), history->boundingRect().center(),
true);
202 writeSvgText(state->
label(), state->
pos() + QPointF(margin(), headerHeight() / 2.0));
209 if (!writeStateInner(state))
214bool SvgExporterPrivate::writeStateInner(
const State *state)
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);
228 for (
State *child : childStates) {
229 if (
auto machine = qobject_cast<StateMachine *>(child)) {
230 if (!writeStateMachine(machine))
232 }
else if (!writeState(child)) {
238 writeSvgText(QStringLiteral(
"..."), p,
true);
241 writer.writeEndElement();
245bool SvgExporterPrivate::writeTransition(
const Transition *transition)
247 const auto path = transition->
shape().translated(transition->
pos());
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();
257 t.rotate(180 - path.angleAtPercent(1.0));
258 arrowHead = t.map(arrowHead);
259 writeSvgPath(arrowHead.translated(path.pointAtPercent(1.0)),
true);
266double SvgExporterPrivate::headerHeight()
268 const QFontMetricsF metrics(QGuiApplication::font());
269 return metrics.height() * 2.0;
272double SvgExporterPrivate::margin()
274 const QFontMetricsF metrics(QGuiApplication::font());
275 return metrics.horizontalAdvance(u
'x');
278double SvgExporterPrivate::arrowSize()
285 : d(new SvgExporterPrivate(this))
287 d->writer.setDevice(ioDevice);
288 d->writer.setAutoFormatting(
true);
304 if (d->writer.hasError()) {
309 d->writer.writeStartDocument();
310 d->writeSvgIntro(machine->
boundingRect().adjusted(-d->margin(), -d->margin(), d->margin(), d->margin()));
311 if (!d->writeStateMachine(machine))
313 d->writer.writeEndElement();
314 d->writer.writeEndDocument();
315 return !d->writer.hasError();
void setErrorString(const QString &errorString)
QPointF pos
The position of the element from the top-left corner.
virtual QRectF boundingRect() const
QList< Transition * > transitions() const
QList< State * > childStates() const
bool exportMachine(StateMachine *machine) override
SvgExporter(QIODevice *ioDevice)
QPainterPath shape
The exact shape of this transition.