in modules/skparagraph/src/TextWrapper.cpp [181:521]
void TextWrapper::moveForward(bool hasEllipsis) {
// We normally break lines by words.
// The only way we may go to clusters is if the word is too long or
// it's the first word and it has an ellipsis attached to it.
// If nothing fits we show the clipping.
if (!fWords.empty()) {
fEndLine.extend(fWords);
#ifdef SK_IGNORE_SKPARAGRAPH_ELLIPSIS_FIX
if (!fTooLongWord || hasEllipsis) { // Ellipsis added to a word
#else
if (!fTooLongWord && !hasEllipsis) { // Ellipsis added to a grapheme
#endif
return;
}
}
if (!fClusters.empty()) {
fEndLine.extend(fClusters);
if (!fTooLongCluster) {
return;
}
}
if (!fClip.empty()) {
// Flutter: forget the clipped cluster but keep the metrics
fEndLine.metrics().add(fClip.metrics());
}
}
// Special case for start/end cluster since they can be clipped
void TextWrapper::trimEndSpaces(TextAlign align) {
// Remember the breaking position
fEndLine.saveBreak();
// Skip all space cluster at the end
for (auto cluster = fEndLine.endCluster();
cluster >= fEndLine.startCluster() && cluster->isWhitespaceBreak();
--cluster) {
fEndLine.trim(cluster);
}
fEndLine.trim();
}
SkScalar TextWrapper::getClustersTrimmedWidth() {
// Move the end of the line to the left
SkScalar width = 0;
bool trailingSpaces = true;
for (auto cluster = fClusters.endCluster(); cluster >= fClusters.startCluster(); --cluster) {
if (cluster->run().isPlaceholder()) {
continue;
}
if (trailingSpaces) {
if (!cluster->isWhitespaceBreak()) {
width += cluster->trimmedWidth(cluster->endPos());
trailingSpaces = false;
}
continue;
}
width += cluster->width();
}
return width;
}
// Trim the beginning spaces in case of soft line break
std::tuple<Cluster*, size_t, SkScalar> TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
if (fHardLineBreak) {
// End of line is always end of cluster, but need to skip \n
auto width = fEndLine.width();
auto cluster = fEndLine.endCluster() + 1;
while (cluster < fEndLine.breakCluster() && cluster->isWhitespaceBreak()) {
width += cluster->width();
++cluster;
}
return std::make_tuple(fEndLine.breakCluster() + 1, 0, width);
}
// breakCluster points to the end of the line;
// It's a soft line break so we need to move lineStart forward skipping all the spaces
auto width = fEndLine.widthWithGhostSpaces();
auto cluster = fEndLine.breakCluster() + 1;
while (cluster < endOfClusters && cluster->isWhitespaceBreak()) {
width += cluster->width();
++cluster;
}
if (fEndLine.breakCluster()->isWhitespaceBreak() && fEndLine.breakCluster() < endOfClusters) {
// In case of a soft line break by the whitespace
// fBreak should point to the beginning of the next line
// (it only matters when there are trailing spaces)
fEndLine.shiftBreak();
}
return std::make_tuple(cluster, 0, width);
}
// TODO: refactor the code for line ending (with/without ellipsis)
void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
SkScalar maxWidth,
const AddLineToParagraph& addLine) {
fHeight = 0;
fMinIntrinsicWidth = std::numeric_limits<SkScalar>::min();
fMaxIntrinsicWidth = std::numeric_limits<SkScalar>::min();
auto span = parent->clusters();
if (span.empty()) {
return;
}
auto maxLines = parent->paragraphStyle().getMaxLines();
auto align = parent->paragraphStyle().effective_align();
auto unlimitedLines = maxLines == std::numeric_limits<size_t>::max();
auto endlessLine = !SkIsFinite(maxWidth);
auto hasEllipsis = parent->paragraphStyle().ellipsized();
auto disableFirstAscent = parent->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent;
auto disableLastDescent = parent->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent;
bool firstLine = true; // We only interested in fist line if we have to disable the first ascent
SkScalar softLineMaxIntrinsicWidth = 0;
fEndLine = TextStretch(span.begin(), span.begin(), parent->strutForceHeight());
auto end = span.end() - 1;
auto start = span.begin();
InternalLineMetrics maxRunMetrics;
bool needEllipsis = false;
TextIndent indent = parent->paragraphStyle().getTextIndent();
SkScalar lineIndent = indent.getFirstLine();
while (fEndLine.endCluster() != end) {
this->lookAhead(maxWidth - lineIndent, end, parent->getApplyRoundingHack());
auto lastLine = (hasEllipsis && unlimitedLines) || fLineNumber >= maxLines;
needEllipsis = hasEllipsis && !endlessLine && lastLine;
this->moveForward(needEllipsis);
needEllipsis &= fEndLine.endCluster() < end - 1; // Only if we have some text to ellipsize
// Do not trim end spaces on the naturally last line of the left aligned text
this->trimEndSpaces(align);
// For soft line breaks add to the line all the spaces next to it
Cluster* startLine;
size_t pos;
SkScalar widthWithSpaces;
std::tie(startLine, pos, widthWithSpaces) = this->trimStartSpaces(end);
if (needEllipsis && !fHardLineBreak) {
// This is what we need to do to preserve a space before the ellipsis
fEndLine.restoreBreak();
widthWithSpaces = fEndLine.widthWithGhostSpaces();
}
// It's important to always take into account empty metrics because baseline shifting needs to keep
// the original placement inside final metrics.
fEndLine.metrics().add(parent->getEmptyMetrics());
// Deal with placeholder clusters == runs[@size==1]
Run* lastRun = nullptr;
for (auto cluster = fEndLine.startCluster(); cluster <= fEndLine.endCluster(); ++cluster) {
auto r = cluster->runOrNull();
if (r == lastRun) {
continue;
}
lastRun = r;
if (lastRun->placeholderStyle() != nullptr) {
SkASSERT(lastRun->size() == 1);
// Update the placeholder metrics so we can get the placeholder positions later
// and the line metrics (to make sure the placeholder fits)
lastRun->updateMetrics(&fEndLine.metrics());
}
}
// Before we update the line metrics with struts,
// let's save it for GetRectsForRange(RectHeightStyle::kMax)
maxRunMetrics = fEndLine.metrics();
maxRunMetrics.fForceStrut = false;
// TODO: keep start/end/break info for text and runs but in a better way that below
TextRange textExcludingSpaces(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.breakCluster()->textRange().start);
TextRange textIncludingNewlines(fEndLine.startCluster()->textRange().start, startLine->textRange().start);
if (startLine == end) {
textIncludingNewlines.end = parent->text().size();
text.end = parent->text().size();
}
ClusterRange clusters(fEndLine.startCluster() - start, fEndLine.endCluster() - start + 1);
ClusterRange clustersWithGhosts(fEndLine.startCluster() - start, startLine - start);
if (disableFirstAscent && firstLine) {
fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
}
if (disableLastDescent && (lastLine || (startLine == end && !fHardLineBreak ))) {
fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
}
if (parent->strutEnabled()) {
// Make sure font metrics are not less than the strut
parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
}
SkScalar lineHeight = fEndLine.metrics().height();
firstLine = false;
if (fEndLine.empty()) {
// Correct text and clusters (make it empty for an empty line)
textExcludingSpaces.end = textExcludingSpaces.start;
clusters.end = clusters.start;
}
// In case of a force wrapping we don't have a break cluster and have to use the end cluster
text.end = std::max(text.end, textExcludingSpaces.end);
addLine(textExcludingSpaces,
text,
textIncludingNewlines, clusters, clustersWithGhosts, widthWithSpaces,
fEndLine.startPos(),
fEndLine.endPos(),
SkVector::Make(0, fHeight),
lineIndent,
SkVector::Make(fEndLine.width(), lineHeight),
fEndLine.metrics(),
needEllipsis && !fHardLineBreak);
softLineMaxIntrinsicWidth += widthWithSpaces;
fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
if (fHardLineBreak) {
softLineMaxIntrinsicWidth = 0;
}
// Start a new line
fHeight += lineHeight;
if (!fHardLineBreak || startLine != end) {
fEndLine.clean();
}
fEndLine.startFrom(startLine, pos);
parent->fMaxWidthWithTrailingSpaces = std::max(parent->fMaxWidthWithTrailingSpaces, widthWithSpaces);
if (hasEllipsis && unlimitedLines) {
// There is one case when we need an ellipsis on a separate line
// after a line break when width is infinite
if (!fHardLineBreak) {
break;
}
} else if (lastLine) {
// There is nothing more to draw
fHardLineBreak = false;
break;
}
lineIndent = indent.getRestLine();
++fLineNumber;
}
// We finished formatting the text but we need to scan the rest for some numbers
// TODO: make it a case of a normal flow
if (fEndLine.endCluster() != nullptr) {
auto lastWordLength = 0.0f;
auto cluster = fEndLine.endCluster();
while (cluster != end || cluster->endPos() < end->endPos()) {
fExceededMaxLines = true;
if (cluster->isHardBreak()) {
// Hard line break ends the word and the line
fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
softLineMaxIntrinsicWidth = 0;
fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
lastWordLength = 0;
} else if (cluster->isWhitespaceBreak()) {
// Whitespaces end the word
softLineMaxIntrinsicWidth += cluster->width();
fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
lastWordLength = 0;
} else if (cluster->run().isPlaceholder()) {
// Placeholder ends the previous word and creates a separate one
fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
// Placeholder width now counts in fMinIntrinsicWidth
softLineMaxIntrinsicWidth += cluster->width();
fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
lastWordLength = 0;
} else {
// Nothing out of ordinary - just add this cluster to the word and to the line
softLineMaxIntrinsicWidth += cluster->width();
lastWordLength += cluster->width();
}
++cluster;
}
fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
if (parent->lines().empty()) {
// In case we could not place even a single cluster on the line
if (disableFirstAscent) {
fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
}
if (disableLastDescent && !fHardLineBreak) {
fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
}
fHeight = std::max(fHeight, fEndLine.metrics().height());
}
}
if (fHardLineBreak) {
if (disableLastDescent) {
fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
}
// Last character is a line break
if (parent->strutEnabled()) {
// Make sure font metrics are not less than the strut
parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
}
ClusterRange clusters(fEndLine.breakCluster() - start, fEndLine.endCluster() - start);
addLine(fEndLine.breakCluster()->textRange(),
fEndLine.breakCluster()->textRange(),
fEndLine.endCluster()->textRange(),
clusters,
clusters,
0,
0,
0,
SkVector::Make(0, fHeight),
lineIndent,
SkVector::Make(0, fEndLine.metrics().height()),
fEndLine.metrics(),
needEllipsis);
fHeight += fEndLine.metrics().height();
parent->lines().back().setMaxRunMetrics(maxRunMetrics);
}
if (parent->lines().empty()) {
return;
}
// Correct line metric styles for the first and for the last lines if needed
if (disableFirstAscent) {
parent->lines().front().setAscentStyle(LineMetricStyle::Typographic);
}
if (disableLastDescent) {
parent->lines().back().setDescentStyle(LineMetricStyle::Typographic);
}
}
} // namespace textlayout