323 lines
9.9 KiB
C++
323 lines
9.9 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop version of Telegram messaging app, see https://telegram.org
|
|
|
|
Telegram Desktop is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
It is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
In addition, as a special exception, the copyright holders give permission
|
|
to link the code of portions of this program with the OpenSSL library.
|
|
|
|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
|
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|
*/
|
|
#include "stdafx.h"
|
|
#include "ui/text/text_block.h"
|
|
|
|
// COPIED FROM qtextlayout.cpp AND MODIFIED
|
|
namespace {
|
|
|
|
struct ScriptLine {
|
|
ScriptLine() : length(0), textWidth(0) {
|
|
}
|
|
|
|
int32 length;
|
|
QFixed textWidth;
|
|
};
|
|
|
|
struct LineBreakHelper
|
|
{
|
|
LineBreakHelper()
|
|
: glyphCount(0), maxGlyphs(0), currentPosition(0), fontEngine(0), logClusters(0)
|
|
{
|
|
}
|
|
|
|
|
|
ScriptLine tmpData;
|
|
ScriptLine spaceData;
|
|
|
|
QGlyphLayout glyphs;
|
|
|
|
int glyphCount;
|
|
int maxGlyphs;
|
|
int currentPosition;
|
|
glyph_t previousGlyph;
|
|
|
|
QFixed rightBearing;
|
|
|
|
QFontEngine *fontEngine;
|
|
const unsigned short *logClusters;
|
|
|
|
inline glyph_t currentGlyph() const
|
|
{
|
|
Q_ASSERT(currentPosition > 0);
|
|
Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs);
|
|
|
|
return glyphs.glyphs[logClusters[currentPosition - 1]];
|
|
}
|
|
|
|
inline void saveCurrentGlyph()
|
|
{
|
|
previousGlyph = 0;
|
|
if (currentPosition > 0 &&
|
|
logClusters[currentPosition - 1] < glyphs.numGlyphs) {
|
|
previousGlyph = currentGlyph(); // needed to calculate right bearing later
|
|
}
|
|
}
|
|
|
|
inline void adjustRightBearing(glyph_t glyph)
|
|
{
|
|
qreal rb;
|
|
fontEngine->getGlyphBearings(glyph, 0, &rb);
|
|
rightBearing = qMin(QFixed(), QFixed::fromReal(rb));
|
|
}
|
|
|
|
inline void adjustRightBearing()
|
|
{
|
|
if (currentPosition <= 0)
|
|
return;
|
|
adjustRightBearing(currentGlyph());
|
|
}
|
|
|
|
inline void adjustPreviousRightBearing()
|
|
{
|
|
if (previousGlyph > 0)
|
|
adjustRightBearing(previousGlyph);
|
|
}
|
|
|
|
};
|
|
|
|
static inline void addNextCluster(int &pos, int end, ScriptLine &line, int &glyphCount,
|
|
const QScriptItem ¤t, const unsigned short *logClusters,
|
|
const QGlyphLayout &glyphs)
|
|
{
|
|
int glyphPosition = logClusters[pos];
|
|
do { // got to the first next cluster
|
|
++pos;
|
|
++line.length;
|
|
} while (pos < end && logClusters[pos] == glyphPosition);
|
|
do { // calculate the textWidth for the rest of the current cluster.
|
|
if (!glyphs.attributes[glyphPosition].dontPrint)
|
|
line.textWidth += glyphs.advances[glyphPosition];
|
|
++glyphPosition;
|
|
} while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart);
|
|
|
|
Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition);
|
|
|
|
++glyphCount;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
class BlockParser {
|
|
public:
|
|
|
|
BlockParser(QTextEngine *e, TextBlock *b, QFixed minResizeWidth, int32 blockFrom, const QString &str)
|
|
: block(b), eng(e), str(str) {
|
|
parseWords(minResizeWidth, blockFrom);
|
|
}
|
|
|
|
void parseWords(QFixed minResizeWidth, int32 blockFrom) {
|
|
LineBreakHelper lbh;
|
|
|
|
lbh.maxGlyphs = INT_MAX;
|
|
|
|
int item = -1;
|
|
int newItem = eng->findItem(0);
|
|
|
|
style::align alignment = eng->option.alignment();
|
|
|
|
const QCharAttributes *attributes = eng->attributes();
|
|
if (!attributes)
|
|
return;
|
|
lbh.currentPosition = 0;
|
|
int end = 0;
|
|
lbh.logClusters = eng->layoutData->logClustersPtr;
|
|
lbh.previousGlyph = 0;
|
|
|
|
block->_lpadding = 0;
|
|
block->_words.clear();
|
|
|
|
int wordStart = lbh.currentPosition;
|
|
|
|
bool addingEachGrapheme = false;
|
|
int lastGraphemeBoundaryPosition = -1;
|
|
ScriptLine lastGraphemeBoundaryLine;
|
|
|
|
while (newItem < eng->layoutData->items.size()) {
|
|
if (newItem != item) {
|
|
item = newItem;
|
|
const QScriptItem ¤t = eng->layoutData->items[item];
|
|
if (!current.num_glyphs) {
|
|
eng->shape(item);
|
|
attributes = eng->attributes();
|
|
if (!attributes)
|
|
return;
|
|
lbh.logClusters = eng->layoutData->logClustersPtr;
|
|
}
|
|
lbh.currentPosition = current.position;
|
|
end = current.position + eng->length(item);
|
|
lbh.glyphs = eng->shapedGlyphs(¤t);
|
|
QFontEngine *fontEngine = eng->fontEngine(current);
|
|
if (lbh.fontEngine != fontEngine) {
|
|
lbh.fontEngine = fontEngine;
|
|
}
|
|
}
|
|
const QScriptItem ¤t = eng->layoutData->items[item];
|
|
|
|
if (attributes[lbh.currentPosition].whiteSpace) {
|
|
while (lbh.currentPosition < end && attributes[lbh.currentPosition].whiteSpace)
|
|
addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount,
|
|
current, lbh.logClusters, lbh.glyphs);
|
|
|
|
if (block->_words.isEmpty()) {
|
|
block->_lpadding = lbh.spaceData.textWidth;
|
|
} else {
|
|
block->_words.back().rpadding += lbh.spaceData.textWidth;
|
|
block->_width += lbh.spaceData.textWidth;
|
|
}
|
|
lbh.spaceData.length = 0;
|
|
lbh.spaceData.textWidth = 0;
|
|
|
|
wordStart = lbh.currentPosition;
|
|
|
|
addingEachGrapheme = false;
|
|
lastGraphemeBoundaryPosition = -1;
|
|
lastGraphemeBoundaryLine = ScriptLine();
|
|
} else {
|
|
do {
|
|
addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
|
|
current, lbh.logClusters, lbh.glyphs);
|
|
|
|
if (lbh.currentPosition >= eng->layoutData->string.length()
|
|
|| attributes[lbh.currentPosition].whiteSpace
|
|
|| isLineBreak(attributes, lbh.currentPosition)) {
|
|
lbh.adjustRightBearing();
|
|
block->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, qMin(QFixed(), lbh.rightBearing)));
|
|
block->_width += lbh.tmpData.textWidth;
|
|
lbh.tmpData.textWidth = 0;
|
|
lbh.tmpData.length = 0;
|
|
wordStart = lbh.currentPosition;
|
|
break;
|
|
} else if (attributes[lbh.currentPosition].graphemeBoundary) {
|
|
if (!addingEachGrapheme && lbh.tmpData.textWidth > minResizeWidth) {
|
|
if (lastGraphemeBoundaryPosition >= 0) {
|
|
lbh.adjustPreviousRightBearing();
|
|
block->_words.push_back(TextWord(wordStart + blockFrom, -lastGraphemeBoundaryLine.textWidth, qMin(QFixed(), lbh.rightBearing)));
|
|
block->_width += lastGraphemeBoundaryLine.textWidth;
|
|
lbh.tmpData.textWidth -= lastGraphemeBoundaryLine.textWidth;
|
|
lbh.tmpData.length -= lastGraphemeBoundaryLine.length;
|
|
wordStart = lastGraphemeBoundaryPosition;
|
|
}
|
|
addingEachGrapheme = true;
|
|
}
|
|
if (addingEachGrapheme) {
|
|
lbh.adjustRightBearing();
|
|
block->_words.push_back(TextWord(wordStart + blockFrom, -lbh.tmpData.textWidth, qMin(QFixed(), lbh.rightBearing)));
|
|
block->_width += lbh.tmpData.textWidth;
|
|
lbh.tmpData.textWidth = 0;
|
|
lbh.tmpData.length = 0;
|
|
wordStart = lbh.currentPosition;
|
|
} else {
|
|
lastGraphemeBoundaryPosition = lbh.currentPosition;
|
|
lastGraphemeBoundaryLine = lbh.tmpData;
|
|
lbh.saveCurrentGlyph();
|
|
}
|
|
}
|
|
} while (lbh.currentPosition < end);
|
|
}
|
|
if (lbh.currentPosition == end)
|
|
newItem = item + 1;
|
|
}
|
|
if (block->_words.isEmpty()) {
|
|
block->_rpadding = 0;
|
|
} else {
|
|
block->_rpadding = block->_words.back().rpadding;
|
|
block->_width -= block->_rpadding;
|
|
block->_words.squeeze();
|
|
}
|
|
}
|
|
|
|
bool isLineBreak(const QCharAttributes *attributes, int32 index) {
|
|
bool lineBreak = attributes[index].lineBreak;
|
|
if (lineBreak && block->lnkIndex() > 0 && index > 0 && str.at(index - 1) == '/') {
|
|
return false; // don't break after / in links
|
|
}
|
|
return lineBreak;
|
|
}
|
|
|
|
private:
|
|
|
|
TextBlock *block;
|
|
QTextEngine *eng;
|
|
const QString &str;
|
|
|
|
};
|
|
|
|
TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResizeWidth, uint16 from, uint16 length, uchar flags, const style::color &color, uint16 lnkIndex) : ITextBlock(font, str, from, length, flags, color, lnkIndex) {
|
|
_flags |= ((TextBlockTText & 0x0F) << 8);
|
|
if (length) {
|
|
style::font blockFont = font;
|
|
if (!flags && lnkIndex) {
|
|
// should use textStyle lnkFlags somehow... not supported
|
|
}
|
|
|
|
if ((flags & TextBlockFPre) || (flags & TextBlockFCode)) {
|
|
blockFont = App::monofont();
|
|
if (blockFont->size() != font->size() || blockFont->flags() != font->flags()) {
|
|
blockFont = style::font(font->size(), font->flags(), blockFont->family());
|
|
}
|
|
} else {
|
|
if (flags & TextBlockFBold) {
|
|
blockFont = blockFont->bold();
|
|
} else if (flags & TextBlockFSemibold) {
|
|
blockFont = st::semiboldFont;
|
|
if (blockFont->size() != font->size() || blockFont->flags() != font->flags()) {
|
|
blockFont = style::font(font->size(), font->flags(), blockFont->family());
|
|
}
|
|
}
|
|
if (flags & TextBlockFItalic) blockFont = blockFont->italic();
|
|
if (flags & TextBlockFUnderline) blockFont = blockFont->underline();
|
|
if (flags & TextBlockFTilde) { // tilde fix in OpenSans
|
|
blockFont = st::semiboldFont;
|
|
}
|
|
}
|
|
|
|
QString part = str.mid(_from, length);
|
|
QStackTextEngine engine(part, blockFont->f);
|
|
engine.itemize();
|
|
|
|
QTextLayout layout(&engine);
|
|
layout.beginLayout();
|
|
layout.createLine();
|
|
|
|
bool logCrashString = (rand_value<uchar>() % 4 == 1);
|
|
if (logCrashString) {
|
|
SignalHandlers::setCrashAnnotationRef("CrashString", &str);
|
|
}
|
|
BlockParser parser(&engine, this, minResizeWidth, _from, part);
|
|
if (logCrashString) {
|
|
SignalHandlers::clearCrashAnnotationRef("CrashString");
|
|
}
|
|
|
|
layout.endLayout();
|
|
}
|
|
}
|
|
|
|
EmojiBlock::EmojiBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, const style::color &color, uint16 lnkIndex, const EmojiData *emoji) : ITextBlock(font, str, from, length, flags, color, lnkIndex), emoji(emoji) {
|
|
_flags |= ((TextBlockTEmoji & 0x0F) << 8);
|
|
_width = int(st::emojiSize + 2 * st::emojiPadding);
|
|
}
|
|
|
|
SkipBlock::SkipBlock(const style::font &font, const QString &str, uint16 from, int32 w, int32 h, uint16 lnkIndex) : ITextBlock(font, str, from, 1, 0, style::color(), lnkIndex), _height(h) {
|
|
_flags |= ((TextBlockTSkip & 0x0F) << 8);
|
|
_width = w;
|
|
}
|