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