blob: 7eaf771c6cde8c5873374cac600c283a790d68f7 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include "display_list/dl_color.h"
#include "display_list/dl_paint.h"
#include "display_list/dl_tile_mode.h"
#include "display_list/effects/dl_color_source.h"
#include "display_list/utils/dl_receiver_utils.h"
#include "gtest/gtest.h"
#include "include/core/SkScalar.h"
#include "runtime/test_font_data.h"
#include "skia/paragraph_builder_skia.h"
#include "testing/canvas_test.h"
namespace flutter {
namespace testing {
//------------------------------------------------------------------------------
/// @brief A custom |DlOpReceiver| that records some |DlOps| it receives.
class DlOpRecorder final : public virtual DlOpReceiver,
private IgnoreAttributeDispatchHelper,
private IgnoreClipDispatchHelper,
private IgnoreDrawDispatchHelper,
private IgnoreTransformDispatchHelper {
public:
int lineCount() const { return lines_.size(); }
int rectCount() const { return rects_.size(); }
int pathCount() const { return paths_.size(); }
int textFrameCount() const { return text_frames_.size(); }
int blobCount() const { return blobs_.size(); }
bool hasPathEffect() const { return path_effect_ != nullptr; }
private:
void drawLine(const SkPoint& p0, const SkPoint& p1) override {
lines_.emplace_back(p0, p1);
}
void drawTextFrame(const std::shared_ptr<impeller::TextFrame>& text_frame,
SkScalar x,
SkScalar y) override {
text_frames_.push_back(text_frame);
}
void drawTextBlob(const sk_sp<SkTextBlob> blob,
SkScalar x,
SkScalar y) override {
blobs_.push_back(blob);
}
void drawRect(const SkRect& rect) override { rects_.push_back(rect); }
void drawPath(const SkPath& path) override { paths_.push_back(path); }
void setPathEffect(const DlPathEffect* effect) override {
path_effect_ = effect;
}
std::vector<std::shared_ptr<impeller::TextFrame>> text_frames_;
std::vector<sk_sp<SkTextBlob>> blobs_;
std::vector<std::pair<SkPoint, SkPoint>> lines_;
std::vector<SkRect> rects_;
std::vector<SkPath> paths_;
const DlPathEffect* path_effect_;
};
template <typename T>
class PainterTestBase : public CanvasTestBase<T> {
public:
PainterTestBase() = default;
void PretendImpellerIsEnabled(bool impeller) { impeller_ = impeller; }
protected:
txt::TextStyle makeDecoratedStyle(txt::TextDecorationStyle style) {
auto t_style = txt::TextStyle();
t_style.color = SK_ColorBLACK; // default
t_style.font_weight = txt::FontWeight::w400; // normal
t_style.font_size = 14; // default
t_style.decoration = txt::TextDecoration::kUnderline;
t_style.decoration_style = style;
t_style.decoration_color = SK_ColorBLACK;
t_style.font_families.push_back("ahem");
return t_style;
}
txt::TextStyle makeStyle() {
auto t_style = txt::TextStyle();
t_style.color = SK_ColorBLACK; // default
t_style.font_weight = txt::FontWeight::w400; // normal
t_style.font_size = 14; // default
t_style.font_families.push_back("ahem");
return t_style;
}
sk_sp<DisplayList> draw(txt::TextStyle style) const {
auto pb_skia = makeParagraphBuilder();
pb_skia.PushStyle(style);
pb_skia.AddText(u"Hello World!");
pb_skia.Pop();
auto builder = DisplayListBuilder();
auto paragraph = pb_skia.Build();
paragraph->Layout(10000);
paragraph->Paint(&builder, 0, 0);
return builder.Build();
}
private:
std::shared_ptr<txt::FontCollection> makeFontCollection() const {
auto f_collection = std::make_shared<txt::FontCollection>();
auto font_provider = std::make_unique<txt::TypefaceFontAssetProvider>();
for (auto& font : GetTestFontData()) {
font_provider->RegisterTypeface(font);
}
auto manager = sk_make_sp<txt::AssetFontManager>(std::move(font_provider));
f_collection->SetAssetFontManager(manager);
return f_collection;
}
txt::ParagraphBuilderSkia makeParagraphBuilder() const {
auto p_style = txt::ParagraphStyle();
auto f_collection = makeFontCollection();
return txt::ParagraphBuilderSkia(p_style, f_collection, impeller_);
}
bool impeller_ = false;
};
using PainterTest = PainterTestBase<::testing::Test>;
TEST_F(PainterTest, DrawsSolidLineSkia) {
PretendImpellerIsEnabled(false);
auto recorder = DlOpRecorder();
draw(makeDecoratedStyle(txt::TextDecorationStyle::kSolid))
->Dispatch(recorder);
// Skia may draw a solid underline as a filled rectangle:
// https://skia.googlesource.com/skia/+/refs/heads/main/modules/skparagraph/src/Decorations.cpp#91
EXPECT_EQ(recorder.rectCount(), 1);
EXPECT_FALSE(recorder.hasPathEffect());
}
TEST_F(PainterTest, DrawDashedLineSkia) {
PretendImpellerIsEnabled(false);
auto recorder = DlOpRecorder();
draw(makeDecoratedStyle(txt::TextDecorationStyle::kDashed))
->Dispatch(recorder);
// Skia draws a dashed underline as a filled rectangle with a path effect.
EXPECT_EQ(recorder.lineCount(), 1);
EXPECT_TRUE(recorder.hasPathEffect());
}
#ifdef IMPELLER_SUPPORTS_RENDERING
TEST_F(PainterTest, DrawsSolidLineImpeller) {
PretendImpellerIsEnabled(true);
auto recorder = DlOpRecorder();
draw(makeDecoratedStyle(txt::TextDecorationStyle::kSolid))
->Dispatch(recorder);
// Skia may draw a solid underline as a filled rectangle:
// https://skia.googlesource.com/skia/+/refs/heads/main/modules/skparagraph/src/Decorations.cpp#91
EXPECT_EQ(recorder.rectCount(), 1);
EXPECT_FALSE(recorder.hasPathEffect());
}
TEST_F(PainterTest, DrawDashedLineImpeller) {
PretendImpellerIsEnabled(true);
auto recorder = DlOpRecorder();
draw(makeDecoratedStyle(txt::TextDecorationStyle::kDashed))
->Dispatch(recorder);
// Impeller draws a dashed underline as a path.
EXPECT_EQ(recorder.pathCount(), 1);
EXPECT_FALSE(recorder.hasPathEffect());
}
TEST_F(PainterTest, DrawTextFrameImpeller) {
PretendImpellerIsEnabled(true);
auto recorder = DlOpRecorder();
draw(makeStyle())->Dispatch(recorder);
EXPECT_EQ(recorder.textFrameCount(), 1);
EXPECT_EQ(recorder.blobCount(), 0);
}
TEST_F(PainterTest, DrawStrokedTextImpeller) {
PretendImpellerIsEnabled(true);
auto style = makeStyle();
// What is your shtyle?
DlPaint foreground;
foreground.setDrawStyle(DlDrawStyle::kStroke);
style.foreground = foreground;
auto recorder = DlOpRecorder();
draw(style)->Dispatch(recorder);
EXPECT_EQ(recorder.textFrameCount(), 0);
EXPECT_EQ(recorder.blobCount(), 0);
EXPECT_EQ(recorder.pathCount(), 1);
}
TEST_F(PainterTest, DrawTextWithGradientImpeller) {
PretendImpellerIsEnabled(true);
auto style = makeStyle();
// how do you like my shtyle?
DlPaint foreground;
std::vector<DlColor> colors = {DlColor::kRed(), DlColor::kCyan()};
std::vector<float> stops = {0.0, 1.0};
foreground.setColorSource(DlColorSource::MakeLinear(
SkPoint::Make(0, 0), SkPoint::Make(100, 100), 2, colors.data(),
stops.data(), DlTileMode::kClamp));
style.foreground = foreground;
auto recorder = DlOpRecorder();
draw(style)->Dispatch(recorder);
EXPECT_EQ(recorder.textFrameCount(), 0);
EXPECT_EQ(recorder.blobCount(), 0);
EXPECT_EQ(recorder.pathCount(), 1);
}
TEST_F(PainterTest, DrawTextBlobNoImpeller) {
PretendImpellerIsEnabled(false);
auto recorder = DlOpRecorder();
draw(makeStyle())->Dispatch(recorder);
EXPECT_EQ(recorder.textFrameCount(), 0);
EXPECT_EQ(recorder.blobCount(), 1);
}
#endif // IMPELLER_SUPPORTS_RENDERING
} // namespace testing
} // namespace flutter