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