1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
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"
18 using namespace mozilla
;
19 using namespace mozilla::gfx
;
22 static const uint8_t gMiniFontData
[] = {
23 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
,
24 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,
25 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
,
26 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,
27 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,
31 /* Parameters that control the rendering of hexboxes. They look like this:
33 BMP codepoints non-BMP codepoints
34 (U+0000 - U+FFFF) (U+10000 - U+10FFFF)
36 +---------+ +-------------+
38 | HHH HHH | | HHH HHH HHH |
39 | HHH HHH | | HHH HHH HHH |
40 | HHH HHH | | HHH HHH HHH |
41 | HHH HHH | | HHH HHH HHH |
42 | HHH HHH | | HHH HHH HHH |
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 +---------+ +-------------+
53 /** Width of a minifont glyph (see above) */
54 static const int MINIFONT_WIDTH
= 3;
55 /** Height of a minifont glyph (see above) */
56 static const int MINIFONT_HEIGHT
= 5;
58 * Gap between minifont glyphs (both horizontal and vertical) and also
59 * the minimum desired gap between the box border and the glyphs
61 static const int HEX_CHAR_GAP
= 1;
63 * The amount of space between the vertical edge of the glyphbox and the
64 * box border. We make this nonzero so that when multiple missing glyphs
65 * occur consecutively there's a gap between their rendered boxes.
67 static const int BOX_HORIZONTAL_INSET
= 1;
68 /** The width of the border */
69 static const int BOX_BORDER_WIDTH
= 1;
71 * The scaling factor for the border opacity; this is multiplied by the current
72 * opacity being used to draw the text.
74 static const Float BOX_BORDER_OPACITY
= 0.5;
76 #ifndef MOZ_GFX_OPTIMIZE_MOBILE
78 static RefPtr
<DrawTarget
> gGlyphDrawTarget
;
79 static RefPtr
<SourceSurface
> gGlyphMask
;
80 static RefPtr
<SourceSurface
> gGlyphAtlas
;
81 static Color gGlyphColor
;
84 * Generates a new colored mini-font atlas from the mini-font mask.
87 MakeGlyphAtlas(const Color
& aColor
)
89 gGlyphAtlas
= nullptr;
90 if (!gGlyphDrawTarget
) {
91 gGlyphDrawTarget
= gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
92 IntSize(MINIFONT_WIDTH
* 16, MINIFONT_HEIGHT
), SurfaceFormat::B8G8R8A8
);
93 if (!gGlyphDrawTarget
) {
98 gGlyphMask
= gGlyphDrawTarget
->CreateSourceSurfaceFromData(
99 const_cast<uint8_t*>(gMiniFontData
), IntSize(MINIFONT_WIDTH
* 16, MINIFONT_HEIGHT
),
100 MINIFONT_WIDTH
* 16, SurfaceFormat::A8
);
105 gGlyphDrawTarget
->MaskSurface(ColorPattern(aColor
), gGlyphMask
, Point(0, 0),
106 DrawOptions(1.0f
, CompositionOp::OP_SOURCE
));
107 gGlyphAtlas
= gGlyphDrawTarget
->Snapshot();
111 gGlyphColor
= aColor
;
116 * Reuse the current mini-font atlas if the color matches, otherwise regenerate it.
118 static inline already_AddRefed
<SourceSurface
>
119 GetGlyphAtlas(const Color
& aColor
)
121 // Get the opaque color, ignoring any transparency which will be handled later.
122 Color
color(aColor
.r
, aColor
.g
, aColor
.b
);
123 if ((gGlyphAtlas
&& gGlyphColor
== color
) || MakeGlyphAtlas(color
)) {
124 return do_AddRef(gGlyphAtlas
);
130 * Clear any cached glyph atlas resources.
135 gGlyphAtlas
= nullptr;
136 gGlyphDrawTarget
= nullptr;
137 gGlyphMask
= nullptr;
140 // WebRender layer manager user data that will get signaled when the layer
141 // manager is destroyed.
142 class WRUserData
: public layers::LayerUserData
, public LinkedListElement
<WRUserData
>
145 explicit WRUserData(layers::WebRenderLayerManager
* aManager
);
150 Assign(layers::WebRenderLayerManager
* aManager
)
152 if (!aManager
->HasUserData(&sWRUserDataKey
)) {
153 aManager
->SetUserData(&sWRUserDataKey
, new WRUserData(aManager
));
160 mManager
->RemoveUserData(&sWRUserDataKey
);
163 layers::WebRenderLayerManager
* mManager
;
165 static UserDataKey sWRUserDataKey
;
168 static RefPtr
<SourceSurface
> gWRGlyphAtlas
[8];
169 static LinkedList
<WRUserData
> gWRUsers
;
170 UserDataKey
WRUserData::sWRUserDataKey
;
173 * Generates a transformed WebRender mini-font atlas for a given orientation.
175 static already_AddRefed
<SourceSurface
>
176 MakeWRGlyphAtlas(const Matrix
* aMat
)
178 IntSize
size(MINIFONT_WIDTH
* 16, MINIFONT_HEIGHT
);
179 // If the orientation is transposed, width/height are swapped.
180 if (aMat
&& aMat
->_11
== 0) {
181 std::swap(size
.width
, size
.height
);
183 RefPtr
<DrawTarget
> ref
= gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
184 RefPtr
<DrawTarget
> dt
= gfxPlatform::GetPlatform()->CreateSimilarSoftwareDrawTarget(
185 ref
, size
, SurfaceFormat::B8G8R8A8
);
190 // Select appropriate transform matrix based on whether the
191 // orientation is transposed.
192 dt
->SetTransform(aMat
->_11
== 0 ?
193 Matrix(0.0f
, copysign(1.0f
, aMat
->_12
),
194 copysign(1.0f
, aMat
->_21
), 0.0f
,
195 aMat
->_21
< 0 ? MINIFONT_HEIGHT
: 0.0f
,
196 aMat
->_12
< 0 ? MINIFONT_WIDTH
* 16 : 0.0f
) :
197 Matrix(copysign(1.0f
, aMat
->_11
), 0.0f
,
198 0.0f
, copysign(1.0f
, aMat
->_22
),
199 aMat
->_11
< 0 ? MINIFONT_WIDTH
* 16 : 0.0f
,
200 aMat
->_22
< 0 ? MINIFONT_HEIGHT
: 0.0f
));
202 RefPtr
<SourceSurface
> mask
= dt
->CreateSourceSurfaceFromData(
203 const_cast<uint8_t*>(gMiniFontData
), IntSize(MINIFONT_WIDTH
* 16, MINIFONT_HEIGHT
),
204 MINIFONT_WIDTH
* 16, SurfaceFormat::A8
);
208 dt
->MaskSurface(ColorPattern(Color(1.0f
, 1.0f
, 1.0f
)), mask
, Point(0, 0));
209 return dt
->Snapshot();
213 * Clear any cached WebRender glyph atlas resources.
218 // For each WR layer manager, we need go through each atlas orientation
219 // and see if it has a stashed image key. If it does, remove the image
220 // from the layer manager.
221 for (WRUserData
* user
: gWRUsers
) {
222 auto* manager
= user
->mManager
;
223 for (size_t i
= 0; i
< 8; i
++) {
224 if (gWRGlyphAtlas
[i
]) {
226 (uint32_t)(uintptr_t)gWRGlyphAtlas
[i
]->GetUserData(
227 reinterpret_cast<UserDataKey
*>(manager
));
229 manager
->AddImageKeyForDiscard(
230 wr::ImageKey
{manager
->WrBridge()->GetNamespace(), handle
});
235 // Remove the layer managers' destroy notifications only after processing
236 // so as not to mess up gWRUsers iteration.
237 while (!gWRUsers
.isEmpty()) {
238 gWRUsers
.popFirst()->Remove();
240 // Finally, clear out the atlases.
241 for (size_t i
= 0; i
< 8; i
++) {
242 gWRGlyphAtlas
[i
] = nullptr;
246 WRUserData::WRUserData(layers::WebRenderLayerManager
* aManager
)
249 gWRUsers
.insertFront(this);
252 WRUserData::~WRUserData()
254 // When the layer manager is destroyed, we need go through each
255 // atlas and remove any assigned image keys.
257 for (size_t i
= 0; i
< 8; i
++) {
258 if (gWRGlyphAtlas
[i
]) {
259 gWRGlyphAtlas
[i
]->RemoveUserData(reinterpret_cast<UserDataKey
*>(mManager
));
265 static already_AddRefed
<SourceSurface
>
266 GetWRGlyphAtlas(DrawTarget
& aDrawTarget
, const Matrix
* aMat
)
269 // Encode orientation in the key.
271 if (aMat
->_11
== 0) {
272 key
|= 4 | (aMat
->_12
< 0 ? 1 : 0) | (aMat
->_21
< 0 ? 2 : 0);
274 key
|= (aMat
->_11
< 0 ? 1 : 0) | (aMat
->_22
< 0 ? 2 : 0);
277 // Check if an atlas was already created, or create one if necessary.
278 RefPtr
<SourceSurface
> atlas
= gWRGlyphAtlas
[key
];
280 atlas
= MakeWRGlyphAtlas(aMat
);
281 gWRGlyphAtlas
[key
] = atlas
;
283 // The atlas may exist, but an image key may not be assigned for it to
284 // the given layer manager.
285 auto* tdt
= static_cast<layout::TextDrawTarget
*>(&aDrawTarget
);
286 auto* manager
= tdt
->WrLayerManager();
287 if (!atlas
->GetUserData(reinterpret_cast<UserDataKey
*>(manager
))) {
288 // No image key, so we need to map the atlas' data for transfer to WR.
289 RefPtr
<DataSourceSurface
> dataSurface
= atlas
->GetDataSurface();
293 DataSourceSurface::ScopedMap
map(dataSurface
, DataSourceSurface::READ
);
294 if (!map
.IsMapped()) {
297 // Transfer the data and get an image key for it.
298 Maybe
<wr::ImageKey
> result
=
299 tdt
->DefineImage(atlas
->GetSize(),
303 if (!result
.isSome()) {
306 // Assign the image key to the atlas.
307 atlas
->AddUserData(reinterpret_cast<UserDataKey
*>(manager
),
308 (void*)(uintptr_t)result
.value().mHandle
,
310 // Create a user data notification for when the layer manager is
311 // destroyed so we can clean up any assigned image keys.
312 WRUserData::Assign(manager
);
314 return atlas
.forget();
318 DrawHexChar(uint32_t aDigit
, Float aLeft
, Float aTop
, DrawTarget
& aDrawTarget
,
319 SourceSurface
* aAtlas
, const Color
& aColor
,
320 const Matrix
* aMat
= nullptr)
322 Rect
dest(aLeft
, aTop
, MINIFONT_WIDTH
, MINIFONT_HEIGHT
);
323 if (aDrawTarget
.GetBackendType() == BackendType::WEBRENDER_TEXT
) {
324 // For WR, we need to get the image key assigned to the given WR layer manager
325 // for referencing the image.
326 auto* tdt
= static_cast<layout::TextDrawTarget
*>(&aDrawTarget
);
327 auto* manager
= tdt
->WrLayerManager();
329 manager
->WrBridge()->GetNamespace(),
330 (uint32_t)(uintptr_t)aAtlas
->GetUserData(reinterpret_cast<UserDataKey
*>(manager
))
332 // Transform the bounds of the atlas into the given orientation, and then also transform
333 // a small clip rect which will be used to select the given digit from the atlas.
334 Rect
bounds(aLeft
- aDigit
* MINIFONT_WIDTH
, aTop
, MINIFONT_WIDTH
* 16, MINIFONT_HEIGHT
);
336 // Width and height may be negative after the transform, so move the rect
337 // if necessary and fix size.
338 bounds
= aMat
->TransformRect(bounds
);
339 bounds
.x
+= std::min(bounds
.width
, 0.0f
);
340 bounds
.y
+= std::min(bounds
.height
, 0.0f
);
341 bounds
.width
= fabs(bounds
.width
);
342 bounds
.height
= fabs(bounds
.height
);
343 dest
= aMat
->TransformRect(dest
);
344 dest
.x
+= std::min(dest
.width
, 0.0f
);
345 dest
.y
+= std::min(dest
.height
, 0.0f
);
346 dest
.width
= fabs(dest
.width
);
347 dest
.height
= fabs(dest
.height
);
349 // Finally, push the colored image with point filtering.
351 wr::ToLayoutRect(bounds
),
352 wr::ToLayoutRect(dest
),
353 wr::ImageRendering::Pixelated
,
354 wr::ToColorF(aColor
));
356 // For the normal case, just draw the given digit from the atlas. Point filtering is used
357 // to ensure the mini-font rectangles stay sharp with any scaling. Handle any transparency
359 aDrawTarget
.DrawSurface(aAtlas
,
361 Rect(aDigit
* MINIFONT_WIDTH
, 0, MINIFONT_WIDTH
, MINIFONT_HEIGHT
),
362 DrawSurfaceOptions(SamplingFilter::POINT
),
363 DrawOptions(aColor
.a
, CompositionOp::OP_OVER
, AntialiasMode::NONE
));
368 gfxFontMissingGlyphs::Purge()
374 #else // MOZ_GFX_OPTIMIZE_MOBILE
377 gfxFontMissingGlyphs::Purge()
384 gfxFontMissingGlyphs::Shutdown()
390 gfxFontMissingGlyphs::DrawMissingGlyph(uint32_t aChar
,
392 DrawTarget
& aDrawTarget
,
393 const Pattern
& aPattern
,
394 uint32_t aAppUnitsPerDevPixel
,
398 // If there is an orientation transform, reorient the bounding rect.
400 rect
.MoveBy(-aRect
.BottomLeft());
401 rect
= aMat
->TransformBounds(rect
);
402 rect
.MoveBy(aRect
.BottomLeft());
405 // If we're currently drawing with some kind of pattern, we just draw the
406 // missing-glyph data in black.
407 Color color
= aPattern
.GetType() == PatternType::COLOR
?
408 static_cast<const ColorPattern
&>(aPattern
).mColor
:
409 ToDeviceColor(Color(0.f
, 0.f
, 0.f
, 1.f
));
411 // Stroke a rectangle so that the stroke's left edge is inset one pixel
412 // from the left edge of the glyph box and the stroke's right edge
413 // is inset one pixel from the right edge of the glyph box.
414 Float halfBorderWidth
= BOX_BORDER_WIDTH
/ 2.0;
415 Float borderLeft
= rect
.X() + BOX_HORIZONTAL_INSET
+ halfBorderWidth
;
416 Float borderRight
= rect
.XMost() - BOX_HORIZONTAL_INSET
- halfBorderWidth
;
417 Rect
borderStrokeRect(borderLeft
, rect
.Y() + halfBorderWidth
,
418 borderRight
- borderLeft
,
419 rect
.Height() - 2.0 * halfBorderWidth
);
420 if (!borderStrokeRect
.IsEmpty()) {
421 ColorPattern
adjustedColor(color
);
422 adjustedColor
.mColor
.a
*= BOX_BORDER_OPACITY
;
423 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
424 aDrawTarget
.FillRect(borderStrokeRect
, adjustedColor
);
426 StrokeOptions
strokeOptions(BOX_BORDER_WIDTH
);
427 aDrawTarget
.StrokeRect(borderStrokeRect
, adjustedColor
, strokeOptions
);
431 #ifndef MOZ_GFX_OPTIMIZE_MOBILE
432 RefPtr
<SourceSurface
> atlas
=
433 aDrawTarget
.GetBackendType() == BackendType::WEBRENDER_TEXT
?
434 GetWRGlyphAtlas(aDrawTarget
, aMat
) :
435 GetGlyphAtlas(color
);
440 Point center
= rect
.Center();
441 Float halfGap
= HEX_CHAR_GAP
/ 2.f
;
442 Float top
= -(MINIFONT_HEIGHT
+ halfGap
);
443 // We always want integer scaling, otherwise the "bitmap" glyphs will look
444 // even uglier than usual when zoomed
445 int32_t devPixelsPerCSSPx
=
446 std::max
<int32_t>(1, AppUnitsPerCSSPixel() /
447 aAppUnitsPerDevPixel
);
451 // If there is an orientation transform, since draw target transforms may
452 // not be supported, scale and translate it so that it can be directly used
453 // for rendering the mini font without changing the draw target transform.
454 tempMat
= Matrix(*aMat
).PostScale(devPixelsPerCSSPx
, devPixelsPerCSSPx
)
455 .PostTranslate(center
);
458 // Otherwise, scale and translate the draw target transform assuming it
460 tempMat
= aDrawTarget
.GetTransform();
461 aDrawTarget
.SetTransform(
462 Matrix(tempMat
).PreTranslate(center
)
463 .PreScale(devPixelsPerCSSPx
, devPixelsPerCSSPx
));
466 if (aChar
< 0x10000) {
467 if (rect
.Width() >= 2 * (MINIFONT_WIDTH
+ HEX_CHAR_GAP
) &&
468 rect
.Height() >= 2 * MINIFONT_HEIGHT
+ HEX_CHAR_GAP
) {
469 // Draw 4 digits for BMP
470 Float left
= -(MINIFONT_WIDTH
+ halfGap
);
471 DrawHexChar((aChar
>> 12) & 0xF, left
, top
, aDrawTarget
, atlas
, color
, aMat
);
472 DrawHexChar((aChar
>> 8) & 0xF, halfGap
, top
, aDrawTarget
, atlas
, color
, aMat
);
473 DrawHexChar((aChar
>> 4) & 0xF, left
, halfGap
, aDrawTarget
, atlas
, color
, aMat
);
474 DrawHexChar(aChar
& 0xF, halfGap
, halfGap
, aDrawTarget
, atlas
, color
, aMat
);
477 if (rect
.Width() >= 3 * (MINIFONT_WIDTH
+ HEX_CHAR_GAP
) &&
478 rect
.Height() >= 2 * MINIFONT_HEIGHT
+ HEX_CHAR_GAP
) {
479 // Draw 6 digits for non-BMP
480 Float first
= -(MINIFONT_WIDTH
* 1.5 + HEX_CHAR_GAP
);
481 Float second
= -(MINIFONT_WIDTH
/ 2.0);
482 Float third
= (MINIFONT_WIDTH
/ 2.0 + HEX_CHAR_GAP
);
483 DrawHexChar((aChar
>> 20) & 0xF, first
, top
, aDrawTarget
, atlas
, color
, aMat
);
484 DrawHexChar((aChar
>> 16) & 0xF, second
, top
, aDrawTarget
, atlas
, color
, aMat
);
485 DrawHexChar((aChar
>> 12) & 0xF, third
, top
, aDrawTarget
, atlas
, color
, aMat
);
486 DrawHexChar((aChar
>> 8) & 0xF, first
, halfGap
, aDrawTarget
, atlas
, color
, aMat
);
487 DrawHexChar((aChar
>> 4) & 0xF, second
, halfGap
, aDrawTarget
, atlas
, color
, aMat
);
488 DrawHexChar(aChar
& 0xF, third
, halfGap
, aDrawTarget
, atlas
, color
, aMat
);
493 // The draw target transform was changed, so it must be restored to
494 // the original value.
495 aDrawTarget
.SetTransform(tempMat
);
501 gfxFontMissingGlyphs::GetDesiredMinWidth(uint32_t aChar
,
502 uint32_t aAppUnitsPerDevPixel
)
505 * The minimum desired width for a missing-glyph glyph box. I've laid it out
506 * like this so you can see what goes where.
508 Float width
= BOX_HORIZONTAL_INSET
+ BOX_BORDER_WIDTH
+ HEX_CHAR_GAP
+
509 MINIFONT_WIDTH
+ HEX_CHAR_GAP
+ MINIFONT_WIDTH
+
510 ((aChar
< 0x10000) ? 0 : HEX_CHAR_GAP
+ MINIFONT_WIDTH
) +
511 HEX_CHAR_GAP
+ BOX_BORDER_WIDTH
+ BOX_HORIZONTAL_INSET
;
512 // Note that this will give us floating-point division, so the width will
513 // -not- be snapped to integer multiples of its basic pixel value
514 width
*= Float(AppUnitsPerCSSPixel()) / aAppUnitsPerDevPixel
;