Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions lib/ui/text/paragraph.cc
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ void Paragraph::paint(Canvas* canvas, double x, double y) {
}

std::vector<TextBox> Paragraph::getRectsForRange(unsigned start, unsigned end) {
return m_paragraphImpl->getRectsForRange(start, end,
txt::Paragraph::RectStyle::kTight);
return m_paragraphImpl->getRectsForRange(
start, end, txt::Paragraph::RectHeightStyle::kTight,
txt::Paragraph::RectWidthStyle::kTight);
}

Dart_Handle Paragraph::getPositionForOffset(double dx, double dy) {
Expand Down
3 changes: 2 additions & 1 deletion lib/ui/text/paragraph_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ class ParagraphImpl {
virtual std::vector<TextBox> getRectsForRange(
unsigned start,
unsigned end,
txt::Paragraph::RectStyle rect_style) = 0;
txt::Paragraph::RectHeightStyle rect_height_style,
txt::Paragraph::RectWidthStyle rect_width_style) = 0;

virtual Dart_Handle getPositionForOffset(double dx, double dy) = 0;

Expand Down
7 changes: 4 additions & 3 deletions lib/ui/text/paragraph_impl_txt.cc
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,11 @@ void ParagraphImplTxt::paint(Canvas* canvas, double x, double y) {
std::vector<TextBox> ParagraphImplTxt::getRectsForRange(
unsigned start,
unsigned end,
txt::Paragraph::RectStyle rect_style) {
txt::Paragraph::RectHeightStyle rect_height_style,
txt::Paragraph::RectWidthStyle rect_width_style) {
std::vector<TextBox> result;
std::vector<txt::Paragraph::TextBox> boxes =
m_paragraph->GetRectsForRange(start, end, rect_style);
std::vector<txt::Paragraph::TextBox> boxes = m_paragraph->GetRectsForRange(
start, end, rect_height_style, rect_width_style);
for (const txt::Paragraph::TextBox& box : boxes) {
result.emplace_back(box.rect,
static_cast<blink::TextDirection>(box.direction));
Expand Down
3 changes: 2 additions & 1 deletion lib/ui/text/paragraph_impl_txt.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ class ParagraphImplTxt : public ParagraphImpl {
std::vector<TextBox> getRectsForRange(
unsigned start,
unsigned end,
txt::Paragraph::RectStyle rect_style) override;
txt::Paragraph::RectHeightStyle rect_height_style,
txt::Paragraph::RectWidthStyle rect_width_style) override;
Dart_Handle getPositionForOffset(double dx, double dy) override;
Dart_Handle getWordBoundary(unsigned offset) override;

Expand Down
166 changes: 147 additions & 19 deletions third_party/txt/src/txt/paragraph.cc
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,11 @@ void Paragraph::Layout(double width, bool force) {
line_baselines_.clear();
glyph_lines_.clear();
code_unit_runs_.clear();
line_max_spacings_.clear();
line_max_descent_.clear();
line_max_ascent_.clear();
max_right_ = FLT_MIN;
min_left_ = FLT_MAX;

minikin::Layout layout;
SkTextBlobBuilder builder;
Expand Down Expand Up @@ -703,6 +708,9 @@ void Paragraph::Layout(double width, bool force) {
Range<double>(glyph_positions.front().x_pos.start,
glyph_positions.back().x_pos.end),
line_number, metrics, run.direction());

min_left_ = std::min(min_left_, glyph_positions.front().x_pos.start);
max_right_ = std::max(max_right_, glyph_positions.back().x_pos.end);
} // for each in glyph_blobs

run_x_offset += layout.getAdvance();
Expand All @@ -729,8 +737,12 @@ void Paragraph::Layout(double width, bool force) {

double max_line_spacing = 0;
double max_descent = 0;
SkScalar max_unscaled_ascent = 0;
auto update_line_metrics = [&](const SkPaint::FontMetrics& metrics,
const TextStyle& style) {
// TODO(garyq): Multipling in the style.height on the first line is
// probably wrong. Figure out how paragraph and line heights are supposed
// to work and fix it.
double line_spacing =
(line_number == 0)
? -metrics.fAscent * style.height
Expand All @@ -747,6 +759,8 @@ void Paragraph::Layout(double width, bool force) {

double descent = metrics.fDescent * style.height;
max_descent = std::max(descent, max_descent);

max_unscaled_ascent = std::max(-metrics.fAscent, max_unscaled_ascent);
};
for (const PaintRecord& paint_record : paint_records) {
update_line_metrics(paint_record.metrics(), paint_record.style());
Expand All @@ -762,12 +776,20 @@ void Paragraph::Layout(double width, bool force) {
update_line_metrics(metrics, style);
}

// TODO(garyq): Remove rounding of line heights because it is irrelevant in
// a world of high DPI devices.
line_heights_.push_back((line_heights_.empty() ? 0 : line_heights_.back()) +
round(max_line_spacing + max_descent));
line_baselines_.push_back(line_heights_.back() - max_descent);
y_offset += round(max_line_spacing + prev_max_descent);
prev_max_descent = max_descent;

// The max line spacing and ascent have been multiplied by -1 to make math
// in GetRectsForRange more logical/readable.
line_max_spacings_.push_back(max_line_spacing);
line_max_descent_.push_back(max_descent);
line_max_ascent_.push_back(max_unscaled_ascent);

for (PaintRecord& paint_record : paint_records) {
paint_record.SetOffset(
SkPoint::Make(paint_record.offset().x() + line_x_offset, y_offset));
Expand Down Expand Up @@ -1093,10 +1115,30 @@ void Paragraph::PaintShadow(SkCanvas* canvas,
std::vector<Paragraph::TextBox> Paragraph::GetRectsForRange(
size_t start,
size_t end,
RectStyle rect_style) const {
std::map<size_t, std::vector<Paragraph::TextBox>> line_boxes;

RectHeightStyle rect_height_style,
RectWidthStyle rect_width_style) const {
// Struct that holds calculated metrics for each line.
struct LineBoxMetrics {
std::vector<Paragraph::TextBox> boxes;
// Per-line metrics for max and min coordinates for left and right boxes.
// These metrics cannot be calculated in layout generically because of
// selections that do not cover the whole line.
SkScalar max_right = FLT_MIN;
SkScalar min_left = FLT_MAX;
};

std::map<size_t, LineBoxMetrics> line_metrics;
// Text direction of the first line so we can extend the correct side for
// RectWidthStyle::kMax.
TextDirection first_line_dir = TextDirection::ltr;

// Lines that are actually in the requested range.
size_t max_line = 0;
size_t min_line = INT_MAX;

// Generate initial boxes and calculate metrics.
for (const CodeUnitRun& run : code_unit_runs_) {
// Check to see if we are finished.
if (run.code_units.start >= end)
break;
if (run.code_units.end <= start)
Expand All @@ -1106,6 +1148,10 @@ std::vector<Paragraph::TextBox> Paragraph::GetRectsForRange(
SkScalar top = baseline + run.font_metrics.fAscent;
SkScalar bottom = baseline + run.font_metrics.fDescent;

max_line = std::max(run.line_number, max_line);
min_line = std::min(run.line_number, min_line);

// Calculate left and right.
SkScalar left, right;
if (run.code_units.start >= start && run.code_units.end <= end) {
left = run.x_pos.start;
Expand All @@ -1122,7 +1168,18 @@ std::vector<Paragraph::TextBox> Paragraph::GetRectsForRange(
if (left == SK_ScalarMax || right == SK_ScalarMin)
continue;
}
line_boxes[run.line_number].emplace_back(
// Keep track of the min and max horizontal coordinates over all lines. Not
// needed for kTight.
if (rect_width_style == RectWidthStyle::kMax) {
line_metrics[run.line_number].max_right =
std::max(line_metrics[run.line_number].max_right, right);
line_metrics[run.line_number].min_left =
std::min(line_metrics[run.line_number].min_left, left);
if (min_line == run.line_number) {
first_line_dir = run.direction;
}
}
line_metrics[run.line_number].boxes.emplace_back(
SkRect::MakeLTRB(left, top, right, bottom), run.direction);
}

Expand All @@ -1135,34 +1192,105 @@ std::vector<Paragraph::TextBox> Paragraph::GetRectsForRange(
break;
if (line.end_including_newline <= start)
continue;
if (line_boxes.find(line_number) == line_boxes.end()) {
if (line_metrics.find(line_number) == line_metrics.end()) {
if (line.end != line.end_including_newline && line.end >= start &&
line.end_including_newline <= end) {
SkScalar x = line_widths_[line_number];
SkScalar top = (line_number > 0) ? line_heights_[line_number - 1] : 0;
SkScalar bottom = line_heights_[line_number];
line_boxes[line_number].emplace_back(
line_metrics[line_number].boxes.emplace_back(
SkRect::MakeLTRB(x, top, x, bottom), TextDirection::ltr);
}
}
}

// "Post-process" metrics and aggregate final rects to return.
std::vector<Paragraph::TextBox> boxes;
for (const auto& kv : line_boxes) {
if (rect_style & RectStyle::kTight) {
for (const auto& kv : line_metrics) {
// Handle rect_width_styles. We skip the last line because not everything is
// selected.
if (rect_width_style == RectWidthStyle::kMax && kv.first != max_line) {
if (line_metrics[kv.first].min_left > min_left_ &&
(kv.first != min_line || first_line_dir == TextDirection::rtl)) {
line_metrics[kv.first].boxes.emplace_back(
SkRect::MakeLTRB(
min_left_,
line_baselines_[kv.first] - line_max_ascent_[kv.first],
line_metrics[kv.first].min_left,
line_baselines_[kv.first] + line_max_descent_[kv.first]),
TextDirection::rtl);
}
if (line_metrics[kv.first].max_right < max_right_ &&
(kv.first != min_line || first_line_dir == TextDirection::ltr)) {
line_metrics[kv.first].boxes.emplace_back(
SkRect::MakeLTRB(
line_metrics[kv.first].max_right,
line_baselines_[kv.first] - line_max_ascent_[kv.first],
max_right_,
line_baselines_[kv.first] + line_max_descent_[kv.first]),
TextDirection::ltr);
}
}

// Handle rect_height_styles. The height metrics used are all positive to
// make the signage clear here.
if (rect_height_style == RectHeightStyle::kTight) {
// Ignore line max height and width and generate tight bounds.
boxes.insert(boxes.end(), kv.second.begin(), kv.second.end());
} else {
// Set each box to the max height of each line to ensure continuity.
float min_top = DBL_MAX;
float max_bottom = 0;
for (const Paragraph::TextBox& box : kv.second) {
min_top = std::min(box.rect.fTop, min_top);
max_bottom = std::max(box.rect.fBottom, max_bottom);
boxes.insert(boxes.end(), kv.second.boxes.begin(), kv.second.boxes.end());
} else if (rect_height_style == RectHeightStyle::kMax) {
for (const Paragraph::TextBox& box : kv.second.boxes) {
boxes.emplace_back(
SkRect::MakeLTRB(
box.rect.fLeft,
line_baselines_[kv.first] - line_max_ascent_[kv.first],
box.rect.fRight,
line_baselines_[kv.first] + line_max_descent_[kv.first]),
box.direction);
}
} else if (rect_height_style ==
RectHeightStyle::kIncludeLineSpacingMiddle) {
SkScalar adjusted_bottom =
line_baselines_[kv.first] + line_max_descent_[kv.first];
if (kv.first < line_ranges_.size() - 1) {
adjusted_bottom += (line_max_spacings_[kv.first + 1] -
line_max_ascent_[kv.first + 1]) /
2;
}
SkScalar adjusted_top =
line_baselines_[kv.first] - line_max_ascent_[kv.first];
if (kv.first != 0) {
adjusted_top -=
(line_max_spacings_[kv.first] - line_max_ascent_[kv.first]) / 2;
}
for (const Paragraph::TextBox& box : kv.second) {
boxes.emplace_back(SkRect::MakeLTRB(box.rect.fLeft, min_top,
box.rect.fRight, max_bottom),
for (const Paragraph::TextBox& box : kv.second.boxes) {
boxes.emplace_back(SkRect::MakeLTRB(box.rect.fLeft, adjusted_top,
box.rect.fRight, adjusted_bottom),
box.direction);
}
} else if (rect_height_style == RectHeightStyle::kIncludeLineSpacingTop) {
for (const Paragraph::TextBox& box : kv.second.boxes) {
SkScalar adjusted_top =
kv.first == 0
? line_baselines_[kv.first] - line_max_ascent_[kv.first]
: line_baselines_[kv.first] - line_max_spacings_[kv.first];
boxes.emplace_back(
SkRect::MakeLTRB(
box.rect.fLeft, adjusted_top, box.rect.fRight,
line_baselines_[kv.first] + line_max_descent_[kv.first]),
box.direction);
}
} else { // kIncludeLineSpacingBottom
for (const Paragraph::TextBox& box : kv.second.boxes) {
SkScalar adjusted_bottom =
line_baselines_[kv.first] + line_max_descent_[kv.first];
if (kv.first < line_ranges_.size() - 1) {
adjusted_bottom +=
-line_max_ascent_[kv.first] + line_max_spacings_[kv.first];
}
boxes.emplace_back(SkRect::MakeLTRB(box.rect.fLeft,
line_baselines_[kv.first] -
line_max_ascent_[kv.first],
box.rect.fRight, adjusted_bottom),
box.direction);
}
}
Expand Down
55 changes: 41 additions & 14 deletions third_party/txt/src/txt/paragraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,36 @@ class Paragraph {

// Options for various types of bounding boxes provided by
// GetRectsForRange(...).
// These options can be individually enabled, for example:
//
// (RectStyle::kTight | RectStyle::kExtendEndOfLine)
//
// provides tight bounding boxes and extends the last box per line to the end
// of the layout area.
enum RectStyle {
kNone = 0x0, // kNone cannot be combined with |.

// Provide tight bounding boxes that fit heights per span. Otherwise, the
// heights of spans are the max of the heights of the line the span belongs
// in.
kTight = 0x1
enum class RectHeightStyle {
// Provide tight bounding boxes that fit heights per run.
kTight,

// The height of the boxes will be the maximum height of all runs in the
// line. All rects in the same line will be the same height.
kMax,

// Extends the top and/or bottom edge of the bounds to fully cover any line
// spacing. The top edge of each line should be the same as the bottom edge
// of the line above. There should be no gaps in vertical coverage given any
// ParagraphStyle line_height.
//
// The top and bottom of each rect will cover half of the
// space above and half of the space below the line.
kIncludeLineSpacingMiddle,
// The line spacing will be added to the top of the rect.
kIncludeLineSpacingTop,
// The line spacing will be added to the bottom of the rect.
kIncludeLineSpacingBottom
};

enum class RectWidthStyle {
// Provide tight bounding boxes that fit widths to the runs of each line
// independently.
kTight,

// Extends the width of the last rect of each line to match the position of
// the widest rect over all the lines.
kMax
};

struct PositionWithAffinity {
Expand Down Expand Up @@ -158,7 +175,8 @@ class Paragraph {
// end glyph indexes, including start and excluding end.
std::vector<TextBox> GetRectsForRange(size_t start,
size_t end,
RectStyle rect_style) const;
RectHeightStyle rect_height_style,
RectWidthStyle rect_width_style) const;

// Returns the index of the glyph that corresponds to the provided coordinate,
// with the top left corner as the origin, and +y direction as down.
Expand Down Expand Up @@ -240,6 +258,15 @@ class Paragraph {
std::vector<double> line_baselines_;
bool did_exceed_max_lines_;

// Metrics for use in GetRectsForRange(...);
// Per-line max metrics over all runs in a given line.
std::vector<SkScalar> line_max_spacings_;
std::vector<SkScalar> line_max_descent_;
std::vector<SkScalar> line_max_ascent_;
// Overall left and right extremes over all lines.
double max_right_;
double min_left_;

class BidiRun {
public:
BidiRun(size_t s, size_t e, TextDirection d, const TextStyle& st)
Expand Down
Loading