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