Skip to content

Commit

Permalink
Support all combinations of GetRectsForRange styles (flutter#6591)
Browse files Browse the repository at this point in the history
  • Loading branch information
GaryQian committed Oct 23, 2018
1 parent e78f86e commit 2586e94
Show file tree
Hide file tree
Showing 7 changed files with 692 additions and 59 deletions.
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

0 comments on commit 2586e94

Please sign in to comment.