File indexing completed on 2024-05-12 15:56:12

0001 /*
0002  *  SPDX-FileCopyrightText: 2004 Cyrille Berger <cberger@cberger.net>
0003  *  SPDX-FileCopyrightText: 2011 Lukáš Tvrdý <lukast.dev@gmail.com>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "kis_text_brush.h"
0009 
0010 #include <QDomDocument>
0011 #include <QDomElement>
0012 #include <QFontMetrics>
0013 #include <QPainter>
0014 
0015 #include "kis_gbr_brush.h"
0016 #include "kis_brushes_pipe.h"
0017 #include <kis_dom_utils.h>
0018 #include <kis_threaded_text_rendering_workaround.h>
0019 
0020 #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND
0021 #include <QApplication>
0022 #include <QWidget>
0023 #include <QThread>
0024 #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */
0025 
0026 
0027 class KisTextBrushesPipe : public KisBrushesPipe<KisGbrBrush>
0028 {
0029 public:
0030     KisTextBrushesPipe() {
0031         m_charIndex = 0;
0032         m_currentBrushIndex = 0;
0033     }
0034 
0035     KisTextBrushesPipe(const KisTextBrushesPipe &rhs)
0036         : KisBrushesPipe<KisGbrBrush>(), // no copy here!
0037           m_text(rhs.m_text),
0038           m_charIndex(rhs.m_charIndex),
0039           m_currentBrushIndex(rhs.m_currentBrushIndex)
0040     {
0041         m_brushesMap.clear();
0042 
0043         QMapIterator<QChar, KisGbrBrushSP> iter(rhs.m_brushesMap);
0044         while (iter.hasNext()) {
0045             iter.next();
0046             KisGbrBrushSP brush(new KisGbrBrush(*iter.value()));
0047             m_brushesMap.insert(iter.key(), brush);
0048             KisBrushesPipe<KisGbrBrush>::addBrush(brush);
0049         }
0050     }
0051 
0052     void setText(const QString &text, const QFont &font) {
0053         m_text = text;
0054 
0055         m_charIndex = 0;
0056 
0057         clear();
0058 
0059         for (int i = 0; i < m_text.length(); i++) {
0060 
0061             const QChar letter = m_text.at(i);
0062 
0063             // skip letters that are already present in the brushes pipe
0064             if (m_brushesMap.contains(letter)) continue;
0065 
0066             QImage image = renderChar(letter, font);
0067             KisGbrBrushSP brush(new KisGbrBrush(image, letter));
0068             brush->setSpacing(0.1); // support for letter spacing?
0069             brush->makeMaskImage(false);
0070 
0071             m_brushesMap.insert(letter, brush);
0072             KisBrushesPipe<KisGbrBrush>::addBrush(brush);
0073         }
0074     }
0075 
0076     static QImage renderChar(const QString& text, const QFont &font) {
0077 #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND
0078         QWidget *focusWidget = qApp->focusWidget();
0079         if (focusWidget) {
0080             QThread *guiThread = focusWidget->thread();
0081             if (guiThread != QThread::currentThread()) {
0082                 warnKrita << "WARNING: Rendering text in non-GUI thread!"
0083                            << "That may lead to hangups and crashes on some"
0084                            << "versions of X11/Qt!";
0085             }
0086         }
0087 #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */
0088 
0089         QFontMetrics metric(font);
0090         QRect rect = metric.boundingRect(text);
0091 
0092         if (rect.isEmpty()) {
0093             rect = QRect(0, 0, 1, 1); // paint at least something
0094         }
0095 
0096         QRect paintingRect = rect.translated(-rect.x(), -rect.y());
0097 
0098         QImage renderedChar(paintingRect.size(), QImage::Format_ARGB32);
0099         QPainter p;
0100         p.begin(&renderedChar);
0101         p.setFont(font);
0102         p.fillRect(paintingRect, Qt::white);
0103         p.setPen(Qt::black);
0104         p.drawText(-rect.x(), -rect.y(), text);
0105         p.end();
0106         return renderedChar;
0107     }
0108 
0109     void clear() override {
0110         m_brushesMap.clear();
0111         KisBrushesPipe<KisGbrBrush>::clear();
0112     }
0113 
0114     KisGbrBrushSP firstBrush() const {
0115         Q_ASSERT(m_text.size() > 0);
0116         Q_ASSERT(m_brushesMap.size() > 0);
0117         return m_brushesMap.value(m_text.at(0));
0118     }
0119 
0120     void notifyStrokeStarted() override {
0121         m_charIndex = 0;
0122         updateBrushIndexesImpl();
0123     }
0124 
0125     int currentBrushIndex() override {
0126         return m_currentBrushIndex;
0127     }
0128 
0129 protected:
0130 
0131     int chooseNextBrush(const KisPaintInformation& info) override {
0132         Q_UNUSED(info);
0133         return m_currentBrushIndex;
0134     }
0135 
0136     void updateBrushIndexes(const KisPaintInformation& info, int seqNo) override {
0137         Q_UNUSED(info);
0138 
0139         if (m_text.size()) {
0140             m_charIndex = (seqNo >= 0 ? seqNo : (m_charIndex + 1)) % m_text.size();
0141         } else {
0142             m_charIndex = 0;
0143         }
0144 
0145         updateBrushIndexesImpl();
0146     }
0147 
0148 private:
0149     void updateBrushIndexesImpl() {
0150         if (m_text.isEmpty()) return;
0151 
0152         if (m_charIndex >= m_text.size()) {
0153             m_charIndex = 0;
0154         }
0155 
0156         QChar letter = m_text.at(m_charIndex);
0157         Q_ASSERT(m_brushesMap.contains(letter));
0158 
0159         m_currentBrushIndex = m_brushes.indexOf(m_brushesMap.value(letter));
0160     }
0161 
0162 private:
0163     QMap<QChar, KisGbrBrushSP> m_brushesMap;
0164     QString m_text;
0165     int m_charIndex;
0166     int m_currentBrushIndex;
0167 };
0168 
0169 
0170 KisTextBrush::KisTextBrush()
0171     : m_brushesPipe(new KisTextBrushesPipe())
0172 {
0173     setPipeMode(false);
0174 }
0175 
0176 KisTextBrush::KisTextBrush(const KisTextBrush &rhs)
0177     : KisScalingSizeBrush(rhs),
0178       m_font(rhs.m_font),
0179       m_text(rhs.m_text),
0180       m_brushesPipe(new KisTextBrushesPipe(*rhs.m_brushesPipe))
0181 {
0182 }
0183 
0184 KisTextBrush::~KisTextBrush()
0185 {
0186     delete m_brushesPipe;
0187 }
0188 
0189 KoResourceSP KisTextBrush::clone() const
0190 {
0191     return KisBrushSP(new KisTextBrush(*this));
0192 }
0193 
0194 bool KisTextBrush::isEphemeral() const
0195 {
0196     return true;
0197 }
0198 
0199 bool KisTextBrush::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface)
0200 {
0201     Q_UNUSED(dev);
0202     Q_UNUSED(resourcesInterface);
0203     return false;
0204 }
0205 
0206 bool KisTextBrush::saveToDevice(QIODevice *dev) const
0207 {
0208     Q_UNUSED(dev);
0209     return false;
0210 }
0211 
0212 void KisTextBrush::setPipeMode(bool pipe)
0213 {
0214     setBrushType(pipe ? PIPE_MASK : MASK);
0215 }
0216 
0217 bool KisTextBrush::pipeMode() const
0218 {
0219     return brushType() == PIPE_MASK;
0220 }
0221 
0222 void KisTextBrush::setText(const QString& txt)
0223 {
0224     m_text = txt;
0225 }
0226 
0227 QString KisTextBrush::text(void) const
0228 {
0229     return m_text;
0230 }
0231 
0232 void KisTextBrush::setFont(const QFont& font)
0233 {
0234     m_font = font;
0235 }
0236 
0237 QFont KisTextBrush::font()
0238 {
0239     return m_font;
0240 }
0241 
0242 void KisTextBrush::notifyStrokeStarted()
0243 {
0244     m_brushesPipe->notifyStrokeStarted();
0245 }
0246 
0247 void KisTextBrush::prepareForSeqNo(const KisPaintInformation &info, int seqNo)
0248 {
0249     m_brushesPipe->prepareForSeqNo(info, seqNo);
0250 }
0251 
0252 void KisTextBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation,
0253     KisDabShape const& shape,
0254     const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor, qreal lightnessStrength) const
0255 {
0256     if (brushType() == MASK) {
0257         KisBrush::generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor, lightnessStrength);
0258     }
0259     else { /* if (brushType() == PIPE_MASK)*/
0260         m_brushesPipe->generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor, lightnessStrength);
0261     }
0262 }
0263 
0264 KisFixedPaintDeviceSP KisTextBrush::paintDevice(const KoColorSpace * colorSpace,
0265     KisDabShape const& shape,
0266     const KisPaintInformation& info,
0267     double subPixelX, double subPixelY) const
0268 {
0269     if (brushType() == MASK) {
0270         return KisBrush::paintDevice(colorSpace, shape, info, subPixelX, subPixelY);
0271     }
0272     else { /* if (brushType() == PIPE_MASK)*/
0273         return m_brushesPipe->paintDevice(colorSpace, shape, info, subPixelX, subPixelY);
0274     }
0275 }
0276 
0277 void KisTextBrush::toXML(QDomDocument& doc, QDomElement& e) const
0278 {
0279     Q_UNUSED(doc);
0280 
0281     e.setAttribute("type", "kis_text_brush");
0282     e.setAttribute("spacing", KisDomUtils::toString(spacing()));
0283     e.setAttribute("text", m_text);
0284     e.setAttribute("font", m_font.toString());
0285     e.setAttribute("pipe", (brushType() == PIPE_MASK) ? "true" : "false");
0286     KisBrush::toXML(doc, e);
0287 }
0288 
0289 void KisTextBrush::updateBrush()
0290 {
0291     KIS_ASSERT_RECOVER((brushType() == PIPE_MASK) || (brushType() == MASK)) {
0292         setBrushType(MASK);
0293     }
0294 
0295     if (brushType() == PIPE_MASK) {
0296         m_brushesPipe->setText(m_text, m_font);
0297         setBrushTipImage(m_brushesPipe->firstBrush()->brushTipImage());
0298     }
0299     else { /* if (brushType() == MASK)*/
0300         setBrushTipImage(KisTextBrushesPipe::renderChar(m_text, m_font));
0301     }
0302 
0303     resetOutlineCache();
0304     setValid(true);
0305 }
0306 
0307 quint32 KisTextBrush::brushIndex() const
0308 {
0309     return brushType() == MASK ? 0 : 1 + m_brushesPipe->currentBrushIndex();
0310 }
0311 
0312 qint32 KisTextBrush::maskWidth(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const
0313 {
0314     return brushType() == MASK ?
0315            KisBrush::maskWidth(shape, subPixelX, subPixelY, info) :
0316            m_brushesPipe->maskWidth(shape, subPixelX, subPixelY, info);
0317 }
0318 
0319 qint32 KisTextBrush::maskHeight(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const
0320 {
0321     return brushType() == MASK ?
0322            KisBrush::maskHeight(shape, subPixelX, subPixelY, info) :
0323            m_brushesPipe->maskHeight(shape, subPixelX, subPixelY, info);
0324 }
0325 
0326 void KisTextBrush::setAngle(qreal _angle)
0327 {
0328     KisBrush::setAngle(_angle);
0329     m_brushesPipe->setAngle(_angle);
0330 }
0331 
0332 void KisTextBrush::setScale(qreal _scale)
0333 {
0334     KisBrush::setScale(_scale);
0335     m_brushesPipe->setScale(_scale);
0336 }
0337 
0338 void KisTextBrush::setSpacing(double _spacing)
0339 {
0340     KisBrush::setSpacing(_spacing);
0341     m_brushesPipe->setSpacing(_spacing);
0342 }
0343