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