gfxFontMissingGlyphs.cpp (21054B)
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "gfxFontMissingGlyphs.h" 7 8 #include "gfxUtils.h" 9 #include "mozilla/gfx/2D.h" 10 #include "mozilla/gfx/Helpers.h" 11 #include "mozilla/gfx/PathHelpers.h" 12 #include "mozilla/LinkedList.h" 13 #include "mozilla/RefPtr.h" 14 #include "nsDeviceContext.h" 15 #include "nsLayoutUtils.h" 16 #include "TextDrawTarget.h" 17 #include "LayerUserData.h" 18 19 using namespace mozilla; 20 using namespace mozilla::gfx; 21 22 #ifndef MOZ_GFX_OPTIMIZE_MOBILE 23 # define X 255 24 static const uint8_t gMiniFontData[] = { 25 0, X, 0, 0, X, 0, X, X, X, X, X, X, X, 0, X, X, X, X, X, X, X, X, X, X, 26 X, X, X, X, X, X, X, X, X, X, X, 0, 0, X, X, X, X, 0, X, X, X, X, X, X, 27 X, 0, X, 0, X, 0, 0, 0, X, 0, 0, X, X, 0, X, X, 0, 0, X, 0, 0, 0, 0, X, 28 X, 0, X, X, 0, X, X, 0, X, X, 0, X, X, 0, 0, X, 0, X, X, 0, 0, X, 0, 0, 29 X, 0, X, 0, X, 0, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, 0, 0, X, 30 X, X, X, X, X, X, X, X, X, X, X, 0, X, 0, 0, X, 0, X, X, X, X, X, X, X, 31 X, 0, X, 0, X, 0, X, 0, 0, 0, 0, X, 0, 0, X, 0, 0, X, X, 0, X, 0, 0, X, 32 X, 0, X, 0, 0, X, X, 0, X, X, 0, X, X, 0, 0, X, 0, X, X, 0, 0, X, 0, 0, 33 0, X, 0, 0, X, 0, X, X, X, X, X, X, 0, 0, X, X, X, X, X, X, X, 0, 0, X, 34 X, X, X, 0, 0, X, X, 0, X, X, X, 0, 0, X, X, X, X, 0, X, X, X, X, 0, 0, 35 }; 36 # undef X 37 #endif 38 39 /* Parameters that control the rendering of hexboxes. They look like this: 40 41 BMP codepoints non-BMP codepoints 42 (U+0000 - U+FFFF) (U+10000 - U+10FFFF) 43 44 +---------+ +-------------+ 45 | | | | 46 | HHH HHH | | HHH HHH HHH | 47 | HHH HHH | | HHH HHH HHH | 48 | HHH HHH | | HHH HHH HHH | 49 | HHH HHH | | HHH HHH HHH | 50 | HHH HHH | | HHH HHH HHH | 51 | | | | 52 | HHH HHH | | HHH HHH HHH | 53 | HHH HHH | | HHH HHH HHH | 54 | HHH HHH | | HHH HHH HHH | 55 | HHH HHH | | HHH HHH HHH | 56 | HHH HHH | | HHH HHH HHH | 57 | | | | 58 +---------+ +-------------+ 59 */ 60 61 /** Width of a minifont glyph (see above) */ 62 static const int MINIFONT_WIDTH = 3; 63 /** Height of a minifont glyph (see above) */ 64 #ifndef MOZ_GFX_OPTIMIZE_MOBILE 65 static const int MINIFONT_HEIGHT = 5; 66 #endif 67 /** 68 * Gap between minifont glyphs (both horizontal and vertical) and also 69 * the minimum desired gap between the box border and the glyphs 70 */ 71 static const int HEX_CHAR_GAP = 1; 72 /** 73 * The amount of space between the vertical edge of the glyphbox and the 74 * box border. We make this nonzero so that when multiple missing glyphs 75 * occur consecutively there's a gap between their rendered boxes. 76 */ 77 static const int BOX_HORIZONTAL_INSET = 1; 78 /** The width of the border */ 79 static const int BOX_BORDER_WIDTH = 1; 80 /** 81 * The scaling factor for the border opacity; this is multiplied by the current 82 * opacity being used to draw the text. 83 */ 84 static const Float BOX_BORDER_OPACITY = 0.5; 85 86 #ifndef MOZ_GFX_OPTIMIZE_MOBILE 87 88 class GlyphAtlas { 89 public: 90 GlyphAtlas(RefPtr<SourceSurface>&& aSurface, const DeviceColor& aColor) 91 : mSurface(std::move(aSurface)), mColor(aColor) {} 92 ~GlyphAtlas() = default; 93 94 already_AddRefed<SourceSurface> Surface() const { 95 RefPtr surface = mSurface; 96 return surface.forget(); 97 } 98 DeviceColor Color() const { return mColor; } 99 100 private: 101 RefPtr<SourceSurface> mSurface; 102 DeviceColor mColor; 103 }; 104 105 // This is an owning reference that we will manage via exchange() and 106 // explicit new/delete operations. 107 static std::atomic<GlyphAtlas*> gGlyphAtlas; 108 109 /** 110 * Generates a new colored mini-font atlas from the mini-font mask. 111 */ 112 static GlyphAtlas* MakeGlyphAtlas(const DeviceColor& aColor) { 113 RefPtr<DrawTarget> glyphDrawTarget = 114 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( 115 IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT), 116 SurfaceFormat::B8G8R8A8); 117 if (!glyphDrawTarget) { 118 return nullptr; 119 } 120 RefPtr<SourceSurface> glyphMask = 121 glyphDrawTarget->CreateSourceSurfaceFromData( 122 const_cast<uint8_t*>(gMiniFontData), 123 IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT), MINIFONT_WIDTH * 16, 124 SurfaceFormat::A8); 125 if (!glyphMask) { 126 return nullptr; 127 } 128 glyphDrawTarget->MaskSurface(ColorPattern(aColor), glyphMask, Point(0, 0), 129 DrawOptions(1.0f, CompositionOp::OP_SOURCE)); 130 RefPtr<SourceSurface> surface = glyphDrawTarget->Snapshot(); 131 if (!surface) { 132 return nullptr; 133 } 134 return new GlyphAtlas(std::move(surface), aColor); 135 } 136 137 /** 138 * Reuse the current mini-font atlas if the color matches, otherwise regenerate 139 * it. 140 */ 141 static inline already_AddRefed<SourceSurface> GetGlyphAtlas( 142 const DeviceColor& aColor) { 143 // Get the opaque color, ignoring any transparency which will be handled 144 // later. 145 DeviceColor color(aColor.r, aColor.g, aColor.b); 146 147 // Atomically grab the current GlyphAtlas pointer (if any). Because we 148 // exchange with nullptr here, no other thread will be able to touch the 149 // currAtlas record while we're using it; if they try, they'll just see 150 // the null that we stored. 151 GlyphAtlas* currAtlas = gGlyphAtlas.exchange(nullptr); 152 153 if (currAtlas && currAtlas->Color() == color) { 154 // If its color is right, grab a reference to its surface. 155 RefPtr<SourceSurface> surface = currAtlas->Surface(); 156 // Now put the currAtlas record back in the global. If some other thread 157 // has stored an atlas there in the meantime, we just discard it. 158 delete gGlyphAtlas.exchange(currAtlas); 159 return surface.forget(); 160 } 161 162 // Make a new atlas in the color we want. 163 GlyphAtlas* atlas = MakeGlyphAtlas(color); 164 RefPtr<SourceSurface> surface = atlas ? atlas->Surface() : nullptr; 165 166 // Store the newly-created atlas in the global; release any other. 167 delete gGlyphAtlas.exchange(atlas); 168 return surface.forget(); 169 } 170 171 /** 172 * Clear any cached glyph atlas resources. 173 */ 174 static void PurgeGlyphAtlas() { delete gGlyphAtlas.exchange(nullptr); } 175 176 // WebRender layer manager user data that will get signaled when the layer 177 // manager is destroyed. 178 class WRUserData : public layers::LayerUserData, 179 public LinkedListElement<WRUserData> { 180 public: 181 explicit WRUserData(layers::WebRenderLayerManager* aManager); 182 183 ~WRUserData(); 184 185 static void Assign(layers::WebRenderLayerManager* aManager) { 186 if (!aManager->HasUserData(&sWRUserDataKey)) { 187 aManager->SetUserData(&sWRUserDataKey, new WRUserData(aManager)); 188 } 189 } 190 191 void Remove() { mManager->RemoveUserData(&sWRUserDataKey); } 192 193 layers::WebRenderLayerManager* mManager; 194 195 static UserDataKey sWRUserDataKey; 196 }; 197 198 static void DestroyImageKey(void* aClosure) { 199 auto* key = static_cast<wr::ImageKey*>(aClosure); 200 delete key; 201 } 202 203 constinit static RefPtr<SourceSurface> gWRGlyphAtlas[8]; 204 MOZ_RUNINIT static LinkedList<WRUserData> gWRUsers; 205 UserDataKey WRUserData::sWRUserDataKey; 206 207 /** 208 * Generates a transformed WebRender mini-font atlas for a given orientation. 209 */ 210 static already_AddRefed<SourceSurface> MakeWRGlyphAtlas(const Matrix* aMat) { 211 IntSize size(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT); 212 // If the orientation is transposed, width/height are swapped. 213 if (aMat && aMat->_11 == 0) { 214 std::swap(size.width, size.height); 215 } 216 RefPtr<DrawTarget> ref = 217 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); 218 RefPtr<DrawTarget> dt = 219 gfxPlatform::GetPlatform()->CreateSimilarSoftwareDrawTarget( 220 ref, size, SurfaceFormat::B8G8R8A8); 221 if (!dt) { 222 return nullptr; 223 } 224 if (aMat) { 225 // Select appropriate transform matrix based on whether the 226 // orientation is transposed. 227 dt->SetTransform(aMat->_11 == 0 228 ? Matrix(0.0f, copysign(1.0f, aMat->_12), 229 copysign(1.0f, aMat->_21), 0.0f, 230 aMat->_21 < 0 ? MINIFONT_HEIGHT : 0.0f, 231 aMat->_12 < 0 ? MINIFONT_WIDTH * 16 : 0.0f) 232 : Matrix(copysign(1.0f, aMat->_11), 0.0f, 0.0f, 233 copysign(1.0f, aMat->_22), 234 aMat->_11 < 0 ? MINIFONT_WIDTH * 16 : 0.0f, 235 aMat->_22 < 0 ? MINIFONT_HEIGHT : 0.0f)); 236 } 237 RefPtr<SourceSurface> mask = dt->CreateSourceSurfaceFromData( 238 const_cast<uint8_t*>(gMiniFontData), 239 IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT), MINIFONT_WIDTH * 16, 240 SurfaceFormat::A8); 241 if (!mask) { 242 return nullptr; 243 } 244 dt->MaskSurface(ColorPattern(DeviceColor::MaskOpaqueWhite()), mask, 245 Point(0, 0)); 246 return dt->Snapshot(); 247 } 248 249 /** 250 * Clear any cached WebRender glyph atlas resources. 251 */ 252 static void PurgeWRGlyphAtlas() { 253 // For each WR layer manager, we need go through each atlas orientation 254 // and see if it has a stashed image key. If it does, remove the image 255 // from the layer manager. 256 for (WRUserData* user : gWRUsers) { 257 auto* manager = user->mManager; 258 for (size_t i = 0; i < 8; i++) { 259 if (gWRGlyphAtlas[i]) { 260 auto* key = static_cast<wr::ImageKey*>(gWRGlyphAtlas[i]->GetUserData( 261 reinterpret_cast<UserDataKey*>(manager))); 262 if (key) { 263 manager->GetRenderRootStateManager()->AddImageKeyForDiscard(*key); 264 } 265 } 266 } 267 } 268 // Remove the layer managers' destroy notifications only after processing 269 // so as not to mess up gWRUsers iteration. 270 while (!gWRUsers.isEmpty()) { 271 gWRUsers.popFirst()->Remove(); 272 } 273 // Finally, clear out the atlases. 274 for (size_t i = 0; i < 8; i++) { 275 gWRGlyphAtlas[i] = nullptr; 276 } 277 } 278 279 WRUserData::WRUserData(layers::WebRenderLayerManager* aManager) 280 : mManager(aManager) { 281 gWRUsers.insertFront(this); 282 } 283 284 WRUserData::~WRUserData() { 285 // When the layer manager is destroyed, we need go through each 286 // atlas and remove any assigned image keys. 287 if (isInList()) { 288 for (size_t i = 0; i < 8; i++) { 289 if (gWRGlyphAtlas[i]) { 290 gWRGlyphAtlas[i]->RemoveUserData( 291 reinterpret_cast<UserDataKey*>(mManager)); 292 } 293 } 294 } 295 } 296 297 static already_AddRefed<SourceSurface> GetWRGlyphAtlas(DrawTarget& aDrawTarget, 298 const Matrix* aMat) { 299 uint32_t key = 0; 300 // Encode orientation in the key. 301 if (aMat) { 302 if (aMat->_11 == 0) { 303 key |= 4 | (aMat->_12 < 0 ? 1 : 0) | (aMat->_21 < 0 ? 2 : 0); 304 } else { 305 key |= (aMat->_11 < 0 ? 1 : 0) | (aMat->_22 < 0 ? 2 : 0); 306 } 307 } 308 309 // Check if an atlas was already created, or create one if necessary. 310 RefPtr<SourceSurface> atlas = gWRGlyphAtlas[key]; 311 if (!atlas) { 312 atlas = MakeWRGlyphAtlas(aMat); 313 gWRGlyphAtlas[key] = atlas; 314 } 315 316 // The atlas may exist, but an image key may not be assigned for it to 317 // the given layer manager, or it may no longer be valid. 318 auto* tdt = static_cast<layout::TextDrawTarget*>(&aDrawTarget); 319 auto* manager = tdt->WrLayerManager(); 320 auto* imageKey = static_cast<wr::ImageKey*>( 321 atlas->GetUserData(reinterpret_cast<UserDataKey*>(manager))); 322 if (!imageKey || !manager->WrBridge()->MatchesNamespace(*imageKey)) { 323 // No image key, so we need to map the atlas' data for transfer to WR. 324 RefPtr<DataSourceSurface> dataSurface = atlas->GetDataSurface(); 325 if (!dataSurface) { 326 return nullptr; 327 } 328 DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ); 329 if (!map.IsMapped()) { 330 return nullptr; 331 } 332 // Transfer the data and get an image key for it. 333 Maybe<wr::ImageKey> result = tdt->DefineImage( 334 atlas->GetSize(), map.GetStride(), atlas->GetFormat(), map.GetData()); 335 if (!result.isSome()) { 336 return nullptr; 337 } 338 // Assign the image key to the atlas. 339 atlas->AddUserData(reinterpret_cast<UserDataKey*>(manager), 340 new wr::ImageKey(result.ref()), DestroyImageKey); 341 // Create a user data notification for when the layer manager is 342 // destroyed so we can clean up any assigned image keys. 343 WRUserData::Assign(manager); 344 } 345 return atlas.forget(); 346 } 347 348 static void DrawHexChar(uint32_t aDigit, Float aLeft, Float aTop, 349 DrawTarget& aDrawTarget, SourceSurface* aAtlas, 350 const DeviceColor& aColor, 351 const Matrix* aMat = nullptr) { 352 Rect dest(aLeft, aTop, MINIFONT_WIDTH, MINIFONT_HEIGHT); 353 if (aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT) { 354 // For WR, we need to get the image key assigned to the given WR layer 355 // manager for referencing the image. 356 auto* tdt = static_cast<layout::TextDrawTarget*>(&aDrawTarget); 357 auto* manager = tdt->WrLayerManager(); 358 auto* key = static_cast<wr::ImageKey*>( 359 aAtlas->GetUserData(reinterpret_cast<UserDataKey*>(manager))); 360 MOZ_ASSERT(key); 361 // Transform the bounds of the atlas into the given orientation, and then 362 // also transform a small clip rect which will be used to select the given 363 // digit from the atlas. 364 Rect bounds(aLeft - aDigit * MINIFONT_WIDTH, aTop, MINIFONT_WIDTH * 16, 365 MINIFONT_HEIGHT); 366 if (aMat) { 367 // Width and height may be negative after the transform, so move the rect 368 // if necessary and fix size. 369 bounds = aMat->TransformRect(bounds); 370 bounds.x += std::min(bounds.width, 0.0f); 371 bounds.y += std::min(bounds.height, 0.0f); 372 bounds.width = fabs(bounds.width); 373 bounds.height = fabs(bounds.height); 374 dest = aMat->TransformRect(dest); 375 dest.x += std::min(dest.width, 0.0f); 376 dest.y += std::min(dest.height, 0.0f); 377 dest.width = fabs(dest.width); 378 dest.height = fabs(dest.height); 379 } 380 // Finally, push the colored image with point filtering. 381 tdt->PushImage(*key, bounds, dest, wr::ImageRendering::Pixelated, 382 wr::ToColorF(aColor)); 383 } else { 384 // For the normal case, just draw the given digit from the atlas. Point 385 // filtering is used to ensure the mini-font rectangles stay sharp with any 386 // scaling. Handle any transparency here as well. 387 aDrawTarget.DrawSurface( 388 aAtlas, dest, 389 Rect(aDigit * MINIFONT_WIDTH, 0, MINIFONT_WIDTH, MINIFONT_HEIGHT), 390 DrawSurfaceOptions(SamplingFilter::POINT), 391 DrawOptions(aColor.a, CompositionOp::OP_OVER, AntialiasMode::NONE)); 392 } 393 } 394 395 void gfxFontMissingGlyphs::Purge() { 396 PurgeGlyphAtlas(); 397 PurgeWRGlyphAtlas(); 398 } 399 400 #else // MOZ_GFX_OPTIMIZE_MOBILE 401 402 void gfxFontMissingGlyphs::Purge() {} 403 404 #endif 405 406 void gfxFontMissingGlyphs::Shutdown() { Purge(); } 407 408 void gfxFontMissingGlyphs::DrawMissingGlyph(uint32_t aChar, const Rect& aRect, 409 DrawTarget& aDrawTarget, 410 const Pattern& aPattern, 411 const Matrix* aMat) { 412 Rect rect(aRect); 413 // If there is an orientation transform, reorient the bounding rect. 414 if (aMat) { 415 rect.MoveBy(-aRect.BottomLeft()); 416 rect = aMat->TransformBounds(rect); 417 rect.MoveBy(aRect.BottomLeft()); 418 } 419 420 // If we're currently drawing with some kind of pattern, we just draw the 421 // missing-glyph data in black. 422 DeviceColor color = aPattern.GetType() == PatternType::COLOR 423 ? static_cast<const ColorPattern&>(aPattern).mColor 424 : ToDeviceColor(sRGBColor::OpaqueBlack()); 425 426 // Stroke a rectangle so that the stroke's left edge is inset one pixel 427 // from the left edge of the glyph box and the stroke's right edge 428 // is inset one pixel from the right edge of the glyph box. 429 Float halfBorderWidth = BOX_BORDER_WIDTH / 2.0; 430 Float borderLeft = rect.X() + BOX_HORIZONTAL_INSET + halfBorderWidth; 431 Float borderRight = rect.XMost() - BOX_HORIZONTAL_INSET - halfBorderWidth; 432 Rect borderStrokeRect(borderLeft, rect.Y() + halfBorderWidth, 433 borderRight - borderLeft, 434 rect.Height() - 2.0 * halfBorderWidth); 435 if (!borderStrokeRect.IsEmpty()) { 436 ColorPattern adjustedColor(color); 437 adjustedColor.mColor.a *= BOX_BORDER_OPACITY; 438 #ifdef MOZ_GFX_OPTIMIZE_MOBILE 439 aDrawTarget.FillRect(borderStrokeRect, adjustedColor); 440 #else 441 StrokeOptions strokeOptions(BOX_BORDER_WIDTH); 442 aDrawTarget.StrokeRect(borderStrokeRect, adjustedColor, strokeOptions); 443 #endif 444 } 445 446 #ifndef MOZ_GFX_OPTIMIZE_MOBILE 447 RefPtr<SourceSurface> atlas = 448 aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT 449 ? GetWRGlyphAtlas(aDrawTarget, aMat) 450 : GetGlyphAtlas(color); 451 if (!atlas) { 452 return; 453 } 454 455 Point center = rect.Center(); 456 Float halfGap = HEX_CHAR_GAP / 2.f; 457 Float top = -(MINIFONT_HEIGHT + halfGap); 458 459 // Figure out a scaling factor that will fit the glyphs in the target rect 460 // both horizontally and vertically. 461 Float width = HEX_CHAR_GAP + MINIFONT_WIDTH + HEX_CHAR_GAP + MINIFONT_WIDTH + 462 ((aChar < 0x10000) ? 0 : HEX_CHAR_GAP + MINIFONT_WIDTH) + 463 HEX_CHAR_GAP; 464 Float height = HEX_CHAR_GAP + MINIFONT_HEIGHT + HEX_CHAR_GAP + 465 MINIFONT_HEIGHT + HEX_CHAR_GAP; 466 Float scaling = std::min(rect.Height() / height, rect.Width() / width); 467 468 // We always want integer scaling, otherwise the "bitmap" glyphs will look 469 // even uglier than usual when scaled to the target. 470 int32_t devPixelsPerCSSPx = std::max<int32_t>(1, std::floor(scaling)); 471 472 Matrix tempMat; 473 if (aMat) { 474 // If there is an orientation transform, since draw target transforms may 475 // not be supported, scale and translate it so that it can be directly used 476 // for rendering the mini font without changing the draw target transform. 477 tempMat = Matrix(*aMat) 478 .PostScale(devPixelsPerCSSPx, devPixelsPerCSSPx) 479 .PostTranslate(center); 480 aMat = &tempMat; 481 } else { 482 // Otherwise, scale and translate the draw target transform assuming it 483 // supports that. 484 tempMat = aDrawTarget.GetTransform(); 485 aDrawTarget.SetTransform(Matrix(tempMat).PreTranslate(center).PreScale( 486 devPixelsPerCSSPx, devPixelsPerCSSPx)); 487 } 488 489 if (aChar < 0x10000) { 490 if (rect.Width() >= 2 * (MINIFONT_WIDTH + HEX_CHAR_GAP) && 491 rect.Height() >= 2 * MINIFONT_HEIGHT + HEX_CHAR_GAP) { 492 // Draw 4 digits for BMP 493 Float left = -(MINIFONT_WIDTH + halfGap); 494 DrawHexChar((aChar >> 12) & 0xF, left, top, aDrawTarget, atlas, color, 495 aMat); 496 DrawHexChar((aChar >> 8) & 0xF, halfGap, top, aDrawTarget, atlas, color, 497 aMat); 498 DrawHexChar((aChar >> 4) & 0xF, left, halfGap, aDrawTarget, atlas, color, 499 aMat); 500 DrawHexChar(aChar & 0xF, halfGap, halfGap, aDrawTarget, atlas, color, 501 aMat); 502 } 503 } else { 504 if (rect.Width() >= 3 * (MINIFONT_WIDTH + HEX_CHAR_GAP) && 505 rect.Height() >= 2 * MINIFONT_HEIGHT + HEX_CHAR_GAP) { 506 // Draw 6 digits for non-BMP 507 Float first = -(MINIFONT_WIDTH * 1.5 + HEX_CHAR_GAP); 508 Float second = -(MINIFONT_WIDTH / 2.0); 509 Float third = (MINIFONT_WIDTH / 2.0 + HEX_CHAR_GAP); 510 DrawHexChar((aChar >> 20) & 0xF, first, top, aDrawTarget, atlas, color, 511 aMat); 512 DrawHexChar((aChar >> 16) & 0xF, second, top, aDrawTarget, atlas, color, 513 aMat); 514 DrawHexChar((aChar >> 12) & 0xF, third, top, aDrawTarget, atlas, color, 515 aMat); 516 DrawHexChar((aChar >> 8) & 0xF, first, halfGap, aDrawTarget, atlas, color, 517 aMat); 518 DrawHexChar((aChar >> 4) & 0xF, second, halfGap, aDrawTarget, atlas, 519 color, aMat); 520 DrawHexChar(aChar & 0xF, third, halfGap, aDrawTarget, atlas, color, aMat); 521 } 522 } 523 524 if (!aMat) { 525 // The draw target transform was changed, so it must be restored to 526 // the original value. 527 aDrawTarget.SetTransform(tempMat); 528 } 529 #endif 530 } 531 532 Float gfxFontMissingGlyphs::GetDesiredMinWidth(uint32_t aChar, 533 uint32_t aAppUnitsPerDevPixel) { 534 /** 535 * The minimum desired width for a missing-glyph glyph box. I've laid it out 536 * like this so you can see what goes where. 537 */ 538 Float width = BOX_HORIZONTAL_INSET + BOX_BORDER_WIDTH + HEX_CHAR_GAP + 539 MINIFONT_WIDTH + HEX_CHAR_GAP + MINIFONT_WIDTH + 540 ((aChar < 0x10000) ? 0 : HEX_CHAR_GAP + MINIFONT_WIDTH) + 541 HEX_CHAR_GAP + BOX_BORDER_WIDTH + BOX_HORIZONTAL_INSET; 542 // Note that this will give us floating-point division, so the width will 543 // -not- be snapped to integer multiples of its basic pixel value 544 width *= Float(AppUnitsPerCSSPixel()) / aAppUnitsPerDevPixel; 545 return width; 546 }