Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
paragraph_skia.cc
Go to the documentation of this file.
1/*
2 * Copyright 2019 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "paragraph_skia.h"
18
19#include <algorithm>
20#include <numeric>
22#include "fml/logging.h"
25
26namespace txt {
27
28namespace skt = skia::textlayout;
29using PaintID = skt::ParagraphPainter::PaintID;
30
31using namespace flutter;
32
33namespace {
34
35// Convert SkFontStyle::Weight values (ranging from 100-900) to txt::FontWeight
36// values (ranging from 0-8).
37txt::FontWeight GetTxtFontWeight(int font_weight) {
38 int txt_weight = (font_weight - 100) / 100;
39 txt_weight = std::clamp(txt_weight, static_cast<int>(txt::FontWeight::w100),
40 static_cast<int>(txt::FontWeight::w900));
41 return static_cast<txt::FontWeight>(txt_weight);
42}
43
44txt::FontStyle GetTxtFontStyle(SkFontStyle::Slant font_slant) {
45 return font_slant == SkFontStyle::Slant::kUpright_Slant
48}
49
50class DisplayListParagraphPainter : public skt::ParagraphPainter {
51 public:
52 //----------------------------------------------------------------------------
53 /// @brief Creates a |skt::ParagraphPainter| that draws to DisplayList.
54 ///
55 /// @param builder The display list builder.
56 /// @param[in] dl_paints The paints referenced by ID in the `drawX` methods.
57 /// @param[in] draw_path_effect If true, draw path effects directly by
58 /// drawing multiple lines instead of providing
59 // a path effect to the paint.
60 ///
61 /// @note Impeller does not (and will not) support path effects, but the
62 /// Skia backend does. That means that if we want to draw dashed
63 /// and dotted lines, we need to draw them directly using the
64 /// `drawLine` API instead of using a path effect.
65 ///
66 /// See https://github.com/flutter/flutter/issues/126673. It
67 /// probably makes sense to eventually make this a compile-time
68 /// decision (i.e. with `#ifdef`) instead of a runtime option.
69 DisplayListParagraphPainter(DisplayListBuilder* builder,
70 const std::vector<DlPaint>& dl_paints,
71 bool impeller_enabled)
72 : builder_(builder),
73 dl_paints_(dl_paints),
74 impeller_enabled_(impeller_enabled) {}
75
76 void drawTextBlob(const sk_sp<SkTextBlob>& blob,
77 SkScalar x,
78 SkScalar y,
79 const SkPaintOrID& paint) override {
80 if (!blob) {
81 return;
82 }
83 size_t paint_id = std::get<PaintID>(paint);
84 FML_DCHECK(paint_id < dl_paints_.size());
85
86#ifdef IMPELLER_SUPPORTS_RENDERING
87 if (impeller_enabled_) {
88 if (ShouldRenderAsPath(dl_paints_[paint_id])) {
90 auto transformed = path.makeTransform(SkMatrix::Translate(
91 x + blob->bounds().left(), y + blob->bounds().top()));
92 builder_->DrawPath(transformed, dl_paints_[paint_id]);
93 return;
94 }
95
96 builder_->DrawTextFrame(impeller::MakeTextFrameFromTextBlobSkia(blob), x,
97 y, dl_paints_[paint_id]);
98 return;
99 }
100#endif // IMPELLER_SUPPORTS_RENDERING
101 builder_->DrawTextBlob(blob, x, y, dl_paints_[paint_id]);
102 }
103
104 void drawTextShadow(const sk_sp<SkTextBlob>& blob,
105 SkScalar x,
106 SkScalar y,
108 SkScalar blur_sigma) override {
109 if (!blob) {
110 return;
111 }
113 paint.setColor(DlColor(color));
114 if (blur_sigma > 0.0) {
115 DlBlurMaskFilter filter(DlBlurStyle::kNormal, blur_sigma, false);
116 paint.setMaskFilter(&filter);
117 }
118 if (impeller_enabled_) {
119 builder_->DrawTextFrame(impeller::MakeTextFrameFromTextBlobSkia(blob), x,
120 y, paint);
121 return;
122 }
123 builder_->DrawTextBlob(blob, 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(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(rect, paint);
136 }
137
138 void drawPath(const SkPath& path,
139 const DecorationStyle& decor_style) override {
140 builder_->DrawPath(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 // We only support horizontal lines.
149 FML_DCHECK(y0 == y1);
150
151 // This function is called for both solid and dashed lines. If we're drawing
152 // a dashed line, and we're using the Impeller backend, then we need to draw
153 // the line directly using the `drawLine` API instead of using a path effect
154 // (because Impeller does not support path effects).
155 auto dash_path_effect = decor_style.getDashPathEffect();
156#ifdef IMPELLER_SUPPORTS_RENDERING
157 if (impeller_enabled_ && dash_path_effect) {
158 auto path = dashedLine(x0, x1, y0, *dash_path_effect);
159 builder_->DrawPath(path, toDlPaint(decor_style));
160 return;
161 }
162#endif // IMPELLER_SUPPORTS_RENDERING
163
164 auto paint = toDlPaint(decor_style);
165 if (dash_path_effect) {
166 setPathEffect(paint, *dash_path_effect);
167 }
168 builder_->DrawLine(SkPoint::Make(x0, y0), SkPoint::Make(x1, y1), paint);
169 }
170
171 void clipRect(const SkRect& rect) override {
172 builder_->ClipRect(rect, DlCanvas::ClipOp::kIntersect, false);
173 }
174
175 void translate(SkScalar dx, SkScalar dy) override {
176 builder_->Translate(dx, dy);
177 }
178
179 void save() override { builder_->Save(); }
180
181 void restore() override { builder_->Restore(); }
182
183 private:
184 SkPath dashedLine(SkScalar x0,
185 SkScalar x1,
186 SkScalar y0,
187 const DashPathEffect& dash_path_effect) {
188 auto dx = 0.0;
189 auto path = SkPath();
190 auto on = true;
191 auto length = x1 - x0;
192 while (dx < length) {
193 if (on) {
194 // Draw the on part of the dash.
195 path.moveTo(x0 + dx, y0);
196 dx += dash_path_effect.fOnLength;
197 path.lineTo(x0 + dx, y0);
198 } else {
199 // Skip the off part of the dash.
200 dx += dash_path_effect.fOffLength;
201 }
202 on = !on;
203 }
204
205 path.close();
206 return path;
207 }
208
209 bool ShouldRenderAsPath(const DlPaint& paint) const {
210 FML_DCHECK(impeller_enabled_);
211 // Text with non-trivial color sources or stroke paint mode should be
212 // rendered as a path when running on Impeller for correctness. These
213 // filters rely on having the glyph coverage, whereas regular text is
214 // drawn as rectangular texture samples.
215 return ((paint.getColorSource() && !paint.getColorSource()->asColor()) ||
216 paint.getDrawStyle() != DlDrawStyle::kFill);
217 }
218
219 DlPaint toDlPaint(const DecorationStyle& decor_style,
220 DlDrawStyle draw_style = DlDrawStyle::kStroke) {
222 paint.setDrawStyle(draw_style);
223 paint.setAntiAlias(true);
224 paint.setColor(DlColor(decor_style.getColor()));
225 paint.setStrokeWidth(decor_style.getStrokeWidth());
226 return paint;
227 }
228
229 void setPathEffect(DlPaint& paint, const DashPathEffect& dash_path_effect) {
230 // Impeller does not support path effects, so we should never be setting.
231 FML_DCHECK(!impeller_enabled_);
232
233 std::array<SkScalar, 2> intervals{dash_path_effect.fOnLength,
234 dash_path_effect.fOffLength};
235 auto effect = DlDashPathEffect::Make(intervals.data(), intervals.size(), 0);
236 paint.setPathEffect(effect);
237 }
238
239 DisplayListBuilder* builder_;
240 const std::vector<DlPaint>& dl_paints_;
241 const bool impeller_enabled_;
242};
243
244} // anonymous namespace
245
246ParagraphSkia::ParagraphSkia(std::unique_ptr<skt::Paragraph> paragraph,
247 std::vector<flutter::DlPaint>&& dl_paints,
248 bool impeller_enabled)
249 : paragraph_(std::move(paragraph)),
250 dl_paints_(dl_paints),
251 impeller_enabled_(impeller_enabled) {}
252
254 return SkScalarToDouble(paragraph_->getMaxWidth());
255}
256
258 return SkScalarToDouble(paragraph_->getHeight());
259}
260
262 return SkScalarToDouble(paragraph_->getLongestLine());
263}
264
265std::vector<LineMetrics>& ParagraphSkia::GetLineMetrics() {
266 if (!line_metrics_) {
267 std::vector<skt::LineMetrics> metrics;
268 paragraph_->getLineMetrics(metrics);
269
270 line_metrics_.emplace();
271 line_metrics_styles_.reserve(
272 std::accumulate(metrics.begin(), metrics.end(), 0,
273 [](const int a, const skt::LineMetrics& b) {
274 return a + b.fLineMetrics.size();
275 }));
276
277 for (const skt::LineMetrics& skm : metrics) {
278 LineMetrics& txtm = line_metrics_->emplace_back(
279 skm.fStartIndex, skm.fEndIndex, skm.fEndExcludingWhitespaces,
280 skm.fEndIncludingNewline, skm.fHardBreak);
281 txtm.ascent = skm.fAscent;
282 txtm.descent = skm.fDescent;
283 txtm.unscaled_ascent = skm.fUnscaledAscent;
284 txtm.height = skm.fHeight;
285 txtm.width = skm.fWidth;
286 txtm.left = skm.fLeft;
287 txtm.baseline = skm.fBaseline;
288 txtm.line_number = skm.fLineNumber;
289
290 for (const auto& sk_iter : skm.fLineMetrics) {
291 const skt::StyleMetrics& sk_style_metrics = sk_iter.second;
292 line_metrics_styles_.push_back(SkiaToTxt(*sk_style_metrics.text_style));
293 txtm.run_metrics.emplace(
294 std::piecewise_construct, std::forward_as_tuple(sk_iter.first),
295 std::forward_as_tuple(&line_metrics_styles_.back(),
296 sk_style_metrics.font_metrics));
297 }
298 }
299 }
300
301 return line_metrics_.value();
302}
303
305 skt::LineMetrics* lineMetrics) const {
306 return paragraph_->getLineMetricsAt(lineNumber, lineMetrics);
307};
308
310 return SkScalarToDouble(paragraph_->getMinIntrinsicWidth());
311}
312
314 return SkScalarToDouble(paragraph_->getMaxIntrinsicWidth());
315}
316
318 return SkScalarToDouble(paragraph_->getAlphabeticBaseline());
319}
320
322 return SkScalarToDouble(paragraph_->getIdeographicBaseline());
323}
324
326 return paragraph_->didExceedMaxLines();
327}
328
330 line_metrics_.reset();
331 line_metrics_styles_.clear();
332 paragraph_->layout(width);
333}
334
335bool ParagraphSkia::Paint(DisplayListBuilder* builder, double x, double y) {
336 DisplayListParagraphPainter painter(builder, dl_paints_, impeller_enabled_);
337 paragraph_->paint(&painter, x, y);
338 return true;
339}
340
341std::vector<Paragraph::TextBox> ParagraphSkia::GetRectsForRange(
342 size_t start,
343 size_t end,
344 RectHeightStyle rect_height_style,
345 RectWidthStyle rect_width_style) {
346 std::vector<skt::TextBox> skia_boxes = paragraph_->getRectsForRange(
347 start, end, static_cast<skt::RectHeightStyle>(rect_height_style),
348 static_cast<skt::RectWidthStyle>(rect_width_style));
349
350 std::vector<Paragraph::TextBox> boxes;
351 for (const skt::TextBox& skia_box : skia_boxes) {
352 boxes.emplace_back(skia_box.rect,
353 static_cast<TextDirection>(skia_box.direction));
354 }
355
356 return boxes;
357}
358
359std::vector<Paragraph::TextBox> ParagraphSkia::GetRectsForPlaceholders() {
360 std::vector<skt::TextBox> skia_boxes = paragraph_->getRectsForPlaceholders();
361
362 std::vector<Paragraph::TextBox> boxes;
363 for (const skt::TextBox& skia_box : skia_boxes) {
364 boxes.emplace_back(skia_box.rect,
365 static_cast<TextDirection>(skia_box.direction));
366 }
367
368 return boxes;
369}
370
372 double dx,
373 double dy) {
375 paragraph_->getGlyphPositionAtCoordinate(dx, dy);
376
377 return ParagraphSkia::PositionWithAffinity(
378 skia_pos.position, static_cast<Affinity>(skia_pos.affinity));
379}
380
382 unsigned offset,
384 return paragraph_->getGlyphInfoAtUTF16Offset(offset, glyphInfo);
385}
386
388 double dx,
389 double dy,
391 return paragraph_->getClosestUTF16GlyphInfoAt(dx, dy, glyphInfo);
392};
393
395 skt::SkRange<size_t> range = paragraph_->getWordBoundary(offset);
396 return Paragraph::Range<size_t>(range.start, range.end);
397}
398
400 return paragraph_->lineNumber();
401}
402
403int ParagraphSkia::GetLineNumberAt(size_t codeUnitIndex) const {
404 return paragraph_->getLineNumberAtUTF16Offset(codeUnitIndex);
405}
406
407TextStyle ParagraphSkia::SkiaToTxt(const skt::TextStyle& skia) {
409
410 txt.color = skia.getColor();
411 txt.decoration = static_cast<TextDecoration>(skia.getDecorationType());
412 txt.decoration_color = skia.getDecorationColor();
413 txt.decoration_style =
414 static_cast<TextDecorationStyle>(skia.getDecorationStyle());
415 txt.decoration_thickness_multiplier =
416 SkScalarToDouble(skia.getDecorationThicknessMultiplier());
417 txt.font_weight = GetTxtFontWeight(skia.getFontStyle().weight());
418 txt.font_style = GetTxtFontStyle(skia.getFontStyle().slant());
419
420 txt.text_baseline = static_cast<TextBaseline>(skia.getTextBaseline());
421
422 for (const SkString& font_family : skia.getFontFamilies()) {
423 txt.font_families.emplace_back(font_family.c_str());
424 }
425
426 txt.font_size = SkScalarToDouble(skia.getFontSize());
427 txt.letter_spacing = SkScalarToDouble(skia.getLetterSpacing());
428 txt.word_spacing = SkScalarToDouble(skia.getWordSpacing());
429 txt.height = SkScalarToDouble(skia.getHeight());
430
431 txt.locale = skia.getLocale().c_str();
432 if (skia.hasBackground()) {
433 PaintID background_id = std::get<PaintID>(skia.getBackgroundPaintOrID());
434 txt.background = dl_paints_[background_id];
435 }
436 if (skia.hasForeground()) {
437 PaintID foreground_id = std::get<PaintID>(skia.getForegroundPaintOrID());
438 txt.foreground = dl_paints_[foreground_id];
439 }
440
441 txt.text_shadows.clear();
442 for (const skt::TextShadow& skia_shadow : skia.getShadows()) {
443 txt::TextShadow shadow;
444 shadow.offset = skia_shadow.fOffset;
445 shadow.blur_sigma = skia_shadow.fBlurSigma;
446 shadow.color = skia_shadow.fColor;
447 txt.text_shadows.emplace_back(shadow);
448 }
449
450 return txt;
451}
452
453} // namespace txt
SkColor4f color
uint32_t SkColor
Definition SkColor.h:37
#define SkScalarToDouble(x)
Definition SkScalar.h:63
static SkMatrix Translate(SkScalar dx, SkScalar dy)
Definition SkMatrix.h:91
T * get() const
Definition SkRefCnt.h:303
static SkPath GetPath(SkTextBlob *textBlob)
const TextStyle * text_style
Definition Metrics.h:17
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
const Paint & paint
float SkScalar
Definition extension.cpp:12
static bool b
struct MyStruct a[10]
#define FML_DCHECK(condition)
Definition logging.h:103
size_t length
static void drawPath(SkPath &path, SkCanvas *canvas, SkColor color, const SkRect &clip, SkPaint::Cap cap, SkPaint::Join join, SkPaint::Style style, SkPathFillType fill, SkScalar strokeWidth)
Definition linepaths.cpp:22
double y
double x
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 switches.h:57
DlDrawStyle
Definition dl_paint.h:20
std::shared_ptr< TextFrame > MakeTextFrameFromTextBlobSkia(const sk_sp< SkTextBlob > &blob)
Definition ref_ptr.h:256
skt::ParagraphPainter::PaintID PaintID
FontWeight
Definition font_weight.h:22
FontStyle
Definition font_style.h:22
TextDecorationStyle
int32_t width
Point offset
static constexpr SkPoint Make(float x, float y)