| // 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/entity/contents/text_contents.h" |
| |
| #include <cstring> |
| #include <optional> |
| #include <utility> |
| |
| #include "impeller/core/buffer_view.h" |
| #include "impeller/core/formats.h" |
| #include "impeller/core/sampler_descriptor.h" |
| #include "impeller/entity/contents/content_context.h" |
| #include "impeller/entity/entity.h" |
| #include "impeller/geometry/color.h" |
| #include "impeller/geometry/point.h" |
| #include "impeller/renderer/render_pass.h" |
| #include "impeller/typographer/glyph_atlas.h" |
| #include "impeller/typographer/lazy_glyph_atlas.h" |
| |
| namespace impeller { |
| |
| TextContents::TextContents() = default; |
| |
| TextContents::~TextContents() = default; |
| |
| void TextContents::SetTextFrame(const std::shared_ptr<TextFrame>& frame) { |
| frame_ = frame; |
| } |
| |
| void TextContents::SetColor(Color color) { |
| color_ = color; |
| } |
| |
| Color TextContents::GetColor() const { |
| return color_.WithAlpha(color_.alpha * inherited_opacity_); |
| } |
| |
| bool TextContents::CanInheritOpacity(const Entity& entity) const { |
| // Computing whether or not opacity can be inherited requires determining if |
| // any glyphs can overlap exactly. While this was previously implemented |
| // via TextFrame::MaybeHasOverlapping, this code relied on scaling up text |
| // bounds for a size specified at 1.0 DPR, which was not accurate at |
| // higher or lower DPRs. Rather than re-implement the checks to compute exact |
| // glyph bounds, for now this optimization has been disabled for Text. |
| return false; |
| } |
| |
| void TextContents::SetInheritedOpacity(Scalar opacity) { |
| inherited_opacity_ = opacity; |
| } |
| |
| void TextContents::SetOffset(Vector2 offset) { |
| offset_ = offset; |
| } |
| |
| void TextContents::SetForceTextColor(bool value) { |
| force_text_color_ = value; |
| } |
| |
| std::optional<Rect> TextContents::GetCoverage(const Entity& entity) const { |
| return frame_->GetBounds().TransformBounds(entity.GetTransform()); |
| } |
| |
| void TextContents::PopulateGlyphAtlas( |
| const std::shared_ptr<LazyGlyphAtlas>& lazy_glyph_atlas, |
| Scalar scale) { |
| lazy_glyph_atlas->AddTextFrame(*frame_, scale, offset_, properties_); |
| scale_ = scale; |
| } |
| |
| void TextContents::SetTextProperties(Color color, |
| bool stroke, |
| Scalar stroke_width, |
| Cap stroke_cap, |
| Join stroke_join, |
| Scalar stroke_miter) { |
| if (frame_->HasColor()) { |
| // Alpha is always applied when rendering, remove it here so |
| // we do not double-apply the alpha. |
| properties_.color = color.WithAlpha(1.0); |
| } |
| if (stroke) { |
| properties_.stroke = true; |
| properties_.stroke_width = stroke_width; |
| properties_.stroke_cap = stroke_cap; |
| properties_.stroke_join = stroke_join; |
| properties_.stroke_miter = stroke_miter; |
| } |
| } |
| |
| bool TextContents::Render(const ContentContext& renderer, |
| const Entity& entity, |
| RenderPass& pass) const { |
| auto color = GetColor(); |
| if (color.IsTransparent()) { |
| return true; |
| } |
| |
| auto type = frame_->GetAtlasType(); |
| const std::shared_ptr<GlyphAtlas>& atlas = |
| renderer.GetLazyGlyphAtlas()->CreateOrGetGlyphAtlas( |
| *renderer.GetContext(), renderer.GetTransientsBuffer(), type); |
| |
| if (!atlas || !atlas->IsValid()) { |
| VALIDATION_LOG << "Cannot render glyphs without prepared atlas."; |
| return false; |
| } |
| |
| // Information shared by all glyph draw calls. |
| pass.SetCommandLabel("TextFrame"); |
| auto opts = OptionsFromPassAndEntity(pass, entity); |
| opts.primitive_type = PrimitiveType::kTriangle; |
| pass.SetPipeline(renderer.GetGlyphAtlasPipeline(opts)); |
| |
| using VS = GlyphAtlasPipeline::VertexShader; |
| using FS = GlyphAtlasPipeline::FragmentShader; |
| |
| // Common vertex uniforms for all glyphs. |
| VS::FrameInfo frame_info; |
| frame_info.mvp = |
| Entity::GetShaderTransform(entity.GetShaderClipDepth(), pass, Matrix()); |
| ISize atlas_size = atlas->GetTexture()->GetSize(); |
| bool is_translation_scale = entity.GetTransform().IsTranslationScaleOnly(); |
| Matrix entity_transform = entity.GetTransform(); |
| Matrix basis_transform = entity_transform.Basis(); |
| |
| VS::BindFrameInfo(pass, |
| renderer.GetTransientsBuffer().EmplaceUniform(frame_info)); |
| |
| FS::FragInfo frag_info; |
| frag_info.use_text_color = force_text_color_ ? 1.0 : 0.0; |
| frag_info.text_color = ToVector(color.Premultiply()); |
| frag_info.is_color_glyph = type == GlyphAtlas::Type::kColorBitmap; |
| |
| FS::BindFragInfo(pass, |
| renderer.GetTransientsBuffer().EmplaceUniform(frag_info)); |
| |
| SamplerDescriptor sampler_desc; |
| if (is_translation_scale) { |
| sampler_desc.min_filter = MinMagFilter::kNearest; |
| sampler_desc.mag_filter = MinMagFilter::kNearest; |
| } else { |
| // Currently, we only propagate the scale of the transform to the atlas |
| // renderer, so if the transform has more than just a translation, we turn |
| // on linear sampling to prevent crunchiness caused by the pixel grid not |
| // being perfectly aligned. |
| // The downside is that this slightly over-blurs rotated/skewed text. |
| sampler_desc.min_filter = MinMagFilter::kLinear; |
| sampler_desc.mag_filter = MinMagFilter::kLinear; |
| } |
| |
| // No mipmaps for glyph atlas (glyphs are generated at exact scales). |
| sampler_desc.mip_filter = MipFilter::kBase; |
| |
| FS::BindGlyphAtlasSampler( |
| pass, // command |
| atlas->GetTexture(), // texture |
| renderer.GetContext()->GetSamplerLibrary()->GetSampler( |
| sampler_desc) // sampler |
| ); |
| |
| // Common vertex information for all glyphs. |
| // All glyphs are given the same vertex information in the form of a |
| // unit-sized quad. The size of the glyph is specified in per instance data |
| // and the vertex shader uses this to size the glyph correctly. The |
| // interpolated vertex information is also used in the fragment shader to |
| // sample from the glyph atlas. |
| |
| constexpr std::array<Point, 6> unit_points = {Point{0, 0}, Point{1, 0}, |
| Point{0, 1}, Point{1, 0}, |
| Point{0, 1}, Point{1, 1}}; |
| |
| auto& host_buffer = renderer.GetTransientsBuffer(); |
| size_t vertex_count = 0; |
| for (const auto& run : frame_->GetRuns()) { |
| vertex_count += run.GetGlyphPositions().size(); |
| } |
| vertex_count *= 6; |
| |
| BufferView buffer_view = host_buffer.Emplace( |
| vertex_count * sizeof(VS::PerVertexData), alignof(VS::PerVertexData), |
| [&](uint8_t* contents) { |
| VS::PerVertexData vtx; |
| VS::PerVertexData* vtx_contents = |
| reinterpret_cast<VS::PerVertexData*>(contents); |
| size_t i = 0u; |
| for (const TextRun& run : frame_->GetRuns()) { |
| const Font& font = run.GetFont(); |
| Scalar rounded_scale = TextFrame::RoundScaledFontSize( |
| scale_, font.GetMetrics().point_size); |
| const FontGlyphAtlas* font_atlas = |
| atlas->GetFontGlyphAtlas(font, rounded_scale); |
| if (!font_atlas) { |
| VALIDATION_LOG << "Could not find font in the atlas."; |
| continue; |
| } |
| |
| // Adjust glyph position based on the subpixel rounding |
| // used by the font. |
| Point subpixel_adjustment(0.5, 0.5); |
| switch (font.GetAxisAlignment()) { |
| case AxisAlignment::kNone: |
| break; |
| case AxisAlignment::kX: |
| subpixel_adjustment.x = 0.125; |
| break; |
| case AxisAlignment::kY: |
| subpixel_adjustment.y = 0.125; |
| break; |
| case AxisAlignment::kAll: |
| subpixel_adjustment.x = 0.125; |
| subpixel_adjustment.y = 0.125; |
| break; |
| } |
| |
| Point screen_offset = (entity_transform * Point(0, 0)); |
| for (const TextRun::GlyphPosition& glyph_position : |
| run.GetGlyphPositions()) { |
| // Note: uses unrounded scale for more accurate subpixel position. |
| Point subpixel = TextFrame::ComputeSubpixelPosition( |
| glyph_position, font.GetAxisAlignment(), offset_, scale_); |
| std::optional<std::pair<Rect, Rect>> maybe_atlas_glyph_bounds = |
| font_atlas->FindGlyphBounds( |
| SubpixelGlyph{glyph_position.glyph, subpixel, properties_}); |
| if (!maybe_atlas_glyph_bounds.has_value()) { |
| VALIDATION_LOG << "Could not find glyph position in the atlas."; |
| continue; |
| } |
| const Rect& atlas_glyph_bounds = |
| maybe_atlas_glyph_bounds.value().first; |
| Rect glyph_bounds = maybe_atlas_glyph_bounds.value().second; |
| // For each glyph, we compute two rectangles. One for the vertex |
| // positions and one for the texture coordinates (UVs). The atlas |
| // glyph bounds are used to compute UVs in cases where the |
| // destination and source sizes may differ due to clamping the sizes |
| // of large glyphs. |
| Point uv_origin = |
| (atlas_glyph_bounds.GetLeftTop() - Point(0.5, 0.5)) / |
| atlas_size; |
| Point uv_size = |
| (atlas_glyph_bounds.GetSize() + Point(1, 1)) / atlas_size; |
| |
| Point unrounded_glyph_position = |
| (basis_transform * glyph_position.position) + |
| glyph_bounds.GetLeftTop(); |
| Point screen_glyph_position = |
| (screen_offset + unrounded_glyph_position + subpixel_adjustment) |
| .Floor(); |
| |
| Size scaled_size = glyph_bounds.GetSize(); |
| for (const Point& point : unit_points) { |
| Point position; |
| if (is_translation_scale) { |
| position = screen_glyph_position + (point * scaled_size); |
| } else { |
| Rect scaled_bounds = glyph_bounds.Scale(1.0 / rounded_scale); |
| position = entity_transform * (glyph_position.position + |
| scaled_bounds.GetLeftTop() + |
| point * scaled_bounds.GetSize()); |
| } |
| vtx.uv = uv_origin + (uv_size * point); |
| vtx.position = position; |
| vtx_contents[i++] = vtx; |
| } |
| } |
| } |
| }); |
| |
| pass.SetVertexBuffer({ |
| .vertex_buffer = std::move(buffer_view), |
| .index_buffer = {}, |
| .vertex_count = vertex_count, |
| .index_type = IndexType::kNone, |
| }); |
| |
| return pass.Draw().ok(); |
| } |
| |
| } // namespace impeller |