Skip to content

Commit

Permalink
Manually draw remainder curve for wavy decorations (flutter#9468)
Browse files Browse the repository at this point in the history
  • Loading branch information
GaryQian authored Jun 27, 2019
1 parent 6f7700f commit 185087a
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 11 deletions.
56 changes: 45 additions & 11 deletions third_party/txt/src/txt/paragraph.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1347,17 +1347,9 @@ void Paragraph::PaintDecorations(SkCanvas* canvas,
break;
}
case TextDecorationStyle::kWavy: {
int wave_count = 0;
double x_start = 0;
double wavelength =
underline_thickness * record.style().decoration_thickness_multiplier;
path.moveTo(x, y);
while (x_start + wavelength * 2 < width) {
path.rQuadTo(wavelength, wave_count % 2 != 0 ? wavelength : -wavelength,
wavelength * 2, 0);
x_start += wavelength * 2;
++wave_count;
}
ComputeWavyDecoration(
path, x, y, width,
underline_thickness * record.style().decoration_thickness_multiplier);
break;
}
}
Expand Down Expand Up @@ -1425,6 +1417,48 @@ void Paragraph::PaintDecorations(SkCanvas* canvas,
}
}

void Paragraph::ComputeWavyDecoration(SkPath& path,
double x,
double y,
double width,
double thickness) {
int wave_count = 0;
double x_start = 0;
// One full wavelength is 4 * thickness.
double quarter = thickness;
path.moveTo(x, y);
double remaining = width;
while (x_start + (quarter * 2) < width) {
path.rQuadTo(quarter, wave_count % 2 == 0 ? -quarter : quarter, quarter * 2,
0);
x_start += quarter * 2;
remaining = width - x_start;
++wave_count;
}
// Manually add a final partial quad for the remaining width that do
// not fit nicely into a half-wavelength.
// The following math is based off of quadratic bezier equations:
//
// * Let P(x) be the equation for the curve.
// * Let P0 = start, P1 = control point, P2 = end
// * P(x) = -2x^2 - 2x
// * P0 = (0, 0)
// * P1 = 2P(0.5) - 0.5 * P0 - 0.5 * P2
// * P2 = P(remaining / (wavelength / 2))
//
// Simplified implementation coursesy of @jim-flar at
// https://github.com/flutter/engine/pull/9468#discussion_r297872739
// Unsimplified original version at
// https://github.com/flutter/engine/pull/9468#discussion_r297879129

double x1 = remaining / 2;
double y1 = remaining / 2 * (wave_count % 2 == 0 ? -1 : 1);
double x2 = remaining;
double y2 = (remaining - remaining * remaining / (quarter * 2)) *
(wave_count % 2 == 0 ? -1 : 1);
path.rQuadTo(x1, y1, x2, y2);
}

void Paragraph::PaintBackground(SkCanvas* canvas,
const PaintRecord& record,
SkPoint base_offset) {
Expand Down
9 changes: 9 additions & 0 deletions third_party/txt/src/txt/paragraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ class Paragraph {
FRIEND_TEST(ParagraphTest, RepeatLayoutParagraph);
FRIEND_TEST(ParagraphTest, Ellipsize);
FRIEND_TEST(ParagraphTest, UnderlineShiftParagraph);
FRIEND_TEST(ParagraphTest, WavyDecorationParagraph);
FRIEND_TEST(ParagraphTest, SimpleShadow);
FRIEND_TEST(ParagraphTest, ComplexShadow);
FRIEND_TEST(ParagraphTest, FontFallbackParagraph);
Expand Down Expand Up @@ -478,6 +479,14 @@ class Paragraph {
const PaintRecord& record,
SkPoint base_offset);

// Computes the beziers for a wavy decoration. The results will be
// applied to path.
void ComputeWavyDecoration(SkPath& path,
double x,
double y,
double width,
double thickness);

// Draws the background onto the canvas.
void PaintBackground(SkCanvas* canvas,
const PaintRecord& record,
Expand Down
139 changes: 139 additions & 0 deletions third_party/txt/tests/paragraph_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "render_test.h"
#include "third_party/icu/source/common/unicode/unistr.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkPath.h"
#include "txt/font_style.h"
#include "txt/font_weight.h"
#include "txt/paragraph.h"
Expand Down Expand Up @@ -1902,6 +1903,144 @@ TEST_F(ParagraphTest, DecorationsParagraph) {
1.0);
}

TEST_F(ParagraphTest, WavyDecorationParagraph) {
txt::ParagraphStyle paragraph_style;
paragraph_style.max_lines = 14;
paragraph_style.text_align = TextAlign::left;
txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection());

txt::TextStyle text_style;
text_style.font_families = std::vector<std::string>(1, "Roboto");
text_style.font_size = 26;
text_style.letter_spacing = 0;
text_style.word_spacing = 5;
text_style.color = SK_ColorBLACK;
text_style.height = 2;
text_style.decoration = TextDecoration::kUnderline |
TextDecoration::kOverline |
TextDecoration::kLineThrough;

text_style.decoration_style = txt::TextDecorationStyle::kWavy;
text_style.decoration_color = SK_ColorRED;
text_style.decoration_thickness_multiplier = 1.0;
builder.PushStyle(text_style);

builder.AddText(u" Otherwise, bad things happen.");

builder.Pop();

auto paragraph = builder.Build();
paragraph->Layout(GetTestCanvasWidth() - 100);

paragraph->Paint(GetCanvas(), 0, 0);

ASSERT_TRUE(Snapshot());
ASSERT_EQ(paragraph->runs_.size(), 1ull);
ASSERT_EQ(paragraph->records_.size(), 1ull);

for (size_t i = 0; i < 1; ++i) {
ASSERT_EQ(paragraph->records_[i].style().decoration,
TextDecoration::kUnderline | TextDecoration::kOverline |
TextDecoration::kLineThrough);
}

ASSERT_EQ(paragraph->records_[0].style().decoration_style,
txt::TextDecorationStyle::kWavy);

ASSERT_EQ(paragraph->records_[0].style().decoration_color, SK_ColorRED);

ASSERT_EQ(paragraph->records_[0].style().decoration_thickness_multiplier,
1.0);

SkPath path0;
SkPath canonical_path0;
paragraph->ComputeWavyDecoration(path0, 1, 1, 9.56, 1);

canonical_path0.moveTo(1, 1);
canonical_path0.rQuadTo(1, -1, 2, 0);
canonical_path0.rQuadTo(1, 1, 2, 0);
canonical_path0.rQuadTo(1, -1, 2, 0);
canonical_path0.rQuadTo(1, 1, 2, 0);
canonical_path0.rQuadTo(0.78, -0.78, 1.56, -0.3432);

ASSERT_EQ(path0.countPoints(), canonical_path0.countPoints());
for (int i = 0; i < canonical_path0.countPoints(); ++i) {
ASSERT_EQ(path0.getPoint(i).x(), canonical_path0.getPoint(i).x());
ASSERT_EQ(path0.getPoint(i).y(), canonical_path0.getPoint(i).y());
}

SkPath path1;
SkPath canonical_path1;
paragraph->ComputeWavyDecoration(path1, 1, 1, 8.35, 1);

canonical_path1.moveTo(1, 1);
canonical_path1.rQuadTo(1, -1, 2, 0);
canonical_path1.rQuadTo(1, 1, 2, 0);
canonical_path1.rQuadTo(1, -1, 2, 0);
canonical_path1.rQuadTo(1, 1, 2, 0);
canonical_path1.rQuadTo(0.175, -0.175, 0.35, -0.28875);

ASSERT_EQ(path1.countPoints(), canonical_path1.countPoints());
for (int i = 0; i < canonical_path1.countPoints(); ++i) {
ASSERT_EQ(path1.getPoint(i).x(), canonical_path1.getPoint(i).x());
ASSERT_EQ(path1.getPoint(i).y(), canonical_path1.getPoint(i).y());
}

SkPath path2;
SkPath canonical_path2;
paragraph->ComputeWavyDecoration(path2, 1, 1, 10.59, 1);

canonical_path2.moveTo(1, 1);
canonical_path2.rQuadTo(1, -1, 2, 0);
canonical_path2.rQuadTo(1, 1, 2, 0);
canonical_path2.rQuadTo(1, -1, 2, 0);
canonical_path2.rQuadTo(1, 1, 2, 0);
canonical_path2.rQuadTo(1, -1, 2, 0);
canonical_path2.rQuadTo(0.295, 0.295, 0.59, 0.41595);

ASSERT_EQ(path2.countPoints(), canonical_path2.countPoints());
for (int i = 0; i < canonical_path2.countPoints(); ++i) {
ASSERT_EQ(path2.getPoint(i).x(), canonical_path2.getPoint(i).x());
ASSERT_EQ(path2.getPoint(i).y(), canonical_path2.getPoint(i).y());
}

SkPath path3;
SkPath canonical_path3;
paragraph->ComputeWavyDecoration(path3, 1, 1, 11.2, 1);

canonical_path3.moveTo(1, 1);
canonical_path3.rQuadTo(1, -1, 2, 0);
canonical_path3.rQuadTo(1, 1, 2, 0);
canonical_path3.rQuadTo(1, -1, 2, 0);
canonical_path3.rQuadTo(1, 1, 2, 0);
canonical_path3.rQuadTo(1, -1, 2, 0);
canonical_path3.rQuadTo(0.6, 0.6, 1.2, 0.48);

ASSERT_EQ(path3.countPoints(), canonical_path3.countPoints());
for (int i = 0; i < canonical_path3.countPoints(); ++i) {
ASSERT_EQ(path3.getPoint(i).x(), canonical_path3.getPoint(i).x());
ASSERT_EQ(path3.getPoint(i).y(), canonical_path3.getPoint(i).y());
}

SkPath path4;
SkPath canonical_path4;
paragraph->ComputeWavyDecoration(path4, 1, 1, 12, 1);

canonical_path4.moveTo(1, 1);
canonical_path4.rQuadTo(1, -1, 2, 0);
canonical_path4.rQuadTo(1, 1, 2, 0);
canonical_path4.rQuadTo(1, -1, 2, 0);
canonical_path4.rQuadTo(1, 1, 2, 0);
canonical_path4.rQuadTo(1, -1, 2, 0);
canonical_path4.rQuadTo(1, 1, 2, 0);

ASSERT_EQ(path4.countPoints(), canonical_path4.countPoints());
for (int i = 0; i < canonical_path4.countPoints(); ++i) {
ASSERT_EQ(path4.getPoint(i).x(), canonical_path4.getPoint(i).x());
ASSERT_EQ(path4.getPoint(i).y(), canonical_path4.getPoint(i).y());
}
}

TEST_F(ParagraphTest, ItalicsParagraph) {
txt::ParagraphStyle paragraph_style;
txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection());
Expand Down

0 comments on commit 185087a

Please sign in to comment.