Krita Source Code Documentation
Loading...
Searching...
No Matches
KoCssStylePreset.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2025 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6#include "KoCssStylePreset.h"
7
8#include <KoShapePainter.h>
9#include <KoSvgTextShape.h>
11#include <KLocalizedString>
12
13#include <SvgWriter.h>
14#include <SvgParser.h>
15
16#include <QDomDocument>
17#include <QBuffer>
18#include <QFileInfo>
19
20#include <FlakeDebug.h>
21
22const QString TITLE = "title";
23const QString DESCRIPTION = "description";
24const QString DESC = "desc";
25const QString SAMPLE_SVG = "sample_svg";
26const QString SAMPLE_ALIGN = "sample_align";
27const QString STYLE_TYPE = "style_type";
28const QString STORED_PPI = "stored_ppi";
29const QString PRIMARY_FONT_FAMILY = "primary_font_family";
30
31const QString STYLE_TYPE_PARAGRAPH = "paragraph";
32const QString STYLE_TYPE_CHARACTER = "character";
33const KLocalizedString SAMPLE_PLACEHOLDER = ki18nc("info:placeholder", "Style Sample");
34
47
48KoCssStylePreset::KoCssStylePreset(const QString &filename)
49 : KoResource(filename)
50 , d(new Private())
51{
52 setName(name().replace("_", " "));
53 if (name().endsWith(defaultFileExtension())) {
54 const QFileInfo f(name());
55 setName(f.completeBaseName());
56 }
57}
58
60 : KoResource(rhs)
61 , d(new Private())
62{
63 d->properties = rhs.d->properties;
64 d->sample = rhs.d->sample;
65 d->afterText = rhs.d->afterText;
66 d->beforeText = rhs.d->beforeText;
69 setValid(true);
70}
71
76
77KoSvgTextProperties KoCssStylePreset::properties(int ppi, bool removeKraProps) const
78{
80 const int storedPPI = storedPPIResolution();
81 if (storedPPI > 0 && ppi > 0) {
82 const double scale = double(storedPPI)/double(ppi);
83 props.scaleAbsoluteValues(scale, scale);
84 }
85 if (removeKraProps) {
89 }
90 // remove fill and stroke for now.
97 return props;
98}
99
101{
102 if (d->properties == properties)
103 return;
105 QStringList fonts = d->properties.property(KoSvgTextProperties::FontFamiliesId).toStringList();
106 //TODO: Apparantly we cannot remove metadata, only set it to nothing...
107 addMetaData(PRIMARY_FONT_FAMILY, fonts.value(0));
108 setValid(true);
109 setDirty(true);
110}
111
113{
114 QMap<QString, QVariant> m = metadata();
115 return m[DESCRIPTION].toString();
116}
117
118void KoCssStylePreset::setDescription(const QString &desc)
119{
120 QMap<QString, QVariant> m = metadata();
121 if (m[DESCRIPTION].toString() == desc) return;
123 setDirty(true);
124}
125
127{
128 QMap<QString, QVariant> m = metadata();
129 return m.value(STYLE_TYPE, STYLE_TYPE_PARAGRAPH).toString();
130}
131
132void KoCssStylePreset::setStyleType(const QString &type)
133{
134 QMap<QString, QVariant> m = metadata();
135 if (m[STYLE_TYPE].toString() == type) return;
136 addMetaData(STYLE_TYPE, type);
137 setDirty(true);
138}
139
141{
142 return d->sample;
143}
144
145void KoCssStylePreset::setSampleText(const QString &text)
146{
147 if (d->sample == text) return;
148 d->sample = text;
149 setDirty(true);
150}
151
153{
154 KoSvgTextProperties modifiedProps = d->properties;
155 const QString sample = d->sample;
156 const QString after = d->afterText;
157 const QString before = d->beforeText;
158
159 QScopedPointer<KoSvgTextShape> sampleText(new KoSvgTextShape());
160 sampleText->insertText(0, sample.isEmpty()? name().isEmpty()? SAMPLE_PLACEHOLDER.toString(): name(): sample);
161 const QString type = styleType().isEmpty()? STYLE_TYPE_CHARACTER: styleType();
162
163 bool removeParagraph = type == STYLE_TYPE_CHARACTER;
164
165 // Remove properties that cannot be edited.
166 Q_FOREACH(KoSvgTextProperties::PropertyId p, modifiedProps.properties()) {
168 if (removeParagraph) {
169 modifiedProps.removeProperty(p);
170 }
171 } else {
172 if (!removeParagraph) {
173 modifiedProps.removeProperty(p);
174 }
175 }
176
177 }
178 // This one is added after removing, because otherwise, the type is removed...
180 if (storedPPIResolution() > 0) {
182 } else {
184 }
185 // Always remove inline size, it is shape-specific.
187 // Remove fill and stroke for now as we have no widgets for them.
194
195 sampleText->setPropertiesAtPos(-1, modifiedProps);
196
197 if (type == STYLE_TYPE_PARAGRAPH) {
198 // For paragraph we'll add a shape, as those will allow wrapping,
199 // without being part of the properties like inline-size is.
200 KoPathShape *inlineShape = new KoPathShape();
201 inlineShape->moveTo(QPointF(0, 0));
202 inlineShape->lineTo(QPointF(120, 0));
203 inlineShape->lineTo(QPointF(120, 120));
204 inlineShape->lineTo(QPointF(0, 120));
205 inlineShape->lineTo(QPointF(0, 0));
206 inlineShape->close();
207 sampleText->setShapesInside({inlineShape});
208 sampleText->relayout();
209
210 return sampleText.take();
211 } else {
214 QScopedPointer<KoSvgTextShape> newShape(new KoSvgTextShape());
216 // Set whitespace rule to pre-wrap.
219 newShape->setPropertiesAtPos(-1, paraProps);
220 if (!after.isEmpty()) {
221 newShape->insertText(0, after);
222 }
223 if (!before.isEmpty()) {
224 newShape->insertText(0, before);
225 }
226
227 newShape->insertRichText(newShape->posForIndex(before.size()), sampleText.data());
228 return newShape.take();
229 }
230
231 return nullptr;
232}
233
234Qt::Alignment KoCssStylePreset::alignSample() const
235{
236 QMap<QString, QVariant> m = metadata();
237 QVariant v = m.value(SAMPLE_ALIGN, static_cast<Qt::Alignment::Int>(Qt::AlignHCenter | Qt::AlignVCenter));
238 return static_cast<Qt::Alignment>(v.value<Qt::Alignment::Int>());
239}
240
242{
243 QMap<QString, QVariant> m = metadata();
244 return m.value(PRIMARY_FONT_FAMILY).toString();
245}
246
248{
249 Qt::AlignmentFlag hComponent = Qt::AlignHCenter;
250 Qt::AlignmentFlag vComponent = Qt::AlignVCenter;
251
252 const KoSvgTextProperties props = d->properties;
253 const QString type = styleType().isEmpty()? props.property(KoSvgTextProperties::KraTextStyleType).toString(): styleType();
254 if (type == STYLE_TYPE_PARAGRAPH) {
257 const bool textAlignLast = props.hasProperty(KoSvgTextProperties::TextAlignLastId);
258 if (props.hasProperty(KoSvgTextProperties::TextAlignAllId) || textAlignLast) {
261
262 if (mode == KoSvgText::HorizontalTB) {
263 vComponent = Qt::AlignTop;
264 if (align == KoSvgText::AlignStart || align == KoSvgText::AlignLastAuto) {
266 hComponent = Qt::AlignLeft;
267 } else {
268 hComponent = Qt::AlignRight;
269 }
270 } else if (align == KoSvgText::AlignEnd) {
272 hComponent = Qt::AlignRight;
273 } else {
274 hComponent = Qt::AlignLeft;
275 }
276 } else if (align == KoSvgText::AlignLeft) {
277 hComponent = Qt::AlignLeft;
278 } else if (align == KoSvgText::AlignRight) {
279 hComponent = Qt::AlignRight;
280 }
281 } else {
282 hComponent = mode == KoSvgText::VerticalRL? Qt::AlignRight: Qt::AlignLeft;
283 if (align == KoSvgText::AlignStart || align == KoSvgText::AlignLastAuto) {
285 vComponent = Qt::AlignTop;
286 } else {
287 vComponent = Qt::AlignBottom;
288 }
289 } else if (align == KoSvgText::AlignEnd) {
291 vComponent = Qt::AlignBottom;
292 } else {
293 vComponent = Qt::AlignTop;
294 }
295 } else if (align == KoSvgText::AlignLeft) {
296 vComponent = Qt::AlignTop;
297 } else if (align == KoSvgText::AlignRight) {
298 vComponent = Qt::AlignBottom;
299 }
300 }
301 } else {
303
304 if (mode == KoSvgText::HorizontalTB) {
305 vComponent = Qt::AlignTop;
306 if (anchor == KoSvgText::AnchorStart) {
308 hComponent = Qt::AlignLeft;
309 } else {
310 hComponent = Qt::AlignRight;
311 }
312 } else if (anchor == KoSvgText::AnchorEnd) {
314 hComponent = Qt::AlignRight;
315 } else {
316 hComponent = Qt::AlignLeft;
317 }
318 } else {
319 hComponent = Qt::AlignHCenter;
320 }
321 } else {
322 hComponent = mode == KoSvgText::VerticalRL? Qt::AlignRight: Qt::AlignLeft;
323 if (anchor == KoSvgText::AnchorStart) {
325 vComponent = Qt::AlignTop;
326 } else {
327 vComponent = Qt::AlignBottom;
328 }
329 } else if (anchor == KoSvgText::AnchorEnd) {
331 vComponent = Qt::AlignBottom;
332 } else {
333 vComponent = Qt::AlignTop;
334 }
335 } else {
336 vComponent = Qt::AlignVCenter;
337 }
338 }
339 }
340 }
341
342 addMetaData(SAMPLE_ALIGN, static_cast<Qt::Alignment::Int>(hComponent | vComponent));
343}
344
346{
347 return d->beforeText;
348}
349
350void KoCssStylePreset::setBeforeText(const QString &text)
351{
352 if (d->beforeText == text) return;
353 d->beforeText = text;
354 setDirty(true);
355}
356
358{
359 return d->afterText;
360}
361
362void KoCssStylePreset::setAfterText(const QString &text)
363{
364 if (d->afterText == text) return;
365 d->afterText = text;
366 setDirty(true);
367}
368
370{
371 QMap<QString, QVariant> m = metadata();
372 return m[SAMPLE_SVG].toString();
373}
374
376{
377 QMap<QString, QVariant> m = metadata();
378 return m.value(STORED_PPI, 0).toInt();
379}
380
382{
384}
385
386
388{
389 return KoResourceSP(new KoCssStylePreset(*this));
390}
391
392bool KoCssStylePreset::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface)
393{
394 Q_UNUSED(resourcesInterface)
395 if (!dev->isOpen()) dev->open(QIODevice::ReadOnly);
396 QString errorMsg;
397 int errorLine = 0;
398 int errorColumn = 0;
399 QDomDocument xmlDocument = SvgParser::createDocumentFromSvg(dev, &errorMsg, &errorLine, &errorColumn);
400 if (xmlDocument.isNull()) {
401
402 errorFlake << "Parsing error in " << filename() << "! Aborting!" << Qt::endl
403 << " In line: " << errorLine << ", column: " << errorColumn << Qt::endl
404 << " Error message: " << errorMsg << Qt::endl;
405 errorFlake << "Parsing error in the main document at line" << errorLine
406 << ", column" << errorColumn << Qt::endl
407 << "Error message: " << errorMsg;
408
409 return false;
410 }
411
413 SvgParser parser(&manager);
414 parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values
416 QSizeF fragmentSize;
417
418 QList<KoShape*> shapes = parser.parseSvg(xmlDocument.documentElement(), &fragmentSize);
419
420 Q_FOREACH(KoShape *shape, shapes) {
421 KoSvgTextShape *textShape = dynamic_cast<KoSvgTextShape*>(shape);
422 if (textShape) {
423 setName(textShape->additionalAttribute(TITLE));
427 if (node.properties()) {
428 KoSvgTextProperties props = *(node.properties());
432 }
434 QStringList fonts = props.property(KoSvgTextProperties::FontFamiliesId).toStringList();
435 addMetaData(PRIMARY_FONT_FAMILY, fonts.value(0));
436 }
437 setProperties(props);
438
439 QPair<int, int> pos = textShape->findRangeForNodeIndex(node);
440 pos.first = textShape->indexForPos(pos.first);
441 pos.second = textShape->indexForPos(pos.second);
442
443 d->sample = textShape->plainText().mid(pos.first, pos.second-pos.first);
444 d->beforeText = textShape->plainText().mid(0, pos.first);
445 d->afterText = textShape->plainText().mid(pos.second);
446 }
447
449
451 setValid(true);
452 return true;
453 }
454 }
455
456
457 return false;
458}
459
460bool KoCssStylePreset::saveToDevice(QIODevice *dev) const
461{
462 QScopedPointer<KoShape> shape(generateSampleShape());
463 if (!shape) return false;
464
465 QMap<QString, QVariant> m = metadata();
466 shape->setAdditionalAttribute(DESC, m[DESCRIPTION].toString());
467 shape->setAdditionalAttribute(TITLE, name());
468
469 const QRectF boundingRect = shape->boundingRect();
470 SvgWriter writer({shape.take()});
471 return writer.save(*dev, boundingRect.size());
472}
473
475{
476 return ".svg";
477}
478
479QString generateSVG(const KoSvgTextShape *shape) {
480
481 SvgWriter writer({shape->textOutline()});
482 QBuffer buffer;
483 buffer.open(QIODevice::WriteOnly);
484 writer.save(buffer, shape->boundingRect().size());
485 buffer.close();
486
487 return QString::fromUtf8(buffer.data());
488}
489
491{
492 QScopedPointer<KoSvgTextShape> shape (dynamic_cast<KoSvgTextShape*>(generateSampleShape()));
493 if (!shape) return;
494 QImage img(256,
495 256,
496 QImage::Format_ARGB32);
497 img.fill(Qt::white);
498
499 KoShapePainter painter;
500 painter.setShapes({shape.data()});
501 painter.paint(img);
502
504 addMetaData(SAMPLE_SVG, generateSVG(shape.data()));
506
507 setImage(img);
508}
509
510QPair<QString, QString> KoCssStylePreset::resourceType() const
511{
512 return QPair<QString, QString>(ResourceType::CssStyles, "");
513}
#define errorFlake
Definition FlakeDebug.h:17
const Params2D p
qreal v
const QString STYLE_TYPE_PARAGRAPH
const QString SAMPLE_SVG
QString generateSVG(const KoSvgTextShape *shape)
const QString DESCRIPTION
const QString TITLE
const QString DESC
const QString STYLE_TYPE
const QString PRIMARY_FONT_FAMILY
const QString SAMPLE_ALIGN
const QString STORED_PPI
const QString STYLE_TYPE_CHARACTER
const KLocalizedString SAMPLE_PLACEHOLDER
The KoCssStylePreset class.
KoCssStylePreset(const QString &filename)
void setStyleType(const QString &type)
void setBeforeText(const QString &text)
set the before text. Call updateThumbnail to update the sample.
bool loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) override
QString defaultFileExtension() const override
QString beforeText() const
The text displayed before the sample. Only relevant when in Character mode.
void setProperties(const KoSvgTextProperties &properties)
set the properties. Call updateThumbnail to update the sample.
KoShape * generateSampleShape() const
generateSampleShape This generates the sample textshape from the properties and sample text(s).
QString sampleText() const
The sample text that is being styled by this preset.
int storedPPIResolution() const
void setAfterText(const QString &text)
set the after text. Call updateThumbnail to update the sample.
void setSampleText(const QString &text)
set the sample. Call updateThumbnail to update the sample.
void setDescription(const QString &desc)
QString afterText() const
The text displayed after the sample, only relevant when in character mode.
void setStoredPPIResolution(const int ppi)
void updateThumbnail() override
updateThumbnail updates the thumbnail for this resource. Reimplement if your thumbnail is something e...
Qt::Alignment alignSample() const
QScopedPointer< Private > d
KoSvgTextProperties properties(int ppi=72, bool removeKraProps=false) const
The actual text properties.
QString sampleSvg() const
Returns the sample svg metadata. Use updateThumbnail to update it.
bool saveToDevice(QIODevice *dev) const override
QString description() const
The description associated with this style.
KoResourceSP clone() const override
QPair< QString, QString > resourceType() const override
QString primaryFontFamily() const
primaryFontFamily If a style uses a FontFamily, it may not look as expected when that font family is ...
QString styleType() const
Set the style type, type is either "paragraph" or "character".
The position of a path point within a path shape.
Definition KoPathShape.h:63
KoPathPoint * lineTo(const QPointF &p)
Adds a new line segment.
void close()
Closes the current subpath.
KoPathPoint * moveTo(const QPointF &p)
Starts a new Subpath.
void setShapes(const QList< KoShape * > &shapes)
void paint(QPainter &painter)
QString additionalAttribute(const QString &name) const
Definition KoShape.cpp:1279
The KoSvgTextNodeIndex class.
KoSvgTextProperties * properties()
properties The properties for this node as a pointer.
@ Visiblity
Bool, CSS visibility.
@ TextAnchorId
KoSvgText::TextAnchor.
@ InlineSizeId
KoSvgText::AutoValue.
@ PaintOrder
QVector<KoShape::PaintOrder>
@ KraTextStyleResolution
Int, used to scale style presets to be pixel-relative.
@ KraTextVersionId
Int, used for handling incorrectly saved files.
@ Opacity
Double, SVG shape opacity.
@ KraTextStyleType
string, used to identify the style preset type (character or paragraph).
@ TextOrientationId
KoSvgText::TextOrientation.
@ TextAlignAllId
KoSvgText::TextAlign.
@ TextCollapseId
KoSvgText::TextSpaceCollapse.
@ StrokeId
KoSvgText::StrokeProperty.
@ FillId
KoSvgText::BackgroundProperty.
@ WritingModeId
KoSvgText::WritingMode.
@ DirectionId
KoSvgText::Direction.
@ TextWrapId
KoSvgText::TextWrap.
@ TextAlignLastId
KoSvgText::TextAlign.
void removeProperty(PropertyId id)
QList< PropertyId > properties() const
static bool propertyIsBlockOnly(KoSvgTextProperties::PropertyId id)
QVariant property(PropertyId id, const QVariant &defaultValue=QVariant()) const
static const KoSvgTextProperties & defaultProperties()
void scaleAbsoluteValues(const double scaleInline=1.0, const double scaleBlock=1.0)
scaleAbsoluteValues This scales all absolute values stored in these text properties....
bool hasProperty(PropertyId id) const
void setProperty(PropertyId id, const QVariant &value)
QVariant propertyOrDefault(PropertyId id) const
QPair< int, int > findRangeForNodeIndex(const KoSvgTextNodeIndex &node) const
findRangeForNodeIndex Find the start and end cursor position for a given nodeIndex.
QRectF boundingRect() const override
Get the bounding box of the shape.
KoSvgTextNodeIndex findNodeIndexForPropertyId(KoSvgTextProperties::PropertyId propertyId)
findNodeIndexForPropertyId
int indexForPos(int pos) const
indexForPos get the string index for a given cursor position.
KoShape * textOutline() const
textOutline This turns the text object into non-text KoShape(s) to the best of its abilities.
static QDomDocument createDocumentFromSvg(QIODevice *device, QString *errorMsg=0, int *errorLine=0, int *errorColumn=0)
void setResolveTextPropertiesForTopLevel(const bool enable)
QList< KoShape * > parseSvg(const QDomElement &e, QSizeF *fragmentSize=0)
Parses a svg fragment, returning the list of top level child shapes.
void setResolution(const QRectF boundsInPixels, qreal pixelsPerInch)
Implements exporting shapes to SVG.
Definition SvgWriter.h:33
bool save(QIODevice &outputDevice, const QSizeF &pageSize)
Writes svg to specified output device.
Definition SvgWriter.cpp:82
QSharedPointer< KoResource > KoResourceSP
@ AlignLastAuto
Definition KoSvgText.h:163
TextAnchor
Where the text is anchored for SVG 1.1 text and 'inline-size'.
Definition KoSvgText.h:79
@ AnchorEnd
Anchor right for LTR, left for RTL.
Definition KoSvgText.h:82
@ AnchorStart
Anchor left for LTR, right for RTL.
Definition KoSvgText.h:80
Direction
Base direction used by Bidi algorithm.
Definition KoSvgText.h:48
@ DirectionLeftToRight
Definition KoSvgText.h:49
@ HorizontalTB
Definition KoSvgText.h:38
@ Preserve
Do not collapse any space.
Definition KoSvgText.h:99
const QString CssStyles
KoSvgTextProperties properties
void setValid(bool valid)
void setName(const QString &name)
void addMetaData(QString key, QVariant value)
store the given key, value pair in the resource
QString filename
void setDirty(bool value)
Mark the preset as modified but not saved.
void setImage(const QImage &image)
QMap< QString, QVariant > metadata
QString name