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