Skip to content

Commit

Permalink
Add //impeller/typographer. (flutter#36)
Browse files Browse the repository at this point in the history
Renders shaped text frames. Has the ability to plug into to different text
shapers and render glyphs using different techniques.

For now, the Aiks layer expects a prepared glyph atlas. But this will be changed
so that render pass will be responsible for preparing these and setting these on
the content renderer after pass consolidation.
  • Loading branch information
chinmaygarde authored and dnfield committed Apr 27, 2022
1 parent 451f93e commit 0dae5ad
Show file tree
Hide file tree
Showing 63 changed files with 1,778 additions and 23 deletions.
2 changes: 2 additions & 0 deletions impeller/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ group("impeller") {
"geometry",
"image",
"renderer",
"typographer",
]

if (is_host) {
Expand All @@ -43,5 +44,6 @@ executable("impeller_unittests") {
"image:image_unittests",
"playground",
"renderer:renderer_unittests",
"typographer:typographer_unittests",
]
}
112 changes: 112 additions & 0 deletions impeller/aiks/aiks_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
#include "impeller/aiks/image.h"
#include "impeller/geometry/geometry_unittests.h"
#include "impeller/geometry/path_builder.h"
#include "impeller/typographer/backends/skia/text_frame_skia.h"
#include "impeller/typographer/backends/skia/text_render_context_skia.h"
#include "third_party/skia/include/core/SkData.h"

namespace impeller {
namespace testing {
Expand Down Expand Up @@ -283,5 +286,114 @@ TEST_F(AiksTest, CanRenderDifferencePaths) {
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

static sk_sp<SkData> OpenFixtureAsSkData(const char* fixture_name) {
auto mapping = flutter::testing::OpenFixtureAsMapping(fixture_name);
if (!mapping) {
return nullptr;
}
return SkData::MakeWithProc(
mapping->GetMapping(), mapping->GetSize(),
[](const void* ptr, void* context) {
delete reinterpret_cast<fml::Mapping*>(context);
},
mapping.release());
}

TEST_F(AiksTest, CanRenderTextFrame) {
Canvas canvas;

Scalar baseline = 200.0;
Point text_position = {100, baseline};

// Draw the baseline.
canvas.DrawRect({50, baseline, 900, 10},
Paint{.color = Color::Aqua().WithAlpha(0.25)});

// Mark the point at which the text is drawn.
canvas.DrawCircle(text_position, 5.0,
Paint{.color = Color::Red().WithAlpha(0.25)});

// Construct the text blob.
auto mapping = OpenFixtureAsSkData("Roboto-Regular.ttf");
ASSERT_TRUE(mapping);
SkFont sk_font(SkTypeface::MakeFromData(mapping), 50.0);
auto blob = SkTextBlob::MakeFromString(
"the quick brown fox jumped over the lazy dog!.?", sk_font);
ASSERT_TRUE(blob);

// Create the Impeller text frame and draw it at the designated baseline.
auto frame = TextFrameFromTextBlob(blob);
TextRenderContextSkia text_context(GetContext());
ASSERT_TRUE(text_context.IsValid());
auto atlas = text_context.CreateGlyphAtlas(frame);
ASSERT_NE(atlas, nullptr);
canvas.DrawTextFrame(std::move(frame), std::move(atlas), text_position);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_F(AiksTest, CanRenderItalicizedText) {
Canvas canvas;

Scalar baseline = 200.0;
Point text_position = {100, baseline};

// Draw the baseline.
canvas.DrawRect({50, baseline, 900, 10},
Paint{.color = Color::Aqua().WithAlpha(0.25)});

// Mark the point at which the text is drawn.
canvas.DrawCircle(text_position, 5.0,
Paint{.color = Color::Red().WithAlpha(0.25)});

// Construct the text blob.
auto mapping = OpenFixtureAsSkData("HomemadeApple.ttf");
ASSERT_TRUE(mapping);
SkFont sk_font(SkTypeface::MakeFromData(mapping), 50.0);
auto blob = SkTextBlob::MakeFromString(
"the quick brown fox jumped over the lazy dog!.?", sk_font);
ASSERT_TRUE(blob);

// Create the Impeller text frame and draw it at the designated baseline.
auto frame = TextFrameFromTextBlob(blob);
TextRenderContextSkia text_context(GetContext());
ASSERT_TRUE(text_context.IsValid());
auto atlas = text_context.CreateGlyphAtlas(frame);
ASSERT_NE(atlas, nullptr);
canvas.DrawTextFrame(std::move(frame), std::move(atlas), text_position);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_F(AiksTest, CanRenderEmojiTextFrame) {
Canvas canvas;

Scalar baseline = 200.0;
Point text_position = {100, baseline};

// Draw the baseline.
canvas.DrawRect({50, baseline, 900, 10},
Paint{.color = Color::Aqua().WithAlpha(0.25)});

// Mark the point at which the text is drawn.
canvas.DrawCircle(text_position, 5.0,
Paint{.color = Color::Red().WithAlpha(0.25)});

// Construct the text blob.
auto mapping = OpenFixtureAsSkData("NotoColorEmoji.ttf");
ASSERT_TRUE(mapping);
SkFont sk_font(SkTypeface::MakeFromData(mapping), 50.0);
auto blob = SkTextBlob::MakeFromString(
"😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊", sk_font);
ASSERT_TRUE(blob);

// Create the Impeller text frame and draw it at the designated baseline.
auto frame = TextFrameFromTextBlob(blob);
TextRenderContextSkia text_context(GetContext());
ASSERT_TRUE(text_context.IsValid());
auto atlas = text_context.CreateGlyphAtlas(frame);
ASSERT_NE(atlas, nullptr);
canvas.DrawTextFrame(std::move(frame), std::move(atlas), text_position);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

} // namespace testing
} // namespace impeller
21 changes: 21 additions & 0 deletions impeller/aiks/canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -252,4 +252,25 @@ void Canvas::Save(bool create_subpass) {
xformation_stack_.emplace_back(std::move(entry));
}

void Canvas::DrawTextFrame(TextFrame text_frame,
std::shared_ptr<GlyphAtlas> atlas,
Point position) {
if (!atlas || !atlas->IsValid()) {
return;
}

auto text_contents = std::make_shared<TextContents>();
text_contents->SetTextFrame(std::move(text_frame));
text_contents->SetGlyphAtlas(std::move(atlas));

Entity entity;
entity.SetTransformation(GetCurrentTransformation() *
Matrix::MakeTranslation(position));
entity.SetPath({});
entity.SetStencilDepth(GetStencilDepth());
entity.SetContents(std::move(text_contents));

GetCurrentPass().AddEntity(std::move(entity));
}

} // namespace impeller
5 changes: 5 additions & 0 deletions impeller/aiks/canvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "impeller/geometry/path.h"
#include "impeller/geometry/point.h"
#include "impeller/geometry/vector.h"
#include "impeller/typographer/text_frame.h"

namespace impeller {

Expand Down Expand Up @@ -74,6 +75,10 @@ class Canvas {

void DrawPicture(Picture picture);

void DrawTextFrame(TextFrame text_frame,
std::shared_ptr<GlyphAtlas> atlas,
Point position);

Picture EndRecordingAsPicture();

private:
Expand Down
4 changes: 4 additions & 0 deletions impeller/aiks/paint.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
namespace impeller {

std::shared_ptr<Contents> Paint::CreateContentsForEntity() const {
if (contents) {
return contents;
}

switch (style) {
case Style::kFill: {
auto solid_color = std::make_shared<SolidColorContents>();
Expand Down
1 change: 1 addition & 0 deletions impeller/aiks/paint.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ struct Paint {
Color color = Color::Black();
Scalar stroke_width = 0.0;
Style style = Style::kFill;
std::shared_ptr<Contents> contents;

std::shared_ptr<Contents> CreateContentsForEntity() const;
};
Expand Down
1 change: 1 addition & 0 deletions impeller/base/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ impeller_component("base") {
sources = [
"allocation.cc",
"allocation.h",
"backend_cast.h",
"base.h",
"config.h",
"promise.cc",
Expand Down
2 changes: 1 addition & 1 deletion impeller/base/allocation.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ bool Allocation::Truncate(size_t length, bool npot) {
return true;
}

static uint32_t NextPowerOfTwoSize(uint32_t x) {
uint32_t Allocation::NextPowerOfTwoSize(uint32_t x) {
if (x == 0) {
return 1;
}
Expand Down
2 changes: 2 additions & 0 deletions impeller/base/allocation.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class Allocation {

[[nodiscard]] bool Truncate(size_t length, bool npot = true);

static uint32_t NextPowerOfTwoSize(uint32_t x);

private:
uint8_t* buffer_ = nullptr;
size_t length_ = 0;
Expand Down
File renamed without changes.
60 changes: 53 additions & 7 deletions impeller/display_list/display_list_dispatcher.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

#include "flutter/fml/trace_event.h"
#include "impeller/geometry/path_builder.h"
#include "impeller/typographer/backends/skia/text_frame_skia.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkShader.h"

namespace impeller {

Expand Down Expand Up @@ -72,8 +75,56 @@ void DisplayListDispatcher::setStrokeJoin(SkPaint::Join join) {
UNIMPLEMENTED;
}

static Point ToPoint(const SkPoint& point) {
return Point::MakeXY(point.fX, point.fY);
}

static Color ToColor(const SkColor& color) {
return {
static_cast<Scalar>(SkColorGetR(color) / 255.0), //
static_cast<Scalar>(SkColorGetG(color) / 255.0), //
static_cast<Scalar>(SkColorGetB(color) / 255.0), //
static_cast<Scalar>(SkColorGetA(color) / 255.0) //
};
}

// |flutter::Dispatcher|
void DisplayListDispatcher::setShader(sk_sp<SkShader> shader) {
if (!shader) {
return;
}

{
SkShader::GradientInfo info = {};
constexpr auto kColorsArrayCount = 2u;
info.fColorCount = kColorsArrayCount;
SkColor sk_colors[kColorsArrayCount];
info.fColors = sk_colors;
auto gradient_type = shader->asAGradient(&info);
switch (gradient_type) {
case SkShader::kLinear_GradientType: {
auto contents = std::make_shared<LinearGradientContents>();
contents->SetEndPoints(ToPoint(info.fPoint[0]),
ToPoint(info.fPoint[1]));
std::vector<Color> colors;
for (auto i = 0; i < info.fColorCount; i++) {
colors.emplace_back(ToColor(sk_colors[i]));
}
contents->SetColors(std::move(colors));
paint_.contents = std::move(contents);
return;
} break;
case SkShader::kNone_GradientType:
case SkShader::kColor_GradientType:
case SkShader::kRadial_GradientType:
case SkShader::kSweep_GradientType:
case SkShader::kConical_GradientType:
default:
UNIMPLEMENTED;
break;
}
}

// Needs https://github.com/flutter/flutter/issues/95434
UNIMPLEMENTED;
}
Expand Down Expand Up @@ -154,9 +205,8 @@ static std::optional<Rect> ToRect(const SkRect* rect) {
// |flutter::Dispatcher|
void DisplayListDispatcher::saveLayer(const SkRect* bounds,
const flutter::SaveLayerOptions options) {
canvas_.SaveLayer(
options.renders_with_attributes() ? paint_ : Paint{},
ToRect(bounds));
canvas_.SaveLayer(options.renders_with_attributes() ? paint_ : Paint{},
ToRect(bounds));
}

// |flutter::Dispatcher|
Expand Down Expand Up @@ -242,10 +292,6 @@ void DisplayListDispatcher::clipRect(const SkRect& rect,
canvas_.ClipPath(std::move(path));
}

static Point ToPoint(const SkVector& vector) {
return {vector.fX, vector.fY};
}

static PathBuilder::RoundingRadii ToRoundingRadii(const SkRRect& rrect) {
using Corner = SkRRect::Corner;
PathBuilder::RoundingRadii radii;
Expand Down
3 changes: 3 additions & 0 deletions impeller/entity/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ impeller_shaders("entity_shaders") {
"shaders/solid_stroke.vert",
"shaders/texture_fill.frag",
"shaders/texture_fill.vert",
"shaders/glyph_atlas.frag",
"shaders/glyph_atlas.vert",
]
}

Expand All @@ -38,6 +40,7 @@ impeller_component("entity") {
"../archivist",
"../image",
"../renderer",
"../typographer",
]
}

Expand Down
1 change: 1 addition & 0 deletions impeller/entity/content_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ ContentContext::ContentContext(std::shared_ptr<Context> context)
texture_pipelines_[{}] = std::make_unique<TexturePipeline>(*context_);
solid_stroke_pipelines_[{}] =
std::make_unique<SolidStrokePipeline>(*context_);
glyph_atlas_pipelines_[{}] = std::make_unique<GlyphAtlasPipeline>(*context_);

// Pipelines that are variants of the base pipelines with custom descriptors.
// TODO(98684): Rework this API to allow fetching the descriptor without
Expand Down
9 changes: 9 additions & 0 deletions impeller/entity/content_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

#include "flutter/fml/hash_combine.h"
#include "flutter/fml/macros.h"
#include "flutter/impeller/entity/glyph_atlas.frag.h"
#include "flutter/impeller/entity/glyph_atlas.vert.h"
#include "flutter/impeller/entity/gradient_fill.frag.h"
#include "flutter/impeller/entity/gradient_fill.vert.h"
#include "flutter/impeller/entity/solid_fill.frag.h"
Expand All @@ -29,6 +31,8 @@ using TexturePipeline =
PipelineT<TextureFillVertexShader, TextureFillFragmentShader>;
using SolidStrokePipeline =
PipelineT<SolidStrokeVertexShader, SolidStrokeFragmentShader>;
using GlyphAtlasPipeline =
PipelineT<GlyphAtlasVertexShader, GlyphAtlasFragmentShader>;
// Instead of requiring new shaders for clips, the solid fill stages are used
// to redirect writing to the stencil instead of color attachments.
using ClipPipeline = PipelineT<SolidFillVertexShader, SolidFillFragmentShader>;
Expand Down Expand Up @@ -81,6 +85,10 @@ class ContentContext {
return GetPipeline(clip_restoration_pipelines_, opts);
}

std::shared_ptr<Pipeline> GetGlyphAtlasPipeline(Options opts) const {
return GetPipeline(glyph_atlas_pipelines_, opts);
}

std::shared_ptr<Context> GetContext() const;

private:
Expand All @@ -99,6 +107,7 @@ class ContentContext {
mutable Variants<SolidStrokePipeline> solid_stroke_pipelines_;
mutable Variants<ClipPipeline> clip_pipelines_;
mutable Variants<ClipPipeline> clip_restoration_pipelines_;
mutable Variants<GlyphAtlasPipeline> glyph_atlas_pipelines_;

static void ApplyOptionsToDescriptor(PipelineDescriptor& desc,
const Options& options) {
Expand Down
Loading

0 comments on commit 0dae5ad

Please sign in to comment.