1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQuick module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qquicktextnodeengine_p.h" |
41 | |
42 | #include <QtCore/qpoint.h> |
43 | #include <QtGui/qabstracttextdocumentlayout.h> |
44 | #include <QtGui/qrawfont.h> |
45 | #include <QtGui/qtextdocument.h> |
46 | #include <QtGui/qtextlayout.h> |
47 | #include <QtGui/qtextobject.h> |
48 | #include <QtGui/qtexttable.h> |
49 | #include <QtGui/qtextlist.h> |
50 | |
51 | #include <private/qquicktext_p.h> |
52 | #include <private/qquicktextdocument_p.h> |
53 | #include <private/qtextdocumentlayout_p.h> |
54 | #include <private/qtextimagehandler_p.h> |
55 | #include <private/qrawfont_p.h> |
56 | #include <private/qglyphrun_p.h> |
57 | |
58 | QT_BEGIN_NAMESPACE |
59 | |
60 | QQuickTextNodeEngine::BinaryTreeNodeKey::BinaryTreeNodeKey(BinaryTreeNode *node) |
61 | : fontEngine(QRawFontPrivate::get(font: node->glyphRun.rawFont())->fontEngine) |
62 | , clipNode(node->clipNode) |
63 | , color(node->color.rgba()) |
64 | , selectionState(node->selectionState) |
65 | { |
66 | } |
67 | |
68 | QQuickTextNodeEngine::BinaryTreeNode::BinaryTreeNode(const QGlyphRun &g, |
69 | SelectionState selState, |
70 | const QRectF &brect, |
71 | const Decorations &decs, |
72 | const QColor &c, |
73 | const QColor &bc, |
74 | const QPointF &pos, qreal a) |
75 | : glyphRun(g) |
76 | , boundingRect(brect) |
77 | , selectionState(selState) |
78 | , clipNode(nullptr) |
79 | , decorations(decs) |
80 | , color(c) |
81 | , backgroundColor(bc) |
82 | , position(pos) |
83 | , ascent(a) |
84 | , leftChildIndex(-1) |
85 | , rightChildIndex(-1) |
86 | { |
87 | QGlyphRunPrivate *d = QGlyphRunPrivate::get(glyphRun: g); |
88 | ranges.append(t: qMakePair(x: d->textRangeStart, y: d->textRangeEnd)); |
89 | } |
90 | |
91 | |
92 | void QQuickTextNodeEngine::BinaryTreeNode::insert(QVarLengthArray<BinaryTreeNode, 16> *binaryTree, const QGlyphRun &glyphRun, SelectionState selectionState, |
93 | Decorations decorations, const QColor &textColor, |
94 | const QColor &backgroundColor, const QPointF &position) |
95 | { |
96 | QRectF searchRect = glyphRun.boundingRect(); |
97 | searchRect.translate(p: position); |
98 | |
99 | if (qFuzzyIsNull(d: searchRect.width()) || qFuzzyIsNull(d: searchRect.height())) |
100 | return; |
101 | |
102 | decorations |= (glyphRun.underline() ? Decoration::Underline : Decoration::NoDecoration); |
103 | decorations |= (glyphRun.overline() ? Decoration::Overline : Decoration::NoDecoration); |
104 | decorations |= (glyphRun.strikeOut() ? Decoration::StrikeOut : Decoration::NoDecoration); |
105 | decorations |= (backgroundColor.isValid() ? Decoration::Background : Decoration::NoDecoration); |
106 | |
107 | qreal ascent = glyphRun.rawFont().ascent(); |
108 | insert(binaryTree, binaryTreeNode: BinaryTreeNode(glyphRun, |
109 | selectionState, |
110 | searchRect, |
111 | decorations, |
112 | textColor, |
113 | backgroundColor, |
114 | position, |
115 | ascent)); |
116 | } |
117 | |
118 | void QQuickTextNodeEngine::BinaryTreeNode::insert(QVarLengthArray<BinaryTreeNode, 16> *binaryTree, const BinaryTreeNode &binaryTreeNode) |
119 | { |
120 | int newIndex = binaryTree->size(); |
121 | binaryTree->append(t: binaryTreeNode); |
122 | if (newIndex == 0) |
123 | return; |
124 | |
125 | int searchIndex = 0; |
126 | forever { |
127 | BinaryTreeNode *node = binaryTree->data() + searchIndex; |
128 | if (binaryTreeNode.boundingRect.left() < node->boundingRect.left()) { |
129 | if (node->leftChildIndex < 0) { |
130 | node->leftChildIndex = newIndex; |
131 | break; |
132 | } else { |
133 | searchIndex = node->leftChildIndex; |
134 | } |
135 | } else { |
136 | if (node->rightChildIndex < 0) { |
137 | node->rightChildIndex = newIndex; |
138 | break; |
139 | } else { |
140 | searchIndex = node->rightChildIndex; |
141 | } |
142 | } |
143 | } |
144 | } |
145 | |
146 | void QQuickTextNodeEngine::BinaryTreeNode::inOrder(const QVarLengthArray<BinaryTreeNode, 16> &binaryTree, |
147 | QVarLengthArray<int> *sortedIndexes, int currentIndex) |
148 | { |
149 | Q_ASSERT(currentIndex < binaryTree.size()); |
150 | |
151 | const BinaryTreeNode *node = binaryTree.data() + currentIndex; |
152 | if (node->leftChildIndex >= 0) |
153 | inOrder(binaryTree, sortedIndexes, currentIndex: node->leftChildIndex); |
154 | |
155 | sortedIndexes->append(t: currentIndex); |
156 | |
157 | if (node->rightChildIndex >= 0) |
158 | inOrder(binaryTree, sortedIndexes, currentIndex: node->rightChildIndex); |
159 | } |
160 | |
161 | |
162 | int QQuickTextNodeEngine::addText(const QTextBlock &block, |
163 | const QTextCharFormat &charFormat, |
164 | const QColor &textColor, |
165 | const QVarLengthArray<QTextLayout::FormatRange> &colorChanges, |
166 | int textPos, int fragmentEnd, |
167 | int selectionStart, int selectionEnd) |
168 | { |
169 | if (charFormat.foreground().style() != Qt::NoBrush) |
170 | setTextColor(charFormat.foreground().color()); |
171 | else |
172 | setTextColor(textColor); |
173 | |
174 | while (textPos < fragmentEnd) { |
175 | int blockRelativePosition = textPos - block.position(); |
176 | QTextLine line = block.layout()->lineForTextPosition(pos: blockRelativePosition); |
177 | if (!currentLine().isValid() |
178 | || line.lineNumber() != currentLine().lineNumber()) { |
179 | setCurrentLine(line); |
180 | } |
181 | |
182 | Q_ASSERT(line.textLength() > 0); |
183 | int lineEnd = line.textStart() + block.position() + line.textLength(); |
184 | |
185 | int len = qMin(a: lineEnd - textPos, b: fragmentEnd - textPos); |
186 | Q_ASSERT(len > 0); |
187 | |
188 | int currentStepEnd = textPos + len; |
189 | |
190 | addGlyphsForRanges(ranges: colorChanges, |
191 | start: textPos - block.position(), |
192 | end: currentStepEnd - block.position(), |
193 | selectionStart: selectionStart - block.position(), |
194 | selectionEnd: selectionEnd - block.position()); |
195 | |
196 | textPos = currentStepEnd; |
197 | } |
198 | return textPos; |
199 | } |
200 | |
201 | void QQuickTextNodeEngine::addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations, |
202 | qreal offset, qreal thickness) |
203 | { |
204 | for (int i=0; i<textDecorations.size(); ++i) { |
205 | TextDecoration textDecoration = textDecorations.at(idx: i); |
206 | |
207 | { |
208 | QRectF &rect = textDecoration.rect; |
209 | rect.setY(qRound(d: rect.y() |
210 | + m_currentLine.ascent() |
211 | + (m_currentLine.leadingIncluded() ? m_currentLine.leading() : qreal(0.0f)) |
212 | + offset)); |
213 | rect.setHeight(thickness); |
214 | } |
215 | |
216 | m_lines.append(t: textDecoration); |
217 | } |
218 | } |
219 | |
220 | void QQuickTextNodeEngine::processCurrentLine() |
221 | { |
222 | // No glyphs, do nothing |
223 | if (m_currentLineTree.isEmpty()) |
224 | return; |
225 | |
226 | // 1. Go through current line and get correct decoration position for each node based on |
227 | // neighbouring decorations. Add decoration to global list |
228 | // 2. Create clip nodes for all selected text. Try to merge as many as possible within |
229 | // the line. |
230 | // 3. Add QRects to a list of selection rects. |
231 | // 4. Add all nodes to a global processed list |
232 | QVarLengthArray<int> sortedIndexes; // Indexes in tree sorted by x position |
233 | BinaryTreeNode::inOrder(binaryTree: m_currentLineTree, sortedIndexes: &sortedIndexes); |
234 | |
235 | Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size()); |
236 | |
237 | SelectionState currentSelectionState = Unselected; |
238 | QRectF currentRect; |
239 | |
240 | Decorations currentDecorations = Decoration::NoDecoration; |
241 | qreal underlineOffset = 0.0; |
242 | qreal underlineThickness = 0.0; |
243 | |
244 | qreal overlineOffset = 0.0; |
245 | qreal overlineThickness = 0.0; |
246 | |
247 | qreal strikeOutOffset = 0.0; |
248 | qreal strikeOutThickness = 0.0; |
249 | |
250 | QRectF decorationRect = currentRect; |
251 | |
252 | QColor lastColor; |
253 | QColor lastBackgroundColor; |
254 | |
255 | QVarLengthArray<TextDecoration> pendingUnderlines; |
256 | QVarLengthArray<TextDecoration> pendingOverlines; |
257 | QVarLengthArray<TextDecoration> pendingStrikeOuts; |
258 | if (!sortedIndexes.isEmpty()) { |
259 | QQuickDefaultClipNode *currentClipNode = m_hasSelection ? new QQuickDefaultClipNode(QRectF()) : nullptr; |
260 | bool currentClipNodeUsed = false; |
261 | for (int i=0; i<=sortedIndexes.size(); ++i) { |
262 | BinaryTreeNode *node = nullptr; |
263 | if (i < sortedIndexes.size()) { |
264 | int sortedIndex = sortedIndexes.at(idx: i); |
265 | Q_ASSERT(sortedIndex < m_currentLineTree.size()); |
266 | |
267 | node = m_currentLineTree.data() + sortedIndex; |
268 | } |
269 | |
270 | if (i == 0) |
271 | currentSelectionState = node->selectionState; |
272 | |
273 | // Update decorations |
274 | if (currentDecorations != Decoration::NoDecoration) { |
275 | decorationRect.setY(m_position.y() + m_currentLine.y()); |
276 | decorationRect.setHeight(m_currentLine.height()); |
277 | |
278 | if (node != nullptr) |
279 | decorationRect.setRight(node->boundingRect.left()); |
280 | |
281 | TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor); |
282 | if (currentDecorations & Decoration::Underline) |
283 | pendingUnderlines.append(t: textDecoration); |
284 | |
285 | if (currentDecorations & Decoration::Overline) |
286 | pendingOverlines.append(t: textDecoration); |
287 | |
288 | if (currentDecorations & Decoration::StrikeOut) |
289 | pendingStrikeOuts.append(t: textDecoration); |
290 | |
291 | if (currentDecorations & Decoration::Background) |
292 | m_backgrounds.append(t: qMakePair(x: decorationRect, y: lastBackgroundColor)); |
293 | } |
294 | |
295 | // If we've reached an unselected node from a selected node, we add the |
296 | // selection rect to the graph, and we add decoration every time the |
297 | // selection state changes, because that means the text color changes |
298 | if (node == nullptr || node->selectionState != currentSelectionState) { |
299 | currentRect.setY(m_position.y() + m_currentLine.y()); |
300 | currentRect.setHeight(m_currentLine.height()); |
301 | |
302 | if (currentSelectionState == Selected) |
303 | m_selectionRects.append(t: currentRect); |
304 | |
305 | if (currentClipNode != nullptr) { |
306 | if (!currentClipNodeUsed) { |
307 | delete currentClipNode; |
308 | } else { |
309 | currentClipNode->setIsRectangular(true); |
310 | currentClipNode->setRect(currentRect); |
311 | currentClipNode->update(); |
312 | } |
313 | } |
314 | |
315 | if (node != nullptr && m_hasSelection) |
316 | currentClipNode = new QQuickDefaultClipNode(QRectF()); |
317 | else |
318 | currentClipNode = nullptr; |
319 | currentClipNodeUsed = false; |
320 | |
321 | if (node != nullptr) { |
322 | currentSelectionState = node->selectionState; |
323 | currentRect = node->boundingRect; |
324 | |
325 | // Make sure currentRect is valid, otherwise the unite won't work |
326 | if (currentRect.isNull()) |
327 | currentRect.setSize(QSizeF(1, 1)); |
328 | } |
329 | } else { |
330 | if (currentRect.isNull()) |
331 | currentRect = node->boundingRect; |
332 | else |
333 | currentRect = currentRect.united(r: node->boundingRect); |
334 | } |
335 | |
336 | if (node != nullptr) { |
337 | if (node->selectionState == Selected) { |
338 | node->clipNode = currentClipNode; |
339 | currentClipNodeUsed = true; |
340 | } |
341 | |
342 | decorationRect = node->boundingRect; |
343 | |
344 | // If previous item(s) had underline and current does not, then we add the |
345 | // pending lines to the lists and likewise for overlines and strikeouts |
346 | if (!pendingUnderlines.isEmpty() |
347 | && !(node->decorations & Decoration::Underline)) { |
348 | addTextDecorations(textDecorations: pendingUnderlines, offset: underlineOffset, thickness: underlineThickness); |
349 | |
350 | pendingUnderlines.clear(); |
351 | |
352 | underlineOffset = 0.0; |
353 | underlineThickness = 0.0; |
354 | } |
355 | |
356 | // ### Add pending when overlineOffset/thickness changes to minimize number of |
357 | // nodes |
358 | if (!pendingOverlines.isEmpty()) { |
359 | addTextDecorations(textDecorations: pendingOverlines, offset: overlineOffset, thickness: overlineThickness); |
360 | |
361 | pendingOverlines.clear(); |
362 | |
363 | overlineOffset = 0.0; |
364 | overlineThickness = 0.0; |
365 | } |
366 | |
367 | // ### Add pending when overlineOffset/thickness changes to minimize number of |
368 | // nodes |
369 | if (!pendingStrikeOuts.isEmpty()) { |
370 | addTextDecorations(textDecorations: pendingStrikeOuts, offset: strikeOutOffset, thickness: strikeOutThickness); |
371 | |
372 | pendingStrikeOuts.clear(); |
373 | |
374 | strikeOutOffset = 0.0; |
375 | strikeOutThickness = 0.0; |
376 | } |
377 | |
378 | // Merge current values with previous. Prefer greatest thickness |
379 | QRawFont rawFont = node->glyphRun.rawFont(); |
380 | if (node->decorations & Decoration::Underline) { |
381 | if (rawFont.lineThickness() > underlineThickness) { |
382 | underlineThickness = rawFont.lineThickness(); |
383 | underlineOffset = rawFont.underlinePosition(); |
384 | } |
385 | } |
386 | |
387 | if (node->decorations & Decoration::Overline) { |
388 | overlineOffset = -rawFont.ascent(); |
389 | overlineThickness = rawFont.lineThickness(); |
390 | } |
391 | |
392 | if (node->decorations & Decoration::StrikeOut) { |
393 | strikeOutThickness = rawFont.lineThickness(); |
394 | strikeOutOffset = rawFont.ascent() / -3.0; |
395 | } |
396 | |
397 | currentDecorations = node->decorations; |
398 | lastColor = node->color; |
399 | lastBackgroundColor = node->backgroundColor; |
400 | m_processedNodes.append(t: *node); |
401 | } |
402 | } |
403 | |
404 | if (!pendingUnderlines.isEmpty()) |
405 | addTextDecorations(textDecorations: pendingUnderlines, offset: underlineOffset, thickness: underlineThickness); |
406 | |
407 | if (!pendingOverlines.isEmpty()) |
408 | addTextDecorations(textDecorations: pendingOverlines, offset: overlineOffset, thickness: overlineThickness); |
409 | |
410 | if (!pendingStrikeOuts.isEmpty()) |
411 | addTextDecorations(textDecorations: pendingStrikeOuts, offset: strikeOutOffset, thickness: strikeOutThickness); |
412 | } |
413 | |
414 | m_currentLineTree.clear(); |
415 | m_currentLine = QTextLine(); |
416 | m_hasSelection = false; |
417 | } |
418 | |
419 | void QQuickTextNodeEngine::addImage(const QRectF &rect, const QImage &image, qreal ascent, |
420 | SelectionState selectionState, |
421 | QTextFrameFormat::Position layoutPosition) |
422 | { |
423 | QRectF searchRect = rect; |
424 | if (layoutPosition == QTextFrameFormat::InFlow) { |
425 | if (m_currentLineTree.isEmpty()) { |
426 | qreal y = m_currentLine.ascent() - ascent; |
427 | if (m_currentTextDirection == Qt::RightToLeft) |
428 | searchRect.moveTopRight(p: m_position + m_currentLine.rect().topRight() + QPointF(0, y)); |
429 | else |
430 | searchRect.moveTopLeft(p: m_position + m_currentLine.position() + QPointF(0, y)); |
431 | } else { |
432 | const BinaryTreeNode *lastNode = m_currentLineTree.data() + m_currentLineTree.size() - 1; |
433 | if (lastNode->glyphRun.isRightToLeft()) { |
434 | QPointF lastPos = lastNode->boundingRect.topLeft(); |
435 | searchRect.moveTopRight(p: lastPos - QPointF(0, ascent - lastNode->ascent)); |
436 | } else { |
437 | QPointF lastPos = lastNode->boundingRect.topRight(); |
438 | searchRect.moveTopLeft(p: lastPos - QPointF(0, ascent - lastNode->ascent)); |
439 | } |
440 | } |
441 | } |
442 | |
443 | BinaryTreeNode::insert(binaryTree: &m_currentLineTree, rect: searchRect, image, ascent, selectionState); |
444 | m_hasContents = true; |
445 | } |
446 | |
447 | void QQuickTextNodeEngine::addTextObject(const QTextBlock &block, const QPointF &position, const QTextCharFormat &format, |
448 | SelectionState selectionState, |
449 | QTextDocument *textDocument, int pos, |
450 | QTextFrameFormat::Position layoutPosition) |
451 | { |
452 | QTextObjectInterface *handler = textDocument->documentLayout()->handlerForObject(objectType: format.objectType()); |
453 | if (handler != nullptr) { |
454 | QImage image; |
455 | QSizeF size = handler->intrinsicSize(doc: textDocument, posInDocument: pos, format); |
456 | |
457 | if (format.objectType() == QTextFormat::ImageObject) { |
458 | QTextImageFormat imageFormat = format.toImageFormat(); |
459 | if (QQuickTextDocumentWithImageResources *imageDoc = qobject_cast<QQuickTextDocumentWithImageResources *>(object: textDocument)) { |
460 | image = imageDoc->image(format: imageFormat); |
461 | |
462 | if (image.isNull()) |
463 | return; |
464 | } else { |
465 | QTextImageHandler *imageHandler = static_cast<QTextImageHandler *>(handler); |
466 | image = imageHandler->image(doc: textDocument, imageFormat); |
467 | } |
468 | } |
469 | |
470 | if (image.isNull()) { |
471 | image = QImage(size.toSize(), QImage::Format_ARGB32_Premultiplied); |
472 | image.fill(color: Qt::transparent); |
473 | { |
474 | QPainter painter(&image); |
475 | handler->drawObject(painter: &painter, rect: image.rect(), doc: textDocument, posInDocument: pos, format); |
476 | } |
477 | } |
478 | |
479 | // Use https://developer.mozilla.org/de/docs/Web/CSS/vertical-align as a reference |
480 | // The top/bottom positions are supposed to be higher/lower than the text and reference |
481 | // the line height, not the text height (using QFontMetrics) |
482 | qreal ascent; |
483 | QTextLine line = block.layout()->lineForTextPosition(pos: pos - block.position()); |
484 | switch (format.verticalAlignment()) |
485 | { |
486 | case QTextCharFormat::AlignTop: |
487 | ascent = line.ascent(); |
488 | break; |
489 | case QTextCharFormat::AlignMiddle: |
490 | // Middlepoint of line (height - descent) + Half object height |
491 | ascent = (line.ascent() + line.descent()) / 2 - line.descent() + size.height() / 2; |
492 | break; |
493 | case QTextCharFormat::AlignBottom: |
494 | ascent = size.height() - line.descent(); |
495 | break; |
496 | case QTextCharFormat::AlignBaseline: |
497 | default: |
498 | ascent = size.height(); |
499 | } |
500 | |
501 | addImage(rect: QRectF(position, size), image, ascent, selectionState, layoutPosition); |
502 | } |
503 | } |
504 | |
505 | void QQuickTextNodeEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun) |
506 | { |
507 | BinaryTreeNode::insert(binaryTree: &m_currentLineTree, |
508 | glyphRun, |
509 | selectionState: Unselected, |
510 | decorations: Decoration::NoDecoration, |
511 | textColor: m_textColor, |
512 | backgroundColor: m_backgroundColor, |
513 | position: m_position); |
514 | } |
515 | |
516 | void QQuickTextNodeEngine::addSelectedGlyphs(const QGlyphRun &glyphRun) |
517 | { |
518 | int currentSize = m_currentLineTree.size(); |
519 | BinaryTreeNode::insert(binaryTree: &m_currentLineTree, |
520 | glyphRun, |
521 | selectionState: Selected, |
522 | decorations: Decoration::NoDecoration, |
523 | textColor: m_textColor, |
524 | backgroundColor: m_backgroundColor, |
525 | position: m_position); |
526 | m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize; |
527 | } |
528 | |
529 | void QQuickTextNodeEngine::addGlyphsForRanges(const QVarLengthArray<QTextLayout::FormatRange> &ranges, |
530 | int start, int end, |
531 | int selectionStart, int selectionEnd) |
532 | { |
533 | int currentPosition = start; |
534 | int remainingLength = end - start; |
535 | for (int j=0; j<ranges.size(); ++j) { |
536 | const QTextLayout::FormatRange &range = ranges.at(idx: j); |
537 | if (range.start + range.length > currentPosition |
538 | && range.start < currentPosition + remainingLength) { |
539 | |
540 | if (range.start > currentPosition) { |
541 | addGlyphsInRange(rangeStart: currentPosition, rangeEnd: range.start - currentPosition, |
542 | color: QColor(), backgroundColor: QColor(), selectionStart, selectionEnd); |
543 | } |
544 | int rangeEnd = qMin(a: range.start + range.length, b: currentPosition + remainingLength); |
545 | QColor rangeColor; |
546 | if (range.format.hasProperty(propertyId: QTextFormat::ForegroundBrush)) |
547 | rangeColor = range.format.foreground().color(); |
548 | else if (range.format.isAnchor()) |
549 | rangeColor = m_anchorColor; |
550 | QColor rangeBackgroundColor = range.format.hasProperty(propertyId: QTextFormat::BackgroundBrush) |
551 | ? range.format.background().color() |
552 | : QColor(); |
553 | |
554 | addGlyphsInRange(rangeStart: range.start, rangeEnd: rangeEnd - range.start, |
555 | color: rangeColor, backgroundColor: rangeBackgroundColor, |
556 | selectionStart, selectionEnd); |
557 | |
558 | currentPosition = range.start + range.length; |
559 | remainingLength = end - currentPosition; |
560 | |
561 | } else if (range.start > currentPosition + remainingLength || remainingLength <= 0) { |
562 | break; |
563 | } |
564 | } |
565 | |
566 | if (remainingLength > 0) { |
567 | addGlyphsInRange(rangeStart: currentPosition, rangeEnd: remainingLength, color: QColor(), backgroundColor: QColor(), |
568 | selectionStart, selectionEnd); |
569 | } |
570 | |
571 | } |
572 | |
573 | void QQuickTextNodeEngine::addGlyphsInRange(int rangeStart, int rangeLength, |
574 | const QColor &color, const QColor &backgroundColor, |
575 | int selectionStart, int selectionEnd) |
576 | { |
577 | QColor oldColor; |
578 | if (color.isValid()) { |
579 | oldColor = m_textColor; |
580 | m_textColor = color; |
581 | } |
582 | |
583 | QColor oldBackgroundColor = m_backgroundColor; |
584 | if (backgroundColor.isValid()) { |
585 | oldBackgroundColor = m_backgroundColor; |
586 | m_backgroundColor = backgroundColor; |
587 | } |
588 | |
589 | bool hasSelection = selectionEnd >= 0 |
590 | && selectionStart <= selectionEnd; |
591 | |
592 | QTextLine &line = m_currentLine; |
593 | int rangeEnd = rangeStart + rangeLength; |
594 | if (!hasSelection || (selectionStart > rangeEnd || selectionEnd < rangeStart)) { |
595 | QList<QGlyphRun> glyphRuns = line.glyphRuns(from: rangeStart, length: rangeLength); |
596 | for (int j=0; j<glyphRuns.size(); ++j) { |
597 | const QGlyphRun &glyphRun = glyphRuns.at(i: j); |
598 | addUnselectedGlyphs(glyphRun); |
599 | } |
600 | } else { |
601 | if (rangeStart < selectionStart) { |
602 | int length = qMin(a: selectionStart - rangeStart, b: rangeLength); |
603 | QList<QGlyphRun> glyphRuns = line.glyphRuns(from: rangeStart, length); |
604 | for (int j=0; j<glyphRuns.size(); ++j) { |
605 | const QGlyphRun &glyphRun = glyphRuns.at(i: j); |
606 | addUnselectedGlyphs(glyphRun); |
607 | } |
608 | } |
609 | |
610 | if (rangeEnd > selectionStart) { |
611 | int start = qMax(a: selectionStart, b: rangeStart); |
612 | int length = qMin(a: selectionEnd - start + 1, b: rangeEnd - start); |
613 | QList<QGlyphRun> glyphRuns = line.glyphRuns(from: start, length); |
614 | |
615 | for (int j=0; j<glyphRuns.size(); ++j) { |
616 | const QGlyphRun &glyphRun = glyphRuns.at(i: j); |
617 | addSelectedGlyphs(glyphRun); |
618 | } |
619 | } |
620 | |
621 | if (selectionEnd >= rangeStart && selectionEnd < rangeEnd) { |
622 | int start = selectionEnd + 1; |
623 | int length = rangeEnd - selectionEnd - 1; |
624 | QList<QGlyphRun> glyphRuns = line.glyphRuns(from: start, length); |
625 | for (int j=0; j<glyphRuns.size(); ++j) { |
626 | const QGlyphRun &glyphRun = glyphRuns.at(i: j); |
627 | addUnselectedGlyphs(glyphRun); |
628 | } |
629 | } |
630 | } |
631 | |
632 | if (backgroundColor.isValid()) |
633 | m_backgroundColor = oldBackgroundColor; |
634 | |
635 | if (oldColor.isValid()) |
636 | m_textColor = oldColor; |
637 | } |
638 | |
639 | void QQuickTextNodeEngine::addBorder(const QRectF &rect, qreal border, |
640 | QTextFrameFormat::BorderStyle borderStyle, |
641 | const QBrush &borderBrush) |
642 | { |
643 | const QColor &color = borderBrush.color(); |
644 | |
645 | // Currently we don't support other styles than solid |
646 | Q_UNUSED(borderStyle); |
647 | |
648 | m_backgrounds.append(t: qMakePair(x: QRectF(rect.left(), rect.top(), border, rect.height() + border), y: color)); |
649 | m_backgrounds.append(t: qMakePair(x: QRectF(rect.left() + border, rect.top(), rect.width(), border), y: color)); |
650 | m_backgrounds.append(t: qMakePair(x: QRectF(rect.right(), rect.top() + border, border, rect.height() - border), y: color)); |
651 | m_backgrounds.append(t: qMakePair(x: QRectF(rect.left() + border, rect.bottom(), rect.width(), border), y: color)); |
652 | } |
653 | |
654 | void QQuickTextNodeEngine::addFrameDecorations(QTextDocument *document, QTextFrame *frame) |
655 | { |
656 | QTextDocumentLayout *documentLayout = qobject_cast<QTextDocumentLayout *>(object: document->documentLayout()); |
657 | if (Q_UNLIKELY(!documentLayout)) |
658 | return; |
659 | |
660 | QTextFrameFormat frameFormat = frame->format().toFrameFormat(); |
661 | QTextTable *table = qobject_cast<QTextTable *>(object: frame); |
662 | |
663 | QRectF boundingRect = table == nullptr |
664 | ? documentLayout->frameBoundingRect(frame) |
665 | : documentLayout->tableBoundingRect(table); |
666 | |
667 | QBrush bg = frame->frameFormat().background(); |
668 | if (bg.style() != Qt::NoBrush) |
669 | m_backgrounds.append(t: qMakePair(x: boundingRect, y: bg.color())); |
670 | |
671 | if (!frameFormat.hasProperty(propertyId: QTextFormat::FrameBorder)) |
672 | return; |
673 | |
674 | qreal borderWidth = frameFormat.border(); |
675 | if (qFuzzyIsNull(d: borderWidth)) |
676 | return; |
677 | |
678 | QBrush borderBrush = frameFormat.borderBrush(); |
679 | QTextFrameFormat::BorderStyle borderStyle = frameFormat.borderStyle(); |
680 | if (borderStyle == QTextFrameFormat::BorderStyle_None) |
681 | return; |
682 | |
683 | addBorder(rect: boundingRect.adjusted(xp1: frameFormat.leftMargin(), yp1: frameFormat.topMargin(), |
684 | xp2: -frameFormat.rightMargin(), yp2: -frameFormat.bottomMargin()), |
685 | border: borderWidth, borderStyle, borderBrush); |
686 | if (table != nullptr) { |
687 | int rows = table->rows(); |
688 | int columns = table->columns(); |
689 | |
690 | for (int row=0; row<rows; ++row) { |
691 | for (int column=0; column<columns; ++column) { |
692 | QTextTableCell cell = table->cellAt(row, col: column); |
693 | |
694 | QRectF cellRect = documentLayout->tableCellBoundingRect(table, cell); |
695 | addBorder(rect: cellRect.adjusted(xp1: -borderWidth, yp1: -borderWidth, xp2: 0, yp2: 0), border: borderWidth, |
696 | borderStyle, borderBrush); |
697 | } |
698 | } |
699 | } |
700 | } |
701 | |
702 | uint qHash(const QQuickTextNodeEngine::BinaryTreeNodeKey &key) |
703 | { |
704 | // Just use the default hash for pairs |
705 | return qHash(key: qMakePair(x: key.fontEngine, y: qMakePair(x: key.clipNode, |
706 | y: qMakePair(x: key.color, y: key.selectionState)))); |
707 | } |
708 | |
709 | void QQuickTextNodeEngine::mergeProcessedNodes(QList<BinaryTreeNode *> *regularNodes, |
710 | QList<BinaryTreeNode *> *imageNodes) |
711 | { |
712 | QHash<BinaryTreeNodeKey, QList<BinaryTreeNode *> > map; |
713 | |
714 | for (int i = 0; i < m_processedNodes.size(); ++i) { |
715 | BinaryTreeNode *node = m_processedNodes.data() + i; |
716 | |
717 | if (node->image.isNull()) { |
718 | BinaryTreeNodeKey key(node); |
719 | |
720 | QList<BinaryTreeNode *> &nodes = map[key]; |
721 | if (nodes.isEmpty()) |
722 | regularNodes->append(t: node); |
723 | |
724 | nodes.append(t: node); |
725 | } else { |
726 | imageNodes->append(t: node); |
727 | } |
728 | } |
729 | |
730 | for (int i = 0; i < regularNodes->size(); ++i) { |
731 | BinaryTreeNode *primaryNode = regularNodes->at(i); |
732 | BinaryTreeNodeKey key(primaryNode); |
733 | |
734 | const QList<BinaryTreeNode *> &nodes = map.value(akey: key); |
735 | Q_ASSERT(nodes.first() == primaryNode); |
736 | |
737 | int count = 0; |
738 | for (int j = 0; j < nodes.size(); ++j) |
739 | count += nodes.at(i: j)->glyphRun.glyphIndexes().size(); |
740 | |
741 | if (count != primaryNode->glyphRun.glyphIndexes().size()) { |
742 | QGlyphRun &glyphRun = primaryNode->glyphRun; |
743 | QVector<quint32> glyphIndexes = glyphRun.glyphIndexes(); |
744 | glyphIndexes.reserve(asize: count); |
745 | |
746 | QVector<QPointF> glyphPositions = glyphRun.positions(); |
747 | glyphPositions.reserve(asize: count); |
748 | |
749 | QRectF glyphBoundingRect = glyphRun.boundingRect(); |
750 | |
751 | for (int j = 1; j < nodes.size(); ++j) { |
752 | BinaryTreeNode *otherNode = nodes.at(i: j); |
753 | glyphIndexes += otherNode->glyphRun.glyphIndexes(); |
754 | primaryNode->ranges += otherNode->ranges; |
755 | glyphBoundingRect = glyphBoundingRect.united(r: otherNode->boundingRect); |
756 | |
757 | QVector<QPointF> otherPositions = otherNode->glyphRun.positions(); |
758 | for (int k = 0; k < otherPositions.size(); ++k) |
759 | glyphPositions += otherPositions.at(i: k) + (otherNode->position - primaryNode->position); |
760 | } |
761 | |
762 | Q_ASSERT(glyphPositions.size() == count); |
763 | Q_ASSERT(glyphIndexes.size() == count); |
764 | |
765 | glyphRun.setGlyphIndexes(glyphIndexes); |
766 | glyphRun.setPositions(glyphPositions); |
767 | glyphRun.setBoundingRect(glyphBoundingRect); |
768 | } |
769 | } |
770 | } |
771 | |
772 | void QQuickTextNodeEngine::addToSceneGraph(QQuickTextNode *parentNode, |
773 | QQuickText::TextStyle style, |
774 | const QColor &styleColor) |
775 | { |
776 | if (m_currentLine.isValid()) |
777 | processCurrentLine(); |
778 | |
779 | QList<BinaryTreeNode *> nodes; |
780 | QList<BinaryTreeNode *> imageNodes; |
781 | mergeProcessedNodes(regularNodes: &nodes, imageNodes: &imageNodes); |
782 | |
783 | for (int i = 0; i < m_backgrounds.size(); ++i) { |
784 | const QRectF &rect = m_backgrounds.at(i).first; |
785 | const QColor &color = m_backgrounds.at(i).second; |
786 | if (color.alpha() != 0) |
787 | parentNode->addRectangleNode(rect, color); |
788 | } |
789 | |
790 | // Add all text with unselected color first |
791 | for (int i = 0; i < nodes.size(); ++i) { |
792 | const BinaryTreeNode *node = nodes.at(i); |
793 | parentNode->addGlyphs(position: node->position, glyphs: node->glyphRun, color: node->color, style, styleColor, parentNode: nullptr); |
794 | } |
795 | |
796 | for (int i = 0; i < imageNodes.size(); ++i) { |
797 | const BinaryTreeNode *node = imageNodes.at(i); |
798 | if (node->selectionState == Unselected) |
799 | parentNode->addImage(rect: node->boundingRect, image: node->image); |
800 | } |
801 | |
802 | // Then, prepend all selection rectangles to the tree |
803 | for (int i = 0; i < m_selectionRects.size(); ++i) { |
804 | const QRectF &rect = m_selectionRects.at(i); |
805 | if (m_selectionColor.alpha() != 0) |
806 | parentNode->addRectangleNode(rect, color: m_selectionColor); |
807 | } |
808 | |
809 | // Add decorations for each node to the tree. |
810 | for (int i = 0; i < m_lines.size(); ++i) { |
811 | const TextDecoration &textDecoration = m_lines.at(i); |
812 | |
813 | QColor color = textDecoration.selectionState == Selected |
814 | ? m_selectedTextColor |
815 | : textDecoration.color; |
816 | |
817 | parentNode->addRectangleNode(rect: textDecoration.rect, color); |
818 | } |
819 | |
820 | // Finally add the selected text on top of everything |
821 | for (int i = 0; i < nodes.size(); ++i) { |
822 | const BinaryTreeNode *node = nodes.at(i); |
823 | QQuickDefaultClipNode *clipNode = node->clipNode; |
824 | if (clipNode != nullptr && clipNode->parent() == nullptr) |
825 | parentNode->appendChildNode(node: clipNode); |
826 | |
827 | if (node->selectionState == Selected) { |
828 | QColor color = m_selectedTextColor; |
829 | int previousNodeIndex = i - 1; |
830 | int nextNodeIndex = i + 1; |
831 | const BinaryTreeNode *previousNode = previousNodeIndex < 0 ? 0 : nodes.at(i: previousNodeIndex); |
832 | while (previousNode != nullptr && qFuzzyCompare(p1: previousNode->boundingRect.left(), p2: node->boundingRect.left())) |
833 | previousNode = --previousNodeIndex < 0 ? 0 : nodes.at(i: previousNodeIndex); |
834 | |
835 | const BinaryTreeNode *nextNode = nextNodeIndex == nodes.size() ? 0 : nodes.at(i: nextNodeIndex); |
836 | |
837 | if (previousNode != nullptr && previousNode->selectionState == Unselected) |
838 | parentNode->addGlyphs(position: previousNode->position, glyphs: previousNode->glyphRun, color, style, styleColor, parentNode: clipNode); |
839 | |
840 | if (nextNode != nullptr && nextNode->selectionState == Unselected) |
841 | parentNode->addGlyphs(position: nextNode->position, glyphs: nextNode->glyphRun, color, style, styleColor, parentNode: clipNode); |
842 | |
843 | // If the previous or next node completely overlaps this one, then we have already drawn the glyphs of |
844 | // this node |
845 | bool drawCurrent = false; |
846 | if (previousNode != nullptr || nextNode != nullptr) { |
847 | for (int i = 0; i < node->ranges.size(); ++i) { |
848 | const QPair<int, int> &range = node->ranges.at(i); |
849 | |
850 | int rangeLength = range.second - range.first + 1; |
851 | if (previousNode != nullptr) { |
852 | for (int j = 0; j < previousNode->ranges.size(); ++j) { |
853 | const QPair<int, int> &otherRange = previousNode->ranges.at(i: j); |
854 | |
855 | if (range.first < otherRange.second && range.second > otherRange.first) { |
856 | int start = qMax(a: range.first, b: otherRange.first); |
857 | int end = qMin(a: range.second, b: otherRange.second); |
858 | rangeLength -= end - start + 1; |
859 | if (rangeLength == 0) |
860 | break; |
861 | } |
862 | } |
863 | } |
864 | |
865 | if (nextNode != nullptr && rangeLength > 0) { |
866 | for (int j = 0; j < nextNode->ranges.size(); ++j) { |
867 | const QPair<int, int> &otherRange = nextNode->ranges.at(i: j); |
868 | |
869 | if (range.first < otherRange.second && range.second > otherRange.first) { |
870 | int start = qMax(a: range.first, b: otherRange.first); |
871 | int end = qMin(a: range.second, b: otherRange.second); |
872 | rangeLength -= end - start + 1; |
873 | if (rangeLength == 0) |
874 | break; |
875 | } |
876 | } |
877 | } |
878 | |
879 | if (rangeLength > 0) { |
880 | drawCurrent = true; |
881 | break; |
882 | } |
883 | } |
884 | } else { |
885 | drawCurrent = true; |
886 | } |
887 | |
888 | if (drawCurrent) |
889 | parentNode->addGlyphs(position: node->position, glyphs: node->glyphRun, color, style, styleColor, parentNode: clipNode); |
890 | } |
891 | } |
892 | |
893 | for (int i = 0; i < imageNodes.size(); ++i) { |
894 | const BinaryTreeNode *node = imageNodes.at(i); |
895 | if (node->selectionState == Selected) { |
896 | parentNode->addImage(rect: node->boundingRect, image: node->image); |
897 | if (node->selectionState == Selected) { |
898 | QColor color = m_selectionColor; |
899 | color.setAlpha(128); |
900 | parentNode->addRectangleNode(rect: node->boundingRect, color); |
901 | } |
902 | } |
903 | } |
904 | } |
905 | |
906 | void QQuickTextNodeEngine::mergeFormats(QTextLayout *textLayout, QVarLengthArray<QTextLayout::FormatRange> *mergedFormats) |
907 | { |
908 | Q_ASSERT(mergedFormats != nullptr); |
909 | if (textLayout == nullptr) |
910 | return; |
911 | |
912 | QVector<QTextLayout::FormatRange> additionalFormats = textLayout->formats(); |
913 | for (int i=0; i<additionalFormats.size(); ++i) { |
914 | QTextLayout::FormatRange additionalFormat = additionalFormats.at(i); |
915 | if (additionalFormat.format.hasProperty(propertyId: QTextFormat::ForegroundBrush) |
916 | || additionalFormat.format.hasProperty(propertyId: QTextFormat::BackgroundBrush) |
917 | || additionalFormat.format.isAnchor()) { |
918 | // Merge overlapping formats |
919 | if (!mergedFormats->isEmpty()) { |
920 | QTextLayout::FormatRange *lastFormat = mergedFormats->data() + mergedFormats->size() - 1; |
921 | |
922 | if (additionalFormat.start < lastFormat->start + lastFormat->length) { |
923 | QTextLayout::FormatRange *mergedRange = nullptr; |
924 | |
925 | int length = additionalFormat.length; |
926 | if (additionalFormat.start > lastFormat->start) { |
927 | lastFormat->length = additionalFormat.start - lastFormat->start; |
928 | length -= lastFormat->length; |
929 | |
930 | mergedFormats->append(t: QTextLayout::FormatRange()); |
931 | mergedRange = mergedFormats->data() + mergedFormats->size() - 1; |
932 | lastFormat = mergedFormats->data() + mergedFormats->size() - 2; |
933 | } else { |
934 | mergedRange = lastFormat; |
935 | } |
936 | |
937 | mergedRange->format = lastFormat->format; |
938 | mergedRange->format.merge(other: additionalFormat.format); |
939 | mergedRange->start = additionalFormat.start; |
940 | |
941 | int end = qMin(a: additionalFormat.start + additionalFormat.length, |
942 | b: lastFormat->start + lastFormat->length); |
943 | |
944 | mergedRange->length = end - mergedRange->start; |
945 | length -= mergedRange->length; |
946 | |
947 | additionalFormat.start = end; |
948 | additionalFormat.length = length; |
949 | } |
950 | } |
951 | |
952 | if (additionalFormat.length > 0) |
953 | mergedFormats->append(t: additionalFormat); |
954 | } |
955 | } |
956 | |
957 | } |
958 | |
959 | void QQuickTextNodeEngine::addTextBlock(QTextDocument *textDocument, const QTextBlock &block, const QPointF &position, const QColor &textColor, const QColor &anchorColor, int selectionStart, int selectionEnd) |
960 | { |
961 | Q_ASSERT(textDocument); |
962 | #if QT_CONFIG(im) |
963 | int preeditLength = block.isValid() ? block.layout()->preeditAreaText().length() : 0; |
964 | int preeditPosition = block.isValid() ? block.layout()->preeditAreaPosition() : -1; |
965 | #endif |
966 | |
967 | setCurrentTextDirection(block.textDirection()); |
968 | |
969 | QVarLengthArray<QTextLayout::FormatRange> colorChanges; |
970 | mergeFormats(textLayout: block.layout(), mergedFormats: &colorChanges); |
971 | |
972 | const QTextCharFormat charFormat = block.charFormat(); |
973 | const QRectF blockBoundingRect = textDocument->documentLayout()->blockBoundingRect(block).translated(p: position); |
974 | |
975 | if (charFormat.background().style() != Qt::NoBrush) |
976 | m_backgrounds.append(t: qMakePair(x: blockBoundingRect, y: charFormat.background().color())); |
977 | |
978 | if (QTextList *textList = block.textList()) { |
979 | QPointF pos = blockBoundingRect.topLeft(); |
980 | QTextLayout *layout = block.layout(); |
981 | if (layout->lineCount() > 0) { |
982 | QTextLine firstLine = layout->lineAt(i: 0); |
983 | Q_ASSERT(firstLine.isValid()); |
984 | |
985 | setCurrentLine(firstLine); |
986 | |
987 | QRectF textRect = firstLine.naturalTextRect(); |
988 | pos += textRect.topLeft(); |
989 | if (block.textDirection() == Qt::RightToLeft) |
990 | pos.rx() += textRect.width(); |
991 | |
992 | QFont font(charFormat.font()); |
993 | QFontMetricsF fontMetrics(font); |
994 | QTextListFormat listFormat = textList->format(); |
995 | |
996 | QString listItemBullet; |
997 | switch (listFormat.style()) { |
998 | case QTextListFormat::ListCircle: |
999 | listItemBullet = QChar(0x25E6); // White bullet |
1000 | break; |
1001 | case QTextListFormat::ListSquare: |
1002 | listItemBullet = QChar(0x25AA); // Black small square |
1003 | break; |
1004 | case QTextListFormat::ListDecimal: |
1005 | case QTextListFormat::ListLowerAlpha: |
1006 | case QTextListFormat::ListUpperAlpha: |
1007 | case QTextListFormat::ListLowerRoman: |
1008 | case QTextListFormat::ListUpperRoman: |
1009 | listItemBullet = textList->itemText(block); |
1010 | break; |
1011 | default: |
1012 | listItemBullet = QChar(0x2022); // Black bullet |
1013 | break; |
1014 | }; |
1015 | |
1016 | switch (block.blockFormat().marker()) { |
1017 | case QTextBlockFormat::MarkerType::Checked: |
1018 | listItemBullet = QChar(0x2612); // Checked checkbox |
1019 | break; |
1020 | case QTextBlockFormat::MarkerType::Unchecked: |
1021 | listItemBullet = QChar(0x2610); // Unchecked checkbox |
1022 | break; |
1023 | case QTextBlockFormat::MarkerType::NoMarker: |
1024 | break; |
1025 | } |
1026 | |
1027 | QSizeF size(fontMetrics.horizontalAdvance(string: listItemBullet), fontMetrics.height()); |
1028 | qreal xoff = fontMetrics.horizontalAdvance(QLatin1Char(' ')); |
1029 | if (block.textDirection() == Qt::LeftToRight) |
1030 | xoff = -xoff - size.width(); |
1031 | setPosition(pos + QPointF(xoff, 0)); |
1032 | |
1033 | QTextLayout layout; |
1034 | layout.setFont(font); |
1035 | layout.setText(listItemBullet); // Bullet |
1036 | layout.beginLayout(); |
1037 | QTextLine line = layout.createLine(); |
1038 | line.setPosition(QPointF(0, 0)); |
1039 | layout.endLayout(); |
1040 | |
1041 | QList<QGlyphRun> glyphRuns = layout.glyphRuns(); |
1042 | for (int i=0; i<glyphRuns.size(); ++i) |
1043 | addUnselectedGlyphs(glyphRun: glyphRuns.at(i)); |
1044 | } |
1045 | } |
1046 | |
1047 | int textPos = block.position(); |
1048 | QTextBlock::iterator blockIterator = block.begin(); |
1049 | |
1050 | while (!blockIterator.atEnd()) { |
1051 | QTextFragment fragment = blockIterator.fragment(); |
1052 | QString text = fragment.text(); |
1053 | if (text.isEmpty()) |
1054 | continue; |
1055 | |
1056 | QTextCharFormat charFormat = fragment.charFormat(); |
1057 | QFont font(charFormat.font()); |
1058 | QFontMetricsF fontMetrics(font); |
1059 | |
1060 | int fontHeight = fontMetrics.descent() + fontMetrics.ascent(); |
1061 | int valign = charFormat.verticalAlignment(); |
1062 | if (valign == QTextCharFormat::AlignSuperScript) |
1063 | setPosition(QPointF(blockBoundingRect.x(), blockBoundingRect.y() - fontHeight / 2)); |
1064 | else if (valign == QTextCharFormat::AlignSubScript) |
1065 | setPosition(QPointF(blockBoundingRect.x(), blockBoundingRect.y() + fontHeight / 6)); |
1066 | else |
1067 | setPosition(blockBoundingRect.topLeft()); |
1068 | |
1069 | if (text.contains(c: QChar::ObjectReplacementCharacter)) { |
1070 | QTextFrame *frame = qobject_cast<QTextFrame *>(object: textDocument->objectForFormat(charFormat)); |
1071 | if (!frame || frame->frameFormat().position() == QTextFrameFormat::InFlow) { |
1072 | int blockRelativePosition = textPos - block.position(); |
1073 | QTextLine line = block.layout()->lineForTextPosition(pos: blockRelativePosition); |
1074 | if (!currentLine().isValid() |
1075 | || line.lineNumber() != currentLine().lineNumber()) { |
1076 | setCurrentLine(line); |
1077 | } |
1078 | |
1079 | QQuickTextNodeEngine::SelectionState selectionState = |
1080 | (selectionStart < textPos + text.length() |
1081 | && selectionEnd >= textPos) |
1082 | ? QQuickTextNodeEngine::Selected |
1083 | : QQuickTextNodeEngine::Unselected; |
1084 | |
1085 | addTextObject(block, position: QPointF(), format: charFormat, selectionState, textDocument, pos: textPos); |
1086 | } |
1087 | textPos += text.length(); |
1088 | } else { |
1089 | if (charFormat.foreground().style() != Qt::NoBrush) |
1090 | setTextColor(charFormat.foreground().color()); |
1091 | else if (charFormat.isAnchor()) |
1092 | setTextColor(anchorColor); |
1093 | else |
1094 | setTextColor(textColor); |
1095 | |
1096 | int fragmentEnd = textPos + fragment.length(); |
1097 | #if QT_CONFIG(im) |
1098 | if (preeditPosition >= 0 |
1099 | && (preeditPosition + block.position()) >= textPos |
1100 | && (preeditPosition + block.position()) <= fragmentEnd) { |
1101 | fragmentEnd += preeditLength; |
1102 | } |
1103 | #endif |
1104 | if (charFormat.background().style() != Qt::NoBrush) { |
1105 | QTextLayout::FormatRange additionalFormat; |
1106 | additionalFormat.start = textPos - block.position(); |
1107 | additionalFormat.length = fragmentEnd - textPos; |
1108 | additionalFormat.format = charFormat; |
1109 | colorChanges << additionalFormat; |
1110 | } |
1111 | |
1112 | textPos = addText(block, charFormat, textColor, colorChanges, textPos, fragmentEnd, |
1113 | selectionStart, selectionEnd); |
1114 | } |
1115 | |
1116 | ++blockIterator; |
1117 | } |
1118 | |
1119 | #if QT_CONFIG(im) |
1120 | if (preeditLength >= 0 && textPos <= block.position() + preeditPosition) { |
1121 | setPosition(blockBoundingRect.topLeft()); |
1122 | textPos = block.position() + preeditPosition; |
1123 | QTextLine line = block.layout()->lineForTextPosition(pos: preeditPosition); |
1124 | if (!currentLine().isValid() |
1125 | || line.lineNumber() != currentLine().lineNumber()) { |
1126 | setCurrentLine(line); |
1127 | } |
1128 | textPos = addText(block, charFormat: block.charFormat(), textColor, colorChanges, |
1129 | textPos, fragmentEnd: textPos + preeditLength, |
1130 | selectionStart, selectionEnd); |
1131 | } |
1132 | #endif |
1133 | |
1134 | setCurrentLine(QTextLine()); // Reset current line because the text layout changed |
1135 | m_hasContents = true; |
1136 | } |
1137 | |
1138 | |
1139 | QT_END_NAMESPACE |
1140 | |
1141 | |