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
36
38 {
39 }
41
43 QString beforeText;
44 QString sample = SAMPLE_PLACEHOLDER.toString();
45 QString afterText;
46
47 QSizeF paragraphSampleSize = QSizeF(120, 120);
48};
49
50KoCssStylePreset::KoCssStylePreset(const QString &filename)
51 : KoResource(filename)
52 , d(new Private())
53{
54 setName(name().replace("_", " "));
55 if (name().endsWith(defaultFileExtension())) {
56 const QFileInfo f(name());
57 setName(f.completeBaseName());
58 }
59}
60
62 : KoResource(rhs)
63 , d(new Private())
64{
65 d->properties = rhs.d->properties;
66 d->sample = rhs.d->sample;
67 d->afterText = rhs.d->afterText;
68 d->beforeText = rhs.d->beforeText;
71 setValid(true);
72}
73
78
79KoSvgTextProperties KoCssStylePreset::properties(int ppi, bool removeKraProps) const
80{
82 const int storedPPI = storedPPIResolution();
83 if (storedPPI > 0 && ppi > 0) {
84 const double scale = double(storedPPI)/double(ppi);
85 props.scaleAbsoluteValues(scale, scale);
86 }
87 if (removeKraProps) {
91 }
92 // remove fill and stroke for now.
99 return props;
100}
101
103{
104 if (d->properties == properties)
105 return;
107 QStringList fonts = d->properties.property(KoSvgTextProperties::FontFamiliesId).toStringList();
108 //TODO: Apparantly we cannot remove metadata, only set it to nothing...
109 addMetaData(PRIMARY_FONT_FAMILY, fonts.value(0));
110 setValid(true);
111 setDirty(true);
112}
113
115{
116 QMap<QString, QVariant> m = metadata();
117 return m[DESCRIPTION].toString();
118}
119
120void KoCssStylePreset::setDescription(const QString &desc)
121{
122 QMap<QString, QVariant> m = metadata();
123 if (m[DESCRIPTION].toString() == desc) return;
125 setDirty(true);
126}
127
129{
130 QMap<QString, QVariant> m = metadata();
131 return m.value(STYLE_TYPE, STYLE_TYPE_PARAGRAPH).toString();
132}
133
134void KoCssStylePreset::setStyleType(const QString &type)
135{
136 QMap<QString, QVariant> m = metadata();
137 if (m[STYLE_TYPE].toString() == type) return;
138 addMetaData(STYLE_TYPE, type);
139 setDirty(true);
140}
141
143{
144 return d->sample;
145}
146
147void KoCssStylePreset::setSampleText(const QString &text)
148{
149 if (d->sample == text) return;
150 d->sample = text;
151 setDirty(true);
152}
153
155{
156 KoSvgTextProperties modifiedProps = d->properties;
157 const QString sample = d->sample;
158 const QString after = d->afterText;
159 const QString before = d->beforeText;
160
161 QScopedPointer<KoSvgTextShape> sampleText(new KoSvgTextShape());
162 sampleText->insertText(0, sample.isEmpty()? name().isEmpty()? SAMPLE_PLACEHOLDER.toString(): name(): sample);
163 const QString type = styleType().isEmpty()? STYLE_TYPE_CHARACTER: styleType();
164
165 bool removeParagraph = type == STYLE_TYPE_CHARACTER;
166
167 // Remove properties that cannot be edited.
168 Q_FOREACH(KoSvgTextProperties::PropertyId p, modifiedProps.properties()) {
169 if (KoSvgTextProperties::propertyIsBlockOnly(p) && removeParagraph) {
170 modifiedProps.removeProperty(p);
171 }
172 }
173 // This one is added after removing, because otherwise, the type is removed...
175 if (storedPPIResolution() > 0) {
177 } else {
179 }
180 // Always remove inline size, it is shape-specific.
182 // Remove fill and stroke for now as we have no widgets for them.
189
190 sampleText->setPropertiesAtPos(-1, modifiedProps);
191
192
193 if (type == STYLE_TYPE_PARAGRAPH) {
194 const QSizeF sampleSize = d->paragraphSampleSize;
195 // For paragraph we'll add a shape, as those will allow wrapping,
196 // without being part of the properties like inline-size is.
197 KoPathShape *inlineShape = new KoPathShape();
198 inlineShape->moveTo(QPointF(0, 0));
199 inlineShape->lineTo(QPointF(sampleSize.width(), 0));
200 inlineShape->lineTo(QPointF(sampleSize.width(), sampleSize.height()));
201 inlineShape->lineTo(QPointF(0, sampleSize.height()));
202 inlineShape->lineTo(QPointF(0, 0));
203 inlineShape->close();
204 sampleText->setShapesInside({inlineShape});
205 sampleText->relayout();
206
207 return sampleText.take();
208 } else {
211 QScopedPointer<KoSvgTextShape> newShape(new KoSvgTextShape());
213 // Set whitespace rule to pre-wrap.
216 newShape->setPropertiesAtPos(-1, paraProps);
217 if (!after.isEmpty()) {
218 newShape->insertText(0, after);
219 }
220 if (!before.isEmpty()) {
221 newShape->insertText(0, before);
222 }
223
224 newShape->insertRichText(newShape->posForIndex(before.size()), sampleText.data());
225 return newShape.take();
226 }
227
228 return nullptr;
229}
230
231Qt::Alignment KoCssStylePreset::alignSample() const
232{
233 QMap<QString, QVariant> m = metadata();
234 QVariant v = m.value(SAMPLE_ALIGN, static_cast<Qt::Alignment::Int>(Qt::AlignHCenter | Qt::AlignVCenter));
235 return static_cast<Qt::Alignment>(v.value<Qt::Alignment::Int>());
236}
237
239{
240 QMap<QString, QVariant> m = metadata();
241 return m.value(PRIMARY_FONT_FAMILY).toString();
242}
243
245{
246 Qt::AlignmentFlag hComponent = Qt::AlignHCenter;
247 Qt::AlignmentFlag vComponent = Qt::AlignVCenter;
248
249 const KoSvgTextProperties props = d->properties;
250 const QString type = styleType().isEmpty()? props.property(KoSvgTextProperties::KraTextStyleType).toString(): styleType();
251 if (type == STYLE_TYPE_PARAGRAPH) {
254 const bool textAlignLast = props.hasProperty(KoSvgTextProperties::TextAlignLastId);
255 if (props.hasProperty(KoSvgTextProperties::TextAlignAllId) || textAlignLast) {
258
259 if (mode == KoSvgText::HorizontalTB) {
260 vComponent = Qt::AlignTop;
261 if (align == KoSvgText::AlignStart || align == KoSvgText::AlignLastAuto) {
263 hComponent = Qt::AlignLeft;
264 } else {
265 hComponent = Qt::AlignRight;
266 }
267 } else if (align == KoSvgText::AlignEnd) {
269 hComponent = Qt::AlignRight;
270 } else {
271 hComponent = Qt::AlignLeft;
272 }
273 } else if (align == KoSvgText::AlignLeft) {
274 hComponent = Qt::AlignLeft;
275 } else if (align == KoSvgText::AlignRight) {
276 hComponent = Qt::AlignRight;
277 }
278 } else {
279 hComponent = mode == KoSvgText::VerticalRL? Qt::AlignRight: Qt::AlignLeft;
280 if (align == KoSvgText::AlignStart || align == KoSvgText::AlignLastAuto) {
282 vComponent = Qt::AlignTop;
283 } else {
284 vComponent = Qt::AlignBottom;
285 }
286 } else if (align == KoSvgText::AlignEnd) {
288 vComponent = Qt::AlignBottom;
289 } else {
290 vComponent = Qt::AlignTop;
291 }
292 } else if (align == KoSvgText::AlignLeft) {
293 vComponent = Qt::AlignTop;
294 } else if (align == KoSvgText::AlignRight) {
295 vComponent = Qt::AlignBottom;
296 }
297 }
298 } else {
300
301 if (mode == KoSvgText::HorizontalTB) {
302 vComponent = Qt::AlignTop;
303 if (anchor == KoSvgText::AnchorStart) {
305 hComponent = Qt::AlignLeft;
306 } else {
307 hComponent = Qt::AlignRight;
308 }
309 } else if (anchor == KoSvgText::AnchorEnd) {
311 hComponent = Qt::AlignRight;
312 } else {
313 hComponent = Qt::AlignLeft;
314 }
315 } else {
316 hComponent = Qt::AlignHCenter;
317 }
318 } else {
319 hComponent = mode == KoSvgText::VerticalRL? Qt::AlignRight: Qt::AlignLeft;
320 if (anchor == KoSvgText::AnchorStart) {
322 vComponent = Qt::AlignTop;
323 } else {
324 vComponent = Qt::AlignBottom;
325 }
326 } else if (anchor == KoSvgText::AnchorEnd) {
328 vComponent = Qt::AlignBottom;
329 } else {
330 vComponent = Qt::AlignTop;
331 }
332 } else {
333 vComponent = Qt::AlignVCenter;
334 }
335 }
336 }
337 }
338
339 addMetaData(SAMPLE_ALIGN, static_cast<Qt::Alignment::Int>(hComponent | vComponent));
340}
341
343{
344 return d->beforeText;
345}
346
347void KoCssStylePreset::setBeforeText(const QString &text)
348{
349 if (d->beforeText == text) return;
350 d->beforeText = text;
351 setDirty(true);
352}
353
355{
356 return d->afterText;
357}
358
359void KoCssStylePreset::setAfterText(const QString &text)
360{
361 if (d->afterText == text) return;
362 d->afterText = text;
363 setDirty(true);
364}
365
367{
368 QMap<QString, QVariant> m = metadata();
369 return m[SAMPLE_SVG].toString();
370}
371
373{
374 return d->paragraphSampleSize;
375}
376
378{
379 d->paragraphSampleSize = size;
380 setDirty(true);
381}
382
384{
385 QMap<QString, QVariant> m = metadata();
386 return m.value(STORED_PPI, 0).toInt();
387}
388
390{
392}
393
394
396{
397 return KoResourceSP(new KoCssStylePreset(*this));
398}
399
400bool KoCssStylePreset::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface)
401{
402 Q_UNUSED(resourcesInterface)
403 if (!dev->isOpen()) dev->open(QIODevice::ReadOnly);
404 QString errorMsg;
405 int errorLine = 0;
406 int errorColumn = 0;
407 QDomDocument xmlDocument = SvgParser::createDocumentFromSvg(dev, &errorMsg, &errorLine, &errorColumn);
408 if (xmlDocument.isNull()) {
409
410 errorFlake << "Parsing error in " << filename() << "! Aborting!" << Qt::endl
411 << " In line: " << errorLine << ", column: " << errorColumn << Qt::endl
412 << " Error message: " << errorMsg << Qt::endl;
413 errorFlake << "Parsing error in the main document at line" << errorLine
414 << ", column" << errorColumn << Qt::endl
415 << "Error message: " << errorMsg;
416
417 return false;
418 }
419
421 SvgParser parser(&manager);
422 parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values
424 QSizeF fragmentSize;
425
426 QList<KoShape*> shapes = parser.parseSvg(xmlDocument.documentElement(), &fragmentSize);
427
428 Q_FOREACH(KoShape *shape, shapes) {
429 KoSvgTextShape *textShape = dynamic_cast<KoSvgTextShape*>(shape);
430 if (textShape) {
431 setName(textShape->additionalAttribute(TITLE));
435 if (node.properties()) {
436 KoSvgTextProperties props = *(node.properties());
440 }
442 QStringList fonts = props.property(KoSvgTextProperties::FontFamiliesId).toStringList();
443 addMetaData(PRIMARY_FONT_FAMILY, fonts.value(0));
444 }
445 setProperties(props);
446
447 QPair<int, int> pos = textShape->findRangeForNodeIndex(node);
448 pos.first = textShape->indexForPos(pos.first);
449 pos.second = textShape->indexForPos(pos.second);
450
451 d->sample = textShape->plainText().mid(pos.first, pos.second-pos.first);
452 d->beforeText = textShape->plainText().mid(0, pos.first);
453 d->afterText = textShape->plainText().mid(pos.second);
454
455 if (!textShape->shapesInside().isEmpty() && styleType == STYLE_TYPE_PARAGRAPH) {
456 d->paragraphSampleSize = textShape->shapesInside().first()->size();
457 }
458
459 }
460
462
464 setValid(true);
465 return true;
466 }
467 }
468
469
470 return false;
471}
472
473bool KoCssStylePreset::saveToDevice(QIODevice *dev) const
474{
475 QScopedPointer<KoShape> shape(generateSampleShape());
476 if (!shape) return false;
477
478 QMap<QString, QVariant> m = metadata();
479 shape->setAdditionalAttribute(DESC, m[DESCRIPTION].toString());
480 shape->setAdditionalAttribute(TITLE, name());
481
482 const QRectF boundingRect = shape->boundingRect();
483 SvgWriter writer({shape.take()});
484 return writer.save(*dev, boundingRect.size());
485}
486
488{
489 return ".svg";
490}
491
492QString generateSVG(const KoSvgTextShape *shape) {
493
494 SvgWriter writer({shape->textOutline()});
495 QBuffer buffer;
496 buffer.open(QIODevice::WriteOnly);
497 writer.save(buffer, shape->boundingRect().size());
498 buffer.close();
499
500 return QString::fromUtf8(buffer.data());
501}
502
504{
505 QScopedPointer<KoSvgTextShape> shape (dynamic_cast<KoSvgTextShape*>(generateSampleShape()));
506 if (!shape) return;
507 QImage img(256,
508 256,
509 QImage::Format_ARGB32);
510 img.fill(Qt::white);
511
512 KoShapePainter painter;
513 painter.setShapes({shape.data()});
514 painter.paint(img);
515
517 addMetaData(SAMPLE_SVG, generateSVG(shape.data()));
519
520 setImage(img);
521}
522
523QPair<QString, QString> KoCssStylePreset::resourceType() const
524{
525 return QPair<QString, QString>(ResourceType::CssStyles, "");
526}
#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)
QSizeF paragraphSampleSize() const
Returns the size of the shape which the paragraph is set as.
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 setParagraphSampleSize(const QSizeF size)
Set the size of the shape the paragraph is set in.
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:1087
The KoSvgTextNodeIndex class.
KoSvgTextProperties * properties()
properties The properties for this node as a pointer.
@ 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.
@ Visibility
Bool, CSS visibility.
@ 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.
QList< KoShape * > shapesInside
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