From 29a6523dd8d3fac9be198c73153691c6dcdb3b21 Mon Sep 17 00:00:00 2001
From: Jiang Jiang <jiang.jiang@nokia.com>
Date: Tue, 10 Aug 2010 15:58:25 +0200
Subject: Make selection work across ligatures

For widgets like QPlainTextEdit, selection across ligatures (typically
'fi', 'ffi', 'fl', etc.) end up highlighting the entire ligature
glyphs, this patch fixed that by dividing width inside the ligature so
that selection will not expand past the actual selected characters.
Since cursor position already considered this, we merely adopted the
algorithm and made it a separated helper function for all necessary
cases. Dividing width directly looks like a temporary workaround but
works well enough so far for cursor positions.

Task-number: QTBUG-11969
Reviewed-by: Eskil
(cherry picked from commit 99fd5825dfb4d50cff93165995701a65b7a8e4ed)
---
 src/gui/text/qtextlayout.cpp | 64 +++++++++++++++++++++++++++++---------------
 1 file changed, 43 insertions(+), 21 deletions(-)

diff --git a/src/gui/text/qtextlayout.cpp b/src/gui/text/qtextlayout.cpp
index c34a04b..7328ea9 100644
--- a/src/gui/text/qtextlayout.cpp
+++ b/src/gui/text/qtextlayout.cpp
@@ -1011,6 +1011,35 @@ QScriptItem &QTextLineItemIterator::next()
     return *si;
 }
 
+static QFixed offsetInLigature(const unsigned short *logClusters,
+                               const QGlyphLayout &glyphs,
+                               int pos, int max, int glyph_pos)
+{
+    int offsetInCluster = 0;
+    for (int i = pos - 1; i >= 0; i--) {
+        if (logClusters[i] == glyph_pos)
+            offsetInCluster++;
+        else
+            break;
+    }
+
+    // in the case that the offset is inside a (multi-character) glyph,
+    // interpolate the position.
+    if (offsetInCluster > 0) {
+        int clusterLength = 0;
+        for (int i = pos - offsetInCluster; i < max; i++) {
+            if (logClusters[i] == glyph_pos)
+                clusterLength++;
+            else
+                break;
+        }
+        if (clusterLength)
+            return glyphs.advances_x[glyph_pos] * offsetInCluster / clusterLength;
+    }
+
+    return 0;
+}
+
 bool QTextLineItemIterator::getSelectionBounds(QFixed *selectionX, QFixed *selectionWidth) const
 {
     *selectionX = *selectionWidth = 0;
@@ -1050,8 +1079,19 @@ bool QTextLineItemIterator::getSelectionBounds(QFixed *selectionX, QFixed *selec
                 swidth += glyphs.effectiveAdvance(g);
         }
 
-        *selectionX = x + soff;
-        *selectionWidth = swidth;
+        // If the starting character is in the middle of a ligature,
+        // selection should only contain the right part of that ligature
+        // glyph, so we need to get the width of the left part here and
+        // add it to *selectionX
+        QFixed leftOffsetInLigature = offsetInLigature(logClusters, glyphs, from,
+                                                       to, start_glyph);
+        *selectionX = x + soff + leftOffsetInLigature;
+        *selectionWidth = swidth - leftOffsetInLigature;
+        // If the ending character is also part of a ligature, swidth does
+        // not contain that part yet, we also need to find out the width of
+        // that left part
+        *selectionWidth += offsetInLigature(logClusters, glyphs, to,
+                                            eng->length(item), end_glyph);
     }
     return true;
 }
@@ -2465,14 +2505,6 @@ qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
         if(pos == l)
             x += si->width;
     } else {
-        int offsetInCluster = 0;
-        for (int i=pos-1; i >= 0; i--) {
-            if (logClusters[i] == glyph_pos)
-                offsetInCluster++;
-            else
-                break;
-        }
-
         if (reverse) {
             int end = qMin(lineEnd, si->position + l) - si->position;
             int glyph_end = end == l ? si->num_glyphs : logClusters[end];
@@ -2484,17 +2516,7 @@ qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
             for (int i = glyph_start; i < glyph_pos; i++)
                 x += glyphs.effectiveAdvance(i);
         }
-        if (offsetInCluster > 0) { // in the case that the offset is inside a (multi-character) glyph, interpolate the position.
-            int clusterLength = 0;
-            for (int i=pos - offsetInCluster; i < line.length; i++) {
-                if (logClusters[i] == glyph_pos)
-                    clusterLength++;
-                else
-                    break;
-            }
-            if (clusterLength)
-                x+= glyphs.advances_x[glyph_pos] * offsetInCluster / clusterLength;
-        }
+        x += offsetInLigature(logClusters, glyphs, pos, line.length, glyph_pos);
     }
 
     *cursorPos = pos + si->position;
-- 
cgit v0.12


From d80949eee06ff464d58bd97a6c89bae7e961f3c8 Mon Sep 17 00:00:00 2001
From: Jiang Jiang <jiang.jiang@nokia.com>
Date: Thu, 19 May 2011 12:40:03 +0200
Subject: Fix ligature offset in multi-line text

Reviewed-by: Eskil
---
 src/gui/text/qtextlayout.cpp               |  4 ++--
 tests/auto/qtextlayout/tst_qtextlayout.cpp | 17 +++++++++++++++++
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/src/gui/text/qtextlayout.cpp b/src/gui/text/qtextlayout.cpp
index 7328ea9..de4ca4f 100644
--- a/src/gui/text/qtextlayout.cpp
+++ b/src/gui/text/qtextlayout.cpp
@@ -2505,8 +2505,8 @@ qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
         if(pos == l)
             x += si->width;
     } else {
+        int end = qMin(lineEnd, si->position + l) - si->position;
         if (reverse) {
-            int end = qMin(lineEnd, si->position + l) - si->position;
             int glyph_end = end == l ? si->num_glyphs : logClusters[end];
             for (int i = glyph_end - 1; i >= glyph_pos; i--)
                 x += glyphs.effectiveAdvance(i);
@@ -2516,7 +2516,7 @@ qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
             for (int i = glyph_start; i < glyph_pos; i++)
                 x += glyphs.effectiveAdvance(i);
         }
-        x += offsetInLigature(logClusters, glyphs, pos, line.length, glyph_pos);
+        x += offsetInLigature(logClusters, glyphs, pos, end, glyph_pos);
     }
 
     *cursorPos = pos + si->position;
diff --git a/tests/auto/qtextlayout/tst_qtextlayout.cpp b/tests/auto/qtextlayout/tst_qtextlayout.cpp
index ad33b70..964679a 100644
--- a/tests/auto/qtextlayout/tst_qtextlayout.cpp
+++ b/tests/auto/qtextlayout/tst_qtextlayout.cpp
@@ -126,6 +126,7 @@ private slots:
     void textWidthWithStackedTextEngine();
     void textWidthWithLineSeparator();
     void textWithSurrogates_qtbug15679();
+    void cursorInLigatureWithMultipleLines();
 
 private:
     QFont testFont;
@@ -1436,5 +1437,21 @@ void tst_QTextLayout::textWithSurrogates_qtbug15679()
     QCOMPARE(x[2] - x[0], x[5] - x[3]);
 }
 
+void tst_QTextLayout::cursorInLigatureWithMultipleLines()
+{
+#if !defined(Q_WS_MAC)
+    QSKIP("This test can only be run on Mac", SkipAll);
+#endif
+    QTextLayout layout("first line finish", QFont("Times", 20));
+    layout.beginLayout();
+    QTextLine line = layout.createLine();
+    line.setLineWidth(70);
+    line = layout.createLine();
+    layout.endLayout();
+
+    // The second line will be "finish", with "fi" as a ligature
+    QVERIFY(line.cursorToX(0) != line.cursorToX(1));
+}
+
 QTEST_MAIN(tst_QTextLayout)
 #include "tst_qtextlayout.moc"
-- 
cgit v0.12