Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 18db52f

Browse files
RusinoSkia Commit-Bot
authored andcommitted
Skips in underline decorations
Bug: skia:10166 Change-Id: Ib1d71f69d8647840f71c8599ca3517cb575bf945 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/287620 Reviewed-by: Ben Wagner <[email protected]> Commit-Queue: Julia Lavrova <[email protected]>
1 parent 041232a commit 18db52f

File tree

9 files changed

+350
-170
lines changed

9 files changed

+350
-170
lines changed

modules/skparagraph/include/TextStyle.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ constexpr TextDecoration AllTextDecorations[] = {
5050

5151
enum TextDecorationStyle { kSolid, kDouble, kDotted, kDashed, kWavy };
5252

53+
enum TextDecorationMode { kGaps, kThrough };
54+
5355
enum StyleType {
5456
kNone,
5557
kAllAttributes,
@@ -64,12 +66,14 @@ enum StyleType {
6466

6567
struct Decoration {
6668
TextDecoration fType;
69+
TextDecorationMode fMode;
6770
SkColor fColor;
6871
TextDecorationStyle fStyle;
6972
SkScalar fThicknessMultiplier;
7073

7174
bool operator==(const Decoration& other) const {
7275
return this->fType == other.fType &&
76+
this->fMode == other.fMode &&
7377
this->fColor == other.fColor &&
7478
this->fStyle == other.fStyle &&
7579
this->fThicknessMultiplier == other.fThicknessMultiplier;
@@ -181,12 +185,14 @@ class TextStyle {
181185
// Decorations
182186
Decoration getDecoration() const { return fDecoration; }
183187
TextDecoration getDecorationType() const { return fDecoration.fType; }
188+
TextDecorationMode getDecorationMode() const { return fDecoration.fMode; }
184189
SkColor getDecorationColor() const { return fDecoration.fColor; }
185190
TextDecorationStyle getDecorationStyle() const { return fDecoration.fStyle; }
186191
SkScalar getDecorationThicknessMultiplier() const {
187192
return fDecoration.fThicknessMultiplier;
188193
}
189194
void setDecoration(TextDecoration decoration) { fDecoration.fType = decoration; }
195+
void setDecorationMode(TextDecorationMode mode) { fDecoration.fMode = mode; }
190196
void setDecorationStyle(TextDecorationStyle style) { fDecoration.fStyle = style; }
191197
void setDecorationColor(SkColor color) { fDecoration.fColor = color; }
192198
void setDecorationThicknessMultiplier(SkScalar m) { fDecoration.fThicknessMultiplier = m; }

modules/skparagraph/skparagraph.gni

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ skparagraph_public = [
1919
]
2020

2121
skparagraph_sources = [
22+
"$_src/Decorations.cpp",
23+
"$_src/Decorations.h",
2224
"$_src/FontCollection.cpp",
2325
"$_src/Iterators.h",
2426
"$_src/OneLineShaper.cpp",
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
// Copyright 2020 Google LLC.
2+
#include "include/effects/SkDashPathEffect.h"
3+
#include "include/effects/SkDiscretePathEffect.h"
4+
#include "modules/skparagraph/src/Decorations.h"
5+
6+
namespace skia {
7+
namespace textlayout {
8+
9+
static const float kDoubleDecorationSpacing = 3.0f;
10+
void Decorations::paint(SkCanvas* canvas, const TextStyle& textStyle, const TextLine::ClipContext& context, SkScalar baseline, SkScalar shift) {
11+
if (textStyle.getDecorationType() == TextDecoration::kNoDecoration) {
12+
return;
13+
}
14+
15+
// Get thickness and position
16+
calculateThickness(textStyle, context.run->font().refTypeface());
17+
18+
for (auto decoration : AllTextDecorations) {
19+
if ((textStyle.getDecorationType() & decoration) == 0) {
20+
continue;
21+
}
22+
23+
calculatePosition(decoration, context.run->correctAscent());
24+
25+
calculatePaint(textStyle);
26+
27+
auto width = context.clip.width();
28+
SkScalar x = context.clip.left();
29+
SkScalar y = context.clip.top() + fPosition;
30+
31+
bool drawGaps = textStyle.getDecorationMode() == TextDecorationMode::kGaps &&
32+
textStyle.getDecorationType() == TextDecoration::kUnderline;
33+
34+
switch (textStyle.getDecorationStyle()) {
35+
case TextDecorationStyle::kWavy: {
36+
calculateWaves(textStyle, context.clip);
37+
fPath.offset(x, y);
38+
canvas->drawPath(fPath, fPaint);
39+
break;
40+
}
41+
case TextDecorationStyle::kDouble: {
42+
SkScalar bottom = y + kDoubleDecorationSpacing;
43+
if (drawGaps) {
44+
SkScalar left = x - context.fTextShift;
45+
canvas->translate(context.fTextShift, 0);
46+
calculateGaps(context, left, left + width, y, y + fThickness, baseline, fThickness);
47+
canvas->drawPath(fPath, fPaint);
48+
calculateGaps(context, left, left + width, bottom, bottom + fThickness, baseline, fThickness);
49+
canvas->drawPath(fPath, fPaint);
50+
} else {
51+
canvas->drawLine(x, y, x + width, y, fPaint);
52+
canvas->drawLine(x, bottom, x + width, bottom, fPaint);
53+
}
54+
break;
55+
}
56+
case TextDecorationStyle::kDashed:
57+
case TextDecorationStyle::kDotted:
58+
if (drawGaps) {
59+
SkScalar left = x - context.fTextShift;
60+
canvas->translate(context.fTextShift, 0);
61+
calculateGaps(context, left, left + width, y, y + fThickness, baseline, 0);
62+
canvas->drawPath(fPath, fPaint);
63+
} else {
64+
canvas->drawLine(x, y, x + width, y, fPaint);
65+
}
66+
break;
67+
case TextDecorationStyle::kSolid:
68+
if (drawGaps) {
69+
SkScalar left = x - context.fTextShift;
70+
canvas->translate(context.fTextShift, 0);
71+
calculateGaps(context, left, left + width, y, y + fThickness, baseline, fThickness);
72+
canvas->drawPath(fPath, fPaint);
73+
} else {
74+
canvas->drawLine(x, y, x + width, y, fPaint);
75+
}
76+
break;
77+
default:break;
78+
}
79+
80+
canvas->save();
81+
canvas->restore();
82+
}
83+
}
84+
85+
void Decorations::calculateGaps(const TextLine::ClipContext& context, SkScalar x0, SkScalar x1, SkScalar y0, SkScalar y1, SkScalar baseline, SkScalar halo) {
86+
87+
fPath.reset();
88+
89+
// Create a special textblob for decorations
90+
SkTextBlobBuilder builder;
91+
context.run->copyTo(builder,
92+
SkToU32(context.pos),
93+
context.size,
94+
SkVector::Make(0, baseline));
95+
auto blob = builder.make();
96+
97+
const SkScalar bounds[2] = {y0, y1};
98+
auto count = blob->getIntercepts(bounds, nullptr, &fPaint);
99+
SkTArray<SkScalar> intersections(count);
100+
intersections.resize(count);
101+
blob->getIntercepts(bounds, intersections.data(), &fPaint);
102+
103+
auto start = x0;
104+
fPath.moveTo({x0, y0});
105+
for (int i = 0; i < intersections.count(); i += 2) {
106+
auto end = intersections[i] - halo;
107+
if (end - start >= halo) {
108+
start = intersections[i + 1] + halo;
109+
fPath.lineTo(end, y0).moveTo(start, y0);
110+
}
111+
}
112+
if (!intersections.empty() && (x1 - start > halo)) {
113+
fPath.lineTo(x1, y0);
114+
}
115+
}
116+
117+
// This is how flutter calculates the thickness
118+
void Decorations::calculateThickness(TextStyle textStyle, sk_sp<SkTypeface> typeface) {
119+
120+
textStyle.setTypeface(typeface);
121+
textStyle.getFontMetrics(&fFontMetrics);
122+
123+
fThickness = textStyle.getFontSize() / 14.0f;
124+
125+
if ((fFontMetrics.fFlags & SkFontMetrics::FontMetricsFlags::kUnderlineThicknessIsValid_Flag) &&
126+
fFontMetrics.fUnderlineThickness > 0) {
127+
fThickness = fFontMetrics.fUnderlineThickness;
128+
}
129+
130+
if (textStyle.getDecorationType() == TextDecoration::kLineThrough) {
131+
if ((fFontMetrics.fFlags & SkFontMetrics::FontMetricsFlags::kStrikeoutThicknessIsValid_Flag) &&
132+
fFontMetrics.fStrikeoutThickness > 0) {
133+
fThickness = fFontMetrics.fStrikeoutThickness;
134+
}
135+
}
136+
fThickness *= textStyle.getDecorationThicknessMultiplier();
137+
}
138+
139+
// This is how flutter calculates the positioning
140+
void Decorations::calculatePosition(TextDecoration decoration, SkScalar ascent) {
141+
switch (decoration) {
142+
case TextDecoration::kUnderline:
143+
if ((fFontMetrics.fFlags & SkFontMetrics::FontMetricsFlags::kUnderlinePositionIsValid_Flag) &&
144+
fFontMetrics.fUnderlinePosition > 0) {
145+
fPosition = fFontMetrics.fUnderlinePosition;
146+
} else {
147+
fPosition = fThickness;
148+
}
149+
fPosition -= ascent;
150+
break;
151+
case TextDecoration::kOverline:
152+
fPosition = 0;
153+
break;
154+
case TextDecoration::kLineThrough: {
155+
fPosition = (fFontMetrics.fFlags & SkFontMetrics::FontMetricsFlags::kStrikeoutThicknessIsValid_Flag)
156+
? fFontMetrics.fStrikeoutPosition
157+
: fFontMetrics.fXHeight / -2;
158+
fPosition -= ascent;
159+
break;
160+
}
161+
default:SkASSERT(false);
162+
break;
163+
}
164+
}
165+
166+
void Decorations::calculatePaint(const TextStyle& textStyle) {
167+
168+
fPaint.reset();
169+
170+
fPaint.setStyle(SkPaint::kStroke_Style);
171+
if (textStyle.getDecorationColor() == SK_ColorTRANSPARENT) {
172+
fPaint.setColor(textStyle.getColor());
173+
} else {
174+
fPaint.setColor(textStyle.getDecorationColor());
175+
}
176+
fPaint.setAntiAlias(true);
177+
fPaint.setStrokeWidth(fThickness);
178+
179+
SkScalar scaleFactor = textStyle.getFontSize() / 14.f;
180+
switch (textStyle.getDecorationStyle()) {
181+
// Note: the intervals are scaled by the thickness of the line, so it is
182+
// possible to change spacing by changing the decoration_thickness
183+
// property of TextStyle.
184+
case TextDecorationStyle::kDotted: {
185+
const SkScalar intervals[] = {1.0f * scaleFactor, 1.5f * scaleFactor,
186+
1.0f * scaleFactor, 1.5f * scaleFactor};
187+
size_t count = sizeof(intervals) / sizeof(intervals[0]);
188+
fPaint.setPathEffect(SkPathEffect::MakeCompose(
189+
SkDashPathEffect::Make(intervals, (int32_t)count, 0.0f),
190+
SkDiscretePathEffect::Make(0, 0)));
191+
break;
192+
}
193+
// Note: the intervals are scaled by the thickness of the line, so it is
194+
// possible to change spacing by changing the decoration_thickness
195+
// property of TextStyle.
196+
case TextDecorationStyle::kDashed: {
197+
const SkScalar intervals[] = {4.0f * scaleFactor, 2.0f * scaleFactor,
198+
4.0f * scaleFactor, 2.0f * scaleFactor};
199+
size_t count = sizeof(intervals) / sizeof(intervals[0]);
200+
fPaint.setPathEffect(SkPathEffect::MakeCompose(
201+
SkDashPathEffect::Make(intervals, (int32_t)count, 0.0f),
202+
SkDiscretePathEffect::Make(0, 0)));
203+
break;
204+
}
205+
default: break;
206+
}
207+
}
208+
209+
void Decorations::calculateWaves(const TextStyle& textStyle, SkRect clip) {
210+
211+
fPath.reset();
212+
int wave_count = 0;
213+
SkScalar x_start = 0;
214+
SkScalar quarterWave = fThickness;
215+
fPath.moveTo(0, 0);
216+
while (x_start + quarterWave * 2 < clip.width()) {
217+
fPath.rQuadTo(quarterWave,
218+
wave_count % 2 != 0 ? quarterWave : -quarterWave,
219+
quarterWave * 2,
220+
0);
221+
x_start += quarterWave * 2;
222+
++wave_count;
223+
}
224+
225+
// The rest of the wave
226+
auto remaining = clip.width() - x_start;
227+
if (remaining > 0) {
228+
double x1 = remaining / 2;
229+
double y1 = remaining / 2 * (wave_count % 2 == 0 ? -1 : 1);
230+
double x2 = remaining;
231+
double y2 = (remaining - remaining * remaining / (quarterWave * 2)) *
232+
(wave_count % 2 == 0 ? -1 : 1);
233+
fPath.rQuadTo(x1, y1, x2, y2);
234+
}
235+
}
236+
237+
}
238+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2020 Google LLC.
2+
#ifndef Decorations_DEFINED
3+
#define Decorations_DEFINED
4+
5+
#include "include/core/SkCanvas.h"
6+
#include "include/core/SkPath.h"
7+
#include "modules/skparagraph/include/TextStyle.h"
8+
#include "modules/skparagraph/src/TextLine.h"
9+
10+
namespace skia {
11+
namespace textlayout {
12+
13+
class Decorations {
14+
public:
15+
void paint(SkCanvas* canvas, const TextStyle& textStyle, const TextLine::ClipContext& context, SkScalar baseline, SkScalar shift);
16+
17+
private:
18+
19+
void calculateThickness(TextStyle textStyle, sk_sp<SkTypeface> typeface);
20+
void calculatePosition(TextDecoration decoration, SkScalar ascent);
21+
void calculatePaint(const TextStyle& textStyle);
22+
void calculateWaves(const TextStyle& textStyle, SkRect clip);
23+
void calculateGaps(const TextLine::ClipContext& context, SkScalar x0, SkScalar x1, SkScalar y0, SkScalar y1, SkScalar baseline, SkScalar halo);
24+
25+
SkScalar fThickness;
26+
SkScalar fPosition;
27+
28+
SkFontMetrics fFontMetrics;
29+
SkPaint fPaint;
30+
SkPath fPath;
31+
};
32+
}
33+
}
34+
#endif

modules/skparagraph/src/Run.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ class Run {
184184
void resetJustificationShifts() {
185185
fJustificationShifts.reset();
186186
}
187+
187188
private:
188189
friend class ParagraphImpl;
189190
friend class TextLine;
@@ -212,7 +213,6 @@ class Run {
212213
SkSTArray<128, SkPoint, true> fOffsets;
213214
SkSTArray<128, uint32_t, true> fClusterIndexes;
214215
SkSTArray<128, SkRect, true> fBounds;
215-
216216
SkSTArray<128, SkScalar, true> fShifts; // For formatting (letter/word spacing)
217217
bool fSpaced;
218218
};

0 commit comments

Comments
 (0)