Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_text_brush.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2004 Cyrille Berger <cberger@cberger.net>
3 * SPDX-FileCopyrightText: 2011 Lukáš Tvrdý <lukast.dev@gmail.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include "kis_text_brush.h"
9
10#include <QDomDocument>
11#include <QDomElement>
12#include <QFontMetrics>
13#include <QPainter>
14
15#include "kis_gbr_brush.h"
16#include "kis_brushes_pipe.h"
17#include <kis_dom_utils.h>
19
20#ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND
21#include <QApplication>
22#include <QWidget>
23#include <QThread>
24#endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */
25
26
27class KisTextBrushesPipe : public KisBrushesPipe<KisGbrBrush>
28{
29public:
34
36 : KisBrushesPipe<KisGbrBrush>(), // no copy here!
37 m_text(rhs.m_text),
40 {
41 m_brushesMap.clear();
42
43 QMapIterator<QChar, KisGbrBrushSP> iter(rhs.m_brushesMap);
44 while (iter.hasNext()) {
45 iter.next();
46 KisGbrBrushSP brush(new KisGbrBrush(*iter.value()));
47 m_brushesMap.insert(iter.key(), brush);
49 }
50 }
51
52 void setText(const QString &text, const QFont &font) {
53 m_text = text;
54
55 m_charIndex = 0;
56
57 clear();
58
59 for (int i = 0; i < m_text.length(); i++) {
60
61 const QChar letter = m_text.at(i);
62
63 // skip letters that are already present in the brushes pipe
64 if (m_brushesMap.contains(letter)) continue;
65
66 QImage image = renderChar(letter, font);
67 KisGbrBrushSP brush(new KisGbrBrush(image, letter));
68 brush->setSpacing(0.1); // support for letter spacing?
69 brush->makeMaskImage(false);
70
71 m_brushesMap.insert(letter, brush);
73 }
74 }
75
76 static QImage renderChar(const QString& text, const QFont &font) {
77#ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND
78 QWidget *focusWidget = qApp->focusWidget();
79 if (focusWidget) {
80 QThread *guiThread = focusWidget->thread();
81 if (guiThread != QThread::currentThread()) {
82 warnKrita << "WARNING: Rendering text in non-GUI thread!"
83 << "That may lead to hangups and crashes on some"
84 << "versions of X11/Qt!";
85 }
86 }
87#endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */
88
89 QFontMetrics metric(font);
90 QRect rect = metric.boundingRect(text);
91
92 if (rect.isEmpty()) {
93 rect = QRect(0, 0, 1, 1); // paint at least something
94 }
95
96 QRect paintingRect = rect.translated(-rect.x(), -rect.y());
97
98 QImage renderedChar(paintingRect.size(), QImage::Format_ARGB32);
99 QPainter p;
100 p.begin(&renderedChar);
101 p.setFont(font);
102 p.fillRect(paintingRect, Qt::white);
103 p.setPen(Qt::black);
104 p.drawText(-rect.x(), -rect.y(), text);
105 p.end();
106 return renderedChar;
107 }
108
109 void clear() override {
110 m_brushesMap.clear();
112 }
113
115 Q_ASSERT(m_text.size() > 0);
116 Q_ASSERT(m_brushesMap.size() > 0);
117 return m_brushesMap.value(m_text.at(0));
118 }
119
120 void notifyStrokeStarted() override {
121 m_charIndex = 0;
123 }
124
125 int currentBrushIndex() override {
126 return m_currentBrushIndex;
127 }
128
129protected:
130
131 int chooseNextBrush(const KisPaintInformation& info) override {
132 Q_UNUSED(info);
133 return m_currentBrushIndex;
134 }
135
136 void updateBrushIndexes(KisRandomSourceSP randomSource, int seqNo) override {
137 Q_UNUSED(randomSource);
138
139 if (m_text.size()) {
140 m_charIndex = (seqNo >= 0 ? seqNo : (m_charIndex + 1)) % m_text.size();
141 } else {
142 m_charIndex = 0;
143 }
144
146 }
147
148private:
150 if (m_text.isEmpty()) return;
151
152 if (m_charIndex >= m_text.size()) {
153 m_charIndex = 0;
154 }
155
156 QChar letter = m_text.at(m_charIndex);
157 Q_ASSERT(m_brushesMap.contains(letter));
158
159 m_currentBrushIndex = m_brushes.indexOf(m_brushesMap.value(letter));
160 }
161
162private:
163 QMap<QChar, KisGbrBrushSP> m_brushesMap;
164 QString m_text;
167};
168
169
171 : m_brushesPipe(new KisTextBrushesPipe())
172{
173 setPipeMode(false);
174}
175
177 : KisScalingSizeBrush(rhs),
178 m_font(rhs.m_font),
179 m_text(rhs.m_text),
180 m_brushesPipe(new KisTextBrushesPipe(*rhs.m_brushesPipe))
181{
182}
183
188
190{
191 return KisBrushSP(new KisTextBrush(*this));
192}
193
195{
196 return true;
197}
198
199bool KisTextBrush::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface)
200{
201 Q_UNUSED(dev);
202 Q_UNUSED(resourcesInterface);
203 return false;
204}
205
206bool KisTextBrush::saveToDevice(QIODevice *dev) const
207{
208 Q_UNUSED(dev);
209 return false;
210}
211
213{
214 setBrushType(pipe ? PIPE_MASK : MASK);
215}
216
218{
219 return brushType() == PIPE_MASK;
220}
221
222void KisTextBrush::setText(const QString& txt)
223{
224 m_text = txt;
225}
226
227QString KisTextBrush::text(void) const
228{
229 return m_text;
230}
231
232void KisTextBrush::setFont(const QFont& font)
233{
234 m_font = font;
235}
236
238{
239 return m_font;
240}
241
246
248{
249 m_brushesPipe->prepareForSeqNo(info, seqNo);
250}
251
253 KisDabShape const& shape,
254 const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor, qreal lightnessStrength) const
255{
256 if (brushType() == MASK) {
257 KisBrush::generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor, lightnessStrength);
258 }
259 else { /* if (brushType() == PIPE_MASK)*/
260 m_brushesPipe->generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor, lightnessStrength);
261 }
262}
263
265 KisDabShape const& shape,
266 const KisPaintInformation& info,
267 double subPixelX, double subPixelY) const
268{
269 if (brushType() == MASK) {
270 return KisBrush::paintDevice(colorSpace, shape, info, subPixelX, subPixelY);
271 }
272 else { /* if (brushType() == PIPE_MASK)*/
273 return m_brushesPipe->paintDevice(colorSpace, shape, info, subPixelX, subPixelY);
274 }
275}
276
277void KisTextBrush::toXML(QDomDocument& doc, QDomElement& e) const
278{
279 Q_UNUSED(doc);
280
281 e.setAttribute("type", "kis_text_brush");
282 e.setAttribute("spacing", KisDomUtils::toString(spacing()));
283 e.setAttribute("text", m_text);
284 e.setAttribute("font", m_font.toString());
285 e.setAttribute("pipe", (brushType() == PIPE_MASK) ? "true" : "false");
286 KisBrush::toXML(doc, e);
287}
288
290{
293 }
294
295 if (brushType() == PIPE_MASK) {
297 if (m_text.isEmpty()) {
298 // Dummy brushtip to avoid a crash...
300 return;
301 }
302 setBrushTipImage(m_brushesPipe->firstBrush()->brushTipImage());
303 }
304 else { /* if (brushType() == MASK)*/
306 }
307
309 setValid(true);
310}
311
313{
314 return brushType() == MASK ? 0 : 1 + m_brushesPipe->currentBrushIndex();
315}
316
317qint32 KisTextBrush::maskWidth(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const
318{
319 return brushType() == MASK ?
320 KisBrush::maskWidth(shape, subPixelX, subPixelY, info) :
321 m_brushesPipe->maskWidth(shape, subPixelX, subPixelY, info);
322}
323
324qint32 KisTextBrush::maskHeight(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const
325{
326 return brushType() == MASK ?
327 KisBrush::maskHeight(shape, subPixelX, subPixelY, info) :
328 m_brushesPipe->maskHeight(shape, subPixelX, subPixelY, info);
329}
330
331void KisTextBrush::setAngle(qreal _angle)
332{
333 KisBrush::setAngle(_angle);
334 m_brushesPipe->setAngle(_angle);
335}
336
337void KisTextBrush::setScale(qreal _scale)
338{
339 KisBrush::setScale(_scale);
340 m_brushesPipe->setScale(_scale);
341}
342
343void KisTextBrush::setSpacing(double _spacing)
344{
345 KisBrush::setSpacing(_spacing);
346 m_brushesPipe->setSpacing(_spacing);
347}
348
const Params2D p
virtual qint32 maskHeight(KisDabShape const &, qreal subPixelX, qreal subPixelY, const KisPaintInformation &info) const
virtual enumBrushType brushType() const
virtual void setSpacing(double spacing)
virtual void setAngle(qreal _angle)
virtual void setBrushType(enumBrushType type)
virtual void setScale(qreal _scale)
void resetOutlineCache()
double spacing() const
virtual KisFixedPaintDeviceSP paintDevice(const KoColorSpace *colorSpace, KisDabShape const &, const KisPaintInformation &info, double subPixelX=0, double subPixelY=0) const
virtual void toXML(QDomDocument &, QDomElement &) const
virtual void setBrushTipImage(const QImage &image)
virtual void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, ColoringInformation *coloringInfo, KisDabShape const &, const KisPaintInformation &info, double subPixelX, double subPixelY, qreal softnessFactor, qreal lightnessStrength) const
virtual qint32 maskWidth(KisDabShape const &, qreal subPixelX, qreal subPixelY, const KisPaintInformation &info) const
QVector< QSharedPointer< KisGbrBrush > > m_brushes
void setSpacing(double spacing)
KisFixedPaintDeviceSP paintDevice(const KoColorSpace *colorSpace, KisDabShape const &shape, const KisPaintInformation &info, double subPixelX, double subPixelY)
qint32 maskWidth(KisDabShape const &shape, double subPixelX, double subPixelY, const KisPaintInformation &info)
void addBrush(QSharedPointer< BrushType > brush)
void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation *coloringInformation, KisDabShape const &shape, const KisPaintInformation &info, double subPixelX, double subPixelY, qreal softnessFactor, qreal lightnessStrength=DEFAULT_LIGHTNESS_STRENGTH)
void prepareForSeqNo(const KisPaintInformation &info, int seqNo)
qint32 maskHeight(KisDabShape const &shape, double subPixelX, double subPixelY, const KisPaintInformation &info)
void setAngle(qreal angle)
void setScale(qreal scale)
virtual void clear()
void setPipeMode(bool pipe)
void notifyStrokeStarted() override
KoResourceSP clone() const override
bool saveToDevice(QIODevice *dev) const override
KisTextBrushesPipe * m_brushesPipe
void setText(const QString &txt)
bool pipeMode() const
bool isEphemeral() const override
void setAngle(qreal _angle) override
void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation *coloringInformation, KisDabShape const &, const KisPaintInformation &info, double subPixelX=0, double subPixelY=0, qreal softnessFactor=DEFAULT_SOFTNESS_FACTOR, qreal lightnessStrength=1.0) const override
KisFixedPaintDeviceSP paintDevice(const KoColorSpace *colorSpace, KisDabShape const &, const KisPaintInformation &info, double subPixelX, double subPixelY) const override
qint32 maskWidth(KisDabShape const &, double subPixelX, double subPixelY, const KisPaintInformation &info) const override
void setFont(const QFont &font)
qint32 maskHeight(KisDabShape const &, double subPixelX, double subPixelY, const KisPaintInformation &info) const override
void setSpacing(double _spacing) override
~KisTextBrush() override
quint32 brushIndex() const override
bool loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) override
QString text(void) const
void prepareForSeqNo(const KisPaintInformation &info, int seqNo) override
void setScale(qreal _scale) override
void toXML(QDomDocument &, QDomElement &) const override
KisGbrBrushSP firstBrush() const
KisTextBrushesPipe(const KisTextBrushesPipe &rhs)
void notifyStrokeStarted() override
void updateBrushIndexes(KisRandomSourceSP randomSource, int seqNo) override
void setText(const QString &text, const QFont &font)
int currentBrushIndex() override
int chooseNextBrush(const KisPaintInformation &info) override
static QImage renderChar(const QString &text, const QFont &font)
QMap< QChar, KisGbrBrushSP > m_brushesMap
void clear() override
#define KIS_ASSERT_RECOVER(cond)
Definition kis_assert.h:55
@ PIPE_MASK
Definition kis_brush.h:34
@ MASK
Definition kis_brush.h:32
QSharedPointer< KisBrush > KisBrushSP
Definition kis_brush.h:49
#define warnKrita
Definition kis_debug.h:87
QString toString(const QString &value)
void setValid(bool valid)