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;
|
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 {
|
class ParagraphView58 : public ParagraphView_Base {
|
||||||
protected:
|
protected:
|
||||||
SkString name() override { return SkString("Paragraph58"); }
|
SkString name() override { return SkString("Paragraph58"); }
|
||||||
@ -3446,21 +3432,14 @@ protected:
|
|||||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||||
TextStyle text_style;
|
TextStyle text_style;
|
||||||
text_style.setFontFamilies({SkString("Roboto")});
|
text_style.setFontFamilies({SkString("Roboto")});
|
||||||
text_style.setFontSize(32);
|
text_style.setFontSize(40);
|
||||||
text_style.setHeight(5.0);
|
|
||||||
text_style.setHeightOverride(true);
|
|
||||||
text_style.setColor(SK_ColorBLACK);
|
text_style.setColor(SK_ColorBLACK);
|
||||||
builder.pushStyle(text_style);
|
builder.pushStyle(text_style);
|
||||||
builder.addText("Line1\nLine2\nLine3\nLine4\n");
|
builder.addText(u"Text1 Google\u00A0Pay Text2");
|
||||||
|
|
||||||
auto paragraph = builder.Build();
|
auto paragraph = builder.Build();
|
||||||
paragraph->layout(width());
|
paragraph->layout(width());
|
||||||
paragraph->paint(canvas, 0, 0);
|
paragraph->paint(canvas, 0, 0);
|
||||||
|
|
||||||
std::vector<LineMetrics> metrics;
|
|
||||||
paragraph->getLineMetrics(metrics);
|
|
||||||
|
|
||||||
print_line_metrics(metrics);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -28,6 +28,11 @@ namespace textlayout {
|
|||||||
|
|
||||||
namespace {
|
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) {
|
SkScalar littleRound(SkScalar a) {
|
||||||
// This rounding is done to match Flutter tests. Must be removed..
|
// This rounding is done to match Flutter tests. Must be removed..
|
||||||
auto val = std::fabs(a);
|
auto val = std::fabs(a);
|
||||||
@ -736,6 +741,18 @@ size_t ParagraphImpl::getWhitespacesLength(TextRange textRange) {
|
|||||||
return len;
|
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) {
|
void ParagraphImpl::getLineMetrics(std::vector<LineMetrics>& metrics) {
|
||||||
metrics.clear();
|
metrics.clear();
|
||||||
for (auto& line : fLines) {
|
for (auto& line : fLines) {
|
||||||
|
@ -220,6 +220,7 @@ public:
|
|||||||
using CodeUnitRangeVisitor = std::function<bool(TextRange textRange)>;
|
using CodeUnitRangeVisitor = std::function<bool(TextRange textRange)>;
|
||||||
void forEachCodeUnitPropertyRange(CodeUnitFlags property, CodeUnitRangeVisitor visitor);
|
void forEachCodeUnitPropertyRange(CodeUnitFlags property, CodeUnitRangeVisitor visitor);
|
||||||
size_t getWhitespacesLength(TextRange textRange);
|
size_t getWhitespacesLength(TextRange textRange);
|
||||||
|
bool isSpace(TextRange textRange);
|
||||||
|
|
||||||
bool codeUnitHasProperty(size_t index, CodeUnitFlags property) const { return (fCodeUnitProperties[index] & property) == property; }
|
bool codeUnitHasProperty(size_t index, CodeUnitFlags property) const { return (fCodeUnitProperties[index] & property) == property; }
|
||||||
|
|
||||||
|
@ -383,6 +383,7 @@ Cluster::Cluster(ParagraphImpl* owner,
|
|||||||
, fHalfLetterSpacing(0.0) {
|
, fHalfLetterSpacing(0.0) {
|
||||||
size_t len = fOwner->getWhitespacesLength(fTextRange);
|
size_t len = fOwner->getWhitespacesLength(fTextRange);
|
||||||
fIsWhiteSpaces = (len == this->fTextRange.width());
|
fIsWhiteSpaces = (len == this->fTextRange.width());
|
||||||
|
fIsSpaces = fOwner->isSpace(fTextRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace textlayout
|
} // namespace textlayout
|
||||||
|
@ -248,6 +248,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isWhitespaces() const { return fIsWhiteSpaces; }
|
bool isWhitespaces() const { return fIsWhiteSpaces; }
|
||||||
|
bool isSpaces() const { return fIsSpaces; }
|
||||||
bool isHardBreak() const;
|
bool isHardBreak() const;
|
||||||
bool isSoftBreak() const;
|
bool isSoftBreak() const;
|
||||||
bool isGraphemeBreak() const;
|
bool isGraphemeBreak() const;
|
||||||
@ -297,6 +298,7 @@ private:
|
|||||||
SkScalar fHeight;
|
SkScalar fHeight;
|
||||||
SkScalar fHalfLetterSpacing;
|
SkScalar fHalfLetterSpacing;
|
||||||
bool fIsWhiteSpaces;
|
bool fIsWhiteSpaces;
|
||||||
|
bool fIsSpaces;
|
||||||
};
|
};
|
||||||
|
|
||||||
class InternalLineMetrics {
|
class InternalLineMetrics {
|
||||||
|
@ -29,6 +29,7 @@ void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters) {
|
|||||||
fClusters.startFrom(fEndLine.startCluster(), fEndLine.startPos());
|
fClusters.startFrom(fEndLine.startCluster(), fEndLine.startPos());
|
||||||
fClip.startFrom(fEndLine.startCluster(), fEndLine.startPos());
|
fClip.startFrom(fEndLine.startCluster(), fEndLine.startPos());
|
||||||
|
|
||||||
|
Cluster* nextNonBreakingSpace = nullptr;
|
||||||
for (auto cluster = fEndLine.endCluster(); cluster < endOfClusters; ++cluster) {
|
for (auto cluster = fEndLine.endCluster(); cluster < endOfClusters; ++cluster) {
|
||||||
// TODO: Trying to deal with flutter rounding problem. Must be removed...
|
// TODO: Trying to deal with flutter rounding problem. Must be removed...
|
||||||
auto width = fWords.width() + fClusters.width() + cluster->width();
|
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
|
// Walk further to see if there is a too long word, cluster or glyph
|
||||||
SkScalar nextWordLength = fClusters.width();
|
SkScalar nextWordLength = fClusters.width();
|
||||||
|
SkScalar nextShortWordLength = nextWordLength;
|
||||||
for (auto further = cluster; further != endOfClusters; ++further) {
|
for (auto further = cluster; further != endOfClusters; ++further) {
|
||||||
if (further->isSoftBreak() || further->isHardBreak() || further->isWhitespaces()) {
|
if (further->isSoftBreak() || further->isHardBreak() || further->isWhitespaces()) {
|
||||||
break;
|
break;
|
||||||
@ -69,6 +71,13 @@ void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters) {
|
|||||||
// Placeholder ends the word
|
// Placeholder ends the word
|
||||||
break;
|
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) {
|
if (maxWidth == 0) {
|
||||||
// This is a tricky flutter case: layout(width:0) places 1 cluster on each line
|
// This is a tricky flutter case: layout(width:0) places 1 cluster on each line
|
||||||
nextWordLength = std::max(nextWordLength, further->width());
|
nextWordLength = std::max(nextWordLength, further->width());
|
||||||
@ -77,6 +86,22 @@ void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (nextWordLength > maxWidth) {
|
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
|
// If the word is too long we can break it right now and hope it's enough
|
||||||
fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextWordLength);
|
fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextWordLength);
|
||||||
if (fClusters.endPos() - fClusters.startPos() > 1 ||
|
if (fClusters.endPos() - fClusters.startPos() > 1 ||
|
||||||
|
@ -41,7 +41,11 @@ class TextWrapper {
|
|||||||
if (c->run() != nullptr) {
|
if (c->run() != nullptr) {
|
||||||
fMetrics.add(c->run());
|
fMetrics.add(c->run());
|
||||||
}
|
}
|
||||||
|
if (c < e) {
|
||||||
|
fWidth += c->width();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
fWidthWithGhostSpaces = fWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline SkScalar width() const { return fWidth; }
|
inline SkScalar width() const { return fWidth; }
|
||||||
|
@ -113,6 +113,7 @@ class SKUNICODE_API SkUnicode {
|
|||||||
|
|
||||||
virtual bool isControl(SkUnichar utf8) = 0;
|
virtual bool isControl(SkUnichar utf8) = 0;
|
||||||
virtual bool isWhitespace(SkUnichar utf8) = 0;
|
virtual bool isWhitespace(SkUnichar utf8) = 0;
|
||||||
|
virtual bool isSpace(SkUnichar utf8) = 0;
|
||||||
virtual SkString convertUtf16ToUtf8(const std::u16string& utf16) = 0;
|
virtual SkString convertUtf16ToUtf8(const std::u16string& utf16) = 0;
|
||||||
|
|
||||||
// Methods used in SkShaper
|
// Methods used in SkShaper
|
||||||
|
@ -459,6 +459,10 @@ public:
|
|||||||
return u_isWhitespace(utf8);
|
return u_isWhitespace(utf8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isSpace(SkUnichar utf8) override {
|
||||||
|
return u_isspace(utf8);
|
||||||
|
}
|
||||||
|
|
||||||
static bool isHardLineBreak(SkUnichar utf8) {
|
static bool isHardLineBreak(SkUnichar utf8) {
|
||||||
auto property = u_getIntPropertyValue(utf8, UCHAR_LINE_BREAK);
|
auto property = u_getIntPropertyValue(utf8, UCHAR_LINE_BREAK);
|
||||||
return property == U_LB_LINE_FEED || property == U_LB_MANDATORY_BREAK;
|
return property == U_LB_LINE_FEED || property == U_LB_MANDATORY_BREAK;
|
||||||
|
Loading…
Reference in New Issue
Block a user