blob: 1330d77495d3095f7f3123fe29276d5a1d62fe79 [file] [log] [blame]
// 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 "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(atlas_size.width, 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);
// If the new atlas size is the same size as the previous texture, reuse the
// texture and treat this as an updated that replaces all glyphs.
if (last_atlas && last_atlas->GetTexture()) {
std::shared_ptr<Texture> last_texture = last_atlas->GetTexture();
if (atlas_size == last_texture->GetSize()) {
if (!UpdateGlyphTextureAtlas(bitmap, last_texture)) {
return nullptr;
}
glyph_atlas->SetTexture(last_texture);
return glyph_atlas;
}
}
// ---------------------------------------------------------------------------
// Step 7b: Upload the atlas as a texture.
// ---------------------------------------------------------------------------
PixelFormat format;
switch (type) {
case GlyphAtlas::Type::kAlphaBitmap:
format = PixelFormat::kA8UNormInt;
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