Bug 1492908 [wpt PR 13122] - Update wpt metadata, a=testonly
[gecko.git] / gfx / thebes / gfxFontMissingGlyphs.cpp
blob7fb157afac74d9666db67cdb30dd8199e74cf1a7
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"
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"
18 using namespace mozilla;
19 using namespace mozilla::gfx;
21 #define X 255
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,
29 #undef X
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 +---------+ +-------------+
37 | | | |
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 |
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 +---------+ +-------------+
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;
57 /**
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;
62 /**
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;
70 /**
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;
83 /**
84 * Generates a new colored mini-font atlas from the mini-font mask.
86 static bool
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) {
94 return false;
97 if (!gGlyphMask) {
98 gGlyphMask = gGlyphDrawTarget->CreateSourceSurfaceFromData(
99 const_cast<uint8_t*>(gMiniFontData), IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT),
100 MINIFONT_WIDTH * 16, SurfaceFormat::A8);
101 if (!gGlyphMask) {
102 return false;
105 gGlyphDrawTarget->MaskSurface(ColorPattern(aColor), gGlyphMask, Point(0, 0),
106 DrawOptions(1.0f, CompositionOp::OP_SOURCE));
107 gGlyphAtlas = gGlyphDrawTarget->Snapshot();
108 if (!gGlyphAtlas) {
109 return false;
111 gGlyphColor = aColor;
112 return true;
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);
126 return nullptr;
130 * Clear any cached glyph atlas resources.
132 static void
133 PurgeGlyphAtlas()
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>
144 public:
145 explicit WRUserData(layers::WebRenderLayerManager* aManager);
147 ~WRUserData();
149 static void
150 Assign(layers::WebRenderLayerManager* aManager)
152 if (!aManager->HasUserData(&sWRUserDataKey)) {
153 aManager->SetUserData(&sWRUserDataKey, new WRUserData(aManager));
157 void
158 Remove()
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);
186 if (!dt) {
187 return nullptr;
189 if (aMat) {
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);
205 if (!mask) {
206 return nullptr;
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.
215 static void
216 PurgeWRGlyphAtlas()
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]) {
225 uint32_t handle =
226 (uint32_t)(uintptr_t)gWRGlyphAtlas[i]->GetUserData(
227 reinterpret_cast<UserDataKey*>(manager));
228 if (handle) {
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)
247 : mManager(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.
256 if (isInList()) {
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)
268 uint32_t key = 0;
269 // Encode orientation in the key.
270 if (aMat) {
271 if (aMat->_11 == 0) {
272 key |= 4 | (aMat->_12 < 0 ? 1 : 0) | (aMat->_21 < 0 ? 2 : 0);
273 } else {
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];
279 if (!atlas) {
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();
290 if (!dataSurface) {
291 return nullptr;
293 DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ);
294 if (!map.IsMapped()) {
295 return nullptr;
297 // Transfer the data and get an image key for it.
298 Maybe<wr::ImageKey> result =
299 tdt->DefineImage(atlas->GetSize(),
300 map.GetStride(),
301 atlas->GetFormat(),
302 map.GetData());
303 if (!result.isSome()) {
304 return nullptr;
306 // Assign the image key to the atlas.
307 atlas->AddUserData(reinterpret_cast<UserDataKey*>(manager),
308 (void*)(uintptr_t)result.value().mHandle,
309 nullptr);
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();
317 static void
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();
328 wr::ImageKey key = {
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);
335 if (aMat) {
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.
350 tdt->PushImage(key,
351 wr::ToLayoutRect(bounds),
352 wr::ToLayoutRect(dest),
353 wr::ImageRendering::Pixelated,
354 wr::ToColorF(aColor));
355 } else {
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
358 // here as well.
359 aDrawTarget.DrawSurface(aAtlas,
360 dest,
361 Rect(aDigit * MINIFONT_WIDTH, 0, MINIFONT_WIDTH, MINIFONT_HEIGHT),
362 DrawSurfaceOptions(SamplingFilter::POINT),
363 DrawOptions(aColor.a, CompositionOp::OP_OVER, AntialiasMode::NONE));
367 void
368 gfxFontMissingGlyphs::Purge()
370 PurgeGlyphAtlas();
371 PurgeWRGlyphAtlas();
374 #else // MOZ_GFX_OPTIMIZE_MOBILE
376 void
377 gfxFontMissingGlyphs::Purge()
381 #endif
383 void
384 gfxFontMissingGlyphs::Shutdown()
386 Purge();
389 void
390 gfxFontMissingGlyphs::DrawMissingGlyph(uint32_t aChar,
391 const Rect& aRect,
392 DrawTarget& aDrawTarget,
393 const Pattern& aPattern,
394 uint32_t aAppUnitsPerDevPixel,
395 const Matrix* aMat)
397 Rect rect(aRect);
398 // If there is an orientation transform, reorient the bounding rect.
399 if (aMat) {
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);
425 #else
426 StrokeOptions strokeOptions(BOX_BORDER_WIDTH);
427 aDrawTarget.StrokeRect(borderStrokeRect, adjustedColor, strokeOptions);
428 #endif
431 #ifndef MOZ_GFX_OPTIMIZE_MOBILE
432 RefPtr<SourceSurface> atlas =
433 aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT ?
434 GetWRGlyphAtlas(aDrawTarget, aMat) :
435 GetGlyphAtlas(color);
436 if (!atlas) {
437 return;
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);
449 Matrix tempMat;
450 if (aMat) {
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);
456 aMat = &tempMat;
457 } else {
458 // Otherwise, scale and translate the draw target transform assuming it
459 // supports that.
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);
476 } else {
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);
492 if (!aMat) {
493 // The draw target transform was changed, so it must be restored to
494 // the original value.
495 aDrawTarget.SetTransform(tempMat);
497 #endif
500 Float
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;
515 return width;