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

Reply via email to