Using non-breaking spaces as a hint to break too long words
Change-Id: Ic88e961ea3fb08e61340895115f514c6f5dec163 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/379798 Reviewed-by: Ben Wagner <bungeman@google.com> Commit-Queue: Julia Lavrova <jlavrova@google.com>
This commit is contained in:
parent
07ee548d5b
commit
c70f8c331d
@ -3416,20 +3416,6 @@ private:
|
||||
using INHERITED = Sample;
|
||||
};
|
||||
|
||||
void print_line_metrics(std::vector<LineMetrics> metrics) {
|
||||
for (size_t i = 0; i < metrics.size(); ++i) {
|
||||
auto& line = metrics[i];
|
||||
printf("Line[%zd] startIndex=%zd, endIndex=%zd, endExcludingWhitespaces=%zd, _endIncludingNewline=%zd, hardBreak=%d, baseline=%.2f, ascent=%.2f, descent=%.2f, unscaledAscent=%.2f, height=%.2f, width=%.2f, left=%.2f, lineNumber=%zd\n",
|
||||
i,
|
||||
line.fStartIndex, line.fEndIndex,
|
||||
line.fEndExcludingWhitespaces, line.fEndIncludingNewline,
|
||||
line.fHardBreak,
|
||||
line.fBaseline, line.fAscent, line.fDescent, line.fUnscaledAscent,
|
||||
line.fHeight, line.fWidth, line.fLeft,
|
||||
line.fLineNumber);
|
||||
}
|
||||
}
|
||||
|
||||
class ParagraphView58 : public ParagraphView_Base {
|
||||
protected:
|
||||
SkString name() override { return SkString("Paragraph58"); }
|
||||
@ -3446,21 +3432,14 @@ protected:
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
TextStyle text_style;
|
||||
text_style.setFontFamilies({SkString("Roboto")});
|
||||
text_style.setFontSize(32);
|
||||
text_style.setHeight(5.0);
|
||||
text_style.setHeightOverride(true);
|
||||
text_style.setFontSize(40);
|
||||
text_style.setColor(SK_ColorBLACK);
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText("Line1\nLine2\nLine3\nLine4\n");
|
||||
builder.addText(u"Text1 Google\u00A0Pay Text2");
|
||||
|
||||
auto paragraph = builder.Build();
|
||||
paragraph->layout(width());
|
||||
paragraph->paint(canvas, 0, 0);
|
||||
|
||||
std::vector<LineMetrics> metrics;
|
||||
paragraph->getLineMetrics(metrics);
|
||||
|
||||
print_line_metrics(metrics);
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -28,6 +28,11 @@ namespace textlayout {
|
||||
|
||||
namespace {
|
||||
|
||||
static inline SkUnichar nextUtf8Unit(const char** ptr, const char* end) {
|
||||
SkUnichar val = SkUTF::NextUTF8(ptr, end);
|
||||
return val < 0 ? 0xFFFD : val;
|
||||
}
|
||||
|
||||
SkScalar littleRound(SkScalar a) {
|
||||
// This rounding is done to match Flutter tests. Must be removed..
|
||||
auto val = std::fabs(a);
|
||||
@ -736,6 +741,18 @@ size_t ParagraphImpl::getWhitespacesLength(TextRange textRange) {
|
||||
return len;
|
||||
}
|
||||
|
||||
bool ParagraphImpl::isSpace(TextRange textRange) {
|
||||
auto text = ParagraphImpl::text(textRange);
|
||||
const char* ch = text.begin();
|
||||
while (ch != text.end()) {
|
||||
SkUnichar unicode = nextUtf8Unit(&ch, text.end());
|
||||
if (!fUnicode->isSpace(unicode)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ParagraphImpl::getLineMetrics(std::vector<LineMetrics>& metrics) {
|
||||
metrics.clear();
|
||||
for (auto& line : fLines) {
|
||||
|
@ -220,6 +220,7 @@ public:
|
||||
using CodeUnitRangeVisitor = std::function<bool(TextRange textRange)>;
|
||||
void forEachCodeUnitPropertyRange(CodeUnitFlags property, CodeUnitRangeVisitor visitor);
|
||||
size_t getWhitespacesLength(TextRange textRange);
|
||||
bool isSpace(TextRange textRange);
|
||||
|
||||
bool codeUnitHasProperty(size_t index, CodeUnitFlags property) const { return (fCodeUnitProperties[index] & property) == property; }
|
||||
|
||||
|
@ -383,6 +383,7 @@ Cluster::Cluster(ParagraphImpl* owner,
|
||||
, fHalfLetterSpacing(0.0) {
|
||||
size_t len = fOwner->getWhitespacesLength(fTextRange);
|
||||
fIsWhiteSpaces = (len == this->fTextRange.width());
|
||||
fIsSpaces = fOwner->isSpace(fTextRange);
|
||||
}
|
||||
|
||||
} // namespace textlayout
|
||||
|
@ -248,6 +248,7 @@ public:
|
||||
}
|
||||
|
||||
bool isWhitespaces() const { return fIsWhiteSpaces; }
|
||||
bool isSpaces() const { return fIsSpaces; }
|
||||
bool isHardBreak() const;
|
||||
bool isSoftBreak() const;
|
||||
bool isGraphemeBreak() const;
|
||||
@ -297,6 +298,7 @@ private:
|
||||
SkScalar fHeight;
|
||||
SkScalar fHalfLetterSpacing;
|
||||
bool fIsWhiteSpaces;
|
||||
bool fIsSpaces;
|
||||
};
|
||||
|
||||
class InternalLineMetrics {
|
||||
|
@ -29,6 +29,7 @@ void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters) {
|
||||
fClusters.startFrom(fEndLine.startCluster(), fEndLine.startPos());
|
||||
fClip.startFrom(fEndLine.startCluster(), fEndLine.startPos());
|
||||
|
||||
Cluster* nextNonBreakingSpace = nullptr;
|
||||
for (auto cluster = fEndLine.endCluster(); cluster < endOfClusters; ++cluster) {
|
||||
// TODO: Trying to deal with flutter rounding problem. Must be removed...
|
||||
auto width = fWords.width() + fClusters.width() + cluster->width();
|
||||
@ -61,6 +62,7 @@ void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters) {
|
||||
|
||||
// Walk further to see if there is a too long word, cluster or glyph
|
||||
SkScalar nextWordLength = fClusters.width();
|
||||
SkScalar nextShortWordLength = nextWordLength;
|
||||
for (auto further = cluster; further != endOfClusters; ++further) {
|
||||
if (further->isSoftBreak() || further->isHardBreak() || further->isWhitespaces()) {
|
||||
break;
|
||||
@ -69,6 +71,13 @@ void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters) {
|
||||
// Placeholder ends the word
|
||||
break;
|
||||
}
|
||||
|
||||
if (further->isSpaces() && nextWordLength <= maxWidth) {
|
||||
// The cluster is spaces but not the end of the word in a normal sense
|
||||
nextNonBreakingSpace = further;
|
||||
nextShortWordLength = nextWordLength;
|
||||
}
|
||||
|
||||
if (maxWidth == 0) {
|
||||
// This is a tricky flutter case: layout(width:0) places 1 cluster on each line
|
||||
nextWordLength = std::max(nextWordLength, further->width());
|
||||
@ -77,6 +86,22 @@ void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters) {
|
||||
}
|
||||
}
|
||||
if (nextWordLength > maxWidth) {
|
||||
if (nextNonBreakingSpace != nullptr) {
|
||||
// We only get here if the non-breaking space improves our situation
|
||||
// (allows us to break the text to fit the word)
|
||||
auto shortLength = littleRound(fWords.width() + nextShortWordLength);
|
||||
if (shortLength <= maxWidth) {
|
||||
// We can add the short word to the existing line
|
||||
fClusters = TextStretch(fClusters.startCluster(), nextNonBreakingSpace, fClusters.metrics().getForceStrut());
|
||||
fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextShortWordLength);
|
||||
fWords.extend(fClusters);
|
||||
} else {
|
||||
// We can place the short word on the next line
|
||||
fClusters.clean();
|
||||
}
|
||||
// Either way we are not in "word is too long" situation anymore
|
||||
break;
|
||||
}
|
||||
// If the word is too long we can break it right now and hope it's enough
|
||||
fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextWordLength);
|
||||
if (fClusters.endPos() - fClusters.startPos() > 1 ||
|
||||
|
@ -41,7 +41,11 @@ class TextWrapper {
|
||||
if (c->run() != nullptr) {
|
||||
fMetrics.add(c->run());
|
||||
}
|
||||
if (c < e) {
|
||||
fWidth += c->width();
|
||||
}
|
||||
}
|
||||
fWidthWithGhostSpaces = fWidth;
|
||||
}
|
||||
|
||||
inline SkScalar width() const { return fWidth; }
|
||||
|
@ -113,6 +113,7 @@ class SKUNICODE_API SkUnicode {
|
||||
|
||||
virtual bool isControl(SkUnichar utf8) = 0;
|
||||
virtual bool isWhitespace(SkUnichar utf8) = 0;
|
||||
virtual bool isSpace(SkUnichar utf8) = 0;
|
||||
virtual SkString convertUtf16ToUtf8(const std::u16string& utf16) = 0;
|
||||
|
||||
// Methods used in SkShaper
|
||||
|
@ -459,6 +459,10 @@ public:
|
||||
return u_isWhitespace(utf8);
|
||||
}
|
||||
|
||||
bool isSpace(SkUnichar utf8) override {
|
||||
return u_isspace(utf8);
|
||||
}
|
||||
|
||||
static bool isHardLineBreak(SkUnichar utf8) {
|
||||
auto property = u_getIntPropertyValue(utf8, UCHAR_LINE_BREAK);
|
||||
return property == U_LB_LINE_FEED || property == U_LB_MANDATORY_BREAK;
|
||||
|
Loading…
Reference in New Issue
Block a user