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()) {
170 if (removeParagraph) {
171 modifiedProps.removeProperty(p);
172 }
173 } else {
174 if (!removeParagraph) {
175 modifiedProps.removeProperty(p);
176 }
177 }
178
179 }
180 // This one is added after removing, because otherwise, the type is removed...
182 if (storedPPIResolution() > 0) {
184 } else {
186 }
187 // Always remove inline size, it is shape-specific.
189 // Remove fill and stroke for now as we have no widgets for them.
196
197 sampleText->setPropertiesAtPos(-1, modifiedProps);
198
199
200 if (type == STYLE_TYPE_PARAGRAPH) {
201 const QSizeF sampleSize = d->paragraphSampleSize;
202 // For paragraph we'll add a shape, as those will allow wrapping,
203 // without being part of the properties like inline-size is.
204 KoPathShape *inlineShape = new KoPathShape();
205 inlineShape->moveTo(QPointF(0, 0));
206 inlineShape->lineTo(QPointF(sampleSize.width(), 0));
207 inlineShape->lineTo(QPointF(sampleSize.width(), sampleSize.height()));
208 inlineShape->lineTo(QPointF(0, sampleSize.height()));
209 inlineShape->lineTo(QPointF(0, 0));
210 inlineShape->close();
211 sampleText->setShapesInside({inlineShape});
212 sampleText->relayout();
213
214 return sampleText.take();
215 } else {
218 QScopedPointer<KoSvgTextShape> newShape(new KoSvgTextShape());
220 // Set whitespace rule to pre-wrap.
223 newShape->setPropertiesAtPos(-1, paraProps);
224 if (!after.isEmpty()) {
225 newShape->insertText(0, after);
226 }
227 if (!before.isEmpty()) {
228 newShape->insertText(0, before);
229 }
230
231 newShape->insertRichText(newShape->posForIndex(before.size()), sampleText.data());
232 return newShape.take();
233 }
234
235 return nullptr;
236}
237
238Qt::Alignment KoCssStylePreset::alignSample() const
239{
240 QMap<QString, QVariant> m = metadata();
241 QVariant v = m.value(SAMPLE_ALIGN, static_cast<Qt::Alignment::Int>(Qt::AlignHCenter | Qt::AlignVCenter));
242 return static_cast<Qt::Alignment>(v.value<Qt::Alignment::Int>());
243}
244
246{
247 QMap<QString, QVariant> m = metadata();
248 return m.value(PRIMARY_FONT_FAMILY).toString();
249}
250
252{
253 Qt::AlignmentFlag hComponent = Qt::AlignHCenter;
254 Qt::AlignmentFlag vComponent = Qt::AlignVCenter;
255
256 const KoSvgTextProperties props = d->properties;
257 const QString type = styleType().isEmpty()? props.property(KoSvgTextProperties::KraTextStyleType).toString(): styleType();
258 if (type == STYLE_TYPE_PARAGRAPH) {
261 const bool textAlignLast = props.hasProperty(KoSvgTextProperties::TextAlignLastId);
262 if (props.hasProperty(KoSvgTextProperties::TextAlignAllId) || textAlignLast) {
265
266 if (mode == KoSvgText::HorizontalTB) {
267 vComponent = Qt::AlignTop;
268 if (align == KoSvgText::AlignStart || align == KoSvgText::AlignLastAuto) {
270 hComponent = Qt::AlignLeft;
271 } else {
272 hComponent = Qt::AlignRight;
273 }
274 } else if (align == KoSvgText::AlignEnd) {
276 hComponent = Qt::AlignRight;
277 } else {
278 hComponent = Qt::AlignLeft;
279 }
280 } else if (align == KoSvgText::AlignLeft) {
281 hComponent = Qt::AlignLeft;
282 } else if (align == KoSvgText::AlignRight) {
283 hComponent = Qt::AlignRight;
284 }
285 } else {
286 hComponent = mode == KoSvgText::VerticalRL? Qt::AlignRight: Qt::AlignLeft;
287 if (align == KoSvgText::AlignStart || align == KoSvgText::AlignLastAuto) {
289 vComponent = Qt::AlignTop;
290 } else {
291 vComponent = Qt::AlignBottom;
292 }
293 } else if (align == KoSvgText::AlignEnd) {
295 vComponent = Qt::AlignBottom;
296 } else {
297 vComponent = Qt::AlignTop;
298 }
299 } else if (align == KoSvgText::AlignLeft) {
300 vComponent = Qt::AlignTop;
301 } else if (align == KoSvgText::AlignRight) {
302 vComponent = Qt::AlignBottom;
303 }
304 }
305 } else {
307
308 if (mode == KoSvgText::HorizontalTB) {
309 vComponent = Qt::AlignTop;
310 if (anchor == KoSvgText::AnchorStart) {
312 hComponent = Qt::AlignLeft;
313 } else {
314 hComponent = Qt::AlignRight;
315 }
316 } else if (anchor == KoSvgText::AnchorEnd) {
318 hComponent = Qt::AlignRight;
319 } else {
320 hComponent = Qt::AlignLeft;
321 }
322 } else {
323 hComponent = Qt::AlignHCenter;
324 }
325 } else {
326 hComponent = mode == KoSvgText::VerticalRL? Qt::AlignRight: Qt::AlignLeft;
327 if (anchor == KoSvgText::AnchorStart) {
329 vComponent = Qt::AlignTop;
330 } else {
331 vComponent = Qt::AlignBottom;
332 }
333 } else if (anchor == KoSvgText::AnchorEnd) {
335 vComponent = Qt::AlignBottom;
336 } else {
337 vComponent = Qt::AlignTop;
338 }
339 } else {
340 vComponent = Qt::AlignVCenter;
341 }
342 }
343 }
344 }
345
346 addMetaData(SAMPLE_ALIGN, static_cast<Qt::Alignment::Int>(hComponent | vComponent));
347}
348
350{
351 return d->beforeText;
352}
353
354void KoCssStylePreset::setBeforeText(const QString &text)
355{
356 if (d->beforeText == text) return;
357 d->beforeText = text;
358 setDirty(true);
359}
360
362{
363 return d->afterText;
364}
365
366void KoCssStylePreset::setAfterText(const QString &text)
367{
368 if (d->afterText == text) return;
369 d->afterText = text;
370 setDirty(true);
371}
372
374{
375 QMap<QString, QVariant> m = metadata();
376 return m[SAMPLE_SVG].toString();
377}
378
380{
381 return d->paragraphSampleSize;
382}
383
385{
386 d->paragraphSampleSize = size;
387 setDirty(true);
388}
389
391{
392 QMap<QString, QVariant> m = metadata();
393 return m.value(STORED_PPI, 0).toInt();
394}
395
397{
399}
400
401
403{
404 return KoResourceSP(new KoCssStylePreset(*this));
405}
406
407bool KoCssStylePreset::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface)
408{
409 Q_UNUSED(resourcesInterface)
410 if (!dev->isOpen()) dev->open(QIODevice::ReadOnly);
411 QString errorMsg;
412 int errorLine = 0;
413 int errorColumn = 0;
414 QDomDocument xmlDocument = SvgParser::createDocumentFromSvg(dev, &errorMsg, &errorLine, &errorColumn);
415 if (xmlDocument.isNull()) {
416
417 errorFlake << "Parsing error in " << filename() << "! Aborting!" << Qt::endl
418 << " In line: " << errorLine << ", column: " << errorColumn << Qt::endl
419 << " Error message: " << errorMsg << Qt::endl;
420 errorFlake << "Parsing error in the main document at line" << errorLine
421 << ", column" << errorColumn << Qt::endl
422 << "Error message: " << errorMsg;
423
424 return false;
425 }
426
428 SvgParser parser(&manager);
429 parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values
431 QSizeF fragmentSize;
432
433 QList<KoShape*> shapes = parser.parseSvg(xmlDocument.documentElement(), &fragmentSize);
434
435 Q_FOREACH(KoShape *shape, shapes) {
436 KoSvgTextShape *textShape = dynamic_cast<KoSvgTextShape*>(shape);
437 if (textShape) {
438 setName(textShape->additionalAttribute(TITLE));
442 if (node.properties()) {
443 KoSvgTextProperties props = *(node.properties());
447 }
449 QStringList fonts = props.property(KoSvgTextProperties::FontFamiliesId).toStringList();
450 addMetaData(PRIMARY_FONT_FAMILY, fonts.value(0));
451 }
452 setProperties(props);
453
454 QPair<int, int> pos = textShape->findRangeForNodeIndex(node);
455 pos.first = textShape->indexForPos(pos.first);
456 pos.second = textShape->indexForPos(pos.second);
457
458 d->sample = textShape->plainText().mid(pos.first, pos.second-pos.first);
459 d->beforeText = textShape->plainText().mid(0, pos.first);
460 d->afterText = textShape->plainText().mid(pos.second);
461
462 if (!textShape->shapesInside().isEmpty() && styleType == STYLE_TYPE_PARAGRAPH) {
463 d->paragraphSampleSize = textShape->shapesInside().first()->size();
464 }
465
466 }
467
469
471 setValid(true);
472 return true;
473 }
474 }
475
476
477 return false;
478}
479
480bool KoCssStylePreset::saveToDevice(QIODevice *dev) const
481{
482 QScopedPointer<KoShape> shape(generateSampleShape());
483 if (!shape) return false;
484
485 QMap<QString, QVariant> m = metadata();
486 shape->setAdditionalAttribute(DESC, m[DESCRIPTION].toString());
487 shape->setAdditionalAttribute(TITLE, name());
488
489 const QRectF boundingRect = shape->boundingRect();
490 SvgWriter writer({shape.take()});
491 return writer.save(*dev, boundingRect.size());
492}
493
495{
496 return ".svg";
497}
498
499QString generateSVG(const KoSvgTextShape *shape) {
500
501 SvgWriter writer({shape->textOutline()});
502 QBuffer buffer;
503 buffer.open(QIODevice::WriteOnly);
504 writer.save(buffer, shape->boundingRect().size());
505 buffer.close();
506
507 return QString::fromUtf8(buffer.data());
508}
509
511{
512 QScopedPointer<KoSvgTextShape> shape (dynamic_cast<KoSvgTextShape*>(generateSampleShape()));
513 if (!shape) return;
514 QImage img(256,
515 256,
516 QImage::Format_ARGB32);
517 img.fill(Qt::white);
518
519 KoShapePainter painter;
520 painter.setShapes({shape.data()});
521 painter.paint(img);
522
524 addMetaData(SAMPLE_SVG, generateSVG(shape.data()));
526
527 setImage(img);
528}
529
530QPair<QString, QString> KoCssStylePreset::resourceType() const
531{
532 return QPair<QString, QString>(ResourceType::CssStyles, "");
533}
#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:1092
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