mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[Impeller] SDF text rendering (flutter/engine#36171)
This commit is contained in:
parent
232882b9a7
commit
c4d33221d8
@ -656,6 +656,8 @@ FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur.frag
|
||||
FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur.vert
|
||||
FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas.frag
|
||||
FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas.vert
|
||||
FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas_sdf.frag
|
||||
FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas_sdf.vert
|
||||
FILE: ../../../flutter/impeller/entity/shaders/gradient_fill.vert
|
||||
FILE: ../../../flutter/impeller/entity/shaders/linear_gradient_fill.frag
|
||||
FILE: ../../../flutter/impeller/entity/shaders/linear_to_srgb_filter.frag
|
||||
|
||||
@ -11,4 +11,24 @@ vec2 IPVec2TransformPosition(mat4 matrix, vec2 point) {
|
||||
return transformed.xy / transformed.w;
|
||||
}
|
||||
|
||||
// Returns the transformed gl_Position for a given glyph position in a glyph
|
||||
// atlas.
|
||||
vec4 IPPositionForGlyphPosition(mat4 mvp, vec2 unit_vertex, vec2 glyph_position, vec2 glyph_size) {
|
||||
vec4 translate = mvp[0] * glyph_position.x
|
||||
+ mvp[1] * glyph_position.y
|
||||
+ mvp[3];
|
||||
mat4 translated_mvp = mat4(
|
||||
mvp[0],
|
||||
mvp[1],
|
||||
mvp[2],
|
||||
vec4(
|
||||
translate.xyz,
|
||||
mvp[3].w
|
||||
)
|
||||
);
|
||||
return translated_mvp *
|
||||
vec4(unit_vertex.x * glyph_size.x,
|
||||
unit_vertex.y * glyph_size.y, 0.0, 1.0);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -36,6 +36,8 @@ impeller_shaders("entity_shaders") {
|
||||
"shaders/gaussian_blur.vert",
|
||||
"shaders/glyph_atlas.frag",
|
||||
"shaders/glyph_atlas.vert",
|
||||
"shaders/glyph_atlas_sdf.frag",
|
||||
"shaders/glyph_atlas_sdf.vert",
|
||||
"shaders/gradient_fill.vert",
|
||||
"shaders/linear_to_srgb_filter.frag",
|
||||
"shaders/linear_to_srgb_filter.vert",
|
||||
|
||||
@ -207,6 +207,8 @@ ContentContext::ContentContext(std::shared_ptr<Context> context)
|
||||
CreateDefaultPipeline<SolidStrokePipeline>(*context_);
|
||||
glyph_atlas_pipelines_[{}] =
|
||||
CreateDefaultPipeline<GlyphAtlasPipeline>(*context_);
|
||||
glyph_atlas_sdf_pipelines_[{}] =
|
||||
CreateDefaultPipeline<GlyphAtlasSdfPipeline>(*context_);
|
||||
vertices_pipelines_[{}] = CreateDefaultPipeline<VerticesPipeline>(*context_);
|
||||
atlas_pipelines_[{}] = CreateDefaultPipeline<AtlasPipeline>(*context_);
|
||||
|
||||
|
||||
@ -40,6 +40,8 @@
|
||||
#include "impeller/entity/gaussian_blur.vert.h"
|
||||
#include "impeller/entity/glyph_atlas.frag.h"
|
||||
#include "impeller/entity/glyph_atlas.vert.h"
|
||||
#include "impeller/entity/glyph_atlas_sdf.frag.h"
|
||||
#include "impeller/entity/glyph_atlas_sdf.vert.h"
|
||||
#include "impeller/entity/gradient_fill.vert.h"
|
||||
#include "impeller/entity/linear_gradient_fill.frag.h"
|
||||
#include "impeller/entity/linear_to_srgb_filter.frag.h"
|
||||
@ -144,6 +146,8 @@ using SolidStrokePipeline =
|
||||
RenderPipelineT<SolidStrokeVertexShader, SolidStrokeFragmentShader>;
|
||||
using GlyphAtlasPipeline =
|
||||
RenderPipelineT<GlyphAtlasVertexShader, GlyphAtlasFragmentShader>;
|
||||
using GlyphAtlasSdfPipeline =
|
||||
RenderPipelineT<GlyphAtlasSdfVertexShader, GlyphAtlasSdfFragmentShader>;
|
||||
using VerticesPipeline =
|
||||
RenderPipelineT<VerticesVertexShader, VerticesFragmentShader>;
|
||||
using AtlasPipeline =
|
||||
@ -272,6 +276,11 @@ class ContentContext {
|
||||
return GetPipeline(glyph_atlas_pipelines_, opts);
|
||||
}
|
||||
|
||||
std::shared_ptr<Pipeline<PipelineDescriptor>> GetGlyphAtlasSdfPipeline(
|
||||
ContentContextOptions opts) const {
|
||||
return GetPipeline(glyph_atlas_sdf_pipelines_, opts);
|
||||
}
|
||||
|
||||
std::shared_ptr<Pipeline<PipelineDescriptor>> GetVerticesPipeline(
|
||||
ContentContextOptions opts) const {
|
||||
return GetPipeline(vertices_pipelines_, opts);
|
||||
@ -399,6 +408,7 @@ class ContentContext {
|
||||
mutable Variants<SolidStrokePipeline> solid_stroke_pipelines_;
|
||||
mutable Variants<ClipPipeline> clip_pipelines_;
|
||||
mutable Variants<GlyphAtlasPipeline> glyph_atlas_pipelines_;
|
||||
mutable Variants<GlyphAtlasSdfPipeline> glyph_atlas_sdf_pipelines_;
|
||||
mutable Variants<VerticesPipeline> vertices_pipelines_;
|
||||
mutable Variants<AtlasPipeline> atlas_pipelines_;
|
||||
// Advanced blends.
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
|
||||
#include "impeller/entity/contents/text_contents.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
|
||||
#include "impeller/entity/contents/content_context.h"
|
||||
#include "impeller/entity/entity.h"
|
||||
@ -28,22 +28,16 @@ void TextContents::SetTextFrame(TextFrame frame) {
|
||||
frame_ = std::move(frame);
|
||||
}
|
||||
|
||||
void TextContents::SetGlyphAtlas(std::shared_ptr<GlyphAtlas> atlas) {
|
||||
atlas_ = std::move(atlas);
|
||||
}
|
||||
|
||||
void TextContents::SetGlyphAtlas(std::shared_ptr<LazyGlyphAtlas> atlas) {
|
||||
atlas_ = std::move(atlas);
|
||||
lazy_atlas_ = std::move(atlas);
|
||||
}
|
||||
|
||||
std::shared_ptr<GlyphAtlas> TextContents::ResolveAtlas(
|
||||
GlyphAtlas::Type type,
|
||||
std::shared_ptr<Context> context) const {
|
||||
if (auto lazy_atlas = std::get_if<std::shared_ptr<LazyGlyphAtlas>>(&atlas_)) {
|
||||
return lazy_atlas->get()->CreateOrGetGlyphAtlas(context);
|
||||
}
|
||||
|
||||
if (auto atlas = std::get_if<std::shared_ptr<GlyphAtlas>>(&atlas_)) {
|
||||
return *atlas;
|
||||
FML_DCHECK(lazy_atlas_);
|
||||
if (lazy_atlas_) {
|
||||
return lazy_atlas_->CreateOrGetGlyphAtlas(type, context);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
@ -61,33 +55,19 @@ std::optional<Rect> TextContents::GetCoverage(const Entity& entity) const {
|
||||
return bounds->TransformBounds(entity.GetTransformation());
|
||||
}
|
||||
|
||||
bool TextContents::Render(const ContentContext& renderer,
|
||||
const Entity& entity,
|
||||
RenderPass& pass) const {
|
||||
if (color_.IsTransparent()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto atlas = ResolveAtlas(renderer.GetContext());
|
||||
|
||||
if (!atlas || !atlas->IsValid()) {
|
||||
VALIDATION_LOG << "Cannot render glyphs without prepared atlas.";
|
||||
return false;
|
||||
}
|
||||
|
||||
using VS = GlyphAtlasPipeline::VertexShader;
|
||||
using FS = GlyphAtlasPipeline::FragmentShader;
|
||||
|
||||
// Information shared by all glyph draw calls.
|
||||
Command cmd;
|
||||
cmd.label = "TextFrame";
|
||||
cmd.primitive_type = PrimitiveType::kTriangle;
|
||||
cmd.pipeline =
|
||||
renderer.GetGlyphAtlasPipeline(OptionsFromPassAndEntity(pass, entity));
|
||||
cmd.stencil_reference = entity.GetStencilDepth();
|
||||
template <class TPipeline>
|
||||
static bool CommonRender(const ContentContext& renderer,
|
||||
const Entity& entity,
|
||||
RenderPass& pass,
|
||||
const Color& color,
|
||||
const TextFrame& frame,
|
||||
std::shared_ptr<GlyphAtlas> atlas,
|
||||
Command& cmd) {
|
||||
using VS = typename TPipeline::VertexShader;
|
||||
using FS = typename TPipeline::FragmentShader;
|
||||
|
||||
// Common vertex uniforms for all glyphs.
|
||||
VS::FrameInfo frame_info;
|
||||
typename VS::FrameInfo frame_info;
|
||||
frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
|
||||
entity.GetTransformation();
|
||||
VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frame_info));
|
||||
@ -96,8 +76,8 @@ bool TextContents::Render(const ContentContext& renderer,
|
||||
sampler_desc.min_filter = MinMagFilter::kLinear;
|
||||
sampler_desc.mag_filter = MinMagFilter::kLinear;
|
||||
|
||||
FS::FragInfo frag_info;
|
||||
frag_info.text_color = ToVector(color_.Premultiply());
|
||||
typename FS::FragInfo frag_info;
|
||||
frag_info.text_color = ToVector(color.Premultiply());
|
||||
frag_info.atlas_size =
|
||||
Point{static_cast<Scalar>(atlas->GetTexture()->GetSize().width),
|
||||
static_cast<Scalar>(atlas->GetTexture()->GetSize().height)};
|
||||
@ -122,16 +102,15 @@ bool TextContents::Render(const ContentContext& renderer,
|
||||
{0, 0}, {1, 0}, {0, 1}, {1, 0}, {0, 1}, {1, 1},
|
||||
};
|
||||
|
||||
VertexBufferBuilder<VS::PerVertexData> vertex_builder;
|
||||
for (const auto& run : frame_.GetRuns()) {
|
||||
VertexBufferBuilder<typename VS::PerVertexData> vertex_builder;
|
||||
for (const auto& run : frame.GetRuns()) {
|
||||
auto font = run.GetFont();
|
||||
auto glyph_size = ISize::Ceil(font.GetMetrics().GetBoundingBox().size);
|
||||
for (const auto& glyph_position : run.GetGlyphPositions()) {
|
||||
FontGlyphPair font_glyph_pair{font, glyph_position.glyph};
|
||||
auto color_glyph =
|
||||
atlas->IsColorFontGlyphPair(font_glyph_pair) ? 1.0 : 0.0;
|
||||
|
||||
for (const auto& point : unit_vertex_points) {
|
||||
VS::PerVertexData vtx;
|
||||
typename VS::PerVertexData vtx;
|
||||
vtx.unit_vertex = point;
|
||||
|
||||
auto atlas_glyph_pos = atlas->FindFontGlyphPosition(font_glyph_pair);
|
||||
@ -149,7 +128,10 @@ bool TextContents::Render(const ContentContext& renderer,
|
||||
1 / atlas_glyph_pos->size.height};
|
||||
vtx.atlas_glyph_size =
|
||||
Point{atlas_glyph_pos->size.width, atlas_glyph_pos->size.height};
|
||||
vtx.color_glyph = color_glyph;
|
||||
if constexpr (std::is_same_v<TPipeline, GlyphAtlasPipeline>) {
|
||||
vtx.color_glyph =
|
||||
glyph_position.glyph.type == Glyph::Type::kBitmap ? 1.0 : 0.0;
|
||||
}
|
||||
vertex_builder.AppendVertex(std::move(vtx));
|
||||
}
|
||||
}
|
||||
@ -165,4 +147,55 @@ bool TextContents::Render(const ContentContext& renderer,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextContents::RenderSdf(const ContentContext& renderer,
|
||||
const Entity& entity,
|
||||
RenderPass& pass) const {
|
||||
auto atlas = ResolveAtlas(GlyphAtlas::Type::kSignedDistanceField,
|
||||
renderer.GetContext());
|
||||
|
||||
if (!atlas || !atlas->IsValid()) {
|
||||
VALIDATION_LOG << "Cannot render glyphs without prepared atlas.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Information shared by all glyph draw calls.
|
||||
Command cmd;
|
||||
cmd.label = "TextFrameSDF";
|
||||
cmd.primitive_type = PrimitiveType::kTriangle;
|
||||
cmd.pipeline =
|
||||
renderer.GetGlyphAtlasSdfPipeline(OptionsFromPassAndEntity(pass, entity));
|
||||
cmd.stencil_reference = entity.GetStencilDepth();
|
||||
|
||||
return CommonRender<GlyphAtlasSdfPipeline>(renderer, entity, pass, color_,
|
||||
frame_, atlas, cmd);
|
||||
}
|
||||
|
||||
bool TextContents::Render(const ContentContext& renderer,
|
||||
const Entity& entity,
|
||||
RenderPass& pass) const {
|
||||
if (color_.IsTransparent()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto atlas = ResolveAtlas(frame_.HasColor() ? GlyphAtlas::Type::kColorBitmap
|
||||
: GlyphAtlas::Type::kAlphaBitmap,
|
||||
renderer.GetContext());
|
||||
|
||||
if (!atlas || !atlas->IsValid()) {
|
||||
VALIDATION_LOG << "Cannot render glyphs without prepared atlas.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Information shared by all glyph draw calls.
|
||||
Command cmd;
|
||||
cmd.label = "TextFrame";
|
||||
cmd.primitive_type = PrimitiveType::kTriangle;
|
||||
cmd.pipeline =
|
||||
renderer.GetGlyphAtlasPipeline(OptionsFromPassAndEntity(pass, entity));
|
||||
cmd.stencil_reference = entity.GetStencilDepth();
|
||||
|
||||
return CommonRender<GlyphAtlasPipeline>(renderer, entity, pass, color_,
|
||||
frame_, atlas, cmd);
|
||||
}
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
@ -12,11 +12,11 @@
|
||||
#include "flutter/fml/macros.h"
|
||||
#include "impeller/entity/contents/contents.h"
|
||||
#include "impeller/geometry/color.h"
|
||||
#include "impeller/typographer/glyph_atlas.h"
|
||||
#include "impeller/typographer/text_frame.h"
|
||||
|
||||
namespace impeller {
|
||||
|
||||
class GlyphAtlas;
|
||||
class LazyGlyphAtlas;
|
||||
class Context;
|
||||
|
||||
@ -28,8 +28,6 @@ class TextContents final : public Contents {
|
||||
|
||||
void SetTextFrame(TextFrame frame);
|
||||
|
||||
void SetGlyphAtlas(std::shared_ptr<GlyphAtlas> atlas);
|
||||
|
||||
void SetGlyphAtlas(std::shared_ptr<LazyGlyphAtlas> atlas);
|
||||
|
||||
void SetColor(Color color);
|
||||
@ -42,14 +40,18 @@ class TextContents final : public Contents {
|
||||
const Entity& entity,
|
||||
RenderPass& pass) const override;
|
||||
|
||||
// TODO(dnfield): remove this https://github.com/flutter/flutter/issues/111640
|
||||
bool RenderSdf(const ContentContext& renderer,
|
||||
const Entity& entity,
|
||||
RenderPass& pass) const;
|
||||
|
||||
private:
|
||||
TextFrame frame_;
|
||||
Color color_;
|
||||
mutable std::variant<std::shared_ptr<GlyphAtlas>,
|
||||
std::shared_ptr<LazyGlyphAtlas>>
|
||||
atlas_;
|
||||
mutable std::shared_ptr<LazyGlyphAtlas> lazy_atlas_;
|
||||
|
||||
std::shared_ptr<GlyphAtlas> ResolveAtlas(
|
||||
GlyphAtlas::Type type,
|
||||
std::shared_ptr<Context> context) const;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(TextContents);
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
#include "impeller/entity/contents/rrect_shadow_contents.h"
|
||||
#include "impeller/entity/contents/solid_color_contents.h"
|
||||
#include "impeller/entity/contents/solid_stroke_contents.h"
|
||||
#include "impeller/entity/contents/text_contents.h"
|
||||
#include "impeller/entity/contents/texture_contents.h"
|
||||
#include "impeller/entity/contents/vertices_contents.h"
|
||||
#include "impeller/entity/entity.h"
|
||||
@ -35,8 +36,11 @@
|
||||
#include "impeller/renderer/render_pass.h"
|
||||
#include "impeller/renderer/vertex_buffer_builder.h"
|
||||
#include "impeller/tessellator/tessellator.h"
|
||||
#include "impeller/typographer/backends/skia/text_frame_skia.h"
|
||||
#include "impeller/typographer/backends/skia/text_render_context_skia.h"
|
||||
#include "include/core/SkBlendMode.h"
|
||||
#include "third_party/imgui/imgui.h"
|
||||
#include "third_party/skia/include/core/SkTextBlob.h"
|
||||
|
||||
namespace impeller {
|
||||
namespace testing {
|
||||
@ -1976,5 +1980,31 @@ TEST_P(EntityTest, TTTBlendColor) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(EntityTest, SdfText) {
|
||||
auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
|
||||
SkFont font;
|
||||
font.setSize(30);
|
||||
auto blob = SkTextBlob::MakeFromString(
|
||||
"the quick brown fox jumped over the lazy dog (but with sdf).", font);
|
||||
auto frame = TextFrameFromTextBlob(blob);
|
||||
auto lazy_glyph_atlas = std::make_shared<LazyGlyphAtlas>();
|
||||
lazy_glyph_atlas->AddTextFrame(frame);
|
||||
|
||||
auto text_contents = std::make_shared<TextContents>();
|
||||
text_contents->SetTextFrame(std::move(frame));
|
||||
text_contents->SetGlyphAtlas(std::move(lazy_glyph_atlas));
|
||||
text_contents->SetColor(Color(1.0, 0.0, 0.0, 1.0));
|
||||
Entity entity;
|
||||
entity.SetTransformation(
|
||||
Matrix::MakeTranslation(Vector3{200.0, 200.0, 0.0}) *
|
||||
Matrix::MakeScale(GetContentScale()));
|
||||
entity.SetContents(text_contents);
|
||||
|
||||
// Force SDF rendering.
|
||||
return text_contents->RenderSdf(context, entity, pass);
|
||||
};
|
||||
ASSERT_TRUE(OpenPlaygroundHere(callback));
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace impeller
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <impeller/transform.glsl>
|
||||
|
||||
uniform FrameInfo {
|
||||
mat4 mvp;
|
||||
} frame_info;
|
||||
@ -19,22 +21,7 @@ out vec2 v_atlas_glyph_size;
|
||||
out float v_color_glyph;
|
||||
|
||||
void main() {
|
||||
vec4 translate = frame_info.mvp[0] * glyph_position.x
|
||||
+ frame_info.mvp[1] * glyph_position.y
|
||||
+ frame_info.mvp[3];
|
||||
mat4 translated_mvp = mat4(
|
||||
frame_info.mvp[0],
|
||||
frame_info.mvp[1],
|
||||
frame_info.mvp[2],
|
||||
vec4(
|
||||
translate.xyz,
|
||||
frame_info.mvp[3].w
|
||||
)
|
||||
);
|
||||
gl_Position = translated_mvp
|
||||
* vec4(unit_vertex.x * glyph_size.x,
|
||||
unit_vertex.y * glyph_size.y, 0.0, 1.0);
|
||||
|
||||
gl_Position = IPPositionForGlyphPosition(frame_info.mvp, unit_vertex, glyph_position, glyph_size);
|
||||
v_unit_vertex = unit_vertex;
|
||||
v_atlas_position = atlas_position;
|
||||
v_atlas_glyph_size = atlas_glyph_size;
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
// 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.
|
||||
|
||||
uniform sampler2D glyph_atlas_sampler;
|
||||
|
||||
uniform FragInfo {
|
||||
vec2 atlas_size;
|
||||
vec4 text_color;
|
||||
} frag_info;
|
||||
|
||||
in vec2 v_unit_vertex;
|
||||
in vec2 v_atlas_position;
|
||||
in vec2 v_atlas_glyph_size;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
void main() {
|
||||
vec2 scale_perspective = v_atlas_glyph_size / frag_info.atlas_size;
|
||||
vec2 offset = v_atlas_position / frag_info.atlas_size;
|
||||
|
||||
// Inspired by Metal by Example's SDF text rendering shader:
|
||||
// https://github.com/metal-by-example/sample-code/blob/master/objc/12-TextRendering/TextRendering/Shaders.metal
|
||||
|
||||
// Outline of glyph is the isocontour with value 50%
|
||||
float edge_distance = 0.5;
|
||||
// Sample the signed-distance field to find distance from this fragment to the glyph outline
|
||||
float sample_distance = texture(glyph_atlas_sampler, v_unit_vertex * scale_perspective + offset).a;
|
||||
// Use local automatic gradients to find anti-aliased anisotropic edge width, cf. Gustavson 2012
|
||||
float edge_width = length(vec2(dFdx(sample_distance), dFdy(sample_distance)));
|
||||
// Smooth the glyph edge by interpolating across the boundary in a band with the width determined above
|
||||
float insideness = smoothstep(edge_distance - edge_width, edge_distance + edge_width, sample_distance);
|
||||
frag_color = frag_info.text_color * insideness;
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
// 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 <impeller/transform.glsl>
|
||||
|
||||
uniform FrameInfo {
|
||||
mat4 mvp;
|
||||
} frame_info;
|
||||
|
||||
in vec2 unit_vertex;
|
||||
in vec2 glyph_position;
|
||||
in vec2 glyph_size;
|
||||
in vec2 atlas_position;
|
||||
in vec2 atlas_glyph_size;
|
||||
|
||||
out vec2 v_unit_vertex;
|
||||
out vec2 v_atlas_position;
|
||||
out vec2 v_atlas_glyph_size;
|
||||
|
||||
void main() {
|
||||
gl_Position = IPPositionForGlyphPosition(frame_info.mvp, unit_vertex, glyph_position, glyph_size);
|
||||
v_unit_vertex = unit_vertex;
|
||||
v_atlas_position = atlas_position;
|
||||
v_atlas_glyph_size = atlas_glyph_size;
|
||||
}
|
||||
@ -8,6 +8,7 @@
|
||||
#include "impeller/typographer/backends/skia/typeface_skia.h"
|
||||
#include "third_party/skia/include/core/SkFont.h"
|
||||
#include "third_party/skia/include/core/SkFontMetrics.h"
|
||||
#include "third_party/skia/src/core/SkStrikeSpec.h" // nogncheck
|
||||
#include "third_party/skia/src/core/SkTextBlobPriv.h" // nogncheck
|
||||
|
||||
namespace impeller {
|
||||
@ -38,6 +39,12 @@ TextFrame TextFrameFromTextBlob(sk_sp<SkTextBlob> blob, Scalar scale) {
|
||||
|
||||
for (SkTextBlobRunIterator run(blob.get()); !run.done(); run.next()) {
|
||||
TextRun text_run(ToFont(run.font(), scale));
|
||||
|
||||
// TODO(jonahwilliams): ask Skia for a public API to look this up.
|
||||
// https://github.com/flutter/flutter/issues/112005
|
||||
SkStrikeSpec strikeSpec = SkStrikeSpec::MakeWithNoDevice(run.font());
|
||||
SkBulkGlyphMetricsAndPaths paths{strikeSpec};
|
||||
|
||||
const auto glyph_count = run.glyphCount();
|
||||
const auto* glyphs = run.glyphs();
|
||||
switch (run.positioning()) {
|
||||
@ -52,7 +59,12 @@ TextFrame TextFrameFromTextBlob(sk_sp<SkTextBlob> blob, Scalar scale) {
|
||||
// kFull_Positioning has two scalars per glyph.
|
||||
const SkPoint* glyph_points = run.points();
|
||||
const auto* point = glyph_points + i;
|
||||
text_run.AddGlyph(glyphs[i], Point{point->x(), point->y()});
|
||||
Glyph::Type type = paths.glyph(glyphs[i])->isColor()
|
||||
? Glyph::Type::kBitmap
|
||||
: Glyph::Type::kPath;
|
||||
|
||||
text_run.AddGlyph(Glyph{glyphs[i], type},
|
||||
Point{point->x(), point->y()});
|
||||
}
|
||||
break;
|
||||
case SkTextBlobRunIterator::kRSXform_Positioning:
|
||||
|
||||
@ -15,9 +15,8 @@
|
||||
#include "third_party/skia/include/core/SkFontMetrics.h"
|
||||
#include "third_party/skia/include/core/SkRSXform.h"
|
||||
#include "third_party/skia/include/core/SkSurface.h"
|
||||
#include "third_party/skia/src/core/SkIPoint16.h" //nogncheck
|
||||
#include "third_party/skia/src/core/SkStrikeSpec.h" // nogncheck
|
||||
#include "third_party/skia/src/gpu/GrRectanizer.h" //nogncheck
|
||||
#include "third_party/skia/src/core/SkIPoint16.h" // nogncheck
|
||||
#include "third_party/skia/src/gpu/GrRectanizer.h" // nogncheck
|
||||
|
||||
namespace impeller {
|
||||
|
||||
@ -27,11 +26,15 @@ TextRenderContextSkia::TextRenderContextSkia(std::shared_ptr<Context> context)
|
||||
TextRenderContextSkia::~TextRenderContextSkia() = default;
|
||||
|
||||
static FontGlyphPair::Set CollectUniqueFontGlyphPairsSet(
|
||||
GlyphAtlas::Type type,
|
||||
TextRenderContext::FrameIterator frame_iterator) {
|
||||
FontGlyphPair::Set set;
|
||||
while (auto frame = frame_iterator()) {
|
||||
for (const auto& run : frame->GetRuns()) {
|
||||
auto font = run.GetFont();
|
||||
// TODO(dnfield): If we're doing SDF here, we should be using a consistent
|
||||
// point size.
|
||||
// https://github.com/flutter/flutter/issues/112016
|
||||
for (const auto& glyph_position : run.GetGlyphPositions()) {
|
||||
set.insert({font, glyph_position.glyph});
|
||||
}
|
||||
@ -41,10 +44,11 @@ static FontGlyphPair::Set CollectUniqueFontGlyphPairsSet(
|
||||
}
|
||||
|
||||
static FontGlyphPair::Vector CollectUniqueFontGlyphPairs(
|
||||
GlyphAtlas::Type type,
|
||||
TextRenderContext::FrameIterator frame_iterator) {
|
||||
TRACE_EVENT0("impeller", __FUNCTION__);
|
||||
FontGlyphPair::Vector vector;
|
||||
auto set = CollectUniqueFontGlyphPairsSet(frame_iterator);
|
||||
auto set = CollectUniqueFontGlyphPairsSet(type, frame_iterator);
|
||||
vector.reserve(set.size());
|
||||
for (const auto& item : set) {
|
||||
vector.emplace_back(std::move(item));
|
||||
@ -104,13 +108,137 @@ static size_t OptimumAtlasSizeForFontGlyphPairs(
|
||||
return 0u;
|
||||
}
|
||||
|
||||
/// Compute signed-distance field for an 8-bpp grayscale image (values greater
|
||||
/// than 127 are considered "on") For details of this algorithm, see "The 'dead
|
||||
/// reckoning' signed distance transform" [Grevera 2004]
|
||||
static void ConvertBitmapToSignedDistanceField(uint8_t* pixels,
|
||||
uint16_t width,
|
||||
uint16_t height) {
|
||||
if (!pixels || width == 0 || height == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
using ShortPoint = TPoint<uint16_t>;
|
||||
|
||||
// distance to nearest boundary point map
|
||||
std::vector<Scalar> distance_map(width * height);
|
||||
// nearest boundary point map
|
||||
std::vector<ShortPoint> boundary_point_map(width * height);
|
||||
|
||||
// Some helpers for manipulating the above arrays
|
||||
#define image(_x, _y) (pixels[(_y)*width + (_x)] > 0x7f)
|
||||
#define distance(_x, _y) distance_map[(_y)*width + (_x)]
|
||||
#define nearestpt(_x, _y) boundary_point_map[(_y)*width + (_x)]
|
||||
|
||||
const Scalar maxDist = hypot(width, height);
|
||||
const Scalar distUnit = 1;
|
||||
const Scalar distDiag = sqrt(2);
|
||||
|
||||
// Initialization phase: set all distances to "infinity"; zero out nearest
|
||||
// boundary point map
|
||||
for (uint16_t y = 0; y < height; ++y) {
|
||||
for (uint16_t x = 0; x < width; ++x) {
|
||||
distance(x, y) = maxDist;
|
||||
nearestpt(x, y) = ShortPoint{0, 0};
|
||||
}
|
||||
}
|
||||
|
||||
// Immediate interior/exterior phase: mark all points along the boundary as
|
||||
// such
|
||||
for (uint16_t y = 1; y < height - 1; ++y) {
|
||||
for (uint16_t x = 1; x < width - 1; ++x) {
|
||||
bool inside = image(x, y);
|
||||
if (image(x - 1, y) != inside || image(x + 1, y) != inside ||
|
||||
image(x, y - 1) != inside || image(x, y + 1) != inside) {
|
||||
distance(x, y) = 0;
|
||||
nearestpt(x, y) = ShortPoint{x, y};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Forward dead-reckoning pass
|
||||
for (uint16_t y = 1; y < height - 2; ++y) {
|
||||
for (uint16_t x = 1; x < width - 2; ++x) {
|
||||
if (distance_map[(y - 1) * width + (x - 1)] + distDiag < distance(x, y)) {
|
||||
nearestpt(x, y) = nearestpt(x - 1, y - 1);
|
||||
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
|
||||
}
|
||||
if (distance(x, y - 1) + distUnit < distance(x, y)) {
|
||||
nearestpt(x, y) = nearestpt(x, y - 1);
|
||||
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
|
||||
}
|
||||
if (distance(x + 1, y - 1) + distDiag < distance(x, y)) {
|
||||
nearestpt(x, y) = nearestpt(x + 1, y - 1);
|
||||
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
|
||||
}
|
||||
if (distance(x - 1, y) + distUnit < distance(x, y)) {
|
||||
nearestpt(x, y) = nearestpt(x - 1, y);
|
||||
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Backward dead-reckoning pass
|
||||
for (uint16_t y = height - 2; y >= 1; --y) {
|
||||
for (uint16_t x = width - 2; x >= 1; --x) {
|
||||
if (distance(x + 1, y) + distUnit < distance(x, y)) {
|
||||
nearestpt(x, y) = nearestpt(x + 1, y);
|
||||
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
|
||||
}
|
||||
if (distance(x - 1, y + 1) + distDiag < distance(x, y)) {
|
||||
nearestpt(x, y) = nearestpt(x - 1, y + 1);
|
||||
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
|
||||
}
|
||||
if (distance(x, y + 1) + distUnit < distance(x, y)) {
|
||||
nearestpt(x, y) = nearestpt(x, y + 1);
|
||||
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
|
||||
}
|
||||
if (distance(x + 1, y + 1) + distDiag < distance(x, y)) {
|
||||
nearestpt(x, y) = nearestpt(x + 1, y + 1);
|
||||
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Interior distance negation pass; distances outside the figure are
|
||||
// considered negative
|
||||
// Also does final quantization.
|
||||
for (uint16_t y = 0; y < height; ++y) {
|
||||
for (uint16_t x = 0; x < width; ++x) {
|
||||
if (!image(x, y)) {
|
||||
distance(x, y) = -distance(x, y);
|
||||
}
|
||||
|
||||
float norm_factor = 13.5;
|
||||
float dist = distance(x, y);
|
||||
float clamped_dist = fmax(-norm_factor, fmin(dist, norm_factor));
|
||||
float scaled_dist = clamped_dist / norm_factor;
|
||||
uint8_t quantized_value = ((scaled_dist + 1) / 2) * UINT8_MAX;
|
||||
pixels[y * width + x] = quantized_value;
|
||||
}
|
||||
}
|
||||
|
||||
#undef image
|
||||
#undef distance
|
||||
#undef nearestpt
|
||||
}
|
||||
|
||||
static std::shared_ptr<SkBitmap> CreateAtlasBitmap(const GlyphAtlas& atlas,
|
||||
size_t atlas_size) {
|
||||
TRACE_EVENT0("impeller", __FUNCTION__);
|
||||
auto bitmap = std::make_shared<SkBitmap>();
|
||||
auto image_info = atlas.ContainsColorGlyph()
|
||||
? SkImageInfo::MakeN32Premul(atlas_size, atlas_size)
|
||||
: SkImageInfo::MakeA8(atlas_size, atlas_size);
|
||||
SkImageInfo image_info;
|
||||
|
||||
switch (atlas.GetType()) {
|
||||
case GlyphAtlas::Type::kSignedDistanceField:
|
||||
case GlyphAtlas::Type::kAlphaBitmap:
|
||||
image_info = SkImageInfo::MakeA8(atlas_size, atlas_size);
|
||||
break;
|
||||
case GlyphAtlas::Type::kColorBitmap:
|
||||
image_info = SkImageInfo::MakeN32Premul(atlas_size, atlas_size);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!bitmap->tryAllocPixels(image_info)) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -133,7 +261,6 @@ static std::shared_ptr<SkBitmap> CreateAtlasBitmap(const GlyphAtlas& atlas,
|
||||
SkFont sk_font(
|
||||
TypefaceSkia::Cast(*font_glyph.font.GetTypeface()).GetSkiaTypeface(),
|
||||
metrics.point_size);
|
||||
|
||||
auto glyph_color = SK_ColorWHITE;
|
||||
|
||||
SkPaint glyph_paint;
|
||||
@ -196,29 +323,20 @@ static std::shared_ptr<Texture> UploadGlyphTextureAtlas(
|
||||
}
|
||||
|
||||
std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
|
||||
GlyphAtlas::Type type,
|
||||
FrameIterator frame_iterator) const {
|
||||
TRACE_EVENT0("impeller", __FUNCTION__);
|
||||
if (!IsValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto glyph_atlas = std::make_shared<GlyphAtlas>();
|
||||
glyph_atlas->SetFontColorCallback([](const FontGlyphPair& font_glyph) {
|
||||
// TODO(jonahwilliams): ask Skia for a public API to look this up.
|
||||
SkFont sk_font(
|
||||
TypefaceSkia::Cast(*font_glyph.font.GetTypeface()).GetSkiaTypeface(),
|
||||
font_glyph.font.GetMetrics().point_size);
|
||||
|
||||
SkStrikeSpec strikeSpec = SkStrikeSpec::MakeWithNoDevice(sk_font);
|
||||
SkBulkGlyphMetricsAndPaths paths{strikeSpec};
|
||||
return paths.glyph(font_glyph.glyph.index)->isColor();
|
||||
});
|
||||
auto glyph_atlas = std::make_shared<GlyphAtlas>(type);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Step 1: Collect unique font-glyph pairs in the frame.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
auto font_glyph_pairs = CollectUniqueFontGlyphPairs(frame_iterator);
|
||||
auto font_glyph_pairs = CollectUniqueFontGlyphPairs(type, frame_iterator);
|
||||
if (font_glyph_pairs.empty()) {
|
||||
return glyph_atlas;
|
||||
}
|
||||
@ -262,9 +380,19 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
|
||||
// ---------------------------------------------------------------------------
|
||||
// Step 6: Upload the atlas as a texture.
|
||||
// ---------------------------------------------------------------------------
|
||||
auto format = glyph_atlas->ContainsColorGlyph()
|
||||
? PixelFormat::kR8G8B8A8UNormInt
|
||||
: PixelFormat::kA8UNormInt;
|
||||
PixelFormat format;
|
||||
switch (type) {
|
||||
case GlyphAtlas::Type::kSignedDistanceField:
|
||||
ConvertBitmapToSignedDistanceField(
|
||||
reinterpret_cast<uint8_t*>(bitmap->getPixels()), atlas_size,
|
||||
atlas_size);
|
||||
case GlyphAtlas::Type::kAlphaBitmap:
|
||||
format = PixelFormat::kA8UNormInt;
|
||||
break;
|
||||
case GlyphAtlas::Type::kColorBitmap:
|
||||
format = PixelFormat::kR8G8B8A8UNormInt;
|
||||
break;
|
||||
}
|
||||
auto texture = UploadGlyphTextureAtlas(GetContext()->GetResourceAllocator(),
|
||||
bitmap, atlas_size, format);
|
||||
if (!texture) {
|
||||
|
||||
@ -17,6 +17,7 @@ class TextRenderContextSkia : public TextRenderContext {
|
||||
|
||||
// |TextRenderContext|
|
||||
std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
|
||||
GlyphAtlas::Type type,
|
||||
FrameIterator iterator) const override;
|
||||
|
||||
private:
|
||||
|
||||
@ -15,9 +15,19 @@ namespace impeller {
|
||||
/// @brief The glyph index in the typeface.
|
||||
///
|
||||
struct Glyph {
|
||||
enum class Type {
|
||||
kPath,
|
||||
kBitmap,
|
||||
};
|
||||
|
||||
uint16_t index = 0;
|
||||
|
||||
Glyph(uint16_t p_index) : index(p_index) {}
|
||||
//------------------------------------------------------------------------------
|
||||
/// @brief Whether the glyph is a path or a bitmap.
|
||||
///
|
||||
Type type = Type::kPath;
|
||||
|
||||
Glyph(uint16_t p_index, Type p_type) : index(p_index), type(p_type) {}
|
||||
};
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
|
||||
namespace impeller {
|
||||
|
||||
GlyphAtlas::GlyphAtlas() = default;
|
||||
GlyphAtlas::GlyphAtlas(Type type) : type_(type) {}
|
||||
|
||||
GlyphAtlas::~GlyphAtlas() = default;
|
||||
|
||||
@ -14,8 +14,8 @@ bool GlyphAtlas::IsValid() const {
|
||||
return !!texture_;
|
||||
}
|
||||
|
||||
bool GlyphAtlas::ContainsColorGlyph() const {
|
||||
return has_color_glyph;
|
||||
GlyphAtlas::Type GlyphAtlas::GetType() const {
|
||||
return type_;
|
||||
}
|
||||
|
||||
const std::shared_ptr<Texture>& GlyphAtlas::GetTexture() const {
|
||||
@ -27,28 +27,9 @@ void GlyphAtlas::SetTexture(std::shared_ptr<Texture> texture) {
|
||||
}
|
||||
|
||||
void GlyphAtlas::AddTypefaceGlyphPosition(FontGlyphPair pair, Rect rect) {
|
||||
if (callback_.has_value()) {
|
||||
auto has_color = callback_.value()(pair);
|
||||
has_color_glyph |= has_color;
|
||||
colors_[pair] = has_color;
|
||||
}
|
||||
|
||||
positions_[pair] = rect;
|
||||
}
|
||||
|
||||
void GlyphAtlas::SetFontColorCallback(
|
||||
std::function<bool(const FontGlyphPair& pair)> callback) {
|
||||
callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
bool GlyphAtlas::IsColorFontGlyphPair(const FontGlyphPair& pair) const {
|
||||
auto found = colors_.find(pair);
|
||||
if (found == colors_.end()) {
|
||||
return false;
|
||||
}
|
||||
return found->second;
|
||||
}
|
||||
|
||||
std::optional<Rect> GlyphAtlas::FindFontGlyphPosition(
|
||||
const FontGlyphPair& pair) const {
|
||||
auto found = positions_.find(pair);
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
|
||||
#include "flutter/fml/macros.h"
|
||||
#include "impeller/geometry/rect.h"
|
||||
#include "impeller/renderer/pipeline.h"
|
||||
#include "impeller/renderer/texture.h"
|
||||
#include "impeller/typographer/font_glyph_pair.h"
|
||||
|
||||
@ -23,19 +24,44 @@ namespace impeller {
|
||||
///
|
||||
class GlyphAtlas {
|
||||
public:
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Describes how the glyphs are represented in the texture.
|
||||
enum class Type {
|
||||
//--------------------------------------------------------------------------
|
||||
/// The glyphs are represented at a fixed size in an 8-bit grayscale texture
|
||||
/// where the value of each pixel represents a signed-distance field that
|
||||
/// stores the glyph outlines.
|
||||
///
|
||||
kSignedDistanceField,
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// The glyphs are reprsented at their requested size using only an 8-bit
|
||||
/// alpha channel.
|
||||
///
|
||||
kAlphaBitmap,
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// The glyphs are reprsented at their requested size using N32 premul
|
||||
/// colors.
|
||||
///
|
||||
kColorBitmap,
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Create an empty glyph atlas.
|
||||
///
|
||||
GlyphAtlas();
|
||||
/// @param[in] type How the glyphs are represented in the texture.
|
||||
///
|
||||
explicit GlyphAtlas(Type type);
|
||||
|
||||
~GlyphAtlas();
|
||||
|
||||
bool IsValid() const;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Whether at least one font-glyph pair has colors.
|
||||
/// @brief Describes how the glyphs are represented in the texture.
|
||||
///
|
||||
bool ContainsColorGlyph() const;
|
||||
Type GetType() const;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Set the texture for the glyph atlas.
|
||||
@ -44,20 +70,6 @@ class GlyphAtlas {
|
||||
///
|
||||
void SetTexture(std::shared_ptr<Texture> texture);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Set a callback that determines if a glyph-font pair
|
||||
/// has color.
|
||||
///
|
||||
/// @param[in] callback The callback
|
||||
///
|
||||
void SetFontColorCallback(
|
||||
std::function<bool(const FontGlyphPair& pair)> callback);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Whether the provided glyph-font pair contains color.
|
||||
///
|
||||
bool IsColorFontGlyphPair(const FontGlyphPair& pair) const;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Get the texture for the glyph atlas.
|
||||
///
|
||||
@ -104,9 +116,8 @@ class GlyphAtlas {
|
||||
std::optional<Rect> FindFontGlyphPosition(const FontGlyphPair& pair) const;
|
||||
|
||||
private:
|
||||
const Type type_;
|
||||
std::shared_ptr<Texture> texture_;
|
||||
std::optional<std::function<bool(const FontGlyphPair& pair)>> callback_;
|
||||
bool has_color_glyph = false;
|
||||
|
||||
std::unordered_map<FontGlyphPair,
|
||||
Rect,
|
||||
@ -114,12 +125,6 @@ class GlyphAtlas {
|
||||
FontGlyphPair::Equal>
|
||||
positions_;
|
||||
|
||||
std::unordered_map<FontGlyphPair,
|
||||
bool,
|
||||
FontGlyphPair::Hash,
|
||||
FontGlyphPair::Equal>
|
||||
colors_;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(GlyphAtlas);
|
||||
};
|
||||
|
||||
|
||||
@ -14,14 +14,18 @@ LazyGlyphAtlas::LazyGlyphAtlas() = default;
|
||||
LazyGlyphAtlas::~LazyGlyphAtlas() = default;
|
||||
|
||||
void LazyGlyphAtlas::AddTextFrame(TextFrame frame) {
|
||||
FML_DCHECK(!atlas_);
|
||||
FML_DCHECK(atlas_map_.empty());
|
||||
frames_.emplace_back(std::move(frame));
|
||||
}
|
||||
|
||||
std::shared_ptr<GlyphAtlas> LazyGlyphAtlas::CreateOrGetGlyphAtlas(
|
||||
GlyphAtlas::Type type,
|
||||
std::shared_ptr<Context> context) const {
|
||||
if (atlas_) {
|
||||
return atlas_;
|
||||
{
|
||||
auto atlas_it = atlas_map_.find(type);
|
||||
if (atlas_it != atlas_map_.end()) {
|
||||
return atlas_it->second;
|
||||
}
|
||||
}
|
||||
|
||||
auto text_context = TextRenderContext::Create(std::move(context));
|
||||
@ -37,13 +41,13 @@ std::shared_ptr<GlyphAtlas> LazyGlyphAtlas::CreateOrGetGlyphAtlas(
|
||||
i++;
|
||||
return &result;
|
||||
};
|
||||
auto atlas = text_context->CreateGlyphAtlas(iterator);
|
||||
auto atlas = text_context->CreateGlyphAtlas(type, iterator);
|
||||
if (!atlas || !atlas->IsValid()) {
|
||||
VALIDATION_LOG << "Could not create valid atlas.";
|
||||
return nullptr;
|
||||
}
|
||||
atlas_ = std::move(atlas);
|
||||
return atlas_;
|
||||
atlas_map_[type] = atlas;
|
||||
return atlas;
|
||||
}
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "flutter/fml/macros.h"
|
||||
#include "impeller/renderer/context.h"
|
||||
#include "impeller/typographer/glyph_atlas.h"
|
||||
@ -20,11 +22,13 @@ class LazyGlyphAtlas {
|
||||
void AddTextFrame(TextFrame frame);
|
||||
|
||||
std::shared_ptr<GlyphAtlas> CreateOrGetGlyphAtlas(
|
||||
GlyphAtlas::Type type,
|
||||
std::shared_ptr<Context> context) const;
|
||||
|
||||
private:
|
||||
std::vector<TextFrame> frames_;
|
||||
mutable std::shared_ptr<GlyphAtlas> atlas_;
|
||||
mutable std::unordered_map<GlyphAtlas::Type, std::shared_ptr<GlyphAtlas>>
|
||||
atlas_map_;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(LazyGlyphAtlas);
|
||||
};
|
||||
|
||||
@ -29,6 +29,7 @@ bool TextFrame::AddTextRun(TextRun run) {
|
||||
if (!run.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
has_color_ |= run.HasColor();
|
||||
runs_.emplace_back(std::move(run));
|
||||
return true;
|
||||
}
|
||||
@ -41,4 +42,8 @@ const std::vector<TextRun>& TextFrame::GetRuns() const {
|
||||
return runs_;
|
||||
}
|
||||
|
||||
bool TextFrame::HasColor() const {
|
||||
return has_color_;
|
||||
}
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
@ -52,8 +52,13 @@ class TextFrame {
|
||||
///
|
||||
const std::vector<TextRun>& GetRuns() const;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Whether any run in this frame has color.
|
||||
bool HasColor() const;
|
||||
|
||||
private:
|
||||
std::vector<TextRun> runs_;
|
||||
bool has_color_ = false;
|
||||
};
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
@ -33,6 +33,7 @@ const std::shared_ptr<Context>& TextRenderContext::GetContext() const {
|
||||
}
|
||||
|
||||
std::shared_ptr<GlyphAtlas> TextRenderContext::CreateGlyphAtlas(
|
||||
GlyphAtlas::Type type,
|
||||
const TextFrame& frame) const {
|
||||
size_t count = 0;
|
||||
FrameIterator iterator = [&]() -> const TextFrame* {
|
||||
@ -42,7 +43,7 @@ std::shared_ptr<GlyphAtlas> TextRenderContext::CreateGlyphAtlas(
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
return CreateGlyphAtlas(iterator);
|
||||
return CreateGlyphAtlas(type, iterator);
|
||||
}
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
@ -38,10 +38,16 @@ class TextRenderContext {
|
||||
const std::shared_ptr<Context>& GetContext() const;
|
||||
|
||||
using FrameIterator = std::function<const TextFrame*(void)>;
|
||||
|
||||
// TODO(dnfield): Callers should not need to know which type of atlas to
|
||||
// create. https://github.com/flutter/flutter/issues/111640
|
||||
|
||||
virtual std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
|
||||
GlyphAtlas::Type type,
|
||||
FrameIterator iterator) const = 0;
|
||||
|
||||
std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(const TextFrame& frame) const;
|
||||
std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(GlyphAtlas::Type type,
|
||||
const TextFrame& frame) const;
|
||||
|
||||
protected:
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
@ -17,6 +17,7 @@ TextRun::~TextRun() = default;
|
||||
|
||||
bool TextRun::AddGlyph(Glyph glyph, Point position) {
|
||||
glyphs_.emplace_back(GlyphPosition{glyph, position});
|
||||
has_color_ |= glyph.type == Glyph::Type::kBitmap;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -36,4 +37,8 @@ const Font& TextRun::GetFont() const {
|
||||
return font_;
|
||||
}
|
||||
|
||||
bool TextRun::HasColor() const {
|
||||
return has_color_;
|
||||
}
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
@ -68,10 +68,15 @@ class TextRun {
|
||||
///
|
||||
const Font& GetFont() const;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Whether any glyph in this run has color.
|
||||
bool HasColor() const;
|
||||
|
||||
private:
|
||||
Font font_;
|
||||
std::vector<GlyphPosition> glyphs_;
|
||||
bool is_valid_ = false;
|
||||
bool has_color_ = false;
|
||||
};
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
@ -38,9 +38,9 @@ TEST_P(TypographerTest, CanCreateGlyphAtlas) {
|
||||
SkFont sk_font;
|
||||
auto blob = SkTextBlob::MakeFromString("hello", sk_font);
|
||||
ASSERT_TRUE(blob);
|
||||
auto atlas = context->CreateGlyphAtlas(TextFrameFromTextBlob(blob));
|
||||
auto atlas = context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap,
|
||||
TextFrameFromTextBlob(blob));
|
||||
ASSERT_NE(atlas, nullptr);
|
||||
ASSERT_FALSE(atlas->ContainsColorGlyph());
|
||||
OpenPlaygroundHere([](RenderTarget&) { return true; });
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user