Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 43e9683

Browse files
authored
[Impeller] Implement TextDecoration (i.e. dashed lines) (#45041)
Closes flutter/flutter#126673. Ideally, this would be an `#ifdef`, but currently Impeller is a runtime flag, for reasons (CI capacity?) ~~To be able to write tests, I'd need flutter/flutter#133264 completed.~~ Edit: ![life-uh](https://github.com/flutter/engine/assets/168174/c77314e4-f3f9-4b0d-b3f7-5b070ef51308) ## Before ![Screenshot 2023-08-23 at 5 25 40 PM](https://github.com/flutter/engine/assets/168174/40576807-17f2-4f5f-ba56-3cd5771e472d) ## After <img width="795" alt="Screenshot 2023-08-24 at 9 56 48 AM" src="https://github.com/flutter/engine/assets/168174/0e33f705-6a71-4468-8ec8-372a2e98624e">
1 parent fc59272 commit 43e9683

File tree

10 files changed

+287
-39
lines changed

10 files changed

+287
-39
lines changed

lib/ui/text/paragraph_builder.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,9 @@ ParagraphBuilder::ParagraphBuilder(
300300
->client()
301301
->GetFontCollection();
302302

303+
auto impeller_enabled = UIDartState::Current()->IsImpellerEnabled();
303304
m_paragraphBuilder = txt::ParagraphBuilder::CreateSkiaBuilder(
304-
style, font_collection.GetFontCollection());
305+
style, font_collection.GetFontCollection(), impeller_enabled);
305306
}
306307

307308
ParagraphBuilder::~ParagraphBuilder() = default;

third_party/txt/BUILD.gn

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ if (enable_unittests) {
143143

144144
sources = [
145145
"tests/font_collection_tests.cc",
146+
"tests/paragraph_unittests.cc",
146147
"tests/txt_run_all_unittests.cc",
147148
]
148149

@@ -154,7 +155,10 @@ if (enable_unittests) {
154155
":txt",
155156
":txt_fixtures",
156157
"//flutter/fml",
158+
"//flutter/runtime:test_font",
159+
"//flutter/testing:skia",
157160
"//flutter/testing:testing_lib",
161+
"//third_party/skia/modules/skparagraph:skparagraph",
158162
]
159163

160164
# This is needed for //third_party/googletest for linking zircon symbols.

third_party/txt/src/skia/paragraph_builder_skia.cc

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ SkFontStyle MakeSkFontStyle(txt::FontWeight font_weight,
4646

4747
ParagraphBuilderSkia::ParagraphBuilderSkia(
4848
const ParagraphStyle& style,
49-
std::shared_ptr<FontCollection> font_collection)
50-
: base_style_(style.GetTextStyle()) {
49+
std::shared_ptr<FontCollection> font_collection,
50+
const bool impeller_enabled)
51+
: base_style_(style.GetTextStyle()), impeller_enabled_(impeller_enabled) {
5152
builder_ = skt::ParagraphBuilder::make(
5253
TxtToSkia(style), font_collection->CreateSktFontCollection());
5354
}
@@ -85,8 +86,8 @@ void ParagraphBuilderSkia::AddPlaceholder(PlaceholderRun& span) {
8586
}
8687

8788
std::unique_ptr<Paragraph> ParagraphBuilderSkia::Build() {
88-
return std::make_unique<ParagraphSkia>(builder_->Build(),
89-
std::move(dl_paints_));
89+
return std::make_unique<ParagraphSkia>(
90+
builder_->Build(), std::move(dl_paints_), impeller_enabled_);
9091
}
9192

9293
skt::ParagraphPainter::PaintID ParagraphBuilderSkia::CreatePaintID(

third_party/txt/src/skia/paragraph_builder_skia.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,16 @@
2424

2525
namespace txt {
2626

27-
// Implementation of ParagraphBuilder based on Skia's text layout module.
27+
//------------------------------------------------------------------------------
28+
/// @brief ParagraphBuilder implementation using Skia's text layout module.
29+
///
30+
/// @note Despite the suffix "Skia", this class is not specific to Skia
31+
/// and is also used with the Impeller backend.
2832
class ParagraphBuilderSkia : public ParagraphBuilder {
2933
public:
3034
ParagraphBuilderSkia(const ParagraphStyle& style,
31-
std::shared_ptr<FontCollection> font_collection);
35+
std::shared_ptr<FontCollection> font_collection,
36+
const bool impeller_enabled);
3237

3338
virtual ~ParagraphBuilderSkia();
3439

@@ -47,6 +52,14 @@ class ParagraphBuilderSkia : public ParagraphBuilder {
4752

4853
std::shared_ptr<skia::textlayout::ParagraphBuilder> builder_;
4954
TextStyle base_style_;
55+
56+
/// @brief Whether Impeller is enabled in the runtime.
57+
///
58+
/// @note As of the time of this writing, this is used to draw text
59+
/// decorations (i.e. dashed and dotted lines) directly using the
60+
/// `drawLine` API, because Impeller's path rendering does not
61+
/// support dashed and dotted lines (but Skia's does).
62+
const bool impeller_enabled_;
5063
std::stack<TextStyle> txt_style_stack_;
5164
std::vector<flutter::DlPaint> dl_paints_;
5265
};

third_party/txt/src/skia/paragraph_skia.cc

Lines changed: 94 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include <algorithm>
2020
#include <numeric>
21+
#include "fml/logging.h"
2122

2223
namespace txt {
2324

@@ -45,27 +46,29 @@ txt::FontStyle GetTxtFontStyle(SkFontStyle::Slant font_slant) {
4546

4647
class DisplayListParagraphPainter : public skt::ParagraphPainter {
4748
public:
49+
//----------------------------------------------------------------------------
50+
/// @brief Creates a |skt::ParagraphPainter| that draws to DisplayList.
51+
///
52+
/// @param builder The display list builder.
53+
/// @param[in] dl_paints The paints referenced by ID in the `drawX` methods.
54+
/// @param[in] draw_path_effect If true, draw path effects directly by
55+
/// drawing multiple lines instead of providing
56+
// a path effect to the paint.
57+
///
58+
/// @note Impeller does not (and will not) support path effects, but the
59+
/// Skia backend does. That means that if we want to draw dashed
60+
/// and dotted lines, we need to draw them directly using the
61+
/// `drawLine` API instead of using a path effect.
62+
///
63+
/// See https://github.com/flutter/flutter/issues/126673. It
64+
/// probably makes sense to eventually make this a compile-time
65+
/// decision (i.e. with `#ifdef`) instead of a runtime option.
4866
DisplayListParagraphPainter(DisplayListBuilder* builder,
49-
const std::vector<DlPaint>& dl_paints)
50-
: builder_(builder), dl_paints_(dl_paints) {}
51-
52-
DlPaint toDlPaint(const DecorationStyle& decor_style,
53-
DlDrawStyle draw_style = DlDrawStyle::kStroke) {
54-
DlPaint paint;
55-
paint.setDrawStyle(draw_style);
56-
paint.setAntiAlias(true);
57-
paint.setColor(decor_style.getColor());
58-
paint.setStrokeWidth(decor_style.getStrokeWidth());
59-
std::optional<DashPathEffect> dash_path_effect =
60-
decor_style.getDashPathEffect();
61-
if (dash_path_effect) {
62-
std::array<SkScalar, 2> intervals{dash_path_effect->fOnLength,
63-
dash_path_effect->fOffLength};
64-
paint.setPathEffect(
65-
DlDashPathEffect::Make(intervals.data(), intervals.size(), 0));
66-
}
67-
return paint;
68-
}
67+
const std::vector<DlPaint>& dl_paints,
68+
bool draw_path_effect)
69+
: builder_(builder),
70+
dl_paints_(dl_paints),
71+
draw_path_effect_(draw_path_effect) {}
6972

7073
void drawTextBlob(const sk_sp<SkTextBlob>& blob,
7174
SkScalar x,
@@ -118,8 +121,25 @@ class DisplayListParagraphPainter : public skt::ParagraphPainter {
118121
SkScalar x1,
119122
SkScalar y1,
120123
const DecorationStyle& decor_style) override {
121-
builder_->DrawLine(SkPoint::Make(x0, y0), SkPoint::Make(x1, y1),
122-
toDlPaint(decor_style));
124+
// We only support horizontal lines.
125+
FML_DCHECK(y0 == y1);
126+
127+
// This function is called for both solid and dashed lines. If we're drawing
128+
// a dashed line, and we're using the Impeller backend, then we need to draw
129+
// the line directly using the `drawLine` API instead of using a path effect
130+
// (because Impeller does not support path effects).
131+
auto dash_path_effect = decor_style.getDashPathEffect();
132+
if (draw_path_effect_ && dash_path_effect) {
133+
auto path = dashedLine(x0, x1, y0, *dash_path_effect);
134+
builder_->DrawPath(path, toDlPaint(decor_style));
135+
return;
136+
}
137+
138+
auto paint = toDlPaint(decor_style);
139+
if (dash_path_effect) {
140+
setPathEffect(paint, *dash_path_effect);
141+
}
142+
builder_->DrawLine(SkPoint::Make(x0, y0), SkPoint::Make(x1, y1), paint);
123143
}
124144

125145
void clipRect(const SkRect& rect) override {
@@ -135,15 +155,64 @@ class DisplayListParagraphPainter : public skt::ParagraphPainter {
135155
void restore() override { builder_->Restore(); }
136156

137157
private:
158+
SkPath dashedLine(SkScalar x0,
159+
SkScalar x1,
160+
SkScalar y0,
161+
const DashPathEffect& dash_path_effect) {
162+
auto dx = 0.0;
163+
auto path = SkPath();
164+
auto on = true;
165+
auto length = x1 - x0;
166+
while (dx < length) {
167+
if (on) {
168+
// Draw the on part of the dash.
169+
path.moveTo(x0 + dx, y0);
170+
dx += dash_path_effect.fOnLength;
171+
path.lineTo(x0 + dx, y0);
172+
} else {
173+
// Skip the off part of the dash.
174+
dx += dash_path_effect.fOffLength;
175+
}
176+
on = !on;
177+
}
178+
179+
path.close();
180+
return path;
181+
}
182+
183+
DlPaint toDlPaint(const DecorationStyle& decor_style,
184+
DlDrawStyle draw_style = DlDrawStyle::kStroke) {
185+
DlPaint paint;
186+
paint.setDrawStyle(draw_style);
187+
paint.setAntiAlias(true);
188+
paint.setColor(decor_style.getColor());
189+
paint.setStrokeWidth(decor_style.getStrokeWidth());
190+
return paint;
191+
}
192+
193+
void setPathEffect(DlPaint& paint, const DashPathEffect& dash_path_effect) {
194+
// Impeller does not support path effects, so we should never be setting.
195+
FML_DCHECK(!draw_path_effect_);
196+
197+
std::array<SkScalar, 2> intervals{dash_path_effect.fOnLength,
198+
dash_path_effect.fOffLength};
199+
auto effect = DlDashPathEffect::Make(intervals.data(), intervals.size(), 0);
200+
paint.setPathEffect(effect);
201+
}
202+
138203
DisplayListBuilder* builder_;
139204
const std::vector<DlPaint>& dl_paints_;
205+
bool draw_path_effect_;
140206
};
141207

142208
} // anonymous namespace
143209

144210
ParagraphSkia::ParagraphSkia(std::unique_ptr<skt::Paragraph> paragraph,
145-
std::vector<flutter::DlPaint>&& dl_paints)
146-
: paragraph_(std::move(paragraph)), dl_paints_(dl_paints) {}
211+
std::vector<flutter::DlPaint>&& dl_paints,
212+
bool impeller_enabled)
213+
: paragraph_(std::move(paragraph)),
214+
dl_paints_(dl_paints),
215+
impeller_enabled_(impeller_enabled) {}
147216

148217
double ParagraphSkia::GetMaxWidth() {
149218
return SkScalarToDouble(paragraph_->getMaxWidth());
@@ -223,7 +292,7 @@ void ParagraphSkia::Layout(double width) {
223292
}
224293

225294
bool ParagraphSkia::Paint(DisplayListBuilder* builder, double x, double y) {
226-
DisplayListParagraphPainter painter(builder, dl_paints_);
295+
DisplayListParagraphPainter painter(builder, dl_paints_, impeller_enabled_);
227296
paragraph_->paint(&painter, x, y);
228297
return true;
229298
}

third_party/txt/src/skia/paragraph_skia.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ namespace txt {
2929
class ParagraphSkia : public Paragraph {
3030
public:
3131
ParagraphSkia(std::unique_ptr<skia::textlayout::Paragraph> paragraph,
32-
std::vector<flutter::DlPaint>&& dl_paints);
32+
std::vector<flutter::DlPaint>&& dl_paints,
33+
bool impeller_enabled);
3334

3435
virtual ~ParagraphSkia() = default;
3536

@@ -75,6 +76,7 @@ class ParagraphSkia : public Paragraph {
7576
std::vector<flutter::DlPaint> dl_paints_;
7677
std::optional<std::vector<LineMetrics>> line_metrics_;
7778
std::vector<TextStyle> line_metrics_styles_;
79+
const bool impeller_enabled_;
7880
};
7981

8082
} // namespace txt

third_party/txt/src/txt/paragraph_builder.cc

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,18 @@
2222

2323
namespace txt {
2424

25+
//------------------------------------------------------------------------------
26+
/// @brief Creates a |ParagraphBuilder| based on Skia's text layout module.
27+
///
28+
/// @param[in] style The style to use for the paragraph.
29+
/// @param[in] font_collection The font collection to use for the paragraph.
30+
/// @param[in] impeller_enabled Whether Impeller is enabled in the runtime.
2531
std::unique_ptr<ParagraphBuilder> ParagraphBuilder::CreateSkiaBuilder(
2632
const ParagraphStyle& style,
27-
std::shared_ptr<FontCollection> font_collection) {
28-
return std::make_unique<ParagraphBuilderSkia>(style, font_collection);
33+
std::shared_ptr<FontCollection> font_collection,
34+
const bool impeller_enabled) {
35+
return std::make_unique<ParagraphBuilderSkia>(style, font_collection,
36+
impeller_enabled);
2937
}
3038

3139
} // namespace txt

third_party/txt/src/txt/paragraph_builder.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ class ParagraphBuilder {
3333
public:
3434
static std::unique_ptr<ParagraphBuilder> CreateSkiaBuilder(
3535
const ParagraphStyle& style,
36-
std::shared_ptr<FontCollection> font_collection);
36+
std::shared_ptr<FontCollection> font_collection,
37+
const bool impeller_enabled);
3738

3839
virtual ~ParagraphBuilder() = default;
3940

0 commit comments

Comments
 (0)