blob: 13e4acc255557248ccc9b91cb58d55e6498adadc [file] [log] [blame]
// Copyright 2021 Google LLC.
#include "experimental/sktext/src/Wrapper.h"
namespace skia {
namespace text {
bool Wrapper::process() {
// TODO: define max line number based on this->fHeight
return this->breakTextIntoLines(this->fWidth);
}
GlyphRange Wrapper::glyphRange(const TextRun* run, const TextRange& textRange) {
Range glyphRange = EMPTY_RANGE;
for (size_t i = 0; i < run->fClusters.size(); ++i) {
auto cluster = run->fClusters[i];
if (cluster < textRange.fStart) continue;
if (cluster > textRange.fEnd) break;
if (glyphRange.fStart == EMPTY_INDEX) {
glyphRange.fStart = i;
}
glyphRange.fEnd = i;
}
return glyphRange;
}
TextRange Wrapper::textRange(const TextRun* run, const GlyphRange& glyphRange) {
Range textRange = EMPTY_RANGE;
for (size_t i = 0; i < run->fClusters.size(); ++i) {
auto cluster = run->fClusters[i];
if (i < glyphRange.fStart) continue;
if (i > glyphRange.fEnd) break;
if (textRange.fStart == EMPTY_INDEX) {
textRange.fStart = cluster;
}
textRange.fEnd = cluster;
}
return textRange;
}
bool Wrapper::breakTextIntoLines(SkScalar width) {
// line : spaces : clusters
Stretch line;
Stretch spaces;
Stretch clusters;
for (size_t runIndex = 0; runIndex < fProcessor->fRuns.size(); ++runIndex ) {
auto& run = fProcessor->fRuns[runIndex];
TextMetrics runMetrics(run.fFont);
Stretch cluster;
if (!run.leftToRight()) {
cluster.setTextRange({ run.fUtf8Range.end(), run.fUtf8Range.end()});
}
for (size_t glyphIndex = 0; glyphIndex < run.fPositions.size(); ++glyphIndex) {
auto textIndex = run.fClusters[glyphIndex];
if (cluster.isEmpty()) {
cluster = Stretch(GlyphPos(runIndex, glyphIndex), textIndex, runMetrics);
continue;
}
// The entire cluster belongs to a single run
SkASSERT(cluster.glyphStart().runIndex() == runIndex);
auto clusterWidth = run.fPositions[glyphIndex].fX - run.fPositions[cluster.glyphStartIndex()].fX;
cluster.finish(glyphIndex, textIndex, clusterWidth);
auto isHardLineBreak = fProcessor->isHardLineBreak(cluster.textStart());
auto isSoftLineBreak = fProcessor->isSoftLineBreak(cluster.textStart());
auto isWhitespaces = fProcessor->isWhitespaces(cluster.textRange());
auto isEndOfText = run.leftToRight() ? textIndex == run.fUtf8Range.end() : textIndex == run.fUtf8Range.begin();
if (isSoftLineBreak || isWhitespaces || isHardLineBreak) {
// This is the end of the word
if (!clusters.isEmpty()) {
line.moveTo(spaces);
line.moveTo(clusters);
spaces = clusters;
}
}
// line + spaces + clusters + cluster
if (isWhitespaces) {
// Whitespaces do not extend the line width
spaces.moveTo(cluster);
clusters = cluster;
continue;
} else if (isHardLineBreak) {
// Hard line break ends the line but does not extend the width
// Same goes for the end of the text
this->addLine(line, spaces);
break;
} else if (!SkScalarIsFinite(width)) {
clusters.moveTo(cluster);
continue;
}
// Now let's find out if we can add the cluster to the line
if ((line.width() + spaces.width() + clusters.width() + cluster.width()) <= width) {
clusters.moveTo(cluster);
} else {
if (line.isEmpty()) {
if (spaces.isEmpty() && clusters.isEmpty()) {
// There is only this cluster and it's too long;
// we are drawing it anyway
line.moveTo(cluster);
} else {
// We break the only one word on the line by this cluster
line.moveTo(clusters);
}
} else {
// We move clusters + cluster on the next line
// TODO: Parametrise possible ways of breaking too long word
// (start it from a new line or squeeze the part of it on this line)
}
this->addLine(line, spaces);
clusters.moveTo(cluster);
}
cluster = Stretch(GlyphPos(runIndex, glyphIndex), textIndex, runMetrics);
}
}
if (!clusters.isEmpty()) {
line.moveTo(spaces);
line.moveTo(clusters);
spaces = clusters;
}
this->addLine(line, spaces);
return true;
}
/*
type SingleRunSingleStyleCallback = (style, glyphRange) => number;
breakRunIntoStyles(inputs, styleFlags : TextStyleFlagsEnum, runStart, run, glyphRange, callback: SingleRunSingleStyleCallback) {
let lastFlags = TextStyleFlagsEnum.kNoStyle;
let styleRange;
let index = 0;
for (let flags of this.fTextStyles) {
let currentFlags = lastFlags = flags & styleFlags;
if (currentFlags == lastFlags) {
styleRange.end = index;
} else {
callback();
lastFlags = currentFlags;
styleRange = { start: index, end: index};
}
}
const textRange = this.glyphRange(run, glyphRange);
for (let block of inputs.blocks) {
const intersect = this.intersect(block.textRange, textRange);
if (this.width(intersect) == 0) {
continue;
}
const glyphRange = this.glyphRange(run, intersect);
runStart += callback(block.style, glyphRange);
}
}
generateDrawingOperations(inputs, outputs) {
const textLayout = this;
// TODO: Sort runs on the line accordingly to the visual order (for LTR/RTL mixture)
// Iterate through all the runs on the line
let runStart = 0.0;
let lineVerticalStart = 0.0;
this.fLines.forEach(function (line, lineIndex) {
var run = null;
var lastRunIndex;
for (var runIndex = line.textStart.runIndex; runIndex <= line.textEnd.runIndex; ++runIndex) {
if (run != null && lastRunIndex != runIndex) {
continue;
}
run = inputs.runs[runIndex];
lastRunIndex = runIndex;
let runGlyphRange = {
start: line.textStart.runIndex ? line.textStart.glyphIndex : 0,
end: line.textEnd.runIndex ? line.textEnd.glyphIndex : run.glyphs.size()
}
// Iterate through all the styles in the run
// TODO: For now assume that the style edges are cluster edges and don't fall between clusters
// TODO: Just show the text, not decorations of any kind
textLayout.breakRunIntoStyles(inputs, runStart, run, runGlyphRange, function(style, glyphRange) {
// left, top, right, bottom
let runWidth = textLayout.glyphRangeWidth(run, glyphRange);
if (style.background == undefined) {
return runWidth;
}
let rectangle = {
backgroundColor: style.background,
left: runStart,
top: lineVerticalStart,
width: runWidth,
height: run.font.metrics.ascent + run.font.metrics.descent + run.font.metrics.leading
};
outputs.rectangles.push(rectangle);
return runWidth;
});
textLayout.breakRunIntoStyles(inputs, runStart, run, runGlyphRange, function(style, glyphRange) {
// TODO: Ignore clipping cluster by position for now
let textBlob = {
foregroundColor: style.foreground,
run : runIndex,
glyphRange: glyphRange,
shift: {
x: runStart,
y: lineVerticalStart
}
};
outputs.textBlobs.push(textBlob);
return textLayout.glyphRangeWidth(run, glyphRange);
});
}
});
}
}
*/
} // namespace text
} // namespace skia