1 //
2 // Copyright (C) 2017 James Turner  zakalawe@mac.com
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License as
6 // published by the Free Software Foundation; either version 2 of the
7 // License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful, but
10 // WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 // General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 
18 #include "fgcanvastext.h"
19 
20 #include <QPainter>
21 #include <QDebug>
22 #include <QQmlComponent>
23 #include <QQmlEngine>
24 #include <QTextLayout>
25 
26 #include <private/qquicktextnode_p.h>
27 
28 #include "fgcanvaspaintcontext.h"
29 #include "localprop.h"
30 #include "fgqcanvasfontcache.h"
31 #include "canvasitem.h"
32 #include "canvasconnection.h"
33 
34 class TextCanvasItem : public CanvasItem
35 {
36     Q_OBJECT
37 
38 public:
TextCanvasItem(QQuickItem * parent)39     TextCanvasItem(QQuickItem* parent)
40         : CanvasItem(parent)
41     {
42         setFlag(ItemHasContents);
43     }
44 
setText(QString t)45     void setText(QString t)
46     {
47         if (t == m_text) {
48             return;
49         }
50 
51         m_text = t;
52         updateTextLayout();
53         update();
54     }
55 
setColor(QColor c)56     void setColor(QColor c)
57     {
58         m_color = c;
59         update();
60     }
61 
setAlignment(Qt::Alignment textAlign)62     void setAlignment(Qt::Alignment textAlign)
63     {
64         m_alignment = textAlign;
65         updateTextLayout();
66         update();
67     }
68 
setFont(QFont font)69     void setFont(QFont font)
70     {
71         m_font = font;
72         updateTextLayout();
73         update();
74     }
75 
updateRealPaintNode(QSGNode * oldNode,QQuickItem::UpdatePaintNodeData * data)76     QSGNode* updateRealPaintNode(QSGNode* oldNode, QQuickItem::UpdatePaintNodeData *data) override
77     {
78 
79         if (!m_textNode) {
80             m_textNode = new QQuickTextNode(this);
81         }
82 
83         m_textNode->deleteContent();
84         QPointF pos = posForAlignment();
85 
86         m_textNode->addTextLayout(pos,
87                                   &m_layout,
88                                   m_color,
89                                   QQuickText::Normal);
90 
91         return m_textNode;
92     }
93 
94 protected:
posForAlignment()95     QPointF posForAlignment()
96     {
97         float x = 0.0f;
98         float y = 0.0f;
99         const float itemWidth = width();
100         const float itemHeight = height();
101         const float textWidth = m_layout.boundingRect().width();
102         const float textHeight = m_layout.boundingRect().height();
103 
104         switch (m_alignment & Qt::AlignHorizontal_Mask) {
105         case Qt::AlignLeft:
106         case Qt::AlignJustify:
107             break;
108         case Qt::AlignRight:
109             x = itemWidth - textWidth;
110             break;
111         case Qt::AlignHCenter:
112             x = (itemWidth - textWidth) / 2;
113             break;
114         }
115 
116         switch (m_alignment & Qt::AlignVertical_Mask) {
117         case Qt::AlignTop:
118             break;
119         case Qt::AlignBottom:
120             y = itemHeight - textHeight;
121             break;
122         case Qt::AlignVCenter:
123             y = (itemHeight - textHeight) / 2;
124             break;
125         case Qt::AlignBaseline:
126             y = -m_baselineOffset;
127             break;
128         }
129 
130         return QPointF(x,y);
131     }
132 
geometryChanged(const QRectF & newGeometry,const QRectF & oldGeometry)133     void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override
134     {
135         QQuickItem::geometryChanged(newGeometry, oldGeometry);
136         update();
137     }
138 
boundingRect() const139     QRectF boundingRect() const override
140     {
141         if ((width() == 0.0) || (height() == 0.0)) {
142             return m_layout.boundingRect();
143         }
144 
145         return QQuickItem::boundingRect();
146     }
147 
updateTextLayout()148     void updateTextLayout()
149     {
150         QFontMetricsF fm(m_font);
151         m_baselineOffset = fm.ascent(); // for aligning to first base line
152 
153         m_layout.setText(m_text);
154         QTextOption textOpt(m_alignment);
155         m_layout.setTextOption(textOpt);
156         m_layout.setFont(m_font);
157 
158         m_layout.beginLayout();
159         float leading = fm.leading();
160         int lineCount = 0;
161         float y = 0.0;
162 
163         QTextLine line = m_layout.createLine();
164         while (line.isValid()) {
165             int nextBreak = m_text.indexOf('\n', line.textStart());
166             int columnsToNextBreak = nextBreak >= 0 ? nextBreak : INT_MAX;
167             line.setNumColumns(columnsToNextBreak);
168             line.setPosition(QPointF(0.0, y));
169             ++lineCount;
170             y += leading + line.height();
171             line = m_layout.createLine();
172         }
173 
174         m_layout.endLayout();
175     }
176 
177 private:
178     QQuickTextNode* m_textNode = nullptr;
179     QColor m_color;
180     QFont m_font;
181     QString m_text;
182     Qt::Alignment m_alignment = Qt::AlignCenter;
183     QTextLayout m_layout;
184     float m_baselineOffset;
185 };
186 
FGCanvasText(FGCanvasGroup * pr,LocalProp * prop)187 FGCanvasText::FGCanvasText(FGCanvasGroup* pr, LocalProp* prop) :
188     FGCanvasElement(pr, prop),
189     _metrics(QFont())
190 {
191 }
192 
createQuickItem(QQuickItem * parent)193 CanvasItem *FGCanvasText::createQuickItem(QQuickItem *parent)
194 {
195     _quickItem = new TextCanvasItem(parent);
196     markFontDirty(); // so it gets set on the new item
197     return _quickItem;
198 }
199 
quickItem() const200 CanvasItem *FGCanvasText::quickItem() const
201 {
202     return _quickItem;
203 }
204 
dumpElement()205 void FGCanvasText::dumpElement()
206 {
207     qDebug() << "Text:" << _text << " at " << _propertyRoot->path();
208 }
209 
doPaint(FGCanvasPaintContext * context) const210 void FGCanvasText::doPaint(FGCanvasPaintContext *context) const
211 {
212     context->painter()->setFont(_font);
213     QColor c = fillColor();
214     if (!c.isValid()) {
215         c = Qt::white;
216     }
217 
218     context->painter()->setPen(c);
219     context->painter()->setBrush(Qt::NoBrush);
220     QRectF rect(0, 0, 1000, 1000);
221 
222     if (_alignment & Qt::AlignBottom) {
223         rect.moveBottom(0.0);
224     } else if (_alignment & Qt::AlignVCenter) {
225         rect.moveCenter(QPointF(rect.center().x(), 0.0));
226     } else if (_alignment & Qt::AlignBaseline) {
227         // this is really annoying. Point-based drawing would align
228         // with the baseline automatically, but with no line-wrapping
229         // we need to work out the offset from the top of the box to
230         // the base line. font metrics time!
231         rect.moveTop(-_metrics.ascent());
232     }
233 
234     if (_alignment & Qt::AlignRight) {
235         rect.moveRight(0.0);
236     } else if (_alignment & Qt::AlignHCenter) {
237         rect.moveCenter(QPointF(0.0, rect.center().y()));
238     }
239 
240     context->painter()->drawText(rect, _alignment, _text);
241 
242    // context->painter()->setPen(Qt::cyan);
243    // context->painter()->drawRect(rect);
244 }
245 
doPolish()246 void FGCanvasText::doPolish()
247 {
248     if (_fontDirty) {
249         rebuildFont();
250         _fontDirty = false;
251     }
252 }
253 
markStyleDirty()254 void FGCanvasText::markStyleDirty()
255 {
256     markFontDirty();
257 }
258 
doDestroy()259 void FGCanvasText::doDestroy()
260 {
261     delete _quickItem;
262 }
263 
onChildAdded(LocalProp * prop)264 bool FGCanvasText::onChildAdded(LocalProp *prop)
265 {
266     if (FGCanvasElement::onChildAdded(prop)) {
267         return true;
268     }
269 
270     if (prop->name() == "text") {
271         connect(prop, &LocalProp::valueChanged, this, &FGCanvasText::onTextChanged);
272         return true;
273     }
274 
275     if (prop->name() == "draw-mode") {
276         connect(prop, &LocalProp::valueChanged, this, &FGCanvasText::setDrawMode);
277         return true;
278     }
279 
280     if (prop->name() == "character-aspect-ratio") {
281         return true;
282     }
283 
284     qDebug() << "text saw child:" << prop->name() << prop->index();
285     return false;
286 }
287 
onTextChanged(QVariant var)288 void FGCanvasText::onTextChanged(QVariant var)
289 {
290     _text = var.toString();
291     if (_quickItem) {
292         _quickItem->setText(var.toString());
293     }
294 }
295 
setDrawMode(QVariant var)296 void FGCanvasText::setDrawMode(QVariant var)
297 {
298     int mode = var.toInt();
299     if (mode != 1) {
300         qDebug() << _propertyRoot->path() << "draw mode is now" << mode;
301     }
302 }
303 
rebuildAlignment(QVariant var) const304 void FGCanvasText::rebuildAlignment(QVariant var) const
305 {
306     QByteArray alignString = var.toByteArray();
307     if (alignString.isEmpty()) {
308         _alignment = Qt::AlignBaseline | Qt::AlignLeft;
309         return;
310     }
311 
312     if (alignString == "center") {
313         _alignment = Qt::AlignCenter;
314 
315         if (_quickItem) {
316             _quickItem->setAlignment(_alignment);
317         }
318         return;
319     }
320 
321     Qt::Alignment newAlignment;
322     if (alignString.startsWith("left-")) {
323         newAlignment |= Qt::AlignLeft;
324     } else if (alignString.startsWith("center-")) {
325         newAlignment |= Qt::AlignHCenter;
326     } else if (alignString.startsWith("right-")) {
327         newAlignment |= Qt::AlignRight;
328     }
329 
330     if (alignString.endsWith("-baseline")) {
331         newAlignment |= Qt::AlignBaseline;
332     } else if (alignString.endsWith("-top")) {
333         newAlignment |= Qt::AlignTop;
334     } else if (alignString.endsWith("-bottom")) {
335         newAlignment |= Qt::AlignBottom;
336     } else if (alignString.endsWith("-center")) {
337         newAlignment |= Qt::AlignVCenter;
338     }
339 
340     if (newAlignment == 0) {
341         qWarning() << "implement me" << alignString;
342     }
343 
344     _alignment = newAlignment;
345     if (_quickItem) {
346         _quickItem->setAlignment(_alignment);
347     }
348 }
349 
markFontDirty()350 void FGCanvasText::markFontDirty()
351 {
352     _fontDirty = true;
353 }
354 
onFontLoaded(QByteArray name)355 void FGCanvasText::onFontLoaded(QByteArray name)
356 {
357     QByteArray fontName = getCascadedStyle("font", QString()).toByteArray();
358     if (name != fontName) {
359         return; // not our font
360     }
361 
362     auto fontCache = connection()->fontCache();
363     disconnect(fontCache, &FGQCanvasFontCache::fontLoaded, this, &FGCanvasText::onFontLoaded);
364     markFontDirty();
365 }
366 
rebuildFont() const367 void FGCanvasText::rebuildFont() const
368 {
369     QByteArray fontName = getCascadedStyle("font", QString()).toByteArray();
370     bool ok;
371     auto fontCache = connection()->fontCache();
372     QFont f = fontCache->fontForName(fontName, &ok);
373     if (!ok) {
374         // wait for the correct font
375         connect(fontCache, &FGQCanvasFontCache::fontLoaded, this, &FGCanvasText::onFontLoaded);
376         return;
377     }
378 
379     const int pixelSize = getCascadedStyle("character-size", 16).toInt();
380     f.setPixelSize(pixelSize);
381     _font = f;
382     _metrics = QFontMetricsF(_font);
383     rebuildAlignment(getCascadedStyle("alignment"));
384 
385     if (_quickItem) {
386         _quickItem->setFont(f);
387         _quickItem->setColor(fillColor());
388        // _quickItem->setProperty("fontPixelSize", pixelSize);
389     }
390 }
391 
392 #include "fgcanvastext.moc"
393