Flutter Engine
 
Loading...
Searching...
No Matches
paragraph_skia.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "paragraph_skia.h"
6
7#include <algorithm>
8#include <numeric>
12#include "fml/logging.h"
15#include "include/core/SkMatrix.h"
16#include "third_party/skia/src/core/SkTextBlobPriv.h" // nogncheck
17
18namespace txt {
19
20namespace skt = skia::textlayout;
21using PaintID = skt::ParagraphPainter::PaintID;
22
23using namespace flutter;
24
25namespace {
26
27txt::FontStyle GetTxtFontStyle(SkFontStyle::Slant font_slant) {
28 return font_slant == SkFontStyle::Slant::kUpright_Slant
31}
32
33class DisplayListParagraphPainter : public skt::ParagraphPainter {
34 public:
35 //----------------------------------------------------------------------------
36 /// @brief Creates a |skt::ParagraphPainter| that draws to DisplayList.
37 ///
38 /// @param builder The display list builder.
39 /// @param[in] dl_paints The paints referenced by ID in the `drawX` methods.
40 /// @param[in] draw_path_effect If true, draw path effects directly by
41 /// drawing multiple lines instead of providing
42 // a path effect to the paint.
43 ///
44 /// @note Impeller does not (and will not) support path effects, but the
45 /// Skia backend does. That means that if we want to draw dashed
46 /// and dotted lines, we need to draw them directly using the
47 /// `drawLine` API instead of using a path effect.
48 ///
49 /// See https://github.com/flutter/flutter/issues/126673. It
50 /// probably makes sense to eventually make this a compile-time
51 /// decision (i.e. with `#ifdef`) instead of a runtime option.
52 DisplayListParagraphPainter(DisplayListBuilder* builder,
53 const std::vector<DlPaint>& dl_paints,
54 bool impeller_enabled)
55 : builder_(builder),
56 dl_paints_(dl_paints),
57 impeller_enabled_(impeller_enabled) {}
58
59 void drawTextBlob(const sk_sp<SkTextBlob>& blob,
60 SkScalar x,
61 SkScalar y,
62 const SkPaintOrID& paint) override {
63 if (!blob) {
64 return;
65 }
66 size_t paint_id = std::get<PaintID>(paint);
67 FML_DCHECK(paint_id < dl_paints_.size());
68
69#ifdef IMPELLER_SUPPORTS_RENDERING
70 if (impeller_enabled_) {
71 SkTextBlobRunIterator run(blob.get());
72 if (ShouldRenderAsPath(dl_paints_[paint_id])) {
73 SkPath path = skia::textlayout::Paragraph::GetPath(blob.get());
74 // If there is no path, this is an emoji and should be drawn as is,
75 // ignoring the color source.
76 if (path.isEmpty()) {
77 builder_->DrawText(DlTextImpeller::Make(
79 x, y, dl_paints_[paint_id]);
80
81 return;
82 }
83
84 auto transformed = path.makeTransform(SkMatrix::Translate(
85 x + blob->bounds().left(), y + blob->bounds().top()));
86 builder_->DrawPath(DlPath(transformed), dl_paints_[paint_id]);
87 return;
88 }
89 builder_->DrawText(
90 DlTextImpeller::Make(impeller::MakeTextFrameFromTextBlobSkia(blob)),
91 x, y, dl_paints_[paint_id]);
92 return;
93 }
94#endif // IMPELLER_SUPPORTS_RENDERING
95 builder_->DrawText(DlTextSkia::Make(blob), x, y, dl_paints_[paint_id]);
96 }
97
98 void drawTextShadow(const sk_sp<SkTextBlob>& blob,
99 SkScalar x,
100 SkScalar y,
101 SkColor color,
102 SkScalar blur_sigma) override {
103 if (!blob) {
104 return;
105 }
106 DlPaint paint;
107 paint.setColor(DlColor(color));
108 if (blur_sigma > 0.0) {
109 DlBlurMaskFilter filter(DlBlurStyle::kNormal, blur_sigma, false);
110 paint.setMaskFilter(&filter);
111 }
112 std::shared_ptr<DlText> text;
113#if IMPELLER_SUPPORTS_RENDERING
114 if (impeller_enabled_) {
115 text =
116 DlTextImpeller::Make(impeller::MakeTextFrameFromTextBlobSkia(blob));
117 } else {
118 text = DlTextSkia::Make(blob);
119 }
120#else
121 text = DlTextSkia::Make(blob);
122#endif
123 builder_->DrawText(text, x, y, paint);
124 }
125
126 void drawRect(const SkRect& rect, const SkPaintOrID& paint) override {
127 size_t paint_id = std::get<PaintID>(paint);
128 FML_DCHECK(paint_id < dl_paints_.size());
129 builder_->DrawRect(ToDlRect(rect), dl_paints_[paint_id]);
130 }
131
132 void drawFilledRect(const SkRect& rect,
133 const DecorationStyle& decor_style) override {
134 DlPaint paint = toDlPaint(decor_style, DlDrawStyle::kFill);
135 builder_->DrawRect(ToDlRect(rect), paint);
136 }
137
138 void drawPath(const SkPath& path,
139 const DecorationStyle& decor_style) override {
140 builder_->DrawPath(DlPath(path), toDlPaint(decor_style));
141 }
142
143 void drawLine(SkScalar x0,
144 SkScalar y0,
145 SkScalar x1,
146 SkScalar y1,
147 const DecorationStyle& decor_style) override {
148 auto dash_path_effect = decor_style.getDashPathEffect();
149 auto paint = toDlPaint(decor_style);
150
151 if (dash_path_effect) {
152 builder_->DrawDashedLine(DlPoint(x0, y0), DlPoint(x1, y1),
153 dash_path_effect->fOnLength,
154 dash_path_effect->fOffLength, paint);
155 } else {
156 builder_->DrawLine(DlPoint(x0, y0), DlPoint(x1, y1), paint);
157 }
158 }
159
160 void clipRect(const SkRect& rect) override {
161 builder_->ClipRect(ToDlRect(rect), DlClipOp::kIntersect, false);
162 }
163
164 void translate(SkScalar dx, SkScalar dy) override {
165 builder_->Translate(dx, dy);
166 }
167
168 void save() override { builder_->Save(); }
169
170 void restore() override { builder_->Restore(); }
171
172 private:
173 bool ShouldRenderAsPath(const DlPaint& paint) const {
174 FML_DCHECK(impeller_enabled_);
175 // Text with non-trivial color sources should be rendered as a path when
176 // running on Impeller for correctness. These filters rely on having the
177 // glyph coverage, whereas regular text is drawn as rectangular texture
178 // samples.
179 // If the text is stroked and the stroke width is large enough, use path
180 // rendering anyway, as the fidelity problems won't be as noticable and
181 // rendering will be faster as it avoids software rasterization. A stroke
182 // width of four was chosen by eyeballing the point at which the path
183 // text looks good enough, with some room for error.
184 return paint.getColorSource() ||
185 (paint.getDrawStyle() == DlDrawStyle::kStroke &&
186 paint.getStrokeWidth() > 4);
187 }
188
189 DlPaint toDlPaint(const DecorationStyle& decor_style,
190 DlDrawStyle draw_style = DlDrawStyle::kStroke) {
191 DlPaint paint;
192 paint.setDrawStyle(draw_style);
193 paint.setAntiAlias(true);
194 paint.setColor(DlColor(decor_style.getColor()));
195 paint.setStrokeWidth(decor_style.getStrokeWidth());
196 return paint;
197 }
198
199 DisplayListBuilder* builder_;
200 const std::vector<DlPaint>& dl_paints_;
201 const bool impeller_enabled_;
202};
203
204} // anonymous namespace
205
206ParagraphSkia::ParagraphSkia(std::unique_ptr<skt::Paragraph> paragraph,
207 std::vector<flutter::DlPaint>&& dl_paints,
208 bool impeller_enabled)
209 : paragraph_(std::move(paragraph)),
210 dl_paints_(dl_paints),
211 impeller_enabled_(impeller_enabled) {}
212
214 return SkScalarToDouble(paragraph_->getMaxWidth());
215}
216
218 return SkScalarToDouble(paragraph_->getHeight());
219}
220
222 return SkScalarToDouble(paragraph_->getLongestLine());
223}
224
225std::vector<LineMetrics>& ParagraphSkia::GetLineMetrics() {
226 if (!line_metrics_) {
227 std::vector<skt::LineMetrics> metrics;
228 paragraph_->getLineMetrics(metrics);
229
230 line_metrics_.emplace();
231 line_metrics_styles_.reserve(
232 std::accumulate(metrics.begin(), metrics.end(), 0,
233 [](const int a, const skt::LineMetrics& b) {
234 return a + b.fLineMetrics.size();
235 }));
236
237 for (const skt::LineMetrics& skm : metrics) {
238 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
239 LineMetrics& txtm = line_metrics_->emplace_back(
240 skm.fStartIndex, skm.fEndIndex, skm.fEndExcludingWhitespaces,
241 skm.fEndIncludingNewline, skm.fHardBreak);
242 txtm.ascent = skm.fAscent;
243 txtm.descent = skm.fDescent;
244 txtm.unscaled_ascent = skm.fUnscaledAscent;
245 txtm.height = skm.fHeight;
246 txtm.width = skm.fWidth;
247 txtm.left = skm.fLeft;
248 txtm.baseline = skm.fBaseline;
249 txtm.line_number = skm.fLineNumber;
250
251 for (const auto& sk_iter : skm.fLineMetrics) {
252 const skt::StyleMetrics& sk_style_metrics = sk_iter.second;
253 line_metrics_styles_.push_back(SkiaToTxt(*sk_style_metrics.text_style));
254 txtm.run_metrics.emplace(
255 std::piecewise_construct, std::forward_as_tuple(sk_iter.first),
256 std::forward_as_tuple(&line_metrics_styles_.back(),
257 sk_style_metrics.font_metrics));
258 }
259 }
260 }
261
262 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
263 return line_metrics_.value();
264}
265
267 skt::LineMetrics* lineMetrics) const {
268 return paragraph_->getLineMetricsAt(lineNumber, lineMetrics);
269};
270
272 return SkScalarToDouble(paragraph_->getMinIntrinsicWidth());
273}
274
276 return SkScalarToDouble(paragraph_->getMaxIntrinsicWidth());
277}
278
280 return SkScalarToDouble(paragraph_->getAlphabeticBaseline());
281}
282
284 return SkScalarToDouble(paragraph_->getIdeographicBaseline());
285}
286
288 return paragraph_->didExceedMaxLines();
289}
290
292 line_metrics_.reset();
293 line_metrics_styles_.clear();
294 paragraph_->layout(width);
295}
296
297bool ParagraphSkia::Paint(DisplayListBuilder* builder, double x, double y) {
298 DisplayListParagraphPainter painter(builder, dl_paints_, impeller_enabled_);
299 paragraph_->paint(&painter, x, y);
300 return true;
301}
302
303std::vector<Paragraph::TextBox> ParagraphSkia::GetRectsForRange(
304 size_t start,
305 size_t end,
306 RectHeightStyle rect_height_style,
307 RectWidthStyle rect_width_style) {
308 std::vector<skt::TextBox> skia_boxes = paragraph_->getRectsForRange(
309 start, end, static_cast<skt::RectHeightStyle>(rect_height_style),
310 static_cast<skt::RectWidthStyle>(rect_width_style));
311
312 std::vector<Paragraph::TextBox> boxes;
313 boxes.reserve(skia_boxes.size());
314 for (const skt::TextBox& skia_box : skia_boxes) {
315 boxes.emplace_back(skia_box.rect,
316 static_cast<TextDirection>(skia_box.direction));
317 }
318
319 return boxes;
320}
321
322std::vector<Paragraph::TextBox> ParagraphSkia::GetRectsForPlaceholders() {
323 std::vector<skt::TextBox> skia_boxes = paragraph_->getRectsForPlaceholders();
324
325 std::vector<Paragraph::TextBox> boxes;
326 boxes.reserve(skia_boxes.size());
327 for (const skt::TextBox& skia_box : skia_boxes) {
328 boxes.emplace_back(skia_box.rect,
329 static_cast<TextDirection>(skia_box.direction));
330 }
331
332 return boxes;
333}
334
336 double dx,
337 double dy) {
338 skt::PositionWithAffinity skia_pos =
339 paragraph_->getGlyphPositionAtCoordinate(dx, dy);
340
341 return ParagraphSkia::PositionWithAffinity(
342 skia_pos.position, static_cast<Affinity>(skia_pos.affinity));
343}
344
346 unsigned offset,
347 skia::textlayout::Paragraph::GlyphInfo* glyphInfo) const {
348 return paragraph_->getGlyphInfoAtUTF16Offset(offset, glyphInfo);
349}
350
352 double dx,
353 double dy,
354 skia::textlayout::Paragraph::GlyphInfo* glyphInfo) const {
355 return paragraph_->getClosestUTF16GlyphInfoAt(dx, dy, glyphInfo);
356};
357
359 skt::SkRange<size_t> range = paragraph_->getWordBoundary(offset);
360 return Paragraph::Range<size_t>(range.start, range.end);
361}
362
364 return paragraph_->lineNumber();
365}
366
367int ParagraphSkia::GetLineNumberAt(size_t codeUnitIndex) const {
368 return paragraph_->getLineNumberAtUTF16Offset(codeUnitIndex);
369}
370
371TextStyle ParagraphSkia::SkiaToTxt(const skt::TextStyle& skia) {
373
374 txt.color = skia.getColor();
375 txt.decoration = static_cast<TextDecoration>(skia.getDecorationType());
376 txt.decoration_color = skia.getDecorationColor();
377 txt.decoration_style =
378 static_cast<TextDecorationStyle>(skia.getDecorationStyle());
379 txt.decoration_thickness_multiplier =
380 SkScalarToDouble(skia.getDecorationThicknessMultiplier());
381 txt.font_weight = skia.getFontStyle().weight();
382 txt.font_style = GetTxtFontStyle(skia.getFontStyle().slant());
383
384 txt.text_baseline = static_cast<TextBaseline>(skia.getTextBaseline());
385
386 for (const SkString& font_family : skia.getFontFamilies()) {
387 txt.font_families.emplace_back(font_family.c_str());
388 }
389
390 txt.font_size = SkScalarToDouble(skia.getFontSize());
391 txt.letter_spacing = SkScalarToDouble(skia.getLetterSpacing());
392 txt.word_spacing = SkScalarToDouble(skia.getWordSpacing());
393 txt.height = SkScalarToDouble(skia.getHeight());
394
395 txt.locale = skia.getLocale().c_str();
396 if (skia.hasBackground()) {
397 PaintID background_id = std::get<PaintID>(skia.getBackgroundPaintOrID());
398 txt.background = dl_paints_[background_id];
399 }
400 if (skia.hasForeground()) {
401 PaintID foreground_id = std::get<PaintID>(skia.getForegroundPaintOrID());
402 txt.foreground = dl_paints_[foreground_id];
403 }
404
405 txt.text_shadows.clear();
406 for (const skt::TextShadow& skia_shadow : skia.getShadows()) {
407 txt::TextShadow shadow;
408 shadow.offset = skia_shadow.fOffset;
409 shadow.blur_sigma = skia_shadow.fBlurSigma;
410 shadow.color = skia_shadow.fColor;
411 txt.text_shadows.emplace_back(shadow);
412 }
413
414 return txt;
415}
416
417} // namespace txt
DlPaint & setColor(DlColor color)
Definition dl_paint.h:70
DlPaint & setAntiAlias(bool isAntiAlias)
Definition dl_paint.h:58
DlPaint & setStrokeWidth(float width)
Definition dl_paint.h:115
DlDrawStyle getDrawStyle() const
Definition dl_paint.h:90
const std::shared_ptr< const DlColorSource > & getColorSource() const
Definition dl_paint.h:126
float getStrokeWidth() const
Definition dl_paint.h:114
DlPaint & setMaskFilter(std::nullptr_t filter)
Definition dl_paint.h:185
DlPaint & setDrawStyle(DlDrawStyle style)
Definition dl_paint.h:93
std::map< size_t, RunMetrics > run_metrics
double GetMaxIntrinsicWidth() override
bool DidExceedMaxLines() override
double GetHeight() override
ParagraphSkia(std::unique_ptr< skia::textlayout::Paragraph > paragraph, std::vector< flutter::DlPaint > &&dl_paints, bool impeller_enabled)
size_t GetNumberOfLines() const override
PositionWithAffinity GetGlyphPositionAtCoordinate(double dx, double dy) override
double GetAlphabeticBaseline() override
bool GetClosestGlyphInfoAtCoordinate(double dx, double dy, skia::textlayout::Paragraph::GlyphInfo *glyphInfo) const override
int GetLineNumberAt(size_t utf16Offset) const override
bool GetLineMetricsAt(int lineNumber, skia::textlayout::LineMetrics *lineMetrics) const override
double GetLongestLine() override
std::vector< LineMetrics > & GetLineMetrics() override
void Layout(double width) override
std::vector< TextBox > GetRectsForRange(size_t start, size_t end, RectHeightStyle rect_height_style, RectWidthStyle rect_width_style) override
std::vector< TextBox > GetRectsForPlaceholders() override
double GetMaxWidth() override
bool Paint(flutter::DisplayListBuilder *builder, double x, double y) override
bool GetGlyphInfoAt(unsigned offset, skia::textlayout::Paragraph::GlyphInfo *glyphInfo) const override
double GetMinIntrinsicWidth() override
Range< size_t > GetWordBoundary(size_t offset) override
double GetIdeographicBaseline() override
int32_t x
#define FML_DCHECK(condition)
Definition logging.h:122
std::u16string text
double y
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition switch_defs.h:52
DlDrawStyle
Definition dl_paint.h:19
const DlRect & ToDlRect(const SkRect &rect)
std::shared_ptr< TextFrame > MakeTextFrameFromTextBlobSkia(const sk_sp< SkTextBlob > &blob)
Definition ref_ptr.h:261
skt::ParagraphPainter::PaintID PaintID
FontStyle
Definition font_style.h:10
TextDecorationStyle
int32_t width
const size_t start