commit b9e45e4196f63c1b079aeb29422ce264bc4a01db Author: Jean-Marc Lasgouttes <lasgout...@lyx.org> Date: Fri Sep 1 15:42:00 2023 +0200
Improve computation of text vertical dimension Some fonts possess glyphs which ascents/descents are larger that the font global ascent/descent. This is known to happen on macOS. This patch introduces code to make these individual ascents/descents available: 1/ FontMetrics::breakString now provides a full dimension for each line of text that it produces 2/ The new FontMetrics::dimension provides the dimension of a given string 3/ FontMetrics::rectText is modified to rely on this new method. The new code is used when constructing rows to replace some of the uses of FontMetrics::maxAscent/Descent by proper values. This holds in particular for calculation of row height. Possible fix to bug #12879. --- src/Row.cpp | 8 ++++---- src/TextMetrics.cpp | 12 ++++++----- src/frontends/FontMetrics.h | 10 ++++++--- src/frontends/qt/GuiFontMetrics.cpp | 41 ++++++++++++++++++++++++++++++------- src/frontends/qt/GuiFontMetrics.h | 3 +++ 5 files changed, 55 insertions(+), 19 deletions(-) diff --git a/src/Row.cpp b/src/Row.cpp index 563868cf36..474b134c9b 100644 --- a/src/Row.cpp +++ b/src/Row.cpp @@ -142,7 +142,7 @@ bool Row::Element::splitAt(int const width, int next_width, SplitType split_type if (!(row_flags & CanBreakInside)) { // has width been computed yet? if (dim.wid == 0) - dim.wid = fm.width(str); + dim = fm.dimension(str); return false; } @@ -156,7 +156,7 @@ bool Row::Element::splitAt(int const width, int next_width, SplitType split_type if ((split_type == FIT && breaks.front().nspc_wid > width) || (breaks.size() > 1 && breaks.front().len == 0)) { if (dim.wid == 0) - dim.wid = fm.width(str); + dim = fm.dimension(str); return false; } @@ -170,7 +170,7 @@ bool Row::Element::splitAt(int const width, int next_width, SplitType split_type for (FontMetrics::Break const & brk : breaks) { Element e(type, curpos, font, change); e.str = str.substr(i, brk.len); - e.dim.wid = brk.wid; + e.dim = brk.dim; e.nspc_wid = brk.nspc_wid; e.row_flags = CanBreakInside | BreakAfter; if (type == PREEDIT) { @@ -554,7 +554,7 @@ void Row::addVirtual(pos_type const pos, docstring const & s, finalizeLast(); Element e(VIRTUAL, pos, f, ch); e.str = s; - e.dim.wid = theFontMetrics(f).width(s); + e.dim = theFontMetrics(f).dimension(s); e.endpos = pos; // Copy after* flags from previous elements, forbid break before element int const prev_row_flags = elements_.empty() ? Inline : elements_.back().row_flags; diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp index ce2af92a12..46e2b9c9df 100644 --- a/src/TextMetrics.cpp +++ b/src/TextMetrics.cpp @@ -1415,10 +1415,11 @@ void TextMetrics::setRowHeight(Row & row) const if (row.pos() == 0 && layout.labelIsInline()) { FontInfo const labelfont = text_->labelFont(par); FontMetrics const & lfm = theFontMetrics(labelfont); - maxasc = max(maxasc, int(lfm.maxAscent() + Dimension const ldim = lfm.dimension(par.labelString()); + maxasc = max(maxasc, int(ldim.ascent() // add leading space + lfm.maxHeight() * (spacing_val - 1))); - maxdes = max(maxdes, int(lfm.maxDescent())); + maxdes = max(maxdes, int(ldim.descent())); } // Find the ascent/descent of the row contents @@ -1428,10 +1429,11 @@ void TextMetrics::setRowHeight(Row & row) const maxdes = max(maxdes, e.dim.descent()); } else { FontMetrics const & fm2 = theFontMetrics(e.font); - maxasc = max(maxasc, int(fm2.maxAscent() + maxasc = max(maxasc, e.dim.ascent() + // FIXME: is this right (or shall we use a real height) ?? // add leading space - + fm2.maxHeight() * (spacing_val - 1))); - maxdes = max(maxdes, int(fm2.maxDescent())); + + int(fm2.maxHeight() * (spacing_val - 1))); + maxdes = max(maxdes, e.dim.descent()); } } diff --git a/src/frontends/FontMetrics.h b/src/frontends/FontMetrics.h index 28e3dec4dd..e8700931ba 100644 --- a/src/frontends/FontMetrics.h +++ b/src/frontends/FontMetrics.h @@ -14,6 +14,7 @@ #ifndef FONT_METRICS_H #define FONT_METRICS_H +#include "Dimension.h" #include "support/mute_warning.h" #include "support/docstring.h" @@ -128,11 +129,11 @@ public: // The places where to break a string and the width of the resulting lines. struct Break { - Break(int l, int w, int nsw) : len(l), wid(w), nspc_wid(nsw) {} + Break(int l, Dimension const & d, int nsw) : len(l), dim(d), nspc_wid(nsw) {} // Number of characters int len = 0; - // text width - int wid = 0; + // text dimension + Dimension dim; // text width when trailing spaces are removed; only makes a // difference for the last break. int nspc_wid = 0; @@ -151,6 +152,9 @@ public: virtual Breaks breakString(docstring const & s, int first_wid, int wid, bool rtl, bool force) const = 0; + // return string dimension for the font. + virtual Dimension const dimension(docstring const & str) const = 0; + /// return char dimension for the font. virtual Dimension const dimension(char_type c) const = 0; /** diff --git a/src/frontends/qt/GuiFontMetrics.cpp b/src/frontends/qt/GuiFontMetrics.cpp index f0b100101e..3766a6379f 100644 --- a/src/frontends/qt/GuiFontMetrics.cpp +++ b/src/frontends/qt/GuiFontMetrics.cpp @@ -75,6 +75,8 @@ namespace { // Limit strwidth_cache_ total cost to 1MB of string data. int const strwidth_cache_max_cost = 1024 * 1024; +// Limit strdim_cache_ total cost to 1MB of string data. +int const strdim_cache_max_cost = 1024 * 1024; // Limit breakstr_cache_ total cost to 10MB of string data. // This is useful for documents with very large insets. int const breakstr_cache_max_cost = 10 * 1024 * 1024; @@ -114,6 +116,7 @@ inline QChar const ucs4_to_qchar(char_type const ucs4) GuiFontMetrics::GuiFontMetrics(QFont const & font) : font_(font), metrics_(font, 0), xheight_(-metrics_.boundingRect('x').top()), strwidth_cache_(strwidth_cache_max_cost), + strdim_cache_(strdim_cache_max_cost), breakstr_cache_(breakstr_cache_max_cost), qtextlayout_cache_(qtextlayout_cache_max_size) { @@ -566,15 +569,18 @@ GuiFontMetrics::breakString_helper(docstring const & s, int first_wid, int wid, QTextLine const & line = tl.lineAt(i); int const line_epos = line.textStart() + line.textLength(); int const epos = tlh.qpos2pos(line_epos); + Dimension dim; // This does not take trailing spaces into account, except for the last line. - int const wid = iround(line.naturalTextWidth()); + dim.wid = iround(line.naturalTextWidth()); + dim.asc = iround(line.ascent()); + dim.des = iround(line.descent()); // If the line is not the last one, trailing space is always omitted. - int nspc_wid = wid; + int nspc_wid = dim.wid; // For the last line, compute the width without trailing space if (i + 1 == tl.lineCount() && !s.empty() && isSpace(s.back()) && line.textStart() <= tlh.pos2qpos(s.size() - 1)) nspc_wid = iround(line.cursorToX(tlh.pos2qpos(s.size() - 1))); - breaks.emplace_back(epos - pos, wid, nspc_wid); + breaks.emplace_back(epos - pos, dim, nspc_wid); pos = epos; } @@ -615,10 +621,10 @@ void GuiFontMetrics::rectText(docstring const & str, { // FIXME: let offset depend on font (this is Inset::TEXT_TO_OFFSET) int const offset = 4; - - w = width(str) + offset; - ascent = metrics_.ascent() + offset / 2; - descent = metrics_.descent() + offset / 2; + Dimension const dim = dimension(str); + w = dim.wid + offset; + ascent = dim.asc + offset / 2; + descent = dim.des + offset / 2; } @@ -642,6 +648,27 @@ Dimension const GuiFontMetrics::dimension(char_type c) const } +Dimension const GuiFontMetrics::dimension(docstring const & s) const +{ + PROFILE_THIS_BLOCK(dimension); + if (Dimension * dim_p = strdim_cache_.object_ptr(s)) + return *dim_p; + PROFILE_CACHE_MISS(dimension); + QTextLayout tl; + tl.setText(toqstr(s)); + tl.setFont(font_); + tl.beginLayout(); + QTextLine line = tl.createLine(); + tl.endLayout(); + Dimension dim; + dim.wid = iround(line.naturalTextWidth()); + dim.asc = iround(line.ascent()); + dim.des = iround(line.descent()); + strdim_cache_.insert(s, dim, s.size() * sizeof(char_type)); + return dim; +} + + GuiFontMetrics::AscendDescend const GuiFontMetrics::fillMetricsCache( char_type c) const { diff --git a/src/frontends/qt/GuiFontMetrics.h b/src/frontends/qt/GuiFontMetrics.h index 5f73172c0d..24a1c801d0 100644 --- a/src/frontends/qt/GuiFontMetrics.h +++ b/src/frontends/qt/GuiFontMetrics.h @@ -83,6 +83,7 @@ public: int x2pos(docstring const & s, int & x, bool rtl, double ws) const override; Breaks breakString(docstring const & s, int first_wid, int wid, bool rtl, bool force) const override; Dimension const dimension(char_type c) const override; + Dimension const dimension(docstring const & s) const override; void rectText(docstring const & str, int & width, @@ -126,6 +127,8 @@ private: mutable QHash<char_type, int> width_cache_; /// Cache of string widths mutable Cache<docstring, int> strwidth_cache_; + /// Cache of string dimension + mutable Cache<docstring, Dimension> strdim_cache_; /// Cache for breakString mutable Cache<BreakStringKey, Breaks> breakstr_cache_; /// Cache for QTextLayout -- lyx-cvs mailing list lyx-cvs@lists.lyx.org https://lists.lyx.org/mailman/listinfo/lyx-cvs