/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "gfxFontMissingGlyphs.h" #include "gfxUtils.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/Helpers.h" #include "mozilla/gfx/PathHelpers.h" #include "mozilla/LinkedList.h" #include "mozilla/RefPtr.h" #include "nsDeviceContext.h" #include "nsLayoutUtils.h" #include "TextDrawTarget.h" #include "LayerUserData.h" using namespace mozilla; using namespace mozilla::gfx; #define X 255 static const uint8_t gMiniFontData[] = { 0, X, 0, 0, X, 0, X, X, X, X, X, X, X, 0, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, 0, 0, X, X, X, X, 0, X, X, X, X, X, X, X, 0, X, 0, X, 0, 0, 0, X, 0, 0, X, X, 0, X, X, 0, 0, X, 0, 0, 0, 0, X, X, 0, X, X, 0, X, X, 0, X, X, 0, X, X, 0, 0, X, 0, X, X, 0, 0, X, 0, 0, X, 0, X, 0, X, 0, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, 0, 0, X, X, X, X, X, X, X, X, X, X, X, X, 0, X, 0, 0, X, 0, X, X, X, X, X, X, X, X, 0, X, 0, X, 0, X, 0, 0, 0, 0, X, 0, 0, X, 0, 0, X, X, 0, X, 0, 0, X, X, 0, X, 0, 0, X, X, 0, X, X, 0, X, X, 0, 0, X, 0, X, X, 0, 0, X, 0, 0, 0, X, 0, 0, X, 0, X, X, X, X, X, X, 0, 0, X, X, X, X, X, X, X, 0, 0, X, X, X, X, 0, 0, X, X, 0, X, X, X, 0, 0, X, X, X, X, 0, X, X, X, X, 0, 0, }; #undef X /* Parameters that control the rendering of hexboxes. They look like this: BMP codepoints non-BMP codepoints (U+0000 - U+FFFF) (U+10000 - U+10FFFF) +---------+ +-------------+ | | | | | HHH HHH | | HHH HHH HHH | | HHH HHH | | HHH HHH HHH | | HHH HHH | | HHH HHH HHH | | HHH HHH | | HHH HHH HHH | | HHH HHH | | HHH HHH HHH | | | | | | HHH HHH | | HHH HHH HHH | | HHH HHH | | HHH HHH HHH | | HHH HHH | | HHH HHH HHH | | HHH HHH | | HHH HHH HHH | | HHH HHH | | HHH HHH HHH | | | | | +---------+ +-------------+ */ /** Width of a minifont glyph (see above) */ static const int MINIFONT_WIDTH = 3; /** Height of a minifont glyph (see above) */ static const int MINIFONT_HEIGHT = 5; /** * Gap between minifont glyphs (both horizontal and vertical) and also * the minimum desired gap between the box border and the glyphs */ static const int HEX_CHAR_GAP = 1; /** * The amount of space between the vertical edge of the glyphbox and the * box border. We make this nonzero so that when multiple missing glyphs * occur consecutively there's a gap between their rendered boxes. */ static const int BOX_HORIZONTAL_INSET = 1; /** The width of the border */ static const int BOX_BORDER_WIDTH = 1; /** * The scaling factor for the border opacity; this is multiplied by the current * opacity being used to draw the text. */ static const Float BOX_BORDER_OPACITY = 0.5; #ifndef MOZ_GFX_OPTIMIZE_MOBILE class GlyphAtlas { public: GlyphAtlas(RefPtr&& aSurface, const DeviceColor& aColor) : mSurface(std::move(aSurface)), mColor(aColor) {} ~GlyphAtlas() = default; already_AddRefed Surface() const { RefPtr surface = mSurface; return surface.forget(); } DeviceColor Color() const { return mColor; } private: RefPtr mSurface; DeviceColor mColor; }; // This is an owning reference that we will manage via exchange() and // explicit new/delete operations. static std::atomic gGlyphAtlas; /** * Generates a new colored mini-font atlas from the mini-font mask. */ static GlyphAtlas* MakeGlyphAtlas(const DeviceColor& aColor) { RefPtr glyphDrawTarget = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT), SurfaceFormat::B8G8R8A8); if (!glyphDrawTarget) { return nullptr; } RefPtr glyphMask = glyphDrawTarget->CreateSourceSurfaceFromData( const_cast(gMiniFontData), IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT), MINIFONT_WIDTH * 16, SurfaceFormat::A8); if (!glyphMask) { return nullptr; } glyphDrawTarget->MaskSurface(ColorPattern(aColor), glyphMask, Point(0, 0), DrawOptions(1.0f, CompositionOp::OP_SOURCE)); RefPtr surface = glyphDrawTarget->Snapshot(); if (!surface) { return nullptr; } return new GlyphAtlas(std::move(surface), aColor); } /** * Reuse the current mini-font atlas if the color matches, otherwise regenerate * it. */ static inline already_AddRefed GetGlyphAtlas( const DeviceColor& aColor) { // Get the opaque color, ignoring any transparency which will be handled // later. DeviceColor color(aColor.r, aColor.g, aColor.b); // Atomically grab the current GlyphAtlas pointer (if any). Because we // exchange with nullptr here, no other thread will be able to touch the // currAtlas record while we're using it; if they try, they'll just see // the null that we stored. GlyphAtlas* currAtlas = gGlyphAtlas.exchange(nullptr); if (currAtlas && currAtlas->Color() == color) { // If its color is right, grab a reference to its surface. RefPtr surface = currAtlas->Surface(); // Now put the currAtlas record back in the global. If some other thread // has stored an atlas there in the meantime, we just discard it. delete gGlyphAtlas.exchange(currAtlas); return surface.forget(); } // Make a new atlas in the color we want. GlyphAtlas* atlas = MakeGlyphAtlas(color); RefPtr surface = atlas ? atlas->Surface() : nullptr; // Store the newly-created atlas in the global; release any other. delete gGlyphAtlas.exchange(atlas); return surface.forget(); } /** * Clear any cached glyph atlas resources. */ static void PurgeGlyphAtlas() { delete gGlyphAtlas.exchange(nullptr); } // WebRender layer manager user data that will get signaled when the layer // manager is destroyed. class WRUserData : public layers::LayerUserData, public LinkedListElement { public: explicit WRUserData(layers::WebRenderLayerManager* aManager); ~WRUserData(); static void Assign(layers::WebRenderLayerManager* aManager) { if (!aManager->HasUserData(&sWRUserDataKey)) { aManager->SetUserData(&sWRUserDataKey, new WRUserData(aManager)); } } void Remove() { mManager->RemoveUserData(&sWRUserDataKey); } layers::WebRenderLayerManager* mManager; static UserDataKey sWRUserDataKey; }; static void DestroyImageKey(void* aClosure) { auto* key = static_cast(aClosure); delete key; } static RefPtr gWRGlyphAtlas[8]; static LinkedList gWRUsers; UserDataKey WRUserData::sWRUserDataKey; /** * Generates a transformed WebRender mini-font atlas for a given orientation. */ static already_AddRefed MakeWRGlyphAtlas(const Matrix* aMat) { IntSize size(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT); // If the orientation is transposed, width/height are swapped. if (aMat && aMat->_11 == 0) { std::swap(size.width, size.height); } RefPtr ref = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); RefPtr dt = gfxPlatform::GetPlatform()->CreateSimilarSoftwareDrawTarget( ref, size, SurfaceFormat::B8G8R8A8); if (!dt) { return nullptr; } if (aMat) { // Select appropriate transform matrix based on whether the // orientation is transposed. dt->SetTransform(aMat->_11 == 0 ? Matrix(0.0f, copysign(1.0f, aMat->_12), copysign(1.0f, aMat->_21), 0.0f, aMat->_21 < 0 ? MINIFONT_HEIGHT : 0.0f, aMat->_12 < 0 ? MINIFONT_WIDTH * 16 : 0.0f) : Matrix(copysign(1.0f, aMat->_11), 0.0f, 0.0f, copysign(1.0f, aMat->_22), aMat->_11 < 0 ? MINIFONT_WIDTH * 16 : 0.0f, aMat->_22 < 0 ? MINIFONT_HEIGHT : 0.0f)); } RefPtr mask = dt->CreateSourceSurfaceFromData( const_cast(gMiniFontData), IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT), MINIFONT_WIDTH * 16, SurfaceFormat::A8); if (!mask) { return nullptr; } dt->MaskSurface(ColorPattern(DeviceColor::MaskOpaqueWhite()), mask, Point(0, 0)); return dt->Snapshot(); } /** * Clear any cached WebRender glyph atlas resources. */ static void PurgeWRGlyphAtlas() { // For each WR layer manager, we need go through each atlas orientation // and see if it has a stashed image key. If it does, remove the image // from the layer manager. for (WRUserData* user : gWRUsers) { auto* manager = user->mManager; for (size_t i = 0; i < 8; i++) { if (gWRGlyphAtlas[i]) { auto* key = static_cast(gWRGlyphAtlas[i]->GetUserData( reinterpret_cast(manager))); if (key) { manager->GetRenderRootStateManager()->AddImageKeyForDiscard(*key); } } } } // Remove the layer managers' destroy notifications only after processing // so as not to mess up gWRUsers iteration. while (!gWRUsers.isEmpty()) { gWRUsers.popFirst()->Remove(); } // Finally, clear out the atlases. for (size_t i = 0; i < 8; i++) { gWRGlyphAtlas[i] = nullptr; } } WRUserData::WRUserData(layers::WebRenderLayerManager* aManager) : mManager(aManager) { gWRUsers.insertFront(this); } WRUserData::~WRUserData() { // When the layer manager is destroyed, we need go through each // atlas and remove any assigned image keys. if (isInList()) { for (size_t i = 0; i < 8; i++) { if (gWRGlyphAtlas[i]) { gWRGlyphAtlas[i]->RemoveUserData( reinterpret_cast(mManager)); } } } } static already_AddRefed GetWRGlyphAtlas(DrawTarget& aDrawTarget, const Matrix* aMat) { uint32_t key = 0; // Encode orientation in the key. if (aMat) { if (aMat->_11 == 0) { key |= 4 | (aMat->_12 < 0 ? 1 : 0) | (aMat->_21 < 0 ? 2 : 0); } else { key |= (aMat->_11 < 0 ? 1 : 0) | (aMat->_22 < 0 ? 2 : 0); } } // Check if an atlas was already created, or create one if necessary. RefPtr atlas = gWRGlyphAtlas[key]; if (!atlas) { atlas = MakeWRGlyphAtlas(aMat); gWRGlyphAtlas[key] = atlas; } // The atlas may exist, but an image key may not be assigned for it to // the given layer manager, or it may no longer be valid. auto* tdt = static_cast(&aDrawTarget); auto* manager = tdt->WrLayerManager(); auto* imageKey = static_cast( atlas->GetUserData(reinterpret_cast(manager))); if (!imageKey || !manager->WrBridge()->MatchesNamespace(*imageKey)) { // No image key, so we need to map the atlas' data for transfer to WR. RefPtr dataSurface = atlas->GetDataSurface(); if (!dataSurface) { return nullptr; } DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ); if (!map.IsMapped()) { return nullptr; } // Transfer the data and get an image key for it. Maybe result = tdt->DefineImage( atlas->GetSize(), map.GetStride(), atlas->GetFormat(), map.GetData()); if (!result.isSome()) { return nullptr; } // Assign the image key to the atlas. atlas->AddUserData(reinterpret_cast(manager), new wr::ImageKey(result.ref()), DestroyImageKey); // Create a user data notification for when the layer manager is // destroyed so we can clean up any assigned image keys. WRUserData::Assign(manager); } return atlas.forget(); } static void DrawHexChar(uint32_t aDigit, Float aLeft, Float aTop, DrawTarget& aDrawTarget, SourceSurface* aAtlas, const DeviceColor& aColor, const Matrix* aMat = nullptr) { Rect dest(aLeft, aTop, MINIFONT_WIDTH, MINIFONT_HEIGHT); if (aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT) { // For WR, we need to get the image key assigned to the given WR layer // manager for referencing the image. auto* tdt = static_cast(&aDrawTarget); auto* manager = tdt->WrLayerManager(); auto* key = static_cast( aAtlas->GetUserData(reinterpret_cast(manager))); MOZ_ASSERT(key); // Transform the bounds of the atlas into the given orientation, and then // also transform a small clip rect which will be used to select the given // digit from the atlas. Rect bounds(aLeft - aDigit * MINIFONT_WIDTH, aTop, MINIFONT_WIDTH * 16, MINIFONT_HEIGHT); if (aMat) { // Width and height may be negative after the transform, so move the rect // if necessary and fix size. bounds = aMat->TransformRect(bounds); bounds.x += std::min(bounds.width, 0.0f); bounds.y += std::min(bounds.height, 0.0f); bounds.width = fabs(bounds.width); bounds.height = fabs(bounds.height); dest = aMat->TransformRect(dest); dest.x += std::min(dest.width, 0.0f); dest.y += std::min(dest.height, 0.0f); dest.width = fabs(dest.width); dest.height = fabs(dest.height); } // Finally, push the colored image with point filtering. tdt->PushImage(*key, bounds, dest, wr::ImageRendering::Pixelated, wr::ToColorF(aColor)); } else { // For the normal case, just draw the given digit from the atlas. Point // filtering is used to ensure the mini-font rectangles stay sharp with any // scaling. Handle any transparency here as well. aDrawTarget.DrawSurface( aAtlas, dest, Rect(aDigit * MINIFONT_WIDTH, 0, MINIFONT_WIDTH, MINIFONT_HEIGHT), DrawSurfaceOptions(SamplingFilter::POINT), DrawOptions(aColor.a, CompositionOp::OP_OVER, AntialiasMode::NONE)); } } void gfxFontMissingGlyphs::Purge() { PurgeGlyphAtlas(); PurgeWRGlyphAtlas(); } #else // MOZ_GFX_OPTIMIZE_MOBILE void gfxFontMissingGlyphs::Purge() {} #endif void gfxFontMissingGlyphs::Shutdown() { Purge(); } void gfxFontMissingGlyphs::DrawMissingGlyph(uint32_t aChar, const Rect& aRect, DrawTarget& aDrawTarget, const Pattern& aPattern, const Matrix* aMat) { Rect rect(aRect); // If there is an orientation transform, reorient the bounding rect. if (aMat) { rect.MoveBy(-aRect.BottomLeft()); rect = aMat->TransformBounds(rect); rect.MoveBy(aRect.BottomLeft()); } // If we're currently drawing with some kind of pattern, we just draw the // missing-glyph data in black. DeviceColor color = aPattern.GetType() == PatternType::COLOR ? static_cast(aPattern).mColor : ToDeviceColor(sRGBColor::OpaqueBlack()); // Stroke a rectangle so that the stroke's left edge is inset one pixel // from the left edge of the glyph box and the stroke's right edge // is inset one pixel from the right edge of the glyph box. Float halfBorderWidth = BOX_BORDER_WIDTH / 2.0; Float borderLeft = rect.X() + BOX_HORIZONTAL_INSET + halfBorderWidth; Float borderRight = rect.XMost() - BOX_HORIZONTAL_INSET - halfBorderWidth; Rect borderStrokeRect(borderLeft, rect.Y() + halfBorderWidth, borderRight - borderLeft, rect.Height() - 2.0 * halfBorderWidth); if (!borderStrokeRect.IsEmpty()) { ColorPattern adjustedColor(color); adjustedColor.mColor.a *= BOX_BORDER_OPACITY; #ifdef MOZ_GFX_OPTIMIZE_MOBILE aDrawTarget.FillRect(borderStrokeRect, adjustedColor); #else StrokeOptions strokeOptions(BOX_BORDER_WIDTH); aDrawTarget.StrokeRect(borderStrokeRect, adjustedColor, strokeOptions); #endif } #ifndef MOZ_GFX_OPTIMIZE_MOBILE RefPtr atlas = aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT ? GetWRGlyphAtlas(aDrawTarget, aMat) : GetGlyphAtlas(color); if (!atlas) { return; } Point center = rect.Center(); Float halfGap = HEX_CHAR_GAP / 2.f; Float top = -(MINIFONT_HEIGHT + halfGap); // Figure out a scaling factor that will fit the glyphs in the target rect // both horizontally and vertically. Float width = HEX_CHAR_GAP + MINIFONT_WIDTH + HEX_CHAR_GAP + MINIFONT_WIDTH + ((aChar < 0x10000) ? 0 : HEX_CHAR_GAP + MINIFONT_WIDTH) + HEX_CHAR_GAP; Float height = HEX_CHAR_GAP + MINIFONT_HEIGHT + HEX_CHAR_GAP + MINIFONT_HEIGHT + HEX_CHAR_GAP; Float scaling = std::min(rect.Height() / height, rect.Width() / width); // We always want integer scaling, otherwise the "bitmap" glyphs will look // even uglier than usual when scaled to the target. int32_t devPixelsPerCSSPx = std::max(1, std::floor(scaling)); Matrix tempMat; if (aMat) { // If there is an orientation transform, since draw target transforms may // not be supported, scale and translate it so that it can be directly used // for rendering the mini font without changing the draw target transform. tempMat = Matrix(*aMat) .PostScale(devPixelsPerCSSPx, devPixelsPerCSSPx) .PostTranslate(center); aMat = &tempMat; } else { // Otherwise, scale and translate the draw target transform assuming it // supports that. tempMat = aDrawTarget.GetTransform(); aDrawTarget.SetTransform(Matrix(tempMat).PreTranslate(center).PreScale( devPixelsPerCSSPx, devPixelsPerCSSPx)); } if (aChar < 0x10000) { if (rect.Width() >= 2 * (MINIFONT_WIDTH + HEX_CHAR_GAP) && rect.Height() >= 2 * MINIFONT_HEIGHT + HEX_CHAR_GAP) { // Draw 4 digits for BMP Float left = -(MINIFONT_WIDTH + halfGap); DrawHexChar((aChar >> 12) & 0xF, left, top, aDrawTarget, atlas, color, aMat); DrawHexChar((aChar >> 8) & 0xF, halfGap, top, aDrawTarget, atlas, color, aMat); DrawHexChar((aChar >> 4) & 0xF, left, halfGap, aDrawTarget, atlas, color, aMat); DrawHexChar(aChar & 0xF, halfGap, halfGap, aDrawTarget, atlas, color, aMat); } } else { if (rect.Width() >= 3 * (MINIFONT_WIDTH + HEX_CHAR_GAP) && rect.Height() >= 2 * MINIFONT_HEIGHT + HEX_CHAR_GAP) { // Draw 6 digits for non-BMP Float first = -(MINIFONT_WIDTH * 1.5 + HEX_CHAR_GAP); Float second = -(MINIFONT_WIDTH / 2.0); Float third = (MINIFONT_WIDTH / 2.0 + HEX_CHAR_GAP); DrawHexChar((aChar >> 20) & 0xF, first, top, aDrawTarget, atlas, color, aMat); DrawHexChar((aChar >> 16) & 0xF, second, top, aDrawTarget, atlas, color, aMat); DrawHexChar((aChar >> 12) & 0xF, third, top, aDrawTarget, atlas, color, aMat); DrawHexChar((aChar >> 8) & 0xF, first, halfGap, aDrawTarget, atlas, color, aMat); DrawHexChar((aChar >> 4) & 0xF, second, halfGap, aDrawTarget, atlas, color, aMat); DrawHexChar(aChar & 0xF, third, halfGap, aDrawTarget, atlas, color, aMat); } } if (!aMat) { // The draw target transform was changed, so it must be restored to // the original value. aDrawTarget.SetTransform(tempMat); } #endif } Float gfxFontMissingGlyphs::GetDesiredMinWidth(uint32_t aChar, uint32_t aAppUnitsPerDevPixel) { /** * The minimum desired width for a missing-glyph glyph box. I've laid it out * like this so you can see what goes where. */ Float width = BOX_HORIZONTAL_INSET + BOX_BORDER_WIDTH + HEX_CHAR_GAP + MINIFONT_WIDTH + HEX_CHAR_GAP + MINIFONT_WIDTH + ((aChar < 0x10000) ? 0 : HEX_CHAR_GAP + MINIFONT_WIDTH) + HEX_CHAR_GAP + BOX_BORDER_WIDTH + BOX_HORIZONTAL_INSET; // Note that this will give us floating-point division, so the width will // -not- be snapped to integer multiples of its basic pixel value width *= Float(AppUnitsPerCSSPixel()) / aAppUnitsPerDevPixel; return width; }