417 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			417 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // This file is part of Desktop App Toolkit,
 | |
| // a set of libraries for developing nice desktop applications.
 | |
| //
 | |
| // For license and copyright information please follow this link:
 | |
| // https://github.com/desktop-app/legal/blob/master/LEGAL
 | |
| //
 | |
| #include "ui/text/text_block.h"
 | |
| 
 | |
| #include "styles/style_basic.h"
 | |
| #include "base/integration.h"
 | |
| 
 | |
| #include <private/qfontengine_p.h>
 | |
| 
 | |
| // COPIED FROM qtextlayout.cpp AND MODIFIED
 | |
| namespace Ui {
 | |
| namespace Text {
 | |
| namespace {
 | |
| 
 | |
| struct ScriptLine {
 | |
| 	int length = 0;
 | |
| 	QFixed textWidth;
 | |
| };
 | |
| 
 | |
| // All members finished with "_" are internal.
 | |
| struct LineBreakHelper {
 | |
| 	ScriptLine tmpData;
 | |
| 	ScriptLine spaceData;
 | |
| 
 | |
| 	QGlyphLayout glyphs;
 | |
| 
 | |
| 	int glyphCount = 0;
 | |
| 	int maxGlyphs = INT_MAX;
 | |
| 	int currentPosition = 0;
 | |
| 
 | |
| 	glyph_t previousGlyph_ = 0;
 | |
| 	QFontEngine *previousFontEngine_ = nullptr;
 | |
| 
 | |
| 	QFixed rightBearing;
 | |
| 
 | |
| 	QFontEngine *fontEngine = nullptr;
 | |
| 	const unsigned short *logClusters = nullptr;
 | |
| 
 | |
| 	glyph_t currentGlyph() const;
 | |
| 	void saveCurrentGlyph();
 | |
| 	void calculateRightBearing(QFontEngine *engine, glyph_t glyph);
 | |
| 	void calculateRightBearing();
 | |
| 	void calculateRightBearingForPreviousGlyph();
 | |
| 
 | |
| 	// We always calculate the right bearing right before it is needed.
 | |
| 	// So we don't need caching / optimizations referred to delayed right bearing calculations.
 | |
| 
 | |
| 	//static const QFixed RightBearingNotCalculated;
 | |
| 
 | |
| 	//inline void resetRightBearing()
 | |
| 	//{
 | |
| 	//	rightBearing = RightBearingNotCalculated;
 | |
| 	//}
 | |
| 
 | |
| 	// We express the negative right bearing as an absolute number
 | |
| 	// so that it can be applied to the width using addition.
 | |
| 	QFixed negativeRightBearing() const;
 | |
| 
 | |
| };
 | |
| 
 | |
| //const QFixed LineBreakHelper::RightBearingNotCalculated = QFixed(1);
 | |
| 
 | |
| glyph_t LineBreakHelper::currentGlyph() const {
 | |
| 	Q_ASSERT(currentPosition > 0);
 | |
| 	Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs);
 | |
| 
 | |
| 	return glyphs.glyphs[logClusters[currentPosition - 1]];
 | |
| }
 | |
| 
 | |
| void LineBreakHelper::saveCurrentGlyph() {
 | |
| 	if (currentPosition > 0
 | |
| 		&& logClusters[currentPosition - 1] < glyphs.numGlyphs) {
 | |
| 		// needed to calculate right bearing later
 | |
| 		previousGlyph_ = currentGlyph();
 | |
| 		previousFontEngine_ = fontEngine;
 | |
| 	} else {
 | |
| 		previousGlyph_ = 0;
 | |
| 		previousFontEngine_ = nullptr;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void LineBreakHelper::calculateRightBearing(
 | |
| 		QFontEngine *engine,
 | |
| 		glyph_t glyph) {
 | |
| 	qreal rb;
 | |
| 	engine->getGlyphBearings(glyph, 0, &rb);
 | |
| 
 | |
| 	// We only care about negative right bearings, so we limit the range
 | |
| 	// of the bearing here so that we can assume it's negative in the rest
 | |
| 	// of the code, as well ase use QFixed(1) as a sentinel to represent
 | |
| 	// the state where we have yet to compute the right bearing.
 | |
| 	rightBearing = qMin(QFixed::fromReal(rb), QFixed(0));
 | |
| }
 | |
| 
 | |
| void LineBreakHelper::calculateRightBearing() {
 | |
| 	if (currentPosition > 0
 | |
| 		&& logClusters[currentPosition - 1] < glyphs.numGlyphs) {
 | |
| 		calculateRightBearing(fontEngine, currentGlyph());
 | |
| 	} else {
 | |
| 		rightBearing = 0;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void LineBreakHelper::calculateRightBearingForPreviousGlyph() {
 | |
| 	if (previousGlyph_ > 0) {
 | |
| 		calculateRightBearing(previousFontEngine_, previousGlyph_);
 | |
| 	} else {
 | |
| 		rightBearing = 0;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // We always calculate the right bearing right before it is needed.
 | |
| // So we don't need caching / optimizations referred to delayed right bearing calculations.
 | |
| 
 | |
| //static const QFixed RightBearingNotCalculated;
 | |
| 
 | |
| //inline void resetRightBearing()
 | |
| //{
 | |
| //	rightBearing = RightBearingNotCalculated;
 | |
| //}
 | |
| 
 | |
| // We express the negative right bearing as an absolute number
 | |
| // so that it can be applied to the width using addition.
 | |
| QFixed LineBreakHelper::negativeRightBearing() const {
 | |
| 	//if (rightBearing == RightBearingNotCalculated)
 | |
| 	//	return QFixed(0);
 | |
| 
 | |
| 	return qAbs(rightBearing);
 | |
| }
 | |
| 
 | |
| QString DebugCurrentParsingString, DebugCurrentParsingPart;
 | |
| int DebugCurrentParsingFrom = 0;
 | |
| int DebugCurrentParsingLength = 0;
 | |
| 
 | |
| 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);
 | |
| 
 | |
| 	if (!((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition)) {
 | |
| 		auto str = QStringList();
 | |
| 		for (auto i = 0; i < pos; ++i) {
 | |
| 			str.append(QString::number(logClusters[i]));
 | |
| 		}
 | |
| 		base::Integration::Instance().logAssertionViolation(QString("text: %1 (from: %2, length: %3) part: %4").arg(DebugCurrentParsingString).arg(DebugCurrentParsingFrom).arg(DebugCurrentParsingLength).arg(DebugCurrentParsingPart));
 | |
| 		base::Integration::Instance().logAssertionViolation(QString("pos: %1, end: %2, glyphPosition: %3, glyphCount: %4, lineLength: %5, num_glyphs: %6, logClusters[0..pos]: %7").arg(pos).arg(end).arg(glyphPosition).arg(glyphCount).arg(line.length).arg(current.num_glyphs).arg(str.join(",")));
 | |
| 		Unexpected("Values in addNextCluster()");
 | |
| 	}
 | |
| 	Q_ASSERT((pos == end && glyphPosition == current.num_glyphs)
 | |
| 		|| logClusters[pos] == glyphPosition);
 | |
| 
 | |
| 	++glyphCount;
 | |
| }
 | |
| 
 | |
| } // anonymous namespace
 | |
| 
 | |
| class BlockParser {
 | |
| public:
 | |
| 	BlockParser(
 | |
| 		QTextEngine *e,
 | |
| 		TextBlock *b,
 | |
| 		QFixed minResizeWidth,
 | |
| 		int blockFrom,
 | |
| 		const QString &str);
 | |
| 
 | |
| private:
 | |
| 	void parseWords(QFixed minResizeWidth, int blockFrom);
 | |
| 	bool isLineBreak(const QCharAttributes *attributes, int index);
 | |
| 
 | |
| 	TextBlock *block;
 | |
| 	QTextEngine *eng;
 | |
| 	const QString &str;
 | |
| 
 | |
| };
 | |
| 
 | |
| BlockParser::BlockParser(
 | |
| 	QTextEngine *e,
 | |
| 	TextBlock *b,
 | |
| 	QFixed minResizeWidth,
 | |
| 	int blockFrom,
 | |
| 	const QString &str)
 | |
| : block(b)
 | |
| , eng(e)
 | |
| , str(str) {
 | |
| 	parseWords(minResizeWidth, blockFrom);
 | |
| }
 | |
| 
 | |
| void BlockParser::parseWords(QFixed minResizeWidth, int blockFrom) {
 | |
| 	LineBreakHelper lbh;
 | |
| 
 | |
| 	// Helper for debugging crashes in text processing.
 | |
| 	//
 | |
| 	//		auto debugChars = QString();
 | |
| 	//		debugChars.reserve(str.size() * 7);
 | |
| 	//		for (const auto ch : str) {
 | |
| 	//			debugChars.append(
 | |
| 	//				"0x").append(
 | |
| 	//				QString::number(ch.unicode(), 16).toUpper()).append(
 | |
| 	//				' ');
 | |
| 	//		}
 | |
| 	//		LOG(("Text: %1, chars: %2").arg(str).arg(debugChars));
 | |
| 
 | |
| 	int item = -1;
 | |
| 	int newItem = eng->findItem(0);
 | |
| 
 | |
| 	style::align alignment = eng->option.alignment();
 | |
| 
 | |
| 	const QCharAttributes *attributes = eng->attributes();
 | |
| 	if (!attributes)
 | |
| 		return;
 | |
| 	int end = 0;
 | |
| 	lbh.logClusters = eng->layoutData->logClustersPtr;
 | |
| 
 | |
| 	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->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, -lbh.negativeRightBearing()));
 | |
| 			}
 | |
| 			block->_words.back().add_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.calculateRightBearing();
 | |
| 					block->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, -lbh.negativeRightBearing()));
 | |
| 					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.calculateRightBearingForPreviousGlyph();
 | |
| 							block->_words.push_back(TextWord(wordStart + blockFrom, -lastGraphemeBoundaryLine.textWidth, -lbh.negativeRightBearing()));
 | |
| 							block->_width += lastGraphemeBoundaryLine.textWidth;
 | |
| 							lbh.tmpData.textWidth -= lastGraphemeBoundaryLine.textWidth;
 | |
| 							lbh.tmpData.length -= lastGraphemeBoundaryLine.length;
 | |
| 							wordStart = lastGraphemeBoundaryPosition;
 | |
| 						}
 | |
| 						addingEachGrapheme = true;
 | |
| 					}
 | |
| 					if (addingEachGrapheme) {
 | |
| 						lbh.calculateRightBearing();
 | |
| 						block->_words.push_back(TextWord(wordStart + blockFrom, -lbh.tmpData.textWidth, -lbh.negativeRightBearing()));
 | |
| 						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 = block->_words.back().f_rpadding();
 | |
| 		block->_width -= block->_rpadding;
 | |
| 		block->_words.squeeze();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool BlockParser::isLineBreak(
 | |
| 		const QCharAttributes *attributes,
 | |
| 		int 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;
 | |
| }
 | |
| 
 | |
| QFixed AbstractBlock::f_rbearing() const {
 | |
| 	return (type() == TextBlockTText)
 | |
| 		? static_cast<const TextBlock*>(this)->real_f_rbearing()
 | |
| 		: 0;
 | |
| }
 | |
| 
 | |
| TextBlock::TextBlock(
 | |
| 	const style::font &font,
 | |
| 	const QString &str,
 | |
| 	QFixed minResizeWidth,
 | |
| 	uint16 from,
 | |
| 	uint16 length,
 | |
| 	uchar flags,
 | |
| 	uint16 lnkIndex)
 | |
| : AbstractBlock(font, str, from, length, flags, 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 = blockFont->monospace();
 | |
| 		} else {
 | |
| 			if (flags & TextBlockFBold) {
 | |
| 				blockFont = blockFont->bold();
 | |
| 			} else if (flags & TextBlockFSemibold) {
 | |
| 				blockFont = blockFont->semibold();
 | |
| 			}
 | |
| 			if (flags & TextBlockFItalic) blockFont = blockFont->italic();
 | |
| 			if (flags & TextBlockFUnderline) blockFont = blockFont->underline();
 | |
| 			if (flags & TextBlockFStrikeOut) blockFont = blockFont->strikeout();
 | |
| 			if (flags & TextBlockFTilde) { // tilde fix in OpenSans
 | |
| 				blockFont = blockFont->semibold();
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		DebugCurrentParsingString = str;
 | |
| 		DebugCurrentParsingFrom = _from;
 | |
| 		DebugCurrentParsingLength = length;
 | |
| 		const auto part = DebugCurrentParsingPart = str.mid(_from, length);
 | |
| 
 | |
| 		QStackTextEngine engine(part, blockFont->f);
 | |
| 		BlockParser parser(&engine, this, minResizeWidth, _from, part);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| EmojiBlock::EmojiBlock(
 | |
| 	const style::font &font,
 | |
| 	const QString &str,
 | |
| 	uint16 from,
 | |
| 	uint16 length,
 | |
| 	uchar flags,
 | |
| 	uint16 lnkIndex,
 | |
| 	EmojiPtr emoji)
 | |
| : AbstractBlock(font, str, from, length, flags, lnkIndex)
 | |
| , _emoji(emoji) {
 | |
| 	_flags |= ((TextBlockTEmoji & 0x0F) << 8);
 | |
| 	_width = int(st::emojiSize + 2 * st::emojiPadding);
 | |
| 	_rpadding = 0;
 | |
| 	for (auto i = length; i != 0;) {
 | |
| 		auto ch = str[_from + (--i)];
 | |
| 		if (ch.unicode() == QChar::Space) {
 | |
| 			_rpadding += font->spacew;
 | |
| 		} else {
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| } // namespace Text
 | |
| } // namespace Ui
 | 
