[Impeller] Use scaled font to determine bounds, match Skia position rounding behavior, add subpixel X/Y/All/None positioning. (#53042)
Multiple fixes to text rendering that match skia behavior on almost all bugs I've found, except for the glyphs are still _slightly_ too fine for some CJK text. The fixes are:
1. Compute the gylph size in the typographer context, using text size * scale factor text, instead of computing smaller bounds and scaling it up. This was not accurate and as a result we would positon glyphs incorrect by multiple pixels sometimes, causing uneven rows.
2. Match Skia's rounding behavior. previously we were rounding in multiple places, Skia rounds once. This is important to prevent jumping.
3. Use 4 subpixel X positions for rendering. This is the big one that ensures the visible layout matches exactly. Adds support for Y, both, and none positioning too. I couldn't find any examples of just Y or both. Some fonts may specify that have no subpixel positioning. So we don't bother to compute it for those.
Fixes https://github.com/flutter/flutter/issues/138386 / mostly, except slightly not bold enough.
Fixes https://github.com/flutter/flutter/issues/147577 / mostly, except slightly not bold enough.
Fixes https://github.com/flutter/flutter/issues/140475
Fixes https://github.com/flutter/flutter/issues/141467
Fixes https://github.com/flutter/flutter/issues/135523
Fixes https://github.com/flutter/flutter/issues/127815
diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc
index de607be..01f5a29 100644
--- a/impeller/aiks/aiks_unittests.cc
+++ b/impeller/aiks/aiks_unittests.cc
@@ -738,6 +738,26 @@
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
+TEST_P(AiksTest, CanRenderTextFrameWithHalfScaling) {
+ Canvas canvas;
+ canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
+ canvas.Scale({0.5, 0.5, 1});
+ ASSERT_TRUE(RenderTextInCanvasSkia(
+ GetContext(), canvas, "the quick brown fox jumped over the lazy dog!.?",
+ "Roboto-Regular.ttf"));
+ ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
+}
+
+TEST_P(AiksTest, CanRenderTextFrameWithFractionScaling) {
+ Canvas canvas;
+ canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
+ canvas.Scale({2.625, 2.625, 1});
+ ASSERT_TRUE(RenderTextInCanvasSkia(
+ GetContext(), canvas, "the quick brown fox jumped over the lazy dog!.?",
+ "Roboto-Regular.ttf"));
+ ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
+}
+
TEST_P(AiksTest, CanRenderTextFrameSTB) {
Canvas canvas;
canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc
index 6643450..9f243ea 100644
--- a/impeller/aiks/canvas.cc
+++ b/impeller/aiks/canvas.cc
@@ -883,6 +883,7 @@
text_contents->SetTextFrame(text_frame);
text_contents->SetColor(paint.color);
text_contents->SetForceTextColor(paint.mask_blur_descriptor.has_value());
+ text_contents->SetOffset(position);
entity.SetTransform(GetCurrentTransform() *
Matrix::MakeTranslation(position));
diff --git a/impeller/display_list/dl_dispatcher.cc b/impeller/display_list/dl_dispatcher.cc
index f62aeab..75fabad 100644
--- a/impeller/display_list/dl_dispatcher.cc
+++ b/impeller/display_list/dl_dispatcher.cc
@@ -1249,8 +1249,8 @@
const std::shared_ptr<impeller::TextFrame>& text_frame,
SkScalar x,
SkScalar y) {
- renderer_.GetLazyGlyphAtlas()->AddTextFrame(*text_frame,
- matrix_.GetMaxBasisLengthXY());
+ renderer_.GetLazyGlyphAtlas()->AddTextFrame(
+ *text_frame, matrix_.GetMaxBasisLengthXY(), Point(x, y));
}
void TextFrameDispatcher::drawDisplayList(
diff --git a/impeller/entity/contents/text_contents.cc b/impeller/entity/contents/text_contents.cc
index eb9a1c7..d4b0d81 100644
--- a/impeller/entity/contents/text_contents.cc
+++ b/impeller/entity/contents/text_contents.cc
@@ -13,6 +13,8 @@
#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"
@@ -36,7 +38,13 @@
}
bool TextContents::CanInheritOpacity(const Entity& entity) const {
- return !frame_->MaybeHasOverlapping();
+ // 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) {
@@ -58,7 +66,7 @@
void TextContents::PopulateGlyphAtlas(
const std::shared_ptr<LazyGlyphAtlas>& lazy_glyph_atlas,
Scalar scale) {
- lazy_glyph_atlas->AddTextFrame(*frame_, scale);
+ lazy_glyph_atlas->AddTextFrame(*frame_, scale, offset_);
scale_ = scale;
}
@@ -93,13 +101,10 @@
VS::FrameInfo frame_info;
frame_info.mvp =
Entity::GetShaderTransform(entity.GetShaderClipDepth(), pass, Matrix());
- frame_info.atlas_size =
- Vector2{static_cast<Scalar>(atlas->GetTexture()->GetSize().width),
- static_cast<Scalar>(atlas->GetTexture()->GetSize().height)};
- frame_info.offset = offset_;
- frame_info.is_translation_scale =
- entity.GetTransform().IsTranslationScaleOnly();
- frame_info.entity_transform = entity.GetTransform();
+ 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));
@@ -113,7 +118,7 @@
renderer.GetTransientsBuffer().EmplaceUniform(frag_info));
SamplerDescriptor sampler_desc;
- if (frame_info.is_translation_scale) {
+ if (is_translation_scale) {
sampler_desc.min_filter = MinMagFilter::kNearest;
sampler_desc.mag_filter = MinMagFilter::kNearest;
} else {
@@ -160,7 +165,7 @@
VS::PerVertexData vtx;
VS::PerVertexData* vtx_contents =
reinterpret_cast<VS::PerVertexData*>(contents);
- size_t offset = 0u;
+ size_t i = 0u;
for (const TextRun& run : frame_->GetRuns()) {
const Font& font = run.GetFont();
Scalar rounded_scale = TextFrame::RoundScaledFontSize(
@@ -172,22 +177,72 @@
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()) {
- std::optional<Rect> maybe_atlas_glyph_bounds =
- font_atlas->FindGlyphBounds(glyph_position.glyph);
+ // 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});
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();
- vtx.atlas_glyph_bounds = Vector4(atlas_glyph_bounds.GetXYWH());
- vtx.glyph_bounds = Vector4(glyph_position.glyph.bounds.GetXYWH());
- vtx.glyph_position = glyph_position.position;
+ 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) {
- vtx.unit_position = point;
- vtx_contents[offset++] = vtx;
+ 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;
}
}
}
diff --git a/impeller/entity/contents/text_contents.h b/impeller/entity/contents/text_contents.h
index 1df495a..f076e09 100644
--- a/impeller/entity/contents/text_contents.h
+++ b/impeller/entity/contents/text_contents.h
@@ -41,6 +41,7 @@
// |Contents|
void SetInheritedOpacity(Scalar opacity) override;
+ // The offset is only used for computing the subpixel glyph position.
void SetOffset(Vector2 offset);
std::optional<Rect> GetTextFrameBounds() const;
diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc
index 3d739d0..3025eec 100644
--- a/impeller/entity/entity_unittests.cc
+++ b/impeller/entity/entity_unittests.cc
@@ -2356,26 +2356,6 @@
tiled_texture->SetInheritedOpacity(0.5);
ASSERT_EQ(tiled_texture->GetOpacityFactor(), 0.25);
- // Text contents can accept opacity if the text frames do not
- // overlap
- SkFont font = flutter::testing::CreateTestFontOfSize(30);
- auto blob = SkTextBlob::MakeFromString("A", font);
- auto frame = MakeTextFrameFromTextBlobSkia(blob);
- auto lazy_glyph_atlas =
- std::make_shared<LazyGlyphAtlas>(TypographerContextSkia::Make());
- lazy_glyph_atlas->AddTextFrame(*frame, 1.0f);
-
- auto text_contents = std::make_shared<TextContents>();
- text_contents->SetTextFrame(frame);
- text_contents->SetColor(Color::Blue().WithAlpha(0.5));
-
- ASSERT_TRUE(text_contents->CanInheritOpacity(entity));
-
- text_contents->SetInheritedOpacity(0.5);
- ASSERT_EQ(text_contents->GetColor().alpha, 0.25);
- text_contents->SetInheritedOpacity(0.5);
- ASSERT_EQ(text_contents->GetColor().alpha, 0.25);
-
// Clips and restores trivially accept opacity.
ASSERT_TRUE(ClipContents().CanInheritOpacity(entity));
ASSERT_TRUE(ClipRestoreContents().CanInheritOpacity(entity));
diff --git a/impeller/entity/shaders/glyph_atlas.vert b/impeller/entity/shaders/glyph_atlas.vert
index e2a7e04..75a8d2f 100644
--- a/impeller/entity/shaders/glyph_atlas.vert
+++ b/impeller/entity/shaders/glyph_atlas.vert
@@ -1,81 +1,20 @@
// 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>
#include <impeller/types.glsl>
uniform FrameInfo {
mat4 mvp;
- mat4 entity_transform;
- vec2 atlas_size;
- vec2 offset;
- float is_translation_scale;
}
frame_info;
-// XYWH.
-in vec4 atlas_glyph_bounds;
-// XYWH
-in vec4 glyph_bounds;
-
-in vec2 unit_position;
-in vec2 glyph_position;
+in vec2 uv;
+in vec2 position;
out vec2 v_uv;
-mat4 basis(mat4 m) {
- return mat4(m[0][0], m[0][1], m[0][2], 0.0, //
- m[1][0], m[1][1], m[1][2], 0.0, //
- m[2][0], m[2][1], m[2][2], 0.0, //
- 0.0, 0.0, 0.0, 1.0 //
- );
-}
-
-vec2 project(mat4 m, vec2 v) {
- float w = v.x * m[0][3] + v.y * m[1][3] + m[3][3];
- vec2 result = vec2(v.x * m[0][0] + v.y * m[1][0] + m[3][0],
- v.x * m[0][1] + v.y * m[1][1] + m[3][1]);
-
- // This is Skia's behavior, but it may be reasonable to allow UB for the w=0
- // case.
- if (w != 0) {
- w = 1 / w;
- }
- return result * w;
-}
-
void main() {
- vec2 screen_offset =
- round(project(frame_info.entity_transform, frame_info.offset));
-
- // For each glyph, we compute two rectangles. One for the vertex positions
- // and one for the texture coordinates (UVs).
- vec2 uv_origin = (atlas_glyph_bounds.xy - vec2(0.5)) / frame_info.atlas_size;
- vec2 uv_size = (atlas_glyph_bounds.zw + vec2(1)) / frame_info.atlas_size;
-
- // Rounding here prevents most jitter between glyphs in the run when
- // nearest sampling.
- mat4 basis_transform = basis(frame_info.entity_transform);
- vec2 screen_glyph_position =
- screen_offset +
- round(project(basis_transform, (glyph_position + glyph_bounds.xy)));
-
- vec4 position;
- if (frame_info.is_translation_scale == 1.0) {
- // Rouding up here prevents the bounds from becoming 1 pixel too small
- // when nearest sampling. This path breaks down for projections.
- position = vec4(
- screen_glyph_position +
- ceil(project(basis_transform, unit_position * glyph_bounds.zw)),
- 0.0, 1.0);
- } else {
- position = frame_info.entity_transform *
- vec4(frame_info.offset + glyph_position + glyph_bounds.xy +
- unit_position * glyph_bounds.zw,
- 0.0, 1.0);
- }
-
- gl_Position = frame_info.mvp * position;
- v_uv = uv_origin + unit_position * uv_size;
+ gl_Position = frame_info.mvp * vec4(position, 0, 1);
+ v_uv = uv;
}
diff --git a/impeller/tools/malioc.json b/impeller/tools/malioc.json
index 47994f7..f156b6c 100644
--- a/impeller/tools/malioc.json
+++ b/impeller/tools/malioc.json
@@ -2323,7 +2323,7 @@
"Mali-G78": {
"core": "Mali-G78",
"filename": "flutter/impeller/entity/gles/glyph_atlas.vert.gles",
- "has_uniform_computation": true,
+ "has_uniform_computation": false,
"type": "Vertex",
"variants": {
"Position": {
@@ -2334,11 +2334,11 @@
"load_store"
],
"longest_path_cycles": [
- 0.515625,
- 0.515625,
+ 0.140625,
0.140625,
0.0,
- 4.0,
+ 0.0,
+ 2.0,
0.0
],
"pipelines": [
@@ -2353,43 +2353,43 @@
"load_store"
],
"shortest_path_cycles": [
- 0.484375,
- 0.484375,
- 0.03125,
+ 0.140625,
+ 0.140625,
0.0,
- 4.0,
+ 0.0,
+ 2.0,
0.0
],
"total_bound_pipelines": [
"load_store"
],
"total_cycles": [
- 0.737500011920929,
- 0.737500011920929,
- 0.15625,
+ 0.140625,
+ 0.140625,
0.0,
- 4.0,
+ 0.0,
+ 2.0,
0.0
]
},
"stack_spill_bytes": 0,
"thread_occupancy": 100,
- "uniform_registers_used": 44,
+ "uniform_registers_used": 20,
"work_registers_used": 32
},
"Varying": {
- "fp16_arithmetic": 0,
+ "fp16_arithmetic": null,
"has_stack_spilling": false,
"performance": {
"longest_path_bound_pipelines": [
"load_store"
],
"longest_path_cycles": [
- 0.15625,
- 0.15625,
0.0,
0.0,
- 4.0,
+ 0.0,
+ 0.0,
+ 3.0,
0.0
],
"pipelines": [
@@ -2404,36 +2404,36 @@
"load_store"
],
"shortest_path_cycles": [
- 0.15625,
- 0.15625,
0.0,
0.0,
- 4.0,
+ 0.0,
+ 0.0,
+ 3.0,
0.0
],
"total_bound_pipelines": [
"load_store"
],
"total_cycles": [
- 0.15625,
- 0.15625,
0.0,
0.0,
- 4.0,
+ 0.0,
+ 0.0,
+ 3.0,
0.0
]
},
"stack_spill_bytes": 0,
"thread_occupancy": 100,
- "uniform_registers_used": 14,
- "work_registers_used": 11
+ "uniform_registers_used": 8,
+ "work_registers_used": 7
}
}
},
"Mali-T880": {
"core": "Mali-T880",
"filename": "flutter/impeller/entity/gles/glyph_atlas.vert.gles",
- "has_uniform_computation": true,
+ "has_uniform_computation": false,
"type": "Vertex",
"variants": {
"Main": {
@@ -2443,8 +2443,8 @@
"load_store"
],
"longest_path_cycles": [
- 6.929999828338623,
- 7.0,
+ 2.640000104904175,
+ 5.0,
0.0
],
"pipelines": [
@@ -2456,22 +2456,22 @@
"load_store"
],
"shortest_path_cycles": [
- 5.940000057220459,
- 7.0,
+ 2.640000104904175,
+ 5.0,
0.0
],
"total_bound_pipelines": [
- "arithmetic"
+ "load_store"
],
"total_cycles": [
- 9.0,
- 7.0,
+ 2.6666667461395264,
+ 5.0,
0.0
]
},
"thread_occupancy": 100,
- "uniform_registers_used": 11,
- "work_registers_used": 3
+ "uniform_registers_used": 5,
+ "work_registers_used": 2
}
}
}
@@ -5441,11 +5441,11 @@
"load_store"
],
"longest_path_cycles": [
- 0.5,
- 0.5,
- 0.140625,
+ 0.125,
+ 0.125,
0.0,
- 4.0,
+ 0.0,
+ 2.0,
0.0
],
"pipelines": [
@@ -5460,43 +5460,43 @@
"load_store"
],
"shortest_path_cycles": [
- 0.46875,
- 0.46875,
- 0.03125,
+ 0.125,
+ 0.125,
0.0,
- 4.0,
+ 0.0,
+ 2.0,
0.0
],
"total_bound_pipelines": [
"load_store"
],
"total_cycles": [
- 0.71875,
- 0.71875,
- 0.15625,
+ 0.125,
+ 0.125,
0.0,
- 4.0,
+ 0.0,
+ 2.0,
0.0
]
},
"stack_spill_bytes": 0,
"thread_occupancy": 100,
- "uniform_registers_used": 52,
+ "uniform_registers_used": 28,
"work_registers_used": 32
},
"Varying": {
- "fp16_arithmetic": 0,
+ "fp16_arithmetic": null,
"has_stack_spilling": false,
"performance": {
"longest_path_bound_pipelines": [
"load_store"
],
"longest_path_cycles": [
- 0.15625,
- 0.15625,
0.0,
0.0,
- 4.0,
+ 0.0,
+ 0.0,
+ 3.0,
0.0
],
"pipelines": [
@@ -5511,29 +5511,29 @@
"load_store"
],
"shortest_path_cycles": [
- 0.15625,
- 0.15625,
0.0,
0.0,
- 4.0,
+ 0.0,
+ 0.0,
+ 3.0,
0.0
],
"total_bound_pipelines": [
"load_store"
],
"total_cycles": [
- 0.15625,
- 0.15625,
0.0,
0.0,
- 4.0,
+ 0.0,
+ 0.0,
+ 3.0,
0.0
]
},
"stack_spill_bytes": 0,
"thread_occupancy": 100,
- "uniform_registers_used": 44,
- "work_registers_used": 11
+ "uniform_registers_used": 20,
+ "work_registers_used": 7
}
}
}
diff --git a/impeller/typographer/backends/skia/text_frame_skia.cc b/impeller/typographer/backends/skia/text_frame_skia.cc
index 5167cdd..cd79684 100644
--- a/impeller/typographer/backends/skia/text_frame_skia.cc
+++ b/impeller/typographer/backends/skia/text_frame_skia.cc
@@ -9,14 +9,36 @@
#include "display_list/dl_color.h"
#include "flutter/fml/logging.h"
#include "impeller/typographer/backends/skia/typeface_skia.h"
-#include "include/core/SkRect.h"
+#include "impeller/typographer/font.h"
+#include "impeller/typographer/glyph.h"
#include "third_party/skia/include/core/SkFont.h"
#include "third_party/skia/include/core/SkFontMetrics.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/core/SkRect.h"
#include "third_party/skia/src/core/SkStrikeSpec.h" // nogncheck
#include "third_party/skia/src/core/SkTextBlobPriv.h" // nogncheck
namespace impeller {
+/// @brief Convert a Skia axis alignment into an Impeller alignment.
+///
+/// This does not include a case for AxisAlignment::kNone, that should
+/// be used if SkFont::isSubpixel is false.
+static AxisAlignment ToAxisAligment(SkAxisAlignment aligment) {
+ switch (aligment) {
+ case SkAxisAlignment::kNone:
+ // Skia calls this case none, meaning alignment in both X and Y.
+ // Impeller will call it "all" since that is less confusing. "none"
+ // is reserved for no subpixel alignment.
+ return AxisAlignment::kAll;
+ case SkAxisAlignment::kX:
+ return AxisAlignment::kX;
+ case SkAxisAlignment::kY:
+ return AxisAlignment::kY;
+ }
+ FML_UNREACHABLE();
+}
+
static Color ToColor(const flutter::DlColor& color) {
return {
static_cast<Scalar>(color.getRedF()), //
@@ -26,7 +48,7 @@
};
}
-static Font ToFont(const SkTextBlobRunIterator& run) {
+static Font ToFont(const SkTextBlobRunIterator& run, AxisAlignment alignment) {
auto& font = run.font();
auto typeface = std::make_shared<TypefaceSkia>(font.refTypeface());
@@ -39,15 +61,13 @@
metrics.skewX = font.getSkewX();
metrics.scaleX = font.getScaleX();
- return Font{std::move(typeface), metrics};
+ return Font{std::move(typeface), metrics, alignment};
}
static Rect ToRect(const SkRect& rect) {
return Rect::MakeLTRB(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
}
-static constexpr Scalar kScaleSize = 64.0f;
-
std::shared_ptr<TextFrame> MakeTextFrameFromTextBlobSkia(
const sk_sp<SkTextBlob>& blob,
flutter::DlColor dl_color) {
@@ -59,40 +79,33 @@
// https://github.com/flutter/flutter/issues/112005
SkStrikeSpec strikeSpec = SkStrikeSpec::MakeWithNoDevice(run.font());
SkBulkGlyphMetricsAndPaths paths{strikeSpec};
+ AxisAlignment alignment = AxisAlignment::kNone;
+ if (run.font().isSubpixel()) {
+ alignment = ToAxisAligment(
+ strikeSpec.createScalerContext()->computeAxisAlignmentForHText());
+ }
const auto glyph_count = run.glyphCount();
const auto* glyphs = run.glyphs();
switch (run.positioning()) {
case SkTextBlobRunIterator::kFull_Positioning: {
- std::vector<SkRect> glyph_bounds;
- glyph_bounds.resize(glyph_count);
- SkFont font = run.font();
- auto font_size = font.getSize();
- // For some platforms (including Android), `SkFont::getBounds()` snaps
- // the computed bounds to integers. And so we scale up the font size
- // prior to fetching the bounds to ensure that the returned bounds are
- // always precise enough. Scaling too large will cause Skia to use
- // path rendering and potentially inaccurate glyph sizes.
- font.setSize(kScaleSize);
- font.getBounds(glyphs, glyph_count, glyph_bounds.data(), nullptr);
-
std::vector<TextRun::GlyphPosition> positions;
positions.reserve(glyph_count);
for (auto i = 0u; i < glyph_count; i++) {
// kFull_Positioning has two scalars per glyph.
const SkPoint* glyph_points = run.points();
- const auto* point = glyph_points + i;
+ const SkPoint* point = glyph_points + i;
Glyph::Type type = paths.glyph(glyphs[i])->isColor()
? Glyph::Type::kBitmap
: Glyph::Type::kPath;
has_color |= type == Glyph::Type::kBitmap;
-
- positions.emplace_back(TextRun::GlyphPosition{
- Glyph{glyphs[i], type,
- ToRect(glyph_bounds[i]).Scale(font_size / kScaleSize)},
- Point{point->x(), point->y()}});
+ positions.emplace_back(
+ TextRun::GlyphPosition{Glyph{glyphs[i], type}, Point{
+ point->x(),
+ point->y(),
+ }});
}
- TextRun text_run(ToFont(run), positions);
+ TextRun text_run(ToFont(run, alignment), positions);
runs.emplace_back(text_run);
break;
}
diff --git a/impeller/typographer/backends/skia/text_frame_skia.h b/impeller/typographer/backends/skia/text_frame_skia.h
index 9586fcb..99ff93f 100644
--- a/impeller/typographer/backends/skia/text_frame_skia.h
+++ b/impeller/typographer/backends/skia/text_frame_skia.h
@@ -7,6 +7,7 @@
#include "display_list/dl_color.h"
#include "impeller/typographer/text_frame.h"
+
#include "third_party/skia/include/core/SkTextBlob.h"
namespace impeller {
diff --git a/impeller/typographer/backends/skia/typographer_context_skia.cc b/impeller/typographer/backends/skia/typographer_context_skia.cc
index 96027ea..5002c67 100644
--- a/impeller/typographer/backends/skia/typographer_context_skia.cc
+++ b/impeller/typographer/backends/skia/typographer_context_skia.cc
@@ -20,21 +20,23 @@
#include "impeller/core/host_buffer.h"
#include "impeller/core/platform.h"
#include "impeller/core/texture_descriptor.h"
+#include "impeller/geometry/rect.h"
#include "impeller/geometry/size.h"
#include "impeller/renderer/command_buffer.h"
#include "impeller/renderer/render_pass.h"
#include "impeller/renderer/render_target.h"
#include "impeller/typographer/backends/skia/typeface_skia.h"
#include "impeller/typographer/font_glyph_pair.h"
+#include "impeller/typographer/glyph.h"
#include "impeller/typographer/glyph_atlas.h"
#include "impeller/typographer/rectangle_packer.h"
#include "impeller/typographer/typographer_context.h"
#include "include/core/SkColor.h"
#include "include/core/SkImageInfo.h"
-#include "include/core/SkPixelRef.h"
#include "include/core/SkSize.h"
#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkBlendMode.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkFont.h"
#include "third_party/skia/include/core/SkSurface.h"
@@ -76,6 +78,7 @@
const std::shared_ptr<GlyphAtlas>& atlas,
const std::vector<FontGlyphPair>& extra_pairs,
std::vector<Rect>& glyph_positions,
+ const std::vector<Rect>& glyph_sizes,
ISize atlas_size,
int64_t height_adjustment,
const std::shared_ptr<RectanglePacker>& rect_packer) {
@@ -85,9 +88,7 @@
}
for (size_t i = 0; i < extra_pairs.size(); i++) {
- const FontGlyphPair& pair = extra_pairs[i];
- const auto glyph_size =
- ISize::Ceil(pair.glyph.bounds.GetSize() * pair.scaled_font.scale);
+ ISize glyph_size = ISize::Ceil(glyph_sizes[i].GetSize());
IPoint16 location_in_atlas;
if (!rect_packer->AddRect(glyph_size.width + kPadding, //
glyph_size.height + kPadding, //
@@ -95,11 +96,12 @@
)) {
return i;
}
+ // Position the glyph in the center of the 1px padding.
glyph_positions.push_back(Rect::MakeXYWH(
- location_in_atlas.x(), //
- location_in_atlas.y() + height_adjustment, //
- glyph_size.width, //
- glyph_size.height //
+ location_in_atlas.x() + 1, //
+ location_in_atlas.y() + height_adjustment + 1, //
+ glyph_size.width, //
+ glyph_size.height //
));
}
@@ -110,15 +112,14 @@
const std::vector<FontGlyphPair>& pairs,
const ISize& atlas_size,
std::vector<Rect>& glyph_positions,
+ const std::vector<Rect>& glyph_sizes,
int64_t height_adjustment,
const std::shared_ptr<RectanglePacker>& rect_packer,
size_t start_index) {
FML_DCHECK(!atlas_size.IsEmpty());
for (size_t i = start_index; i < pairs.size(); i++) {
- const auto& pair = pairs[i];
- const auto glyph_size =
- ISize::Ceil(pair.glyph.bounds.GetSize() * pair.scaled_font.scale);
+ ISize glyph_size = ISize::Ceil(glyph_sizes[i].GetSize());
IPoint16 location_in_atlas;
if (!rect_packer->AddRect(glyph_size.width + kPadding, //
glyph_size.height + kPadding, //
@@ -127,10 +128,10 @@
return i;
}
glyph_positions.push_back(Rect::MakeXYWH(
- location_in_atlas.x(), //
- location_in_atlas.y() + height_adjustment, //
- glyph_size.width, //
- glyph_size.height //
+ location_in_atlas.x() + 1, //
+ location_in_atlas.y() + height_adjustment + 1, //
+ glyph_size.width, //
+ glyph_size.height //
));
}
@@ -141,6 +142,7 @@
const std::shared_ptr<GlyphAtlasContext>& atlas_context,
const std::vector<FontGlyphPair>& extra_pairs,
std::vector<Rect>& glyph_positions,
+ const std::vector<Rect>& glyph_sizes,
size_t glyph_index_start,
int64_t max_texture_height) {
// Because we can't grow the skyline packer horizontally, pick a reasonable
@@ -166,9 +168,9 @@
glyph_positions.erase(glyph_positions.begin() + glyph_index_start,
glyph_positions.end());
atlas_context->UpdateRectPacker(rect_packer);
- auto next_index = PairsFitInAtlasOfSize(extra_pairs, current_size,
- glyph_positions, height_adjustment,
- rect_packer, glyph_index_start);
+ auto next_index = PairsFitInAtlasOfSize(
+ extra_pairs, current_size, glyph_positions, glyph_sizes,
+ height_adjustment, rect_packer, glyph_index_start);
if (next_index == extra_pairs.size()) {
return current_size;
}
@@ -179,11 +181,12 @@
static void DrawGlyph(SkCanvas* canvas,
const ScaledFont& scaled_font,
- const Glyph& glyph,
+ const SubpixelGlyph& glyph,
+ const Rect& scaled_bounds,
bool has_color) {
const auto& metrics = scaled_font.font.GetMetrics();
- const auto position = SkPoint::Make(0, 0);
- SkGlyphID glyph_id = glyph.index;
+ SkPoint position = SkPoint::Make(1, 1);
+ SkGlyphID glyph_id = glyph.glyph.index;
SkFont sk_font(
TypefaceSkia::Cast(*scaled_font.font.GetTypeface()).GetSkiaTypeface(),
@@ -191,21 +194,22 @@
sk_font.setEdging(SkFont::Edging::kAntiAlias);
sk_font.setHinting(SkFontHinting::kSlight);
sk_font.setEmbolden(metrics.embolden);
+ sk_font.setSubpixel(true);
+ sk_font.setSize(sk_font.getSize() * scaled_font.scale);
auto glyph_color = has_color ? scaled_font.color.ToARGB() : SK_ColorBLACK;
SkPaint glyph_paint;
glyph_paint.setColor(glyph_color);
- canvas->resetMatrix();
- canvas->scale(scaled_font.scale, scaled_font.scale);
-
- canvas->drawGlyphs(
- 1u, // count
- &glyph_id, // glyphs
- &position, // positions
- SkPoint::Make(-glyph.bounds.GetLeft(), -glyph.bounds.GetTop()), // origin
- sk_font, // font
- glyph_paint // paint
+ glyph_paint.setBlendMode(SkBlendMode::kSrc);
+ canvas->translate(glyph.subpixel_offset.x, glyph.subpixel_offset.y);
+ canvas->drawGlyphs(1u, // count
+ &glyph_id, // glyphs
+ &position, // positions
+ SkPoint::Make(-scaled_bounds.GetLeft(),
+ -scaled_bounds.GetTop()), // origin
+ sk_font, // font
+ glyph_paint // paint
);
}
@@ -222,20 +226,26 @@
for (size_t i = start_index; i < end_index; i++) {
const FontGlyphPair& pair = new_pairs[i];
- auto pos = atlas.FindFontGlyphBounds(pair);
- if (!pos.has_value()) {
+ auto data = atlas.FindFontGlyphBounds(pair);
+ if (!data.has_value()) {
continue;
}
- Size size = pos->GetSize();
+ auto [pos, bounds] = data.value();
+ Size size = pos.GetSize();
if (size.IsEmpty()) {
continue;
}
+ // The uploaded bitmap is expanded by 1px of padding
+ // on each side.
+ size.width += 2;
+ size.height += 2;
SkBitmap bitmap;
bitmap.setInfo(GetImageInfo(atlas, size));
if (!bitmap.tryAllocPixels()) {
return false;
}
+
auto surface = SkSurfaces::WrapPixels(bitmap.pixmap());
if (!surface) {
return false;
@@ -245,7 +255,7 @@
return false;
}
- DrawGlyph(canvas, pair.scaled_font, pair.glyph, has_color);
+ DrawGlyph(canvas, pair.scaled_font, pair.glyph, bounds, has_color);
// Writing to a malloc'd buffer and then copying to the staging buffers
// benchmarks as substantially faster on a number of Android devices.
@@ -260,7 +270,7 @@
// on Vulkan where we are responsible for managing image layouts.
if (!blit_pass->AddCopy(std::move(buffer_view), //
texture, //
- IRect::MakeXYWH(pos->GetLeft(), pos->GetTop(),
+ IRect::MakeXYWH(pos.GetLeft() - 1, pos.GetTop() - 1,
size.width, size.height), //
/*label=*/"", //
/*slice=*/0, //
@@ -272,6 +282,20 @@
return blit_pass->ConvertTextureToShaderRead(texture);
}
+static Rect ComputeGlyphSize(const SkFont& font, const SubpixelGlyph& glyph) {
+ SkRect scaled_bounds;
+ font.getBounds(&glyph.glyph.index, 1, &scaled_bounds, nullptr);
+
+ // Expand the bounds of glyphs at subpixel offsets by 2 in the x direction.
+ Scalar adjustment = 0.0;
+ if (glyph.subpixel_offset != Point(0, 0)) {
+ adjustment = 1.0;
+ }
+ return Rect::MakeLTRB(scaled_bounds.fLeft - adjustment, scaled_bounds.fTop,
+ scaled_bounds.fRight + adjustment,
+ scaled_bounds.fBottom);
+};
+
std::shared_ptr<GlyphAtlas> TypographerContextSkia::CreateGlyphAtlas(
Context& context,
GlyphAtlas::Type type,
@@ -291,22 +315,41 @@
// ---------------------------------------------------------------------------
// Step 1: Determine if the atlas type and font glyph pairs are compatible
- // with the current atlas and reuse if possible.
+ // with the current atlas and reuse if possible. For each new font and
+ // glyph pair, compute the glyph size at scale.
// ---------------------------------------------------------------------------
+ std::vector<Rect> glyph_sizes;
std::vector<FontGlyphPair> new_glyphs;
for (const auto& font_value : font_glyph_map) {
const ScaledFont& scaled_font = font_value.first;
const FontGlyphAtlas* font_glyph_atlas = last_atlas->GetFontGlyphAtlas(
scaled_font.font, scaled_font.scale, scaled_font.color);
+
+ auto metrics = scaled_font.font.GetMetrics();
+
+ SkFont sk_font(
+ TypefaceSkia::Cast(*scaled_font.font.GetTypeface()).GetSkiaTypeface(),
+ metrics.point_size, metrics.scaleX, metrics.skewX);
+ sk_font.setEdging(SkFont::Edging::kAntiAlias);
+ sk_font.setHinting(SkFontHinting::kSlight);
+ sk_font.setEmbolden(metrics.embolden);
+ // Rather than computing the bounds at the requested point size and scaling
+ // up the bounds, we scale up the font size and request the bounds. This
+ // seems to give more accurate bounds information.
+ sk_font.setSize(sk_font.getSize() * scaled_font.scale);
+ sk_font.setSubpixel(true);
+
if (font_glyph_atlas) {
- for (const Glyph& glyph : font_value.second) {
+ for (const SubpixelGlyph& glyph : font_value.second) {
if (!font_glyph_atlas->FindGlyphBounds(glyph)) {
new_glyphs.emplace_back(scaled_font, glyph);
+ glyph_sizes.push_back(ComputeGlyphSize(sk_font, glyph));
}
}
} else {
- for (const Glyph& glyph : font_value.second) {
+ for (const SubpixelGlyph& glyph : font_value.second) {
new_glyphs.emplace_back(scaled_font, glyph);
+ glyph_sizes.push_back(ComputeGlyphSize(sk_font, glyph));
}
}
}
@@ -325,15 +368,17 @@
if (last_atlas->GetTexture()) {
// Append all glyphs that fit into the current atlas.
first_missing_index = AppendToExistingAtlas(
- last_atlas, new_glyphs, glyph_positions, atlas_context->GetAtlasSize(),
- atlas_context->GetHeightAdjustment(), atlas_context->GetRectPacker());
+ last_atlas, new_glyphs, glyph_positions, glyph_sizes,
+ atlas_context->GetAtlasSize(), atlas_context->GetHeightAdjustment(),
+ atlas_context->GetRectPacker());
// ---------------------------------------------------------------------------
// Step 3a: Record the positions in the glyph atlas of the newly added
// glyphs.
// ---------------------------------------------------------------------------
for (size_t i = 0; i < first_missing_index; i++) {
- last_atlas->AddTypefaceGlyphPosition(new_glyphs[i], glyph_positions[i]);
+ last_atlas->AddTypefaceGlyphPositionAndBounds(
+ new_glyphs[i], glyph_positions[i], glyph_sizes[i]);
}
std::shared_ptr<CommandBuffer> cmd_buffer = context.CreateCommandBuffer();
@@ -386,6 +431,7 @@
ISize atlas_size = ComputeNextAtlasSize(atlas_context, //
new_glyphs, //
glyph_positions, //
+ glyph_sizes, //
first_missing_index, //
max_texture_height //
);
@@ -456,7 +502,8 @@
// glyphs.
// ---------------------------------------------------------------------------
for (size_t i = first_missing_index; i < glyph_positions.size(); i++) {
- new_atlas->AddTypefaceGlyphPosition(new_glyphs[i], glyph_positions[i]);
+ new_atlas->AddTypefaceGlyphPositionAndBounds(
+ new_glyphs[i], glyph_positions[i], glyph_sizes[i]);
}
// ---------------------------------------------------------------------------
diff --git a/impeller/typographer/backends/stb/text_frame_stb.cc b/impeller/typographer/backends/stb/text_frame_stb.cc
index d4e3985..5ec1def 100644
--- a/impeller/typographer/backends/stb/text_frame_stb.cc
+++ b/impeller/typographer/backends/stb/text_frame_stb.cc
@@ -12,7 +12,7 @@
const std::shared_ptr<TypefaceSTB>& typeface_stb,
Font::Metrics metrics,
const std::string& text) {
- TextRun run(Font(typeface_stb, metrics));
+ TextRun run(Font(typeface_stb, metrics, AxisAlignment::kNone));
// Shape the text run using STB. The glyph positions could also be resolved
// using a more advanced text shaper such as harfbuzz.
@@ -28,6 +28,8 @@
descent = std::round(descent * scale);
float x = 0;
+ std::vector<Rect> bounds;
+ bounds.resize(text.size());
for (size_t i = 0; i < text.size(); i++) {
int glyph_index =
stbtt_FindGlyphIndex(typeface_stb->GetFontInfo(), text[i]);
@@ -42,8 +44,8 @@
stbtt_GetGlyphHMetrics(typeface_stb->GetFontInfo(), glyph_index,
&advance_width, &left_side_bearing);
- Glyph glyph(glyph_index, Glyph::Type::kPath,
- Rect::MakeXYWH(0, 0, x1 - x0, y1 - y0));
+ bounds.push_back(Rect::MakeXYWH(0, 0, x1 - x0, y1 - y0));
+ Glyph glyph(glyph_index, Glyph::Type::kPath);
run.AddGlyph(glyph, {x + (left_side_bearing * scale), y});
if (i + 1 < text.size()) {
@@ -54,10 +56,10 @@
}
std::optional<Rect> result;
- for (const auto& glyph_position : run.GetGlyphPositions()) {
- Rect glyph_rect = Rect::MakeOriginSize(
- glyph_position.position + glyph_position.glyph.bounds.GetOrigin(),
- glyph_position.glyph.bounds.GetSize());
+ for (auto i = 0u; i < bounds.size(); i++) {
+ const TextRun::GlyphPosition& gp = run.GetGlyphPositions()[i];
+ Rect glyph_rect = Rect::MakeOriginSize(gp.position + bounds[i].GetOrigin(),
+ bounds[i].GetSize());
result = result.has_value() ? result->Union(glyph_rect) : glyph_rect;
}
diff --git a/impeller/typographer/backends/stb/typographer_context_stb.cc b/impeller/typographer/backends/stb/typographer_context_stb.cc
index c8d6aff..75f5e2c 100644
--- a/impeller/typographer/backends/stb/typographer_context_stb.cc
+++ b/impeller/typographer/backends/stb/typographer_context_stb.cc
@@ -75,8 +75,9 @@
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
float scale = stbtt_ScaleForMappingEmToPixels(typeface_stb->GetFontInfo(),
text_size_pixels);
- stbtt_GetGlyphBitmapBox(typeface_stb->GetFontInfo(), pair.glyph.index,
- scale, scale, &x0, &y0, &x1, &y1);
+ stbtt_GetGlyphBitmapBox(typeface_stb->GetFontInfo(),
+ pair.glyph.glyph.index, scale, scale, &x0, &y0,
+ &x1, &y1);
glyph_size = ISize(x1 - x0, y1 - y0);
}
@@ -132,8 +133,9 @@
float scale_y = stbtt_ScaleForMappingEmToPixels(
typeface_stb->GetFontInfo(), text_size_pixels);
float scale_x = scale_y;
- stbtt_GetGlyphBitmapBox(typeface_stb->GetFontInfo(), pair.glyph.index,
- scale_x, scale_y, &x0, &y0, &x1, &y1);
+ stbtt_GetGlyphBitmapBox(typeface_stb->GetFontInfo(),
+ pair.glyph.glyph.index, scale_x, scale_y, &x0,
+ &y0, &x1, &y1);
glyph_size = ISize(x1 - x0, y1 - y0);
}
@@ -280,8 +282,8 @@
if (!pos.has_value()) {
continue;
}
- DrawGlyph(bitmap.get(), pair.scaled_font, pair.glyph, pos.value(),
- has_color);
+ DrawGlyph(bitmap.get(), pair.scaled_font, pair.glyph.glyph,
+ pos.value().first, has_color);
}
return true;
}
@@ -301,9 +303,9 @@
bool has_color = atlas.GetType() == GlyphAtlas::Type::kColorBitmap;
atlas.IterateGlyphs([&bitmap, has_color](const ScaledFont& scaled_font,
- const Glyph& glyph,
+ const SubpixelGlyph& glyph,
const Rect& location) -> bool {
- DrawGlyph(bitmap.get(), scaled_font, glyph, location, has_color);
+ DrawGlyph(bitmap.get(), scaled_font, glyph.glyph, location, has_color);
return true;
});
@@ -381,6 +383,19 @@
return texture;
}
+static Rect ComputeGlyphSize(const ScaledFont& font,
+ const SubpixelGlyph& glyph) {
+ std::shared_ptr<TypefaceSTB> typeface_stb =
+ std::reinterpret_pointer_cast<TypefaceSTB>(font.font.GetTypeface());
+ float scale = stbtt_ScaleForMappingEmToPixels(
+ typeface_stb->GetFontInfo(),
+ font.font.GetMetrics().point_size * TypefaceSTB::kPointsToPixels);
+ int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
+ stbtt_GetGlyphBitmapBox(typeface_stb->GetFontInfo(), glyph.glyph.index, scale,
+ scale, &x0, &y0, &x1, &y1);
+ return Rect::MakeLTRB(0, 0, x1 - x0, y1 - y0);
+}
+
std::shared_ptr<GlyphAtlas> TypographerContextSTB::CreateGlyphAtlas(
Context& context,
GlyphAtlas::Type type,
@@ -410,19 +425,22 @@
// with the current atlas and reuse if possible.
// ---------------------------------------------------------------------------
std::vector<FontGlyphPair> new_glyphs;
+ std::vector<Rect> new_sizes;
for (const auto& font_value : font_glyph_map) {
const ScaledFont& scaled_font = font_value.first;
const FontGlyphAtlas* font_glyph_atlas = last_atlas->GetFontGlyphAtlas(
scaled_font.font, scaled_font.scale, scaled_font.color);
if (font_glyph_atlas) {
- for (const Glyph& glyph : font_value.second) {
+ for (const SubpixelGlyph& glyph : font_value.second) {
if (!font_glyph_atlas->FindGlyphBounds(glyph)) {
new_glyphs.emplace_back(scaled_font, glyph);
+ new_sizes.push_back(ComputeGlyphSize(scaled_font, glyph));
}
}
} else {
- for (const Glyph& glyph : font_value.second) {
+ for (const SubpixelGlyph& glyph : font_value.second) {
new_glyphs.emplace_back(scaled_font, glyph);
+ new_sizes.push_back(ComputeGlyphSize(scaled_font, glyph));
}
}
}
@@ -448,7 +466,8 @@
// glyphs.
// ---------------------------------------------------------------------------
for (size_t i = 0, count = glyph_positions.size(); i < count; i++) {
- last_atlas->AddTypefaceGlyphPosition(new_glyphs[i], glyph_positions[i]);
+ last_atlas->AddTypefaceGlyphPositionAndBounds(
+ new_glyphs[i], glyph_positions[i], new_sizes[i]);
}
// ---------------------------------------------------------------------------
@@ -480,7 +499,7 @@
[](const int a, const auto& b) { return a + b.second.size(); }));
for (const auto& font_value : font_glyph_map) {
const ScaledFont& scaled_font = font_value.first;
- for (const Glyph& glyph : font_value.second) {
+ for (const SubpixelGlyph& glyph : font_value.second) {
font_glyph_pairs.push_back({scaled_font, glyph});
}
}
@@ -515,7 +534,8 @@
size_t i = 0;
for (auto it = font_glyph_pairs.begin(); it != font_glyph_pairs.end();
++i, ++it) {
- glyph_atlas->AddTypefaceGlyphPosition(*it, glyph_positions[i]);
+ glyph_atlas->AddTypefaceGlyphPositionAndBounds(*it, glyph_positions[i],
+ new_sizes[i]);
}
}
diff --git a/impeller/typographer/font.cc b/impeller/typographer/font.cc
index 75ba05f..06aad9a 100644
--- a/impeller/typographer/font.cc
+++ b/impeller/typographer/font.cc
@@ -6,8 +6,12 @@
namespace impeller {
-Font::Font(std::shared_ptr<Typeface> typeface, Metrics metrics)
- : typeface_(std::move(typeface)), metrics_(metrics) {
+Font::Font(std::shared_ptr<Typeface> typeface,
+ Metrics metrics,
+ AxisAlignment axis_alignment)
+ : typeface_(std::move(typeface)),
+ metrics_(metrics),
+ axis_alignment_(axis_alignment) {
if (!typeface_) {
return;
}
@@ -34,6 +38,10 @@
is_valid_ == other.is_valid_ && metrics_ == other.metrics_;
}
+AxisAlignment Font::GetAxisAlignment() const {
+ return axis_alignment_;
+}
+
const Font::Metrics& Font::GetMetrics() const {
return metrics_;
}
diff --git a/impeller/typographer/font.h b/impeller/typographer/font.h
index 9140d75..a48f882 100644
--- a/impeller/typographer/font.h
+++ b/impeller/typographer/font.h
@@ -9,12 +9,26 @@
#include "fml/hash_combine.h"
#include "impeller/base/comparable.h"
-#include "impeller/typographer/glyph.h"
+#include "impeller/geometry/scalar.h"
#include "impeller/typographer/typeface.h"
namespace impeller {
//------------------------------------------------------------------------------
+/// @brief Determines the axis along which there is subpixel positioning.
+///
+enum class AxisAlignment : uint8_t {
+ // No subpixel positioning.
+ kNone,
+ // Subpixel positioning in the X axis only.
+ kX,
+ // Subpixel positioning in the Y axis only.
+ kY,
+ // No specific axis, subpixel positioning in each direction.
+ kAll,
+};
+
+//------------------------------------------------------------------------------
/// @brief Describes a typeface along with any modifications to its
/// intrinsic properties.
///
@@ -42,7 +56,9 @@
}
};
- Font(std::shared_ptr<Typeface> typeface, Metrics metrics);
+ Font(std::shared_ptr<Typeface> typeface,
+ Metrics metrics,
+ AxisAlignment axis_alignment);
~Font();
@@ -63,9 +79,12 @@
// |Comparable<Font>|
bool IsEqual(const Font& other) const override;
+ AxisAlignment GetAxisAlignment() const;
+
private:
std::shared_ptr<Typeface> typeface_;
Metrics metrics_ = {};
+ AxisAlignment axis_alignment_;
bool is_valid_ = false;
};
diff --git a/impeller/typographer/font_glyph_pair.h b/impeller/typographer/font_glyph_pair.h
index 01767fb..2e79ade 100644
--- a/impeller/typographer/font_glyph_pair.h
+++ b/impeller/typographer/font_glyph_pair.h
@@ -8,6 +8,8 @@
#include <unordered_map>
#include <unordered_set>
+#include "impeller/geometry/color.h"
+#include "impeller/geometry/point.h"
#include "impeller/typographer/font.h"
#include "impeller/typographer/glyph.h"
@@ -23,17 +25,29 @@
Color color;
};
-using FontGlyphMap = std::unordered_map<ScaledFont, std::unordered_set<Glyph>>;
+//------------------------------------------------------------------------------
+/// @brief A glyph and its subpixel position.
+///
+struct SubpixelGlyph {
+ Glyph glyph;
+ Point subpixel_offset;
+
+ SubpixelGlyph(Glyph p_glyph, Point p_subpixel_offset)
+ : glyph(p_glyph), subpixel_offset(p_subpixel_offset) {}
+};
+
+using FontGlyphMap =
+ std::unordered_map<ScaledFont, std::unordered_set<SubpixelGlyph>>;
//------------------------------------------------------------------------------
/// @brief A font along with a glyph in that font rendered at a particular
-/// scale.
+/// scale and subpixel position.
///
struct FontGlyphPair {
- FontGlyphPair(const ScaledFont& sf, const Glyph& g)
+ FontGlyphPair(const ScaledFont& sf, const SubpixelGlyph& g)
: scaled_font(sf), glyph(g) {}
const ScaledFont& scaled_font;
- const Glyph& glyph;
+ const SubpixelGlyph& glyph;
};
} // namespace impeller
@@ -54,4 +68,22 @@
}
};
+template <>
+struct std::hash<impeller::SubpixelGlyph> {
+ constexpr std::size_t operator()(const impeller::SubpixelGlyph& sg) const {
+ return fml::HashCombine(sg.glyph.index, sg.subpixel_offset.x,
+ sg.subpixel_offset.y);
+ }
+};
+
+template <>
+struct std::equal_to<impeller::SubpixelGlyph> {
+ constexpr bool operator()(const impeller::SubpixelGlyph& lhs,
+ const impeller::SubpixelGlyph& rhs) const {
+ return lhs.glyph.index == rhs.glyph.index &&
+ lhs.glyph.type == rhs.glyph.type &&
+ lhs.subpixel_offset == rhs.subpixel_offset;
+ }
+};
+
#endif // FLUTTER_IMPELLER_TYPOGRAPHER_FONT_GLYPH_PAIR_H_
diff --git a/impeller/typographer/glyph.h b/impeller/typographer/glyph.h
index 6736f30..3a1b813 100644
--- a/impeller/typographer/glyph.h
+++ b/impeller/typographer/glyph.h
@@ -8,8 +8,6 @@
#include <cstdint>
#include <functional>
-#include "impeller/geometry/rect.h"
-
namespace impeller {
//------------------------------------------------------------------------------
@@ -28,19 +26,12 @@
///
Type type = Type::kPath;
- //------------------------------------------------------------------------------
- /// @brief Visibility coverage of the glyph in text run space (relative to
- /// the baseline, no scaling applied).
- ///
- Rect bounds;
-
- Glyph(uint16_t p_index, Type p_type, Rect p_bounds)
- : index(p_index), type(p_type), bounds(p_bounds) {}
+ Glyph(uint16_t p_index, Type p_type) : index(p_index), type(p_type) {}
};
// Many Glyph instances are instantiated, so care should be taken when
// increasing the size.
-static_assert(sizeof(Glyph) == 20);
+static_assert(sizeof(Glyph) == 4);
} // namespace impeller
diff --git a/impeller/typographer/glyph_atlas.cc b/impeller/typographer/glyph_atlas.cc
index 6e9b46a..59a3f32 100644
--- a/impeller/typographer/glyph_atlas.cc
+++ b/impeller/typographer/glyph_atlas.cc
@@ -63,12 +63,14 @@
texture_ = std::move(texture);
}
-void GlyphAtlas::AddTypefaceGlyphPosition(const FontGlyphPair& pair,
- Rect rect) {
- font_atlas_map_[pair.scaled_font].positions_[pair.glyph] = rect;
+void GlyphAtlas::AddTypefaceGlyphPositionAndBounds(const FontGlyphPair& pair,
+ Rect position,
+ Rect bounds) {
+ font_atlas_map_[pair.scaled_font].positions_[pair.glyph] =
+ std::make_pair(position, bounds);
}
-std::optional<Rect> GlyphAtlas::FindFontGlyphBounds(
+std::optional<std::pair<Rect, Rect>> GlyphAtlas::FindFontGlyphBounds(
const FontGlyphPair& pair) const {
const auto& found = font_atlas_map_.find(pair.scaled_font);
if (found == font_atlas_map_.end()) {
@@ -96,7 +98,7 @@
size_t GlyphAtlas::IterateGlyphs(
const std::function<bool(const ScaledFont& scaled_font,
- const Glyph& glyph,
+ const SubpixelGlyph& glyph,
const Rect& rect)>& iterator) const {
if (!iterator) {
return 0u;
@@ -106,7 +108,8 @@
for (const auto& font_value : font_atlas_map_) {
for (const auto& glyph_value : font_value.second.positions_) {
count++;
- if (!iterator(font_value.first, glyph_value.first, glyph_value.second)) {
+ if (!iterator(font_value.first, glyph_value.first,
+ glyph_value.second.first)) {
return count;
}
}
@@ -114,7 +117,8 @@
return count;
}
-std::optional<Rect> FontGlyphAtlas::FindGlyphBounds(const Glyph& glyph) const {
+std::optional<std::pair<Rect, Rect>> FontGlyphAtlas::FindGlyphBounds(
+ const SubpixelGlyph& glyph) const {
const auto& found = positions_.find(glyph);
if (found == positions_.end()) {
return std::nullopt;
diff --git a/impeller/typographer/glyph_atlas.h b/impeller/typographer/glyph_atlas.h
index 605e70d..98a6556 100644
--- a/impeller/typographer/glyph_atlas.h
+++ b/impeller/typographer/glyph_atlas.h
@@ -79,9 +79,12 @@
/// atlas.
///
/// @param[in] pair The font-glyph pair
- /// @param[in] rect The rectangle
+ /// @param[in] rect The position in the atlas
+ /// @param[in] bounds The bounds of the glyph at scale
///
- void AddTypefaceGlyphPosition(const FontGlyphPair& pair, Rect rect);
+ void AddTypefaceGlyphPositionAndBounds(const FontGlyphPair& pair,
+ Rect position,
+ Rect bounds);
//----------------------------------------------------------------------------
/// @brief Get the number of unique font-glyph pairs in this atlas.
@@ -101,7 +104,7 @@
///
size_t IterateGlyphs(
const std::function<bool(const ScaledFont& scaled_font,
- const Glyph& glyph,
+ const SubpixelGlyph& glyph,
const Rect& rect)>& iterator) const;
//----------------------------------------------------------------------------
@@ -112,7 +115,8 @@
/// @return The location of the font-glyph pair in the atlas.
/// `std::nullopt` if the pair is not in the atlas.
///
- std::optional<Rect> FindFontGlyphBounds(const FontGlyphPair& pair) const;
+ std::optional<std::pair<Rect, Rect>> FindFontGlyphBounds(
+ const FontGlyphPair& pair) const;
//----------------------------------------------------------------------------
/// @brief Obtain an interface for querying the location of glyphs in the
@@ -207,11 +211,12 @@
/// @return The location of the glyph in the atlas.
/// `std::nullopt` if the glyph is not in the atlas.
///
- std::optional<Rect> FindGlyphBounds(const Glyph& glyph) const;
+ std::optional<std::pair<Rect, Rect>> FindGlyphBounds(
+ const SubpixelGlyph& glyph) const;
private:
friend class GlyphAtlas;
- std::unordered_map<Glyph, Rect> positions_;
+ std::unordered_map<SubpixelGlyph, std::pair<Rect, Rect>> positions_;
FontGlyphAtlas(const FontGlyphAtlas&) = delete;
diff --git a/impeller/typographer/lazy_glyph_atlas.cc b/impeller/typographer/lazy_glyph_atlas.cc
index 839625b..5e6c164 100644
--- a/impeller/typographer/lazy_glyph_atlas.cc
+++ b/impeller/typographer/lazy_glyph_atlas.cc
@@ -30,12 +30,14 @@
LazyGlyphAtlas::~LazyGlyphAtlas() = default;
-void LazyGlyphAtlas::AddTextFrame(const TextFrame& frame, Scalar scale) {
+void LazyGlyphAtlas::AddTextFrame(const TextFrame& frame,
+ Scalar scale,
+ Point offset) {
FML_DCHECK(alpha_atlas_ == nullptr && color_atlas_ == nullptr);
if (frame.GetAtlasType() == GlyphAtlas::Type::kAlphaBitmap) {
- frame.CollectUniqueFontGlyphPairs(alpha_glyph_map_, scale);
+ frame.CollectUniqueFontGlyphPairs(alpha_glyph_map_, scale, offset);
} else {
- frame.CollectUniqueFontGlyphPairs(color_glyph_map_, scale);
+ frame.CollectUniqueFontGlyphPairs(color_glyph_map_, scale, offset);
}
}
diff --git a/impeller/typographer/lazy_glyph_atlas.h b/impeller/typographer/lazy_glyph_atlas.h
index 921e914..6fa2e54 100644
--- a/impeller/typographer/lazy_glyph_atlas.h
+++ b/impeller/typographer/lazy_glyph_atlas.h
@@ -19,7 +19,7 @@
~LazyGlyphAtlas();
- void AddTextFrame(const TextFrame& frame, Scalar scale);
+ void AddTextFrame(const TextFrame& frame, Scalar scale, Point offset);
void ResetTextFrames();
diff --git a/impeller/typographer/text_frame.cc b/impeller/typographer/text_frame.cc
index e597e54..a209647 100644
--- a/impeller/typographer/text_frame.cc
+++ b/impeller/typographer/text_frame.cc
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "impeller/typographer/text_frame.h"
+#include "impeller/typographer/font.h"
#include "impeller/typographer/font_glyph_pair.h"
namespace impeller {
@@ -41,38 +42,6 @@
return color_;
}
-bool TextFrame::MaybeHasOverlapping() const {
- if (runs_.size() > 1) {
- return true;
- }
- auto glyph_positions = runs_[0].GetGlyphPositions();
- if (glyph_positions.size() > 10) {
- return true;
- }
- if (glyph_positions.size() == 1) {
- return false;
- }
- // To avoid quadradic behavior the overlapping is checked against an
- // accumulated bounds rect. This gives faster but less precise information
- // on text runs.
- auto first_position = glyph_positions[0];
- auto overlapping_rect = Rect::MakeOriginSize(
- first_position.position + first_position.glyph.bounds.GetOrigin(),
- first_position.glyph.bounds.GetSize());
- for (auto i = 1u; i < glyph_positions.size(); i++) {
- auto glyph_position = glyph_positions[i];
- auto glyph_rect = Rect::MakeOriginSize(
- glyph_position.position + glyph_position.glyph.bounds.GetOrigin(),
- glyph_position.glyph.bounds.GetSize());
- auto intersection = glyph_rect.Intersection(overlapping_rect);
- if (intersection.has_value()) {
- return true;
- }
- overlapping_rect = overlapping_rect.Union(glyph_rect);
- }
- return false;
-}
-
// static
Scalar TextFrame::RoundScaledFontSize(Scalar scale, Scalar point_size) {
// An arbitrarily chosen maximum text scale to ensure that regardless of the
@@ -83,8 +52,47 @@
return std::clamp(result, 0.0f, kMaximumTextScale);
}
+static constexpr Scalar ComputeFractionalPosition(Scalar value) {
+ value += 0.125;
+ value = (value - floorf(value));
+ if (value < 0.25) {
+ return 0;
+ }
+ if (value < 0.5) {
+ return 0.25;
+ }
+ if (value < 0.75) {
+ return 0.5;
+ }
+ return 0.75;
+}
+
+// Compute subpixel position for glyphs based on X position and provided
+// max basis length (scale).
+// This logic is based on the SkPackedGlyphID logic in SkGlyph.h
+// static
+Point TextFrame::ComputeSubpixelPosition(
+ const TextRun::GlyphPosition& glyph_position,
+ AxisAlignment alignment,
+ Point offset,
+ Scalar scale) {
+ Point pos = glyph_position.position + offset;
+ switch (alignment) {
+ case AxisAlignment::kNone:
+ return Point(0, 0);
+ case AxisAlignment::kX:
+ return Point(ComputeFractionalPosition(pos.x * scale), 0);
+ case AxisAlignment::kY:
+ return Point(0, ComputeFractionalPosition(pos.y * scale));
+ case AxisAlignment::kAll:
+ return Point(ComputeFractionalPosition(pos.x * scale),
+ ComputeFractionalPosition(pos.y * scale));
+ }
+}
+
void TextFrame::CollectUniqueFontGlyphPairs(FontGlyphMap& glyph_map,
- Scalar scale) const {
+ Scalar scale,
+ Point offset) const {
for (const TextRun& run : GetRuns()) {
const Font& font = run.GetFont();
auto rounded_scale =
@@ -92,14 +100,9 @@
auto& set = glyph_map[ScaledFont{font, rounded_scale, color_}];
for (const TextRun::GlyphPosition& glyph_position :
run.GetGlyphPositions()) {
-#if false
-// Glyph size error due to RoundScaledFontSize usage above.
-if (rounded_scale != scale) {
- auto delta = std::abs(rounded_scale - scale);
- FML_LOG(ERROR) << glyph_position.glyph.bounds.size * delta;
-}
-#endif
- set.insert(glyph_position.glyph);
+ Point subpixel = ComputeSubpixelPosition(
+ glyph_position, font.GetAxisAlignment(), offset, scale);
+ set.emplace(glyph_position.glyph, subpixel);
}
}
}
diff --git a/impeller/typographer/text_frame.h b/impeller/typographer/text_frame.h
index 97cf52b..7a9b890 100644
--- a/impeller/typographer/text_frame.h
+++ b/impeller/typographer/text_frame.h
@@ -27,7 +27,15 @@
~TextFrame();
- void CollectUniqueFontGlyphPairs(FontGlyphMap& glyph_map, Scalar scale) const;
+ void CollectUniqueFontGlyphPairs(FontGlyphMap& glyph_map,
+ Scalar scale,
+ Point offset) const;
+
+ static Point ComputeSubpixelPosition(
+ const TextRun::GlyphPosition& glyph_position,
+ AxisAlignment alignment,
+ Point offset,
+ Scalar scale);
static Scalar RoundScaledFontSize(Scalar scale, Scalar point_size);
@@ -54,16 +62,6 @@
const std::vector<TextRun>& GetRuns() const;
//----------------------------------------------------------------------------
- /// @brief Whether any of the glyphs of this run are potentially
- /// overlapping
- ///
- /// It is always safe to return true from this method. Generally,
- /// any large blobs of text should return true to avoid
- /// computationally complex calculations. This information is used
- /// to apply opacity peephole optimizations to text blobs.
- bool MaybeHasOverlapping() const;
-
- //----------------------------------------------------------------------------
/// @brief Returns the paint color this text frame was recorded with.
///
/// Non-bitmap/COLR fonts always use a black text color here, but
diff --git a/impeller/typographer/typographer_unittests.cc b/impeller/typographer/typographer_unittests.cc
index 37f53c4..a2d1813 100644
--- a/impeller/typographer/typographer_unittests.cc
+++ b/impeller/typographer/typographer_unittests.cc
@@ -39,7 +39,7 @@
const std::shared_ptr<GlyphAtlasContext>& atlas_context,
const TextFrame& frame) {
FontGlyphMap font_glyph_map;
- frame.CollectUniqueFontGlyphPairs(font_glyph_map, scale);
+ frame.CollectUniqueFontGlyphPairs(font_glyph_map, scale, {0, 0});
return typographer_context->CreateGlyphAtlas(context, type, host_buffer,
atlas_context, font_glyph_map);
}
@@ -54,7 +54,7 @@
const std::vector<std::shared_ptr<TextFrame>>& frames) {
FontGlyphMap font_glyph_map;
for (auto& frame : frames) {
- frame->CollectUniqueFontGlyphPairs(font_glyph_map, scale);
+ frame->CollectUniqueFontGlyphPairs(font_glyph_map, scale, {0, 0});
}
return typographer_context->CreateGlyphAtlas(context, type, host_buffer,
atlas_context, font_glyph_map);
@@ -97,9 +97,10 @@
ASSERT_EQ(atlas->GetGlyphCount(), 4llu);
std::optional<impeller::ScaledFont> first_scaled_font;
- std::optional<impeller::Glyph> first_glyph;
+ std::optional<impeller::SubpixelGlyph> first_glyph;
Rect first_rect;
- atlas->IterateGlyphs([&](const ScaledFont& scaled_font, const Glyph& glyph,
+ atlas->IterateGlyphs([&](const ScaledFont& scaled_font,
+ const SubpixelGlyph& glyph,
const Rect& rect) -> bool {
first_scaled_font = scaled_font;
first_glyph = glyph;
@@ -134,14 +135,14 @@
LazyGlyphAtlas lazy_atlas(TypographerContextSkia::Make());
- lazy_atlas.AddTextFrame(*frame, 1.0f);
+ lazy_atlas.AddTextFrame(*frame, 1.0f, {0, 0});
frame = MakeTextFrameFromTextBlobSkia(
SkTextBlob::MakeFromString("😀 ", emoji_font));
ASSERT_TRUE(frame->GetAtlasType() == GlyphAtlas::Type::kColorBitmap);
- lazy_atlas.AddTextFrame(*frame, 1.0f);
+ lazy_atlas.AddTextFrame(*frame, 1.0f, {0, 0});
// Creates different atlases for color and red bitmap.
auto color_atlas = lazy_atlas.CreateOrGetGlyphAtlas(
@@ -221,7 +222,7 @@
size_t size_count = 8;
for (size_t index = 0; index < size_count; index += 1) {
MakeTextFrameFromTextBlobSkia(blob)->CollectUniqueFontGlyphPairs(
- font_glyph_map, 0.6 * index);
+ font_glyph_map, 0.6 * index, {0, 0});
};
auto atlas =
context->CreateGlyphAtlas(*GetContext(), GlyphAtlas::Type::kAlphaBitmap,
@@ -231,14 +232,15 @@
std::set<uint16_t> unique_glyphs;
std::vector<uint16_t> total_glyphs;
- atlas->IterateGlyphs(
- [&](const ScaledFont& scaled_font, const Glyph& glyph, const Rect& rect) {
- unique_glyphs.insert(glyph.index);
- total_glyphs.push_back(glyph.index);
- return true;
- });
+ atlas->IterateGlyphs([&](const ScaledFont& scaled_font,
+ const SubpixelGlyph& glyph, const Rect& rect) {
+ unique_glyphs.insert(glyph.glyph.index);
+ total_glyphs.push_back(glyph.glyph.index);
+ return true;
+ });
- EXPECT_EQ(unique_glyphs.size() * size_count, atlas->GetGlyphCount());
+ // These numbers may be different due to subpixel positions.
+ EXPECT_LE(unique_glyphs.size() * size_count, atlas->GetGlyphCount());
EXPECT_EQ(total_glyphs.size(), atlas->GetGlyphCount());
EXPECT_TRUE(atlas->GetGlyphCount() > 0);
@@ -283,22 +285,6 @@
ASSERT_EQ(old_packer, new_packer);
}
-TEST_P(TypographerTest, MaybeHasOverlapping) {
- sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
- sk_sp<SkTypeface> typeface =
- font_mgr->matchFamilyStyle("Arial", SkFontStyle::Normal());
- SkFont sk_font(typeface, 0.5f);
-
- auto frame =
- MakeTextFrameFromTextBlobSkia(SkTextBlob::MakeFromString("1", sk_font));
- // Single character has no overlapping
- ASSERT_FALSE(frame->MaybeHasOverlapping());
-
- auto frame_2 = MakeTextFrameFromTextBlobSkia(
- SkTextBlob::MakeFromString("123456789", sk_font));
- ASSERT_FALSE(frame_2->MaybeHasOverlapping());
-}
-
TEST_P(TypographerTest, GlyphColorIsPartOfCacheKey) {
auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator());
#if FML_OS_MACOSX
diff --git a/testing/impeller_golden_tests_output.txt b/testing/impeller_golden_tests_output.txt
index c02b816..8adb5b2 100644
--- a/testing/impeller_golden_tests_output.txt
+++ b/testing/impeller_golden_tests_output.txt
@@ -439,6 +439,12 @@
impeller_Play_AiksTest_CanRenderTextFrameSTB_Metal.png
impeller_Play_AiksTest_CanRenderTextFrameSTB_OpenGLES.png
impeller_Play_AiksTest_CanRenderTextFrameSTB_Vulkan.png
+impeller_Play_AiksTest_CanRenderTextFrameWithFractionScaling_Metal.png
+impeller_Play_AiksTest_CanRenderTextFrameWithFractionScaling_OpenGLES.png
+impeller_Play_AiksTest_CanRenderTextFrameWithFractionScaling_Vulkan.png
+impeller_Play_AiksTest_CanRenderTextFrameWithHalfScaling_Metal.png
+impeller_Play_AiksTest_CanRenderTextFrameWithHalfScaling_OpenGLES.png
+impeller_Play_AiksTest_CanRenderTextFrameWithHalfScaling_Vulkan.png
impeller_Play_AiksTest_CanRenderTextFrame_Metal.png
impeller_Play_AiksTest_CanRenderTextFrame_OpenGLES.png
impeller_Play_AiksTest_CanRenderTextFrame_Vulkan.png
diff --git a/third_party/txt/src/skia/paragraph_skia.cc b/third_party/txt/src/skia/paragraph_skia.cc
index 2c3b588..90193bd 100644
--- a/third_party/txt/src/skia/paragraph_skia.cc
+++ b/third_party/txt/src/skia/paragraph_skia.cc
@@ -92,9 +92,9 @@
builder_->DrawPath(transformed, dl_paints_[paint_id]);
return;
}
-
- builder_->DrawTextFrame(impeller::MakeTextFrameFromTextBlobSkia(blob, dl_paints_[paint_id].getColor()), x,
- y, dl_paints_[paint_id]);
+ builder_->DrawTextFrame(impeller::MakeTextFrameFromTextBlobSkia(
+ blob, dl_paints_[paint_id].getColor()),
+ x, y, dl_paints_[paint_id]);
return;
}
#endif // IMPELLER_SUPPORTS_RENDERING
@@ -116,8 +116,9 @@
paint.setMaskFilter(&filter);
}
if (impeller_enabled_) {
- builder_->DrawTextFrame(impeller::MakeTextFrameFromTextBlobSkia(blob, paint.getColor()), x,
- y, paint);
+ builder_->DrawTextFrame(
+ impeller::MakeTextFrameFromTextBlobSkia(blob, paint.getColor()), x, y,
+ paint);
return;
}
builder_->DrawTextBlob(blob, x, y, paint);