Support placing cursor in ligature with mouse or touch
We need to find out the closest element in the ligature to the point we clicked (or tapped), currently we do this by dividing the width of that ligature glyph evenly by the number of characters it covered. We only support Common and Greek script at this point, ligatures in other scripts are still handled as a whole. Task-number: QTBUG-19260 Reviewed-by: Eskil (cherry picked from commit 5338d78aa9d80ddd2bcb21e6b22cd2cf1522a7d3) Change-Id: Ic747e9458d561aca0848dcd1e8b94e0a23fd8189 Reviewed-on: http://codereview.qt.nokia.com/196 Reviewed-by: Jiang Jiang <jiang.jiang@nokia.com>
This commit is contained in:
parent
a4ecb10eff
commit
9f837af945
@ -2825,6 +2825,75 @@ QFixed QTextEngine::offsetInLigature(const QScriptItem *si, int pos, int max, in
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Scan in logClusters[from..to-1] for glyph_pos
|
||||
int QTextEngine::getClusterLength(unsigned short *logClusters,
|
||||
const HB_CharAttributes *attributes,
|
||||
int from, int to, int glyph_pos, int *start)
|
||||
{
|
||||
int clusterLength = 0;
|
||||
for (int i = from; i < to; i++) {
|
||||
if (logClusters[i] == glyph_pos && attributes[i].charStop) {
|
||||
if (*start < 0)
|
||||
*start = i;
|
||||
clusterLength++;
|
||||
}
|
||||
else if (clusterLength)
|
||||
break;
|
||||
}
|
||||
return clusterLength;
|
||||
}
|
||||
|
||||
int QTextEngine::positionInLigature(const QScriptItem *si, int end,
|
||||
QFixed x, QFixed edge, int glyph_pos,
|
||||
bool cursorOnCharacter)
|
||||
{
|
||||
unsigned short *logClusters = this->logClusters(si);
|
||||
int clusterStart = -1;
|
||||
int clusterLength = 0;
|
||||
|
||||
if (si->analysis.script != QUnicodeTables::Common &&
|
||||
si->analysis.script != QUnicodeTables::Greek) {
|
||||
if (glyph_pos == -1)
|
||||
return si->position + end;
|
||||
else {
|
||||
int i;
|
||||
for (i = 0; i < end; i++)
|
||||
if (logClusters[i] == glyph_pos)
|
||||
break;
|
||||
return si->position + i;
|
||||
}
|
||||
}
|
||||
|
||||
if (glyph_pos == -1 && end > 0)
|
||||
glyph_pos = logClusters[end - 1];
|
||||
else {
|
||||
if (x <= edge)
|
||||
glyph_pos--;
|
||||
}
|
||||
|
||||
const HB_CharAttributes *attrs = attributes();
|
||||
clusterLength = getClusterLength(logClusters, attrs, 0, end, glyph_pos, &clusterStart);
|
||||
|
||||
if (clusterLength) {
|
||||
const QGlyphLayout &glyphs = shapedGlyphs(si);
|
||||
QFixed glyphWidth = glyphs.effectiveAdvance(glyph_pos);
|
||||
// the approximate width of each individual element of the ligature
|
||||
QFixed perItemWidth = glyphWidth / clusterLength;
|
||||
QFixed left = x > edge ? edge : edge - glyphWidth;
|
||||
int n = ((x - left) / perItemWidth).floor().toInt();
|
||||
QFixed dist = x - left - n * perItemWidth;
|
||||
int closestItem = dist > (perItemWidth / 2) ? n + 1 : n;
|
||||
if (cursorOnCharacter && closestItem > 0)
|
||||
closestItem--;
|
||||
int pos = si->position + clusterStart + closestItem;
|
||||
// Jump to the next charStop
|
||||
while (!attrs[pos].charStop && pos < end)
|
||||
pos++;
|
||||
return pos;
|
||||
}
|
||||
return si->position + end;
|
||||
}
|
||||
|
||||
int QTextEngine::previousLogicalPosition(int oldPos) const
|
||||
{
|
||||
const HB_CharAttributes *attrs = attributes();
|
||||
|
@ -620,6 +620,7 @@ public:
|
||||
QFixed leadingSpaceWidth(const QScriptLine &line);
|
||||
|
||||
QFixed offsetInLigature(const QScriptItem *si, int pos, int max, int glyph_pos);
|
||||
int positionInLigature(const QScriptItem *si, int end, QFixed x, QFixed edge, int glyph_pos, bool cursorOnCharacter);
|
||||
int previousLogicalPosition(int oldPos) const;
|
||||
int nextLogicalPosition(int oldPos) const;
|
||||
int lineNumberForTextPosition(int pos);
|
||||
@ -642,6 +643,7 @@ private:
|
||||
void resolveAdditionalFormats() const;
|
||||
int endOfLine(int lineNum);
|
||||
int beginningOfLine(int lineNum);
|
||||
int getClusterLength(unsigned short *logClusters, const HB_CharAttributes *attributes, int from, int to, int glyph_pos, int *start);
|
||||
};
|
||||
|
||||
class QStackTextEngine : public QTextEngine {
|
||||
|
@ -2741,6 +2741,7 @@ int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
|
||||
}
|
||||
|
||||
int glyph_pos = -1;
|
||||
QFixed edge;
|
||||
// has to be inside run
|
||||
if (cpos == QTextLine::CursorOnCharacter) {
|
||||
if (si.analysis.bidiLevel % 2) {
|
||||
@ -2751,6 +2752,7 @@ int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
|
||||
if (pos < x)
|
||||
break;
|
||||
glyph_pos = gs;
|
||||
edge = pos;
|
||||
break;
|
||||
}
|
||||
pos -= glyphs.effectiveAdvance(gs);
|
||||
@ -2763,6 +2765,7 @@ int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
|
||||
if (pos > x)
|
||||
break;
|
||||
glyph_pos = gs;
|
||||
edge = pos;
|
||||
}
|
||||
pos += glyphs.effectiveAdvance(gs);
|
||||
++gs;
|
||||
@ -2776,6 +2779,7 @@ int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
|
||||
while (gs <= ge) {
|
||||
if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
|
||||
glyph_pos = gs;
|
||||
edge = pos;
|
||||
dist = qAbs(x-pos);
|
||||
}
|
||||
pos -= glyphs.effectiveAdvance(gs);
|
||||
@ -2785,6 +2789,7 @@ int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
|
||||
while (ge >= gs) {
|
||||
if (glyphs.attributes[ge].clusterStart && qAbs(x-pos) < dist) {
|
||||
glyph_pos = ge;
|
||||
edge = pos;
|
||||
dist = qAbs(x-pos);
|
||||
}
|
||||
pos += glyphs.effectiveAdvance(ge);
|
||||
@ -2796,6 +2801,7 @@ int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
|
||||
while (gs <= ge) {
|
||||
if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
|
||||
glyph_pos = gs;
|
||||
edge = pos;
|
||||
dist = qAbs(x-pos);
|
||||
}
|
||||
pos += glyphs.effectiveAdvance(gs);
|
||||
@ -2807,6 +2813,7 @@ int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
|
||||
pos += glyphs.effectiveAdvance(gs);
|
||||
if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
|
||||
glyph_pos = gs;
|
||||
edge = pos;
|
||||
dist = qAbs(x-pos);
|
||||
}
|
||||
++gs;
|
||||
@ -2823,16 +2830,13 @@ int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
|
||||
if (rtl && nchars > 0)
|
||||
return insertionPoints[lastLine ? nchars : nchars - 1];
|
||||
}
|
||||
return si.position + end;
|
||||
return eng->positionInLigature(&si, end, x, pos, -1,
|
||||
cpos == QTextLine::CursorOnCharacter);
|
||||
}
|
||||
}
|
||||
Q_ASSERT(glyph_pos != -1);
|
||||
int j;
|
||||
for (j = 0; j < eng->length(item); ++j)
|
||||
if (logClusters[j] == glyph_pos)
|
||||
break;
|
||||
// qDebug("at pos %d (in run: %d)", si.position + j, j);
|
||||
return si.position + j;
|
||||
return eng->positionInLigature(&si, end, x, edge, glyph_pos,
|
||||
cpos == QTextLine::CursorOnCharacter);
|
||||
}
|
||||
}
|
||||
// right of last item
|
||||
|
@ -128,6 +128,7 @@ private slots:
|
||||
void textWidthWithStackedTextEngine();
|
||||
void textWidthWithLineSeparator();
|
||||
void cursorInLigatureWithMultipleLines();
|
||||
void xToCursorForLigatures();
|
||||
|
||||
private:
|
||||
QFont testFont;
|
||||
@ -1477,5 +1478,29 @@ void tst_QTextLayout::cursorInLigatureWithMultipleLines()
|
||||
QVERIFY(line.cursorToX(0) != line.cursorToX(1));
|
||||
}
|
||||
|
||||
void tst_QTextLayout::xToCursorForLigatures()
|
||||
{
|
||||
#if !defined(Q_WS_MAC)
|
||||
QSKIP("This test can not be run on Mac", SkipAll);
|
||||
#endif
|
||||
QTextLayout layout("fi", QFont("Times", 20));
|
||||
layout.beginLayout();
|
||||
QTextLine line = layout.createLine();
|
||||
layout.endLayout();
|
||||
|
||||
QVERIFY(line.xToCursor(0) != line.xToCursor(line.naturalTextWidth() / 2));
|
||||
|
||||
// U+0061 U+0308
|
||||
QTextLayout layout2(QString::fromUtf8("\x61\xCC\x88"), QFont("Times", 20));
|
||||
|
||||
layout2.beginLayout();
|
||||
line = layout2.createLine();
|
||||
layout2.endLayout();
|
||||
|
||||
qreal width = line.naturalTextWidth();
|
||||
QVERIFY(line.xToCursor(0) == line.xToCursor(width / 2) ||
|
||||
line.xToCursor(width) == line.xToCursor(width / 2));
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QTextLayout)
|
||||
#include "tst_qtextlayout.moc"
|
||||
|
Loading…
Reference in New Issue
Block a user