| // 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/typographer/backends/skia/typographer_context_skia.h" |
| |
| #include <numeric> |
| #include <utility> |
| |
| #include "flutter/fml/logging.h" |
| #include "flutter/fml/trace_event.h" |
| #include "impeller/base/allocation.h" |
| #include "impeller/core/allocator.h" |
| #include "impeller/typographer/backends/skia/glyph_atlas_context_skia.h" |
| #include "impeller/typographer/backends/skia/typeface_skia.h" |
| #include "impeller/typographer/rectangle_packer.h" |
| #include "impeller/typographer/typographer_context.h" |
| #include "include/core/SkColor.h" |
| #include "include/core/SkSize.h" |
| #include "third_party/skia/include/core/SkBitmap.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" |
| |
| namespace impeller { |
| |
| // TODO(bdero): We might be able to remove this per-glyph padding if we fix |
| // the underlying causes of the overlap. |
| // https://github.com/flutter/flutter/issues/114563 |
| constexpr auto kPadding = 2; |
| |
| std::shared_ptr<TypographerContext> TypographerContextSkia::Make() { |
| return std::make_shared<TypographerContextSkia>(); |
| } |
| |
| TypographerContextSkia::TypographerContextSkia() = default; |
| |
| TypographerContextSkia::~TypographerContextSkia() = default; |
| |
| std::shared_ptr<GlyphAtlasContext> |
| TypographerContextSkia::CreateGlyphAtlasContext() const { |
| return std::make_shared<GlyphAtlasContextSkia>(); |
| } |
| |
| static size_t PairsFitInAtlasOfSize( |
| const std::vector<FontGlyphPair>& pairs, |
| const ISize& atlas_size, |
| std::vector<Rect>& glyph_positions, |
| const std::shared_ptr<RectanglePacker>& rect_packer) { |
| if (atlas_size.IsEmpty()) { |
| return false; |
| } |
| |
| glyph_positions.clear(); |
| glyph_positions.reserve(pairs.size()); |
| |
| size_t i = 0; |
| for (auto it = pairs.begin(); it != pairs.end(); ++i, ++it) { |
| const auto& pair = *it; |
| |
| const auto glyph_size = |
| ISize::Ceil(pair.glyph.bounds.GetSize() * pair.scaled_font.scale); |
| IPoint16 location_in_atlas; |
| if (!rect_packer->addRect(glyph_size.width + kPadding, // |
| glyph_size.height + kPadding, // |
| &location_in_atlas // |
| )) { |
| return pairs.size() - i; |
| } |
| glyph_positions.emplace_back(Rect::MakeXYWH(location_in_atlas.x(), // |
| location_in_atlas.y(), // |
| glyph_size.width, // |
| glyph_size.height // |
| )); |
| } |
| |
| return 0; |
| } |
| |
| static bool CanAppendToExistingAtlas( |
| const std::shared_ptr<GlyphAtlas>& atlas, |
| const std::vector<FontGlyphPair>& extra_pairs, |
| std::vector<Rect>& glyph_positions, |
| ISize atlas_size, |
| const std::shared_ptr<RectanglePacker>& rect_packer) { |
| TRACE_EVENT0("impeller", __FUNCTION__); |
| if (!rect_packer || atlas_size.IsEmpty()) { |
| return false; |
| } |
| |
| // We assume that all existing glyphs will fit. After all, they fit before. |
| // The glyph_positions only contains the values for the additional glyphs |
| // from extra_pairs. |
| FML_DCHECK(glyph_positions.size() == 0); |
| glyph_positions.reserve(extra_pairs.size()); |
| 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); |
| IPoint16 location_in_atlas; |
| if (!rect_packer->addRect(glyph_size.width + kPadding, // |
| glyph_size.height + kPadding, // |
| &location_in_atlas // |
| )) { |
| return false; |
| } |
| glyph_positions.emplace_back(Rect::MakeXYWH(location_in_atlas.x(), // |
| location_in_atlas.y(), // |
| glyph_size.width, // |
| glyph_size.height // |
| )); |
| } |
| |
| return true; |
| } |
| |
| static ISize OptimumAtlasSizeForFontGlyphPairs( |
| const std::vector<FontGlyphPair>& pairs, |
| std::vector<Rect>& glyph_positions, |
| const std::shared_ptr<GlyphAtlasContext>& atlas_context, |
| GlyphAtlas::Type type, |
| const ISize& max_texture_size) { |
| static constexpr auto kMinAtlasSize = 8u; |
| static constexpr auto kMinAlphaBitmapSize = 1024u; |
| |
| TRACE_EVENT0("impeller", __FUNCTION__); |
| |
| ISize current_size = type == GlyphAtlas::Type::kAlphaBitmap |
| ? ISize(kMinAlphaBitmapSize, kMinAlphaBitmapSize) |
| : ISize(kMinAtlasSize, kMinAtlasSize); |
| size_t total_pairs = pairs.size() + 1; |
| do { |
| auto rect_packer = std::shared_ptr<RectanglePacker>( |
| RectanglePacker::Factory(current_size.width, current_size.height)); |
| |
| auto remaining_pairs = PairsFitInAtlasOfSize(pairs, current_size, |
| glyph_positions, rect_packer); |
| if (remaining_pairs == 0) { |
| atlas_context->UpdateRectPacker(rect_packer); |
| return current_size; |
| } else if (remaining_pairs < std::ceil(total_pairs / 2)) { |
| current_size = ISize::MakeWH( |
| std::max(current_size.width, current_size.height), |
| Allocation::NextPowerOfTwoSize( |
| std::min(current_size.width, current_size.height) + 1)); |
| } else { |
| current_size = ISize::MakeWH( |
| Allocation::NextPowerOfTwoSize(current_size.width + 1), |
| Allocation::NextPowerOfTwoSize(current_size.height + 1)); |
| } |
| } while (current_size.width <= max_texture_size.width && |
| current_size.height <= max_texture_size.height); |
| return ISize{0, 0}; |
| } |
| |
| static void DrawGlyph(SkCanvas* canvas, |
| const ScaledFont& scaled_font, |
| const Glyph& glyph, |
| const Rect& location, |
| bool has_color) { |
| const auto& metrics = scaled_font.font.GetMetrics(); |
| const auto position = SkPoint::Make(location.GetX() / scaled_font.scale, |
| location.GetY() / scaled_font.scale); |
| SkGlyphID glyph_id = glyph.index; |
| |
| 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); |
| |
| auto glyph_color = has_color ? SK_ColorWHITE : 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 |
| ); |
| } |
| |
| static bool UpdateAtlasBitmap(const GlyphAtlas& atlas, |
| const std::shared_ptr<SkBitmap>& bitmap, |
| const std::vector<FontGlyphPair>& new_pairs) { |
| TRACE_EVENT0("impeller", __FUNCTION__); |
| FML_DCHECK(bitmap != nullptr); |
| |
| auto surface = SkSurfaces::WrapPixels(bitmap->pixmap()); |
| if (!surface) { |
| return false; |
| } |
| auto canvas = surface->getCanvas(); |
| if (!canvas) { |
| return false; |
| } |
| |
| bool has_color = atlas.GetType() == GlyphAtlas::Type::kColorBitmap; |
| |
| for (const FontGlyphPair& pair : new_pairs) { |
| auto pos = atlas.FindFontGlyphBounds(pair); |
| if (!pos.has_value()) { |
| continue; |
| } |
| DrawGlyph(canvas, pair.scaled_font, pair.glyph, pos.value(), has_color); |
| } |
| return true; |
| } |
| |
| static std::shared_ptr<SkBitmap> CreateAtlasBitmap(const GlyphAtlas& atlas, |
| const ISize& atlas_size) { |
| TRACE_EVENT0("impeller", __FUNCTION__); |
| auto bitmap = std::make_shared<SkBitmap>(); |
| SkImageInfo image_info; |
| |
| switch (atlas.GetType()) { |
| case GlyphAtlas::Type::kAlphaBitmap: |
| image_info = |
| SkImageInfo::MakeA8(SkISize{static_cast<int32_t>(atlas_size.width), |
| static_cast<int32_t>(atlas_size.height)}); |
| break; |
| case GlyphAtlas::Type::kColorBitmap: |
| image_info = |
| SkImageInfo::MakeN32Premul(atlas_size.width, atlas_size.height); |
| break; |
| } |
| |
| if (!bitmap->tryAllocPixels(image_info)) { |
| return nullptr; |
| } |
| |
| auto surface = SkSurfaces::WrapPixels(bitmap->pixmap()); |
| if (!surface) { |
| return nullptr; |
| } |
| auto canvas = surface->getCanvas(); |
| if (!canvas) { |
| return nullptr; |
| } |
| |
| bool has_color = atlas.GetType() == GlyphAtlas::Type::kColorBitmap; |
| |
| atlas.IterateGlyphs([canvas, has_color](const ScaledFont& scaled_font, |
| const Glyph& glyph, |
| const Rect& location) -> bool { |
| DrawGlyph(canvas, scaled_font, glyph, location, has_color); |
| return true; |
| }); |
| |
| return bitmap; |
| } |
| |
| static bool UpdateGlyphTextureAtlas(std::shared_ptr<SkBitmap> bitmap, |
| const std::shared_ptr<Texture>& texture) { |
| TRACE_EVENT0("impeller", __FUNCTION__); |
| |
| FML_DCHECK(bitmap != nullptr); |
| auto texture_descriptor = texture->GetTextureDescriptor(); |
| |
| auto mapping = std::make_shared<fml::NonOwnedMapping>( |
| reinterpret_cast<const uint8_t*>(bitmap->getAddr(0, 0)), // data |
| texture_descriptor.GetByteSizeOfBaseMipLevel(), // size |
| [bitmap](auto, auto) mutable { bitmap.reset(); } // proc |
| ); |
| |
| return texture->SetContents(mapping); |
| } |
| |
| static std::shared_ptr<Texture> UploadGlyphTextureAtlas( |
| const std::shared_ptr<Allocator>& allocator, |
| std::shared_ptr<SkBitmap> bitmap, |
| const ISize& atlas_size, |
| PixelFormat format) { |
| TRACE_EVENT0("impeller", __FUNCTION__); |
| if (!allocator) { |
| return nullptr; |
| } |
| |
| FML_DCHECK(bitmap != nullptr); |
| const auto& pixmap = bitmap->pixmap(); |
| |
| TextureDescriptor texture_descriptor; |
| texture_descriptor.storage_mode = StorageMode::kHostVisible; |
| texture_descriptor.format = format; |
| texture_descriptor.size = atlas_size; |
| |
| if (pixmap.rowBytes() * pixmap.height() != |
| texture_descriptor.GetByteSizeOfBaseMipLevel()) { |
| return nullptr; |
| } |
| |
| auto texture = allocator->CreateTexture(texture_descriptor); |
| if (!texture || !texture->IsValid()) { |
| return nullptr; |
| } |
| texture->SetLabel("GlyphAtlas"); |
| |
| auto mapping = std::make_shared<fml::NonOwnedMapping>( |
| reinterpret_cast<const uint8_t*>(bitmap->getAddr(0, 0)), // data |
| texture_descriptor.GetByteSizeOfBaseMipLevel(), // size |
| [bitmap](auto, auto) mutable { bitmap.reset(); } // proc |
| ); |
| |
| if (!texture->SetContents(mapping)) { |
| return nullptr; |
| } |
| return texture; |
| } |
| |
| std::shared_ptr<GlyphAtlas> TypographerContextSkia::CreateGlyphAtlas( |
| Context& context, |
| GlyphAtlas::Type type, |
| const std::shared_ptr<GlyphAtlasContext>& atlas_context, |
| const FontGlyphMap& font_glyph_map) const { |
| TRACE_EVENT0("impeller", __FUNCTION__); |
| if (!IsValid()) { |
| return nullptr; |
| } |
| auto& atlas_context_skia = GlyphAtlasContextSkia::Cast(*atlas_context); |
| std::shared_ptr<GlyphAtlas> last_atlas = atlas_context->GetGlyphAtlas(); |
| |
| if (font_glyph_map.empty()) { |
| return last_atlas; |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Step 1: Determine if the atlas type and font glyph pairs are compatible |
| // with the current atlas and reuse if possible. |
| // --------------------------------------------------------------------------- |
| 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); |
| if (font_glyph_atlas) { |
| for (const Glyph& glyph : font_value.second) { |
| if (!font_glyph_atlas->FindGlyphBounds(glyph)) { |
| new_glyphs.emplace_back(scaled_font, glyph); |
| } |
| } |
| } else { |
| for (const Glyph& glyph : font_value.second) { |
| new_glyphs.emplace_back(scaled_font, glyph); |
| } |
| } |
| } |
| if (last_atlas->GetType() == type && new_glyphs.size() == 0) { |
| return last_atlas; |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Step 2: Determine if the additional missing glyphs can be appended to the |
| // existing bitmap without recreating the atlas. This requires that |
| // the type is identical. |
| // --------------------------------------------------------------------------- |
| std::vector<Rect> glyph_positions; |
| if (last_atlas->GetType() == type && |
| CanAppendToExistingAtlas(last_atlas, new_glyphs, glyph_positions, |
| atlas_context->GetAtlasSize(), |
| atlas_context->GetRectPacker())) { |
| // The old bitmap will be reused and only the additional glyphs will be |
| // added. |
| |
| // --------------------------------------------------------------------------- |
| // Step 3a: Record the positions in the glyph atlas of the newly added |
| // glyphs. |
| // --------------------------------------------------------------------------- |
| for (size_t i = 0, count = glyph_positions.size(); i < count; i++) { |
| last_atlas->AddTypefaceGlyphPosition(new_glyphs[i], glyph_positions[i]); |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Step 4a: Draw new font-glyph pairs into the existing bitmap. |
| // --------------------------------------------------------------------------- |
| auto bitmap = atlas_context_skia.GetBitmap(); |
| if (!UpdateAtlasBitmap(*last_atlas, bitmap, new_glyphs)) { |
| return nullptr; |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Step 5a: Update the existing texture with the updated bitmap. |
| // --------------------------------------------------------------------------- |
| if (!UpdateGlyphTextureAtlas(bitmap, last_atlas->GetTexture())) { |
| return nullptr; |
| } |
| return last_atlas; |
| } |
| // A new glyph atlas must be created. |
| |
| // --------------------------------------------------------------------------- |
| // Step 3b: Get the optimum size of the texture atlas. |
| // --------------------------------------------------------------------------- |
| std::vector<FontGlyphPair> font_glyph_pairs; |
| font_glyph_pairs.reserve(std::accumulate( |
| font_glyph_map.begin(), font_glyph_map.end(), 0, |
| [](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) { |
| font_glyph_pairs.push_back({scaled_font, glyph}); |
| } |
| } |
| auto glyph_atlas = std::make_shared<GlyphAtlas>(type); |
| auto atlas_size = OptimumAtlasSizeForFontGlyphPairs( |
| font_glyph_pairs, // |
| glyph_positions, // |
| atlas_context, // |
| type, // |
| context.GetResourceAllocator()->GetMaxTextureSizeSupported() // |
| ); |
| |
| atlas_context->UpdateGlyphAtlas(glyph_atlas, atlas_size); |
| if (atlas_size.IsEmpty()) { |
| return nullptr; |
| } |
| // --------------------------------------------------------------------------- |
| // Step 4b: Find location of font-glyph pairs in the atlas. We have this from |
| // the last step. So no need to do create another rect packer. But just do a |
| // sanity check of counts. This could also be just an assertion as only a |
| // construction issue would cause such a failure. |
| // --------------------------------------------------------------------------- |
| if (glyph_positions.size() != font_glyph_pairs.size()) { |
| return nullptr; |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Step 5b: Record the positions in the glyph atlas. |
| // --------------------------------------------------------------------------- |
| { |
| 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]); |
| } |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Step 6b: Draw font-glyph pairs in the correct spot in the atlas. |
| // --------------------------------------------------------------------------- |
| auto bitmap = CreateAtlasBitmap(*glyph_atlas, atlas_size); |
| if (!bitmap) { |
| return nullptr; |
| } |
| atlas_context_skia.UpdateBitmap(bitmap); |
| |
| // --------------------------------------------------------------------------- |
| // Step 7b: Upload the atlas as a texture. |
| // --------------------------------------------------------------------------- |
| PixelFormat format; |
| switch (type) { |
| case GlyphAtlas::Type::kAlphaBitmap: |
| format = context.GetCapabilities()->GetDefaultGlyphAtlasFormat(); |
| break; |
| case GlyphAtlas::Type::kColorBitmap: |
| format = PixelFormat::kR8G8B8A8UNormInt; |
| break; |
| } |
| auto texture = UploadGlyphTextureAtlas(context.GetResourceAllocator(), bitmap, |
| atlas_size, format); |
| if (!texture) { |
| return nullptr; |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Step 8b: Record the texture in the glyph atlas. |
| // --------------------------------------------------------------------------- |
| glyph_atlas->SetTexture(std::move(texture)); |
| |
| return glyph_atlas; |
| } |
| |
| } // namespace impeller |