• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 Google LLC.
2 #ifdef ENABLE_TEXT_ENHANCE
3 #include <map>
4 #endif
5 #include "include/core/SkPathBuilder.h"
6 #include "modules/skparagraph/src/Decorations.h"
7 
8 using namespace skia_private;
9 
10 namespace skia {
11 namespace textlayout {
12 #ifdef ENABLE_TEXT_ENHANCE
13 static const std::map<SkPaint::Style, RSDrawing::Paint::PaintStyle> PAINT_STYLE = {
14     {SkPaint::kFill_Style, RSDrawing::Paint::PaintStyle::PAINT_FILL},
15     {SkPaint::kStroke_Style, RSDrawing::Paint::PaintStyle::PAINT_STROKE},
16     {SkPaint::kStrokeAndFill_Style, RSDrawing::Paint::PaintStyle::PAINT_FILL_STROKE},
17 };
18 #endif
19 namespace {
draw_line_as_rect(ParagraphPainter * painter,SkScalar x,SkScalar y,SkScalar width,const ParagraphPainter::DecorationStyle & decorStyle)20 void draw_line_as_rect(ParagraphPainter* painter, SkScalar x, SkScalar y, SkScalar width,
21                        const ParagraphPainter::DecorationStyle& decorStyle) {
22     SkASSERT(decorStyle.skPaint().getPathEffect() == nullptr);
23     SkASSERT(decorStyle.skPaint().getStrokeCap() == SkPaint::kButt_Cap);
24     SkASSERT(decorStyle.skPaint().getStrokeWidth() > 0);   // this trick won't work for hairlines
25 
26     float radius = decorStyle.getStrokeWidth() * 0.5f;
27     painter->drawFilledRect({x, y - radius, x + width, y + radius}, decorStyle);
28 }
29 
30 const float kDoubleDecorationSpacing = 3.0f;
31 }  // namespace
32 
33 #ifdef ENABLE_TEXT_ENHANCE
updateDecorationPosition(TextDecoration decorationMode,SkScalar baselineShift,const TextLine::ClipContext & context,SkScalar & positionY)34 void Decorations::updateDecorationPosition(TextDecoration decorationMode, SkScalar baselineShift,
35     const TextLine::ClipContext& context, SkScalar& positionY) {
36     switch (getVerticalAlignment()) {
37         case TextVerticalAlign::TOP:
38             if (decorationMode == TextDecoration::kOverline) {
39                 positionY = context.run->getTopInGroup() - baselineShift;
40             }
41             break;
42         case TextVerticalAlign::CENTER:
43             if (decorationMode == TextDecoration::kLineThrough) {
44                 // Position the line-through decoration at the vertical center of the line
45                 // The division by 2 places it in the middle between the line
46                 positionY = fDecorationContext.lineHeight / 2 - baselineShift;
47             }
48             break;
49         case TextVerticalAlign::BOTTOM:
50             if (decorationMode == TextDecoration::kUnderline) {
51                 positionY = fDecorationContext.lineHeight - baselineShift;
52             }
53             break;
54         default:
55             break;
56     }
57 }
58 
calculateThickness(const TextStyle & textStyle,const TextLine::ClipContext & context)59 SkScalar Decorations::calculateThickness(const TextStyle& textStyle, const TextLine::ClipContext& context) {
60     calculateThickness(textStyle, const_cast<RSFont&>(context.run->font()).GetTypeface());
61     return fThickness;
62 }
63 
paint(ParagraphPainter * painter,const TextStyle & textStyle,const TextLine::ClipContext & context,SkScalar baseline)64 void Decorations::paint(ParagraphPainter* painter, const TextStyle& textStyle, const TextLine::ClipContext& context, SkScalar baseline) {
65     if (textStyle.getDecorationType() == TextDecoration::kNoDecoration) {
66         return;
67     }
68 
69     // Get thickness and position
70     calculateThickness(textStyle, const_cast<RSFont&>(context.run->font()).GetTypeface());
71 
72     for (auto decoration : AllTextDecorations) {
73         if ((textStyle.getDecorationType() & decoration) == 0) {
74             continue;
75         }
76 
77         SkScalar textBaselineShift = 0.0f;
78         if (getVerticalAlignment() == TextVerticalAlign::BASELINE) {
79             textBaselineShift = textStyle.getTotalVerticalShift();
80         } else {
81             textBaselineShift = context.run->baselineShift();
82         }
83         calculatePosition(decoration,
84                           decoration == TextDecoration::kOverline
85                           ? context.run->correctAscent() - context.run->ascent()
86                           : context.run->correctAscent(),
87                           textStyle, textBaselineShift, *context.run);
88 
89         calculatePaint(textStyle);
90 
91         auto width = context.clip.width();
92         if (context.fIsTrimTrailingSpaceWidth) {
93             width = std::max(width - context.fTrailingSpaceWidth, 0.0f);
94         }
95 
96         SkScalar x = context.clip.left();
97         SkScalar y = (TextDecoration::kUnderline == decoration) ?
98             fPosition : (context.clip.top() + fPosition);
99         updateDecorationPosition(decoration, textBaselineShift, context, y);
100         baseline += context.run->getVerticalAlignShift();
101         bool drawGaps = textStyle.getDecorationMode() == TextDecorationMode::kGaps &&
102                         textStyle.getDecorationType() == TextDecoration::kUnderline;
103 
104         switch (textStyle.getDecorationStyle()) {
105           case TextDecorationStyle::kWavy: {
106               if (drawGaps) {
107                   calculateAvoidanceWaves(textStyle, context.clip);
108                   fPath.Offset(x, y);
109                   painter->drawPath(fPath, fDecorStyle);
110                   break;
111               }
112               calculateWaves(textStyle, context.clip);
113               fPath.Offset(x, y);
114               painter->drawPath(fPath, fDecorStyle);
115               break;
116           }
117           case TextDecorationStyle::kDouble: {
118               SkScalar bottom = y + kDoubleDecorationSpacing * fThickness / 2.0;
119               if (drawGaps) {
120                   SkScalar left = x - context.fTextShift;
121                   painter->translate(context.fTextShift, 0);
122                   calculateGaps(context, SkRect::MakeXYWH(left, y, width, fThickness), baseline, fThickness, textStyle);
123                   painter->drawPath(fPath, fDecorStyle);
124                   calculateGaps(context, SkRect::MakeXYWH(left, bottom, width, fThickness), baseline,
125                       fThickness, textStyle);
126                   painter->drawPath(fPath, fDecorStyle);
127               } else {
128                   draw_line_as_rect(painter, x,      y, width, fDecorStyle);
129                   draw_line_as_rect(painter, x, bottom, width, fDecorStyle);
130               }
131               break;
132           }
133           case TextDecorationStyle::kDashed:
134           case TextDecorationStyle::kDotted:
135               if (drawGaps) {
136                   SkScalar left = x - context.fTextShift;
137                   painter->translate(context.fTextShift, 0);
138                   calculateGaps(context, SkRect::MakeXYWH(left, y, width, fThickness), baseline, fThickness, textStyle);
139                   painter->drawPath(fPath, fDecorStyle);
140               } else {
141                   painter->drawLine(x, y, x + width, y, fDecorStyle);
142               }
143               break;
144           case TextDecorationStyle::kSolid:
145               if (drawGaps) {
146                   SkScalar left = x - context.fTextShift;
147                   painter->translate(context.fTextShift, 0);
148                   SkRect rect = SkRect::MakeXYWH(left, y, width, fThickness);
149                   calculateGaps(context, rect, baseline, fThickness, textStyle);
150                   painter->drawPath(fPath, fDecorStyle);
151               } else {
152                   draw_line_as_rect(painter, x, y, width, fDecorStyle);
153               }
154               break;
155           default:break;
156         }
157     }
158 }
159 
ConvertDrawingStyle(SkPaint::Style skStyle)160 static RSDrawing::Paint::PaintStyle ConvertDrawingStyle(SkPaint::Style skStyle) {
161     if (PAINT_STYLE.find(skStyle) != PAINT_STYLE.end()) {
162         return PAINT_STYLE.at(skStyle);
163     } else {
164         return RSDrawing::Paint::PaintStyle::PAINT_NONE;
165     }
166 }
167 
ConvertDecorStyle(const ParagraphPainter::DecorationStyle & decorStyle)168 static RSDrawing::Paint ConvertDecorStyle(const ParagraphPainter::DecorationStyle& decorStyle) {
169     const SkPaint& decorPaint = decorStyle.skPaint();
170     RSDrawing::Paint paint;
171     paint.SetStyle(ConvertDrawingStyle(decorPaint.getStyle()));
172     paint.SetAntiAlias(decorPaint.isAntiAlias());
173     paint.SetColor(decorPaint.getColor());
174     paint.SetWidth(decorPaint.getStrokeWidth());
175     if (decorStyle.getDashPathEffect().has_value()) {
176         auto dashPathEffect = decorStyle.getDashPathEffect().value();
177         RSDrawing::scalar intervals[] = {dashPathEffect.fOnLength, dashPathEffect.fOffLength,
178             dashPathEffect.fOnLength, dashPathEffect.fOffLength};
179         size_t count = sizeof(intervals) / sizeof(intervals[0]);
180         auto pathEffect1 = RSDrawing::PathEffect::CreateDashPathEffect(intervals, count, 0.0f);
181         auto pathEffect2 = RSDrawing::PathEffect::CreateDiscretePathEffect(0, 0);
182         auto pathEffect = RSDrawing::PathEffect::CreateComposePathEffect(*pathEffect1.get(), *pathEffect2.get());
183         paint.SetPathEffect(pathEffect);
184     }
185     return paint;
186 }
187 
calculateGaps(const TextLine::ClipContext & context,const SkRect & rect,SkScalar baseline,SkScalar halo,const TextStyle & textStyle)188 void Decorations::calculateGaps(const TextLine::ClipContext& context, const SkRect& rect,
189     SkScalar baseline, SkScalar halo, const TextStyle& textStyle) {
190     // Create a special text blob for decorations
191     RSTextBlobBuilder builder;
192     context.run->copyTo(builder, SkToU32(context.pos), context.size);
193     auto blob = builder.Make();
194     if (!blob) {
195         // There is no text really
196         return;
197     }
198     SkScalar top = textStyle.getHeight() != 0 ? this->fDecorationContext.textBlobTop + baseline : rect.fTop;
199     // Since we do not shift down the text by {baseline}
200     // (it now happens on drawTextBlob but we do not draw text here)
201     // we have to shift up the bounds to compensate
202     // This baseline thing ends with getIntercepts
203     const SkScalar bounds[2] = {top - baseline, top + halo - baseline};
204     RSDrawing::Paint paint = ConvertDecorStyle(fDecorStyle);
205     auto count = blob->GetIntercepts(bounds, nullptr, &paint);
206     skia_private::TArray<SkScalar> intersections(count);
207     intersections.resize(count);
208     blob->GetIntercepts(bounds, intersections.data(), &paint);
209 
210     RSPath path;
211     auto start = rect.fLeft;
212     path.MoveTo(rect.fLeft, rect.fTop);
213     for (int i = 0; i < intersections.size(); i += 2) {
214         auto end = intersections[i] - halo;
215         if (end - start >= halo) {
216             start = intersections[i + 1] + halo;
217             path.LineTo(end, rect.fTop);
218             path.MoveTo(start, rect.fTop);
219         } else {
220             start = intersections[i + 1] + halo;
221             path.MoveTo(start, rect.fTop);
222         }
223     }
224     if (!intersections.empty() && (rect.fRight - start > halo)) {
225         path.LineTo(rect.fRight, rect.fTop);
226     }
227 
228     if (intersections.empty()) {
229         path.LineTo(rect.fRight, rect.fTop);
230     }
231     fPath = path;
232 }
233 
calculateAvoidanceWaves(const TextStyle & textStyle,SkRect clip)234 void Decorations::calculateAvoidanceWaves(const TextStyle& textStyle, SkRect clip) {
235     fPath.Reset();
236     int wave_count = 0;
237     const int step = 2;
238     const float zer = 0.01;
239     SkScalar x_start = 0;
240     SkScalar quarterWave = fThickness;
241     if (quarterWave <= zer) {
242         return;
243     }
244     fPath.MoveTo(0, 0);
245     while (x_start + quarterWave * step < clip.width()) {
246         fPath.RQuadTo(quarterWave,
247             wave_count % step != 0 ? quarterWave : -quarterWave,
248             quarterWave * step, 0);
249         x_start += quarterWave * step;
250         ++wave_count;
251     }
252 
253     // The rest of the wave
254     auto remaining = clip.width() - x_start;
255     if (remaining > 0) {
256         double x1 = remaining / step;
257         double y1 = remaining / step * (wave_count % step == 0 ? -1 : 1);
258         double x2 = remaining;
259         double y2 = (remaining - remaining * remaining / (quarterWave * step)) *
260                     (wave_count % step == 0 ? -1 : 1);
261         fPath.RQuadTo(x1, y1, x2, y2);
262     }
263 }
264 
calculateThickness(TextStyle textStyle,std::shared_ptr<RSTypeface> typeface)265 void Decorations::calculateThickness(TextStyle textStyle, std::shared_ptr<RSTypeface> typeface) {
266     textStyle.setTypeface(std::move(typeface));
267     textStyle.getFontMetrics(&fFontMetrics);
268     if (textStyle.getDecoration().fType == TextDecoration::kUnderline &&
269         !SkScalarNearlyZero(fThickness)) {
270         return;
271     }
272 
273     fThickness = textStyle.getFontSize() * UNDER_LINE_THICKNESS_RATIO;
274     fThickness *= textStyle.getDecorationThicknessMultiplier();
275 }
276 
calculatePosition(TextDecoration decoration,SkScalar ascent,const TextStyle & textStyle,SkScalar textBaselineShift,const Run & run)277 void Decorations::calculatePosition(TextDecoration decoration, SkScalar ascent,
278     const TextStyle& textStyle, SkScalar textBaselineShift, const Run& run) {
279     switch (decoration) {
280         case TextDecoration::kUnderline:
281             fPosition = fDecorationContext.underlinePosition + run.getVerticalAlignShift();
282             break;
283         case TextDecoration::kOverline:
284             fPosition = (textStyle.getDecorationStyle() == TextDecorationStyle::kWavy ?
285                 fThickness : fThickness / 2.0f) - ascent;
286             break;
287         case TextDecoration::kLineThrough:
288             fPosition = LINE_THROUGH_TOP * textStyle.getCorrectFontSize() - ascent + textBaselineShift;
289             break;
290         default:
291             break;
292     }
293 }
294 
calculateWaves(const TextStyle & textStyle,SkRect clip)295 void Decorations::calculateWaves(const TextStyle& textStyle, SkRect clip) {
296     if (SkScalarNearlyZero(fThickness) || fThickness < 0) {
297         return;
298     }
299     fPath.Reset();
300     int wave_count = 0;
301     SkScalar x_start = 0;
302     SkScalar quarterWave = fThickness;
303     fPath.MoveTo(0, 0);
304 
305     while (x_start + quarterWave * 2 < clip.width()) {
306         fPath.RQuadTo(quarterWave,
307                      wave_count % 2 != 0 ? quarterWave : -quarterWave,
308                      quarterWave * 2,
309                      0);
310         x_start += quarterWave * 2;
311         ++wave_count;
312     }
313 
314     // The rest of the wave
315     auto remaining = clip.width() - x_start;
316     if (remaining > 0) {
317         double x1 = remaining / 2;
318         double y1 = remaining / 2 * (wave_count % 2 == 0 ? -1 : 1);
319         double x2 = remaining;
320         double y2 = (remaining - remaining * remaining / (quarterWave * 2)) *
321                     (wave_count % 2 == 0 ? -1 : 1);
322         fPath.RQuadTo(x1, y1, x2, y2);
323     }
324 }
325 #else
paint(ParagraphPainter * painter,const TextStyle & textStyle,const TextLine::ClipContext & context,SkScalar baseline)326 void Decorations::paint(ParagraphPainter* painter, const TextStyle& textStyle, const TextLine::ClipContext& context, SkScalar baseline) {
327     if (textStyle.getDecorationType() == TextDecoration::kNoDecoration) {
328         return;
329     }
330 
331     // Get thickness and position
332     calculateThickness(textStyle, context.run->font().refTypeface());
333 
334     for (auto decoration : AllTextDecorations) {
335         if ((textStyle.getDecorationType() & decoration) == 0) {
336             continue;
337         }
338 
339         calculatePosition(decoration,
340                           decoration == TextDecoration::kOverline
341                           ? context.run->correctAscent() - context.run->ascent()
342                           : context.run->correctAscent());
343 
344         calculatePaint(textStyle);
345 
346         auto width = context.clip.width();
347         SkScalar x = context.clip.left();
348         SkScalar y = context.clip.top() + fPosition;
349 
350         bool drawGaps = textStyle.getDecorationMode() == TextDecorationMode::kGaps &&
351                         textStyle.getDecorationType() == TextDecoration::kUnderline;
352 
353         switch (textStyle.getDecorationStyle()) {
354           case TextDecorationStyle::kWavy: {
355               calculateWaves(textStyle, context.clip);
356               fPath.offset(x, y);
357               painter->drawPath(fPath, fDecorStyle);
358               break;
359           }
360           case TextDecorationStyle::kDouble: {
361               SkScalar bottom = y + kDoubleDecorationSpacing;
362               if (drawGaps) {
363                   SkScalar left = x - context.fTextShift;
364                   painter->translate(context.fTextShift, 0);
365                   calculateGaps(context, SkRect::MakeXYWH(left, y, width, fThickness), baseline, fThickness);
366                   painter->drawPath(fPath, fDecorStyle);
367                   calculateGaps(context, SkRect::MakeXYWH(left, bottom, width, fThickness), baseline, fThickness);
368                   painter->drawPath(fPath, fDecorStyle);
369               } else {
370                   draw_line_as_rect(painter, x,      y, width, fDecorStyle);
371                   draw_line_as_rect(painter, x, bottom, width, fDecorStyle);
372               }
373               break;
374           }
375           case TextDecorationStyle::kDashed:
376           case TextDecorationStyle::kDotted:
377               if (drawGaps) {
378                   SkScalar left = x - context.fTextShift;
379                   painter->translate(context.fTextShift, 0);
380                   calculateGaps(context, SkRect::MakeXYWH(left, y, width, fThickness), baseline, 0);
381                   painter->drawPath(fPath, fDecorStyle);
382               } else {
383                   painter->drawLine(x, y, x + width, y, fDecorStyle);
384               }
385               break;
386           case TextDecorationStyle::kSolid:
387               if (drawGaps) {
388                   SkScalar left = x - context.fTextShift;
389                   painter->translate(context.fTextShift, 0);
390                   calculateGaps(context, SkRect::MakeXYWH(left, y, width, fThickness), baseline, fThickness);
391                   painter->drawPath(fPath, fDecorStyle);
392               } else {
393                   draw_line_as_rect(painter, x, y, width, fDecorStyle);
394               }
395               break;
396           default:break;
397         }
398     }
399 }
400 
calculateGaps(const TextLine::ClipContext & context,const SkRect & rect,SkScalar baseline,SkScalar halo)401 void Decorations::calculateGaps(const TextLine::ClipContext& context, const SkRect& rect,
402                                 SkScalar baseline, SkScalar halo) {
403     // Create a special text blob for decorations
404     SkTextBlobBuilder builder;
405     context.run->copyTo(builder,
406                       SkToU32(context.pos),
407                       context.size);
408     sk_sp<SkTextBlob> blob = builder.make();
409     if (!blob) {
410         // There is no text really
411         return;
412     }
413     // Since we do not shift down the text by {baseline}
414     // (it now happens on drawTextBlob but we do not draw text here)
415     // we have to shift up the bounds to compensate
416     // This baseline thing ends with getIntercepts
417     const SkScalar bounds[2] = {rect.fTop - baseline, rect.fBottom - baseline};
418     const SkPaint& decorPaint = fDecorStyle.skPaint();
419     auto count = blob->getIntercepts(bounds, nullptr, &decorPaint);
420     TArray<SkScalar> intersections(count);
421     intersections.resize(count);
422     blob->getIntercepts(bounds, intersections.data(), &decorPaint);
423 
424     SkPathBuilder path;
425     auto start = rect.fLeft;
426     path.moveTo(rect.fLeft, rect.fTop);
427     for (int i = 0; i < intersections.size(); i += 2) {
428         auto end = intersections[i] - halo;
429         if (end - start >= halo) {
430             start = intersections[i + 1] + halo;
431             path.lineTo(end, rect.fTop).moveTo(start, rect.fTop);
432         }
433     }
434     if (!intersections.empty() && (rect.fRight - start > halo)) {
435         path.lineTo(rect.fRight, rect.fTop);
436     }
437     fPath = path.detach();
438 }
439 
calculateThickness(TextStyle textStyle,sk_sp<SkTypeface> typeface)440 void Decorations::calculateThickness(TextStyle textStyle, sk_sp<SkTypeface> typeface) {
441 
442     textStyle.setTypeface(std::move(typeface));
443     textStyle.getFontMetrics(&fFontMetrics);
444 
445     fThickness = textStyle.getFontSize() / 14.0f;
446 
447     if ((fFontMetrics.fFlags & SkFontMetrics::FontMetricsFlags::kUnderlineThicknessIsValid_Flag) &&
448          fFontMetrics.fUnderlineThickness > 0) {
449         fThickness = fFontMetrics.fUnderlineThickness;
450     }
451 
452     if (textStyle.getDecorationType() == TextDecoration::kLineThrough) {
453         if ((fFontMetrics.fFlags & SkFontMetrics::FontMetricsFlags::kStrikeoutThicknessIsValid_Flag) &&
454              fFontMetrics.fStrikeoutThickness > 0) {
455             fThickness = fFontMetrics.fStrikeoutThickness;
456         }
457     }
458     fThickness *= textStyle.getDecorationThicknessMultiplier();
459 }
460 
calculatePosition(TextDecoration decoration,SkScalar ascent)461 void Decorations::calculatePosition(TextDecoration decoration, SkScalar ascent) {
462     switch (decoration) {
463       case TextDecoration::kUnderline:
464           if ((fFontMetrics.fFlags & SkFontMetrics::FontMetricsFlags::kUnderlinePositionIsValid_Flag) &&
465                fFontMetrics.fUnderlinePosition > 0) {
466             fPosition  = fFontMetrics.fUnderlinePosition;
467           } else {
468             fPosition = fThickness;
469           }
470           fPosition -= ascent;
471           break;
472       case TextDecoration::kOverline:
473           fPosition = - ascent;
474         break;
475       case TextDecoration::kLineThrough: {
476           fPosition = (fFontMetrics.fFlags & SkFontMetrics::FontMetricsFlags::kStrikeoutPositionIsValid_Flag)
477                      ? fFontMetrics.fStrikeoutPosition
478                      : fFontMetrics.fXHeight / -2;
479           fPosition -= ascent;
480           break;
481       }
482       default:SkASSERT(false);
483           break;
484     }
485 }
486 
calculateWaves(const TextStyle & textStyle,SkRect clip)487 void Decorations::calculateWaves(const TextStyle& textStyle, SkRect clip) {
488     fPath.reset();
489     int wave_count = 0;
490     SkScalar x_start = 0;
491     SkScalar quarterWave = fThickness;
492     fPath.moveTo(0, 0);
493     while (x_start + quarterWave * 2 < clip.width()) {
494         fPath.rQuadTo(quarterWave,
495                      wave_count % 2 != 0 ? quarterWave : -quarterWave,
496                      quarterWave * 2,
497                      0);
498         x_start += quarterWave * 2;
499         ++wave_count;
500     }
501 
502     // The rest of the wave
503     auto remaining = clip.width() - x_start;
504     if (remaining > 0) {
505         double x1 = remaining / 2;
506         double y1 = remaining / 2 * (wave_count % 2 == 0 ? -1 : 1);
507         double x2 = remaining;
508         double y2 = (remaining - remaining * remaining / (quarterWave * 2)) *
509                     (wave_count % 2 == 0 ? -1 : 1);
510         fPath.rQuadTo(x1, y1, x2, y2);
511     }
512 }
513 #endif
514 
calculatePaint(const TextStyle & textStyle)515 void Decorations::calculatePaint(const TextStyle& textStyle) {
516     std::optional<ParagraphPainter::DashPathEffect> dashPathEffect;
517     SkScalar scaleFactor = textStyle.getFontSize() / 14.f;
518     switch (textStyle.getDecorationStyle()) {
519             // Note: the intervals are scaled by the thickness of the line, so it is
520             // possible to change spacing by changing the decoration_thickness
521             // property of TextStyle.
522         case TextDecorationStyle::kDotted: {
523             dashPathEffect.emplace(1.0f * scaleFactor, 1.5f * scaleFactor);
524             break;
525         }
526             // Note: the intervals are scaled by the thickness of the line, so it is
527             // possible to change spacing by changing the decoration_thickness
528             // property of TextStyle.
529         case TextDecorationStyle::kDashed: {
530             dashPathEffect.emplace(4.0f * scaleFactor, 2.0f * scaleFactor);
531             break;
532         }
533         default: break;
534     }
535 
536     SkColor color = (textStyle.getDecorationColor() == SK_ColorTRANSPARENT)
537         ? textStyle.getColor()
538         : textStyle.getDecorationColor();
539 
540     fDecorStyle = ParagraphPainter::DecorationStyle(color, fThickness, dashPathEffect);
541 }
542 
543 }  // namespace textlayout
544 }  // namespace skia
545