QtBase  v6.3.1
qtextengine.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 **
3 ** Copyright (C) 2021 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtGui 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 <QtGui/private/qtguiglobal_p.h>
41 #include "qdebug.h"
42 #include "qtextformat.h"
43 #include "qtextformat_p.h"
44 #include "qtextengine_p.h"
46 #include "qtextlayout.h"
47 #include "qtextboundaryfinder.h"
48 #include <QtCore/private/qunicodetables_p.h>
49 #include "qvarlengtharray.h"
50 #include "qfont.h"
51 #include "qfont_p.h"
52 #include "qfontengine_p.h"
53 #include "qstring.h"
54 #include "qtextdocument_p.h"
55 #include "qrawfont.h"
56 #include "qrawfont_p.h"
57 #include <qguiapplication.h>
58 #include <qinputmethod.h>
59 #include <algorithm>
60 #include <stdlib.h>
61 
63 
64 static const float smallCapsFraction = 0.7f;
65 
66 namespace {
67 // Helper class used in QTextEngine::itemize
68 // keep it out here to allow us to keep supporting various compilers.
69 class Itemizer {
70 public:
71  Itemizer(const QString &string, const QScriptAnalysis *analysis, QScriptItemArray &items)
72  : m_string(string),
73  m_analysis(analysis),
74  m_items(items),
75  m_splitter(nullptr)
76  {
77  }
78  ~Itemizer()
79  {
80  delete m_splitter;
81  }
82 
85  void generate(int start, int length, QFont::Capitalization caps)
86  {
87  if (caps == QFont::SmallCaps)
88  generateScriptItemsSmallCaps(reinterpret_cast<const ushort *>(m_string.unicode()), start, length);
89  else if (caps == QFont::Capitalize)
90  generateScriptItemsCapitalize(start, length);
91  else if (caps != QFont::MixedCase) {
92  generateScriptItemsAndChangeCase(start, length,
94  }
95  else
96  generateScriptItems(start, length);
97  }
98 
99 private:
100  enum { MaxItemLength = 4096 };
101 
102  void generateScriptItemsAndChangeCase(int start, int length, QScriptAnalysis::Flags flags)
103  {
104  generateScriptItems(start, length);
105  if (m_items.isEmpty()) // the next loop won't work in that case
106  return;
107  QScriptItemArray::Iterator iter = m_items.end();
108  do {
109  iter--;
110  if (iter->analysis.flags < QScriptAnalysis::LineOrParagraphSeparator)
111  iter->analysis.flags = flags;
112  } while (iter->position > start);
113  }
114 
115  void generateScriptItems(int start, int length)
116  {
117  if (!length)
118  return;
119  const int end = start + length;
120  for (int i = start + 1; i < end; ++i) {
121  if (m_analysis[i].bidiLevel == m_analysis[start].bidiLevel
122  && m_analysis[i].flags == m_analysis[start].flags
123  && (m_analysis[i].script == m_analysis[start].script || m_string[i] == QLatin1Char('.'))
124  && m_analysis[i].flags < QScriptAnalysis::SpaceTabOrObject
125  && i - start < MaxItemLength)
126  continue;
127  m_items.append(QScriptItem(start, m_analysis[start]));
128  start = i;
129  }
130  m_items.append(QScriptItem(start, m_analysis[start]));
131  }
132 
133  void generateScriptItemsCapitalize(int start, int length)
134  {
135  if (!length)
136  return;
137 
138  if (!m_splitter)
140  m_string.constData(), m_string.length(),
141  /*buffer*/nullptr, /*buffer size*/0);
142 
143  m_splitter->setPosition(start);
144  QScriptAnalysis itemAnalysis = m_analysis[start];
145 
146  if (m_splitter->boundaryReasons() & QTextBoundaryFinder::StartOfItem)
147  itemAnalysis.flags = QScriptAnalysis::Uppercase;
148 
149  m_splitter->toNextBoundary();
150 
151  const int end = start + length;
152  for (int i = start + 1; i < end; ++i) {
153  bool atWordStart = false;
154 
155  if (i == m_splitter->position()) {
156  if (m_splitter->boundaryReasons() & QTextBoundaryFinder::StartOfItem) {
158  atWordStart = true;
159  }
160 
161  m_splitter->toNextBoundary();
162  }
163 
164  if (m_analysis[i] == itemAnalysis
165  && m_analysis[i].flags < QScriptAnalysis::TabOrObject
166  && !atWordStart
167  && i - start < MaxItemLength)
168  continue;
169 
170  m_items.append(QScriptItem(start, itemAnalysis));
171  start = i;
172  itemAnalysis = m_analysis[start];
173 
174  if (atWordStart)
175  itemAnalysis.flags = QScriptAnalysis::Uppercase;
176  }
177  m_items.append(QScriptItem(start, itemAnalysis));
178  }
179 
180  void generateScriptItemsSmallCaps(const ushort *uc, int start, int length)
181  {
182  if (!length)
183  return;
184  bool lower = (QChar::category(uc[start]) == QChar::Letter_Lowercase);
185  const int end = start + length;
186  // split text into parts that are already uppercase and parts that are lowercase, and mark the latter to be uppercased later.
187  for (int i = start + 1; i < end; ++i) {
188  bool l = (QChar::category(uc[i]) == QChar::Letter_Lowercase);
189  if ((m_analysis[i] == m_analysis[start])
190  && m_analysis[i].flags < QScriptAnalysis::TabOrObject
191  && l == lower
192  && i - start < MaxItemLength)
193  continue;
194  m_items.append(QScriptItem(start, m_analysis[start]));
195  if (lower)
196  m_items.last().analysis.flags = QScriptAnalysis::SmallCaps;
197 
198  start = i;
199  lower = l;
200  }
201  m_items.append(QScriptItem(start, m_analysis[start]));
202  if (lower)
203  m_items.last().analysis.flags = QScriptAnalysis::SmallCaps;
204  }
205 
206  const QString &m_string;
207  const QScriptAnalysis * const m_analysis;
208  QScriptItemArray &m_items;
209  QTextBoundaryFinder *m_splitter;
210 };
211 
212 // -----------------------------------------------------------------------------------------------------
213 //
214 // The Unicode Bidi algorithm.
215 // See http://www.unicode.org/reports/tr9/tr9-37.html
216 //
217 // -----------------------------------------------------------------------------------------------------
218 
219 // #define DEBUG_BIDI
220 #ifndef DEBUG_BIDI
221 enum { BidiDebugEnabled = false };
222 #define BIDI_DEBUG if (1) ; else qDebug
223 #else
224 enum { BidiDebugEnabled = true };
225 static const char *directions[] = {
226  "DirL", "DirR", "DirEN", "DirES", "DirET", "DirAN", "DirCS", "DirB", "DirS", "DirWS", "DirON",
227  "DirLRE", "DirLRO", "DirAL", "DirRLE", "DirRLO", "DirPDF", "DirNSM", "DirBN",
228  "DirLRI", "DirRLI", "DirFSI", "DirPDI"
229 };
230 #define BIDI_DEBUG qDebug
232  return (d << directions[dir]);
233 }
234 #endif
235 
236 struct QBidiAlgorithm {
237  template<typename T> using Vector = QVarLengthArray<T, 64>;
238 
239  QBidiAlgorithm(const QChar *text, QScriptAnalysis *analysis, int length, bool baseDirectionIsRtl)
240  : text(text),
241  analysis(analysis),
242  length(length),
243  baseLevel(baseDirectionIsRtl ? 1 : 0)
244  {
245 
246  }
247 
248  struct IsolatePair {
249  int start;
250  int end;
251  };
252 
253  void initScriptAnalysisAndIsolatePairs(Vector<IsolatePair> &isolatePairs)
254  {
255  int isolateStack[128];
256  int isolateLevel = 0;
257  // load directions of string, and determine isolate pairs
258  for (int i = 0; i < length; ++i) {
259  int pos = i;
260  char32_t uc = text[i].unicode();
261  if (QChar::isHighSurrogate(uc) && i < length - 1 && text[i + 1].isLowSurrogate()) {
262  ++i;
263  analysis[i].bidiDirection = QChar::DirNSM;
264  uc = QChar::surrogateToUcs4(ushort(uc), text[i].unicode());
265  }
267  analysis[pos].bidiDirection = QChar::Direction(p->direction);
268  switch (QChar::Direction(p->direction)) {
269  case QChar::DirON:
270  // all mirrored chars are DirON
271  if (p->mirrorDiff)
272  analysis[pos].bidiFlags = QScriptAnalysis::BidiMirrored;
273  break;
274  case QChar::DirLRE:
275  case QChar::DirRLE:
276  case QChar::DirLRO:
277  case QChar::DirRLO:
278  case QChar::DirPDF:
279  case QChar::DirBN:
281  break;
282  case QChar::DirLRI:
283  case QChar::DirRLI:
284  case QChar::DirFSI:
285  if (isolateLevel < 128) {
286  isolateStack[isolateLevel] = isolatePairs.size();
287  isolatePairs.append({ pos, length });
288  }
289  ++isolateLevel;
291  break;
292  case QChar::DirPDI:
293  if (isolateLevel > 0) {
294  --isolateLevel;
295  if (isolateLevel < 128)
296  isolatePairs[isolateStack[isolateLevel]].end = pos;
297  }
298  Q_FALLTHROUGH();
299  case QChar::DirWS:
301  break;
302  case QChar::DirS:
303  case QChar::DirB:
304  analysis[pos].bidiFlags = QScriptAnalysis::BidiResetToParagraphLevel;
305  if (uc == QChar::ParagraphSeparator) {
306  // close all open isolates as we start a new paragraph
307  while (isolateLevel > 0) {
308  --isolateLevel;
309  if (isolateLevel < 128)
310  isolatePairs[isolateStack[isolateLevel]].end = pos;
311  }
312  }
313  break;
314  default:
315  break;
316  }
317  }
318  }
319 
320  struct DirectionalRun {
321  int start;
322  int end;
323  int continuation;
324  ushort level;
325  bool isContinuation;
326  bool hasContent;
327  };
328 
329  void generateDirectionalRuns(const Vector<IsolatePair> &isolatePairs, Vector<DirectionalRun> &runs)
330  {
331  struct DirectionalStack {
332  enum { MaxDepth = 125 };
333  struct Item {
334  ushort level;
335  bool isOverride;
336  bool isIsolate;
337  int runBeforeIsolate;
338  };
339  Item items[128];
340  int counter = 0;
341 
342  void push(Item i) {
343  items[counter] = i;
344  ++counter;
345  }
346  void pop() {
347  --counter;
348  }
349  int depth() const {
350  return counter;
351  }
352  const Item &top() const {
353  return items[counter - 1];
354  }
355  } stack;
356  int overflowIsolateCount = 0;
357  int overflowEmbeddingCount = 0;
358  int validIsolateCount = 0;
359 
360  ushort level = baseLevel;
361  bool override = false;
362  stack.push({ level, false, false, -1 });
363 
364  BIDI_DEBUG() << "resolving explicit levels";
365  int runStart = 0;
366  int continuationFrom = -1;
367  int lastRunWithContent = -1;
368  bool runHasContent = false;
369 
370  auto appendRun = [&](int runEnd) {
371  if (runEnd < runStart)
372  return;
373  bool isContinuation = false;
374  if (continuationFrom != -1) {
375  runs[continuationFrom].continuation = runs.size();
376  isContinuation = true;
377  } else if (lastRunWithContent != -1 && level == runs.at(lastRunWithContent).level) {
378  runs[lastRunWithContent].continuation = runs.size();
379  isContinuation = true;
380  }
381  if (runHasContent)
382  lastRunWithContent = runs.size();
383  BIDI_DEBUG() << " appending run start/end" << runStart << runEnd << "level" << level;
384  runs.append({ runStart, runEnd, -1, level, isContinuation, runHasContent });
385  runHasContent = false;
386  runStart = runEnd + 1;
387  continuationFrom = -1;
388  };
389 
390  int isolatePairPosition = 0;
391 
392  for (int i = 0; i < length; ++i) {
393  QChar::Direction dir = analysis[i].bidiDirection;
394 
395 
396  auto doEmbed = [&](bool isRtl, bool isOverride, bool isIsolate) {
397  if (isIsolate) {
398  if (override)
399  analysis[i].bidiDirection = (level & 1) ? QChar::DirR : QChar::DirL;
400  runHasContent = true;
401  lastRunWithContent = -1;
402  ++isolatePairPosition;
403  }
404  int runBeforeIsolate = runs.size();
405  ushort newLevel = isRtl ? ((stack.top().level + 1) | 1) : ((stack.top().level + 2) & ~1);
406  if (newLevel <= DirectionalStack::MaxDepth && !overflowEmbeddingCount && !overflowIsolateCount) {
407  if (isIsolate)
408  ++validIsolateCount;
409  else
410  runBeforeIsolate = -1;
411  appendRun(isIsolate ? i : i - 1);
412  BIDI_DEBUG() << "pushing new item on stack: level" << (int)newLevel << "isOverride" << isOverride << "isIsolate" << isIsolate << runBeforeIsolate;
413  stack.push({ newLevel, isOverride, isIsolate, runBeforeIsolate });
414  override = isOverride;
415  level = newLevel;
416  } else {
417  if (isIsolate)
418  ++overflowIsolateCount;
419  else if (!overflowIsolateCount)
420  ++overflowEmbeddingCount;
421  }
422  if (!isIsolate) {
423  if (override)
424  analysis[i].bidiDirection = (level & 1) ? QChar::DirR : QChar::DirL;
425  else
426  analysis[i].bidiDirection = QChar::DirBN;
427  }
428  };
429 
430  switch (dir) {
431  case QChar::DirLRE:
432  doEmbed(false, false, false);
433  break;
434  case QChar::DirRLE:
435  doEmbed(true, false, false);
436  break;
437  case QChar::DirLRO:
438  doEmbed(false, true, false);
439  break;
440  case QChar::DirRLO:
441  doEmbed(true, true, false);
442  break;
443  case QChar::DirLRI:
444  doEmbed(false, false, true);
445  break;
446  case QChar::DirRLI:
447  doEmbed(true, false, true);
448  break;
449  case QChar::DirFSI: {
450  bool isRtl = false;
451  if (isolatePairPosition < isolatePairs.size()) {
452  const auto &pair = isolatePairs.at(isolatePairPosition);
453  Q_ASSERT(pair.start == i);
454  isRtl = QStringView(text + pair.start + 1, pair.end - pair.start - 1).isRightToLeft();
455  }
456  doEmbed(isRtl, false, true);
457  break;
458  }
459 
460  case QChar::DirPDF:
461  if (override)
462  analysis[i].bidiDirection = (level & 1) ? QChar::DirR : QChar::DirL;
463  else
464  analysis[i].bidiDirection = QChar::DirBN;
465  if (overflowIsolateCount) {
466  ; // do nothing
467  } else if (overflowEmbeddingCount) {
468  --overflowEmbeddingCount;
469  } else if (!stack.top().isIsolate && stack.depth() >= 2) {
470  appendRun(i);
471  stack.pop();
472  override = stack.top().isOverride;
473  level = stack.top().level;
474  BIDI_DEBUG() << "popped PDF from stack, level now" << (int)stack.top().level;
475  }
476  break;
477  case QChar::DirPDI:
478  runHasContent = true;
479  if (overflowIsolateCount) {
480  --overflowIsolateCount;
481  } else if (validIsolateCount == 0) {
482  ; // do nothing
483  } else {
484  appendRun(i - 1);
485  overflowEmbeddingCount = 0;
486  while (!stack.top().isIsolate)
487  stack.pop();
488  continuationFrom = stack.top().runBeforeIsolate;
489  BIDI_DEBUG() << "popped PDI from stack, level now" << (int)stack.top().level << "continuation from" << continuationFrom;
490  stack.pop();
491  override = stack.top().isOverride;
492  level = stack.top().level;
493  lastRunWithContent = -1;
494  --validIsolateCount;
495  }
496  if (override)
497  analysis[i].bidiDirection = (level & 1) ? QChar::DirR : QChar::DirL;
498  break;
499  case QChar::DirB:
500  // paragraph separator, go down to base direction, reset all state
501  if (text[i].unicode() == QChar::ParagraphSeparator) {
502  appendRun(i - 1);
503  while (stack.counter > 1) {
504  // there might be remaining isolates on the stack that are missing a PDI. Those need to get
505  // a continuation indicating to take the eos from the end of the string (ie. the paragraph level)
506  const auto &t = stack.top();
507  if (t.isIsolate) {
508  runs[t.runBeforeIsolate].continuation = -2;
509  }
510  --stack.counter;
511  }
512  continuationFrom = -1;
513  lastRunWithContent = -1;
514  validIsolateCount = 0;
515  overflowIsolateCount = 0;
516  overflowEmbeddingCount = 0;
517  level = baseLevel;
518  }
519  break;
520  default:
521  runHasContent = true;
522  Q_FALLTHROUGH();
523  case QChar::DirBN:
524  if (override)
525  analysis[i].bidiDirection = (level & 1) ? QChar::DirR : QChar::DirL;
526  break;
527  }
528  }
529  appendRun(length - 1);
530  while (stack.counter > 1) {
531  // there might be remaining isolates on the stack that are missing a PDI. Those need to get
532  // a continuation indicating to take the eos from the end of the string (ie. the paragraph level)
533  const auto &t = stack.top();
534  if (t.isIsolate) {
535  runs[t.runBeforeIsolate].continuation = -2;
536  }
537  --stack.counter;
538  }
539  }
540 
541  void resolveExplicitLevels(Vector<DirectionalRun> &runs)
542  {
543  Vector<IsolatePair> isolatePairs;
544 
545  initScriptAnalysisAndIsolatePairs(isolatePairs);
546  generateDirectionalRuns(isolatePairs, runs);
547  }
548 
549  struct IsolatedRunSequenceIterator {
550  struct Position {
551  int current = -1;
552  int pos = -1;
553 
554  Position() = default;
555  Position(int current, int pos) : current(current), pos(pos) {}
556 
557  bool isValid() const { return pos != -1; }
558  void clear() { pos = -1; }
559  };
560  IsolatedRunSequenceIterator(const Vector<DirectionalRun> &runs, int i)
561  : runs(runs),
562  current(i)
563  {
564  pos = runs.at(current).start;
565  }
566  int operator *() const { return pos; }
567  bool atEnd() const { return pos < 0; }
568  void operator++() {
569  ++pos;
570  if (pos > runs.at(current).end) {
571  current = runs.at(current).continuation;
572  if (current > -1)
573  pos = runs.at(current).start;
574  else
575  pos = -1;
576  }
577  }
578  void setPosition(Position p) {
579  current = p.current;
580  pos = p.pos;
581  }
582  Position position() const {
583  return Position(current, pos);
584  }
585  bool operator !=(int position) const {
586  return pos != position;
587  }
588 
589  const Vector<DirectionalRun> &runs;
590  int current;
591  int pos;
592  };
593 
594 
595  void resolveW1W2W3(const Vector<DirectionalRun> &runs, int i, QChar::Direction sos)
596  {
597  QChar::Direction last = sos;
598  QChar::Direction lastStrong = sos;
599  IsolatedRunSequenceIterator it(runs, i);
600  while (!it.atEnd()) {
601  int pos = *it;
602 
603  // Rule W1: Resolve NSM
604  QChar::Direction current = analysis[pos].bidiDirection;
605  if (current == QChar::DirNSM) {
606  current = last;
607  analysis[pos].bidiDirection = current;
608  } else if (current >= QChar::DirLRI) {
609  last = QChar::DirON;
610  } else if (current == QChar::DirBN) {
611  current = last;
612  } else {
613  // there shouldn't be any explicit embedding marks here
614  Q_ASSERT(current != QChar::DirLRE);
615  Q_ASSERT(current != QChar::DirRLE);
616  Q_ASSERT(current != QChar::DirLRO);
617  Q_ASSERT(current != QChar::DirRLO);
618  Q_ASSERT(current != QChar::DirPDF);
619 
620  last = current;
621  }
622 
623  // Rule W2
624  if (current == QChar::DirEN && lastStrong == QChar::DirAL) {
625  current = QChar::DirAN;
626  analysis[pos].bidiDirection = current;
627  }
628 
629  // remember last strong char for rule W2
630  if (current == QChar::DirL || current == QChar::DirR) {
631  lastStrong = current;
632  } else if (current == QChar::DirAL) {
633  // Rule W3
634  lastStrong = current;
635  analysis[pos].bidiDirection = QChar::DirR;
636  }
637  last = current;
638  ++it;
639  }
640  }
641 
642 
643  void resolveW4(const Vector<DirectionalRun> &runs, int i, QChar::Direction sos)
644  {
645  // Rule W4
646  QChar::Direction secondLast = sos;
647 
648  IsolatedRunSequenceIterator it(runs, i);
649  int lastPos = *it;
650  QChar::Direction last = analysis[lastPos].bidiDirection;
651 
652 // BIDI_DEBUG() << "Applying rule W4/W5";
653  ++it;
654  while (!it.atEnd()) {
655  int pos = *it;
656  QChar::Direction current = analysis[pos].bidiDirection;
657  if (current == QChar::DirBN) {
658  ++it;
659  continue;
660  }
661 // BIDI_DEBUG() << pos << secondLast << last << current;
662  if (last == QChar::DirES && current == QChar::DirEN && secondLast == QChar::DirEN) {
663  last = QChar::DirEN;
664  analysis[lastPos].bidiDirection = last;
665  } else if (last == QChar::DirCS) {
666  if (current == QChar::DirEN && secondLast == QChar::DirEN) {
667  last = QChar::DirEN;
668  analysis[lastPos].bidiDirection = last;
669  } else if (current == QChar::DirAN && secondLast == QChar::DirAN) {
670  last = QChar::DirAN;
671  analysis[lastPos].bidiDirection = last;
672  }
673  }
674  secondLast = last;
675  last = current;
676  lastPos = pos;
677  ++it;
678  }
679  }
680 
681  void resolveW5(const Vector<DirectionalRun> &runs, int i)
682  {
683  // Rule W5
685 
686  IsolatedRunSequenceIterator it(runs, i);
687  int lastPos = *it;
688  QChar::Direction last = analysis[lastPos].bidiDirection;
689  if (last == QChar::DirET || last == QChar::DirBN)
690  lastETPosition = it.position();
691 
692  ++it;
693  while (!it.atEnd()) {
694  int pos = *it;
695  QChar::Direction current = analysis[pos].bidiDirection;
696  if (current == QChar::DirBN) {
697  ++it;
698  continue;
699  }
700  if (current == QChar::DirET) {
701  if (last == QChar::DirEN) {
702  current = QChar::DirEN;
703  analysis[pos].bidiDirection = current;
704  } else if (!lastETPosition.isValid()) {
705  lastETPosition = it.position();
706  }
707  } else if (lastETPosition.isValid()) {
708  if (current == QChar::DirEN) {
709  it.setPosition(lastETPosition);
710  while (it != pos) {
711  int pos = *it;
712  analysis[pos].bidiDirection = QChar::DirEN;
713  ++it;
714  }
715  }
716  lastETPosition.clear();
717  }
718  last = current;
719  lastPos = pos;
720  ++it;
721  }
722  }
723 
724  void resolveW6W7(const Vector<DirectionalRun> &runs, int i, QChar::Direction sos)
725  {
726  QChar::Direction lastStrong = sos;
727  IsolatedRunSequenceIterator it(runs, i);
728  while (!it.atEnd()) {
729  int pos = *it;
730 
731  // Rule W6
732  QChar::Direction current = analysis[pos].bidiDirection;
733  if (current == QChar::DirBN) {
734  ++it;
735  continue;
736  }
737  if (current == QChar::DirET || current == QChar::DirES || current == QChar::DirCS) {
738  analysis[pos].bidiDirection = QChar::DirON;
739  }
740 
741  // Rule W7
742  else if (current == QChar::DirL || current == QChar::DirR) {
743  lastStrong = current;
744  } else if (current == QChar::DirEN && lastStrong == QChar::DirL) {
745  analysis[pos].bidiDirection = lastStrong;
746  }
747  ++it;
748  }
749  }
750 
751  struct BracketPair {
752  int first;
753  int second;
754 
755  bool isValid() const { return second > 0; }
756 
757  QChar::Direction containedDirection(const QScriptAnalysis *analysis, QChar::Direction embeddingDir) const {
758  int isolateCounter = 0;
759  QChar::Direction containedDir = QChar::DirON;
760  for (int i = first + 1; i < second; ++i) {
761  QChar::Direction dir = analysis[i].bidiDirection;
762  if (isolateCounter) {
763  if (dir == QChar::DirPDI)
764  --isolateCounter;
765  continue;
766  }
767  if (dir == QChar::DirL) {
768  containedDir = dir;
769  if (embeddingDir == dir)
770  break;
771  } else if (dir == QChar::DirR || dir == QChar::DirAN || dir == QChar::DirEN) {
772  containedDir = QChar::DirR;
773  if (embeddingDir == QChar::DirR)
774  break;
775  } else if (dir == QChar::DirLRI || dir == QChar::DirRLI || dir == QChar::DirFSI)
776  ++isolateCounter;
777  }
778  BIDI_DEBUG() << " contained dir for backet pair" << first << "/" << second << "is" << containedDir;
779  return containedDir;
780  }
781  };
782 
783 
784  struct BracketStack {
785  struct Item {
786  Item() = default;
787  Item(uint pairedBracked, int position) : pairedBracked(pairedBracked), position(position) {}
788  uint pairedBracked = 0;
789  int position = 0;
790  };
791 
792  void push(uint closingUnicode, int pos) {
793  if (position < MaxDepth)
794  stack[position] = Item(closingUnicode, pos);
795  ++position;
796  }
797  int match(uint unicode) {
798  Q_ASSERT(!overflowed());
799  int p = position;
800  while (--p >= 0) {
801  if (stack[p].pairedBracked == unicode ||
802  // U+3009 and U+2329 are canonical equivalents of each other. Fortunately it's the only pair in Unicode 10
803  (stack[p].pairedBracked == 0x3009 && unicode == 0x232a) ||
804  (stack[p].pairedBracked == 0x232a && unicode == 0x3009)) {
805  position = p;
806  return stack[p].position;
807  }
808 
809  }
810  return -1;
811  }
812 
813  enum { MaxDepth = 63 };
814  Item stack[MaxDepth];
815  int position = 0;
816 
817  bool overflowed() const { return position > MaxDepth; }
818  };
819 
820  void resolveN0(const Vector<DirectionalRun> &runs, int i, QChar::Direction sos)
821  {
822  ushort level = runs.at(i).level;
823 
824  Vector<BracketPair> bracketPairs;
825  {
826  BracketStack bracketStack;
827  IsolatedRunSequenceIterator it(runs, i);
828  while (!it.atEnd()) {
829  int pos = *it;
831  if (dir == QChar::DirON) {
832  const QUnicodeTables::Properties *p = QUnicodeTables::properties(char16_t{text[pos].unicode()});
833  if (p->mirrorDiff) {
834  // either opening or closing bracket
835  if (p->category == QChar::Punctuation_Open) {
836  // opening bracked
837  uint closingBracked = text[pos].unicode() + p->mirrorDiff;
838  bracketStack.push(closingBracked, bracketPairs.size());
839  if (bracketStack.overflowed()) {
840  bracketPairs.clear();
841  break;
842  }
843  bracketPairs.append({ pos, -1 });
844  } else if (p->category == QChar::Punctuation_Close) {
845  int pairPos = bracketStack.match(text[pos].unicode());
846  if (pairPos != -1)
847  bracketPairs[pairPos].second = pos;
848  }
849  }
850  }
851  ++it;
852  }
853  }
854 
855  if (BidiDebugEnabled && bracketPairs.size()) {
856  BIDI_DEBUG() << "matched bracket pairs:";
857  for (int i = 0; i < bracketPairs.size(); ++i)
858  BIDI_DEBUG() << " " << bracketPairs.at(i).first << bracketPairs.at(i).second;
859  }
860 
861  QChar::Direction lastStrong = sos;
862  IsolatedRunSequenceIterator it(runs, i);
863  QChar::Direction embeddingDir = (level & 1) ? QChar::DirR : QChar::DirL;
864  for (int i = 0; i < bracketPairs.size(); ++i) {
865  const auto &pair = bracketPairs.at(i);
866  if (!pair.isValid())
867  continue;
868  QChar::Direction containedDir = pair.containedDirection(analysis, embeddingDir);
869  if (containedDir == QChar::DirON) {
870  BIDI_DEBUG() << " 3: resolve bracket pair" << i << "to DirON";
871  continue;
872  } else if (containedDir == embeddingDir) {
873  analysis[pair.first].bidiDirection = embeddingDir;
874  analysis[pair.second].bidiDirection = embeddingDir;
875  BIDI_DEBUG() << " 1: resolve bracket pair" << i << "to" << embeddingDir;
876  } else {
877  // case c.
878  while (it.pos < pair.first) {
879  int pos = *it;
880  switch (analysis[pos].bidiDirection) {
881  case QChar::DirR:
882  case QChar::DirEN:
883  case QChar::DirAN:
884  lastStrong = QChar::DirR;
885  break;
886  case QChar::DirL:
887  lastStrong = QChar::DirL;
888  break;
889  default:
890  break;
891  }
892  ++it;
893  }
894  analysis[pair.first].bidiDirection = lastStrong;
895  analysis[pair.second].bidiDirection = lastStrong;
896  BIDI_DEBUG() << " 2: resolve bracket pair" << i << "to" << lastStrong;
897  }
898  for (int i = pair.second + 1; i < length; ++i) {
899  if (text[i].direction() == QChar::DirNSM)
900  analysis[i].bidiDirection = analysis[pair.second].bidiDirection;
901  else
902  break;
903  }
904  }
905  }
906 
907  void resolveN1N2(const Vector<DirectionalRun> &runs, int i, QChar::Direction sos, QChar::Direction eos)
908  {
909  // Rule N1 & N2
910  QChar::Direction lastStrong = sos;
912  IsolatedRunSequenceIterator it(runs, i);
913 // QChar::Direction last = QChar::DirON;
914  while (1) {
915  int pos = *it;
916 
917  QChar::Direction current = pos >= 0 ? analysis[pos].bidiDirection : eos;
918  QChar::Direction currentStrong = current;
919  switch (current) {
920  case QChar::DirEN:
921  case QChar::DirAN:
922  currentStrong = QChar::DirR;
923  Q_FALLTHROUGH();
924  case QChar::DirL:
925  case QChar::DirR:
926  if (niPos.isValid()) {
927  QChar::Direction dir = currentStrong;
928  if (lastStrong != currentStrong)
929  dir = (runs.at(i).level) & 1 ? QChar::DirR : QChar::DirL;
930  it.setPosition(niPos);
931  while (*it != pos) {
932  if (analysis[*it].bidiDirection != QChar::DirBN)
933  analysis[*it].bidiDirection = dir;
934  ++it;
935  }
936  niPos.clear();
937  }
938  lastStrong = currentStrong;
939  break;
940 
941  case QChar::DirBN:
942  case QChar::DirS:
943  case QChar::DirWS:
944  case QChar::DirON:
945  case QChar::DirFSI:
946  case QChar::DirLRI:
947  case QChar::DirRLI:
948  case QChar::DirPDI:
949  case QChar::DirB:
950  if (!niPos.isValid())
951  niPos = it.position();
952  break;
953 
954  default:
955  Q_UNREACHABLE();
956  }
957  if (it.atEnd())
958  break;
959 // last = current;
960  ++it;
961  }
962  }
963 
964  void resolveImplicitLevelsForIsolatedRun(const Vector<DirectionalRun> &runs, int i)
965  {
966  // Rule X10
967  int level = runs.at(i).level;
968  int before = i - 1;
969  while (before >= 0 && !runs.at(before).hasContent)
970  --before;
971  int level_before = (before >= 0) ? runs.at(before).level : baseLevel;
972  int after = i;
973  while (runs.at(after).continuation >= 0)
974  after = runs.at(after).continuation;
975  if (runs.at(after).continuation == -2) {
976  after = runs.size();
977  } else {
978  ++after;
979  while (after < runs.size() && !runs.at(after).hasContent)
980  ++after;
981  }
982  int level_after = (after == runs.size()) ? baseLevel : runs.at(after).level;
983  QChar::Direction sos = (qMax(level_before, level) & 1) ? QChar::DirR : QChar::DirL;
984  QChar::Direction eos = (qMax(level_after, level) & 1) ? QChar::DirR : QChar::DirL;
985 
986  if (BidiDebugEnabled) {
987  BIDI_DEBUG() << "Isolated run starting at" << i << "sos/eos" << sos << eos;
988  BIDI_DEBUG() << "before implicit level processing:";
989  IsolatedRunSequenceIterator it(runs, i);
990  while (!it.atEnd()) {
991  BIDI_DEBUG() << " " << *it << Qt::hex << text[*it].unicode() << analysis[*it].bidiDirection;
992  ++it;
993  }
994  }
995 
996  resolveW1W2W3(runs, i, sos);
997  resolveW4(runs, i, sos);
998  resolveW5(runs, i);
999 
1000  if (BidiDebugEnabled) {
1001  BIDI_DEBUG() << "after W4/W5";
1002  IsolatedRunSequenceIterator it(runs, i);
1003  while (!it.atEnd()) {
1004  BIDI_DEBUG() << " " << *it << Qt::hex << text[*it].unicode() << analysis[*it].bidiDirection;
1005  ++it;
1006  }
1007  }
1008 
1009  resolveW6W7(runs, i, sos);
1010 
1011  // Resolve neutral types
1012 
1013  // Rule N0
1014  resolveN0(runs, i, sos);
1015  resolveN1N2(runs, i, sos, eos);
1016 
1017  BIDI_DEBUG() << "setting levels (run at" << level << ")";
1018  // Rules I1 & I2: set correct levels
1019  {
1020  ushort level = runs.at(i).level;
1021  IsolatedRunSequenceIterator it(runs, i);
1022  while (!it.atEnd()) {
1023  int pos = *it;
1024 
1025  QChar::Direction current = analysis[pos].bidiDirection;
1026  switch (current) {
1027  case QChar::DirBN:
1028  break;
1029  case QChar::DirL:
1030  analysis[pos].bidiLevel = (level + 1) & ~1;
1031  break;
1032  case QChar::DirR:
1033  analysis[pos].bidiLevel = level | 1;
1034  break;
1035  case QChar::DirAN:
1036  case QChar::DirEN:
1037  analysis[pos].bidiLevel = (level + 2) & ~1;
1038  break;
1039  default:
1040  Q_UNREACHABLE();
1041  }
1042  BIDI_DEBUG() << " " << pos << current << analysis[pos].bidiLevel;
1043  ++it;
1044  }
1045  }
1046  }
1047 
1048  void resolveImplicitLevels(const Vector<DirectionalRun> &runs)
1049  {
1050  for (int i = 0; i < runs.size(); ++i) {
1051  if (runs.at(i).isContinuation)
1052  continue;
1053 
1054  resolveImplicitLevelsForIsolatedRun(runs, i);
1055  }
1056  }
1057 
1058  bool checkForBidi() const
1059  {
1060  if (baseLevel != 0)
1061  return true;
1062  for (int i = 0; i < length; ++i) {
1063  if (text[i].unicode() >= 0x590) {
1064  switch (text[i].direction()) {
1065  case QChar::DirR: case QChar::DirAN:
1066  case QChar::DirLRE: case QChar::DirLRO: case QChar::DirAL:
1067  case QChar::DirRLE: case QChar::DirRLO: case QChar::DirPDF:
1068  case QChar::DirLRI: case QChar::DirRLI: case QChar::DirFSI: case QChar::DirPDI:
1069  return true;
1070  default:
1071  break;
1072  }
1073  }
1074  }
1075  return false;
1076  }
1077 
1078  bool process()
1079  {
1080  memset(analysis, 0, length * sizeof(QScriptAnalysis));
1081 
1082  bool hasBidi = checkForBidi();
1083 
1084  if (!hasBidi)
1085  return false;
1086 
1087  if (BidiDebugEnabled) {
1088  BIDI_DEBUG() << ">>>> start bidi, text length" << length;
1089  for (int i = 0; i < length; ++i)
1090  BIDI_DEBUG() << Qt::hex << " (" << i << ")" << text[i].unicode() << text[i].direction();
1091  }
1092 
1093  {
1094  Vector<DirectionalRun> runs;
1095  resolveExplicitLevels(runs);
1096 
1097  if (BidiDebugEnabled) {
1098  BIDI_DEBUG() << "resolved explicit levels, nruns" << runs.size();
1099  for (int i = 0; i < runs.size(); ++i)
1100  BIDI_DEBUG() << " " << i << "start/end" << runs.at(i).start << runs.at(i).end << "level" << (int)runs.at(i).level << "continuation" << runs.at(i).continuation;
1101  }
1102 
1103  // now we have a list of isolated run sequences inside the vector of runs, that can be fed
1104  // through the implicit level resolving
1105 
1106  resolveImplicitLevels(runs);
1107  }
1108 
1109  BIDI_DEBUG() << "Rule L1:";
1110  // Rule L1:
1111  bool resetLevel = true;
1112  for (int i = length - 1; i >= 0; --i) {
1113  if (analysis[i].bidiFlags & QScriptAnalysis::BidiResetToParagraphLevel) {
1114  BIDI_DEBUG() << "resetting pos" << i << "to baselevel";
1115  analysis[i].bidiLevel = baseLevel;
1116  resetLevel = true;
1117  } else if (resetLevel && analysis[i].bidiFlags & QScriptAnalysis::BidiMaybeResetToParagraphLevel) {
1118  BIDI_DEBUG() << "resetting pos" << i << "to baselevel (maybereset flag)";
1119  analysis[i].bidiLevel = baseLevel;
1120  } else {
1121  resetLevel = false;
1122  }
1123  }
1124 
1125  // set directions for BN to the minimum of adjacent chars
1126  // This makes is possible to be conformant with the Bidi algorithm even though we don't
1127  // remove BN and explicit embedding chars from the stream of characters to reorder
1128  int lastLevel = baseLevel;
1129  int lastBNPos = -1;
1130  for (int i = 0; i < length; ++i) {
1131  if (analysis[i].bidiFlags & QScriptAnalysis::BidiBN) {
1132  if (lastBNPos < 0)
1133  lastBNPos = i;
1134  analysis[i].bidiLevel = lastLevel;
1135  } else {
1136  int l = analysis[i].bidiLevel;
1137  if (lastBNPos >= 0) {
1138  if (l < lastLevel) {
1139  while (lastBNPos < i) {
1140  analysis[lastBNPos].bidiLevel = l;
1141  ++lastBNPos;
1142  }
1143  }
1144  lastBNPos = -1;
1145  }
1146  lastLevel = l;
1147  }
1148  }
1149  if (lastBNPos >= 0 && baseLevel < lastLevel) {
1150  while (lastBNPos < length) {
1151  analysis[lastBNPos].bidiLevel = baseLevel;
1152  ++lastBNPos;
1153  }
1154  }
1155 
1156  if (BidiDebugEnabled) {
1157  BIDI_DEBUG() << "final resolved levels:";
1158  for (int i = 0; i < length; ++i)
1159  BIDI_DEBUG() << " " << i << Qt::hex << text[i].unicode() << Qt::dec << (int)analysis[i].bidiLevel;
1160  }
1161 
1162  return true;
1163  }
1164 
1165 
1166  const QChar *text;
1167  QScriptAnalysis *analysis;
1168  int length;
1169  char baseLevel;
1170 };
1171 
1172 } // namespace
1173 
1174 void QTextEngine::bidiReorder(int numItems, const quint8 *levels, int *visualOrder)
1175 {
1176 
1177  // first find highest and lowest levels
1178  quint8 levelLow = 128;
1179  quint8 levelHigh = 0;
1180  int i = 0;
1181  while (i < numItems) {
1182  //printf("level = %d\n", r->level);
1183  if (levels[i] > levelHigh)
1184  levelHigh = levels[i];
1185  if (levels[i] < levelLow)
1186  levelLow = levels[i];
1187  i++;
1188  }
1189 
1190  // implements reordering of the line (L2 according to BiDi spec):
1191  // L2. From the highest level found in the text to the lowest odd level on each line,
1192  // reverse any contiguous sequence of characters that are at that level or higher.
1193 
1194  // reversing is only done up to the lowest odd level
1195  if (!(levelLow%2)) levelLow++;
1196 
1197  BIDI_DEBUG() << "reorderLine: lineLow = " << (uint)levelLow << ", lineHigh = " << (uint)levelHigh;
1198 
1199  int count = numItems - 1;
1200  for (i = 0; i < numItems; i++)
1201  visualOrder[i] = i;
1202 
1203  while(levelHigh >= levelLow) {
1204  int i = 0;
1205  while (i < count) {
1206  while(i < count && levels[i] < levelHigh) i++;
1207  int start = i;
1208  while(i <= count && levels[i] >= levelHigh) i++;
1209  int end = i-1;
1210 
1211  if (start != end) {
1212  //qDebug() << "reversing from " << start << " to " << end;
1213  for(int j = 0; j < (end-start+1)/2; j++) {
1214  int tmp = visualOrder[start+j];
1215  visualOrder[start+j] = visualOrder[end-j];
1216  visualOrder[end-j] = tmp;
1217  }
1218  }
1219  i++;
1220  }
1221  levelHigh--;
1222  }
1223 
1224 // BIDI_DEBUG("visual order is:");
1225 // for (i = 0; i < numItems; i++)
1226 // BIDI_DEBUG() << visualOrder[i];
1227 }
1228 
1229 
1231  Justification_Prohibited = 0, // Justification can not be applied after this glyph
1232  Justification_Arabic_Space = 1, // This glyph represents a space inside arabic text
1233  Justification_Character = 2, // Inter-character justification point follows this glyph
1234  Justification_Space = 4, // This glyph represents a blank outside an Arabic run
1235  Justification_Arabic_Normal = 7, // Normal Middle-Of-Word glyph that connects to the right (begin)
1236  Justification_Arabic_Waw = 8, // Next character is final form of Waw/Ain/Qaf/Feh
1237  Justification_Arabic_BaRa = 9, // Next two characters are Ba + Ra/Ya/AlefMaksura
1238  Justification_Arabic_Alef = 10, // Next character is final form of Alef/Tah/Lam/Kaf/Gaf
1239  Justification_Arabic_HahDal = 11, // Next character is final form of Hah/Dal/Teh Marbuta
1240  Justification_Arabic_Seen = 12, // Initial or medial form of Seen/Sad
1241  Justification_Arabic_Kashida = 13 // User-inserted Kashida(U+0640)
1242 };
1243 
1244 #if QT_CONFIG(harfbuzz)
1245 
1246 /*
1247  Adds an inter character justification opportunity after the number or letter
1248  character and a space justification opportunity after the space character.
1249 */
1250 static inline void qt_getDefaultJustificationOpportunities(const ushort *string, int length, const QGlyphLayout &g, ushort *log_clusters, int spaceAs)
1251 {
1252  int str_pos = 0;
1253  while (str_pos < length) {
1254  int glyph_pos = log_clusters[str_pos];
1255 
1256  Q_ASSERT(glyph_pos < g.numGlyphs && g.attributes[glyph_pos].clusterStart);
1257 
1258  uint ucs4 = string[str_pos];
1259  if (QChar::isHighSurrogate(ucs4) && str_pos + 1 < length) {
1260  ushort low = string[str_pos + 1];
1261  if (QChar::isLowSurrogate(low)) {
1262  ++str_pos;
1263  ucs4 = QChar::surrogateToUcs4(ucs4, low);
1264  }
1265  }
1266 
1267  // skip whole cluster
1268  do {
1269  ++str_pos;
1270  } while (str_pos < length && log_clusters[str_pos] == glyph_pos);
1271  do {
1272  ++glyph_pos;
1273  } while (glyph_pos < g.numGlyphs && !g.attributes[glyph_pos].clusterStart);
1274  --glyph_pos;
1275 
1276  // justification opportunity at the end of cluster
1277  if (Q_LIKELY(QChar::isLetterOrNumber(ucs4)))
1278  g.attributes[glyph_pos].justification = Justification_Character;
1279  else if (Q_LIKELY(QChar::isSpace(ucs4)))
1280  g.attributes[glyph_pos].justification = spaceAs;
1281  }
1282 }
1283 
1284 static inline void qt_getJustificationOpportunities(const ushort *string, int length, const QScriptItem &si, const QGlyphLayout &g, ushort *log_clusters)
1285 {
1286  Q_ASSERT(length > 0 && g.numGlyphs > 0);
1287 
1288  for (int glyph_pos = 0; glyph_pos < g.numGlyphs; ++glyph_pos)
1289  g.attributes[glyph_pos].justification = Justification_Prohibited;
1290 
1291  int spaceAs;
1292 
1293  switch (si.analysis.script) {
1294  case QChar::Script_Arabic:
1295  case QChar::Script_Syriac:
1296  case QChar::Script_Nko:
1297  case QChar::Script_Mandaic:
1299  case QChar::Script_PhagsPa:
1302  // same as default but inter character justification takes precedence
1303  spaceAs = Justification_Arabic_Space;
1304  break;
1305 
1306  case QChar::Script_Tibetan:
1310  case QChar::Script_Han:
1311  // same as default but inter character justification is the only option
1312  spaceAs = Justification_Character;
1313  break;
1314 
1315  default:
1316  spaceAs = Justification_Space;
1317  break;
1318  }
1319 
1320  qt_getDefaultJustificationOpportunities(string, length, g, log_clusters, spaceAs);
1321 }
1322 
1323 #endif // harfbuzz
1324 
1325 
1326 // shape all the items that intersect with the line, taking tab widths into account to find out what text actually fits in the line.
1328 {
1329  QFixed x;
1330  bool first = true;
1331  int item = findItem(line.from);
1332  if (item == -1)
1333  return;
1334 
1335  const int end = findItem(line.from + line.length + line.trailingSpaces - 1, item);
1336  for ( ; item <= end; ++item) {
1337  QScriptItem &si = layoutData->items[item];
1338  if (si.analysis.flags == QScriptAnalysis::Tab) {
1339  ensureSpace(1);
1340  si.width = calculateTabWidth(item, x);
1341  } else {
1342  shape(item);
1343  }
1344  if (first && si.position != line.from) { // that means our x position has to be offset
1345  QGlyphLayout glyphs = shapedGlyphs(&si);
1346  Q_ASSERT(line.from > si.position);
1347  for (int i = line.from - si.position - 1; i >= 0; i--) {
1348  x -= glyphs.effectiveAdvance(i);
1349  }
1350  }
1351  first = false;
1352 
1353  x += si.width;
1354  }
1355 }
1356 
1357 #if QT_CONFIG(harfbuzz)
1358 extern bool qt_useHarfbuzzNG(); // defined in qfontengine.cpp
1359 #endif
1360 
1361 static void applyVisibilityRules(ushort ucs, QGlyphLayout *glyphs, uint glyphPosition, QFontEngine *fontEngine)
1362 {
1363  // hide characters that should normally be invisible
1364  switch (ucs) {
1365  case QChar::LineFeed:
1366  case 0x000c: // FormFeed
1367  case QChar::CarriageReturn:
1368  case QChar::LineSeparator:
1370  glyphs->attributes[glyphPosition].dontPrint = true;
1371  break;
1372  case QChar::SoftHyphen:
1373  if (!fontEngine->symbol) {
1374  // U+00AD [SOFT HYPHEN] is a default ignorable codepoint,
1375  // so we replace its glyph and metrics with ones for
1376  // U+002D [HYPHEN-MINUS] or U+2010 [HYPHEN] and make
1377  // it visible if it appears at line-break
1378  const uint engineIndex = glyphs->glyphs[glyphPosition] & 0xff000000;
1379  glyph_t glyph = fontEngine->glyphIndex(0x002d);
1380  if (glyph == 0)
1381  glyph = fontEngine->glyphIndex(0x2010);
1382  if (glyph == 0)
1383  glyph = fontEngine->glyphIndex(0x00ad);
1384  glyphs->glyphs[glyphPosition] = glyph;
1385  if (Q_LIKELY(glyphs->glyphs[glyphPosition] != 0)) {
1386  glyphs->glyphs[glyphPosition] |= engineIndex;
1387  QGlyphLayout tmp = glyphs->mid(glyphPosition, 1);
1388  fontEngine->recalcAdvances(&tmp, { });
1389  }
1390  glyphs->attributes[glyphPosition].dontPrint = true;
1391  }
1392  break;
1393  default:
1394  break;
1395  }
1396 }
1397 
1398 void QTextEngine::shapeText(int item) const
1399 {
1400  Q_ASSERT(item < layoutData->items.size());
1401  QScriptItem &si = layoutData->items[item];
1402 
1403  if (si.num_glyphs)
1404  return;
1405 
1406  si.width = 0;
1408 
1409  const ushort *string = reinterpret_cast<const ushort *>(layoutData->string.constData()) + si.position;
1410  const int itemLength = length(item);
1411 
1412  QString casedString;
1414  casedString.resize(itemLength);
1415  ushort *uc = reinterpret_cast<ushort *>(casedString.data());
1416  for (int i = 0; i < itemLength; ++i) {
1417  uint ucs4 = string[i];
1418  if (QChar::isHighSurrogate(ucs4) && i + 1 < itemLength) {
1419  uint low = string[i + 1];
1420  if (QChar::isLowSurrogate(low)) {
1421  // high part never changes in simple casing
1422  uc[i] = ucs4;
1423  ++i;
1424  ucs4 = QChar::surrogateToUcs4(ucs4, low);
1426  : QChar::toUpper(ucs4);
1427  uc[i] = QChar::lowSurrogate(ucs4);
1428  }
1429  } else {
1431  : QChar::toUpper(ucs4);
1432  }
1433  }
1434  string = reinterpret_cast<const ushort *>(casedString.constData());
1435  }
1436 
1437  if (Q_UNLIKELY(!ensureSpace(itemLength))) {
1438  Q_UNREACHABLE(); // ### report OOM error somehow
1439  return;
1440  }
1441 
1442  QFontEngine *fontEngine = this->fontEngine(si, &si.ascent, &si.descent, &si.leading);
1443 
1444  bool kerningEnabled;
1445  bool letterSpacingIsAbsolute;
1446  bool shapingEnabled;
1447  QFixed letterSpacing, wordSpacing;
1448 #ifndef QT_NO_RAWFONT
1449  if (useRawFont) {
1450  QTextCharFormat f = format(&si);
1451  QFont font = f.font();
1452  kerningEnabled = font.kerning();
1455  wordSpacing = QFixed::fromReal(font.wordSpacing());
1456  letterSpacing = QFixed::fromReal(font.letterSpacing());
1457  letterSpacingIsAbsolute = true;
1458  } else
1459 #endif
1460  {
1461  QFont font = this->font(si);
1462  kerningEnabled = font.d->kerning;
1465  letterSpacingIsAbsolute = font.d->letterSpacingIsAbsolute;
1466  letterSpacing = font.d->letterSpacing;
1467  wordSpacing = font.d->wordSpacing;
1468 
1469  if (letterSpacingIsAbsolute && letterSpacing.value())
1470  letterSpacing *= font.d->dpi / qt_defaultDpiY();
1471  }
1472 
1473  // split up the item into parts that come from different font engines
1474  // k * 3 entries, array[k] == index in string, array[k + 1] == index in glyphs, array[k + 2] == engine index
1475  QList<uint> itemBoundaries;
1476  itemBoundaries.reserve(24);
1477 
1478  QGlyphLayout initialGlyphs = availableGlyphs(&si);
1479  int nGlyphs = initialGlyphs.numGlyphs;
1480  if (fontEngine->type() == QFontEngine::Multi || !shapingEnabled) {
1481  // ask the font engine to find out which glyphs (as an index in the specific font)
1482  // to use for the text in one item.
1483  QFontEngine::ShaperFlags shaperFlags =
1484  shapingEnabled
1487  if (!fontEngine->stringToCMap(reinterpret_cast<const QChar *>(string), itemLength, &initialGlyphs, &nGlyphs, shaperFlags))
1488  Q_UNREACHABLE();
1489  }
1490 
1491  if (fontEngine->type() == QFontEngine::Multi) {
1492  uint lastEngine = ~0u;
1493  for (int i = 0, glyph_pos = 0; i < itemLength; ++i, ++glyph_pos) {
1494  const uint engineIdx = initialGlyphs.glyphs[glyph_pos] >> 24;
1495  if (lastEngine != engineIdx) {
1496  itemBoundaries.append(i);
1497  itemBoundaries.append(glyph_pos);
1498  itemBoundaries.append(engineIdx);
1499 
1500  if (engineIdx != 0) {
1501  QFontEngine *actualFontEngine = static_cast<QFontEngineMulti *>(fontEngine)->engine(engineIdx);
1502  si.ascent = qMax(actualFontEngine->ascent(), si.ascent);
1503  si.descent = qMax(actualFontEngine->descent(), si.descent);
1504  si.leading = qMax(actualFontEngine->leading(), si.leading);
1505  }
1506 
1507  lastEngine = engineIdx;
1508  }
1509 
1510  if (QChar::isHighSurrogate(string[i]) && i + 1 < itemLength && QChar::isLowSurrogate(string[i + 1]))
1511  ++i;
1512  }
1513  } else {
1514  itemBoundaries.append(0);
1515  itemBoundaries.append(0);
1516  itemBoundaries.append(0);
1517  }
1518 
1519 #if QT_CONFIG(harfbuzz)
1520  if (Q_LIKELY(shapingEnabled && qt_useHarfbuzzNG())) {
1521  si.num_glyphs = shapeTextWithHarfbuzzNG(si, string, itemLength, fontEngine, itemBoundaries, kerningEnabled, letterSpacing != 0);
1522  } else
1523 #endif
1524  {
1525  ushort *log_clusters = logClusters(&si);
1526 
1527  int glyph_pos = 0;
1528  for (int i = 0; i < itemLength; ++i, ++glyph_pos) {
1529  log_clusters[i] = glyph_pos;
1530  initialGlyphs.attributes[glyph_pos].clusterStart = true;
1531  if (QChar::isHighSurrogate(string[i])
1532  && i + 1 < itemLength
1533  && QChar::isLowSurrogate(string[i + 1])) {
1534  initialGlyphs.attributes[glyph_pos].dontPrint = !QChar::isPrint(QChar::surrogateToUcs4(string[i], string[i + 1]));
1535  ++i;
1536  log_clusters[i] = glyph_pos;
1537 
1538  } else {
1539  initialGlyphs.attributes[glyph_pos].dontPrint = !QChar::isPrint(string[i]);
1540  }
1541 
1542  if (Q_UNLIKELY(!initialGlyphs.attributes[glyph_pos].dontPrint)) {
1543  QFontEngine *actualFontEngine = fontEngine;
1544  if (actualFontEngine->type() == QFontEngine::Multi) {
1545  const uint engineIdx = initialGlyphs.glyphs[glyph_pos] >> 24;
1546  actualFontEngine = static_cast<QFontEngineMulti *>(fontEngine)->engine(engineIdx);
1547  }
1548 
1549  applyVisibilityRules(string[i], &initialGlyphs, glyph_pos, actualFontEngine);
1550  }
1551  }
1552 
1553  si.num_glyphs = glyph_pos;
1554  }
1555 
1556  if (Q_UNLIKELY(si.num_glyphs == 0)) {
1557  if (Q_UNLIKELY(!ensureSpace(si.glyph_data_offset + 1))) {
1558  qWarning() << "Unable to allocate space for place-holder glyph";
1559  return;
1560  }
1561 
1562  si.num_glyphs = 1;
1563 
1564  // Overwrite with 0 token to indicate failure
1566  g.glyphs[0] = 0;
1567  g.attributes[0].clusterStart = true;
1568 
1569  ushort *log_clusters = logClusters(&si);
1570  for (int i = 0; i < itemLength; ++i)
1571  log_clusters[i] = 0;
1572 
1573  return;
1574  }
1575 
1576  layoutData->used += si.num_glyphs;
1577 
1578  QGlyphLayout glyphs = shapedGlyphs(&si);
1579 
1580 #if QT_CONFIG(harfbuzz)
1581  if (Q_LIKELY(qt_useHarfbuzzNG()))
1582  qt_getJustificationOpportunities(string, itemLength, si, glyphs, logClusters(&si));
1583 #endif
1584 
1585  if (letterSpacing != 0) {
1586  for (int i = 1; i < si.num_glyphs; ++i) {
1587  if (glyphs.attributes[i].clusterStart) {
1588  if (letterSpacingIsAbsolute)
1589  glyphs.advances[i - 1] += letterSpacing;
1590  else {
1591  QFixed &advance = glyphs.advances[i - 1];
1592  advance += (letterSpacing - 100) * advance / 100;
1593  }
1594  }
1595  }
1596  if (letterSpacingIsAbsolute)
1597  glyphs.advances[si.num_glyphs - 1] += letterSpacing;
1598  else {
1599  QFixed &advance = glyphs.advances[si.num_glyphs - 1];
1600  advance += (letterSpacing - 100) * advance / 100;
1601  }
1602  }
1603  if (wordSpacing != 0) {
1604  for (int i = 0; i < si.num_glyphs; ++i) {
1607  // word spacing only gets added once to a consecutive run of spaces (see CSS spec)
1608  if (i + 1 == si.num_glyphs
1611  glyphs.advances[i] += wordSpacing;
1612  }
1613  }
1614  }
1615 
1616  for (int i = 0; i < si.num_glyphs; ++i)
1617  si.width += glyphs.advances[i] * !glyphs.attributes[i].dontPrint;
1618 }
1619 
1620 #if QT_CONFIG(harfbuzz)
1621 
1623 
1624 #include "qharfbuzzng_p.h"
1625 
1627 
1628 int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si,
1629  const ushort *string,
1630  int itemLength,
1631  QFontEngine *fontEngine,
1632  const QList<uint> &itemBoundaries,
1633  bool kerningEnabled,
1634  bool hasLetterSpacing) const
1635 {
1636  uint glyphs_shaped = 0;
1637 
1640  hb_buffer_pre_allocate(buffer, itemLength);
1643  return 0;
1644  }
1645 
1647  props.direction = si.analysis.bidiLevel % 2 ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
1650  // ### TODO get_default_for_script?
1651  props.language = hb_language_get_default(); // use default language from locale
1652 
1653  for (int k = 0; k < itemBoundaries.size(); k += 3) {
1654  const uint item_pos = itemBoundaries[k];
1655  const uint item_length = (k + 4 < itemBoundaries.size() ? itemBoundaries[k + 3] : itemLength) - item_pos;
1656  const uint engineIdx = itemBoundaries[k + 2];
1657 
1658  QFontEngine *actualFontEngine = fontEngine->type() != QFontEngine::Multi ? fontEngine
1659  : static_cast<QFontEngineMulti *>(fontEngine)->engine(engineIdx);
1660 
1661 
1662  // prepare buffer
1664  hb_buffer_add_utf16(buffer, reinterpret_cast<const uint16_t *>(string) + item_pos, item_length, 0, item_length);
1665 
1667 
1668  uint buffer_flags = HB_BUFFER_FLAG_DEFAULT;
1669  // Symbol encoding used to encode various crap in the 32..255 character code range,
1670  // and thus might override U+00AD [SHY]; avoid hiding default ignorables
1671  if (Q_UNLIKELY(actualFontEngine->symbol))
1674 
1675 
1676  // shape
1677  {
1678  hb_font_t *hb_font = hb_qt_font_get_for_engine(actualFontEngine);
1679  Q_ASSERT(hb_font);
1680  hb_qt_font_set_use_design_metrics(hb_font, option.useDesignMetrics() ? uint(QFontEngine::DesignMetrics) : 0); // ###
1681 
1682  // Ligatures are incompatible with custom letter spacing, so when a letter spacing is set,
1683  // we disable them for writing systems where they are purely cosmetic.
1684  bool scriptRequiresOpenType = ((script >= QChar::Script_Syriac && script <= QChar::Script_Sinhala)
1686 
1687  bool dontLigate = hasLetterSpacing && !scriptRequiresOpenType;
1688  const hb_feature_t features[5] = {
1689  { HB_TAG('k','e','r','n'), !!kerningEnabled, HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END },
1690  { HB_TAG('l','i','g','a'), false, HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END },
1691  { HB_TAG('c','l','i','g'), false, HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END },
1692  { HB_TAG('d','l','i','g'), false, HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END },
1693  { HB_TAG('h','l','i','g'), false, HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END }
1694  };
1695  const int num_features = dontLigate ? 5 : 1;
1696 
1697  // whitelist cross-platforms shapers only
1698  static const char *shaper_list[] = {
1699  "graphite2",
1700  "ot",
1701  "fallback",
1702  nullptr
1703  };
1704 
1705  bool shapedOk = hb_shape_full(hb_font, buffer, features, num_features, shaper_list);
1706  if (Q_UNLIKELY(!shapedOk)) {
1708  return 0;
1709  }
1710 
1711  if (Q_UNLIKELY(HB_DIRECTION_IS_BACKWARD(props.direction)))
1713  }
1714 
1715  const uint num_glyphs = hb_buffer_get_length(buffer);
1716  // ensure we have enough space for shaped glyphs and metrics
1717  if (Q_UNLIKELY(num_glyphs == 0 || !ensureSpace(glyphs_shaped + num_glyphs))) {
1719  return 0;
1720  }
1721 
1722  // fetch the shaped glyphs and metrics
1723  QGlyphLayout g = availableGlyphs(&si).mid(glyphs_shaped, num_glyphs);
1724  ushort *log_clusters = logClusters(&si) + item_pos;
1725 
1728  uint str_pos = 0;
1729  uint last_cluster = ~0u;
1730  uint last_glyph_pos = glyphs_shaped;
1731  for (uint i = 0; i < num_glyphs; ++i, ++infos, ++positions) {
1732  g.glyphs[i] = infos->codepoint;
1733 
1734  g.advances[i] = QFixed::fromFixed(positions->x_advance);
1735  g.offsets[i].x = QFixed::fromFixed(positions->x_offset);
1736  g.offsets[i].y = QFixed::fromFixed(positions->y_offset);
1737 
1738  uint cluster = infos->cluster;
1739  if (Q_LIKELY(last_cluster != cluster)) {
1740  g.attributes[i].clusterStart = true;
1741 
1742  // fix up clusters so that the cluster indices will be monotonic
1743  // and thus we never return out-of-order indices
1744  while (last_cluster++ < cluster && str_pos < item_length)
1745  log_clusters[str_pos++] = last_glyph_pos;
1746  last_glyph_pos = i + glyphs_shaped;
1747  last_cluster = cluster;
1748 
1749  applyVisibilityRules(string[item_pos + str_pos], &g, i, actualFontEngine);
1750  }
1751  }
1752  while (str_pos < item_length)
1753  log_clusters[str_pos++] = last_glyph_pos;
1754 
1755  if (Q_UNLIKELY(engineIdx != 0)) {
1756  for (quint32 i = 0; i < num_glyphs; ++i)
1757  g.glyphs[i] |= (engineIdx << 24);
1758  }
1759 
1760  if (!actualFontEngine->supportsHorizontalSubPixelPositions()) {
1761  for (uint i = 0; i < num_glyphs; ++i)
1762  g.advances[i] = g.advances[i].round();
1763  }
1764 
1765  glyphs_shaped += num_glyphs;
1766  }
1767 
1769 
1770  return glyphs_shaped;
1771 }
1772 
1773 #endif // harfbuzz
1774 
1775 void QTextEngine::init(QTextEngine *e)
1776 {
1777  e->ignoreBidi = false;
1778  e->cacheGlyphs = false;
1779  e->forceJustification = false;
1780  e->visualMovement = false;
1781  e->delayDecorations = false;
1782 
1783  e->layoutData = nullptr;
1784 
1785  e->minWidth = 0;
1786  e->maxWidth = 0;
1787 
1788  e->specialData = nullptr;
1789  e->stackEngine = false;
1790 #ifndef QT_NO_RAWFONT
1791  e->useRawFont = false;
1792 #endif
1793 }
1794 
1796 {
1797  init(this);
1798 }
1799 
1801  : text(str),
1802  fnt(f)
1803 {
1804  init(this);
1805 }
1806 
1808 {
1809  if (!stackEngine)
1810  delete layoutData;
1811  delete specialData;
1813 }
1814 
1816 {
1818  return (QCharAttributes *) layoutData->memory;
1819 
1820  itemize();
1821  if (! ensureSpace(layoutData->string.length()))
1822  return nullptr;
1823 
1825  for (int i = 0; i < layoutData->items.size(); ++i) {
1826  const QScriptItem &si = layoutData->items.at(i);
1827  scriptItems[i].position = si.position;
1828  scriptItems[i].script = QChar::Script(si.analysis.script);
1829  }
1830 
1832  layoutData->string,
1833  scriptItems.data(), scriptItems.size(),
1834  reinterpret_cast<QCharAttributes *>(layoutData->memory),
1835  QUnicodeTools::CharAttributeOptions(QUnicodeTools::GraphemeBreaks
1839 
1840 
1842  return (QCharAttributes *) layoutData->memory;
1843 }
1844 
1845 void QTextEngine::shape(int item) const
1846 {
1847  auto &li = layoutData->items[item];
1848  if (li.analysis.flags == QScriptAnalysis::Object) {
1849  ensureSpace(1);
1850  if (QTextDocumentPrivate::get(block) != nullptr) {
1852  li.position + block.position(),
1853  format(&li));
1854  }
1855  // fix log clusters to point to the previous glyph, as the object doesn't have a glyph of it's own.
1856  // This is required so that all entries in the array get initialized and are ordered correctly.
1857  if (layoutData->logClustersPtr) {
1858  ushort *lc = logClusters(&li);
1859  *lc = (lc != layoutData->logClustersPtr) ? lc[-1] : 0;
1860  }
1861  } else if (li.analysis.flags == QScriptAnalysis::Tab) {
1862  // set up at least the ascent/descent/leading of the script item for the tab
1863  fontEngine(li, &li.ascent, &li.descent, &li.leading);
1864  // see the comment above
1865  if (layoutData->logClustersPtr) {
1866  ushort *lc = logClusters(&li);
1867  *lc = (lc != layoutData->logClustersPtr) ? lc[-1] : 0;
1868  }
1869  } else {
1870  shapeText(item);
1871  }
1872 }
1873 
1874 static inline void releaseCachedFontEngine(QFontEngine *fontEngine)
1875 {
1876  if (fontEngine && !fontEngine->ref.deref())
1877  delete fontEngine;
1878 }
1879 
1881 {
1882  releaseCachedFontEngine(feCache.prevFontEngine);
1883  releaseCachedFontEngine(feCache.prevScaledFontEngine);
1884  feCache.reset();
1885 }
1886 
1888 {
1889  freeMemory();
1890  minWidth = 0;
1891  maxWidth = 0;
1892 
1894 }
1895 
1897 {
1898  lines.clear();
1899 }
1900 
1902 {
1903  if (layoutData)
1904  return;
1905  layoutData = new LayoutData();
1906  if (QTextDocumentPrivate::get(block) != nullptr) {
1907  layoutData->string = block.text();
1908  const bool nextBlockValid = block.next().isValid();
1909  if (!nextBlockValid && option.flags() & QTextOption::ShowDocumentTerminator) {
1910  layoutData->string += QLatin1Char('\xA7');
1911  } else if (option.flags() & QTextOption::ShowLineAndParagraphSeparators) {
1912  layoutData->string += QLatin1Char(nextBlockValid ? '\xB6' : '\x20');
1913  }
1914 
1915  } else {
1916  layoutData->string = text;
1917  }
1918  if (specialData && specialData->preeditPosition != -1)
1919  layoutData->string.insert(specialData->preeditPosition, specialData->preeditText);
1920 }
1921 
1923 {
1924  validate();
1925  if (layoutData->items.size())
1926  return;
1927 
1928  int length = layoutData->string.length();
1929  if (!length)
1930  return;
1931 
1932  const ushort *string = reinterpret_cast<const ushort *>(layoutData->string.unicode());
1933 
1934  bool rtl = isRightToLeft();
1935 
1937  QScriptAnalysis *analysis = scriptAnalysis.data();
1938 
1939  QBidiAlgorithm bidi(layoutData->string.constData(), analysis, length, rtl);
1940  layoutData->hasBidi = bidi.process();
1941 
1942  {
1943  QUnicodeTools::ScriptItemArray scriptItems;
1945  for (int i = 0; i < scriptItems.length(); ++i) {
1946  const auto &item = scriptItems.at(i);
1947  int end = i < scriptItems.length() - 1 ? scriptItems.at(i + 1).position : length;
1948  for (int j = item.position; j < end; ++j)
1949  analysis[j].script = item.script;
1950  }
1951  }
1952 
1953  const ushort *uc = string;
1954  const ushort *e = uc + length;
1955  while (uc < e) {
1956  switch (*uc) {
1958  analysis->flags = QScriptAnalysis::Object;
1959  break;
1960  case QChar::LineSeparator:
1963  const int offset = uc - string;
1965  string = reinterpret_cast<const ushort *>(layoutData->string.unicode());
1966  uc = string + offset;
1967  e = string + length;
1968  *const_cast<ushort*>(uc) = 0x21B5; // visual line separator
1969  }
1970  break;
1971  case QChar::Tabulation:
1972  analysis->flags = QScriptAnalysis::Tab;
1973  analysis->bidiLevel = bidi.baseLevel;
1974  break;
1975  case QChar::Space:
1976  case QChar::Nbsp:
1977  if (option.flags() & QTextOption::ShowTabsAndSpaces) {
1979  break;
1980  }
1981  Q_FALLTHROUGH();
1982  default:
1983  analysis->flags = QScriptAnalysis::None;
1984  break;
1985  }
1986  ++uc;
1987  ++analysis;
1988  }
1990  (analysis-1)->flags = QScriptAnalysis::LineOrParagraphSeparator; // to exclude it from width
1991  }
1992 
1993  Itemizer itemizer(layoutData->string, scriptAnalysis.data(), layoutData->items);
1994 
1996  if (p) {
1997  SpecialData *s = specialData;
1998 
2000  QTextDocumentPrivate::FragmentIterator end = p->find(block.position() + block.length() - 1); // -1 to omit the block separator char
2001  int format = it.value()->format;
2002 
2003  int preeditPosition = s ? s->preeditPosition : INT_MAX;
2004  int prevPosition = 0;
2005  int position = prevPosition;
2006  while (1) {
2007  const QTextFragmentData * const frag = it.value();
2008  if (it == end || format != frag->format) {
2009  if (s && position >= preeditPosition) {
2010  position += s->preeditText.length();
2011  preeditPosition = INT_MAX;
2012  }
2013  Q_ASSERT(position <= length);
2014  QFont::Capitalization capitalization =
2018  if (s) {
2019  for (const auto &range : qAsConst(s->formats)) {
2020  if (range.start + range.length <= prevPosition || range.start >= position)
2021  continue;
2022  if (range.format.hasProperty(QTextFormat::FontCapitalization)) {
2023  if (range.start > prevPosition)
2024  itemizer.generate(prevPosition, range.start - prevPosition, capitalization);
2025  int newStart = std::max(prevPosition, range.start);
2026  int newEnd = std::min(position, range.start + range.length);
2027  itemizer.generate(newStart, newEnd - newStart, range.format.fontCapitalization());
2028  prevPosition = newEnd;
2029  }
2030  }
2031  }
2032  itemizer.generate(prevPosition, position - prevPosition, capitalization);
2033  if (it == end) {
2034  if (position < length)
2035  itemizer.generate(position, length - position, capitalization);
2036  break;
2037  }
2038  format = frag->format;
2039  prevPosition = position;
2040  }
2041  position += frag->size_array[0];
2042  ++it;
2043  }
2044  } else {
2045 #ifndef QT_NO_RAWFONT
2046  if (useRawFont && specialData) {
2047  int lastIndex = 0;
2048  for (int i = 0; i < specialData->formats.size(); ++i) {
2049  const QTextLayout::FormatRange &range = specialData->formats.at(i);
2050  const QTextCharFormat &format = range.format;
2051  if (format.hasProperty(QTextFormat::FontCapitalization)) {
2052  itemizer.generate(lastIndex, range.start - lastIndex, QFont::MixedCase);
2053  itemizer.generate(range.start, range.length, format.fontCapitalization());
2054  lastIndex = range.start + range.length;
2055  }
2056  }
2057  itemizer.generate(lastIndex, length - lastIndex, QFont::MixedCase);
2058  } else
2059 #endif
2060  itemizer.generate(0, length, static_cast<QFont::Capitalization> (fnt.d->capital));
2061  }
2062 
2063  addRequiredBoundaries();
2064  resolveFormats();
2065 }
2066 
2068 {
2069  switch (option.textDirection()) {
2070  case Qt::LeftToRight:
2071  return false;
2072  case Qt::RightToLeft:
2073  return true;
2074  default:
2075  break;
2076  }
2077  if (!layoutData)
2078  itemize();
2079  // this places the cursor in the right position depending on the keyboard layout
2080  if (layoutData->string.isEmpty())
2082  return layoutData->string.isRightToLeft();
2083 }
2084 
2085 
2086 int QTextEngine::findItem(int strPos, int firstItem) const
2087 {
2088  itemize();
2089  if (strPos < 0 || strPos >= layoutData->string.size() || firstItem < 0)
2090  return -1;
2091 
2092  int left = firstItem + 1;
2093  int right = layoutData->items.size()-1;
2094  while(left <= right) {
2095  int middle = ((right-left)/2)+left;
2096  if (strPos > layoutData->items.at(middle).position)
2097  left = middle+1;
2098  else if (strPos < layoutData->items.at(middle).position)
2099  right = middle-1;
2100  else {
2101  return middle;
2102  }
2103  }
2104  return right;
2105 }
2106 
2107 namespace {
2108 template<typename InnerFunc>
2109 void textIterator(const QTextEngine *textEngine, int from, int len, QFixed &width, InnerFunc &&innerFunc)
2110 {
2111  for (int i = 0; i < textEngine->layoutData->items.size(); i++) {
2112  const QScriptItem *si = textEngine->layoutData->items.constData() + i;
2113  int pos = si->position;
2114  int ilen = textEngine->length(i);
2115 // qDebug("item %d: from %d len %d", i, pos, ilen);
2116  if (pos >= from + len)
2117  break;
2118  if (pos + ilen > from) {
2119  if (!si->num_glyphs)
2120  textEngine->shape(i);
2121 
2122  if (si->analysis.flags == QScriptAnalysis::Object) {
2123  width += si->width;
2124  continue;
2125  } else if (si->analysis.flags == QScriptAnalysis::Tab) {
2126  width += textEngine->calculateTabWidth(i, width);
2127  continue;
2128  }
2129 
2130  unsigned short *logClusters = textEngine->logClusters(si);
2131 
2132 // fprintf(stderr, " logclusters:");
2133 // for (int k = 0; k < ilen; k++)
2134 // fprintf(stderr, " %d", logClusters[k]);
2135 // fprintf(stderr, "\n");
2136  // do the simple thing for now and give the first glyph in a cluster the full width, all other ones 0.
2137  int charFrom = from - pos;
2138  if (charFrom < 0)
2139  charFrom = 0;
2140  int glyphStart = logClusters[charFrom];
2141  if (charFrom > 0 && logClusters[charFrom-1] == glyphStart)
2142  while (charFrom < ilen && logClusters[charFrom] == glyphStart)
2143  charFrom++;
2144  if (charFrom < ilen) {
2145  glyphStart = logClusters[charFrom];
2146  int charEnd = from + len - 1 - pos;
2147  if (charEnd >= ilen)
2148  charEnd = ilen-1;
2149  int glyphEnd = logClusters[charEnd];
2150  while (charEnd < ilen && logClusters[charEnd] == glyphEnd)
2151  charEnd++;
2152  glyphEnd = (charEnd == ilen) ? si->num_glyphs : logClusters[charEnd];
2153 
2154 // qDebug("char: start=%d end=%d / glyph: start = %d, end = %d", charFrom, charEnd, glyphStart, glyphEnd);
2155  innerFunc(glyphStart, glyphEnd, si);
2156  }
2157  }
2158  }
2159 }
2160 } // namespace
2161 
2162 QFixed QTextEngine::width(int from, int len) const
2163 {
2164  itemize();
2165 
2166  QFixed w = 0;
2167 // qDebug("QTextEngine::width(from = %d, len = %d), numItems=%d, strleng=%d", from, len, items.size(), string.length());
2168  textIterator(this, from, len, w, [this, &w](int glyphStart, int glyphEnd, const QScriptItem *si) {
2169  QGlyphLayout glyphs = this->shapedGlyphs(si);
2170  for (int j = glyphStart; j < glyphEnd; j++)
2171  w += glyphs.advances[j] * !glyphs.attributes[j].dontPrint;
2172  });
2173 // qDebug(" --> w= %d ", w);
2174  return w;
2175 }
2176 
2178 {
2179  itemize();
2180 
2181  glyph_metrics_t gm;
2182 
2183  textIterator(this, from, len, gm.width, [this, &gm](int glyphStart, int glyphEnd, const QScriptItem *si) {
2184  if (glyphStart <= glyphEnd) {
2185  QGlyphLayout glyphs = this->shapedGlyphs(si);
2186  QFontEngine *fe = this->fontEngine(*si);
2187  glyph_metrics_t m = fe->boundingBox(glyphs.mid(glyphStart, glyphEnd - glyphStart));
2188  gm.x = qMin(gm.x, m.x + gm.xoff);
2189  gm.y = qMin(gm.y, m.y + gm.yoff);
2190  gm.width = qMax(gm.width, m.width + gm.xoff);
2191  gm.height = qMax(gm.height, m.height + gm.yoff);
2192  gm.xoff += m.xoff;
2193  gm.yoff += m.yoff;
2194  }
2195  });
2196 
2197  return gm;
2198 }
2199 
2201 {
2202  itemize();
2203 
2204  glyph_metrics_t gm;
2205 
2206  textIterator(this, from, len, gm.width, [this, &gm](int glyphStart, int glyphEnd, const QScriptItem *si) {
2207  if (glyphStart <= glyphEnd) {
2208  QGlyphLayout glyphs = this->shapedGlyphs(si);
2209  QFontEngine *fe = fontEngine(*si);
2210  glyph_metrics_t m = fe->tightBoundingBox(glyphs.mid(glyphStart, glyphEnd - glyphStart));
2211  gm.x = qMin(gm.x, m.x + gm.xoff);
2212  gm.y = qMin(gm.y, m.y + gm.yoff);
2213  gm.width = qMax(gm.width, m.width + gm.xoff);
2214  gm.height = qMax(gm.height, m.height + gm.yoff);
2215  gm.xoff += m.xoff;
2216  gm.yoff += m.yoff;
2217  }
2218  });
2219  return gm;
2220 }
2221 
2223 {
2224  QFont font = fnt;
2225  if (hasFormats()) {
2226  QTextCharFormat f = format(&si);
2227  font = f.font();
2228 
2230  if (document_d != nullptr && document_d->layout() != nullptr) {
2231  // Make sure we get the right dpi on printers
2232  QPaintDevice *pdev = document_d->layout()->paintDevice();
2233  if (pdev)
2234  font = QFont(font, pdev);
2235  } else {
2236  font = font.resolve(fnt);
2237  }
2238  QTextCharFormat::VerticalAlignment valign = f.verticalAlignment();
2240  if (font.pointSize() != -1)
2241  font.setPointSize((font.pointSize() * 2) / 3);
2242  else
2243  font.setPixelSize((font.pixelSize() * 2) / 3);
2244  }
2245  }
2246 
2248  font = font.d->smallCapsFont();
2249 
2250  return font;
2251 }
2252 
2253 QTextEngine::FontEngineCache::FontEngineCache()
2254 {
2255  reset();
2256 }
2257 
2258 //we cache the previous results of this function, as calling it numerous times with the same effective
2259 //input is common (and hard to cache at a higher level)
2260 QFontEngine *QTextEngine::fontEngine(const QScriptItem &si, QFixed *ascent, QFixed *descent, QFixed *leading) const
2261 {
2262  QFontEngine *engine = nullptr;
2263  QFontEngine *scaledEngine = nullptr;
2264  int script = si.analysis.script;
2265 
2266  QFont font = fnt;
2267 #ifndef QT_NO_RAWFONT
2268  if (useRawFont && rawFont.isValid()) {
2269  if (feCache.prevFontEngine && feCache.prevFontEngine->type() == QFontEngine::Multi && feCache.prevScript == script) {
2270  engine = feCache.prevFontEngine;
2271  } else {
2273  feCache.prevFontEngine = engine;
2274  feCache.prevScript = script;
2275  engine->ref.ref();
2276  if (feCache.prevScaledFontEngine) {
2277  releaseCachedFontEngine(feCache.prevScaledFontEngine);
2278  feCache.prevScaledFontEngine = nullptr;
2279  }
2280  }
2282  if (feCache.prevScaledFontEngine) {
2283  scaledEngine = feCache.prevScaledFontEngine;
2284  } else {
2285  // GCC 12 gets confused about QFontEngine::ref, for some non-obvious reason
2286  // warning: ‘unsigned int __atomic_or_fetch_4(volatile void*, unsigned int, int)’ writing 4 bytes
2287  // into a region of size 0 overflows the destination [-Wstringop-overflow=]
2289  QT_WARNING_DISABLE_GCC("-Wstringop-overflow")
2290 
2291  QFontEngine *scEngine = rawFont.d->fontEngine->cloneWithSize(smallCapsFraction * rawFont.pixelSize());
2292  scEngine->ref.ref();
2293  scaledEngine = QFontEngineMulti::createMultiFontEngine(scEngine, script);
2294  scaledEngine->ref.ref();
2295  feCache.prevScaledFontEngine = scaledEngine;
2296  // If scEngine is not ref'ed by scaledEngine, make sure it is deallocated and not leaked.
2297  if (!scEngine->ref.deref())
2298  delete scEngine;
2299 
2301  }
2302  }
2303  } else
2304 #endif
2305  {
2306  if (hasFormats()) {
2307  if (feCache.prevFontEngine && feCache.prevPosition == si.position && feCache.prevLength == length(&si) && feCache.prevScript == script) {
2308  engine = feCache.prevFontEngine;
2309  scaledEngine = feCache.prevScaledFontEngine;
2310  } else {
2311  QTextCharFormat f = format(&si);
2312  font = f.font();
2313 
2314  if (QTextDocumentPrivate::get(block) != nullptr && QTextDocumentPrivate::get(block)->layout() != nullptr) {
2315  // Make sure we get the right dpi on printers
2317  if (pdev)
2318  font = QFont(font, pdev);
2319  } else {
2320  font = font.resolve(fnt);
2321  }
2322  engine = font.d->engineForScript(script);
2323  Q_ASSERT(engine);
2324  engine->ref.ref();
2325 
2326  QTextCharFormat::VerticalAlignment valign = f.verticalAlignment();
2328  if (font.pointSize() != -1)
2329  font.setPointSize((font.pointSize() * 2) / 3);
2330  else
2331  font.setPixelSize((font.pixelSize() * 2) / 3);
2332  scaledEngine = font.d->engineForScript(script);
2333  if (scaledEngine)
2334  scaledEngine->ref.ref();
2335  }
2336 
2337  if (feCache.prevFontEngine)
2338  releaseCachedFontEngine(feCache.prevFontEngine);
2339  feCache.prevFontEngine = engine;
2340 
2341  if (feCache.prevScaledFontEngine)
2342  releaseCachedFontEngine(feCache.prevScaledFontEngine);
2343  feCache.prevScaledFontEngine = scaledEngine;
2344 
2345  feCache.prevScript = script;
2346  feCache.prevPosition = si.position;
2347  feCache.prevLength = length(&si);
2348  }
2349  } else {
2350  if (feCache.prevFontEngine && feCache.prevScript == script && feCache.prevPosition == -1) {
2351  engine = feCache.prevFontEngine;
2352  } else {
2353  engine = font.d->engineForScript(script);
2354  Q_ASSERT(engine);
2355  engine->ref.ref();
2356  if (feCache.prevFontEngine)
2357  releaseCachedFontEngine(feCache.prevFontEngine);
2358  feCache.prevFontEngine = engine;
2359 
2360  feCache.prevScript = script;
2361  feCache.prevPosition = -1;
2362  feCache.prevLength = -1;
2363  feCache.prevScaledFontEngine = nullptr;
2364  }
2365  }
2366 
2369  scaledEngine = p->engineForScript(script);
2370  }
2371  }
2372 
2373  if (leading) {
2374  Q_ASSERT(engine);
2375  Q_ASSERT(ascent);
2376  Q_ASSERT(descent);
2377  *ascent = engine->ascent();
2378  *descent = engine->descent();
2379  *leading = engine->leading();
2380  }
2381 
2382  if (scaledEngine)
2383  return scaledEngine;
2384  return engine;
2385 }
2386 
2388  int type;
2391 };
2392 
2394 
2395 static void set(QJustificationPoint *point, int type, const QGlyphLayout &glyph, QFontEngine *fe)
2396 {
2397  point->type = type;
2398  point->glyph = glyph;
2399 
2401  const char32_t ch = U'\x640'; // Kashida character
2402 
2403  glyph_t kashidaGlyph = fe->glyphIndex(ch);
2404  if (kashidaGlyph != 0) {
2405  QGlyphLayout g;
2406  g.numGlyphs = 1;
2407  g.glyphs = &kashidaGlyph;
2408  g.advances = &point->kashidaWidth;
2409  fe->recalcAdvances(&g, { });
2410 
2411  if (point->kashidaWidth == 0)
2412  point->type = Justification_Prohibited;
2413  } else {
2414  point->type = Justification_Prohibited;
2415  point->kashidaWidth = 0;
2416  }
2417  }
2418 }
2419 
2420 
2422 {
2423 // qDebug("justify: line.gridfitted = %d, line.justified=%d", line.gridfitted, line.justified);
2424  if (line.gridfitted && line.justified)
2425  return;
2426 
2427  if (!line.gridfitted) {
2428  // redo layout in device metrics, then adjust
2429  const_cast<QScriptLine &>(line).gridfitted = true;
2430  }
2431 
2432  if ((option.alignment() & Qt::AlignHorizontal_Mask) != Qt::AlignJustify)
2433  return;
2434 
2435  itemize();
2436 
2437  if (!forceJustification) {
2438  int end = line.from + (int)line.length + line.trailingSpaces;
2439  if (end == layoutData->string.length())
2440  return; // no justification at end of paragraph
2442  return; // no justification at the end of an explicitly separated line
2443  }
2444 
2445  // justify line
2446  int maxJustify = 0;
2447 
2448  // don't include trailing white spaces when doing justification
2449  int line_length = line.length;
2450  const QCharAttributes *a = attributes();
2451  if (! a)
2452  return;
2453  a += line.from;
2454  while (line_length && a[line_length-1].whiteSpace)
2455  --line_length;
2456  // subtract one char more, as we can't justfy after the last character
2457  --line_length;
2458 
2459  if (line_length <= 0)
2460  return;
2461 
2462  int firstItem = findItem(line.from);
2463  int lastItem = findItem(line.from + line_length - 1, firstItem);
2464  int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2465 
2466  QVarLengthArray<QJustificationPoint> justificationPoints;
2467  int nPoints = 0;
2468 // qDebug("justifying from %d len %d, firstItem=%d, nItems=%d (%s)", line.from, line_length, firstItem, nItems, layoutData->string.mid(line.from, line_length).toUtf8().constData());
2469  QFixed minKashida = 0x100000;
2470 
2471  // we need to do all shaping before we go into the next loop, as we there
2472  // store pointers to the glyph data that could get reallocated by the shaping
2473  // process.
2474  for (int i = 0; i < nItems; ++i) {
2475  const QScriptItem &si = layoutData->items.at(firstItem + i);
2476  if (!si.num_glyphs)
2477  shape(firstItem + i);
2478  }
2479 
2480  for (int i = 0; i < nItems; ++i) {
2481  const QScriptItem &si = layoutData->items.at(firstItem + i);
2482 
2483  int kashida_type = Justification_Arabic_Normal;
2484  int kashida_pos = -1;
2485 
2486  int start = qMax(line.from - si.position, 0);
2487  int end = qMin(line.from + line_length - (int)si.position, length(firstItem+i));
2488 
2489  unsigned short *log_clusters = logClusters(&si);
2490 
2491  int gs = log_clusters[start];
2492  int ge = (end == length(firstItem+i) ? si.num_glyphs : log_clusters[end]);
2493 
2494  Q_ASSERT(ge <= si.num_glyphs);
2495 
2496  const QGlyphLayout g = shapedGlyphs(&si);
2497 
2498  for (int i = gs; i < ge; ++i) {
2499  g.justifications[i].type = QGlyphJustification::JustifyNone;
2500  g.justifications[i].nKashidas = 0;
2501  g.justifications[i].space_18d6 = 0;
2502 
2503  justificationPoints.resize(nPoints+3);
2504  int justification = g.attributes[i].justification;
2505 
2506  switch(justification) {
2508  break;
2509  case Justification_Space:
2511  if (kashida_pos >= 0) {
2512 // qDebug("kashida position at %d in word", kashida_pos);
2513  set(&justificationPoints[nPoints], kashida_type, g.mid(kashida_pos), fontEngine(si));
2514  if (justificationPoints[nPoints].kashidaWidth > 0) {
2515  minKashida = qMin(minKashida, justificationPoints[nPoints].kashidaWidth);
2516  maxJustify = qMax(maxJustify, justificationPoints[nPoints].type);
2517  ++nPoints;
2518  }
2519  }
2520  kashida_pos = -1;
2521  kashida_type = Justification_Arabic_Normal;
2522  Q_FALLTHROUGH();
2524  set(&justificationPoints[nPoints++], justification, g.mid(i), fontEngine(si));
2525  maxJustify = qMax(maxJustify, justification);
2526  break;
2534  if (justification >= kashida_type) {
2535  kashida_pos = i;
2536  kashida_type = justification;
2537  }
2538  }
2539  }
2540  if (kashida_pos >= 0) {
2541  set(&justificationPoints[nPoints], kashida_type, g.mid(kashida_pos), fontEngine(si));
2542  if (justificationPoints[nPoints].kashidaWidth > 0) {
2543  minKashida = qMin(minKashida, justificationPoints[nPoints].kashidaWidth);
2544  maxJustify = qMax(maxJustify, justificationPoints[nPoints].type);
2545  ++nPoints;
2546  }
2547  }
2548  }
2549 
2550  QFixed leading = leadingSpaceWidth(line);
2551  QFixed need = line.width - line.textWidth - leading;
2552  if (need < 0) {
2553  // line overflows already!
2554  const_cast<QScriptLine &>(line).justified = true;
2555  return;
2556  }
2557 
2558 // qDebug("doing justification: textWidth=%x, requested=%x, maxJustify=%d", line.textWidth.value(), line.width.value(), maxJustify);
2559 // qDebug(" minKashida=%f, need=%f", minKashida.toReal(), need.toReal());
2560 
2561  // distribute in priority order
2562  if (maxJustify >= Justification_Arabic_Normal) {
2563  while (need >= minKashida) {
2564  for (int type = maxJustify; need >= minKashida && type >= Justification_Arabic_Normal; --type) {
2565  for (int i = 0; need >= minKashida && i < nPoints; ++i) {
2566  if (justificationPoints[i].type == type && justificationPoints[i].kashidaWidth <= need) {
2567  justificationPoints[i].glyph.justifications->nKashidas++;
2568  // ############
2569  justificationPoints[i].glyph.justifications->space_18d6 += justificationPoints[i].kashidaWidth.value();
2570  need -= justificationPoints[i].kashidaWidth;
2571 // qDebug("adding kashida type %d with width %x, neednow %x", type, justificationPoints[i].kashidaWidth, need.value());
2572  }
2573  }
2574  }
2575  }
2576  }
2577  Q_ASSERT(need >= 0);
2578  if (!need)
2579  goto end;
2580 
2581  maxJustify = qMin(maxJustify, int(Justification_Space));
2582  for (int type = maxJustify; need != 0 && type > 0; --type) {
2583  int n = 0;
2584  for (int i = 0; i < nPoints; ++i) {
2585  if (justificationPoints[i].type == type)
2586  ++n;
2587  }
2588 // qDebug("number of points for justification type %d: %d", type, n);
2589 
2590 
2591  if (!n)
2592  continue;
2593 
2594  for (int i = 0; i < nPoints; ++i) {
2595  if (justificationPoints[i].type == type) {
2596  QFixed add = need/n;
2597 // qDebug("adding %x to glyph %x", add.value(), justificationPoints[i].glyph->glyph);
2598  justificationPoints[i].glyph.justifications[0].space_18d6 = add.value();
2599  need -= add;
2600  --n;
2601  }
2602  }
2603 
2604  Q_ASSERT(!need);
2605  }
2606  end:
2607  const_cast<QScriptLine &>(line).justified = true;
2608 }
2609 
2611 {
2612  QFont f;
2613  QFontEngine *e;
2614 
2615  if (QTextDocumentPrivate::get(eng->block) != nullptr && QTextDocumentPrivate::get(eng->block)->layout() != nullptr) {
2616  f = eng->block.charFormat().font();
2617  // Make sure we get the right dpi on printers
2619  if (pdev)
2620  f = QFont(f, pdev);
2621  e = f.d->engineForScript(QChar::Script_Common);
2622  } else {
2624  }
2625 
2626  QFixed other_ascent = e->ascent();
2627  QFixed other_descent = e->descent();
2628  QFixed other_leading = e->leading();
2629  leading = qMax(leading + ascent, other_leading + other_ascent) - qMax(ascent, other_ascent);
2630  ascent = qMax(ascent, other_ascent);
2631  descent = qMax(descent, other_descent);
2632 }
2633 
2635 {
2636  memory = nullptr;
2637  allocated = 0;
2638  memory_on_stack = false;
2639  used = 0;
2640  hasBidi = false;
2642  haveCharAttributes = false;
2643  logClustersPtr = nullptr;
2644  available_glyphs = 0;
2645 }
2646 
2647 QTextEngine::LayoutData::LayoutData(const QString &str, void **stack_memory, int _allocated)
2648  : string(str)
2649 {
2650  allocated = _allocated;
2651 
2652  int space_charAttributes = int(sizeof(QCharAttributes) * string.length() / sizeof(void*) + 1);
2653  int space_logClusters = int(sizeof(unsigned short) * string.length() / sizeof(void*) + 1);
2654  available_glyphs = ((int)allocated - space_charAttributes - space_logClusters)*(int)sizeof(void*)/(int)QGlyphLayout::SpaceNeeded;
2655 
2656  if (available_glyphs < str.length()) {
2657  // need to allocate on the heap
2658  allocated = 0;
2659 
2660  memory_on_stack = false;
2661  memory = nullptr;
2662  logClustersPtr = nullptr;
2663  } else {
2664  memory_on_stack = true;
2665  memory = stack_memory;
2666  logClustersPtr = (unsigned short *)(memory + space_charAttributes);
2667 
2668  void *m = memory + space_charAttributes + space_logClusters;
2669  glyphLayout = QGlyphLayout(reinterpret_cast<char *>(m), str.length());
2670  glyphLayout.clear();
2671  memset(memory, 0, space_charAttributes*sizeof(void *));
2672  }
2673  used = 0;
2674  hasBidi = false;
2676  haveCharAttributes = false;
2677 }
2678 
2680 {
2681  if (!memory_on_stack)
2682  free(memory);
2683  memory = nullptr;
2684 }
2685 
2687 {
2688  Q_ASSERT(totalGlyphs >= glyphLayout.numGlyphs);
2689  if (memory_on_stack && available_glyphs >= totalGlyphs) {
2690  glyphLayout.grow(glyphLayout.data(), totalGlyphs);
2691  return true;
2692  }
2693 
2694  int space_charAttributes = int(sizeof(QCharAttributes) * string.length() / sizeof(void*) + 1);
2695  int space_logClusters = int(sizeof(unsigned short) * string.length() / sizeof(void*) + 1);
2696  int space_glyphs = (totalGlyphs * QGlyphLayout::SpaceNeeded) / sizeof(void *) + 2;
2697 
2698  int newAllocated = space_charAttributes + space_glyphs + space_logClusters;
2699  // These values can be negative if the length of string/glyphs causes overflow,
2700  // we can't layout such a long string all at once, so return false here to
2701  // indicate there is a failure
2702  if (space_charAttributes < 0 || space_logClusters < 0 || space_glyphs < 0 || newAllocated < allocated) {
2703  layoutState = LayoutFailed;
2704  return false;
2705  }
2706 
2707  void **newMem = (void **)::realloc(memory_on_stack ? nullptr : memory, newAllocated*sizeof(void *));
2708  if (!newMem) {
2709  layoutState = LayoutFailed;
2710  return false;
2711  }
2712  if (memory_on_stack)
2713  memcpy(newMem, memory, allocated*sizeof(void *));
2714  memory = newMem;
2715  memory_on_stack = false;
2716 
2717  void **m = memory;
2718  m += space_charAttributes;
2719  logClustersPtr = (unsigned short *) m;
2720  m += space_logClusters;
2721 
2722  const int space_preGlyphLayout = space_charAttributes + space_logClusters;
2723  if (allocated < space_preGlyphLayout)
2724  memset(memory + allocated, 0, (space_preGlyphLayout - allocated)*sizeof(void *));
2725 
2726  glyphLayout.grow(reinterpret_cast<char *>(m), totalGlyphs);
2727 
2728  allocated = newAllocated;
2729  return true;
2730 }
2731 
2732 // grow to the new size, copying the existing data to the new layout
2733 void QGlyphLayout::grow(char *address, int totalGlyphs)
2734 {
2735  QGlyphLayout oldLayout(address, numGlyphs);
2736  QGlyphLayout newLayout(address, totalGlyphs);
2737 
2738  if (numGlyphs) {
2739  // move the existing data
2740  memmove(newLayout.attributes, oldLayout.attributes, numGlyphs * sizeof(QGlyphAttributes));
2741  memmove(newLayout.justifications, oldLayout.justifications, numGlyphs * sizeof(QGlyphJustification));
2742  memmove(newLayout.advances, oldLayout.advances, numGlyphs * sizeof(QFixed));
2743  memmove(newLayout.glyphs, oldLayout.glyphs, numGlyphs * sizeof(glyph_t));
2744  }
2745 
2746  // clear the new data
2747  newLayout.clear(numGlyphs);
2748 
2749  *this = newLayout;
2750 }
2751 
2753 {
2754  if (!stackEngine) {
2755  delete layoutData;
2756  layoutData = nullptr;
2757  } else {
2758  layoutData->used = 0;
2759  layoutData->hasBidi = false;
2761  layoutData->haveCharAttributes = false;
2762  layoutData->items.clear();
2763  }
2764  if (specialData)
2765  specialData->resolvedFormats.clear();
2766  for (int i = 0; i < lines.size(); ++i) {
2767  lines[i].justified = 0;
2768  lines[i].gridfitted = 0;
2769  }
2770 }
2771 
2773 {
2774  if (specialData && !specialData->resolvedFormats.isEmpty()) {
2775  QTextFormatCollection *collection = formatCollection();
2776  Q_ASSERT(collection);
2777  return collection->indexForFormat(specialData->resolvedFormats.at(si - &layoutData->items.at(0)));
2778  }
2779 
2781  if (!p)
2782  return -1;
2783  int pos = si->position;
2784  if (specialData && si->position >= specialData->preeditPosition) {
2785  if (si->position < specialData->preeditPosition + specialData->preeditText.length())
2786  pos = qMax(qMin(block.length(), specialData->preeditPosition) - 1, 0);
2787  else
2788  pos -= specialData->preeditText.length();
2789  }
2791  return it.value()->format;
2792 }
2793 
2794 
2796 {
2797  if (const QTextFormatCollection *collection = formatCollection())
2798  return collection->charFormat(formatIndex(si));
2799  return QTextCharFormat();
2800 }
2801 
2802 void QTextEngine::addRequiredBoundaries() const
2803 {
2804  if (specialData) {
2805  for (int i = 0; i < specialData->formats.size(); ++i) {
2806  const QTextLayout::FormatRange &r = specialData->formats.at(i);
2807  setBoundary(r.start);
2808  setBoundary(r.start + r.length);
2809  //qDebug("adding boundaries %d %d", r.start, r.start+r.length);
2810  }
2811  }
2812 }
2813 
2815 {
2816  const QChar c = layoutData->string.at(position);
2817  switch (c.unicode()) {
2818  case '.':
2819  case ',':
2820  case '?':
2821  case '!':
2822  case '@':
2823  case '#':
2824  case '$':
2825  case ':':
2826  case ';':
2827  case '-':
2828  case '<':
2829  case '>':
2830  case '[':
2831  case ']':
2832  case '(':
2833  case ')':
2834  case '{':
2835  case '}':
2836  case '=':
2837  case '/':
2838  case '+':
2839  case '%':
2840  case '&':
2841  case '^':
2842  case '*':
2843  case '\'':
2844  case '"':
2845  case '`':
2846  case '~':
2847  case '|':
2848  case '\\':
2849  return true;
2850  default:
2851  break;
2852  }
2853  return false;
2854 }
2855 
2856 void QTextEngine::setPreeditArea(int position, const QString &preeditText)
2857 {
2858  if (preeditText.isEmpty()) {
2859  if (!specialData)
2860  return;
2861  if (specialData->formats.isEmpty()) {
2862  delete specialData;
2863  specialData = nullptr;
2864  } else {
2865  specialData->preeditText = QString();
2866  specialData->preeditPosition = -1;
2867  }
2868  } else {
2869  if (!specialData)
2870  specialData = new SpecialData;
2871  specialData->preeditPosition = position;
2872  specialData->preeditText = preeditText;
2873  }
2874  invalidate();
2875  clearLineData();
2876 }
2877 
2879 {
2880  if (formats.isEmpty()) {
2881  if (!specialData)
2882  return;
2883  if (specialData->preeditText.isEmpty()) {
2884  delete specialData;
2885  specialData = nullptr;
2886  } else {
2887  specialData->formats.clear();
2888  }
2889  } else {
2890  if (!specialData) {
2891  specialData = new SpecialData;
2892  specialData->preeditPosition = -1;
2893  }
2894  specialData->formats = formats;
2895  indexFormats();
2896  }
2897  invalidate();
2898  clearLineData();
2899 }
2900 
2901 void QTextEngine::indexFormats()
2902 {
2903  QTextFormatCollection *collection = formatCollection();
2904  if (!collection) {
2906  specialData->formatCollection.reset(new QTextFormatCollection);
2907  collection = specialData->formatCollection.data();
2908  }
2909 
2910  // replace with shared copies
2911  for (int i = 0; i < specialData->formats.size(); ++i) {
2912  QTextCharFormat &format = specialData->formats[i].format;
2913  format = collection->charFormat(collection->indexForFormat(format));
2914  }
2915 }
2916 
2917 /* These two helper functions are used to determine whether we need to insert a ZWJ character
2918  between the text that gets truncated and the ellipsis. This is important to get
2919  correctly shaped results for arabic text.
2920 */
2921 static inline bool nextCharJoins(const QString &string, int pos)
2922 {
2923  while (pos < string.length() && string.at(pos).category() == QChar::Mark_NonSpacing)
2924  ++pos;
2925  if (pos == string.length())
2926  return false;
2927  QChar::JoiningType joining = string.at(pos).joiningType();
2928  return joining != QChar::Joining_None && joining != QChar::Joining_Transparent;
2929 }
2930 
2931 static inline bool prevCharJoins(const QString &string, int pos)
2932 {
2933  while (pos > 0 && string.at(pos - 1).category() == QChar::Mark_NonSpacing)
2934  --pos;
2935  if (pos == 0)
2936  return false;
2937  QChar::JoiningType joining = string.at(pos - 1).joiningType();
2938  return joining == QChar::Joining_Dual || joining == QChar::Joining_Causing;
2939 }
2940 
2941 static inline bool isRetainableControlCode(QChar c)
2942 {
2943  return (c.unicode() >= 0x202a && c.unicode() <= 0x202e) // LRE, RLE, PDF, LRO, RLO
2944  || (c.unicode() >= 0x200e && c.unicode() <= 0x200f) // LRM, RLM
2945  || (c.unicode() >= 0x2066 && c.unicode() <= 0x2069); // LRI, RLI, FSI, PDI
2946 }
2947 
2948 static QString stringMidRetainingBidiCC(const QString &string,
2949  const QString &ellidePrefix,
2950  const QString &ellideSuffix,
2951  int subStringFrom,
2952  int subStringTo,
2953  int midStart,
2954  int midLength)
2955 {
2956  QString prefix;
2957  for (int i=subStringFrom; i<midStart; ++i) {
2958  QChar c = string.at(i);
2959  if (isRetainableControlCode(c))
2960  prefix += c;
2961  }
2962 
2963  QString suffix;
2964  for (int i=midStart + midLength; i<subStringTo; ++i) {
2965  QChar c = string.at(i);
2966  if (isRetainableControlCode(c))
2967  suffix += c;
2968  }
2969 
2970  return prefix + ellidePrefix + QStringView{string}.mid(midStart, midLength) + ellideSuffix + suffix;
2971 }
2972 
2974 {
2975 // qDebug() << "elidedText; available width" << width.toReal() << "text width:" << this->width(0, layoutData->string.length()).toReal();
2976 
2977  if (flags & Qt::TextShowMnemonic) {
2978  itemize();
2979  QCharAttributes *attributes = const_cast<QCharAttributes *>(this->attributes());
2980  if (!attributes)
2981  return QString();
2982  for (int i = 0; i < layoutData->items.size(); ++i) {
2983  const QScriptItem &si = layoutData->items.at(i);
2984  if (!si.num_glyphs)
2985  shape(i);
2986 
2987  unsigned short *logClusters = this->logClusters(&si);
2988  QGlyphLayout glyphs = shapedGlyphs(&si);
2989 
2990  const int end = si.position + length(&si);
2991  for (int i = si.position; i < end - 1; ++i) {
2992  if (layoutData->string.at(i) == QLatin1Char('&')
2993  && !attributes[i + 1].whiteSpace && attributes[i + 1].graphemeBoundary) {
2994  const int gp = logClusters[i - si.position];
2995  glyphs.attributes[gp].dontPrint = true;
2996  // emulate grapheme cluster
2997  attributes[i] = attributes[i + 1];
2998  memset(attributes + i + 1, 0, sizeof(QCharAttributes));
2999  if (layoutData->string.at(i + 1) == QLatin1Char('&'))
3000  ++i;
3001  }
3002  }
3003  }
3004  }
3005 
3006  validate();
3007 
3008  const int to = count >= 0 && count <= layoutData->string.length() - from
3009  ? from + count
3010  : layoutData->string.length();
3011 
3012  if (mode == Qt::ElideNone
3013  || this->width(from, layoutData->string.length()) <= width
3014  || to - from <= 1)
3015  return layoutData->string.mid(from, from - to);
3016 
3017  QFixed ellipsisWidth;
3018  QString ellipsisText;
3019  {
3021 
3022  QChar ellipsisChar = u'\x2026';
3023 
3024  // We only want to use the ellipsis character if it is from the main
3025  // font (not one of the fallbacks), since using a fallback font
3026  // will affect the metrics of the text, potentially causing it to shift
3027  // when it is being elided.
3028  if (engine->type() == QFontEngine::Multi) {
3029  QFontEngineMulti *multiEngine = static_cast<QFontEngineMulti *>(engine);
3030  multiEngine->ensureEngineAt(0);
3031  engine = multiEngine->engine(0);
3032  }
3033 
3034  glyph_t glyph = engine->glyphIndex(ellipsisChar.unicode());
3035 
3036  QGlyphLayout glyphs;
3037  glyphs.numGlyphs = 1;
3038  glyphs.glyphs = &glyph;
3039  glyphs.advances = &ellipsisWidth;
3040 
3041  if (glyph != 0) {
3042  engine->recalcAdvances(&glyphs, { });
3043 
3044  ellipsisText = ellipsisChar;
3045  } else {
3046  glyph = engine->glyphIndex('.');
3047  if (glyph != 0) {
3048  engine->recalcAdvances(&glyphs, { });
3049 
3050  ellipsisWidth *= 3;
3051  ellipsisText = QStringLiteral("...");
3052  } else {
3054  glyph = engine->glyphIndex(ellipsisChar.unicode());
3055  engine->recalcAdvances(&glyphs, { });
3056  ellipsisText = ellipsisChar;
3057  }
3058  }
3059  }
3060 
3061  const QFixed availableWidth = width - ellipsisWidth;
3062  if (availableWidth < 0)
3063  return QString();
3064 
3065  const QCharAttributes *attributes = this->attributes();
3066  if (!attributes)
3067  return QString();
3068 
3069  constexpr char16_t ZWJ = u'\x200d'; // ZERO-WIDTH JOINER
3070 
3071  if (mode == Qt::ElideRight) {
3072  QFixed currentWidth;
3073  int pos;
3074  int nextBreak = from;
3075 
3076  do {
3077  pos = nextBreak;
3078 
3079  ++nextBreak;
3080  while (nextBreak < layoutData->string.length() && !attributes[nextBreak].graphemeBoundary)
3081  ++nextBreak;
3082 
3083  currentWidth += this->width(pos, nextBreak - pos);
3084  } while (nextBreak < to
3085  && currentWidth < availableWidth);
3086 
3087  if (nextCharJoins(layoutData->string, pos))
3088  ellipsisText.prepend(ZWJ);
3089 
3090  return stringMidRetainingBidiCC(layoutData->string,
3091  QString(), ellipsisText,
3092  from, to,
3093  from, pos - from);
3094  } else if (mode == Qt::ElideLeft) {
3095  QFixed currentWidth;
3096  int pos;
3097  int nextBreak = to;
3098 
3099  do {
3100  pos = nextBreak;
3101 
3102  --nextBreak;
3103  while (nextBreak > 0 && !attributes[nextBreak].graphemeBoundary)
3104  --nextBreak;
3105 
3106  currentWidth += this->width(nextBreak, pos - nextBreak);
3107  } while (nextBreak > from
3108  && currentWidth < availableWidth);
3109 
3110  if (prevCharJoins(layoutData->string, pos))
3111  ellipsisText.append(ZWJ);
3112 
3113  return stringMidRetainingBidiCC(layoutData->string,
3114  ellipsisText, QString(),
3115  from, to,
3116  pos, to - pos);
3117  } else if (mode == Qt::ElideMiddle) {
3118  QFixed leftWidth;
3119  QFixed rightWidth;
3120 
3121  int leftPos = from;
3122  int nextLeftBreak = from;
3123 
3124  int rightPos = to;
3125  int nextRightBreak = to;
3126 
3127  do {
3128  leftPos = nextLeftBreak;
3129  rightPos = nextRightBreak;
3130 
3131  ++nextLeftBreak;
3132  while (nextLeftBreak < layoutData->string.length() && !attributes[nextLeftBreak].graphemeBoundary)
3133  ++nextLeftBreak;
3134 
3135  --nextRightBreak;
3136  while (nextRightBreak > from && !attributes[nextRightBreak].graphemeBoundary)
3137  --nextRightBreak;
3138 
3139  leftWidth += this->width(leftPos, nextLeftBreak - leftPos);
3140  rightWidth += this->width(nextRightBreak, rightPos - nextRightBreak);
3141  } while (nextLeftBreak < to
3142  && nextRightBreak > from
3143  && leftWidth + rightWidth < availableWidth);
3144 
3145  if (nextCharJoins(layoutData->string, leftPos))
3146  ellipsisText.prepend(ZWJ);
3147  if (prevCharJoins(layoutData->string, rightPos))
3148  ellipsisText.append(ZWJ);
3149 
3150  return QStringView{layoutData->string}.mid(from, leftPos - from) + ellipsisText + QStringView{layoutData->string}.mid(rightPos, to - rightPos);
3151  }
3152 
3153  return layoutData->string.mid(from, to - from);
3154 }
3155 
3156 void QTextEngine::setBoundary(int strPos) const
3157 {
3158  const int item = findItem(strPos);
3159  if (item < 0)
3160  return;
3161 
3162  QScriptItem newItem = layoutData->items.at(item);
3163  if (newItem.position != strPos) {
3164  newItem.position = strPos;
3165  layoutData->items.insert(item + 1, newItem);
3166  }
3167 }
3168 
3170 {
3171  const QScriptItem &si = layoutData->items[item];
3172 
3173  QFixed dpiScale = 1;
3174  if (QTextDocumentPrivate::get(block) != nullptr && QTextDocumentPrivate::get(block)->layout() != nullptr) {
3176  if (pdev)
3177  dpiScale = QFixed::fromReal(pdev->logicalDpiY() / qreal(qt_defaultDpiY()));
3178  } else {
3179  dpiScale = QFixed::fromReal(fnt.d->dpi / qreal(qt_defaultDpiY()));
3180  }
3181 
3182  QList<QTextOption::Tab> tabArray = option.tabs();
3183  if (!tabArray.isEmpty()) {
3184  if (isRightToLeft()) { // rebase the tabArray positions.
3185  auto isLeftOrRightTab = [](const QTextOption::Tab &tab) {
3186  return tab.type == QTextOption::LeftTab || tab.type == QTextOption::RightTab;
3187  };
3188  const auto cbegin = tabArray.cbegin();
3189  const auto cend = tabArray.cend();
3190  const auto cit = std::find_if(cbegin, cend, isLeftOrRightTab);
3191  if (cit != cend) {
3192  const int index = std::distance(cbegin, cit);
3193  auto iter = tabArray.begin() + index;
3194  const auto end = tabArray.end();
3195  while (iter != end) {
3196  QTextOption::Tab &tab = *iter;
3197  if (tab.type == QTextOption::LeftTab)
3199  else if (tab.type == QTextOption::RightTab)
3200  tab.type = QTextOption::LeftTab;
3201  ++iter;
3202  }
3203  }
3204  }
3205  for (const QTextOption::Tab &tabSpec : qAsConst(tabArray)) {
3206  QFixed tab = QFixed::fromReal(tabSpec.position) * dpiScale;
3207  if (tab > x) { // this is the tab we need.
3208  int tabSectionEnd = layoutData->string.count();
3209  if (tabSpec.type == QTextOption::RightTab || tabSpec.type == QTextOption::CenterTab) {
3210  // find next tab to calculate the width required.
3211  tab = QFixed::fromReal(tabSpec.position);
3212  for (int i=item + 1; i < layoutData->items.count(); i++) {
3213  const QScriptItem &item = layoutData->items[i];
3214  if (item.analysis.flags == QScriptAnalysis::TabOrObject) { // found it.
3215  tabSectionEnd = item.position;
3216  break;
3217  }
3218  }
3219  }
3220  else if (tabSpec.type == QTextOption::DelimiterTab)
3221  // find delimiter character to calculate the width required
3222  tabSectionEnd = qMax(si.position, layoutData->string.indexOf(tabSpec.delimiter, si.position) + 1);
3223 
3224  if (tabSectionEnd > si.position) {
3225  QFixed length;
3226  // Calculate the length of text between this tab and the tabSectionEnd
3227  for (int i=item; i < layoutData->items.count(); i++) {
3228  const QScriptItem &item = layoutData->items.at(i);
3229  if (item.position > tabSectionEnd || item.position <= si.position)
3230  continue;
3231  shape(i); // first, lets make sure relevant text is already shaped
3232  if (item.analysis.flags == QScriptAnalysis::Object) {
3233  length += item.width;
3234  continue;
3235  }
3236  QGlyphLayout glyphs = this->shapedGlyphs(&item);
3237  const int end = qMin(item.position + item.num_glyphs, tabSectionEnd) - item.position;
3238  for (int i=0; i < end; i++)
3239  length += glyphs.advances[i] * !glyphs.attributes[i].dontPrint;
3240  if (end + item.position == tabSectionEnd && tabSpec.type == QTextOption::DelimiterTab) // remove half of matching char
3241  length -= glyphs.advances[end] / 2 * !glyphs.attributes[end].dontPrint;
3242  }
3243 
3244  switch (tabSpec.type) {
3246  length /= 2;
3247  Q_FALLTHROUGH();
3249  case QTextOption::RightTab:
3250  tab = QFixed::fromReal(tabSpec.position) * dpiScale - length;
3251  if (tab < x) // default to tab taking no space
3252  return QFixed();
3253  break;
3254  case QTextOption::LeftTab:
3255  break;
3256  }
3257  }
3258  return tab - x;
3259  }
3260  }
3261  }
3262  QFixed tab = QFixed::fromReal(option.tabStopDistance());
3263  if (tab <= 0)
3264  tab = 80; // default
3265  tab *= dpiScale;
3266  QFixed nextTabPos = ((x / tab).truncate() + 1) * tab;
3267  QFixed tabWidth = nextTabPos - x;
3268 
3269  return tabWidth;
3270 }
3271 
3272 namespace {
3273 class FormatRangeComparatorByStart {
3275 public:
3276  FormatRangeComparatorByStart(const QList<QTextLayout::FormatRange> &list) : list(list) { }
3277  bool operator()(int a, int b) {
3278  return list.at(a).start < list.at(b).start;
3279  }
3280 };
3281 class FormatRangeComparatorByEnd {
3283 public:
3284  FormatRangeComparatorByEnd(const QList<QTextLayout::FormatRange> &list) : list(list) { }
3285  bool operator()(int a, int b) {
3286  return list.at(a).start + list.at(a).length < list.at(b).start + list.at(b).length;
3287  }
3288 };
3289 }
3290 
3291 void QTextEngine::resolveFormats() const
3292 {
3293  if (!specialData || specialData->formats.isEmpty())
3294  return;
3295  Q_ASSERT(specialData->resolvedFormats.isEmpty());
3296 
3297  QTextFormatCollection *collection = formatCollection();
3298 
3299  QList<QTextCharFormat> resolvedFormats(layoutData->items.count());
3300 
3301  QVarLengthArray<int, 64> formatsSortedByStart;
3302  formatsSortedByStart.reserve(specialData->formats.size());
3303  for (int i = 0; i < specialData->formats.size(); ++i) {
3304  if (specialData->formats.at(i).length >= 0)
3305  formatsSortedByStart.append(i);
3306  }
3307  QVarLengthArray<int, 64> formatsSortedByEnd = formatsSortedByStart;
3308  std::sort(formatsSortedByStart.begin(), formatsSortedByStart.end(),
3309  FormatRangeComparatorByStart(specialData->formats));
3310  std::sort(formatsSortedByEnd.begin(), formatsSortedByEnd.end(),
3311  FormatRangeComparatorByEnd(specialData->formats));
3312 
3313  QVarLengthArray<int, 16> currentFormats;
3314  const int *startIt = formatsSortedByStart.constBegin();
3315  const int *endIt = formatsSortedByEnd.constBegin();
3316 
3317  for (int i = 0; i < layoutData->items.count(); ++i) {
3318  const QScriptItem *si = &layoutData->items.at(i);
3319  int end = si->position + length(si);
3320 
3321  while (startIt != formatsSortedByStart.constEnd() &&
3322  specialData->formats.at(*startIt).start <= si->position) {
3323  currentFormats.insert(std::upper_bound(currentFormats.begin(), currentFormats.end(), *startIt),
3324  *startIt);
3325  ++startIt;
3326  }
3327  while (endIt != formatsSortedByEnd.constEnd() &&
3328  specialData->formats.at(*endIt).start + specialData->formats.at(*endIt).length < end) {
3329  int *currentFormatIterator = std::lower_bound(currentFormats.begin(), currentFormats.end(), *endIt);
3330  if (*endIt < *currentFormatIterator)
3331  currentFormatIterator = currentFormats.end();
3332  currentFormats.remove(currentFormatIterator - currentFormats.begin());
3333  ++endIt;
3334  }
3335 
3336  QTextCharFormat &format = resolvedFormats[i];
3337  if (QTextDocumentPrivate::get(block) != nullptr) {
3338  // when we have a QTextDocumentPrivate, formatIndex might still return a valid index based
3339  // on the preeditPosition. for all other cases, we cleared the resolved format indices
3340  format = collection->charFormat(formatIndex(si));
3341  }
3342  if (!currentFormats.isEmpty()) {
3343  for (int cur : currentFormats) {
3344  const QTextLayout::FormatRange &range = specialData->formats.at(cur);
3345  Q_ASSERT(range.start <= si->position && range.start + range.length >= end);
3346  format.merge(range.format);
3347  }
3348  format = collection->charFormat(collection->indexForFormat(format)); // get shared copy
3349  }
3350  }
3351 
3352  specialData->resolvedFormats = resolvedFormats;
3353 }
3354 
3356 {
3357  if (!line.hasTrailingSpaces
3359  || !isRightToLeft())
3360  return QFixed();
3361 
3362  return width(line.from + line.length, line.trailingSpaces);
3363 }
3364 
3366 {
3367  QFixed x = 0;
3368  justify(line);
3369  // if width is QFIXED_MAX that means we used setNumColumns() and that implicitly makes this line left aligned.
3370  if (!line.justified && line.width != QFIXED_MAX) {
3371  int align = option.alignment();
3372  if (align & Qt::AlignJustify && isRightToLeft())
3373  align = Qt::AlignRight;
3374  if (align & Qt::AlignRight)
3375  x = line.width - (line.textAdvance);
3376  else if (align & Qt::AlignHCenter)
3377  x = (line.width - line.textAdvance)/2;
3378  }
3379  return x;
3380 }
3381 
3382 QFixed QTextEngine::offsetInLigature(const QScriptItem *si, int pos, int max, int glyph_pos)
3383 {
3384  unsigned short *logClusters = this->logClusters(si);
3385  const QGlyphLayout &glyphs = shapedGlyphs(si);
3386 
3387  int offsetInCluster = 0;
3388  for (int i = pos - 1; i >= 0; i--) {
3389  if (logClusters[i] == glyph_pos)
3390  offsetInCluster++;
3391  else
3392  break;
3393  }
3394 
3395  // in the case that the offset is inside a (multi-character) glyph,
3396  // interpolate the position.
3397  if (offsetInCluster > 0) {
3398  int clusterLength = 0;
3399  for (int i = pos - offsetInCluster; i < max; i++) {
3400  if (logClusters[i] == glyph_pos)
3401  clusterLength++;
3402  else
3403  break;
3404  }
3405  if (clusterLength)
3406  return glyphs.advances[glyph_pos] * offsetInCluster / clusterLength;
3407  }
3408 
3409  return 0;
3410 }
3411 
3412 // Scan in logClusters[from..to-1] for glyph_pos
3413 int QTextEngine::getClusterLength(unsigned short *logClusters,
3414  const QCharAttributes *attributes,
3415  int from, int to, int glyph_pos, int *start)
3416 {
3417  int clusterLength = 0;
3418  for (int i = from; i < to; i++) {
3419  if (logClusters[i] == glyph_pos && attributes[i].graphemeBoundary) {
3420  if (*start < 0)
3421  *start = i;
3422  clusterLength++;
3423  }
3424  else if (clusterLength)
3425  break;
3426  }
3427  return clusterLength;
3428 }
3429 
3431  QFixed x, QFixed edge, int glyph_pos,
3432  bool cursorOnCharacter)
3433 {
3434  unsigned short *logClusters = this->logClusters(si);
3435  int clusterStart = -1;
3436  int clusterLength = 0;
3437 
3438  if (si->analysis.script != QChar::Script_Common &&
3445  if (glyph_pos == -1)
3446  return si->position + end;
3447  else {
3448  int i;
3449  for (i = 0; i < end; i++)
3450  if (logClusters[i] == glyph_pos)
3451  break;
3452  return si->position + i;
3453  }
3454  }
3455 
3456  if (glyph_pos == -1 && end > 0)
3457  glyph_pos = logClusters[end - 1];
3458  else {
3459  if (x <= edge)
3460  glyph_pos--;
3461  }
3462 
3463  const QCharAttributes *attrs = attributes() + si->position;
3464  logClusters = this->logClusters(si);
3465  clusterLength = getClusterLength(logClusters, attrs, 0, end, glyph_pos, &clusterStart);
3466 
3467  if (clusterLength) {
3468  const QGlyphLayout &glyphs = shapedGlyphs(si);
3469  QFixed glyphWidth = glyphs.effectiveAdvance(glyph_pos);
3470  // the approximate width of each individual element of the ligature
3471  QFixed perItemWidth = glyphWidth / clusterLength;
3472  if (perItemWidth <= 0)
3473  return si->position + clusterStart;
3474  QFixed left = x > edge ? edge : edge - glyphWidth;
3475  int n = ((x - left) / perItemWidth).floor().toInt();
3476  QFixed dist = x - left - n * perItemWidth;
3477  int closestItem = dist > (perItemWidth / 2) ? n + 1 : n;
3478  if (cursorOnCharacter && closestItem > 0)
3479  closestItem--;
3480  int pos = clusterStart + closestItem;
3481  // Jump to the next grapheme boundary
3482  while (pos < end && !attrs[pos].graphemeBoundary)
3483  pos++;
3484  return si->position + pos;
3485  }
3486  return si->position + end;
3487 }
3488 
3490 {
3491  const QCharAttributes *attrs = attributes();
3492  int len = block.isValid() ? block.length() - 1
3493  : layoutData->string.length();
3495  if (!attrs || oldPos <= 0 || oldPos > len)
3496  return oldPos;
3497 
3498  oldPos--;
3499  while (oldPos && !attrs[oldPos].graphemeBoundary)
3500  oldPos--;
3501  return oldPos;
3502 }
3503 
3505 {
3506  const QCharAttributes *attrs = attributes();
3507  int len = block.isValid() ? block.length() - 1
3508  : layoutData->string.length();
3510  if (!attrs || oldPos < 0 || oldPos >= len)
3511  return oldPos;
3512 
3513  oldPos++;
3514  while (oldPos < len && !attrs[oldPos].graphemeBoundary)
3515  oldPos++;
3516  return oldPos;
3517 }
3518 
3520 {
3521  if (!layoutData)
3522  itemize();
3523  if (pos == layoutData->string.length() && lines.size())
3524  return lines.size() - 1;
3525  for (int i = 0; i < lines.size(); ++i) {
3526  const QScriptLine& line = lines[i];
3527  if (line.from + line.length + line.trailingSpaces > pos)
3528  return i;
3529  }
3530  return -1;
3531 }
3532 
3533 std::vector<int> QTextEngine::insertionPointsForLine(int lineNum)
3534 {
3535  QTextLineItemIterator iterator(this, lineNum);
3536 
3537  std::vector<int> insertionPoints;
3538  insertionPoints.reserve(size_t(iterator.line.length));
3539 
3540  bool lastLine = lineNum >= lines.size() - 1;
3541 
3542  while (!iterator.atEnd()) {
3543  const QScriptItem &si = iterator.next();
3544 
3545  int end = iterator.itemEnd;
3546  if (lastLine && iterator.item == iterator.lastItem)
3547  ++end; // the last item in the last line -> insert eol position
3548  if (si.analysis.bidiLevel % 2) {
3549  for (int i = end - 1; i >= iterator.itemStart; --i)
3550  insertionPoints.push_back(i);
3551  } else {
3552  for (int i = iterator.itemStart; i < end; ++i)
3553  insertionPoints.push_back(i);
3554  }
3555  }
3556  return insertionPoints;
3557 }
3558 
3559 int QTextEngine::endOfLine(int lineNum)
3560 {
3561  const auto insertionPoints = insertionPointsForLine(lineNum);
3562  if (insertionPoints.size() > 0)
3563  return insertionPoints.back();
3564  return 0;
3565 }
3566 
3567 int QTextEngine::beginningOfLine(int lineNum)
3568 {
3569  const auto insertionPoints = insertionPointsForLine(lineNum);
3570  if (insertionPoints.size() > 0)
3571  return insertionPoints.front();
3572  return 0;
3573 }
3574 
3576 {
3577  itemize();
3578 
3579  bool moveRight = (op == QTextCursor::Right);
3580  bool alignRight = isRightToLeft();
3581  if (!layoutData->hasBidi)
3582  return moveRight ^ alignRight ? nextLogicalPosition(pos) : previousLogicalPosition(pos);
3583 
3584  int lineNum = lineNumberForTextPosition(pos);
3585  if (lineNum < 0)
3586  return pos;
3587 
3588  const auto insertionPoints = insertionPointsForLine(lineNum);
3589  for (size_t i = 0, max = insertionPoints.size(); i < max; ++i)
3590  if (pos == insertionPoints[i]) {
3591  if (moveRight) {
3592  if (i + 1 < max)
3593  return insertionPoints[i + 1];
3594  } else {
3595  if (i > 0)
3596  return insertionPoints[i - 1];
3597  }
3598 
3599  if (moveRight ^ alignRight) {
3600  if (lineNum + 1 < lines.size())
3601  return alignRight ? endOfLine(lineNum + 1) : beginningOfLine(lineNum + 1);
3602  }
3603  else {
3604  if (lineNum > 0)
3605  return alignRight ? beginningOfLine(lineNum - 1) : endOfLine(lineNum - 1);
3606  }
3607 
3608  break;
3609  }
3610 
3611  return pos;
3612 }
3613 
3614 void QTextEngine::addItemDecoration(QPainter *painter, const QLineF &line, ItemDecorationList *decorationList)
3615 {
3616  if (delayDecorations) {
3617  decorationList->append(ItemDecoration(line.x1(), line.x2(), line.y1(), painter->pen()));
3618  } else {
3619  painter->drawLine(line);
3620  }
3621 }
3622 
3624 {
3625  // qDebug() << "Adding underline:" << line;
3626  addItemDecoration(painter, line, &underlineList);
3627 }
3628 
3630 {
3631  addItemDecoration(painter, line, &strikeOutList);
3632 }
3633 
3635 {
3636  addItemDecoration(painter, line, &overlineList);
3637 }
3638 
3639 void QTextEngine::drawItemDecorationList(QPainter *painter, const ItemDecorationList &decorationList)
3640 {
3641  // qDebug() << "Drawing" << decorationList.size() << "decorations";
3642  if (decorationList.isEmpty())
3643  return;
3644 
3645  for (const ItemDecoration &decoration : decorationList) {
3646  painter->setPen(decoration.pen);
3648  }
3649 }
3650 
3652 {
3653  QPen oldPen = painter->pen();
3654 
3655  adjustUnderlines();
3656  drawItemDecorationList(painter, underlineList);
3657  drawItemDecorationList(painter, strikeOutList);
3658  drawItemDecorationList(painter, overlineList);
3659 
3660  clearDecorations();
3661 
3662  painter->setPen(oldPen);
3663 }
3664 
3666 {
3667  underlineList.clear();
3668  strikeOutList.clear();
3669  overlineList.clear();
3670 }
3671 
3673 {
3674  // qDebug() << __PRETTY_FUNCTION__ << underlineList.count() << "underlines";
3675  if (underlineList.isEmpty())
3676  return;
3677 
3681  qreal underlinePos = start->y;
3682  qreal penWidth = start->pen.widthF();
3683  qreal lastLineEnd = start->x1;
3684 
3685  while (it != end) {
3686  if (qFuzzyCompare(lastLineEnd, it->x1)) { // no gap between underlines
3687  underlinePos = qMax(underlinePos, it->y);
3688  penWidth = qMax(penWidth, it->pen.widthF());
3689  } else { // gap between this and the last underline
3690  adjustUnderlines(start, it, underlinePos, penWidth);
3691  start = it;
3692  underlinePos = start->y;
3693  penWidth = start->pen.widthF();
3694  }
3695  lastLineEnd = it->x2;
3696  ++it;
3697  }
3698 
3699  adjustUnderlines(start, end, underlinePos, penWidth);
3700 }
3701 
3704  qreal underlinePos, qreal penWidth)
3705 {
3706  for (ItemDecorationList::iterator it = start; it != end; ++it) {
3707  it->y = underlinePos;
3708  it->pen.setWidthF(penWidth);
3709  }
3710 }
3711 
3713  : QTextEngine(string, f),
3714  _layoutData(string, _memory, MemSize)
3715 {
3716  stackEngine = true;
3718 }
3719 
3721  : charFormat(format),
3722  f(font),
3723  fontEngine(font->d->engineForScript(si.analysis.script))
3724 {
3726 
3727  initWithScriptItem(si);
3728 }
3729 
3730 QTextItemInt::QTextItemInt(const QGlyphLayout &g, QFont *font, const QChar *chars_, int numChars, QFontEngine *fe, const QTextCharFormat &format)
3731  : charFormat(format),
3732  num_chars(numChars),
3733  chars(chars_),
3734  f(font),
3735  glyphs(g),
3736  fontEngine(fe)
3737 {
3738 }
3739 
3740 // Fix up flags and underlineStyle with given info
3742 {
3743  // explicitly initialize flags so that initFontAttributes can be called
3744  // multiple times on the same TextItem
3745  flags = { };
3746  if (si.analysis.bidiLevel %2)
3748  ascent = si.ascent;
3749  descent = si.descent;
3750 
3754  || f->d->underline) {
3756  }
3757 
3758  // compat
3761 
3762  if (f->d->overline || charFormat.fontOverline())
3764  if (f->d->strikeOut || charFormat.fontStrikeOut())
3766 }
3767 
3769 {
3770  QTextItemInt ti = *this;
3771  const int end = firstGlyphIndex + numGlyphs;
3773  ti.fontEngine = fontEngine;
3774 
3775  if (logClusters && chars) {
3776  const int logClusterOffset = logClusters[0];
3777  while (logClusters[ti.chars - chars] - logClusterOffset < firstGlyphIndex)
3778  ++ti.chars;
3779 
3780  ti.logClusters += (ti.chars - chars);
3781 
3782  ti.num_chars = 0;
3783  int char_start = ti.chars - chars;
3784  while (char_start + ti.num_chars < num_chars && ti.logClusters[ti.num_chars] - logClusterOffset < end)
3785  ++ti.num_chars;
3786  }
3787  return ti;
3788 }
3789 
3790 
3792 {
3793  QRectF rect = x.mapRect(QRectF(0, 0, w, h));
3794  return x * QTransform::fromTranslate(-rect.x(), -rect.y());
3795 }
3796 
3797 
3799 {
3800  if (matrix.type() < QTransform::TxTranslate)
3801  return *this;
3802 
3803  glyph_metrics_t m = *this;
3804 
3805  qreal w = width.toReal();
3806  qreal h = height.toReal();
3808 
3809  QRectF rect(0, 0, w, h);
3810  rect = xform.mapRect(rect);
3811  m.width = QFixed::fromReal(rect.width());
3812  m.height = QFixed::fromReal(rect.height());
3813 
3814  QLineF l = xform.map(QLineF(x.toReal(), y.toReal(), xoff.toReal(), yoff.toReal()));
3815 
3816  m.x = QFixed::fromReal(l.x1());
3817  m.y = QFixed::fromReal(l.y1());
3818 
3819  // The offset is relative to the baseline which is why we use dx/dy of the line
3820  m.xoff = QFixed::fromReal(l.dx());
3821  m.yoff = QFixed::fromReal(l.dy());
3822 
3823  return m;
3824 }
3825 
3827  const QTextLayout::FormatRange *_selection)
3828  : eng(_eng),
3829  line(eng->lines[_lineNum]),
3830  si(nullptr),
3831  lineNum(_lineNum),
3832  lineEnd(line.from + line.length),
3833  firstItem(eng->findItem(line.from)),
3834  lastItem(eng->findItem(lineEnd - 1, firstItem)),
3835  nItems((firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0),
3836  logicalItem(-1),
3837  item(-1),
3838  visualOrder(nItems),
3839  selection(_selection)
3840 {
3841  x = QFixed::fromReal(pos.x());
3842 
3843  x += line.x;
3844 
3845  x += eng->alignLine(line);
3846 
3847  if (nItems > 0) {
3849  for (int i = 0; i < nItems; ++i)
3852  }
3853 
3854  eng->shapeLine(line);
3855 }
3856 
3858 {
3859  x += itemWidth;
3860 
3861  ++logicalItem;
3863  itemLength = eng->length(item);
3864  si = &eng->layoutData->items[item];
3865  if (!si->num_glyphs)
3866  eng->shape(item);
3867 
3868  itemStart = qMax(line.from, si->position);
3869  itemEnd = qMin(lineEnd, si->position + itemLength);
3870 
3872  glyphsStart = 0;
3873  glyphsEnd = 1;
3874  itemWidth = si->width;
3875  return *si;
3876  }
3877 
3878  unsigned short *logClusters = eng->logClusters(si);
3879  QGlyphLayout glyphs = eng->shapedGlyphs(si);
3880 
3881  glyphsStart = logClusters[itemStart - si->position];
3882  glyphsEnd = (itemEnd == si->position + itemLength) ? si->num_glyphs : logClusters[itemEnd - si->position];
3883 
3884  // show soft-hyphen at line-break
3887  glyphs.attributes[glyphsEnd - 1].dontPrint = false;
3888 
3889  itemWidth = 0;
3890  for (int g = glyphsStart; g < glyphsEnd; ++g)
3891  itemWidth += glyphs.effectiveAdvance(g);
3892 
3893  return *si;
3894 }
3895 
3896 bool QTextLineItemIterator::getSelectionBounds(QFixed *selectionX, QFixed *selectionWidth) const
3897 {
3898  *selectionX = *selectionWidth = 0;
3899 
3900  if (!selection)
3901  return false;
3902 
3905  || si->position + itemLength <= selection->start)
3906  return false;
3907 
3908  *selectionX = x;
3909  *selectionWidth = itemWidth;
3910  } else {
3911  unsigned short *logClusters = eng->logClusters(si);
3912  QGlyphLayout glyphs = eng->shapedGlyphs(si);
3913 
3914  int from = qMax(itemStart, selection->start) - si->position;
3915  int to = qMin(itemEnd, selection->start + selection->length) - si->position;
3916  if (from >= to)
3917  return false;
3918 
3919  int start_glyph = logClusters[from];
3920  int end_glyph = (to == itemLength) ? si->num_glyphs : logClusters[to];
3921  QFixed soff;
3922  QFixed swidth;
3923  if (si->analysis.bidiLevel %2) {
3924  for (int g = glyphsEnd - 1; g >= end_glyph; --g)
3925  soff += glyphs.effectiveAdvance(g);
3926  for (int g = end_glyph - 1; g >= start_glyph; --g)
3927  swidth += glyphs.effectiveAdvance(g);
3928  } else {
3929  for (int g = glyphsStart; g < start_glyph; ++g)
3930  soff += glyphs.effectiveAdvance(g);
3931  for (int g = start_glyph; g < end_glyph; ++g)
3932  swidth += glyphs.effectiveAdvance(g);
3933  }
3934 
3935  // If the starting character is in the middle of a ligature,
3936  // selection should only contain the right part of that ligature
3937  // glyph, so we need to get the width of the left part here and
3938  // add it to *selectionX
3939  QFixed leftOffsetInLigature = eng->offsetInLigature(si, from, to, start_glyph);
3940  *selectionX = x + soff + leftOffsetInLigature;
3941  *selectionWidth = swidth - leftOffsetInLigature;
3942  // If the ending character is also part of a ligature, swidth does
3943  // not contain that part yet, we also need to find out the width of
3944  // that left part
3945  *selectionWidth += eng->offsetInLigature(si, to, itemLength, end_glyph);
3946  }
3947  return true;
3948 }
3949 
small capitals from c petite p scientific f u
Definition: afcover.h:88
small capitals from c petite p scientific i
[1]
Definition: afcover.h:80
xD9 x84 xD8 xAD xD9 x80 xF0 x90 xAC x9A xE0 xA7 xA6 xE0 xA7 xAA xF0 x91 x84 xA4 xF0 x91 x84 x89 xF0 x91 x84 x9B xF0 x90 x8A xAB xF0 x90 x8B x89 xE2 xB2 x9E xE2 xB2 x9F xD0 xBE xD0 x9E xF0 x90 x90 x84 xF0 x90 x90 xAC xE1 x83 x98 xE1 x83 x94 xE1 x83 x90 xE1 xB2 xBF xE2 xB0 x95 xE2 xB1 x85 xCE xBF xCE x9F xE0 xA8 xA0 xE0 xA8 xB0 xE0 xA9 xA6 Kayah xEA xA4 x8D xEA xA4 x80 Khmer xE1 xA7 xA1 xE1 xA7 xAA xE0 xBB x90 Latin Subscript xE2 x82 x92 xE2 x82 x80 xEA x93 xB3 xF0 x96 xB9 xA1 xF0 x96 xB9 x9B xF0 x96 xB9 xAF xE1 x80 x9D xE1 x80 x84 xE1 x80 x82 no script
Definition: afscript.h:271
Definition: lalr.h:110
virtual void resizeInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format)
bool ref() noexcept
Definition: qbasicatomic.h:101
bool deref() noexcept
Definition: qbasicatomic.h:102
The QChar class provides a 16-bit Unicode character.
Definition: qchar.h:84
constexpr bool isLetterOrNumber() const noexcept
Definition: qchar.h:503
static constexpr char32_t surrogateToUcs4(char16_t high, char16_t low) noexcept
Definition: qchar.h:539
@ ObjectReplacementCharacter
Definition: qchar.h:96
@ Nbsp
Definition: qchar.h:93
@ CarriageReturn
Definition: qchar.h:91
@ Tabulation
Definition: qchar.h:88
@ ParagraphSeparator
Definition: qchar.h:99
@ LineSeparator
Definition: qchar.h:100
@ Space
Definition: qchar.h:92
@ LineFeed
Definition: qchar.h:89
@ SoftHyphen
Definition: qchar.h:94
QChar toLower() const noexcept
Definition: qchar.h:479
Direction
Definition: qchar.h:377
@ DirFSI
Definition: qchar.h:380
@ DirNSM
Definition: qchar.h:379
@ DirPDF
Definition: qchar.h:379
@ DirON
Definition: qchar.h:378
@ DirLRO
Definition: qchar.h:379
@ DirWS
Definition: qchar.h:378
@ DirL
Definition: qchar.h:378
@ DirEN
Definition: qchar.h:378
@ DirCS
Definition: qchar.h:378
@ DirRLO
Definition: qchar.h:379
@ DirAN
Definition: qchar.h:378
@ DirAL
Definition: qchar.h:379
@ DirB
Definition: qchar.h:378
@ DirRLI
Definition: qchar.h:380
@ DirS
Definition: qchar.h:378
@ DirPDI
Definition: qchar.h:380
@ DirLRE
Definition: qchar.h:379
@ DirLRI
Definition: qchar.h:380
@ DirBN
Definition: qchar.h:379
@ DirRLE
Definition: qchar.h:379
@ DirET
Definition: qchar.h:378
@ DirR
Definition: qchar.h:378
@ DirES
Definition: qchar.h:378
@ Letter_Lowercase
Definition: qchar.h:160
@ Punctuation_Close
Definition: qchar.h:168
@ Mark_NonSpacing
Definition: qchar.h:141
@ Punctuation_Open
Definition: qchar.h:167
constexpr bool isLowSurrogate() const noexcept
Definition: qchar.h:511
constexpr char16_t unicode() const noexcept
Definition: qchar.h:489
bool isPrint() const noexcept
Definition: qchar.h:496
static constexpr char16_t lowSurrogate(char32_t ucs4) noexcept
Definition: qchar.h:553
JoiningType
Definition: qchar.h:405
@ Joining_Dual
Definition: qchar.h:408
@ Joining_Causing
Definition: qchar.h:407
@ Joining_None
Definition: qchar.h:406
@ Joining_Transparent
Definition: qchar.h:411
Category category() const noexcept
Definition: qchar.h:467
Script
Definition: qchar.h:180
@ Script_Arabic
Definition: qchar.h:190
@ Script_Bopomofo
Definition: qchar.h:218
@ Script_Han
Definition: qchar.h:219
@ Script_PhagsPa
Definition: qchar.h:253
@ Script_PsalterPahlavi
Definition: qchar.h:319
@ Script_Hiragana
Definition: qchar.h:216
@ Script_Mandaic
Definition: qchar.h:289
@ Script_Common
Definition: qchar.h:183
@ Script_Greek
Definition: qchar.h:186
@ Script_Mongolian
Definition: qchar.h:215
@ Script_Tibetan
Definition: qchar.h:205
@ Script_Manichaean
Definition: qchar.h:310
@ Script_Khmer
Definition: qchar.h:214
@ Script_Katakana
Definition: qchar.h:217
@ Script_Nko
Definition: qchar.h:254
@ Script_Syriac
Definition: qchar.h:191
@ Script_Latin
Definition: qchar.h:185
@ Script_Sinhala
Definition: qchar.h:202
constexpr bool isSpace() const noexcept
Definition: qchar.h:497
constexpr bool isHighSurrogate() const noexcept
Definition: qchar.h:510
operator<<(QDataStream &ds, qfloat16 f)
Definition: qfloat16.cpp:327
The QDebug class provides an output stream for debugging information.
Definition: qdebug.h:65
virtual bool supportsHorizontalSubPixelPositions() const
virtual QFixed descent() const
virtual QFixed ascent() const
static bool scriptRequiresOpenType(QChar::Script script)
Type type() const
virtual glyph_t glyphIndex(uint ucs4) const =0
virtual QFontEngine * cloneWithSize(qreal) const
QAtomicInt ref
virtual void recalcAdvances(QGlyphLayout *, ShaperFlags) const
virtual QFixed leading() const
virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, ShaperFlags flags) const =0
void ensureEngineAt(int at)
static QFontEngine * createMultiFontEngine(QFontEngine *fe, int script)
QFontEngine * engine(int at) const
The QFont class specifies a query for a font used for drawing text.
Definition: qfont.h:56
StyleStrategy styleStrategy() const
Definition: qfont.cpp:1414
void setPointSize(int)
Definition: qfont.cpp:1006
int pixelSize() const
Definition: qfont.cpp:1094
Capitalization
Definition: qfont.h:130
@ AllLowercase
Definition: qfont.h:133
@ Capitalize
Definition: qfont.h:135
@ MixedCase
Definition: qfont.h:131
@ SmallCaps
Definition: qfont.h:134
qreal letterSpacing() const
Definition: qfont.cpp:1633
QFont resolve(const QFont &) const
Definition: qfont.cpp:1874
Capitalization capitalization() const
Definition: qfont.cpp:1755
qreal wordSpacing() const
Definition: qfont.cpp:1683
int pointSize() const
Definition: qfont.cpp:899
bool kerning() const
Definition: qfont.cpp:1379
void setPixelSize(int)
Definition: qfont.cpp:1069
@ PreferNoShaping
Definition: qfont.h:83
bool letterSpacingIsAbsolute
Definition: qfont_p.h:199
QFontDef request
Definition: qfont_p.h:190
QFontPrivate * smallCapsFontPrivate() const
Definition: qfont.cpp:302
QFontEngine * engineForScript(int script) const
Definition: qfont.cpp:272
QFixed wordSpacing
Definition: qfont_p.h:202
uint kerning
Definition: qfont_p.h:197
QFixed letterSpacing
Definition: qfont_p.h:201
uint capital
Definition: qfont_p.h:198
QFont smallCapsFont() const
Definition: qfont_p.h:205
quint32 size_array[N]
virtual int type() const
GraphicsItemFlags flags() const
static QInputMethod * inputMethod()
Qt::LayoutDirection inputDirection
Current input direction.
Definition: qinputmethod.h:66
The QLineF class provides a two-dimensional vector using floating point precision.
Definition: qline.h:215
qsizetype size() const noexcept
Definition: qlist.h:414
const_pointer constData() const noexcept
Definition: qlist.h:444
bool isEmpty() const noexcept
Definition: qlist.h:418
iterator Iterator
Definition: qlist.h:279
iterator insert(qsizetype i, parameter_type t)
Definition: qlist.h:499
iterator end()
Definition: qlist.h:624
const_reference at(qsizetype i) const noexcept
Definition: qlist.h:457
qsizetype count() const noexcept
Definition: qlist.h:415
iterator begin()
Definition: qlist.h:623
void reserve(qsizetype size)
Definition: qlist.h:757
const_iterator cend() const noexcept
Definition: qlist.h:629
void append(parameter_type t)
Definition: qlist.h:469
const_iterator cbegin() const noexcept
Definition: qlist.h:628
void clear()
Definition: qlist.h:445
int logicalDpiY() const
Definition: qpaintdevice.h:82
The QPainter class performs low-level painting on widgets and other paint devices.
Definition: qpainter.h:82
const QPen & pen() const
Definition: qpainter.cpp:3736
void setPen(const QColor &color)
Definition: qpainter.cpp:3640
void drawLine(const QLineF &line)
Definition: qpainter.h:477
The QPen class defines how a QPainter should draw lines and outlines of shapes.
Definition: qpen.h:61
The QPointF class defines a point in the plane using floating point precision.
Definition: qpoint.h:242
constexpr int x() const noexcept
Definition: qpoint.h:155
qreal pixelSize() const
Definition: qrawfont.cpp:438
bool isValid() const
Definition: qrawfont.cpp:218
QFontEngine * fontEngine
Definition: qrawfont_p.h:142
The QRectF class defines a finite rectangle in the plane using floating point precision.
Definition: qrect.h:511
constexpr int width() const noexcept
Definition: qrect.h:263
QStackTextEngine(const QString &string, const QFont &f)
LayoutData _layoutData
The QString class provides a Unicode character string.
Definition: qstring.h:388
QString & prepend(QChar c)
Definition: qstring.h:656
qsizetype count() const
Definition: qstring.h:414
const QChar * constData() const
Definition: qstring.h:1234
qsizetype size() const
Definition: qstring.h:413
QString mid(qsizetype position, qsizetype n=-1) const
Definition: qstring.cpp:4994
void detach()
Definition: qstring.h:1236
const QChar at(qsizetype i) const
Definition: qstring.h:1212
bool isEmpty() const
Definition: qstring.h:1216
QString & insert(qsizetype i, QChar c)
Definition: qstring.cpp:3043
QChar * data()
Definition: qstring.h:1228
QString & append(QChar c)
Definition: qstring.cpp:3152
static QString static QString qsizetype indexOf(QChar c, qsizetype from=0, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition: qstring.cpp:4197
bool isRightToLeft() const
Definition: qstring.cpp:8780
qsizetype length() const
Definition: qstring.h:415
const QChar * unicode() const
Definition: qstring.h:1218
void resize(qsizetype size)
Definition: qstring.cpp:2670
The QStringView class provides a unified view on UTF-16 strings with a read-only subset of the QStrin...
Definition: qstringview.h:122
bool isRightToLeft() const noexcept
Definition: qstringview.h:385
constexpr QStringView mid(qsizetype pos, qsizetype n=-1) const noexcept
Definition: qstringview.h:261
int length() const
bool isValid() const
QTextBlock next() const
int position() const
QString text() const
QTextCharFormat charFormat() const
The QTextBoundaryFinder class provides a way of finding Unicode text boundaries in a string.
The QTextCharFormat class provides formatting information for characters in a QTextDocument....
Definition: qtextformat.h:416
UnderlineStyle underlineStyle() const
Definition: qtextformat.h:560
bool fontStrikeOut() const
Definition: qtextformat.h:517
QFont::Capitalization fontCapitalization() const
Definition: qtextformat.h:491
bool fontOverline() const
Definition: qtextformat.h:512
QFont font() const
FragmentMap::ConstIterator FragmentIterator
static const QTextDocumentPrivate * get(const QTextDocument *document)
QAbstractTextDocumentLayout * layout() const
QList< ItemDecoration > ItemDecorationList
glyph_metrics_t boundingBox(int from, int len) const
void setPreeditArea(int position, const QString &text)
void validate() const
int lineNumberForTextPosition(int pos)
ItemDecorationList strikeOutList
QString elidedText(Qt::TextElideMode mode, const QFixed &width, int flags=0, int from=0, int count=-1) const
void justify(const QScriptLine &si)
ItemDecorationList underlineList
QFixed maxWidth
QFixed minWidth
QAbstractTextDocumentLayout * docLayout() const
bool isRightToLeft() const
void adjustUnderlines()
void shapeLine(const QScriptLine &line)
QScriptLineArray lines
LayoutData * layoutData
QGlyphLayout shapedGlyphs(const QScriptItem *si) const
bool hasFormats() const
int positionInLigature(const QScriptItem *si, int end, QFixed x, QFixed edge, int glyph_pos, bool cursorOnCharacter)
int findItem(int strPos, int firstItem=0) const
void invalidate()
uint forceJustification
void shape(int item) const
void resetFontEngineCache()
const QCharAttributes * attributes() const
QTextBlock block
void addOverline(QPainter *painter, const QLineF &line)
QFixed leadingSpaceWidth(const QScriptLine &line)
QFixed alignLine(const QScriptLine &line)
uint delayDecorations
bool ensureSpace(int nGlyphs) const
int positionAfterVisualMovement(int oldPos, QTextCursor::MoveOperation op)
QRawFont rawFont
void freeMemory()
int formatIndex(const QScriptItem *si) const
QFontEngine * fontEngine(const QScriptItem &si, QFixed *ascent=nullptr, QFixed *descent=nullptr, QFixed *leading=nullptr) const
void drawDecorations(QPainter *painter)
QFixed calculateTabWidth(int index, QFixed x) const
returns the width of tab at index (in the tabs array) with the tab-start at position x
bool atWordSeparator(int position) const
void clearLineData()
void addStrikeOut(QPainter *painter, const QLineF &line)
void setFormats(const QList< QTextLayout::FormatRange > &formats)
void itemize() const
QGlyphLayout availableGlyphs(const QScriptItem *si) const
unsigned short * logClusters(const QScriptItem *si) const
void addUnderline(QPainter *painter, const QLineF &line)
QTextCharFormat format(const QScriptItem *si) const
QString text
QPointF position
std::vector< int > insertionPointsForLine(int lineNum)
int nextLogicalPosition(int oldPos) const
QFixed width(int charFrom, int numChars) const
static void bidiReorder(int numRuns, const quint8 *levels, int *visualOrder)
QFont font() const
glyph_metrics_t tightBoundingBox(int from, int len) const
int previousLogicalPosition(int oldPos) const
QTextFormatCollection * formatCollection() const
QFixed offsetInLigature(const QScriptItem *si, int pos, int max, int glyph_pos)
QList< QTextLayout::FormatRange > formats() const
void clearDecorations()
int length(int item) const
ItemDecorationList overlineList
int indexForFormat(const QTextFormat &f)
QFont defaultFont() const
QTextCharFormat charFormat(int index) const
Definition: qtextformat_p.h:84
@ FontCapitalization
Definition: qtextformat.h:174
@ TextUnderlineStyle
Definition: qtextformat.h:203
bool boolProperty(int propertyId) const
bool hasProperty(int propertyId) const
The QTextInlineObject class represents an inline object in a QAbstractTextDocumentLayout and its impl...
Definition: qtextlayout.h:70
Internal QTextItem.
void initWithScriptItem(const QScriptItem &si)
const QTextCharFormat charFormat
QTextItemInt midItem(QFontEngine *fontEngine, int firstGlyphIndex, int numGlyphs) const
const QChar * chars
QGlyphLayout glyphs
const unsigned short * logClusters
QTextCharFormat::UnderlineStyle underlineStyle
QTextItemInt()=default
QFontEngine * fontEngine
@ ShowDocumentTerminator
Definition: qtextoption.h:111
@ ShowLineAndParagraphSeparators
Definition: qtextoption.h:108
@ IncludeTrailingSpaces
Definition: qtextoption.h:112
The QTransform class specifies 2D transformations of a coordinate system.
Definition: qtransform.h:56
QPoint map(const QPoint &p) const
static QTransform fromTranslate(qreal dx, qreal dy)
Definition: qtransform.cpp:437
QRect mapRect(const QRect &) const
constexpr size_type size() const noexcept
bool isEmpty() const
void remove(qsizetype i, qsizetype n=1)
value_type value(qsizetype i) const
void resize(qsizetype sz)
iterator end() noexcept
void insert(qsizetype i, T &&t)
T * data() noexcept
const_iterator constEnd() const
const T & at(qsizetype idx) const
qsizetype length() const
void append(const T &t)
auto constBegin() const -> const_iterator
void reserve(qsizetype sz)
iterator begin() noexcept
switch(msgBox.exec())
const QLoggingCategory & category()
[1]
b clear()
QString str
[2]
QString text
[meta data]
double e
rect
[4]
direction
for(n=0;n< outline->n_points;n++)
Definition: ftbbox.c:494
constexpr T & operator()(T &v) const
Definition: hb-algs.hh:2
void hb_buffer_add_utf16(hb_buffer_t *buffer, const uint16_t *text, int text_length, unsigned int item_offset, int item_length)
Definition: hb-buffer.cc:1682
void hb_buffer_reverse(hb_buffer_t *buffer)
Definition: hb-buffer.cc:1495
hb_bool_t hb_buffer_pre_allocate(hb_buffer_t *buffer, unsigned int size)
Definition: hb-buffer.cc:1281
void hb_buffer_clear_contents(hb_buffer_t *buffer)
Definition: hb-buffer.cc:1260
void hb_buffer_set_segment_properties(hb_buffer_t *buffer, const hb_segment_properties_t *props)
Definition: hb-buffer.cc:1027
unsigned int hb_buffer_get_length(hb_buffer_t *buffer)
Definition: hb-buffer.cc:1384
hb_buffer_t * hb_buffer_create()
Definition: hb-buffer.cc:648
void hb_buffer_set_flags(hb_buffer_t *buffer, hb_buffer_flags_t flags)
Definition: hb-buffer.cc:1063
hb_glyph_position_t * hb_buffer_get_glyph_positions(hb_buffer_t *buffer, unsigned int *length)
Definition: hb-buffer.cc:1433
void hb_buffer_destroy(hb_buffer_t *buffer)
Definition: hb-buffer.cc:747
hb_bool_t hb_buffer_allocation_successful(hb_buffer_t *buffer)
Definition: hb-buffer.cc:1298
void hb_buffer_set_unicode_funcs(hb_buffer_t *buffer, hb_unicode_funcs_t *unicode_funcs)
Definition: hb-buffer.cc:854
hb_glyph_info_t * hb_buffer_get_glyph_infos(hb_buffer_t *buffer, unsigned int *length)
Definition: hb-buffer.cc:1404
hb_language_t hb_language_get_default()
Definition: hb-common.cc:430
hb_bool_t hb_shape_full(hb_font_t *font, hb_buffer_t *buffer, const hb_feature_t *features, unsigned int num_features, const char *const *shaper_list)
Definition: hb-shape.cc:123
auto generate(StringRef generatorName, SourceLineInfo const &lineInfo, L const &generatorExpression) -> decltype(std::declval< decltype(generatorExpression())>().get())
Definition: catch_p_p.h:4085
typename C::iterator iterator
QHighDpiScaling::Point position(T, QHighDpiScaling::Point::Kind)
const PluginKeyMapConstIterator cend
Q_DECL_CONST_FUNCTION Q_CORE_EXPORT const Properties *QT_FASTCALL properties(char32_t ucs4) noexcept
Q_CORE_EXPORT void initCharAttributes(QStringView string, const ScriptItem *items, qsizetype numItems, QCharAttributes *attributes, CharAttributeOptions options)
Q_CORE_EXPORT void initScripts(QStringView string, ScriptItemArray *scripts)
@ AlignRight
Definition: qnamespace.h:171
@ AlignJustify
Definition: qnamespace.h:174
@ AlignHCenter
Definition: qnamespace.h:173
@ AlignHorizontal_Mask
Definition: qnamespace.h:176
QTextStream & hex(QTextStream &stream)
@ LeftToRight
Definition: qnamespace.h:1463
@ RightToLeft
Definition: qnamespace.h:1464
@ TextShowMnemonic
Definition: qnamespace.h:198
QTextStream & dec(QTextStream &stream)
TextElideMode
Definition: qnamespace.h:213
@ ElideMiddle
Definition: qnamespace.h:216
@ ElideRight
Definition: qnamespace.h:215
@ ElideNone
Definition: qnamespace.h:217
@ ElideLeft
Definition: qnamespace.h:214
decltype(auto) cbegin(const T &t)
int distance(TestIterator &a, TestIterator &b)
#define QString()
Definition: parse-defines.h:51
void *PRIV() memmove(void *d, const void *s, size_t n)
set set set set set set set macro pixldst1 abits if abits op else op endif endm macro pixldst2 abits if abits op else op endif endm macro pixldst4 abits if abits op else op endif endm macro pixldst0 abits op endm macro pixldst3 mem_operand op endm macro pixldst30 mem_operand op endm macro pixldst abits if abits elseif abits elseif abits elseif abits elseif abits pixldst0 abits else pixldst0 abits pixldst0 abits pixldst0 abits pixldst0 abits endif elseif abits else pixldst0 abits pixldst0 abits endif elseif abits else error unsupported bpp *numpix else pixst endif endm macro vuzp8 reg2 vuzp d d &reg2 endm macro vzip8 reg2 vzip d d &reg2 endm macro pixdeinterleave basereg basereg basereg basereg basereg endif endm macro pixinterleave basereg basereg basereg basereg basereg endif endm macro PF boost_increment endif if endif PF tst PF addne PF subne PF cmp ORIG_W if endif if endif if endif PF subge ORIG_W PF subges if endif if endif if endif endif endm macro cache_preload_simple endif if dst_r_bpp pld[DST_R, #(PREFETCH_DISTANCE_SIMPLE *dst_r_bpp/8)] endif if mask_bpp pld if[MASK, #(PREFETCH_DISTANCE_SIMPLE *mask_bpp/8)] endif endif endm macro ensure_destination_ptr_alignment process_pixblock_tail_head if beq irp skip1(dst_w_bpp<=(lowbit *8)) &&((lowbit *8)<(pixblock_size *dst_w_bpp)) .if lowbit< 16 tst DST_R
[3]
set set set set set set set macro pixldst1 op
#define Q_FALLTHROUGH()
#define Q_UNLIKELY(x)
#define QT_WARNING_POP
#define Q_LIKELY(x)
#define Q_UNREACHABLE()
#define QT_WARNING_DISABLE_GCC(text)
#define QT_WARNING_PUSH
constexpr bool operator!=(const timespec &t1, const timespec &t2)
Definition: qcore_unix_p.h:124
constexpr timespec operator*(const timespec &t1, int mul)
Definition: qcore_unix_p.h:146
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter * iter
#define QFIXED_MAX
Definition: qfixed_p.h:159
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition: qfloat16.h:233
Q_GUI_EXPORT int qt_defaultDpiY()
Definition: qfont.cpp:156
unsigned int quint32
Definition: qglobal.h:288
#define QT_BEGIN_INCLUDE_NAMESPACE
Definition: qglobal.h:265
QT_END_INCLUDE_NAMESPACE typedef double qreal
Definition: qglobal.h:341
#define QT_END_INCLUDE_NAMESPACE
Definition: qglobal.h:266
unsigned int uint
Definition: qglobal.h:334
unsigned short ushort
Definition: qglobal.h:333
unsigned char quint8
Definition: qglobal.h:284
hb_unicode_funcs_t * hb_qt_get_unicode_funcs()
hb_font_t * hb_qt_font_get_for_engine(QFontEngine *fe)
hb_script_t hb_qt_script_to_script(QChar::Script script)
void hb_qt_font_set_use_design_metrics(hb_font_t *font, uint value)
@ text
#define qWarning
Definition: qlogging.h:179
GLenum GLuint GLenum GLsizei length
Definition: qopengl.h:270
GLenum type
Definition: qopengl.h:270
GLboolean GLboolean GLboolean b
GLint GLint GLint GLint GLint x
[0]
GLint GLenum GLsizei GLsizei GLsizei depth
GLenum mode
const GLfloat * m
GLenum GLuint GLint level
GLboolean r
[2]
GLfloat GLfloat GLfloat w
[0]
GLint GLsizei GLsizei height
GLboolean GLboolean GLboolean GLboolean a
[7]
GLuint index
[2]
GLuint GLuint end
GLdouble GLdouble GLdouble GLdouble top
GLenum GLenum GLsizei count
GLdouble GLdouble right
GLenum const void GLbitfield GLsizei numGlyphs
GLfloat GLfloat f
GLsizei levels
GLsizei range
GLenum GLuint buffer
GLint GLsizei width
GLint left
GLbitfield flags
GLenum GLuint GLsizei const GLenum * props
GLuint start
GLenum GLuint GLintptr offset
GLboolean GLboolean g
GLint first
GLfloat n
GLint GLsizei GLsizei GLenum format
GLsizei GLenum GLsizei GLsizei GLuint memory
GLenum const void GLbitfield GLuint firstGlyphIndex
GLint y
GLfloat GLfloat GLfloat GLfloat h
GLuint counter
GLboolean reset
Definition: qopenglext.h:2748
const GLubyte * c
Definition: qopenglext.h:12701
GLenum GLsizei len
Definition: qopenglext.h:3292
GLuint GLenum matrix
Definition: qopenglext.h:11564
GLdouble GLdouble t
[9]
Definition: qopenglext.h:243
GLuint GLuint64EXT address
Definition: qopenglext.h:11428
GLdouble s
[6]
Definition: qopenglext.h:235
GLfloat GLfloat p
[1]
Definition: qopenglext.h:12698
GLuint GLenum option
Definition: qopenglext.h:5929
GLsizei const GLchar *const * string
[0]
Definition: qopenglext.h:694
#define Q_ASSERT(cond)
Definition: qrandom.cpp:84
#define QStringLiteral(str)
#define gp
QTransform qt_true_matrix(qreal w, qreal h, const QTransform &x)
Q_DECLARE_TYPEINFO(QJustificationPoint, Q_PRIMITIVE_TYPE)
JustificationClass
@ Justification_Arabic_Waw
@ Justification_Arabic_Kashida
@ Justification_Arabic_Alef
@ Justification_Arabic_HahDal
@ Justification_Character
@ Justification_Arabic_Space
@ Justification_Space
@ Justification_Arabic_Seen
@ Justification_Arabic_BaRa
@ Justification_Arabic_Normal
@ Justification_Prohibited
#define BIDI_DEBUG
unsigned int glyph_t
@ Q_PRIMITIVE_TYPE
Definition: qtypeinfo.h:155
#define HB_SEGMENT_PROPERTIES_DEFAULT
Definition: hb-buffer.h:215
hb_buffer_flags_t
Definition: hb-buffer.h:381
@ HB_BUFFER_FLAG_PRESERVE_DEFAULT_IGNORABLES
Definition: hb-buffer.h:385
@ HB_BUFFER_FLAG_DEFAULT
Definition: hb-buffer.h:382
#define HB_TAG(c1, c2, c3, c4)
Definition: hb-common.h:169
#define HB_FEATURE_GLOBAL_END
Definition: hb-common.h:794
#define HB_DIRECTION_IS_BACKWARD(dir)
Definition: hb-common.h:288
@ HB_DIRECTION_RTL
Definition: hb-common.h:232
@ HB_DIRECTION_LTR
Definition: hb-common.h:231
#define HB_FEATURE_GLOBAL_START
Definition: hb-common.h:784
QFuture< QSet< QChar > > set
[10]
std::uniform_real_distribution dist(1, 2.5)
[2]
QObject::connect nullptr
QVBoxLayout * layout
QString dir
[11]
QGraphicsItem * item
QTransform xform
[18]
QItemSelection * selection
[0]
QList< QTreeWidgetItem * > items
QPainter painter(this)
[7]
stack push(command1)
QAction * at
QStringList::Iterator it
QStringList list
[0]
constexpr QFixed floor() const
Definition: qfixed_p.h:82
constexpr int value() const
Definition: qfixed_p.h:74
constexpr static QFixed fromReal(qreal r)
Definition: qfixed_p.h:71
constexpr int toInt() const
Definition: qfixed_p.h:77
constexpr qreal toReal() const
Definition: qfixed_p.h:78
constexpr static QFixed fromFixed(int fixed)
Definition: qfixed_p.h:72
uint styleStrategy
Definition: qfont_p.h:99
QGlyphJustification * justifications
void grow(char *address, int totalGlyphs)
QFixed effectiveAdvance(int item) const
void clear(int first=0, int last=-1)
QGlyphAttributes * attributes
glyph_t * glyphs
QGlyphLayout mid(int position, int n=-1) const
QFixed * advances
The QLatin1Char class provides an 8-bit ASCII/Latin-1 character.
Definition: qchar.h:53
unsigned short flags
@ BidiMaybeResetToParagraphLevel
unsigned short bidiLevel
QChar::Direction bidiDirection
unsigned short script
int glyph_data_offset
QScriptAnalysis analysis
unsigned short num_glyphs
QFixed descent
QFixed leading
QFixed leading
void setDefaultHeight(QTextEngine *eng)
QFixed descent
bool reallocate(int totalGlyphs)
unsigned short * logClustersPtr
QScriptItemArray items
The QTextLayout::FormatRange structure is used to apply extra formatting information for a specified ...
Definition: qtextlayout.h:132
bool getSelectionBounds(QFixed *selectionX, QFixed *selectionWidth) const
QTextLineItemIterator(QTextEngine *eng, int lineNum, const QPointF &pos=QPointF(), const QTextLayout::FormatRange *_selection=nullptr)
const QTextLayout::FormatRange * selection
const QScriptLine & line
QVarLengthArray< int > visualOrder
QScriptItem & next()
glyph_metrics_t transformed(const QTransform &xform) const
hb_position_t y_offset
Definition: hb-buffer.h:185
hb_position_t x_advance
Definition: hb-buffer.h:182
hb_position_t x_offset
Definition: hb-buffer.h:184
QPair< int, int > Position
void add(int &result, const int &sum)