Dealing with ghost spaces
Change-Id: I9cf133e915658f17d00f279ee1fa2662effa2021 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/231646 Commit-Queue: Julia Lavrova <jlavrova@google.com> Reviewed-by: Ben Wagner <bungeman@google.com> Reviewed-by: Julia Lavrova <jlavrova@google.com>
This commit is contained in:
parent
5ed96f7213
commit
db9f669eb1
@ -27,11 +27,14 @@ struct StrutStyle {
|
||||
void setLeading(SkScalar Leading) { fLeading = Leading; }
|
||||
SkScalar getLeading() const { return fLeading; }
|
||||
|
||||
bool getStrutEnabled() const { return fStrutEnabled; }
|
||||
void setStrutEnabled(bool v) { fStrutEnabled = v; }
|
||||
bool getStrutEnabled() const { return fEnabled; }
|
||||
void setStrutEnabled(bool v) { fEnabled = v; }
|
||||
|
||||
bool getForceStrutHeight() const { return fForceStrutHeight; }
|
||||
void setForceStrutHeight(bool v) { fForceStrutHeight = v; }
|
||||
bool getForceStrutHeight() const { return fForceHeight; }
|
||||
void setForceStrutHeight(bool v) { fForceHeight = v; }
|
||||
|
||||
bool getHeightOverride() const { return fHeightOverride; }
|
||||
void setHeightOverride(bool v) { fHeightOverride = v; }
|
||||
|
||||
private:
|
||||
|
||||
@ -40,8 +43,9 @@ private:
|
||||
SkScalar fFontSize;
|
||||
SkScalar fHeight;
|
||||
SkScalar fLeading;
|
||||
bool fForceStrutHeight;
|
||||
bool fStrutEnabled;
|
||||
bool fForceHeight;
|
||||
bool fEnabled;
|
||||
bool fHeightOverride;
|
||||
};
|
||||
|
||||
struct ParagraphStyle {
|
||||
|
@ -120,7 +120,10 @@ public:
|
||||
}
|
||||
|
||||
void setHeight(SkScalar height) { fHeight = height; }
|
||||
SkScalar getHeight() const { return fHeight; }
|
||||
SkScalar getHeight() const { return fHeightOverride ? fHeight : 0; }
|
||||
|
||||
void setHeightOverride(bool heightOverride) { fHeightOverride = heightOverride; }
|
||||
bool getHeightOverride() const { return fHeightOverride; }
|
||||
|
||||
void setLetterSpacing(SkScalar letterSpacing) { fLetterSpacing = letterSpacing; }
|
||||
SkScalar getLetterSpacing() const { return fLetterSpacing; }
|
||||
@ -138,15 +141,7 @@ public:
|
||||
TextBaseline getTextBaseline() const { return fTextBaseline; }
|
||||
void setTextBaseline(TextBaseline baseline) { fTextBaseline = baseline; }
|
||||
|
||||
// TODO: Not to use SkFontMetrics class (it has different purpose and meaning)
|
||||
void getFontMetrics(SkFontMetrics* metrics) const {
|
||||
SkFont font(fTypeface, fFontSize);
|
||||
font.getMetrics(metrics);
|
||||
metrics->fAscent =
|
||||
(metrics->fAscent - metrics->fLeading / 2) * (fHeight == 0 ? 1 : fHeight);
|
||||
metrics->fDescent =
|
||||
(metrics->fDescent + metrics->fLeading / 2) * (fHeight == 0 ? 1 : fHeight);
|
||||
}
|
||||
void getFontMetrics(SkFontMetrics* metrics) const;
|
||||
|
||||
private:
|
||||
Decoration fDecoration;
|
||||
@ -156,6 +151,7 @@ private:
|
||||
std::vector<SkString> fFontFamilies;
|
||||
SkScalar fFontSize;
|
||||
SkScalar fHeight;
|
||||
bool fHeightOverride;
|
||||
SkString fLocale;
|
||||
SkScalar fLetterSpacing;
|
||||
SkScalar fWordSpacing;
|
||||
|
@ -205,9 +205,7 @@ void FontResolver::addResolvedWhitespacesToMapping() {
|
||||
fUnresolved -= resolvedWhitespaces;
|
||||
}
|
||||
|
||||
FontDescr FontResolver::makeFont(sk_sp<SkTypeface> typeface,
|
||||
SkScalar size,
|
||||
SkScalar height) {
|
||||
FontDescr FontResolver::makeFont(sk_sp<SkTypeface> typeface, SkScalar size, SkScalar height) {
|
||||
SkFont font(typeface, size);
|
||||
font.setEdging(SkFont::Edging::kAntiAlias);
|
||||
font.setHinting(SkFontHinting::kSlight);
|
||||
|
@ -19,28 +19,29 @@ namespace textlayout {
|
||||
class FontIterator final : public SkShaper::FontRunIterator {
|
||||
public:
|
||||
FontIterator(SkSpan<const char> utf8, FontResolver* fontResolver)
|
||||
: fText(utf8), fCurrentChar(utf8.begin()), fFontResolver(fontResolver) { }
|
||||
: fText(utf8), fCurrentChar(utf8.begin()), fFontResolver(fontResolver) {
|
||||
}
|
||||
|
||||
void consume() override {
|
||||
SkASSERT(fCurrentChar < fText.end());
|
||||
SkString locale;
|
||||
auto found = fFontResolver->findNext(fCurrentChar, &fFont, &fLineHeight);
|
||||
SkASSERT(found);
|
||||
|
||||
// Move until we find the first character that cannot be resolved with the current font
|
||||
while (++fCurrentChar != fText.end()) {
|
||||
SkFont font;
|
||||
SkScalar height;
|
||||
SkString locale;
|
||||
found = fFontResolver->findNext(fCurrentChar, &font, &height);
|
||||
if (found) {
|
||||
if (fFont == font && fLineHeight == height) {
|
||||
continue;
|
||||
}
|
||||
SkASSERT(fCurrentChar < fText.end());
|
||||
auto found = fFontResolver->findNext(fCurrentChar, &fFont, &fLineHeight);
|
||||
SkASSERT(found);
|
||||
if (!found) {
|
||||
fCurrentChar = fText.end();
|
||||
return;
|
||||
}
|
||||
// Move until we find the first character that cannot be resolved with the current font
|
||||
while (++fCurrentChar != fText.end()) {
|
||||
SkFont font;
|
||||
SkScalar height;
|
||||
if (fFontResolver->findNext(fCurrentChar, &font, &height)) {
|
||||
if (fFont == font && fLineHeight == height) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t endOfCurrentRun() const override { return fCurrentChar - fText.begin(); }
|
||||
bool atEnd() const override { return fCurrentChar == fText.end(); }
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <unicode/brkiter.h>
|
||||
#include <unicode/ubidi.h>
|
||||
#include <unicode/unistr.h>
|
||||
#include <unicode/urename.h>
|
||||
#include "include/core/SkBlurTypes.h"
|
||||
#include "include/core/SkCanvas.h"
|
||||
#include "include/core/SkFontMgr.h"
|
||||
@ -26,7 +27,7 @@ public:
|
||||
|
||||
bool initialize(SkSpan<const char> text, UBreakIteratorType type) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
fIterator = nullptr;
|
||||
fSize = text.size();
|
||||
UText utf8UText = UTEXT_INITIALIZER;
|
||||
utext_openUTF8(&utf8UText, text.begin(), text.size(), &status);
|
||||
@ -36,13 +37,13 @@ public:
|
||||
SkDebugf("Could not create utf8UText: %s", u_errorName(status));
|
||||
return false;
|
||||
}
|
||||
fIterator = ubrk_open(type, "en", nullptr, 0, &status);
|
||||
fIterator.reset(ubrk_open(type, "en", nullptr, 0, &status));
|
||||
if (U_FAILURE(status)) {
|
||||
SkDebugf("Could not create line break iterator: %s", u_errorName(status));
|
||||
SK_ABORT("");
|
||||
}
|
||||
|
||||
ubrk_setUText(fIterator, &utf8UText, &status);
|
||||
ubrk_setUText(fIterator.get(), &utf8UText, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
SkDebugf("Could not setText on break iterator: %s", u_errorName(status));
|
||||
return false;
|
||||
@ -53,24 +54,22 @@ public:
|
||||
}
|
||||
|
||||
size_t first() {
|
||||
fPos = ubrk_first(fIterator);
|
||||
fPos = ubrk_first(fIterator.get());
|
||||
return eof() ? fSize : fPos;
|
||||
}
|
||||
|
||||
size_t next() {
|
||||
fPos = ubrk_next(fIterator);
|
||||
fPos = ubrk_next(fIterator.get());
|
||||
return eof() ? fSize : fPos;
|
||||
}
|
||||
|
||||
int32_t status() { return ubrk_getRuleStatus(fIterator); }
|
||||
int32_t status() { return ubrk_getRuleStatus(fIterator.get()); }
|
||||
|
||||
bool eof() { return fPos == icu::BreakIterator::DONE; }
|
||||
|
||||
~TextBreaker() = default;
|
||||
|
||||
private:
|
||||
std::unique_ptr<UText, SkFunctionWrapper<UText*, UText, utext_close>> fAutoClose;
|
||||
UBreakIterator* fIterator;
|
||||
std::unique_ptr<UBreakIterator, SkFunctionWrapper<void, UBreakIterator, ubrk_close>> fIterator;
|
||||
int32_t fPos;
|
||||
size_t fSize;
|
||||
};
|
||||
@ -80,6 +79,7 @@ namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
TextRange operator*(const TextRange& a, const TextRange& b) {
|
||||
if (a.start == b.start && a.end == b.end) return a;
|
||||
auto begin = SkTMax(a.start, b.start);
|
||||
auto end = SkTMin(a.end, b.end);
|
||||
return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
|
||||
@ -95,6 +95,7 @@ ParagraphImpl::ParagraphImpl(const SkString& text,
|
||||
, fTextSpan(fText.c_str(), fText.size())
|
||||
, fState(kUnknown)
|
||||
, fPicture(nullptr)
|
||||
, fStrutMetrics(false)
|
||||
, fOldWidth(0)
|
||||
, fOldHeight(0) {
|
||||
// TODO: extractStyles();
|
||||
@ -108,6 +109,7 @@ ParagraphImpl::ParagraphImpl(const std::u16string& utf16text,
|
||||
, fTextStyles(std::move(blocks))
|
||||
, fState(kUnknown)
|
||||
, fPicture(nullptr)
|
||||
, fStrutMetrics(false)
|
||||
, fOldWidth(0)
|
||||
, fOldHeight(0) {
|
||||
icu::UnicodeString unicode((UChar*)utf16text.data(), SkToS32(utf16text.size()));
|
||||
@ -139,7 +141,7 @@ void ParagraphImpl::layout(SkScalar width) {
|
||||
FontIterator font(SkMakeSpan(" "), &fFontResolver);
|
||||
// Get the font metrics
|
||||
font.consume();
|
||||
LineMetrics lineMetrics(font.currentFont());
|
||||
LineMetrics lineMetrics(font.currentFont(), paragraphStyle().getStrutStyle().getForceStrutHeight());
|
||||
// Set the important values that are not zero
|
||||
fHeight = lineMetrics.height();
|
||||
fAlphabeticBaseline = lineMetrics.alphabeticBaseline();
|
||||
@ -263,30 +265,45 @@ void ParagraphImpl::markLineBreaks() {
|
||||
}
|
||||
}
|
||||
|
||||
// Walk through all the clusters in the direction of input text
|
||||
|
||||
// Walk through all the clusters in the direction of shaped text
|
||||
Block* currentStyle = this->fTextStyles.begin();
|
||||
SkScalar shift = 0;
|
||||
for (auto& cluster : fClusters) {
|
||||
auto run = cluster.run();
|
||||
for (auto& run : fRuns) {
|
||||
|
||||
// Shift the cluster
|
||||
run->shift(&cluster, shift);
|
||||
bool soFarWhitespacesOnly = true;
|
||||
for (size_t index = 0; index != run.clusterRange().width(); ++index) {
|
||||
auto correctIndex = run.leftToRight()
|
||||
? index + run.clusterRange().start
|
||||
: run.clusterRange().end - index - 1;
|
||||
const auto cluster = &this->cluster(correctIndex);
|
||||
|
||||
// Synchronize styles (one cluster can be covered by few styles)
|
||||
while (!cluster.startsIn(currentStyle->fRange)) {
|
||||
currentStyle++;
|
||||
SkASSERT(currentStyle != this->fTextStyles.end());
|
||||
}
|
||||
// Shift the cluster (shift collected from the previous clusters)
|
||||
run.shift(cluster, shift);
|
||||
|
||||
// Take spacing styles in account
|
||||
if (currentStyle->fStyle.getWordSpacing() != 0 &&
|
||||
fParagraphStyle.getTextAlign() != TextAlign::kJustify) {
|
||||
if (cluster.isWhitespaces() && cluster.isSoftBreak()) {
|
||||
shift += run->addSpacesAtTheEnd(currentStyle->fStyle.getWordSpacing(), &cluster);
|
||||
// Synchronize styles (one cluster can be covered by few styles)
|
||||
while (!cluster->startsIn(currentStyle->fRange)) {
|
||||
currentStyle++;
|
||||
SkASSERT(currentStyle != this->fTextStyles.end());
|
||||
}
|
||||
|
||||
// Process word spacing
|
||||
if (currentStyle->fStyle.getWordSpacing() != 0/* &&
|
||||
fParagraphStyle.getTextAlign() != TextAlign::kJustify*/) {
|
||||
if (cluster->isWhitespaces() && cluster->isSoftBreak()) {
|
||||
if (!soFarWhitespacesOnly) {
|
||||
shift += run.addSpacesAtTheEnd(currentStyle->fStyle.getWordSpacing(), cluster);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Process letter spacing
|
||||
if (currentStyle->fStyle.getLetterSpacing() != 0) {
|
||||
shift += run.addSpacesEvenly(currentStyle->fStyle.getLetterSpacing(), cluster);
|
||||
}
|
||||
|
||||
if (soFarWhitespacesOnly && !cluster->isWhitespaces()) {
|
||||
soFarWhitespacesOnly = false;
|
||||
}
|
||||
}
|
||||
if (currentStyle->fStyle.getLetterSpacing() != 0) {
|
||||
shift += run->addSpacesEvenly(currentStyle->fStyle.getLetterSpacing(), &cluster);
|
||||
}
|
||||
}
|
||||
|
||||
@ -385,6 +402,8 @@ void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
|
||||
[&](TextRange text,
|
||||
TextRange textWithSpaces,
|
||||
ClusterRange clusters,
|
||||
ClusterRange clustersWithGhosts,
|
||||
SkScalar widthWithSpaces,
|
||||
size_t startPos,
|
||||
size_t endPos,
|
||||
SkVector offset,
|
||||
@ -393,7 +412,7 @@ void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
|
||||
bool addEllipsis) {
|
||||
// Add the line
|
||||
// TODO: Take in account clipped edges
|
||||
auto& line = this->addLine(offset, advance, text, textWithSpaces, clusters, metrics);
|
||||
auto& line = this->addLine(offset, advance, text, textWithSpaces, clusters, clustersWithGhosts, widthWithSpaces, metrics);
|
||||
if (addEllipsis) {
|
||||
line.createEllipsis(maxWidth, fParagraphStyle.getEllipsis(), true);
|
||||
}
|
||||
@ -409,7 +428,10 @@ void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
|
||||
void ParagraphImpl::formatLines(SkScalar maxWidth) {
|
||||
auto effectiveAlign = fParagraphStyle.effective_align();
|
||||
for (auto& line : fLines) {
|
||||
line.format(effectiveAlign, maxWidth, &line != &fLines.back());
|
||||
if (&line == &fLines.back() && effectiveAlign == TextAlign::kJustify) {
|
||||
effectiveAlign = line.assumedTextAlign();
|
||||
}
|
||||
line.format(effectiveAlign, maxWidth);
|
||||
}
|
||||
}
|
||||
|
||||
@ -426,7 +448,7 @@ void ParagraphImpl::paintLinesIntoPicture() {
|
||||
|
||||
void ParagraphImpl::resolveStrut() {
|
||||
auto strutStyle = this->paragraphStyle().getStrutStyle();
|
||||
if (!strutStyle.getStrutEnabled()) {
|
||||
if (!strutStyle.getStrutEnabled() || strutStyle.getFontSize() < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -438,18 +460,26 @@ void ParagraphImpl::resolveStrut() {
|
||||
}
|
||||
}
|
||||
if (typeface.get() == nullptr) {
|
||||
typeface = SkTypeface::MakeDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
SkFont font(typeface, strutStyle.getFontSize());
|
||||
SkFontMetrics metrics;
|
||||
font.getMetrics(&metrics);
|
||||
|
||||
fStrutMetrics = LineMetrics(metrics.fAscent * strutStyle.getHeight(),
|
||||
metrics.fDescent * strutStyle.getHeight(),
|
||||
strutStyle.getLeading() < 0
|
||||
? metrics.fLeading
|
||||
: strutStyle.getLeading() * strutStyle.getFontSize());
|
||||
if (strutStyle.getHeightOverride()) {
|
||||
auto strutHeight = metrics.fDescent - metrics.fAscent + metrics.fLeading;
|
||||
auto strutMultiplier = strutStyle.getHeight() * strutStyle.getFontSize();
|
||||
fStrutMetrics = LineMetrics(
|
||||
metrics.fAscent / strutHeight * strutMultiplier,
|
||||
metrics.fDescent / strutHeight * strutMultiplier,
|
||||
strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize());
|
||||
} else {
|
||||
fStrutMetrics = LineMetrics(
|
||||
metrics.fAscent,
|
||||
metrics.fDescent,
|
||||
strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize());
|
||||
}
|
||||
}
|
||||
|
||||
BlockRange ParagraphImpl::findAllBlocks(TextRange textRange) {
|
||||
@ -477,11 +507,13 @@ TextLine& ParagraphImpl::addLine(SkVector offset,
|
||||
TextRange text,
|
||||
TextRange textWithSpaces,
|
||||
ClusterRange clusters,
|
||||
ClusterRange clustersWithGhosts,
|
||||
SkScalar widthWithSpaces,
|
||||
LineMetrics sizes) {
|
||||
// Define a list of styles that covers the line
|
||||
auto blocks = findAllBlocks(text);
|
||||
|
||||
return fLines.emplace_back(this, offset, advance, blocks, text, textWithSpaces, clusters, sizes);
|
||||
return fLines.emplace_back(this, offset, advance, blocks, text, textWithSpaces, clusters, clustersWithGhosts, widthWithSpaces, sizes);
|
||||
}
|
||||
|
||||
// Returns a vector of bounding boxes that enclose all text between
|
||||
@ -495,21 +527,26 @@ std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
|
||||
return results;
|
||||
}
|
||||
|
||||
// Calculate the utf8 substring
|
||||
// Make sure the edges are set on the glyph edges
|
||||
TextRange text;
|
||||
const char* first = fTextSpan.begin();
|
||||
for (unsigned i = 0; i < start; ++i) {
|
||||
size_t startPos = 0;
|
||||
while (startPos < start) {
|
||||
++startPos;
|
||||
utf8_next(&first, fTextSpan.end());
|
||||
}
|
||||
const char* last = first;
|
||||
for (unsigned i = start; i < end; ++i) {
|
||||
utf8_next(&last, fTextSpan.end());
|
||||
text.start = first - fTextSpan.begin();
|
||||
size_t endPos = startPos;
|
||||
while (endPos < end) {
|
||||
++endPos;
|
||||
utf8_next(&first, fTextSpan.end());
|
||||
}
|
||||
TextRange text(first - fTextSpan.begin(), last - fTextSpan.begin());
|
||||
text.end = first - fTextSpan.begin();
|
||||
|
||||
for (auto& line : fLines) {
|
||||
auto lineText = line.textWithSpaces();
|
||||
auto intersect = lineText * text;
|
||||
if (intersect.empty() && (!lineText.empty() || lineText.start != text.start)) {
|
||||
if (intersect.empty() && lineText.start != text.start) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -520,60 +557,85 @@ std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
|
||||
before, 0, true,
|
||||
[](Run*, size_t, size_t, SkRect, SkScalar, bool) { return true; });
|
||||
}
|
||||
auto firstBox = results.size();
|
||||
line.iterateThroughRuns(intersect,
|
||||
runOffset,
|
||||
true,
|
||||
[&results, &line](Run* run, size_t pos, size_t size, SkRect clip,
|
||||
SkScalar shift, bool clippingNeeded) {
|
||||
clip.offset(line.offset());
|
||||
results.emplace_back(clip, run->leftToRight()
|
||||
? TextDirection::kLtr
|
||||
: TextDirection::kRtl);
|
||||
return true;
|
||||
});
|
||||
auto firstBoxOnTheLine = results.size();
|
||||
auto paragraphTextDirection = paragraphStyle().getTextDirection();
|
||||
line.iterateThroughRuns(
|
||||
intersect,
|
||||
runOffset,
|
||||
paragraphStyle().getTextAlign() != TextAlign::kJustify || &line == &fLines.back(),
|
||||
[&results, &line, rectHeightStyle, this, paragraphTextDirection](Run* run, size_t pos, size_t size, SkRect clip,
|
||||
SkScalar shift, bool clippingNeeded) {
|
||||
|
||||
SkRect trailingSpaces = SkRect::MakeEmpty();
|
||||
auto spaces = clip.left() >= line.width() ? 0 : clip.right() - line.width();
|
||||
if (spaces > 0) {
|
||||
// There are trailing spaces; let's make a special box for them
|
||||
if (paragraphTextDirection == TextDirection::kRtl) {
|
||||
trailingSpaces.fLeft = clip.fLeft - spaces;
|
||||
trailingSpaces.fRight = clip.fLeft;
|
||||
clip.fRight = line.width();
|
||||
} else if (clip.fRight < line.widthWithSpaces()){
|
||||
trailingSpaces.fLeft = line.width();
|
||||
trailingSpaces.fRight = clip.fRight;
|
||||
clip.fRight = line.width();
|
||||
}
|
||||
trailingSpaces.fTop = clip.fTop;
|
||||
trailingSpaces.fBottom = clip.fBottom;
|
||||
trailingSpaces.offset(line.offset());
|
||||
}
|
||||
|
||||
clip.offset(line.offset());
|
||||
|
||||
if (rectHeightStyle != RectHeightStyle::kTight) {
|
||||
// Align all the rectangles
|
||||
for (auto i = firstBox; i < results.size(); ++i) {
|
||||
auto& rect = results[i].rect;
|
||||
if (rectHeightStyle == RectHeightStyle::kMax) {
|
||||
rect.fTop = line.offset().fY + line.roundingDelta();
|
||||
rect.fBottom = line.offset().fY + line.height();
|
||||
// Mimicking TxtLib: clip.fTop = line.offset().fY + line.roundingDelta();
|
||||
clip.fBottom = line.offset().fY + line.height();
|
||||
|
||||
} else if (rectHeightStyle == RectHeightStyle::kIncludeLineSpacingTop) {
|
||||
rect.fTop = line.offset().fY;
|
||||
|
||||
if (&line != &fLines.front()) {
|
||||
clip.fTop -= line.sizes().runTop(run);
|
||||
}
|
||||
clip.fBottom -= line.sizes().runTop(run);
|
||||
} else if (rectHeightStyle == RectHeightStyle::kIncludeLineSpacingMiddle) {
|
||||
rect.fTop -= (rect.fTop - line.offset().fY) / 2;
|
||||
rect.fBottom += (line.offset().fY + line.height() - rect.fBottom) / 2;
|
||||
|
||||
if (&line != &fLines.front()) {
|
||||
clip.fTop -= line.sizes().runTop(run) / 2;
|
||||
}
|
||||
if (&line == &fLines.back()) {
|
||||
clip.fBottom -= line.sizes().runTop(run);
|
||||
} else {
|
||||
clip.fBottom -= line.sizes().runTop(run) / 2;
|
||||
}
|
||||
} else if (rectHeightStyle == RectHeightStyle::kIncludeLineSpacingBottom) {
|
||||
rect.fBottom = line.offset().fY + line.height();
|
||||
if (&line == &fLines.back()) {
|
||||
clip.fBottom -= line.sizes().runTop(run);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Just leave the boxes the way they are
|
||||
}
|
||||
|
||||
results.emplace_back(
|
||||
clip, run->leftToRight() ? TextDirection::kLtr : TextDirection::kRtl);
|
||||
|
||||
if (trailingSpaces.width() > 0) {
|
||||
results.emplace_back(trailingSpaces, paragraphTextDirection);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (rectWidthStyle == RectWidthStyle::kMax) {
|
||||
for (auto& i = firstBox; i < results.size(); ++i) {
|
||||
auto clip = results[i].rect;
|
||||
auto dir = results[i].direction;
|
||||
if (clip.fLeft > line.offset().fX) {
|
||||
SkRect left = SkRect::MakeXYWH(0, clip.fTop, clip.fLeft - line.offset().fX,
|
||||
clip.fBottom);
|
||||
results.insert(results.begin() + i, {left, dir});
|
||||
++i;
|
||||
}
|
||||
if (clip.fRight < line.offset().fX + line.width()) {
|
||||
SkRect right = SkRect::MakeXYWH(clip.fRight - line.offset().fX,
|
||||
clip.fTop,
|
||||
line.width() - (clip.fRight - line.offset().fX),
|
||||
clip.fBottom);
|
||||
results.insert(results.begin() + i, {right, dir});
|
||||
++i;
|
||||
}
|
||||
// Align the very left/right box horizontally
|
||||
auto lineStart = line.offset().fX;
|
||||
auto lineEnd = line.offset().fX + line.width();
|
||||
auto left = results.front();
|
||||
auto right = results.back();
|
||||
if (left.rect.fLeft > lineStart && left.direction == TextDirection::kRtl) {
|
||||
left.rect.fRight = left.rect.fLeft;
|
||||
left.rect.fLeft = 0;
|
||||
results.insert(results.begin() + firstBoxOnTheLine + 1, left);
|
||||
}
|
||||
if (right.direction == TextDirection::kLtr &&
|
||||
right.rect.fRight >= lineEnd && right.rect.fRight < this->fMaxWidthWithTrailingSpaces) {
|
||||
right.rect.fLeft = right.rect.fRight;
|
||||
right.rect.fRight = this->fMaxWidthWithTrailingSpaces;
|
||||
results.emplace_back(right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -601,7 +663,7 @@ PositionWithAffinity ParagraphImpl::getGlyphPositionAtCoordinate(SkScalar dx, Sk
|
||||
if (dx >= clip.fRight) {
|
||||
// We have to keep looking but just in case keep the last one as the closes
|
||||
// so far
|
||||
result = {SkToS32(run->fClusterIndexes[pos + size]), kUpstream};
|
||||
result = {SkToS32(run->fClusterIndexes[pos + size - 1]) + 1, kUpstream};
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -614,9 +676,7 @@ PositionWithAffinity ParagraphImpl::getGlyphPositionAtCoordinate(SkScalar dx, Sk
|
||||
found = i;
|
||||
}
|
||||
|
||||
if (found == pos) {
|
||||
result = {SkToS32(run->fClusterIndexes[found]), kDownstream};
|
||||
} else if (found == pos + size - 1) {
|
||||
if (found == pos + size - 1) {
|
||||
result = {SkToS32(run->fClusterIndexes[found]), kUpstream};
|
||||
} else {
|
||||
auto center = (run->positionX(found + 1) + run->positionX(found)) / 2;
|
||||
|
@ -67,7 +67,8 @@ public:
|
||||
size_t lineNumber() override { return fLines.size(); }
|
||||
|
||||
TextLine& addLine(SkVector offset, SkVector advance, TextRange text, TextRange textWithSpaces,
|
||||
ClusterRange clusters, LineMetrics sizes);
|
||||
ClusterRange clusters, ClusterRange clustersWithGhosts, SkScalar AddLineToParagraph,
|
||||
LineMetrics sizes);
|
||||
|
||||
SkSpan<const char> text() const { return fTextSpan; }
|
||||
InternalState state() const { return fState; }
|
||||
@ -77,7 +78,7 @@ public:
|
||||
return SkSpan<Block>(fTextStyles.data(), fTextStyles.size());
|
||||
}
|
||||
SkSpan<TextLine> lines() { return SkSpan<TextLine>(fLines.data(), fLines.size()); }
|
||||
ParagraphStyle paragraphStyle() const { return fParagraphStyle; }
|
||||
const ParagraphStyle& paragraphStyle() const { return fParagraphStyle; }
|
||||
SkSpan<Cluster> clusters() { return SkSpan<Cluster>(fClusters.begin(), fClusters.size()); }
|
||||
sk_sp<FontCollection> fontCollection() const { return fFontCollection; }
|
||||
void formatLines(SkScalar maxWidth);
|
||||
@ -101,6 +102,9 @@ public:
|
||||
bool strutForceHeight() const {
|
||||
return paragraphStyle().getStrutStyle().getForceStrutHeight();
|
||||
}
|
||||
bool strutHeightOverride() const {
|
||||
return paragraphStyle().getStrutStyle().getHeightOverride();
|
||||
}
|
||||
LineMetrics strutMetrics() const { return fStrutMetrics; }
|
||||
|
||||
Measurement measurement() {
|
||||
@ -149,6 +153,8 @@ private:
|
||||
friend class ParagraphCacheValue;
|
||||
friend class ParagraphCache;
|
||||
|
||||
friend class TextWrapper;
|
||||
|
||||
BlockRange findAllBlocks(TextRange textRange);
|
||||
void extractStyles();
|
||||
|
||||
@ -177,6 +183,7 @@ private:
|
||||
|
||||
SkScalar fOldWidth;
|
||||
SkScalar fOldHeight;
|
||||
SkScalar fMaxWidthWithTrailingSpaces;
|
||||
};
|
||||
} // namespace textlayout
|
||||
} // namespace skia
|
||||
|
@ -11,8 +11,8 @@ StrutStyle::StrutStyle() {
|
||||
fFontSize = 14;
|
||||
fHeight = 1;
|
||||
fLeading = -1;
|
||||
fForceStrutHeight = false;
|
||||
fStrutEnabled = false;
|
||||
fForceHeight = false;
|
||||
fEnabled = false;
|
||||
}
|
||||
|
||||
ParagraphStyle::ParagraphStyle() {
|
||||
|
@ -4,6 +4,15 @@
|
||||
#include "include/core/SkFontMetrics.h"
|
||||
#include "modules/skparagraph/src/ParagraphImpl.h"
|
||||
#include <algorithm>
|
||||
#include "src/utils/SkUTF.h"
|
||||
|
||||
namespace {
|
||||
|
||||
SkUnichar utf8_next(const char** ptr, const char* end) {
|
||||
SkUnichar val = SkUTF::NextUTF8(ptr, end);
|
||||
return val < 0 ? 0xFFFD : val;
|
||||
}
|
||||
}
|
||||
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
@ -198,12 +207,14 @@ void Run::shift(const Cluster* cluster, SkScalar offset) {
|
||||
}
|
||||
|
||||
void Cluster::setIsWhiteSpaces() {
|
||||
auto text = fMaster->text();
|
||||
auto pos = fTextRange.end;
|
||||
while (--pos >= fTextRange.start) {
|
||||
auto ch = text[pos];
|
||||
if (!u_isspace(ch) && u_charType(ch) != U_CONTROL_CHAR &&
|
||||
u_charType(ch) != U_NON_SPACING_MARK) {
|
||||
|
||||
fWhiteSpaces = false;
|
||||
|
||||
auto span = fMaster->text(fTextRange);
|
||||
const char* ch = span.begin();
|
||||
while (ch < span.end()) {
|
||||
auto unichar = utf8_next(&ch, span.end());
|
||||
if (!u_isWhitespace(unichar)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +65,30 @@ public:
|
||||
SkScalar ascent() const { return fFontMetrics.fAscent; }
|
||||
SkScalar descent() const { return fFontMetrics.fDescent; }
|
||||
SkScalar leading() const { return fFontMetrics.fLeading; }
|
||||
SkScalar correctAscent() const {
|
||||
|
||||
if (fHeightMultiplier == 0 || fHeightMultiplier == 1) {
|
||||
return fFontMetrics.fAscent;
|
||||
}
|
||||
return fFontMetrics.fAscent * fHeightMultiplier * fFont.getSize() /
|
||||
(fFontMetrics.fDescent - fFontMetrics.fAscent + fFontMetrics.fLeading);
|
||||
}
|
||||
SkScalar correctDescent() const {
|
||||
|
||||
if (fHeightMultiplier == 0 || fHeightMultiplier == 1) {
|
||||
return fFontMetrics.fDescent;
|
||||
}
|
||||
return fFontMetrics.fDescent * fHeightMultiplier * fFont.getSize() /
|
||||
(fFontMetrics.fDescent - fFontMetrics.fAscent + fFontMetrics.fLeading);
|
||||
}
|
||||
SkScalar correctLeading() const {
|
||||
|
||||
if (fHeightMultiplier == 0 || fHeightMultiplier == 1) {
|
||||
return fFontMetrics.fAscent;
|
||||
}
|
||||
return fFontMetrics.fLeading * fHeightMultiplier * fFont.getSize() /
|
||||
(fFontMetrics.fDescent - fFontMetrics.fAscent + fFontMetrics.fLeading);
|
||||
}
|
||||
const SkFont& font() const { return fFont; }
|
||||
bool leftToRight() const { return fBidiLevel % 2 == 0; }
|
||||
size_t index() const { return fIndex; }
|
||||
@ -84,7 +108,12 @@ public:
|
||||
SkScalar addSpacesEvenly(SkScalar space, Cluster* cluster);
|
||||
void shift(const Cluster* cluster, SkScalar offset);
|
||||
|
||||
SkScalar calculateHeight() const { return fFontMetrics.fDescent - fFontMetrics.fAscent; }
|
||||
SkScalar calculateHeight() const {
|
||||
if (fHeightMultiplier == 0 || fHeightMultiplier == 1) {
|
||||
return fFontMetrics.fDescent - fFontMetrics.fAscent;
|
||||
}
|
||||
return fHeightMultiplier * fFont.getSize();
|
||||
}
|
||||
SkScalar calculateWidth(size_t start, size_t end, bool clip) const;
|
||||
|
||||
void copyTo(SkTextBlobBuilder& builder, size_t pos, size_t size, SkVector offset) const;
|
||||
@ -98,15 +127,16 @@ public:
|
||||
void iterateThroughClustersInTextOrder(const ClusterVisitor& visitor);
|
||||
|
||||
std::tuple<bool, ClusterIndex, ClusterIndex> findLimitingClusters(TextRange);
|
||||
SkSpan<const SkGlyphID> glyphs() {
|
||||
SkSpan<const SkGlyphID> glyphs() const {
|
||||
return SkSpan<const SkGlyphID>(fGlyphs.begin(), fGlyphs.size());
|
||||
}
|
||||
SkSpan<const SkPoint> positions() {
|
||||
SkSpan<const SkPoint> positions() const {
|
||||
return SkSpan<const SkPoint>(fPositions.begin(), fPositions.size());
|
||||
}
|
||||
SkSpan<const uint32_t> clusterIndexes() {
|
||||
SkSpan<const uint32_t> clusterIndexes() const {
|
||||
return SkSpan<const uint32_t>(fClusterIndexes.begin(), fClusterIndexes.size());
|
||||
}
|
||||
SkSpan<const SkScalar> offsets() const { return SkSpan<const SkScalar>(fOffsets.begin(), fOffsets.size()); }
|
||||
|
||||
private:
|
||||
friend class ParagraphImpl;
|
||||
@ -234,15 +264,17 @@ private:
|
||||
|
||||
class LineMetrics {
|
||||
public:
|
||||
LineMetrics() { clean(); }
|
||||
|
||||
LineMetrics(SkScalar a, SkScalar d, SkScalar l) {
|
||||
LineMetrics() { clean(); }
|
||||
LineMetrics(bool forceStrut) : fForceStrut(forceStrut) { clean(); }
|
||||
|
||||
LineMetrics(SkScalar a, SkScalar d, SkScalar l) : fForceStrut(false) {
|
||||
fAscent = a;
|
||||
fDescent = d;
|
||||
fLeading = l;
|
||||
}
|
||||
|
||||
LineMetrics(const SkFont& font) {
|
||||
LineMetrics(const SkFont& font, bool forceStrut) : fForceStrut(forceStrut) {
|
||||
SkFontMetrics metrics;
|
||||
font.getMetrics(&metrics);
|
||||
fAscent = metrics.fAscent;
|
||||
@ -251,9 +283,21 @@ public:
|
||||
}
|
||||
|
||||
void add(Run* run) {
|
||||
fAscent = SkTMin(fAscent, run->ascent() * run->lineHeight());
|
||||
fDescent = SkTMax(fDescent, run->descent() * run->lineHeight());
|
||||
fLeading = SkTMax(fLeading, run->leading() * run->lineHeight());
|
||||
|
||||
if (fForceStrut) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (run->lineHeight() == 0 || run->lineHeight() == 1) {
|
||||
fAscent = SkTMin(fAscent, run->ascent());
|
||||
fDescent = SkTMax(fDescent, run->descent());
|
||||
fLeading = SkTMax(fLeading, run->leading());
|
||||
} else {
|
||||
fAscent = SkTMin(fAscent, run->correctAscent());
|
||||
fDescent = SkTMax(fDescent, run->correctDescent());
|
||||
fLeading = SkTMax(fLeading, run->correctLeading());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void add(LineMetrics other) {
|
||||
@ -269,16 +313,10 @@ public:
|
||||
|
||||
SkScalar delta() const { return height() - ideographicBaseline(); }
|
||||
|
||||
void updateLineMetrics(LineMetrics& metrics, bool forceHeight) {
|
||||
if (forceHeight) {
|
||||
metrics.fAscent = fAscent;
|
||||
metrics.fDescent = fDescent;
|
||||
metrics.fLeading = fLeading;
|
||||
} else {
|
||||
metrics.fAscent = SkTMin(metrics.fAscent, fAscent);
|
||||
metrics.fDescent = SkTMax(metrics.fDescent, fDescent);
|
||||
metrics.fLeading = SkTMax(metrics.fLeading, fLeading);
|
||||
}
|
||||
void updateLineMetrics(LineMetrics& metrics) {
|
||||
metrics.fAscent = SkTMin(metrics.fAscent, fAscent);
|
||||
metrics.fDescent = SkTMax(metrics.fDescent, fDescent);
|
||||
metrics.fLeading = SkTMax(metrics.fLeading, fLeading);
|
||||
}
|
||||
|
||||
SkScalar runTop(Run* run) const { return fLeading / 2 - fAscent + run->ascent() + delta(); }
|
||||
@ -294,6 +332,7 @@ private:
|
||||
SkScalar fAscent;
|
||||
SkScalar fDescent;
|
||||
SkScalar fLeading;
|
||||
bool fForceStrut;
|
||||
};
|
||||
} // namespace textlayout
|
||||
} // namespace skia
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
// TODO: deal with all the intersection functionality
|
||||
int32_t intersectedSize(TextRange a, TextRange b) {
|
||||
if (a.empty() || b.empty()) {
|
||||
return -1;
|
||||
@ -22,9 +22,10 @@ int32_t intersectedSize(TextRange a, TextRange b) {
|
||||
}
|
||||
|
||||
TextRange intersected(const TextRange& a, const TextRange& b) {
|
||||
if (a.start == b.start && a.end == b.end) return a;
|
||||
auto begin = SkTMax(a.start, b.start);
|
||||
auto end = SkTMin(a.end, b.end);
|
||||
return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
|
||||
return end >= begin ? TextRange(begin, end) : EMPTY_TEXT;
|
||||
}
|
||||
|
||||
SkTHashMap<SkFont, Run> TextLine::fEllipsisCache;
|
||||
@ -36,25 +37,29 @@ TextLine::TextLine(ParagraphImpl* master,
|
||||
TextRange text,
|
||||
TextRange textWithSpaces,
|
||||
ClusterRange clusters,
|
||||
ClusterRange clustersWithGhosts,
|
||||
SkScalar widthWithSpaces,
|
||||
LineMetrics sizes)
|
||||
: fMaster(master)
|
||||
, fBlockRange(blocks)
|
||||
, fTextRange(text)
|
||||
, fTextWithWhitespacesRange(textWithSpaces)
|
||||
, fClusterRange(clusters)
|
||||
, fGhostClusterRange(clustersWithGhosts)
|
||||
, fLogical()
|
||||
, fAdvance(advance)
|
||||
, fOffset(offset)
|
||||
, fShift(0.0)
|
||||
, fWidthWithSpaces(widthWithSpaces)
|
||||
, fEllipsis(nullptr)
|
||||
, fSizes(sizes)
|
||||
, fHasBackground(false)
|
||||
, fHasShadows(false)
|
||||
, fHasDecorations(false) {
|
||||
// Reorder visual runs
|
||||
auto start = master->clusters().begin() + fClusterRange.start;
|
||||
auto end = master->clusters().begin() + fClusterRange.end - 1;
|
||||
size_t numRuns = end->runIndex() - start->runIndex() + 1;
|
||||
auto& start = master->cluster(fGhostClusterRange.start);
|
||||
auto& end = master->cluster(fGhostClusterRange.end - 1);
|
||||
size_t numRuns = end.runIndex() - start.runIndex() + 1;
|
||||
|
||||
for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
|
||||
auto b = fMaster->styles().begin() + index;
|
||||
@ -71,7 +76,7 @@ TextLine::TextLine(ParagraphImpl* master,
|
||||
|
||||
// Get the logical order
|
||||
std::vector<UBiDiLevel> runLevels;
|
||||
for (auto runIndex = start->runIndex(); runIndex <= end->runIndex(); ++runIndex) {
|
||||
for (auto runIndex = start.runIndex(); runIndex <= end.runIndex(); ++runIndex) {
|
||||
auto& run = fMaster->run(runIndex);
|
||||
runLevels.emplace_back(run.fBidiLevel);
|
||||
}
|
||||
@ -79,7 +84,7 @@ TextLine::TextLine(ParagraphImpl* master,
|
||||
std::vector<int32_t> logicalOrder(numRuns);
|
||||
ubidi_reorderVisual(runLevels.data(), SkToU32(numRuns), logicalOrder.data());
|
||||
|
||||
auto firstRunIndex = start->runIndex();
|
||||
auto firstRunIndex = start.runIndex();
|
||||
for (auto index : logicalOrder) {
|
||||
fLogical.push_back(firstRunIndex + index);
|
||||
}
|
||||
@ -126,13 +131,13 @@ void TextLine::paint(SkCanvas* textCanvas) {
|
||||
textCanvas->restore();
|
||||
}
|
||||
|
||||
void TextLine::format(TextAlign effectiveAlign, SkScalar maxWidth, bool notLastLine) {
|
||||
void TextLine::format(TextAlign effectiveAlign, SkScalar maxWidth) {
|
||||
SkScalar delta = maxWidth - this->width();
|
||||
if (delta <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (effectiveAlign == TextAlign::kJustify && notLastLine) {
|
||||
if (effectiveAlign == TextAlign::kJustify) {
|
||||
this->justify(maxWidth);
|
||||
} else if (effectiveAlign == TextAlign::kRight) {
|
||||
fShift = delta;
|
||||
@ -141,6 +146,18 @@ void TextLine::format(TextAlign effectiveAlign, SkScalar maxWidth, bool notLastL
|
||||
}
|
||||
}
|
||||
|
||||
TextAlign TextLine::assumedTextAlign() const {
|
||||
if (this->fMaster->paragraphStyle().getTextAlign() != TextAlign::kJustify) {
|
||||
return this->fMaster->paragraphStyle().getTextAlign();
|
||||
}
|
||||
if (fClusterRange.empty()) {
|
||||
return TextAlign::kLeft;
|
||||
} else {
|
||||
auto run = this->fMaster->cluster(fClusterRange.end - 1).run();
|
||||
return run->leftToRight() ? TextAlign::kLeft : TextAlign::kRight;
|
||||
}
|
||||
}
|
||||
|
||||
void TextLine::scanStyles(StyleType style, const StyleVisitor& visitor) {
|
||||
if (this->empty()) {
|
||||
return;
|
||||
@ -257,14 +274,14 @@ SkScalar TextLine::paintDecorations(SkCanvas* canvas, TextRange textRange,
|
||||
}
|
||||
|
||||
for (auto decoration : AllTextDecorations) {
|
||||
if (style.getDecorationType() && decoration == 0) {
|
||||
if ((style.getDecorationType() & decoration) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SkScalar thickness = style.getDecorationThicknessMultiplier();
|
||||
//
|
||||
SkScalar position = 0;
|
||||
switch (style.getDecorationType()) {
|
||||
switch (decoration) {
|
||||
case TextDecoration::kUnderline:
|
||||
position = -run->ascent() + thickness;
|
||||
break;
|
||||
@ -277,6 +294,7 @@ SkScalar TextLine::paintDecorations(SkCanvas* canvas, TextRange textRange,
|
||||
}
|
||||
default:
|
||||
// TODO: can we actually get here?
|
||||
SkASSERT(false);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -504,7 +522,7 @@ Run* TextLine::shapeEllipsis(const SkString& ellipsis, Run* run) {
|
||||
}
|
||||
|
||||
SkRect TextLine::measureTextInsideOneRun(
|
||||
TextRange textRange, Run* run, size_t& pos, size_t& size, bool& clippingNeeded) const {
|
||||
TextRange textRange, Run* run, size_t& pos, size_t& size, bool includeGhostSpaces, bool& clippingNeeded) const {
|
||||
|
||||
SkASSERT(intersectedSize(run->textRange(), textRange) >= 0);
|
||||
|
||||
@ -528,7 +546,8 @@ SkRect TextLine::measureTextInsideOneRun(
|
||||
// EOL (when we expect the last cluster clipped without any spaces)
|
||||
// Anything else (when we want the cluster width contain all the spaces -
|
||||
// coming from letter spacing or word spacing or justification)
|
||||
bool needsClipping = (run->leftToRight() ? endIndex : startIndex) == fClusterRange.end - 1;
|
||||
auto range = includeGhostSpaces ? fGhostClusterRange : fClusterRange;
|
||||
bool needsClipping = (run->leftToRight() ? endIndex == range.end - 1 : startIndex == range.end);
|
||||
SkRect clip =
|
||||
SkRect::MakeXYWH(run->positionX(start->startPos()) - run->positionX(0),
|
||||
sizes().runTop(run),
|
||||
@ -574,15 +593,16 @@ void TextLine::iterateThroughClustersInGlyphsOrder(bool reverse,
|
||||
// Walk through the runs in the logical order
|
||||
SkScalar TextLine::iterateThroughRuns(TextRange textRange,
|
||||
SkScalar runOffset,
|
||||
bool includeEmptyText,
|
||||
bool includeGhostWhitespaces,
|
||||
const RunVisitor& visitor) const {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
|
||||
SkScalar width = 0;
|
||||
for (auto& runIndex : fLogical) {
|
||||
auto run = this->fMaster->runs().begin() + runIndex;
|
||||
const auto run = &this->fMaster->run(runIndex);
|
||||
// Only skip the text if it does not even touch the run
|
||||
if (intersectedSize(run->textRange(), textRange) < 0) {
|
||||
auto intersection = intersectedSize(run->textRange(), textRange);
|
||||
if (intersection < 0 || (intersection == 0 && textRange.end != run->textRange().end)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -591,21 +611,25 @@ SkScalar TextLine::iterateThroughRuns(TextRange textRange,
|
||||
continue;
|
||||
}
|
||||
|
||||
// Measure the text
|
||||
size_t pos;
|
||||
size_t size;
|
||||
bool clippingNeeded;
|
||||
SkRect clip = this->measureTextInsideOneRun(intersect, run, pos, size, clippingNeeded);
|
||||
SkRect clip = this->measureTextInsideOneRun(intersect, run, pos, size, includeGhostWhitespaces, clippingNeeded);
|
||||
if (clip.height() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Clip the text
|
||||
auto shift = runOffset - clip.fLeft;
|
||||
clip.offset(shift, 0);
|
||||
if (clip.fRight > fAdvance.fX) {
|
||||
clip.fRight = fAdvance.fX;
|
||||
clippingNeeded = true; // Correct the clip in case there was an ellipsis
|
||||
} else if (runIndex == fLogical.back() && this->ellipsis() != nullptr) {
|
||||
clippingNeeded = true; // To avoid trouble
|
||||
if (includeGhostWhitespaces) {
|
||||
clippingNeeded = false;
|
||||
} else {
|
||||
clippingNeeded = true;
|
||||
if (clip.fRight > fAdvance.fX) {
|
||||
clip.fRight = fAdvance.fX;
|
||||
}
|
||||
}
|
||||
|
||||
if (!visitor(run, pos, size, clip, shift - run->positionX(0), clippingNeeded)) {
|
||||
|
@ -25,6 +25,8 @@ public:
|
||||
TextRange text,
|
||||
TextRange textWithSpaces,
|
||||
ClusterRange clusters,
|
||||
ClusterRange clustersWithGhosts,
|
||||
SkScalar widthWithSpaces,
|
||||
LineMetrics sizes);
|
||||
|
||||
inline void setMaster(ParagraphImpl* master) { fMaster = master; }
|
||||
@ -40,6 +42,7 @@ public:
|
||||
return fAdvance.fX + (fEllipsis != nullptr ? fEllipsis->fAdvance.fX : 0);
|
||||
}
|
||||
inline SkScalar shift() const { return fShift; }
|
||||
SkScalar widthWithSpaces() const { return fWidthWithSpaces; }
|
||||
SkVector offset() const;
|
||||
|
||||
inline SkScalar alphabeticBaseline() const { return fSizes.alphabeticBaseline(); }
|
||||
@ -55,13 +58,13 @@ public:
|
||||
SkScalar shift, bool clippingNeeded)>;
|
||||
SkScalar iterateThroughRuns(TextRange textRange,
|
||||
SkScalar offsetX,
|
||||
bool includeEmptyText,
|
||||
bool includeGhostWhitespaces,
|
||||
const RunVisitor& visitor) const;
|
||||
|
||||
using ClustersVisitor = std::function<bool(const Cluster* cluster, ClusterIndex index)>;
|
||||
void iterateThroughClustersInGlyphsOrder(bool reverse, const ClustersVisitor& visitor) const;
|
||||
|
||||
void format(TextAlign effectiveAlign, SkScalar maxWidth, bool last);
|
||||
void format(TextAlign effectiveAlign, SkScalar maxWidth);
|
||||
void paint(SkCanvas* canvas);
|
||||
|
||||
void createEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool ltr);
|
||||
@ -70,6 +73,8 @@ public:
|
||||
void scanStyles(StyleType style, const StyleVisitor& visitor);
|
||||
void scanRuns(const RunVisitor& visitor);
|
||||
|
||||
TextAlign assumedTextAlign() const;
|
||||
|
||||
private:
|
||||
|
||||
Run* shapeEllipsis(const SkString& ellipsis, Run* run);
|
||||
@ -79,6 +84,7 @@ private:
|
||||
Run* run,
|
||||
size_t& pos,
|
||||
size_t& size,
|
||||
bool includeGhostSpaces,
|
||||
bool& clippingNeeded) const;
|
||||
|
||||
SkScalar paintText(SkCanvas* canvas, TextRange textRange, const TextStyle& style, SkScalar offsetX) const;
|
||||
@ -101,11 +107,13 @@ private:
|
||||
TextRange fTextRange;
|
||||
TextRange fTextWithWhitespacesRange;
|
||||
ClusterRange fClusterRange;
|
||||
ClusterRange fGhostClusterRange;
|
||||
|
||||
SkTArray<size_t, true> fLogical;
|
||||
SkVector fAdvance; // Text size
|
||||
SkVector fOffset; // Text position
|
||||
SkScalar fShift; // Left right
|
||||
SkScalar fWidthWithSpaces;
|
||||
std::shared_ptr<Run> fEllipsis; // In case the line ends with the ellipsis
|
||||
LineMetrics fSizes; // Line metrics as a max of all run metrics
|
||||
bool fHasBackground;
|
||||
|
@ -21,6 +21,7 @@ TextStyle::TextStyle() : fFontStyle() {
|
||||
fLetterSpacing = 0.0;
|
||||
fWordSpacing = 0.0;
|
||||
fHeight = 1.0;
|
||||
fHeightOverride = false;
|
||||
fHasBackground = false;
|
||||
fHasForeground = false;
|
||||
fTextBaseline = TextBaseline::kAlphabetic;
|
||||
@ -121,5 +122,18 @@ bool TextStyle::matchOneAttribute(StyleType styleType, const TextStyle& other) c
|
||||
}
|
||||
}
|
||||
|
||||
void TextStyle::getFontMetrics(SkFontMetrics* metrics) const {
|
||||
SkFont font(fTypeface, fFontSize);
|
||||
font.getMetrics(metrics);
|
||||
if (fHeight == 0 || fHeight == 1) {
|
||||
metrics->fAscent = (metrics->fAscent - metrics->fLeading / 2);
|
||||
metrics->fDescent = (metrics->fDescent + metrics->fLeading / 2);
|
||||
} else {
|
||||
auto height = metrics->fDescent - metrics->fAscent + metrics->fLeading;
|
||||
metrics->fAscent = (metrics->fAscent - metrics->fLeading / 2) * height / fHeight;
|
||||
metrics->fDescent = (metrics->fDescent + metrics->fLeading / 2) * height / fHeight;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace textlayout
|
||||
} // namespace skia
|
||||
|
@ -79,16 +79,27 @@ void TextWrapper::moveForward() {
|
||||
}
|
||||
|
||||
// Special case for start/end cluster since they can be clipped
|
||||
void TextWrapper::trimEndSpaces() {
|
||||
void TextWrapper::trimEndSpaces(TextAlign align) {
|
||||
// Remember the breaking position
|
||||
fEndLine.saveBreak();
|
||||
// Move the end of the line to the left
|
||||
// Skip all space cluster at the end
|
||||
//bool left = align == TextAlign::kStart || align == TextAlign::kLeft;
|
||||
bool right = align == TextAlign::kRight || align == TextAlign::kEnd;
|
||||
for (auto cluster = fEndLine.endCluster();
|
||||
cluster >= fEndLine.startCluster() && cluster->isWhitespaces();
|
||||
--cluster) {
|
||||
fEndLine.trim(cluster);
|
||||
if ((/*left && */cluster->run()->leftToRight()) ||
|
||||
(right && !cluster->run()->leftToRight()) ||
|
||||
align == TextAlign::kJustify || align == TextAlign::kCenter) {
|
||||
fEndLine.trim(cluster);
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!right) {
|
||||
fEndLine.trim();
|
||||
}
|
||||
fEndLine.trim();
|
||||
}
|
||||
|
||||
SkScalar TextWrapper::getClustersTrimmedWidth() {
|
||||
@ -105,26 +116,26 @@ SkScalar TextWrapper::getClustersTrimmedWidth() {
|
||||
}
|
||||
|
||||
// Trim the beginning spaces in case of soft line break
|
||||
void TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
|
||||
// Restore the breaking position
|
||||
fEndLine.restoreBreak();
|
||||
fEndLine.nextPos();
|
||||
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
|
||||
fEndLine.startFrom(fEndLine.endCluster(), 0);
|
||||
return;
|
||||
}
|
||||
if (fEndLine.endPos() != 0) {
|
||||
// Clipping
|
||||
fEndLine.startFrom(fEndLine.endCluster(), fEndLine.endPos());
|
||||
return;
|
||||
auto width = fEndLine.width();
|
||||
auto cluster = fEndLine.endCluster() + 1;
|
||||
while (cluster < fEndLine.breakCluster() && cluster->isWhitespaces()) {
|
||||
width += cluster->width();
|
||||
++cluster;
|
||||
}
|
||||
return { fEndLine.breakCluster() + 1, 0, width };
|
||||
}
|
||||
|
||||
auto cluster = fEndLine.endCluster();
|
||||
auto width = fEndLine.width();
|
||||
auto cluster = fEndLine.breakCluster() + 1;
|
||||
while (cluster < endOfClusters && cluster->isWhitespaces()) {
|
||||
width += cluster->width();
|
||||
++cluster;
|
||||
}
|
||||
fEndLine.startFrom(cluster, 0);
|
||||
return { cluster, 0, width };
|
||||
}
|
||||
|
||||
void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
|
||||
@ -132,13 +143,15 @@ void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
|
||||
const AddLineToParagraph& addLine) {
|
||||
auto span = parent->clusters();
|
||||
auto maxLines = parent->paragraphStyle().getMaxLines();
|
||||
auto ellipsisStr = parent->paragraphStyle().getEllipsis();
|
||||
auto& ellipsisStr = parent->paragraphStyle().getEllipsis();
|
||||
auto align = parent->paragraphStyle().getTextAlign();
|
||||
|
||||
fHeight = 0;
|
||||
fMinIntrinsicWidth = 0;
|
||||
fMaxIntrinsicWidth = 0;
|
||||
fEndLine = TextStretch(span.begin(), span.begin());
|
||||
auto end = &span.back();
|
||||
fEndLine = TextStretch(span.begin(), span.begin(), parent->strutForceHeight());
|
||||
auto end = span.end() - 1;
|
||||
auto start = span.begin();
|
||||
while (fEndLine.endCluster() != end) {
|
||||
reset();
|
||||
|
||||
@ -146,7 +159,13 @@ void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
|
||||
moveForward();
|
||||
|
||||
// Do not trim end spaces on the naturally last line of the left aligned text
|
||||
trimEndSpaces();
|
||||
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) = trimStartSpaces(end);
|
||||
|
||||
auto lastLine = maxLines == std::numeric_limits<size_t>::max() ||
|
||||
fLineNumber >= maxLines;
|
||||
@ -159,15 +178,20 @@ void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
|
||||
// TODO: perform ellipsis work here
|
||||
if (parent->strutEnabled()) {
|
||||
// Make sure font metrics are not less than the strut
|
||||
parent->strutMetrics().updateLineMetrics(fEndLine.metrics(),
|
||||
parent->strutForceHeight());
|
||||
parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
|
||||
}
|
||||
fMaxIntrinsicWidth = SkMaxScalar(fMaxIntrinsicWidth, fEndLine.width());
|
||||
// TODO: keep start/end/break info for text and runs but in a better way that below
|
||||
TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
|
||||
TextRange textWithSpaces(fEndLine.startCluster()->textRange().start, fEndLine.breakCluster()->textRange().end);
|
||||
ClusterRange clusters(fEndLine.startCluster() - parent->clusters().begin(), fEndLine.endCluster() - parent->clusters().begin() + 1);
|
||||
addLine(text, textWithSpaces, clusters,
|
||||
TextRange textWithSpaces(fEndLine.startCluster()->textRange().start, startLine->textRange().start);
|
||||
if (fEndLine.breakCluster()->isHardBreak()) {
|
||||
textWithSpaces.end = fEndLine.breakCluster()->textRange().start;
|
||||
} else if (startLine == end) {
|
||||
textWithSpaces.end = fEndLine.breakCluster()->textRange().end;
|
||||
}
|
||||
ClusterRange clusters(fEndLine.startCluster() - start, fEndLine.endCluster() - start);
|
||||
ClusterRange clustersWithGhosts(fEndLine.startCluster() - start, startLine - start);
|
||||
addLine(text, textWithSpaces, clusters, clustersWithGhosts, widthWithSpaces,
|
||||
fEndLine.startPos(),
|
||||
fEndLine.endPos(),
|
||||
SkVector::Make(0, fHeight),
|
||||
@ -178,7 +202,11 @@ void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
|
||||
// Start a new line
|
||||
fHeight += fEndLine.metrics().height();
|
||||
|
||||
trimStartSpaces(end);
|
||||
if (!fHardLineBreak) {
|
||||
fEndLine.clean();
|
||||
}
|
||||
fEndLine.startFrom(startLine, pos);
|
||||
parent->fMaxWidthWithTrailingSpaces = SkMaxScalar(parent->fMaxWidthWithTrailingSpaces, widthWithSpaces);
|
||||
|
||||
if (needEllipsis || fLineNumber >= maxLines) {
|
||||
break;
|
||||
@ -190,12 +218,13 @@ void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
|
||||
// Last character is a line break
|
||||
if (parent->strutEnabled()) {
|
||||
// Make sure font metrics are not less than the strut
|
||||
parent->strutMetrics().updateLineMetrics(fEndLine.metrics(),
|
||||
parent->strutForceHeight());
|
||||
parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
|
||||
}
|
||||
TextRange empty(fEndLine.breakCluster()->textRange().start, fEndLine.breakCluster()->textRange().start);
|
||||
ClusterRange clusters(fEndLine.breakCluster() - parent->clusters().begin(), fEndLine.breakCluster() - parent->clusters().begin());
|
||||
addLine(empty, empty, clusters,
|
||||
TextRange hardBreak(fEndLine.breakCluster()->textRange().end, fEndLine.breakCluster()->textRange().end);
|
||||
ClusterRange clusters(fEndLine.breakCluster() - start, fEndLine.breakCluster() - start);
|
||||
addLine(empty, hardBreak, clusters, clusters,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
SkVector::Make(0, fHeight),
|
||||
|
@ -34,9 +34,9 @@ class TextWrapper {
|
||||
};
|
||||
class TextStretch {
|
||||
public:
|
||||
TextStretch() : fStart(), fEnd(), fWidth(0) {}
|
||||
TextStretch(Cluster* s, Cluster* e)
|
||||
: fStart(s, 0), fEnd(e, e->endPos()), fMetrics(), fWidth(0) {
|
||||
TextStretch() : fStart(), fEnd(), fWidth(0), fWidthWithGhostSpaces(0) {}
|
||||
TextStretch(Cluster* s, Cluster* e, bool forceStrut)
|
||||
: fStart(s, 0), fEnd(e, e->endPos()), fMetrics(forceStrut), fWidth(0), fWidthWithGhostSpaces(0) {
|
||||
for (auto c = s; c <= e; ++c) {
|
||||
if (c->run() != nullptr) {
|
||||
fMetrics.add(c->run());
|
||||
@ -45,12 +45,14 @@ class TextWrapper {
|
||||
}
|
||||
|
||||
inline SkScalar width() const { return fWidth; }
|
||||
SkScalar withWithGhostSpaces() const { return fWidthWithGhostSpaces; }
|
||||
inline Cluster* startCluster() const { return fStart.cluster(); }
|
||||
inline Cluster* endCluster() const { return fEnd.cluster(); }
|
||||
inline Cluster* breakCluster() const { return fBreak.cluster(); }
|
||||
inline LineMetrics& metrics() { return fMetrics; }
|
||||
inline size_t startPos() const { return fStart.position(); }
|
||||
inline size_t endPos() const { return fEnd.position(); }
|
||||
inline size_t breakPos() const { return fBreak.position(); }
|
||||
bool endOfCluster() { return fEnd.position() == fEnd.cluster()->endPos(); }
|
||||
bool endOfWord() {
|
||||
return endOfCluster() &&
|
||||
@ -89,15 +91,22 @@ class TextWrapper {
|
||||
fWidth = 0;
|
||||
}
|
||||
|
||||
void nextPos() {
|
||||
if (fEnd.position() == fEnd.cluster()->endPos()) {
|
||||
fEnd.move(true);
|
||||
void nextBreakPos(Cluster* endOfClusters) {
|
||||
if (fBreak.position() == fBreak.cluster()->endPos()) {
|
||||
if (fBreak.cluster() < endOfClusters) {
|
||||
fBreak.move(true);
|
||||
} else {
|
||||
fBreak.setPosition(0);
|
||||
}
|
||||
} else {
|
||||
fEnd.setPosition(fEnd.cluster()->endPos());
|
||||
fBreak.setPosition(fBreak.cluster()->endPos());
|
||||
}
|
||||
}
|
||||
|
||||
void saveBreak() { fBreak = fEnd; }
|
||||
void saveBreak() {
|
||||
fWidthWithGhostSpaces = fWidth;
|
||||
fBreak = fEnd;
|
||||
}
|
||||
|
||||
void restoreBreak() { fEnd = fBreak; }
|
||||
|
||||
@ -128,6 +137,7 @@ class TextWrapper {
|
||||
ClusterPos fBreak;
|
||||
LineMetrics fMetrics;
|
||||
SkScalar fWidth;
|
||||
SkScalar fWidthWithGhostSpaces;
|
||||
};
|
||||
|
||||
public:
|
||||
@ -136,6 +146,8 @@ public:
|
||||
using AddLineToParagraph = std::function<void(TextRange text,
|
||||
TextRange textWithSpaces,
|
||||
ClusterRange clusters,
|
||||
ClusterRange clustersWithGhosts,
|
||||
SkScalar AddLineToParagraph,
|
||||
size_t startClip,
|
||||
size_t endClip,
|
||||
SkVector offset,
|
||||
@ -175,8 +187,8 @@ private:
|
||||
|
||||
void lookAhead(SkScalar maxWidth, Cluster* endOfClusters);
|
||||
void moveForward();
|
||||
void trimEndSpaces();
|
||||
void trimStartSpaces(Cluster* endOfClusters);
|
||||
void trimEndSpaces(TextAlign align);
|
||||
std::tuple<Cluster*, size_t, SkScalar> trimStartSpaces(Cluster* endOfClusters);
|
||||
SkScalar getClustersTrimmedWidth();
|
||||
};
|
||||
} // namespace textlayout
|
||||
|
@ -972,7 +972,7 @@ protected:
|
||||
canvas->drawColor(background);
|
||||
|
||||
const char* line =
|
||||
"World domination is such an ugly phrase - I prefer to call it world optimisation";
|
||||
"World domination is such an ugly phrase - I prefer to call it world optimisation.";
|
||||
|
||||
ParagraphStyle paragraphStyle;
|
||||
paragraphStyle.setTextAlign(TextAlign::kLeft);
|
||||
@ -1033,7 +1033,7 @@ private:
|
||||
|
||||
class ParagraphView8 : public ParagraphView_Base {
|
||||
protected:
|
||||
SkString name() override { return SkString("Paragraph7"); }
|
||||
SkString name() override { return SkString("Paragraph8"); }
|
||||
|
||||
void drawText(SkCanvas* canvas, SkColor background, SkScalar wordSpace, SkScalar w,
|
||||
SkScalar h) {
|
||||
@ -1042,7 +1042,7 @@ protected:
|
||||
canvas->drawColor(background);
|
||||
|
||||
const char* line =
|
||||
"World domination is such an ugly phrase - I prefer to call it world optimisation";
|
||||
"World domination is such an ugly phrase - I prefer to call it world optimisation.";
|
||||
|
||||
ParagraphStyle paragraphStyle;
|
||||
paragraphStyle.setTextAlign(TextAlign::kLeft);
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user