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"
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
;
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,
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 +---------+ +-------------+
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 |
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 |
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;
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;
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;
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
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
; }
97 RefPtr
<SourceSurface
> mSurface
;
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
) {
116 RefPtr
<SourceSurface
> glyphMask
=
117 glyphDrawTarget
->CreateSourceSurfaceFromData(
118 const_cast<uint8_t*>(gMiniFontData
),
119 IntSize(MINIFONT_WIDTH
* 16, MINIFONT_HEIGHT
), MINIFONT_WIDTH
* 16,
124 glyphDrawTarget
->MaskSurface(ColorPattern(aColor
), glyphMask
, Point(0, 0),
125 DrawOptions(1.0f
, CompositionOp::OP_SOURCE
));
126 RefPtr
<SourceSurface
> surface
= glyphDrawTarget
->Snapshot();
130 return new GlyphAtlas(std::move(surface
), aColor
);
134 * Reuse the current mini-font atlas if the color matches, otherwise regenerate
137 static inline already_AddRefed
<SourceSurface
> GetGlyphAtlas(
138 const DeviceColor
& aColor
) {
139 // Get the opaque color, ignoring any transparency which will be handled
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
> {
177 explicit WRUserData(layers::WebRenderLayerManager
* aManager
);
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
);
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
);
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,
240 dt
->MaskSurface(ColorPattern(DeviceColor::MaskOpaqueWhite()), mask
,
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
)));
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.
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
) {
296 // Encode orientation in the key.
298 if (aMat
->_11
== 0) {
299 key
|= 4 | (aMat
->_12
< 0 ? 1 : 0) | (aMat
->_21
< 0 ? 2 : 0);
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
];
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();
324 DataSourceSurface::ScopedMap
map(dataSurface
, DataSourceSurface::READ
);
325 if (!map
.IsMapped()) {
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()) {
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
)));
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,
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
));
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(
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() {
396 #else // MOZ_GFX_OPTIMIZE_MOBILE
398 void gfxFontMissingGlyphs::Purge() {}
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
) {
409 // If there is an orientation transform, reorient the bounding rect.
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
);
437 StrokeOptions
strokeOptions(BOX_BORDER_WIDTH
);
438 aDrawTarget
.StrokeRect(borderStrokeRect
, adjustedColor
, strokeOptions
);
442 #ifndef MOZ_GFX_OPTIMIZE_MOBILE
443 RefPtr
<SourceSurface
> atlas
=
444 aDrawTarget
.GetBackendType() == BackendType::WEBRENDER_TEXT
445 ? GetWRGlyphAtlas(aDrawTarget
, aMat
)
446 : GetGlyphAtlas(color
);
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
) +
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
));
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
);
478 // Otherwise, scale and translate the draw target transform assuming it
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
,
492 DrawHexChar((aChar
>> 8) & 0xF, halfGap
, top
, aDrawTarget
, atlas
, color
,
494 DrawHexChar((aChar
>> 4) & 0xF, left
, halfGap
, aDrawTarget
, atlas
, color
,
496 DrawHexChar(aChar
& 0xF, halfGap
, halfGap
, aDrawTarget
, atlas
, color
,
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
,
508 DrawHexChar((aChar
>> 16) & 0xF, second
, top
, aDrawTarget
, atlas
, color
,
510 DrawHexChar((aChar
>> 12) & 0xF, third
, top
, aDrawTarget
, atlas
, color
,
512 DrawHexChar((aChar
>> 8) & 0xF, first
, halfGap
, aDrawTarget
, atlas
, color
,
514 DrawHexChar((aChar
>> 4) & 0xF, second
, halfGap
, aDrawTarget
, atlas
,
516 DrawHexChar(aChar
& 0xF, third
, halfGap
, aDrawTarget
, atlas
, color
, aMat
);
521 // The draw target transform was changed, so it must be restored to
522 // the original value.
523 aDrawTarget
.SetTransform(tempMat
);
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
;