+#endif
+}
+
+
+int GuiFontMetrics::countExpanders(docstring const & str) const
+{
+ // Numbers of characters that are expanded by inter-word spacing. These
+ // characters are spaces, except for characters 09-0D which are treated
+ // specially. (From a combination of testing with the notepad found in qt's
+ // examples, and reading the source code.) In addition, consecutive spaces
+ // only count as one expander.
+ bool wasspace = false;
+ int nexp = 0;
+ for (char_type c : str)
+ if (c > 0x0d && QChar(c).isSpace()) {
+ if (!wasspace) {
+ ++nexp;
+ wasspace = true;
+ }
+ } else
+ wasspace = false;
+ return nexp;
+}
+
+
+pair<int, int>
+GuiFontMetrics::breakAt_helper(docstring const & s, int const x,
+ bool const rtl, bool const force) const
+{
+ QTextLayout tl;
+ /* Qt will not break at a leading or trailing space, and we need
+ * that sometimes, see http://www.lyx.org/trac/ticket/9921.
+ *
+ * To work around the problem, we enclose the string between
+ * zero-width characters so that the QTextLayout algorithm will
+ * agree to break the text at these extremal spaces.
+ */
+ // Unicode character ZERO WIDTH NO-BREAK SPACE
+ QChar const zerow_nbsp(0xfeff);
+ QString qs = zerow_nbsp + toqstr(s) + zerow_nbsp;
+#if 1
+ /* Use unicode override characters to enforce drawing direction
+ * Source: http://www.iamcal.com/understanding-bidirectional-text/
+ */
+ if (rtl)
+ // Right-to-left override: forces to draw text right-to-left
+ qs = QChar(0x202E) + qs;
+ else
+ // Left-to-right override: forces to draw text left-to-right
+ qs = QChar(0x202D) + qs;
+ int const offset = 2;
+#else
+ // Alternative version that breaks with Qt5 and arabic text (#10436)
+ // Note that both setFlags and the enums are undocumented
+ tl.setFlags(rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight);
+ int const offset = 1;
+#endif
+
+ tl.setText(qs);
+ tl.setFont(font_);
+ QTextOption to;
+ to.setWrapMode(force ? QTextOption::WrapAtWordBoundaryOrAnywhere
+ : QTextOption::WordWrap);
+ tl.setTextOption(to);
+ tl.beginLayout();
+ QTextLine line = tl.createLine();
+ line.setLineWidth(x);
+ tl.createLine();
+ tl.endLayout();
+ if ((force && line.textLength() == offset) || int(line.naturalTextWidth()) > x)
+ return {-1, -1};
+ /* Since QString is UTF-16 and docstring is UCS-4, the offsets may
+ * not be the same when there are high-plan unicode characters
+ * (bug #10443).
+ */
+ // The variable `offset' is here to account for the extra leading characters.
+ // The ending character zerow_nbsp has to be ignored if the line is complete.
+ int const qlen = line.textLength() - offset - (line.textLength() == qs.length());
+#if QT_VERSION < 0x040801 || QT_VERSION >= 0x050100
+ int len = qstring_to_ucs4(qs.mid(offset, qlen)).length();
+#else
+ /* Due to QTBUG-25536 in 4.8.1 <= Qt < 5.1.0, the string returned
+ * by QString::toUcs4 (used by qstring_to_ucs4) may have wrong
+ * length. We work around the problem by trying all docstring
+ * positions until the right one is found. This is slow only if
+ * there are many high-plane Unicode characters. It might be
+ * worthwhile to implement a dichotomy search if this shows up
+ * under a profiler.
+ */
+ int len = min(qlen, static_cast<int>(s.length()));
+ while (len >= 0 && toqstr(s.substr(0, len)).length() != qlen)
+ --len;
+ LASSERT(len > 0 || qlen == 0, /**/);
+#endif
+ // The -1 is here to account for the leading zerow_nbsp.
+ return {len, int(line.naturalTextWidth())};
+}
+
+
+bool GuiFontMetrics::breakAt(docstring & s, int & x, bool const rtl, bool const force) const
+{
+ PROFILE_THIS_BLOCK(breakAt);
+ if (s.empty())
+ return false;
+
+ docstring const s_cache =
+ s + convert<docstring>(x) + (rtl ? "r" : "l") + (force ? "f" : "w");
+ pair<int, int> pp;
+
+ if (breakat_cache_.contains(s_cache))
+ pp = breakat_cache_[s_cache];
+ else {
+ PROFILE_CACHE_MISS(breakAt);
+ pp = breakAt_helper(s, x, rtl, force);
+ breakat_cache_.insert(s_cache, pp, s_cache.size() * sizeof(char_type));
+ }
+ if (pp.first == -1)
+ return false;
+ s = s.substr(0, pp.first);
+ x = pp.second;
+ return true;