summaryrefslogtreecommitdiffstats
path: root/gfx/thebes
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /gfx/thebes
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/thebes')
-rw-r--r--gfx/thebes/AllOfDcomp.h40
-rw-r--r--gfx/thebes/CJKCompatSVS.cpp2048
-rw-r--r--gfx/thebes/COLRFonts.cpp2673
-rw-r--r--gfx/thebes/COLRFonts.h138
-rw-r--r--gfx/thebes/D3D11Checks.cpp490
-rw-r--r--gfx/thebes/D3D11Checks.h49
-rw-r--r--gfx/thebes/DeviceManagerDx.cpp1443
-rw-r--r--gfx/thebes/DeviceManagerDx.h217
-rw-r--r--gfx/thebes/DisplayConfigWindows.cpp92
-rw-r--r--gfx/thebes/DisplayConfigWindows.h34
-rw-r--r--gfx/thebes/DrawMode.h26
-rw-r--r--gfx/thebes/PrintPromise.h18
-rw-r--r--gfx/thebes/PrintTarget.cpp200
-rw-r--r--gfx/thebes/PrintTarget.h153
-rw-r--r--gfx/thebes/PrintTargetCG.h50
-rw-r--r--gfx/thebes/PrintTargetCG.mm277
-rw-r--r--gfx/thebes/PrintTargetPDF.cpp94
-rw-r--r--gfx/thebes/PrintTargetPDF.h38
-rw-r--r--gfx/thebes/PrintTargetRecording.cpp111
-rw-r--r--gfx/thebes/PrintTargetRecording.h39
-rw-r--r--gfx/thebes/PrintTargetSkPDF.cpp126
-rw-r--r--gfx/thebes/PrintTargetSkPDF.h70
-rw-r--r--gfx/thebes/PrintTargetThebes.cpp98
-rw-r--r--gfx/thebes/PrintTargetThebes.h55
-rw-r--r--gfx/thebes/PrintTargetWindows.cpp120
-rw-r--r--gfx/thebes/PrintTargetWindows.h41
-rw-r--r--gfx/thebes/SharedFontList-impl.h386
-rw-r--r--gfx/thebes/SharedFontList.cpp1361
-rw-r--r--gfx/thebes/SharedFontList.h396
-rw-r--r--gfx/thebes/SkMemoryReporter.cpp26
-rw-r--r--gfx/thebes/SkMemoryReporter.h29
-rw-r--r--gfx/thebes/SoftwareVsyncSource.cpp143
-rw-r--r--gfx/thebes/SoftwareVsyncSource.h53
-rw-r--r--gfx/thebes/StandardFonts-linux.inc494
-rw-r--r--gfx/thebes/StandardFonts-macos.inc295
-rw-r--r--gfx/thebes/StandardFonts-win10.inc201
-rw-r--r--gfx/thebes/ThebesRLBox.h35
-rw-r--r--gfx/thebes/ThebesRLBoxTypes.h17
-rw-r--r--gfx/thebes/VsyncSource.cpp160
-rw-r--r--gfx/thebes/VsyncSource.h119
-rw-r--r--gfx/thebes/XlibDisplay.cpp40
-rw-r--r--gfx/thebes/XlibDisplay.h41
-rw-r--r--gfx/thebes/cairo-xlib-utils.h115
-rw-r--r--gfx/thebes/d3dkmtQueryStatistics.h173
-rw-r--r--gfx/thebes/genLanguageTagList.pl86
-rw-r--r--gfx/thebes/gencjkcisvs.py88
-rw-r--r--gfx/thebes/gfx2DGlue.h118
-rw-r--r--gfx/thebes/gfxASurface.cpp503
-rw-r--r--gfx/thebes/gfxASurface.h184
-rw-r--r--gfx/thebes/gfxAlphaRecovery.cpp51
-rw-r--r--gfx/thebes/gfxAlphaRecovery.h111
-rw-r--r--gfx/thebes/gfxAlphaRecoverySSE2.cpp234
-rw-r--r--gfx/thebes/gfxAndroidPlatform.cpp369
-rw-r--r--gfx/thebes/gfxAndroidPlatform.h58
-rw-r--r--gfx/thebes/gfxBaseSharedMemorySurface.cpp10
-rw-r--r--gfx/thebes/gfxBaseSharedMemorySurface.h165
-rw-r--r--gfx/thebes/gfxBlur.cpp1224
-rw-r--r--gfx/thebes/gfxBlur.h203
-rw-r--r--gfx/thebes/gfxColor.h62
-rw-r--r--gfx/thebes/gfxContext.cpp603
-rw-r--r--gfx/thebes/gfxContext.h811
-rw-r--r--gfx/thebes/gfxCoreTextShaper.cpp651
-rw-r--r--gfx/thebes/gfxCoreTextShaper.h69
-rw-r--r--gfx/thebes/gfxDWriteCommon.cpp204
-rw-r--r--gfx/thebes/gfxDWriteCommon.h161
-rw-r--r--gfx/thebes/gfxDWriteFontList.cpp2623
-rw-r--r--gfx/thebes/gfxDWriteFontList.h504
-rw-r--r--gfx/thebes/gfxDWriteFonts.cpp847
-rw-r--r--gfx/thebes/gfxDWriteFonts.h117
-rw-r--r--gfx/thebes/gfxDrawable.cpp216
-rw-r--r--gfx/thebes/gfxDrawable.h170
-rw-r--r--gfx/thebes/gfxEnv.h139
-rw-r--r--gfx/thebes/gfxFT2FontBase.cpp824
-rw-r--r--gfx/thebes/gfxFT2FontBase.h152
-rw-r--r--gfx/thebes/gfxFT2FontList.cpp1895
-rw-r--r--gfx/thebes/gfxFT2FontList.h262
-rw-r--r--gfx/thebes/gfxFT2Fonts.cpp243
-rw-r--r--gfx/thebes/gfxFT2Fonts.h81
-rw-r--r--gfx/thebes/gfxFT2Utils.cpp157
-rw-r--r--gfx/thebes/gfxFT2Utils.h76
-rw-r--r--gfx/thebes/gfxFailure.h24
-rw-r--r--gfx/thebes/gfxFcPlatformFontList.cpp2922
-rw-r--r--gfx/thebes/gfxFcPlatformFontList.h436
-rw-r--r--gfx/thebes/gfxFont.cpp4614
-rw-r--r--gfx/thebes/gfxFont.h2375
-rw-r--r--gfx/thebes/gfxFontConstants.h185
-rw-r--r--gfx/thebes/gfxFontEntry.cpp2203
-rw-r--r--gfx/thebes/gfxFontEntry.h1253
-rw-r--r--gfx/thebes/gfxFontFeatures.cpp47
-rw-r--r--gfx/thebes/gfxFontFeatures.h103
-rw-r--r--gfx/thebes/gfxFontInfoLoader.cpp318
-rw-r--r--gfx/thebes/gfxFontInfoLoader.h214
-rw-r--r--gfx/thebes/gfxFontMissingGlyphs.cpp542
-rw-r--r--gfx/thebes/gfxFontMissingGlyphs.h56
-rw-r--r--gfx/thebes/gfxFontPrefLangList.h35
-rw-r--r--gfx/thebes/gfxFontSrcPrincipal.cpp36
-rw-r--r--gfx/thebes/gfxFontSrcPrincipal.h55
-rw-r--r--gfx/thebes/gfxFontSrcURI.cpp113
-rw-r--r--gfx/thebes/gfxFontSrcURI.h81
-rw-r--r--gfx/thebes/gfxFontUtils.cpp1791
-rw-r--r--gfx/thebes/gfxFontUtils.h1467
-rw-r--r--gfx/thebes/gfxFontVariations.h41
-rw-r--r--gfx/thebes/gfxGDIFont.cpp553
-rw-r--r--gfx/thebes/gfxGDIFont.h91
-rw-r--r--gfx/thebes/gfxGDIFontList.cpp1107
-rw-r--r--gfx/thebes/gfxGDIFontList.h356
-rw-r--r--gfx/thebes/gfxGlyphExtents.cpp150
-rw-r--r--gfx/thebes/gfxGlyphExtents.h172
-rw-r--r--gfx/thebes/gfxGradientCache.cpp285
-rw-r--r--gfx/thebes/gfxGradientCache.h32
-rw-r--r--gfx/thebes/gfxGraphiteShaper.cpp513
-rw-r--r--gfx/thebes/gfxGraphiteShaper.h75
-rw-r--r--gfx/thebes/gfxHarfBuzzShaper.cpp1724
-rw-r--r--gfx/thebes/gfxHarfBuzzShaper.h203
-rw-r--r--gfx/thebes/gfxImageSurface.cpp326
-rw-r--r--gfx/thebes/gfxImageSurface.h194
-rw-r--r--gfx/thebes/gfxLanguageTagList.cpp7901
-rw-r--r--gfx/thebes/gfxLineSegment.h78
-rw-r--r--gfx/thebes/gfxMacFont.cpp581
-rw-r--r--gfx/thebes/gfxMacFont.h91
-rw-r--r--gfx/thebes/gfxMacPlatformFontList.h266
-rw-r--r--gfx/thebes/gfxMacPlatformFontList.mm2242
-rw-r--r--gfx/thebes/gfxMacUtils.cpp37
-rw-r--r--gfx/thebes/gfxMacUtils.h20
-rw-r--r--gfx/thebes/gfxMathTable.cpp200
-rw-r--r--gfx/thebes/gfxMathTable.h152
-rw-r--r--gfx/thebes/gfxMatrix.h13
-rw-r--r--gfx/thebes/gfxOTSUtils.h178
-rw-r--r--gfx/thebes/gfxPattern.cpp203
-rw-r--r--gfx/thebes/gfxPattern.h77
-rw-r--r--gfx/thebes/gfxPlatform.cpp3902
-rw-r--r--gfx/thebes/gfxPlatform.h1034
-rw-r--r--gfx/thebes/gfxPlatformFontList.cpp3174
-rw-r--r--gfx/thebes/gfxPlatformFontList.h1067
-rw-r--r--gfx/thebes/gfxPlatformGtk.cpp1031
-rw-r--r--gfx/thebes/gfxPlatformGtk.h81
-rw-r--r--gfx/thebes/gfxPlatformMac.cpp1030
-rw-r--r--gfx/thebes/gfxPlatformMac.h101
-rw-r--r--gfx/thebes/gfxPlatformWorker.cpp70
-rw-r--r--gfx/thebes/gfxPlatformWorker.h44
-rw-r--r--gfx/thebes/gfxPoint.h14
-rw-r--r--gfx/thebes/gfxQuad.h53
-rw-r--r--gfx/thebes/gfxQuartzNativeDrawing.cpp86
-rw-r--r--gfx/thebes/gfxQuartzNativeDrawing.h71
-rw-r--r--gfx/thebes/gfxQuartzSurface.cpp82
-rw-r--r--gfx/thebes/gfxQuartzSurface.h39
-rw-r--r--gfx/thebes/gfxQuaternion.h117
-rw-r--r--gfx/thebes/gfxRect.h14
-rw-r--r--gfx/thebes/gfxSVGGlyphs.cpp466
-rw-r--r--gfx/thebes/gfxSVGGlyphs.h239
-rw-r--r--gfx/thebes/gfxScriptItemizer.cpp256
-rw-r--r--gfx/thebes/gfxScriptItemizer.h100
-rw-r--r--gfx/thebes/gfxSharedImageSurface.h27
-rw-r--r--gfx/thebes/gfxSkipChars.cpp146
-rw-r--r--gfx/thebes/gfxSkipChars.h253
-rw-r--r--gfx/thebes/gfxTextRun.cpp3852
-rw-r--r--gfx/thebes/gfxTextRun.h1535
-rw-r--r--gfx/thebes/gfxTypes.h163
-rw-r--r--gfx/thebes/gfxUserFontSet.cpp1408
-rw-r--r--gfx/thebes/gfxUserFontSet.h792
-rw-r--r--gfx/thebes/gfxUtils.cpp1820
-rw-r--r--gfx/thebes/gfxUtils.h626
-rw-r--r--gfx/thebes/gfxWindowsNativeDrawing.cpp302
-rw-r--r--gfx/thebes/gfxWindowsNativeDrawing.h107
-rw-r--r--gfx/thebes/gfxWindowsPlatform.cpp1956
-rw-r--r--gfx/thebes/gfxWindowsPlatform.h261
-rw-r--r--gfx/thebes/gfxWindowsSurface.cpp122
-rw-r--r--gfx/thebes/gfxWindowsSurface.h54
-rw-r--r--gfx/thebes/gfxXlibSurface.cpp412
-rw-r--r--gfx/thebes/gfxXlibSurface.h104
-rw-r--r--gfx/thebes/moz.build299
-rw-r--r--gfx/thebes/nsIFontLoadCompleteCallback.idl13
172 files changed, 91149 insertions, 0 deletions
diff --git a/gfx/thebes/AllOfDcomp.h b/gfx/thebes/AllOfDcomp.h
new file mode 100644
index 0000000000..bd4448b68b
--- /dev/null
+++ b/gfx/thebes/AllOfDcomp.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_gfx_AllOfDcomp_h
+#define mozilla_gfx_AllOfDcomp_h
+
+// Getting everything that we need in dcomp.h defined means messing with some
+// defines.
+
+#if (_WIN32_WINNT < _WIN32_WINNT_WIN10)
+
+# define XSTR(x) STR(x)
+# define STR(x) #x
+// clang-format off
+
+# pragma message "IDCompositionFilterEffect in dcomp.h requires _WIN32_WINNT >= _WIN32_WINNT_WIN10."
+// Pedantically, it actually requires _WIN32_WINNT_WINTHRESHOLD, but that's the
+// same as _WIN32_WINNT_WIN10.
+
+# pragma message "Forcing NTDDI_VERSION " XSTR(NTDDI_VERSION) " -> " XSTR(NTDDI_WIN10)
+# undef NTDDI_VERSION
+# define NTDDI_VERSION NTDDI_WIN10
+
+# pragma message "Forcing _WIN32_WINNT " XSTR(_WIN32_WINNT) " -> " XSTR(_WIN32_WINNT_WIN10)
+# undef _WIN32_WINNT
+# define _WIN32_WINNT _WIN32_WINNT_WIN10
+
+// clang-format on
+# undef STR
+# undef XSTR
+
+#endif
+
+// -
+
+#include <dcomp.h>
+
+#endif // mozilla_gfx_AllOfDcomp_h
diff --git a/gfx/thebes/CJKCompatSVS.cpp b/gfx/thebes/CJKCompatSVS.cpp
new file mode 100644
index 0000000000..553dadcee8
--- /dev/null
+++ b/gfx/thebes/CJKCompatSVS.cpp
@@ -0,0 +1,2048 @@
+// Generated by gencjkcisvs.py. Do not edit.
+
+#include <stdint.h>
+
+#define U16(v) (((v) >> 8) & 0xFF), ((v)&0xFF)
+#define U24(v) (((v) >> 16) & 0xFF), (((v) >> 8) & 0xFF), ((v)&0xFF)
+#define U32(v) \
+ (((v) >> 24) & 0xFF), (((v) >> 16) & 0xFF), (((v) >> 8) & 0xFF), ((v)&0xFF)
+#define GLYPH(v) U16(v >= 0x2F800 ? (v) - (0x2F800 - 0xFB00) : (v))
+
+// Fallback mappings for CJK Compatibility Ideographs Standardized Variants
+// taken from StandardizedVariants-6.3.0.txt.
+// Using OpenType format 14 cmap subtable structure to reuse the lookup code
+// for fonts. The glyphID field is used to store the corresponding codepoints
+// CJK Compatibility Ideographs. To fit codepoints into the 16-bit glyphID
+// field, CJK Compatibility Ideographs Supplement (U+2F800..U+2FA1F) will be
+// mapped to 0xFB00..0xFD1F.
+extern const uint8_t sCJKCompatSVSTable[] = {
+ U16(14), // format
+ U32(5065), // length
+ U32(3), // numVarSelectorRecords
+ U24(0xFE00),
+ U32(0),
+ U32(43), // varSelectorRecord[0]
+ U24(0xFE01),
+ U32(0),
+ U32(4557), // varSelectorRecord[1]
+ U24(0xFE02),
+ U32(0),
+ U32(5001), // varSelectorRecord[2]
+ // 0xFE00
+ U32(902), // numUVSMappings
+ U24(0x349E),
+ GLYPH(0x2F80C),
+ U24(0x34B9),
+ GLYPH(0x2F813),
+ U24(0x34BB),
+ GLYPH(0x2F9CA),
+ U24(0x34DF),
+ GLYPH(0x2F81F),
+ U24(0x3515),
+ GLYPH(0x2F824),
+ U24(0x36EE),
+ GLYPH(0x2F867),
+ U24(0x36FC),
+ GLYPH(0x2F868),
+ U24(0x3781),
+ GLYPH(0x2F876),
+ U24(0x382F),
+ GLYPH(0x2F883),
+ U24(0x3862),
+ GLYPH(0x2F888),
+ U24(0x387C),
+ GLYPH(0x2F88A),
+ U24(0x38C7),
+ GLYPH(0x2F896),
+ U24(0x38E3),
+ GLYPH(0x2F89B),
+ U24(0x391C),
+ GLYPH(0x2F8A2),
+ U24(0x393A),
+ GLYPH(0x2F8A1),
+ U24(0x3A2E),
+ GLYPH(0x2F8C2),
+ U24(0x3A6C),
+ GLYPH(0x2F8C7),
+ U24(0x3AE4),
+ GLYPH(0x2F8D1),
+ U24(0x3B08),
+ GLYPH(0x2F8D0),
+ U24(0x3B19),
+ GLYPH(0x2F8CE),
+ U24(0x3B49),
+ GLYPH(0x2F8DE),
+ U24(0x3B9D),
+ GLYPH(0xFAD2),
+ U24(0x3C18),
+ GLYPH(0x2F8EE),
+ U24(0x3C4E),
+ GLYPH(0x2F8F2),
+ U24(0x3D33),
+ GLYPH(0x2F90A),
+ U24(0x3D96),
+ GLYPH(0x2F916),
+ U24(0x3EAC),
+ GLYPH(0x2F92A),
+ U24(0x3EB8),
+ GLYPH(0x2F92C),
+ U24(0x3F1B),
+ GLYPH(0x2F933),
+ U24(0x3FFC),
+ GLYPH(0x2F93E),
+ U24(0x4008),
+ GLYPH(0x2F93F),
+ U24(0x4018),
+ GLYPH(0xFAD3),
+ U24(0x4039),
+ GLYPH(0xFAD4),
+ U24(0x4046),
+ GLYPH(0x2F94B),
+ U24(0x4096),
+ GLYPH(0x2F94C),
+ U24(0x40E3),
+ GLYPH(0x2F951),
+ U24(0x412F),
+ GLYPH(0x2F958),
+ U24(0x4202),
+ GLYPH(0x2F960),
+ U24(0x4227),
+ GLYPH(0x2F964),
+ U24(0x42A0),
+ GLYPH(0x2F967),
+ U24(0x4301),
+ GLYPH(0x2F96D),
+ U24(0x4334),
+ GLYPH(0x2F971),
+ U24(0x4359),
+ GLYPH(0x2F974),
+ U24(0x43D5),
+ GLYPH(0x2F981),
+ U24(0x43D9),
+ GLYPH(0x2F8D7),
+ U24(0x440B),
+ GLYPH(0x2F984),
+ U24(0x446B),
+ GLYPH(0x2F98E),
+ U24(0x452B),
+ GLYPH(0x2F9A7),
+ U24(0x455D),
+ GLYPH(0x2F9AE),
+ U24(0x4561),
+ GLYPH(0x2F9AF),
+ U24(0x456B),
+ GLYPH(0x2F9B2),
+ U24(0x45D7),
+ GLYPH(0x2F9BF),
+ U24(0x45F9),
+ GLYPH(0x2F9C2),
+ U24(0x4635),
+ GLYPH(0x2F9C8),
+ U24(0x46BE),
+ GLYPH(0x2F9CD),
+ U24(0x46C7),
+ GLYPH(0x2F9CE),
+ U24(0x4995),
+ GLYPH(0x2F9EF),
+ U24(0x49E6),
+ GLYPH(0x2F9F2),
+ U24(0x4A6E),
+ GLYPH(0x2F9F8),
+ U24(0x4A76),
+ GLYPH(0x2F9F9),
+ U24(0x4AB2),
+ GLYPH(0x2F9FC),
+ U24(0x4B33),
+ GLYPH(0x2FA03),
+ U24(0x4BCE),
+ GLYPH(0x2FA08),
+ U24(0x4CCE),
+ GLYPH(0x2FA0D),
+ U24(0x4CED),
+ GLYPH(0x2FA0E),
+ U24(0x4CF8),
+ GLYPH(0x2FA11),
+ U24(0x4D56),
+ GLYPH(0x2FA16),
+ U24(0x4E0D),
+ GLYPH(0xF967),
+ U24(0x4E26),
+ GLYPH(0xFA70),
+ U24(0x4E32),
+ GLYPH(0xF905),
+ U24(0x4E38),
+ GLYPH(0x2F801),
+ U24(0x4E39),
+ GLYPH(0xF95E),
+ U24(0x4E3D),
+ GLYPH(0x2F800),
+ U24(0x4E41),
+ GLYPH(0x2F802),
+ U24(0x4E82),
+ GLYPH(0xF91B),
+ U24(0x4E86),
+ GLYPH(0xF9BA),
+ U24(0x4EAE),
+ GLYPH(0xF977),
+ U24(0x4EC0),
+ GLYPH(0xF9FD),
+ U24(0x4ECC),
+ GLYPH(0x2F819),
+ U24(0x4EE4),
+ GLYPH(0xF9A8),
+ U24(0x4F60),
+ GLYPH(0x2F804),
+ U24(0x4F80),
+ GLYPH(0xFA73),
+ U24(0x4F86),
+ GLYPH(0xF92D),
+ U24(0x4F8B),
+ GLYPH(0xF9B5),
+ U24(0x4FAE),
+ GLYPH(0xFA30),
+ U24(0x4FBB),
+ GLYPH(0x2F806),
+ U24(0x4FBF),
+ GLYPH(0xF965),
+ U24(0x5002),
+ GLYPH(0x2F807),
+ U24(0x502B),
+ GLYPH(0xF9D4),
+ U24(0x507A),
+ GLYPH(0x2F808),
+ U24(0x5099),
+ GLYPH(0x2F809),
+ U24(0x50CF),
+ GLYPH(0x2F80B),
+ U24(0x50DA),
+ GLYPH(0xF9BB),
+ U24(0x50E7),
+ GLYPH(0xFA31),
+ U24(0x5140),
+ GLYPH(0xFA0C),
+ U24(0x5145),
+ GLYPH(0xFA74),
+ U24(0x514D),
+ GLYPH(0xFA32),
+ U24(0x5154),
+ GLYPH(0x2F80F),
+ U24(0x5164),
+ GLYPH(0x2F810),
+ U24(0x5167),
+ GLYPH(0x2F814),
+ U24(0x5168),
+ GLYPH(0xFA72),
+ U24(0x5169),
+ GLYPH(0xF978),
+ U24(0x516D),
+ GLYPH(0xF9D1),
+ U24(0x5177),
+ GLYPH(0x2F811),
+ U24(0x5180),
+ GLYPH(0xFA75),
+ U24(0x518D),
+ GLYPH(0x2F815),
+ U24(0x5192),
+ GLYPH(0x2F8D2),
+ U24(0x5195),
+ GLYPH(0x2F8D3),
+ U24(0x5197),
+ GLYPH(0x2F817),
+ U24(0x51A4),
+ GLYPH(0x2F818),
+ U24(0x51AC),
+ GLYPH(0x2F81A),
+ U24(0x51B5),
+ GLYPH(0xFA71),
+ U24(0x51B7),
+ GLYPH(0xF92E),
+ U24(0x51C9),
+ GLYPH(0xF979),
+ U24(0x51CC),
+ GLYPH(0xF955),
+ U24(0x51DC),
+ GLYPH(0xF954),
+ U24(0x51DE),
+ GLYPH(0xFA15),
+ U24(0x51F5),
+ GLYPH(0x2F81D),
+ U24(0x5203),
+ GLYPH(0x2F81E),
+ U24(0x5207),
+ GLYPH(0xFA00),
+ U24(0x5217),
+ GLYPH(0xF99C),
+ U24(0x5229),
+ GLYPH(0xF9DD),
+ U24(0x523A),
+ GLYPH(0xF9FF),
+ U24(0x523B),
+ GLYPH(0x2F820),
+ U24(0x5246),
+ GLYPH(0x2F821),
+ U24(0x5272),
+ GLYPH(0x2F822),
+ U24(0x5277),
+ GLYPH(0x2F823),
+ U24(0x5289),
+ GLYPH(0xF9C7),
+ U24(0x529B),
+ GLYPH(0xF98A),
+ U24(0x52A3),
+ GLYPH(0xF99D),
+ U24(0x52B3),
+ GLYPH(0x2F992),
+ U24(0x52C7),
+ GLYPH(0xFA76),
+ U24(0x52C9),
+ GLYPH(0xFA33),
+ U24(0x52D2),
+ GLYPH(0xF952),
+ U24(0x52DE),
+ GLYPH(0xF92F),
+ U24(0x52E4),
+ GLYPH(0xFA34),
+ U24(0x52F5),
+ GLYPH(0xF97F),
+ U24(0x52FA),
+ GLYPH(0xFA77),
+ U24(0x5305),
+ GLYPH(0x2F829),
+ U24(0x5306),
+ GLYPH(0x2F82A),
+ U24(0x5317),
+ GLYPH(0xF963),
+ U24(0x533F),
+ GLYPH(0xF9EB),
+ U24(0x5349),
+ GLYPH(0x2F82C),
+ U24(0x5351),
+ GLYPH(0xFA35),
+ U24(0x535A),
+ GLYPH(0x2F82E),
+ U24(0x5373),
+ GLYPH(0x2F82F),
+ U24(0x5375),
+ GLYPH(0xF91C),
+ U24(0x537D),
+ GLYPH(0x2F830),
+ U24(0x537F),
+ GLYPH(0x2F831),
+ U24(0x53C3),
+ GLYPH(0xF96B),
+ U24(0x53CA),
+ GLYPH(0x2F836),
+ U24(0x53DF),
+ GLYPH(0x2F837),
+ U24(0x53E5),
+ GLYPH(0xF906),
+ U24(0x53EB),
+ GLYPH(0x2F839),
+ U24(0x53F1),
+ GLYPH(0x2F83A),
+ U24(0x5406),
+ GLYPH(0x2F83B),
+ U24(0x540F),
+ GLYPH(0xF9DE),
+ U24(0x541D),
+ GLYPH(0xF9ED),
+ U24(0x5438),
+ GLYPH(0x2F83D),
+ U24(0x5442),
+ GLYPH(0xF980),
+ U24(0x5448),
+ GLYPH(0x2F83E),
+ U24(0x5468),
+ GLYPH(0x2F83F),
+ U24(0x549E),
+ GLYPH(0x2F83C),
+ U24(0x54A2),
+ GLYPH(0x2F840),
+ U24(0x54BD),
+ GLYPH(0xF99E),
+ U24(0x54F6),
+ GLYPH(0x2F841),
+ U24(0x5510),
+ GLYPH(0x2F842),
+ U24(0x5553),
+ GLYPH(0x2F843),
+ U24(0x5555),
+ GLYPH(0xFA79),
+ U24(0x5563),
+ GLYPH(0x2F844),
+ U24(0x5584),
+ GLYPH(0x2F845),
+ U24(0x5587),
+ GLYPH(0xF90B),
+ U24(0x5599),
+ GLYPH(0xFA7A),
+ U24(0x559D),
+ GLYPH(0xFA36),
+ U24(0x55AB),
+ GLYPH(0x2F848),
+ U24(0x55B3),
+ GLYPH(0x2F849),
+ U24(0x55C0),
+ GLYPH(0xFA0D),
+ U24(0x55C2),
+ GLYPH(0x2F84A),
+ U24(0x55E2),
+ GLYPH(0xFA7B),
+ U24(0x5606),
+ GLYPH(0xFA37),
+ U24(0x5651),
+ GLYPH(0x2F84E),
+ U24(0x5668),
+ GLYPH(0xFA38),
+ U24(0x5674),
+ GLYPH(0x2F84F),
+ U24(0x56F9),
+ GLYPH(0xF9A9),
+ U24(0x5716),
+ GLYPH(0x2F84B),
+ U24(0x5717),
+ GLYPH(0x2F84D),
+ U24(0x578B),
+ GLYPH(0x2F855),
+ U24(0x57CE),
+ GLYPH(0x2F852),
+ U24(0x57F4),
+ GLYPH(0x2F853),
+ U24(0x580D),
+ GLYPH(0x2F854),
+ U24(0x5831),
+ GLYPH(0x2F857),
+ U24(0x5832),
+ GLYPH(0x2F856),
+ U24(0x5840),
+ GLYPH(0xFA39),
+ U24(0x585A),
+ GLYPH(0xFA10),
+ U24(0x585E),
+ GLYPH(0xF96C),
+ U24(0x58A8),
+ GLYPH(0xFA3A),
+ U24(0x58AC),
+ GLYPH(0x2F858),
+ U24(0x58B3),
+ GLYPH(0xFA7D),
+ U24(0x58D8),
+ GLYPH(0xF94A),
+ U24(0x58DF),
+ GLYPH(0xF942),
+ U24(0x58EE),
+ GLYPH(0x2F851),
+ U24(0x58F2),
+ GLYPH(0x2F85A),
+ U24(0x58F7),
+ GLYPH(0x2F85B),
+ U24(0x5906),
+ GLYPH(0x2F85C),
+ U24(0x591A),
+ GLYPH(0x2F85D),
+ U24(0x5922),
+ GLYPH(0x2F85E),
+ U24(0x5944),
+ GLYPH(0xFA7E),
+ U24(0x5948),
+ GLYPH(0xF90C),
+ U24(0x5951),
+ GLYPH(0xF909),
+ U24(0x5954),
+ GLYPH(0xFA7F),
+ U24(0x5962),
+ GLYPH(0x2F85F),
+ U24(0x5973),
+ GLYPH(0xF981),
+ U24(0x59D8),
+ GLYPH(0x2F865),
+ U24(0x59EC),
+ GLYPH(0x2F862),
+ U24(0x5A1B),
+ GLYPH(0x2F863),
+ U24(0x5A27),
+ GLYPH(0x2F864),
+ U24(0x5A62),
+ GLYPH(0xFA80),
+ U24(0x5A66),
+ GLYPH(0x2F866),
+ U24(0x5AB5),
+ GLYPH(0x2F986),
+ U24(0x5B08),
+ GLYPH(0x2F869),
+ U24(0x5B28),
+ GLYPH(0xFA81),
+ U24(0x5B3E),
+ GLYPH(0x2F86A),
+ U24(0x5B85),
+ GLYPH(0xFA04),
+ U24(0x5BC3),
+ GLYPH(0x2F86D),
+ U24(0x5BD8),
+ GLYPH(0x2F86E),
+ U24(0x5BE7),
+ GLYPH(0xF95F),
+ U24(0x5BEE),
+ GLYPH(0xF9BC),
+ U24(0x5BF3),
+ GLYPH(0x2F870),
+ U24(0x5BFF),
+ GLYPH(0x2F872),
+ U24(0x5C06),
+ GLYPH(0x2F873),
+ U24(0x5C22),
+ GLYPH(0x2F875),
+ U24(0x5C3F),
+ GLYPH(0xF9BD),
+ U24(0x5C60),
+ GLYPH(0x2F877),
+ U24(0x5C62),
+ GLYPH(0xF94B),
+ U24(0x5C64),
+ GLYPH(0xFA3B),
+ U24(0x5C65),
+ GLYPH(0xF9DF),
+ U24(0x5C6E),
+ GLYPH(0xFA3C),
+ U24(0x5C8D),
+ GLYPH(0x2F87A),
+ U24(0x5CC0),
+ GLYPH(0x2F879),
+ U24(0x5D19),
+ GLYPH(0xF9D5),
+ U24(0x5D43),
+ GLYPH(0x2F87C),
+ U24(0x5D50),
+ GLYPH(0xF921),
+ U24(0x5D6B),
+ GLYPH(0x2F87F),
+ U24(0x5D6E),
+ GLYPH(0x2F87E),
+ U24(0x5D7C),
+ GLYPH(0x2F880),
+ U24(0x5DB2),
+ GLYPH(0x2F9F4),
+ U24(0x5DBA),
+ GLYPH(0xF9AB),
+ U24(0x5DE1),
+ GLYPH(0x2F881),
+ U24(0x5DE2),
+ GLYPH(0x2F882),
+ U24(0x5DFD),
+ GLYPH(0x2F884),
+ U24(0x5E28),
+ GLYPH(0x2F885),
+ U24(0x5E3D),
+ GLYPH(0x2F886),
+ U24(0x5E69),
+ GLYPH(0x2F887),
+ U24(0x5E74),
+ GLYPH(0xF98E),
+ U24(0x5EA6),
+ GLYPH(0xFA01),
+ U24(0x5EB0),
+ GLYPH(0x2F88B),
+ U24(0x5EB3),
+ GLYPH(0x2F88C),
+ U24(0x5EB6),
+ GLYPH(0x2F88D),
+ U24(0x5EC9),
+ GLYPH(0xF9A2),
+ U24(0x5ECA),
+ GLYPH(0xF928),
+ U24(0x5ED2),
+ GLYPH(0xFA82),
+ U24(0x5ED3),
+ GLYPH(0xFA0B),
+ U24(0x5ED9),
+ GLYPH(0xFA83),
+ U24(0x5EEC),
+ GLYPH(0xF982),
+ U24(0x5EFE),
+ GLYPH(0x2F890),
+ U24(0x5F04),
+ GLYPH(0xF943),
+ U24(0x5F22),
+ GLYPH(0x2F894),
+ U24(0x5F53),
+ GLYPH(0x2F874),
+ U24(0x5F62),
+ GLYPH(0x2F899),
+ U24(0x5F69),
+ GLYPH(0xFA84),
+ U24(0x5F6B),
+ GLYPH(0x2F89A),
+ U24(0x5F8B),
+ GLYPH(0xF9D8),
+ U24(0x5F9A),
+ GLYPH(0x2F89C),
+ U24(0x5FA9),
+ GLYPH(0xF966),
+ U24(0x5FAD),
+ GLYPH(0xFA85),
+ U24(0x5FCD),
+ GLYPH(0x2F89D),
+ U24(0x5FD7),
+ GLYPH(0x2F89E),
+ U24(0x5FF5),
+ GLYPH(0xF9A3),
+ U24(0x5FF9),
+ GLYPH(0x2F89F),
+ U24(0x6012),
+ GLYPH(0xF960),
+ U24(0x601C),
+ GLYPH(0xF9AC),
+ U24(0x6075),
+ GLYPH(0xFA6B),
+ U24(0x6081),
+ GLYPH(0x2F8A0),
+ U24(0x6094),
+ GLYPH(0xFA3D),
+ U24(0x60C7),
+ GLYPH(0x2F8A5),
+ U24(0x60D8),
+ GLYPH(0xFA86),
+ U24(0x60E1),
+ GLYPH(0xF9B9),
+ U24(0x6108),
+ GLYPH(0xFA88),
+ U24(0x6144),
+ GLYPH(0xF9D9),
+ U24(0x6148),
+ GLYPH(0x2F8A6),
+ U24(0x614C),
+ GLYPH(0x2F8A7),
+ U24(0x614E),
+ GLYPH(0xFA87),
+ U24(0x6160),
+ GLYPH(0xFA8A),
+ U24(0x6168),
+ GLYPH(0xFA3E),
+ U24(0x617A),
+ GLYPH(0x2F8AA),
+ U24(0x618E),
+ GLYPH(0xFA3F),
+ U24(0x6190),
+ GLYPH(0xF98F),
+ U24(0x61A4),
+ GLYPH(0x2F8AD),
+ U24(0x61AF),
+ GLYPH(0x2F8AE),
+ U24(0x61B2),
+ GLYPH(0x2F8AC),
+ U24(0x61DE),
+ GLYPH(0x2F8AF),
+ U24(0x61F2),
+ GLYPH(0xFA40),
+ U24(0x61F6),
+ GLYPH(0xF90D),
+ U24(0x6200),
+ GLYPH(0xF990),
+ U24(0x6210),
+ GLYPH(0x2F8B2),
+ U24(0x621B),
+ GLYPH(0x2F8B3),
+ U24(0x622E),
+ GLYPH(0xF9D2),
+ U24(0x6234),
+ GLYPH(0xFA8C),
+ U24(0x625D),
+ GLYPH(0x2F8B4),
+ U24(0x62B1),
+ GLYPH(0x2F8B5),
+ U24(0x62C9),
+ GLYPH(0xF925),
+ U24(0x62CF),
+ GLYPH(0xF95B),
+ U24(0x62D3),
+ GLYPH(0xFA02),
+ U24(0x62D4),
+ GLYPH(0x2F8B6),
+ U24(0x62FC),
+ GLYPH(0x2F8BA),
+ U24(0x62FE),
+ GLYPH(0xF973),
+ U24(0x633D),
+ GLYPH(0x2F8B9),
+ U24(0x6350),
+ GLYPH(0x2F8B7),
+ U24(0x6368),
+ GLYPH(0x2F8BB),
+ U24(0x637B),
+ GLYPH(0xF9A4),
+ U24(0x6383),
+ GLYPH(0x2F8BC),
+ U24(0x63A0),
+ GLYPH(0xF975),
+ U24(0x63A9),
+ GLYPH(0x2F8C1),
+ U24(0x63C4),
+ GLYPH(0xFA8D),
+ U24(0x63C5),
+ GLYPH(0x2F8C0),
+ U24(0x63E4),
+ GLYPH(0x2F8BD),
+ U24(0x641C),
+ GLYPH(0xFA8E),
+ U24(0x6422),
+ GLYPH(0x2F8BF),
+ U24(0x6452),
+ GLYPH(0xFA8F),
+ U24(0x6469),
+ GLYPH(0x2F8C3),
+ U24(0x6477),
+ GLYPH(0x2F8C6),
+ U24(0x647E),
+ GLYPH(0x2F8C4),
+ U24(0x649A),
+ GLYPH(0xF991),
+ U24(0x649D),
+ GLYPH(0x2F8C5),
+ U24(0x64C4),
+ GLYPH(0xF930),
+ U24(0x654F),
+ GLYPH(0xFA41),
+ U24(0x6556),
+ GLYPH(0xFA90),
+ U24(0x656C),
+ GLYPH(0x2F8C9),
+ U24(0x6578),
+ GLYPH(0xF969),
+ U24(0x6599),
+ GLYPH(0xF9BE),
+ U24(0x65C5),
+ GLYPH(0xF983),
+ U24(0x65E2),
+ GLYPH(0xFA42),
+ U24(0x65E3),
+ GLYPH(0x2F8CB),
+ U24(0x6613),
+ GLYPH(0xF9E0),
+ U24(0x6649),
+ GLYPH(0x2F8CD),
+ U24(0x6674),
+ GLYPH(0xFA12),
+ U24(0x6688),
+ GLYPH(0xF9C5),
+ U24(0x6691),
+ GLYPH(0xFA43),
+ U24(0x669C),
+ GLYPH(0x2F8D5),
+ U24(0x66B4),
+ GLYPH(0xFA06),
+ U24(0x66C6),
+ GLYPH(0xF98B),
+ U24(0x66F4),
+ GLYPH(0xF901),
+ U24(0x66F8),
+ GLYPH(0x2F8CC),
+ U24(0x6700),
+ GLYPH(0x2F8D4),
+ U24(0x6717),
+ GLYPH(0xF929),
+ U24(0x671B),
+ GLYPH(0xFA93),
+ U24(0x6721),
+ GLYPH(0x2F8DA),
+ U24(0x674E),
+ GLYPH(0xF9E1),
+ U24(0x6753),
+ GLYPH(0x2F8DC),
+ U24(0x6756),
+ GLYPH(0xFA94),
+ U24(0x675E),
+ GLYPH(0x2F8DB),
+ U24(0x677B),
+ GLYPH(0xF9C8),
+ U24(0x6785),
+ GLYPH(0x2F8E0),
+ U24(0x6797),
+ GLYPH(0xF9F4),
+ U24(0x67F3),
+ GLYPH(0xF9C9),
+ U24(0x67FA),
+ GLYPH(0x2F8DF),
+ U24(0x6817),
+ GLYPH(0xF9DA),
+ U24(0x681F),
+ GLYPH(0x2F8E5),
+ U24(0x6852),
+ GLYPH(0x2F8E1),
+ U24(0x6881),
+ GLYPH(0xF97A),
+ U24(0x6885),
+ GLYPH(0xFA44),
+ U24(0x688E),
+ GLYPH(0x2F8E4),
+ U24(0x68A8),
+ GLYPH(0xF9E2),
+ U24(0x6914),
+ GLYPH(0x2F8E6),
+ U24(0x6942),
+ GLYPH(0x2F8E8),
+ U24(0x69A3),
+ GLYPH(0x2F8E9),
+ U24(0x69EA),
+ GLYPH(0x2F8EA),
+ U24(0x6A02),
+ GLYPH(0xF914),
+ U24(0x6A13),
+ GLYPH(0xF94C),
+ U24(0x6AA8),
+ GLYPH(0x2F8EB),
+ U24(0x6AD3),
+ GLYPH(0xF931),
+ U24(0x6ADB),
+ GLYPH(0x2F8ED),
+ U24(0x6B04),
+ GLYPH(0xF91D),
+ U24(0x6B21),
+ GLYPH(0x2F8EF),
+ U24(0x6B54),
+ GLYPH(0x2F8F1),
+ U24(0x6B72),
+ GLYPH(0x2F8F3),
+ U24(0x6B77),
+ GLYPH(0xF98C),
+ U24(0x6B79),
+ GLYPH(0xFA95),
+ U24(0x6B9F),
+ GLYPH(0x2F8F4),
+ U24(0x6BAE),
+ GLYPH(0xF9A5),
+ U24(0x6BBA),
+ GLYPH(0xF970),
+ U24(0x6BBB),
+ GLYPH(0x2F8F6),
+ U24(0x6C4E),
+ GLYPH(0x2F8FA),
+ U24(0x6C67),
+ GLYPH(0x2F8FE),
+ U24(0x6C88),
+ GLYPH(0xF972),
+ U24(0x6CBF),
+ GLYPH(0x2F8FC),
+ U24(0x6CCC),
+ GLYPH(0xF968),
+ U24(0x6CCD),
+ GLYPH(0x2F8FD),
+ U24(0x6CE5),
+ GLYPH(0xF9E3),
+ U24(0x6D16),
+ GLYPH(0x2F8FF),
+ U24(0x6D1B),
+ GLYPH(0xF915),
+ U24(0x6D1E),
+ GLYPH(0xFA05),
+ U24(0x6D34),
+ GLYPH(0x2F907),
+ U24(0x6D3E),
+ GLYPH(0x2F900),
+ U24(0x6D41),
+ GLYPH(0xF9CA),
+ U24(0x6D69),
+ GLYPH(0x2F903),
+ U24(0x6D6A),
+ GLYPH(0xF92A),
+ U24(0x6D77),
+ GLYPH(0xFA45),
+ U24(0x6D78),
+ GLYPH(0x2F904),
+ U24(0x6D85),
+ GLYPH(0x2F905),
+ U24(0x6DCB),
+ GLYPH(0xF9F5),
+ U24(0x6DDA),
+ GLYPH(0xF94D),
+ U24(0x6DEA),
+ GLYPH(0xF9D6),
+ U24(0x6DF9),
+ GLYPH(0x2F90E),
+ U24(0x6E1A),
+ GLYPH(0xFA46),
+ U24(0x6E2F),
+ GLYPH(0x2F908),
+ U24(0x6E6E),
+ GLYPH(0x2F909),
+ U24(0x6E9C),
+ GLYPH(0xF9CB),
+ U24(0x6EBA),
+ GLYPH(0xF9EC),
+ U24(0x6EC7),
+ GLYPH(0x2F90C),
+ U24(0x6ECB),
+ GLYPH(0xFA99),
+ U24(0x6ED1),
+ GLYPH(0xF904),
+ U24(0x6EDB),
+ GLYPH(0xFA98),
+ U24(0x6F0F),
+ GLYPH(0xF94E),
+ U24(0x6F22),
+ GLYPH(0xFA47),
+ U24(0x6F23),
+ GLYPH(0xF992),
+ U24(0x6F6E),
+ GLYPH(0x2F90F),
+ U24(0x6FC6),
+ GLYPH(0x2F912),
+ U24(0x6FEB),
+ GLYPH(0xF922),
+ U24(0x6FFE),
+ GLYPH(0xF984),
+ U24(0x701B),
+ GLYPH(0x2F915),
+ U24(0x701E),
+ GLYPH(0xFA9B),
+ U24(0x7039),
+ GLYPH(0x2F913),
+ U24(0x704A),
+ GLYPH(0x2F917),
+ U24(0x7070),
+ GLYPH(0x2F835),
+ U24(0x7077),
+ GLYPH(0x2F919),
+ U24(0x707D),
+ GLYPH(0x2F918),
+ U24(0x7099),
+ GLYPH(0xF9FB),
+ U24(0x70AD),
+ GLYPH(0x2F91A),
+ U24(0x70C8),
+ GLYPH(0xF99F),
+ U24(0x70D9),
+ GLYPH(0xF916),
+ U24(0x7145),
+ GLYPH(0x2F91C),
+ U24(0x7149),
+ GLYPH(0xF993),
+ U24(0x716E),
+ GLYPH(0xFA48),
+ U24(0x719C),
+ GLYPH(0x2F91E),
+ U24(0x71CE),
+ GLYPH(0xF9C0),
+ U24(0x71D0),
+ GLYPH(0xF9EE),
+ U24(0x7210),
+ GLYPH(0xF932),
+ U24(0x721B),
+ GLYPH(0xF91E),
+ U24(0x7228),
+ GLYPH(0x2F920),
+ U24(0x722B),
+ GLYPH(0xFA49),
+ U24(0x7235),
+ GLYPH(0xFA9E),
+ U24(0x7250),
+ GLYPH(0x2F922),
+ U24(0x7262),
+ GLYPH(0xF946),
+ U24(0x7280),
+ GLYPH(0x2F924),
+ U24(0x7295),
+ GLYPH(0x2F925),
+ U24(0x72AF),
+ GLYPH(0xFA9F),
+ U24(0x72C0),
+ GLYPH(0xF9FA),
+ U24(0x72FC),
+ GLYPH(0xF92B),
+ U24(0x732A),
+ GLYPH(0xFA16),
+ U24(0x7375),
+ GLYPH(0xF9A7),
+ U24(0x737A),
+ GLYPH(0x2F928),
+ U24(0x7387),
+ GLYPH(0xF961),
+ U24(0x738B),
+ GLYPH(0x2F929),
+ U24(0x73A5),
+ GLYPH(0x2F92B),
+ U24(0x73B2),
+ GLYPH(0xF9AD),
+ U24(0x73DE),
+ GLYPH(0xF917),
+ U24(0x7406),
+ GLYPH(0xF9E4),
+ U24(0x7409),
+ GLYPH(0xF9CC),
+ U24(0x7422),
+ GLYPH(0xFA4A),
+ U24(0x7447),
+ GLYPH(0x2F92E),
+ U24(0x745C),
+ GLYPH(0x2F92F),
+ U24(0x7469),
+ GLYPH(0xF9AE),
+ U24(0x7471),
+ GLYPH(0xFAA1),
+ U24(0x7485),
+ GLYPH(0x2F931),
+ U24(0x7489),
+ GLYPH(0xF994),
+ U24(0x7498),
+ GLYPH(0xF9EF),
+ U24(0x74CA),
+ GLYPH(0x2F932),
+ U24(0x7506),
+ GLYPH(0xFAA2),
+ U24(0x7524),
+ GLYPH(0x2F934),
+ U24(0x753B),
+ GLYPH(0xFAA3),
+ U24(0x753E),
+ GLYPH(0x2F936),
+ U24(0x7559),
+ GLYPH(0xF9CD),
+ U24(0x7565),
+ GLYPH(0xF976),
+ U24(0x7570),
+ GLYPH(0xF962),
+ U24(0x75E2),
+ GLYPH(0xF9E5),
+ U24(0x7610),
+ GLYPH(0x2F93A),
+ U24(0x761D),
+ GLYPH(0xFAA4),
+ U24(0x761F),
+ GLYPH(0xFAA5),
+ U24(0x7642),
+ GLYPH(0xF9C1),
+ U24(0x7669),
+ GLYPH(0xF90E),
+ U24(0x76CA),
+ GLYPH(0xFA17),
+ U24(0x76DB),
+ GLYPH(0xFAA7),
+ U24(0x76E7),
+ GLYPH(0xF933),
+ U24(0x76F4),
+ GLYPH(0xFAA8),
+ U24(0x7701),
+ GLYPH(0xF96D),
+ U24(0x771E),
+ GLYPH(0x2F945),
+ U24(0x771F),
+ GLYPH(0x2F946),
+ U24(0x7740),
+ GLYPH(0xFAAA),
+ U24(0x774A),
+ GLYPH(0xFAA9),
+ U24(0x778B),
+ GLYPH(0x2F94A),
+ U24(0x77A7),
+ GLYPH(0xFA9D),
+ U24(0x784E),
+ GLYPH(0x2F94E),
+ U24(0x786B),
+ GLYPH(0xF9CE),
+ U24(0x788C),
+ GLYPH(0xF93B),
+ U24(0x7891),
+ GLYPH(0xFA4B),
+ U24(0x78CA),
+ GLYPH(0xF947),
+ U24(0x78CC),
+ GLYPH(0xFAAB),
+ U24(0x78FB),
+ GLYPH(0xF964),
+ U24(0x792A),
+ GLYPH(0xF985),
+ U24(0x793C),
+ GLYPH(0xFA18),
+ U24(0x793E),
+ GLYPH(0xFA4C),
+ U24(0x7948),
+ GLYPH(0xFA4E),
+ U24(0x7949),
+ GLYPH(0xFA4D),
+ U24(0x7950),
+ GLYPH(0xFA4F),
+ U24(0x7956),
+ GLYPH(0xFA50),
+ U24(0x795D),
+ GLYPH(0xFA51),
+ U24(0x795E),
+ GLYPH(0xFA19),
+ U24(0x7965),
+ GLYPH(0xFA1A),
+ U24(0x797F),
+ GLYPH(0xF93C),
+ U24(0x798D),
+ GLYPH(0xFA52),
+ U24(0x798E),
+ GLYPH(0xFA53),
+ U24(0x798F),
+ GLYPH(0xFA1B),
+ U24(0x79AE),
+ GLYPH(0xF9B6),
+ U24(0x79CA),
+ GLYPH(0xF995),
+ U24(0x79EB),
+ GLYPH(0x2F957),
+ U24(0x7A1C),
+ GLYPH(0xF956),
+ U24(0x7A40),
+ GLYPH(0xFA54),
+ U24(0x7A4A),
+ GLYPH(0x2F95A),
+ U24(0x7A4F),
+ GLYPH(0x2F95B),
+ U24(0x7A81),
+ GLYPH(0xFA55),
+ U24(0x7AB1),
+ GLYPH(0xFAAC),
+ U24(0x7ACB),
+ GLYPH(0xF9F7),
+ U24(0x7AEE),
+ GLYPH(0x2F95F),
+ U24(0x7B20),
+ GLYPH(0xF9F8),
+ U24(0x7BC0),
+ GLYPH(0xFA56),
+ U24(0x7BC6),
+ GLYPH(0x2F962),
+ U24(0x7BC9),
+ GLYPH(0x2F963),
+ U24(0x7C3E),
+ GLYPH(0xF9A6),
+ U24(0x7C60),
+ GLYPH(0xF944),
+ U24(0x7C7B),
+ GLYPH(0xFAAE),
+ U24(0x7C92),
+ GLYPH(0xF9F9),
+ U24(0x7CBE),
+ GLYPH(0xFA1D),
+ U24(0x7CD2),
+ GLYPH(0x2F966),
+ U24(0x7CD6),
+ GLYPH(0xFA03),
+ U24(0x7CE3),
+ GLYPH(0x2F969),
+ U24(0x7CE7),
+ GLYPH(0xF97B),
+ U24(0x7CE8),
+ GLYPH(0x2F968),
+ U24(0x7D00),
+ GLYPH(0x2F96A),
+ U24(0x7D10),
+ GLYPH(0xF9CF),
+ U24(0x7D22),
+ GLYPH(0xF96A),
+ U24(0x7D2F),
+ GLYPH(0xF94F),
+ U24(0x7D5B),
+ GLYPH(0xFAAF),
+ U24(0x7D63),
+ GLYPH(0x2F96C),
+ U24(0x7DA0),
+ GLYPH(0xF93D),
+ U24(0x7DBE),
+ GLYPH(0xF957),
+ U24(0x7DC7),
+ GLYPH(0x2F96E),
+ U24(0x7DF4),
+ GLYPH(0xF996),
+ U24(0x7E02),
+ GLYPH(0x2F96F),
+ U24(0x7E09),
+ GLYPH(0xFA58),
+ U24(0x7E37),
+ GLYPH(0xF950),
+ U24(0x7E41),
+ GLYPH(0xFA59),
+ U24(0x7E45),
+ GLYPH(0x2F970),
+ U24(0x7F3E),
+ GLYPH(0xFAB1),
+ U24(0x7F72),
+ GLYPH(0xFA5A),
+ U24(0x7F79),
+ GLYPH(0xF9E6),
+ U24(0x7F7A),
+ GLYPH(0x2F976),
+ U24(0x7F85),
+ GLYPH(0xF90F),
+ U24(0x7F95),
+ GLYPH(0x2F978),
+ U24(0x7F9A),
+ GLYPH(0xF9AF),
+ U24(0x7FBD),
+ GLYPH(0xFA1E),
+ U24(0x7FFA),
+ GLYPH(0x2F979),
+ U24(0x8001),
+ GLYPH(0xF934),
+ U24(0x8005),
+ GLYPH(0xFA5B),
+ U24(0x8046),
+ GLYPH(0xF9B0),
+ U24(0x8060),
+ GLYPH(0x2F97D),
+ U24(0x806F),
+ GLYPH(0xF997),
+ U24(0x8070),
+ GLYPH(0x2F97F),
+ U24(0x807E),
+ GLYPH(0xF945),
+ U24(0x808B),
+ GLYPH(0xF953),
+ U24(0x80AD),
+ GLYPH(0x2F8D6),
+ U24(0x80B2),
+ GLYPH(0x2F982),
+ U24(0x8103),
+ GLYPH(0x2F983),
+ U24(0x813E),
+ GLYPH(0x2F985),
+ U24(0x81D8),
+ GLYPH(0xF926),
+ U24(0x81E8),
+ GLYPH(0xF9F6),
+ U24(0x81ED),
+ GLYPH(0xFA5C),
+ U24(0x8201),
+ GLYPH(0x2F893),
+ U24(0x8204),
+ GLYPH(0x2F98C),
+ U24(0x8218),
+ GLYPH(0xFA6D),
+ U24(0x826F),
+ GLYPH(0xF97C),
+ U24(0x8279),
+ GLYPH(0xFA5D),
+ U24(0x828B),
+ GLYPH(0x2F990),
+ U24(0x8291),
+ GLYPH(0x2F98F),
+ U24(0x829D),
+ GLYPH(0x2F991),
+ U24(0x82B1),
+ GLYPH(0x2F993),
+ U24(0x82B3),
+ GLYPH(0x2F994),
+ U24(0x82BD),
+ GLYPH(0x2F995),
+ U24(0x82E5),
+ GLYPH(0xF974),
+ U24(0x82E6),
+ GLYPH(0x2F996),
+ U24(0x831D),
+ GLYPH(0x2F999),
+ U24(0x8323),
+ GLYPH(0x2F99C),
+ U24(0x8336),
+ GLYPH(0xF9FE),
+ U24(0x8352),
+ GLYPH(0xFAB3),
+ U24(0x8353),
+ GLYPH(0x2F9A0),
+ U24(0x8363),
+ GLYPH(0x2F99A),
+ U24(0x83AD),
+ GLYPH(0x2F99B),
+ U24(0x83BD),
+ GLYPH(0x2F99D),
+ U24(0x83C9),
+ GLYPH(0xF93E),
+ U24(0x83CA),
+ GLYPH(0x2F9A1),
+ U24(0x83CC),
+ GLYPH(0x2F9A2),
+ U24(0x83DC),
+ GLYPH(0x2F9A3),
+ U24(0x83E7),
+ GLYPH(0x2F99E),
+ U24(0x83EF),
+ GLYPH(0xFAB4),
+ U24(0x83F1),
+ GLYPH(0xF958),
+ U24(0x843D),
+ GLYPH(0xF918),
+ U24(0x8449),
+ GLYPH(0xF96E),
+ U24(0x8457),
+ GLYPH(0xFA5F),
+ U24(0x84EE),
+ GLYPH(0xF999),
+ U24(0x84F1),
+ GLYPH(0x2F9A8),
+ U24(0x84F3),
+ GLYPH(0x2F9A9),
+ U24(0x84FC),
+ GLYPH(0xF9C2),
+ U24(0x8516),
+ GLYPH(0x2F9AA),
+ U24(0x8564),
+ GLYPH(0x2F9AC),
+ U24(0x85CD),
+ GLYPH(0xF923),
+ U24(0x85FA),
+ GLYPH(0xF9F0),
+ U24(0x8606),
+ GLYPH(0xF935),
+ U24(0x8612),
+ GLYPH(0xFA20),
+ U24(0x862D),
+ GLYPH(0xF91F),
+ U24(0x863F),
+ GLYPH(0xF910),
+ U24(0x8650),
+ GLYPH(0x2F9B3),
+ U24(0x865C),
+ GLYPH(0xF936),
+ U24(0x8667),
+ GLYPH(0x2F9B5),
+ U24(0x8669),
+ GLYPH(0x2F9B6),
+ U24(0x8688),
+ GLYPH(0x2F9B8),
+ U24(0x86A9),
+ GLYPH(0x2F9B7),
+ U24(0x86E2),
+ GLYPH(0x2F9BA),
+ U24(0x870E),
+ GLYPH(0x2F9B9),
+ U24(0x8728),
+ GLYPH(0x2F9BC),
+ U24(0x876B),
+ GLYPH(0x2F9BD),
+ U24(0x8779),
+ GLYPH(0xFAB5),
+ U24(0x8786),
+ GLYPH(0x2F9BE),
+ U24(0x87BA),
+ GLYPH(0xF911),
+ U24(0x87E1),
+ GLYPH(0x2F9C0),
+ U24(0x8801),
+ GLYPH(0x2F9C1),
+ U24(0x881F),
+ GLYPH(0xF927),
+ U24(0x884C),
+ GLYPH(0xFA08),
+ U24(0x8860),
+ GLYPH(0x2F9C3),
+ U24(0x8863),
+ GLYPH(0x2F9C4),
+ U24(0x88C2),
+ GLYPH(0xF9A0),
+ U24(0x88CF),
+ GLYPH(0xF9E7),
+ U24(0x88D7),
+ GLYPH(0x2F9C6),
+ U24(0x88DE),
+ GLYPH(0x2F9C7),
+ U24(0x88E1),
+ GLYPH(0xF9E8),
+ U24(0x88F8),
+ GLYPH(0xF912),
+ U24(0x88FA),
+ GLYPH(0x2F9C9),
+ U24(0x8910),
+ GLYPH(0xFA60),
+ U24(0x8941),
+ GLYPH(0xFAB6),
+ U24(0x8964),
+ GLYPH(0xF924),
+ U24(0x8986),
+ GLYPH(0xFAB7),
+ U24(0x898B),
+ GLYPH(0xFA0A),
+ U24(0x8996),
+ GLYPH(0xFA61),
+ U24(0x8AA0),
+ GLYPH(0x2F9CF),
+ U24(0x8AAA),
+ GLYPH(0xF96F),
+ U24(0x8ABF),
+ GLYPH(0xFAB9),
+ U24(0x8ACB),
+ GLYPH(0xFABB),
+ U24(0x8AD2),
+ GLYPH(0xF97D),
+ U24(0x8AD6),
+ GLYPH(0xF941),
+ U24(0x8AED),
+ GLYPH(0xFABE),
+ U24(0x8AF8),
+ GLYPH(0xFA22),
+ U24(0x8AFE),
+ GLYPH(0xF95D),
+ U24(0x8B01),
+ GLYPH(0xFA62),
+ U24(0x8B39),
+ GLYPH(0xFA63),
+ U24(0x8B58),
+ GLYPH(0xF9FC),
+ U24(0x8B80),
+ GLYPH(0xF95A),
+ U24(0x8B8A),
+ GLYPH(0xFAC0),
+ U24(0x8C48),
+ GLYPH(0xF900),
+ U24(0x8C55),
+ GLYPH(0x2F9D2),
+ U24(0x8CAB),
+ GLYPH(0x2F9D4),
+ U24(0x8CC1),
+ GLYPH(0x2F9D5),
+ U24(0x8CC2),
+ GLYPH(0xF948),
+ U24(0x8CC8),
+ GLYPH(0xF903),
+ U24(0x8CD3),
+ GLYPH(0xFA64),
+ U24(0x8D08),
+ GLYPH(0xFA65),
+ U24(0x8D1B),
+ GLYPH(0x2F9D6),
+ U24(0x8D77),
+ GLYPH(0x2F9D7),
+ U24(0x8DBC),
+ GLYPH(0x2F9DB),
+ U24(0x8DCB),
+ GLYPH(0x2F9DA),
+ U24(0x8DEF),
+ GLYPH(0xF937),
+ U24(0x8DF0),
+ GLYPH(0x2F9DC),
+ U24(0x8ECA),
+ GLYPH(0xF902),
+ U24(0x8ED4),
+ GLYPH(0x2F9DE),
+ U24(0x8F26),
+ GLYPH(0xF998),
+ U24(0x8F2A),
+ GLYPH(0xF9D7),
+ U24(0x8F38),
+ GLYPH(0xFAC2),
+ U24(0x8F3B),
+ GLYPH(0xFA07),
+ U24(0x8F62),
+ GLYPH(0xF98D),
+ U24(0x8F9E),
+ GLYPH(0x2F98D),
+ U24(0x8FB0),
+ GLYPH(0xF971),
+ U24(0x8FB6),
+ GLYPH(0xFA66),
+ U24(0x9023),
+ GLYPH(0xF99A),
+ U24(0x9038),
+ GLYPH(0xFA25),
+ U24(0x9072),
+ GLYPH(0xFAC3),
+ U24(0x907C),
+ GLYPH(0xF9C3),
+ U24(0x908F),
+ GLYPH(0xF913),
+ U24(0x9094),
+ GLYPH(0x2F9E2),
+ U24(0x90CE),
+ GLYPH(0xF92C),
+ U24(0x90DE),
+ GLYPH(0xFA2E),
+ U24(0x90F1),
+ GLYPH(0x2F9E3),
+ U24(0x90FD),
+ GLYPH(0xFA26),
+ U24(0x9111),
+ GLYPH(0x2F9E4),
+ U24(0x911B),
+ GLYPH(0x2F9E6),
+ U24(0x916A),
+ GLYPH(0xF919),
+ U24(0x9199),
+ GLYPH(0xFAC4),
+ U24(0x91B4),
+ GLYPH(0xF9B7),
+ U24(0x91CC),
+ GLYPH(0xF9E9),
+ U24(0x91CF),
+ GLYPH(0xF97E),
+ U24(0x91D1),
+ GLYPH(0xF90A),
+ U24(0x9234),
+ GLYPH(0xF9B1),
+ U24(0x9238),
+ GLYPH(0x2F9E7),
+ U24(0x9276),
+ GLYPH(0xFAC5),
+ U24(0x927C),
+ GLYPH(0x2F9EA),
+ U24(0x92D7),
+ GLYPH(0x2F9E8),
+ U24(0x92D8),
+ GLYPH(0x2F9E9),
+ U24(0x9304),
+ GLYPH(0xF93F),
+ U24(0x934A),
+ GLYPH(0xF99B),
+ U24(0x93F9),
+ GLYPH(0x2F9EB),
+ U24(0x9415),
+ GLYPH(0x2F9EC),
+ U24(0x958B),
+ GLYPH(0x2F9EE),
+ U24(0x95AD),
+ GLYPH(0xF986),
+ U24(0x95B7),
+ GLYPH(0x2F9F0),
+ U24(0x962E),
+ GLYPH(0xF9C6),
+ U24(0x964B),
+ GLYPH(0xF951),
+ U24(0x964D),
+ GLYPH(0xFA09),
+ U24(0x9675),
+ GLYPH(0xF959),
+ U24(0x9678),
+ GLYPH(0xF9D3),
+ U24(0x967C),
+ GLYPH(0xFAC6),
+ U24(0x9686),
+ GLYPH(0xF9DC),
+ U24(0x96A3),
+ GLYPH(0xF9F1),
+ U24(0x96B7),
+ GLYPH(0xFA2F),
+ U24(0x96B8),
+ GLYPH(0xF9B8),
+ U24(0x96C3),
+ GLYPH(0x2F9F3),
+ U24(0x96E2),
+ GLYPH(0xF9EA),
+ U24(0x96E3),
+ GLYPH(0xFA68),
+ U24(0x96F6),
+ GLYPH(0xF9B2),
+ U24(0x96F7),
+ GLYPH(0xF949),
+ U24(0x9723),
+ GLYPH(0x2F9F5),
+ U24(0x9732),
+ GLYPH(0xF938),
+ U24(0x9748),
+ GLYPH(0xF9B3),
+ U24(0x9756),
+ GLYPH(0xFA1C),
+ U24(0x97DB),
+ GLYPH(0xFAC9),
+ U24(0x97E0),
+ GLYPH(0x2F9FA),
+ U24(0x97FF),
+ GLYPH(0xFA69),
+ U24(0x980B),
+ GLYPH(0xFACB),
+ U24(0x9818),
+ GLYPH(0xF9B4),
+ U24(0x9829),
+ GLYPH(0x2FA00),
+ U24(0x983B),
+ GLYPH(0xFA6A),
+ U24(0x985E),
+ GLYPH(0xF9D0),
+ U24(0x98E2),
+ GLYPH(0x2FA02),
+ U24(0x98EF),
+ GLYPH(0xFA2A),
+ U24(0x98FC),
+ GLYPH(0xFA2B),
+ U24(0x9928),
+ GLYPH(0xFA2C),
+ U24(0x9929),
+ GLYPH(0x2FA04),
+ U24(0x99A7),
+ GLYPH(0x2FA05),
+ U24(0x99C2),
+ GLYPH(0x2FA06),
+ U24(0x99F1),
+ GLYPH(0xF91A),
+ U24(0x99FE),
+ GLYPH(0x2FA07),
+ U24(0x9A6A),
+ GLYPH(0xF987),
+ U24(0x9B12),
+ GLYPH(0xFACD),
+ U24(0x9B6F),
+ GLYPH(0xF939),
+ U24(0x9C40),
+ GLYPH(0x2FA0B),
+ U24(0x9C57),
+ GLYPH(0xF9F2),
+ U24(0x9CFD),
+ GLYPH(0x2FA0C),
+ U24(0x9D67),
+ GLYPH(0x2FA0F),
+ U24(0x9DB4),
+ GLYPH(0xFA2D),
+ U24(0x9DFA),
+ GLYPH(0xF93A),
+ U24(0x9E1E),
+ GLYPH(0xF920),
+ U24(0x9E7F),
+ GLYPH(0xF940),
+ U24(0x9E97),
+ GLYPH(0xF988),
+ U24(0x9E9F),
+ GLYPH(0xF9F3),
+ U24(0x9EBB),
+ GLYPH(0x2FA15),
+ U24(0x9ECE),
+ GLYPH(0xF989),
+ U24(0x9EF9),
+ GLYPH(0x2FA17),
+ U24(0x9EFE),
+ GLYPH(0x2FA18),
+ U24(0x9F05),
+ GLYPH(0x2FA19),
+ U24(0x9F0F),
+ GLYPH(0x2FA1A),
+ U24(0x9F16),
+ GLYPH(0x2FA1B),
+ U24(0x9F3B),
+ GLYPH(0x2FA1C),
+ U24(0x9F43),
+ GLYPH(0xFAD8),
+ U24(0x9F8D),
+ GLYPH(0xF9C4),
+ U24(0x9F8E),
+ GLYPH(0xFAD9),
+ U24(0x9F9C),
+ GLYPH(0xF907),
+ U24(0x20122),
+ GLYPH(0x2F803),
+ U24(0x2051C),
+ GLYPH(0x2F812),
+ U24(0x20525),
+ GLYPH(0x2F91B),
+ U24(0x2054B),
+ GLYPH(0x2F816),
+ U24(0x2063A),
+ GLYPH(0x2F80D),
+ U24(0x20804),
+ GLYPH(0x2F9D9),
+ U24(0x208DE),
+ GLYPH(0x2F9DD),
+ U24(0x20A2C),
+ GLYPH(0x2F834),
+ U24(0x20B63),
+ GLYPH(0x2F838),
+ U24(0x214E4),
+ GLYPH(0x2F859),
+ U24(0x216A8),
+ GLYPH(0x2F860),
+ U24(0x216EA),
+ GLYPH(0x2F861),
+ U24(0x219C8),
+ GLYPH(0x2F86C),
+ U24(0x21B18),
+ GLYPH(0x2F871),
+ U24(0x21D0B),
+ GLYPH(0x2F8F8),
+ U24(0x21DE4),
+ GLYPH(0x2F87B),
+ U24(0x21DE6),
+ GLYPH(0x2F87D),
+ U24(0x22183),
+ GLYPH(0x2F889),
+ U24(0x2219F),
+ GLYPH(0x2F939),
+ U24(0x22331),
+ GLYPH(0x2F891),
+ U24(0x226D4),
+ GLYPH(0x2F8A4),
+ U24(0x22844),
+ GLYPH(0xFAD0),
+ U24(0x2284A),
+ GLYPH(0xFACF),
+ U24(0x22B0C),
+ GLYPH(0x2F8B8),
+ U24(0x22BF1),
+ GLYPH(0x2F8BE),
+ U24(0x2300A),
+ GLYPH(0x2F8CA),
+ U24(0x232B8),
+ GLYPH(0x2F897),
+ U24(0x2335F),
+ GLYPH(0x2F980),
+ U24(0x23393),
+ GLYPH(0x2F989),
+ U24(0x2339C),
+ GLYPH(0x2F98A),
+ U24(0x233C3),
+ GLYPH(0x2F8DD),
+ U24(0x233D5),
+ GLYPH(0xFAD1),
+ U24(0x2346D),
+ GLYPH(0x2F8E3),
+ U24(0x236A3),
+ GLYPH(0x2F8EC),
+ U24(0x238A7),
+ GLYPH(0x2F8F0),
+ U24(0x23A8D),
+ GLYPH(0x2F8F7),
+ U24(0x23AFA),
+ GLYPH(0x2F8F9),
+ U24(0x23CBC),
+ GLYPH(0x2F8FB),
+ U24(0x23D1E),
+ GLYPH(0x2F906),
+ U24(0x23ED1),
+ GLYPH(0x2F90D),
+ U24(0x23F5E),
+ GLYPH(0x2F910),
+ U24(0x23F8E),
+ GLYPH(0x2F911),
+ U24(0x24263),
+ GLYPH(0x2F91D),
+ U24(0x242EE),
+ GLYPH(0xFA6C),
+ U24(0x243AB),
+ GLYPH(0x2F91F),
+ U24(0x24608),
+ GLYPH(0x2F923),
+ U24(0x24735),
+ GLYPH(0x2F926),
+ U24(0x24814),
+ GLYPH(0x2F927),
+ U24(0x24C36),
+ GLYPH(0x2F935),
+ U24(0x24C92),
+ GLYPH(0x2F937),
+ U24(0x24FA1),
+ GLYPH(0x2F93B),
+ U24(0x24FB8),
+ GLYPH(0x2F93C),
+ U24(0x25044),
+ GLYPH(0x2F93D),
+ U24(0x250F2),
+ GLYPH(0x2F942),
+ U24(0x250F3),
+ GLYPH(0x2F941),
+ U24(0x25119),
+ GLYPH(0x2F943),
+ U24(0x25133),
+ GLYPH(0x2F944),
+ U24(0x25249),
+ GLYPH(0xFAD5),
+ U24(0x2541D),
+ GLYPH(0x2F94D),
+ U24(0x25626),
+ GLYPH(0x2F952),
+ U24(0x2569A),
+ GLYPH(0x2F954),
+ U24(0x256C5),
+ GLYPH(0x2F955),
+ U24(0x2597C),
+ GLYPH(0x2F95C),
+ U24(0x25AA7),
+ GLYPH(0x2F95D),
+ U24(0x25BAB),
+ GLYPH(0x2F961),
+ U24(0x25C80),
+ GLYPH(0x2F965),
+ U24(0x25CD0),
+ GLYPH(0xFAD6),
+ U24(0x25F86),
+ GLYPH(0x2F96B),
+ U24(0x261DA),
+ GLYPH(0x2F898),
+ U24(0x26228),
+ GLYPH(0x2F972),
+ U24(0x26247),
+ GLYPH(0x2F973),
+ U24(0x262D9),
+ GLYPH(0x2F975),
+ U24(0x2633E),
+ GLYPH(0x2F977),
+ U24(0x264DA),
+ GLYPH(0x2F97B),
+ U24(0x26523),
+ GLYPH(0x2F97C),
+ U24(0x265A8),
+ GLYPH(0x2F97E),
+ U24(0x267A7),
+ GLYPH(0x2F987),
+ U24(0x267B5),
+ GLYPH(0x2F988),
+ U24(0x26B3C),
+ GLYPH(0x2F997),
+ U24(0x26C36),
+ GLYPH(0x2F9A4),
+ U24(0x26CD5),
+ GLYPH(0x2F9A6),
+ U24(0x26D6B),
+ GLYPH(0x2F9A5),
+ U24(0x26F2C),
+ GLYPH(0x2F9AD),
+ U24(0x26FB1),
+ GLYPH(0x2F9B0),
+ U24(0x270D2),
+ GLYPH(0x2F9B1),
+ U24(0x273CA),
+ GLYPH(0x2F9AB),
+ U24(0x27667),
+ GLYPH(0x2F9C5),
+ U24(0x278AE),
+ GLYPH(0x2F9CB),
+ U24(0x27966),
+ GLYPH(0x2F9CC),
+ U24(0x27CA8),
+ GLYPH(0x2F9D3),
+ U24(0x27ED3),
+ GLYPH(0xFAD7),
+ U24(0x27F2F),
+ GLYPH(0x2F9D8),
+ U24(0x285D2),
+ GLYPH(0x2F9E0),
+ U24(0x285ED),
+ GLYPH(0x2F9E1),
+ U24(0x2872E),
+ GLYPH(0x2F9E5),
+ U24(0x28BFA),
+ GLYPH(0x2F9ED),
+ U24(0x28D77),
+ GLYPH(0x2F9F1),
+ U24(0x29145),
+ GLYPH(0x2F9F6),
+ U24(0x291DF),
+ GLYPH(0x2F81C),
+ U24(0x2921A),
+ GLYPH(0x2F9F7),
+ U24(0x2940A),
+ GLYPH(0x2F9FB),
+ U24(0x29496),
+ GLYPH(0x2F9FD),
+ U24(0x295B6),
+ GLYPH(0x2FA01),
+ U24(0x29B30),
+ GLYPH(0x2FA09),
+ U24(0x2A0CE),
+ GLYPH(0x2FA10),
+ U24(0x2A105),
+ GLYPH(0x2FA12),
+ U24(0x2A20E),
+ GLYPH(0x2FA13),
+ U24(0x2A291),
+ GLYPH(0x2FA14),
+ U24(0x2A392),
+ GLYPH(0x2F88F),
+ U24(0x2A600),
+ GLYPH(0x2FA1D),
+ // 0xFE01
+ U32(88), // numUVSMappings
+ U24(0x3B9D),
+ GLYPH(0x2F8E7),
+ U24(0x3EB8),
+ GLYPH(0x2F92D),
+ U24(0x4039),
+ GLYPH(0x2F949),
+ U24(0x4FAE),
+ GLYPH(0x2F805),
+ U24(0x50E7),
+ GLYPH(0x2F80A),
+ U24(0x514D),
+ GLYPH(0x2F80E),
+ U24(0x51B5),
+ GLYPH(0x2F81B),
+ U24(0x5207),
+ GLYPH(0x2F850),
+ U24(0x52C7),
+ GLYPH(0x2F825),
+ U24(0x52C9),
+ GLYPH(0x2F826),
+ U24(0x52E4),
+ GLYPH(0x2F827),
+ U24(0x52FA),
+ GLYPH(0x2F828),
+ U24(0x5317),
+ GLYPH(0x2F82B),
+ U24(0x5351),
+ GLYPH(0x2F82D),
+ U24(0x537F),
+ GLYPH(0x2F832),
+ U24(0x5584),
+ GLYPH(0x2F846),
+ U24(0x5599),
+ GLYPH(0x2F847),
+ U24(0x559D),
+ GLYPH(0xFA78),
+ U24(0x5606),
+ GLYPH(0x2F84C),
+ U24(0x585A),
+ GLYPH(0xFA7C),
+ U24(0x5B3E),
+ GLYPH(0x2F86B),
+ U24(0x5BE7),
+ GLYPH(0xF9AA),
+ U24(0x5C6E),
+ GLYPH(0x2F878),
+ U24(0x5ECA),
+ GLYPH(0x2F88E),
+ U24(0x5F22),
+ GLYPH(0x2F895),
+ U24(0x6094),
+ GLYPH(0x2F8A3),
+ U24(0x614C),
+ GLYPH(0x2F8A9),
+ U24(0x614E),
+ GLYPH(0x2F8A8),
+ U24(0x618E),
+ GLYPH(0xFA89),
+ U24(0x61F2),
+ GLYPH(0xFA8B),
+ U24(0x61F6),
+ GLYPH(0x2F8B1),
+ U24(0x654F),
+ GLYPH(0x2F8C8),
+ U24(0x6674),
+ GLYPH(0xFA91),
+ U24(0x6691),
+ GLYPH(0x2F8CF),
+ U24(0x6717),
+ GLYPH(0xFA92),
+ U24(0x671B),
+ GLYPH(0x2F8D9),
+ U24(0x6885),
+ GLYPH(0x2F8E2),
+ U24(0x6A02),
+ GLYPH(0xF95C),
+ U24(0x6BBA),
+ GLYPH(0xFA96),
+ U24(0x6D41),
+ GLYPH(0xFA97),
+ U24(0x6D77),
+ GLYPH(0x2F901),
+ U24(0x6ECB),
+ GLYPH(0x2F90B),
+ U24(0x6F22),
+ GLYPH(0xFA9A),
+ U24(0x701E),
+ GLYPH(0x2F914),
+ U24(0x716E),
+ GLYPH(0xFA9C),
+ U24(0x7235),
+ GLYPH(0x2F921),
+ U24(0x732A),
+ GLYPH(0xFAA0),
+ U24(0x7387),
+ GLYPH(0xF9DB),
+ U24(0x7471),
+ GLYPH(0x2F930),
+ U24(0x7570),
+ GLYPH(0x2F938),
+ U24(0x76CA),
+ GLYPH(0xFAA6),
+ U24(0x76F4),
+ GLYPH(0x2F940),
+ U24(0x771F),
+ GLYPH(0x2F947),
+ U24(0x774A),
+ GLYPH(0x2F948),
+ U24(0x788C),
+ GLYPH(0x2F94F),
+ U24(0x78CC),
+ GLYPH(0x2F950),
+ U24(0x7956),
+ GLYPH(0x2F953),
+ U24(0x798F),
+ GLYPH(0x2F956),
+ U24(0x7A40),
+ GLYPH(0x2F959),
+ U24(0x7BC0),
+ GLYPH(0xFAAD),
+ U24(0x7DF4),
+ GLYPH(0xFA57),
+ U24(0x8005),
+ GLYPH(0xFAB2),
+ U24(0x8201),
+ GLYPH(0x2F98B),
+ U24(0x8279),
+ GLYPH(0xFA5E),
+ U24(0x82E5),
+ GLYPH(0x2F998),
+ U24(0x8457),
+ GLYPH(0x2F99F),
+ U24(0x865C),
+ GLYPH(0x2F9B4),
+ U24(0x8779),
+ GLYPH(0x2F9BB),
+ U24(0x8996),
+ GLYPH(0xFAB8),
+ U24(0x8AAA),
+ GLYPH(0xF9A1),
+ U24(0x8AED),
+ GLYPH(0x2F9D0),
+ U24(0x8AF8),
+ GLYPH(0xFABA),
+ U24(0x8AFE),
+ GLYPH(0xFABD),
+ U24(0x8B01),
+ GLYPH(0xFABC),
+ U24(0x8B39),
+ GLYPH(0xFABF),
+ U24(0x8B8A),
+ GLYPH(0x2F9D1),
+ U24(0x8D08),
+ GLYPH(0xFAC1),
+ U24(0x8F38),
+ GLYPH(0x2F9DF),
+ U24(0x9038),
+ GLYPH(0xFA67),
+ U24(0x96E3),
+ GLYPH(0xFAC7),
+ U24(0x9756),
+ GLYPH(0xFAC8),
+ U24(0x97FF),
+ GLYPH(0xFACA),
+ U24(0x980B),
+ GLYPH(0x2F9FE),
+ U24(0x983B),
+ GLYPH(0xFACC),
+ U24(0x9B12),
+ GLYPH(0x2FA0A),
+ U24(0x9F9C),
+ GLYPH(0xF908),
+ U24(0x22331),
+ GLYPH(0x2F892),
+ U24(0x25AA7),
+ GLYPH(0x2F95E),
+ // 0xFE02
+ U32(12), // numUVSMappings
+ U24(0x537F),
+ GLYPH(0x2F833),
+ U24(0x5BE7),
+ GLYPH(0x2F86F),
+ U24(0x618E),
+ GLYPH(0x2F8AB),
+ U24(0x61F2),
+ GLYPH(0x2F8B0),
+ U24(0x6717),
+ GLYPH(0x2F8D8),
+ U24(0x6A02),
+ GLYPH(0xF9BF),
+ U24(0x6BBA),
+ GLYPH(0x2F8F5),
+ U24(0x6D41),
+ GLYPH(0x2F902),
+ U24(0x7DF4),
+ GLYPH(0xFAB0),
+ U24(0x8005),
+ GLYPH(0x2F97A),
+ U24(0x980B),
+ GLYPH(0x2F9FF),
+ U24(0x9F9C),
+ GLYPH(0xFACE),
+};
+
+#undef U16
+#undef U24
+#undef U32
+#undef GLYPH
+
+static_assert(sizeof sCJKCompatSVSTable == 5065, "Table generator has a bug.");
diff --git a/gfx/thebes/COLRFonts.cpp b/gfx/thebes/COLRFonts.cpp
new file mode 100644
index 0000000000..89ca35661e
--- /dev/null
+++ b/gfx/thebes/COLRFonts.cpp
@@ -0,0 +1,2673 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "COLRFonts.h"
+#include "gfxFontUtils.h"
+#include "gfxUtils.h"
+#include "harfbuzz/hb.h"
+#include "harfbuzz/hb-ot.h"
+#include "mozilla/gfx/Helpers.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "TextDrawTarget.h"
+
+#include <limits>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+namespace { // anonymous namespace for implementation internals
+
+#pragma pack(1) // ensure no padding is added to the COLR structs
+
+// Alias bigendian-reading types from gfxFontUtils to names used in the spec.
+using int16 = AutoSwap_PRInt16;
+using uint16 = AutoSwap_PRUint16;
+using int32 = AutoSwap_PRInt32;
+using uint32 = AutoSwap_PRUint32;
+using FWORD = AutoSwap_PRInt16;
+using UFWORD = AutoSwap_PRUint16;
+using Offset16 = AutoSwap_PRUint16;
+using Offset24 = AutoSwap_PRUint24;
+using Offset32 = AutoSwap_PRUint32;
+
+struct COLRv1Header;
+struct ClipList;
+struct LayerRecord;
+struct BaseGlyphRecord;
+struct DeltaSetIndexMap;
+struct ItemVariationStore;
+
+struct COLRHeader {
+ uint16 version;
+ uint16 numBaseGlyphRecords;
+ Offset32 baseGlyphRecordsOffset;
+ Offset32 layerRecordsOffset;
+ uint16 numLayerRecords;
+
+ const BaseGlyphRecord* GetBaseGlyphRecords() const {
+ return reinterpret_cast<const BaseGlyphRecord*>(
+ reinterpret_cast<const char*>(this) + baseGlyphRecordsOffset);
+ }
+
+ const LayerRecord* GetLayerRecords() const {
+ return reinterpret_cast<const LayerRecord*>(
+ reinterpret_cast<const char*>(this) + layerRecordsOffset);
+ }
+
+ bool Validate(uint64_t aLength) const;
+};
+
+struct BaseGlyphPaintRecord {
+ uint16 glyphID;
+ Offset32 paintOffset;
+};
+
+struct BaseGlyphList {
+ uint32 numBaseGlyphPaintRecords;
+ // BaseGlyphPaintRecord baseGlyphPaintRecords[numBaseGlyphPaintRecords];
+ const BaseGlyphPaintRecord* baseGlyphPaintRecords() const {
+ return reinterpret_cast<const BaseGlyphPaintRecord*>(this + 1);
+ }
+ bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const;
+};
+
+struct LayerList {
+ uint32 numLayers;
+ // uint32 paintOffsets[numLayers];
+ const uint32* paintOffsets() const {
+ return reinterpret_cast<const uint32*>(this + 1);
+ }
+ bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const;
+};
+
+struct COLRv1Header {
+ COLRHeader base;
+ Offset32 baseGlyphListOffset;
+ Offset32 layerListOffset;
+ Offset32 clipListOffset;
+ Offset32 varIndexMapOffset;
+ Offset32 itemVariationStoreOffset;
+
+ bool Validate(uint64_t aLength) const;
+
+ const BaseGlyphList* baseGlyphList() const {
+ uint32_t offset = baseGlyphListOffset;
+ if (!offset) {
+ return nullptr;
+ }
+ const char* ptr = reinterpret_cast<const char*>(this) + offset;
+ return reinterpret_cast<const struct BaseGlyphList*>(ptr);
+ }
+
+ const LayerList* layerList() const {
+ uint32_t offset = layerListOffset;
+ if (!offset) {
+ return nullptr;
+ }
+ const char* ptr = reinterpret_cast<const char*>(this) + offset;
+ return reinterpret_cast<const LayerList*>(ptr);
+ }
+
+ const struct ClipList* clipList() const {
+ uint32_t offset = clipListOffset;
+ if (!offset) {
+ return nullptr;
+ }
+ const char* ptr = reinterpret_cast<const char*>(this) + offset;
+ return reinterpret_cast<const ClipList*>(ptr);
+ }
+
+ const struct DeltaSetIndexMap* varIndexMap() const {
+ uint32_t offset = varIndexMapOffset;
+ if (!offset) {
+ return nullptr;
+ }
+ const char* ptr = reinterpret_cast<const char*>(this) + offset;
+ return reinterpret_cast<const DeltaSetIndexMap*>(ptr);
+ }
+
+ const struct ItemVariationStore* itemVariationStore() const {
+ uint32_t offset = itemVariationStoreOffset;
+ if (!offset) {
+ return nullptr;
+ }
+ const char* ptr = reinterpret_cast<const char*>(this) + offset;
+ return reinterpret_cast<const ItemVariationStore*>(ptr);
+ }
+
+ const BaseGlyphPaintRecord* GetBaseGlyphPaint(uint32_t aGlyphId) const;
+};
+
+struct PaintState {
+ union {
+ const COLRHeader* v0;
+ const COLRv1Header* v1;
+ } mHeader;
+ const sRGBColor* mPalette;
+ DrawTarget* mDrawTarget;
+ ScaledFont* mScaledFont;
+ const int* mCoords;
+ DrawOptions mDrawOptions;
+ uint32_t mCOLRLength;
+ sRGBColor mCurrentColor;
+ float mFontUnitsToPixels;
+ uint16_t mNumColors;
+ uint16_t mCoordCount;
+ nsTArray<uint32_t>* mVisited;
+
+ const char* COLRv1BaseAddr() const {
+ return reinterpret_cast<const char*>(mHeader.v1);
+ }
+
+ DeviceColor GetColor(uint16_t aPaletteIndex, float aAlpha) const;
+
+ // Convert from FUnits (either integer or Fixed 16.16) to device pixels.
+ template <typename T>
+ float F2P(T aPixels) const {
+ return mFontUnitsToPixels * float(aPixels);
+ }
+};
+
+DeviceColor PaintState::GetColor(uint16_t aPaletteIndex, float aAlpha) const {
+ sRGBColor color;
+ if (aPaletteIndex < mNumColors) {
+ color = mPalette[uint16_t(aPaletteIndex)];
+ } else if (aPaletteIndex == 0xffff) {
+ color = mCurrentColor;
+ } else { // Palette index out of range! Return transparent black.
+ color = sRGBColor();
+ }
+ color.a *= aAlpha;
+ return ToDeviceColor(color);
+}
+
+static bool DispatchPaint(const PaintState& aState, uint32_t aOffset,
+ const Rect* aBounds /* may be nullptr if unknown */);
+static UniquePtr<Pattern> DispatchMakePattern(const PaintState& aState,
+ uint32_t aOffset);
+static Rect DispatchGetBounds(const PaintState& aState, uint32_t aOffset);
+static Matrix DispatchGetMatrix(const PaintState& aState, uint32_t aOffset);
+
+// Variation-data types
+struct Fixed {
+ enum { kFractionBits = 16 };
+ operator float() const {
+ return float(int32_t(value)) / float(1 << kFractionBits);
+ }
+ int32_t intRepr() const { return int32_t(value); }
+
+ private:
+ int32 value;
+};
+
+struct F2DOT14 {
+ enum { kFractionBits = 14 };
+ operator float() const {
+ return float(int16_t(value)) / float(1 << kFractionBits);
+ }
+ int32_t intRepr() const { return int16_t(value); }
+
+ private:
+ int16 value;
+};
+
+// Saturating addition used for variation indexes to avoid wrap-around.
+static uint32_t SatAdd(uint32_t a, uint32_t b) {
+ if (a <= std::numeric_limits<uint32_t>::max() - b) {
+ return a + b;
+ }
+ return std::numeric_limits<uint32_t>::max();
+}
+
+struct RegionAxisCoordinates {
+ F2DOT14 startCoord;
+ F2DOT14 peakCoord;
+ F2DOT14 endCoord;
+};
+
+struct VariationRegion {
+ // RegionAxisCoordinates regionAxes[axisCount];
+ const RegionAxisCoordinates* regionAxes() const {
+ return reinterpret_cast<const RegionAxisCoordinates*>(this);
+ }
+};
+
+struct VariationRegionList {
+ uint16 axisCount;
+ uint16 regionCount;
+ // VariationRegion variationRegions[regionCount];
+ const char* variationRegionsBase() const {
+ return reinterpret_cast<const char*>(this + 1);
+ }
+ size_t regionSize() const {
+ return uint16_t(axisCount) * sizeof(RegionAxisCoordinates);
+ }
+ const VariationRegion* getRegion(uint16_t i) const {
+ if (i >= uint16_t(regionCount)) {
+ return nullptr;
+ }
+ return reinterpret_cast<const VariationRegion*>(variationRegionsBase() +
+ i * regionSize());
+ }
+ bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const {
+ if (variationRegionsBase() - reinterpret_cast<const char*>(aHeader) +
+ uint16_t(regionCount) * regionSize() >
+ aLength) {
+ return false;
+ }
+ return true;
+ }
+};
+
+struct DeltaSet {
+ // (int16 and int8)
+ // *or*
+ // (int32 and int16) deltaData[regionIndexCount];
+};
+
+struct DeltaSetIndexMap {
+ enum { INNER_INDEX_BIT_COUNT_MASK = 0x0f, MAP_ENTRY_SIZE_MASK = 0x30 };
+ uint8_t format;
+ uint8_t entryFormat;
+ union {
+ struct {
+ uint16 mapCount;
+ // uint8 mapData[variable];
+ } v0;
+ struct {
+ uint32 mapCount;
+ // uint8 mapData[variable];
+ } v1;
+ };
+ uint32_t entrySize() const {
+ return (((entryFormat & MAP_ENTRY_SIZE_MASK) >> 4) + 1);
+ }
+ uint32_t map(uint32_t aIndex) const {
+ uint32_t mapCount;
+ const uint8_t* mapData;
+ switch (format) {
+ case 0:
+ mapCount = uint32_t(v0.mapCount);
+ mapData = reinterpret_cast<const uint8_t*>(&v0.mapCount) +
+ sizeof(v0.mapCount);
+ break;
+ case 1:
+ mapCount = uint32_t(v1.mapCount);
+ mapData = reinterpret_cast<const uint8_t*>(&v1.mapCount) +
+ sizeof(v1.mapCount);
+ break;
+ default:
+ // unknown DeltaSetIndexMap format
+ return aIndex;
+ }
+ if (!mapCount) {
+ return aIndex;
+ }
+ if (aIndex >= mapCount) {
+ aIndex = mapCount - 1;
+ }
+ const uint8_t* entryData = mapData + aIndex * entrySize();
+ uint32_t entry = 0;
+ for (uint32_t i = 0; i < entrySize(); ++i) {
+ entry = (entry << 8) + entryData[i];
+ }
+ uint16_t outerIndex =
+ entry >> ((entryFormat & INNER_INDEX_BIT_COUNT_MASK) + 1);
+ uint16_t innerIndex =
+ entry & ((1 << ((entryFormat & INNER_INDEX_BIT_COUNT_MASK) + 1)) - 1);
+ return (uint32_t(outerIndex) << 16) + innerIndex;
+ }
+ bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const;
+};
+
+enum EntryFormatMasks {
+ INNER_INDEX_BIT_COUNT_MASK = 0x0f,
+ MAP_ENTRY_SIZE_MASK = 0x30
+};
+
+struct ItemVariationData {
+ enum { WORD_DELTA_COUNT_MASK = 0x7FFF, LONG_WORDS = 0x8000 };
+ uint16 itemCount;
+ uint16 wordDeltaCount;
+ uint16 regionIndexCount;
+ // uint16 regionIndexes[regionIndexCount];
+ const uint16* regionIndexes() const {
+ return reinterpret_cast<const uint16*>(
+ reinterpret_cast<const char*>(this + 1));
+ }
+ // DeltaSet deltaSets[itemCount];
+ const DeltaSet* deltaSets() const {
+ return reinterpret_cast<const DeltaSet*>(
+ reinterpret_cast<const char*>(this + 1) +
+ uint16_t(regionIndexCount) * sizeof(uint16));
+ }
+ bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const;
+};
+
+struct ItemVariationStore {
+ uint16 format;
+ Offset32 variationRegionListOffset;
+ uint16 itemVariationDataCount;
+ // Offset32 itemVariationDataOffsets[itemVariationDataCount];
+ const Offset32* itemVariationDataOffsets() const {
+ return reinterpret_cast<const Offset32*>(
+ reinterpret_cast<const char*>(this + 1));
+ }
+ const VariationRegionList* variationRegionList() const {
+ return reinterpret_cast<const VariationRegionList*>(
+ reinterpret_cast<const char*>(this) + variationRegionListOffset);
+ }
+ bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const;
+};
+
+static int32_t ApplyVariation(const PaintState& aState, int32_t aValue,
+ uint32_t aIndex) {
+ if (aIndex == 0xffffffff) {
+ return aValue;
+ }
+ const auto* store = aState.mHeader.v1->itemVariationStore();
+ if (!store || uint16_t(store->format) != 1) {
+ return aValue;
+ }
+ const DeltaSetIndexMap* map = aState.mHeader.v1->varIndexMap();
+ uint32_t mappedIndex = map ? map->map(aIndex) : aIndex;
+ uint16_t outerIndex = mappedIndex >> 16;
+ uint16_t innerIndex = mappedIndex & 0xffff;
+ const auto* itemVariationDataOffsets = store->itemVariationDataOffsets();
+ if (mappedIndex == 0xffffffff ||
+ outerIndex >= uint16_t(store->itemVariationDataCount) ||
+ !itemVariationDataOffsets[outerIndex]) {
+ return aValue;
+ }
+ const auto* regionList = store->variationRegionList();
+ if (outerIndex >= uint16_t(store->itemVariationDataCount)) {
+ return aValue;
+ }
+ const auto* variationData = reinterpret_cast<const ItemVariationData*>(
+ reinterpret_cast<const char*>(store) +
+ itemVariationDataOffsets[outerIndex]);
+ if (innerIndex >= uint16_t(variationData->itemCount)) {
+ return aValue;
+ }
+ const auto* regionIndexes = variationData->regionIndexes();
+ uint16_t regionIndexCount = variationData->regionIndexCount;
+ const DeltaSet* deltaSets = variationData->deltaSets();
+ uint16_t wordDeltaCount = variationData->wordDeltaCount;
+ bool longWords = wordDeltaCount & ItemVariationData::LONG_WORDS;
+ wordDeltaCount &= ItemVariationData::WORD_DELTA_COUNT_MASK;
+ uint32_t deltaSetSize = (regionIndexCount + wordDeltaCount) << longWords;
+ const uint8_t* deltaData =
+ reinterpret_cast<const uint8_t*>(deltaSets) + deltaSetSize * innerIndex;
+ uint16_t deltaSize = longWords ? 4 : 2;
+ int32_t result = aValue;
+ for (uint16_t i = 0; i < regionIndexCount; ++i, deltaData += deltaSize) {
+ if (i == wordDeltaCount) {
+ deltaSize >>= 1;
+ }
+ const auto* region = regionList->getRegion(uint16_t(regionIndexes[i]));
+ if (!region) {
+ return aValue;
+ }
+ // XXX Should we do the calculations here in fixed-point? Check spec.
+ float scalar = -1.0;
+ for (uint16_t axisIndex = 0; axisIndex < uint16_t(regionList->axisCount);
+ ++axisIndex) {
+ const auto& axis = region->regionAxes()[axisIndex];
+ float peak = axis.peakCoord;
+ if (peak == 0.0) {
+ // This axis cannot contribute to scalar.
+ continue;
+ }
+ float start = axis.startCoord;
+ float end = axis.endCoord;
+ float value = axisIndex < aState.mCoordCount
+ ? float(aState.mCoords[axisIndex]) / 16384.0f
+ : 0.0;
+ if (value < start || value > end) {
+ // Out of range: this region is not applicable.
+ scalar = -1.0;
+ break;
+ }
+ if (scalar < 0.0) {
+ scalar = 1.0;
+ }
+ if (value == peak) {
+ continue;
+ }
+ if (value < peak && peak > start) {
+ scalar *= (value - start) / (peak - start);
+ } else if (value > peak && peak < end) {
+ scalar *= (end - value) / (end - peak);
+ }
+ }
+ if (scalar <= 0.0) {
+ continue;
+ }
+ int32_t delta = *reinterpret_cast<const int8_t*>(deltaData); // sign-extend
+ for (uint16_t j = 1; j < deltaSize; ++j) {
+ delta = (delta << 8) | deltaData[j];
+ }
+ delta = int32_t(floorf((float(delta) * scalar) + 0.5f));
+ result += delta;
+ }
+ return result;
+};
+
+static float ApplyVariation(const PaintState& aState, Fixed aValue,
+ uint32_t aIndex) {
+ return float(ApplyVariation(aState, aValue.intRepr(), aIndex)) /
+ (1 << Fixed::kFractionBits);
+}
+
+static float ApplyVariation(const PaintState& aState, F2DOT14 aValue,
+ uint32_t aIndex) {
+ return float(ApplyVariation(aState, aValue.intRepr(), aIndex)) /
+ (1 << F2DOT14::kFractionBits);
+}
+
+struct ClipBoxFormat1 {
+ enum { kFormat = 1 };
+ uint8_t format;
+ FWORD xMin;
+ FWORD yMin;
+ FWORD xMax;
+ FWORD yMax;
+
+ Rect GetRect(const PaintState& aState) const {
+ MOZ_ASSERT(format == kFormat);
+ int32_t x0 = int16_t(xMin);
+ int32_t y0 = int16_t(yMin);
+ int32_t x1 = int16_t(xMax);
+ int32_t y1 = int16_t(yMax);
+ // Flip the y-coordinates to map from OpenType to Moz2d space.
+ return Rect(aState.F2P(x0), -aState.F2P(y1), aState.F2P(x1 - x0),
+ aState.F2P(y1 - y0));
+ }
+};
+
+struct ClipBoxFormat2 : public ClipBoxFormat1 {
+ enum { kFormat = 2 };
+ uint32 varIndexBase;
+
+ Rect GetRect(const PaintState& aState) const {
+ MOZ_ASSERT(format == kFormat);
+ int32_t x0 = ApplyVariation(aState, int16_t(xMin), varIndexBase);
+ int32_t y0 = ApplyVariation(aState, int16_t(yMin), SatAdd(varIndexBase, 1));
+ int32_t x1 = ApplyVariation(aState, int16_t(xMax), SatAdd(varIndexBase, 2));
+ int32_t y1 = ApplyVariation(aState, int16_t(yMax), SatAdd(varIndexBase, 3));
+ return Rect(aState.F2P(x0), -aState.F2P(y1), aState.F2P(x1 - x0),
+ aState.F2P(y1 - y0));
+ }
+};
+
+struct Clip {
+ uint16 startGlyphID;
+ uint16 endGlyphID;
+ Offset24 clipBoxOffset;
+
+ Rect GetRect(const PaintState& aState) const {
+ uint32_t offset = aState.mHeader.v1->clipListOffset + clipBoxOffset;
+ const auto* box = aState.COLRv1BaseAddr() + offset;
+ switch (*box) {
+ case 1:
+ return reinterpret_cast<const ClipBoxFormat1*>(box)->GetRect(aState);
+ case 2:
+ return reinterpret_cast<const ClipBoxFormat2*>(box)->GetRect(aState);
+ default:
+ // unknown ClipBoxFormat
+ break;
+ }
+ return Rect();
+ }
+ bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const;
+};
+
+struct ClipList {
+ uint8_t format;
+ uint32 numClips;
+ // Clip clips[numClips]
+ const Clip* clips() const { return reinterpret_cast<const Clip*>(this + 1); }
+ const Clip* GetClip(uint32_t aGlyphId) const {
+ auto compare = [](const void* key, const void* data) -> int {
+ uint32_t glyphId = (uint32_t)(uintptr_t)key;
+ const auto* clip = reinterpret_cast<const Clip*>(data);
+ uint32_t start = uint16_t(clip->startGlyphID);
+ uint32_t end = uint16_t(clip->endGlyphID);
+ if (start <= glyphId && end >= glyphId) {
+ return 0;
+ }
+ return start > glyphId ? -1 : 1;
+ };
+ return reinterpret_cast<const Clip*>(bsearch((void*)(uintptr_t)aGlyphId,
+ clips(), uint32_t(numClips),
+ sizeof(Clip), compare));
+ }
+ bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const;
+};
+
+struct LayerRecord {
+ uint16 glyphId;
+ uint16 paletteEntryIndex;
+
+ bool Paint(const PaintState& aState, float aAlpha,
+ const Point& aPoint) const {
+ Glyph glyph{uint16_t(glyphId), aPoint};
+ GlyphBuffer buffer{&glyph, 1};
+ aState.mDrawTarget->FillGlyphs(
+ aState.mScaledFont, buffer,
+ ColorPattern(aState.GetColor(paletteEntryIndex, aAlpha)),
+ aState.mDrawOptions);
+ return true;
+ }
+};
+
+struct BaseGlyphRecord {
+ uint16 glyphId;
+ uint16 firstLayerIndex;
+ uint16 numLayers;
+
+ bool Paint(const PaintState& aState, float aAlpha,
+ const Point& aPoint) const {
+ uint32_t layerIndex = uint16_t(firstLayerIndex);
+ uint32_t end = layerIndex + uint16_t(numLayers);
+ if (end > uint16_t(aState.mHeader.v0->numLayerRecords)) {
+ MOZ_ASSERT_UNREACHABLE("bad COLRv0 table");
+ return false;
+ }
+ const auto* layers = aState.mHeader.v0->GetLayerRecords();
+ while (layerIndex < end) {
+ if (!layers[layerIndex].Paint(aState, aAlpha, aPoint)) {
+ return false;
+ }
+ ++layerIndex;
+ }
+ return true;
+ }
+};
+
+struct ColorStop {
+ F2DOT14 stopOffset;
+ uint16 paletteIndex;
+ F2DOT14 alpha;
+
+ float GetStopOffset(const PaintState& aState) const { return stopOffset; }
+ uint16_t GetPaletteIndex() const { return paletteIndex; }
+ float GetAlpha(const PaintState& aState) const { return alpha; }
+};
+
+struct VarColorStop : public ColorStop {
+ uint32 varIndexBase;
+
+ float GetStopOffset(const PaintState& aState) const {
+ return ApplyVariation(aState, stopOffset, varIndexBase);
+ }
+ float GetAlpha(const PaintState& aState) const {
+ return ApplyVariation(aState, alpha, SatAdd(varIndexBase, 1));
+ }
+};
+
+template <typename T>
+struct ColorLineT {
+ enum { EXTEND_PAD = 0, EXTEND_REPEAT = 1, EXTEND_REFLECT = 2 };
+ uint8_t extend;
+ uint16 numStops;
+ const T* colorStops() const { return reinterpret_cast<const T*>(this + 1); }
+
+ // If the color line has only one stop, return it as a simple ColorPattern.
+ UniquePtr<Pattern> AsSolidColor(const PaintState& aState) const {
+ if (uint16_t(numStops) != 1) {
+ return nullptr;
+ }
+ const auto* stop = colorStops();
+ return MakeUnique<ColorPattern>(
+ aState.GetColor(stop->GetPaletteIndex(), stop->GetAlpha(aState)));
+ }
+
+ // Retrieve the color stops into an array of GradientStop records. The stops
+ // are normalized to the range [0 .. 1], and the original offsets of the
+ // first and last stops are returned.
+ // If aReverse is true, the color line is reversed.
+ void CollectGradientStops(const PaintState& aState,
+ nsTArray<GradientStop>& aStops, float* aFirstStop,
+ float* aLastStop, bool aReverse = false) const {
+ MOZ_ASSERT(aStops.IsEmpty());
+ uint16_t count = numStops;
+ if (!count) {
+ return;
+ }
+ const auto* stop = colorStops();
+ if (reinterpret_cast<const char*>(stop) + count * sizeof(T) >
+ aState.COLRv1BaseAddr() + aState.mCOLRLength) {
+ return;
+ }
+ aStops.SetCapacity(count);
+ for (uint16_t i = 0; i < count; ++i, ++stop) {
+ DeviceColor color =
+ aState.GetColor(stop->GetPaletteIndex(), stop->GetAlpha(aState));
+ aStops.AppendElement(GradientStop{stop->GetStopOffset(aState), color});
+ }
+ if (count == 1) {
+ *aFirstStop = *aLastStop = aStops[0].offset;
+ return;
+ }
+ aStops.StableSort(nsDefaultComparator<GradientStop, GradientStop>());
+ if (aReverse) {
+ float a = aStops[0].offset;
+ float b = aStops.LastElement().offset;
+ aStops.Reverse();
+ for (auto& gs : aStops) {
+ gs.offset = a + b - gs.offset;
+ }
+ }
+ // Normalize stops to the range 0.0 .. 1.0, and return the original
+ // start & end.
+ // Note that if all stops are at the same offset, no normalization
+ // will be done.
+ *aFirstStop = aStops[0].offset;
+ *aLastStop = aStops.LastElement().offset;
+ if ((*aLastStop > *aFirstStop) &&
+ (*aLastStop != 1.0f || *aFirstStop != 0.0f)) {
+ float f = 1.0f / (*aLastStop - *aFirstStop);
+ for (auto& gs : aStops) {
+ gs.offset = (gs.offset - *aFirstStop) * f;
+ }
+ }
+ }
+
+ // Create a gfx::GradientStops representing the given color line stops,
+ // applying our extend mode.
+ already_AddRefed<GradientStops> MakeGradientStops(
+ const PaintState& aState, nsTArray<GradientStop>& aStops) const {
+ auto mapExtendMode = [](uint8_t aExtend) -> ExtendMode {
+ switch (aExtend) {
+ case EXTEND_REPEAT:
+ return ExtendMode::REPEAT;
+ case EXTEND_REFLECT:
+ return ExtendMode::REFLECT;
+ case EXTEND_PAD:
+ default:
+ return ExtendMode::CLAMP;
+ }
+ };
+ return aState.mDrawTarget->CreateGradientStops(
+ aStops.Elements(), aStops.Length(), mapExtendMode(extend));
+ }
+
+ already_AddRefed<GradientStops> MakeGradientStops(
+ const PaintState& aState, float* aFirstStop, float* aLastStop,
+ bool aReverse = false) const {
+ AutoTArray<GradientStop, 8> stops;
+ CollectGradientStops(aState, stops, aFirstStop, aLastStop, aReverse);
+ if (stops.IsEmpty()) {
+ return nullptr;
+ }
+ return MakeGradientStops(aState, stops);
+ }
+};
+
+using ColorLine = ColorLineT<ColorStop>;
+using VarColorLine = ColorLineT<VarColorStop>;
+
+// Used to check for cycles in the paint graph, and bail out to avoid infinite
+// recursion when traversing the graph in Paint() or GetBoundingRect(). (Only
+// PaintColrLayers and PaintColrGlyph can cause cycles; all other paint types
+// have only forward references within the table.)
+#define IF_CYCLE_RETURN(retval) \
+ if (aState.mVisited->Contains(aOffset)) { \
+ return retval; \
+ } \
+ aState.mVisited->AppendElement(aOffset); \
+ ScopeExit e([aState]() { aState.mVisited->RemoveLastElement(); })
+
+struct PaintColrLayers {
+ enum { kFormat = 1 };
+ uint8_t format;
+ uint8_t numLayers;
+ uint32 firstLayerIndex;
+
+ bool Paint(const PaintState& aState, uint32_t aOffset,
+ const Rect* aBounds) const {
+ MOZ_ASSERT(format == kFormat);
+ IF_CYCLE_RETURN(true);
+ const auto* layerList = aState.mHeader.v1->layerList();
+ if (!layerList) {
+ return false;
+ }
+ if (uint64_t(firstLayerIndex) + numLayers > layerList->numLayers) {
+ return false;
+ }
+ const auto* paintOffsets = layerList->paintOffsets() + firstLayerIndex;
+ for (uint32_t i = 0; i < numLayers; i++) {
+ if (!DispatchPaint(aState,
+ aState.mHeader.v1->layerListOffset + paintOffsets[i],
+ aBounds)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ Rect GetBoundingRect(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ IF_CYCLE_RETURN(Rect());
+ const auto* layerList = aState.mHeader.v1->layerList();
+ if (!layerList) {
+ return Rect();
+ }
+ if (uint64_t(firstLayerIndex) + numLayers > layerList->numLayers) {
+ return Rect();
+ }
+ Rect result;
+ const auto* paintOffsets = layerList->paintOffsets() + firstLayerIndex;
+ for (uint32_t i = 0; i < numLayers; i++) {
+ result = result.Union(DispatchGetBounds(
+ aState, aState.mHeader.v1->layerListOffset + paintOffsets[i]));
+ }
+ return result;
+ }
+};
+
+struct PaintPatternBase {
+ bool Paint(const PaintState& aState, uint32_t aOffset,
+ const Rect* aBounds) const {
+ Matrix m = aState.mDrawTarget->GetTransform();
+ if (m.Invert()) {
+ if (auto pattern = DispatchMakePattern(aState, aOffset)) {
+ aState.mDrawTarget->FillRect(
+ m.TransformBounds(IntRectToRect(aState.mDrawTarget->GetRect())),
+ *pattern, aState.mDrawOptions);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ Rect GetBoundingRect(const PaintState& aState, uint32_t aOffset) const {
+ return Rect();
+ }
+};
+
+struct PaintSolid : public PaintPatternBase {
+ enum { kFormat = 2 };
+ uint8_t format;
+ uint16 paletteIndex;
+ F2DOT14 alpha;
+
+ UniquePtr<Pattern> MakePattern(const PaintState& aState,
+ uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ return MakeUnique<ColorPattern>(aState.GetColor(paletteIndex, alpha));
+ }
+};
+
+struct PaintVarSolid : public PaintSolid {
+ enum { kFormat = 3 };
+ uint32 varIndexBase;
+
+ UniquePtr<Pattern> MakePattern(const PaintState& aState,
+ uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ return MakeUnique<ColorPattern>(aState.GetColor(
+ paletteIndex, ApplyVariation(aState, alpha, varIndexBase)));
+ }
+};
+
+struct PaintLinearGradient : public PaintPatternBase {
+ enum { kFormat = 4 };
+ uint8_t format;
+ Offset24 colorLineOffset;
+ FWORD x0;
+ FWORD y0;
+ FWORD x1;
+ FWORD y1;
+ FWORD x2;
+ FWORD y2;
+
+ UniquePtr<Pattern> MakePattern(const PaintState& aState,
+ uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ uint32_t clOffset = aOffset + colorLineOffset;
+ if (clOffset + sizeof(ColorLine) + sizeof(ColorStop) > aState.mCOLRLength) {
+ return nullptr;
+ }
+ const auto* colorLine =
+ reinterpret_cast<const ColorLine*>(aState.COLRv1BaseAddr() + clOffset);
+ Point p0(aState.F2P(int16_t(x0)), aState.F2P(int16_t(y0)));
+ Point p1(aState.F2P(int16_t(x1)), aState.F2P(int16_t(y1)));
+ Point p2(aState.F2P(int16_t(x2)), aState.F2P(int16_t(y2)));
+ return NormalizeAndMakeGradient(aState, colorLine, p0, p1, p2);
+ }
+
+ template <typename T>
+ UniquePtr<Pattern> NormalizeAndMakeGradient(const PaintState& aState,
+ const T* aColorLine, Point p0,
+ Point p1, Point p2) const {
+ // Ill-formed gradient should not be rendered.
+ if (p1 == p0 || p2 == p0) {
+ return MakeUnique<ColorPattern>(DeviceColor());
+ }
+ UniquePtr<Pattern> solidColor = aColorLine->AsSolidColor(aState);
+ if (solidColor) {
+ return solidColor;
+ }
+ float firstStop, lastStop;
+ AutoTArray<GradientStop, 8> stopArray;
+ aColorLine->CollectGradientStops(aState, stopArray, &firstStop, &lastStop);
+ if (stopArray.IsEmpty()) {
+ return MakeUnique<ColorPattern>(DeviceColor());
+ }
+ if (firstStop != 0.0 || lastStop != 1.0) {
+ if (firstStop == lastStop) {
+ if (aColorLine->extend != T::EXTEND_PAD) {
+ return MakeUnique<ColorPattern>(DeviceColor());
+ }
+ // For extend-pad, when the color line is zero-length, we add a "fake"
+ // color stop to create a [0.0..1.0]-normalized color line, so that the
+ // projection of points below works as expected.
+ for (auto& gs : stopArray) {
+ gs.offset = 0.0f;
+ }
+ stopArray.AppendElement(
+ GradientStop{1.0f, stopArray.LastElement().color});
+ lastStop += 1.0f;
+ }
+ // Adjust positions of the points to account for normalization of the
+ // color line stop offsets.
+ Point v = p1 - p0;
+ p0 += v * firstStop;
+ p1 -= v * (1.0f - lastStop);
+ // Move the rotation vector to maintain the same direction from p0.
+ p2 += v * firstStop;
+ }
+ Point p3;
+ if (FuzzyEqualsMultiplicative(p2.y, p0.y)) {
+ // rotation vector is horizontal
+ p3 = Point(p0.x, p1.y);
+ } else if (FuzzyEqualsMultiplicative(p2.x, p0.x)) {
+ // rotation vector is vertical
+ p3 = Point(p1.x, p0.y);
+ } else {
+ float m = (p2.y - p0.y) / (p2.x - p0.x); // slope of line p0->p2
+ float mInv = -1.0f / m; // slope of desired perpendicular p0->p3
+ float c1 = p0.y - mInv * p0.x; // line p0->p3 is m * x - y + c1 = 0
+ float c2 = p1.y - m * p1.x; // line p1->p3 is mInv * x - y + c2 = 0
+ float x3 = (c1 - c2) / (m - mInv);
+ float y3 = (c1 * m - c2 * mInv) / (m - mInv);
+ p3 = Point(x3, y3);
+ }
+ RefPtr stops = aColorLine->MakeGradientStops(aState, stopArray);
+ return MakeUnique<LinearGradientPattern>(p0, p3, std::move(stops),
+ Matrix::Scaling(1.0, -1.0));
+ }
+};
+
+struct PaintVarLinearGradient : public PaintLinearGradient {
+ enum { kFormat = 5 };
+ uint32 varIndexBase;
+
+ UniquePtr<Pattern> MakePattern(const PaintState& aState,
+ uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ uint32_t clOffset = aOffset + colorLineOffset;
+ if (clOffset + sizeof(VarColorLine) + sizeof(VarColorStop) >
+ aState.mCOLRLength) {
+ return nullptr;
+ }
+ const auto* colorLine = reinterpret_cast<const VarColorLine*>(
+ aState.COLRv1BaseAddr() + clOffset);
+ Point p0(aState.F2P(ApplyVariation(aState, int16_t(x0), varIndexBase)),
+ aState.F2P(
+ ApplyVariation(aState, int16_t(y0), SatAdd(varIndexBase, 1))));
+ Point p1(aState.F2P(
+ ApplyVariation(aState, int16_t(x1), SatAdd(varIndexBase, 2))),
+ aState.F2P(
+ ApplyVariation(aState, int16_t(y1), SatAdd(varIndexBase, 3))));
+ Point p2(aState.F2P(
+ ApplyVariation(aState, int16_t(x2), SatAdd(varIndexBase, 4))),
+ aState.F2P(
+ ApplyVariation(aState, int16_t(y2), SatAdd(varIndexBase, 5))));
+ return NormalizeAndMakeGradient(aState, colorLine, p0, p1, p2);
+ }
+};
+
+struct PaintRadialGradient : public PaintPatternBase {
+ enum { kFormat = 6 };
+ uint8_t format;
+ Offset24 colorLineOffset;
+ FWORD x0;
+ FWORD y0;
+ UFWORD radius0;
+ FWORD x1;
+ FWORD y1;
+ UFWORD radius1;
+
+ UniquePtr<Pattern> MakePattern(const PaintState& aState,
+ uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ uint32_t clOffset = aOffset + colorLineOffset;
+ if (clOffset + sizeof(ColorLine) + sizeof(ColorStop) > aState.mCOLRLength) {
+ return nullptr;
+ }
+ const auto* colorLine =
+ reinterpret_cast<const ColorLine*>(aState.COLRv1BaseAddr() + clOffset);
+ Point c1(aState.F2P(int16_t(x0)), aState.F2P(int16_t(y0)));
+ Point c2(aState.F2P(int16_t(x1)), aState.F2P(int16_t(y1)));
+ float r1 = aState.F2P(uint16_t(radius0));
+ float r2 = aState.F2P(uint16_t(radius1));
+ return NormalizeAndMakeGradient(aState, colorLine, c1, c2, r1, r2);
+ }
+
+ // Helper function to trim the gradient stops array at the start or end.
+ void TruncateGradientStops(nsTArray<GradientStop>& aStops, float aStart,
+ float aEnd) const {
+ // For pad mode, we may need a sub-range of the line: figure out which
+ // stops to trim, and interpolate as needed at truncation point(s).
+ // (Currently this is only ever used to trim one end of the color line,
+ // so edge cases that may occur when trimming both ends are untested.)
+ MOZ_ASSERT(aStart == 0.0f || aEnd == 1.0f,
+ "Trimming both ends of color-line is untested!");
+
+ // Create a color that is |r| of the way from c1 to c2.
+ auto interpolateColor = [](DeviceColor c1, DeviceColor c2, float r) {
+ return DeviceColor(
+ c2.r * r + c1.r * (1.0f - r), c2.g * r + c1.g * (1.0f - r),
+ c2.b * r + c1.b * (1.0f - r), c2.a * r + c1.a * (1.0f - r));
+ };
+
+ size_t count = aStops.Length();
+ MOZ_ASSERT(count > 1);
+
+ // Truncate at the start of the color line?
+ if (aStart > 0.0f) {
+ // Skip forward past any stops that can be dropped.
+ size_t i = 0;
+ while (i < count - 1 && aStops[i].offset < aStart) {
+ ++i;
+ }
+ // If we're not truncating exactly at a color-stop offset, shift the
+ // preceding stop to the truncation offset and interpolate its color.
+ if (i && aStops[i].offset > aStart) {
+ auto& prev = aStops[i - 1];
+ auto& curr = aStops[i];
+ float ratio = (aStart - prev.offset) / (curr.offset - prev.offset);
+ prev.color = interpolateColor(prev.color, curr.color, ratio);
+ prev.offset = aStart;
+ --i; // We don't want to remove this stop, as we adjusted it.
+ }
+ aStops.RemoveElementsAt(0, i);
+ // Re-normalize the remaining stops to the [0, 1] range.
+ if (aStart < 1.0f) {
+ float r = 1.0f / (1.0f - aStart);
+ for (auto& gs : aStops) {
+ gs.offset = r * (gs.offset - aStart);
+ }
+ }
+ }
+
+ // Truncate at the end of the color line?
+ if (aEnd < 1.0f) {
+ // Skip back over any stops that can be dropped.
+ size_t i = count - 1;
+ while (i && aStops[i].offset > aEnd) {
+ --i;
+ }
+ // If we're not truncating exactly at a color-stop offset, shift the
+ // following stop to the truncation offset and interpolate its color.
+ if (i + 1 < count && aStops[i].offset < aEnd) {
+ auto& next = aStops[i + 1];
+ auto& curr = aStops[i];
+ float ratio = (aEnd - curr.offset) / (next.offset - curr.offset);
+ next.color = interpolateColor(curr.color, next.color, ratio);
+ next.offset = aEnd;
+ ++i;
+ }
+ aStops.RemoveElementsAt(i + 1, count - i - 1);
+ // Re-normalize the remaining stops to the [0, 1] range.
+ if (aEnd > 0.0f) {
+ float r = 1.0f / aEnd;
+ for (auto& gs : aStops) {
+ gs.offset = r * gs.offset;
+ }
+ }
+ }
+ }
+
+ template <typename T>
+ UniquePtr<Pattern> NormalizeAndMakeGradient(const PaintState& aState,
+ const T* aColorLine, Point c1,
+ Point c2, float r1,
+ float r2) const {
+ if ((c1 == c2 && r1 == r2) || (r1 == 0.0 && r2 == 0.0)) {
+ return MakeUnique<ColorPattern>(DeviceColor());
+ }
+ UniquePtr<Pattern> solidColor = aColorLine->AsSolidColor(aState);
+ if (solidColor) {
+ return solidColor;
+ }
+ float firstStop, lastStop;
+ AutoTArray<GradientStop, 8> stopArray;
+ aColorLine->CollectGradientStops(aState, stopArray, &firstStop, &lastStop);
+ if (stopArray.IsEmpty()) {
+ return MakeUnique<ColorPattern>(DeviceColor());
+ }
+ // If the color stop offsets had to be normalized to the [0, 1] range,
+ // adjust the circle positions and radii to match.
+ if (firstStop != 0.0f || lastStop != 1.0f) {
+ if (firstStop == lastStop) {
+ if (aColorLine->extend != T::EXTEND_PAD) {
+ return MakeUnique<ColorPattern>(DeviceColor());
+ }
+ // For extend-pad, when the color line is zero-length, we add a "fake"
+ // color stop to ensure we'll maintain the orientation of the cone,
+ // otherwise when we adjust circles to account for the normalized color
+ // stops, the centers will coalesce and the cone or cylinder collapses.
+ for (auto& gs : stopArray) {
+ gs.offset = 0.0f;
+ }
+ stopArray.AppendElement(
+ GradientStop{1.0f, stopArray.LastElement().color});
+ lastStop += 1.0f;
+ }
+ // Adjust centers along the vector between them, and scale radii for
+ // gradient line defined from 0.0 to 1.0.
+ Point vec = c2 - c1;
+ c1 += vec * firstStop;
+ c2 -= vec * (1.0f - lastStop);
+ float deltaR = r2 - r1;
+ r1 = r1 + deltaR * firstStop;
+ r2 = r2 - deltaR * (1.0f - lastStop);
+ }
+ if ((r1 < 0.0f || r2 < 0.0f) && aColorLine->extend == T::EXTEND_PAD) {
+ // For EXTEND_PAD, we can restrict the gradient definition to just its
+ // visible portion because the shader doesn't need to see any part of the
+ // color line that extends into the negative-radius "virtual cone".
+ if (r1 < 0.0f && r2 < 0.0f) {
+ // If both radii are negative, then only the color at the closer circle
+ // will appear in the projected positive cone (or if they're equal,
+ // nothing will be visible at all).
+ if (r1 == r2) {
+ return MakeUnique<ColorPattern>(DeviceColor());
+ }
+ // The defined range of the color line is entirely in the invisible
+ // cone; all that will project into visible space is a single color.
+ if (r1 < r2) {
+ // Keep only the last color stop.
+ stopArray.RemoveElementsAt(0, stopArray.Length() - 1);
+ } else {
+ // Keep only the first color stop.
+ stopArray.RemoveElementsAt(1, stopArray.Length() - 1);
+ }
+ } else {
+ // Truncate the gradient at the tip of the visible cone: find the color
+ // stops closest to that point and interpolate between them.
+ if (r1 < r2) {
+ float start = r1 / (r1 - r2);
+ TruncateGradientStops(stopArray, start, 1.0f);
+ r1 = 0.0f;
+ c1 = c1 * (1.0f - start) + c2 * start;
+ } else if (r2 < r1) {
+ float end = 1.0f - r2 / (r2 - r1);
+ TruncateGradientStops(stopArray, 0.0f, end);
+ r2 = 0.0f;
+ c2 = c1 * (1.0f - end) + c2 * end;
+ }
+ }
+ }
+ // Handle negative radii, which the shader won't understand directly, by
+ // projecting the circles along the cones such that both radii are positive.
+ if (r1 < 0.0f || r2 < 0.0f) {
+ float deltaR = r2 - r1;
+ // If deltaR is zero, then nothing is visible because the cone has
+ // degenerated into a negative-radius cylinder, and does not project
+ // into visible space at all.
+ if (deltaR == 0.0f) {
+ return MakeUnique<ColorPattern>(DeviceColor());
+ }
+ Point vec = c2 - c1;
+ if (aColorLine->extend == T::EXTEND_REFLECT) {
+ deltaR *= 2.0f;
+ vec = vec * 2.0f;
+ }
+ if (r2 < r1) {
+ vec = -vec;
+ deltaR = -deltaR;
+ }
+ // Number of repeats by which we need to shift.
+ float n = std::ceil(std::max(-r1, -r2) / deltaR);
+ deltaR *= n;
+ r1 += deltaR;
+ r2 += deltaR;
+ vec = vec * n;
+ c1 += vec;
+ c2 += vec;
+ }
+ RefPtr stops = aColorLine->MakeGradientStops(aState, stopArray);
+ if (!stops) {
+ return MakeUnique<ColorPattern>(DeviceColor());
+ }
+ return MakeUnique<RadialGradientPattern>(c1, c2, r1, r2, std::move(stops),
+ Matrix::Scaling(1.0, -1.0));
+ }
+};
+
+struct PaintVarRadialGradient : public PaintRadialGradient {
+ enum { kFormat = 7 };
+ uint32 varIndexBase;
+
+ UniquePtr<Pattern> MakePattern(const PaintState& aState,
+ uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ uint32_t clOffset = aOffset + colorLineOffset;
+ if (clOffset + sizeof(VarColorLine) + sizeof(VarColorStop) >
+ aState.mCOLRLength) {
+ return nullptr;
+ }
+ const auto* colorLine = reinterpret_cast<const VarColorLine*>(
+ aState.COLRv1BaseAddr() + clOffset);
+ Point c1(aState.F2P(ApplyVariation(aState, int16_t(x0), varIndexBase)),
+ aState.F2P(
+ ApplyVariation(aState, int16_t(y0), SatAdd(varIndexBase, 1))));
+ float r1 = aState.F2P(
+ ApplyVariation(aState, uint16_t(radius0), SatAdd(varIndexBase, 2)));
+ Point c2(aState.F2P(
+ ApplyVariation(aState, int16_t(x1), SatAdd(varIndexBase, 3))),
+ aState.F2P(
+ ApplyVariation(aState, int16_t(y1), SatAdd(varIndexBase, 4))));
+ float r2 = aState.F2P(
+ ApplyVariation(aState, uint16_t(radius1), SatAdd(varIndexBase, 5)));
+ return NormalizeAndMakeGradient(aState, colorLine, c1, c2, r1, r2);
+ }
+};
+
+struct PaintSweepGradient : public PaintPatternBase {
+ enum { kFormat = 8 };
+ uint8_t format;
+ Offset24 colorLineOffset;
+ FWORD centerX;
+ FWORD centerY;
+ F2DOT14 startAngle;
+ F2DOT14 endAngle;
+
+ UniquePtr<Pattern> MakePattern(const PaintState& aState,
+ uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ uint32_t clOffset = aOffset + colorLineOffset;
+ if (clOffset + sizeof(ColorLine) + sizeof(ColorStop) > aState.mCOLRLength) {
+ return nullptr;
+ }
+ const auto* colorLine =
+ reinterpret_cast<const ColorLine*>(aState.COLRv1BaseAddr() + clOffset);
+ float start = float(startAngle) + 1.0f;
+ float end = float(endAngle) + 1.0f;
+ Point center(aState.F2P(int16_t(centerX)), aState.F2P(int16_t(centerY)));
+ return NormalizeAndMakeGradient(aState, colorLine, center, start, end);
+ }
+
+ template <typename T>
+ UniquePtr<Pattern> NormalizeAndMakeGradient(const PaintState& aState,
+ const T* aColorLine,
+ Point aCenter, float aStart,
+ float aEnd) const {
+ if (aStart == aEnd && aColorLine->extend != T::EXTEND_PAD) {
+ return MakeUnique<ColorPattern>(DeviceColor());
+ }
+ UniquePtr<Pattern> solidColor = aColorLine->AsSolidColor(aState);
+ if (solidColor) {
+ return solidColor;
+ }
+ // ConicGradientPattern works counterclockwise. If the gradient is defined
+ // clockwise (with aStart greater than aEnd), we'll reverse the color line
+ // and swap the start and end angles.
+ bool reverse = aEnd < aStart;
+ float firstStop, lastStop;
+ RefPtr stops =
+ aColorLine->MakeGradientStops(aState, &firstStop, &lastStop, reverse);
+ if (!stops) {
+ return nullptr;
+ }
+ if (firstStop != 0.0 || lastStop != 1.0) {
+ if (firstStop == lastStop) {
+ if (aColorLine->extend != T::EXTEND_PAD) {
+ return MakeUnique<ColorPattern>(DeviceColor());
+ }
+ } else {
+ float sweep = aEnd - aStart;
+ aStart = aStart + sweep * firstStop;
+ aEnd = aStart + sweep * (lastStop - firstStop);
+ }
+ }
+ if (reverse) {
+ std::swap(aStart, aEnd);
+ }
+ return MakeUnique<ConicGradientPattern>(aCenter, M_PI / 2.0, aStart / 2.0,
+ aEnd / 2.0, std::move(stops),
+ Matrix::Scaling(1.0, -1.0));
+ }
+};
+
+struct PaintVarSweepGradient : public PaintSweepGradient {
+ enum { kFormat = 9 };
+ uint32 varIndexBase;
+
+ UniquePtr<Pattern> MakePattern(const PaintState& aState,
+ uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ uint32_t clOffset = aOffset + colorLineOffset;
+ if (clOffset + sizeof(VarColorLine) + sizeof(VarColorStop) >
+ aState.mCOLRLength) {
+ return nullptr;
+ }
+ const auto* colorLine = reinterpret_cast<const VarColorLine*>(
+ aState.COLRv1BaseAddr() + clOffset);
+ float start =
+ ApplyVariation(aState, startAngle, SatAdd(varIndexBase, 2)) + 1.0f;
+ float end =
+ ApplyVariation(aState, endAngle, SatAdd(varIndexBase, 3)) + 1.0f;
+ Point center(
+ aState.F2P(ApplyVariation(aState, int16_t(centerX), varIndexBase)),
+ aState.F2P(
+ ApplyVariation(aState, int16_t(centerY), SatAdd(varIndexBase, 1))));
+ return NormalizeAndMakeGradient(aState, colorLine, center, start, end);
+ }
+};
+
+struct PaintGlyph {
+ enum { kFormat = 10 };
+ uint8_t format;
+ Offset24 paintOffset;
+ uint16 glyphID;
+
+ bool Paint(const PaintState& aState, uint32_t aOffset,
+ const Rect* aBounds) const {
+ MOZ_ASSERT(format == kFormat);
+ if (!paintOffset) {
+ return true;
+ }
+ Glyph g{uint16_t(glyphID), Point()};
+ GlyphBuffer buffer{&g, 1};
+ // If the paint is a simple fill (rather than a sub-graph of further paint
+ // records), we can just use FillGlyphs to render it instead of setting up
+ // a clip.
+ UniquePtr<Pattern> fillPattern =
+ DispatchMakePattern(aState, aOffset + paintOffset);
+ if (fillPattern) {
+ // On macOS we can't use FillGlyphs because when we render the glyph,
+ // Core Text's own color font support may step in and ignore the
+ // pattern. So to avoid this, fill the glyph as a path instead.
+#if XP_MACOSX
+ RefPtr<Path> path =
+ aState.mScaledFont->GetPathForGlyphs(buffer, aState.mDrawTarget);
+ aState.mDrawTarget->Fill(path, *fillPattern, aState.mDrawOptions);
+#else
+ aState.mDrawTarget->FillGlyphs(aState.mScaledFont, buffer, *fillPattern,
+ aState.mDrawOptions);
+#endif
+ return true;
+ }
+ RefPtr<Path> path =
+ aState.mScaledFont->GetPathForGlyphs(buffer, aState.mDrawTarget);
+ aState.mDrawTarget->PushClip(path);
+ bool ok = DispatchPaint(aState, aOffset + paintOffset, aBounds);
+ aState.mDrawTarget->PopClip();
+ return ok;
+ }
+
+ Rect GetBoundingRect(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ Glyph g{uint16_t(glyphID), Point()};
+ GlyphBuffer buffer{&g, 1};
+ RefPtr<Path> path =
+ aState.mScaledFont->GetPathForGlyphs(buffer, aState.mDrawTarget);
+ return path->GetFastBounds();
+ }
+};
+
+struct PaintColrGlyph {
+ enum { kFormat = 11 };
+ uint8_t format;
+ uint16 glyphID;
+
+ // Factored out as a helper because this is also used by the top-level
+ // PaintGlyphGraph function.
+ static bool DoPaint(const PaintState& aState,
+ const BaseGlyphPaintRecord* aBaseGlyphPaint,
+ uint32_t aGlyphId, const Rect* aBounds) {
+ AutoPopClips clips(aState.mDrawTarget);
+ Rect clipRect;
+ if (const auto* clipList = aState.mHeader.v1->clipList()) {
+ if (const auto* clip = clipList->GetClip(aGlyphId)) {
+ clipRect = clip->GetRect(aState);
+ clips.PushClipRect(clipRect);
+ if (!aBounds) {
+ aBounds = &clipRect;
+ }
+ }
+ }
+ return DispatchPaint(
+ aState,
+ aState.mHeader.v1->baseGlyphListOffset + aBaseGlyphPaint->paintOffset,
+ aBounds);
+ }
+
+ bool Paint(const PaintState& aState, uint32_t aOffset,
+ const Rect* aBounds) const {
+ MOZ_ASSERT(format == kFormat);
+ IF_CYCLE_RETURN(true);
+ const auto* base = aState.mHeader.v1->GetBaseGlyphPaint(glyphID);
+ return base ? DoPaint(aState, base, uint16_t(glyphID), aBounds) : false;
+ }
+
+ Rect GetBoundingRect(const PaintState& aState, uint32_t aOffset) const {
+ IF_CYCLE_RETURN(Rect());
+ if (const auto* clipList = aState.mHeader.v1->clipList()) {
+ if (const auto* clip = clipList->GetClip(uint16_t(glyphID))) {
+ return clip->GetRect(aState);
+ }
+ }
+ if (const auto* base =
+ aState.mHeader.v1->GetBaseGlyphPaint(uint16_t(glyphID))) {
+ return DispatchGetBounds(
+ aState, aState.mHeader.v1->baseGlyphListOffset + base->paintOffset);
+ }
+ return Rect();
+ }
+};
+
+#undef IF_CYCLE_RETURN
+
+struct Affine2x3 {
+ Fixed xx;
+ Fixed yx;
+ Fixed xy;
+ Fixed yy;
+ Fixed dx;
+ Fixed dy;
+
+ Matrix AsMatrix(const PaintState& aState) const {
+ // Flip signs because of opposite y-axis direction in moz2d vs opentype.
+ return Matrix(float(xx), -float(yx), -float(xy), float(yy),
+ aState.F2P(float(dx)), -aState.F2P(float(dy)));
+ }
+};
+
+struct VarAffine2x3 : public Affine2x3 {
+ uint32 varIndexBase;
+
+ Matrix AsMatrix(const PaintState& aState) const {
+ return Matrix(
+ ApplyVariation(aState, xx, varIndexBase),
+ -ApplyVariation(aState, yx, SatAdd(varIndexBase, 1)),
+ -ApplyVariation(aState, xy, SatAdd(varIndexBase, 2)),
+ ApplyVariation(aState, yy, SatAdd(varIndexBase, 3)),
+ aState.F2P(ApplyVariation(aState, dx, SatAdd(varIndexBase, 4))),
+ -aState.F2P(ApplyVariation(aState, dy, SatAdd(varIndexBase, 5))));
+ };
+};
+
+struct PaintTransformBase {
+ uint8_t format;
+ Offset24 paintOffset;
+
+ bool Paint(const PaintState& aState, uint32_t aOffset,
+ const Rect* aBounds) const {
+ if (!paintOffset) {
+ return true;
+ }
+ AutoRestoreTransform saveTransform(aState.mDrawTarget);
+ aState.mDrawTarget->ConcatTransform(DispatchGetMatrix(aState, aOffset));
+ return DispatchPaint(aState, aOffset + paintOffset, aBounds);
+ }
+
+ Rect GetBoundingRect(const PaintState& aState, uint32_t aOffset) const {
+ if (!paintOffset) {
+ return Rect();
+ }
+ Rect bounds = DispatchGetBounds(aState, aOffset + paintOffset);
+ bounds = DispatchGetMatrix(aState, aOffset).TransformBounds(bounds);
+ return bounds;
+ }
+};
+
+struct PaintTransform : public PaintTransformBase {
+ enum { kFormat = 12 };
+ Offset24 transformOffset;
+
+ Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ if (aOffset + transformOffset + sizeof(Affine2x3) > aState.mCOLRLength) {
+ return Matrix();
+ }
+ const auto* t = reinterpret_cast<const Affine2x3*>(
+ aState.COLRv1BaseAddr() + aOffset + transformOffset);
+ return t->AsMatrix(aState);
+ }
+};
+
+struct PaintVarTransform : public PaintTransformBase {
+ enum { kFormat = 13 };
+ Offset24 transformOffset;
+
+ Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ if (aOffset + transformOffset + sizeof(VarAffine2x3) > aState.mCOLRLength) {
+ return Matrix();
+ }
+ const auto* t = reinterpret_cast<const VarAffine2x3*>(
+ aState.COLRv1BaseAddr() + aOffset + transformOffset);
+ return t->AsMatrix(aState);
+ }
+};
+
+struct PaintTranslate : public PaintTransformBase {
+ enum { kFormat = 14 };
+ FWORD dx;
+ FWORD dy;
+
+ Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ return Matrix::Translation(aState.F2P(int16_t(dx)),
+ -aState.F2P(int16_t(dy)));
+ }
+};
+
+struct PaintVarTranslate : public PaintTranslate {
+ enum { kFormat = 15 };
+ uint32 varIndexBase;
+
+ Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ return Matrix::Translation(
+ aState.F2P(ApplyVariation(aState, int16_t(dx), varIndexBase)),
+ -aState.F2P(
+ ApplyVariation(aState, int16_t(dy), SatAdd(varIndexBase, 1))));
+ }
+};
+
+struct PaintScale : public PaintTransformBase {
+ enum { kFormat = 16 };
+ F2DOT14 scaleX;
+ F2DOT14 scaleY;
+
+ Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ return Matrix::Scaling(float(scaleX), float(scaleY));
+ }
+};
+
+struct PaintVarScale : public PaintScale {
+ enum { kFormat = 17 };
+ uint32 varIndexBase;
+
+ Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ return Matrix::Scaling(
+ ApplyVariation(aState, scaleX, varIndexBase),
+ ApplyVariation(aState, scaleY, SatAdd(varIndexBase, 1)));
+ }
+};
+
+struct PaintScaleAroundCenter : public PaintTransformBase {
+ enum { kFormat = 18 };
+ F2DOT14 scaleX;
+ F2DOT14 scaleY;
+ FWORD centerX;
+ FWORD centerY;
+
+ Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ Point center(aState.F2P(int16_t(centerX)), -aState.F2P(int16_t(centerY)));
+ return Matrix::Translation(center)
+ .PreScale(float(scaleX), float(scaleY))
+ .PreTranslate(-center);
+ }
+};
+
+struct PaintVarScaleAroundCenter : public PaintScaleAroundCenter {
+ enum { kFormat = 19 };
+ uint32 varIndexBase;
+
+ Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ Point center(aState.F2P(ApplyVariation(aState, int16_t(centerX),
+ SatAdd(varIndexBase, 2))),
+ -aState.F2P(ApplyVariation(aState, int16_t(centerY),
+ SatAdd(varIndexBase, 3))));
+ return Matrix::Translation(center)
+ .PreScale(ApplyVariation(aState, scaleX, varIndexBase),
+ ApplyVariation(aState, scaleY, SatAdd(varIndexBase, 1)))
+ .PreTranslate(-center);
+ }
+};
+
+struct PaintScaleUniform : public PaintTransformBase {
+ enum { kFormat = 20 };
+ F2DOT14 scale;
+
+ Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ return Matrix::Scaling(float(scale), float(scale));
+ }
+};
+
+struct PaintVarScaleUniform : public PaintScaleUniform {
+ enum { kFormat = 21 };
+ uint32 varIndexBase;
+
+ Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ float sc = ApplyVariation(aState, scale, varIndexBase);
+ return Matrix::Scaling(sc, sc);
+ }
+};
+
+struct PaintScaleUniformAroundCenter : public PaintTransformBase {
+ enum { kFormat = 22 };
+ F2DOT14 scale;
+ FWORD centerX;
+ FWORD centerY;
+
+ Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ Point center(aState.F2P(int16_t(centerX)), -aState.F2P(int16_t(centerY)));
+ return Matrix::Translation(center)
+ .PreScale(float(scale), float(scale))
+ .PreTranslate(-center);
+ }
+};
+
+struct PaintVarScaleUniformAroundCenter : public PaintScaleUniformAroundCenter {
+ enum { kFormat = 23 };
+ uint32 varIndexBase;
+
+ Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ float sc = ApplyVariation(aState, scale, varIndexBase);
+ Point center(aState.F2P(ApplyVariation(aState, int16_t(centerX),
+ SatAdd(varIndexBase, 1))),
+ -aState.F2P(ApplyVariation(aState, int16_t(centerY),
+ SatAdd(varIndexBase, 2))));
+ return Matrix::Translation(center).PreScale(sc, sc).PreTranslate(-center);
+ }
+};
+
+struct PaintRotate : public PaintTransformBase {
+ enum { kFormat = 24 };
+ F2DOT14 angle;
+
+ Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ return Matrix::Rotation(-float(angle) * float(M_PI));
+ }
+};
+
+struct PaintVarRotate : public PaintRotate {
+ enum { kFormat = 25 };
+ uint32 varIndexBase;
+
+ Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ float ang = ApplyVariation(aState, angle, varIndexBase);
+ return Matrix::Rotation(-ang * float(M_PI));
+ }
+};
+
+struct PaintRotateAroundCenter : public PaintTransformBase {
+ enum { kFormat = 26 };
+ F2DOT14 angle;
+ FWORD centerX;
+ FWORD centerY;
+
+ Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ Point center(aState.F2P(int16_t(centerX)), -aState.F2P(int16_t(centerY)));
+ return Matrix::Translation(center)
+ .PreRotate(-float(angle) * float(M_PI))
+ .PreTranslate(-center);
+ }
+};
+
+struct PaintVarRotateAroundCenter : public PaintRotateAroundCenter {
+ enum { kFormat = 27 };
+ uint32 varIndexBase;
+
+ Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ float ang = ApplyVariation(aState, angle, varIndexBase);
+ Point center(aState.F2P(ApplyVariation(aState, int16_t(centerX),
+ SatAdd(varIndexBase, 1))),
+ -aState.F2P(ApplyVariation(aState, int16_t(centerY),
+ SatAdd(varIndexBase, 2))));
+ return Matrix::Translation(center)
+ .PreRotate(-ang * float(M_PI))
+ .PreTranslate(-center);
+ }
+};
+
+static inline Matrix SkewMatrix(float aSkewX, float aSkewY) {
+ float xy = tanf(aSkewX * float(M_PI));
+ float yx = tanf(aSkewY * float(M_PI));
+ return std::isnan(xy) || std::isnan(yx) ? Matrix()
+ : Matrix(1.0, -yx, xy, 1.0, 0.0, 0.0);
+}
+
+struct PaintSkew : public PaintTransformBase {
+ enum { kFormat = 28 };
+ F2DOT14 xSkewAngle;
+ F2DOT14 ySkewAngle;
+
+ Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ return SkewMatrix(float(xSkewAngle), float(ySkewAngle));
+ }
+};
+
+struct PaintVarSkew : public PaintSkew {
+ enum { kFormat = 29 };
+ uint32 varIndexBase;
+
+ Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ return SkewMatrix(
+ float(ApplyVariation(aState, xSkewAngle, varIndexBase)),
+ float(ApplyVariation(aState, ySkewAngle, SatAdd(varIndexBase, 1))));
+ }
+};
+
+struct PaintSkewAroundCenter : public PaintTransformBase {
+ enum { kFormat = 30 };
+ F2DOT14 xSkewAngle;
+ F2DOT14 ySkewAngle;
+ FWORD centerX;
+ FWORD centerY;
+
+ Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ Point center(aState.F2P(int16_t(centerX)), -aState.F2P(int16_t(centerY)));
+ return Matrix::Translation(center)
+ .PreMultiply(SkewMatrix(float(xSkewAngle), float(ySkewAngle)))
+ .PreTranslate(-center);
+ }
+};
+
+struct PaintVarSkewAroundCenter : public PaintSkewAroundCenter {
+ enum { kFormat = 31 };
+ uint32 varIndexBase;
+
+ Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
+ MOZ_ASSERT(format == kFormat);
+ Point center(aState.F2P(ApplyVariation(aState, int16_t(centerX),
+ SatAdd(varIndexBase, 2))),
+ -aState.F2P(ApplyVariation(aState, int16_t(centerY),
+ SatAdd(varIndexBase, 3))));
+ return Matrix::Translation(center)
+ .PreMultiply(SkewMatrix(
+ ApplyVariation(aState, xSkewAngle, varIndexBase),
+ ApplyVariation(aState, ySkewAngle, SatAdd(varIndexBase, 1))))
+ .PreTranslate(-center);
+ }
+};
+
+struct PaintComposite {
+ enum { kFormat = 32 };
+ uint8_t format;
+ Offset24 sourcePaintOffset;
+ uint8_t compositeMode;
+ Offset24 backdropPaintOffset;
+
+ enum {
+ COMPOSITE_CLEAR = 0,
+ COMPOSITE_SRC = 1,
+ COMPOSITE_DEST = 2,
+ COMPOSITE_SRC_OVER = 3,
+ COMPOSITE_DEST_OVER = 4,
+ COMPOSITE_SRC_IN = 5,
+ COMPOSITE_DEST_IN = 6,
+ COMPOSITE_SRC_OUT = 7,
+ COMPOSITE_DEST_OUT = 8,
+ COMPOSITE_SRC_ATOP = 9,
+ COMPOSITE_DEST_ATOP = 10,
+ COMPOSITE_XOR = 11,
+ COMPOSITE_PLUS = 12,
+ COMPOSITE_SCREEN = 13,
+ COMPOSITE_OVERLAY = 14,
+ COMPOSITE_DARKEN = 15,
+ COMPOSITE_LIGHTEN = 16,
+ COMPOSITE_COLOR_DODGE = 17,
+ COMPOSITE_COLOR_BURN = 18,
+ COMPOSITE_HARD_LIGHT = 19,
+ COMPOSITE_SOFT_LIGHT = 20,
+ COMPOSITE_DIFFERENCE = 21,
+ COMPOSITE_EXCLUSION = 22,
+ COMPOSITE_MULTIPLY = 23,
+ COMPOSITE_HSL_HUE = 24,
+ COMPOSITE_HSL_SATURATION = 25,
+ COMPOSITE_HSL_COLOR = 26,
+ COMPOSITE_HSL_LUMINOSITY = 27
+ };
+
+ bool Paint(const PaintState& aState, uint32_t aOffset,
+ const Rect* aBounds) const {
+ MOZ_ASSERT(format == kFormat);
+ if (!backdropPaintOffset || !sourcePaintOffset) {
+ return true;
+ }
+ auto mapCompositionMode = [](uint8_t aMode) -> CompositionOp {
+ switch (aMode) {
+ default:
+ return CompositionOp::OP_SOURCE;
+ case COMPOSITE_CLEAR:
+ case COMPOSITE_SRC:
+ case COMPOSITE_DEST:
+ MOZ_ASSERT_UNREACHABLE("should have short-circuited");
+ return CompositionOp::OP_SOURCE;
+ case COMPOSITE_SRC_OVER:
+ return CompositionOp::OP_OVER;
+ case COMPOSITE_DEST_OVER:
+ return CompositionOp::OP_DEST_OVER;
+ case COMPOSITE_SRC_IN:
+ return CompositionOp::OP_IN;
+ case COMPOSITE_DEST_IN:
+ return CompositionOp::OP_DEST_IN;
+ case COMPOSITE_SRC_OUT:
+ return CompositionOp::OP_OUT;
+ case COMPOSITE_DEST_OUT:
+ return CompositionOp::OP_DEST_OUT;
+ case COMPOSITE_SRC_ATOP:
+ return CompositionOp::OP_ATOP;
+ case COMPOSITE_DEST_ATOP:
+ return CompositionOp::OP_DEST_ATOP;
+ case COMPOSITE_XOR:
+ return CompositionOp::OP_XOR;
+ case COMPOSITE_PLUS:
+ return CompositionOp::OP_ADD;
+ case COMPOSITE_SCREEN:
+ return CompositionOp::OP_SCREEN;
+ case COMPOSITE_OVERLAY:
+ return CompositionOp::OP_OVERLAY;
+ case COMPOSITE_DARKEN:
+ return CompositionOp::OP_DARKEN;
+ case COMPOSITE_LIGHTEN:
+ return CompositionOp::OP_LIGHTEN;
+ case COMPOSITE_COLOR_DODGE:
+ return CompositionOp::OP_COLOR_DODGE;
+ case COMPOSITE_COLOR_BURN:
+ return CompositionOp::OP_COLOR_BURN;
+ case COMPOSITE_HARD_LIGHT:
+ return CompositionOp::OP_HARD_LIGHT;
+ case COMPOSITE_SOFT_LIGHT:
+ return CompositionOp::OP_SOFT_LIGHT;
+ case COMPOSITE_DIFFERENCE:
+ return CompositionOp::OP_DIFFERENCE;
+ case COMPOSITE_EXCLUSION:
+ return CompositionOp::OP_EXCLUSION;
+ case COMPOSITE_MULTIPLY:
+ return CompositionOp::OP_MULTIPLY;
+ case COMPOSITE_HSL_HUE:
+ return CompositionOp::OP_HUE;
+ case COMPOSITE_HSL_SATURATION:
+ return CompositionOp::OP_SATURATION;
+ case COMPOSITE_HSL_COLOR:
+ return CompositionOp::OP_COLOR;
+ case COMPOSITE_HSL_LUMINOSITY:
+ return CompositionOp::OP_LUMINOSITY;
+ }
+ };
+ // Short-circuit cases:
+ if (compositeMode == COMPOSITE_CLEAR) {
+ return true;
+ }
+ if (compositeMode == COMPOSITE_SRC) {
+ return DispatchPaint(aState, aOffset + sourcePaintOffset, aBounds);
+ }
+ if (compositeMode == COMPOSITE_DEST) {
+ return DispatchPaint(aState, aOffset + backdropPaintOffset, aBounds);
+ }
+
+ // We need bounds for the temporary surfaces; so if we didn't have
+ // explicitly-provided bounds from a clipList entry for the top-level
+ // glyph, then we need to determine the bounding rect here.
+ Rect bounds = aBounds ? *aBounds : GetBoundingRect(aState, aOffset);
+ if (bounds.IsEmpty()) {
+ return true;
+ }
+ bounds.RoundOut();
+
+ PaintState state = aState;
+ state.mDrawOptions.mCompositionOp = CompositionOp::OP_OVER;
+ IntSize intSize(int(bounds.width), int(bounds.height));
+
+ if (!aState.mDrawTarget->CanCreateSimilarDrawTarget(
+ intSize, SurfaceFormat::B8G8R8A8)) {
+ // We're not going to be able to render this, so just bail out.
+ // (Returning true rather than false means we'll just not paint this
+ // part of the glyph, but won't return an error and likely fall back
+ // to an ugly black blob.)
+ return true;
+ }
+
+ // Draw the backdrop paint graph to a temporary surface.
+ RefPtr backdrop = aState.mDrawTarget->CreateSimilarDrawTarget(
+ intSize, SurfaceFormat::B8G8R8A8);
+ if (!backdrop) {
+ return true;
+ }
+ backdrop->SetTransform(Matrix::Translation(-bounds.TopLeft()));
+ state.mDrawTarget = backdrop;
+ if (!DispatchPaint(state, aOffset + backdropPaintOffset, &bounds)) {
+ return false;
+ }
+
+ // Draw the source paint graph to another temp surface.
+ RefPtr source = aState.mDrawTarget->CreateSimilarDrawTarget(
+ intSize, SurfaceFormat::B8G8R8A8);
+ if (!source) {
+ return true;
+ }
+ source->SetTransform(Matrix::Translation(-bounds.TopLeft()));
+ state.mDrawTarget = source;
+ if (!DispatchPaint(state, aOffset + sourcePaintOffset, &bounds)) {
+ return false;
+ }
+
+ // Composite the source onto the backdrop using the specified operation.
+ Rect localBounds(Point(), bounds.Size());
+ RefPtr snapshot = source->Snapshot();
+ backdrop->SetTransform(Matrix());
+ backdrop->DrawSurface(snapshot, localBounds, localBounds,
+ DrawSurfaceOptions(),
+ DrawOptions(1.0, mapCompositionMode(compositeMode)));
+
+ // And copy the composited result to our final destination.
+ snapshot = backdrop->Snapshot();
+ aState.mDrawTarget->DrawSurface(snapshot, bounds, localBounds);
+
+ return true;
+ }
+
+ Rect GetBoundingRect(const PaintState& aState, uint32_t aOffset) const {
+ if (!backdropPaintOffset || !sourcePaintOffset) {
+ return Rect();
+ }
+ // For now, just return the maximal bounds that could result; this could be
+ // smarter, returning just one of the rects or their intersection when
+ // appropriate for the composite mode in effect.
+ return DispatchGetBounds(aState, aOffset + backdropPaintOffset)
+ .Union(DispatchGetBounds(aState, aOffset + sourcePaintOffset));
+ }
+};
+
+#pragma pack()
+
+const BaseGlyphPaintRecord* COLRv1Header::GetBaseGlyphPaint(
+ uint32_t aGlyphId) const {
+ const auto* list = baseGlyphList();
+ if (!list) {
+ return nullptr;
+ }
+ auto compare = [](const void* key, const void* data) -> int {
+ uint32_t glyphId = (uint32_t)(uintptr_t)key;
+ const auto* paintRecord =
+ reinterpret_cast<const BaseGlyphPaintRecord*>(data);
+ uint32_t baseGlyphId = uint16_t(paintRecord->glyphID);
+ if (baseGlyphId == glyphId) {
+ return 0;
+ }
+ return baseGlyphId > glyphId ? -1 : 1;
+ };
+ return reinterpret_cast<const BaseGlyphPaintRecord*>(
+ bsearch((void*)(uintptr_t)aGlyphId, list + 1,
+ uint32_t(list->numBaseGlyphPaintRecords),
+ sizeof(BaseGlyphPaintRecord), compare));
+}
+
+#define DO_CASE_VAR(T) \
+ DO_CASE(Paint##T); \
+ DO_CASE(PaintVar##T)
+
+// Process paint table at aOffset from start of COLRv1 table.
+static bool DispatchPaint(const PaintState& aState, uint32_t aOffset,
+ const Rect* aBounds) {
+ if (aOffset >= aState.mCOLRLength) {
+ return false;
+ }
+
+ const char* paint = aState.COLRv1BaseAddr() + aOffset;
+ // All paint table formats start with an 8-bit 'format' field.
+ uint8_t format = uint8_t(*paint);
+
+#define DO_CASE(T) \
+ case T::kFormat: \
+ return aOffset + sizeof(T) <= aState.mCOLRLength \
+ ? reinterpret_cast<const T*>(paint)->Paint(aState, aOffset, \
+ aBounds) \
+ : false
+
+ switch (format) {
+ DO_CASE(PaintColrLayers);
+ DO_CASE_VAR(Solid);
+ DO_CASE_VAR(LinearGradient);
+ DO_CASE_VAR(RadialGradient);
+ DO_CASE_VAR(SweepGradient);
+ DO_CASE(PaintGlyph);
+ DO_CASE(PaintColrGlyph);
+ DO_CASE_VAR(Transform);
+ DO_CASE_VAR(Translate);
+ DO_CASE_VAR(Scale);
+ DO_CASE_VAR(ScaleAroundCenter);
+ DO_CASE_VAR(ScaleUniform);
+ DO_CASE_VAR(ScaleUniformAroundCenter);
+ DO_CASE_VAR(Rotate);
+ DO_CASE_VAR(RotateAroundCenter);
+ DO_CASE_VAR(Skew);
+ DO_CASE_VAR(SkewAroundCenter);
+ DO_CASE(PaintComposite);
+ default:
+ break;
+ }
+
+#undef DO_CASE
+
+ return false;
+}
+
+// Get a gfx::Pattern corresponding to the given paint table, if it is a
+// simple format that can be used as a fill (not a sub-graph).
+static UniquePtr<Pattern> DispatchMakePattern(const PaintState& aState,
+ uint32_t aOffset) {
+ if (aOffset >= aState.mCOLRLength) {
+ return nullptr;
+ }
+
+ const char* paint = aState.COLRv1BaseAddr() + aOffset;
+ // All paint table formats start with an 8-bit 'format' field.
+ uint8_t format = uint8_t(*paint);
+
+#define DO_CASE(T) \
+ case T::kFormat: \
+ return aOffset + sizeof(T) <= aState.mCOLRLength \
+ ? reinterpret_cast<const T*>(paint)->MakePattern(aState, \
+ aOffset) \
+ : nullptr;
+
+ switch (format) {
+ DO_CASE_VAR(Solid);
+ DO_CASE_VAR(LinearGradient);
+ DO_CASE_VAR(RadialGradient);
+ DO_CASE_VAR(SweepGradient);
+ default:
+ break;
+ }
+
+#undef DO_CASE
+
+ return nullptr;
+}
+
+static Matrix DispatchGetMatrix(const PaintState& aState, uint32_t aOffset) {
+ if (aOffset >= aState.mCOLRLength) {
+ return Matrix();
+ }
+
+ const char* paint = aState.COLRv1BaseAddr() + aOffset;
+ // All paint table formats start with an 8-bit 'format' field.
+ uint8_t format = uint8_t(*paint);
+
+#define DO_CASE(T) \
+ case T::kFormat: \
+ return aOffset + sizeof(T) <= aState.mCOLRLength \
+ ? reinterpret_cast<const T*>(paint)->GetMatrix(aState, aOffset) \
+ : Matrix();
+
+ switch (format) {
+ DO_CASE_VAR(Transform);
+ DO_CASE_VAR(Translate);
+ DO_CASE_VAR(Scale);
+ DO_CASE_VAR(ScaleAroundCenter);
+ DO_CASE_VAR(ScaleUniform);
+ DO_CASE_VAR(ScaleUniformAroundCenter);
+ DO_CASE_VAR(Rotate);
+ DO_CASE_VAR(RotateAroundCenter);
+ DO_CASE_VAR(Skew);
+ DO_CASE_VAR(SkewAroundCenter);
+ default:
+ break;
+ }
+
+#undef DO_CASE
+
+ return Matrix();
+}
+
+static Rect DispatchGetBounds(const PaintState& aState, uint32_t aOffset) {
+ if (aOffset >= aState.mCOLRLength) {
+ return Rect();
+ }
+
+ const char* paint = aState.COLRv1BaseAddr() + aOffset;
+ // All paint table formats start with an 8-bit 'format' field.
+ uint8_t format = uint8_t(*paint);
+
+#define DO_CASE(T) \
+ case T::kFormat: \
+ return aOffset + sizeof(T) <= aState.mCOLRLength \
+ ? reinterpret_cast<const T*>(paint)->GetBoundingRect(aState, \
+ aOffset) \
+ : Rect();
+
+ switch (format) {
+ DO_CASE(PaintColrLayers);
+ DO_CASE_VAR(Solid);
+ DO_CASE_VAR(LinearGradient);
+ DO_CASE_VAR(RadialGradient);
+ DO_CASE_VAR(SweepGradient);
+ DO_CASE(PaintGlyph);
+ DO_CASE(PaintColrGlyph);
+ DO_CASE_VAR(Transform);
+ DO_CASE_VAR(Translate);
+ DO_CASE_VAR(Scale);
+ DO_CASE_VAR(ScaleAroundCenter);
+ DO_CASE_VAR(ScaleUniform);
+ DO_CASE_VAR(ScaleUniformAroundCenter);
+ DO_CASE_VAR(Rotate);
+ DO_CASE_VAR(RotateAroundCenter);
+ DO_CASE_VAR(Skew);
+ DO_CASE_VAR(SkewAroundCenter);
+ DO_CASE(PaintComposite);
+ default:
+ break;
+ }
+
+#undef DO_CASE
+
+ return Rect();
+}
+
+#undef DO_CASE_VAR
+
+bool COLRHeader::Validate(uint64_t aLength) const {
+ uint64_t count;
+ if ((count = numBaseGlyphRecords)) {
+ if (baseGlyphRecordsOffset + count * sizeof(BaseGlyphRecord) > aLength) {
+ return false;
+ }
+ }
+ if ((count = numLayerRecords)) {
+ if (layerRecordsOffset + count * sizeof(LayerRecord) > aLength) {
+ return false;
+ }
+ }
+ // Check ordering of baseGlyphRecords, and that layer indices are in bounds.
+ int32_t lastGlyphId = -1;
+ const auto* baseGlyph = reinterpret_cast<const BaseGlyphRecord*>(
+ reinterpret_cast<const char*>(this) + baseGlyphRecordsOffset);
+ for (uint16_t i = 0; i < uint16_t(numBaseGlyphRecords); i++, baseGlyph++) {
+ uint16_t glyphId = baseGlyph->glyphId;
+ if (lastGlyphId >= int32_t(glyphId)) {
+ return false;
+ }
+ if (uint32_t(baseGlyph->firstLayerIndex) + uint32_t(baseGlyph->numLayers) >
+ uint32_t(numLayerRecords)) {
+ return false;
+ }
+ lastGlyphId = glyphId;
+ }
+ // We don't need to validate all the layer paletteEntryIndex fields here,
+ // because PaintState.GetColor will range-check them at paint time.
+ return true;
+}
+
+bool COLRv1Header::Validate(uint64_t aLength) const {
+ if (!base.Validate(aLength)) {
+ return false;
+ }
+ if (baseGlyphListOffset + sizeof(BaseGlyphList) > aLength ||
+ layerListOffset + sizeof(LayerList) > aLength ||
+ clipListOffset + sizeof(ClipList) > aLength ||
+ varIndexMapOffset + sizeof(DeltaSetIndexMap) > aLength ||
+ itemVariationStoreOffset + sizeof(ItemVariationStore) > aLength) {
+ return false;
+ }
+ const auto* b = baseGlyphList();
+ if (b && !b->Validate(this, aLength)) {
+ return false;
+ }
+ const auto* l = layerList();
+ if (l && !l->Validate(this, aLength)) {
+ return false;
+ }
+ const auto* c = clipList();
+ if (c && !c->Validate(this, aLength)) {
+ return false;
+ }
+ const auto* v = varIndexMap();
+ if (v && !v->Validate(this, aLength)) {
+ return false;
+ }
+ const auto* i = itemVariationStore();
+ if (i && !i->Validate(this, aLength)) {
+ return false;
+ }
+ return true;
+}
+
+bool BaseGlyphList::Validate(const COLRv1Header* aHeader,
+ uint64_t aLength) const {
+ uint64_t count = numBaseGlyphPaintRecords;
+ if (aHeader->baseGlyphListOffset + sizeof(BaseGlyphList) +
+ count * sizeof(BaseGlyphPaintRecord) >
+ aLength) {
+ return false;
+ }
+ // Check ordering of baseGlyphPaint records.
+ const auto* records = baseGlyphPaintRecords();
+ int32_t prevGlyphID = -1;
+ for (uint32_t i = 0; i < numBaseGlyphPaintRecords; ++i) {
+ const auto& base = records[i];
+ if (int32_t(uint16_t(base.glyphID)) <= prevGlyphID) {
+ return false;
+ }
+ prevGlyphID = base.glyphID;
+ }
+ return true;
+}
+
+bool LayerList::Validate(const COLRv1Header* aHeader, uint64_t aLength) const {
+ // Check that paintOffsets array fits.
+ uint64_t count = numLayers;
+ uint32_t listOffset = aHeader->layerListOffset;
+ if (listOffset + sizeof(LayerList) + count * sizeof(uint32) > aLength) {
+ return false;
+ }
+ // Check that values in paintOffsets are within bounds.
+ const auto* offsets = paintOffsets();
+ for (uint32_t i = 0; i < count; i++) {
+ if (listOffset + offsets[i] >= aLength) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool Clip::Validate(const COLRv1Header* aHeader, uint64_t aLength) const {
+ uint32_t offset = aHeader->clipListOffset + clipBoxOffset;
+ if (offset >= aLength) {
+ return false;
+ }
+ // ClipBox format begins with a 1-byte format field.
+ const uint8_t* box = reinterpret_cast<const uint8_t*>(aHeader) + offset;
+ switch (*box) {
+ case 1:
+ if (offset <= aLength - sizeof(ClipBoxFormat1)) {
+ return true;
+ }
+ break;
+ case 2:
+ if (offset <= aLength - sizeof(ClipBoxFormat2)) {
+ return true;
+ }
+ break;
+ default:
+ // unknown ClipBoxFormat
+ break;
+ }
+ return false;
+}
+
+bool ClipList::Validate(const COLRv1Header* aHeader, uint64_t aLength) const {
+ uint64_t count = numClips;
+ if (aHeader->clipListOffset + sizeof(ClipList) + count * sizeof(Clip) >
+ aLength) {
+ return false;
+ }
+ // Check ordering of clip records, and that they are within bounds.
+ const auto* clipArray = clips();
+ int32_t prevEnd = -1;
+ for (uint32_t i = 0; i < count; ++i) {
+ const auto& clip = clipArray[i];
+ if (int32_t(uint16_t(clip.startGlyphID)) <= prevEnd) {
+ return false;
+ }
+ if (!clip.Validate(aHeader, aLength)) {
+ return false;
+ }
+ prevEnd = uint16_t(clip.endGlyphID);
+ }
+ return true;
+}
+
+bool DeltaSetIndexMap::Validate(const COLRv1Header* aHeader,
+ uint64_t aLength) const {
+ uint64_t entrySize = ((entryFormat & MAP_ENTRY_SIZE_MASK) >> 4) + 1;
+ uint64_t mapCount;
+ uint64_t baseSize;
+ switch (format) {
+ case 0:
+ mapCount = uint32_t(v0.mapCount);
+ baseSize = 4;
+ break;
+ case 1:
+ mapCount = uint32_t(v1.mapCount);
+ baseSize = 6;
+ break;
+ default:
+ return false;
+ }
+ if (aHeader->varIndexMapOffset + baseSize + mapCount * entrySize > aLength) {
+ return false;
+ }
+ return true;
+}
+
+bool ItemVariationStore::Validate(const COLRv1Header* aHeader,
+ uint64_t aLength) const {
+ uint64_t offset = reinterpret_cast<const char*>(this) -
+ reinterpret_cast<const char*>(aHeader);
+ if (offset + variationRegionListOffset + sizeof(VariationRegionList) >
+ aLength) {
+ return false;
+ }
+ if (!variationRegionList()->Validate(aHeader, aLength)) {
+ return false;
+ }
+ uint16_t count = itemVariationDataCount;
+ if (offset + sizeof(ItemVariationStore) + count * sizeof(Offset32) >
+ aLength) {
+ return false;
+ }
+ const auto* ivdOffsets = itemVariationDataOffsets();
+ for (uint16_t i = 0; i < count; ++i) {
+ uint32_t o = ivdOffsets[i];
+ if (offset + o + sizeof(ItemVariationData) > aLength) {
+ return false;
+ }
+ const auto* variationData = reinterpret_cast<const ItemVariationData*>(
+ reinterpret_cast<const char*>(this) + ivdOffsets[i]);
+ if (!variationData->Validate(aHeader, aLength)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool ItemVariationData::Validate(const COLRv1Header* aHeader,
+ uint64_t aLength) const {
+ if (reinterpret_cast<const char*>(regionIndexes() +
+ uint16_t(regionIndexCount)) >
+ reinterpret_cast<const char*>(aHeader) + aLength) {
+ return false;
+ }
+ uint16_t wordDeltaCount = this->wordDeltaCount;
+ bool longWords = wordDeltaCount & LONG_WORDS;
+ wordDeltaCount &= WORD_DELTA_COUNT_MASK;
+ uint32_t deltaSetSize =
+ (uint16_t(regionIndexCount) + uint16_t(wordDeltaCount)) << longWords;
+ if (reinterpret_cast<const char*>(deltaSets()) +
+ uint16_t(itemCount) * deltaSetSize >
+ reinterpret_cast<const char*>(aHeader) + aLength) {
+ return false;
+ }
+ return true;
+}
+
+} // end anonymous namespace
+
+namespace mozilla::gfx {
+
+bool COLRFonts::ValidateColorGlyphs(hb_blob_t* aCOLR, hb_blob_t* aCPAL) {
+ struct ColorRecord {
+ uint8_t blue;
+ uint8_t green;
+ uint8_t red;
+ uint8_t alpha;
+ };
+
+ struct CPALHeaderVersion0 {
+ uint16 version;
+ uint16 numPaletteEntries;
+ uint16 numPalettes;
+ uint16 numColorRecords;
+ Offset32 colorRecordsArrayOffset;
+ // uint16 colorRecordIndices[numPalettes];
+ const uint16* colorRecordIndices() const {
+ return reinterpret_cast<const uint16*>(this + 1);
+ }
+ };
+
+ unsigned int cpalLength;
+ const CPALHeaderVersion0* cpal = reinterpret_cast<const CPALHeaderVersion0*>(
+ hb_blob_get_data(aCPAL, &cpalLength));
+ if (!cpal || cpalLength < sizeof(CPALHeaderVersion0)) {
+ return false;
+ }
+
+ // We can handle either version 0 or 1.
+ if (uint16_t(cpal->version) > 1) {
+ return false;
+ }
+
+ uint16_t numPaletteEntries = cpal->numPaletteEntries;
+ uint16_t numPalettes = cpal->numPalettes;
+ uint16_t numColorRecords = cpal->numColorRecords;
+ uint32_t colorRecordsArrayOffset = cpal->colorRecordsArrayOffset;
+ const auto* indices = cpal->colorRecordIndices();
+ if (colorRecordsArrayOffset >= cpalLength) {
+ return false;
+ }
+ if (!numPaletteEntries || !numPalettes ||
+ numColorRecords < numPaletteEntries) {
+ return false;
+ }
+ if (sizeof(ColorRecord) * numColorRecords >
+ cpalLength - colorRecordsArrayOffset) {
+ return false;
+ }
+ if (sizeof(uint16) * numPalettes > cpalLength - sizeof(CPALHeaderVersion0)) {
+ return false;
+ }
+ for (uint16_t i = 0; i < numPalettes; ++i) {
+ uint32_t index = indices[i];
+ if (index + numPaletteEntries > numColorRecords) {
+ return false;
+ }
+ }
+
+ // The additional fields in CPALv1 are not checked here; the harfbuzz code
+ // handles reading them safely.
+
+ unsigned int colrLength;
+ const COLRHeader* colr =
+ reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &colrLength));
+ if (!colr || colrLength < sizeof(COLRHeader)) {
+ return false;
+ }
+
+ if (uint16_t(colr->version) == 1) {
+ return StaticPrefs::gfx_font_rendering_colr_v1_enabled() &&
+ colrLength >= sizeof(COLRv1Header) &&
+ reinterpret_cast<const COLRv1Header*>(colr)->Validate(colrLength);
+ }
+
+ if (uint16_t(colr->version) != 0) {
+ // We only support version 1 (above) or version 0 headers.
+ return false;
+ }
+
+ return colr->Validate(colrLength);
+}
+
+const COLRFonts::GlyphLayers* COLRFonts::GetGlyphLayers(hb_blob_t* aCOLR,
+ uint32_t aGlyphId) {
+ unsigned int length;
+ const COLRHeader* colr =
+ reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &length));
+ // This should never be called unless we have checked that the COLR table is
+ // structurally valid, so it will be safe to read the header fields.
+ MOZ_RELEASE_ASSERT(colr && length >= sizeof(COLRHeader), "bad COLR table!");
+ auto compareBaseGlyph = [](const void* key, const void* data) -> int {
+ uint32_t glyphId = (uint32_t)(uintptr_t)key;
+ const auto* baseGlyph = reinterpret_cast<const BaseGlyphRecord*>(data);
+ uint32_t baseGlyphId = uint16_t(baseGlyph->glyphId);
+ if (baseGlyphId == glyphId) {
+ return 0;
+ }
+ return baseGlyphId > glyphId ? -1 : 1;
+ };
+ return reinterpret_cast<const GlyphLayers*>(
+ bsearch((void*)(uintptr_t)aGlyphId, colr->GetBaseGlyphRecords(),
+ uint16_t(colr->numBaseGlyphRecords), sizeof(BaseGlyphRecord),
+ compareBaseGlyph));
+}
+
+bool COLRFonts::PaintGlyphLayers(
+ hb_blob_t* aCOLR, hb_face_t* aFace, const GlyphLayers* aLayers,
+ DrawTarget* aDrawTarget, layout::TextDrawTarget* aTextDrawer,
+ ScaledFont* aScaledFont, DrawOptions aDrawOptions, const Point& aPoint,
+ const sRGBColor& aCurrentColor, const nsTArray<sRGBColor>* aColors) {
+ const auto* glyphRecord = reinterpret_cast<const BaseGlyphRecord*>(aLayers);
+ // Default to opaque rendering (non-webrender applies alpha with a layer)
+ float alpha = 1.0;
+ if (aTextDrawer) {
+ // defaultColor is the one that comes from CSS, so it has transparency info.
+ bool hasComplexTransparency =
+ 0.0 < aCurrentColor.a && aCurrentColor.a < 1.0;
+ if (hasComplexTransparency && uint16_t(glyphRecord->numLayers) > 1) {
+ // WebRender doesn't support drawing multi-layer transparent color-glyphs,
+ // as it requires compositing all the layers before applying transparency.
+ // (pretend to succeed, output doesn't matter, we will emit a blob)
+ aTextDrawer->FoundUnsupportedFeature();
+ return true;
+ }
+
+ // If we get here, then either alpha is 0 or 1, or there's only one layer
+ // which shouldn't have composition issues. In all of these cases, applying
+ // transparency directly to the glyph should work perfectly fine.
+ //
+ // Note that we must still emit completely transparent emoji, because they
+ // might be wrapped in a shadow that uses the text run's glyphs.
+ alpha = aCurrentColor.a;
+ }
+
+ unsigned int length;
+ const COLRHeader* colr =
+ reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &length));
+ PaintState state{{colr},
+ aColors->Elements(),
+ aDrawTarget,
+ aScaledFont,
+ nullptr, // variations not needed
+ aDrawOptions,
+ length,
+ aCurrentColor,
+ 0.0, // fontUnitsToPixels not needed
+ uint16_t(aColors->Length()),
+ 0,
+ nullptr};
+ return glyphRecord->Paint(state, alpha, aPoint);
+}
+
+const COLRFonts::GlyphPaintGraph* COLRFonts::GetGlyphPaintGraph(
+ hb_blob_t* aCOLR, uint32_t aGlyphId) {
+ if (!StaticPrefs::gfx_font_rendering_colr_v1_enabled()) {
+ return nullptr;
+ }
+ unsigned int blobLength;
+ const auto* colr =
+ reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &blobLength));
+ MOZ_ASSERT(colr, "Cannot get COLR raw data");
+ MOZ_ASSERT(blobLength >= sizeof(COLRHeader), "COLR data too small");
+
+ uint16_t version = colr->version;
+ if (version == 1) {
+ MOZ_ASSERT(blobLength >= sizeof(COLRv1Header), "COLRv1 data too small");
+ const auto* colrv1 = reinterpret_cast<const COLRv1Header*>(colr);
+ return reinterpret_cast<const GlyphPaintGraph*>(
+ colrv1->GetBaseGlyphPaint(aGlyphId));
+ }
+
+ return nullptr;
+}
+
+bool COLRFonts::PaintGlyphGraph(
+ hb_blob_t* aCOLR, hb_font_t* aFont, const GlyphPaintGraph* aPaintGraph,
+ DrawTarget* aDrawTarget, layout::TextDrawTarget* aTextDrawer,
+ ScaledFont* aScaledFont, DrawOptions aDrawOptions, const Point& aPoint,
+ const sRGBColor& aCurrentColor, const nsTArray<sRGBColor>* aColors,
+ uint32_t aGlyphId, float aFontUnitsToPixels) {
+ if (aTextDrawer) {
+ // Currently we always punt to a blob for COLRv1 glyphs.
+ aTextDrawer->FoundUnsupportedFeature();
+ return true;
+ }
+
+ unsigned int coordCount;
+ const int* coords = hb_font_get_var_coords_normalized(aFont, &coordCount);
+
+ AutoTArray<uint32_t, 32> visitedOffsets;
+ PaintState state{{nullptr},
+ aColors->Elements(),
+ aDrawTarget,
+ aScaledFont,
+ coords,
+ aDrawOptions,
+ hb_blob_get_length(aCOLR),
+ aCurrentColor,
+ aFontUnitsToPixels,
+ uint16_t(aColors->Length()),
+ uint16_t(coordCount),
+ &visitedOffsets};
+ state.mHeader.v1 =
+ reinterpret_cast<const COLRv1Header*>(hb_blob_get_data(aCOLR, nullptr));
+ AutoRestoreTransform saveTransform(aDrawTarget);
+ aDrawTarget->ConcatTransform(Matrix::Translation(aPoint));
+ return PaintColrGlyph::DoPaint(
+ state, reinterpret_cast<const BaseGlyphPaintRecord*>(aPaintGraph),
+ aGlyphId, nullptr);
+}
+
+Rect COLRFonts::GetColorGlyphBounds(hb_blob_t* aCOLR, hb_font_t* aFont,
+ uint32_t aGlyphId, DrawTarget* aDrawTarget,
+ ScaledFont* aScaledFont,
+ float aFontUnitsToPixels) {
+ unsigned int coordCount;
+ const int* coords = hb_font_get_var_coords_normalized(aFont, &coordCount);
+
+ AutoTArray<uint32_t, 32> visitedOffsets;
+ PaintState state{{nullptr},
+ nullptr, // palette is not needed
+ aDrawTarget,
+ aScaledFont,
+ coords,
+ DrawOptions(),
+ hb_blob_get_length(aCOLR),
+ sRGBColor(),
+ aFontUnitsToPixels,
+ 0, // numPaletteEntries
+ uint16_t(coordCount),
+ &visitedOffsets};
+ state.mHeader.v1 =
+ reinterpret_cast<const COLRv1Header*>(hb_blob_get_data(aCOLR, nullptr));
+ MOZ_ASSERT(uint16_t(state.mHeader.v1->base.version) == 1);
+ // If a clip rect is provided, return this as the glyph bounds.
+ const auto* clipList = state.mHeader.v1->clipList();
+ if (clipList) {
+ const auto* clip = clipList->GetClip(aGlyphId);
+ if (clip) {
+ return clip->GetRect(state);
+ }
+ }
+ // Otherwise, compute bounds by walking the paint graph.
+ const auto* base = state.mHeader.v1->GetBaseGlyphPaint(aGlyphId);
+ if (base) {
+ return DispatchGetBounds(
+ state, state.mHeader.v1->baseGlyphListOffset + base->paintOffset);
+ }
+ return Rect();
+}
+
+uint16_t COLRFonts::GetColrTableVersion(hb_blob_t* aCOLR) {
+ unsigned int blobLength;
+ const auto* colr =
+ reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &blobLength));
+ MOZ_ASSERT(colr, "Cannot get COLR raw data");
+ MOZ_ASSERT(blobLength >= sizeof(COLRHeader), "COLR data too small");
+ return colr->version;
+}
+
+UniquePtr<nsTArray<sRGBColor>> COLRFonts::SetupColorPalette(
+ hb_face_t* aFace, const FontPaletteValueSet* aPaletteValueSet,
+ nsAtom* aFontPalette, const nsACString& aFamilyName) {
+ // Find the base color palette to use, if there are multiple available;
+ // default to first in the font, if nothing matches what is requested.
+ unsigned int paletteIndex = 0;
+ unsigned int count = hb_ot_color_palette_get_count(aFace);
+ MOZ_ASSERT(count > 0, "No palettes? Font should have been rejected!");
+
+ const FontPaletteValueSet::PaletteValues* fpv = nullptr;
+ if (aFontPalette && aFontPalette != nsGkAtoms::normal &&
+ (count > 1 || aPaletteValueSet)) {
+ auto findPalette = [&](hb_ot_color_palette_flags_t flag) -> unsigned int {
+ MOZ_ASSERT(flag != HB_OT_COLOR_PALETTE_FLAG_DEFAULT);
+ for (unsigned int i = 0; i < count; ++i) {
+ if (hb_ot_color_palette_get_flags(aFace, i) & flag) {
+ return i;
+ }
+ }
+ return 0;
+ };
+
+ if (aFontPalette == nsGkAtoms::light) {
+ paletteIndex =
+ findPalette(HB_OT_COLOR_PALETTE_FLAG_USABLE_WITH_LIGHT_BACKGROUND);
+ } else if (aFontPalette == nsGkAtoms::dark) {
+ paletteIndex =
+ findPalette(HB_OT_COLOR_PALETTE_FLAG_USABLE_WITH_DARK_BACKGROUND);
+ } else {
+ if (aPaletteValueSet) {
+ if ((fpv = aPaletteValueSet->Lookup(aFontPalette, aFamilyName))) {
+ if (fpv->mBasePalette >= 0 && fpv->mBasePalette < int32_t(count)) {
+ paletteIndex = fpv->mBasePalette;
+ } else if (fpv->mBasePalette ==
+ FontPaletteValueSet::PaletteValues::kLight) {
+ paletteIndex = findPalette(
+ HB_OT_COLOR_PALETTE_FLAG_USABLE_WITH_LIGHT_BACKGROUND);
+ } else if (fpv->mBasePalette ==
+ FontPaletteValueSet::PaletteValues::kDark) {
+ paletteIndex = findPalette(
+ HB_OT_COLOR_PALETTE_FLAG_USABLE_WITH_DARK_BACKGROUND);
+ }
+ }
+ }
+ }
+ }
+
+ // Collect the palette colors and convert them to sRGBColor values.
+ count =
+ hb_ot_color_palette_get_colors(aFace, paletteIndex, 0, nullptr, nullptr);
+ nsTArray<hb_color_t> colors;
+ colors.SetLength(count);
+ hb_ot_color_palette_get_colors(aFace, paletteIndex, 0, &count,
+ colors.Elements());
+
+ auto palette = MakeUnique<nsTArray<sRGBColor>>();
+ palette->SetCapacity(count);
+ for (const auto c : colors) {
+ palette->AppendElement(
+ sRGBColor(hb_color_get_red(c) / 255.0, hb_color_get_green(c) / 255.0,
+ hb_color_get_blue(c) / 255.0, hb_color_get_alpha(c) / 255.0));
+ }
+
+ // Apply @font-palette-values overrides, if present.
+ if (fpv) {
+ for (const auto overrideColor : fpv->mOverrides) {
+ if (overrideColor.mIndex < palette->Length()) {
+ (*palette)[overrideColor.mIndex] = overrideColor.mColor;
+ }
+ }
+ }
+
+ return palette;
+}
+
+const FontPaletteValueSet::PaletteValues* FontPaletteValueSet::Lookup(
+ nsAtom* aName, const nsACString& aFamily) const {
+ nsAutoCString family(aFamily);
+ ToLowerCase(family);
+ if (const HashEntry* entry =
+ mValues.GetEntry(PaletteHashKey(aName, family))) {
+ return &entry->mValue;
+ }
+ return nullptr;
+}
+
+FontPaletteValueSet::PaletteValues* FontPaletteValueSet::Insert(
+ nsAtom* aName, const nsACString& aFamily) {
+ nsAutoCString family(aFamily);
+ ToLowerCase(family);
+ HashEntry* entry = mValues.PutEntry(PaletteHashKey(aName, family));
+ return &entry->mValue;
+}
+
+} // end namespace mozilla::gfx
diff --git a/gfx/thebes/COLRFonts.h b/gfx/thebes/COLRFonts.h
new file mode 100644
index 0000000000..a073bb4164
--- /dev/null
+++ b/gfx/thebes/COLRFonts.h
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef COLR_FONTS_H
+#define COLR_FONTS_H
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/UniquePtr.h"
+#include "nsAtom.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+
+struct hb_blob_t;
+struct hb_face_t;
+struct hb_font_t;
+
+namespace mozilla {
+
+namespace layout {
+class TextDrawTarget;
+}
+
+namespace gfx {
+
+class FontPaletteValueSet {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FontPaletteValueSet)
+
+ struct OverrideColor {
+ uint32_t mIndex = 0;
+ sRGBColor mColor;
+ };
+
+ struct PaletteValues {
+ enum { kLight = -1, kDark = -2 };
+ int32_t mBasePalette = 0; // 0-based index, or kLight/kDark constants
+ nsTArray<OverrideColor> mOverrides;
+ };
+
+ const PaletteValues* Lookup(nsAtom* aName, const nsACString& aFamily) const;
+
+ PaletteValues* Insert(nsAtom* aName, const nsACString& aFamily);
+
+ private:
+ ~FontPaletteValueSet() = default;
+
+ struct PaletteHashKey {
+ RefPtr<nsAtom> mName;
+ nsCString mFamily;
+
+ PaletteHashKey() = delete;
+ PaletteHashKey(nsAtom* aName, const nsACString& aFamily)
+ : mName(aName), mFamily(aFamily) {}
+ PaletteHashKey(const PaletteHashKey& aKey) = default;
+ };
+
+ class HashEntry : public PLDHashEntryHdr {
+ public:
+ using KeyType = const PaletteHashKey&;
+ using KeyTypePointer = const PaletteHashKey*;
+
+ HashEntry() = delete;
+ explicit HashEntry(KeyTypePointer aKey) : mKey(*aKey) {}
+ HashEntry(HashEntry&& other) noexcept
+ : PLDHashEntryHdr(std::move(other)),
+ mKey(other.mKey),
+ mValue(std::move(other.mValue)) {
+ NS_ERROR("Should not be called");
+ }
+ ~HashEntry() = default;
+
+ bool KeyEquals(const KeyTypePointer aKey) const {
+ return mKey.mName == aKey->mName && mKey.mFamily.Equals(aKey->mFamily);
+ }
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(const KeyTypePointer aKey) {
+ return aKey->mName->hash() +
+ HashString(aKey->mFamily.get(), aKey->mFamily.Length());
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ PaletteHashKey mKey;
+ PaletteValues mValue;
+ };
+
+ nsTHashtable<HashEntry> mValues;
+};
+
+class COLRFonts {
+ public:
+ static bool ValidateColorGlyphs(hb_blob_t* aCOLR, hb_blob_t* aCPAL);
+
+ // COLRv0: color glyph is represented as a simple list of colored layers.
+ // (This is used only as an opaque pointer; the internal type is private.)
+ struct GlyphLayers;
+
+ static const GlyphLayers* GetGlyphLayers(hb_blob_t* aCOLR, uint32_t aGlyphId);
+
+ static bool PaintGlyphLayers(
+ hb_blob_t* aCOLR, hb_face_t* aFace, const GlyphLayers* aLayers,
+ DrawTarget* aDrawTarget, layout::TextDrawTarget* aTextDrawer,
+ ScaledFont* aScaledFont, DrawOptions aDrawOptions, const Point& aPoint,
+ const sRGBColor& aCurrentColor, const nsTArray<sRGBColor>* aColors);
+
+ // COLRv1 support: color glyph is represented by a directed acyclic graph of
+ // paint records.
+ // (This is used only as an opaque pointer; the internal type is private.)
+ struct GlyphPaintGraph;
+
+ static const GlyphPaintGraph* GetGlyphPaintGraph(hb_blob_t* aCOLR,
+ uint32_t aGlyphId);
+
+ static bool PaintGlyphGraph(
+ hb_blob_t* aCOLR, hb_font_t* aFont, const GlyphPaintGraph* aPaintGraph,
+ DrawTarget* aDrawTarget, layout::TextDrawTarget* aTextDrawer,
+ ScaledFont* aScaledFont, DrawOptions aDrawOptions, const Point& aPoint,
+ const sRGBColor& aCurrentColor, const nsTArray<sRGBColor>* aColors,
+ uint32_t aGlyphId, float aFontUnitsToPixels);
+
+ static Rect GetColorGlyphBounds(hb_blob_t* aCOLR, hb_font_t* aFont,
+ uint32_t aGlyphId, DrawTarget* aDrawTarget,
+ ScaledFont* aScaledFont,
+ float aFontUnitsToPixels);
+
+ static uint16_t GetColrTableVersion(hb_blob_t* aCOLR);
+
+ static UniquePtr<nsTArray<sRGBColor>> SetupColorPalette(
+ hb_face_t* aFace, const FontPaletteValueSet* aPaletteValueSet,
+ nsAtom* aFontPalette, const nsACString& aFamilyName);
+};
+
+} // namespace gfx
+
+} // namespace mozilla
+
+#endif // COLR_FONTS_H
diff --git a/gfx/thebes/D3D11Checks.cpp b/gfx/thebes/D3D11Checks.cpp
new file mode 100644
index 0000000000..2bf167efd3
--- /dev/null
+++ b/gfx/thebes/D3D11Checks.cpp
@@ -0,0 +1,490 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "D3D11Checks.h"
+#include "DXVA2Manager.h"
+#include "gfxConfig.h"
+#include "GfxDriverInfo.h"
+#include "gfxWindowsPlatform.h"
+#include "mozilla/Components.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/StaticPrefs_layers.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/layers/TextureD3D11.h"
+#include "nsIGfxInfo.h"
+#include <dxgi.h>
+#include <dxgi1_2.h>
+#include <d3d10_1.h>
+#include <d3d11.h>
+
+namespace mozilla {
+namespace gfx {
+
+using namespace mozilla::widget;
+using mozilla::layers::AutoTextureLock;
+
+/* static */
+bool D3D11Checks::DoesRenderTargetViewNeedRecreating(ID3D11Device* aDevice) {
+ bool result = false;
+ // CreateTexture2D is known to crash on lower feature levels, see bugs
+ // 1170211 and 1089413.
+ if (aDevice->GetFeatureLevel() < D3D_FEATURE_LEVEL_10_0) {
+ return true;
+ }
+
+ RefPtr<ID3D11DeviceContext> deviceContext;
+ aDevice->GetImmediateContext(getter_AddRefs(deviceContext));
+ int backbufferWidth = 32;
+ int backbufferHeight = 32;
+ RefPtr<ID3D11Texture2D> offscreenTexture;
+ RefPtr<IDXGIKeyedMutex> keyedMutex;
+
+ D3D11_TEXTURE2D_DESC offscreenTextureDesc = {0};
+ offscreenTextureDesc.Width = backbufferWidth;
+ offscreenTextureDesc.Height = backbufferHeight;
+ offscreenTextureDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
+ offscreenTextureDesc.MipLevels = 0;
+ offscreenTextureDesc.ArraySize = 1;
+ offscreenTextureDesc.SampleDesc.Count = 1;
+ offscreenTextureDesc.SampleDesc.Quality = 0;
+ offscreenTextureDesc.Usage = D3D11_USAGE_DEFAULT;
+ offscreenTextureDesc.BindFlags =
+ D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
+ offscreenTextureDesc.CPUAccessFlags = 0;
+ offscreenTextureDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
+
+ HRESULT hr = aDevice->CreateTexture2D(&offscreenTextureDesc, NULL,
+ getter_AddRefs(offscreenTexture));
+ if (FAILED(hr)) {
+ gfxCriticalNote << "DoesRecreatingCreateTexture2DFail";
+ return false;
+ }
+
+ hr = offscreenTexture->QueryInterface(__uuidof(IDXGIKeyedMutex),
+ (void**)getter_AddRefs(keyedMutex));
+ if (FAILED(hr)) {
+ gfxCriticalNote << "DoesRecreatingKeyedMutexFailed";
+ return false;
+ }
+ D3D11_RENDER_TARGET_VIEW_DESC offscreenRTVDesc;
+ offscreenRTVDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
+ offscreenRTVDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
+ offscreenRTVDesc.Texture2D.MipSlice = 0;
+
+ RefPtr<ID3D11RenderTargetView> offscreenRTView;
+ hr = aDevice->CreateRenderTargetView(offscreenTexture, &offscreenRTVDesc,
+ getter_AddRefs(offscreenRTView));
+ if (FAILED(hr)) {
+ gfxCriticalNote << "DoesRecreatingCreateRenderTargetViewFailed";
+ return false;
+ }
+
+ {
+ // Acquire and clear
+ HRESULT hr;
+ AutoTextureLock lock(keyedMutex, hr, INFINITE);
+ FLOAT color1[4] = {1, 1, 0.5, 1};
+ deviceContext->ClearRenderTargetView(offscreenRTView, color1);
+ }
+
+ {
+ HRESULT hr;
+ AutoTextureLock lock(keyedMutex, hr, INFINITE);
+ FLOAT color2[4] = {1, 1, 0, 1};
+
+ deviceContext->ClearRenderTargetView(offscreenRTView, color2);
+ D3D11_TEXTURE2D_DESC desc;
+
+ offscreenTexture->GetDesc(&desc);
+ desc.Usage = D3D11_USAGE_STAGING;
+ desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+ desc.MiscFlags = 0;
+ desc.BindFlags = 0;
+ RefPtr<ID3D11Texture2D> cpuTexture;
+ hr = aDevice->CreateTexture2D(&desc, NULL, getter_AddRefs(cpuTexture));
+ if (FAILED(hr)) {
+ gfxCriticalNote << "DoesRecreatingCreateCPUTextureFailed";
+ return false;
+ }
+
+ deviceContext->CopyResource(cpuTexture, offscreenTexture);
+
+ D3D11_MAPPED_SUBRESOURCE mapped;
+ hr = deviceContext->Map(cpuTexture, 0, D3D11_MAP_READ, 0, &mapped);
+ if (FAILED(hr)) {
+ gfxCriticalNote << "DoesRecreatingMapFailed " << hexa(hr);
+ return false;
+ }
+ uint32_t resultColor = *(uint32_t*)mapped.pData;
+ deviceContext->Unmap(cpuTexture, 0);
+ cpuTexture = nullptr;
+
+ // XXX on some drivers resultColor will not have changed to
+ // match the clear
+ if (resultColor != 0xffffff00) {
+ gfxCriticalNote << "RenderTargetViewNeedsRecreating";
+ result = true;
+ }
+ }
+ return result;
+}
+
+/* static */
+bool D3D11Checks::DoesDeviceWork() {
+ static bool checked = false;
+ static bool result = false;
+
+ if (checked) return result;
+ checked = true;
+
+ if (StaticPrefs::gfx_direct2d_force_enabled_AtStartup() ||
+ gfxConfig::IsForcedOnByUser(Feature::HW_COMPOSITING)) {
+ result = true;
+ return true;
+ }
+
+ if (GetModuleHandleW(L"igd10umd32.dll")) {
+ const wchar_t* checkModules[] = {L"dlumd32.dll", L"dlumd11.dll",
+ L"dlumd10.dll"};
+ for (size_t i = 0; i < PR_ARRAY_SIZE(checkModules); i += 1) {
+ if (GetModuleHandleW(checkModules[i])) {
+ nsString displayLinkModuleVersionString;
+ gfxWindowsPlatform::GetDLLVersion(checkModules[i],
+ displayLinkModuleVersionString);
+ uint64_t displayLinkModuleVersion;
+ if (!ParseDriverVersion(displayLinkModuleVersionString,
+ &displayLinkModuleVersion)) {
+ gfxCriticalError()
+ << "DisplayLink: could not parse version " << checkModules[i];
+ return false;
+ }
+ if (displayLinkModuleVersion <= V(8, 6, 1, 36484)) {
+ NS_ConvertUTF16toUTF8 version(displayLinkModuleVersionString);
+ gfxCriticalError(CriticalLog::DefaultOptions(false))
+ << "DisplayLink: too old version " << version.get();
+ return false;
+ }
+ }
+ }
+ }
+ result = true;
+ return true;
+}
+
+static bool TryCreateTexture2D(ID3D11Device* device, D3D11_TEXTURE2D_DESC* desc,
+ D3D11_SUBRESOURCE_DATA* data,
+ RefPtr<ID3D11Texture2D>& texture) {
+ // Older Intel driver version (see bug 1221348 for version #s) crash when
+ // creating a texture with shared keyed mutex and data.
+ MOZ_SEH_TRY {
+ return !FAILED(
+ device->CreateTexture2D(desc, data, getter_AddRefs(texture)));
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ // For now we want to aggregrate all the crash signature to a known crash.
+ gfxDevCrash(LogReason::TextureCreation)
+ << "Crash creating texture. See bug 1221348.";
+ return false;
+ }
+}
+
+// See bug 1083071. On some drivers, Direct3D 11 CreateShaderResourceView fails
+// with E_OUTOFMEMORY.
+static bool DoesTextureSharingWorkInternal(ID3D11Device* device,
+ DXGI_FORMAT format, UINT bindflags) {
+ // CreateTexture2D is known to crash on lower feature levels, see bugs
+ // 1170211 and 1089413.
+ if (device->GetFeatureLevel() < D3D_FEATURE_LEVEL_10_0) {
+ return false;
+ }
+
+ if (StaticPrefs::gfx_direct2d_force_enabled_AtStartup() ||
+ gfxConfig::IsForcedOnByUser(Feature::HW_COMPOSITING)) {
+ return true;
+ }
+
+ if (GetModuleHandleW(L"atidxx32.dll")) {
+ nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
+ if (gfxInfo) {
+ nsString vendorID, vendorID2;
+ gfxInfo->GetAdapterVendorID(vendorID);
+ gfxInfo->GetAdapterVendorID2(vendorID2);
+ if (vendorID.EqualsLiteral("0x8086") && vendorID2.IsEmpty()) {
+ if (!StaticPrefs::layers_amd_switchable_gfx_enabled_AtStartup()) {
+ return false;
+ }
+ gfxCriticalError(CriticalLog::DefaultOptions(false))
+ << "PossiblyBrokenSurfaceSharing_UnexpectedAMDGPU";
+ }
+ }
+ }
+
+ RefPtr<ID3D11Texture2D> texture;
+ D3D11_TEXTURE2D_DESC desc;
+ const int texture_size = 32;
+ desc.Width = texture_size;
+ desc.Height = texture_size;
+ desc.MipLevels = 1;
+ desc.ArraySize = 1;
+ desc.Format = format;
+ desc.SampleDesc.Count = 1;
+ desc.SampleDesc.Quality = 0;
+ desc.Usage = D3D11_USAGE_DEFAULT;
+ desc.CPUAccessFlags = 0;
+ desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
+ desc.BindFlags = bindflags;
+
+ uint32_t color[texture_size * texture_size];
+ for (size_t i = 0; i < sizeof(color) / sizeof(color[0]); i++) {
+ color[i] = 0xff00ffff;
+ }
+ // XXX If we pass the data directly at texture creation time we
+ // get a crash on Intel 8.5.10.[18xx-1994] drivers.
+ // We can work around this issue by doing UpdateSubresource.
+ if (!TryCreateTexture2D(device, &desc, nullptr, texture)) {
+ gfxCriticalNote << "DoesD3D11TextureSharingWork_TryCreateTextureFailure";
+ return false;
+ }
+
+ RefPtr<IDXGIKeyedMutex> sourceSharedMutex;
+ texture->QueryInterface(__uuidof(IDXGIKeyedMutex),
+ (void**)getter_AddRefs(sourceSharedMutex));
+ if (FAILED(sourceSharedMutex->AcquireSync(0, 30 * 1000))) {
+ gfxCriticalError() << "DoesD3D11TextureSharingWork_SourceMutexTimeout";
+ // only wait for 30 seconds
+ return false;
+ }
+
+ RefPtr<ID3D11DeviceContext> deviceContext;
+ device->GetImmediateContext(getter_AddRefs(deviceContext));
+
+ int stride = texture_size * 4;
+ deviceContext->UpdateSubresource(texture, 0, nullptr, color, stride,
+ stride * texture_size);
+
+ if (FAILED(sourceSharedMutex->ReleaseSync(0))) {
+ gfxCriticalError()
+ << "DoesD3D11TextureSharingWork_SourceReleaseSyncTimeout";
+ return false;
+ }
+
+ HANDLE shareHandle;
+ RefPtr<IDXGIResource> otherResource;
+ if (FAILED(texture->QueryInterface(__uuidof(IDXGIResource),
+ getter_AddRefs(otherResource)))) {
+ gfxCriticalError() << "DoesD3D11TextureSharingWork_GetResourceFailure";
+ return false;
+ }
+
+ if (FAILED(otherResource->GetSharedHandle(&shareHandle))) {
+ gfxCriticalError() << "DoesD3D11TextureSharingWork_GetSharedTextureFailure";
+ return false;
+ }
+
+ RefPtr<ID3D11Resource> sharedResource;
+ RefPtr<ID3D11Texture2D> sharedTexture;
+ if (FAILED(device->OpenSharedResource(shareHandle, __uuidof(ID3D11Resource),
+ getter_AddRefs(sharedResource)))) {
+ gfxCriticalError(CriticalLog::DefaultOptions(false))
+ << "OpenSharedResource failed for format " << format;
+ return false;
+ }
+
+ if (FAILED(sharedResource->QueryInterface(__uuidof(ID3D11Texture2D),
+ getter_AddRefs(sharedTexture)))) {
+ gfxCriticalError() << "DoesD3D11TextureSharingWork_GetSharedTextureFailure";
+ return false;
+ }
+
+ // create a staging texture for readback
+ RefPtr<ID3D11Texture2D> cpuTexture;
+ desc.Usage = D3D11_USAGE_STAGING;
+ desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+ desc.MiscFlags = 0;
+ desc.BindFlags = 0;
+ if (FAILED(device->CreateTexture2D(&desc, nullptr,
+ getter_AddRefs(cpuTexture)))) {
+ gfxCriticalError() << "DoesD3D11TextureSharingWork_CreateTextureFailure";
+ return false;
+ }
+
+ RefPtr<IDXGIKeyedMutex> sharedMutex;
+ sharedResource->QueryInterface(__uuidof(IDXGIKeyedMutex),
+ (void**)getter_AddRefs(sharedMutex));
+ {
+ HRESULT hr;
+ AutoTextureLock lock(sharedMutex, hr, 30 * 1000);
+ if (FAILED(hr)) {
+ gfxCriticalError() << "DoesD3D11TextureSharingWork_AcquireSyncTimeout";
+ // only wait for 30 seconds
+ return false;
+ }
+
+ // Copy to the cpu texture so that we can readback
+ deviceContext->CopyResource(cpuTexture, sharedTexture);
+
+ // We only need to hold on to the mutex during the copy.
+ sharedMutex->ReleaseSync(0);
+ }
+
+ D3D11_MAPPED_SUBRESOURCE mapped;
+ uint32_t resultColor = 0;
+ if (SUCCEEDED(
+ deviceContext->Map(cpuTexture, 0, D3D11_MAP_READ, 0, &mapped))) {
+ // read the texture
+ resultColor = *(uint32_t*)mapped.pData;
+ deviceContext->Unmap(cpuTexture, 0);
+ } else {
+ gfxCriticalError() << "DoesD3D11TextureSharingWork_MapFailed";
+ return false;
+ }
+
+ // check that the color we put in is the color we get out
+ if (resultColor != color[0]) {
+ // Shared surfaces seem to be broken on dual AMD & Intel HW when using the
+ // AMD GPU
+ gfxCriticalNote << "DoesD3D11TextureSharingWork_ColorMismatch";
+ return false;
+ }
+
+ RefPtr<ID3D11ShaderResourceView> sharedView;
+
+ // This if(FAILED()) is the one that actually fails on systems affected by bug
+ // 1083071.
+ if (FAILED(device->CreateShaderResourceView(sharedTexture, NULL,
+ getter_AddRefs(sharedView)))) {
+ gfxCriticalNote << "CreateShaderResourceView failed for format" << format;
+ return false;
+ }
+
+ return true;
+}
+
+/* static */
+bool D3D11Checks::DoesTextureSharingWork(ID3D11Device* device) {
+ return DoesTextureSharingWorkInternal(
+ device, DXGI_FORMAT_B8G8R8A8_UNORM,
+ D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE);
+}
+
+/* static */
+bool D3D11Checks::DoesAlphaTextureSharingWork(ID3D11Device* device) {
+ return DoesTextureSharingWorkInternal(device, DXGI_FORMAT_R8_UNORM,
+ D3D11_BIND_SHADER_RESOURCE);
+}
+
+/* static */
+bool D3D11Checks::GetDxgiDesc(ID3D11Device* device, DXGI_ADAPTER_DESC* out) {
+ RefPtr<IDXGIDevice> dxgiDevice;
+ HRESULT hr =
+ device->QueryInterface(__uuidof(IDXGIDevice), getter_AddRefs(dxgiDevice));
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ RefPtr<IDXGIAdapter> dxgiAdapter;
+ if (FAILED(dxgiDevice->GetAdapter(getter_AddRefs(dxgiAdapter)))) {
+ return false;
+ }
+
+ return SUCCEEDED(dxgiAdapter->GetDesc(out));
+}
+
+/* static */
+void D3D11Checks::WarnOnAdapterMismatch(ID3D11Device* device) {
+ DXGI_ADAPTER_DESC desc;
+ PodZero(&desc);
+ GetDxgiDesc(device, &desc);
+
+ nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
+ nsString vendorID;
+ gfxInfo->GetAdapterVendorID(vendorID);
+ nsresult ec;
+ int32_t vendor = vendorID.ToInteger(&ec, 16);
+ if (vendor != static_cast<int32_t>(desc.VendorId)) {
+ gfxCriticalNote << "VendorIDMismatch V " << hexa(vendor) << " "
+ << hexa(desc.VendorId);
+ }
+}
+
+/* static */
+bool D3D11Checks::DoesRemotePresentWork(IDXGIAdapter* adapter) {
+ // Remote presentation was added in DXGI 1.2, for Windows 8 and the Platform
+ // Update to Windows 7.
+ RefPtr<IDXGIAdapter2> check;
+ HRESULT hr =
+ adapter->QueryInterface(__uuidof(IDXGIAdapter2), getter_AddRefs(check));
+ return SUCCEEDED(hr) && check;
+}
+
+/* static */ D3D11Checks::VideoFormatOptionSet D3D11Checks::FormatOptions(
+ ID3D11Device* device) {
+ auto doesNV12Work = [&]() {
+ if (gfxVars::DXNV12Blocked()) {
+ return false;
+ }
+
+ DXGI_ADAPTER_DESC desc;
+ PodZero(&desc);
+ if (!GetDxgiDesc(device, &desc)) {
+ // Failed to retrieve device information, assume it doesn't work
+ return false;
+ }
+
+ UINT formatSupport;
+ HRESULT hr = device->CheckFormatSupport(DXGI_FORMAT_NV12, &formatSupport);
+ if (FAILED(hr) || !(formatSupport & D3D11_FORMAT_SUPPORT_TEXTURE2D)) {
+ return false;
+ }
+
+ nsString version;
+ nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
+ if (gfxInfo) {
+ gfxInfo->GetAdapterDriverVersion(version);
+ }
+ return DXVA2Manager::IsNV12Supported(desc.VendorId, desc.DeviceId, version);
+ };
+
+ auto doesP010Work = [&]() {
+ if (gfxVars::DXP010Blocked() &&
+ !StaticPrefs::media_wmf_force_allow_p010_format()) {
+ return false;
+ }
+ UINT formatSupport;
+ HRESULT hr = device->CheckFormatSupport(DXGI_FORMAT_P010, &formatSupport);
+ return (SUCCEEDED(hr) && (formatSupport & D3D11_FORMAT_SUPPORT_TEXTURE2D));
+ };
+
+ auto doesP016Work = [&]() {
+ if (gfxVars::DXP016Blocked() &&
+ !StaticPrefs::media_wmf_force_allow_p010_format()) {
+ return false;
+ }
+ UINT formatSupport;
+ HRESULT hr = device->CheckFormatSupport(DXGI_FORMAT_P016, &formatSupport);
+ return (SUCCEEDED(hr) && (formatSupport & D3D11_FORMAT_SUPPORT_TEXTURE2D));
+ };
+
+ VideoFormatOptionSet options;
+ if (!doesNV12Work()) {
+ // If the device doesn't support NV12, there's really no point testing for
+ // P010 and P016.
+ return options;
+ }
+ options += VideoFormatOption::NV12;
+ if (doesP010Work()) {
+ options += VideoFormatOption::P010;
+ }
+ if (doesP016Work()) {
+ options += VideoFormatOption::P016;
+ }
+ return options;
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/thebes/D3D11Checks.h b/gfx/thebes/D3D11Checks.h
new file mode 100644
index 0000000000..622cf3229c
--- /dev/null
+++ b/gfx/thebes/D3D11Checks.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_gfx_thebes_D3D11Checks_h
+#define mozilla_gfx_thebes_D3D11Checks_h
+
+#include "mozilla/EnumSet.h"
+#include "mozilla/EnumTypeTraits.h"
+
+struct ID3D11Device;
+struct IDXGIAdapter;
+struct DXGI_ADAPTER_DESC;
+
+namespace mozilla {
+namespace gfx {
+
+struct D3D11Checks {
+ enum class VideoFormatOption {
+ NV12,
+ P010,
+ P016,
+ };
+ using VideoFormatOptionSet = EnumSet<VideoFormatOption>;
+
+ static bool DoesRenderTargetViewNeedRecreating(ID3D11Device* aDevice);
+ static bool DoesDeviceWork();
+ static bool DoesTextureSharingWork(ID3D11Device* device);
+ static bool DoesAlphaTextureSharingWork(ID3D11Device* device);
+ static void WarnOnAdapterMismatch(ID3D11Device* device);
+ static bool GetDxgiDesc(ID3D11Device* device, DXGI_ADAPTER_DESC* out);
+ static bool DoesRemotePresentWork(IDXGIAdapter* adapter);
+ static VideoFormatOptionSet FormatOptions(ID3D11Device* device);
+};
+
+} // namespace gfx
+
+// Used for IPDL serialization.
+// The 'value' have to be the biggest enum from D3D11Checks::Option.
+template <>
+struct MaxEnumValue<::mozilla::gfx::D3D11Checks::VideoFormatOption> {
+ static constexpr unsigned int value =
+ static_cast<unsigned int>(gfx::D3D11Checks::VideoFormatOption::P016);
+};
+
+} // namespace mozilla
+
+#endif // mozilla_gfx_thebes_D3D11Checks_h
diff --git a/gfx/thebes/DeviceManagerDx.cpp b/gfx/thebes/DeviceManagerDx.cpp
new file mode 100644
index 0000000000..100cd3192a
--- /dev/null
+++ b/gfx/thebes/DeviceManagerDx.cpp
@@ -0,0 +1,1443 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DeviceManagerDx.h"
+#include "D3D11Checks.h"
+#include "gfxConfig.h"
+#include "GfxDriverInfo.h"
+#include "gfxWindowsPlatform.h"
+#include "mozilla/D3DMessageUtils.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/StaticPrefs_layers.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/gfx/GPUParent.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "mozilla/gfx/GraphicsMessages.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/DeviceAttachmentsD3D11.h"
+#include "mozilla/Preferences.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+
+// -
+
+#include "mozilla/gfx/AllOfDcomp.h"
+#include <d3d11.h>
+#include <ddraw.h>
+#include <dxgi.h>
+
+namespace mozilla {
+namespace gfx {
+
+using namespace mozilla::widget;
+using namespace mozilla::layers;
+
+StaticAutoPtr<DeviceManagerDx> DeviceManagerDx::sInstance;
+
+// We don't have access to the D3D11CreateDevice type in gfxWindowsPlatform.h,
+// since it doesn't include d3d11.h, so we use a static here. It should only
+// be used within InitializeD3D11.
+decltype(D3D11CreateDevice)* sD3D11CreateDeviceFn = nullptr;
+
+// It should only be used within CreateDirectCompositionDevice.
+decltype(DCompositionCreateDevice2)* sDcompCreateDevice2Fn = nullptr;
+decltype(DCompositionCreateDevice3)* sDcompCreateDevice3Fn = nullptr;
+
+// It should only be used within CreateDCompSurfaceHandle
+decltype(DCompositionCreateSurfaceHandle)* sDcompCreateSurfaceHandleFn =
+ nullptr;
+
+// We don't have access to the DirectDrawCreateEx type in gfxWindowsPlatform.h,
+// since it doesn't include ddraw.h, so we use a static here. It should only
+// be used within InitializeDirectDrawConfig.
+decltype(DirectDrawCreateEx)* sDirectDrawCreateExFn = nullptr;
+
+/* static */
+void DeviceManagerDx::Init() { sInstance = new DeviceManagerDx(); }
+
+/* static */
+void DeviceManagerDx::Shutdown() { sInstance = nullptr; }
+
+DeviceManagerDx::DeviceManagerDx()
+ : mDeviceLock("gfxWindowsPlatform.mDeviceLock"),
+ mCompositorDeviceSupportsVideo(false) {
+ // Set up the D3D11 feature levels we can ask for.
+ if (IsWin8OrLater()) {
+ mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_11_1);
+ }
+ mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_11_0);
+ mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_10_1);
+ mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_10_0);
+ MOZ_COUNT_CTOR(DeviceManagerDx);
+}
+
+DeviceManagerDx::~DeviceManagerDx() { MOZ_COUNT_DTOR(DeviceManagerDx); }
+
+bool DeviceManagerDx::LoadD3D11() {
+ FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING);
+ MOZ_ASSERT(d3d11.IsEnabled());
+
+ if (sD3D11CreateDeviceFn) {
+ return true;
+ }
+
+ nsModuleHandle module(LoadLibrarySystem32(L"d3d11.dll"));
+ if (!module) {
+ d3d11.SetFailed(FeatureStatus::Unavailable,
+ "Direct3D11 not available on this computer",
+ "FEATURE_FAILURE_D3D11_LIB"_ns);
+ return false;
+ }
+
+ sD3D11CreateDeviceFn =
+ (decltype(D3D11CreateDevice)*)GetProcAddress(module, "D3D11CreateDevice");
+ if (!sD3D11CreateDeviceFn) {
+ // We should just be on Windows Vista or XP in this case.
+ d3d11.SetFailed(FeatureStatus::Unavailable,
+ "Direct3D11 not available on this computer",
+ "FEATURE_FAILURE_D3D11_FUNCPTR"_ns);
+ return false;
+ }
+
+ mD3D11Module.steal(module);
+ return true;
+}
+
+bool DeviceManagerDx::LoadDcomp() {
+ MOZ_ASSERT(gfxConfig::GetFeature(Feature::D3D11_COMPOSITING).IsEnabled());
+ MOZ_ASSERT(gfxVars::UseWebRenderANGLE());
+ MOZ_ASSERT(gfxVars::UseWebRenderDCompWin());
+
+ if (sDcompCreateDevice2Fn) {
+ return true; // Already loaded.
+ }
+
+ nsModuleHandle module(LoadLibrarySystem32(L"dcomp.dll"));
+ if (!module) {
+ return false;
+ }
+
+ sDcompCreateDevice2Fn = (decltype(DCompositionCreateDevice2)*)GetProcAddress(
+ module, "DCompositionCreateDevice2");
+ sDcompCreateDevice3Fn = (decltype(DCompositionCreateDevice3)*)GetProcAddress(
+ module, "DCompositionCreateDevice3");
+ if (!sDcompCreateDevice2Fn) {
+ return false;
+ }
+
+ // Load optional API for external compositing
+ sDcompCreateSurfaceHandleFn =
+ (decltype(DCompositionCreateSurfaceHandle)*)::GetProcAddress(
+ module, "DCompositionCreateSurfaceHandle");
+
+ mDcompModule.steal(module);
+ return true;
+}
+
+void DeviceManagerDx::ReleaseD3D11() {
+ MOZ_ASSERT(!mCompositorDevice);
+ MOZ_ASSERT(!mContentDevice);
+ MOZ_ASSERT(!mVRDevice);
+ MOZ_ASSERT(!mDecoderDevice);
+
+ mD3D11Module.reset();
+ sD3D11CreateDeviceFn = nullptr;
+}
+
+nsTArray<DXGI_OUTPUT_DESC1> DeviceManagerDx::EnumerateOutputs() {
+ RefPtr<IDXGIAdapter> adapter = GetDXGIAdapter();
+
+ if (!adapter) {
+ NS_WARNING("Failed to acquire a DXGI adapter for enumerating outputs.");
+ return nsTArray<DXGI_OUTPUT_DESC1>();
+ }
+
+ nsTArray<DXGI_OUTPUT_DESC1> outputs;
+ for (UINT i = 0;; ++i) {
+ RefPtr<IDXGIOutput> output = nullptr;
+ if (FAILED(adapter->EnumOutputs(i, getter_AddRefs(output)))) {
+ break;
+ }
+
+ RefPtr<IDXGIOutput6> output6 = nullptr;
+ if (FAILED(output->QueryInterface(__uuidof(IDXGIOutput6),
+ getter_AddRefs(output6)))) {
+ break;
+ }
+
+ DXGI_OUTPUT_DESC1 desc;
+ if (FAILED(output6->GetDesc1(&desc))) {
+ break;
+ }
+
+ outputs.AppendElement(desc);
+ }
+ return outputs;
+}
+
+bool DeviceManagerDx::GetOutputFromMonitor(HMONITOR monitor,
+ RefPtr<IDXGIOutput>* aOutOutput) {
+ RefPtr<IDXGIAdapter> adapter = GetDXGIAdapter();
+
+ if (!adapter) {
+ NS_WARNING("Failed to acquire a DXGI adapter for GetOutputFromMonitor.");
+ return false;
+ }
+
+ for (UINT i = 0;; ++i) {
+ RefPtr<IDXGIOutput> output = nullptr;
+ if (FAILED(adapter->EnumOutputs(i, getter_AddRefs(output)))) {
+ break;
+ }
+
+ DXGI_OUTPUT_DESC desc;
+ if (FAILED(output->GetDesc(&desc))) {
+ continue;
+ }
+
+ if (desc.Monitor == monitor) {
+ *aOutOutput = output;
+ return true;
+ }
+ }
+ return false;
+}
+
+void DeviceManagerDx::CheckHardwareStretchingSupport(HwStretchingSupport& aRv) {
+ RefPtr<IDXGIAdapter> adapter = GetDXGIAdapter();
+
+ if (!adapter) {
+ NS_WARNING(
+ "Failed to acquire a DXGI adapter for checking hardware stretching "
+ "support.");
+ ++aRv.mError;
+ return;
+ }
+
+ for (UINT i = 0;; ++i) {
+ RefPtr<IDXGIOutput> output = nullptr;
+ HRESULT result = adapter->EnumOutputs(i, getter_AddRefs(output));
+ if (result == DXGI_ERROR_NOT_FOUND) {
+ // No more outputs to check.
+ break;
+ }
+
+ if (FAILED(result)) {
+ ++aRv.mError;
+ break;
+ }
+
+ RefPtr<IDXGIOutput6> output6 = nullptr;
+ if (FAILED(output->QueryInterface(__uuidof(IDXGIOutput6),
+ getter_AddRefs(output6)))) {
+ ++aRv.mError;
+ continue;
+ }
+
+ UINT flags = 0;
+ if (FAILED(output6->CheckHardwareCompositionSupport(&flags))) {
+ ++aRv.mError;
+ continue;
+ }
+
+ bool fullScreen = flags & DXGI_HARDWARE_COMPOSITION_SUPPORT_FLAG_FULLSCREEN;
+ bool window = flags & DXGI_HARDWARE_COMPOSITION_SUPPORT_FLAG_WINDOWED;
+ if (fullScreen && window) {
+ ++aRv.mBoth;
+ } else if (fullScreen) {
+ ++aRv.mFullScreenOnly;
+ } else if (window) {
+ ++aRv.mWindowOnly;
+ } else {
+ ++aRv.mNone;
+ }
+ }
+}
+
+#ifdef DEBUG
+static inline bool ProcessOwnsCompositor() {
+ return XRE_GetProcessType() == GeckoProcessType_GPU ||
+ XRE_GetProcessType() == GeckoProcessType_VR ||
+ (XRE_IsParentProcess() && !gfxConfig::IsEnabled(Feature::GPU_PROCESS));
+}
+#endif
+
+bool DeviceManagerDx::CreateCompositorDevices() {
+ MutexAutoLock lock(mDeviceLock);
+ return CreateCompositorDevicesLocked();
+}
+
+bool DeviceManagerDx::CreateCompositorDevicesLocked() {
+ MOZ_ASSERT(ProcessOwnsCompositor());
+
+ FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING);
+ MOZ_ASSERT(d3d11.IsEnabled());
+
+ if (int32_t sleepSec =
+ StaticPrefs::gfx_direct3d11_sleep_on_create_device_AtStartup()) {
+ printf_stderr("Attach to PID: %lu\n", GetCurrentProcessId());
+ Sleep(sleepSec * 1000);
+ }
+
+ if (!LoadD3D11()) {
+ return false;
+ }
+
+ CreateCompositorDevice(d3d11);
+
+ if (!d3d11.IsEnabled()) {
+ MOZ_ASSERT(!mCompositorDevice);
+ ReleaseD3D11();
+
+ return false;
+ }
+
+ // We leak these everywhere and we need them our entire runtime anyway, let's
+ // leak it here as well. We keep the pointer to sD3D11CreateDeviceFn around
+ // as well for D2D1 and device resets.
+ mD3D11Module.disown();
+
+ MOZ_ASSERT(mCompositorDevice);
+ if (!d3d11.IsEnabled()) {
+ return false;
+ }
+
+ // When WR is used, do not preload attachments for D3D11 Non-WR compositor.
+ //
+ // Fallback from WR to D3D11 Non-WR compositor without re-creating gpu process
+ // could happen when WR causes error. In this case, the attachments are loaded
+ // synchronously.
+ if (gfx::gfxVars::UseSoftwareWebRender()) {
+ PreloadAttachmentsOnCompositorThread();
+ }
+
+ return true;
+}
+
+bool DeviceManagerDx::CreateVRDevice() {
+ MOZ_ASSERT(ProcessOwnsCompositor());
+
+ if (mVRDevice) {
+ return true;
+ }
+
+ if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
+ NS_WARNING("Direct3D11 Compositing required for VR");
+ return false;
+ }
+
+ if (!LoadD3D11()) {
+ return false;
+ }
+
+ RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapterLocked();
+ if (!adapter) {
+ NS_WARNING("Failed to acquire a DXGI adapter for VR");
+ return false;
+ }
+
+ UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
+
+ HRESULT hr;
+ if (!CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, flags, hr, mVRDevice)) {
+ gfxCriticalError() << "Crash during D3D11 device creation for VR";
+ return false;
+ }
+
+ if (FAILED(hr) || !mVRDevice) {
+ NS_WARNING("Failed to acquire a D3D11 device for VR");
+ return false;
+ }
+
+ return true;
+}
+
+bool DeviceManagerDx::CreateCanvasDevice() {
+ MutexAutoLock lock(mDeviceLock);
+ return CreateCanvasDeviceLocked();
+}
+
+bool DeviceManagerDx::CreateCanvasDeviceLocked() {
+ MOZ_ASSERT(ProcessOwnsCompositor());
+
+ if (mCanvasDevice) {
+ return true;
+ }
+
+ if (!LoadD3D11()) {
+ return false;
+ }
+
+ RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapterLocked();
+ if (!adapter) {
+ NS_WARNING("Failed to acquire a DXGI adapter for Canvas");
+ return false;
+ }
+
+ UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
+
+ HRESULT hr;
+ if (!CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, flags, hr,
+ mCanvasDevice)) {
+ gfxCriticalError() << "Crash during D3D11 device creation for Canvas";
+ return false;
+ }
+
+ if (StaticPrefs::
+ gfx_direct2d_target_independent_rasterization_disabled_AtStartup()) {
+ int creationFlags = 0x2; // disable target independent rasterization
+ const GUID D2D_INTERNAL_DEVICE_CREATION_OPTIONS = {
+ 0xfb3a8e1a,
+ 0x2e3c,
+ 0x4de1,
+ {0x84, 0x42, 0x40, 0x43, 0xe0, 0xb0, 0x94, 0x95}};
+ mCanvasDevice->SetPrivateData(D2D_INTERNAL_DEVICE_CREATION_OPTIONS,
+ sizeof(creationFlags), &creationFlags);
+ }
+
+ if (FAILED(hr) || !mCanvasDevice) {
+ NS_WARNING("Failed to acquire a D3D11 device for Canvas");
+ return false;
+ }
+
+ if (!D3D11Checks::DoesTextureSharingWork(mCanvasDevice)) {
+ mCanvasDevice = nullptr;
+ return false;
+ }
+
+ if (XRE_IsGPUProcess()) {
+ Factory::SetDirect3D11Device(mCanvasDevice);
+ }
+
+ return true;
+}
+
+void DeviceManagerDx::CreateDirectCompositionDevice() {
+ MutexAutoLock lock(mDeviceLock);
+ CreateDirectCompositionDeviceLocked();
+}
+
+void DeviceManagerDx::CreateDirectCompositionDeviceLocked() {
+ if (!gfxVars::UseWebRenderDCompWin()) {
+ return;
+ }
+
+ if (!mCompositorDevice) {
+ return;
+ }
+
+ if (!LoadDcomp()) {
+ return;
+ }
+
+ RefPtr<IDXGIDevice> dxgiDevice;
+ if (mCompositorDevice->QueryInterface(
+ IID_PPV_ARGS((IDXGIDevice**)getter_AddRefs(dxgiDevice))) != S_OK) {
+ return;
+ }
+
+ HRESULT hr;
+ RefPtr<IDCompositionDesktopDevice> desktopDevice;
+ MOZ_SEH_TRY {
+ hr = sDcompCreateDevice3Fn(
+ dxgiDevice.get(),
+ IID_PPV_ARGS(
+ (IDCompositionDesktopDevice**)getter_AddRefs(desktopDevice)));
+ if (!desktopDevice) {
+ hr = sDcompCreateDevice2Fn(
+ dxgiDevice.get(),
+ IID_PPV_ARGS(
+ (IDCompositionDesktopDevice**)getter_AddRefs(desktopDevice)));
+ }
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { return; }
+
+ if (!SUCCEEDED(hr)) {
+ return;
+ }
+
+ RefPtr<IDCompositionDevice2> compositionDevice;
+ if (desktopDevice->QueryInterface(IID_PPV_ARGS(
+ (IDCompositionDevice2**)getter_AddRefs(compositionDevice))) != S_OK) {
+ return;
+ }
+
+ mDirectCompositionDevice = compositionDevice;
+}
+
+/* static */
+HANDLE DeviceManagerDx::CreateDCompSurfaceHandle() {
+ if (!sDcompCreateSurfaceHandleFn) {
+ return 0;
+ }
+
+ HANDLE handle = 0;
+ HRESULT hr = sDcompCreateSurfaceHandleFn(COMPOSITIONOBJECT_ALL_ACCESS,
+ nullptr, &handle);
+ if (FAILED(hr)) {
+ return 0;
+ }
+
+ return handle;
+}
+
+void DeviceManagerDx::ImportDeviceInfo(const D3D11DeviceStatus& aDeviceStatus) {
+ MOZ_ASSERT(!ProcessOwnsCompositor());
+
+ MutexAutoLock lock(mDeviceLock);
+ mDeviceStatus = Some(aDeviceStatus);
+}
+
+bool DeviceManagerDx::ExportDeviceInfo(D3D11DeviceStatus* aOut) {
+ MutexAutoLock lock(mDeviceLock);
+ if (mDeviceStatus) {
+ *aOut = mDeviceStatus.value();
+ return true;
+ }
+
+ return false;
+}
+
+void DeviceManagerDx::CreateContentDevices() {
+ MutexAutoLock lock(mDeviceLock);
+ CreateContentDevicesLocked();
+}
+
+void DeviceManagerDx::CreateContentDevicesLocked() {
+ MOZ_ASSERT(gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING));
+
+ if (!LoadD3D11()) {
+ return;
+ }
+
+ // We should have been assigned a DeviceStatus from the parent process,
+ // GPU process, or the same process if using in-process compositing.
+ MOZ_ASSERT(mDeviceStatus);
+
+ if (CreateContentDevice() == FeatureStatus::CrashedInHandler) {
+ DisableD3D11AfterCrash();
+ }
+}
+
+already_AddRefed<IDXGIAdapter1> DeviceManagerDx::GetDXGIAdapter() {
+ MutexAutoLock lock(mDeviceLock);
+ return do_AddRef(GetDXGIAdapterLocked());
+}
+
+IDXGIAdapter1* DeviceManagerDx::GetDXGIAdapterLocked() {
+ if (mAdapter) {
+ return mAdapter;
+ }
+
+ nsModuleHandle dxgiModule(LoadLibrarySystem32(L"dxgi.dll"));
+ decltype(CreateDXGIFactory1)* createDXGIFactory1 =
+ (decltype(CreateDXGIFactory1)*)GetProcAddress(dxgiModule,
+ "CreateDXGIFactory1");
+ if (!createDXGIFactory1) {
+ return nullptr;
+ }
+ static const auto fCreateDXGIFactory2 =
+ (decltype(CreateDXGIFactory2)*)GetProcAddress(dxgiModule,
+ "CreateDXGIFactory2");
+
+ // Try to use a DXGI 1.1 adapter in order to share resources
+ // across processes.
+ RefPtr<IDXGIFactory1> factory1;
+ if (StaticPrefs::gfx_direct3d11_enable_debug_layer_AtStartup()) {
+ RefPtr<IDXGIFactory2> factory2;
+ if (fCreateDXGIFactory2) {
+ auto hr = fCreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG,
+ __uuidof(IDXGIFactory2),
+ getter_AddRefs(factory2));
+ MOZ_ALWAYS_TRUE(!FAILED(hr));
+ } else {
+ NS_WARNING(
+ "fCreateDXGIFactory2 not loaded, cannot create debug IDXGIFactory2.");
+ }
+ factory1 = factory2;
+ }
+ if (!factory1) {
+ HRESULT hr =
+ createDXGIFactory1(__uuidof(IDXGIFactory1), getter_AddRefs(factory1));
+ if (FAILED(hr) || !factory1) {
+ // This seems to happen with some people running the iZ3D driver.
+ // They won't get acceleration.
+ return nullptr;
+ }
+ }
+
+ if (!mDeviceStatus) {
+ // If we haven't created a device yet, and have no existing device status,
+ // then this must be the compositor device. Pick the first adapter we can.
+ if (FAILED(factory1->EnumAdapters1(0, getter_AddRefs(mAdapter)))) {
+ return nullptr;
+ }
+ } else {
+ // In the UI and GPU process, we clear mDeviceStatus on device reset, so we
+ // should never reach here. Furthermore, the UI process does not create
+ // devices when using a GPU process.
+ //
+ // So, this should only ever get called on the content process or RDD
+ // process
+ MOZ_ASSERT(XRE_IsContentProcess() || XRE_IsRDDProcess());
+
+ // In the child process, we search for the adapter that matches the parent
+ // process. The first adapter can be mismatched on dual-GPU systems.
+ for (UINT index = 0;; index++) {
+ RefPtr<IDXGIAdapter1> adapter;
+ if (FAILED(factory1->EnumAdapters1(index, getter_AddRefs(adapter)))) {
+ break;
+ }
+
+ const DxgiAdapterDesc& preferred = mDeviceStatus->adapter();
+
+ DXGI_ADAPTER_DESC desc;
+ if (SUCCEEDED(adapter->GetDesc(&desc)) &&
+ desc.AdapterLuid.HighPart == preferred.AdapterLuid.HighPart &&
+ desc.AdapterLuid.LowPart == preferred.AdapterLuid.LowPart &&
+ desc.VendorId == preferred.VendorId &&
+ desc.DeviceId == preferred.DeviceId) {
+ mAdapter = adapter.forget();
+ break;
+ }
+ }
+ }
+
+ if (!mAdapter) {
+ return nullptr;
+ }
+
+ // We leak this module everywhere, we might as well do so here as well.
+ dxgiModule.disown();
+ return mAdapter;
+}
+
+bool DeviceManagerDx::CreateCompositorDeviceHelper(
+ FeatureState& aD3d11, IDXGIAdapter1* aAdapter, bool aAttemptVideoSupport,
+ RefPtr<ID3D11Device>& aOutDevice) {
+ // Check if a failure was injected for testing.
+ if (StaticPrefs::gfx_testing_device_fail()) {
+ aD3d11.SetFailed(FeatureStatus::Failed,
+ "Direct3D11 device failure simulated by preference",
+ "FEATURE_FAILURE_D3D11_SIM"_ns);
+ return false;
+ }
+
+ UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
+
+ DXGI_ADAPTER_DESC desc;
+ aAdapter->GetDesc(&desc);
+ if (desc.VendorId != 0x1414) {
+ // 0x1414 is Microsoft (e.g. WARP)
+ // When not using WARP, use
+ // D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS to prevent
+ // bug 1092260. IE 11 also uses this flag.
+ flags |= D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS;
+ }
+
+ if (aAttemptVideoSupport) {
+ flags |= D3D11_CREATE_DEVICE_VIDEO_SUPPORT;
+ }
+
+ HRESULT hr;
+ RefPtr<ID3D11Device> device;
+ if (!CreateDevice(aAdapter, D3D_DRIVER_TYPE_UNKNOWN, flags, hr, device)) {
+ if (!aAttemptVideoSupport) {
+ gfxCriticalError() << "Crash during D3D11 device creation";
+ aD3d11.SetFailed(FeatureStatus::CrashedInHandler,
+ "Crashed trying to acquire a D3D11 device",
+ "FEATURE_FAILURE_D3D11_DEVICE1"_ns);
+ }
+ return false;
+ }
+
+ if (FAILED(hr) || !device) {
+ if (!aAttemptVideoSupport) {
+ aD3d11.SetFailed(FeatureStatus::Failed,
+ "Failed to acquire a D3D11 device",
+ "FEATURE_FAILURE_D3D11_DEVICE2"_ns);
+ }
+ return false;
+ }
+ if (!D3D11Checks::DoesDeviceWork()) {
+ if (!aAttemptVideoSupport) {
+ aD3d11.SetFailed(FeatureStatus::Broken,
+ "Direct3D11 device was determined to be broken",
+ "FEATURE_FAILURE_D3D11_BROKEN"_ns);
+ }
+ return false;
+ }
+
+ aOutDevice = device;
+ return true;
+}
+
+// Note that it's enough for us to just use a counter for a unique ID,
+// even though the counter isn't synchronized between processes. If we
+// start in the GPU process and wind up in the parent process, the
+// whole graphics stack is blown away anyway. But just in case, we
+// make gpu process IDs negative and parent process IDs positive.
+static inline int32_t GetNextDeviceCounter() {
+ static int32_t sDeviceCounter = 0;
+ return XRE_IsGPUProcess() ? --sDeviceCounter : ++sDeviceCounter;
+}
+
+void DeviceManagerDx::CreateCompositorDevice(FeatureState& d3d11) {
+ if (StaticPrefs::layers_d3d11_force_warp_AtStartup()) {
+ CreateWARPCompositorDevice();
+ return;
+ }
+
+ RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapterLocked();
+ if (!adapter) {
+ d3d11.SetFailed(FeatureStatus::Unavailable,
+ "Failed to acquire a DXGI adapter",
+ "FEATURE_FAILURE_D3D11_DXGI"_ns);
+ return;
+ }
+
+ if (XRE_IsGPUProcess() && !D3D11Checks::DoesRemotePresentWork(adapter)) {
+ d3d11.SetFailed(FeatureStatus::Unavailable,
+ "DXGI does not support out-of-process presentation",
+ "FEATURE_FAILURE_D3D11_REMOTE_PRESENT"_ns);
+ return;
+ }
+
+ RefPtr<ID3D11Device> device;
+ if (!CreateCompositorDeviceHelper(d3d11, adapter, true, device)) {
+ // Try again without video support and record that it failed.
+ mCompositorDeviceSupportsVideo = false;
+ if (!CreateCompositorDeviceHelper(d3d11, adapter, false, device)) {
+ return;
+ }
+ } else {
+ mCompositorDeviceSupportsVideo = true;
+ }
+
+ // Only test this when not using WARP since it can fail and cause
+ // GetDeviceRemovedReason to return weird values.
+ bool textureSharingWorks = D3D11Checks::DoesTextureSharingWork(device);
+
+ DXGI_ADAPTER_DESC desc;
+ PodZero(&desc);
+ adapter->GetDesc(&desc);
+
+ if (!textureSharingWorks) {
+ gfxConfig::SetFailed(Feature::D3D11_HW_ANGLE, FeatureStatus::Broken,
+ "Texture sharing doesn't work",
+ "FEATURE_FAILURE_HW_ANGLE_NEEDS_TEXTURE_SHARING"_ns);
+ }
+ if (D3D11Checks::DoesRenderTargetViewNeedRecreating(device)) {
+ gfxConfig::SetFailed(Feature::D3D11_HW_ANGLE, FeatureStatus::Broken,
+ "RenderTargetViews need recreating",
+ "FEATURE_FAILURE_HW_ANGLE_NEEDS_RTV_RECREATION"_ns);
+ }
+ if (XRE_IsParentProcess()) {
+ // It seems like this may only happen when we're using the NVIDIA gpu
+ D3D11Checks::WarnOnAdapterMismatch(device);
+ }
+
+ uint32_t featureLevel = device->GetFeatureLevel();
+ auto formatOptions = D3D11Checks::FormatOptions(device);
+ mCompositorDevice = device;
+
+ int32_t sequenceNumber = GetNextDeviceCounter();
+ mDeviceStatus = Some(D3D11DeviceStatus(
+ false, textureSharingWorks, featureLevel, DxgiAdapterDesc::From(desc),
+ sequenceNumber, formatOptions));
+ mCompositorDevice->SetExceptionMode(0);
+}
+
+bool DeviceManagerDx::CreateDevice(IDXGIAdapter* aAdapter,
+ D3D_DRIVER_TYPE aDriverType, UINT aFlags,
+ HRESULT& aResOut,
+ RefPtr<ID3D11Device>& aOutDevice) {
+ if (StaticPrefs::gfx_direct3d11_enable_debug_layer_AtStartup() ||
+ StaticPrefs::gfx_direct3d11_break_on_error_AtStartup()) {
+ aFlags |= D3D11_CREATE_DEVICE_DEBUG;
+ }
+
+ MOZ_SEH_TRY {
+ aResOut = sD3D11CreateDeviceFn(
+ aAdapter, aDriverType, nullptr, aFlags, mFeatureLevels.Elements(),
+ mFeatureLevels.Length(), D3D11_SDK_VERSION, getter_AddRefs(aOutDevice),
+ nullptr, nullptr);
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { return false; }
+
+ if (StaticPrefs::gfx_direct3d11_break_on_error_AtStartup()) {
+ do {
+ if (!aOutDevice) break;
+
+ RefPtr<ID3D11Debug> debug;
+ if (!SUCCEEDED(aOutDevice->QueryInterface(__uuidof(ID3D11Debug),
+ getter_AddRefs(debug))))
+ break;
+
+ RefPtr<ID3D11InfoQueue> infoQueue;
+ if (!SUCCEEDED(debug->QueryInterface(__uuidof(ID3D11InfoQueue),
+ getter_AddRefs(infoQueue))))
+ break;
+
+ D3D11_INFO_QUEUE_FILTER filter;
+ PodZero(&filter);
+
+ // Disable warnings caused by Advanced Layers that are known and not
+ // problematic.
+ D3D11_MESSAGE_ID blockIDs[] = {
+ D3D11_MESSAGE_ID_DEVICE_DRAW_CONSTANT_BUFFER_TOO_SMALL};
+ filter.DenyList.NumIDs = MOZ_ARRAY_LENGTH(blockIDs);
+ filter.DenyList.pIDList = blockIDs;
+ infoQueue->PushStorageFilter(&filter);
+
+ infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_CORRUPTION, true);
+ infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, true);
+ infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_WARNING, true);
+ } while (false);
+ }
+
+ return true;
+}
+
+void DeviceManagerDx::CreateWARPCompositorDevice() {
+ ScopedGfxFeatureReporter reporterWARP(
+ "D3D11-WARP", StaticPrefs::layers_d3d11_force_warp_AtStartup());
+ FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING);
+
+ HRESULT hr;
+ RefPtr<ID3D11Device> device;
+
+ // Use D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS
+ // to prevent bug 1092260. IE 11 also uses this flag.
+ UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
+ if (!CreateDevice(nullptr, D3D_DRIVER_TYPE_WARP, flags, hr, device)) {
+ gfxCriticalError() << "Exception occurred initializing WARP D3D11 device!";
+ d3d11.SetFailed(FeatureStatus::CrashedInHandler,
+ "Crashed creating a D3D11 WARP device",
+ "FEATURE_FAILURE_D3D11_WARP_DEVICE"_ns);
+ }
+
+ if (FAILED(hr) || !device) {
+ // This should always succeed... in theory.
+ gfxCriticalError() << "Failed to initialize WARP D3D11 device! "
+ << hexa(hr);
+ d3d11.SetFailed(FeatureStatus::Failed,
+ "Failed to create a D3D11 WARP device",
+ "FEATURE_FAILURE_D3D11_WARP_DEVICE2"_ns);
+ return;
+ }
+
+ // Only test for texture sharing on Windows 8 since it puts the device into
+ // an unusable state if used on Windows 7
+ bool textureSharingWorks = false;
+ if (IsWin8OrLater()) {
+ textureSharingWorks = D3D11Checks::DoesTextureSharingWork(device);
+ }
+
+ DXGI_ADAPTER_DESC desc;
+ D3D11Checks::GetDxgiDesc(device, &desc);
+
+ int featureLevel = device->GetFeatureLevel();
+
+ auto formatOptions = D3D11Checks::FormatOptions(device);
+ mCompositorDevice = device;
+
+ int32_t sequenceNumber = GetNextDeviceCounter();
+ mDeviceStatus = Some(D3D11DeviceStatus(
+ true, textureSharingWorks, featureLevel, DxgiAdapterDesc::From(desc),
+ sequenceNumber, formatOptions));
+ mCompositorDevice->SetExceptionMode(0);
+
+ reporterWARP.SetSuccessful();
+}
+
+FeatureStatus DeviceManagerDx::CreateContentDevice() {
+ RefPtr<IDXGIAdapter1> adapter;
+ if (!mDeviceStatus->isWARP()) {
+ adapter = GetDXGIAdapterLocked();
+ if (!adapter) {
+ gfxCriticalNote << "Could not get a DXGI adapter";
+ return FeatureStatus::Unavailable;
+ }
+ }
+
+ HRESULT hr;
+ RefPtr<ID3D11Device> device;
+
+ UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
+ D3D_DRIVER_TYPE type =
+ mDeviceStatus->isWARP() ? D3D_DRIVER_TYPE_WARP : D3D_DRIVER_TYPE_UNKNOWN;
+ if (!CreateDevice(adapter, type, flags, hr, device)) {
+ gfxCriticalNote
+ << "Recovered from crash while creating a D3D11 content device";
+ gfxWindowsPlatform::RecordContentDeviceFailure(
+ TelemetryDeviceCode::Content);
+ return FeatureStatus::CrashedInHandler;
+ }
+
+ if (FAILED(hr) || !device) {
+ gfxCriticalNote << "Failed to create a D3D11 content device: " << hexa(hr);
+ gfxWindowsPlatform::RecordContentDeviceFailure(
+ TelemetryDeviceCode::Content);
+ return FeatureStatus::Failed;
+ }
+
+ // InitializeD2D() will abort early if the compositor device did not support
+ // texture sharing. If we're in the content process, we can't rely on the
+ // parent device alone: some systems have dual GPUs that are capable of
+ // binding the parent and child processes to different GPUs. As a safety net,
+ // we re-check texture sharing against the newly created D3D11 content device.
+ // If it fails, we won't use Direct2D.
+ if (XRE_IsContentProcess()) {
+ if (!D3D11Checks::DoesTextureSharingWork(device)) {
+ return FeatureStatus::Failed;
+ }
+
+ DebugOnly<bool> ok = ContentAdapterIsParentAdapter(device);
+ MOZ_ASSERT(ok);
+ }
+
+ mContentDevice = device;
+ mContentDevice->SetExceptionMode(0);
+
+ RefPtr<ID3D10Multithread> multi;
+ hr = mContentDevice->QueryInterface(__uuidof(ID3D10Multithread),
+ getter_AddRefs(multi));
+ if (SUCCEEDED(hr) && multi) {
+ multi->SetMultithreadProtected(TRUE);
+ }
+ return FeatureStatus::Available;
+}
+
+RefPtr<ID3D11Device> DeviceManagerDx::CreateDecoderDevice(
+ bool aHardwareWebRender) {
+ MutexAutoLock lock(mDeviceLock);
+
+ if (!mDeviceStatus) {
+ return nullptr;
+ }
+
+ bool isAMD = mDeviceStatus->adapter().VendorId == 0x1002;
+ bool reuseDevice = false;
+ if (gfxVars::ReuseDecoderDevice()) {
+ reuseDevice = true;
+ } else if (isAMD) {
+ reuseDevice = true;
+ gfxCriticalNoteOnce << "Always have to reuse decoder device on AMD";
+ }
+
+ if (reuseDevice) {
+ // Use mCompositorDevice for decoder device only for hardware WebRender.
+ if (aHardwareWebRender && mCompositorDevice &&
+ mCompositorDeviceSupportsVideo && !mDecoderDevice) {
+ mDecoderDevice = mCompositorDevice;
+
+ RefPtr<ID3D10Multithread> multi;
+ mDecoderDevice->QueryInterface(__uuidof(ID3D10Multithread),
+ getter_AddRefs(multi));
+ if (multi) {
+ multi->SetMultithreadProtected(TRUE);
+ }
+ }
+
+ if (mDecoderDevice) {
+ RefPtr<ID3D11Device> dev = mDecoderDevice;
+ return dev.forget();
+ }
+ }
+
+ if (!sD3D11CreateDeviceFn) {
+ // We should just be on Windows Vista or XP in this case.
+ return nullptr;
+ }
+
+ RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapterLocked();
+ if (!adapter) {
+ return nullptr;
+ }
+
+ HRESULT hr;
+ RefPtr<ID3D11Device> device;
+
+ UINT flags = D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS |
+ D3D11_CREATE_DEVICE_VIDEO_SUPPORT;
+ if (!CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, flags, hr, device)) {
+ return nullptr;
+ }
+ if (FAILED(hr) || !device || !D3D11Checks::DoesDeviceWork()) {
+ return nullptr;
+ }
+
+ RefPtr<ID3D10Multithread> multi;
+ device->QueryInterface(__uuidof(ID3D10Multithread), getter_AddRefs(multi));
+ if (multi) {
+ multi->SetMultithreadProtected(TRUE);
+ }
+ if (reuseDevice) {
+ mDecoderDevice = device;
+ }
+ return device;
+}
+
+// ID3D11DeviceChild, IDXGIObject and ID3D11Device implement SetPrivateData with
+// the exact same parameters.
+template <typename T>
+static HRESULT SetDebugName(T* d3d11Object, const char* debugString) {
+ return d3d11Object->SetPrivateData(WKPDID_D3DDebugObjectName,
+ strlen(debugString), debugString);
+}
+
+RefPtr<ID3D11Device> DeviceManagerDx::CreateMediaEngineDevice() {
+ MutexAutoLock lock(mDeviceLock);
+ if (!LoadD3D11()) {
+ return nullptr;
+ }
+
+ HRESULT hr;
+ RefPtr<ID3D11Device> device;
+ UINT flags = D3D11_CREATE_DEVICE_VIDEO_SUPPORT |
+ D3D11_CREATE_DEVICE_BGRA_SUPPORT |
+ D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS;
+ if (!CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, flags, hr, device)) {
+ return nullptr;
+ }
+ if (FAILED(hr) || !device || !D3D11Checks::DoesDeviceWork()) {
+ return nullptr;
+ }
+ Unused << SetDebugName(device.get(), "MFMediaEngineDevice");
+
+ RefPtr<ID3D10Multithread> multi;
+ device->QueryInterface(__uuidof(ID3D10Multithread), getter_AddRefs(multi));
+ if (multi) {
+ multi->SetMultithreadProtected(TRUE);
+ }
+ return device;
+}
+
+void DeviceManagerDx::ResetDevices() {
+ MutexAutoLock lock(mDeviceLock);
+ ResetDevicesLocked();
+}
+
+void DeviceManagerDx::ResetDevicesLocked() {
+ mAdapter = nullptr;
+ mCompositorAttachments = nullptr;
+ mCompositorDevice = nullptr;
+ mContentDevice = nullptr;
+ mCanvasDevice = nullptr;
+ mImageDevice = nullptr;
+ mVRDevice = nullptr;
+ mDecoderDevice = nullptr;
+ mDirectCompositionDevice = nullptr;
+ mDeviceStatus = Nothing();
+ mDeviceResetReason = Nothing();
+ Factory::SetDirect3D11Device(nullptr);
+}
+
+bool DeviceManagerDx::MaybeResetAndReacquireDevices() {
+ MutexAutoLock lock(mDeviceLock);
+
+ DeviceResetReason resetReason;
+ if (!HasDeviceResetLocked(&resetReason)) {
+ return false;
+ }
+
+ GPUProcessManager::RecordDeviceReset(resetReason);
+
+ bool createCompositorDevice = !!mCompositorDevice;
+ bool createContentDevice = !!mContentDevice;
+ bool createCanvasDevice = !!mCanvasDevice;
+ bool createDirectCompositionDevice = !!mDirectCompositionDevice;
+
+ ResetDevicesLocked();
+
+ if (createCompositorDevice && !CreateCompositorDevicesLocked()) {
+ // Just stop, don't try anything more
+ return true;
+ }
+ if (createContentDevice) {
+ CreateContentDevicesLocked();
+ }
+ if (createCanvasDevice) {
+ CreateCanvasDeviceLocked();
+ }
+ if (createDirectCompositionDevice) {
+ CreateDirectCompositionDeviceLocked();
+ }
+
+ return true;
+}
+
+bool DeviceManagerDx::ContentAdapterIsParentAdapter(ID3D11Device* device) {
+ DXGI_ADAPTER_DESC desc;
+ if (!D3D11Checks::GetDxgiDesc(device, &desc)) {
+ gfxCriticalNote << "Could not query device DXGI adapter info";
+ return false;
+ }
+
+ const DxgiAdapterDesc& preferred = mDeviceStatus->adapter();
+
+ if (desc.VendorId != preferred.VendorId ||
+ desc.DeviceId != preferred.DeviceId ||
+ desc.SubSysId != preferred.SubSysId ||
+ desc.AdapterLuid.HighPart != preferred.AdapterLuid.HighPart ||
+ desc.AdapterLuid.LowPart != preferred.AdapterLuid.LowPart) {
+ gfxCriticalNote << "VendorIDMismatch P " << hexa(preferred.VendorId) << " "
+ << hexa(desc.VendorId);
+ return false;
+ }
+
+ return true;
+}
+
+static DeviceResetReason HResultToResetReason(HRESULT hr) {
+ switch (hr) {
+ case DXGI_ERROR_DEVICE_HUNG:
+ return DeviceResetReason::HUNG;
+ case DXGI_ERROR_DEVICE_REMOVED:
+ return DeviceResetReason::REMOVED;
+ case DXGI_ERROR_DEVICE_RESET:
+ return DeviceResetReason::RESET;
+ case DXGI_ERROR_DRIVER_INTERNAL_ERROR:
+ return DeviceResetReason::DRIVER_ERROR;
+ case DXGI_ERROR_INVALID_CALL:
+ return DeviceResetReason::INVALID_CALL;
+ case E_OUTOFMEMORY:
+ return DeviceResetReason::OUT_OF_MEMORY;
+ default:
+ MOZ_ASSERT(false);
+ }
+ return DeviceResetReason::OTHER;
+}
+
+bool DeviceManagerDx::HasDeviceReset(DeviceResetReason* aOutReason) {
+ MutexAutoLock lock(mDeviceLock);
+ return HasDeviceResetLocked(aOutReason);
+}
+
+bool DeviceManagerDx::HasDeviceResetLocked(DeviceResetReason* aOutReason) {
+ if (mDeviceResetReason) {
+ if (aOutReason) {
+ *aOutReason = mDeviceResetReason.value();
+ }
+ return true;
+ }
+
+ DeviceResetReason reason;
+ if (GetAnyDeviceRemovedReason(&reason)) {
+ mDeviceResetReason = Some(reason);
+ if (aOutReason) {
+ *aOutReason = reason;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+static inline bool DidDeviceReset(const RefPtr<ID3D11Device>& aDevice,
+ DeviceResetReason* aOutReason) {
+ if (!aDevice) {
+ return false;
+ }
+ HRESULT hr = aDevice->GetDeviceRemovedReason();
+ if (hr == S_OK) {
+ return false;
+ }
+
+ *aOutReason = HResultToResetReason(hr);
+ return true;
+}
+
+bool DeviceManagerDx::GetAnyDeviceRemovedReason(DeviceResetReason* aOutReason) {
+ if (DidDeviceReset(mCompositorDevice, aOutReason) ||
+ DidDeviceReset(mContentDevice, aOutReason) ||
+ DidDeviceReset(mCanvasDevice, aOutReason)) {
+ return true;
+ }
+
+ if (XRE_IsParentProcess() && NS_IsMainThread() &&
+ StaticPrefs::gfx_testing_device_reset()) {
+ Preferences::SetInt("gfx.testing.device-reset", 0);
+ *aOutReason = DeviceResetReason::FORCED_RESET;
+ return true;
+ }
+
+ return false;
+}
+
+void DeviceManagerDx::ForceDeviceReset(ForcedDeviceResetReason aReason) {
+ Telemetry::Accumulate(Telemetry::FORCED_DEVICE_RESET_REASON,
+ uint32_t(aReason));
+ {
+ MutexAutoLock lock(mDeviceLock);
+ if (!mDeviceResetReason) {
+ mDeviceResetReason = Some(DeviceResetReason::FORCED_RESET);
+ }
+ }
+}
+
+void DeviceManagerDx::DisableD3D11AfterCrash() {
+ gfxConfig::Disable(Feature::D3D11_COMPOSITING,
+ FeatureStatus::CrashedInHandler,
+ "Crashed while acquiring a Direct3D11 device",
+ "FEATURE_FAILURE_D3D11_CRASH"_ns);
+ ResetDevices();
+}
+
+RefPtr<ID3D11Device> DeviceManagerDx::GetCompositorDevice() {
+ MutexAutoLock lock(mDeviceLock);
+ return mCompositorDevice;
+}
+
+RefPtr<ID3D11Device> DeviceManagerDx::GetContentDevice() {
+ MOZ_ASSERT(XRE_IsGPUProcess() ||
+ gfxPlatform::GetPlatform()->DevicesInitialized());
+
+ MutexAutoLock lock(mDeviceLock);
+ return mContentDevice;
+}
+
+RefPtr<ID3D11Device> DeviceManagerDx::GetImageDevice() {
+ MutexAutoLock lock(mDeviceLock);
+ if (mImageDevice) {
+ return mImageDevice;
+ }
+
+ RefPtr<ID3D11Device> device = mContentDevice;
+ if (!device) {
+ device = mCompositorDevice;
+ }
+
+ if (!device) {
+ return nullptr;
+ }
+
+ RefPtr<ID3D10Multithread> multi;
+ HRESULT hr =
+ device->QueryInterface((ID3D10Multithread**)getter_AddRefs(multi));
+ if (FAILED(hr) || !multi) {
+ gfxWarning() << "Multithread safety interface not supported. " << hr;
+ return nullptr;
+ }
+ multi->SetMultithreadProtected(TRUE);
+
+ mImageDevice = device;
+
+ return mImageDevice;
+}
+
+RefPtr<ID3D11Device> DeviceManagerDx::GetVRDevice() {
+ MutexAutoLock lock(mDeviceLock);
+ if (!mVRDevice) {
+ CreateVRDevice();
+ }
+ return mVRDevice;
+}
+
+RefPtr<ID3D11Device> DeviceManagerDx::GetCanvasDevice() {
+ MutexAutoLock lock(mDeviceLock);
+ return mCanvasDevice;
+}
+
+RefPtr<IDCompositionDevice2> DeviceManagerDx::GetDirectCompositionDevice() {
+ MutexAutoLock lock(mDeviceLock);
+ return mDirectCompositionDevice;
+}
+
+unsigned DeviceManagerDx::GetCompositorFeatureLevel() const {
+ MutexAutoLock lock(mDeviceLock);
+ if (!mDeviceStatus) {
+ return 0;
+ }
+ return mDeviceStatus->featureLevel();
+}
+
+bool DeviceManagerDx::TextureSharingWorks() {
+ MutexAutoLock lock(mDeviceLock);
+ if (!mDeviceStatus) {
+ return false;
+ }
+ return mDeviceStatus->textureSharingWorks();
+}
+
+bool DeviceManagerDx::CanInitializeKeyedMutexTextures() {
+ MutexAutoLock lock(mDeviceLock);
+ return mDeviceStatus && StaticPrefs::gfx_direct3d11_allow_keyed_mutex() &&
+ gfxVars::AllowD3D11KeyedMutex();
+}
+
+bool DeviceManagerDx::HasCrashyInitData() {
+ MutexAutoLock lock(mDeviceLock);
+ if (!mDeviceStatus) {
+ return false;
+ }
+
+ return (mDeviceStatus->adapter().VendorId == 0x8086 && !IsWin10OrLater());
+}
+
+bool DeviceManagerDx::CheckRemotePresentSupport() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapter();
+ if (!adapter) {
+ return false;
+ }
+ if (!D3D11Checks::DoesRemotePresentWork(adapter)) {
+ return false;
+ }
+ return true;
+}
+
+bool DeviceManagerDx::IsWARP() {
+ MutexAutoLock lock(mDeviceLock);
+ if (!mDeviceStatus) {
+ return false;
+ }
+ return mDeviceStatus->isWARP();
+}
+
+bool DeviceManagerDx::CanUseNV12() {
+ MutexAutoLock lock(mDeviceLock);
+ if (!mDeviceStatus) {
+ return false;
+ }
+ return mDeviceStatus->formatOptions().contains(
+ D3D11Checks::VideoFormatOption::NV12);
+}
+
+bool DeviceManagerDx::CanUseP010() {
+ MutexAutoLock lock(mDeviceLock);
+ if (!mDeviceStatus) {
+ return false;
+ }
+ return mDeviceStatus->formatOptions().contains(
+ D3D11Checks::VideoFormatOption::P010);
+}
+
+bool DeviceManagerDx::CanUseP016() {
+ MutexAutoLock lock(mDeviceLock);
+ if (!mDeviceStatus) {
+ return false;
+ }
+ return mDeviceStatus->formatOptions().contains(
+ D3D11Checks::VideoFormatOption::P016);
+}
+
+bool DeviceManagerDx::CanUseDComp() {
+ MutexAutoLock lock(mDeviceLock);
+ return !!mDirectCompositionDevice;
+}
+
+void DeviceManagerDx::InitializeDirectDraw() {
+ MOZ_ASSERT(layers::CompositorThreadHolder::IsInCompositorThread());
+
+ if (mDirectDraw) {
+ // Already initialized.
+ return;
+ }
+
+ FeatureState& ddraw = gfxConfig::GetFeature(Feature::DIRECT_DRAW);
+ if (!ddraw.IsEnabled()) {
+ return;
+ }
+
+ // Check if DirectDraw is available on this system.
+ mDirectDrawDLL.own(LoadLibrarySystem32(L"ddraw.dll"));
+ if (!mDirectDrawDLL) {
+ ddraw.SetFailed(FeatureStatus::Unavailable,
+ "DirectDraw not available on this computer",
+ "FEATURE_FAILURE_DDRAW_LIB"_ns);
+ return;
+ }
+
+ sDirectDrawCreateExFn = (decltype(DirectDrawCreateEx)*)GetProcAddress(
+ mDirectDrawDLL, "DirectDrawCreateEx");
+ if (!sDirectDrawCreateExFn) {
+ ddraw.SetFailed(FeatureStatus::Unavailable,
+ "DirectDraw not available on this computer",
+ "FEATURE_FAILURE_DDRAW_LIB"_ns);
+ return;
+ }
+
+ HRESULT hr;
+ MOZ_SEH_TRY {
+ hr = sDirectDrawCreateExFn(nullptr, getter_AddRefs(mDirectDraw),
+ IID_IDirectDraw7, nullptr);
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ ddraw.SetFailed(FeatureStatus::Failed, "Failed to create DirectDraw",
+ "FEATURE_FAILURE_DDRAW_LIB"_ns);
+ gfxCriticalNote << "DoesCreatingDirectDrawFailed";
+ return;
+ }
+ if (FAILED(hr)) {
+ ddraw.SetFailed(FeatureStatus::Failed, "Failed to create DirectDraw",
+ "FEATURE_FAILURE_DDRAW_LIB"_ns);
+ gfxCriticalNote << "DoesCreatingDirectDrawFailed " << hexa(hr);
+ return;
+ }
+}
+
+IDirectDraw7* DeviceManagerDx::GetDirectDraw() { return mDirectDraw; }
+
+void DeviceManagerDx::GetCompositorDevices(
+ RefPtr<ID3D11Device>* aOutDevice,
+ RefPtr<layers::DeviceAttachmentsD3D11>* aOutAttachments) {
+ RefPtr<ID3D11Device> device;
+ {
+ MutexAutoLock lock(mDeviceLock);
+ if (!mCompositorDevice) {
+ return;
+ }
+ if (mCompositorAttachments) {
+ *aOutDevice = mCompositorDevice;
+ *aOutAttachments = mCompositorAttachments;
+ return;
+ }
+
+ // Otherwise, we'll try to create attachments outside the lock.
+ device = mCompositorDevice;
+ }
+
+ // We save the attachments object even if it fails to initialize, so the
+ // compositor can grab the failure ID.
+ RefPtr<layers::DeviceAttachmentsD3D11> attachments =
+ layers::DeviceAttachmentsD3D11::Create(device);
+ {
+ MutexAutoLock lock(mDeviceLock);
+ if (device != mCompositorDevice) {
+ return;
+ }
+ mCompositorAttachments = attachments;
+ }
+
+ *aOutDevice = device;
+ *aOutAttachments = attachments;
+}
+
+/* static */
+void DeviceManagerDx::PreloadAttachmentsOnCompositorThread() {
+ if (!CompositorThread()) {
+ return;
+ }
+
+ RefPtr<Runnable> task = NS_NewRunnableFunction(
+ "DeviceManagerDx::PreloadAttachmentsOnCompositorThread", []() -> void {
+ if (DeviceManagerDx* dm = DeviceManagerDx::Get()) {
+ RefPtr<ID3D11Device> device;
+ RefPtr<layers::DeviceAttachmentsD3D11> attachments;
+ dm->GetCompositorDevices(&device, &attachments);
+ }
+ });
+ CompositorThread()->Dispatch(task.forget());
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/thebes/DeviceManagerDx.h b/gfx/thebes/DeviceManagerDx.h
new file mode 100644
index 0000000000..d6d2259d9a
--- /dev/null
+++ b/gfx/thebes/DeviceManagerDx.h
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_gfx_thebes_DeviceManagerDx_h
+#define mozilla_gfx_thebes_DeviceManagerDx_h
+
+#include "gfxPlatform.h"
+#include "gfxTelemetry.h"
+#include "gfxTypes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/gfx/GraphicsMessages.h"
+#include "nsTArray.h"
+#include "nsWindowsHelpers.h"
+
+#include <windows.h>
+#include <objbase.h>
+
+#include <d3d11.h>
+#include <dxgi.h>
+#include <dxgi1_6.h>
+
+// This header is available in the June 2010 SDK and in the Win8 SDK
+#include <d3dcommon.h>
+// Win 8.0 SDK types we'll need when building using older sdks.
+#if !defined(D3D_FEATURE_LEVEL_11_1) // defined in the 8.0 SDK only
+# define D3D_FEATURE_LEVEL_11_1 static_cast<D3D_FEATURE_LEVEL>(0xb100)
+# define D3D_FL9_1_REQ_TEXTURE2D_U_OR_V_DIMENSION 2048
+# define D3D_FL9_3_REQ_TEXTURE2D_U_OR_V_DIMENSION 4096
+#endif
+
+struct ID3D11Device;
+struct IDCompositionDevice2;
+struct IDirectDraw7;
+
+namespace mozilla {
+class ScopedGfxFeatureReporter;
+namespace layers {
+class DeviceAttachmentsD3D11;
+} // namespace layers
+
+namespace gfx {
+class FeatureState;
+
+class DeviceManagerDx final {
+ public:
+ static void Init();
+ static void Shutdown();
+
+ DeviceManagerDx();
+ ~DeviceManagerDx();
+
+ static DeviceManagerDx* Get() { return sInstance; }
+
+ RefPtr<ID3D11Device> GetCompositorDevice();
+ RefPtr<ID3D11Device> GetContentDevice();
+ RefPtr<ID3D11Device> GetCanvasDevice();
+ RefPtr<ID3D11Device> GetImageDevice();
+ RefPtr<IDCompositionDevice2> GetDirectCompositionDevice();
+ RefPtr<ID3D11Device> GetVRDevice();
+ RefPtr<ID3D11Device> CreateDecoderDevice(bool aHardwareWebRender);
+ RefPtr<ID3D11Device> CreateMediaEngineDevice();
+ IDirectDraw7* GetDirectDraw();
+
+ unsigned GetCompositorFeatureLevel() const;
+ bool TextureSharingWorks();
+ bool IsWARP();
+ bool CanUseNV12();
+ bool CanUseP010();
+ bool CanUseP016();
+ bool CanUseDComp();
+
+ // Returns true if we can create a texture with
+ // D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX and also
+ // upload texture data during the CreateTexture2D
+ // call. This crashes on some devices, so we might
+ // need to avoid it.
+ bool CanInitializeKeyedMutexTextures();
+
+ // Intel devices on older windows versions seem to occasionally have
+ // stability issues when supplying InitData to CreateTexture2D.
+ bool HasCrashyInitData();
+
+ // Enumerate and return all outputs on the current adapter.
+ nsTArray<DXGI_OUTPUT_DESC1> EnumerateOutputs();
+
+ // find the IDXGIOutput with a description.Monitor matching
+ // 'monitor'; returns false if not found or some error occurred.
+ bool GetOutputFromMonitor(HMONITOR monitor, RefPtr<IDXGIOutput>* aOutOutput);
+
+ // Check if the current adapter supports hardware stretching
+ void CheckHardwareStretchingSupport(HwStretchingSupport& aRv);
+
+ bool CreateCompositorDevices();
+ void CreateContentDevices();
+ void CreateDirectCompositionDevice();
+ bool CreateCanvasDevice();
+
+ static HANDLE CreateDCompSurfaceHandle();
+
+ void GetCompositorDevices(
+ RefPtr<ID3D11Device>* aOutDevice,
+ RefPtr<layers::DeviceAttachmentsD3D11>* aOutAttachments);
+
+ void ImportDeviceInfo(const D3D11DeviceStatus& aDeviceStatus);
+
+ // Returns whether the device info was exported.
+ bool ExportDeviceInfo(D3D11DeviceStatus* aOut);
+
+ void ResetDevices();
+ void InitializeDirectDraw();
+
+ // Reset and reacquire the devices if a reset has happened.
+ // Returns whether a reset occurred not whether reacquiring
+ // was successful.
+ bool MaybeResetAndReacquireDevices();
+
+ // Test whether we can acquire a DXGI 1.2-compatible adapter. This should
+ // only be called on startup before devices are initialized.
+ bool CheckRemotePresentSupport();
+
+ // Device reset helpers.
+ bool HasDeviceReset(DeviceResetReason* aOutReason = nullptr);
+
+ // Note: these set the cached device reset reason, which will be picked up
+ // on the next frame.
+ void ForceDeviceReset(ForcedDeviceResetReason aReason);
+
+ private:
+ void ResetDevicesLocked() MOZ_REQUIRES(mDeviceLock);
+
+ // Device reset helpers.
+ bool HasDeviceResetLocked(DeviceResetReason* aOutReason = nullptr)
+ MOZ_REQUIRES(mDeviceLock);
+
+ // Pre-load any compositor resources that are expensive, and are needed when
+ // we attempt to create a compositor.
+ static void PreloadAttachmentsOnCompositorThread();
+
+ already_AddRefed<IDXGIAdapter1> GetDXGIAdapter();
+ IDXGIAdapter1* GetDXGIAdapterLocked() MOZ_REQUIRES(mDeviceLock);
+
+ void DisableD3D11AfterCrash();
+
+ bool CreateCompositorDevicesLocked() MOZ_REQUIRES(mDeviceLock);
+ void CreateContentDevicesLocked() MOZ_REQUIRES(mDeviceLock);
+ void CreateDirectCompositionDeviceLocked() MOZ_REQUIRES(mDeviceLock);
+ bool CreateCanvasDeviceLocked() MOZ_REQUIRES(mDeviceLock);
+
+ void CreateCompositorDevice(mozilla::gfx::FeatureState& d3d11)
+ MOZ_REQUIRES(mDeviceLock);
+ bool CreateCompositorDeviceHelper(mozilla::gfx::FeatureState& aD3d11,
+ IDXGIAdapter1* aAdapter,
+ bool aAttemptVideoSupport,
+ RefPtr<ID3D11Device>& aOutDevice)
+ MOZ_REQUIRES(mDeviceLock);
+
+ void CreateWARPCompositorDevice() MOZ_REQUIRES(mDeviceLock);
+ bool CreateVRDevice() MOZ_REQUIRES(mDeviceLock);
+
+ mozilla::gfx::FeatureStatus CreateContentDevice() MOZ_REQUIRES(mDeviceLock);
+
+ bool CreateDevice(IDXGIAdapter* aAdapter, D3D_DRIVER_TYPE aDriverType,
+ UINT aFlags, HRESULT& aResOut,
+ RefPtr<ID3D11Device>& aOutDevice) MOZ_REQUIRES(mDeviceLock);
+
+ bool ContentAdapterIsParentAdapter(ID3D11Device* device)
+ MOZ_REQUIRES(mDeviceLock);
+
+ bool LoadD3D11();
+ bool LoadDcomp();
+ void ReleaseD3D11() MOZ_REQUIRES(mDeviceLock);
+
+ // Call GetDeviceRemovedReason on each device until one returns
+ // a failure.
+ bool GetAnyDeviceRemovedReason(DeviceResetReason* aOutReason)
+ MOZ_REQUIRES(mDeviceLock);
+
+ private:
+ static StaticAutoPtr<DeviceManagerDx> sInstance;
+
+ // This is assigned during device creation. Afterwards, it is released if
+ // devices failed, and "forgotten" if devices succeeded (meaning, we leak
+ // the ref and unassign the module).
+ nsModuleHandle mD3D11Module;
+
+ nsModuleHandle mDcompModule;
+
+ mutable mozilla::Mutex mDeviceLock;
+ nsTArray<D3D_FEATURE_LEVEL> mFeatureLevels MOZ_GUARDED_BY(mDeviceLock);
+ RefPtr<IDXGIAdapter1> mAdapter MOZ_GUARDED_BY(mDeviceLock);
+ RefPtr<ID3D11Device> mCompositorDevice MOZ_GUARDED_BY(mDeviceLock);
+ RefPtr<ID3D11Device> mContentDevice MOZ_GUARDED_BY(mDeviceLock);
+ RefPtr<ID3D11Device> mCanvasDevice MOZ_GUARDED_BY(mDeviceLock);
+ RefPtr<ID3D11Device> mImageDevice MOZ_GUARDED_BY(mDeviceLock);
+ RefPtr<ID3D11Device> mVRDevice MOZ_GUARDED_BY(mDeviceLock);
+ RefPtr<ID3D11Device> mDecoderDevice MOZ_GUARDED_BY(mDeviceLock);
+ RefPtr<IDCompositionDevice2> mDirectCompositionDevice
+ MOZ_GUARDED_BY(mDeviceLock);
+ RefPtr<layers::DeviceAttachmentsD3D11> mCompositorAttachments
+ MOZ_GUARDED_BY(mDeviceLock);
+ bool mCompositorDeviceSupportsVideo MOZ_GUARDED_BY(mDeviceLock);
+ Maybe<D3D11DeviceStatus> mDeviceStatus MOZ_GUARDED_BY(mDeviceLock);
+ Maybe<DeviceResetReason> mDeviceResetReason MOZ_GUARDED_BY(mDeviceLock);
+
+ nsModuleHandle mDirectDrawDLL;
+ RefPtr<IDirectDraw7> mDirectDraw;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // mozilla_gfx_thebes_DeviceManagerDx_h
diff --git a/gfx/thebes/DisplayConfigWindows.cpp b/gfx/thebes/DisplayConfigWindows.cpp
new file mode 100644
index 0000000000..f9696a4070
--- /dev/null
+++ b/gfx/thebes/DisplayConfigWindows.cpp
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <iostream>
+#include <windows.h>
+
+#include "DisplayConfigWindows.h"
+
+namespace mozilla {
+namespace gfx {
+
+using namespace std;
+
+optional<DisplayConfig> GetDisplayConfig() {
+ LONG result;
+
+ UINT32 numPaths;
+ UINT32 numModes;
+ vector<DISPLAYCONFIG_PATH_INFO> paths;
+ vector<DISPLAYCONFIG_MODE_INFO> modes;
+ do {
+ result = GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &numPaths,
+ &numModes);
+ if (result != ERROR_SUCCESS) {
+ return {};
+ }
+ // allocate the recommended amount of space
+ paths.resize(numPaths);
+ modes.resize(numModes);
+
+ result = QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &numPaths, paths.data(),
+ &numModes, modes.data(), NULL);
+ // try again if there wasn't enough space
+ } while (result == ERROR_INSUFFICIENT_BUFFER);
+
+ if (result != ERROR_SUCCESS) return {};
+
+ // shrink to fit the actual number of modes and paths returned
+ modes.resize(numModes);
+ paths.resize(numPaths);
+
+ return DisplayConfig{paths, modes};
+}
+
+bool HasScaledResolution() {
+ auto config = GetDisplayConfig();
+ if (config) {
+ for (auto& path : config->mPaths) {
+ auto& modes = config->mModes;
+ int targetModeIndex = path.targetInfo.modeInfoIdx;
+ int sourceModeIndex = path.sourceInfo.modeInfoIdx;
+
+ // Check if the source and target resolutions are different
+ if ((modes[targetModeIndex]
+ .targetMode.targetVideoSignalInfo.activeSize.cx !=
+ modes[sourceModeIndex].sourceMode.width) ||
+ (modes[targetModeIndex]
+ .targetMode.targetVideoSignalInfo.activeSize.cy !=
+ modes[sourceModeIndex].sourceMode.height)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void GetScaledResolutions(ScaledResolutionSet& aRv) {
+ auto config = GetDisplayConfig();
+ if (config) {
+ for (auto& path : config->mPaths) {
+ auto& modes = config->mModes;
+ int targetModeIndex = path.targetInfo.modeInfoIdx;
+ int sourceModeIndex = path.sourceInfo.modeInfoIdx;
+
+ // Check if the source and target resolutions are different
+ IntSize src(modes[sourceModeIndex].sourceMode.width,
+ modes[sourceModeIndex].sourceMode.height);
+ IntSize dst(
+ modes[targetModeIndex].targetMode.targetVideoSignalInfo.activeSize.cx,
+ modes[targetModeIndex]
+ .targetMode.targetVideoSignalInfo.activeSize.cy);
+ if (src != dst) {
+ aRv.AppendElement(std::pair<IntSize, IntSize>{src, dst});
+ }
+ }
+ }
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/thebes/DisplayConfigWindows.h b/gfx/thebes/DisplayConfigWindows.h
new file mode 100644
index 0000000000..62bc03cf3d
--- /dev/null
+++ b/gfx/thebes/DisplayConfigWindows.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_gfx_thebes_DisplayConfigWindows_h
+#define mozilla_gfx_thebes_DisplayConfigWindows_h
+
+#include <optional> // for std::optional
+#include <utility> // for std::pair
+#include <vector> // for std::vector
+#include <wingdi.h>
+#include "mozilla/gfx/Point.h" // for IntSize
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace gfx {
+
+struct DisplayConfig {
+ std::vector<DISPLAYCONFIG_PATH_INFO> mPaths;
+ std::vector<DISPLAYCONFIG_MODE_INFO> mModes;
+};
+
+std::optional<DisplayConfig> GetDisplayConfig();
+
+extern bool HasScaledResolution();
+
+typedef nsTArray<std::pair<IntSize, IntSize>> ScaledResolutionSet;
+void GetScaledResolutions(ScaledResolutionSet& aRv);
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // mozilla_gfx_thebes_DisplayConfigWindows_h
diff --git a/gfx/thebes/DrawMode.h b/gfx/thebes/DrawMode.h
new file mode 100644
index 0000000000..f9c2ac2b64
--- /dev/null
+++ b/gfx/thebes/DrawMode.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DrawMode_h
+#define DrawMode_h
+
+#include "mozilla/TypedEnumBits.h"
+
+// Options for how the text should be drawn
+enum class DrawMode : int {
+ // GLYPH_FILL and GLYPH_STROKE draw into the current context
+ // and may be used together with bitwise OR.
+ GLYPH_FILL = 1 << 0,
+ // Note: using GLYPH_STROKE will destroy the current path.
+ GLYPH_STROKE = 1 << 1,
+ // Appends glyphs to the current path. Can NOT be used with
+ // GLYPH_FILL or GLYPH_STROKE.
+ GLYPH_PATH = 1 << 2,
+ // When GLYPH_FILL and GLYPH_STROKE are both set, draws the
+ // stroke underneath the fill.
+ GLYPH_STROKE_UNDERNEATH = 1 << 3
+};
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DrawMode)
+#endif
diff --git a/gfx/thebes/PrintPromise.h b/gfx/thebes/PrintPromise.h
new file mode 100644
index 0000000000..8213d676f3
--- /dev/null
+++ b/gfx/thebes/PrintPromise.h
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_GFX_PRINTPROMISE_H
+#define MOZILLA_GFX_PRINTPROMISE_H
+
+#include "ErrorList.h"
+#include "mozilla/MozPromise.h"
+
+namespace mozilla::gfx {
+
+using PrintEndDocumentPromise = MozPromise</* unused */ bool, nsresult, false>;
+
+} // namespace mozilla::gfx
+
+#endif
diff --git a/gfx/thebes/PrintTarget.cpp b/gfx/thebes/PrintTarget.cpp
new file mode 100644
index 0000000000..7ab6984d8d
--- /dev/null
+++ b/gfx/thebes/PrintTarget.cpp
@@ -0,0 +1,200 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PrintTarget.h"
+
+#include "cairo.h"
+#ifdef CAIRO_HAS_QUARTZ_SURFACE
+# include "cairo-quartz.h"
+#endif
+#ifdef CAIRO_HAS_WIN32_SURFACE
+# include "cairo-win32.h"
+#endif
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/HelpersCairo.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "nsUTF8Utils.h"
+
+// IPP spec disallow the job-name which is over 255 characters.
+// RFC: https://tools.ietf.org/html/rfc2911#section-4.1.2
+#define IPP_JOB_NAME_LIMIT_LENGTH 255
+
+namespace mozilla::gfx {
+
+PrintTarget::PrintTarget(cairo_surface_t* aCairoSurface, const IntSize& aSize)
+ : mCairoSurface(aCairoSurface),
+ mSize(aSize),
+ mIsFinished(false)
+#ifdef DEBUG
+ ,
+ mHasActivePage(false)
+#endif
+
+{
+#if 0
+ // aCairoSurface is null when our PrintTargetThebes subclass's ctor calls us.
+ // Once PrintTargetThebes is removed, enable this assertion.
+ MOZ_ASSERT(aCairoSurface && !cairo_surface_status(aCairoSurface),
+ "CreateOrNull factory methods should not call us without a "
+ "valid cairo_surface_t*");
+#endif
+
+ // CreateOrNull factory methods hand over ownership of aCairoSurface,
+ // so we don't call cairo_surface_reference(aSurface) here.
+
+ // This code was copied from gfxASurface::Init:
+ if (mCairoSurface &&
+ cairo_surface_get_content(mCairoSurface) != CAIRO_CONTENT_COLOR) {
+ cairo_surface_set_subpixel_antialiasing(
+ mCairoSurface, CAIRO_SUBPIXEL_ANTIALIASING_DISABLED);
+ }
+}
+
+PrintTarget::~PrintTarget() {
+ // null surfaces are allowed here
+ cairo_surface_destroy(mCairoSurface);
+ mCairoSurface = nullptr;
+}
+
+already_AddRefed<DrawTarget> PrintTarget::MakeDrawTarget(
+ const IntSize& aSize, DrawEventRecorder* aRecorder) {
+ MOZ_ASSERT(mCairoSurface,
+ "We shouldn't have been constructed without a cairo surface");
+
+ // This should not be called outside of BeginPage()/EndPage() calls since
+ // some backends can only provide a valid DrawTarget at that time.
+ MOZ_ASSERT(mHasActivePage, "We can't guarantee a valid DrawTarget");
+
+ if (cairo_surface_status(mCairoSurface)) {
+ return nullptr;
+ }
+
+ // Note than aSize may not be the same as mSize (the size of mCairoSurface).
+ // See the comments in our header. If the sizes are different a clip will
+ // be applied to mCairoSurface.
+ RefPtr<DrawTarget> dt =
+ Factory::CreateDrawTargetForCairoSurface(mCairoSurface, aSize);
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+
+ if (aRecorder) {
+ dt = CreateRecordingDrawTarget(aRecorder, dt);
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+ }
+
+ return dt.forget();
+}
+
+already_AddRefed<DrawTarget> PrintTarget::GetReferenceDrawTarget() {
+ if (!mRefDT) {
+ const IntSize size(1, 1);
+
+ cairo_surface_t* similar;
+ switch (cairo_surface_get_type(mCairoSurface)) {
+#ifdef CAIRO_HAS_WIN32_SURFACE
+ case CAIRO_SURFACE_TYPE_WIN32:
+ similar = cairo_win32_surface_create_with_dib(
+ CairoContentToCairoFormat(cairo_surface_get_content(mCairoSurface)),
+ size.width, size.height);
+ break;
+#endif
+#ifdef CAIRO_HAS_QUARTZ_SURFACE
+ case CAIRO_SURFACE_TYPE_QUARTZ:
+ if (StaticPrefs::gfx_cairo_quartz_cg_layer_enabled()) {
+ similar = cairo_quartz_surface_create_cg_layer(
+ mCairoSurface, cairo_surface_get_content(mCairoSurface),
+ size.width, size.height);
+ break;
+ }
+ [[fallthrough]];
+#endif
+ default:
+ similar = cairo_surface_create_similar(
+ mCairoSurface, cairo_surface_get_content(mCairoSurface), size.width,
+ size.height);
+ break;
+ }
+
+ if (cairo_surface_status(similar)) {
+ return nullptr;
+ }
+
+ RefPtr<DrawTarget> dt =
+ Factory::CreateDrawTargetForCairoSurface(similar, size);
+
+ // The DT addrefs the surface, so we need drop our own reference to it:
+ cairo_surface_destroy(similar);
+
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+ mRefDT = std::move(dt);
+ }
+
+ return do_AddRef(mRefDT);
+}
+
+/* static */
+void PrintTarget::AdjustPrintJobNameForIPP(const nsAString& aJobName,
+ nsCString& aAdjustedJobName) {
+ CopyUTF16toUTF8(aJobName, aAdjustedJobName);
+
+ if (aAdjustedJobName.Length() > IPP_JOB_NAME_LIMIT_LENGTH) {
+ uint32_t length = RewindToPriorUTF8Codepoint(
+ aAdjustedJobName.get(), (IPP_JOB_NAME_LIMIT_LENGTH - 3U));
+ aAdjustedJobName.SetLength(length);
+ aAdjustedJobName.AppendLiteral("...");
+ }
+}
+
+/* static */
+void PrintTarget::AdjustPrintJobNameForIPP(const nsAString& aJobName,
+ nsString& aAdjustedJobName) {
+ nsAutoCString jobName;
+ AdjustPrintJobNameForIPP(aJobName, jobName);
+
+ CopyUTF8toUTF16(jobName, aAdjustedJobName);
+}
+
+/* static */
+already_AddRefed<DrawTarget> PrintTarget::CreateRecordingDrawTarget(
+ DrawEventRecorder* aRecorder, DrawTarget* aDrawTarget) {
+ MOZ_ASSERT(aRecorder);
+ MOZ_ASSERT(aDrawTarget);
+
+ RefPtr<DrawTarget> dt;
+
+ if (aRecorder) {
+ // It doesn't really matter what we pass as the DrawTarget here.
+ dt = gfx::Factory::CreateRecordingDrawTarget(aRecorder, aDrawTarget,
+ aDrawTarget->GetRect());
+ }
+
+ if (!dt || !dt->IsValid()) {
+ gfxCriticalNote
+ << "Failed to create a recording DrawTarget for PrintTarget";
+ return nullptr;
+ }
+
+ return dt.forget();
+}
+
+void PrintTarget::Finish() {
+ if (mIsFinished) {
+ return;
+ }
+ mIsFinished = true;
+
+ // null surfaces are allowed here
+ cairo_surface_finish(mCairoSurface);
+}
+
+} // namespace mozilla::gfx
diff --git a/gfx/thebes/PrintTarget.h b/gfx/thebes/PrintTarget.h
new file mode 100644
index 0000000000..9f1d018c6c
--- /dev/null
+++ b/gfx/thebes/PrintTarget.h
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_GFX_PRINTTARGET_H
+#define MOZILLA_GFX_PRINTTARGET_H
+
+#include <functional>
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/gfx/2D.h"
+#include "nsISupportsImpl.h"
+#include "nsStringFwd.h"
+
+namespace mozilla {
+namespace gfx {
+
+class DrawEventRecorder;
+
+/**
+ * A class that is used to draw output that is to be sent to a printer or print
+ * preview.
+ *
+ * This class wraps a cairo_surface_t* and provides access to it via a
+ * DrawTarget. The various checkpointing methods manage the state of the
+ * platform specific cairo_surface_t*.
+ */
+class PrintTarget {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(PrintTarget);
+
+ /// Must be matched 1:1 by an EndPrinting/AbortPrinting call.
+ virtual nsresult BeginPrinting(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage, int32_t aEndPage) {
+ return NS_OK;
+ }
+ virtual nsresult EndPrinting() { return NS_OK; }
+ virtual nsresult AbortPrinting() {
+#ifdef DEBUG
+ mHasActivePage = false;
+#endif
+ return NS_OK;
+ }
+ virtual nsresult BeginPage() {
+#ifdef DEBUG
+ MOZ_ASSERT(!mHasActivePage, "Missing EndPage() call");
+ mHasActivePage = true;
+#endif
+ return NS_OK;
+ }
+ virtual nsresult EndPage() {
+#ifdef DEBUG
+ mHasActivePage = false;
+#endif
+ return NS_OK;
+ }
+
+ /**
+ * Releases the resources used by this PrintTarget. Typically this should be
+ * called after calling EndPrinting(). Calling this more than once is
+ * allowed, but subsequent calls are a no-op.
+ *
+ * Note that any DrawTarget obtained from this PrintTarget will no longer be
+ * useful after this method has been called.
+ */
+ virtual void Finish();
+
+ const IntSize& GetSize() const { return mSize; }
+
+ /**
+ * Makes a DrawTarget to draw the printer output to, or returns null on
+ * failure.
+ *
+ * If aRecorder is passed a recording DrawTarget will be created instead of
+ * the type of DrawTarget that would normally be returned for a particular
+ * subclass of this class. This argument is only intended to be used in
+ * the e10s content process if printing output can't otherwise be transfered
+ * over to the parent process using the normal DrawTarget type.
+ *
+ * NOTE: this should only be called between BeginPage()/EndPage() calls, and
+ * the returned DrawTarget should not be drawn to after EndPage() has been
+ * called.
+ *
+ * XXX For consistency with the old code this takes a size parameter even
+ * though we already have the size passed to our subclass's CreateOrNull
+ * factory methods. The size passed to the factory method comes from
+ * nsIDeviceContextSpec::MakePrintTarget overrides, whereas the size
+ * passed to us comes from nsDeviceContext::CreateRenderingContext. In at
+ * least one case (nsDeviceContextSpecAndroid::MakePrintTarget) these are
+ * different. At some point we should align the two sources and get rid of
+ * this method's size parameter.
+ *
+ * XXX For consistency with the old code this returns a new DrawTarget for
+ * each call. Perhaps we can create and cache a DrawTarget in our subclass's
+ * CreateOrNull factory methods and return that on each call? Currently that
+ * seems to cause Mochitest failures on Windows though, which coincidentally
+ * is the only platform where we get passed an aRecorder. Probably the
+ * issue is that we get called more than once with a different aRecorder, so
+ * storing one recording DrawTarget for our lifetime doesn't currently work.
+ *
+ * XXX Could we pass aRecorder to our subclass's CreateOrNull factory methods?
+ * We'd need to check that our consumers always pass the same aRecorder for
+ * our entire lifetime.
+ *
+ * XXX Once PrintTargetThebes is removed this can become non-virtual.
+ *
+ * XXX In the long run, this class and its sub-classes should be converted to
+ * use STL classes and mozilla::RefCounted<> so the can be moved to Moz2D.
+ *
+ * TODO: Consider adding a SetDPI method that calls
+ * cairo_surface_set_fallback_resolution.
+ */
+ virtual already_AddRefed<DrawTarget> MakeDrawTarget(
+ const IntSize& aSize, DrawEventRecorder* aRecorder = nullptr);
+
+ /**
+ * Returns a reference DrawTarget. Unlike MakeDrawTarget, this method is not
+ * restricted to being called between BeginPage()/EndPage() calls, and the
+ * returned DrawTarget is still valid to use after EndPage() has been called.
+ */
+ virtual already_AddRefed<DrawTarget> GetReferenceDrawTarget();
+
+ static void AdjustPrintJobNameForIPP(const nsAString& aJobName,
+ nsCString& aAdjustedJobName);
+ static void AdjustPrintJobNameForIPP(const nsAString& aJobName,
+ nsString& aAdjustedJobName);
+
+ protected:
+ // Only created via subclass's constructors
+ explicit PrintTarget(cairo_surface_t* aCairoSurface, const IntSize& aSize);
+
+ // Protected because we're refcounted
+ virtual ~PrintTarget();
+
+ static already_AddRefed<DrawTarget> CreateRecordingDrawTarget(
+ DrawEventRecorder* aRecorder, DrawTarget* aDrawTarget);
+
+ cairo_surface_t* mCairoSurface;
+ RefPtr<DrawTarget> mRefDT; // reference DT
+
+ IntSize mSize;
+ bool mIsFinished;
+#ifdef DEBUG
+ bool mHasActivePage;
+#endif
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_PRINTTARGET_H */
diff --git a/gfx/thebes/PrintTargetCG.h b/gfx/thebes/PrintTargetCG.h
new file mode 100644
index 0000000000..01e75bd36e
--- /dev/null
+++ b/gfx/thebes/PrintTargetCG.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_GFX_PRINTTARGETCG_H
+#define MOZILLA_GFX_PRINTTARGETCG_H
+
+#include <Carbon/Carbon.h>
+#include "PrintTarget.h"
+
+class nsIOutputStream;
+
+namespace mozilla::gfx {
+
+/**
+ * CoreGraphics printing target.
+ */
+class PrintTargetCG final : public PrintTarget {
+ public:
+ static already_AddRefed<PrintTargetCG> CreateOrNull(
+ nsIOutputStream* aOutputStream, PMPrintSession aPrintSession,
+ PMPageFormat aPageFormat, PMPrintSettings aPrintSettings,
+ const IntSize& aSize);
+
+ nsresult BeginPrinting(const nsAString& aTitle,
+ const nsAString& aPrintToFileName, int32_t aStartPage,
+ int32_t aEndPage) final;
+ nsresult EndPrinting() final;
+ nsresult AbortPrinting() final;
+ nsresult BeginPage() final;
+ nsresult EndPage() final;
+
+ already_AddRefed<DrawTarget> GetReferenceDrawTarget() final;
+
+ private:
+ PrintTargetCG(CGContextRef aPrintToStreamContext,
+ PMPrintSession aPrintSession, PMPageFormat aPageFormat,
+ PMPrintSettings aPrintSettings, const IntSize& aSize);
+ ~PrintTargetCG();
+
+ CGContextRef mPrintToStreamContext = nullptr;
+ PMPrintSession mPrintSession;
+ PMPageFormat mPageFormat;
+ PMPrintSettings mPrintSettings;
+};
+
+} // namespace mozilla::gfx
+
+#endif /* MOZILLA_GFX_PRINTTARGETCG_H */
diff --git a/gfx/thebes/PrintTargetCG.mm b/gfx/thebes/PrintTargetCG.mm
new file mode 100644
index 0000000000..a5c7caea55
--- /dev/null
+++ b/gfx/thebes/PrintTargetCG.mm
@@ -0,0 +1,277 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PrintTargetCG.h"
+
+#include "cairo.h"
+#include "cairo-quartz.h"
+#include "mozilla/gfx/HelpersCairo.h"
+#include "nsObjCExceptions.h"
+#include "nsString.h"
+#include "nsIOutputStream.h"
+
+namespace mozilla::gfx {
+
+static size_t PutBytesNull(void* info, const void* buffer, size_t count) { return count; }
+
+PrintTargetCG::PrintTargetCG(CGContextRef aPrintToStreamContext, PMPrintSession aPrintSession,
+ PMPageFormat aPageFormat, PMPrintSettings aPrintSettings,
+ const IntSize& aSize)
+ : PrintTarget(/* aCairoSurface */ nullptr, aSize),
+ mPrintToStreamContext(aPrintToStreamContext),
+ mPrintSession(aPrintSession),
+ mPageFormat(aPageFormat),
+ mPrintSettings(aPrintSettings) {
+ NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
+
+ MOZ_ASSERT(mPrintSession && mPageFormat && mPrintSettings);
+
+ ::PMRetain(mPrintSession);
+ ::PMRetain(mPageFormat);
+ ::PMRetain(mPrintSettings);
+
+ // TODO: Add memory reporting like gfxQuartzSurface.
+ // RecordMemoryUsed(mSize.height * 4 + sizeof(gfxQuartzSurface));
+
+ NS_OBJC_END_TRY_IGNORE_BLOCK;
+}
+
+PrintTargetCG::~PrintTargetCG() {
+ NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
+
+ ::PMRelease(mPrintSession);
+ ::PMRelease(mPageFormat);
+ ::PMRelease(mPrintSettings);
+
+ if (mPrintToStreamContext) {
+ CGContextRelease(mPrintToStreamContext);
+ }
+
+ NS_OBJC_END_TRY_IGNORE_BLOCK;
+}
+
+static size_t WriteStreamBytes(void* aInfo, const void* aBuffer, size_t aCount) {
+ auto* stream = static_cast<nsIOutputStream*>(aInfo);
+ auto* data = static_cast<const char*>(aBuffer);
+ size_t remaining = aCount;
+ do {
+ uint32_t wrote = 0;
+ // Handle potential narrowing from size_t to uint32_t.
+ uint32_t toWrite = uint32_t(std::min(remaining, size_t(std::numeric_limits<uint32_t>::max())));
+ if (NS_WARN_IF(NS_FAILED(stream->Write(data, toWrite, &wrote)))) {
+ break;
+ }
+ data += wrote;
+ remaining -= size_t(wrote);
+ } while (remaining);
+ return aCount;
+}
+
+static void ReleaseStream(void* aInfo) {
+ auto* stream = static_cast<nsIOutputStream*>(aInfo);
+ stream->Close();
+ NS_RELEASE(stream);
+}
+
+static CGContextRef CreatePrintToStreamContext(nsIOutputStream* aOutputStream,
+ const IntSize& aSize) {
+ MOZ_ASSERT(aOutputStream);
+
+ NS_ADDREF(aOutputStream); // Matched by the NS_RELEASE in ReleaseStream.
+
+ CGRect pageBox{{0.0, 0.0}, {CGFloat(aSize.width), CGFloat(aSize.height)}};
+ CGDataConsumerCallbacks callbacks = {WriteStreamBytes, ReleaseStream};
+ CGDataConsumerRef consumer = CGDataConsumerCreate(aOutputStream, &callbacks);
+
+ // This metadata is added by the CorePrinting APIs in the non-stream case.
+ NSString* bundleName =
+ [NSBundle.mainBundle.localizedInfoDictionary objectForKey:(NSString*)kCFBundleNameKey];
+ CFMutableDictionaryRef auxiliaryInfo = CFDictionaryCreateMutable(
+ kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ CFDictionaryAddValue(auxiliaryInfo, kCGPDFContextCreator, (__bridge CFStringRef)bundleName);
+
+ CGContextRef pdfContext = CGPDFContextCreate(consumer, &pageBox, auxiliaryInfo);
+ CGDataConsumerRelease(consumer);
+ CFRelease(auxiliaryInfo);
+ return pdfContext;
+}
+
+/* static */ already_AddRefed<PrintTargetCG> PrintTargetCG::CreateOrNull(
+ nsIOutputStream* aOutputStream, PMPrintSession aPrintSession, PMPageFormat aPageFormat,
+ PMPrintSettings aPrintSettings, const IntSize& aSize) {
+ if (!Factory::CheckSurfaceSize(aSize)) {
+ return nullptr;
+ }
+
+ CGContextRef printToStreamContext = nullptr;
+ if (aOutputStream) {
+ printToStreamContext = CreatePrintToStreamContext(aOutputStream, aSize);
+ if (!printToStreamContext) {
+ return nullptr;
+ }
+ }
+
+ RefPtr<PrintTargetCG> target =
+ new PrintTargetCG(printToStreamContext, aPrintSession, aPageFormat, aPrintSettings, aSize);
+
+ return target.forget();
+}
+
+already_AddRefed<DrawTarget> PrintTargetCG::GetReferenceDrawTarget() {
+ if (!mRefDT) {
+ const IntSize size(1, 1);
+
+ CGDataConsumerCallbacks callbacks = {PutBytesNull, nullptr};
+ CGDataConsumerRef consumer = CGDataConsumerCreate(nullptr, &callbacks);
+ CGContextRef pdfContext = CGPDFContextCreate(consumer, nullptr, nullptr);
+ CGDataConsumerRelease(consumer);
+
+ cairo_surface_t* similar =
+ cairo_quartz_surface_create_for_cg_context(pdfContext, size.width, size.height);
+
+ CGContextRelease(pdfContext);
+
+ if (cairo_surface_status(similar)) {
+ return nullptr;
+ }
+
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForCairoSurface(similar, size);
+
+ // The DT addrefs the surface, so we need drop our own reference to it:
+ cairo_surface_destroy(similar);
+
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+ mRefDT = dt.forget();
+ }
+
+ return do_AddRef(mRefDT);
+}
+
+nsresult PrintTargetCG::BeginPrinting(const nsAString& aTitle, const nsAString& aPrintToFileName,
+ int32_t aStartPage, int32_t aEndPage) {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ if (mPrintToStreamContext) {
+ return NS_OK;
+ }
+
+ // Print Core of Application Service sent print job with names exceeding
+ // 255 bytes. This is a workaround until fix it.
+ // (https://openradar.appspot.com/34428043)
+ nsAutoString adjustedTitle;
+ PrintTarget::AdjustPrintJobNameForIPP(aTitle, adjustedTitle);
+
+ if (!adjustedTitle.IsEmpty()) {
+ CFStringRef cfString = ::CFStringCreateWithCharacters(
+ NULL, reinterpret_cast<const UniChar*>(adjustedTitle.BeginReading()),
+ adjustedTitle.Length());
+ if (cfString) {
+ ::PMPrintSettingsSetJobName(mPrintSettings, cfString);
+ ::CFRelease(cfString);
+ }
+ }
+
+ OSStatus status;
+ status = ::PMSetFirstPage(mPrintSettings, aStartPage, false);
+ NS_ASSERTION(status == noErr, "PMSetFirstPage failed");
+ status = ::PMSetLastPage(mPrintSettings, aEndPage, false);
+ NS_ASSERTION(status == noErr, "PMSetLastPage failed");
+
+ status = ::PMSessionBeginCGDocumentNoDialog(mPrintSession, mPrintSettings, mPageFormat);
+
+ return status == noErr ? NS_OK : NS_ERROR_ABORT;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+nsresult PrintTargetCG::EndPrinting() {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ if (mPrintToStreamContext) {
+ CGContextFlush(mPrintToStreamContext);
+ CGPDFContextClose(mPrintToStreamContext);
+ return NS_OK;
+ }
+
+ ::PMSessionEndDocumentNoDialog(mPrintSession);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+nsresult PrintTargetCG::AbortPrinting() {
+#ifdef DEBUG
+ mHasActivePage = false;
+#endif
+ return EndPrinting();
+}
+
+nsresult PrintTargetCG::BeginPage() {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ CGContextRef context;
+ if (mPrintToStreamContext) {
+ CGContextBeginPage(mPrintToStreamContext, nullptr);
+ context = mPrintToStreamContext;
+ } else {
+ PMSessionError(mPrintSession);
+ OSStatus status = ::PMSessionBeginPageNoDialog(mPrintSession, mPageFormat, nullptr);
+ if (status != noErr) {
+ return NS_ERROR_ABORT;
+ }
+
+ // This call will fail if it wasn't called between the PMSessionBeginPage/
+ // PMSessionEndPage calls:
+ ::PMSessionGetCGGraphicsContext(mPrintSession, &context);
+
+ if (!context) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ unsigned int width = static_cast<unsigned int>(mSize.width);
+ unsigned int height = static_cast<unsigned int>(mSize.height);
+
+ // Initially, origin is at bottom-left corner of the paper.
+ // Here, we translate it to top-left corner of the paper.
+ CGContextTranslateCTM(context, 0, height);
+ CGContextScaleCTM(context, 1.0, -1.0);
+
+ cairo_surface_t* surface = cairo_quartz_surface_create_for_cg_context(context, width, height);
+
+ if (cairo_surface_status(surface)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mCairoSurface = surface;
+
+ return PrintTarget::BeginPage();
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+nsresult PrintTargetCG::EndPage() {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ cairo_surface_finish(mCairoSurface);
+ mCairoSurface = nullptr;
+
+ if (mPrintToStreamContext) {
+ CGContextEndPage(mPrintToStreamContext);
+ } else {
+ OSStatus status = ::PMSessionEndPageNoDialog(mPrintSession);
+ if (status != noErr) {
+ return NS_ERROR_ABORT;
+ }
+ }
+
+ return PrintTarget::EndPage();
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+} // namespace mozilla::gfx
diff --git a/gfx/thebes/PrintTargetPDF.cpp b/gfx/thebes/PrintTargetPDF.cpp
new file mode 100644
index 0000000000..fd60812f5c
--- /dev/null
+++ b/gfx/thebes/PrintTargetPDF.cpp
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PrintTargetPDF.h"
+
+#include "cairo.h"
+#include "cairo-pdf.h"
+#include "mozilla/AppShutdown.h"
+#include "nsContentUtils.h"
+#include "nsString.h"
+
+namespace mozilla::gfx {
+
+static cairo_status_t write_func(void* closure, const unsigned char* data,
+ unsigned int length) {
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ return CAIRO_STATUS_SUCCESS;
+ }
+ nsCOMPtr<nsIOutputStream> out = reinterpret_cast<nsIOutputStream*>(closure);
+ do {
+ uint32_t wrote = 0;
+ if (NS_FAILED(out->Write((const char*)data, length, &wrote))) {
+ break;
+ }
+ data += wrote;
+ length -= wrote;
+ } while (length > 0);
+ NS_ASSERTION(length == 0, "not everything was written to the file");
+ return CAIRO_STATUS_SUCCESS;
+}
+
+PrintTargetPDF::PrintTargetPDF(cairo_surface_t* aCairoSurface,
+ const IntSize& aSize, nsIOutputStream* aStream)
+ : PrintTarget(aCairoSurface, aSize), mStream(aStream) {}
+
+PrintTargetPDF::~PrintTargetPDF() {
+ // We get called first, then PrintTarget's dtor. That means that mStream
+ // is destroyed before PrintTarget's dtor calls cairo_surface_destroy. This
+ // can be a problem if Finish() hasn't been called on us, since
+ // cairo_surface_destroy will then call cairo_surface_finish and that will
+ // end up invoking write_func above with the by now dangling pointer mStream
+ // that mCairoSurface stored. To prevent that from happening we must call
+ // Flush here before mStream is deleted.
+ Finish();
+}
+
+/* static */
+already_AddRefed<PrintTargetPDF> PrintTargetPDF::CreateOrNull(
+ nsIOutputStream* aStream, const IntSize& aSizeInPoints) {
+ if (NS_WARN_IF(!aStream)) {
+ return nullptr;
+ }
+
+ cairo_surface_t* surface = cairo_pdf_surface_create_for_stream(
+ write_func, (void*)aStream, aSizeInPoints.width, aSizeInPoints.height);
+ if (cairo_surface_status(surface)) {
+ return nullptr;
+ }
+
+ nsAutoString creatorName;
+ if (NS_SUCCEEDED(nsContentUtils::GetLocalizedString(
+ nsContentUtils::eBRAND_PROPERTIES, "brandFullName", creatorName)) &&
+ !creatorName.IsEmpty()) {
+ creatorName.Append(u" " MOZILLA_VERSION);
+ cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_CREATOR,
+ NS_ConvertUTF16toUTF8(creatorName).get());
+ }
+
+ // The new object takes ownership of our surface reference.
+ RefPtr<PrintTargetPDF> target =
+ new PrintTargetPDF(surface, aSizeInPoints, aStream);
+ return target.forget();
+}
+
+nsresult PrintTargetPDF::EndPage() {
+ cairo_surface_show_page(mCairoSurface);
+ return PrintTarget::EndPage();
+}
+
+void PrintTargetPDF::Finish() {
+ if (mIsFinished ||
+ AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ // We don't want to call Close() on mStream more than once, and we don't
+ // want to block shutdown if for some reason the user shuts down the
+ // browser mid print.
+ return;
+ }
+ PrintTarget::Finish();
+ mStream->Close();
+}
+
+} // namespace mozilla::gfx
diff --git a/gfx/thebes/PrintTargetPDF.h b/gfx/thebes/PrintTargetPDF.h
new file mode 100644
index 0000000000..489f6b373a
--- /dev/null
+++ b/gfx/thebes/PrintTargetPDF.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_GFX_PRINTTARGETPDF_H
+#define MOZILLA_GFX_PRINTTARGETPDF_H
+
+#include "nsCOMPtr.h"
+#include "nsIOutputStream.h"
+#include "PrintTarget.h"
+
+namespace mozilla {
+namespace gfx {
+
+/**
+ * PDF printing target.
+ */
+class PrintTargetPDF final : public PrintTarget {
+ public:
+ static already_AddRefed<PrintTargetPDF> CreateOrNull(
+ nsIOutputStream* aStream, const IntSize& aSizeInPoints);
+
+ nsresult EndPage() override;
+ void Finish() override;
+
+ private:
+ PrintTargetPDF(cairo_surface_t* aCairoSurface, const IntSize& aSize,
+ nsIOutputStream* aStream);
+ virtual ~PrintTargetPDF();
+
+ nsCOMPtr<nsIOutputStream> mStream;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_PRINTTARGETPDF_H */
diff --git a/gfx/thebes/PrintTargetRecording.cpp b/gfx/thebes/PrintTargetRecording.cpp
new file mode 100644
index 0000000000..4d73995184
--- /dev/null
+++ b/gfx/thebes/PrintTargetRecording.cpp
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PrintTargetRecording.h"
+
+#include "cairo.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Logging.h"
+
+namespace mozilla {
+namespace gfx {
+
+PrintTargetRecording::PrintTargetRecording(cairo_surface_t* aCairoSurface,
+ const IntSize& aSize)
+ : PrintTarget(aCairoSurface, aSize) {}
+
+/* static */
+already_AddRefed<PrintTargetRecording> PrintTargetRecording::CreateOrNull(
+ const IntSize& aSize) {
+ if (!Factory::CheckSurfaceSize(aSize)) {
+ return nullptr;
+ }
+
+ // Perhaps surprisingly, this surface is never actually drawn to. This class
+ // creates a DrawTargetRecording using CreateRecordingDrawTarget, and
+ // that needs another DrawTarget to be passed to it. You might expect the
+ // type of the DrawTarget that is passed to matter because it would seem
+ // logical to encoded its type in the recording, and on replaying the
+ // recording a DrawTarget of the same type would be created. However, the
+ // passed DrawTarget's type doesn't seem to be encoded any more accurately
+ // than just "BackendType::CAIRO". Even if it were, the code that replays the
+ // recording is PrintTranslator::TranslateRecording which (indirectly) calls
+ // MakePrintTarget on the type of nsIDeviceContextSpecProxy that is created
+ // for the platform that we're running on, and the type of DrawTarget that
+ // that returns is hardcoded.
+ //
+ // The only reason that we use cairo_recording_surface_create here is:
+ //
+ // * It's pretty much the only cairo_*_surface_create methods that's both
+ // available on all platforms and doesn't require allocating a
+ // potentially large surface.
+ //
+ // * Since we need a DrawTarget to pass to CreateRecordingDrawTarget we
+ // might as well leverage our base class's machinery to create a
+ // DrawTarget (it's as good a way as any other that will work), and to do
+ // that we need a cairo_surface_t.
+ //
+ // So the fact that this is a "recording" PrintTarget and the function that
+ // we call here is cairo_recording_surface_create is simply a coincidence. We
+ // could use any cairo_*_surface_create method and this class would still
+ // work.
+ //
+ cairo_surface_t* surface =
+ cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, nullptr);
+
+ if (cairo_surface_status(surface)) {
+ return nullptr;
+ }
+
+ // The new object takes ownership of our surface reference.
+ RefPtr<PrintTargetRecording> target =
+ new PrintTargetRecording(surface, aSize);
+
+ return target.forget();
+}
+
+already_AddRefed<DrawTarget> PrintTargetRecording::MakeDrawTarget(
+ const IntSize& aSize, DrawEventRecorder* aRecorder) {
+ MOZ_ASSERT(aRecorder, "A DrawEventRecorder is required");
+
+ if (!aRecorder) {
+ return nullptr;
+ }
+
+ RefPtr<DrawTarget> dt = PrintTarget::MakeDrawTarget(aSize, nullptr);
+ if (dt) {
+ dt = CreateRecordingDrawTarget(aRecorder, dt);
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+ }
+
+ return dt.forget();
+}
+
+already_AddRefed<DrawTarget> PrintTargetRecording::CreateRecordingDrawTarget(
+ DrawEventRecorder* aRecorder, DrawTarget* aDrawTarget) {
+ MOZ_ASSERT(aRecorder);
+ MOZ_ASSERT(aDrawTarget);
+
+ RefPtr<DrawTarget> dt;
+
+ if (aRecorder) {
+ // It doesn't really matter what we pass as the DrawTarget here.
+ dt = gfx::Factory::CreateRecordingDrawTarget(aRecorder, aDrawTarget,
+ aDrawTarget->GetRect());
+ }
+
+ if (!dt || !dt->IsValid()) {
+ gfxCriticalNote
+ << "Failed to create a recording DrawTarget for PrintTarget";
+ return nullptr;
+ }
+
+ return dt.forget();
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/thebes/PrintTargetRecording.h b/gfx/thebes/PrintTargetRecording.h
new file mode 100644
index 0000000000..dccf35e615
--- /dev/null
+++ b/gfx/thebes/PrintTargetRecording.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_GFX_PRINTTARGETRECORDING_H
+#define MOZILLA_GFX_PRINTTARGETRECORDING_H
+
+#include "PrintTarget.h"
+
+namespace mozilla {
+namespace gfx {
+
+/**
+ * Recording printing target.
+ *
+ * This exists for use on e10s's content process in order to record print
+ * output, send it over to the parent process, and replay it on a DrawTarget
+ * there for printing.
+ */
+class PrintTargetRecording final : public PrintTarget {
+ public:
+ static already_AddRefed<PrintTargetRecording> CreateOrNull(
+ const IntSize& aSize);
+
+ already_AddRefed<DrawTarget> MakeDrawTarget(
+ const IntSize& aSize, DrawEventRecorder* aRecorder = nullptr) override;
+
+ private:
+ PrintTargetRecording(cairo_surface_t* aCairoSurface, const IntSize& aSize);
+
+ already_AddRefed<DrawTarget> CreateWrapAndRecordDrawTarget(
+ DrawEventRecorder* aRecorder, DrawTarget* aDrawTarget);
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_PRINTTARGETRECORDING_H */
diff --git a/gfx/thebes/PrintTargetSkPDF.cpp b/gfx/thebes/PrintTargetSkPDF.cpp
new file mode 100644
index 0000000000..6cf7d3729e
--- /dev/null
+++ b/gfx/thebes/PrintTargetSkPDF.cpp
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PrintTargetSkPDF.h"
+
+#include "mozilla/gfx/2D.h"
+#include "nsString.h"
+#include <vector>
+
+namespace mozilla::gfx {
+
+PrintTargetSkPDF::PrintTargetSkPDF(const IntSize& aSize,
+ UniquePtr<SkWStream> aStream)
+ : PrintTarget(/* not using cairo_surface_t */ nullptr, aSize),
+ mOStream(std::move(aStream)),
+ mPageCanvas(nullptr),
+ mRefCanvas(nullptr) {}
+
+PrintTargetSkPDF::~PrintTargetSkPDF() {
+ Finish(); // ensure stream is flushed
+
+ // Make sure mPDFDoc and mRefPDFDoc are destroyed before our member streams
+ // (which they wrap) are destroyed:
+ mPDFDoc = nullptr;
+ mRefPDFDoc = nullptr;
+}
+
+/* static */
+already_AddRefed<PrintTargetSkPDF> PrintTargetSkPDF::CreateOrNull(
+ UniquePtr<SkWStream> aStream, const IntSize& aSizeInPoints) {
+ return do_AddRef(new PrintTargetSkPDF(aSizeInPoints, std::move(aStream)));
+}
+
+nsresult PrintTargetSkPDF::BeginPrinting(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage, int32_t aEndPage) {
+ // We need to create the SkPDFDocument here rather than in CreateOrNull
+ // because it's only now that we are given aTitle which we want for the
+ // PDF metadata.
+
+ SkPDF::Metadata metadata;
+ metadata.fTitle = NS_ConvertUTF16toUTF8(aTitle).get();
+ metadata.fCreator = "Firefox";
+ SkTime::DateTime now;
+ SkTime::GetDateTime(&now);
+ metadata.fCreation = now;
+ metadata.fModified = now;
+
+ // SkDocument stores a non-owning raw pointer to aStream
+ mPDFDoc = SkPDF::MakeDocument(mOStream.get(), metadata);
+
+ return mPDFDoc ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult PrintTargetSkPDF::BeginPage() {
+ mPageCanvas = mPDFDoc->beginPage(mSize.width, mSize.height);
+
+ return !mPageCanvas ? NS_ERROR_FAILURE : PrintTarget::BeginPage();
+}
+
+nsresult PrintTargetSkPDF::EndPage() {
+ mPageCanvas = nullptr;
+ mPageDT = nullptr;
+ return PrintTarget::EndPage();
+}
+
+nsresult PrintTargetSkPDF::EndPrinting() {
+ mPDFDoc->close();
+ if (mRefPDFDoc) {
+ mRefPDFDoc->close();
+ }
+ mPageCanvas = nullptr;
+ mPageDT = nullptr;
+ return NS_OK;
+}
+
+void PrintTargetSkPDF::Finish() {
+ if (mIsFinished) {
+ return;
+ }
+ mOStream->flush();
+ PrintTarget::Finish();
+}
+
+already_AddRefed<DrawTarget> PrintTargetSkPDF::MakeDrawTarget(
+ const IntSize& aSize, DrawEventRecorder* aRecorder) {
+ if (aRecorder) {
+ return PrintTarget::MakeDrawTarget(aSize, aRecorder);
+ }
+ // MOZ_ASSERT(aSize == mSize, "Should mPageCanvas size match?");
+ if (!mPageCanvas) {
+ return nullptr;
+ }
+ mPageDT = Factory::CreateDrawTargetWithSkCanvas(mPageCanvas);
+ if (!mPageDT) {
+ mPageCanvas = nullptr;
+ return nullptr;
+ }
+ return do_AddRef(mPageDT);
+}
+
+already_AddRefed<DrawTarget> PrintTargetSkPDF::GetReferenceDrawTarget() {
+ if (!mRefDT) {
+ SkPDF::Metadata metadata;
+ // SkDocument stores a non-owning raw pointer to aStream
+ mRefPDFDoc = SkPDF::MakeDocument(&mRefOStream, metadata);
+ if (!mRefPDFDoc) {
+ return nullptr;
+ }
+ mRefCanvas = mRefPDFDoc->beginPage(mSize.width, mSize.height);
+ if (!mRefCanvas) {
+ return nullptr;
+ }
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTargetWithSkCanvas(mRefCanvas);
+ if (!dt) {
+ return nullptr;
+ }
+ mRefDT = std::move(dt);
+ }
+
+ return do_AddRef(mRefDT);
+}
+
+} // namespace mozilla::gfx
diff --git a/gfx/thebes/PrintTargetSkPDF.h b/gfx/thebes/PrintTargetSkPDF.h
new file mode 100644
index 0000000000..9cbe9e4ab3
--- /dev/null
+++ b/gfx/thebes/PrintTargetSkPDF.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_GFX_PRINTTARGETSKPDF_H
+#define MOZILLA_GFX_PRINTTARGETSKPDF_H
+
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "PrintTarget.h"
+#include "skia/include/core/SkCanvas.h"
+#include "skia/include/docs/SkPDFDocument.h"
+#include "skia/include/core/SkStream.h"
+
+namespace mozilla {
+namespace gfx {
+
+/**
+ * Skia PDF printing target.
+ */
+class PrintTargetSkPDF final : public PrintTarget {
+ public:
+ // The returned PrintTargetSkPDF keeps a raw pointer to the passed SkWStream
+ // but does not own it. Callers are responsible for ensuring that passed
+ // stream outlives the returned PrintTarget.
+ static already_AddRefed<PrintTargetSkPDF> CreateOrNull(
+ UniquePtr<SkWStream> aStream, const IntSize& aSizeInPoints);
+
+ nsresult BeginPrinting(const nsAString& aTitle,
+ const nsAString& aPrintToFileName, int32_t aStartPage,
+ int32_t aEndPage) override;
+ nsresult EndPrinting() override;
+ void Finish() override;
+
+ nsresult BeginPage() override;
+ nsresult EndPage() override;
+
+ already_AddRefed<DrawTarget> MakeDrawTarget(
+ const IntSize& aSize, DrawEventRecorder* aRecorder = nullptr) final;
+
+ already_AddRefed<DrawTarget> GetReferenceDrawTarget() final;
+
+ private:
+ PrintTargetSkPDF(const IntSize& aSize, UniquePtr<SkWStream> aStream);
+ virtual ~PrintTargetSkPDF();
+
+ // Do not hand out references to this object. It holds a non-owning
+ // reference to mOStreame, so must not outlive mOStream.
+ sk_sp<SkDocument> mPDFDoc;
+
+ // The stream that the SkDocument outputs to.
+ UniquePtr<SkWStream> mOStream;
+
+ // The current page's SkCanvas and its wrapping DrawTarget:
+ // Canvas is owned by mPDFDoc, which handles its deletion.
+ SkCanvas* mPageCanvas;
+ RefPtr<DrawTarget> mPageDT;
+
+ // Members needed to provide a reference DrawTarget:
+ sk_sp<SkDocument> mRefPDFDoc;
+ // Canvas owned by mRefPDFDoc, which handles its deletion.
+ SkCanvas* mRefCanvas;
+ SkDynamicMemoryWStream mRefOStream;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_PRINTTARGETSKPDF_H */
diff --git a/gfx/thebes/PrintTargetThebes.cpp b/gfx/thebes/PrintTargetThebes.cpp
new file mode 100644
index 0000000000..e121b0a7e2
--- /dev/null
+++ b/gfx/thebes/PrintTargetThebes.cpp
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PrintTargetThebes.h"
+
+#include "gfxASurface.h"
+#include "gfxPlatform.h"
+#include "mozilla/gfx/Logging.h"
+
+namespace mozilla::gfx {
+
+/* static */
+already_AddRefed<PrintTargetThebes> PrintTargetThebes::CreateOrNull(
+ gfxASurface* aSurface) {
+ MOZ_ASSERT(aSurface);
+
+ if (!aSurface || aSurface->CairoStatus()) {
+ return nullptr;
+ }
+
+ RefPtr<PrintTargetThebes> target = new PrintTargetThebes(aSurface);
+
+ return target.forget();
+}
+
+PrintTargetThebes::PrintTargetThebes(gfxASurface* aSurface)
+ : PrintTarget(nullptr, aSurface->GetSize()), mGfxSurface(aSurface) {}
+
+already_AddRefed<DrawTarget> PrintTargetThebes::MakeDrawTarget(
+ const IntSize& aSize, DrawEventRecorder* aRecorder) {
+ // This should not be called outside of BeginPage()/EndPage() calls since
+ // some backends can only provide a valid DrawTarget at that time.
+ MOZ_ASSERT(mHasActivePage, "We can't guarantee a valid DrawTarget");
+
+ RefPtr<gfx::DrawTarget> dt =
+ gfxPlatform::CreateDrawTargetForSurface(mGfxSurface, aSize);
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+
+ if (aRecorder) {
+ dt = CreateRecordingDrawTarget(aRecorder, dt);
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+ }
+
+ return dt.forget();
+}
+
+already_AddRefed<DrawTarget> PrintTargetThebes::GetReferenceDrawTarget() {
+ if (!mRefDT) {
+ RefPtr<gfx::DrawTarget> dt =
+ gfxPlatform::CreateDrawTargetForSurface(mGfxSurface, mSize);
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+ mRefDT = dt->CreateSimilarDrawTarget(IntSize(1, 1), dt->GetFormat());
+ }
+
+ return do_AddRef(mRefDT);
+}
+
+nsresult PrintTargetThebes::BeginPrinting(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage,
+ int32_t aEndPage) {
+ return mGfxSurface->BeginPrinting(aTitle, aPrintToFileName);
+}
+
+nsresult PrintTargetThebes::EndPrinting() { return mGfxSurface->EndPrinting(); }
+
+nsresult PrintTargetThebes::AbortPrinting() {
+#ifdef DEBUG
+ mHasActivePage = false;
+#endif
+ return mGfxSurface->AbortPrinting();
+}
+
+nsresult PrintTargetThebes::BeginPage() {
+#ifdef DEBUG
+ mHasActivePage = true;
+#endif
+ return mGfxSurface->BeginPage();
+}
+
+nsresult PrintTargetThebes::EndPage() {
+#ifdef DEBUG
+ mHasActivePage = false;
+#endif
+ return mGfxSurface->EndPage();
+}
+
+void PrintTargetThebes::Finish() { return mGfxSurface->Finish(); }
+
+} // namespace mozilla::gfx
diff --git a/gfx/thebes/PrintTargetThebes.h b/gfx/thebes/PrintTargetThebes.h
new file mode 100644
index 0000000000..1a3b72365d
--- /dev/null
+++ b/gfx/thebes/PrintTargetThebes.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_GFX_PRINTTARGETTHEBES_H
+#define MOZILLA_GFX_PRINTTARGETTHEBES_H
+
+#include "mozilla/gfx/PrintTarget.h"
+
+class gfxASurface;
+
+namespace mozilla {
+namespace gfx {
+
+/**
+ * XXX Remove this class.
+ *
+ * This class should go away once all the logic from the gfxASurface subclasses
+ * has been moved to new PrintTarget subclasses and we no longer need to
+ * wrap a gfxASurface.
+ *
+ * When removing this class, be sure to make PrintTarget::MakeDrawTarget
+ * non-virtual!
+ */
+class PrintTargetThebes final : public PrintTarget {
+ public:
+ static already_AddRefed<PrintTargetThebes> CreateOrNull(
+ gfxASurface* aSurface);
+
+ nsresult BeginPrinting(const nsAString& aTitle,
+ const nsAString& aPrintToFileName, int32_t aStartPage,
+ int32_t aEndPage) override;
+ nsresult EndPrinting() override;
+ nsresult AbortPrinting() override;
+ nsresult BeginPage() override;
+ nsresult EndPage() override;
+ void Finish() override;
+
+ already_AddRefed<DrawTarget> MakeDrawTarget(
+ const IntSize& aSize, DrawEventRecorder* aRecorder = nullptr) override;
+
+ already_AddRefed<DrawTarget> GetReferenceDrawTarget() final;
+
+ private:
+ // Only created via CreateOrNull
+ explicit PrintTargetThebes(gfxASurface* aSurface);
+
+ RefPtr<gfxASurface> mGfxSurface;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_PRINTTARGETTHEBES_H */
diff --git a/gfx/thebes/PrintTargetWindows.cpp b/gfx/thebes/PrintTargetWindows.cpp
new file mode 100644
index 0000000000..e97a1187e5
--- /dev/null
+++ b/gfx/thebes/PrintTargetWindows.cpp
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PrintTargetWindows.h"
+
+#include "cairo-win32.h"
+#include "mozilla/gfx/HelpersCairo.h"
+#include "nsCoord.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace gfx {
+
+PrintTargetWindows::PrintTargetWindows(cairo_surface_t* aCairoSurface,
+ const IntSize& aSize, HDC aDC)
+ : PrintTarget(aCairoSurface, aSize), mDC(aDC) {
+ // TODO: At least add basic memory reporting.
+ // 4 * mSize.width * mSize.height + sizeof(PrintTargetWindows) ?
+}
+
+/* static */
+already_AddRefed<PrintTargetWindows> PrintTargetWindows::CreateOrNull(HDC aDC) {
+ // Figure out the paper size, the actual surface size will be the printable
+ // area which is likely smaller, but the size here is later used to create the
+ // draw target where the full page size is needed.
+ // Note: we only scale the printing using the LOGPIXELSY,
+ // so we use that when calculating the surface width as well as the height.
+ int32_t heightDPI = ::GetDeviceCaps(aDC, LOGPIXELSY);
+ float width =
+ (::GetDeviceCaps(aDC, PHYSICALWIDTH) * POINTS_PER_INCH_FLOAT) / heightDPI;
+ float height =
+ (::GetDeviceCaps(aDC, PHYSICALHEIGHT) * POINTS_PER_INCH_FLOAT) /
+ heightDPI;
+ IntSize size = IntSize::Truncate(width, height);
+
+ if (!Factory::CheckSurfaceSize(size)) {
+ return nullptr;
+ }
+
+ cairo_surface_t* surface = cairo_win32_printing_surface_create(aDC);
+
+ if (cairo_surface_status(surface)) {
+ return nullptr;
+ }
+
+ // The new object takes ownership of our surface reference.
+ RefPtr<PrintTargetWindows> target =
+ new PrintTargetWindows(surface, size, aDC);
+
+ return target.forget();
+}
+
+nsresult PrintTargetWindows::BeginPrinting(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage,
+ int32_t aEndPage) {
+ const uint32_t DOC_TITLE_LENGTH = MAX_PATH - 1;
+
+ DOCINFOW docinfo;
+
+ nsString titleStr(aTitle);
+ if (titleStr.Length() > DOC_TITLE_LENGTH) {
+ titleStr.SetLength(DOC_TITLE_LENGTH - 3);
+ titleStr.AppendLiteral("...");
+ }
+
+ nsString docName(aPrintToFileName);
+ docinfo.cbSize = sizeof(docinfo);
+ docinfo.lpszDocName =
+ titleStr.Length() > 0 ? titleStr.get() : L"Mozilla Document";
+ docinfo.lpszOutput = docName.Length() > 0 ? docName.get() : nullptr;
+ docinfo.lpszDatatype = nullptr;
+ docinfo.fwType = 0;
+
+ // If the user selected Microsoft Print to PDF or XPS Document Printer, then
+ // the following StartDoc call will put up a dialog window to prompt the
+ // user to provide the name and location of the file to be saved. A zero or
+ // negative return value indicates failure. In that case we want to check
+ // whether that is because the user hit Cancel, since we want to treat that
+ // specially to avoid notifying the user that the print "failed" in that
+ // case.
+ // XXX We should perhaps introduce a new NS_ERROR_USER_CANCELLED errer.
+ int result = ::StartDocW(mDC, &docinfo);
+ if (result <= 0) {
+ if (::GetLastError() == ERROR_CANCELLED) {
+ return NS_ERROR_ABORT;
+ }
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult PrintTargetWindows::EndPrinting() {
+ int result = ::EndDoc(mDC);
+ return (result <= 0) ? NS_ERROR_FAILURE : NS_OK;
+}
+
+nsresult PrintTargetWindows::AbortPrinting() {
+ PrintTarget::AbortPrinting();
+ int result = ::AbortDoc(mDC);
+ return (result <= 0) ? NS_ERROR_FAILURE : NS_OK;
+}
+
+nsresult PrintTargetWindows::BeginPage() {
+ PrintTarget::BeginPage();
+ int result = ::StartPage(mDC);
+ return (result <= 0) ? NS_ERROR_FAILURE : NS_OK;
+}
+
+nsresult PrintTargetWindows::EndPage() {
+ cairo_surface_show_page(mCairoSurface);
+ PrintTarget::EndPage();
+ int result = ::EndPage(mDC);
+ return (result <= 0) ? NS_ERROR_FAILURE : NS_OK;
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/thebes/PrintTargetWindows.h b/gfx/thebes/PrintTargetWindows.h
new file mode 100644
index 0000000000..4080835e68
--- /dev/null
+++ b/gfx/thebes/PrintTargetWindows.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_GFX_PRINTTARGETWINDOWS_H
+#define MOZILLA_GFX_PRINTTARGETWINDOWS_H
+
+#include "PrintTarget.h"
+
+/* include windows.h for the HDC definitions that we need. */
+#include <windows.h>
+
+namespace mozilla {
+namespace gfx {
+
+/**
+ * Windows printing target.
+ */
+class PrintTargetWindows final : public PrintTarget {
+ public:
+ static already_AddRefed<PrintTargetWindows> CreateOrNull(HDC aDC);
+
+ nsresult BeginPrinting(const nsAString& aTitle,
+ const nsAString& aPrintToFileName, int32_t aStartPage,
+ int32_t aEndPage) override;
+ nsresult EndPrinting() override;
+ nsresult AbortPrinting() override;
+ nsresult BeginPage() override;
+ nsresult EndPage() override;
+
+ private:
+ PrintTargetWindows(cairo_surface_t* aCairoSurface, const IntSize& aSize,
+ HDC aDC);
+ HDC mDC;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_PRINTTARGETWINDOWS_H */
diff --git a/gfx/thebes/SharedFontList-impl.h b/gfx/thebes/SharedFontList-impl.h
new file mode 100644
index 0000000000..e39e4dd47d
--- /dev/null
+++ b/gfx/thebes/SharedFontList-impl.h
@@ -0,0 +1,386 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SharedFontList_impl_h
+#define SharedFontList_impl_h
+
+#include "SharedFontList.h"
+
+#include "base/shared_memory.h"
+
+#include "gfxFontUtils.h"
+#include "nsClassHashtable.h"
+#include "nsTHashMap.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/UniquePtr.h"
+
+// This is split out from SharedFontList.h because that header is included
+// quite widely (via gfxPlatformFontList.h, gfxTextRun.h, etc), and other code
+// such as the generated DOM bindings code gets upset at (indirect) inclusion
+// of <windows.h> via SharedMemoryBasic.h. So this header, which defines the
+// actual shared-memory FontList class, is included only by the .cpp files that
+// implement or directly interface with the font list, to avoid polluting other
+// headers.
+
+namespace mozilla {
+namespace fontlist {
+
+/**
+ * Data used to initialize a font family alias (a "virtual" family that refers
+ * to some or all of the faces of another family, used when alternate family
+ * names are found in the font resource for localization or for styled
+ * subfamilies). AliasData records are collected incrementally while scanning
+ * the fonts, and then used to set up the Aliases list in the shared font list.
+ */
+struct AliasData {
+ nsTArray<Pointer> mFaces;
+ nsCString mBaseFamily;
+ uint32_t mIndex = 0;
+ FontVisibility mVisibility = FontVisibility::Unknown;
+ bool mBundled = false;
+ bool mBadUnderline = false;
+ bool mForceClassic = false;
+
+ void InitFromFamily(const Family* aFamily, const nsCString& aBaseFamily) {
+ mBaseFamily = aBaseFamily;
+ mIndex = aFamily->Index();
+ mVisibility = aFamily->Visibility();
+ mBundled = aFamily->IsBundled();
+ mBadUnderline = aFamily->IsBadUnderlineFamily();
+ mForceClassic = aFamily->IsForceClassic();
+ }
+};
+
+/**
+ * The Shared Font List is a collection of data that lives in shared memory
+ * so that all processes can use it, rather than maintaining their own copies,
+ * and provides the metadata needed for CSS font-matching (a list of all the
+ * available font families and their faces, style properties, etc, as well as
+ * character coverage).
+ *
+ * An important assumption is that all processes see the same collection of
+ * installed fonts; therefore it is valid for them all to share the same set
+ * of font metadata. The data is updated only by the parent process; content
+ * processes have read-only access to it.
+ *
+ * The total size of this data varies greatly depending on the user's installed
+ * fonts; and it is not known at startup because we load a lot of the font data
+ * on first use rather than preloading during initialization (because that's
+ * too expensive/slow).
+ *
+ * Therefore, the shared memory area needs to be able to grow during the
+ * session; we can't predict how much space will be needed, and we can't afford
+ * to pre-allocate such a huge block that it could never overflow. To handle
+ * this, we maintain a (generally short) array of blocks of shared memory,
+ * and then allocate our Family, Face, etc. objects within these. Because we
+ * only ever add data (never delete what we've already stored), we can use a
+ * simplified allocator that doesn't ever need to free blocks; the only time
+ * the memory is released during a session is the (rare) case where a font is
+ * installed or deleted while the browser is running, and in this case we just
+ * delete the entire shared font list and start afresh.
+ */
+class FontList {
+ public:
+ friend struct Pointer;
+
+ explicit FontList(uint32_t aGeneration);
+ ~FontList();
+
+ /**
+ * Initialize the master list of installed font families. This must be
+ * set during font-list creation, before the list is shared with any
+ * content processes. All installed font families known to the browser
+ * appear in this list, although some may be marked as "hidden" so that
+ * they are not exposed to the font-family property.
+ *
+ * The passed-in array may be modified (to eliminate duplicates of bundled
+ * fonts, or restrict the available list to a specified subset), so if the
+ * caller intends to make further use of it this should be kept in mind.
+ *
+ * Once initialized, the master family list is immutable; in the (rare)
+ * event that the system's collection of installed fonts changes, we discard
+ * the FontList and create a new one.
+ *
+ * In some cases, a font family may be known by multiple names (e.g.
+ * localizations in multiple languages, or there may be legacy family names
+ * that correspond to specific styled faces like "Arial Black"). Such names
+ * do not appear in this master list, but are referred to as aliases (see
+ * SetAliases below); the alias list need not be populated before the font
+ * list is shared to content processes and used.
+ *
+ * Only used in the parent process.
+ */
+ void SetFamilyNames(nsTArray<Family::InitData>& aFamilies);
+
+ /**
+ * Aliases are Family records whose Face entries are already part of another
+ * family (either because the family has multiple localized names, or because
+ * the alias family is a legacy name like "Arial Narrow" that is a subset of
+ * the faces in the main "Arial" family). The table of aliases is initialized
+ * from a hash of alias family name -> array of Face records.
+ *
+ * Like the master family list, the list of family aliases is immutable once
+ * initialized.
+ *
+ * Only used in the parent process.
+ */
+ void SetAliases(nsClassHashtable<nsCStringHashKey, AliasData>& aAliasTable);
+
+ /**
+ * Local names are PostScript or Full font names of individual faces, used
+ * to look up faces for @font-face { src: local(...) } rules. Some platforms
+ * (e.g. macOS) can look up local names directly using platform font APIs,
+ * in which case the local names table here is unused.
+ *
+ * The list of local names is immutable once initialized. Local font name
+ * lookups may occur before this list has been set up, in which case they
+ * will use the SearchForLocalFace method.
+ *
+ * Only used in the parent process.
+ */
+ void SetLocalNames(
+ nsTHashMap<nsCStringHashKey, LocalFaceRec::InitData>& aLocalNameTable);
+
+ /**
+ * Look up a Family record by name, typically to satisfy the font-family
+ * property or a font family listed in preferences.
+ */
+ Family* FindFamily(const nsCString& aName, bool aPrimaryNameOnly = false);
+
+ /**
+ * Look up an individual Face by PostScript or Full name, for @font-face
+ * rules using src:local(...). This requires the local names list to have
+ * been initialized.
+ */
+ LocalFaceRec* FindLocalFace(const nsCString& aName);
+
+ /**
+ * Search families for a face with local name aName; should only be used if
+ * the mLocalFaces array has not yet been set up, as this will be a more
+ * expensive search than FindLocalFace.
+ */
+ void SearchForLocalFace(const nsACString& aName, Family** aFamily,
+ Face** aFace);
+
+ /**
+ * Return the localized name for the given family in the current system
+ * locale (if multiple localizations are available).
+ */
+ nsCString LocalizedFamilyName(const Family* aFamily);
+
+ bool Initialized() { return mBlocks.Length() > 0 && NumFamilies() > 0; }
+
+ uint32_t NumFamilies() { return GetHeader().mFamilyCount; }
+ Family* Families() {
+ return GetHeader().mFamilies.ToArray<Family>(this, NumFamilies());
+ }
+
+ uint32_t NumAliases() { return GetHeader().mAliasCount; }
+ Family* AliasFamilies() {
+ return GetHeader().mAliases.ToArray<Family>(this, NumAliases());
+ }
+
+ uint32_t NumLocalFaces() { return GetHeader().mLocalFaceCount; }
+ LocalFaceRec* LocalFaces() {
+ return GetHeader().mLocalFaces.ToArray<LocalFaceRec>(this, NumLocalFaces());
+ }
+
+ /**
+ * Ask the font list to initialize the character map for a given face.
+ */
+ void LoadCharMapFor(Face& aFace, const Family* aFamily);
+
+ /**
+ * Allocate shared-memory space for a record of aSize bytes. The returned
+ * pointer will be 32-bit aligned. (This method may trigger the allocation of
+ * a new shared memory block, if required.)
+ *
+ * Only used in the parent process.
+ */
+ Pointer Alloc(uint32_t aSize);
+
+ /**
+ * Convert a native pointer to a shared-memory Pointer record that can be
+ * passed between processes.
+ */
+ Pointer ToSharedPointer(const void* aPtr);
+
+ uint32_t GetGeneration() { return GetHeader().mGeneration; }
+
+ /**
+ * Header fields present in every shared-memory block. The mBlockSize field
+ * is not modified after initial block creation (before the block has been
+ * shared to any other process), so does not need to be std::atomic<>.
+ * The mAllocated field is checked during Pointer::ToPtr(), so we make that
+ * atomic to avoid data races.
+ */
+ struct BlockHeader {
+ std::atomic<uint32_t> mAllocated; // Space allocated from this block.
+ uint32_t mBlockSize; // Total size of this block.
+ };
+
+ /**
+ * Header info that is stored at the beginning of the first shared-memory
+ * block for the font list.
+ * (Subsequent blocks have only the mBlockHeader.)
+ * The mGeneration and mFamilyCount fields are set by the parent process
+ * during font-list construction, before the list has been shared with any
+ * other process, and subsequently never change; therefore, we don't need
+ * to use std::atomic<> for these.
+ */
+ struct Header {
+ BlockHeader mBlockHeader;
+ uint32_t mGeneration; // Font-list generation ID
+ uint32_t mFamilyCount; // Number of font families in the list
+ std::atomic<uint32_t> mBlockCount; // Total number of blocks that exist
+ std::atomic<uint32_t> mAliasCount; // Number of family aliases
+ std::atomic<uint32_t> mLocalFaceCount; // Number of local face names
+ Pointer mFamilies; // Pointer to array of |mFamilyCount| families
+ Pointer mAliases; // Pointer to array of |mAliasCount| aliases
+ Pointer mLocalFaces; // Pointer to array of |mLocalFaceCount| face records
+ };
+
+ /**
+ * Used by the parent process to pass a handle to a shared block to a
+ * specific child process. This is used when a child process requests
+ * an additional block that was not already passed to it (because the
+ * list has changed/grown since the child was first initialized).
+ */
+ void ShareShmBlockToProcess(uint32_t aIndex, base::ProcessId aPid,
+ base::SharedMemoryHandle* aOut) {
+ MOZ_RELEASE_ASSERT(mReadOnlyShmems.Length() == mBlocks.Length());
+ if (aIndex >= mReadOnlyShmems.Length()) {
+ // Block index out of range
+ *aOut = base::SharedMemory::NULLHandle();
+ return;
+ }
+ *aOut = mReadOnlyShmems[aIndex]->CloneHandle();
+ if (!*aOut) {
+ MOZ_CRASH("failed to share block");
+ }
+ }
+
+ /**
+ * Collect an array of handles to all the shmem blocks, ready to be
+ * shared to the given process. This is used at child process startup
+ * to pass the complete list at once.
+ */
+ void ShareBlocksToProcess(nsTArray<base::SharedMemoryHandle>* aBlocks,
+ base::ProcessId aPid);
+
+ base::SharedMemoryHandle ShareBlockToProcess(uint32_t aIndex,
+ base::ProcessId aPid);
+
+ void ShmBlockAdded(uint32_t aGeneration, uint32_t aIndex,
+ base::SharedMemoryHandle aHandle);
+ /**
+ * Support for memory reporter.
+ */
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ size_t AllocatedShmemSize() const;
+
+ /**
+ * Using a larger block size will speed up allocation, at the cost of more
+ * wasted space in the shared memory (on average).
+ */
+#if ANDROID
+ // Android devices usually have a much smaller number of fonts than desktop
+ // systems, and memory is more constrained, so use a smaller default block
+ // size.
+ static constexpr uint32_t SHM_BLOCK_SIZE = 64 * 1024;
+#elif XP_LINUX
+ // On Linux, font face descriptors are rather large (serialized FcPatterns),
+ // so use a larger block size for efficiency.
+ static constexpr uint32_t SHM_BLOCK_SIZE = 1024 * 1024;
+#else
+ // Default block size for Windows and macOS.
+ static constexpr uint32_t SHM_BLOCK_SIZE = 256 * 1024;
+#endif
+ static_assert(SHM_BLOCK_SIZE <= (1 << Pointer::kBlockShift),
+ "SHM_BLOCK_SIZE too large");
+
+ private:
+ struct ShmBlock {
+ // Takes ownership of aShmem. Note that in a child process, aShmem will be
+ // mapped as read-only.
+ explicit ShmBlock(mozilla::UniquePtr<base::SharedMemory>&& aShmem)
+ : mShmem(std::move(aShmem)) {}
+
+ // Get pointer to the mapped memory.
+ void* Memory() const { return mShmem->memory(); }
+
+ // Only the parent process does allocation, so only it will update this
+ // field. Content processes read the value when checking Pointer validity.
+ uint32_t Allocated() const {
+ return static_cast<BlockHeader*>(Memory())->mAllocated;
+ }
+
+ void StoreAllocated(uint32_t aSize) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ static_cast<BlockHeader*>(Memory())->mAllocated.store(aSize);
+ }
+
+ // This is stored by the parent process during block creation and never
+ // changes, so does not need to be atomic.
+ // Note that some blocks may be larger than SHM_BLOCK_SIZE, if needed for
+ // individual large allocations.
+ uint32_t& BlockSize() const {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ return static_cast<BlockHeader*>(Memory())->mBlockSize;
+ }
+
+ mozilla::UniquePtr<base::SharedMemory> mShmem;
+ };
+
+ Header& GetHeader() const;
+
+ /**
+ * Create a new shared memory block and append to the FontList's list
+ * of blocks.
+ *
+ * Only used in the parent process.
+ */
+ bool AppendShmBlock(uint32_t aSizeNeeded);
+
+ /**
+ * Used by child processes to ensure all the blocks are registered.
+ * Returns false on failure.
+ * Pass aMustLock=true to take the gfxPlatformFontList lock during the
+ * update (not required when calling from the constructor).
+ */
+ [[nodiscard]] bool UpdateShmBlocks(bool aMustLock);
+
+ /**
+ * This makes a *sync* IPC call to get a shared block from the parent.
+ * As such, it may block for a while if the parent is busy; fortunately,
+ * we'll generally only call this a handful of times in the course of an
+ * entire session. If the requested block does not yet exist (because the
+ * child is wanting to allocate an object, and there wasn't room in any
+ * existing block), the parent will create a new shared block and return it.
+ * This may (in rare cases) return null, if the parent has recreated the
+ * font list and we actually need to reinitialize.
+ */
+ ShmBlock* GetBlockFromParent(uint32_t aIndex);
+
+ void DetachShmBlocks();
+
+ /**
+ * Array of pointers to the shared-memory block records.
+ * NOTE: if mBlocks.Length() < GetHeader().mBlockCount, then the parent has
+ * added a block (or blocks) to the list, and we need to update!
+ */
+ nsTArray<mozilla::UniquePtr<ShmBlock>> mBlocks;
+
+ /**
+ * Auxiliary array, used only in the parent process; holds read-only copies
+ * of the shmem blocks; these are what will be shared to child processes.
+ */
+ nsTArray<mozilla::UniquePtr<base::SharedMemory>> mReadOnlyShmems;
+};
+
+} // namespace fontlist
+} // namespace mozilla
+
+#endif /* SharedFontList_impl_h */
diff --git a/gfx/thebes/SharedFontList.cpp b/gfx/thebes/SharedFontList.cpp
new file mode 100644
index 0000000000..e9f64089be
--- /dev/null
+++ b/gfx/thebes/SharedFontList.cpp
@@ -0,0 +1,1361 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SharedFontList-impl.h"
+#include "gfxPlatformFontList.h"
+#include "gfxFontUtils.h"
+#include "gfxFont.h"
+#include "nsReadableUtils.h"
+#include "prerror.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Unused.h"
+
+#define LOG_FONTLIST(args) \
+ MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug, args)
+#define LOG_FONTLIST_ENABLED() \
+ MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug)
+
+namespace mozilla {
+namespace fontlist {
+
+static double WSSDistance(const Face* aFace, const gfxFontStyle& aStyle) {
+ double stretchDist = StretchDistance(aFace->mStretch, aStyle.stretch);
+ double styleDist = StyleDistance(aFace->mStyle, aStyle.style);
+ double weightDist = WeightDistance(aFace->mWeight, aStyle.weight);
+
+ // Sanity-check that the distances are within the expected range
+ // (update if implementation of the distance functions is changed).
+ MOZ_ASSERT(stretchDist >= 0.0 && stretchDist <= 2000.0);
+ MOZ_ASSERT(styleDist >= 0.0 && styleDist <= 500.0);
+ MOZ_ASSERT(weightDist >= 0.0 && weightDist <= 1600.0);
+
+ // weight/style/stretch priority: stretch >> style >> weight
+ // so we multiply the stretch and style values to make them dominate
+ // the result
+ return stretchDist * kStretchFactor + styleDist * kStyleFactor +
+ weightDist * kWeightFactor;
+}
+
+void* Pointer::ToPtr(FontList* aFontList,
+ size_t aSize) const MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ // On failure, we'll return null; callers need to handle this appropriately
+ // (e.g. via fallback).
+ void* result = nullptr;
+
+ if (IsNull()) {
+ return result;
+ }
+
+ // Ensure the list doesn't get replaced out from under us. Font-list rebuild
+ // happens on the main thread, so only non-main-thread callers need to lock
+ // it here.
+ bool isMainThread = NS_IsMainThread();
+ if (!isMainThread) {
+ gfxPlatformFontList::PlatformFontList()->Lock();
+ }
+
+ uint32_t blockIndex = Block();
+
+ // If the Pointer refers to a block we have not yet mapped in this process,
+ // we first need to retrieve new block handle(s) from the parent and update
+ // our mBlocks list.
+ auto& blocks = aFontList->mBlocks;
+ if (blockIndex >= blocks.Length()) {
+ if (MOZ_UNLIKELY(XRE_IsParentProcess())) {
+ // Shouldn't happen! A content process tried to pass a bad Pointer?
+ goto cleanup;
+ }
+ // If we're not on the main thread, we can't do the IPC involved in
+ // UpdateShmBlocks; just let the lookup fail for now.
+ if (!isMainThread) {
+ goto cleanup;
+ }
+ // UpdateShmBlocks can fail, if the parent has replaced the font list with
+ // a new generation. In that case we just return null, and whatever font
+ // the content process was trying to use will appear unusable for now. It's
+ // about to receive a notification of the new font list anyhow, at which
+ // point it will flush its caches and reflow everything, so the temporary
+ // failure of this font will be forgotten.
+ // UpdateShmBlocks will take the platform font-list lock during the update.
+ if (MOZ_UNLIKELY(!aFontList->UpdateShmBlocks(true))) {
+ goto cleanup;
+ }
+ MOZ_ASSERT(blockIndex < blocks.Length(), "failure in UpdateShmBlocks?");
+ // This is wallpapering bug 1667977; it's unclear if we will always survive
+ // this, as the content process may be unable to shape/render text if all
+ // font lookups are failing.
+ // In at least some cases, however, this can occur transiently while the
+ // font list is being rebuilt by the parent; content will then be notified
+ // that the list has changed, and should refresh everything successfully.
+ if (MOZ_UNLIKELY(blockIndex >= blocks.Length())) {
+ goto cleanup;
+ }
+ }
+
+ {
+ // Don't create a pointer that's outside what the block has allocated!
+ const auto& block = blocks[blockIndex];
+ if (MOZ_LIKELY(Offset() + aSize <= block->Allocated())) {
+ result = static_cast<char*>(block->Memory()) + Offset();
+ }
+ }
+
+cleanup:
+ if (!isMainThread) {
+ gfxPlatformFontList::PlatformFontList()->Unlock();
+ }
+
+ return result;
+}
+
+void String::Assign(const nsACString& aString, FontList* aList) {
+ // We only assign to previously-empty strings; they are never modified
+ // after initial assignment.
+ MOZ_ASSERT(mPointer.IsNull());
+ mLength = aString.Length();
+ mPointer = aList->Alloc(mLength + 1);
+ auto* p = mPointer.ToArray<char>(aList, mLength);
+ std::memcpy(p, aString.BeginReading(), mLength);
+ p[mLength] = '\0';
+}
+
+Family::Family(FontList* aList, const InitData& aData)
+ : mFaceCount(0),
+ mKey(aList, aData.mKey),
+ mName(aList, aData.mName),
+ mCharacterMap(Pointer::Null()),
+ mFaces(Pointer::Null()),
+ mIndex(aData.mIndex),
+ mVisibility(aData.mVisibility),
+ mIsSimple(false),
+ mIsBundled(aData.mBundled),
+ mIsBadUnderlineFamily(aData.mBadUnderline),
+ mIsForceClassic(aData.mForceClassic),
+ mIsAltLocale(aData.mAltLocale) {}
+
+class SetCharMapRunnable : public mozilla::Runnable {
+ public:
+ SetCharMapRunnable(uint32_t aListGeneration, Pointer aFacePtr,
+ gfxCharacterMap* aCharMap)
+ : Runnable("SetCharMapRunnable"),
+ mListGeneration(aListGeneration),
+ mFacePtr(aFacePtr),
+ mCharMap(aCharMap) {}
+
+ NS_IMETHOD Run() override {
+ auto* list = gfxPlatformFontList::PlatformFontList()->SharedFontList();
+ if (!list || list->GetGeneration() != mListGeneration) {
+ return NS_OK;
+ }
+ dom::ContentChild::GetSingleton()->SendSetCharacterMap(mListGeneration,
+ mFacePtr, *mCharMap);
+ return NS_OK;
+ }
+
+ private:
+ uint32_t mListGeneration;
+ Pointer mFacePtr;
+ RefPtr<gfxCharacterMap> mCharMap;
+};
+
+void Face::SetCharacterMap(FontList* aList, gfxCharacterMap* aCharMap) {
+ if (!XRE_IsParentProcess()) {
+ Pointer ptr = aList->ToSharedPointer(this);
+ if (NS_IsMainThread()) {
+ dom::ContentChild::GetSingleton()->SendSetCharacterMap(
+ aList->GetGeneration(), ptr, *aCharMap);
+ } else {
+ NS_DispatchToMainThread(
+ new SetCharMapRunnable(aList->GetGeneration(), ptr, aCharMap));
+ }
+ return;
+ }
+ auto pfl = gfxPlatformFontList::PlatformFontList();
+ mCharacterMap = pfl->GetShmemCharMap(aCharMap);
+}
+
+void Family::AddFaces(FontList* aList, const nsTArray<Face::InitData>& aFaces) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (mFaceCount > 0) {
+ // Already initialized!
+ return;
+ }
+
+ uint32_t count = aFaces.Length();
+ bool isSimple = false;
+ // A family is "simple" (i.e. simplified style selection may be used instead
+ // of the full CSS font-matching algorithm) if there is at maximum one normal,
+ // bold, italic, and bold-italic face; in this case, they are stored at known
+ // positions in the mFaces array.
+ const Face::InitData* slots[4] = {nullptr, nullptr, nullptr, nullptr};
+ if (count >= 2 && count <= 4) {
+ // Check if this can be treated as a "simple" family
+ isSimple = true;
+ for (const auto& f : aFaces) {
+ if (!f.mWeight.IsSingle() || !f.mStretch.IsSingle() ||
+ !f.mStyle.IsSingle()) {
+ isSimple = false;
+ break;
+ }
+ if (!f.mStretch.Min().IsNormal()) {
+ isSimple = false;
+ break;
+ }
+ // Figure out which slot (0-3) this face belongs in
+ size_t slot = 0;
+ static_assert((kBoldMask | kItalicMask) == 0b11, "bad bold/italic bits");
+ if (f.mWeight.Min().IsBold()) {
+ slot |= kBoldMask;
+ }
+ if (f.mStyle.Min().IsItalic() || f.mStyle.Min().IsOblique()) {
+ slot |= kItalicMask;
+ }
+ if (slots[slot]) {
+ // More than one face mapped to the same slot - not a simple family!
+ isSimple = false;
+ break;
+ }
+ slots[slot] = &f;
+ }
+ if (isSimple) {
+ // Ensure all 4 slots will exist, even if some are empty.
+ count = 4;
+ }
+ }
+
+ // Allocate space for the face records, and initialize them.
+ // coverity[suspicious_sizeof]
+ Pointer p = aList->Alloc(count * sizeof(Pointer));
+ auto* facePtrs = p.ToArray<Pointer>(aList, count);
+ for (size_t i = 0; i < count; i++) {
+ if (isSimple && !slots[i]) {
+ facePtrs[i] = Pointer::Null();
+ } else {
+ const auto* initData = isSimple ? slots[i] : &aFaces[i];
+ Pointer fp = aList->Alloc(sizeof(Face));
+ auto* face = fp.ToPtr<Face>(aList);
+ (void)new (face) Face(aList, *initData);
+ facePtrs[i] = fp;
+ if (initData->mCharMap) {
+ face->SetCharacterMap(aList, initData->mCharMap);
+ }
+ }
+ }
+
+ mIsSimple = isSimple;
+ mFaces = p;
+ mFaceCount.store(count);
+
+ if (LOG_FONTLIST_ENABLED()) {
+ const nsCString& fam = DisplayName().AsString(aList);
+ for (unsigned j = 0; j < aFaces.Length(); j++) {
+ nsAutoCString weight, style, stretch;
+ aFaces[j].mWeight.ToString(weight);
+ aFaces[j].mStyle.ToString(style);
+ aFaces[j].mStretch.ToString(stretch);
+ LOG_FONTLIST(
+ ("(shared-fontlist) family (%s) added face (%s) index %u, weight "
+ "%s, style %s, stretch %s",
+ fam.get(), aFaces[j].mDescriptor.get(), aFaces[j].mIndex,
+ weight.get(), style.get(), stretch.get()));
+ }
+ }
+}
+
+bool Family::FindAllFacesForStyleInternal(FontList* aList,
+ const gfxFontStyle& aStyle,
+ nsTArray<Face*>& aFaceList) const {
+ MOZ_ASSERT(aFaceList.IsEmpty());
+ if (!IsInitialized()) {
+ return false;
+ }
+
+ Pointer* facePtrs = Faces(aList);
+ if (!facePtrs) {
+ return false;
+ }
+
+ // Depending on the kind of family, we have to do varying amounts of work
+ // to figure out what face(s) to use for the requested style properties.
+
+ // If the family has only one face, we simply use it; no further style
+ // checking needed. (However, for bitmap fonts we may still need to check
+ // whether the size is acceptable.)
+ if (NumFaces() == 1) {
+ MOZ_ASSERT(!facePtrs[0].IsNull());
+ auto* face = facePtrs[0].ToPtr<Face>(aList);
+ if (face && face->HasValidDescriptor()) {
+ aFaceList.AppendElement(face);
+#ifdef MOZ_WIDGET_GTK
+ if (face->mSize) {
+ return true;
+ }
+#endif
+ }
+ return false;
+ }
+
+ // Most families are "simple", having just Regular/Bold/Italic/BoldItalic,
+ // or some subset of these. In this case, we have exactly 4 entries in
+ // mAvailableFonts, stored in the above order; note that some of the entries
+ // may be nullptr. We can then pick the required entry based on whether the
+ // request is for bold or non-bold, italic or non-italic, without running
+ // the more complex matching algorithm used for larger families with many
+ // weights and/or widths.
+
+ if (mIsSimple) {
+ // Family has no more than the "standard" 4 faces, at fixed indexes;
+ // calculate which one we want.
+ // Note that we cannot simply return it as not all 4 faces are necessarily
+ // present.
+ bool wantBold = aStyle.weight.IsBold();
+ bool wantItalic = !aStyle.style.IsNormal();
+ uint8_t faceIndex =
+ (wantItalic ? kItalicMask : 0) | (wantBold ? kBoldMask : 0);
+
+ // If the desired style is available, use it directly.
+ auto* face = facePtrs[faceIndex].ToPtr<Face>(aList);
+ if (face && face->HasValidDescriptor()) {
+ aFaceList.AppendElement(face);
+#ifdef MOZ_WIDGET_GTK
+ if (face->mSize) {
+ return true;
+ }
+#endif
+ return false;
+ }
+
+ // Order to check fallback faces in a simple family, depending on the
+ // requested style.
+ static const uint8_t simpleFallbacks[4][3] = {
+ {kBoldFaceIndex, kItalicFaceIndex,
+ kBoldItalicFaceIndex}, // fallback sequence for Regular
+ {kRegularFaceIndex, kBoldItalicFaceIndex, kItalicFaceIndex}, // Bold
+ {kBoldItalicFaceIndex, kRegularFaceIndex, kBoldFaceIndex}, // Italic
+ {kItalicFaceIndex, kBoldFaceIndex, kRegularFaceIndex} // BoldItalic
+ };
+ const uint8_t* order = simpleFallbacks[faceIndex];
+
+ for (uint8_t trial = 0; trial < 3; ++trial) {
+ // check remaining faces in order of preference to find the first that
+ // actually exists
+ face = facePtrs[order[trial]].ToPtr<Face>(aList);
+ if (face && face->HasValidDescriptor()) {
+ aFaceList.AppendElement(face);
+#ifdef MOZ_WIDGET_GTK
+ if (face->mSize) {
+ return true;
+ }
+#endif
+ return false;
+ }
+ }
+
+ // We can only reach here if we failed to resolve the face pointer, which
+ // can happen if we're on a stylo thread and caught the font list being
+ // updated; in that case we just fail quietly and let font fallback do
+ // something for the time being.
+ return false;
+ }
+
+ // Pick the font(s) that are closest to the desired weight, style, and
+ // stretch. Iterate over all fonts, measuring the weight/style distance.
+ // Because of unicode-range values, there may be more than one font for a
+ // given but the 99% use case is only a single font entry per
+ // weight/style/stretch distance value. To optimize this, only add entries
+ // to the matched font array when another entry already has the same
+ // weight/style/stretch distance and add the last matched font entry. For
+ // normal platform fonts with a single font entry for each
+ // weight/style/stretch combination, only the last matched font entry will
+ // be added.
+ double minDistance = INFINITY;
+ Face* matched = nullptr;
+ // Keep track of whether we've included any non-scalable font resources in
+ // the selected set.
+ bool anyNonScalable = false;
+ for (uint32_t i = 0; i < NumFaces(); i++) {
+ auto* face = facePtrs[i].ToPtr<Face>(aList);
+ if (face) {
+ // weight/style/stretch priority: stretch >> style >> weight
+ double distance = WSSDistance(face, aStyle);
+ if (distance < minDistance) {
+ matched = face;
+ if (!aFaceList.IsEmpty()) {
+ aFaceList.Clear();
+ }
+ minDistance = distance;
+ } else if (distance == minDistance) {
+ if (matched) {
+ aFaceList.AppendElement(matched);
+#ifdef MOZ_WIDGET_GTK
+ if (matched->mSize) {
+ anyNonScalable = true;
+ }
+#endif
+ }
+ matched = face;
+ }
+ }
+ }
+
+ MOZ_ASSERT(matched, "didn't match a font within a family");
+ if (matched) {
+ aFaceList.AppendElement(matched);
+#ifdef MOZ_WIDGET_GTK
+ if (matched->mSize) {
+ anyNonScalable = true;
+ }
+#endif
+ }
+
+ return anyNonScalable;
+}
+
+void Family::FindAllFacesForStyle(FontList* aList, const gfxFontStyle& aStyle,
+ nsTArray<Face*>& aFaceList,
+ bool aIgnoreSizeTolerance) const {
+#ifdef MOZ_WIDGET_GTK
+ bool anyNonScalable =
+#else
+ Unused <<
+#endif
+ FindAllFacesForStyleInternal(aList, aStyle, aFaceList);
+
+#ifdef MOZ_WIDGET_GTK
+ // aFaceList now contains whatever faces are the best style match for
+ // the requested style. If specifically-sized bitmap faces are supported,
+ // we need to additionally filter the list to choose the appropriate size.
+ //
+ // It would be slightly more efficient to integrate this directly into the
+ // face-selection algorithm above, but it's a rare case that doesn't apply
+ // at all to most font families.
+ //
+ // Currently we only support pixel-sized bitmap font faces on Linux/Gtk (i.e.
+ // when using the gfxFcPlatformFontList implementation), so this filtering is
+ // not needed on other platforms.
+ //
+ // (Note that color-bitmap emoji fonts like Apple Color Emoji or Noto Color
+ // Emoji don't count here; they package multiple bitmap sizes into a single
+ // OpenType wrapper, so they appear as a single "scalable" face in our list.)
+ if (anyNonScalable) {
+ uint16_t best = 0;
+ gfxFloat dist = 0.0;
+ for (const auto& f : aFaceList) {
+ if (f->mSize == 0) {
+ // Scalable face; no size distance to compute.
+ continue;
+ }
+ gfxFloat d = fabs(gfxFloat(f->mSize) - aStyle.size);
+ if (!aIgnoreSizeTolerance && (d * 5.0 > f->mSize)) {
+ continue; // Too far from the requested size, ignore.
+ }
+ // If we haven't found a "best" bitmap size yet, or if this is a better
+ // match, remember it.
+ if (!best || d < dist) {
+ best = f->mSize;
+ dist = d;
+ }
+ }
+ // Discard all faces except the chosen "best" size; or if no pixel size was
+ // chosen, all except scalable faces.
+ // This may eliminate *all* faces in the family, if all were bitmaps and
+ // none was a good enough size match, in which case we'll fall back to the
+ // next font-family name.
+ aFaceList.RemoveElementsBy([=](const auto& e) { return e->mSize != best; });
+ }
+#endif
+}
+
+Face* Family::FindFaceForStyle(FontList* aList, const gfxFontStyle& aStyle,
+ bool aIgnoreSizeTolerance) const {
+ AutoTArray<Face*, 4> faces;
+ FindAllFacesForStyle(aList, aStyle, faces, aIgnoreSizeTolerance);
+ return faces.IsEmpty() ? nullptr : faces[0];
+}
+
+void Family::SearchAllFontsForChar(FontList* aList,
+ GlobalFontMatch* aMatchData) {
+ auto* charmap = mCharacterMap.ToPtr<const SharedBitSet>(aList);
+ if (!charmap) {
+ // If the face list is not yet initialized, or if character maps have
+ // not been loaded, go ahead and do this now (by sending a message to the
+ // parent process, if we're running in a child).
+ // After this, all faces should have their mCharacterMap set up, and the
+ // family's mCharacterMap should also be set; but in the code below we
+ // don't assume this all succeeded, so it still checks.
+ if (!gfxPlatformFontList::PlatformFontList()->InitializeFamily(this,
+ true)) {
+ return;
+ }
+ charmap = mCharacterMap.ToPtr<const SharedBitSet>(aList);
+ }
+ if (charmap && !charmap->test(aMatchData->mCh)) {
+ return;
+ }
+
+ uint32_t numFaces = NumFaces();
+ uint32_t charMapsLoaded = 0; // number of faces whose charmap is loaded
+ Pointer* facePtrs = Faces(aList);
+ if (!facePtrs) {
+ return;
+ }
+ for (uint32_t i = 0; i < numFaces; i++) {
+ auto* face = facePtrs[i].ToPtr<Face>(aList);
+ if (!face) {
+ continue;
+ }
+ MOZ_ASSERT(face->HasValidDescriptor());
+ // Get the face's character map, if available (may be null!)
+ charmap = face->mCharacterMap.ToPtr<const SharedBitSet>(aList);
+ if (charmap) {
+ ++charMapsLoaded;
+ }
+ // Check style distance if the char is supported, or if charmap not known
+ // (so that we don't trigger cmap-loading for faces that would be a worse
+ // match than what we've already found).
+ if (!charmap || charmap->test(aMatchData->mCh)) {
+ double distance = WSSDistance(face, aMatchData->mStyle);
+ if (distance < aMatchData->mMatchDistance) {
+ // It's a better style match: get a fontEntry, and if we haven't
+ // already checked character coverage, do it now (note that
+ // HasCharacter() will trigger loading the fontEntry's cmap, if
+ // needed).
+ RefPtr<gfxFontEntry> fe =
+ gfxPlatformFontList::PlatformFontList()->GetOrCreateFontEntry(face,
+ this);
+ if (!fe) {
+ continue;
+ }
+ if (!charmap && !fe->HasCharacter(aMatchData->mCh)) {
+ continue;
+ }
+ if (aMatchData->mPresentation != eFontPresentation::Any) {
+ RefPtr<gfxFont> font = fe->FindOrMakeFont(&aMatchData->mStyle);
+ if (!font) {
+ continue;
+ }
+ bool hasColorGlyph =
+ font->HasColorGlyphFor(aMatchData->mCh, aMatchData->mNextCh);
+ if (hasColorGlyph != PrefersColor(aMatchData->mPresentation)) {
+ distance += kPresentationMismatch;
+ if (distance >= aMatchData->mMatchDistance) {
+ continue;
+ }
+ }
+ }
+ aMatchData->mBestMatch = fe;
+ aMatchData->mMatchDistance = distance;
+ aMatchData->mMatchedSharedFamily = this;
+ }
+ }
+ }
+ if (mCharacterMap.IsNull() && charMapsLoaded == numFaces) {
+ SetupFamilyCharMap(aList);
+ }
+}
+
+void Family::SetFacePtrs(FontList* aList, nsTArray<Pointer>& aFaces) {
+ if (aFaces.Length() >= 2 && aFaces.Length() <= 4) {
+ // Check whether the faces meet the criteria for a "simple" family: no more
+ // than one each of Regular, Bold, Italic, BoldItalic styles. If so, store
+ // them at the appropriate slots in mFaces and set the mIsSimple flag to
+ // accelerate font-matching.
+ bool isSimple = true;
+ Pointer slots[4] = {Pointer::Null(), Pointer::Null(), Pointer::Null(),
+ Pointer::Null()};
+ for (const Pointer& fp : aFaces) {
+ auto* f = fp.ToPtr<const Face>(aList);
+ if (!f->mWeight.IsSingle() || !f->mStyle.IsSingle() ||
+ !f->mStretch.IsSingle()) {
+ isSimple = false;
+ break;
+ }
+ if (!f->mStretch.Min().IsNormal()) {
+ isSimple = false;
+ break;
+ }
+ size_t slot = 0;
+ if (f->mWeight.Min().IsBold()) {
+ slot |= kBoldMask;
+ }
+ if (f->mStyle.Min().IsItalic() || f->mStyle.Min().IsOblique()) {
+ slot |= kItalicMask;
+ }
+ if (!slots[slot].IsNull()) {
+ isSimple = false;
+ break;
+ }
+ slots[slot] = fp;
+ }
+ if (isSimple) {
+ size_t size = 4 * sizeof(Pointer);
+ mFaces = aList->Alloc(size);
+ memcpy(mFaces.ToPtr(aList, size), slots, size);
+ mFaceCount.store(4);
+ mIsSimple = true;
+ return;
+ }
+ }
+ size_t size = aFaces.Length() * sizeof(Pointer);
+ mFaces = aList->Alloc(size);
+ memcpy(mFaces.ToPtr(aList, size), aFaces.Elements(), size);
+ mFaceCount.store(aFaces.Length());
+}
+
+void Family::SetupFamilyCharMap(FontList* aList) {
+ // Set the character map of the family to the union of all the face cmaps,
+ // to allow font fallback searches to more rapidly reject the family.
+ if (!mCharacterMap.IsNull()) {
+ return;
+ }
+ if (!XRE_IsParentProcess()) {
+ // |this| could be a Family record in either the Families() or Aliases()
+ // arrays
+ if (NS_IsMainThread()) {
+ dom::ContentChild::GetSingleton()->SendSetupFamilyCharMap(
+ aList->GetGeneration(), aList->ToSharedPointer(this));
+ return;
+ }
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "SetupFamilyCharMap callback",
+ [gen = aList->GetGeneration(), ptr = aList->ToSharedPointer(this)] {
+ dom::ContentChild::GetSingleton()->SendSetupFamilyCharMap(gen, ptr);
+ }));
+ return;
+ }
+ gfxSparseBitSet familyMap;
+ Pointer firstMapShmPointer;
+ const SharedBitSet* firstMap = nullptr;
+ bool merged = false;
+ Pointer* faces = Faces(aList);
+ if (!faces) {
+ return;
+ }
+ for (size_t i = 0; i < NumFaces(); i++) {
+ auto* f = faces[i].ToPtr<const Face>(aList);
+ if (!f) {
+ continue; // Skip missing face (in an incomplete "simple" family)
+ }
+ auto* faceMap = f->mCharacterMap.ToPtr<const SharedBitSet>(aList);
+ if (!faceMap) {
+ continue; // If there's a face where setting up the cmap failed, we skip
+ // it as unusable.
+ }
+ if (!firstMap) {
+ firstMap = faceMap;
+ firstMapShmPointer = f->mCharacterMap;
+ } else if (faceMap != firstMap) {
+ if (!merged) {
+ familyMap.Union(*firstMap);
+ merged = true;
+ }
+ familyMap.Union(*faceMap);
+ }
+ }
+ // If we created a merged cmap, we need to save that on the family; or if we
+ // found no usable cmaps at all, we need to store the empty familyMap so that
+ // we won't repeatedly attempt this for an unusable family.
+ if (merged || firstMapShmPointer.IsNull()) {
+ mCharacterMap =
+ gfxPlatformFontList::PlatformFontList()->GetShmemCharMap(&familyMap);
+ } else {
+ // If all [usable] faces had the same cmap, we can just share it.
+ mCharacterMap = firstMapShmPointer;
+ }
+}
+
+FontList::FontList(uint32_t aGeneration) {
+ if (XRE_IsParentProcess()) {
+ // Create the initial shared block, and initialize Header
+ if (AppendShmBlock(SHM_BLOCK_SIZE)) {
+ Header& header = GetHeader();
+ header.mBlockHeader.mAllocated.store(sizeof(Header));
+ header.mGeneration = aGeneration;
+ header.mFamilyCount = 0;
+ header.mBlockCount.store(1);
+ header.mAliasCount.store(0);
+ header.mLocalFaceCount.store(0);
+ header.mFamilies = Pointer::Null();
+ header.mAliases = Pointer::Null();
+ header.mLocalFaces = Pointer::Null();
+ } else {
+ MOZ_CRASH("parent: failed to initialize FontList");
+ }
+ } else {
+ // Initialize using the list of shmem blocks passed by the parent via
+ // SetXPCOMProcessAttributes.
+ auto& blocks = dom::ContentChild::GetSingleton()->SharedFontListBlocks();
+ for (auto& handle : blocks) {
+ auto newShm = MakeUnique<base::SharedMemory>();
+ if (!newShm->IsHandleValid(handle)) {
+ // Bail out and let UpdateShmBlocks try to do its thing below.
+ break;
+ }
+ if (!newShm->SetHandle(std::move(handle), true)) {
+ MOZ_CRASH("failed to set shm handle");
+ }
+ if (!newShm->Map(SHM_BLOCK_SIZE) || !newShm->memory()) {
+ MOZ_CRASH("failed to map shared memory");
+ }
+ uint32_t size = static_cast<BlockHeader*>(newShm->memory())->mBlockSize;
+ MOZ_ASSERT(size >= SHM_BLOCK_SIZE);
+ if (size != SHM_BLOCK_SIZE) {
+ newShm->Unmap();
+ if (!newShm->Map(size) || !newShm->memory()) {
+ MOZ_CRASH("failed to map shared memory");
+ }
+ }
+ mBlocks.AppendElement(new ShmBlock(std::move(newShm)));
+ }
+ blocks.Clear();
+ // Update in case of any changes since the initial message was sent.
+ for (unsigned retryCount = 0; retryCount < 3; ++retryCount) {
+ if (UpdateShmBlocks(false)) {
+ return;
+ }
+ // The only reason for UpdateShmBlocks to fail is if the parent recreated
+ // the list after we read its first block, but before we finished getting
+ // them all, and so the generation check failed on a subsequent request.
+ // Discarding whatever we've got and retrying should get us a new,
+ // consistent set of memory blocks in this case. If this doesn't work
+ // after a couple of retries, bail out.
+ DetachShmBlocks();
+ }
+ NS_WARNING("child: failed to initialize shared FontList");
+ }
+}
+
+FontList::~FontList() { DetachShmBlocks(); }
+
+FontList::Header& FontList::GetHeader() const MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ // We only need to lock if we're not on the main thread.
+ bool isMainThread = NS_IsMainThread();
+ if (!isMainThread) {
+ gfxPlatformFontList::PlatformFontList()->Lock();
+ }
+
+ // It's invalid to try and access this before the first block exists.
+ MOZ_ASSERT(mBlocks.Length() > 0);
+ auto& result = *static_cast<Header*>(mBlocks[0]->Memory());
+
+ if (!isMainThread) {
+ gfxPlatformFontList::PlatformFontList()->Unlock();
+ }
+
+ return result;
+}
+
+bool FontList::AppendShmBlock(uint32_t aSizeNeeded) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ uint32_t size = std::max(aSizeNeeded, SHM_BLOCK_SIZE);
+ auto newShm = MakeUnique<base::SharedMemory>();
+ if (!newShm->CreateFreezeable(size)) {
+ MOZ_CRASH("failed to create shared memory");
+ return false;
+ }
+ if (!newShm->Map(size) || !newShm->memory()) {
+ MOZ_CRASH("failed to map shared memory");
+ return false;
+ }
+ auto readOnly = MakeUnique<base::SharedMemory>();
+ if (!newShm->ReadOnlyCopy(readOnly.get())) {
+ MOZ_CRASH("failed to create read-only copy");
+ return false;
+ }
+
+ ShmBlock* block = new ShmBlock(std::move(newShm));
+ block->StoreAllocated(sizeof(BlockHeader));
+ block->BlockSize() = size;
+
+ mBlocks.AppendElement(block);
+ GetHeader().mBlockCount.store(mBlocks.Length());
+
+ mReadOnlyShmems.AppendElement(std::move(readOnly));
+
+ // We don't need to broadcast the addition of the initial block,
+ // because child processes can't have initialized their list at all
+ // prior to the first block being set up.
+ if (mBlocks.Length() > 1) {
+ if (NS_IsMainThread()) {
+ dom::ContentParent::BroadcastShmBlockAdded(GetGeneration(),
+ mBlocks.Length() - 1);
+ } else {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "ShmBlockAdded callback",
+ [generation = GetGeneration(), index = mBlocks.Length() - 1] {
+ dom::ContentParent::BroadcastShmBlockAdded(generation, index);
+ }));
+ }
+ }
+
+ return true;
+}
+
+void FontList::ShmBlockAdded(uint32_t aGeneration, uint32_t aIndex,
+ base::SharedMemoryHandle aHandle) {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ MOZ_ASSERT(mBlocks.Length() > 0);
+
+ auto newShm = MakeUnique<base::SharedMemory>();
+ if (!newShm->IsHandleValid(aHandle)) {
+ return;
+ }
+ if (!newShm->SetHandle(std::move(aHandle), true)) {
+ MOZ_CRASH("failed to set shm handle");
+ }
+
+ if (aIndex != mBlocks.Length()) {
+ return;
+ }
+ if (aGeneration != GetGeneration()) {
+ return;
+ }
+
+ if (!newShm->Map(SHM_BLOCK_SIZE) || !newShm->memory()) {
+ MOZ_CRASH("failed to map shared memory");
+ }
+
+ uint32_t size = static_cast<BlockHeader*>(newShm->memory())->mBlockSize;
+ MOZ_ASSERT(size >= SHM_BLOCK_SIZE);
+ if (size != SHM_BLOCK_SIZE) {
+ newShm->Unmap();
+ if (!newShm->Map(size) || !newShm->memory()) {
+ MOZ_CRASH("failed to map shared memory");
+ }
+ }
+
+ mBlocks.AppendElement(new ShmBlock(std::move(newShm)));
+}
+
+void FontList::DetachShmBlocks() {
+ for (auto& i : mBlocks) {
+ i->mShmem = nullptr;
+ }
+ mBlocks.Clear();
+ mReadOnlyShmems.Clear();
+}
+
+FontList::ShmBlock* FontList::GetBlockFromParent(uint32_t aIndex) {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ // If we have no existing blocks, we don't want a generation check yet;
+ // the header in the first block will define the generation of this list
+ uint32_t generation = aIndex == 0 ? 0 : GetGeneration();
+ base::SharedMemoryHandle handle = base::SharedMemory::NULLHandle();
+ if (!dom::ContentChild::GetSingleton()->SendGetFontListShmBlock(
+ generation, aIndex, &handle)) {
+ return nullptr;
+ }
+ auto newShm = MakeUnique<base::SharedMemory>();
+ if (!newShm->IsHandleValid(handle)) {
+ return nullptr;
+ }
+ if (!newShm->SetHandle(std::move(handle), true)) {
+ MOZ_CRASH("failed to set shm handle");
+ }
+ if (!newShm->Map(SHM_BLOCK_SIZE) || !newShm->memory()) {
+ MOZ_CRASH("failed to map shared memory");
+ }
+ uint32_t size = static_cast<BlockHeader*>(newShm->memory())->mBlockSize;
+ MOZ_ASSERT(size >= SHM_BLOCK_SIZE);
+ if (size != SHM_BLOCK_SIZE) {
+ newShm->Unmap();
+ if (!newShm->Map(size) || !newShm->memory()) {
+ MOZ_CRASH("failed to map shared memory");
+ }
+ }
+ return new ShmBlock(std::move(newShm));
+}
+
+// We don't take the lock when called from the constructor, so disable thread-
+// safety analysis here.
+bool FontList::UpdateShmBlocks(bool aMustLock) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ if (aMustLock) {
+ gfxPlatformFontList::PlatformFontList()->Lock();
+ }
+ bool result = true;
+ while (!mBlocks.Length() || mBlocks.Length() < GetHeader().mBlockCount) {
+ ShmBlock* newBlock = GetBlockFromParent(mBlocks.Length());
+ if (!newBlock) {
+ result = false;
+ break;
+ }
+ mBlocks.AppendElement(newBlock);
+ }
+ if (aMustLock) {
+ gfxPlatformFontList::PlatformFontList()->Unlock();
+ }
+ return result;
+}
+
+void FontList::ShareBlocksToProcess(nsTArray<base::SharedMemoryHandle>* aBlocks,
+ base::ProcessId aPid) {
+ MOZ_RELEASE_ASSERT(mReadOnlyShmems.Length() == mBlocks.Length());
+ for (auto& shmem : mReadOnlyShmems) {
+ auto handle = shmem->CloneHandle();
+ if (!handle) {
+ // If something went wrong here, we just bail out; the child will need to
+ // request the blocks as needed, at some performance cost. (Although in
+ // practice this may mean resources are so constrained the child process
+ // isn't really going to work at all. But that's not our problem here.)
+ aBlocks->Clear();
+ return;
+ }
+ aBlocks->AppendElement(std::move(handle));
+ }
+}
+
+base::SharedMemoryHandle FontList::ShareBlockToProcess(uint32_t aIndex,
+ base::ProcessId aPid) {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+ MOZ_RELEASE_ASSERT(mReadOnlyShmems.Length() == mBlocks.Length());
+ MOZ_RELEASE_ASSERT(aIndex < mReadOnlyShmems.Length());
+
+ return mReadOnlyShmems[aIndex]->CloneHandle();
+}
+
+Pointer FontList::Alloc(uint32_t aSize) {
+ // Only the parent process does allocation.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // 4-byte alignment is good enough for anything we allocate in the font list,
+ // as our "Pointer" (block index/offset) is a 32-bit value even on x64.
+ auto align = [](uint32_t aSize) -> size_t { return (aSize + 3u) & ~3u; };
+
+ aSize = align(aSize);
+
+ int32_t blockIndex = -1;
+ uint32_t curAlloc, size;
+
+ if (aSize < SHM_BLOCK_SIZE - sizeof(BlockHeader)) {
+ // Try to allocate in the most recently added block first, as this is
+ // highly likely to succeed; if not, try earlier blocks (to fill gaps).
+ const int32_t blockCount = mBlocks.Length();
+ for (blockIndex = blockCount - 1; blockIndex >= 0; --blockIndex) {
+ size = mBlocks[blockIndex]->BlockSize();
+ curAlloc = mBlocks[blockIndex]->Allocated();
+ if (size - curAlloc >= aSize) {
+ break;
+ }
+ }
+ }
+
+ if (blockIndex < 0) {
+ // Couldn't find enough space (or the requested size is too large to use
+ // a part of a block): create a new block.
+ if (!AppendShmBlock(aSize + sizeof(BlockHeader))) {
+ return Pointer::Null();
+ }
+ blockIndex = mBlocks.Length() - 1;
+ curAlloc = mBlocks[blockIndex]->Allocated();
+ }
+
+ // We've found a block; allocate space from it, and return
+ mBlocks[blockIndex]->StoreAllocated(curAlloc + aSize);
+
+ return Pointer(blockIndex, curAlloc);
+}
+
+void FontList::SetFamilyNames(nsTArray<Family::InitData>& aFamilies) {
+ // Only the parent process should ever assign the list of families.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ Header& header = GetHeader();
+ MOZ_ASSERT(!header.mFamilyCount);
+
+ gfxPlatformFontList::PlatformFontList()->ApplyWhitelist(aFamilies);
+ aFamilies.Sort();
+
+ size_t count = aFamilies.Length();
+
+ // Check for duplicate family entries (can occur if there is a bundled font
+ // that has the same name as a system-installed one); in this case we keep
+ // the bundled one as it will always be exposed.
+ if (count > 1) {
+ for (size_t i = 1; i < count; ++i) {
+ if (aFamilies[i].mKey.Equals(aFamilies[i - 1].mKey)) {
+ // Decide whether to discard the current entry or the preceding one
+ size_t discard =
+ aFamilies[i].mBundled && !aFamilies[i - 1].mBundled ? i - 1 : i;
+ aFamilies.RemoveElementAt(discard);
+ --count;
+ --i;
+ }
+ }
+ }
+
+ header.mFamilies = Alloc(count * sizeof(Family));
+ if (header.mFamilies.IsNull()) {
+ return;
+ }
+
+ // We can't call Families() here because the mFamilyCount field has not yet
+ // been set!
+ auto* families = header.mFamilies.ToArray<Family>(this, count);
+ for (size_t i = 0; i < count; i++) {
+ (void)new (&families[i]) Family(this, aFamilies[i]);
+ LOG_FONTLIST(("(shared-fontlist) family %u (%s)", (unsigned)i,
+ aFamilies[i].mName.get()));
+ }
+
+ header.mFamilyCount = count;
+}
+
+void FontList::SetAliases(
+ nsClassHashtable<nsCStringHashKey, AliasData>& aAliasTable) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ Header& header = GetHeader();
+
+ // Build an array of Family::InitData records based on the entries in
+ // aAliasTable, then sort them and store into the fontlist.
+ nsTArray<Family::InitData> aliasArray;
+ aliasArray.SetCapacity(aAliasTable.Count());
+ for (const auto& entry : aAliasTable) {
+ aliasArray.AppendElement(Family::InitData(
+ entry.GetKey(), entry.GetData()->mBaseFamily, entry.GetData()->mIndex,
+ entry.GetData()->mVisibility, entry.GetData()->mBundled,
+ entry.GetData()->mBadUnderline, entry.GetData()->mForceClassic, true));
+ }
+ aliasArray.Sort();
+
+ size_t count = aliasArray.Length();
+ if (count < header.mAliasCount) {
+ // This shouldn't happen, but handle it safely by just bailing out.
+ NS_WARNING("cannot reduce number of aliases");
+ return;
+ }
+ fontlist::Pointer ptr = Alloc(count * sizeof(Family));
+ auto* aliases = ptr.ToArray<Family>(this, count);
+ for (size_t i = 0; i < count; i++) {
+ (void)new (&aliases[i]) Family(this, aliasArray[i]);
+ LOG_FONTLIST(("(shared-fontlist) alias family %u (%s: %s)", (unsigned)i,
+ aliasArray[i].mKey.get(), aliasArray[i].mName.get()));
+ aliases[i].SetFacePtrs(this, aAliasTable.Get(aliasArray[i].mKey)->mFaces);
+ if (LOG_FONTLIST_ENABLED()) {
+ const auto& faces = aAliasTable.Get(aliasArray[i].mKey)->mFaces;
+ for (unsigned j = 0; j < faces.Length(); j++) {
+ auto* face = faces[j].ToPtr<const Face>(this);
+ const nsCString& desc = face->mDescriptor.AsString(this);
+ nsAutoCString weight, style, stretch;
+ face->mWeight.ToString(weight);
+ face->mStyle.ToString(style);
+ face->mStretch.ToString(stretch);
+ LOG_FONTLIST(
+ ("(shared-fontlist) face (%s) index %u, weight %s, style %s, "
+ "stretch %s",
+ desc.get(), face->mIndex, weight.get(), style.get(),
+ stretch.get()));
+ }
+ }
+ }
+
+ // Set the pointer before the count, so that any other process trying to read
+ // will not risk out-of-bounds access to the array.
+ header.mAliases = ptr;
+ header.mAliasCount.store(count);
+}
+
+void FontList::SetLocalNames(
+ nsTHashMap<nsCStringHashKey, LocalFaceRec::InitData>& aLocalNameTable) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ Header& header = GetHeader();
+ if (header.mLocalFaceCount > 0) {
+ return; // already been done!
+ }
+ auto faceArray = ToTArray<nsTArray<nsCString>>(aLocalNameTable.Keys());
+ faceArray.Sort();
+ size_t count = faceArray.Length();
+ Family* families = Families();
+ fontlist::Pointer ptr = Alloc(count * sizeof(LocalFaceRec));
+ auto* faces = ptr.ToArray<LocalFaceRec>(this, count);
+ for (size_t i = 0; i < count; i++) {
+ (void)new (&faces[i]) LocalFaceRec();
+ const auto& rec = aLocalNameTable.Get(faceArray[i]);
+ faces[i].mKey.Assign(faceArray[i], this);
+ // Local face name records will refer to the canonical family name; we don't
+ // need to search aliases here.
+ const auto* family = FindFamily(rec.mFamilyName, /*aPrimaryNameOnly*/ true);
+ if (!family) {
+ // Skip this record if the family was excluded by the font whitelist pref.
+ continue;
+ }
+ faces[i].mFamilyIndex = family - families;
+ if (rec.mFaceIndex == uint32_t(-1)) {
+ // The InitData record contains an mFaceDescriptor rather than an index,
+ // so now we need to look for the appropriate index in the family.
+ faces[i].mFaceIndex = 0;
+ const Pointer* faceList =
+ static_cast<const Pointer*>(family->Faces(this));
+ for (uint32_t j = 0; j < family->NumFaces(); j++) {
+ if (!faceList[j].IsNull()) {
+ auto* f = faceList[j].ToPtr<const Face>(this);
+ if (f && rec.mFaceDescriptor == f->mDescriptor.AsString(this)) {
+ faces[i].mFaceIndex = j;
+ break;
+ }
+ }
+ }
+ } else {
+ faces[i].mFaceIndex = rec.mFaceIndex;
+ }
+ }
+ header.mLocalFaces = ptr;
+ header.mLocalFaceCount.store(count);
+}
+
+nsCString FontList::LocalizedFamilyName(const Family* aFamily) {
+ // If the given family was created for an alternate locale or legacy name,
+ // search for a standard family that corresponds to it. This is a linear
+ // search of the font list, but (a) this is only used to show names in
+ // Preferences, so is not performance-critical for layout etc.; and (b) few
+ // such family names are normally present anyway, the vast majority of fonts
+ // just have a single family name and we return it directly.
+ if (aFamily->IsAltLocaleFamily()) {
+ // Currently only the Windows backend actually does this; on other systems,
+ // the family index is unused and will be kNoIndex for all fonts.
+ if (aFamily->Index() != Family::kNoIndex) {
+ const Family* families = Families();
+ for (uint32_t i = 0; i < NumFamilies(); ++i) {
+ if (families[i].Index() == aFamily->Index() &&
+ families[i].IsBundled() == aFamily->IsBundled() &&
+ !families[i].IsAltLocaleFamily()) {
+ return families[i].DisplayName().AsString(this);
+ }
+ }
+ }
+ }
+
+ // For standard families (or if we failed to find the expected standard
+ // family for some reason), just return the DisplayName.
+ return aFamily->DisplayName().AsString(this);
+}
+
+Family* FontList::FindFamily(const nsCString& aName, bool aPrimaryNameOnly) {
+ struct FamilyNameComparator {
+ FamilyNameComparator(FontList* aList, const nsCString& aTarget)
+ : mList(aList), mTarget(aTarget) {}
+
+ int operator()(const Family& aVal) const {
+ return Compare(mTarget,
+ nsDependentCString(aVal.Key().BeginReading(mList)));
+ }
+
+ private:
+ FontList* mList;
+ const nsCString& mTarget;
+ };
+
+ const Header& header = GetHeader();
+
+ Family* families = Families();
+ if (!families) {
+ return nullptr;
+ }
+
+ size_t match;
+ if (BinarySearchIf(families, 0, header.mFamilyCount,
+ FamilyNameComparator(this, aName), &match)) {
+ return &families[match];
+ }
+
+ if (aPrimaryNameOnly) {
+ return nullptr;
+ }
+
+ if (header.mAliasCount) {
+ Family* aliases = AliasFamilies();
+ size_t match;
+ if (aliases && BinarySearchIf(aliases, 0, header.mAliasCount,
+ FamilyNameComparator(this, aName), &match)) {
+ return &aliases[match];
+ }
+ }
+
+#ifdef XP_WIN
+ // For Windows only, because of how DWrite munges font family names in some
+ // cases (see
+ // https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Components.PostAttachments/00/02/24/90/36/WPF%20Font%20Selection%20Model.pdf
+ // and discussion on the OpenType list), try stripping a possible style-name
+ // suffix from the end of the requested family name.
+ // After the deferred font loader has finished, this is no longer needed as
+ // the "real" family names will have been found in AliasFamilies() above.
+ if (aName.Contains(' ')) {
+ auto pfl = gfxPlatformFontList::PlatformFontList();
+ pfl->mLock.AssertCurrentThreadIn();
+ if (header.mAliasCount) {
+ // Aliases have been fully loaded by the parent process, so just discard
+ // any stray mAliasTable and mLocalNameTable entries from earlier calls
+ // to this code, and return.
+ pfl->mAliasTable.Clear();
+ pfl->mLocalNameTable.Clear();
+ return nullptr;
+ }
+
+ // Do we already have an aliasData record for this name? If so, we just
+ // return its base family.
+ if (auto lookup = pfl->mAliasTable.Lookup(aName)) {
+ return FindFamily(lookup.Data()->mBaseFamily, true);
+ }
+
+ // Strip the style suffix (after last space in the name) to get a "base"
+ // family name.
+ const char* data = aName.BeginReading();
+ int32_t index = aName.Length();
+ while (--index > 0) {
+ if (data[index] == ' ') {
+ break;
+ }
+ }
+ if (index <= 0) {
+ return nullptr;
+ }
+ nsAutoCString base(Substring(aName, 0, index));
+ if (BinarySearchIf(families, 0, header.mFamilyCount,
+ FamilyNameComparator(this, base), &match)) {
+ // This may be a possible base family to satisfy the search; call
+ // ReadFaceNamesForFamily and see if the desired name ends up in
+ // mAliasTable.
+ // Note that ReadFaceNamesForFamily may store entries in mAliasTable
+ // (and mLocalNameTable), but if this is happening in a content
+ // process (which is the common case) those entries will not be saved
+ // into the shared font list; they're just used here until the "real"
+ // alias list is ready, then discarded.
+ Family* baseFamily = &families[match];
+ pfl->ReadFaceNamesForFamily(baseFamily, false);
+ if (auto lookup = pfl->mAliasTable.Lookup(aName)) {
+ if (lookup.Data()->mFaces.Length() != baseFamily->NumFaces()) {
+ // If the alias family doesn't have all the faces of the base family,
+ // then style matching may end up resolving to a face that isn't
+ // supposed to be available in the legacy styled family. To ensure
+ // such mis-styling will get fixed, we start the async font info
+ // loader (if it hasn't yet been triggered), which will pull in the
+ // full metadata we need and then force a reflow.
+ pfl->InitOtherFamilyNames(/* aDeferOtherFamilyNamesLoading */ true);
+ }
+ return baseFamily;
+ }
+ }
+ }
+#endif
+
+ return nullptr;
+}
+
+LocalFaceRec* FontList::FindLocalFace(const nsCString& aName) {
+ struct FaceNameComparator {
+ FaceNameComparator(FontList* aList, const nsCString& aTarget)
+ : mList(aList), mTarget(aTarget) {}
+
+ int operator()(const LocalFaceRec& aVal) const {
+ return Compare(mTarget,
+ nsDependentCString(aVal.mKey.BeginReading(mList)));
+ }
+
+ private:
+ FontList* mList;
+ const nsCString& mTarget;
+ };
+
+ Header& header = GetHeader();
+
+ LocalFaceRec* faces = LocalFaces();
+ size_t match;
+ if (faces && BinarySearchIf(faces, 0, header.mLocalFaceCount,
+ FaceNameComparator(this, aName), &match)) {
+ return &faces[match];
+ }
+
+ return nullptr;
+}
+
+void FontList::SearchForLocalFace(const nsACString& aName, Family** aFamily,
+ Face** aFace) {
+ Header& header = GetHeader();
+ MOZ_ASSERT(header.mLocalFaceCount == 0,
+ "do not use when local face names are already set up!");
+ LOG_FONTLIST(
+ ("(shared-fontlist) local face search for (%s)", aName.BeginReading()));
+ char initial = aName[0];
+ Family* families = Families();
+ if (!families) {
+ return;
+ }
+ for (uint32_t i = 0; i < header.mFamilyCount; i++) {
+ Family* family = &families[i];
+ if (family->Key().BeginReading(this)[0] != initial) {
+ continue;
+ }
+ LOG_FONTLIST(("(shared-fontlist) checking family (%s)",
+ family->Key().AsString(this).BeginReading()));
+ if (!family->IsInitialized()) {
+ if (!gfxPlatformFontList::PlatformFontList()->InitializeFamily(family)) {
+ continue;
+ }
+ }
+ Pointer* faces = family->Faces(this);
+ if (!faces) {
+ continue;
+ }
+ for (uint32_t j = 0; j < family->NumFaces(); j++) {
+ auto* face = faces[j].ToPtr<Face>(this);
+ if (!face) {
+ continue;
+ }
+ nsAutoCString psname, fullname;
+ if (gfxPlatformFontList::PlatformFontList()->ReadFaceNames(
+ family, face, psname, fullname)) {
+ LOG_FONTLIST(("(shared-fontlist) read psname (%s) fullname (%s)",
+ psname.get(), fullname.get()));
+ ToLowerCase(psname);
+ ToLowerCase(fullname);
+ if (aName == psname || aName == fullname) {
+ *aFamily = family;
+ *aFace = face;
+ return;
+ }
+ }
+ }
+ }
+}
+
+Pointer FontList::ToSharedPointer(const void* aPtr) {
+ const char* p = (const char*)aPtr;
+ const uint32_t blockCount = mBlocks.Length();
+ for (uint32_t i = 0; i < blockCount; ++i) {
+ const char* blockAddr = (const char*)mBlocks[i]->Memory();
+ if (p >= blockAddr && p < blockAddr + SHM_BLOCK_SIZE) {
+ return Pointer(i, p - blockAddr);
+ }
+ }
+ MOZ_DIAGNOSTIC_ASSERT(false, "invalid shared-memory pointer");
+ return Pointer::Null();
+}
+
+size_t FontList::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+size_t FontList::SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t result = mBlocks.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (const auto& b : mBlocks) {
+ result += aMallocSizeOf(b.get()) + aMallocSizeOf(b->mShmem.get());
+ }
+ return result;
+}
+
+size_t FontList::AllocatedShmemSize() const {
+ size_t result = 0;
+ for (const auto& b : mBlocks) {
+ result += b->BlockSize();
+ }
+ return result;
+}
+
+} // namespace fontlist
+} // namespace mozilla
diff --git a/gfx/thebes/SharedFontList.h b/gfx/thebes/SharedFontList.h
new file mode 100644
index 0000000000..f2eaf28223
--- /dev/null
+++ b/gfx/thebes/SharedFontList.h
@@ -0,0 +1,396 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SharedFontList_h
+#define SharedFontList_h
+
+#include "gfxFontEntry.h"
+#include <atomic>
+
+class gfxCharacterMap;
+struct gfxFontStyle;
+struct GlobalFontMatch;
+
+namespace mozilla {
+namespace fontlist {
+
+class FontList; // See the separate SharedFontList-impl.h header
+
+/**
+ * A Pointer in the shared font list contains a packed index/offset pair,
+ * with a 12-bit index into the array of shared-memory blocks, and a 20-bit
+ * offset into the block.
+ * The maximum size of each block is therefore 2^20 bytes (1 MB) if sub-parts
+ * of the block are to be allocated; however, a larger block (up to 2^32 bytes)
+ * can be created and used as a single allocation if necessary.
+ */
+struct Pointer {
+ private:
+ friend class FontList;
+ static const uint32_t kIndexBits = 12u;
+ static const uint32_t kBlockShift = 20u;
+ static_assert(kIndexBits + kBlockShift == 32u, "bad Pointer bit count");
+
+ static const uint32_t kNullValue = 0xffffffffu;
+ static const uint32_t kOffsetMask = (1u << kBlockShift) - 1;
+
+ public:
+ static Pointer Null() { return Pointer(); }
+
+ Pointer() : mBlockAndOffset(kNullValue) {}
+
+ Pointer(uint32_t aBlock, uint32_t aOffset)
+ : mBlockAndOffset((aBlock << kBlockShift) | aOffset) {
+ MOZ_ASSERT(aBlock < (1u << kIndexBits) && aOffset < (1u << kBlockShift));
+ }
+
+ Pointer(const Pointer& aOther) {
+ mBlockAndOffset.store(aOther.mBlockAndOffset);
+ }
+
+ Pointer(Pointer&& aOther) { mBlockAndOffset.store(aOther.mBlockAndOffset); }
+
+ /**
+ * Check if a Pointer has the null value.
+ *
+ * NOTE!
+ * In a child process, it is possible that conversion to a "real" pointer
+ * using ToPtr() will fail even when IsNull() is false, so calling code
+ * that may run in child processes must be prepared to handle this.
+ */
+ bool IsNull() const { return mBlockAndOffset == kNullValue; }
+
+ uint32_t Block() const { return mBlockAndOffset >> kBlockShift; }
+
+ uint32_t Offset() const { return mBlockAndOffset & kOffsetMask; }
+
+ /**
+ * Convert a fontlist::Pointer to a standard C++ pointer. This requires the
+ * FontList, which will know where the shared memory block is mapped in
+ * the current process's address space.
+ *
+ * aSize is the expected size of the pointed-to object, for bounds checking.
+ *
+ * NOTE!
+ * In child processes this may fail and return nullptr, even if IsNull() is
+ * false, in cases where the font list is in the process of being rebuilt.
+ */
+ void* ToPtr(FontList* aFontList, size_t aSize) const;
+
+ template <typename T>
+ T* ToPtr(FontList* aFontList) const {
+ return static_cast<T*>(ToPtr(aFontList, sizeof(T)));
+ }
+
+ template <typename T>
+ T* ToArray(FontList* aFontList, size_t aCount) const {
+ return static_cast<T*>(ToPtr(aFontList, sizeof(T) * aCount));
+ }
+
+ Pointer& operator=(const Pointer& aOther) {
+ mBlockAndOffset.store(aOther.mBlockAndOffset);
+ return *this;
+ }
+
+ Pointer& operator=(Pointer&& aOther) {
+ mBlockAndOffset.store(aOther.mBlockAndOffset);
+ return *this;
+ }
+
+ // We store the block index and the offset within the block as a single
+ // atomic 32-bit value so we can safely modify a Pointer without other
+ // processes seeing a broken (partially-updated) value.
+ std::atomic<uint32_t> mBlockAndOffset;
+};
+
+/**
+ * Family and face names are stored as String records, which hold a length
+ * (in utf-8 code units) and a Pointer to the string's UTF-8 characters.
+ */
+struct String {
+ String() : mPointer(Pointer::Null()), mLength(0) {}
+
+ String(FontList* aList, const nsACString& aString)
+ : mPointer(Pointer::Null()) {
+ Assign(aString, aList);
+ }
+
+ const nsCString AsString(FontList* aList) const {
+ MOZ_ASSERT(!mPointer.IsNull());
+ // It's tempting to use AssignLiteral here so that we get an nsCString that
+ // simply wraps the character data in the shmem block without needing to
+ // allocate or copy. But that's unsafe because in the event of font-list
+ // reinitalization, that shared memory will be unmapped; then any copy of
+ // the nsCString that may still be around will crash if accessed.
+ return nsCString(mPointer.ToArray<const char>(aList, mLength), mLength);
+ }
+
+ void Assign(const nsACString& aString, FontList* aList);
+
+ const char* BeginReading(FontList* aList) const {
+ MOZ_ASSERT(!mPointer.IsNull());
+ auto* str = mPointer.ToArray<const char>(aList, mLength);
+ return str ? str : "";
+ }
+
+ uint32_t Length() const { return mLength; }
+
+ /**
+ * Return whether the String has been set to a value.
+ *
+ * NOTE!
+ * In a child process, accessing the value could fail even if IsNull()
+ * returned false. In this case, the nsCString constructor used by AsString()
+ * will be passed a null pointer, and return an empty string despite the
+ * non-zero Length() recorded here.
+ */
+ bool IsNull() const { return mPointer.IsNull(); }
+
+ private:
+ Pointer mPointer;
+ uint32_t mLength;
+};
+
+/**
+ * A Face record represents an individual font resource; it has the style
+ * properties needed for font matching, as well as a pointer to a character
+ * map that records the supported character set. This may be Null if we have
+ * not yet loaded the data.
+ * The mDescriptor and mIndex fields provide the information needed to
+ * instantiate a (platform-specific) font reference that can be used with
+ * platform font APIs; their content depends on the requirements of the
+ * platform APIs (e.g. font PostScript name, file pathname, serialized
+ * fontconfig pattern, etc).
+ */
+struct Face {
+ // Data required to initialize a Face
+ struct InitData {
+ nsCString mDescriptor; // descriptor that can be used to instantiate a
+ // platform font reference
+ uint16_t mIndex; // an index used with descriptor (on some platforms)
+#ifdef MOZ_WIDGET_GTK
+ uint16_t mSize; // pixel size if bitmap; zero indicates scalable
+#endif
+ bool mFixedPitch; // is the face fixed-pitch (monospaced)?
+ mozilla::WeightRange mWeight; // CSS font-weight value
+ mozilla::StretchRange mStretch; // CSS font-stretch value
+ mozilla::SlantStyleRange mStyle; // CSS font-style value
+ RefPtr<gfxCharacterMap> mCharMap; // character map, or null if not loaded
+ };
+
+ // Note that mCharacterMap is not set from the InitData by this constructor;
+ // the caller must use SetCharacterMap to handle that separately if required.
+ Face(FontList* aList, const InitData& aData)
+ : mDescriptor(aList, aData.mDescriptor),
+ mIndex(aData.mIndex),
+#ifdef MOZ_WIDGET_GTK
+ mSize(aData.mSize),
+#endif
+ mFixedPitch(aData.mFixedPitch),
+ mWeight(aData.mWeight),
+ mStretch(aData.mStretch),
+ mStyle(aData.mStyle),
+ mCharacterMap(Pointer::Null()) {
+ }
+
+ bool HasValidDescriptor() const {
+ return !mDescriptor.IsNull() && mIndex != uint16_t(-1);
+ }
+
+ void SetCharacterMap(FontList* aList, gfxCharacterMap* aCharMap);
+
+ String mDescriptor;
+ uint16_t mIndex;
+#ifdef MOZ_WIDGET_GTK
+ uint16_t mSize;
+#endif
+ bool mFixedPitch;
+ mozilla::WeightRange mWeight;
+ mozilla::StretchRange mStretch;
+ mozilla::SlantStyleRange mStyle;
+ Pointer mCharacterMap;
+};
+
+/**
+ * A Family record represents an available (installed) font family; it has
+ * a name (for display purposes) and a key (lowercased, for case-insensitive
+ * lookups), as well as a pointer to an array of Faces. Depending on the
+ * platform, the array of faces may be lazily initialized the first time we
+ * want to use the family.
+ */
+struct Family {
+ static constexpr uint32_t kNoIndex = uint32_t(-1);
+
+ // Data required to initialize a Family
+ struct InitData {
+ InitData(const nsACString& aKey, // lookup key (lowercased)
+ const nsACString& aName, // display name
+ uint32_t aIndex = kNoIndex, // [win] system collection index
+ FontVisibility aVisibility = FontVisibility::Unknown,
+ bool aBundled = false, // [win] font was bundled with the app
+ // rather than system-installed
+ bool aBadUnderline = false, // underline-position in font is bad
+ bool aForceClassic = false, // [win] use "GDI classic" rendering
+ bool aAltLocale = false // font is alternate localized family
+ )
+ : mKey(aKey),
+ mName(aName),
+ mIndex(aIndex),
+ mVisibility(aVisibility),
+ mBundled(aBundled),
+ mBadUnderline(aBadUnderline),
+ mForceClassic(aForceClassic),
+ mAltLocale(aAltLocale) {}
+ bool operator<(const InitData& aRHS) const { return mKey < aRHS.mKey; }
+ bool operator==(const InitData& aRHS) const {
+ return mKey == aRHS.mKey && mName == aRHS.mName &&
+ mVisibility == aRHS.mVisibility && mBundled == aRHS.mBundled &&
+ mBadUnderline == aRHS.mBadUnderline;
+ }
+ const nsCString mKey;
+ const nsCString mName;
+ uint32_t mIndex;
+ FontVisibility mVisibility;
+ bool mBundled;
+ bool mBadUnderline;
+ bool mForceClassic;
+ bool mAltLocale;
+ };
+
+ /**
+ * Font families are considered "simple" if they contain only 4 faces with
+ * style attributes corresponding to Regular, Bold, Italic, and BoldItalic
+ * respectively, or a subset of these (e.g. only Regular and Bold). In this
+ * case, the faces are stored at predefined positions in the mFaces array,
+ * and a simplified (faster) style-matching algorithm can be used.
+ */
+ enum {
+ // Indexes into mFaces for families where mIsSimple is true
+ kRegularFaceIndex = 0,
+ kBoldFaceIndex = 1,
+ kItalicFaceIndex = 2,
+ kBoldItalicFaceIndex = 3,
+ // mask values for selecting face with bold and/or italic attributes
+ kBoldMask = 0x01,
+ kItalicMask = 0x02
+ };
+
+ Family(FontList* aList, const InitData& aData);
+
+ void AddFaces(FontList* aList, const nsTArray<Face::InitData>& aFaces);
+
+ void SetFacePtrs(FontList* aList, nsTArray<Pointer>& aFaces);
+
+ const String& Key() const { return mKey; }
+
+ const String& DisplayName() const { return mName; }
+
+ uint32_t Index() const { return mIndex; }
+ bool IsBundled() const { return mIsBundled; }
+
+ uint32_t NumFaces() const {
+ MOZ_ASSERT(IsInitialized());
+ return mFaceCount;
+ }
+
+ Pointer* Faces(FontList* aList) const {
+ MOZ_ASSERT(IsInitialized());
+ return mFaces.ToArray<Pointer>(aList, mFaceCount);
+ }
+
+ FontVisibility Visibility() const { return mVisibility; }
+ bool IsHidden() const { return Visibility() == FontVisibility::Hidden; }
+
+ bool IsBadUnderlineFamily() const { return mIsBadUnderlineFamily; }
+ bool IsForceClassic() const { return mIsForceClassic; }
+ bool IsSimple() const { return mIsSimple; }
+ bool IsAltLocaleFamily() const { return mIsAltLocale; }
+
+ // IsInitialized indicates whether the family has been populated with faces,
+ // and is therefore ready to use.
+ // It is possible that character maps have not yet been loaded.
+ bool IsInitialized() const { return !mFaces.IsNull(); }
+
+ // IsFullyInitialized indicates that not only faces but also character maps
+ // have been set up, so the family can be searched without the possibility
+ // that IPC messaging will be triggered.
+ bool IsFullyInitialized() const {
+ return IsInitialized() && !mCharacterMap.IsNull();
+ }
+
+ void FindAllFacesForStyle(FontList* aList, const gfxFontStyle& aStyle,
+ nsTArray<Face*>& aFaceList,
+ bool aIgnoreSizeTolerance = false) const;
+
+ Face* FindFaceForStyle(FontList* aList, const gfxFontStyle& aStyle,
+ bool aIgnoreSizeTolerance = false) const;
+
+ void SearchAllFontsForChar(FontList* aList, GlobalFontMatch* aMatchData);
+
+ void SetupFamilyCharMap(FontList* aList);
+
+ private:
+ // Returns true if there are specifically-sized bitmap faces in the list,
+ // so size selection still needs to be done. (Currently only on Linux.)
+ bool FindAllFacesForStyleInternal(FontList* aList, const gfxFontStyle& aStyle,
+ nsTArray<Face*>& aFaceList) const;
+
+ std::atomic<uint32_t> mFaceCount;
+ String mKey;
+ String mName;
+ Pointer mCharacterMap; // If non-null, union of character coverage of all
+ // faces in the family
+ Pointer mFaces; // Pointer to array of |mFaceCount| face pointers
+ uint32_t mIndex; // [win] Top bit set indicates app-bundled font family
+ FontVisibility mVisibility;
+ bool mIsSimple; // family allows simplified style matching: mFaces contains
+ // exactly 4 entries [Regular, Bold, Italic, BoldItalic].
+ bool mIsBundled : 1;
+ bool mIsBadUnderlineFamily : 1;
+ bool mIsForceClassic : 1;
+ bool mIsAltLocale : 1;
+};
+
+/**
+ * For platforms where we build an index of local font face names (PS-name
+ * and fullname of the font) to support @font-face{src:local(...)}, we map
+ * each face name to an index into the family list, and an index into the
+ * family's list of faces.
+ */
+struct LocalFaceRec {
+ /**
+ * The InitData struct needs to record the family name rather than index,
+ * as we may be collecting these records at the same time as building the
+ * family list, so we don't yet know the final family index.
+ * Likewise, in some cases we don't know the final face index because the
+ * faces may be re-sorted to fit into predefined positions in a "simple"
+ * family (if we're reading names before the family has been fully set up).
+ * In that case, we'll store uint32_t(-1) as mFaceIndex, and record the
+ * string descriptor instead.
+ * When actually recorded in the FontList's mLocalFaces array, the family
+ * will be stored as a simple index into the mFamilies array, and the face
+ * as an index into the family's mFaces.
+ */
+ struct InitData {
+ nsCString mFamilyName;
+ nsCString mFaceDescriptor;
+ uint32_t mFaceIndex = uint32_t(-1);
+ InitData(const nsACString& aFamily, const nsACString& aFace)
+ : mFamilyName(aFamily), mFaceDescriptor(aFace) {}
+ InitData(const nsACString& aFamily, uint32_t aFaceIndex)
+ : mFamilyName(aFamily), mFaceIndex(aFaceIndex) {}
+ InitData() = default;
+ };
+ String mKey;
+ uint32_t mFamilyIndex; // Index into the font list's Families array
+ uint32_t mFaceIndex; // Index into the family's Faces array
+};
+
+} // namespace fontlist
+} // namespace mozilla
+
+#undef ERROR // This is defined via Windows.h, but conflicts with some bindings
+ // code when this gets included in the same compilation unit.
+
+#endif /* SharedFontList_h */
diff --git a/gfx/thebes/SkMemoryReporter.cpp b/gfx/thebes/SkMemoryReporter.cpp
new file mode 100644
index 0000000000..d8448908f8
--- /dev/null
+++ b/gfx/thebes/SkMemoryReporter.cpp
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SkMemoryReporter.h"
+
+#include "skia/include/core/SkGraphics.h"
+
+namespace mozilla {
+namespace gfx {
+
+NS_IMETHODIMP
+SkMemoryReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ MOZ_COLLECT_REPORT("explicit/skia-font-cache", KIND_HEAP, UNITS_BYTES,
+ SkGraphics::GetFontCacheUsed(),
+ "Memory used in the skia font cache.");
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(SkMemoryReporter, nsIMemoryReporter);
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/thebes/SkMemoryReporter.h b/gfx/thebes/SkMemoryReporter.h
new file mode 100644
index 0000000000..e092b48541
--- /dev/null
+++ b/gfx/thebes/SkMemoryReporter.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_SKMEMORYREPORTER_H
+#define GFX_SKMEMORYREPORTER_H
+
+#include "nsIMemoryReporter.h"
+
+namespace mozilla {
+namespace gfx {
+
+class SkMemoryReporter final : public nsIMemoryReporter {
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override;
+
+ private:
+ ~SkMemoryReporter() = default;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* GFX_SKMEMORYREPORTER_H */
diff --git a/gfx/thebes/SoftwareVsyncSource.cpp b/gfx/thebes/SoftwareVsyncSource.cpp
new file mode 100644
index 0000000000..b8e12390e7
--- /dev/null
+++ b/gfx/thebes/SoftwareVsyncSource.cpp
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "SoftwareVsyncSource.h"
+#include "base/task.h"
+#include "gfxPlatform.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla::gfx {
+
+SoftwareVsyncSource::SoftwareVsyncSource(const TimeDuration& aInitialVsyncRate)
+ : mVsyncEnabled(false),
+ mVsyncRate(TimeDuration{aInitialVsyncRate},
+ "SoftwareVsyncSource::mVsyncRate") {
+ MOZ_ASSERT(NS_IsMainThread());
+ mVsyncThread = new base::Thread("SoftwareVsyncThread");
+ MOZ_RELEASE_ASSERT(mVsyncThread->Start(),
+ "GFX: Could not start software vsync thread");
+}
+
+SoftwareVsyncSource::~SoftwareVsyncSource() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mVsyncThread) {
+ mVsyncThread->Stop();
+ delete mVsyncThread;
+ }
+};
+
+void SoftwareVsyncSource::EnableVsync() {
+ MOZ_ASSERT(mVsyncThread->IsRunning());
+ if (NS_IsMainThread()) {
+ if (mVsyncEnabled) {
+ return;
+ }
+ mVsyncEnabled = true;
+
+ mVsyncThread->message_loop()->PostTask(
+ NewRunnableMethod("SoftwareVsyncSource::EnableVsync", this,
+ &SoftwareVsyncSource::EnableVsync));
+ return;
+ }
+
+ MOZ_ASSERT(IsInSoftwareVsyncThread());
+ TimeStamp vsyncTime = TimeStamp::Now();
+ TimeStamp outputTime = vsyncTime + GetVsyncRate();
+ NotifyVsync(vsyncTime, outputTime);
+}
+
+void SoftwareVsyncSource::DisableVsync() {
+ MOZ_ASSERT(mVsyncThread->IsRunning());
+ if (NS_IsMainThread()) {
+ if (!mVsyncEnabled) {
+ return;
+ }
+ mVsyncEnabled = false;
+
+ mVsyncThread->message_loop()->PostTask(
+ NewRunnableMethod("SoftwareVsyncSource::DisableVsync", this,
+ &SoftwareVsyncSource::DisableVsync));
+ return;
+ }
+
+ MOZ_ASSERT(IsInSoftwareVsyncThread());
+ if (mCurrentVsyncTask) {
+ mCurrentVsyncTask->Cancel();
+ mCurrentVsyncTask = nullptr;
+ }
+}
+
+bool SoftwareVsyncSource::IsVsyncEnabled() {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mVsyncEnabled;
+}
+
+bool SoftwareVsyncSource::IsInSoftwareVsyncThread() {
+ return mVsyncThread->thread_id() == PlatformThread::CurrentId();
+}
+
+void SoftwareVsyncSource::NotifyVsync(const TimeStamp& aVsyncTimestamp,
+ const TimeStamp& aOutputTimestamp) {
+ MOZ_ASSERT(IsInSoftwareVsyncThread());
+
+ TimeStamp displayVsyncTime = aVsyncTimestamp;
+ TimeStamp now = TimeStamp::Now();
+ // Posted tasks can only have integer millisecond delays
+ // whereas TimeDurations can have floating point delays.
+ // Thus the vsync timestamp can be in the future, which large parts
+ // of the system can't handle, including animations. Force the timestamp to be
+ // now.
+ if (aVsyncTimestamp > now) {
+ displayVsyncTime = now;
+ }
+
+ VsyncSource::NotifyVsync(displayVsyncTime, aOutputTimestamp);
+
+ // Prevent skew by still scheduling based on the original
+ // vsync timestamp
+ ScheduleNextVsync(aVsyncTimestamp);
+}
+
+TimeDuration SoftwareVsyncSource::GetVsyncRate() {
+ auto rate = mVsyncRate.Lock();
+ return *rate;
+}
+
+void SoftwareVsyncSource::SetVsyncRate(const TimeDuration& aNewRate) {
+ auto rate = mVsyncRate.Lock();
+ *rate = aNewRate;
+}
+
+void SoftwareVsyncSource::ScheduleNextVsync(TimeStamp aVsyncTimestamp) {
+ MOZ_ASSERT(IsInSoftwareVsyncThread());
+ TimeDuration vsyncRate = GetVsyncRate();
+ TimeStamp nextVsync = aVsyncTimestamp + vsyncRate;
+ TimeDuration delay = nextVsync - TimeStamp::Now();
+ if (delay.ToMilliseconds() < 0) {
+ delay = TimeDuration::FromMilliseconds(0);
+ nextVsync = TimeStamp::Now();
+ }
+
+ TimeStamp outputTime = nextVsync + vsyncRate;
+
+ mCurrentVsyncTask = NewCancelableRunnableMethod<TimeStamp, TimeStamp>(
+ "SoftwareVsyncSource::NotifyVsync", this,
+ &SoftwareVsyncSource::NotifyVsync, nextVsync, outputTime);
+
+ RefPtr<Runnable> addrefedTask = mCurrentVsyncTask;
+ mVsyncThread->message_loop()->PostDelayedTask(addrefedTask.forget(),
+ delay.ToMilliseconds());
+}
+
+void SoftwareVsyncSource::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ DisableVsync();
+ mVsyncThread->Stop();
+ delete mVsyncThread;
+ mVsyncThread = nullptr;
+}
+
+} // namespace mozilla::gfx
diff --git a/gfx/thebes/SoftwareVsyncSource.h b/gfx/thebes/SoftwareVsyncSource.h
new file mode 100644
index 0000000000..0a86432db3
--- /dev/null
+++ b/gfx/thebes/SoftwareVsyncSource.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef GFX_SOFTWARE_VSYNC_SOURCE_H
+#define GFX_SOFTWARE_VSYNC_SOURCE_H
+
+#include "mozilla/DataMutex.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "base/thread.h"
+#include "nsISupportsImpl.h"
+#include "VsyncSource.h"
+
+namespace mozilla::gfx {
+
+// Fallback option to use a software timer to mimic vsync. Useful for gtests
+// To mimic a hardware vsync thread, we create a dedicated software timer
+// vsync thread.
+class SoftwareVsyncSource : public VsyncSource {
+ public:
+ explicit SoftwareVsyncSource(const TimeDuration& aInitialVsyncRate);
+ virtual ~SoftwareVsyncSource();
+
+ void EnableVsync() override;
+ void DisableVsync() override;
+ bool IsVsyncEnabled() override;
+ bool IsInSoftwareVsyncThread();
+ void NotifyVsync(const TimeStamp& aVsyncTimestamp,
+ const TimeStamp& aOutputTimestamp) override;
+ TimeDuration GetVsyncRate() override;
+ void ScheduleNextVsync(TimeStamp aVsyncTimestamp);
+ void Shutdown() override;
+
+ // Can be called on any thread
+ void SetVsyncRate(const TimeDuration& aNewRate);
+
+ protected:
+ // Use a chromium thread because nsITimers* fire on the main thread
+ base::Thread* mVsyncThread;
+ RefPtr<CancelableRunnable> mCurrentVsyncTask; // only access on vsync thread
+ bool mVsyncEnabled; // Only access on main thread
+
+ private:
+ DataMutex<TimeDuration> mVsyncRate; // can be accessed on any thread
+};
+
+} // namespace mozilla::gfx
+
+#endif /* GFX_SOFTWARE_VSYNC_SOURCE_H */
diff --git a/gfx/thebes/StandardFonts-linux.inc b/gfx/thebes/StandardFonts-linux.inc
new file mode 100644
index 0000000000..3a62262ead
--- /dev/null
+++ b/gfx/thebes/StandardFonts-linux.inc
@@ -0,0 +1,494 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// List of standard font families present in a default English install
+// of Ubuntu 20.04:
+static const char* kBaseFonts_Ubuntu_20_04[] = {
+ "aakar",
+ "Abyssinica SIL",
+ "Ani",
+ "AnjaliOldLipi",
+ "Bitstream Charter",
+ "C059",
+ "Century Schoolbook L",
+ "Chandas",
+ "Chilanka",
+ "Courier 10 Pitch",
+ "D050000L",
+ "DejaVu Sans",
+ "DejaVu Sans Mono",
+ "DejaVu Serif",
+ "Dingbats",
+ "Droid Sans Fallback",
+ "Dyuthi",
+ "FreeMono",
+ "FreeSans",
+ "FreeSerif",
+ "Gargi",
+ "Garuda",
+ "Gayathri",
+ "Gayathri Thin",
+ "Gubbi",
+ "Jamrul",
+ "KacstArt",
+ "KacstBook",
+ "KacstDecorative",
+ "KacstDigital",
+ "KacstFarsi",
+ "KacstLetter",
+ "KacstNaskh",
+ "KacstOffice",
+ "KacstOne",
+ "KacstPen",
+ "KacstPoster",
+ "KacstQurn",
+ "KacstScreen",
+ "KacstTitle",
+ "KacstTitleL",
+ "Kalapi",
+ "Kalimati",
+ "Karumbi",
+ "Keraleeyam",
+ "Khmer OS",
+ "Khmer OS System",
+ "Kinnari",
+ "Laksaman",
+ "Liberation Mono",
+ "Liberation Sans",
+ "Liberation Sans Narrow",
+ "Liberation Serif",
+ "Likhan",
+ "LKLUG",
+ "Lohit Assamese",
+ "Lohit Bengali",
+ "Lohit Devanagari",
+ "Lohit Gujarati",
+ "Lohit Gurmukhi",
+ "Lohit Kannada",
+ "Lohit Malayalam",
+ "Lohit Odia",
+ "Lohit Tamil",
+ "Lohit Tamil Classical",
+ "Lohit Telugu",
+ "Loma",
+ "Manjari",
+ "Manjari Thin",
+ "Meera",
+ "Mitra Mono",
+ "mry_KacstQurn",
+ "Mukti Narrow",
+ "Mukti Narrow Bold",
+ "Nakula",
+ "Navilu",
+ "Nimbus Mono L",
+ "Nimbus Mono PS",
+ "Nimbus Roman",
+ "Nimbus Roman No9 L",
+ "Nimbus Sans",
+ "Nimbus Sans L",
+ "Nimbus Sans Narrow",
+ "Norasi",
+ "Noto Color Emoji",
+ "Noto Mono",
+ "Noto Sans CJK HK",
+ "Noto Sans CJK JP",
+ "Noto Sans CJK KR",
+ "Noto Sans CJK SC",
+ "Noto Sans CJK TC",
+ "Noto Sans Mono CJK HK",
+ "Noto Sans Mono CJK JP",
+ "Noto Sans Mono CJK KR",
+ "Noto Sans Mono CJK SC",
+ "Noto Sans Mono CJK TC",
+ "Noto Serif CJK JP",
+ "Noto Serif CJK KR",
+ "Noto Serif CJK SC",
+ "Noto Serif CJK TC",
+ "OpenSymbol",
+ "ori1Uni",
+ "P052",
+ "Padauk",
+ "Padauk Book",
+ "padmaa",
+ "padmaa-Bold.1.1",
+ "padmmaa",
+ "Pagul",
+ "Phetsarath OT",
+ "Pothana2000",
+ "Purisa",
+ "Rachana",
+ "RaghuMalayalamSans",
+ "Rasa",
+ "Rasa Light",
+ "Rasa Medium",
+ "Rasa SemiBold",
+ "Rekha",
+ "Saab",
+ "Sahadeva",
+ "Samanata",
+ "Samyak Devanagari",
+ "Samyak Gujarati",
+ "Samyak Malayalam",
+ "Samyak Tamil",
+ "Sarai",
+ "Sawasdee",
+ "Standard Symbols L",
+ "Standard Symbols PS",
+ "Suruma",
+ "Tibetan Machine Uni",
+ "Tlwg Mono",
+ "Tlwg Typewriter",
+ "Tlwg Typist",
+ "Tlwg Typo",
+ "Ubuntu",
+ "Ubuntu Condensed",
+ "Ubuntu Light",
+ "Ubuntu Mono",
+ "Ubuntu Thin",
+ "Umpush",
+ "Uroob",
+ "URW Bookman",
+ "URW Bookman L",
+ "URW Chancery L",
+ "URW Gothic",
+ "URW Gothic L",
+ "URW Palladio L",
+ "utkal",
+ "Vemana2000",
+ "Waree",
+ "Yrsa",
+ "Yrsa Light",
+ "Yrsa Medium",
+ "Yrsa SemiBold",
+ "Z003",
+ "गार्गी",
+ "नालिमाटी",
+ "অনি Dvf",
+ "মিত্র",
+ "মুক্তি",
+ "মুক্তি পাতনা",
+};
+
+// Additional font families installed when all languages are enabled via the
+// Language Support utility on Ubuntu 20.04:
+static const char* kLangFonts_Ubuntu_20_04[] = {
+ "Aharoni CLM",
+ "AlArabiya",
+ "AlBattar",
+ "AlHor",
+ "AlManzomah",
+ "AlYarmook",
+ "Amiri",
+ "Amiri Quran",
+ "Amiri Quran Colored",
+ "AR PL UKai CN",
+ "AR PL UKai HK",
+ "AR PL UKai TW",
+ "AR PL UKai TW MBE",
+ "AR PL UMing CN",
+ "AR PL UMing HK",
+ "AR PL UMing TW",
+ "AR PL UMing TW MBE",
+ "Arab",
+ "Caladings CLM",
+ "Cortoba",
+ "David CLM",
+ "Dimnah",
+ "Drugulin CLM",
+ "Electron",
+ "Ellinia CLM",
+ "Ezra SIL",
+ "Ezra SIL SR",
+ "Frank Ruehl CLM",
+ "Furat",
+ "Granada",
+ "Graph",
+ "Hadasim CLM",
+ "Hani",
+ "Haramain",
+ "Homa",
+ "Hor",
+ "Japan",
+ "Jet",
+ "Kayrawan",
+ "Keter YG",
+ "Khalid",
+ "Khmer OS Battambang",
+ "Khmer OS Bokor",
+ "Khmer OS Content",
+ "Khmer OS Fasthand",
+ "Khmer OS Freehand",
+ "Khmer OS Metal Chrieng",
+ "Khmer OS Muol",
+ "Khmer OS Muol Light",
+ "Khmer OS Muol Pali",
+ "Khmer OS Siemreap",
+ "Mashq",
+ "Mashq-Bold",
+ "Metal",
+ "Miriam CLM",
+ "Miriam Mono CLM",
+ "Nachlieli CLM",
+ "Nada",
+ "Nagham",
+ "Nazli",
+ "Nice",
+ "Noto Sans CJK HK Black",
+ "Noto Sans CJK HK DemiLight",
+ "Noto Sans CJK HK Light",
+ "Noto Sans CJK HK Medium",
+ "Noto Sans CJK HK Thin",
+ "Noto Sans CJK JP Black",
+ "Noto Sans CJK JP DemiLight",
+ "Noto Sans CJK JP Light",
+ "Noto Sans CJK JP Medium",
+ "Noto Sans CJK JP Thin",
+ "Noto Sans CJK KR Black",
+ "Noto Sans CJK KR DemiLight",
+ "Noto Sans CJK KR Light",
+ "Noto Sans CJK KR Medium",
+ "Noto Sans CJK KR Thin",
+ "Noto Sans CJK SC Black",
+ "Noto Sans CJK SC DemiLight",
+ "Noto Sans CJK SC Light",
+ "Noto Sans CJK SC Medium",
+ "Noto Sans CJK SC Thin",
+ "Noto Sans CJK TC Black",
+ "Noto Sans CJK TC DemiLight",
+ "Noto Sans CJK TC Light",
+ "Noto Sans CJK TC Medium",
+ "Noto Sans CJK TC Thin",
+ "Noto Serif CJK JP Black",
+ "Noto Serif CJK JP ExtraLight",
+ "Noto Serif CJK JP Light",
+ "Noto Serif CJK JP Medium",
+ "Noto Serif CJK JP SemiBold",
+ "Noto Serif CJK KR Black",
+ "Noto Serif CJK KR ExtraLight",
+ "Noto Serif CJK KR Light",
+ "Noto Serif CJK KR Medium",
+ "Noto Serif CJK KR SemiBold",
+ "Noto Serif CJK SC Black",
+ "Noto Serif CJK SC ExtraLight",
+ "Noto Serif CJK SC Light",
+ "Noto Serif CJK SC Medium",
+ "Noto Serif CJK SC SemiBold",
+ "Noto Serif CJK TC Black",
+ "Noto Serif CJK TC ExtraLight",
+ "Noto Serif CJK TC Light",
+ "Noto Serif CJK TC Medium",
+ "Noto Serif CJK TC SemiBold",
+ "Ostorah",
+ "Ouhod",
+ "Ouhod-Bold",
+ "Petra",
+ "Rasheeq",
+ "Rasheeq-Bold",
+ "Rehan",
+ "Salem",
+ "Scheherazade",
+ "Shado",
+ "Sharjah",
+ "Shofar",
+ "Simple CLM",
+ "Sindbad",
+ "Stam Ashkenaz CLM",
+ "Stam Sefarad CLM",
+ "Tarablus",
+ "Tholoth",
+ "Titr",
+ "UKIJ 3D",
+ "UKIJ Basma",
+ "UKIJ Bom",
+ "UKIJ Chechek",
+ "UKIJ Chiwer Kesme",
+ "UKIJ CJK",
+ "UKIJ Diwani",
+ "UKIJ Diwani Kawak",
+ "UKIJ Diwani Tom",
+ "UKIJ Diwani Yantu",
+ "UKIJ Ekran",
+ "UKIJ Elipbe",
+ "UKIJ Elipbe_Chekitlik",
+ "UKIJ Esliye",
+ "UKIJ Esliye Chiwer",
+ "UKIJ Esliye Neqish",
+ "UKIJ Esliye Qara",
+ "UKIJ Esliye Tom",
+ "UKIJ Imaret",
+ "UKIJ Inchike",
+ "UKIJ Jelliy",
+ "UKIJ Junun",
+ "UKIJ Kawak",
+ "UKIJ Kawak 3D",
+ "UKIJ Kesme",
+ "UKIJ Kesme Tuz",
+ "UKIJ Kufi",
+ "UKIJ Kufi 3D",
+ "UKIJ Kufi Chiwer",
+ "UKIJ Kufi Gul",
+ "UKIJ Kufi Kawak",
+ "UKIJ Kufi Tar",
+ "UKIJ Kufi Uz",
+ "UKIJ Kufi Yay",
+ "UKIJ Kufi Yolluq",
+ "UKIJ Mejnun",
+ "UKIJ Mejnuntal",
+ "UKIJ Merdane",
+ "UKIJ Moy Qelem",
+ "UKIJ Nasq",
+ "UKIJ Nasq Zilwa",
+ "UKIJ Orqun Basma",
+ "UKIJ Orqun Yazma",
+ "UKIJ Orxun-Yensey",
+ "UKIJ Qara",
+ "UKIJ Qolyazma",
+ "UKIJ Qolyazma Tez",
+ "UKIJ Qolyazma Tuz",
+ "UKIJ Qolyazma Yantu",
+ "UKIJ Ruqi",
+ "UKIJ Saet",
+ "UKIJ Sulus",
+ "UKIJ Sulus Tom",
+ "UKIJ Teng",
+ "UKIJ Tiken",
+ "UKIJ Title",
+ "UKIJ Tor",
+ "UKIJ Tughra",
+ "UKIJ Tuz",
+ "UKIJ Tuz Basma",
+ "UKIJ Tuz Gezit",
+ "UKIJ Tuz Kitab",
+ "UKIJ Tuz Neqish",
+ "UKIJ Tuz Qara",
+ "UKIJ Tuz Tom",
+ "UKIJ Tuz Tor",
+ "UKIJ Zilwa",
+ "UKIJ_Mac Basma",
+ "UKIJ_Mac Ekran",
+ "Yehuda CLM",
+ "מרים",
+};
+
+// List of standard font families installed on Fedora 32 Workstation:
+static const char* kBaseFonts_Fedora_32[] = {
+ "Abyssinica SIL",
+ "C059",
+ "Caladea",
+ "Cantarell",
+ "Cantarell Extra Bold",
+ "Cantarell Light",
+ "Cantarell Thin",
+ "Carlito",
+ "Comfortaa",
+ "Comfortaa Light",
+ "D050000L",
+ "DejaVu Math TeX Gyre",
+ "DejaVu Sans",
+ "DejaVu Sans Condensed",
+ "DejaVu Sans Light",
+ "DejaVu Sans Mono",
+ "DejaVu Serif",
+ "DejaVu Serif Condensed",
+ "Droid Arabic Kufi",
+ "Droid Sans",
+ "Droid Sans Armenian",
+ "Droid Sans Devanagari",
+ "Droid Sans Ethiopic",
+ "Droid Sans Fallback",
+ "Droid Sans Georgian",
+ "Droid Sans Hebrew",
+ "Droid Sans Japanese",
+ "Droid Sans Tamil",
+ "Droid Sans Thai",
+ "Jomolhari",
+ "Khmer OS",
+ "Khmer OS Content",
+ "Khmer OS System",
+ "Liberation Mono",
+ "Liberation Sans",
+ "Liberation Serif",
+ "Lohit Assamese",
+ "Lohit Bengali",
+ "Lohit Devanagari",
+ "Lohit Gujarati",
+ "Lohit Kannada",
+ "Lohit Odia",
+ "Lohit Tamil",
+ "Lohit Telugu",
+ "Meera",
+ "Mingzat",
+ "Montserrat",
+ "Montserrat Black",
+ "Montserrat ExtraBold",
+ "Montserrat ExtraLight",
+ "Montserrat Light",
+ "Montserrat Medium",
+ "Montserrat SemiBold",
+ "Montserrat Thin",
+ "Nimbus Mono PS",
+ "Nimbus Roman",
+ "Nimbus Sans",
+ "Nimbus Sans Narrow",
+ "Noto Color Emoji",
+ "Noto Sans CJK HK",
+ "Noto Sans CJK HK Black",
+ "Noto Sans CJK HK DemiLight",
+ "Noto Sans CJK HK Light",
+ "Noto Sans CJK HK Medium",
+ "Noto Sans CJK HK Thin",
+ "Noto Sans CJK JP",
+ "Noto Sans CJK JP Black",
+ "Noto Sans CJK JP DemiLight",
+ "Noto Sans CJK JP Light",
+ "Noto Sans CJK JP Medium",
+ "Noto Sans CJK JP Thin",
+ "Noto Sans CJK KR",
+ "Noto Sans CJK KR Black",
+ "Noto Sans CJK KR DemiLight",
+ "Noto Sans CJK KR Light",
+ "Noto Sans CJK KR Medium",
+ "Noto Sans CJK KR Thin",
+ "Noto Sans CJK SC",
+ "Noto Sans CJK SC Black",
+ "Noto Sans CJK SC DemiLight",
+ "Noto Sans CJK SC Light",
+ "Noto Sans CJK SC Medium",
+ "Noto Sans CJK SC Thin",
+ "Noto Sans CJK TC",
+ "Noto Sans CJK TC Black",
+ "Noto Sans CJK TC DemiLight",
+ "Noto Sans CJK TC Light",
+ "Noto Sans CJK TC Medium",
+ "Noto Sans CJK TC Thin",
+ "Noto Sans Gurmukhi",
+ "Noto Sans Mono CJK HK",
+ "Noto Sans Mono CJK JP",
+ "Noto Sans Mono CJK KR",
+ "Noto Sans Mono CJK SC",
+ "Noto Sans Mono CJK TC",
+ "Noto Sans Sinhala",
+ "Nuosu SIL",
+ "OpenSymbol",
+ "P052",
+ "Padauk",
+ "PakType Naskh Basic",
+ "PT Sans",
+ "PT Sans Narrow",
+ "Source Code Pro",
+ "Source Code Pro Black",
+ "Source Code Pro ExtraLight",
+ "Source Code Pro Light",
+ "Source Code Pro Medium",
+ "Source Code Pro Semibold",
+ "Standard Symbols PS",
+ "STIX",
+ "STIX Two Math",
+ "STIX Two Text",
+ "Symbola",
+ "URW Bookman",
+ "URW Gothic",
+ "Waree",
+ "Z003",
+};
diff --git a/gfx/thebes/StandardFonts-macos.inc b/gfx/thebes/StandardFonts-macos.inc
new file mode 100644
index 0000000000..6975e28690
--- /dev/null
+++ b/gfx/thebes/StandardFonts-macos.inc
@@ -0,0 +1,295 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// List of standard font families installed as part of macOS 10.15 "Catalina"
+// from https://developer.apple.com/fonts/system-fonts/
+static const char* kBaseFonts[] = {
+ "Al Bayan",
+ "Al Nile",
+ "Al Tarikh",
+ "American Typewriter",
+ "Andale Mono",
+ "Apple Braille",
+ "Apple Chancery",
+ "Apple Color Emoji",
+ "Apple SD Gothic Neo",
+ "Apple Symbols",
+ "AppleGothic",
+ "AppleMyungjo",
+ "Arial",
+ "Arial Black",
+ "Arial Hebrew",
+ "Arial Hebrew Scholar",
+ "Arial Narrow",
+ "Arial Rounded MT Bold",
+ "Arial Unicode MS",
+ "Avenir",
+ "Avenir Next",
+ "Ayuthaya",
+ "Baghdad",
+ "Bangla MN",
+ "Bangla Sangam MN",
+ "Baskerville",
+ "Beirut",
+ "Big Caslon",
+ "Bodoni 72",
+ "Bodoni 72 Oldstyle",
+ "Bodoni 72 Smallcaps",
+ "Bodoni Ornaments",
+ "Bradley Hand",
+ "Brush Script MT",
+ "Chalkboard",
+ "Chalkboard SE",
+ "Chalkduster",
+ "Charter",
+ "Cochin",
+ "Comic Sans MS",
+ "Copperplate",
+ "Corsiva Hebrew",
+ "Courier",
+ "Courier New",
+ "Damascus",
+ "DecoType Naskh",
+ "Devanagari MT",
+ "Devanagari Sangam MN",
+ "Didot",
+ "DIN Alternate",
+ "DIN Condensed",
+ "Diwan Kufi",
+ "Diwan Thuluth",
+ "Euphemia UCAS",
+ "Farah",
+ "Farisi",
+ "Futura",
+ "Galvji",
+ "GB18030 Bitmap",
+ "Geeza Pro",
+ "Geneva",
+ "Georgia",
+ "Gill Sans",
+ "Gujarati MT",
+ "Gujarati Sangam MN",
+ "Gurmukhi MN",
+ "Gurmukhi MT",
+ "Gurmukhi Sangam MN",
+ "Heiti SC",
+ "Heiti TC",
+ "Helvetica",
+ "Helvetica Neue",
+ "Hiragino Maru Gothic ProN",
+ "Hiragino Mincho ProN",
+ "Hiragino Sans",
+ "Hiragino Sans GB",
+ "Hoefler Text",
+ "Impact",
+ "InaiMathi",
+ "ITF Devanagari",
+ "ITF Devanagari Marathi",
+ "Kailasa",
+ "Kannada MN",
+ "Kannada Sangam MN",
+ "Kefa",
+ "Khmer MN",
+ "Khmer Sangam MN",
+ "Kohinoor Bangla",
+ "Kohinoor Devanagari",
+ "Kohinoor Gujarati",
+ "Kohinoor Telugu",
+ "Kokonor",
+ "Krungthep",
+ "KufiStandardGK",
+ "Lao MN",
+ "Lao Sangam MN",
+ "Lucida Grande",
+ "Luminari",
+ "Malayalam MN",
+ "Malayalam Sangam MN",
+ "Marker Felt",
+ "Menlo",
+ "Microsoft Sans Serif",
+ "Mishafi",
+ "Mishafi Gold",
+ "Monaco",
+ "Mshtakan",
+ "Mukta Mahee",
+ "Muna",
+ "Myanmar MN",
+ "Myanmar Sangam MN",
+ "Nadeem Regular",
+ "New Peninim MT",
+ "Noteworthy",
+ "Noto Nastaliq Urdu",
+ "Noto Sans Adlam",
+ "Noto Sans Armenian",
+ "Noto Sans Avestan",
+ "Noto Sans Bamum",
+ "Noto Sans Bassa Vah",
+ "Noto Sans Batak",
+ "Noto Sans Bhaiksuki",
+ "Noto Sans Brahmi",
+ "Noto Sans Buginese",
+ "Noto Sans Buhid",
+ "Noto Sans Canadian Aboriginal",
+ "Noto Sans Carian",
+ "Noto Sans Caucasian Albanian",
+ "Noto Sans Chakma",
+ "Noto Sans Cham",
+ "Noto Sans Coptic",
+ "Noto Sans Cuneiform",
+ "Noto Sans Cypriot",
+ "Noto Sans Duployan",
+ "Noto Sans Egyptian Hieroglyphs",
+ "Noto Sans Elbasan",
+ "Noto Sans Glagolitic",
+ "Noto Sans Gothic",
+ "Noto Sans Gunjala Gondi",
+ "Noto Sans Hanifi Rohingya",
+ "Noto Sans Hanunoo",
+ "Noto Sans Hatran",
+ "Noto Sans Imperial Aramaic",
+ "Noto Sans Inscriptional Pahlavi",
+ "Noto Sans Inscriptional Parthian",
+ "Noto Sans Javanese",
+ "Noto Sans Kaithi",
+ "Noto Sans Kannada",
+ "Noto Sans Kayah Li",
+ "Noto Sans Kharoshthi",
+ "Noto Sans Khojki",
+ "Noto Sans Khudawadi",
+ "Noto Sans Lepcha",
+ "Noto Sans Limbu",
+ "Noto Sans Linear A",
+ "Noto Sans Linear B",
+ "Noto Sans Lisu",
+ "Noto Sans Lycian",
+ "Noto Sans Lydian",
+ "Noto Sans Mahajani",
+ "Noto Sans Mandaic",
+ "Noto Sans Manichaean",
+ "Noto Sans Marchen",
+ "Noto Sans Masaram Gondi",
+ "Noto Sans Meetei Mayek",
+ "Noto Sans Mende Kikakui",
+ "Noto Sans Meroitic",
+ "Noto Sans Miao",
+ "Noto Sans Modi",
+ "Noto Sans Mongolian",
+ "Noto Sans Mro",
+ "Noto Sans Multani",
+ "Noto Sans Myanmar",
+ "Noto Sans Nabataean",
+ "Noto Sans New Tai Lue",
+ "Noto Sans Newa",
+ "Noto Sans NKo",
+ "Noto Sans Ol Chiki",
+ "Noto Sans Old Hungarian",
+ "Noto Sans Old Italic",
+ "Noto Sans Old North Arabian",
+ "Noto Sans Old Permic",
+ "Noto Sans Old Persian",
+ "Noto Sans Old South Arabian",
+ "Noto Sans Old Turkic",
+ "Noto Sans Oriya",
+ "Noto Sans Osage",
+ "Noto Sans Osmanya",
+ "Noto Sans Pahawh Hmong",
+ "Noto Sans Palmyrene",
+ "Noto Sans Pau Cin Hau",
+ "Noto Sans PhagsPa",
+ "Noto Sans Phoenician",
+ "Noto Sans Psalter Pahlavi",
+ "Noto Sans Rejang",
+ "Noto Sans Samaritan",
+ "Noto Sans Saurashtra",
+ "Noto Sans Sharada",
+ "Noto Sans Siddham",
+ "Noto Sans Sora Sompeng",
+ "Noto Sans Sundanese",
+ "Noto Sans Syloti Nagri",
+ "Noto Sans Syriac",
+ "Noto Sans Tagalog",
+ "Noto Sans Tagbanwa",
+ "Noto Sans Tai Le",
+ "Noto Sans Tai Tham",
+ "Noto Sans Tai Viet",
+ "Noto Sans Takri",
+ "Noto Sans Thaana",
+ "Noto Sans Tifinagh",
+ "Noto Sans Tirhuta",
+ "Noto Sans Ugaritic",
+ "Noto Sans Vai",
+ "Noto Sans Wancho",
+ "Noto Sans Warang Citi",
+ "Noto Sans Yi",
+ "Noto Sans Zawgyi",
+ "Noto Serif Ahom",
+ "Noto Serif Balinese",
+ "Noto Serif Hmong Nyiakeng",
+ "Noto Serif Myanmar",
+ "Noto Serif Yezidi",
+ "Optima",
+ "Oriya MN",
+ "Oriya Sangam MN",
+ "Palatino",
+ "Papyrus",
+ "Phosphate",
+ "PingFang HK",
+ "PingFang SC",
+ "PingFang TC",
+ "Plantagenet Cherokee",
+ "PT Mono",
+ "PT Sans",
+ "PT Sans Caption",
+ "PT Sans Narrow",
+ "PT Serif",
+ "PT Serif Caption",
+ "Raanana",
+ "Rockwell",
+ "Sana",
+ "Sathu",
+ "Savoye LET",
+ "Shree Devanagari 714",
+ "SignPainter",
+ "Silom",
+ "Sinhala MN",
+ "Sinhala Sangam MN",
+ "Skia",
+ "Snell Roundhand",
+ "Songti SC",
+ "Songti TC",
+ "STIXGeneral",
+ "STIXIntegralsD",
+ "STIXIntegralsSm",
+ "STIXIntegralsUp",
+ "STIXIntegralsUpD",
+ "STIXIntegralsUpSm",
+ "STIXNonUnicode",
+ "STIXSizeFiveSym",
+ "STIXSizeFourSym",
+ "STIXSizeOneSym",
+ "STIXSizeThreeSym",
+ "STIXSizeTwoSym",
+ "STIXVariants",
+ "STSong",
+ "Sukhumvit Set",
+ "Symbol",
+ "Tahoma",
+ "Tamil MN",
+ "Tamil Sangam MN",
+ "Telugu MN",
+ "Telugu Sangam MN",
+ "Thonburi",
+ "Times",
+ "Times New Roman",
+ "Trattatello",
+ "Trebuchet MS",
+ "Verdana",
+ "Waseem",
+ "Webdings",
+ "Wingdings",
+ "Wingdings 2",
+ "Wingdings 3",
+ "Zapf Dingbats",
+ "Zapfino",
+};
diff --git a/gfx/thebes/StandardFonts-win10.inc b/gfx/thebes/StandardFonts-win10.inc
new file mode 100644
index 0000000000..c9306e5307
--- /dev/null
+++ b/gfx/thebes/StandardFonts-win10.inc
@@ -0,0 +1,201 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// List of standard font families installed as part of Windows 10
+// from https://docs.microsoft.com/en-us/typography/fonts/windows_10_font_list
+// TODO: check whether we need to list legacy styled family names like "... Light".
+static const char* kBaseFonts[] = {
+ "AlternateGothic2 BT",
+ "Arial",
+ "Arial Black",
+ "Bahnschrift",
+ "Bahnschrift Light",
+ "Bahnschrift SemiBold",
+ "Bahnschrift SemiLight",
+ "Calibri",
+ "Calibri Light",
+ "Cambria",
+ "Cambria Math",
+ "Candara",
+ "Comic Sans MS",
+ "Consolas",
+ "Constantia",
+ "Corbel",
+ "Courier New",
+ "Ebrima",
+ "Franklin Gothic Medium",
+ "Gabriola",
+ "Gadugi",
+ "Georgia",
+ "HoloLens MDL2 Assets",
+ "Impact",
+ "Javanese Text",
+ "Leelawadee UI",
+ "Leelawadee UI Semilight",
+ "Lucida Console",
+ "Lucida Sans Unicode",
+ "Malgun Gothic",
+ "Malgun Gothic Semilight",
+ "Marlett",
+ "Microsoft Himalaya",
+ "Microsoft JhengHei",
+ "Microsoft JhengHei Light",
+ "Microsoft JhengHei UI",
+ "Microsoft JhengHei UI Light",
+ "Microsoft New Tai Lue",
+ "Microsoft PhagsPa",
+ "Microsoft Sans Serif",
+ "Microsoft Tai Le",
+ "Microsoft YaHei",
+ "Microsoft YaHei Light",
+ "Microsoft YaHei UI",
+ "Microsoft YaHei UI Light",
+ "Microsoft Yi Baiti",
+ "MingLiU-ExtB",
+ "MingLiU_HKSCS-ExtB",
+ "Mongolian Baiti",
+ "MS Gothic",
+ "MS PGothic",
+ "MS UI Gothic",
+ "MV Boli",
+ "Myanmar Text",
+ "Nirmala UI",
+ "Nirmala UI Semilight",
+ "NSimSun",
+ "Palatino Linotype",
+ "PMingLiU-ExtB",
+ "Segoe MDL2 Assets",
+ "Segoe Print",
+ "Segoe Script",
+ "Segoe UI",
+ "Segoe UI Black",
+ "Segoe UI Emoji",
+ "Segoe UI Historic",
+ "Segoe UI Light",
+ "Segoe UI Semibold",
+ "Segoe UI Semilight",
+ "Segoe UI Symbol",
+ "SimSun",
+ "SimSun-ExtB",
+ "Sitka Banner",
+ "Sitka Display",
+ "Sitka Heading",
+ "Sitka Small",
+ "Sitka Subheading",
+ "Sitka Text",
+ "Sylfaen",
+ "Symbol",
+ "Tahoma",
+ "Times New Roman",
+ "Trebuchet MS",
+ "Verdana",
+ "Webdings",
+ "Wingdings",
+ "Yu Gothic",
+ "Yu Gothic Light",
+ "Yu Gothic Medium",
+ "Yu Gothic UI",
+ "Yu Gothic UI Light",
+ "Yu Gothic UI Semibold",
+ "Yu Gothic UI Semilight",
+};
+
+// Additional fonts provided by language-pack installation.
+static const char* kLangPackFonts[] = {
+ "Aharoni Bold", // Hebrew Supplemental Fonts
+ "Aldhabi", // Arabic Script Supplemental Fonts
+ "Andalus", // Arabic Script Supplemental Fonts
+ "Angsana New", // Thai Supplemental Fonts
+ "AngsanaUPC", // Thai Supplemental Fonts
+ "Aparajita", // Devanagari Supplemental Fonts
+ "Arabic Typesetting", // Arabic Script Supplemental Fonts
+ "Batang", // Korean Supplemental Fonts
+ "BatangChe", // Korean Supplemental Fonts
+ "BIZ UDGothic", // Japanese Supplemental Fonts
+ "BIZ UDMincho", // Japanese Supplemental Fonts
+ "BIZ UDPGothic", // Japanese Supplemental Fonts
+ "BIZ UDPMincho", // Japanese Supplemental Fonts
+ "Browallia New", // Thai Supplemental Fonts
+ "BrowalliaUPC", // Thai Supplemental Fonts
+ "Cordia New", // Thai Supplemental Fonts
+ "CordiaUPC", // Thai Supplemental Fonts
+ "DaunPenh", // Khmer Supplemental Fonts
+ "David", // Hebrew Supplemental Fonts
+ "DengXian", // Chinese (Simplified) Supplemental Fonts
+ "DFKai-SB", // Chinese (Traditional) Supplemental Fonts
+ "DilleniaUPC", // Thai Supplemental Fonts
+ "DokChampa", // Lao Supplemental Fonts
+ "Dotum", // Korean Supplemental Fonts
+ "DotumChe", // Korean Supplemental Fonts
+ "Estrangelo Edessa", // Syriac Supplemental Fonts
+ "EucrosiaUPC", // Thai Supplemental Fonts
+ "Euphemia", // Canadian Aboriginal Syllabics Supplemental Fonts
+ "FangSong", // Chinese (Simplified) Supplemental Fonts
+ "FrankRuehl", // Hebrew Supplemental Fonts
+ "FreesiaUPC", // Thai Supplemental Fonts
+ "Gautami", // Telugu Supplemental Fonts
+ "Gisha", // Hebrew Supplemental Fonts
+ "Gulim", // Korean Supplemental Fonts
+ "GulimChe", // Korean Supplemental Fonts
+ "Gungsuh", // Korean Supplemental Fonts
+ "GungsuhChe", // Korean Supplemental Fonts
+ "IrisUPC", // Thai Supplemental Fonts
+ "Iskoola Pota", // Sinhala Supplemental Fonts
+ "JasmineUPC", // Thai Supplemental Fonts
+ "KaiTi", // Chinese (Simplified) Supplemental Fonts
+ "Kalinga", // Odia Supplemental Fonts
+ "Kartika", // Malayalam Supplemental Fonts
+ "Khmer UI", // Khmer Supplemental Fonts
+ "KodchiangUPC", // Thai Supplemental Fonts
+ "Kokila", // Devanagari Supplemental Fonts
+ "Lao UI", // Lao Supplemental Fonts
+ "Latha", // Tamil Supplemental Fonts
+ "Leelawadee", // Thai Supplemental Fonts
+ "Levenim MT", // Hebrew Supplemental Fonts
+ "LilyUPC", // Thai Supplemental Fonts
+ "Mangal", // Devanagari Supplemental Fonts
+ "Meiryo", // Japanese Supplemental Fonts
+ "Meiryo UI", // Japanese Supplemental Fonts
+ "Microsoft Uighur", // Arabic Script Supplemental Fonts
+ "MingLiU", // Chinese (Traditional) Supplemental Fonts
+ "MingLiU_HKSCS", // Chinese (Traditional) Supplemental Fonts
+ "Miriam", // Hebrew Supplemental Fonts
+ "MoolBoran", // Khmer Supplemental Fonts
+ "MS Mincho", // Japanese Supplemental Fonts
+ "MS PMincho", // Japanese Supplemental Fonts
+ "Narkisim", // Hebrew Supplemental Fonts
+ "Nyala", // Ethiopic Supplemental Fonts
+ "Plantagenet Cherokee", // Cherokee Supplemental Fonts
+ "PMingLiU", // Chinese (Traditional) Supplemental Fonts
+ "Raavi", // Gurmukhi Supplemental Fonts
+ "Rod", // Hebrew Supplemental Fonts
+ "Sakkal Majalla", // Arabic Script Supplemental Fonts
+ "Sanskrit Text", // Devanagari Supplemental Fonts
+ "Shonar Bangla", // Bangla Script Supplemental Fonts
+ "Shruti", // Gujarati Supplemental Fonts
+ "SimHei", // Chinese (Simplified) Supplemental Fonts
+ "Simplified Arabic", // Arabic Script Supplemental Fonts
+ "Traditional Arabic", // Arabic Script Supplemental Fonts
+ "Tunga", // Kannada Supplemental Fonts
+ "UD Digi Kyokasho", // Japanese Supplemental Fonts
+ "UD Digi Kyokasho N-R", // Japanese Supplemental Fonts
+ "UD Digi Kyokasho NK-B", // Japanese Supplemental Fonts
+ "UD Digi Kyokasho NK-R", // Japanese Supplemental Fonts
+ "UD Digi Kyokasho NP-B", // Japanese Supplemental Fonts
+ "UD Digi Kyokasho NP-R", // Japanese Supplemental Fonts
+ "Urdu Typesetting", // Arabic Script Supplemental Fonts
+ "Utsaah", // Devanagari Supplemental Fonts
+ "Vani", // Telugu Supplemental Fonts
+ "Vijaya", // Tamil Supplemental Fonts
+ "Vrinda", // Bangla Script Supplemental Fonts
+ "Yu Mincho", // Japanese Supplemental Fonts
+// Latin/Greek/Cyrillic scripts are already well-supported by the base fonts,
+// so we do not include these even when the LangPack collection is enabled.
+// "Arial Nova", // Pan-European Supplemental Fonts - EXCLUDED
+// "Georgia Pro", // Pan-European Supplemental Fonts - EXCLUDED
+// "Gill Sans Nova", // Pan-European Supplemental Fonts - EXCLUDED
+// "Neue Haas Grotesk Text Pro", // Pan-European Supplemental Fonts - EXCLUDED
+// "Rockwell Nova", // Pan-European Supplemental Fonts - EXCLUDED
+// "Verdana Pro", // Pan-European Supplemental Fonts - EXCLUDED
+};
diff --git a/gfx/thebes/ThebesRLBox.h b/gfx/thebes/ThebesRLBox.h
new file mode 100644
index 0000000000..2f4188a962
--- /dev/null
+++ b/gfx/thebes/ThebesRLBox.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef THEBES_RLBOX
+#define THEBES_RLBOX
+
+#include "ThebesRLBoxTypes.h"
+
+// Load general firefox configuration of RLBox
+#include "mozilla/rlbox/rlbox_config.h"
+
+#ifdef MOZ_WASM_SANDBOXING_GRAPHITE
+// Include the generated header file so that we are able to resolve the symbols
+// in the wasm binary
+# include "rlbox.wasm.h"
+# define RLBOX_USE_STATIC_CALLS() rlbox_wasm2c_sandbox_lookup_symbol
+# include "mozilla/rlbox/rlbox_wasm2c_sandbox.hpp"
+#else
+# define RLBOX_USE_STATIC_CALLS() rlbox_noop_sandbox_lookup_symbol
+# include "mozilla/rlbox/rlbox_noop_sandbox.hpp"
+#endif
+
+#include "mozilla/rlbox/rlbox.hpp"
+
+// Struct info needed for rlbox_load_structs_from_library
+#include "graphite2/Font.h"
+#include "graphite2/GraphiteExtra.h"
+#include "graphite2/Segment.h"
+
+#include "graphite2/GraphiteStructsForRLBox.h"
+rlbox_load_structs_from_library(graphite);
+
+#endif
diff --git a/gfx/thebes/ThebesRLBoxTypes.h b/gfx/thebes/ThebesRLBoxTypes.h
new file mode 100644
index 0000000000..83a2f62df4
--- /dev/null
+++ b/gfx/thebes/ThebesRLBoxTypes.h
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef THEBES_RLBOX_TYPES
+#define THEBES_RLBOX_TYPES
+
+#include "mozilla/rlbox/rlbox_types.hpp"
+
+#ifdef MOZ_WASM_SANDBOXING_GRAPHITE
+RLBOX_DEFINE_BASE_TYPES_FOR(gr, wasm2c)
+#else
+RLBOX_DEFINE_BASE_TYPES_FOR(gr, noop)
+#endif
+
+#endif
diff --git a/gfx/thebes/VsyncSource.cpp b/gfx/thebes/VsyncSource.cpp
new file mode 100644
index 0000000000..d7cfb339aa
--- /dev/null
+++ b/gfx/thebes/VsyncSource.cpp
@@ -0,0 +1,160 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "VsyncSource.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/VsyncDispatcher.h"
+#include "MainThreadUtils.h"
+#include "gfxPlatform.h"
+
+#ifdef MOZ_WAYLAND
+# include "WaylandVsyncSource.h"
+#endif
+
+namespace mozilla {
+namespace gfx {
+
+VsyncSource::VsyncSource() : mState("VsyncSource::State") {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+VsyncSource::~VsyncSource() { MOZ_ASSERT(NS_IsMainThread()); }
+
+// Called on the vsync thread
+void VsyncSource::NotifyVsync(const TimeStamp& aVsyncTimestamp,
+ const TimeStamp& aOutputTimestamp) {
+ VsyncId vsyncId;
+ nsTArray<DispatcherRefWithCount> dispatchers;
+
+ {
+ auto state = mState.Lock();
+ vsyncId = state->mVsyncId.Next();
+ dispatchers = state->mDispatchers.Clone();
+ state->mVsyncId = vsyncId;
+ }
+
+ // Notify our listeners, outside of the lock.
+ const VsyncEvent event(vsyncId, aVsyncTimestamp, aOutputTimestamp);
+ for (const auto& dispatcher : dispatchers) {
+ dispatcher.mDispatcher->NotifyVsync(event);
+ }
+}
+
+void VsyncSource::AddVsyncDispatcher(VsyncDispatcher* aVsyncDispatcher) {
+ MOZ_ASSERT(aVsyncDispatcher);
+ {
+ auto state = mState.Lock();
+
+ // Find the dispatcher in mDispatchers. If it is already present, increment
+ // the count. If not, add it with a count of 1.
+ bool found = false;
+ for (auto& dispatcherRefWithCount : state->mDispatchers) {
+ if (dispatcherRefWithCount.mDispatcher == aVsyncDispatcher) {
+ dispatcherRefWithCount.mCount++;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ state->mDispatchers.AppendElement(
+ DispatcherRefWithCount{aVsyncDispatcher, 1});
+ }
+ }
+
+ UpdateVsyncStatus();
+}
+
+void VsyncSource::RemoveVsyncDispatcher(VsyncDispatcher* aVsyncDispatcher) {
+ MOZ_ASSERT(aVsyncDispatcher);
+ {
+ auto state = mState.Lock();
+
+ // Find the dispatcher in mDispatchers. If found, decrement the count.
+ // If the count becomes zero, remove it from mDispatchers.
+ for (auto it = state->mDispatchers.begin(); it != state->mDispatchers.end();
+ ++it) {
+ if (it->mDispatcher == aVsyncDispatcher) {
+ it->mCount--;
+ if (it->mCount == 0) {
+ state->mDispatchers.RemoveElementAt(it);
+ }
+ break;
+ }
+ }
+
+ // In the future we should probably MOZ_RELEASE_ASSERT here that we don't
+ // try to remove a dispatcher which isn't in mDispatchers.
+ }
+
+ UpdateVsyncStatus();
+}
+
+// This is the base class implementation. Subclasses override this method.
+TimeDuration VsyncSource::GetVsyncRate() {
+ // If hardware queries fail / are unsupported, we have to just guess.
+ return TimeDuration::FromMilliseconds(1000.0 / 60.0);
+}
+
+void VsyncSource::UpdateVsyncStatus() {
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "VsyncSource::UpdateVsyncStatus",
+ [self = RefPtr{this}] { self->UpdateVsyncStatus(); }));
+ return;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+ // WARNING: This function SHOULD NOT BE CALLED WHILE HOLDING LOCKS
+ // NotifyVsync grabs a lock to dispatch vsync events
+ // When disabling vsync, we wait for the underlying thread to stop on some
+ // platforms We can deadlock if we wait for the underlying vsync thread to
+ // stop while the vsync thread is in NotifyVsync.
+ bool enableVsync = false;
+ { // scope lock
+ auto state = mState.Lock();
+ enableVsync = !state->mDispatchers.IsEmpty();
+ }
+
+ if (enableVsync) {
+ EnableVsync();
+ } else {
+ DisableVsync();
+ }
+
+ if (IsVsyncEnabled() != enableVsync) {
+ NS_WARNING("Vsync status did not change.");
+ }
+}
+
+// static
+Maybe<TimeDuration> VsyncSource::GetFastestVsyncRate() {
+ Maybe<TimeDuration> retVal;
+ if (!gfxPlatform::Initialized()) {
+ return retVal;
+ }
+
+ RefPtr<VsyncDispatcher> vsyncDispatcher =
+ gfxPlatform::GetPlatform()->GetGlobalVsyncDispatcher();
+ RefPtr<VsyncSource> vsyncSource = vsyncDispatcher->GetCurrentVsyncSource();
+ if (vsyncSource->IsVsyncEnabled()) {
+ retVal.emplace(vsyncSource->GetVsyncRate());
+#ifdef MOZ_WAYLAND
+ Maybe<TimeDuration> waylandRate = WaylandVsyncSource::GetFastestVsyncRate();
+ if (waylandRate) {
+ if (!retVal) {
+ retVal.emplace(*waylandRate);
+ } else if (*waylandRate < *retVal) {
+ retVal = waylandRate;
+ }
+ }
+#endif
+ }
+
+ return retVal;
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/thebes/VsyncSource.h b/gfx/thebes/VsyncSource.h
new file mode 100644
index 0000000000..948b6ada9c
--- /dev/null
+++ b/gfx/thebes/VsyncSource.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_VSYNCSOURCE_H
+#define GFX_VSYNCSOURCE_H
+
+#include "nsTArray.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/layers/LayersTypes.h"
+
+namespace mozilla {
+class VsyncDispatcher;
+class VsyncObserver;
+struct VsyncEvent;
+
+class VsyncIdType {};
+typedef layers::BaseTransactionId<VsyncIdType> VsyncId;
+
+namespace gfx {
+
+// Controls how and when to enable/disable vsync. Lives as long as the
+// gfxPlatform does on the parent process
+class VsyncSource {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VsyncSource)
+
+ typedef mozilla::VsyncDispatcher VsyncDispatcher;
+
+ public:
+ VsyncSource();
+
+ // Notified when this display's vsync callback occurs, on the vsync thread
+ // Different platforms give different aVsyncTimestamp values.
+ // macOS: TimeStamp::Now() or the output time of the previous vsync
+ // callback, whichever is older.
+ // Windows: It's messy, see gfxWindowsPlatform.
+ // Android: TODO
+ //
+ // @param aVsyncTimestamp The time of the Vsync that just occured. Needs to
+ // be at or before the time of the NotifyVsync call.
+ // @param aOutputTimestamp The estimated timestamp at which drawing will
+ // appear on the screen, if the drawing happens within a certain
+ // (unknown) budget. Useful for Audio/Video sync. On platforms where
+ // this timestamp is provided by the system (macOS), it is a much more
+ // stable and consistent timing source than the time at which the vsync
+ // callback is called.
+ virtual void NotifyVsync(const TimeStamp& aVsyncTimestamp,
+ const TimeStamp& aOutputTimestamp);
+
+ // Can be called on any thread.
+ // Adding the same dispatcher multiple times will increment a count.
+ // This means that the sequence "Add, Add, Remove" has the same behavior as
+ // "Add, Remove, Add".
+ void AddVsyncDispatcher(VsyncDispatcher* aDispatcher);
+ void RemoveVsyncDispatcher(VsyncDispatcher* aDispatcher);
+
+ virtual TimeDuration GetVsyncRate();
+
+ // These should all only be called on the main thread
+ virtual void EnableVsync() = 0;
+ virtual void DisableVsync() = 0;
+ virtual bool IsVsyncEnabled() = 0;
+ virtual void Shutdown() = 0;
+
+ // Returns the rate of the fastest enabled VsyncSource or Nothing().
+ static Maybe<TimeDuration> GetFastestVsyncRate();
+
+ protected:
+ virtual ~VsyncSource();
+
+ private:
+ // Can be called on any thread
+ void UpdateVsyncStatus();
+
+ struct DispatcherRefWithCount {
+ // The dispatcher.
+ RefPtr<VsyncDispatcher> mDispatcher;
+ // The number of add calls minus the number of remove calls for this
+ // dispatcher. Should always be > 0 as long as this dispatcher is in
+ // mDispatchers.
+ size_t mCount = 0;
+ };
+
+ struct State {
+ // The set of VsyncDispatchers which are registered with this source.
+ // At the moment, the length of this array is always zero or one.
+ // The ability to register multiple dispatchers is not used yet; it is
+ // intended for when we have one dispatcher per widget.
+ nsTArray<DispatcherRefWithCount> mDispatchers;
+
+ // The vsync ID which we used for the last vsync event.
+ VsyncId mVsyncId;
+ };
+
+ DataMutex<State> mState;
+};
+
+} // namespace gfx
+
+struct VsyncEvent {
+ VsyncId mId;
+ TimeStamp mTime;
+ TimeStamp mOutputTime; // estimate
+
+ VsyncEvent(const VsyncId& aId, const TimeStamp& aVsyncTime,
+ const TimeStamp& aOutputTime)
+ : mId(aId), mTime(aVsyncTime), mOutputTime(aOutputTime) {}
+ VsyncEvent() = default;
+};
+
+} // namespace mozilla
+
+#endif /* GFX_VSYNCSOURCE_H */
diff --git a/gfx/thebes/XlibDisplay.cpp b/gfx/thebes/XlibDisplay.cpp
new file mode 100644
index 0000000000..3bae50194e
--- /dev/null
+++ b/gfx/thebes/XlibDisplay.cpp
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "XlibDisplay.h"
+
+#include "mozilla/Assertions.h"
+
+namespace mozilla::gfx {
+
+XlibDisplay::XlibDisplay(Display* aDisplay, bool aOwned)
+ : mDisplay(aDisplay), mOwned(aOwned) {
+ MOZ_ASSERT(mDisplay);
+}
+
+XlibDisplay::~XlibDisplay() {
+ if (mOwned) {
+ XCloseDisplay(mDisplay);
+ }
+}
+
+/* static */
+std::shared_ptr<XlibDisplay> XlibDisplay::Borrow(Display* aDisplay) {
+ if (!aDisplay) {
+ return nullptr;
+ }
+ return std::shared_ptr<XlibDisplay>(new XlibDisplay(aDisplay, false));
+}
+
+/* static */
+std::shared_ptr<XlibDisplay> XlibDisplay::Open(const char* aDisplayName) {
+ Display* disp = XOpenDisplay(aDisplayName);
+ if (!disp) {
+ return nullptr;
+ }
+ return std::shared_ptr<XlibDisplay>(new XlibDisplay(disp, true));
+}
+
+} // namespace mozilla::gfx
diff --git a/gfx/thebes/XlibDisplay.h b/gfx/thebes/XlibDisplay.h
new file mode 100644
index 0000000000..0c0f8413a4
--- /dev/null
+++ b/gfx/thebes/XlibDisplay.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_XLIBDISPLAY_H
+#define GFX_XLIBDISPLAY_H
+
+#include <X11/Xlib.h>
+#include "X11UndefineNone.h"
+
+#include <memory>
+
+namespace mozilla::gfx {
+
+// Represents an X11 display connection which may be either borrowed
+// (e.g., from GTK) or owned; in the latter case it will be closed
+// with this object becomes unreferenced. See also the `EglDisplay`
+// class.
+class XlibDisplay final {
+ public:
+ ~XlibDisplay();
+
+ // Explicit `->get()` may be needed with some `Xlib.h` macros that
+ // expand to C-style pointer casts.
+ Display* get() const { return mDisplay; }
+ operator Display*() const { return mDisplay; }
+
+ static std::shared_ptr<XlibDisplay> Borrow(Display* aDisplay);
+ static std::shared_ptr<XlibDisplay> Open(const char* aDisplayName);
+
+ private:
+ Display* const mDisplay;
+ bool const mOwned;
+
+ XlibDisplay(Display*, bool);
+};
+
+} // namespace mozilla::gfx
+
+#endif // GFX_XLIBDISPLAY_H
diff --git a/gfx/thebes/cairo-xlib-utils.h b/gfx/thebes/cairo-xlib-utils.h
new file mode 100644
index 0000000000..0125ff12cb
--- /dev/null
+++ b/gfx/thebes/cairo-xlib-utils.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CAIROXLIBUTILS_H_
+#define CAIROXLIBUTILS_H_
+
+#include "cairo.h"
+#include <X11/Xlib.h>
+
+CAIRO_BEGIN_DECLS
+
+/**
+ * This callback encapsulates Xlib-based rendering. We assume that the
+ * execution of the callback is equivalent to compositing some RGBA image of
+ * size (bounds_width, bounds_height) onto the drawable at offset (offset_x,
+ * offset_y), clipped to the union of the clip_rects if num_rects is greater
+ * than zero. This includes the assumption that the same RGBA image
+ * is composited if you call the callback multiple times with the same closure,
+ * display and visual during a single cairo_draw_with_xlib call.
+ *
+ * @return True on success, False on non-recoverable error
+ */
+typedef cairo_bool_t (*cairo_xlib_drawing_callback)(
+ void* closure, Screen* screen, Drawable drawable, Visual* visual,
+ short offset_x, short offset_y, XRectangle* clip_rects,
+ unsigned int num_rects);
+
+/**
+ * This structure captures the result of the native drawing, in case the
+ * caller may wish to reapply the drawing efficiently later.
+ */
+typedef struct {
+ cairo_surface_t* surface;
+ cairo_bool_t uniform_alpha;
+ cairo_bool_t uniform_color;
+ double alpha; /* valid only if uniform_alpha is TRUE */
+ double r, g, b; /* valid only if uniform_color is TRUE */
+} cairo_xlib_drawing_result_t;
+
+/**
+ * This type specifies whether the native drawing callback draws all pixels
+ * in its bounds opaquely, independent of the contents of the target drawable,
+ * or whether it leaves pixels transparent/translucent or depends on the
+ * existing contents of the target drawable in some way.
+ */
+typedef enum _cairo_xlib_drawing_opacity {
+ CAIRO_XLIB_DRAWING_OPAQUE,
+ CAIRO_XLIB_DRAWING_TRANSPARENT
+} cairo_xlib_drawing_opacity_t;
+
+/**
+ * This type encodes the capabilities of the native drawing callback.
+ *
+ * If CAIRO_XLIB_DRAWING_SUPPORTS_OFFSET is set, 'offset_x' and 'offset_y'
+ * can be nonzero in the call to the callback; otherwise they will be zero.
+ *
+ * If CAIRO_XLIB_DRAWING_SUPPORTS_CLIP_RECT is set, then 'num_rects' can be
+ * zero or one in the call to the callback. If
+ * CAIRO_XLIB_DRAWING_SUPPORTS_CLIP_LIST is set, then 'num_rects' can be
+ * anything in the call to the callback. Otherwise 'num_rects' will be zero.
+ * Do not set both of these values.
+ *
+ * If CAIRO_XLIB_DRAWING_SUPPORTS_ALTERNATE_SCREEN is set, then 'screen' can
+ * be any screen on any display, otherwise it will be the default screen of
+ * the display passed into cairo_draw_with_xlib.
+ *
+ * If CAIRO_XLIB_DRAWING_SUPPORTS_NONDEFAULT_VISUAL is set, then 'visual' can be
+ * any visual, otherwise it will be equal to
+ * DefaultVisualOfScreen (screen).
+ */
+typedef enum {
+ CAIRO_XLIB_DRAWING_SUPPORTS_OFFSET = 0x01,
+ CAIRO_XLIB_DRAWING_SUPPORTS_CLIP_RECT = 0x02,
+ CAIRO_XLIB_DRAWING_SUPPORTS_CLIP_LIST = 0x04,
+ CAIRO_XLIB_DRAWING_SUPPORTS_ALTERNATE_SCREEN = 0x08,
+ CAIRO_XLIB_DRAWING_SUPPORTS_NONDEFAULT_VISUAL = 0x10
+} cairo_xlib_drawing_support_t;
+
+/**
+ * Draw Xlib output into any cairo context. All cairo transforms and effects
+ * are honored, including the current operator. This is equivalent to a
+ * cairo_set_source_surface and then cairo_paint.
+ * @param cr the context to draw into
+ * @param callback the code to perform Xlib rendering
+ * @param closure associated data
+ * @param dpy an X display to use in case the cairo context has no associated X
+ * display
+ * @param width the width of the putative image drawn by the callback
+ * @param height the height of the putative image drawn by the callback
+ * @param is_opaque set to CAIRO_XLIB_DRAWING_IS_OPAQUE to indicate
+ * that all alpha values of the putative image will be 1.0; the pixels drawn
+ * into the Drawable must not depend on the prior contents of the Drawable in
+ * any way
+ * @param capabilities the capabilities of the callback as described above.
+ * @param result if non-NULL, we *may* fill in the struct with information about
+ * the rendered image. 'surface' may be filled in with a surface representing
+ * the image, similar to the target of 'cr'. If 'uniform_alpha' is True then
+ * every pixel of the image has the same alpha value 'alpha'. If
+ * 'uniform_color' is True then every pixel of the image has the same RGB
+ * color (r, g, b). If the image has uniform color and alpha (or alpha is zero,
+ * in which case the color is always uniform) then we won't bother returning
+ * a surface for it.
+ */
+void cairo_draw_with_xlib(cairo_t* cr, cairo_xlib_drawing_callback callback,
+ void* closure, Display* dpy, unsigned int width,
+ unsigned int height,
+ cairo_xlib_drawing_opacity_t is_opaque,
+ cairo_xlib_drawing_support_t capabilities,
+ cairo_xlib_drawing_result_t* result);
+
+CAIRO_END_DECLS
+
+#endif /*CAIROXLIBUTILS_H_*/
diff --git a/gfx/thebes/d3dkmtQueryStatistics.h b/gfx/thebes/d3dkmtQueryStatistics.h
new file mode 100644
index 0000000000..d00b706966
--- /dev/null
+++ b/gfx/thebes/d3dkmtQueryStatistics.h
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* This file is based on a header file that was briefly seen in the
+ * Windows 8 RC SDK. The work for this file itself was based on the one in
+ * ProcessHacker at
+ * http://processhacker.svn.sourceforge.net/viewvc/processhacker/2.x/trunk/plugins/ExtendedTools/d3dkmt.h?revision=4758&view=markup
+ * For more details see Mozilla Bug 689870.
+ * [Bug 917496 indicates that some of these structs may not match reality, and
+ * therefore should not be trusted. See the reference to bug 917496 in
+ * gfxWindowsPlatform.cpp.]
+ */
+
+typedef struct _D3DKMTQS_COUNTER {
+ ULONG Count;
+ ULONGLONG Bytes;
+} D3DKMTQS_COUNTER;
+
+typedef struct _D3DKMTQS_ADAPTER_INFO {
+ ULONG NbSegments;
+ ULONG NodeCount;
+
+ ULONG Filler[3];
+ ULONGLONG Filler2[2]; // Assumed sizeof(LONGLONG) = sizeof(ULONGLONG)
+ struct {
+ ULONG Filler[14];
+ } Filler_RDMAB;
+ struct {
+ ULONG Filler[9];
+ } Filler_R;
+ struct {
+ ULONG Filler[4];
+ D3DKMTQS_COUNTER Filler2;
+ } Filler_P;
+ struct {
+ D3DKMTQS_COUNTER Filler[16];
+ ULONG Filler2[2];
+ } Filler_PF;
+ struct {
+ ULONGLONG Filler[8];
+ } Filler_PT;
+ struct {
+ ULONG Filler[2];
+ } Filler_SR;
+ struct {
+ ULONG Filler[7];
+ } Filler_L;
+ struct {
+ D3DKMTQS_COUNTER Filler[7];
+ } Filler_A;
+ struct {
+ D3DKMTQS_COUNTER Filler[4];
+ } Filler_T;
+ ULONG64 Reserved[8];
+} D3DKMTQS_ADAPTER_INFO;
+
+typedef struct _D3DKMTQS_SEGMENT_INFO_WIN7 {
+ ULONG Filler[3];
+ struct {
+ ULONGLONG Filler;
+ ULONG Filler2[2];
+ } Filler_M;
+
+ ULONG Aperture;
+
+ ULONGLONG Filler3[5];
+ ULONG64 Filler4[8];
+} D3DKMTQS_SEGMENT_INFO_WIN7;
+
+typedef struct _D3DKMTQS_SEGMENT_INFO_WIN8 {
+ ULONGLONG Filler[3];
+ struct {
+ ULONGLONG Filler;
+ ULONG Filler2[2];
+ } Filler_M;
+
+ ULONG Aperture;
+
+ ULONGLONG Filler3[5];
+ ULONG64 Filler4[8];
+} D3DKMTQS_SEGMENT_INFO_WIN8;
+
+typedef struct _D3DKMTQS_SYSTEM_MEMORY {
+ ULONGLONG BytesAllocated;
+ ULONG Filler[2];
+ ULONGLONG Filler2[7];
+} D3DKMTQS_SYSTEM_MEMORY;
+
+typedef struct _D3DKMTQS_PROCESS_INFO {
+ ULONG Filler[2];
+ struct {
+ ULONGLONG BytesAllocated;
+
+ ULONG Filler[2];
+ ULONGLONG Filler2[7];
+ } SystemMemory;
+ ULONG64 Reserved[8];
+} D3DKMTQS_PROCESS_INFO;
+
+typedef struct _D3DKMTQS_PROCESS_SEGMENT_INFO {
+ union {
+ struct {
+ ULONGLONG BytesCommitted;
+ } Win8;
+ struct {
+ ULONG BytesCommitted;
+ ULONG UnknownRandomness;
+ } Win7;
+ };
+
+ ULONGLONG Filler[2];
+ ULONG Filler2;
+ struct {
+ ULONG Filler;
+ D3DKMTQS_COUNTER Filler2[6];
+ ULONGLONG Filler3;
+ } Filler3;
+ struct {
+ ULONGLONG Filler;
+ } Filler4;
+ ULONG64 Reserved[8];
+} D3DKMTQS_PROCESS_SEGMENT_INFO;
+
+typedef struct _D3DKMTQS_PROCESS_NODE_INFO {
+ LARGE_INTEGER RunningTime; // 100ns
+ ULONG ContextSwitch;
+ ULONG PreemptionStatistics[16];
+ ULONG PacketStatistics[32];
+ ULONG64 Reserved[8];
+} D3DKMTQS_PROCESS_NODE_INFO;
+
+typedef enum _D3DKMTQS_TYPE {
+ D3DKMTQS_ADAPTER = 0,
+ D3DKMTQS_PROCESS = 1,
+ D3DKMTQS_SEGMENT = 3,
+ D3DKMTQS_PROCESS_SEGMENT = 4,
+ D3DKMTQS_PROCESS_NODE = 6,
+} D3DKMTQS_TYPE;
+
+typedef union _D3DKMTQS_RESULT {
+ D3DKMTQS_ADAPTER_INFO AdapterInfo;
+ D3DKMTQS_SEGMENT_INFO_WIN7 SegmentInfoWin7;
+ D3DKMTQS_SEGMENT_INFO_WIN8 SegmentInfoWin8;
+ D3DKMTQS_PROCESS_INFO ProcessInfo;
+ D3DKMTQS_PROCESS_SEGMENT_INFO ProcessSegmentInfo;
+ D3DKMTQS_PROCESS_NODE_INFO ProcessNodeInformation;
+} D3DKMTQS_RESULT;
+
+typedef struct _D3DKMTQS_QUERY_SEGMENT {
+ ULONG SegmentId;
+} D3DKMTQS_QUERY_SEGMENT;
+
+typedef struct _D3DKMTQS_QUERY_NODE {
+ ULONG NodeId;
+} D3DKMTQS_QUERY_NODE;
+
+typedef struct _D3DKMTQS {
+ D3DKMTQS_TYPE Type;
+ LUID AdapterLuid;
+ HANDLE hProcess;
+ D3DKMTQS_RESULT QueryResult;
+
+ union {
+ D3DKMTQS_QUERY_SEGMENT QuerySegment;
+ D3DKMTQS_QUERY_SEGMENT QueryProcessSegment;
+ D3DKMTQS_QUERY_NODE QueryProcessNode;
+ };
+} D3DKMTQS;
+
+extern "C" {
+typedef __checkReturn NTSTATUS(APIENTRY* PFND3DKMTQS)(const D3DKMTQS*);
+}
diff --git a/gfx/thebes/genLanguageTagList.pl b/gfx/thebes/genLanguageTagList.pl
new file mode 100644
index 0000000000..0fa6c65f82
--- /dev/null
+++ b/gfx/thebes/genLanguageTagList.pl
@@ -0,0 +1,86 @@
+#!/usr/bin/env perl
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This tool is used to prepare a list of valid language subtags from the
+# IANA registry (http://www.iana.org/assignments/language-subtag-registry).
+
+# Run as
+#
+# perl genLanguageTagList.pl language-subtag-registry > gfxLanguageTagList.cpp
+#
+# where language-subtag-registry is a copy of the IANA registry file.
+
+use strict;
+
+my $timestamp = gmtime();
+print <<__END;
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Derived from the IANA language subtag registry by genLanguageTagList.pl.
+ *
+ * Created on $timestamp.
+ *
+ * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * *
+ */
+
+__END
+
+my $isLanguage = 0;
+
+while (<>) {
+ # strip leading/trailing whitespace, if any
+ chomp;
+ s/^\s+//;
+ s/\s+$//;
+
+ # assume File-Date precedes the actual list;
+ # record the date, and begin assignment to an array of valid tags
+ if (m/^File-Date:\s*(.+)$/) {
+ print "// Based on IANA registry dated $1\n\n";
+ print "static const uint32_t sLanguageTagList[] = {";
+ next;
+ }
+
+ if (m/^%%/) {
+ $isLanguage = 0;
+ next;
+ }
+
+ # we only care about records of type 'language'
+ if (m/^Type:\s*(.+)$/) {
+ $isLanguage = ($1 eq 'language');
+ next;
+ }
+
+ # append the tag to our string, with ";" as a delimiter
+ if ($isLanguage && m/^Subtag:\s*([a-z]{2,3})\s*$/) {
+ my $tagstr = $1;
+ print "\n TRUETYPE_TAG(",
+ join(",", map { $_ eq " " ? " 0 " : "'" . $_ . "'" } split(//, substr($tagstr . " ", 0, 4))),
+ "), // ", $tagstr;
+ next;
+ }
+
+ if ($isLanguage && m/^Description:\s*(.+)$/) {
+ print " = $1";
+ $isLanguage = 0; # only print first Description field
+ next;
+ }
+}
+
+# at end of file, terminate our assignment to the array of tags
+print <<__END;
+
+ 0x0 // end of language code list
+};
+
+/*
+ * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * *
+ */
+__END
diff --git a/gfx/thebes/gencjkcisvs.py b/gfx/thebes/gencjkcisvs.py
new file mode 100644
index 0000000000..1797f2be6c
--- /dev/null
+++ b/gfx/thebes/gencjkcisvs.py
@@ -0,0 +1,88 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os.path
+import re
+import sys
+
+f = open(sys.argv[1] if len(sys.argv) > 1 else "StandardizedVariants.txt")
+
+line = f.readline()
+m = re.compile("^# (StandardizedVariants(-\d+(\.\d+)*)?\.txt)").search(line)
+fileversion = m.group(1)
+vsdict = {}
+r = re.compile(
+ "^([0-9A-F]{4,6}) (FE0[0-9A-F]); CJK COMPATIBILITY IDEOGRAPH-([0-9A-F]{4,6});"
+)
+while True:
+ line = f.readline()
+ if not line:
+ break
+ if "CJK COMPATIBILITY IDEOGRAPH-" not in line:
+ continue
+
+ m = r.search(line)
+ unified = int(m.group(1), 16)
+ vs = int(m.group(2), 16)
+ compat = int(m.group(3), 16)
+
+ if vs not in vsdict:
+ vsdict[vs] = {}
+ vsdict[vs][unified] = compat
+
+f.close
+
+offsets = []
+length = 10 + 11 * len(vsdict)
+for (k, mappings) in sorted(vsdict.items()):
+ offsets.append(length)
+ length += 4 + 5 * len(mappings)
+
+f = open(sys.argv[2] if len(sys.argv) > 2 else "CJKCompatSVS.cpp", "wb")
+f.write(
+ """// Generated by %s. Do not edit.
+
+#include <stdint.h>
+
+#define U16(v) (((v) >> 8) & 0xFF), ((v) & 0xFF)
+#define U24(v) (((v) >> 16) & 0xFF), (((v) >> 8) & 0xFF), ((v) & 0xFF)
+#define U32(v) (((v) >> 24) & 0xFF), (((v) >> 16) & 0xFF), (((v) >> 8) & 0xFF), ((v) & 0xFF)
+#define GLYPH(v) U16(v >= 0x2F800 ? (v) - (0x2F800 - 0xFB00) : (v))
+
+// Fallback mappings for CJK Compatibility Ideographs Standardized Variants
+// taken from %s.
+// Using OpenType format 14 cmap subtable structure to reuse the lookup code
+// for fonts. The glyphID field is used to store the corresponding codepoints
+// CJK Compatibility Ideographs. To fit codepoints into the 16-bit glyphID
+// field, CJK Compatibility Ideographs Supplement (U+2F800..U+2FA1F) will be
+// mapped to 0xFB00..0xFD1F.
+extern const uint8_t sCJKCompatSVSTable[] = {
+"""
+ % (os.path.basename(sys.argv[0]), fileversion)
+)
+f.write(" U16(14), // format\n")
+f.write(" U32(%d), // length\n" % length)
+f.write(" U32(%d), // numVarSelectorRecords\n" % len(vsdict))
+for i, k in enumerate(sorted(vsdict.keys())):
+ f.write(
+ " U24(0x%04X), U32(0), U32(%d), // varSelectorRecord[%d]\n"
+ % (k, offsets[i], i)
+ )
+for (k, mappings) in sorted(vsdict.items()):
+ f.write(" // 0x%04X\n" % k)
+ f.write(" U32(%d), // numUVSMappings\n" % len(mappings))
+ for (unified, compat) in sorted(mappings.items()):
+ f.write(" U24(0x%04X), GLYPH(0x%04X),\n" % (unified, compat))
+f.write(
+ """};
+
+#undef U16
+#undef U24
+#undef U32
+#undef GLYPH
+
+static_assert(sizeof sCJKCompatSVSTable == %d, "Table generator has a bug.");
+"""
+ % length
+)
diff --git a/gfx/thebes/gfx2DGlue.h b/gfx/thebes/gfx2DGlue.h
new file mode 100644
index 0000000000..578d742502
--- /dev/null
+++ b/gfx/thebes/gfx2DGlue.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_2D_GLUE_H
+#define GFX_2D_GLUE_H
+
+#include "gfxMatrix.h"
+#include "gfxPoint.h"
+#include "gfxRect.h"
+#include "gfxTypes.h"
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/gfx/Types.h"
+
+namespace mozilla {
+namespace gfx {
+
+inline Rect ToRect(const gfxRect& aRect) {
+ return Rect(Float(aRect.X()), Float(aRect.Y()), Float(aRect.Width()),
+ Float(aRect.Height()));
+}
+
+inline RectDouble ToRectDouble(const gfxRect& aRect) {
+ return RectDouble(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height());
+}
+
+inline Matrix ToMatrix(const gfxMatrix& aMatrix) {
+ return Matrix(Float(aMatrix._11), Float(aMatrix._12), Float(aMatrix._21),
+ Float(aMatrix._22), Float(aMatrix._31), Float(aMatrix._32));
+}
+
+inline gfxMatrix ThebesMatrix(const Matrix& aMatrix) {
+ return gfxMatrix(aMatrix._11, aMatrix._12, aMatrix._21, aMatrix._22,
+ aMatrix._31, aMatrix._32);
+}
+
+inline Point ToPoint(const gfxPoint& aPoint) {
+ return Point(Float(aPoint.x), Float(aPoint.y));
+}
+
+inline Size ToSize(const gfxSize& aSize) {
+ return Size(Float(aSize.width), Float(aSize.height));
+}
+
+inline gfxPoint ThebesPoint(const Point& aPoint) {
+ return gfxPoint(aPoint.x, aPoint.y);
+}
+
+inline gfxSize ThebesSize(const Size& aSize) {
+ return gfxSize(aSize.width, aSize.height);
+}
+
+inline gfxRect ThebesRect(const Rect& aRect) {
+ return gfxRect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height());
+}
+
+inline gfxRect ThebesRect(const IntRect& aRect) {
+ return gfxRect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height());
+}
+
+inline gfxRect ThebesRect(const RectDouble& aRect) {
+ return gfxRect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height());
+}
+
+inline gfxImageFormat SurfaceFormatToImageFormat(SurfaceFormat aFormat) {
+ switch (aFormat) {
+ case SurfaceFormat::B8G8R8A8:
+ return SurfaceFormat::A8R8G8B8_UINT32;
+ case SurfaceFormat::B8G8R8X8:
+ return SurfaceFormat::X8R8G8B8_UINT32;
+ case SurfaceFormat::R5G6B5_UINT16:
+ return SurfaceFormat::R5G6B5_UINT16;
+ case SurfaceFormat::A8:
+ return SurfaceFormat::A8;
+ default:
+ return SurfaceFormat::UNKNOWN;
+ }
+}
+
+inline SurfaceFormat ImageFormatToSurfaceFormat(gfxImageFormat aFormat) {
+ switch (aFormat) {
+ case SurfaceFormat::A8R8G8B8_UINT32:
+ return SurfaceFormat::B8G8R8A8;
+ case SurfaceFormat::X8R8G8B8_UINT32:
+ return SurfaceFormat::B8G8R8X8;
+ case SurfaceFormat::R5G6B5_UINT16:
+ return SurfaceFormat::R5G6B5_UINT16;
+ case SurfaceFormat::A8:
+ return SurfaceFormat::A8;
+ default:
+ case SurfaceFormat::UNKNOWN:
+ return SurfaceFormat::B8G8R8A8;
+ }
+}
+
+inline gfxContentType ContentForFormat(const SurfaceFormat& aFormat) {
+ switch (aFormat) {
+ case SurfaceFormat::R5G6B5_UINT16:
+ case SurfaceFormat::B8G8R8X8:
+ case SurfaceFormat::R8G8B8X8:
+ return gfxContentType::COLOR;
+ case SurfaceFormat::A8:
+ return gfxContentType::ALPHA;
+ case SurfaceFormat::B8G8R8A8:
+ case SurfaceFormat::R8G8B8A8:
+ default:
+ return gfxContentType::COLOR_ALPHA;
+ }
+}
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif
diff --git a/gfx/thebes/gfxASurface.cpp b/gfx/thebes/gfxASurface.cpp
new file mode 100644
index 0000000000..f77c836fb9
--- /dev/null
+++ b/gfx/thebes/gfxASurface.cpp
@@ -0,0 +1,503 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIMemoryReporter.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Base64.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/HelpersCairo.h"
+#include "gfx2DGlue.h"
+
+#include "gfxASurface.h"
+#include "gfxContext.h"
+#include "gfxImageSurface.h"
+#include "gfxPlatform.h"
+#include "gfxRect.h"
+
+#include "cairo.h"
+#include <algorithm>
+
+#ifdef CAIRO_HAS_WIN32_SURFACE
+# include "gfxWindowsSurface.h"
+#endif
+
+#ifdef MOZ_X11
+# include "gfxXlibSurface.h"
+#endif
+
+#ifdef CAIRO_HAS_QUARTZ_SURFACE
+# include "gfxQuartzSurface.h"
+#endif
+
+#include <stdio.h>
+#include <limits.h>
+
+#include "nsComponentManagerUtils.h"
+#include "nsISupportsUtils.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+static cairo_user_data_key_t gfxasurface_pointer_key;
+
+gfxASurface::gfxASurface()
+ : mSurface(nullptr),
+ mFloatingRefs(0),
+ mBytesRecorded(0),
+ mSurfaceValid(false) {
+ MOZ_COUNT_CTOR(gfxASurface);
+}
+
+gfxASurface::~gfxASurface() {
+ RecordMemoryFreed();
+
+ MOZ_COUNT_DTOR(gfxASurface);
+}
+
+// Surfaces use refcounting that's tied to the cairo surface refcnt, to avoid
+// refcount mismatch issues.
+nsrefcnt gfxASurface::AddRef(void) {
+ if (mSurfaceValid) {
+ if (mFloatingRefs) {
+ // eat a floating ref
+ mFloatingRefs--;
+ } else {
+ cairo_surface_reference(mSurface);
+ }
+
+ return (nsrefcnt)cairo_surface_get_reference_count(mSurface);
+ }
+ // the surface isn't valid, but we still need to refcount
+ // the gfxASurface
+ return ++mFloatingRefs;
+}
+
+nsrefcnt gfxASurface::Release(void) {
+ if (mSurfaceValid) {
+ NS_ASSERTION(
+ mFloatingRefs == 0,
+ "gfxASurface::Release with floating refs still hanging around!");
+
+ // Note that there is a destructor set on user data for mSurface,
+ // which will delete this gfxASurface wrapper when the surface's refcount
+ // goes out of scope.
+ nsrefcnt refcnt = (nsrefcnt)cairo_surface_get_reference_count(mSurface);
+ cairo_surface_destroy(mSurface);
+
+ // |this| may not be valid any more, don't use it!
+
+ return --refcnt;
+ }
+ if (--mFloatingRefs == 0) {
+ delete this;
+ return 0;
+ }
+ return mFloatingRefs;
+}
+
+void gfxASurface::SurfaceDestroyFunc(void* data) {
+ gfxASurface* surf = (gfxASurface*)data;
+ // fprintf (stderr, "Deleting wrapper for %p (wrapper: %p)\n", surf->mSurface,
+ // data);
+ delete surf;
+}
+
+gfxASurface* gfxASurface::GetSurfaceWrapper(cairo_surface_t* csurf) {
+ if (!csurf) return nullptr;
+ return (gfxASurface*)cairo_surface_get_user_data(csurf,
+ &gfxasurface_pointer_key);
+}
+
+void gfxASurface::SetSurfaceWrapper(cairo_surface_t* csurf,
+ gfxASurface* asurf) {
+ if (!csurf) return;
+ cairo_surface_set_user_data(csurf, &gfxasurface_pointer_key, asurf,
+ SurfaceDestroyFunc);
+}
+
+already_AddRefed<gfxASurface> gfxASurface::Wrap(cairo_surface_t* csurf,
+ const IntSize& aSize) {
+ RefPtr<gfxASurface> result;
+
+ /* Do we already have a wrapper for this surface? */
+ result = GetSurfaceWrapper(csurf);
+ if (result) {
+ // fprintf(stderr, "Existing wrapper for %p -> %p\n", csurf, result);
+ return result.forget();
+ }
+
+ /* No wrapper; figure out the surface type and create it */
+ cairo_surface_type_t stype = cairo_surface_get_type(csurf);
+
+ if (stype == CAIRO_SURFACE_TYPE_IMAGE) {
+ result = new gfxImageSurface(csurf);
+ }
+#ifdef CAIRO_HAS_WIN32_SURFACE
+ else if (stype == CAIRO_SURFACE_TYPE_WIN32 ||
+ stype == CAIRO_SURFACE_TYPE_WIN32_PRINTING) {
+ result = new gfxWindowsSurface(csurf);
+ }
+#endif
+#ifdef MOZ_X11
+ else if (stype == CAIRO_SURFACE_TYPE_XLIB) {
+ result = new gfxXlibSurface(csurf);
+ }
+#endif
+#ifdef CAIRO_HAS_QUARTZ_SURFACE
+ else if (stype == CAIRO_SURFACE_TYPE_QUARTZ) {
+ result = new gfxQuartzSurface(csurf, aSize);
+ }
+#endif
+ else {
+ result = new gfxUnknownSurface(csurf, aSize);
+ }
+
+ // fprintf(stderr, "New wrapper for %p -> %p\n", csurf, result);
+
+ return result.forget();
+}
+
+void gfxASurface::Init(cairo_surface_t* surface, bool existingSurface) {
+ SetSurfaceWrapper(surface, this);
+ MOZ_ASSERT(surface, "surface should be a valid pointer");
+
+ mSurface = surface;
+ mSurfaceValid = !cairo_surface_status(surface);
+ if (!mSurfaceValid) {
+ gfxWarning() << "ASurface Init failed with Cairo status "
+ << cairo_surface_status(surface) << " on " << hexa(surface);
+ }
+
+ if (existingSurface || !mSurfaceValid) {
+ mFloatingRefs = 0;
+ } else {
+ mFloatingRefs = 1;
+ if (cairo_surface_get_content(surface) != CAIRO_CONTENT_COLOR) {
+ cairo_surface_set_subpixel_antialiasing(
+ surface, CAIRO_SUBPIXEL_ANTIALIASING_DISABLED);
+ }
+ }
+}
+
+gfxSurfaceType gfxASurface::GetType() const {
+ if (!mSurfaceValid) return (gfxSurfaceType)-1;
+
+ return (gfxSurfaceType)cairo_surface_get_type(mSurface);
+}
+
+gfxContentType gfxASurface::GetContentType() const {
+ if (!mSurfaceValid) return (gfxContentType)-1;
+
+ return (gfxContentType)cairo_surface_get_content(mSurface);
+}
+
+void gfxASurface::SetDeviceOffset(const gfxPoint& offset) {
+ if (!mSurfaceValid) return;
+ cairo_surface_set_device_offset(mSurface, offset.x, offset.y);
+}
+
+gfxPoint gfxASurface::GetDeviceOffset() const {
+ if (!mSurfaceValid) return gfxPoint(0.0, 0.0);
+ gfxPoint pt;
+ cairo_surface_get_device_offset(mSurface, &pt.x.value, &pt.y.value);
+ return pt;
+}
+
+void gfxASurface::Flush() const {
+ if (!mSurfaceValid) return;
+ cairo_surface_flush(mSurface);
+ gfxPlatform::ClearSourceSurfaceForSurface(const_cast<gfxASurface*>(this));
+}
+
+void gfxASurface::MarkDirty() {
+ if (!mSurfaceValid) return;
+ cairo_surface_mark_dirty(mSurface);
+ gfxPlatform::ClearSourceSurfaceForSurface(this);
+}
+
+void gfxASurface::MarkDirty(const gfxRect& r) {
+ if (!mSurfaceValid) return;
+ cairo_surface_mark_dirty_rectangle(mSurface, (int)r.X(), (int)r.Y(),
+ (int)r.Width(), (int)r.Height());
+ gfxPlatform::ClearSourceSurfaceForSurface(this);
+}
+
+void gfxASurface::SetData(const cairo_user_data_key_t* key, void* user_data,
+ thebes_destroy_func_t destroy) {
+ if (!mSurfaceValid) return;
+ cairo_surface_set_user_data(mSurface, key, user_data, destroy);
+}
+
+void* gfxASurface::GetData(const cairo_user_data_key_t* key) {
+ if (!mSurfaceValid) return nullptr;
+ return cairo_surface_get_user_data(mSurface, key);
+}
+
+void gfxASurface::Finish() {
+ // null surfaces are allowed here
+ cairo_surface_finish(mSurface);
+}
+
+already_AddRefed<gfxImageSurface> gfxASurface::CopyToARGB32ImageSurface() {
+ if (!mSurface || !mSurfaceValid) {
+ return nullptr;
+ }
+
+ const IntSize size = GetSize();
+ RefPtr<gfxImageSurface> imgSurface =
+ new gfxImageSurface(size, SurfaceFormat::A8R8G8B8_UINT32);
+
+ RefPtr<DrawTarget> dt = gfxPlatform::CreateDrawTargetForSurface(
+ imgSurface, IntSize(size.width, size.height));
+ RefPtr<SourceSurface> source =
+ gfxPlatform::GetSourceSurfaceForSurface(dt, this);
+
+ dt->CopySurface(source, IntRect(0, 0, size.width, size.height), IntPoint());
+
+ return imgSurface.forget();
+}
+
+int gfxASurface::CairoStatus() {
+ if (!mSurfaceValid) return -1;
+
+ return cairo_surface_status(mSurface);
+}
+
+nsresult gfxASurface::BeginPrinting(const nsAString& aTitle,
+ const nsAString& aPrintToFileName) {
+ return NS_OK;
+}
+
+nsresult gfxASurface::EndPrinting() { return NS_OK; }
+
+nsresult gfxASurface::AbortPrinting() { return NS_OK; }
+
+nsresult gfxASurface::BeginPage() { return NS_OK; }
+
+nsresult gfxASurface::EndPage() { return NS_OK; }
+
+gfxContentType gfxASurface::ContentFromFormat(gfxImageFormat format) {
+ switch (format) {
+ case SurfaceFormat::A8R8G8B8_UINT32:
+ return gfxContentType::COLOR_ALPHA;
+ case SurfaceFormat::X8R8G8B8_UINT32:
+ case SurfaceFormat::R5G6B5_UINT16:
+ return gfxContentType::COLOR;
+ case SurfaceFormat::A8:
+ return gfxContentType::ALPHA;
+
+ case SurfaceFormat::UNKNOWN:
+ default:
+ return gfxContentType::COLOR;
+ }
+}
+
+int32_t gfxASurface::BytePerPixelFromFormat(gfxImageFormat format) {
+ switch (format) {
+ case SurfaceFormat::A8R8G8B8_UINT32:
+ case SurfaceFormat::X8R8G8B8_UINT32:
+ return 4;
+ case SurfaceFormat::R5G6B5_UINT16:
+ return 2;
+ case SurfaceFormat::A8:
+ return 1;
+ default:
+ NS_WARNING("Unknown byte per pixel value for Image format");
+ }
+ return 0;
+}
+
+/** Memory reporting **/
+
+static const char* sDefaultSurfaceDescription =
+ "Memory used by gfx surface of the given type.";
+
+struct SurfaceMemoryReporterAttrs {
+ const char* path;
+ const char* description;
+};
+
+static const SurfaceMemoryReporterAttrs sSurfaceMemoryReporterAttrs[] = {
+ {"gfx-surface-image", nullptr},
+ {"gfx-surface-pdf", nullptr},
+ {"gfx-surface-ps", nullptr},
+ {"gfx-surface-xlib",
+ "Memory used by xlib surfaces to store pixmaps. This memory lives in "
+ "the X server's process rather than in this application, so the bytes "
+ "accounted for here aren't counted in vsize, resident, explicit, or any "
+ "of "
+ "the other measurements on this page."},
+ {"gfx-surface-xcb", nullptr},
+ {"gfx-surface-glitz???", nullptr}, // should never be used
+ {"gfx-surface-quartz", nullptr},
+ {"gfx-surface-win32", nullptr},
+ {"gfx-surface-beos", nullptr},
+ {"gfx-surface-directfb???", nullptr}, // should never be used
+ {"gfx-surface-svg", nullptr},
+ {"gfx-surface-os2", nullptr},
+ {"gfx-surface-win32printing", nullptr},
+ {"gfx-surface-quartzimage", nullptr},
+ {"gfx-surface-script", nullptr},
+ {"gfx-surface-qpainter", nullptr},
+ {"gfx-surface-recording", nullptr},
+ {"gfx-surface-vg", nullptr},
+ {"gfx-surface-gl", nullptr},
+ {"gfx-surface-drm", nullptr},
+ {"gfx-surface-tee", nullptr},
+ {"gfx-surface-xml", nullptr},
+ {"gfx-surface-skia", nullptr},
+ {"gfx-surface-subsurface", nullptr},
+};
+
+static_assert(MOZ_ARRAY_LENGTH(sSurfaceMemoryReporterAttrs) ==
+ size_t(gfxSurfaceType::Max),
+ "sSurfaceMemoryReporterAttrs exceeds max capacity");
+static_assert(uint32_t(CAIRO_SURFACE_TYPE_SKIA) ==
+ uint32_t(gfxSurfaceType::Skia),
+ "CAIRO_SURFACE_TYPE_SKIA not equal to gfxSurfaceType::Skia");
+
+/* Surface size memory reporting */
+
+class SurfaceMemoryReporter final : public nsIMemoryReporter {
+ ~SurfaceMemoryReporter() = default;
+
+ // We can touch this array on several different threads, and we don't
+ // want to introduce memory barriers when recording the memory used. To
+ // assure dynamic race checkers like TSan that this is OK, we use
+ // relaxed memory ordering here.
+ static Atomic<size_t, Relaxed>
+ sSurfaceMemoryUsed[size_t(gfxSurfaceType::Max)];
+
+ public:
+ static void AdjustUsedMemory(gfxSurfaceType aType, int32_t aBytes) {
+ // A read-modify-write operation like += would require a memory barrier
+ // here, which would defeat the purpose of using relaxed memory
+ // ordering. So separate out the read and write operations.
+ sSurfaceMemoryUsed[size_t(aType)] =
+ sSurfaceMemoryUsed[size_t(aType)] + aBytes;
+ };
+
+ // This memory reporter is sometimes allocated on the compositor thread,
+ // but always released on the main thread, so its refcounting needs to be
+ // threadsafe.
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ const size_t len = ArrayLength(sSurfaceMemoryReporterAttrs);
+ for (size_t i = 0; i < len; i++) {
+ int64_t amount = sSurfaceMemoryUsed[i];
+
+ if (amount != 0) {
+ const char* path = sSurfaceMemoryReporterAttrs[i].path;
+ const char* desc = sSurfaceMemoryReporterAttrs[i].description;
+ if (!desc) {
+ desc = sDefaultSurfaceDescription;
+ }
+
+ aHandleReport->Callback(""_ns, nsCString(path), KIND_OTHER, UNITS_BYTES,
+ amount, nsCString(desc), aData);
+ }
+ }
+
+ return NS_OK;
+ }
+};
+
+Atomic<size_t, Relaxed>
+ SurfaceMemoryReporter::sSurfaceMemoryUsed[size_t(gfxSurfaceType::Max)];
+
+NS_IMPL_ISUPPORTS(SurfaceMemoryReporter, nsIMemoryReporter)
+
+void gfxASurface::RecordMemoryUsedForSurfaceType(gfxSurfaceType aType,
+ int32_t aBytes) {
+ if (int(aType) < 0 || aType >= gfxSurfaceType::Max) {
+ NS_WARNING("Invalid type to RecordMemoryUsedForSurfaceType!");
+ return;
+ }
+
+ static bool registered = false;
+ if (!registered) {
+ RegisterStrongMemoryReporter(new SurfaceMemoryReporter());
+ registered = true;
+ }
+
+ SurfaceMemoryReporter::AdjustUsedMemory(aType, aBytes);
+}
+
+void gfxASurface::RecordMemoryUsed(int32_t aBytes) {
+ RecordMemoryUsedForSurfaceType(GetType(), aBytes);
+ mBytesRecorded += aBytes;
+}
+
+void gfxASurface::RecordMemoryFreed() {
+ if (mBytesRecorded) {
+ RecordMemoryUsedForSurfaceType(GetType(), -mBytesRecorded);
+ mBytesRecorded = 0;
+ }
+}
+
+size_t gfxASurface::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ // We don't measure mSurface because cairo doesn't allow it.
+ return 0;
+}
+
+size_t gfxASurface::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+/* static */
+uint8_t gfxASurface::BytesPerPixel(gfxImageFormat aImageFormat) {
+ switch (aImageFormat) {
+ case SurfaceFormat::A8R8G8B8_UINT32:
+ return 4;
+ case SurfaceFormat::X8R8G8B8_UINT32:
+ return 4;
+ case SurfaceFormat::R5G6B5_UINT16:
+ return 2;
+ case SurfaceFormat::A8:
+ return 1;
+ case SurfaceFormat::UNKNOWN:
+ default:
+ MOZ_ASSERT_UNREACHABLE("Not really sure what you want me to say here");
+ return 0;
+ }
+}
+
+void gfxASurface::SetOpaqueRect(const gfxRect& aRect) {
+ if (aRect.IsEmpty()) {
+ mOpaqueRect = nullptr;
+ } else if (!!mOpaqueRect) {
+ *mOpaqueRect = aRect;
+ } else {
+ mOpaqueRect = MakeUnique<gfxRect>(aRect);
+ }
+}
+
+/* static */ const gfxRect& gfxASurface::GetEmptyOpaqueRect() {
+ static const gfxRect empty(0, 0, 0, 0);
+ return empty;
+}
+
+const IntSize gfxASurface::GetSize() const { return IntSize(-1, -1); }
+
+SurfaceFormat gfxASurface::GetSurfaceFormat() const {
+ if (!mSurfaceValid) {
+ return SurfaceFormat::UNKNOWN;
+ }
+ return GfxFormatForCairoSurface(mSurface);
+}
+
+already_AddRefed<gfxImageSurface> gfxASurface::GetAsImageSurface() {
+ return nullptr;
+}
diff --git a/gfx/thebes/gfxASurface.h b/gfx/thebes/gfxASurface.h
new file mode 100644
index 0000000000..d2e6daf771
--- /dev/null
+++ b/gfx/thebes/gfxASurface.h
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_ASURFACE_H
+#define GFX_ASURFACE_H
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/UniquePtr.h"
+
+#include "gfxPoint.h"
+#include "gfxRect.h"
+#include "gfxTypes.h"
+#include "nscore.h"
+#include "nsSize.h"
+#include "mozilla/gfx/Rect.h"
+
+#include "nsStringFwd.h"
+
+class gfxImageSurface;
+
+template <typename T>
+struct already_AddRefed;
+
+/**
+ * A surface is something you can draw on. Instantiate a subclass of this
+ * abstract class, and use gfxContext to draw on this surface.
+ */
+class gfxASurface {
+ public:
+#ifdef MOZILLA_INTERNAL_API
+ nsrefcnt AddRef(void);
+ nsrefcnt Release(void);
+#else
+ virtual nsrefcnt AddRef(void);
+ virtual nsrefcnt Release(void);
+#endif
+
+ public:
+ /** Wrap the given cairo surface and return a gfxASurface for it.
+ * This adds a reference to csurf (owned by the returned gfxASurface).
+ */
+ static already_AddRefed<gfxASurface> Wrap(
+ cairo_surface_t* csurf,
+ const mozilla::gfx::IntSize& aSize = mozilla::gfx::IntSize(-1, -1));
+
+ /*** this DOES NOT addref the surface */
+ cairo_surface_t* CairoSurface() { return mSurface; }
+
+ gfxSurfaceType GetType() const;
+
+ gfxContentType GetContentType() const;
+
+ void SetDeviceOffset(const gfxPoint& offset);
+ gfxPoint GetDeviceOffset() const;
+
+ void Flush() const;
+ void MarkDirty();
+ void MarkDirty(const gfxRect& r);
+
+ /* Printing backend functions */
+ virtual nsresult BeginPrinting(const nsAString& aTitle,
+ const nsAString& aPrintToFileName);
+ virtual nsresult EndPrinting();
+ virtual nsresult AbortPrinting();
+ virtual nsresult BeginPage();
+ virtual nsresult EndPage();
+
+ void SetData(const cairo_user_data_key_t* key, void* user_data,
+ thebes_destroy_func_t destroy);
+ void* GetData(const cairo_user_data_key_t* key);
+
+ virtual void Finish();
+
+ /**
+ * Returns an image surface for this surface, or nullptr if not supported.
+ * This will not copy image data, just wraps an image surface around
+ * pixel data already available in memory.
+ */
+ virtual already_AddRefed<gfxImageSurface> GetAsImageSurface();
+
+ /**
+ * Creates a new ARGB32 image surface with the same contents as this surface.
+ * Returns null on error.
+ */
+ already_AddRefed<gfxImageSurface> CopyToARGB32ImageSurface();
+
+ int CairoStatus();
+
+ static gfxContentType ContentFromFormat(gfxImageFormat format);
+
+ /**
+ * Record number of bytes for given surface type. Use positive bytes
+ * for allocations and negative bytes for deallocations.
+ */
+ static void RecordMemoryUsedForSurfaceType(gfxSurfaceType aType,
+ int32_t aBytes);
+
+ /**
+ * Same as above, but use current surface type as returned by GetType().
+ * The bytes will be accumulated until RecordMemoryFreed is called,
+ * in which case the value that was recorded for this surface will
+ * be freed.
+ */
+ void RecordMemoryUsed(int32_t aBytes);
+ void RecordMemoryFreed();
+
+ virtual int32_t KnownMemoryUsed() { return mBytesRecorded; }
+
+ virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ // gfxASurface has many sub-classes. This method indicates if a sub-class
+ // is capable of measuring its own size accurately. If not, the caller
+ // must fall back to a computed size. (Note that gfxASurface can actually
+ // measure itself, but we must |return false| here because it serves as the
+ // (conservative) default for all the sub-classes. Therefore, this
+ // function should only be called on a |gfxASurface*| that actually points
+ // to a sub-class of gfxASurface.)
+ virtual bool SizeOfIsMeasured() const { return false; }
+
+ static int32_t BytePerPixelFromFormat(gfxImageFormat format);
+
+ virtual const mozilla::gfx::IntSize GetSize() const;
+
+ virtual mozilla::gfx::SurfaceFormat GetSurfaceFormat() const;
+
+ void SetOpaqueRect(const gfxRect& aRect);
+
+ const gfxRect& GetOpaqueRect() {
+ if (!!mOpaqueRect) return *mOpaqueRect;
+ return GetEmptyOpaqueRect();
+ }
+
+ static uint8_t BytesPerPixel(gfxImageFormat aImageFormat);
+
+ protected:
+ gfxASurface();
+
+ static gfxASurface* GetSurfaceWrapper(cairo_surface_t* csurf);
+ static void SetSurfaceWrapper(cairo_surface_t* csurf, gfxASurface* asurf);
+
+ // NB: Init() *must* be called from within subclass's
+ // constructors. It's unsafe to call it after the ctor finishes;
+ // leaks and use-after-frees are possible.
+ void Init(cairo_surface_t* surface, bool existingSurface = false);
+
+ // out-of-line helper to allow GetOpaqueRect() to be inlined
+ // without including gfxRect.h here
+ static const gfxRect& GetEmptyOpaqueRect();
+
+ virtual ~gfxASurface();
+
+ cairo_surface_t* mSurface;
+ mozilla::UniquePtr<gfxRect> mOpaqueRect;
+
+ private:
+ static void SurfaceDestroyFunc(void* data);
+
+ int32_t mFloatingRefs;
+ int32_t mBytesRecorded;
+
+ protected:
+ bool mSurfaceValid;
+};
+
+/**
+ * An Unknown surface; used to wrap unknown cairo_surface_t returns from cairo
+ */
+class gfxUnknownSurface : public gfxASurface {
+ public:
+ gfxUnknownSurface(cairo_surface_t* surf, const mozilla::gfx::IntSize& aSize)
+ : mSize(aSize) {
+ Init(surf, true);
+ }
+
+ virtual ~gfxUnknownSurface() = default;
+ const mozilla::gfx::IntSize GetSize() const override { return mSize; }
+
+ private:
+ mozilla::gfx::IntSize mSize;
+};
+
+#endif /* GFX_ASURFACE_H */
diff --git a/gfx/thebes/gfxAlphaRecovery.cpp b/gfx/thebes/gfxAlphaRecovery.cpp
new file mode 100644
index 0000000000..98a78ed3cb
--- /dev/null
+++ b/gfx/thebes/gfxAlphaRecovery.cpp
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxAlphaRecovery.h"
+
+#include "gfxImageSurface.h"
+
+#define MOZILLA_SSE_INCLUDE_HEADER_FOR_SSE2
+#include "mozilla/SSE.h"
+
+/* static */
+bool gfxAlphaRecovery::RecoverAlpha(gfxImageSurface* blackSurf,
+ const gfxImageSurface* whiteSurf) {
+ mozilla::gfx::IntSize size = blackSurf->GetSize();
+
+ if (size != whiteSurf->GetSize() ||
+ (blackSurf->Format() != mozilla::gfx::SurfaceFormat::A8R8G8B8_UINT32 &&
+ blackSurf->Format() != mozilla::gfx::SurfaceFormat::X8R8G8B8_UINT32) ||
+ (whiteSurf->Format() != mozilla::gfx::SurfaceFormat::A8R8G8B8_UINT32 &&
+ whiteSurf->Format() != mozilla::gfx::SurfaceFormat::X8R8G8B8_UINT32))
+ return false;
+
+#ifdef MOZILLA_MAY_SUPPORT_SSE2
+ if (mozilla::supports_sse2() && RecoverAlphaSSE2(blackSurf, whiteSurf)) {
+ return true;
+ }
+#endif
+
+ blackSurf->Flush();
+ whiteSurf->Flush();
+
+ unsigned char* blackData = blackSurf->Data();
+ unsigned char* whiteData = whiteSurf->Data();
+
+ for (int32_t i = 0; i < size.height; ++i) {
+ uint32_t* blackPixel = reinterpret_cast<uint32_t*>(blackData);
+ const uint32_t* whitePixel = reinterpret_cast<uint32_t*>(whiteData);
+ for (int32_t j = 0; j < size.width; ++j) {
+ uint32_t recovered = RecoverPixel(blackPixel[j], whitePixel[j]);
+ blackPixel[j] = recovered;
+ }
+ blackData += blackSurf->Stride();
+ whiteData += whiteSurf->Stride();
+ }
+
+ blackSurf->MarkDirty();
+
+ return true;
+}
diff --git a/gfx/thebes/gfxAlphaRecovery.h b/gfx/thebes/gfxAlphaRecovery.h
new file mode 100644
index 0000000000..53b38f2ac0
--- /dev/null
+++ b/gfx/thebes/gfxAlphaRecovery.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _GFXALPHARECOVERY_H_
+#define _GFXALPHARECOVERY_H_
+
+#include "mozilla/SSE.h"
+#include "gfxTypes.h"
+#include "mozilla/gfx/Rect.h"
+
+class gfxImageSurface;
+
+class gfxAlphaRecovery {
+ public:
+ /**
+ * Some SIMD fast-paths only can be taken if the relative
+ * byte-alignment of images' pointers and strides meets certain
+ * criteria. Aligning image pointers and strides by
+ * |GoodAlignmentLog2()| below will ensure that fast-paths aren't
+ * skipped because of misalignment. Fast-paths may still be taken
+ * even if GoodAlignmentLog2() is not met, in some conditions.
+ */
+ static uint32_t GoodAlignmentLog2() { return 4; /* for SSE2 */ }
+
+ /* Given two surfaces of equal size with the same rendering, one onto a
+ * black background and the other onto white, recovers alpha values from
+ * the difference and sets the alpha values on the black surface.
+ * The surfaces must have format RGB24 or ARGB32.
+ * Returns true on success.
+ */
+ static bool RecoverAlpha(gfxImageSurface* blackSurface,
+ const gfxImageSurface* whiteSurface);
+
+#ifdef MOZILLA_MAY_SUPPORT_SSE2
+ /* This does the same as the previous function, but uses SSE2
+ * optimizations. Usually this should not be called directly. Be sure to
+ * check mozilla::supports_sse2() before calling this function.
+ */
+ static bool RecoverAlphaSSE2(gfxImageSurface* blackSurface,
+ const gfxImageSurface* whiteSurface);
+
+ /**
+ * A common use-case for alpha recovery is to paint into a
+ * temporary "white image", then paint onto a subrect of the
+ * surface, the "black image", into which alpha-recovered pixels
+ * are eventually to be written. This function returns a rect
+ * aligned so that recovering alpha for that rect will hit SIMD
+ * fast-paths, if possible. It's not always possible to align
+ * |aRect| so that fast-paths will be taken.
+ *
+ * The returned rect is always a superset of |aRect|.
+ */
+ static mozilla::gfx::IntRect AlignRectForSubimageRecovery(
+ const mozilla::gfx::IntRect& aRect, gfxImageSurface* aSurface);
+#else
+ static mozilla::gfx::IntRect AlignRectForSubimageRecovery(
+ const mozilla::gfx::IntRect& aRect, gfxImageSurface*) {
+ return aRect;
+ }
+#endif
+
+ /** from cairo-xlib-utils.c, modified */
+ /**
+ * Given the RGB data for two image surfaces, one a source image composited
+ * with OVER onto a black background, and one a source image composited with
+ * OVER onto a white background, reconstruct the original image data into
+ * black_data.
+ *
+ * Consider a single color channel and a given pixel. Suppose the original
+ * premultiplied color value was C and the alpha value was A. Let the final
+ * on-black color be B and the final on-white color be W. All values range
+ * over 0-255.
+ *
+ * Then B=C and W=(255*(255 - A) + C*255)/255. Solving for A, we get
+ * A=255 - (W - C). Therefore it suffices to leave the black_data color
+ * data alone and set the alpha values using that simple formula. It shouldn't
+ * matter what color channel we pick for the alpha computation, but we'll
+ * pick green because if we went through a color channel downsample the green
+ * bits are likely to be the most accurate.
+ *
+ * This function needs to be in the header file since it's used by both
+ * gfxRecoverAlpha.cpp and gfxRecoverAlphaSSE2.cpp.
+ */
+
+ static inline uint32_t RecoverPixel(uint32_t black, uint32_t white) {
+ const uint32_t GREEN_MASK = 0x0000FF00;
+ const uint32_t ALPHA_MASK = 0xFF000000;
+
+ /* |diff| here is larger when the source image pixel is more
+ transparent. If both renderings are from the same source image
+ composited with OVER, then the color values on white will always be
+ greater than those on black, so |diff| would not overflow. However,
+ overflow may happen, for example, when a plugin plays a video and
+ the image is rapidly changing. If there is overflow, then behave as
+ if we limit to the difference to
+ >= 0, which will make the rendering opaque. (Without this overflow
+ will make the rendering transparent.) */
+ uint32_t diff = (white & GREEN_MASK) - (black & GREEN_MASK);
+ /* |diff| is 0xFFFFxx00 on overflow and 0x0000xx00 otherwise, so use
+ this to limit the transparency. */
+ uint32_t limit = diff & ALPHA_MASK;
+ /* The alpha bits of the result */
+ uint32_t alpha = (ALPHA_MASK - (diff << 16)) | limit;
+
+ return alpha | (black & ~ALPHA_MASK);
+ }
+};
+
+#endif /* _GFXALPHARECOVERY_H_ */
diff --git a/gfx/thebes/gfxAlphaRecoverySSE2.cpp b/gfx/thebes/gfxAlphaRecoverySSE2.cpp
new file mode 100644
index 0000000000..d64cb18bad
--- /dev/null
+++ b/gfx/thebes/gfxAlphaRecoverySSE2.cpp
@@ -0,0 +1,234 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxAlphaRecovery.h"
+#include "gfxImageSurface.h"
+#include "nsDebug.h"
+#include <emmintrin.h>
+
+// This file should only be compiled on x86 and x64 systems. Additionally,
+// you'll need to compile it with -msse2 if you're using GCC on x86.
+
+#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64))
+__declspec(align(16)) static uint32_t greenMaski[] = {0x0000ff00, 0x0000ff00,
+ 0x0000ff00, 0x0000ff00};
+__declspec(align(16)) static uint32_t alphaMaski[] = {0xff000000, 0xff000000,
+ 0xff000000, 0xff000000};
+#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
+static uint32_t greenMaski[] __attribute__((aligned(16))) = {
+ 0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00};
+static uint32_t alphaMaski[] __attribute__((aligned(16))) = {
+ 0xff000000, 0xff000000, 0xff000000, 0xff000000};
+#elif defined(__SUNPRO_CC) && (defined(__i386) || defined(__x86_64__))
+# pragma align 16(greenMaski, alphaMaski)
+static uint32_t greenMaski[] = {0x0000ff00, 0x0000ff00, 0x0000ff00, 0x0000ff00};
+static uint32_t alphaMaski[] = {0xff000000, 0xff000000, 0xff000000, 0xff000000};
+#endif
+
+bool gfxAlphaRecovery::RecoverAlphaSSE2(gfxImageSurface* blackSurf,
+ const gfxImageSurface* whiteSurf) {
+ mozilla::gfx::IntSize size = blackSurf->GetSize();
+
+ if (size != whiteSurf->GetSize() ||
+ (blackSurf->Format() != mozilla::gfx::SurfaceFormat::A8R8G8B8_UINT32 &&
+ blackSurf->Format() != mozilla::gfx::SurfaceFormat::X8R8G8B8_UINT32) ||
+ (whiteSurf->Format() != mozilla::gfx::SurfaceFormat::A8R8G8B8_UINT32 &&
+ whiteSurf->Format() != mozilla::gfx::SurfaceFormat::X8R8G8B8_UINT32))
+ return false;
+
+ blackSurf->Flush();
+ whiteSurf->Flush();
+
+ unsigned char* blackData = blackSurf->Data();
+ unsigned char* whiteData = whiteSurf->Data();
+
+ if ((NS_PTR_TO_UINT32(blackData) & 0xf) !=
+ (NS_PTR_TO_UINT32(whiteData) & 0xf) ||
+ (blackSurf->Stride() - whiteSurf->Stride()) & 0xf) {
+ // Cannot keep these in alignment.
+ return false;
+ }
+
+ __m128i greenMask = _mm_load_si128((__m128i*)greenMaski);
+ __m128i alphaMask = _mm_load_si128((__m128i*)alphaMaski);
+
+ for (int32_t i = 0; i < size.height; ++i) {
+ int32_t j = 0;
+ // Loop single pixels until at 4 byte alignment.
+ while (NS_PTR_TO_UINT32(blackData) & 0xf && j < size.width) {
+ *((uint32_t*)blackData) =
+ RecoverPixel(*reinterpret_cast<uint32_t*>(blackData),
+ *reinterpret_cast<uint32_t*>(whiteData));
+ blackData += 4;
+ whiteData += 4;
+ j++;
+ }
+ // This extra loop allows the compiler to do some more clever registry
+ // management and makes it about 5% faster than with only the 4 pixel
+ // at a time loop.
+ for (; j < size.width - 8; j += 8) {
+ __m128i black1 = _mm_load_si128((__m128i*)blackData);
+ __m128i white1 = _mm_load_si128((__m128i*)whiteData);
+ __m128i black2 = _mm_load_si128((__m128i*)(blackData + 16));
+ __m128i white2 = _mm_load_si128((__m128i*)(whiteData + 16));
+
+ // Execute the same instructions as described in RecoverPixel, only
+ // using an SSE2 packed saturated subtract.
+ white1 = _mm_subs_epu8(white1, black1);
+ white2 = _mm_subs_epu8(white2, black2);
+ white1 = _mm_subs_epu8(greenMask, white1);
+ white2 = _mm_subs_epu8(greenMask, white2);
+ // Producing the final black pixel in an XMM register and storing
+ // that is actually faster than doing a masked store since that
+ // does an unaligned storage. We have the black pixel in a register
+ // anyway.
+ black1 = _mm_andnot_si128(alphaMask, black1);
+ black2 = _mm_andnot_si128(alphaMask, black2);
+ white1 = _mm_slli_si128(white1, 2);
+ white2 = _mm_slli_si128(white2, 2);
+ white1 = _mm_and_si128(alphaMask, white1);
+ white2 = _mm_and_si128(alphaMask, white2);
+ black1 = _mm_or_si128(white1, black1);
+ black2 = _mm_or_si128(white2, black2);
+
+ _mm_store_si128((__m128i*)blackData, black1);
+ _mm_store_si128((__m128i*)(blackData + 16), black2);
+ blackData += 32;
+ whiteData += 32;
+ }
+ for (; j < size.width - 4; j += 4) {
+ __m128i black = _mm_load_si128((__m128i*)blackData);
+ __m128i white = _mm_load_si128((__m128i*)whiteData);
+
+ white = _mm_subs_epu8(white, black);
+ white = _mm_subs_epu8(greenMask, white);
+ black = _mm_andnot_si128(alphaMask, black);
+ white = _mm_slli_si128(white, 2);
+ white = _mm_and_si128(alphaMask, white);
+ black = _mm_or_si128(white, black);
+ _mm_store_si128((__m128i*)blackData, black);
+ blackData += 16;
+ whiteData += 16;
+ }
+ // Loop single pixels until we're done.
+ while (j < size.width) {
+ *((uint32_t*)blackData) =
+ RecoverPixel(*reinterpret_cast<uint32_t*>(blackData),
+ *reinterpret_cast<uint32_t*>(whiteData));
+ blackData += 4;
+ whiteData += 4;
+ j++;
+ }
+ blackData += blackSurf->Stride() - j * 4;
+ whiteData += whiteSurf->Stride() - j * 4;
+ }
+
+ blackSurf->MarkDirty();
+
+ return true;
+}
+
+static int32_t ByteAlignment(int32_t aAlignToLog2, int32_t aX, int32_t aY = 0,
+ int32_t aStride = 1) {
+ return (aX + aStride * aY) & ((1 << aAlignToLog2) - 1);
+}
+
+/*static*/ mozilla::gfx::IntRect gfxAlphaRecovery::AlignRectForSubimageRecovery(
+ const mozilla::gfx::IntRect& aRect, gfxImageSurface* aSurface) {
+ NS_ASSERTION(
+ mozilla::gfx::SurfaceFormat::A8R8G8B8_UINT32 == aSurface->Format(),
+ "Thebes grew support for non-ARGB32 COLOR_ALPHA?");
+ static const int32_t kByteAlignLog2 = GoodAlignmentLog2();
+ static const int32_t bpp = 4;
+ static const int32_t pixPerAlign = (1 << kByteAlignLog2) / bpp;
+ //
+ // We're going to create a subimage of the surface with size
+ // <sw,sh> for alpha recovery, and want a SIMD fast-path. The
+ // rect <x,y, w,h> /needs/ to be redrawn, but it might not be
+ // properly aligned for SIMD. So we want to find a rect <x',y',
+ // w',h'> that's a superset of what needs to be redrawn but is
+ // properly aligned. Proper alignment is
+ //
+ // BPP * (x' + y' * sw) \cong 0 (mod ALIGN)
+ // BPP * w' \cong BPP * sw (mod ALIGN)
+ //
+ // (We assume the pixel at surface <0,0> is already ALIGN'd.)
+ // That rect (obviously) has to fit within the surface bounds, and
+ // we should also minimize the extra pixels redrawn only for
+ // alignment's sake. So we also want
+ //
+ // minimize <x',y', w',h'>
+ // 0 <= x' <= x
+ // 0 <= y' <= y
+ // w <= w' <= sw
+ // h <= h' <= sh
+ //
+ // This is a messy integer non-linear programming problem, except
+ // ... we can assume that ALIGN/BPP is a very small constant. So,
+ // brute force is viable. The algorithm below will find a
+ // solution if one exists, but isn't guaranteed to find the
+ // minimum solution. (For SSE2, ALIGN/BPP = 4, so it'll do at
+ // most 64 iterations below). In what's likely the common case,
+ // an already-aligned rectangle, it only needs 1 iteration.
+ //
+ // Is this alignment worth doing? Recovering alpha will take work
+ // proportional to w*h (assuming alpha recovery computation isn't
+ // memory bound). This analysis can lead to O(w+h) extra work
+ // (with small constants). In exchange, we expect to shave off a
+ // ALIGN/BPP constant by using SIMD-ized alpha recovery. So as
+ // w*h diverges from w+h, the win factor approaches ALIGN/BPP. We
+ // only really care about the w*h >> w+h case anyway; others
+ // should be fast enough even with the overhead. (Unless the cost
+ // of repainting the expanded rect is high, but in that case
+ // SIMD-ized alpha recovery won't make a difference so this code
+ // shouldn't be called.)
+ //
+ mozilla::gfx::IntSize surfaceSize = aSurface->GetSize();
+ const int32_t stride = bpp * surfaceSize.width;
+ if (stride != aSurface->Stride()) {
+ NS_WARNING("Unexpected stride, falling back on slow alpha recovery");
+ return aRect;
+ }
+
+ const int32_t x = aRect.X(), y = aRect.Y(), w = aRect.Width(),
+ h = aRect.Height();
+ const int32_t r = x + w;
+ const int32_t sw = surfaceSize.width;
+ const int32_t strideAlign = ByteAlignment(kByteAlignLog2, stride);
+
+ // The outer two loops below keep the rightmost (|r| above) and
+ // bottommost pixels in |aRect| fixed wrt <x,y>, to ensure that we
+ // return only a superset of the original rect. These loops
+ // search for an aligned top-left pixel by trying to expand <x,y>
+ // left and up by <dx,dy> pixels, respectively.
+ //
+ // Then if a properly-aligned top-left pixel is found, the
+ // innermost loop tries to find an aligned stride by moving the
+ // rightmost pixel rightward by dr.
+ int32_t dx, dy, dr;
+ for (dy = 0; (dy < pixPerAlign) && (y - dy >= 0); ++dy) {
+ for (dx = 0; (dx < pixPerAlign) && (x - dx >= 0); ++dx) {
+ if (0 != ByteAlignment(kByteAlignLog2, bpp * (x - dx), y - dy, stride)) {
+ continue;
+ }
+ for (dr = 0; (dr < pixPerAlign) && (r + dr <= sw); ++dr) {
+ if (strideAlign == ByteAlignment(kByteAlignLog2, bpp * (w + dr + dx))) {
+ goto FOUND_SOLUTION;
+ }
+ }
+ }
+ }
+
+ // Didn't find a solution.
+ return aRect;
+
+FOUND_SOLUTION:
+ mozilla::gfx::IntRect solution =
+ mozilla::gfx::IntRect(x - dx, y - dy, w + dr + dx, h + dy);
+ MOZ_ASSERT(
+ mozilla::gfx::IntRect(0, 0, sw, surfaceSize.height).Contains(solution),
+ "'Solution' extends outside surface bounds!");
+ return solution;
+}
diff --git a/gfx/thebes/gfxAndroidPlatform.cpp b/gfx/thebes/gfxAndroidPlatform.cpp
new file mode 100644
index 0000000000..7c7fe11fc6
--- /dev/null
+++ b/gfx/thebes/gfxAndroidPlatform.cpp
@@ -0,0 +1,369 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/basictypes.h"
+
+#include "gfxAndroidPlatform.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/CountingAllocatorBase.h"
+#include "mozilla/intl/LocaleService.h"
+#include "mozilla/intl/OSPreferences.h"
+#include "mozilla/java/GeckoAppShellWrappers.h"
+#include "mozilla/jni/Utils.h"
+#include "mozilla/layers/AndroidHardwareBuffer.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/StaticPrefs_webgl.h"
+#include "mozilla/widget/AndroidVsync.h"
+
+#include "gfx2DGlue.h"
+#include "gfxFT2FontList.h"
+#include "gfxImageSurface.h"
+#include "gfxTextRun.h"
+#include "nsXULAppAPI.h"
+#include "nsIScreen.h"
+#include "nsServiceManagerUtils.h"
+#include "nsUnicodeProperties.h"
+#include "cairo.h"
+#include "VsyncSource.h"
+
+#include "ft2build.h"
+#include FT_FREETYPE_H
+#include FT_MODULE_H
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::unicode;
+using mozilla::intl::LocaleService;
+using mozilla::intl::OSPreferences;
+
+static FT_Library gPlatformFTLibrary = nullptr;
+
+class FreetypeReporter final : public nsIMemoryReporter,
+ public CountingAllocatorBase<FreetypeReporter> {
+ private:
+ ~FreetypeReporter() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ static void* Malloc(FT_Memory, long size) { return CountingMalloc(size); }
+
+ static void Free(FT_Memory, void* p) { return CountingFree(p); }
+
+ static void* Realloc(FT_Memory, long cur_size, long new_size, void* p) {
+ return CountingRealloc(p, new_size);
+ }
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ MOZ_COLLECT_REPORT("explicit/freetype", KIND_HEAP, UNITS_BYTES,
+ MemoryAllocated(), "Memory used by Freetype.");
+
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(FreetypeReporter, nsIMemoryReporter)
+
+static FT_MemoryRec_ sFreetypeMemoryRecord;
+
+gfxAndroidPlatform::gfxAndroidPlatform() {
+ // A custom allocator. It counts allocations, enabling memory reporting.
+ sFreetypeMemoryRecord.user = nullptr;
+ sFreetypeMemoryRecord.alloc = FreetypeReporter::Malloc;
+ sFreetypeMemoryRecord.free = FreetypeReporter::Free;
+ sFreetypeMemoryRecord.realloc = FreetypeReporter::Realloc;
+
+ // These two calls are equivalent to FT_Init_FreeType(), but allow us to
+ // provide a custom memory allocator.
+ FT_New_Library(&sFreetypeMemoryRecord, &gPlatformFTLibrary);
+ FT_Add_Default_Modules(gPlatformFTLibrary);
+
+ Factory::SetFTLibrary(gPlatformFTLibrary);
+
+ RegisterStrongMemoryReporter(new FreetypeReporter());
+
+ mOffscreenFormat = GetScreenDepth() == 16 ? SurfaceFormat::R5G6B5_UINT16
+ : SurfaceFormat::X8R8G8B8_UINT32;
+
+ if (StaticPrefs::gfx_android_rgb16_force_AtStartup()) {
+ mOffscreenFormat = SurfaceFormat::R5G6B5_UINT16;
+ }
+}
+
+gfxAndroidPlatform::~gfxAndroidPlatform() {
+ FT_Done_Library(gPlatformFTLibrary);
+ gPlatformFTLibrary = nullptr;
+ layers::AndroidHardwareBufferManager::Shutdown();
+ layers::AndroidHardwareBufferApi::Shutdown();
+}
+
+void gfxAndroidPlatform::InitAcceleration() { gfxPlatform::InitAcceleration(); }
+
+already_AddRefed<gfxASurface> gfxAndroidPlatform::CreateOffscreenSurface(
+ const IntSize& aSize, gfxImageFormat aFormat) {
+ if (!Factory::AllowedSurfaceSize(aSize)) {
+ return nullptr;
+ }
+
+ RefPtr<gfxASurface> newSurface;
+ newSurface = new gfxImageSurface(aSize, aFormat);
+
+ return newSurface.forget();
+}
+
+static bool IsJapaneseLocale() {
+ static bool sInitialized = false;
+ static bool sIsJapanese = false;
+
+ if (!sInitialized) {
+ sInitialized = true;
+
+ nsAutoCString appLocale;
+ LocaleService::GetInstance()->GetAppLocaleAsBCP47(appLocale);
+
+ const nsDependentCSubstring lang(appLocale, 0, 2);
+ if (lang.EqualsLiteral("ja")) {
+ sIsJapanese = true;
+ } else {
+ OSPreferences::GetInstance()->GetSystemLocale(appLocale);
+
+ const nsDependentCSubstring lang(appLocale, 0, 2);
+ if (lang.EqualsLiteral("ja")) {
+ sIsJapanese = true;
+ }
+ }
+ }
+
+ return sIsJapanese;
+}
+
+void gfxAndroidPlatform::GetCommonFallbackFonts(
+ uint32_t aCh, Script aRunScript, eFontPresentation aPresentation,
+ nsTArray<const char*>& aFontList) {
+ static const char kDroidSansJapanese[] = "Droid Sans Japanese";
+ static const char kMotoyaLMaru[] = "MotoyaLMaru";
+ static const char kNotoSansCJKJP[] = "Noto Sans CJK JP";
+ static const char kNotoColorEmoji[] = "Noto Color Emoji";
+
+ if (PrefersColor(aPresentation)) {
+ aFontList.AppendElement(kNotoColorEmoji);
+ }
+
+ if (IS_IN_BMP(aCh)) {
+ // try language-specific "Droid Sans *" and "Noto Sans *" fonts for
+ // certain blocks, as most devices probably have these
+ uint8_t block = (aCh >> 8) & 0xff;
+ switch (block) {
+ case 0x05:
+ aFontList.AppendElement("Noto Sans Hebrew");
+ aFontList.AppendElement("Droid Sans Hebrew");
+ aFontList.AppendElement("Noto Sans Armenian");
+ aFontList.AppendElement("Droid Sans Armenian");
+ break;
+ case 0x06:
+ aFontList.AppendElement("Noto Sans Arabic");
+ aFontList.AppendElement("Droid Sans Arabic");
+ break;
+ case 0x09:
+ aFontList.AppendElement("Noto Sans Devanagari");
+ aFontList.AppendElement("Noto Sans Bengali");
+ aFontList.AppendElement("Droid Sans Devanagari");
+ break;
+ case 0x0a:
+ aFontList.AppendElement("Noto Sans Gurmukhi");
+ aFontList.AppendElement("Noto Sans Gujarati");
+ break;
+ case 0x0b:
+ aFontList.AppendElement("Noto Sans Tamil");
+ aFontList.AppendElement("Noto Sans Oriya");
+ aFontList.AppendElement("Droid Sans Tamil");
+ break;
+ case 0x0c:
+ aFontList.AppendElement("Noto Sans Telugu");
+ aFontList.AppendElement("Noto Sans Kannada");
+ break;
+ case 0x0d:
+ aFontList.AppendElement("Noto Sans Malayalam");
+ aFontList.AppendElement("Noto Sans Sinhala");
+ break;
+ case 0x0e:
+ aFontList.AppendElement("Noto Sans Thai");
+ aFontList.AppendElement("Noto Sans Lao");
+ aFontList.AppendElement("Droid Sans Thai");
+ break;
+ case 0x0f:
+ aFontList.AppendElement("Noto Sans Tibetan");
+ break;
+ case 0x10:
+ case 0x2d:
+ aFontList.AppendElement("Noto Sans Georgian");
+ aFontList.AppendElement("Droid Sans Georgian");
+ break;
+ case 0x12:
+ case 0x13:
+ aFontList.AppendElement("Noto Sans Ethiopic");
+ aFontList.AppendElement("Droid Sans Ethiopic");
+ break;
+ case 0x21:
+ case 0x23:
+ case 0x24:
+ case 0x26:
+ case 0x27:
+ case 0x29:
+ aFontList.AppendElement("Noto Sans Symbols");
+ break;
+ case 0xf9:
+ case 0xfa:
+ if (IsJapaneseLocale()) {
+ aFontList.AppendElement(kMotoyaLMaru);
+ aFontList.AppendElement(kNotoSansCJKJP);
+ aFontList.AppendElement(kDroidSansJapanese);
+ }
+ break;
+ default:
+ if (block >= 0x2e && block <= 0x9f && IsJapaneseLocale()) {
+ aFontList.AppendElement(kMotoyaLMaru);
+ aFontList.AppendElement(kNotoSansCJKJP);
+ aFontList.AppendElement(kDroidSansJapanese);
+ }
+ break;
+ }
+ }
+ // and try Droid Sans Fallback as a last resort
+ aFontList.AppendElement("Droid Sans Fallback");
+}
+
+bool gfxAndroidPlatform::CreatePlatformFontList() {
+ return gfxPlatformFontList::Initialize(new gfxFT2FontList);
+}
+
+void gfxAndroidPlatform::ReadSystemFontList(
+ mozilla::dom::SystemFontList* aFontList) {
+ gfxFT2FontList::PlatformFontList()->ReadSystemFontList(aFontList);
+}
+
+bool gfxAndroidPlatform::FontHintingEnabled() {
+ // In "mobile" builds, we sometimes use non-reflow-zoom, so we
+ // might not want hinting. Let's see.
+
+#ifdef MOZ_WIDGET_ANDROID
+ // On Android, we currently only use gecko to render web
+ // content that can always be be non-reflow-zoomed. So turn off
+ // hinting.
+ //
+ // XXX when gecko-android-java is used as an "app runtime", we may
+ // want to re-enable hinting for non-browser processes there.
+ //
+ // If this value is changed, we must ensure that the argument passed
+ // to SkInitCairoFT() in GPUParent::RecvInit() is also updated.
+ return false;
+#endif // MOZ_WIDGET_ANDROID
+
+ // Currently, we don't have any other targets, but if/when we do,
+ // decide how to handle them here.
+
+ MOZ_ASSERT_UNREACHABLE("oops, what platform is this?");
+ return gfxPlatform::FontHintingEnabled();
+}
+
+bool gfxAndroidPlatform::RequiresLinearZoom() {
+#ifdef MOZ_WIDGET_ANDROID
+ // On Android, we currently only use gecko to render web
+ // content that can always be be non-reflow-zoomed.
+ //
+ // XXX when gecko-android-java is used as an "app runtime", we may
+ // want to use linear zoom only for the web browser process, not other apps.
+ return true;
+#endif
+
+ MOZ_ASSERT_UNREACHABLE("oops, what platform is this?");
+ return gfxPlatform::RequiresLinearZoom();
+}
+
+class AndroidVsyncSource final : public VsyncSource,
+ public widget::AndroidVsync::Observer {
+ public:
+ AndroidVsyncSource() : mAndroidVsync(widget::AndroidVsync::GetInstance()) {}
+
+ bool IsVsyncEnabled() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mObservingVsync;
+ }
+
+ void EnableVsync() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mObservingVsync) {
+ return;
+ }
+
+ float fps = java::GeckoAppShell::GetScreenRefreshRate();
+ MOZ_ASSERT(fps > 0.0f);
+ mVsyncRate = TimeDuration::FromMilliseconds(1000.0 / fps);
+ mAndroidVsync->RegisterObserver(this, widget::AndroidVsync::RENDER);
+ mObservingVsync = true;
+ }
+
+ void DisableVsync() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mObservingVsync) {
+ return;
+ }
+ mAndroidVsync->UnregisterObserver(this, widget::AndroidVsync::RENDER);
+ mObservingVsync = false;
+ }
+
+ TimeDuration GetVsyncRate() override { return mVsyncRate; }
+
+ void Shutdown() override { DisableVsync(); }
+
+ // Override for the widget::AndroidVsync::Observer method
+ void OnVsync(const TimeStamp& aTimeStamp) override {
+ // Use the timebase from the frame callback as the vsync time, unless it
+ // is in the future.
+ TimeStamp now = TimeStamp::Now();
+ TimeStamp vsyncTime = aTimeStamp < now ? aTimeStamp : now;
+ TimeStamp outputTime = vsyncTime + GetVsyncRate();
+
+ NotifyVsync(vsyncTime, outputTime);
+ }
+
+ void OnMaybeUpdateRefreshRate() override {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction(__func__, [self = RefPtr{this}]() {
+ if (!self->mObservingVsync) {
+ return;
+ }
+ self->DisableVsync();
+ self->EnableVsync();
+ }));
+ }
+
+ private:
+ virtual ~AndroidVsyncSource() { DisableVsync(); }
+
+ RefPtr<widget::AndroidVsync> mAndroidVsync;
+ TimeDuration mVsyncRate;
+ bool mObservingVsync = false;
+};
+
+already_AddRefed<mozilla::gfx::VsyncSource>
+gfxAndroidPlatform::CreateGlobalHardwareVsyncSource() {
+ // Vsync was introduced since JB (API 16~18) but inaccurate. Enable only for
+ // KK (API 19) and later.
+ if (jni::GetAPIVersion() >= 19) {
+ RefPtr<AndroidVsyncSource> vsyncSource = new AndroidVsyncSource();
+ return vsyncSource.forget();
+ }
+
+ NS_WARNING("Vsync not supported. Falling back to software vsync");
+ return GetSoftwareVsyncSource();
+}
diff --git a/gfx/thebes/gfxAndroidPlatform.h b/gfx/thebes/gfxAndroidPlatform.h
new file mode 100644
index 0000000000..6d028375d4
--- /dev/null
+++ b/gfx/thebes/gfxAndroidPlatform.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_PLATFORM_ANDROID_H
+#define GFX_PLATFORM_ANDROID_H
+
+#include "gfxPlatform.h"
+#include "gfxUserFontSet.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+
+class gfxAndroidPlatform final : public gfxPlatform {
+ public:
+ gfxAndroidPlatform();
+ virtual ~gfxAndroidPlatform();
+
+ static gfxAndroidPlatform* GetPlatform() {
+ return (gfxAndroidPlatform*)gfxPlatform::GetPlatform();
+ }
+
+ already_AddRefed<gfxASurface> CreateOffscreenSurface(
+ const IntSize& aSize, gfxImageFormat aFormat) override;
+
+ gfxImageFormat GetOffscreenFormat() override { return mOffscreenFormat; }
+
+ // platform implementations of font functions
+ bool CreatePlatformFontList() override;
+
+ void ReadSystemFontList(mozilla::dom::SystemFontList*) override;
+
+ void GetCommonFallbackFonts(uint32_t aCh, Script aRunScript,
+ eFontPresentation aPresentation,
+ nsTArray<const char*>& aFontList) override;
+
+ bool FontHintingEnabled() override;
+ bool RequiresLinearZoom() override;
+
+ already_AddRefed<mozilla::gfx::VsyncSource> CreateGlobalHardwareVsyncSource()
+ override;
+
+ static bool CheckVariationFontSupport() {
+ // We build with in-tree FreeType, so we know it is a new enough
+ // version to support variations.
+ return true;
+ }
+
+ protected:
+ void InitAcceleration() override;
+
+ bool AccelerateLayersByDefault() override { return true; }
+
+ private:
+ gfxImageFormat mOffscreenFormat;
+};
+
+#endif /* GFX_PLATFORM_ANDROID_H */
diff --git a/gfx/thebes/gfxBaseSharedMemorySurface.cpp b/gfx/thebes/gfxBaseSharedMemorySurface.cpp
new file mode 100644
index 0000000000..0ef7bd33d0
--- /dev/null
+++ b/gfx/thebes/gfxBaseSharedMemorySurface.cpp
@@ -0,0 +1,10 @@
+// vim:set ts=4 sts=2 sw=2 et cin:
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxBaseSharedMemorySurface.h"
+#include "cairo.h"
+
+const cairo_user_data_key_t SHM_KEY = {0};
diff --git a/gfx/thebes/gfxBaseSharedMemorySurface.h b/gfx/thebes/gfxBaseSharedMemorySurface.h
new file mode 100644
index 0000000000..bf211edab5
--- /dev/null
+++ b/gfx/thebes/gfxBaseSharedMemorySurface.h
@@ -0,0 +1,165 @@
+// vim:set ts=4 sts=2 sw=2 et cin:
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_SHARED_MEMORYSURFACE_H
+#define GFX_SHARED_MEMORYSURFACE_H
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/ipc/Shmem.h"
+#include "mozilla/ipc/SharedMemory.h"
+
+#include "gfxASurface.h"
+#include "gfxImageSurface.h"
+#include "pratom.h"
+
+typedef struct _cairo_user_data_key cairo_user_data_key_t;
+
+struct SharedImageInfo {
+ int32_t width;
+ int32_t height;
+ gfxImageFormat format;
+ int32_t readCount;
+};
+
+inline SharedImageInfo* GetShmInfoPtr(const mozilla::ipc::Shmem& aShmem) {
+ return reinterpret_cast<SharedImageInfo*>(
+ aShmem.get<char>() + aShmem.Size<char>() - sizeof(SharedImageInfo));
+}
+
+extern const cairo_user_data_key_t SHM_KEY;
+
+template <typename Base, typename Sub>
+class gfxBaseSharedMemorySurface : public Base {
+ typedef mozilla::ipc::SharedMemory SharedMemory;
+ typedef mozilla::ipc::Shmem Shmem;
+
+ protected:
+ virtual ~gfxBaseSharedMemorySurface() {
+ MOZ_COUNT_DTOR(gfxBaseSharedMemorySurface);
+ }
+
+ public:
+ /**
+ * Return a new gfxSharedImageSurface around a shmem segment newly
+ * allocated by this function. |aAllocator| is the object used to
+ * allocate the new shmem segment. Null is returned if creating
+ * the surface failed.
+ *
+ * NB: the *caller* is responsible for freeing the Shmem allocated
+ * by this function.
+ */
+ template <class ShmemAllocator>
+ static already_AddRefed<Sub> Create(ShmemAllocator* aAllocator,
+ const mozilla::gfx::IntSize& aSize,
+ gfxImageFormat aFormat) {
+ return Create<ShmemAllocator, false>(aAllocator, aSize, aFormat);
+ }
+
+ /**
+ * Return a new gfxSharedImageSurface that wraps a shmem segment
+ * already created by the Create() above. Bad things will happen
+ * if an attempt is made to wrap any other shmem segment. Null is
+ * returned if creating the surface failed.
+ */
+ static already_AddRefed<Sub> Open(const Shmem& aShmem) {
+ SharedImageInfo* shmInfo = GetShmInfoPtr(aShmem);
+ mozilla::gfx::IntSize size(shmInfo->width, shmInfo->height);
+ if (!mozilla::gfx::Factory::CheckSurfaceSize(size)) return nullptr;
+
+ gfxImageFormat format = shmInfo->format;
+ long stride = gfxImageSurface::ComputeStride(size, format);
+
+ RefPtr<Sub> s = new Sub(size, stride, format, aShmem);
+ // We didn't create this Shmem and so don't free it on errors
+ return (s->CairoStatus() != 0) ? nullptr : s.forget();
+ }
+
+ template <class ShmemAllocator>
+ static already_AddRefed<Sub> CreateUnsafe(ShmemAllocator* aAllocator,
+ const mozilla::gfx::IntSize& aSize,
+ gfxImageFormat aFormat) {
+ return Create<ShmemAllocator, true>(aAllocator, aSize, aFormat);
+ }
+
+ Shmem& GetShmem() { return mShmem; }
+
+ static bool IsSharedImage(gfxASurface* aSurface) {
+ return (aSurface && aSurface->GetType() == gfxSurfaceType::Image &&
+ aSurface->GetData(&SHM_KEY));
+ }
+
+ protected:
+ gfxBaseSharedMemorySurface(const mozilla::gfx::IntSize& aSize, long aStride,
+ gfxImageFormat aFormat, const Shmem& aShmem)
+ : Base(aShmem.get<unsigned char>(), aSize, aStride, aFormat) {
+ MOZ_COUNT_CTOR(gfxBaseSharedMemorySurface);
+
+ mShmem = aShmem;
+ this->SetData(&SHM_KEY, this, nullptr);
+ }
+
+ private:
+ void WriteShmemInfo() {
+ SharedImageInfo* shmInfo = GetShmInfoPtr(mShmem);
+ shmInfo->width = this->mSize.width;
+ shmInfo->height = this->mSize.height;
+ shmInfo->format = this->mFormat;
+ shmInfo->readCount = 0;
+ }
+
+ int32_t ReadLock() {
+ SharedImageInfo* shmInfo = GetShmInfoPtr(mShmem);
+ return PR_ATOMIC_INCREMENT(&shmInfo->readCount);
+ }
+
+ int32_t ReadUnlock() {
+ SharedImageInfo* shmInfo = GetShmInfoPtr(mShmem);
+ return PR_ATOMIC_DECREMENT(&shmInfo->readCount);
+ }
+
+ int32_t GetReadCount() {
+ SharedImageInfo* shmInfo = GetShmInfoPtr(mShmem);
+ return shmInfo->readCount;
+ }
+
+ static size_t GetAlignedSize(const mozilla::gfx::IntSize& aSize,
+ long aStride) {
+#define MOZ_ALIGN_WORD(x) (((x) + 3) & ~3)
+ return MOZ_ALIGN_WORD(sizeof(SharedImageInfo) + aSize.height * aStride);
+ }
+
+ template <class ShmemAllocator, bool Unsafe>
+ static already_AddRefed<Sub> Create(ShmemAllocator* aAllocator,
+ const mozilla::gfx::IntSize& aSize,
+ gfxImageFormat aFormat) {
+ if (!mozilla::gfx::Factory::CheckSurfaceSize(aSize)) return nullptr;
+
+ Shmem shmem;
+ long stride = gfxImageSurface::ComputeStride(aSize, aFormat);
+ size_t size = GetAlignedSize(aSize, stride);
+ if (!Unsafe) {
+ if (!aAllocator->AllocShmem(size, &shmem)) return nullptr;
+ } else {
+ if (!aAllocator->AllocUnsafeShmem(size, &shmem)) return nullptr;
+ }
+
+ RefPtr<Sub> s = new Sub(aSize, stride, aFormat, shmem);
+ if (s->CairoStatus() != 0) {
+ aAllocator->DeallocShmem(shmem);
+ return nullptr;
+ }
+ s->WriteShmemInfo();
+ return s.forget();
+ }
+
+ Shmem mShmem;
+
+ // Calling these is very bad, disallow it
+ gfxBaseSharedMemorySurface(const gfxBaseSharedMemorySurface&);
+ gfxBaseSharedMemorySurface& operator=(const gfxBaseSharedMemorySurface&);
+};
+
+#endif /* GFX_SHARED_MEMORYSURFACE_H */
diff --git a/gfx/thebes/gfxBlur.cpp b/gfx/thebes/gfxBlur.cpp
new file mode 100644
index 0000000000..605bd5567c
--- /dev/null
+++ b/gfx/thebes/gfxBlur.cpp
@@ -0,0 +1,1224 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxBlur.h"
+
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Blur.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/Maybe.h"
+#include "nsExpirationTracker.h"
+#include "nsClassHashtable.h"
+#include "gfxUtils.h"
+#include <limits>
+#include <cmath>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+gfxAlphaBoxBlur::~gfxAlphaBoxBlur() = default;
+
+UniquePtr<gfxContext> gfxAlphaBoxBlur::Init(gfxContext* aDestinationCtx,
+ const gfxRect& aRect,
+ const IntSize& aSpreadRadius,
+ const IntSize& aBlurRadius,
+ const gfxRect* aDirtyRect,
+ const gfxRect* aSkipRect,
+ bool aUseHardwareAccel) {
+ DrawTarget* refDT = aDestinationCtx->GetDrawTarget();
+ Maybe<Rect> dirtyRect = aDirtyRect ? Some(ToRect(*aDirtyRect)) : Nothing();
+ Maybe<Rect> skipRect = aSkipRect ? Some(ToRect(*aSkipRect)) : Nothing();
+ RefPtr<DrawTarget> dt = InitDrawTarget(
+ refDT, ToRect(aRect), aSpreadRadius, aBlurRadius,
+ dirtyRect.ptrOr(nullptr), skipRect.ptrOr(nullptr), aUseHardwareAccel);
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+
+ auto context = MakeUnique<gfxContext>(dt);
+ context->SetMatrix(Matrix::Translation(-mBlur.GetRect().TopLeft()));
+ return context;
+}
+
+already_AddRefed<DrawTarget> gfxAlphaBoxBlur::InitDrawTarget(
+ const DrawTarget* aReferenceDT, const Rect& aRect,
+ const IntSize& aSpreadRadius, const IntSize& aBlurRadius,
+ const Rect* aDirtyRect, const Rect* aSkipRect, bool aUseHardwareAccel) {
+ mBlur.Init(aRect, aSpreadRadius, aBlurRadius, aDirtyRect, aSkipRect);
+ size_t blurDataSize = mBlur.GetSurfaceAllocationSize();
+ if (blurDataSize == 0) {
+ return nullptr;
+ }
+
+ BackendType backend = aReferenceDT->GetBackendType();
+
+ // Check if the backend has an accelerated DrawSurfaceWithShadow.
+ // Currently, only D2D1.1 supports this.
+ // Otherwise, DrawSurfaceWithShadow only supports square blurs without spread.
+ // When blurring small draw targets such as short spans text, the cost of
+ // creating and flushing an accelerated draw target may exceed the speedup
+ // gained from the faster blur. It's up to the users of this blur
+ // to determine whether they want to use hardware acceleration.
+ if (aBlurRadius.IsSquare() && aSpreadRadius.IsEmpty() && aUseHardwareAccel &&
+ backend == BackendType::DIRECT2D1_1) {
+ mAccelerated = true;
+ }
+
+ if (mAccelerated) {
+ // Note: CreateShadowDrawTarget is only implemented for Cairo.
+ mDrawTarget = aReferenceDT->CreateShadowDrawTarget(
+ mBlur.GetSize(), SurfaceFormat::A8,
+ AlphaBoxBlur::CalculateBlurSigma(aBlurRadius.width));
+ if (mDrawTarget) {
+ // See Bug 1526045 - this is to force DT initialization.
+ mDrawTarget->ClearRect(gfx::Rect());
+ }
+ } else {
+ // Make an alpha-only surface to draw on. We will play with the data after
+ // everything is drawn to create a blur effect.
+ // This will be freed when the DrawTarget dies
+ mData = static_cast<uint8_t*>(calloc(1, blurDataSize));
+ if (!mData) {
+ return nullptr;
+ }
+ mDrawTarget =
+ Factory::DoesBackendSupportDataDrawtarget(backend)
+ ? Factory::CreateDrawTargetForData(backend, mData, mBlur.GetSize(),
+ mBlur.GetStride(),
+ SurfaceFormat::A8)
+ : gfxPlatform::CreateDrawTargetForData(
+ mData, mBlur.GetSize(), mBlur.GetStride(), SurfaceFormat::A8);
+ }
+
+ if (!mDrawTarget || !mDrawTarget->IsValid()) {
+ if (mData) {
+ free(mData);
+ }
+
+ return nullptr;
+ }
+
+ if (mData) {
+ mDrawTarget->AddUserData(reinterpret_cast<UserDataKey*>(mDrawTarget.get()),
+ mData, free);
+ }
+
+ mDrawTarget->SetTransform(Matrix::Translation(-mBlur.GetRect().TopLeft()));
+ return do_AddRef(mDrawTarget);
+}
+
+already_AddRefed<SourceSurface> gfxAlphaBoxBlur::DoBlur(
+ const sRGBColor* aShadowColor, IntPoint* aOutTopLeft) {
+ if (aOutTopLeft) {
+ *aOutTopLeft = mBlur.GetRect().TopLeft();
+ }
+
+ RefPtr<SourceSurface> blurMask;
+ if (mData) {
+ mBlur.Blur(mData);
+ blurMask = mDrawTarget->Snapshot();
+ } else if (mAccelerated) {
+ blurMask = mDrawTarget->Snapshot();
+ RefPtr<DrawTarget> blurDT = mDrawTarget->CreateSimilarDrawTarget(
+ blurMask->GetSize(), SurfaceFormat::A8);
+ if (!blurDT) {
+ return nullptr;
+ }
+ blurDT->DrawSurfaceWithShadow(
+ blurMask, Point(0, 0),
+ ShadowOptions(
+ DeviceColor::MaskOpaqueWhite(), Point(0, 0),
+ AlphaBoxBlur::CalculateBlurSigma(mBlur.GetBlurRadius().width)),
+ CompositionOp::OP_OVER);
+ blurMask = blurDT->Snapshot();
+ }
+
+ if (!aShadowColor) {
+ return blurMask.forget();
+ }
+
+ RefPtr<DrawTarget> shadowDT = mDrawTarget->CreateSimilarDrawTarget(
+ blurMask->GetSize(), SurfaceFormat::B8G8R8A8);
+ if (!shadowDT) {
+ return nullptr;
+ }
+ ColorPattern shadowColor(ToDeviceColor(*aShadowColor));
+ shadowDT->MaskSurface(shadowColor, blurMask, Point(0, 0));
+
+ return shadowDT->Snapshot();
+}
+
+void gfxAlphaBoxBlur::Paint(gfxContext* aDestinationCtx) {
+ if (mDrawTarget && !mAccelerated && !mData) {
+ return;
+ }
+
+ DrawTarget* dest = aDestinationCtx->GetDrawTarget();
+ if (!dest) {
+ NS_WARNING("Blurring not supported for Thebes contexts!");
+ return;
+ }
+
+ RefPtr<gfxPattern> thebesPat = aDestinationCtx->GetPattern();
+ Pattern* pat = thebesPat->GetPattern(dest, nullptr);
+ if (!pat) {
+ NS_WARNING("Failed to get pattern for blur!");
+ return;
+ }
+
+ IntPoint topLeft;
+ RefPtr<SourceSurface> mask = DoBlur(nullptr, &topLeft);
+ if (!mask) {
+ NS_ERROR("Failed to create mask!");
+ return;
+ }
+
+ // Avoid a semi-expensive clip operation if we can, otherwise
+ // clip to the dirty rect
+ Rect* dirtyRect = mBlur.GetDirtyRect();
+ if (dirtyRect) {
+ dest->PushClipRect(*dirtyRect);
+ }
+
+ Matrix oldTransform = dest->GetTransform();
+ Matrix newTransform = oldTransform;
+ newTransform.PreTranslate(topLeft);
+ dest->SetTransform(newTransform);
+
+ dest->MaskSurface(*pat, mask, Point(0, 0));
+
+ dest->SetTransform(oldTransform);
+
+ if (dirtyRect) {
+ dest->PopClip();
+ }
+}
+
+IntSize gfxAlphaBoxBlur::CalculateBlurRadius(const gfxPoint& aStd) {
+ mozilla::gfx::Point std(Float(aStd.x), Float(aStd.y));
+ IntSize size = AlphaBoxBlur::CalculateBlurRadius(std);
+ return IntSize(size.width, size.height);
+}
+
+struct BlurCacheKey : public PLDHashEntryHdr {
+ typedef const BlurCacheKey& KeyType;
+ typedef const BlurCacheKey* KeyTypePointer;
+ enum { ALLOW_MEMMOVE = true };
+
+ IntSize mMinSize;
+ IntSize mBlurRadius;
+ sRGBColor mShadowColor;
+ BackendType mBackend;
+ RectCornerRadii mCornerRadii;
+ bool mIsInset;
+
+ // Only used for inset blurs
+ IntSize mInnerMinSize;
+
+ BlurCacheKey(const IntSize& aMinSize, const IntSize& aBlurRadius,
+ const RectCornerRadii* aCornerRadii,
+ const sRGBColor& aShadowColor, BackendType aBackendType)
+ : BlurCacheKey(aMinSize, IntSize(0, 0), aBlurRadius, aCornerRadii,
+ aShadowColor, false, aBackendType) {}
+
+ explicit BlurCacheKey(const BlurCacheKey* aOther)
+ : mMinSize(aOther->mMinSize),
+ mBlurRadius(aOther->mBlurRadius),
+ mShadowColor(aOther->mShadowColor),
+ mBackend(aOther->mBackend),
+ mCornerRadii(aOther->mCornerRadii),
+ mIsInset(aOther->mIsInset),
+ mInnerMinSize(aOther->mInnerMinSize) {}
+
+ explicit BlurCacheKey(const IntSize& aOuterMinSize,
+ const IntSize& aInnerMinSize,
+ const IntSize& aBlurRadius,
+ const RectCornerRadii* aCornerRadii,
+ const sRGBColor& aShadowColor, bool aIsInset,
+ BackendType aBackendType)
+ : mMinSize(aOuterMinSize),
+ mBlurRadius(aBlurRadius),
+ mShadowColor(aShadowColor),
+ mBackend(aBackendType),
+ mCornerRadii(aCornerRadii ? *aCornerRadii : RectCornerRadii()),
+ mIsInset(aIsInset),
+ mInnerMinSize(aInnerMinSize) {}
+
+ BlurCacheKey(BlurCacheKey&&) = default;
+
+ static PLDHashNumber HashKey(const KeyTypePointer aKey) {
+ PLDHashNumber hash = 0;
+ hash = AddToHash(hash, aKey->mMinSize.width, aKey->mMinSize.height);
+ hash = AddToHash(hash, aKey->mBlurRadius.width, aKey->mBlurRadius.height);
+
+ hash = AddToHash(
+ hash, HashBytes(&aKey->mShadowColor.r, sizeof(aKey->mShadowColor.r)));
+ hash = AddToHash(
+ hash, HashBytes(&aKey->mShadowColor.g, sizeof(aKey->mShadowColor.g)));
+ hash = AddToHash(
+ hash, HashBytes(&aKey->mShadowColor.b, sizeof(aKey->mShadowColor.b)));
+ hash = AddToHash(
+ hash, HashBytes(&aKey->mShadowColor.a, sizeof(aKey->mShadowColor.a)));
+
+ for (int i = 0; i < 4; i++) {
+ hash = AddToHash(hash, aKey->mCornerRadii[i].width,
+ aKey->mCornerRadii[i].height);
+ }
+
+ hash = AddToHash(hash, (uint32_t)aKey->mBackend);
+
+ if (aKey->mIsInset) {
+ hash = AddToHash(hash, aKey->mInnerMinSize.width,
+ aKey->mInnerMinSize.height);
+ }
+ return hash;
+ }
+
+ bool KeyEquals(KeyTypePointer aKey) const {
+ if (aKey->mMinSize == mMinSize && aKey->mBlurRadius == mBlurRadius &&
+ aKey->mCornerRadii == mCornerRadii &&
+ aKey->mShadowColor == mShadowColor && aKey->mBackend == mBackend) {
+ if (mIsInset) {
+ return (mInnerMinSize == aKey->mInnerMinSize);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+};
+
+/**
+ * This class is what is cached. It need to be allocated in an object separated
+ * to the cache entry to be able to be tracked by the nsExpirationTracker.
+ * */
+struct BlurCacheData {
+ BlurCacheData(SourceSurface* aBlur, const IntMargin& aBlurMargin,
+ BlurCacheKey&& aKey)
+ : mBlur(aBlur), mBlurMargin(aBlurMargin), mKey(std::move(aKey)) {}
+
+ BlurCacheData(BlurCacheData&& aOther) = default;
+
+ nsExpirationState* GetExpirationState() { return &mExpirationState; }
+
+ nsExpirationState mExpirationState;
+ RefPtr<SourceSurface> mBlur;
+ IntMargin mBlurMargin;
+ BlurCacheKey mKey;
+};
+
+/**
+ * This class implements a cache with no maximum size, that retains the
+ * SourceSurfaces used to draw the blurs.
+ *
+ * An entry stays in the cache as long as it is used often.
+ */
+class BlurCache final : public nsExpirationTracker<BlurCacheData, 4> {
+ public:
+ BlurCache()
+ : nsExpirationTracker<BlurCacheData, 4>(GENERATION_MS, "BlurCache") {}
+
+ virtual void NotifyExpired(BlurCacheData* aObject) override {
+ RemoveObject(aObject);
+ mHashEntries.Remove(aObject->mKey);
+ }
+
+ BlurCacheData* Lookup(const IntSize& aMinSize, const IntSize& aBlurRadius,
+ const RectCornerRadii* aCornerRadii,
+ const sRGBColor& aShadowColor,
+ BackendType aBackendType) {
+ BlurCacheData* blur = mHashEntries.Get(BlurCacheKey(
+ aMinSize, aBlurRadius, aCornerRadii, aShadowColor, aBackendType));
+ if (blur) {
+ MarkUsed(blur);
+ }
+
+ return blur;
+ }
+
+ BlurCacheData* LookupInsetBoxShadow(const IntSize& aOuterMinSize,
+ const IntSize& aInnerMinSize,
+ const IntSize& aBlurRadius,
+ const RectCornerRadii* aCornerRadii,
+ const sRGBColor& aShadowColor,
+ BackendType aBackendType) {
+ bool insetBoxShadow = true;
+ BlurCacheKey key(aOuterMinSize, aInnerMinSize, aBlurRadius, aCornerRadii,
+ aShadowColor, insetBoxShadow, aBackendType);
+ BlurCacheData* blur = mHashEntries.Get(key);
+ if (blur) {
+ MarkUsed(blur);
+ }
+
+ return blur;
+ }
+
+ void RegisterEntry(UniquePtr<BlurCacheData> aValue) {
+ nsresult rv = AddObject(aValue.get());
+ if (NS_FAILED(rv)) {
+ // We are OOM, and we cannot track this object. We don't want stall
+ // entries in the hash table (since the expiration tracker is responsible
+ // for removing the cache entries), so we avoid putting that entry in the
+ // table, which is a good thing considering we are short on memory
+ // anyway, we probably don't want to retain things.
+ return;
+ }
+ mHashEntries.InsertOrUpdate(aValue->mKey, std::move(aValue));
+ }
+
+ protected:
+ static const uint32_t GENERATION_MS = 1000;
+ /**
+ * FIXME use nsTHashtable to avoid duplicating the BlurCacheKey.
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47
+ */
+ nsClassHashtable<BlurCacheKey, BlurCacheData> mHashEntries;
+};
+
+static BlurCache* gBlurCache = nullptr;
+
+static IntSize ComputeMinSizeForShadowShape(const RectCornerRadii* aCornerRadii,
+ const IntSize& aBlurRadius,
+ IntMargin& aOutSlice,
+ const IntSize& aRectSize) {
+ Size cornerSize(0, 0);
+ if (aCornerRadii) {
+ const RectCornerRadii& corners = *aCornerRadii;
+ for (const auto i : mozilla::AllPhysicalCorners()) {
+ cornerSize.width = std::max(cornerSize.width, corners[i].width);
+ cornerSize.height = std::max(cornerSize.height, corners[i].height);
+ }
+ }
+
+ IntSize margin = IntSize::Ceil(cornerSize) + aBlurRadius;
+ aOutSlice =
+ IntMargin(margin.height, margin.width, margin.height, margin.width);
+
+ IntSize minSize(aOutSlice.LeftRight() + 1, aOutSlice.TopBottom() + 1);
+
+ // If aRectSize is smaller than minSize, the border-image approach won't
+ // work; there's no way to squeeze parts of the min box-shadow source
+ // image such that the result looks correct. So we need to adjust minSize
+ // in such a way that we can later draw it without stretching in the affected
+ // dimension. We also need to adjust "slice" to ensure that we're not trying
+ // to slice away more than we have.
+ if (aRectSize.width < minSize.width) {
+ minSize.width = aRectSize.width;
+ aOutSlice.left = 0;
+ aOutSlice.right = 0;
+ }
+ if (aRectSize.height < minSize.height) {
+ minSize.height = aRectSize.height;
+ aOutSlice.top = 0;
+ aOutSlice.bottom = 0;
+ }
+
+ MOZ_ASSERT(aOutSlice.LeftRight() <= minSize.width);
+ MOZ_ASSERT(aOutSlice.TopBottom() <= minSize.height);
+ return minSize;
+}
+
+static void CacheBlur(DrawTarget* aDT, const IntSize& aMinSize,
+ const IntSize& aBlurRadius,
+ const RectCornerRadii* aCornerRadii,
+ const sRGBColor& aShadowColor,
+ const IntMargin& aBlurMargin, SourceSurface* aBoxShadow) {
+ gBlurCache->RegisterEntry(MakeUnique<BlurCacheData>(
+ aBoxShadow, aBlurMargin,
+ BlurCacheKey(aMinSize, aBlurRadius, aCornerRadii, aShadowColor,
+ aDT->GetBackendType())));
+}
+
+// Blurs a small surface and creates the colored box shadow.
+static already_AddRefed<SourceSurface> CreateBoxShadow(
+ DrawTarget* aDestDrawTarget, const IntSize& aMinSize,
+ const RectCornerRadii* aCornerRadii, const IntSize& aBlurRadius,
+ const sRGBColor& aShadowColor, bool aMirrorCorners,
+ IntMargin& aOutBlurMargin) {
+ gfxAlphaBoxBlur blur;
+ Rect minRect(Point(0, 0), Size(aMinSize));
+ Rect blurRect(minRect);
+ // If mirroring corners, we only need to draw the top-left quadrant.
+ // Use ceil to preserve the remaining 1x1 middle area for minimized box
+ // shadows.
+ if (aMirrorCorners) {
+ blurRect.SizeTo(ceil(blurRect.Width() * 0.5f),
+ ceil(blurRect.Height() * 0.5f));
+ }
+ IntSize zeroSpread(0, 0);
+ RefPtr<DrawTarget> blurDT =
+ blur.InitDrawTarget(aDestDrawTarget, blurRect, zeroSpread, aBlurRadius);
+ if (!blurDT) {
+ return nullptr;
+ }
+
+ ColorPattern black(DeviceColor::MaskOpaqueBlack());
+
+ if (aCornerRadii) {
+ RefPtr<Path> roundedRect =
+ MakePathForRoundedRect(*blurDT, minRect, *aCornerRadii);
+ blurDT->Fill(roundedRect, black);
+ } else {
+ blurDT->FillRect(minRect, black);
+ }
+
+ IntPoint topLeft;
+ RefPtr<SourceSurface> result = blur.DoBlur(&aShadowColor, &topLeft);
+ if (!result) {
+ return nullptr;
+ }
+
+ // Since blurRect is at (0, 0), we can find the inflated margin by
+ // negating the new rect origin, which would have been negative if
+ // the rect was inflated.
+ aOutBlurMargin = IntMargin(-topLeft.y, -topLeft.x, -topLeft.y, -topLeft.x);
+
+ return result.forget();
+}
+
+static already_AddRefed<SourceSurface> GetBlur(
+ gfxContext* aDestinationCtx, const IntSize& aRectSize,
+ const IntSize& aBlurRadius, const RectCornerRadii* aCornerRadii,
+ const sRGBColor& aShadowColor, bool aMirrorCorners,
+ IntMargin& aOutBlurMargin, IntMargin& aOutSlice, IntSize& aOutMinSize) {
+ if (!gBlurCache) {
+ gBlurCache = new BlurCache();
+ }
+
+ IntSize minSize = ComputeMinSizeForShadowShape(aCornerRadii, aBlurRadius,
+ aOutSlice, aRectSize);
+
+ // We can get seams using the min size rect when drawing to the destination
+ // rect if we have a non-pixel aligned destination transformation. In those
+ // cases, fallback to just rendering the destination rect. During printing, we
+ // record all the Moz 2d commands and replay them on the parent side with
+ // Cairo. Cairo printing uses StretchDIBits to stretch the surface. However,
+ // since our source image is only 1px for some parts, we make thousands of
+ // calls. Instead just render the blur ourself here as one image and send it
+ // over for printing.
+ // TODO: May need to change this with the blob renderer in WR since it also
+ // records.
+ Matrix destMatrix = aDestinationCtx->CurrentMatrix();
+ bool useDestRect = !destMatrix.IsRectilinear() ||
+ destMatrix.HasNonIntegerTranslation() ||
+ aDestinationCtx->GetDrawTarget()->IsRecording();
+ if (useDestRect) {
+ minSize = aRectSize;
+ }
+
+ int32_t maxTextureSize = gfxPlatform::MaxTextureSize();
+ if (minSize.width > maxTextureSize || minSize.height > maxTextureSize) {
+ return nullptr;
+ }
+
+ aOutMinSize = minSize;
+
+ DrawTarget* destDT = aDestinationCtx->GetDrawTarget();
+
+ if (!useDestRect) {
+ BlurCacheData* cached =
+ gBlurCache->Lookup(minSize, aBlurRadius, aCornerRadii, aShadowColor,
+ destDT->GetBackendType());
+ if (cached) {
+ // See CreateBoxShadow() for these values
+ aOutBlurMargin = cached->mBlurMargin;
+ RefPtr<SourceSurface> blur = cached->mBlur;
+ return blur.forget();
+ }
+ }
+
+ RefPtr<SourceSurface> boxShadow =
+ CreateBoxShadow(destDT, minSize, aCornerRadii, aBlurRadius, aShadowColor,
+ aMirrorCorners, aOutBlurMargin);
+ if (!boxShadow) {
+ return nullptr;
+ }
+
+ if (RefPtr<SourceSurface> opt = destDT->OptimizeSourceSurface(boxShadow)) {
+ boxShadow = opt;
+ }
+
+ if (!useDestRect) {
+ CacheBlur(destDT, minSize, aBlurRadius, aCornerRadii, aShadowColor,
+ aOutBlurMargin, boxShadow);
+ }
+ return boxShadow.forget();
+}
+
+void gfxAlphaBoxBlur::ShutdownBlurCache() {
+ delete gBlurCache;
+ gBlurCache = nullptr;
+}
+
+static Rect RectWithEdgesTRBL(Float aTop, Float aRight, Float aBottom,
+ Float aLeft) {
+ return Rect(aLeft, aTop, aRight - aLeft, aBottom - aTop);
+}
+
+static bool ShouldStretchSurface(DrawTarget* aDT, SourceSurface* aSurface) {
+ // Use stretching if possible, since it leads to less seams when the
+ // destination is transformed. However, don't do this if we're using cairo,
+ // because if cairo is using pixman it won't render anything for large
+ // stretch factors because pixman's internal fixed point precision is not
+ // high enough to handle those scale factors.
+ return aDT->GetBackendType() != BackendType::CAIRO;
+}
+
+static void RepeatOrStretchSurface(DrawTarget* aDT, SourceSurface* aSurface,
+ const Rect& aDest, const Rect& aSrc,
+ const Rect& aSkipRect) {
+ if (aSkipRect.Contains(aDest)) {
+ return;
+ }
+
+ if (ShouldStretchSurface(aDT, aSurface)) {
+ aDT->DrawSurface(aSurface, aDest, aSrc);
+ return;
+ }
+
+ SurfacePattern pattern(aSurface, ExtendMode::REPEAT,
+ Matrix::Translation(aDest.TopLeft() - aSrc.TopLeft()),
+ SamplingFilter::GOOD, RoundedToInt(aSrc));
+ aDT->FillRect(aDest, pattern);
+}
+
+static void DrawCorner(DrawTarget* aDT, SourceSurface* aSurface,
+ const Rect& aDest, const Rect& aSrc,
+ const Rect& aSkipRect) {
+ if (aSkipRect.Contains(aDest)) {
+ return;
+ }
+
+ aDT->DrawSurface(aSurface, aDest, aSrc);
+}
+
+static void DrawMinBoxShadow(DrawTarget* aDestDrawTarget,
+ SourceSurface* aSourceBlur, const Rect& aDstOuter,
+ const Rect& aDstInner, const Rect& aSrcOuter,
+ const Rect& aSrcInner, const Rect& aSkipRect,
+ bool aMiddle = false) {
+ // Corners: top left, top right, bottom left, bottom right
+ DrawCorner(aDestDrawTarget, aSourceBlur,
+ RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.X(), aDstInner.Y(),
+ aDstOuter.X()),
+ RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.X(), aSrcInner.Y(),
+ aSrcOuter.X()),
+ aSkipRect);
+
+ DrawCorner(aDestDrawTarget, aSourceBlur,
+ RectWithEdgesTRBL(aDstOuter.Y(), aDstOuter.XMost(), aDstInner.Y(),
+ aDstInner.XMost()),
+ RectWithEdgesTRBL(aSrcOuter.Y(), aSrcOuter.XMost(), aSrcInner.Y(),
+ aSrcInner.XMost()),
+ aSkipRect);
+
+ DrawCorner(aDestDrawTarget, aSourceBlur,
+ RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.X(),
+ aDstOuter.YMost(), aDstOuter.X()),
+ RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.X(),
+ aSrcOuter.YMost(), aSrcOuter.X()),
+ aSkipRect);
+
+ DrawCorner(aDestDrawTarget, aSourceBlur,
+ RectWithEdgesTRBL(aDstInner.YMost(), aDstOuter.XMost(),
+ aDstOuter.YMost(), aDstInner.XMost()),
+ RectWithEdgesTRBL(aSrcInner.YMost(), aSrcOuter.XMost(),
+ aSrcOuter.YMost(), aSrcInner.XMost()),
+ aSkipRect);
+
+ // Edges: top, left, right, bottom
+ RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
+ RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.XMost(),
+ aDstInner.Y(), aDstInner.X()),
+ RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(),
+ aSrcInner.Y(), aSrcInner.X()),
+ aSkipRect);
+ RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
+ RectWithEdgesTRBL(aDstInner.Y(), aDstInner.X(),
+ aDstInner.YMost(), aDstOuter.X()),
+ RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(),
+ aSrcInner.YMost(), aSrcOuter.X()),
+ aSkipRect);
+
+ RepeatOrStretchSurface(
+ aDestDrawTarget, aSourceBlur,
+ RectWithEdgesTRBL(aDstInner.Y(), aDstOuter.XMost(), aDstInner.YMost(),
+ aDstInner.XMost()),
+ RectWithEdgesTRBL(aSrcInner.Y(), aSrcOuter.XMost(), aSrcInner.YMost(),
+ aSrcInner.XMost()),
+ aSkipRect);
+ RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
+ RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.XMost(),
+ aDstOuter.YMost(), aDstInner.X()),
+ RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.XMost(),
+ aSrcOuter.YMost(), aSrcInner.X()),
+ aSkipRect);
+
+ // Middle part
+ if (aMiddle) {
+ RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
+ RectWithEdgesTRBL(aDstInner.Y(), aDstInner.XMost(),
+ aDstInner.YMost(), aDstInner.X()),
+ RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.XMost(),
+ aSrcInner.YMost(), aSrcInner.X()),
+ aSkipRect);
+ }
+}
+
+static void DrawMirroredRect(DrawTarget* aDT, SourceSurface* aSurface,
+ const Rect& aDest, const Point& aSrc,
+ Float aScaleX, Float aScaleY) {
+ SurfacePattern pattern(
+ aSurface, ExtendMode::CLAMP,
+ Matrix::Scaling(aScaleX, aScaleY)
+ .PreTranslate(-aSrc)
+ .PostTranslate(aScaleX < 0 ? aDest.XMost() : aDest.X(),
+ aScaleY < 0 ? aDest.YMost() : aDest.Y()));
+ aDT->FillRect(aDest, pattern);
+}
+
+static void DrawMirroredBoxShadow(DrawTarget* aDT, SourceSurface* aSurface,
+ const Rect& aDestRect) {
+ Point center(ceil(aDestRect.X() + aDestRect.Width() / 2),
+ ceil(aDestRect.Y() + aDestRect.Height() / 2));
+ Rect topLeft(aDestRect.X(), aDestRect.Y(), center.x - aDestRect.X(),
+ center.y - aDestRect.Y());
+ Rect bottomRight(topLeft.BottomRight(), aDestRect.Size() - topLeft.Size());
+ Rect topRight(bottomRight.X(), topLeft.Y(), bottomRight.Width(),
+ topLeft.Height());
+ Rect bottomLeft(topLeft.X(), bottomRight.Y(), topLeft.Width(),
+ bottomRight.Height());
+ DrawMirroredRect(aDT, aSurface, topLeft, Point(), 1, 1);
+ DrawMirroredRect(aDT, aSurface, topRight, Point(), -1, 1);
+ DrawMirroredRect(aDT, aSurface, bottomLeft, Point(), 1, -1);
+ DrawMirroredRect(aDT, aSurface, bottomRight, Point(), -1, -1);
+}
+
+static void DrawMirroredCorner(DrawTarget* aDT, SourceSurface* aSurface,
+ const Rect& aDest, const Point& aSrc,
+ const Rect& aSkipRect, Float aScaleX,
+ Float aScaleY) {
+ if (aSkipRect.Contains(aDest)) {
+ return;
+ }
+
+ DrawMirroredRect(aDT, aSurface, aDest, aSrc, aScaleX, aScaleY);
+}
+
+static void RepeatOrStretchMirroredSurface(DrawTarget* aDT,
+ SourceSurface* aSurface,
+ const Rect& aDest, const Rect& aSrc,
+ const Rect& aSkipRect, Float aScaleX,
+ Float aScaleY) {
+ if (aSkipRect.Contains(aDest)) {
+ return;
+ }
+
+ if (ShouldStretchSurface(aDT, aSurface)) {
+ aScaleX *= aDest.Width() / aSrc.Width();
+ aScaleY *= aDest.Height() / aSrc.Height();
+ DrawMirroredRect(aDT, aSurface, aDest, aSrc.TopLeft(), aScaleX, aScaleY);
+ return;
+ }
+
+ SurfacePattern pattern(
+ aSurface, ExtendMode::REPEAT,
+ Matrix::Scaling(aScaleX, aScaleY)
+ .PreTranslate(-aSrc.TopLeft())
+ .PostTranslate(aScaleX < 0 ? aDest.XMost() : aDest.X(),
+ aScaleY < 0 ? aDest.YMost() : aDest.Y()),
+ SamplingFilter::GOOD, RoundedToInt(aSrc));
+ aDT->FillRect(aDest, pattern);
+}
+
+static void DrawMirroredMinBoxShadow(
+ DrawTarget* aDestDrawTarget, SourceSurface* aSourceBlur,
+ const Rect& aDstOuter, const Rect& aDstInner, const Rect& aSrcOuter,
+ const Rect& aSrcInner, const Rect& aSkipRect, bool aMiddle = false) {
+ // Corners: top left, top right, bottom left, bottom right
+ // Compute quadrant bounds and then clip them to corners along
+ // dimensions where we need to stretch from min size.
+ Point center(ceil(aDstOuter.X() + aDstOuter.Width() / 2),
+ ceil(aDstOuter.Y() + aDstOuter.Height() / 2));
+ Rect topLeft(aDstOuter.X(), aDstOuter.Y(), center.x - aDstOuter.X(),
+ center.y - aDstOuter.Y());
+ Rect bottomRight(topLeft.BottomRight(), aDstOuter.Size() - topLeft.Size());
+ Rect topRight(bottomRight.X(), topLeft.Y(), bottomRight.Width(),
+ topLeft.Height());
+ Rect bottomLeft(topLeft.X(), bottomRight.Y(), topLeft.Width(),
+ bottomRight.Height());
+
+ // Check if the middle part has been minimized along each dimension.
+ // If so, those will be strecthed/drawn separately and need to be clipped out.
+ if (aSrcInner.Width() == 1) {
+ topLeft.SetRightEdge(aDstInner.X());
+ topRight.SetLeftEdge(aDstInner.XMost());
+ bottomLeft.SetRightEdge(aDstInner.X());
+ bottomRight.SetLeftEdge(aDstInner.XMost());
+ }
+ if (aSrcInner.Height() == 1) {
+ topLeft.SetBottomEdge(aDstInner.Y());
+ topRight.SetBottomEdge(aDstInner.Y());
+ bottomLeft.SetTopEdge(aDstInner.YMost());
+ bottomRight.SetTopEdge(aDstInner.YMost());
+ }
+
+ DrawMirroredCorner(aDestDrawTarget, aSourceBlur, topLeft, aSrcOuter.TopLeft(),
+ aSkipRect, 1, 1);
+ DrawMirroredCorner(aDestDrawTarget, aSourceBlur, topRight,
+ aSrcOuter.TopLeft(), aSkipRect, -1, 1);
+ DrawMirroredCorner(aDestDrawTarget, aSourceBlur, bottomLeft,
+ aSrcOuter.TopLeft(), aSkipRect, 1, -1);
+ DrawMirroredCorner(aDestDrawTarget, aSourceBlur, bottomRight,
+ aSrcOuter.TopLeft(), aSkipRect, -1, -1);
+
+ // Edges: top, bottom, left, right
+ // Draw middle edges where they need to be stretched. The top and left
+ // sections that are part of the top-left quadrant will be mirrored to
+ // the bottom and right sections, respectively.
+ if (aSrcInner.Width() == 1) {
+ Rect dstTop = RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.XMost(),
+ aDstInner.Y(), aDstInner.X());
+ Rect srcTop = RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(),
+ aSrcInner.Y(), aSrcInner.X());
+ Rect dstBottom = RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.XMost(),
+ aDstOuter.YMost(), aDstInner.X());
+ Rect srcBottom = RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(),
+ aSrcInner.Y(), aSrcInner.X());
+ // If we only need to stretch along the X axis and we're drawing
+ // the middle section, just sample all the way to the center of the
+ // source on the Y axis to avoid extra draw calls.
+ if (aMiddle && aSrcInner.Height() != 1) {
+ dstTop.SetBottomEdge(center.y);
+ srcTop.SetHeight(dstTop.Height());
+ dstBottom.SetTopEdge(dstTop.YMost());
+ srcBottom.SetHeight(dstBottom.Height());
+ }
+ RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstTop, srcTop,
+ aSkipRect, 1, 1);
+ RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstBottom,
+ srcBottom, aSkipRect, 1, -1);
+ }
+
+ if (aSrcInner.Height() == 1) {
+ Rect dstLeft = RectWithEdgesTRBL(aDstInner.Y(), aDstInner.X(),
+ aDstInner.YMost(), aDstOuter.X());
+ Rect srcLeft = RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(),
+ aSrcInner.YMost(), aSrcOuter.X());
+ Rect dstRight = RectWithEdgesTRBL(aDstInner.Y(), aDstOuter.XMost(),
+ aDstInner.YMost(), aDstInner.XMost());
+ Rect srcRight = RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(),
+ aSrcInner.YMost(), aSrcOuter.X());
+ // Only stretching on Y axis, so sample source to the center of the X axis.
+ if (aMiddle && aSrcInner.Width() != 1) {
+ dstLeft.SetRightEdge(center.x);
+ srcLeft.SetWidth(dstLeft.Width());
+ dstRight.SetLeftEdge(dstLeft.XMost());
+ srcRight.SetWidth(dstRight.Width());
+ }
+ RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstLeft,
+ srcLeft, aSkipRect, 1, 1);
+ RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstRight,
+ srcRight, aSkipRect, -1, 1);
+ }
+
+ // If we need to stretch along both dimensions, then the middle part
+ // must be drawn separately.
+ if (aMiddle && aSrcInner.Width() == 1 && aSrcInner.Height() == 1) {
+ RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
+ RectWithEdgesTRBL(aDstInner.Y(), aDstInner.XMost(),
+ aDstInner.YMost(), aDstInner.X()),
+ RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.XMost(),
+ aSrcInner.YMost(), aSrcInner.X()),
+ aSkipRect);
+ }
+}
+
+/***
+ * We draw a blurred a rectangle by only blurring a smaller rectangle and
+ * splitting the rectangle into 9 parts.
+ * First, a small minimum source rect is calculated and used to create a blur
+ * mask since the actual blurring itself is expensive. Next, we use the mask
+ * with the given shadow color to create a minimally-sized box shadow of the
+ * right color. Finally, we cut out the 9 parts from the box-shadow source and
+ * paint each part in the right place, stretching the non-corner parts to fill
+ * the space between the corners.
+ */
+
+/* static */
+void gfxAlphaBoxBlur::BlurRectangle(gfxContext* aDestinationCtx,
+ const gfxRect& aRect,
+ const RectCornerRadii* aCornerRadii,
+ const gfxPoint& aBlurStdDev,
+ const sRGBColor& aShadowColor,
+ const gfxRect& aDirtyRect,
+ const gfxRect& aSkipRect) {
+ if (!RectIsInt32Safe(ToRect(aRect))) {
+ return;
+ }
+
+ IntSize blurRadius = CalculateBlurRadius(aBlurStdDev);
+ bool mirrorCorners = !aCornerRadii || aCornerRadii->AreRadiiSame();
+
+ IntRect rect = RoundedToInt(ToRect(aRect));
+ IntMargin blurMargin;
+ IntMargin slice;
+ IntSize minSize;
+ RefPtr<SourceSurface> boxShadow =
+ GetBlur(aDestinationCtx, rect.Size(), blurRadius, aCornerRadii,
+ aShadowColor, mirrorCorners, blurMargin, slice, minSize);
+ if (!boxShadow) {
+ return;
+ }
+
+ DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
+ destDrawTarget->PushClipRect(ToRect(aDirtyRect));
+
+ // Copy the right parts from boxShadow into destDrawTarget. The middle parts
+ // will be stretched, border-image style.
+
+ Rect srcOuter(Point(blurMargin.left, blurMargin.top), Size(minSize));
+ Rect srcInner(srcOuter);
+ srcOuter.Inflate(Margin(blurMargin));
+ srcInner.Deflate(Margin(slice));
+
+ Rect dstOuter(rect);
+ Rect dstInner(rect);
+ dstOuter.Inflate(Margin(blurMargin));
+ dstInner.Deflate(Margin(slice));
+
+ Rect skipRect = ToRect(aSkipRect);
+
+ if (minSize == rect.Size()) {
+ // The target rect is smaller than the minimal size so just draw the surface
+ if (mirrorCorners) {
+ DrawMirroredBoxShadow(destDrawTarget, boxShadow, dstOuter);
+ } else {
+ destDrawTarget->DrawSurface(boxShadow, dstOuter, srcOuter);
+ }
+ } else {
+ if (mirrorCorners) {
+ DrawMirroredMinBoxShadow(destDrawTarget, boxShadow, dstOuter, dstInner,
+ srcOuter, srcInner, skipRect, true);
+ } else {
+ DrawMinBoxShadow(destDrawTarget, boxShadow, dstOuter, dstInner, srcOuter,
+ srcInner, skipRect, true);
+ }
+ }
+
+ // A note about anti-aliasing and seems between adjacent parts:
+ // We don't explicitly disable anti-aliasing in the DrawSurface calls above,
+ // so if there's a transform on destDrawTarget that is not pixel-aligned,
+ // there will be seams between adjacent parts of the box-shadow. It's hard to
+ // avoid those without the use of an intermediate surface.
+ // You might think that we could avoid those by just turning off AA, but there
+ // is a problem with that: Box-shadow rendering needs to clip out the
+ // element's border box, and we'd like that clip to have anti-aliasing -
+ // especially if the element has rounded corners! So we can't do that unless
+ // we have a way to say "Please anti-alias the clip, but don't antialias the
+ // destination rect of the DrawSurface call".
+
+ destDrawTarget->PopClip();
+}
+
+static already_AddRefed<Path> GetBoxShadowInsetPath(
+ DrawTarget* aDrawTarget, const Rect aOuterRect, const Rect aInnerRect,
+ const RectCornerRadii* aInnerClipRadii) {
+ /***
+ * We create an inset path by having two rects.
+ *
+ * -----------------------
+ * | ________________ |
+ * | | | |
+ * | | | |
+ * | ------------------ |
+ * |_____________________|
+ *
+ * The outer rect and the inside rect. The path
+ * creates a frame around the content where we draw the inset shadow.
+ */
+ RefPtr<PathBuilder> builder =
+ aDrawTarget->CreatePathBuilder(FillRule::FILL_EVEN_ODD);
+ AppendRectToPath(builder, aOuterRect, true);
+
+ if (aInnerClipRadii) {
+ AppendRoundedRectToPath(builder, aInnerRect, *aInnerClipRadii, false);
+ } else {
+ AppendRectToPath(builder, aInnerRect, false);
+ }
+ return builder->Finish();
+}
+
+static void FillDestinationPath(
+ gfxContext* aDestinationCtx, const Rect& aDestinationRect,
+ const Rect& aShadowClipRect, const sRGBColor& aShadowColor,
+ const RectCornerRadii* aInnerClipRadii = nullptr) {
+ // When there is no blur radius, fill the path onto the destination
+ // surface.
+ aDestinationCtx->SetColor(aShadowColor);
+ DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
+ RefPtr<Path> shadowPath = GetBoxShadowInsetPath(
+ destDrawTarget, aDestinationRect, aShadowClipRect, aInnerClipRadii);
+
+ aDestinationCtx->SetPath(shadowPath);
+ aDestinationCtx->Fill();
+}
+
+static void CacheInsetBlur(const IntSize& aMinOuterSize,
+ const IntSize& aMinInnerSize,
+ const IntSize& aBlurRadius,
+ const RectCornerRadii* aCornerRadii,
+ const sRGBColor& aShadowColor,
+ BackendType aBackendType,
+ SourceSurface* aBoxShadow) {
+ bool isInsetBlur = true;
+ BlurCacheKey key(aMinOuterSize, aMinInnerSize, aBlurRadius, aCornerRadii,
+ aShadowColor, isInsetBlur, aBackendType);
+ IntMargin blurMargin(0, 0, 0, 0);
+
+ gBlurCache->RegisterEntry(
+ MakeUnique<BlurCacheData>(aBoxShadow, blurMargin, std::move(key)));
+}
+
+already_AddRefed<SourceSurface> gfxAlphaBoxBlur::GetInsetBlur(
+ const Rect& aOuterRect, const Rect& aWhitespaceRect, bool aIsDestRect,
+ const sRGBColor& aShadowColor, const IntSize& aBlurRadius,
+ const RectCornerRadii* aInnerClipRadii, DrawTarget* aDestDrawTarget,
+ bool aMirrorCorners) {
+ if (!gBlurCache) {
+ gBlurCache = new BlurCache();
+ }
+
+ IntSize outerSize = IntSize::Truncate(aOuterRect.Size());
+ IntSize whitespaceSize = IntSize::Truncate(aWhitespaceRect.Size());
+ if (!aIsDestRect) {
+ BlurCacheData* cached = gBlurCache->LookupInsetBoxShadow(
+ outerSize, whitespaceSize, aBlurRadius, aInnerClipRadii, aShadowColor,
+ aDestDrawTarget->GetBackendType());
+ if (cached) {
+ // So we don't forget the actual cached blur
+ RefPtr<SourceSurface> cachedBlur = cached->mBlur;
+ return cachedBlur.forget();
+ }
+ }
+
+ // If we can do a min rect, the whitespace rect will be expanded in Init to
+ // aOuterRect.
+ Rect blurRect = aIsDestRect ? aOuterRect : aWhitespaceRect;
+ // If mirroring corners, we only need to draw the top-left quadrant.
+ // Use ceil to preserve the remaining 1x1 middle area for minimized box
+ // shadows.
+ if (aMirrorCorners) {
+ blurRect.SizeTo(ceil(blurRect.Width() * 0.5f),
+ ceil(blurRect.Height() * 0.5f));
+ }
+ IntSize zeroSpread(0, 0);
+ RefPtr<DrawTarget> minDrawTarget =
+ InitDrawTarget(aDestDrawTarget, blurRect, zeroSpread, aBlurRadius);
+ if (!minDrawTarget) {
+ return nullptr;
+ }
+
+ // This is really annoying. When we create the AlphaBoxBlur, the DrawTarget
+ // has a translation applied to it that is the topLeft point. This is actually
+ // the rect we gave it plus the blur radius. The rects we give this for the
+ // outer and whitespace rects are based at (0, 0). We could either translate
+ // those rects when we don't have a destination rect or ignore the translation
+ // when using the dest rect. The dest rects layout gives us expect this
+ // translation.
+ if (!aIsDestRect) {
+ minDrawTarget->SetTransform(Matrix());
+ }
+
+ // Fill in the path between the inside white space / outer rects
+ // NOT the inner frame
+ RefPtr<Path> maskPath = GetBoxShadowInsetPath(
+ minDrawTarget, aOuterRect, aWhitespaceRect, aInnerClipRadii);
+
+ ColorPattern black(DeviceColor::MaskOpaqueBlack());
+ minDrawTarget->Fill(maskPath, black);
+
+ // Blur and fill in with the color we actually wanted
+ RefPtr<SourceSurface> minInsetBlur = DoBlur(&aShadowColor);
+ if (!minInsetBlur) {
+ return nullptr;
+ }
+
+ if (RefPtr<SourceSurface> opt =
+ aDestDrawTarget->OptimizeSourceSurface(minInsetBlur)) {
+ minInsetBlur = opt;
+ }
+
+ if (!aIsDestRect) {
+ CacheInsetBlur(outerSize, whitespaceSize, aBlurRadius, aInnerClipRadii,
+ aShadowColor, aDestDrawTarget->GetBackendType(),
+ minInsetBlur);
+ }
+
+ return minInsetBlur.forget();
+}
+
+/***
+ * We create our minimal rect with 2 rects.
+ * The first is the inside whitespace rect, that is "cut out"
+ * from the box. This is (1). This must be the size
+ * of the blur radius + corner radius so we can have a big enough
+ * inside cut.
+ *
+ * The second (2) is one blur radius surrounding the inner
+ * frame of (1). This is the amount of blur space required
+ * to get a proper blend.
+ *
+ * B = one blur size
+ * W = one blur + corner radii - known as inner margin
+ * ___________________________________
+ * | |
+ * | | | |
+ * | (2) | (1) | (2) |
+ * | B | W | B |
+ * | | | |
+ * | | | |
+ * | | |
+ * |________________________________|
+ */
+static void GetBlurMargins(const RectCornerRadii* aInnerClipRadii,
+ const IntSize& aBlurRadius, Margin& aOutBlurMargin,
+ Margin& aOutInnerMargin) {
+ Size cornerSize(0, 0);
+ if (aInnerClipRadii) {
+ const RectCornerRadii& corners = *aInnerClipRadii;
+ for (const auto i : mozilla::AllPhysicalCorners()) {
+ cornerSize.width = std::max(cornerSize.width, corners[i].width);
+ cornerSize.height = std::max(cornerSize.height, corners[i].height);
+ }
+ }
+
+ // Only the inside whitespace size cares about the border radius size.
+ // Outer sizes only care about blur.
+ IntSize margin = IntSize::Ceil(cornerSize) + aBlurRadius;
+
+ aOutInnerMargin.SizeTo(margin.height, margin.width, margin.height,
+ margin.width);
+ aOutBlurMargin.SizeTo(aBlurRadius.height, aBlurRadius.width,
+ aBlurRadius.height, aBlurRadius.width);
+}
+
+static bool GetInsetBoxShadowRects(const Margin& aBlurMargin,
+ const Margin& aInnerMargin,
+ const Rect& aShadowClipRect,
+ const Rect& aDestinationRect,
+ Rect& aOutWhitespaceRect,
+ Rect& aOutOuterRect) {
+ // We always copy (2 * blur radius) + corner radius worth of data to the
+ // destination rect This covers the blend of the path + the actual blur Need
+ // +1 so that we copy the edges correctly as we'll copy over the min box
+ // shadow corners then the +1 for the edges between Note, the (x,y)
+ // coordinates are from the blur margin since the frame outside the whitespace
+ // rect is 1 blur radius extra space.
+ Rect insideWhiteSpace(aBlurMargin.left, aBlurMargin.top,
+ aInnerMargin.LeftRight() + 1,
+ aInnerMargin.TopBottom() + 1);
+
+ // If the inner white space rect is larger than the shadow clip rect
+ // our approach does not work as we'll just copy one corner
+ // and cover the destination. In those cases, fallback to the destination rect
+ bool useDestRect = (aShadowClipRect.Width() <= aInnerMargin.LeftRight()) ||
+ (aShadowClipRect.Height() <= aInnerMargin.TopBottom());
+
+ if (useDestRect) {
+ aOutWhitespaceRect = aShadowClipRect;
+ aOutOuterRect = aDestinationRect;
+ } else {
+ aOutWhitespaceRect = insideWhiteSpace;
+ aOutOuterRect = aOutWhitespaceRect;
+ aOutOuterRect.Inflate(aBlurMargin);
+ }
+
+ return useDestRect;
+}
+
+void gfxAlphaBoxBlur::BlurInsetBox(
+ gfxContext* aDestinationCtx, const Rect& aDestinationRect,
+ const Rect& aShadowClipRect, const IntSize& aBlurRadius,
+ const sRGBColor& aShadowColor, const RectCornerRadii* aInnerClipRadii,
+ const Rect& aSkipRect, const Point& aShadowOffset) {
+ if ((aBlurRadius.width == 0 && aBlurRadius.height == 0) ||
+ aShadowClipRect.IsEmpty()) {
+ FillDestinationPath(aDestinationCtx, aDestinationRect, aShadowClipRect,
+ aShadowColor, aInnerClipRadii);
+ return;
+ }
+
+ DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
+
+ Margin innerMargin;
+ Margin blurMargin;
+ GetBlurMargins(aInnerClipRadii, aBlurRadius, blurMargin, innerMargin);
+
+ Rect whitespaceRect;
+ Rect outerRect;
+ bool useDestRect =
+ GetInsetBoxShadowRects(blurMargin, innerMargin, aShadowClipRect,
+ aDestinationRect, whitespaceRect, outerRect);
+
+ // Check that the inset margin between the outer and whitespace rects is
+ // symmetric, and that all corner radii are the same, in which case the blur
+ // can be mirrored.
+ Margin checkMargin = outerRect - whitespaceRect;
+ bool mirrorCorners = checkMargin.left == checkMargin.right &&
+ checkMargin.top == checkMargin.bottom &&
+ (!aInnerClipRadii || aInnerClipRadii->AreRadiiSame());
+ RefPtr<SourceSurface> minBlur =
+ GetInsetBlur(outerRect, whitespaceRect, useDestRect, aShadowColor,
+ aBlurRadius, aInnerClipRadii, destDrawTarget, mirrorCorners);
+ if (!minBlur) {
+ return;
+ }
+
+ if (useDestRect) {
+ Rect destBlur = aDestinationRect;
+ destBlur.Inflate(blurMargin);
+ if (mirrorCorners) {
+ DrawMirroredBoxShadow(destDrawTarget, minBlur.get(), destBlur);
+ } else {
+ Rect srcBlur(Point(0, 0), Size(minBlur->GetSize()));
+ MOZ_ASSERT(RoundedOut(srcBlur).Size() == RoundedOut(destBlur).Size());
+ destDrawTarget->DrawSurface(minBlur, destBlur, srcBlur);
+ }
+ } else {
+ Rect srcOuter(outerRect);
+ Rect srcInner(srcOuter);
+ srcInner.Deflate(blurMargin); // The outer color fill
+ srcInner.Deflate(innerMargin); // The inner whitespace
+
+ // The shadow clip rect already takes into account the spread radius
+ Rect outerFillRect(aShadowClipRect);
+ outerFillRect.Inflate(blurMargin);
+ FillDestinationPath(aDestinationCtx, aDestinationRect, outerFillRect,
+ aShadowColor);
+
+ // Inflate once for the frame around the whitespace
+ Rect destRect(aShadowClipRect);
+ destRect.Inflate(blurMargin);
+
+ // Deflate for the blurred in white space
+ Rect destInnerRect(aShadowClipRect);
+ destInnerRect.Deflate(innerMargin);
+
+ if (mirrorCorners) {
+ DrawMirroredMinBoxShadow(destDrawTarget, minBlur, destRect, destInnerRect,
+ srcOuter, srcInner, aSkipRect);
+ } else {
+ DrawMinBoxShadow(destDrawTarget, minBlur, destRect, destInnerRect,
+ srcOuter, srcInner, aSkipRect);
+ }
+ }
+}
diff --git a/gfx/thebes/gfxBlur.h b/gfx/thebes/gfxBlur.h
new file mode 100644
index 0000000000..da6ccae9a7
--- /dev/null
+++ b/gfx/thebes/gfxBlur.h
@@ -0,0 +1,203 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_BLUR_H
+#define GFX_BLUR_H
+
+#include "gfxTypes.h"
+#include "gfxRect.h"
+#include "nsSize.h"
+#include "gfxPoint.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/gfx/Blur.h"
+
+class gfxContext;
+
+namespace mozilla {
+namespace gfx {
+struct sRGBColor;
+struct RectCornerRadii;
+class SourceSurface;
+class DrawTarget;
+} // namespace gfx
+} // namespace mozilla
+
+/**
+ * Implementation of a triple box blur approximation of a Gaussian blur.
+ *
+ * A Gaussian blur is good for blurring because, when done independently
+ * in the horizontal and vertical directions, it matches the result that
+ * would be obtained using a different (rotated) set of axes. A triple
+ * box blur is a very close approximation of a Gaussian.
+ *
+ * Creates an 8-bit alpha channel context for callers to draw in,
+ * spreads the contents of that context, blurs the contents, and applies
+ * it as an alpha mask on a different existing context.
+ *
+ * A spread N makes each output pixel the maximum value of all source
+ * pixels within a square of side length 2N+1 centered on the output pixel.
+ *
+ * A temporary surface is created in the Init function. The caller then draws
+ * any desired content onto the context acquired through GetContext, and lastly
+ * calls Paint to apply the blurred content as an alpha mask.
+ */
+class gfxAlphaBoxBlur final {
+ typedef mozilla::gfx::sRGBColor sRGBColor;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
+
+ public:
+ gfxAlphaBoxBlur() = default;
+
+ ~gfxAlphaBoxBlur();
+
+ /**
+ * Constructs a box blur and initializes the temporary surface.
+ *
+ * @param aDestinationCtx The destination to blur to.
+ *
+ * @param aRect The coordinates of the surface to create in device units.
+ *
+ * @param aBlurRadius The blur radius in pixels. This is the radius of
+ * the entire (triple) kernel function. Each individual box blur has
+ * radius approximately 1/3 this value, or diameter approximately 2/3
+ * this value. This parameter should nearly always be computed using
+ * CalculateBlurRadius, below.
+ *
+ * @param aDirtyRect A pointer to a dirty rect, measured in device units,
+ * if available. This will be used for optimizing the blur operation. It
+ * is safe to pass nullptr here.
+ *
+ * @param aSkipRect A pointer to a rect, measured in device units, that
+ * represents an area where blurring is unnecessary and shouldn't be done
+ * for speed reasons. It is safe to pass nullptr here.
+ *
+ * @param aUseHardwareAccel Flag to state whether or not we can use hardware
+ * acceleration to speed up this blur.
+ */
+ mozilla::UniquePtr<gfxContext> Init(
+ gfxContext* aDestinationCtx, const gfxRect& aRect,
+ const mozilla::gfx::IntSize& aSpreadRadius,
+ const mozilla::gfx::IntSize& aBlurRadius, const gfxRect* aDirtyRect,
+ const gfxRect* aSkipRect, bool aUseHardwareAccel = true);
+
+ already_AddRefed<DrawTarget> InitDrawTarget(
+ const mozilla::gfx::DrawTarget* aReferenceDT,
+ const mozilla::gfx::Rect& aRect,
+ const mozilla::gfx::IntSize& aSpreadRadius,
+ const mozilla::gfx::IntSize& aBlurRadius,
+ const mozilla::gfx::Rect* aDirtyRect = nullptr,
+ const mozilla::gfx::Rect* aSkipRect = nullptr,
+ bool aUseHardwareAccel = true);
+
+ /**
+ * Performs the blur and optionally colors the result if aShadowColor is not
+ * null.
+ */
+ already_AddRefed<mozilla::gfx::SourceSurface> DoBlur(
+ const mozilla::gfx::sRGBColor* aShadowColor = nullptr,
+ mozilla::gfx::IntPoint* aOutTopLeft = nullptr);
+
+ /**
+ * Does the actual blurring/spreading and mask applying. Users of this
+ * object must have drawn whatever they want to be blurred onto the internal
+ * gfxContext returned by GetContext before calling this.
+ *
+ * @param aDestinationCtx The graphics context on which to apply the
+ * blurred mask.
+ */
+ void Paint(gfxContext* aDestinationCtx);
+
+ /**
+ * Calculates a blur radius that, when used with box blur, approximates
+ * a Gaussian blur with the given standard deviation. The result of
+ * this function should be used as the aBlurRadius parameter to Init,
+ * above.
+ */
+ static mozilla::gfx::IntSize CalculateBlurRadius(
+ const gfxPoint& aStandardDeviation);
+
+ /**
+ * Blurs a coloured rectangle onto aDestinationCtx. This is equivalent
+ * to calling Init(), drawing a rectangle onto the returned surface
+ * and then calling Paint, but may let us optimize better in the
+ * backend.
+ *
+ * @param aDestinationCtx The destination to blur to.
+ * @param aRect The rectangle to blur in device pixels.
+ * @param aCornerRadii Corner radii for aRect, if it is a rounded
+ * rectangle.
+ * @param aBlurRadius The standard deviation of the blur.
+ * @param aShadowColor The color to draw the blurred shadow.
+ * @param aDirtyRect An area in device pixels that is dirty and
+ * needs to be redrawn.
+ * @param aSkipRect An area in device pixels to avoid blurring
+ * over, to prevent unnecessary work.
+ */
+ static void BlurRectangle(gfxContext* aDestinationCtx, const gfxRect& aRect,
+ const RectCornerRadii* aCornerRadii,
+ const gfxPoint& aBlurStdDev,
+ const sRGBColor& aShadowColor,
+ const gfxRect& aDirtyRect,
+ const gfxRect& aSkipRect);
+
+ static void ShutdownBlurCache();
+
+ /***
+ * Blurs an inset box shadow according to a given path.
+ * This is equivalent to calling Init(), drawing the inset path,
+ * and calling paint. Do not call Init() if using this method.
+ *
+ * @param aDestinationCtx The destination to blur to.
+ * @param aDestinationRect The destination rect in device pixels
+ * @param aShadowClipRect The destiniation inner rect of the
+ * inset path in device pixels.
+ * @param aBlurRadius The standard deviation of the blur.
+ * @param aShadowColor The color of the blur.
+ * @param aInnerClipRadii Corner radii for the inside rect if it is a
+ * rounded rect.
+ * @param aSkipRect An area in device pixels we don't have to
+ * paint in.
+ */
+ void BlurInsetBox(gfxContext* aDestinationCtx,
+ const mozilla::gfx::Rect& aDestinationRect,
+ const mozilla::gfx::Rect& aShadowClipRect,
+ const mozilla::gfx::IntSize& aBlurRadius,
+ const mozilla::gfx::sRGBColor& aShadowColor,
+ const RectCornerRadii* aInnerClipRadii,
+ const mozilla::gfx::Rect& aSkipRect,
+ const mozilla::gfx::Point& aShadowOffset);
+
+ protected:
+ already_AddRefed<mozilla::gfx::SourceSurface> GetInsetBlur(
+ const mozilla::gfx::Rect& aOuterRect,
+ const mozilla::gfx::Rect& aWhitespaceRect, bool aIsDestRect,
+ const mozilla::gfx::sRGBColor& aShadowColor,
+ const mozilla::gfx::IntSize& aBlurRadius,
+ const RectCornerRadii* aInnerClipRadii, DrawTarget* aDestDrawTarget,
+ bool aMirrorCorners);
+
+ /**
+ * The DrawTarget of the temporary alpha surface.
+ */
+ RefPtr<DrawTarget> mDrawTarget;
+
+ /**
+ * The temporary alpha surface.
+ */
+ uint8_t* mData = nullptr;
+
+ /**
+ * The object that actually does the blurring for us.
+ */
+ mozilla::gfx::AlphaBoxBlur mBlur;
+
+ /**
+ * Indicates using DrawTarget-accelerated blurs.
+ */
+ bool mAccelerated = false;
+};
+
+#endif /* GFX_BLUR_H */
diff --git a/gfx/thebes/gfxColor.h b/gfx/thebes/gfxColor.h
new file mode 100644
index 0000000000..eec2b0b70e
--- /dev/null
+++ b/gfx/thebes/gfxColor.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_COLOR_H
+#define GFX_COLOR_H
+
+#include "mozilla/Attributes.h" // for MOZ_ALWAYS_INLINE
+#include "mozilla/gfx/Types.h" // for mozilla::gfx::SurfaceFormatBit
+
+/**
+ * Fast approximate division by 255. It has the property that
+ * for all 0 <= n <= 255*255, GFX_DIVIDE_BY_255(n) == n/255.
+ * But it only uses two adds and two shifts instead of an
+ * integer division (which is expensive on many processors).
+ *
+ * equivalent to ((v)/255)
+ */
+#define GFX_DIVIDE_BY_255(v) \
+ (((((unsigned)(v)) << 8) + ((unsigned)(v)) + 255) >> 16)
+
+/**
+ * Fast premultiply
+ *
+ * equivalent to (((c)*(a))/255)
+ */
+uint8_t MOZ_ALWAYS_INLINE gfxPreMultiply(uint8_t c, uint8_t a) {
+ return GFX_DIVIDE_BY_255((c) * (a));
+}
+
+/**
+ * Pack the 4 8-bit channels (A,R,G,B)
+ * into a 32-bit packed NON-premultiplied pixel.
+ */
+uint32_t MOZ_ALWAYS_INLINE gfxPackedPixelNoPreMultiply(uint8_t a, uint8_t r,
+ uint8_t g, uint8_t b) {
+ return (((a) << mozilla::gfx::SurfaceFormatBit::OS_A) |
+ ((r) << mozilla::gfx::SurfaceFormatBit::OS_R) |
+ ((g) << mozilla::gfx::SurfaceFormatBit::OS_G) |
+ ((b) << mozilla::gfx::SurfaceFormatBit::OS_B));
+}
+
+/**
+ * Pack the 4 8-bit channels (A,R,G,B)
+ * into a 32-bit packed premultiplied pixel.
+ */
+uint32_t MOZ_ALWAYS_INLINE gfxPackedPixel(uint8_t a, uint8_t r, uint8_t g,
+ uint8_t b) {
+ if (a == 0x00)
+ return 0x00000000;
+ else if (a == 0xFF) {
+ return gfxPackedPixelNoPreMultiply(a, r, g, b);
+ } else {
+ return ((a) << mozilla::gfx::SurfaceFormatBit::OS_A) |
+ (gfxPreMultiply(r, a) << mozilla::gfx::SurfaceFormatBit::OS_R) |
+ (gfxPreMultiply(g, a) << mozilla::gfx::SurfaceFormatBit::OS_G) |
+ (gfxPreMultiply(b, a) << mozilla::gfx::SurfaceFormatBit::OS_B);
+ }
+}
+
+#endif /* _GFX_COLOR_H */
diff --git a/gfx/thebes/gfxContext.cpp b/gfx/thebes/gfxContext.cpp
new file mode 100644
index 0000000000..43cb330e97
--- /dev/null
+++ b/gfx/thebes/gfxContext.cpp
@@ -0,0 +1,603 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <math.h>
+
+#include "mozilla/Alignment.h"
+
+#include "cairo.h"
+
+#include "gfxContext.h"
+
+#include "gfxMatrix.h"
+#include "gfxUtils.h"
+#include "gfxPattern.h"
+#include "gfxPlatform.h"
+
+#include "gfx2DGlue.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/ProfilerLabels.h"
+#include <algorithm>
+#include "TextDrawTarget.h"
+
+#if XP_WIN
+# include "gfxWindowsPlatform.h"
+# include "mozilla/gfx/DeviceManagerDx.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+#ifdef DEBUG
+# define CURRENTSTATE_CHANGED() mAzureState.mContentChanged = true;
+#else
+# define CURRENTSTATE_CHANGED()
+#endif
+
+PatternFromState::operator Pattern&() {
+ const gfxContext::AzureState& state = mContext->mAzureState;
+
+ if (state.pattern) {
+ return *state.pattern->GetPattern(
+ mContext->mDT,
+ state.patternTransformChanged ? &state.patternTransform : nullptr);
+ }
+
+ mPattern = new (mColorPattern.addr()) ColorPattern(state.color);
+ return *mPattern;
+}
+
+/* static */
+UniquePtr<gfxContext> gfxContext::CreateOrNull(DrawTarget* aTarget,
+ const Point& aDeviceOffset) {
+ if (!aTarget || !aTarget->IsValid()) {
+ gfxCriticalNote << "Invalid target in gfxContext::CreateOrNull "
+ << hexa(aTarget);
+ return nullptr;
+ }
+
+ return MakeUnique<gfxContext>(aTarget, aDeviceOffset);
+}
+
+gfxContext::~gfxContext() {
+ while (!mSavedStates.IsEmpty()) {
+ Restore();
+ }
+ for (unsigned int c = 0; c < mAzureState.pushedClips.Length(); c++) {
+ mDT->PopClip();
+ }
+}
+
+mozilla::layout::TextDrawTarget* gfxContext::GetTextDrawer() const {
+ if (mDT->GetBackendType() == BackendType::WEBRENDER_TEXT) {
+ return static_cast<mozilla::layout::TextDrawTarget*>(&*mDT);
+ }
+ return nullptr;
+}
+
+void gfxContext::Save() {
+ mSavedStates.AppendElement(mAzureState);
+ mAzureState.pushedClips.Clear();
+#ifdef DEBUG
+ mAzureState.mContentChanged = false;
+#endif
+}
+
+void gfxContext::Restore() {
+#ifdef DEBUG
+ // gfxContext::Restore is used to restore AzureState. We need to restore it
+ // only if it was altered. The following APIs do change the content of
+ // AzureState, a user should save the state before using them and restore it
+ // after finishing painting:
+ // 1. APIs to setup how to paint, such as SetColor()/SetAntialiasMode(). All
+ // gfxContext SetXXXX public functions belong to this category, except
+ // gfxContext::SetPath & gfxContext::SetMatrix.
+ // 2. Clip functions, such as Clip() or PopClip(). You may call PopClip()
+ // directly instead of using gfxContext::Save if the clip region is the
+ // only thing that you altered in the target context.
+ // 3. Function of setup transform matrix, such as Multiply() and
+ // SetMatrix(). Using gfxContextMatrixAutoSaveRestore is more recommended
+ // if transform data is the only thing that you are going to alter.
+ //
+ // You will hit the assertion message below if there is no above functions
+ // been used between a pair of gfxContext::Save and gfxContext::Restore.
+ // Considerate to remove that pair of Save/Restore if hitting that assertion.
+ //
+ // In the other hand, the following APIs do not alter the content of the
+ // current AzureState, therefore, there is no need to save & restore
+ // AzureState:
+ // 1. constant member functions of gfxContext.
+ // 2. Paint calls, such as Line()/Rectangle()/Fill(). Those APIs change the
+ // content of drawing buffer, which is not part of AzureState.
+ // 3. Path building APIs, such as SetPath()/MoveTo()/LineTo()/NewPath().
+ // Surprisingly, path information is not stored in AzureState either.
+ // Save current AzureState before using these type of APIs does nothing but
+ // make performance worse.
+ NS_ASSERTION(
+ mAzureState.mContentChanged || mAzureState.pushedClips.Length() > 0,
+ "The context of the current AzureState is not altered after "
+ "Save() been called. you may consider to remove this pair of "
+ "gfxContext::Save/Restore.");
+#endif
+
+ for (unsigned int c = 0; c < mAzureState.pushedClips.Length(); c++) {
+ mDT->PopClip();
+ }
+
+ mAzureState = mSavedStates.PopLastElement();
+
+ ChangeTransform(mAzureState.transform, false);
+}
+
+// drawing
+
+void gfxContext::Fill(const Pattern& aPattern) {
+ AUTO_PROFILER_LABEL("gfxContext::Fill", GRAPHICS);
+
+ CompositionOp op = GetOp();
+
+ if (mPathIsRect) {
+ MOZ_ASSERT(!mTransformChanged);
+
+ if (op == CompositionOp::OP_SOURCE) {
+ // Emulate cairo operator source which is bound by mask!
+ mDT->ClearRect(mRect);
+ mDT->FillRect(mRect, aPattern, DrawOptions(1.0f));
+ } else {
+ mDT->FillRect(mRect, aPattern, DrawOptions(1.0f, op, mAzureState.aaMode));
+ }
+ } else {
+ EnsurePath();
+ mDT->Fill(mPath, aPattern, DrawOptions(1.0f, op, mAzureState.aaMode));
+ }
+}
+
+// XXX snapToPixels is only valid when snapping for filled
+// rectangles and for even-width stroked rectangles.
+// For odd-width stroked rectangles, we need to offset x/y by
+// 0.5...
+void gfxContext::Rectangle(const gfxRect& rect, bool snapToPixels) {
+ Rect rec = ToRect(rect);
+
+ if (snapToPixels) {
+ gfxRect newRect(rect);
+ if (UserToDevicePixelSnapped(newRect, SnapOption::IgnoreScale)) {
+ gfxMatrix mat = CurrentMatrixDouble();
+ if (mat.Invert()) {
+ // We need the user space rect.
+ rec = ToRect(mat.TransformBounds(newRect));
+ } else {
+ rec = Rect();
+ }
+ }
+ }
+
+ if (!mPathBuilder && !mPathIsRect) {
+ mPathIsRect = true;
+ mRect = rec;
+ return;
+ }
+
+ EnsurePathBuilder();
+
+ mPathBuilder->MoveTo(rec.TopLeft());
+ mPathBuilder->LineTo(rec.TopRight());
+ mPathBuilder->LineTo(rec.BottomRight());
+ mPathBuilder->LineTo(rec.BottomLeft());
+ mPathBuilder->Close();
+}
+
+void gfxContext::SnappedClip(const gfxRect& rect) {
+ Rect rec = ToRect(rect);
+
+ gfxRect newRect(rect);
+ if (UserToDevicePixelSnapped(newRect, SnapOption::IgnoreScale)) {
+ gfxMatrix mat = CurrentMatrixDouble();
+ if (mat.Invert()) {
+ // We need the user space rect.
+ rec = ToRect(mat.TransformBounds(newRect));
+ } else {
+ rec = Rect();
+ }
+ }
+
+ Clip(rec);
+}
+
+bool gfxContext::UserToDevicePixelSnapped(gfxRect& rect,
+ SnapOptions aOptions) const {
+ if (mDT->GetUserData(&sDisablePixelSnapping)) {
+ return false;
+ }
+
+ // if we're not at 1.0 scale, don't snap, unless we're
+ // ignoring the scale. If we're not -just- a scale,
+ // never snap.
+ const gfxFloat epsilon = 0.0000001;
+#define WITHIN_E(a, b) (fabs((a) - (b)) < epsilon)
+ Matrix mat = mAzureState.transform;
+ if (!aOptions.contains(SnapOption::IgnoreScale) &&
+ (!WITHIN_E(mat._11, 1.0) || !WITHIN_E(mat._22, 1.0) ||
+ !WITHIN_E(mat._12, 0.0) || !WITHIN_E(mat._21, 0.0))) {
+ return false;
+ }
+#undef WITHIN_E
+
+ gfxPoint p1 = UserToDevice(rect.TopLeft());
+ gfxPoint p2 = UserToDevice(rect.TopRight());
+ gfxPoint p3 = UserToDevice(rect.BottomRight());
+
+ // Check that the rectangle is axis-aligned. For an axis-aligned rectangle,
+ // two opposite corners define the entire rectangle. So check if
+ // the axis-aligned rectangle with opposite corners p1 and p3
+ // define an axis-aligned rectangle whose other corners are p2 and p4.
+ // We actually only need to check one of p2 and p4, since an affine
+ // transform maps parallelograms to parallelograms.
+ if (!(p2 == gfxPoint(p1.x, p3.y) || p2 == gfxPoint(p3.x, p1.y))) {
+ return false;
+ }
+
+ if (aOptions.contains(SnapOption::PrioritizeSize)) {
+ // Snap the dimensions of the rect, to minimize distortion; only after that
+ // will we snap its position. In particular, this guarantees that a square
+ // remains square after snapping, which may not be the case if each edge is
+ // independently snapped to device pixels.
+
+ // Use the same rounding approach as gfx::BasePoint::Round.
+ rect.SizeTo(std::floor(rect.width + 0.5), std::floor(rect.height + 0.5));
+
+ // Find the top-left corner based on the original center and the snapped
+ // size, then snap this new corner to the grid.
+ gfxPoint center = (p1 + p3) / 2;
+ gfxPoint topLeft = center - gfxPoint(rect.width / 2.0, rect.height / 2.0);
+ topLeft.Round();
+ rect.MoveTo(topLeft);
+ } else {
+ p1.Round();
+ p3.Round();
+ rect.MoveTo(gfxPoint(std::min(p1.x, p3.x), std::min(p1.y, p3.y)));
+ rect.SizeTo(gfxSize(std::max(p1.x, p3.x) - rect.X(),
+ std::max(p1.y, p3.y) - rect.Y()));
+ }
+
+ return true;
+}
+
+bool gfxContext::UserToDevicePixelSnapped(gfxPoint& pt,
+ bool ignoreScale) const {
+ if (mDT->GetUserData(&sDisablePixelSnapping)) {
+ return false;
+ }
+
+ // if we're not at 1.0 scale, don't snap, unless we're
+ // ignoring the scale. If we're not -just- a scale,
+ // never snap.
+ const gfxFloat epsilon = 0.0000001;
+#define WITHIN_E(a, b) (fabs((a) - (b)) < epsilon)
+ Matrix mat = mAzureState.transform;
+ if (!ignoreScale && (!WITHIN_E(mat._11, 1.0) || !WITHIN_E(mat._22, 1.0) ||
+ !WITHIN_E(mat._12, 0.0) || !WITHIN_E(mat._21, 0.0))) {
+ return false;
+ }
+#undef WITHIN_E
+
+ pt = UserToDevice(pt);
+ pt.Round();
+ return true;
+}
+
+void gfxContext::SetDash(const Float* dashes, int ndash, Float offset,
+ Float devPxScale) {
+ CURRENTSTATE_CHANGED()
+
+ mAzureState.dashPattern.SetLength(ndash);
+ for (int i = 0; i < ndash; i++) {
+ mAzureState.dashPattern[i] = dashes[i] * devPxScale;
+ }
+ mAzureState.strokeOptions.mDashLength = ndash;
+ mAzureState.strokeOptions.mDashOffset = offset * devPxScale;
+ mAzureState.strokeOptions.mDashPattern =
+ ndash ? mAzureState.dashPattern.Elements() : nullptr;
+}
+
+bool gfxContext::CurrentDash(FallibleTArray<Float>& dashes,
+ Float* offset) const {
+ if (mAzureState.strokeOptions.mDashLength == 0 ||
+ !dashes.Assign(mAzureState.dashPattern, fallible)) {
+ return false;
+ }
+
+ *offset = mAzureState.strokeOptions.mDashOffset;
+
+ return true;
+}
+
+// clipping
+void gfxContext::Clip(const Rect& rect) {
+ AzureState::PushedClip clip = {nullptr, rect, mAzureState.transform};
+ mAzureState.pushedClips.AppendElement(clip);
+ mDT->PushClipRect(rect);
+ NewPath();
+}
+
+void gfxContext::Clip(Path* aPath) {
+ mDT->PushClip(aPath);
+ AzureState::PushedClip clip = {aPath, Rect(), mAzureState.transform};
+ mAzureState.pushedClips.AppendElement(clip);
+}
+
+void gfxContext::Clip() {
+ if (mPathIsRect) {
+ MOZ_ASSERT(!mTransformChanged);
+
+ AzureState::PushedClip clip = {nullptr, mRect, mAzureState.transform};
+ mAzureState.pushedClips.AppendElement(clip);
+ mDT->PushClipRect(mRect);
+ } else {
+ EnsurePath();
+ mDT->PushClip(mPath);
+ AzureState::PushedClip clip = {mPath, Rect(), mAzureState.transform};
+ mAzureState.pushedClips.AppendElement(clip);
+ }
+}
+
+gfxRect gfxContext::GetClipExtents(ClipExtentsSpace aSpace) const {
+ Rect rect = GetAzureDeviceSpaceClipBounds();
+
+ if (rect.IsZeroArea()) {
+ return gfxRect(0, 0, 0, 0);
+ }
+
+ if (aSpace == eUserSpace) {
+ Matrix mat = mAzureState.transform;
+ mat.Invert();
+ rect = mat.TransformBounds(rect);
+ }
+
+ return ThebesRect(rect);
+}
+
+bool gfxContext::ExportClip(ClipExporter& aExporter) const {
+ ForAllClips([&](const AzureState::PushedClip& aClip) -> void {
+ gfx::Matrix transform = aClip.transform;
+ transform.PostTranslate(-GetDeviceOffset());
+
+ aExporter.BeginClip(transform);
+ if (aClip.path) {
+ aClip.path->StreamToSink(&aExporter);
+ } else {
+ aExporter.MoveTo(aClip.rect.TopLeft());
+ aExporter.LineTo(aClip.rect.TopRight());
+ aExporter.LineTo(aClip.rect.BottomRight());
+ aExporter.LineTo(aClip.rect.BottomLeft());
+ aExporter.Close();
+ }
+ aExporter.EndClip();
+ });
+
+ return true;
+}
+
+// rendering sources
+
+bool gfxContext::GetDeviceColor(DeviceColor& aColorOut) const {
+ if (mAzureState.pattern) {
+ return mAzureState.pattern->GetSolidColor(aColorOut);
+ }
+
+ aColorOut = mAzureState.color;
+ return true;
+}
+
+already_AddRefed<gfxPattern> gfxContext::GetPattern() const {
+ RefPtr<gfxPattern> pat;
+
+ if (mAzureState.pattern) {
+ pat = mAzureState.pattern;
+ } else {
+ pat = new gfxPattern(mAzureState.color);
+ }
+ return pat.forget();
+}
+
+void gfxContext::Paint(Float alpha) const {
+ AUTO_PROFILER_LABEL("gfxContext::Paint", GRAPHICS);
+
+ Matrix mat = mDT->GetTransform();
+ mat.Invert();
+ Rect paintRect = mat.TransformBounds(Rect(Point(0, 0), Size(mDT->GetSize())));
+
+ mDT->FillRect(paintRect, PatternFromState(this), DrawOptions(alpha, GetOp()));
+}
+
+#ifdef MOZ_DUMP_PAINTING
+void gfxContext::WriteAsPNG(const char* aFile) {
+ gfxUtils::WriteAsPNG(mDT, aFile);
+}
+
+void gfxContext::DumpAsDataURI() { gfxUtils::DumpAsDataURI(mDT); }
+
+void gfxContext::CopyAsDataURI() { gfxUtils::CopyAsDataURI(mDT); }
+#endif
+
+void gfxContext::EnsurePath() {
+ if (mPathBuilder) {
+ mPath = mPathBuilder->Finish();
+ mPathBuilder = nullptr;
+ }
+
+ if (mPath) {
+ if (mTransformChanged) {
+ Matrix mat = mAzureState.transform;
+ mat.Invert();
+ mat = mPathTransform * mat;
+ mPathBuilder = mPath->TransformedCopyToBuilder(mat);
+ mPath = mPathBuilder->Finish();
+ mPathBuilder = nullptr;
+
+ mTransformChanged = false;
+ }
+ return;
+ }
+
+ EnsurePathBuilder();
+ mPath = mPathBuilder->Finish();
+ mPathBuilder = nullptr;
+}
+
+void gfxContext::EnsurePathBuilder() {
+ if (mPathBuilder && !mTransformChanged) {
+ return;
+ }
+
+ if (mPath) {
+ if (!mTransformChanged) {
+ mPathBuilder = mPath->CopyToBuilder();
+ mPath = nullptr;
+ } else {
+ Matrix invTransform = mAzureState.transform;
+ invTransform.Invert();
+ Matrix toNewUS = mPathTransform * invTransform;
+ mPathBuilder = mPath->TransformedCopyToBuilder(toNewUS);
+ }
+ return;
+ }
+
+ DebugOnly<PathBuilder*> oldPath = mPathBuilder.get();
+
+ if (!mPathBuilder) {
+ mPathBuilder = mDT->CreatePathBuilder(FillRule::FILL_WINDING);
+
+ if (mPathIsRect) {
+ mPathBuilder->MoveTo(mRect.TopLeft());
+ mPathBuilder->LineTo(mRect.TopRight());
+ mPathBuilder->LineTo(mRect.BottomRight());
+ mPathBuilder->LineTo(mRect.BottomLeft());
+ mPathBuilder->Close();
+ }
+ }
+
+ if (mTransformChanged) {
+ // This could be an else if since this should never happen when
+ // mPathBuilder is nullptr and mPath is nullptr. But this way we can
+ // assert if all the state is as expected.
+ MOZ_ASSERT(oldPath);
+ MOZ_ASSERT(!mPathIsRect);
+
+ Matrix invTransform = mAzureState.transform;
+ invTransform.Invert();
+ Matrix toNewUS = mPathTransform * invTransform;
+
+ RefPtr<Path> path = mPathBuilder->Finish();
+ if (!path) {
+ gfxCriticalError()
+ << "gfxContext::EnsurePathBuilder failed in PathBuilder::Finish";
+ }
+ mPathBuilder = path->TransformedCopyToBuilder(toNewUS);
+ }
+
+ mPathIsRect = false;
+}
+
+CompositionOp gfxContext::GetOp() const {
+ if (mAzureState.op != CompositionOp::OP_SOURCE) {
+ return mAzureState.op;
+ }
+
+ if (mAzureState.pattern) {
+ if (mAzureState.pattern->IsOpaque()) {
+ return CompositionOp::OP_OVER;
+ } else {
+ return CompositionOp::OP_SOURCE;
+ }
+ } else {
+ if (mAzureState.color.a > 0.999) {
+ return CompositionOp::OP_OVER;
+ } else {
+ return CompositionOp::OP_SOURCE;
+ }
+ }
+}
+
+/* SVG font code can change the transform after having set the pattern on the
+ * context. When the pattern is set it is in user space, if the transform is
+ * changed after doing so the pattern needs to be converted back into userspace.
+ * We just store the old pattern transform here so that we only do the work
+ * needed here if the pattern is actually used.
+ * We need to avoid doing this when this ChangeTransform comes from a restore,
+ * since the current pattern and the current transform are both part of the
+ * state we know the new mAzureState's values are valid. But if we assume
+ * a change they might become invalid since patternTransformChanged is part of
+ * the state and might be false for the restored AzureState.
+ */
+void gfxContext::ChangeTransform(const Matrix& aNewMatrix,
+ bool aUpdatePatternTransform) {
+ if (aUpdatePatternTransform && (mAzureState.pattern) &&
+ !mAzureState.patternTransformChanged) {
+ mAzureState.patternTransform = GetDTTransform();
+ mAzureState.patternTransformChanged = true;
+ }
+
+ if (mPathIsRect) {
+ Matrix invMatrix = aNewMatrix;
+
+ invMatrix.Invert();
+
+ Matrix toNewUS = mAzureState.transform * invMatrix;
+
+ if (toNewUS.IsRectilinear()) {
+ mRect = toNewUS.TransformBounds(mRect);
+ mRect.NudgeToIntegers();
+ } else {
+ mPathBuilder = mDT->CreatePathBuilder(FillRule::FILL_WINDING);
+
+ mPathBuilder->MoveTo(toNewUS.TransformPoint(mRect.TopLeft()));
+ mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.TopRight()));
+ mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.BottomRight()));
+ mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.BottomLeft()));
+ mPathBuilder->Close();
+
+ mPathIsRect = false;
+ }
+
+ // No need to consider the transform changed now!
+ mTransformChanged = false;
+ } else if ((mPath || mPathBuilder) && !mTransformChanged) {
+ mTransformChanged = true;
+ mPathTransform = mAzureState.transform;
+ }
+
+ mAzureState.transform = aNewMatrix;
+
+ mDT->SetTransform(GetDTTransform());
+}
+
+Rect gfxContext::GetAzureDeviceSpaceClipBounds() const {
+ Rect rect(mAzureState.deviceOffset.x + Float(mDT->GetRect().x),
+ mAzureState.deviceOffset.y + Float(mDT->GetRect().y),
+ Float(mDT->GetSize().width), Float(mDT->GetSize().height));
+ ForAllClips([&](const AzureState::PushedClip& aClip) -> void {
+ if (aClip.path) {
+ rect.IntersectRect(rect, aClip.path->GetBounds(aClip.transform));
+ } else {
+ rect.IntersectRect(rect, aClip.transform.TransformBounds(aClip.rect));
+ }
+ });
+
+ return rect;
+}
+
+template <typename F>
+void gfxContext::ForAllClips(F&& aLambda) const {
+ for (const auto& state : mSavedStates) {
+ for (const auto& clip : state.pushedClips) {
+ aLambda(clip);
+ }
+ }
+ for (const auto& clip : mAzureState.pushedClips) {
+ aLambda(clip);
+ }
+}
diff --git a/gfx/thebes/gfxContext.h b/gfx/thebes/gfxContext.h
new file mode 100644
index 0000000000..a6ebe76e7b
--- /dev/null
+++ b/gfx/thebes/gfxContext.h
@@ -0,0 +1,811 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_CONTEXT_H
+#define GFX_CONTEXT_H
+
+#include "gfx2DGlue.h"
+#include "gfxPattern.h"
+#include "gfxUtils.h"
+#include "nsTArray.h"
+
+#include "mozilla/EnumSet.h"
+#include "mozilla/gfx/2D.h"
+
+typedef struct _cairo cairo_t;
+class GlyphBufferAzure;
+
+namespace mozilla {
+namespace gfx {
+struct RectCornerRadii;
+} // namespace gfx
+namespace layout {
+class TextDrawTarget;
+} // namespace layout
+} // namespace mozilla
+
+class ClipExporter;
+
+/* This class lives on the stack and allows gfxContext users to easily, and
+ * performantly get a gfx::Pattern to use for drawing in their current context.
+ */
+class PatternFromState {
+ public:
+ explicit PatternFromState(const gfxContext* aContext)
+ : mContext(aContext), mPattern(nullptr) {}
+ ~PatternFromState() {
+ if (mPattern) {
+ mPattern->~Pattern();
+ }
+ }
+
+ operator mozilla::gfx::Pattern&();
+
+ private:
+ mozilla::AlignedStorage2<mozilla::gfx::ColorPattern> mColorPattern;
+
+ const gfxContext* mContext;
+ mozilla::gfx::Pattern* mPattern;
+};
+
+/**
+ * This is the main class for doing actual drawing. It is initialized using
+ * a surface and can be drawn on. It manages various state information like
+ * a current transformation matrix (CTM), a current path, current color,
+ * etc.
+ *
+ * All drawing happens by creating a path and then stroking or filling it.
+ * The functions like Rectangle and Arc do not do any drawing themselves.
+ * When a path is drawn (stroked or filled), it is filled/stroked with a
+ * pattern set by SetPattern or SetColor.
+ *
+ * Note that the gfxContext takes coordinates in device pixels,
+ * as opposed to app units.
+ */
+class gfxContext final {
+#ifdef DEBUG
+# define CURRENTSTATE_CHANGED() mAzureState.mContentChanged = true;
+#else
+# define CURRENTSTATE_CHANGED()
+#endif
+
+ typedef mozilla::gfx::BackendType BackendType;
+ typedef mozilla::gfx::CapStyle CapStyle;
+ typedef mozilla::gfx::CompositionOp CompositionOp;
+ typedef mozilla::gfx::DeviceColor DeviceColor;
+ typedef mozilla::gfx::DrawOptions DrawOptions;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::JoinStyle JoinStyle;
+ typedef mozilla::gfx::FillRule FillRule;
+ typedef mozilla::gfx::Float Float;
+ typedef mozilla::gfx::Matrix Matrix;
+ typedef mozilla::gfx::Path Path;
+ typedef mozilla::gfx::Pattern Pattern;
+ typedef mozilla::gfx::Point Point;
+ typedef mozilla::gfx::Rect Rect;
+ typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
+ typedef mozilla::gfx::Size Size;
+
+ public:
+ /**
+ * Initialize this context from a DrawTarget, which must be non-null.
+ * Strips any transform from aTarget, unless aPreserveTransform is true.
+ * aTarget will be flushed in the gfxContext's destructor.
+ */
+ MOZ_NONNULL(2)
+ explicit gfxContext(DrawTarget* aTarget, const Point& aDeviceOffset = Point())
+ : mDT(aTarget) {
+ mAzureState.deviceOffset = aDeviceOffset;
+ mDT->SetTransform(GetDTTransform());
+ }
+
+ MOZ_NONNULL(2)
+ gfxContext(DrawTarget* aTarget, bool aPreserveTransform) : mDT(aTarget) {
+ if (aPreserveTransform) {
+ SetMatrix(aTarget->GetTransform());
+ } else {
+ mDT->SetTransform(GetDTTransform());
+ }
+ }
+
+ ~gfxContext();
+
+ /**
+ * Initialize this context from a DrawTarget.
+ * Strips any transform from aTarget.
+ * aTarget will be flushed in the gfxContext's destructor.
+ * If aTarget is null or invalid, nullptr is returned. The caller
+ * is responsible for handling this scenario as appropriate.
+ */
+ static mozilla::UniquePtr<gfxContext> CreateOrNull(
+ DrawTarget* aTarget, const Point& aDeviceOffset = Point());
+
+ DrawTarget* GetDrawTarget() const { return mDT; }
+
+ /**
+ * Returns the DrawTarget if it's actually a TextDrawTarget.
+ */
+ mozilla::layout::TextDrawTarget* GetTextDrawer() const;
+
+ /**
+ ** State
+ **/
+ // XXX document exactly what bits are saved
+ void Save();
+ void Restore();
+
+ /**
+ ** Paths & Drawing
+ **/
+
+ /**
+ * Fill the current path according to the current settings.
+ *
+ * Does not consume the current path.
+ */
+ void Fill() { Fill(PatternFromState(this)); }
+ void Fill(const Pattern& aPattern);
+
+ /**
+ * Forgets the current path.
+ */
+ void NewPath() {
+ mPath = nullptr;
+ mPathBuilder = nullptr;
+ mPathIsRect = false;
+ mTransformChanged = false;
+ }
+
+ /**
+ * Returns the current path.
+ */
+ already_AddRefed<Path> GetPath() {
+ EnsurePath();
+ RefPtr<Path> path(mPath);
+ return path.forget();
+ }
+
+ /**
+ * Sets the given path as the current path.
+ */
+ void SetPath(Path* path) {
+ MOZ_ASSERT(path->GetBackendType() == mDT->GetBackendType() ||
+ path->GetBackendType() == BackendType::RECORDING ||
+ (mDT->GetBackendType() == BackendType::DIRECT2D1_1 &&
+ path->GetBackendType() == BackendType::DIRECT2D));
+ mPath = path;
+ mPathBuilder = nullptr;
+ mPathIsRect = false;
+ mTransformChanged = false;
+ }
+
+ /**
+ * Draws the rectangle given by rect.
+ */
+ void Rectangle(const gfxRect& rect) { return Rectangle(rect, false); }
+ void SnappedRectangle(const gfxRect& rect) { return Rectangle(rect, true); }
+
+ private:
+ void Rectangle(const gfxRect& rect, bool snapToPixels);
+
+ public:
+ /**
+ ** Transformation Matrix manipulation
+ **/
+
+ /**
+ * Post-multiplies 'other' onto the current CTM, i.e. this
+ * matrix's transformation will take place before the previously set
+ * transformations.
+ */
+ void Multiply(const gfxMatrix& aMatrix) { Multiply(ToMatrix(aMatrix)); }
+ void Multiply(const Matrix& aOther) {
+ CURRENTSTATE_CHANGED()
+ ChangeTransform(aOther * mAzureState.transform);
+ }
+
+ /**
+ * Replaces the current transformation matrix with matrix.
+ */
+ void SetMatrix(const Matrix& aMatrix) {
+ CURRENTSTATE_CHANGED()
+ ChangeTransform(aMatrix);
+ }
+ void SetMatrixDouble(const gfxMatrix& aMatrix) {
+ SetMatrix(ToMatrix(aMatrix));
+ }
+
+ void SetCrossProcessPaintScale(float aScale) {
+ MOZ_ASSERT(mCrossProcessPaintScale == 1.0f,
+ "Should only be initialized once");
+ mCrossProcessPaintScale = aScale;
+ }
+
+ float GetCrossProcessPaintScale() const { return mCrossProcessPaintScale; }
+
+ /**
+ * Returns the current transformation matrix.
+ */
+ Matrix CurrentMatrix() const { return mAzureState.transform; }
+ gfxMatrix CurrentMatrixDouble() const {
+ return ThebesMatrix(CurrentMatrix());
+ }
+
+ /**
+ * Converts a point from device to user coordinates using the inverse
+ * transformation matrix.
+ */
+ gfxPoint DeviceToUser(const gfxPoint& aPoint) const {
+ return ThebesPoint(
+ mAzureState.transform.Inverse().TransformPoint(ToPoint(aPoint)));
+ }
+
+ /**
+ * Converts a size from device to user coordinates. This does not apply
+ * translation components of the matrix.
+ */
+ Size DeviceToUser(const Size& aSize) const {
+ return mAzureState.transform.Inverse().TransformSize(aSize);
+ }
+
+ /**
+ * Converts a rectangle from device to user coordinates; this has the
+ * same effect as using DeviceToUser on both the rectangle's point and
+ * size.
+ */
+ gfxRect DeviceToUser(const gfxRect& aRect) const {
+ return ThebesRect(
+ mAzureState.transform.Inverse().TransformBounds(ToRect(aRect)));
+ }
+
+ /**
+ * Converts a point from user to device coordinates using the transformation
+ * matrix.
+ */
+ gfxPoint UserToDevice(const gfxPoint& aPoint) const {
+ return ThebesPoint(mAzureState.transform.TransformPoint(ToPoint(aPoint)));
+ }
+
+ /**
+ * Converts a size from user to device coordinates. This does not apply
+ * translation components of the matrix.
+ */
+ Size UserToDevice(const Size& aSize) const {
+ const auto& mtx = mAzureState.transform;
+ return Size(aSize.width * mtx._11 + aSize.height * mtx._12,
+ aSize.width * mtx._21 + aSize.height * mtx._22);
+ }
+
+ /**
+ * Converts a rectangle from user to device coordinates. The
+ * resulting rectangle is the minimum device-space rectangle that
+ * encloses the user-space rectangle given.
+ */
+ gfxRect UserToDevice(const gfxRect& rect) const {
+ return ThebesRect(mAzureState.transform.TransformBounds(ToRect(rect)));
+ }
+
+ /**
+ * Takes the given rect and tries to align it to device pixels. If
+ * this succeeds, the method will return true, and the rect will
+ * be in device coordinates (already transformed by the CTM). If it
+ * fails, the method will return false, and the rect will not be
+ * changed.
+ *
+ * aOptions parameter:
+ * If IgnoreScale is set, then snapping will take place even if the CTM
+ * has a scale applied. Snapping never takes place if there is a rotation
+ * in the CTM.
+ *
+ * If PrioritizeSize is set, the rect's dimensions will first be snapped
+ * and then its position aligned to device pixels, rather than snapping
+ * the position of each edge independently.
+ */
+ enum class SnapOption : uint8_t {
+ IgnoreScale = 1,
+ PrioritizeSize = 2,
+ };
+ using SnapOptions = mozilla::EnumSet<SnapOption>;
+ bool UserToDevicePixelSnapped(gfxRect& rect, SnapOptions aOptions = {}) const;
+
+ /**
+ * Takes the given point and tries to align it to device pixels. If
+ * this succeeds, the method will return true, and the point will
+ * be in device coordinates (already transformed by the CTM). If it
+ * fails, the method will return false, and the point will not be
+ * changed.
+ *
+ * If ignoreScale is true, then snapping will take place even if
+ * the CTM has a scale applied. Snapping never takes place if
+ * there is a rotation in the CTM.
+ */
+ bool UserToDevicePixelSnapped(gfxPoint& pt, bool ignoreScale = false) const;
+
+ /**
+ ** Painting sources
+ **/
+
+ /**
+ * Set a solid color to use for drawing. This color is in the device color
+ * space and is not transformed.
+ */
+ void SetDeviceColor(const DeviceColor& aColor) {
+ CURRENTSTATE_CHANGED()
+ mAzureState.pattern = nullptr;
+ mAzureState.color = aColor;
+ }
+
+ /**
+ * Gets the current color. It's returned in the device color space.
+ * returns false if there is something other than a color
+ * set as the current source (pattern, surface, etc)
+ */
+ bool GetDeviceColor(DeviceColor& aColorOut) const;
+
+ /**
+ * Returns true if color is neither opaque nor transparent (i.e. alpha is not
+ * 0 or 1), and false otherwise. If true, aColorOut is set on output.
+ */
+ bool HasNonOpaqueNonTransparentColor(DeviceColor& aColorOut) const {
+ return GetDeviceColor(aColorOut) && 0.f < aColorOut.a && aColorOut.a < 1.f;
+ }
+
+ /**
+ * Set a solid color in the sRGB color space to use for drawing.
+ * If CMS is not enabled, the color is treated as a device-space color
+ * and this call is identical to SetDeviceColor().
+ */
+ void SetColor(const mozilla::gfx::sRGBColor& aColor) {
+ CURRENTSTATE_CHANGED()
+ mAzureState.pattern = nullptr;
+ mAzureState.color = ToDeviceColor(aColor);
+ }
+
+ /**
+ * Uses a pattern for drawing.
+ */
+ void SetPattern(gfxPattern* pattern) {
+ CURRENTSTATE_CHANGED()
+ mAzureState.patternTransformChanged = false;
+ mAzureState.pattern = pattern;
+ }
+
+ /**
+ * Get the source pattern (solid color, normal pattern, surface, etc)
+ */
+ already_AddRefed<gfxPattern> GetPattern() const;
+
+ /**
+ ** Painting
+ **/
+ /**
+ * Paints the current source surface/pattern everywhere in the current
+ * clip region.
+ */
+ void Paint(Float alpha = 1.0) const;
+
+ /**
+ ** Line Properties
+ **/
+
+ // Set the dash pattern, applying devPxScale to convert passed-in lengths
+ // to device pixels (used by the SVGUtils::SetupStrokeGeometry caller,
+ // which has the desired dash pattern in CSS px).
+ void SetDash(const Float* dashes, int ndash, Float offset, Float devPxScale);
+
+ // Return true if dashing is set, false if it's not enabled or the
+ // context is in an error state. |offset| can be nullptr to mean
+ // "don't care".
+ bool CurrentDash(FallibleTArray<Float>& dashes, Float* offset) const;
+
+ /**
+ * Sets the line width that's used for line drawing.
+ */
+ void SetLineWidth(Float width) {
+ CURRENTSTATE_CHANGED()
+ mAzureState.strokeOptions.mLineWidth = width;
+ }
+
+ /**
+ * Returns the currently set line width.
+ *
+ * @see SetLineWidth
+ */
+ Float CurrentLineWidth() const {
+ return mAzureState.strokeOptions.mLineWidth;
+ }
+
+ /**
+ * Sets the line caps, i.e. how line endings are drawn.
+ */
+ void SetLineCap(CapStyle cap) {
+ CURRENTSTATE_CHANGED()
+ mAzureState.strokeOptions.mLineCap = cap;
+ }
+ CapStyle CurrentLineCap() const { return mAzureState.strokeOptions.mLineCap; }
+
+ /**
+ * Sets the line join, i.e. how the connection between two lines is
+ * drawn.
+ */
+ void SetLineJoin(JoinStyle join) {
+ CURRENTSTATE_CHANGED()
+ mAzureState.strokeOptions.mLineJoin = join;
+ }
+ JoinStyle CurrentLineJoin() const {
+ return mAzureState.strokeOptions.mLineJoin;
+ }
+
+ void SetMiterLimit(Float limit) {
+ CURRENTSTATE_CHANGED()
+ mAzureState.strokeOptions.mMiterLimit = limit;
+ }
+ Float CurrentMiterLimit() const {
+ return mAzureState.strokeOptions.mMiterLimit;
+ }
+
+ /**
+ * Sets the operator used for all further drawing. The operator affects
+ * how drawing something will modify the destination. For example, the
+ * OVER operator will do alpha blending of source and destination, while
+ * SOURCE will replace the destination with the source.
+ */
+ void SetOp(CompositionOp aOp) {
+ CURRENTSTATE_CHANGED()
+ mAzureState.op = aOp;
+ }
+ CompositionOp CurrentOp() const { return mAzureState.op; }
+
+ void SetAntialiasMode(mozilla::gfx::AntialiasMode aMode) {
+ CURRENTSTATE_CHANGED()
+ mAzureState.aaMode = aMode;
+ }
+ mozilla::gfx::AntialiasMode CurrentAntialiasMode() const {
+ return mAzureState.aaMode;
+ }
+
+ /**
+ ** Clipping
+ **/
+
+ /**
+ * Clips all further drawing to the current path.
+ * This does not consume the current path.
+ */
+ void Clip();
+
+ /**
+ * Helper functions that will create a rect path and call Clip().
+ * Any current path will be destroyed by these functions!
+ */
+ void Clip(const gfxRect& aRect) { Clip(ToRect(aRect)); }
+ void Clip(const Rect& rect); // will clip to a rect
+ void SnappedClip(const gfxRect& rect); // snap rect and clip to the result
+ void Clip(Path* aPath);
+
+ void PopClip() {
+ MOZ_ASSERT(!mAzureState.pushedClips.IsEmpty());
+ mAzureState.pushedClips.RemoveLastElement();
+ mDT->PopClip();
+ }
+
+ enum ClipExtentsSpace {
+ eUserSpace = 0,
+ eDeviceSpace = 1,
+ };
+
+ /**
+ * According to aSpace, this function will return the current bounds of
+ * the clip region in user space or device space.
+ */
+ gfxRect GetClipExtents(ClipExtentsSpace aSpace = eUserSpace) const;
+
+ /**
+ * Exports the current clip using the provided exporter.
+ */
+ bool ExportClip(ClipExporter& aExporter) const;
+
+ /**
+ * Groups
+ */
+ void PushGroupForBlendBack(gfxContentType content, Float aOpacity = 1.0f,
+ mozilla::gfx::SourceSurface* aMask = nullptr,
+ const Matrix& aMaskTransform = Matrix()) const {
+ mDT->PushLayer(content == gfxContentType::COLOR, aOpacity, aMask,
+ aMaskTransform);
+ }
+
+ void PopGroupAndBlend() const { mDT->PopLayer(); }
+
+ Point GetDeviceOffset() const { return mAzureState.deviceOffset; }
+ void SetDeviceOffset(const Point& aOffset) {
+ mAzureState.deviceOffset = aOffset;
+ }
+
+#ifdef MOZ_DUMP_PAINTING
+ /**
+ * Debug functions to encode the current surface as a PNG and export it.
+ */
+
+ /**
+ * Writes a binary PNG file.
+ */
+ void WriteAsPNG(const char* aFile);
+
+ /**
+ * Write as a PNG encoded Data URL to stdout.
+ */
+ void DumpAsDataURI();
+
+ /**
+ * Copy a PNG encoded Data URL to the clipboard.
+ */
+ void CopyAsDataURI();
+#endif
+
+ private:
+ friend class PatternFromState;
+ friend class GlyphBufferAzure;
+
+ typedef mozilla::gfx::sRGBColor sRGBColor;
+ typedef mozilla::gfx::StrokeOptions StrokeOptions;
+ typedef mozilla::gfx::PathBuilder PathBuilder;
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+
+ struct AzureState {
+ AzureState()
+ : op(CompositionOp::OP_OVER),
+ color(0, 0, 0, 1.0f),
+ aaMode(mozilla::gfx::AntialiasMode::SUBPIXEL),
+ patternTransformChanged(false)
+#ifdef DEBUG
+ ,
+ mContentChanged(false)
+#endif
+ {
+ }
+
+ CompositionOp op;
+ DeviceColor color;
+ RefPtr<gfxPattern> pattern;
+ Matrix transform;
+ struct PushedClip {
+ RefPtr<Path> path;
+ Rect rect;
+ Matrix transform;
+ };
+ CopyableTArray<PushedClip> pushedClips;
+ CopyableTArray<Float> dashPattern;
+ StrokeOptions strokeOptions;
+ mozilla::gfx::AntialiasMode aaMode;
+ bool patternTransformChanged;
+ Matrix patternTransform;
+ DeviceColor fontSmoothingBackgroundColor;
+ // This is used solely for using minimal intermediate surface size.
+ Point deviceOffset;
+#ifdef DEBUG
+ // Whether the content of this AzureState changed after construction.
+ bool mContentChanged;
+#endif
+ };
+
+ // This ensures mPath contains a valid path (in user space!)
+ void EnsurePath();
+ // This ensures mPathBuilder contains a valid PathBuilder (in user space!)
+ void EnsurePathBuilder();
+ CompositionOp GetOp() const;
+ void ChangeTransform(const Matrix& aNewMatrix,
+ bool aUpdatePatternTransform = true);
+ Rect GetAzureDeviceSpaceClipBounds() const;
+ Matrix GetDTTransform() const {
+ Matrix mat = mAzureState.transform;
+ mat.PostTranslate(-mAzureState.deviceOffset);
+ return mat;
+ }
+
+ bool mPathIsRect = false;
+ bool mTransformChanged = false;
+ Matrix mPathTransform;
+ Rect mRect;
+ RefPtr<PathBuilder> mPathBuilder;
+ RefPtr<Path> mPath;
+ AzureState mAzureState;
+ nsTArray<AzureState> mSavedStates;
+
+ // Iterate over all clips in the saved and current states, calling aLambda
+ // with each of them.
+ template <typename F>
+ void ForAllClips(F&& aLambda) const;
+
+ const AzureState& CurrentState() const { return mAzureState; }
+
+ RefPtr<DrawTarget> const mDT;
+ float mCrossProcessPaintScale = 1.0f;
+
+#ifdef DEBUG
+# undef CURRENTSTATE_CHANGED
+#endif
+};
+
+/**
+ * Sentry helper class for functions with multiple return points that need to
+ * call Save() on a gfxContext and have Restore() called automatically on the
+ * gfxContext before they return.
+ */
+class MOZ_STACK_CLASS gfxContextAutoSaveRestore final {
+ public:
+ gfxContextAutoSaveRestore() : mContext(nullptr) {}
+
+ explicit gfxContextAutoSaveRestore(gfxContext* aContext)
+ : mContext(aContext) {
+ mContext->Save();
+ }
+
+ ~gfxContextAutoSaveRestore() { Restore(); }
+
+ void SetContext(gfxContext* aContext) {
+ MOZ_ASSERT(!mContext, "no context?");
+ mContext = aContext;
+ mContext->Save();
+ }
+
+ void EnsureSaved(gfxContext* aContext) {
+ MOZ_ASSERT(!mContext || mContext == aContext, "wrong context");
+ if (!mContext) {
+ mContext = aContext;
+ mContext->Save();
+ }
+ }
+
+ void Restore() {
+ if (mContext) {
+ mContext->Restore();
+ mContext = nullptr;
+ }
+ }
+
+ private:
+ gfxContext* mContext;
+};
+
+/**
+ * Sentry helper class for functions with multiple return points that need to
+ * back up the current matrix of a context and have it automatically restored
+ * before they return.
+ */
+class MOZ_STACK_CLASS gfxContextMatrixAutoSaveRestore final {
+ public:
+ gfxContextMatrixAutoSaveRestore() : mContext(nullptr) {}
+
+ explicit gfxContextMatrixAutoSaveRestore(gfxContext* aContext)
+ : mContext(aContext), mMatrix(aContext->CurrentMatrix()) {}
+
+ ~gfxContextMatrixAutoSaveRestore() {
+ if (mContext) {
+ mContext->SetMatrix(mMatrix);
+ }
+ }
+
+ void SetContext(gfxContext* aContext) {
+ NS_ASSERTION(!mContext, "Not going to restore the matrix on some context!");
+ mContext = aContext;
+ mMatrix = aContext->CurrentMatrix();
+ }
+
+ void Restore() {
+ if (mContext) {
+ mContext->SetMatrix(mMatrix);
+ mContext = nullptr;
+ }
+ }
+
+ const mozilla::gfx::Matrix& Matrix() {
+ MOZ_ASSERT(mContext, "mMatrix doesn't contain a useful matrix");
+ return mMatrix;
+ }
+
+ bool HasMatrix() const { return !!mContext; }
+
+ private:
+ gfxContext* mContext;
+ mozilla::gfx::Matrix mMatrix;
+};
+
+class MOZ_STACK_CLASS gfxGroupForBlendAutoSaveRestore final {
+ public:
+ using Float = mozilla::gfx::Float;
+ using Matrix = mozilla::gfx::Matrix;
+
+ explicit gfxGroupForBlendAutoSaveRestore(gfxContext* aContext)
+ : mContext(aContext) {}
+
+ ~gfxGroupForBlendAutoSaveRestore() {
+ if (mPushedGroup) {
+ mContext->PopGroupAndBlend();
+ }
+ }
+
+ void PushGroupForBlendBack(gfxContentType aContent, Float aOpacity = 1.0f,
+ mozilla::gfx::SourceSurface* aMask = nullptr,
+ const Matrix& aMaskTransform = Matrix()) {
+ MOZ_ASSERT(!mPushedGroup, "Already called PushGroupForBlendBack once");
+ mContext->PushGroupForBlendBack(aContent, aOpacity, aMask, aMaskTransform);
+ mPushedGroup = true;
+ }
+
+ private:
+ gfxContext* mContext;
+ bool mPushedGroup = false;
+};
+
+class MOZ_STACK_CLASS gfxClipAutoSaveRestore final {
+ public:
+ using Rect = mozilla::gfx::Rect;
+
+ explicit gfxClipAutoSaveRestore(gfxContext* aContext) : mContext(aContext) {}
+
+ void Clip(const gfxRect& aRect) { Clip(ToRect(aRect)); }
+
+ void Clip(const Rect& aRect) {
+ MOZ_ASSERT(!mClipped, "Already called Clip once");
+ mContext->Clip(aRect);
+ mClipped = true;
+ }
+
+ void TransformedClip(const gfxMatrix& aTransform, const gfxRect& aRect) {
+ MOZ_ASSERT(!mClipped, "Already called Clip once");
+ if (aTransform.IsSingular()) {
+ return;
+ }
+ gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(mContext);
+ mContext->Multiply(aTransform);
+ mContext->Clip(aRect);
+ mClipped = true;
+ }
+
+ ~gfxClipAutoSaveRestore() {
+ if (mClipped) {
+ mContext->PopClip();
+ }
+ }
+
+ private:
+ gfxContext* mContext;
+ bool mClipped = false;
+};
+
+class MOZ_STACK_CLASS DrawTargetAutoDisableSubpixelAntialiasing final {
+ public:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ DrawTargetAutoDisableSubpixelAntialiasing(DrawTarget* aDT, bool aDisable)
+ : mSubpixelAntialiasingEnabled(false) {
+ if (aDisable) {
+ mDT = aDT;
+ mSubpixelAntialiasingEnabled = mDT->GetPermitSubpixelAA();
+ mDT->SetPermitSubpixelAA(false);
+ }
+ }
+ ~DrawTargetAutoDisableSubpixelAntialiasing() {
+ if (mDT) {
+ mDT->SetPermitSubpixelAA(mSubpixelAntialiasingEnabled);
+ }
+ }
+
+ private:
+ RefPtr<DrawTarget> mDT;
+ bool mSubpixelAntialiasingEnabled;
+};
+
+/* This interface should be implemented to handle exporting the clip from a
+ * context.
+ */
+class ClipExporter : public mozilla::gfx::PathSink {
+ public:
+ virtual void BeginClip(const mozilla::gfx::Matrix& aMatrix) = 0;
+ virtual void EndClip() = 0;
+};
+
+#endif /* GFX_CONTEXT_H */
diff --git a/gfx/thebes/gfxCoreTextShaper.cpp b/gfx/thebes/gfxCoreTextShaper.cpp
new file mode 100644
index 0000000000..727f8f8671
--- /dev/null
+++ b/gfx/thebes/gfxCoreTextShaper.cpp
@@ -0,0 +1,651 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "gfxCoreTextShaper.h"
+#include "gfxMacFont.h"
+#include "gfxFontUtils.h"
+#include "gfxTextRun.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/ScaledFontMac.h"
+#include "mozilla/UniquePtrExtensions.h"
+
+#include <algorithm>
+
+#include <dlfcn.h>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+// standard font descriptors that we construct the first time they're needed
+CTFontDescriptorRef gfxCoreTextShaper::sFeaturesDescriptor[kMaxFontInstances];
+
+// Helper to create a CFDictionary with the right attributes for shaping our
+// text, including imposing the given directionality.
+CFDictionaryRef gfxCoreTextShaper::CreateAttrDict(bool aRightToLeft) {
+ // Because we always shape unidirectional runs, and may have applied
+ // directional overrides, we want to force a direction rather than
+ // allowing CoreText to do its own unicode-based bidi processing.
+ SInt16 dirOverride = kCTWritingDirectionOverride |
+ (aRightToLeft ? kCTWritingDirectionRightToLeft
+ : kCTWritingDirectionLeftToRight);
+ CFNumberRef dirNumber =
+ ::CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt16Type, &dirOverride);
+ CFArrayRef dirArray = ::CFArrayCreate(
+ kCFAllocatorDefault, (const void**)&dirNumber, 1, &kCFTypeArrayCallBacks);
+ ::CFRelease(dirNumber);
+ CFTypeRef attrs[] = {kCTFontAttributeName, kCTWritingDirectionAttributeName};
+ CFTypeRef values[] = {mCTFont[0], dirArray};
+ CFDictionaryRef attrDict = ::CFDictionaryCreate(
+ kCFAllocatorDefault, attrs, values, ArrayLength(attrs),
+ &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ ::CFRelease(dirArray);
+ return attrDict;
+}
+
+gfxCoreTextShaper::gfxCoreTextShaper(gfxMacFont* aFont)
+ : gfxFontShaper(aFont),
+ mAttributesDictLTR(nullptr),
+ mAttributesDictRTL(nullptr) {
+ for (size_t i = 0; i < kMaxFontInstances; i++) {
+ mCTFont[i] = nullptr;
+ }
+ // Create our default CTFontRef
+ mCTFont[0] = CreateCTFontWithFeatures(
+ aFont->GetAdjustedSize(), GetFeaturesDescriptor(kDefaultFeatures));
+}
+
+gfxCoreTextShaper::~gfxCoreTextShaper() {
+ if (mAttributesDictLTR) {
+ ::CFRelease(mAttributesDictLTR);
+ }
+ if (mAttributesDictRTL) {
+ ::CFRelease(mAttributesDictRTL);
+ }
+ for (size_t i = 0; i < kMaxFontInstances; i++) {
+ if (mCTFont[i]) {
+ ::CFRelease(mCTFont[i]);
+ }
+ }
+}
+
+static bool IsBuggyIndicScript(intl::Script aScript) {
+ return aScript == intl::Script::BENGALI || aScript == intl::Script::KANNADA ||
+ aScript == intl::Script::ORIYA || aScript == intl::Script::KHMER;
+}
+
+bool gfxCoreTextShaper::ShapeText(DrawTarget* aDrawTarget,
+ const char16_t* aText, uint32_t aOffset,
+ uint32_t aLength, Script aScript,
+ nsAtom* aLanguage, bool aVertical,
+ RoundingFlags aRounding,
+ gfxShapedText* aShapedText) {
+ // Create a CFAttributedString with text and style info, so we can use
+ // CoreText to lay it out.
+ bool isRightToLeft = aShapedText->IsRightToLeft();
+ const UniChar* text = reinterpret_cast<const UniChar*>(aText);
+
+ CFStringRef stringObj = ::CFStringCreateWithCharactersNoCopy(
+ kCFAllocatorDefault, text, aLength, kCFAllocatorNull);
+
+ // Figure out whether we should try to set the AAT small-caps feature:
+ // examine OpenType tags for the requested style, and see if 'smcp' is
+ // among them.
+ const gfxFontStyle* style = mFont->GetStyle();
+ gfxFontEntry* entry = mFont->GetFontEntry();
+ auto handleFeatureTag = [](const uint32_t& aTag, uint32_t& aValue,
+ void* aUserArg) -> void {
+ if (aTag == HB_TAG('s', 'm', 'c', 'p') && aValue) {
+ *static_cast<bool*>(aUserArg) = true;
+ }
+ };
+ bool addSmallCaps = false;
+ MergeFontFeatures(style, entry->mFeatureSettings, false, entry->FamilyName(),
+ false, handleFeatureTag, &addSmallCaps);
+
+ // Get an attributes dictionary suitable for shaping text in the
+ // current direction, creating it if necessary.
+ CFDictionaryRef attrObj =
+ isRightToLeft ? mAttributesDictRTL : mAttributesDictLTR;
+ if (!attrObj) {
+ attrObj = CreateAttrDict(isRightToLeft);
+ (isRightToLeft ? mAttributesDictRTL : mAttributesDictLTR) = attrObj;
+ }
+
+ FeatureFlags featureFlags = kDefaultFeatures;
+ if (IsBuggyIndicScript(aScript)) {
+ // To work around buggy Indic AAT fonts shipped with OS X,
+ // we re-enable the Line Initial Smart Swashes feature that is needed
+ // for "split vowels" to work in at least Bengali and Kannada fonts.
+ // Affected fonts include Bangla MN, Bangla Sangam MN, Kannada MN,
+ // Kannada Sangam MN. See bugs 686225, 728557, 953231, 1145515.
+ // Also applies to Oriya and Khmer, see bug 1370927 and bug 1403166.
+ featureFlags |= kIndicFeatures;
+ }
+ if (aShapedText->DisableLigatures()) {
+ // For letterspacing (or maybe other situations) we need to make
+ // a copy of the CTFont with the ligature feature disabled.
+ featureFlags |= kDisableLigatures;
+ }
+ if (addSmallCaps) {
+ featureFlags |= kAddSmallCaps;
+ }
+
+ // For the disabled-ligature, buggy-indic-font or small-caps case, replace
+ // the default CTFont in the attribute dictionary with a tweaked version.
+ CFMutableDictionaryRef mutableAttr = nullptr;
+ if (featureFlags != 0) {
+ if (!mCTFont[featureFlags]) {
+ mCTFont[featureFlags] = CreateCTFontWithFeatures(
+ mFont->GetAdjustedSize(), GetFeaturesDescriptor(featureFlags));
+ }
+ mutableAttr =
+ ::CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 2, attrObj);
+ ::CFDictionaryReplaceValue(mutableAttr, kCTFontAttributeName,
+ mCTFont[featureFlags]);
+ attrObj = mutableAttr;
+ }
+
+ // Now we can create an attributed string
+ CFAttributedStringRef attrStringObj =
+ ::CFAttributedStringCreate(kCFAllocatorDefault, stringObj, attrObj);
+ ::CFRelease(stringObj);
+
+ // Create the CoreText line from our string, then we're done with it
+ CTLineRef line = ::CTLineCreateWithAttributedString(attrStringObj);
+ ::CFRelease(attrStringObj);
+
+ // and finally retrieve the glyph data and store into the gfxTextRun
+ CFArrayRef glyphRuns = ::CTLineGetGlyphRuns(line);
+ uint32_t numRuns = ::CFArrayGetCount(glyphRuns);
+
+ // Iterate through the glyph runs.
+ bool success = true;
+ for (uint32_t runIndex = 0; runIndex < numRuns; runIndex++) {
+ CTRunRef aCTRun = (CTRunRef)::CFArrayGetValueAtIndex(glyphRuns, runIndex);
+ CFRange range = ::CTRunGetStringRange(aCTRun);
+ CFDictionaryRef runAttr = ::CTRunGetAttributes(aCTRun);
+ if (runAttr != attrObj) {
+ // If Core Text manufactured a new dictionary, this may indicate
+ // unexpected font substitution. In that case, we fail (and fall
+ // back to harfbuzz shaping)...
+ const void* font1 = ::CFDictionaryGetValue(attrObj, kCTFontAttributeName);
+ const void* font2 = ::CFDictionaryGetValue(runAttr, kCTFontAttributeName);
+ if (font1 != font2) {
+ // ...except that if the fallback was only for a variation
+ // selector or join control that is otherwise unsupported,
+ // we just ignore it.
+ if (range.length == 1) {
+ char16_t ch = aText[range.location];
+ if (gfxFontUtils::IsJoinControl(ch) ||
+ gfxFontUtils::IsVarSelector(ch)) {
+ continue;
+ }
+ }
+ NS_WARNING("unexpected font fallback in Core Text");
+ success = false;
+ break;
+ }
+ }
+ if (SetGlyphsFromRun(aShapedText, aOffset, aLength, aCTRun) != NS_OK) {
+ success = false;
+ break;
+ }
+ }
+
+ if (mutableAttr) {
+ ::CFRelease(mutableAttr);
+ }
+ ::CFRelease(line);
+
+ return success;
+}
+
+#define SMALL_GLYPH_RUN \
+ 128 // preallocated size of our auto arrays for per-glyph data;
+ // some testing indicates that 90%+ of glyph runs will fit
+ // without requiring a separate allocation
+
+nsresult gfxCoreTextShaper::SetGlyphsFromRun(gfxShapedText* aShapedText,
+ uint32_t aOffset, uint32_t aLength,
+ CTRunRef aCTRun) {
+ typedef gfxShapedText::CompressedGlyph CompressedGlyph;
+
+ int32_t direction = aShapedText->IsRightToLeft() ? -1 : 1;
+
+ int32_t numGlyphs = ::CTRunGetGlyphCount(aCTRun);
+ if (numGlyphs == 0) {
+ return NS_OK;
+ }
+
+ int32_t wordLength = aLength;
+
+ // character offsets get really confusing here, as we have to keep track of
+ // (a) the text in the actual textRun we're constructing
+ // (c) the string that was handed to CoreText, which contains the text of
+ // the font run
+ // (d) the CTRun currently being processed, which may be a sub-run of the
+ // CoreText line
+
+ // get the source string range within the CTLine's text
+ CFRange stringRange = ::CTRunGetStringRange(aCTRun);
+ // skip the run if it is entirely outside the actual range of the font run
+ if (stringRange.location + stringRange.length <= 0 ||
+ stringRange.location >= wordLength) {
+ return NS_OK;
+ }
+
+ // retrieve the laid-out glyph data from the CTRun
+ UniquePtr<CGGlyph[]> glyphsArray;
+ UniquePtr<CGPoint[]> positionsArray;
+ UniquePtr<CFIndex[]> glyphToCharArray;
+ const CGGlyph* glyphs = nullptr;
+ const CGPoint* positions = nullptr;
+ const CFIndex* glyphToChar = nullptr;
+
+ // Testing indicates that CTRunGetGlyphsPtr (almost?) always succeeds,
+ // and so allocating a new array and copying data with CTRunGetGlyphs
+ // will be extremely rare.
+ // If this were not the case, we could use an AutoTArray<> to
+ // try and avoid the heap allocation for small runs.
+ // It's possible that some future change to CoreText will mean that
+ // CTRunGetGlyphsPtr fails more often; if this happens, AutoTArray<>
+ // may become an attractive option.
+ glyphs = ::CTRunGetGlyphsPtr(aCTRun);
+ if (!glyphs) {
+ glyphsArray = MakeUniqueFallible<CGGlyph[]>(numGlyphs);
+ if (!glyphsArray) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ ::CTRunGetGlyphs(aCTRun, ::CFRangeMake(0, 0), glyphsArray.get());
+ glyphs = glyphsArray.get();
+ }
+
+ positions = ::CTRunGetPositionsPtr(aCTRun);
+ if (!positions) {
+ positionsArray = MakeUniqueFallible<CGPoint[]>(numGlyphs);
+ if (!positionsArray) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ ::CTRunGetPositions(aCTRun, ::CFRangeMake(0, 0), positionsArray.get());
+ positions = positionsArray.get();
+ }
+
+ // Remember that the glyphToChar indices relate to the CoreText line,
+ // not to the beginning of the textRun, the font run,
+ // or the stringRange of the glyph run
+ glyphToChar = ::CTRunGetStringIndicesPtr(aCTRun);
+ if (!glyphToChar) {
+ glyphToCharArray = MakeUniqueFallible<CFIndex[]>(numGlyphs);
+ if (!glyphToCharArray) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ ::CTRunGetStringIndices(aCTRun, ::CFRangeMake(0, 0),
+ glyphToCharArray.get());
+ glyphToChar = glyphToCharArray.get();
+ }
+
+ double runWidth = ::CTRunGetTypographicBounds(aCTRun, ::CFRangeMake(0, 0),
+ nullptr, nullptr, nullptr);
+
+ AutoTArray<gfxShapedText::DetailedGlyph, 1> detailedGlyphs;
+ CompressedGlyph* charGlyphs = aShapedText->GetCharacterGlyphs() + aOffset;
+
+ // CoreText gives us the glyphindex-to-charindex mapping, which relates each
+ // glyph to a source text character; we also need the charindex-to-glyphindex
+ // mapping to find the glyph for a given char. Note that some chars may not
+ // map to any glyph (ligature continuations), and some may map to several
+ // glyphs (eg Indic split vowels). We set the glyph index to NO_GLYPH for
+ // chars that have no associated glyph, and we record the last glyph index for
+ // cases where the char maps to several glyphs, so that our clumping will
+ // include all the glyph fragments for the character.
+
+ // The charToGlyph array is indexed by char position within the stringRange of
+ // the glyph run.
+
+ static const int32_t NO_GLYPH = -1;
+ AutoTArray<int32_t, SMALL_GLYPH_RUN> charToGlyphArray;
+ if (!charToGlyphArray.SetLength(stringRange.length, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ int32_t* charToGlyph = charToGlyphArray.Elements();
+ for (int32_t offset = 0; offset < stringRange.length; ++offset) {
+ charToGlyph[offset] = NO_GLYPH;
+ }
+ for (int32_t i = 0; i < numGlyphs; ++i) {
+ int32_t loc = glyphToChar[i] - stringRange.location;
+ if (loc >= 0 && loc < stringRange.length) {
+ charToGlyph[loc] = i;
+ }
+ }
+
+ // Find character and glyph clumps that correspond, allowing for ligatures,
+ // indic reordering, split glyphs, etc.
+ //
+ // The idea is that we'll find a character sequence starting at the first char
+ // of stringRange, and extend it until it includes the character associated
+ // with the first glyph; we also extend it as long as there are "holes" in the
+ // range of glyphs. So we will eventually have a contiguous sequence of
+ // characters, starting at the beginning of the range, that map to a
+ // contiguous sequence of glyphs, starting at the beginning of the glyph
+ // array. That's a clump; then we update the starting positions and repeat.
+ //
+ // NB: In the case of RTL layouts, we iterate over the stringRange in reverse.
+ //
+
+ // This may find characters that fall outside the range 0:wordLength,
+ // so we won't necessarily use everything we find here.
+
+ bool isRightToLeft = aShapedText->IsRightToLeft();
+ int32_t glyphStart =
+ 0; // looking for a clump that starts at this glyph index
+ int32_t charStart =
+ isRightToLeft
+ ? stringRange.length - 1
+ : 0; // and this char index (in the stringRange of the glyph run)
+
+ while (glyphStart <
+ numGlyphs) { // keep finding groups until all glyphs are accounted for
+ bool inOrder = true;
+ int32_t charEnd = glyphToChar[glyphStart] - stringRange.location;
+ NS_WARNING_ASSERTION(charEnd >= 0 && charEnd < stringRange.length,
+ "glyph-to-char mapping points outside string range");
+ // clamp charEnd to the valid range of the string
+ charEnd = std::max(charEnd, 0);
+ charEnd = std::min(charEnd, int32_t(stringRange.length));
+
+ int32_t glyphEnd = glyphStart;
+ int32_t charLimit = isRightToLeft ? -1 : stringRange.length;
+ do {
+ // This is normally executed once for each iteration of the outer loop,
+ // but in unusual cases where the character/glyph association is complex,
+ // the initial character range might correspond to a non-contiguous
+ // glyph range with "holes" in it. If so, we will repeat this loop to
+ // extend the character range until we have a contiguous glyph sequence.
+ NS_ASSERTION((direction > 0 && charEnd < charLimit) ||
+ (direction < 0 && charEnd > charLimit),
+ "no characters left in range?");
+ charEnd += direction;
+ while (charEnd != charLimit && charToGlyph[charEnd] == NO_GLYPH) {
+ charEnd += direction;
+ }
+
+ // find the maximum glyph index covered by the clump so far
+ if (isRightToLeft) {
+ for (int32_t i = charStart; i > charEnd; --i) {
+ if (charToGlyph[i] != NO_GLYPH) {
+ // update extent of glyph range
+ glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1);
+ }
+ }
+ } else {
+ for (int32_t i = charStart; i < charEnd; ++i) {
+ if (charToGlyph[i] != NO_GLYPH) {
+ // update extent of glyph range
+ glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1);
+ }
+ }
+ }
+
+ if (glyphEnd == glyphStart + 1) {
+ // for the common case of a single-glyph clump, we can skip the
+ // following checks
+ break;
+ }
+
+ if (glyphEnd == glyphStart) {
+ // no glyphs, try to extend the clump
+ continue;
+ }
+
+ // check whether all glyphs in the range are associated with the
+ // characters in our clump; if not, we have a discontinuous range, and
+ // should extend it unless we've reached the end of the text
+ bool allGlyphsAreWithinCluster = true;
+ int32_t prevGlyphCharIndex = charStart;
+ for (int32_t i = glyphStart; i < glyphEnd; ++i) {
+ int32_t glyphCharIndex = glyphToChar[i] - stringRange.location;
+ if (isRightToLeft) {
+ if (glyphCharIndex > charStart || glyphCharIndex <= charEnd) {
+ allGlyphsAreWithinCluster = false;
+ break;
+ }
+ if (glyphCharIndex > prevGlyphCharIndex) {
+ inOrder = false;
+ }
+ prevGlyphCharIndex = glyphCharIndex;
+ } else {
+ if (glyphCharIndex < charStart || glyphCharIndex >= charEnd) {
+ allGlyphsAreWithinCluster = false;
+ break;
+ }
+ if (glyphCharIndex < prevGlyphCharIndex) {
+ inOrder = false;
+ }
+ prevGlyphCharIndex = glyphCharIndex;
+ }
+ }
+ if (allGlyphsAreWithinCluster) {
+ break;
+ }
+ } while (charEnd != charLimit);
+
+ NS_WARNING_ASSERTION(glyphStart < glyphEnd,
+ "character/glyph clump contains no glyphs!");
+ if (glyphStart == glyphEnd) {
+ ++glyphStart; // make progress - avoid potential infinite loop
+ charStart = charEnd;
+ continue;
+ }
+
+ NS_WARNING_ASSERTION(charStart != charEnd,
+ "character/glyph clump contains no characters!");
+ if (charStart == charEnd) {
+ glyphStart = glyphEnd; // this is bad - we'll discard the glyph(s),
+ // as there's nowhere to attach them
+ continue;
+ }
+
+ // Now charStart..charEnd is a ligature clump, corresponding to
+ // glyphStart..glyphEnd; Set baseCharIndex to the char we'll actually attach
+ // the glyphs to (1st of ligature), and endCharIndex to the limit (position
+ // beyond the last char), adjusting for the offset of the stringRange
+ // relative to the textRun.
+ int32_t baseCharIndex, endCharIndex;
+ if (isRightToLeft) {
+ while (charEnd >= 0 && charToGlyph[charEnd] == NO_GLYPH) {
+ charEnd--;
+ }
+ baseCharIndex = charEnd + stringRange.location + 1;
+ endCharIndex = charStart + stringRange.location + 1;
+ } else {
+ while (charEnd < stringRange.length && charToGlyph[charEnd] == NO_GLYPH) {
+ charEnd++;
+ }
+ baseCharIndex = charStart + stringRange.location;
+ endCharIndex = charEnd + stringRange.location;
+ }
+
+ // Then we check if the clump falls outside our actual string range; if so,
+ // just go to the next.
+ if (endCharIndex <= 0 || baseCharIndex >= wordLength) {
+ glyphStart = glyphEnd;
+ charStart = charEnd;
+ continue;
+ }
+ // Ensure we won't try to go beyond the valid length of the word's text
+ baseCharIndex = std::max(baseCharIndex, 0);
+ endCharIndex = std::min(endCharIndex, wordLength);
+
+ // Now we're ready to set the glyph info in the textRun; measure the glyph
+ // width of the first (perhaps only) glyph, to see if it is "Simple"
+ int32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit();
+ double toNextGlyph;
+ if (glyphStart < numGlyphs - 1) {
+ toNextGlyph = positions[glyphStart + 1].x - positions[glyphStart].x;
+ } else {
+ toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x;
+ }
+ int32_t advance = int32_t(toNextGlyph * appUnitsPerDevUnit);
+
+ // Check if it's a simple one-to-one mapping
+ int32_t glyphsInClump = glyphEnd - glyphStart;
+ if (glyphsInClump == 1 &&
+ gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyphs[glyphStart]) &&
+ gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
+ charGlyphs[baseCharIndex].IsClusterStart() &&
+ positions[glyphStart].y == 0.0) {
+ charGlyphs[baseCharIndex].SetSimpleGlyph(advance, glyphs[glyphStart]);
+ } else {
+ // collect all glyphs in a list to be assigned to the first char;
+ // there must be at least one in the clump, and we already measured its
+ // advance, hence the placement of the loop-exit test and the measurement
+ // of the next glyph
+ while (true) {
+ gfxTextRun::DetailedGlyph* details = detailedGlyphs.AppendElement();
+ details->mGlyphID = glyphs[glyphStart];
+ details->mOffset.y = -positions[glyphStart].y * appUnitsPerDevUnit;
+ details->mAdvance = advance;
+ if (++glyphStart >= glyphEnd) {
+ break;
+ }
+ if (glyphStart < numGlyphs - 1) {
+ toNextGlyph = positions[glyphStart + 1].x - positions[glyphStart].x;
+ } else {
+ toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x;
+ }
+ advance = int32_t(toNextGlyph * appUnitsPerDevUnit);
+ }
+
+ aShapedText->SetDetailedGlyphs(aOffset + baseCharIndex,
+ detailedGlyphs.Length(),
+ detailedGlyphs.Elements());
+
+ detailedGlyphs.Clear();
+ }
+
+ // the rest of the chars in the group are ligature continuations, no
+ // associated glyphs
+ while (++baseCharIndex != endCharIndex && baseCharIndex < wordLength) {
+ CompressedGlyph& shapedTextGlyph = charGlyphs[baseCharIndex];
+ NS_ASSERTION(!shapedTextGlyph.IsSimpleGlyph(),
+ "overwriting a simple glyph");
+ shapedTextGlyph.SetComplex(inOrder && shapedTextGlyph.IsClusterStart(),
+ false);
+ }
+
+ glyphStart = glyphEnd;
+ charStart = charEnd;
+ }
+
+ return NS_OK;
+}
+
+#undef SMALL_GLYPH_RUN
+
+// Construct the font attribute descriptor that we'll apply by default when
+// creating a CTFontRef. This will turn off line-edge swashes by default,
+// because we don't know the actual line breaks when doing glyph shaping.
+
+// We also cache feature descriptors for shaping with disabled ligatures, and
+// for buggy Indic AAT font workarounds, created on an as-needed basis.
+
+#define MAX_FEATURES 5 // max used by any of our Get*Descriptor functions
+
+CTFontDescriptorRef gfxCoreTextShaper::CreateFontFeaturesDescriptor(
+ const std::pair<SInt16, SInt16>* aFeatures, size_t aCount) {
+ MOZ_ASSERT(aCount <= MAX_FEATURES);
+
+ CFDictionaryRef featureSettings[MAX_FEATURES];
+
+ for (size_t i = 0; i < aCount; i++) {
+ CFNumberRef type = ::CFNumberCreate(
+ kCFAllocatorDefault, kCFNumberSInt16Type, &aFeatures[i].first);
+ CFNumberRef selector = ::CFNumberCreate(
+ kCFAllocatorDefault, kCFNumberSInt16Type, &aFeatures[i].second);
+
+ CFTypeRef keys[] = {kCTFontFeatureTypeIdentifierKey,
+ kCTFontFeatureSelectorIdentifierKey};
+ CFTypeRef values[] = {type, selector};
+ featureSettings[i] = ::CFDictionaryCreate(
+ kCFAllocatorDefault, (const void**)keys, (const void**)values,
+ ArrayLength(keys), &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ ::CFRelease(selector);
+ ::CFRelease(type);
+ }
+
+ CFArrayRef featuresArray =
+ ::CFArrayCreate(kCFAllocatorDefault, (const void**)featureSettings,
+ aCount, // not ArrayLength(featureSettings), as we
+ // may not have used all the allocated slots
+ &kCFTypeArrayCallBacks);
+
+ for (size_t i = 0; i < aCount; i++) {
+ ::CFRelease(featureSettings[i]);
+ }
+
+ const CFTypeRef attrKeys[] = {kCTFontFeatureSettingsAttribute};
+ const CFTypeRef attrValues[] = {featuresArray};
+ CFDictionaryRef attributesDict = ::CFDictionaryCreate(
+ kCFAllocatorDefault, (const void**)attrKeys, (const void**)attrValues,
+ ArrayLength(attrKeys), &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ ::CFRelease(featuresArray);
+
+ CTFontDescriptorRef descriptor =
+ ::CTFontDescriptorCreateWithAttributes(attributesDict);
+ ::CFRelease(attributesDict);
+
+ return descriptor;
+}
+
+CTFontDescriptorRef gfxCoreTextShaper::GetFeaturesDescriptor(
+ FeatureFlags aFeatureFlags) {
+ MOZ_ASSERT(aFeatureFlags < kMaxFontInstances);
+ if (!sFeaturesDescriptor[aFeatureFlags]) {
+ typedef std::pair<SInt16, SInt16> FeatT;
+ AutoTArray<FeatT, MAX_FEATURES> features;
+ features.AppendElement(
+ FeatT(kSmartSwashType, kLineFinalSwashesOffSelector));
+ if ((aFeatureFlags & kIndicFeatures) == 0) {
+ features.AppendElement(
+ FeatT(kSmartSwashType, kLineInitialSwashesOffSelector));
+ }
+ if (aFeatureFlags & kAddSmallCaps) {
+ features.AppendElement(FeatT(kLetterCaseType, kSmallCapsSelector));
+ features.AppendElement(
+ FeatT(kLowerCaseType, kLowerCaseSmallCapsSelector));
+ }
+ if (aFeatureFlags & kDisableLigatures) {
+ features.AppendElement(
+ FeatT(kLigaturesType, kCommonLigaturesOffSelector));
+ }
+ MOZ_ASSERT(features.Length() <= MAX_FEATURES);
+ sFeaturesDescriptor[aFeatureFlags] =
+ CreateFontFeaturesDescriptor(features.Elements(), features.Length());
+ }
+ return sFeaturesDescriptor[aFeatureFlags];
+}
+
+CTFontRef gfxCoreTextShaper::CreateCTFontWithFeatures(
+ CGFloat aSize, CTFontDescriptorRef aDescriptor) {
+ const gfxFontEntry* fe = mFont->GetFontEntry();
+ bool isInstalledFont = !fe->IsUserFont() || fe->IsLocalUserFont();
+ CGFontRef cgFont = static_cast<gfxMacFont*>(mFont)->GetCGFontRef();
+ return CreateCTFontFromCGFontWithVariations(cgFont, aSize, isInstalledFont,
+ aDescriptor);
+}
+
+void gfxCoreTextShaper::Shutdown() // [static]
+{
+ for (size_t i = 0; i < kMaxFontInstances; i++) {
+ if (sFeaturesDescriptor[i] != nullptr) {
+ ::CFRelease(sFeaturesDescriptor[i]);
+ sFeaturesDescriptor[i] = nullptr;
+ }
+ }
+}
diff --git a/gfx/thebes/gfxCoreTextShaper.h b/gfx/thebes/gfxCoreTextShaper.h
new file mode 100644
index 0000000000..1385bd00bb
--- /dev/null
+++ b/gfx/thebes/gfxCoreTextShaper.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_CORETEXTSHAPER_H
+#define GFX_CORETEXTSHAPER_H
+
+#include "gfxFont.h"
+
+#include <ApplicationServices/ApplicationServices.h>
+
+class gfxMacFont;
+
+class gfxCoreTextShaper : public gfxFontShaper {
+ public:
+ explicit gfxCoreTextShaper(gfxMacFont* aFont);
+
+ virtual ~gfxCoreTextShaper();
+
+ bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, uint32_t aOffset, uint32_t aLength,
+ Script aScript, nsAtom* aLanguage, bool aVertical, RoundingFlags aRounding,
+ gfxShapedText* aShapedText) override;
+
+ // clean up static objects that may have been cached
+ static void Shutdown();
+
+ // Flags used to track what AAT features should be enabled on the Core Text
+ // font instance. (Internal; only public so that we can use the
+ // MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS macro below.)
+ enum FeatureFlags : uint8_t {
+ kDefaultFeatures = 0x00,
+ // bit flags for non-default feature settings we might need
+ // to use, which will require separate font instances
+ kDisableLigatures = 0x01,
+ kAddSmallCaps = 0x02,
+ kIndicFeatures = 0x04,
+
+ // number of font instances, indexed by OR-ing the flags above
+ kMaxFontInstances = 8
+ };
+
+ protected:
+ CTFontRef mCTFont[kMaxFontInstances];
+
+ // attributes for shaping text with LTR or RTL directionality
+ CFDictionaryRef mAttributesDictLTR;
+ CFDictionaryRef mAttributesDictRTL;
+
+ nsresult SetGlyphsFromRun(gfxShapedText* aShapedText, uint32_t aOffset, uint32_t aLength,
+ CTRunRef aCTRun);
+
+ CTFontRef CreateCTFontWithFeatures(CGFloat aSize, CTFontDescriptorRef aDescriptor);
+
+ CFDictionaryRef CreateAttrDict(bool aRightToLeft);
+ CFDictionaryRef CreateAttrDictWithoutDirection();
+
+ static CTFontDescriptorRef CreateFontFeaturesDescriptor(
+ const std::pair<SInt16, SInt16>* aFeatures, size_t aCount);
+
+ static CTFontDescriptorRef GetFeaturesDescriptor(FeatureFlags aFeatureFlags);
+
+ // cached font descriptors, created the first time they're needed
+ static CTFontDescriptorRef sFeaturesDescriptor[kMaxFontInstances];
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(gfxCoreTextShaper::FeatureFlags)
+
+#endif /* GFX_CORETEXTSHAPER_H */
diff --git a/gfx/thebes/gfxDWriteCommon.cpp b/gfx/thebes/gfxDWriteCommon.cpp
new file mode 100644
index 0000000000..ee81f15680
--- /dev/null
+++ b/gfx/thebes/gfxDWriteCommon.cpp
@@ -0,0 +1,204 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxDWriteCommon.h"
+
+#include <unordered_map>
+
+#include "mozilla/Atomics.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/gfx/Logging.h"
+
+class gfxDWriteFontFileStream;
+
+static mozilla::StaticMutex sFontFileStreamsMutex MOZ_UNANNOTATED;
+static uint64_t sNextFontFileKey = 0;
+static std::unordered_map<uint64_t, gfxDWriteFontFileStream*> sFontFileStreams;
+
+IDWriteFontFileLoader* gfxDWriteFontFileLoader::mInstance = nullptr;
+
+class gfxDWriteFontFileStream final : public IDWriteFontFileStream {
+ public:
+ /**
+ * Used by the FontFileLoader to create a new font stream,
+ * this font stream is created from data in memory. The memory
+ * passed may be released after object creation, it will be
+ * copied internally.
+ *
+ * @param aData Font data
+ */
+ gfxDWriteFontFileStream(const uint8_t* aData, uint32_t aLength,
+ uint64_t aFontFileKey);
+ ~gfxDWriteFontFileStream();
+
+ // IUnknown interface
+ IFACEMETHOD(QueryInterface)(IID const& iid, OUT void** ppObject) {
+ if (iid == __uuidof(IDWriteFontFileStream)) {
+ *ppObject = static_cast<IDWriteFontFileStream*>(this);
+ return S_OK;
+ } else if (iid == __uuidof(IUnknown)) {
+ *ppObject = static_cast<IUnknown*>(this);
+ return S_OK;
+ } else {
+ return E_NOINTERFACE;
+ }
+ }
+
+ IFACEMETHOD_(ULONG, AddRef)() {
+ MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
+ return ++mRefCnt;
+ }
+
+ IFACEMETHOD_(ULONG, Release)() {
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ uint32_t count = --mRefCnt;
+ if (count == 0) {
+ // Avoid locking unless necessary. Verify the refcount hasn't changed
+ // while locked. Delete within the scope of the lock when zero.
+ mozilla::StaticMutexAutoLock lock(sFontFileStreamsMutex);
+ if (0 != mRefCnt) {
+ return mRefCnt;
+ }
+ delete this;
+ }
+ return count;
+ }
+
+ // IDWriteFontFileStream methods
+ virtual HRESULT STDMETHODCALLTYPE
+ ReadFileFragment(void const** fragmentStart, UINT64 fileOffset,
+ UINT64 fragmentSize, OUT void** fragmentContext);
+
+ virtual void STDMETHODCALLTYPE ReleaseFileFragment(void* fragmentContext);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFileSize(OUT UINT64* fileSize);
+
+ virtual HRESULT STDMETHODCALLTYPE GetLastWriteTime(OUT UINT64* lastWriteTime);
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ return mData.ShallowSizeOfExcludingThis(mallocSizeOf);
+ }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
+ }
+
+ private:
+ FallibleTArray<uint8_t> mData;
+ mozilla::Atomic<uint32_t> mRefCnt;
+ uint64_t mFontFileKey;
+};
+
+gfxDWriteFontFileStream::gfxDWriteFontFileStream(const uint8_t* aData,
+ uint32_t aLength,
+ uint64_t aFontFileKey)
+ : mFontFileKey(aFontFileKey) {
+ // If this fails, mData will remain empty. That's OK: GetFileSize()
+ // will then return 0, etc., and the font just won't load.
+ if (!mData.AppendElements(aData, aLength, mozilla::fallible_t())) {
+ NS_WARNING("Failed to store data in gfxDWriteFontFileStream");
+ }
+}
+
+gfxDWriteFontFileStream::~gfxDWriteFontFileStream() {
+ sFontFileStreams.erase(mFontFileKey);
+}
+
+HRESULT STDMETHODCALLTYPE
+gfxDWriteFontFileStream::GetFileSize(UINT64* fileSize) {
+ *fileSize = mData.Length();
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE
+gfxDWriteFontFileStream::GetLastWriteTime(UINT64* lastWriteTime) {
+ return E_NOTIMPL;
+}
+
+HRESULT STDMETHODCALLTYPE gfxDWriteFontFileStream::ReadFileFragment(
+ const void** fragmentStart, UINT64 fileOffset, UINT64 fragmentSize,
+ void** fragmentContext) {
+ // We are required to do bounds checking.
+ if (fileOffset + fragmentSize > (UINT64)mData.Length()) {
+ return E_FAIL;
+ }
+ // We should be alive for the duration of this.
+ *fragmentStart = &mData[fileOffset];
+ *fragmentContext = nullptr;
+ return S_OK;
+}
+
+void STDMETHODCALLTYPE
+gfxDWriteFontFileStream::ReleaseFileFragment(void* fragmentContext) {}
+
+HRESULT STDMETHODCALLTYPE gfxDWriteFontFileLoader::CreateStreamFromKey(
+ const void* fontFileReferenceKey, UINT32 fontFileReferenceKeySize,
+ IDWriteFontFileStream** fontFileStream) {
+ if (!fontFileReferenceKey || !fontFileStream) {
+ return E_POINTER;
+ }
+
+ mozilla::StaticMutexAutoLock lock(sFontFileStreamsMutex);
+ uint64_t fontFileKey = *static_cast<const uint64_t*>(fontFileReferenceKey);
+ auto found = sFontFileStreams.find(fontFileKey);
+ if (found == sFontFileStreams.end()) {
+ *fontFileStream = nullptr;
+ return E_FAIL;
+ }
+
+ found->second->AddRef();
+ *fontFileStream = found->second;
+ return S_OK;
+}
+
+/* static */
+HRESULT
+gfxDWriteFontFileLoader::CreateCustomFontFile(
+ const uint8_t* aFontData, uint32_t aLength, IDWriteFontFile** aFontFile,
+ IDWriteFontFileStream** aFontFileStream) {
+ MOZ_ASSERT(aFontFile);
+ MOZ_ASSERT(aFontFileStream);
+
+ RefPtr<IDWriteFactory> factory = mozilla::gfx::Factory::GetDWriteFactory();
+ if (!factory) {
+ gfxCriticalError()
+ << "Failed to get DWrite Factory in CreateCustomFontFile.";
+ return E_FAIL;
+ }
+
+ sFontFileStreamsMutex.Lock();
+ uint64_t fontFileKey = sNextFontFileKey++;
+ RefPtr<gfxDWriteFontFileStream> ffsRef =
+ new gfxDWriteFontFileStream(aFontData, aLength, fontFileKey);
+ sFontFileStreams[fontFileKey] = ffsRef;
+ sFontFileStreamsMutex.Unlock();
+
+ RefPtr<IDWriteFontFile> fontFile;
+ HRESULT hr = factory->CreateCustomFontFileReference(
+ &fontFileKey, sizeof(fontFileKey), Instance(), getter_AddRefs(fontFile));
+ if (FAILED(hr)) {
+ NS_WARNING("Failed to load font file from data!");
+ return hr;
+ }
+
+ fontFile.forget(aFontFile);
+ ffsRef.forget(aFontFileStream);
+
+ return S_OK;
+}
+
+size_t gfxDWriteFontFileLoader::SizeOfIncludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ size_t sizes = mallocSizeOf(this);
+
+ // We are a singleton type that is effective owner of sFontFileStreams.
+ MOZ_ASSERT(this == mInstance);
+ for (const auto& entry : sFontFileStreams) {
+ gfxDWriteFontFileStream* fileStream = entry.second;
+ sizes += fileStream->SizeOfIncludingThis(mallocSizeOf);
+ }
+
+ return sizes;
+}
diff --git a/gfx/thebes/gfxDWriteCommon.h b/gfx/thebes/gfxDWriteCommon.h
new file mode 100644
index 0000000000..8daf02fd14
--- /dev/null
+++ b/gfx/thebes/gfxDWriteCommon.h
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_DWRITECOMMON_H
+#define GFX_DWRITECOMMON_H
+
+// Mozilla includes
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "cairo-features.h"
+#include "gfxFontConstants.h"
+#include "nsTArray.h"
+#include "gfxWindowsPlatform.h"
+
+#include <windows.h>
+#include <dwrite.h>
+
+#define GFX_CLEARTYPE_PARAMS "gfx.font_rendering.cleartype_params."
+#define GFX_CLEARTYPE_PARAMS_GAMMA "gfx.font_rendering.cleartype_params.gamma"
+#define GFX_CLEARTYPE_PARAMS_CONTRAST \
+ "gfx.font_rendering.cleartype_params.enhanced_contrast"
+#define GFX_CLEARTYPE_PARAMS_LEVEL \
+ "gfx.font_rendering.cleartype_params.cleartype_level"
+#define GFX_CLEARTYPE_PARAMS_STRUCTURE \
+ "gfx.font_rendering.cleartype_params.pixel_structure"
+#define GFX_CLEARTYPE_PARAMS_MODE \
+ "gfx.font_rendering.cleartype_params.rendering_mode"
+
+#define DISPLAY1_REGISTRY_KEY \
+ HKEY_CURRENT_USER, L"Software\\Microsoft\\Avalon.Graphics\\DISPLAY1"
+
+#define ENHANCED_CONTRAST_VALUE_NAME L"EnhancedContrastLevel"
+
+// FIXME: This shouldn't look at constants probably.
+static inline DWRITE_FONT_STRETCH DWriteFontStretchFromStretch(
+ mozilla::FontStretch aStretch) {
+ if (aStretch == mozilla::FontStretch::ULTRA_CONDENSED) {
+ return DWRITE_FONT_STRETCH_ULTRA_CONDENSED;
+ }
+ if (aStretch == mozilla::FontStretch::EXTRA_CONDENSED) {
+ return DWRITE_FONT_STRETCH_EXTRA_CONDENSED;
+ }
+ if (aStretch == mozilla::FontStretch::CONDENSED) {
+ return DWRITE_FONT_STRETCH_CONDENSED;
+ }
+ if (aStretch == mozilla::FontStretch::SEMI_CONDENSED) {
+ return DWRITE_FONT_STRETCH_SEMI_CONDENSED;
+ }
+ if (aStretch == mozilla::FontStretch::NORMAL) {
+ return DWRITE_FONT_STRETCH_NORMAL;
+ }
+ if (aStretch == mozilla::FontStretch::SEMI_EXPANDED) {
+ return DWRITE_FONT_STRETCH_SEMI_EXPANDED;
+ }
+ if (aStretch == mozilla::FontStretch::EXPANDED) {
+ return DWRITE_FONT_STRETCH_EXPANDED;
+ }
+ if (aStretch == mozilla::FontStretch::EXTRA_EXPANDED) {
+ return DWRITE_FONT_STRETCH_EXTRA_EXPANDED;
+ }
+ if (aStretch == mozilla::FontStretch::ULTRA_EXPANDED) {
+ return DWRITE_FONT_STRETCH_ULTRA_EXPANDED;
+ }
+ return DWRITE_FONT_STRETCH_UNDEFINED;
+}
+
+static inline mozilla::FontStretch FontStretchFromDWriteStretch(
+ DWRITE_FONT_STRETCH aStretch) {
+ switch (aStretch) {
+ case DWRITE_FONT_STRETCH_ULTRA_CONDENSED:
+ return mozilla::FontStretch::ULTRA_CONDENSED;
+ case DWRITE_FONT_STRETCH_EXTRA_CONDENSED:
+ return mozilla::FontStretch::EXTRA_CONDENSED;
+ case DWRITE_FONT_STRETCH_CONDENSED:
+ return mozilla::FontStretch::CONDENSED;
+ case DWRITE_FONT_STRETCH_SEMI_CONDENSED:
+ return mozilla::FontStretch::SEMI_CONDENSED;
+ case DWRITE_FONT_STRETCH_NORMAL:
+ return mozilla::FontStretch::NORMAL;
+ case DWRITE_FONT_STRETCH_SEMI_EXPANDED:
+ return mozilla::FontStretch::SEMI_EXPANDED;
+ case DWRITE_FONT_STRETCH_EXPANDED:
+ return mozilla::FontStretch::EXPANDED;
+ case DWRITE_FONT_STRETCH_EXTRA_EXPANDED:
+ return mozilla::FontStretch::EXTRA_EXPANDED;
+ case DWRITE_FONT_STRETCH_ULTRA_EXPANDED:
+ return mozilla::FontStretch::ULTRA_EXPANDED;
+ default:
+ return mozilla::FontStretch::NORMAL;
+ }
+}
+
+class gfxDWriteFontFileLoader : public IDWriteFontFileLoader {
+ public:
+ gfxDWriteFontFileLoader() {}
+
+ // IUnknown interface
+ IFACEMETHOD(QueryInterface)(IID const& iid, OUT void** ppObject) {
+ if (iid == __uuidof(IDWriteFontFileLoader)) {
+ *ppObject = static_cast<IDWriteFontFileLoader*>(this);
+ return S_OK;
+ } else if (iid == __uuidof(IUnknown)) {
+ *ppObject = static_cast<IUnknown*>(this);
+ return S_OK;
+ } else {
+ return E_NOINTERFACE;
+ }
+ }
+
+ IFACEMETHOD_(ULONG, AddRef)() { return 1; }
+
+ IFACEMETHOD_(ULONG, Release)() { return 1; }
+
+ // IDWriteFontFileLoader methods
+ /**
+ * Important! Note the key here -has- to be a pointer to a uint64_t.
+ */
+ virtual HRESULT STDMETHODCALLTYPE CreateStreamFromKey(
+ void const* fontFileReferenceKey, UINT32 fontFileReferenceKeySize,
+ OUT IDWriteFontFileStream** fontFileStream);
+
+ /**
+ * Gets the singleton loader instance. Note that when using this font
+ * loader, the key must be a pointer to a unint64_t.
+ */
+ static IDWriteFontFileLoader* Instance() {
+ if (!mInstance) {
+ mInstance = new gfxDWriteFontFileLoader();
+ mozilla::gfx::Factory::GetDWriteFactory()->RegisterFontFileLoader(
+ mInstance);
+ }
+ return mInstance;
+ }
+
+ /**
+ * Creates a IDWriteFontFile and IDWriteFontFileStream from aFontData.
+ * The data from aFontData will be copied internally, so the caller
+ * is free to dispose of it once this method returns.
+ *
+ * @param aFontData the font data for the custom font file
+ * @param aLength length of the font data
+ * @param aFontFile out param for the created font file
+ * @param aFontFileStream out param for the corresponding stream
+ * @return HRESULT of internal calls
+ */
+ static HRESULT CreateCustomFontFile(const uint8_t* aFontData,
+ uint32_t aLength,
+ IDWriteFontFile** aFontFile,
+ IDWriteFontFileStream** aFontFileStream);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ private:
+ static IDWriteFontFileLoader* mInstance;
+};
+
+#endif /* GFX_DWRITECOMMON_H */
diff --git a/gfx/thebes/gfxDWriteFontList.cpp b/gfx/thebes/gfxDWriteFontList.cpp
new file mode 100644
index 0000000000..d31ba05deb
--- /dev/null
+++ b/gfx/thebes/gfxDWriteFontList.cpp
@@ -0,0 +1,2623 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/intl/OSPreferences.h"
+
+#include "gfxDWriteFontList.h"
+#include "gfxDWriteFonts.h"
+#include "nsUnicharUtils.h"
+#include "nsPresContext.h"
+#include "nsServiceManagerUtils.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/WindowsProcessMitigations.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+
+#include "gfxGDIFontList.h"
+#include "gfxRect.h"
+#include "SharedFontList-impl.h"
+
+#include "harfbuzz/hb.h"
+
+#include "StandardFonts-win10.inc"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using mozilla::intl::OSPreferences;
+
+#define LOG_FONTLIST(args) \
+ MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug, args)
+#define LOG_FONTLIST_ENABLED() \
+ MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug)
+
+#define LOG_FONTINIT(args) \
+ MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug, args)
+#define LOG_FONTINIT_ENABLED() \
+ MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug)
+
+#define LOG_CMAPDATA_ENABLED() \
+ MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_cmapdata), LogLevel::Debug)
+
+static __inline void BuildKeyNameFromFontName(nsACString& aName) {
+ ToLowerCase(aName);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// gfxDWriteFontFamily
+
+gfxDWriteFontFamily::~gfxDWriteFontFamily() {}
+
+static bool GetNameAsUtf8(nsACString& aName, IDWriteLocalizedStrings* aStrings,
+ UINT32 aIndex) {
+ AutoTArray<WCHAR, 32> name;
+ UINT32 length;
+ HRESULT hr = aStrings->GetStringLength(aIndex, &length);
+ if (FAILED(hr)) {
+ return false;
+ }
+ if (!name.SetLength(length + 1, fallible)) {
+ return false;
+ }
+ hr = aStrings->GetString(aIndex, name.Elements(), length + 1);
+ if (FAILED(hr)) {
+ return false;
+ }
+ aName.Truncate();
+ AppendUTF16toUTF8(
+ Substring(reinterpret_cast<const char16_t*>(name.Elements()),
+ name.Length() - 1),
+ aName);
+ return true;
+}
+
+static bool GetEnglishOrFirstName(nsACString& aName,
+ IDWriteLocalizedStrings* aStrings) {
+ UINT32 englishIdx = 0;
+ BOOL exists;
+ HRESULT hr = aStrings->FindLocaleName(L"en-us", &englishIdx, &exists);
+ if (FAILED(hr) || !exists) {
+ // Use 0 index if english is not found.
+ englishIdx = 0;
+ }
+ return GetNameAsUtf8(aName, aStrings, englishIdx);
+}
+
+static HRESULT GetDirectWriteFontName(IDWriteFont* aFont,
+ nsACString& aFontName) {
+ HRESULT hr;
+
+ RefPtr<IDWriteLocalizedStrings> names;
+ hr = aFont->GetFaceNames(getter_AddRefs(names));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ if (!GetEnglishOrFirstName(aFontName, names)) {
+ return E_FAIL;
+ }
+
+ return S_OK;
+}
+
+#define FULLNAME_ID DWRITE_INFORMATIONAL_STRING_FULL_NAME
+#define PSNAME_ID DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME
+
+// for use in reading postscript or fullname
+static HRESULT GetDirectWriteFaceName(IDWriteFont* aFont,
+ DWRITE_INFORMATIONAL_STRING_ID aWhichName,
+ nsACString& aFontName) {
+ HRESULT hr;
+
+ BOOL exists;
+ RefPtr<IDWriteLocalizedStrings> infostrings;
+ hr = aFont->GetInformationalStrings(aWhichName, getter_AddRefs(infostrings),
+ &exists);
+ if (FAILED(hr) || !exists) {
+ return E_FAIL;
+ }
+
+ if (!GetEnglishOrFirstName(aFontName, infostrings)) {
+ return E_FAIL;
+ }
+
+ return S_OK;
+}
+
+void gfxDWriteFontFamily::FindStyleVariationsLocked(
+ FontInfoData* aFontInfoData) {
+ HRESULT hr;
+ if (mHasStyles) {
+ return;
+ }
+
+ mHasStyles = true;
+
+ gfxPlatformFontList* fp = gfxPlatformFontList::PlatformFontList();
+
+ bool skipFaceNames =
+ mFaceNamesInitialized || !fp->NeedFullnamePostscriptNames();
+ bool fontInfoShouldHaveFaceNames = !mFaceNamesInitialized &&
+ fp->NeedFullnamePostscriptNames() &&
+ aFontInfoData;
+
+ for (UINT32 i = 0; i < mDWFamily->GetFontCount(); i++) {
+ RefPtr<IDWriteFont> font;
+ hr = mDWFamily->GetFont(i, getter_AddRefs(font));
+ if (FAILED(hr)) {
+ // This should never happen.
+ NS_WARNING("Failed to get existing font from family.");
+ continue;
+ }
+
+ if (font->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE) {
+ // We don't want these in the font list; we'll apply simulations
+ // on the fly when appropriate.
+ continue;
+ }
+
+ // name
+ nsCString fullID(mName);
+ nsAutoCString faceName;
+ hr = GetDirectWriteFontName(font, faceName);
+ if (FAILED(hr)) {
+ continue;
+ }
+ fullID.Append(' ');
+ fullID.Append(faceName);
+
+ // Ignore italic style's "Meiryo" because "Meiryo (Bold) Italic" has
+ // non-italic style glyphs as Japanese characters. However, using it
+ // causes serious problem if web pages wants some elements to be
+ // different style from others only with font-style. For example,
+ // <em> and <i> should be rendered as italic in the default style.
+ if (fullID.EqualsLiteral("Meiryo Italic") ||
+ fullID.EqualsLiteral("Meiryo Bold Italic")) {
+ continue;
+ }
+
+ gfxDWriteFontEntry* fe =
+ new gfxDWriteFontEntry(fullID, font, mIsSystemFontFamily);
+ fe->SetForceGDIClassic(mForceGDIClassic);
+
+ fe->SetupVariationRanges();
+
+ AddFontEntryLocked(fe);
+
+ // postscript/fullname if needed
+ nsAutoCString psname, fullname;
+ if (fontInfoShouldHaveFaceNames) {
+ aFontInfoData->GetFaceNames(fe->Name(), fullname, psname);
+ if (!fullname.IsEmpty()) {
+ fp->AddFullname(fe, fullname);
+ }
+ if (!psname.IsEmpty()) {
+ fp->AddPostscriptName(fe, psname);
+ }
+ } else if (!skipFaceNames) {
+ hr = GetDirectWriteFaceName(font, PSNAME_ID, psname);
+ if (FAILED(hr)) {
+ skipFaceNames = true;
+ } else if (psname.Length() > 0) {
+ fp->AddPostscriptName(fe, psname);
+ }
+
+ hr = GetDirectWriteFaceName(font, FULLNAME_ID, fullname);
+ if (FAILED(hr)) {
+ skipFaceNames = true;
+ } else if (fullname.Length() > 0) {
+ fp->AddFullname(fe, fullname);
+ }
+ }
+
+ if (LOG_FONTLIST_ENABLED()) {
+ nsAutoCString weightString;
+ fe->Weight().ToString(weightString);
+ LOG_FONTLIST(
+ ("(fontlist) added (%s) to family (%s)"
+ " with style: %s weight: %s stretch: %d psname: %s fullname: %s",
+ fe->Name().get(), Name().get(),
+ (fe->IsItalic()) ? "italic"
+ : (fe->IsOblique() ? "oblique" : "normal"),
+ weightString.get(), fe->Stretch().AsScalar(), psname.get(),
+ fullname.get()));
+ }
+ }
+
+ // assume that if no error, all postscript/fullnames were initialized
+ if (!skipFaceNames) {
+ mFaceNamesInitialized = true;
+ }
+
+ if (!mAvailableFonts.Length()) {
+ NS_WARNING("Family with no font faces in it.");
+ }
+
+ if (mIsBadUnderlineFamily) {
+ SetBadUnderlineFonts();
+ }
+
+ CheckForSimpleFamily();
+ if (mIsSimpleFamily) {
+ for (auto& f : mAvailableFonts) {
+ if (f) {
+ static_cast<gfxDWriteFontEntry*>(f.get())->mMayUseGDIAccess = true;
+ }
+ }
+ }
+}
+
+void gfxDWriteFontFamily::ReadFaceNames(gfxPlatformFontList* aPlatformFontList,
+ bool aNeedFullnamePostscriptNames,
+ FontInfoData* aFontInfoData) {
+ // if all needed names have already been read, skip
+ if (mOtherFamilyNamesInitialized &&
+ (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) {
+ return;
+ }
+
+ // If we've been passed a FontInfoData, we skip the DWrite implementation
+ // here and fall back to the generic code which will use that info.
+ if (!aFontInfoData) {
+ // DirectWrite version of this will try to read
+ // postscript/fullnames via DirectWrite API
+ FindStyleVariations();
+ }
+
+ // fallback to looking up via name table
+ if (!mOtherFamilyNamesInitialized || !mFaceNamesInitialized) {
+ gfxFontFamily::ReadFaceNames(aPlatformFontList,
+ aNeedFullnamePostscriptNames, aFontInfoData);
+ }
+}
+
+void gfxDWriteFontFamily::LocalizedName(nsACString& aLocalizedName) {
+ aLocalizedName = Name(); // just return canonical name in case of failure
+
+ if (!mDWFamily) {
+ return;
+ }
+
+ HRESULT hr;
+ nsAutoCString locale;
+ // We use system locale here because it's what user expects to see.
+ // See bug 1349454 for details.
+ RefPtr<OSPreferences> osprefs = OSPreferences::GetInstanceAddRefed();
+ if (!osprefs) {
+ return;
+ }
+ osprefs->GetSystemLocale(locale);
+
+ RefPtr<IDWriteLocalizedStrings> names;
+
+ hr = mDWFamily->GetFamilyNames(getter_AddRefs(names));
+ if (FAILED(hr)) {
+ return;
+ }
+ UINT32 idx = 0;
+ BOOL exists;
+ hr =
+ names->FindLocaleName(NS_ConvertUTF8toUTF16(locale).get(), &idx, &exists);
+ if (FAILED(hr)) {
+ return;
+ }
+ if (!exists) {
+ // Use english is localized is not found.
+ hr = names->FindLocaleName(L"en-us", &idx, &exists);
+ if (FAILED(hr)) {
+ return;
+ }
+ if (!exists) {
+ // Use 0 index if english is not found.
+ idx = 0;
+ }
+ }
+ AutoTArray<WCHAR, 32> famName;
+ UINT32 length;
+
+ hr = names->GetStringLength(idx, &length);
+ if (FAILED(hr)) {
+ return;
+ }
+
+ if (!famName.SetLength(length + 1, fallible)) {
+ // Eeep - running out of memory. Unlikely to end well.
+ return;
+ }
+
+ hr = names->GetString(idx, famName.Elements(), length + 1);
+ if (FAILED(hr)) {
+ return;
+ }
+
+ aLocalizedName = NS_ConvertUTF16toUTF8((const char16_t*)famName.Elements(),
+ famName.Length() - 1);
+}
+
+bool gfxDWriteFontFamily::IsSymbolFontFamily() const {
+ // Just check the first font in the family
+ if (mDWFamily->GetFontCount() > 0) {
+ RefPtr<IDWriteFont> font;
+ if (SUCCEEDED(mDWFamily->GetFont(0, getter_AddRefs(font)))) {
+ return font->IsSymbolFont();
+ }
+ }
+ return false;
+}
+
+void gfxDWriteFontFamily::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ gfxFontFamily::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+ // TODO:
+ // This doesn't currently account for |mDWFamily|
+}
+
+void gfxDWriteFontFamily::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ aSizes->mFontListSize += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// gfxDWriteFontEntry
+
+gfxFontEntry* gfxDWriteFontEntry::Clone() const {
+ MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!");
+ gfxDWriteFontEntry* fe = new gfxDWriteFontEntry(Name(), mFont);
+ fe->mWeightRange = mWeightRange;
+ fe->mStretchRange = mStretchRange;
+ fe->mStyleRange = mStyleRange;
+ return fe;
+}
+
+gfxDWriteFontEntry::~gfxDWriteFontEntry() {}
+
+static bool UsingArabicOrHebrewScriptSystemLocale() {
+ LANGID langid = PRIMARYLANGID(::GetSystemDefaultLangID());
+ switch (langid) {
+ case LANG_ARABIC:
+ case LANG_DARI:
+ case LANG_PASHTO:
+ case LANG_PERSIAN:
+ case LANG_SINDHI:
+ case LANG_UIGHUR:
+ case LANG_URDU:
+ case LANG_HEBREW:
+ return true;
+ default:
+ return false;
+ }
+}
+
+nsresult gfxDWriteFontEntry::CopyFontTable(uint32_t aTableTag,
+ nsTArray<uint8_t>& aBuffer) {
+ gfxDWriteFontList* pFontList = gfxDWriteFontList::PlatformFontList();
+ const uint32_t tagBE = NativeEndian::swapToBigEndian(aTableTag);
+
+ // Don't use GDI table loading for symbol fonts or for
+ // italic fonts in Arabic-script system locales because of
+ // potential cmap discrepancies, see bug 629386.
+ // Ditto for Hebrew, bug 837498.
+ if (mFont && mMayUseGDIAccess && pFontList->UseGDIFontTableAccess() &&
+ !(!IsUpright() && UsingArabicOrHebrewScriptSystemLocale()) &&
+ !mFont->IsSymbolFont()) {
+ LOGFONTW logfont = {0};
+ if (InitLogFont(mFont, &logfont)) {
+ AutoDC dc;
+ AutoSelectFont font(dc.GetDC(), &logfont);
+ if (font.IsValid()) {
+ uint32_t tableSize = ::GetFontData(dc.GetDC(), tagBE, 0, nullptr, 0);
+ if (tableSize != GDI_ERROR) {
+ if (aBuffer.SetLength(tableSize, fallible)) {
+ ::GetFontData(dc.GetDC(), tagBE, 0, aBuffer.Elements(),
+ aBuffer.Length());
+ return NS_OK;
+ }
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+ }
+
+ RefPtr<IDWriteFontFace> fontFace;
+ nsresult rv = CreateFontFace(getter_AddRefs(fontFace));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint8_t* tableData;
+ uint32_t len;
+ void* tableContext = nullptr;
+ BOOL exists;
+ HRESULT hr = fontFace->TryGetFontTable(tagBE, (const void**)&tableData, &len,
+ &tableContext, &exists);
+ if (FAILED(hr) || !exists) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aBuffer.SetLength(len, fallible)) {
+ memcpy(aBuffer.Elements(), tableData, len);
+ rv = NS_OK;
+ } else {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (tableContext) {
+ fontFace->ReleaseFontTable(&tableContext);
+ }
+
+ return rv;
+}
+
+// Access to font tables packaged in hb_blob_t form
+
+// object attached to the Harfbuzz blob, used to release
+// the table when the blob is destroyed
+class FontTableRec {
+ public:
+ FontTableRec(IDWriteFontFace* aFontFace, void* aContext)
+ : mFontFace(aFontFace), mContext(aContext) {
+ MOZ_COUNT_CTOR(FontTableRec);
+ }
+
+ ~FontTableRec() {
+ MOZ_COUNT_DTOR(FontTableRec);
+ mFontFace->ReleaseFontTable(mContext);
+ }
+
+ private:
+ RefPtr<IDWriteFontFace> mFontFace;
+ void* mContext;
+};
+
+static void DestroyBlobFunc(void* aUserData) {
+ FontTableRec* ftr = static_cast<FontTableRec*>(aUserData);
+ delete ftr;
+}
+
+hb_blob_t* gfxDWriteFontEntry::GetFontTable(uint32_t aTag) {
+ // try to avoid potentially expensive DWrite call if we haven't actually
+ // created the font face yet, by using the gfxFontEntry method that will
+ // use CopyFontTable and then cache the data
+ if (!mFontFace) {
+ return gfxFontEntry::GetFontTable(aTag);
+ }
+
+ const void* data;
+ UINT32 size;
+ void* context;
+ BOOL exists;
+ HRESULT hr = mFontFace->TryGetFontTable(NativeEndian::swapToBigEndian(aTag),
+ &data, &size, &context, &exists);
+ if (SUCCEEDED(hr) && exists) {
+ FontTableRec* ftr = new FontTableRec(mFontFace, context);
+ return hb_blob_create(static_cast<const char*>(data), size,
+ HB_MEMORY_MODE_READONLY, ftr, DestroyBlobFunc);
+ }
+
+ return nullptr;
+}
+
+nsresult gfxDWriteFontEntry::ReadCMAP(FontInfoData* aFontInfoData) {
+ AUTO_PROFILER_LABEL("gfxDWriteFontEntry::ReadCMAP", GRAPHICS);
+
+ // attempt this once, if errors occur leave a blank cmap
+ if (mCharacterMap || mShmemCharacterMap) {
+ return NS_OK;
+ }
+
+ RefPtr<gfxCharacterMap> charmap;
+ nsresult rv;
+
+ uint32_t uvsOffset = 0;
+ if (aFontInfoData &&
+ (charmap = GetCMAPFromFontInfo(aFontInfoData, uvsOffset))) {
+ rv = NS_OK;
+ } else {
+ uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p');
+ charmap = new gfxCharacterMap();
+ AutoTable cmapTable(this, kCMAP);
+
+ if (cmapTable) {
+ uint32_t cmapLen;
+ const uint8_t* cmapData = reinterpret_cast<const uint8_t*>(
+ hb_blob_get_data(cmapTable, &cmapLen));
+ rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, uvsOffset);
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+ mUVSOffset.exchange(uvsOffset);
+
+ bool setCharMap = true;
+ if (NS_SUCCEEDED(rv)) {
+ // Bug 969504: exclude U+25B6 from Segoe UI family, because it's used
+ // by sites to represent a "Play" icon, but the glyph in Segoe UI Light
+ // and Semibold on Windows 7 is too thin. (Ditto for leftward U+25C0.)
+ // Fallback to Segoe UI Symbol is preferred.
+ if (FamilyName().EqualsLiteral("Segoe UI")) {
+ charmap->clear(0x25b6);
+ charmap->clear(0x25c0);
+ }
+ gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
+ fontlist::FontList* sharedFontList = pfl->SharedFontList();
+ if (!IsUserFont() && mShmemFace) {
+ mShmemFace->SetCharacterMap(sharedFontList, charmap); // async
+ if (TrySetShmemCharacterMap()) {
+ setCharMap = false;
+ }
+ } else {
+ charmap = pfl->FindCharMap(charmap);
+ }
+ mHasCmapTable = true;
+ } else {
+ // if error occurred, initialize to null cmap
+ charmap = new gfxCharacterMap();
+ mHasCmapTable = false;
+ }
+ if (setCharMap) {
+ // Temporarily retain charmap, until the shared version is
+ // ready for use.
+ if (mCharacterMap.compareExchange(nullptr, charmap.get())) {
+ charmap.get()->AddRef();
+ }
+ }
+
+ LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %zu hash: %8.8x%s\n",
+ mName.get(), charmap->SizeOfIncludingThis(moz_malloc_size_of),
+ charmap->mHash, mCharacterMap == charmap ? " new" : ""));
+ if (LOG_CMAPDATA_ENABLED()) {
+ char prefix[256];
+ SprintfLiteral(prefix, "(cmapdata) name: %.220s", mName.get());
+ charmap->Dump(prefix, eGfxLog_cmapdata);
+ }
+
+ return rv;
+}
+
+bool gfxDWriteFontEntry::HasVariations() {
+ if (mHasVariationsInitialized) {
+ return mHasVariations;
+ }
+ mHasVariationsInitialized = true;
+ mHasVariations = false;
+
+ if (!gfxPlatform::HasVariationFontSupport()) {
+ return mHasVariations;
+ }
+
+ if (!mFontFace) {
+ // CreateFontFace will initialize the mFontFace field, and also
+ // mFontFace5 if available on the current DWrite version.
+ RefPtr<IDWriteFontFace> fontFace;
+ if (NS_FAILED(CreateFontFace(getter_AddRefs(fontFace)))) {
+ return mHasVariations;
+ }
+ }
+ if (mFontFace5) {
+ mHasVariations = mFontFace5->HasVariations();
+ }
+ return mHasVariations;
+}
+
+void gfxDWriteFontEntry::GetVariationAxes(
+ nsTArray<gfxFontVariationAxis>& aAxes) {
+ if (!HasVariations()) {
+ return;
+ }
+ // HasVariations() will have ensured the mFontFace5 interface is available;
+ // so we can get an IDWriteFontResource and ask it for the axis info.
+ RefPtr<IDWriteFontResource> resource;
+ HRESULT hr = mFontFace5->GetFontResource(getter_AddRefs(resource));
+ if (FAILED(hr) || !resource) {
+ return;
+ }
+
+ uint32_t count = resource->GetFontAxisCount();
+ AutoTArray<DWRITE_FONT_AXIS_VALUE, 4> defaultValues;
+ AutoTArray<DWRITE_FONT_AXIS_RANGE, 4> ranges;
+ defaultValues.SetLength(count);
+ ranges.SetLength(count);
+ resource->GetDefaultFontAxisValues(defaultValues.Elements(), count);
+ resource->GetFontAxisRanges(ranges.Elements(), count);
+ for (uint32_t i = 0; i < count; ++i) {
+ gfxFontVariationAxis axis;
+ MOZ_ASSERT(ranges[i].axisTag == defaultValues[i].axisTag);
+ DWRITE_FONT_AXIS_ATTRIBUTES attrs = resource->GetFontAxisAttributes(i);
+ if (attrs & DWRITE_FONT_AXIS_ATTRIBUTES_HIDDEN) {
+ continue;
+ }
+ if (!(attrs & DWRITE_FONT_AXIS_ATTRIBUTES_VARIABLE)) {
+ continue;
+ }
+ // Extract the 4 chars of the tag from DWrite's packed version,
+ // and reassemble them in the order we use for TRUETYPE_TAG.
+ uint32_t t = defaultValues[i].axisTag;
+ axis.mTag = TRUETYPE_TAG(t & 0xff, (t >> 8) & 0xff, (t >> 16) & 0xff,
+ (t >> 24) & 0xff);
+ // Try to get a human-friendly name (may not be present)
+ RefPtr<IDWriteLocalizedStrings> names;
+ resource->GetAxisNames(i, getter_AddRefs(names));
+ if (names) {
+ GetEnglishOrFirstName(axis.mName, names);
+ }
+ axis.mMinValue = ranges[i].minValue;
+ axis.mMaxValue = ranges[i].maxValue;
+ axis.mDefaultValue = defaultValues[i].value;
+ aAxes.AppendElement(axis);
+ }
+}
+
+void gfxDWriteFontEntry::GetVariationInstances(
+ nsTArray<gfxFontVariationInstance>& aInstances) {
+ gfxFontUtils::GetVariationData(this, nullptr, &aInstances);
+}
+
+gfxFont* gfxDWriteFontEntry::CreateFontInstance(
+ const gfxFontStyle* aFontStyle) {
+ // We use the DirectWrite bold simulation for installed fonts, but NOT for
+ // webfonts; those will use multi-strike synthetic bold instead.
+ bool useBoldSim = false;
+ if (aFontStyle->NeedsSyntheticBold(this)) {
+ switch (StaticPrefs::gfx_font_rendering_directwrite_bold_simulation()) {
+ case 0: // never use the DWrite simulation
+ break;
+ case 1: // use DWrite simulation for installed fonts but not webfonts
+ useBoldSim = !mIsDataUserFont;
+ break;
+ default: // always use DWrite bold simulation
+ useBoldSim = true;
+ break;
+ }
+ }
+ DWRITE_FONT_SIMULATIONS sims =
+ useBoldSim ? DWRITE_FONT_SIMULATIONS_BOLD : DWRITE_FONT_SIMULATIONS_NONE;
+ ThreadSafeWeakPtr<UnscaledFontDWrite>& unscaledFontPtr =
+ useBoldSim ? mUnscaledFontBold : mUnscaledFont;
+ RefPtr<UnscaledFontDWrite> unscaledFont(unscaledFontPtr);
+ if (!unscaledFont) {
+ RefPtr<IDWriteFontFace> fontFace;
+ nsresult rv =
+ CreateFontFace(getter_AddRefs(fontFace), nullptr, sims, nullptr);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+ // Only pass in the underlying IDWriteFont if the unscaled font doesn't
+ // reflect a data font. This signals whether or not we can safely query
+ // a descriptor to represent the font for various transport use-cases.
+ unscaledFont =
+ new UnscaledFontDWrite(fontFace, !mIsDataUserFont ? mFont : nullptr);
+ unscaledFontPtr = unscaledFont;
+ }
+ RefPtr<IDWriteFontFace> fontFace;
+ if (HasVariations()) {
+ // Get the variation settings needed to instantiate the fontEntry
+ // for a particular fontStyle.
+ AutoTArray<gfxFontVariation, 4> vars;
+ GetVariationsForStyle(vars, *aFontStyle);
+
+ if (!vars.IsEmpty()) {
+ nsresult rv =
+ CreateFontFace(getter_AddRefs(fontFace), aFontStyle, sims, &vars);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+ }
+ }
+ return new gfxDWriteFont(unscaledFont, this, aFontStyle, fontFace);
+}
+
+nsresult gfxDWriteFontEntry::CreateFontFace(
+ IDWriteFontFace** aFontFace, const gfxFontStyle* aFontStyle,
+ DWRITE_FONT_SIMULATIONS aSimulations,
+ const nsTArray<gfxFontVariation>* aVariations) {
+ // Convert an OpenType font tag from our uint32_t representation
+ // (as constructed by TRUETYPE_TAG(...)) to the order DWrite wants.
+ auto makeDWriteAxisTag = [](uint32_t aTag) {
+ return DWRITE_MAKE_FONT_AXIS_TAG((aTag >> 24) & 0xff, (aTag >> 16) & 0xff,
+ (aTag >> 8) & 0xff, aTag & 0xff);
+ };
+
+ MOZ_SEH_TRY {
+ // initialize mFontFace if this hasn't been done before
+ if (!mFontFace) {
+ HRESULT hr;
+ if (mFont) {
+ hr = mFont->CreateFontFace(getter_AddRefs(mFontFace));
+ } else if (mFontFile) {
+ IDWriteFontFile* fontFile = mFontFile.get();
+ hr = Factory::GetDWriteFactory()->CreateFontFace(
+ mFaceType, 1, &fontFile, 0, DWRITE_FONT_SIMULATIONS_NONE,
+ getter_AddRefs(mFontFace));
+ } else {
+ MOZ_ASSERT_UNREACHABLE("invalid font entry");
+ return NS_ERROR_FAILURE;
+ }
+ if (FAILED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+ // Also get the IDWriteFontFace5 interface if we're running on a
+ // sufficiently new DWrite version where it is available.
+ if (mFontFace) {
+ mFontFace->QueryInterface(__uuidof(IDWriteFontFace5),
+ (void**)getter_AddRefs(mFontFace5));
+ if (!mVariationSettings.IsEmpty()) {
+ // If the font entry has variations specified, mFontFace5 will
+ // be a distinct face that has the variations applied.
+ RefPtr<IDWriteFontResource> resource;
+ HRESULT hr = mFontFace5->GetFontResource(getter_AddRefs(resource));
+ if (SUCCEEDED(hr) && resource) {
+ AutoTArray<DWRITE_FONT_AXIS_VALUE, 4> fontAxisValues;
+ for (const auto& v : mVariationSettings) {
+ DWRITE_FONT_AXIS_VALUE axisValue = {makeDWriteAxisTag(v.mTag),
+ v.mValue};
+ fontAxisValues.AppendElement(axisValue);
+ }
+ resource->CreateFontFace(
+ mFontFace->GetSimulations(), fontAxisValues.Elements(),
+ fontAxisValues.Length(), getter_AddRefs(mFontFace5));
+ }
+ }
+ }
+ }
+
+ // Do we need to modify DWrite simulations from what mFontFace has?
+ bool needSimulations =
+ (aSimulations & DWRITE_FONT_SIMULATIONS_BOLD) &&
+ !(mFontFace->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD);
+
+ // If the IDWriteFontFace5 interface is available, we can try using
+ // IDWriteFontResource to create a new modified face.
+ if (mFontFace5 && (HasVariations() || needSimulations)) {
+ RefPtr<IDWriteFontResource> resource;
+ HRESULT hr = mFontFace5->GetFontResource(getter_AddRefs(resource));
+ if (SUCCEEDED(hr) && resource) {
+ AutoTArray<DWRITE_FONT_AXIS_VALUE, 4> fontAxisValues;
+
+ // Copy variation settings to DWrite's type.
+ if (aVariations) {
+ for (const auto& v : *aVariations) {
+ DWRITE_FONT_AXIS_VALUE axisValue = {makeDWriteAxisTag(v.mTag),
+ v.mValue};
+ fontAxisValues.AppendElement(axisValue);
+ }
+ }
+
+ IDWriteFontFace5* ff5;
+ resource->CreateFontFace(aSimulations, fontAxisValues.Elements(),
+ fontAxisValues.Length(), &ff5);
+ if (ff5) {
+ *aFontFace = ff5;
+ return NS_OK;
+ }
+ }
+ }
+
+ // Do we need to add DWrite simulations to the face?
+ if (needSimulations) {
+ // if so, we need to return not mFontFace itself but a version that
+ // has the Bold simulation - unfortunately, old DWrite doesn't provide
+ // a simple API for this
+ UINT32 numberOfFiles = 0;
+ if (FAILED(mFontFace->GetFiles(&numberOfFiles, nullptr))) {
+ return NS_ERROR_FAILURE;
+ }
+ AutoTArray<IDWriteFontFile*, 1> files;
+ files.AppendElements(numberOfFiles);
+ if (FAILED(mFontFace->GetFiles(&numberOfFiles, files.Elements()))) {
+ return NS_ERROR_FAILURE;
+ }
+ HRESULT hr = Factory::GetDWriteFactory()->CreateFontFace(
+ mFontFace->GetType(), numberOfFiles, files.Elements(),
+ mFontFace->GetIndex(), aSimulations, aFontFace);
+ for (UINT32 i = 0; i < numberOfFiles; ++i) {
+ files[i]->Release();
+ }
+ return FAILED(hr) ? NS_ERROR_FAILURE : NS_OK;
+ }
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ gfxCriticalNote << "Exception occurred creating font face for "
+ << mName.get();
+ }
+
+ // no simulation: we can just add a reference to mFontFace5 (if present)
+ // or mFontFace (otherwise) and return that
+ if (mFontFace5) {
+ *aFontFace = mFontFace5;
+ } else {
+ *aFontFace = mFontFace;
+ }
+ (*aFontFace)->AddRef();
+ return NS_OK;
+}
+
+bool gfxDWriteFontEntry::InitLogFont(IDWriteFont* aFont, LOGFONTW* aLogFont) {
+ HRESULT hr;
+
+ BOOL isInSystemCollection;
+ IDWriteGdiInterop* gdi =
+ gfxDWriteFontList::PlatformFontList()->GetGDIInterop();
+ hr = gdi->ConvertFontToLOGFONT(aFont, aLogFont, &isInSystemCollection);
+ // If the font is not in the system collection, GDI will be unable to
+ // select it and load its tables, so we return false here to indicate
+ // failure, and let CopyFontTable fall back to DWrite native methods.
+ return (SUCCEEDED(hr) && isInSystemCollection);
+}
+
+bool gfxDWriteFontEntry::IsCJKFont() {
+ if (mIsCJK != UNINITIALIZED_VALUE) {
+ return mIsCJK;
+ }
+
+ mIsCJK = false;
+
+ const uint32_t kOS2Tag = TRUETYPE_TAG('O', 'S', '/', '2');
+ gfxFontUtils::AutoHBBlob blob(GetFontTable(kOS2Tag));
+ if (!blob) {
+ return mIsCJK;
+ }
+
+ uint32_t len;
+ const OS2Table* os2 =
+ reinterpret_cast<const OS2Table*>(hb_blob_get_data(blob, &len));
+ // ulCodePageRange bit definitions for the CJK codepages,
+ // from http://www.microsoft.com/typography/otspec/os2.htm#cpr
+ const uint32_t CJK_CODEPAGE_BITS =
+ (1 << 17) | // codepage 932 - JIS/Japan
+ (1 << 18) | // codepage 936 - Chinese (simplified)
+ (1 << 19) | // codepage 949 - Korean Wansung
+ (1 << 20) | // codepage 950 - Chinese (traditional)
+ (1 << 21); // codepage 1361 - Korean Johab
+ if (len >= offsetof(OS2Table, sxHeight)) {
+ if ((uint32_t(os2->codePageRange1) & CJK_CODEPAGE_BITS) != 0) {
+ mIsCJK = true;
+ }
+ }
+
+ return mIsCJK;
+}
+
+void gfxDWriteFontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ gfxFontEntry::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+ // TODO:
+ // This doesn't currently account for the |mFont| and |mFontFile| members
+}
+
+void gfxDWriteFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ aSizes->mFontListSize += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// gfxDWriteFontList
+
+gfxDWriteFontList::gfxDWriteFontList() : mForceGDIClassicMaxFontSize(0.0) {
+ CheckFamilyList(kBaseFonts);
+ CheckFamilyList(kLangPackFonts);
+}
+
+// bug 602792 - CJK systems default to large CJK fonts which cause excessive
+// I/O strain during cold startup due to dwrite caching bugs. Default to
+// Arial to avoid this.
+
+FontFamily gfxDWriteFontList::GetDefaultFontForPlatform(
+ nsPresContext* aPresContext, const gfxFontStyle* aStyle,
+ nsAtom* aLanguage) {
+ // try Arial first
+ FontFamily ff;
+ ff = FindFamily(aPresContext, "Arial"_ns);
+ if (!ff.IsNull()) {
+ return ff;
+ }
+
+ // otherwise, use local default
+ NONCLIENTMETRICSW ncm;
+ ncm.cbSize = sizeof(ncm);
+ BOOL status =
+ ::SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0);
+
+ if (status) {
+ ff = FindFamily(aPresContext,
+ NS_ConvertUTF16toUTF8(ncm.lfMessageFont.lfFaceName));
+ }
+
+ return ff;
+}
+
+gfxFontEntry* gfxDWriteFontList::LookupLocalFont(
+ nsPresContext* aPresContext, const nsACString& aFontName,
+ WeightRange aWeightForEntry, StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry) {
+ AutoLock lock(mLock);
+
+ if (SharedFontList()) {
+ return LookupInSharedFaceNameList(aPresContext, aFontName, aWeightForEntry,
+ aStretchForEntry, aStyleForEntry);
+ }
+
+ gfxFontEntry* lookup;
+
+ lookup = LookupInFaceNameLists(aFontName);
+ if (!lookup) {
+ return nullptr;
+ }
+
+ gfxDWriteFontEntry* dwriteLookup = static_cast<gfxDWriteFontEntry*>(lookup);
+ gfxDWriteFontEntry* fe =
+ new gfxDWriteFontEntry(lookup->Name(), dwriteLookup->mFont,
+ aWeightForEntry, aStretchForEntry, aStyleForEntry);
+ fe->SetForceGDIClassic(dwriteLookup->GetForceGDIClassic());
+ return fe;
+}
+
+gfxFontEntry* gfxDWriteFontList::MakePlatformFont(
+ const nsACString& aFontName, WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry, SlantStyleRange aStyleForEntry,
+ const uint8_t* aFontData, uint32_t aLength) {
+ RefPtr<IDWriteFontFileStream> fontFileStream;
+ RefPtr<IDWriteFontFile> fontFile;
+ HRESULT hr = gfxDWriteFontFileLoader::CreateCustomFontFile(
+ aFontData, aLength, getter_AddRefs(fontFile),
+ getter_AddRefs(fontFileStream));
+ free((void*)aFontData);
+ NS_ASSERTION(SUCCEEDED(hr), "Failed to create font file reference");
+ if (FAILED(hr)) {
+ return nullptr;
+ }
+
+ nsAutoString uniqueName;
+ nsresult rv = gfxFontUtils::MakeUniqueUserFontName(uniqueName);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to make unique user font name");
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ BOOL isSupported;
+ DWRITE_FONT_FILE_TYPE fileType;
+ UINT32 numFaces;
+
+ auto entry = MakeUnique<gfxDWriteFontEntry>(
+ NS_ConvertUTF16toUTF8(uniqueName), fontFile, fontFileStream,
+ aWeightForEntry, aStretchForEntry, aStyleForEntry);
+
+ hr = fontFile->Analyze(&isSupported, &fileType, &entry->mFaceType, &numFaces);
+ NS_ASSERTION(SUCCEEDED(hr), "IDWriteFontFile::Analyze failed");
+ if (FAILED(hr)) {
+ return nullptr;
+ }
+ NS_ASSERTION(isSupported, "Unsupported font file");
+ if (!isSupported) {
+ return nullptr;
+ }
+ NS_ASSERTION(numFaces == 1, "Font file does not contain exactly 1 face");
+ if (numFaces != 1) {
+ // We don't know how to deal with 0 faces either.
+ return nullptr;
+ }
+
+ return entry.release();
+}
+
+bool gfxDWriteFontList::UseGDIFontTableAccess() const {
+ // Using GDI font table access for DWrite is controlled by a pref, but also we
+ // must be able to make win32k calls.
+ return mGDIFontTableAccess && !IsWin32kLockedDown();
+}
+
+static void GetPostScriptNameFromNameTable(IDWriteFontFace* aFace,
+ nsCString& aName) {
+ const auto kNAME =
+ NativeEndian::swapToBigEndian(TRUETYPE_TAG('n', 'a', 'm', 'e'));
+ const char* data;
+ UINT32 size;
+ void* context;
+ BOOL exists;
+ if (SUCCEEDED(aFace->TryGetFontTable(kNAME, (const void**)&data, &size,
+ &context, &exists)) &&
+ exists) {
+ if (NS_FAILED(gfxFontUtils::ReadCanonicalName(
+ data, size, gfxFontUtils::NAME_ID_POSTSCRIPT, aName))) {
+ aName.Truncate(0);
+ }
+ aFace->ReleaseFontTable(context);
+ }
+}
+
+gfxFontEntry* gfxDWriteFontList::CreateFontEntry(
+ fontlist::Face* aFace, const fontlist::Family* aFamily) {
+ IDWriteFontCollection* collection =
+#ifdef MOZ_BUNDLED_FONTS
+ aFamily->IsBundled() ? mBundledFonts : mSystemFonts;
+#else
+ mSystemFonts;
+#endif
+ RefPtr<IDWriteFontFamily> family;
+ bool foundExpectedFamily = false;
+ const nsCString& familyName =
+ aFamily->DisplayName().AsString(SharedFontList());
+
+ // The DirectWrite calls here might throw exceptions, e.g. in case of disk
+ // errors when trying to read the font file.
+ MOZ_SEH_TRY {
+ if (aFamily->Index() < collection->GetFontFamilyCount()) {
+ HRESULT hr =
+ collection->GetFontFamily(aFamily->Index(), getter_AddRefs(family));
+ // Check that the family name is what we expected; if not, fall back to
+ // search by name. It's sad we have to do this, but it is possible for
+ // Windows to have given different versions of the system font collection
+ // to the parent and child processes.
+ if (SUCCEEDED(hr) && family) {
+ RefPtr<IDWriteLocalizedStrings> names;
+ hr = family->GetFamilyNames(getter_AddRefs(names));
+ if (SUCCEEDED(hr) && names) {
+ nsAutoCString name;
+ if (GetEnglishOrFirstName(name, names)) {
+ foundExpectedFamily = name.Equals(familyName);
+ }
+ }
+ }
+ }
+ if (!foundExpectedFamily) {
+ // Try to get family by name instead of index (to deal with the case of
+ // collection mismatch).
+ UINT32 index;
+ BOOL exists;
+ NS_ConvertUTF8toUTF16 name16(familyName);
+ HRESULT hr = collection->FindFamilyName(
+ reinterpret_cast<const WCHAR*>(name16.BeginReading()), &index,
+ &exists);
+ if (FAILED(hr) || !exists || index == UINT_MAX ||
+ FAILED(collection->GetFontFamily(index, getter_AddRefs(family))) ||
+ !family) {
+ return nullptr;
+ }
+ }
+
+ // Retrieve the required face by index within the family.
+ RefPtr<IDWriteFont> font;
+ if (FAILED(family->GetFont(aFace->mIndex, getter_AddRefs(font))) || !font) {
+ return nullptr;
+ }
+
+ // Retrieve the psName from the font, so we can check we've found the
+ // expected face.
+ nsAutoCString psName;
+ if (FAILED(GetDirectWriteFaceName(font, PSNAME_ID, psName))) {
+ RefPtr<IDWriteFontFace> dwFontFace;
+ if (SUCCEEDED(font->CreateFontFace(getter_AddRefs(dwFontFace)))) {
+ GetPostScriptNameFromNameTable(dwFontFace, psName);
+ }
+ }
+
+ // If it doesn't match, DirectWrite must have shuffled the order of faces
+ // returned for the family; search by name as a fallback.
+ nsCString faceName = aFace->mDescriptor.AsString(SharedFontList());
+ if (psName != faceName) {
+ gfxWarning() << "Face name mismatch for index " << aFace->mIndex
+ << " in family " << familyName.get() << ": expected "
+ << faceName.get() << ", found " << psName.get();
+ for (uint32_t i = 0; i < family->GetFontCount(); ++i) {
+ if (i == aFace->mIndex) {
+ continue; // this was the face we already tried
+ }
+ if (FAILED(family->GetFont(i, getter_AddRefs(font))) || !font) {
+ return nullptr; // this font family is broken!
+ }
+ if (FAILED(GetDirectWriteFaceName(font, PSNAME_ID, psName))) {
+ RefPtr<IDWriteFontFace> dwFontFace;
+ if (SUCCEEDED(font->CreateFontFace(getter_AddRefs(dwFontFace)))) {
+ GetPostScriptNameFromNameTable(dwFontFace, psName);
+ }
+ }
+ if (psName == faceName) {
+ break;
+ }
+ }
+ }
+ if (psName != faceName) {
+ return nullptr;
+ }
+
+ auto fe = new gfxDWriteFontEntry(faceName, font, !aFamily->IsBundled());
+ fe->InitializeFrom(aFace, aFamily);
+ fe->mForceGDIClassic = aFamily->IsForceClassic();
+ fe->mMayUseGDIAccess = aFamily->IsSimple();
+ return fe;
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ gfxCriticalNote << "Exception occurred while creating font entry for "
+ << familyName.get();
+ }
+ return nullptr;
+}
+
+FontVisibility gfxDWriteFontList::GetVisibilityForFamily(
+ const nsACString& aName) const {
+ if (FamilyInList(aName, kBaseFonts)) {
+ return FontVisibility::Base;
+ }
+ if (FamilyInList(aName, kLangPackFonts)) {
+ return FontVisibility::LangPack;
+ }
+ return FontVisibility::User;
+}
+
+void gfxDWriteFontList::AppendFamiliesFromCollection(
+ IDWriteFontCollection* aCollection,
+ nsTArray<fontlist::Family::InitData>& aFamilies,
+ const nsTArray<nsCString>* aForceClassicFams) {
+ auto allFacesUltraBold = [](IDWriteFontFamily* aFamily) -> bool {
+ for (UINT32 i = 0; i < aFamily->GetFontCount(); i++) {
+ RefPtr<IDWriteFont> font;
+ HRESULT hr = aFamily->GetFont(i, getter_AddRefs(font));
+ if (FAILED(hr)) {
+ NS_WARNING("Failed to get existing font from family.");
+ continue;
+ }
+ nsAutoCString faceName;
+ hr = GetDirectWriteFontName(font, faceName);
+ if (FAILED(hr)) {
+ continue;
+ }
+ if (faceName.Find("Ultra Bold"_ns) == kNotFound) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ nsAutoCString locale;
+ OSPreferences::GetInstance()->GetSystemLocale(locale);
+ ToLowerCase(locale);
+ NS_ConvertUTF8toUTF16 loc16(locale);
+
+ for (unsigned i = 0; i < aCollection->GetFontFamilyCount(); ++i) {
+ RefPtr<IDWriteFontFamily> family;
+ aCollection->GetFontFamily(i, getter_AddRefs(family));
+ RefPtr<IDWriteLocalizedStrings> localizedNames;
+ HRESULT hr = family->GetFamilyNames(getter_AddRefs(localizedNames));
+ if (FAILED(hr)) {
+ gfxWarning() << "Failed to get names for font-family " << i;
+ continue;
+ }
+
+ auto addFamily = [&](const nsACString& name, bool altLocale = false) {
+ nsAutoCString key;
+ key = name;
+ BuildKeyNameFromFontName(key);
+ bool bad = mBadUnderlineFamilyNames.ContainsSorted(key);
+ bool classic =
+ aForceClassicFams && aForceClassicFams->ContainsSorted(key);
+ FontVisibility visibility;
+ // Special case: hide the "Gill Sans" family that contains only UltraBold
+ // faces, as this leads to breakage on sites with CSS that targeted the
+ // Gill Sans family as found on macOS. (Bug 551313, bug 1632738)
+ // TODO (jfkthame): the ultrabold faces from Gill Sans should be treated
+ // as belonging to the Gill Sans MT family.
+ if (key.EqualsLiteral("gill sans") && allFacesUltraBold(family)) {
+ visibility = FontVisibility::Hidden;
+ } else {
+ visibility = aCollection == mSystemFonts ? GetVisibilityForFamily(name)
+ : FontVisibility::Base;
+ }
+ aFamilies.AppendElement(fontlist::Family::InitData(
+ key, name, i, visibility, aCollection != mSystemFonts, bad, classic,
+ altLocale));
+ };
+
+ unsigned count = localizedNames->GetCount();
+ if (count == 1) {
+ // This is the common case: the great majority of fonts only provide an
+ // en-US family name.
+ nsAutoCString name;
+ if (!GetNameAsUtf8(name, localizedNames, 0)) {
+ gfxWarning() << "GetNameAsUtf8 failed for index 0 in font-family " << i;
+ continue;
+ }
+ addFamily(name);
+ } else {
+ AutoTArray<nsCString, 4> names;
+ int sysLocIndex = -1;
+ for (unsigned index = 0; index < count; ++index) {
+ nsAutoCString name;
+ if (!GetNameAsUtf8(name, localizedNames, index)) {
+ gfxWarning() << "GetNameAsUtf8 failed for index " << index
+ << " in font-family " << i;
+ continue;
+ }
+ if (!names.Contains(name)) {
+ if (sysLocIndex == -1) {
+ WCHAR buf[32];
+ if (SUCCEEDED(localizedNames->GetLocaleName(index, buf, 32))) {
+ if (loc16.Equals(buf)) {
+ sysLocIndex = names.Length();
+ }
+ }
+ }
+ names.AppendElement(name);
+ }
+ }
+ // If we didn't find a name that matched the system locale, use the
+ // first (which is most often en-US).
+ if (sysLocIndex == -1) {
+ sysLocIndex = 0;
+ }
+ // Hack to work around EPSON fonts with bad names (tagged as MacRoman
+ // but actually contain MacJapanese data): if we've chosen the first
+ // name, *and* it is non-ASCII, *and* there is an alternative present,
+ // use the next option instead as being more likely to be valid.
+ if (sysLocIndex == 0 && names.Length() > 1 && !IsAscii(names[0])) {
+ sysLocIndex = 1;
+ }
+ for (unsigned index = 0; index < names.Length(); ++index) {
+ addFamily(names[index], index != static_cast<unsigned>(sysLocIndex));
+ }
+ }
+ }
+}
+
+void gfxDWriteFontList::GetFacesInitDataForFamily(
+ const fontlist::Family* aFamily, nsTArray<fontlist::Face::InitData>& aFaces,
+ bool aLoadCmaps) const {
+ IDWriteFontCollection* collection =
+#ifdef MOZ_BUNDLED_FONTS
+ aFamily->IsBundled() ? mBundledFonts : mSystemFonts;
+#else
+ mSystemFonts;
+#endif
+ if (!collection) {
+ return;
+ }
+ RefPtr<IDWriteFontFamily> family;
+ collection->GetFontFamily(aFamily->Index(), getter_AddRefs(family));
+ for (unsigned i = 0; i < family->GetFontCount(); ++i) {
+ RefPtr<IDWriteFont> dwFont;
+ family->GetFont(i, getter_AddRefs(dwFont));
+ if (!dwFont || dwFont->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE) {
+ continue;
+ }
+ DWRITE_FONT_STYLE dwstyle = dwFont->GetStyle();
+ // Ignore italic styles of Meiryo because "Meiryo (Bold) Italic" has
+ // non-italic style glyphs as Japanese characters. However, using it
+ // causes serious problem if web pages wants some elements to be
+ // different style from others only with font-style. For example,
+ // <em> and <i> should be rendered as italic in the default style.
+ if (dwstyle != DWRITE_FONT_STYLE_NORMAL &&
+ aFamily->Key().AsString(SharedFontList()).EqualsLiteral("meiryo")) {
+ continue;
+ }
+ WeightRange weight(FontWeight::FromInt(dwFont->GetWeight()));
+ StretchRange stretch(FontStretchFromDWriteStretch(dwFont->GetStretch()));
+ // Try to read PSName as a unique face identifier; if this fails we'll get
+ // it directly from the 'name' table, and if that also fails we consider
+ // the face unusable.
+ MOZ_SEH_TRY {
+ nsAutoCString name;
+ RefPtr<gfxCharacterMap> charmap;
+ if (FAILED(GetDirectWriteFaceName(dwFont, PSNAME_ID, name)) ||
+ aLoadCmaps) {
+ RefPtr<IDWriteFontFace> dwFontFace;
+ if (SUCCEEDED(dwFont->CreateFontFace(getter_AddRefs(dwFontFace)))) {
+ if (name.IsEmpty()) {
+ GetPostScriptNameFromNameTable(dwFontFace, name);
+ }
+ const auto kCMAP =
+ NativeEndian::swapToBigEndian(TRUETYPE_TAG('c', 'm', 'a', 'p'));
+ const char* data;
+ UINT32 size;
+ void* context;
+ BOOL exists;
+ if (aLoadCmaps) {
+ if (SUCCEEDED(dwFontFace->TryGetFontTable(
+ kCMAP, (const void**)&data, &size, &context, &exists)) &&
+ exists) {
+ charmap = new gfxCharacterMap();
+ uint32_t offset;
+ gfxFontUtils::ReadCMAP((const uint8_t*)data, size, *charmap,
+ offset);
+ dwFontFace->ReleaseFontTable(context);
+ }
+ }
+ }
+ }
+ if (name.IsEmpty()) {
+ gfxWarning() << "Failed to get name for face " << i << " in family "
+ << aFamily->Key().AsString(SharedFontList()).get();
+ continue;
+ }
+ SlantStyleRange slant(
+ dwstyle == DWRITE_FONT_STYLE_NORMAL ? FontSlantStyle::NORMAL
+ : dwstyle == DWRITE_FONT_STYLE_ITALIC ? FontSlantStyle::ITALIC
+ : FontSlantStyle::OBLIQUE);
+ aFaces.AppendElement(fontlist::Face::InitData{
+ name, uint16_t(i), false, weight, stretch, slant, charmap});
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ // Exception (e.g. disk i/o error) occurred when DirectWrite tried to use
+ // the font resource. We'll just skip the bad face.
+ gfxCriticalNote << "Exception occurred reading faces for "
+ << aFamily->Key().AsString(SharedFontList()).get();
+ }
+ }
+}
+
+bool gfxDWriteFontList::ReadFaceNames(const fontlist::Family* aFamily,
+ const fontlist::Face* aFace,
+ nsCString& aPSName,
+ nsCString& aFullName) {
+ IDWriteFontCollection* collection =
+#ifdef MOZ_BUNDLED_FONTS
+ aFamily->IsBundled() ? mBundledFonts : mSystemFonts;
+#else
+ mSystemFonts;
+#endif
+ RefPtr<IDWriteFontFamily> family;
+ if (FAILED(collection->GetFontFamily(aFamily->Index(),
+ getter_AddRefs(family)))) {
+ MOZ_ASSERT_UNREACHABLE("failed to get font-family");
+ return false;
+ }
+ RefPtr<IDWriteFont> dwFont;
+ if (FAILED(family->GetFont(aFace->mIndex, getter_AddRefs(dwFont)))) {
+ MOZ_ASSERT_UNREACHABLE("failed to get font from family");
+ return false;
+ }
+ HRESULT ps = GetDirectWriteFaceName(dwFont, PSNAME_ID, aPSName);
+ HRESULT full = GetDirectWriteFaceName(dwFont, FULLNAME_ID, aFullName);
+ if (FAILED(ps) || FAILED(full) || aPSName.IsEmpty() || aFullName.IsEmpty()) {
+ // We'll return true if either name was found, false if both fail.
+ // Note that on older Win7 systems, GetDirectWriteFaceName may "succeed"
+ // but return an empty string, so we have to check for non-empty strings
+ // to be sure we actually got a usable name.
+
+ // Initialize result to true if either name was already found.
+ bool result = (SUCCEEDED(ps) && !aPSName.IsEmpty()) ||
+ (SUCCEEDED(full) && !aFullName.IsEmpty());
+ RefPtr<IDWriteFontFace> dwFontFace;
+ if (FAILED(dwFont->CreateFontFace(getter_AddRefs(dwFontFace)))) {
+ NS_WARNING("failed to create font face");
+ return result;
+ }
+ void* context;
+ const char* data;
+ UINT32 size;
+ BOOL exists;
+ if (FAILED(dwFontFace->TryGetFontTable(
+ NativeEndian::swapToBigEndian(TRUETYPE_TAG('n', 'a', 'm', 'e')),
+ (const void**)&data, &size, &context, &exists)) ||
+ !exists) {
+ NS_WARNING("failed to get name table");
+ return result;
+ }
+ MOZ_SEH_TRY {
+ // Try to read the name table entries, and ensure result is true if either
+ // one succeeds.
+ if (FAILED(ps) || aPSName.IsEmpty()) {
+ if (NS_SUCCEEDED(gfxFontUtils::ReadCanonicalName(
+ data, size, gfxFontUtils::NAME_ID_POSTSCRIPT, aPSName))) {
+ result = true;
+ } else {
+ NS_WARNING("failed to read psname");
+ }
+ }
+ if (FAILED(full) || aFullName.IsEmpty()) {
+ if (NS_SUCCEEDED(gfxFontUtils::ReadCanonicalName(
+ data, size, gfxFontUtils::NAME_ID_FULL, aFullName))) {
+ result = true;
+ } else {
+ NS_WARNING("failed to read fullname");
+ }
+ }
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ gfxCriticalNote << "Exception occurred reading face names for "
+ << aFamily->Key().AsString(SharedFontList()).get();
+ }
+ if (dwFontFace && context) {
+ dwFontFace->ReleaseFontTable(context);
+ }
+ return result;
+ }
+ return true;
+}
+
+void gfxDWriteFontList::ReadFaceNamesForFamily(
+ fontlist::Family* aFamily, bool aNeedFullnamePostscriptNames) {
+ if (!aFamily->IsInitialized()) {
+ if (!InitializeFamily(aFamily)) {
+ return;
+ }
+ }
+ IDWriteFontCollection* collection =
+#ifdef MOZ_BUNDLED_FONTS
+ aFamily->IsBundled() ? mBundledFonts : mSystemFonts;
+#else
+ mSystemFonts;
+#endif
+ RefPtr<IDWriteFontFamily> family;
+ if (FAILED(collection->GetFontFamily(aFamily->Index(),
+ getter_AddRefs(family)))) {
+ return;
+ }
+ fontlist::FontList* list = SharedFontList();
+ const fontlist::Pointer* facePtrs = aFamily->Faces(list);
+ nsAutoCString familyName(aFamily->DisplayName().AsString(list));
+ nsAutoCString key(aFamily->Key().AsString(list));
+
+ MOZ_SEH_TRY {
+ // Read PS-names and fullnames of the faces, and any alternate family names
+ // (either localizations or legacy subfamily names)
+ for (unsigned i = 0; i < aFamily->NumFaces(); ++i) {
+ auto* face = facePtrs[i].ToPtr<fontlist::Face>(list);
+ if (!face) {
+ continue;
+ }
+ RefPtr<IDWriteFont> dwFont;
+ if (FAILED(family->GetFont(face->mIndex, getter_AddRefs(dwFont)))) {
+ continue;
+ }
+ RefPtr<IDWriteFontFace> dwFontFace;
+ if (FAILED(dwFont->CreateFontFace(getter_AddRefs(dwFontFace)))) {
+ continue;
+ }
+
+ const char* data;
+ UINT32 size;
+ void* context;
+ BOOL exists;
+ if (FAILED(dwFontFace->TryGetFontTable(
+ NativeEndian::swapToBigEndian(TRUETYPE_TAG('n', 'a', 'm', 'e')),
+ (const void**)&data, &size, &context, &exists)) ||
+ !exists) {
+ continue;
+ }
+
+ AutoTArray<nsCString, 4> otherFamilyNames;
+ gfxFontUtils::ReadOtherFamilyNamesForFace(familyName, data, size,
+ otherFamilyNames, false);
+ for (const auto& alias : otherFamilyNames) {
+ nsAutoCString key(alias);
+ ToLowerCase(key);
+ auto aliasData = mAliasTable.GetOrInsertNew(key);
+ aliasData->InitFromFamily(aFamily, familyName);
+ aliasData->mFaces.AppendElement(facePtrs[i]);
+ }
+
+ nsAutoCString psname, fullname;
+ // Bug 1854090: don't load PSname if the family name ends with ".tmp",
+ // as some PDF-related software appears to pollute the font collection
+ // with spurious re-encoded versions of standard fonts like Arial, fails
+ // to alter the PSname, and thus can result in garbled rendering because
+ // the wrong resource may be found via src:local(...).
+ if (!StringEndsWith(key, ".tmp"_ns)) {
+ if (NS_SUCCEEDED(gfxFontUtils::ReadCanonicalName(
+ data, size, gfxFontUtils::NAME_ID_POSTSCRIPT, psname))) {
+ ToLowerCase(psname);
+ mLocalNameTable.InsertOrUpdate(
+ psname, fontlist::LocalFaceRec::InitData(key, i));
+ }
+ }
+ if (NS_SUCCEEDED(gfxFontUtils::ReadCanonicalName(
+ data, size, gfxFontUtils::NAME_ID_FULL, fullname))) {
+ ToLowerCase(fullname);
+ if (fullname != psname) {
+ mLocalNameTable.InsertOrUpdate(
+ fullname, fontlist::LocalFaceRec::InitData(key, i));
+ }
+ }
+
+ dwFontFace->ReleaseFontTable(context);
+ }
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ gfxCriticalNote << "Exception occurred reading names for "
+ << familyName.get();
+ }
+}
+
+enum DWriteInitError {
+ errGDIInterop = 1,
+ errSystemFontCollection = 2,
+ errNoFonts = 3
+};
+
+void gfxDWriteFontList::InitSharedFontListForPlatform() {
+ mGDIFontTableAccess = Preferences::GetBool(
+ "gfx.font_rendering.directwrite.use_gdi_table_loading", false);
+ mForceGDIClassicMaxFontSize = Preferences::GetInt(
+ "gfx.font_rendering.cleartype_params.force_gdi_classic_max_size",
+ mForceGDIClassicMaxFontSize);
+
+ mSubstitutions.Clear();
+ mNonExistingFonts.Clear();
+
+ RefPtr<IDWriteFactory> factory = Factory::GetDWriteFactory();
+ HRESULT hr = factory->GetGdiInterop(getter_AddRefs(mGDIInterop));
+ if (FAILED(hr)) {
+ Telemetry::Accumulate(Telemetry::DWRITEFONT_INIT_PROBLEM,
+ uint32_t(errGDIInterop));
+ mSharedFontList.reset(nullptr);
+ return;
+ }
+
+ mSystemFonts = Factory::GetDWriteSystemFonts(true);
+ NS_ASSERTION(mSystemFonts != nullptr, "GetSystemFontCollection failed!");
+ if (!mSystemFonts) {
+ Telemetry::Accumulate(Telemetry::DWRITEFONT_INIT_PROBLEM,
+ uint32_t(errSystemFontCollection));
+ mSharedFontList.reset(nullptr);
+ return;
+ }
+#ifdef MOZ_BUNDLED_FONTS
+ // We activate bundled fonts if the pref is > 0 (on) or < 0 (auto), only an
+ // explicit value of 0 (off) will disable them.
+ TimeStamp start1 = TimeStamp::Now();
+ if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) {
+ mBundledFonts = CreateBundledFontsCollection(factory);
+ }
+ TimeStamp end1 = TimeStamp::Now();
+#endif
+
+ if (XRE_IsParentProcess()) {
+ nsAutoCString classicFamilies;
+ AutoTArray<nsCString, 16> forceClassicFams;
+ nsresult rv = Preferences::GetCString(
+ "gfx.font_rendering.cleartype_params.force_gdi_classic_for_families",
+ classicFamilies);
+ if (NS_SUCCEEDED(rv)) {
+ for (auto name :
+ nsCCharSeparatedTokenizer(classicFamilies, ',').ToRange()) {
+ BuildKeyNameFromFontName(name);
+ forceClassicFams.AppendElement(name);
+ }
+ forceClassicFams.Sort();
+ }
+ nsTArray<fontlist::Family::InitData> families;
+ AppendFamiliesFromCollection(mSystemFonts, families, &forceClassicFams);
+#ifdef MOZ_BUNDLED_FONTS
+ if (mBundledFonts) {
+ TimeStamp start2 = TimeStamp::Now();
+ AppendFamiliesFromCollection(mBundledFonts, families);
+ TimeStamp end2 = TimeStamp::Now();
+ Telemetry::Accumulate(
+ Telemetry::FONTLIST_BUNDLEDFONTS_ACTIVATE,
+ (end1 - start1).ToMilliseconds() + (end2 - start2).ToMilliseconds());
+ }
+#endif
+ SharedFontList()->SetFamilyNames(families);
+ GetPrefsAndStartLoader();
+ }
+
+ if (!SharedFontList()->Initialized()) {
+ return;
+ }
+
+ GetDirectWriteSubstitutes();
+ GetFontSubstitutes();
+}
+
+nsresult gfxDWriteFontList::InitFontListForPlatform() {
+ LARGE_INTEGER frequency; // ticks per second
+ LARGE_INTEGER t1, t2, t3, t4, t5; // ticks
+ double elapsedTime, upTime;
+ char nowTime[256], nowDate[256];
+
+ if (LOG_FONTINIT_ENABLED()) {
+ GetTimeFormatA(LOCALE_INVARIANT, TIME_FORCE24HOURFORMAT, nullptr, nullptr,
+ nowTime, 256);
+ GetDateFormatA(LOCALE_INVARIANT, 0, nullptr, nullptr, nowDate, 256);
+ upTime = (double)GetTickCount();
+ }
+ QueryPerformanceFrequency(&frequency);
+ QueryPerformanceCounter(&t1); // start
+
+ HRESULT hr;
+ mGDIFontTableAccess = Preferences::GetBool(
+ "gfx.font_rendering.directwrite.use_gdi_table_loading", false);
+
+ mFontSubstitutes.Clear();
+ mNonExistingFonts.Clear();
+
+ RefPtr<IDWriteFactory> factory = Factory::GetDWriteFactory();
+
+ hr = factory->GetGdiInterop(getter_AddRefs(mGDIInterop));
+ if (FAILED(hr)) {
+ Telemetry::Accumulate(Telemetry::DWRITEFONT_INIT_PROBLEM,
+ uint32_t(errGDIInterop));
+ return NS_ERROR_FAILURE;
+ }
+
+ QueryPerformanceCounter(&t2); // base-class/interop initialization
+
+ mSystemFonts = Factory::GetDWriteSystemFonts(true);
+ NS_ASSERTION(mSystemFonts != nullptr, "GetSystemFontCollection failed!");
+
+ if (!mSystemFonts) {
+ Telemetry::Accumulate(Telemetry::DWRITEFONT_INIT_PROBLEM,
+ uint32_t(errSystemFontCollection));
+ return NS_ERROR_FAILURE;
+ }
+
+#ifdef MOZ_BUNDLED_FONTS
+ // Get bundled fonts before the system collection, so that in the case of
+ // duplicate names, we have recorded the family as bundled (and therefore
+ // available regardless of visibility settings).
+ // We activate bundled fonts if the pref is > 0 (on) or < 0 (auto), only an
+ // explicit value of 0 (off) will disable them.
+ if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) {
+ TimeStamp start = TimeStamp::Now();
+ mBundledFonts = CreateBundledFontsCollection(factory);
+ if (mBundledFonts) {
+ GetFontsFromCollection(mBundledFonts);
+ }
+ TimeStamp end = TimeStamp::Now();
+ Telemetry::Accumulate(Telemetry::FONTLIST_BUNDLEDFONTS_ACTIVATE,
+ (end - start).ToMilliseconds());
+ }
+#endif
+ const uint32_t kBundledCount = mFontFamilies.Count();
+
+ QueryPerformanceCounter(&t3); // system font collection
+
+ GetFontsFromCollection(mSystemFonts);
+
+ // if no fonts found, something is out of whack, bail and use GDI backend
+ NS_ASSERTION(mFontFamilies.Count() > kBundledCount,
+ "no fonts found in the system fontlist -- holy crap batman!");
+ if (mFontFamilies.Count() == kBundledCount) {
+ Telemetry::Accumulate(Telemetry::DWRITEFONT_INIT_PROBLEM,
+ uint32_t(errNoFonts));
+ return NS_ERROR_FAILURE;
+ }
+
+ QueryPerformanceCounter(&t4); // iterate over system fonts
+
+ mOtherFamilyNamesInitialized = true;
+ GetFontSubstitutes();
+
+ // bug 642093 - DirectWrite does not support old bitmap (.fon)
+ // font files, but a few of these such as "Courier" and "MS Sans Serif"
+ // are frequently specified in shoddy CSS, without appropriate fallbacks.
+ // By mapping these to TrueType equivalents, we provide better consistency
+ // with both pre-DW systems and with IE9, which appears to do the same.
+ GetDirectWriteSubstitutes();
+
+ // bug 551313 - DirectWrite creates a Gill Sans family out of
+ // poorly named members of the Gill Sans MT family containing
+ // only Ultra Bold weights. This causes big problems for pages
+ // using Gill Sans which is usually only available on OSX
+
+ nsAutoCString nameGillSans("Gill Sans");
+ nsAutoCString nameGillSansMT("Gill Sans MT");
+ BuildKeyNameFromFontName(nameGillSans);
+ BuildKeyNameFromFontName(nameGillSansMT);
+
+ gfxFontFamily* gillSansFamily = mFontFamilies.GetWeak(nameGillSans);
+ gfxFontFamily* gillSansMTFamily = mFontFamilies.GetWeak(nameGillSansMT);
+
+ if (gillSansFamily && gillSansMTFamily) {
+ gillSansFamily->FindStyleVariations();
+
+ gillSansFamily->ReadLock();
+ const auto& faces = gillSansFamily->GetFontList();
+
+ bool allUltraBold = true;
+ for (const auto& face : faces) {
+ // does the face have 'Ultra Bold' in the name?
+ if (face->Name().Find("Ultra Bold"_ns) == -1) {
+ allUltraBold = false;
+ break;
+ }
+ }
+
+ // if all the Gill Sans faces are Ultra Bold ==> move faces
+ // for Gill Sans into Gill Sans MT family
+ if (allUltraBold) {
+ // add faces to Gill Sans MT
+ for (const auto& face : faces) {
+ // change the entry's family name to match its adoptive family
+ face->mFamilyName = gillSansMTFamily->Name();
+ gillSansMTFamily->AddFontEntry(face);
+
+ if (LOG_FONTLIST_ENABLED()) {
+ nsAutoCString weightString;
+ face->Weight().ToString(weightString);
+ LOG_FONTLIST(
+ ("(fontlist) moved (%s) to family (%s)"
+ " with style: %s weight: %s stretch: %d",
+ face->Name().get(), gillSansMTFamily->Name().get(),
+ (face->IsItalic()) ? "italic"
+ : (face->IsOblique() ? "oblique" : "normal"),
+ weightString.get(), face->Stretch().AsScalar()));
+ }
+ }
+ gillSansFamily->ReadUnlock();
+
+ // remove Gill Sans
+ mFontFamilies.Remove(nameGillSans);
+ } else {
+ gillSansFamily->ReadUnlock();
+ }
+ }
+
+ nsAutoCString classicFamilies;
+ nsresult rv = Preferences::GetCString(
+ "gfx.font_rendering.cleartype_params.force_gdi_classic_for_families",
+ classicFamilies);
+ if (NS_SUCCEEDED(rv)) {
+ nsCCharSeparatedTokenizer tokenizer(classicFamilies, ',');
+ while (tokenizer.hasMoreTokens()) {
+ nsAutoCString name(tokenizer.nextToken());
+ BuildKeyNameFromFontName(name);
+ gfxFontFamily* family = mFontFamilies.GetWeak(name);
+ if (family) {
+ static_cast<gfxDWriteFontFamily*>(family)->SetForceGDIClassic(true);
+ }
+ }
+ }
+ mForceGDIClassicMaxFontSize = Preferences::GetInt(
+ "gfx.font_rendering.cleartype_params.force_gdi_classic_max_size",
+ mForceGDIClassicMaxFontSize);
+
+ GetPrefsAndStartLoader();
+
+ QueryPerformanceCounter(&t5); // misc initialization
+
+ if (LOG_FONTINIT_ENABLED()) {
+ // determine dwrite version
+ nsAutoString dwriteVers;
+ gfxWindowsPlatform::GetDLLVersion(L"dwrite.dll", dwriteVers);
+ LOG_FONTINIT(("(fontinit) Start: %s %s\n", nowDate, nowTime));
+ LOG_FONTINIT(("(fontinit) Uptime: %9.3f s\n", upTime / 1000));
+ LOG_FONTINIT(("(fontinit) dwrite version: %s\n",
+ NS_ConvertUTF16toUTF8(dwriteVers).get()));
+ }
+
+ elapsedTime = (t5.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart;
+ Telemetry::Accumulate(Telemetry::DWRITEFONT_DELAYEDINITFONTLIST_TOTAL,
+ elapsedTime);
+ Telemetry::Accumulate(Telemetry::DWRITEFONT_DELAYEDINITFONTLIST_COUNT,
+ mSystemFonts->GetFontFamilyCount());
+ LOG_FONTINIT((
+ "(fontinit) Total time in InitFontList: %9.3f ms (families: %d, %s)\n",
+ elapsedTime, mSystemFonts->GetFontFamilyCount(),
+ (mGDIFontTableAccess ? "gdi table access" : "dwrite table access")));
+
+ elapsedTime = (t2.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart;
+ LOG_FONTINIT(
+ ("(fontinit) --- base/interop obj initialization init: %9.3f ms\n",
+ elapsedTime));
+
+ elapsedTime = (t3.QuadPart - t2.QuadPart) * 1000.0 / frequency.QuadPart;
+ Telemetry::Accumulate(Telemetry::DWRITEFONT_DELAYEDINITFONTLIST_COLLECT,
+ elapsedTime);
+ LOG_FONTINIT(
+ ("(fontinit) --- GetSystemFontCollection: %9.3f ms\n", elapsedTime));
+
+ elapsedTime = (t4.QuadPart - t3.QuadPart) * 1000.0 / frequency.QuadPart;
+ LOG_FONTINIT(
+ ("(fontinit) --- iterate over families: %9.3f ms\n", elapsedTime));
+
+ elapsedTime = (t5.QuadPart - t4.QuadPart) * 1000.0 / frequency.QuadPart;
+ LOG_FONTINIT(
+ ("(fontinit) --- misc initialization: %9.3f ms\n", elapsedTime));
+
+ return NS_OK;
+}
+
+void gfxDWriteFontList::GetFontsFromCollection(
+ IDWriteFontCollection* aCollection) {
+ for (UINT32 i = 0; i < aCollection->GetFontFamilyCount(); i++) {
+ RefPtr<IDWriteFontFamily> family;
+ aCollection->GetFontFamily(i, getter_AddRefs(family));
+
+ RefPtr<IDWriteLocalizedStrings> names;
+ HRESULT hr = family->GetFamilyNames(getter_AddRefs(names));
+ if (FAILED(hr)) {
+ continue;
+ }
+
+ nsAutoCString name;
+ if (!GetEnglishOrFirstName(name, names)) {
+ continue;
+ }
+ nsAutoCString familyName(
+ name); // keep a copy before we lowercase it as a key
+
+ BuildKeyNameFromFontName(name);
+
+ RefPtr<gfxFontFamily> fam;
+
+ if (mFontFamilies.GetWeak(name)) {
+ continue;
+ }
+
+ FontVisibility visibility = aCollection == mSystemFonts
+ ? GetVisibilityForFamily(familyName)
+ : FontVisibility::Base;
+
+ fam = new gfxDWriteFontFamily(familyName, visibility, family,
+ aCollection == mSystemFonts);
+ if (!fam) {
+ continue;
+ }
+
+ if (mBadUnderlineFamilyNames.ContainsSorted(name)) {
+ fam->SetBadUnderlineFamily();
+ }
+ mFontFamilies.InsertOrUpdate(name, RefPtr{fam});
+
+ // now add other family name localizations, if present
+ uint32_t nameCount = names->GetCount();
+ uint32_t nameIndex;
+
+ if (nameCount > 1) {
+ UINT32 englishIdx = 0;
+ BOOL exists;
+ // if this fails/doesn't exist, we'll have used name index 0,
+ // so that's the one we'll want to skip here
+ names->FindLocaleName(L"en-us", &englishIdx, &exists);
+ AutoTArray<nsCString, 4> otherFamilyNames;
+ for (nameIndex = 0; nameIndex < nameCount; nameIndex++) {
+ UINT32 nameLen;
+ AutoTArray<WCHAR, 32> localizedName;
+
+ // only add other names
+ if (nameIndex == englishIdx) {
+ continue;
+ }
+
+ hr = names->GetStringLength(nameIndex, &nameLen);
+ if (FAILED(hr)) {
+ continue;
+ }
+
+ if (!localizedName.SetLength(nameLen + 1, fallible)) {
+ continue;
+ }
+
+ hr = names->GetString(nameIndex, localizedName.Elements(), nameLen + 1);
+ if (FAILED(hr)) {
+ continue;
+ }
+
+ NS_ConvertUTF16toUTF8 locName(localizedName.Elements());
+
+ if (!familyName.Equals(locName)) {
+ otherFamilyNames.AppendElement(locName);
+ }
+ }
+ if (!otherFamilyNames.IsEmpty()) {
+ AddOtherFamilyNames(fam, otherFamilyNames);
+ }
+ }
+
+ // at this point, all family names have been read in
+ fam->SetOtherFamilyNamesInitialized();
+ }
+}
+
+static void RemoveCharsetFromFontSubstitute(nsACString& aName) {
+ int32_t comma = aName.FindChar(',');
+ if (comma >= 0) aName.Truncate(comma);
+}
+
+#define MAX_VALUE_NAME 512
+#define MAX_VALUE_DATA 512
+
+nsresult gfxDWriteFontList::GetFontSubstitutes() {
+ HKEY hKey;
+ DWORD i, rv, lenAlias, lenActual, valueType;
+ WCHAR aliasName[MAX_VALUE_NAME];
+ WCHAR actualName[MAX_VALUE_DATA];
+
+ if (RegOpenKeyExW(
+ HKEY_LOCAL_MACHINE,
+ L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\FontSubstitutes",
+ 0, KEY_READ, &hKey) != ERROR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ for (i = 0, rv = ERROR_SUCCESS; rv != ERROR_NO_MORE_ITEMS; i++) {
+ aliasName[0] = 0;
+ lenAlias = ArrayLength(aliasName);
+ actualName[0] = 0;
+ lenActual = sizeof(actualName);
+ rv = RegEnumValueW(hKey, i, aliasName, &lenAlias, nullptr, &valueType,
+ (LPBYTE)actualName, &lenActual);
+
+ if (rv != ERROR_SUCCESS || valueType != REG_SZ || lenAlias == 0) {
+ continue;
+ }
+
+ if (aliasName[0] == WCHAR('@')) {
+ continue;
+ }
+
+ NS_ConvertUTF16toUTF8 substituteName((char16_t*)aliasName);
+ NS_ConvertUTF16toUTF8 actualFontName((char16_t*)actualName);
+ RemoveCharsetFromFontSubstitute(substituteName);
+ BuildKeyNameFromFontName(substituteName);
+ RemoveCharsetFromFontSubstitute(actualFontName);
+ BuildKeyNameFromFontName(actualFontName);
+ if (SharedFontList()) {
+ // Skip substitution if the original font is available, unless the option
+ // to apply substitutions unconditionally is enabled.
+ if (!StaticPrefs::gfx_windows_font_substitutes_always_AtStartup()) {
+ // Font substitutions are recorded for the canonical family names; we
+ // don't need FindFamily to consider localized aliases when searching.
+ if (SharedFontList()->FindFamily(substituteName,
+ /*aPrimaryNameOnly*/ true)) {
+ continue;
+ }
+ }
+ if (SharedFontList()->FindFamily(actualFontName,
+ /*aPrimaryNameOnly*/ true)) {
+ mSubstitutions.InsertOrUpdate(substituteName,
+ MakeUnique<nsCString>(actualFontName));
+ } else if (mSubstitutions.Get(actualFontName)) {
+ mSubstitutions.InsertOrUpdate(
+ substituteName,
+ MakeUnique<nsCString>(*mSubstitutions.Get(actualFontName)));
+ } else {
+ mNonExistingFonts.AppendElement(substituteName);
+ }
+ } else {
+ gfxFontFamily* ff;
+ if (!actualFontName.IsEmpty() &&
+ (ff = mFontFamilies.GetWeak(actualFontName))) {
+ mFontSubstitutes.InsertOrUpdate(substituteName, RefPtr{ff});
+ } else {
+ mNonExistingFonts.AppendElement(substituteName);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+struct FontSubstitution {
+ const char* aliasName;
+ const char* actualName;
+};
+
+static const FontSubstitution sDirectWriteSubs[] = {
+ {"MS Sans Serif", "Microsoft Sans Serif"},
+ {"MS Serif", "Times New Roman"},
+ {"Courier", "Courier New"},
+ {"Small Fonts", "Arial"},
+ {"Roman", "Times New Roman"},
+ {"Script", "Mistral"}};
+
+void gfxDWriteFontList::GetDirectWriteSubstitutes() {
+ for (uint32_t i = 0; i < ArrayLength(sDirectWriteSubs); ++i) {
+ const FontSubstitution& sub(sDirectWriteSubs[i]);
+ nsAutoCString substituteName(sub.aliasName);
+ BuildKeyNameFromFontName(substituteName);
+ if (SharedFontList()) {
+ // Skip substitution if the original font is available, unless the option
+ // to apply substitutions unconditionally is enabled.
+ if (!StaticPrefs::gfx_windows_font_substitutes_always_AtStartup()) {
+ // We don't need FindFamily to consider localized aliases when searching
+ // for the DirectWrite substitutes, we know the canonical names.
+ if (SharedFontList()->FindFamily(substituteName,
+ /*aPrimaryNameOnly*/ true)) {
+ continue;
+ }
+ }
+ nsAutoCString actualFontName(sub.actualName);
+ BuildKeyNameFromFontName(actualFontName);
+ if (SharedFontList()->FindFamily(actualFontName,
+ /*aPrimaryNameOnly*/ true)) {
+ mSubstitutions.InsertOrUpdate(substituteName,
+ MakeUnique<nsCString>(actualFontName));
+ } else {
+ mNonExistingFonts.AppendElement(substituteName);
+ }
+ } else {
+ if (nullptr != mFontFamilies.GetWeak(substituteName)) {
+ // don't do the substitution if user actually has a usable font
+ // with this name installed
+ continue;
+ }
+ nsAutoCString actualFontName(sub.actualName);
+ BuildKeyNameFromFontName(actualFontName);
+ gfxFontFamily* ff;
+ if (nullptr != (ff = mFontFamilies.GetWeak(actualFontName))) {
+ mFontSubstitutes.InsertOrUpdate(substituteName, RefPtr{ff});
+ } else {
+ mNonExistingFonts.AppendElement(substituteName);
+ }
+ }
+ }
+}
+
+bool gfxDWriteFontList::FindAndAddFamiliesLocked(
+ nsPresContext* aPresContext, StyleGenericFontFamily aGeneric,
+ const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput,
+ FindFamiliesFlags aFlags, gfxFontStyle* aStyle, nsAtom* aLanguage,
+ gfxFloat aDevToCssSize) {
+ nsAutoCString keyName(aFamily);
+ BuildKeyNameFromFontName(keyName);
+
+ if (SharedFontList()) {
+ nsACString* subst = mSubstitutions.Get(keyName);
+ if (subst) {
+ keyName = *subst;
+ }
+ } else {
+ gfxFontFamily* ff = mFontSubstitutes.GetWeak(keyName);
+ FontVisibility level =
+ aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User;
+ if (ff && IsVisibleToCSS(*ff, level)) {
+ aOutput->AppendElement(FamilyAndGeneric(ff, aGeneric));
+ return true;
+ }
+ }
+
+ if (mNonExistingFonts.Contains(keyName)) {
+ return false;
+ }
+
+ return gfxPlatformFontList::FindAndAddFamiliesLocked(
+ aPresContext, aGeneric, keyName, aOutput, aFlags, aStyle, aLanguage,
+ aDevToCssSize);
+}
+
+void gfxDWriteFontList::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ gfxPlatformFontList::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+
+ AutoLock lock(mLock);
+
+ // We are a singleton, so include the font loader singleton's memory.
+ MOZ_ASSERT(static_cast<const gfxPlatformFontList*>(this) ==
+ gfxPlatformFontList::PlatformFontList());
+ gfxDWriteFontFileLoader* loader = static_cast<gfxDWriteFontFileLoader*>(
+ gfxDWriteFontFileLoader::Instance());
+ aSizes->mLoaderSize += loader->SizeOfIncludingThis(aMallocSizeOf);
+
+ aSizes->mFontListSize +=
+ SizeOfFontFamilyTableExcludingThis(mFontSubstitutes, aMallocSizeOf);
+
+ aSizes->mFontListSize +=
+ mNonExistingFonts.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (uint32_t i = 0; i < mNonExistingFonts.Length(); ++i) {
+ aSizes->mFontListSize +=
+ mNonExistingFonts[i].SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ }
+}
+
+void gfxDWriteFontList::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ aSizes->mFontListSize += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
+
+static HRESULT GetFamilyName(IDWriteFont* aFont, nsCString& aFamilyName) {
+ HRESULT hr;
+ RefPtr<IDWriteFontFamily> family;
+
+ // clean out previous value
+ aFamilyName.Truncate();
+
+ hr = aFont->GetFontFamily(getter_AddRefs(family));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ RefPtr<IDWriteLocalizedStrings> familyNames;
+
+ hr = family->GetFamilyNames(getter_AddRefs(familyNames));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ if (!GetEnglishOrFirstName(aFamilyName, familyNames)) {
+ return E_FAIL;
+ }
+
+ return S_OK;
+}
+
+// bug 705594 - the method below doesn't actually do any "drawing", it's only
+// used to invoke the DirectWrite layout engine to determine the fallback font
+// for a given character.
+
+IFACEMETHODIMP DWriteFontFallbackRenderer::DrawGlyphRun(
+ void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY,
+ DWRITE_MEASURING_MODE measuringMode, DWRITE_GLYPH_RUN const* glyphRun,
+ DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription,
+ IUnknown* clientDrawingEffect) {
+ if (!mSystemFonts) {
+ return E_FAIL;
+ }
+
+ HRESULT hr = S_OK;
+
+ RefPtr<IDWriteFont> font;
+ hr = mSystemFonts->GetFontFromFontFace(glyphRun->fontFace,
+ getter_AddRefs(font));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ // copy the family name
+ hr = GetFamilyName(font, mFamilyName);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ // Arial is used as the default fallback font
+ // so if it matches ==> no font found
+ if (mFamilyName.EqualsLiteral("Arial")) {
+ mFamilyName.Truncate();
+ return E_FAIL;
+ }
+ return hr;
+}
+
+gfxFontEntry* gfxDWriteFontList::PlatformGlobalFontFallback(
+ nsPresContext* aPresContext, const uint32_t aCh, Script aRunScript,
+ const gfxFontStyle* aMatchStyle, FontFamily& aMatchedFamily) {
+ HRESULT hr;
+
+ RefPtr<IDWriteFactory> dwFactory = Factory::GetDWriteFactory();
+ if (!dwFactory) {
+ return nullptr;
+ }
+
+ // initialize fallback renderer
+ if (!mFallbackRenderer) {
+ mFallbackRenderer = new DWriteFontFallbackRenderer(dwFactory);
+ }
+ if (!mFallbackRenderer->IsValid()) {
+ return nullptr;
+ }
+
+ // initialize text format
+ if (!mFallbackFormat) {
+ hr = dwFactory->CreateTextFormat(
+ L"Arial", nullptr, DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL,
+ DWRITE_FONT_STRETCH_NORMAL, 72.0f, L"en-us",
+ getter_AddRefs(mFallbackFormat));
+ if (FAILED(hr)) {
+ return nullptr;
+ }
+ }
+
+ // set up string with fallback character
+ wchar_t str[16];
+ uint32_t strLen;
+
+ if (IS_IN_BMP(aCh)) {
+ str[0] = static_cast<wchar_t>(aCh);
+ str[1] = 0;
+ strLen = 1;
+ } else {
+ str[0] = static_cast<wchar_t>(H_SURROGATE(aCh));
+ str[1] = static_cast<wchar_t>(L_SURROGATE(aCh));
+ str[2] = 0;
+ strLen = 2;
+ }
+
+ // set up layout
+ RefPtr<IDWriteTextLayout> fallbackLayout;
+
+ hr = dwFactory->CreateTextLayout(str, strLen, mFallbackFormat, 200.0f, 200.0f,
+ getter_AddRefs(fallbackLayout));
+ if (FAILED(hr)) {
+ return nullptr;
+ }
+
+ // call the draw method to invoke the DirectWrite layout functions
+ // which determine the fallback font
+ MOZ_SEH_TRY {
+ hr = fallbackLayout->Draw(nullptr, mFallbackRenderer, 50.0f, 50.0f);
+ if (FAILED(hr)) {
+ return nullptr;
+ }
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ gfxCriticalNote << "Exception occurred during DWrite font fallback";
+ return nullptr;
+ }
+
+ FontFamily family =
+ FindFamily(aPresContext, mFallbackRenderer->FallbackFamilyName());
+ if (!family.IsNull()) {
+ gfxFontEntry* fontEntry = nullptr;
+ if (family.mShared) {
+ auto face =
+ family.mShared->FindFaceForStyle(SharedFontList(), *aMatchStyle);
+ if (face) {
+ fontEntry = GetOrCreateFontEntry(face, family.mShared);
+ }
+ } else {
+ fontEntry = family.mUnshared->FindFontForStyle(*aMatchStyle);
+ }
+ if (fontEntry && fontEntry->HasCharacter(aCh)) {
+ aMatchedFamily = family;
+ return fontEntry;
+ }
+ Telemetry::Accumulate(Telemetry::BAD_FALLBACK_FONT, true);
+ }
+
+ return nullptr;
+}
+
+// used to load system-wide font info on off-main thread
+class DirectWriteFontInfo : public FontInfoData {
+ public:
+ DirectWriteFontInfo(bool aLoadOtherNames, bool aLoadFaceNames,
+ bool aLoadCmaps, IDWriteFontCollection* aSystemFonts
+#ifdef MOZ_BUNDLED_FONTS
+ ,
+ IDWriteFontCollection* aBundledFonts
+#endif
+ )
+ : FontInfoData(aLoadOtherNames, aLoadFaceNames, aLoadCmaps),
+ mSystemFonts(aSystemFonts)
+#ifdef MOZ_BUNDLED_FONTS
+ ,
+ mBundledFonts(aBundledFonts)
+#endif
+ {
+ }
+
+ virtual ~DirectWriteFontInfo() = default;
+
+ // loads font data for all members of a given family
+ virtual void LoadFontFamilyData(const nsACString& aFamilyName);
+
+ private:
+ RefPtr<IDWriteFontCollection> mSystemFonts;
+#ifdef MOZ_BUNDLED_FONTS
+ RefPtr<IDWriteFontCollection> mBundledFonts;
+#endif
+};
+
+void DirectWriteFontInfo::LoadFontFamilyData(const nsACString& aFamilyName) {
+ // lookup the family
+ NS_ConvertUTF8toUTF16 famName(aFamilyName);
+
+ HRESULT hr;
+ BOOL exists = false;
+
+ uint32_t index;
+ RefPtr<IDWriteFontFamily> family;
+ hr = mSystemFonts->FindFamilyName((const wchar_t*)famName.get(), &index,
+ &exists);
+ if (SUCCEEDED(hr) && exists) {
+ mSystemFonts->GetFontFamily(index, getter_AddRefs(family));
+ if (!family) {
+ return;
+ }
+ }
+
+#ifdef MOZ_BUNDLED_FONTS
+ if (!family && mBundledFonts) {
+ hr = mBundledFonts->FindFamilyName((const wchar_t*)famName.get(), &index,
+ &exists);
+ if (SUCCEEDED(hr) && exists) {
+ mBundledFonts->GetFontFamily(index, getter_AddRefs(family));
+ }
+ }
+#endif
+
+ if (!family) {
+ return;
+ }
+
+ // later versions of DirectWrite support querying the fullname/psname
+ bool loadFaceNamesUsingDirectWrite = mLoadFaceNames;
+
+ for (uint32_t i = 0; i < family->GetFontCount(); i++) {
+ // get the font
+ RefPtr<IDWriteFont> dwFont;
+ hr = family->GetFont(i, getter_AddRefs(dwFont));
+ if (FAILED(hr)) {
+ // This should never happen.
+ NS_WARNING("Failed to get existing font from family.");
+ continue;
+ }
+
+ if (dwFont->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE) {
+ // We don't want these in the font list; we'll apply simulations
+ // on the fly when appropriate.
+ continue;
+ }
+
+ mLoadStats.fonts++;
+
+ // get the name of the face
+ nsCString fullID(aFamilyName);
+ nsAutoCString fontName;
+ hr = GetDirectWriteFontName(dwFont, fontName);
+ if (FAILED(hr)) {
+ continue;
+ }
+ fullID.Append(' ');
+ fullID.Append(fontName);
+
+ FontFaceData fontData;
+ bool haveData = true;
+ RefPtr<IDWriteFontFace> dwFontFace;
+
+ if (mLoadFaceNames) {
+ // try to load using DirectWrite first
+ if (loadFaceNamesUsingDirectWrite) {
+ hr =
+ GetDirectWriteFaceName(dwFont, PSNAME_ID, fontData.mPostscriptName);
+ if (FAILED(hr)) {
+ loadFaceNamesUsingDirectWrite = false;
+ }
+ hr = GetDirectWriteFaceName(dwFont, FULLNAME_ID, fontData.mFullName);
+ if (FAILED(hr)) {
+ loadFaceNamesUsingDirectWrite = false;
+ }
+ }
+
+ // if DirectWrite read fails, load directly from name table
+ if (!loadFaceNamesUsingDirectWrite) {
+ hr = dwFont->CreateFontFace(getter_AddRefs(dwFontFace));
+ if (SUCCEEDED(hr)) {
+ uint32_t kNAME =
+ NativeEndian::swapToBigEndian(TRUETYPE_TAG('n', 'a', 'm', 'e'));
+ const char* nameData;
+ BOOL exists;
+ void* ctx;
+ uint32_t nameSize;
+
+ hr = dwFontFace->TryGetFontTable(kNAME, (const void**)&nameData,
+ &nameSize, &ctx, &exists);
+ if (SUCCEEDED(hr) && nameData && nameSize > 0) {
+ MOZ_SEH_TRY {
+ gfxFontUtils::ReadCanonicalName(nameData, nameSize,
+ gfxFontUtils::NAME_ID_FULL,
+ fontData.mFullName);
+ gfxFontUtils::ReadCanonicalName(nameData, nameSize,
+ gfxFontUtils::NAME_ID_POSTSCRIPT,
+ fontData.mPostscriptName);
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ gfxCriticalNote << "Exception occurred reading names for "
+ << PromiseFlatCString(aFamilyName).get();
+ }
+ dwFontFace->ReleaseFontTable(ctx);
+ }
+ }
+ }
+
+ haveData =
+ !fontData.mPostscriptName.IsEmpty() || !fontData.mFullName.IsEmpty();
+ if (haveData) {
+ mLoadStats.facenames++;
+ }
+ }
+
+ // cmaps
+ if (mLoadCmaps) {
+ if (!dwFontFace) {
+ hr = dwFont->CreateFontFace(getter_AddRefs(dwFontFace));
+ if (!SUCCEEDED(hr)) {
+ continue;
+ }
+ }
+
+ uint32_t kCMAP =
+ NativeEndian::swapToBigEndian(TRUETYPE_TAG('c', 'm', 'a', 'p'));
+ const uint8_t* cmapData;
+ BOOL exists;
+ void* ctx;
+ uint32_t cmapSize;
+
+ hr = dwFontFace->TryGetFontTable(kCMAP, (const void**)&cmapData,
+ &cmapSize, &ctx, &exists);
+
+ if (SUCCEEDED(hr) && exists) {
+ bool cmapLoaded = false;
+ RefPtr<gfxCharacterMap> charmap = new gfxCharacterMap();
+ uint32_t offset;
+ MOZ_SEH_TRY {
+ if (cmapData && cmapSize > 0 &&
+ NS_SUCCEEDED(gfxFontUtils::ReadCMAP(cmapData, cmapSize, *charmap,
+ offset))) {
+ fontData.mCharacterMap = charmap;
+ fontData.mUVSOffset = offset;
+ cmapLoaded = true;
+ mLoadStats.cmaps++;
+ }
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ gfxCriticalNote << "Exception occurred reading cmaps for "
+ << PromiseFlatCString(aFamilyName).get();
+ }
+ dwFontFace->ReleaseFontTable(ctx);
+ haveData = haveData || cmapLoaded;
+ }
+ }
+
+ // if have data, load
+ if (haveData) {
+ mFontFaceData.InsertOrUpdate(fullID, fontData);
+ }
+ }
+}
+
+already_AddRefed<FontInfoData> gfxDWriteFontList::CreateFontInfoData() {
+ bool loadCmaps = !UsesSystemFallback() ||
+ gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback();
+
+ RefPtr<DirectWriteFontInfo> fi = new DirectWriteFontInfo(
+ false, NeedFullnamePostscriptNames(), loadCmaps, mSystemFonts
+#ifdef MOZ_BUNDLED_FONTS
+ ,
+ mBundledFonts
+#endif
+ );
+
+ return fi.forget();
+}
+
+gfxFontFamily* gfxDWriteFontList::CreateFontFamily(
+ const nsACString& aName, FontVisibility aVisibility) const {
+ return new gfxDWriteFontFamily(aName, aVisibility, nullptr);
+}
+
+#ifdef MOZ_BUNDLED_FONTS
+
+# define IMPL_QI_FOR_DWRITE(_interface) \
+ public: \
+ IFACEMETHOD(QueryInterface)(IID const& riid, void** ppvObject) { \
+ if (__uuidof(_interface) == riid) { \
+ *ppvObject = this; \
+ } else if (__uuidof(IUnknown) == riid) { \
+ *ppvObject = this; \
+ } else { \
+ *ppvObject = nullptr; \
+ return E_NOINTERFACE; \
+ } \
+ this->AddRef(); \
+ return S_OK; \
+ }
+
+class BundledFontFileEnumerator : public IDWriteFontFileEnumerator {
+ IMPL_QI_FOR_DWRITE(IDWriteFontFileEnumerator)
+
+ NS_INLINE_DECL_REFCOUNTING(BundledFontFileEnumerator)
+
+ public:
+ BundledFontFileEnumerator(IDWriteFactory* aFactory, nsIFile* aFontDir);
+
+ IFACEMETHODIMP MoveNext(BOOL* hasCurrentFile);
+
+ IFACEMETHODIMP GetCurrentFontFile(IDWriteFontFile** fontFile);
+
+ private:
+ BundledFontFileEnumerator() = delete;
+ BundledFontFileEnumerator(const BundledFontFileEnumerator&) = delete;
+ BundledFontFileEnumerator& operator=(const BundledFontFileEnumerator&) =
+ delete;
+ virtual ~BundledFontFileEnumerator() = default;
+
+ RefPtr<IDWriteFactory> mFactory;
+
+ nsCOMPtr<nsIFile> mFontDir;
+ nsCOMPtr<nsIDirectoryEnumerator> mEntries;
+ nsCOMPtr<nsISupports> mCurrent;
+};
+
+BundledFontFileEnumerator::BundledFontFileEnumerator(IDWriteFactory* aFactory,
+ nsIFile* aFontDir)
+ : mFactory(aFactory), mFontDir(aFontDir) {
+ mFontDir->GetDirectoryEntries(getter_AddRefs(mEntries));
+}
+
+IFACEMETHODIMP
+BundledFontFileEnumerator::MoveNext(BOOL* aHasCurrentFile) {
+ bool hasMore = false;
+ if (mEntries) {
+ if (NS_SUCCEEDED(mEntries->HasMoreElements(&hasMore)) && hasMore) {
+ if (NS_SUCCEEDED(mEntries->GetNext(getter_AddRefs(mCurrent)))) {
+ hasMore = true;
+ }
+ }
+ }
+ *aHasCurrentFile = hasMore;
+ return S_OK;
+}
+
+IFACEMETHODIMP
+BundledFontFileEnumerator::GetCurrentFontFile(IDWriteFontFile** aFontFile) {
+ nsCOMPtr<nsIFile> file = do_QueryInterface(mCurrent);
+ if (!file) {
+ return E_FAIL;
+ }
+ nsString path;
+ if (NS_FAILED(file->GetPath(path))) {
+ return E_FAIL;
+ }
+ return mFactory->CreateFontFileReference((const WCHAR*)path.get(), nullptr,
+ aFontFile);
+}
+
+class BundledFontLoader : public IDWriteFontCollectionLoader {
+ IMPL_QI_FOR_DWRITE(IDWriteFontCollectionLoader)
+
+ NS_INLINE_DECL_REFCOUNTING(BundledFontLoader)
+
+ public:
+ BundledFontLoader() {}
+
+ IFACEMETHODIMP CreateEnumeratorFromKey(
+ IDWriteFactory* aFactory, const void* aCollectionKey,
+ UINT32 aCollectionKeySize,
+ IDWriteFontFileEnumerator** aFontFileEnumerator);
+
+ private:
+ BundledFontLoader(const BundledFontLoader&) = delete;
+ BundledFontLoader& operator=(const BundledFontLoader&) = delete;
+ virtual ~BundledFontLoader() = default;
+};
+
+IFACEMETHODIMP
+BundledFontLoader::CreateEnumeratorFromKey(
+ IDWriteFactory* aFactory, const void* aCollectionKey,
+ UINT32 aCollectionKeySize,
+ IDWriteFontFileEnumerator** aFontFileEnumerator) {
+ nsIFile* fontDir = *(nsIFile**)aCollectionKey;
+ *aFontFileEnumerator = new BundledFontFileEnumerator(aFactory, fontDir);
+ NS_ADDREF(*aFontFileEnumerator);
+ return S_OK;
+}
+
+already_AddRefed<IDWriteFontCollection>
+gfxDWriteFontList::CreateBundledFontsCollection(IDWriteFactory* aFactory) {
+ nsCOMPtr<nsIFile> localDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+ if (NS_FAILED(localDir->Append(u"fonts"_ns))) {
+ return nullptr;
+ }
+ bool isDir;
+ if (NS_FAILED(localDir->IsDirectory(&isDir)) || !isDir) {
+ return nullptr;
+ }
+
+ RefPtr<BundledFontLoader> loader = new BundledFontLoader();
+ if (FAILED(aFactory->RegisterFontCollectionLoader(loader))) {
+ return nullptr;
+ }
+
+ const void* key = localDir.get();
+ RefPtr<IDWriteFontCollection> collection;
+ HRESULT hr = aFactory->CreateCustomFontCollection(loader, &key, sizeof(key),
+ getter_AddRefs(collection));
+
+ aFactory->UnregisterFontCollectionLoader(loader);
+
+ if (FAILED(hr)) {
+ return nullptr;
+ } else {
+ return collection.forget();
+ }
+}
+
+#endif
diff --git a/gfx/thebes/gfxDWriteFontList.h b/gfx/thebes/gfxDWriteFontList.h
new file mode 100644
index 0000000000..3ff0e6f7c5
--- /dev/null
+++ b/gfx/thebes/gfxDWriteFontList.h
@@ -0,0 +1,504 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_DWRITEFONTLIST_H
+#define GFX_DWRITEFONTLIST_H
+
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/MemoryReporting.h"
+#include "gfxDWriteCommon.h"
+#include "dwrite_3.h"
+
+// Currently, we build with WINVER=0x601 (Win7), which means newer
+// declarations in dwrite_3.h will not be visible. Also, we don't
+// yet have the Fall Creators Update SDK available on build machines,
+// so even with updated WINVER, some of the interfaces we need would
+// not be present.
+// To work around this, until the build environment is updated,
+// we #include an extra header that contains copies of the relevant
+// classes/interfaces we need.
+#if !defined(__MINGW32__) && WINVER < 0x0A00
+# include "mozilla/gfx/dw-extra.h"
+#endif
+
+#include "gfxFont.h"
+#include "gfxUserFontSet.h"
+#include "cairo-win32.h"
+
+#include "gfxPlatformFontList.h"
+#include "gfxPlatform.h"
+#include <algorithm>
+
+#include "mozilla/gfx/UnscaledFontDWrite.h"
+
+/**
+ * \brief Class representing directwrite font family.
+ *
+ * gfxDWriteFontFamily is a class that describes one of the font families on
+ * the user's system. It holds each gfxDWriteFontEntry (maps more directly to
+ * a font face) which holds font type, charset info and character map info.
+ */
+class gfxDWriteFontFamily final : public gfxFontFamily {
+ public:
+ typedef mozilla::FontStretch FontStretch;
+ typedef mozilla::FontSlantStyle FontSlantStyle;
+ typedef mozilla::FontWeight FontWeight;
+
+ /**
+ * Constructs a new DWriteFont Family.
+ *
+ * \param aName Name identifying the family
+ * \param aFamily IDWriteFontFamily object representing the directwrite
+ * family object.
+ */
+ gfxDWriteFontFamily(const nsACString& aName, FontVisibility aVisibility,
+ IDWriteFontFamily* aFamily,
+ bool aIsSystemFontFamily = false)
+ : gfxFontFamily(aName, aVisibility),
+ mDWFamily(aFamily),
+ mIsSystemFontFamily(aIsSystemFontFamily),
+ mForceGDIClassic(false) {}
+ virtual ~gfxDWriteFontFamily();
+
+ void FindStyleVariationsLocked(FontInfoData* aFontInfoData = nullptr)
+ MOZ_REQUIRES(mLock) final;
+
+ void LocalizedName(nsACString& aLocalizedName) final;
+
+ void ReadFaceNames(gfxPlatformFontList* aPlatformFontList,
+ bool aNeedFullnamePostscriptNames,
+ FontInfoData* aFontInfoData = nullptr) final;
+
+ void SetForceGDIClassic(bool aForce) { mForceGDIClassic = aForce; }
+
+ void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const final;
+ void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const final;
+
+ bool FilterForFontList(nsAtom* aLangGroup,
+ const nsACString& aGeneric) const final {
+ return !IsSymbolFontFamily();
+ }
+
+ protected:
+ // helper for FilterForFontList
+ bool IsSymbolFontFamily() const;
+
+ /** This font family's directwrite fontfamily object */
+ RefPtr<IDWriteFontFamily> mDWFamily;
+ bool mIsSystemFontFamily;
+ bool mForceGDIClassic;
+};
+
+/**
+ * \brief Class representing DirectWrite FontEntry (a unique font style/family)
+ */
+class gfxDWriteFontEntry final : public gfxFontEntry {
+ public:
+ /**
+ * Constructs a font entry.
+ *
+ * \param aFaceName The name of the corresponding font face.
+ * \param aFont DirectWrite font object
+ */
+ gfxDWriteFontEntry(const nsACString& aFaceName, IDWriteFont* aFont,
+ bool aIsSystemFont = false)
+ : gfxFontEntry(aFaceName),
+ mFont(aFont),
+ mFontFile(nullptr),
+ mIsSystemFont(aIsSystemFont),
+ mForceGDIClassic(false),
+ mHasVariations(false),
+ mHasVariationsInitialized(false) {
+ DWRITE_FONT_STYLE dwriteStyle = aFont->GetStyle();
+ FontSlantStyle style = (dwriteStyle == DWRITE_FONT_STYLE_ITALIC
+ ? FontSlantStyle::ITALIC
+ : (dwriteStyle == DWRITE_FONT_STYLE_OBLIQUE
+ ? FontSlantStyle::OBLIQUE
+ : FontSlantStyle::NORMAL));
+ mStyleRange = SlantStyleRange(style);
+
+ mStretchRange =
+ StretchRange(FontStretchFromDWriteStretch(aFont->GetStretch()));
+
+ int weight = NS_ROUNDUP(aFont->GetWeight() - 50, 100);
+ weight = mozilla::Clamp(weight, 100, 900);
+ mWeightRange = WeightRange(FontWeight::FromInt(weight));
+
+ mIsCJK = UNINITIALIZED_VALUE;
+ }
+
+ /**
+ * Constructs a font entry using a font. But with custom font values.
+ * This is used for creating correct font entries for @font-face with local
+ * font source.
+ *
+ * \param aFaceName The name of the corresponding font face.
+ * \param aFont DirectWrite font object
+ * \param aWeight Weight of the font
+ * \param aStretch Stretch of the font
+ * \param aStyle italic or oblique of font
+ */
+ gfxDWriteFontEntry(const nsACString& aFaceName, IDWriteFont* aFont,
+ WeightRange aWeight, StretchRange aStretch,
+ SlantStyleRange aStyle)
+ : gfxFontEntry(aFaceName),
+ mFont(aFont),
+ mFontFile(nullptr),
+ mIsSystemFont(false),
+ mForceGDIClassic(false),
+ mHasVariations(false),
+ mHasVariationsInitialized(false) {
+ mWeightRange = aWeight;
+ mStretchRange = aStretch;
+ mStyleRange = aStyle;
+ mIsLocalUserFont = true;
+ mIsCJK = UNINITIALIZED_VALUE;
+ }
+
+ /**
+ * Constructs a font entry using a font file.
+ *
+ * \param aFaceName The name of the corresponding font face.
+ * \param aFontFile DirectWrite fontfile object
+ * \param aFontFileStream DirectWrite fontfile stream object
+ * \param aWeight Weight of the font
+ * \param aStretch Stretch of the font
+ * \param aStyle italic or oblique of font
+ */
+ gfxDWriteFontEntry(const nsACString& aFaceName, IDWriteFontFile* aFontFile,
+ IDWriteFontFileStream* aFontFileStream,
+ WeightRange aWeight, StretchRange aStretch,
+ SlantStyleRange aStyle)
+ : gfxFontEntry(aFaceName),
+ mFont(nullptr),
+ mFontFile(aFontFile),
+ mFontFileStream(aFontFileStream),
+ mIsSystemFont(false),
+ mForceGDIClassic(false),
+ mHasVariations(false),
+ mHasVariationsInitialized(false) {
+ mWeightRange = aWeight;
+ mStretchRange = aStretch;
+ mStyleRange = aStyle;
+ mIsDataUserFont = true;
+ mIsCJK = UNINITIALIZED_VALUE;
+ }
+
+ gfxFontEntry* Clone() const override;
+
+ virtual ~gfxDWriteFontEntry();
+
+ hb_blob_t* GetFontTable(uint32_t aTableTag) override;
+
+ nsresult ReadCMAP(FontInfoData* aFontInfoData = nullptr) override;
+
+ bool IsCJKFont();
+
+ bool HasVariations() override;
+ void GetVariationAxes(nsTArray<gfxFontVariationAxis>& aAxes) override;
+ void GetVariationInstances(
+ nsTArray<gfxFontVariationInstance>& aInstances) override;
+
+ void SetForceGDIClassic(bool aForce) { mForceGDIClassic = aForce; }
+ bool GetForceGDIClassic() { return mForceGDIClassic; }
+
+ void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const override;
+ void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const override;
+
+ protected:
+ friend class gfxDWriteFont;
+ friend class gfxDWriteFontList;
+ friend class gfxDWriteFontFamily;
+
+ virtual nsresult CopyFontTable(uint32_t aTableTag,
+ nsTArray<uint8_t>& aBuffer) override;
+
+ virtual gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle);
+
+ nsresult CreateFontFace(
+ IDWriteFontFace** aFontFace, const gfxFontStyle* aFontStyle = nullptr,
+ DWRITE_FONT_SIMULATIONS aSimulations = DWRITE_FONT_SIMULATIONS_NONE,
+ const nsTArray<gfxFontVariation>* aVariations = nullptr);
+
+ static bool InitLogFont(IDWriteFont* aFont, LOGFONTW* aLogFont);
+
+ /**
+ * A fontentry only needs to have either of these. If it has both only
+ * the IDWriteFont will be used.
+ */
+ RefPtr<IDWriteFont> mFont;
+ RefPtr<IDWriteFontFile> mFontFile;
+
+ // For custom fonts, we hold a reference to the IDWriteFontFileStream for
+ // for the IDWriteFontFile, so that the data is available.
+ RefPtr<IDWriteFontFileStream> mFontFileStream;
+
+ // font face corresponding to the mFont/mFontFile *without* any DWrite
+ // style simulations applied
+ RefPtr<IDWriteFontFace> mFontFace;
+ // Extended fontface interface if supported, else null
+ RefPtr<IDWriteFontFace5> mFontFace5;
+
+ DWRITE_FONT_FACE_TYPE mFaceType;
+
+ int8_t mIsCJK;
+ bool mIsSystemFont;
+ bool mForceGDIClassic;
+ bool mHasVariations;
+ bool mHasVariationsInitialized;
+
+ // Set to true only if the font belongs to a "simple" family where the
+ // faces can be reliably identified via a GDI LOGFONT structure.
+ bool mMayUseGDIAccess = false;
+
+ mozilla::ThreadSafeWeakPtr<mozilla::gfx::UnscaledFontDWrite> mUnscaledFont;
+ mozilla::ThreadSafeWeakPtr<mozilla::gfx::UnscaledFontDWrite>
+ mUnscaledFontBold;
+};
+
+// custom text renderer used to determine the fallback font for a given char
+class DWriteFontFallbackRenderer final : public IDWriteTextRenderer {
+ public:
+ explicit DWriteFontFallbackRenderer(IDWriteFactory* aFactory) : mRefCount(0) {
+ HRESULT hr =
+ aFactory->GetSystemFontCollection(getter_AddRefs(mSystemFonts));
+ NS_ASSERTION(SUCCEEDED(hr), "GetSystemFontCollection failed!");
+ (void)hr;
+ }
+
+ ~DWriteFontFallbackRenderer() {}
+
+ // If we don't have an mSystemFonts pointer, this renderer is unusable.
+ bool IsValid() const { return mSystemFonts; }
+
+ // IDWriteTextRenderer methods
+ IFACEMETHOD(DrawGlyphRun)
+ (void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY,
+ DWRITE_MEASURING_MODE measuringMode, DWRITE_GLYPH_RUN const* glyphRun,
+ DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription,
+ IUnknown* clientDrawingEffect);
+
+ IFACEMETHOD(DrawUnderline)
+ (void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY,
+ DWRITE_UNDERLINE const* underline, IUnknown* clientDrawingEffect) {
+ return E_NOTIMPL;
+ }
+
+ IFACEMETHOD(DrawStrikethrough)
+ (void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY,
+ DWRITE_STRIKETHROUGH const* strikethrough, IUnknown* clientDrawingEffect) {
+ return E_NOTIMPL;
+ }
+
+ IFACEMETHOD(DrawInlineObject)
+ (void* clientDrawingContext, FLOAT originX, FLOAT originY,
+ IDWriteInlineObject* inlineObject, BOOL isSideways, BOOL isRightToLeft,
+ IUnknown* clientDrawingEffect) {
+ return E_NOTIMPL;
+ }
+
+ // IDWritePixelSnapping methods
+
+ IFACEMETHOD(IsPixelSnappingDisabled)
+ (void* clientDrawingContext, BOOL* isDisabled) {
+ *isDisabled = FALSE;
+ return S_OK;
+ }
+
+ IFACEMETHOD(GetCurrentTransform)
+ (void* clientDrawingContext, DWRITE_MATRIX* transform) {
+ const DWRITE_MATRIX ident = {1.0, 0.0, 0.0, 1.0, 0.0, 0.0};
+ *transform = ident;
+ return S_OK;
+ }
+
+ IFACEMETHOD(GetPixelsPerDip)
+ (void* clientDrawingContext, FLOAT* pixelsPerDip) {
+ *pixelsPerDip = 1.0f;
+ return S_OK;
+ }
+
+ // IUnknown methods
+
+ IFACEMETHOD_(unsigned long, AddRef)() {
+ return InterlockedIncrement(&mRefCount);
+ }
+
+ IFACEMETHOD_(unsigned long, Release)() {
+ unsigned long newCount = InterlockedDecrement(&mRefCount);
+ if (newCount == 0) {
+ delete this;
+ return 0;
+ }
+
+ return newCount;
+ }
+
+ IFACEMETHOD(QueryInterface)(IID const& riid, void** ppvObject) {
+ if (__uuidof(IDWriteTextRenderer) == riid) {
+ *ppvObject = this;
+ } else if (__uuidof(IDWritePixelSnapping) == riid) {
+ *ppvObject = this;
+ } else if (__uuidof(IUnknown) == riid) {
+ *ppvObject = this;
+ } else {
+ *ppvObject = nullptr;
+ return E_FAIL;
+ }
+
+ this->AddRef();
+ return S_OK;
+ }
+
+ const nsCString& FallbackFamilyName() { return mFamilyName; }
+
+ protected:
+ long mRefCount;
+ RefPtr<IDWriteFontCollection> mSystemFonts;
+ nsCString mFamilyName;
+};
+
+class gfxDWriteFontList final : public gfxPlatformFontList {
+ public:
+ gfxDWriteFontList();
+ virtual ~gfxDWriteFontList() { AutoLock lock(mLock); }
+
+ static gfxDWriteFontList* PlatformFontList() {
+ return static_cast<gfxDWriteFontList*>(
+ gfxPlatformFontList::PlatformFontList());
+ }
+
+ // initialize font lists
+ nsresult InitFontListForPlatform() MOZ_REQUIRES(mLock) override;
+ void InitSharedFontListForPlatform() MOZ_REQUIRES(mLock) override;
+
+ FontVisibility GetVisibilityForFamily(const nsACString& aName) const;
+
+ gfxFontFamily* CreateFontFamily(const nsACString& aName,
+ FontVisibility aVisibility) const override;
+
+ gfxFontEntry* CreateFontEntry(
+ mozilla::fontlist::Face* aFace,
+ const mozilla::fontlist::Family* aFamily) override;
+
+ void ReadFaceNamesForFamily(mozilla::fontlist::Family* aFamily,
+ bool aNeedFullnamePostscriptNames)
+ MOZ_REQUIRES(mLock) override;
+
+ bool ReadFaceNames(const mozilla::fontlist::Family* aFamily,
+ const mozilla::fontlist::Face* aFace, nsCString& aPSName,
+ nsCString& aFullName) override;
+
+ void GetFacesInitDataForFamily(
+ const mozilla::fontlist::Family* aFamily,
+ nsTArray<mozilla::fontlist::Face::InitData>& aFaces,
+ bool aLoadCmaps) const override;
+
+ gfxFontEntry* LookupLocalFont(nsPresContext* aPresContext,
+ const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry) override;
+
+ gfxFontEntry* MakePlatformFont(const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry,
+ const uint8_t* aFontData,
+ uint32_t aLength) override;
+
+ IDWriteGdiInterop* GetGDIInterop() { return mGDIInterop; }
+ bool UseGDIFontTableAccess() const;
+
+ bool FindAndAddFamiliesLocked(
+ nsPresContext* aPresContext, mozilla::StyleGenericFontFamily aGeneric,
+ const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput,
+ FindFamiliesFlags aFlags, gfxFontStyle* aStyle = nullptr,
+ nsAtom* aLanguage = nullptr, gfxFloat aDevToCssSize = 1.0)
+ MOZ_REQUIRES(mLock) override;
+
+ gfxFloat GetForceGDIClassicMaxFontSize() {
+ return mForceGDIClassicMaxFontSize;
+ }
+
+ virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const;
+ virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const;
+
+ protected:
+ FontFamily GetDefaultFontForPlatform(nsPresContext* aPresContext,
+ const gfxFontStyle* aStyle,
+ nsAtom* aLanguage = nullptr)
+ MOZ_REQUIRES(mLock) override;
+
+ // attempt to use platform-specific fallback for the given character,
+ // return null if no usable result found
+ gfxFontEntry* PlatformGlobalFontFallback(nsPresContext* aPresContext,
+ const uint32_t aCh,
+ Script aRunScript,
+ const gfxFontStyle* aMatchStyle,
+ FontFamily& aMatchedFamily)
+ MOZ_REQUIRES(mLock) override;
+
+ private:
+ friend class gfxDWriteFontFamily;
+
+ nsresult GetFontSubstitutes() MOZ_REQUIRES(mLock);
+
+ void GetDirectWriteSubstitutes() MOZ_REQUIRES(mLock);
+
+ virtual bool UsesSystemFallback() { return true; }
+
+ void GetFontsFromCollection(IDWriteFontCollection* aCollection)
+ MOZ_REQUIRES(mLock);
+
+ void AppendFamiliesFromCollection(
+ IDWriteFontCollection* aCollection,
+ nsTArray<mozilla::fontlist::Family::InitData>& aFamilies,
+ const nsTArray<nsCString>* aForceClassicFams = nullptr)
+ MOZ_REQUIRES(mLock);
+
+#ifdef MOZ_BUNDLED_FONTS
+ already_AddRefed<IDWriteFontCollection> CreateBundledFontsCollection(
+ IDWriteFactory* aFactory);
+#endif
+
+ /**
+ * Fonts listed in the registry as substitutes but for which no actual
+ * font family is found.
+ */
+ nsTArray<nsCString> mNonExistingFonts;
+
+ /**
+ * Table of font substitutes, we grab this from the registry to get
+ * alternative font names.
+ */
+ FontFamilyTable mFontSubstitutes;
+ nsClassHashtable<nsCStringHashKey, nsCString> mSubstitutions;
+
+ virtual already_AddRefed<FontInfoData> CreateFontInfoData();
+
+ gfxFloat mForceGDIClassicMaxFontSize;
+
+ // whether to use GDI font table access routines
+ bool mGDIFontTableAccess;
+ RefPtr<IDWriteGdiInterop> mGDIInterop;
+
+ RefPtr<DWriteFontFallbackRenderer> mFallbackRenderer;
+ RefPtr<IDWriteTextFormat> mFallbackFormat;
+
+ RefPtr<IDWriteFontCollection> mSystemFonts;
+#ifdef MOZ_BUNDLED_FONTS
+ RefPtr<IDWriteFontCollection> mBundledFonts;
+#endif
+};
+
+#endif /* GFX_DWRITEFONTLIST_H */
diff --git a/gfx/thebes/gfxDWriteFonts.cpp b/gfx/thebes/gfxDWriteFonts.cpp
new file mode 100644
index 0000000000..fa6bc48cf9
--- /dev/null
+++ b/gfx/thebes/gfxDWriteFonts.cpp
@@ -0,0 +1,847 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxDWriteFonts.h"
+
+#include <algorithm>
+#include "gfxDWriteFontList.h"
+#include "gfxContext.h"
+#include "gfxHarfBuzzShaper.h"
+#include "gfxTextRun.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DWriteSettings.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/Preferences.h"
+
+#include "harfbuzz/hb.h"
+#include "mozilla/FontPropertyTypes.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+// Code to determine whether Windows is set to use ClearType font smoothing;
+// based on private functions in cairo-win32-font.c
+
+#ifndef SPI_GETFONTSMOOTHINGTYPE
+# define SPI_GETFONTSMOOTHINGTYPE 0x200a
+#endif
+#ifndef FE_FONTSMOOTHINGCLEARTYPE
+# define FE_FONTSMOOTHINGCLEARTYPE 2
+#endif
+
+// Cleartype can be dynamically enabled/disabled, so we have to allow for
+// dynamically updating it.
+static BYTE GetSystemTextQuality() {
+ BOOL font_smoothing;
+ UINT smoothing_type;
+
+ if (!SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &font_smoothing, 0)) {
+ return DEFAULT_QUALITY;
+ }
+
+ if (font_smoothing) {
+ if (!SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, &smoothing_type,
+ 0)) {
+ return DEFAULT_QUALITY;
+ }
+
+ if (smoothing_type == FE_FONTSMOOTHINGCLEARTYPE) {
+ return CLEARTYPE_QUALITY;
+ }
+
+ return ANTIALIASED_QUALITY;
+ }
+
+ return DEFAULT_QUALITY;
+}
+
+#ifndef SPI_GETFONTSMOOTHINGCONTRAST
+# define SPI_GETFONTSMOOTHINGCONTRAST 0x200c
+#endif
+
+// "Retrieves a contrast value that is used in ClearType smoothing. Valid
+// contrast values are from 1000 to 2200. The default value is 1400."
+static FLOAT GetSystemGDIGamma() {
+ UINT value = 0;
+ if (!SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, &value, 0) ||
+ value < 1000 || value > 2200) {
+ value = 1400;
+ }
+ return value / 1000.0f;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// gfxDWriteFont
+gfxDWriteFont::gfxDWriteFont(const RefPtr<UnscaledFontDWrite>& aUnscaledFont,
+ gfxFontEntry* aFontEntry,
+ const gfxFontStyle* aFontStyle,
+ RefPtr<IDWriteFontFace> aFontFace,
+ AntialiasOption anAAOption)
+ : gfxFont(aUnscaledFont, aFontEntry, aFontStyle, anAAOption),
+ mFontFace(aFontFace ? aFontFace : aUnscaledFont->GetFontFace()),
+ mUseSubpixelPositions(false),
+ mAllowManualShowGlyphs(true),
+ mAzureScaledFontUsedClearType(false) {
+ // If the IDWriteFontFace1 interface is available, we can use that for
+ // faster glyph width retrieval.
+ mFontFace->QueryInterface(__uuidof(IDWriteFontFace1),
+ (void**)getter_AddRefs(mFontFace1));
+ // If a fake-bold effect is needed, determine whether we're using DWrite's
+ // "simulation" or applying our multi-strike "synthetic bold".
+ if (aFontStyle->NeedsSyntheticBold(aFontEntry)) {
+ switch (StaticPrefs::gfx_font_rendering_directwrite_bold_simulation()) {
+ case 0: // never use the DWrite simulation
+ mApplySyntheticBold = true;
+ break;
+ case 1: // use DWrite simulation for installed fonts but not webfonts
+ mApplySyntheticBold = aFontEntry->mIsDataUserFont;
+ break;
+ default: // always use DWrite bold simulation
+ // the flag is initialized to false in gfxFont
+ break;
+ }
+ }
+ ComputeMetrics(anAAOption);
+}
+
+gfxDWriteFont::~gfxDWriteFont() {
+ if (auto* scaledFont = mAzureScaledFontGDI.exchange(nullptr)) {
+ scaledFont->Release();
+ }
+}
+
+/* static */
+bool gfxDWriteFont::InitDWriteSupport() {
+ if (!Factory::EnsureDWriteFactory()) {
+ return false;
+ }
+
+ if (XRE_IsParentProcess()) {
+ UpdateSystemTextVars();
+ } else {
+ // UpdateClearTypeVars doesn't update the vars in non parent processes, but
+ // it does set sForceGDIClassicEnabled so we still need to call it.
+ UpdateClearTypeVars();
+ }
+ DWriteSettings::Initialize();
+
+ return true;
+}
+
+/* static */
+void gfxDWriteFont::UpdateSystemTextVars() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (!gfxVars::IsInitialized()) {
+ return;
+ }
+
+ BYTE newQuality = GetSystemTextQuality();
+ if (gfxVars::SystemTextQuality() != newQuality) {
+ gfxVars::SetSystemTextQuality(newQuality);
+ }
+
+ FLOAT newGDIGamma = GetSystemGDIGamma();
+ if (gfxVars::SystemGDIGamma() != newGDIGamma) {
+ gfxVars::SetSystemGDIGamma(newGDIGamma);
+ }
+
+ UpdateClearTypeVars();
+}
+
+void gfxDWriteFont::SystemTextQualityChanged() {
+ // If ClearType status has changed, update our value,
+ Factory::SetSystemTextQuality(gfxVars::SystemTextQuality());
+ // flush cached stuff that depended on the old setting, and force
+ // reflow everywhere to ensure we are using correct glyph metrics.
+ gfxPlatform::FlushFontAndWordCaches();
+ gfxPlatform::ForceGlobalReflow(gfxPlatform::NeedsReframe::No);
+}
+
+mozilla::Atomic<bool> gfxDWriteFont::sForceGDIClassicEnabled{true};
+
+/* static */
+void gfxDWriteFont::UpdateClearTypeVars() {
+ // We don't force GDI classic if the cleartype rendering mode pref is set to
+ // something valid.
+ int32_t renderingModePref =
+ Preferences::GetInt(GFX_CLEARTYPE_PARAMS_MODE, -1);
+ if (renderingModePref < 0 || renderingModePref > 5) {
+ renderingModePref = -1;
+ }
+ sForceGDIClassicEnabled = (renderingModePref == -1);
+
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ if (!Factory::GetDWriteFactory()) {
+ return;
+ }
+
+ // First set sensible hard coded defaults.
+ float clearTypeLevel = 1.0f;
+ float enhancedContrast = 1.0f;
+ float gamma = 2.2f;
+ int pixelGeometry = DWRITE_PIXEL_GEOMETRY_RGB;
+ int renderingMode = DWRITE_RENDERING_MODE_DEFAULT;
+
+ // Override these from DWrite function if available.
+ RefPtr<IDWriteRenderingParams> defaultRenderingParams;
+ HRESULT hr = Factory::GetDWriteFactory()->CreateRenderingParams(
+ getter_AddRefs(defaultRenderingParams));
+ if (SUCCEEDED(hr) && defaultRenderingParams) {
+ clearTypeLevel = defaultRenderingParams->GetClearTypeLevel();
+
+ // For enhanced contrast, we only use the default if the user has set it
+ // in the registry (by using the ClearType Tuner).
+ // XXXbobowen it seems slightly odd that we do this and only for enhanced
+ // contrast, but this reproduces previous functionality from
+ // gfxWindowsPlatform::SetupClearTypeParams.
+ HKEY hKey;
+ LONG res = RegOpenKeyExW(DISPLAY1_REGISTRY_KEY, 0, KEY_READ, &hKey);
+ if (res == ERROR_SUCCESS) {
+ res = RegQueryValueExW(hKey, ENHANCED_CONTRAST_VALUE_NAME, nullptr,
+ nullptr, nullptr, nullptr);
+ if (res == ERROR_SUCCESS) {
+ enhancedContrast = defaultRenderingParams->GetEnhancedContrast();
+ }
+ RegCloseKey(hKey);
+ }
+
+ gamma = defaultRenderingParams->GetGamma();
+ pixelGeometry = defaultRenderingParams->GetPixelGeometry();
+ renderingMode = defaultRenderingParams->GetRenderingMode();
+ } else {
+ gfxWarning() << "Failed to create default rendering params";
+ }
+
+ // Finally override from prefs if valid values are set. If ClearType is
+ // turned off we just use the default params, this reproduces the previous
+ // functionality that was spread across gfxDWriteFont::GetScaledFont and
+ // gfxWindowsPlatform::SetupClearTypeParams, but it seems odd because the
+ // default params will still be the ClearType ones although we won't use the
+ // anti-alias for ClearType because of GetSystemDefaultAAMode.
+ if (gfxVars::SystemTextQuality() == CLEARTYPE_QUALITY) {
+ int32_t prefInt = Preferences::GetInt(GFX_CLEARTYPE_PARAMS_LEVEL, -1);
+ if (prefInt >= 0 && prefInt <= 100) {
+ clearTypeLevel = float(prefInt / 100.0);
+ }
+
+ prefInt = Preferences::GetInt(GFX_CLEARTYPE_PARAMS_CONTRAST, -1);
+ if (prefInt >= 0 && prefInt <= 1000) {
+ enhancedContrast = float(prefInt / 100.0);
+ }
+
+ prefInt = Preferences::GetInt(GFX_CLEARTYPE_PARAMS_GAMMA, -1);
+ if (prefInt >= 1000 && prefInt <= 2200) {
+ gamma = float(prefInt / 1000.0);
+ }
+
+ prefInt = Preferences::GetInt(GFX_CLEARTYPE_PARAMS_STRUCTURE, -1);
+ if (prefInt >= 0 && prefInt <= 2) {
+ pixelGeometry = prefInt;
+ }
+
+ // renderingModePref is retrieved and validated above.
+ if (renderingModePref != -1) {
+ renderingMode = renderingModePref;
+ }
+ }
+
+ if (gfxVars::SystemTextClearTypeLevel() != clearTypeLevel) {
+ gfxVars::SetSystemTextClearTypeLevel(clearTypeLevel);
+ }
+
+ if (gfxVars::SystemTextEnhancedContrast() != enhancedContrast) {
+ gfxVars::SetSystemTextEnhancedContrast(enhancedContrast);
+ }
+
+ if (gfxVars::SystemTextGamma() != gamma) {
+ gfxVars::SetSystemTextGamma(gamma);
+ }
+
+ if (gfxVars::SystemTextPixelGeometry() != pixelGeometry) {
+ gfxVars::SetSystemTextPixelGeometry(pixelGeometry);
+ }
+
+ if (gfxVars::SystemTextRenderingMode() != renderingMode) {
+ gfxVars::SetSystemTextRenderingMode(renderingMode);
+ }
+
+ // Set cairo dwrite params in the parent process where it might still be
+ // needed for printing. We use the validated pref int directly for rendering
+ // mode, because a negative (i.e. not set) rendering mode is also used for
+ // deciding on forcing GDI in cairo.
+ cairo_dwrite_set_cleartype_params(gamma, enhancedContrast, clearTypeLevel,
+ pixelGeometry, renderingModePref);
+}
+
+gfxFont* gfxDWriteFont::CopyWithAntialiasOption(
+ AntialiasOption anAAOption) const {
+ auto entry = static_cast<gfxDWriteFontEntry*>(mFontEntry.get());
+ RefPtr<UnscaledFontDWrite> unscaledFont =
+ static_cast<UnscaledFontDWrite*>(mUnscaledFont.get());
+ return new gfxDWriteFont(unscaledFont, entry, &mStyle, mFontFace, anAAOption);
+}
+
+bool gfxDWriteFont::GetFakeMetricsForArialBlack(
+ DWRITE_FONT_METRICS* aFontMetrics) {
+ gfxFontStyle style(mStyle);
+ style.weight = FontWeight::FromInt(700);
+
+ gfxFontEntry* fe = gfxPlatformFontList::PlatformFontList()->FindFontForFamily(
+ nullptr, "Arial"_ns, &style);
+ if (!fe || fe == mFontEntry) {
+ return false;
+ }
+
+ RefPtr<gfxFont> font = fe->FindOrMakeFont(&style);
+ gfxDWriteFont* dwFont = static_cast<gfxDWriteFont*>(font.get());
+ dwFont->mFontFace->GetMetrics(aFontMetrics);
+
+ return true;
+}
+
+void gfxDWriteFont::ComputeMetrics(AntialiasOption anAAOption) {
+ ::memset(&mMetrics, 0, sizeof(mMetrics));
+
+ DWRITE_FONT_METRICS fontMetrics;
+ if (!(mFontEntry->Weight().Min() == FontWeight::FromInt(900) &&
+ mFontEntry->Weight().Max() == FontWeight::FromInt(900) &&
+ !mFontEntry->IsUserFont() &&
+ mFontEntry->Name().EqualsLiteral("Arial Black") &&
+ GetFakeMetricsForArialBlack(&fontMetrics))) {
+ mFontFace->GetMetrics(&fontMetrics);
+ }
+
+ if (GetAdjustedSize() > 0.0 && mStyle.sizeAdjust >= 0.0 &&
+ FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) !=
+ FontSizeAdjust::Tag::None) {
+ // For accurate measurement during the font-size-adjust computations;
+ // these may be reset later according to the adjusted size.
+ mUseSubpixelPositions = true;
+ mFUnitsConvFactor = float(mAdjustedSize / fontMetrics.designUnitsPerEm);
+ gfxFloat aspect;
+ switch (FontSizeAdjust::Tag(mStyle.sizeAdjustBasis)) {
+ default:
+ MOZ_ASSERT_UNREACHABLE("unhandled sizeAdjustBasis?");
+ aspect = 0.0;
+ break;
+ case FontSizeAdjust::Tag::ExHeight:
+ aspect = (gfxFloat)fontMetrics.xHeight / fontMetrics.designUnitsPerEm;
+ break;
+ case FontSizeAdjust::Tag::CapHeight:
+ aspect = (gfxFloat)fontMetrics.capHeight / fontMetrics.designUnitsPerEm;
+ break;
+ case FontSizeAdjust::Tag::ChWidth: {
+ gfxFloat advance = GetCharAdvance('0');
+ aspect = advance > 0.0 ? advance / mAdjustedSize : 0.5;
+ break;
+ }
+ case FontSizeAdjust::Tag::IcWidth:
+ case FontSizeAdjust::Tag::IcHeight: {
+ bool vertical = FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) ==
+ FontSizeAdjust::Tag::IcHeight;
+ gfxFloat advance = GetCharAdvance(kWaterIdeograph, vertical);
+ aspect = advance > 0.0 ? advance / mAdjustedSize : 1.0;
+ break;
+ }
+ }
+ if (aspect > 0.0) {
+ // If we created a shaper above (to measure glyphs), discard it so we
+ // get a new one for the adjusted scaling.
+ delete mHarfBuzzShaper.exchange(nullptr);
+ mAdjustedSize = mStyle.GetAdjustedSize(aspect);
+ }
+ }
+
+ // Update now that we've adjusted the size if necessary.
+ mFUnitsConvFactor = float(mAdjustedSize / fontMetrics.designUnitsPerEm);
+
+ // Note that GetMeasuringMode depends on mAdjustedSize
+ if ((anAAOption == gfxFont::kAntialiasDefault && UsingClearType() &&
+ GetMeasuringMode() == DWRITE_MEASURING_MODE_NATURAL) ||
+ anAAOption == gfxFont::kAntialiasSubpixel) {
+ mUseSubpixelPositions = true;
+ // note that this may be reset to FALSE if we determine that a bitmap
+ // strike is going to be used
+ } else {
+ mUseSubpixelPositions = false;
+ }
+
+ gfxDWriteFontEntry* fe = static_cast<gfxDWriteFontEntry*>(mFontEntry.get());
+ if (fe->IsCJKFont() && HasBitmapStrikeForSize(NS_lround(mAdjustedSize))) {
+ mAdjustedSize = NS_lround(mAdjustedSize);
+ mUseSubpixelPositions = false;
+ // if we have bitmaps, we need to tell Cairo NOT to use subpixel AA,
+ // to avoid the manual-subpixel codepath in cairo-d2d-surface.cpp
+ // which fails to render bitmap glyphs (see bug 626299).
+ // This option will be passed to the cairo_dwrite_scaled_font_t
+ // after creation.
+ mAllowManualShowGlyphs = false;
+ }
+
+ mMetrics.xHeight = fontMetrics.xHeight * mFUnitsConvFactor;
+ mMetrics.capHeight = fontMetrics.capHeight * mFUnitsConvFactor;
+
+ mMetrics.maxAscent = round(fontMetrics.ascent * mFUnitsConvFactor);
+ mMetrics.maxDescent = round(fontMetrics.descent * mFUnitsConvFactor);
+ mMetrics.maxHeight = mMetrics.maxAscent + mMetrics.maxDescent;
+
+ mMetrics.emHeight = mAdjustedSize;
+ mMetrics.emAscent =
+ mMetrics.emHeight * mMetrics.maxAscent / mMetrics.maxHeight;
+ mMetrics.emDescent = mMetrics.emHeight - mMetrics.emAscent;
+
+ mMetrics.maxAdvance = mAdjustedSize;
+
+ // try to get the true maxAdvance value from 'hhea'
+ gfxFontEntry::AutoTable hheaTable(GetFontEntry(),
+ TRUETYPE_TAG('h', 'h', 'e', 'a'));
+ if (hheaTable) {
+ uint32_t len;
+ const MetricsHeader* hhea = reinterpret_cast<const MetricsHeader*>(
+ hb_blob_get_data(hheaTable, &len));
+ if (len >= sizeof(MetricsHeader)) {
+ mMetrics.maxAdvance = uint16_t(hhea->advanceWidthMax) * mFUnitsConvFactor;
+ }
+ }
+
+ mMetrics.internalLeading =
+ std::max(mMetrics.maxHeight - mMetrics.emHeight, 0.0);
+ mMetrics.externalLeading = ceil(fontMetrics.lineGap * mFUnitsConvFactor);
+
+ UINT32 ucs = L' ';
+ UINT16 glyph;
+ if (SUCCEEDED(mFontFace->GetGlyphIndices(&ucs, 1, &glyph)) && glyph != 0) {
+ mSpaceGlyph = glyph;
+ mMetrics.spaceWidth = MeasureGlyphWidth(glyph);
+ } else {
+ mMetrics.spaceWidth = 0;
+ }
+
+ // try to get aveCharWidth from the OS/2 table, fall back to measuring 'x'
+ // if the table is not available or if using hinted/pixel-snapped widths
+ if (mUseSubpixelPositions) {
+ mMetrics.aveCharWidth = 0;
+ gfxFontEntry::AutoTable os2Table(GetFontEntry(),
+ TRUETYPE_TAG('O', 'S', '/', '2'));
+ if (os2Table) {
+ uint32_t len;
+ const OS2Table* os2 =
+ reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len));
+ if (len >= 4) {
+ // Not checking against sizeof(mozilla::OS2Table) here because older
+ // versions of the table have different sizes; we only need the first
+ // two 16-bit fields here.
+ mMetrics.aveCharWidth = int16_t(os2->xAvgCharWidth) * mFUnitsConvFactor;
+ }
+ }
+ }
+
+ if (mMetrics.aveCharWidth < 1) {
+ mMetrics.aveCharWidth = GetCharAdvance('x');
+ if (mMetrics.aveCharWidth < 1) {
+ // Let's just assume the X is square.
+ mMetrics.aveCharWidth = fontMetrics.xHeight * mFUnitsConvFactor;
+ }
+ }
+
+ mMetrics.zeroWidth = GetCharAdvance('0');
+
+ mMetrics.ideographicWidth = GetCharAdvance(kWaterIdeograph);
+
+ mMetrics.underlineOffset = fontMetrics.underlinePosition * mFUnitsConvFactor;
+ mMetrics.underlineSize = fontMetrics.underlineThickness * mFUnitsConvFactor;
+ mMetrics.strikeoutOffset =
+ fontMetrics.strikethroughPosition * mFUnitsConvFactor;
+ mMetrics.strikeoutSize =
+ fontMetrics.strikethroughThickness * mFUnitsConvFactor;
+
+ SanitizeMetrics(&mMetrics, GetFontEntry()->mIsBadUnderlineFont);
+
+ if (ApplySyntheticBold()) {
+ auto delta = GetSyntheticBoldOffset();
+ mMetrics.spaceWidth += delta;
+ mMetrics.aveCharWidth += delta;
+ mMetrics.maxAdvance += delta;
+ if (mMetrics.zeroWidth > 0) {
+ mMetrics.zeroWidth += delta;
+ }
+ if (mMetrics.ideographicWidth > 0) {
+ mMetrics.ideographicWidth += delta;
+ }
+ }
+
+#if 0
+ printf("Font: %p (%s) size: %f\n", this,
+ NS_ConvertUTF16toUTF8(GetName()).get(), mStyle.size);
+ printf(" emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent);
+ printf(" maxAscent: %f maxDescent: %f maxAdvance: %f\n", mMetrics.maxAscent, mMetrics.maxDescent, mMetrics.maxAdvance);
+ printf(" internalLeading: %f externalLeading: %f\n", mMetrics.internalLeading, mMetrics.externalLeading);
+ printf(" spaceWidth: %f aveCharWidth: %f zeroWidth: %f\n",
+ mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.zeroWidth);
+ printf(" xHeight: %f capHeight: %f\n", mMetrics.xHeight, mMetrics.capHeight);
+ printf(" uOff: %f uSize: %f stOff: %f stSize: %f\n",
+ mMetrics.underlineOffset, mMetrics.underlineSize, mMetrics.strikeoutOffset, mMetrics.strikeoutSize);
+#endif
+}
+
+using namespace mozilla; // for AutoSwap_* types
+
+struct EBLCHeader {
+ AutoSwap_PRUint32 version;
+ AutoSwap_PRUint32 numSizes;
+};
+
+struct SbitLineMetrics {
+ int8_t ascender;
+ int8_t descender;
+ uint8_t widthMax;
+ int8_t caretSlopeNumerator;
+ int8_t caretSlopeDenominator;
+ int8_t caretOffset;
+ int8_t minOriginSB;
+ int8_t minAdvanceSB;
+ int8_t maxBeforeBL;
+ int8_t minAfterBL;
+ int8_t pad1;
+ int8_t pad2;
+};
+
+struct BitmapSizeTable {
+ AutoSwap_PRUint32 indexSubTableArrayOffset;
+ AutoSwap_PRUint32 indexTablesSize;
+ AutoSwap_PRUint32 numberOfIndexSubTables;
+ AutoSwap_PRUint32 colorRef;
+ SbitLineMetrics hori;
+ SbitLineMetrics vert;
+ AutoSwap_PRUint16 startGlyphIndex;
+ AutoSwap_PRUint16 endGlyphIndex;
+ uint8_t ppemX;
+ uint8_t ppemY;
+ uint8_t bitDepth;
+ uint8_t flags;
+};
+
+typedef EBLCHeader EBSCHeader;
+
+struct BitmapScaleTable {
+ SbitLineMetrics hori;
+ SbitLineMetrics vert;
+ uint8_t ppemX;
+ uint8_t ppemY;
+ uint8_t substitutePpemX;
+ uint8_t substitutePpemY;
+};
+
+bool gfxDWriteFont::HasBitmapStrikeForSize(uint32_t aSize) {
+ uint8_t* tableData;
+ uint32_t len;
+ void* tableContext;
+ BOOL exists;
+ HRESULT hr = mFontFace->TryGetFontTable(
+ DWRITE_MAKE_OPENTYPE_TAG('E', 'B', 'L', 'C'), (const void**)&tableData,
+ &len, &tableContext, &exists);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ bool hasStrike = false;
+ // not really a loop, but this lets us use 'break' to skip out of the block
+ // as soon as we know the answer, and skips it altogether if the table is
+ // not present
+ while (exists) {
+ if (len < sizeof(EBLCHeader)) {
+ break;
+ }
+ const EBLCHeader* hdr = reinterpret_cast<const EBLCHeader*>(tableData);
+ if (hdr->version != 0x00020000) {
+ break;
+ }
+ uint32_t numSizes = hdr->numSizes;
+ if (numSizes > 0xffff) { // sanity-check, prevent overflow below
+ break;
+ }
+ if (len < sizeof(EBLCHeader) + numSizes * sizeof(BitmapSizeTable)) {
+ break;
+ }
+ const BitmapSizeTable* sizeTable =
+ reinterpret_cast<const BitmapSizeTable*>(hdr + 1);
+ for (uint32_t i = 0; i < numSizes; ++i, ++sizeTable) {
+ if (sizeTable->ppemX == aSize && sizeTable->ppemY == aSize) {
+ // we ignore a strike that contains fewer than 4 glyphs,
+ // as that probably indicates a font such as Courier New
+ // that provides bitmaps ONLY for the "shading" characters
+ // U+2591..2593
+ hasStrike = (uint16_t(sizeTable->endGlyphIndex) >=
+ uint16_t(sizeTable->startGlyphIndex) + 3);
+ break;
+ }
+ }
+ // if we reach here, we didn't find a strike; unconditionally break
+ // out of the while-loop block
+ break;
+ }
+ mFontFace->ReleaseFontTable(tableContext);
+
+ if (hasStrike) {
+ return true;
+ }
+
+ // if we didn't find a real strike, check if the font calls for scaling
+ // another bitmap to this size
+ hr = mFontFace->TryGetFontTable(DWRITE_MAKE_OPENTYPE_TAG('E', 'B', 'S', 'C'),
+ (const void**)&tableData, &len, &tableContext,
+ &exists);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ while (exists) {
+ if (len < sizeof(EBSCHeader)) {
+ break;
+ }
+ const EBSCHeader* hdr = reinterpret_cast<const EBSCHeader*>(tableData);
+ if (hdr->version != 0x00020000) {
+ break;
+ }
+ uint32_t numSizes = hdr->numSizes;
+ if (numSizes > 0xffff) {
+ break;
+ }
+ if (len < sizeof(EBSCHeader) + numSizes * sizeof(BitmapScaleTable)) {
+ break;
+ }
+ const BitmapScaleTable* scaleTable =
+ reinterpret_cast<const BitmapScaleTable*>(hdr + 1);
+ for (uint32_t i = 0; i < numSizes; ++i, ++scaleTable) {
+ if (scaleTable->ppemX == aSize && scaleTable->ppemY == aSize) {
+ hasStrike = true;
+ break;
+ }
+ }
+ break;
+ }
+ mFontFace->ReleaseFontTable(tableContext);
+
+ return hasStrike;
+}
+
+bool gfxDWriteFont::IsValid() const { return mFontFace != nullptr; }
+
+IDWriteFontFace* gfxDWriteFont::GetFontFace() { return mFontFace.get(); }
+
+gfxFont::RunMetrics gfxDWriteFont::Measure(const gfxTextRun* aTextRun,
+ uint32_t aStart, uint32_t aEnd,
+ BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget,
+ Spacing* aSpacing,
+ gfx::ShapedTextFlags aOrientation) {
+ gfxFont::RunMetrics metrics =
+ gfxFont::Measure(aTextRun, aStart, aEnd, aBoundingBoxType, aRefDrawTarget,
+ aSpacing, aOrientation);
+
+ // if aBoundingBoxType is LOOSE_INK_EXTENTS
+ // and the underlying cairo font may be antialiased,
+ // we can't trust Windows to have considered all the pixels
+ // so we need to add "padding" to the bounds.
+ // (see bugs 475968, 439831, compare also bug 445087)
+ if (aBoundingBoxType == LOOSE_INK_EXTENTS &&
+ mAntialiasOption != kAntialiasNone &&
+ GetMeasuringMode() == DWRITE_MEASURING_MODE_GDI_CLASSIC &&
+ metrics.mBoundingBox.Width() > 0) {
+ metrics.mBoundingBox.MoveByX(-aTextRun->GetAppUnitsPerDevUnit());
+ metrics.mBoundingBox.SetWidth(metrics.mBoundingBox.Width() +
+ aTextRun->GetAppUnitsPerDevUnit() * 3);
+ }
+
+ return metrics;
+}
+
+bool gfxDWriteFont::ProvidesGlyphWidths() const {
+ return !mUseSubpixelPositions ||
+ (mFontFace->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD) ||
+ ((gfxDWriteFontEntry*)(GetFontEntry()))->HasVariations();
+}
+
+int32_t gfxDWriteFont::GetGlyphWidth(uint16_t aGID) {
+ if (!mGlyphWidths) {
+ mGlyphWidths = MakeUnique<nsTHashMap<nsUint32HashKey, int32_t>>(128);
+ }
+
+ return mGlyphWidths->LookupOrInsertWith(
+ aGID, [&] { return NS_lround(MeasureGlyphWidth(aGID) * 65536.0); });
+}
+
+bool gfxDWriteFont::GetForceGDIClassic() const {
+ return sForceGDIClassicEnabled &&
+ static_cast<gfxDWriteFontEntry*>(mFontEntry.get())
+ ->GetForceGDIClassic() &&
+ GetAdjustedSize() <= gfxDWriteFontList::PlatformFontList()
+ ->GetForceGDIClassicMaxFontSize();
+}
+
+DWRITE_MEASURING_MODE
+gfxDWriteFont::GetMeasuringMode() const {
+ return DWriteSettings::Get(GetForceGDIClassic()).MeasuringMode();
+}
+
+gfxFloat gfxDWriteFont::MeasureGlyphWidth(uint16_t aGlyph) {
+ MOZ_SEH_TRY {
+ HRESULT hr;
+ if (mFontFace1) {
+ int32_t advance;
+ if (mUseSubpixelPositions) {
+ hr = mFontFace1->GetDesignGlyphAdvances(1, &aGlyph, &advance, FALSE);
+ if (SUCCEEDED(hr)) {
+ return advance * mFUnitsConvFactor;
+ }
+ } else {
+ hr = mFontFace1->GetGdiCompatibleGlyphAdvances(
+ FLOAT(mAdjustedSize), 1.0f, nullptr,
+ GetMeasuringMode() == DWRITE_MEASURING_MODE_GDI_NATURAL, FALSE, 1,
+ &aGlyph, &advance);
+ if (SUCCEEDED(hr)) {
+ return NS_lround(advance * mFUnitsConvFactor);
+ }
+ }
+ } else {
+ DWRITE_GLYPH_METRICS metrics;
+ if (mUseSubpixelPositions) {
+ hr = mFontFace->GetDesignGlyphMetrics(&aGlyph, 1, &metrics, FALSE);
+ if (SUCCEEDED(hr)) {
+ return metrics.advanceWidth * mFUnitsConvFactor;
+ }
+ } else {
+ hr = mFontFace->GetGdiCompatibleGlyphMetrics(
+ FLOAT(mAdjustedSize), 1.0f, nullptr,
+ GetMeasuringMode() == DWRITE_MEASURING_MODE_GDI_NATURAL, &aGlyph, 1,
+ &metrics, FALSE);
+ if (SUCCEEDED(hr)) {
+ return NS_lround(metrics.advanceWidth * mFUnitsConvFactor);
+ }
+ }
+ }
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ // Exception (e.g. disk i/o error) occurred when DirectWrite tried to use
+ // the font resource; possibly a failing drive or similar hardware issue.
+ // Mark the font as invalid, and wipe the fontEntry's charmap so that font
+ // selection will skip it; we'll use a fallback font instead.
+ mIsValid = false;
+ GetFontEntry()->mCharacterMap = new gfxCharacterMap();
+ GetFontEntry()->mShmemCharacterMap = nullptr;
+ gfxCriticalError() << "Exception occurred measuring glyph width for "
+ << GetFontEntry()->Name().get();
+ }
+ return 0.0;
+}
+
+bool gfxDWriteFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds,
+ bool aTight) {
+ MOZ_SEH_TRY {
+ DWRITE_GLYPH_METRICS m;
+ HRESULT hr = mFontFace->GetDesignGlyphMetrics(&aGID, 1, &m, FALSE);
+ if (FAILED(hr)) {
+ return false;
+ }
+ gfxRect bounds(m.leftSideBearing, m.topSideBearing - m.verticalOriginY,
+ m.advanceWidth - m.leftSideBearing - m.rightSideBearing,
+ m.advanceHeight - m.topSideBearing - m.bottomSideBearing);
+ bounds.Scale(mFUnitsConvFactor);
+ // GetDesignGlyphMetrics returns 'ideal' glyph metrics, we need to pad to
+ // account for antialiasing.
+ if (!aTight && !aBounds->IsEmpty()) {
+ bounds.Inflate(1.0, 0.0);
+ }
+ *aBounds = bounds;
+ return true;
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ // Exception (e.g. disk i/o error) occurred when DirectWrite tried to use
+ // the font resource; possibly a failing drive or similar hardware issue.
+ // Mark the font as invalid, and wipe the fontEntry's charmap so that font
+ // selection will skip it; we'll use a fallback font instead.
+ mIsValid = false;
+ GetFontEntry()->mCharacterMap = new gfxCharacterMap();
+ GetFontEntry()->mShmemCharacterMap = nullptr;
+ gfxCriticalError() << "Exception occurred measuring glyph bounds for "
+ << GetFontEntry()->Name().get();
+ }
+ return false;
+}
+
+void gfxDWriteFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const {
+ gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+ if (mGlyphWidths) {
+ aSizes->mFontInstances +=
+ mGlyphWidths->ShallowSizeOfIncludingThis(aMallocSizeOf);
+ }
+}
+
+void gfxDWriteFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const {
+ aSizes->mFontInstances += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
+
+already_AddRefed<ScaledFont> gfxDWriteFont::GetScaledFont(
+ const TextRunDrawParams& aRunParams) {
+ bool useClearType = UsingClearType();
+ if (mAzureScaledFontUsedClearType != useClearType) {
+ if (auto* oldScaledFont = mAzureScaledFont.exchange(nullptr)) {
+ oldScaledFont->Release();
+ }
+ if (auto* oldScaledFont = mAzureScaledFontGDI.exchange(nullptr)) {
+ oldScaledFont->Release();
+ }
+ }
+ bool forceGDI = aRunParams.allowGDI && GetForceGDIClassic();
+ ScaledFont* scaledFont = forceGDI ? mAzureScaledFontGDI : mAzureScaledFont;
+ if (scaledFont) {
+ return do_AddRef(scaledFont);
+ }
+
+ gfxDWriteFontEntry* fe = static_cast<gfxDWriteFontEntry*>(mFontEntry.get());
+ bool useEmbeddedBitmap =
+ (gfxVars::SystemTextRenderingMode() == DWRITE_RENDERING_MODE_DEFAULT ||
+ forceGDI) &&
+ fe->IsCJKFont() && HasBitmapStrikeForSize(NS_lround(mAdjustedSize));
+
+ const gfxFontStyle* fontStyle = GetStyle();
+ RefPtr<ScaledFont> newScaledFont = Factory::CreateScaledFontForDWriteFont(
+ mFontFace, fontStyle, GetUnscaledFont(), GetAdjustedSize(),
+ useEmbeddedBitmap, ApplySyntheticBold(), forceGDI);
+ if (!newScaledFont) {
+ return nullptr;
+ }
+ InitializeScaledFont(newScaledFont);
+
+ if (forceGDI) {
+ if (mAzureScaledFontGDI.compareExchange(nullptr, newScaledFont.get())) {
+ Unused << newScaledFont.forget();
+ mAzureScaledFontUsedClearType = useClearType;
+ }
+ scaledFont = mAzureScaledFontGDI;
+ } else {
+ if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) {
+ Unused << newScaledFont.forget();
+ mAzureScaledFontUsedClearType = useClearType;
+ }
+ scaledFont = mAzureScaledFont;
+ }
+ return do_AddRef(scaledFont);
+}
+
+bool gfxDWriteFont::ShouldRoundXOffset(cairo_t* aCairo) const {
+ // show_glyphs is implemented on the font and so is used for all Cairo
+ // surface types; however, it may pixel-snap depending on the dwrite
+ // rendering mode
+ return GetMeasuringMode() != DWRITE_MEASURING_MODE_NATURAL;
+}
diff --git a/gfx/thebes/gfxDWriteFonts.h b/gfx/thebes/gfxDWriteFonts.h
new file mode 100644
index 0000000000..fc01aee6d9
--- /dev/null
+++ b/gfx/thebes/gfxDWriteFonts.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_WINDOWSDWRITEFONTS_H
+#define GFX_WINDOWSDWRITEFONTS_H
+
+#include "mozilla/Atomics.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/UniquePtr.h"
+#include <dwrite_1.h>
+
+#include "gfxFont.h"
+#include "gfxUserFontSet.h"
+#include "nsTHashMap.h"
+#include "nsHashKeys.h"
+
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/gfx/UnscaledFontDWrite.h"
+
+/**
+ * \brief Class representing a font face for a font entry.
+ */
+class gfxDWriteFont final : public gfxFont {
+ public:
+ gfxDWriteFont(const RefPtr<mozilla::gfx::UnscaledFontDWrite>& aUnscaledFont,
+ gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle,
+ RefPtr<IDWriteFontFace> aFontFace = nullptr,
+ AntialiasOption = kAntialiasDefault);
+
+ static bool InitDWriteSupport();
+
+ // These Update functions update gfxVars with font settings, they must only be
+ // called in the parent process.
+ static void UpdateSystemTextVars();
+ static void UpdateClearTypeVars();
+
+ static void SystemTextQualityChanged();
+
+ gfxFont* CopyWithAntialiasOption(AntialiasOption anAAOption) const override;
+
+ bool AllowSubpixelAA() const override { return mAllowManualShowGlyphs; }
+
+ bool IsValid() const;
+
+ IDWriteFontFace* GetFontFace();
+
+ /* override Measure to add padding for antialiasing */
+ RunMetrics Measure(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd,
+ BoundingBoxType aBoundingBoxType,
+ DrawTarget* aDrawTargetForTightBoundingBox,
+ Spacing* aSpacing,
+ mozilla::gfx::ShapedTextFlags aOrientation) override;
+
+ bool ProvidesGlyphWidths() const override;
+
+ int32_t GetGlyphWidth(uint16_t aGID) override;
+
+ bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override;
+
+ void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const override;
+ void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const override;
+
+ FontType GetType() const override { return FONT_TYPE_DWRITE; }
+
+ already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont(
+ const TextRunDrawParams& aRunParams) override;
+
+ bool ShouldRoundXOffset(cairo_t* aCairo) const override;
+
+ protected:
+ ~gfxDWriteFont() override;
+
+ const Metrics& GetHorizontalMetrics() const override { return mMetrics; }
+
+ bool GetFakeMetricsForArialBlack(DWRITE_FONT_METRICS* aFontMetrics);
+
+ void ComputeMetrics(AntialiasOption anAAOption);
+
+ bool HasBitmapStrikeForSize(uint32_t aSize);
+
+ gfxFloat MeasureGlyphWidth(uint16_t aGlyph);
+
+ DWRITE_MEASURING_MODE GetMeasuringMode() const;
+
+ static mozilla::Atomic<bool> sForceGDIClassicEnabled;
+ bool GetForceGDIClassic() const;
+
+ RefPtr<IDWriteFontFace> mFontFace;
+ RefPtr<IDWriteFontFace1> mFontFace1; // may be unavailable on older DWrite
+
+ Metrics mMetrics;
+
+ // cache of glyph widths in 16.16 fixed-point pixels
+ mozilla::UniquePtr<nsTHashMap<nsUint32HashKey, int32_t>> mGlyphWidths;
+
+ bool mUseSubpixelPositions;
+ bool mAllowManualShowGlyphs;
+
+ // Used to record the sUseClearType setting at the time mAzureScaledFont
+ // was set up, so we can tell if it's stale and needs to be re-created.
+ mozilla::Atomic<bool> mAzureScaledFontUsedClearType;
+
+ // Cache the GDI version of the ScaledFont so that font keys and other
+ // meta-data can remain stable even if there is thrashing between GDI and
+ // non-GDI usage.
+ mozilla::Atomic<mozilla::gfx::ScaledFont*> mAzureScaledFontGDI;
+
+ bool UsingClearType() {
+ return mozilla::gfx::gfxVars::SystemTextQuality() == CLEARTYPE_QUALITY;
+ }
+};
+
+#endif
diff --git a/gfx/thebes/gfxDrawable.cpp b/gfx/thebes/gfxDrawable.cpp
new file mode 100644
index 0000000000..de0ea1948a
--- /dev/null
+++ b/gfx/thebes/gfxDrawable.cpp
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxDrawable.h"
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+#include "gfx2DGlue.h"
+#ifdef MOZ_X11
+# include "cairo.h"
+# include "gfxXlibSurface.h"
+#endif
+#include "mozilla/gfx/Logging.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+gfxSurfaceDrawable::gfxSurfaceDrawable(SourceSurface* aSurface,
+ const IntSize aSize,
+ const gfxMatrix aTransform)
+ : gfxDrawable(aSize), mSourceSurface(aSurface), mTransform(aTransform) {
+ if (!mSourceSurface) {
+ gfxWarning() << "Creating gfxSurfaceDrawable with null SourceSurface";
+ }
+}
+
+bool gfxSurfaceDrawable::DrawWithSamplingRect(
+ DrawTarget* aDrawTarget, CompositionOp aOp, AntialiasMode aAntialiasMode,
+ const gfxRect& aFillRect, const gfxRect& aSamplingRect,
+ ExtendMode aExtendMode, const SamplingFilter aSamplingFilter,
+ gfxFloat aOpacity) {
+ if (!mSourceSurface) {
+ return true;
+ }
+
+ // When drawing with CLAMP we can expand the sampling rect to the nearest
+ // pixel without changing the result.
+ IntRect intRect =
+ IntRect::RoundOut(aSamplingRect.X(), aSamplingRect.Y(),
+ aSamplingRect.Width(), aSamplingRect.Height());
+
+ IntSize size = mSourceSurface->GetSize();
+ if (!IntRect(IntPoint(), size).Contains(intRect)) {
+ return false;
+ }
+
+ DrawInternal(aDrawTarget, aOp, aAntialiasMode, aFillRect, intRect,
+ ExtendMode::CLAMP, aSamplingFilter, aOpacity, gfxMatrix());
+ return true;
+}
+
+bool gfxSurfaceDrawable::Draw(gfxContext* aContext, const gfxRect& aFillRect,
+ ExtendMode aExtendMode,
+ const SamplingFilter aSamplingFilter,
+ gfxFloat aOpacity, const gfxMatrix& aTransform)
+
+{
+ if (!mSourceSurface) {
+ return true;
+ }
+
+ DrawInternal(aContext->GetDrawTarget(), aContext->CurrentOp(),
+ aContext->CurrentAntialiasMode(), aFillRect, IntRect(),
+ aExtendMode, aSamplingFilter, aOpacity, aTransform);
+ return true;
+}
+
+void gfxSurfaceDrawable::DrawInternal(
+ DrawTarget* aDrawTarget, CompositionOp aOp, AntialiasMode aAntialiasMode,
+ const gfxRect& aFillRect, const IntRect& aSamplingRect,
+ ExtendMode aExtendMode, const SamplingFilter aSamplingFilter,
+ gfxFloat aOpacity, const gfxMatrix& aTransform) {
+ Matrix patternTransform = ToMatrix(aTransform * mTransform);
+ patternTransform.Invert();
+
+ SurfacePattern pattern(mSourceSurface, aExtendMode, patternTransform,
+ aSamplingFilter, aSamplingRect);
+
+ Rect fillRect = ToRect(aFillRect);
+
+ if (aOp == CompositionOp::OP_SOURCE && aOpacity == 1.0) {
+ // Emulate cairo operator source which is bound by mask!
+ aDrawTarget->ClearRect(fillRect);
+ aDrawTarget->FillRect(fillRect, pattern);
+ } else {
+ aDrawTarget->FillRect(fillRect, pattern,
+ DrawOptions(aOpacity, aOp, aAntialiasMode));
+ }
+}
+
+gfxCallbackDrawable::gfxCallbackDrawable(gfxDrawingCallback* aCallback,
+ const IntSize aSize)
+ : gfxDrawable(aSize), mCallback(aCallback) {}
+
+already_AddRefed<gfxSurfaceDrawable> gfxCallbackDrawable::MakeSurfaceDrawable(
+ gfxContext* aContext, const SamplingFilter aSamplingFilter) {
+ SurfaceFormat format = gfxPlatform::GetPlatform()->Optimal2DFormatForContent(
+ gfxContentType::COLOR_ALPHA);
+ if (!aContext->GetDrawTarget()->CanCreateSimilarDrawTarget(mSize, format)) {
+ return nullptr;
+ }
+ RefPtr<DrawTarget> dt =
+ aContext->GetDrawTarget()->CreateSimilarDrawTarget(mSize, format);
+
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+
+ gfxContext ctx(dt);
+ Draw(&ctx, gfxRect(0, 0, mSize.width, mSize.height), ExtendMode::CLAMP,
+ aSamplingFilter);
+
+ RefPtr<SourceSurface> surface = dt->Snapshot();
+ if (surface) {
+ RefPtr<gfxSurfaceDrawable> drawable =
+ new gfxSurfaceDrawable(surface, mSize);
+ return drawable.forget();
+ }
+ return nullptr;
+}
+
+static bool IsRepeatingExtendMode(ExtendMode aExtendMode) {
+ switch (aExtendMode) {
+ case ExtendMode::REPEAT:
+ case ExtendMode::REPEAT_X:
+ case ExtendMode::REPEAT_Y:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool gfxCallbackDrawable::Draw(gfxContext* aContext, const gfxRect& aFillRect,
+ ExtendMode aExtendMode,
+ const SamplingFilter aSamplingFilter,
+ gfxFloat aOpacity, const gfxMatrix& aTransform) {
+ if ((IsRepeatingExtendMode(aExtendMode) || aOpacity != 1.0 ||
+ aContext->CurrentOp() != CompositionOp::OP_OVER) &&
+ !mSurfaceDrawable) {
+ mSurfaceDrawable = MakeSurfaceDrawable(aContext, aSamplingFilter);
+ }
+
+ if (mSurfaceDrawable)
+ return mSurfaceDrawable->Draw(aContext, aFillRect, aExtendMode,
+ aSamplingFilter, aOpacity, aTransform);
+
+ if (mCallback)
+ return (*mCallback)(aContext, aFillRect, aSamplingFilter, aTransform);
+
+ return false;
+}
+
+gfxPatternDrawable::gfxPatternDrawable(gfxPattern* aPattern,
+ const IntSize aSize)
+ : gfxDrawable(aSize), mPattern(aPattern) {}
+
+gfxPatternDrawable::~gfxPatternDrawable() = default;
+
+class DrawingCallbackFromDrawable : public gfxDrawingCallback {
+ public:
+ explicit DrawingCallbackFromDrawable(gfxDrawable* aDrawable)
+ : mDrawable(aDrawable) {
+ NS_ASSERTION(aDrawable, "aDrawable is null!");
+ }
+
+ virtual ~DrawingCallbackFromDrawable() = default;
+
+ bool operator()(gfxContext* aContext, const gfxRect& aFillRect,
+ const SamplingFilter aSamplingFilter,
+ const gfxMatrix& aTransform = gfxMatrix()) override {
+ return mDrawable->Draw(aContext, aFillRect, ExtendMode::CLAMP,
+ aSamplingFilter, 1.0, aTransform);
+ }
+
+ private:
+ RefPtr<gfxDrawable> mDrawable;
+};
+
+already_AddRefed<gfxCallbackDrawable>
+gfxPatternDrawable::MakeCallbackDrawable() {
+ RefPtr<gfxDrawingCallback> callback = new DrawingCallbackFromDrawable(this);
+ RefPtr<gfxCallbackDrawable> callbackDrawable =
+ new gfxCallbackDrawable(callback, mSize);
+ return callbackDrawable.forget();
+}
+
+bool gfxPatternDrawable::Draw(gfxContext* aContext, const gfxRect& aFillRect,
+ ExtendMode aExtendMode,
+ const SamplingFilter aSamplingFilter,
+ gfxFloat aOpacity, const gfxMatrix& aTransform) {
+ DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
+
+ if (!mPattern) return false;
+
+ if (IsRepeatingExtendMode(aExtendMode)) {
+ // We can't use mPattern directly: We want our repeated tiles to have
+ // the size mSize, which might not be the case in mPattern.
+ // So we need to draw mPattern into a surface of size mSize, create
+ // a pattern from the surface and draw that pattern.
+ // gfxCallbackDrawable and gfxSurfaceDrawable already know how to do
+ // those things, so we use them here. Drawing mPattern into the surface
+ // will happen through this Draw() method with aRepeat = false.
+ RefPtr<gfxCallbackDrawable> callbackDrawable = MakeCallbackDrawable();
+ return callbackDrawable->Draw(aContext, aFillRect, aExtendMode,
+ aSamplingFilter, aOpacity, aTransform);
+ }
+
+ gfxMatrix oldMatrix = mPattern->GetMatrix();
+ mPattern->SetMatrix(aTransform * oldMatrix);
+ DrawOptions drawOptions(aOpacity);
+ aDrawTarget.FillRect(ToRect(aFillRect), *mPattern->GetPattern(&aDrawTarget),
+ drawOptions);
+ mPattern->SetMatrix(oldMatrix);
+ return true;
+}
diff --git a/gfx/thebes/gfxDrawable.h b/gfx/thebes/gfxDrawable.h
new file mode 100644
index 0000000000..b0d47e3691
--- /dev/null
+++ b/gfx/thebes/gfxDrawable.h
@@ -0,0 +1,170 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_DRAWABLE_H
+#define GFX_DRAWABLE_H
+
+#include "gfxRect.h"
+#include "gfxMatrix.h"
+#include "gfxTypes.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Types.h"
+#include "nsISupportsImpl.h"
+
+class gfxContext;
+class gfxPattern;
+
+/**
+ * gfxDrawable
+ * An Interface representing something that has an intrinsic size and can draw
+ * itself repeatedly.
+ */
+class gfxDrawable {
+ NS_INLINE_DECL_REFCOUNTING(gfxDrawable)
+ public:
+ typedef mozilla::gfx::AntialiasMode AntialiasMode;
+ typedef mozilla::gfx::CompositionOp CompositionOp;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ explicit gfxDrawable(const mozilla::gfx::IntSize aSize) : mSize(aSize) {}
+
+ /**
+ * Draw into aContext filling aFillRect, possibly repeating, using
+ * aSamplingFilter. aTransform is a userspace to "image"space matrix. For
+ * example, if Draw draws using a gfxPattern, this is the matrix that should
+ * be set on the pattern prior to rendering it.
+ * @return whether drawing was successful
+ */
+ virtual bool Draw(gfxContext* aContext, const gfxRect& aFillRect,
+ mozilla::gfx::ExtendMode aExtendMode,
+ const mozilla::gfx::SamplingFilter aSamplingFilter,
+ gfxFloat aOpacity = 1.0,
+ const gfxMatrix& aTransform = gfxMatrix()) = 0;
+
+ virtual bool DrawWithSamplingRect(
+ DrawTarget* aDrawTarget, CompositionOp aOp, AntialiasMode aAntialiasMode,
+ const gfxRect& aFillRect, const gfxRect& aSamplingRect,
+ mozilla::gfx::ExtendMode aExtendMode,
+ const mozilla::gfx::SamplingFilter aSamplingFilter,
+ gfxFloat aOpacity = 1.0) {
+ return false;
+ }
+
+ virtual mozilla::gfx::IntSize Size() { return mSize; }
+
+ protected:
+ // Protected destructor, to discourage deletion outside of Release():
+ virtual ~gfxDrawable() = default;
+
+ const mozilla::gfx::IntSize mSize;
+};
+
+/**
+ * gfxSurfaceDrawable
+ * A convenience implementation of gfxDrawable for surfaces.
+ */
+class gfxSurfaceDrawable : public gfxDrawable {
+ public:
+ gfxSurfaceDrawable(mozilla::gfx::SourceSurface* aSurface,
+ const mozilla::gfx::IntSize aSize,
+ const gfxMatrix aTransform = gfxMatrix());
+ virtual ~gfxSurfaceDrawable() = default;
+
+ virtual bool Draw(gfxContext* aContext, const gfxRect& aFillRect,
+ mozilla::gfx::ExtendMode aExtendMode,
+ const mozilla::gfx::SamplingFilter aSamplingFilter,
+ gfxFloat aOpacity = 1.0,
+ const gfxMatrix& aTransform = gfxMatrix()) override;
+
+ virtual bool DrawWithSamplingRect(
+ DrawTarget* aDrawTarget, CompositionOp aOp, AntialiasMode aAntialiasMode,
+ const gfxRect& aFillRect, const gfxRect& aSamplingRect,
+ mozilla::gfx::ExtendMode aExtendMode,
+ const mozilla::gfx::SamplingFilter aSamplingFilter,
+ gfxFloat aOpacity = 1.0) override;
+
+ protected:
+ void DrawInternal(DrawTarget* aDrawTarget, CompositionOp aOp,
+ AntialiasMode aAntialiasMode, const gfxRect& aFillRect,
+ const mozilla::gfx::IntRect& aSamplingRect,
+ mozilla::gfx::ExtendMode aExtendMode,
+ const mozilla::gfx::SamplingFilter aSamplingFilter,
+ gfxFloat aOpacity,
+ const gfxMatrix& aTransform = gfxMatrix());
+
+ RefPtr<mozilla::gfx::SourceSurface> mSourceSurface;
+ const gfxMatrix mTransform;
+};
+
+/**
+ * gfxDrawingCallback
+ * A simple drawing functor.
+ */
+class gfxDrawingCallback {
+ NS_INLINE_DECL_REFCOUNTING(gfxDrawingCallback)
+ protected:
+ // Protected destructor, to discourage deletion outside of Release():
+ virtual ~gfxDrawingCallback() = default;
+
+ public:
+ /**
+ * Draw into aContext filling aFillRect using aSamplingFilter.
+ * aTransform is a userspace to "image"space matrix. For example, if Draw
+ * draws using a gfxPattern, this is the matrix that should be set on the
+ * pattern prior to rendering it.
+ * @return whether drawing was successful
+ */
+ virtual bool operator()(gfxContext* aContext, const gfxRect& aFillRect,
+ const mozilla::gfx::SamplingFilter aSamplingFilter,
+ const gfxMatrix& aTransform = gfxMatrix()) = 0;
+};
+
+/**
+ * gfxCallbackDrawable
+ * A convenience implementation of gfxDrawable for callbacks.
+ */
+class gfxCallbackDrawable : public gfxDrawable {
+ public:
+ gfxCallbackDrawable(gfxDrawingCallback* aCallback,
+ const mozilla::gfx::IntSize aSize);
+ virtual ~gfxCallbackDrawable() = default;
+
+ virtual bool Draw(gfxContext* aContext, const gfxRect& aFillRect,
+ mozilla::gfx::ExtendMode aExtendMode,
+ const mozilla::gfx::SamplingFilter aSamplingFilter,
+ gfxFloat aOpacity = 1.0,
+ const gfxMatrix& aTransform = gfxMatrix()) override;
+
+ protected:
+ already_AddRefed<gfxSurfaceDrawable> MakeSurfaceDrawable(
+ gfxContext* aContext, mozilla::gfx::SamplingFilter aSamplingFilter =
+ mozilla::gfx::SamplingFilter::LINEAR);
+
+ RefPtr<gfxDrawingCallback> mCallback;
+ RefPtr<gfxSurfaceDrawable> mSurfaceDrawable;
+};
+
+/**
+ * gfxPatternDrawable
+ * A convenience implementation of gfxDrawable for patterns.
+ */
+class gfxPatternDrawable : public gfxDrawable {
+ public:
+ gfxPatternDrawable(gfxPattern* aPattern, const mozilla::gfx::IntSize aSize);
+ virtual ~gfxPatternDrawable();
+
+ virtual bool Draw(gfxContext* aContext, const gfxRect& aFillRect,
+ mozilla::gfx::ExtendMode aExtendMode,
+ const mozilla::gfx::SamplingFilter aSamplingFilter,
+ gfxFloat aOpacity = 1.0,
+ const gfxMatrix& aTransform = gfxMatrix()) override;
+
+ protected:
+ already_AddRefed<gfxCallbackDrawable> MakeCallbackDrawable();
+
+ RefPtr<gfxPattern> mPattern;
+};
+
+#endif /* GFX_DRAWABLE_H */
diff --git a/gfx/thebes/gfxEnv.h b/gfx/thebes/gfxEnv.h
new file mode 100644
index 0000000000..a75f26aa97
--- /dev/null
+++ b/gfx/thebes/gfxEnv.h
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_ENV_H
+#define GFX_ENV_H
+
+#include "mozilla/Attributes.h"
+#include "nsDebug.h"
+#include "prenv.h"
+
+#include <sstream>
+#include <string_view>
+
+// To register the check for an environment variable existence (and not empty),
+// add a line in this file using the DECL_GFX_ENV macro.
+//
+// For example this line in the .h:
+// DECL_GFX_ENV(MOZ_GL_SPEW);
+
+// means that you can call e.g.
+// if (gfxEnv::MOZ_GL_SPEW()) { ... }
+// if (gfxEnv::MOZ_GL_SPEW().as_str == "2") { ... }
+// and that the value will be checked only once, first time we call it, then
+// cached.
+
+struct EnvVal {
+ std::string_view as_str;
+
+ static auto From(const char* const raw) {
+ auto ret = EnvVal{};
+
+ ret.as_str = std::string_view{};
+ // Empty string counts as missing.
+ if (raw) {
+ ret.as_str = raw;
+ }
+
+ return ret;
+ }
+
+ MOZ_IMPLICIT operator bool() const {
+ return !as_str.empty(); // Warning, this means ENV=0" -> true!
+ }
+};
+
+class gfxEnv final {
+ public:
+ gfxEnv() = delete;
+
+ static EnvVal Uncached(const char* name) {
+ const auto raw = PR_GetEnv(name);
+ const auto ret = EnvVal::From(raw);
+ if (ret && ret.as_str == "0") {
+ auto msg = std::stringstream{};
+ msg << name << "=" << ret.as_str << " -> true!";
+ NS_WARNING(msg.str().c_str());
+ }
+ return ret;
+ }
+
+#define DECL_GFX_ENV(Name) \
+ static const EnvVal& Name() { \
+ static const auto cached = Uncached(#Name); \
+ return cached; \
+ }
+
+ // This is where DECL_GFX_ENV for each of the environment variables should go.
+ // We will keep these in an alphabetical order by the environment variable,
+ // to make it easier to see if a method accessing an entry already exists.
+ // Just insert yours in the list.
+
+ // OpenGL shader debugging in OGLShaderProgram, in DEBUG only
+ DECL_GFX_ENV(MOZ_DEBUG_SHADERS)
+
+ // Disabling the crash guard in DriverCrashGuard
+ DECL_GFX_ENV(MOZ_DISABLE_CRASH_GUARD)
+ DECL_GFX_ENV(MOZ_FORCE_CRASH_GUARD_NIGHTLY)
+
+ // We force present to work around some Windows bugs - disable that if this
+ // environment variable is set.
+ DECL_GFX_ENV(MOZ_DISABLE_FORCE_PRESENT)
+
+ // Together with paint dumping, only when MOZ_DUMP_PAINTING is defined.
+ // Dumping compositor textures is broken pretty badly. For example,
+ // on Linux it crashes TextureHost::GetAsSurface() returns null.
+ // Expect to have to fix things like this if you turn it on.
+ // Meanwhile, content-side texture dumping
+ // (conditioned on DebugDumpPainting()) is a good replacement.
+ DECL_GFX_ENV(MOZ_DUMP_COMPOSITOR_TEXTURES)
+
+ // Dump GLBlitHelper shader source text.
+ DECL_GFX_ENV(MOZ_DUMP_GLBLITHELPER)
+
+ // Paint dumping, only when MOZ_DUMP_PAINTING is defined.
+ DECL_GFX_ENV(MOZ_DUMP_PAINT)
+ DECL_GFX_ENV(MOZ_DUMP_PAINT_ITEMS)
+ DECL_GFX_ENV(MOZ_DUMP_PAINT_TO_FILE)
+
+ // Force gfxDevCrash to use MOZ_CRASH in Beta and Release
+ DECL_GFX_ENV(MOZ_GFX_CRASH_MOZ_CRASH)
+ // Force gfxDevCrash to use telemetry in Nightly and Aurora
+ DECL_GFX_ENV(MOZ_GFX_CRASH_TELEMETRY)
+
+ // Debugging in GLContext
+ DECL_GFX_ENV(MOZ_GL_DEBUG)
+ DECL_GFX_ENV(MOZ_GL_DEBUG_VERBOSE)
+ DECL_GFX_ENV(MOZ_GL_DEBUG_ABORT_ON_ERROR)
+ DECL_GFX_ENV(MOZ_GL_RELEASE_ASSERT_CONTEXT_OWNERSHIP)
+ DECL_GFX_ENV(MOZ_EGL_RELEASE_ASSERT_CONTEXT_OWNERSHIP)
+
+ // Count GL extensions
+ DECL_GFX_ENV(MOZ_GL_DUMP_EXTS)
+
+ // Very noisy GLContext and GLContextProviderEGL
+ DECL_GFX_ENV(MOZ_GL_SPEW)
+
+ // Do extra work before and after each GLX call in GLContextProviderGLX
+ DECL_GFX_ENV(MOZ_GLX_DEBUG)
+
+ // GL compositing on Windows
+ DECL_GFX_ENV(MOZ_LAYERS_PREFER_EGL)
+
+ // Offscreen GL context for main layer manager
+ DECL_GFX_ENV(MOZ_LAYERS_PREFER_OFFSCREEN)
+
+ // WebGL workarounds
+ DECL_GFX_ENV(MOZ_WEBGL_WORKAROUND_FIRST_AFFECTS_INSTANCE_ID)
+
+ // WARNING:
+ // For readability reasons, please make sure that you've added your new envvar
+ // to the list above in alphabetical order.
+ // Please do not just append it to the end of the list!
+
+#undef DECL_GFX_ENV
+};
+
+#endif /* GFX_ENV_H */
diff --git a/gfx/thebes/gfxFT2FontBase.cpp b/gfx/thebes/gfxFT2FontBase.cpp
new file mode 100644
index 0000000000..72c03a469d
--- /dev/null
+++ b/gfx/thebes/gfxFT2FontBase.cpp
@@ -0,0 +1,824 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxFT2FontBase.h"
+#include "gfxFT2Utils.h"
+#include "harfbuzz/hb.h"
+#include "mozilla/Likely.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "gfxFontConstants.h"
+#include "gfxFontUtils.h"
+#include "gfxHarfBuzzShaper.h"
+#include <algorithm>
+#include <dlfcn.h>
+
+#include FT_TRUETYPE_TAGS_H
+#include FT_TRUETYPE_TABLES_H
+#include FT_ADVANCES_H
+#include FT_MULTIPLE_MASTERS_H
+
+#ifndef FT_LOAD_COLOR
+# define FT_LOAD_COLOR (1L << 20)
+#endif
+#ifndef FT_FACE_FLAG_COLOR
+# define FT_FACE_FLAG_COLOR (1L << 14)
+#endif
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+gfxFT2FontBase::gfxFT2FontBase(
+ const RefPtr<UnscaledFontFreeType>& aUnscaledFont,
+ RefPtr<mozilla::gfx::SharedFTFace>&& aFTFace, gfxFontEntry* aFontEntry,
+ const gfxFontStyle* aFontStyle, int aLoadFlags, bool aEmbolden)
+ : gfxFont(aUnscaledFont, aFontEntry, aFontStyle, kAntialiasDefault),
+ mFTFace(std::move(aFTFace)),
+ mFTLoadFlags(aLoadFlags | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH |
+ FT_LOAD_COLOR),
+ mEmbolden(aEmbolden),
+ mFTSize(0.0) {}
+
+gfxFT2FontBase::~gfxFT2FontBase() { mFTFace->ForgetLockOwner(this); }
+
+FT_Face gfxFT2FontBase::LockFTFace() const
+ MOZ_CAPABILITY_ACQUIRE(mFTFace) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ if (!mFTFace->Lock(this)) {
+ FT_Set_Transform(mFTFace->GetFace(), nullptr, nullptr);
+
+ FT_F26Dot6 charSize = NS_lround(mFTSize * 64.0);
+ FT_Set_Char_Size(mFTFace->GetFace(), charSize, charSize, 0, 0);
+ }
+ return mFTFace->GetFace();
+}
+
+void gfxFT2FontBase::UnlockFTFace() const
+ MOZ_CAPABILITY_RELEASE(mFTFace) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ mFTFace->Unlock();
+}
+
+static FT_ULong GetTableSizeFromFTFace(SharedFTFace* aFace,
+ uint32_t aTableTag) {
+ if (!aFace) {
+ return 0;
+ }
+ FT_ULong len = 0;
+ if (FT_Load_Sfnt_Table(aFace->GetFace(), aTableTag, 0, nullptr, &len) != 0) {
+ return 0;
+ }
+ return len;
+}
+
+bool gfxFT2FontEntryBase::FaceHasTable(SharedFTFace* aFace,
+ uint32_t aTableTag) {
+ return GetTableSizeFromFTFace(aFace, aTableTag) > 0;
+}
+
+nsresult gfxFT2FontEntryBase::CopyFaceTable(SharedFTFace* aFace,
+ uint32_t aTableTag,
+ nsTArray<uint8_t>& aBuffer) {
+ FT_ULong length = GetTableSizeFromFTFace(aFace, aTableTag);
+ if (!length) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (!aBuffer.SetLength(length, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ if (FT_Load_Sfnt_Table(aFace->GetFace(), aTableTag, 0, aBuffer.Elements(),
+ &length) != 0) {
+ aBuffer.Clear();
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+uint32_t gfxFT2FontEntryBase::GetGlyph(uint32_t aCharCode,
+ gfxFT2FontBase* aFont) {
+ const uint32_t slotIndex = aCharCode % kNumCmapCacheSlots;
+ {
+ // Try to read a cached entry without taking an exclusive lock.
+ AutoReadLock lock(mLock);
+ if (mCmapCache) {
+ const auto& slot = mCmapCache[slotIndex];
+ if (slot.mCharCode == aCharCode) {
+ return slot.mGlyphIndex;
+ }
+ }
+ }
+
+ // Create/update the charcode-to-glyphid cache.
+ AutoWriteLock lock(mLock);
+
+ // This cache algorithm and size is based on what is done in
+ // cairo_scaled_font_text_to_glyphs and pango_fc_font_real_get_glyph. I
+ // think the concept is that adjacent characters probably come mostly from
+ // one Unicode block. This assumption is probably not so valid with
+ // scripts with large character sets as used for East Asian languages.
+ if (!mCmapCache) {
+ mCmapCache = mozilla::MakeUnique<CmapCacheSlot[]>(kNumCmapCacheSlots);
+
+ // Invalidate slot 0 by setting its char code to something that would
+ // never end up in slot 0. All other slots are already invalid
+ // because they have mCharCode = 0 and a glyph for char code 0 will
+ // always be in the slot 0.
+ mCmapCache[0].mCharCode = 1;
+ }
+
+ auto& slot = mCmapCache[slotIndex];
+ if (slot.mCharCode != aCharCode) {
+ slot.mCharCode = aCharCode;
+ slot.mGlyphIndex = gfxFT2LockedFace(aFont).GetGlyph(aCharCode);
+ }
+ return slot.mGlyphIndex;
+}
+
+// aScale is intended for a 16.16 x/y_scale of an FT_Size_Metrics
+static inline FT_Long ScaleRoundDesignUnits(FT_Short aDesignMetric,
+ FT_Fixed aScale) {
+ FT_Long fixed26dot6 = FT_MulFix(aDesignMetric, aScale);
+ return ROUND_26_6_TO_INT(fixed26dot6);
+}
+
+// Snap a line to pixels while keeping the center and size of the line as
+// close to the original position as possible.
+//
+// Pango does similar snapping for underline and strikethrough when fonts are
+// hinted, but nsCSSRendering::GetTextDecorationRectInternal always snaps the
+// top and size of lines. Optimizing the distance between the line and
+// baseline is probably good for the gap between text and underline, but
+// optimizing the center of the line is better for positioning strikethough.
+static void SnapLineToPixels(gfxFloat& aOffset, gfxFloat& aSize) {
+ gfxFloat snappedSize = std::max(floor(aSize + 0.5), 1.0);
+ // Correct offset for change in size
+ gfxFloat offset = aOffset - 0.5 * (aSize - snappedSize);
+ // Snap offset
+ aOffset = floor(offset + 0.5);
+ aSize = snappedSize;
+}
+
+static inline gfxRect ScaleGlyphBounds(const IntRect& aBounds,
+ gfxFloat aScale) {
+ return gfxRect(FLOAT_FROM_26_6(aBounds.x) * aScale,
+ FLOAT_FROM_26_6(aBounds.y) * aScale,
+ FLOAT_FROM_26_6(aBounds.width) * aScale,
+ FLOAT_FROM_26_6(aBounds.height) * aScale);
+}
+
+/**
+ * Get extents for a simple character representable by a single glyph.
+ * The return value is the glyph id of that glyph or zero if no such glyph
+ * exists. aWidth/aBounds is only set when this returns a non-zero glyph id.
+ * This is just for use during initialization, and doesn't use the width cache.
+ */
+uint32_t gfxFT2FontBase::GetCharExtents(uint32_t aChar, gfxFloat* aWidth,
+ gfxRect* aBounds) {
+ FT_UInt gid = GetGlyph(aChar);
+ int32_t width;
+ IntRect bounds;
+ if (gid && GetFTGlyphExtents(gid, aWidth ? &width : nullptr,
+ aBounds ? &bounds : nullptr)) {
+ if (aWidth) {
+ *aWidth = FLOAT_FROM_16_16(width);
+ }
+ if (aBounds) {
+ *aBounds = ScaleGlyphBounds(bounds, GetAdjustedSize() / mFTSize);
+ }
+ return gid;
+ } else {
+ return 0;
+ }
+}
+
+/**
+ * Find the closest available fixed strike size, if applicable, to the
+ * desired font size.
+ */
+static double FindClosestSize(FT_Face aFace, double aSize) {
+ // FT size selection does not actually support sizes smaller than 1 and will
+ // clamp this internally, regardless of what is requested. Do the clamp here
+ // instead so that glyph extents/font matrix scaling will compensate it, as
+ // Cairo normally would.
+ if (aSize < 1.0) {
+ aSize = 1.0;
+ }
+ if (FT_IS_SCALABLE(aFace)) {
+ return aSize;
+ }
+ double bestDist = DBL_MAX;
+ FT_Int bestSize = -1;
+ for (FT_Int i = 0; i < aFace->num_fixed_sizes; i++) {
+ double dist = aFace->available_sizes[i].y_ppem / 64.0 - aSize;
+ // If the previous best is smaller than the desired size, prefer
+ // a bigger size. Otherwise, just choose whatever size is closest.
+ if (bestDist < 0 ? dist >= bestDist : fabs(dist) <= bestDist) {
+ bestDist = dist;
+ bestSize = i;
+ }
+ }
+ if (bestSize < 0) {
+ return aSize;
+ }
+ return aFace->available_sizes[bestSize].y_ppem / 64.0;
+}
+
+void gfxFT2FontBase::InitMetrics() {
+ mFUnitsConvFactor = 0.0;
+
+ if (MOZ_UNLIKELY(mStyle.AdjustedSizeMustBeZero())) {
+ memset(&mMetrics, 0, sizeof(mMetrics)); // zero initialize
+ mSpaceGlyph = GetGlyph(' ');
+ return;
+ }
+
+ if (FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) !=
+ FontSizeAdjust::Tag::None &&
+ mStyle.sizeAdjust >= 0.0 && GetAdjustedSize() > 0.0 && mFTSize == 0.0) {
+ // If font-size-adjust is in effect, we need to get metrics in order to
+ // determine the aspect ratio, then compute the final adjusted size and
+ // re-initialize metrics.
+ // Setting mFTSize nonzero here ensures we will not recurse again; the
+ // actual value will be overridden by FindClosestSize below.
+ mFTSize = 1.0;
+ InitMetrics();
+ // Now do the font-size-adjust calculation and set the final size.
+ gfxFloat aspect;
+ switch (FontSizeAdjust::Tag(mStyle.sizeAdjustBasis)) {
+ default:
+ MOZ_ASSERT_UNREACHABLE("unhandled sizeAdjustBasis?");
+ aspect = 0.0;
+ break;
+ case FontSizeAdjust::Tag::ExHeight:
+ aspect = mMetrics.xHeight / mAdjustedSize;
+ break;
+ case FontSizeAdjust::Tag::CapHeight:
+ aspect = mMetrics.capHeight / mAdjustedSize;
+ break;
+ case FontSizeAdjust::Tag::ChWidth:
+ aspect =
+ mMetrics.zeroWidth > 0.0 ? mMetrics.zeroWidth / mAdjustedSize : 0.5;
+ break;
+ case FontSizeAdjust::Tag::IcWidth:
+ case FontSizeAdjust::Tag::IcHeight: {
+ bool vertical = FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) ==
+ FontSizeAdjust::Tag::IcHeight;
+ gfxFloat advance = GetCharAdvance(kWaterIdeograph, vertical);
+ aspect = advance > 0.0 ? advance / mAdjustedSize : 1.0;
+ break;
+ }
+ }
+ if (aspect > 0.0) {
+ // If we created a shaper above (to measure glyphs), discard it so we
+ // get a new one for the adjusted scaling.
+ delete mHarfBuzzShaper.exchange(nullptr);
+ mAdjustedSize = mStyle.GetAdjustedSize(aspect);
+ // Ensure the FT_Face will be reconfigured for the new size next time we
+ // need to use it.
+ mFTFace->ForgetLockOwner(this);
+ }
+ }
+
+ // Set mAdjustedSize if it hasn't already been set by a font-size-adjust
+ // computation.
+ mAdjustedSize = GetAdjustedSize();
+
+ // Cairo metrics are normalized to em-space, so that whatever fixed size
+ // might actually be chosen is factored out. They are then later scaled by
+ // the font matrix to the target adjusted size. Stash the chosen closest
+ // size here for later scaling of the metrics.
+ mFTSize = FindClosestSize(mFTFace->GetFace(), GetAdjustedSize());
+
+ // Explicitly lock the face so we can release it early before calling
+ // back into Cairo below.
+ FT_Face face = LockFTFace();
+
+ if (MOZ_UNLIKELY(!face)) {
+ // No face. This unfortunate situation might happen if the font
+ // file is (re)moved at the wrong time.
+ const gfxFloat emHeight = GetAdjustedSize();
+ mMetrics.emHeight = emHeight;
+ mMetrics.maxAscent = mMetrics.emAscent = 0.8 * emHeight;
+ mMetrics.maxDescent = mMetrics.emDescent = 0.2 * emHeight;
+ mMetrics.maxHeight = emHeight;
+ mMetrics.internalLeading = 0.0;
+ mMetrics.externalLeading = 0.2 * emHeight;
+ const gfxFloat spaceWidth = 0.5 * emHeight;
+ mMetrics.spaceWidth = spaceWidth;
+ mMetrics.maxAdvance = spaceWidth;
+ mMetrics.aveCharWidth = spaceWidth;
+ mMetrics.zeroWidth = spaceWidth;
+ mMetrics.ideographicWidth = emHeight;
+ const gfxFloat xHeight = 0.5 * emHeight;
+ mMetrics.xHeight = xHeight;
+ mMetrics.capHeight = mMetrics.maxAscent;
+ const gfxFloat underlineSize = emHeight / 14.0;
+ mMetrics.underlineSize = underlineSize;
+ mMetrics.underlineOffset = -underlineSize;
+ mMetrics.strikeoutOffset = 0.25 * emHeight;
+ mMetrics.strikeoutSize = underlineSize;
+
+ SanitizeMetrics(&mMetrics, false);
+ UnlockFTFace();
+ return;
+ }
+
+ const FT_Size_Metrics& ftMetrics = face->size->metrics;
+
+ mMetrics.maxAscent = FLOAT_FROM_26_6(ftMetrics.ascender);
+ mMetrics.maxDescent = -FLOAT_FROM_26_6(ftMetrics.descender);
+ mMetrics.maxAdvance = FLOAT_FROM_26_6(ftMetrics.max_advance);
+ gfxFloat lineHeight = FLOAT_FROM_26_6(ftMetrics.height);
+
+ gfxFloat emHeight;
+ // Scale for vertical design metric conversion: pixels per design unit.
+ // If this remains at 0.0, we can't use metrics from OS/2 etc.
+ gfxFloat yScale = 0.0;
+ if (FT_IS_SCALABLE(face)) {
+ // Prefer FT_Size_Metrics::x_scale to x_ppem as x_ppem does not
+ // have subpixel accuracy.
+ //
+ // FT_Size_Metrics::y_scale is in 16.16 fixed point format. Its
+ // (fractional) value is a factor that converts vertical metrics from
+ // design units to units of 1/64 pixels, so that the result may be
+ // interpreted as pixels in 26.6 fixed point format.
+ mFUnitsConvFactor = FLOAT_FROM_26_6(FLOAT_FROM_16_16(ftMetrics.x_scale));
+ yScale = FLOAT_FROM_26_6(FLOAT_FROM_16_16(ftMetrics.y_scale));
+ emHeight = face->units_per_EM * yScale;
+ } else { // Not scalable.
+ emHeight = ftMetrics.y_ppem;
+ // FT_Face doc says units_per_EM and a bunch of following fields
+ // are "only relevant to scalable outlines". If it's an sfnt,
+ // we can get units_per_EM from the 'head' table instead; otherwise,
+ // we don't have a unitsPerEm value so we can't compute/use yScale or
+ // mFUnitsConvFactor (x scale).
+ const TT_Header* head =
+ static_cast<TT_Header*>(FT_Get_Sfnt_Table(face, ft_sfnt_head));
+ if (head) {
+ // Bug 1267909 - Even if the font is not explicitly scalable,
+ // if the face has color bitmaps, it should be treated as scalable
+ // and scaled to the desired size. Metrics based on y_ppem need
+ // to be rescaled for the adjusted size. This makes metrics agree
+ // with the scales we pass to Cairo for Fontconfig fonts.
+ if (face->face_flags & FT_FACE_FLAG_COLOR) {
+ emHeight = GetAdjustedSize();
+ gfxFloat adjustScale = emHeight / ftMetrics.y_ppem;
+ mMetrics.maxAscent *= adjustScale;
+ mMetrics.maxDescent *= adjustScale;
+ mMetrics.maxAdvance *= adjustScale;
+ lineHeight *= adjustScale;
+ }
+ gfxFloat emUnit = head->Units_Per_EM;
+ mFUnitsConvFactor = ftMetrics.x_ppem / emUnit;
+ yScale = emHeight / emUnit;
+ }
+ }
+
+ TT_OS2* os2 = static_cast<TT_OS2*>(FT_Get_Sfnt_Table(face, ft_sfnt_os2));
+
+ if (os2 && os2->sTypoAscender && yScale > 0.0) {
+ mMetrics.emAscent = os2->sTypoAscender * yScale;
+ mMetrics.emDescent = -os2->sTypoDescender * yScale;
+ FT_Short typoHeight =
+ os2->sTypoAscender - os2->sTypoDescender + os2->sTypoLineGap;
+ lineHeight = typoHeight * yScale;
+
+ // If the OS/2 fsSelection USE_TYPO_METRICS bit is set,
+ // set maxAscent/Descent from the sTypo* fields instead of hhea.
+ const uint16_t kUseTypoMetricsMask = 1 << 7;
+ if ((os2->fsSelection & kUseTypoMetricsMask) ||
+ // maxAscent/maxDescent get used for frame heights, and some fonts
+ // don't have the HHEA table ascent/descent set (bug 279032).
+ (mMetrics.maxAscent == 0.0 && mMetrics.maxDescent == 0.0)) {
+ // We use NS_round here to parallel the pixel-rounded values that
+ // freetype gives us for ftMetrics.ascender/descender.
+ mMetrics.maxAscent = NS_round(mMetrics.emAscent);
+ mMetrics.maxDescent = NS_round(mMetrics.emDescent);
+ }
+ } else {
+ mMetrics.emAscent = mMetrics.maxAscent;
+ mMetrics.emDescent = mMetrics.maxDescent;
+ }
+
+ // gfxFont::Metrics::underlineOffset is the position of the top of the
+ // underline.
+ //
+ // FT_FaceRec documentation describes underline_position as "the
+ // center of the underlining stem". This was the original definition
+ // of the PostScript metric, but in the PostScript table of OpenType
+ // fonts the metric is "the top of the underline"
+ // (http://www.microsoft.com/typography/otspec/post.htm), and FreeType
+ // (up to version 2.3.7) doesn't make any adjustment.
+ //
+ // Therefore get the underline position directly from the table
+ // ourselves when this table exists. Use FreeType's metrics for
+ // other (including older PostScript) fonts.
+ if (face->underline_position && face->underline_thickness && yScale > 0.0) {
+ mMetrics.underlineSize = face->underline_thickness * yScale;
+ TT_Postscript* post =
+ static_cast<TT_Postscript*>(FT_Get_Sfnt_Table(face, ft_sfnt_post));
+ if (post && post->underlinePosition) {
+ mMetrics.underlineOffset = post->underlinePosition * yScale;
+ } else {
+ mMetrics.underlineOffset =
+ face->underline_position * yScale + 0.5 * mMetrics.underlineSize;
+ }
+ } else { // No underline info.
+ // Imitate Pango.
+ mMetrics.underlineSize = emHeight / 14.0;
+ mMetrics.underlineOffset = -mMetrics.underlineSize;
+ }
+
+ if (os2 && os2->yStrikeoutSize && os2->yStrikeoutPosition && yScale > 0.0) {
+ mMetrics.strikeoutSize = os2->yStrikeoutSize * yScale;
+ mMetrics.strikeoutOffset = os2->yStrikeoutPosition * yScale;
+ } else { // No strikeout info.
+ mMetrics.strikeoutSize = mMetrics.underlineSize;
+ // Use OpenType spec's suggested position for Roman font.
+ mMetrics.strikeoutOffset =
+ emHeight * 409.0 / 2048.0 + 0.5 * mMetrics.strikeoutSize;
+ }
+ SnapLineToPixels(mMetrics.strikeoutOffset, mMetrics.strikeoutSize);
+
+ if (os2 && os2->sxHeight && yScale > 0.0) {
+ mMetrics.xHeight = os2->sxHeight * yScale;
+ } else {
+ // CSS 2.1, section 4.3.2 Lengths: "In the cases where it is
+ // impossible or impractical to determine the x-height, a value of
+ // 0.5em should be used."
+ mMetrics.xHeight = 0.5 * emHeight;
+ }
+
+ // aveCharWidth is used for the width of text input elements so be
+ // liberal rather than conservative in the estimate.
+ if (os2 && os2->xAvgCharWidth) {
+ // Round to pixels as this is compared with maxAdvance to guess
+ // whether this is a fixed width font.
+ mMetrics.aveCharWidth =
+ ScaleRoundDesignUnits(os2->xAvgCharWidth, ftMetrics.x_scale);
+ } else {
+ mMetrics.aveCharWidth = 0.0; // updated below
+ }
+
+ if (os2 && os2->sCapHeight && yScale > 0.0) {
+ mMetrics.capHeight = os2->sCapHeight * yScale;
+ } else {
+ mMetrics.capHeight = mMetrics.maxAscent;
+ }
+
+ // Release the face lock to safely load glyphs with GetCharExtents if
+ // necessary without recursively locking.
+ UnlockFTFace();
+
+ gfxFloat width;
+ mSpaceGlyph = GetCharExtents(' ', &width);
+ if (mSpaceGlyph) {
+ mMetrics.spaceWidth = width;
+ } else {
+ mMetrics.spaceWidth = mMetrics.maxAdvance; // guess
+ }
+
+ if (GetCharExtents('0', &width)) {
+ mMetrics.zeroWidth = width;
+ } else {
+ mMetrics.zeroWidth = -1.0; // indicates not found
+ }
+
+ if (GetCharExtents(kWaterIdeograph, &width)) {
+ mMetrics.ideographicWidth = width;
+ } else {
+ mMetrics.ideographicWidth = -1.0;
+ }
+
+ // If we didn't get a usable x-height or cap-height above, try measuring
+ // specific glyphs. This can be affected by hinting, leading to erratic
+ // behavior across font sizes and system configuration, so we prefer to
+ // use the metrics directly from the font if possible.
+ // Using glyph bounds for x-height or cap-height may not really be right,
+ // if fonts have fancy swashes etc. For x-height, CSS 2.1 suggests possibly
+ // using the height of an "o", which may be more consistent across fonts,
+ // but then curve-overshoot should also be accounted for.
+ gfxFloat xWidth;
+ gfxRect xBounds;
+ if (mMetrics.xHeight == 0.0) {
+ if (GetCharExtents('x', &xWidth, &xBounds) && xBounds.y < 0.0) {
+ mMetrics.xHeight = -xBounds.y;
+ mMetrics.aveCharWidth = std::max(mMetrics.aveCharWidth, xWidth);
+ }
+ }
+
+ if (mMetrics.capHeight == 0.0) {
+ if (GetCharExtents('H', nullptr, &xBounds) && xBounds.y < 0.0) {
+ mMetrics.capHeight = -xBounds.y;
+ }
+ }
+
+ mMetrics.aveCharWidth = std::max(mMetrics.aveCharWidth, mMetrics.zeroWidth);
+ if (mMetrics.aveCharWidth == 0.0) {
+ mMetrics.aveCharWidth = mMetrics.spaceWidth;
+ }
+ // Apparently hinting can mean that max_advance is not always accurate.
+ mMetrics.maxAdvance = std::max(mMetrics.maxAdvance, mMetrics.aveCharWidth);
+
+ mMetrics.maxHeight = mMetrics.maxAscent + mMetrics.maxDescent;
+
+ // Make the line height an integer number of pixels so that lines will be
+ // equally spaced (rather than just being snapped to pixels, some up and
+ // some down). Layout calculates line height from the emHeight +
+ // internalLeading + externalLeading, but first each of these is rounded
+ // to layout units. To ensure that the result is an integer number of
+ // pixels, round each of the components to pixels.
+ mMetrics.emHeight = floor(emHeight + 0.5);
+
+ // maxHeight will normally be an integer, but round anyway in case
+ // FreeType is configured differently.
+ mMetrics.internalLeading =
+ floor(mMetrics.maxHeight - mMetrics.emHeight + 0.5);
+
+ // Text input boxes currently don't work well with lineHeight
+ // significantly less than maxHeight (with Verdana, for example).
+ lineHeight = floor(std::max(lineHeight, mMetrics.maxHeight) + 0.5);
+ mMetrics.externalLeading =
+ lineHeight - mMetrics.internalLeading - mMetrics.emHeight;
+
+ // Ensure emAscent + emDescent == emHeight
+ gfxFloat sum = mMetrics.emAscent + mMetrics.emDescent;
+ mMetrics.emAscent =
+ sum > 0.0 ? mMetrics.emAscent * mMetrics.emHeight / sum : 0.0;
+ mMetrics.emDescent = mMetrics.emHeight - mMetrics.emAscent;
+
+ SanitizeMetrics(&mMetrics, false);
+
+#if 0
+ // printf("font name: %s %f\n", NS_ConvertUTF16toUTF8(GetName()).get(), GetStyle()->size);
+ // printf ("pango font %s\n", pango_font_description_to_string (pango_font_describe (font)));
+
+ fprintf (stderr, "Font: %s\n", GetName().get());
+ fprintf (stderr, " emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent);
+ fprintf (stderr, " maxAscent: %f maxDescent: %f\n", mMetrics.maxAscent, mMetrics.maxDescent);
+ fprintf (stderr, " internalLeading: %f externalLeading: %f\n", mMetrics.externalLeading, mMetrics.internalLeading);
+ fprintf (stderr, " spaceWidth: %f aveCharWidth: %f xHeight: %f\n", mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.xHeight);
+ fprintf (stderr, " ideographicWidth: %f\n", mMetrics.ideographicWidth);
+ fprintf (stderr, " uOff: %f uSize: %f stOff: %f stSize: %f\n", mMetrics.underlineOffset, mMetrics.underlineSize, mMetrics.strikeoutOffset, mMetrics.strikeoutSize);
+#endif
+}
+
+uint32_t gfxFT2FontBase::GetGlyph(uint32_t unicode,
+ uint32_t variation_selector) {
+ if (variation_selector) {
+ uint32_t id =
+ gfxFT2LockedFace(this).GetUVSGlyph(unicode, variation_selector);
+ if (id) {
+ return id;
+ }
+ unicode = gfxFontUtils::GetUVSFallback(unicode, variation_selector);
+ if (unicode) {
+ return GetGlyph(unicode);
+ }
+ return 0;
+ }
+
+ return GetGlyph(unicode);
+}
+
+bool gfxFT2FontBase::ShouldRoundXOffset(cairo_t* aCairo) const {
+ // Force rounding if outputting to a Cairo context or if requested by pref to
+ // disable subpixel positioning. Otherwise, allow subpixel positioning (no
+ // rounding) if rendering a scalable outline font with anti-aliasing.
+ // Monochrome rendering or some bitmap fonts can become too distorted with
+ // subpixel positioning, so force rounding in those cases. Also be careful not
+ // to use subpixel positioning if the user requests full hinting via
+ // Fontconfig, which we detect by checking that neither hinting was disabled
+ // nor light hinting was requested. Allow pref to force subpixel positioning
+ // on even if full hinting was requested.
+ return MOZ_UNLIKELY(
+ StaticPrefs::
+ gfx_text_subpixel_position_force_disabled_AtStartup()) ||
+ aCairo != nullptr || !mFTFace || !FT_IS_SCALABLE(mFTFace->GetFace()) ||
+ (mFTLoadFlags & FT_LOAD_MONOCHROME) ||
+ !((mFTLoadFlags & FT_LOAD_NO_HINTING) ||
+ FT_LOAD_TARGET_MODE(mFTLoadFlags) == FT_RENDER_MODE_LIGHT ||
+ MOZ_UNLIKELY(
+ StaticPrefs::
+ gfx_text_subpixel_position_force_enabled_AtStartup()));
+}
+
+FT_Vector gfxFT2FontBase::GetEmboldenStrength(FT_Face aFace) const {
+ FT_Vector strength = {0, 0};
+ if (!mEmbolden) {
+ return strength;
+ }
+
+ // If it's an outline glyph, we'll be using mozilla_glyphslot_embolden_less
+ // (see gfx/wr/webrender/src/platform/unix/font.rs), so we need to match its
+ // emboldening strength here.
+ if (aFace->glyph->format == FT_GLYPH_FORMAT_OUTLINE) {
+ strength.x =
+ FT_MulFix(aFace->units_per_EM, aFace->size->metrics.y_scale) / 48;
+ strength.y = strength.x;
+ return strength;
+ }
+
+ // This is the embolden "strength" used by FT_GlyphSlot_Embolden.
+ strength.x =
+ FT_MulFix(aFace->units_per_EM, aFace->size->metrics.y_scale) / 24;
+ strength.y = strength.x;
+ if (aFace->glyph->format == FT_GLYPH_FORMAT_BITMAP) {
+ strength.x &= -64;
+ if (!strength.x) {
+ strength.x = 64;
+ }
+ strength.y &= -64;
+ }
+ return strength;
+}
+
+bool gfxFT2FontBase::GetFTGlyphExtents(uint16_t aGID, int32_t* aAdvance,
+ IntRect* aBounds) const {
+ gfxFT2LockedFace face(this);
+ MOZ_ASSERT(face.get());
+ if (!face.get()) {
+ // Failed to get the FT_Face? Give up already.
+ NS_WARNING("failed to get FT_Face!");
+ return false;
+ }
+
+ FT_Int32 flags = mFTLoadFlags;
+ if (!aBounds) {
+ flags |= FT_LOAD_ADVANCE_ONLY;
+ }
+
+ // Whether to disable subpixel positioning
+ bool roundX = ShouldRoundXOffset(nullptr);
+
+ // Workaround for FT_Load_Glyph not setting linearHoriAdvance for SVG glyphs.
+ // See https://gitlab.freedesktop.org/freetype/freetype/-/issues/1156.
+ if (!roundX &&
+ GetFontEntry()->HasFontTable(TRUETYPE_TAG('S', 'V', 'G', ' '))) {
+ flags &= ~FT_LOAD_COLOR;
+ }
+
+ if (Factory::LoadFTGlyph(face.get(), aGID, flags) != FT_Err_Ok) {
+ // FT_Face was somehow broken/invalid? Don't try to access glyph slot.
+ // This probably shouldn't happen, but does: see bug 1440938.
+ NS_WARNING("failed to load glyph!");
+ return false;
+ }
+
+ // Whether to interpret hinting settings (i.e. not printing)
+ bool hintMetrics = ShouldHintMetrics();
+ // No hinting disables X and Y hinting. Light disables only X hinting.
+ bool unhintedY = (mFTLoadFlags & FT_LOAD_NO_HINTING) != 0;
+ bool unhintedX =
+ unhintedY || FT_LOAD_TARGET_MODE(mFTLoadFlags) == FT_RENDER_MODE_LIGHT;
+
+ // Normalize out the loaded FT glyph size and then scale to the actually
+ // desired size, in case these two sizes differ.
+ gfxFloat extentsScale = GetAdjustedSize() / mFTSize;
+
+ FT_Vector bold = GetEmboldenStrength(face.get());
+
+ // Due to freetype bug 52683 we MUST use the linearHoriAdvance field when
+ // dealing with a variation font; also use it for scalable fonts when not
+ // applying hinting. Otherwise, prefer hinted width from glyph->advance.x.
+ if (aAdvance) {
+ FT_Fixed advance;
+ if (!roundX || FT_HAS_MULTIPLE_MASTERS(face.get())) {
+ advance = face.get()->glyph->linearHoriAdvance;
+ } else {
+ advance = face.get()->glyph->advance.x << 10; // convert 26.6 to 16.16
+ }
+ if (advance) {
+ advance += bold.x << 10; // convert 26.6 to 16.16
+ }
+ // Hinting was requested, but FT did not apply any hinting to the metrics.
+ // Round the advance here to approximate hinting as Cairo does. This must
+ // happen BEFORE we apply the glyph extents scale, just like FT hinting
+ // would.
+ if (hintMetrics && roundX && unhintedX) {
+ advance = (advance + 0x8000) & 0xffff0000u;
+ }
+ *aAdvance = NS_lround(advance * extentsScale);
+ }
+
+ if (aBounds) {
+ const FT_Glyph_Metrics& metrics = face.get()->glyph->metrics;
+ FT_F26Dot6 x = metrics.horiBearingX;
+ FT_F26Dot6 y = -metrics.horiBearingY;
+ FT_F26Dot6 x2 = x + metrics.width;
+ FT_F26Dot6 y2 = y + metrics.height;
+ // Synthetic bold moves the glyph top and right boundaries.
+ y -= bold.y;
+ x2 += bold.x;
+ if (hintMetrics) {
+ if (roundX && unhintedX) {
+ x &= -64;
+ x2 = (x2 + 63) & -64;
+ }
+ if (unhintedY) {
+ y &= -64;
+ y2 = (y2 + 63) & -64;
+ }
+ }
+ *aBounds = IntRect(x, y, x2 - x, y2 - y);
+ }
+ return true;
+}
+
+/**
+ * Get the cached glyph metrics for the glyph id if available. Otherwise, query
+ * FreeType for the glyph extents and initialize the glyph metrics.
+ */
+const gfxFT2FontBase::GlyphMetrics& gfxFT2FontBase::GetCachedGlyphMetrics(
+ uint16_t aGID, IntRect* aBounds) const {
+ {
+ // Try to read cached metrics without exclusive locking.
+ AutoReadLock lock(mLock);
+ if (mGlyphMetrics) {
+ if (auto metrics = mGlyphMetrics->Lookup(aGID)) {
+ return metrics.Data();
+ }
+ }
+ }
+
+ // We need to create/update the cache.
+ AutoWriteLock lock(mLock);
+ if (!mGlyphMetrics) {
+ mGlyphMetrics =
+ mozilla::MakeUnique<nsTHashMap<nsUint32HashKey, GlyphMetrics>>(128);
+ }
+
+ return mGlyphMetrics->LookupOrInsertWith(aGID, [&] {
+ GlyphMetrics metrics;
+ IntRect bounds;
+ if (GetFTGlyphExtents(aGID, &metrics.mAdvance, &bounds)) {
+ metrics.SetBounds(bounds);
+ if (aBounds) {
+ *aBounds = bounds;
+ }
+ }
+ return metrics;
+ });
+}
+
+bool gfxFT2FontBase::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds,
+ bool aTight) {
+ IntRect bounds;
+ const GlyphMetrics& metrics = GetCachedGlyphMetrics(aGID, &bounds);
+ if (!metrics.HasValidBounds()) {
+ return false;
+ }
+ // Check if there are cached bounds and use those if available. Otherwise,
+ // fall back to directly querying the glyph extents.
+ if (metrics.HasCachedBounds()) {
+ bounds = metrics.GetBounds();
+ } else if (bounds.IsEmpty() && !GetFTGlyphExtents(aGID, nullptr, &bounds)) {
+ return false;
+ }
+ // The bounds are stored unscaled, so must be scaled to the adjusted size.
+ *aBounds = ScaleGlyphBounds(bounds, GetAdjustedSize() / mFTSize);
+ return true;
+}
+
+// For variation fonts, figure out the variation coordinates to be applied
+// for each axis, in freetype's order (which may not match the order of
+// axes in mStyle.variationSettings, so we need to search by axis tag).
+/*static*/
+void gfxFT2FontBase::SetupVarCoords(
+ FT_MM_Var* aMMVar, const nsTArray<gfxFontVariation>& aVariations,
+ FT_Face aFTFace) {
+ if (!aMMVar) {
+ return;
+ }
+
+ nsTArray<FT_Fixed> coords;
+ for (unsigned i = 0; i < aMMVar->num_axis; ++i) {
+ coords.AppendElement(aMMVar->axis[i].def);
+ for (const auto& v : aVariations) {
+ if (aMMVar->axis[i].tag == v.mTag) {
+ FT_Fixed val = v.mValue * 0x10000;
+ val = std::min(val, aMMVar->axis[i].maximum);
+ val = std::max(val, aMMVar->axis[i].minimum);
+ coords[i] = val;
+ break;
+ }
+ }
+ }
+
+ if (!coords.IsEmpty()) {
+#if MOZ_TREE_FREETYPE
+ FT_Set_Var_Design_Coordinates(aFTFace, coords.Length(), coords.Elements());
+#else
+ typedef FT_Error (*SetCoordsFunc)(FT_Face, FT_UInt, FT_Fixed*);
+ static SetCoordsFunc setCoords;
+ static bool firstTime = true;
+ if (firstTime) {
+ firstTime = false;
+ setCoords =
+ (SetCoordsFunc)dlsym(RTLD_DEFAULT, "FT_Set_Var_Design_Coordinates");
+ }
+ if (setCoords) {
+ (*setCoords)(aFTFace, coords.Length(), coords.Elements());
+ }
+#endif
+ }
+}
diff --git a/gfx/thebes/gfxFT2FontBase.h b/gfx/thebes/gfxFT2FontBase.h
new file mode 100644
index 0000000000..1699cba683
--- /dev/null
+++ b/gfx/thebes/gfxFT2FontBase.h
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_FT2FONTBASE_H
+#define GFX_FT2FONTBASE_H
+
+#include "gfxContext.h"
+#include "gfxFont.h"
+#include "gfxFontEntry.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/UnscaledFontFreeType.h"
+#include "nsTHashMap.h"
+#include "nsHashKeys.h"
+
+class gfxFT2FontBase;
+
+class gfxFT2FontEntryBase : public gfxFontEntry {
+ public:
+ explicit gfxFT2FontEntryBase(const nsACString& aName) : gfxFontEntry(aName) {}
+
+ uint32_t GetGlyph(uint32_t aCharCode, gfxFT2FontBase* aFont);
+
+ static bool FaceHasTable(mozilla::gfx::SharedFTFace*, uint32_t aTableTag);
+ static nsresult CopyFaceTable(mozilla::gfx::SharedFTFace*, uint32_t aTableTag,
+ nsTArray<uint8_t>&);
+
+ private:
+ enum { kNumCmapCacheSlots = 256 };
+
+ struct CmapCacheSlot {
+ CmapCacheSlot() : mCharCode(0), mGlyphIndex(0) {}
+
+ uint32_t mCharCode;
+ uint32_t mGlyphIndex;
+ };
+
+ mozilla::UniquePtr<CmapCacheSlot[]> mCmapCache MOZ_GUARDED_BY(mLock);
+};
+
+class gfxFT2FontBase : public gfxFont {
+ public:
+ gfxFT2FontBase(
+ const RefPtr<mozilla::gfx::UnscaledFontFreeType>& aUnscaledFont,
+ RefPtr<mozilla::gfx::SharedFTFace>&& aFTFace, gfxFontEntry* aFontEntry,
+ const gfxFontStyle* aFontStyle, int aLoadFlags, bool aEmbolden);
+
+ uint32_t GetGlyph(uint32_t aCharCode) {
+ auto* entry = static_cast<gfxFT2FontEntryBase*>(mFontEntry.get());
+ return entry->GetGlyph(aCharCode, this);
+ }
+
+ bool ProvidesGetGlyph() const override { return true; }
+ virtual uint32_t GetGlyph(uint32_t unicode,
+ uint32_t variation_selector) override;
+
+ bool ProvidesGlyphWidths() const override { return true; }
+ int32_t GetGlyphWidth(uint16_t aGID) override {
+ return GetCachedGlyphMetrics(aGID).mAdvance;
+ }
+
+ bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override;
+
+ FontType GetType() const override { return FONT_TYPE_FT2; }
+
+ bool ShouldRoundXOffset(cairo_t* aCairo) const override;
+
+ static void SetupVarCoords(FT_MM_Var* aMMVar,
+ const nsTArray<gfxFontVariation>& aVariations,
+ FT_Face aFTFace);
+
+ FT_Face LockFTFace() const;
+ void UnlockFTFace() const;
+
+ private:
+ uint32_t GetCharExtents(uint32_t aChar, gfxFloat* aWidth,
+ gfxRect* aBounds = nullptr);
+
+ // Get advance (and optionally bounds) of a single glyph from FreeType,
+ // and return true, or return false if we failed.
+ bool GetFTGlyphExtents(uint16_t aGID, int32_t* aWidth,
+ mozilla::gfx::IntRect* aBounds = nullptr) const;
+
+ protected:
+ ~gfxFT2FontBase() override;
+ void InitMetrics();
+ const Metrics& GetHorizontalMetrics() const override { return mMetrics; }
+ FT_Vector GetEmboldenStrength(FT_Face aFace) const;
+
+ RefPtr<mozilla::gfx::SharedFTFace> mFTFace;
+
+ Metrics mMetrics;
+ int mFTLoadFlags;
+ bool mEmbolden;
+ gfxFloat mFTSize;
+
+ // For variation/multiple-master fonts, this will be an array of the values
+ // for each axis, as specified by mStyle.variationSettings (or the font's
+ // default for axes not present in variationSettings). Values here are in
+ // freetype's 16.16 fixed-point format, and clamped to the valid min/max
+ // range reported by the face.
+ nsTArray<FT_Fixed> mCoords;
+
+ // Store cached glyph metrics for reasonably small glyph sizes. The bounds
+ // are stored unscaled to losslessly compress 26.6 fixed point to an int16_t.
+ // Larger glyphs are handled directly via GetFTGlyphExtents.
+ struct GlyphMetrics {
+ // Set the X coord to INT16_MIN to signal the bounds are invalid, or
+ // INT16_MAX to signal that the bounds would overflow an int16_t.
+ enum { INVALID = INT16_MIN, LARGE = INT16_MAX };
+
+ GlyphMetrics() : mAdvance(0), mX(INVALID), mY(0), mWidth(0), mHeight(0) {}
+
+ bool HasValidBounds() const { return mX != INVALID; }
+ bool HasCachedBounds() const { return mX != LARGE; }
+
+ // If the bounds can fit in an int16_t, set them. Otherwise, leave the
+ // bounds invalid to signal that GetFTGlyphExtents should be queried
+ // directly.
+ void SetBounds(const mozilla::gfx::IntRect& aBounds) {
+ if (aBounds.x > INT16_MIN && aBounds.x < INT16_MAX &&
+ aBounds.y > INT16_MIN && aBounds.y < INT16_MAX &&
+ aBounds.width <= UINT16_MAX && aBounds.height <= UINT16_MAX) {
+ mX = aBounds.x;
+ mY = aBounds.y;
+ mWidth = aBounds.width;
+ mHeight = aBounds.height;
+ } else {
+ mX = LARGE;
+ }
+ }
+
+ mozilla::gfx::IntRect GetBounds() const {
+ return mozilla::gfx::IntRect(mX, mY, mWidth, mHeight);
+ }
+
+ int32_t mAdvance;
+ int16_t mX;
+ int16_t mY;
+ uint16_t mWidth;
+ uint16_t mHeight;
+ };
+
+ const GlyphMetrics& GetCachedGlyphMetrics(
+ uint16_t aGID, mozilla::gfx::IntRect* aBounds = nullptr) const;
+
+ mutable mozilla::UniquePtr<nsTHashMap<nsUint32HashKey, GlyphMetrics>>
+ mGlyphMetrics MOZ_GUARDED_BY(mLock);
+};
+
+#endif /* GFX_FT2FONTBASE_H */
diff --git a/gfx/thebes/gfxFT2FontList.cpp b/gfx/thebes/gfxFT2FontList.cpp
new file mode 100644
index 0000000000..372abce92d
--- /dev/null
+++ b/gfx/thebes/gfxFT2FontList.cpp
@@ -0,0 +1,1895 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Base64.h"
+#include "mozilla/MemoryReporting.h"
+
+#include "mozilla/dom/ContentChild.h"
+#include "gfxAndroidPlatform.h"
+#include "mozilla/Omnijar.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "nsReadableUtils.h"
+
+#include "nsXULAppAPI.h"
+#include <dirent.h>
+#include <android/log.h>
+#define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gecko", ##args)
+
+#include "ft2build.h"
+#include FT_FREETYPE_H
+#include FT_TRUETYPE_TAGS_H
+#include FT_TRUETYPE_TABLES_H
+#include FT_MULTIPLE_MASTERS_H
+#include "cairo-ft.h"
+
+#include "gfxFT2FontList.h"
+#include "gfxFT2Fonts.h"
+#include "gfxFT2Utils.h"
+#include "gfxUserFontSet.h"
+#include "gfxFontUtils.h"
+#include "SharedFontList-impl.h"
+#include "harfbuzz/hb-ot.h" // for name ID constants
+
+#include "nsServiceManagerUtils.h"
+#include "nsIObserverService.h"
+#include "nsTArray.h"
+#include "nsUnicharUtils.h"
+#include "nsCRT.h"
+
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsMemory.h"
+#include "nsPresContext.h"
+#include "gfxFontConstants.h"
+
+#include "mozilla/EndianUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/scache/StartupCache.h"
+#include "mozilla/Telemetry.h"
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "AndroidBuild.h"
+# include "mozilla/jni/Utils.h"
+# include <dlfcn.h>
+#endif
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+static LazyLogModule sFontInfoLog("fontInfoLog");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(sFontInfoLog, mozilla::LogLevel::Debug, args)
+#define LOG_ENABLED() MOZ_LOG_TEST(sFontInfoLog, mozilla::LogLevel::Debug)
+
+static __inline void BuildKeyNameFromFontName(nsACString& aName) {
+ ToLowerCase(aName);
+}
+
+// Helper to access the FT_Face for a given FT2FontEntry,
+// creating a temporary face if the entry does not have one yet.
+// This allows us to read font names, tables, etc if necessary
+// without permanently instantiating a freetype face and consuming
+// memory long-term.
+// This may fail (resulting in a null FT_Face), e.g. if it fails to
+// allocate memory to uncompress a font from omnijar.
+already_AddRefed<SharedFTFace> FT2FontEntry::GetFTFace(bool aCommit) {
+ if (mFTFace) {
+ // Create a new reference, and return it.
+ RefPtr<SharedFTFace> face(mFTFace);
+ return face.forget();
+ }
+
+ NS_ASSERTION(!mFilename.IsEmpty(),
+ "can't use GetFTFace for fonts without a filename");
+
+ // A relative path (no initial "/") means this is a resource in
+ // omnijar, not an installed font on the device.
+ // The NS_ASSERTIONs here should never fail, as the resource must have
+ // been read successfully during font-list initialization or we'd never
+ // have created the font entry. The only legitimate runtime failure
+ // here would be memory allocation, in which case mFace remains null.
+ RefPtr<SharedFTFace> face;
+ if (mFilename[0] != '/') {
+ RefPtr<nsZipArchive> reader = Omnijar::GetReader(Omnijar::Type::GRE);
+ nsZipItem* item = reader->GetItem(mFilename.get());
+ NS_ASSERTION(item, "failed to find zip entry");
+
+ uint32_t bufSize = item->RealSize();
+ uint8_t* fontDataBuf = static_cast<uint8_t*>(malloc(bufSize));
+ if (fontDataBuf) {
+ nsZipCursor cursor(item, reader, fontDataBuf, bufSize);
+ cursor.Copy(&bufSize);
+ NS_ASSERTION(bufSize == item->RealSize(), "error reading bundled font");
+ RefPtr<FTUserFontData> ufd = new FTUserFontData(fontDataBuf, bufSize);
+ face = ufd->CloneFace(mFTFontIndex);
+ if (!face) {
+ NS_WARNING("failed to create freetype face");
+ return nullptr;
+ }
+ }
+ } else {
+ RefPtr<FTUserFontData> fd = new FTUserFontData(mFilename.get());
+ face = fd->CloneFace(mFTFontIndex);
+ if (!face) {
+ NS_WARNING("failed to create freetype face");
+ return nullptr;
+ }
+ if (FT_Err_Ok != FT_Select_Charmap(face->GetFace(), FT_ENCODING_UNICODE) &&
+ FT_Err_Ok !=
+ FT_Select_Charmap(face->GetFace(), FT_ENCODING_MS_SYMBOL)) {
+ NS_WARNING("failed to select Unicode or symbol charmap");
+ }
+ }
+
+ if (aCommit) {
+ if (mFTFace.compareExchange(nullptr, face.get())) {
+ // The reference we created is now owned by mFTFace.
+ Unused << face.forget();
+ } else {
+ // We lost a race! Just discard our new face and use the existing one.
+ }
+ }
+
+ // Create a new reference, and return it.
+ face = mFTFace;
+ return face.forget();
+}
+
+FTUserFontData* FT2FontEntry::GetUserFontData() {
+ if (SharedFTFace* face = mFTFace) {
+ return static_cast<FTUserFontData*>(face->GetData());
+ }
+ return nullptr;
+}
+
+/*
+ * FT2FontEntry
+ * gfxFontEntry subclass corresponding to a specific face that can be
+ * rendered by freetype. This is associated with a face index in a
+ * file (normally a .ttf/.otf file holding a single face, but in principle
+ * there could be .ttc files with multiple faces).
+ * The FT2FontEntry can create the necessary FT_Face on demand, and can
+ * then create a Cairo font_face and scaled_font for drawing.
+ */
+
+FT2FontEntry::~FT2FontEntry() {
+ if (mMMVar) {
+ SharedFTFace* face = mFTFace;
+ FT_Done_MM_Var(face->GetFace()->glyph->library, mMMVar);
+ }
+ if (mFTFace) {
+ auto face = mFTFace.exchange(nullptr);
+ NS_IF_RELEASE(face);
+ }
+}
+
+gfxFontEntry* FT2FontEntry::Clone() const {
+ MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!");
+ FT2FontEntry* fe = new FT2FontEntry(Name());
+ fe->mFilename = mFilename;
+ fe->mFTFontIndex = mFTFontIndex;
+ fe->mWeightRange = mWeightRange;
+ fe->mStretchRange = mStretchRange;
+ fe->mStyleRange = mStyleRange;
+ return fe;
+}
+
+gfxFont* FT2FontEntry::CreateFontInstance(const gfxFontStyle* aStyle) {
+ RefPtr<SharedFTFace> face = GetFTFace(true);
+ if (!face) {
+ return nullptr;
+ }
+
+ if (face->GetFace()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) {
+ // Resolve variations from entry (descriptor) and style (property)
+ AutoTArray<gfxFontVariation, 8> settings;
+ GetVariationsForStyle(settings, aStyle ? *aStyle : gfxFontStyle());
+
+ // If variations are present, we will not use our cached mFTFace
+ // but always create a new one as it will have custom variation
+ // coordinates applied.
+ if (!settings.IsEmpty()) {
+ // Create a separate FT_Face because we need to apply custom
+ // variation settings to it.
+ RefPtr<SharedFTFace> varFace;
+ if (!mFilename.IsEmpty() && mFilename[0] == '/') {
+ varFace =
+ Factory::NewSharedFTFace(nullptr, mFilename.get(), mFTFontIndex);
+ } else {
+ varFace = face->GetData()->CloneFace(mFTFontIndex);
+ }
+ if (varFace) {
+ gfxFT2FontBase::SetupVarCoords(GetMMVar(), settings,
+ varFace->GetFace());
+ face = std::move(varFace);
+ }
+ }
+ }
+
+ int loadFlags = gfxPlatform::GetPlatform()->FontHintingEnabled()
+ ? FT_LOAD_DEFAULT
+ : (FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING);
+ if (face->GetFace()->face_flags & FT_FACE_FLAG_TRICKY) {
+ loadFlags &= ~FT_LOAD_NO_AUTOHINT;
+ }
+
+ RefPtr<UnscaledFontFreeType> unscaledFont(mUnscaledFont);
+ if (!unscaledFont) {
+ RefPtr<SharedFTFace> origFace(mFTFace);
+ unscaledFont =
+ !mFilename.IsEmpty() && mFilename[0] == '/'
+ ? new UnscaledFontFreeType(mFilename.BeginReading(), mFTFontIndex,
+ std::move(origFace))
+ : new UnscaledFontFreeType(std::move(origFace));
+ mUnscaledFont = unscaledFont;
+ }
+
+ gfxFont* font =
+ new gfxFT2Font(unscaledFont, std::move(face), this, aStyle, loadFlags);
+ return font;
+}
+
+/* static */
+FT2FontEntry* FT2FontEntry::CreateFontEntry(
+ const nsACString& aFontName, WeightRange aWeight, StretchRange aStretch,
+ SlantStyleRange aStyle, const uint8_t* aFontData, uint32_t aLength) {
+ // Ownership of aFontData is passed in here; the fontEntry must
+ // retain it as long as the FT_Face needs it, and ensure it is
+ // eventually deleted.
+ RefPtr<FTUserFontData> ufd = new FTUserFontData(aFontData, aLength);
+ RefPtr<SharedFTFace> face = ufd->CloneFace();
+ if (!face) {
+ return nullptr;
+ }
+ // Create our FT2FontEntry, which inherits the name of the userfont entry
+ // as it's not guaranteed that the face has valid names (bug 737315)
+ FT2FontEntry* fe =
+ FT2FontEntry::CreateFontEntry(aFontName, nullptr, 0, nullptr);
+ if (fe) {
+ fe->mFTFace = face.forget().take(); // mFTFace takes ownership.
+ fe->mStyleRange = aStyle;
+ fe->mWeightRange = aWeight;
+ fe->mStretchRange = aStretch;
+ fe->mIsDataUserFont = true;
+ }
+ return fe;
+}
+
+/* static */
+FT2FontEntry* FT2FontEntry::CreateFontEntry(const FontListEntry& aFLE) {
+ FT2FontEntry* fe = new FT2FontEntry(aFLE.faceName());
+ fe->mFilename = aFLE.filepath();
+ fe->mFTFontIndex = aFLE.index();
+ fe->mWeightRange = WeightRange::FromScalar(aFLE.weightRange());
+ fe->mStretchRange = StretchRange::FromScalar(aFLE.stretchRange());
+ fe->mStyleRange = SlantStyleRange::FromScalar(aFLE.styleRange());
+ return fe;
+}
+
+// Extract font entry properties from an hb_face_t
+static void SetPropertiesFromFace(gfxFontEntry* aFontEntry,
+ const hb_face_t* aFace) {
+ // OS2 width class to CSS 'stretch' mapping from
+ // https://docs.microsoft.com/en-gb/typography/opentype/spec/os2#uswidthclass
+ const float kOS2WidthToStretch[] = {
+ 100, // (invalid, treat as normal)
+ 50, // Ultra-condensed
+ 62.5, // Extra-condensed
+ 75, // Condensed
+ 87.5, // Semi-condensed
+ 100, // Normal
+ 112.5, // Semi-expanded
+ 125, // Expanded
+ 150, // Extra-expanded
+ 200 // Ultra-expanded
+ };
+
+ // Get the macStyle field from the 'head' table
+ gfxFontUtils::AutoHBBlob headBlob(
+ hb_face_reference_table(aFace, HB_TAG('h', 'e', 'a', 'd')));
+ unsigned int len;
+ const char* data = hb_blob_get_data(headBlob, &len);
+ uint16_t style = 0;
+ if (len >= sizeof(HeadTable)) {
+ const HeadTable* head = reinterpret_cast<const HeadTable*>(data);
+ style = head->macStyle;
+ }
+
+ // Get the OS/2 table for weight & width fields
+ gfxFontUtils::AutoHBBlob os2blob(
+ hb_face_reference_table(aFace, HB_TAG('O', 'S', '/', '2')));
+ data = hb_blob_get_data(os2blob, &len);
+ uint16_t os2weight = 400;
+ float stretch = 100.0;
+ if (len >= offsetof(OS2Table, fsType)) {
+ const OS2Table* os2 = reinterpret_cast<const OS2Table*>(data);
+ os2weight = os2->usWeightClass;
+ uint16_t os2width = os2->usWidthClass;
+ if (os2width < ArrayLength(kOS2WidthToStretch)) {
+ stretch = kOS2WidthToStretch[os2width];
+ }
+ }
+
+ aFontEntry->mStyleRange = SlantStyleRange(
+ (style & 2) ? FontSlantStyle::ITALIC : FontSlantStyle::NORMAL);
+ aFontEntry->mWeightRange = WeightRange(FontWeight::FromInt(int(os2weight)));
+ aFontEntry->mStretchRange = StretchRange(FontStretch::FromFloat(stretch));
+
+ // For variable fonts, update the style/weight/stretch attributes if the
+ // corresponding variation axes are present.
+ aFontEntry->SetupVariationRanges();
+}
+
+// Used to create the font entry for installed faces on the device,
+// when iterating over the fonts directories.
+// We use the hb_face_t to retrieve the details needed for the font entry,
+// but do *not* save a reference to it, nor create a cairo face.
+/* static */
+FT2FontEntry* FT2FontEntry::CreateFontEntry(const nsACString& aName,
+ const char* aFilename,
+ uint8_t aIndex,
+ const hb_face_t* aFace) {
+ FT2FontEntry* fe = new FT2FontEntry(aName);
+ fe->mFilename = aFilename;
+ fe->mFTFontIndex = aIndex;
+
+ if (aFace) {
+ SetPropertiesFromFace(fe, aFace);
+ } else {
+ // If nullptr is passed for aFace, the caller is intending to override
+ // these attributes anyway. We just set defaults here to be safe.
+ fe->mStyleRange = SlantStyleRange(FontSlantStyle::NORMAL);
+ fe->mWeightRange = WeightRange(FontWeight::NORMAL);
+ fe->mStretchRange = StretchRange(FontStretch::NORMAL);
+ }
+
+ return fe;
+}
+
+FT2FontEntry* gfxFT2Font::GetFontEntry() {
+ return static_cast<FT2FontEntry*>(mFontEntry.get());
+}
+
+// Copied/modified from similar code in gfxMacPlatformFontList.mm:
+// Complex scripts will not render correctly unless Graphite or OT
+// layout tables are present.
+// For OpenType, we also check that the GSUB table supports the relevant
+// script tag, to avoid using things like Arial Unicode MS for Lao (it has
+// the characters, but lacks OpenType support).
+
+// TODO: consider whether we should move this to gfxFontEntry and do similar
+// cmap-masking on all platforms to avoid using fonts that won't shape
+// properly.
+
+nsresult FT2FontEntry::ReadCMAP(FontInfoData* aFontInfoData) {
+ if (mCharacterMap || mShmemCharacterMap) {
+ return NS_OK;
+ }
+
+ RefPtr<gfxCharacterMap> charmap = new gfxCharacterMap();
+
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ uint32_t uvsOffset = 0;
+ gfxFontUtils::AutoHBBlob cmapBlob(GetFontTable(TTAG_cmap));
+ if (cmapBlob) {
+ unsigned int length;
+ const char* data = hb_blob_get_data(cmapBlob, &length);
+ rv = gfxFontUtils::ReadCMAP((const uint8_t*)data, length, *charmap,
+ uvsOffset);
+ }
+ mUVSOffset.exchange(uvsOffset);
+
+ if (NS_SUCCEEDED(rv) && !mIsDataUserFont && !HasGraphiteTables()) {
+ // For downloadable fonts, trust the author and don't
+ // try to munge the cmap based on script shaping support.
+
+ // We also assume a Graphite font knows what it's doing,
+ // and provides whatever shaping is needed for the
+ // characters it supports, so only check/clear the
+ // complex-script ranges for non-Graphite fonts
+
+ // for layout support, check for the presence of opentype layout tables
+ bool hasGSUB = HasFontTable(TRUETYPE_TAG('G', 'S', 'U', 'B'));
+
+ for (const ScriptRange* sr = gfxPlatformFontList::sComplexScriptRanges;
+ sr->rangeStart; sr++) {
+ // check to see if the cmap includes complex script codepoints
+ if (charmap->TestRange(sr->rangeStart, sr->rangeEnd)) {
+ // We check for GSUB here, as GPOS alone would not be ok.
+ if (hasGSUB && SupportsScriptInGSUB(sr->tags, sr->numTags)) {
+ continue;
+ }
+ charmap->ClearRange(sr->rangeStart, sr->rangeEnd);
+ }
+ }
+ }
+
+#ifdef MOZ_WIDGET_ANDROID
+ // Hack for the SamsungDevanagari font, bug 1012365:
+ // pretend the font supports U+0972.
+ if (!charmap->test(0x0972) && charmap->test(0x0905) &&
+ charmap->test(0x0945)) {
+ charmap->set(0x0972);
+ }
+#endif
+
+ bool setCharMap = true;
+ if (NS_SUCCEEDED(rv)) {
+ gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
+ fontlist::FontList* sharedFontList = pfl->SharedFontList();
+ if (!IsUserFont() && mShmemFace) {
+ mShmemFace->SetCharacterMap(sharedFontList, charmap); // async
+ if (TrySetShmemCharacterMap()) {
+ setCharMap = false;
+ }
+ } else {
+ charmap = pfl->FindCharMap(charmap);
+ }
+ mHasCmapTable = true;
+ } else {
+ // if error occurred, initialize to null cmap
+ charmap = new gfxCharacterMap();
+ mHasCmapTable = false;
+ }
+ if (setCharMap) {
+ if (mCharacterMap.compareExchange(nullptr, charmap.get())) {
+ // We forget rather than addref because we don't use the charmap below.
+ Unused << charmap.forget();
+ }
+ }
+
+ return rv;
+}
+
+hb_face_t* FT2FontEntry::CreateHBFace() const {
+ hb_face_t* result = nullptr;
+
+ if (mFilename[0] == '/') {
+ // An absolute path means a normal file in the filesystem, so we can use
+ // hb_blob_create_from_file to read it.
+ gfxFontUtils::AutoHBBlob fileBlob(
+ hb_blob_create_from_file(mFilename.get()));
+ if (hb_blob_get_length(fileBlob) > 0) {
+ result = hb_face_create(fileBlob, mFTFontIndex);
+ }
+ } else {
+ // A relative path means an omnijar resource, which we may need to
+ // decompress to a temporary buffer.
+ RefPtr<nsZipArchive> reader = Omnijar::GetReader(Omnijar::Type::GRE);
+ nsZipItem* item = reader->GetItem(mFilename.get());
+ MOZ_ASSERT(item, "failed to find zip entry");
+ if (item) {
+ // TODO(jfkthame):
+ // Check whether the item is compressed; if not, we could just get a
+ // pointer without needing to allocate a buffer and copy the data.
+ // (Currently this configuration isn't used for Gecko on Android.)
+ uint32_t length = item->RealSize();
+ uint8_t* buffer = static_cast<uint8_t*>(malloc(length));
+ if (buffer) {
+ nsZipCursor cursor(item, reader, buffer, length);
+ cursor.Copy(&length);
+ MOZ_ASSERT(length == item->RealSize(), "error reading font");
+ if (length == item->RealSize()) {
+ gfxFontUtils::AutoHBBlob blob(
+ hb_blob_create((const char*)buffer, length,
+ HB_MEMORY_MODE_READONLY, buffer, free));
+ result = hb_face_create(blob, mFTFontIndex);
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+bool FT2FontEntry::HasFontTable(uint32_t aTableTag) {
+ // If we already have a FreeType face, we can just use that.
+ if (mFTFace) {
+ RefPtr<SharedFTFace> face = GetFTFace();
+ return gfxFT2FontEntryBase::FaceHasTable(face, aTableTag);
+ }
+
+ {
+ // If we have a cached set of tables, query that.
+ AutoReadLock lock(mLock);
+ if (mAvailableTables.Count() > 0) {
+ return mAvailableTables.Contains(aTableTag);
+ }
+ }
+
+ // If we haven't created a FreeType face already, try to avoid that by
+ // reading the available table tags via harfbuzz and caching in a hashset.
+ AutoWriteLock lock(mLock);
+ if (!mFTFace && !mFilename.IsEmpty()) {
+ hb_face_t* face = CreateHBFace();
+ if (face) {
+ // Read table tags in batches; 32 should be enough for most fonts in a
+ // single operation.
+ const unsigned TAG_BUF_LENGTH = 32;
+ hb_tag_t tags[TAG_BUF_LENGTH];
+ unsigned int startOffset = 0;
+ unsigned int totalTables = 0;
+ do {
+ unsigned int count = TAG_BUF_LENGTH;
+ // Updates count to the number of table tags actually retrieved
+ totalTables = hb_face_get_table_tags(face, startOffset, &count, tags);
+ startOffset += count;
+ while (count-- > 0) {
+ mAvailableTables.Insert(tags[count]);
+ }
+ } while (startOffset < totalTables);
+ hb_face_destroy(face);
+ } else {
+ // Failed to create the HarfBuzz face! The font is probably broken.
+ // Put a dummy entry in mAvailableTables so that we don't bother
+ // re-trying here.
+ mAvailableTables.Insert(uint32_t(-1));
+ }
+ return mAvailableTables.Contains(aTableTag);
+ }
+
+ // Last resort: we'll have to create a (temporary) FreeType face to query
+ // for table presence.
+ RefPtr<SharedFTFace> face = GetFTFace();
+ return gfxFT2FontEntryBase::FaceHasTable(face, aTableTag);
+}
+
+nsresult FT2FontEntry::CopyFontTable(uint32_t aTableTag,
+ nsTArray<uint8_t>& aBuffer) {
+ RefPtr<SharedFTFace> face = GetFTFace();
+ return gfxFT2FontEntryBase::CopyFaceTable(face, aTableTag, aBuffer);
+}
+
+hb_blob_t* FT2FontEntry::GetFontTable(uint32_t aTableTag) {
+ if (FTUserFontData* userFontData = GetUserFontData()) {
+ // If there's a cairo font face, we may be able to return a blob
+ // that just wraps a range of the attached user font data
+ if (userFontData->FontData()) {
+ return gfxFontUtils::GetTableFromFontData(userFontData->FontData(),
+ aTableTag);
+ }
+ }
+
+ // If the FT_Face hasn't been instantiated, try to read table directly
+ // via harfbuzz API to avoid expensive FT_Face creation.
+ if (!mFTFace && !mFilename.IsEmpty()) {
+ hb_face_t* face = CreateHBFace();
+ if (face) {
+ hb_blob_t* result = hb_face_reference_table(face, aTableTag);
+ hb_face_destroy(face);
+ return result;
+ }
+ }
+
+ // Otherwise, use the default method (which in turn will call our
+ // implementation of CopyFontTable).
+ return gfxFontEntry::GetFontTable(aTableTag);
+}
+
+bool FT2FontEntry::HasVariations() {
+ switch (mHasVariations) {
+ case HasVariationsState::No:
+ return false;
+ case HasVariationsState::Yes:
+ return true;
+ case HasVariationsState::Uninitialized:
+ break;
+ }
+
+ SharedFTFace* face = mFTFace;
+ bool hasVariations =
+ face ? face->GetFace()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS
+ : gfxPlatform::HasVariationFontSupport() &&
+ HasFontTable(TRUETYPE_TAG('f', 'v', 'a', 'r'));
+ mHasVariations =
+ hasVariations ? HasVariationsState::Yes : HasVariationsState::No;
+ return hasVariations;
+}
+
+void FT2FontEntry::GetVariationAxes(nsTArray<gfxFontVariationAxis>& aAxes) {
+ if (!HasVariations()) {
+ return;
+ }
+ if (FT_MM_Var* mmVar = GetMMVar()) {
+ gfxFT2Utils::GetVariationAxes(mmVar, aAxes);
+ }
+}
+
+void FT2FontEntry::GetVariationInstances(
+ nsTArray<gfxFontVariationInstance>& aInstances) {
+ if (!HasVariations()) {
+ return;
+ }
+ if (FT_MM_Var* mmVar = GetMMVar()) {
+ gfxFT2Utils::GetVariationInstances(this, mmVar, aInstances);
+ }
+}
+
+FT_MM_Var* FT2FontEntry::GetMMVar() {
+ if (mMMVarInitialized) {
+ return mMMVar;
+ }
+ AutoWriteLock lock(mLock);
+ RefPtr<SharedFTFace> face = GetFTFace(true);
+ if (!face) {
+ return nullptr;
+ }
+ if (FT_Err_Ok != FT_Get_MM_Var(face->GetFace(), &mMMVar)) {
+ mMMVar = nullptr;
+ }
+ mMMVarInitialized = true;
+ return mMMVar;
+}
+
+void FT2FontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ gfxFontEntry::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+ aSizes->mFontListSize +=
+ mFilename.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+}
+
+void FT2FontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ aSizes->mFontListSize += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
+
+/*
+ * FT2FontFamily
+ * A standard gfxFontFamily; just adds a method used to support sending
+ * the font list from chrome to content via IPC.
+ */
+
+void FT2FontFamily::AddFacesToFontList(nsTArray<FontListEntry>* aFontList) {
+ AutoReadLock lock(mLock);
+ for (int i = 0, n = mAvailableFonts.Length(); i < n; ++i) {
+ const FT2FontEntry* fe =
+ static_cast<const FT2FontEntry*>(mAvailableFonts[i].get());
+ if (!fe) {
+ continue;
+ }
+
+ aFontList->AppendElement(FontListEntry(
+ Name(), fe->Name(), fe->mFilename, fe->Weight().AsScalar(),
+ fe->Stretch().AsScalar(), fe->SlantStyle().AsScalar(), fe->mFTFontIndex,
+ Visibility()));
+ }
+}
+
+/*
+ * Startup cache support for the font list:
+ * We store the list of families and faces, with their style attributes and the
+ * corresponding font files, in the startup cache.
+ * This allows us to recreate the gfxFT2FontList collection of families and
+ * faces without instantiating Freetype faces for each font file (in order to
+ * find their attributes), leading to significantly quicker startup.
+ */
+
+#define CACHE_KEY "font.cached-list"
+
+void gfxFT2FontList::CollectInitData(const FontListEntry& aFLE,
+ const nsCString& aPSName,
+ const nsCString& aFullName,
+ StandardFile aStdFile) {
+ nsAutoCString key(aFLE.familyName());
+ BuildKeyNameFromFontName(key);
+ mFaceInitData
+ .LookupOrInsertWith(
+ key,
+ [&] {
+ mFamilyInitData.AppendElement(
+ fontlist::Family::InitData{key, aFLE.familyName()});
+ return MakeUnique<nsTArray<fontlist::Face::InitData>>();
+ })
+ ->AppendElement(fontlist::Face::InitData{
+ aFLE.filepath(), aFLE.index(), false,
+ WeightRange::FromScalar(aFLE.weightRange()),
+ StretchRange::FromScalar(aFLE.stretchRange()),
+ SlantStyleRange::FromScalar(aFLE.styleRange())});
+ nsAutoCString psname(aPSName), fullname(aFullName);
+ if (!psname.IsEmpty()) {
+ ToLowerCase(psname);
+ mLocalNameTable.InsertOrUpdate(
+ psname, fontlist::LocalFaceRec::InitData(key, aFLE.filepath()));
+ }
+ if (!fullname.IsEmpty()) {
+ ToLowerCase(fullname);
+ if (fullname != psname) {
+ mLocalNameTable.InsertOrUpdate(
+ fullname, fontlist::LocalFaceRec::InitData(key, aFLE.filepath()));
+ }
+ }
+}
+
+class FontNameCache {
+ public:
+ // Delimiters used in the cached font-list records we store in startupCache
+ static const char kFileSep = 0x1c;
+ static const char kGroupSep = 0x1d;
+ static const char kRecordSep = 0x1e;
+ static const char kFieldSep = 0x1f;
+
+ // Separator for font property ranges; we only look for this within a
+ // field that holds a serialized FontPropertyValue or Range, so there's no
+ // risk of conflicting with printable characters in font names.
+ // Note that this must be a character that will terminate strtof() parsing
+ // of a number.
+ static const char kRangeSep = ':';
+
+ // Creates the object but does NOT load the cached data from the startup
+ // cache; call Init() after creation to do that.
+ FontNameCache() : mMap(&mOps, sizeof(FNCMapEntry), 0), mWriteNeeded(false) {
+ // HACK ALERT: it's weird to assign |mOps| after we passed a pointer to
+ // it to |mMap|'s constructor. A more normal approach here would be to
+ // have a static |sOps| member. Unfortunately, this mysteriously but
+ // consistently makes Fennec start-up slower, so we take this
+ // unorthodox approach instead. It's safe because PLDHashTable's
+ // constructor doesn't dereference the pointer; it just makes a copy of
+ // it.
+ mOps = (PLDHashTableOps){StringHash, HashMatchEntry, MoveEntry,
+ PLDHashTable::ClearEntryStub, nullptr};
+
+ MOZ_ASSERT(XRE_IsParentProcess(),
+ "FontNameCache should only be used in chrome process");
+ mCache = mozilla::scache::StartupCache::GetSingleton();
+ }
+
+ ~FontNameCache() { WriteCache(); }
+
+ size_t EntryCount() const { return mMap.EntryCount(); }
+
+ void DropStaleEntries() {
+ for (auto iter = mMap.ConstIter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<FNCMapEntry*>(iter.Get());
+ if (!entry->mFileExists) {
+ iter.Remove();
+ }
+ }
+ }
+
+ void WriteCache() {
+ if (!mWriteNeeded || !mCache) {
+ return;
+ }
+
+ LOG(("Writing FontNameCache:"));
+ nsAutoCString buf;
+ for (auto iter = mMap.ConstIter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<FNCMapEntry*>(iter.Get());
+ MOZ_ASSERT(entry->mFileExists);
+ buf.Append(entry->mFilename);
+ buf.Append(kGroupSep);
+ buf.Append(entry->mFaces);
+ buf.Append(kGroupSep);
+ buf.AppendInt(entry->mTimestamp);
+ buf.Append(kGroupSep);
+ buf.AppendInt(entry->mFilesize);
+ buf.Append(kFileSep);
+ }
+
+ LOG(("putting FontNameCache to " CACHE_KEY ", length %zu",
+ buf.Length() + 1));
+ mCache->PutBuffer(CACHE_KEY, UniqueFreePtr<char[]>(ToNewCString(buf)),
+ buf.Length() + 1);
+ mWriteNeeded = false;
+ }
+
+ // This may be called more than once (if we re-load the font list).
+ void Init() {
+ if (!mCache) {
+ return;
+ }
+
+ uint32_t size;
+ const char* cur;
+ if (NS_FAILED(mCache->GetBuffer(CACHE_KEY, &cur, &size))) {
+ LOG(("no cache of " CACHE_KEY));
+ return;
+ }
+
+ LOG(("got: %u bytes from the cache " CACHE_KEY, size));
+
+ mMap.Clear();
+ mWriteNeeded = false;
+
+ while (const char* fileEnd = strchr(cur, kFileSep)) {
+ // The cached record for one file is at [cur, fileEnd].
+
+ // Find end of field that starts at aStart, terminated by kGroupSep or
+ // end of record.
+ auto endOfField = [=](const char* aStart) -> const char* {
+ MOZ_ASSERT(aStart <= fileEnd);
+ const char* end = static_cast<const char*>(
+ memchr(aStart, kGroupSep, fileEnd - aStart));
+ if (end) {
+ return end;
+ }
+ return fileEnd;
+ };
+
+ // Advance aStart and aEnd to indicate the range of the next field and
+ // return true, or just return false if already at end of record.
+ auto nextField = [=](const char*& aStart, const char*& aEnd) -> bool {
+ if (aEnd < fileEnd) {
+ aStart = aEnd + 1;
+ aEnd = endOfField(aStart);
+ return true;
+ }
+ return false;
+ };
+
+ const char* end = endOfField(cur);
+ nsCString filename(cur, end - cur);
+ if (!nextField(cur, end)) {
+ break;
+ }
+ nsCString faceList(cur, end - cur);
+ if (!nextField(cur, end)) {
+ break;
+ }
+ uint32_t timestamp = strtoul(cur, nullptr, 10);
+ if (!nextField(cur, end)) {
+ break;
+ }
+ uint32_t filesize = strtoul(cur, nullptr, 10);
+
+ auto mapEntry =
+ static_cast<FNCMapEntry*>(mMap.Add(filename.get(), fallible));
+ if (mapEntry) {
+ mapEntry->mFilename.Assign(filename);
+ mapEntry->mTimestamp = timestamp;
+ mapEntry->mFilesize = filesize;
+ mapEntry->mFaces.Assign(faceList);
+ // entries from the startupcache are marked "non-existing"
+ // until we have confirmed that the file still exists
+ mapEntry->mFileExists = false;
+ }
+
+ cur = fileEnd + 1;
+ }
+ }
+
+ void GetInfoForFile(const nsCString& aFileName, nsCString& aFaceList,
+ uint32_t* aTimestamp, uint32_t* aFilesize) {
+ auto entry = static_cast<FNCMapEntry*>(mMap.Search(aFileName.get()));
+ if (entry) {
+ *aTimestamp = entry->mTimestamp;
+ *aFilesize = entry->mFilesize;
+ aFaceList.Assign(entry->mFaces);
+ // this entry does correspond to an existing file
+ // (although it might not be up-to-date, in which case
+ // it will get overwritten via CacheFileInfo)
+ entry->mFileExists = true;
+ }
+ }
+
+ void CacheFileInfo(const nsCString& aFileName, const nsCString& aFaceList,
+ uint32_t aTimestamp, uint32_t aFilesize) {
+ auto entry = static_cast<FNCMapEntry*>(mMap.Add(aFileName.get(), fallible));
+ if (entry) {
+ entry->mFilename.Assign(aFileName);
+ entry->mTimestamp = aTimestamp;
+ entry->mFilesize = aFilesize;
+ entry->mFaces.Assign(aFaceList);
+ entry->mFileExists = true;
+ }
+ mWriteNeeded = true;
+ }
+
+ private:
+ mozilla::scache::StartupCache* mCache;
+ PLDHashTable mMap;
+ bool mWriteNeeded;
+
+ PLDHashTableOps mOps;
+
+ struct FNCMapEntry : public PLDHashEntryHdr {
+ public:
+ nsCString mFilename;
+ uint32_t mTimestamp;
+ uint32_t mFilesize;
+ nsCString mFaces;
+ bool mFileExists;
+ };
+
+ static PLDHashNumber StringHash(const void* key) {
+ return HashString(reinterpret_cast<const char*>(key));
+ }
+
+ static bool HashMatchEntry(const PLDHashEntryHdr* aHdr, const void* key) {
+ const FNCMapEntry* entry = static_cast<const FNCMapEntry*>(aHdr);
+ return entry->mFilename.Equals(reinterpret_cast<const char*>(key));
+ }
+
+ static void MoveEntry(PLDHashTable* table, const PLDHashEntryHdr* aFrom,
+ PLDHashEntryHdr* aTo) {
+ FNCMapEntry* to = static_cast<FNCMapEntry*>(aTo);
+ const FNCMapEntry* from = static_cast<const FNCMapEntry*>(aFrom);
+ to->mFilename.Assign(from->mFilename);
+ to->mTimestamp = from->mTimestamp;
+ to->mFilesize = from->mFilesize;
+ to->mFaces.Assign(from->mFaces);
+ to->mFileExists = from->mFileExists;
+ }
+};
+
+/***************************************************************
+ *
+ * gfxFT2FontList
+ *
+ */
+
+// For Mobile, we use gfxFT2Fonts, and we build the font list by directly
+// scanning the system's Fonts directory for OpenType and TrueType files.
+
+#define JAR_LAST_MODIFED_TIME "jar-last-modified-time"
+
+class WillShutdownObserver : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ explicit WillShutdownObserver(gfxFT2FontList* aFontList)
+ : mFontList(aFontList) {}
+
+ void Remove() {
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
+ }
+ mFontList = nullptr;
+ }
+
+ protected:
+ virtual ~WillShutdownObserver() = default;
+
+ gfxFT2FontList* mFontList;
+};
+
+NS_IMPL_ISUPPORTS(WillShutdownObserver, nsIObserver)
+
+NS_IMETHODIMP
+WillShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!nsCRT::strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID)) {
+ mFontList->WillShutdown();
+ } else {
+ MOZ_ASSERT_UNREACHABLE("unexpected notification topic");
+ }
+ return NS_OK;
+}
+
+gfxFT2FontList::gfxFT2FontList() : mJarModifiedTime(0) {
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ mObserver = new WillShutdownObserver(this);
+ obs->AddObserver(mObserver, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false);
+ }
+}
+
+gfxFT2FontList::~gfxFT2FontList() {
+ AutoLock lock(mLock);
+ if (mObserver) {
+ mObserver->Remove();
+ }
+}
+
+bool gfxFT2FontList::AppendFacesFromCachedFaceList(CollectFunc aCollectFace,
+ const nsCString& aFileName,
+ const nsCString& aFaceList,
+ StandardFile aStdFile) {
+ const char* start = aFaceList.get();
+ int count = 0;
+
+ while (const char* recEnd = strchr(start, FontNameCache::kRecordSep)) {
+ auto endOfField = [=](const char* aStart) -> const char* {
+ MOZ_ASSERT(aStart <= recEnd);
+ const char* end = static_cast<const char*>(
+ memchr(aStart, FontNameCache::kFieldSep, recEnd - aStart));
+ if (end) {
+ return end;
+ }
+ return recEnd;
+ };
+
+ auto nextField = [=](const char*& aStart, const char*& aEnd) -> bool {
+ if (aEnd < recEnd) {
+ aStart = aEnd + 1;
+ aEnd = endOfField(aStart);
+ return true;
+ }
+ return false;
+ };
+
+ const char* end = endOfField(start);
+ nsAutoCString familyName(start, end - start);
+ nsAutoCString key(familyName);
+ ToLowerCase(key);
+
+ if (!nextField(start, end)) {
+ break;
+ }
+ nsAutoCString faceName(start, end - start);
+
+ if (!nextField(start, end)) {
+ break;
+ }
+ uint32_t index = strtoul(start, nullptr, 10);
+
+ if (!nextField(start, end)) {
+ break;
+ }
+
+ auto readIntPair = [&](int32_t& aStart, int32_t& aEnd) {
+ char* limit = nullptr;
+ aStart = strtol(start, &limit, 10);
+ if (*limit == FontNameCache::kRangeSep && limit + 1 < end) {
+ aEnd = strtof(limit + 1, nullptr);
+ }
+ };
+
+ int32_t minStyle, maxStyle;
+ readIntPair(minStyle, maxStyle);
+
+ if (!nextField(start, end)) {
+ break;
+ }
+
+ int32_t minWeight, maxWeight;
+ readIntPair(minWeight, maxWeight);
+
+ if (!nextField(start, end)) {
+ break;
+ }
+
+ int32_t minStretch, maxStretch;
+ readIntPair(minStretch, maxStretch);
+
+ if (!nextField(start, end)) {
+ break;
+ }
+ nsAutoCString psname(start, end - start);
+
+ if (!nextField(start, end)) {
+ break;
+ }
+ nsAutoCString fullname(start, end - start);
+
+ if (!nextField(start, end)) {
+ break;
+ }
+ FontVisibility visibility = FontVisibility(strtoul(start, nullptr, 10));
+
+ FontListEntry fle(familyName, faceName, aFileName,
+ WeightRange(FontWeight::FromRaw(minWeight),
+ FontWeight::FromRaw(maxWeight))
+ .AsScalar(),
+ StretchRange(FontStretch::FromRaw(minStretch),
+ FontStretch::FromRaw(maxStretch))
+ .AsScalar(),
+ SlantStyleRange(FontSlantStyle::FromRaw(minStyle),
+ FontSlantStyle::FromRaw(maxStyle))
+ .AsScalar(),
+ index, visibility);
+
+ aCollectFace(fle, psname, fullname, aStdFile);
+ count++;
+
+ start = recEnd + 1;
+ }
+
+ return count > 0;
+}
+
+void FT2FontEntry::AppendToFaceList(nsCString& aFaceList,
+ const nsACString& aFamilyName,
+ const nsACString& aPSName,
+ const nsACString& aFullName,
+ FontVisibility aVisibility) {
+ aFaceList.Append(aFamilyName);
+ aFaceList.Append(FontNameCache::kFieldSep);
+ aFaceList.Append(Name());
+ aFaceList.Append(FontNameCache::kFieldSep);
+ aFaceList.AppendInt(mFTFontIndex);
+ aFaceList.Append(FontNameCache::kFieldSep);
+ aFaceList.AppendInt(SlantStyle().Min().Raw());
+ aFaceList.Append(FontNameCache::kRangeSep);
+ aFaceList.AppendInt(SlantStyle().Max().Raw());
+ aFaceList.Append(FontNameCache::kFieldSep);
+ aFaceList.AppendInt(Weight().Min().Raw());
+ aFaceList.Append(FontNameCache::kRangeSep);
+ aFaceList.AppendInt(Weight().Max().Raw());
+ aFaceList.Append(FontNameCache::kFieldSep);
+ aFaceList.AppendInt(Stretch().Min().Raw());
+ aFaceList.Append(FontNameCache::kRangeSep);
+ aFaceList.AppendInt(Stretch().Max().Raw());
+ aFaceList.Append(FontNameCache::kFieldSep);
+ aFaceList.Append(aPSName);
+ aFaceList.Append(FontNameCache::kFieldSep);
+ aFaceList.Append(aFullName);
+ aFaceList.Append(FontNameCache::kFieldSep);
+ aFaceList.AppendInt(int(aVisibility));
+ aFaceList.Append(FontNameCache::kRecordSep);
+}
+
+void FT2FontEntry::CheckForBrokenFont(gfxFontFamily* aFamily) {
+ // note if the family is in the "bad underline" blocklist
+ if (aFamily->IsBadUnderlineFamily()) {
+ mIsBadUnderlineFont = true;
+ }
+ nsAutoCString familyKey(aFamily->Name());
+ BuildKeyNameFromFontName(familyKey);
+ CheckForBrokenFont(familyKey);
+}
+
+void FT2FontEntry::CheckForBrokenFont(const nsACString& aFamilyKey) {
+ // bug 721719 - set the IgnoreGSUB flag on entries for Roboto
+ // because of unwanted on-by-default "ae" ligature.
+ // (See also AppendFaceFromFontListEntry.)
+ if (aFamilyKey.EqualsLiteral("roboto")) {
+ mIgnoreGSUB = true;
+ return;
+ }
+
+ // bug 706888 - set the IgnoreGSUB flag on the broken version of
+ // Droid Sans Arabic from certain phones, as identified by the
+ // font checksum in the 'head' table
+ if (aFamilyKey.EqualsLiteral("droid sans arabic")) {
+ RefPtr<SharedFTFace> face = GetFTFace();
+ if (face) {
+ const TT_Header* head = static_cast<const TT_Header*>(
+ FT_Get_Sfnt_Table(face->GetFace(), ft_sfnt_head));
+ if (head && head->CheckSum_Adjust == 0xe445242) {
+ mIgnoreGSUB = true;
+ }
+ }
+ }
+}
+
+void gfxFT2FontList::AppendFacesFromBlob(
+ const nsCString& aFileName, StandardFile aStdFile, hb_blob_t* aBlob,
+ FontNameCache* aCache, uint32_t aTimestamp, uint32_t aFilesize) {
+ nsCString newFaceList;
+ uint32_t numFaces = 1;
+ unsigned int length;
+ const char* data = hb_blob_get_data(aBlob, &length);
+ // Check for a possible TrueType Collection
+ if (length >= sizeof(TTCHeader)) {
+ const TTCHeader* ttc = reinterpret_cast<const TTCHeader*>(data);
+ if (ttc->ttcTag == TRUETYPE_TAG('t', 't', 'c', 'f')) {
+ numFaces = ttc->numFonts;
+ }
+ }
+ for (unsigned int index = 0; index < numFaces; index++) {
+ hb_face_t* face = hb_face_create(aBlob, index);
+ if (face != hb_face_get_empty()) {
+ AddFaceToList(aFileName, index, aStdFile, face, newFaceList);
+ }
+ hb_face_destroy(face);
+ }
+ if (aCache && !newFaceList.IsEmpty()) {
+ aCache->CacheFileInfo(aFileName, newFaceList, aTimestamp, aFilesize);
+ }
+}
+
+void gfxFT2FontList::AppendFacesFromFontFile(const nsCString& aFileName,
+ FontNameCache* aCache,
+ StandardFile aStdFile) {
+ nsCString cachedFaceList;
+ uint32_t filesize = 0, timestamp = 0;
+ if (aCache) {
+ aCache->GetInfoForFile(aFileName, cachedFaceList, &timestamp, &filesize);
+ }
+
+ struct stat s;
+ int statRetval = stat(aFileName.get(), &s);
+ if (!cachedFaceList.IsEmpty() && 0 == statRetval &&
+ uint32_t(s.st_mtime) == timestamp && s.st_size == filesize) {
+ CollectFunc unshared =
+ [](const FontListEntry& aFLE, const nsCString& aPSName,
+ const nsCString& aFullName, StandardFile aStdFile) {
+ auto* pfl = PlatformFontList();
+ pfl->mLock.AssertCurrentThreadIn();
+ pfl->AppendFaceFromFontListEntry(aFLE, aStdFile);
+ };
+ CollectFunc shared = [](const FontListEntry& aFLE, const nsCString& aPSName,
+ const nsCString& aFullName, StandardFile aStdFile) {
+ PlatformFontList()->CollectInitData(aFLE, aPSName, aFullName, aStdFile);
+ };
+ if (AppendFacesFromCachedFaceList(SharedFontList() ? shared : unshared,
+ aFileName, cachedFaceList, aStdFile)) {
+ LOG(("using cached font info for %s", aFileName.get()));
+ return;
+ }
+ }
+
+ gfxFontUtils::AutoHBBlob fileBlob(hb_blob_create_from_file(aFileName.get()));
+ if (hb_blob_get_length(fileBlob) > 0) {
+ LOG(("reading font info via harfbuzz for %s", aFileName.get()));
+ AppendFacesFromBlob(aFileName, aStdFile, fileBlob,
+ 0 == statRetval ? aCache : nullptr, s.st_mtime,
+ s.st_size);
+ }
+}
+
+void gfxFT2FontList::FindFontsInOmnijar(FontNameCache* aCache) {
+ bool jarChanged = false;
+
+ mozilla::scache::StartupCache* cache =
+ mozilla::scache::StartupCache::GetSingleton();
+ const char* cachedModifiedTimeBuf;
+ uint32_t longSize;
+ if (cache &&
+ NS_SUCCEEDED(cache->GetBuffer(JAR_LAST_MODIFED_TIME,
+ &cachedModifiedTimeBuf, &longSize)) &&
+ longSize == sizeof(int64_t)) {
+ nsCOMPtr<nsIFile> jarFile = Omnijar::GetPath(Omnijar::Type::GRE);
+ jarFile->GetLastModifiedTime(&mJarModifiedTime);
+ if (mJarModifiedTime > LittleEndian::readInt64(cachedModifiedTimeBuf)) {
+ jarChanged = true;
+ }
+ }
+
+ static const char* sJarSearchPaths[] = {
+ "res/fonts/*.ttf$",
+ };
+ RefPtr<nsZipArchive> reader = Omnijar::GetReader(Omnijar::Type::GRE);
+ for (unsigned i = 0; i < ArrayLength(sJarSearchPaths); i++) {
+ nsZipFind* find;
+ if (NS_SUCCEEDED(reader->FindInit(sJarSearchPaths[i], &find))) {
+ const char* path;
+ uint16_t len;
+ while (NS_SUCCEEDED(find->FindNext(&path, &len))) {
+ nsCString entryName(path, len);
+ AppendFacesFromOmnijarEntry(reader, entryName, aCache, jarChanged);
+ }
+ delete find;
+ }
+ }
+}
+
+static void GetName(hb_face_t* aFace, hb_ot_name_id_t aNameID,
+ nsACString& aName) {
+ unsigned int n = 0;
+ n = hb_ot_name_get_utf8(aFace, aNameID, HB_LANGUAGE_INVALID, &n, nullptr);
+ if (n) {
+ aName.SetLength(n++); // increment n to account for NUL terminator
+ n = hb_ot_name_get_utf8(aFace, aNameID, HB_LANGUAGE_INVALID, &n,
+ aName.BeginWriting());
+ }
+}
+
+// Given the harfbuzz face corresponding to an entryName and face index,
+// add the face to the available font list and to the faceList string
+void gfxFT2FontList::AddFaceToList(const nsCString& aEntryName, uint32_t aIndex,
+ StandardFile aStdFile, hb_face_t* aFace,
+ nsCString& aFaceList) {
+ nsAutoCString familyName;
+ bool preferTypographicNames = true;
+ GetName(aFace, HB_OT_NAME_ID_TYPOGRAPHIC_FAMILY, familyName);
+ if (familyName.IsEmpty()) {
+ preferTypographicNames = false;
+ GetName(aFace, HB_OT_NAME_ID_FONT_FAMILY, familyName);
+ }
+ if (familyName.IsEmpty()) {
+ return;
+ }
+
+ nsAutoCString fullname;
+ GetName(aFace, HB_OT_NAME_ID_FULL_NAME, fullname);
+ if (fullname.IsEmpty()) {
+ // Construct fullname from family + style
+ fullname = familyName;
+ nsAutoCString styleName;
+ if (preferTypographicNames) {
+ GetName(aFace, HB_OT_NAME_ID_TYPOGRAPHIC_SUBFAMILY, styleName);
+ }
+ if (styleName.IsEmpty()) {
+ GetName(aFace, HB_OT_NAME_ID_FONT_SUBFAMILY, styleName);
+ }
+ if (!styleName.IsEmpty() && !styleName.EqualsLiteral("Regular")) {
+ fullname.Append(' ');
+ fullname.Append(styleName);
+ }
+ }
+
+ // Build the font entry name and create an FT2FontEntry,
+ // but do -not- keep a reference to the FT_Face.
+ // (When using the shared font list, this entry will not be retained,
+ // it is used only to call AppendToFaceList.)
+ RefPtr<FT2FontEntry> fe =
+ FT2FontEntry::CreateFontEntry(fullname, aEntryName.get(), aIndex, aFace);
+
+ if (fe) {
+ fe->mStandardFace = (aStdFile == kStandard);
+ nsAutoCString familyKey(familyName);
+ BuildKeyNameFromFontName(familyKey);
+
+ FontVisibility visibility = FontVisibility::Unknown;
+
+ nsAutoCString psname;
+ GetName(aFace, HB_OT_NAME_ID_POSTSCRIPT_NAME, psname);
+
+ if (SharedFontList()) {
+ FontListEntry fle(familyName, fe->Name(), fe->mFilename,
+ fe->Weight().AsScalar(), fe->Stretch().AsScalar(),
+ fe->SlantStyle().AsScalar(), fe->mFTFontIndex,
+ visibility);
+ CollectInitData(fle, psname, fullname, aStdFile);
+ } else {
+ RefPtr<gfxFontFamily> family =
+ mFontFamilies.LookupOrInsertWith(familyKey, [&] {
+ auto family = MakeRefPtr<FT2FontFamily>(familyName, visibility);
+ if (mSkipSpaceLookupCheckFamilies.Contains(familyKey)) {
+ family->SetSkipSpaceFeatureCheck(true);
+ }
+ if (mBadUnderlineFamilyNames.ContainsSorted(familyKey)) {
+ family->SetBadUnderlineFamily();
+ }
+ return family;
+ });
+ family->AddFontEntry(fe);
+ fe->CheckForBrokenFont(family);
+ }
+
+ fe->AppendToFaceList(aFaceList, familyName, psname, fullname, visibility);
+ if (LOG_ENABLED()) {
+ nsAutoCString weightString;
+ fe->Weight().ToString(weightString);
+ nsAutoCString stretchString;
+ fe->Stretch().ToString(stretchString);
+ LOG(
+ ("(fontinit) added (%s) to family (%s)"
+ " with style: %s weight: %s stretch: %s",
+ fe->Name().get(), familyName.get(),
+ fe->IsItalic() ? "italic" : "normal", weightString.get(),
+ stretchString.get()));
+ }
+ }
+}
+
+void gfxFT2FontList::AppendFacesFromOmnijarEntry(nsZipArchive* aArchive,
+ const nsCString& aEntryName,
+ FontNameCache* aCache,
+ bool aJarChanged) {
+ nsCString faceList;
+ if (aCache && !aJarChanged) {
+ uint32_t filesize, timestamp;
+ aCache->GetInfoForFile(aEntryName, faceList, &timestamp, &filesize);
+ if (faceList.Length() > 0) {
+ CollectFunc unshared =
+ [](const FontListEntry& aFLE, const nsCString& aPSName,
+ const nsCString& aFullName, StandardFile aStdFile) {
+ auto* pfl = PlatformFontList();
+ pfl->mLock.AssertCurrentThreadIn();
+ pfl->AppendFaceFromFontListEntry(aFLE, aStdFile);
+ };
+ CollectFunc shared = [](const FontListEntry& aFLE,
+ const nsCString& aPSName,
+ const nsCString& aFullName,
+ StandardFile aStdFile) {
+ PlatformFontList()->CollectInitData(aFLE, aPSName, aFullName, aStdFile);
+ };
+ if (AppendFacesFromCachedFaceList(SharedFontList() ? shared : unshared,
+ aEntryName, faceList, kStandard)) {
+ return;
+ }
+ }
+ }
+
+ nsZipItem* item = aArchive->GetItem(aEntryName.get());
+ NS_ASSERTION(item, "failed to find zip entry");
+
+ uint32_t bufSize = item->RealSize();
+
+ // We use fallible allocation here; if there's not enough RAM, we'll simply
+ // ignore the bundled fonts and fall back to the device's installed fonts.
+ char* buffer = static_cast<char*>(malloc(bufSize));
+ if (!buffer) {
+ return;
+ }
+
+ nsZipCursor cursor(item, aArchive, (uint8_t*)buffer, bufSize);
+ uint8_t* data = cursor.Copy(&bufSize);
+ MOZ_ASSERT(data && bufSize == item->RealSize(), "error reading bundled font");
+ if (!data) {
+ return;
+ }
+
+ gfxFontUtils::AutoHBBlob blob(
+ hb_blob_create(buffer, bufSize, HB_MEMORY_MODE_READONLY, buffer, free));
+ AppendFacesFromBlob(aEntryName, kStandard, blob, aCache, 0, bufSize);
+}
+
+// Called on each family after all fonts are added to the list;
+// if aSortFaces is true this will sort faces to give priority to "standard"
+// font files.
+void FT2FontFamily::FinalizeMemberList(bool aSortFaces) {
+ AutoWriteLock lock(mLock);
+
+ SetHasStyles(true);
+
+ if (aSortFaces) {
+ SortAvailableFonts();
+ }
+ CheckForSimpleFamily();
+}
+
+void gfxFT2FontList::FindFonts() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // Chrome process: get the cached list (if any)
+ if (!mFontNameCache) {
+ mFontNameCache = MakeUnique<FontNameCache>();
+ }
+ mFontNameCache->Init();
+
+#if defined(MOZ_WIDGET_ANDROID)
+ // Android API 29+ provides system font and font matcher API for native code.
+ typedef void* (*_ASystemFontIterator_open)();
+ typedef void* (*_ASystemFontIterator_next)(void*);
+ typedef void (*_ASystemFontIterator_close)(void*);
+ typedef const char* (*_AFont_getFontFilePath)(const void*);
+ typedef void (*_AFont_close)(void*);
+
+ static _ASystemFontIterator_open systemFontIterator_open = nullptr;
+ static _ASystemFontIterator_next systemFontIterator_next = nullptr;
+ static _ASystemFontIterator_close systemFontIterator_close = nullptr;
+ static _AFont_getFontFilePath font_getFontFilePath = nullptr;
+ static _AFont_close font_close = nullptr;
+
+ static bool firstTime = true;
+
+ nsAutoCString androidFontsRoot = [&] {
+ // ANDROID_ROOT is the root of the android system, typically /system;
+ // font files are in /$ANDROID_ROOT/fonts/
+ nsAutoCString root;
+ char* androidRoot = PR_GetEnv("ANDROID_ROOT");
+ if (androidRoot) {
+ root = androidRoot;
+ } else {
+ root = "/system"_ns;
+ }
+ root.AppendLiteral("/fonts");
+ return root;
+ }();
+
+ if (firstTime) {
+ if (jni::GetAPIVersion() >= 29) {
+ void* handle = dlopen("libandroid.so", RTLD_LAZY | RTLD_LOCAL);
+ MOZ_ASSERT(handle);
+
+ systemFontIterator_open =
+ (_ASystemFontIterator_open)dlsym(handle, "ASystemFontIterator_open");
+ systemFontIterator_next =
+ (_ASystemFontIterator_next)dlsym(handle, "ASystemFontIterator_next");
+ systemFontIterator_close = (_ASystemFontIterator_close)dlsym(
+ handle, "ASystemFontIterator_close");
+ font_getFontFilePath =
+ (_AFont_getFontFilePath)dlsym(handle, "AFont_getFontFilePath");
+ font_close = (_AFont_close)dlsym(handle, "AFont_close");
+
+ if (NS_WARN_IF(!systemFontIterator_next) ||
+ NS_WARN_IF(!systemFontIterator_close) ||
+ NS_WARN_IF(!font_getFontFilePath) || NS_WARN_IF(!font_close)) {
+ // Since any functions aren't resolved, use old way to enumerate fonts.
+ systemFontIterator_open = nullptr;
+ }
+ }
+ firstTime = false;
+ }
+
+ bool useSystemFontAPI = !!systemFontIterator_open;
+
+ if (useSystemFontAPI &&
+ !StaticPrefs::
+ gfx_font_list_use_font_match_api_force_enabled_AtStartup()) {
+ // OPPO, realme and OnePlus device seem to crash when using font match API
+ // (Bug 1787551).
+ nsCString manufacturer = java::sdk::Build::MANUFACTURER()->ToCString();
+ if (manufacturer.EqualsLiteral("OPPO") ||
+ manufacturer.EqualsLiteral("realme") ||
+ manufacturer.EqualsLiteral("OnePlus")) {
+ useSystemFontAPI = false;
+ }
+ }
+
+ if (useSystemFontAPI) {
+ void* iter = systemFontIterator_open();
+ if (iter) {
+ void* font = systemFontIterator_next(iter);
+ while (font) {
+ nsDependentCString path(font_getFontFilePath(font));
+ AppendFacesFromFontFile(path, mFontNameCache.get(), kStandard);
+ font_close(font);
+ font = systemFontIterator_next(iter);
+ }
+
+ if (!StaticPrefs::gfx_font_rendering_colr_v1_enabled()) {
+ // We turn off COLRv1 fonts support. Newer android versions have
+ // COLRv1 emoji font, and a legacy and hidden CBDT font we understand,
+ // so try to find NotoColorEmojiLegacy.ttf explicitly for now.
+ nsAutoCString legacyEmojiFont(androidFontsRoot);
+ legacyEmojiFont.Append("/NotoColorEmojiLegacy.ttf");
+ AppendFacesFromFontFile(legacyEmojiFont, mFontNameCache.get(),
+ kStandard);
+ }
+
+ systemFontIterator_close(iter);
+ } else {
+ useSystemFontAPI = false;
+ }
+ }
+
+ if (!useSystemFontAPI)
+#endif
+ {
+ FindFontsInDir(androidFontsRoot, mFontNameCache.get());
+ }
+
+ // Look for fonts stored in omnijar, unless we're on a low-memory
+ // device where we don't want to spend the RAM to decompress them.
+ // (Prefs may disable this, or force-enable it even with low memory.)
+ if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() > 0 ||
+ (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() < 0 &&
+ !nsMemory::IsLowMemoryPlatform())) {
+ TimeStamp start = TimeStamp::Now();
+ FindFontsInOmnijar(mFontNameCache.get());
+ TimeStamp end = TimeStamp::Now();
+ Telemetry::Accumulate(Telemetry::FONTLIST_BUNDLEDFONTS_ACTIVATE,
+ (end - start).ToMilliseconds());
+ }
+
+ // Look for downloaded fonts in a profile-agnostic "fonts" directory.
+ nsCOMPtr<nsIProperties> dirSvc =
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
+ if (dirSvc) {
+ nsCOMPtr<nsIFile> appDir;
+ nsresult rv = dirSvc->Get(NS_XPCOM_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(appDir));
+ if (NS_SUCCEEDED(rv)) {
+ appDir->AppendNative("fonts"_ns);
+ nsCString localPath;
+ if (NS_SUCCEEDED(appDir->GetNativePath(localPath))) {
+ FindFontsInDir(localPath, mFontNameCache.get());
+ }
+ }
+ }
+
+ // look for locally-added fonts in a "fonts" subdir of the profile
+ nsCOMPtr<nsIFile> localDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
+ getter_AddRefs(localDir));
+ if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(localDir->Append(u"fonts"_ns))) {
+ nsCString localPath;
+ rv = localDir->GetNativePath(localPath);
+ if (NS_SUCCEEDED(rv)) {
+ FindFontsInDir(localPath, mFontNameCache.get());
+ }
+ }
+
+ mFontNameCache->DropStaleEntries();
+ if (!mFontNameCache->EntryCount()) {
+ // if we can't find any usable fonts, we are doomed!
+ MOZ_CRASH("No font files found");
+ }
+
+ // Write out FontCache data if needed
+ WriteCache();
+}
+
+void gfxFT2FontList::WriteCache() {
+ if (mFontNameCache) {
+ mFontNameCache->WriteCache();
+ }
+ mozilla::scache::StartupCache* cache =
+ mozilla::scache::StartupCache::GetSingleton();
+ if (cache && mJarModifiedTime > 0) {
+ const size_t bufSize = sizeof(mJarModifiedTime);
+ auto buf = UniqueFreePtr<char[]>(
+ reinterpret_cast<char*>(malloc(sizeof(char) * bufSize)));
+ LittleEndian::writeInt64(buf.get(), mJarModifiedTime);
+
+ LOG(("WriteCache: putting Jar, length %zu", bufSize));
+ cache->PutBuffer(JAR_LAST_MODIFED_TIME, std::move(buf), bufSize);
+ }
+ LOG(("Done with writecache"));
+}
+
+void gfxFT2FontList::FindFontsInDir(const nsCString& aDir,
+ FontNameCache* aFNC) {
+ static const char* sStandardFonts[] = {"DroidSans.ttf",
+ "DroidSans-Bold.ttf",
+ "DroidSerif-Regular.ttf",
+ "DroidSerif-Bold.ttf",
+ "DroidSerif-Italic.ttf",
+ "DroidSerif-BoldItalic.ttf",
+ "DroidSansMono.ttf",
+ "DroidSansArabic.ttf",
+ "DroidSansHebrew.ttf",
+ "DroidSansThai.ttf",
+ "MTLmr3m.ttf",
+ "MTLc3m.ttf",
+ "NanumGothic.ttf",
+ "NotoColorEmoji.ttf",
+ "NotoColorEmojiFlags.ttf",
+ "NotoColorEmojiLegacy.ttf",
+ "DroidSansJapanese.ttf",
+ "DroidSansFallback.ttf"};
+
+ DIR* d = opendir(aDir.get());
+ if (!d) {
+ return;
+ }
+
+ struct dirent* ent = nullptr;
+ while ((ent = readdir(d)) != nullptr) {
+ const char* ext = strrchr(ent->d_name, '.');
+ if (!ext) {
+ continue;
+ }
+ if (strcasecmp(ext, ".ttf") == 0 || strcasecmp(ext, ".otf") == 0 ||
+ strcasecmp(ext, ".woff") == 0 || strcasecmp(ext, ".ttc") == 0) {
+ bool isStdFont = false;
+ for (unsigned int i = 0; i < ArrayLength(sStandardFonts) && !isStdFont;
+ i++) {
+ isStdFont = strcmp(sStandardFonts[i], ent->d_name) == 0;
+ }
+
+ nsAutoCString s(aDir);
+ s.Append('/');
+ s.Append(ent->d_name);
+
+ // Add the face(s) from this file to our font list;
+ // note that if we have cached info for this file in fnc,
+ // and the file is unchanged, we won't actually need to read it.
+ // If the file is new/changed, this will update the FontNameCache.
+ AppendFacesFromFontFile(s, aFNC, isStdFont ? kStandard : kUnknown);
+ }
+ }
+
+ closedir(d);
+}
+
+void gfxFT2FontList::AppendFaceFromFontListEntry(const FontListEntry& aFLE,
+ StandardFile aStdFile) {
+ FT2FontEntry* fe = FT2FontEntry::CreateFontEntry(aFLE);
+ if (fe) {
+ nsAutoCString key(aFLE.familyName());
+ BuildKeyNameFromFontName(key);
+ fe->mStandardFace = (aStdFile == kStandard);
+ RefPtr<gfxFontFamily> family = mFontFamilies.LookupOrInsertWith(key, [&] {
+ auto family =
+ MakeRefPtr<FT2FontFamily>(aFLE.familyName(), aFLE.visibility());
+ if (mSkipSpaceLookupCheckFamilies.Contains(key)) {
+ family->SetSkipSpaceFeatureCheck(true);
+ }
+ if (mBadUnderlineFamilyNames.ContainsSorted(key)) {
+ family->SetBadUnderlineFamily();
+ }
+ return family;
+ });
+ family->AddFontEntry(fe);
+
+ fe->CheckForBrokenFont(family);
+ }
+}
+
+void gfxFT2FontList::ReadSystemFontList(dom::SystemFontList* aList) {
+ AutoLock lock(mLock);
+ for (const auto& entry : mFontFamilies) {
+ auto family = static_cast<FT2FontFamily*>(entry.GetData().get());
+ family->AddFacesToFontList(&aList->entries());
+ }
+}
+
+static void LoadSkipSpaceLookupCheck(
+ nsTHashSet<nsCString>& aSkipSpaceLookupCheck) {
+ AutoTArray<nsCString, 5> skiplist;
+ gfxFontUtils::GetPrefsFontList(
+ "font.whitelist.skip_default_features_space_check", skiplist);
+ uint32_t numFonts = skiplist.Length();
+ for (uint32_t i = 0; i < numFonts; i++) {
+ ToLowerCase(skiplist[i]);
+ aSkipSpaceLookupCheck.Insert(skiplist[i]);
+ }
+}
+
+nsresult gfxFT2FontList::InitFontListForPlatform() {
+ LoadSkipSpaceLookupCheck(mSkipSpaceLookupCheckFamilies);
+
+ if (XRE_IsParentProcess()) {
+ // This will populate/update mFontNameCache and store it in the
+ // startupCache for future startups.
+ FindFonts();
+
+ // Finalize the families by sorting faces into standard order
+ // and marking "simple" families.
+ for (const auto& entry : mFontFamilies) {
+ auto* family = static_cast<FT2FontFamily*>(entry.GetData().get());
+ family->FinalizeMemberList(/* aSortFaces */ true);
+ }
+
+ return NS_OK;
+ }
+
+ // Content process: use font list passed from the chrome process via
+ // the GetXPCOMProcessAttributes message.
+ auto& fontList = dom::ContentChild::GetSingleton()->SystemFontList();
+ for (FontListEntry& fle : fontList.entries()) {
+ // We don't need to identify "standard" font files here,
+ // as the faces are already sorted.
+ AppendFaceFromFontListEntry(fle, kUnknown);
+ }
+
+ // We don't need to sort faces (because they were already sorted by the
+ // chrome process, so we just maintain the existing order)
+ for (const auto& entry : mFontFamilies) {
+ auto* family = static_cast<FT2FontFamily*>(entry.GetData().get());
+ family->FinalizeMemberList(/* aSortFaces */ false);
+ }
+
+ LOG(("got font list from chrome process: %" PRIdPTR " faces in %" PRIu32
+ " families",
+ fontList.entries().Length(), mFontFamilies.Count()));
+ fontList.entries().Clear();
+
+ return NS_OK;
+}
+
+void gfxFT2FontList::InitSharedFontListForPlatform() {
+ if (!XRE_IsParentProcess()) {
+ // Content processes will access the shared-memory data created by the
+ // parent, so don't need to scan for available fonts themselves.
+ return;
+ }
+
+ // This will populate mFontNameCache with entries for all the available font
+ // files, and record them in mFamilies (unshared list) or mFamilyInitData and
+ // mFaceInitData (shared font list).
+ FindFonts();
+
+ mozilla::fontlist::FontList* list = SharedFontList();
+ list->SetFamilyNames(mFamilyInitData);
+
+ auto families = list->Families();
+ for (uint32_t i = 0; i < mFamilyInitData.Length(); i++) {
+ auto faceList = mFaceInitData.Get(mFamilyInitData[i].mKey);
+ MOZ_ASSERT(faceList);
+ families[i].AddFaces(list, *faceList);
+ }
+
+ mFamilyInitData.Clear();
+ mFaceInitData.Clear();
+}
+
+gfxFontEntry* gfxFT2FontList::CreateFontEntry(fontlist::Face* aFace,
+ const fontlist::Family* aFamily) {
+ fontlist::FontList* list = SharedFontList();
+ nsAutoCString desc(aFace->mDescriptor.AsString(list));
+ FT2FontEntry* fe =
+ FT2FontEntry::CreateFontEntry(desc, desc.get(), aFace->mIndex, nullptr);
+ fe->InitializeFrom(aFace, aFamily);
+ fe->CheckForBrokenFont(aFamily->Key().AsString(list));
+ return fe;
+}
+
+// called for each family name, based on the assumption that the
+// first part of the full name is the family name
+
+gfxFontEntry* gfxFT2FontList::LookupLocalFont(nsPresContext* aPresContext,
+ const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry) {
+ AutoLock lock(mLock);
+
+ if (SharedFontList()) {
+ return LookupInSharedFaceNameList(aPresContext, aFontName, aWeightForEntry,
+ aStretchForEntry, aStyleForEntry);
+ }
+
+ // walk over list of names
+ FT2FontEntry* fontEntry = nullptr;
+ FontVisibility level =
+ aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User;
+
+ for (const RefPtr<gfxFontFamily>& fontFamily : mFontFamilies.Values()) {
+ if (!IsVisibleToCSS(*fontFamily, level)) {
+ continue;
+ }
+
+ // Check family name, based on the assumption that the
+ // first part of the full name is the family name
+
+ // does the family name match up to the length of the family name?
+ const nsCString& family = fontFamily->Name();
+
+ const nsAutoCString fullNameFamily(
+ Substring(aFontName, 0, family.Length()));
+
+ // if so, iterate over faces in this family to see if there is a match
+ if (family.Equals(fullNameFamily, nsCaseInsensitiveCStringComparator)) {
+ gfxFontEntry* fe =
+ fontFamily->FindFont(aFontName, nsCaseInsensitiveCStringComparator);
+ if (fe) {
+ fontEntry = static_cast<FT2FontEntry*>(fe);
+ goto searchDone;
+ }
+ }
+ }
+
+searchDone:
+ if (!fontEntry) {
+ return nullptr;
+ }
+
+ // Clone the font entry so that we can then set its style descriptors
+ // from the userfont entry rather than the actual font.
+
+ // Ensure existence of mFTFace in the original entry
+ RefPtr<SharedFTFace> face = fontEntry->GetFTFace(true);
+ if (!face) {
+ return nullptr;
+ }
+
+ FT2FontEntry* fe = FT2FontEntry::CreateFontEntry(
+ fontEntry->Name(), fontEntry->mFilename.get(), fontEntry->mFTFontIndex,
+ nullptr);
+ if (fe) {
+ fe->mStyleRange = aStyleForEntry;
+ fe->mWeightRange = aWeightForEntry;
+ fe->mStretchRange = aStretchForEntry;
+ fe->mIsLocalUserFont = true;
+ }
+
+ return fe;
+}
+
+FontFamily gfxFT2FontList::GetDefaultFontForPlatform(
+ nsPresContext* aPresContext, const gfxFontStyle* aStyle,
+ nsAtom* aLanguage) {
+ FontFamily ff;
+#if defined(MOZ_WIDGET_ANDROID)
+ ff = FindFamily(aPresContext, "Roboto"_ns);
+ if (ff.IsNull()) {
+ ff = FindFamily(aPresContext, "Droid Sans"_ns);
+ }
+#endif
+ /* TODO: what about Qt or other platforms that may use this? */
+ return ff;
+}
+
+gfxFontEntry* gfxFT2FontList::MakePlatformFont(const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry,
+ const uint8_t* aFontData,
+ uint32_t aLength) {
+ // The FT2 font needs the font data to persist, so we do NOT free it here
+ // but instead pass ownership to the font entry.
+ // Deallocation will happen later, when the font face is destroyed.
+ return FT2FontEntry::CreateFontEntry(aFontName, aWeightForEntry,
+ aStretchForEntry, aStyleForEntry,
+ aFontData, aLength);
+}
+
+gfxFontFamily* gfxFT2FontList::CreateFontFamily(
+ const nsACString& aName, FontVisibility aVisibility) const {
+ return new FT2FontFamily(aName, aVisibility);
+}
+
+void gfxFT2FontList::WillShutdown() {
+ LOG(("WillShutdown"));
+ WriteCache();
+ mFontNameCache = nullptr;
+}
diff --git a/gfx/thebes/gfxFT2FontList.h b/gfx/thebes/gfxFT2FontList.h
new file mode 100644
index 0000000000..269fbf28db
--- /dev/null
+++ b/gfx/thebes/gfxFT2FontList.h
@@ -0,0 +1,262 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_FT2FONTLIST_H
+#define GFX_FT2FONTLIST_H
+
+#include "mozilla/MemoryReporting.h"
+#include "gfxFT2FontBase.h"
+#include "gfxPlatformFontList.h"
+#include "nsTHashSet.h"
+
+namespace mozilla {
+namespace dom {
+class SystemFontListEntry;
+};
+namespace gfx {
+class FTUserFontData;
+};
+}; // namespace mozilla
+
+class FontNameCache;
+typedef struct FT_FaceRec_* FT_Face;
+class nsZipArchive;
+class WillShutdownObserver;
+
+class FT2FontEntry final : public gfxFT2FontEntryBase {
+ friend class gfxFT2FontList;
+
+ using FontListEntry = mozilla::dom::SystemFontListEntry;
+ using FTUserFontData = mozilla::gfx::FTUserFontData;
+
+ public:
+ explicit FT2FontEntry(const nsACString& aFaceName)
+ : gfxFT2FontEntryBase(aFaceName), mFTFontIndex(0) {}
+
+ ~FT2FontEntry();
+
+ gfxFontEntry* Clone() const override;
+
+ const nsCString& GetName() const { return Name(); }
+
+ // create a font entry for a downloaded font
+ static FT2FontEntry* CreateFontEntry(
+ const nsACString& aFontName, WeightRange aWeight, StretchRange aStretch,
+ SlantStyleRange aStyle, const uint8_t* aFontData, uint32_t aLength);
+
+ // create a font entry representing an installed font, identified by
+ // a FontListEntry; the freetype and cairo faces will not be instantiated
+ // until actually needed
+ static FT2FontEntry* CreateFontEntry(const FontListEntry& aFLE);
+
+ // Create a font entry with the given name; if it is an installed font,
+ // also record the filename and index.
+ // If a non-null harfbuzz face is passed, also set style/weight/stretch
+ // properties of the entry from the values in the face.
+ static FT2FontEntry* CreateFontEntry(const nsACString& aName,
+ const char* aFilename, uint8_t aIndex,
+ const hb_face_t* aFace);
+
+ gfxFont* CreateFontInstance(const gfxFontStyle* aStyle) override;
+
+ nsresult ReadCMAP(FontInfoData* aFontInfoData = nullptr) override;
+
+ hb_blob_t* GetFontTable(uint32_t aTableTag) override;
+
+ bool HasFontTable(uint32_t aTableTag) override;
+ nsresult CopyFontTable(uint32_t aTableTag, nsTArray<uint8_t>&) override;
+
+ bool HasVariations() override;
+ void GetVariationAxes(
+ nsTArray<gfxFontVariationAxis>& aVariationAxes) override;
+ void GetVariationInstances(
+ nsTArray<gfxFontVariationInstance>& aInstances) override;
+
+ // Check for various kinds of brokenness, and set flags on the entry
+ // accordingly so that we avoid using bad font tables
+ void CheckForBrokenFont(gfxFontFamily* aFamily);
+ void CheckForBrokenFont(const nsACString& aFamilyKey);
+
+ already_AddRefed<mozilla::gfx::SharedFTFace> GetFTFace(bool aCommit = false);
+ FTUserFontData* GetUserFontData();
+
+ FT_MM_Var* GetMMVar() override;
+
+ // Get a harfbuzz face for this font, if possible. The caller is responsible
+ // to destroy the face when no longer needed.
+ // This may be a bit expensive, so avoid calling multiple times if the same
+ // face can be re-used for several purposes instead.
+ hb_face_t* CreateHBFace() const;
+
+ /**
+ * Append this face's metadata to aFaceList for storage in the FontNameCache
+ * (for faster startup).
+ * The aPSName and aFullName parameters here can in principle be empty,
+ * but if they are missing for a given face then src:local() lookups will
+ * not be able to find it when the shared font list is in use.
+ */
+ void AppendToFaceList(nsCString& aFaceList, const nsACString& aFamilyName,
+ const nsACString& aPSName, const nsACString& aFullName,
+ FontVisibility aVisibility);
+
+ void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const override;
+ void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const override;
+
+ // Strong reference (addref'd), but held in an atomic ptr rather than a
+ // normal RefPtr.
+ mozilla::Atomic<mozilla::gfx::SharedFTFace*> mFTFace;
+
+ FT_MM_Var* mMMVar = nullptr;
+
+ nsCString mFilename;
+ uint8_t mFTFontIndex;
+
+ mozilla::ThreadSafeWeakPtr<mozilla::gfx::UnscaledFontFreeType> mUnscaledFont;
+
+ nsTHashSet<uint32_t> mAvailableTables;
+
+ enum class HasVariationsState : int8_t {
+ Uninitialized = -1,
+ No = 0,
+ Yes = 1,
+ };
+ std::atomic<HasVariationsState> mHasVariations =
+ HasVariationsState::Uninitialized;
+
+ bool mMMVarInitialized = false;
+};
+
+class FT2FontFamily final : public gfxFontFamily {
+ using FontListEntry = mozilla::dom::SystemFontListEntry;
+
+ public:
+ explicit FT2FontFamily(const nsACString& aName, FontVisibility aVisibility)
+ : gfxFontFamily(aName, aVisibility) {}
+
+ // Append this family's faces to the IPC fontlist
+ void AddFacesToFontList(nsTArray<FontListEntry>* aFontList);
+
+ void FinalizeMemberList(bool aSortFaces);
+};
+
+class gfxFT2FontList final : public gfxPlatformFontList {
+ using FontListEntry = mozilla::dom::SystemFontListEntry;
+
+ public:
+ gfxFT2FontList();
+ virtual ~gfxFT2FontList();
+
+ gfxFontEntry* CreateFontEntry(
+ mozilla::fontlist::Face* aFace,
+ const mozilla::fontlist::Family* aFamily) override;
+
+ gfxFontEntry* LookupLocalFont(nsPresContext* aPresContext,
+ const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry) override;
+
+ gfxFontEntry* MakePlatformFont(const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry,
+ const uint8_t* aFontData,
+ uint32_t aLength) override;
+
+ void WriteCache();
+
+ void ReadSystemFontList(mozilla::dom::SystemFontList*);
+
+ static gfxFT2FontList* PlatformFontList() {
+ return static_cast<gfxFT2FontList*>(
+ gfxPlatformFontList::PlatformFontList());
+ }
+
+ gfxFontFamily* CreateFontFamily(const nsACString& aName,
+ FontVisibility aVisibility) const override;
+
+ void WillShutdown();
+
+ protected:
+ typedef enum { kUnknown, kStandard } StandardFile;
+
+ // initialize font lists
+ nsresult InitFontListForPlatform() MOZ_REQUIRES(mLock) override;
+
+ void AppendFaceFromFontListEntry(const FontListEntry& aFLE,
+ StandardFile aStdFile) MOZ_REQUIRES(mLock);
+
+ void AppendFacesFromBlob(const nsCString& aFileName, StandardFile aStdFile,
+ hb_blob_t* aBlob, FontNameCache* aCache,
+ uint32_t aTimestamp, uint32_t aFilesize)
+ MOZ_REQUIRES(mLock);
+
+ void AppendFacesFromFontFile(const nsCString& aFileName,
+ FontNameCache* aCache, StandardFile aStdFile)
+ MOZ_REQUIRES(mLock);
+
+ void AppendFacesFromOmnijarEntry(nsZipArchive* aReader,
+ const nsCString& aEntryName,
+ FontNameCache* aCache, bool aJarChanged)
+ MOZ_REQUIRES(mLock);
+
+ void InitSharedFontListForPlatform() MOZ_REQUIRES(mLock) override;
+ void CollectInitData(const FontListEntry& aFLE, const nsCString& aPSName,
+ const nsCString& aFullName, StandardFile aStdFile);
+
+ /**
+ * Callback passed to AppendFacesFromCachedFaceList to collect family/face
+ * information in either the unshared or shared list we're building.
+ */
+ typedef void (*CollectFunc)(const FontListEntry& aFLE,
+ const nsCString& aPSName,
+ const nsCString& aFullName,
+ StandardFile aStdFile);
+
+ /**
+ * Append faces from the face-list record for a specific file.
+ * aCollectFace is a callback that will store the face(s) in either the
+ * unshared mFontFamilies list or the mFamilyInitData/mFaceInitData tables
+ * that will be used to initialize the shared list.
+ * Returns true if it is able to read at least one face entry; false if no
+ * usable face entry was found.
+ */
+ bool AppendFacesFromCachedFaceList(CollectFunc aCollectFace,
+ const nsCString& aFileName,
+ const nsCString& aFaceList,
+ StandardFile aStdFile) MOZ_REQUIRES(mLock);
+
+ void AddFaceToList(const nsCString& aEntryName, uint32_t aIndex,
+ StandardFile aStdFile, hb_face_t* aFace,
+ nsCString& aFaceList) MOZ_REQUIRES(mLock);
+
+ void FindFonts() MOZ_REQUIRES(mLock);
+
+ void FindFontsInOmnijar(FontNameCache* aCache) MOZ_REQUIRES(mLock);
+
+ void FindFontsInDir(const nsCString& aDir, FontNameCache* aFNC)
+ MOZ_REQUIRES(mLock);
+
+ FontFamily GetDefaultFontForPlatform(nsPresContext* aPresContext,
+ const gfxFontStyle* aStyle,
+ nsAtom* aLanguage = nullptr)
+ MOZ_REQUIRES(mLock) override;
+
+ nsTHashSet<nsCString> mSkipSpaceLookupCheckFamilies;
+
+ private:
+ mozilla::UniquePtr<FontNameCache> mFontNameCache;
+ int64_t mJarModifiedTime;
+ RefPtr<WillShutdownObserver> mObserver;
+
+ nsTArray<mozilla::fontlist::Family::InitData> mFamilyInitData;
+ nsClassHashtable<nsCStringHashKey,
+ nsTArray<mozilla::fontlist::Face::InitData>>
+ mFaceInitData;
+};
+
+#endif /* GFX_FT2FONTLIST_H */
diff --git a/gfx/thebes/gfxFT2Fonts.cpp b/gfx/thebes/gfxFT2Fonts.cpp
new file mode 100644
index 0000000000..9cc3a4bfd5
--- /dev/null
+++ b/gfx/thebes/gfxFT2Fonts.cpp
@@ -0,0 +1,243 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if defined(MOZ_WIDGET_GTK)
+# include "gfxPlatformGtk.h"
+# define gfxToolkitPlatform gfxPlatformGtk
+#elif defined(XP_WIN)
+# include "gfxWindowsPlatform.h"
+# define gfxToolkitPlatform gfxWindowsPlatform
+#elif defined(ANDROID)
+# include "gfxAndroidPlatform.h"
+# define gfxToolkitPlatform gfxAndroidPlatform
+#endif
+
+#include "gfxTypes.h"
+#include "gfxFT2Fonts.h"
+#include "gfxFT2FontBase.h"
+#include "gfxFT2Utils.h"
+#include "gfxFT2FontList.h"
+#include "gfxTextRun.h"
+#include <locale.h>
+#include "nsGkAtoms.h"
+#include "nsTArray.h"
+#include "nsCRT.h"
+#include "nsXULAppAPI.h"
+
+#include "mozilla/Logging.h"
+#include "prinit.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/gfx/2D.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+/**
+ * gfxFT2Font
+ */
+
+bool gfxFT2Font::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText,
+ uint32_t aOffset, uint32_t aLength, Script aScript,
+ nsAtom* aLanguage, bool aVertical,
+ RoundingFlags aRounding,
+ gfxShapedText* aShapedText) {
+ if (!gfxFont::ShapeText(aDrawTarget, aText, aOffset, aLength, aScript,
+ aLanguage, aVertical, aRounding, aShapedText)) {
+ // harfbuzz must have failed(?!), just render raw glyphs
+ AddRange(aText, aOffset, aLength, aShapedText);
+ PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical,
+ aShapedText);
+ }
+
+ return true;
+}
+
+void gfxFT2Font::AddRange(const char16_t* aText, uint32_t aOffset,
+ uint32_t aLength, gfxShapedText* aShapedText) {
+ typedef gfxShapedText::CompressedGlyph CompressedGlyph;
+
+ const uint32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit();
+ // we'll pass this in/figure it out dynamically, but at this point there can
+ // be only one face.
+ gfxFT2LockedFace faceLock(this);
+ FT_Face face = faceLock.get();
+
+ CompressedGlyph* charGlyphs = aShapedText->GetCharacterGlyphs();
+
+ const gfxFT2Font::CachedGlyphData *cgd = nullptr, *cgdNext = nullptr;
+
+ FT_UInt spaceGlyph = GetSpaceGlyph();
+
+ for (uint32_t i = 0; i < aLength; i++, aOffset++) {
+ char16_t ch = aText[i];
+
+ if (ch == 0) {
+ // treat this null byte as a missing glyph, don't create a glyph for it
+ aShapedText->SetMissingGlyph(aOffset, 0, this);
+ continue;
+ }
+
+ NS_ASSERTION(!gfxFontGroup::IsInvalidChar(ch), "Invalid char detected");
+
+ if (cgdNext) {
+ cgd = cgdNext;
+ cgdNext = nullptr;
+ } else {
+ cgd = GetGlyphDataForChar(face, ch);
+ }
+
+ FT_UInt gid = cgd->glyphIndex;
+ int32_t advance = 0;
+
+ if (gid == 0) {
+ advance = -1; // trigger the missing glyphs case below
+ } else {
+ // find next character and its glyph -- in case they exist
+ // and exist in the current font face -- to compute kerning
+ char16_t chNext = 0;
+ FT_UInt gidNext = 0;
+ FT_Pos lsbDeltaNext = 0;
+
+ if (FT_HAS_KERNING(face) && i + 1 < aLength) {
+ chNext = aText[i + 1];
+ if (chNext != 0) {
+ cgdNext = GetGlyphDataForChar(face, chNext);
+ gidNext = cgdNext->glyphIndex;
+ if (gidNext && gidNext != spaceGlyph)
+ lsbDeltaNext = cgdNext->lsbDelta;
+ }
+ }
+
+ advance = cgd->xAdvance;
+
+ // now add kerning to the current glyph's advance
+ if (chNext && gidNext) {
+ FT_Vector kerning;
+ kerning.x = 0;
+ FT_Get_Kerning(face, gid, gidNext, FT_KERNING_DEFAULT, &kerning);
+ advance += kerning.x;
+ if (cgd->rsbDelta - lsbDeltaNext >= 32) {
+ advance -= 64;
+ } else if (cgd->rsbDelta - lsbDeltaNext < -32) {
+ advance += 64;
+ }
+ }
+
+ // convert 26.6 fixed point to app units
+ // round rather than truncate to nearest pixel
+ // because these advances are often scaled
+ advance = ((advance * appUnitsPerDevUnit + 32) >> 6);
+ }
+
+ if (advance >= 0 && CompressedGlyph::IsSimpleAdvance(advance) &&
+ CompressedGlyph::IsSimpleGlyphID(gid)) {
+ charGlyphs[aOffset].SetSimpleGlyph(advance, gid);
+ } else if (gid == 0) {
+ // gid = 0 only happens when the glyph is missing from the font
+ aShapedText->SetMissingGlyph(aOffset, ch, this);
+ } else {
+ gfxTextRun::DetailedGlyph details;
+ details.mGlyphID = gid;
+ NS_ASSERTION(details.mGlyphID == gid,
+ "Seriously weird glyph ID detected!");
+ details.mAdvance = advance;
+ aShapedText->SetDetailedGlyphs(aOffset, 1, &details);
+ }
+ }
+}
+
+gfxFT2Font::gfxFT2Font(const RefPtr<UnscaledFontFreeType>& aUnscaledFont,
+ RefPtr<mozilla::gfx::SharedFTFace>&& aFTFace,
+ FT2FontEntry* aFontEntry, const gfxFontStyle* aFontStyle,
+ int aLoadFlags)
+ : gfxFT2FontBase(aUnscaledFont, std::move(aFTFace), aFontEntry, aFontStyle,
+ aLoadFlags, aFontStyle->NeedsSyntheticBold(aFontEntry)),
+ mCharGlyphCache(32) {
+ NS_ASSERTION(mFontEntry,
+ "Unable to find font entry for font. Something is whack.");
+ InitMetrics();
+}
+
+gfxFT2Font::~gfxFT2Font() {}
+
+already_AddRefed<ScaledFont> gfxFT2Font::GetScaledFont(
+ const TextRunDrawParams& aRunParams) {
+ if (ScaledFont* scaledFont = mAzureScaledFont) {
+ return do_AddRef(scaledFont);
+ }
+
+ RefPtr<ScaledFont> newScaledFont = Factory::CreateScaledFontForFreeTypeFont(
+ GetUnscaledFont(), GetAdjustedSize(), mFTFace,
+ GetStyle()->NeedsSyntheticBold(GetFontEntry()));
+ if (!newScaledFont) {
+ return nullptr;
+ }
+
+ InitializeScaledFont(newScaledFont);
+
+ if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) {
+ Unused << newScaledFont.forget();
+ }
+ ScaledFont* scaledFont = mAzureScaledFont;
+ return do_AddRef(scaledFont);
+}
+
+bool gfxFT2Font::ShouldHintMetrics() const {
+ return !gfxPlatform::GetPlatform()->RequiresLinearZoom();
+}
+
+void gfxFT2Font::FillGlyphDataForChar(FT_Face face, uint32_t ch,
+ CachedGlyphData* gd) {
+ if (!face->charmap || (face->charmap->encoding != FT_ENCODING_UNICODE &&
+ face->charmap->encoding != FT_ENCODING_MS_SYMBOL)) {
+ if (FT_Err_Ok != FT_Select_Charmap(face, FT_ENCODING_UNICODE) &&
+ FT_Err_Ok != FT_Select_Charmap(face, FT_ENCODING_MS_SYMBOL)) {
+ NS_WARNING("failed to select Unicode or symbol charmap!");
+ }
+ }
+ FT_UInt gid = FT_Get_Char_Index(face, ch);
+
+ if (gid == 0) {
+ // this font doesn't support this char!
+ NS_ASSERTION(gid != 0,
+ "We don't have a glyph, but font indicated that it supported "
+ "this char in tables?");
+ gd->glyphIndex = 0;
+ return;
+ }
+
+ FT_Error err = Factory::LoadFTGlyph(face, gid, mFTLoadFlags);
+
+ if (err) {
+ // hmm, this is weird, we failed to load a glyph that we had?
+ NS_WARNING("Failed to load glyph that we got from Get_Char_index");
+
+ gd->glyphIndex = 0;
+ return;
+ }
+
+ gd->glyphIndex = gid;
+ gd->lsbDelta = face->glyph->lsb_delta;
+ gd->rsbDelta = face->glyph->rsb_delta;
+ gd->xAdvance = face->glyph->advance.x;
+ if (gd->xAdvance) {
+ gd->xAdvance += GetEmboldenStrength(face).x;
+ }
+}
+
+void gfxFT2Font::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const {
+ gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+ aSizes->mFontInstances +=
+ mCharGlyphCache.ShallowSizeOfExcludingThis(aMallocSizeOf);
+}
+
+void gfxFT2Font::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const {
+ aSizes->mFontInstances += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
diff --git a/gfx/thebes/gfxFT2Fonts.h b/gfx/thebes/gfxFT2Fonts.h
new file mode 100644
index 0000000000..3b164c711e
--- /dev/null
+++ b/gfx/thebes/gfxFT2Fonts.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_FT2FONTS_H
+#define GFX_FT2FONTS_H
+
+#include "mozilla/MemoryReporting.h"
+#include "gfxTypes.h"
+#include "gfxFont.h"
+#include "gfxFT2FontBase.h"
+#include "gfxContext.h"
+#include "gfxFontUtils.h"
+#include "gfxUserFontSet.h"
+
+class FT2FontEntry;
+
+class gfxFT2Font final : public gfxFT2FontBase {
+ public: // new functions
+ gfxFT2Font(const RefPtr<mozilla::gfx::UnscaledFontFreeType>& aUnscaledFont,
+ RefPtr<mozilla::gfx::SharedFTFace>&& aFTFace,
+ FT2FontEntry* aFontEntry, const gfxFontStyle* aFontStyle,
+ int aLoadFlags);
+
+ FT2FontEntry* GetFontEntry();
+
+ already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont(
+ const TextRunDrawParams& aRunParams) override;
+
+ bool ShouldHintMetrics() const override;
+
+ void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const override;
+ void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const override;
+
+ protected:
+ ~gfxFT2Font() override;
+
+ struct CachedGlyphData {
+ CachedGlyphData() : glyphIndex(0xffffffffU) {}
+
+ explicit CachedGlyphData(uint32_t gid) : glyphIndex(gid) {}
+
+ uint32_t glyphIndex;
+ int32_t lsbDelta;
+ int32_t rsbDelta;
+ int32_t xAdvance;
+ };
+
+ const CachedGlyphData* GetGlyphDataForChar(FT_Face aFace, uint32_t ch) {
+ CharGlyphMapEntryType* entry = mCharGlyphCache.PutEntry(ch);
+
+ if (!entry) return nullptr;
+
+ if (entry->GetData().glyphIndex == 0xffffffffU) {
+ // this is a new entry, fill it
+ FillGlyphDataForChar(aFace, ch, entry->GetModifiableData());
+ }
+
+ return &entry->GetData();
+ }
+
+ bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText,
+ uint32_t aOffset, uint32_t aLength, Script aScript,
+ nsAtom* aLanguage, bool aVertical, RoundingFlags aRounding,
+ gfxShapedText* aShapedText) override;
+
+ void FillGlyphDataForChar(FT_Face face, uint32_t ch, CachedGlyphData* gd);
+
+ void AddRange(const char16_t* aText, uint32_t aOffset, uint32_t aLength,
+ gfxShapedText* aShapedText);
+
+ typedef nsBaseHashtableET<nsUint32HashKey, CachedGlyphData>
+ CharGlyphMapEntryType;
+ typedef nsTHashtable<CharGlyphMapEntryType> CharGlyphMap;
+ CharGlyphMap mCharGlyphCache;
+};
+
+#endif /* GFX_FT2FONTS_H */
diff --git a/gfx/thebes/gfxFT2Utils.cpp b/gfx/thebes/gfxFT2Utils.cpp
new file mode 100644
index 0000000000..94403c9dc1
--- /dev/null
+++ b/gfx/thebes/gfxFT2Utils.cpp
@@ -0,0 +1,157 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxFT2FontBase.h"
+#include "gfxFT2Utils.h"
+#include "mozilla/Likely.h"
+
+#ifdef USE_FC_FREETYPE
+# include <fontconfig/fcfreetype.h>
+#endif
+
+#include "ft2build.h"
+#include FT_MULTIPLE_MASTERS_H
+
+#include "prlink.h"
+
+uint32_t gfxFT2LockedFace::GetGlyph(uint32_t aCharCode) {
+ if (MOZ_UNLIKELY(!mFace)) return 0;
+
+#ifdef USE_FC_FREETYPE
+ // FcFreeTypeCharIndex will search starting from the most recently
+ // selected charmap. This can cause non-determistic behavior when more
+ // than one charmap supports a character but with different glyphs, as
+ // with older versions of MS Gothic, for example. Always prefer a Unicode
+ // charmap, if there is one; failing that, try MS_SYMBOL.
+ // (FcFreeTypeCharIndex usually does the appropriate Unicode conversion,
+ // but some fonts have non-Roman glyphs for FT_ENCODING_APPLE_ROMAN
+ // characters.)
+ if (!mFace->charmap || (mFace->charmap->encoding != FT_ENCODING_UNICODE &&
+ mFace->charmap->encoding != FT_ENCODING_MS_SYMBOL)) {
+ if (FT_Err_Ok != FT_Select_Charmap(mFace, FT_ENCODING_UNICODE) &&
+ FT_Err_Ok != FT_Select_Charmap(mFace, FT_ENCODING_MS_SYMBOL)) {
+ NS_WARNING("failed to select Unicode or symbol charmap");
+ }
+ }
+
+ uint32_t gid = FcFreeTypeCharIndex(mFace, aCharCode);
+#else
+ uint32_t gid = FT_Get_Char_Index(mFace, aCharCode);
+#endif
+ if (!gid && mFace->charmap &&
+ mFace->charmap->encoding == FT_ENCODING_MS_SYMBOL) {
+ if (auto pua = gfxFontUtils::MapLegacySymbolFontCharToPUA(aCharCode)) {
+ gid = FT_Get_Char_Index(mFace, pua);
+ }
+ }
+ return gid;
+}
+
+typedef FT_UInt (*GetCharVariantFunction)(FT_Face face, FT_ULong charcode,
+ FT_ULong variantSelector);
+
+uint32_t gfxFT2LockedFace::GetUVSGlyph(uint32_t aCharCode,
+ uint32_t aVariantSelector) {
+ MOZ_ASSERT(aVariantSelector, "aVariantSelector should not be NULL");
+
+ if (MOZ_UNLIKELY(!mFace)) return 0;
+
+ // This function is available from FreeType 2.3.6 (June 2008).
+ static CharVariantFunction sGetCharVariantPtr = FindCharVariantFunction();
+ if (!sGetCharVariantPtr) return 0;
+
+#ifdef USE_FC_FREETYPE
+ // FcFreeTypeCharIndex may have changed the selected charmap.
+ // FT_Face_GetCharVariantIndex needs a unicode charmap.
+ if (!mFace->charmap || mFace->charmap->encoding != FT_ENCODING_UNICODE) {
+ FT_Select_Charmap(mFace, FT_ENCODING_UNICODE);
+ }
+#endif
+
+ return (*sGetCharVariantPtr)(mFace, aCharCode, aVariantSelector);
+}
+
+gfxFT2LockedFace::CharVariantFunction
+gfxFT2LockedFace::FindCharVariantFunction() {
+ // This function is available from FreeType 2.3.6 (June 2008).
+ PRLibrary* lib = nullptr;
+ CharVariantFunction function = reinterpret_cast<CharVariantFunction>(
+ PR_FindFunctionSymbolAndLibrary("FT_Face_GetCharVariantIndex", &lib));
+ if (!lib) {
+ return nullptr;
+ }
+
+ FT_Int major;
+ FT_Int minor;
+ FT_Int patch;
+ FT_Library_Version(mFace->glyph->library, &major, &minor, &patch);
+
+ // Versions 2.4.0 to 2.4.3 crash if configured with
+ // FT_CONFIG_OPTION_OLD_INTERNALS. Presence of the symbol FT_Alloc
+ // indicates FT_CONFIG_OPTION_OLD_INTERNALS.
+ if (major == 2 && minor == 4 && patch < 4 &&
+ PR_FindFunctionSymbol(lib, "FT_Alloc")) {
+ function = nullptr;
+ }
+
+ // Decrement the reference count incremented in
+ // PR_FindFunctionSymbolAndLibrary.
+ PR_UnloadLibrary(lib);
+
+ return function;
+}
+
+/*static*/
+void gfxFT2Utils::GetVariationAxes(const FT_MM_Var* aMMVar,
+ nsTArray<gfxFontVariationAxis>& aAxes) {
+ MOZ_ASSERT(aAxes.IsEmpty());
+ if (!aMMVar) {
+ return;
+ }
+ aAxes.SetCapacity(aMMVar->num_axis);
+ for (unsigned i = 0; i < aMMVar->num_axis; i++) {
+ const auto& a = aMMVar->axis[i];
+ gfxFontVariationAxis axis;
+ axis.mMinValue = a.minimum / 65536.0;
+ axis.mMaxValue = a.maximum / 65536.0;
+ axis.mDefaultValue = a.def / 65536.0;
+ axis.mTag = a.tag;
+ axis.mName = a.name;
+ aAxes.AppendElement(axis);
+ }
+}
+
+/*static*/
+void gfxFT2Utils::GetVariationInstances(
+ gfxFontEntry* aFontEntry, const FT_MM_Var* aMMVar,
+ nsTArray<gfxFontVariationInstance>& aInstances) {
+ MOZ_ASSERT(aInstances.IsEmpty());
+ if (!aMMVar) {
+ return;
+ }
+ gfxFontUtils::AutoHBBlob nameTable(
+ aFontEntry->GetFontTable(TRUETYPE_TAG('n', 'a', 'm', 'e')));
+ if (!nameTable) {
+ return;
+ }
+ aInstances.SetCapacity(aMMVar->num_namedstyles);
+ for (unsigned i = 0; i < aMMVar->num_namedstyles; i++) {
+ const auto& ns = aMMVar->namedstyle[i];
+ gfxFontVariationInstance inst;
+ nsresult rv =
+ gfxFontUtils::ReadCanonicalName(nameTable, ns.strid, inst.mName);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ inst.mValues.SetCapacity(aMMVar->num_axis);
+ for (unsigned j = 0; j < aMMVar->num_axis; j++) {
+ gfxFontVariationValue value;
+ value.mAxis = aMMVar->axis[j].tag;
+ value.mValue = ns.coords[j] / 65536.0;
+ inst.mValues.AppendElement(value);
+ }
+ aInstances.AppendElement(inst);
+ }
+}
diff --git a/gfx/thebes/gfxFT2Utils.h b/gfx/thebes/gfxFT2Utils.h
new file mode 100644
index 0000000000..37e930c743
--- /dev/null
+++ b/gfx/thebes/gfxFT2Utils.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_FT2UTILS_H
+#define GFX_FT2UTILS_H
+
+#include "cairo-ft.h"
+#include "gfxFT2FontBase.h"
+#include "mozilla/Likely.h"
+
+// Rounding and truncation functions for a FreeType fixed point number
+// (FT26Dot6) stored in a 32bit integer with high 26 bits for the integer
+// part and low 6 bits for the fractional part.
+#define FLOAT_FROM_26_6(x) ((x) / 64.0)
+#define FLOAT_FROM_16_16(x) ((x) / 65536.0)
+#define ROUND_26_6_TO_INT(x) ((x) >= 0 ? ((32 + (x)) >> 6) : -((32 - (x)) >> 6))
+
+typedef struct FT_FaceRec_* FT_Face;
+
+/**
+ * BEWARE: Recursively locking with gfxFT2LockedFace is not supported.
+ * Do not instantiate gfxFT2LockedFace within the scope of another instance.
+ * Do not attempt to call into Cairo within the scope of gfxFT2LockedFace,
+ * as that may accidentally try to re-lock the face within Cairo itself
+ * and thus deadlock.
+ */
+class MOZ_STACK_CLASS gfxFT2LockedFace {
+ public:
+ explicit gfxFT2LockedFace(const gfxFT2FontBase* aFont)
+ : mGfxFont(aFont), mFace(aFont->LockFTFace()) {}
+ ~gfxFT2LockedFace() {
+ if (mFace) {
+ mGfxFont->UnlockFTFace();
+ }
+ }
+
+ FT_Face get() { return mFace; };
+
+ /**
+ * Get the glyph id for a Unicode character representable by a single
+ * glyph, or return zero if there is no such glyph. This does no caching,
+ * so you probably want gfxFcFont::GetGlyph.
+ */
+ uint32_t GetGlyph(uint32_t aCharCode);
+ /**
+ * Returns 0 if there is no variation selector cmap subtable.
+ */
+ uint32_t GetUVSGlyph(uint32_t aCharCode, uint32_t aVariantSelector);
+
+ protected:
+ typedef FT_UInt (*CharVariantFunction)(FT_Face face, FT_ULong charcode,
+ FT_ULong variantSelector);
+ CharVariantFunction FindCharVariantFunction();
+
+ const gfxFT2FontBase* MOZ_NON_OWNING_REF mGfxFont; // owned by caller
+ FT_Face mFace;
+};
+
+// A couple of FreeType-based utilities shared by gfxFontconfigFontEntry
+// and FT2FontEntry.
+
+typedef struct FT_MM_Var_ FT_MM_Var;
+
+class gfxFT2Utils {
+ public:
+ static void GetVariationAxes(const FT_MM_Var* aMMVar,
+ nsTArray<gfxFontVariationAxis>& aAxes);
+
+ static void GetVariationInstances(
+ gfxFontEntry* aFontEntry, const FT_MM_Var* aMMVar,
+ nsTArray<gfxFontVariationInstance>& aInstances);
+};
+
+#endif /* GFX_FT2UTILS_H */
diff --git a/gfx/thebes/gfxFailure.h b/gfx/thebes/gfxFailure.h
new file mode 100644
index 0000000000..04192a5a6e
--- /dev/null
+++ b/gfx/thebes/gfxFailure.h
@@ -0,0 +1,24 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef gfxFailure_h_
+#define gfxFailure_h_
+
+#include "nsString.h"
+#include "nsIGfxInfo.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla {
+namespace gfx {
+inline void LogFailure(const nsCString& failure) {
+ nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
+ gfxInfo->LogFailure(failure);
+}
+} // namespace gfx
+} // namespace mozilla
+
+#endif // gfxFailure_h_
diff --git a/gfx/thebes/gfxFcPlatformFontList.cpp b/gfx/thebes/gfxFcPlatformFontList.cpp
new file mode 100644
index 0000000000..4ecbe0d657
--- /dev/null
+++ b/gfx/thebes/gfxFcPlatformFontList.cpp
@@ -0,0 +1,2922 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Logging.h"
+
+#include "gfxFcPlatformFontList.h"
+#include "gfxFont.h"
+#include "gfxFontConstants.h"
+#include "gfxFT2Utils.h"
+#include "gfxPlatform.h"
+#include "nsPresContext.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "nsGkAtoms.h"
+#include "nsString.h"
+#include "nsStringFwd.h"
+#include "nsUnicodeProperties.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsXULAppAPI.h"
+#include "SharedFontList-impl.h"
+#include "StandardFonts-linux.inc"
+#include "mozilla/intl/Locale.h"
+
+#include "mozilla/gfx/HelpersCairo.h"
+
+#include <cairo-ft.h>
+#include <fontconfig/fcfreetype.h>
+#include <fontconfig/fontconfig.h>
+#include <harfbuzz/hb.h>
+#include <dlfcn.h>
+#include <unistd.h>
+
+#ifdef MOZ_WIDGET_GTK
+# include <gdk/gdk.h>
+# include <gtk/gtk.h>
+# include "gfxPlatformGtk.h"
+# include "mozilla/WidgetUtilsGtk.h"
+#endif
+
+#ifdef MOZ_X11
+# include "mozilla/X11Util.h"
+#endif
+
+#if defined(MOZ_SANDBOX) && defined(XP_LINUX)
+# include "mozilla/SandboxBrokerPolicyFactory.h"
+# include "mozilla/SandboxSettings.h"
+#endif
+
+#include FT_MULTIPLE_MASTERS_H
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::unicode;
+using namespace mozilla::intl;
+
+#ifndef FC_POSTSCRIPT_NAME
+# define FC_POSTSCRIPT_NAME "postscriptname" /* String */
+#endif
+#ifndef FC_VARIABLE
+# define FC_VARIABLE "variable" /* Bool */
+#endif
+
+#define PRINTING_FC_PROPERTY "gfx.printing"
+
+#define LOG_FONTLIST(args) \
+ MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug, args)
+#define LOG_FONTLIST_ENABLED() \
+ MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug)
+#define LOG_CMAPDATA_ENABLED() \
+ MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_cmapdata), LogLevel::Debug)
+
+static const FcChar8* ToFcChar8Ptr(const char* aStr) {
+ return reinterpret_cast<const FcChar8*>(aStr);
+}
+
+static const char* ToCharPtr(const FcChar8* aStr) {
+ return reinterpret_cast<const char*>(aStr);
+}
+
+// canonical name ==> first en name or first name if no en name
+// This is the required logic for fullname lookups as per CSS3 Fonts spec.
+static uint32_t FindCanonicalNameIndex(FcPattern* aFont,
+ const char* aLangField) {
+ uint32_t n = 0, en = 0;
+ FcChar8* lang;
+ while (FcPatternGetString(aFont, aLangField, n, &lang) == FcResultMatch) {
+ // look for 'en' or variants, en-US, en-JP etc.
+ uint32_t len = strlen(ToCharPtr(lang));
+ bool enPrefix = (strncmp(ToCharPtr(lang), "en", 2) == 0);
+ if (enPrefix && (len == 2 || (len > 2 && aLangField[2] == '-'))) {
+ en = n;
+ break;
+ }
+ n++;
+ }
+ return en;
+}
+
+static void GetFaceNames(FcPattern* aFont, const nsACString& aFamilyName,
+ nsACString& aPostscriptName, nsACString& aFullname) {
+ // get the Postscript name
+ FcChar8* psname;
+ if (FcPatternGetString(aFont, FC_POSTSCRIPT_NAME, 0, &psname) ==
+ FcResultMatch) {
+ aPostscriptName = ToCharPtr(psname);
+ }
+
+ // get the canonical fullname (i.e. en name or first name)
+ uint32_t en = FindCanonicalNameIndex(aFont, FC_FULLNAMELANG);
+ FcChar8* fullname;
+ if (FcPatternGetString(aFont, FC_FULLNAME, en, &fullname) == FcResultMatch) {
+ aFullname = ToCharPtr(fullname);
+ }
+
+ // if have fullname, done
+ if (!aFullname.IsEmpty()) {
+ return;
+ }
+
+ // otherwise, set the fullname to family + style name [en] and use that
+ aFullname = aFamilyName;
+
+ // figure out the en style name
+ en = FindCanonicalNameIndex(aFont, FC_STYLELANG);
+ nsAutoCString style;
+ FcChar8* stylename = nullptr;
+ FcPatternGetString(aFont, FC_STYLE, en, &stylename);
+ if (stylename) {
+ style = ToCharPtr(stylename);
+ }
+
+ if (!style.IsEmpty() && !style.EqualsLiteral("Regular")) {
+ aFullname.Append(' ');
+ aFullname.Append(style);
+ }
+}
+
+static FontWeight MapFcWeight(int aFcWeight) {
+ if (aFcWeight <= (FC_WEIGHT_THIN + FC_WEIGHT_EXTRALIGHT) / 2) {
+ return FontWeight::FromInt(100);
+ }
+ if (aFcWeight <= (FC_WEIGHT_EXTRALIGHT + FC_WEIGHT_LIGHT) / 2) {
+ return FontWeight::FromInt(200);
+ }
+ if (aFcWeight <= (FC_WEIGHT_LIGHT + FC_WEIGHT_BOOK) / 2) {
+ return FontWeight::FromInt(300);
+ }
+ if (aFcWeight <= (FC_WEIGHT_REGULAR + FC_WEIGHT_MEDIUM) / 2) {
+ // This includes FC_WEIGHT_BOOK
+ return FontWeight::FromInt(400);
+ }
+ if (aFcWeight <= (FC_WEIGHT_MEDIUM + FC_WEIGHT_DEMIBOLD) / 2) {
+ return FontWeight::FromInt(500);
+ }
+ if (aFcWeight <= (FC_WEIGHT_DEMIBOLD + FC_WEIGHT_BOLD) / 2) {
+ return FontWeight::FromInt(600);
+ }
+ if (aFcWeight <= (FC_WEIGHT_BOLD + FC_WEIGHT_EXTRABOLD) / 2) {
+ return FontWeight::FromInt(700);
+ }
+ if (aFcWeight <= (FC_WEIGHT_EXTRABOLD + FC_WEIGHT_BLACK) / 2) {
+ return FontWeight::FromInt(800);
+ }
+ if (aFcWeight <= FC_WEIGHT_BLACK) {
+ return FontWeight::FromInt(900);
+ }
+
+ // including FC_WEIGHT_EXTRABLACK
+ return FontWeight::FromInt(901);
+}
+
+// TODO(emilio, jfkthame): I think this can now be more fine-grained.
+static FontStretch MapFcWidth(int aFcWidth) {
+ if (aFcWidth <= (FC_WIDTH_ULTRACONDENSED + FC_WIDTH_EXTRACONDENSED) / 2) {
+ return FontStretch::ULTRA_CONDENSED;
+ }
+ if (aFcWidth <= (FC_WIDTH_EXTRACONDENSED + FC_WIDTH_CONDENSED) / 2) {
+ return FontStretch::EXTRA_CONDENSED;
+ }
+ if (aFcWidth <= (FC_WIDTH_CONDENSED + FC_WIDTH_SEMICONDENSED) / 2) {
+ return FontStretch::CONDENSED;
+ }
+ if (aFcWidth <= (FC_WIDTH_SEMICONDENSED + FC_WIDTH_NORMAL) / 2) {
+ return FontStretch::SEMI_CONDENSED;
+ }
+ if (aFcWidth <= (FC_WIDTH_NORMAL + FC_WIDTH_SEMIEXPANDED) / 2) {
+ return FontStretch::NORMAL;
+ }
+ if (aFcWidth <= (FC_WIDTH_SEMIEXPANDED + FC_WIDTH_EXPANDED) / 2) {
+ return FontStretch::SEMI_EXPANDED;
+ }
+ if (aFcWidth <= (FC_WIDTH_EXPANDED + FC_WIDTH_EXTRAEXPANDED) / 2) {
+ return FontStretch::EXPANDED;
+ }
+ if (aFcWidth <= (FC_WIDTH_EXTRAEXPANDED + FC_WIDTH_ULTRAEXPANDED) / 2) {
+ return FontStretch::EXTRA_EXPANDED;
+ }
+ return FontStretch::ULTRA_EXPANDED;
+}
+
+static void GetFontProperties(FcPattern* aFontPattern, WeightRange* aWeight,
+ StretchRange* aStretch,
+ SlantStyleRange* aSlantStyle,
+ uint16_t* aSize = nullptr) {
+ // weight
+ int weight;
+ if (FcPatternGetInteger(aFontPattern, FC_WEIGHT, 0, &weight) !=
+ FcResultMatch) {
+ weight = FC_WEIGHT_REGULAR;
+ }
+ *aWeight = WeightRange(MapFcWeight(weight));
+
+ // width
+ int width;
+ if (FcPatternGetInteger(aFontPattern, FC_WIDTH, 0, &width) != FcResultMatch) {
+ width = FC_WIDTH_NORMAL;
+ }
+ *aStretch = StretchRange(MapFcWidth(width));
+
+ // italic
+ int slant;
+ if (FcPatternGetInteger(aFontPattern, FC_SLANT, 0, &slant) != FcResultMatch) {
+ slant = FC_SLANT_ROMAN;
+ }
+ if (slant == FC_SLANT_OBLIQUE) {
+ *aSlantStyle = SlantStyleRange(FontSlantStyle::OBLIQUE);
+ } else if (slant > 0) {
+ *aSlantStyle = SlantStyleRange(FontSlantStyle::ITALIC);
+ }
+
+ if (aSize) {
+ // pixel size, or zero if scalable
+ FcBool scalable;
+ if (FcPatternGetBool(aFontPattern, FC_SCALABLE, 0, &scalable) ==
+ FcResultMatch &&
+ scalable) {
+ *aSize = 0;
+ } else {
+ double size;
+ if (FcPatternGetDouble(aFontPattern, FC_PIXEL_SIZE, 0, &size) ==
+ FcResultMatch) {
+ *aSize = uint16_t(NS_round(size));
+ } else {
+ *aSize = 0;
+ }
+ }
+ }
+}
+
+void gfxFontconfigFontEntry::GetUserFontFeatures(FcPattern* aPattern) {
+ int fontFeaturesNum = 0;
+ char* s;
+ hb_feature_t tmpFeature;
+ while (FcResultMatch == FcPatternGetString(aPattern, "fontfeatures",
+ fontFeaturesNum, (FcChar8**)&s)) {
+ bool ret = hb_feature_from_string(s, -1, &tmpFeature);
+ if (ret) {
+ mFeatureSettings.AppendElement(
+ (gfxFontFeature){tmpFeature.tag, tmpFeature.value});
+ }
+ fontFeaturesNum++;
+ }
+}
+
+gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsACString& aFaceName,
+ FcPattern* aFontPattern,
+ bool aIgnoreFcCharmap)
+ : gfxFT2FontEntryBase(aFaceName),
+ mFontPattern(aFontPattern),
+ mFTFaceInitialized(false),
+ mIgnoreFcCharmap(aIgnoreFcCharmap) {
+ GetFontProperties(aFontPattern, &mWeightRange, &mStretchRange, &mStyleRange);
+ GetUserFontFeatures(mFontPattern);
+}
+
+gfxFontEntry* gfxFontconfigFontEntry::Clone() const {
+ MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!");
+ return new gfxFontconfigFontEntry(Name(), mFontPattern, mIgnoreFcCharmap);
+}
+
+static already_AddRefed<FcPattern> CreatePatternForFace(FT_Face aFace) {
+ // Use fontconfig to fill out the pattern from the FTFace.
+ // The "file" argument cannot be nullptr (in fontconfig-2.6.0 at
+ // least). The dummy file passed here is removed below.
+ //
+ // When fontconfig scans the system fonts, FcConfigGetBlanks(nullptr)
+ // is passed as the "blanks" argument, which provides that unexpectedly
+ // blank glyphs are elided. Here, however, we pass nullptr for
+ // "blanks", effectively assuming that, if the font has a blank glyph,
+ // then the author intends any associated character to be rendered
+ // blank.
+ RefPtr<FcPattern> pattern =
+ dont_AddRef(FcFreeTypeQueryFace(aFace, ToFcChar8Ptr(""), 0, nullptr));
+ // given that we have a FT_Face, not really sure this is possible...
+ if (!pattern) {
+ pattern = dont_AddRef(FcPatternCreate());
+ }
+ FcPatternDel(pattern, FC_FILE);
+ FcPatternDel(pattern, FC_INDEX);
+
+ // Make a new pattern and store the face in it so that cairo uses
+ // that when creating a cairo font face.
+ FcPatternAddFTFace(pattern, FC_FT_FACE, aFace);
+
+ return pattern.forget();
+}
+
+static already_AddRefed<SharedFTFace> CreateFaceForPattern(
+ FcPattern* aPattern) {
+ FcChar8* filename;
+ if (FcPatternGetString(aPattern, FC_FILE, 0, &filename) != FcResultMatch) {
+ return nullptr;
+ }
+ int index;
+ if (FcPatternGetInteger(aPattern, FC_INDEX, 0, &index) != FcResultMatch) {
+ index = 0; // default to 0 if not found in pattern
+ }
+ return Factory::NewSharedFTFace(nullptr, ToCharPtr(filename), index);
+}
+
+gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsACString& aFaceName,
+ WeightRange aWeight,
+ StretchRange aStretch,
+ SlantStyleRange aStyle,
+ RefPtr<SharedFTFace>&& aFace)
+ : gfxFT2FontEntryBase(aFaceName),
+ mFontPattern(CreatePatternForFace(aFace->GetFace())),
+ mFTFace(aFace.forget().take()),
+ mFTFaceInitialized(true),
+ mIgnoreFcCharmap(true) {
+ mWeightRange = aWeight;
+ mStyleRange = aStyle;
+ mStretchRange = aStretch;
+ mIsDataUserFont = true;
+}
+
+gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsACString& aFaceName,
+ FcPattern* aFontPattern,
+ WeightRange aWeight,
+ StretchRange aStretch,
+ SlantStyleRange aStyle)
+ : gfxFT2FontEntryBase(aFaceName),
+ mFontPattern(aFontPattern),
+ mFTFaceInitialized(false) {
+ mWeightRange = aWeight;
+ mStyleRange = aStyle;
+ mStretchRange = aStretch;
+ mIsLocalUserFont = true;
+
+ // The proper setting of mIgnoreFcCharmap is tricky for fonts loaded
+ // via src:local()...
+ // If the local font happens to come from the application fontset,
+ // we want to set it to true so that color/svg fonts will work even
+ // if the default glyphs are blank; but if the local font is a non-
+ // sfnt face (e.g. legacy type 1) then we need to set it to false
+ // because our cmap-reading code will fail and we depend on FT+Fc to
+ // determine the coverage.
+ // We set the flag here, but may flip it the first time TestCharacterMap
+ // is called, at which point we'll look to see whether a 'cmap' is
+ // actually present in the font.
+ mIgnoreFcCharmap = true;
+
+ GetUserFontFeatures(mFontPattern);
+}
+
+typedef FT_Error (*GetVarFunc)(FT_Face, FT_MM_Var**);
+typedef FT_Error (*DoneVarFunc)(FT_Library, FT_MM_Var*);
+static GetVarFunc sGetVar;
+static DoneVarFunc sDoneVar;
+static bool sInitializedVarFuncs = false;
+
+static void InitializeVarFuncs() {
+ if (sInitializedVarFuncs) {
+ return;
+ }
+ sInitializedVarFuncs = true;
+#if MOZ_TREE_FREETYPE
+ sGetVar = &FT_Get_MM_Var;
+ sDoneVar = &FT_Done_MM_Var;
+#else
+ sGetVar = (GetVarFunc)dlsym(RTLD_DEFAULT, "FT_Get_MM_Var");
+ sDoneVar = (DoneVarFunc)dlsym(RTLD_DEFAULT, "FT_Done_MM_Var");
+#endif
+}
+
+gfxFontconfigFontEntry::~gfxFontconfigFontEntry() {
+ if (mMMVar) {
+ // Prior to freetype 2.9, there was no specific function to free the
+ // FT_MM_Var record, and the docs just said to use free().
+ // InitializeVarFuncs must have been called in order for mMMVar to be
+ // non-null here, so we don't need to do it again.
+ if (sDoneVar) {
+ auto ftFace = GetFTFace();
+ MOZ_ASSERT(ftFace, "How did mMMVar get set without a face?");
+ (*sDoneVar)(ftFace->GetFace()->glyph->library, mMMVar);
+ } else {
+ free(mMMVar);
+ }
+ }
+ if (mFTFaceInitialized) {
+ auto face = mFTFace.exchange(nullptr);
+ NS_IF_RELEASE(face);
+ }
+}
+
+nsresult gfxFontconfigFontEntry::ReadCMAP(FontInfoData* aFontInfoData) {
+ // attempt this once, if errors occur leave a blank cmap
+ if (mCharacterMap) {
+ return NS_OK;
+ }
+
+ RefPtr<gfxCharacterMap> charmap;
+ nsresult rv;
+
+ uint32_t uvsOffset = 0;
+ if (aFontInfoData &&
+ (charmap = GetCMAPFromFontInfo(aFontInfoData, uvsOffset))) {
+ rv = NS_OK;
+ } else {
+ uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p');
+ charmap = new gfxCharacterMap();
+ AutoTable cmapTable(this, kCMAP);
+
+ if (cmapTable) {
+ uint32_t cmapLen;
+ const uint8_t* cmapData = reinterpret_cast<const uint8_t*>(
+ hb_blob_get_data(cmapTable, &cmapLen));
+ rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, uvsOffset);
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+ mUVSOffset.exchange(uvsOffset);
+
+ bool setCharMap = true;
+ if (NS_SUCCEEDED(rv)) {
+ gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
+ fontlist::FontList* sharedFontList = pfl->SharedFontList();
+ if (!IsUserFont() && mShmemFace) {
+ mShmemFace->SetCharacterMap(sharedFontList, charmap); // async
+ if (TrySetShmemCharacterMap()) {
+ setCharMap = false;
+ }
+ } else {
+ charmap = pfl->FindCharMap(charmap);
+ }
+ mHasCmapTable = true;
+ } else {
+ // if error occurred, initialize to null cmap
+ charmap = new gfxCharacterMap();
+ mHasCmapTable = false;
+ }
+ if (setCharMap) {
+ if (mCharacterMap.compareExchange(nullptr, charmap.get())) {
+ charmap.get()->AddRef();
+ }
+ }
+
+ LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %zu hash: %8.8x%s\n",
+ mName.get(), charmap->SizeOfIncludingThis(moz_malloc_size_of),
+ charmap->mHash, mCharacterMap == charmap ? " new" : ""));
+ if (LOG_CMAPDATA_ENABLED()) {
+ char prefix[256];
+ SprintfLiteral(prefix, "(cmapdata) name: %.220s", mName.get());
+ charmap->Dump(prefix, eGfxLog_cmapdata);
+ }
+
+ return rv;
+}
+
+static bool HasChar(FcPattern* aFont, FcChar32 aCh) {
+ FcCharSet* charset = nullptr;
+ FcPatternGetCharSet(aFont, FC_CHARSET, 0, &charset);
+ return charset && FcCharSetHasChar(charset, aCh);
+}
+
+bool gfxFontconfigFontEntry::TestCharacterMap(uint32_t aCh) {
+ // For user fonts, or for fonts bundled with the app (which might include
+ // color/svg glyphs where the default glyphs may be blank, and thus confuse
+ // fontconfig/freetype's char map checking), we instead check the cmap
+ // directly for character coverage.
+ if (mIgnoreFcCharmap) {
+ // If it does not actually have a cmap, switch our strategy to use
+ // fontconfig's charmap after all (except for data fonts, which must
+ // always have a cmap to have passed OTS validation).
+ if (!mIsDataUserFont && !HasFontTable(TRUETYPE_TAG('c', 'm', 'a', 'p'))) {
+ mIgnoreFcCharmap = false;
+ // ...and continue with HasChar() below.
+ } else {
+ return gfxFontEntry::TestCharacterMap(aCh);
+ }
+ }
+ // otherwise (for system fonts), use the charmap in the pattern
+ return HasChar(mFontPattern, aCh);
+}
+
+bool gfxFontconfigFontEntry::HasFontTable(uint32_t aTableTag) {
+ if (FTUserFontData* ufd = GetUserFontData()) {
+ if (ufd->FontData()) {
+ return !!gfxFontUtils::FindTableDirEntry(ufd->FontData(), aTableTag);
+ }
+ }
+ return gfxFT2FontEntryBase::FaceHasTable(GetFTFace(), aTableTag);
+}
+
+hb_blob_t* gfxFontconfigFontEntry::GetFontTable(uint32_t aTableTag) {
+ // for data fonts, read directly from the font data
+ if (FTUserFontData* ufd = GetUserFontData()) {
+ if (ufd->FontData()) {
+ return gfxFontUtils::GetTableFromFontData(ufd->FontData(), aTableTag);
+ }
+ }
+
+ return gfxFontEntry::GetFontTable(aTableTag);
+}
+
+double gfxFontconfigFontEntry::GetAspect(uint8_t aSizeAdjustBasis) {
+ using FontSizeAdjust = gfxFont::FontSizeAdjust;
+ if (FontSizeAdjust::Tag(aSizeAdjustBasis) == FontSizeAdjust::Tag::ExHeight ||
+ FontSizeAdjust::Tag(aSizeAdjustBasis) == FontSizeAdjust::Tag::CapHeight) {
+ // try to compute aspect from OS/2 metrics if available
+ AutoTable os2Table(this, TRUETYPE_TAG('O', 'S', '/', '2'));
+ if (os2Table) {
+ uint16_t upem = UnitsPerEm();
+ if (upem != kInvalidUPEM) {
+ uint32_t len;
+ const auto* os2 =
+ reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len));
+ if (uint16_t(os2->version) >= 2) {
+ // XXX(jfkthame) Other implementations don't have the check for
+ // values <= 0.1em; should we drop that here? Just require it to be
+ // a positive number?
+ if (FontSizeAdjust::Tag(aSizeAdjustBasis) ==
+ FontSizeAdjust::Tag::ExHeight) {
+ if (len >= offsetof(OS2Table, sxHeight) + sizeof(int16_t) &&
+ int16_t(os2->sxHeight) > 0.1 * upem) {
+ return double(int16_t(os2->sxHeight)) / upem;
+ }
+ }
+ if (FontSizeAdjust::Tag(aSizeAdjustBasis) ==
+ FontSizeAdjust::Tag::CapHeight) {
+ if (len >= offsetof(OS2Table, sCapHeight) + sizeof(int16_t) &&
+ int16_t(os2->sCapHeight) > 0.1 * upem) {
+ return double(int16_t(os2->sCapHeight)) / upem;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // create a font to calculate the requested aspect
+ gfxFontStyle s;
+ s.size = 256.0; // pick large size to reduce hinting artifacts
+ RefPtr<gfxFont> font = FindOrMakeFont(&s);
+ if (font) {
+ const gfxFont::Metrics& metrics =
+ font->GetMetrics(nsFontMetrics::eHorizontal);
+ if (metrics.emHeight == 0) {
+ return 0;
+ }
+ switch (FontSizeAdjust::Tag(aSizeAdjustBasis)) {
+ case FontSizeAdjust::Tag::ExHeight:
+ return metrics.xHeight / metrics.emHeight;
+ case FontSizeAdjust::Tag::CapHeight:
+ return metrics.capHeight / metrics.emHeight;
+ case FontSizeAdjust::Tag::ChWidth:
+ return metrics.zeroWidth > 0 ? metrics.zeroWidth / metrics.emHeight
+ : 0.5;
+ case FontSizeAdjust::Tag::IcWidth:
+ case FontSizeAdjust::Tag::IcHeight: {
+ bool vertical = FontSizeAdjust::Tag(aSizeAdjustBasis) ==
+ FontSizeAdjust::Tag::IcHeight;
+ gfxFloat advance =
+ font->GetCharAdvance(gfxFont::kWaterIdeograph, vertical);
+ return advance > 0 ? advance / metrics.emHeight : 1.0;
+ }
+ default:
+ break;
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE("failed to compute size-adjust aspect");
+ return 0.5;
+}
+
+static void PrepareFontOptions(FcPattern* aPattern, int* aOutLoadFlags,
+ unsigned int* aOutSynthFlags) {
+ int loadFlags = FT_LOAD_DEFAULT;
+ unsigned int synthFlags = 0;
+
+ // xxx - taken from the gfxFontconfigFonts code, needs to be reviewed
+
+ FcBool printing;
+ if (FcPatternGetBool(aPattern, PRINTING_FC_PROPERTY, 0, &printing) !=
+ FcResultMatch) {
+ printing = FcFalse;
+ }
+
+ // Font options are set explicitly here to improve cairo's caching
+ // behavior and to record the relevant parts of the pattern so that
+ // the pattern can be released.
+ //
+ // Most font_options have already been set as defaults on the FcPattern
+ // with cairo_ft_font_options_substitute(), then user and system
+ // fontconfig configurations were applied. The resulting font_options
+ // have been recorded on the face during
+ // cairo_ft_font_face_create_for_pattern().
+ //
+ // None of the settings here cause this scaled_font to behave any
+ // differently from how it would behave if it were created from the same
+ // face with default font_options.
+ //
+ // We set options explicitly so that the same scaled_font will be found in
+ // the cairo_scaled_font_map when cairo loads glyphs from a context with
+ // the same font_face, font_matrix, ctm, and surface font_options.
+ //
+ // Unfortunately, _cairo_scaled_font_keys_equal doesn't know about the
+ // font_options on the cairo_ft_font_face, and doesn't consider default
+ // option values to not match any explicit values.
+ //
+ // Even after cairo_set_scaled_font is used to set font_options for the
+ // cairo context, when cairo looks for a scaled_font for the context, it
+ // will look for a font with some option values from the target surface if
+ // any values are left default on the context font_options. If this
+ // scaled_font is created with default font_options, cairo will not find
+ // it.
+ //
+ // The one option not recorded in the pattern is hint_metrics, which will
+ // affect glyph metrics. The default behaves as CAIRO_HINT_METRICS_ON.
+ // We should be considering the font_options of the surface on which this
+ // font will be used, but currently we don't have different gfxFonts for
+ // different surface font_options, so we'll create a font suitable for the
+ // Screen. Image and xlib surfaces default to CAIRO_HINT_METRICS_ON.
+
+ // The remaining options have been recorded on the pattern and the face.
+ // _cairo_ft_options_merge has some logic to decide which options from the
+ // scaled_font or from the cairo_ft_font_face take priority in the way the
+ // font behaves.
+ //
+ // In the majority of cases, _cairo_ft_options_merge uses the options from
+ // the cairo_ft_font_face, so sometimes it is not so important which
+ // values are set here so long as they are not defaults, but we'll set
+ // them to the exact values that we expect from the font, to be consistent
+ // and to protect against changes in cairo.
+ //
+ // In some cases, _cairo_ft_options_merge uses some options from the
+ // scaled_font's font_options rather than options on the
+ // cairo_ft_font_face (from fontconfig).
+ // https://bugs.freedesktop.org/show_bug.cgi?id=11838
+ //
+ // Surface font options were set on the pattern in
+ // cairo_ft_font_options_substitute. If fontconfig has changed the
+ // hint_style then that is what the user (or distribution) wants, so we
+ // use the setting from the FcPattern.
+ //
+ // Fallback values here mirror treatment of defaults in cairo-ft-font.c.
+ FcBool hinting = FcFalse;
+ if (FcPatternGetBool(aPattern, FC_HINTING, 0, &hinting) != FcResultMatch) {
+ hinting = FcTrue;
+ }
+
+ int fc_hintstyle = FC_HINT_NONE;
+ if (!printing && hinting &&
+ FcPatternGetInteger(aPattern, FC_HINT_STYLE, 0, &fc_hintstyle) !=
+ FcResultMatch) {
+ fc_hintstyle = FC_HINT_FULL;
+ }
+ switch (fc_hintstyle) {
+ case FC_HINT_NONE:
+ loadFlags = FT_LOAD_NO_HINTING;
+ break;
+ case FC_HINT_SLIGHT:
+ loadFlags = FT_LOAD_TARGET_LIGHT;
+ break;
+ }
+
+ FcBool fc_antialias;
+ if (FcPatternGetBool(aPattern, FC_ANTIALIAS, 0, &fc_antialias) !=
+ FcResultMatch) {
+ fc_antialias = FcTrue;
+ }
+ if (!fc_antialias) {
+ if (fc_hintstyle != FC_HINT_NONE) {
+ loadFlags = FT_LOAD_TARGET_MONO;
+ }
+ loadFlags |= FT_LOAD_MONOCHROME;
+ } else if (fc_hintstyle == FC_HINT_FULL) {
+ int fc_rgba;
+ if (FcPatternGetInteger(aPattern, FC_RGBA, 0, &fc_rgba) != FcResultMatch) {
+ fc_rgba = FC_RGBA_UNKNOWN;
+ }
+ switch (fc_rgba) {
+ case FC_RGBA_RGB:
+ case FC_RGBA_BGR:
+ loadFlags = FT_LOAD_TARGET_LCD;
+ break;
+ case FC_RGBA_VRGB:
+ case FC_RGBA_VBGR:
+ loadFlags = FT_LOAD_TARGET_LCD_V;
+ break;
+ }
+ }
+
+ if (!FcPatternAllowsBitmaps(aPattern, fc_antialias != FcFalse,
+ fc_hintstyle != FC_HINT_NONE)) {
+ loadFlags |= FT_LOAD_NO_BITMAP;
+ }
+
+ FcBool autohint;
+ if (FcPatternGetBool(aPattern, FC_AUTOHINT, 0, &autohint) == FcResultMatch &&
+ autohint) {
+ loadFlags |= FT_LOAD_FORCE_AUTOHINT;
+ }
+
+ FcBool embolden;
+ if (FcPatternGetBool(aPattern, FC_EMBOLDEN, 0, &embolden) == FcResultMatch &&
+ embolden) {
+ synthFlags |= CAIRO_FT_SYNTHESIZE_BOLD;
+ }
+
+ *aOutLoadFlags = loadFlags;
+ *aOutSynthFlags = synthFlags;
+}
+
+#ifdef MOZ_X11
+static bool GetXftInt(Display* aDisplay, const char* aName, int* aResult) {
+ if (!aDisplay) {
+ return false;
+ }
+ char* value = XGetDefault(aDisplay, "Xft", aName);
+ if (!value) {
+ return false;
+ }
+ if (FcNameConstant(const_cast<FcChar8*>(ToFcChar8Ptr(value)), aResult)) {
+ return true;
+ }
+ char* end;
+ *aResult = strtol(value, &end, 0);
+ if (end != value) {
+ return true;
+ }
+ return false;
+}
+#endif
+
+static void PreparePattern(FcPattern* aPattern, bool aIsPrinterFont) {
+ FcConfigSubstitute(nullptr, aPattern, FcMatchPattern);
+
+ // This gets cairo_font_options_t for the Screen. We should have
+ // different font options for printing (no hinting) but we are not told
+ // what we are measuring for.
+ //
+ // If cairo adds support for lcd_filter, gdk will not provide the default
+ // setting for that option. We could get the default setting by creating
+ // an xlib surface once, recording its font_options, and then merging the
+ // gdk options.
+ //
+ // Using an xlib surface would also be an option to get Screen font
+ // options for non-GTK X11 toolkits, but less efficient than using GDK to
+ // pick up dynamic changes.
+ if (aIsPrinterFont) {
+ cairo_font_options_t* options = cairo_font_options_create();
+ cairo_font_options_set_hint_style(options, CAIRO_HINT_STYLE_NONE);
+ cairo_font_options_set_antialias(options, CAIRO_ANTIALIAS_GRAY);
+ cairo_ft_font_options_substitute(options, aPattern);
+ cairo_font_options_destroy(options);
+ FcPatternAddBool(aPattern, PRINTING_FC_PROPERTY, FcTrue);
+#ifdef MOZ_WIDGET_GTK
+ } else {
+ gfxFcPlatformFontList::PlatformFontList()->SubstituteSystemFontOptions(
+ aPattern);
+#endif // MOZ_WIDGET_GTK
+ }
+
+ FcDefaultSubstitute(aPattern);
+}
+
+void gfxFontconfigFontEntry::UnscaledFontCache::MoveToFront(size_t aIndex) {
+ if (aIndex > 0) {
+ ThreadSafeWeakPtr<UnscaledFontFontconfig> front =
+ std::move(mUnscaledFonts[aIndex]);
+ for (size_t i = aIndex; i > 0; i--) {
+ mUnscaledFonts[i] = std::move(mUnscaledFonts[i - 1]);
+ }
+ mUnscaledFonts[0] = std::move(front);
+ }
+}
+
+already_AddRefed<UnscaledFontFontconfig>
+gfxFontconfigFontEntry::UnscaledFontCache::Lookup(const std::string& aFile,
+ uint32_t aIndex) {
+ for (size_t i = 0; i < kNumEntries; i++) {
+ RefPtr<UnscaledFontFontconfig> entry(mUnscaledFonts[i]);
+ if (entry && entry->GetFile() == aFile && entry->GetIndex() == aIndex) {
+ MoveToFront(i);
+ return entry.forget();
+ }
+ }
+ return nullptr;
+}
+
+static inline gfxFloat SizeForStyle(gfxFontconfigFontEntry* aEntry,
+ const gfxFontStyle& aStyle) {
+ return StyleFontSizeAdjust::Tag(aStyle.sizeAdjustBasis) !=
+ StyleFontSizeAdjust::Tag::None
+ ? aStyle.GetAdjustedSize(aEntry->GetAspect(aStyle.sizeAdjustBasis))
+ : aStyle.size * aEntry->mSizeAdjust;
+}
+
+static double ChooseFontSize(gfxFontconfigFontEntry* aEntry,
+ const gfxFontStyle& aStyle) {
+ double requestedSize = SizeForStyle(aEntry, aStyle);
+ double bestDist = -1.0;
+ double bestSize = requestedSize;
+ double size;
+ int v = 0;
+ while (FcPatternGetDouble(aEntry->GetPattern(), FC_PIXEL_SIZE, v, &size) ==
+ FcResultMatch) {
+ ++v;
+ double dist = fabs(size - requestedSize);
+ if (bestDist < 0.0 || dist < bestDist) {
+ bestDist = dist;
+ bestSize = size;
+ }
+ }
+ // If the font has bitmaps but wants to be scaled, then let it scale.
+ if (bestSize >= 0.0) {
+ FcBool scalable;
+ if (FcPatternGetBool(aEntry->GetPattern(), FC_SCALABLE, 0, &scalable) ==
+ FcResultMatch &&
+ scalable) {
+ return requestedSize;
+ }
+ }
+ return bestSize;
+}
+
+gfxFont* gfxFontconfigFontEntry::CreateFontInstance(
+ const gfxFontStyle* aFontStyle) {
+ RefPtr<FcPattern> pattern = dont_AddRef(FcPatternCreate());
+ if (!pattern) {
+ NS_WARNING("Failed to create Fontconfig pattern for font instance");
+ return nullptr;
+ }
+
+ double size = ChooseFontSize(this, *aFontStyle);
+ FcPatternAddDouble(pattern, FC_PIXEL_SIZE, size);
+
+ RefPtr<SharedFTFace> face = GetFTFace();
+ if (!face) {
+ NS_WARNING("Failed to get FreeType face for pattern");
+ return nullptr;
+ }
+ if (HasVariations()) {
+ // For variation fonts, we create a new FT_Face here so that
+ // variation coordinates from the style can be applied without
+ // affecting other font instances created from the same entry
+ // (font resource).
+ // For user fonts: create a new FT_Face from the font data, and then make
+ // a pattern from that.
+ // For system fonts: create a new FT_Face and store it in a copy of the
+ // original mFontPattern.
+ RefPtr<SharedFTFace> varFace = face->GetData()
+ ? face->GetData()->CloneFace()
+ : CreateFaceForPattern(mFontPattern);
+ if (varFace) {
+ AutoTArray<gfxFontVariation, 8> settings;
+ GetVariationsForStyle(settings, *aFontStyle);
+ gfxFT2FontBase::SetupVarCoords(GetMMVar(), settings, varFace->GetFace());
+ face = std::move(varFace);
+ }
+ }
+
+ PreparePattern(pattern, aFontStyle->printerFont);
+ RefPtr<FcPattern> renderPattern =
+ dont_AddRef(FcFontRenderPrepare(nullptr, pattern, mFontPattern));
+ if (!renderPattern) {
+ NS_WARNING("Failed to prepare Fontconfig pattern for font instance");
+ return nullptr;
+ }
+
+ if (aFontStyle->NeedsSyntheticBold(this)) {
+ FcPatternAddBool(renderPattern, FC_EMBOLDEN, FcTrue);
+ }
+
+ // will synthetic oblique be applied using a transform?
+ if (IsUpright() && !aFontStyle->style.IsNormal() &&
+ aFontStyle->allowSyntheticStyle) {
+ // disable embedded bitmaps (mimics behavior in 90-synthetic.conf)
+ FcPatternDel(renderPattern, FC_EMBEDDED_BITMAP);
+ FcPatternAddBool(renderPattern, FC_EMBEDDED_BITMAP, FcFalse);
+ }
+
+ int loadFlags;
+ unsigned int synthFlags;
+ PrepareFontOptions(renderPattern, &loadFlags, &synthFlags);
+
+ std::string file;
+ int index = 0;
+ if (!face->GetData()) {
+ const FcChar8* fcFile;
+ if (FcPatternGetString(renderPattern, FC_FILE, 0,
+ const_cast<FcChar8**>(&fcFile)) != FcResultMatch ||
+ FcPatternGetInteger(renderPattern, FC_INDEX, 0, &index) !=
+ FcResultMatch) {
+ NS_WARNING("No file in Fontconfig pattern for font instance");
+ return nullptr;
+ }
+ file = ToCharPtr(fcFile);
+ }
+
+ RefPtr<UnscaledFontFontconfig> unscaledFont;
+ {
+ AutoReadLock lock(mLock);
+ unscaledFont = mUnscaledFontCache.Lookup(file, index);
+ }
+
+ if (!unscaledFont) {
+ AutoWriteLock lock(mLock);
+ // Here, we use the original mFTFace, not a potential clone with variation
+ // settings applied.
+ auto ftFace = GetFTFace();
+ unscaledFont = ftFace->GetData() ? new UnscaledFontFontconfig(ftFace)
+ : new UnscaledFontFontconfig(
+ std::move(file), index, ftFace);
+ mUnscaledFontCache.Add(unscaledFont);
+ }
+
+ gfxFont* newFont = new gfxFontconfigFont(
+ unscaledFont, std::move(face), renderPattern, size, this, aFontStyle,
+ loadFlags, (synthFlags & CAIRO_FT_SYNTHESIZE_BOLD) != 0);
+
+ return newFont;
+}
+
+SharedFTFace* gfxFontconfigFontEntry::GetFTFace() {
+ if (!mFTFaceInitialized) {
+ RefPtr<SharedFTFace> face = CreateFaceForPattern(mFontPattern);
+ if (face) {
+ if (mFTFace.compareExchange(nullptr, face.get())) {
+ Unused << face.forget(); // The reference is now owned by mFTFace.
+ mFTFaceInitialized = true;
+ } else {
+ // We lost a race to set mFTFace! Just discard our new face.
+ }
+ }
+ }
+ return mFTFace;
+}
+
+FTUserFontData* gfxFontconfigFontEntry::GetUserFontData() {
+ auto face = GetFTFace();
+ if (face && face->GetData()) {
+ return static_cast<FTUserFontData*>(face->GetData());
+ }
+ return nullptr;
+}
+
+bool gfxFontconfigFontEntry::HasVariations() {
+ // If the answer is already cached, just return it.
+ switch (mHasVariations) {
+ case HasVariationsState::No:
+ return false;
+ case HasVariationsState::Yes:
+ return true;
+ case HasVariationsState::Uninitialized:
+ break;
+ }
+
+ // Figure out whether we have variations, and record in mHasVariations.
+ // (It doesn't matter if we race with another thread to set this; the result
+ // will be the same.)
+
+ if (!gfxPlatform::HasVariationFontSupport()) {
+ mHasVariations = HasVariationsState::No;
+ return false;
+ }
+
+ // For installed fonts, query the fontconfig pattern rather than paying
+ // the cost of loading a FT_Face that we otherwise might never need.
+ if (!IsUserFont() || IsLocalUserFont()) {
+ FcBool variable;
+ if ((FcPatternGetBool(mFontPattern, FC_VARIABLE, 0, &variable) ==
+ FcResultMatch) &&
+ variable) {
+ mHasVariations = HasVariationsState::Yes;
+ return true;
+ }
+ } else {
+ if (auto ftFace = GetFTFace()) {
+ if (ftFace->GetFace()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) {
+ mHasVariations = HasVariationsState::Yes;
+ return true;
+ }
+ }
+ }
+
+ mHasVariations = HasVariationsState::No;
+ return false;
+}
+
+FT_MM_Var* gfxFontconfigFontEntry::GetMMVar() {
+ {
+ AutoReadLock lock(mLock);
+ if (mMMVarInitialized) {
+ return mMMVar;
+ }
+ }
+
+ AutoWriteLock lock(mLock);
+
+ mMMVarInitialized = true;
+ InitializeVarFuncs();
+ if (!sGetVar) {
+ return nullptr;
+ }
+ auto ftFace = GetFTFace();
+ if (!ftFace) {
+ return nullptr;
+ }
+ if (FT_Err_Ok != (*sGetVar)(ftFace->GetFace(), &mMMVar)) {
+ mMMVar = nullptr;
+ }
+ return mMMVar;
+}
+
+void gfxFontconfigFontEntry::GetVariationAxes(
+ nsTArray<gfxFontVariationAxis>& aAxes) {
+ if (!HasVariations()) {
+ return;
+ }
+ gfxFT2Utils::GetVariationAxes(GetMMVar(), aAxes);
+}
+
+void gfxFontconfigFontEntry::GetVariationInstances(
+ nsTArray<gfxFontVariationInstance>& aInstances) {
+ if (!HasVariations()) {
+ return;
+ }
+ gfxFT2Utils::GetVariationInstances(this, GetMMVar(), aInstances);
+}
+
+nsresult gfxFontconfigFontEntry::CopyFontTable(uint32_t aTableTag,
+ nsTArray<uint8_t>& aBuffer) {
+ NS_ASSERTION(!mIsDataUserFont,
+ "data fonts should be reading tables directly from memory");
+ return gfxFT2FontEntryBase::CopyFaceTable(GetFTFace(), aTableTag, aBuffer);
+}
+
+void gfxFontconfigFontFamily::FindStyleVariationsLocked(
+ FontInfoData* aFontInfoData) {
+ if (mHasStyles) {
+ return;
+ }
+
+ // add font entries for each of the faces
+ uint32_t numFonts = mFontPatterns.Length();
+ NS_ASSERTION(numFonts, "font family containing no faces!!");
+ uint32_t numRegularFaces = 0;
+ for (uint32_t i = 0; i < numFonts; i++) {
+ FcPattern* face = mFontPatterns[i];
+
+ // figure out the psname/fullname and choose which to use as the facename
+ nsAutoCString psname, fullname;
+ GetFaceNames(face, mName, psname, fullname);
+ const nsAutoCString& faceName = !psname.IsEmpty() ? psname : fullname;
+
+ gfxFontconfigFontEntry* fontEntry =
+ new gfxFontconfigFontEntry(faceName, face, mContainsAppFonts);
+
+ if (gfxPlatform::HasVariationFontSupport()) {
+ fontEntry->SetupVariationRanges();
+ }
+
+ AddFontEntryLocked(fontEntry);
+
+ if (fontEntry->IsNormalStyle()) {
+ numRegularFaces++;
+ }
+
+ if (LOG_FONTLIST_ENABLED()) {
+ nsAutoCString weightString;
+ fontEntry->Weight().ToString(weightString);
+ nsAutoCString stretchString;
+ fontEntry->Stretch().ToString(stretchString);
+ nsAutoCString styleString;
+ fontEntry->SlantStyle().ToString(styleString);
+ LOG_FONTLIST(
+ ("(fontlist) added (%s) to family (%s)"
+ " with style: %s weight: %s stretch: %s"
+ " psname: %s fullname: %s",
+ fontEntry->Name().get(), Name().get(), styleString.get(),
+ weightString.get(), stretchString.get(), psname.get(),
+ fullname.get()));
+ }
+ }
+
+ // somewhat arbitrary, but define a family with two or more regular
+ // faces as a family for which intra-family fallback should be used
+ if (numRegularFaces > 1) {
+ mCheckForFallbackFaces = true;
+ }
+ mFaceNamesInitialized = true;
+ mFontPatterns.Clear();
+ SetHasStyles(true);
+
+ CheckForSimpleFamily();
+}
+
+void gfxFontconfigFontFamily::AddFontPattern(FcPattern* aFontPattern,
+ bool aSingleName) {
+ NS_ASSERTION(
+ !mHasStyles,
+ "font patterns must not be added to already enumerated families");
+
+ FcBool outline;
+ if (FcPatternGetBool(aFontPattern, FC_OUTLINE, 0, &outline) !=
+ FcResultMatch ||
+ !outline) {
+ mHasNonScalableFaces = true;
+
+ FcBool scalable;
+ if (FcPatternGetBool(aFontPattern, FC_SCALABLE, 0, &scalable) ==
+ FcResultMatch &&
+ scalable) {
+ mForceScalable = true;
+ }
+ }
+
+ if (aSingleName) {
+ mFontPatterns.InsertElementAt(mUniqueNameFaceCount++, aFontPattern);
+ } else {
+ mFontPatterns.AppendElement(aFontPattern);
+ }
+}
+
+static const double kRejectDistance = 10000.0;
+
+// Calculate a distance score representing the size disparity between the
+// requested style's size and the font entry's size.
+static double SizeDistance(gfxFontconfigFontEntry* aEntry,
+ const gfxFontStyle& aStyle, bool aForceScalable) {
+ double requestedSize = SizeForStyle(aEntry, aStyle);
+ double bestDist = -1.0;
+ double size;
+ int v = 0;
+ while (FcPatternGetDouble(aEntry->GetPattern(), FC_PIXEL_SIZE, v, &size) ==
+ FcResultMatch) {
+ ++v;
+ double dist = fabs(size - requestedSize);
+ if (bestDist < 0.0 || dist < bestDist) {
+ bestDist = dist;
+ }
+ }
+ if (bestDist < 0.0) {
+ // No size means scalable
+ return -1.0;
+ } else if (aForceScalable || 5.0 * bestDist < requestedSize) {
+ // fontconfig prefers a matching family or lang to pixelsize of bitmap
+ // fonts. CSS suggests a tolerance of 20% on pixelsize.
+ return bestDist;
+ } else {
+ // Reject any non-scalable fonts that are not within tolerance.
+ return kRejectDistance;
+ }
+}
+
+void gfxFontconfigFontFamily::FindAllFontsForStyle(
+ const gfxFontStyle& aFontStyle, nsTArray<gfxFontEntry*>& aFontEntryList,
+ bool aIgnoreSizeTolerance) {
+ gfxFontFamily::FindAllFontsForStyle(aFontStyle, aFontEntryList,
+ aIgnoreSizeTolerance);
+
+ if (!mHasNonScalableFaces) {
+ return;
+ }
+
+ // Iterate over the the available fonts while compacting any groups
+ // of unscalable fonts with matching styles into a single entry
+ // corresponding to the closest available size. If the closest
+ // available size is rejected for being outside tolerance, then the
+ // entire group will be skipped.
+ size_t skipped = 0;
+ gfxFontconfigFontEntry* bestEntry = nullptr;
+ double bestDist = -1.0;
+ for (size_t i = 0; i < aFontEntryList.Length(); i++) {
+ gfxFontconfigFontEntry* entry =
+ static_cast<gfxFontconfigFontEntry*>(aFontEntryList[i]);
+ double dist =
+ SizeDistance(entry, aFontStyle, mForceScalable || aIgnoreSizeTolerance);
+ // If the entry is scalable or has a style that does not match
+ // the group of unscalable fonts, then start a new group.
+ if (dist < 0.0 || !bestEntry || bestEntry->Stretch() != entry->Stretch() ||
+ bestEntry->Weight() != entry->Weight() ||
+ bestEntry->SlantStyle() != entry->SlantStyle()) {
+ // If the best entry in this group is still outside the tolerance,
+ // then skip the entire group.
+ if (bestDist >= kRejectDistance) {
+ skipped++;
+ }
+ // Remove any compacted entries from the previous group.
+ if (skipped) {
+ i -= skipped;
+ aFontEntryList.RemoveElementsAt(i, skipped);
+ skipped = 0;
+ }
+ // Mark the start of the new group.
+ bestEntry = entry;
+ bestDist = dist;
+ } else {
+ // If this entry more closely matches the requested size than the
+ // current best in the group, then take this entry instead.
+ if (dist < bestDist) {
+ aFontEntryList[i - 1 - skipped] = entry;
+ bestEntry = entry;
+ bestDist = dist;
+ }
+ skipped++;
+ }
+ }
+ // If the best entry in this group is still outside the tolerance,
+ // then skip the entire group.
+ if (bestDist >= kRejectDistance) {
+ skipped++;
+ }
+ // Remove any compacted entries from the current group.
+ if (skipped) {
+ aFontEntryList.TruncateLength(aFontEntryList.Length() - skipped);
+ }
+}
+
+static bool PatternHasLang(const FcPattern* aPattern, const FcChar8* aLang) {
+ FcLangSet* langset;
+
+ if (FcPatternGetLangSet(aPattern, FC_LANG, 0, &langset) != FcResultMatch) {
+ return false;
+ }
+
+ if (FcLangSetHasLang(langset, aLang) != FcLangDifferentLang) {
+ return true;
+ }
+ return false;
+}
+
+bool gfxFontconfigFontFamily::SupportsLangGroup(nsAtom* aLangGroup) const {
+ if (!aLangGroup || aLangGroup == nsGkAtoms::Unicode) {
+ return true;
+ }
+
+ nsAutoCString fcLang;
+ gfxFcPlatformFontList* pfl = gfxFcPlatformFontList::PlatformFontList();
+ pfl->GetSampleLangForGroup(aLangGroup, fcLang);
+ if (fcLang.IsEmpty()) {
+ return true;
+ }
+
+ // Before FindStyleVariations has been called, mFontPatterns will contain
+ // the font patterns. Afterward, it'll be empty, but mAvailableFonts
+ // will contain the font entries, each of which holds a reference to its
+ // pattern. We only check the first pattern in each list, because support
+ // for langs is considered to be consistent across all faces in a family.
+ AutoReadLock lock(mLock);
+ FcPattern* fontPattern;
+ if (mFontPatterns.Length()) {
+ fontPattern = mFontPatterns[0];
+ } else if (mAvailableFonts.Length()) {
+ fontPattern = static_cast<gfxFontconfigFontEntry*>(mAvailableFonts[0].get())
+ ->GetPattern();
+ } else {
+ return true;
+ }
+
+ // is lang included in the underlying pattern?
+ return PatternHasLang(fontPattern, ToFcChar8Ptr(fcLang.get()));
+}
+
+/* virtual */
+gfxFontconfigFontFamily::~gfxFontconfigFontFamily() {
+ // Should not be dropped by stylo
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+template <typename Func>
+void gfxFontconfigFontFamily::AddFacesToFontList(Func aAddPatternFunc) {
+ AutoReadLock lock(mLock);
+ if (HasStyles()) {
+ for (auto& fe : mAvailableFonts) {
+ if (!fe) {
+ continue;
+ }
+ auto fce = static_cast<gfxFontconfigFontEntry*>(fe.get());
+ aAddPatternFunc(fce->GetPattern(), mContainsAppFonts);
+ }
+ } else {
+ for (auto& pat : mFontPatterns) {
+ aAddPatternFunc(pat, mContainsAppFonts);
+ }
+ }
+}
+
+gfxFontconfigFont::gfxFontconfigFont(
+ const RefPtr<UnscaledFontFontconfig>& aUnscaledFont,
+ RefPtr<SharedFTFace>&& aFTFace, FcPattern* aPattern, gfxFloat aAdjustedSize,
+ gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle, int aLoadFlags,
+ bool aEmbolden)
+ : gfxFT2FontBase(aUnscaledFont, std::move(aFTFace), aFontEntry, aFontStyle,
+ aLoadFlags, aEmbolden),
+ mPattern(aPattern) {
+ mAdjustedSize = aAdjustedSize;
+ InitMetrics();
+}
+
+gfxFontconfigFont::~gfxFontconfigFont() = default;
+
+already_AddRefed<ScaledFont> gfxFontconfigFont::GetScaledFont(
+ const TextRunDrawParams& aRunParams) {
+ if (ScaledFont* scaledFont = mAzureScaledFont) {
+ return do_AddRef(scaledFont);
+ }
+
+ RefPtr<ScaledFont> newScaledFont = Factory::CreateScaledFontForFontconfigFont(
+ GetUnscaledFont(), GetAdjustedSize(), mFTFace, GetPattern());
+ if (!newScaledFont) {
+ return nullptr;
+ }
+
+ InitializeScaledFont(newScaledFont);
+
+ if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) {
+ Unused << newScaledFont.forget();
+ }
+ ScaledFont* scaledFont = mAzureScaledFont;
+ return do_AddRef(scaledFont);
+}
+
+bool gfxFontconfigFont::ShouldHintMetrics() const {
+ return !GetStyle()->printerFont;
+}
+
+gfxFcPlatformFontList::gfxFcPlatformFontList()
+ : mLocalNames(64),
+ mGenericMappings(32),
+ mFcSubstituteCache(64),
+ mLastConfig(nullptr),
+ mAlwaysUseFontconfigGenerics(true) {
+ CheckFamilyList(kBaseFonts_Ubuntu_20_04);
+ CheckFamilyList(kLangFonts_Ubuntu_20_04);
+ CheckFamilyList(kBaseFonts_Fedora_32);
+ mLastConfig = FcConfigGetCurrent();
+ if (XRE_IsParentProcess()) {
+ // if the rescan interval is set, start the timer
+ int rescanInterval = FcConfigGetRescanInterval(nullptr);
+ if (rescanInterval) {
+ NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mCheckFontUpdatesTimer), CheckFontUpdates, this,
+ (rescanInterval + 1) * 1000, nsITimer::TYPE_REPEATING_SLACK,
+ "gfxFcPlatformFontList::gfxFcPlatformFontList");
+ if (!mCheckFontUpdatesTimer) {
+ NS_WARNING("Failure to create font updates timer");
+ }
+ }
+ }
+
+#ifdef MOZ_BUNDLED_FONTS
+ mBundledFontsInitialized = false;
+#endif
+}
+
+gfxFcPlatformFontList::~gfxFcPlatformFontList() {
+ AutoLock lock(mLock);
+
+ if (mCheckFontUpdatesTimer) {
+ mCheckFontUpdatesTimer->Cancel();
+ mCheckFontUpdatesTimer = nullptr;
+ }
+#ifdef MOZ_WIDGET_GTK
+ ClearSystemFontOptions();
+#endif
+}
+
+void gfxFcPlatformFontList::AddFontSetFamilies(FcFontSet* aFontSet,
+ const SandboxPolicy* aPolicy,
+ bool aAppFonts) {
+ // This iterates over the fonts in a font set and adds in gfxFontFamily
+ // objects for each family. Individual gfxFontEntry objects for each face
+ // are not created here; the patterns are just stored in the family. When
+ // a family is actually used, it will be populated with gfxFontEntry
+ // records and the patterns moved to those.
+
+ if (NS_WARN_IF(!aFontSet)) {
+ return;
+ }
+
+ FcChar8* lastFamilyName = (FcChar8*)"";
+ RefPtr<gfxFontconfigFontFamily> fontFamily;
+ nsAutoCString familyName;
+ for (int f = 0; f < aFontSet->nfont; f++) {
+ FcPattern* pattern = aFontSet->fonts[f];
+
+ // Skip any fonts that aren't readable for us (e.g. due to restrictive
+ // file ownership/permissions).
+ FcChar8* path;
+ if (FcPatternGetString(pattern, FC_FILE, 0, &path) != FcResultMatch) {
+ continue;
+ }
+ if (access(reinterpret_cast<const char*>(path), F_OK | R_OK) != 0) {
+ continue;
+ }
+
+#if defined(MOZ_SANDBOX) && defined(XP_LINUX)
+ // Skip any fonts that will be blocked by the content-process sandbox
+ // policy.
+ if (aPolicy && !(aPolicy->Lookup(reinterpret_cast<const char*>(path)) &
+ SandboxBroker::Perms::MAY_READ)) {
+ continue;
+ }
+#endif
+
+ AddPatternToFontList(pattern, lastFamilyName, familyName, fontFamily,
+ aAppFonts);
+ }
+}
+
+void gfxFcPlatformFontList::AddPatternToFontList(
+ FcPattern* aFont, FcChar8*& aLastFamilyName, nsACString& aFamilyName,
+ RefPtr<gfxFontconfigFontFamily>& aFontFamily, bool aAppFonts) {
+ // get canonical name
+ uint32_t cIndex = FindCanonicalNameIndex(aFont, FC_FAMILYLANG);
+ FcChar8* canonical = nullptr;
+ FcPatternGetString(aFont, FC_FAMILY, cIndex, &canonical);
+ if (!canonical) {
+ return;
+ }
+
+ // same as the last one? no need to add a new family, skip
+ if (FcStrCmp(canonical, aLastFamilyName) != 0) {
+ aLastFamilyName = canonical;
+
+ // add new family if one doesn't already exist
+ aFamilyName.Truncate();
+ aFamilyName = ToCharPtr(canonical);
+ nsAutoCString keyName(aFamilyName);
+ ToLowerCase(keyName);
+
+ aFontFamily = static_cast<gfxFontconfigFontFamily*>(
+ mFontFamilies
+ .LookupOrInsertWith(keyName,
+ [&] {
+ FontVisibility visibility =
+ aAppFonts
+ ? FontVisibility::Base
+ : GetVisibilityForFamily(keyName);
+ return MakeRefPtr<gfxFontconfigFontFamily>(
+ aFamilyName, visibility);
+ })
+ .get());
+ // Record if the family contains fonts from the app font set
+ // (in which case we won't rely on fontconfig's charmap, due to
+ // bug 1276594).
+ if (aAppFonts) {
+ aFontFamily->SetFamilyContainsAppFonts(true);
+ }
+ }
+
+ // Add pointers to other localized family names. Most fonts
+ // only have a single name, so the first call to GetString
+ // will usually not match
+ FcChar8* otherName;
+ int n = (cIndex == 0 ? 1 : 0);
+ AutoTArray<nsCString, 4> otherFamilyNames;
+ while (FcPatternGetString(aFont, FC_FAMILY, n, &otherName) == FcResultMatch) {
+ otherFamilyNames.AppendElement(nsCString(ToCharPtr(otherName)));
+ n++;
+ if (n == int(cIndex)) {
+ n++; // skip over canonical name
+ }
+ }
+ if (!otherFamilyNames.IsEmpty()) {
+ AddOtherFamilyNames(aFontFamily, otherFamilyNames);
+ }
+
+ const bool singleName = n == 1;
+
+ MOZ_ASSERT(aFontFamily, "font must belong to a font family");
+ aFontFamily->AddFontPattern(aFont, singleName);
+
+ // map the psname, fullname ==> font family for local font lookups
+ nsAutoCString psname, fullname;
+ GetFaceNames(aFont, aFamilyName, psname, fullname);
+ if (!psname.IsEmpty()) {
+ ToLowerCase(psname);
+ mLocalNames.InsertOrUpdate(psname, RefPtr{aFont});
+ }
+ if (!fullname.IsEmpty()) {
+ ToLowerCase(fullname);
+ mLocalNames.WithEntryHandle(fullname, [&](auto&& entry) {
+ if (entry && !singleName) {
+ return;
+ }
+ entry.InsertOrUpdate(RefPtr{aFont});
+ });
+ }
+}
+
+nsresult gfxFcPlatformFontList::InitFontListForPlatform() {
+#ifdef MOZ_BUNDLED_FONTS
+ if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) {
+ ActivateBundledFonts();
+ }
+#endif
+
+ mLocalNames.Clear();
+ mFcSubstituteCache.Clear();
+
+ ClearSystemFontOptions();
+
+ mAlwaysUseFontconfigGenerics = PrefFontListsUseOnlyGenerics();
+ mOtherFamilyNamesInitialized = true;
+
+ mLastConfig = FcConfigGetCurrent();
+
+ if (XRE_IsContentProcess()) {
+ // Content process: use the font list passed from the chrome process,
+ // because we can't rely on fontconfig in the presence of sandboxing;
+ // it may report fonts that we can't actually access.
+
+ FcChar8* lastFamilyName = (FcChar8*)"";
+ RefPtr<gfxFontconfigFontFamily> fontFamily;
+ nsAutoCString familyName;
+
+ // Get font list that was passed during XPCOM startup
+ // or in an UpdateFontList message.
+ auto& fontList = dom::ContentChild::GetSingleton()->SystemFontList();
+
+#ifdef MOZ_WIDGET_GTK
+ UpdateSystemFontOptionsFromIpc(fontList.options());
+#endif
+
+ // For fontconfig versions between 2.10.94 and 2.11.1 inclusive,
+ // we need to escape any leading space in the charset element,
+ // otherwise FcNameParse will fail. :(
+ //
+ // The bug was introduced on 2013-05-24 by
+ // https://cgit.freedesktop.org/fontconfig/commit/?id=cd9b1033a68816a7acfbba1718ba0aa5888f6ec7
+ // "Bug 64906 - FcNameParse() should ignore leading whitespace in
+ // parameters"
+ // because ignoring a leading space in the encoded value of charset
+ // causes erroneous decoding of the whole element.
+ // This first shipped in version 2.10.94, and was eventually fixed as
+ // a side-effect of switching to the "human-readable" representation of
+ // charsets on 2014-07-03 in
+ // https://cgit.freedesktop.org/fontconfig/commit/?id=e708e97c351d3bc9f7030ef22ac2f007d5114730
+ // "Change charset parse/unparse format to be human readable"
+ // (with a followup fix next day) which means a leading space is no
+ // longer significant. This fix landed after 2.11.1 had been shipped,
+ // so the first version tag without the bug is 2.11.91.
+ int fcVersion = FcGetVersion();
+ bool fcCharsetParseBug = fcVersion >= 21094 && fcVersion <= 21101;
+
+ for (FontPatternListEntry& fpe : fontList.entries()) {
+ nsCString& patternStr = fpe.pattern();
+ if (fcCharsetParseBug) {
+ int32_t index = patternStr.Find(":charset= ");
+ if (index != kNotFound) {
+ // insert backslash after the =, before the space
+ patternStr.Insert('\\', index + 9);
+ }
+ }
+ FcPattern* pattern = FcNameParse((const FcChar8*)patternStr.get());
+ AddPatternToFontList(pattern, lastFamilyName, familyName, fontFamily,
+ fpe.appFontFamily());
+ FcPatternDestroy(pattern);
+ }
+
+ LOG_FONTLIST(
+ ("got font list from chrome process: "
+ "%u faces in %u families",
+ (unsigned)fontList.entries().Length(), mFontFamilies.Count()));
+
+ fontList.entries().Clear();
+ return NS_OK;
+ }
+
+ UpdateSystemFontOptions();
+
+ UniquePtr<SandboxPolicy> policy;
+
+#if defined(MOZ_SANDBOX) && defined(XP_LINUX)
+ // If read sandboxing is enabled, create a temporary SandboxPolicy to
+ // check font paths; use a fake PID to avoid picking up any PID-specific
+ // rules by accident.
+ SandboxBrokerPolicyFactory policyFactory;
+ if (GetEffectiveContentSandboxLevel() > 2 &&
+ !PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX")) {
+ policy = policyFactory.GetContentPolicy(-1, false);
+ }
+#endif
+
+#ifdef MOZ_BUNDLED_FONTS
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1745715:
+ // It's important to do this *before* processing the standard system fonts,
+ // so that if a family is present in both font sets, we'll treat it as app-
+ // bundled and therefore always visible.
+ if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) {
+ FcFontSet* appFonts = FcConfigGetFonts(nullptr, FcSetApplication);
+ AddFontSetFamilies(appFonts, policy.get(), /* aAppFonts = */ true);
+ }
+#endif
+
+ // iterate over available fonts
+ FcFontSet* systemFonts = FcConfigGetFonts(nullptr, FcSetSystem);
+ AddFontSetFamilies(systemFonts, policy.get(), /* aAppFonts = */ false);
+
+ return NS_OK;
+}
+
+void gfxFcPlatformFontList::ReadSystemFontList(dom::SystemFontList* retValue) {
+ AutoLock lock(mLock);
+
+ // Fontconfig versions below 2.9 drop the FC_FILE element in FcNameUnparse
+ // (see https://bugs.freedesktop.org/show_bug.cgi?id=26718), so when using
+ // an older version, we manually append it to the unparsed pattern.
+#ifdef MOZ_WIDGET_GTK
+ SystemFontOptionsToIpc(retValue->options());
+#endif
+
+ if (FcGetVersion() < 20900) {
+ for (const auto& entry : mFontFamilies) {
+ auto* family = static_cast<gfxFontconfigFontFamily*>(entry.GetWeak());
+ family->AddFacesToFontList([&](FcPattern* aPat, bool aAppFonts) {
+ char* s = (char*)FcNameUnparse(aPat);
+ nsDependentCString patternStr(s);
+ char* file = nullptr;
+ if (FcResultMatch ==
+ FcPatternGetString(aPat, FC_FILE, 0, (FcChar8**)&file)) {
+ patternStr.Append(":file=");
+ patternStr.Append(file);
+ }
+ retValue->entries().AppendElement(
+ FontPatternListEntry(patternStr, aAppFonts));
+ free(s);
+ });
+ }
+ } else {
+ for (const auto& entry : mFontFamilies) {
+ auto* family = static_cast<gfxFontconfigFontFamily*>(entry.GetWeak());
+ family->AddFacesToFontList([&](FcPattern* aPat, bool aAppFonts) {
+ char* s = (char*)FcNameUnparse(aPat);
+ nsDependentCString patternStr(s);
+ retValue->entries().AppendElement(
+ FontPatternListEntry(patternStr, aAppFonts));
+ free(s);
+ });
+ }
+ }
+}
+
+// Per family array of faces.
+class FacesData {
+ using FaceInitArray = AutoTArray<fontlist::Face::InitData, 8>;
+
+ FaceInitArray mFaces;
+
+ // Number of faces that have a single name. Faces that have multiple names are
+ // sorted last.
+ uint32_t mUniqueNameFaceCount = 0;
+
+ public:
+ void Add(fontlist::Face::InitData&& aData, bool aSingleName) {
+ if (aSingleName) {
+ mFaces.InsertElementAt(mUniqueNameFaceCount++, std::move(aData));
+ } else {
+ mFaces.AppendElement(std::move(aData));
+ }
+ }
+
+ const FaceInitArray& Get() const { return mFaces; }
+};
+
+void gfxFcPlatformFontList::InitSharedFontListForPlatform() {
+ mLocalNames.Clear();
+ mFcSubstituteCache.Clear();
+
+ mAlwaysUseFontconfigGenerics = PrefFontListsUseOnlyGenerics();
+ mOtherFamilyNamesInitialized = true;
+
+ mLastConfig = FcConfigGetCurrent();
+
+ if (!XRE_IsParentProcess()) {
+#ifdef MOZ_WIDGET_GTK
+ auto& fontList = dom::ContentChild::GetSingleton()->SystemFontList();
+ UpdateSystemFontOptionsFromIpc(fontList.options());
+#endif
+ // Content processes will access the shared-memory data created by the
+ // parent, so they do not need to query fontconfig for the available
+ // fonts themselves.
+ return;
+ }
+
+#ifdef MOZ_WIDGET_GTK
+ UpdateSystemFontOptions();
+#endif
+
+#ifdef MOZ_BUNDLED_FONTS
+ if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) {
+ TimeStamp start = TimeStamp::Now();
+ ActivateBundledFonts();
+ TimeStamp end = TimeStamp::Now();
+ Telemetry::Accumulate(Telemetry::FONTLIST_BUNDLEDFONTS_ACTIVATE,
+ (end - start).ToMilliseconds());
+ }
+#endif
+
+ UniquePtr<SandboxPolicy> policy;
+
+#if defined(MOZ_CONTENT_SANDBOX) && defined(XP_LINUX)
+ // If read sandboxing is enabled, create a temporary SandboxPolicy to
+ // check font paths; use a fake PID to avoid picking up any PID-specific
+ // rules by accident.
+ SandboxBrokerPolicyFactory policyFactory;
+ if (GetEffectiveContentSandboxLevel() > 2 &&
+ !PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX")) {
+ policy = policyFactory.GetContentPolicy(-1, false);
+ }
+#endif
+
+ nsTArray<fontlist::Family::InitData> families;
+
+ nsClassHashtable<nsCStringHashKey, FacesData> faces;
+
+ // Do we need to work around the fontconfig FcNameParse/FcNameUnparse bug
+ // (present in versions between 2.10.94 and 2.11.1 inclusive)? See comment
+ // in InitFontListForPlatform for details.
+ int fcVersion = FcGetVersion();
+ bool fcCharsetParseBug = fcVersion >= 21094 && fcVersion <= 21101;
+
+ auto addPattern = [this, fcCharsetParseBug, &families, &faces](
+ FcPattern* aPattern, FcChar8*& aLastFamilyName,
+ nsCString& aFamilyName, bool aAppFont) -> void {
+ // get canonical name
+ uint32_t cIndex = FindCanonicalNameIndex(aPattern, FC_FAMILYLANG);
+ FcChar8* canonical = nullptr;
+ FcPatternGetString(aPattern, FC_FAMILY, cIndex, &canonical);
+ if (!canonical) {
+ return;
+ }
+
+ nsAutoCString keyName;
+ keyName = ToCharPtr(canonical);
+ ToLowerCase(keyName);
+
+ aLastFamilyName = canonical;
+ aFamilyName = ToCharPtr(canonical);
+
+ // Same canonical family name as the last one? Definitely no need to add a
+ // new family record.
+ auto* faceList =
+ faces
+ .LookupOrInsertWith(
+ keyName,
+ [&] {
+ FontVisibility visibility =
+ aAppFont ? FontVisibility::Base
+ : GetVisibilityForFamily(keyName);
+ families.AppendElement(fontlist::Family::InitData(
+ keyName, aFamilyName, fontlist::Family::kNoIndex,
+ visibility,
+ /*bundled*/ aAppFont, /*badUnderline*/ false));
+ return MakeUnique<FacesData>();
+ })
+ .get();
+
+ char* s = (char*)FcNameUnparse(aPattern);
+ nsAutoCString descriptor(s);
+ free(s);
+
+ if (fcCharsetParseBug) {
+ // Escape any leading space in charset to work around FcNameParse bug.
+ int32_t index = descriptor.Find(":charset= ");
+ if (index != kNotFound) {
+ // insert backslash after the =, before the space
+ descriptor.Insert('\\', index + 9);
+ }
+ }
+
+ WeightRange weight(FontWeight::NORMAL);
+ StretchRange stretch(FontStretch::NORMAL);
+ SlantStyleRange style(FontSlantStyle::NORMAL);
+ uint16_t size;
+ GetFontProperties(aPattern, &weight, &stretch, &style, &size);
+
+ auto initData = fontlist::Face::InitData{descriptor, 0, size, false,
+ weight, stretch, style};
+
+ // Add entries for any other localized family names. (Most fonts only have
+ // a single family name, so the first call to GetString will usually fail).
+ FcChar8* otherName;
+ int n = (cIndex == 0 ? 1 : 0);
+ while (FcPatternGetString(aPattern, FC_FAMILY, n, &otherName) ==
+ FcResultMatch) {
+ nsAutoCString otherFamilyName(ToCharPtr(otherName));
+ keyName = otherFamilyName;
+ ToLowerCase(keyName);
+
+ faces
+ .LookupOrInsertWith(
+ keyName,
+ [&] {
+ FontVisibility visibility =
+ aAppFont ? FontVisibility::Base
+ : GetVisibilityForFamily(keyName);
+ families.AppendElement(fontlist::Family::InitData(
+ keyName, otherFamilyName, fontlist::Family::kNoIndex,
+ visibility,
+ /*bundled*/ aAppFont, /*badUnderline*/ false));
+
+ return MakeUnique<FacesData>();
+ })
+ .get()
+ ->Add(fontlist::Face::InitData(initData), /* singleName = */ false);
+
+ n++;
+ if (n == int(cIndex)) {
+ n++; // skip over canonical name
+ }
+ }
+
+ const bool singleName = n == 1;
+ faceList->Add(std::move(initData), singleName);
+
+ // map the psname, fullname ==> font family for local font lookups
+ nsAutoCString psname, fullname;
+ GetFaceNames(aPattern, aFamilyName, psname, fullname);
+ if (!psname.IsEmpty()) {
+ ToLowerCase(psname);
+ mLocalNameTable.InsertOrUpdate(
+ psname, fontlist::LocalFaceRec::InitData(keyName, descriptor));
+ }
+ if (!fullname.IsEmpty()) {
+ ToLowerCase(fullname);
+ if (fullname != psname) {
+ mLocalNameTable.WithEntryHandle(fullname, [&](auto&& entry) {
+ if (entry && !singleName) {
+ // We only override an existing entry if this is the only way to
+ // name this family. This prevents dubious aliases from clobbering
+ // the local name table.
+ return;
+ }
+ entry.InsertOrUpdate(
+ fontlist::LocalFaceRec::InitData(keyName, descriptor));
+ });
+ }
+ }
+ };
+
+ auto addFontSetFamilies = [&addPattern](FcFontSet* aFontSet,
+ SandboxPolicy* aPolicy,
+ bool aAppFonts) -> void {
+ if (NS_WARN_IF(!aFontSet)) {
+ return;
+ }
+ FcChar8* lastFamilyName = (FcChar8*)"";
+ RefPtr<gfxFontconfigFontFamily> fontFamily;
+ nsAutoCString familyName;
+ for (int f = 0; f < aFontSet->nfont; f++) {
+ FcPattern* pattern = aFontSet->fonts[f];
+
+ // Skip any fonts that aren't readable for us (e.g. due to restrictive
+ // file ownership/permissions).
+ FcChar8* path;
+ if (FcPatternGetString(pattern, FC_FILE, 0, &path) != FcResultMatch) {
+ continue;
+ }
+ if (access(reinterpret_cast<const char*>(path), F_OK | R_OK) != 0) {
+ continue;
+ }
+
+#if defined(MOZ_CONTENT_SANDBOX) && defined(XP_LINUX)
+ // Skip any fonts that will be blocked by the content-process sandbox
+ // policy.
+ if (aPolicy && !(aPolicy->Lookup(reinterpret_cast<const char*>(path)) &
+ SandboxBroker::Perms::MAY_READ)) {
+ continue;
+ }
+#endif
+
+ // Clone the pattern, because we can't operate on the one belonging to
+ // the FcFontSet directly.
+ FcPattern* clone = FcPatternDuplicate(pattern);
+
+ // Pick up any configuration options applicable to the font (e.g. custom
+ // fontfeatures settings).
+ if (!FcConfigSubstitute(nullptr, clone, FcMatchFont)) {
+ // Out of memory?! We're probably doomed, but just skip this font.
+ FcPatternDestroy(clone);
+ continue;
+ }
+ // But ignore hinting settings from FcConfigSubstitute, as we don't want
+ // to bake them into the pattern in the font list.
+ FcPatternDel(clone, FC_HINT_STYLE);
+ FcPatternDel(clone, FC_HINTING);
+
+ // If this is a TrueType or OpenType font, discard the FC_CHARSET object
+ // (which may be very large), because we'll read the 'cmap' directly.
+ // This substantially reduces the pressure on shared memory (bug 1664151)
+ // due to the large font descriptors (serialized patterns).
+ FcChar8* fontFormat;
+ if (FcPatternGetString(clone, FC_FONTFORMAT, 0, &fontFormat) ==
+ FcResultMatch &&
+ (!FcStrCmp(fontFormat, (const FcChar8*)"TrueType") ||
+ !FcStrCmp(fontFormat, (const FcChar8*)"CFF"))) {
+ FcPatternDel(clone, FC_CHARSET);
+ addPattern(clone, lastFamilyName, familyName, aAppFonts);
+ } else {
+ addPattern(clone, lastFamilyName, familyName, aAppFonts);
+ }
+
+ FcPatternDestroy(clone);
+ }
+ };
+
+#ifdef MOZ_BUNDLED_FONTS
+ // Add bundled fonts before system fonts, to set correct visibility status
+ // for any families that appear in both.
+ if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) {
+ FcFontSet* appFonts = FcConfigGetFonts(nullptr, FcSetApplication);
+ addFontSetFamilies(appFonts, policy.get(), /* aAppFonts = */ true);
+ }
+#endif
+
+ // iterate over available fonts
+ FcFontSet* systemFonts = FcConfigGetFonts(nullptr, FcSetSystem);
+ addFontSetFamilies(systemFonts, policy.get(), /* aAppFonts = */ false);
+
+ mozilla::fontlist::FontList* list = SharedFontList();
+ list->SetFamilyNames(families);
+
+ for (uint32_t i = 0; i < families.Length(); i++) {
+ list->Families()[i].AddFaces(list, faces.Get(families[i].mKey)->Get());
+ }
+}
+
+gfxFcPlatformFontList::DistroID gfxFcPlatformFontList::GetDistroID() const {
+ // Helper called to initialize sResult the first time this is used.
+ auto getDistroID = []() {
+ DistroID result = DistroID::Unknown;
+ FILE* fp = fopen("/etc/os-release", "r");
+ if (fp) {
+ char buf[512];
+ while (fgets(buf, sizeof(buf), fp)) {
+ if (strncmp(buf, "ID=", 3) == 0) {
+ if (strncmp(buf + 3, "ubuntu", 6) == 0) {
+ result = DistroID::Ubuntu;
+ } else if (strncmp(buf + 3, "fedora", 6) == 0) {
+ result = DistroID::Fedora;
+ }
+ break;
+ }
+ }
+ fclose(fp);
+ }
+ return result;
+ };
+ static DistroID sResult = getDistroID();
+ return sResult;
+}
+
+FontVisibility gfxFcPlatformFontList::GetVisibilityForFamily(
+ const nsACString& aName) const {
+ switch (GetDistroID()) {
+ case DistroID::Ubuntu:
+ if (FamilyInList(aName, kBaseFonts_Ubuntu_20_04)) {
+ return FontVisibility::Base;
+ }
+ if (FamilyInList(aName, kLangFonts_Ubuntu_20_04)) {
+ return FontVisibility::LangPack;
+ }
+ return FontVisibility::User;
+ case DistroID::Fedora:
+ if (FamilyInList(aName, kBaseFonts_Fedora_32)) {
+ return FontVisibility::Base;
+ }
+ return FontVisibility::User;
+ default:
+ // We don't know how to categorize fonts on this system
+ return FontVisibility::Unknown;
+ }
+}
+
+gfxFontEntry* gfxFcPlatformFontList::CreateFontEntry(
+ fontlist::Face* aFace, const fontlist::Family* aFamily) {
+ nsAutoCString desc(aFace->mDescriptor.AsString(SharedFontList()));
+ FcPattern* pattern = FcNameParse((const FcChar8*)desc.get());
+ auto* fe = new gfxFontconfigFontEntry(desc, pattern, true);
+ FcPatternDestroy(pattern);
+ fe->InitializeFrom(aFace, aFamily);
+ return fe;
+}
+
+// For displaying the fontlist in UI, use explicit call to FcFontList. Using
+// FcFontList results in the list containing the localized names as dictated
+// by system defaults.
+static void GetSystemFontList(nsTArray<nsString>& aListOfFonts,
+ nsAtom* aLangGroup) {
+ aListOfFonts.Clear();
+
+ RefPtr<FcPattern> pat = dont_AddRef(FcPatternCreate());
+ if (!pat) {
+ return;
+ }
+
+ UniquePtr<FcObjectSet> os(FcObjectSetBuild(FC_FAMILY, nullptr));
+ if (!os) {
+ return;
+ }
+
+ // add the lang to the pattern
+ nsAutoCString fcLang;
+ gfxFcPlatformFontList* pfl = gfxFcPlatformFontList::PlatformFontList();
+ pfl->GetSampleLangForGroup(aLangGroup, fcLang,
+ /*aForFontEnumerationThread*/ true);
+ if (!fcLang.IsEmpty()) {
+ FcPatternAddString(pat, FC_LANG, ToFcChar8Ptr(fcLang.get()));
+ }
+
+ UniquePtr<FcFontSet> fs(FcFontList(nullptr, pat, os.get()));
+ if (!fs) {
+ return;
+ }
+
+ for (int i = 0; i < fs->nfont; i++) {
+ char* family;
+
+ if (FcPatternGetString(fs->fonts[i], FC_FAMILY, 0, (FcChar8**)&family) !=
+ FcResultMatch) {
+ continue;
+ }
+
+ // Remove duplicates...
+ nsAutoString strFamily;
+ AppendUTF8toUTF16(MakeStringSpan(family), strFamily);
+ if (aListOfFonts.Contains(strFamily)) {
+ continue;
+ }
+
+ aListOfFonts.AppendElement(strFamily);
+ }
+
+ aListOfFonts.Sort();
+}
+
+void gfxFcPlatformFontList::GetFontList(nsAtom* aLangGroup,
+ const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts) {
+ // Get the list of font family names using fontconfig
+ GetSystemFontList(aListOfFonts, aLangGroup);
+
+ // Under Linux, the generics "serif", "sans-serif" and "monospace"
+ // are included in the pref fontlist. These map to whatever fontconfig
+ // decides they should be for a given language, rather than one of the
+ // fonts listed in the prefs font lists (e.g. font.name.*, font.name-list.*)
+ bool serif = false, sansSerif = false, monospace = false;
+ if (aGenericFamily.IsEmpty())
+ serif = sansSerif = monospace = true;
+ else if (aGenericFamily.LowerCaseEqualsLiteral("serif"))
+ serif = true;
+ else if (aGenericFamily.LowerCaseEqualsLiteral("sans-serif"))
+ sansSerif = true;
+ else if (aGenericFamily.LowerCaseEqualsLiteral("monospace"))
+ monospace = true;
+ else if (aGenericFamily.LowerCaseEqualsLiteral("cursive") ||
+ aGenericFamily.LowerCaseEqualsLiteral("fantasy"))
+ serif = sansSerif = true;
+ else
+ MOZ_ASSERT_UNREACHABLE("unexpected CSS generic font family");
+
+ // The first in the list becomes the default in
+ // FontBuilder.readFontSelection() if the preference-selected font is not
+ // available, so put system configured defaults first.
+ if (monospace) aListOfFonts.InsertElementAt(0, u"monospace"_ns);
+ if (sansSerif) aListOfFonts.InsertElementAt(0, u"sans-serif"_ns);
+ if (serif) aListOfFonts.InsertElementAt(0, u"serif"_ns);
+}
+
+FontFamily gfxFcPlatformFontList::GetDefaultFontForPlatform(
+ nsPresContext* aPresContext, const gfxFontStyle* aStyle,
+ nsAtom* aLanguage) {
+ // Get the default font by using a fake name to retrieve the first
+ // scalable font that fontconfig suggests for the given language.
+ PrefFontList* prefFonts =
+ FindGenericFamilies(aPresContext, "-moz-default"_ns,
+ aLanguage ? aLanguage : nsGkAtoms::x_western);
+ NS_ASSERTION(prefFonts, "null list of generic fonts");
+ if (prefFonts && !prefFonts->IsEmpty()) {
+ return (*prefFonts)[0];
+ }
+ return FontFamily();
+}
+
+gfxFontEntry* gfxFcPlatformFontList::LookupLocalFont(
+ nsPresContext* aPresContext, const nsACString& aFontName,
+ WeightRange aWeightForEntry, StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry) {
+ AutoLock lock(mLock);
+
+ nsAutoCString keyName(aFontName);
+ ToLowerCase(keyName);
+
+ if (SharedFontList()) {
+ return LookupInSharedFaceNameList(aPresContext, aFontName, aWeightForEntry,
+ aStretchForEntry, aStyleForEntry);
+ }
+
+ // if name is not in the global list, done
+ const auto fontPattern = mLocalNames.Lookup(keyName);
+ if (!fontPattern) {
+ return nullptr;
+ }
+
+ return new gfxFontconfigFontEntry(aFontName, *fontPattern, aWeightForEntry,
+ aStretchForEntry, aStyleForEntry);
+}
+
+gfxFontEntry* gfxFcPlatformFontList::MakePlatformFont(
+ const nsACString& aFontName, WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry, SlantStyleRange aStyleForEntry,
+ const uint8_t* aFontData, uint32_t aLength) {
+ RefPtr<FTUserFontData> ufd = new FTUserFontData(aFontData, aLength);
+ RefPtr<SharedFTFace> face = ufd->CloneFace();
+ if (!face) {
+ return nullptr;
+ }
+ return new gfxFontconfigFontEntry(aFontName, aWeightForEntry,
+ aStretchForEntry, aStyleForEntry,
+ std::move(face));
+}
+
+static bool UseCustomFontconfigLookupsForLocale(const Locale& aLocale) {
+ return aLocale.Script().EqualTo("Hans") || aLocale.Script().EqualTo("Hant") ||
+ aLocale.Script().EqualTo("Jpan") || aLocale.Script().EqualTo("Kore") ||
+ aLocale.Script().EqualTo("Arab");
+}
+
+bool gfxFcPlatformFontList::FindAndAddFamiliesLocked(
+ nsPresContext* aPresContext, StyleGenericFontFamily aGeneric,
+ const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput,
+ FindFamiliesFlags aFlags, gfxFontStyle* aStyle, nsAtom* aLanguage,
+ gfxFloat aDevToCssSize) {
+ nsAutoCString familyName(aFamily);
+ ToLowerCase(familyName);
+
+ if (!(aFlags & FindFamiliesFlags::eQuotedFamilyName)) {
+ // deprecated generic names are explicitly converted to standard generics
+ bool isDeprecatedGeneric = false;
+ if (familyName.EqualsLiteral("sans") ||
+ familyName.EqualsLiteral("sans serif")) {
+ familyName.AssignLiteral("sans-serif");
+ isDeprecatedGeneric = true;
+ } else if (familyName.EqualsLiteral("mono")) {
+ familyName.AssignLiteral("monospace");
+ isDeprecatedGeneric = true;
+ }
+
+ // fontconfig generics? use fontconfig to determine the family for lang
+ if (isDeprecatedGeneric ||
+ mozilla::StyleSingleFontFamily::Parse(familyName).IsGeneric()) {
+ PrefFontList* prefFonts =
+ FindGenericFamilies(aPresContext, familyName, aLanguage);
+ if (prefFonts && !prefFonts->IsEmpty()) {
+ aOutput->AppendElements(*prefFonts);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ // fontconfig allows conditional substitutions in such a way that it's
+ // difficult to distinguish an explicit substitution from other suggested
+ // choices. To sniff out explicit substitutions, compare the substitutions
+ // for "font, -moz-sentinel" to "-moz-sentinel" to sniff out the
+ // substitutions
+ //
+ // Example:
+ //
+ // serif ==> DejaVu Serif, ...
+ // Helvetica, serif ==> Helvetica, TeX Gyre Heros, Nimbus Sans L, DejaVu
+ // Serif
+ //
+ // In this case fontconfig is including Tex Gyre Heros and
+ // Nimbus Sans L as alternatives for Helvetica.
+
+ // Because the FcConfigSubstitute call is quite expensive, we cache the
+ // actual font families found via this process.
+ nsAutoCString cacheKey;
+
+ // For languages that use CJK or Arabic script, we include the language as
+ // part of the cache key because fontconfig may have lang-specific rules that
+ // specify different substitutions. (In theory, this could apply to *any*
+ // language, but it's highly unlikely to matter for non-CJK/Arabic scripts,
+ // and it gets really expensive to do separate lookups for 300+ distinct lang
+ // tags e.g. on wikipedia.org, when they all end up mapping to the same font
+ // list.)
+ // We remember the most recently checked aLanguage atom so that when the same
+ // language is used in many successive calls, we can avoid repeating the
+ // locale code processing every time.
+ if (aLanguage != mPrevLanguage) {
+ GetSampleLangForGroup(aLanguage, mSampleLang);
+ ToLowerCase(mSampleLang);
+ Locale locale;
+ mUseCustomLookups = LocaleParser::TryParse(mSampleLang, locale).isOk() &&
+ locale.AddLikelySubtags().isOk() &&
+ UseCustomFontconfigLookupsForLocale(locale);
+ mPrevLanguage = aLanguage;
+ }
+ if (mUseCustomLookups) {
+ cacheKey = mSampleLang;
+ cacheKey.Append(':');
+ }
+
+ cacheKey.Append(familyName);
+ auto vis =
+ aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User;
+ cacheKey.Append(':');
+ cacheKey.AppendInt(int(vis));
+ if (const auto& cached = mFcSubstituteCache.Lookup(cacheKey)) {
+ if (cached->IsEmpty()) {
+ return false;
+ }
+ aOutput->AppendElements(*cached);
+ return true;
+ }
+
+ // It wasn't in the cache, so we need to ask fontconfig...
+ const FcChar8* kSentinelName = ToFcChar8Ptr("-moz-sentinel");
+ const FcChar8* terminator = nullptr;
+ RefPtr<FcPattern> sentinelSubst = dont_AddRef(FcPatternCreate());
+ FcPatternAddString(sentinelSubst, FC_FAMILY, kSentinelName);
+ if (!mSampleLang.IsEmpty()) {
+ FcPatternAddString(sentinelSubst, FC_LANG, ToFcChar8Ptr(mSampleLang.get()));
+ }
+ FcConfigSubstitute(nullptr, sentinelSubst, FcMatchPattern);
+
+ // If the sentinel name is still present, we'll use that as the terminator
+ // for the family names we collect; this means that if fontconfig prepends
+ // additional family names (e.g. an emoji font, or lang-specific preferred
+ // font) to all patterns, it won't simply mask all actual requested names.
+ // If the sentinel has been deleted/replaced altogether, then we'll take
+ // the first substitute name as the new terminator.
+ FcChar8* substName;
+ for (int i = 0; FcPatternGetString(sentinelSubst, FC_FAMILY, i, &substName) ==
+ FcResultMatch;
+ i++) {
+ if (FcStrCmp(substName, kSentinelName) == 0) {
+ terminator = kSentinelName;
+ break;
+ }
+ if (!terminator) {
+ terminator = substName;
+ }
+ }
+
+ // substitutions for font, -moz-sentinel pattern
+ RefPtr<FcPattern> fontWithSentinel = dont_AddRef(FcPatternCreate());
+ FcPatternAddString(fontWithSentinel, FC_FAMILY,
+ ToFcChar8Ptr(familyName.get()));
+ FcPatternAddString(fontWithSentinel, FC_FAMILY, kSentinelName);
+ if (!mSampleLang.IsEmpty()) {
+ FcPatternAddString(sentinelSubst, FC_LANG, ToFcChar8Ptr(mSampleLang.get()));
+ }
+ FcConfigSubstitute(nullptr, fontWithSentinel, FcMatchPattern);
+
+ // Add all font family matches until reaching the terminator.
+ AutoTArray<FamilyAndGeneric, 10> cachedFamilies;
+ for (int i = 0; FcPatternGetString(fontWithSentinel, FC_FAMILY, i,
+ &substName) == FcResultMatch;
+ i++) {
+ if (terminator && FcStrCmp(substName, terminator) == 0) {
+ break;
+ }
+ gfxPlatformFontList::FindAndAddFamiliesLocked(
+ aPresContext, aGeneric, nsDependentCString(ToCharPtr(substName)),
+ &cachedFamilies, aFlags, aStyle, aLanguage);
+ }
+
+ const auto& insertedCachedFamilies =
+ mFcSubstituteCache.InsertOrUpdate(cacheKey, std::move(cachedFamilies));
+
+ if (insertedCachedFamilies.IsEmpty()) {
+ return false;
+ }
+ aOutput->AppendElements(insertedCachedFamilies);
+ return true;
+}
+
+bool gfxFcPlatformFontList::GetStandardFamilyName(const nsCString& aFontName,
+ nsACString& aFamilyName) {
+ aFamilyName.Truncate();
+
+ // The fontconfig list of fonts includes generic family names in the
+ // font list. For these, just use the generic name.
+ if (aFontName.EqualsLiteral("serif") ||
+ aFontName.EqualsLiteral("sans-serif") ||
+ aFontName.EqualsLiteral("monospace")) {
+ aFamilyName.Assign(aFontName);
+ return true;
+ }
+
+ RefPtr<FcPattern> pat = dont_AddRef(FcPatternCreate());
+ if (!pat) {
+ return true;
+ }
+
+ UniquePtr<FcObjectSet> os(FcObjectSetBuild(FC_FAMILY, nullptr));
+ if (!os) {
+ return true;
+ }
+
+ // add the family name to the pattern
+ FcPatternAddString(pat, FC_FAMILY, ToFcChar8Ptr(aFontName.get()));
+
+ UniquePtr<FcFontSet> givenFS(FcFontList(nullptr, pat, os.get()));
+ if (!givenFS) {
+ return true;
+ }
+
+ // See if there is a font face with first family equal to the given family
+ // (needs to be in sync with names coming from GetFontList())
+ nsTArray<nsCString> candidates;
+ for (int i = 0; i < givenFS->nfont; i++) {
+ char* firstFamily;
+
+ if (FcPatternGetString(givenFS->fonts[i], FC_FAMILY, 0,
+ (FcChar8**)&firstFamily) != FcResultMatch) {
+ continue;
+ }
+
+ nsDependentCString first(firstFamily);
+ if (!candidates.Contains(first)) {
+ candidates.AppendElement(first);
+
+ if (aFontName.Equals(first)) {
+ aFamilyName.Assign(aFontName);
+ return true;
+ }
+ }
+ }
+
+ // Because fontconfig conflates different family name types, need to
+ // double check that the candidate name is not simply a different
+ // name type. For example, if a font with nameID=16 "Minion Pro" and
+ // nameID=21 "Minion Pro Caption" exists, calling FcFontList with
+ // family="Minion Pro" will return a set of patterns some of which
+ // will have a first family of "Minion Pro Caption". Ignore these
+ // patterns and use the first candidate that maps to a font set with
+ // the same number of faces and an identical set of patterns.
+ for (uint32_t j = 0; j < candidates.Length(); ++j) {
+ FcPatternDel(pat, FC_FAMILY);
+ FcPatternAddString(pat, FC_FAMILY, (FcChar8*)candidates[j].get());
+
+ UniquePtr<FcFontSet> candidateFS(FcFontList(nullptr, pat, os.get()));
+ if (!candidateFS) {
+ return true;
+ }
+
+ if (candidateFS->nfont != givenFS->nfont) {
+ continue;
+ }
+
+ bool equal = true;
+ for (int i = 0; i < givenFS->nfont; ++i) {
+ if (!FcPatternEqual(candidateFS->fonts[i], givenFS->fonts[i])) {
+ equal = false;
+ break;
+ }
+ }
+ if (equal) {
+ aFamilyName = candidates[j];
+ return true;
+ }
+ }
+
+ // didn't find localized name, leave family name blank
+ return true;
+}
+
+void gfxFcPlatformFontList::AddGenericFonts(
+ nsPresContext* aPresContext, StyleGenericFontFamily aGenericType,
+ nsAtom* aLanguage, nsTArray<FamilyAndGeneric>& aFamilyList) {
+ const char* generic = GetGenericName(aGenericType);
+ NS_ASSERTION(generic, "weird generic font type");
+ if (!generic) {
+ return;
+ }
+
+ // By default, most font prefs on Linux map to "use fontconfig"
+ // keywords. So only need to explicitly lookup font pref if
+ // non-default settings exist, or if we are the system-ui font, which we deal
+ // with in the base class.
+ const bool isSystemUi = aGenericType == StyleGenericFontFamily::SystemUi;
+ bool usePrefFontList = isSystemUi;
+
+ nsAutoCString genericToLookup(generic);
+ if ((!mAlwaysUseFontconfigGenerics && aLanguage) ||
+ aLanguage == nsGkAtoms::x_math) {
+ nsAtom* langGroup = GetLangGroup(aLanguage);
+ nsAutoCString fontlistValue;
+ mFontPrefs->LookupName(PrefName(generic, langGroup), fontlistValue);
+ if (fontlistValue.IsEmpty()) {
+ // The font name list may have two or more family names as comma
+ // separated list. In such case, not matching with generic font
+ // name is fine because if the list prefers specific font, we
+ // should try to use the pref with complicated path.
+ mFontPrefs->LookupNameList(PrefName(generic, langGroup), fontlistValue);
+ }
+ if (!fontlistValue.IsEmpty()) {
+ if (!fontlistValue.EqualsLiteral("serif") &&
+ !fontlistValue.EqualsLiteral("sans-serif") &&
+ !fontlistValue.EqualsLiteral("monospace")) {
+ usePrefFontList = true;
+ } else {
+ // serif, sans-serif or monospace was specified
+ genericToLookup = fontlistValue;
+ }
+ }
+ }
+
+ // when pref fonts exist, use standard pref font lookup
+ if (usePrefFontList) {
+ gfxPlatformFontList::AddGenericFonts(aPresContext, aGenericType, aLanguage,
+ aFamilyList);
+ if (!isSystemUi) {
+ return;
+ }
+ }
+
+ AutoLock lock(mLock);
+ PrefFontList* prefFonts =
+ FindGenericFamilies(aPresContext, genericToLookup, aLanguage);
+ NS_ASSERTION(prefFonts, "null generic font list");
+ aFamilyList.SetCapacity(aFamilyList.Length() + prefFonts->Length());
+ for (auto& f : *prefFonts) {
+ aFamilyList.AppendElement(FamilyAndGeneric(f, aGenericType));
+ }
+}
+
+void gfxFcPlatformFontList::ClearLangGroupPrefFontsLocked() {
+ ClearGenericMappingsLocked();
+ gfxPlatformFontList::ClearLangGroupPrefFontsLocked();
+ mAlwaysUseFontconfigGenerics = PrefFontListsUseOnlyGenerics();
+}
+
+gfxPlatformFontList::PrefFontList* gfxFcPlatformFontList::FindGenericFamilies(
+ nsPresContext* aPresContext, const nsCString& aGeneric, nsAtom* aLanguage) {
+ // set up name
+ nsAutoCString fcLang;
+ GetSampleLangForGroup(aLanguage, fcLang);
+ ToLowerCase(fcLang);
+
+ nsAutoCString cacheKey(aGeneric);
+ if (fcLang.Length() > 0) {
+ cacheKey.Append('-');
+ // If the script is CJK or Arabic, we cache by lang so that different fonts
+ // various locales can be supported; but otherwise, we cache by script
+ // subtag, to avoid a proliferation of entries for Western & similar
+ // languages.
+ // In theory, this means we could fail to respect custom fontconfig rules
+ // for individual (non-CJK/Arab) languages that share the same script, but
+ // such setups are probably vanishingly rare.
+ Locale locale;
+ if (LocaleParser::TryParse(fcLang, locale).isOk() &&
+ locale.AddLikelySubtags().isOk()) {
+ if (UseCustomFontconfigLookupsForLocale(locale)) {
+ cacheKey.Append(fcLang);
+ } else {
+ cacheKey.Append(locale.Script().Span());
+ }
+ } else {
+ cacheKey.Append(fcLang);
+ }
+ }
+
+ // try to get the family from the cache
+ return mGenericMappings.WithEntryHandle(
+ cacheKey, [&](auto&& entry) -> PrefFontList* {
+ if (!entry) {
+ // if not found, ask fontconfig to pick the appropriate font
+ RefPtr<FcPattern> genericPattern = dont_AddRef(FcPatternCreate());
+ FcPatternAddString(genericPattern, FC_FAMILY,
+ ToFcChar8Ptr(aGeneric.get()));
+
+ // -- prefer scalable fonts
+ FcPatternAddBool(genericPattern, FC_SCALABLE, FcTrue);
+
+ // -- add the lang to the pattern
+ if (!fcLang.IsEmpty()) {
+ FcPatternAddString(genericPattern, FC_LANG,
+ ToFcChar8Ptr(fcLang.get()));
+ }
+
+ // -- perform substitutions
+ FcConfigSubstitute(nullptr, genericPattern, FcMatchPattern);
+ FcDefaultSubstitute(genericPattern);
+
+ // -- sort to get the closest matches
+ FcResult result;
+ UniquePtr<FcFontSet> faces(
+ FcFontSort(nullptr, genericPattern, FcFalse, nullptr, &result));
+
+ if (!faces) {
+ return nullptr;
+ }
+
+ // -- select the fonts to be used for the generic
+ auto prefFonts = MakeUnique<PrefFontList>(); // can be empty but in
+ // practice won't happen
+ uint32_t limit = StaticPrefs::
+ gfx_font_rendering_fontconfig_max_generic_substitutions();
+ bool foundFontWithLang = false;
+ for (int i = 0; i < faces->nfont; i++) {
+ FcPattern* font = faces->fonts[i];
+ FcChar8* mappedGeneric = nullptr;
+
+ FcPatternGetString(font, FC_FAMILY, 0, &mappedGeneric);
+ if (mappedGeneric) {
+ mLock.AssertCurrentThreadIn();
+ nsAutoCString mappedGenericName(ToCharPtr(mappedGeneric));
+ AutoTArray<FamilyAndGeneric, 1> genericFamilies;
+ if (gfxPlatformFontList::FindAndAddFamiliesLocked(
+ aPresContext, StyleGenericFontFamily::None,
+ mappedGenericName, &genericFamilies,
+ FindFamiliesFlags(0))) {
+ MOZ_ASSERT(genericFamilies.Length() == 1,
+ "expected a single family");
+ if (!prefFonts->Contains(genericFamilies[0].mFamily)) {
+ prefFonts->AppendElement(genericFamilies[0].mFamily);
+ bool foundLang =
+ !fcLang.IsEmpty() &&
+ PatternHasLang(font, ToFcChar8Ptr(fcLang.get()));
+ foundFontWithLang = foundFontWithLang || foundLang;
+ // check to see if the list is full
+ if (prefFonts->Length() >= limit) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // if no font in the list matches the lang, trim all but the first one
+ if (!prefFonts->IsEmpty() && !foundFontWithLang) {
+ prefFonts->TruncateLength(1);
+ }
+
+ entry.Insert(std::move(prefFonts));
+ }
+ return entry->get();
+ });
+}
+
+bool gfxFcPlatformFontList::PrefFontListsUseOnlyGenerics() {
+ for (auto iter = mFontPrefs->NameIter(); !iter.Done(); iter.Next()) {
+ // Check whether all font.name prefs map to generic keywords
+ // and that the pref name and keyword match.
+ // Ex: font.name.serif.ar ==> "serif" (ok)
+ // Ex: font.name.serif.ar ==> "monospace" (return false)
+ // Ex: font.name.serif.ar ==> "DejaVu Serif" (return false)
+ // Ex: font.name.serif.ar ==> "" and
+ // font.name-list.serif.ar ==> "serif" (ok)
+ // Ex: font.name.serif.ar ==> "" and
+ // font.name-list.serif.ar ==> "Something, serif"
+ // (return false)
+ const nsACString* prefValue = &iter.Data();
+ nsAutoCString listValue;
+ if (iter.Data().IsEmpty()) {
+ // The font name list may have two or more family names as comma
+ // separated list. In such case, not matching with generic font
+ // name is fine because if the list prefers specific font, this
+ // should return false.
+ mFontPrefs->LookupNameList(iter.Key(), listValue);
+ prefValue = &listValue;
+ }
+
+ nsCCharSeparatedTokenizer tokenizer(iter.Key(), '.');
+ const nsDependentCSubstring& generic = tokenizer.nextToken();
+ const nsDependentCSubstring& langGroup = tokenizer.nextToken();
+
+ if (!langGroup.EqualsLiteral("x-math") && !generic.Equals(*prefValue)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/* static */
+void gfxFcPlatformFontList::CheckFontUpdates(nsITimer* aTimer, void* aThis) {
+ // A content process is not supposed to check this directly;
+ // it will be notified by the parent when the font list changes.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // check for font updates
+ FcInitBringUptoDate();
+
+ // update fontlist if current config changed
+ gfxFcPlatformFontList* pfl = static_cast<gfxFcPlatformFontList*>(aThis);
+ FcConfig* current = FcConfigGetCurrent();
+ if (current != pfl->GetLastConfig()) {
+ pfl->UpdateFontList();
+
+ gfxPlatform::ForceGlobalReflow(gfxPlatform::NeedsReframe::Yes);
+ mozilla::dom::ContentParent::NotifyUpdatedFonts(true);
+ }
+}
+
+gfxFontFamily* gfxFcPlatformFontList::CreateFontFamily(
+ const nsACString& aName, FontVisibility aVisibility) const {
+ return new gfxFontconfigFontFamily(aName, aVisibility);
+}
+
+// mapping of moz lang groups ==> default lang
+struct MozLangGroupData {
+ nsAtom* const& mozLangGroup;
+ const char* defaultLang;
+};
+
+const MozLangGroupData MozLangGroups[] = {
+ {nsGkAtoms::x_western, "en"}, {nsGkAtoms::x_cyrillic, "ru"},
+ {nsGkAtoms::x_devanagari, "hi"}, {nsGkAtoms::x_tamil, "ta"},
+ {nsGkAtoms::x_armn, "hy"}, {nsGkAtoms::x_beng, "bn"},
+ {nsGkAtoms::x_cans, "iu"}, {nsGkAtoms::x_ethi, "am"},
+ {nsGkAtoms::x_geor, "ka"}, {nsGkAtoms::x_gujr, "gu"},
+ {nsGkAtoms::x_guru, "pa"}, {nsGkAtoms::x_khmr, "km"},
+ {nsGkAtoms::x_knda, "kn"}, {nsGkAtoms::x_mlym, "ml"},
+ {nsGkAtoms::x_orya, "or"}, {nsGkAtoms::x_sinh, "si"},
+ {nsGkAtoms::x_tamil, "ta"}, {nsGkAtoms::x_telu, "te"},
+ {nsGkAtoms::x_tibt, "bo"}, {nsGkAtoms::Unicode, 0}};
+
+bool gfxFcPlatformFontList::TryLangForGroup(const nsACString& aOSLang,
+ nsAtom* aLangGroup,
+ nsACString& aFcLang,
+ bool aForFontEnumerationThread) {
+ // Truncate at '.' or '@' from aOSLang, and convert '_' to '-'.
+ // aOSLang is in the form "language[_territory][.codeset][@modifier]".
+ // fontconfig takes languages in the form "language-territory".
+ // nsLanguageAtomService takes languages in the form language-subtag,
+ // where subtag may be a territory. fontconfig and nsLanguageAtomService
+ // handle case-conversion for us.
+ const char *pos, *end;
+ aOSLang.BeginReading(pos);
+ aOSLang.EndReading(end);
+ aFcLang.Truncate();
+ while (pos < end) {
+ switch (*pos) {
+ case '.':
+ case '@':
+ end = pos;
+ break;
+ case '_':
+ aFcLang.Append('-');
+ break;
+ default:
+ aFcLang.Append(*pos);
+ }
+ ++pos;
+ }
+
+ if (!aForFontEnumerationThread) {
+ nsAtom* atom = mLangService->LookupLanguage(aFcLang);
+ return atom == aLangGroup;
+ }
+
+ // If we were called by the font enumeration thread, we can't use
+ // mLangService->LookupLanguage because it is not thread-safe.
+ // Use GetUncachedLanguageGroup to avoid unsafe access to the lang-group
+ // mapping cache hashtable.
+ nsAutoCString lowered(aFcLang);
+ ToLowerCase(lowered);
+ RefPtr<nsAtom> lang = NS_Atomize(lowered);
+ RefPtr<nsAtom> group = mLangService->GetUncachedLanguageGroup(lang);
+ return group.get() == aLangGroup;
+}
+
+void gfxFcPlatformFontList::GetSampleLangForGroup(
+ nsAtom* aLanguage, nsACString& aLangStr, bool aForFontEnumerationThread) {
+ aLangStr.Truncate();
+ if (!aLanguage) {
+ return;
+ }
+
+ // set up lang string
+ const MozLangGroupData* mozLangGroup = nullptr;
+
+ // -- look it up in the list of moz lang groups
+ for (unsigned int i = 0; i < ArrayLength(MozLangGroups); ++i) {
+ if (aLanguage == MozLangGroups[i].mozLangGroup) {
+ mozLangGroup = &MozLangGroups[i];
+ break;
+ }
+ }
+
+ // -- not a mozilla lang group? Just return the BCP47 string
+ // representation of the lang group
+ if (!mozLangGroup) {
+ // Not a special mozilla language group.
+ // Use aLanguage as a language code.
+ aLanguage->ToUTF8String(aLangStr);
+ return;
+ }
+
+ // -- check the environment for the user's preferred language that
+ // corresponds to this mozilla lang group.
+ const char* languages = getenv("LANGUAGE");
+ if (languages) {
+ const char separator = ':';
+
+ for (const char* pos = languages; true; ++pos) {
+ if (*pos == '\0' || *pos == separator) {
+ if (languages < pos &&
+ TryLangForGroup(Substring(languages, pos), aLanguage, aLangStr,
+ aForFontEnumerationThread)) {
+ return;
+ }
+
+ if (*pos == '\0') {
+ break;
+ }
+
+ languages = pos + 1;
+ }
+ }
+ }
+ const char* ctype = setlocale(LC_CTYPE, nullptr);
+ if (ctype && TryLangForGroup(nsDependentCString(ctype), aLanguage, aLangStr,
+ aForFontEnumerationThread)) {
+ return;
+ }
+
+ if (mozLangGroup->defaultLang) {
+ aLangStr.Assign(mozLangGroup->defaultLang);
+ } else {
+ aLangStr.Truncate();
+ }
+}
+
+#ifdef MOZ_BUNDLED_FONTS
+void gfxFcPlatformFontList::ActivateBundledFonts() {
+ if (!mBundledFontsInitialized) {
+ mBundledFontsInitialized = true;
+ nsCOMPtr<nsIFile> localDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ if (NS_FAILED(localDir->Append(u"fonts"_ns))) {
+ return;
+ }
+ bool isDir;
+ if (NS_FAILED(localDir->IsDirectory(&isDir)) || !isDir) {
+ return;
+ }
+ if (NS_FAILED(localDir->GetNativePath(mBundledFontsPath))) {
+ return;
+ }
+ }
+ if (!mBundledFontsPath.IsEmpty()) {
+ FcConfigAppFontAddDir(nullptr, ToFcChar8Ptr(mBundledFontsPath.get()));
+ }
+}
+#endif
+
+#ifdef MOZ_WIDGET_GTK
+/***************************************************************************
+ *
+ * These functions must be last in the file because it uses the system cairo
+ * library. Above this point the cairo library used is the tree cairo.
+ */
+
+// Tree cairo symbols have different names. Disable their activation through
+// preprocessor macros.
+# undef cairo_ft_font_options_substitute
+
+# undef cairo_font_options_create
+# undef cairo_font_options_destroy
+# undef cairo_font_options_copy
+# undef cairo_font_options_equal
+
+# undef cairo_font_options_get_antialias
+# undef cairo_font_options_set_antialias
+# undef cairo_font_options_get_hint_style
+# undef cairo_font_options_set_hint_style
+# undef cairo_font_options_get_lcd_filter
+# undef cairo_font_options_set_lcd_filter
+# undef cairo_font_options_get_subpixel_order
+# undef cairo_font_options_set_subpixel_order
+
+// The system cairo functions are not declared because the include paths cause
+// the gdk headers to pick up the tree cairo.h.
+extern "C" {
+NS_VISIBILITY_DEFAULT void cairo_ft_font_options_substitute(
+ const cairo_font_options_t* options, FcPattern* pattern);
+
+NS_VISIBILITY_DEFAULT cairo_font_options_t* cairo_font_options_copy(
+ const cairo_font_options_t*);
+NS_VISIBILITY_DEFAULT cairo_font_options_t* cairo_font_options_create();
+NS_VISIBILITY_DEFAULT void cairo_font_options_destroy(cairo_font_options_t*);
+NS_VISIBILITY_DEFAULT cairo_bool_t cairo_font_options_equal(
+ const cairo_font_options_t*, const cairo_font_options_t*);
+
+NS_VISIBILITY_DEFAULT cairo_antialias_t
+cairo_font_options_get_antialias(const cairo_font_options_t*);
+NS_VISIBILITY_DEFAULT void cairo_font_options_set_antialias(
+ cairo_font_options_t*, cairo_antialias_t);
+NS_VISIBILITY_DEFAULT cairo_hint_style_t
+cairo_font_options_get_hint_style(const cairo_font_options_t*);
+NS_VISIBILITY_DEFAULT void cairo_font_options_set_hint_style(
+ cairo_font_options_t*, cairo_hint_style_t);
+NS_VISIBILITY_DEFAULT cairo_subpixel_order_t
+cairo_font_options_get_subpixel_order(const cairo_font_options_t*);
+NS_VISIBILITY_DEFAULT void cairo_font_options_set_subpixel_order(
+ cairo_font_options_t*, cairo_subpixel_order_t);
+}
+
+void gfxFcPlatformFontList::ClearSystemFontOptions() {
+ if (mSystemFontOptions) {
+ cairo_font_options_destroy(mSystemFontOptions);
+ mSystemFontOptions = nullptr;
+ }
+}
+
+bool gfxFcPlatformFontList::UpdateSystemFontOptions() {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+
+ if (gfxPlatform::IsHeadless()) {
+ return false;
+ }
+
+# ifdef MOZ_X11
+ {
+ // This one shouldn't change during the X session.
+ int lcdfilter;
+ GdkDisplay* dpy = gdk_display_get_default();
+ if (mozilla::widget::GdkIsX11Display(dpy) &&
+ GetXftInt(GDK_DISPLAY_XDISPLAY(dpy), "lcdfilter", &lcdfilter)) {
+ mFreetypeLcdSetting = lcdfilter;
+ }
+ }
+# endif // MOZ_X11
+
+ const cairo_font_options_t* options =
+ gdk_screen_get_font_options(gdk_screen_get_default());
+ if (!options) {
+ bool changed = !!mSystemFontOptions;
+ ClearSystemFontOptions();
+ return changed;
+ }
+
+ cairo_font_options_t* newOptions = cairo_font_options_copy(options);
+
+ if (mSystemFontOptions &&
+ cairo_font_options_equal(mSystemFontOptions, options)) {
+ cairo_font_options_destroy(newOptions);
+ return false;
+ }
+
+ ClearSystemFontOptions();
+ mSystemFontOptions = newOptions;
+ return true;
+}
+
+void gfxFcPlatformFontList::SystemFontOptionsToIpc(
+ dom::SystemFontOptions& aOptions) {
+ aOptions.antialias() =
+ mSystemFontOptions ? cairo_font_options_get_antialias(mSystemFontOptions)
+ : CAIRO_ANTIALIAS_DEFAULT;
+ aOptions.subpixelOrder() =
+ mSystemFontOptions
+ ? cairo_font_options_get_subpixel_order(mSystemFontOptions)
+ : CAIRO_SUBPIXEL_ORDER_DEFAULT;
+ aOptions.hintStyle() =
+ mSystemFontOptions ? cairo_font_options_get_hint_style(mSystemFontOptions)
+ : CAIRO_HINT_STYLE_DEFAULT;
+ aOptions.lcdFilter() = mFreetypeLcdSetting;
+}
+
+void gfxFcPlatformFontList::UpdateSystemFontOptionsFromIpc(
+ const dom::SystemFontOptions& aOptions) {
+ ClearSystemFontOptions();
+ mSystemFontOptions = cairo_font_options_create();
+ cairo_font_options_set_antialias(mSystemFontOptions,
+ cairo_antialias_t(aOptions.antialias()));
+ cairo_font_options_set_hint_style(mSystemFontOptions,
+ cairo_hint_style_t(aOptions.hintStyle()));
+ cairo_font_options_set_subpixel_order(
+ mSystemFontOptions, cairo_subpixel_order_t(aOptions.subpixelOrder()));
+ mFreetypeLcdSetting = aOptions.lcdFilter();
+}
+
+void gfxFcPlatformFontList::SubstituteSystemFontOptions(FcPattern* aPattern) {
+ if (mSystemFontOptions) {
+ cairo_ft_font_options_substitute(mSystemFontOptions, aPattern);
+ }
+
+ if (mFreetypeLcdSetting != -1) {
+ FcValue value;
+ if (FcPatternGet(aPattern, FC_LCD_FILTER, 0, &value) == FcResultNoMatch) {
+ FcPatternAddInteger(aPattern, FC_LCD_FILTER, mFreetypeLcdSetting);
+ }
+ }
+}
+
+#endif // MOZ_WIDGET_GTK
diff --git a/gfx/thebes/gfxFcPlatformFontList.h b/gfx/thebes/gfxFcPlatformFontList.h
new file mode 100644
index 0000000000..70b830f8fa
--- /dev/null
+++ b/gfx/thebes/gfxFcPlatformFontList.h
@@ -0,0 +1,436 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFXFCPLATFORMFONTLIST_H_
+#define GFXFCPLATFORMFONTLIST_H_
+
+#include "gfxFT2FontBase.h"
+#include "gfxPlatformFontList.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/mozalloc.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsClassHashtable.h"
+#include "nsTHashMap.h"
+
+#include <fontconfig/fontconfig.h>
+#include "ft2build.h"
+#include FT_FREETYPE_H
+#include FT_TRUETYPE_TABLES_H
+#include FT_MULTIPLE_MASTERS_H
+
+#if defined(MOZ_SANDBOX) && defined(XP_LINUX)
+# include "mozilla/SandboxBroker.h"
+#endif
+
+namespace mozilla {
+namespace dom {
+class SystemFontListEntry;
+class SystemFontList;
+class SystemFontOptions;
+}; // namespace dom
+
+template <>
+class RefPtrTraits<FcPattern> {
+ public:
+ static void Release(FcPattern* ptr) { FcPatternDestroy(ptr); }
+ static void AddRef(FcPattern* ptr) { FcPatternReference(ptr); }
+};
+
+template <>
+class RefPtrTraits<FcConfig> {
+ public:
+ static void Release(FcConfig* ptr) { FcConfigDestroy(ptr); }
+ static void AddRef(FcConfig* ptr) { FcConfigReference(ptr); }
+};
+
+template <>
+class DefaultDelete<FcFontSet> {
+ public:
+ void operator()(FcFontSet* aPtr) { FcFontSetDestroy(aPtr); }
+};
+
+template <>
+class DefaultDelete<FcObjectSet> {
+ public:
+ void operator()(FcObjectSet* aPtr) { FcObjectSetDestroy(aPtr); }
+};
+
+}; // namespace mozilla
+
+// The names for the font entry and font classes should really
+// the common 'Fc' abbreviation but the gfxPangoFontGroup code already
+// defines versions of these, so use the verbose name for now.
+
+class gfxFontconfigFontEntry final : public gfxFT2FontEntryBase {
+ friend class gfxFcPlatformFontList;
+ using FTUserFontData = mozilla::gfx::FTUserFontData;
+
+ public:
+ // used for system fonts with explicit patterns
+ explicit gfxFontconfigFontEntry(const nsACString& aFaceName,
+ FcPattern* aFontPattern,
+ bool aIgnoreFcCharmap);
+
+ // used for data fonts where the fontentry takes ownership
+ // of the font data and the FT_Face
+ explicit gfxFontconfigFontEntry(const nsACString& aFaceName,
+ WeightRange aWeight, StretchRange aStretch,
+ SlantStyleRange aStyle,
+ RefPtr<mozilla::gfx::SharedFTFace>&& aFace);
+
+ // used for @font-face local system fonts with explicit patterns
+ explicit gfxFontconfigFontEntry(const nsACString& aFaceName,
+ FcPattern* aFontPattern, WeightRange aWeight,
+ StretchRange aStretch,
+ SlantStyleRange aStyle);
+
+ gfxFontEntry* Clone() const override;
+
+ FcPattern* GetPattern() { return mFontPattern; }
+
+ nsresult ReadCMAP(FontInfoData* aFontInfoData = nullptr) override;
+ bool TestCharacterMap(uint32_t aCh) override;
+
+ mozilla::gfx::SharedFTFace* GetFTFace();
+ FTUserFontData* GetUserFontData();
+
+ FT_MM_Var* GetMMVar() override;
+
+ bool HasVariations() override;
+ void GetVariationAxes(nsTArray<gfxFontVariationAxis>& aAxes) override;
+ void GetVariationInstances(
+ nsTArray<gfxFontVariationInstance>& aInstances) override;
+
+ bool HasFontTable(uint32_t aTableTag) override;
+ nsresult CopyFontTable(uint32_t aTableTag, nsTArray<uint8_t>&) override;
+ hb_blob_t* GetFontTable(uint32_t aTableTag) override;
+
+ double GetAspect(uint8_t aSizeAdjustBasis);
+
+ protected:
+ virtual ~gfxFontconfigFontEntry();
+
+ gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle) override;
+
+ void GetUserFontFeatures(FcPattern* aPattern);
+
+ // pattern for a single face of a family
+ RefPtr<FcPattern> mFontPattern;
+
+ // FTFace - initialized when needed. Once mFTFaceInitialized is true,
+ // the face can be accessed without locking.
+ // Note that mFTFace owns a reference to the SharedFTFace, but is not
+ // a RefPtr because we need it to be an atomic.
+ mozilla::Atomic<mozilla::gfx::SharedFTFace*> mFTFace;
+ mozilla::Atomic<bool> mFTFaceInitialized;
+
+ // Whether TestCharacterMap should check the actual cmap rather than asking
+ // fontconfig about character coverage.
+ // We do this for app-bundled (rather than system) fonts, as they may
+ // include color glyphs that fontconfig would overlook, and for fonts
+ // loaded via @font-face.
+ bool mIgnoreFcCharmap;
+
+ // Whether the face supports variations. For system-installed fonts, we
+ // query fontconfig for this (so they will only work if fontconfig is
+ // recent enough to include support); for downloaded user-fonts we query
+ // the FreeType face.
+ enum class HasVariationsState : int8_t {
+ Uninitialized = -1,
+ No = 0,
+ Yes = 1,
+ };
+ std::atomic<HasVariationsState> mHasVariations =
+ HasVariationsState::Uninitialized;
+
+ class UnscaledFontCache {
+ public:
+ already_AddRefed<mozilla::gfx::UnscaledFontFontconfig> Lookup(
+ const std::string& aFile, uint32_t aIndex);
+
+ void Add(
+ const RefPtr<mozilla::gfx::UnscaledFontFontconfig>& aUnscaledFont) {
+ mUnscaledFonts[kNumEntries - 1] = aUnscaledFont;
+ MoveToFront(kNumEntries - 1);
+ }
+
+ private:
+ void MoveToFront(size_t aIndex);
+
+ static const size_t kNumEntries = 3;
+ mozilla::ThreadSafeWeakPtr<mozilla::gfx::UnscaledFontFontconfig>
+ mUnscaledFonts[kNumEntries];
+ };
+
+ UnscaledFontCache mUnscaledFontCache;
+
+ // Because of FreeType bug 52955, we keep the FT_MM_Var struct when it is
+ // first loaded, rather than releasing it and re-fetching it as needed.
+ FT_MM_Var* mMMVar = nullptr;
+ bool mMMVarInitialized = false;
+};
+
+class gfxFontconfigFontFamily final : public gfxFontFamily {
+ public:
+ gfxFontconfigFontFamily(const nsACString& aName, FontVisibility aVisibility)
+ : gfxFontFamily(aName, aVisibility),
+ mContainsAppFonts(false),
+ mHasNonScalableFaces(false),
+ mForceScalable(false) {}
+
+ template <typename Func>
+ void AddFacesToFontList(Func aAddPatternFunc);
+
+ void FindStyleVariationsLocked(FontInfoData* aFontInfoData = nullptr)
+ MOZ_REQUIRES(mLock) override;
+
+ // Families are constructed initially with just references to patterns.
+ // When necessary, these are enumerated within FindStyleVariations.
+ void AddFontPattern(FcPattern* aFontPattern, bool aSingleName);
+
+ void SetFamilyContainsAppFonts(bool aContainsAppFonts) {
+ mContainsAppFonts = aContainsAppFonts;
+ }
+
+ void FindAllFontsForStyle(const gfxFontStyle& aFontStyle,
+ nsTArray<gfxFontEntry*>& aFontEntryList,
+ bool aIgnoreSizeTolerance) override;
+
+ bool FilterForFontList(nsAtom* aLangGroup,
+ const nsACString& aGeneric) const final {
+ return SupportsLangGroup(aLangGroup);
+ }
+
+ protected:
+ virtual ~gfxFontconfigFontFamily();
+
+ // helper for FilterForFontList
+ bool SupportsLangGroup(nsAtom* aLangGroup) const;
+
+ nsTArray<RefPtr<FcPattern>> mFontPatterns;
+
+ // Number of faces that have a single name. Faces that have multiple names are
+ // sorted last.
+ uint32_t mUniqueNameFaceCount = 0;
+ bool mContainsAppFonts : 1;
+ bool mHasNonScalableFaces : 1;
+ bool mForceScalable : 1;
+};
+
+class gfxFontconfigFont final : public gfxFT2FontBase {
+ public:
+ gfxFontconfigFont(
+ const RefPtr<mozilla::gfx::UnscaledFontFontconfig>& aUnscaledFont,
+ RefPtr<mozilla::gfx::SharedFTFace>&& aFTFace, FcPattern* aPattern,
+ gfxFloat aAdjustedSize, gfxFontEntry* aFontEntry,
+ const gfxFontStyle* aFontStyle, int aLoadFlags, bool aEmbolden);
+
+ FontType GetType() const override { return FONT_TYPE_FONTCONFIG; }
+ FcPattern* GetPattern() const { return mPattern; }
+
+ already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont(
+ const TextRunDrawParams& aRunParams) override;
+
+ bool ShouldHintMetrics() const override;
+
+ private:
+ ~gfxFontconfigFont() override;
+
+ RefPtr<FcPattern> mPattern;
+};
+
+class gfxFcPlatformFontList final : public gfxPlatformFontList {
+ using FontPatternListEntry = mozilla::dom::SystemFontListEntry;
+
+ public:
+ gfxFcPlatformFontList();
+
+ static gfxFcPlatformFontList* PlatformFontList() {
+ return static_cast<gfxFcPlatformFontList*>(
+ gfxPlatformFontList::PlatformFontList());
+ }
+
+ // initialize font lists
+ nsresult InitFontListForPlatform() MOZ_REQUIRES(mLock) override;
+ void InitSharedFontListForPlatform() MOZ_REQUIRES(mLock) override;
+
+ void GetFontList(nsAtom* aLangGroup, const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts) override;
+
+ void ReadSystemFontList(mozilla::dom::SystemFontList*);
+
+ gfxFontEntry* CreateFontEntry(
+ mozilla::fontlist::Face* aFace,
+ const mozilla::fontlist::Family* aFamily) override;
+
+ gfxFontEntry* LookupLocalFont(nsPresContext* aPresContext,
+ const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry) override;
+
+ gfxFontEntry* MakePlatformFont(const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry,
+ const uint8_t* aFontData,
+ uint32_t aLength) override;
+
+ bool FindAndAddFamiliesLocked(
+ nsPresContext* aPresContext, mozilla::StyleGenericFontFamily aGeneric,
+ const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput,
+ FindFamiliesFlags aFlags, gfxFontStyle* aStyle = nullptr,
+ nsAtom* aLanguage = nullptr, gfxFloat aDevToCssSize = 1.0)
+ MOZ_REQUIRES(mLock) override;
+
+ bool GetStandardFamilyName(const nsCString& aFontName,
+ nsACString& aFamilyName) override;
+
+ FcConfig* GetLastConfig() const { return mLastConfig; }
+
+ // override to use fontconfig lookup for generics
+ void AddGenericFonts(nsPresContext* aPresContext,
+ mozilla::StyleGenericFontFamily, nsAtom* aLanguage,
+ nsTArray<FamilyAndGeneric>& aFamilyList) override;
+
+ void ClearLangGroupPrefFontsLocked() MOZ_REQUIRES(mLock) override;
+
+ // clear out cached generic-lang ==> family-list mappings
+ void ClearGenericMappings() {
+ AutoLock lock(mLock);
+ ClearGenericMappingsLocked();
+ }
+ void ClearGenericMappingsLocked() MOZ_REQUIRES(mLock) {
+ mGenericMappings.Clear();
+ }
+
+ // map lang group ==> lang string
+ // When aForFontEnumerationThread is true, this method will avoid using
+ // LanguageService::LookupLanguage, because it is not safe for off-main-
+ // thread use (except by stylo traversal, which does the necessary locking)
+ void GetSampleLangForGroup(nsAtom* aLanguage, nsACString& aLangStr,
+ bool aForFontEnumerationThread = false);
+
+ protected:
+ virtual ~gfxFcPlatformFontList();
+
+#if defined(MOZ_SANDBOX) && defined(XP_LINUX)
+ typedef mozilla::SandboxBroker::Policy SandboxPolicy;
+#else
+ // Dummy type just so we can still have a SandboxPolicy* parameter.
+ struct SandboxPolicy {};
+#endif
+
+ // Add all the font families found in a font set.
+ // aAppFonts indicates whether this is the system or application fontset.
+ void AddFontSetFamilies(FcFontSet* aFontSet, const SandboxPolicy* aPolicy,
+ bool aAppFonts) MOZ_REQUIRES(mLock);
+
+ // Helper for above, to add a single font pattern.
+ void AddPatternToFontList(FcPattern* aFont, FcChar8*& aLastFamilyName,
+ nsACString& aFamilyName,
+ RefPtr<gfxFontconfigFontFamily>& aFontFamily,
+ bool aAppFonts) MOZ_REQUIRES(mLock);
+
+ // figure out which families fontconfig maps a generic to
+ // (aGeneric assumed already lowercase)
+ PrefFontList* FindGenericFamilies(nsPresContext* aPresContext,
+ const nsCString& aGeneric,
+ nsAtom* aLanguage) MOZ_REQUIRES(mLock);
+
+ // are all pref font settings set to use fontconfig generics?
+ bool PrefFontListsUseOnlyGenerics() MOZ_REQUIRES(mLock);
+
+ static void CheckFontUpdates(nsITimer* aTimer, void* aThis);
+
+ FontFamily GetDefaultFontForPlatform(nsPresContext* aPresContext,
+ const gfxFontStyle* aStyle,
+ nsAtom* aLanguage = nullptr)
+ MOZ_REQUIRES(mLock) override;
+
+ enum class DistroID : int8_t {
+ Unknown = 0,
+ Ubuntu = 1,
+ Fedora = 2,
+ // To be extended with any distros that ship a useful base set of fonts
+ // that we want to explicitly support.
+ };
+ DistroID GetDistroID() const; // -> DistroID::Unknown if we can't tell
+
+ FontVisibility GetVisibilityForFamily(const nsACString& aName) const;
+
+ gfxFontFamily* CreateFontFamily(const nsACString& aName,
+ FontVisibility aVisibility) const override;
+
+ // helper method for finding an appropriate lang string
+ bool TryLangForGroup(const nsACString& aOSLang, nsAtom* aLangGroup,
+ nsACString& aLang, bool aForFontEnumerationThread);
+
+#ifdef MOZ_BUNDLED_FONTS
+ void ActivateBundledFonts();
+ nsCString mBundledFontsPath;
+ bool mBundledFontsInitialized;
+#endif
+
+ // to avoid enumerating all fonts, maintain a mapping of local font
+ // names to family
+ nsTHashMap<nsCString, RefPtr<FcPattern>> mLocalNames;
+
+ // caching generic/lang ==> font family list
+ nsClassHashtable<nsCStringHashKey, PrefFontList> mGenericMappings;
+
+ // Caching family lookups as found by FindAndAddFamilies after resolving
+ // substitutions. The gfxFontFamily objects cached here are owned by the
+ // gfxFcPlatformFontList via its mFamilies table; note that if the main
+ // font list is rebuilt (e.g. due to a fontconfig configuration change),
+ // these pointers will be invalidated. InitFontList() flushes the cache
+ // in this case.
+ nsTHashMap<nsCStringHashKey, nsTArray<FamilyAndGeneric>> mFcSubstituteCache;
+
+ nsCOMPtr<nsITimer> mCheckFontUpdatesTimer;
+ RefPtr<FcConfig> mLastConfig;
+
+ // The current system font options in effect.
+#ifdef MOZ_WIDGET_GTK
+ // NOTE(emilio): This is a *system cairo* cairo_font_options_t object. As
+ // such, it can't be used outside of the few functions defined here.
+ cairo_font_options_t* mSystemFontOptions = nullptr;
+ int32_t mFreetypeLcdSetting = -1; // -1 for not set
+
+ void ClearSystemFontOptions();
+
+ // Returns whether options actually changed.
+ // TODO(emilio): We could call this when gsettings change or such, but
+ // historically we haven't reacted to these settings changes, so keeping it
+ // simple for now.
+ bool UpdateSystemFontOptions();
+
+ void UpdateSystemFontOptionsFromIpc(const mozilla::dom::SystemFontOptions&);
+ void SystemFontOptionsToIpc(mozilla::dom::SystemFontOptions&);
+
+ public:
+ void SubstituteSystemFontOptions(FcPattern*);
+
+ private:
+#endif
+
+ // Cache for most recently used language code in FindAndAddFamiliesLocked,
+ // and the result of checking whether to use lang-specific lookups.
+ RefPtr<nsAtom> mPrevLanguage;
+ nsCString mSampleLang;
+ bool mUseCustomLookups = false;
+
+ // By default, font prefs under Linux are set to simply lookup
+ // via fontconfig the appropriate font for serif/sans-serif/monospace.
+ // Rather than check each time a font pref is used, check them all at startup
+ // and set a boolean to flag the case that non-default user font prefs exist
+ // Note: langGroup == x-math is handled separately
+ bool mAlwaysUseFontconfigGenerics;
+
+ static FT_Library sFTLibrary;
+};
+
+#endif /* GFXPLATFORMFONTLIST_H_ */
diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp
new file mode 100644
index 0000000000..68bf0d902c
--- /dev/null
+++ b/gfx/thebes/gfxFont.cpp
@@ -0,0 +1,4614 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxFont.h"
+
+#include "mozilla/BinarySearch.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/intl/Segmenter.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/SVGContextPaint.h"
+
+#include "mozilla/Logging.h"
+
+#include "nsITimer.h"
+
+#include "gfxGlyphExtents.h"
+#include "gfxPlatform.h"
+#include "gfxTextRun.h"
+#include "nsGkAtoms.h"
+
+#include "gfxTypes.h"
+#include "gfxContext.h"
+#include "gfxFontMissingGlyphs.h"
+#include "gfxGraphiteShaper.h"
+#include "gfxHarfBuzzShaper.h"
+#include "gfxUserFontSet.h"
+#include "nsCRT.h"
+#include "nsSpecialCasingData.h"
+#include "nsTextRunTransformations.h"
+#include "nsUGenCategory.h"
+#include "nsUnicodeProperties.h"
+#include "nsStyleConsts.h"
+#include "mozilla/AppUnits.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
+#include "gfxMathTable.h"
+#include "gfxSVGGlyphs.h"
+#include "gfx2DGlue.h"
+#include "TextDrawTarget.h"
+
+#include "ThebesRLBox.h"
+
+#include "GreekCasing.h"
+
+#include "cairo.h"
+#ifdef XP_WIN
+# include "cairo-win32.h"
+# include "gfxWindowsPlatform.h"
+#endif
+
+#include "harfbuzz/hb.h"
+#include "harfbuzz/hb-ot.h"
+
+#include <algorithm>
+#include <limits>
+#include <cmath>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::unicode;
+using mozilla::services::GetObserverService;
+
+gfxFontCache* gfxFontCache::gGlobalCache = nullptr;
+
+#ifdef DEBUG_roc
+# define DEBUG_TEXT_RUN_STORAGE_METRICS
+#endif
+
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+uint32_t gTextRunStorageHighWaterMark = 0;
+uint32_t gTextRunStorage = 0;
+uint32_t gFontCount = 0;
+uint32_t gGlyphExtentsCount = 0;
+uint32_t gGlyphExtentsWidthsTotalSize = 0;
+uint32_t gGlyphExtentsSetupEagerSimple = 0;
+uint32_t gGlyphExtentsSetupEagerTight = 0;
+uint32_t gGlyphExtentsSetupLazyTight = 0;
+uint32_t gGlyphExtentsSetupFallBackToTight = 0;
+#endif
+
+#define LOG_FONTINIT(args) \
+ MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug, args)
+#define LOG_FONTINIT_ENABLED() \
+ MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug)
+
+/*
+ * gfxFontCache - global cache of gfxFont instances.
+ * Expires unused fonts after a short interval;
+ * notifies fonts to age their cached shaped-word records;
+ * observes memory-pressure notification and tells fonts to clear their
+ * shaped-word caches to free up memory.
+ */
+
+MOZ_DEFINE_MALLOC_SIZE_OF(FontCacheMallocSizeOf)
+
+NS_IMPL_ISUPPORTS(gfxFontCache::MemoryReporter, nsIMemoryReporter)
+
+/*virtual*/
+gfxTextRunFactory::~gfxTextRunFactory() {
+ // Should not be dropped by stylo
+ MOZ_ASSERT(!Servo_IsWorkerThread());
+}
+
+NS_IMETHODIMP
+gfxFontCache::MemoryReporter::CollectReports(
+ nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) {
+ FontCacheSizes sizes;
+
+ gfxFontCache::GetCache()->AddSizeOfIncludingThis(&FontCacheMallocSizeOf,
+ &sizes);
+
+ MOZ_COLLECT_REPORT("explicit/gfx/font-cache", KIND_HEAP, UNITS_BYTES,
+ sizes.mFontInstances,
+ "Memory used for active font instances.");
+
+ MOZ_COLLECT_REPORT("explicit/gfx/font-shaped-words", KIND_HEAP, UNITS_BYTES,
+ sizes.mShapedWords,
+ "Memory used to cache shaped glyph data.");
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(gfxFontCache::Observer, nsIObserver)
+
+NS_IMETHODIMP
+gfxFontCache::Observer::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* someData) {
+ if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
+ gfxFontCache* fontCache = gfxFontCache::GetCache();
+ if (fontCache) {
+ fontCache->FlushShapedWordCaches();
+ }
+ } else {
+ MOZ_ASSERT_UNREACHABLE("unexpected notification topic");
+ }
+ return NS_OK;
+}
+
+nsresult gfxFontCache::Init() {
+ NS_ASSERTION(!gGlobalCache, "Where did this come from?");
+ gGlobalCache = new gfxFontCache(GetMainThreadSerialEventTarget());
+ if (!gGlobalCache) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ RegisterStrongMemoryReporter(new MemoryReporter());
+ return NS_OK;
+}
+
+void gfxFontCache::Shutdown() {
+ delete gGlobalCache;
+ gGlobalCache = nullptr;
+
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+ printf("Textrun storage high water mark=%d\n", gTextRunStorageHighWaterMark);
+ printf("Total number of fonts=%d\n", gFontCount);
+ printf("Total glyph extents allocated=%d (size %d)\n", gGlyphExtentsCount,
+ int(gGlyphExtentsCount * sizeof(gfxGlyphExtents)));
+ printf("Total glyph extents width-storage size allocated=%d\n",
+ gGlyphExtentsWidthsTotalSize);
+ printf("Number of simple glyph extents eagerly requested=%d\n",
+ gGlyphExtentsSetupEagerSimple);
+ printf("Number of tight glyph extents eagerly requested=%d\n",
+ gGlyphExtentsSetupEagerTight);
+ printf("Number of tight glyph extents lazily requested=%d\n",
+ gGlyphExtentsSetupLazyTight);
+ printf("Number of simple glyph extent setups that fell back to tight=%d\n",
+ gGlyphExtentsSetupFallBackToTight);
+#endif
+}
+
+gfxFontCache::gfxFontCache(nsIEventTarget* aEventTarget)
+ : ExpirationTrackerImpl<gfxFont, 3, Lock, AutoLock>(
+ FONT_TIMEOUT_SECONDS * 1000, "gfxFontCache", aEventTarget) {
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ if (obs) {
+ obs->AddObserver(new Observer, "memory-pressure", false);
+ }
+
+ nsIEventTarget* target = nullptr;
+ if (XRE_IsContentProcess() && NS_IsMainThread()) {
+ target = aEventTarget;
+ }
+
+ // Create the timer used to expire shaped-word records from each font's
+ // cache after a short period of non-use. We have a single timer in
+ // gfxFontCache that loops over all fonts known to the cache, to avoid
+ // the overhead of individual timers in each font instance.
+ // The timer will be started any time shaped word records are cached
+ // (and pauses itself when all caches become empty).
+ mWordCacheExpirationTimer = NS_NewTimer(target);
+}
+
+gfxFontCache::~gfxFontCache() {
+ // Ensure the user font cache releases its references to font entries,
+ // so they aren't kept alive after the font instances and font-list
+ // have been shut down.
+ gfxUserFontSet::UserFontCache::Shutdown();
+
+ if (mWordCacheExpirationTimer) {
+ mWordCacheExpirationTimer->Cancel();
+ mWordCacheExpirationTimer = nullptr;
+ }
+
+ // Expire everything manually so we don't leak them.
+ Flush();
+}
+
+bool gfxFontCache::HashEntry::KeyEquals(const KeyTypePointer aKey) const {
+ const gfxCharacterMap* fontUnicodeRangeMap = mFont->GetUnicodeRangeMap();
+ return aKey->mFontEntry == mFont->GetFontEntry() &&
+ aKey->mStyle->Equals(*mFont->GetStyle()) &&
+ ((!aKey->mUnicodeRangeMap && !fontUnicodeRangeMap) ||
+ (aKey->mUnicodeRangeMap && fontUnicodeRangeMap &&
+ aKey->mUnicodeRangeMap->Equals(fontUnicodeRangeMap)));
+}
+
+already_AddRefed<gfxFont> gfxFontCache::Lookup(
+ const gfxFontEntry* aFontEntry, const gfxFontStyle* aStyle,
+ const gfxCharacterMap* aUnicodeRangeMap) {
+ MutexAutoLock lock(mMutex);
+
+ Key key(aFontEntry, aStyle, aUnicodeRangeMap);
+ HashEntry* entry = mFonts.GetEntry(key);
+
+ Telemetry::Accumulate(Telemetry::FONT_CACHE_HIT, entry != nullptr);
+
+ if (!entry) {
+ return nullptr;
+ }
+
+ RefPtr<gfxFont> font = entry->mFont;
+ if (font->GetExpirationState()->IsTracked()) {
+ RemoveObjectLocked(font, lock);
+ }
+ return font.forget();
+}
+
+already_AddRefed<gfxFont> gfxFontCache::MaybeInsert(gfxFont* aFont) {
+ MOZ_ASSERT(aFont);
+ MutexAutoLock lock(mMutex);
+
+ Key key(aFont->GetFontEntry(), aFont->GetStyle(),
+ aFont->GetUnicodeRangeMap());
+ HashEntry* entry = mFonts.PutEntry(key);
+ if (!entry) {
+ return do_AddRef(aFont);
+ }
+
+ // If it is null, then we are inserting a new entry. Otherwise we are
+ // attempting to replace an existing font, probably due to a thread race, in
+ // which case stick with the original font.
+ if (!entry->mFont) {
+ entry->mFont = aFont;
+ // Assert that we can find the entry we just put in (this fails if the key
+ // has a NaN float value in it, e.g. 'sizeAdjust').
+ MOZ_ASSERT(entry == mFonts.GetEntry(key));
+ } else {
+ MOZ_ASSERT(entry->mFont != aFont);
+ aFont->Destroy();
+ if (entry->mFont->GetExpirationState()->IsTracked()) {
+ RemoveObjectLocked(entry->mFont, lock);
+ }
+ }
+
+ return do_AddRef(entry->mFont);
+}
+
+bool gfxFontCache::MaybeDestroy(gfxFont* aFont) {
+ MOZ_ASSERT(aFont);
+ MutexAutoLock lock(mMutex);
+
+ // If the font has a non-zero refcount, then we must have lost the race with
+ // gfxFontCache::Lookup and the same font was reacquired.
+ if (aFont->GetRefCount() > 0) {
+ return false;
+ }
+
+ Key key(aFont->GetFontEntry(), aFont->GetStyle(),
+ aFont->GetUnicodeRangeMap());
+ HashEntry* entry = mFonts.GetEntry(key);
+ if (!entry || entry->mFont != aFont) {
+ MOZ_ASSERT(!aFont->GetExpirationState()->IsTracked());
+ return true;
+ }
+
+ // If the font is being tracked, we must have then also lost another race with
+ // gfxFontCache::MaybeDestroy which re-added it to the tracker.
+ if (aFont->GetExpirationState()->IsTracked()) {
+ return false;
+ }
+
+ // Typically this won't fail, but it may during startup/shutdown if the timer
+ // service is not available.
+ nsresult rv = AddObjectLocked(aFont, lock);
+ if (NS_SUCCEEDED(rv)) {
+ return false;
+ }
+
+ mFonts.RemoveEntry(entry);
+ return true;
+}
+
+void gfxFontCache::NotifyExpiredLocked(gfxFont* aFont, const AutoLock& aLock) {
+ MOZ_ASSERT(aFont->GetRefCount() == 0);
+
+ RemoveObjectLocked(aFont, aLock);
+ mTrackerDiscard.AppendElement(aFont);
+
+ Key key(aFont->GetFontEntry(), aFont->GetStyle(),
+ aFont->GetUnicodeRangeMap());
+ HashEntry* entry = mFonts.GetEntry(key);
+ if (!entry || entry->mFont != aFont) {
+ MOZ_ASSERT_UNREACHABLE("Invalid font?");
+ return;
+ }
+
+ mFonts.RemoveEntry(entry);
+}
+
+void gfxFontCache::NotifyHandlerEnd() {
+ nsTArray<gfxFont*> discard;
+ {
+ MutexAutoLock lock(mMutex);
+ discard = std::move(mTrackerDiscard);
+ }
+ DestroyDiscard(discard);
+}
+
+void gfxFontCache::DestroyDiscard(nsTArray<gfxFont*>& aDiscard) {
+ for (auto& font : aDiscard) {
+ NS_ASSERTION(font->GetRefCount() == 0,
+ "Destroying with refs outside cache!");
+ font->ClearCachedWords();
+ font->Destroy();
+ }
+ aDiscard.Clear();
+}
+
+void gfxFontCache::Flush() {
+ nsTArray<gfxFont*> discard;
+ {
+ MutexAutoLock lock(mMutex);
+ discard.SetCapacity(mFonts.Count());
+ for (auto iter = mFonts.Iter(); !iter.Done(); iter.Next()) {
+ HashEntry* entry = static_cast<HashEntry*>(iter.Get());
+ if (!entry || !entry->mFont) {
+ MOZ_ASSERT_UNREACHABLE("Invalid font?");
+ continue;
+ }
+
+ if (entry->mFont->GetRefCount() == 0) {
+ // If we are not tracked, then we must have won the race with
+ // gfxFont::MaybeDestroy and it is waiting on the mutex. To avoid a
+ // double free, we let gfxFont::MaybeDestroy handle the freeing when it
+ // acquires the mutex and discovers there is no matching entry in the
+ // hashtable.
+ if (entry->mFont->GetExpirationState()->IsTracked()) {
+ RemoveObjectLocked(entry->mFont, lock);
+ discard.AppendElement(entry->mFont);
+ }
+ } else {
+ MOZ_ASSERT(!entry->mFont->GetExpirationState()->IsTracked());
+ }
+ }
+ MOZ_ASSERT(IsEmptyLocked(lock),
+ "Cache tracker still has fonts after flush!");
+ mFonts.Clear();
+ }
+ DestroyDiscard(discard);
+}
+
+/*static*/
+void gfxFontCache::WordCacheExpirationTimerCallback(nsITimer* aTimer,
+ void* aCache) {
+ gfxFontCache* cache = static_cast<gfxFontCache*>(aCache);
+ cache->AgeCachedWords();
+}
+
+void gfxFontCache::AgeCachedWords() {
+ bool allEmpty = true;
+ {
+ MutexAutoLock lock(mMutex);
+ for (const auto& entry : mFonts) {
+ allEmpty = entry.mFont->AgeCachedWords() && allEmpty;
+ }
+ }
+ if (allEmpty) {
+ PauseWordCacheExpirationTimer();
+ }
+}
+
+void gfxFontCache::FlushShapedWordCaches() {
+ {
+ MutexAutoLock lock(mMutex);
+ for (const auto& entry : mFonts) {
+ entry.mFont->ClearCachedWords();
+ }
+ }
+ PauseWordCacheExpirationTimer();
+}
+
+void gfxFontCache::NotifyGlyphsChanged() {
+ MutexAutoLock lock(mMutex);
+ for (const auto& entry : mFonts) {
+ entry.mFont->NotifyGlyphsChanged();
+ }
+}
+
+void gfxFontCache::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const {
+ // TODO: add the overhead of the expiration tracker (generation arrays)
+
+ MutexAutoLock lock(*const_cast<Mutex*>(&mMutex));
+ aSizes->mFontInstances += mFonts.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (const auto& entry : mFonts) {
+ entry.mFont->AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+ }
+}
+
+void gfxFontCache::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const {
+ aSizes->mFontInstances += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
+
+#define MAX_SSXX_VALUE 99
+#define MAX_CVXX_VALUE 99
+
+static void LookupAlternateValues(const gfxFontFeatureValueSet& aFeatureLookup,
+ const nsACString& aFamily,
+ const StyleVariantAlternates& aAlternates,
+ nsTArray<gfxFontFeature>& aFontFeatures) {
+ using Tag = StyleVariantAlternates::Tag;
+
+ // historical-forms gets handled in nsFont::AddFontFeaturesToStyle.
+ if (aAlternates.IsHistoricalForms()) {
+ return;
+ }
+
+ gfxFontFeature feature;
+ if (aAlternates.IsCharacterVariant()) {
+ for (auto& ident : aAlternates.AsCharacterVariant().AsSpan()) {
+ Span<const uint32_t> values = aFeatureLookup.GetFontFeatureValuesFor(
+ aFamily, NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT,
+ ident.AsAtom());
+ // nothing defined, skip
+ if (values.IsEmpty()) {
+ continue;
+ }
+ NS_ASSERTION(values.Length() <= 2,
+ "too many values allowed for character-variant");
+ // character-variant(12 3) ==> 'cv12' = 3
+ uint32_t nn = values[0];
+ // ignore values greater than 99
+ if (nn == 0 || nn > MAX_CVXX_VALUE) {
+ continue;
+ }
+ feature.mValue = values.Length() > 1 ? values[1] : 1;
+ feature.mTag = HB_TAG('c', 'v', ('0' + nn / 10), ('0' + nn % 10));
+ aFontFeatures.AppendElement(feature);
+ }
+ return;
+ }
+
+ if (aAlternates.IsStyleset()) {
+ for (auto& ident : aAlternates.AsStyleset().AsSpan()) {
+ Span<const uint32_t> values = aFeatureLookup.GetFontFeatureValuesFor(
+ aFamily, NS_FONT_VARIANT_ALTERNATES_STYLESET, ident.AsAtom());
+
+ // styleset(1 2 7) ==> 'ss01' = 1, 'ss02' = 1, 'ss07' = 1
+ feature.mValue = 1;
+ for (uint32_t nn : values) {
+ if (nn == 0 || nn > MAX_SSXX_VALUE) {
+ continue;
+ }
+ feature.mTag = HB_TAG('s', 's', ('0' + nn / 10), ('0' + nn % 10));
+ aFontFeatures.AppendElement(feature);
+ }
+ }
+ return;
+ }
+
+ uint32_t constant = 0;
+ nsAtom* name = nullptr;
+ switch (aAlternates.tag) {
+ case Tag::Swash:
+ constant = NS_FONT_VARIANT_ALTERNATES_SWASH;
+ name = aAlternates.AsSwash().AsAtom();
+ break;
+ case Tag::Stylistic:
+ constant = NS_FONT_VARIANT_ALTERNATES_STYLISTIC;
+ name = aAlternates.AsStylistic().AsAtom();
+ break;
+ case Tag::Ornaments:
+ constant = NS_FONT_VARIANT_ALTERNATES_ORNAMENTS;
+ name = aAlternates.AsOrnaments().AsAtom();
+ break;
+ case Tag::Annotation:
+ constant = NS_FONT_VARIANT_ALTERNATES_ANNOTATION;
+ name = aAlternates.AsAnnotation().AsAtom();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown font-variant-alternates value!");
+ return;
+ }
+
+ Span<const uint32_t> values =
+ aFeatureLookup.GetFontFeatureValuesFor(aFamily, constant, name);
+ if (values.IsEmpty()) {
+ return;
+ }
+ MOZ_ASSERT(values.Length() == 1,
+ "too many values for font-specific font-variant-alternates");
+
+ feature.mValue = values[0];
+ switch (aAlternates.tag) {
+ case Tag::Swash: // swsh, cswh
+ feature.mTag = HB_TAG('s', 'w', 's', 'h');
+ aFontFeatures.AppendElement(feature);
+ feature.mTag = HB_TAG('c', 's', 'w', 'h');
+ break;
+ case Tag::Stylistic: // salt
+ feature.mTag = HB_TAG('s', 'a', 'l', 't');
+ break;
+ case Tag::Ornaments: // ornm
+ feature.mTag = HB_TAG('o', 'r', 'n', 'm');
+ break;
+ case Tag::Annotation: // nalt
+ feature.mTag = HB_TAG('n', 'a', 'l', 't');
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("how?");
+ return;
+ }
+ aFontFeatures.AppendElement(feature);
+}
+
+/* static */
+void gfxFontShaper::MergeFontFeatures(
+ const gfxFontStyle* aStyle, const nsTArray<gfxFontFeature>& aFontFeatures,
+ bool aDisableLigatures, const nsACString& aFamilyName, bool aAddSmallCaps,
+ void (*aHandleFeature)(const uint32_t&, uint32_t&, void*),
+ void* aHandleFeatureData) {
+ const nsTArray<gfxFontFeature>& styleRuleFeatures = aStyle->featureSettings;
+
+ // Bail immediately if nothing to do, which is the common case.
+ if (styleRuleFeatures.IsEmpty() && aFontFeatures.IsEmpty() &&
+ !aDisableLigatures &&
+ aStyle->variantCaps == NS_FONT_VARIANT_CAPS_NORMAL &&
+ aStyle->variantSubSuper == NS_FONT_VARIANT_POSITION_NORMAL &&
+ aStyle->variantAlternates.IsEmpty()) {
+ return;
+ }
+
+ nsTHashMap<nsUint32HashKey, uint32_t> mergedFeatures;
+
+ // add feature values from font
+ for (const gfxFontFeature& feature : aFontFeatures) {
+ mergedFeatures.InsertOrUpdate(feature.mTag, feature.mValue);
+ }
+
+ // font-variant-caps - handled here due to the need for fallback handling
+ // petite caps cases can fallback to appropriate smallcaps
+ uint32_t variantCaps = aStyle->variantCaps;
+ switch (variantCaps) {
+ case NS_FONT_VARIANT_CAPS_NORMAL:
+ break;
+
+ case NS_FONT_VARIANT_CAPS_ALLSMALL:
+ mergedFeatures.InsertOrUpdate(HB_TAG('c', '2', 's', 'c'), 1);
+ // fall through to the small-caps case
+ [[fallthrough]];
+
+ case NS_FONT_VARIANT_CAPS_SMALLCAPS:
+ mergedFeatures.InsertOrUpdate(HB_TAG('s', 'm', 'c', 'p'), 1);
+ break;
+
+ case NS_FONT_VARIANT_CAPS_ALLPETITE:
+ mergedFeatures.InsertOrUpdate(aAddSmallCaps ? HB_TAG('c', '2', 's', 'c')
+ : HB_TAG('c', '2', 'p', 'c'),
+ 1);
+ // fall through to the petite-caps case
+ [[fallthrough]];
+
+ case NS_FONT_VARIANT_CAPS_PETITECAPS:
+ mergedFeatures.InsertOrUpdate(aAddSmallCaps ? HB_TAG('s', 'm', 'c', 'p')
+ : HB_TAG('p', 'c', 'a', 'p'),
+ 1);
+ break;
+
+ case NS_FONT_VARIANT_CAPS_TITLING:
+ mergedFeatures.InsertOrUpdate(HB_TAG('t', 'i', 't', 'l'), 1);
+ break;
+
+ case NS_FONT_VARIANT_CAPS_UNICASE:
+ mergedFeatures.InsertOrUpdate(HB_TAG('u', 'n', 'i', 'c'), 1);
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected variantCaps");
+ break;
+ }
+
+ // font-variant-position - handled here due to the need for fallback
+ switch (aStyle->variantSubSuper) {
+ case NS_FONT_VARIANT_POSITION_NORMAL:
+ break;
+ case NS_FONT_VARIANT_POSITION_SUPER:
+ mergedFeatures.InsertOrUpdate(HB_TAG('s', 'u', 'p', 's'), 1);
+ break;
+ case NS_FONT_VARIANT_POSITION_SUB:
+ mergedFeatures.InsertOrUpdate(HB_TAG('s', 'u', 'b', 's'), 1);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected variantSubSuper");
+ break;
+ }
+
+ // add font-specific feature values from style rules
+ if (aStyle->featureValueLookup && !aStyle->variantAlternates.IsEmpty()) {
+ AutoTArray<gfxFontFeature, 4> featureList;
+
+ // insert list of alternate feature settings
+ for (auto& alternate : aStyle->variantAlternates.AsSpan()) {
+ LookupAlternateValues(*aStyle->featureValueLookup, aFamilyName, alternate,
+ featureList);
+ }
+
+ for (const gfxFontFeature& feature : featureList) {
+ mergedFeatures.InsertOrUpdate(feature.mTag, feature.mValue);
+ }
+ }
+
+ auto disableOptionalLigatures = [&]() -> void {
+ mergedFeatures.InsertOrUpdate(HB_TAG('l', 'i', 'g', 'a'), 0);
+ mergedFeatures.InsertOrUpdate(HB_TAG('c', 'l', 'i', 'g'), 0);
+ mergedFeatures.InsertOrUpdate(HB_TAG('d', 'l', 'i', 'g'), 0);
+ mergedFeatures.InsertOrUpdate(HB_TAG('h', 'l', 'i', 'g'), 0);
+ };
+
+ // Add features that are already resolved to tags & values in the style.
+ if (styleRuleFeatures.IsEmpty()) {
+ // Disable optional ligatures if non-zero letter-spacing is in effect.
+ if (aDisableLigatures) {
+ disableOptionalLigatures();
+ }
+ } else {
+ for (const gfxFontFeature& feature : styleRuleFeatures) {
+ // A dummy feature (0,0) is used as a sentinel to separate features
+ // originating from font-variant-* or other high-level properties from
+ // those directly specified as font-feature-settings. The high-level
+ // features may be overridden by aDisableLigatures, while low-level
+ // features specified directly as tags will come last and therefore
+ // take precedence over everything else.
+ if (feature.mTag) {
+ mergedFeatures.InsertOrUpdate(feature.mTag, feature.mValue);
+ } else if (aDisableLigatures) {
+ // Handle ligature-disabling setting at the boundary between high-
+ // and low-level features.
+ disableOptionalLigatures();
+ }
+ }
+ }
+
+ if (mergedFeatures.Count() != 0) {
+ for (auto iter = mergedFeatures.Iter(); !iter.Done(); iter.Next()) {
+ aHandleFeature(iter.Key(), iter.Data(), aHandleFeatureData);
+ }
+ }
+}
+
+void gfxShapedText::SetupClusterBoundaries(uint32_t aOffset,
+ const char16_t* aString,
+ uint32_t aLength) {
+ if (aLength == 0) {
+ return;
+ }
+
+ CompressedGlyph* const glyphs = GetCharacterGlyphs() + aOffset;
+ CompressedGlyph extendCluster = CompressedGlyph::MakeComplex(false, true);
+
+ // GraphemeClusterBreakIteratorUtf16 won't be able to tell us if the string
+ // _begins_ with a cluster-extender, so we handle that here
+ uint32_t ch = aString[0];
+ if (aLength > 1 && NS_IS_SURROGATE_PAIR(ch, aString[1])) {
+ ch = SURROGATE_TO_UCS4(ch, aString[1]);
+ }
+ if (IsClusterExtender(ch)) {
+ glyphs[0] = extendCluster;
+ }
+
+ intl::GraphemeClusterBreakIteratorUtf16 iter(
+ Span<const char16_t>(aString, aLength));
+ uint32_t pos = 0;
+
+ const char16_t kIdeographicSpace = 0x3000;
+ // Special case for Bengali: although Virama normally clusters with the
+ // preceding letter, we *also* want to cluster it with a following Ya
+ // so that when the Virama+Ya form ya-phala, this is not separated from the
+ // preceding letter by any letter-spacing or justification.
+ const char16_t kBengaliVirama = 0x09CD;
+ const char16_t kBengaliYa = 0x09AF;
+ while (pos < aLength) {
+ const char16_t ch = aString[pos];
+ if (ch == char16_t(' ') || ch == kIdeographicSpace) {
+ glyphs[pos].SetIsSpace();
+ } else if (ch == kBengaliYa) {
+ // Unless we're at the start, check for a preceding virama.
+ if (pos > 0 && aString[pos - 1] == kBengaliVirama) {
+ glyphs[pos] = extendCluster;
+ }
+ }
+ // advance iter to the next cluster-start (or end of text)
+ const uint32_t nextPos = *iter.Next();
+ // step past the first char of the cluster
+ ++pos;
+ // mark all the rest as cluster-continuations
+ for (; pos < nextPos; ++pos) {
+ glyphs[pos] = extendCluster;
+ }
+ }
+}
+
+void gfxShapedText::SetupClusterBoundaries(uint32_t aOffset,
+ const uint8_t* aString,
+ uint32_t aLength) {
+ CompressedGlyph* glyphs = GetCharacterGlyphs() + aOffset;
+ const uint8_t* limit = aString + aLength;
+
+ while (aString < limit) {
+ if (*aString == uint8_t(' ')) {
+ glyphs->SetIsSpace();
+ }
+ aString++;
+ glyphs++;
+ }
+}
+
+gfxShapedText::DetailedGlyph* gfxShapedText::AllocateDetailedGlyphs(
+ uint32_t aIndex, uint32_t aCount) {
+ NS_ASSERTION(aIndex < GetLength(), "Index out of range");
+
+ if (!mDetailedGlyphs) {
+ mDetailedGlyphs = MakeUnique<DetailedGlyphStore>();
+ }
+
+ return mDetailedGlyphs->Allocate(aIndex, aCount);
+}
+
+void gfxShapedText::SetDetailedGlyphs(uint32_t aIndex, uint32_t aGlyphCount,
+ const DetailedGlyph* aGlyphs) {
+ CompressedGlyph& g = GetCharacterGlyphs()[aIndex];
+
+ MOZ_ASSERT(aIndex > 0 || g.IsLigatureGroupStart(),
+ "First character can't be a ligature continuation!");
+
+ if (aGlyphCount > 0) {
+ DetailedGlyph* details = AllocateDetailedGlyphs(aIndex, aGlyphCount);
+ memcpy(details, aGlyphs, sizeof(DetailedGlyph) * aGlyphCount);
+ }
+
+ g.SetGlyphCount(aGlyphCount);
+}
+
+#define ZWNJ 0x200C
+#define ZWJ 0x200D
+static inline bool IsIgnorable(uint32_t aChar) {
+ return (IsDefaultIgnorable(aChar)) || aChar == ZWNJ || aChar == ZWJ;
+}
+
+void gfxShapedText::SetMissingGlyph(uint32_t aIndex, uint32_t aChar,
+ gfxFont* aFont) {
+ CompressedGlyph& g = GetCharacterGlyphs()[aIndex];
+ uint8_t category = GetGeneralCategory(aChar);
+ if (category >= HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK &&
+ category <= HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) {
+ g.SetComplex(false, true);
+ }
+
+ // Leaving advance as zero will prevent drawing the hexbox for ignorables.
+ int32_t advance = 0;
+ if (!IsIgnorable(aChar)) {
+ gfxFloat width =
+ std::max(aFont->GetMetrics(nsFontMetrics::eHorizontal).aveCharWidth,
+ gfxFloat(gfxFontMissingGlyphs::GetDesiredMinWidth(
+ aChar, mAppUnitsPerDevUnit)));
+ advance = int32_t(width * mAppUnitsPerDevUnit);
+ }
+ DetailedGlyph detail = {aChar, advance, gfx::Point()};
+ SetDetailedGlyphs(aIndex, 1, &detail);
+ g.SetMissing();
+}
+
+bool gfxShapedText::FilterIfIgnorable(uint32_t aIndex, uint32_t aCh) {
+ if (IsIgnorable(aCh)) {
+ // There are a few default-ignorables of Letter category (currently,
+ // just the Hangul filler characters) that we'd better not discard
+ // if they're followed by additional characters in the same cluster.
+ // Some fonts use them to carry the width of a whole cluster of
+ // combining jamos; see bug 1238243.
+ auto* charGlyphs = GetCharacterGlyphs();
+ if (GetGenCategory(aCh) == nsUGenCategory::kLetter &&
+ aIndex + 1 < GetLength() && !charGlyphs[aIndex + 1].IsClusterStart()) {
+ return false;
+ }
+ // A compressedGlyph that is set to MISSING but has no DetailedGlyphs list
+ // will be zero-width/invisible, which is what we want here.
+ CompressedGlyph& g = charGlyphs[aIndex];
+ g.SetComplex(g.IsClusterStart(), g.IsLigatureGroupStart()).SetMissing();
+ return true;
+ }
+ return false;
+}
+
+void gfxShapedText::AdjustAdvancesForSyntheticBold(float aSynBoldOffset,
+ uint32_t aOffset,
+ uint32_t aLength) {
+ int32_t synAppUnitOffset = aSynBoldOffset * mAppUnitsPerDevUnit;
+ CompressedGlyph* charGlyphs = GetCharacterGlyphs();
+ for (uint32_t i = aOffset; i < aOffset + aLength; ++i) {
+ CompressedGlyph* glyphData = charGlyphs + i;
+ if (glyphData->IsSimpleGlyph()) {
+ // simple glyphs ==> just add the advance
+ int32_t advance = glyphData->GetSimpleAdvance();
+ if (advance > 0) {
+ advance += synAppUnitOffset;
+ if (CompressedGlyph::IsSimpleAdvance(advance)) {
+ glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph());
+ } else {
+ // rare case, tested by making this the default
+ uint32_t glyphIndex = glyphData->GetSimpleGlyph();
+ // convert the simple CompressedGlyph to an empty complex record
+ glyphData->SetComplex(true, true);
+ // then set its details (glyph ID with its new advance)
+ DetailedGlyph detail = {glyphIndex, advance, gfx::Point()};
+ SetDetailedGlyphs(i, 1, &detail);
+ }
+ }
+ } else {
+ // complex glyphs ==> add offset at cluster/ligature boundaries
+ uint32_t detailedLength = glyphData->GetGlyphCount();
+ if (detailedLength) {
+ DetailedGlyph* details = GetDetailedGlyphs(i);
+ if (!details) {
+ continue;
+ }
+ if (IsRightToLeft()) {
+ if (details[0].mAdvance > 0) {
+ details[0].mAdvance += synAppUnitOffset;
+ }
+ } else {
+ if (details[detailedLength - 1].mAdvance > 0) {
+ details[detailedLength - 1].mAdvance += synAppUnitOffset;
+ }
+ }
+ }
+ }
+ }
+}
+
+float gfxFont::AngleForSyntheticOblique() const {
+ // First check conditions that mean no synthetic slant should be used:
+ if (mStyle.style == FontSlantStyle::NORMAL) {
+ return 0.0f; // Requested style is 'normal'.
+ }
+ if (!mStyle.allowSyntheticStyle) {
+ return 0.0f; // Synthetic obliquing is disabled.
+ }
+ if (!mFontEntry->MayUseSyntheticSlant()) {
+ return 0.0f; // The resource supports "real" slant, so don't synthesize.
+ }
+
+ // If style calls for italic, and face doesn't support it, use default
+ // oblique angle as a simulation.
+ if (mStyle.style.IsItalic()) {
+ return mFontEntry->SupportsItalic()
+ ? 0.0f
+ : FontSlantStyle::DEFAULT_OBLIQUE_DEGREES;
+ }
+
+ // OK, we're going to use synthetic oblique: return the requested angle.
+ return mStyle.style.ObliqueAngle();
+}
+
+float gfxFont::SkewForSyntheticOblique() const {
+ // Precomputed value of tan(kDefaultAngle), the default italic/oblique slant;
+ // avoids calling tan() at runtime except for custom oblique values.
+ static const float kTanDefaultAngle =
+ tan(FontSlantStyle::DEFAULT_OBLIQUE_DEGREES * (M_PI / 180.0));
+
+ float angle = AngleForSyntheticOblique();
+ if (angle == 0.0f) {
+ return 0.0f;
+ } else if (angle == FontSlantStyle::DEFAULT_OBLIQUE_DEGREES) {
+ return kTanDefaultAngle;
+ } else {
+ return tan(angle * (M_PI / 180.0));
+ }
+}
+
+void gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther,
+ bool aOtherIsOnLeft) {
+ mAscent = std::max(mAscent, aOther.mAscent);
+ mDescent = std::max(mDescent, aOther.mDescent);
+ if (aOtherIsOnLeft) {
+ mBoundingBox = (mBoundingBox + gfxPoint(aOther.mAdvanceWidth, 0))
+ .Union(aOther.mBoundingBox);
+ } else {
+ mBoundingBox =
+ mBoundingBox.Union(aOther.mBoundingBox + gfxPoint(mAdvanceWidth, 0));
+ }
+ mAdvanceWidth += aOther.mAdvanceWidth;
+}
+
+gfxFont::gfxFont(const RefPtr<UnscaledFont>& aUnscaledFont,
+ gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle,
+ AntialiasOption anAAOption)
+ : mFontEntry(aFontEntry),
+ mLock("gfxFont lock"),
+ mUnscaledFont(aUnscaledFont),
+ mStyle(*aFontStyle),
+ mAdjustedSize(-1.0), // negative to indicate "not yet initialized"
+ mFUnitsConvFactor(-1.0f), // negative to indicate "not yet initialized"
+ mAntialiasOption(anAAOption),
+ mIsValid(true),
+ mApplySyntheticBold(false),
+ mKerningEnabled(false),
+ mMathInitialized(false) {
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+ ++gFontCount;
+#endif
+
+ if (MOZ_UNLIKELY(StaticPrefs::gfx_text_disable_aa_AtStartup())) {
+ mAntialiasOption = kAntialiasNone;
+ }
+
+ // Turn off AA for Ahem for testing purposes when requested.
+ if (MOZ_UNLIKELY(StaticPrefs::gfx_font_rendering_ahem_antialias_none() &&
+ mFontEntry->FamilyName().EqualsLiteral("Ahem"))) {
+ mAntialiasOption = kAntialiasNone;
+ }
+
+ mKerningSet = HasFeatureSet(HB_TAG('k', 'e', 'r', 'n'), mKerningEnabled);
+}
+
+gfxFont::~gfxFont() {
+ mFontEntry->NotifyFontDestroyed(this);
+
+ // Delete objects owned through atomic pointers. (Some of these may be null,
+ // but that's OK.)
+ delete mVerticalMetrics.exchange(nullptr);
+ delete mHarfBuzzShaper.exchange(nullptr);
+ delete mGraphiteShaper.exchange(nullptr);
+ delete mMathTable.exchange(nullptr);
+ delete mNonAAFont.exchange(nullptr);
+
+ if (auto* scaledFont = mAzureScaledFont.exchange(nullptr)) {
+ scaledFont->Release();
+ }
+
+ if (mGlyphChangeObservers) {
+ for (const auto& key : *mGlyphChangeObservers) {
+ key->ForgetFont();
+ }
+ }
+}
+
+// Work out whether cairo will snap inter-glyph spacing to pixels.
+//
+// Layout does not align text to pixel boundaries, so, with font drawing
+// backends that snap glyph positions to pixels, it is important that
+// inter-glyph spacing within words is always an integer number of pixels.
+// This ensures that the drawing backend snaps all of the word's glyphs in the
+// same direction and so inter-glyph spacing remains the same.
+//
+gfxFont::RoundingFlags gfxFont::GetRoundOffsetsToPixels(
+ DrawTarget* aDrawTarget) {
+ // Could do something fancy here for ScaleFactors of
+ // AxisAlignedTransforms, but we leave things simple.
+ // Not much point rounding if a matrix will mess things up anyway.
+ // Also check if the font already knows hint metrics is off...
+ if (aDrawTarget->GetTransform().HasNonTranslation() || !ShouldHintMetrics()) {
+ return RoundingFlags(0);
+ }
+
+ cairo_t* cr = static_cast<cairo_t*>(
+ aDrawTarget->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT));
+ if (cr) {
+ cairo_surface_t* target = cairo_get_target(cr);
+
+ // Check whether the cairo surface's font options hint metrics.
+ cairo_font_options_t* fontOptions = cairo_font_options_create();
+ cairo_surface_get_font_options(target, fontOptions);
+ cairo_hint_metrics_t hintMetrics =
+ cairo_font_options_get_hint_metrics(fontOptions);
+ cairo_font_options_destroy(fontOptions);
+
+ switch (hintMetrics) {
+ case CAIRO_HINT_METRICS_OFF:
+ return RoundingFlags(0);
+ case CAIRO_HINT_METRICS_ON:
+ return RoundingFlags::kRoundX | RoundingFlags::kRoundY;
+ default:
+ break;
+ }
+ }
+
+ if (ShouldRoundXOffset(cr)) {
+ return RoundingFlags::kRoundX | RoundingFlags::kRoundY;
+ } else {
+ return RoundingFlags::kRoundY;
+ }
+}
+
+gfxHarfBuzzShaper* gfxFont::GetHarfBuzzShaper() {
+ if (!mHarfBuzzShaper) {
+ auto* shaper = new gfxHarfBuzzShaper(this);
+ shaper->Initialize();
+ if (!mHarfBuzzShaper.compareExchange(nullptr, shaper)) {
+ delete shaper;
+ }
+ }
+ gfxHarfBuzzShaper* shaper = mHarfBuzzShaper;
+ return shaper->IsInitialized() ? shaper : nullptr;
+}
+
+gfxFloat gfxFont::GetGlyphAdvance(uint16_t aGID, bool aVertical) {
+ if (!aVertical && ProvidesGlyphWidths()) {
+ return GetGlyphWidth(aGID) / 65536.0;
+ }
+ if (mFUnitsConvFactor < 0.0f) {
+ // Metrics haven't been initialized; lock while we do that.
+ AutoWriteLock lock(mLock);
+ if (mFUnitsConvFactor < 0.0f) {
+ GetMetrics(nsFontMetrics::eHorizontal);
+ }
+ }
+ NS_ASSERTION(mFUnitsConvFactor >= 0.0f,
+ "missing font unit conversion factor");
+ if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) {
+ if (aVertical) {
+ // Note that GetGlyphVAdvance may return -1 to indicate it was unable
+ // to retrieve vertical metrics; in that case we fall back to the
+ // aveCharWidth value as a default advance.
+ int32_t advance = shaper->GetGlyphVAdvance(aGID);
+ if (advance < 0) {
+ return GetMetrics(nsFontMetrics::eVertical).aveCharWidth;
+ }
+ return advance / 65536.0;
+ }
+ return shaper->GetGlyphHAdvance(aGID) / 65536.0;
+ }
+ return 0.0;
+}
+
+gfxFloat gfxFont::GetCharAdvance(uint32_t aUnicode, bool aVertical) {
+ uint32_t gid = 0;
+ if (ProvidesGetGlyph()) {
+ gid = GetGlyph(aUnicode, 0);
+ } else {
+ if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) {
+ gid = shaper->GetNominalGlyph(aUnicode);
+ }
+ }
+ if (!gid) {
+ return -1.0;
+ }
+ return GetGlyphAdvance(gid, aVertical);
+}
+
+static void CollectLookupsByFeature(hb_face_t* aFace, hb_tag_t aTableTag,
+ uint32_t aFeatureIndex,
+ hb_set_t* aLookups) {
+ uint32_t lookups[32];
+ uint32_t i, len, offset;
+
+ offset = 0;
+ do {
+ len = ArrayLength(lookups);
+ hb_ot_layout_feature_get_lookups(aFace, aTableTag, aFeatureIndex, offset,
+ &len, lookups);
+ for (i = 0; i < len; i++) {
+ hb_set_add(aLookups, lookups[i]);
+ }
+ offset += len;
+ } while (len == ArrayLength(lookups));
+}
+
+static void CollectLookupsByLanguage(
+ hb_face_t* aFace, hb_tag_t aTableTag,
+ const nsTHashSet<uint32_t>& aSpecificFeatures, hb_set_t* aOtherLookups,
+ hb_set_t* aSpecificFeatureLookups, uint32_t aScriptIndex,
+ uint32_t aLangIndex) {
+ uint32_t reqFeatureIndex;
+ if (hb_ot_layout_language_get_required_feature_index(
+ aFace, aTableTag, aScriptIndex, aLangIndex, &reqFeatureIndex)) {
+ CollectLookupsByFeature(aFace, aTableTag, reqFeatureIndex, aOtherLookups);
+ }
+
+ uint32_t featureIndexes[32];
+ uint32_t i, len, offset;
+
+ offset = 0;
+ do {
+ len = ArrayLength(featureIndexes);
+ hb_ot_layout_language_get_feature_indexes(aFace, aTableTag, aScriptIndex,
+ aLangIndex, offset, &len,
+ featureIndexes);
+
+ for (i = 0; i < len; i++) {
+ uint32_t featureIndex = featureIndexes[i];
+
+ // get the feature tag
+ hb_tag_t featureTag;
+ uint32_t tagLen = 1;
+ hb_ot_layout_language_get_feature_tags(aFace, aTableTag, aScriptIndex,
+ aLangIndex, offset + i, &tagLen,
+ &featureTag);
+
+ // collect lookups
+ hb_set_t* lookups = aSpecificFeatures.Contains(featureTag)
+ ? aSpecificFeatureLookups
+ : aOtherLookups;
+ CollectLookupsByFeature(aFace, aTableTag, featureIndex, lookups);
+ }
+ offset += len;
+ } while (len == ArrayLength(featureIndexes));
+}
+
+static bool HasLookupRuleWithGlyphByScript(
+ hb_face_t* aFace, hb_tag_t aTableTag, hb_tag_t aScriptTag,
+ uint32_t aScriptIndex, uint16_t aGlyph,
+ const nsTHashSet<uint32_t>& aDefaultFeatures,
+ bool& aHasDefaultFeatureWithGlyph) {
+ uint32_t numLangs, lang;
+ hb_set_t* defaultFeatureLookups = hb_set_create();
+ hb_set_t* nonDefaultFeatureLookups = hb_set_create();
+
+ // default lang
+ CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures,
+ nonDefaultFeatureLookups, defaultFeatureLookups,
+ aScriptIndex, HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX);
+
+ // iterate over langs
+ numLangs = hb_ot_layout_script_get_language_tags(
+ aFace, aTableTag, aScriptIndex, 0, nullptr, nullptr);
+ for (lang = 0; lang < numLangs; lang++) {
+ CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures,
+ nonDefaultFeatureLookups, defaultFeatureLookups,
+ aScriptIndex, lang);
+ }
+
+ // look for the glyph among default feature lookups
+ aHasDefaultFeatureWithGlyph = false;
+ hb_set_t* glyphs = hb_set_create();
+ hb_codepoint_t index = -1;
+ while (hb_set_next(defaultFeatureLookups, &index)) {
+ hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs,
+ glyphs, nullptr);
+ if (hb_set_has(glyphs, aGlyph)) {
+ aHasDefaultFeatureWithGlyph = true;
+ break;
+ }
+ }
+
+ // look for the glyph among non-default feature lookups
+ // if no default feature lookups contained spaces
+ bool hasNonDefaultFeatureWithGlyph = false;
+ if (!aHasDefaultFeatureWithGlyph) {
+ hb_set_clear(glyphs);
+ index = -1;
+ while (hb_set_next(nonDefaultFeatureLookups, &index)) {
+ hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs,
+ glyphs, glyphs, nullptr);
+ if (hb_set_has(glyphs, aGlyph)) {
+ hasNonDefaultFeatureWithGlyph = true;
+ break;
+ }
+ }
+ }
+
+ hb_set_destroy(glyphs);
+ hb_set_destroy(defaultFeatureLookups);
+ hb_set_destroy(nonDefaultFeatureLookups);
+
+ return aHasDefaultFeatureWithGlyph || hasNonDefaultFeatureWithGlyph;
+}
+
+static void HasLookupRuleWithGlyph(hb_face_t* aFace, hb_tag_t aTableTag,
+ bool& aHasGlyph, hb_tag_t aSpecificFeature,
+ bool& aHasGlyphSpecific, uint16_t aGlyph) {
+ // iterate over the scripts in the font
+ uint32_t numScripts, numLangs, script, lang;
+ hb_set_t* otherLookups = hb_set_create();
+ hb_set_t* specificFeatureLookups = hb_set_create();
+ nsTHashSet<uint32_t> specificFeature(1);
+
+ specificFeature.Insert(aSpecificFeature);
+
+ numScripts =
+ hb_ot_layout_table_get_script_tags(aFace, aTableTag, 0, nullptr, nullptr);
+
+ for (script = 0; script < numScripts; script++) {
+ // default lang
+ CollectLookupsByLanguage(aFace, aTableTag, specificFeature, otherLookups,
+ specificFeatureLookups, script,
+ HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX);
+
+ // iterate over langs
+ numLangs = hb_ot_layout_script_get_language_tags(
+ aFace, HB_OT_TAG_GPOS, script, 0, nullptr, nullptr);
+ for (lang = 0; lang < numLangs; lang++) {
+ CollectLookupsByLanguage(aFace, aTableTag, specificFeature, otherLookups,
+ specificFeatureLookups, script, lang);
+ }
+ }
+
+ // look for the glyph among non-specific feature lookups
+ hb_set_t* glyphs = hb_set_create();
+ hb_codepoint_t index = -1;
+ while (hb_set_next(otherLookups, &index)) {
+ hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs,
+ glyphs, nullptr);
+ if (hb_set_has(glyphs, aGlyph)) {
+ aHasGlyph = true;
+ break;
+ }
+ }
+
+ // look for the glyph among specific feature lookups
+ hb_set_clear(glyphs);
+ index = -1;
+ while (hb_set_next(specificFeatureLookups, &index)) {
+ hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs,
+ glyphs, nullptr);
+ if (hb_set_has(glyphs, aGlyph)) {
+ aHasGlyphSpecific = true;
+ break;
+ }
+ }
+
+ hb_set_destroy(glyphs);
+ hb_set_destroy(specificFeatureLookups);
+ hb_set_destroy(otherLookups);
+}
+
+Atomic<nsTHashMap<nsUint32HashKey, intl::Script>*> gfxFont::sScriptTagToCode;
+Atomic<nsTHashSet<uint32_t>*> gfxFont::sDefaultFeatures;
+
+static inline bool HasSubstitution(uint32_t* aBitVector, intl::Script aScript) {
+ return (aBitVector[static_cast<uint32_t>(aScript) >> 5] &
+ (1 << (static_cast<uint32_t>(aScript) & 0x1f))) != 0;
+}
+
+// union of all default substitution features across scripts
+static const hb_tag_t defaultFeatures[] = {
+ HB_TAG('a', 'b', 'v', 'f'), HB_TAG('a', 'b', 'v', 's'),
+ HB_TAG('a', 'k', 'h', 'n'), HB_TAG('b', 'l', 'w', 'f'),
+ HB_TAG('b', 'l', 'w', 's'), HB_TAG('c', 'a', 'l', 't'),
+ HB_TAG('c', 'c', 'm', 'p'), HB_TAG('c', 'f', 'a', 'r'),
+ HB_TAG('c', 'j', 'c', 't'), HB_TAG('c', 'l', 'i', 'g'),
+ HB_TAG('f', 'i', 'n', '2'), HB_TAG('f', 'i', 'n', '3'),
+ HB_TAG('f', 'i', 'n', 'a'), HB_TAG('h', 'a', 'l', 'f'),
+ HB_TAG('h', 'a', 'l', 'n'), HB_TAG('i', 'n', 'i', 't'),
+ HB_TAG('i', 's', 'o', 'l'), HB_TAG('l', 'i', 'g', 'a'),
+ HB_TAG('l', 'j', 'm', 'o'), HB_TAG('l', 'o', 'c', 'l'),
+ HB_TAG('l', 't', 'r', 'a'), HB_TAG('l', 't', 'r', 'm'),
+ HB_TAG('m', 'e', 'd', '2'), HB_TAG('m', 'e', 'd', 'i'),
+ HB_TAG('m', 's', 'e', 't'), HB_TAG('n', 'u', 'k', 't'),
+ HB_TAG('p', 'r', 'e', 'f'), HB_TAG('p', 'r', 'e', 's'),
+ HB_TAG('p', 's', 't', 'f'), HB_TAG('p', 's', 't', 's'),
+ HB_TAG('r', 'c', 'l', 't'), HB_TAG('r', 'l', 'i', 'g'),
+ HB_TAG('r', 'k', 'r', 'f'), HB_TAG('r', 'p', 'h', 'f'),
+ HB_TAG('r', 't', 'l', 'a'), HB_TAG('r', 't', 'l', 'm'),
+ HB_TAG('t', 'j', 'm', 'o'), HB_TAG('v', 'a', 't', 'u'),
+ HB_TAG('v', 'e', 'r', 't'), HB_TAG('v', 'j', 'm', 'o')};
+
+void gfxFont::CheckForFeaturesInvolvingSpace() const {
+ gfxFontEntry::SpaceFeatures flags = gfxFontEntry::SpaceFeatures::None;
+
+ auto setFlags =
+ MakeScopeExit([&]() { mFontEntry->mHasSpaceFeatures = flags; });
+
+ bool log = LOG_FONTINIT_ENABLED();
+ TimeStamp start;
+ if (MOZ_UNLIKELY(log)) {
+ start = TimeStamp::Now();
+ }
+
+ uint32_t spaceGlyph = GetSpaceGlyph();
+ if (!spaceGlyph) {
+ return;
+ }
+
+ auto face(GetFontEntry()->GetHBFace());
+
+ // GSUB lookups - examine per script
+ if (hb_ot_layout_has_substitution(face)) {
+ // Get the script ==> code hashtable, creating it on first use.
+ nsTHashMap<nsUint32HashKey, Script>* tagToCode = sScriptTagToCode;
+ if (!tagToCode) {
+ tagToCode = new nsTHashMap<nsUint32HashKey, Script>(
+ size_t(Script::NUM_SCRIPT_CODES));
+ tagToCode->InsertOrUpdate(HB_TAG('D', 'F', 'L', 'T'), Script::COMMON);
+ // Ensure that we don't try to look at script codes beyond what the
+ // current version of ICU (at runtime -- in case of system ICU)
+ // knows about.
+ Script scriptCount = Script(
+ std::min<int>(intl::UnicodeProperties::GetMaxNumberOfScripts() + 1,
+ int(Script::NUM_SCRIPT_CODES)));
+ for (Script s = Script::ARABIC; s < scriptCount;
+ s = Script(static_cast<int>(s) + 1)) {
+ hb_script_t script = hb_script_t(GetScriptTagForCode(s));
+ unsigned int scriptCount = 4;
+ hb_tag_t scriptTags[4];
+ hb_ot_tags_from_script_and_language(script, HB_LANGUAGE_INVALID,
+ &scriptCount, scriptTags, nullptr,
+ nullptr);
+ for (unsigned int i = 0; i < scriptCount; i++) {
+ tagToCode->InsertOrUpdate(scriptTags[i], s);
+ }
+ }
+ if (!sScriptTagToCode.compareExchange(nullptr, tagToCode)) {
+ // We lost a race! Discard our new table and use the winner.
+ delete tagToCode;
+ tagToCode = sScriptTagToCode;
+ }
+ }
+
+ // Set up the default-features hashset on first use.
+ if (!sDefaultFeatures) {
+ uint32_t numDefaultFeatures = ArrayLength(defaultFeatures);
+ auto* set = new nsTHashSet<uint32_t>(numDefaultFeatures);
+ for (uint32_t i = 0; i < numDefaultFeatures; i++) {
+ set->Insert(defaultFeatures[i]);
+ }
+ if (!sDefaultFeatures.compareExchange(nullptr, set)) {
+ delete set;
+ }
+ }
+
+ // iterate over the scripts in the font
+ hb_tag_t scriptTags[8];
+
+ uint32_t len, offset = 0;
+ do {
+ len = ArrayLength(scriptTags);
+ hb_ot_layout_table_get_script_tags(face, HB_OT_TAG_GSUB, offset, &len,
+ scriptTags);
+ for (uint32_t i = 0; i < len; i++) {
+ bool isDefaultFeature = false;
+ Script s;
+ if (!HasLookupRuleWithGlyphByScript(
+ face, HB_OT_TAG_GSUB, scriptTags[i], offset + i, spaceGlyph,
+ *sDefaultFeatures, isDefaultFeature) ||
+ !tagToCode->Get(scriptTags[i], &s)) {
+ continue;
+ }
+ flags = flags | gfxFontEntry::SpaceFeatures::HasFeatures;
+ uint32_t index = static_cast<uint32_t>(s) >> 5;
+ uint32_t bit = static_cast<uint32_t>(s) & 0x1f;
+ if (isDefaultFeature) {
+ mFontEntry->mDefaultSubSpaceFeatures[index] |= (1 << bit);
+ } else {
+ mFontEntry->mNonDefaultSubSpaceFeatures[index] |= (1 << bit);
+ }
+ }
+ offset += len;
+ } while (len == ArrayLength(scriptTags));
+ }
+
+ // spaces in default features of default script?
+ // ==> can't use word cache, skip GPOS analysis
+ bool canUseWordCache = true;
+ if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, Script::COMMON)) {
+ canUseWordCache = false;
+ }
+
+ // GPOS lookups - distinguish kerning from non-kerning features
+ if (canUseWordCache && hb_ot_layout_has_positioning(face)) {
+ bool hasKerning = false, hasNonKerning = false;
+ HasLookupRuleWithGlyph(face, HB_OT_TAG_GPOS, hasNonKerning,
+ HB_TAG('k', 'e', 'r', 'n'), hasKerning, spaceGlyph);
+ if (hasKerning) {
+ flags |= gfxFontEntry::SpaceFeatures::HasFeatures |
+ gfxFontEntry::SpaceFeatures::Kerning;
+ }
+ if (hasNonKerning) {
+ flags |= gfxFontEntry::SpaceFeatures::HasFeatures |
+ gfxFontEntry::SpaceFeatures::NonKerning;
+ }
+ }
+
+ if (MOZ_UNLIKELY(log)) {
+ TimeDuration elapsed = TimeStamp::Now() - start;
+ LOG_FONTINIT((
+ "(fontinit-spacelookups) font: %s - "
+ "subst default: %8.8x %8.8x %8.8x %8.8x "
+ "subst non-default: %8.8x %8.8x %8.8x %8.8x "
+ "kerning: %s non-kerning: %s time: %6.3f\n",
+ mFontEntry->Name().get(), mFontEntry->mDefaultSubSpaceFeatures[3],
+ mFontEntry->mDefaultSubSpaceFeatures[2],
+ mFontEntry->mDefaultSubSpaceFeatures[1],
+ mFontEntry->mDefaultSubSpaceFeatures[0],
+ mFontEntry->mNonDefaultSubSpaceFeatures[3],
+ mFontEntry->mNonDefaultSubSpaceFeatures[2],
+ mFontEntry->mNonDefaultSubSpaceFeatures[1],
+ mFontEntry->mNonDefaultSubSpaceFeatures[0],
+ (mFontEntry->mHasSpaceFeatures & gfxFontEntry::SpaceFeatures::Kerning
+ ? "true"
+ : "false"),
+ (mFontEntry->mHasSpaceFeatures & gfxFontEntry::SpaceFeatures::NonKerning
+ ? "true"
+ : "false"),
+ elapsed.ToMilliseconds()));
+ }
+}
+
+bool gfxFont::HasSubstitutionRulesWithSpaceLookups(Script aRunScript) const {
+ NS_ASSERTION(GetFontEntry()->mHasSpaceFeatures !=
+ gfxFontEntry::SpaceFeatures::Uninitialized,
+ "need to initialize space lookup flags");
+ NS_ASSERTION(aRunScript < Script::NUM_SCRIPT_CODES, "weird script code");
+ if (aRunScript == Script::INVALID || aRunScript >= Script::NUM_SCRIPT_CODES) {
+ return false;
+ }
+
+ // default features have space lookups ==> true
+ if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, Script::COMMON) ||
+ HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, aRunScript)) {
+ return true;
+ }
+
+ // non-default features have space lookups and some type of
+ // font feature, in font or style is specified ==> true
+ if ((HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures,
+ Script::COMMON) ||
+ HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures, aRunScript)) &&
+ (!mStyle.featureSettings.IsEmpty() ||
+ !mFontEntry->mFeatureSettings.IsEmpty())) {
+ return true;
+ }
+
+ return false;
+}
+
+tainted_boolean_hint gfxFont::SpaceMayParticipateInShaping(
+ Script aRunScript) const {
+ // avoid checking fonts known not to include default space-dependent features
+ if (MOZ_UNLIKELY(mFontEntry->mSkipDefaultFeatureSpaceCheck)) {
+ if (!mKerningSet && mStyle.featureSettings.IsEmpty() &&
+ mFontEntry->mFeatureSettings.IsEmpty()) {
+ return false;
+ }
+ }
+
+ if (FontCanSupportGraphite()) {
+ if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
+ return mFontEntry->HasGraphiteSpaceContextuals();
+ }
+ }
+
+ // We record the presence of space-dependent features in the font entry
+ // so that subsequent instantiations for the same font face won't
+ // require us to re-check the tables; however, the actual check is done
+ // by gfxFont because not all font entry subclasses know how to create
+ // a harfbuzz face for introspection.
+ gfxFontEntry::SpaceFeatures flags = mFontEntry->mHasSpaceFeatures;
+ if (flags == gfxFontEntry::SpaceFeatures::Uninitialized) {
+ CheckForFeaturesInvolvingSpace();
+ flags = mFontEntry->mHasSpaceFeatures;
+ }
+
+ if (!(flags & gfxFontEntry::SpaceFeatures::HasFeatures)) {
+ return false;
+ }
+
+ // if font has substitution rules or non-kerning positioning rules
+ // that involve spaces, bypass
+ if (HasSubstitutionRulesWithSpaceLookups(aRunScript) ||
+ (flags & gfxFontEntry::SpaceFeatures::NonKerning)) {
+ return true;
+ }
+
+ // if kerning explicitly enabled/disabled via font-feature-settings or
+ // font-kerning and kerning rules use spaces, only bypass when enabled
+ if (mKerningSet && (flags & gfxFontEntry::SpaceFeatures::Kerning)) {
+ return mKerningEnabled;
+ }
+
+ return false;
+}
+
+bool gfxFont::SupportsFeature(Script aScript, uint32_t aFeatureTag) {
+ if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
+ return GetFontEntry()->SupportsGraphiteFeature(aFeatureTag);
+ }
+ return GetFontEntry()->SupportsOpenTypeFeature(aScript, aFeatureTag);
+}
+
+bool gfxFont::SupportsVariantCaps(Script aScript, uint32_t aVariantCaps,
+ bool& aFallbackToSmallCaps,
+ bool& aSyntheticLowerToSmallCaps,
+ bool& aSyntheticUpperToSmallCaps) {
+ bool ok = true; // cases without fallback are fine
+ aFallbackToSmallCaps = false;
+ aSyntheticLowerToSmallCaps = false;
+ aSyntheticUpperToSmallCaps = false;
+ switch (aVariantCaps) {
+ case NS_FONT_VARIANT_CAPS_SMALLCAPS:
+ ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p'));
+ if (!ok) {
+ aSyntheticLowerToSmallCaps = true;
+ }
+ break;
+ case NS_FONT_VARIANT_CAPS_ALLSMALL:
+ ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')) &&
+ SupportsFeature(aScript, HB_TAG('c', '2', 's', 'c'));
+ if (!ok) {
+ aSyntheticLowerToSmallCaps = true;
+ aSyntheticUpperToSmallCaps = true;
+ }
+ break;
+ case NS_FONT_VARIANT_CAPS_PETITECAPS:
+ ok = SupportsFeature(aScript, HB_TAG('p', 'c', 'a', 'p'));
+ if (!ok) {
+ ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p'));
+ aFallbackToSmallCaps = ok;
+ }
+ if (!ok) {
+ aSyntheticLowerToSmallCaps = true;
+ }
+ break;
+ case NS_FONT_VARIANT_CAPS_ALLPETITE:
+ ok = SupportsFeature(aScript, HB_TAG('p', 'c', 'a', 'p')) &&
+ SupportsFeature(aScript, HB_TAG('c', '2', 'p', 'c'));
+ if (!ok) {
+ ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')) &&
+ SupportsFeature(aScript, HB_TAG('c', '2', 's', 'c'));
+ aFallbackToSmallCaps = ok;
+ }
+ if (!ok) {
+ aSyntheticLowerToSmallCaps = true;
+ aSyntheticUpperToSmallCaps = true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ NS_ASSERTION(
+ !(ok && (aSyntheticLowerToSmallCaps || aSyntheticUpperToSmallCaps)),
+ "shouldn't use synthetic features if we found real ones");
+
+ NS_ASSERTION(!(!ok && aFallbackToSmallCaps),
+ "if we found a usable fallback, that counts as ok");
+
+ return ok;
+}
+
+bool gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript,
+ const uint8_t* aString, uint32_t aLength,
+ Script aRunScript) {
+ NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aString),
+ aLength);
+ return SupportsSubSuperscript(aSubSuperscript, unicodeString.get(), aLength,
+ aRunScript);
+}
+
+bool gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript,
+ const char16_t* aString, uint32_t aLength,
+ Script aRunScript) {
+ NS_ASSERTION(aSubSuperscript == NS_FONT_VARIANT_POSITION_SUPER ||
+ aSubSuperscript == NS_FONT_VARIANT_POSITION_SUB,
+ "unknown value of font-variant-position");
+
+ uint32_t feature = aSubSuperscript == NS_FONT_VARIANT_POSITION_SUPER
+ ? HB_TAG('s', 'u', 'p', 's')
+ : HB_TAG('s', 'u', 'b', 's');
+
+ if (!SupportsFeature(aRunScript, feature)) {
+ return false;
+ }
+
+ // xxx - for graphite, don't really know how to sniff lookups so bail
+ if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
+ return true;
+ }
+
+ gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper();
+ if (!shaper) {
+ return false;
+ }
+
+ // get the hbset containing input glyphs for the feature
+ const hb_set_t* inputGlyphs =
+ mFontEntry->InputsForOpenTypeFeature(aRunScript, feature);
+
+ // create an hbset containing default glyphs for the script run
+ hb_set_t* defaultGlyphsInRun = hb_set_create();
+
+ // for each character, get the glyph id
+ for (uint32_t i = 0; i < aLength; i++) {
+ uint32_t ch = aString[i];
+
+ if (i + 1 < aLength && NS_IS_SURROGATE_PAIR(ch, aString[i + 1])) {
+ i++;
+ ch = SURROGATE_TO_UCS4(ch, aString[i]);
+ }
+
+ hb_codepoint_t gid = shaper->GetNominalGlyph(ch);
+ hb_set_add(defaultGlyphsInRun, gid);
+ }
+
+ // intersect with input glyphs, if size is not the same ==> fallback
+ uint32_t origSize = hb_set_get_population(defaultGlyphsInRun);
+ hb_set_intersect(defaultGlyphsInRun, inputGlyphs);
+ uint32_t intersectionSize = hb_set_get_population(defaultGlyphsInRun);
+ hb_set_destroy(defaultGlyphsInRun);
+
+ return origSize == intersectionSize;
+}
+
+bool gfxFont::FeatureWillHandleChar(Script aRunScript, uint32_t aFeature,
+ uint32_t aUnicode) {
+ if (!SupportsFeature(aRunScript, aFeature)) {
+ return false;
+ }
+
+ // xxx - for graphite, don't really know how to sniff lookups so bail
+ if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
+ return true;
+ }
+
+ if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) {
+ // get the hbset containing input glyphs for the feature
+ const hb_set_t* inputGlyphs =
+ mFontEntry->InputsForOpenTypeFeature(aRunScript, aFeature);
+
+ hb_codepoint_t gid = shaper->GetNominalGlyph(aUnicode);
+ return hb_set_has(inputGlyphs, gid);
+ }
+
+ return false;
+}
+
+bool gfxFont::HasFeatureSet(uint32_t aFeature, bool& aFeatureOn) {
+ aFeatureOn = false;
+
+ if (mStyle.featureSettings.IsEmpty() &&
+ GetFontEntry()->mFeatureSettings.IsEmpty()) {
+ return false;
+ }
+
+ // add feature values from font
+ bool featureSet = false;
+ uint32_t i, count;
+
+ nsTArray<gfxFontFeature>& fontFeatures = GetFontEntry()->mFeatureSettings;
+ count = fontFeatures.Length();
+ for (i = 0; i < count; i++) {
+ const gfxFontFeature& feature = fontFeatures.ElementAt(i);
+ if (feature.mTag == aFeature) {
+ featureSet = true;
+ aFeatureOn = (feature.mValue != 0);
+ }
+ }
+
+ // add feature values from style rules
+ nsTArray<gfxFontFeature>& styleFeatures = mStyle.featureSettings;
+ count = styleFeatures.Length();
+ for (i = 0; i < count; i++) {
+ const gfxFontFeature& feature = styleFeatures.ElementAt(i);
+ if (feature.mTag == aFeature) {
+ featureSet = true;
+ aFeatureOn = (feature.mValue != 0);
+ }
+ }
+
+ return featureSet;
+}
+
+already_AddRefed<mozilla::gfx::ScaledFont> gfxFont::GetScaledFont(
+ mozilla::gfx::DrawTarget* aDrawTarget) {
+ TextRunDrawParams params;
+ params.dt = aDrawTarget;
+ return GetScaledFont(params);
+}
+
+void gfxFont::InitializeScaledFont(
+ const RefPtr<mozilla::gfx::ScaledFont>& aScaledFont) {
+ if (!aScaledFont) {
+ return;
+ }
+
+ float angle = AngleForSyntheticOblique();
+ if (angle != 0.0f) {
+ aScaledFont->SetSyntheticObliqueAngle(angle);
+ }
+}
+
+/**
+ * A helper function in case we need to do any rounding or other
+ * processing here.
+ */
+#define ToDeviceUnits(aAppUnits, aDevUnitsPerAppUnit) \
+ (double(aAppUnits) * double(aDevUnitsPerAppUnit))
+
+static AntialiasMode Get2DAAMode(gfxFont::AntialiasOption aAAOption) {
+ switch (aAAOption) {
+ case gfxFont::kAntialiasSubpixel:
+ return AntialiasMode::SUBPIXEL;
+ case gfxFont::kAntialiasGrayscale:
+ return AntialiasMode::GRAY;
+ case gfxFont::kAntialiasNone:
+ return AntialiasMode::NONE;
+ default:
+ return AntialiasMode::DEFAULT;
+ }
+}
+
+class GlyphBufferAzure {
+#define AUTO_BUFFER_SIZE (2048 / sizeof(Glyph))
+
+ typedef mozilla::image::imgDrawingParams imgDrawingParams;
+
+ public:
+ GlyphBufferAzure(const TextRunDrawParams& aRunParams,
+ const FontDrawParams& aFontParams)
+ : mRunParams(aRunParams),
+ mFontParams(aFontParams),
+ mBuffer(*mAutoBuffer.addr()),
+ mBufSize(AUTO_BUFFER_SIZE),
+ mCapacity(0),
+ mNumGlyphs(0) {}
+
+ ~GlyphBufferAzure() {
+ if (mNumGlyphs > 0) {
+ FlushGlyphs();
+ }
+
+ if (mBuffer != *mAutoBuffer.addr()) {
+ free(mBuffer);
+ }
+ }
+
+ // Ensure the buffer has enough space for aGlyphCount glyphs to be added,
+ // considering the supplied strike multipler aStrikeCount.
+ // This MUST be called before OutputGlyph is used to actually store glyph
+ // records in the buffer. It may be called repeated to add further capacity
+ // in case we don't know up-front exactly what will be needed.
+ void AddCapacity(uint32_t aGlyphCount, uint32_t aStrikeCount) {
+ // Calculate the new capacity and ensure it will fit within the maximum
+ // allowed capacity.
+ static const uint64_t kMaxCapacity = 64 * 1024;
+ mCapacity = uint32_t(std::min(
+ kMaxCapacity,
+ uint64_t(mCapacity) + uint64_t(aGlyphCount) * uint64_t(aStrikeCount)));
+ // See if the required capacity fits within the already-allocated space
+ if (mCapacity <= mBufSize) {
+ return;
+ }
+ // We need to grow the buffer: determine a new size, allocate, and
+ // copy the existing data over if we didn't use realloc (which would
+ // do it automatically).
+ mBufSize = std::max(mCapacity, mBufSize * 2);
+ if (mBuffer == *mAutoBuffer.addr()) {
+ // switching from autobuffer to malloc, so we need to copy
+ mBuffer = reinterpret_cast<Glyph*>(moz_xmalloc(mBufSize * sizeof(Glyph)));
+ std::memcpy(mBuffer, *mAutoBuffer.addr(), mNumGlyphs * sizeof(Glyph));
+ } else {
+ mBuffer = reinterpret_cast<Glyph*>(
+ moz_xrealloc(mBuffer, mBufSize * sizeof(Glyph)));
+ }
+ }
+
+ void OutputGlyph(uint32_t aGlyphID, const gfx::Point& aPt) {
+ // If the buffer is full, flush to make room for the new glyph.
+ if (mNumGlyphs >= mCapacity) {
+ // Check that AddCapacity has been used appropriately!
+ MOZ_ASSERT(mCapacity > 0 && mNumGlyphs == mCapacity);
+ Flush();
+ }
+ Glyph* glyph = mBuffer + mNumGlyphs++;
+ glyph->mIndex = aGlyphID;
+ glyph->mPosition = aPt;
+ }
+
+ void Flush() {
+ if (mNumGlyphs > 0) {
+ FlushGlyphs();
+ mNumGlyphs = 0;
+ }
+ }
+
+ const TextRunDrawParams& mRunParams;
+ const FontDrawParams& mFontParams;
+
+ private:
+ static DrawMode GetStrokeMode(DrawMode aMode) {
+ return aMode & (DrawMode::GLYPH_STROKE | DrawMode::GLYPH_STROKE_UNDERNEATH);
+ }
+
+ // Render the buffered glyphs to the draw target.
+ void FlushGlyphs() {
+ gfx::GlyphBuffer buf;
+ buf.mGlyphs = mBuffer;
+ buf.mNumGlyphs = mNumGlyphs;
+
+ const gfxContext::AzureState& state = mRunParams.context->CurrentState();
+
+ // Draw stroke first if the UNDERNEATH flag is set in drawMode.
+ if (mRunParams.strokeOpts &&
+ GetStrokeMode(mRunParams.drawMode) ==
+ (DrawMode::GLYPH_STROKE | DrawMode::GLYPH_STROKE_UNDERNEATH)) {
+ DrawStroke(state, buf);
+ }
+
+ if (mRunParams.drawMode & DrawMode::GLYPH_FILL) {
+ if (state.pattern || mFontParams.contextPaint) {
+ Pattern* pat;
+
+ RefPtr<gfxPattern> fillPattern;
+ if (mFontParams.contextPaint) {
+ imgDrawingParams imgParams;
+ fillPattern = mFontParams.contextPaint->GetFillPattern(
+ mRunParams.context->GetDrawTarget(),
+ mRunParams.context->CurrentMatrixDouble(), imgParams);
+ }
+ if (!fillPattern) {
+ if (state.pattern) {
+ RefPtr<gfxPattern> statePattern =
+ mRunParams.context->CurrentState().pattern;
+ pat = statePattern->GetPattern(mRunParams.dt,
+ state.patternTransformChanged
+ ? &state.patternTransform
+ : nullptr);
+ } else {
+ pat = nullptr;
+ }
+ } else {
+ pat = fillPattern->GetPattern(mRunParams.dt);
+ }
+
+ if (pat) {
+ mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf, *pat,
+ mFontParams.drawOptions);
+ }
+ } else {
+ mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf,
+ ColorPattern(state.color),
+ mFontParams.drawOptions);
+ }
+ }
+
+ // Draw stroke if the UNDERNEATH flag is not set.
+ if (mRunParams.strokeOpts &&
+ GetStrokeMode(mRunParams.drawMode) == DrawMode::GLYPH_STROKE) {
+ DrawStroke(state, buf);
+ }
+
+ if (mRunParams.drawMode & DrawMode::GLYPH_PATH) {
+ mRunParams.context->EnsurePathBuilder();
+ Matrix mat = mRunParams.dt->GetTransform();
+ mFontParams.scaledFont->CopyGlyphsToBuilder(
+ buf, mRunParams.context->mPathBuilder, &mat);
+ }
+ }
+
+ void DrawStroke(const gfxContext::AzureState& aState,
+ gfx::GlyphBuffer& aBuffer) {
+ if (mRunParams.textStrokePattern) {
+ Pattern* pat = mRunParams.textStrokePattern->GetPattern(
+ mRunParams.dt,
+ aState.patternTransformChanged ? &aState.patternTransform : nullptr);
+
+ if (pat) {
+ FlushStroke(aBuffer, *pat);
+ }
+ } else {
+ FlushStroke(aBuffer,
+ ColorPattern(ToDeviceColor(mRunParams.textStrokeColor)));
+ }
+ }
+
+ void FlushStroke(gfx::GlyphBuffer& aBuf, const Pattern& aPattern) {
+ mRunParams.dt->StrokeGlyphs(mFontParams.scaledFont, aBuf, aPattern,
+ *mRunParams.strokeOpts,
+ mFontParams.drawOptions);
+ }
+
+ // We use an "inline" buffer automatically allocated (on the stack) as part
+ // of the GlyphBufferAzure object to hold the glyphs in most cases, falling
+ // back to a separately-allocated heap buffer if the count of buffered
+ // glyphs gets too big.
+ //
+ // This is basically a rudimentary AutoTArray; so why not use AutoTArray
+ // itself?
+ //
+ // If we used an AutoTArray, we'd want to avoid using SetLength or
+ // AppendElements to allocate the space we actually need, because those
+ // methods would default-construct the new elements.
+ //
+ // Could we use SetCapacity to reserve the necessary buffer space without
+ // default-constructing all the Glyph records? No, because of a failure
+ // that could occur when we need to grow the buffer, which happens when we
+ // encounter a DetailedGlyph in the textrun that refers to a sequence of
+ // several real glyphs. At that point, we need to add some extra capacity
+ // to the buffer we initially allocated based on the length of the textrun
+ // range we're rendering.
+ //
+ // This buffer growth would work fine as long as it still fits within the
+ // array's inline buffer (we just use a bit more of it), or if the buffer
+ // was already heap-allocated (in which case AutoTArray will use realloc(),
+ // preserving its contents). But a problem will arise when the initial
+ // capacity we allocated (based on the length of the run) fits within the
+ // array's inline buffer, but subsequently we need to extend the buffer
+ // beyond the inline buffer size, so we reallocate to the heap. Because we
+ // haven't "officially" filled the array with SetLength or AppendElements,
+ // its mLength is still zero; as far as it's concerned the buffer is just
+ // uninitialized space, and when it switches to use a malloc'd buffer it
+ // won't copy the existing contents.
+
+ // Allocate space for a buffer of Glyph records, without initializing them.
+ AlignedStorage2<Glyph[AUTO_BUFFER_SIZE]> mAutoBuffer;
+
+ // Pointer to the buffer we're currently using -- initially mAutoBuffer,
+ // but may be changed to a malloc'd buffer, in which case that buffer must
+ // be free'd on destruction.
+ Glyph* mBuffer;
+
+ uint32_t mBufSize; // size of allocated buffer; capacity can grow to
+ // this before reallocation is needed
+ uint32_t mCapacity; // amount of buffer size reserved
+ uint32_t mNumGlyphs; // number of glyphs actually present in the buffer
+
+#undef AUTO_BUFFER_SIZE
+};
+
+// Bug 674909. When synthetic bolding text by drawing twice, need to
+// render using a pixel offset in device pixels, otherwise text
+// doesn't appear bolded, it appears as if a bad text shadow exists
+// when a non-identity transform exists. Use an offset factor so that
+// the second draw occurs at a constant offset in device pixels.
+
+gfx::Float gfxFont::CalcXScale(DrawTarget* aDrawTarget) {
+ // determine magnitude of a 1px x offset in device space
+ Size t = aDrawTarget->GetTransform().TransformSize(Size(1.0, 0.0));
+ if (t.width == 1.0 && t.height == 0.0) {
+ // short-circuit the most common case to avoid sqrt() and division
+ return 1.0;
+ }
+
+ gfx::Float m = sqrtf(t.width * t.width + t.height * t.height);
+
+ NS_ASSERTION(m != 0.0, "degenerate transform while synthetic bolding");
+ if (m == 0.0) {
+ return 0.0; // effectively disables offset
+ }
+
+ // scale factor so that offsets are 1px in device pixels
+ return 1.0 / m;
+}
+
+// Draw a run of CharacterGlyph records from the given offset in aShapedText.
+// Returns true if glyph paths were actually emitted.
+template <gfxFont::FontComplexityT FC, gfxFont::SpacingT S>
+bool gfxFont::DrawGlyphs(const gfxShapedText* aShapedText,
+ uint32_t aOffset, // offset in the textrun
+ uint32_t aCount, // length of run to draw
+ gfx::Point* aPt,
+ const gfx::Matrix* aOffsetMatrix, // may be null
+ GlyphBufferAzure& aBuffer) {
+ float& inlineCoord =
+ aBuffer.mFontParams.isVerticalFont ? aPt->y.value : aPt->x.value;
+
+ const gfxShapedText::CompressedGlyph* glyphData =
+ &aShapedText->GetCharacterGlyphs()[aOffset];
+
+ if (S == SpacingT::HasSpacing) {
+ float space = aBuffer.mRunParams.spacing[0].mBefore *
+ aBuffer.mFontParams.advanceDirection;
+ inlineCoord += space;
+ }
+
+ // Allocate buffer space for the run, assuming all simple glyphs.
+ uint32_t capacityMult = 1 + aBuffer.mFontParams.extraStrikes;
+ aBuffer.AddCapacity(aCount, capacityMult);
+
+ bool emittedGlyphs = false;
+
+ for (uint32_t i = 0; i < aCount; ++i, ++glyphData) {
+ if (glyphData->IsSimpleGlyph()) {
+ float advance =
+ glyphData->GetSimpleAdvance() * aBuffer.mFontParams.advanceDirection;
+ if (aBuffer.mRunParams.isRTL) {
+ inlineCoord += advance;
+ }
+ DrawOneGlyph<FC>(glyphData->GetSimpleGlyph(), *aPt, aBuffer,
+ &emittedGlyphs);
+ if (!aBuffer.mRunParams.isRTL) {
+ inlineCoord += advance;
+ }
+ } else {
+ uint32_t glyphCount = glyphData->GetGlyphCount();
+ if (glyphCount > 0) {
+ // Add extra buffer capacity to allow for multiple-glyph entry.
+ aBuffer.AddCapacity(glyphCount - 1, capacityMult);
+ const gfxShapedText::DetailedGlyph* details =
+ aShapedText->GetDetailedGlyphs(aOffset + i);
+ MOZ_ASSERT(details, "missing DetailedGlyph!");
+ for (uint32_t j = 0; j < glyphCount; ++j, ++details) {
+ float advance =
+ details->mAdvance * aBuffer.mFontParams.advanceDirection;
+ if (aBuffer.mRunParams.isRTL) {
+ inlineCoord += advance;
+ }
+ if (glyphData->IsMissing()) {
+ if (!DrawMissingGlyph(aBuffer.mRunParams, aBuffer.mFontParams,
+ details, *aPt)) {
+ return false;
+ }
+ } else {
+ gfx::Point glyphPt(
+ *aPt + (aOffsetMatrix
+ ? aOffsetMatrix->TransformPoint(details->mOffset)
+ : details->mOffset));
+ DrawOneGlyph<FC>(details->mGlyphID, glyphPt, aBuffer,
+ &emittedGlyphs);
+ }
+ if (!aBuffer.mRunParams.isRTL) {
+ inlineCoord += advance;
+ }
+ }
+ }
+ }
+
+ if (S == SpacingT::HasSpacing) {
+ float space = aBuffer.mRunParams.spacing[i].mAfter;
+ if (i + 1 < aCount) {
+ space += aBuffer.mRunParams.spacing[i + 1].mBefore;
+ }
+ space *= aBuffer.mFontParams.advanceDirection;
+ inlineCoord += space;
+ }
+ }
+
+ return emittedGlyphs;
+}
+
+// Draw an individual glyph at a specific location.
+// *aPt is the glyph position in appUnits; it is converted to device
+// coordinates (devPt) here.
+template <gfxFont::FontComplexityT FC>
+void gfxFont::DrawOneGlyph(uint32_t aGlyphID, const gfx::Point& aPt,
+ GlyphBufferAzure& aBuffer, bool* aEmittedGlyphs) {
+ const TextRunDrawParams& runParams(aBuffer.mRunParams);
+
+ gfx::Point devPt(ToDeviceUnits(aPt.x, runParams.devPerApp),
+ ToDeviceUnits(aPt.y, runParams.devPerApp));
+
+ auto* textDrawer = runParams.textDrawer;
+ if (textDrawer) {
+ // If the glyph is entirely outside the clip rect, we don't need to draw it
+ // at all. (We check the font extents here rather than the individual glyph
+ // bounds because that's cheaper to look up, and provides a conservative
+ // "worst case" for where this glyph might want to draw.)
+ LayoutDeviceRect extents =
+ LayoutDeviceRect::FromUnknownRect(aBuffer.mFontParams.fontExtents);
+ extents.MoveBy(LayoutDevicePoint::FromUnknownPoint(devPt));
+ if (!extents.Intersects(runParams.clipRect)) {
+ return;
+ }
+ }
+
+ if (FC == FontComplexityT::ComplexFont) {
+ const FontDrawParams& fontParams(aBuffer.mFontParams);
+
+ gfxContextMatrixAutoSaveRestore matrixRestore;
+
+ if (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont &&
+ !textDrawer) {
+ // We have to flush each glyph individually when doing
+ // synthetic-oblique for vertical-upright text, because
+ // the skew transform needs to be applied to a separate
+ // origin for each glyph, not once for the whole run.
+ aBuffer.Flush();
+ matrixRestore.SetContext(runParams.context);
+ gfx::Point skewPt(
+ devPt.x + GetMetrics(nsFontMetrics::eVertical).emHeight / 2, devPt.y);
+ gfx::Matrix mat =
+ runParams.context->CurrentMatrix()
+ .PreTranslate(skewPt)
+ .PreMultiply(gfx::Matrix(1, fontParams.obliqueSkew, 0, 1, 0, 0))
+ .PreTranslate(-skewPt);
+ runParams.context->SetMatrix(mat);
+ }
+
+ if (fontParams.haveSVGGlyphs) {
+ if (!runParams.paintSVGGlyphs) {
+ return;
+ }
+ NS_WARNING_ASSERTION(
+ runParams.drawMode != DrawMode::GLYPH_PATH,
+ "Rendering SVG glyph despite request for glyph path");
+ if (RenderSVGGlyph(runParams.context, textDrawer, devPt, aGlyphID,
+ fontParams.contextPaint, runParams.callbacks,
+ *aEmittedGlyphs)) {
+ return;
+ }
+ }
+
+ if (fontParams.haveColorGlyphs && !UseNativeColrFontSupport() &&
+ RenderColorGlyph(runParams.dt, runParams.context, textDrawer,
+ fontParams, devPt, aGlyphID)) {
+ return;
+ }
+
+ aBuffer.OutputGlyph(aGlyphID, devPt);
+
+ // Synthetic bolding (if required) by multi-striking.
+ for (int32_t i = 0; i < fontParams.extraStrikes; ++i) {
+ if (fontParams.isVerticalFont) {
+ devPt.y += fontParams.synBoldOnePixelOffset;
+ } else {
+ devPt.x += fontParams.synBoldOnePixelOffset;
+ }
+ aBuffer.OutputGlyph(aGlyphID, devPt);
+ }
+
+ if (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont &&
+ !textDrawer) {
+ aBuffer.Flush();
+ }
+ } else {
+ aBuffer.OutputGlyph(aGlyphID, devPt);
+ }
+
+ *aEmittedGlyphs = true;
+}
+
+bool gfxFont::DrawMissingGlyph(const TextRunDrawParams& aRunParams,
+ const FontDrawParams& aFontParams,
+ const gfxShapedText::DetailedGlyph* aDetails,
+ const gfx::Point& aPt) {
+ // Default-ignorable chars will have zero advance width;
+ // we don't have to draw the hexbox for them.
+ float advance = aDetails->mAdvance;
+ if (aRunParams.drawMode != DrawMode::GLYPH_PATH && advance > 0) {
+ auto* textDrawer = aRunParams.textDrawer;
+ const Matrix* matPtr = nullptr;
+ Matrix mat;
+ if (textDrawer) {
+ // Generate an orientation matrix for the current writing mode
+ wr::FontInstanceFlags flags = textDrawer->GetWRGlyphFlags();
+ if (flags & wr::FontInstanceFlags::TRANSPOSE) {
+ std::swap(mat._11, mat._12);
+ std::swap(mat._21, mat._22);
+ }
+ mat.PostScale(flags & wr::FontInstanceFlags::FLIP_X ? -1.0f : 1.0f,
+ flags & wr::FontInstanceFlags::FLIP_Y ? -1.0f : 1.0f);
+ matPtr = &mat;
+ }
+
+ Point pt(Float(ToDeviceUnits(aPt.x, aRunParams.devPerApp)),
+ Float(ToDeviceUnits(aPt.y, aRunParams.devPerApp)));
+ Float advanceDevUnits = Float(ToDeviceUnits(advance, aRunParams.devPerApp));
+ Float height = GetMetrics(nsFontMetrics::eHorizontal).maxAscent;
+ // Horizontally center if drawing vertically upright with no sideways
+ // transform.
+ Rect glyphRect =
+ aFontParams.isVerticalFont && !mat.HasNonAxisAlignedTransform()
+ ? Rect(pt.x - height / 2, pt.y, height, advanceDevUnits)
+ : Rect(pt.x, pt.y - height, advanceDevUnits, height);
+
+ // If there's a fake-italic skew in effect as part
+ // of the drawTarget's transform, we need to undo
+ // this before drawing the hexbox. (Bug 983985)
+ gfxContextMatrixAutoSaveRestore matrixRestore;
+ if (aFontParams.obliqueSkew != 0.0f && !aFontParams.isVerticalFont &&
+ !textDrawer) {
+ matrixRestore.SetContext(aRunParams.context);
+ gfx::Matrix mat =
+ aRunParams.context->CurrentMatrix()
+ .PreTranslate(pt)
+ .PreMultiply(gfx::Matrix(1, 0, aFontParams.obliqueSkew, 1, 0, 0))
+ .PreTranslate(-pt);
+ aRunParams.context->SetMatrix(mat);
+ }
+
+ gfxFontMissingGlyphs::DrawMissingGlyph(
+ aDetails->mGlyphID, glyphRect, *aRunParams.dt,
+ PatternFromState(aRunParams.context), matPtr);
+ }
+ return true;
+}
+
+// This method is mostly parallel to DrawGlyphs.
+void gfxFont::DrawEmphasisMarks(const gfxTextRun* aShapedText, gfx::Point* aPt,
+ uint32_t aOffset, uint32_t aCount,
+ const EmphasisMarkDrawParams& aParams) {
+ float& inlineCoord = aParams.isVertical ? aPt->y.value : aPt->x.value;
+ gfxTextRun::Range markRange(aParams.mark);
+ gfxTextRun::DrawParams params(aParams.context);
+
+ float clusterStart = -std::numeric_limits<float>::infinity();
+ bool shouldDrawEmphasisMark = false;
+ for (uint32_t i = 0, idx = aOffset; i < aCount; ++i, ++idx) {
+ if (aParams.spacing) {
+ inlineCoord += aParams.direction * aParams.spacing[i].mBefore;
+ }
+ if (aShapedText->IsClusterStart(idx) ||
+ clusterStart == -std::numeric_limits<float>::infinity()) {
+ clusterStart = inlineCoord;
+ }
+ if (aShapedText->CharMayHaveEmphasisMark(idx)) {
+ shouldDrawEmphasisMark = true;
+ }
+ inlineCoord += aParams.direction * aShapedText->GetAdvanceForGlyph(idx);
+ if (shouldDrawEmphasisMark &&
+ (i + 1 == aCount || aShapedText->IsClusterStart(idx + 1))) {
+ float clusterAdvance = inlineCoord - clusterStart;
+ // Move the coord backward to get the needed start point.
+ float delta = (clusterAdvance + aParams.advance) / 2;
+ inlineCoord -= delta;
+ aParams.mark->Draw(markRange, *aPt, params);
+ inlineCoord += delta;
+ shouldDrawEmphasisMark = false;
+ }
+ if (aParams.spacing) {
+ inlineCoord += aParams.direction * aParams.spacing[i].mAfter;
+ }
+ }
+}
+
+nsTArray<mozilla::gfx::sRGBColor>* TextRunDrawParams::GetPaletteFor(
+ const gfxFont* aFont) {
+ auto entry = mPaletteCache.Lookup(aFont);
+ if (!entry) {
+ CacheData newData;
+ newData.mKey = aFont;
+
+ gfxFontEntry* fe = aFont->GetFontEntry();
+ gfxFontEntry::AutoHBFace face = fe->GetHBFace();
+ newData.mPalette = COLRFonts::SetupColorPalette(
+ face, paletteValueSet, fontPalette, fe->FamilyName());
+
+ entry.Set(std::move(newData));
+ }
+ return entry.Data().mPalette.get();
+}
+
+void gfxFont::Draw(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd,
+ gfx::Point* aPt, TextRunDrawParams& aRunParams,
+ gfx::ShapedTextFlags aOrientation) {
+ NS_ASSERTION(aRunParams.drawMode == DrawMode::GLYPH_PATH ||
+ !(int(aRunParams.drawMode) & int(DrawMode::GLYPH_PATH)),
+ "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or "
+ "GLYPH_STROKE_UNDERNEATH");
+
+ if (aStart >= aEnd) {
+ return;
+ }
+
+ FontDrawParams fontParams;
+
+ if (aRunParams.drawOpts) {
+ fontParams.drawOptions = *aRunParams.drawOpts;
+ }
+
+ fontParams.scaledFont = GetScaledFont(aRunParams);
+ if (!fontParams.scaledFont) {
+ return;
+ }
+ auto* textDrawer = aRunParams.textDrawer;
+
+ fontParams.obliqueSkew = SkewForSyntheticOblique();
+ fontParams.haveSVGGlyphs = GetFontEntry()->TryGetSVGData(this);
+ fontParams.haveColorGlyphs = GetFontEntry()->TryGetColorGlyphs();
+ fontParams.contextPaint = aRunParams.runContextPaint;
+
+ if (fontParams.haveColorGlyphs && !UseNativeColrFontSupport()) {
+ DeviceColor ctxColor;
+ fontParams.currentColor = aRunParams.context->GetDeviceColor(ctxColor)
+ ? sRGBColor::FromABGR(ctxColor.ToABGR())
+ : sRGBColor::OpaqueBlack();
+ fontParams.palette = aRunParams.GetPaletteFor(this);
+ }
+
+ if (textDrawer) {
+ fontParams.isVerticalFont = aRunParams.isVerticalRun;
+ } else {
+ fontParams.isVerticalFont =
+ aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
+ }
+
+ gfxContextMatrixAutoSaveRestore matrixRestore;
+ layout::TextDrawTarget::AutoRestoreWRGlyphFlags glyphFlagsRestore;
+
+ // Save the current baseline offset for restoring later, in case it is
+ // modified.
+ float& baseline = fontParams.isVerticalFont ? aPt->x.value : aPt->y.value;
+ float origBaseline = baseline;
+
+ // The point may be advanced in local-space, while the resulting point on
+ // return must be advanced in transformed space. So save the original point so
+ // we can properly transform the advance later.
+ gfx::Point origPt = *aPt;
+ const gfx::Matrix* offsetMatrix = nullptr;
+
+ // Default to advancing along the +X direction (-X if RTL).
+ fontParams.advanceDirection = aRunParams.isRTL ? -1.0f : 1.0f;
+ // Default to offsetting baseline downward along the +Y direction.
+ float baselineDir = 1.0f;
+ // The direction of sideways rotation, if applicable.
+ // -1 for rotating left/counter-clockwise
+ // 1 for rotating right/clockwise
+ // 0 for no rotation
+ float sidewaysDir =
+ (aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT
+ ? -1.0f
+ : (aOrientation ==
+ gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT
+ ? 1.0f
+ : 0.0f));
+ // If we're rendering a sideways run, we need to push a rotation transform to
+ // the context.
+ if (sidewaysDir != 0.0f) {
+ if (textDrawer) {
+ // For WebRender, we can't use a DrawTarget transform and must instead use
+ // flags that locally transform the glyph, without affecting the glyph
+ // origin. The glyph origins must thus be offset in the transformed
+ // directions (instead of local-space directions). Modify the advance and
+ // baseline directions to account for the indicated transform.
+
+ // The default text orientation is down being +Y and right being +X.
+ // Rotating 90 degrees left/CCW makes down be +X and right be -Y.
+ // Rotating 90 degrees right/CW makes down be -X and right be +Y.
+ // Thus the advance direction (moving right) is just sidewaysDir,
+ // i.e. negative along Y axis if rotated left and positive if
+ // rotated right.
+ fontParams.advanceDirection *= sidewaysDir;
+ // The baseline direction (moving down) is negated relative to the
+ // advance direction for sideways transforms.
+ baselineDir *= -sidewaysDir;
+
+ glyphFlagsRestore.Save(textDrawer);
+ // Set the transform flags accordingly. Both sideways rotations transpose
+ // X and Y, while left rotation flips the resulting Y axis, and right
+ // rotation flips the resulting X axis.
+ textDrawer->SetWRGlyphFlags(
+ textDrawer->GetWRGlyphFlags() | wr::FontInstanceFlags::TRANSPOSE |
+ (aOrientation ==
+ gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT
+ ? wr::FontInstanceFlags::FLIP_Y
+ : wr::FontInstanceFlags::FLIP_X));
+ // We also need to set up a transform for the glyph offset vector that
+ // may be present in DetailedGlyph records.
+ static const gfx::Matrix kSidewaysLeft = {0, -1, 1, 0, 0, 0};
+ static const gfx::Matrix kSidewaysRight = {0, 1, -1, 0, 0, 0};
+ offsetMatrix =
+ (aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT)
+ ? &kSidewaysLeft
+ : &kSidewaysRight;
+ } else {
+ // For non-WebRender targets, just push a rotation transform.
+ matrixRestore.SetContext(aRunParams.context);
+ gfxPoint p(aPt->x * aRunParams.devPerApp, aPt->y * aRunParams.devPerApp);
+ // Get a matrix we can use to draw the (horizontally-shaped) textrun
+ // with 90-degree CW rotation.
+ const gfxFloat rotation = sidewaysDir * M_PI / 2.0f;
+ gfxMatrix mat = aRunParams.context->CurrentMatrixDouble()
+ .PreTranslate(p)
+ . // translate origin for rotation
+ PreRotate(rotation)
+ . // turn 90deg CCW (sideways-left) or CW (*-right)
+ PreTranslate(-p); // undo the translation
+
+ aRunParams.context->SetMatrixDouble(mat);
+ }
+
+ // If we're drawing rotated horizontal text for an element styled
+ // text-orientation:mixed, the dominant baseline will be vertical-
+ // centered. So in this case, we need to adjust the position so that
+ // the rotated horizontal text (which uses an alphabetic baseline) will
+ // look OK when juxtaposed with upright glyphs (rendered on a centered
+ // vertical baseline). The adjustment here is somewhat ad hoc; we
+ // should eventually look for baseline tables[1] in the fonts and use
+ // those if available.
+ // [1] See http://www.microsoft.com/typography/otspec/base.htm
+ if (aTextRun->UseCenterBaseline()) {
+ const Metrics& metrics = GetMetrics(nsFontMetrics::eHorizontal);
+ float baseAdj = (metrics.emAscent - metrics.emDescent) / 2;
+ baseline += baseAdj * aTextRun->GetAppUnitsPerDevUnit() * baselineDir;
+ }
+ } else if (textDrawer &&
+ aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT) {
+ glyphFlagsRestore.Save(textDrawer);
+ textDrawer->SetWRGlyphFlags(textDrawer->GetWRGlyphFlags() |
+ wr::FontInstanceFlags::VERTICAL);
+ }
+
+ if (fontParams.obliqueSkew != 0.0f && !fontParams.isVerticalFont &&
+ !textDrawer) {
+ // Adjust matrix for synthetic-oblique, except if we're doing vertical-
+ // upright text, in which case this will be handled for each glyph
+ // individually in DrawOneGlyph.
+ if (!matrixRestore.HasMatrix()) {
+ matrixRestore.SetContext(aRunParams.context);
+ }
+ gfx::Point p(aPt->x * aRunParams.devPerApp, aPt->y * aRunParams.devPerApp);
+ gfx::Matrix mat =
+ aRunParams.context->CurrentMatrix()
+ .PreTranslate(p)
+ .PreMultiply(gfx::Matrix(1, 0, -fontParams.obliqueSkew, 1, 0, 0))
+ .PreTranslate(-p);
+ aRunParams.context->SetMatrix(mat);
+ }
+
+ RefPtr<SVGContextPaint> contextPaint;
+ if (fontParams.haveSVGGlyphs && !fontParams.contextPaint) {
+ // If no pattern is specified for fill, use the current pattern
+ NS_ASSERTION((int(aRunParams.drawMode) & int(DrawMode::GLYPH_STROKE)) == 0,
+ "no pattern supplied for stroking text");
+ RefPtr<gfxPattern> fillPattern = aRunParams.context->GetPattern();
+ contextPaint = new SimpleTextContextPaint(
+ fillPattern, nullptr, aRunParams.context->CurrentMatrixDouble());
+ fontParams.contextPaint = contextPaint.get();
+ }
+
+ // Synthetic-bold strikes are each offset one device pixel in run direction
+ // (these values are only needed if ApplySyntheticBold() is true).
+ // If drawing via webrender, it will do multistrike internally so we don't
+ // need to handle it here.
+ bool doMultistrikeBold = ApplySyntheticBold() && !textDrawer;
+ if (doMultistrikeBold) {
+ // For screen display, we want to try and repeat strikes with an offset of
+ // one device pixel, accounting for zoom or other transforms that may be
+ // in effect, so compute x-axis scale factor from the drawtarget.
+ // However, when generating PDF output the drawtarget's transform does not
+ // really bear any relation to "device pixels", and may result in an
+ // excessively large offset relative to the font size (bug 1823888), so
+ // we limit it based on the used font size to avoid this.
+ // The constant 48.0 reflects the threshold where the calculation in
+ // gfxFont::GetSyntheticBoldOffset() switches to a simple origin-based
+ // slope, though the exact value is somewhat arbitrary; it's selected to
+ // allow a visible amount of boldness while preventing the offset from
+ // becoming "large" in relation to the glyphs.
+ Float xscale =
+ std::min<Float>(GetAdjustedSize() / 48.0,
+ CalcXScale(aRunParams.context->GetDrawTarget()));
+ fontParams.synBoldOnePixelOffset = aRunParams.direction * xscale;
+ if (xscale != 0.0) {
+ static const int32_t kMaxExtraStrikes = 128;
+ gfxFloat extraStrikes = GetSyntheticBoldOffset() / xscale;
+ if (extraStrikes > kMaxExtraStrikes) {
+ // if too many strikes are required, limit them and increase the step
+ // size to compensate
+ fontParams.extraStrikes = kMaxExtraStrikes;
+ fontParams.synBoldOnePixelOffset = aRunParams.direction *
+ GetSyntheticBoldOffset() /
+ fontParams.extraStrikes;
+ } else {
+ // use as many strikes as needed for the increased advance
+ fontParams.extraStrikes = NS_lroundf(std::max(1.0, extraStrikes));
+ }
+ } else {
+ // Degenerate transform?!
+ fontParams.extraStrikes = 0;
+ }
+ } else {
+ fontParams.synBoldOnePixelOffset = 0;
+ fontParams.extraStrikes = 0;
+ }
+
+ // Figure out the maximum extents for the font, accounting for synthetic
+ // oblique and bold.
+ if (mFUnitsConvFactor > 0.0) {
+ fontParams.fontExtents = GetFontEntry()->GetFontExtents(mFUnitsConvFactor);
+ } else {
+ // Was it not an sfnt? Maybe on Linux... use arbitrary huge extents, so we
+ // don't inadvertently clip stuff. A bit less efficient than true extents,
+ // but this should be extremely rare.
+ auto size = GetAdjustedSize();
+ fontParams.fontExtents = Rect(-2 * size, -2 * size, 5 * size, 5 * size);
+ }
+ if (fontParams.obliqueSkew != 0.0f) {
+ gfx::Point p(fontParams.fontExtents.x, fontParams.fontExtents.y);
+ gfx::Matrix skew(1, 0, fontParams.obliqueSkew, 1, 0, 0);
+ fontParams.fontExtents = skew.TransformBounds(fontParams.fontExtents);
+ }
+ if (fontParams.extraStrikes) {
+ if (fontParams.isVerticalFont) {
+ fontParams.fontExtents.height +=
+ float(fontParams.extraStrikes) * fontParams.synBoldOnePixelOffset;
+ } else {
+ fontParams.fontExtents.width +=
+ float(fontParams.extraStrikes) * fontParams.synBoldOnePixelOffset;
+ }
+ }
+
+ bool oldSubpixelAA = aRunParams.dt->GetPermitSubpixelAA();
+ if (!AllowSubpixelAA()) {
+ aRunParams.dt->SetPermitSubpixelAA(false);
+ }
+
+ Matrix mat;
+ Matrix oldMat = aRunParams.dt->GetTransform();
+
+ fontParams.drawOptions.mAntialiasMode = Get2DAAMode(mAntialiasOption);
+
+ if (mStyle.baselineOffset != 0.0) {
+ baseline +=
+ mStyle.baselineOffset * aTextRun->GetAppUnitsPerDevUnit() * baselineDir;
+ }
+
+ bool emittedGlyphs;
+ {
+ // Select appropriate version of the templated DrawGlyphs method
+ // to output glyphs to the buffer, depending on complexity needed
+ // for the type of font, and whether added inter-glyph spacing
+ // is specified.
+ GlyphBufferAzure buffer(aRunParams, fontParams);
+ if (fontParams.haveSVGGlyphs || fontParams.haveColorGlyphs ||
+ fontParams.extraStrikes ||
+ (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont &&
+ !textDrawer)) {
+ if (aRunParams.spacing) {
+ emittedGlyphs =
+ DrawGlyphs<FontComplexityT::ComplexFont, SpacingT::HasSpacing>(
+ aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer);
+ } else {
+ emittedGlyphs =
+ DrawGlyphs<FontComplexityT::ComplexFont, SpacingT::NoSpacing>(
+ aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer);
+ }
+ } else {
+ if (aRunParams.spacing) {
+ emittedGlyphs =
+ DrawGlyphs<FontComplexityT::SimpleFont, SpacingT::HasSpacing>(
+ aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer);
+ } else {
+ emittedGlyphs =
+ DrawGlyphs<FontComplexityT::SimpleFont, SpacingT::NoSpacing>(
+ aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer);
+ }
+ }
+ }
+
+ baseline = origBaseline;
+
+ if (aRunParams.callbacks && emittedGlyphs) {
+ aRunParams.callbacks->NotifyGlyphPathEmitted();
+ }
+
+ aRunParams.dt->SetTransform(oldMat);
+ aRunParams.dt->SetPermitSubpixelAA(oldSubpixelAA);
+
+ if (sidewaysDir != 0.0f && !textDrawer) {
+ // Adjust updated aPt to account for the transform we were using.
+ // The advance happened horizontally in local-space, but the transformed
+ // sideways advance is actually vertical, with sign depending on the
+ // direction of rotation.
+ float advance = aPt->x - origPt.x;
+ *aPt = gfx::Point(origPt.x, origPt.y + advance * sidewaysDir);
+ }
+}
+
+bool gfxFont::RenderSVGGlyph(gfxContext* aContext,
+ layout::TextDrawTarget* aTextDrawer,
+ gfx::Point aPoint, uint32_t aGlyphId,
+ SVGContextPaint* aContextPaint) const {
+ if (!GetFontEntry()->HasSVGGlyph(aGlyphId)) {
+ return false;
+ }
+
+ if (aTextDrawer) {
+ // WebRender doesn't support SVG Glyphs.
+ // (pretend to succeed, output doesn't matter, we will emit a blob)
+ aTextDrawer->FoundUnsupportedFeature();
+ return true;
+ }
+
+ const gfxFloat devUnitsPerSVGUnit =
+ GetAdjustedSize() / GetFontEntry()->UnitsPerEm();
+ gfxContextMatrixAutoSaveRestore matrixRestore(aContext);
+
+ aContext->SetMatrix(aContext->CurrentMatrix()
+ .PreTranslate(aPoint.x, aPoint.y)
+ .PreScale(devUnitsPerSVGUnit, devUnitsPerSVGUnit));
+
+ aContextPaint->InitStrokeGeometry(aContext, devUnitsPerSVGUnit);
+
+ GetFontEntry()->RenderSVGGlyph(aContext, aGlyphId, aContextPaint);
+ aContext->NewPath();
+ return true;
+}
+
+bool gfxFont::RenderSVGGlyph(gfxContext* aContext,
+ layout::TextDrawTarget* aTextDrawer,
+ gfx::Point aPoint, uint32_t aGlyphId,
+ SVGContextPaint* aContextPaint,
+ gfxTextRunDrawCallbacks* aCallbacks,
+ bool& aEmittedGlyphs) const {
+ if (aCallbacks && aEmittedGlyphs) {
+ aCallbacks->NotifyGlyphPathEmitted();
+ aEmittedGlyphs = false;
+ }
+ return RenderSVGGlyph(aContext, aTextDrawer, aPoint, aGlyphId, aContextPaint);
+}
+
+bool gfxFont::RenderColorGlyph(DrawTarget* aDrawTarget, gfxContext* aContext,
+ layout::TextDrawTarget* aTextDrawer,
+ const FontDrawParams& aFontParams,
+ const Point& aPoint, uint32_t aGlyphId) {
+ if (const auto* paintGraph =
+ COLRFonts::GetGlyphPaintGraph(GetFontEntry()->GetCOLR(), aGlyphId)) {
+ const auto* hbShaper = GetHarfBuzzShaper();
+ if (hbShaper && hbShaper->IsInitialized()) {
+ return COLRFonts::PaintGlyphGraph(
+ GetFontEntry()->GetCOLR(), hbShaper->GetHBFont(), paintGraph,
+ aDrawTarget, aTextDrawer, aFontParams.scaledFont,
+ aFontParams.drawOptions, aPoint, aFontParams.currentColor,
+ aFontParams.palette, aGlyphId, mFUnitsConvFactor);
+ }
+ }
+
+ if (const auto* layers =
+ COLRFonts::GetGlyphLayers(GetFontEntry()->GetCOLR(), aGlyphId)) {
+ auto face(GetFontEntry()->GetHBFace());
+ bool ok = COLRFonts::PaintGlyphLayers(
+ GetFontEntry()->GetCOLR(), face, layers, aDrawTarget, aTextDrawer,
+ aFontParams.scaledFont, aFontParams.drawOptions, aPoint,
+ aFontParams.currentColor, aFontParams.palette);
+ return ok;
+ }
+
+ return false;
+}
+
+bool gfxFont::HasColorGlyphFor(uint32_t aCh, uint32_t aNextCh) {
+ // Bitmap fonts are assumed to provide "color" glyphs for all supported chars.
+ gfxFontEntry* fe = GetFontEntry();
+ if (fe->HasColorBitmapTable()) {
+ return true;
+ }
+ // Use harfbuzz shaper to look up the default glyph ID for the character.
+ auto* shaper = GetHarfBuzzShaper();
+ if (!shaper) {
+ return false;
+ }
+ uint32_t gid = 0;
+ if (gfxFontUtils::IsVarSelector(aNextCh)) {
+ gid = shaper->GetVariationGlyph(aCh, aNextCh);
+ }
+ if (!gid) {
+ gid = shaper->GetNominalGlyph(aCh);
+ }
+ if (!gid) {
+ return false;
+ }
+
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1801521:
+ // Emoji special-case: flag sequences NOT based on Regional Indicator pairs
+ // use the BLACK FLAG character plus a series of plane-14 TAG LETTERs, e.g.
+ // England = <black-flag, tag-G, tag-B, tag-E, tag-N, tag-G, tag-cancel>
+ // Here, we don't check for support of the entire sequence (too much
+ // expensive lookahead), but we check that the font at least supports the
+ // first of the tag letter codes, because if it doesn't, we're at risk of
+ // just getting an undifferentiated black flag glyph.
+ if (gfxFontUtils::IsEmojiFlagAndTag(aCh, aNextCh)) {
+ if (!shaper->GetNominalGlyph(aNextCh)) {
+ return false;
+ }
+ }
+
+ // Check if there is a COLR/CPAL or SVG glyph for this ID.
+ if (fe->TryGetColorGlyphs() &&
+ (COLRFonts::GetGlyphPaintGraph(fe->GetCOLR(), gid) ||
+ COLRFonts::GetGlyphLayers(fe->GetCOLR(), gid))) {
+ return true;
+ }
+ if (fe->TryGetSVGData(this) && fe->HasSVGGlyph(gid)) {
+ return true;
+ }
+ return false;
+}
+
+static void UnionRange(gfxFloat aX, gfxFloat* aDestMin, gfxFloat* aDestMax) {
+ *aDestMin = std::min(*aDestMin, aX);
+ *aDestMax = std::max(*aDestMax, aX);
+}
+
+// We get precise glyph extents if the textrun creator requested them, or
+// if the font is a user font --- in which case the author may be relying
+// on overflowing glyphs.
+static bool NeedsGlyphExtents(gfxFont* aFont, const gfxTextRun* aTextRun) {
+ return (aTextRun->GetFlags() &
+ gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX) ||
+ aFont->GetFontEntry()->IsUserFont();
+}
+
+bool gfxFont::IsSpaceGlyphInvisible(DrawTarget* aRefDrawTarget,
+ const gfxTextRun* aTextRun) {
+ gfxFontEntry::LazyFlag flag = mFontEntry->mSpaceGlyphIsInvisible;
+ if (flag == gfxFontEntry::LazyFlag::Uninitialized &&
+ GetAdjustedSize() >= 1.0) {
+ gfxGlyphExtents* extents =
+ GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit());
+ gfxRect glyphExtents;
+ flag = extents->GetTightGlyphExtentsAppUnits(
+ this, aRefDrawTarget, GetSpaceGlyph(), &glyphExtents) &&
+ glyphExtents.IsEmpty()
+ ? gfxFontEntry::LazyFlag::Yes
+ : gfxFontEntry::LazyFlag::No;
+ mFontEntry->mSpaceGlyphIsInvisible = flag;
+ }
+ return flag == gfxFontEntry::LazyFlag::Yes;
+}
+
+bool gfxFont::MeasureGlyphs(const gfxTextRun* aTextRun, uint32_t aStart,
+ uint32_t aEnd, BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget, Spacing* aSpacing,
+ gfxGlyphExtents* aExtents, bool aIsRTL,
+ bool aNeedsGlyphExtents, RunMetrics& aMetrics,
+ gfxFloat* aAdvanceMin, gfxFloat* aAdvanceMax) {
+ const gfxTextRun::CompressedGlyph* charGlyphs =
+ aTextRun->GetCharacterGlyphs();
+ double x = 0;
+ if (aSpacing) {
+ x += aSpacing[0].mBefore;
+ }
+ uint32_t spaceGlyph = GetSpaceGlyph();
+ bool allGlyphsInvisible = true;
+
+ AutoReadLock lock(aExtents->mLock);
+
+ for (uint32_t i = aStart; i < aEnd; ++i) {
+ const gfxTextRun::CompressedGlyph* glyphData = &charGlyphs[i];
+ if (glyphData->IsSimpleGlyph()) {
+ double advance = glyphData->GetSimpleAdvance();
+ uint32_t glyphIndex = glyphData->GetSimpleGlyph();
+ if (allGlyphsInvisible) {
+ if (glyphIndex != spaceGlyph) {
+ allGlyphsInvisible = false;
+ } else {
+ gfxFontEntry::LazyFlag flag = mFontEntry->mSpaceGlyphIsInvisible;
+ if (flag == gfxFontEntry::LazyFlag::Uninitialized &&
+ GetAdjustedSize() >= 1.0) {
+ gfxRect glyphExtents;
+ flag = aExtents->GetTightGlyphExtentsAppUnitsLocked(
+ this, aRefDrawTarget, spaceGlyph, &glyphExtents) &&
+ glyphExtents.IsEmpty()
+ ? gfxFontEntry::LazyFlag::Yes
+ : gfxFontEntry::LazyFlag::No;
+ mFontEntry->mSpaceGlyphIsInvisible = flag;
+ }
+ if (flag == gfxFontEntry::LazyFlag::No) {
+ allGlyphsInvisible = false;
+ }
+ }
+ }
+ // Only get the real glyph horizontal extent if we were asked
+ // for the tight bounding box or we're in quality mode
+ if (aBoundingBoxType != LOOSE_INK_EXTENTS || aNeedsGlyphExtents) {
+ uint16_t extentsWidth =
+ aExtents->GetContainedGlyphWidthAppUnitsLocked(glyphIndex);
+ if (extentsWidth != gfxGlyphExtents::INVALID_WIDTH &&
+ aBoundingBoxType == LOOSE_INK_EXTENTS) {
+ UnionRange(x, aAdvanceMin, aAdvanceMax);
+ UnionRange(x + extentsWidth, aAdvanceMin, aAdvanceMax);
+ } else {
+ gfxRect glyphRect;
+ if (!aExtents->GetTightGlyphExtentsAppUnitsLocked(
+ this, aRefDrawTarget, glyphIndex, &glyphRect)) {
+ glyphRect = gfxRect(0, aMetrics.mBoundingBox.Y(), advance,
+ aMetrics.mBoundingBox.Height());
+ }
+ if (aIsRTL) {
+ // In effect, swap left and right sidebearings of the glyph, for
+ // proper accumulation of potentially-overlapping glyph rects.
+ glyphRect.MoveToX(advance - glyphRect.XMost());
+ }
+ glyphRect.MoveByX(x);
+ aMetrics.mBoundingBox = aMetrics.mBoundingBox.Union(glyphRect);
+ }
+ }
+ x += advance;
+ } else {
+ allGlyphsInvisible = false;
+ uint32_t glyphCount = glyphData->GetGlyphCount();
+ if (glyphCount > 0) {
+ const gfxTextRun::DetailedGlyph* details =
+ aTextRun->GetDetailedGlyphs(i);
+ NS_ASSERTION(details != nullptr,
+ "detailedGlyph record should not be missing!");
+ uint32_t j;
+ for (j = 0; j < glyphCount; ++j, ++details) {
+ uint32_t glyphIndex = details->mGlyphID;
+ double advance = details->mAdvance;
+ gfxRect glyphRect;
+ if (glyphData->IsMissing() ||
+ !aExtents->GetTightGlyphExtentsAppUnitsLocked(
+ this, aRefDrawTarget, glyphIndex, &glyphRect)) {
+ // We might have failed to get glyph extents due to
+ // OOM or something
+ glyphRect = gfxRect(0, -aMetrics.mAscent, advance,
+ aMetrics.mAscent + aMetrics.mDescent);
+ }
+ if (aIsRTL) {
+ // Swap left/right sidebearings of the glyph, because we're doing
+ // mirrored measurement.
+ glyphRect.MoveToX(advance - glyphRect.XMost());
+ // Move to current x position, mirroring any x-offset amount.
+ glyphRect.MoveByX(x - details->mOffset.x);
+ } else {
+ glyphRect.MoveByX(x + details->mOffset.x);
+ }
+ glyphRect.MoveByY(details->mOffset.y);
+ aMetrics.mBoundingBox = aMetrics.mBoundingBox.Union(glyphRect);
+ x += advance;
+ }
+ }
+ }
+ if (aSpacing) {
+ double space = aSpacing[i - aStart].mAfter;
+ if (i + 1 < aEnd) {
+ space += aSpacing[i + 1 - aStart].mBefore;
+ }
+ x += space;
+ }
+ }
+
+ aMetrics.mAdvanceWidth = x;
+ return allGlyphsInvisible;
+}
+
+bool gfxFont::MeasureGlyphs(const gfxTextRun* aTextRun, uint32_t aStart,
+ uint32_t aEnd, BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget, Spacing* aSpacing,
+ bool aIsRTL, RunMetrics& aMetrics) {
+ const gfxTextRun::CompressedGlyph* charGlyphs =
+ aTextRun->GetCharacterGlyphs();
+ double x = 0;
+ if (aSpacing) {
+ x += aSpacing[0].mBefore;
+ }
+ uint32_t spaceGlyph = GetSpaceGlyph();
+ bool allGlyphsInvisible = true;
+
+ for (uint32_t i = aStart; i < aEnd; ++i) {
+ const gfxTextRun::CompressedGlyph* glyphData = &charGlyphs[i];
+ if (glyphData->IsSimpleGlyph()) {
+ double advance = glyphData->GetSimpleAdvance();
+ uint32_t glyphIndex = glyphData->GetSimpleGlyph();
+ if (allGlyphsInvisible &&
+ (glyphIndex != spaceGlyph ||
+ !IsSpaceGlyphInvisible(aRefDrawTarget, aTextRun))) {
+ allGlyphsInvisible = false;
+ }
+ x += advance;
+ } else {
+ allGlyphsInvisible = false;
+ uint32_t glyphCount = glyphData->GetGlyphCount();
+ if (glyphCount > 0) {
+ const gfxTextRun::DetailedGlyph* details =
+ aTextRun->GetDetailedGlyphs(i);
+ NS_ASSERTION(details != nullptr,
+ "detailedGlyph record should not be missing!");
+ uint32_t j;
+ for (j = 0; j < glyphCount; ++j, ++details) {
+ double advance = details->mAdvance;
+ gfxRect glyphRect(0, -aMetrics.mAscent, advance,
+ aMetrics.mAscent + aMetrics.mDescent);
+ if (aIsRTL) {
+ // Swap left/right sidebearings of the glyph, because we're doing
+ // mirrored measurement.
+ glyphRect.MoveToX(advance - glyphRect.XMost());
+ // Move to current x position, mirroring any x-offset amount.
+ glyphRect.MoveByX(x - details->mOffset.x);
+ } else {
+ glyphRect.MoveByX(x + details->mOffset.x);
+ }
+ glyphRect.MoveByY(details->mOffset.y);
+ aMetrics.mBoundingBox = aMetrics.mBoundingBox.Union(glyphRect);
+ x += advance;
+ }
+ }
+ }
+ if (aSpacing) {
+ double space = aSpacing[i - aStart].mAfter;
+ if (i + 1 < aEnd) {
+ space += aSpacing[i + 1 - aStart].mBefore;
+ }
+ x += space;
+ }
+ }
+
+ aMetrics.mAdvanceWidth = x;
+ return allGlyphsInvisible;
+}
+
+gfxFont::RunMetrics gfxFont::Measure(const gfxTextRun* aTextRun,
+ uint32_t aStart, uint32_t aEnd,
+ BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget,
+ Spacing* aSpacing,
+ gfx::ShapedTextFlags aOrientation) {
+ // If aBoundingBoxType is TIGHT_HINTED_OUTLINE_EXTENTS
+ // and the underlying cairo font may be antialiased,
+ // we need to create a copy in order to avoid getting cached extents.
+ // This is only used by MathML layout at present.
+ if (aBoundingBoxType == TIGHT_HINTED_OUTLINE_EXTENTS &&
+ mAntialiasOption != kAntialiasNone) {
+ gfxFont* nonAA = mNonAAFont;
+ if (!nonAA) {
+ nonAA = CopyWithAntialiasOption(kAntialiasNone);
+ if (nonAA) {
+ if (!mNonAAFont.compareExchange(nullptr, nonAA)) {
+ delete nonAA;
+ nonAA = mNonAAFont;
+ }
+ }
+ }
+ // if font subclass doesn't implement CopyWithAntialiasOption(),
+ // it will return null and we'll proceed to use the existing font
+ if (nonAA) {
+ return nonAA->Measure(aTextRun, aStart, aEnd,
+ TIGHT_HINTED_OUTLINE_EXTENTS, aRefDrawTarget,
+ aSpacing, aOrientation);
+ }
+ }
+
+ const int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
+ // Current position in appunits
+ Orientation orientation =
+ aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
+ ? nsFontMetrics::eVertical
+ : nsFontMetrics::eHorizontal;
+ const gfxFont::Metrics& fontMetrics = GetMetrics(orientation);
+
+ gfxFloat baselineOffset = 0;
+ if (aTextRun->UseCenterBaseline() &&
+ orientation == nsFontMetrics::eHorizontal) {
+ // For a horizontal font being used in vertical writing mode with
+ // text-orientation:mixed, the overall metrics we're accumulating
+ // will be aimed at a center baseline. But this font's metrics were
+ // based on the alphabetic baseline. So we compute a baseline offset
+ // that will be applied to ascent/descent values and glyph rects
+ // to effectively shift them relative to the baseline.
+ // XXX Eventually we should probably use the BASE table, if present.
+ // But it usually isn't, so we need an ad hoc adjustment for now.
+ baselineOffset =
+ appUnitsPerDevUnit * (fontMetrics.emAscent - fontMetrics.emDescent) / 2;
+ }
+
+ RunMetrics metrics;
+ metrics.mAscent = fontMetrics.maxAscent * appUnitsPerDevUnit;
+ metrics.mDescent = fontMetrics.maxDescent * appUnitsPerDevUnit;
+
+ if (aStart == aEnd) {
+ // exit now before we look at aSpacing[0], which is undefined
+ metrics.mAscent -= baselineOffset;
+ metrics.mDescent += baselineOffset;
+ metrics.mBoundingBox =
+ gfxRect(0, -metrics.mAscent, 0, metrics.mAscent + metrics.mDescent);
+ return metrics;
+ }
+
+ gfxFloat advanceMin = 0, advanceMax = 0;
+ bool isRTL = aTextRun->IsRightToLeft();
+ bool needsGlyphExtents = NeedsGlyphExtents(this, aTextRun);
+ gfxGlyphExtents* extents =
+ ((aBoundingBoxType == LOOSE_INK_EXTENTS && !needsGlyphExtents &&
+ !aTextRun->HasDetailedGlyphs()) ||
+ MOZ_UNLIKELY(GetStyle()->AdjustedSizeMustBeZero()))
+ ? nullptr
+ : GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit());
+
+ bool allGlyphsInvisible;
+ if (extents) {
+ allGlyphsInvisible = MeasureGlyphs(
+ aTextRun, aStart, aEnd, aBoundingBoxType, aRefDrawTarget, aSpacing,
+ extents, isRTL, needsGlyphExtents, metrics, &advanceMin, &advanceMax);
+ } else {
+ allGlyphsInvisible =
+ MeasureGlyphs(aTextRun, aStart, aEnd, aBoundingBoxType, aRefDrawTarget,
+ aSpacing, isRTL, metrics);
+ }
+
+ if (allGlyphsInvisible) {
+ metrics.mBoundingBox.SetEmpty();
+ } else if (aBoundingBoxType == LOOSE_INK_EXTENTS) {
+ UnionRange(metrics.mAdvanceWidth, &advanceMin, &advanceMax);
+ gfxRect fontBox(advanceMin, -metrics.mAscent, advanceMax - advanceMin,
+ metrics.mAscent + metrics.mDescent);
+ metrics.mBoundingBox = metrics.mBoundingBox.Union(fontBox);
+ }
+
+ if (isRTL) {
+ // Reverse the effect of having swapped each glyph's sidebearings, to get
+ // the correct sidebearings of the merged bounding box.
+ metrics.mBoundingBox.MoveToX(metrics.mAdvanceWidth -
+ metrics.mBoundingBox.XMost());
+ }
+
+ // If the font may be rendered with a fake-italic effect, we need to allow
+ // for the top-right of the glyphs being skewed to the right, and the
+ // bottom-left being skewed further left.
+ gfxFloat skew = SkewForSyntheticOblique();
+ if (skew != 0.0) {
+ gfxFloat extendLeftEdge, extendRightEdge;
+ if (orientation == nsFontMetrics::eVertical) {
+ // The glyph will actually be skewed vertically, but "left" and "right"
+ // here refer to line-left (physical top) and -right (bottom), so these
+ // are still the directions in which we need to extend the box.
+ extendLeftEdge = skew < 0.0 ? ceil(-skew * metrics.mBoundingBox.XMost())
+ : ceil(skew * -metrics.mBoundingBox.X());
+ extendRightEdge = skew < 0.0 ? ceil(-skew * -metrics.mBoundingBox.X())
+ : ceil(skew * metrics.mBoundingBox.XMost());
+ } else {
+ extendLeftEdge = skew < 0.0 ? ceil(-skew * -metrics.mBoundingBox.Y())
+ : ceil(skew * metrics.mBoundingBox.YMost());
+ extendRightEdge = skew < 0.0 ? ceil(-skew * metrics.mBoundingBox.YMost())
+ : ceil(skew * -metrics.mBoundingBox.Y());
+ }
+ metrics.mBoundingBox.SetWidth(metrics.mBoundingBox.Width() +
+ extendLeftEdge + extendRightEdge);
+ metrics.mBoundingBox.MoveByX(-extendLeftEdge);
+ }
+
+ if (baselineOffset != 0) {
+ metrics.mAscent -= baselineOffset;
+ metrics.mDescent += baselineOffset;
+ metrics.mBoundingBox.MoveByY(baselineOffset);
+ }
+
+ return metrics;
+}
+
+bool gfxFont::AgeCachedWords() {
+ mozilla::AutoWriteLock lock(mLock);
+ if (mWordCache) {
+ for (auto it = mWordCache->Iter(); !it.Done(); it.Next()) {
+ CacheHashEntry* entry = it.Get();
+ if (!entry->mShapedWord) {
+ NS_ASSERTION(entry->mShapedWord, "cache entry has no gfxShapedWord!");
+ it.Remove();
+ } else if (entry->mShapedWord->IncrementAge() == kShapedWordCacheMaxAge) {
+ it.Remove();
+ }
+ }
+ return mWordCache->IsEmpty();
+ }
+ return true;
+}
+
+void gfxFont::NotifyGlyphsChanged() const {
+ AutoReadLock lock(mLock);
+ uint32_t i, count = mGlyphExtentsArray.Length();
+ for (i = 0; i < count; ++i) {
+ // Flush cached extents array
+ mGlyphExtentsArray[i]->NotifyGlyphsChanged();
+ }
+
+ if (mGlyphChangeObservers) {
+ for (const auto& key : *mGlyphChangeObservers) {
+ key->NotifyGlyphsChanged();
+ }
+ }
+}
+
+// If aChar is a "word boundary" for shaped-word caching purposes, return it;
+// else return 0.
+static char16_t IsBoundarySpace(char16_t aChar, char16_t aNextChar) {
+ if ((aChar == ' ' || aChar == 0x00A0) && !IsClusterExtender(aNextChar)) {
+ return aChar;
+ }
+ return 0;
+}
+
+#ifdef __GNUC__
+# define GFX_MAYBE_UNUSED __attribute__((unused))
+#else
+# define GFX_MAYBE_UNUSED
+#endif
+
+template <typename T, typename Func>
+bool gfxFont::ProcessShapedWordInternal(
+ DrawTarget* aDrawTarget, const T* aText, uint32_t aLength, uint32_t aHash,
+ Script aRunScript, nsAtom* aLanguage, bool aVertical,
+ int32_t aAppUnitsPerDevUnit, gfx::ShapedTextFlags aFlags,
+ RoundingFlags aRounding, gfxTextPerfMetrics* aTextPerf GFX_MAYBE_UNUSED,
+ Func aCallback) {
+ CacheHashKey key(aText, aLength, aHash, aRunScript, aLanguage,
+ aAppUnitsPerDevUnit, aFlags, aRounding);
+ {
+ // If we have a word cache, attempt to look up the word in it.
+ AutoReadLock lock(mLock);
+ if (mWordCache) {
+ // if there's a cached entry for this word, just return it
+ if (CacheHashEntry* entry = mWordCache->GetEntry(key)) {
+ gfxShapedWord* sw = entry->mShapedWord.get();
+ sw->ResetAge();
+#ifndef RELEASE_OR_BETA
+ if (aTextPerf) {
+ // XXX we should make sure this is atomic
+ aTextPerf->current.wordCacheHit++;
+ }
+#endif
+ aCallback(sw);
+ return true;
+ }
+ }
+ }
+
+ // We didn't find a cached word (or don't even have a cache yet), so create
+ // a new gfxShapedWord and cache it. We don't have to lock during shaping,
+ // only when it comes time to cache the new entry.
+
+ gfxShapedWord* sw =
+ gfxShapedWord::Create(aText, aLength, aRunScript, aLanguage,
+ aAppUnitsPerDevUnit, aFlags, aRounding);
+ if (!sw) {
+ NS_WARNING("failed to create gfxShapedWord - expect missing text");
+ return false;
+ }
+ DebugOnly<bool> ok = ShapeText(aDrawTarget, aText, 0, aLength, aRunScript,
+ aLanguage, aVertical, aRounding, sw);
+ NS_WARNING_ASSERTION(ok, "failed to shape word - expect garbled text");
+
+ {
+ // We're going to cache the new shaped word, so lock for writing now.
+ AutoWriteLock lock(mLock);
+ if (!mWordCache) {
+ mWordCache = MakeUnique<nsTHashtable<CacheHashEntry>>();
+ } else {
+ // If the cache is getting too big, flush it and start over.
+ uint32_t wordCacheMaxEntries =
+ gfxPlatform::GetPlatform()->WordCacheMaxEntries();
+ if (mWordCache->Count() > wordCacheMaxEntries) {
+ // Flush the cache if it is getting overly big.
+ NS_WARNING("flushing shaped-word cache");
+ ClearCachedWordsLocked();
+ }
+ }
+ CacheHashEntry* entry = mWordCache->PutEntry(key, fallible);
+ if (!entry) {
+ NS_WARNING("failed to create word cache entry - expect missing text");
+ delete sw;
+ return false;
+ }
+
+ // It's unlikely, but maybe another thread got there before us...
+ if (entry->mShapedWord) {
+ // Just discard the newly-created word, and use the existing one.
+ delete sw;
+ sw = entry->mShapedWord.get();
+ sw->ResetAge();
+#ifndef RELEASE_OR_BETA
+ if (aTextPerf) {
+ aTextPerf->current.wordCacheHit++;
+ }
+#endif
+ aCallback(sw);
+ return true;
+ }
+
+ entry->mShapedWord.reset(sw);
+
+#ifndef RELEASE_OR_BETA
+ if (aTextPerf) {
+ aTextPerf->current.wordCacheMiss++;
+ }
+#endif
+ aCallback(sw);
+ }
+
+ gfxFontCache::GetCache()->RunWordCacheExpirationTimer();
+ return true;
+}
+
+bool gfxFont::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const {
+ const gfxShapedWord* sw = mShapedWord.get();
+ if (!sw) {
+ return false;
+ }
+ if (sw->GetLength() != aKey->mLength || sw->GetFlags() != aKey->mFlags ||
+ sw->GetRounding() != aKey->mRounding ||
+ sw->GetAppUnitsPerDevUnit() != aKey->mAppUnitsPerDevUnit ||
+ sw->GetScript() != aKey->mScript ||
+ sw->GetLanguage() != aKey->mLanguage) {
+ return false;
+ }
+ if (sw->TextIs8Bit()) {
+ if (aKey->mTextIs8Bit) {
+ return (0 == memcmp(sw->Text8Bit(), aKey->mText.mSingle,
+ aKey->mLength * sizeof(uint8_t)));
+ }
+ // The key has 16-bit text, even though all the characters are < 256,
+ // so the TEXT_IS_8BIT flag was set and the cached ShapedWord we're
+ // comparing with will have 8-bit text.
+ const uint8_t* s1 = sw->Text8Bit();
+ const char16_t* s2 = aKey->mText.mDouble;
+ const char16_t* s2end = s2 + aKey->mLength;
+ while (s2 < s2end) {
+ if (*s1++ != *s2++) {
+ return false;
+ }
+ }
+ return true;
+ }
+ NS_ASSERTION(!(aKey->mFlags & gfx::ShapedTextFlags::TEXT_IS_8BIT) &&
+ !aKey->mTextIs8Bit,
+ "didn't expect 8-bit text here");
+ return (0 == memcmp(sw->TextUnicode(), aKey->mText.mDouble,
+ aKey->mLength * sizeof(char16_t)));
+}
+
+bool gfxFont::ProcessSingleSpaceShapedWord(
+ DrawTarget* aDrawTarget, bool aVertical, int32_t aAppUnitsPerDevUnit,
+ gfx::ShapedTextFlags aFlags, RoundingFlags aRounding,
+ const std::function<void(gfxShapedWord*)>& aCallback) {
+ static const uint8_t space = ' ';
+ return ProcessShapedWordInternal(
+ aDrawTarget, &space, 1, gfxShapedWord::HashMix(0, ' '), Script::LATIN,
+ /* aLanguage = */ nullptr, aVertical, aAppUnitsPerDevUnit, aFlags,
+ aRounding, nullptr, aCallback);
+}
+
+bool gfxFont::ShapeText(DrawTarget* aDrawTarget, const uint8_t* aText,
+ uint32_t aOffset, uint32_t aLength, Script aScript,
+ nsAtom* aLanguage, bool aVertical,
+ RoundingFlags aRounding, gfxShapedText* aShapedText) {
+ nsDependentCSubstring ascii((const char*)aText, aLength);
+ nsAutoString utf16;
+ AppendASCIItoUTF16(ascii, utf16);
+ if (utf16.Length() != aLength) {
+ return false;
+ }
+ return ShapeText(aDrawTarget, utf16.BeginReading(), aOffset, aLength, aScript,
+ aLanguage, aVertical, aRounding, aShapedText);
+}
+
+bool gfxFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText,
+ uint32_t aOffset, uint32_t aLength, Script aScript,
+ nsAtom* aLanguage, bool aVertical,
+ RoundingFlags aRounding, gfxShapedText* aShapedText) {
+ // XXX Currently, we do all vertical shaping through harfbuzz.
+ // Vertical graphite support may be wanted as a future enhancement.
+ // XXX Graphite shaping currently only supported on the main thread!
+ // Worker-thread shaping (offscreen canvas) will always go via harfbuzz.
+ if (FontCanSupportGraphite() && !aVertical && NS_IsMainThread()) {
+ if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
+ gfxGraphiteShaper* shaper = mGraphiteShaper;
+ if (!shaper) {
+ shaper = new gfxGraphiteShaper(this);
+ if (mGraphiteShaper.compareExchange(nullptr, shaper)) {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::BROWSER_USAGE_GRAPHITE, 1);
+ } else {
+ delete shaper;
+ shaper = mGraphiteShaper;
+ }
+ }
+ if (shaper->ShapeText(aDrawTarget, aText, aOffset, aLength, aScript,
+ aLanguage, aVertical, aRounding, aShapedText)) {
+ PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical,
+ aShapedText);
+ return true;
+ }
+ }
+ }
+
+ gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper();
+ if (shaper &&
+ shaper->ShapeText(aDrawTarget, aText, aOffset, aLength, aScript,
+ aLanguage, aVertical, aRounding, aShapedText)) {
+ PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical,
+ aShapedText);
+ if (GetFontEntry()->HasTrackingTable()) {
+ // Convert font size from device pixels back to CSS px
+ // to use in selecting tracking value
+ float trackSize = GetAdjustedSize() *
+ aShapedText->GetAppUnitsPerDevUnit() /
+ AppUnitsPerCSSPixel();
+ float tracking =
+ GetFontEntry()->TrackingForCSSPx(trackSize) * mFUnitsConvFactor;
+ // Applying tracking is a lot like the adjustment we do for
+ // synthetic bold: we want to apply between clusters, not to
+ // non-spacing glyphs within a cluster. So we can reuse that
+ // helper here.
+ aShapedText->AdjustAdvancesForSyntheticBold(tracking, aOffset, aLength);
+ }
+ return true;
+ }
+
+ NS_WARNING_ASSERTION(false, "shaper failed, expect scrambled/missing text");
+ return false;
+}
+
+void gfxFont::PostShapingFixup(DrawTarget* aDrawTarget, const char16_t* aText,
+ uint32_t aOffset, uint32_t aLength,
+ bool aVertical, gfxShapedText* aShapedText) {
+ if (ApplySyntheticBold()) {
+ const Metrics& metrics = GetMetrics(aVertical ? nsFontMetrics::eVertical
+ : nsFontMetrics::eHorizontal);
+ if (metrics.maxAdvance > metrics.aveCharWidth) {
+ aShapedText->AdjustAdvancesForSyntheticBold(GetSyntheticBoldOffset(),
+ aOffset, aLength);
+ }
+ }
+}
+
+#define MAX_SHAPING_LENGTH \
+ 32760 // slightly less than 32K, trying to avoid
+ // over-stressing platform shapers
+#define BACKTRACK_LIMIT \
+ 16 // backtrack this far looking for a good place
+ // to split into fragments for separate shaping
+
+template <typename T>
+bool gfxFont::ShapeFragmentWithoutWordCache(DrawTarget* aDrawTarget,
+ const T* aText, uint32_t aOffset,
+ uint32_t aLength, Script aScript,
+ nsAtom* aLanguage, bool aVertical,
+ RoundingFlags aRounding,
+ gfxTextRun* aTextRun) {
+ aTextRun->SetupClusterBoundaries(aOffset, aText, aLength);
+
+ bool ok = true;
+
+ while (ok && aLength > 0) {
+ uint32_t fragLen = aLength;
+
+ // limit the length of text we pass to shapers in a single call
+ if (fragLen > MAX_SHAPING_LENGTH) {
+ fragLen = MAX_SHAPING_LENGTH;
+
+ // in the 8-bit case, there are no multi-char clusters,
+ // so we don't need to do this check
+ if constexpr (sizeof(T) == sizeof(char16_t)) {
+ uint32_t i;
+ for (i = 0; i < BACKTRACK_LIMIT; ++i) {
+ if (aTextRun->IsClusterStart(aOffset + fragLen - i)) {
+ fragLen -= i;
+ break;
+ }
+ }
+ if (i == BACKTRACK_LIMIT) {
+ // if we didn't find any cluster start while backtracking,
+ // just check that we're not in the middle of a surrogate
+ // pair; back up by one code unit if we are.
+ if (NS_IS_SURROGATE_PAIR(aText[fragLen - 1], aText[fragLen])) {
+ --fragLen;
+ }
+ }
+ }
+ }
+
+ ok = ShapeText(aDrawTarget, aText, aOffset, fragLen, aScript, aLanguage,
+ aVertical, aRounding, aTextRun);
+
+ aText += fragLen;
+ aOffset += fragLen;
+ aLength -= fragLen;
+ }
+
+ return ok;
+}
+
+// Check if aCh is an unhandled control character that should be displayed
+// as a hexbox rather than rendered by some random font on the system.
+// We exclude \r as stray &#13;s are rather common (bug 941940).
+// Note that \n and \t don't come through here, as they have specific
+// meanings that have already been handled.
+static bool IsInvalidControlChar(uint32_t aCh) {
+ return aCh != '\r' && ((aCh & 0x7f) < 0x20 || aCh == 0x7f);
+}
+
+template <typename T>
+bool gfxFont::ShapeTextWithoutWordCache(DrawTarget* aDrawTarget, const T* aText,
+ uint32_t aOffset, uint32_t aLength,
+ Script aScript, nsAtom* aLanguage,
+ bool aVertical, RoundingFlags aRounding,
+ gfxTextRun* aTextRun) {
+ uint32_t fragStart = 0;
+ bool ok = true;
+
+ for (uint32_t i = 0; i <= aLength && ok; ++i) {
+ T ch = (i < aLength) ? aText[i] : '\n';
+ bool invalid = gfxFontGroup::IsInvalidChar(ch);
+ uint32_t length = i - fragStart;
+
+ // break into separate fragments when we hit an invalid char
+ if (!invalid) {
+ continue;
+ }
+
+ if (length > 0) {
+ ok = ShapeFragmentWithoutWordCache(
+ aDrawTarget, aText + fragStart, aOffset + fragStart, length, aScript,
+ aLanguage, aVertical, aRounding, aTextRun);
+ }
+
+ if (i == aLength) {
+ break;
+ }
+
+ // fragment was terminated by an invalid char: skip it,
+ // unless it's a control char that we want to show as a hexbox,
+ // but record where TAB or NEWLINE occur
+ if (ch == '\t') {
+ aTextRun->SetIsTab(aOffset + i);
+ } else if (ch == '\n') {
+ aTextRun->SetIsNewline(aOffset + i);
+ } else if (GetGeneralCategory(ch) == HB_UNICODE_GENERAL_CATEGORY_FORMAT) {
+ aTextRun->SetIsFormattingControl(aOffset + i);
+ } else if (IsInvalidControlChar(ch) &&
+ !(aTextRun->GetFlags() &
+ gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS)) {
+ if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) {
+ ShapeFragmentWithoutWordCache(aDrawTarget, aText + i, aOffset + i, 1,
+ aScript, aLanguage, aVertical, aRounding,
+ aTextRun);
+ } else {
+ aTextRun->SetMissingGlyph(aOffset + i, ch, this);
+ }
+ }
+ fragStart = i + 1;
+ }
+
+ NS_WARNING_ASSERTION(ok, "failed to shape text - expect garbled text");
+ return ok;
+}
+
+#ifndef RELEASE_OR_BETA
+# define TEXT_PERF_INCR(tp, m) (tp ? (tp)->current.m++ : 0)
+#else
+# define TEXT_PERF_INCR(tp, m)
+#endif
+
+inline static bool IsChar8Bit(uint8_t /*aCh*/) { return true; }
+inline static bool IsChar8Bit(char16_t aCh) { return aCh < 0x100; }
+
+inline static bool HasSpaces(const uint8_t* aString, uint32_t aLen) {
+ return memchr(aString, 0x20, aLen) != nullptr;
+}
+
+inline static bool HasSpaces(const char16_t* aString, uint32_t aLen) {
+ for (const char16_t* ch = aString; ch < aString + aLen; ch++) {
+ if (*ch == 0x20) {
+ return true;
+ }
+ }
+ return false;
+}
+
+template <typename T>
+bool gfxFont::SplitAndInitTextRun(
+ DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
+ const T* aString, // text for this font run
+ uint32_t aRunStart, // position in the textrun
+ uint32_t aRunLength, Script aRunScript, nsAtom* aLanguage,
+ ShapedTextFlags aOrientation) {
+ if (aRunLength == 0) {
+ return true;
+ }
+
+ gfxTextPerfMetrics* tp = nullptr;
+ RoundingFlags rounding = GetRoundOffsetsToPixels(aDrawTarget);
+
+#ifndef RELEASE_OR_BETA
+ tp = aTextRun->GetFontGroup()->GetTextPerfMetrics();
+ if (tp) {
+ if (mStyle.systemFont) {
+ tp->current.numChromeTextRuns++;
+ } else {
+ tp->current.numContentTextRuns++;
+ }
+ tp->current.numChars += aRunLength;
+ if (aRunLength > tp->current.maxTextRunLen) {
+ tp->current.maxTextRunLen = aRunLength;
+ }
+ }
+#endif
+
+ uint32_t wordCacheCharLimit =
+ gfxPlatform::GetPlatform()->WordCacheCharLimit();
+
+ bool vertical = aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
+
+ // If spaces can participate in shaping (e.g. within lookups for automatic
+ // fractions), need to shape without using the word cache which segments
+ // textruns on space boundaries. Word cache can be used if the textrun
+ // is short enough to fit in the word cache and it lacks spaces.
+ tainted_boolean_hint t_canParticipate =
+ SpaceMayParticipateInShaping(aRunScript);
+ bool canParticipate = t_canParticipate.unverified_safe_because(
+ "We need to ensure that this function operates safely independent of "
+ "t_canParticipate. The worst that can happen here is that the decision "
+ "to use the cache is incorrectly made, resulting in a bad "
+ "rendering/slowness. However, this would not compromise the memory "
+ "safety of Firefox in any way, and can thus be permitted");
+
+ if (canParticipate) {
+ if (aRunLength > wordCacheCharLimit || HasSpaces(aString, aRunLength)) {
+ TEXT_PERF_INCR(tp, wordCacheSpaceRules);
+ return ShapeTextWithoutWordCache(aDrawTarget, aString, aRunStart,
+ aRunLength, aRunScript, aLanguage,
+ vertical, rounding, aTextRun);
+ }
+ }
+
+ // the only flags we care about for ShapedWord construction/caching
+ gfx::ShapedTextFlags flags = aTextRun->GetFlags();
+ flags &= (gfx::ShapedTextFlags::TEXT_IS_RTL |
+ gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES |
+ gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT |
+ gfx::ShapedTextFlags::TEXT_ORIENT_MASK);
+ if constexpr (sizeof(T) == sizeof(uint8_t)) {
+ flags |= gfx::ShapedTextFlags::TEXT_IS_8BIT;
+ }
+
+ uint32_t wordStart = 0;
+ uint32_t hash = 0;
+ bool wordIs8Bit = true;
+ int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
+
+ T nextCh = aString[0];
+ for (uint32_t i = 0; i <= aRunLength; ++i) {
+ T ch = nextCh;
+ nextCh = (i < aRunLength - 1) ? aString[i + 1] : '\n';
+ T boundary = IsBoundarySpace(ch, nextCh);
+ bool invalid = !boundary && gfxFontGroup::IsInvalidChar(ch);
+ uint32_t length = i - wordStart;
+
+ // break into separate ShapedWords when we hit an invalid char,
+ // or a boundary space (always handled individually),
+ // or the first non-space after a space
+ if (!boundary && !invalid) {
+ if (!IsChar8Bit(ch)) {
+ wordIs8Bit = false;
+ }
+ // include this character in the hash, and move on to next
+ hash = gfxShapedWord::HashMix(hash, ch);
+ continue;
+ }
+
+ // We've decided to break here (i.e. we're at the end of a "word");
+ // shape the word and add it to the textrun.
+ // For words longer than the limit, we don't use the
+ // font's word cache but just shape directly into the textrun.
+ if (length > wordCacheCharLimit) {
+ TEXT_PERF_INCR(tp, wordCacheLong);
+ bool ok = ShapeFragmentWithoutWordCache(
+ aDrawTarget, aString + wordStart, aRunStart + wordStart, length,
+ aRunScript, aLanguage, vertical, rounding, aTextRun);
+ if (!ok) {
+ return false;
+ }
+ } else if (length > 0) {
+ gfx::ShapedTextFlags wordFlags = flags;
+ // in the 8-bit version of this method, TEXT_IS_8BIT was
+ // already set as part of |flags|, so no need for a per-word
+ // adjustment here
+ if (sizeof(T) == sizeof(char16_t)) {
+ if (wordIs8Bit) {
+ wordFlags |= gfx::ShapedTextFlags::TEXT_IS_8BIT;
+ }
+ }
+ bool processed = ProcessShapedWordInternal(
+ aDrawTarget, aString + wordStart, length, hash, aRunScript, aLanguage,
+ vertical, appUnitsPerDevUnit, wordFlags, rounding, tp,
+ [&](gfxShapedWord* aShapedWord) {
+ aTextRun->CopyGlyphDataFrom(aShapedWord, aRunStart + wordStart);
+ });
+ if (!processed) {
+ return false; // failed, presumably out of memory?
+ }
+ }
+
+ if (boundary) {
+ // word was terminated by a space: add that to the textrun
+ MOZ_ASSERT(aOrientation != ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED,
+ "text-orientation:mixed should be resolved earlier");
+ if (boundary != ' ' || !aTextRun->SetSpaceGlyphIfSimple(
+ this, aRunStart + i, ch, aOrientation)) {
+ // Currently, the only "boundary" characters we recognize are
+ // space and no-break space, which are both 8-bit, so we force
+ // that flag (below). If we ever change IsBoundarySpace, we
+ // may need to revise this.
+ // Avoid tautological-constant-out-of-range-compare in 8-bit:
+ DebugOnly<char16_t> boundary16 = boundary;
+ NS_ASSERTION(boundary16 < 256, "unexpected boundary!");
+ bool processed = ProcessShapedWordInternal(
+ aDrawTarget, &boundary, 1, gfxShapedWord::HashMix(0, boundary),
+ aRunScript, aLanguage, vertical, appUnitsPerDevUnit,
+ flags | gfx::ShapedTextFlags::TEXT_IS_8BIT, rounding, tp,
+ [&](gfxShapedWord* aShapedWord) {
+ aTextRun->CopyGlyphDataFrom(aShapedWord, aRunStart + i);
+ if (boundary == ' ') {
+ aTextRun->GetCharacterGlyphs()[aRunStart + i].SetIsSpace();
+ }
+ });
+ if (!processed) {
+ return false;
+ }
+ }
+ hash = 0;
+ wordStart = i + 1;
+ wordIs8Bit = true;
+ continue;
+ }
+
+ if (i == aRunLength) {
+ break;
+ }
+
+ NS_ASSERTION(invalid, "how did we get here except via an invalid char?");
+
+ // word was terminated by an invalid char: skip it,
+ // unless it's a control char that we want to show as a hexbox,
+ // but record where TAB or NEWLINE occur
+ if (ch == '\t') {
+ aTextRun->SetIsTab(aRunStart + i);
+ } else if (ch == '\n') {
+ aTextRun->SetIsNewline(aRunStart + i);
+ } else if (GetGeneralCategory(ch) == HB_UNICODE_GENERAL_CATEGORY_FORMAT) {
+ aTextRun->SetIsFormattingControl(aRunStart + i);
+ } else if (IsInvalidControlChar(ch) &&
+ !(aTextRun->GetFlags() &
+ gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS)) {
+ if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) {
+ ShapeFragmentWithoutWordCache(aDrawTarget, aString + i, aRunStart + i,
+ 1, aRunScript, aLanguage, vertical,
+ rounding, aTextRun);
+ } else {
+ aTextRun->SetMissingGlyph(aRunStart + i, ch, this);
+ }
+ }
+
+ hash = 0;
+ wordStart = i + 1;
+ wordIs8Bit = true;
+ }
+
+ return true;
+}
+
+// Explicit instantiations of SplitAndInitTextRun, to avoid libxul link failure
+template bool gfxFont::SplitAndInitTextRun(
+ DrawTarget* aDrawTarget, gfxTextRun* aTextRun, const uint8_t* aString,
+ uint32_t aRunStart, uint32_t aRunLength, Script aRunScript,
+ nsAtom* aLanguage, ShapedTextFlags aOrientation);
+template bool gfxFont::SplitAndInitTextRun(
+ DrawTarget* aDrawTarget, gfxTextRun* aTextRun, const char16_t* aString,
+ uint32_t aRunStart, uint32_t aRunLength, Script aRunScript,
+ nsAtom* aLanguage, ShapedTextFlags aOrientation);
+
+template <>
+bool gfxFont::InitFakeSmallCapsRun(
+ nsPresContext* aPresContext, DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
+ const char16_t* aText, uint32_t aOffset, uint32_t aLength,
+ FontMatchType aMatchType, gfx::ShapedTextFlags aOrientation, Script aScript,
+ nsAtom* aLanguage, bool aSyntheticLower, bool aSyntheticUpper) {
+ bool ok = true;
+
+ RefPtr<gfxFont> smallCapsFont = GetSmallCapsFont();
+ if (!smallCapsFont) {
+ NS_WARNING("failed to get reduced-size font for smallcaps!");
+ smallCapsFont = this;
+ }
+
+ bool isCJK = gfxTextRun::IsCJKScript(aScript);
+
+ enum RunCaseAction { kNoChange, kUppercaseReduce, kUppercase };
+
+ RunCaseAction runAction = kNoChange;
+ uint32_t runStart = 0;
+
+ for (uint32_t i = 0; i <= aLength; ++i) {
+ uint32_t extraCodeUnits = 0; // Will be set to 1 if we need to consume
+ // a trailing surrogate as well as the
+ // current code unit.
+ RunCaseAction chAction = kNoChange;
+ // Unless we're at the end, figure out what treatment the current
+ // character will need.
+ if (i < aLength) {
+ uint32_t ch = aText[i];
+ if (i < aLength - 1 && NS_IS_SURROGATE_PAIR(ch, aText[i + 1])) {
+ ch = SURROGATE_TO_UCS4(ch, aText[i + 1]);
+ extraCodeUnits = 1;
+ }
+ // Characters that aren't the start of a cluster are ignored here.
+ // They get added to whatever lowercase/non-lowercase run we're in.
+ if (IsClusterExtender(ch)) {
+ chAction = runAction;
+ } else {
+ if (ch != ToUpperCase(ch) || SpecialUpper(ch)) {
+ // ch is lower case
+ chAction = (aSyntheticLower ? kUppercaseReduce : kNoChange);
+ } else if (ch != ToLowerCase(ch)) {
+ // ch is upper case
+ chAction = (aSyntheticUpper ? kUppercaseReduce : kNoChange);
+ if (aLanguage == nsGkAtoms::el) {
+ // In Greek, check for characters that will be modified by
+ // the GreekUpperCase mapping - this catches accented
+ // capitals where the accent is to be removed (bug 307039).
+ // These are handled by using the full-size font with the
+ // uppercasing transform.
+ mozilla::GreekCasing::State state;
+ bool markEta, updateEta;
+ uint32_t ch2 =
+ mozilla::GreekCasing::UpperCase(ch, state, markEta, updateEta);
+ if ((ch != ch2 || markEta) && !aSyntheticUpper) {
+ chAction = kUppercase;
+ }
+ }
+ }
+ }
+ }
+
+ // At the end of the text or when the current character needs different
+ // casing treatment from the current run, finish the run-in-progress
+ // and prepare to accumulate a new run.
+ // Note that we do not look at any source data for offset [i] here,
+ // as that would be invalid in the case where i==length.
+ if ((i == aLength || runAction != chAction) && runStart < i) {
+ uint32_t runLength = i - runStart;
+ gfxFont* f = this;
+ switch (runAction) {
+ case kNoChange:
+ // just use the current font and the existing string
+ aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, true,
+ aOrientation, isCJK);
+ if (!f->SplitAndInitTextRun(aDrawTarget, aTextRun, aText + runStart,
+ aOffset + runStart, runLength, aScript,
+ aLanguage, aOrientation)) {
+ ok = false;
+ }
+ break;
+
+ case kUppercaseReduce:
+ // use reduced-size font, then fall through to uppercase the text
+ f = smallCapsFont;
+ [[fallthrough]];
+
+ case kUppercase:
+ // apply uppercase transform to the string
+ nsDependentSubstring origString(aText + runStart, runLength);
+ nsAutoString convertedString;
+ AutoTArray<bool, 50> charsToMergeArray;
+ AutoTArray<bool, 50> deletedCharsArray;
+
+ StyleTextTransform globalTransform{StyleTextTransformCase::Uppercase,
+ {}};
+ // No mask needed; we're doing case conversion, not password-hiding.
+ const char16_t maskChar = 0;
+ bool mergeNeeded = nsCaseTransformTextRunFactory::TransformString(
+ origString, convertedString, Some(globalTransform), maskChar,
+ /* aCaseTransformsOnly = */ false, aLanguage, charsToMergeArray,
+ deletedCharsArray);
+
+ if (mergeNeeded) {
+ // This is the hard case: the transformation caused chars
+ // to be inserted or deleted, so we can't shape directly
+ // into the destination textrun but have to handle the
+ // mismatch of character positions.
+ gfxTextRunFactory::Parameters params = {
+ aDrawTarget, nullptr, nullptr,
+ nullptr, 0, aTextRun->GetAppUnitsPerDevUnit()};
+ RefPtr<gfxTextRun> tempRun(gfxTextRun::Create(
+ &params, convertedString.Length(), aTextRun->GetFontGroup(),
+ gfx::ShapedTextFlags(), nsTextFrameUtils::Flags()));
+ tempRun->AddGlyphRun(f, aMatchType, 0, true, aOrientation, isCJK);
+ if (!f->SplitAndInitTextRun(aDrawTarget, tempRun.get(),
+ convertedString.BeginReading(), 0,
+ convertedString.Length(), aScript,
+ aLanguage, aOrientation)) {
+ ok = false;
+ } else {
+ RefPtr<gfxTextRun> mergedRun(gfxTextRun::Create(
+ &params, runLength, aTextRun->GetFontGroup(),
+ gfx::ShapedTextFlags(), nsTextFrameUtils::Flags()));
+ MergeCharactersInTextRun(mergedRun.get(), tempRun.get(),
+ charsToMergeArray.Elements(),
+ deletedCharsArray.Elements());
+ gfxTextRun::Range runRange(0, runLength);
+ aTextRun->CopyGlyphDataFrom(mergedRun.get(), runRange,
+ aOffset + runStart);
+ }
+ } else {
+ aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, true,
+ aOrientation, isCJK);
+ if (!f->SplitAndInitTextRun(aDrawTarget, aTextRun,
+ convertedString.BeginReading(),
+ aOffset + runStart, runLength, aScript,
+ aLanguage, aOrientation)) {
+ ok = false;
+ }
+ }
+ break;
+ }
+
+ runStart = i;
+ }
+
+ i += extraCodeUnits;
+ if (i < aLength) {
+ runAction = chAction;
+ }
+ }
+
+ return ok;
+}
+
+template <>
+bool gfxFont::InitFakeSmallCapsRun(
+ nsPresContext* aPresContext, DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
+ const uint8_t* aText, uint32_t aOffset, uint32_t aLength,
+ FontMatchType aMatchType, gfx::ShapedTextFlags aOrientation, Script aScript,
+ nsAtom* aLanguage, bool aSyntheticLower, bool aSyntheticUpper) {
+ NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aText),
+ aLength);
+ return InitFakeSmallCapsRun(aPresContext, aDrawTarget, aTextRun,
+ static_cast<const char16_t*>(unicodeString.get()),
+ aOffset, aLength, aMatchType, aOrientation,
+ aScript, aLanguage, aSyntheticLower,
+ aSyntheticUpper);
+}
+
+already_AddRefed<gfxFont> gfxFont::GetSmallCapsFont() const {
+ gfxFontStyle style(*GetStyle());
+ style.size *= SMALL_CAPS_SCALE_FACTOR;
+ style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL;
+ gfxFontEntry* fe = GetFontEntry();
+ return fe->FindOrMakeFont(&style, mUnicodeRangeMap);
+}
+
+already_AddRefed<gfxFont> gfxFont::GetSubSuperscriptFont(
+ int32_t aAppUnitsPerDevPixel) const {
+ gfxFontStyle style(*GetStyle());
+ style.AdjustForSubSuperscript(aAppUnitsPerDevPixel);
+ gfxFontEntry* fe = GetFontEntry();
+ return fe->FindOrMakeFont(&style, mUnicodeRangeMap);
+}
+
+gfxGlyphExtents* gfxFont::GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit) {
+ uint32_t readCount;
+ {
+ AutoReadLock lock(mLock);
+ readCount = mGlyphExtentsArray.Length();
+ for (uint32_t i = 0; i < readCount; ++i) {
+ if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit)
+ return mGlyphExtentsArray[i].get();
+ }
+ }
+ AutoWriteLock lock(mLock);
+ // Re-check in case of race.
+ uint32_t count = mGlyphExtentsArray.Length();
+ for (uint32_t i = readCount; i < count; ++i) {
+ if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit)
+ return mGlyphExtentsArray[i].get();
+ }
+ gfxGlyphExtents* glyphExtents = new gfxGlyphExtents(aAppUnitsPerDevUnit);
+ if (glyphExtents) {
+ mGlyphExtentsArray.AppendElement(glyphExtents);
+ // Initialize the extents of a space glyph, assuming that spaces don't
+ // render anything!
+ glyphExtents->SetContainedGlyphWidthAppUnits(GetSpaceGlyph(), 0);
+ }
+ return glyphExtents;
+}
+
+void gfxFont::SetupGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphID,
+ bool aNeedTight, gfxGlyphExtents* aExtents) {
+ gfxRect svgBounds;
+ if (mFontEntry->TryGetSVGData(this) && mFontEntry->HasSVGGlyph(aGlyphID) &&
+ mFontEntry->GetSVGGlyphExtents(aDrawTarget, aGlyphID, GetAdjustedSize(),
+ &svgBounds)) {
+ gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit();
+ aExtents->SetTightGlyphExtents(
+ aGlyphID, gfxRect(svgBounds.X() * d2a, svgBounds.Y() * d2a,
+ svgBounds.Width() * d2a, svgBounds.Height() * d2a));
+ return;
+ }
+
+ if (mFontEntry->TryGetColorGlyphs() && mFontEntry->mCOLR &&
+ COLRFonts::GetColrTableVersion(mFontEntry->mCOLR) == 1) {
+ auto* shaper = GetHarfBuzzShaper();
+ if (shaper && shaper->IsInitialized()) {
+ RefPtr scaledFont = GetScaledFont(aDrawTarget);
+ Rect r = COLRFonts::GetColorGlyphBounds(
+ mFontEntry->mCOLR, shaper->GetHBFont(), aGlyphID, aDrawTarget,
+ scaledFont, mFUnitsConvFactor);
+ if (!r.IsEmpty()) {
+ gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit();
+ aExtents->SetTightGlyphExtents(
+ aGlyphID, gfxRect(r.X() * d2a, r.Y() * d2a, r.Width() * d2a,
+ r.Height() * d2a));
+ return;
+ }
+ }
+ }
+
+ gfxRect bounds;
+ GetGlyphBounds(aGlyphID, &bounds, mAntialiasOption == kAntialiasNone);
+
+ const Metrics& fontMetrics = GetMetrics(nsFontMetrics::eHorizontal);
+ int32_t appUnitsPerDevUnit = aExtents->GetAppUnitsPerDevUnit();
+ if (!aNeedTight && bounds.x >= 0.0 && bounds.y >= -fontMetrics.maxAscent &&
+ bounds.height + bounds.y <= fontMetrics.maxDescent) {
+ uint32_t appUnitsWidth =
+ uint32_t(ceil((bounds.x + bounds.width) * appUnitsPerDevUnit));
+ if (appUnitsWidth < gfxGlyphExtents::INVALID_WIDTH) {
+ aExtents->SetContainedGlyphWidthAppUnits(aGlyphID,
+ uint16_t(appUnitsWidth));
+ return;
+ }
+ }
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+ if (!aNeedTight) {
+ ++gGlyphExtentsSetupFallBackToTight;
+ }
+#endif
+
+ gfxFloat d2a = appUnitsPerDevUnit;
+ aExtents->SetTightGlyphExtents(
+ aGlyphID, gfxRect(bounds.x * d2a, bounds.y * d2a, bounds.width * d2a,
+ bounds.height * d2a));
+}
+
+// Try to initialize font metrics by reading sfnt tables directly;
+// set mIsValid=TRUE and return TRUE on success.
+// Return FALSE if the gfxFontEntry subclass does not
+// implement GetFontTable(), or for non-sfnt fonts where tables are
+// not available.
+// If this returns TRUE without setting the mIsValid flag, then we -did-
+// apparently find an sfnt, but it was too broken to be used.
+bool gfxFont::InitMetricsFromSfntTables(Metrics& aMetrics) {
+ mIsValid = false; // font is NOT valid in case of early return
+
+ const uint32_t kHheaTableTag = TRUETYPE_TAG('h', 'h', 'e', 'a');
+ const uint32_t kOS_2TableTag = TRUETYPE_TAG('O', 'S', '/', '2');
+
+ uint32_t len;
+
+ if (mFUnitsConvFactor < 0.0) {
+ // If the conversion factor from FUnits is not yet set,
+ // get the unitsPerEm from the 'head' table via the font entry
+ uint16_t unitsPerEm = GetFontEntry()->UnitsPerEm();
+ if (unitsPerEm == gfxFontEntry::kInvalidUPEM) {
+ return false;
+ }
+ mFUnitsConvFactor = GetAdjustedSize() / unitsPerEm;
+ }
+
+ // 'hhea' table is required for the advanceWidthMax field
+ gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag);
+ if (!hheaTable) {
+ return false; // no 'hhea' table -> not an sfnt
+ }
+ const MetricsHeader* hhea =
+ reinterpret_cast<const MetricsHeader*>(hb_blob_get_data(hheaTable, &len));
+ if (len < sizeof(MetricsHeader)) {
+ return false;
+ }
+
+#define SET_UNSIGNED(field, src) \
+ aMetrics.field = uint16_t(src) * mFUnitsConvFactor
+#define SET_SIGNED(field, src) aMetrics.field = int16_t(src) * mFUnitsConvFactor
+
+ SET_UNSIGNED(maxAdvance, hhea->advanceWidthMax);
+
+ // 'OS/2' table is optional, if not found we'll estimate xHeight
+ // and aveCharWidth by measuring glyphs
+ gfxFontEntry::AutoTable os2Table(mFontEntry, kOS_2TableTag);
+ if (os2Table) {
+ const OS2Table* os2 =
+ reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len));
+ // this should always be present in any valid OS/2 of any version
+ if (len >= offsetof(OS2Table, xAvgCharWidth) + sizeof(int16_t)) {
+ SET_SIGNED(aveCharWidth, os2->xAvgCharWidth);
+ }
+ }
+
+#undef SET_SIGNED
+#undef SET_UNSIGNED
+
+ hb_font_t* hbFont = gfxHarfBuzzShaper::CreateHBFont(this);
+ hb_position_t position;
+
+ auto FixedToFloat = [](hb_position_t f) -> gfxFloat { return f / 65536.0; };
+
+ if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_ASCENDER,
+ &position)) {
+ aMetrics.maxAscent = FixedToFloat(position);
+ }
+ if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER,
+ &position)) {
+ aMetrics.maxDescent = -FixedToFloat(position);
+ }
+ if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_LINE_GAP,
+ &position)) {
+ aMetrics.externalLeading = FixedToFloat(position);
+ }
+
+ if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_UNDERLINE_OFFSET,
+ &position)) {
+ aMetrics.underlineOffset = FixedToFloat(position);
+ }
+ if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_UNDERLINE_SIZE,
+ &position)) {
+ aMetrics.underlineSize = FixedToFloat(position);
+ }
+ if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_STRIKEOUT_OFFSET,
+ &position)) {
+ aMetrics.strikeoutOffset = FixedToFloat(position);
+ }
+ if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_STRIKEOUT_SIZE,
+ &position)) {
+ aMetrics.strikeoutSize = FixedToFloat(position);
+ }
+
+ // Although sxHeight and sCapHeight are signed fields, we consider
+ // zero/negative values to be erroneous and just ignore them.
+ if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_X_HEIGHT,
+ &position) &&
+ position > 0) {
+ aMetrics.xHeight = FixedToFloat(position);
+ }
+ if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_CAP_HEIGHT,
+ &position) &&
+ position > 0) {
+ aMetrics.capHeight = FixedToFloat(position);
+ }
+ hb_font_destroy(hbFont);
+
+ mIsValid = true;
+
+ return true;
+}
+
+static double RoundToNearestMultiple(double aValue, double aFraction) {
+ return floor(aValue / aFraction + 0.5) * aFraction;
+}
+
+void gfxFont::CalculateDerivedMetrics(Metrics& aMetrics) {
+ aMetrics.maxAscent =
+ ceil(RoundToNearestMultiple(aMetrics.maxAscent, 1 / 1024.0));
+ aMetrics.maxDescent =
+ ceil(RoundToNearestMultiple(aMetrics.maxDescent, 1 / 1024.0));
+
+ if (aMetrics.xHeight <= 0) {
+ // only happens if we couldn't find either font metrics
+ // or a char to measure;
+ // pick an arbitrary value that's better than zero
+ aMetrics.xHeight = aMetrics.maxAscent * DEFAULT_XHEIGHT_FACTOR;
+ }
+
+ // If we have a font that doesn't provide a capHeight value, use maxAscent
+ // as a reasonable fallback.
+ if (aMetrics.capHeight <= 0) {
+ aMetrics.capHeight = aMetrics.maxAscent;
+ }
+
+ aMetrics.maxHeight = aMetrics.maxAscent + aMetrics.maxDescent;
+
+ if (aMetrics.maxHeight - aMetrics.emHeight > 0.0) {
+ aMetrics.internalLeading = aMetrics.maxHeight - aMetrics.emHeight;
+ } else {
+ aMetrics.internalLeading = 0.0;
+ }
+
+ aMetrics.emAscent =
+ aMetrics.maxAscent * aMetrics.emHeight / aMetrics.maxHeight;
+ aMetrics.emDescent = aMetrics.emHeight - aMetrics.emAscent;
+
+ if (GetFontEntry()->IsFixedPitch()) {
+ // Some Quartz fonts are fixed pitch, but there's some glyph with a bigger
+ // advance than the average character width... this forces
+ // those fonts to be recognized like fixed pitch fonts by layout.
+ aMetrics.maxAdvance = aMetrics.aveCharWidth;
+ }
+
+ if (!aMetrics.strikeoutOffset) {
+ aMetrics.strikeoutOffset = aMetrics.xHeight * 0.5;
+ }
+ if (!aMetrics.strikeoutSize) {
+ aMetrics.strikeoutSize = aMetrics.underlineSize;
+ }
+}
+
+void gfxFont::SanitizeMetrics(gfxFont::Metrics* aMetrics,
+ bool aIsBadUnderlineFont) {
+ // Even if this font size is zero, this font is created with non-zero size.
+ // However, for layout and others, we should return the metrics of zero size
+ // font.
+ if (mStyle.AdjustedSizeMustBeZero()) {
+ memset(aMetrics, 0, sizeof(gfxFont::Metrics));
+ return;
+ }
+
+ // If the font entry has ascent/descent/lineGap-override values,
+ // replace the metrics from the font with the overrides.
+ gfxFloat adjustedSize = GetAdjustedSize();
+ if (mFontEntry->mAscentOverride >= 0.0) {
+ aMetrics->maxAscent = mFontEntry->mAscentOverride * adjustedSize;
+ aMetrics->maxHeight = aMetrics->maxAscent + aMetrics->maxDescent;
+ aMetrics->internalLeading =
+ std::max(0.0, aMetrics->maxHeight - aMetrics->emHeight);
+ }
+ if (mFontEntry->mDescentOverride >= 0.0) {
+ aMetrics->maxDescent = mFontEntry->mDescentOverride * adjustedSize;
+ aMetrics->maxHeight = aMetrics->maxAscent + aMetrics->maxDescent;
+ aMetrics->internalLeading =
+ std::max(0.0, aMetrics->maxHeight - aMetrics->emHeight);
+ }
+ if (mFontEntry->mLineGapOverride >= 0.0) {
+ aMetrics->externalLeading = mFontEntry->mLineGapOverride * adjustedSize;
+ }
+
+ aMetrics->underlineSize = std::max(1.0, aMetrics->underlineSize);
+ aMetrics->strikeoutSize = std::max(1.0, aMetrics->strikeoutSize);
+
+ aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -1.0);
+
+ if (aMetrics->maxAscent < 1.0) {
+ // We cannot draw strikeout line and overline in the ascent...
+ aMetrics->underlineSize = 0;
+ aMetrics->underlineOffset = 0;
+ aMetrics->strikeoutSize = 0;
+ aMetrics->strikeoutOffset = 0;
+ return;
+ }
+
+ /**
+ * Some CJK fonts have bad underline offset. Therefore, if this is such font,
+ * we need to lower the underline offset to bottom of *em* descent.
+ * However, if this is system font, we should not do this for the rendering
+ * compatibility with another application's UI on the platform.
+ * XXX Should not use this hack if the font size is too small?
+ * Such text cannot be read, this might be used for tight CSS
+ * rendering? (E.g., Acid2)
+ */
+ if (!mStyle.systemFont && aIsBadUnderlineFont) {
+ // First, we need 2 pixels between baseline and underline at least. Because
+ // many CJK characters put their glyphs on the baseline, so, 1 pixel is too
+ // close for CJK characters.
+ aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -2.0);
+
+ // Next, we put the underline to bottom of below of the descent space.
+ if (aMetrics->internalLeading + aMetrics->externalLeading >
+ aMetrics->underlineSize) {
+ aMetrics->underlineOffset =
+ std::min(aMetrics->underlineOffset, -aMetrics->emDescent);
+ } else {
+ aMetrics->underlineOffset =
+ std::min(aMetrics->underlineOffset,
+ aMetrics->underlineSize - aMetrics->emDescent);
+ }
+ }
+ // If underline positioned is too far from the text, descent position is
+ // preferred so that underline will stay within the boundary.
+ else if (aMetrics->underlineSize - aMetrics->underlineOffset >
+ aMetrics->maxDescent) {
+ if (aMetrics->underlineSize > aMetrics->maxDescent)
+ aMetrics->underlineSize = std::max(aMetrics->maxDescent, 1.0);
+ // The max underlineOffset is 1px (the min underlineSize is 1px, and min
+ // maxDescent is 0px.)
+ aMetrics->underlineOffset = aMetrics->underlineSize - aMetrics->maxDescent;
+ }
+
+ // If strikeout line is overflowed from the ascent, the line should be resized
+ // and moved for that being in the ascent space. Note that the strikeoutOffset
+ // is *middle* of the strikeout line position.
+ gfxFloat halfOfStrikeoutSize = floor(aMetrics->strikeoutSize / 2.0 + 0.5);
+ if (halfOfStrikeoutSize + aMetrics->strikeoutOffset > aMetrics->maxAscent) {
+ if (aMetrics->strikeoutSize > aMetrics->maxAscent) {
+ aMetrics->strikeoutSize = std::max(aMetrics->maxAscent, 1.0);
+ halfOfStrikeoutSize = floor(aMetrics->strikeoutSize / 2.0 + 0.5);
+ }
+ gfxFloat ascent = floor(aMetrics->maxAscent + 0.5);
+ aMetrics->strikeoutOffset = std::max(halfOfStrikeoutSize, ascent / 2.0);
+ }
+
+ // If overline is larger than the ascent, the line should be resized.
+ if (aMetrics->underlineSize > aMetrics->maxAscent) {
+ aMetrics->underlineSize = aMetrics->maxAscent;
+ }
+}
+
+// Create a Metrics record to be used for vertical layout. This should never
+// fail, as we've already decided this is a valid font. We do not have the
+// option of marking it invalid (as can happen if we're unable to read
+// horizontal metrics), because that could break a font that we're already
+// using for horizontal text.
+// So we will synthesize *something* usable here even if there aren't any of the
+// usual font tables (which can happen in the case of a legacy bitmap or Type1
+// font for which the platform-specific backend used platform APIs instead of
+// sfnt tables to create the horizontal metrics).
+void gfxFont::CreateVerticalMetrics() {
+ const uint32_t kHheaTableTag = TRUETYPE_TAG('h', 'h', 'e', 'a');
+ const uint32_t kVheaTableTag = TRUETYPE_TAG('v', 'h', 'e', 'a');
+ const uint32_t kPostTableTag = TRUETYPE_TAG('p', 'o', 's', 't');
+ const uint32_t kOS_2TableTag = TRUETYPE_TAG('O', 'S', '/', '2');
+ uint32_t len;
+
+ auto* metrics = new Metrics();
+ ::memset(metrics, 0, sizeof(Metrics));
+
+ // Some basic defaults, in case the font lacks any real metrics tables.
+ // TODO: consider what rounding (if any) we should apply to these.
+ metrics->emHeight = GetAdjustedSize();
+ metrics->emAscent = metrics->emHeight / 2;
+ metrics->emDescent = metrics->emHeight - metrics->emAscent;
+
+ metrics->maxAscent = metrics->emAscent;
+ metrics->maxDescent = metrics->emDescent;
+
+ const float UNINITIALIZED_LEADING = -10000.0f;
+ metrics->externalLeading = UNINITIALIZED_LEADING;
+
+ if (mFUnitsConvFactor < 0.0) {
+ uint16_t upem = GetFontEntry()->UnitsPerEm();
+ if (upem != gfxFontEntry::kInvalidUPEM) {
+ AutoWriteLock lock(mLock);
+ mFUnitsConvFactor = GetAdjustedSize() / upem;
+ }
+ }
+
+#define SET_UNSIGNED(field, src) \
+ metrics->field = uint16_t(src) * mFUnitsConvFactor
+#define SET_SIGNED(field, src) metrics->field = int16_t(src) * mFUnitsConvFactor
+
+ gfxFontEntry::AutoTable os2Table(mFontEntry, kOS_2TableTag);
+ if (os2Table && mFUnitsConvFactor >= 0.0) {
+ const OS2Table* os2 =
+ reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len));
+ // These fields should always be present in any valid OS/2 table
+ if (len >= offsetof(OS2Table, sTypoLineGap) + sizeof(int16_t)) {
+ SET_SIGNED(strikeoutSize, os2->yStrikeoutSize);
+ // Use ascent+descent from the horizontal metrics as the default
+ // advance (aveCharWidth) in vertical mode
+ gfxFloat ascentDescent =
+ gfxFloat(mFUnitsConvFactor) *
+ (int16_t(os2->sTypoAscender) - int16_t(os2->sTypoDescender));
+ metrics->aveCharWidth = std::max(metrics->emHeight, ascentDescent);
+ // Use xAvgCharWidth from horizontal metrics as minimum font extent
+ // for vertical layout, applying half of it to ascent and half to
+ // descent (to work with a default centered baseline).
+ gfxFloat halfCharWidth =
+ int16_t(os2->xAvgCharWidth) * gfxFloat(mFUnitsConvFactor) / 2;
+ metrics->maxAscent = std::max(metrics->maxAscent, halfCharWidth);
+ metrics->maxDescent = std::max(metrics->maxDescent, halfCharWidth);
+ }
+ }
+
+ // If we didn't set aveCharWidth from OS/2, try to read 'hhea' metrics
+ // and use the line height from its ascent/descent.
+ if (!metrics->aveCharWidth) {
+ gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag);
+ if (hheaTable && mFUnitsConvFactor >= 0.0) {
+ const MetricsHeader* hhea = reinterpret_cast<const MetricsHeader*>(
+ hb_blob_get_data(hheaTable, &len));
+ if (len >= sizeof(MetricsHeader)) {
+ SET_SIGNED(aveCharWidth,
+ int16_t(hhea->ascender) - int16_t(hhea->descender));
+ metrics->maxAscent = metrics->aveCharWidth / 2;
+ metrics->maxDescent = metrics->aveCharWidth - metrics->maxAscent;
+ }
+ }
+ }
+
+ // Read real vertical metrics if available.
+ metrics->ideographicWidth = -1.0;
+ metrics->zeroWidth = -1.0;
+ gfxFontEntry::AutoTable vheaTable(mFontEntry, kVheaTableTag);
+ if (vheaTable && mFUnitsConvFactor >= 0.0) {
+ const MetricsHeader* vhea = reinterpret_cast<const MetricsHeader*>(
+ hb_blob_get_data(vheaTable, &len));
+ if (len >= sizeof(MetricsHeader)) {
+ SET_UNSIGNED(maxAdvance, vhea->advanceWidthMax);
+ // Redistribute space between ascent/descent because we want a
+ // centered vertical baseline by default.
+ gfxFloat halfExtent =
+ 0.5 * gfxFloat(mFUnitsConvFactor) *
+ (int16_t(vhea->ascender) + std::abs(int16_t(vhea->descender)));
+ // Some bogus fonts have ascent and descent set to zero in 'vhea'.
+ // In that case we just ignore them and keep our synthetic values
+ // from above.
+ if (halfExtent > 0) {
+ metrics->maxAscent = halfExtent;
+ metrics->maxDescent = halfExtent;
+ SET_SIGNED(externalLeading, vhea->lineGap);
+ }
+ // Call gfxHarfBuzzShaper::GetGlyphVAdvance directly, as GetCharAdvance
+ // would potentially recurse if no v-advance is available and it attempts
+ // to fall back to a value from mVerticalMetrics.
+ if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) {
+ uint32_t gid = ProvidesGetGlyph()
+ ? GetGlyph(kWaterIdeograph, 0)
+ : shaper->GetNominalGlyph(kWaterIdeograph);
+ if (gid) {
+ int32_t advance = shaper->GetGlyphVAdvance(gid);
+ // Convert 16.16 fixed-point advance from the shaper to a float.
+ metrics->ideographicWidth =
+ advance < 0 ? metrics->aveCharWidth : advance / 65536.0;
+ }
+ gid = ProvidesGetGlyph() ? GetGlyph('0', 0)
+ : shaper->GetNominalGlyph('0');
+ if (gid) {
+ int32_t advance = shaper->GetGlyphVAdvance(gid);
+ metrics->zeroWidth =
+ advance < 0 ? metrics->aveCharWidth : advance / 65536.0;
+ }
+ }
+ }
+ }
+
+ // If we didn't set aveCharWidth above, we must be dealing with a non-sfnt
+ // font of some kind (Type1, bitmap, vector, ...), so fall back to using
+ // whatever the platform backend figured out for horizontal layout.
+ // And if we haven't set externalLeading yet, then copy that from the
+ // horizontal metrics as well, to help consistency of CSS line-height.
+ if (!metrics->aveCharWidth ||
+ metrics->externalLeading == UNINITIALIZED_LEADING) {
+ const Metrics& horizMetrics = GetHorizontalMetrics();
+ if (!metrics->aveCharWidth) {
+ metrics->aveCharWidth = horizMetrics.maxAscent + horizMetrics.maxDescent;
+ }
+ if (metrics->externalLeading == UNINITIALIZED_LEADING) {
+ metrics->externalLeading = horizMetrics.externalLeading;
+ }
+ }
+
+ // Get underline thickness from the 'post' table if available.
+ // We also read the underline position, although in vertical-upright mode
+ // this will not be appropriate to use directly (see nsTextFrame.cpp).
+ gfxFontEntry::AutoTable postTable(mFontEntry, kPostTableTag);
+ if (postTable) {
+ const PostTable* post =
+ reinterpret_cast<const PostTable*>(hb_blob_get_data(postTable, &len));
+ if (len >= offsetof(PostTable, underlineThickness) + sizeof(uint16_t)) {
+ static_assert(offsetof(PostTable, underlinePosition) <
+ offsetof(PostTable, underlineThickness),
+ "broken PostTable struct?");
+ SET_SIGNED(underlineOffset, post->underlinePosition);
+ SET_UNSIGNED(underlineSize, post->underlineThickness);
+ // Also use for strikeout if we didn't find that in OS/2 above.
+ if (!metrics->strikeoutSize) {
+ metrics->strikeoutSize = metrics->underlineSize;
+ }
+ }
+ }
+
+#undef SET_UNSIGNED
+#undef SET_SIGNED
+
+ // If we didn't read this from a vhea table, it will still be zero.
+ // In any case, let's make sure it is not less than the value we've
+ // come up with for aveCharWidth.
+ metrics->maxAdvance = std::max(metrics->maxAdvance, metrics->aveCharWidth);
+
+ // Thickness of underline and strikeout may have been read from tables,
+ // but in case they were not present, ensure a minimum of 1 pixel.
+ metrics->underlineSize = std::max(1.0, metrics->underlineSize);
+
+ metrics->strikeoutSize = std::max(1.0, metrics->strikeoutSize);
+ metrics->strikeoutOffset = -0.5 * metrics->strikeoutSize;
+
+ // Somewhat arbitrary values for now, subject to future refinement...
+ metrics->spaceWidth = metrics->aveCharWidth;
+ metrics->maxHeight = metrics->maxAscent + metrics->maxDescent;
+ metrics->xHeight = metrics->emHeight / 2;
+ metrics->capHeight = metrics->maxAscent;
+
+ if (metrics->zeroWidth < 0.0) {
+ metrics->zeroWidth = metrics->aveCharWidth;
+ }
+
+ if (!mVerticalMetrics.compareExchange(nullptr, metrics)) {
+ delete metrics;
+ }
+}
+
+gfxFloat gfxFont::SynthesizeSpaceWidth(uint32_t aCh) {
+ // return an appropriate width for various Unicode space characters
+ // that we "fake" if they're not actually present in the font;
+ // returns negative value if the char is not a known space.
+ switch (aCh) {
+ case 0x2000: // en quad
+ case 0x2002:
+ return GetAdjustedSize() / 2; // en space
+ case 0x2001: // em quad
+ case 0x2003:
+ return GetAdjustedSize(); // em space
+ case 0x2004:
+ return GetAdjustedSize() / 3; // three-per-em space
+ case 0x2005:
+ return GetAdjustedSize() / 4; // four-per-em space
+ case 0x2006:
+ return GetAdjustedSize() / 6; // six-per-em space
+ case 0x2007:
+ return GetMetrics(nsFontMetrics::eHorizontal)
+ .ZeroOrAveCharWidth(); // figure space
+ case 0x2008:
+ return GetMetrics(nsFontMetrics::eHorizontal)
+ .spaceWidth; // punctuation space
+ case 0x2009:
+ return GetAdjustedSize() / 5; // thin space
+ case 0x200a:
+ return GetAdjustedSize() / 10; // hair space
+ case 0x202f:
+ return GetAdjustedSize() / 5; // narrow no-break space
+ case 0x3000:
+ return GetAdjustedSize(); // ideographic space
+ default:
+ return -1.0;
+ }
+}
+
+void gfxFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const {
+ AutoReadLock lock(mLock);
+ for (uint32_t i = 0; i < mGlyphExtentsArray.Length(); ++i) {
+ aSizes->mFontInstances +=
+ mGlyphExtentsArray[i]->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ if (mWordCache) {
+ aSizes->mShapedWords += mWordCache->SizeOfIncludingThis(aMallocSizeOf);
+ }
+}
+
+void gfxFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const {
+ aSizes->mFontInstances += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
+
+void gfxFont::AddGlyphChangeObserver(GlyphChangeObserver* aObserver) {
+ AutoWriteLock lock(mLock);
+ if (!mGlyphChangeObservers) {
+ mGlyphChangeObservers = MakeUnique<nsTHashSet<GlyphChangeObserver*>>();
+ }
+ mGlyphChangeObservers->Insert(aObserver);
+}
+
+void gfxFont::RemoveGlyphChangeObserver(GlyphChangeObserver* aObserver) {
+ AutoWriteLock lock(mLock);
+ NS_ASSERTION(mGlyphChangeObservers, "No observers registered");
+ NS_ASSERTION(mGlyphChangeObservers->Contains(aObserver),
+ "Observer not registered");
+ mGlyphChangeObservers->Remove(aObserver);
+}
+
+#define DEFAULT_PIXEL_FONT_SIZE 16.0f
+
+gfxFontStyle::gfxFontStyle()
+ : size(DEFAULT_PIXEL_FONT_SIZE),
+ sizeAdjust(0.0f),
+ baselineOffset(0.0f),
+ languageOverride(NO_FONT_LANGUAGE_OVERRIDE),
+ fontSmoothingBackgroundColor(NS_RGBA(0, 0, 0, 0)),
+ weight(FontWeight::NORMAL),
+ stretch(FontStretch::NORMAL),
+ style(FontSlantStyle::NORMAL),
+ variantCaps(NS_FONT_VARIANT_CAPS_NORMAL),
+ variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL),
+ sizeAdjustBasis(uint8_t(FontSizeAdjust::Tag::None)),
+ systemFont(true),
+ printerFont(false),
+ useGrayscaleAntialiasing(false),
+ allowSyntheticWeight(true),
+ allowSyntheticStyle(true),
+ allowSyntheticSmallCaps(true),
+ noFallbackVariantFeatures(true) {}
+
+gfxFontStyle::gfxFontStyle(FontSlantStyle aStyle, FontWeight aWeight,
+ FontStretch aStretch, gfxFloat aSize,
+ const FontSizeAdjust& aSizeAdjust, bool aSystemFont,
+ bool aPrinterFont, bool aAllowWeightSynthesis,
+ bool aAllowStyleSynthesis,
+ bool aAllowSmallCapsSynthesis,
+ uint32_t aLanguageOverride)
+ : size(aSize),
+ baselineOffset(0.0f),
+ languageOverride(aLanguageOverride),
+ fontSmoothingBackgroundColor(NS_RGBA(0, 0, 0, 0)),
+ weight(aWeight),
+ stretch(aStretch),
+ style(aStyle),
+ variantCaps(NS_FONT_VARIANT_CAPS_NORMAL),
+ variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL),
+ systemFont(aSystemFont),
+ printerFont(aPrinterFont),
+ useGrayscaleAntialiasing(false),
+ allowSyntheticWeight(aAllowWeightSynthesis),
+ allowSyntheticStyle(aAllowStyleSynthesis),
+ allowSyntheticSmallCaps(aAllowSmallCapsSynthesis),
+ noFallbackVariantFeatures(true) {
+ MOZ_ASSERT(!std::isnan(size));
+
+ switch (aSizeAdjust.tag) {
+ case FontSizeAdjust::Tag::None:
+ sizeAdjust = 0.0f;
+ break;
+ case FontSizeAdjust::Tag::ExHeight:
+ sizeAdjust = aSizeAdjust.AsExHeight();
+ break;
+ case FontSizeAdjust::Tag::CapHeight:
+ sizeAdjust = aSizeAdjust.AsCapHeight();
+ break;
+ case FontSizeAdjust::Tag::ChWidth:
+ sizeAdjust = aSizeAdjust.AsChWidth();
+ break;
+ case FontSizeAdjust::Tag::IcWidth:
+ sizeAdjust = aSizeAdjust.AsIcWidth();
+ break;
+ case FontSizeAdjust::Tag::IcHeight:
+ sizeAdjust = aSizeAdjust.AsIcHeight();
+ break;
+ }
+ MOZ_ASSERT(!std::isnan(sizeAdjust));
+
+ sizeAdjustBasis = uint8_t(aSizeAdjust.tag);
+ // sizeAdjustBasis is currently a small bitfield, so let's assert that the
+ // tag value was not truncated.
+ MOZ_ASSERT(FontSizeAdjust::Tag(sizeAdjustBasis) == aSizeAdjust.tag,
+ "gfxFontStyle.sizeAdjustBasis too small?");
+
+ if (weight > FontWeight::FromInt(1000)) {
+ weight = FontWeight::FromInt(1000);
+ }
+ if (weight < FontWeight::FromInt(1)) {
+ weight = FontWeight::FromInt(1);
+ }
+
+ if (size >= FONT_MAX_SIZE) {
+ size = FONT_MAX_SIZE;
+ sizeAdjust = 0.0f;
+ sizeAdjustBasis = uint8_t(FontSizeAdjust::Tag::None);
+ } else if (size < 0.0) {
+ NS_WARNING("negative font size");
+ size = 0.0;
+ }
+}
+
+PLDHashNumber gfxFontStyle::Hash() const {
+ uint32_t hash = variationSettings.IsEmpty()
+ ? 0
+ : mozilla::HashBytes(variationSettings.Elements(),
+ variationSettings.Length() *
+ sizeof(gfxFontVariation));
+ return mozilla::AddToHash(hash, systemFont, style.Raw(), stretch.Raw(),
+ weight.Raw(), size, int32_t(sizeAdjust * 1000.0f));
+}
+
+void gfxFontStyle::AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel) {
+ MOZ_ASSERT(
+ variantSubSuper != NS_FONT_VARIANT_POSITION_NORMAL && baselineOffset == 0,
+ "can't adjust this style for sub/superscript");
+
+ // calculate the baseline offset (before changing the size)
+ if (variantSubSuper == NS_FONT_VARIANT_POSITION_SUPER) {
+ baselineOffset = size * -NS_FONT_SUPERSCRIPT_OFFSET_RATIO;
+ } else {
+ baselineOffset = size * NS_FONT_SUBSCRIPT_OFFSET_RATIO;
+ }
+
+ // calculate reduced size, roughly mimicing behavior of font-size: smaller
+ float cssSize = size * aAppUnitsPerDevPixel / AppUnitsPerCSSPixel();
+ if (cssSize < NS_FONT_SUB_SUPER_SMALL_SIZE) {
+ size *= NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL;
+ } else if (cssSize >= NS_FONT_SUB_SUPER_LARGE_SIZE) {
+ size *= NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE;
+ } else {
+ gfxFloat t = (cssSize - NS_FONT_SUB_SUPER_SMALL_SIZE) /
+ (NS_FONT_SUB_SUPER_LARGE_SIZE - NS_FONT_SUB_SUPER_SMALL_SIZE);
+ size *= (1.0 - t) * NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL +
+ t * NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE;
+ }
+
+ // clear the variant field
+ variantSubSuper = NS_FONT_VARIANT_POSITION_NORMAL;
+}
+
+bool gfxFont::TryGetMathTable() {
+ if (mMathInitialized) {
+ return !!mMathTable;
+ }
+
+ auto face(GetFontEntry()->GetHBFace());
+ if (hb_ot_math_has_data(face)) {
+ auto* mathTable = new gfxMathTable(face, GetAdjustedSize());
+ if (!mMathTable.compareExchange(nullptr, mathTable)) {
+ delete mathTable;
+ }
+ }
+ mMathInitialized = true;
+
+ return !!mMathTable;
+}
diff --git a/gfx/thebes/gfxFont.h b/gfx/thebes/gfxFont.h
new file mode 100644
index 0000000000..3a3d13c810
--- /dev/null
+++ b/gfx/thebes/gfxFont.h
@@ -0,0 +1,2375 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=4 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_FONT_H
+#define GFX_FONT_H
+
+#include <new>
+#include <utility>
+#include <functional>
+#include "PLDHashTable.h"
+#include "ThebesRLBoxTypes.h"
+#include "gfxFontVariations.h"
+#include "gfxRect.h"
+#include "gfxTypes.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/MruCache.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/RWLock.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/gfx/MatrixFwd.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/intl/UnicodeScriptCodes.h"
+#include "nsCOMPtr.h"
+#include "nsColor.h"
+#include "nsTHashMap.h"
+#include "nsTHashSet.h"
+#include "nsExpirationTracker.h"
+#include "nsFontMetrics.h"
+#include "nsHashKeys.h"
+#include "nsIMemoryReporter.h"
+#include "nsIObserver.h"
+#include "nsISupports.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+#include "nscore.h"
+#include "DrawMode.h"
+
+// Only required for function bodies
+#include "gfxFontEntry.h"
+#include "gfxFontFeatures.h"
+
+class gfxContext;
+class gfxGraphiteShaper;
+class gfxHarfBuzzShaper;
+class gfxGlyphExtents;
+class gfxMathTable;
+class gfxPattern;
+class gfxShapedText;
+class gfxShapedWord;
+class gfxSkipChars;
+class gfxTextRun;
+class nsIEventTarget;
+class nsITimer;
+struct gfxTextRunDrawCallbacks;
+
+namespace mozilla {
+class SVGContextPaint;
+namespace layout {
+class TextDrawTarget;
+}
+} // namespace mozilla
+
+typedef struct _cairo cairo_t;
+typedef struct _cairo_scaled_font cairo_scaled_font_t;
+
+#define FONT_MAX_SIZE 2000.0
+
+#define SMALL_CAPS_SCALE_FACTOR 0.8
+
+// The skew factor used for synthetic-italic [oblique] fonts;
+// we use a platform-dependent value to harmonize with the platform's own APIs.
+#ifdef XP_WIN
+# define OBLIQUE_SKEW_FACTOR 0.3f
+#elif defined(MOZ_WIDGET_GTK)
+# define OBLIQUE_SKEW_FACTOR 0.2f
+#else
+# define OBLIQUE_SKEW_FACTOR 0.25f
+#endif
+
+struct gfxFontStyle {
+ using FontStretch = mozilla::FontStretch;
+ using FontSlantStyle = mozilla::FontSlantStyle;
+ using FontWeight = mozilla::FontWeight;
+ using FontSizeAdjust = mozilla::StyleFontSizeAdjust;
+
+ gfxFontStyle();
+ gfxFontStyle(FontSlantStyle aStyle, FontWeight aWeight, FontStretch aStretch,
+ gfxFloat aSize, const FontSizeAdjust& aSizeAdjust,
+ bool aSystemFont, bool aPrinterFont, bool aWeightSynthesis,
+ bool aStyleSynthesis, bool aSmallCapsSynthesis,
+ uint32_t aLanguageOverride);
+ // Features are composed of (1) features from style rules (2) features
+ // from feature settings rules and (3) family-specific features. (1) and
+ // (3) are guaranteed to be mutually exclusive
+
+ // custom opentype feature settings
+ CopyableTArray<gfxFontFeature> featureSettings;
+
+ // Some font-variant property values require font-specific settings
+ // defined via @font-feature-values rules. These are resolved after
+ // font matching occurs.
+
+ // -- list of value tags for specific alternate features
+ mozilla::StyleFontVariantAlternates variantAlternates;
+
+ // -- object used to look these up once the font is matched
+ RefPtr<gfxFontFeatureValueSet> featureValueLookup;
+
+ // opentype variation settings
+ CopyableTArray<gfxFontVariation> variationSettings;
+
+ // The logical size of the font, in pixels
+ gfxFloat size;
+
+ // The optical size value to apply (if supported); negative means none.
+ float autoOpticalSize = -1.0f;
+
+ // The aspect-value (ie., the ratio actualsize:actualxheight) that any
+ // actual physical font created from this font structure must have when
+ // rendering or measuring a string. A value of -1.0 means no adjustment
+ // needs to be done; otherwise the value must be nonnegative.
+ float sizeAdjust;
+
+ // baseline offset, used when simulating sub/superscript glyphs
+ float baselineOffset;
+
+ // Language system tag, to override document language;
+ // an OpenType "language system" tag represented as a 32-bit integer
+ // (see http://www.microsoft.com/typography/otspec/languagetags.htm).
+ // Normally 0, so font rendering will use the document or element language
+ // (see above) to control any language-specific rendering, but the author
+ // can override this for cases where the options implemented in the font
+ // do not directly match the actual language. (E.g. lang may be Macedonian,
+ // but the font in use does not explicitly support this; the author can
+ // use font-language-override to request the Serbian option in the font
+ // in order to get correct glyph shapes.)
+ uint32_t languageOverride;
+
+ // The estimated background color behind the text. Enables a special
+ // rendering mode when NS_GET_A(.) > 0. Only used for text in the chrome.
+ nscolor fontSmoothingBackgroundColor;
+
+ // The Font{Weight,Stretch,SlantStyle} fields are each a 16-bit type.
+
+ // The weight of the font: 100, 200, ... 900.
+ FontWeight weight;
+
+ // The stretch of the font
+ FontStretch stretch;
+
+ // The style of font
+ FontSlantStyle style;
+
+ // Whether face-selection properties weight/style/stretch are all 'normal'
+ bool IsNormalStyle() const {
+ return weight.IsNormal() && style.IsNormal() && stretch.IsNormal();
+ }
+
+ // We pack these three small-integer fields into a single byte to avoid
+ // overflowing an 8-byte boundary [in a 64-bit build] and ending up with
+ // 7 bytes of padding at the end of the struct.
+
+ // caps variant (small-caps, petite-caps, etc.)
+ uint8_t variantCaps : 3; // uses range 0..6
+
+ // sub/superscript variant
+ uint8_t variantSubSuper : 2; // uses range 0..2
+
+ // font metric used as basis of font-size-adjust
+ uint8_t sizeAdjustBasis : 3; // uses range 0..4
+
+ // Say that this font is a system font and therefore does not
+ // require certain fixup that we do for fonts from untrusted
+ // sources.
+ bool systemFont : 1;
+
+ // Say that this font is used for print or print preview.
+ bool printerFont : 1;
+
+ // Used to imitate -webkit-font-smoothing: antialiased
+ bool useGrayscaleAntialiasing : 1;
+
+ // Whether synthetic styles are allowed
+ bool allowSyntheticWeight : 1;
+ bool allowSyntheticStyle : 1;
+ bool allowSyntheticSmallCaps : 1;
+
+ // some variant features require fallback which complicates the shaping
+ // code, so set up a bool to indicate when shaping with fallback is needed
+ bool noFallbackVariantFeatures : 1;
+
+ // Return the final adjusted font size for the given aspect ratio.
+ // Not meant to be called when sizeAdjustBasis is NONE.
+ gfxFloat GetAdjustedSize(gfxFloat aspect) const {
+ MOZ_ASSERT(
+ FontSizeAdjust::Tag(sizeAdjustBasis) != FontSizeAdjust::Tag::None,
+ "Not meant to be called when sizeAdjustBasis is none");
+ gfxFloat adjustedSize =
+ std::max(NS_round(size * (sizeAdjust / aspect)), 1.0);
+ return std::min(adjustedSize, FONT_MAX_SIZE);
+ }
+
+ // Some callers want to take a short-circuit path if they can be sure the
+ // adjusted size will be zero.
+ bool AdjustedSizeMustBeZero() const {
+ return size == 0.0 ||
+ (FontSizeAdjust::Tag(sizeAdjustBasis) != FontSizeAdjust::Tag::None &&
+ sizeAdjust == 0.0);
+ }
+
+ PLDHashNumber Hash() const;
+
+ // Adjust this style to simulate sub/superscript (as requested in the
+ // variantSubSuper field) using size and baselineOffset instead.
+ void AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel);
+
+ // Should this style cause the given font entry to use synthetic bold?
+ bool NeedsSyntheticBold(gfxFontEntry* aFontEntry) const {
+ return weight.IsBold() && allowSyntheticWeight &&
+ !aFontEntry->SupportsBold();
+ }
+
+ bool Equals(const gfxFontStyle& other) const {
+ return mozilla::NumbersAreBitwiseIdentical(size, other.size) &&
+ (style == other.style) && (weight == other.weight) &&
+ (stretch == other.stretch) && (variantCaps == other.variantCaps) &&
+ (variantSubSuper == other.variantSubSuper) &&
+ (allowSyntheticWeight == other.allowSyntheticWeight) &&
+ (allowSyntheticStyle == other.allowSyntheticStyle) &&
+ (systemFont == other.systemFont) &&
+ (printerFont == other.printerFont) &&
+ (useGrayscaleAntialiasing == other.useGrayscaleAntialiasing) &&
+ (baselineOffset == other.baselineOffset) &&
+ mozilla::NumbersAreBitwiseIdentical(sizeAdjust, other.sizeAdjust) &&
+ (sizeAdjustBasis == other.sizeAdjustBasis) &&
+ (featureSettings == other.featureSettings) &&
+ (variantAlternates == other.variantAlternates) &&
+ (featureValueLookup == other.featureValueLookup) &&
+ (variationSettings == other.variationSettings) &&
+ (languageOverride == other.languageOverride) &&
+ mozilla::NumbersAreBitwiseIdentical(autoOpticalSize,
+ other.autoOpticalSize) &&
+ (fontSmoothingBackgroundColor == other.fontSmoothingBackgroundColor);
+ }
+};
+
+/**
+ * Font cache design:
+ *
+ * The mFonts hashtable contains most fonts, indexed by (gfxFontEntry*, style).
+ * It maintains a strong reference to the fonts it contains.
+ * Whenever a font is accessed, it is marked as used to move it to a new
+ * generation in the tracker to avoid expiration.
+ * The expiration tracker will only expire fonts with a single reference, the
+ * cache itself. Fonts with more than one reference are marked as used.
+ *
+ * We're using 3 generations with a ten-second generation interval, so
+ * zero-refcount fonts will be deleted 20-30 seconds after their refcount
+ * goes to zero, if timer events fire in a timely manner.
+ *
+ * The font cache also handles timed expiration of cached ShapedWords
+ * for "persistent" fonts: it has a repeating timer, and notifies
+ * each cached font to "age" its shaped words. The words will be released
+ * by the fonts if they get aged three times without being re-used in the
+ * meantime.
+ *
+ * Note that the ShapedWord timeout is much larger than the font timeout,
+ * so that in the case of a short-lived font, we'll discard the gfxFont
+ * completely, with all its words, and avoid the cost of aging the words
+ * individually. That only happens with longer-lived fonts.
+ */
+struct FontCacheSizes {
+ FontCacheSizes() : mFontInstances(0), mShapedWords(0) {}
+
+ size_t mFontInstances; // memory used by instances of gfxFont subclasses
+ size_t mShapedWords; // memory used by the per-font shapedWord caches
+};
+
+class gfxFontCache final
+ : public ExpirationTrackerImpl<gfxFont, 3, mozilla::Mutex,
+ mozilla::MutexAutoLock> {
+ protected:
+ // Expiration tracker implementation.
+ enum { FONT_TIMEOUT_SECONDS = 10 };
+
+ typedef mozilla::Mutex Lock;
+ typedef mozilla::MutexAutoLock AutoLock;
+
+ // This protects the ExpirationTracker tables.
+ Lock mMutex = Lock("fontCacheExpirationMutex");
+
+ Lock& GetMutex() override { return mMutex; }
+
+ public:
+ explicit gfxFontCache(nsIEventTarget* aEventTarget);
+ ~gfxFontCache();
+
+ enum { SHAPED_WORD_TIMEOUT_SECONDS = 60 };
+
+ /*
+ * Get the global gfxFontCache. You must call Init() before
+ * calling this method --- the result will not be null.
+ */
+ static gfxFontCache* GetCache() { return gGlobalCache; }
+
+ static nsresult Init();
+ // It's OK to call this even if Init() has not been called.
+ static void Shutdown();
+
+ // Look up a font in the cache. Returns null if there's nothing matching
+ // in the cache
+ already_AddRefed<gfxFont> Lookup(const gfxFontEntry* aFontEntry,
+ const gfxFontStyle* aStyle,
+ const gfxCharacterMap* aUnicodeRangeMap);
+
+ // We created a new font (presumably because Lookup returned null);
+ // put it in the cache. The font's refcount should be nonzero. It is
+ // allowable to add a new font even if there is one already in the
+ // cache with the same key, as we may race with other threads to do
+ // the insertion -- in that case we will return the original font,
+ // and destroy the new one.
+ already_AddRefed<gfxFont> MaybeInsert(gfxFont* aFont);
+
+ bool MaybeDestroy(gfxFont* aFont);
+
+ // Cleans out the hashtable and removes expired fonts waiting for cleanup.
+ // Other gfxFont objects may be still in use but they will be pushed
+ // into the expiration queues and removed.
+ void Flush();
+
+ void FlushShapedWordCaches();
+ void NotifyGlyphsChanged();
+
+ void AgeCachedWords();
+
+ void RunWordCacheExpirationTimer() {
+ if (!mTimerRunning) {
+ mozilla::MutexAutoLock lock(mMutex);
+ if (!mTimerRunning && mWordCacheExpirationTimer) {
+ mWordCacheExpirationTimer->InitWithNamedFuncCallback(
+ WordCacheExpirationTimerCallback, this,
+ SHAPED_WORD_TIMEOUT_SECONDS * 1000, nsITimer::TYPE_REPEATING_SLACK,
+ "gfxFontCache::WordCacheExpiration");
+ mTimerRunning = true;
+ }
+ }
+ }
+ void PauseWordCacheExpirationTimer() {
+ if (mTimerRunning) {
+ mozilla::MutexAutoLock lock(mMutex);
+ if (mTimerRunning && mWordCacheExpirationTimer) {
+ mWordCacheExpirationTimer->Cancel();
+ mTimerRunning = false;
+ }
+ }
+ }
+
+ void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const;
+ void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const;
+
+ protected:
+ class MemoryReporter final : public nsIMemoryReporter {
+ ~MemoryReporter() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+ };
+
+ // Observer for notifications that the font cache cares about
+ class Observer final : public nsIObserver {
+ ~Observer() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ };
+
+ nsresult AddObject(gfxFont* aFont) {
+ AutoLock lock(mMutex);
+ return AddObjectLocked(aFont, lock);
+ }
+
+ // This gets called when the timeout has expired on a single-refcount
+ // font; we just delete it.
+ void NotifyExpiredLocked(gfxFont* aFont, const AutoLock&)
+ MOZ_REQUIRES(mMutex) override;
+ void NotifyHandlerEnd() override;
+
+ void DestroyDiscard(nsTArray<gfxFont*>& aDiscard);
+
+ static gfxFontCache* gGlobalCache;
+
+ struct MOZ_STACK_CLASS Key {
+ const gfxFontEntry* mFontEntry;
+ const gfxFontStyle* mStyle;
+ const gfxCharacterMap* mUnicodeRangeMap;
+ Key(const gfxFontEntry* aFontEntry, const gfxFontStyle* aStyle,
+ const gfxCharacterMap* aUnicodeRangeMap)
+ : mFontEntry(aFontEntry),
+ mStyle(aStyle),
+ mUnicodeRangeMap(aUnicodeRangeMap) {}
+ };
+
+ class HashEntry : public PLDHashEntryHdr {
+ public:
+ typedef const Key& KeyType;
+ typedef const Key* KeyTypePointer;
+
+ // When constructing a new entry in the hashtable, we'll leave this
+ // blank. The caller of Put() will fill this in.
+ explicit HashEntry(KeyTypePointer aStr) {}
+
+ bool KeyEquals(const KeyTypePointer aKey) const;
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(const KeyTypePointer aKey) {
+ return mozilla::HashGeneric(aKey->mStyle->Hash(), aKey->mFontEntry,
+ aKey->mUnicodeRangeMap);
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ gfxFont* MOZ_UNSAFE_REF("tracking for deferred deletion") mFont = nullptr;
+ };
+
+ nsTHashtable<HashEntry> mFonts MOZ_GUARDED_BY(mMutex);
+
+ nsTArray<gfxFont*> mTrackerDiscard MOZ_GUARDED_BY(mMutex);
+
+ static void WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache);
+
+ nsCOMPtr<nsITimer> mWordCacheExpirationTimer MOZ_GUARDED_BY(mMutex);
+ std::atomic<bool> mTimerRunning = false;
+};
+
+class gfxTextPerfMetrics {
+ public:
+ struct TextCounts {
+ uint32_t numContentTextRuns;
+ uint32_t numChromeTextRuns;
+ uint32_t numChars;
+ uint32_t maxTextRunLen;
+ uint32_t wordCacheSpaceRules;
+ uint32_t wordCacheLong;
+ uint32_t wordCacheHit;
+ uint32_t wordCacheMiss;
+ uint32_t fallbackPrefs;
+ uint32_t fallbackSystem;
+ uint32_t textrunConst;
+ uint32_t textrunDestr;
+ uint32_t genericLookups;
+ };
+
+ uint32_t reflowCount;
+
+ // counts per reflow operation
+ TextCounts current;
+
+ // totals for the lifetime of a document
+ TextCounts cumulative;
+
+ gfxTextPerfMetrics() { memset(this, 0, sizeof(gfxTextPerfMetrics)); }
+
+ // add current totals to cumulative ones
+ void Accumulate() {
+ if (current.numChars == 0) {
+ return;
+ }
+ cumulative.numContentTextRuns += current.numContentTextRuns;
+ cumulative.numChromeTextRuns += current.numChromeTextRuns;
+ cumulative.numChars += current.numChars;
+ if (current.maxTextRunLen > cumulative.maxTextRunLen) {
+ cumulative.maxTextRunLen = current.maxTextRunLen;
+ }
+ cumulative.wordCacheSpaceRules += current.wordCacheSpaceRules;
+ cumulative.wordCacheLong += current.wordCacheLong;
+ cumulative.wordCacheHit += current.wordCacheHit;
+ cumulative.wordCacheMiss += current.wordCacheMiss;
+ cumulative.fallbackPrefs += current.fallbackPrefs;
+ cumulative.fallbackSystem += current.fallbackSystem;
+ cumulative.textrunConst += current.textrunConst;
+ cumulative.textrunDestr += current.textrunDestr;
+ cumulative.genericLookups += current.genericLookups;
+ memset(&current, 0, sizeof(current));
+ }
+};
+
+namespace mozilla {
+namespace gfx {
+
+class UnscaledFont;
+
+// Flags that live in the gfxShapedText::mFlags field.
+// (Note that gfxTextRun has an additional mFlags2 field for use
+// by textrun clients like nsTextFrame.)
+//
+// If you add a flag, please add support for it in gfxTextRun::Dump.
+enum class ShapedTextFlags : uint16_t {
+ /**
+ * When set, the text is RTL.
+ */
+ TEXT_IS_RTL = 0x0001,
+ /**
+ * When set, spacing is enabled and the textrun needs to call GetSpacing
+ * on the spacing provider.
+ */
+ TEXT_ENABLE_SPACING = 0x0002,
+ /**
+ * When set, the text has no characters above 255 and it is stored
+ * in the textrun in 8-bit format.
+ */
+ TEXT_IS_8BIT = 0x0004,
+ /**
+ * When set, GetHyphenationBreaks may return true for some character
+ * positions, otherwise it will always return false for all characters.
+ */
+ TEXT_ENABLE_HYPHEN_BREAKS = 0x0008,
+ /**
+ * When set, the RunMetrics::mBoundingBox field will be initialized
+ * properly based on glyph extents, in particular, glyph extents that
+ * overflow the standard font-box (the box defined by the ascent, descent
+ * and advance width of the glyph). When not set, it may just be the
+ * standard font-box even if glyphs overflow.
+ */
+ TEXT_NEED_BOUNDING_BOX = 0x0010,
+ /**
+ * When set, optional ligatures are disabled. Ligatures that are
+ * required for legible text should still be enabled.
+ */
+ TEXT_DISABLE_OPTIONAL_LIGATURES = 0x0020,
+ /**
+ * When set, the textrun should favour speed of construction over
+ * quality. This may involve disabling ligatures and/or kerning or
+ * other effects.
+ */
+ TEXT_OPTIMIZE_SPEED = 0x0040,
+ /**
+ * When set, the textrun should discard control characters instead of
+ * turning them into hexboxes.
+ */
+ TEXT_HIDE_CONTROL_CHARACTERS = 0x0080,
+
+ /**
+ * nsTextFrameThebes sets these, but they're defined here rather than
+ * in nsTextFrameUtils.h because ShapedWord creation/caching also needs
+ * to check the _INCOMING flag
+ */
+ TEXT_TRAILING_ARABICCHAR = 0x0100,
+ /**
+ * When set, the previous character for this textrun was an Arabic
+ * character. This is used for the context detection necessary for
+ * bidi.numeral implementation.
+ */
+ TEXT_INCOMING_ARABICCHAR = 0x0200,
+
+ /**
+ * Set if the textrun should use the OpenType 'math' script.
+ */
+ TEXT_USE_MATH_SCRIPT = 0x0400,
+
+ /*
+ * Bit 0x0800 is currently unused.
+ */
+
+ /**
+ * Field for orientation of the textrun and glyphs within it.
+ * Possible values of the TEXT_ORIENT_MASK field:
+ * TEXT_ORIENT_HORIZONTAL
+ * TEXT_ORIENT_VERTICAL_UPRIGHT
+ * TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT
+ * TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT
+ * TEXT_ORIENT_VERTICAL_MIXED
+ * For all VERTICAL settings, the x and y coordinates of glyph
+ * positions are exchanged, so that simple advances are vertical.
+ *
+ * The MIXED value indicates vertical textRuns for which the CSS
+ * text-orientation property is 'mixed', but is never used for
+ * individual glyphRuns; it will be resolved to either UPRIGHT
+ * or SIDEWAYS_RIGHT according to the UTR50 properties of the
+ * characters, and separate glyphRuns created for the resulting
+ * glyph orientations.
+ */
+ TEXT_ORIENT_MASK = 0x7000,
+ TEXT_ORIENT_HORIZONTAL = 0x0000,
+ TEXT_ORIENT_VERTICAL_UPRIGHT = 0x1000,
+ TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT = 0x2000,
+ TEXT_ORIENT_VERTICAL_MIXED = 0x3000,
+ TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT = 0x4000,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ShapedTextFlags)
+} // namespace gfx
+} // namespace mozilla
+
+class gfxTextRunFactory {
+ // Used by stylo
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxTextRunFactory)
+
+ public:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ /**
+ * This record contains all the parameters needed to initialize a textrun.
+ */
+ struct MOZ_STACK_CLASS Parameters {
+ // Shape text params suggesting where the textrun will be rendered
+ DrawTarget* mDrawTarget;
+ // Pointer to arbitrary user data (which should outlive the textrun)
+ void* mUserData;
+ // A description of which characters have been stripped from the original
+ // DOM string to produce the characters in the textrun. May be null
+ // if that information is not relevant.
+ gfxSkipChars* mSkipChars;
+ // A list of where linebreaks are currently placed in the textrun. May
+ // be null if mInitialBreakCount is zero.
+ uint32_t* mInitialBreaks;
+ uint32_t mInitialBreakCount;
+ // The ratio to use to convert device pixels to application layout units
+ int32_t mAppUnitsPerDevUnit;
+ };
+
+ protected:
+ // Protected destructor, to discourage deletion outside of Release():
+ virtual ~gfxTextRunFactory();
+};
+
+/**
+ * gfxFontShaper
+ *
+ * This class implements text shaping (character to glyph mapping and
+ * glyph layout). There is a gfxFontShaper subclass for each text layout
+ * technology (uniscribe, core text, harfbuzz,....) we support.
+ *
+ * The shaper is responsible for setting up glyph data in gfxTextRuns.
+ *
+ * A generic, platform-independent shaper relies only on the standard
+ * gfxFont interface and can work with any concrete subclass of gfxFont.
+ *
+ * Platform-specific implementations designed to interface to platform
+ * shaping APIs such as Uniscribe or CoreText may rely on features of a
+ * specific font subclass to access native font references
+ * (such as CTFont, HFONT, DWriteFont, etc).
+ */
+
+class gfxFontShaper {
+ public:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::intl::Script Script;
+
+ enum class RoundingFlags : uint8_t { kRoundX = 0x01, kRoundY = 0x02 };
+
+ explicit gfxFontShaper(gfxFont* aFont) : mFont(aFont) {
+ NS_ASSERTION(aFont, "shaper requires a valid font!");
+ }
+
+ virtual ~gfxFontShaper() = default;
+
+ // Shape a piece of text and store the resulting glyph data into
+ // aShapedText. Parameters aOffset/aLength indicate the range of
+ // aShapedText to be updated; aLength is also the length of aText.
+ virtual bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText,
+ uint32_t aOffset, uint32_t aLength, Script aScript,
+ nsAtom* aLanguage, // may be null, indicating no
+ // lang-specific shaping to be
+ // applied
+ bool aVertical, RoundingFlags aRounding,
+ gfxShapedText* aShapedText) = 0;
+
+ gfxFont* GetFont() const { return mFont; }
+
+ static void MergeFontFeatures(
+ const gfxFontStyle* aStyle, const nsTArray<gfxFontFeature>& aFontFeatures,
+ bool aDisableLigatures, const nsACString& aFamilyName, bool aAddSmallCaps,
+ void (*aHandleFeature)(const uint32_t&, uint32_t&, void*),
+ void* aHandleFeatureData);
+
+ protected:
+ // the font this shaper is working with. The font owns a UniquePtr reference
+ // to this object, and will destroy it before it dies. Thus, mFont will always
+ // be valid.
+ gfxFont* MOZ_NON_OWNING_REF mFont;
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(gfxFontShaper::RoundingFlags)
+
+/*
+ * gfxShapedText is an abstract superclass for gfxShapedWord and gfxTextRun.
+ * These are objects that store a list of zero or more glyphs for each
+ * character. For each glyph we store the glyph ID, the advance, and possibly
+ * x/y-offsets. The idea is that a string is rendered by a loop that draws each
+ * glyph at its designated offset from the current point, then advances the
+ * current point by the glyph's advance in the direction of the textrun (LTR or
+ * RTL). Each glyph advance is always rounded to the nearest appunit; this
+ * ensures consistent results when dividing the text in a textrun into multiple
+ * text frames (frame boundaries are always aligned to appunits). We optimize
+ * for the case where a character has a single glyph and zero xoffset and
+ * yoffset, and the glyph ID and advance are in a reasonable range so we can
+ * pack all necessary data into 32 bits.
+ *
+ * gfxFontShaper can shape text into either a gfxShapedWord (cached by a
+ * gfxFont) or directly into a gfxTextRun (for cases where we want to shape
+ * textruns in their entirety rather than using cached words, because there may
+ * be layout features that depend on the inter-word spaces).
+ */
+class gfxShapedText {
+ public:
+ typedef mozilla::intl::Script Script;
+
+ gfxShapedText(uint32_t aLength, mozilla::gfx::ShapedTextFlags aFlags,
+ uint16_t aAppUnitsPerDevUnit)
+ : mLength(aLength),
+ mFlags(aFlags),
+ mAppUnitsPerDevUnit(aAppUnitsPerDevUnit) {}
+
+ virtual ~gfxShapedText() = default;
+
+ /**
+ * This class records the information associated with a character in the
+ * input string. It's optimized for the case where there is one glyph
+ * representing that character alone.
+ *
+ * A character can have zero or more associated glyphs. Each glyph
+ * has an advance width and an x and y offset.
+ * A character may be the start of a cluster.
+ * A character may be the start of a ligature group.
+ * A character can be "missing", indicating that the system is unable
+ * to render the character.
+ *
+ * All characters in a ligature group conceptually share all the glyphs
+ * associated with the characters in a group.
+ */
+ class CompressedGlyph {
+ public:
+ enum {
+ // Indicates that a cluster and ligature group starts at this
+ // character; this character has a single glyph with a reasonable
+ // advance and zero offsets. A "reasonable" advance
+ // is one that fits in the available bits (currently 12) (specified
+ // in appunits).
+ FLAG_IS_SIMPLE_GLYPH = 0x80000000U,
+
+ // These flags are applicable to both "simple" and "complex" records.
+ COMMON_FLAGS_MASK = 0x70000000U,
+
+ // Indicates whether a linebreak is allowed before this character;
+ // this is a two-bit field that holds a FLAG_BREAK_TYPE_xxx value
+ // indicating the kind of linebreak (if any) allowed here.
+ FLAGS_CAN_BREAK_BEFORE = 0x60000000U,
+
+ FLAGS_CAN_BREAK_SHIFT = 29,
+ FLAG_BREAK_TYPE_NONE = 0,
+ FLAG_BREAK_TYPE_NORMAL = 1,
+ FLAG_BREAK_TYPE_HYPHEN = 2,
+
+ FLAG_CHAR_IS_SPACE = 0x10000000U,
+
+ // Fields present only when FLAG_IS_SIMPLE_GLYPH is /true/.
+ // The advance is stored in appunits as a 12-bit field:
+ ADVANCE_MASK = 0x0FFF0000U,
+ ADVANCE_SHIFT = 16,
+ // and the glyph ID is stored in the low 16 bits.
+ GLYPH_MASK = 0x0000FFFFU,
+
+ // Fields present only when FLAG_IS_SIMPLE_GLYPH is /false/.
+ // Non-simple glyphs may or may not have glyph data in the
+ // corresponding mDetailedGlyphs entry. They have a glyph count
+ // stored in the low 16 bits, and the following flag bits:
+ GLYPH_COUNT_MASK = 0x0000FFFFU,
+
+ // When NOT set, indicates that this character corresponds to a
+ // missing glyph and should be skipped (or possibly, render the character
+ // Unicode value in some special way). If there are glyphs,
+ // the mGlyphID is actually the UTF16 character code. The bit is
+ // inverted so we can memset the array to zero to indicate all missing.
+ FLAG_NOT_MISSING = 0x010000,
+ FLAG_NOT_CLUSTER_START = 0x020000,
+ FLAG_NOT_LIGATURE_GROUP_START = 0x040000,
+ // Flag bit 0x080000 is currently unused.
+
+ // Certain types of characters are marked so that they can be given
+ // special treatment in rendering. This may require use of a "complex"
+ // CompressedGlyph record even for a character that would otherwise be
+ // treated as "simple".
+ CHAR_TYPE_FLAGS_MASK = 0xF00000,
+ FLAG_CHAR_IS_TAB = 0x100000,
+ FLAG_CHAR_IS_NEWLINE = 0x200000,
+ // Per CSS Text Decoration Module Level 3, emphasis marks are not
+ // drawn for any character in Unicode categories Z*, Cc, Cf, and Cn
+ // which is not combined with any combining characters. This flag is
+ // set for all those characters except 0x20 whitespace.
+ FLAG_CHAR_NO_EMPHASIS_MARK = 0x400000,
+ // Per CSS Text, letter-spacing is not applied to formatting chars
+ // (category Cf). We mark those in the textrun so as to be able to
+ // skip them when setting up spacing in nsTextFrame.
+ FLAG_CHAR_IS_FORMATTING_CONTROL = 0x800000,
+
+ // The bits 0x0F000000 are currently unused in non-simple glyphs.
+ };
+
+ // "Simple glyphs" have a simple glyph ID, simple advance and their
+ // x and y offsets are zero. Also the glyph extents do not overflow
+ // the font-box defined by the font ascent, descent and glyph advance width.
+ // These case is optimized to avoid storing DetailedGlyphs.
+
+ // Returns true if the glyph ID aGlyph fits into the compressed
+ // representation
+ static bool IsSimpleGlyphID(uint32_t aGlyph) {
+ return (aGlyph & GLYPH_MASK) == aGlyph;
+ }
+ // Returns true if the advance aAdvance fits into the compressed
+ // representation. aAdvance is in appunits.
+ static bool IsSimpleAdvance(uint32_t aAdvance) {
+ return (aAdvance & (ADVANCE_MASK >> ADVANCE_SHIFT)) == aAdvance;
+ }
+
+ bool IsSimpleGlyph() const { return mValue & FLAG_IS_SIMPLE_GLYPH; }
+ uint32_t GetSimpleAdvance() const {
+ MOZ_ASSERT(IsSimpleGlyph());
+ return (mValue & ADVANCE_MASK) >> ADVANCE_SHIFT;
+ }
+ uint32_t GetSimpleGlyph() const {
+ MOZ_ASSERT(IsSimpleGlyph());
+ return mValue & GLYPH_MASK;
+ }
+
+ bool IsMissing() const {
+ return !(mValue & (FLAG_NOT_MISSING | FLAG_IS_SIMPLE_GLYPH));
+ }
+ bool IsClusterStart() const {
+ return IsSimpleGlyph() || !(mValue & FLAG_NOT_CLUSTER_START);
+ }
+ bool IsLigatureGroupStart() const {
+ return IsSimpleGlyph() || !(mValue & FLAG_NOT_LIGATURE_GROUP_START);
+ }
+ bool IsLigatureContinuation() const {
+ return !IsSimpleGlyph() &&
+ (mValue & (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING)) ==
+ (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING);
+ }
+
+ // Return true if the original character was a normal (breakable,
+ // trimmable) space (U+0020). Not true for other characters that
+ // may happen to map to the space glyph (U+00A0).
+ bool CharIsSpace() const { return mValue & FLAG_CHAR_IS_SPACE; }
+
+ bool CharIsTab() const {
+ return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_TAB);
+ }
+ bool CharIsNewline() const {
+ return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_NEWLINE);
+ }
+ bool CharMayHaveEmphasisMark() const {
+ return !CharIsSpace() &&
+ (IsSimpleGlyph() || !(mValue & FLAG_CHAR_NO_EMPHASIS_MARK));
+ }
+ bool CharIsFormattingControl() const {
+ return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_FORMATTING_CONTROL);
+ }
+
+ uint32_t CharTypeFlags() const {
+ return IsSimpleGlyph() ? 0 : (mValue & CHAR_TYPE_FLAGS_MASK);
+ }
+
+ void SetClusterStart(bool aIsClusterStart) {
+ MOZ_ASSERT(!IsSimpleGlyph());
+ if (aIsClusterStart) {
+ mValue &= ~FLAG_NOT_CLUSTER_START;
+ } else {
+ mValue |= FLAG_NOT_CLUSTER_START;
+ }
+ }
+
+ uint8_t CanBreakBefore() const {
+ return (mValue & FLAGS_CAN_BREAK_BEFORE) >> FLAGS_CAN_BREAK_SHIFT;
+ }
+ // Returns FLAGS_CAN_BREAK_BEFORE if the setting changed, 0 otherwise
+ uint32_t SetCanBreakBefore(uint8_t aCanBreakBefore) {
+ MOZ_ASSERT(aCanBreakBefore <= 2, "Bogus break-before value!");
+ uint32_t breakMask = (uint32_t(aCanBreakBefore) << FLAGS_CAN_BREAK_SHIFT);
+ uint32_t toggle = breakMask ^ (mValue & FLAGS_CAN_BREAK_BEFORE);
+ mValue ^= toggle;
+ return toggle;
+ }
+
+ // Create a CompressedGlyph value representing a simple glyph with
+ // no extra flags (line-break or is_space) set.
+ static CompressedGlyph MakeSimpleGlyph(uint32_t aAdvanceAppUnits,
+ uint32_t aGlyph) {
+ MOZ_ASSERT(IsSimpleAdvance(aAdvanceAppUnits));
+ MOZ_ASSERT(IsSimpleGlyphID(aGlyph));
+ CompressedGlyph g;
+ g.mValue =
+ FLAG_IS_SIMPLE_GLYPH | (aAdvanceAppUnits << ADVANCE_SHIFT) | aGlyph;
+ return g;
+ }
+
+ // Assign a simple glyph value to an existing CompressedGlyph record,
+ // preserving line-break/is-space flags if present.
+ CompressedGlyph& SetSimpleGlyph(uint32_t aAdvanceAppUnits,
+ uint32_t aGlyph) {
+ MOZ_ASSERT(!CharTypeFlags(), "Char type flags lost");
+ mValue = (mValue & COMMON_FLAGS_MASK) |
+ MakeSimpleGlyph(aAdvanceAppUnits, aGlyph).mValue;
+ return *this;
+ }
+
+ // Create a CompressedGlyph value representing a complex glyph record,
+ // without any line-break or char-type flags.
+ static CompressedGlyph MakeComplex(bool aClusterStart,
+ bool aLigatureStart) {
+ CompressedGlyph g;
+ g.mValue = FLAG_NOT_MISSING |
+ (aClusterStart ? 0 : FLAG_NOT_CLUSTER_START) |
+ (aLigatureStart ? 0 : FLAG_NOT_LIGATURE_GROUP_START);
+ return g;
+ }
+
+ // Assign a complex glyph value to an existing CompressedGlyph record,
+ // preserving line-break/char-type flags if present.
+ // This sets the glyphCount to zero; it will be updated when we call
+ // gfxShapedText::SetDetailedGlyphs.
+ CompressedGlyph& SetComplex(bool aClusterStart, bool aLigatureStart) {
+ mValue = (mValue & COMMON_FLAGS_MASK) | CharTypeFlags() |
+ MakeComplex(aClusterStart, aLigatureStart).mValue;
+ return *this;
+ }
+
+ /**
+ * Mark a glyph record as being a missing-glyph.
+ * Missing glyphs are treated as ligature group starts; don't mess with
+ * the cluster-start flag (see bugs 618870 and 619286).
+ * We also preserve the glyph count here, as this is used after any
+ * required DetailedGlyphs (to store the char code for a hexbox) has been
+ * set up.
+ * This must be called *after* SetDetailedGlyphs is used for the relevant
+ * offset in the shaped-word, because that will mark it as not-missing.
+ */
+ CompressedGlyph& SetMissing() {
+ MOZ_ASSERT(!IsSimpleGlyph());
+ mValue &= ~(FLAG_NOT_MISSING | FLAG_NOT_LIGATURE_GROUP_START);
+ return *this;
+ }
+
+ uint32_t GetGlyphCount() const {
+ MOZ_ASSERT(!IsSimpleGlyph());
+ return mValue & GLYPH_COUNT_MASK;
+ }
+ void SetGlyphCount(uint32_t aGlyphCount) {
+ MOZ_ASSERT(!IsSimpleGlyph());
+ MOZ_ASSERT(GetGlyphCount() == 0, "Glyph count already set");
+ MOZ_ASSERT(aGlyphCount <= 0xffff, "Glyph count out of range");
+ mValue |= FLAG_NOT_MISSING | aGlyphCount;
+ }
+
+ void SetIsSpace() { mValue |= FLAG_CHAR_IS_SPACE; }
+ void SetIsTab() {
+ MOZ_ASSERT(!IsSimpleGlyph());
+ mValue |= FLAG_CHAR_IS_TAB;
+ }
+ void SetIsNewline() {
+ MOZ_ASSERT(!IsSimpleGlyph());
+ mValue |= FLAG_CHAR_IS_NEWLINE;
+ }
+ void SetNoEmphasisMark() {
+ MOZ_ASSERT(!IsSimpleGlyph());
+ mValue |= FLAG_CHAR_NO_EMPHASIS_MARK;
+ }
+ void SetIsFormattingControl() {
+ MOZ_ASSERT(!IsSimpleGlyph());
+ mValue |= FLAG_CHAR_IS_FORMATTING_CONTROL;
+ }
+
+ private:
+ uint32_t mValue;
+ };
+
+ // Accessor for the array of CompressedGlyph records, which will be in
+ // a different place in gfxShapedWord vs gfxTextRun
+ virtual const CompressedGlyph* GetCharacterGlyphs() const = 0;
+ virtual CompressedGlyph* GetCharacterGlyphs() = 0;
+
+ /**
+ * When the glyphs for a character don't fit into a CompressedGlyph record
+ * in SimpleGlyph format, we use an array of DetailedGlyphs instead.
+ */
+ struct DetailedGlyph {
+ // The glyphID, or the Unicode character if this is a missing glyph
+ uint32_t mGlyphID;
+ // The advance of the glyph, in appunits.
+ // mAdvance is in the text direction (RTL or LTR),
+ // and will normally be non-negative (although this is not guaranteed)
+ int32_t mAdvance;
+ // The offset from the glyph's default position, in line-relative
+ // coordinates (so mOffset.x is an offset in the line-right direction,
+ // and mOffset.y is an offset in line-downwards direction).
+ // These values are in floating-point appUnits.
+ mozilla::gfx::Point mOffset;
+ };
+
+ // Store DetailedGlyph records for the given index. (This does not modify
+ // the associated CompressedGlyph character-type or break flags.)
+ void SetDetailedGlyphs(uint32_t aIndex, uint32_t aGlyphCount,
+ const DetailedGlyph* aGlyphs);
+
+ void SetMissingGlyph(uint32_t aIndex, uint32_t aChar, gfxFont* aFont);
+
+ void SetIsSpace(uint32_t aIndex) {
+ GetCharacterGlyphs()[aIndex].SetIsSpace();
+ }
+
+ bool HasDetailedGlyphs() const { return mDetailedGlyphs != nullptr; }
+
+ bool IsLigatureGroupStart(uint32_t aPos) {
+ NS_ASSERTION(aPos < GetLength(), "aPos out of range");
+ return GetCharacterGlyphs()[aPos].IsLigatureGroupStart();
+ }
+
+ // NOTE that this must not be called for a character offset that does
+ // not have any DetailedGlyph records; callers must have verified that
+ // GetCharacterGlyphs()[aCharIndex].GetGlyphCount() is greater than zero.
+ DetailedGlyph* GetDetailedGlyphs(uint32_t aCharIndex) const {
+ NS_ASSERTION(GetCharacterGlyphs() && HasDetailedGlyphs() &&
+ !GetCharacterGlyphs()[aCharIndex].IsSimpleGlyph() &&
+ GetCharacterGlyphs()[aCharIndex].GetGlyphCount() > 0,
+ "invalid use of GetDetailedGlyphs; check the caller!");
+ return mDetailedGlyphs->Get(aCharIndex);
+ }
+
+ void AdjustAdvancesForSyntheticBold(float aSynBoldOffset, uint32_t aOffset,
+ uint32_t aLength);
+
+ // Mark clusters in the CompressedGlyph records, starting at aOffset,
+ // based on the Unicode properties of the text in aString.
+ // This is also responsible to set the IsSpace flag for space characters.
+ void SetupClusterBoundaries(uint32_t aOffset, const char16_t* aString,
+ uint32_t aLength);
+ // In 8-bit text, there won't actually be any clusters, but we still need
+ // the space-marking functionality.
+ void SetupClusterBoundaries(uint32_t aOffset, const uint8_t* aString,
+ uint32_t aLength);
+
+ mozilla::gfx::ShapedTextFlags GetFlags() const { return mFlags; }
+
+ bool IsVertical() const {
+ return (GetFlags() & mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_MASK) !=
+ mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL;
+ }
+
+ bool UseCenterBaseline() const {
+ mozilla::gfx::ShapedTextFlags orient =
+ GetFlags() & mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_MASK;
+ return orient ==
+ mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED ||
+ orient ==
+ mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
+ }
+
+ bool IsRightToLeft() const {
+ return (GetFlags() & mozilla::gfx::ShapedTextFlags::TEXT_IS_RTL) ==
+ mozilla::gfx::ShapedTextFlags::TEXT_IS_RTL;
+ }
+
+ bool IsSidewaysLeft() const {
+ return (GetFlags() & mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_MASK) ==
+ mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT;
+ }
+
+ // Return true if the logical inline direction is reversed compared to
+ // normal physical coordinates (i.e. if it is leftwards or upwards)
+ bool IsInlineReversed() const { return IsSidewaysLeft() != IsRightToLeft(); }
+
+ gfxFloat GetDirection() const { return IsInlineReversed() ? -1.0f : 1.0f; }
+
+ bool DisableLigatures() const {
+ return (GetFlags() &
+ mozilla::gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES) ==
+ mozilla::gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES;
+ }
+
+ bool TextIs8Bit() const {
+ return (GetFlags() & mozilla::gfx::ShapedTextFlags::TEXT_IS_8BIT) ==
+ mozilla::gfx::ShapedTextFlags::TEXT_IS_8BIT;
+ }
+
+ int32_t GetAppUnitsPerDevUnit() const { return mAppUnitsPerDevUnit; }
+
+ uint32_t GetLength() const { return mLength; }
+
+ bool FilterIfIgnorable(uint32_t aIndex, uint32_t aCh);
+
+ protected:
+ // Allocate aCount DetailedGlyphs for the given index
+ DetailedGlyph* AllocateDetailedGlyphs(uint32_t aCharIndex, uint32_t aCount);
+
+ // Ensure the glyph on the given index is complex glyph so that we can use
+ // it to record specific characters that layout may need to detect.
+ void EnsureComplexGlyph(uint32_t aIndex, CompressedGlyph& aGlyph) {
+ MOZ_ASSERT(GetCharacterGlyphs() + aIndex == &aGlyph);
+ if (aGlyph.IsSimpleGlyph()) {
+ DetailedGlyph details = {aGlyph.GetSimpleGlyph(),
+ (int32_t)aGlyph.GetSimpleAdvance(),
+ mozilla::gfx::Point()};
+ aGlyph.SetComplex(true, true);
+ SetDetailedGlyphs(aIndex, 1, &details);
+ }
+ }
+
+ // For characters whose glyph data does not fit the "simple" glyph criteria
+ // in CompressedGlyph, we use a sorted array to store the association
+ // between the source character offset and an index into an array
+ // DetailedGlyphs. The CompressedGlyph record includes a count of
+ // the number of DetailedGlyph records that belong to the character,
+ // starting at the given index.
+ class DetailedGlyphStore {
+ public:
+ DetailedGlyphStore() = default;
+
+ // This is optimized for the most common calling patterns:
+ // we rarely need random access to the records, access is most commonly
+ // sequential through the textRun, so we record the last-used index
+ // and check whether the caller wants the same record again, or the
+ // next; if not, it's most likely we're starting over from the start
+ // of the run, so we check the first entry before resorting to binary
+ // search as a last resort.
+ // NOTE that this must not be called for a character offset that does
+ // not have any DetailedGlyph records; callers must have verified that
+ // mCharacterGlyphs[aOffset].GetGlyphCount() is greater than zero
+ // before calling this, otherwise the assertions here will fire (in a
+ // debug build), and we'll probably crash.
+ DetailedGlyph* Get(uint32_t aOffset) {
+ NS_ASSERTION(mOffsetToIndex.Length() > 0, "no detailed glyph records!");
+ DetailedGlyph* details = mDetails.Elements();
+ // check common cases (fwd iteration, initial entry, etc) first
+ if (mLastUsed < mOffsetToIndex.Length() - 1 &&
+ aOffset == mOffsetToIndex[mLastUsed + 1].mOffset) {
+ ++mLastUsed;
+ } else if (aOffset == mOffsetToIndex[0].mOffset) {
+ mLastUsed = 0;
+ } else if (aOffset == mOffsetToIndex[mLastUsed].mOffset) {
+ // do nothing
+ } else if (mLastUsed > 0 &&
+ aOffset == mOffsetToIndex[mLastUsed - 1].mOffset) {
+ --mLastUsed;
+ } else {
+ mLastUsed = mOffsetToIndex.BinaryIndexOf(aOffset, CompareToOffset());
+ }
+ NS_ASSERTION(mLastUsed != nsTArray<DGRec>::NoIndex,
+ "detailed glyph record missing!");
+ return details + mOffsetToIndex[mLastUsed].mIndex;
+ }
+
+ DetailedGlyph* Allocate(uint32_t aOffset, uint32_t aCount) {
+ uint32_t detailIndex = mDetails.Length();
+ DetailedGlyph* details = mDetails.AppendElements(aCount);
+ // We normally set up glyph records sequentially, so the common case
+ // here is to append new records to the mOffsetToIndex array;
+ // test for that before falling back to the InsertElementSorted
+ // method.
+ if (mOffsetToIndex.Length() == 0 ||
+ aOffset > mOffsetToIndex[mOffsetToIndex.Length() - 1].mOffset) {
+ mOffsetToIndex.AppendElement(DGRec(aOffset, detailIndex));
+ } else {
+ mOffsetToIndex.InsertElementSorted(DGRec(aOffset, detailIndex),
+ CompareRecordOffsets());
+ }
+ return details;
+ }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
+ return aMallocSizeOf(this) +
+ mDetails.ShallowSizeOfExcludingThis(aMallocSizeOf) +
+ mOffsetToIndex.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ struct DGRec {
+ DGRec(const uint32_t& aOffset, const uint32_t& aIndex)
+ : mOffset(aOffset), mIndex(aIndex) {}
+ uint32_t mOffset; // source character offset in the textrun
+ uint32_t mIndex; // index where this char's DetailedGlyphs begin
+ };
+
+ struct CompareToOffset {
+ bool Equals(const DGRec& a, const uint32_t& b) const {
+ return a.mOffset == b;
+ }
+ bool LessThan(const DGRec& a, const uint32_t& b) const {
+ return a.mOffset < b;
+ }
+ };
+
+ struct CompareRecordOffsets {
+ bool Equals(const DGRec& a, const DGRec& b) const {
+ return a.mOffset == b.mOffset;
+ }
+ bool LessThan(const DGRec& a, const DGRec& b) const {
+ return a.mOffset < b.mOffset;
+ }
+ };
+
+ // Concatenated array of all the DetailedGlyph records needed for the
+ // textRun; individual character offsets are associated with indexes
+ // into this array via the mOffsetToIndex table.
+ nsTArray<DetailedGlyph> mDetails;
+
+ // For each character offset that needs DetailedGlyphs, we record the
+ // index in mDetails where the list of glyphs begins. This array is
+ // sorted by mOffset.
+ nsTArray<DGRec> mOffsetToIndex;
+
+ // Records the most recently used index into mOffsetToIndex, so that
+ // we can support sequential access more quickly than just doing
+ // a binary search each time.
+ nsTArray<DGRec>::index_type mLastUsed = 0;
+ };
+
+ mozilla::UniquePtr<DetailedGlyphStore> mDetailedGlyphs;
+
+ // Number of char16_t characters and CompressedGlyph glyph records
+ uint32_t mLength;
+
+ // Shaping flags (direction, ligature-suppression)
+ mozilla::gfx::ShapedTextFlags mFlags;
+
+ uint16_t mAppUnitsPerDevUnit;
+};
+
+/*
+ * gfxShapedWord: an individual (space-delimited) run of text shaped with a
+ * particular font, without regard to external context.
+ *
+ * The glyph data is copied into gfxTextRuns as needed from the cache of
+ * ShapedWords associated with each gfxFont instance.
+ */
+class gfxShapedWord final : public gfxShapedText {
+ public:
+ typedef mozilla::intl::Script Script;
+
+ // Create a ShapedWord that can hold glyphs for aLength characters,
+ // with mCharacterGlyphs sized appropriately.
+ //
+ // Returns null on allocation failure (does NOT use infallible alloc)
+ // so caller must check for success.
+ //
+ // This does NOT perform shaping, so the returned word contains no
+ // glyph data; the caller must call gfxFont::ShapeText() with appropriate
+ // parameters to set up the glyphs.
+ static gfxShapedWord* Create(const uint8_t* aText, uint32_t aLength,
+ Script aRunScript, nsAtom* aLanguage,
+ uint16_t aAppUnitsPerDevUnit,
+ mozilla::gfx::ShapedTextFlags aFlags,
+ gfxFontShaper::RoundingFlags aRounding) {
+ NS_ASSERTION(aLength <= gfxPlatform::GetPlatform()->WordCacheCharLimit(),
+ "excessive length for gfxShapedWord!");
+
+ // Compute size needed including the mCharacterGlyphs array
+ // and a copy of the original text
+ uint32_t size = offsetof(gfxShapedWord, mCharGlyphsStorage) +
+ aLength * (sizeof(CompressedGlyph) + sizeof(uint8_t));
+ void* storage = malloc(size);
+ if (!storage) {
+ return nullptr;
+ }
+
+ // Construct in the pre-allocated storage, using placement new
+ return new (storage) gfxShapedWord(aText, aLength, aRunScript, aLanguage,
+ aAppUnitsPerDevUnit, aFlags, aRounding);
+ }
+
+ static gfxShapedWord* Create(const char16_t* aText, uint32_t aLength,
+ Script aRunScript, nsAtom* aLanguage,
+ uint16_t aAppUnitsPerDevUnit,
+ mozilla::gfx::ShapedTextFlags aFlags,
+ gfxFontShaper::RoundingFlags aRounding) {
+ NS_ASSERTION(aLength <= gfxPlatform::GetPlatform()->WordCacheCharLimit(),
+ "excessive length for gfxShapedWord!");
+
+ // In the 16-bit version of Create, if the TEXT_IS_8BIT flag is set,
+ // then we convert the text to an 8-bit version and call the 8-bit
+ // Create function instead.
+ if (aFlags & mozilla::gfx::ShapedTextFlags::TEXT_IS_8BIT) {
+ nsAutoCString narrowText;
+ LossyAppendUTF16toASCII(nsDependentSubstring(aText, aLength), narrowText);
+ return Create((const uint8_t*)(narrowText.BeginReading()), aLength,
+ aRunScript, aLanguage, aAppUnitsPerDevUnit, aFlags,
+ aRounding);
+ }
+
+ uint32_t size = offsetof(gfxShapedWord, mCharGlyphsStorage) +
+ aLength * (sizeof(CompressedGlyph) + sizeof(char16_t));
+ void* storage = malloc(size);
+ if (!storage) {
+ return nullptr;
+ }
+
+ return new (storage) gfxShapedWord(aText, aLength, aRunScript, aLanguage,
+ aAppUnitsPerDevUnit, aFlags, aRounding);
+ }
+
+ // Override operator delete to properly free the object that was
+ // allocated via malloc.
+ void operator delete(void* p) { free(p); }
+
+ const CompressedGlyph* GetCharacterGlyphs() const override {
+ return &mCharGlyphsStorage[0];
+ }
+ CompressedGlyph* GetCharacterGlyphs() override {
+ return &mCharGlyphsStorage[0];
+ }
+
+ const uint8_t* Text8Bit() const {
+ NS_ASSERTION(TextIs8Bit(), "invalid use of Text8Bit()");
+ return reinterpret_cast<const uint8_t*>(mCharGlyphsStorage + GetLength());
+ }
+
+ const char16_t* TextUnicode() const {
+ NS_ASSERTION(!TextIs8Bit(), "invalid use of TextUnicode()");
+ return reinterpret_cast<const char16_t*>(mCharGlyphsStorage + GetLength());
+ }
+
+ char16_t GetCharAt(uint32_t aOffset) const {
+ NS_ASSERTION(aOffset < GetLength(), "aOffset out of range");
+ return TextIs8Bit() ? char16_t(Text8Bit()[aOffset])
+ : TextUnicode()[aOffset];
+ }
+
+ Script GetScript() const { return mScript; }
+ nsAtom* GetLanguage() const { return mLanguage.get(); }
+
+ gfxFontShaper::RoundingFlags GetRounding() const { return mRounding; }
+
+ void ResetAge() { mAgeCounter = 0; }
+ uint32_t IncrementAge() { return ++mAgeCounter; }
+
+ // Helper used when hashing a word for the shaped-word caches
+ static uint32_t HashMix(uint32_t aHash, char16_t aCh) {
+ return (aHash >> 28) ^ (aHash << 4) ^ aCh;
+ }
+
+ private:
+ // so that gfxTextRun can share our DetailedGlyphStore class
+ friend class gfxTextRun;
+
+ // Construct storage for a ShapedWord, ready to receive glyph data
+ gfxShapedWord(const uint8_t* aText, uint32_t aLength, Script aRunScript,
+ nsAtom* aLanguage, uint16_t aAppUnitsPerDevUnit,
+ mozilla::gfx::ShapedTextFlags aFlags,
+ gfxFontShaper::RoundingFlags aRounding)
+ : gfxShapedText(aLength,
+ aFlags | mozilla::gfx::ShapedTextFlags::TEXT_IS_8BIT,
+ aAppUnitsPerDevUnit),
+ mLanguage(aLanguage),
+ mScript(aRunScript),
+ mRounding(aRounding),
+ mAgeCounter(0) {
+ memset(mCharGlyphsStorage, 0, aLength * sizeof(CompressedGlyph));
+ uint8_t* text = reinterpret_cast<uint8_t*>(&mCharGlyphsStorage[aLength]);
+ memcpy(text, aText, aLength * sizeof(uint8_t));
+ }
+
+ gfxShapedWord(const char16_t* aText, uint32_t aLength, Script aRunScript,
+ nsAtom* aLanguage, uint16_t aAppUnitsPerDevUnit,
+ mozilla::gfx::ShapedTextFlags aFlags,
+ gfxFontShaper::RoundingFlags aRounding)
+ : gfxShapedText(aLength, aFlags, aAppUnitsPerDevUnit),
+ mLanguage(aLanguage),
+ mScript(aRunScript),
+ mRounding(aRounding),
+ mAgeCounter(0) {
+ memset(mCharGlyphsStorage, 0, aLength * sizeof(CompressedGlyph));
+ char16_t* text = reinterpret_cast<char16_t*>(&mCharGlyphsStorage[aLength]);
+ memcpy(text, aText, aLength * sizeof(char16_t));
+ SetupClusterBoundaries(0, aText, aLength);
+ }
+
+ RefPtr<nsAtom> mLanguage;
+ Script mScript;
+
+ gfxFontShaper::RoundingFlags mRounding;
+
+ // With multithreaded shaping, this may be updated by any thread.
+ std::atomic<uint32_t> mAgeCounter;
+
+ // The mCharGlyphsStorage array is actually a variable-size member;
+ // when the ShapedWord is created, its size will be increased as necessary
+ // to allow the proper number of glyphs to be stored.
+ // The original text, in either 8-bit or 16-bit form, will be stored
+ // immediately following the CompressedGlyphs.
+ CompressedGlyph mCharGlyphsStorage[1];
+};
+
+class GlyphBufferAzure;
+struct TextRunDrawParams;
+struct FontDrawParams;
+struct EmphasisMarkDrawParams;
+
+class gfxFont {
+ friend class gfxHarfBuzzShaper;
+ friend class gfxGraphiteShaper;
+
+ protected:
+ using DrawTarget = mozilla::gfx::DrawTarget;
+ using Script = mozilla::intl::Script;
+ using SVGContextPaint = mozilla::SVGContextPaint;
+
+ using RoundingFlags = gfxFontShaper::RoundingFlags;
+
+ public:
+ using FontSlantStyle = mozilla::FontSlantStyle;
+ using FontSizeAdjust = mozilla::StyleFontSizeAdjust;
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(gfxFont, MaybeDestroy())
+ int32_t GetRefCount() { return int32_t(mRefCnt); }
+
+ // options to specify the kind of AA to be used when creating a font
+ typedef enum : uint8_t {
+ kAntialiasDefault,
+ kAntialiasNone,
+ kAntialiasGrayscale,
+ kAntialiasSubpixel
+ } AntialiasOption;
+
+ protected:
+ gfxFont(const RefPtr<mozilla::gfx::UnscaledFont>& aUnscaledFont,
+ gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle,
+ AntialiasOption anAAOption = kAntialiasDefault);
+
+ virtual ~gfxFont();
+
+ void MaybeDestroy() {
+ bool destroy = true;
+ if (gfxFontCache* fc = gfxFontCache::GetCache()) {
+ destroy = fc->MaybeDestroy(this);
+ }
+ if (destroy) {
+ Destroy();
+ }
+ }
+
+ public:
+ void Destroy() {
+ MOZ_ASSERT(GetRefCount() == 0);
+ delete this;
+ }
+
+ bool Valid() const { return mIsValid; }
+
+ // options for the kind of bounding box to return from measurement
+ typedef enum {
+ LOOSE_INK_EXTENTS,
+ // A box that encloses all the painted pixels, and may
+ // include sidebearings and/or additional ascent/descent
+ // within the glyph cell even if the ink is smaller.
+ TIGHT_INK_EXTENTS,
+ // A box that tightly encloses all the painted pixels
+ // (although actually on Windows, at least, it may be
+ // slightly larger than strictly necessary because
+ // we can't get precise extents with ClearType).
+ TIGHT_HINTED_OUTLINE_EXTENTS
+ // A box that tightly encloses the glyph outline,
+ // ignoring possible antialiasing pixels that extend
+ // beyond this.
+ // NOTE: The default implementation of gfxFont::Measure(),
+ // which works with the glyph extents cache, does not
+ // differentiate between this and TIGHT_INK_EXTENTS.
+ // Whether the distinction is important depends on the
+ // antialiasing behavior of the platform; currently the
+ // distinction is only implemented in the gfxWindowsFont
+ // subclass, because of ClearType's tendency to paint
+ // outside the hinted outline.
+ // Also NOTE: it is relatively expensive to request this,
+ // as it does not use cached glyph extents in the font.
+ } BoundingBoxType;
+
+ const nsCString& GetName() const { return mFontEntry->Name(); }
+ const gfxFontStyle* GetStyle() const { return &mStyle; }
+
+ virtual gfxFont* CopyWithAntialiasOption(AntialiasOption anAAOption) const {
+ // platforms where this actually matters should override
+ return nullptr;
+ }
+
+ gfxFloat GetAdjustedSize() const {
+ // mAdjustedSize is cached here if not already set to a non-zero value;
+ // but it may be overridden by a value computed in metrics initialization
+ // from font-size-adjust.
+ if (mAdjustedSize < 0.0) {
+ mAdjustedSize = mStyle.AdjustedSizeMustBeZero()
+ ? 0.0
+ : mStyle.size * mFontEntry->mSizeAdjust;
+ }
+ return mAdjustedSize;
+ }
+
+ float FUnitsToDevUnitsFactor() const {
+ // check this was set up during font initialization
+ NS_ASSERTION(mFUnitsConvFactor >= 0.0f, "mFUnitsConvFactor not valid");
+ return mFUnitsConvFactor;
+ }
+
+ // check whether this is an sfnt we can potentially use with harfbuzz
+ bool FontCanSupportHarfBuzz() const { return mFontEntry->HasCmapTable(); }
+
+ // check whether this is an sfnt we can potentially use with Graphite
+ bool FontCanSupportGraphite() const {
+ return mFontEntry->HasGraphiteTables();
+ }
+
+ // Whether this is a font that may be doing full-color rendering,
+ // and therefore needs us to use a mask for text-shadow even when
+ // we're not actually blurring.
+ bool AlwaysNeedsMaskForShadow() const {
+ return mFontEntry->TryGetColorGlyphs() || mFontEntry->TryGetSVGData(this) ||
+ mFontEntry->HasFontTable(TRUETYPE_TAG('C', 'B', 'D', 'T')) ||
+ mFontEntry->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'));
+ }
+
+ // whether a feature is supported by the font (limited to a small set
+ // of features for which some form of fallback needs to be implemented)
+ bool SupportsFeature(Script aScript, uint32_t aFeatureTag);
+
+ // whether the font supports "real" small caps, petite caps etc.
+ // aFallbackToSmallCaps true when petite caps should fallback to small caps
+ bool SupportsVariantCaps(Script aScript, uint32_t aVariantCaps,
+ bool& aFallbackToSmallCaps,
+ bool& aSyntheticLowerToSmallCaps,
+ bool& aSyntheticUpperToSmallCaps);
+
+ // whether the font supports subscript/superscript feature
+ // for fallback, need to verify that all characters in the run
+ // have variant substitutions
+ bool SupportsSubSuperscript(uint32_t aSubSuperscript, const uint8_t* aString,
+ uint32_t aLength, Script aRunScript);
+
+ bool SupportsSubSuperscript(uint32_t aSubSuperscript, const char16_t* aString,
+ uint32_t aLength, Script aRunScript);
+
+ // whether the specified feature will apply to the given character
+ bool FeatureWillHandleChar(Script aRunScript, uint32_t aFeature,
+ uint32_t aUnicode);
+
+ // Subclasses may choose to look up glyph ids for characters.
+ // If they do not override this, gfxHarfBuzzShaper will fetch the cmap
+ // table and use that.
+ virtual bool ProvidesGetGlyph() const { return false; }
+ // Map unicode character to glyph ID.
+ // Only used if ProvidesGetGlyph() returns true.
+ virtual uint32_t GetGlyph(uint32_t unicode, uint32_t variation_selector) {
+ return 0;
+ }
+
+ // Return the advance of a glyph.
+ gfxFloat GetGlyphAdvance(uint16_t aGID, bool aVertical = false);
+
+ // Return the advance of a given Unicode char in isolation.
+ // Returns -1.0 if the char is not supported.
+ gfxFloat GetCharAdvance(uint32_t aUnicode, bool aVertical = false);
+
+ gfxFloat SynthesizeSpaceWidth(uint32_t aCh);
+
+ // Work out whether cairo will snap inter-glyph spacing to pixels
+ // when rendering to the given drawTarget.
+ RoundingFlags GetRoundOffsetsToPixels(DrawTarget* aDrawTarget);
+
+ virtual bool ShouldHintMetrics() const { return true; }
+ virtual bool ShouldRoundXOffset(cairo_t* aCairo) const { return true; }
+
+ // Return the font's owned harfbuzz shaper, creating and initializing it if
+ // necessary; returns null if shaper initialization has failed.
+ gfxHarfBuzzShaper* GetHarfBuzzShaper();
+
+ // Font metrics
+ struct Metrics {
+ gfxFloat capHeight;
+ gfxFloat xHeight;
+ gfxFloat strikeoutSize;
+ gfxFloat strikeoutOffset;
+ gfxFloat underlineSize;
+ gfxFloat underlineOffset;
+
+ gfxFloat internalLeading;
+ gfxFloat externalLeading;
+
+ gfxFloat emHeight;
+ gfxFloat emAscent;
+ gfxFloat emDescent;
+ gfxFloat maxHeight;
+ gfxFloat maxAscent;
+ gfxFloat maxDescent;
+ gfxFloat maxAdvance;
+
+ gfxFloat aveCharWidth;
+ gfxFloat spaceWidth;
+ gfxFloat zeroWidth; // -1 if there was no zero glyph
+ gfxFloat ideographicWidth; // -1 if kWaterIdeograph is not supported
+
+ gfxFloat ZeroOrAveCharWidth() const {
+ return zeroWidth >= 0 ? zeroWidth : aveCharWidth;
+ }
+ };
+ // Unicode character used as basis for 'ic' unit:
+ static constexpr uint32_t kWaterIdeograph = 0x6C34;
+
+ typedef nsFontMetrics::FontOrientation Orientation;
+
+ const Metrics& GetMetrics(Orientation aOrientation) {
+ if (aOrientation == nsFontMetrics::eHorizontal) {
+ return GetHorizontalMetrics();
+ }
+ if (!mVerticalMetrics) {
+ CreateVerticalMetrics();
+ }
+ return *mVerticalMetrics;
+ }
+
+ /**
+ * We let layout specify spacing on either side of any
+ * character. We need to specify both before and after
+ * spacing so that substring measurement can do the right things.
+ * These values are in appunits. They're always an integral number of
+ * appunits, but we specify them in floats in case very large spacing
+ * values are required.
+ */
+ struct Spacing {
+ gfxFloat mBefore;
+ gfxFloat mAfter;
+ };
+ /**
+ * Metrics for a particular string
+ */
+ struct RunMetrics {
+ RunMetrics() { mAdvanceWidth = mAscent = mDescent = 0.0; }
+
+ void CombineWith(const RunMetrics& aOther, bool aOtherIsOnLeft);
+
+ // can be negative (partly due to negative spacing).
+ // Advance widths should be additive: the advance width of the
+ // (offset1, length1) plus the advance width of (offset1 + length1,
+ // length2) should be the advance width of (offset1, length1 + length2)
+ gfxFloat mAdvanceWidth;
+
+ // For zero-width substrings, these must be zero!
+ gfxFloat mAscent; // always non-negative
+ gfxFloat mDescent; // always non-negative
+
+ // Bounding box that is guaranteed to include everything drawn.
+ // If a tight boundingBox was requested when these metrics were
+ // generated, this will tightly wrap the glyphs, otherwise it is
+ // "loose" and may be larger than the true bounding box.
+ // Coordinates are relative to the baseline left origin, so typically
+ // mBoundingBox.y == -mAscent
+ gfxRect mBoundingBox;
+ };
+
+ /**
+ * Draw a series of glyphs to aContext. The direction of aTextRun must
+ * be honoured.
+ * @param aStart the first character to draw
+ * @param aEnd draw characters up to here
+ * @param aPt the baseline origin; the left end of the baseline
+ * for LTR textruns, the right end for RTL textruns.
+ * On return, this will be updated to the other end of the baseline.
+ * In application units, really!
+ * @param aRunParams record with drawing parameters, see TextRunDrawParams.
+ * Particular fields of interest include
+ * .spacing spacing to insert before and after characters (for RTL
+ * glyphs, before-spacing is inserted to the right of characters). There
+ * are aEnd - aStart elements in this array, unless it's null to indicate
+ * that there is no spacing.
+ * .drawMode specifies whether the fill or stroke of the glyph should be
+ * drawn, or if it should be drawn into the current path
+ * .contextPaint information about how to construct the fill and
+ * stroke pattern. Can be nullptr if we are not stroking the text, which
+ * indicates that the current source from context should be used for fill
+ * .context the Thebes graphics context to which we're drawing
+ * .dt Moz2D DrawTarget to which we're drawing
+ *
+ * Callers guarantee:
+ * -- aStart and aEnd are aligned to cluster and ligature boundaries
+ * -- all glyphs use this font
+ */
+ void Draw(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd,
+ mozilla::gfx::Point* aPt, TextRunDrawParams& aRunParams,
+ mozilla::gfx::ShapedTextFlags aOrientation);
+
+ /**
+ * Draw the emphasis marks for the given text run. Its prerequisite
+ * and output are similiar to the method Draw().
+ * @param aPt the baseline origin of the emphasis marks.
+ * @param aParams some drawing parameters, see EmphasisMarkDrawParams.
+ */
+ void DrawEmphasisMarks(const gfxTextRun* aShapedText,
+ mozilla::gfx::Point* aPt, uint32_t aOffset,
+ uint32_t aCount,
+ const EmphasisMarkDrawParams& aParams);
+
+ /**
+ * Measure a run of characters. See gfxTextRun::Metrics.
+ * @param aTight if false, then return the union of the glyph extents
+ * with the font-box for the characters (the rectangle with x=0,width=
+ * the advance width for the character run,y=-(font ascent), and height=
+ * font ascent + font descent). Otherwise, we must return as tight as possible
+ * an approximation to the area actually painted by glyphs.
+ * @param aDrawTargetForTightBoundingBox when aTight is true, this must
+ * be non-null.
+ * @param aSpacing spacing to insert before and after glyphs. The bounding box
+ * need not include the spacing itself, but the spacing affects the glyph
+ * positions. null if there is no spacing.
+ *
+ * Callers guarantee:
+ * -- aStart and aEnd are aligned to cluster and ligature boundaries
+ * -- all glyphs use this font
+ *
+ * The default implementation just uses font metrics and aTextRun's
+ * advances, and assumes no characters fall outside the font box. In
+ * general this is insufficient, because that assumption is not always true.
+ */
+ virtual RunMetrics Measure(const gfxTextRun* aTextRun, uint32_t aStart,
+ uint32_t aEnd, BoundingBoxType aBoundingBoxType,
+ DrawTarget* aDrawTargetForTightBoundingBox,
+ Spacing* aSpacing,
+ mozilla::gfx::ShapedTextFlags aOrientation);
+ /**
+ * Line breaks have been changed at the beginning and/or end of a substring
+ * of the text. Reshaping may be required; glyph updating is permitted.
+ * @return true if anything was changed, false otherwise
+ */
+ bool NotifyLineBreaksChanged(gfxTextRun* aTextRun, uint32_t aStart,
+ uint32_t aLength) {
+ return false;
+ }
+
+ // Expiration tracking
+ nsExpirationState* GetExpirationState() { return &mExpirationState; }
+
+ // Get the glyphID of a space
+ uint16_t GetSpaceGlyph() const { return mSpaceGlyph; }
+
+ gfxGlyphExtents* GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit);
+
+ void SetupGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphID,
+ bool aNeedTight, gfxGlyphExtents* aExtents);
+
+ virtual bool AllowSubpixelAA() const { return true; }
+
+ bool ApplySyntheticBold() const { return mApplySyntheticBold; }
+
+ float AngleForSyntheticOblique() const;
+ float SkewForSyntheticOblique() const;
+
+ // Amount by which synthetic bold "fattens" the glyphs:
+ // For size S up to a threshold size T, we use (0.25 + 3S / 4T),
+ // so that the result ranges from 0.25 to 1.0; thereafter,
+ // simply use (S / T).
+ gfxFloat GetSyntheticBoldOffset() const {
+ gfxFloat size = GetAdjustedSize();
+ const gfxFloat threshold = 48.0;
+ return size < threshold ? (0.25 + 0.75 * size / threshold)
+ : (size / threshold);
+ }
+
+ gfxFontEntry* GetFontEntry() const { return mFontEntry.get(); }
+ bool HasCharacter(uint32_t ch) const {
+ if (!mIsValid || (mUnicodeRangeMap && !mUnicodeRangeMap->test(ch))) {
+ return false;
+ }
+ return mFontEntry->HasCharacter(ch);
+ }
+
+ const gfxCharacterMap* GetUnicodeRangeMap() const {
+ return mUnicodeRangeMap.get();
+ }
+
+ void SetUnicodeRangeMap(gfxCharacterMap* aUnicodeRangeMap) {
+ mUnicodeRangeMap = aUnicodeRangeMap;
+ }
+
+ uint16_t GetUVSGlyph(uint32_t aCh, uint32_t aVS) const {
+ if (!mIsValid) {
+ return 0;
+ }
+ return mFontEntry->GetUVSGlyph(aCh, aVS);
+ }
+
+ template <typename T>
+ bool InitFakeSmallCapsRun(nsPresContext* aPresContext,
+ DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
+ const T* aText, uint32_t aOffset, uint32_t aLength,
+ FontMatchType aMatchType,
+ mozilla::gfx::ShapedTextFlags aOrientation,
+ Script aScript, nsAtom* aLanguage,
+ bool aSyntheticLower, bool aSyntheticUpper);
+
+ // call the (virtual) InitTextRun method to do glyph generation/shaping,
+ // limiting the length of text passed by processing the run in multiple
+ // segments if necessary
+ template <typename T>
+ bool SplitAndInitTextRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
+ const T* aString, uint32_t aRunStart,
+ uint32_t aRunLength, Script aRunScript,
+ nsAtom* aLanguage,
+ mozilla::gfx::ShapedTextFlags aOrientation);
+
+ // Get a ShapedWord representing a single space for use in setting up a
+ // gfxTextRun.
+ bool ProcessSingleSpaceShapedWord(
+ DrawTarget* aDrawTarget, bool aVertical, int32_t aAppUnitsPerDevUnit,
+ mozilla::gfx::ShapedTextFlags aFlags, RoundingFlags aRounding,
+ const std::function<void(gfxShapedWord*)>& aCallback);
+
+ // Called by the gfxFontCache timer to increment the age of all the words,
+ // so that they'll expire after a sufficient period of non-use.
+ // Returns true if the cache is now empty, otherwise false.
+ bool AgeCachedWords();
+
+ // Discard all cached word records; called on memory-pressure notification.
+ void ClearCachedWords() {
+ mozilla::AutoWriteLock lock(mLock);
+ if (mWordCache) {
+ ClearCachedWordsLocked();
+ }
+ }
+ void ClearCachedWordsLocked() MOZ_REQUIRES(mLock) {
+ MOZ_ASSERT(mWordCache);
+ mWordCache->Clear();
+ }
+
+ // Glyph rendering/geometry has changed, so invalidate data as necessary.
+ void NotifyGlyphsChanged() const;
+
+ virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const;
+ virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const;
+
+ typedef enum {
+ FONT_TYPE_DWRITE,
+ FONT_TYPE_GDI,
+ FONT_TYPE_FT2,
+ FONT_TYPE_MAC,
+ FONT_TYPE_OS2,
+ FONT_TYPE_CAIRO,
+ FONT_TYPE_FONTCONFIG
+ } FontType;
+
+ virtual FontType GetType() const = 0;
+
+ const RefPtr<mozilla::gfx::UnscaledFont>& GetUnscaledFont() const {
+ return mUnscaledFont;
+ }
+
+ virtual already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont(
+ const TextRunDrawParams& aRunParams) = 0;
+ already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont(
+ mozilla::gfx::DrawTarget* aDrawTarget);
+
+ // gfxFont implementations may cache ScaledFont versions other than the
+ // default, so InitializeScaledFont must support explicitly specifying
+ // which ScaledFonts to initialize.
+ void InitializeScaledFont(
+ const RefPtr<mozilla::gfx::ScaledFont>& aScaledFont);
+
+ bool KerningDisabled() const { return mKerningSet && !mKerningEnabled; }
+
+ /**
+ * Subclass this object to be notified of glyph changes. Delete the object
+ * when no longer needed.
+ */
+ class GlyphChangeObserver {
+ public:
+ virtual ~GlyphChangeObserver() {
+ if (mFont) {
+ mFont->RemoveGlyphChangeObserver(this);
+ }
+ }
+ // This gets called when the gfxFont dies.
+ void ForgetFont() { mFont = nullptr; }
+ virtual void NotifyGlyphsChanged() = 0;
+
+ protected:
+ explicit GlyphChangeObserver(gfxFont* aFont) : mFont(aFont) {
+ mFont->AddGlyphChangeObserver(this);
+ }
+ // This pointer is nulled by ForgetFont in the gfxFont's
+ // destructor. Before the gfxFont dies.
+ gfxFont* MOZ_NON_OWNING_REF mFont;
+ };
+ friend class GlyphChangeObserver;
+
+ bool GlyphsMayChange() const {
+ // Currently only fonts with SVG glyphs can have animated glyphs
+ return mFontEntry->TryGetSVGData(this);
+ }
+
+ static void DestroySingletons() {
+ delete sScriptTagToCode;
+ delete sDefaultFeatures;
+ }
+
+ // Call TryGetMathTable() to try and load the Open Type MATH table.
+ // If (and ONLY if) TryGetMathTable() has returned true, the MathTable()
+ // method may be called to access the gfxMathTable data.
+ bool TryGetMathTable();
+ gfxMathTable* MathTable() const {
+ MOZ_RELEASE_ASSERT(mMathTable,
+ "A successful call to TryGetMathTable() must be "
+ "performed before calling this function");
+ return mMathTable;
+ }
+
+ // Return a cloned font resized and offset to simulate sub/superscript
+ // glyphs. This does not add a reference to the returned font.
+ already_AddRefed<gfxFont> GetSubSuperscriptFont(
+ int32_t aAppUnitsPerDevPixel) const;
+
+ bool HasColorGlyphFor(uint32_t aCh, uint32_t aNextCh);
+
+ protected:
+ virtual const Metrics& GetHorizontalMetrics() const = 0;
+
+ void CreateVerticalMetrics();
+
+ bool MeasureGlyphs(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd,
+ BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget, Spacing* aSpacing,
+ gfxGlyphExtents* aExtents, bool aIsRTL,
+ bool aNeedsGlyphExtents, RunMetrics& aMetrics,
+ gfxFloat* aAdvanceMin, gfxFloat* aAdvanceMax);
+
+ bool MeasureGlyphs(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd,
+ BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget, Spacing* aSpacing, bool aIsRTL,
+ RunMetrics& aMetrics);
+
+ // Template parameters for DrawGlyphs/DrawOneGlyph, used to select
+ // simplified versions of the methods in the most common cases.
+ enum class FontComplexityT { SimpleFont, ComplexFont };
+ enum class SpacingT { NoSpacing, HasSpacing };
+
+ // Output a run of glyphs at *aPt, which is updated to follow the last glyph
+ // in the run. This method also takes account of any letter-spacing provided
+ // in aRunParams.
+ template <FontComplexityT FC, SpacingT S>
+ bool DrawGlyphs(const gfxShapedText* aShapedText,
+ uint32_t aOffset, // offset in the textrun
+ uint32_t aCount, // length of run to draw
+ mozilla::gfx::Point* aPt,
+ // transform for mOffset field in DetailedGlyph records,
+ // to account for rotations (may be null)
+ const mozilla::gfx::Matrix* aOffsetMatrix,
+ GlyphBufferAzure& aBuffer);
+
+ // Output a single glyph at *aPt.
+ // Normal glyphs are simply accumulated in aBuffer until it is full and
+ // gets flushed, but SVG or color-font glyphs will instead be rendered
+ // directly to the destination (found from the buffer's parameters).
+ template <FontComplexityT FC>
+ void DrawOneGlyph(uint32_t aGlyphID, const mozilla::gfx::Point& aPt,
+ GlyphBufferAzure& aBuffer, bool* aEmittedGlyphs);
+
+ // Helper for DrawOneGlyph to handle missing glyphs, rendering either
+ // nothing (for default-ignorables) or a missing-glyph hexbox.
+ bool DrawMissingGlyph(const TextRunDrawParams& aRunParams,
+ const FontDrawParams& aFontParams,
+ const gfxShapedText::DetailedGlyph* aDetails,
+ const mozilla::gfx::Point& aPt);
+
+ // set the font size and offset used for
+ // synthetic subscript/superscript glyphs
+ void CalculateSubSuperSizeAndOffset(int32_t aAppUnitsPerDevPixel,
+ gfxFloat& aSubSuperSizeRatio,
+ float& aBaselineOffset);
+
+ // Return a font that is a "clone" of this one, but reduced to 80% size
+ // (and with variantCaps set to normal). This does not add a reference to
+ // the returned font.
+ already_AddRefed<gfxFont> GetSmallCapsFont() const;
+
+ // subclasses may provide (possibly hinted) glyph widths (in font units);
+ // if they do not override this, harfbuzz will use unhinted widths
+ // derived from the font tables
+ virtual bool ProvidesGlyphWidths() const { return false; }
+
+ // The return value is interpreted as a horizontal advance in 16.16 fixed
+ // point format.
+ virtual int32_t GetGlyphWidth(uint16_t aGID) { return -1; }
+
+ virtual bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds,
+ bool aTight = false) {
+ return false;
+ }
+
+ bool IsSpaceGlyphInvisible(DrawTarget* aRefDrawTarget,
+ const gfxTextRun* aTextRun);
+
+ void AddGlyphChangeObserver(GlyphChangeObserver* aObserver);
+ void RemoveGlyphChangeObserver(GlyphChangeObserver* aObserver);
+
+ // whether font contains substitution lookups containing spaces
+ bool HasSubstitutionRulesWithSpaceLookups(Script aRunScript) const;
+
+ // do spaces participate in shaping rules? if so, can't used word cache
+ // Note that this function uses HasGraphiteSpaceContextuals, so it can only
+ // return a "hint" to the correct answer. The calling code must ensure it
+ // performs safe actions independent of the value returned.
+ tainted_boolean_hint SpaceMayParticipateInShaping(Script aRunScript) const;
+
+ // For 8-bit text, expand to 16-bit and then call the following method.
+ bool ShapeText(DrawTarget* aContext, const uint8_t* aText,
+ uint32_t aOffset, // dest offset in gfxShapedText
+ uint32_t aLength, Script aScript, nsAtom* aLanguage,
+ bool aVertical, RoundingFlags aRounding,
+ gfxShapedText* aShapedText); // where to store the result
+
+ // Call the appropriate shaper to generate glyphs for aText and store
+ // them into aShapedText.
+ virtual bool ShapeText(DrawTarget* aContext, const char16_t* aText,
+ uint32_t aOffset, uint32_t aLength, Script aScript,
+ nsAtom* aLanguage, bool aVertical,
+ RoundingFlags aRounding, gfxShapedText* aShapedText);
+
+ // Helper to adjust for synthetic bold and set character-type flags
+ // in the shaped text; implementations of ShapeText should call this
+ // after glyph shaping has been completed.
+ void PostShapingFixup(DrawTarget* aContext, const char16_t* aText,
+ uint32_t aOffset, // position within aShapedText
+ uint32_t aLength, bool aVertical,
+ gfxShapedText* aShapedText);
+
+ // Shape text directly into a range within a textrun, without using the
+ // font's word cache. Intended for use when the font has layout features
+ // that involve space, and therefore require shaping complete runs rather
+ // than isolated words, or for long strings that are inefficient to cache.
+ // This will split the text on "invalid" characters (tab/newline) that are
+ // not handled via normal shaping, but does not otherwise divide up the
+ // text.
+ template <typename T>
+ bool ShapeTextWithoutWordCache(DrawTarget* aDrawTarget, const T* aText,
+ uint32_t aOffset, uint32_t aLength,
+ Script aScript, nsAtom* aLanguage,
+ bool aVertical, RoundingFlags aRounding,
+ gfxTextRun* aTextRun);
+
+ // Shape a fragment of text (a run that is known to contain only
+ // "valid" characters, no newlines/tabs/other control chars).
+ // All non-wordcache shaping goes through here; this is the function
+ // that will ensure we don't pass excessively long runs to the various
+ // platform shapers.
+ template <typename T>
+ bool ShapeFragmentWithoutWordCache(DrawTarget* aDrawTarget, const T* aText,
+ uint32_t aOffset, uint32_t aLength,
+ Script aScript, nsAtom* aLanguage,
+ bool aVertical, RoundingFlags aRounding,
+ gfxTextRun* aTextRun);
+
+ void CheckForFeaturesInvolvingSpace() const;
+
+ // Get a ShapedWord representing the given text (either 8- or 16-bit)
+ // for use in setting up a gfxTextRun.
+ template <typename T, typename Func>
+ bool ProcessShapedWordInternal(DrawTarget* aDrawTarget, const T* aText,
+ uint32_t aLength, uint32_t aHash,
+ Script aRunScript, nsAtom* aLanguage,
+ bool aVertical, int32_t aAppUnitsPerDevUnit,
+ mozilla::gfx::ShapedTextFlags aFlags,
+ RoundingFlags aRounding,
+ gfxTextPerfMetrics* aTextPerf, Func aCallback);
+
+ // whether a given feature is included in feature settings from both the
+ // font and the style. aFeatureOn set if resolved feature value is non-zero
+ bool HasFeatureSet(uint32_t aFeature, bool& aFeatureOn);
+
+ // used when analyzing whether a font has space contextual lookups
+ static mozilla::Atomic<nsTHashMap<nsUint32HashKey, Script>*> sScriptTagToCode;
+ static mozilla::Atomic<nsTHashSet<uint32_t>*> sDefaultFeatures;
+
+ RefPtr<gfxFontEntry> mFontEntry;
+ mutable mozilla::RWLock mLock;
+
+ struct CacheHashKey {
+ union {
+ const uint8_t* mSingle;
+ const char16_t* mDouble;
+ } mText;
+ uint32_t mLength;
+ mozilla::gfx::ShapedTextFlags mFlags;
+ Script mScript;
+ RefPtr<nsAtom> mLanguage;
+ int32_t mAppUnitsPerDevUnit;
+ PLDHashNumber mHashKey;
+ bool mTextIs8Bit;
+ RoundingFlags mRounding;
+
+ CacheHashKey(const uint8_t* aText, uint32_t aLength, uint32_t aStringHash,
+ Script aScriptCode, nsAtom* aLanguage,
+ int32_t aAppUnitsPerDevUnit,
+ mozilla::gfx::ShapedTextFlags aFlags, RoundingFlags aRounding)
+ : mLength(aLength),
+ mFlags(aFlags),
+ mScript(aScriptCode),
+ mLanguage(aLanguage),
+ mAppUnitsPerDevUnit(aAppUnitsPerDevUnit),
+ mHashKey(aStringHash + static_cast<int32_t>(aScriptCode) +
+ aAppUnitsPerDevUnit * 0x100 + uint16_t(aFlags) * 0x10000 +
+ int(aRounding) + (aLanguage ? aLanguage->hash() : 0)),
+ mTextIs8Bit(true),
+ mRounding(aRounding) {
+ NS_ASSERTION(aFlags & mozilla::gfx::ShapedTextFlags::TEXT_IS_8BIT,
+ "8-bit flag should have been set");
+ mText.mSingle = aText;
+ }
+
+ CacheHashKey(const char16_t* aText, uint32_t aLength, uint32_t aStringHash,
+ Script aScriptCode, nsAtom* aLanguage,
+ int32_t aAppUnitsPerDevUnit,
+ mozilla::gfx::ShapedTextFlags aFlags, RoundingFlags aRounding)
+ : mLength(aLength),
+ mFlags(aFlags),
+ mScript(aScriptCode),
+ mLanguage(aLanguage),
+ mAppUnitsPerDevUnit(aAppUnitsPerDevUnit),
+ mHashKey(aStringHash + static_cast<int32_t>(aScriptCode) +
+ aAppUnitsPerDevUnit * 0x100 + uint16_t(aFlags) * 0x10000 +
+ int(aRounding)),
+ mTextIs8Bit(false),
+ mRounding(aRounding) {
+ // We can NOT assert that TEXT_IS_8BIT is false in aFlags here,
+ // because this might be an 8bit-only word from a 16-bit textrun,
+ // in which case the text we're passed is still in 16-bit form,
+ // and we'll have to use an 8-to-16bit comparison in KeyEquals.
+ mText.mDouble = aText;
+ }
+ };
+
+ class CacheHashEntry : public PLDHashEntryHdr {
+ public:
+ typedef const CacheHashKey& KeyType;
+ typedef const CacheHashKey* KeyTypePointer;
+
+ // When constructing a new entry in the hashtable, the caller of Put()
+ // will fill us in.
+ explicit CacheHashEntry(KeyTypePointer aKey) {}
+ CacheHashEntry(const CacheHashEntry&) = delete;
+ CacheHashEntry& operator=(const CacheHashEntry&) = delete;
+ CacheHashEntry(CacheHashEntry&&) = default;
+ CacheHashEntry& operator=(CacheHashEntry&&) = default;
+
+ bool KeyEquals(const KeyTypePointer aKey) const;
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+
+ static PLDHashNumber HashKey(const KeyTypePointer aKey) {
+ return aKey->mHashKey;
+ }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(mShapedWord.get());
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ mozilla::UniquePtr<gfxShapedWord> mShapedWord;
+ };
+
+ mozilla::UniquePtr<nsTHashtable<CacheHashEntry>> mWordCache
+ MOZ_GUARDED_BY(mLock);
+
+ static const uint32_t kShapedWordCacheMaxAge = 3;
+
+ nsTArray<mozilla::UniquePtr<gfxGlyphExtents>> mGlyphExtentsArray
+ MOZ_GUARDED_BY(mLock);
+ mozilla::UniquePtr<nsTHashSet<GlyphChangeObserver*>> mGlyphChangeObservers
+ MOZ_GUARDED_BY(mLock);
+
+ // a copy of the font without antialiasing, if needed for separate
+ // measurement by mathml code
+ mozilla::Atomic<gfxFont*> mNonAAFont;
+
+ // we create either or both of these shapers when needed, depending
+ // whether the font has graphite tables, and whether graphite shaping
+ // is actually enabled
+ mozilla::Atomic<gfxHarfBuzzShaper*> mHarfBuzzShaper;
+ mozilla::Atomic<gfxGraphiteShaper*> mGraphiteShaper;
+
+ // If a userfont with unicode-range specified, contains map of *possible*
+ // ranges supported by font. This is set during user-font initialization,
+ // before the font is available to other threads, and thereafter is inert
+ // so no guard is needed.
+ RefPtr<gfxCharacterMap> mUnicodeRangeMap;
+
+ // This is immutable once initialized by the constructor, so does not need
+ // locking.
+ RefPtr<mozilla::gfx::UnscaledFont> mUnscaledFont;
+
+ mozilla::Atomic<mozilla::gfx::ScaledFont*> mAzureScaledFont;
+
+ // For vertical metrics, created on demand.
+ mozilla::Atomic<Metrics*> mVerticalMetrics;
+
+ // Table used for MathML layout.
+ mozilla::Atomic<gfxMathTable*> mMathTable;
+
+ gfxFontStyle mStyle;
+ mutable gfxFloat mAdjustedSize;
+
+ // Conversion factor from font units to dev units; note that this may be
+ // zero (in the degenerate case where mAdjustedSize has become zero).
+ // This is OK because we only multiply by this factor, never divide.
+ float mFUnitsConvFactor;
+
+ // This is guarded by gfxFontCache::GetCache()->GetMutex() but it is difficult
+ // to annotate that fact.
+ nsExpirationState mExpirationState;
+
+ // Glyph ID of the font's <space> glyph, zero if missing
+ uint16_t mSpaceGlyph = 0;
+
+ // the AA setting requested for this font - may affect glyph bounds
+ AntialiasOption mAntialiasOption;
+
+ bool mIsValid;
+
+ // use synthetic bolding for environments where this is not supported
+ // by the platform
+ bool mApplySyntheticBold;
+
+ bool mKerningSet; // kerning explicitly set?
+ bool mKerningEnabled; // if set, on or off?
+
+ mozilla::Atomic<bool> mMathInitialized; // TryGetMathTable() called?
+
+ // Helper for subclasses that want to initialize standard metrics from the
+ // tables of sfnt (TrueType/OpenType) fonts.
+ // This will use mFUnitsConvFactor if it is already set, else compute it
+ // from mAdjustedSize and the unitsPerEm in the font's 'head' table.
+ // Returns TRUE and sets mIsValid=TRUE if successful;
+ // Returns TRUE but leaves mIsValid=FALSE if the font seems to be broken.
+ // Returns FALSE if the font does not appear to be an sfnt at all,
+ // and should be handled (if possible) using other APIs.
+ bool InitMetricsFromSfntTables(Metrics& aMetrics);
+
+ // Helper to calculate various derived metrics from the results of
+ // InitMetricsFromSfntTables or equivalent platform code
+ void CalculateDerivedMetrics(Metrics& aMetrics);
+
+ // some fonts have bad metrics, this method sanitize them.
+ // if this font has bad underline offset, aIsBadUnderlineFont should be true.
+ void SanitizeMetrics(Metrics* aMetrics, bool aIsBadUnderlineFont);
+
+ bool RenderSVGGlyph(gfxContext* aContext,
+ mozilla::layout::TextDrawTarget* aTextDrawer,
+ mozilla::gfx::Point aPoint, uint32_t aGlyphId,
+ SVGContextPaint* aContextPaint) const;
+ bool RenderSVGGlyph(gfxContext* aContext,
+ mozilla::layout::TextDrawTarget* aTextDrawer,
+ mozilla::gfx::Point aPoint, uint32_t aGlyphId,
+ SVGContextPaint* aContextPaint,
+ gfxTextRunDrawCallbacks* aCallbacks,
+ bool& aEmittedGlyphs) const;
+
+ bool RenderColorGlyph(DrawTarget* aDrawTarget, gfxContext* aContext,
+ mozilla::layout::TextDrawTarget* aTextDrawer,
+ const FontDrawParams& aFontParams,
+ const mozilla::gfx::Point& aPoint, uint32_t aGlyphId);
+
+ // Subclasses can override to return true if the platform is able to render
+ // COLR-font glyphs directly, instead of us painting the layers explicitly.
+ // (Currently used only for COLR.v0 fonts on macOS.)
+ virtual bool UseNativeColrFontSupport() const { return false; }
+
+ // Bug 674909. When synthetic bolding text by drawing twice, need to
+ // render using a pixel offset in device pixels, otherwise text
+ // doesn't appear bolded, it appears as if a bad text shadow exists
+ // when a non-identity transform exists. Use an offset factor so that
+ // the second draw occurs at a constant offset in device pixels.
+ // This helper calculates the scale factor we need to apply to the
+ // synthetic-bold offset.
+ static mozilla::gfx::Float CalcXScale(DrawTarget* aDrawTarget);
+};
+
+// proportion of ascent used for x-height, if unable to read value from font
+#define DEFAULT_XHEIGHT_FACTOR 0.56f
+
+// Parameters passed to gfxFont methods for drawing glyphs from a textrun.
+// The TextRunDrawParams are set up once per textrun; the FontDrawParams
+// are dependent on the specific font, so they are set per GlyphRun.
+
+struct MOZ_STACK_CLASS TextRunDrawParams {
+ RefPtr<mozilla::gfx::DrawTarget> dt;
+ gfxContext* context = nullptr;
+ gfxFont::Spacing* spacing = nullptr;
+ gfxTextRunDrawCallbacks* callbacks = nullptr;
+ mozilla::SVGContextPaint* runContextPaint = nullptr;
+ mozilla::layout::TextDrawTarget* textDrawer = nullptr;
+ mozilla::LayoutDeviceRect clipRect;
+ mozilla::gfx::Float direction = 1.0f;
+ double devPerApp = 1.0;
+ nscolor textStrokeColor = 0;
+ gfxPattern* textStrokePattern = nullptr;
+ const mozilla::gfx::StrokeOptions* strokeOpts = nullptr;
+ const mozilla::gfx::DrawOptions* drawOpts = nullptr;
+ const mozilla::gfx::FontPaletteValueSet* paletteValueSet = nullptr;
+ nsAtom* fontPalette = nullptr;
+ DrawMode drawMode = DrawMode::GLYPH_FILL;
+ bool isVerticalRun = false;
+ bool isRTL = false;
+ bool paintSVGGlyphs = true;
+ bool allowGDI = true;
+
+ // MRU cache of color-font palettes being used by fonts in the run. We cache
+ // these in the TextRunDrawParams so that we can avoid re-creating a new
+ // palette (which can be quite expensive) for each individual glyph run.
+ using CacheKey = const gfxFont*;
+
+ struct CacheData {
+ CacheKey mKey;
+ mozilla::UniquePtr<nsTArray<mozilla::gfx::sRGBColor>> mPalette;
+ };
+
+ class PaletteCache
+ : public mozilla::MruCache<CacheKey, CacheData, PaletteCache> {
+ public:
+ static mozilla::HashNumber Hash(const CacheKey& aKey) {
+ return mozilla::HashGeneric(aKey);
+ }
+ static bool Match(const CacheKey& aKey, const CacheData& aVal) {
+ return aVal.mKey == aKey;
+ }
+ };
+
+ PaletteCache mPaletteCache;
+
+ // Returns a pointer to a palette owned by the PaletteCache. This is only
+ // valid until the next call to GetPaletteFor (which might evict it) or
+ // until the TextRunDrawParams goes out of scope.
+ nsTArray<mozilla::gfx::sRGBColor>* GetPaletteFor(const gfxFont* aFont);
+};
+
+struct MOZ_STACK_CLASS FontDrawParams {
+ RefPtr<mozilla::gfx::ScaledFont> scaledFont;
+ mozilla::SVGContextPaint* contextPaint;
+ mozilla::gfx::Float synBoldOnePixelOffset;
+ mozilla::gfx::Float obliqueSkew;
+ int32_t extraStrikes;
+ mozilla::gfx::DrawOptions drawOptions;
+ gfxFloat advanceDirection;
+ mozilla::gfx::sRGBColor currentColor;
+ nsTArray<mozilla::gfx::sRGBColor>* palette; // owned by TextRunDrawParams
+ mozilla::gfx::Rect fontExtents;
+ bool isVerticalFont;
+ bool haveSVGGlyphs;
+ bool haveColorGlyphs;
+};
+
+struct MOZ_STACK_CLASS EmphasisMarkDrawParams {
+ gfxContext* context;
+ gfxFont::Spacing* spacing;
+ gfxTextRun* mark;
+ gfxFloat advance;
+ gfxFloat direction;
+ bool isVertical;
+};
+
+#endif
diff --git a/gfx/thebes/gfxFontConstants.h b/gfx/thebes/gfxFontConstants.h
new file mode 100644
index 0000000000..f41af73aad
--- /dev/null
+++ b/gfx/thebes/gfxFontConstants.h
@@ -0,0 +1,185 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* font constants shared by both thebes and layout */
+
+#ifndef GFX_FONT_CONSTANTS_H
+#define GFX_FONT_CONSTANTS_H
+
+/*
+ * This file is separate from gfxFont.h so that layout can include it
+ * without bringing in gfxFont.h and everything it includes.
+ */
+
+#define NS_FONT_STYLE_NORMAL 0
+#define NS_FONT_STYLE_ITALIC 1
+#define NS_FONT_STYLE_OBLIQUE 2
+
+#define NS_FONT_WEIGHT_NORMAL 400
+#define NS_FONT_WEIGHT_BOLD 700
+#define NS_FONT_WEIGHT_THIN 100
+
+#define NS_FONT_STRETCH_ULTRA_CONDENSED 50
+#define NS_FONT_STRETCH_EXTRA_CONDENSED 62
+#define NS_FONT_STRETCH_CONDENSED 75
+#define NS_FONT_STRETCH_SEMI_CONDENSED 87
+#define NS_FONT_STRETCH_NORMAL 100
+#define NS_FONT_STRETCH_SEMI_EXPANDED 112
+#define NS_FONT_STRETCH_EXPANDED 125
+#define NS_FONT_STRETCH_EXTRA_EXPANDED 150
+#define NS_FONT_STRETCH_ULTRA_EXPANDED 200
+
+#define NS_FONT_SMOOTHING_AUTO 0
+#define NS_FONT_SMOOTHING_GRAYSCALE 1
+
+#define NS_FONT_KERNING_AUTO 0
+#define NS_FONT_KERNING_NONE 1
+#define NS_FONT_KERNING_NORMAL 2
+
+#define NS_FONT_OPTICAL_SIZING_AUTO 0
+#define NS_FONT_OPTICAL_SIZING_NONE 1
+
+#define NS_FONT_VARIANT_ALTERNATES_NORMAL 0
+// alternates - simple enumerated values
+#define NS_FONT_VARIANT_ALTERNATES_HISTORICAL (1 << 0)
+
+// alternates - values that use functional syntax
+#define NS_FONT_VARIANT_ALTERNATES_STYLISTIC (1 << 1)
+#define NS_FONT_VARIANT_ALTERNATES_STYLESET (1 << 2)
+#define NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT (1 << 3)
+#define NS_FONT_VARIANT_ALTERNATES_SWASH (1 << 4)
+#define NS_FONT_VARIANT_ALTERNATES_ORNAMENTS (1 << 5)
+#define NS_FONT_VARIANT_ALTERNATES_ANNOTATION (1 << 6)
+#define NS_FONT_VARIANT_ALTERNATES_COUNT 7
+
+#define NS_FONT_VARIANT_ALTERNATES_ENUMERATED_MASK \
+ NS_FONT_VARIANT_ALTERNATES_HISTORICAL
+
+#define NS_FONT_VARIANT_ALTERNATES_FUNCTIONAL_MASK \
+ (NS_FONT_VARIANT_ALTERNATES_STYLISTIC | \
+ NS_FONT_VARIANT_ALTERNATES_STYLESET | \
+ NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT | \
+ NS_FONT_VARIANT_ALTERNATES_SWASH | NS_FONT_VARIANT_ALTERNATES_ORNAMENTS | \
+ NS_FONT_VARIANT_ALTERNATES_ANNOTATION)
+
+#define NS_FONT_VARIANT_CAPS_NORMAL 0
+#define NS_FONT_VARIANT_CAPS_SMALLCAPS 1
+#define NS_FONT_VARIANT_CAPS_ALLSMALL 2
+#define NS_FONT_VARIANT_CAPS_PETITECAPS 3
+#define NS_FONT_VARIANT_CAPS_ALLPETITE 4
+#define NS_FONT_VARIANT_CAPS_TITLING 5
+#define NS_FONT_VARIANT_CAPS_UNICASE 6
+
+#define NS_FONT_VARIANT_EAST_ASIAN_NORMAL 0
+#define NS_FONT_VARIANT_EAST_ASIAN_JIS78 (1 << 0)
+#define NS_FONT_VARIANT_EAST_ASIAN_JIS83 (1 << 1)
+#define NS_FONT_VARIANT_EAST_ASIAN_JIS90 (1 << 2)
+#define NS_FONT_VARIANT_EAST_ASIAN_JIS04 (1 << 3)
+#define NS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED (1 << 4)
+#define NS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL (1 << 5)
+#define NS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH (1 << 6)
+#define NS_FONT_VARIANT_EAST_ASIAN_PROP_WIDTH (1 << 7)
+#define NS_FONT_VARIANT_EAST_ASIAN_RUBY (1 << 8)
+#define NS_FONT_VARIANT_EAST_ASIAN_COUNT 9
+
+#define NS_FONT_VARIANT_EAST_ASIAN_VARIANT_MASK \
+ (NS_FONT_VARIANT_EAST_ASIAN_JIS78 | NS_FONT_VARIANT_EAST_ASIAN_JIS83 | \
+ NS_FONT_VARIANT_EAST_ASIAN_JIS90 | NS_FONT_VARIANT_EAST_ASIAN_JIS04 | \
+ NS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED | \
+ NS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL)
+
+#define NS_FONT_VARIANT_EAST_ASIAN_WIDTH_MASK \
+ (NS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH | \
+ NS_FONT_VARIANT_EAST_ASIAN_PROP_WIDTH)
+
+#define NS_FONT_VARIANT_LIGATURES_NORMAL 0
+#define NS_FONT_VARIANT_LIGATURES_NONE (1 << 0)
+#define NS_FONT_VARIANT_LIGATURES_COMMON (1 << 1)
+#define NS_FONT_VARIANT_LIGATURES_NO_COMMON (1 << 2)
+#define NS_FONT_VARIANT_LIGATURES_DISCRETIONARY (1 << 3)
+#define NS_FONT_VARIANT_LIGATURES_NO_DISCRETIONARY (1 << 4)
+#define NS_FONT_VARIANT_LIGATURES_HISTORICAL (1 << 5)
+#define NS_FONT_VARIANT_LIGATURES_NO_HISTORICAL (1 << 6)
+#define NS_FONT_VARIANT_LIGATURES_CONTEXTUAL (1 << 7)
+#define NS_FONT_VARIANT_LIGATURES_NO_CONTEXTUAL (1 << 8)
+#define NS_FONT_VARIANT_LIGATURES_COUNT 9
+
+#define NS_FONT_VARIANT_LIGATURES_COMMON_MASK \
+ (NS_FONT_VARIANT_LIGATURES_COMMON | NS_FONT_VARIANT_LIGATURES_NO_COMMON)
+
+#define NS_FONT_VARIANT_LIGATURES_DISCRETIONARY_MASK \
+ (NS_FONT_VARIANT_LIGATURES_DISCRETIONARY | \
+ NS_FONT_VARIANT_LIGATURES_NO_DISCRETIONARY)
+
+#define NS_FONT_VARIANT_LIGATURES_HISTORICAL_MASK \
+ (NS_FONT_VARIANT_LIGATURES_HISTORICAL | \
+ NS_FONT_VARIANT_LIGATURES_NO_HISTORICAL)
+
+#define NS_FONT_VARIANT_LIGATURES_CONTEXTUAL_MASK \
+ NS_FONT_VARIANT_LIGATURES_CONTEXTUAL | NS_FONT_VARIANT_LIGATURES_NO_CONTEXTUAL
+
+#define NS_FONT_VARIANT_NUMERIC_NORMAL 0
+#define NS_FONT_VARIANT_NUMERIC_LINING (1 << 0)
+#define NS_FONT_VARIANT_NUMERIC_OLDSTYLE (1 << 1)
+#define NS_FONT_VARIANT_NUMERIC_PROPORTIONAL (1 << 2)
+#define NS_FONT_VARIANT_NUMERIC_TABULAR (1 << 3)
+#define NS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS (1 << 4)
+#define NS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS (1 << 5)
+#define NS_FONT_VARIANT_NUMERIC_SLASHZERO (1 << 6)
+#define NS_FONT_VARIANT_NUMERIC_ORDINAL (1 << 7)
+#define NS_FONT_VARIANT_NUMERIC_COUNT 8
+
+#define NS_FONT_VARIANT_NUMERIC_FIGURE_MASK \
+ NS_FONT_VARIANT_NUMERIC_LINING | NS_FONT_VARIANT_NUMERIC_OLDSTYLE
+
+#define NS_FONT_VARIANT_NUMERIC_SPACING_MASK \
+ NS_FONT_VARIANT_NUMERIC_PROPORTIONAL | NS_FONT_VARIANT_NUMERIC_TABULAR
+
+#define NS_FONT_VARIANT_NUMERIC_FRACTION_MASK \
+ NS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS | \
+ NS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS
+
+#define NS_FONT_VARIANT_POSITION_NORMAL 0
+#define NS_FONT_VARIANT_POSITION_SUPER 1
+#define NS_FONT_VARIANT_POSITION_SUB 2
+
+#define NS_FONT_VARIANT_WIDTH_NORMAL 0
+#define NS_FONT_VARIANT_WIDTH_FULL 1
+#define NS_FONT_VARIANT_WIDTH_HALF 2
+#define NS_FONT_VARIANT_WIDTH_THIRD 3
+#define NS_FONT_VARIANT_WIDTH_QUARTER 4
+
+enum class StyleFontVariantEmoji : uint8_t { Normal, Text, Emoji, Unicode };
+
+// based on fixed offset values used within WebKit
+#define NS_FONT_SUBSCRIPT_OFFSET_RATIO (0.20)
+#define NS_FONT_SUPERSCRIPT_OFFSET_RATIO (0.34)
+
+// this roughly corresponds to font-size: smaller behavior
+// at smaller sizes <20px the ratio is closer to 0.8 while at
+// larger sizes >45px the ratio is closer to 0.667 and in between
+// a blend of values is used
+#define NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL (0.82)
+#define NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE (0.667)
+#define NS_FONT_SUB_SUPER_SMALL_SIZE (20.0)
+#define NS_FONT_SUB_SUPER_LARGE_SIZE (45.0)
+
+// pref lang id's for font prefs
+enum eFontPrefLang {
+#define FONT_PREF_LANG(enum_id_, str_, atom_id_) eFontPrefLang_##enum_id_
+#include "gfxFontPrefLangList.h"
+#undef FONT_PREF_LANG
+
+ ,
+ eFontPrefLang_CJKSet // special code for CJK set
+ ,
+ eFontPrefLang_Emoji // special code for emoji presentation
+ ,
+ eFontPrefLang_First = eFontPrefLang_Western,
+ eFontPrefLang_Last = eFontPrefLang_Others,
+ eFontPrefLang_Count = (eFontPrefLang_Last - eFontPrefLang_First + 1)
+};
+
+#endif
diff --git a/gfx/thebes/gfxFontEntry.cpp b/gfx/thebes/gfxFontEntry.cpp
new file mode 100644
index 0000000000..6d32cc2cfc
--- /dev/null
+++ b/gfx/thebes/gfxFontEntry.cpp
@@ -0,0 +1,2203 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxFontEntry.h"
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/MathAlgorithms.h"
+
+#include "mozilla/Logging.h"
+
+#include "gfxTextRun.h"
+#include "gfxPlatform.h"
+#include "nsGkAtoms.h"
+
+#include "gfxTypes.h"
+#include "gfxContext.h"
+#include "gfxFontConstants.h"
+#include "gfxGraphiteShaper.h"
+#include "gfxHarfBuzzShaper.h"
+#include "gfxUserFontSet.h"
+#include "gfxPlatformFontList.h"
+#include "nsUnicodeProperties.h"
+#include "nsMathUtils.h"
+#include "nsBidiUtils.h"
+#include "nsStyleConsts.h"
+#include "mozilla/AppUnits.h"
+#include "mozilla/FloatingPoint.h"
+#ifdef MOZ_WASM_SANDBOXING_GRAPHITE
+# include "mozilla/ipc/LibrarySandboxPreload.h"
+#endif
+#include "mozilla/Likely.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/Telemetry.h"
+#include "gfxSVGGlyphs.h"
+#include "gfx2DGlue.h"
+
+#include "harfbuzz/hb.h"
+#include "harfbuzz/hb-ot.h"
+#include "graphite2/Font.h"
+
+#include "ThebesRLBox.h"
+
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::unicode;
+
+void gfxCharacterMap::NotifyMaybeReleased(gfxCharacterMap* aCmap) {
+ // Tell gfxPlatformFontList that a charmap's refcount was decremented,
+ // so it should check whether the object is to be deleted.
+ gfxPlatformFontList::PlatformFontList()->MaybeRemoveCmap(aCmap);
+}
+
+gfxFontEntry::gfxFontEntry(const nsACString& aName, bool aIsStandardFace)
+ : mName(aName),
+ mLock("gfxFontEntry lock"),
+ mFeatureInfoLock("gfxFontEntry featureInfo mutex"),
+ mFixedPitch(false),
+ mIsBadUnderlineFont(false),
+ mIsUserFontContainer(false),
+ mIsDataUserFont(false),
+ mIsLocalUserFont(false),
+ mStandardFace(aIsStandardFace),
+ mIgnoreGDEF(false),
+ mIgnoreGSUB(false),
+ mSkipDefaultFeatureSpaceCheck(false),
+ mSVGInitialized(false),
+ mHasCmapTable(false),
+ mGrFaceInitialized(false),
+ mCheckedForColorGlyph(false),
+ mCheckedForVariationAxes(false),
+ mSpaceGlyphIsInvisible(LazyFlag::Uninitialized),
+ mHasGraphiteTables(LazyFlag::Uninitialized),
+ mHasGraphiteSpaceContextuals(LazyFlag::Uninitialized),
+ mHasColorBitmapTable(LazyFlag::Uninitialized),
+ mHasSpaceFeatures(SpaceFeatures::Uninitialized) {
+ mTrakTable.exchange(kTrakTableUninitialized);
+ memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures));
+ memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures));
+}
+
+gfxFontEntry::~gfxFontEntry() {
+ // Should not be dropped by stylo
+ MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal());
+
+ hb_blob_destroy(mCOLR.exchange(nullptr));
+ hb_blob_destroy(mCPAL.exchange(nullptr));
+
+ if (TrakTableInitialized()) {
+ // Only if it was initialized, so that we don't try to call hb_blob_destroy
+ // on the kTrakTableUninitialized flag value!
+ hb_blob_destroy(mTrakTable.exchange(nullptr));
+ }
+
+ // For downloaded fonts, we need to tell the user font cache that this
+ // entry is being deleted.
+ if (mIsDataUserFont) {
+ gfxUserFontSet::UserFontCache::ForgetFont(this);
+ }
+
+ if (mFeatureInputs) {
+ for (auto iter = mFeatureInputs->Iter(); !iter.Done(); iter.Next()) {
+ hb_set_t*& set = iter.Data();
+ hb_set_destroy(set);
+ }
+ }
+
+ delete mFontTableCache.exchange(nullptr);
+ delete mSVGGlyphs.exchange(nullptr);
+ delete[] mUVSData.exchange(nullptr);
+
+ gfxCharacterMap* cmap = mCharacterMap.exchange(nullptr);
+ NS_IF_RELEASE(cmap);
+
+ // By the time the entry is destroyed, all font instances that were
+ // using it should already have been deleted, and so the HB and/or Gr
+ // face objects should have been released.
+ MOZ_ASSERT(!mHBFace);
+ MOZ_ASSERT(!mGrFaceInitialized);
+}
+
+// Only used during initialization, before any other thread has a chance to see
+// the entry, so locking not required.
+void gfxFontEntry::InitializeFrom(fontlist::Face* aFace,
+ const fontlist::Family* aFamily) {
+ mStyleRange = aFace->mStyle;
+ mWeightRange = aFace->mWeight;
+ mStretchRange = aFace->mStretch;
+ mFixedPitch = aFace->mFixedPitch;
+ mIsBadUnderlineFont = aFamily->IsBadUnderlineFamily();
+ mShmemFace = aFace;
+ auto* list = gfxPlatformFontList::PlatformFontList()->SharedFontList();
+ mFamilyName = aFamily->DisplayName().AsString(list);
+ mHasCmapTable = TrySetShmemCharacterMap();
+}
+
+bool gfxFontEntry::TrySetShmemCharacterMap() {
+ MOZ_ASSERT(mShmemFace);
+ auto list = gfxPlatformFontList::PlatformFontList()->SharedFontList();
+ auto* shmemCmap = mShmemFace->mCharacterMap.ToPtr<const SharedBitSet>(list);
+ mShmemCharacterMap.exchange(shmemCmap);
+ return shmemCmap != nullptr;
+}
+
+bool gfxFontEntry::TestCharacterMap(uint32_t aCh) {
+ if (!mCharacterMap && !mShmemCharacterMap) {
+ ReadCMAP();
+ MOZ_ASSERT(mCharacterMap || mShmemCharacterMap,
+ "failed to initialize character map");
+ }
+ return mShmemCharacterMap ? GetShmemCharacterMap()->test(aCh)
+ : GetCharacterMap()->test(aCh);
+}
+
+void gfxFontEntry::EnsureUVSMapInitialized() {
+ // mUVSOffset will not be initialized
+ // until cmap is initialized.
+ if (!mCharacterMap && !mShmemCharacterMap) {
+ ReadCMAP();
+ NS_ASSERTION(mCharacterMap || mShmemCharacterMap,
+ "failed to initialize character map");
+ }
+
+ if (!mUVSOffset) {
+ return;
+ }
+
+ if (!mUVSData) {
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ const uint32_t kCmapTag = TRUETYPE_TAG('c', 'm', 'a', 'p');
+ AutoTable cmapTable(this, kCmapTag);
+ if (cmapTable) {
+ const uint8_t* uvsData = nullptr;
+ unsigned int cmapLen;
+ const char* cmapData = hb_blob_get_data(cmapTable, &cmapLen);
+ rv = gfxFontUtils::ReadCMAPTableFormat14(
+ (const uint8_t*)cmapData + mUVSOffset, cmapLen - mUVSOffset, uvsData);
+ if (NS_SUCCEEDED(rv)) {
+ if (!mUVSData.compareExchange(nullptr, uvsData)) {
+ delete uvsData;
+ }
+ }
+ }
+ if (NS_FAILED(rv)) {
+ mUVSOffset = 0; // don't try to read the table again
+ }
+ }
+}
+
+uint16_t gfxFontEntry::GetUVSGlyph(uint32_t aCh, uint32_t aVS) {
+ EnsureUVSMapInitialized();
+
+ if (const auto* uvsData = GetUVSData()) {
+ return gfxFontUtils::MapUVSToGlyphFormat14(uvsData, aCh, aVS);
+ }
+
+ return 0;
+}
+
+bool gfxFontEntry::SupportsScriptInGSUB(const hb_tag_t* aScriptTags,
+ uint32_t aNumTags) {
+ auto face(GetHBFace());
+
+ unsigned int index;
+ hb_tag_t chosenScript;
+ bool found = hb_ot_layout_table_select_script(
+ face, TRUETYPE_TAG('G', 'S', 'U', 'B'), aNumTags, aScriptTags, &index,
+ &chosenScript);
+
+ return found && chosenScript != TRUETYPE_TAG('D', 'F', 'L', 'T');
+}
+
+nsresult gfxFontEntry::ReadCMAP(FontInfoData* aFontInfoData) {
+ MOZ_ASSERT(false, "using default no-op implementation of ReadCMAP");
+ RefPtr<gfxCharacterMap> cmap = new gfxCharacterMap();
+ if (mCharacterMap.compareExchange(nullptr, cmap.get())) {
+ Unused << cmap.forget(); // mCharacterMap now owns the reference
+ }
+ return NS_OK;
+}
+
+nsCString gfxFontEntry::RealFaceName() {
+ AutoTable nameTable(this, TRUETYPE_TAG('n', 'a', 'm', 'e'));
+ if (nameTable) {
+ nsAutoCString name;
+ nsresult rv = gfxFontUtils::GetFullNameFromTable(nameTable, name);
+ if (NS_SUCCEEDED(rv)) {
+ return std::move(name);
+ }
+ }
+ return Name();
+}
+
+already_AddRefed<gfxFont> gfxFontEntry::FindOrMakeFont(
+ const gfxFontStyle* aStyle, gfxCharacterMap* aUnicodeRangeMap) {
+ RefPtr<gfxFont> font =
+ gfxFontCache::GetCache()->Lookup(this, aStyle, aUnicodeRangeMap);
+ if (font) {
+ return font.forget();
+ }
+
+ gfxFont* newFont = CreateFontInstance(aStyle);
+ if (!newFont) {
+ return nullptr;
+ }
+ if (!newFont->Valid()) {
+ newFont->Destroy();
+ return nullptr;
+ }
+ newFont->SetUnicodeRangeMap(aUnicodeRangeMap);
+ return gfxFontCache::GetCache()->MaybeInsert(newFont);
+}
+
+uint16_t gfxFontEntry::UnitsPerEm() {
+ if (!mUnitsPerEm) {
+ AutoTable headTable(this, TRUETYPE_TAG('h', 'e', 'a', 'd'));
+ if (headTable) {
+ uint32_t len;
+ const HeadTable* head =
+ reinterpret_cast<const HeadTable*>(hb_blob_get_data(headTable, &len));
+ if (len >= sizeof(HeadTable)) {
+ mUnitsPerEm = head->unitsPerEm;
+ if (int16_t(head->xMax) > int16_t(head->xMin) &&
+ int16_t(head->yMax) > int16_t(head->yMin)) {
+ mXMin = head->xMin;
+ mYMin = head->yMin;
+ mXMax = head->xMax;
+ mYMax = head->yMax;
+ }
+ }
+ }
+
+ // if we didn't find a usable 'head' table, or if the value was
+ // outside the valid range, record it as invalid
+ if (mUnitsPerEm < kMinUPEM || mUnitsPerEm > kMaxUPEM) {
+ mUnitsPerEm = kInvalidUPEM;
+ }
+ }
+ return mUnitsPerEm;
+}
+
+bool gfxFontEntry::HasSVGGlyph(uint32_t aGlyphId) {
+ NS_ASSERTION(mSVGInitialized,
+ "SVG data has not yet been loaded. TryGetSVGData() first.");
+ return GetSVGGlyphs()->HasSVGGlyph(aGlyphId);
+}
+
+bool gfxFontEntry::GetSVGGlyphExtents(DrawTarget* aDrawTarget,
+ uint32_t aGlyphId, gfxFloat aSize,
+ gfxRect* aResult) {
+ MOZ_ASSERT(mSVGInitialized,
+ "SVG data has not yet been loaded. TryGetSVGData() first.");
+ MOZ_ASSERT(mUnitsPerEm >= kMinUPEM && mUnitsPerEm <= kMaxUPEM,
+ "font has invalid unitsPerEm");
+
+ gfxMatrix svgToApp(aSize / mUnitsPerEm, 0, 0, aSize / mUnitsPerEm, 0, 0);
+ return GetSVGGlyphs()->GetGlyphExtents(aGlyphId, svgToApp, aResult);
+}
+
+void gfxFontEntry::RenderSVGGlyph(gfxContext* aContext, uint32_t aGlyphId,
+ SVGContextPaint* aContextPaint) {
+ NS_ASSERTION(mSVGInitialized,
+ "SVG data has not yet been loaded. TryGetSVGData() first.");
+ GetSVGGlyphs()->RenderGlyph(aContext, aGlyphId, aContextPaint);
+}
+
+bool gfxFontEntry::TryGetSVGData(const gfxFont* aFont) {
+ if (!gfxPlatform::GetPlatform()->OpenTypeSVGEnabled()) {
+ return false;
+ }
+
+ // We don't support SVG-in-OT glyphs in offscreen-canvas worker threads.
+ if (!NS_IsMainThread()) {
+ return false;
+ }
+
+ if (!mSVGInitialized) {
+ // If UnitsPerEm is not known/valid, we can't use SVG glyphs
+ if (UnitsPerEm() == kInvalidUPEM) {
+ mSVGInitialized = true;
+ return false;
+ }
+
+ // We don't use AutoTable here because we'll pass ownership of this
+ // blob to the gfxSVGGlyphs, once we've confirmed the table exists
+ hb_blob_t* svgTable = GetFontTable(TRUETYPE_TAG('S', 'V', 'G', ' '));
+ if (!svgTable) {
+ mSVGInitialized = true;
+ return false;
+ }
+
+ // gfxSVGGlyphs will hb_blob_destroy() the table when it is finished
+ // with it.
+ auto* svgGlyphs = new gfxSVGGlyphs(svgTable, this);
+ if (!mSVGGlyphs.compareExchange(nullptr, svgGlyphs)) {
+ delete svgGlyphs;
+ }
+ mSVGInitialized = true;
+ }
+
+ if (GetSVGGlyphs()) {
+ AutoWriteLock lock(mLock);
+ if (!mFontsUsingSVGGlyphs.Contains(aFont)) {
+ mFontsUsingSVGGlyphs.AppendElement(aFont);
+ }
+ }
+
+ return !!GetSVGGlyphs();
+}
+
+void gfxFontEntry::NotifyFontDestroyed(gfxFont* aFont) {
+ AutoWriteLock lock(mLock);
+ mFontsUsingSVGGlyphs.RemoveElement(aFont);
+}
+
+void gfxFontEntry::NotifyGlyphsChanged() {
+ AutoReadLock lock(mLock);
+ for (uint32_t i = 0, count = mFontsUsingSVGGlyphs.Length(); i < count; ++i) {
+ const gfxFont* font = mFontsUsingSVGGlyphs[i];
+ font->NotifyGlyphsChanged();
+ }
+}
+
+bool gfxFontEntry::TryGetColorGlyphs() {
+ if (mCheckedForColorGlyph) {
+ return mCOLR && mCPAL;
+ }
+
+ auto* colr = GetFontTable(TRUETYPE_TAG('C', 'O', 'L', 'R'));
+ auto* cpal = colr ? GetFontTable(TRUETYPE_TAG('C', 'P', 'A', 'L')) : nullptr;
+
+ if (colr && cpal && gfx::COLRFonts::ValidateColorGlyphs(colr, cpal)) {
+ if (!mCOLR.compareExchange(nullptr, colr)) {
+ hb_blob_destroy(colr);
+ }
+ if (!mCPAL.compareExchange(nullptr, cpal)) {
+ hb_blob_destroy(cpal);
+ }
+ } else {
+ hb_blob_destroy(colr);
+ hb_blob_destroy(cpal);
+ }
+
+ mCheckedForColorGlyph = true;
+ return mCOLR && mCPAL;
+}
+
+/**
+ * FontTableBlobData
+ *
+ * See FontTableHashEntry for the general strategy.
+ */
+
+class gfxFontEntry::FontTableBlobData {
+ public:
+ explicit FontTableBlobData(nsTArray<uint8_t>&& aBuffer)
+ : mTableData(std::move(aBuffer)), mHashtable(nullptr), mHashKey(0) {
+ MOZ_COUNT_CTOR(FontTableBlobData);
+ }
+
+ ~FontTableBlobData() {
+ MOZ_COUNT_DTOR(FontTableBlobData);
+ if (mHashtable && mHashKey) {
+ mHashtable->RemoveEntry(mHashKey);
+ }
+ }
+
+ // Useful for creating blobs
+ const char* GetTable() const {
+ return reinterpret_cast<const char*>(mTableData.Elements());
+ }
+ uint32_t GetTableLength() const { return mTableData.Length(); }
+
+ // Tell this FontTableBlobData to remove the HashEntry when this is
+ // destroyed.
+ void ManageHashEntry(nsTHashtable<FontTableHashEntry>* aHashtable,
+ uint32_t aHashKey) {
+ mHashtable = aHashtable;
+ mHashKey = aHashKey;
+ }
+
+ // Disconnect from the HashEntry (because the blob has already been
+ // removed from the hashtable).
+ void ForgetHashEntry() {
+ mHashtable = nullptr;
+ mHashKey = 0;
+ }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ return mTableData.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ // The font table data block
+ nsTArray<uint8_t> mTableData;
+
+ // The blob destroy function needs to know the owning hashtable
+ // and the hashtable key, so that it can remove the entry.
+ nsTHashtable<FontTableHashEntry>* mHashtable;
+ uint32_t mHashKey;
+
+ // not implemented
+ FontTableBlobData(const FontTableBlobData&);
+};
+
+hb_blob_t* gfxFontEntry::FontTableHashEntry::ShareTableAndGetBlob(
+ nsTArray<uint8_t>&& aTable, nsTHashtable<FontTableHashEntry>* aHashtable) {
+ Clear();
+ // adopts elements of aTable
+ mSharedBlobData = new FontTableBlobData(std::move(aTable));
+
+ mBlob = hb_blob_create(
+ mSharedBlobData->GetTable(), mSharedBlobData->GetTableLength(),
+ HB_MEMORY_MODE_READONLY, mSharedBlobData, DeleteFontTableBlobData);
+ if (mBlob == hb_blob_get_empty()) {
+ // The FontTableBlobData was destroyed during hb_blob_create().
+ // The (empty) blob is still be held in the hashtable with a strong
+ // reference.
+ return hb_blob_reference(mBlob);
+ }
+
+ // Tell the FontTableBlobData to remove this hash entry when destroyed.
+ // The hashtable does not keep a strong reference.
+ mSharedBlobData->ManageHashEntry(aHashtable, GetKey());
+ return mBlob;
+}
+
+void gfxFontEntry::FontTableHashEntry::Clear() {
+ // If the FontTableBlobData is managing the hash entry, then the blob is
+ // not owned by this HashEntry; otherwise there is strong reference to the
+ // blob that must be removed.
+ if (mSharedBlobData) {
+ mSharedBlobData->ForgetHashEntry();
+ mSharedBlobData = nullptr;
+ } else {
+ hb_blob_destroy(mBlob);
+ }
+ mBlob = nullptr;
+}
+
+// a hb_destroy_func for hb_blob_create
+
+/* static */
+void gfxFontEntry::FontTableHashEntry::DeleteFontTableBlobData(
+ void* aBlobData) {
+ delete static_cast<FontTableBlobData*>(aBlobData);
+}
+
+hb_blob_t* gfxFontEntry::FontTableHashEntry::GetBlob() const {
+ return hb_blob_reference(mBlob);
+}
+
+bool gfxFontEntry::GetExistingFontTable(uint32_t aTag, hb_blob_t** aBlob) {
+ // Accessing the mFontTableCache pointer is atomic, so we don't need to take
+ // a write lock even if we're initializing it here...
+ MOZ_PUSH_IGNORE_THREAD_SAFETY
+ if (MOZ_UNLIKELY(!mFontTableCache)) {
+ // We do this here rather than on fontEntry construction
+ // because not all shapers will access the table cache at all.
+ //
+ // We're not holding a write lock, so make sure to atomically update
+ // the cache pointer.
+ auto* newCache = new FontTableCache(8);
+ if (MOZ_UNLIKELY(!mFontTableCache.compareExchange(nullptr, newCache))) {
+ delete newCache;
+ }
+ }
+ FontTableCache* cache = GetFontTableCache();
+ MOZ_POP_THREAD_SAFETY
+
+ // ...but we do need a lock to read the actual hashtable contents.
+ AutoReadLock lock(mLock);
+ FontTableHashEntry* entry = cache->GetEntry(aTag);
+ if (!entry) {
+ return false;
+ }
+
+ *aBlob = entry->GetBlob();
+ return true;
+}
+
+hb_blob_t* gfxFontEntry::ShareFontTableAndGetBlob(uint32_t aTag,
+ nsTArray<uint8_t>* aBuffer) {
+ MOZ_PUSH_IGNORE_THREAD_SAFETY
+ if (MOZ_UNLIKELY(!mFontTableCache)) {
+ auto* newCache = new FontTableCache(8);
+ if (MOZ_UNLIKELY(!mFontTableCache.compareExchange(nullptr, newCache))) {
+ delete newCache;
+ }
+ }
+ FontTableCache* cache = GetFontTableCache();
+ MOZ_POP_THREAD_SAFETY
+
+ AutoWriteLock lock(mLock);
+ FontTableHashEntry* entry = cache->PutEntry(aTag);
+ if (MOZ_UNLIKELY(!entry)) { // OOM
+ return nullptr;
+ }
+
+ if (!aBuffer) {
+ // ensure the entry is null
+ entry->Clear();
+ return nullptr;
+ }
+
+ return entry->ShareTableAndGetBlob(std::move(*aBuffer), cache);
+}
+
+already_AddRefed<gfxCharacterMap> gfxFontEntry::GetCMAPFromFontInfo(
+ FontInfoData* aFontInfoData, uint32_t& aUVSOffset) {
+ if (!aFontInfoData || !aFontInfoData->mLoadCmaps) {
+ return nullptr;
+ }
+
+ return aFontInfoData->GetCMAP(mName, aUVSOffset);
+}
+
+hb_blob_t* gfxFontEntry::GetFontTable(uint32_t aTag) {
+ hb_blob_t* blob;
+ if (GetExistingFontTable(aTag, &blob)) {
+ return blob;
+ }
+
+ nsTArray<uint8_t> buffer;
+ bool haveTable = NS_SUCCEEDED(CopyFontTable(aTag, buffer));
+
+ return ShareFontTableAndGetBlob(aTag, haveTable ? &buffer : nullptr);
+}
+
+// callback for HarfBuzz to get a font table (in hb_blob_t form)
+// from the font entry (passed as aUserData)
+/*static*/
+hb_blob_t* gfxFontEntry::HBGetTable(hb_face_t* face, uint32_t aTag,
+ void* aUserData) {
+ gfxFontEntry* fontEntry = static_cast<gfxFontEntry*>(aUserData);
+
+ // bug 589682 - ignore the GDEF table in buggy fonts (applies to
+ // Italic and BoldItalic faces of Times New Roman)
+ if (aTag == TRUETYPE_TAG('G', 'D', 'E', 'F') && fontEntry->IgnoreGDEF()) {
+ return nullptr;
+ }
+
+ // bug 721719 - ignore the GSUB table in buggy fonts (applies to Roboto,
+ // at least on some Android ICS devices; set in gfxFT2FontList.cpp)
+ if (aTag == TRUETYPE_TAG('G', 'S', 'U', 'B') && fontEntry->IgnoreGSUB()) {
+ return nullptr;
+ }
+
+ return fontEntry->GetFontTable(aTag);
+}
+
+static thread_local gfxFontEntry* tl_grGetFontTableCallbackData = nullptr;
+
+class gfxFontEntryCallbacks {
+ public:
+ static tainted_gr<const void*> GrGetTable(
+ rlbox_sandbox_gr& sandbox, tainted_gr<const void*> /* aAppFaceHandle */,
+ tainted_gr<unsigned int> aName, tainted_gr<unsigned int*> aLen) {
+ gfxFontEntry* fontEntry = tl_grGetFontTableCallbackData;
+ *aLen = 0;
+ tainted_gr<const void*> ret = nullptr;
+
+ if (fontEntry) {
+ unsigned int fontTableKey = aName.unverified_safe_because(
+ "This is only being used to index into a hashmap, which is robust "
+ "for any value. No checks needed.");
+ gfxFontUtils::AutoHBBlob blob(fontEntry->GetFontTable(fontTableKey));
+
+ if (blob) {
+ unsigned int blobLength;
+ const void* tableData = hb_blob_get_data(blob, &blobLength);
+ // tableData is read-only data shared with the sandbox.
+ // Making a copy in sandbox memory
+ tainted_gr<void*> t_tableData = rlbox::sandbox_reinterpret_cast<void*>(
+ sandbox.malloc_in_sandbox<char>(blobLength));
+ if (t_tableData) {
+ rlbox::memcpy(sandbox, t_tableData, tableData, blobLength);
+ *aLen = blobLength;
+ ret = rlbox::sandbox_const_cast<const void*>(t_tableData);
+ }
+ }
+ }
+
+ return ret;
+ }
+
+ static void GrReleaseTable(rlbox_sandbox_gr& sandbox,
+ tainted_gr<const void*> /* aAppFaceHandle */,
+ tainted_gr<const void*> aTableBuffer) {
+ sandbox.free_in_sandbox(aTableBuffer);
+ }
+
+ static tainted_gr<float> GrGetAdvance(rlbox_sandbox_gr& sandbox,
+ tainted_gr<const void*> appFontHandle,
+ tainted_gr<uint16_t> glyphid) {
+ tainted_opaque_gr<float> ret = gfxGraphiteShaper::GrGetAdvance(
+ sandbox, appFontHandle.to_opaque(), glyphid.to_opaque());
+ return rlbox::from_opaque(ret);
+ }
+};
+
+struct gfxFontEntry::GrSandboxData {
+ rlbox_sandbox_gr sandbox;
+ sandbox_callback_gr<const void* (*)(const void*, unsigned int, unsigned int*)>
+ grGetTableCallback;
+ sandbox_callback_gr<void (*)(const void*, const void*)>
+ grReleaseTableCallback;
+ // Text Shapers register a callback to get glyph advances
+ sandbox_callback_gr<float (*)(const void*, uint16_t)>
+ grGetGlyphAdvanceCallback;
+
+ GrSandboxData() {
+ sandbox.create_sandbox();
+ grGetTableCallback =
+ sandbox.register_callback(gfxFontEntryCallbacks::GrGetTable);
+ grReleaseTableCallback =
+ sandbox.register_callback(gfxFontEntryCallbacks::GrReleaseTable);
+ grGetGlyphAdvanceCallback =
+ sandbox.register_callback(gfxFontEntryCallbacks::GrGetAdvance);
+ }
+
+ ~GrSandboxData() {
+ grGetTableCallback.unregister();
+ grReleaseTableCallback.unregister();
+ grGetGlyphAdvanceCallback.unregister();
+ sandbox.destroy_sandbox();
+ }
+};
+
+rlbox_sandbox_gr* gfxFontEntry::GetGrSandbox() {
+ AutoReadLock lock(mLock);
+ MOZ_ASSERT(mSandboxData != nullptr);
+ return &mSandboxData->sandbox;
+}
+
+sandbox_callback_gr<float (*)(const void*, uint16_t)>*
+gfxFontEntry::GetGrSandboxAdvanceCallbackHandle() {
+ AutoReadLock lock(mLock);
+ MOZ_ASSERT(mSandboxData != nullptr);
+ return &mSandboxData->grGetGlyphAdvanceCallback;
+}
+
+tainted_opaque_gr<gr_face*> gfxFontEntry::GetGrFace() {
+ if (!mGrFaceInitialized) {
+ // When possible, the below code will use WASM as a sandboxing mechanism.
+ // At this time the wasm sandbox does not support threads.
+ // If Thebes is updated to make callst to the sandbox on multiple threaads,
+ // we need to make sure the underlying sandbox supports threading.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mSandboxData = new GrSandboxData();
+
+ auto p_faceOps = mSandboxData->sandbox.malloc_in_sandbox<gr_face_ops>();
+ if (!p_faceOps) {
+ MOZ_CRASH("Graphite sandbox memory allocation failed");
+ }
+ p_faceOps->size = sizeof(*p_faceOps);
+ p_faceOps->get_table = mSandboxData->grGetTableCallback;
+ p_faceOps->release_table = mSandboxData->grReleaseTableCallback;
+
+ tl_grGetFontTableCallbackData = this;
+ auto face = sandbox_invoke(
+ mSandboxData->sandbox, gr_make_face_with_ops,
+ // For security, we do not pass the callback data to this arg, and use
+ // a TLS var instead. However, gr_make_face_with_ops expects this to
+ // be a non null ptr. Therefore, we should pass some dummy non null
+ // pointer which will be passed to callbacks, but never used. Let's just
+ // pass p_faceOps again, as this is a non-null tainted pointer.
+ p_faceOps /* appFaceHandle */, p_faceOps, gr_face_default);
+ tl_grGetFontTableCallbackData = nullptr;
+ mGrFace = face.to_opaque();
+ mGrFaceInitialized = true;
+ mSandboxData->sandbox.free_in_sandbox(p_faceOps);
+ }
+ ++mGrFaceRefCnt;
+ return mGrFace;
+}
+
+void gfxFontEntry::ReleaseGrFace(tainted_opaque_gr<gr_face*> aFace) {
+ MOZ_ASSERT(
+ (rlbox::from_opaque(aFace) == rlbox::from_opaque(mGrFace))
+ .unverified_safe_because(
+ "This is safe as the only thing we are doing is comparing "
+ "addresses of two tainted pointers. Furthermore this is used "
+ "merely as a debugging aid in the debug builds. This function is "
+ "called only from the trusted Firefox code rather than the "
+ "untrusted libGraphite.")); // sanity-check
+ MOZ_ASSERT(mGrFaceRefCnt > 0);
+ if (--mGrFaceRefCnt == 0) {
+ auto t_mGrFace = rlbox::from_opaque(mGrFace);
+
+ tl_grGetFontTableCallbackData = this;
+ sandbox_invoke(mSandboxData->sandbox, gr_face_destroy, t_mGrFace);
+ tl_grGetFontTableCallbackData = nullptr;
+
+ t_mGrFace = nullptr;
+ mGrFace = t_mGrFace.to_opaque();
+
+ delete mSandboxData;
+ mSandboxData = nullptr;
+
+ mGrFaceInitialized = false;
+ }
+}
+
+void gfxFontEntry::DisconnectSVG() {
+ if (mSVGInitialized && mSVGGlyphs) {
+ mSVGGlyphs = nullptr;
+ mSVGInitialized = false;
+ }
+}
+
+bool gfxFontEntry::HasFontTable(uint32_t aTableTag) {
+ AutoTable table(this, aTableTag);
+ return table && hb_blob_get_length(table) > 0;
+}
+
+tainted_boolean_hint gfxFontEntry::HasGraphiteSpaceContextuals() {
+ LazyFlag flag = mHasGraphiteSpaceContextuals;
+ if (flag == LazyFlag::Uninitialized) {
+ auto face = GetGrFace();
+ auto t_face = rlbox::from_opaque(face);
+ if (t_face) {
+ tainted_gr<const gr_faceinfo*> faceInfo =
+ sandbox_invoke(mSandboxData->sandbox, gr_face_info, t_face, 0);
+ // Comparison with a value in sandboxed memory returns a
+ // tainted_boolean_hint, i.e. a "hint", since the value could be changed
+ // maliciously at any moment.
+ tainted_boolean_hint is_not_none =
+ faceInfo->space_contextuals != gr_faceinfo::gr_space_none;
+ flag = is_not_none.unverified_safe_because(
+ "Note ideally mHasGraphiteSpaceContextuals would be "
+ "tainted_boolean_hint, but RLBox does not yet support "
+ "bitfields, so it is not wrapped. However, its value is only "
+ "ever accessed through this function which returns a "
+ "tainted_boolean_hint, so unwrapping temporarily is safe. "
+ "We remove the wrapper now and re-add it below.")
+ ? LazyFlag::Yes
+ : LazyFlag::No;
+ }
+ ReleaseGrFace(face); // always balance GetGrFace, even if face is null
+ mHasGraphiteSpaceContextuals = flag;
+ }
+
+ return tainted_boolean_hint(flag == LazyFlag::Yes);
+}
+
+#define FEATURE_SCRIPT_MASK 0x000000ff // script index replaces low byte of tag
+
+static_assert(int(intl::Script::NUM_SCRIPT_CODES) <= FEATURE_SCRIPT_MASK,
+ "Too many script codes");
+
+// high-order three bytes of tag with script in low-order byte
+#define SCRIPT_FEATURE(s, tag) \
+ (((~FEATURE_SCRIPT_MASK) & (tag)) | \
+ ((FEATURE_SCRIPT_MASK) & static_cast<uint32_t>(s)))
+
+bool gfxFontEntry::SupportsOpenTypeFeature(Script aScript,
+ uint32_t aFeatureTag) {
+ MutexAutoLock lock(mFeatureInfoLock);
+ if (!mSupportedFeatures) {
+ mSupportedFeatures = MakeUnique<nsTHashMap<nsUint32HashKey, bool>>();
+ }
+
+ // note: high-order three bytes *must* be unique for each feature
+ // listed below (see SCRIPT_FEATURE macro def'n)
+ NS_ASSERTION(aFeatureTag == HB_TAG('s', 'm', 'c', 'p') ||
+ aFeatureTag == HB_TAG('c', '2', 's', 'c') ||
+ aFeatureTag == HB_TAG('p', 'c', 'a', 'p') ||
+ aFeatureTag == HB_TAG('c', '2', 'p', 'c') ||
+ aFeatureTag == HB_TAG('s', 'u', 'p', 's') ||
+ aFeatureTag == HB_TAG('s', 'u', 'b', 's') ||
+ aFeatureTag == HB_TAG('v', 'e', 'r', 't'),
+ "use of unknown feature tag");
+
+ // note: graphite feature support uses the last script index
+ NS_ASSERTION(int(aScript) < FEATURE_SCRIPT_MASK - 1,
+ "need to bump the size of the feature shift");
+
+ uint32_t scriptFeature = SCRIPT_FEATURE(aScript, aFeatureTag);
+ return mSupportedFeatures->LookupOrInsertWith(scriptFeature, [&] {
+ bool result = false;
+ auto face(GetHBFace());
+
+ if (hb_ot_layout_has_substitution(face)) {
+ hb_script_t hbScript =
+ gfxHarfBuzzShaper::GetHBScriptUsedForShaping(aScript);
+
+ // Get the OpenType tag(s) that match this script code
+ unsigned int scriptCount = 4;
+ hb_tag_t scriptTags[4];
+ hb_ot_tags_from_script_and_language(hbScript, HB_LANGUAGE_INVALID,
+ &scriptCount, scriptTags, nullptr,
+ nullptr);
+
+ // Append DEFAULT to the returned tags, if room
+ if (scriptCount < 4) {
+ scriptTags[scriptCount++] = HB_OT_TAG_DEFAULT_SCRIPT;
+ }
+
+ // Now check for 'smcp' under the first of those scripts that is present
+ const hb_tag_t kGSUB = HB_TAG('G', 'S', 'U', 'B');
+ result = std::any_of(scriptTags, scriptTags + scriptCount,
+ [&](const hb_tag_t& scriptTag) {
+ unsigned int scriptIndex;
+ return hb_ot_layout_table_find_script(
+ face, kGSUB, scriptTag, &scriptIndex) &&
+ hb_ot_layout_language_find_feature(
+ face, kGSUB, scriptIndex,
+ HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX,
+ aFeatureTag, nullptr);
+ });
+ }
+
+ return result;
+ });
+}
+
+const hb_set_t* gfxFontEntry::InputsForOpenTypeFeature(Script aScript,
+ uint32_t aFeatureTag) {
+ MutexAutoLock lock(mFeatureInfoLock);
+ if (!mFeatureInputs) {
+ mFeatureInputs = MakeUnique<nsTHashMap<nsUint32HashKey, hb_set_t*>>();
+ }
+
+ NS_ASSERTION(aFeatureTag == HB_TAG('s', 'u', 'p', 's') ||
+ aFeatureTag == HB_TAG('s', 'u', 'b', 's') ||
+ aFeatureTag == HB_TAG('v', 'e', 'r', 't'),
+ "use of unknown feature tag");
+
+ uint32_t scriptFeature = SCRIPT_FEATURE(aScript, aFeatureTag);
+ hb_set_t* inputGlyphs;
+ if (mFeatureInputs->Get(scriptFeature, &inputGlyphs)) {
+ return inputGlyphs;
+ }
+
+ inputGlyphs = hb_set_create();
+
+ auto face(GetHBFace());
+
+ if (hb_ot_layout_has_substitution(face)) {
+ hb_script_t hbScript =
+ gfxHarfBuzzShaper::GetHBScriptUsedForShaping(aScript);
+
+ // Get the OpenType tag(s) that match this script code
+ unsigned int scriptCount = 4;
+ hb_tag_t scriptTags[5]; // space for null terminator
+ hb_ot_tags_from_script_and_language(hbScript, HB_LANGUAGE_INVALID,
+ &scriptCount, scriptTags, nullptr,
+ nullptr);
+
+ // Append DEFAULT to the returned tags, if room
+ if (scriptCount < 4) {
+ scriptTags[scriptCount++] = HB_OT_TAG_DEFAULT_SCRIPT;
+ }
+ scriptTags[scriptCount++] = 0;
+
+ const hb_tag_t kGSUB = HB_TAG('G', 'S', 'U', 'B');
+ hb_tag_t features[2] = {aFeatureTag, HB_TAG_NONE};
+ hb_set_t* featurelookups = hb_set_create();
+ hb_ot_layout_collect_lookups(face, kGSUB, scriptTags, nullptr, features,
+ featurelookups);
+ hb_codepoint_t index = -1;
+ while (hb_set_next(featurelookups, &index)) {
+ hb_ot_layout_lookup_collect_glyphs(face, kGSUB, index, nullptr,
+ inputGlyphs, nullptr, nullptr);
+ }
+ hb_set_destroy(featurelookups);
+ }
+
+ mFeatureInputs->InsertOrUpdate(scriptFeature, inputGlyphs);
+ return inputGlyphs;
+}
+
+bool gfxFontEntry::SupportsGraphiteFeature(uint32_t aFeatureTag) {
+ MutexAutoLock lock(mFeatureInfoLock);
+
+ if (!mSupportedFeatures) {
+ mSupportedFeatures = MakeUnique<nsTHashMap<nsUint32HashKey, bool>>();
+ }
+
+ // note: high-order three bytes *must* be unique for each feature
+ // listed below (see SCRIPT_FEATURE macro def'n)
+ NS_ASSERTION(aFeatureTag == HB_TAG('s', 'm', 'c', 'p') ||
+ aFeatureTag == HB_TAG('c', '2', 's', 'c') ||
+ aFeatureTag == HB_TAG('p', 'c', 'a', 'p') ||
+ aFeatureTag == HB_TAG('c', '2', 'p', 'c') ||
+ aFeatureTag == HB_TAG('s', 'u', 'p', 's') ||
+ aFeatureTag == HB_TAG('s', 'u', 'b', 's'),
+ "use of unknown feature tag");
+
+ // graphite feature check uses the last script slot
+ uint32_t scriptFeature = SCRIPT_FEATURE(FEATURE_SCRIPT_MASK, aFeatureTag);
+ bool result;
+ if (mSupportedFeatures->Get(scriptFeature, &result)) {
+ return result;
+ }
+
+ auto face = GetGrFace();
+ auto t_face = rlbox::from_opaque(face);
+ result = t_face ? sandbox_invoke(mSandboxData->sandbox, gr_face_find_fref,
+ t_face, aFeatureTag) != nullptr
+ : false;
+ ReleaseGrFace(face);
+
+ mSupportedFeatures->InsertOrUpdate(scriptFeature, result);
+
+ return result;
+}
+
+void gfxFontEntry::GetFeatureInfo(nsTArray<gfxFontFeatureInfo>& aFeatureInfo) {
+ // TODO: implement alternative code path for graphite fonts
+
+ auto autoFace(GetHBFace());
+ // Expose the raw hb_face_t to be captured by the lambdas (not the
+ // AutoHBFace wrapper).
+ hb_face_t* face = autoFace;
+
+ // Get the list of features for a specific <script,langSys> pair and
+ // append them to aFeatureInfo.
+ auto collectForLang = [=, &aFeatureInfo](
+ hb_tag_t aTableTag, unsigned int aScript,
+ hb_tag_t aScriptTag, unsigned int aLang,
+ hb_tag_t aLangTag) {
+ unsigned int featCount = hb_ot_layout_language_get_feature_tags(
+ face, aTableTag, aScript, aLang, 0, nullptr, nullptr);
+ AutoTArray<hb_tag_t, 32> featTags;
+ featTags.SetLength(featCount);
+ hb_ot_layout_language_get_feature_tags(face, aTableTag, aScript, aLang, 0,
+ &featCount, featTags.Elements());
+ MOZ_ASSERT(featCount <= featTags.Length());
+ // Just in case HB didn't fill featTags (i.e. in case it returned fewer
+ // tags than it promised), we truncate at the length it says it filled:
+ featTags.SetLength(featCount);
+ for (hb_tag_t t : featTags) {
+ aFeatureInfo.AppendElement(gfxFontFeatureInfo{t, aScriptTag, aLangTag});
+ }
+ };
+
+ // Iterate over the language systems supported by a given script,
+ // and call collectForLang for each of them.
+ auto collectForScript = [=](hb_tag_t aTableTag, unsigned int aScript,
+ hb_tag_t aScriptTag) {
+ collectForLang(aTableTag, aScript, aScriptTag,
+ HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX,
+ HB_TAG('d', 'f', 'l', 't'));
+ unsigned int langCount = hb_ot_layout_script_get_language_tags(
+ face, aTableTag, aScript, 0, nullptr, nullptr);
+ AutoTArray<hb_tag_t, 32> langTags;
+ langTags.SetLength(langCount);
+ hb_ot_layout_script_get_language_tags(face, aTableTag, aScript, 0,
+ &langCount, langTags.Elements());
+ MOZ_ASSERT(langCount <= langTags.Length());
+ langTags.SetLength(langCount);
+ for (unsigned int lang = 0; lang < langCount; ++lang) {
+ collectForLang(aTableTag, aScript, aScriptTag, lang, langTags[lang]);
+ }
+ };
+
+ // Iterate over the scripts supported by a table (GSUB or GPOS), and call
+ // collectForScript for each of them.
+ auto collectForTable = [=](hb_tag_t aTableTag) {
+ unsigned int scriptCount = hb_ot_layout_table_get_script_tags(
+ face, aTableTag, 0, nullptr, nullptr);
+ AutoTArray<hb_tag_t, 32> scriptTags;
+ scriptTags.SetLength(scriptCount);
+ hb_ot_layout_table_get_script_tags(face, aTableTag, 0, &scriptCount,
+ scriptTags.Elements());
+ MOZ_ASSERT(scriptCount <= scriptTags.Length());
+ scriptTags.SetLength(scriptCount);
+ for (unsigned int script = 0; script < scriptCount; ++script) {
+ collectForScript(aTableTag, script, scriptTags[script]);
+ }
+ };
+
+ // Collect all OpenType Layout features, both substitution and positioning,
+ // supported by the font resource.
+ collectForTable(HB_TAG('G', 'S', 'U', 'B'));
+ collectForTable(HB_TAG('G', 'P', 'O', 'S'));
+}
+
+typedef struct {
+ AutoSwap_PRUint32 version;
+ AutoSwap_PRUint16 format;
+ AutoSwap_PRUint16 horizOffset;
+ AutoSwap_PRUint16 vertOffset;
+ AutoSwap_PRUint16 reserved;
+ // TrackData horizData;
+ // TrackData vertData;
+} TrakHeader;
+
+typedef struct {
+ AutoSwap_PRUint16 nTracks;
+ AutoSwap_PRUint16 nSizes;
+ AutoSwap_PRUint32 sizeTableOffset;
+ // trackTableEntry trackTable[];
+ // fixed32 sizeTable[];
+} TrackData;
+
+typedef struct {
+ AutoSwap_PRUint32 track;
+ AutoSwap_PRUint16 nameIndex;
+ AutoSwap_PRUint16 offset;
+} TrackTableEntry;
+
+bool gfxFontEntry::HasTrackingTable() {
+ if (!TrakTableInitialized()) {
+ hb_blob_t* trak = GetFontTable(TRUETYPE_TAG('t', 'r', 'a', 'k'));
+ if (trak) {
+ // mTrakTable itself is atomic, but we also want to set the auxiliary
+ // pointers mTrakValues and mTrakSizeTable, so we take a lock here to
+ // avoid racing with another thread also initializing the same values.
+ AutoWriteLock lock(mLock);
+ if (!mTrakTable.compareExchange(kTrakTableUninitialized, trak)) {
+ hb_blob_destroy(trak);
+ } else if (!ParseTrakTable()) {
+ hb_blob_destroy(mTrakTable.exchange(nullptr));
+ }
+ } else {
+ mTrakTable.exchange(nullptr);
+ }
+ }
+ return GetTrakTable() != nullptr;
+}
+
+bool gfxFontEntry::ParseTrakTable() {
+ // Check table validity and set up the subtable pointers we need;
+ // if 'trak' table is invalid, or doesn't contain a 'normal' track,
+ // return false to tell the caller not to try using it.
+ unsigned int len;
+ const char* data = hb_blob_get_data(GetTrakTable(), &len);
+ if (len < sizeof(TrakHeader)) {
+ return false;
+ }
+ auto trak = reinterpret_cast<const TrakHeader*>(data);
+ uint16_t horizOffset = trak->horizOffset;
+ if (trak->version != 0x00010000 || uint16_t(trak->format) != 0 ||
+ horizOffset == 0 || uint16_t(trak->reserved) != 0) {
+ return false;
+ }
+ // Find the horizontal trackData, and check it doesn't overrun the buffer.
+ if (horizOffset > len - sizeof(TrackData)) {
+ return false;
+ }
+ auto trackData = reinterpret_cast<const TrackData*>(data + horizOffset);
+ uint16_t nTracks = trackData->nTracks;
+ mNumTrakSizes = trackData->nSizes;
+ if (nTracks == 0 || mNumTrakSizes < 2) {
+ return false;
+ }
+ uint32_t sizeTableOffset = trackData->sizeTableOffset;
+ // Find the trackTable, and check it doesn't overrun the buffer.
+ if (horizOffset >
+ len - (sizeof(TrackData) + nTracks * sizeof(TrackTableEntry))) {
+ return false;
+ }
+ auto trackTable = reinterpret_cast<const TrackTableEntry*>(
+ data + horizOffset + sizeof(TrackData));
+ // Look for 'normal' tracking, bail out if no such track is present.
+ unsigned trackIndex;
+ for (trackIndex = 0; trackIndex < nTracks; ++trackIndex) {
+ if (trackTable[trackIndex].track == 0x00000000) {
+ break;
+ }
+ }
+ if (trackIndex == nTracks) {
+ return false;
+ }
+ // Find list of tracking values, and check they won't overrun.
+ uint16_t offset = trackTable[trackIndex].offset;
+ if (offset > len - mNumTrakSizes * sizeof(uint16_t)) {
+ return false;
+ }
+ mTrakValues = reinterpret_cast<const AutoSwap_PRInt16*>(data + offset);
+ // Find the size subtable, and check it doesn't overrun the buffer.
+ mTrakSizeTable =
+ reinterpret_cast<const AutoSwap_PRInt32*>(data + sizeTableOffset);
+ if (mTrakSizeTable + mNumTrakSizes >
+ reinterpret_cast<const AutoSwap_PRInt32*>(data + len)) {
+ return false;
+ }
+ return true;
+}
+
+float gfxFontEntry::TrackingForCSSPx(float aSize) const {
+ // No locking because this does read-only access of fields that are inert
+ // once initialized.
+ MOZ_ASSERT(TrakTableInitialized() && mTrakTable && mTrakValues &&
+ mTrakSizeTable);
+
+ // Find index of first sizeTable entry that is >= the requested size.
+ int32_t fixedSize = int32_t(aSize * 65536.0); // float -> 16.16 fixed-point
+ unsigned sizeIndex;
+ for (sizeIndex = 0; sizeIndex < mNumTrakSizes; ++sizeIndex) {
+ if (mTrakSizeTable[sizeIndex] >= fixedSize) {
+ break;
+ }
+ }
+ // Return the tracking value for the requested size, or an interpolated
+ // value if the exact size isn't found.
+ if (sizeIndex == mNumTrakSizes) {
+ // Request is larger than last entry in the table, so just use that.
+ // (We don't attempt to extrapolate more extreme tracking values than
+ // the largest or smallest present in the table.)
+ return int16_t(mTrakValues[mNumTrakSizes - 1]);
+ }
+ if (sizeIndex == 0 || mTrakSizeTable[sizeIndex] == fixedSize) {
+ // Found an exact match, or size was smaller than the first entry.
+ return int16_t(mTrakValues[sizeIndex]);
+ }
+ // Requested size falls between two entries: interpolate value.
+ double s0 = mTrakSizeTable[sizeIndex - 1] / 65536.0; // 16.16 -> float
+ double s1 = mTrakSizeTable[sizeIndex] / 65536.0;
+ double t = (aSize - s0) / (s1 - s0);
+ return (1.0 - t) * int16_t(mTrakValues[sizeIndex - 1]) +
+ t * int16_t(mTrakValues[sizeIndex]);
+}
+
+void gfxFontEntry::SetupVariationRanges() {
+ // No locking because this is done during initialization before any other
+ // thread has access to the entry.
+ if (!gfxPlatform::HasVariationFontSupport() ||
+ !StaticPrefs::layout_css_font_variations_enabled() || !HasVariations() ||
+ IsUserFont()) {
+ return;
+ }
+ AutoTArray<gfxFontVariationAxis, 4> axes;
+ GetVariationAxes(axes);
+ for (const auto& axis : axes) {
+ switch (axis.mTag) {
+ case HB_TAG('w', 'g', 'h', 't'):
+ // If the axis range looks like it doesn't fit the CSS font-weight
+ // scale, we don't hook up the high-level property, and we mark
+ // the face (in mRangeFlags) as having non-standard weight. This
+ // means we won't map CSS font-weight to the axis. Setting 'wght'
+ // with font-variation-settings will still work.
+ // Strictly speaking, the min value should be checked against 1.0,
+ // not 0.0, but we'll allow font makers that amount of leeway, as
+ // in practice a number of fonts seem to use 0..1000.
+ if (axis.mMinValue >= 0.0f && axis.mMaxValue <= 1000.0f &&
+ // If axis.mMaxValue is less than the default weight we already
+ // set up, assume the axis has a non-standard range (like Skia)
+ // and don't try to map it.
+ Weight().Min() <= FontWeight::FromFloat(axis.mMaxValue)) {
+ if (FontWeight::FromFloat(axis.mDefaultValue) != Weight().Min()) {
+ mStandardFace = false;
+ }
+ mWeightRange =
+ WeightRange(FontWeight::FromFloat(std::max(1.0f, axis.mMinValue)),
+ FontWeight::FromFloat(axis.mMaxValue));
+ } else {
+ mRangeFlags |= RangeFlags::eNonCSSWeight;
+ }
+ break;
+
+ case HB_TAG('w', 'd', 't', 'h'):
+ if (axis.mMinValue >= 0.0f && axis.mMaxValue <= 1000.0f &&
+ Stretch().Min() <= FontStretch::FromFloat(axis.mMaxValue)) {
+ if (FontStretch::FromFloat(axis.mDefaultValue) != Stretch().Min()) {
+ mStandardFace = false;
+ }
+ mStretchRange = StretchRange(FontStretch::FromFloat(axis.mMinValue),
+ FontStretch::FromFloat(axis.mMaxValue));
+ } else {
+ mRangeFlags |= RangeFlags::eNonCSSStretch;
+ }
+ break;
+
+ case HB_TAG('s', 'l', 'n', 't'):
+ if (axis.mMinValue >= -90.0f && axis.mMaxValue <= 90.0f) {
+ if (FontSlantStyle::FromFloat(axis.mDefaultValue) !=
+ SlantStyle().Min()) {
+ mStandardFace = false;
+ }
+ // OpenType and CSS measure angles in opposite directions, so we
+ // have to flip signs and swap min/max when setting up the CSS
+ // font-style range here.
+ mStyleRange =
+ SlantStyleRange(FontSlantStyle::FromFloat(-axis.mMaxValue),
+ FontSlantStyle::FromFloat(-axis.mMinValue));
+ }
+ break;
+
+ case HB_TAG('i', 't', 'a', 'l'):
+ if (axis.mMinValue <= 0.0f && axis.mMaxValue >= 1.0f) {
+ if (axis.mDefaultValue != 0.0f) {
+ mStandardFace = false;
+ }
+ mStyleRange =
+ SlantStyleRange(FontSlantStyle::NORMAL, FontSlantStyle::ITALIC);
+ }
+ break;
+
+ default:
+ continue;
+ }
+ }
+}
+
+void gfxFontEntry::CheckForVariationAxes() {
+ if (mCheckedForVariationAxes) {
+ return;
+ }
+ mCheckedForVariationAxes = true;
+ if (HasVariations()) {
+ AutoTArray<gfxFontVariationAxis, 4> axes;
+ GetVariationAxes(axes);
+ for (const auto& axis : axes) {
+ if (axis.mTag == HB_TAG('w', 'g', 'h', 't') && axis.mMaxValue >= 600.0f) {
+ mRangeFlags |= RangeFlags::eBoldVariableWeight;
+ } else if (axis.mTag == HB_TAG('i', 't', 'a', 'l') &&
+ axis.mMaxValue >= 1.0f) {
+ mRangeFlags |= RangeFlags::eItalicVariation;
+ } else if (axis.mTag == HB_TAG('s', 'l', 'n', 't')) {
+ mRangeFlags |= RangeFlags::eSlantVariation;
+ } else if (axis.mTag == HB_TAG('o', 'p', 's', 'z')) {
+ mRangeFlags |= RangeFlags::eOpticalSize;
+ }
+ }
+ }
+}
+
+bool gfxFontEntry::HasBoldVariableWeight() {
+ MOZ_ASSERT(!mIsUserFontContainer,
+ "should not be called for user-font containers!");
+ CheckForVariationAxes();
+ return bool(mRangeFlags & RangeFlags::eBoldVariableWeight);
+}
+
+bool gfxFontEntry::HasItalicVariation() {
+ MOZ_ASSERT(!mIsUserFontContainer,
+ "should not be called for user-font containers!");
+ CheckForVariationAxes();
+ return bool(mRangeFlags & RangeFlags::eItalicVariation);
+}
+
+bool gfxFontEntry::HasSlantVariation() {
+ MOZ_ASSERT(!mIsUserFontContainer,
+ "should not be called for user-font containers!");
+ CheckForVariationAxes();
+ return bool(mRangeFlags & RangeFlags::eSlantVariation);
+}
+
+bool gfxFontEntry::HasOpticalSize() {
+ MOZ_ASSERT(!mIsUserFontContainer,
+ "should not be called for user-font containers!");
+ CheckForVariationAxes();
+ return bool(mRangeFlags & RangeFlags::eOpticalSize);
+}
+
+void gfxFontEntry::GetVariationsForStyle(nsTArray<gfxFontVariation>& aResult,
+ const gfxFontStyle& aStyle) {
+ if (!gfxPlatform::HasVariationFontSupport() ||
+ !StaticPrefs::layout_css_font_variations_enabled()) {
+ return;
+ }
+
+ if (!HasVariations()) {
+ return;
+ }
+
+ // Resolve high-level CSS properties from the requested style
+ // (font-{style,weight,stretch}) to the appropriate variations.
+ // The value used is clamped to the range available in the font face,
+ // unless the face is a user font where no explicit descriptor was
+ // given, indicated by the corresponding 'auto' range-flag.
+
+ // We don't do these mappings if the font entry has weight and/or stretch
+ // ranges that do not appear to use the CSS property scale. Some older
+ // fonts created for QuickDrawGX/AAT may use "normalized" values where the
+ // standard variation is 1.0 rather than 400.0 (weight) or 100.0 (stretch).
+
+ if (!(mRangeFlags & RangeFlags::eNonCSSWeight)) {
+ float weight = (IsUserFont() && (mRangeFlags & RangeFlags::eAutoWeight))
+ ? aStyle.weight.ToFloat()
+ : Weight().Clamp(aStyle.weight).ToFloat();
+ aResult.AppendElement(gfxFontVariation{HB_TAG('w', 'g', 'h', 't'), weight});
+ }
+
+ if (!(mRangeFlags & RangeFlags::eNonCSSStretch)) {
+ float stretch = (IsUserFont() && (mRangeFlags & RangeFlags::eAutoStretch))
+ ? aStyle.stretch.ToFloat()
+ : Stretch().Clamp(aStyle.stretch).ToFloat();
+ aResult.AppendElement(
+ gfxFontVariation{HB_TAG('w', 'd', 't', 'h'), stretch});
+ }
+
+ if (aStyle.style.IsItalic() && SupportsItalic()) {
+ // The 'ital' axis is normally a binary toggle; intermediate values
+ // can only be set using font-variation-settings.
+ aResult.AppendElement(gfxFontVariation{HB_TAG('i', 't', 'a', 'l'), 1.0f});
+ } else if (aStyle.style != StyleFontStyle::NORMAL && HasSlantVariation()) {
+ // Figure out what slant angle we should try to match from the
+ // requested style.
+ float angle = aStyle.style.SlantAngle();
+ // Clamp to the available range, unless the face is a user font
+ // with no explicit descriptor.
+ if (!(IsUserFont() && (mRangeFlags & RangeFlags::eAutoSlantStyle))) {
+ angle = SlantStyle().Clamp(FontSlantStyle::FromFloat(angle)).SlantAngle();
+ }
+ // OpenType and CSS measure angles in opposite directions, so we have to
+ // invert the sign of the CSS oblique value when setting OpenType 'slnt'.
+ aResult.AppendElement(gfxFontVariation{HB_TAG('s', 'l', 'n', 't'), -angle});
+ }
+
+ struct TagEquals {
+ bool Equals(const gfxFontVariation& aIter, uint32_t aTag) const {
+ return aIter.mTag == aTag;
+ }
+ };
+
+ auto replaceOrAppend = [&aResult](const gfxFontVariation& aSetting) {
+ auto index = aResult.IndexOf(aSetting.mTag, 0, TagEquals());
+ if (index == aResult.NoIndex) {
+ aResult.AppendElement(aSetting);
+ } else {
+ aResult[index].mValue = aSetting.mValue;
+ }
+ };
+
+ // The low-level font-variation-settings descriptor from @font-face,
+ // if present, takes precedence over automatic variation settings
+ // from high-level properties.
+ for (const auto& v : mVariationSettings) {
+ replaceOrAppend(v);
+ }
+
+ // And the low-level font-variation-settings property takes precedence
+ // over the descriptor.
+ for (const auto& v : aStyle.variationSettings) {
+ replaceOrAppend(v);
+ }
+
+ // If there's no explicit opsz in the settings, apply 'auto' value.
+ if (HasOpticalSize() && aStyle.autoOpticalSize >= 0.0f) {
+ const uint32_t kOpszTag = HB_TAG('o', 'p', 's', 'z');
+ auto index = aResult.IndexOf(kOpszTag, 0, TagEquals());
+ if (index == aResult.NoIndex) {
+ float value = aStyle.autoOpticalSize * mSizeAdjust;
+ aResult.AppendElement(gfxFontVariation{kOpszTag, value});
+ }
+ }
+}
+
+size_t gfxFontEntry::FontTableHashEntry::SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+ if (mBlob) {
+ n += aMallocSizeOf(mBlob);
+ }
+ if (mSharedBlobData) {
+ n += mSharedBlobData->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return n;
+}
+
+void gfxFontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ aSizes->mFontListSize += mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+
+ // cmaps are shared so only non-shared cmaps are included here
+ if (mCharacterMap && GetCharacterMap()->mBuildOnTheFly) {
+ aSizes->mCharMapsSize +=
+ GetCharacterMap()->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ {
+ AutoReadLock lock(mLock);
+ if (mFontTableCache) {
+ aSizes->mFontTableCacheSize +=
+ GetFontTableCache()->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+
+ // If the font has UVS data, we count that as part of the character map.
+ if (mUVSData) {
+ aSizes->mCharMapsSize += aMallocSizeOf(GetUVSData());
+ }
+
+ // The following, if present, are essentially cached forms of font table
+ // data, so we'll accumulate them together with the basic table cache.
+ if (mUserFontData) {
+ aSizes->mFontTableCacheSize +=
+ mUserFontData->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ if (mSVGGlyphs) {
+ aSizes->mFontTableCacheSize +=
+ GetSVGGlyphs()->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ {
+ MutexAutoLock lock(mFeatureInfoLock);
+ if (mSupportedFeatures) {
+ aSizes->mFontTableCacheSize +=
+ mSupportedFeatures->ShallowSizeOfIncludingThis(aMallocSizeOf);
+ }
+ if (mFeatureInputs) {
+ aSizes->mFontTableCacheSize +=
+ mFeatureInputs->ShallowSizeOfIncludingThis(aMallocSizeOf);
+ // XXX Can't this simply be
+ // aSizes->mFontTableCacheSize += 8192 * mFeatureInputs->Count();
+ for (auto iter = mFeatureInputs->ConstIter(); !iter.Done(); iter.Next()) {
+ // There's no API to get the real size of an hb_set, so we'll use
+ // an approximation based on knowledge of the implementation.
+ aSizes->mFontTableCacheSize += 8192; // vector of 64K bits
+ }
+ }
+ }
+ // We don't include the size of mCOLR/mCPAL here, because (depending on the
+ // font backend implementation) they will either wrap blocks of data owned
+ // by the system (and potentially shared), or tables that are in our font
+ // table cache and therefore already counted.
+}
+
+void gfxFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ aSizes->mFontListSize += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
+
+// This is used to report the size of an individual downloaded font in the
+// user font cache. (Fonts that are part of the platform font list accumulate
+// their sizes to the font list's reporter using the AddSizeOf... methods
+// above.)
+size_t gfxFontEntry::ComputedSizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ FontListSizes s = {0};
+ AddSizeOfExcludingThis(aMallocSizeOf, &s);
+
+ // When reporting memory used for the main platform font list,
+ // where we're typically summing the totals for a few hundred font faces,
+ // we report the fields of FontListSizes separately.
+ // But for downloaded user fonts, the actual resource data (added below)
+ // will dominate, and the minor overhead of these pieces isn't worth
+ // splitting out for an individual font.
+ size_t result = s.mFontListSize + s.mFontTableCacheSize + s.mCharMapsSize;
+
+ if (mIsDataUserFont) {
+ MOZ_ASSERT(mComputedSizeOfUserFont > 0, "user font with no data?");
+ result += mComputedSizeOfUserFont;
+ }
+
+ return result;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// class gfxFontFamily
+//
+//////////////////////////////////////////////////////////////////////////////
+
+// we consider faces with mStandardFace == true to be "less than" those with
+// false, because during style matching, earlier entries are tried first
+class FontEntryStandardFaceComparator {
+ public:
+ bool Equals(const RefPtr<gfxFontEntry>& a,
+ const RefPtr<gfxFontEntry>& b) const {
+ return a->mStandardFace == b->mStandardFace;
+ }
+ bool LessThan(const RefPtr<gfxFontEntry>& a,
+ const RefPtr<gfxFontEntry>& b) const {
+ return (a->mStandardFace == true && b->mStandardFace == false);
+ }
+};
+
+void gfxFontFamily::SortAvailableFonts() {
+ MOZ_ASSERT(mLock.LockedForWritingByCurrentThread());
+ mAvailableFonts.Sort(FontEntryStandardFaceComparator());
+}
+
+bool gfxFontFamily::HasOtherFamilyNames() {
+ // need to read in other family names to determine this
+ if (!mOtherFamilyNamesInitialized) {
+ ReadOtherFamilyNames(
+ gfxPlatformFontList::PlatformFontList()); // sets mHasOtherFamilyNames
+ }
+ return mHasOtherFamilyNames;
+}
+
+gfxFontEntry* gfxFontFamily::FindFontForStyle(const gfxFontStyle& aFontStyle,
+ bool aIgnoreSizeTolerance) {
+ AutoTArray<gfxFontEntry*, 4> matched;
+ FindAllFontsForStyle(aFontStyle, matched, aIgnoreSizeTolerance);
+ if (!matched.IsEmpty()) {
+ return matched[0];
+ }
+ return nullptr;
+}
+
+static inline double WeightStyleStretchDistance(
+ gfxFontEntry* aFontEntry, const gfxFontStyle& aTargetStyle) {
+ double stretchDist =
+ StretchDistance(aFontEntry->Stretch(), aTargetStyle.stretch);
+ double styleDist =
+ StyleDistance(aFontEntry->SlantStyle(), aTargetStyle.style);
+ double weightDist = WeightDistance(aFontEntry->Weight(), aTargetStyle.weight);
+
+ // Sanity-check that the distances are within the expected range
+ // (update if implementation of the distance functions is changed).
+ MOZ_ASSERT(stretchDist >= 0.0 && stretchDist <= 2000.0);
+ MOZ_ASSERT(styleDist >= 0.0 && styleDist <= 500.0);
+ MOZ_ASSERT(weightDist >= 0.0 && weightDist <= 1600.0);
+
+ // weight/style/stretch priority: stretch >> style >> weight
+ // so we multiply the stretch and style values to make them dominate
+ // the result
+ return stretchDist * kStretchFactor + styleDist * kStyleFactor +
+ weightDist * kWeightFactor;
+}
+
+void gfxFontFamily::FindAllFontsForStyle(
+ const gfxFontStyle& aFontStyle, nsTArray<gfxFontEntry*>& aFontEntryList,
+ bool aIgnoreSizeTolerance) {
+ if (!mHasStyles) {
+ FindStyleVariations(); // collect faces for the family, if not already
+ // done
+ }
+
+ AutoReadLock lock(mLock);
+
+ NS_ASSERTION(mAvailableFonts.Length() > 0, "font family with no faces!");
+ NS_ASSERTION(aFontEntryList.IsEmpty(), "non-empty fontlist passed in");
+
+ gfxFontEntry* fe = nullptr;
+
+ // If the family has only one face, we simply return it; no further
+ // checking needed
+ uint32_t count = mAvailableFonts.Length();
+ if (count == 1) {
+ fe = mAvailableFonts[0];
+ aFontEntryList.AppendElement(fe);
+ return;
+ }
+
+ // Most families are "simple", having just Regular/Bold/Italic/BoldItalic,
+ // or some subset of these. In this case, we have exactly 4 entries in
+ // mAvailableFonts, stored in the above order; note that some of the entries
+ // may be nullptr. We can then pick the required entry based on whether the
+ // request is for bold or non-bold, italic or non-italic, without running the
+ // more complex matching algorithm used for larger families with many weights
+ // and/or widths.
+
+ if (mIsSimpleFamily) {
+ // Family has no more than the "standard" 4 faces, at fixed indexes;
+ // calculate which one we want.
+ // Note that we cannot simply return it as not all 4 faces are necessarily
+ // present.
+ bool wantBold = aFontStyle.weight >= FontWeight::FromInt(600);
+ bool wantItalic = !aFontStyle.style.IsNormal();
+ uint8_t faceIndex =
+ (wantItalic ? kItalicMask : 0) | (wantBold ? kBoldMask : 0);
+
+ // if the desired style is available, return it directly
+ fe = mAvailableFonts[faceIndex];
+ if (fe) {
+ aFontEntryList.AppendElement(fe);
+ return;
+ }
+
+ // order to check fallback faces in a simple family, depending on requested
+ // style
+ static const uint8_t simpleFallbacks[4][3] = {
+ {kBoldFaceIndex, kItalicFaceIndex,
+ kBoldItalicFaceIndex}, // fallbacks for Regular
+ {kRegularFaceIndex, kBoldItalicFaceIndex, kItalicFaceIndex}, // Bold
+ {kBoldItalicFaceIndex, kRegularFaceIndex, kBoldFaceIndex}, // Italic
+ {kItalicFaceIndex, kBoldFaceIndex, kRegularFaceIndex} // BoldItalic
+ };
+ const uint8_t* order = simpleFallbacks[faceIndex];
+
+ for (uint8_t trial = 0; trial < 3; ++trial) {
+ // check remaining faces in order of preference to find the first that
+ // actually exists
+ fe = mAvailableFonts[order[trial]];
+ if (fe) {
+ aFontEntryList.AppendElement(fe);
+ return;
+ }
+ }
+
+ // this can't happen unless we have totally broken the font-list manager!
+ MOZ_ASSERT_UNREACHABLE("no face found in simple font family!");
+ }
+
+ // Pick the font(s) that are closest to the desired weight, style, and
+ // stretch. Iterate over all fonts, measuring the weight/style distance.
+ // Because of unicode-range values, there may be more than one font for a
+ // given but the 99% use case is only a single font entry per
+ // weight/style/stretch distance value. To optimize this, only add entries
+ // to the matched font array when another entry already has the same
+ // weight/style/stretch distance and add the last matched font entry. For
+ // normal platform fonts with a single font entry for each
+ // weight/style/stretch combination, only the last matched font entry will
+ // be added.
+
+ double minDistance = INFINITY;
+ gfxFontEntry* matched = nullptr;
+ // iterate in forward order so that faces like 'Bold' are matched before
+ // matching style distance faces such as 'Bold Outline' (see bug 1185812)
+ for (uint32_t i = 0; i < count; i++) {
+ fe = mAvailableFonts[i];
+ // weight/style/stretch priority: stretch >> style >> weight
+ double distance = WeightStyleStretchDistance(fe, aFontStyle);
+ if (distance < minDistance) {
+ matched = fe;
+ if (!aFontEntryList.IsEmpty()) {
+ aFontEntryList.Clear();
+ }
+ minDistance = distance;
+ } else if (distance == minDistance) {
+ if (matched) {
+ aFontEntryList.AppendElement(matched);
+ }
+ matched = fe;
+ }
+ }
+
+ NS_ASSERTION(matched, "didn't match a font within a family");
+
+ if (matched) {
+ aFontEntryList.AppendElement(matched);
+ }
+}
+
+void gfxFontFamily::CheckForSimpleFamily() {
+ MOZ_ASSERT(mLock.LockedForWritingByCurrentThread());
+ // already checked this family
+ if (mIsSimpleFamily) {
+ return;
+ }
+
+ uint32_t count = mAvailableFonts.Length();
+ if (count > 4 || count == 0) {
+ return; // can't be "simple" if there are >4 faces;
+ // if none then the family is unusable anyway
+ }
+
+ if (count == 1) {
+ mIsSimpleFamily = true;
+ return;
+ }
+
+ StretchRange firstStretch = mAvailableFonts[0]->Stretch();
+ if (!firstStretch.IsSingle()) {
+ return; // family with variation fonts is not considered "simple"
+ }
+
+ gfxFontEntry* faces[4] = {0};
+ for (uint8_t i = 0; i < count; ++i) {
+ gfxFontEntry* fe = mAvailableFonts[i];
+ if (fe->Stretch() != firstStretch || fe->IsOblique()) {
+ // simple families don't have varying font-stretch or oblique
+ return;
+ }
+ if (!fe->Weight().IsSingle() || !fe->SlantStyle().IsSingle()) {
+ return; // family with variation fonts is not considered "simple"
+ }
+ uint8_t faceIndex = (fe->IsItalic() ? kItalicMask : 0) |
+ (fe->SupportsBold() ? kBoldMask : 0);
+ if (faces[faceIndex]) {
+ return; // two faces resolve to the same slot; family isn't "simple"
+ }
+ faces[faceIndex] = fe;
+ }
+
+ // we have successfully slotted the available faces into the standard
+ // 4-face framework
+ mAvailableFonts.SetLength(4);
+ for (uint8_t i = 0; i < 4; ++i) {
+ if (mAvailableFonts[i].get() != faces[i]) {
+ mAvailableFonts[i].swap(faces[i]);
+ }
+ }
+
+ mIsSimpleFamily = true;
+}
+
+#ifdef DEBUG
+bool gfxFontFamily::ContainsFace(gfxFontEntry* aFontEntry) {
+ AutoReadLock lock(mLock);
+
+ uint32_t i, numFonts = mAvailableFonts.Length();
+ for (i = 0; i < numFonts; i++) {
+ if (mAvailableFonts[i] == aFontEntry) {
+ return true;
+ }
+ // userfonts contain the actual real font entry
+ if (mAvailableFonts[i] && mAvailableFonts[i]->mIsUserFontContainer) {
+ gfxUserFontEntry* ufe =
+ static_cast<gfxUserFontEntry*>(mAvailableFonts[i].get());
+ if (ufe->GetPlatformFontEntry() == aFontEntry) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+#endif
+
+void gfxFontFamily::LocalizedName(nsACString& aLocalizedName) {
+ // just return the primary name; subclasses should override
+ aLocalizedName = mName;
+}
+
+void gfxFontFamily::FindFontForChar(GlobalFontMatch* aMatchData) {
+ gfxPlatformFontList::PlatformFontList()->mLock.AssertCurrentThreadIn();
+
+ {
+ AutoReadLock lock(mLock);
+ if (mFamilyCharacterMapInitialized && !TestCharacterMap(aMatchData->mCh)) {
+ // none of the faces in the family support the required char,
+ // so bail out immediately
+ return;
+ }
+ }
+
+ nsCString charAndName;
+ if (profiler_thread_is_being_profiled(
+ Combine(ThreadProfilingFeatures::Sampling,
+ ThreadProfilingFeatures::Markers))) {
+ charAndName = nsPrintfCString("\\u%x %s", aMatchData->mCh, mName.get());
+ }
+ AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("gfxFontFamily::FindFontForChar",
+ LAYOUT, charAndName);
+
+ AutoTArray<gfxFontEntry*, 4> entries;
+ FindAllFontsForStyle(aMatchData->mStyle, entries,
+ /*aIgnoreSizeTolerance*/ true);
+ if (entries.IsEmpty()) {
+ return;
+ }
+
+ gfxFontEntry* fe = nullptr;
+ float distance = INFINITY;
+
+ for (auto e : entries) {
+ if (e->SkipDuringSystemFallback()) {
+ continue;
+ }
+
+ aMatchData->mCmapsTested++;
+ if (e->HasCharacter(aMatchData->mCh)) {
+ aMatchData->mCount++;
+
+ LogModule* log = gfxPlatform::GetLog(eGfxLog_textrun);
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Debug))) {
+ intl::Script script =
+ intl::UnicodeProperties::GetScriptCode(aMatchData->mCh);
+ MOZ_LOG(log, LogLevel::Debug,
+ ("(textrun-systemfallback-fonts) char: u+%6.6x "
+ "script: %d match: [%s]\n",
+ aMatchData->mCh, int(script), e->Name().get()));
+ }
+
+ fe = e;
+ distance = WeightStyleStretchDistance(fe, aMatchData->mStyle);
+ if (aMatchData->mPresentation != eFontPresentation::Any) {
+ RefPtr<gfxFont> font = fe->FindOrMakeFont(&aMatchData->mStyle);
+ if (!font) {
+ continue;
+ }
+ bool hasColorGlyph =
+ font->HasColorGlyphFor(aMatchData->mCh, aMatchData->mNextCh);
+ if (hasColorGlyph != PrefersColor(aMatchData->mPresentation)) {
+ distance += kPresentationMismatch;
+ }
+ }
+ break;
+ }
+ }
+
+ if (!fe && !aMatchData->mStyle.IsNormalStyle()) {
+ // If style/weight/stretch was not Normal, see if we can
+ // fall back to a next-best face (e.g. Arial Black -> Bold,
+ // or Arial Narrow -> Regular).
+ GlobalFontMatch data(aMatchData->mCh, aMatchData->mNextCh,
+ aMatchData->mStyle, aMatchData->mPresentation);
+ SearchAllFontsForChar(&data);
+ if (!data.mBestMatch) {
+ return;
+ }
+ fe = data.mBestMatch;
+ distance = data.mMatchDistance;
+ }
+
+ if (!fe) {
+ return;
+ }
+
+ if (distance < aMatchData->mMatchDistance ||
+ (distance == aMatchData->mMatchDistance &&
+ Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0)) {
+ aMatchData->mBestMatch = fe;
+ aMatchData->mMatchedFamily = this;
+ aMatchData->mMatchDistance = distance;
+ }
+}
+
+void gfxFontFamily::SearchAllFontsForChar(GlobalFontMatch* aMatchData) {
+ if (!mFamilyCharacterMapInitialized) {
+ ReadAllCMAPs();
+ }
+ AutoReadLock lock(mLock);
+ if (!mFamilyCharacterMap.test(aMatchData->mCh)) {
+ return;
+ }
+ uint32_t i, numFonts = mAvailableFonts.Length();
+ for (i = 0; i < numFonts; i++) {
+ gfxFontEntry* fe = mAvailableFonts[i];
+ if (fe && fe->HasCharacter(aMatchData->mCh)) {
+ float distance = WeightStyleStretchDistance(fe, aMatchData->mStyle);
+ if (aMatchData->mPresentation != eFontPresentation::Any) {
+ RefPtr<gfxFont> font = fe->FindOrMakeFont(&aMatchData->mStyle);
+ if (!font) {
+ continue;
+ }
+ bool hasColorGlyph =
+ font->HasColorGlyphFor(aMatchData->mCh, aMatchData->mNextCh);
+ if (hasColorGlyph != PrefersColor(aMatchData->mPresentation)) {
+ distance += kPresentationMismatch;
+ }
+ }
+ if (distance < aMatchData->mMatchDistance ||
+ (distance == aMatchData->mMatchDistance &&
+ Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0)) {
+ aMatchData->mBestMatch = fe;
+ aMatchData->mMatchedFamily = this;
+ aMatchData->mMatchDistance = distance;
+ }
+ }
+ }
+}
+
+/*virtual*/
+gfxFontFamily::~gfxFontFamily() {
+ // Should not be dropped by stylo, but the InitFontList thread might use
+ // a transient gfxFontFamily and that's OK.
+ MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal());
+}
+
+// returns true if other names were found, false otherwise
+bool gfxFontFamily::ReadOtherFamilyNamesForFace(
+ gfxPlatformFontList* aPlatformFontList, hb_blob_t* aNameTable,
+ bool useFullName) {
+ uint32_t dataLength;
+ const char* nameData = hb_blob_get_data(aNameTable, &dataLength);
+ AutoTArray<nsCString, 4> otherFamilyNames;
+
+ gfxFontUtils::ReadOtherFamilyNamesForFace(mName, nameData, dataLength,
+ otherFamilyNames, useFullName);
+
+ if (!otherFamilyNames.IsEmpty()) {
+ aPlatformFontList->AddOtherFamilyNames(this, otherFamilyNames);
+ }
+
+ return !otherFamilyNames.IsEmpty();
+}
+
+void gfxFontFamily::ReadOtherFamilyNames(
+ gfxPlatformFontList* aPlatformFontList) {
+ AutoWriteLock lock(mLock);
+ if (mOtherFamilyNamesInitialized) {
+ return;
+ }
+
+ mOtherFamilyNamesInitialized = true;
+
+ FindStyleVariationsLocked();
+
+ // read in other family names for the first face in the list
+ uint32_t i, numFonts = mAvailableFonts.Length();
+ const uint32_t kNAME = TRUETYPE_TAG('n', 'a', 'm', 'e');
+
+ for (i = 0; i < numFonts; ++i) {
+ gfxFontEntry* fe = mAvailableFonts[i];
+ if (!fe) {
+ continue;
+ }
+ gfxFontEntry::AutoTable nameTable(fe, kNAME);
+ if (!nameTable) {
+ continue;
+ }
+ mHasOtherFamilyNames =
+ ReadOtherFamilyNamesForFace(aPlatformFontList, nameTable);
+ break;
+ }
+
+ // read in other names for the first face in the list with the assumption
+ // that if extra names don't exist in that face then they don't exist in
+ // other faces for the same font
+ if (!mHasOtherFamilyNames) {
+ return;
+ }
+
+ // read in names for all faces, needed to catch cases where fonts have
+ // family names for individual weights (e.g. Hiragino Kaku Gothic Pro W6)
+ for (; i < numFonts; i++) {
+ gfxFontEntry* fe = mAvailableFonts[i];
+ if (!fe) {
+ continue;
+ }
+ gfxFontEntry::AutoTable nameTable(fe, kNAME);
+ if (!nameTable) {
+ continue;
+ }
+ ReadOtherFamilyNamesForFace(aPlatformFontList, nameTable);
+ }
+}
+
+static bool LookForLegacyFamilyName(const nsACString& aCanonicalName,
+ const char* aNameData, uint32_t aDataLength,
+ nsACString& aLegacyName /* outparam */) {
+ const gfxFontUtils::NameHeader* nameHeader =
+ reinterpret_cast<const gfxFontUtils::NameHeader*>(aNameData);
+
+ uint32_t nameCount = nameHeader->count;
+ if (nameCount * sizeof(gfxFontUtils::NameRecord) > aDataLength) {
+ NS_WARNING("invalid font (name records)");
+ return false;
+ }
+
+ const gfxFontUtils::NameRecord* nameRecord =
+ reinterpret_cast<const gfxFontUtils::NameRecord*>(
+ aNameData + sizeof(gfxFontUtils::NameHeader));
+ uint32_t stringsBase = uint32_t(nameHeader->stringOffset);
+
+ for (uint32_t i = 0; i < nameCount; i++, nameRecord++) {
+ uint32_t nameLen = nameRecord->length;
+ uint32_t nameOff = nameRecord->offset;
+
+ if (stringsBase + nameOff + nameLen > aDataLength) {
+ NS_WARNING("invalid font (name table strings)");
+ return false;
+ }
+
+ if (uint16_t(nameRecord->nameID) == gfxFontUtils::NAME_ID_FAMILY) {
+ bool ok = gfxFontUtils::DecodeFontName(
+ aNameData + stringsBase + nameOff, nameLen,
+ uint32_t(nameRecord->platformID), uint32_t(nameRecord->encodingID),
+ uint32_t(nameRecord->languageID), aLegacyName);
+ // It's only a legacy name if it case-insensitively differs from the
+ // canonical name (otherwise it would map to the same key).
+ if (ok && !aLegacyName.Equals(aCanonicalName,
+ nsCaseInsensitiveCStringComparator)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool gfxFontFamily::CheckForLegacyFamilyNames(gfxPlatformFontList* aFontList) {
+ aFontList->mLock.AssertCurrentThreadIn();
+ if (mCheckedForLegacyFamilyNames) {
+ // we already did this, so there's nothing more to add
+ return false;
+ }
+ mCheckedForLegacyFamilyNames = true;
+ bool added = false;
+ const uint32_t kNAME = TRUETYPE_TAG('n', 'a', 'm', 'e');
+ AutoTArray<RefPtr<gfxFontEntry>, 16> faces;
+ {
+ // Take a local copy of the array of font entries, because it's possible
+ // AddWithLegacyFamilyName will mutate it (and it needs to be able to take
+ // an exclusive lock on the family to do so, so we release the read lock
+ // here).
+ AutoReadLock lock(mLock);
+ faces.AppendElements(mAvailableFonts);
+ }
+ for (const auto& fe : faces) {
+ if (!fe) {
+ continue;
+ }
+ gfxFontEntry::AutoTable nameTable(fe, kNAME);
+ if (!nameTable) {
+ continue;
+ }
+ nsAutoCString legacyName;
+ uint32_t dataLength;
+ const char* nameData = hb_blob_get_data(nameTable, &dataLength);
+ if (LookForLegacyFamilyName(Name(), nameData, dataLength, legacyName)) {
+ if (aFontList->AddWithLegacyFamilyName(legacyName, fe, mVisibility)) {
+ added = true;
+ }
+ }
+ }
+ return added;
+}
+
+void gfxFontFamily::ReadFaceNames(gfxPlatformFontList* aPlatformFontList,
+ bool aNeedFullnamePostscriptNames,
+ FontInfoData* aFontInfoData) {
+ aPlatformFontList->mLock.AssertCurrentThreadIn();
+
+ // if all needed names have already been read, skip
+ if (mOtherFamilyNamesInitialized &&
+ (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) {
+ return;
+ }
+
+ AutoWriteLock lock(mLock);
+
+ bool asyncFontLoaderDisabled = false;
+
+ if (!mOtherFamilyNamesInitialized && aFontInfoData &&
+ aFontInfoData->mLoadOtherNames && !asyncFontLoaderDisabled) {
+ const auto* otherFamilyNames = aFontInfoData->GetOtherFamilyNames(mName);
+ if (otherFamilyNames && otherFamilyNames->Length()) {
+ aPlatformFontList->AddOtherFamilyNames(this, *otherFamilyNames);
+ }
+ mOtherFamilyNamesInitialized = true;
+ }
+
+ // if all needed data has been initialized, return
+ if (mOtherFamilyNamesInitialized &&
+ (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) {
+ return;
+ }
+
+ FindStyleVariationsLocked(aFontInfoData);
+
+ // check again, as style enumeration code may have loaded names
+ if (mOtherFamilyNamesInitialized &&
+ (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) {
+ return;
+ }
+
+ uint32_t i, numFonts = mAvailableFonts.Length();
+ const uint32_t kNAME = TRUETYPE_TAG('n', 'a', 'm', 'e');
+
+ bool firstTime = true, readAllFaces = false;
+ for (i = 0; i < numFonts; ++i) {
+ gfxFontEntry* fe = mAvailableFonts[i];
+ if (!fe) {
+ continue;
+ }
+
+ nsAutoCString fullname, psname;
+ bool foundFaceNames = false;
+ if (!mFaceNamesInitialized && aNeedFullnamePostscriptNames &&
+ aFontInfoData && aFontInfoData->mLoadFaceNames) {
+ aFontInfoData->GetFaceNames(fe->Name(), fullname, psname);
+ if (!fullname.IsEmpty()) {
+ aPlatformFontList->AddFullnameLocked(fe, fullname);
+ }
+ if (!psname.IsEmpty()) {
+ aPlatformFontList->AddPostscriptNameLocked(fe, psname);
+ }
+ foundFaceNames = true;
+
+ // found everything needed? skip to next font
+ if (mOtherFamilyNamesInitialized) {
+ continue;
+ }
+ }
+
+ // load directly from the name table
+ gfxFontEntry::AutoTable nameTable(fe, kNAME);
+ if (!nameTable) {
+ continue;
+ }
+
+ if (aNeedFullnamePostscriptNames && !foundFaceNames) {
+ if (gfxFontUtils::ReadCanonicalName(nameTable, gfxFontUtils::NAME_ID_FULL,
+ fullname) == NS_OK) {
+ aPlatformFontList->AddFullnameLocked(fe, fullname);
+ }
+
+ if (gfxFontUtils::ReadCanonicalName(
+ nameTable, gfxFontUtils::NAME_ID_POSTSCRIPT, psname) == NS_OK) {
+ aPlatformFontList->AddPostscriptNameLocked(fe, psname);
+ }
+ }
+
+ if (!mOtherFamilyNamesInitialized && (firstTime || readAllFaces)) {
+ bool foundOtherName =
+ ReadOtherFamilyNamesForFace(aPlatformFontList, nameTable);
+
+ // if the first face has a different name, scan all faces, otherwise
+ // assume the family doesn't have other names
+ if (firstTime && foundOtherName) {
+ mHasOtherFamilyNames = true;
+ readAllFaces = true;
+ }
+ firstTime = false;
+ }
+
+ // if not reading in any more names, skip other faces
+ if (!readAllFaces && !aNeedFullnamePostscriptNames) {
+ break;
+ }
+ }
+
+ mFaceNamesInitialized = true;
+ mOtherFamilyNamesInitialized = true;
+}
+
+gfxFontEntry* gfxFontFamily::FindFont(const nsACString& aFontName,
+ const nsCStringComparator& aCmp) const {
+ // find the font using a simple linear search
+ AutoReadLock lock(mLock);
+ uint32_t numFonts = mAvailableFonts.Length();
+ for (uint32_t i = 0; i < numFonts; i++) {
+ gfxFontEntry* fe = mAvailableFonts[i].get();
+ if (fe && fe->Name().Equals(aFontName, aCmp)) {
+ return fe;
+ }
+ }
+ return nullptr;
+}
+
+void gfxFontFamily::ReadAllCMAPs(FontInfoData* aFontInfoData) {
+ AutoWriteLock lock(mLock);
+ FindStyleVariationsLocked(aFontInfoData);
+
+ uint32_t i, numFonts = mAvailableFonts.Length();
+ for (i = 0; i < numFonts; i++) {
+ gfxFontEntry* fe = mAvailableFonts[i];
+ // don't try to load cmaps for downloadable fonts not yet loaded
+ if (!fe || fe->mIsUserFontContainer) {
+ continue;
+ }
+ fe->ReadCMAP(aFontInfoData);
+ mFamilyCharacterMap.Union(*(fe->GetCharacterMap()));
+ }
+ mFamilyCharacterMap.Compact();
+ mFamilyCharacterMapInitialized = true;
+}
+
+void gfxFontFamily::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ AutoReadLock lock(mLock);
+ aSizes->mFontListSize += mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ aSizes->mCharMapsSize +=
+ mFamilyCharacterMap.SizeOfExcludingThis(aMallocSizeOf);
+
+ aSizes->mFontListSize +=
+ mAvailableFonts.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (uint32_t i = 0; i < mAvailableFonts.Length(); ++i) {
+ gfxFontEntry* fe = mAvailableFonts[i];
+ if (fe) {
+ fe->AddSizeOfIncludingThis(aMallocSizeOf, aSizes);
+ }
+ }
+}
+
+void gfxFontFamily::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ aSizes->mFontListSize += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
diff --git a/gfx/thebes/gfxFontEntry.h b/gfx/thebes/gfxFontEntry.h
new file mode 100644
index 0000000000..86d2445d0b
--- /dev/null
+++ b/gfx/thebes/gfxFontEntry.h
@@ -0,0 +1,1253 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_FONTENTRY_H
+#define GFX_FONTENTRY_H
+
+#include <limits>
+#include <math.h>
+#include <new>
+#include <utility>
+#include "COLRFonts.h"
+#include "ThebesRLBoxTypes.h"
+#include "gfxFontUtils.h"
+#include "gfxFontVariations.h"
+#include "gfxRect.h"
+#include "gfxTypes.h"
+#include "harfbuzz/hb.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/RWLock.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/intl/UnicodeScriptCodes.h"
+#include "nsTHashMap.h"
+#include "nsDebug.h"
+#include "nsHashKeys.h"
+#include "nsISupports.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "nscore.h"
+
+class FontInfoData;
+class gfxContext;
+class gfxFont;
+class gfxFontFamily;
+class gfxPlatformFontList;
+class gfxSVGGlyphs;
+class gfxUserFontData;
+class nsAtom;
+struct FontListSizes;
+struct gfxFontFeature;
+struct gfxFontStyle;
+enum class eFontPresentation : uint8_t;
+
+namespace IPC {
+template <class P>
+struct ParamTraits;
+}
+
+namespace mozilla {
+class SVGContextPaint;
+namespace fontlist {
+struct Face;
+struct Family;
+} // namespace fontlist
+} // namespace mozilla
+
+typedef struct gr_face gr_face;
+typedef struct FT_MM_Var_ FT_MM_Var;
+
+#define NO_FONT_LANGUAGE_OVERRIDE 0
+
+class gfxCharacterMap : public gfxSparseBitSet {
+ public:
+ // gfxCharacterMap instances may be shared across multiple threads via a
+ // global table managed by gfxPlatformFontList. Once a gfxCharacterMap is
+ // inserted in the global table, its mShared flag will be TRUE, and we
+ // cannot safely delete it except from gfxPlatformFontList (which will
+ // use a lock to ensure entries are removed from its table and deleted
+ // safely).
+
+ // AddRef() is pretty much standard. We don't return the refcount as our
+ // users don't care about it.
+ void AddRef() {
+ MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(gfxCharacterMap);
+ MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
+ [[maybe_unused]] nsrefcnt count = ++mRefCnt;
+ NS_LOG_ADDREF(this, count, "gfxCharacterMap", sizeof(*this));
+ }
+
+ // Custom Release(): if the object is referenced from the global shared
+ // table, and we're releasing the last *other* reference to it, then we
+ // notify the global table to consider also releasing its ref. (That may
+ // not actually happen, if another thread is racing with us and takes a
+ // new reference, or completes the release first!)
+ void Release() {
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
+ // We can't safely read this after we've decremented mRefCnt, so save it
+ // in a local variable here. Note that the value is never reset to false
+ // once it has been set to true (when recording the cmap in the shared
+ // table), so there's no risk of this resulting in a "false positive" when
+ // tested later. A "false negative" is possible but harmless; it would
+ // just mean we miss an opportunity to release a reference from the shared
+ // cmap table.
+ bool isShared = mShared;
+
+ // Ensure we only access mRefCnt once, for consistency if the object is
+ // being used by multiple threads.
+ nsrefcnt count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "gfxCharacterMap");
+
+ // If isShared was true, this object has been shared across threads. In
+ // that case, if the refcount went to 1, we notify the shared table so
+ // it can drop its reference and delete the object.
+ if (isShared) {
+ MOZ_ASSERT(count > 0);
+ if (count == 1) {
+ NotifyMaybeReleased(this);
+ }
+ return;
+ }
+
+ // Otherwise, this object hasn't been shared and we can safely delete it
+ // as we must have been holding the only reference. (Note that if we were
+ // holding the only reference, there's no other owner who can have set
+ // mShared to true since we read it above.)
+ if (count == 0) {
+ delete this;
+ }
+ }
+
+ gfxCharacterMap() = default;
+
+ explicit gfxCharacterMap(const gfxSparseBitSet& aOther)
+ : gfxSparseBitSet(aOther) {}
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return gfxSparseBitSet::SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ // hash of the cmap bitvector
+ uint32_t mHash = 0;
+
+ // if cmap is built on the fly it's never shared
+ bool mBuildOnTheFly = false;
+
+ // Character map is shared globally. This can only be set by the thread that
+ // originally created the map, as no other thread can get a reference until
+ // it has been shared via the global table.
+ bool mShared = false;
+
+ protected:
+ friend class gfxPlatformFontList;
+
+ // Destructor should not be called except via Release().
+ // (Note that our "friend" gfxPlatformFontList also accesses this from its
+ // MaybeRemoveCmap method.)
+ ~gfxCharacterMap() = default;
+
+ nsrefcnt RefCount() const { return mRefCnt; }
+
+ void CalcHash() { mHash = GetChecksum(); }
+
+ static void NotifyMaybeReleased(gfxCharacterMap* aCmap);
+
+ // Only used when clearing the shared-cmap hashtable during shutdown.
+ void ClearSharedFlag() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mShared = false;
+ }
+
+ mozilla::ThreadSafeAutoRefCnt mRefCnt;
+
+ private:
+ gfxCharacterMap(const gfxCharacterMap&) = delete;
+ gfxCharacterMap& operator=(const gfxCharacterMap&) = delete;
+};
+
+// Info on an individual font feature, for reporting available features
+// to DevTools via the GetFeatureInfo method.
+struct gfxFontFeatureInfo {
+ uint32_t mTag;
+ uint32_t mScript;
+ uint32_t mLangSys;
+};
+
+class gfxFontEntryCallbacks;
+
+class gfxFontEntry {
+ public:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::intl::Script Script;
+ typedef mozilla::FontWeight FontWeight;
+ typedef mozilla::FontSlantStyle FontSlantStyle;
+ typedef mozilla::FontStretch FontStretch;
+ typedef mozilla::WeightRange WeightRange;
+ typedef mozilla::SlantStyleRange SlantStyleRange;
+ typedef mozilla::StretchRange StretchRange;
+
+ // Used by stylo
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxFontEntry)
+
+ explicit gfxFontEntry(const nsACString& aName, bool aIsStandardFace = false);
+
+ gfxFontEntry() = delete;
+ gfxFontEntry(const gfxFontEntry&) = delete;
+ gfxFontEntry& operator=(const gfxFontEntry&) = delete;
+
+ // Create a new entry that refers to the same font as this, but without
+ // additional state that may have been set up (such as family name).
+ // (This is only to be used for system fonts in the platform font list,
+ // not user fonts.)
+ virtual gfxFontEntry* Clone() const = 0;
+
+ // unique name for the face, *not* the family; not necessarily the
+ // "real" or user-friendly name, may be an internal identifier
+ const nsCString& Name() const { return mName; }
+
+ // family name
+ const nsCString& FamilyName() const { return mFamilyName; }
+
+ // The following two methods may be relatively expensive, as they
+ // will (usually, except on Linux) load and parse the 'name' table;
+ // they are intended only for the font-inspection API, not for
+ // perf-critical layout/drawing work.
+
+ // The "real" name of the face, if available from the font resource;
+ // returns Name() if nothing better is available.
+ virtual nsCString RealFaceName();
+
+ WeightRange Weight() const { return mWeightRange; }
+ StretchRange Stretch() const { return mStretchRange; }
+ SlantStyleRange SlantStyle() const { return mStyleRange; }
+
+ bool IsUserFont() const { return mIsDataUserFont || mIsLocalUserFont; }
+ bool IsLocalUserFont() const { return mIsLocalUserFont; }
+ bool IsFixedPitch() const { return mFixedPitch; }
+ bool IsItalic() const { return SlantStyle().Min().IsItalic(); }
+ bool IsOblique() const { return SlantStyle().Min().IsOblique(); }
+ bool IsUpright() const { return SlantStyle().Min().IsNormal(); }
+ inline bool SupportsItalic(); // defined below, because of RangeFlags use
+ inline bool SupportsBold();
+ inline bool MayUseSyntheticSlant();
+ bool IgnoreGDEF() const { return mIgnoreGDEF; }
+ bool IgnoreGSUB() const { return mIgnoreGSUB; }
+
+ // Return whether the face corresponds to "normal" CSS style properties:
+ // font-style: normal;
+ // font-weight: normal;
+ // font-stretch: normal;
+ // If this is false, we might want to fall back to a different face and
+ // possibly apply synthetic styling.
+ bool IsNormalStyle() const {
+ return IsUpright() && Weight().Min() <= FontWeight::NORMAL &&
+ Weight().Max() >= FontWeight::NORMAL &&
+ Stretch().Min() <= FontStretch::NORMAL &&
+ Stretch().Max() >= FontStretch::NORMAL;
+ }
+
+ // whether a feature is supported by the font (limited to a small set
+ // of features for which some form of fallback needs to be implemented)
+ virtual bool SupportsOpenTypeFeature(Script aScript, uint32_t aFeatureTag);
+ bool SupportsGraphiteFeature(uint32_t aFeatureTag);
+
+ // returns a set containing all input glyph ids for a given feature
+ const hb_set_t* InputsForOpenTypeFeature(Script aScript,
+ uint32_t aFeatureTag);
+
+ virtual bool HasFontTable(uint32_t aTableTag);
+
+ inline bool HasGraphiteTables() {
+ LazyFlag flag = mHasGraphiteTables;
+ if (flag == LazyFlag::Uninitialized) {
+ flag = CheckForGraphiteTables() ? LazyFlag::Yes : LazyFlag::No;
+ mHasGraphiteTables = flag;
+ }
+ return flag == LazyFlag::Yes;
+ }
+
+ inline bool HasCmapTable() {
+ if (!mCharacterMap && !mShmemCharacterMap) {
+ ReadCMAP();
+ NS_ASSERTION(mCharacterMap || mShmemCharacterMap,
+ "failed to initialize character map");
+ }
+ return mHasCmapTable;
+ }
+
+ inline bool HasCharacter(uint32_t ch) {
+ if (mShmemCharacterMap) {
+ return GetShmemCharacterMap()->test(ch);
+ }
+ if (mCharacterMap) {
+ if (mShmemFace && TrySetShmemCharacterMap()) {
+ // Forget our temporary local copy, now we can use the shared cmap
+ auto* oldCmap = mCharacterMap.exchange(nullptr);
+ NS_IF_RELEASE(oldCmap);
+ return GetShmemCharacterMap()->test(ch);
+ }
+ if (GetCharacterMap()->test(ch)) {
+ return true;
+ }
+ }
+ return TestCharacterMap(ch);
+ }
+
+ virtual bool SkipDuringSystemFallback() { return false; }
+ void EnsureUVSMapInitialized();
+ uint16_t GetUVSGlyph(uint32_t aCh, uint32_t aVS);
+
+ // All concrete gfxFontEntry subclasses (except gfxUserFontEntry) need
+ // to override this, otherwise the font will never be used as it will
+ // be considered to support no characters.
+ // ReadCMAP() must *always* set the mCharacterMap pointer to a valid
+ // gfxCharacterMap, even if empty, as other code assumes this pointer
+ // can be safely dereferenced.
+ virtual nsresult ReadCMAP(FontInfoData* aFontInfoData = nullptr);
+
+ bool TryGetSVGData(const gfxFont* aFont);
+ bool HasSVGGlyph(uint32_t aGlyphId);
+ bool GetSVGGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphId,
+ gfxFloat aSize, gfxRect* aResult);
+ void RenderSVGGlyph(gfxContext* aContext, uint32_t aGlyphId,
+ mozilla::SVGContextPaint* aContextPaint);
+ // Call this when glyph geometry or rendering has changed
+ // (e.g. animated SVG glyphs)
+ void NotifyGlyphsChanged();
+
+ bool TryGetColorGlyphs();
+
+ bool HasColorBitmapTable() {
+ LazyFlag flag = mHasColorBitmapTable;
+ if (flag == LazyFlag::Uninitialized) {
+ flag = HasFontTable(TRUETYPE_TAG('C', 'B', 'D', 'T')) ||
+ HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))
+ ? LazyFlag::Yes
+ : LazyFlag::No;
+ mHasColorBitmapTable = flag;
+ }
+ return flag == LazyFlag::Yes;
+ }
+
+ // Access to raw font table data (needed for Harfbuzz):
+ // returns a pointer to data owned by the fontEntry or the OS,
+ // which will remain valid until the blob is destroyed.
+ // The data MUST be treated as read-only; we may be getting a
+ // reference to a shared system font cache.
+ //
+ // The default implementation uses CopyFontTable to get the data
+ // into a byte array, and maintains a cache of loaded tables.
+ //
+ // Subclasses should override this if they can provide more efficient
+ // access than copying table data into our own buffers.
+ //
+ // Get blob that encapsulates a specific font table, or nullptr if
+ // the table doesn't exist in the font.
+ //
+ // Caller is responsible to call hb_blob_destroy() on the returned blob
+ // (if non-nullptr) when no longer required. For transient access to a
+ // table, use of AutoTable (below) is generally preferred.
+ virtual hb_blob_t* GetFontTable(uint32_t aTag);
+
+ // Stack-based utility to return a specified table, automatically releasing
+ // the blob when the AutoTable goes out of scope.
+ class AutoTable {
+ public:
+ AutoTable(gfxFontEntry* aFontEntry, uint32_t aTag) {
+ mBlob = aFontEntry->GetFontTable(aTag);
+ }
+ ~AutoTable() { hb_blob_destroy(mBlob); }
+ operator hb_blob_t*() const { return mBlob; }
+
+ private:
+ hb_blob_t* mBlob;
+ // not implemented:
+ AutoTable(const AutoTable&) = delete;
+ AutoTable& operator=(const AutoTable&) = delete;
+ };
+
+ // Return a font instance for a particular style. This may be a newly-
+ // created instance, or a font already in the global cache.
+ // We can't return a UniquePtr here, because we may be returning a shared
+ // cached instance; but we also don't return already_AddRefed, because
+ // the caller may only need to use the font temporarily and doesn't need
+ // a strong reference.
+ already_AddRefed<gfxFont> FindOrMakeFont(
+ const gfxFontStyle* aStyle, gfxCharacterMap* aUnicodeRangeMap = nullptr);
+
+ // Get an existing font table cache entry in aBlob if it has been
+ // registered, or return false if not. Callers must call
+ // hb_blob_destroy on aBlob if true is returned.
+ //
+ // Note that some gfxFont implementations may not call this at all,
+ // if it is more efficient to get the table from the OS at that level.
+ bool GetExistingFontTable(uint32_t aTag, hb_blob_t** aBlob);
+
+ // Elements of aTable are transferred (not copied) to and returned in a
+ // new hb_blob_t which is registered on the gfxFontEntry, but the initial
+ // reference is owned by the caller. Removing the last reference
+ // unregisters the table from the font entry.
+ //
+ // Pass nullptr for aBuffer to indicate that the table is not present and
+ // nullptr will be returned. Also returns nullptr on OOM.
+ hb_blob_t* ShareFontTableAndGetBlob(uint32_t aTag, nsTArray<uint8_t>* aTable);
+
+ // Get the font's unitsPerEm from the 'head' table, in the case of an
+ // sfnt resource. Will return kInvalidUPEM for non-sfnt fonts,
+ // if present on the platform.
+ uint16_t UnitsPerEm();
+ enum {
+ kMinUPEM = 16, // Limits on valid unitsPerEm range, from the
+ kMaxUPEM = 16384, // OpenType spec
+ kInvalidUPEM = uint16_t(-1)
+ };
+
+ // Shaper face accessors:
+ // NOTE that harfbuzz and graphite handle ownership/lifetime of the face
+ // object in completely different ways.
+
+ // Create a HarfBuzz face corresponding to this font file.
+ // Our reference to the underlying hb_face_t will be released when the
+ // returned AutoHBFace goes out of scope, but the hb_face_t itself may
+ // be kept alive by other references (e.g. if an hb_font_t has been
+ // instantiated for it).
+ class MOZ_STACK_CLASS AutoHBFace {
+ public:
+ explicit AutoHBFace(hb_face_t* aFace) : mFace(aFace) {}
+ ~AutoHBFace() { hb_face_destroy(mFace); }
+
+ operator hb_face_t*() const { return mFace; }
+
+ // Not default-constructible, not copyable.
+ AutoHBFace() = delete;
+ AutoHBFace(const AutoHBFace&) = delete;
+ AutoHBFace& operator=(const AutoHBFace&) = delete;
+
+ private:
+ hb_face_t* mFace;
+ };
+
+ AutoHBFace GetHBFace() {
+ return AutoHBFace(hb_face_create_for_tables(HBGetTable, this, nullptr));
+ }
+
+ // Get the sandbox instance that graphite is running in.
+ rlbox_sandbox_gr* GetGrSandbox();
+
+ // Register and get the callback handle for the glyph advance firefox callback
+ // Since the sandbox instance is shared with multiple test shapers, callback
+ // registration must be handled centrally to ensure multiple instances don't
+ // register the same callback.
+ sandbox_callback_gr<float (*)(const void*, uint16_t)>*
+ GetGrSandboxAdvanceCallbackHandle();
+
+ // Get Graphite face corresponding to this font file.
+ // Caller must call gfxFontEntry::ReleaseGrFace when finished with it.
+ // Graphite is run in a sandbox
+ tainted_opaque_gr<gr_face*> GetGrFace();
+ void ReleaseGrFace(tainted_opaque_gr<gr_face*> aFace);
+
+ // Does the font have graphite contextuals that involve the space glyph
+ // (and therefore we should bypass the word cache)?
+ // Since this function inspects data from libGraphite stored in sandbox memory
+ // it can only return a "hint" to the correct return value. This is because
+ // a compromised libGraphite could change the sandbox memory maliciously at
+ // any moment. The caller must ensure the calling code performs safe actions
+ // independent of the value returned, to unwrap this return.
+ tainted_boolean_hint HasGraphiteSpaceContextuals();
+
+ // Release any SVG-glyphs document this font may have loaded.
+ void DisconnectSVG();
+
+ // Called to notify that aFont is being destroyed. Needed when we're tracking
+ // the fonts belonging to this font entry.
+ void NotifyFontDestroyed(gfxFont* aFont);
+
+ // For memory reporting of the platform font list.
+ virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const;
+ virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const;
+
+ // Used for reporting on individual font entries in the user font cache,
+ // which are not present in the platform font list.
+ size_t ComputedSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ // Used when checking for complex script support, to mask off cmap ranges
+ struct ScriptRange {
+ uint32_t rangeStart;
+ uint32_t rangeEnd;
+ uint32_t numTags; // number of entries in the tags[] array
+ hb_tag_t tags[3]; // up to three OpenType script tags to check
+ };
+
+ bool SupportsScriptInGSUB(const hb_tag_t* aScriptTags, uint32_t aNumTags);
+
+ /**
+ * Font-variation query methods.
+ *
+ * Font backends that don't support variations should provide empty
+ * implementations.
+ */
+ virtual bool HasVariations() = 0;
+
+ virtual void GetVariationAxes(
+ nsTArray<gfxFontVariationAxis>& aVariationAxes) = 0;
+
+ virtual void GetVariationInstances(
+ nsTArray<gfxFontVariationInstance>& aInstances) = 0;
+
+ bool HasBoldVariableWeight();
+ bool HasItalicVariation();
+ bool HasSlantVariation();
+ bool HasOpticalSize();
+
+ void CheckForVariationAxes();
+
+ // Set up the entry's weight/stretch/style ranges according to axes found
+ // by GetVariationAxes (for installed fonts; do NOT call this for user
+ // fonts, where the ranges are provided by @font-face descriptors).
+ void SetupVariationRanges();
+
+ // Get variation axis settings that should be used to implement a particular
+ // font style using this resource.
+ void GetVariationsForStyle(nsTArray<gfxFontVariation>& aResult,
+ const gfxFontStyle& aStyle);
+
+ // Get the font's list of features (if any) for DevTools support.
+ void GetFeatureInfo(nsTArray<gfxFontFeatureInfo>& aFeatureInfo);
+
+ // This is only called on platforms where we use FreeType.
+ virtual FT_MM_Var* GetMMVar() { return nullptr; }
+
+ // Return true if the font has a 'trak' table (and we can successfully
+ // interpret it), otherwise false. This will load and cache the table
+ // the first time it is called.
+ bool HasTrackingTable();
+
+ // Return the tracking (in font units) to be applied for the given size.
+ // (This is a floating-point number because of possible interpolation.)
+ float TrackingForCSSPx(float aSize) const;
+
+ mozilla::gfx::Rect GetFontExtents(float aFUnitScaleFactor) const {
+ // Flip the y-axis here to match the orientation of Gecko's coordinates.
+ return mozilla::gfx::Rect(float(mXMin) * aFUnitScaleFactor,
+ float(-mYMax) * aFUnitScaleFactor,
+ float(mXMax - mXMin) * aFUnitScaleFactor,
+ float(mYMax - mYMin) * aFUnitScaleFactor);
+ }
+
+ nsCString mName;
+ nsCString mFamilyName;
+
+ // These are mutable so that we can take a read lock within a const method.
+ mutable mozilla::RWLock mLock;
+ mutable mozilla::Mutex mFeatureInfoLock;
+
+ mozilla::Atomic<gfxCharacterMap*> mCharacterMap; // strong ref
+ gfxCharacterMap* GetCharacterMap() const { return mCharacterMap; }
+
+ mozilla::fontlist::Face* mShmemFace = nullptr;
+
+ mozilla::Atomic<const SharedBitSet*> mShmemCharacterMap;
+ const SharedBitSet* GetShmemCharacterMap() const {
+ return mShmemCharacterMap;
+ }
+
+ mozilla::Atomic<const uint8_t*> mUVSData;
+ const uint8_t* GetUVSData() const { return mUVSData; }
+
+ mozilla::UniquePtr<gfxUserFontData> mUserFontData;
+
+ mozilla::Atomic<gfxSVGGlyphs*> mSVGGlyphs;
+ gfxSVGGlyphs* GetSVGGlyphs() const { return mSVGGlyphs; }
+
+ // list of gfxFonts that are using SVG glyphs
+ nsTArray<const gfxFont*> mFontsUsingSVGGlyphs MOZ_GUARDED_BY(mLock);
+ nsTArray<gfxFontFeature> mFeatureSettings;
+ nsTArray<gfxFontVariation> mVariationSettings;
+
+ mozilla::UniquePtr<nsTHashMap<nsUint32HashKey, bool>> mSupportedFeatures
+ MOZ_GUARDED_BY(mFeatureInfoLock);
+ mozilla::UniquePtr<nsTHashMap<nsUint32HashKey, hb_set_t*>> mFeatureInputs
+ MOZ_GUARDED_BY(mFeatureInfoLock);
+
+ // Color Layer font support. These tables are inert once loaded, so we don't
+ // need to hold a lock when reading them.
+ mozilla::Atomic<hb_blob_t*> mCOLR;
+ mozilla::Atomic<hb_blob_t*> mCPAL;
+ hb_blob_t* GetCOLR() const { return mCOLR; }
+ hb_blob_t* GetCPAL() const { return mCPAL; }
+
+ // bitvector of substitution space features per script, one each
+ // for default and non-default features
+ uint32_t mDefaultSubSpaceFeatures[(int(Script::NUM_SCRIPT_CODES) + 31) / 32];
+ uint32_t
+ mNonDefaultSubSpaceFeatures[(int(Script::NUM_SCRIPT_CODES) + 31) / 32];
+
+ mozilla::Atomic<uint32_t> mUVSOffset;
+
+ uint32_t mLanguageOverride = NO_FONT_LANGUAGE_OVERRIDE;
+
+ WeightRange mWeightRange = WeightRange(FontWeight::FromInt(500));
+ StretchRange mStretchRange = StretchRange(FontStretch::NORMAL);
+ SlantStyleRange mStyleRange = SlantStyleRange(FontSlantStyle::NORMAL);
+
+ // Font metrics overrides (as multiples of used font size); negative values
+ // indicate no override to be applied.
+ float mAscentOverride = -1.0;
+ float mDescentOverride = -1.0;
+ float mLineGapOverride = -1.0;
+
+ // Scaling factor to be applied to the font size.
+ float mSizeAdjust = 1.0;
+
+ // For user fonts (only), we need to record whether or not weight/stretch/
+ // slant variations should be clamped to the range specified in the entry
+ // properties. When the @font-face rule omitted one or more of these
+ // descriptors, it is treated as the initial value for font-matching (and
+ // so that is what we record in the font entry), but when rendering the
+ // range is NOT clamped.
+ enum class RangeFlags : uint16_t {
+ eNoFlags = 0,
+ eAutoWeight = (1 << 0),
+ eAutoStretch = (1 << 1),
+ eAutoSlantStyle = (1 << 2),
+
+ // Flag to record whether the face has a variable "wght" axis
+ // that supports "bold" values, used to disable the application
+ // of synthetic-bold effects.
+ eBoldVariableWeight = (1 << 3),
+ // Whether the face has an 'ital' axis.
+ eItalicVariation = (1 << 4),
+ // Whether the face has a 'slnt' axis.
+ eSlantVariation = (1 << 5),
+
+ // Flags to record if the face uses a non-CSS-compatible scale
+ // for weight and/or stretch, in which case we won't map the
+ // properties to the variation axes (though they can still be
+ // explicitly set using font-variation-settings).
+ eNonCSSWeight = (1 << 6),
+ eNonCSSStretch = (1 << 7),
+
+ // Whether the font has an 'opsz' axis.
+ eOpticalSize = (1 << 8)
+ };
+ RangeFlags mRangeFlags = RangeFlags::eNoFlags;
+
+ bool mFixedPitch : 1;
+ bool mIsBadUnderlineFont : 1;
+ bool mIsUserFontContainer : 1; // userfont entry
+ bool mIsDataUserFont : 1; // platform font entry (data)
+ bool mIsLocalUserFont : 1; // platform font entry (local)
+ bool mStandardFace : 1;
+ bool mIgnoreGDEF : 1;
+ bool mIgnoreGSUB : 1;
+ bool mSkipDefaultFeatureSpaceCheck : 1;
+
+ mozilla::Atomic<bool> mSVGInitialized;
+ mozilla::Atomic<bool> mHasCmapTable;
+ mozilla::Atomic<bool> mGrFaceInitialized;
+ mozilla::Atomic<bool> mCheckedForColorGlyph;
+ mozilla::Atomic<bool> mCheckedForVariationAxes;
+
+ // Atomic flags that are lazily evaluated - initially set to UNINITIALIZED,
+ // changed to NO or YES once we determine the actual value.
+ enum class LazyFlag : uint8_t { Uninitialized = 0xff, No = 0, Yes = 1 };
+
+ std::atomic<LazyFlag> mSpaceGlyphIsInvisible;
+ std::atomic<LazyFlag> mHasGraphiteTables;
+ std::atomic<LazyFlag> mHasGraphiteSpaceContextuals;
+ std::atomic<LazyFlag> mHasColorBitmapTable;
+
+ enum class SpaceFeatures : uint8_t {
+ Uninitialized = 0xff,
+ None = 0,
+ HasFeatures = 1 << 0,
+ Kerning = 1 << 1,
+ NonKerning = 1 << 2
+ };
+
+ std::atomic<SpaceFeatures> mHasSpaceFeatures;
+
+ protected:
+ friend class gfxPlatformFontList;
+ friend class gfxFontFamily;
+ friend class gfxUserFontEntry;
+
+ // Protected destructor, to discourage deletion outside of Release():
+ virtual ~gfxFontEntry();
+
+ virtual gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle) = 0;
+
+ inline bool CheckForGraphiteTables() {
+ return HasFontTable(TRUETYPE_TAG('S', 'i', 'l', 'f'));
+ }
+
+ // Copy a font table into aBuffer.
+ // The caller will be responsible for ownership of the data.
+ virtual nsresult CopyFontTable(uint32_t aTableTag,
+ nsTArray<uint8_t>& aBuffer) {
+ MOZ_ASSERT_UNREACHABLE(
+ "forgot to override either GetFontTable or "
+ "CopyFontTable?");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Helper for HasTrackingTable; check/parse the table and cache pointers
+ // to the subtables we need. Returns false on failure, in which case the
+ // table is unusable.
+ bool ParseTrakTable() MOZ_REQUIRES(mLock);
+
+ // lookup the cmap in cached font data
+ virtual already_AddRefed<gfxCharacterMap> GetCMAPFromFontInfo(
+ FontInfoData* aFontInfoData, uint32_t& aUVSOffset);
+
+ // helper for HasCharacter(), which is what client code should call
+ virtual bool TestCharacterMap(uint32_t aCh);
+
+ // Try to set mShmemCharacterMap, based on the char map in mShmemFace;
+ // return true if successful, false if it remains null (maybe the parent
+ // hasn't handled our SetCharacterMap message yet).
+ bool TrySetShmemCharacterMap();
+
+ // Helper for gfxPlatformFontList::CreateFontEntry methods: set properties
+ // of the gfxFontEntry based on shared Face and Family records.
+ void InitializeFrom(mozilla::fontlist::Face* aFace,
+ const mozilla::fontlist::Family* aFamily);
+
+ // Shaper-specific face objects, shared by all instantiations of the same
+ // physical font, regardless of size.
+ // Usually, only one of these will actually be created for any given font
+ // entry, depending on the font tables that are present.
+
+ // hb_face_t is refcounted internally, so each shaper that's using it will
+ // bump the ref count when it acquires the face, and "destroy" (release) it
+ // in its destructor. The font entry has only this non-owning reference to
+ // the face; when the face is deleted, it will tell the font entry to forget
+ // it, so that a new face will be created next time it is needed.
+ mozilla::Atomic<hb_face_t*> mHBFace;
+
+ static hb_blob_t* HBGetTable(hb_face_t* face, uint32_t aTag, void* aUserData);
+
+ // Callback that the hb_face will use to tell us when it is being deleted.
+ static void HBFaceDeletedCallback(void* aUserData);
+
+ // All libGraphite functionality is sandboxed in an rlbox sandbox. This
+ // contains data for the sandbox instance.
+ // Currently graphite shaping is only supported on the main thread.
+ struct GrSandboxData;
+ GrSandboxData* mSandboxData = nullptr;
+
+ // gr_face is -not- refcounted, so it will be owned directly by the font
+ // entry, and we'll keep a count of how many references we've handed out;
+ // each shaper is responsible to call ReleaseGrFace on its entry when
+ // finished with it, so that we know when it can be deleted.
+ tainted_opaque_gr<gr_face*> mGrFace;
+
+ // For AAT font, a strong reference to the 'trak' table (if present).
+ hb_blob_t* const kTrakTableUninitialized = (hb_blob_t*)(intptr_t(-1));
+ mozilla::Atomic<hb_blob_t*> mTrakTable;
+ hb_blob_t* GetTrakTable() const { return mTrakTable; }
+ bool TrakTableInitialized() const {
+ return mTrakTable != kTrakTableUninitialized;
+ }
+
+ // Cached pointers to tables within 'trak', initialized by ParseTrakTable.
+ // This data is inert once loaded, so locking is not required to read it.
+ const mozilla::AutoSwap_PRInt16* mTrakValues = nullptr;
+ const mozilla::AutoSwap_PRInt32* mTrakSizeTable = nullptr;
+
+ // number of current users of this entry's mGrFace
+ nsrefcnt mGrFaceRefCnt = 0;
+
+ friend class gfxFontEntryCallbacks;
+
+ // For memory reporting: size of user-font data belonging to this entry.
+ // We record this in the font entry because the actual data block may be
+ // handed over to platform APIs, so that it would become difficult (and
+ // platform-specific) to measure it directly at report-gathering time.
+ uint32_t mComputedSizeOfUserFont = 0;
+
+ // Font's unitsPerEm from the 'head' table, if available (will be set to
+ // kInvalidUPEM for non-sfnt font formats)
+ uint16_t mUnitsPerEm = 0;
+
+ uint16_t mNumTrakSizes = 0;
+
+ // Font extents in FUnits. (To be set from the 'head' table; default to
+ // "huge" to avoid any clipping if real extents not available.)
+ int16_t mXMin = std::numeric_limits<int16_t>::min();
+ int16_t mYMin = std::numeric_limits<int16_t>::min();
+ int16_t mXMax = std::numeric_limits<int16_t>::max();
+ int16_t mYMax = std::numeric_limits<int16_t>::max();
+
+ private:
+ /**
+ * Font table hashtable, to support GetFontTable for harfbuzz.
+ *
+ * The harfbuzz shaper (and potentially other clients) needs access to raw
+ * font table data. This needs to be cached so that it can be used
+ * repeatedly (each time we construct a text run; in some cases, for
+ * each character/glyph within the run) without re-fetching large tables
+ * every time.
+ *
+ * Because we may instantiate many gfxFonts for the same physical font
+ * file (at different sizes), we should ensure that they can share a
+ * single cached copy of the font tables. To do this, we implement table
+ * access and sharing on the fontEntry rather than the font itself.
+ *
+ * The default implementation uses GetFontTable() to read font table
+ * data into byte arrays, and wraps them in blobs which are registered in
+ * a hashtable. The hashtable can then return pre-existing blobs to
+ * harfbuzz.
+ *
+ * Harfbuzz will "destroy" the blobs when it is finished with them. When
+ * the last blob reference is removed, the FontTableBlobData user data
+ * will remove the blob from the hashtable if still registered.
+ */
+
+ class FontTableBlobData;
+
+ /**
+ * FontTableHashEntry manages the entries of hb_blob_t's containing font
+ * table data.
+ *
+ * This is used to share font tables across fonts with the same
+ * font entry (but different sizes) for use by HarfBuzz. The hashtable
+ * does not own a strong reference to the blob, but keeps a weak pointer,
+ * managed by FontTableBlobData. Similarly FontTableBlobData keeps only a
+ * weak pointer to the hashtable, managed by FontTableHashEntry.
+ */
+
+ class FontTableHashEntry : public nsUint32HashKey {
+ public:
+ // Declarations for nsTHashtable
+
+ typedef nsUint32HashKey KeyClass;
+ typedef KeyClass::KeyType KeyType;
+ typedef KeyClass::KeyTypePointer KeyTypePointer;
+
+ explicit FontTableHashEntry(KeyTypePointer aTag)
+ : KeyClass(aTag), mSharedBlobData(nullptr), mBlob(nullptr) {}
+
+ // NOTE: This assumes the new entry belongs to the same hashtable as
+ // the old, because the mHashtable pointer in mSharedBlobData (if
+ // present) will not be updated.
+ FontTableHashEntry(FontTableHashEntry&& toMove)
+ : KeyClass(std::move(toMove)),
+ mSharedBlobData(std::move(toMove.mSharedBlobData)),
+ mBlob(std::move(toMove.mBlob)) {
+ toMove.mSharedBlobData = nullptr;
+ toMove.mBlob = nullptr;
+ }
+
+ ~FontTableHashEntry() { Clear(); }
+
+ // FontTable/Blob API
+
+ // Transfer (not copy) elements of aTable to a new hb_blob_t and
+ // return ownership to the caller. A weak reference to the blob is
+ // recorded in the hashtable entry so that others may use the same
+ // table.
+ hb_blob_t* ShareTableAndGetBlob(
+ nsTArray<uint8_t>&& aTable,
+ nsTHashtable<FontTableHashEntry>* aHashtable);
+
+ // Return a strong reference to the blob.
+ // Callers must hb_blob_destroy the returned blob.
+ hb_blob_t* GetBlob() const;
+
+ void Clear();
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ static void DeleteFontTableBlobData(void* aBlobData);
+ // not implemented
+ FontTableHashEntry& operator=(FontTableHashEntry& toCopy);
+
+ FontTableBlobData* mSharedBlobData;
+ hb_blob_t* mBlob;
+ };
+
+ using FontTableCache = nsTHashtable<FontTableHashEntry>;
+ mozilla::Atomic<FontTableCache*> mFontTableCache;
+ FontTableCache* GetFontTableCache() const { return mFontTableCache; }
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(gfxFontEntry::RangeFlags)
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(gfxFontEntry::SpaceFeatures)
+
+inline bool gfxFontEntry::SupportsItalic() {
+ return SlantStyle().Max().IsItalic() ||
+ ((mRangeFlags & RangeFlags::eAutoSlantStyle) ==
+ RangeFlags::eAutoSlantStyle &&
+ HasItalicVariation());
+}
+
+inline bool gfxFontEntry::SupportsBold() {
+ // bold == weights 600 and above
+ // We return true if the face has a max weight descriptor >= 600,
+ // OR if it's a user font with auto-weight (no descriptor) and has
+ // a weight axis that supports values >= 600
+ return Weight().Max().IsBold() ||
+ ((mRangeFlags & RangeFlags::eAutoWeight) == RangeFlags::eAutoWeight &&
+ HasBoldVariableWeight());
+}
+
+inline bool gfxFontEntry::MayUseSyntheticSlant() {
+ if (!IsUpright()) {
+ return false; // The resource is already non-upright.
+ }
+ if (HasSlantVariation()) {
+ if (mRangeFlags & RangeFlags::eAutoSlantStyle) {
+ return false;
+ }
+ if (!SlantStyle().IsSingle()) {
+ return false; // The resource has a 'slnt' axis, and has not been
+ // clamped to just its upright setting.
+ }
+ }
+ return true;
+}
+
+// used when iterating over all fonts looking for a match for a given character
+struct GlobalFontMatch {
+ GlobalFontMatch(uint32_t aCharacter, uint32_t aNextCh,
+ const gfxFontStyle& aStyle, eFontPresentation aPresentation)
+ : mStyle(aStyle),
+ mCh(aCharacter),
+ mNextCh(aNextCh),
+ mPresentation(aPresentation) {}
+
+ RefPtr<gfxFontEntry> mBestMatch; // current best match
+ RefPtr<gfxFontFamily> mMatchedFamily; // the family it belongs to
+ mozilla::fontlist::Family* mMatchedSharedFamily = nullptr;
+ const gfxFontStyle& mStyle; // style to match
+ const uint32_t mCh; // codepoint to be matched
+ const uint32_t mNextCh; // following codepoint (or zero)
+ eFontPresentation mPresentation;
+ uint32_t mCount = 0; // number of fonts matched
+ uint32_t mCmapsTested = 0; // number of cmaps tested
+ double mMatchDistance = INFINITY; // metric indicating closest match
+};
+
+class gfxFontFamily {
+ public:
+ // Used by stylo
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxFontFamily)
+
+ gfxFontFamily(const nsACString& aName, FontVisibility aVisibility)
+ : mName(aName),
+ mLock("gfxFontFamily lock"),
+ mVisibility(aVisibility),
+ mIsSimpleFamily(false),
+ mIsBadUnderlineFamily(false),
+ mSkipDefaultFeatureSpaceCheck(false),
+ mCheckForFallbackFaces(false) {}
+
+ const nsCString& Name() const { return mName; }
+
+ virtual void LocalizedName(nsACString& aLocalizedName);
+ virtual bool HasOtherFamilyNames();
+
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=835204:
+ // check the font's 'name' table to see if it has a legacy family name
+ // that would have been used by GDI (e.g. to split extra-bold or light
+ // faces in a large family into separate "styled families" because of
+ // GDI's 4-faces-per-family limitation). If found, the styled family
+ // name will be added to the font list's "other family names" table.
+ // Note that the caller must already hold the gfxPlatformFontList lock.
+ bool CheckForLegacyFamilyNames(gfxPlatformFontList* aFontList);
+
+ // Callers must hold a read-lock for as long as they're using the list.
+ const nsTArray<RefPtr<gfxFontEntry>>& GetFontList()
+ MOZ_REQUIRES_SHARED(mLock) {
+ return mAvailableFonts;
+ }
+ void ReadLock() MOZ_ACQUIRE_SHARED(mLock) { mLock.ReadLock(); }
+ void ReadUnlock() MOZ_RELEASE_SHARED(mLock) { mLock.ReadUnlock(); }
+
+ uint32_t FontListLength() const {
+ mozilla::AutoReadLock lock(mLock);
+ return mAvailableFonts.Length();
+ }
+
+ void AddFontEntry(RefPtr<gfxFontEntry> aFontEntry) {
+ mozilla::AutoWriteLock lock(mLock);
+ AddFontEntryLocked(aFontEntry);
+ }
+
+ void AddFontEntryLocked(RefPtr<gfxFontEntry> aFontEntry) MOZ_REQUIRES(mLock) {
+ // Avoid potentially duplicating entries.
+ if (mAvailableFonts.Contains(aFontEntry)) {
+ return;
+ }
+ // bug 589682 - set the IgnoreGDEF flag on entries for Italic faces
+ // of Times New Roman, because of buggy table in those fonts
+ if (aFontEntry->IsItalic() && !aFontEntry->IsUserFont() &&
+ Name().EqualsLiteral("Times New Roman")) {
+ aFontEntry->mIgnoreGDEF = true;
+ }
+ if (aFontEntry->mFamilyName.IsEmpty()) {
+ aFontEntry->mFamilyName = Name();
+ } else {
+ MOZ_ASSERT(aFontEntry->mFamilyName.Equals(Name()));
+ }
+ aFontEntry->mSkipDefaultFeatureSpaceCheck = mSkipDefaultFeatureSpaceCheck;
+ mAvailableFonts.AppendElement(aFontEntry);
+
+ // If we're adding a face to a family that has been marked as "simple",
+ // we need to ensure any null entries are removed, as well as clearing
+ // the flag (which may be set again later).
+ if (mIsSimpleFamily) {
+ mAvailableFonts.RemoveElementsBy([](const auto& font) { return !font; });
+ mIsSimpleFamily = false;
+ }
+ }
+
+ // note that the styles for this family have been added
+ bool HasStyles() const { return mHasStyles; }
+ void SetHasStyles(bool aHasStyles) { mHasStyles = aHasStyles; }
+
+ void SetCheckedForLegacyFamilyNames(bool aChecked) {
+ mCheckedForLegacyFamilyNames = aChecked;
+ }
+
+ // choose a specific face to match a style using CSS font matching
+ // rules (weight matching occurs here). may return a face that doesn't
+ // precisely match (e.g. normal face when no italic face exists).
+ gfxFontEntry* FindFontForStyle(const gfxFontStyle& aFontStyle,
+ bool aIgnoreSizeTolerance = false);
+
+ virtual void FindAllFontsForStyle(const gfxFontStyle& aFontStyle,
+ nsTArray<gfxFontEntry*>& aFontEntryList,
+ bool aIgnoreSizeTolerance = false);
+
+ // Checks for a matching font within the family; used as part of the font
+ // fallback process.
+ // Note that when this is called, the caller must already be holding the
+ // gfxPlatformFontList lock.
+ void FindFontForChar(GlobalFontMatch* aMatchData);
+
+ // checks all fonts for a matching font within the family
+ void SearchAllFontsForChar(GlobalFontMatch* aMatchData);
+
+ // read in other family names, if any, and use functor to add each into cache
+ virtual void ReadOtherFamilyNames(gfxPlatformFontList* aPlatformFontList);
+
+ // set when other family names have been read in
+ void SetOtherFamilyNamesInitialized() { mOtherFamilyNamesInitialized = true; }
+
+ // Read in other localized family names, fullnames and Postscript names
+ // for all faces and append to lookup tables.
+ // Note that when this is called, the caller must already be holding the
+ // gfxPlatformFontList lock.
+ virtual void ReadFaceNames(gfxPlatformFontList* aPlatformFontList,
+ bool aNeedFullnamePostscriptNames,
+ FontInfoData* aFontInfoData = nullptr);
+
+ // Find faces belonging to this family (platform implementations override).
+ // This is a no-op in cases where the family is explicitly populated by other
+ // means, rather than being asked to find its faces via system API.
+ virtual void FindStyleVariationsLocked(FontInfoData* aFontInfoData = nullptr)
+ MOZ_REQUIRES(mLock){};
+ void FindStyleVariations(FontInfoData* aFontInfoData = nullptr) {
+ if (mHasStyles) {
+ return;
+ }
+ mozilla::AutoWriteLock lock(mLock);
+ FindStyleVariationsLocked(aFontInfoData);
+ }
+
+ // search for a specific face using the Postscript name
+ gfxFontEntry* FindFont(const nsACString& aFontName,
+ const nsCStringComparator& aCmp) const;
+
+ // Read in cmaps for all the faces.
+ // Note that when this is called, the caller must already be holding the
+ // gfxPlatformFontList lock.
+ void ReadAllCMAPs(FontInfoData* aFontInfoData = nullptr);
+
+ bool TestCharacterMap(uint32_t aCh) {
+ if (!mFamilyCharacterMapInitialized) {
+ ReadAllCMAPs();
+ }
+ mozilla::AutoReadLock lock(mLock);
+ return mFamilyCharacterMap.test(aCh);
+ }
+
+ void ResetCharacterMap() MOZ_REQUIRES(mLock) {
+ mFamilyCharacterMap.reset();
+ mFamilyCharacterMapInitialized = false;
+ }
+
+ // mark this family as being in the "bad" underline offset blocklist
+ void SetBadUnderlineFamily() {
+ mozilla::AutoWriteLock lock(mLock);
+ mIsBadUnderlineFamily = true;
+ if (mHasStyles) {
+ SetBadUnderlineFonts();
+ }
+ }
+
+ virtual bool IsSingleFaceFamily() const { return false; }
+
+ bool IsBadUnderlineFamily() const { return mIsBadUnderlineFamily; }
+ bool CheckForFallbackFaces() const { return mCheckForFallbackFaces; }
+
+ // sort available fonts to put preferred (standard) faces towards the end
+ void SortAvailableFonts() MOZ_REQUIRES(mLock);
+
+ // check whether the family fits into the simple 4-face model,
+ // so we can use simplified style-matching;
+ // if so set the mIsSimpleFamily flag (defaults to False before we've checked)
+ void CheckForSimpleFamily() MOZ_REQUIRES(mLock);
+
+ // For memory reporter
+ virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const;
+ virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const;
+
+#ifdef DEBUG
+ // Only used for debugging checks - does a linear search
+ bool ContainsFace(gfxFontEntry* aFontEntry);
+#endif
+
+ void SetSkipSpaceFeatureCheck(bool aSkipCheck) {
+ mSkipDefaultFeatureSpaceCheck = aSkipCheck;
+ }
+
+ // Check whether this family is appropriate to include in the Preferences
+ // font list for the given langGroup and CSS generic, if the platform lets
+ // us determine this.
+ // Return true if the family should be included in the list, false to omit.
+ // Default implementation returns true for everything, so no filtering
+ // will occur; individual platforms may override.
+ virtual bool FilterForFontList(nsAtom* aLangGroup,
+ const nsACString& aGeneric) const {
+ return true;
+ }
+
+ FontVisibility Visibility() const { return mVisibility; }
+ bool IsHidden() const { return Visibility() == FontVisibility::Hidden; }
+ bool IsWebFontFamily() const {
+ return Visibility() == FontVisibility::Webfont;
+ }
+
+ protected:
+ // Protected destructor, to discourage deletion outside of Release():
+ virtual ~gfxFontFamily();
+
+ bool ReadOtherFamilyNamesForFace(gfxPlatformFontList* aPlatformFontList,
+ hb_blob_t* aNameTable,
+ bool useFullName = false);
+
+ // set whether this font family is in "bad" underline offset blocklist.
+ void SetBadUnderlineFonts() MOZ_REQUIRES(mLock) {
+ for (auto& f : mAvailableFonts) {
+ if (f) {
+ f->mIsBadUnderlineFont = true;
+ }
+ }
+ }
+
+ nsCString mName;
+ nsTArray<RefPtr<gfxFontEntry>> mAvailableFonts MOZ_GUARDED_BY(mLock);
+ gfxSparseBitSet mFamilyCharacterMap MOZ_GUARDED_BY(mLock);
+
+ mutable mozilla::RWLock mLock;
+
+ FontVisibility mVisibility;
+
+ mozilla::Atomic<bool> mOtherFamilyNamesInitialized;
+ mozilla::Atomic<bool> mFaceNamesInitialized;
+ mozilla::Atomic<bool> mHasStyles;
+ mozilla::Atomic<bool> mFamilyCharacterMapInitialized;
+ mozilla::Atomic<bool> mCheckedForLegacyFamilyNames;
+ mozilla::Atomic<bool> mHasOtherFamilyNames;
+
+ bool mIsSimpleFamily : 1 MOZ_GUARDED_BY(mLock);
+ bool mIsBadUnderlineFamily : 1;
+ bool mSkipDefaultFeatureSpaceCheck : 1;
+ bool mCheckForFallbackFaces : 1; // check other faces for character
+
+ enum {
+ // for "simple" families, the faces are stored in mAvailableFonts
+ // with fixed positions:
+ kRegularFaceIndex = 0,
+ kBoldFaceIndex = 1,
+ kItalicFaceIndex = 2,
+ kBoldItalicFaceIndex = 3,
+ // mask values for selecting face with bold and/or italic attributes
+ kBoldMask = 0x01,
+ kItalicMask = 0x02
+ };
+};
+
+// Wrapper for either a raw pointer to a mozilla::fontlist::Family in the shared
+// font list or a strong pointer to an unshared gfxFontFamily that belongs just
+// to the current process.
+struct FontFamily {
+ FontFamily() = default;
+ FontFamily(const FontFamily& aOther) = default;
+
+ explicit FontFamily(RefPtr<gfxFontFamily>&& aFamily)
+ : mUnshared(std::move(aFamily)) {}
+
+ explicit FontFamily(gfxFontFamily* aFamily) : mUnshared(aFamily) {}
+
+ explicit FontFamily(mozilla::fontlist::Family* aFamily) : mShared(aFamily) {}
+
+ bool operator==(const FontFamily& aOther) const {
+ return mShared == aOther.mShared && mUnshared == aOther.mUnshared;
+ }
+
+ bool IsNull() const { return !mShared && !mUnshared; }
+
+ RefPtr<gfxFontFamily> mUnshared;
+ mozilla::fontlist::Family* mShared = nullptr;
+};
+
+// Struct used in the gfxFontGroup font list to keep track of a font family
+// together with the CSS generic (if any) that was mapped to it in this
+// particular case (so it can be reported to the DevTools font inspector).
+struct FamilyAndGeneric final {
+ FamilyAndGeneric()
+ : mFamily(), mGeneric(mozilla::StyleGenericFontFamily(0)) {}
+ FamilyAndGeneric(const FamilyAndGeneric& aOther) = default;
+ explicit FamilyAndGeneric(gfxFontFamily* aFamily,
+ mozilla::StyleGenericFontFamily aGeneric =
+ mozilla::StyleGenericFontFamily(0))
+ : mFamily(aFamily), mGeneric(aGeneric) {}
+ explicit FamilyAndGeneric(RefPtr<gfxFontFamily>&& aFamily,
+ mozilla::StyleGenericFontFamily aGeneric =
+ mozilla::StyleGenericFontFamily(0))
+ : mFamily(std::move(aFamily)), mGeneric(aGeneric) {}
+ explicit FamilyAndGeneric(mozilla::fontlist::Family* aFamily,
+ mozilla::StyleGenericFontFamily aGeneric =
+ mozilla::StyleGenericFontFamily(0))
+ : mFamily(aFamily), mGeneric(aGeneric) {}
+ explicit FamilyAndGeneric(const FontFamily& aFamily,
+ mozilla::StyleGenericFontFamily aGeneric =
+ mozilla::StyleGenericFontFamily(0))
+ : mFamily(aFamily), mGeneric(aGeneric) {}
+
+ bool operator==(const FamilyAndGeneric& aOther) const {
+ return mFamily == aOther.mFamily && mGeneric == aOther.mGeneric;
+ }
+
+ FontFamily mFamily;
+ mozilla::StyleGenericFontFamily mGeneric;
+};
+
+#endif
diff --git a/gfx/thebes/gfxFontFeatures.cpp b/gfx/thebes/gfxFontFeatures.cpp
new file mode 100644
index 0000000000..4d1d82927a
--- /dev/null
+++ b/gfx/thebes/gfxFontFeatures.cpp
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxFontFeatures.h"
+#include "nsAtom.h"
+#include "nsUnicharUtils.h"
+#include "nsHashKeys.h"
+
+using namespace mozilla;
+
+gfxFontFeatureValueSet::gfxFontFeatureValueSet() : mFontFeatureValues(8) {}
+
+Span<const uint32_t> gfxFontFeatureValueSet::GetFontFeatureValuesFor(
+ const nsACString& aFamily, uint32_t aVariantProperty, nsAtom* aName) const {
+ nsAutoCString family(aFamily);
+ ToLowerCase(family);
+ FeatureValueHashKey key(family, aVariantProperty, aName);
+ FeatureValueHashEntry* entry = mFontFeatureValues.GetEntry(key);
+ if (!entry) {
+ return {};
+ }
+ NS_ASSERTION(entry->mValues.Length() > 0,
+ "null array of font feature values");
+ return {entry->mValues};
+}
+
+nsTArray<uint32_t>* gfxFontFeatureValueSet::AppendFeatureValueHashEntry(
+ const nsACString& aFamily, nsAtom* aName, uint32_t aAlternate) {
+ FeatureValueHashKey key(aFamily, aAlternate, aName);
+ FeatureValueHashEntry* entry = mFontFeatureValues.PutEntry(key);
+ entry->mKey = key;
+ return &entry->mValues;
+}
+
+bool gfxFontFeatureValueSet::FeatureValueHashEntry::KeyEquals(
+ const KeyTypePointer aKey) const {
+ return aKey->mPropVal == mKey.mPropVal && aKey->mName == mKey.mName &&
+ aKey->mFamily.Equals(mKey.mFamily);
+}
+
+PLDHashNumber gfxFontFeatureValueSet::FeatureValueHashEntry::HashKey(
+ const KeyTypePointer aKey) {
+ return HashString(aKey->mFamily) + aKey->mName->hash() +
+ aKey->mPropVal * uint32_t(0xdeadbeef);
+}
diff --git a/gfx/thebes/gfxFontFeatures.h b/gfx/thebes/gfxFontFeatures.h
new file mode 100644
index 0000000000..24ece02d36
--- /dev/null
+++ b/gfx/thebes/gfxFontFeatures.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_FONT_FEATURES_H
+#define GFX_FONT_FEATURES_H
+
+#include "nsAtom.h"
+#include "nsTHashtable.h"
+#include "nsTArray.h"
+#include "nsString.h"
+
+// An OpenType feature tag and value pair
+struct gfxFontFeature {
+ uint32_t
+ mTag; // see http://www.microsoft.com/typography/otspec/featuretags.htm
+ uint32_t mValue; // 0 = off, 1 = on, larger values may be used as parameters
+ // to features that select among multiple alternatives
+};
+
+inline bool operator<(const gfxFontFeature& a, const gfxFontFeature& b) {
+ return (a.mTag < b.mTag) || ((a.mTag == b.mTag) && (a.mValue < b.mValue));
+}
+
+inline bool operator==(const gfxFontFeature& a, const gfxFontFeature& b) {
+ return (a.mTag == b.mTag) && (a.mValue == b.mValue);
+}
+
+class nsAtom;
+
+class gfxFontFeatureValueSet final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxFontFeatureValueSet)
+
+ gfxFontFeatureValueSet();
+
+ struct ValueList {
+ ValueList(const nsAString& aName, const nsTArray<uint32_t>& aSelectors)
+ : name(aName), featureSelectors(aSelectors.Clone()) {}
+ nsString name;
+ nsTArray<uint32_t> featureSelectors;
+ };
+
+ struct FeatureValues {
+ uint32_t alternate;
+ nsTArray<ValueList> valuelist;
+ };
+
+ mozilla::Span<const uint32_t> GetFontFeatureValuesFor(
+ const nsACString& aFamily, uint32_t aVariantProperty,
+ nsAtom* aName) const;
+
+ // Appends a new hash entry with given key values and returns a pointer to
+ // mValues array to fill. This should be filled first.
+ nsTArray<uint32_t>* AppendFeatureValueHashEntry(const nsACString& aFamily,
+ nsAtom* aName,
+ uint32_t aAlternate);
+
+ private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~gfxFontFeatureValueSet() = default;
+
+ struct FeatureValueHashKey {
+ nsCString mFamily;
+ uint32_t mPropVal;
+ RefPtr<nsAtom> mName;
+
+ FeatureValueHashKey() : mPropVal(0) {}
+ FeatureValueHashKey(const nsACString& aFamily, uint32_t aPropVal,
+ nsAtom* aName)
+ : mFamily(aFamily), mPropVal(aPropVal), mName(aName) {}
+ FeatureValueHashKey(const FeatureValueHashKey& aKey) = default;
+ };
+
+ class FeatureValueHashEntry : public PLDHashEntryHdr {
+ public:
+ typedef const FeatureValueHashKey& KeyType;
+ typedef const FeatureValueHashKey* KeyTypePointer;
+
+ explicit FeatureValueHashEntry(KeyTypePointer aKey) {}
+ FeatureValueHashEntry(FeatureValueHashEntry&& other)
+ : PLDHashEntryHdr(std::move(other)),
+ mKey(std::move(other.mKey)),
+ mValues(std::move(other.mValues)) {
+ NS_ERROR("Should not be called");
+ }
+ ~FeatureValueHashEntry() = default;
+
+ bool KeyEquals(const KeyTypePointer aKey) const;
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(const KeyTypePointer aKey);
+ enum { ALLOW_MEMMOVE = true };
+
+ FeatureValueHashKey mKey;
+ nsTArray<uint32_t> mValues;
+ };
+
+ nsTHashtable<FeatureValueHashEntry> mFontFeatureValues;
+};
+
+#endif
diff --git a/gfx/thebes/gfxFontInfoLoader.cpp b/gfx/thebes/gfxFontInfoLoader.cpp
new file mode 100644
index 0000000000..41cb519164
--- /dev/null
+++ b/gfx/thebes/gfxFontInfoLoader.cpp
@@ -0,0 +1,318 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxFontInfoLoader.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/AppShutdown.h"
+#include "nsCRT.h"
+#include "nsIObserverService.h"
+#include "nsThreadUtils.h" // for nsRunnable
+#include "gfxPlatformFontList.h"
+
+#ifdef XP_WIN
+# include <windows.h>
+#endif
+
+using namespace mozilla;
+using services::GetObserverService;
+
+#define LOG_FONTINIT(args) \
+ MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug, args)
+#define LOG_FONTINIT_ENABLED() \
+ MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug)
+
+void FontInfoData::Load() {
+ TimeStamp start = TimeStamp::Now();
+
+ uint32_t i, n = mFontFamiliesToLoad.Length();
+ mLoadStats.families = n;
+ for (i = 0; i < n && !mCanceled; i++) {
+ // font file memory mapping sometimes causes exceptions - bug 1100949
+ MOZ_SEH_TRY { LoadFontFamilyData(mFontFamiliesToLoad[i]); }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ gfxCriticalError() << "Exception occurred reading font data for "
+ << mFontFamiliesToLoad[i].get();
+ }
+ }
+
+ mLoadTime = TimeStamp::Now() - start;
+}
+
+class FontInfoLoadCompleteEvent : public Runnable {
+ virtual ~FontInfoLoadCompleteEvent() = default;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(FontInfoLoadCompleteEvent, Runnable)
+
+ explicit FontInfoLoadCompleteEvent(FontInfoData* aFontInfo)
+ : mozilla::Runnable("FontInfoLoadCompleteEvent"), mFontInfo(aFontInfo) {}
+
+ NS_IMETHOD Run() override;
+
+ private:
+ RefPtr<FontInfoData> mFontInfo;
+};
+
+class AsyncFontInfoLoader : public Runnable {
+ virtual ~AsyncFontInfoLoader() = default;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(AsyncFontInfoLoader, Runnable)
+
+ explicit AsyncFontInfoLoader(FontInfoData* aFontInfo)
+ : mozilla::Runnable("AsyncFontInfoLoader"), mFontInfo(aFontInfo) {
+ mCompleteEvent = new FontInfoLoadCompleteEvent(aFontInfo);
+ }
+
+ NS_IMETHOD Run() override;
+
+ private:
+ RefPtr<FontInfoData> mFontInfo;
+ RefPtr<FontInfoLoadCompleteEvent> mCompleteEvent;
+};
+
+class ShutdownThreadEvent : public Runnable {
+ virtual ~ShutdownThreadEvent() = default;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(ShutdownThreadEvent, Runnable)
+
+ explicit ShutdownThreadEvent(nsIThread* aThread)
+ : mozilla::Runnable("ShutdownThreadEvent"), mThread(aThread) {}
+ NS_IMETHOD Run() override {
+ mThread->Shutdown();
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIThread> mThread;
+};
+
+// runs on main thread after async font info loading is done
+nsresult FontInfoLoadCompleteEvent::Run() {
+ gfxFontInfoLoader* loader =
+ static_cast<gfxFontInfoLoader*>(gfxPlatformFontList::PlatformFontList());
+
+ loader->FinalizeLoader(mFontInfo);
+
+ return NS_OK;
+}
+
+// runs on separate thread
+nsresult AsyncFontInfoLoader::Run() {
+ // load platform-specific font info
+ mFontInfo->Load();
+
+ // post a completion event that transfer the data to the fontlist
+ NS_DispatchToMainThread(mCompleteEvent);
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(gfxFontInfoLoader::ShutdownObserver, nsIObserver)
+
+NS_IMETHODIMP
+gfxFontInfoLoader::ShutdownObserver::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* someData) {
+ if (!nsCRT::strcmp(aTopic, "quit-application") ||
+ !nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
+ mLoader->CancelLoader();
+ } else {
+ MOZ_ASSERT_UNREACHABLE("unexpected notification topic");
+ }
+ return NS_OK;
+}
+
+// StartLoader is usually called at startup with a (prefs-derived) delay value,
+// so that the async loader runs shortly after startup, to avoid competing for
+// disk i/o etc with other more critical operations.
+// However, it may be called with aDelay=0 if we find that the font info (e.g.
+// localized names) is needed for layout. In this case we start the loader
+// immediately; however, it is still an async process and we may use fallback
+// fonts to satisfy layout until it completes.
+void gfxFontInfoLoader::StartLoader(uint32_t aDelay) {
+ if (aDelay == 0 && (mState == stateTimerOff || mState == stateAsyncLoad)) {
+ // We were asked to load (async) without delay, but have already started,
+ // so just return and let the loader proceed.
+ return;
+ }
+
+ // We observe for "quit-application" above, so avoid initialization after it.
+ if (NS_WARN_IF(AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown))) {
+ MOZ_ASSERT(!aDelay, "Delayed gfxFontInfoLoader startup after AppShutdown?");
+ return;
+ }
+
+ // sanity check
+ if (mState != stateInitial && mState != stateTimerOff &&
+ mState != stateTimerOnDelay) {
+ CancelLoader();
+ }
+
+ // Create mFontInfo when we're initially called to set up the delay, rather
+ // than when called by the DelayedStartCallback, because on the initial call
+ // we know we'll be holding the gfxPlatformFontList lock.
+ if (!mFontInfo) {
+ mFontInfo = CreateFontInfoData();
+ if (!mFontInfo) {
+ // The platform doesn't want anything loaded, so just bail out.
+ mState = stateTimerOff;
+ return;
+ }
+ }
+
+ AddShutdownObserver();
+
+ // Caller asked for a delay? ==> start async thread after a delay
+ if (aDelay) {
+ // Set up delay timer, or if there is already a timer in place, just
+ // leave it to do its thing. (This can happen if a StartLoader runnable
+ // was posted to the main thread from the InitFontList thread, but then
+ // before it had a chance to run and call StartLoader, the main thread
+ // re-initialized the list due to a platform notification and called
+ // StartLoader directly.)
+ if (mTimer) {
+ return;
+ }
+ mTimer = NS_NewTimer();
+ mTimer->InitWithNamedFuncCallback(DelayedStartCallback, this, aDelay,
+ nsITimer::TYPE_ONE_SHOT,
+ "gfxFontInfoLoader::StartLoader");
+ mState = stateTimerOnDelay;
+ return;
+ }
+
+ // Either we've been called back by the DelayedStartCallback when its timer
+ // fired, or a layout caller has passed aDelay=0 to ask the loader to run
+ // without further delay.
+
+ // Cancel the delay timer, if any.
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ // initialize
+ InitLoader();
+
+ // start async load
+ nsresult rv = NS_NewNamedThread("Font Loader",
+ getter_AddRefs(mFontLoaderThread), nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ PRThread* prThread;
+ if (NS_SUCCEEDED(mFontLoaderThread->GetPRThread(&prThread))) {
+ PR_SetThreadPriority(prThread, PR_PRIORITY_LOW);
+ }
+
+ mState = stateAsyncLoad;
+
+ nsCOMPtr<nsIRunnable> loadEvent = new AsyncFontInfoLoader(mFontInfo);
+
+ mFontLoaderThread->Dispatch(loadEvent.forget(), NS_DISPATCH_NORMAL);
+
+ if (LOG_FONTINIT_ENABLED()) {
+ LOG_FONTINIT(
+ ("(fontinit) fontloader started (fontinfo: %p)\n", mFontInfo.get()));
+ }
+}
+
+class FinalizeLoaderRunnable : public Runnable {
+ virtual ~FinalizeLoaderRunnable() = default;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(FinalizeLoaderRunnable, Runnable)
+
+ explicit FinalizeLoaderRunnable(gfxFontInfoLoader* aLoader)
+ : mozilla::Runnable("FinalizeLoaderRunnable"), mLoader(aLoader) {}
+
+ NS_IMETHOD Run() override {
+ nsresult rv;
+ if (mLoader->LoadFontInfo()) {
+ mLoader->CancelLoader();
+ rv = NS_OK;
+ } else {
+ nsCOMPtr<nsIRunnable> runnable = this;
+ rv = NS_DispatchToCurrentThreadQueue(
+ runnable.forget(), PR_INTERVAL_NO_TIMEOUT, EventQueuePriority::Idle);
+ }
+ return rv;
+ }
+
+ private:
+ gfxFontInfoLoader* mLoader;
+};
+
+void gfxFontInfoLoader::FinalizeLoader(FontInfoData* aFontInfo) {
+ // Avoid loading data if loader has already been canceled.
+ // This should mean that CancelLoader() ran and the Load
+ // thread has already Shutdown(), and likely before processing
+ // the Shutdown event it handled the load event and sent back
+ // our Completion event, thus we end up here.
+ if (mState != stateAsyncLoad || mFontInfo != aFontInfo) {
+ return;
+ }
+
+ mLoadTime = mFontInfo->mLoadTime;
+
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIRunnable> runnable = new FinalizeLoaderRunnable(this);
+ if (NS_FAILED(NS_DispatchToCurrentThreadQueue(runnable.forget(),
+ PR_INTERVAL_NO_TIMEOUT,
+ EventQueuePriority::Idle))) {
+ NS_WARNING("Failed to finalize async font info");
+ }
+}
+
+void gfxFontInfoLoader::CancelLoader() {
+ if (mState == stateInitial) {
+ return;
+ }
+ mState = stateTimerOff;
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ if (mFontInfo) // null during any initial delay
+ mFontInfo->mCanceled = true;
+ if (mFontLoaderThread) {
+ NS_DispatchToMainThread(new ShutdownThreadEvent(mFontLoaderThread));
+ mFontLoaderThread = nullptr;
+ }
+ RemoveShutdownObserver();
+ CleanupLoader();
+}
+
+gfxFontInfoLoader::~gfxFontInfoLoader() {
+ RemoveShutdownObserver();
+ MOZ_COUNT_DTOR(gfxFontInfoLoader);
+}
+
+void gfxFontInfoLoader::AddShutdownObserver() {
+ if (mObserver) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ if (obs) {
+ mObserver = new ShutdownObserver(this);
+ obs->AddObserver(mObserver, "quit-application", false);
+ obs->AddObserver(mObserver, "xpcom-shutdown", false);
+ }
+}
+
+void gfxFontInfoLoader::RemoveShutdownObserver() {
+ if (mObserver) {
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(mObserver, "quit-application");
+ obs->RemoveObserver(mObserver, "xpcom-shutdown");
+ mObserver = nullptr;
+ }
+ }
+}
diff --git a/gfx/thebes/gfxFontInfoLoader.h b/gfx/thebes/gfxFontInfoLoader.h
new file mode 100644
index 0000000000..230cdb25b8
--- /dev/null
+++ b/gfx/thebes/gfxFontInfoLoader.h
@@ -0,0 +1,214 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_FONT_INFO_LOADER_H
+#define GFX_FONT_INFO_LOADER_H
+
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsIThread.h"
+#include "nsString.h"
+#include "gfxFontEntry.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/TimeStamp.h"
+#include "nsISupports.h"
+
+// data retrieved for a given face
+
+struct FontFaceData {
+ nsCString mFullName;
+ nsCString mPostscriptName;
+ RefPtr<gfxCharacterMap> mCharacterMap;
+ uint32_t mUVSOffset = 0;
+};
+
+// base class used to contain cached system-wide font info.
+// methods in this class are called on off-main threads so
+// all methods use only static methods or other thread-safe
+// font data access API's. specifically, no use is made of
+// gfxPlatformFontList, gfxFontFamily, gfxFamily or any
+// harfbuzz API methods within FontInfoData subclasses.
+
+class FontInfoData {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FontInfoData)
+
+ FontInfoData(bool aLoadOtherNames, bool aLoadFaceNames, bool aLoadCmaps)
+ : mCanceled(false),
+ mLoadOtherNames(aLoadOtherNames),
+ mLoadFaceNames(aLoadFaceNames),
+ mLoadCmaps(aLoadCmaps) {
+ MOZ_COUNT_CTOR(FontInfoData);
+ }
+
+ protected:
+ // Protected destructor, to discourage deletion outside of Release():
+ MOZ_COUNTED_DTOR_VIRTUAL(FontInfoData)
+
+ public:
+ virtual void Load();
+
+ // loads font data for all fonts of a given family
+ // (called on async thread)
+ virtual void LoadFontFamilyData(const nsACString& aFamilyName) = 0;
+
+ // -- methods overriden by platform-specific versions --
+
+ // fetches cmap data for a particular font from cached font data
+ virtual already_AddRefed<gfxCharacterMap> GetCMAP(const nsACString& aFontName,
+ uint32_t& aUVSOffset) {
+ FontFaceData faceData;
+ if (!mFontFaceData.Get(aFontName, &faceData) || !faceData.mCharacterMap) {
+ return nullptr;
+ }
+
+ aUVSOffset = faceData.mUVSOffset;
+ RefPtr<gfxCharacterMap> cmap = faceData.mCharacterMap;
+ return cmap.forget();
+ }
+
+ // fetches fullname/postscript names from cached font data
+ virtual void GetFaceNames(const nsACString& aFontName, nsACString& aFullName,
+ nsACString& aPostscriptName) {
+ FontFaceData faceData;
+ if (!mFontFaceData.Get(aFontName, &faceData)) {
+ return;
+ }
+
+ aFullName = faceData.mFullName;
+ aPostscriptName = faceData.mPostscriptName;
+ }
+
+ // fetches localized family name data from cached font data
+ const nsTArray<nsCString>* GetOtherFamilyNames(
+ const nsACString& aFamilyName) {
+ return mOtherFamilyNames.Lookup(aFamilyName).DataPtrOrNull();
+ }
+
+ nsTArray<nsCString> mFontFamiliesToLoad;
+
+ // currently non-issue but beware,
+ // this is also set during cleanup after finishing
+ mozilla::Atomic<bool> mCanceled;
+
+ // time spent on the loader thread
+ mozilla::TimeDuration mLoadTime;
+
+ struct FontCounts {
+ uint32_t families;
+ uint32_t fonts;
+ uint32_t cmaps;
+ uint32_t facenames;
+ uint32_t othernames;
+ };
+
+ FontCounts mLoadStats;
+
+ bool mLoadOtherNames;
+ bool mLoadFaceNames;
+ bool mLoadCmaps;
+
+ // face name ==> per-face data
+ nsTHashMap<nsCStringHashKey, FontFaceData> mFontFaceData;
+
+ // canonical family name ==> array of localized family names
+ nsTHashMap<nsCStringHashKey, CopyableTArray<nsCString> > mOtherFamilyNames;
+};
+
+// gfxFontInfoLoader - helper class for loading font info on async thread
+// For large, "all fonts on system" data, data needed on a given platform
+// (e.g. localized names, face names, cmaps) are loaded async.
+
+// helper class for loading in font info on a separate async thread
+// once async thread completes, completion process is run on the main
+// thread's idle queue in short slices
+
+class gfxFontInfoLoader {
+ public:
+ // state transitions:
+ // initial ---StartLoader with delay---> timer on delay
+ // initial ---StartLoader without delay---> timer off
+ // timer on delay ---LoaderTimerFire---> timer off
+ // timer on delay ---CancelLoader---> timer off
+ // timer off ---StartLoader with delay---> timer on delay
+ // timer off ---StartLoader without delay---> timer off
+ typedef enum {
+ stateInitial,
+ stateTimerOnDelay,
+ stateAsyncLoad,
+ stateTimerOff
+ } TimerState;
+
+ gfxFontInfoLoader() : mState(stateInitial) {
+ MOZ_COUNT_CTOR(gfxFontInfoLoader);
+ }
+
+ virtual ~gfxFontInfoLoader();
+
+ // start timer with an initial delay
+ void StartLoader(uint32_t aDelay);
+
+ // Finalize - async load complete, transfer data (on idle)
+ virtual void FinalizeLoader(FontInfoData* aFontInfo);
+
+ // cancel the timer and cleanup
+ void CancelLoader();
+
+ protected:
+ friend class FinalizeLoaderRunnable;
+
+ class ShutdownObserver : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ explicit ShutdownObserver(gfxFontInfoLoader* aLoader) : mLoader(aLoader) {}
+
+ protected:
+ virtual ~ShutdownObserver() = default;
+
+ gfxFontInfoLoader* mLoader;
+ };
+
+ // CreateFontInfo - create platform-specific object used
+ // to load system-wide font info
+ virtual already_AddRefed<FontInfoData> CreateFontInfoData() {
+ return nullptr;
+ }
+
+ // Init - initialization before async loader thread runs
+ virtual void InitLoader() = 0;
+
+ // LoadFontInfo - transfer font info data within a time limit, return
+ // true when done
+ virtual bool LoadFontInfo() = 0;
+
+ // Cleanup - finish and cleanup after done, including possible reflows
+ virtual void CleanupLoader() { mFontInfo = nullptr; }
+
+ static void DelayedStartCallback(nsITimer* aTimer, void* aThis) {
+ gfxFontInfoLoader* loader = static_cast<gfxFontInfoLoader*>(aThis);
+ loader->StartLoader(0);
+ }
+
+ void LoadFontInfoTimerFire();
+
+ void AddShutdownObserver();
+ void RemoveShutdownObserver();
+
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsIObserver> mObserver;
+ nsCOMPtr<nsIThread> mFontLoaderThread;
+ TimerState mState;
+
+ // after async font loader completes, data is stored here
+ RefPtr<FontInfoData> mFontInfo;
+
+ // time spent on the loader thread
+ mozilla::TimeDuration mLoadTime;
+};
+
+#endif /* GFX_FONT_INFO_LOADER_H */
diff --git a/gfx/thebes/gfxFontMissingGlyphs.cpp b/gfx/thebes/gfxFontMissingGlyphs.cpp
new file mode 100644
index 0000000000..949a71228c
--- /dev/null
+++ b/gfx/thebes/gfxFontMissingGlyphs.cpp
@@ -0,0 +1,542 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxFontMissingGlyphs.h"
+
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Helpers.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/RefPtr.h"
+#include "nsDeviceContext.h"
+#include "nsLayoutUtils.h"
+#include "TextDrawTarget.h"
+#include "LayerUserData.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+#define X 255
+static const uint8_t gMiniFontData[] = {
+ 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,
+ 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,
+ 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,
+ 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,
+ 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,
+};
+#undef X
+
+/* Parameters that control the rendering of hexboxes. They look like this:
+
+ BMP codepoints non-BMP codepoints
+ (U+0000 - U+FFFF) (U+10000 - U+10FFFF)
+
+ +---------+ +-------------+
+ | | | |
+ | HHH HHH | | HHH HHH HHH |
+ | HHH HHH | | HHH HHH HHH |
+ | HHH HHH | | HHH HHH HHH |
+ | HHH HHH | | HHH HHH HHH |
+ | HHH HHH | | HHH HHH HHH |
+ | | | |
+ | HHH HHH | | HHH HHH HHH |
+ | HHH HHH | | HHH HHH HHH |
+ | HHH HHH | | HHH HHH HHH |
+ | HHH HHH | | HHH HHH HHH |
+ | HHH HHH | | HHH HHH HHH |
+ | | | |
+ +---------+ +-------------+
+*/
+
+/** Width of a minifont glyph (see above) */
+static const int MINIFONT_WIDTH = 3;
+/** Height of a minifont glyph (see above) */
+static const int MINIFONT_HEIGHT = 5;
+/**
+ * Gap between minifont glyphs (both horizontal and vertical) and also
+ * the minimum desired gap between the box border and the glyphs
+ */
+static const int HEX_CHAR_GAP = 1;
+/**
+ * The amount of space between the vertical edge of the glyphbox and the
+ * box border. We make this nonzero so that when multiple missing glyphs
+ * occur consecutively there's a gap between their rendered boxes.
+ */
+static const int BOX_HORIZONTAL_INSET = 1;
+/** The width of the border */
+static const int BOX_BORDER_WIDTH = 1;
+/**
+ * The scaling factor for the border opacity; this is multiplied by the current
+ * opacity being used to draw the text.
+ */
+static const Float BOX_BORDER_OPACITY = 0.5;
+
+#ifndef MOZ_GFX_OPTIMIZE_MOBILE
+
+class GlyphAtlas {
+ public:
+ GlyphAtlas(RefPtr<SourceSurface>&& aSurface, const DeviceColor& aColor)
+ : mSurface(std::move(aSurface)), mColor(aColor) {}
+ ~GlyphAtlas() = default;
+
+ already_AddRefed<SourceSurface> Surface() const {
+ RefPtr surface = mSurface;
+ return surface.forget();
+ }
+ DeviceColor Color() const { return mColor; }
+
+ private:
+ RefPtr<SourceSurface> mSurface;
+ DeviceColor mColor;
+};
+
+// This is an owning reference that we will manage via exchange() and
+// explicit new/delete operations.
+static std::atomic<GlyphAtlas*> gGlyphAtlas;
+
+/**
+ * Generates a new colored mini-font atlas from the mini-font mask.
+ */
+static GlyphAtlas* MakeGlyphAtlas(const DeviceColor& aColor) {
+ RefPtr<DrawTarget> glyphDrawTarget =
+ gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+ IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT),
+ SurfaceFormat::B8G8R8A8);
+ if (!glyphDrawTarget) {
+ return nullptr;
+ }
+ RefPtr<SourceSurface> glyphMask =
+ glyphDrawTarget->CreateSourceSurfaceFromData(
+ const_cast<uint8_t*>(gMiniFontData),
+ IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT), MINIFONT_WIDTH * 16,
+ SurfaceFormat::A8);
+ if (!glyphMask) {
+ return nullptr;
+ }
+ glyphDrawTarget->MaskSurface(ColorPattern(aColor), glyphMask, Point(0, 0),
+ DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+ RefPtr<SourceSurface> surface = glyphDrawTarget->Snapshot();
+ if (!surface) {
+ return nullptr;
+ }
+ return new GlyphAtlas(std::move(surface), aColor);
+}
+
+/**
+ * Reuse the current mini-font atlas if the color matches, otherwise regenerate
+ * it.
+ */
+static inline already_AddRefed<SourceSurface> GetGlyphAtlas(
+ const DeviceColor& aColor) {
+ // Get the opaque color, ignoring any transparency which will be handled
+ // later.
+ DeviceColor color(aColor.r, aColor.g, aColor.b);
+
+ // Atomically grab the current GlyphAtlas pointer (if any). Because we
+ // exchange with nullptr here, no other thread will be able to touch the
+ // currAtlas record while we're using it; if they try, they'll just see
+ // the null that we stored.
+ GlyphAtlas* currAtlas = gGlyphAtlas.exchange(nullptr);
+
+ if (currAtlas && currAtlas->Color() == color) {
+ // If its color is right, grab a reference to its surface.
+ RefPtr<SourceSurface> surface = currAtlas->Surface();
+ // Now put the currAtlas record back in the global. If some other thread
+ // has stored an atlas there in the meantime, we just discard it.
+ delete gGlyphAtlas.exchange(currAtlas);
+ return surface.forget();
+ }
+
+ // Make a new atlas in the color we want.
+ GlyphAtlas* atlas = MakeGlyphAtlas(color);
+ RefPtr<SourceSurface> surface = atlas ? atlas->Surface() : nullptr;
+
+ // Store the newly-created atlas in the global; release any other.
+ delete gGlyphAtlas.exchange(atlas);
+ return surface.forget();
+}
+
+/**
+ * Clear any cached glyph atlas resources.
+ */
+static void PurgeGlyphAtlas() { delete gGlyphAtlas.exchange(nullptr); }
+
+// WebRender layer manager user data that will get signaled when the layer
+// manager is destroyed.
+class WRUserData : public layers::LayerUserData,
+ public LinkedListElement<WRUserData> {
+ public:
+ explicit WRUserData(layers::WebRenderLayerManager* aManager);
+
+ ~WRUserData();
+
+ static void Assign(layers::WebRenderLayerManager* aManager) {
+ if (!aManager->HasUserData(&sWRUserDataKey)) {
+ aManager->SetUserData(&sWRUserDataKey, new WRUserData(aManager));
+ }
+ }
+
+ void Remove() { mManager->RemoveUserData(&sWRUserDataKey); }
+
+ layers::WebRenderLayerManager* mManager;
+
+ static UserDataKey sWRUserDataKey;
+};
+
+static void DestroyImageKey(void* aClosure) {
+ auto* key = static_cast<wr::ImageKey*>(aClosure);
+ delete key;
+}
+
+static RefPtr<SourceSurface> gWRGlyphAtlas[8];
+static LinkedList<WRUserData> gWRUsers;
+UserDataKey WRUserData::sWRUserDataKey;
+
+/**
+ * Generates a transformed WebRender mini-font atlas for a given orientation.
+ */
+static already_AddRefed<SourceSurface> MakeWRGlyphAtlas(const Matrix* aMat) {
+ IntSize size(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT);
+ // If the orientation is transposed, width/height are swapped.
+ if (aMat && aMat->_11 == 0) {
+ std::swap(size.width, size.height);
+ }
+ RefPtr<DrawTarget> ref =
+ gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+ RefPtr<DrawTarget> dt =
+ gfxPlatform::GetPlatform()->CreateSimilarSoftwareDrawTarget(
+ ref, size, SurfaceFormat::B8G8R8A8);
+ if (!dt) {
+ return nullptr;
+ }
+ if (aMat) {
+ // Select appropriate transform matrix based on whether the
+ // orientation is transposed.
+ dt->SetTransform(aMat->_11 == 0
+ ? Matrix(0.0f, copysign(1.0f, aMat->_12),
+ copysign(1.0f, aMat->_21), 0.0f,
+ aMat->_21 < 0 ? MINIFONT_HEIGHT : 0.0f,
+ aMat->_12 < 0 ? MINIFONT_WIDTH * 16 : 0.0f)
+ : Matrix(copysign(1.0f, aMat->_11), 0.0f, 0.0f,
+ copysign(1.0f, aMat->_22),
+ aMat->_11 < 0 ? MINIFONT_WIDTH * 16 : 0.0f,
+ aMat->_22 < 0 ? MINIFONT_HEIGHT : 0.0f));
+ }
+ RefPtr<SourceSurface> mask = dt->CreateSourceSurfaceFromData(
+ const_cast<uint8_t*>(gMiniFontData),
+ IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT), MINIFONT_WIDTH * 16,
+ SurfaceFormat::A8);
+ if (!mask) {
+ return nullptr;
+ }
+ dt->MaskSurface(ColorPattern(DeviceColor::MaskOpaqueWhite()), mask,
+ Point(0, 0));
+ return dt->Snapshot();
+}
+
+/**
+ * Clear any cached WebRender glyph atlas resources.
+ */
+static void PurgeWRGlyphAtlas() {
+ // For each WR layer manager, we need go through each atlas orientation
+ // and see if it has a stashed image key. If it does, remove the image
+ // from the layer manager.
+ for (WRUserData* user : gWRUsers) {
+ auto* manager = user->mManager;
+ for (size_t i = 0; i < 8; i++) {
+ if (gWRGlyphAtlas[i]) {
+ auto* key = static_cast<wr::ImageKey*>(gWRGlyphAtlas[i]->GetUserData(
+ reinterpret_cast<UserDataKey*>(manager)));
+ if (key) {
+ manager->GetRenderRootStateManager()->AddImageKeyForDiscard(*key);
+ }
+ }
+ }
+ }
+ // Remove the layer managers' destroy notifications only after processing
+ // so as not to mess up gWRUsers iteration.
+ while (!gWRUsers.isEmpty()) {
+ gWRUsers.popFirst()->Remove();
+ }
+ // Finally, clear out the atlases.
+ for (size_t i = 0; i < 8; i++) {
+ gWRGlyphAtlas[i] = nullptr;
+ }
+}
+
+WRUserData::WRUserData(layers::WebRenderLayerManager* aManager)
+ : mManager(aManager) {
+ gWRUsers.insertFront(this);
+}
+
+WRUserData::~WRUserData() {
+ // When the layer manager is destroyed, we need go through each
+ // atlas and remove any assigned image keys.
+ if (isInList()) {
+ for (size_t i = 0; i < 8; i++) {
+ if (gWRGlyphAtlas[i]) {
+ gWRGlyphAtlas[i]->RemoveUserData(
+ reinterpret_cast<UserDataKey*>(mManager));
+ }
+ }
+ }
+}
+
+static already_AddRefed<SourceSurface> GetWRGlyphAtlas(DrawTarget& aDrawTarget,
+ const Matrix* aMat) {
+ uint32_t key = 0;
+ // Encode orientation in the key.
+ if (aMat) {
+ if (aMat->_11 == 0) {
+ key |= 4 | (aMat->_12 < 0 ? 1 : 0) | (aMat->_21 < 0 ? 2 : 0);
+ } else {
+ key |= (aMat->_11 < 0 ? 1 : 0) | (aMat->_22 < 0 ? 2 : 0);
+ }
+ }
+
+ // Check if an atlas was already created, or create one if necessary.
+ RefPtr<SourceSurface> atlas = gWRGlyphAtlas[key];
+ if (!atlas) {
+ atlas = MakeWRGlyphAtlas(aMat);
+ gWRGlyphAtlas[key] = atlas;
+ }
+
+ // The atlas may exist, but an image key may not be assigned for it to
+ // the given layer manager, or it may no longer be valid.
+ auto* tdt = static_cast<layout::TextDrawTarget*>(&aDrawTarget);
+ auto* manager = tdt->WrLayerManager();
+ auto* imageKey = static_cast<wr::ImageKey*>(
+ atlas->GetUserData(reinterpret_cast<UserDataKey*>(manager)));
+ if (!imageKey || !manager->WrBridge()->MatchesNamespace(*imageKey)) {
+ // No image key, so we need to map the atlas' data for transfer to WR.
+ RefPtr<DataSourceSurface> dataSurface = atlas->GetDataSurface();
+ if (!dataSurface) {
+ return nullptr;
+ }
+ DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ);
+ if (!map.IsMapped()) {
+ return nullptr;
+ }
+ // Transfer the data and get an image key for it.
+ Maybe<wr::ImageKey> result = tdt->DefineImage(
+ atlas->GetSize(), map.GetStride(), atlas->GetFormat(), map.GetData());
+ if (!result.isSome()) {
+ return nullptr;
+ }
+ // Assign the image key to the atlas.
+ atlas->AddUserData(reinterpret_cast<UserDataKey*>(manager),
+ new wr::ImageKey(result.ref()), DestroyImageKey);
+ // Create a user data notification for when the layer manager is
+ // destroyed so we can clean up any assigned image keys.
+ WRUserData::Assign(manager);
+ }
+ return atlas.forget();
+}
+
+static void DrawHexChar(uint32_t aDigit, Float aLeft, Float aTop,
+ DrawTarget& aDrawTarget, SourceSurface* aAtlas,
+ const DeviceColor& aColor,
+ const Matrix* aMat = nullptr) {
+ Rect dest(aLeft, aTop, MINIFONT_WIDTH, MINIFONT_HEIGHT);
+ if (aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT) {
+ // For WR, we need to get the image key assigned to the given WR layer
+ // manager for referencing the image.
+ auto* tdt = static_cast<layout::TextDrawTarget*>(&aDrawTarget);
+ auto* manager = tdt->WrLayerManager();
+ auto* key = static_cast<wr::ImageKey*>(
+ aAtlas->GetUserData(reinterpret_cast<UserDataKey*>(manager)));
+ MOZ_ASSERT(key);
+ // Transform the bounds of the atlas into the given orientation, and then
+ // also transform a small clip rect which will be used to select the given
+ // digit from the atlas.
+ Rect bounds(aLeft - aDigit * MINIFONT_WIDTH, aTop, MINIFONT_WIDTH * 16,
+ MINIFONT_HEIGHT);
+ if (aMat) {
+ // Width and height may be negative after the transform, so move the rect
+ // if necessary and fix size.
+ bounds = aMat->TransformRect(bounds);
+ bounds.x += std::min(bounds.width, 0.0f);
+ bounds.y += std::min(bounds.height, 0.0f);
+ bounds.width = fabs(bounds.width);
+ bounds.height = fabs(bounds.height);
+ dest = aMat->TransformRect(dest);
+ dest.x += std::min(dest.width, 0.0f);
+ dest.y += std::min(dest.height, 0.0f);
+ dest.width = fabs(dest.width);
+ dest.height = fabs(dest.height);
+ }
+ // Finally, push the colored image with point filtering.
+ tdt->PushImage(*key, bounds, dest, wr::ImageRendering::Pixelated,
+ wr::ToColorF(aColor));
+ } else {
+ // For the normal case, just draw the given digit from the atlas. Point
+ // filtering is used to ensure the mini-font rectangles stay sharp with any
+ // scaling. Handle any transparency here as well.
+ aDrawTarget.DrawSurface(
+ aAtlas, dest,
+ Rect(aDigit * MINIFONT_WIDTH, 0, MINIFONT_WIDTH, MINIFONT_HEIGHT),
+ DrawSurfaceOptions(SamplingFilter::POINT),
+ DrawOptions(aColor.a, CompositionOp::OP_OVER, AntialiasMode::NONE));
+ }
+}
+
+void gfxFontMissingGlyphs::Purge() {
+ PurgeGlyphAtlas();
+ PurgeWRGlyphAtlas();
+}
+
+#else // MOZ_GFX_OPTIMIZE_MOBILE
+
+void gfxFontMissingGlyphs::Purge() {}
+
+#endif
+
+void gfxFontMissingGlyphs::Shutdown() { Purge(); }
+
+void gfxFontMissingGlyphs::DrawMissingGlyph(uint32_t aChar, const Rect& aRect,
+ DrawTarget& aDrawTarget,
+ const Pattern& aPattern,
+ const Matrix* aMat) {
+ Rect rect(aRect);
+ // If there is an orientation transform, reorient the bounding rect.
+ if (aMat) {
+ rect.MoveBy(-aRect.BottomLeft());
+ rect = aMat->TransformBounds(rect);
+ rect.MoveBy(aRect.BottomLeft());
+ }
+
+ // If we're currently drawing with some kind of pattern, we just draw the
+ // missing-glyph data in black.
+ DeviceColor color = aPattern.GetType() == PatternType::COLOR
+ ? static_cast<const ColorPattern&>(aPattern).mColor
+ : ToDeviceColor(sRGBColor::OpaqueBlack());
+
+ // Stroke a rectangle so that the stroke's left edge is inset one pixel
+ // from the left edge of the glyph box and the stroke's right edge
+ // is inset one pixel from the right edge of the glyph box.
+ Float halfBorderWidth = BOX_BORDER_WIDTH / 2.0;
+ Float borderLeft = rect.X() + BOX_HORIZONTAL_INSET + halfBorderWidth;
+ Float borderRight = rect.XMost() - BOX_HORIZONTAL_INSET - halfBorderWidth;
+ Rect borderStrokeRect(borderLeft, rect.Y() + halfBorderWidth,
+ borderRight - borderLeft,
+ rect.Height() - 2.0 * halfBorderWidth);
+ if (!borderStrokeRect.IsEmpty()) {
+ ColorPattern adjustedColor(color);
+ adjustedColor.mColor.a *= BOX_BORDER_OPACITY;
+#ifdef MOZ_GFX_OPTIMIZE_MOBILE
+ aDrawTarget.FillRect(borderStrokeRect, adjustedColor);
+#else
+ StrokeOptions strokeOptions(BOX_BORDER_WIDTH);
+ aDrawTarget.StrokeRect(borderStrokeRect, adjustedColor, strokeOptions);
+#endif
+ }
+
+#ifndef MOZ_GFX_OPTIMIZE_MOBILE
+ RefPtr<SourceSurface> atlas =
+ aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT
+ ? GetWRGlyphAtlas(aDrawTarget, aMat)
+ : GetGlyphAtlas(color);
+ if (!atlas) {
+ return;
+ }
+
+ Point center = rect.Center();
+ Float halfGap = HEX_CHAR_GAP / 2.f;
+ Float top = -(MINIFONT_HEIGHT + halfGap);
+
+ // Figure out a scaling factor that will fit the glyphs in the target rect
+ // both horizontally and vertically.
+ Float width = HEX_CHAR_GAP + MINIFONT_WIDTH + HEX_CHAR_GAP + MINIFONT_WIDTH +
+ ((aChar < 0x10000) ? 0 : HEX_CHAR_GAP + MINIFONT_WIDTH) +
+ HEX_CHAR_GAP;
+ Float height = HEX_CHAR_GAP + MINIFONT_HEIGHT + HEX_CHAR_GAP +
+ MINIFONT_HEIGHT + HEX_CHAR_GAP;
+ Float scaling = std::min(rect.Height() / height, rect.Width() / width);
+
+ // We always want integer scaling, otherwise the "bitmap" glyphs will look
+ // even uglier than usual when scaled to the target.
+ int32_t devPixelsPerCSSPx = std::max<int32_t>(1, std::floor(scaling));
+
+ Matrix tempMat;
+ if (aMat) {
+ // If there is an orientation transform, since draw target transforms may
+ // not be supported, scale and translate it so that it can be directly used
+ // for rendering the mini font without changing the draw target transform.
+ tempMat = Matrix(*aMat)
+ .PostScale(devPixelsPerCSSPx, devPixelsPerCSSPx)
+ .PostTranslate(center);
+ aMat = &tempMat;
+ } else {
+ // Otherwise, scale and translate the draw target transform assuming it
+ // supports that.
+ tempMat = aDrawTarget.GetTransform();
+ aDrawTarget.SetTransform(Matrix(tempMat).PreTranslate(center).PreScale(
+ devPixelsPerCSSPx, devPixelsPerCSSPx));
+ }
+
+ if (aChar < 0x10000) {
+ if (rect.Width() >= 2 * (MINIFONT_WIDTH + HEX_CHAR_GAP) &&
+ rect.Height() >= 2 * MINIFONT_HEIGHT + HEX_CHAR_GAP) {
+ // Draw 4 digits for BMP
+ Float left = -(MINIFONT_WIDTH + halfGap);
+ DrawHexChar((aChar >> 12) & 0xF, left, top, aDrawTarget, atlas, color,
+ aMat);
+ DrawHexChar((aChar >> 8) & 0xF, halfGap, top, aDrawTarget, atlas, color,
+ aMat);
+ DrawHexChar((aChar >> 4) & 0xF, left, halfGap, aDrawTarget, atlas, color,
+ aMat);
+ DrawHexChar(aChar & 0xF, halfGap, halfGap, aDrawTarget, atlas, color,
+ aMat);
+ }
+ } else {
+ if (rect.Width() >= 3 * (MINIFONT_WIDTH + HEX_CHAR_GAP) &&
+ rect.Height() >= 2 * MINIFONT_HEIGHT + HEX_CHAR_GAP) {
+ // Draw 6 digits for non-BMP
+ Float first = -(MINIFONT_WIDTH * 1.5 + HEX_CHAR_GAP);
+ Float second = -(MINIFONT_WIDTH / 2.0);
+ Float third = (MINIFONT_WIDTH / 2.0 + HEX_CHAR_GAP);
+ DrawHexChar((aChar >> 20) & 0xF, first, top, aDrawTarget, atlas, color,
+ aMat);
+ DrawHexChar((aChar >> 16) & 0xF, second, top, aDrawTarget, atlas, color,
+ aMat);
+ DrawHexChar((aChar >> 12) & 0xF, third, top, aDrawTarget, atlas, color,
+ aMat);
+ DrawHexChar((aChar >> 8) & 0xF, first, halfGap, aDrawTarget, atlas, color,
+ aMat);
+ DrawHexChar((aChar >> 4) & 0xF, second, halfGap, aDrawTarget, atlas,
+ color, aMat);
+ DrawHexChar(aChar & 0xF, third, halfGap, aDrawTarget, atlas, color, aMat);
+ }
+ }
+
+ if (!aMat) {
+ // The draw target transform was changed, so it must be restored to
+ // the original value.
+ aDrawTarget.SetTransform(tempMat);
+ }
+#endif
+}
+
+Float gfxFontMissingGlyphs::GetDesiredMinWidth(uint32_t aChar,
+ uint32_t aAppUnitsPerDevPixel) {
+ /**
+ * The minimum desired width for a missing-glyph glyph box. I've laid it out
+ * like this so you can see what goes where.
+ */
+ Float width = BOX_HORIZONTAL_INSET + BOX_BORDER_WIDTH + HEX_CHAR_GAP +
+ MINIFONT_WIDTH + HEX_CHAR_GAP + MINIFONT_WIDTH +
+ ((aChar < 0x10000) ? 0 : HEX_CHAR_GAP + MINIFONT_WIDTH) +
+ HEX_CHAR_GAP + BOX_BORDER_WIDTH + BOX_HORIZONTAL_INSET;
+ // Note that this will give us floating-point division, so the width will
+ // -not- be snapped to integer multiples of its basic pixel value
+ width *= Float(AppUnitsPerCSSPixel()) / aAppUnitsPerDevPixel;
+ return width;
+}
diff --git a/gfx/thebes/gfxFontMissingGlyphs.h b/gfx/thebes/gfxFontMissingGlyphs.h
new file mode 100644
index 0000000000..711e96f79e
--- /dev/null
+++ b/gfx/thebes/gfxFontMissingGlyphs.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_FONTMISSINGGLYPHS_H
+#define GFX_FONTMISSINGGLYPHS_H
+
+#include "mozilla/Attributes.h"
+#include "mozilla/gfx/MatrixFwd.h"
+#include "mozilla/gfx/Rect.h"
+
+namespace mozilla {
+namespace gfx {
+class DrawTarget;
+class Pattern;
+} // namespace gfx
+} // namespace mozilla
+
+/**
+ * This class should not be instantiated. It's just a container
+ * for some helper functions.
+ */
+class gfxFontMissingGlyphs final {
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::Float Float;
+ typedef mozilla::gfx::Matrix Matrix;
+ typedef mozilla::gfx::Pattern Pattern;
+ typedef mozilla::gfx::Rect Rect;
+
+ gfxFontMissingGlyphs() = delete; // prevent instantiation
+
+ public:
+ /**
+ * Draw hexboxes for a missing glyph.
+ * @param aChar the UTF16 codepoint for the character
+ * @param aRect the glyph-box for the glyph that is missing
+ * @param aDrawTarget the DrawTarget to draw to
+ * @param aPattern the pattern currently being used to paint
+ * @param aMat optional local-space orientation matrix
+ */
+ static void DrawMissingGlyph(uint32_t aChar, const Rect& aRect,
+ DrawTarget& aDrawTarget, const Pattern& aPattern,
+ const Matrix* aMat = nullptr);
+ /**
+ * @return the desired minimum width for a glyph-box that will allow
+ * the hexboxes to be drawn reasonably.
+ */
+ static Float GetDesiredMinWidth(uint32_t aChar, uint32_t aAppUnitsPerDevUnit);
+
+ static void Purge();
+
+ static void Shutdown();
+};
+
+#endif
diff --git a/gfx/thebes/gfxFontPrefLangList.h b/gfx/thebes/gfxFontPrefLangList.h
new file mode 100644
index 0000000000..5ca1311cb0
--- /dev/null
+++ b/gfx/thebes/gfxFontPrefLangList.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// this needs to match the list of pref font.default.xx entries listed in
+// all.js!
+
+FONT_PREF_LANG(Western, "x-western", x_western),
+ FONT_PREF_LANG(Japanese, "ja", Japanese),
+ FONT_PREF_LANG(ChineseTW, "zh-TW", Taiwanese),
+ FONT_PREF_LANG(ChineseCN, "zh-CN", Chinese),
+ FONT_PREF_LANG(ChineseHK, "zh-HK", HongKongChinese),
+ FONT_PREF_LANG(Korean, "ko", ko),
+ FONT_PREF_LANG(Cyrillic, "x-cyrillic", x_cyrillic),
+ FONT_PREF_LANG(Greek, "el", el), FONT_PREF_LANG(Thai, "th", th),
+ FONT_PREF_LANG(Hebrew, "he", he), FONT_PREF_LANG(Arabic, "ar", ar),
+ FONT_PREF_LANG(Devanagari, "x-devanagari", x_devanagari),
+ FONT_PREF_LANG(Tamil, "x-tamil", x_tamil),
+ FONT_PREF_LANG(Armenian, "x-armn", x_armn),
+ FONT_PREF_LANG(Bengali, "x-beng", x_beng),
+ FONT_PREF_LANG(Canadian, "x-cans", x_cans),
+ FONT_PREF_LANG(Ethiopic, "x-ethi", x_ethi),
+ FONT_PREF_LANG(Georgian, "x-geor", x_geor),
+ FONT_PREF_LANG(Gujarati, "x-gujr", x_gujr),
+ FONT_PREF_LANG(Gurmukhi, "x-guru", x_guru),
+ FONT_PREF_LANG(Khmer, "x-khmr", x_khmr),
+ FONT_PREF_LANG(Malayalam, "x-mlym", x_mlym),
+ FONT_PREF_LANG(Mathematics, "x-math", x_math),
+ FONT_PREF_LANG(Oriya, "x-orya", x_orya),
+ FONT_PREF_LANG(Telugu, "x-telu", x_telu),
+ FONT_PREF_LANG(Kannada, "x-knda", x_knda),
+ FONT_PREF_LANG(Sinhala, "x-sinh", x_sinh),
+ FONT_PREF_LANG(Tibetan, "x-tibt", x_tibt),
+ FONT_PREF_LANG(Others, "x-unicode", Unicode)
diff --git a/gfx/thebes/gfxFontSrcPrincipal.cpp b/gfx/thebes/gfxFontSrcPrincipal.cpp
new file mode 100644
index 0000000000..97ddc5ffe7
--- /dev/null
+++ b/gfx/thebes/gfxFontSrcPrincipal.cpp
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxFontSrcPrincipal.h"
+
+#include "nsURIHashKey.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/HashFunctions.h"
+
+using mozilla::BasePrincipal;
+
+gfxFontSrcPrincipal::gfxFontSrcPrincipal(nsIPrincipal* aNodePrincipal,
+ nsIPrincipal* aStoragePrincipal)
+ : mNodePrincipal(aNodePrincipal),
+ mStoragePrincipal(mozilla::StaticPrefs::privacy_partition_network_state()
+ ? aStoragePrincipal
+ : aNodePrincipal) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aNodePrincipal);
+ MOZ_ASSERT(aStoragePrincipal);
+
+ nsAutoCString suffix;
+ mStoragePrincipal->GetOriginSuffix(suffix);
+
+ mHash = mozilla::AddToHash(mStoragePrincipal->GetHashValue(),
+ mozilla::HashString(suffix));
+}
+
+gfxFontSrcPrincipal::~gfxFontSrcPrincipal() = default;
+
+bool gfxFontSrcPrincipal::Equals(gfxFontSrcPrincipal* aOther) {
+ return BasePrincipal::Cast(mStoragePrincipal)
+ ->FastEquals(BasePrincipal::Cast(aOther->mStoragePrincipal));
+}
diff --git a/gfx/thebes/gfxFontSrcPrincipal.h b/gfx/thebes/gfxFontSrcPrincipal.h
new file mode 100644
index 0000000000..44170701a1
--- /dev/null
+++ b/gfx/thebes/gfxFontSrcPrincipal.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_GFX_FONTSRCPRINCIPAL_H
+#define MOZILLA_GFX_FONTSRCPRINCIPAL_H
+
+#include "nsCOMPtr.h"
+#include "PLDHashTable.h"
+
+class nsIPrincipal;
+
+namespace mozilla {
+namespace net {
+class nsSimpleURI;
+} // namespace net
+} // namespace mozilla
+
+/**
+ * A wrapper for an nsIPrincipal that can be used OMT, which has cached
+ * information useful for the gfxUserFontSet.
+ *
+ * TODO(emilio): This has grown a bit more complex, but nsIPrincipal is now
+ * thread-safe, re-evaluate the existence of this class.
+ */
+class gfxFontSrcPrincipal {
+ public:
+ explicit gfxFontSrcPrincipal(nsIPrincipal* aNodePrincipal,
+ nsIPrincipal* aStoragePrincipal);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxFontSrcPrincipal)
+
+ nsIPrincipal* NodePrincipal() const { return mNodePrincipal; }
+
+ nsIPrincipal* StoragePrincipal() const { return mStoragePrincipal; }
+
+ bool Equals(gfxFontSrcPrincipal* aOther);
+
+ PLDHashNumber Hash() const { return mHash; }
+
+ private:
+ ~gfxFontSrcPrincipal();
+
+ // The principal of the node.
+ nsCOMPtr<nsIPrincipal> mNodePrincipal;
+
+ // The principal used for storage.
+ nsCOMPtr<nsIPrincipal> mStoragePrincipal;
+
+ // Precomputed hash for mStoragePrincipal.
+ PLDHashNumber mHash;
+};
+
+#endif // MOZILLA_GFX_FONTSRCPRINCIPAL_H
diff --git a/gfx/thebes/gfxFontSrcURI.cpp b/gfx/thebes/gfxFontSrcURI.cpp
new file mode 100644
index 0000000000..3153d2abce
--- /dev/null
+++ b/gfx/thebes/gfxFontSrcURI.cpp
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxFontSrcURI.h"
+
+#include "mozilla/ServoStyleSet.h"
+#include "nsIProtocolHandler.h"
+#include "nsProxyRelease.h"
+#include "nsNetUtil.h"
+#include "nsSimpleURI.h"
+#include "nsURIHashKey.h"
+
+static bool HasFlag(nsIURI* aURI, uint32_t aFlag) {
+ nsresult rv;
+ bool value = false;
+ rv = NS_URIChainHasFlags(aURI, aFlag, &value);
+ return NS_SUCCEEDED(rv) && value;
+}
+
+gfxFontSrcURI::gfxFontSrcURI(nsIURI* aURI) : mURI(aURI) {
+ MOZ_ASSERT(aURI);
+
+ // If we have a data: URI, we know that it is backed by an nsSimpleURI,
+ // and that we don't need to serialize it ahead of time.
+ nsCString scheme;
+ mURI->GetScheme(scheme);
+
+ if (scheme.EqualsLiteral("data")) {
+ // We know that nsSimpleURI::From returns us a pointer to the same object,
+ // and we hold a strong reference to the object in mURI, so no need to
+ // hold it strongly here as well. (And we'd have to
+ // NS_ReleaseOnMainThread it in our destructor anyway.)
+ RefPtr<mozilla::net::nsSimpleURI> simpleURI =
+ mozilla::net::nsSimpleURI::From(aURI);
+ mSimpleURI = simpleURI;
+
+ NS_ASSERTION(mSimpleURI,
+ "Why aren't our data: URLs backed by nsSimpleURI?");
+ } else {
+ mSimpleURI = nullptr;
+ }
+
+ if (!mSimpleURI) {
+ mURI->GetSpec(mSpec);
+ }
+
+ mHash = nsURIHashKey::HashKey(mURI);
+}
+
+gfxFontSrcURI::~gfxFontSrcURI() = default;
+
+void gfxFontSrcURI::EnsureInitialized() {
+ MOZ_ASSERT(NS_IsMainThread() || mozilla::ServoStyleSet::IsInServoTraversal());
+
+ if (mInitialized) {
+ return;
+ }
+
+ mInheritsSecurityContext =
+ HasFlag(mURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT);
+ mSyncLoadIsOK = HasFlag(mURI, nsIProtocolHandler::URI_SYNC_LOAD_IS_OK);
+ mInitialized = true;
+}
+
+bool gfxFontSrcURI::Equals(gfxFontSrcURI* aOther) {
+ if (mSimpleURI) {
+ if (aOther->mSimpleURI) {
+ return mSimpleURI->Equals(aOther->mSimpleURI);
+ }
+
+ // The two URIs are probably different. Do a quick check on the
+ // schemes before deciding to serialize mSimpleURI (which might be
+ // quite large).
+ {
+ nsCString thisScheme;
+ mSimpleURI->GetScheme(thisScheme);
+
+ nsCString otherScheme;
+ if (!StringBeginsWith(aOther->mSpec, thisScheme)) {
+ return false;
+ }
+ }
+
+ nsCString thisSpec;
+ mSimpleURI->GetSpec(thisSpec);
+ return thisSpec == aOther->mSpec;
+ }
+
+ if (aOther->mSimpleURI) {
+ return aOther->Equals(this);
+ }
+
+ return mSpec == aOther->mSpec;
+}
+
+nsresult gfxFontSrcURI::GetSpec(nsACString& aResult) {
+ if (mSimpleURI) {
+ return mSimpleURI->GetSpec(aResult);
+ }
+
+ aResult = mSpec;
+ return NS_OK;
+}
+
+nsCString gfxFontSrcURI::GetSpecOrDefault() {
+ if (mSimpleURI) {
+ return mSimpleURI->GetSpecOrDefault();
+ }
+
+ return mSpec;
+}
diff --git a/gfx/thebes/gfxFontSrcURI.h b/gfx/thebes/gfxFontSrcURI.h
new file mode 100644
index 0000000000..994575d7fc
--- /dev/null
+++ b/gfx/thebes/gfxFontSrcURI.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_GFX_FONTSRCURI_H
+#define MOZILLA_GFX_FONTSRCURI_H
+
+#include "nsCOMPtr.h"
+#include "nsTString.h"
+#include "PLDHashTable.h"
+
+class nsIURI;
+
+namespace mozilla {
+namespace net {
+class nsSimpleURI;
+} // namespace net
+} // namespace mozilla
+
+/**
+ * A wrapper for an nsIURI that can be used OMT, which has cached information
+ * useful for the gfxUserFontSet.
+ */
+class gfxFontSrcURI final {
+ public:
+ explicit gfxFontSrcURI(nsIURI* aURI);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxFontSrcURI)
+
+ nsIURI* get() { return mURI; }
+
+ bool Equals(gfxFontSrcURI* aOther);
+ nsresult GetSpec(nsACString& aResult);
+ nsCString GetSpecOrDefault();
+
+ PLDHashNumber Hash() const { return mHash; }
+
+ bool InheritsSecurityContext() {
+ EnsureInitialized();
+ return mInheritsSecurityContext;
+ }
+
+ bool SyncLoadIsOK() {
+ EnsureInitialized();
+ return mSyncLoadIsOK;
+ }
+
+ private:
+ ~gfxFontSrcURI();
+
+ void EnsureInitialized();
+
+ // The URI.
+ nsCOMPtr<nsIURI> mURI;
+
+ // If the nsIURI is an nsSimpleURI for a data: URL, this is a pointer to it.
+ // (Just a weak reference since mURI holds the strong reference.)
+ //
+ // We store this so that we don't duplicate the URL spec for data: URLs,
+ // which can be much larger than other URLs.
+ mozilla::net::nsSimpleURI* mSimpleURI;
+
+ // If the nsIURI is not an nsSimpleURI, this is its spec.
+ nsCString mSpec;
+
+ // Precomputed hash for mURI.
+ PLDHashNumber mHash;
+
+ // Whether the font has been initialized on the main thread.
+ bool mInitialized = false;
+
+ // Whether the nsIURI's protocol handler has the URI_INHERITS_SECURITY_CONTEXT
+ // flag.
+ bool mInheritsSecurityContext = false;
+
+ // Whether the nsIURI's protocol handler has teh URI_SYNC_LOAD_IS_OK flag.
+ bool mSyncLoadIsOK = false;
+};
+
+#endif // MOZILLA_GFX_FONTSRCURI_H
diff --git a/gfx/thebes/gfxFontUtils.cpp b/gfx/thebes/gfxFontUtils.cpp
new file mode 100644
index 0000000000..02543607cd
--- /dev/null
+++ b/gfx/thebes/gfxFontUtils.cpp
@@ -0,0 +1,1791 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/BinarySearch.h"
+
+#include "gfxFontUtils.h"
+#include "gfxFontEntry.h"
+#include "gfxFontVariations.h"
+#include "gfxUtils.h"
+
+#include "nsServiceManagerUtils.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Unused.h"
+
+#include "nsCOMPtr.h"
+#include "nsIUUIDGenerator.h"
+#include "mozilla/Encoding.h"
+
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/dom/WorkerCommon.h"
+
+#include "plbase64.h"
+#include "mozilla/Logging.h"
+
+#ifdef XP_MACOSX
+# include <CoreFoundation/CoreFoundation.h>
+#endif
+
+#define LOG(log, args) MOZ_LOG(gfxPlatform::GetLog(log), LogLevel::Debug, args)
+
+#define UNICODE_BMP_LIMIT 0x10000
+
+using namespace mozilla;
+
+#pragma pack(1)
+
+typedef struct {
+ AutoSwap_PRUint16 format;
+ AutoSwap_PRUint16 reserved;
+ AutoSwap_PRUint32 length;
+ AutoSwap_PRUint32 language;
+ AutoSwap_PRUint32 startCharCode;
+ AutoSwap_PRUint32 numChars;
+} Format10CmapHeader;
+
+typedef struct {
+ AutoSwap_PRUint16 format;
+ AutoSwap_PRUint16 reserved;
+ AutoSwap_PRUint32 length;
+ AutoSwap_PRUint32 language;
+ AutoSwap_PRUint32 numGroups;
+} Format12CmapHeader;
+
+typedef struct {
+ AutoSwap_PRUint32 startCharCode;
+ AutoSwap_PRUint32 endCharCode;
+ AutoSwap_PRUint32 startGlyphId;
+} Format12Group;
+
+#pragma pack()
+
+void gfxSparseBitSet::Dump(const char* aPrefix, eGfxLog aWhichLog) const {
+ uint32_t numBlocks = mBlockIndex.Length();
+
+ for (uint32_t b = 0; b < numBlocks; b++) {
+ if (mBlockIndex[b] == NO_BLOCK) {
+ continue;
+ }
+ const Block* block = &mBlocks[mBlockIndex[b]];
+ const int BUFSIZE = 256;
+ char outStr[BUFSIZE];
+ int index = 0;
+ index += snprintf(&outStr[index], BUFSIZE - index, "%s u+%6.6x [", aPrefix,
+ (b * BLOCK_SIZE_BITS));
+ for (int i = 0; i < 32; i += 4) {
+ for (int j = i; j < i + 4; j++) {
+ uint8_t bits = block->mBits[j];
+ uint8_t flip1 = ((bits & 0xaa) >> 1) | ((bits & 0x55) << 1);
+ uint8_t flip2 = ((flip1 & 0xcc) >> 2) | ((flip1 & 0x33) << 2);
+ uint8_t flipped = ((flip2 & 0xf0) >> 4) | ((flip2 & 0x0f) << 4);
+
+ index += snprintf(&outStr[index], BUFSIZE - index, "%2.2x", flipped);
+ }
+ if (i + 4 != 32) index += snprintf(&outStr[index], BUFSIZE - index, " ");
+ }
+ Unused << snprintf(&outStr[index], BUFSIZE - index, "]");
+ LOG(aWhichLog, ("%s", outStr));
+ }
+}
+
+nsresult gfxFontUtils::ReadCMAPTableFormat10(const uint8_t* aBuf,
+ uint32_t aLength,
+ gfxSparseBitSet& aCharacterMap) {
+ // Ensure table is large enough that we can safely read the header
+ NS_ENSURE_TRUE(aLength >= sizeof(Format10CmapHeader),
+ NS_ERROR_GFX_CMAP_MALFORMED);
+
+ // Sanity-check header fields
+ const Format10CmapHeader* cmap10 =
+ reinterpret_cast<const Format10CmapHeader*>(aBuf);
+ NS_ENSURE_TRUE(uint16_t(cmap10->format) == 10, NS_ERROR_GFX_CMAP_MALFORMED);
+ NS_ENSURE_TRUE(uint16_t(cmap10->reserved) == 0, NS_ERROR_GFX_CMAP_MALFORMED);
+
+ uint32_t tablelen = cmap10->length;
+ NS_ENSURE_TRUE(tablelen >= sizeof(Format10CmapHeader) && tablelen <= aLength,
+ NS_ERROR_GFX_CMAP_MALFORMED);
+
+ NS_ENSURE_TRUE(cmap10->language == 0, NS_ERROR_GFX_CMAP_MALFORMED);
+
+ uint32_t numChars = cmap10->numChars;
+ NS_ENSURE_TRUE(
+ tablelen == sizeof(Format10CmapHeader) + numChars * sizeof(uint16_t),
+ NS_ERROR_GFX_CMAP_MALFORMED);
+
+ uint32_t charCode = cmap10->startCharCode;
+ NS_ENSURE_TRUE(charCode <= CMAP_MAX_CODEPOINT &&
+ charCode + numChars <= CMAP_MAX_CODEPOINT,
+ NS_ERROR_GFX_CMAP_MALFORMED);
+
+ // glyphs[] array immediately follows the subtable header
+ const AutoSwap_PRUint16* glyphs =
+ reinterpret_cast<const AutoSwap_PRUint16*>(cmap10 + 1);
+
+ for (uint32_t i = 0; i < numChars; ++i) {
+ if (uint16_t(*glyphs) != 0) {
+ aCharacterMap.set(charCode);
+ }
+ ++charCode;
+ ++glyphs;
+ }
+
+ aCharacterMap.Compact();
+
+ return NS_OK;
+}
+
+nsresult gfxFontUtils::ReadCMAPTableFormat12or13(
+ const uint8_t* aBuf, uint32_t aLength, gfxSparseBitSet& aCharacterMap) {
+ // Format 13 has the same structure as format 12, the only difference is
+ // the interpretation of the glyphID field. So we can share the code here
+ // that reads the table and just records character coverage.
+
+ // Ensure table is large enough that we can safely read the header
+ NS_ENSURE_TRUE(aLength >= sizeof(Format12CmapHeader),
+ NS_ERROR_GFX_CMAP_MALFORMED);
+
+ // Sanity-check header fields
+ const Format12CmapHeader* cmap12 =
+ reinterpret_cast<const Format12CmapHeader*>(aBuf);
+ NS_ENSURE_TRUE(
+ uint16_t(cmap12->format) == 12 || uint16_t(cmap12->format) == 13,
+ NS_ERROR_GFX_CMAP_MALFORMED);
+ NS_ENSURE_TRUE(uint16_t(cmap12->reserved) == 0, NS_ERROR_GFX_CMAP_MALFORMED);
+
+ uint32_t tablelen = cmap12->length;
+ NS_ENSURE_TRUE(tablelen >= sizeof(Format12CmapHeader) && tablelen <= aLength,
+ NS_ERROR_GFX_CMAP_MALFORMED);
+
+ NS_ENSURE_TRUE(cmap12->language == 0, NS_ERROR_GFX_CMAP_MALFORMED);
+
+ // Check that the table is large enough for the group array
+ const uint32_t numGroups = cmap12->numGroups;
+ NS_ENSURE_TRUE(
+ (tablelen - sizeof(Format12CmapHeader)) / sizeof(Format12Group) >=
+ numGroups,
+ NS_ERROR_GFX_CMAP_MALFORMED);
+
+ // The array of groups immediately follows the subtable header.
+ const Format12Group* group =
+ reinterpret_cast<const Format12Group*>(aBuf + sizeof(Format12CmapHeader));
+
+ // Check that groups are in correct order and do not overlap,
+ // and record character coverage in aCharacterMap.
+ uint32_t prevEndCharCode = 0;
+ for (uint32_t i = 0; i < numGroups; i++, group++) {
+ uint32_t startCharCode = group->startCharCode;
+ const uint32_t endCharCode = group->endCharCode;
+ NS_ENSURE_TRUE((prevEndCharCode < startCharCode || i == 0) &&
+ startCharCode <= endCharCode &&
+ endCharCode <= CMAP_MAX_CODEPOINT,
+ NS_ERROR_GFX_CMAP_MALFORMED);
+ // don't include a character that maps to glyph ID 0 (.notdef)
+ if (group->startGlyphId == 0) {
+ startCharCode++;
+ }
+ if (startCharCode <= endCharCode) {
+ aCharacterMap.SetRange(startCharCode, endCharCode);
+ }
+ prevEndCharCode = endCharCode;
+ }
+
+ aCharacterMap.Compact();
+
+ return NS_OK;
+}
+
+nsresult gfxFontUtils::ReadCMAPTableFormat4(const uint8_t* aBuf,
+ uint32_t aLength,
+ gfxSparseBitSet& aCharacterMap,
+ bool aIsSymbolFont) {
+ enum {
+ OffsetFormat = 0,
+ OffsetLength = 2,
+ OffsetLanguage = 4,
+ OffsetSegCountX2 = 6
+ };
+
+ NS_ENSURE_TRUE(ReadShortAt(aBuf, OffsetFormat) == 4,
+ NS_ERROR_GFX_CMAP_MALFORMED);
+ uint16_t tablelen = ReadShortAt(aBuf, OffsetLength);
+ NS_ENSURE_TRUE(tablelen <= aLength, NS_ERROR_GFX_CMAP_MALFORMED);
+ NS_ENSURE_TRUE(tablelen > 16, NS_ERROR_GFX_CMAP_MALFORMED);
+
+ // This field should normally (except for Mac platform subtables) be zero
+ // according to the OT spec, but some buggy fonts have lang = 1 (which would
+ // be English for MacOS). E.g. Arial Narrow Bold, v. 1.1 (Tiger), Arial
+ // Unicode MS (see bug 530614). So accept either zero or one here; the error
+ // should be harmless.
+ NS_ENSURE_TRUE((ReadShortAt(aBuf, OffsetLanguage) & 0xfffe) == 0,
+ NS_ERROR_GFX_CMAP_MALFORMED);
+
+ uint16_t segCountX2 = ReadShortAt(aBuf, OffsetSegCountX2);
+ NS_ENSURE_TRUE(tablelen >= 16 + (segCountX2 * 4),
+ NS_ERROR_GFX_CMAP_MALFORMED);
+
+ const uint16_t segCount = segCountX2 / 2;
+
+ const uint16_t* endCounts = reinterpret_cast<const uint16_t*>(aBuf + 14);
+ const uint16_t* startCounts =
+ endCounts + 1 /* skip one uint16_t for reservedPad */ + segCount;
+ const uint16_t* idDeltas = startCounts + segCount;
+ const uint16_t* idRangeOffsets = idDeltas + segCount;
+ uint16_t prevEndCount = 0;
+ for (uint16_t i = 0; i < segCount; i++) {
+ const uint16_t endCount = ReadShortAt16(endCounts, i);
+ const uint16_t startCount = ReadShortAt16(startCounts, i);
+ const uint16_t idRangeOffset = ReadShortAt16(idRangeOffsets, i);
+
+ // sanity-check range
+ // This permits ranges to overlap by 1 character, which is strictly
+ // incorrect but occurs in Baskerville on OS X 10.7 (see bug 689087),
+ // and appears to be harmless in practice
+ NS_ENSURE_TRUE(startCount >= prevEndCount && startCount <= endCount,
+ NS_ERROR_GFX_CMAP_MALFORMED);
+ prevEndCount = endCount;
+
+ if (idRangeOffset == 0) {
+ // figure out if there's a code in the range that would map to
+ // glyph ID 0 (.notdef); if so, we need to skip setting that
+ // character code in the map
+ const uint16_t skipCode = 65536 - ReadShortAt16(idDeltas, i);
+ if (startCount < skipCode) {
+ aCharacterMap.SetRange(startCount,
+ std::min<uint16_t>(skipCode - 1, endCount));
+ }
+ if (skipCode < endCount) {
+ aCharacterMap.SetRange(std::max<uint16_t>(startCount, skipCode + 1),
+ endCount);
+ }
+ } else {
+ // Unused: self-documenting.
+ // const uint16_t idDelta = ReadShortAt16(idDeltas, i);
+ for (uint32_t c = startCount; c <= endCount; ++c) {
+ if (c == 0xFFFF) break;
+
+ const uint16_t* gdata =
+ (idRangeOffset / 2 + (c - startCount) + &idRangeOffsets[i]);
+
+ NS_ENSURE_TRUE(
+ (uint8_t*)gdata > aBuf && (uint8_t*)gdata < aBuf + aLength,
+ NS_ERROR_GFX_CMAP_MALFORMED);
+
+ // make sure we have a glyph
+ if (*gdata != 0) {
+ // The glyph index at this point is:
+ uint16_t glyph = ReadShortAt16(idDeltas, i) + *gdata;
+ if (glyph) {
+ aCharacterMap.set(c);
+ }
+ }
+ }
+ }
+ }
+
+ if (aIsSymbolFont) {
+ // For fonts with "MS Symbol" encoding, we duplicate character mappings in
+ // the U+F0xx range down to U+00xx codepoints, so as to support fonts such
+ // as Wingdings.
+ // Note that if the font actually has cmap coverage for the U+00xx range
+ // (either duplicating the PUA codepoints or mapping to separate glyphs),
+ // this will not affect it.
+ for (uint32_t c = 0x0020; c <= 0x00ff; ++c) {
+ if (aCharacterMap.test(0xf000 + c)) {
+ aCharacterMap.set(c);
+ }
+ }
+ }
+
+ aCharacterMap.Compact();
+
+ return NS_OK;
+}
+
+nsresult gfxFontUtils::ReadCMAPTableFormat14(const uint8_t* aBuf,
+ uint32_t aLength,
+ const uint8_t*& aTable) {
+ enum {
+ OffsetFormat = 0,
+ OffsetTableLength = 2,
+ OffsetNumVarSelectorRecords = 6,
+ OffsetVarSelectorRecords = 10,
+
+ SizeOfVarSelectorRecord = 11,
+ VSRecOffsetVarSelector = 0,
+ VSRecOffsetDefUVSOffset = 3,
+ VSRecOffsetNonDefUVSOffset = 7,
+
+ SizeOfDefUVSTable = 4,
+ DefUVSOffsetStartUnicodeValue = 0,
+ DefUVSOffsetAdditionalCount = 3,
+
+ SizeOfNonDefUVSTable = 5,
+ NonDefUVSOffsetUnicodeValue = 0,
+ NonDefUVSOffsetGlyphID = 3
+ };
+ NS_ENSURE_TRUE(aLength >= OffsetVarSelectorRecords,
+ NS_ERROR_GFX_CMAP_MALFORMED);
+
+ NS_ENSURE_TRUE(ReadShortAt(aBuf, OffsetFormat) == 14,
+ NS_ERROR_GFX_CMAP_MALFORMED);
+
+ uint32_t tablelen = ReadLongAt(aBuf, OffsetTableLength);
+ NS_ENSURE_TRUE(tablelen <= aLength, NS_ERROR_GFX_CMAP_MALFORMED);
+ NS_ENSURE_TRUE(tablelen >= OffsetVarSelectorRecords,
+ NS_ERROR_GFX_CMAP_MALFORMED);
+
+ const uint32_t numVarSelectorRecords =
+ ReadLongAt(aBuf, OffsetNumVarSelectorRecords);
+ NS_ENSURE_TRUE(
+ (tablelen - OffsetVarSelectorRecords) / SizeOfVarSelectorRecord >=
+ numVarSelectorRecords,
+ NS_ERROR_GFX_CMAP_MALFORMED);
+
+ const uint8_t* records = aBuf + OffsetVarSelectorRecords;
+ for (uint32_t i = 0; i < numVarSelectorRecords;
+ i++, records += SizeOfVarSelectorRecord) {
+ const uint32_t varSelector = ReadUint24At(records, VSRecOffsetVarSelector);
+ const uint32_t defUVSOffset = ReadLongAt(records, VSRecOffsetDefUVSOffset);
+ const uint32_t nonDefUVSOffset =
+ ReadLongAt(records, VSRecOffsetNonDefUVSOffset);
+ NS_ENSURE_TRUE(varSelector <= CMAP_MAX_CODEPOINT &&
+ defUVSOffset <= tablelen - 4 &&
+ nonDefUVSOffset <= tablelen - 4,
+ NS_ERROR_GFX_CMAP_MALFORMED);
+
+ if (defUVSOffset) {
+ const uint32_t numUnicodeValueRanges = ReadLongAt(aBuf, defUVSOffset);
+ NS_ENSURE_TRUE((tablelen - defUVSOffset) / SizeOfDefUVSTable >=
+ numUnicodeValueRanges,
+ NS_ERROR_GFX_CMAP_MALFORMED);
+ const uint8_t* tables = aBuf + defUVSOffset + 4;
+ uint32_t prevEndUnicode = 0;
+ for (uint32_t j = 0; j < numUnicodeValueRanges;
+ j++, tables += SizeOfDefUVSTable) {
+ const uint32_t startUnicode =
+ ReadUint24At(tables, DefUVSOffsetStartUnicodeValue);
+ const uint32_t endUnicode =
+ startUnicode + tables[DefUVSOffsetAdditionalCount];
+ NS_ENSURE_TRUE((prevEndUnicode < startUnicode || j == 0) &&
+ endUnicode <= CMAP_MAX_CODEPOINT,
+ NS_ERROR_GFX_CMAP_MALFORMED);
+ prevEndUnicode = endUnicode;
+ }
+ }
+
+ if (nonDefUVSOffset) {
+ const uint32_t numUVSMappings = ReadLongAt(aBuf, nonDefUVSOffset);
+ NS_ENSURE_TRUE(
+ (tablelen - nonDefUVSOffset) / SizeOfNonDefUVSTable >= numUVSMappings,
+ NS_ERROR_GFX_CMAP_MALFORMED);
+ const uint8_t* tables = aBuf + nonDefUVSOffset + 4;
+ uint32_t prevUnicode = 0;
+ for (uint32_t j = 0; j < numUVSMappings;
+ j++, tables += SizeOfNonDefUVSTable) {
+ const uint32_t unicodeValue =
+ ReadUint24At(tables, NonDefUVSOffsetUnicodeValue);
+ NS_ENSURE_TRUE((prevUnicode < unicodeValue || j == 0) &&
+ unicodeValue <= CMAP_MAX_CODEPOINT,
+ NS_ERROR_GFX_CMAP_MALFORMED);
+ prevUnicode = unicodeValue;
+ }
+ }
+ }
+
+ uint8_t* table = new uint8_t[tablelen];
+ memcpy(table, aBuf, tablelen);
+
+ aTable = static_cast<const uint8_t*>(table);
+
+ return NS_OK;
+}
+
+// For fonts with two format-4 tables, the first one (Unicode platform) is
+// preferred on the Mac; on other platforms we allow the Microsoft-platform
+// subtable to replace it.
+
+#if defined(XP_MACOSX)
+# define acceptableFormat4(p, e, k) \
+ (((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDMicrosoft && !(k)) || \
+ ((p) == PLATFORM_ID_UNICODE))
+
+# define acceptableUCS4Encoding(p, e, k) \
+ (((p) == PLATFORM_ID_MICROSOFT && \
+ (e) == EncodingIDUCS4ForMicrosoftPlatform) && \
+ (k) != 12 || \
+ ((p) == PLATFORM_ID_UNICODE && ((e) != EncodingIDUVSForUnicodePlatform)))
+#else
+# define acceptableFormat4(p, e, k) \
+ (((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDMicrosoft) || \
+ ((p) == PLATFORM_ID_UNICODE))
+
+# define acceptableUCS4Encoding(p, e, k) \
+ ((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDUCS4ForMicrosoftPlatform)
+#endif
+
+#define acceptablePlatform(p) \
+ ((p) == PLATFORM_ID_UNICODE || (p) == PLATFORM_ID_MICROSOFT)
+#define isSymbol(p, e) ((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDSymbol)
+#define isUVSEncoding(p, e) \
+ ((p) == PLATFORM_ID_UNICODE && (e) == EncodingIDUVSForUnicodePlatform)
+
+uint32_t gfxFontUtils::FindPreferredSubtable(const uint8_t* aBuf,
+ uint32_t aBufLength,
+ uint32_t* aTableOffset,
+ uint32_t* aUVSTableOffset,
+ bool* aIsSymbolFont) {
+ enum {
+ OffsetVersion = 0,
+ OffsetNumTables = 2,
+ SizeOfHeader = 4,
+
+ TableOffsetPlatformID = 0,
+ TableOffsetEncodingID = 2,
+ TableOffsetOffset = 4,
+ SizeOfTable = 8,
+
+ SubtableOffsetFormat = 0
+ };
+ enum {
+ EncodingIDSymbol = 0,
+ EncodingIDMicrosoft = 1,
+ EncodingIDDefaultForUnicodePlatform = 0,
+ EncodingIDUCS4ForUnicodePlatform = 3,
+ EncodingIDUVSForUnicodePlatform = 5,
+ EncodingIDUCS4ForMicrosoftPlatform = 10
+ };
+
+ if (aUVSTableOffset) {
+ *aUVSTableOffset = 0;
+ }
+ if (aIsSymbolFont) {
+ *aIsSymbolFont = false;
+ }
+
+ if (!aBuf || aBufLength < SizeOfHeader) {
+ // cmap table is missing, or too small to contain header fields!
+ return 0;
+ }
+
+ // uint16_t version = ReadShortAt(aBuf, OffsetVersion); // Unused:
+ // self-documenting.
+ uint16_t numTables = ReadShortAt(aBuf, OffsetNumTables);
+ if (aBufLength < uint32_t(SizeOfHeader + numTables * SizeOfTable)) {
+ return 0;
+ }
+
+ // save the format we want here
+ uint32_t keepFormat = 0;
+
+ const uint8_t* table = aBuf + SizeOfHeader;
+ for (uint16_t i = 0; i < numTables; ++i, table += SizeOfTable) {
+ const uint16_t platformID = ReadShortAt(table, TableOffsetPlatformID);
+ if (!acceptablePlatform(platformID)) continue;
+
+ const uint16_t encodingID = ReadShortAt(table, TableOffsetEncodingID);
+ const uint32_t offset = ReadLongAt(table, TableOffsetOffset);
+ if (aBufLength - 2 < offset) {
+ // this subtable is not valid - beyond end of buffer
+ return 0;
+ }
+
+ const uint8_t* subtable = aBuf + offset;
+ const uint16_t format = ReadShortAt(subtable, SubtableOffsetFormat);
+
+ if (isSymbol(platformID, encodingID)) {
+ keepFormat = format;
+ *aTableOffset = offset;
+ if (aIsSymbolFont) {
+ *aIsSymbolFont = true;
+ }
+ break;
+ } else if (format == 4 &&
+ acceptableFormat4(platformID, encodingID, keepFormat)) {
+ keepFormat = format;
+ *aTableOffset = offset;
+ } else if ((format == 10 || format == 12 || format == 13) &&
+ acceptableUCS4Encoding(platformID, encodingID, keepFormat)) {
+ keepFormat = format;
+ *aTableOffset = offset;
+ if (platformID > PLATFORM_ID_UNICODE || !aUVSTableOffset ||
+ *aUVSTableOffset) {
+ break; // we don't want to try anything else when this format is
+ // available.
+ }
+ } else if (format == 14 && isUVSEncoding(platformID, encodingID) &&
+ aUVSTableOffset) {
+ *aUVSTableOffset = offset;
+ if (keepFormat == 10 || keepFormat == 12) {
+ break;
+ }
+ }
+ }
+
+ return keepFormat;
+}
+
+nsresult gfxFontUtils::ReadCMAP(const uint8_t* aBuf, uint32_t aBufLength,
+ gfxSparseBitSet& aCharacterMap,
+ uint32_t& aUVSOffset) {
+ uint32_t offset;
+ bool isSymbolFont;
+ uint32_t format = FindPreferredSubtable(aBuf, aBufLength, &offset,
+ &aUVSOffset, &isSymbolFont);
+
+ switch (format) {
+ case 4:
+ return ReadCMAPTableFormat4(aBuf + offset, aBufLength - offset,
+ aCharacterMap, isSymbolFont);
+
+ case 10:
+ return ReadCMAPTableFormat10(aBuf + offset, aBufLength - offset,
+ aCharacterMap);
+
+ case 12:
+ case 13:
+ return ReadCMAPTableFormat12or13(aBuf + offset, aBufLength - offset,
+ aCharacterMap);
+
+ default:
+ break;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+#pragma pack(1)
+
+typedef struct {
+ AutoSwap_PRUint16 format;
+ AutoSwap_PRUint16 length;
+ AutoSwap_PRUint16 language;
+ AutoSwap_PRUint16 segCountX2;
+ AutoSwap_PRUint16 searchRange;
+ AutoSwap_PRUint16 entrySelector;
+ AutoSwap_PRUint16 rangeShift;
+
+ AutoSwap_PRUint16 arrays[1];
+} Format4Cmap;
+
+typedef struct Format14Cmap {
+ AutoSwap_PRUint16 format;
+ AutoSwap_PRUint32 length;
+ AutoSwap_PRUint32 numVarSelectorRecords;
+
+ typedef struct {
+ AutoSwap_PRUint24 varSelector;
+ AutoSwap_PRUint32 defaultUVSOffset;
+ AutoSwap_PRUint32 nonDefaultUVSOffset;
+ } VarSelectorRecord;
+
+ VarSelectorRecord varSelectorRecords[1];
+} Format14Cmap;
+
+typedef struct NonDefUVSTable {
+ AutoSwap_PRUint32 numUVSMappings;
+
+ typedef struct {
+ AutoSwap_PRUint24 unicodeValue;
+ AutoSwap_PRUint16 glyphID;
+ } UVSMapping;
+
+ UVSMapping uvsMappings[1];
+} NonDefUVSTable;
+
+#pragma pack()
+
+uint32_t gfxFontUtils::MapCharToGlyphFormat4(const uint8_t* aBuf,
+ uint32_t aLength, char16_t aCh) {
+ const Format4Cmap* cmap4 = reinterpret_cast<const Format4Cmap*>(aBuf);
+
+ uint16_t segCount = (uint16_t)(cmap4->segCountX2) / 2;
+
+ const AutoSwap_PRUint16* endCodes = &cmap4->arrays[0];
+ const AutoSwap_PRUint16* startCodes = &cmap4->arrays[segCount + 1];
+ const AutoSwap_PRUint16* idDelta = &startCodes[segCount];
+ const AutoSwap_PRUint16* idRangeOffset = &idDelta[segCount];
+
+ // Sanity-check that the fixed-size arrays don't exceed the buffer.
+ const uint8_t* const limit = aBuf + aLength;
+ if ((const uint8_t*)(&idRangeOffset[segCount]) > limit) {
+ return 0; // broken font, just bail out safely
+ }
+
+ // For most efficient binary search, we want to work on a range of segment
+ // indexes that is a power of 2 so that we can always halve it by shifting.
+ // So we find the largest power of 2 that is <= segCount.
+ // We will offset this range by segOffset so as to reach the end
+ // of the table, provided that doesn't put us beyond the target
+ // value from the outset.
+ uint32_t powerOf2 = mozilla::FindHighestBit(segCount);
+ uint32_t segOffset = segCount - powerOf2;
+ uint32_t idx = 0;
+
+ if (uint16_t(startCodes[segOffset]) <= aCh) {
+ idx = segOffset;
+ }
+
+ // Repeatedly halve the size of the range until we find the target group
+ while (powerOf2 > 1) {
+ powerOf2 >>= 1;
+ if (uint16_t(startCodes[idx + powerOf2]) <= aCh) {
+ idx += powerOf2;
+ }
+ }
+
+ if (aCh >= uint16_t(startCodes[idx]) && aCh <= uint16_t(endCodes[idx])) {
+ uint16_t result;
+ if (uint16_t(idRangeOffset[idx]) == 0) {
+ result = aCh;
+ } else {
+ uint16_t offset = aCh - uint16_t(startCodes[idx]);
+ const AutoSwap_PRUint16* glyphIndexTable =
+ (const AutoSwap_PRUint16*)((const char*)&idRangeOffset[idx] +
+ uint16_t(idRangeOffset[idx]));
+ if ((const uint8_t*)(glyphIndexTable + offset + 1) > limit) {
+ return 0; // broken font, just bail out safely
+ }
+ result = glyphIndexTable[offset];
+ }
+
+ // Note that this is unsigned 16-bit arithmetic, and may wrap around
+ // (which is required behavior per spec)
+ result += uint16_t(idDelta[idx]);
+ return result;
+ }
+
+ return 0;
+}
+
+uint32_t gfxFontUtils::MapCharToGlyphFormat10(const uint8_t* aBuf,
+ uint32_t aCh) {
+ const Format10CmapHeader* cmap10 =
+ reinterpret_cast<const Format10CmapHeader*>(aBuf);
+
+ uint32_t startChar = cmap10->startCharCode;
+ uint32_t numChars = cmap10->numChars;
+
+ if (aCh < startChar || aCh >= startChar + numChars) {
+ return 0;
+ }
+
+ const AutoSwap_PRUint16* glyphs =
+ reinterpret_cast<const AutoSwap_PRUint16*>(cmap10 + 1);
+
+ uint16_t glyph = glyphs[aCh - startChar];
+ return glyph;
+}
+
+uint32_t gfxFontUtils::MapCharToGlyphFormat12or13(const uint8_t* aBuf,
+ uint32_t aCh) {
+ // The only difference between formats 12 and 13 is the interpretation of
+ // the glyphId field. So the code here uses the same "Format12" structures,
+ // etc., to handle both subtable formats.
+
+ const Format12CmapHeader* cmap12 =
+ reinterpret_cast<const Format12CmapHeader*>(aBuf);
+
+ // We know that numGroups is within range for the subtable size
+ // because it was checked by ReadCMAPTableFormat12or13.
+ uint32_t numGroups = cmap12->numGroups;
+
+ // The array of groups immediately follows the subtable header.
+ const Format12Group* groups =
+ reinterpret_cast<const Format12Group*>(aBuf + sizeof(Format12CmapHeader));
+
+ // For most efficient binary search, we want to work on a range that
+ // is a power of 2 so that we can always halve it by shifting.
+ // So we find the largest power of 2 that is <= numGroups.
+ // We will offset this range by rangeOffset so as to reach the end
+ // of the table, provided that doesn't put us beyond the target
+ // value from the outset.
+ uint32_t powerOf2 = mozilla::FindHighestBit(numGroups);
+ uint32_t rangeOffset = numGroups - powerOf2;
+ uint32_t range = 0;
+ uint32_t startCharCode;
+
+ if (groups[rangeOffset].startCharCode <= aCh) {
+ range = rangeOffset;
+ }
+
+ // Repeatedly halve the size of the range until we find the target group
+ while (powerOf2 > 1) {
+ powerOf2 >>= 1;
+ if (groups[range + powerOf2].startCharCode <= aCh) {
+ range += powerOf2;
+ }
+ }
+
+ // Check if the character is actually present in the range and return
+ // the corresponding glyph ID. Here is where formats 12 and 13 interpret
+ // the startGlyphId (12) or glyphId (13) field differently
+ startCharCode = groups[range].startCharCode;
+ if (startCharCode <= aCh && groups[range].endCharCode >= aCh) {
+ return uint16_t(cmap12->format) == 12
+ ? uint16_t(groups[range].startGlyphId) + aCh - startCharCode
+ : uint16_t(groups[range].startGlyphId);
+ }
+
+ // Else it's not present, so return the .notdef glyph
+ return 0;
+}
+
+namespace {
+
+struct Format14CmapWrapper {
+ const Format14Cmap& mCmap14;
+ explicit Format14CmapWrapper(const Format14Cmap& cmap14) : mCmap14(cmap14) {}
+ uint32_t operator[](size_t index) const {
+ return mCmap14.varSelectorRecords[index].varSelector;
+ }
+};
+
+struct NonDefUVSTableWrapper {
+ const NonDefUVSTable& mTable;
+ explicit NonDefUVSTableWrapper(const NonDefUVSTable& table) : mTable(table) {}
+ uint32_t operator[](size_t index) const {
+ return mTable.uvsMappings[index].unicodeValue;
+ }
+};
+
+} // namespace
+
+uint16_t gfxFontUtils::MapUVSToGlyphFormat14(const uint8_t* aBuf, uint32_t aCh,
+ uint32_t aVS) {
+ using mozilla::BinarySearch;
+ const Format14Cmap* cmap14 = reinterpret_cast<const Format14Cmap*>(aBuf);
+
+ size_t index;
+ if (!BinarySearch(Format14CmapWrapper(*cmap14), 0,
+ cmap14->numVarSelectorRecords, aVS, &index)) {
+ return 0;
+ }
+
+ const uint32_t nonDefUVSOffset =
+ cmap14->varSelectorRecords[index].nonDefaultUVSOffset;
+ if (!nonDefUVSOffset) {
+ return 0;
+ }
+
+ const NonDefUVSTable* table =
+ reinterpret_cast<const NonDefUVSTable*>(aBuf + nonDefUVSOffset);
+
+ if (BinarySearch(NonDefUVSTableWrapper(*table), 0, table->numUVSMappings, aCh,
+ &index)) {
+ return table->uvsMappings[index].glyphID;
+ }
+
+ return 0;
+}
+
+uint32_t gfxFontUtils::MapCharToGlyph(const uint8_t* aCmapBuf,
+ uint32_t aBufLength, uint32_t aUnicode,
+ uint32_t aVarSelector) {
+ uint32_t offset, uvsOffset;
+ bool isSymbolFont;
+ uint32_t format = FindPreferredSubtable(aCmapBuf, aBufLength, &offset,
+ &uvsOffset, &isSymbolFont);
+
+ uint32_t gid;
+ switch (format) {
+ case 4:
+ gid = aUnicode < UNICODE_BMP_LIMIT
+ ? MapCharToGlyphFormat4(aCmapBuf + offset, aBufLength - offset,
+ char16_t(aUnicode))
+ : 0;
+ if (!gid && isSymbolFont) {
+ if (auto pua = MapLegacySymbolFontCharToPUA(aUnicode)) {
+ gid = MapCharToGlyphFormat4(aCmapBuf + offset, aBufLength - offset,
+ pua);
+ }
+ }
+ break;
+ case 10:
+ gid = MapCharToGlyphFormat10(aCmapBuf + offset, aUnicode);
+ break;
+ case 12:
+ case 13:
+ gid = MapCharToGlyphFormat12or13(aCmapBuf + offset, aUnicode);
+ break;
+ default:
+ NS_WARNING("unsupported cmap format, glyphs will be missing");
+ gid = 0;
+ }
+
+ if (aVarSelector && uvsOffset && gid) {
+ uint32_t varGID = gfxFontUtils::MapUVSToGlyphFormat14(
+ aCmapBuf + uvsOffset, aUnicode, aVarSelector);
+ if (!varGID) {
+ aUnicode = gfxFontUtils::GetUVSFallback(aUnicode, aVarSelector);
+ if (aUnicode) {
+ switch (format) {
+ case 4:
+ if (aUnicode < UNICODE_BMP_LIMIT) {
+ varGID = MapCharToGlyphFormat4(
+ aCmapBuf + offset, aBufLength - offset, char16_t(aUnicode));
+ }
+ break;
+ case 10:
+ varGID = MapCharToGlyphFormat10(aCmapBuf + offset, aUnicode);
+ break;
+ case 12:
+ case 13:
+ varGID = MapCharToGlyphFormat12or13(aCmapBuf + offset, aUnicode);
+ break;
+ }
+ }
+ }
+ if (varGID) {
+ gid = varGID;
+ }
+
+ // else the variation sequence was not supported, use default mapping
+ // of the character code alone
+ }
+
+ return gid;
+}
+
+void gfxFontUtils::ParseFontList(const nsACString& aFamilyList,
+ nsTArray<nsCString>& aFontList) {
+ const char kComma = ',';
+
+ // append each font name to the list
+ nsAutoCString fontname;
+ const char *p, *p_end;
+ aFamilyList.BeginReading(p);
+ aFamilyList.EndReading(p_end);
+
+ while (p < p_end) {
+ const char* nameStart = p;
+ while (++p != p_end && *p != kComma) /* nothing */
+ ;
+
+ // pull out a single name and clean out leading/trailing whitespace
+ fontname = Substring(nameStart, p);
+ fontname.CompressWhitespace(true, true);
+
+ // append it to the list if it's not empty
+ if (!fontname.IsEmpty()) {
+ aFontList.AppendElement(fontname);
+ }
+ ++p;
+ }
+}
+
+void gfxFontUtils::GetPrefsFontList(const char* aPrefName,
+ nsTArray<nsCString>& aFontList,
+ bool aLocalized) {
+ aFontList.Clear();
+
+ nsAutoCString fontlistValue;
+ nsresult rv = aLocalized
+ ? Preferences::GetLocalizedCString(aPrefName, fontlistValue)
+ : Preferences::GetCString(aPrefName, fontlistValue);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ ParseFontList(fontlistValue, aFontList);
+}
+
+// produce a unique font name that is (1) a valid Postscript name and (2) less
+// than 31 characters in length. Using AddFontMemResourceEx on Windows fails
+// for names longer than 30 characters in length.
+
+#define MAX_B64_LEN 32
+
+nsresult gfxFontUtils::MakeUniqueUserFontName(nsAString& aName) {
+ nsCOMPtr<nsIUUIDGenerator> uuidgen =
+ do_GetService("@mozilla.org/uuid-generator;1");
+ NS_ENSURE_TRUE(uuidgen, NS_ERROR_OUT_OF_MEMORY);
+
+ nsID guid;
+
+ NS_ASSERTION(sizeof(guid) * 2 <= MAX_B64_LEN, "size of nsID has changed!");
+
+ nsresult rv = uuidgen->GenerateUUIDInPlace(&guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char guidB64[MAX_B64_LEN] = {0};
+
+ if (!PL_Base64Encode(reinterpret_cast<char*>(&guid), sizeof(guid), guidB64))
+ return NS_ERROR_FAILURE;
+
+ // all b64 characters except for '/' are allowed in Postscript names, so
+ // convert / ==> -
+ char* p;
+ for (p = guidB64; *p; p++) {
+ if (*p == '/') *p = '-';
+ }
+
+ aName.AssignLiteral(u"uf");
+ aName.AppendASCII(guidB64);
+ return NS_OK;
+}
+
+// TrueType/OpenType table handling code
+
+// need byte aligned structs
+#pragma pack(1)
+
+// name table stores set of name record structures, followed by
+// large block containing all the strings. name record offset and length
+// indicates the offset and length within that block.
+// http://www.microsoft.com/typography/otspec/name.htm
+struct NameRecordData {
+ uint32_t offset;
+ uint32_t length;
+};
+
+#pragma pack()
+
+static bool IsValidSFNTVersion(uint32_t version) {
+ // normally 0x00010000, CFF-style OT fonts == 'OTTO' and Apple TT fonts =
+ // 'true' 'typ1' is also possible for old Type 1 fonts in a SFNT container but
+ // not supported
+ return version == 0x10000 || version == TRUETYPE_TAG('O', 'T', 'T', 'O') ||
+ version == TRUETYPE_TAG('t', 'r', 'u', 'e');
+}
+
+gfxUserFontType gfxFontUtils::DetermineFontDataType(const uint8_t* aFontData,
+ uint32_t aFontDataLength) {
+ // test for OpenType font data
+ // problem: EOT-Lite with 0x10000 length will look like TrueType!
+ if (aFontDataLength >= sizeof(SFNTHeader)) {
+ const SFNTHeader* sfntHeader =
+ reinterpret_cast<const SFNTHeader*>(aFontData);
+ uint32_t sfntVersion = sfntHeader->sfntVersion;
+ if (IsValidSFNTVersion(sfntVersion)) {
+ return GFX_USERFONT_OPENTYPE;
+ }
+ }
+
+ // test for WOFF or WOFF2
+ if (aFontDataLength >= sizeof(AutoSwap_PRUint32)) {
+ const AutoSwap_PRUint32* version =
+ reinterpret_cast<const AutoSwap_PRUint32*>(aFontData);
+ if (uint32_t(*version) == TRUETYPE_TAG('w', 'O', 'F', 'F')) {
+ return GFX_USERFONT_WOFF;
+ }
+ if (uint32_t(*version) == TRUETYPE_TAG('w', 'O', 'F', '2')) {
+ return GFX_USERFONT_WOFF2;
+ }
+ }
+
+ // tests for other formats here
+
+ return GFX_USERFONT_UNKNOWN;
+}
+
+static int DirEntryCmp(const void* aKey, const void* aItem) {
+ int32_t tag = *static_cast<const int32_t*>(aKey);
+ const TableDirEntry* entry = static_cast<const TableDirEntry*>(aItem);
+ return tag - int32_t(entry->tag);
+}
+
+/* static */
+TableDirEntry* gfxFontUtils::FindTableDirEntry(const void* aFontData,
+ uint32_t aTableTag) {
+ const SFNTHeader* header = reinterpret_cast<const SFNTHeader*>(aFontData);
+ const TableDirEntry* dir = reinterpret_cast<const TableDirEntry*>(header + 1);
+ return static_cast<TableDirEntry*>(
+ bsearch(&aTableTag, dir, uint16_t(header->numTables),
+ sizeof(TableDirEntry), DirEntryCmp));
+}
+
+/* static */
+hb_blob_t* gfxFontUtils::GetTableFromFontData(const void* aFontData,
+ uint32_t aTableTag) {
+ const TableDirEntry* dir = FindTableDirEntry(aFontData, aTableTag);
+ if (dir) {
+ return hb_blob_create(
+ reinterpret_cast<const char*>(aFontData) + dir->offset, dir->length,
+ HB_MEMORY_MODE_READONLY, nullptr, nullptr);
+ }
+ return nullptr;
+}
+
+nsresult gfxFontUtils::RenameFont(const nsAString& aName,
+ const uint8_t* aFontData,
+ uint32_t aFontDataLength,
+ FallibleTArray<uint8_t>* aNewFont) {
+ NS_ASSERTION(aNewFont, "null font data array");
+
+ uint64_t dataLength(aFontDataLength);
+
+ // new name table
+ static const uint32_t neededNameIDs[] = {NAME_ID_FAMILY, NAME_ID_STYLE,
+ NAME_ID_UNIQUE, NAME_ID_FULL,
+ NAME_ID_POSTSCRIPT};
+
+ // calculate new name table size
+ uint16_t nameCount = ArrayLength(neededNameIDs);
+
+ // leave room for null-terminator
+ uint32_t nameStrLength = (aName.Length() + 1) * sizeof(char16_t);
+ if (nameStrLength > 65535) {
+ // The name length _in bytes_ must fit in an unsigned short field;
+ // therefore, a name longer than this cannot be used.
+ return NS_ERROR_FAILURE;
+ }
+
+ // round name table size up to 4-byte multiple
+ uint32_t nameTableSize =
+ (sizeof(NameHeader) + sizeof(NameRecord) * nameCount + nameStrLength +
+ 3) &
+ ~3;
+
+ if (dataLength + nameTableSize > UINT32_MAX) return NS_ERROR_FAILURE;
+
+ // bug 505386 - need to handle unpadded font length
+ uint32_t paddedFontDataSize = (aFontDataLength + 3) & ~3;
+ uint32_t adjFontDataSize = paddedFontDataSize + nameTableSize;
+
+ // create new buffer: old font data plus new name table
+ if (!aNewFont->AppendElements(adjFontDataSize, fallible))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // copy the old font data
+ uint8_t* newFontData = reinterpret_cast<uint8_t*>(aNewFont->Elements());
+
+ // null the last four bytes in case the font length is not a multiple of 4
+ memset(newFontData + aFontDataLength, 0,
+ paddedFontDataSize - aFontDataLength);
+
+ // copy font data
+ memcpy(newFontData, aFontData, aFontDataLength);
+
+ // null out the last 4 bytes for checksum calculations
+ memset(newFontData + adjFontDataSize - 4, 0, 4);
+
+ NameHeader* nameHeader =
+ reinterpret_cast<NameHeader*>(newFontData + paddedFontDataSize);
+
+ // -- name header
+ nameHeader->format = 0;
+ nameHeader->count = nameCount;
+ nameHeader->stringOffset =
+ sizeof(NameHeader) + nameCount * sizeof(NameRecord);
+
+ // -- name records
+ uint32_t i;
+ NameRecord* nameRecord = reinterpret_cast<NameRecord*>(nameHeader + 1);
+
+ for (i = 0; i < nameCount; i++, nameRecord++) {
+ nameRecord->platformID = PLATFORM_ID_MICROSOFT;
+ nameRecord->encodingID = ENCODING_ID_MICROSOFT_UNICODEBMP;
+ nameRecord->languageID = LANG_ID_MICROSOFT_EN_US;
+ nameRecord->nameID = neededNameIDs[i];
+ nameRecord->offset = 0;
+ nameRecord->length = nameStrLength;
+ }
+
+ // -- string data, located after the name records, stored in big-endian form
+ char16_t* strData = reinterpret_cast<char16_t*>(nameRecord);
+
+ mozilla::NativeEndian::copyAndSwapToBigEndian(strData, aName.BeginReading(),
+ aName.Length());
+ strData[aName.Length()] = 0; // add null termination
+
+ // adjust name table header to point to the new name table
+ SFNTHeader* sfntHeader = reinterpret_cast<SFNTHeader*>(newFontData);
+
+ // table directory entries begin immediately following SFNT header
+ TableDirEntry* dirEntry =
+ FindTableDirEntry(newFontData, TRUETYPE_TAG('n', 'a', 'm', 'e'));
+ // function only called if font validates, so this should always be true
+ MOZ_ASSERT(dirEntry, "attempt to rename font with no name table");
+
+ uint32_t numTables = sfntHeader->numTables;
+
+ // note: dirEntry now points to 'name' table record
+
+ // recalculate name table checksum
+ uint32_t checkSum = 0;
+ AutoSwap_PRUint32* nameData =
+ reinterpret_cast<AutoSwap_PRUint32*>(nameHeader);
+ AutoSwap_PRUint32* nameDataEnd = nameData + (nameTableSize >> 2);
+
+ while (nameData < nameDataEnd) checkSum = checkSum + *nameData++;
+
+ // adjust name table entry to point to new name table
+ dirEntry->offset = paddedFontDataSize;
+ dirEntry->length = nameTableSize;
+ dirEntry->checkSum = checkSum;
+
+ // fix up checksums
+ uint32_t checksum = 0;
+
+ // checksum for font = (checksum of header) + (checksum of tables)
+ uint32_t headerLen = sizeof(SFNTHeader) + sizeof(TableDirEntry) * numTables;
+ const AutoSwap_PRUint32* headerData =
+ reinterpret_cast<const AutoSwap_PRUint32*>(newFontData);
+
+ // header length is in bytes, checksum calculated in longwords
+ for (i = 0; i < (headerLen >> 2); i++, headerData++) {
+ checksum += *headerData;
+ }
+
+ uint32_t headOffset = 0;
+ dirEntry = reinterpret_cast<TableDirEntry*>(newFontData + sizeof(SFNTHeader));
+
+ for (i = 0; i < numTables; i++, dirEntry++) {
+ if (dirEntry->tag == TRUETYPE_TAG('h', 'e', 'a', 'd')) {
+ headOffset = dirEntry->offset;
+ }
+ checksum += dirEntry->checkSum;
+ }
+
+ NS_ASSERTION(headOffset != 0, "no head table for font");
+
+ HeadTable* headData = reinterpret_cast<HeadTable*>(newFontData + headOffset);
+
+ headData->checkSumAdjustment = HeadTable::HEAD_CHECKSUM_CALC_CONST - checksum;
+
+ return NS_OK;
+}
+
+// This is only called after the basic validity of the downloaded sfnt
+// data has been checked, so it should never fail to find the name table
+// (though it might fail to read it, if memory isn't available);
+// other checks here are just for extra paranoia.
+nsresult gfxFontUtils::GetFullNameFromSFNT(const uint8_t* aFontData,
+ uint32_t aLength,
+ nsACString& aFullName) {
+ aFullName = "(MISSING NAME)"; // should always get replaced
+
+ const TableDirEntry* dirEntry =
+ FindTableDirEntry(aFontData, TRUETYPE_TAG('n', 'a', 'm', 'e'));
+
+ // should never fail, as we're only called after font validation succeeded
+ NS_ENSURE_TRUE(dirEntry, NS_ERROR_NOT_AVAILABLE);
+
+ uint32_t len = dirEntry->length;
+ NS_ENSURE_TRUE(aLength > len && aLength - len >= dirEntry->offset,
+ NS_ERROR_UNEXPECTED);
+
+ AutoHBBlob nameBlob(hb_blob_create((const char*)aFontData + dirEntry->offset,
+ len, HB_MEMORY_MODE_READONLY, nullptr,
+ nullptr));
+ nsresult rv = GetFullNameFromTable(nameBlob, aFullName);
+
+ return rv;
+}
+
+nsresult gfxFontUtils::GetFullNameFromTable(hb_blob_t* aNameTable,
+ nsACString& aFullName) {
+ nsAutoCString name;
+ nsresult rv = gfxFontUtils::ReadCanonicalName(
+ aNameTable, gfxFontUtils::NAME_ID_FULL, name);
+ if (NS_SUCCEEDED(rv) && !name.IsEmpty()) {
+ aFullName = name;
+ return NS_OK;
+ }
+ rv = gfxFontUtils::ReadCanonicalName(aNameTable, gfxFontUtils::NAME_ID_FAMILY,
+ name);
+ if (NS_SUCCEEDED(rv) && !name.IsEmpty()) {
+ nsAutoCString styleName;
+ rv = gfxFontUtils::ReadCanonicalName(
+ aNameTable, gfxFontUtils::NAME_ID_STYLE, styleName);
+ if (NS_SUCCEEDED(rv) && !styleName.IsEmpty()) {
+ name.Append(' ');
+ name.Append(styleName);
+ aFullName = name;
+ }
+ return NS_OK;
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult gfxFontUtils::GetFamilyNameFromTable(hb_blob_t* aNameTable,
+ nsACString& aFamilyName) {
+ nsAutoCString name;
+ nsresult rv = gfxFontUtils::ReadCanonicalName(
+ aNameTable, gfxFontUtils::NAME_ID_FAMILY, name);
+ if (NS_SUCCEEDED(rv) && !name.IsEmpty()) {
+ aFamilyName = name;
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+enum {
+#if defined(XP_MACOSX)
+ CANONICAL_LANG_ID = gfxFontUtils::LANG_ID_MAC_ENGLISH,
+ PLATFORM_ID = gfxFontUtils::PLATFORM_ID_MAC
+#else
+ CANONICAL_LANG_ID = gfxFontUtils::LANG_ID_MICROSOFT_EN_US,
+ PLATFORM_ID = gfxFontUtils::PLATFORM_ID_MICROSOFT
+#endif
+};
+
+nsresult gfxFontUtils::ReadNames(const char* aNameData, uint32_t aDataLen,
+ uint32_t aNameID, int32_t aPlatformID,
+ nsTArray<nsCString>& aNames) {
+ return ReadNames(aNameData, aDataLen, aNameID, LANG_ALL, aPlatformID, aNames);
+}
+
+nsresult gfxFontUtils::ReadCanonicalName(hb_blob_t* aNameTable,
+ uint32_t aNameID, nsCString& aName) {
+ uint32_t nameTableLen;
+ const char* nameTable = hb_blob_get_data(aNameTable, &nameTableLen);
+ return ReadCanonicalName(nameTable, nameTableLen, aNameID, aName);
+}
+
+nsresult gfxFontUtils::ReadCanonicalName(const char* aNameData,
+ uint32_t aDataLen, uint32_t aNameID,
+ nsCString& aName) {
+ nsresult rv;
+
+ nsTArray<nsCString> names;
+
+ // first, look for the English name (this will succeed 99% of the time)
+ rv = ReadNames(aNameData, aDataLen, aNameID, CANONICAL_LANG_ID, PLATFORM_ID,
+ names);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // otherwise, grab names for all languages
+ if (names.Length() == 0) {
+ rv = ReadNames(aNameData, aDataLen, aNameID, LANG_ALL, PLATFORM_ID, names);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+#if defined(XP_MACOSX)
+ // may be dealing with font that only has Microsoft name entries
+ if (names.Length() == 0) {
+ rv = ReadNames(aNameData, aDataLen, aNameID, LANG_ID_MICROSOFT_EN_US,
+ PLATFORM_ID_MICROSOFT, names);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // getting really desperate now, take anything!
+ if (names.Length() == 0) {
+ rv = ReadNames(aNameData, aDataLen, aNameID, LANG_ALL,
+ PLATFORM_ID_MICROSOFT, names);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+#endif
+
+ // return the first name (99.9% of the time names will
+ // contain a single English name)
+ if (names.Length()) {
+ aName.Assign(names[0]);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+// Charsets to use for decoding Mac platform font names.
+// This table is sorted by {encoding, language}, with the wildcard "ANY" being
+// greater than any defined values for each field; we use a binary search on
+// both fields, and fall back to matching only encoding if necessary
+
+// Some "redundant" entries for specific combinations are included such as
+// encoding=roman, lang=english, in order that common entries will be found
+// on the first search.
+
+const uint16_t ANY = 0xffff;
+const gfxFontUtils::MacFontNameCharsetMapping
+ gfxFontUtils::gMacFontNameCharsets[] = {
+ {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_ENGLISH, MACINTOSH_ENCODING},
+ {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_ICELANDIC, X_USER_DEFINED_ENCODING},
+ {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_TURKISH, X_USER_DEFINED_ENCODING},
+ {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_POLISH, X_USER_DEFINED_ENCODING},
+ {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_ROMANIAN, X_USER_DEFINED_ENCODING},
+ {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_CZECH, X_USER_DEFINED_ENCODING},
+ {ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_SLOVAK, X_USER_DEFINED_ENCODING},
+ {ENCODING_ID_MAC_ROMAN, ANY, MACINTOSH_ENCODING},
+ {ENCODING_ID_MAC_JAPANESE, LANG_ID_MAC_JAPANESE, SHIFT_JIS_ENCODING},
+ {ENCODING_ID_MAC_JAPANESE, ANY, SHIFT_JIS_ENCODING},
+ {ENCODING_ID_MAC_TRAD_CHINESE, LANG_ID_MAC_TRAD_CHINESE, BIG5_ENCODING},
+ {ENCODING_ID_MAC_TRAD_CHINESE, ANY, BIG5_ENCODING},
+ {ENCODING_ID_MAC_KOREAN, LANG_ID_MAC_KOREAN, EUC_KR_ENCODING},
+ {ENCODING_ID_MAC_KOREAN, ANY, EUC_KR_ENCODING},
+ {ENCODING_ID_MAC_ARABIC, LANG_ID_MAC_ARABIC, X_USER_DEFINED_ENCODING},
+ {ENCODING_ID_MAC_ARABIC, LANG_ID_MAC_URDU, X_USER_DEFINED_ENCODING},
+ {ENCODING_ID_MAC_ARABIC, LANG_ID_MAC_FARSI, X_USER_DEFINED_ENCODING},
+ {ENCODING_ID_MAC_ARABIC, ANY, X_USER_DEFINED_ENCODING},
+ {ENCODING_ID_MAC_HEBREW, LANG_ID_MAC_HEBREW, X_USER_DEFINED_ENCODING},
+ {ENCODING_ID_MAC_HEBREW, ANY, X_USER_DEFINED_ENCODING},
+ {ENCODING_ID_MAC_GREEK, ANY, X_USER_DEFINED_ENCODING},
+ {ENCODING_ID_MAC_CYRILLIC, ANY, X_MAC_CYRILLIC_ENCODING},
+ {ENCODING_ID_MAC_DEVANAGARI, ANY, X_USER_DEFINED_ENCODING},
+ {ENCODING_ID_MAC_GURMUKHI, ANY, X_USER_DEFINED_ENCODING},
+ {ENCODING_ID_MAC_GUJARATI, ANY, X_USER_DEFINED_ENCODING},
+ {ENCODING_ID_MAC_SIMP_CHINESE, LANG_ID_MAC_SIMP_CHINESE,
+ GB18030_ENCODING},
+ {ENCODING_ID_MAC_SIMP_CHINESE, ANY, GB18030_ENCODING}};
+
+const Encoding* gfxFontUtils::gISOFontNameCharsets[] = {
+ /* 0 */ WINDOWS_1252_ENCODING, /* US-ASCII */
+ /* 1 */ nullptr, /* spec says "ISO 10646" but does not specify encoding
+ form! */
+ /* 2 */ WINDOWS_1252_ENCODING /* ISO-8859-1 */
+};
+
+const Encoding* gfxFontUtils::gMSFontNameCharsets[] = {
+ /* [0] ENCODING_ID_MICROSOFT_SYMBOL */ UTF_16BE_ENCODING,
+ /* [1] ENCODING_ID_MICROSOFT_UNICODEBMP */ UTF_16BE_ENCODING,
+ /* [2] ENCODING_ID_MICROSOFT_SHIFTJIS */ SHIFT_JIS_ENCODING,
+ /* [3] ENCODING_ID_MICROSOFT_PRC */ nullptr,
+ /* [4] ENCODING_ID_MICROSOFT_BIG5 */ BIG5_ENCODING,
+ /* [5] ENCODING_ID_MICROSOFT_WANSUNG */ nullptr,
+ /* [6] ENCODING_ID_MICROSOFT_JOHAB */ nullptr,
+ /* [7] reserved */ nullptr,
+ /* [8] reserved */ nullptr,
+ /* [9] reserved */ nullptr,
+ /*[10] ENCODING_ID_MICROSOFT_UNICODEFULL */ UTF_16BE_ENCODING};
+
+struct MacCharsetMappingComparator {
+ typedef gfxFontUtils::MacFontNameCharsetMapping MacFontNameCharsetMapping;
+ const MacFontNameCharsetMapping& mSearchValue;
+ explicit MacCharsetMappingComparator(
+ const MacFontNameCharsetMapping& aSearchValue)
+ : mSearchValue(aSearchValue) {}
+ int operator()(const MacFontNameCharsetMapping& aEntry) const {
+ if (mSearchValue < aEntry) {
+ return -1;
+ }
+ if (aEntry < mSearchValue) {
+ return 1;
+ }
+ return 0;
+ }
+};
+
+// Return the Encoding object we should use to decode a font name
+// given the name table attributes.
+// Special return values:
+// X_USER_DEFINED_ENCODING One of Mac legacy encodings that is not a part
+// of Encoding Standard
+// nullptr unknown charset, do not attempt conversion
+const Encoding* gfxFontUtils::GetCharsetForFontName(uint16_t aPlatform,
+ uint16_t aScript,
+ uint16_t aLanguage) {
+ switch (aPlatform) {
+ case PLATFORM_ID_UNICODE:
+ return UTF_16BE_ENCODING;
+
+ case PLATFORM_ID_MAC: {
+ MacFontNameCharsetMapping searchValue = {aScript, aLanguage, nullptr};
+ for (uint32_t i = 0; i < 2; ++i) {
+ size_t idx;
+ if (BinarySearchIf(gMacFontNameCharsets, 0,
+ ArrayLength(gMacFontNameCharsets),
+ MacCharsetMappingComparator(searchValue), &idx)) {
+ return gMacFontNameCharsets[idx].mEncoding;
+ }
+
+ // no match, so try again finding one in any language
+ searchValue.mLanguage = ANY;
+ }
+ } break;
+
+ case PLATFORM_ID_ISO:
+ if (aScript < ArrayLength(gISOFontNameCharsets)) {
+ return gISOFontNameCharsets[aScript];
+ }
+ break;
+
+ case PLATFORM_ID_MICROSOFT:
+ if (aScript < ArrayLength(gMSFontNameCharsets)) {
+ return gMSFontNameCharsets[aScript];
+ }
+ break;
+ }
+
+ return nullptr;
+}
+
+template <int N>
+static bool StartsWith(const nsACString& string, const char (&prefix)[N]) {
+ if (N - 1 > string.Length()) {
+ return false;
+ }
+ return memcmp(string.Data(), prefix, N - 1) == 0;
+}
+
+// convert a raw name from the name table to an nsString, if possible;
+// return value indicates whether conversion succeeded
+bool gfxFontUtils::DecodeFontName(const char* aNameData, int32_t aByteLen,
+ uint32_t aPlatformCode, uint32_t aScriptCode,
+ uint32_t aLangCode, nsACString& aName) {
+ if (aByteLen <= 0) {
+ NS_WARNING("empty font name");
+ aName.SetLength(0);
+ return true;
+ }
+
+ auto encoding = GetCharsetForFontName(aPlatformCode, aScriptCode, aLangCode);
+
+ if (!encoding) {
+ // nullptr -> unknown charset
+#ifdef DEBUG
+ char warnBuf[128];
+ if (aByteLen > 64) aByteLen = 64;
+ SprintfLiteral(warnBuf,
+ "skipping font name, unknown charset %d:%d:%d for <%.*s>",
+ aPlatformCode, aScriptCode, aLangCode, aByteLen, aNameData);
+ NS_WARNING(warnBuf);
+#endif
+ return false;
+ }
+
+ if (encoding == X_USER_DEFINED_ENCODING) {
+#ifdef XP_MACOSX
+ // Special case for macOS only: support legacy Mac encodings
+ // that aren't part of the Encoding Standard.
+ if (aPlatformCode == PLATFORM_ID_MAC) {
+ CFStringRef str =
+ CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)aNameData,
+ aByteLen, aScriptCode, false);
+ if (str) {
+ CFIndex length = CFStringGetLength(str);
+ nsAutoString name16;
+ name16.SetLength(length);
+ CFStringGetCharacters(str, CFRangeMake(0, length),
+ (UniChar*)name16.BeginWriting());
+ CFRelease(str);
+ CopyUTF16toUTF8(name16, aName);
+ return true;
+ }
+ }
+#endif
+ NS_WARNING("failed to get the decoder for a font name string");
+ return false;
+ }
+
+ auto rv = encoding->DecodeWithoutBOMHandling(
+ nsDependentCSubstring(aNameData, aByteLen), aName);
+ return NS_SUCCEEDED(rv);
+}
+
+nsresult gfxFontUtils::ReadNames(const char* aNameData, uint32_t aDataLen,
+ uint32_t aNameID, int32_t aLangID,
+ int32_t aPlatformID,
+ nsTArray<nsCString>& aNames) {
+ NS_ASSERTION(aDataLen != 0, "null name table");
+
+ if (!aDataLen) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // -- name table data
+ const NameHeader* nameHeader = reinterpret_cast<const NameHeader*>(aNameData);
+
+ uint32_t nameCount = nameHeader->count;
+
+ // -- sanity check the number of name records
+ if (uint64_t(nameCount) * sizeof(NameRecord) > aDataLen) {
+ NS_WARNING("invalid font (name table data)");
+ return NS_ERROR_FAILURE;
+ }
+
+ // -- iterate through name records
+ const NameRecord* nameRecord =
+ reinterpret_cast<const NameRecord*>(aNameData + sizeof(NameHeader));
+ uint64_t nameStringsBase = uint64_t(nameHeader->stringOffset);
+
+ uint32_t i;
+ for (i = 0; i < nameCount; i++, nameRecord++) {
+ uint32_t platformID;
+
+ // skip over unwanted nameID's
+ if (uint32_t(nameRecord->nameID) != aNameID) {
+ continue;
+ }
+
+ // skip over unwanted platform data
+ platformID = nameRecord->platformID;
+ if (aPlatformID != PLATFORM_ALL && platformID != uint32_t(aPlatformID)) {
+ continue;
+ }
+
+ // skip over unwanted languages
+ if (aLangID != LANG_ALL &&
+ uint32_t(nameRecord->languageID) != uint32_t(aLangID)) {
+ continue;
+ }
+
+ // add name to names array
+
+ // -- calculate string location
+ uint32_t namelen = nameRecord->length;
+ uint32_t nameoff =
+ nameRecord->offset; // offset from base of string storage
+
+ if (nameStringsBase + uint64_t(nameoff) + uint64_t(namelen) > aDataLen) {
+ NS_WARNING("invalid font (name table strings)");
+ return NS_ERROR_FAILURE;
+ }
+
+ // -- decode if necessary and make nsString
+ nsAutoCString name;
+
+ DecodeFontName(aNameData + nameStringsBase + nameoff, namelen, platformID,
+ uint32_t(nameRecord->encodingID),
+ uint32_t(nameRecord->languageID), name);
+
+ uint32_t k, numNames;
+ bool foundName = false;
+
+ numNames = aNames.Length();
+ for (k = 0; k < numNames; k++) {
+ if (name.Equals(aNames[k])) {
+ foundName = true;
+ break;
+ }
+ }
+
+ if (!foundName) aNames.AppendElement(name);
+ }
+
+ return NS_OK;
+}
+
+void gfxFontUtils::GetVariationData(
+ gfxFontEntry* aFontEntry, nsTArray<gfxFontVariationAxis>* aAxes,
+ nsTArray<gfxFontVariationInstance>* aInstances) {
+ MOZ_ASSERT(!aAxes || aAxes->IsEmpty());
+ MOZ_ASSERT(!aInstances || aInstances->IsEmpty());
+
+ if (!aFontEntry->HasVariations()) {
+ return;
+ }
+
+ // Some platforms don't offer a simple API to return the list of instances,
+ // so we have to interpret the 'fvar' table ourselves.
+
+ // https://www.microsoft.com/typography/otspec/fvar.htm#fvarHeader
+ struct FvarHeader {
+ AutoSwap_PRUint16 majorVersion;
+ AutoSwap_PRUint16 minorVersion;
+ AutoSwap_PRUint16 axesArrayOffset;
+ AutoSwap_PRUint16 reserved;
+ AutoSwap_PRUint16 axisCount;
+ AutoSwap_PRUint16 axisSize;
+ AutoSwap_PRUint16 instanceCount;
+ AutoSwap_PRUint16 instanceSize;
+ };
+
+ // https://www.microsoft.com/typography/otspec/fvar.htm#variationAxisRecord
+ struct AxisRecord {
+ AutoSwap_PRUint32 axisTag;
+ AutoSwap_PRInt32 minValue;
+ AutoSwap_PRInt32 defaultValue;
+ AutoSwap_PRInt32 maxValue;
+ AutoSwap_PRUint16 flags;
+ AutoSwap_PRUint16 axisNameID;
+ };
+ const uint16_t HIDDEN_AXIS = 0x0001; // AxisRecord flags value
+
+ // https://www.microsoft.com/typography/otspec/fvar.htm#instanceRecord
+ struct InstanceRecord {
+ AutoSwap_PRUint16 subfamilyNameID;
+ AutoSwap_PRUint16 flags;
+ AutoSwap_PRInt32 coordinates[1]; // variable-size array [axisCount]
+ // The variable-length 'coordinates' array may be followed by an
+ // optional extra field 'postScriptNameID'. We can't directly
+ // represent this in the struct, because its offset varies depending
+ // on the number of axes present.
+ // (Not currently used by our code here anyhow.)
+ // AutoSwap_PRUint16 postScriptNameID;
+ };
+
+ // Load the two font tables we need as harfbuzz blobs; if either is absent,
+ // just bail out.
+ AutoHBBlob fvarTable(
+ aFontEntry->GetFontTable(TRUETYPE_TAG('f', 'v', 'a', 'r')));
+ AutoHBBlob nameTable(
+ aFontEntry->GetFontTable(TRUETYPE_TAG('n', 'a', 'm', 'e')));
+ if (!fvarTable || !nameTable) {
+ return;
+ }
+ unsigned int len;
+ const char* data = hb_blob_get_data(fvarTable, &len);
+ if (len < sizeof(FvarHeader)) {
+ return;
+ }
+ // Read the fields of the table header; bail out if it looks broken.
+ auto fvar = reinterpret_cast<const FvarHeader*>(data);
+ if (uint16_t(fvar->majorVersion) != 1 || uint16_t(fvar->minorVersion) != 0 ||
+ uint16_t(fvar->reserved) != 2) {
+ return;
+ }
+ uint16_t axisCount = fvar->axisCount;
+ uint16_t axisSize = fvar->axisSize;
+ uint16_t instanceCount = fvar->instanceCount;
+ uint16_t instanceSize = fvar->instanceSize;
+ if (axisCount ==
+ 0 || // no axes?
+ // https://www.microsoft.com/typography/otspec/fvar.htm#axisSize
+ axisSize != 20 || // required value for current table version
+ // https://www.microsoft.com/typography/otspec/fvar.htm#instanceSize
+ (instanceSize != axisCount * sizeof(int32_t) + 4 &&
+ instanceSize != axisCount * sizeof(int32_t) + 6)) {
+ return;
+ }
+ // Check that axis array will not exceed table size
+ uint16_t axesOffset = fvar->axesArrayOffset;
+ if (axesOffset + uint32_t(axisCount) * axisSize > len) {
+ return;
+ }
+ // Get pointer to the array of axis records
+ auto axes = reinterpret_cast<const AxisRecord*>(data + axesOffset);
+ // Get address of instance array, and check it doesn't overflow table size.
+ // https://www.microsoft.com/typography/otspec/fvar.htm#axisAndInstanceArrays
+ auto instData = data + axesOffset + axisCount * axisSize;
+ if (instData + uint32_t(instanceCount) * instanceSize > data + len) {
+ return;
+ }
+ if (aInstances) {
+ aInstances->SetCapacity(instanceCount);
+ for (unsigned i = 0; i < instanceCount; ++i, instData += instanceSize) {
+ // Typed pointer to the current instance record, to read its fields.
+ auto inst = reinterpret_cast<const InstanceRecord*>(instData);
+ // Pointer to the coordinates array within the instance record.
+ // This array has axisCount elements, and is included in instanceSize
+ // (which depends on axisCount, and was validated above) so we know
+ // access to coords[j] below will not be outside the table bounds.
+ auto coords = &inst->coordinates[0];
+ gfxFontVariationInstance instance;
+ uint16_t nameID = inst->subfamilyNameID;
+ nsresult rv = ReadCanonicalName(nameTable, nameID, instance.mName);
+ if (NS_FAILED(rv)) {
+ // If no name was available for the instance, ignore it.
+ continue;
+ }
+ instance.mValues.SetCapacity(axisCount);
+ for (unsigned j = 0; j < axisCount; ++j) {
+ gfxFontVariationValue value = {axes[j].axisTag,
+ int32_t(coords[j]) / 65536.0f};
+ instance.mValues.AppendElement(value);
+ }
+ aInstances->AppendElement(std::move(instance));
+ }
+ }
+ if (aAxes) {
+ aAxes->SetCapacity(axisCount);
+ for (unsigned i = 0; i < axisCount; ++i) {
+ if (uint16_t(axes[i].flags) & HIDDEN_AXIS) {
+ continue;
+ }
+ gfxFontVariationAxis axis;
+ axis.mTag = axes[i].axisTag;
+ uint16_t nameID = axes[i].axisNameID;
+ nsresult rv = ReadCanonicalName(nameTable, nameID, axis.mName);
+ if (NS_FAILED(rv)) {
+ axis.mName.Truncate(0);
+ }
+ // Convert values from 16.16 fixed-point to float
+ axis.mMinValue = int32_t(axes[i].minValue) / 65536.0f;
+ axis.mDefaultValue = int32_t(axes[i].defaultValue) / 65536.0f;
+ axis.mMaxValue = int32_t(axes[i].maxValue) / 65536.0f;
+ aAxes->AppendElement(axis);
+ }
+ }
+}
+
+void gfxFontUtils::ReadOtherFamilyNamesForFace(
+ const nsACString& aFamilyName, const char* aNameData, uint32_t aDataLength,
+ nsTArray<nsCString>& aOtherFamilyNames, bool useFullName) {
+ const NameHeader* nameHeader = reinterpret_cast<const NameHeader*>(aNameData);
+
+ uint32_t nameCount = nameHeader->count;
+ if (nameCount * sizeof(NameRecord) > aDataLength) {
+ NS_WARNING("invalid font (name records)");
+ return;
+ }
+
+ const NameRecord* nameRecord =
+ reinterpret_cast<const NameRecord*>(aNameData + sizeof(NameHeader));
+ uint32_t stringsBase = uint32_t(nameHeader->stringOffset);
+
+ for (uint32_t i = 0; i < nameCount; i++, nameRecord++) {
+ uint32_t nameLen = nameRecord->length;
+ uint32_t nameOff =
+ nameRecord->offset; // offset from base of string storage
+
+ if (stringsBase + nameOff + nameLen > aDataLength) {
+ NS_WARNING("invalid font (name table strings)");
+ return;
+ }
+
+ uint16_t nameID = nameRecord->nameID;
+ if ((useFullName && nameID == NAME_ID_FULL) ||
+ (!useFullName &&
+ (nameID == NAME_ID_FAMILY || nameID == NAME_ID_PREFERRED_FAMILY))) {
+ nsAutoCString otherFamilyName;
+ bool ok = DecodeFontName(
+ aNameData + stringsBase + nameOff, nameLen,
+ uint32_t(nameRecord->platformID), uint32_t(nameRecord->encodingID),
+ uint32_t(nameRecord->languageID), otherFamilyName);
+ // add if not same as canonical family name
+ if (ok && otherFamilyName != aFamilyName &&
+ !aOtherFamilyNames.Contains(otherFamilyName)) {
+ aOtherFamilyNames.AppendElement(otherFamilyName);
+ }
+ }
+ }
+}
+
+#ifdef XP_WIN
+
+/* static */
+bool gfxFontUtils::IsCffFont(const uint8_t* aFontData) {
+ // this is only called after aFontData has passed basic validation,
+ // so we know there is enough data present to allow us to read the version!
+ const SFNTHeader* sfntHeader = reinterpret_cast<const SFNTHeader*>(aFontData);
+ return (sfntHeader->sfntVersion == TRUETYPE_TAG('O', 'T', 'T', 'O'));
+}
+
+#endif
+
+/* static */ bool gfxFontUtils::IsInServoTraversal() {
+ if (NS_IsMainThread()) {
+ return ServoStyleSet::IsInServoTraversal();
+ }
+
+ if (dom::GetCurrentThreadWorkerPrivate()) {
+ return false;
+ }
+
+ // The only permissible threads are the main thread, the worker thread, the
+ // servo threads. If the latter, we must be traversing.
+ bool traversing = ServoStyleSet::IsInServoTraversal();
+ MOZ_ASSERT(traversing);
+ return traversing;
+}
+
+/* static */ ServoStyleSet* gfxFontUtils::CurrentServoStyleSet() {
+ // If we are on a worker thread, we must not check for the current set since
+ // the main/servo threads may be busy in parallel.
+ if (dom::GetCurrentThreadWorkerPrivate()) {
+ return nullptr;
+ }
+
+ return ServoStyleSet::Current();
+}
+
+#ifdef DEBUG
+/* static */ void gfxFontUtils::AssertSafeThreadOrServoFontMetricsLocked() {
+ if (!dom::GetCurrentThreadWorkerPrivate()) {
+ AssertIsMainThreadOrServoFontMetricsLocked();
+ }
+}
+#endif
+
+#undef acceptablePlatform
+#undef isSymbol
+#undef isUVSEncoding
+#undef LOG
+#undef LOG_ENABLED
diff --git a/gfx/thebes/gfxFontUtils.h b/gfx/thebes/gfxFontUtils.h
new file mode 100644
index 0000000000..ce11c6943d
--- /dev/null
+++ b/gfx/thebes/gfxFontUtils.h
@@ -0,0 +1,1467 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_FONT_UTILS_H
+#define GFX_FONT_UTILS_H
+
+#include <string.h>
+#include <algorithm>
+#include <new>
+#include <utility>
+#include "gfxPlatform.h"
+#include "harfbuzz/hb.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Casting.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/ServoStyleConstsInlines.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/UniquePtr.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "nscore.h"
+#include "zlib.h"
+
+class PickleIterator;
+class gfxFontEntry;
+struct gfxFontVariationAxis;
+struct gfxFontVariationInstance;
+
+namespace mozilla {
+class Encoding;
+class ServoStyleSet;
+} // namespace mozilla
+
+/* Bug 341128 - w32api defines min/max which causes problems with <bitset> */
+#ifdef __MINGW32__
+# undef min
+# undef max
+#endif
+
+#undef ERROR /* defined by Windows.h, conflicts with some generated bindings \
+ code when this gets indirectly included via shared font list \
+ */
+
+typedef struct hb_blob_t hb_blob_t;
+
+class SharedBitSet;
+
+namespace IPC {
+template <typename T>
+struct ParamTraits;
+}
+
+class gfxSparseBitSet {
+ private:
+ friend class SharedBitSet;
+
+ enum { BLOCK_SIZE = 32 }; // ==> 256 codepoints per block
+ enum { BLOCK_SIZE_BITS = BLOCK_SIZE * 8 };
+ enum { NO_BLOCK = 0xffff }; // index value indicating missing (empty) block
+
+ struct Block {
+ explicit Block(unsigned char memsetValue = 0) {
+ memset(mBits, memsetValue, BLOCK_SIZE);
+ }
+ uint8_t mBits[BLOCK_SIZE];
+ };
+
+ friend struct IPC::ParamTraits<gfxSparseBitSet>;
+ friend struct IPC::ParamTraits<Block>;
+
+ public:
+ gfxSparseBitSet() = default;
+
+ bool Equals(const gfxSparseBitSet* aOther) const {
+ if (mBlockIndex.Length() != aOther->mBlockIndex.Length()) {
+ return false;
+ }
+ size_t n = mBlockIndex.Length();
+ for (size_t i = 0; i < n; ++i) {
+ uint32_t b1 = mBlockIndex[i];
+ uint32_t b2 = aOther->mBlockIndex[i];
+ if ((b1 == NO_BLOCK) != (b2 == NO_BLOCK)) {
+ return false;
+ }
+ if (b1 == NO_BLOCK) {
+ continue;
+ }
+ if (memcmp(&mBlocks[b1].mBits, &aOther->mBlocks[b2].mBits, BLOCK_SIZE) !=
+ 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool test(uint32_t aIndex) const {
+ uint32_t i = aIndex / BLOCK_SIZE_BITS;
+ if (i >= mBlockIndex.Length() || mBlockIndex[i] == NO_BLOCK) {
+ return false;
+ }
+ const Block& block = mBlocks[mBlockIndex[i]];
+ return ((block.mBits[(aIndex >> 3) & (BLOCK_SIZE - 1)]) &
+ (1 << (aIndex & 0x7))) != 0;
+ }
+
+ // dump out contents of bitmap
+ void Dump(const char* aPrefix, eGfxLog aWhichLog) const;
+
+ bool TestRange(uint32_t aStart, uint32_t aEnd) {
+ // start point is beyond the end of the block array? return false
+ // immediately
+ uint32_t startBlock = aStart / BLOCK_SIZE_BITS;
+ uint32_t blockLen = mBlockIndex.Length();
+ if (startBlock >= blockLen) {
+ return false;
+ }
+
+ // check for blocks in range, if none, return false
+ bool hasBlocksInRange = false;
+ uint32_t endBlock = aEnd / BLOCK_SIZE_BITS;
+ for (uint32_t bi = startBlock; bi <= endBlock; bi++) {
+ if (bi < blockLen && mBlockIndex[bi] != NO_BLOCK) {
+ hasBlocksInRange = true;
+ break;
+ }
+ }
+ if (!hasBlocksInRange) {
+ return false;
+ }
+
+ // first block, check bits
+ if (mBlockIndex[startBlock] != NO_BLOCK) {
+ const Block& block = mBlocks[mBlockIndex[startBlock]];
+ uint32_t start = aStart;
+ uint32_t end = std::min(aEnd, ((startBlock + 1) * BLOCK_SIZE_BITS) - 1);
+ for (uint32_t i = start; i <= end; i++) {
+ if ((block.mBits[(i >> 3) & (BLOCK_SIZE - 1)]) & (1 << (i & 0x7))) {
+ return true;
+ }
+ }
+ }
+ if (endBlock == startBlock) {
+ return false;
+ }
+
+ // [2..n-1] blocks check bytes
+ for (uint32_t i = startBlock + 1; i < endBlock; i++) {
+ if (i >= blockLen || mBlockIndex[i] == NO_BLOCK) {
+ continue;
+ }
+ const Block& block = mBlocks[mBlockIndex[i]];
+ for (uint32_t index = 0; index < BLOCK_SIZE; index++) {
+ if (block.mBits[index]) {
+ return true;
+ }
+ }
+ }
+
+ // last block, check bits
+ if (endBlock < blockLen && mBlockIndex[endBlock] != NO_BLOCK) {
+ const Block& block = mBlocks[mBlockIndex[endBlock]];
+ uint32_t start = endBlock * BLOCK_SIZE_BITS;
+ uint32_t end = aEnd;
+ for (uint32_t i = start; i <= end; i++) {
+ if ((block.mBits[(i >> 3) & (BLOCK_SIZE - 1)]) & (1 << (i & 0x7))) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ void set(uint32_t aIndex) {
+ uint32_t i = aIndex / BLOCK_SIZE_BITS;
+ while (i >= mBlockIndex.Length()) {
+ mBlockIndex.AppendElement(NO_BLOCK);
+ }
+ if (mBlockIndex[i] == NO_BLOCK) {
+ mBlocks.AppendElement();
+ MOZ_ASSERT(mBlocks.Length() < 0xffff, "block index overflow!");
+ mBlockIndex[i] = static_cast<uint16_t>(mBlocks.Length() - 1);
+ }
+ Block& block = mBlocks[mBlockIndex[i]];
+ block.mBits[(aIndex >> 3) & (BLOCK_SIZE - 1)] |= 1 << (aIndex & 0x7);
+ }
+
+ void set(uint32_t aIndex, bool aValue) {
+ if (aValue) {
+ set(aIndex);
+ } else {
+ clear(aIndex);
+ }
+ }
+
+ void SetRange(uint32_t aStart, uint32_t aEnd) {
+ const uint32_t startIndex = aStart / BLOCK_SIZE_BITS;
+ const uint32_t endIndex = aEnd / BLOCK_SIZE_BITS;
+
+ while (endIndex >= mBlockIndex.Length()) {
+ mBlockIndex.AppendElement(NO_BLOCK);
+ }
+
+ for (uint32_t i = startIndex; i <= endIndex; ++i) {
+ const uint32_t blockFirstBit = i * BLOCK_SIZE_BITS;
+ const uint32_t blockLastBit = blockFirstBit + BLOCK_SIZE_BITS - 1;
+
+ if (mBlockIndex[i] == NO_BLOCK) {
+ bool fullBlock = (aStart <= blockFirstBit && aEnd >= blockLastBit);
+ mBlocks.AppendElement(Block(fullBlock ? 0xFF : 0));
+ MOZ_ASSERT(mBlocks.Length() < 0xffff, "block index overflow!");
+ mBlockIndex[i] = static_cast<uint16_t>(mBlocks.Length() - 1);
+ if (fullBlock) {
+ continue;
+ }
+ }
+
+ Block& block = mBlocks[mBlockIndex[i]];
+ const uint32_t start =
+ aStart > blockFirstBit ? aStart - blockFirstBit : 0;
+ const uint32_t end =
+ std::min<uint32_t>(aEnd - blockFirstBit, BLOCK_SIZE_BITS - 1);
+
+ for (uint32_t bit = start; bit <= end; ++bit) {
+ block.mBits[bit >> 3] |= 1 << (bit & 0x7);
+ }
+ }
+ }
+
+ void clear(uint32_t aIndex) {
+ uint32_t i = aIndex / BLOCK_SIZE_BITS;
+ if (i >= mBlockIndex.Length()) {
+ return;
+ }
+ if (mBlockIndex[i] == NO_BLOCK) {
+ mBlocks.AppendElement();
+ MOZ_ASSERT(mBlocks.Length() < 0xffff, "block index overflow!");
+ mBlockIndex[i] = static_cast<uint16_t>(mBlocks.Length() - 1);
+ }
+ Block& block = mBlocks[mBlockIndex[i]];
+ block.mBits[(aIndex >> 3) & (BLOCK_SIZE - 1)] &= ~(1 << (aIndex & 0x7));
+ }
+
+ void ClearRange(uint32_t aStart, uint32_t aEnd) {
+ const uint32_t startIndex = aStart / BLOCK_SIZE_BITS;
+ const uint32_t endIndex = aEnd / BLOCK_SIZE_BITS;
+
+ for (uint32_t i = startIndex; i <= endIndex; ++i) {
+ if (i >= mBlockIndex.Length()) {
+ return;
+ }
+ if (mBlockIndex[i] == NO_BLOCK) {
+ continue;
+ }
+
+ const uint32_t blockFirstBit = i * BLOCK_SIZE_BITS;
+ Block& block = mBlocks[mBlockIndex[i]];
+
+ const uint32_t start =
+ aStart > blockFirstBit ? aStart - blockFirstBit : 0;
+ const uint32_t end =
+ std::min<uint32_t>(aEnd - blockFirstBit, BLOCK_SIZE_BITS - 1);
+
+ for (uint32_t bit = start; bit <= end; ++bit) {
+ block.mBits[bit >> 3] &= ~(1 << (bit & 0x7));
+ }
+ }
+ }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return mBlocks.ShallowSizeOfExcludingThis(aMallocSizeOf) +
+ mBlockIndex.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ // clear out all blocks in the array
+ void reset() {
+ mBlocks.Clear();
+ mBlockIndex.Clear();
+ }
+
+ // set this bitset to the union of its current contents and another
+ void Union(const gfxSparseBitSet& aBitset) {
+ // ensure mBlocks is large enough
+ uint32_t blockCount = aBitset.mBlockIndex.Length();
+ while (blockCount > mBlockIndex.Length()) {
+ mBlockIndex.AppendElement(NO_BLOCK);
+ }
+ // for each block that may be present in aBitset...
+ for (uint32_t i = 0; i < blockCount; ++i) {
+ // if it is missing (implicitly empty), just skip
+ if (aBitset.mBlockIndex[i] == NO_BLOCK) {
+ continue;
+ }
+ // if the block is missing in this set, just copy the other
+ if (mBlockIndex[i] == NO_BLOCK) {
+ mBlocks.AppendElement(aBitset.mBlocks[aBitset.mBlockIndex[i]]);
+ MOZ_ASSERT(mBlocks.Length() < 0xffff, "block index overflow!");
+ mBlockIndex[i] = static_cast<uint16_t>(mBlocks.Length() - 1);
+ continue;
+ }
+ // else set existing block to the union of both
+ uint32_t* dst =
+ reinterpret_cast<uint32_t*>(&mBlocks[mBlockIndex[i]].mBits);
+ const uint32_t* src = reinterpret_cast<const uint32_t*>(
+ &aBitset.mBlocks[aBitset.mBlockIndex[i]].mBits);
+ for (uint32_t j = 0; j < BLOCK_SIZE / 4; ++j) {
+ dst[j] |= src[j];
+ }
+ }
+ }
+
+ inline void Union(const SharedBitSet& aBitset);
+
+ void Compact() {
+ // TODO: Discard any empty blocks, and adjust index accordingly.
+ // (May not be worth doing, though, because we so rarely clear bits
+ // that were previously set.)
+ mBlocks.Compact();
+ mBlockIndex.Compact();
+ }
+
+ uint32_t GetChecksum() const {
+ uint32_t check =
+ adler32(0, reinterpret_cast<const uint8_t*>(mBlockIndex.Elements()),
+ mBlockIndex.Length() * sizeof(uint16_t));
+ check = adler32(check, reinterpret_cast<const uint8_t*>(mBlocks.Elements()),
+ mBlocks.Length() * sizeof(Block));
+ return check;
+ }
+
+ private:
+ CopyableTArray<uint16_t> mBlockIndex;
+ CopyableTArray<Block> mBlocks;
+};
+
+/**
+ * SharedBitSet is a version of gfxSparseBitSet that is intended to be used
+ * in a shared-memory block, and can be used regardless of the address at which
+ * the block has been mapped. The SharedBitSet cannot be modified once it has
+ * been created.
+ *
+ * Max size of a SharedBitSet = 4352 * 32 ; blocks
+ * + 4352 * 2 ; index
+ * + 4 ; counts
+ * = 147972 bytes
+ *
+ * Therefore, SharedFontList must be able to allocate a contiguous block of at
+ * least this size.
+ */
+class SharedBitSet {
+ private:
+ // We use the same Block type as gfxSparseBitSet.
+ typedef gfxSparseBitSet::Block Block;
+
+ enum { BLOCK_SIZE = gfxSparseBitSet::BLOCK_SIZE };
+ enum { BLOCK_SIZE_BITS = gfxSparseBitSet::BLOCK_SIZE_BITS };
+ enum { NO_BLOCK = gfxSparseBitSet::NO_BLOCK };
+
+ public:
+ static const size_t kMaxSize = 147972; // see above
+
+ // Returns the size needed for a SharedBitSet version of the given
+ // gfxSparseBitSet.
+ static size_t RequiredSize(const gfxSparseBitSet& aBitset) {
+ size_t total = sizeof(SharedBitSet);
+ size_t len = aBitset.mBlockIndex.Length();
+ total += len * sizeof(uint16_t); // add size for index array
+ // add size for blocks, excluding any missing ones
+ for (uint16_t i = 0; i < len; i++) {
+ if (aBitset.mBlockIndex[i] != NO_BLOCK) {
+ total += sizeof(Block);
+ }
+ }
+ MOZ_ASSERT(total <= kMaxSize);
+ return total;
+ }
+
+ // Create a SharedBitSet in the provided buffer, initializing it with the
+ // contents of aBitset.
+ static SharedBitSet* Create(void* aBuffer, size_t aBufSize,
+ const gfxSparseBitSet& aBitset) {
+ MOZ_ASSERT(aBufSize >= RequiredSize(aBitset));
+ return new (aBuffer) SharedBitSet(aBitset);
+ }
+
+ bool test(uint32_t aIndex) const {
+ const auto i = static_cast<uint16_t>(aIndex / BLOCK_SIZE_BITS);
+ if (i >= mBlockIndexCount) {
+ return false;
+ }
+ const uint16_t* const blockIndex =
+ reinterpret_cast<const uint16_t*>(this + 1);
+ if (blockIndex[i] == NO_BLOCK) {
+ return false;
+ }
+ const Block* const blocks =
+ reinterpret_cast<const Block*>(blockIndex + mBlockIndexCount);
+ const Block& block = blocks[blockIndex[i]];
+ return ((block.mBits[(aIndex >> 3) & (BLOCK_SIZE - 1)]) &
+ (1 << (aIndex & 0x7))) != 0;
+ }
+
+ bool Equals(const gfxSparseBitSet* aOther) const {
+ if (mBlockIndexCount != aOther->mBlockIndex.Length()) {
+ return false;
+ }
+ const uint16_t* const blockIndex =
+ reinterpret_cast<const uint16_t*>(this + 1);
+ const Block* const blocks =
+ reinterpret_cast<const Block*>(blockIndex + mBlockIndexCount);
+ for (uint16_t i = 0; i < mBlockIndexCount; ++i) {
+ uint16_t index = blockIndex[i];
+ uint16_t otherIndex = aOther->mBlockIndex[i];
+ if ((index == NO_BLOCK) != (otherIndex == NO_BLOCK)) {
+ return false;
+ }
+ if (index == NO_BLOCK) {
+ continue;
+ }
+ const Block& b1 = blocks[index];
+ const Block& b2 = aOther->mBlocks[otherIndex];
+ if (memcmp(&b1.mBits, &b2.mBits, BLOCK_SIZE) != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private:
+ friend class gfxSparseBitSet;
+ SharedBitSet() = delete;
+
+ explicit SharedBitSet(const gfxSparseBitSet& aBitset)
+ : mBlockIndexCount(
+ mozilla::AssertedCast<uint16_t>(aBitset.mBlockIndex.Length())),
+ mBlockCount(0) {
+ uint16_t* blockIndex = reinterpret_cast<uint16_t*>(this + 1);
+ Block* blocks = reinterpret_cast<Block*>(blockIndex + mBlockIndexCount);
+ for (uint16_t i = 0; i < mBlockIndexCount; i++) {
+ if (aBitset.mBlockIndex[i] != NO_BLOCK) {
+ const Block& srcBlock = aBitset.mBlocks[aBitset.mBlockIndex[i]];
+ std::memcpy(&blocks[mBlockCount], &srcBlock, sizeof(Block));
+ blockIndex[i] = mBlockCount;
+ mBlockCount++;
+ } else {
+ blockIndex[i] = NO_BLOCK;
+ }
+ }
+ }
+
+ // We never manage SharedBitSet as a "normal" object, it's a view onto a
+ // buffer of shared memory. So we should never be trying to call this.
+ ~SharedBitSet() = delete;
+
+ uint16_t mBlockIndexCount;
+ uint16_t mBlockCount;
+
+ // After the two "header" fields above, we have a block index array
+ // of uint16_t[mBlockIndexCount], followed by mBlockCount Block records.
+};
+
+// Union the contents of a SharedBitSet with the target gfxSparseBitSet
+inline void gfxSparseBitSet::Union(const SharedBitSet& aBitset) {
+ // ensure mBlockIndex is large enough
+ while (mBlockIndex.Length() < aBitset.mBlockIndexCount) {
+ mBlockIndex.AppendElement(NO_BLOCK);
+ }
+ auto blockIndex = reinterpret_cast<const uint16_t*>(&aBitset + 1);
+ auto blocks =
+ reinterpret_cast<const Block*>(blockIndex + aBitset.mBlockIndexCount);
+ for (uint32_t i = 0; i < aBitset.mBlockIndexCount; ++i) {
+ // if it is missing (implicitly empty) in source, just skip
+ if (blockIndex[i] == NO_BLOCK) {
+ continue;
+ }
+ // if the block is missing, just copy from source bitset
+ if (mBlockIndex[i] == NO_BLOCK) {
+ mBlocks.AppendElement(blocks[blockIndex[i]]);
+ MOZ_ASSERT(mBlocks.Length() < 0xffff, "block index overflow");
+ mBlockIndex[i] = uint16_t(mBlocks.Length() - 1);
+ continue;
+ }
+ // Else set existing target block to the union of both.
+ // Note that blocks in SharedBitSet may not be 4-byte aligned, so we don't
+ // try to optimize by casting to uint32_t* here and processing 4 bytes at
+ // once, as this could result in misaligned access.
+ uint8_t* dst = reinterpret_cast<uint8_t*>(&mBlocks[mBlockIndex[i]].mBits);
+ const uint8_t* src =
+ reinterpret_cast<const uint8_t*>(&blocks[blockIndex[i]].mBits);
+ for (uint32_t j = 0; j < BLOCK_SIZE; ++j) {
+ dst[j] |= src[j];
+ }
+ }
+}
+
+#define TRUETYPE_TAG(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d))
+
+namespace mozilla {
+
+// Byte-swapping types and name table structure definitions moved from
+// gfxFontUtils.cpp to .h file so that gfxFont.cpp can also refer to them
+#pragma pack(1)
+
+struct AutoSwap_PRUint16 {
+#ifdef __SUNPRO_CC
+ AutoSwap_PRUint16& operator=(const uint16_t aValue) {
+ this->value = mozilla::NativeEndian::swapToBigEndian(aValue);
+ return *this;
+ }
+#else
+ MOZ_IMPLICIT AutoSwap_PRUint16(uint16_t aValue) {
+ value = mozilla::NativeEndian::swapToBigEndian(aValue);
+ }
+#endif
+ operator uint16_t() const {
+ return mozilla::NativeEndian::swapFromBigEndian(value);
+ }
+
+ operator uint32_t() const {
+ return mozilla::NativeEndian::swapFromBigEndian(value);
+ }
+
+ operator uint64_t() const {
+ return mozilla::NativeEndian::swapFromBigEndian(value);
+ }
+
+ private:
+ uint16_t value;
+};
+
+struct AutoSwap_PRInt16 {
+#ifdef __SUNPRO_CC
+ AutoSwap_PRInt16& operator=(const int16_t aValue) {
+ this->value = mozilla::NativeEndian::swapToBigEndian(aValue);
+ return *this;
+ }
+#else
+ MOZ_IMPLICIT AutoSwap_PRInt16(int16_t aValue) {
+ value = mozilla::NativeEndian::swapToBigEndian(aValue);
+ }
+#endif
+ operator int16_t() const {
+ return mozilla::NativeEndian::swapFromBigEndian(value);
+ }
+
+ operator uint32_t() const {
+ return mozilla::NativeEndian::swapFromBigEndian(value);
+ }
+
+ private:
+ int16_t value;
+};
+
+struct AutoSwap_PRUint32 {
+#ifdef __SUNPRO_CC
+ AutoSwap_PRUint32& operator=(const uint32_t aValue) {
+ this->value = mozilla::NativeEndian::swapToBigEndian(aValue);
+ return *this;
+ }
+#else
+ MOZ_IMPLICIT AutoSwap_PRUint32(uint32_t aValue) {
+ value = mozilla::NativeEndian::swapToBigEndian(aValue);
+ }
+#endif
+ operator uint32_t() const {
+ return mozilla::NativeEndian::swapFromBigEndian(value);
+ }
+
+ private:
+ uint32_t value;
+};
+
+struct AutoSwap_PRInt32 {
+#ifdef __SUNPRO_CC
+ AutoSwap_PRInt32& operator=(const int32_t aValue) {
+ this->value = mozilla::NativeEndian::swapToBigEndian(aValue);
+ return *this;
+ }
+#else
+ MOZ_IMPLICIT AutoSwap_PRInt32(int32_t aValue) {
+ value = mozilla::NativeEndian::swapToBigEndian(aValue);
+ }
+#endif
+ operator int32_t() const {
+ return mozilla::NativeEndian::swapFromBigEndian(value);
+ }
+
+ private:
+ int32_t value;
+};
+
+struct AutoSwap_PRUint64 {
+#ifdef __SUNPRO_CC
+ AutoSwap_PRUint64& operator=(const uint64_t aValue) {
+ this->value = mozilla::NativeEndian::swapToBigEndian(aValue);
+ return *this;
+ }
+#else
+ MOZ_IMPLICIT AutoSwap_PRUint64(uint64_t aValue) {
+ value = mozilla::NativeEndian::swapToBigEndian(aValue);
+ }
+#endif
+ operator uint64_t() const {
+ return mozilla::NativeEndian::swapFromBigEndian(value);
+ }
+
+ private:
+ uint64_t value;
+};
+
+struct AutoSwap_PRUint24 {
+ operator uint32_t() const {
+ return value[0] << 16 | value[1] << 8 | value[2];
+ }
+
+ private:
+ AutoSwap_PRUint24() = default;
+ uint8_t value[3];
+};
+
+struct SFNTHeader {
+ AutoSwap_PRUint32 sfntVersion; // Fixed, 0x00010000 for version 1.0.
+ AutoSwap_PRUint16 numTables; // Number of tables.
+ AutoSwap_PRUint16 searchRange; // (Maximum power of 2 <= numTables) x 16.
+ AutoSwap_PRUint16 entrySelector; // Log2(maximum power of 2 <= numTables).
+ AutoSwap_PRUint16 rangeShift; // NumTables x 16-searchRange.
+};
+
+struct TTCHeader {
+ AutoSwap_PRUint32 ttcTag; // 4 -byte identifier 'ttcf'.
+ AutoSwap_PRUint16 majorVersion;
+ AutoSwap_PRUint16 minorVersion;
+ AutoSwap_PRUint32 numFonts;
+ // followed by:
+ // AutoSwap_PRUint32 offsetTable[numFonts]
+};
+
+struct TableDirEntry {
+ AutoSwap_PRUint32 tag; // 4 -byte identifier.
+ AutoSwap_PRUint32 checkSum; // CheckSum for this table.
+ AutoSwap_PRUint32 offset; // Offset from beginning of TrueType font file.
+ AutoSwap_PRUint32 length; // Length of this table.
+};
+
+struct HeadTable {
+ enum {
+ HEAD_VERSION = 0x00010000,
+ HEAD_MAGIC_NUMBER = 0x5F0F3CF5,
+ HEAD_CHECKSUM_CALC_CONST = 0xB1B0AFBA
+ };
+
+ AutoSwap_PRUint32 tableVersionNumber; // Fixed, 0x00010000 for version 1.0.
+ AutoSwap_PRUint32 fontRevision; // Set by font manufacturer.
+ AutoSwap_PRUint32
+ checkSumAdjustment; // To compute: set it to 0, sum the entire font as
+ // ULONG, then store 0xB1B0AFBA - sum.
+ AutoSwap_PRUint32 magicNumber; // Set to 0x5F0F3CF5.
+ AutoSwap_PRUint16 flags;
+ AutoSwap_PRUint16
+ unitsPerEm; // Valid range is from 16 to 16384. This value should be a
+ // power of 2 for fonts that have TrueType outlines.
+ AutoSwap_PRUint64 created; // Number of seconds since 12:00 midnight, January
+ // 1, 1904. 64-bit integer
+ AutoSwap_PRUint64 modified; // Number of seconds since 12:00 midnight,
+ // January 1, 1904. 64-bit integer
+ AutoSwap_PRInt16 xMin; // For all glyph bounding boxes.
+ AutoSwap_PRInt16 yMin; // For all glyph bounding boxes.
+ AutoSwap_PRInt16 xMax; // For all glyph bounding boxes.
+ AutoSwap_PRInt16 yMax; // For all glyph bounding boxes.
+ AutoSwap_PRUint16 macStyle; // Bit 0: Bold (if set to 1);
+ AutoSwap_PRUint16 lowestRecPPEM; // Smallest readable size in pixels.
+ AutoSwap_PRInt16 fontDirectionHint;
+ AutoSwap_PRInt16 indexToLocFormat;
+ AutoSwap_PRInt16 glyphDataFormat;
+};
+
+struct OS2Table {
+ AutoSwap_PRUint16 version; // 0004 = OpenType 1.5
+ AutoSwap_PRInt16 xAvgCharWidth;
+ AutoSwap_PRUint16 usWeightClass;
+ AutoSwap_PRUint16 usWidthClass;
+ AutoSwap_PRUint16 fsType;
+ AutoSwap_PRInt16 ySubscriptXSize;
+ AutoSwap_PRInt16 ySubscriptYSize;
+ AutoSwap_PRInt16 ySubscriptXOffset;
+ AutoSwap_PRInt16 ySubscriptYOffset;
+ AutoSwap_PRInt16 ySuperscriptXSize;
+ AutoSwap_PRInt16 ySuperscriptYSize;
+ AutoSwap_PRInt16 ySuperscriptXOffset;
+ AutoSwap_PRInt16 ySuperscriptYOffset;
+ AutoSwap_PRInt16 yStrikeoutSize;
+ AutoSwap_PRInt16 yStrikeoutPosition;
+ AutoSwap_PRInt16 sFamilyClass;
+ uint8_t panose[10];
+ AutoSwap_PRUint32 unicodeRange1;
+ AutoSwap_PRUint32 unicodeRange2;
+ AutoSwap_PRUint32 unicodeRange3;
+ AutoSwap_PRUint32 unicodeRange4;
+ uint8_t achVendID[4];
+ AutoSwap_PRUint16 fsSelection;
+ AutoSwap_PRUint16 usFirstCharIndex;
+ AutoSwap_PRUint16 usLastCharIndex;
+ AutoSwap_PRInt16 sTypoAscender;
+ AutoSwap_PRInt16 sTypoDescender;
+ AutoSwap_PRInt16 sTypoLineGap;
+ AutoSwap_PRUint16 usWinAscent;
+ AutoSwap_PRUint16 usWinDescent;
+ AutoSwap_PRUint32 codePageRange1;
+ AutoSwap_PRUint32 codePageRange2;
+ AutoSwap_PRInt16 sxHeight;
+ AutoSwap_PRInt16 sCapHeight;
+ AutoSwap_PRUint16 usDefaultChar;
+ AutoSwap_PRUint16 usBreakChar;
+ AutoSwap_PRUint16 usMaxContext;
+};
+
+struct PostTable {
+ AutoSwap_PRUint32 version;
+ AutoSwap_PRInt32 italicAngle;
+ AutoSwap_PRInt16 underlinePosition;
+ AutoSwap_PRUint16 underlineThickness;
+ AutoSwap_PRUint32 isFixedPitch;
+ AutoSwap_PRUint32 minMemType42;
+ AutoSwap_PRUint32 maxMemType42;
+ AutoSwap_PRUint32 minMemType1;
+ AutoSwap_PRUint32 maxMemType1;
+};
+
+// This structure is used for both 'hhea' and 'vhea' tables.
+// The field names here are those of the horizontal version; the
+// vertical table just exchanges vertical and horizontal coordinates.
+struct MetricsHeader {
+ AutoSwap_PRUint32 version;
+ AutoSwap_PRInt16 ascender;
+ AutoSwap_PRInt16 descender;
+ AutoSwap_PRInt16 lineGap;
+ AutoSwap_PRUint16 advanceWidthMax;
+ AutoSwap_PRInt16 minLeftSideBearing;
+ AutoSwap_PRInt16 minRightSideBearing;
+ AutoSwap_PRInt16 xMaxExtent;
+ AutoSwap_PRInt16 caretSlopeRise;
+ AutoSwap_PRInt16 caretSlopeRun;
+ AutoSwap_PRInt16 caretOffset;
+ AutoSwap_PRInt16 reserved1;
+ AutoSwap_PRInt16 reserved2;
+ AutoSwap_PRInt16 reserved3;
+ AutoSwap_PRInt16 reserved4;
+ AutoSwap_PRInt16 metricDataFormat;
+ AutoSwap_PRUint16 numOfLongMetrics;
+};
+
+struct MaxpTableHeader {
+ AutoSwap_PRUint32 version; // CFF: 0x00005000; TrueType: 0x00010000
+ AutoSwap_PRUint16 numGlyphs;
+ // truetype version has additional fields that we don't currently use
+};
+
+// old 'kern' table, supported on Windows
+// see http://www.microsoft.com/typography/otspec/kern.htm
+struct KernTableVersion0 {
+ AutoSwap_PRUint16 version; // 0x0000
+ AutoSwap_PRUint16 nTables;
+};
+
+struct KernTableSubtableHeaderVersion0 {
+ AutoSwap_PRUint16 version;
+ AutoSwap_PRUint16 length;
+ AutoSwap_PRUint16 coverage;
+};
+
+// newer Mac-only 'kern' table, ignored by Windows
+// see http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6kern.html
+struct KernTableVersion1 {
+ AutoSwap_PRUint32 version; // 0x00010000
+ AutoSwap_PRUint32 nTables;
+};
+
+struct KernTableSubtableHeaderVersion1 {
+ AutoSwap_PRUint32 length;
+ AutoSwap_PRUint16 coverage;
+ AutoSwap_PRUint16 tupleIndex;
+};
+
+#pragma pack()
+
+// Return just the highest bit of the given value, i.e., the highest
+// power of 2 that is <= value, or zero if the input value is zero.
+inline uint32_t FindHighestBit(uint32_t value) {
+ // propagate highest bit into all lower bits of the value
+ value |= (value >> 1);
+ value |= (value >> 2);
+ value |= (value >> 4);
+ value |= (value >> 8);
+ value |= (value >> 16);
+ // isolate the leftmost bit
+ return (value & ~(value >> 1));
+}
+
+} // namespace mozilla
+
+// used for overlaying name changes without touching original font data
+struct FontDataOverlay {
+ // overlaySrc != 0 ==> use overlay
+ uint32_t overlaySrc; // src offset from start of font data
+ uint32_t overlaySrcLen; // src length
+ uint32_t overlayDest; // dest offset from start of font data
+};
+
+enum gfxUserFontType {
+ GFX_USERFONT_UNKNOWN = 0,
+ GFX_USERFONT_OPENTYPE = 1,
+ GFX_USERFONT_SVG = 2,
+ GFX_USERFONT_WOFF = 3,
+ GFX_USERFONT_WOFF2 = 4
+};
+
+extern const uint8_t sCJKCompatSVSTable[];
+
+class gfxFontUtils {
+ public:
+ // these are public because gfxFont.cpp also looks into the name table
+ enum {
+ NAME_ID_FAMILY = 1,
+ NAME_ID_STYLE = 2,
+ NAME_ID_UNIQUE = 3,
+ NAME_ID_FULL = 4,
+ NAME_ID_VERSION = 5,
+ NAME_ID_POSTSCRIPT = 6,
+ NAME_ID_PREFERRED_FAMILY = 16,
+ NAME_ID_PREFERRED_STYLE = 17,
+
+ PLATFORM_ALL = -1,
+ PLATFORM_ID_UNICODE = 0, // Mac OS uses this typically
+ PLATFORM_ID_MAC = 1,
+ PLATFORM_ID_ISO = 2,
+ PLATFORM_ID_MICROSOFT = 3,
+
+ ENCODING_ID_MAC_ROMAN = 0, // traditional Mac OS script manager encodings
+ ENCODING_ID_MAC_JAPANESE =
+ 1, // (there are others defined, but some were never
+ ENCODING_ID_MAC_TRAD_CHINESE =
+ 2, // implemented by Apple, and I have never seen them
+ ENCODING_ID_MAC_KOREAN = 3, // used in font names)
+ ENCODING_ID_MAC_ARABIC = 4,
+ ENCODING_ID_MAC_HEBREW = 5,
+ ENCODING_ID_MAC_GREEK = 6,
+ ENCODING_ID_MAC_CYRILLIC = 7,
+ ENCODING_ID_MAC_DEVANAGARI = 9,
+ ENCODING_ID_MAC_GURMUKHI = 10,
+ ENCODING_ID_MAC_GUJARATI = 11,
+ ENCODING_ID_MAC_SIMP_CHINESE = 25,
+
+ ENCODING_ID_MICROSOFT_SYMBOL = 0, // Microsoft platform encoding IDs
+ ENCODING_ID_MICROSOFT_UNICODEBMP = 1,
+ ENCODING_ID_MICROSOFT_SHIFTJIS = 2,
+ ENCODING_ID_MICROSOFT_PRC = 3,
+ ENCODING_ID_MICROSOFT_BIG5 = 4,
+ ENCODING_ID_MICROSOFT_WANSUNG = 5,
+ ENCODING_ID_MICROSOFT_JOHAB = 6,
+ ENCODING_ID_MICROSOFT_UNICODEFULL = 10,
+
+ LANG_ALL = -1,
+ LANG_ID_MAC_ENGLISH = 0, // many others are defined, but most don't affect
+ LANG_ID_MAC_HEBREW =
+ 10, // the charset; should check all the central/eastern
+ LANG_ID_MAC_JAPANESE = 11, // european codes, though
+ LANG_ID_MAC_ARABIC = 12,
+ LANG_ID_MAC_ICELANDIC = 15,
+ LANG_ID_MAC_TURKISH = 17,
+ LANG_ID_MAC_TRAD_CHINESE = 19,
+ LANG_ID_MAC_URDU = 20,
+ LANG_ID_MAC_KOREAN = 23,
+ LANG_ID_MAC_POLISH = 25,
+ LANG_ID_MAC_FARSI = 31,
+ LANG_ID_MAC_SIMP_CHINESE = 33,
+ LANG_ID_MAC_ROMANIAN = 37,
+ LANG_ID_MAC_CZECH = 38,
+ LANG_ID_MAC_SLOVAK = 39,
+
+ LANG_ID_MICROSOFT_EN_US =
+ 0x0409, // with Microsoft platformID, EN US lang code
+
+ CMAP_MAX_CODEPOINT = 0x10ffff // maximum possible Unicode codepoint
+ // contained in a cmap
+ };
+
+ // name table has a header, followed by name records, followed by string data
+ struct NameHeader {
+ mozilla::AutoSwap_PRUint16 format; // Format selector (=0).
+ mozilla::AutoSwap_PRUint16 count; // Number of name records.
+ mozilla::AutoSwap_PRUint16 stringOffset; // Offset to start of string
+ // storage (from start of table)
+ };
+
+ struct NameRecord {
+ mozilla::AutoSwap_PRUint16 platformID; // Platform ID
+ mozilla::AutoSwap_PRUint16 encodingID; // Platform-specific encoding ID
+ mozilla::AutoSwap_PRUint16 languageID; // Language ID
+ mozilla::AutoSwap_PRUint16 nameID; // Name ID.
+ mozilla::AutoSwap_PRUint16 length; // String length (in bytes).
+ mozilla::AutoSwap_PRUint16 offset; // String offset from start of storage
+ // (in bytes).
+ };
+
+ // Helper to ensure we free a font table when we return.
+ class AutoHBBlob {
+ public:
+ explicit AutoHBBlob(hb_blob_t* aBlob) : mBlob(aBlob) {}
+
+ ~AutoHBBlob() { hb_blob_destroy(mBlob); }
+
+ operator hb_blob_t*() { return mBlob; }
+
+ private:
+ hb_blob_t* const mBlob;
+ };
+
+ // for reading big-endian font data on either big or little-endian platforms
+
+ static inline uint16_t ReadShortAt(const uint8_t* aBuf, uint32_t aIndex) {
+ return static_cast<uint16_t>(aBuf[aIndex] << 8) | aBuf[aIndex + 1];
+ }
+
+ static inline uint16_t ReadShortAt16(const uint16_t* aBuf, uint32_t aIndex) {
+ const uint8_t* buf = reinterpret_cast<const uint8_t*>(aBuf);
+ uint32_t index = aIndex << 1;
+ return static_cast<uint16_t>(buf[index] << 8) | buf[index + 1];
+ }
+
+ static inline uint32_t ReadUint24At(const uint8_t* aBuf, uint32_t aIndex) {
+ return ((aBuf[aIndex] << 16) | (aBuf[aIndex + 1] << 8) |
+ (aBuf[aIndex + 2]));
+ }
+
+ static inline uint32_t ReadLongAt(const uint8_t* aBuf, uint32_t aIndex) {
+ return ((aBuf[aIndex] << 24) | (aBuf[aIndex + 1] << 16) |
+ (aBuf[aIndex + 2] << 8) | (aBuf[aIndex + 3]));
+ }
+
+ static nsresult ReadCMAPTableFormat10(const uint8_t* aBuf, uint32_t aLength,
+ gfxSparseBitSet& aCharacterMap);
+
+ static nsresult ReadCMAPTableFormat12or13(const uint8_t* aBuf,
+ uint32_t aLength,
+ gfxSparseBitSet& aCharacterMap);
+
+ static nsresult ReadCMAPTableFormat4(const uint8_t* aBuf, uint32_t aLength,
+ gfxSparseBitSet& aCharacterMap,
+ bool aIsSymbolFont);
+
+ static nsresult ReadCMAPTableFormat14(const uint8_t* aBuf, uint32_t aLength,
+ const uint8_t*& aTable);
+
+ static uint32_t FindPreferredSubtable(const uint8_t* aBuf,
+ uint32_t aBufLength,
+ uint32_t* aTableOffset,
+ uint32_t* aUVSTableOffset,
+ bool* aIsSymbolFont);
+
+ static nsresult ReadCMAP(const uint8_t* aBuf, uint32_t aBufLength,
+ gfxSparseBitSet& aCharacterMap,
+ uint32_t& aUVSOffset);
+
+ static uint32_t MapCharToGlyphFormat4(const uint8_t* aBuf, uint32_t aLength,
+ char16_t aCh);
+
+ static uint32_t MapCharToGlyphFormat10(const uint8_t* aBuf, uint32_t aCh);
+
+ static uint32_t MapCharToGlyphFormat12or13(const uint8_t* aBuf, uint32_t aCh);
+
+ static uint16_t MapUVSToGlyphFormat14(const uint8_t* aBuf, uint32_t aCh,
+ uint32_t aVS);
+
+ // sCJKCompatSVSTable is a 'cmap' format 14 subtable that maps
+ // <char + var-selector> pairs to the corresponding Unicode
+ // compatibility ideograph codepoints.
+ static MOZ_ALWAYS_INLINE uint32_t GetUVSFallback(uint32_t aCh, uint32_t aVS) {
+ aCh = MapUVSToGlyphFormat14(sCJKCompatSVSTable, aCh, aVS);
+ return aCh >= 0xFB00 ? aCh + (0x2F800 - 0xFB00) : aCh;
+ }
+
+ static uint32_t MapCharToGlyph(const uint8_t* aCmapBuf, uint32_t aBufLength,
+ uint32_t aUnicode, uint32_t aVarSelector = 0);
+
+ // For legacy MS Symbol fonts, we try mapping 8-bit character codes to the
+ // Private Use range at U+F0xx used by the cmaps in these fonts.
+ static MOZ_ALWAYS_INLINE uint32_t MapLegacySymbolFontCharToPUA(uint32_t aCh) {
+ return aCh >= 0x20 && aCh <= 0xff ? 0xf000 + aCh : 0;
+ }
+
+#ifdef XP_WIN
+ // determine whether a font (which has already been sanitized, so is known
+ // to be a valid sfnt) is CFF format rather than TrueType
+ static bool IsCffFont(const uint8_t* aFontData);
+#endif
+
+ // determine the format of font data
+ static gfxUserFontType DetermineFontDataType(const uint8_t* aFontData,
+ uint32_t aFontDataLength);
+
+ // Read the fullname from the sfnt data (used to save the original name
+ // prior to renaming the font for installation).
+ // This is called with sfnt data that has already been validated,
+ // so it should always succeed in finding the name table.
+ static nsresult GetFullNameFromSFNT(const uint8_t* aFontData,
+ uint32_t aLength, nsACString& aFullName);
+
+ // helper to get fullname from name table, constructing from family+style
+ // if no explicit fullname is present
+ static nsresult GetFullNameFromTable(hb_blob_t* aNameTable,
+ nsACString& aFullName);
+
+ // helper to get family name from name table
+ static nsresult GetFamilyNameFromTable(hb_blob_t* aNameTable,
+ nsACString& aFamilyName);
+
+ // Find the table directory entry for a given table tag, in a (validated)
+ // buffer of 'sfnt' data. Returns null if the tag is not present.
+ static mozilla::TableDirEntry* FindTableDirEntry(const void* aFontData,
+ uint32_t aTableTag);
+
+ // Return a blob that wraps a table found within a buffer of font data.
+ // The blob does NOT own its data; caller guarantees that the buffer
+ // will remain valid at least as long as the blob.
+ // Returns null if the specified table is not found.
+ // This method assumes aFontData is valid 'sfnt' data; before using this,
+ // caller is responsible to do any sanitization/validation necessary.
+ static hb_blob_t* GetTableFromFontData(const void* aFontData,
+ uint32_t aTableTag);
+
+ // create a new name table and build a new font with that name table
+ // appended on the end, returns true on success
+ static nsresult RenameFont(const nsAString& aName, const uint8_t* aFontData,
+ uint32_t aFontDataLength,
+ FallibleTArray<uint8_t>* aNewFont);
+
+ // read all names matching aNameID, returning in aNames array
+ static nsresult ReadNames(const char* aNameData, uint32_t aDataLen,
+ uint32_t aNameID, int32_t aPlatformID,
+ nsTArray<nsCString>& aNames);
+
+ // reads English or first name matching aNameID, returning in aName
+ // platform based on OS
+ static nsresult ReadCanonicalName(hb_blob_t* aNameTable, uint32_t aNameID,
+ nsCString& aName);
+
+ static nsresult ReadCanonicalName(const char* aNameData, uint32_t aDataLen,
+ uint32_t aNameID, nsCString& aName);
+
+ // convert a name from the raw name table data into an nsString,
+ // provided we know how; return true if successful, or false
+ // if we can't handle the encoding
+ static bool DecodeFontName(const char* aBuf, int32_t aLength,
+ uint32_t aPlatformCode, uint32_t aScriptCode,
+ uint32_t aLangCode, nsACString& dest);
+
+ static inline bool IsJoinCauser(uint32_t ch) { return (ch == 0x200D); }
+
+ // We treat Combining Grapheme Joiner (U+034F) together with the join
+ // controls (ZWJ, ZWNJ) here, because (like them) it is an invisible
+ // char that will be handled by the shaper even if not explicitly
+ // supported by the font. (See bug 1408366.)
+ static inline bool IsJoinControl(uint32_t ch) {
+ return (ch == 0x200C || ch == 0x200D || ch == 0x034f);
+ }
+
+ enum {
+ kUnicodeVS1 = 0xFE00,
+ kUnicodeVS16 = 0xFE0F,
+ kUnicodeVS17 = 0xE0100,
+ kUnicodeVS256 = 0xE01EF
+ };
+
+ static inline bool IsVarSelector(uint32_t ch) {
+ return (ch >= kUnicodeVS1 && ch <= kUnicodeVS16) ||
+ (ch >= kUnicodeVS17 && ch <= kUnicodeVS256);
+ }
+
+ enum {
+ kUnicodeRegionalIndicatorA = 0x1F1E6,
+ kUnicodeRegionalIndicatorZ = 0x1F1FF
+ };
+
+ static inline bool IsRegionalIndicator(uint32_t aCh) {
+ return aCh >= kUnicodeRegionalIndicatorA &&
+ aCh <= kUnicodeRegionalIndicatorZ;
+ }
+
+ static inline bool IsEmojiFlagAndTag(uint32_t aCh, uint32_t aNext) {
+ constexpr uint32_t kBlackFlag = 0x1F3F4;
+ constexpr uint32_t kTagLetterA = 0xE0061;
+ constexpr uint32_t kTagLetterZ = 0xE007A;
+
+ return aCh == kBlackFlag && aNext >= kTagLetterA && aNext <= kTagLetterZ;
+ }
+
+ static inline bool IsInvalid(uint32_t ch) { return (ch == 0xFFFD); }
+
+ // Font code may want to know if there is the potential for bidi behavior
+ // to be triggered by any of the characters in a text run; this can be
+ // used to test that possibility.
+ enum {
+ kUnicodeBidiScriptsStart = 0x0590,
+ kUnicodeBidiScriptsEnd = 0x08FF,
+ kUnicodeBidiPresentationStart = 0xFB1D,
+ kUnicodeBidiPresentationEnd = 0xFEFC,
+ kUnicodeFirstHighSurrogateBlock = 0xD800,
+ kUnicodeRLM = 0x200F,
+ kUnicodeRLE = 0x202B,
+ kUnicodeRLO = 0x202E
+ };
+
+ static inline bool PotentialRTLChar(char16_t aCh) {
+ if (aCh >= kUnicodeBidiScriptsStart && aCh <= kUnicodeBidiScriptsEnd)
+ // bidi scripts Hebrew, Arabic, Syriac, Thaana, N'Ko are all encoded
+ // together
+ return true;
+
+ if (aCh == kUnicodeRLM || aCh == kUnicodeRLE || aCh == kUnicodeRLO)
+ // directional controls that trigger bidi layout
+ return true;
+
+ if (aCh >= kUnicodeBidiPresentationStart &&
+ aCh <= kUnicodeBidiPresentationEnd)
+ // presentation forms of Arabic and Hebrew letters
+ return true;
+
+ if ((aCh & 0xFF00) == kUnicodeFirstHighSurrogateBlock)
+ // surrogate that could be part of a bidi supplementary char
+ // (Cypriot, Aramaic, Phoenecian, etc)
+ return true;
+
+ // otherwise we know this char cannot trigger bidi reordering
+ return false;
+ }
+
+ // parse a simple list of font family names into
+ // an array of strings
+ static void ParseFontList(const nsACString& aFamilyList,
+ nsTArray<nsCString>& aFontList);
+
+ // for a given pref name, initialize a list of font names
+ static void GetPrefsFontList(const char* aPrefName,
+ nsTArray<nsCString>& aFontList,
+ bool aLocalized = false);
+
+ // generate a unique font name
+ static nsresult MakeUniqueUserFontName(nsAString& aName);
+
+ // Helper used to implement gfxFontEntry::GetVariation{Axes,Instances} for
+ // platforms where the native font APIs don't provide the info we want
+ // in a convenient form, or when native APIs are too expensive.
+ // (Not used on platforms -- currently, freetype -- where the font APIs
+ // expose variation instance details directly.)
+ static void GetVariationData(gfxFontEntry* aFontEntry,
+ nsTArray<gfxFontVariationAxis>* aAxes,
+ nsTArray<gfxFontVariationInstance>* aInstances);
+
+ // Helper method for reading localized family names from the name table
+ // of a single face.
+ static void ReadOtherFamilyNamesForFace(
+ const nsACString& aFamilyName, const char* aNameData,
+ uint32_t aDataLength, nsTArray<nsCString>& aOtherFamilyNames,
+ bool useFullName);
+
+ // Main, DOM worker or servo thread safe method to check if we are performing
+ // Servo traversal.
+ static bool IsInServoTraversal();
+
+ // Main, DOM worker or servo thread safe method to get the current
+ // ServoTypeSet. Always returns nullptr for DOM worker threads.
+ static mozilla::ServoStyleSet* CurrentServoStyleSet();
+
+ static void AssertSafeThreadOrServoFontMetricsLocked()
+#ifdef DEBUG
+ ;
+#else
+ {
+ }
+#endif
+
+ protected:
+ friend struct MacCharsetMappingComparator;
+
+ static nsresult ReadNames(const char* aNameData, uint32_t aDataLen,
+ uint32_t aNameID, int32_t aLangID,
+ int32_t aPlatformID, nsTArray<nsCString>& aNames);
+
+ // convert opentype name-table platform/encoding/language values to an
+ // Encoding object we can use to convert the name data to unicode
+ static const mozilla::Encoding* GetCharsetForFontName(uint16_t aPlatform,
+ uint16_t aScript,
+ uint16_t aLanguage);
+
+ struct MacFontNameCharsetMapping {
+ uint16_t mScript;
+ uint16_t mLanguage;
+ const mozilla::Encoding* mEncoding;
+
+ bool operator<(const MacFontNameCharsetMapping& rhs) const {
+ return (mScript < rhs.mScript) ||
+ ((mScript == rhs.mScript) && (mLanguage < rhs.mLanguage));
+ }
+ };
+ static const MacFontNameCharsetMapping gMacFontNameCharsets[];
+ static const mozilla::Encoding* gISOFontNameCharsets[];
+ static const mozilla::Encoding* gMSFontNameCharsets[];
+};
+
+// Factors used to weight the distances between the available and target font
+// properties during font-matching. These ensure that we respect the CSS-fonts
+// requirement that font-stretch >> font-style >> font-weight; and in addition,
+// a mismatch between the desired and actual glyph presentation (emoji vs text)
+// will take precedence over any of the style attributes.
+constexpr double kPresentationMismatch = 1.0e12;
+constexpr double kStretchFactor = 1.0e8;
+constexpr double kStyleFactor = 1.0e4;
+constexpr double kWeightFactor = 1.0e0;
+
+// style distance ==> [0,500]
+static inline double StyleDistance(const mozilla::SlantStyleRange& aRange,
+ mozilla::FontSlantStyle aTargetStyle) {
+ const mozilla::FontSlantStyle minStyle = aRange.Min();
+ if (aTargetStyle == minStyle) {
+ return 0.0; // styles match exactly ==> 0
+ }
+
+ // bias added to angle difference when searching in the non-preferred
+ // direction from a target angle
+ const double kReverse = 100.0;
+
+ // bias added when we've crossed from positive to negative angles or
+ // vice versa
+ const double kNegate = 200.0;
+
+ if (aTargetStyle.IsNormal()) {
+ if (minStyle.IsOblique()) {
+ // to distinguish oblique 0deg from normal, we add 1.0 to the angle
+ const double minAngle = minStyle.ObliqueAngle();
+ if (minAngle >= 0.0) {
+ return 1.0 + minAngle;
+ }
+ const mozilla::FontSlantStyle maxStyle = aRange.Max();
+ const double maxAngle = maxStyle.ObliqueAngle();
+ if (maxAngle >= 0.0) {
+ // [min,max] range includes 0.0, so just return our minimum
+ return 1.0;
+ }
+ // negative oblique is even worse than italic
+ return kNegate - maxAngle;
+ }
+ // must be italic, which is worse than any non-negative oblique;
+ // treat as a match in the wrong search direction
+ MOZ_ASSERT(minStyle.IsItalic());
+ return kReverse;
+ }
+
+ const double kDefaultAngle = mozilla::FontSlantStyle::DEFAULT_OBLIQUE_DEGREES;
+
+ if (aTargetStyle.IsItalic()) {
+ if (minStyle.IsOblique()) {
+ const double minAngle = minStyle.ObliqueAngle();
+ if (minAngle >= kDefaultAngle) {
+ return 1.0 + (minAngle - kDefaultAngle);
+ }
+ const mozilla::FontSlantStyle maxStyle = aRange.Max();
+ const double maxAngle = maxStyle.ObliqueAngle();
+ if (maxAngle >= kDefaultAngle) {
+ return 1.0;
+ }
+ if (maxAngle > 0.0) {
+ // wrong direction but still > 0, add bias of 100
+ return kReverse + (kDefaultAngle - maxAngle);
+ }
+ // negative oblique angle, add bias of 300
+ return kReverse + kNegate + (kDefaultAngle - maxAngle);
+ }
+ // normal is worse than oblique > 0, but better than oblique <= 0
+ MOZ_ASSERT(minStyle.IsNormal());
+ return kNegate;
+ }
+
+ // target is oblique <angle>: four different cases depending on
+ // the value of the <angle>, which determines the preferred direction
+ // of search
+ const double targetAngle = aTargetStyle.ObliqueAngle();
+ if (targetAngle >= kDefaultAngle) {
+ if (minStyle.IsOblique()) {
+ const double minAngle = minStyle.ObliqueAngle();
+ if (minAngle >= targetAngle) {
+ return minAngle - targetAngle;
+ }
+ const mozilla::FontSlantStyle maxStyle = aRange.Max();
+ const double maxAngle = maxStyle.ObliqueAngle();
+ if (maxAngle >= targetAngle) {
+ return 0.0;
+ }
+ if (maxAngle > 0.0) {
+ return kReverse + (targetAngle - maxAngle);
+ }
+ return kReverse + kNegate + (targetAngle - maxAngle);
+ }
+ if (minStyle.IsItalic()) {
+ return kReverse + kNegate;
+ }
+ return kReverse + kNegate + 1.0;
+ }
+
+ if (targetAngle <= -kDefaultAngle) {
+ if (minStyle.IsOblique()) {
+ const mozilla::FontSlantStyle maxStyle = aRange.Max();
+ const double maxAngle = maxStyle.ObliqueAngle();
+ if (maxAngle <= targetAngle) {
+ return targetAngle - maxAngle;
+ }
+ const double minAngle = minStyle.ObliqueAngle();
+ if (minAngle <= targetAngle) {
+ return 0.0;
+ }
+ if (minAngle < 0.0) {
+ return kReverse + (minAngle - targetAngle);
+ }
+ return kReverse + kNegate + (minAngle - targetAngle);
+ }
+ if (minStyle.IsItalic()) {
+ return kReverse + kNegate;
+ }
+ return kReverse + kNegate + 1.0;
+ }
+
+ if (targetAngle >= 0.0) {
+ if (minStyle.IsOblique()) {
+ const double minAngle = minStyle.ObliqueAngle();
+ if (minAngle > targetAngle) {
+ return kReverse + (minAngle - targetAngle);
+ }
+ const mozilla::FontSlantStyle maxStyle = aRange.Max();
+ const double maxAngle = maxStyle.ObliqueAngle();
+ if (maxAngle >= targetAngle) {
+ return 0.0;
+ }
+ if (maxAngle > 0.0) {
+ return targetAngle - maxAngle;
+ }
+ return kReverse + kNegate + (targetAngle - maxAngle);
+ }
+ if (minStyle.IsItalic()) {
+ return kReverse + kNegate - 2.0;
+ }
+ return kReverse + kNegate - 1.0;
+ }
+
+ // last case: (targetAngle < 0.0 && targetAngle > kDefaultAngle)
+ if (minStyle.IsOblique()) {
+ const mozilla::FontSlantStyle maxStyle = aRange.Max();
+ const double maxAngle = maxStyle.ObliqueAngle();
+ if (maxAngle < targetAngle) {
+ return kReverse + (targetAngle - maxAngle);
+ }
+ const double minAngle = minStyle.ObliqueAngle();
+ if (minAngle <= targetAngle) {
+ return 0.0;
+ }
+ if (minAngle < 0.0) {
+ return minAngle - targetAngle;
+ }
+ return kReverse + kNegate + (minAngle - targetAngle);
+ }
+ if (minStyle.IsItalic()) {
+ return kReverse + kNegate - 2.0;
+ }
+ return kReverse + kNegate - 1.0;
+}
+
+// stretch distance ==> [0,2000]
+static inline double StretchDistance(const mozilla::StretchRange& aRange,
+ mozilla::FontStretch aTargetStretch) {
+ const double kReverseDistance = 1000.0;
+
+ mozilla::FontStretch minStretch = aRange.Min();
+ mozilla::FontStretch maxStretch = aRange.Max();
+
+ // The stretch value is a (non-negative) percentage; currently we support
+ // values in the range 0 .. 1000. (If the upper limit is ever increased,
+ // the kReverseDistance value used here may need to be adjusted.)
+ // If aTargetStretch is >100, we prefer larger values if available;
+ // if <=100, we prefer smaller values if available.
+ if (aTargetStretch < minStretch) {
+ if (aTargetStretch > mozilla::FontStretch::NORMAL) {
+ return minStretch.ToFloat() - aTargetStretch.ToFloat();
+ }
+ return (minStretch.ToFloat() - aTargetStretch.ToFloat()) + kReverseDistance;
+ }
+ if (aTargetStretch > maxStretch) {
+ if (aTargetStretch <= mozilla::FontStretch::NORMAL) {
+ return aTargetStretch.ToFloat() - maxStretch.ToFloat();
+ }
+ return (aTargetStretch.ToFloat() - maxStretch.ToFloat()) + kReverseDistance;
+ }
+ return 0.0;
+}
+
+// Calculate weight distance with values in the range (0..1000). In general,
+// heavier weights match towards even heavier weights while lighter weights
+// match towards even lighter weights. Target weight values in the range
+// [400..500] are special, since they will first match up to 500, then down
+// towards 0, then up again towards 999.
+//
+// Example: with target 600 and font weight 800, distance will be 200. With
+// target 300 and font weight 600, distance will be 900, since heavier
+// weights are farther away than lighter weights. If the target is 5 and the
+// font weight 995, the distance would be 1590 for the same reason.
+
+// weight distance ==> [0,1600]
+static inline double WeightDistance(const mozilla::WeightRange& aRange,
+ mozilla::FontWeight aTargetWeight) {
+ const double kNotWithinCentralRange = 100.0;
+ const double kReverseDistance = 600.0;
+
+ mozilla::FontWeight minWeight = aRange.Min();
+ mozilla::FontWeight maxWeight = aRange.Max();
+
+ if (aTargetWeight >= minWeight && aTargetWeight <= maxWeight) {
+ // Target is within the face's range, so it's a perfect match
+ return 0.0;
+ }
+
+ if (aTargetWeight < mozilla::FontWeight::NORMAL) {
+ // Requested a lighter-than-400 weight
+ if (maxWeight < aTargetWeight) {
+ return aTargetWeight.ToFloat() - maxWeight.ToFloat();
+ }
+ // Add reverse-search penalty for bolder faces
+ return (minWeight.ToFloat() - aTargetWeight.ToFloat()) + kReverseDistance;
+ }
+
+ if (aTargetWeight > mozilla::FontWeight::FromInt(500)) {
+ // Requested a bolder-than-500 weight
+ if (minWeight > aTargetWeight) {
+ return minWeight.ToFloat() - aTargetWeight.ToFloat();
+ }
+ // Add reverse-search penalty for lighter faces
+ return (aTargetWeight.ToFloat() - maxWeight.ToFloat()) + kReverseDistance;
+ }
+
+ // Special case for requested weight in the [400..500] range
+ if (minWeight > aTargetWeight) {
+ if (minWeight <= mozilla::FontWeight::FromInt(500)) {
+ // Bolder weight up to 500 is first choice
+ return minWeight.ToFloat() - aTargetWeight.ToFloat();
+ }
+ // Other bolder weights get a reverse-search penalty
+ return (minWeight.ToFloat() - aTargetWeight.ToFloat()) + kReverseDistance;
+ }
+ // Lighter weights are not as good as bolder ones within [400..500]
+ return (aTargetWeight.ToFloat() - maxWeight.ToFloat()) +
+ kNotWithinCentralRange;
+}
+
+#endif /* GFX_FONT_UTILS_H */
diff --git a/gfx/thebes/gfxFontVariations.h b/gfx/thebes/gfxFontVariations.h
new file mode 100644
index 0000000000..e589ffc8cb
--- /dev/null
+++ b/gfx/thebes/gfxFontVariations.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_FONT_VARIATIONS_H
+#define GFX_FONT_VARIATIONS_H
+
+#include "mozilla/gfx/FontVariation.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+typedef mozilla::gfx::FontVariation gfxFontVariation;
+
+// Structure that describes a single axis of variation in an
+// OpenType Variation or Multiple-Master font.
+struct gfxFontVariationAxis {
+ uint32_t mTag;
+ nsCString mName; // may be empty
+ float mMinValue;
+ float mMaxValue;
+ float mDefaultValue;
+};
+
+// A single <axis, value> pair that may be applied to a variation font.
+struct gfxFontVariationValue {
+ uint32_t mAxis;
+ float mValue;
+};
+
+// Structure that describes a named instance of a variation font:
+// a name like "Light Condensed" or "Black Ultra Extended" etc.,
+// and a list of the corresponding <variation-axis, value> pairs
+// to be used.
+struct gfxFontVariationInstance {
+ nsCString mName;
+ CopyableTArray<gfxFontVariationValue> mValues;
+};
+
+#endif
diff --git a/gfx/thebes/gfxGDIFont.cpp b/gfx/thebes/gfxGDIFont.cpp
new file mode 100644
index 0000000000..2053b33727
--- /dev/null
+++ b/gfx/thebes/gfxGDIFont.cpp
@@ -0,0 +1,553 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxGDIFont.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/WindowsVersion.h"
+
+#include <algorithm>
+#include "gfxWindowsPlatform.h"
+#include "gfxContext.h"
+#include "mozilla/Preferences.h"
+#include "nsUnicodeProperties.h"
+#include "gfxFontConstants.h"
+#include "gfxHarfBuzzShaper.h"
+#include "gfxTextRun.h"
+
+#include "cairo-win32.h"
+
+#define ROUND(x) floor((x) + 0.5)
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::unicode;
+
+gfxGDIFont::gfxGDIFont(GDIFontEntry* aFontEntry, const gfxFontStyle* aFontStyle,
+ AntialiasOption anAAOption)
+ : gfxFont(nullptr, aFontEntry, aFontStyle, anAAOption),
+ mFont(nullptr),
+ mMetrics(nullptr),
+ mIsBitmap(false),
+ mScriptCache(nullptr) {
+ mNeedsSyntheticBold = aFontStyle->NeedsSyntheticBold(aFontEntry);
+
+ Initialize();
+
+ if (mFont) {
+ mUnscaledFont = aFontEntry->LookupUnscaledFont(mFont);
+ }
+}
+
+gfxGDIFont::~gfxGDIFont() {
+ if (mFont) {
+ ::DeleteObject(mFont);
+ }
+ if (mScriptCache) {
+ ScriptFreeCache(&mScriptCache);
+ }
+ delete mMetrics;
+}
+
+gfxFont* gfxGDIFont::CopyWithAntialiasOption(AntialiasOption anAAOption) const {
+ auto entry = static_cast<GDIFontEntry*>(mFontEntry.get());
+ return new gfxGDIFont(entry, &mStyle, anAAOption);
+}
+
+bool gfxGDIFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText,
+ uint32_t aOffset, uint32_t aLength, Script aScript,
+ nsAtom* aLanguage, bool aVertical,
+ RoundingFlags aRounding,
+ gfxShapedText* aShapedText) {
+ if (!mIsValid) {
+ NS_WARNING("invalid font! expect incorrect text rendering");
+ return false;
+ }
+
+ return gfxFont::ShapeText(aDrawTarget, aText, aOffset, aLength, aScript,
+ aLanguage, aVertical, aRounding, aShapedText);
+}
+
+already_AddRefed<ScaledFont> gfxGDIFont::GetScaledFont(
+ const TextRunDrawParams& aRunParams) {
+ if (ScaledFont* scaledFont = mAzureScaledFont) {
+ return do_AddRef(scaledFont);
+ }
+
+ LOGFONT lf;
+ GetObject(GetHFONT(), sizeof(LOGFONT), &lf);
+
+ RefPtr<ScaledFont> newScaledFont = Factory::CreateScaledFontForGDIFont(
+ &lf, GetUnscaledFont(), GetAdjustedSize());
+ if (!newScaledFont) {
+ return nullptr;
+ }
+
+ InitializeScaledFont(newScaledFont);
+
+ if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) {
+ Unused << newScaledFont.forget();
+ }
+ ScaledFont* scaledFont = mAzureScaledFont;
+ return do_AddRef(scaledFont);
+}
+
+gfxFont::RunMetrics gfxGDIFont::Measure(const gfxTextRun* aTextRun,
+ uint32_t aStart, uint32_t aEnd,
+ BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget,
+ Spacing* aSpacing,
+ gfx::ShapedTextFlags aOrientation) {
+ gfxFont::RunMetrics metrics =
+ gfxFont::Measure(aTextRun, aStart, aEnd, aBoundingBoxType, aRefDrawTarget,
+ aSpacing, aOrientation);
+
+ // if aBoundingBoxType is LOOSE_INK_EXTENTS
+ // and the underlying cairo font may be antialiased,
+ // we can't trust Windows to have considered all the pixels
+ // so we need to add "padding" to the bounds.
+ // (see bugs 475968, 439831, compare also bug 445087)
+ if (aBoundingBoxType == LOOSE_INK_EXTENTS &&
+ mAntialiasOption != kAntialiasNone && metrics.mBoundingBox.Width() > 0) {
+ metrics.mBoundingBox.MoveByX(-aTextRun->GetAppUnitsPerDevUnit());
+ metrics.mBoundingBox.SetWidth(metrics.mBoundingBox.Width() +
+ aTextRun->GetAppUnitsPerDevUnit() * 3);
+ }
+
+ return metrics;
+}
+
+void gfxGDIFont::Initialize() {
+ NS_ASSERTION(!mMetrics, "re-creating metrics? this will leak");
+
+ LOGFONTW logFont;
+
+ if (mAdjustedSize == 0.0) {
+ mAdjustedSize = GetAdjustedSize();
+ if (FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) !=
+ FontSizeAdjust::Tag::None) {
+ if (mStyle.sizeAdjust > 0.0 && mAdjustedSize > 0.0) {
+ // to implement font-size-adjust, we first create the "unadjusted" font
+ FillLogFont(logFont, mAdjustedSize);
+ mFont = ::CreateFontIndirectW(&logFont);
+
+ // initialize its metrics so we can calculate size adjustment
+ Initialize();
+
+ // Unless the font was so small that GDI metrics rounded to zero,
+ // calculate the properly adjusted size, and then proceed
+ // to recreate mFont and recalculate metrics
+ if (mMetrics->emHeight > 0.0) {
+ gfxFloat aspect;
+ switch (FontSizeAdjust::Tag(mStyle.sizeAdjustBasis)) {
+ default:
+ MOZ_ASSERT_UNREACHABLE("unhandled sizeAdjustBasis?");
+ aspect = 0.0;
+ break;
+ case FontSizeAdjust::Tag::ExHeight:
+ aspect = mMetrics->xHeight / mMetrics->emHeight;
+ break;
+ case FontSizeAdjust::Tag::CapHeight:
+ aspect = mMetrics->capHeight / mMetrics->emHeight;
+ break;
+ case FontSizeAdjust::Tag::ChWidth: {
+ gfxFloat advance = GetCharAdvance('0');
+ aspect = advance > 0.0 ? advance / mMetrics->emHeight : 0.5;
+ break;
+ }
+ case FontSizeAdjust::Tag::IcWidth:
+ case FontSizeAdjust::Tag::IcHeight: {
+ bool vertical = FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) ==
+ FontSizeAdjust::Tag::IcHeight;
+ gfxFloat advance = GetCharAdvance(kWaterIdeograph, vertical);
+ aspect = advance > 0.0 ? advance / mMetrics->emHeight : 1.0;
+ break;
+ }
+ }
+ if (aspect > 0.0) {
+ // If we created a shaper above (to measure glyphs), discard it so
+ // we get a new one for the adjusted scaling.
+ delete mHarfBuzzShaper.exchange(nullptr);
+ mAdjustedSize = mStyle.GetAdjustedSize(aspect);
+ }
+ }
+
+ // delete the temporary font and metrics
+ ::DeleteObject(mFont);
+ mFont = nullptr;
+ delete mMetrics;
+ mMetrics = nullptr;
+ } else {
+ mAdjustedSize = 0.0;
+ }
+ }
+ }
+
+ // (bug 724231) for local user fonts, we don't use GDI's synthetic bold,
+ // as it could lead to a different, incompatible face being used
+ // but instead do our own multi-striking
+ if (mNeedsSyntheticBold && GetFontEntry()->IsLocalUserFont()) {
+ mApplySyntheticBold = true;
+ }
+
+ // this may end up being zero
+ mAdjustedSize = ROUND(mAdjustedSize);
+ FillLogFont(logFont, mAdjustedSize);
+ mFont = ::CreateFontIndirectW(&logFont);
+
+ mMetrics = new gfxFont::Metrics;
+ ::memset(mMetrics, 0, sizeof(*mMetrics));
+
+ if (!mFont) {
+ NS_WARNING("Failed creating GDI font");
+ mIsValid = false;
+ return;
+ }
+
+ AutoDC dc;
+ SetGraphicsMode(dc.GetDC(), GM_ADVANCED);
+ AutoSelectFont selectFont(dc.GetDC(), mFont);
+
+ // Get font metrics if size > 0
+ if (mAdjustedSize > 0.0) {
+ OUTLINETEXTMETRIC oMetrics;
+ TEXTMETRIC& metrics = oMetrics.otmTextMetrics;
+
+ if (0 < GetOutlineTextMetrics(dc.GetDC(), sizeof(oMetrics), &oMetrics)) {
+ mMetrics->strikeoutSize = (double)oMetrics.otmsStrikeoutSize;
+ mMetrics->strikeoutOffset = (double)oMetrics.otmsStrikeoutPosition;
+ mMetrics->underlineSize = (double)oMetrics.otmsUnderscoreSize;
+ mMetrics->underlineOffset = (double)oMetrics.otmsUnderscorePosition;
+
+ const MAT2 kIdentityMatrix = {{0, 1}, {0, 0}, {0, 0}, {0, 1}};
+ GLYPHMETRICS gm;
+ DWORD len = GetGlyphOutlineW(dc.GetDC(), char16_t('x'), GGO_METRICS, &gm,
+ 0, nullptr, &kIdentityMatrix);
+ if (len == GDI_ERROR || gm.gmptGlyphOrigin.y <= 0) {
+ // 56% of ascent, best guess for true type
+ mMetrics->xHeight =
+ ROUND((double)metrics.tmAscent * DEFAULT_XHEIGHT_FACTOR);
+ } else {
+ mMetrics->xHeight = gm.gmptGlyphOrigin.y;
+ }
+ len = GetGlyphOutlineW(dc.GetDC(), char16_t('H'), GGO_METRICS, &gm, 0,
+ nullptr, &kIdentityMatrix);
+ if (len == GDI_ERROR || gm.gmptGlyphOrigin.y <= 0) {
+ mMetrics->capHeight = metrics.tmAscent - metrics.tmInternalLeading;
+ } else {
+ mMetrics->capHeight = gm.gmptGlyphOrigin.y;
+ }
+ mMetrics->emHeight = metrics.tmHeight - metrics.tmInternalLeading;
+ gfxFloat typEmHeight =
+ (double)oMetrics.otmAscent - (double)oMetrics.otmDescent;
+ mMetrics->emAscent =
+ ROUND(mMetrics->emHeight * (double)oMetrics.otmAscent / typEmHeight);
+ mMetrics->emDescent = mMetrics->emHeight - mMetrics->emAscent;
+ if (oMetrics.otmEMSquare > 0) {
+ mFUnitsConvFactor = float(mAdjustedSize / oMetrics.otmEMSquare);
+ }
+ } else {
+ // Make a best-effort guess at extended metrics
+ // this is based on general typographic guidelines
+
+ // GetTextMetrics can fail if the font file has been removed
+ // or corrupted recently.
+ BOOL result = GetTextMetrics(dc.GetDC(), &metrics);
+ if (!result) {
+ NS_WARNING("Missing or corrupt font data, fasten your seatbelt");
+ mIsValid = false;
+ memset(mMetrics, 0, sizeof(*mMetrics));
+ return;
+ }
+
+ mMetrics->xHeight =
+ ROUND((float)metrics.tmAscent * DEFAULT_XHEIGHT_FACTOR);
+ mMetrics->strikeoutSize = 1;
+ mMetrics->strikeoutOffset =
+ ROUND(mMetrics->xHeight * 0.5f); // 50% of xHeight
+ mMetrics->underlineSize = 1;
+ mMetrics->underlineOffset =
+ -ROUND((float)metrics.tmDescent * 0.30f); // 30% of descent
+ mMetrics->emHeight = metrics.tmHeight - metrics.tmInternalLeading;
+ mMetrics->emAscent = metrics.tmAscent - metrics.tmInternalLeading;
+ mMetrics->emDescent = metrics.tmDescent;
+ mMetrics->capHeight = mMetrics->emAscent;
+ }
+
+ mMetrics->internalLeading = metrics.tmInternalLeading;
+ mMetrics->externalLeading = metrics.tmExternalLeading;
+ mMetrics->maxHeight = metrics.tmHeight;
+ mMetrics->maxAscent = metrics.tmAscent;
+ mMetrics->maxDescent = metrics.tmDescent;
+ mMetrics->maxAdvance = metrics.tmMaxCharWidth;
+ mMetrics->aveCharWidth = std::max<gfxFloat>(1, metrics.tmAveCharWidth);
+ // The font is monospace when TMPF_FIXED_PITCH is *not* set!
+ // See http://msdn2.microsoft.com/en-us/library/ms534202(VS.85).aspx
+ if (!(metrics.tmPitchAndFamily & TMPF_FIXED_PITCH)) {
+ mMetrics->maxAdvance = mMetrics->aveCharWidth;
+ }
+
+ mIsBitmap = !(metrics.tmPitchAndFamily & TMPF_VECTOR);
+
+ // For fonts with USE_TYPO_METRICS set in the fsSelection field,
+ // let the OS/2 sTypo* metrics override the previous values.
+ // (see http://www.microsoft.com/typography/otspec/os2.htm#fss)
+ // Using the equivalent values from oMetrics provides inconsistent
+ // results with CFF fonts, so we instead rely on OS2Table.
+ gfxFontEntry::AutoTable os2Table(mFontEntry,
+ TRUETYPE_TAG('O', 'S', '/', '2'));
+ if (os2Table) {
+ uint32_t len;
+ const OS2Table* os2 =
+ reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len));
+ if (len >= offsetof(OS2Table, sTypoLineGap) + sizeof(int16_t)) {
+ const uint16_t kUseTypoMetricsMask = 1 << 7;
+ if ((uint16_t(os2->fsSelection) & kUseTypoMetricsMask)) {
+ double ascent = int16_t(os2->sTypoAscender);
+ double descent = int16_t(os2->sTypoDescender);
+ double lineGap = int16_t(os2->sTypoLineGap);
+ mMetrics->maxAscent = ROUND(ascent * mFUnitsConvFactor);
+ mMetrics->maxDescent = -ROUND(descent * mFUnitsConvFactor);
+ mMetrics->maxHeight = mMetrics->maxAscent + mMetrics->maxDescent;
+ mMetrics->internalLeading = mMetrics->maxHeight - mMetrics->emHeight;
+ gfxFloat lineHeight =
+ ROUND((ascent - descent + lineGap) * mFUnitsConvFactor);
+ lineHeight = std::max(lineHeight, mMetrics->maxHeight);
+ mMetrics->externalLeading = lineHeight - mMetrics->maxHeight;
+ }
+ }
+ // although sxHeight and sCapHeight are signed fields, we consider
+ // negative values to be erroneous and just ignore them
+ if (uint16_t(os2->version) >= 2) {
+ // version 2 and later includes the x-height and cap-height fields
+ if (len >= offsetof(OS2Table, sxHeight) + sizeof(int16_t) &&
+ int16_t(os2->sxHeight) > 0) {
+ mMetrics->xHeight = ROUND(int16_t(os2->sxHeight) * mFUnitsConvFactor);
+ }
+ if (len >= offsetof(OS2Table, sCapHeight) + sizeof(int16_t) &&
+ int16_t(os2->sCapHeight) > 0) {
+ mMetrics->capHeight =
+ ROUND(int16_t(os2->sCapHeight) * mFUnitsConvFactor);
+ }
+ }
+ }
+
+ WORD glyph;
+ SIZE size;
+ DWORD ret = GetGlyphIndicesW(dc.GetDC(), L" ", 1, &glyph,
+ GGI_MARK_NONEXISTING_GLYPHS);
+ if (ret != GDI_ERROR && glyph != 0xFFFF) {
+ mSpaceGlyph = glyph;
+ // Cache the width of a single space.
+ GetTextExtentPoint32W(dc.GetDC(), L" ", 1, &size);
+ mMetrics->spaceWidth = ROUND(size.cx);
+ } else {
+ mMetrics->spaceWidth = mMetrics->aveCharWidth;
+ }
+
+ // Cache the width of digit zero, if available.
+ ret = GetGlyphIndicesW(dc.GetDC(), L"0", 1, &glyph,
+ GGI_MARK_NONEXISTING_GLYPHS);
+ if (ret != GDI_ERROR && glyph != 0xFFFF) {
+ GetTextExtentPoint32W(dc.GetDC(), L"0", 1, &size);
+ mMetrics->zeroWidth = ROUND(size.cx);
+ } else {
+ mMetrics->zeroWidth = -1.0; // indicates not found
+ }
+
+ wchar_t ch = kWaterIdeograph;
+ ret = GetGlyphIndicesW(dc.GetDC(), &ch, 1, &glyph,
+ GGI_MARK_NONEXISTING_GLYPHS);
+ if (ret != GDI_ERROR && glyph != 0xFFFF) {
+ GetTextExtentPoint32W(dc.GetDC(), &ch, 1, &size);
+ mMetrics->ideographicWidth = ROUND(size.cx);
+ } else {
+ mMetrics->ideographicWidth = -1.0;
+ }
+
+ SanitizeMetrics(mMetrics, GetFontEntry()->mIsBadUnderlineFont);
+ } else {
+ mFUnitsConvFactor = 0.0; // zero-sized font: all values scale to zero
+ }
+
+ if (ApplySyntheticBold()) {
+ auto delta = GetSyntheticBoldOffset();
+ mMetrics->spaceWidth += delta;
+ mMetrics->aveCharWidth += delta;
+ mMetrics->maxAdvance += delta;
+ if (mMetrics->zeroWidth > 0) {
+ mMetrics->zeroWidth += delta;
+ }
+ if (mMetrics->ideographicWidth > 0) {
+ mMetrics->ideographicWidth += delta;
+ }
+ }
+
+#if 0
+ printf("Font: %p (%s) size: %f adjusted size: %f valid: %s\n", this,
+ GetName().get(), mStyle.size, mAdjustedSize, (mIsValid ? "yes" : "no"));
+ printf(" emHeight: %f emAscent: %f emDescent: %f\n", mMetrics->emHeight, mMetrics->emAscent, mMetrics->emDescent);
+ printf(" maxAscent: %f maxDescent: %f maxAdvance: %f\n", mMetrics->maxAscent, mMetrics->maxDescent, mMetrics->maxAdvance);
+ printf(" internalLeading: %f externalLeading: %f\n", mMetrics->internalLeading, mMetrics->externalLeading);
+ printf(" spaceWidth: %f aveCharWidth: %f\n", mMetrics->spaceWidth, mMetrics->aveCharWidth);
+ printf(" xHeight: %f capHeight: %f\n", mMetrics->xHeight, mMetrics->capHeight);
+ printf(" uOff: %f uSize: %f stOff: %f stSize: %f\n",
+ mMetrics->underlineOffset, mMetrics->underlineSize, mMetrics->strikeoutOffset, mMetrics->strikeoutSize);
+#endif
+}
+
+void gfxGDIFont::FillLogFont(LOGFONTW& aLogFont, gfxFloat aSize) {
+ GDIFontEntry* fe = static_cast<GDIFontEntry*>(GetFontEntry());
+
+ // Figure out the lfWeight value to use for GDI font selection,
+ // or zero to use the entry's current LOGFONT value.
+ LONG weight;
+ if (fe->IsUserFont()) {
+ if (fe->IsLocalUserFont()) {
+ // for local user fonts, don't change the original weight
+ // in the entry's logfont, because that could alter the
+ // choice of actual face used (bug 724231)
+ weight = 0;
+ } else {
+ // avoid GDI synthetic bold which occurs when weight
+ // specified is >= font data weight + 200
+ weight = mNeedsSyntheticBold ? 700 : 200;
+ }
+ } else {
+ // GDI doesn't support variation fonts, so for system fonts we know
+ // that the entry has only a single weight, not a range.
+ MOZ_ASSERT(fe->Weight().IsSingle());
+ weight = mNeedsSyntheticBold ? 700 : fe->Weight().Min().ToIntRounded();
+ }
+
+ fe->FillLogFont(&aLogFont, weight, aSize);
+}
+
+uint32_t gfxGDIFont::GetGlyph(uint32_t aUnicode, uint32_t aVarSelector) {
+ // Callback used only for fonts that lack a 'cmap' table.
+
+ // We don't support variation selector sequences or non-BMP characters
+ // in the legacy bitmap, vector or postscript fonts that might use
+ // this code path.
+ if (aUnicode > 0xffff || aVarSelector) {
+ return 0;
+ }
+
+ if (!mGlyphIDs) {
+ mGlyphIDs = MakeUnique<nsTHashMap<nsUint32HashKey, uint32_t>>(64);
+ }
+
+ uint32_t gid;
+ if (mGlyphIDs->Get(aUnicode, &gid)) {
+ return gid;
+ }
+
+ wchar_t ch = aUnicode;
+ WORD glyph;
+ HRESULT ret = ScriptGetCMap(nullptr, &mScriptCache, &ch, 1, 0, &glyph);
+ if (ret != S_OK) {
+ AutoDC dc;
+ AutoSelectFont fs(dc.GetDC(), GetHFONT());
+ if (ret == E_PENDING) {
+ // Try ScriptGetCMap again now that we've set up the font.
+ ret = ScriptGetCMap(dc.GetDC(), &mScriptCache, &ch, 1, 0, &glyph);
+ }
+ if (ret != S_OK) {
+ // If ScriptGetCMap still failed, fall back to GetGlyphIndicesW
+ // (see bug 1105807).
+ DWORD ret = GetGlyphIndicesW(dc.GetDC(), &ch, 1, &glyph,
+ GGI_MARK_NONEXISTING_GLYPHS);
+ if (ret == GDI_ERROR || glyph == 0xFFFF) {
+ glyph = 0;
+ }
+ }
+ }
+
+ mGlyphIDs->InsertOrUpdate(aUnicode, glyph);
+ return glyph;
+}
+
+int32_t gfxGDIFont::GetGlyphWidth(uint16_t aGID) {
+ if (!mGlyphWidths) {
+ mGlyphWidths = MakeUnique<nsTHashMap<nsUint32HashKey, int32_t>>(128);
+ }
+
+ return mGlyphWidths->WithEntryHandle(aGID, [&](auto&& entry) {
+ if (!entry) {
+ DCForMetrics dc;
+ AutoSelectFont fs(dc, GetHFONT());
+
+ int devWidth;
+ if (!GetCharWidthI(dc, aGID, 1, nullptr, &devWidth)) {
+ return -1;
+ }
+ // clamp value to range [0..0x7fff], and convert to 16.16 fixed-point
+ devWidth = std::min(std::max(0, devWidth), 0x7fff);
+ entry.Insert(devWidth << 16);
+ }
+ return *entry;
+ });
+}
+
+bool gfxGDIFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) {
+ DCForMetrics dc;
+ AutoSelectFont fs(dc, GetHFONT());
+
+ if (mIsBitmap) {
+ int devWidth;
+ if (!GetCharWidthI(dc, aGID, 1, nullptr, &devWidth)) {
+ return false;
+ }
+ devWidth = std::min(std::max(0, devWidth), 0x7fff);
+
+ *aBounds = gfxRect(0, -mMetrics->maxAscent, devWidth,
+ mMetrics->maxAscent + mMetrics->maxDescent);
+ return true;
+ }
+
+ const MAT2 kIdentityMatrix = {{0, 1}, {0, 0}, {0, 0}, {0, 1}};
+ GLYPHMETRICS gm;
+ if (GetGlyphOutlineW(dc, aGID, GGO_METRICS | GGO_GLYPH_INDEX, &gm, 0, nullptr,
+ &kIdentityMatrix) == GDI_ERROR) {
+ return false;
+ }
+
+ if (gm.gmBlackBoxX == 1 && gm.gmBlackBoxY == 1 &&
+ !GetGlyphOutlineW(dc, aGID, GGO_NATIVE | GGO_GLYPH_INDEX, &gm, 0, nullptr,
+ &kIdentityMatrix)) {
+ // Workaround for GetGlyphOutline returning 1x1 bounding box
+ // for <space> glyph that is in fact empty.
+ gm.gmBlackBoxX = 0;
+ gm.gmBlackBoxY = 0;
+ } else if (gm.gmBlackBoxX > 0 && !aTight) {
+ // The bounding box reported by Windows supposedly contains the glyph's
+ // "black" area; however, antialiasing (especially with ClearType) means
+ // that the actual image that needs to be rendered may "bleed" into the
+ // adjacent pixels, mainly on the right side.
+ gm.gmptGlyphOrigin.x -= 1;
+ gm.gmBlackBoxX += 3;
+ }
+
+ *aBounds = gfxRect(gm.gmptGlyphOrigin.x, -gm.gmptGlyphOrigin.y,
+ gm.gmBlackBoxX, gm.gmBlackBoxY);
+ return true;
+}
+
+void gfxGDIFont::AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const {
+ gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+ aSizes->mFontInstances += aMallocSizeOf(mMetrics);
+ if (mGlyphWidths) {
+ aSizes->mFontInstances +=
+ mGlyphWidths->ShallowSizeOfIncludingThis(aMallocSizeOf);
+ }
+}
+
+void gfxGDIFont::AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const {
+ aSizes->mFontInstances += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
diff --git a/gfx/thebes/gfxGDIFont.h b/gfx/thebes/gfxGDIFont.h
new file mode 100644
index 0000000000..505f731a79
--- /dev/null
+++ b/gfx/thebes/gfxGDIFont.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_GDIFONT_H
+#define GFX_GDIFONT_H
+
+#include "mozilla/MemoryReporting.h"
+#include "gfxFont.h"
+#include "gfxGDIFontList.h"
+
+#include "nsTHashMap.h"
+#include "nsHashKeys.h"
+
+#include "usp10.h"
+
+class gfxGDIFont final : public gfxFont {
+ public:
+ gfxGDIFont(GDIFontEntry* aFontEntry, const gfxFontStyle* aFontStyle,
+ AntialiasOption anAAOption = kAntialiasDefault);
+
+ HFONT GetHFONT() const { return mFont; }
+
+ already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont(
+ const TextRunDrawParams& aRunParams) override;
+
+ /* override Measure to add padding for antialiasing */
+ RunMetrics Measure(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd,
+ BoundingBoxType aBoundingBoxType,
+ DrawTarget* aDrawTargetForTightBoundingBox,
+ Spacing* aSpacing,
+ mozilla::gfx::ShapedTextFlags aOrientation) override;
+
+ /* required for MathML to suppress effects of ClearType "padding" */
+ gfxFont* CopyWithAntialiasOption(AntialiasOption anAAOption) const override;
+
+ // If the font has a cmap table, we handle it purely with harfbuzz;
+ // but if not (e.g. .fon fonts), we'll use a GDI callback to get glyphs.
+ bool ProvidesGetGlyph() const override { return !mFontEntry->HasCmapTable(); }
+
+ uint32_t GetGlyph(uint32_t aUnicode, uint32_t aVarSelector) override;
+
+ bool ProvidesGlyphWidths() const override { return true; }
+
+ // get hinted glyph width in pixels as 16.16 fixed-point value
+ int32_t GetGlyphWidth(uint16_t aGID) override;
+
+ bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override;
+
+ void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const;
+ void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const;
+
+ FontType GetType() const override { return FONT_TYPE_GDI; }
+
+ protected:
+ ~gfxGDIFont() override;
+
+ const Metrics& GetHorizontalMetrics() const override { return *mMetrics; }
+
+ bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText,
+ uint32_t aOffset, uint32_t aLength, Script aScript,
+ nsAtom* aLanguage, bool aVertical, RoundingFlags aRounding,
+ gfxShapedText* aShapedText) override;
+
+ void Initialize(); // creates metrics and Cairo fonts
+
+ // Fill the given LOGFONT record according to our size.
+ // (Synthetic italic is *not* handled here, because GDI may not reliably
+ // use the face we expect if we tweak the lfItalic field, and because we
+ // have generic support for this in gfxFont::Draw instead.)
+ void FillLogFont(LOGFONTW& aLogFont, gfxFloat aSize);
+
+ HFONT mFont;
+
+ Metrics* mMetrics;
+ bool mIsBitmap;
+
+ bool mNeedsSyntheticBold;
+
+ // cache of glyph IDs (used for non-sfnt fonts only)
+ mozilla::UniquePtr<nsTHashMap<nsUint32HashKey, uint32_t> > mGlyphIDs;
+ SCRIPT_CACHE mScriptCache;
+
+ // cache of glyph widths in 16.16 fixed-point pixels
+ mozilla::UniquePtr<nsTHashMap<nsUint32HashKey, int32_t> > mGlyphWidths;
+};
+
+#endif /* GFX_GDIFONT_H */
diff --git a/gfx/thebes/gfxGDIFontList.cpp b/gfx/thebes/gfxGDIFontList.cpp
new file mode 100644
index 0000000000..3c4c7bce07
--- /dev/null
+++ b/gfx/thebes/gfxGDIFontList.cpp
@@ -0,0 +1,1107 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+#include <algorithm>
+
+#include "mozilla/Logging.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/Sprintf.h"
+
+#include "gfxGDIFontList.h"
+#include "gfxWindowsPlatform.h"
+#include "gfxUserFontSet.h"
+#include "gfxFontUtils.h"
+#include "gfxGDIFont.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsTArray.h"
+#include "nsUnicharUtils.h"
+
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsPresContext.h"
+#include "gfxFontConstants.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/Telemetry.h"
+
+#include <usp10.h>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+#define ROUND(x) floor((x) + 0.5)
+
+#define LOG_FONTLIST(args) \
+ MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug, args)
+#define LOG_FONTLIST_ENABLED() \
+ MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug)
+
+#define LOG_CMAPDATA_ENABLED() \
+ MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_cmapdata), LogLevel::Debug)
+
+static __inline void BuildKeyNameFromFontName(nsAString& aName) {
+ if (aName.Length() >= LF_FACESIZE) aName.Truncate(LF_FACESIZE - 1);
+ ToLowerCase(aName);
+}
+
+// Implementation of gfxPlatformFontList for Win32 GDI,
+// using GDI font enumeration APIs to get the list of fonts
+
+class WinUserFontData : public gfxUserFontData {
+ public:
+ explicit WinUserFontData(HANDLE aFontRef) : mFontRef(aFontRef) {}
+
+ virtual ~WinUserFontData() {
+ DebugOnly<BOOL> success;
+ success = RemoveFontMemResourceEx(mFontRef);
+#if DEBUG
+ if (!success) {
+ char buf[256];
+ SprintfLiteral(
+ buf,
+ "error deleting font handle (%p) - RemoveFontMemResourceEx failed",
+ mFontRef);
+ NS_ASSERTION(success, buf);
+ }
+#endif
+ }
+
+ HANDLE mFontRef;
+};
+
+BYTE FontTypeToOutPrecision(uint8_t fontType) {
+ BYTE ret;
+ switch (fontType) {
+ case GFX_FONT_TYPE_TT_OPENTYPE:
+ case GFX_FONT_TYPE_TRUETYPE:
+ ret = OUT_TT_ONLY_PRECIS;
+ break;
+ case GFX_FONT_TYPE_PS_OPENTYPE:
+ ret = OUT_PS_ONLY_PRECIS;
+ break;
+ case GFX_FONT_TYPE_TYPE1:
+ ret = OUT_OUTLINE_PRECIS;
+ break;
+ case GFX_FONT_TYPE_RASTER:
+ ret = OUT_RASTER_PRECIS;
+ break;
+ case GFX_FONT_TYPE_DEVICE:
+ ret = OUT_DEVICE_PRECIS;
+ break;
+ default:
+ ret = OUT_DEFAULT_PRECIS;
+ }
+ return ret;
+}
+
+/***************************************************************
+ *
+ * GDIFontEntry
+ *
+ */
+
+GDIFontEntry::GDIFontEntry(const nsACString& aFaceName,
+ gfxWindowsFontType aFontType, SlantStyleRange aStyle,
+ WeightRange aWeight, StretchRange aStretch,
+ gfxUserFontData* aUserFontData)
+ : gfxFontEntry(aFaceName), mFontType(aFontType), mForceGDI(false) {
+ mUserFontData.reset(aUserFontData);
+ mStyleRange = aStyle;
+ mWeightRange = aWeight;
+ mStretchRange = aStretch;
+ if (IsType1()) {
+ mForceGDI = true;
+ }
+ mIsDataUserFont = aUserFontData != nullptr;
+
+ InitLogFont(aFaceName, aFontType);
+}
+
+gfxFontEntry* GDIFontEntry::Clone() const {
+ MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!");
+ return new GDIFontEntry(Name(), mFontType, SlantStyle(), Weight(), Stretch(),
+ nullptr);
+}
+
+nsresult GDIFontEntry::ReadCMAP(FontInfoData* aFontInfoData) {
+ AUTO_PROFILER_LABEL("GDIFontEntry::ReadCMAP", OTHER);
+
+ // attempt this once, if errors occur leave a blank cmap
+ if (mCharacterMap) {
+ return NS_OK;
+ }
+
+ // skip non-SFNT fonts completely
+ if (mFontType != GFX_FONT_TYPE_PS_OPENTYPE &&
+ mFontType != GFX_FONT_TYPE_TT_OPENTYPE &&
+ mFontType != GFX_FONT_TYPE_TRUETYPE) {
+ RefPtr<gfxCharacterMap> cmap = new gfxCharacterMap();
+ cmap->mBuildOnTheFly = true;
+ if (mCharacterMap.compareExchange(nullptr, cmap.get())) {
+ Unused << cmap.forget();
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<gfxCharacterMap> charmap;
+ nsresult rv;
+
+ uint32_t uvsOffset = 0;
+ if (aFontInfoData &&
+ (charmap = GetCMAPFromFontInfo(aFontInfoData, uvsOffset))) {
+ rv = NS_OK;
+ } else {
+ uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p');
+ charmap = new gfxCharacterMap();
+ AutoTArray<uint8_t, 16384> cmap;
+ rv = CopyFontTable(kCMAP, cmap);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = gfxFontUtils::ReadCMAP(cmap.Elements(), cmap.Length(), *charmap,
+ uvsOffset);
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
+ charmap = pfl->FindCharMap(charmap);
+ mHasCmapTable = true;
+ } else {
+ // if error occurred, initialize to null cmap
+ charmap = new gfxCharacterMap();
+ // For fonts where we failed to read the character map,
+ // we can take a slow path to look up glyphs character by character
+ charmap->mBuildOnTheFly = true;
+ }
+ if (mCharacterMap.compareExchange(nullptr, charmap.get())) {
+ charmap.get()->AddRef();
+ }
+
+ LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %zd hash: %8.8x%s\n",
+ mName.get(), charmap->SizeOfIncludingThis(moz_malloc_size_of),
+ charmap->mHash, mCharacterMap == charmap ? " new" : ""));
+ if (LOG_CMAPDATA_ENABLED()) {
+ char prefix[256];
+ SprintfLiteral(prefix, "(cmapdata) name: %.220s", mName.get());
+ charmap->Dump(prefix, eGfxLog_cmapdata);
+ }
+
+ return rv;
+}
+
+gfxFont* GDIFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle) {
+ return new gfxGDIFont(this, aFontStyle);
+}
+
+nsresult GDIFontEntry::CopyFontTable(uint32_t aTableTag,
+ nsTArray<uint8_t>& aBuffer) {
+ if (!IsTrueType()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ AutoDC dc;
+ AutoSelectFont font(dc.GetDC(), &mLogFont);
+ if (font.IsValid()) {
+ uint32_t tableSize = ::GetFontData(
+ dc.GetDC(), NativeEndian::swapToBigEndian(aTableTag), 0, nullptr, 0);
+ if (tableSize != GDI_ERROR) {
+ if (aBuffer.SetLength(tableSize, fallible)) {
+ ::GetFontData(dc.GetDC(), NativeEndian::swapToBigEndian(aTableTag), 0,
+ aBuffer.Elements(), tableSize);
+ return NS_OK;
+ }
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+already_AddRefed<UnscaledFontGDI> GDIFontEntry::LookupUnscaledFont(
+ HFONT aFont) {
+ RefPtr<UnscaledFontGDI> unscaledFont(mUnscaledFont);
+ if (!unscaledFont) {
+ LOGFONT lf;
+ GetObject(aFont, sizeof(LOGFONT), &lf);
+ unscaledFont = new UnscaledFontGDI(lf);
+ mUnscaledFont = unscaledFont;
+ }
+
+ return unscaledFont.forget();
+}
+
+void GDIFontEntry::FillLogFont(LOGFONTW* aLogFont, LONG aWeight,
+ gfxFloat aSize) {
+ memcpy(aLogFont, &mLogFont, sizeof(LOGFONTW));
+
+ aLogFont->lfHeight = (LONG)-ROUND(aSize);
+
+ if (aLogFont->lfHeight == 0) {
+ aLogFont->lfHeight = -1;
+ }
+
+ // If a non-zero weight is passed in, use this to override the original
+ // weight in the entry's logfont. This is used to control synthetic bolding
+ // for installed families with no bold face, and for downloaded fonts
+ // (but NOT for local user fonts, because it could cause a different,
+ // glyph-incompatible face to be used)
+ if (aWeight != 0) {
+ aLogFont->lfWeight = aWeight;
+ }
+
+ // for non-local() user fonts, we never want to apply italics here;
+ // if the face is described as italic, we should use it as-is,
+ // and if it's not, but then the element is styled italic, we'll use
+ // a cairo transform to create fake italic (oblique)
+ if (mIsDataUserFont) {
+ aLogFont->lfItalic = 0;
+ }
+}
+
+#define MISSING_GLYPH \
+ 0x1F // glyph index returned for missing characters
+ // on WinXP with .fon fonts, but not Type1 (.pfb)
+
+bool GDIFontEntry::TestCharacterMap(uint32_t aCh) {
+ if (!mCharacterMap) {
+ ReadCMAP();
+ NS_ASSERTION(mCharacterMap, "failed to initialize a character map");
+ }
+
+ if (GetCharacterMap()->mBuildOnTheFly) {
+ if (aCh > 0xFFFF) return false;
+
+ // previous code was using the group style
+ gfxFontStyle fakeStyle;
+ if (!IsUpright()) {
+ fakeStyle.style = FontSlantStyle::ITALIC;
+ }
+ fakeStyle.weight = Weight().Min();
+
+ RefPtr<gfxFont> tempFont = FindOrMakeFont(&fakeStyle, nullptr);
+ if (!tempFont || !tempFont->Valid()) return false;
+ gfxGDIFont* font = static_cast<gfxGDIFont*>(tempFont.get());
+
+ HDC dc = GetDC((HWND) nullptr);
+ SetGraphicsMode(dc, GM_ADVANCED);
+ HFONT hfont = font->GetHFONT();
+ HFONT oldFont = (HFONT)SelectObject(dc, hfont);
+
+ wchar_t str[1] = {(wchar_t)aCh};
+ WORD glyph[1];
+
+ bool hasGlyph = false;
+
+ // Bug 573038 - in some cases GetGlyphIndicesW returns 0xFFFF for a
+ // missing glyph or 0x1F in other cases to indicate the "invalid"
+ // glyph. Map both cases to "not found"
+ if (IsType1() || mForceGDI) {
+ // Type1 fonts and uniscribe APIs don't get along.
+ // ScriptGetCMap will return E_HANDLE
+ DWORD ret =
+ GetGlyphIndicesW(dc, str, 1, glyph, GGI_MARK_NONEXISTING_GLYPHS);
+ if (ret != GDI_ERROR && glyph[0] != 0xFFFF &&
+ (IsType1() || glyph[0] != MISSING_GLYPH)) {
+ hasGlyph = true;
+ }
+ } else {
+ // ScriptGetCMap works better than GetGlyphIndicesW
+ // for things like bitmap/vector fonts
+ SCRIPT_CACHE sc = nullptr;
+ HRESULT rv = ScriptGetCMap(dc, &sc, str, 1, 0, glyph);
+ if (rv == S_OK) hasGlyph = true;
+ }
+
+ SelectObject(dc, oldFont);
+ ReleaseDC(nullptr, dc);
+
+ if (hasGlyph) {
+ GetCharacterMap()->set(aCh);
+ return true;
+ }
+ } else {
+ // font had a cmap so simply check that
+ return GetCharacterMap()->test(aCh);
+ }
+
+ return false;
+}
+
+void GDIFontEntry::InitLogFont(const nsACString& aName,
+ gfxWindowsFontType aFontType) {
+#define CLIP_TURNOFF_FONTASSOCIATION 0x40
+
+ mLogFont.lfHeight = -1;
+
+ // Fill in logFont structure
+ mLogFont.lfWidth = 0;
+ mLogFont.lfEscapement = 0;
+ mLogFont.lfOrientation = 0;
+ mLogFont.lfUnderline = FALSE;
+ mLogFont.lfStrikeOut = FALSE;
+ mLogFont.lfCharSet = DEFAULT_CHARSET;
+ mLogFont.lfOutPrecision = FontTypeToOutPrecision(aFontType);
+ mLogFont.lfClipPrecision = CLIP_TURNOFF_FONTASSOCIATION;
+ mLogFont.lfQuality = DEFAULT_QUALITY;
+ mLogFont.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
+ // always force lfItalic if we want it. Font selection code will
+ // do its best to give us an italic font entry, but if no face exists
+ // it may give us a regular one based on weight. Windows should
+ // do fake italic for us in that case.
+ mLogFont.lfItalic = !IsUpright();
+ mLogFont.lfWeight = Weight().Min().ToIntRounded();
+
+ NS_ConvertUTF8toUTF16 name(aName);
+ int len = std::min<int>(name.Length(), LF_FACESIZE - 1);
+ memcpy(&mLogFont.lfFaceName, name.BeginReading(), len * sizeof(char16_t));
+ mLogFont.lfFaceName[len] = '\0';
+}
+
+GDIFontEntry* GDIFontEntry::CreateFontEntry(const nsACString& aName,
+ gfxWindowsFontType aFontType,
+ SlantStyleRange aStyle,
+ WeightRange aWeight,
+ StretchRange aStretch,
+ gfxUserFontData* aUserFontData) {
+ // jtdfix - need to set charset, pitch/family
+
+ return new GDIFontEntry(aName, aFontType, aStyle, aWeight, aStretch,
+ aUserFontData);
+}
+
+void GDIFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ aSizes->mFontListSize += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
+
+/***************************************************************
+ *
+ * GDIFontFamily
+ *
+ */
+
+static bool ShouldIgnoreItalicStyle(const nsACString& aName) {
+ // Ignore italic style's "Meiryo" because "Meiryo (Bold) Italic" has
+ // non-italic style glyphs as Japanese characters. However, using it
+ // causes serious problem if web pages wants some elements to be
+ // different style from others only with font-style. For example,
+ // <em> and <i> should be rendered as italic in the default style.
+ return aName.EqualsLiteral("Meiryo") ||
+ aName.EqualsLiteral(
+ "\xe3\x83\xa1\xe3\x82\xa4\xe3\x83\xaa\xe3\x82\xaa");
+}
+
+int CALLBACK GDIFontFamily::FamilyAddStylesProc(
+ const ENUMLOGFONTEXW* lpelfe, const NEWTEXTMETRICEXW* nmetrics,
+ DWORD fontType, LPARAM data) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ const NEWTEXTMETRICW& metrics = nmetrics->ntmTm;
+ LOGFONTW logFont = lpelfe->elfLogFont;
+ GDIFontFamily* ff = reinterpret_cast<GDIFontFamily*>(data);
+ MOZ_ASSERT(ff->mLock.LockedForWritingByCurrentThread());
+
+ if (logFont.lfItalic && ShouldIgnoreItalicStyle(ff->mName)) {
+ return 1;
+ }
+
+ // Some fonts claim to support things > 900, but we don't so clamp the sizes
+ logFont.lfWeight = clamped(logFont.lfWeight, LONG(100), LONG(900));
+
+ gfxWindowsFontType feType =
+ GDIFontEntry::DetermineFontType(metrics, fontType);
+
+ GDIFontEntry* fe = nullptr;
+ for (uint32_t i = 0; i < ff->mAvailableFonts.Length(); ++i) {
+ fe = static_cast<GDIFontEntry*>(ff->mAvailableFonts[i].get());
+ if (feType > fe->mFontType) {
+ // if the new type is better than the old one, remove the old entries
+ ff->mAvailableFonts.RemoveElementAt(i);
+ --i;
+ } else if (feType < fe->mFontType) {
+ // otherwise if the new type is worse, skip it
+ return 1;
+ }
+ }
+
+ for (uint32_t i = 0; i < ff->mAvailableFonts.Length(); ++i) {
+ fe = static_cast<GDIFontEntry*>(ff->mAvailableFonts[i].get());
+ // check if we already know about this face
+ if (fe->Weight().Min() == FontWeight::FromInt(int32_t(logFont.lfWeight)) &&
+ fe->IsItalic() == (logFont.lfItalic == 0xFF)) {
+ // update the charset bit here since this could be different
+ // XXX Can we still do this now that we store mCharset
+ // on the font family rather than the font entry?
+ ff->mCharset.set(metrics.tmCharSet);
+ return 1;
+ }
+ }
+
+ // We can't set the hasItalicFace flag correctly here,
+ // because we might not have seen the family's italic face(s) yet.
+ // So we'll set that flag for all members after loading all the faces.
+ auto italicStyle = (logFont.lfItalic == 0xFF ? FontSlantStyle::ITALIC
+ : FontSlantStyle::NORMAL);
+ fe = GDIFontEntry::CreateFontEntry(
+ NS_ConvertUTF16toUTF8(lpelfe->elfFullName), feType,
+ SlantStyleRange(italicStyle),
+ WeightRange(FontWeight::FromInt(int32_t(logFont.lfWeight))),
+ StretchRange(FontStretch::NORMAL), nullptr);
+ if (!fe) {
+ return 1;
+ }
+
+ ff->AddFontEntryLocked(fe);
+
+ if (LOG_FONTLIST_ENABLED()) {
+ LOG_FONTLIST(
+ ("(fontlist) added (%s) to family (%s)"
+ " with style: %s weight: %ld stretch: normal",
+ fe->Name().get(), ff->Name().get(),
+ (logFont.lfItalic == 0xff) ? "italic" : "normal", logFont.lfWeight));
+ }
+ return 1;
+}
+
+void GDIFontFamily::FindStyleVariationsLocked(FontInfoData* aFontInfoData) {
+ if (mHasStyles) {
+ return;
+ }
+ mHasStyles = true;
+
+ HDC hdc = GetDC(nullptr);
+ SetGraphicsMode(hdc, GM_ADVANCED);
+
+ LOGFONTW logFont;
+ memset(&logFont, 0, sizeof(LOGFONTW));
+ logFont.lfCharSet = DEFAULT_CHARSET;
+ logFont.lfPitchAndFamily = 0;
+ NS_ConvertUTF8toUTF16 name(mName);
+ uint32_t l = std::min<uint32_t>(name.Length(), LF_FACESIZE - 1);
+ memcpy(logFont.lfFaceName, name.get(), l * sizeof(char16_t));
+
+ EnumFontFamiliesExW(hdc, &logFont,
+ (FONTENUMPROCW)GDIFontFamily::FamilyAddStylesProc,
+ (LPARAM)this, 0);
+ if (LOG_FONTLIST_ENABLED() && mAvailableFonts.Length() == 0) {
+ LOG_FONTLIST(
+ ("(fontlist) no styles available in family \"%s\"", mName.get()));
+ }
+
+ ReleaseDC(nullptr, hdc);
+
+ if (mIsBadUnderlineFamily) {
+ SetBadUnderlineFonts();
+ }
+
+ CheckForSimpleFamily();
+}
+
+/***************************************************************
+ *
+ * gfxGDIFontList
+ *
+ */
+
+gfxGDIFontList::gfxGDIFontList() : mFontSubstitutes(32) {
+#ifdef MOZ_BUNDLED_FONTS
+ if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) {
+ TimeStamp start = TimeStamp::Now();
+ ActivateBundledFonts();
+ TimeStamp end = TimeStamp::Now();
+ Telemetry::Accumulate(Telemetry::FONTLIST_BUNDLEDFONTS_ACTIVATE,
+ (end - start).ToMilliseconds());
+ }
+#endif
+}
+
+static void RemoveCharsetFromFontSubstitute(nsAString& aName) {
+ int32_t comma = aName.FindChar(char16_t(','));
+ if (comma >= 0) aName.Truncate(comma);
+}
+
+#define MAX_VALUE_NAME 512
+#define MAX_VALUE_DATA 512
+
+nsresult gfxGDIFontList::GetFontSubstitutes() {
+ HKEY hKey;
+ DWORD i, rv, lenAlias, lenActual, valueType;
+ WCHAR aliasName[MAX_VALUE_NAME];
+ WCHAR actualName[MAX_VALUE_DATA];
+
+ if (RegOpenKeyExW(
+ HKEY_LOCAL_MACHINE,
+ L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\FontSubstitutes",
+ 0, KEY_READ, &hKey) != ERROR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ for (i = 0, rv = ERROR_SUCCESS; rv != ERROR_NO_MORE_ITEMS; i++) {
+ aliasName[0] = 0;
+ lenAlias = ArrayLength(aliasName);
+ actualName[0] = 0;
+ lenActual = sizeof(actualName);
+ rv = RegEnumValueW(hKey, i, aliasName, &lenAlias, nullptr, &valueType,
+ (LPBYTE)actualName, &lenActual);
+
+ if (rv != ERROR_SUCCESS || valueType != REG_SZ || lenAlias == 0) {
+ continue;
+ }
+
+ if (aliasName[0] == WCHAR('@')) {
+ continue;
+ }
+
+ nsAutoString substituteName((char16_t*)aliasName);
+ nsAutoString actualFontName((char16_t*)actualName);
+ RemoveCharsetFromFontSubstitute(substituteName);
+ BuildKeyNameFromFontName(substituteName);
+ RemoveCharsetFromFontSubstitute(actualFontName);
+ BuildKeyNameFromFontName(actualFontName);
+ gfxFontFamily* ff;
+ NS_ConvertUTF16toUTF8 substitute(substituteName);
+ NS_ConvertUTF16toUTF8 actual(actualFontName);
+ if (!actual.IsEmpty() && (ff = mFontFamilies.GetWeak(actual))) {
+ mFontSubstitutes.InsertOrUpdate(substitute, RefPtr{ff});
+ } else {
+ mNonExistingFonts.AppendElement(substitute);
+ }
+ }
+
+ // "Courier" on a default Windows install is an ugly bitmap font.
+ // If there is no substitution for Courier in the registry
+ // substitute "Courier" with "Courier New".
+ nsAutoString substituteName;
+ substituteName.AssignLiteral("Courier");
+ BuildKeyNameFromFontName(substituteName);
+ NS_ConvertUTF16toUTF8 substitute(substituteName);
+ if (!mFontSubstitutes.GetWeak(substitute)) {
+ gfxFontFamily* ff;
+ nsAutoString actualFontName;
+ actualFontName.AssignLiteral("Courier New");
+ BuildKeyNameFromFontName(actualFontName);
+ NS_ConvertUTF16toUTF8 actual(actualFontName);
+ ff = mFontFamilies.GetWeak(actual);
+ if (ff) {
+ mFontSubstitutes.InsertOrUpdate(substitute, RefPtr{ff});
+ }
+ }
+ return NS_OK;
+}
+
+nsresult gfxGDIFontList::InitFontListForPlatform() {
+ Telemetry::AutoTimer<Telemetry::GDI_INITFONTLIST_TOTAL> timer;
+
+ mFontSubstitutes.Clear();
+ mNonExistingFonts.Clear();
+
+ // iterate over available families
+ LOGFONTW logfont;
+ memset(&logfont, 0, sizeof(logfont));
+ logfont.lfCharSet = DEFAULT_CHARSET;
+
+ AutoDC hdc;
+ (void)EnumFontFamiliesExW(hdc.GetDC(), &logfont,
+ (FONTENUMPROCW)&EnumFontFamExProc, 0, 0);
+
+ GetFontSubstitutes();
+
+ GetPrefsAndStartLoader();
+
+ return NS_OK;
+}
+
+int CALLBACK gfxGDIFontList::EnumFontFamExProc(ENUMLOGFONTEXW* lpelfe,
+ NEWTEXTMETRICEXW* lpntme,
+ DWORD fontType, LPARAM lParam) {
+ const NEWTEXTMETRICW& metrics = lpntme->ntmTm;
+ const LOGFONTW& lf = lpelfe->elfLogFont;
+
+ if (lf.lfFaceName[0] == '@') {
+ return 1;
+ }
+
+ nsAutoString name(lf.lfFaceName);
+ BuildKeyNameFromFontName(name);
+
+ NS_ConvertUTF16toUTF8 key(name);
+
+ gfxGDIFontList* fontList = PlatformFontList();
+ fontList->mLock.AssertCurrentThreadIn();
+
+ if (!fontList->mFontFamilies.Contains(key)) {
+ NS_ConvertUTF16toUTF8 faceName(lf.lfFaceName);
+ FontVisibility visibility = FontVisibility::Unknown; // TODO
+ RefPtr<GDIFontFamily> family = new GDIFontFamily(faceName, visibility);
+ fontList->mFontFamilies.InsertOrUpdate(key, RefPtr{family});
+
+ // if locale is such that CJK font names are the default coming from
+ // GDI, then if a family name is non-ASCII immediately read in other
+ // family names. This assures that MS Gothic, MS Mincho are all found
+ // before lookups begin.
+ if (!IsAscii(faceName)) {
+ family->ReadOtherFamilyNames(gfxPlatformFontList::PlatformFontList());
+ }
+
+ if (fontList->mBadUnderlineFamilyNames.ContainsSorted(key)) {
+ family->SetBadUnderlineFamily();
+ }
+
+ family->mWindowsFamily = lf.lfPitchAndFamily & 0xF0;
+ family->mWindowsPitch = lf.lfPitchAndFamily & 0x0F;
+
+ // mark the charset bit
+ family->mCharset.set(metrics.tmCharSet);
+ }
+
+ return 1;
+}
+
+gfxFontEntry* gfxGDIFontList::LookupLocalFont(nsPresContext* aPresContext,
+ const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry) {
+ AutoLock lock(mLock);
+
+ gfxFontEntry* lookup = LookupInFaceNameLists(aFontName);
+ if (!lookup) {
+ return nullptr;
+ }
+
+ bool isCFF = false; // jtdfix -- need to determine this
+
+ // use the face name from the lookup font entry, which will be the localized
+ // face name which GDI mapping tables use (e.g. with the system locale set to
+ // Dutch, a fullname of 'Arial Bold' will find a font entry with the face name
+ // 'Arial Vet' which can be used as a key in GDI font lookups).
+ GDIFontEntry* fe = GDIFontEntry::CreateFontEntry(
+ lookup->Name(),
+ gfxWindowsFontType(isCFF ? GFX_FONT_TYPE_PS_OPENTYPE
+ : GFX_FONT_TYPE_TRUETYPE) /*type*/,
+ lookup->SlantStyle(), lookup->Weight(), aStretchForEntry, nullptr);
+
+ if (!fe) return nullptr;
+
+ fe->mIsLocalUserFont = true;
+
+ // make the new font entry match the userfont entry style characteristics
+ fe->mWeightRange = aWeightForEntry;
+ fe->mStyleRange = aStyleForEntry;
+ fe->mStretchRange = aStretchForEntry;
+
+ return fe;
+}
+
+// If aFontData contains only a MS/Symbol cmap subtable, not MS/Unicode,
+// we modify the subtable header to mark it as Unicode instead, because
+// otherwise GDI will refuse to load the font.
+// NOTE that this function does not bounds-check every access to the font data.
+// This is OK because we only use it on data that has already been validated
+// by OTS, and therefore we will not hit out-of-bounds accesses here.
+static bool FixupSymbolEncodedFont(uint8_t* aFontData, uint32_t aLength) {
+ struct CmapHeader {
+ AutoSwap_PRUint16 version;
+ AutoSwap_PRUint16 numTables;
+ };
+ struct CmapEncodingRecord {
+ AutoSwap_PRUint16 platformID;
+ AutoSwap_PRUint16 encodingID;
+ AutoSwap_PRUint32 offset;
+ };
+ const uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p');
+ const TableDirEntry* dir = gfxFontUtils::FindTableDirEntry(aFontData, kCMAP);
+ if (dir && uint32_t(dir->length) >= sizeof(CmapHeader)) {
+ CmapHeader* cmap =
+ reinterpret_cast<CmapHeader*>(aFontData + uint32_t(dir->offset));
+ CmapEncodingRecord* encRec =
+ reinterpret_cast<CmapEncodingRecord*>(cmap + 1);
+ int32_t symbolSubtable = -1;
+ for (uint32_t i = 0; i < (uint16_t)cmap->numTables; ++i) {
+ if (uint16_t(encRec[i].platformID) !=
+ gfxFontUtils::PLATFORM_ID_MICROSOFT) {
+ continue; // only interested in MS platform
+ }
+ if (uint16_t(encRec[i].encodingID) ==
+ gfxFontUtils::ENCODING_ID_MICROSOFT_UNICODEBMP) {
+ // We've got a Microsoft/Unicode table, so don't interfere.
+ symbolSubtable = -1;
+ break;
+ }
+ if (uint16_t(encRec[i].encodingID) ==
+ gfxFontUtils::ENCODING_ID_MICROSOFT_SYMBOL) {
+ // Found a symbol subtable; remember it for possible fixup,
+ // but if we subsequently find a Microsoft/Unicode subtable,
+ // we'll cancel this.
+ symbolSubtable = i;
+ }
+ }
+ if (symbolSubtable != -1) {
+ // We found a windows/symbol cmap table, and no windows/unicode one;
+ // change the encoding ID so that AddFontMemResourceEx will accept it
+ encRec[symbolSubtable].encodingID =
+ gfxFontUtils::ENCODING_ID_MICROSOFT_UNICODEBMP;
+ return true;
+ }
+ }
+ return false;
+}
+
+gfxFontEntry* gfxGDIFontList::MakePlatformFont(const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry,
+ const uint8_t* aFontData,
+ uint32_t aLength) {
+ // MakePlatformFont is responsible for deleting the font data with free
+ // so we set up a stack object to ensure it is freed even if we take an
+ // early exit
+ struct FontDataDeleter {
+ explicit FontDataDeleter(const uint8_t* aFontData) : mFontData(aFontData) {}
+ ~FontDataDeleter() { free((void*)mFontData); }
+ const uint8_t* mFontData;
+ };
+ FontDataDeleter autoDelete(aFontData);
+
+ bool isCFF = gfxFontUtils::IsCffFont(aFontData);
+
+ nsresult rv;
+ HANDLE fontRef = nullptr;
+
+ nsAutoString uniqueName;
+ rv = gfxFontUtils::MakeUniqueUserFontName(uniqueName);
+ if (NS_FAILED(rv)) return nullptr;
+
+ FallibleTArray<uint8_t> newFontData;
+
+ rv = gfxFontUtils::RenameFont(uniqueName, aFontData, aLength, &newFontData);
+
+ if (NS_FAILED(rv)) return nullptr;
+
+ DWORD numFonts = 0;
+
+ uint8_t* fontData = reinterpret_cast<uint8_t*>(newFontData.Elements());
+ uint32_t fontLength = newFontData.Length();
+ NS_ASSERTION(fontData, "null font data after renaming");
+
+ // http://msdn.microsoft.com/en-us/library/ms533942(VS.85).aspx
+ // "A font that is added by AddFontMemResourceEx is always private
+ // to the process that made the call and is not enumerable."
+ fontRef =
+ AddFontMemResourceEx(fontData, fontLength, 0 /* reserved */, &numFonts);
+ if (!fontRef) {
+ if (FixupSymbolEncodedFont(fontData, fontLength)) {
+ fontRef = AddFontMemResourceEx(fontData, fontLength, 0, &numFonts);
+ }
+ }
+ if (!fontRef) {
+ return nullptr;
+ }
+
+ // only load fonts with a single face contained in the data
+ // AddFontMemResourceEx generates an additional face name for
+ // vertical text if the font supports vertical writing but since
+ // the font is referenced via the name this can be ignored
+ if (fontRef && numFonts > 2) {
+ RemoveFontMemResourceEx(fontRef);
+ return nullptr;
+ }
+
+ // make a new font entry using the unique name
+ WinUserFontData* winUserFontData = new WinUserFontData(fontRef);
+ GDIFontEntry* fe = GDIFontEntry::CreateFontEntry(
+ NS_ConvertUTF16toUTF8(uniqueName),
+ gfxWindowsFontType(isCFF ? GFX_FONT_TYPE_PS_OPENTYPE
+ : GFX_FONT_TYPE_TRUETYPE) /*type*/,
+ aStyleForEntry, aWeightForEntry, aStretchForEntry, winUserFontData);
+
+ if (fe) {
+ fe->mIsDataUserFont = true;
+ }
+
+ return fe;
+}
+
+bool gfxGDIFontList::FindAndAddFamiliesLocked(
+ nsPresContext* aPresContext, StyleGenericFontFamily aGeneric,
+ const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput,
+ FindFamiliesFlags aFlags, gfxFontStyle* aStyle, nsAtom* aLanguage,
+ gfxFloat aDevToCssSize) {
+ NS_ConvertUTF8toUTF16 key16(aFamily);
+ BuildKeyNameFromFontName(key16);
+ NS_ConvertUTF16toUTF8 keyName(key16);
+
+ gfxFontFamily* ff = mFontSubstitutes.GetWeak(keyName);
+ FontVisibility level =
+ aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User;
+ if (ff && IsVisibleToCSS(*ff, level)) {
+ aOutput->AppendElement(FamilyAndGeneric(ff, aGeneric));
+ return true;
+ }
+
+ if (mNonExistingFonts.Contains(keyName)) {
+ return false;
+ }
+
+ return gfxPlatformFontList::FindAndAddFamiliesLocked(
+ aPresContext, aGeneric, aFamily, aOutput, aFlags, aStyle, aLanguage,
+ aDevToCssSize);
+}
+
+FontFamily gfxGDIFontList::GetDefaultFontForPlatform(
+ nsPresContext* aPresContext, const gfxFontStyle* aStyle,
+ nsAtom* aLanguage) {
+ FontFamily ff;
+
+ // this really shouldn't fail to find a font....
+ NONCLIENTMETRICSW ncm;
+ ncm.cbSize = sizeof(ncm);
+ BOOL status =
+ ::SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0);
+ if (status) {
+ ff = FindFamily(aPresContext,
+ NS_ConvertUTF16toUTF8(ncm.lfMessageFont.lfFaceName));
+ if (!ff.IsNull()) {
+ return ff;
+ }
+ }
+
+ // ...but just in case, try another (long-deprecated) approach as well
+ HGDIOBJ hGDI = ::GetStockObject(DEFAULT_GUI_FONT);
+ LOGFONTW logFont;
+ if (hGDI && ::GetObjectW(hGDI, sizeof(logFont), &logFont)) {
+ ff = FindFamily(aPresContext, NS_ConvertUTF16toUTF8(logFont.lfFaceName));
+ }
+
+ return ff;
+}
+
+void gfxGDIFontList::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ gfxPlatformFontList::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+
+ AutoLock lock(mLock);
+
+ aSizes->mFontListSize +=
+ SizeOfFontFamilyTableExcludingThis(mFontSubstitutes, aMallocSizeOf);
+ aSizes->mFontListSize +=
+ mNonExistingFonts.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (uint32_t i = 0; i < mNonExistingFonts.Length(); ++i) {
+ aSizes->mFontListSize +=
+ mNonExistingFonts[i].SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ }
+}
+
+void gfxGDIFontList::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ aSizes->mFontListSize += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
+
+// used to load system-wide font info on off-main thread
+class GDIFontInfo : public FontInfoData {
+ public:
+ GDIFontInfo(bool aLoadOtherNames, bool aLoadFaceNames, bool aLoadCmaps)
+ : FontInfoData(aLoadOtherNames, aLoadFaceNames, aLoadCmaps) {}
+
+ virtual ~GDIFontInfo() = default;
+
+ virtual void Load() {
+ mHdc = GetDC(nullptr);
+ SetGraphicsMode(mHdc, GM_ADVANCED);
+ FontInfoData::Load();
+ ReleaseDC(nullptr, mHdc);
+ }
+
+ // loads font data for all members of a given family
+ virtual void LoadFontFamilyData(const nsACString& aFamilyName);
+
+ // callback for GDI EnumFontFamiliesExW call
+ static int CALLBACK EnumerateFontsForFamily(const ENUMLOGFONTEXW* lpelfe,
+ const NEWTEXTMETRICEXW* nmetrics,
+ DWORD fontType, LPARAM data);
+
+ HDC mHdc;
+};
+
+struct EnumerateFontsForFamilyData {
+ EnumerateFontsForFamilyData(const nsACString& aFamilyName,
+ GDIFontInfo& aFontInfo)
+ : mFamilyName(aFamilyName), mFontInfo(aFontInfo) {}
+
+ nsCString mFamilyName;
+ nsTArray<nsCString> mOtherFamilyNames;
+ GDIFontInfo& mFontInfo;
+ nsCString mPreviousFontName;
+};
+
+int CALLBACK GDIFontInfo::EnumerateFontsForFamily(
+ const ENUMLOGFONTEXW* lpelfe, const NEWTEXTMETRICEXW* nmetrics,
+ DWORD fontType, LPARAM data) {
+ EnumerateFontsForFamilyData* famData =
+ reinterpret_cast<EnumerateFontsForFamilyData*>(data);
+ HDC hdc = famData->mFontInfo.mHdc;
+ LOGFONTW logFont = lpelfe->elfLogFont;
+ const NEWTEXTMETRICW& metrics = nmetrics->ntmTm;
+
+ AutoSelectFont font(hdc, &logFont);
+ if (!font.IsValid()) {
+ return 1;
+ }
+
+ FontFaceData fontData;
+ NS_ConvertUTF16toUTF8 fontName(lpelfe->elfFullName);
+
+ // callback called for each style-charset so return if style already seen
+ if (fontName.Equals(famData->mPreviousFontName)) {
+ return 1;
+ }
+ famData->mPreviousFontName = fontName;
+ famData->mFontInfo.mLoadStats.fonts++;
+
+ // read name table info
+ bool nameDataLoaded = false;
+ if (famData->mFontInfo.mLoadFaceNames || famData->mFontInfo.mLoadOtherNames) {
+ uint32_t kNAME =
+ NativeEndian::swapToBigEndian(TRUETYPE_TAG('n', 'a', 'm', 'e'));
+ uint32_t nameSize;
+ AutoTArray<uint8_t, 1024> nameData;
+
+ nameSize = ::GetFontData(hdc, kNAME, 0, nullptr, 0);
+ if (nameSize != GDI_ERROR && nameSize > 0 &&
+ nameData.SetLength(nameSize, fallible)) {
+ ::GetFontData(hdc, kNAME, 0, nameData.Elements(), nameSize);
+
+ // face names
+ if (famData->mFontInfo.mLoadFaceNames) {
+ gfxFontUtils::ReadCanonicalName((const char*)(nameData.Elements()),
+ nameSize, gfxFontUtils::NAME_ID_FULL,
+ fontData.mFullName);
+ gfxFontUtils::ReadCanonicalName(
+ (const char*)(nameData.Elements()), nameSize,
+ gfxFontUtils::NAME_ID_POSTSCRIPT, fontData.mPostscriptName);
+ nameDataLoaded = true;
+ famData->mFontInfo.mLoadStats.facenames++;
+ }
+
+ // other family names
+ if (famData->mFontInfo.mLoadOtherNames) {
+ gfxFontUtils::ReadOtherFamilyNamesForFace(
+ famData->mFamilyName, (const char*)(nameData.Elements()), nameSize,
+ famData->mOtherFamilyNames, false);
+ }
+ }
+ }
+
+ // read cmap
+ bool cmapLoaded = false;
+ gfxWindowsFontType feType =
+ GDIFontEntry::DetermineFontType(metrics, fontType);
+ if (famData->mFontInfo.mLoadCmaps && (feType == GFX_FONT_TYPE_PS_OPENTYPE ||
+ feType == GFX_FONT_TYPE_TT_OPENTYPE ||
+ feType == GFX_FONT_TYPE_TRUETYPE)) {
+ uint32_t kCMAP =
+ NativeEndian::swapToBigEndian(TRUETYPE_TAG('c', 'm', 'a', 'p'));
+ uint32_t cmapSize;
+ AutoTArray<uint8_t, 1024> cmapData;
+
+ cmapSize = ::GetFontData(hdc, kCMAP, 0, nullptr, 0);
+ if (cmapSize != GDI_ERROR && cmapSize > 0 &&
+ cmapData.SetLength(cmapSize, fallible)) {
+ ::GetFontData(hdc, kCMAP, 0, cmapData.Elements(), cmapSize);
+ RefPtr<gfxCharacterMap> charmap = new gfxCharacterMap();
+ uint32_t offset;
+
+ if (NS_SUCCEEDED(gfxFontUtils::ReadCMAP(cmapData.Elements(), cmapSize,
+ *charmap, offset))) {
+ fontData.mCharacterMap = charmap;
+ fontData.mUVSOffset = offset;
+ cmapLoaded = true;
+ famData->mFontInfo.mLoadStats.cmaps++;
+ }
+ }
+ }
+
+ if (cmapLoaded || nameDataLoaded) {
+ famData->mFontInfo.mFontFaceData.InsertOrUpdate(fontName, fontData);
+ }
+
+ return famData->mFontInfo.mCanceled ? 0 : 1;
+}
+
+void GDIFontInfo::LoadFontFamilyData(const nsACString& aFamilyName) {
+ // iterate over the family
+ LOGFONTW logFont;
+ memset(&logFont, 0, sizeof(LOGFONTW));
+ logFont.lfCharSet = DEFAULT_CHARSET;
+ logFont.lfPitchAndFamily = 0;
+ NS_ConvertUTF8toUTF16 name(aFamilyName);
+ uint32_t l = std::min<uint32_t>(name.Length(), LF_FACESIZE - 1);
+ memcpy(logFont.lfFaceName, name.BeginReading(), l * sizeof(char16_t));
+
+ EnumerateFontsForFamilyData data(aFamilyName, *this);
+
+ EnumFontFamiliesExW(mHdc, &logFont,
+ (FONTENUMPROCW)GDIFontInfo::EnumerateFontsForFamily,
+ (LPARAM)(&data), 0);
+
+ // if found other names, insert them
+ if (data.mOtherFamilyNames.Length() != 0) {
+ mOtherFamilyNames.InsertOrUpdate(aFamilyName, data.mOtherFamilyNames);
+ mLoadStats.othernames += data.mOtherFamilyNames.Length();
+ }
+}
+
+already_AddRefed<FontInfoData> gfxGDIFontList::CreateFontInfoData() {
+ bool loadCmaps = !UsesSystemFallback() ||
+ gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback();
+
+ RefPtr<GDIFontInfo> fi =
+ new GDIFontInfo(true, NeedFullnamePostscriptNames(), loadCmaps);
+
+ return fi.forget();
+}
+
+gfxFontFamily* gfxGDIFontList::CreateFontFamily(
+ const nsACString& aName, FontVisibility aVisibility) const {
+ return new GDIFontFamily(aName, aVisibility);
+}
+
+#ifdef MOZ_BUNDLED_FONTS
+
+void gfxGDIFontList::ActivateBundledFonts() {
+ nsCOMPtr<nsIFile> localDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ if (NS_FAILED(localDir->Append(u"fonts"_ns))) {
+ return;
+ }
+ bool isDir;
+ if (NS_FAILED(localDir->IsDirectory(&isDir)) || !isDir) {
+ return;
+ }
+
+ nsCOMPtr<nsIDirectoryEnumerator> e;
+ rv = localDir->GetDirectoryEntries(getter_AddRefs(e));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<nsIFile> file;
+ while (NS_SUCCEEDED(e->GetNextFile(getter_AddRefs(file))) && file) {
+ nsAutoString path;
+ if (NS_FAILED(file->GetPath(path))) {
+ continue;
+ }
+ AddFontResourceExW(path.get(), FR_PRIVATE, nullptr);
+ }
+}
+
+#endif
diff --git a/gfx/thebes/gfxGDIFontList.h b/gfx/thebes/gfxGDIFontList.h
new file mode 100644
index 0000000000..708f7fc114
--- /dev/null
+++ b/gfx/thebes/gfxGDIFontList.h
@@ -0,0 +1,356 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_GDIFONTLIST_H
+#define GFX_GDIFONTLIST_H
+
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/MemoryReporting.h"
+#include "gfxWindowsPlatform.h"
+#include "gfxPlatformFontList.h"
+#include "nsGkAtoms.h"
+#include "mozilla/gfx/UnscaledFontGDI.h"
+
+#include <windows.h>
+
+class AutoDC // get the global device context, and auto-release it on
+ // destruction
+{
+ public:
+ AutoDC() { mDC = ::GetDC(nullptr); }
+
+ ~AutoDC() { ::ReleaseDC(nullptr, mDC); }
+
+ HDC GetDC() { return mDC; }
+
+ private:
+ HDC mDC;
+};
+
+class AutoSelectFont // select a font into the given DC, and auto-restore
+{
+ public:
+ AutoSelectFont(HDC aDC, LOGFONTW* aLogFont) : mOwnsFont(false) {
+ mFont = ::CreateFontIndirectW(aLogFont);
+ if (mFont) {
+ mOwnsFont = true;
+ mDC = aDC;
+ mOldFont = (HFONT)::SelectObject(aDC, mFont);
+ } else {
+ mOldFont = nullptr;
+ }
+ }
+
+ AutoSelectFont(HDC aDC, HFONT aFont) : mOwnsFont(false) {
+ mDC = aDC;
+ mFont = aFont;
+ mOldFont = (HFONT)::SelectObject(aDC, aFont);
+ }
+
+ ~AutoSelectFont() {
+ if (mOldFont) {
+ ::SelectObject(mDC, mOldFont);
+ if (mOwnsFont) {
+ ::DeleteObject(mFont);
+ }
+ }
+ }
+
+ bool IsValid() const { return mFont != nullptr; }
+
+ HFONT GetFont() const { return mFont; }
+
+ private:
+ HDC mDC;
+ HFONT mFont;
+ HFONT mOldFont;
+ bool mOwnsFont;
+};
+
+/**
+ * List of different types of fonts we support on Windows.
+ * These can generally be lumped in to 3 categories where we have to
+ * do special things: Really old fonts bitmap and vector fonts (device
+ * and raster), Type 1 fonts, and TrueType/OpenType fonts.
+ *
+ * This list is sorted in order from least prefered to most prefered.
+ * We prefer Type1 fonts over OpenType fonts to avoid falling back to
+ * things like Arial (opentype) when you ask for Helvetica (type1)
+ **/
+enum gfxWindowsFontType {
+ GFX_FONT_TYPE_UNKNOWN = 0,
+ GFX_FONT_TYPE_DEVICE,
+ GFX_FONT_TYPE_RASTER,
+ GFX_FONT_TYPE_TRUETYPE,
+ GFX_FONT_TYPE_PS_OPENTYPE,
+ GFX_FONT_TYPE_TT_OPENTYPE,
+ GFX_FONT_TYPE_TYPE1
+};
+
+// A single member of a font family (i.e. a single face, such as Times Italic)
+// represented as a LOGFONT that will resolve to the correct face.
+// This replaces FontEntry from gfxWindowsFonts.h/cpp.
+class GDIFontEntry final : public gfxFontEntry {
+ public:
+ LPLOGFONTW GetLogFont() { return &mLogFont; }
+
+ nsresult ReadCMAP(FontInfoData* aFontInfoData = nullptr) override;
+
+ void FillLogFont(LOGFONTW* aLogFont, LONG aWeight, gfxFloat aSize);
+
+ static gfxWindowsFontType DetermineFontType(const NEWTEXTMETRICW& metrics,
+ DWORD fontType) {
+ gfxWindowsFontType feType;
+ if (metrics.ntmFlags & NTM_TYPE1)
+ feType = GFX_FONT_TYPE_TYPE1;
+ else if (metrics.ntmFlags & NTM_PS_OPENTYPE)
+ feType = GFX_FONT_TYPE_PS_OPENTYPE;
+ else if (metrics.ntmFlags & NTM_TT_OPENTYPE)
+ feType = GFX_FONT_TYPE_TT_OPENTYPE;
+ else if (fontType == TRUETYPE_FONTTYPE)
+ feType = GFX_FONT_TYPE_TRUETYPE;
+ else if (fontType == RASTER_FONTTYPE)
+ feType = GFX_FONT_TYPE_RASTER;
+ else if (fontType == DEVICE_FONTTYPE)
+ feType = GFX_FONT_TYPE_DEVICE;
+ else
+ feType = GFX_FONT_TYPE_UNKNOWN;
+
+ return feType;
+ }
+
+ bool IsType1() const { return (mFontType == GFX_FONT_TYPE_TYPE1); }
+
+ bool IsTrueType() const {
+ return (mFontType == GFX_FONT_TYPE_TRUETYPE ||
+ mFontType == GFX_FONT_TYPE_PS_OPENTYPE ||
+ mFontType == GFX_FONT_TYPE_TT_OPENTYPE);
+ }
+
+ bool SkipDuringSystemFallback() override {
+ return !HasCmapTable(); // explicitly skip non-SFNT fonts
+ }
+
+ bool TestCharacterMap(uint32_t aCh) override;
+
+ void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const override;
+
+ gfxFontEntry* Clone() const override;
+
+ // GDI backend doesn't support font variations:
+ bool HasVariations() override { return false; }
+ void GetVariationAxes(nsTArray<gfxFontVariationAxis>&) override {}
+ void GetVariationInstances(nsTArray<gfxFontVariationInstance>&) override {}
+
+ // create a font entry for a font with a given name
+ static GDIFontEntry* CreateFontEntry(const nsACString& aName,
+ gfxWindowsFontType aFontType,
+ SlantStyleRange aStyle,
+ WeightRange aWeight,
+ StretchRange aStretch,
+ gfxUserFontData* aUserFontData);
+
+ gfxWindowsFontType mFontType;
+ bool mForceGDI;
+
+ protected:
+ friend class gfxGDIFont;
+
+ GDIFontEntry(const nsACString& aFaceName, gfxWindowsFontType aFontType,
+ SlantStyleRange aStyle, WeightRange aWeight,
+ StretchRange aStretch, gfxUserFontData* aUserFontData);
+
+ void InitLogFont(const nsACString& aName, gfxWindowsFontType aFontType);
+
+ gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle) override;
+
+ virtual nsresult CopyFontTable(uint32_t aTableTag,
+ nsTArray<uint8_t>& aBuffer) override;
+
+ already_AddRefed<mozilla::gfx::UnscaledFontGDI> LookupUnscaledFont(
+ HFONT aFont);
+
+ LOGFONTW mLogFont;
+
+ mozilla::ThreadSafeWeakPtr<mozilla::gfx::UnscaledFontGDI> mUnscaledFont;
+};
+
+// a single font family, referencing one or more faces
+class GDIFontFamily final : public gfxFontFamily {
+ public:
+ GDIFontFamily(const nsACString& aName, FontVisibility aVisibility)
+ : gfxFontFamily(aName, aVisibility),
+ mWindowsFamily(0),
+ mWindowsPitch(0),
+ mCharset() {}
+
+ void FindStyleVariationsLocked(FontInfoData* aFontInfoData = nullptr)
+ MOZ_REQUIRES(mLock) override;
+
+ bool FilterForFontList(nsAtom* aLangGroup,
+ const nsACString& aGeneric) const final {
+ return !IsSymbolFontFamily() && SupportsLangGroup(aLangGroup) &&
+ MatchesGenericFamily(aGeneric);
+ }
+
+ protected:
+ friend class gfxGDIFontList;
+
+ // helpers for FilterForFontList
+ bool IsSymbolFontFamily() const { return mCharset.test(SYMBOL_CHARSET); }
+
+ bool MatchesGenericFamily(const nsACString& aGeneric) const {
+ if (aGeneric.IsEmpty()) {
+ return true;
+ }
+
+ // Japanese 'Mincho' fonts do not belong to FF_MODERN even if
+ // they are fixed pitch because they have variable stroke width.
+ if (mWindowsFamily == FF_ROMAN && mWindowsPitch & FIXED_PITCH) {
+ return aGeneric.EqualsLiteral("monospace");
+ }
+
+ // Japanese 'Gothic' fonts do not belong to FF_SWISS even if
+ // they are variable pitch because they have constant stroke width.
+ if (mWindowsFamily == FF_MODERN && mWindowsPitch & VARIABLE_PITCH) {
+ return aGeneric.EqualsLiteral("sans-serif");
+ }
+
+ // All other fonts will be grouped correctly using family...
+ switch (mWindowsFamily) {
+ case FF_DONTCARE:
+ return false;
+ case FF_ROMAN:
+ return aGeneric.EqualsLiteral("serif");
+ case FF_SWISS:
+ return aGeneric.EqualsLiteral("sans-serif");
+ case FF_MODERN:
+ return aGeneric.EqualsLiteral("monospace");
+ case FF_SCRIPT:
+ return aGeneric.EqualsLiteral("cursive");
+ case FF_DECORATIVE:
+ return aGeneric.EqualsLiteral("fantasy");
+ }
+
+ return false;
+ }
+
+ bool SupportsLangGroup(nsAtom* aLangGroup) const {
+ if (!aLangGroup || aLangGroup == nsGkAtoms::Unicode) {
+ return true;
+ }
+
+ int16_t bit = -1;
+
+ /* map our langgroup names in to Windows charset bits */
+ if (aLangGroup == nsGkAtoms::x_western) {
+ bit = ANSI_CHARSET;
+ } else if (aLangGroup == nsGkAtoms::Japanese) {
+ bit = SHIFTJIS_CHARSET;
+ } else if (aLangGroup == nsGkAtoms::ko) {
+ bit = HANGEUL_CHARSET;
+ } else if (aLangGroup == nsGkAtoms::zh_cn) {
+ bit = GB2312_CHARSET;
+ } else if (aLangGroup == nsGkAtoms::zh_tw) {
+ bit = CHINESEBIG5_CHARSET;
+ } else if (aLangGroup == nsGkAtoms::el) {
+ bit = GREEK_CHARSET;
+ } else if (aLangGroup == nsGkAtoms::he) {
+ bit = HEBREW_CHARSET;
+ } else if (aLangGroup == nsGkAtoms::ar) {
+ bit = ARABIC_CHARSET;
+ } else if (aLangGroup == nsGkAtoms::x_cyrillic) {
+ bit = RUSSIAN_CHARSET;
+ } else if (aLangGroup == nsGkAtoms::th) {
+ bit = THAI_CHARSET;
+ }
+
+ if (bit != -1) {
+ return mCharset.test(bit);
+ }
+
+ return false;
+ }
+
+ uint8_t mWindowsFamily;
+ uint8_t mWindowsPitch;
+
+ gfxSparseBitSet mCharset;
+
+ private:
+ static int CALLBACK FamilyAddStylesProc(const ENUMLOGFONTEXW* lpelfe,
+ const NEWTEXTMETRICEXW* nmetrics,
+ DWORD fontType, LPARAM data);
+};
+
+class gfxGDIFontList final : public gfxPlatformFontList {
+ public:
+ static gfxGDIFontList* PlatformFontList() {
+ return static_cast<gfxGDIFontList*>(
+ gfxPlatformFontList::PlatformFontList());
+ }
+
+ virtual ~gfxGDIFontList() { AutoLock lock(mLock); }
+
+ // initialize font lists
+ nsresult InitFontListForPlatform() MOZ_REQUIRES(mLock) override;
+
+ gfxFontFamily* CreateFontFamily(const nsACString& aName,
+ FontVisibility aVisibility) const override;
+
+ bool FindAndAddFamiliesLocked(
+ nsPresContext* aPresContext, mozilla::StyleGenericFontFamily aGeneric,
+ const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput,
+ FindFamiliesFlags aFlags, gfxFontStyle* aStyle = nullptr,
+ nsAtom* aLanguage = nullptr, gfxFloat aDevToCssSize = 1.0)
+ MOZ_REQUIRES(mLock) override;
+
+ gfxFontEntry* LookupLocalFont(nsPresContext* aPresContext,
+ const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry) override;
+
+ gfxFontEntry* MakePlatformFont(const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry,
+ const uint8_t* aFontData,
+ uint32_t aLength) override;
+
+ void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const override;
+ void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const override;
+
+ protected:
+ FontFamily GetDefaultFontForPlatform(nsPresContext* aPresContext,
+ const gfxFontStyle* aStyle,
+ nsAtom* aLanguage = nullptr)
+ MOZ_REQUIRES(mLock) override;
+
+ private:
+ friend class gfxWindowsPlatform;
+
+ gfxGDIFontList();
+
+ nsresult GetFontSubstitutes() MOZ_REQUIRES(mLock);
+
+ static int CALLBACK EnumFontFamExProc(ENUMLOGFONTEXW* lpelfe,
+ NEWTEXTMETRICEXW* lpntme,
+ DWORD fontType, LPARAM lParam);
+
+ already_AddRefed<FontInfoData> CreateFontInfoData() override;
+
+#ifdef MOZ_BUNDLED_FONTS
+ void ActivateBundledFonts();
+#endif
+
+ FontFamilyTable mFontSubstitutes;
+ nsTArray<nsCString> mNonExistingFonts;
+};
+
+#endif /* GFX_GDIFONTLIST_H */
diff --git a/gfx/thebes/gfxGlyphExtents.cpp b/gfx/thebes/gfxGlyphExtents.cpp
new file mode 100644
index 0000000000..9c0792481d
--- /dev/null
+++ b/gfx/thebes/gfxGlyphExtents.cpp
@@ -0,0 +1,150 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxGlyphExtents.h"
+#include "gfxTextRun.h"
+
+using namespace mozilla;
+
+#ifdef DEBUG_roc
+# define DEBUG_TEXT_RUN_STORAGE_METRICS
+#endif
+
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+extern uint32_t gTextRunStorageHighWaterMark;
+extern uint32_t gTextRunStorage;
+extern uint32_t gFontCount;
+extern uint32_t gGlyphExtentsCount;
+extern uint32_t gGlyphExtentsWidthsTotalSize;
+extern uint32_t gGlyphExtentsSetupEagerSimple;
+extern uint32_t gGlyphExtentsSetupEagerTight;
+extern uint32_t gGlyphExtentsSetupLazyTight;
+extern uint32_t gGlyphExtentsSetupFallBackToTight;
+#endif
+
+gfxGlyphExtents::~gfxGlyphExtents() {
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+ gGlyphExtentsWidthsTotalSize +=
+ mContainedGlyphWidths.SizeOfExcludingThis(&FontCacheMallocSizeOf);
+ gGlyphExtentsCount++;
+#endif
+ MOZ_COUNT_DTOR(gfxGlyphExtents);
+}
+
+bool gfxGlyphExtents::GetTightGlyphExtentsAppUnitsLocked(
+ gfxFont* aFont, DrawTarget* aDrawTarget, uint32_t aGlyphID,
+ gfxRect* aExtents) {
+ HashEntry* entry = mTightGlyphExtents.GetEntry(aGlyphID);
+ if (!entry) {
+ // Some functions higher up in the call chain deliberately pass in a
+ // nullptr DrawTarget, e.g. GetBaselinePosition() passes nullptr to
+ // gfxTextRun::MeasureText() and that nullptr reaches here.
+ if (!aDrawTarget) {
+ NS_WARNING("Could not get glyph extents (no aDrawTarget)");
+ return false;
+ }
+
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+ ++gGlyphExtentsSetupLazyTight;
+#endif
+ // We need to temporarily release the read lock, as SetupGlyphExtents will
+ // take a write lock internally when it wants to set the new entry.
+ MOZ_PUSH_IGNORE_THREAD_SAFETY
+ mLock.ReadUnlock();
+ aFont->SetupGlyphExtents(aDrawTarget, aGlyphID, true, this);
+ mLock.ReadLock();
+ MOZ_POP_THREAD_SAFETY
+
+ entry = mTightGlyphExtents.GetEntry(aGlyphID);
+ if (!entry) {
+ NS_WARNING("Could not get glyph extents");
+ return false;
+ }
+ }
+
+ *aExtents = gfxRect(entry->x, entry->y, entry->width, entry->height);
+ return true;
+}
+
+gfxGlyphExtents::GlyphWidths::~GlyphWidths() {
+ uint32_t i, count = mBlocks.Length();
+ for (i = 0; i < count; ++i) {
+ uintptr_t bits = mBlocks[i];
+ if (bits && !(bits & 0x1)) {
+ delete[] reinterpret_cast<uint16_t*>(bits);
+ }
+ }
+}
+
+uint32_t gfxGlyphExtents::GlyphWidths::SizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ uint32_t i;
+ uint32_t size = mBlocks.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (i = 0; i < mBlocks.Length(); ++i) {
+ uintptr_t bits = mBlocks[i];
+ if (bits && !(bits & 0x1)) {
+ size += aMallocSizeOf(reinterpret_cast<void*>(bits));
+ }
+ }
+ return size;
+}
+
+void gfxGlyphExtents::GlyphWidths::Set(uint32_t aGlyphID, uint16_t aWidth) {
+ uint32_t block = aGlyphID >> BLOCK_SIZE_BITS;
+ uint32_t len = mBlocks.Length();
+ if (block >= len) {
+ uintptr_t* elems = mBlocks.AppendElements(block + 1 - len);
+ if (!elems) return;
+ memset(elems, 0, sizeof(uintptr_t) * (block + 1 - len));
+ }
+
+ uintptr_t bits = mBlocks[block];
+ uint32_t glyphOffset = aGlyphID & (BLOCK_SIZE - 1);
+ if (!bits) {
+ mBlocks[block] = MakeSingle(glyphOffset, aWidth);
+ return;
+ }
+
+ uint16_t* newBlock;
+ if (bits & 0x1) {
+ // Expand the block to a real block. We could avoid this by checking
+ // glyphOffset == GetGlyphOffset(bits), but that never happens so don't
+ // bother
+ newBlock = new uint16_t[BLOCK_SIZE];
+ if (!newBlock) return;
+ uint32_t i;
+ for (i = 0; i < BLOCK_SIZE; ++i) {
+ newBlock[i] = INVALID_WIDTH;
+ }
+ newBlock[GetGlyphOffset(bits)] = GetWidth(bits);
+ mBlocks[block] = reinterpret_cast<uintptr_t>(newBlock);
+ } else {
+ newBlock = reinterpret_cast<uint16_t*>(bits);
+ }
+ newBlock[glyphOffset] = aWidth;
+}
+
+void gfxGlyphExtents::SetTightGlyphExtents(uint32_t aGlyphID,
+ const gfxRect& aExtentsAppUnits) {
+ AutoWriteLock lock(mLock);
+ HashEntry* entry = mTightGlyphExtents.PutEntry(aGlyphID);
+ if (!entry) {
+ return;
+ }
+ entry->x = aExtentsAppUnits.X();
+ entry->y = aExtentsAppUnits.Y();
+ entry->width = aExtentsAppUnits.Width();
+ entry->height = aExtentsAppUnits.Height();
+}
+
+size_t gfxGlyphExtents::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ AutoReadLock lock(mLock);
+ return mContainedGlyphWidths.SizeOfExcludingThis(aMallocSizeOf) +
+ mTightGlyphExtents.ShallowSizeOfExcludingThis(aMallocSizeOf);
+}
+
+size_t gfxGlyphExtents::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
diff --git a/gfx/thebes/gfxGlyphExtents.h b/gfx/thebes/gfxGlyphExtents.h
new file mode 100644
index 0000000000..fb41112413
--- /dev/null
+++ b/gfx/thebes/gfxGlyphExtents.h
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_GLYPHEXTENTS_H
+#define GFX_GLYPHEXTENTS_H
+
+#include "gfxFont.h"
+#include "gfxRect.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/RWLock.h"
+
+class gfxContext;
+
+namespace mozilla {
+namespace gfx {
+class DrawTarget;
+} // namespace gfx
+} // namespace mozilla
+
+/**
+ * This stores glyph bounds information for a particular gfxFont, at
+ * a particular appunits-per-dev-pixel ratio (because the compressed glyph
+ * width array is stored in appunits).
+ *
+ * We store a hashtable from glyph IDs to float bounding rects. For the
+ * common case where the glyph has no horizontal left bearing, and no
+ * y overflow above the font ascent or below the font descent, and tight
+ * bounding boxes are not required, we avoid storing the glyph ID in the
+ * hashtable and instead consult an array of 16-bit glyph XMost values (in
+ * appunits). This array always has an entry for the font's space glyph --- the
+ * width is assumed to be zero.
+ */
+class gfxGlyphExtents {
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ public:
+ explicit gfxGlyphExtents(int32_t aAppUnitsPerDevUnit)
+ : mAppUnitsPerDevUnit(aAppUnitsPerDevUnit),
+ mLock("gfxGlyphExtents lock") {
+ MOZ_COUNT_CTOR(gfxGlyphExtents);
+ }
+ ~gfxGlyphExtents();
+
+ enum { INVALID_WIDTH = 0xFFFF };
+
+ void NotifyGlyphsChanged() {
+ mozilla::AutoWriteLock lock(mLock);
+ mTightGlyphExtents.Clear();
+ }
+
+ // returns INVALID_WIDTH => not a contained glyph
+ // Otherwise the glyph has no before-bearing or vertical bearings,
+ // and the result is its width measured from the baseline origin, in
+ // appunits.
+ uint16_t GetContainedGlyphWidthAppUnitsLocked(uint32_t aGlyphID) const
+ MOZ_REQUIRES_SHARED(mLock) {
+ return mContainedGlyphWidths.Get(aGlyphID);
+ }
+
+ bool IsGlyphKnownLocked(uint32_t aGlyphID) const MOZ_REQUIRES_SHARED(mLock) {
+ return mContainedGlyphWidths.Get(aGlyphID) != INVALID_WIDTH ||
+ mTightGlyphExtents.GetEntry(aGlyphID) != nullptr;
+ }
+
+ bool IsGlyphKnownWithTightExtentsLocked(uint32_t aGlyphID) const
+ MOZ_REQUIRES_SHARED(mLock) {
+ return mTightGlyphExtents.GetEntry(aGlyphID) != nullptr;
+ }
+
+ // Get glyph extents; a rectangle relative to the left baseline origin
+ // Returns true on success. Can fail on OOM or when aContext is null
+ // and extents were not (successfully) prefetched.
+ bool GetTightGlyphExtentsAppUnitsLocked(gfxFont* aFont,
+ DrawTarget* aDrawTarget,
+ uint32_t aGlyphID, gfxRect* aExtents)
+ MOZ_REQUIRES_SHARED(mLock);
+ bool GetTightGlyphExtentsAppUnits(gfxFont* aFont, DrawTarget* aDrawTarget,
+ uint32_t aGlyphID, gfxRect* aExtents) {
+ mozilla::AutoReadLock lock(mLock);
+ return GetTightGlyphExtentsAppUnitsLocked(aFont, aDrawTarget, aGlyphID,
+ aExtents);
+ }
+
+ void SetContainedGlyphWidthAppUnits(uint32_t aGlyphID, uint16_t aWidth) {
+ mozilla::AutoWriteLock lock(mLock);
+ mContainedGlyphWidths.Set(aGlyphID, aWidth);
+ }
+ void SetTightGlyphExtents(uint32_t aGlyphID, const gfxRect& aExtentsAppUnits);
+
+ int32_t GetAppUnitsPerDevUnit() { return mAppUnitsPerDevUnit; }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ class HashEntry : public nsUint32HashKey {
+ public:
+ // When constructing a new entry in the hashtable, we'll leave this
+ // blank. The caller of Put() will fill this in.
+ explicit HashEntry(KeyTypePointer aPtr)
+ : nsUint32HashKey(aPtr), x(0.0), y(0.0), width(0.0), height(0.0) {}
+ HashEntry(HashEntry&& aOther)
+ : nsUint32HashKey(std::move(aOther)),
+ x(aOther.x),
+ y(aOther.y),
+ width(aOther.width),
+ height(aOther.height) {}
+
+ float x, y, width, height;
+ };
+
+ enum {
+ BLOCK_SIZE_BITS = 7,
+ BLOCK_SIZE = 1 << BLOCK_SIZE_BITS
+ }; // 128-glyph blocks
+
+ class GlyphWidths {
+ public:
+ void Set(uint32_t aIndex, uint16_t aValue);
+ uint16_t Get(uint32_t aIndex) const {
+ uint32_t block = aIndex >> BLOCK_SIZE_BITS;
+ if (block >= mBlocks.Length()) return INVALID_WIDTH;
+ uintptr_t bits = mBlocks[block];
+ if (!bits) return INVALID_WIDTH;
+ uint32_t indexInBlock = aIndex & (BLOCK_SIZE - 1);
+ if (bits & 0x1) {
+ if (GetGlyphOffset(bits) != indexInBlock) return INVALID_WIDTH;
+ return GetWidth(bits);
+ }
+ uint16_t* widths = reinterpret_cast<uint16_t*>(bits);
+ return widths[indexInBlock];
+ }
+
+ uint32_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ ~GlyphWidths();
+
+ private:
+ static uint32_t GetGlyphOffset(uintptr_t aBits) {
+ NS_ASSERTION(aBits & 0x1, "This is really a pointer...");
+ return (aBits >> 1) & ((1 << BLOCK_SIZE_BITS) - 1);
+ }
+ static uint32_t GetWidth(uintptr_t aBits) {
+ NS_ASSERTION(aBits & 0x1, "This is really a pointer...");
+ return aBits >> (1 + BLOCK_SIZE_BITS);
+ }
+ static uintptr_t MakeSingle(uint32_t aGlyphOffset, uint16_t aWidth) {
+ return (aWidth << (1 + BLOCK_SIZE_BITS)) + (aGlyphOffset << 1) + 1;
+ }
+
+ nsTArray<uintptr_t> mBlocks;
+ };
+
+ GlyphWidths mContainedGlyphWidths MOZ_GUARDED_BY(mLock);
+ nsTHashtable<HashEntry> mTightGlyphExtents MOZ_GUARDED_BY(mLock);
+ const int32_t mAppUnitsPerDevUnit;
+
+ public:
+ mutable mozilla::RWLock mLock;
+
+ private:
+ // not implemented:
+ gfxGlyphExtents(const gfxGlyphExtents& aOther) = delete;
+ gfxGlyphExtents& operator=(const gfxGlyphExtents& aOther) = delete;
+};
+
+#endif
diff --git a/gfx/thebes/gfxGradientCache.cpp b/gfx/thebes/gfxGradientCache.cpp
new file mode 100644
index 0000000000..c2a1682a7d
--- /dev/null
+++ b/gfx/thebes/gfxGradientCache.cpp
@@ -0,0 +1,285 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxGradientCache.h"
+
+#include "MainThreadUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/DataMutex.h"
+#include "nsTArray.h"
+#include "PLDHashTable.h"
+#include "nsExpirationTracker.h"
+#include "nsClassHashtable.h"
+#include <time.h>
+
+namespace mozilla {
+namespace gfx {
+
+using namespace mozilla;
+
+struct GradientCacheKey : public PLDHashEntryHdr {
+ typedef const GradientCacheKey& KeyType;
+ typedef const GradientCacheKey* KeyTypePointer;
+ enum { ALLOW_MEMMOVE = true };
+ const CopyableTArray<GradientStop> mStops;
+ ExtendMode mExtend;
+ BackendType mBackendType;
+
+ GradientCacheKey(const nsTArray<GradientStop>& aStops, ExtendMode aExtend,
+ BackendType aBackendType)
+ : mStops(aStops), mExtend(aExtend), mBackendType(aBackendType) {}
+
+ explicit GradientCacheKey(const GradientCacheKey* aOther)
+ : mStops(aOther->mStops),
+ mExtend(aOther->mExtend),
+ mBackendType(aOther->mBackendType) {}
+
+ GradientCacheKey(GradientCacheKey&& aOther) = default;
+
+ union FloatUint32 {
+ float f;
+ uint32_t u;
+ };
+
+ static PLDHashNumber HashKey(const KeyTypePointer aKey) {
+ PLDHashNumber hash = 0;
+ FloatUint32 convert;
+ hash = AddToHash(hash, int(aKey->mBackendType));
+ hash = AddToHash(hash, int(aKey->mExtend));
+ for (uint32_t i = 0; i < aKey->mStops.Length(); i++) {
+ hash = AddToHash(hash, aKey->mStops[i].color.ToABGR());
+ // Use the float bits as hash, except for the cases of 0.0 and -0.0 which
+ // both map to 0
+ convert.f = aKey->mStops[i].offset;
+ hash = AddToHash(hash, convert.f ? convert.u : 0);
+ }
+ return hash;
+ }
+
+ bool KeyEquals(KeyTypePointer aKey) const {
+ bool sameStops = true;
+ if (aKey->mStops.Length() != mStops.Length()) {
+ sameStops = false;
+ } else {
+ for (uint32_t i = 0; i < mStops.Length(); i++) {
+ if (mStops[i].color.ToABGR() != aKey->mStops[i].color.ToABGR() ||
+ mStops[i].offset != aKey->mStops[i].offset) {
+ sameStops = false;
+ break;
+ }
+ }
+ }
+
+ return sameStops && (aKey->mBackendType == mBackendType) &&
+ (aKey->mExtend == mExtend);
+ }
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+};
+
+/**
+ * This class is what is cached. It need to be allocated in an object separated
+ * to the cache entry to be able to be tracked by the nsExpirationTracker.
+ * */
+struct GradientCacheData {
+ GradientCacheData(GradientStops* aStops, GradientCacheKey&& aKey)
+ : mStops(aStops), mKey(std::move(aKey)) {}
+
+ GradientCacheData(GradientCacheData&& aOther) = default;
+
+ nsExpirationState* GetExpirationState() { return &mExpirationState; }
+
+ nsExpirationState mExpirationState;
+ const RefPtr<GradientStops> mStops;
+ GradientCacheKey mKey;
+};
+
+/**
+ * This class implements a cache, that retains the GradientStops used to draw
+ * the gradients.
+ *
+ * An entry stays in the cache as long as it is used often and we don't exceed
+ * the maximum, in which case the most recently used will be kept.
+ */
+class GradientCache;
+using GradientCacheMutex = StaticDataMutex<UniquePtr<GradientCache>>;
+class MOZ_RAII LockedInstance {
+ public:
+ explicit LockedInstance(GradientCacheMutex& aDataMutex)
+ : mAutoLock(aDataMutex.Lock()) {}
+ UniquePtr<GradientCache>& operator->() const& { return mAutoLock.ref(); }
+ UniquePtr<GradientCache>& operator->() const&& = delete;
+ UniquePtr<GradientCache>& operator*() const& { return mAutoLock.ref(); }
+ UniquePtr<GradientCache>& operator*() const&& = delete;
+ explicit operator bool() const { return !!mAutoLock.ref(); }
+
+ private:
+ GradientCacheMutex::AutoLock mAutoLock;
+};
+
+class GradientCache final
+ : public ExpirationTrackerImpl<GradientCacheData, 4, GradientCacheMutex,
+ LockedInstance> {
+ public:
+ GradientCache()
+ : ExpirationTrackerImpl<GradientCacheData, 4, GradientCacheMutex,
+ LockedInstance>(MAX_GENERATION_MS,
+ "GradientCache") {}
+ static bool EnsureInstance() {
+ LockedInstance lockedInstance(sInstanceMutex);
+ return EnsureInstanceLocked(lockedInstance);
+ }
+
+ static void DestroyInstance() {
+ LockedInstance lockedInstance(sInstanceMutex);
+ if (lockedInstance) {
+ *lockedInstance = nullptr;
+ }
+ }
+
+ static void AgeAllGenerations() {
+ LockedInstance lockedInstance(sInstanceMutex);
+ if (!lockedInstance) {
+ return;
+ }
+ lockedInstance->AgeAllGenerationsLocked(lockedInstance);
+ lockedInstance->NotifyHandlerEndLocked(lockedInstance);
+ }
+
+ template <typename CreateFunc>
+ static already_AddRefed<GradientStops> LookupOrInsert(
+ const GradientCacheKey& aKey, CreateFunc aCreateFunc) {
+ uint32_t numberOfEntries;
+ RefPtr<GradientStops> stops;
+ {
+ LockedInstance lockedInstance(sInstanceMutex);
+ if (!EnsureInstanceLocked(lockedInstance)) {
+ return aCreateFunc();
+ }
+
+ GradientCacheData* gradientData = lockedInstance->mHashEntries.Get(aKey);
+ if (gradientData) {
+ if (gradientData->mStops && gradientData->mStops->IsValid()) {
+ lockedInstance->MarkUsedLocked(gradientData, lockedInstance);
+ return do_AddRef(gradientData->mStops);
+ }
+
+ lockedInstance->NotifyExpiredLocked(gradientData, lockedInstance);
+ lockedInstance->NotifyHandlerEndLocked(lockedInstance);
+ }
+
+ stops = aCreateFunc();
+ if (!stops) {
+ return nullptr;
+ }
+
+ auto data = MakeUnique<GradientCacheData>(stops, GradientCacheKey(&aKey));
+ nsresult rv = lockedInstance->AddObjectLocked(data.get(), lockedInstance);
+ if (NS_FAILED(rv)) {
+ // We are OOM, and we cannot track this object. We don't want to store
+ // entries in the hash table (since the expiration tracker is
+ // responsible for removing the cache entries), so we avoid putting that
+ // entry in the table, which is a good thing considering we are short on
+ // memory anyway, we probably don't want to retain things.
+ return stops.forget();
+ }
+ lockedInstance->mHashEntries.InsertOrUpdate(aKey, std::move(data));
+ numberOfEntries = lockedInstance->mHashEntries.Count();
+ }
+
+ if (numberOfEntries > MAX_ENTRIES) {
+ // We have too many entries force the cache to age a generation.
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("GradientCache::OnMaxEntriesBreached", [] {
+ LockedInstance lockedInstance(sInstanceMutex);
+ if (!lockedInstance) {
+ return;
+ }
+ lockedInstance->AgeOneGenerationLocked(lockedInstance);
+ lockedInstance->NotifyHandlerEndLocked(lockedInstance);
+ }));
+ }
+
+ return stops.forget();
+ }
+
+ GradientCacheMutex& GetMutex() final { return sInstanceMutex; }
+
+ void NotifyExpiredLocked(GradientCacheData* aObject,
+ const LockedInstance& aLockedInstance) final {
+ // Remove the gradient from the tracker.
+ RemoveObjectLocked(aObject, aLockedInstance);
+
+ // If entry exists move the data to mRemovedGradientData because we want to
+ // drop it outside of the lock.
+ Maybe<UniquePtr<GradientCacheData>> gradientData =
+ mHashEntries.Extract(aObject->mKey);
+ if (gradientData.isSome()) {
+ mRemovedGradientData.AppendElement(std::move(*gradientData));
+ }
+ }
+
+ void NotifyHandlerEndLocked(const LockedInstance&) final {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("GradientCache::DestroyRemovedGradientStops",
+ [stops = std::move(mRemovedGradientData)] {}));
+ }
+
+ private:
+ static const uint32_t MAX_GENERATION_MS = 10000;
+
+ // On Windows some of the Direct2D objects associated with the gradient stops
+ // can be quite large, so we limit the number of cache entries.
+ static const uint32_t MAX_ENTRIES = 4000;
+ static GradientCacheMutex sInstanceMutex;
+
+ [[nodiscard]] static bool EnsureInstanceLocked(
+ LockedInstance& aLockedInstance) {
+ if (!aLockedInstance) {
+ // GradientCache must be created on the main thread.
+ if (!NS_IsMainThread()) {
+ // This should only happen at shutdown, we fall back to not caching.
+ return false;
+ }
+ *aLockedInstance = MakeUnique<GradientCache>();
+ }
+ return true;
+ }
+
+ /**
+ * FIXME use nsTHashtable to avoid duplicating the GradientCacheKey.
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47
+ */
+ nsClassHashtable<GradientCacheKey, GradientCacheData> mHashEntries;
+ nsTArray<UniquePtr<GradientCacheData>> mRemovedGradientData;
+};
+
+GradientCacheMutex GradientCache::sInstanceMutex("GradientCache");
+
+void gfxGradientCache::Init() {
+ MOZ_RELEASE_ASSERT(GradientCache::EnsureInstance(),
+ "First call must be on main thread.");
+}
+
+already_AddRefed<GradientStops> gfxGradientCache::GetOrCreateGradientStops(
+ const DrawTarget* aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend) {
+ if (aDT->IsRecording()) {
+ return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(),
+ aExtend);
+ }
+
+ return GradientCache::LookupOrInsert(
+ GradientCacheKey(aStops, aExtend, aDT->GetBackendType()),
+ [&]() -> already_AddRefed<GradientStops> {
+ return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(),
+ aExtend);
+ });
+}
+
+void gfxGradientCache::PurgeAllCaches() { GradientCache::AgeAllGenerations(); }
+
+void gfxGradientCache::Shutdown() { GradientCache::DestroyInstance(); }
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/thebes/gfxGradientCache.h b/gfx/thebes/gfxGradientCache.h
new file mode 100644
index 0000000000..98f2aa9c35
--- /dev/null
+++ b/gfx/thebes/gfxGradientCache.h
@@ -0,0 +1,32 @@
+
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_GRADIENT_CACHE_H
+#define GFX_GRADIENT_CACHE_H
+
+#include "nsTArray.h"
+#include "gfxPattern.h"
+#include "mozilla/gfx/2D.h"
+
+namespace mozilla {
+namespace gfx {
+
+class gfxGradientCache {
+ public:
+ static void Init();
+
+ static already_AddRefed<gfx::GradientStops> GetOrCreateGradientStops(
+ const gfx::DrawTarget* aDT, nsTArray<gfx::GradientStop>& aStops,
+ gfx::ExtendMode aExtend);
+
+ static void PurgeAllCaches();
+ static void Shutdown();
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif
diff --git a/gfx/thebes/gfxGraphiteShaper.cpp b/gfx/thebes/gfxGraphiteShaper.cpp
new file mode 100644
index 0000000000..37e3d85d18
--- /dev/null
+++ b/gfx/thebes/gfxGraphiteShaper.cpp
@@ -0,0 +1,513 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxGraphiteShaper.h"
+#include "nsString.h"
+#include "gfxContext.h"
+#include "gfxFontConstants.h"
+#include "gfxTextRun.h"
+
+#include "graphite2/Font.h"
+#include "graphite2/GraphiteExtra.h"
+#include "graphite2/Segment.h"
+
+#include "harfbuzz/hb.h"
+
+#include "mozilla/ScopeExit.h"
+
+#include "ThebesRLBox.h"
+
+#define FloatToFixed(f) (65536 * (f))
+#define FixedToFloat(f) ((f) * (1.0 / 65536.0))
+// Right shifts of negative (signed) integers are undefined, as are overflows
+// when converting unsigned to negative signed integers.
+// (If speed were an issue we could make some 2's complement assumptions.)
+#define FixedToIntRound(f) \
+ ((f) > 0 ? ((32768 + (f)) >> 16) : -((32767 - (f)) >> 16))
+
+#define CopyAndVerifyOrFail(t, cond, failed) \
+ (t).copy_and_verify([&](auto val) { \
+ if (!(cond)) { \
+ *(failed) = true; \
+ } \
+ return val; \
+ })
+
+using namespace mozilla; // for AutoSwap_* types
+
+/*
+ * Creation and destruction; on deletion, release any font tables we're holding
+ */
+
+gfxGraphiteShaper::gfxGraphiteShaper(gfxFont* aFont)
+ : gfxFontShaper(aFont),
+ mGrFace(mFont->GetFontEntry()->GetGrFace()),
+ mSandbox(mFont->GetFontEntry()->GetGrSandbox()),
+ mCallback(mFont->GetFontEntry()->GetGrSandboxAdvanceCallbackHandle()),
+ mFallbackToSmallCaps(false) {
+ mCallbackData.mFont = aFont;
+}
+
+gfxGraphiteShaper::~gfxGraphiteShaper() {
+ auto t_mGrFont = rlbox::from_opaque(mGrFont);
+ if (t_mGrFont) {
+ sandbox_invoke(*mSandbox, gr_font_destroy, t_mGrFont);
+ }
+ mFont->GetFontEntry()->ReleaseGrFace(mGrFace);
+}
+
+/*static*/
+thread_local gfxGraphiteShaper::CallbackData*
+ gfxGraphiteShaper::tl_GrGetAdvanceData = nullptr;
+
+/*static*/
+tainted_opaque_gr<float> gfxGraphiteShaper::GrGetAdvance(
+ rlbox_sandbox_gr& sandbox,
+ tainted_opaque_gr<const void*> /* appFontHandle */,
+ tainted_opaque_gr<uint16_t> t_glyphid) {
+ CallbackData* cb = tl_GrGetAdvanceData;
+ if (!cb) {
+ // GrGetAdvance callback called unexpectedly. Just return safe value.
+ tainted_gr<float> ret = 0;
+ return ret.to_opaque();
+ }
+ auto glyphid = rlbox::from_opaque(t_glyphid).unverified_safe_because(
+ "Here the only use of a glyphid is for lookup to get a width. "
+ "Implementations of GetGlyphWidth in this code base use a hashtable "
+ "which is robust to unknown keys. So no validation is required.");
+ tainted_gr<float> ret = FixedToFloat(cb->mFont->GetGlyphWidth(glyphid));
+ return ret.to_opaque();
+}
+
+static inline uint32_t MakeGraphiteLangTag(uint32_t aTag) {
+ uint32_t grLangTag = aTag;
+ // replace trailing space-padding with NULs for graphite
+ uint32_t mask = 0x000000FF;
+ while ((grLangTag & mask) == ' ') {
+ grLangTag &= ~mask;
+ mask <<= 8;
+ }
+ return grLangTag;
+}
+
+struct GrFontFeatures {
+ tainted_gr<gr_face*> mFace;
+ tainted_gr<gr_feature_val*> mFeatures;
+ rlbox_sandbox_gr* mSandbox;
+};
+
+static void AddFeature(const uint32_t& aTag, uint32_t& aValue, void* aUserArg) {
+ GrFontFeatures* f = static_cast<GrFontFeatures*>(aUserArg);
+
+ tainted_gr<const gr_feature_ref*> fref =
+ sandbox_invoke(*(f->mSandbox), gr_face_find_fref, f->mFace, aTag);
+ if (fref) {
+ sandbox_invoke(*(f->mSandbox), gr_fref_set_feature_value, fref, aValue,
+ f->mFeatures);
+ }
+}
+
+// Count the number of Unicode characters in a UTF-16 string (i.e. surrogate
+// pairs are counted as 1, although they are 2 code units).
+// (Any isolated surrogates will count 1 each, because in decoding they would
+// be replaced by individual U+FFFD REPLACEMENT CHARACTERs.)
+static inline size_t CountUnicodes(const char16_t* aText, uint32_t aLength) {
+ size_t total = 0;
+ const char16_t* end = aText + aLength;
+ while (aText < end) {
+ if (NS_IS_HIGH_SURROGATE(*aText) && aText + 1 < end &&
+ NS_IS_LOW_SURROGATE(*(aText + 1))) {
+ aText += 2;
+ } else {
+ aText++;
+ }
+ total++;
+ }
+ return total;
+}
+
+bool gfxGraphiteShaper::ShapeText(DrawTarget* aDrawTarget,
+ const char16_t* aText, uint32_t aOffset,
+ uint32_t aLength, Script aScript,
+ nsAtom* aLanguage, bool aVertical,
+ RoundingFlags aRounding,
+ gfxShapedText* aShapedText) {
+ const gfxFontStyle* style = mFont->GetStyle();
+ auto t_mGrFace = rlbox::from_opaque(mGrFace);
+ auto t_mGrFont = rlbox::from_opaque(mGrFont);
+
+ if (!t_mGrFont) {
+ if (!t_mGrFace) {
+ return false;
+ }
+
+ if (mFont->ProvidesGlyphWidths()) {
+ auto p_ops = mSandbox->malloc_in_sandbox<gr_font_ops>();
+ if (!p_ops) {
+ return false;
+ }
+ auto clean_ops = MakeScopeExit([&] { mSandbox->free_in_sandbox(p_ops); });
+ p_ops->size = sizeof(*p_ops);
+ p_ops->glyph_advance_x = *mCallback;
+ p_ops->glyph_advance_y = nullptr; // vertical text not yet implemented
+ t_mGrFont = sandbox_invoke(
+ *mSandbox, gr_make_font_with_ops, mFont->GetAdjustedSize(),
+ // For security, we do not pass the callback data to this arg, and use
+ // a TLS var instead. However, gr_make_font_with_ops expects this to
+ // be a non null ptr, and changes its behavior if it isn't. Therefore,
+ // we should pass some dummy non null pointer which will be passed to
+ // the GrGetAdvance callback, but never used. Let's just pass p_ops
+ // again, as this is a non-null tainted pointer.
+ p_ops /* mCallbackData */, p_ops, t_mGrFace);
+ } else {
+ t_mGrFont = sandbox_invoke(*mSandbox, gr_make_font,
+ mFont->GetAdjustedSize(), t_mGrFace);
+ }
+ mGrFont = t_mGrFont.to_opaque();
+
+ if (!t_mGrFont) {
+ return false;
+ }
+
+ // determine whether petite-caps falls back to small-caps
+ if (style->variantCaps != NS_FONT_VARIANT_CAPS_NORMAL) {
+ switch (style->variantCaps) {
+ case NS_FONT_VARIANT_CAPS_ALLPETITE:
+ case NS_FONT_VARIANT_CAPS_PETITECAPS:
+ bool synLower, synUpper;
+ mFont->SupportsVariantCaps(aScript, style->variantCaps,
+ mFallbackToSmallCaps, synLower, synUpper);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ gfxFontEntry* entry = mFont->GetFontEntry();
+ uint32_t grLang = 0;
+ if (style->languageOverride) {
+ grLang = MakeGraphiteLangTag(style->languageOverride);
+ } else if (entry->mLanguageOverride) {
+ grLang = MakeGraphiteLangTag(entry->mLanguageOverride);
+ } else if (aLanguage) {
+ nsAutoCString langString;
+ aLanguage->ToUTF8String(langString);
+ grLang = GetGraphiteTagForLang(langString);
+ }
+ tainted_gr<gr_feature_val*> grFeatures =
+ sandbox_invoke(*mSandbox, gr_face_featureval_for_lang, t_mGrFace, grLang);
+
+ // insert any merged features into Graphite feature list
+ GrFontFeatures f = {t_mGrFace, grFeatures, mSandbox};
+ MergeFontFeatures(style, mFont->GetFontEntry()->mFeatureSettings,
+ aShapedText->DisableLigatures(),
+ mFont->GetFontEntry()->FamilyName(), mFallbackToSmallCaps,
+ AddFeature, &f);
+
+ // Graphite shaping doesn't map U+00a0 (nbsp) to space if it is missing
+ // from the font, so check for that possibility. (Most fonts double-map
+ // the space glyph to both 0x20 and 0xA0, so this won't often be needed;
+ // so we don't copy the text until we know it's required.)
+ nsAutoString transformed;
+ const char16_t NO_BREAK_SPACE = 0x00a0;
+ if (!entry->HasCharacter(NO_BREAK_SPACE)) {
+ nsDependentSubstring src(aText, aLength);
+ if (src.FindChar(NO_BREAK_SPACE) != kNotFound) {
+ transformed = src;
+ transformed.ReplaceChar(NO_BREAK_SPACE, ' ');
+ aText = transformed.BeginReading();
+ }
+ }
+
+ size_t numChars = CountUnicodes(aText, aLength);
+ gr_bidirtl grBidi = gr_bidirtl(
+ aShapedText->IsRightToLeft() ? (gr_rtl | gr_nobidi) : gr_nobidi);
+
+ tainted_gr<char16_t*> t_aText =
+ mSandbox->malloc_in_sandbox<char16_t>(aLength);
+ if (!t_aText) {
+ return false;
+ }
+ auto clean_txt = MakeScopeExit([&] { mSandbox->free_in_sandbox(t_aText); });
+
+ rlbox::memcpy(*mSandbox, t_aText, aText, aLength * sizeof(char16_t));
+
+ tl_GrGetAdvanceData = &mCallbackData;
+ auto clean_adv_data = MakeScopeExit([&] { tl_GrGetAdvanceData = nullptr; });
+
+ tainted_gr<gr_segment*> seg =
+ sandbox_invoke(*mSandbox, gr_make_seg, mGrFont, t_mGrFace, 0, grFeatures,
+ gr_utf16, t_aText, numChars, grBidi);
+
+ sandbox_invoke(*mSandbox, gr_featureval_destroy, grFeatures);
+
+ if (!seg) {
+ return false;
+ }
+
+ nsresult rv =
+ SetGlyphsFromSegment(aShapedText, aOffset, aLength, aText,
+ t_aText.to_opaque(), seg.to_opaque(), aRounding);
+
+ sandbox_invoke(*mSandbox, gr_seg_destroy, seg);
+
+ return NS_SUCCEEDED(rv);
+}
+
+nsresult gfxGraphiteShaper::SetGlyphsFromSegment(
+ gfxShapedText* aShapedText, uint32_t aOffset, uint32_t aLength,
+ const char16_t* aText, tainted_opaque_gr<char16_t*> t_aText,
+ tainted_opaque_gr<gr_segment*> aSegment, RoundingFlags aRounding) {
+ typedef gfxShapedText::CompressedGlyph CompressedGlyph;
+
+ int32_t dev2appUnits = aShapedText->GetAppUnitsPerDevUnit();
+ bool rtl = aShapedText->IsRightToLeft();
+
+ // identify clusters; graphite may have reordered/expanded/ligated glyphs.
+ tainted_gr<gr_glyph_to_char_association*> data =
+ sandbox_invoke(*mSandbox, gr_get_glyph_to_char_association, aSegment,
+ aLength, rlbox::from_opaque(t_aText));
+
+ if (!data) {
+ return NS_ERROR_FAILURE;
+ }
+
+ tainted_gr<gr_glyph_to_char_cluster*> clusters = data->clusters;
+ tainted_gr<uint16_t*> gids = data->gids;
+ tainted_gr<float*> xLocs = data->xLocs;
+ tainted_gr<float*> yLocs = data->yLocs;
+
+ CompressedGlyph* charGlyphs = aShapedText->GetCharacterGlyphs() + aOffset;
+
+ bool roundX = bool(aRounding & RoundingFlags::kRoundX);
+ bool roundY = bool(aRounding & RoundingFlags::kRoundY);
+
+ bool failedVerify = false;
+
+ // cIndex is primarily used to index into the clusters array which has size
+ // aLength below. As cIndex is not changing anymore, let's just verify it
+ // and remove the tainted wrapper.
+ uint32_t cIndex =
+ CopyAndVerifyOrFail(data->cIndex, val < aLength, &failedVerify);
+ if (failedVerify) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ // now put glyphs into the textrun, one cluster at a time
+ for (uint32_t i = 0; i <= cIndex; ++i) {
+ // We makes a local copy of "clusters[i]" which is of type
+ // tainted_gr<gr_glyph_to_char_cluster> below. We do this intentionally
+ // rather than taking a reference. Taking a reference with the code
+ //
+ // tainted_volatile_gr<gr_glyph_to_char_cluster>& c = clusters[i];
+ //
+ // produces a tainted_volatile which means the value can change at any
+ // moment allowing for possible time-of-check-time-of-use vuln. We thus
+ // make a local copy to simplify the verification.
+ tainted_gr<gr_glyph_to_char_cluster> c = clusters[i];
+
+ tainted_gr<float> t_adv; // total advance of the cluster
+ if (rtl) {
+ if (i == 0) {
+ t_adv = sandbox_invoke(*mSandbox, gr_seg_advance_X, aSegment) -
+ xLocs[c.baseGlyph];
+ } else {
+ t_adv = xLocs[clusters[i - 1].baseGlyph] - xLocs[c.baseGlyph];
+ }
+ } else {
+ if (i == cIndex) {
+ t_adv = sandbox_invoke(*mSandbox, gr_seg_advance_X, aSegment) -
+ xLocs[c.baseGlyph];
+ } else {
+ t_adv = xLocs[clusters[i + 1].baseGlyph] - xLocs[c.baseGlyph];
+ }
+ }
+
+ float adv = t_adv.unverified_safe_because(
+ "Per Bug 1569464 - this is the advance width of a glyph or cluster of "
+ "glyphs. There are no a-priori limits on what that might be. Incorrect "
+ "values will tend to result in bad layout or missing text, or bad "
+ "nscoord values. But, these will not result in safety issues.");
+
+ // check unexpected offset - offs used to index into aText
+ uint32_t offs =
+ CopyAndVerifyOrFail(c.baseChar, val < aLength, &failedVerify);
+ if (failedVerify) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // Check for default-ignorable char that didn't get filtered, combined,
+ // etc by the shaping process, and skip it.
+ auto one_glyph = c.nGlyphs == static_cast<uint32_t>(1);
+ auto one_char = c.nChars == static_cast<uint32_t>(1);
+
+ if ((one_glyph && one_char)
+ .unverified_safe_because(
+ "using this boolean check to decide whether to ignore a "
+ "character or not. The worst that can happen is a bad "
+ "rendering.")) {
+ if (aShapedText->FilterIfIgnorable(aOffset + offs, aText[offs])) {
+ continue;
+ }
+ }
+
+ uint32_t appAdvance = roundX ? NSToIntRound(adv) * dev2appUnits
+ : NSToIntRound(adv * dev2appUnits);
+
+ const char gid_simple_value[] =
+ "Per Bug 1569464 - these are glyph IDs that can range from 0 to the "
+ "maximum glyph ID supported by the font. However, out-of-range values "
+ "here should not lead to safety issues; they would simply result in "
+ "blank rendering, although this depends on the platform back-end.";
+
+ // gids[c.baseGlyph] is checked and used below. Since this is a
+ // tainted_volatile, which can change at any moment, we make a local copy
+ // first to prevent a time-of-check-time-of-use vuln.
+ uint16_t gid_of_base_glyph =
+ gids[c.baseGlyph].unverified_safe_because(gid_simple_value);
+
+ const char fast_path[] =
+ "Even if the number of glyphs set is an incorrect value, the else "
+ "branch is a more general purpose algorithm which can handle other "
+ "values of nGlyphs";
+
+ if (one_glyph.unverified_safe_because(fast_path) &&
+ CompressedGlyph::IsSimpleGlyphID(gid_of_base_glyph) &&
+ CompressedGlyph::IsSimpleAdvance(appAdvance) &&
+ charGlyphs[offs].IsClusterStart() &&
+ (yLocs[c.baseGlyph] == 0).unverified_safe_because(fast_path)) {
+ charGlyphs[offs].SetSimpleGlyph(appAdvance, gid_of_base_glyph);
+
+ } else {
+ // not a one-to-one mapping with simple metrics: use DetailedGlyph
+ AutoTArray<gfxShapedText::DetailedGlyph, 8> details;
+ float clusterLoc;
+
+ uint32_t glyph_end =
+ (c.baseGlyph + c.nGlyphs)
+ .unverified_safe_because(
+ "This only controls the total number of glyphs set for this "
+ "particular text. Worst that can happen is a bad rendering");
+
+ // check overflow - ensure loop start is before the end
+ uint32_t glyph_start =
+ CopyAndVerifyOrFail(c.baseGlyph, val <= glyph_end, &failedVerify);
+ if (failedVerify) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ for (uint32_t j = glyph_start; j < glyph_end; ++j) {
+ gfxShapedText::DetailedGlyph* d = details.AppendElement();
+ d->mGlyphID = gids[j].unverified_safe_because(gid_simple_value);
+
+ const char safe_coordinates[] =
+ "There are no limits on coordinates. Worst case, bad values would "
+ "force rendering off-screen, but there are no memory safety "
+ "issues.";
+
+ float yLocs_j = yLocs[j].unverified_safe_because(safe_coordinates);
+ float xLocs_j = xLocs[j].unverified_safe_because(safe_coordinates);
+
+ d->mOffset.y = roundY ? NSToIntRound(-yLocs_j) * dev2appUnits
+ : -yLocs_j * dev2appUnits;
+ if (j == glyph_start) {
+ d->mAdvance = appAdvance;
+ clusterLoc = xLocs_j;
+ } else {
+ float dx =
+ rtl ? (xLocs_j - clusterLoc) : (xLocs_j - clusterLoc - adv);
+ d->mOffset.x =
+ roundX ? NSToIntRound(dx) * dev2appUnits : dx * dev2appUnits;
+ d->mAdvance = 0;
+ }
+ }
+ aShapedText->SetDetailedGlyphs(aOffset + offs, details.Length(),
+ details.Elements());
+ }
+
+ // check unexpected offset
+ uint32_t char_end = CopyAndVerifyOrFail(c.baseChar + c.nChars,
+ val <= aLength, &failedVerify);
+ // check overflow - ensure loop start is before the end
+ uint32_t char_start =
+ CopyAndVerifyOrFail(c.baseChar + 1, val <= char_end, &failedVerify);
+ if (failedVerify) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ for (uint32_t j = char_start; j < char_end; ++j) {
+ CompressedGlyph& g = charGlyphs[j];
+ NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph");
+ g.SetComplex(g.IsClusterStart(), false);
+ }
+ }
+
+ sandbox_invoke(*mSandbox, gr_free_char_association, data);
+ return NS_OK;
+}
+
+// for language tag validation - include list of tags from the IANA registry
+#include "gfxLanguageTagList.cpp"
+
+nsTHashSet<uint32_t>* gfxGraphiteShaper::sLanguageTags;
+
+/*static*/
+uint32_t gfxGraphiteShaper::GetGraphiteTagForLang(const nsCString& aLang) {
+ int len = aLang.Length();
+ if (len < 2) {
+ return 0;
+ }
+
+ // convert primary language subtag to a left-packed, NUL-padded integer
+ // for the Graphite API
+ uint32_t grLang = 0;
+ for (int i = 0; i < 4; ++i) {
+ grLang <<= 8;
+ if (i < len) {
+ uint8_t ch = aLang[i];
+ if (ch == '-') {
+ // found end of primary language subtag, truncate here
+ len = i;
+ continue;
+ }
+ if (ch < 'a' || ch > 'z') {
+ // invalid character in tag, so ignore it completely
+ return 0;
+ }
+ grLang += ch;
+ }
+ }
+
+ // valid tags must have length = 2 or 3
+ if (len < 2 || len > 3) {
+ return 0;
+ }
+
+ if (!sLanguageTags) {
+ // store the registered IANA tags in a hash for convenient validation
+ sLanguageTags = new nsTHashSet<uint32_t>(ArrayLength(sLanguageTagList));
+ for (const uint32_t* tag = sLanguageTagList; *tag != 0; ++tag) {
+ sLanguageTags->Insert(*tag);
+ }
+ }
+
+ // only accept tags known in the IANA registry
+ if (sLanguageTags->Contains(grLang)) {
+ return grLang;
+ }
+
+ return 0;
+}
+
+/*static*/
+void gfxGraphiteShaper::Shutdown() {
+#ifdef NS_FREE_PERMANENT_DATA
+ if (sLanguageTags) {
+ sLanguageTags->Clear();
+ delete sLanguageTags;
+ sLanguageTags = nullptr;
+ }
+#endif
+}
diff --git a/gfx/thebes/gfxGraphiteShaper.h b/gfx/thebes/gfxGraphiteShaper.h
new file mode 100644
index 0000000000..ca2ae8fcb1
--- /dev/null
+++ b/gfx/thebes/gfxGraphiteShaper.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_GRAPHITESHAPER_H
+#define GFX_GRAPHITESHAPER_H
+
+#include "gfxFont.h"
+
+#include "mozilla/gfx/2D.h"
+#include "nsTHashSet.h"
+
+#include "ThebesRLBoxTypes.h"
+
+struct gr_face;
+struct gr_font;
+struct gr_segment;
+
+class gfxGraphiteShaper : public gfxFontShaper {
+ public:
+ explicit gfxGraphiteShaper(gfxFont* aFont);
+ virtual ~gfxGraphiteShaper();
+
+ bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText,
+ uint32_t aOffset, uint32_t aLength, Script aScript,
+ nsAtom* aLanguage, bool aVertical, RoundingFlags aRounding,
+ gfxShapedText* aShapedText) override;
+
+ static void Shutdown();
+
+ protected:
+ nsresult SetGlyphsFromSegment(gfxShapedText* aShapedText, uint32_t aOffset,
+ uint32_t aLength, const char16_t* aText,
+ tainted_opaque_gr<char16_t*> t_aText,
+ tainted_opaque_gr<gr_segment*> aSegment,
+ RoundingFlags aRounding);
+
+ // Graphite is run in a rlbox sandbox. Callback GrGetAdvance must be
+ // explicitly permitted. Since the sandbox is owned in gfxFontEntry class,
+ // gfxFontEntry needs access to the protected callback.
+ friend class gfxFontEntryCallbacks;
+ static tainted_opaque_gr<float> GrGetAdvance(
+ rlbox_sandbox_gr& sandbox, tainted_opaque_gr<const void*> appFontHandle,
+ tainted_opaque_gr<uint16_t> glyphid);
+
+ tainted_opaque_gr<gr_face*>
+ mGrFace; // owned by the font entry; shaper must call
+ // gfxFontEntry::ReleaseGrFace when finished with it
+ tainted_opaque_gr<gr_font*> mGrFont; // owned by the shaper itself
+
+ // All libGraphite functionality is sandboxed. This is the sandbox instance.
+ rlbox_sandbox_gr* mSandbox;
+
+ // Holds the handle to the permitted callback into Firefox for the sandboxed
+ // libGraphite
+ sandbox_callback_gr<float (*)(const void*, uint16_t)>* mCallback;
+
+ struct CallbackData {
+ // mFont is a pointer to the font that owns this shaper, so it will
+ // remain valid throughout our lifetime
+ gfxFont* MOZ_NON_OWNING_REF mFont;
+ };
+
+ CallbackData mCallbackData;
+ static thread_local CallbackData* tl_GrGetAdvanceData;
+
+ bool mFallbackToSmallCaps; // special fallback for the petite-caps case
+
+ // Convert HTML 'lang' (BCP47) to Graphite language code
+ static uint32_t GetGraphiteTagForLang(const nsCString& aLang);
+ static nsTHashSet<uint32_t>* sLanguageTags;
+};
+
+#endif /* GFX_GRAPHITESHAPER_H */
diff --git a/gfx/thebes/gfxHarfBuzzShaper.cpp b/gfx/thebes/gfxHarfBuzzShaper.cpp
new file mode 100644
index 0000000000..71b927cc92
--- /dev/null
+++ b/gfx/thebes/gfxHarfBuzzShaper.cpp
@@ -0,0 +1,1724 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsString.h"
+#include "gfxContext.h"
+#include "gfxFontConstants.h"
+#include "gfxHarfBuzzShaper.h"
+#include "gfxFontUtils.h"
+#include "gfxTextRun.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/intl/String.h"
+#include "mozilla/intl/UnicodeProperties.h"
+#include "mozilla/intl/UnicodeScriptCodes.h"
+#include "nsUnicodeProperties.h"
+
+#include "harfbuzz/hb.h"
+#include "harfbuzz/hb-ot.h"
+
+#include <algorithm>
+
+#define FloatToFixed(f) (65536 * (f))
+#define FixedToFloat(f) ((f) * (1.0 / 65536.0))
+// Right shifts of negative (signed) integers are undefined, as are overflows
+// when converting unsigned to negative signed integers.
+// (If speed were an issue we could make some 2's complement assumptions.)
+#define FixedToIntRound(f) \
+ ((f) > 0 ? ((32768 + (f)) >> 16) : -((32767 - (f)) >> 16))
+
+using namespace mozilla; // for AutoSwap_* types
+using namespace mozilla::unicode; // for Unicode property lookup
+
+/*
+ * Creation and destruction; on deletion, release any font tables we're holding
+ */
+
+gfxHarfBuzzShaper::gfxHarfBuzzShaper(gfxFont* aFont)
+ : gfxFontShaper(aFont),
+ mHBFont(nullptr),
+ mBuffer(nullptr),
+ mCallbackData(),
+ mKernTable(nullptr),
+ mHmtxTable(nullptr),
+ mVmtxTable(nullptr),
+ mVORGTable(nullptr),
+ mLocaTable(nullptr),
+ mGlyfTable(nullptr),
+ mCmapTable(nullptr),
+ mCmapFormat(-1),
+ mSubtableOffset(0),
+ mUVSTableOffset(0),
+ mNumLongHMetrics(0),
+ mNumLongVMetrics(0),
+ mDefaultVOrg(-1.0),
+ mUseFontGetGlyph(aFont->ProvidesGetGlyph()),
+ mIsSymbolFont(false),
+ mUseFontGlyphWidths(aFont->ProvidesGlyphWidths()),
+ mInitialized(false),
+ mVerticalInitialized(false),
+ mUseVerticalPresentationForms(false),
+ mLoadedLocaGlyf(false),
+ mLocaLongOffsets(false) {}
+
+gfxHarfBuzzShaper::~gfxHarfBuzzShaper() {
+ // hb_*_destroy functions are safe to call on nullptr
+ hb_blob_destroy(mCmapTable);
+ hb_blob_destroy(mHmtxTable);
+ hb_blob_destroy(mKernTable);
+ hb_blob_destroy(mVmtxTable);
+ hb_blob_destroy(mVORGTable);
+ hb_blob_destroy(mLocaTable);
+ hb_blob_destroy(mGlyfTable);
+ hb_font_destroy(mHBFont);
+ hb_buffer_destroy(mBuffer);
+}
+
+#define UNICODE_BMP_LIMIT 0x10000
+
+hb_codepoint_t gfxHarfBuzzShaper::GetNominalGlyph(
+ hb_codepoint_t unicode) const {
+ hb_codepoint_t gid = 0;
+
+ if (mUseFontGetGlyph) {
+ gid = mFont->GetGlyph(unicode, 0);
+ } else {
+ // we only instantiate a harfbuzz shaper if there's a cmap available
+ NS_ASSERTION(mCmapTable && (mCmapFormat > 0) && (mSubtableOffset > 0),
+ "cmap data not correctly set up, expect disaster");
+
+ uint32_t length;
+ const uint8_t* data = (const uint8_t*)hb_blob_get_data(mCmapTable, &length);
+
+ switch (mCmapFormat) {
+ case 4:
+ gid =
+ unicode < UNICODE_BMP_LIMIT
+ ? gfxFontUtils::MapCharToGlyphFormat4(
+ data + mSubtableOffset, length - mSubtableOffset, unicode)
+ : 0;
+ break;
+ case 10:
+ gid = gfxFontUtils::MapCharToGlyphFormat10(data + mSubtableOffset,
+ unicode);
+ break;
+ case 12:
+ case 13:
+ gid = gfxFontUtils::MapCharToGlyphFormat12or13(data + mSubtableOffset,
+ unicode);
+ break;
+ default:
+ NS_WARNING("unsupported cmap format, glyphs will be missing");
+ break;
+ }
+ }
+
+ if (!gid) {
+ if (mIsSymbolFont) {
+ // For legacy MS Symbol fonts, we try mapping the given character code
+ // to the PUA range used by these fonts' cmaps.
+ if (auto pua = gfxFontUtils::MapLegacySymbolFontCharToPUA(unicode)) {
+ gid = GetNominalGlyph(pua);
+ }
+ if (gid) {
+ return gid;
+ }
+ }
+ switch (unicode) {
+ case 0xA0:
+ // if there's no glyph for &nbsp;, just use the space glyph instead.
+ gid = mFont->GetSpaceGlyph();
+ break;
+ case 0x2010:
+ case 0x2011:
+ // For Unicode HYPHEN and NON-BREAKING HYPHEN, fall back to the ASCII
+ // HYPHEN-MINUS as a substitute.
+ gid = GetNominalGlyph('-');
+ break;
+ }
+ }
+
+ return gid;
+}
+
+hb_codepoint_t gfxHarfBuzzShaper::GetVariationGlyph(
+ hb_codepoint_t unicode, hb_codepoint_t variation_selector) const {
+ if (mUseFontGetGlyph) {
+ return mFont->GetGlyph(unicode, variation_selector);
+ }
+
+ NS_ASSERTION(mFont->GetFontEntry()->HasCmapTable(),
+ "we cannot be using this font!");
+ NS_ASSERTION(mCmapTable && (mCmapFormat > 0) && (mSubtableOffset > 0),
+ "cmap data not correctly set up, expect disaster");
+
+ uint32_t length;
+ const uint8_t* data = (const uint8_t*)hb_blob_get_data(mCmapTable, &length);
+
+ if (mUVSTableOffset) {
+ hb_codepoint_t gid = gfxFontUtils::MapUVSToGlyphFormat14(
+ data + mUVSTableOffset, unicode, variation_selector);
+ if (gid) {
+ return gid;
+ }
+ }
+
+ uint32_t compat = gfxFontUtils::GetUVSFallback(unicode, variation_selector);
+ if (compat) {
+ switch (mCmapFormat) {
+ case 4:
+ if (compat < UNICODE_BMP_LIMIT) {
+ return gfxFontUtils::MapCharToGlyphFormat4(
+ data + mSubtableOffset, length - mSubtableOffset, compat);
+ }
+ break;
+ case 10:
+ return gfxFontUtils::MapCharToGlyphFormat10(data + mSubtableOffset,
+ compat);
+ break;
+ case 12:
+ case 13:
+ return gfxFontUtils::MapCharToGlyphFormat12or13(data + mSubtableOffset,
+ compat);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int VertFormsGlyphCompare(const void* aKey, const void* aElem) {
+ return int(*((hb_codepoint_t*)(aKey))) - int(*((uint16_t*)(aElem)));
+}
+
+// Return a vertical presentation-form codepoint corresponding to the
+// given Unicode value, or 0 if no such form is available.
+hb_codepoint_t gfxHarfBuzzShaper::GetVerticalPresentationForm(
+ hb_codepoint_t aUnicode) {
+ static const uint16_t sVerticalForms[][2] = {
+ {0x2013, 0xfe32}, // EN DASH
+ {0x2014, 0xfe31}, // EM DASH
+ {0x2025, 0xfe30}, // TWO DOT LEADER
+ {0x2026, 0xfe19}, // HORIZONTAL ELLIPSIS
+ {0x3001, 0xfe11}, // IDEOGRAPHIC COMMA
+ {0x3002, 0xfe12}, // IDEOGRAPHIC FULL STOP
+ {0x3008, 0xfe3f}, // LEFT ANGLE BRACKET
+ {0x3009, 0xfe40}, // RIGHT ANGLE BRACKET
+ {0x300a, 0xfe3d}, // LEFT DOUBLE ANGLE BRACKET
+ {0x300b, 0xfe3e}, // RIGHT DOUBLE ANGLE BRACKET
+ {0x300c, 0xfe41}, // LEFT CORNER BRACKET
+ {0x300d, 0xfe42}, // RIGHT CORNER BRACKET
+ {0x300e, 0xfe43}, // LEFT WHITE CORNER BRACKET
+ {0x300f, 0xfe44}, // RIGHT WHITE CORNER BRACKET
+ {0x3010, 0xfe3b}, // LEFT BLACK LENTICULAR BRACKET
+ {0x3011, 0xfe3c}, // RIGHT BLACK LENTICULAR BRACKET
+ {0x3014, 0xfe39}, // LEFT TORTOISE SHELL BRACKET
+ {0x3015, 0xfe3a}, // RIGHT TORTOISE SHELL BRACKET
+ {0x3016, 0xfe17}, // LEFT WHITE LENTICULAR BRACKET
+ {0x3017, 0xfe18}, // RIGHT WHITE LENTICULAR BRACKET
+ {0xfe4f, 0xfe34}, // WAVY LOW LINE
+ {0xff01, 0xfe15}, // FULLWIDTH EXCLAMATION MARK
+ {0xff08, 0xfe35}, // FULLWIDTH LEFT PARENTHESIS
+ {0xff09, 0xfe36}, // FULLWIDTH RIGHT PARENTHESIS
+ {0xff0c, 0xfe10}, // FULLWIDTH COMMA
+ {0xff1a, 0xfe13}, // FULLWIDTH COLON
+ {0xff1b, 0xfe14}, // FULLWIDTH SEMICOLON
+ {0xff1f, 0xfe16}, // FULLWIDTH QUESTION MARK
+ {0xff3b, 0xfe47}, // FULLWIDTH LEFT SQUARE BRACKET
+ {0xff3d, 0xfe48}, // FULLWIDTH RIGHT SQUARE BRACKET
+ {0xff3f, 0xfe33}, // FULLWIDTH LOW LINE
+ {0xff5b, 0xfe37}, // FULLWIDTH LEFT CURLY BRACKET
+ {0xff5d, 0xfe38} // FULLWIDTH RIGHT CURLY BRACKET
+ };
+ const uint16_t* charPair = static_cast<const uint16_t*>(
+ bsearch(&aUnicode, sVerticalForms, ArrayLength(sVerticalForms),
+ sizeof(sVerticalForms[0]), VertFormsGlyphCompare));
+ return charPair ? charPair[1] : 0;
+}
+
+static hb_bool_t HBGetNominalGlyph(hb_font_t* font, void* font_data,
+ hb_codepoint_t unicode,
+ hb_codepoint_t* glyph, void* user_data) {
+ const gfxHarfBuzzShaper::FontCallbackData* fcd =
+ static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data);
+
+ if (fcd->mShaper->UseVerticalPresentationForms()) {
+ hb_codepoint_t verticalForm =
+ gfxHarfBuzzShaper::GetVerticalPresentationForm(unicode);
+ if (verticalForm) {
+ *glyph = fcd->mShaper->GetNominalGlyph(verticalForm);
+ if (*glyph != 0) {
+ return true;
+ }
+ }
+ // fall back to the non-vertical form if we didn't find an alternate
+ }
+
+ *glyph = fcd->mShaper->GetNominalGlyph(unicode);
+ return *glyph != 0;
+}
+
+static hb_bool_t HBGetVariationGlyph(hb_font_t* font, void* font_data,
+ hb_codepoint_t unicode,
+ hb_codepoint_t variation_selector,
+ hb_codepoint_t* glyph, void* user_data) {
+ const gfxHarfBuzzShaper::FontCallbackData* fcd =
+ static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data);
+
+ if (fcd->mShaper->UseVerticalPresentationForms()) {
+ hb_codepoint_t verticalForm =
+ gfxHarfBuzzShaper::GetVerticalPresentationForm(unicode);
+ if (verticalForm) {
+ *glyph =
+ fcd->mShaper->GetVariationGlyph(verticalForm, variation_selector);
+ if (*glyph != 0) {
+ return true;
+ }
+ }
+ // fall back to the non-vertical form if we didn't find an alternate
+ }
+
+ *glyph = fcd->mShaper->GetVariationGlyph(unicode, variation_selector);
+ return *glyph != 0;
+}
+
+// Glyph metrics structures, shared (with appropriate reinterpretation of
+// field names) by horizontal and vertical metrics tables.
+struct LongMetric {
+ AutoSwap_PRUint16 advanceWidth; // or advanceHeight, when vertical
+ AutoSwap_PRInt16 lsb; // or tsb, when vertical
+};
+
+struct GlyphMetrics {
+ LongMetric metrics[1]; // actually numberOfLongMetrics
+ // the variable-length metrics[] array is immediately followed by:
+ // AutoSwap_PRUint16 leftSideBearing[];
+};
+
+hb_position_t gfxHarfBuzzShaper::GetGlyphHAdvance(hb_codepoint_t glyph) const {
+ // font did not implement GetGlyphWidth, so get an unhinted value
+ // directly from the font tables
+
+ NS_ASSERTION((mNumLongHMetrics > 0) && mHmtxTable != nullptr,
+ "font is lacking metrics, we shouldn't be here");
+
+ if (glyph >= uint32_t(mNumLongHMetrics)) {
+ glyph = mNumLongHMetrics - 1;
+ }
+
+ // glyph must be valid now, because we checked during initialization
+ // that mNumLongHMetrics is > 0, and that the metrics table is large enough
+ // to contain mNumLongHMetrics records
+ const ::GlyphMetrics* metrics = reinterpret_cast<const ::GlyphMetrics*>(
+ hb_blob_get_data(mHmtxTable, nullptr));
+ return FloatToFixed(mFont->FUnitsToDevUnitsFactor() *
+ uint16_t(metrics->metrics[glyph].advanceWidth));
+}
+
+hb_position_t gfxHarfBuzzShaper::GetGlyphVAdvance(hb_codepoint_t glyph) {
+ InitializeVertical();
+
+ if (!mVmtxTable) {
+ // Must be a "vertical" font that doesn't actually have vertical metrics.
+ // Return an invalid (negative) value to tell the caller to fall back to
+ // something else.
+ return -1;
+ }
+
+ NS_ASSERTION(mNumLongVMetrics > 0,
+ "font is lacking metrics, we shouldn't be here");
+
+ if (glyph >= uint32_t(mNumLongVMetrics)) {
+ glyph = mNumLongVMetrics - 1;
+ }
+
+ // glyph must be valid now, because we checked during initialization
+ // that mNumLongVMetrics is > 0, and that the metrics table is large enough
+ // to contain mNumLongVMetrics records
+ const ::GlyphMetrics* metrics = reinterpret_cast<const ::GlyphMetrics*>(
+ hb_blob_get_data(mVmtxTable, nullptr));
+ return FloatToFixed(mFont->FUnitsToDevUnitsFactor() *
+ uint16_t(metrics->metrics[glyph].advanceWidth));
+}
+
+/* static */
+hb_position_t gfxHarfBuzzShaper::HBGetGlyphHAdvance(hb_font_t* font,
+ void* font_data,
+ hb_codepoint_t glyph,
+ void* user_data) {
+ const gfxHarfBuzzShaper::FontCallbackData* fcd =
+ static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data);
+ const gfxHarfBuzzShaper* shaper = fcd->mShaper;
+ if (shaper->mUseFontGlyphWidths) {
+ return shaper->GetFont()->GetGlyphWidth(glyph);
+ }
+ return shaper->GetGlyphHAdvance(glyph);
+}
+
+/* static */
+hb_position_t gfxHarfBuzzShaper::HBGetGlyphVAdvance(hb_font_t* font,
+ void* font_data,
+ hb_codepoint_t glyph,
+ void* user_data) {
+ const gfxHarfBuzzShaper::FontCallbackData* fcd =
+ static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data);
+ // Currently, we don't offer gfxFont subclasses a method to override this
+ // and provide hinted platform-specific vertical advances (analogous to the
+ // GetGlyphWidth method for horizontal advances). If that proves necessary,
+ // we'll add a new gfxFont method and call it from here.
+ hb_position_t advance = fcd->mShaper->GetGlyphVAdvance(glyph);
+ if (advance < 0) {
+ // Not available (e.g. broken metrics in the font); use a fallback value.
+ advance = FloatToFixed(fcd->mShaper->GetFont()
+ ->GetMetrics(nsFontMetrics::eVertical)
+ .aveCharWidth);
+ }
+ // We negate the value from GetGlyphVAdvance here because harfbuzz shapes
+ // with a coordinate system where positive is upwards, whereas the inline
+ // direction in which glyphs advance is downwards.
+ return -advance;
+}
+
+struct VORG {
+ AutoSwap_PRUint16 majorVersion;
+ AutoSwap_PRUint16 minorVersion;
+ AutoSwap_PRInt16 defaultVertOriginY;
+ AutoSwap_PRUint16 numVertOriginYMetrics;
+};
+
+struct VORGrec {
+ AutoSwap_PRUint16 glyphIndex;
+ AutoSwap_PRInt16 vertOriginY;
+};
+
+/* static */
+hb_bool_t gfxHarfBuzzShaper::HBGetGlyphVOrigin(hb_font_t* font, void* font_data,
+ hb_codepoint_t glyph,
+ hb_position_t* x,
+ hb_position_t* y,
+ void* user_data) {
+ const gfxHarfBuzzShaper::FontCallbackData* fcd =
+ static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data);
+ fcd->mShaper->GetGlyphVOrigin(glyph, x, y);
+ return true;
+}
+
+void gfxHarfBuzzShaper::GetGlyphVOrigin(hb_codepoint_t aGlyph,
+ hb_position_t* aX,
+ hb_position_t* aY) const {
+ *aX = 0.5 * (mUseFontGlyphWidths ? mFont->GetGlyphWidth(aGlyph)
+ : GetGlyphHAdvance(aGlyph));
+
+ if (mVORGTable) {
+ // We checked in Initialize() that the VORG table is safely readable,
+ // so no length/bounds-check needed here.
+ const VORG* vorg =
+ reinterpret_cast<const VORG*>(hb_blob_get_data(mVORGTable, nullptr));
+
+ const VORGrec* lo = reinterpret_cast<const VORGrec*>(vorg + 1);
+ const VORGrec* hi = lo + uint16_t(vorg->numVertOriginYMetrics);
+ const VORGrec* limit = hi;
+ while (lo < hi) {
+ const VORGrec* mid = lo + (hi - lo) / 2;
+ if (uint16_t(mid->glyphIndex) < aGlyph) {
+ lo = mid + 1;
+ } else {
+ hi = mid;
+ }
+ }
+
+ if (lo < limit && uint16_t(lo->glyphIndex) == aGlyph) {
+ *aY = FloatToFixed(GetFont()->FUnitsToDevUnitsFactor() *
+ int16_t(lo->vertOriginY));
+ } else {
+ *aY = FloatToFixed(GetFont()->FUnitsToDevUnitsFactor() *
+ int16_t(vorg->defaultVertOriginY));
+ }
+ return;
+ }
+
+ if (mVmtxTable) {
+ bool emptyGlyf;
+ const Glyf* glyf = FindGlyf(aGlyph, &emptyGlyf);
+ if (glyf) {
+ if (emptyGlyf) {
+ *aY = 0;
+ return;
+ }
+
+ const ::GlyphMetrics* metrics = reinterpret_cast<const ::GlyphMetrics*>(
+ hb_blob_get_data(mVmtxTable, nullptr));
+ int16_t lsb;
+ if (aGlyph < hb_codepoint_t(mNumLongVMetrics)) {
+ // Glyph is covered by the first (advance & sidebearing) array
+ lsb = int16_t(metrics->metrics[aGlyph].lsb);
+ } else {
+ // Glyph is covered by the second (sidebearing-only) array
+ const AutoSwap_PRInt16* sidebearings =
+ reinterpret_cast<const AutoSwap_PRInt16*>(
+ &metrics->metrics[mNumLongVMetrics]);
+ lsb = int16_t(sidebearings[aGlyph - mNumLongVMetrics]);
+ }
+ *aY = FloatToFixed(mFont->FUnitsToDevUnitsFactor() *
+ (lsb + int16_t(glyf->yMax)));
+ return;
+ } else {
+ // XXX TODO: not a truetype font; need to get glyph extents
+ // via some other API?
+ // For now, fall through to default code below.
+ }
+ }
+
+ if (mDefaultVOrg < 0.0) {
+ // XXX should we consider using OS/2 sTypo* metrics if available?
+
+ gfxFontEntry::AutoTable hheaTable(GetFont()->GetFontEntry(),
+ TRUETYPE_TAG('h', 'h', 'e', 'a'));
+ if (hheaTable) {
+ uint32_t len;
+ const MetricsHeader* hhea = reinterpret_cast<const MetricsHeader*>(
+ hb_blob_get_data(hheaTable, &len));
+ if (len >= sizeof(MetricsHeader)) {
+ // divide up the default advance we're using (1em) in proportion
+ // to ascender:descender from the hhea table
+ int16_t a = int16_t(hhea->ascender);
+ int16_t d = int16_t(hhea->descender);
+ mDefaultVOrg = FloatToFixed(GetFont()->GetAdjustedSize() * a / (a - d));
+ }
+ }
+
+ if (mDefaultVOrg < 0.0) {
+ // Last resort, for non-sfnt fonts: get the horizontal metrics and
+ // compute a default VOrg from their ascent and descent.
+ const gfxFont::Metrics& mtx = mFont->GetHorizontalMetrics();
+ gfxFloat advance =
+ mFont->GetMetrics(nsFontMetrics::eVertical).aveCharWidth;
+ gfxFloat ascent = mtx.emAscent;
+ gfxFloat height = ascent + mtx.emDescent;
+ // vOrigin that will place the glyph so that its origin is shifted
+ // down most of the way within overall (vertical) advance, in
+ // proportion to the font ascent as a part of the overall font
+ // height.
+ mDefaultVOrg = FloatToFixed(advance * ascent / height);
+ }
+ }
+
+ *aY = mDefaultVOrg;
+}
+
+static hb_bool_t HBGetGlyphExtents(hb_font_t* font, void* font_data,
+ hb_codepoint_t glyph,
+ hb_glyph_extents_t* extents,
+ void* user_data) {
+ const gfxHarfBuzzShaper::FontCallbackData* fcd =
+ static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data);
+ return fcd->mShaper->GetGlyphExtents(glyph, extents);
+}
+
+// Find the data for glyph ID |aGlyph| in the 'glyf' table, if present.
+// Returns null if not found, otherwise pointer to the beginning of the
+// glyph's data. Sets aEmptyGlyf true if there is no actual data;
+// otherwise, it's guaranteed that we can read at least the bounding box.
+const gfxHarfBuzzShaper::Glyf* gfxHarfBuzzShaper::FindGlyf(
+ hb_codepoint_t aGlyph, bool* aEmptyGlyf) const {
+ if (!mLoadedLocaGlyf) {
+ mLoadedLocaGlyf = true; // only try this once; if it fails, this
+ // isn't a truetype font
+ gfxFontEntry* entry = mFont->GetFontEntry();
+ uint32_t len;
+ gfxFontEntry::AutoTable headTable(entry, TRUETYPE_TAG('h', 'e', 'a', 'd'));
+ if (!headTable) {
+ return nullptr;
+ }
+ const HeadTable* head =
+ reinterpret_cast<const HeadTable*>(hb_blob_get_data(headTable, &len));
+ if (len < sizeof(HeadTable)) {
+ return nullptr;
+ }
+ mLocaLongOffsets = int16_t(head->indexToLocFormat) > 0;
+ mLocaTable = entry->GetFontTable(TRUETYPE_TAG('l', 'o', 'c', 'a'));
+ mGlyfTable = entry->GetFontTable(TRUETYPE_TAG('g', 'l', 'y', 'f'));
+ }
+
+ if (!mLocaTable || !mGlyfTable) {
+ // it's not a truetype font
+ return nullptr;
+ }
+
+ uint32_t offset; // offset of glyph record in the 'glyf' table
+ uint32_t len;
+ const char* data = hb_blob_get_data(mLocaTable, &len);
+ if (mLocaLongOffsets) {
+ if ((aGlyph + 1) * sizeof(AutoSwap_PRUint32) > len) {
+ return nullptr;
+ }
+ const AutoSwap_PRUint32* offsets =
+ reinterpret_cast<const AutoSwap_PRUint32*>(data);
+ offset = offsets[aGlyph];
+ *aEmptyGlyf = (offset == uint16_t(offsets[aGlyph + 1]));
+ } else {
+ if ((aGlyph + 1) * sizeof(AutoSwap_PRUint16) > len) {
+ return nullptr;
+ }
+ const AutoSwap_PRUint16* offsets =
+ reinterpret_cast<const AutoSwap_PRUint16*>(data);
+ offset = uint16_t(offsets[aGlyph]);
+ *aEmptyGlyf = (offset == uint16_t(offsets[aGlyph + 1]));
+ offset *= 2;
+ }
+
+ data = hb_blob_get_data(mGlyfTable, &len);
+ if (offset + sizeof(Glyf) > len) {
+ return nullptr;
+ }
+
+ return reinterpret_cast<const Glyf*>(data + offset);
+}
+
+hb_bool_t gfxHarfBuzzShaper::GetGlyphExtents(
+ hb_codepoint_t aGlyph, hb_glyph_extents_t* aExtents) const {
+ bool emptyGlyf;
+ const Glyf* glyf = FindGlyf(aGlyph, &emptyGlyf);
+ if (!glyf) {
+ // TODO: for non-truetype fonts, get extents some other way?
+ return false;
+ }
+
+ if (emptyGlyf) {
+ aExtents->x_bearing = 0;
+ aExtents->y_bearing = 0;
+ aExtents->width = 0;
+ aExtents->height = 0;
+ return true;
+ }
+
+ double f = mFont->FUnitsToDevUnitsFactor();
+ aExtents->x_bearing = FloatToFixed(int16_t(glyf->xMin) * f);
+ aExtents->width =
+ FloatToFixed((int16_t(glyf->xMax) - int16_t(glyf->xMin)) * f);
+
+ // Our y-coordinates are positive-downwards, whereas harfbuzz assumes
+ // positive-upwards; hence the apparently-reversed subtractions here.
+ aExtents->y_bearing = FloatToFixed(int16_t(glyf->yMax) * f -
+ mFont->GetHorizontalMetrics().emAscent);
+ aExtents->height =
+ FloatToFixed((int16_t(glyf->yMin) - int16_t(glyf->yMax)) * f);
+
+ return true;
+}
+
+static hb_bool_t HBGetContourPoint(hb_font_t* font, void* font_data,
+ unsigned int point_index,
+ hb_codepoint_t glyph, hb_position_t* x,
+ hb_position_t* y, void* user_data) {
+ /* not yet implemented - no support for used of hinted contour points
+ to fine-tune anchor positions in GPOS AnchorFormat2 */
+ return false;
+}
+
+struct KernHeaderFmt0 {
+ AutoSwap_PRUint16 nPairs;
+ AutoSwap_PRUint16 searchRange;
+ AutoSwap_PRUint16 entrySelector;
+ AutoSwap_PRUint16 rangeShift;
+};
+
+struct KernPair {
+ AutoSwap_PRUint16 left;
+ AutoSwap_PRUint16 right;
+ AutoSwap_PRInt16 value;
+};
+
+// Find a kern pair in a Format 0 subtable.
+// The aSubtable parameter points to the subtable itself, NOT its header,
+// as the header structure differs between Windows and Mac (v0 and v1.0)
+// versions of the 'kern' table.
+// aSubtableLen is the length of the subtable EXCLUDING its header.
+// If the pair <aFirstGlyph,aSecondGlyph> is found, the kerning value is
+// added to aValue, so that multiple subtables can accumulate a total
+// kerning value for a given pair.
+static void GetKernValueFmt0(const void* aSubtable, uint32_t aSubtableLen,
+ uint16_t aFirstGlyph, uint16_t aSecondGlyph,
+ int32_t& aValue, bool aIsOverride = false,
+ bool aIsMinimum = false) {
+ const KernHeaderFmt0* hdr =
+ reinterpret_cast<const KernHeaderFmt0*>(aSubtable);
+
+ const KernPair* lo = reinterpret_cast<const KernPair*>(hdr + 1);
+ const KernPair* hi = lo + uint16_t(hdr->nPairs);
+ const KernPair* limit = hi;
+
+ if (reinterpret_cast<const char*>(aSubtable) + aSubtableLen <
+ reinterpret_cast<const char*>(hi)) {
+ // subtable is not large enough to contain the claimed number
+ // of kern pairs, so just ignore it
+ return;
+ }
+
+#define KERN_PAIR_KEY(l, r) (uint32_t((uint16_t(l) << 16) + uint16_t(r)))
+
+ uint32_t key = KERN_PAIR_KEY(aFirstGlyph, aSecondGlyph);
+ while (lo < hi) {
+ const KernPair* mid = lo + (hi - lo) / 2;
+ if (KERN_PAIR_KEY(mid->left, mid->right) < key) {
+ lo = mid + 1;
+ } else {
+ hi = mid;
+ }
+ }
+
+ if (lo < limit && KERN_PAIR_KEY(lo->left, lo->right) == key) {
+ if (aIsOverride) {
+ aValue = int16_t(lo->value);
+ } else if (aIsMinimum) {
+ aValue = std::max(aValue, int32_t(lo->value));
+ } else {
+ aValue += int16_t(lo->value);
+ }
+ }
+}
+
+// Get kerning value from Apple (version 1.0) kern table,
+// subtable format 2 (simple N x M array of kerning values)
+
+// See http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html
+// for details of version 1.0 format 2 subtable.
+
+struct KernHeaderVersion1Fmt2 {
+ KernTableSubtableHeaderVersion1 header;
+ AutoSwap_PRUint16 rowWidth;
+ AutoSwap_PRUint16 leftOffsetTable;
+ AutoSwap_PRUint16 rightOffsetTable;
+ AutoSwap_PRUint16 array;
+};
+
+struct KernClassTableHdr {
+ AutoSwap_PRUint16 firstGlyph;
+ AutoSwap_PRUint16 nGlyphs;
+ AutoSwap_PRUint16 offsets[1]; // actually an array of nGlyphs entries
+};
+
+static int16_t GetKernValueVersion1Fmt2(const void* aSubtable,
+ uint32_t aSubtableLen,
+ uint16_t aFirstGlyph,
+ uint16_t aSecondGlyph) {
+ if (aSubtableLen < sizeof(KernHeaderVersion1Fmt2)) {
+ return 0;
+ }
+
+ const char* base = reinterpret_cast<const char*>(aSubtable);
+ const char* subtableEnd = base + aSubtableLen;
+
+ const KernHeaderVersion1Fmt2* h =
+ reinterpret_cast<const KernHeaderVersion1Fmt2*>(aSubtable);
+ uint32_t offset = h->array;
+
+ const KernClassTableHdr* leftClassTable =
+ reinterpret_cast<const KernClassTableHdr*>(base +
+ uint16_t(h->leftOffsetTable));
+ if (reinterpret_cast<const char*>(leftClassTable) +
+ sizeof(KernClassTableHdr) >
+ subtableEnd) {
+ return 0;
+ }
+ if (aFirstGlyph >= uint16_t(leftClassTable->firstGlyph)) {
+ aFirstGlyph -= uint16_t(leftClassTable->firstGlyph);
+ if (aFirstGlyph < uint16_t(leftClassTable->nGlyphs)) {
+ if (reinterpret_cast<const char*>(leftClassTable) +
+ sizeof(KernClassTableHdr) + aFirstGlyph * sizeof(uint16_t) >=
+ subtableEnd) {
+ return 0;
+ }
+ offset = uint16_t(leftClassTable->offsets[aFirstGlyph]);
+ }
+ }
+
+ const KernClassTableHdr* rightClassTable =
+ reinterpret_cast<const KernClassTableHdr*>(base +
+ uint16_t(h->rightOffsetTable));
+ if (reinterpret_cast<const char*>(rightClassTable) +
+ sizeof(KernClassTableHdr) >
+ subtableEnd) {
+ return 0;
+ }
+ if (aSecondGlyph >= uint16_t(rightClassTable->firstGlyph)) {
+ aSecondGlyph -= uint16_t(rightClassTable->firstGlyph);
+ if (aSecondGlyph < uint16_t(rightClassTable->nGlyphs)) {
+ if (reinterpret_cast<const char*>(rightClassTable) +
+ sizeof(KernClassTableHdr) + aSecondGlyph * sizeof(uint16_t) >=
+ subtableEnd) {
+ return 0;
+ }
+ offset += uint16_t(rightClassTable->offsets[aSecondGlyph]);
+ }
+ }
+
+ const AutoSwap_PRInt16* pval =
+ reinterpret_cast<const AutoSwap_PRInt16*>(base + offset);
+ if (reinterpret_cast<const char*>(pval + 1) >= subtableEnd) {
+ return 0;
+ }
+ return *pval;
+}
+
+// Get kerning value from Apple (version 1.0) kern table,
+// subtable format 3 (simple N x M array of kerning values)
+
+// See http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html
+// for details of version 1.0 format 3 subtable.
+
+struct KernHeaderVersion1Fmt3 {
+ KernTableSubtableHeaderVersion1 header;
+ AutoSwap_PRUint16 glyphCount;
+ uint8_t kernValueCount;
+ uint8_t leftClassCount;
+ uint8_t rightClassCount;
+ uint8_t flags;
+};
+
+static int16_t GetKernValueVersion1Fmt3(const void* aSubtable,
+ uint32_t aSubtableLen,
+ uint16_t aFirstGlyph,
+ uint16_t aSecondGlyph) {
+ // check that we can safely read the header fields
+ if (aSubtableLen < sizeof(KernHeaderVersion1Fmt3)) {
+ return 0;
+ }
+
+ const KernHeaderVersion1Fmt3* hdr =
+ reinterpret_cast<const KernHeaderVersion1Fmt3*>(aSubtable);
+ if (hdr->flags != 0) {
+ return 0;
+ }
+
+ uint16_t glyphCount = hdr->glyphCount;
+
+ // check that table is large enough for the arrays
+ if (sizeof(KernHeaderVersion1Fmt3) + hdr->kernValueCount * sizeof(int16_t) +
+ glyphCount + glyphCount + hdr->leftClassCount * hdr->rightClassCount >
+ aSubtableLen) {
+ return 0;
+ }
+
+ if (aFirstGlyph >= glyphCount || aSecondGlyph >= glyphCount) {
+ // glyphs are out of range for the class tables
+ return 0;
+ }
+
+ // get pointers to the four arrays within the subtable
+ const AutoSwap_PRInt16* kernValue =
+ reinterpret_cast<const AutoSwap_PRInt16*>(hdr + 1);
+ const uint8_t* leftClass =
+ reinterpret_cast<const uint8_t*>(kernValue + hdr->kernValueCount);
+ const uint8_t* rightClass = leftClass + glyphCount;
+ const uint8_t* kernIndex = rightClass + glyphCount;
+
+ uint8_t lc = leftClass[aFirstGlyph];
+ uint8_t rc = rightClass[aSecondGlyph];
+ if (lc >= hdr->leftClassCount || rc >= hdr->rightClassCount) {
+ return 0;
+ }
+
+ uint8_t ki = kernIndex[leftClass[aFirstGlyph] * hdr->rightClassCount +
+ rightClass[aSecondGlyph]];
+ if (ki >= hdr->kernValueCount) {
+ return 0;
+ }
+
+ return kernValue[ki];
+}
+
+#define KERN0_COVERAGE_HORIZONTAL 0x0001
+#define KERN0_COVERAGE_MINIMUM 0x0002
+#define KERN0_COVERAGE_CROSS_STREAM 0x0004
+#define KERN0_COVERAGE_OVERRIDE 0x0008
+#define KERN0_COVERAGE_RESERVED 0x00F0
+
+#define KERN1_COVERAGE_VERTICAL 0x8000
+#define KERN1_COVERAGE_CROSS_STREAM 0x4000
+#define KERN1_COVERAGE_VARIATION 0x2000
+#define KERN1_COVERAGE_RESERVED 0x1F00
+
+hb_position_t gfxHarfBuzzShaper::GetHKerning(uint16_t aFirstGlyph,
+ uint16_t aSecondGlyph) const {
+ // We want to ignore any kern pairs involving <space>, because we are
+ // handling words in isolation, the only space characters seen here are
+ // the ones artificially added by the textRun code.
+ uint32_t spaceGlyph = mFont->GetSpaceGlyph();
+ if (aFirstGlyph == spaceGlyph || aSecondGlyph == spaceGlyph) {
+ return 0;
+ }
+
+ if (!mKernTable) {
+ mKernTable =
+ mFont->GetFontEntry()->GetFontTable(TRUETYPE_TAG('k', 'e', 'r', 'n'));
+ if (!mKernTable) {
+ mKernTable = hb_blob_get_empty();
+ }
+ }
+
+ uint32_t len;
+ const char* base = hb_blob_get_data(mKernTable, &len);
+ if (len < sizeof(KernTableVersion0)) {
+ return 0;
+ }
+ int32_t value = 0;
+
+ // First try to interpret as "version 0" kern table
+ // (see http://www.microsoft.com/typography/otspec/kern.htm)
+ const KernTableVersion0* kern0 =
+ reinterpret_cast<const KernTableVersion0*>(base);
+ if (uint16_t(kern0->version) == 0) {
+ uint16_t nTables = kern0->nTables;
+ uint32_t offs = sizeof(KernTableVersion0);
+ for (uint16_t i = 0; i < nTables; ++i) {
+ if (offs + sizeof(KernTableSubtableHeaderVersion0) > len) {
+ break;
+ }
+ const KernTableSubtableHeaderVersion0* st0 =
+ reinterpret_cast<const KernTableSubtableHeaderVersion0*>(base + offs);
+ uint16_t subtableLen = uint16_t(st0->length);
+ if (offs + subtableLen > len) {
+ break;
+ }
+ offs += subtableLen;
+ uint16_t coverage = st0->coverage;
+ if (!(coverage & KERN0_COVERAGE_HORIZONTAL)) {
+ // we only care about horizontal kerning (for now)
+ continue;
+ }
+ if (coverage & (KERN0_COVERAGE_CROSS_STREAM | KERN0_COVERAGE_RESERVED)) {
+ // we don't support cross-stream kerning, and
+ // reserved bits should be zero;
+ // ignore the subtable if not
+ continue;
+ }
+ uint8_t format = (coverage >> 8);
+ switch (format) {
+ case 0:
+ GetKernValueFmt0(st0 + 1, subtableLen - sizeof(*st0), aFirstGlyph,
+ aSecondGlyph, value,
+ (coverage & KERN0_COVERAGE_OVERRIDE) != 0,
+ (coverage & KERN0_COVERAGE_MINIMUM) != 0);
+ break;
+ default:
+ // TODO: implement support for other formats,
+ // if they're ever used in practice
+#if DEBUG
+ {
+ char buf[1024];
+ SprintfLiteral(buf,
+ "unknown kern subtable in %s: "
+ "ver 0 format %d\n",
+ mFont->GetName().get(), format);
+ NS_WARNING(buf);
+ }
+#endif
+ break;
+ }
+ }
+ } else {
+ // It wasn't a "version 0" table; check if it is Apple version 1.0
+ // (see http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html)
+ const KernTableVersion1* kern1 =
+ reinterpret_cast<const KernTableVersion1*>(base);
+ if (uint32_t(kern1->version) == 0x00010000) {
+ uint32_t nTables = kern1->nTables;
+ uint32_t offs = sizeof(KernTableVersion1);
+ for (uint32_t i = 0; i < nTables; ++i) {
+ if (offs + sizeof(KernTableSubtableHeaderVersion1) > len) {
+ break;
+ }
+ const KernTableSubtableHeaderVersion1* st1 =
+ reinterpret_cast<const KernTableSubtableHeaderVersion1*>(base +
+ offs);
+ uint32_t subtableLen = uint32_t(st1->length);
+ offs += subtableLen;
+ uint16_t coverage = st1->coverage;
+ if (coverage & (KERN1_COVERAGE_VERTICAL | KERN1_COVERAGE_CROSS_STREAM |
+ KERN1_COVERAGE_VARIATION | KERN1_COVERAGE_RESERVED)) {
+ // we only care about horizontal kerning (for now),
+ // we don't support cross-stream kerning,
+ // we don't support variations,
+ // reserved bits should be zero;
+ // ignore the subtable if not
+ continue;
+ }
+ uint8_t format = (coverage & 0xff);
+ switch (format) {
+ case 0:
+ GetKernValueFmt0(st1 + 1, subtableLen - sizeof(*st1), aFirstGlyph,
+ aSecondGlyph, value);
+ break;
+ case 2:
+ value = GetKernValueVersion1Fmt2(st1, subtableLen, aFirstGlyph,
+ aSecondGlyph);
+ break;
+ case 3:
+ value = GetKernValueVersion1Fmt3(st1, subtableLen, aFirstGlyph,
+ aSecondGlyph);
+ break;
+ default:
+ // TODO: implement support for other formats.
+ // Note that format 1 cannot be supported here,
+ // as it requires the full glyph array to run the FSM,
+ // not just the current glyph pair.
+#if DEBUG
+ {
+ char buf[1024];
+ SprintfLiteral(buf,
+ "unknown kern subtable in %s: "
+ "ver 0 format %d\n",
+ mFont->GetName().get(), format);
+ NS_WARNING(buf);
+ }
+#endif
+ break;
+ }
+ }
+ }
+ }
+
+ if (value != 0) {
+ return FloatToFixed(mFont->FUnitsToDevUnitsFactor() * value);
+ }
+ return 0;
+}
+
+static hb_position_t HBGetHKerning(hb_font_t* font, void* font_data,
+ hb_codepoint_t first_glyph,
+ hb_codepoint_t second_glyph,
+ void* user_data) {
+ const gfxHarfBuzzShaper::FontCallbackData* fcd =
+ static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data);
+ return fcd->mShaper->GetHKerning(first_glyph, second_glyph);
+}
+
+/*
+ * HarfBuzz unicode property callbacks
+ */
+
+static hb_codepoint_t HBGetMirroring(hb_unicode_funcs_t* ufuncs,
+ hb_codepoint_t aCh, void* user_data) {
+ return intl::UnicodeProperties::CharMirror(aCh);
+}
+
+static hb_unicode_general_category_t HBGetGeneralCategory(
+ hb_unicode_funcs_t* ufuncs, hb_codepoint_t aCh, void* user_data) {
+ return hb_unicode_general_category_t(GetGeneralCategory(aCh));
+}
+
+static hb_script_t HBGetScript(hb_unicode_funcs_t* ufuncs, hb_codepoint_t aCh,
+ void* user_data) {
+ return hb_script_t(
+ GetScriptTagForCode(intl::UnicodeProperties::GetScriptCode(aCh)));
+}
+
+static hb_unicode_combining_class_t HBGetCombiningClass(
+ hb_unicode_funcs_t* ufuncs, hb_codepoint_t aCh, void* user_data) {
+ return hb_unicode_combining_class_t(
+ intl::UnicodeProperties::GetCombiningClass(aCh));
+}
+
+static hb_bool_t HBUnicodeCompose(hb_unicode_funcs_t* ufuncs, hb_codepoint_t a,
+ hb_codepoint_t b, hb_codepoint_t* ab,
+ void* user_data) {
+ char32_t ch = intl::String::ComposePairNFC(a, b);
+ if (ch > 0) {
+ *ab = ch;
+ return true;
+ }
+
+ return false;
+}
+
+static hb_bool_t HBUnicodeDecompose(hb_unicode_funcs_t* ufuncs,
+ hb_codepoint_t ab, hb_codepoint_t* a,
+ hb_codepoint_t* b, void* user_data) {
+#ifdef MOZ_WIDGET_ANDROID
+ // Hack for the SamsungDevanagari font, bug 1012365:
+ // support U+0972 by decomposing it.
+ if (ab == 0x0972) {
+ *a = 0x0905;
+ *b = 0x0945;
+ return true;
+ }
+#endif
+
+ char32_t decomp[2] = {0};
+ if (intl::String::DecomposeRawNFD(ab, decomp)) {
+ if (decomp[1] || decomp[0] != ab) {
+ *a = decomp[0];
+ *b = decomp[1];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void AddOpenTypeFeature(const uint32_t& aTag, uint32_t& aValue,
+ void* aUserArg) {
+ nsTArray<hb_feature_t>* features =
+ static_cast<nsTArray<hb_feature_t>*>(aUserArg);
+
+ hb_feature_t feat = {0, 0, 0, UINT_MAX};
+ feat.tag = aTag;
+ feat.value = aValue;
+ features->AppendElement(feat);
+}
+
+/*
+ * gfxFontShaper override to initialize the text run using HarfBuzz
+ */
+
+static hb_font_funcs_t* sHBFontFuncs = nullptr;
+static hb_font_funcs_t* sNominalGlyphFunc = nullptr;
+static hb_unicode_funcs_t* sHBUnicodeFuncs = nullptr;
+static const hb_script_t sMathScript =
+ hb_ot_tag_to_script(HB_TAG('m', 'a', 't', 'h'));
+
+bool gfxHarfBuzzShaper::Initialize() {
+ if (mInitialized) {
+ return mHBFont != nullptr;
+ }
+ mInitialized = true;
+ mCallbackData.mShaper = this;
+
+ if (!sHBFontFuncs) {
+ // static function callback pointers, initialized by the first
+ // harfbuzz shaper used
+ sHBFontFuncs = hb_font_funcs_create();
+ hb_font_funcs_set_nominal_glyph_func(sHBFontFuncs, HBGetNominalGlyph,
+ nullptr, nullptr);
+ hb_font_funcs_set_variation_glyph_func(sHBFontFuncs, HBGetVariationGlyph,
+ nullptr, nullptr);
+ hb_font_funcs_set_glyph_h_advance_func(sHBFontFuncs, HBGetGlyphHAdvance,
+ nullptr, nullptr);
+ hb_font_funcs_set_glyph_v_advance_func(sHBFontFuncs, HBGetGlyphVAdvance,
+ nullptr, nullptr);
+ hb_font_funcs_set_glyph_v_origin_func(sHBFontFuncs, HBGetGlyphVOrigin,
+ nullptr, nullptr);
+ hb_font_funcs_set_glyph_extents_func(sHBFontFuncs, HBGetGlyphExtents,
+ nullptr, nullptr);
+ hb_font_funcs_set_glyph_contour_point_func(sHBFontFuncs, HBGetContourPoint,
+ nullptr, nullptr);
+ hb_font_funcs_set_glyph_h_kerning_func(sHBFontFuncs, HBGetHKerning, nullptr,
+ nullptr);
+ hb_font_funcs_make_immutable(sHBFontFuncs);
+
+ sNominalGlyphFunc = hb_font_funcs_create();
+ hb_font_funcs_set_nominal_glyph_func(sNominalGlyphFunc, HBGetNominalGlyph,
+ nullptr, nullptr);
+ hb_font_funcs_make_immutable(sNominalGlyphFunc);
+
+ sHBUnicodeFuncs = hb_unicode_funcs_create(hb_unicode_funcs_get_empty());
+ hb_unicode_funcs_set_mirroring_func(sHBUnicodeFuncs, HBGetMirroring,
+ nullptr, nullptr);
+ hb_unicode_funcs_set_script_func(sHBUnicodeFuncs, HBGetScript, nullptr,
+ nullptr);
+ hb_unicode_funcs_set_general_category_func(
+ sHBUnicodeFuncs, HBGetGeneralCategory, nullptr, nullptr);
+ hb_unicode_funcs_set_combining_class_func(
+ sHBUnicodeFuncs, HBGetCombiningClass, nullptr, nullptr);
+ hb_unicode_funcs_set_compose_func(sHBUnicodeFuncs, HBUnicodeCompose,
+ nullptr, nullptr);
+ hb_unicode_funcs_set_decompose_func(sHBUnicodeFuncs, HBUnicodeDecompose,
+ nullptr, nullptr);
+ hb_unicode_funcs_make_immutable(sHBUnicodeFuncs);
+ }
+
+ gfxFontEntry* entry = mFont->GetFontEntry();
+ if (!mUseFontGetGlyph) {
+ // get the cmap table and find offset to our subtable
+ mCmapTable = entry->GetFontTable(TRUETYPE_TAG('c', 'm', 'a', 'p'));
+ if (!mCmapTable) {
+ NS_WARNING("failed to load cmap, glyphs will be missing");
+ return false;
+ }
+ uint32_t len;
+ const uint8_t* data = (const uint8_t*)hb_blob_get_data(mCmapTable, &len);
+ mCmapFormat = gfxFontUtils::FindPreferredSubtable(
+ data, len, &mSubtableOffset, &mUVSTableOffset, &mIsSymbolFont);
+ if (mCmapFormat <= 0) {
+ return false;
+ }
+ }
+
+ if (!mUseFontGlyphWidths) {
+ // If font doesn't implement GetGlyphWidth, we will be reading
+ // the metrics table directly, so make sure we can load it.
+ if (!LoadHmtxTable()) {
+ return false;
+ }
+ }
+
+ mBuffer = hb_buffer_create();
+ hb_buffer_set_unicode_funcs(mBuffer, sHBUnicodeFuncs);
+ hb_buffer_set_cluster_level(mBuffer,
+ HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
+
+ auto* funcs =
+ mFont->GetFontEntry()->HasFontTable(TRUETYPE_TAG('C', 'F', 'F', ' '))
+ ? sNominalGlyphFunc
+ : sHBFontFuncs;
+ mHBFont = CreateHBFont(mFont, funcs, &mCallbackData);
+
+ return true;
+}
+
+hb_font_t* gfxHarfBuzzShaper::CreateHBFont(gfxFont* aFont,
+ hb_font_funcs_t* aFontFuncs,
+ FontCallbackData* aCallbackData) {
+ auto face(aFont->GetFontEntry()->GetHBFace());
+ hb_font_t* result = hb_font_create(face);
+
+ if (aFontFuncs && aCallbackData) {
+ if (aFontFuncs == sNominalGlyphFunc) {
+ hb_font_t* subfont = hb_font_create_sub_font(result);
+ hb_font_destroy(result);
+ result = subfont;
+ }
+ hb_font_set_funcs(result, aFontFuncs, aCallbackData, nullptr);
+ }
+ hb_font_set_ppem(result, aFont->GetAdjustedSize(), aFont->GetAdjustedSize());
+ uint32_t scale = FloatToFixed(aFont->GetAdjustedSize()); // 16.16 fixed-point
+ hb_font_set_scale(result, scale, scale);
+
+ AutoTArray<gfxFontVariation, 8> vars;
+ aFont->GetFontEntry()->GetVariationsForStyle(vars, *aFont->GetStyle());
+ if (vars.Length() > 0) {
+ // Fortunately, the hb_variation_t struct is compatible with our
+ // gfxFontVariation, so we can simply cast here.
+ static_assert(
+ sizeof(gfxFontVariation) == sizeof(hb_variation_t) &&
+ offsetof(gfxFontVariation, mTag) == offsetof(hb_variation_t, tag) &&
+ offsetof(gfxFontVariation, mValue) ==
+ offsetof(hb_variation_t, value),
+ "Gecko vs HarfBuzz struct mismatch!");
+ auto hbVars = reinterpret_cast<const hb_variation_t*>(vars.Elements());
+ hb_font_set_variations(result, hbVars, vars.Length());
+ }
+
+ return result;
+}
+
+bool gfxHarfBuzzShaper::LoadHmtxTable() {
+ // Read mNumLongHMetrics from metrics-head table without caching its
+ // blob, and preload/cache the metrics table.
+ gfxFontEntry* entry = mFont->GetFontEntry();
+ gfxFontEntry::AutoTable hheaTable(entry, TRUETYPE_TAG('h', 'h', 'e', 'a'));
+ if (hheaTable) {
+ uint32_t len;
+ const MetricsHeader* hhea = reinterpret_cast<const MetricsHeader*>(
+ hb_blob_get_data(hheaTable, &len));
+ if (len >= sizeof(MetricsHeader)) {
+ mNumLongHMetrics = hhea->numOfLongMetrics;
+ if (mNumLongHMetrics > 0 && int16_t(hhea->metricDataFormat) == 0) {
+ // no point reading metrics if number of entries is zero!
+ // in that case, we won't be able to use this font
+ // (this method will return FALSE below if mHmtxTable
+ // is null)
+ mHmtxTable = entry->GetFontTable(TRUETYPE_TAG('h', 'm', 't', 'x'));
+ if (mHmtxTable && hb_blob_get_length(mHmtxTable) <
+ mNumLongHMetrics * sizeof(LongMetric)) {
+ // metrics table is not large enough for the claimed
+ // number of entries: invalid, do not use.
+ hb_blob_destroy(mHmtxTable);
+ mHmtxTable = nullptr;
+ }
+ }
+ }
+ }
+ if (!mHmtxTable) {
+ return false;
+ }
+ return true;
+}
+
+void gfxHarfBuzzShaper::InitializeVertical() {
+ // We only do this once. If we don't have a mHmtxTable after that,
+ // we'll be making up fallback metrics.
+ if (mVerticalInitialized) {
+ return;
+ }
+ mVerticalInitialized = true;
+
+ if (!mHmtxTable) {
+ if (!LoadHmtxTable()) {
+ return;
+ }
+ }
+
+ // Load vertical metrics if present in the font; if not, we'll synthesize
+ // vertical glyph advances based on (horizontal) ascent/descent metrics.
+ gfxFontEntry* entry = mFont->GetFontEntry();
+ gfxFontEntry::AutoTable vheaTable(entry, TRUETYPE_TAG('v', 'h', 'e', 'a'));
+ if (vheaTable) {
+ uint32_t len;
+ const MetricsHeader* vhea = reinterpret_cast<const MetricsHeader*>(
+ hb_blob_get_data(vheaTable, &len));
+ if (len >= sizeof(MetricsHeader)) {
+ mNumLongVMetrics = vhea->numOfLongMetrics;
+ gfxFontEntry::AutoTable maxpTable(entry,
+ TRUETYPE_TAG('m', 'a', 'x', 'p'));
+ int numGlyphs = -1; // invalid if we fail to read 'maxp'
+ if (maxpTable &&
+ hb_blob_get_length(maxpTable) >= sizeof(MaxpTableHeader)) {
+ const MaxpTableHeader* maxp = reinterpret_cast<const MaxpTableHeader*>(
+ hb_blob_get_data(maxpTable, nullptr));
+ numGlyphs = uint16_t(maxp->numGlyphs);
+ }
+ if (mNumLongVMetrics > 0 && mNumLongVMetrics <= numGlyphs &&
+ int16_t(vhea->metricDataFormat) == 0) {
+ mVmtxTable = entry->GetFontTable(TRUETYPE_TAG('v', 'm', 't', 'x'));
+ if (mVmtxTable &&
+ hb_blob_get_length(mVmtxTable) <
+ mNumLongVMetrics * sizeof(LongMetric) +
+ (numGlyphs - mNumLongVMetrics) * sizeof(int16_t)) {
+ // metrics table is not large enough for the claimed
+ // number of entries: invalid, do not use.
+ hb_blob_destroy(mVmtxTable);
+ mVmtxTable = nullptr;
+ }
+ }
+ }
+ }
+
+ // For CFF fonts only, load a VORG table if present.
+ if (entry->HasFontTable(TRUETYPE_TAG('C', 'F', 'F', ' '))) {
+ mVORGTable = entry->GetFontTable(TRUETYPE_TAG('V', 'O', 'R', 'G'));
+ if (mVORGTable) {
+ uint32_t len;
+ const VORG* vorg =
+ reinterpret_cast<const VORG*>(hb_blob_get_data(mVORGTable, &len));
+ if (len < sizeof(VORG) || uint16_t(vorg->majorVersion) != 1 ||
+ uint16_t(vorg->minorVersion) != 0 ||
+ len < sizeof(VORG) +
+ uint16_t(vorg->numVertOriginYMetrics) * sizeof(VORGrec)) {
+ // VORG table is an unknown version, or not large enough
+ // to be valid -- discard it.
+ NS_WARNING("discarding invalid VORG table");
+ hb_blob_destroy(mVORGTable);
+ mVORGTable = nullptr;
+ }
+ }
+ }
+}
+
+bool gfxHarfBuzzShaper::ShapeText(DrawTarget* aDrawTarget,
+ const char16_t* aText, uint32_t aOffset,
+ uint32_t aLength, Script aScript,
+ nsAtom* aLanguage, bool aVertical,
+ RoundingFlags aRounding,
+ gfxShapedText* aShapedText) {
+ mUseVerticalPresentationForms = false;
+
+ if (!Initialize()) {
+ return false;
+ }
+
+ if (aVertical) {
+ InitializeVertical();
+ if (!mFont->GetFontEntry()->SupportsOpenTypeFeature(
+ aScript, HB_TAG('v', 'e', 'r', 't'))) {
+ mUseVerticalPresentationForms = true;
+ }
+ }
+
+ const gfxFontStyle* style = mFont->GetStyle();
+
+ // determine whether petite-caps falls back to small-caps
+ bool addSmallCaps = false;
+ if (style->variantCaps != NS_FONT_VARIANT_CAPS_NORMAL) {
+ switch (style->variantCaps) {
+ case NS_FONT_VARIANT_CAPS_ALLPETITE:
+ case NS_FONT_VARIANT_CAPS_PETITECAPS:
+ bool synLower, synUpper;
+ mFont->SupportsVariantCaps(aScript, style->variantCaps, addSmallCaps,
+ synLower, synUpper);
+ break;
+ default:
+ break;
+ }
+ }
+
+ gfxFontEntry* entry = mFont->GetFontEntry();
+
+ // insert any merged features into hb_feature array
+ AutoTArray<hb_feature_t, 20> features;
+ MergeFontFeatures(style, entry->mFeatureSettings,
+ aShapedText->DisableLigatures(), entry->FamilyName(),
+ addSmallCaps, AddOpenTypeFeature, &features);
+
+ // For CJK script, match kerning and proportional-alternates (palt) features
+ // (and their vertical counterparts) as per spec:
+ // https://learn.microsoft.com/en-us/typography/opentype/spec/features_pt#tag-palt
+ // and disable kerning by default (for font-kerning:auto).
+ if (gfxTextRun::IsCJKScript(aScript)) {
+ hb_tag_t kern =
+ aVertical ? HB_TAG('v', 'k', 'r', 'n') : HB_TAG('k', 'e', 'r', 'n');
+ hb_tag_t alt =
+ aVertical ? HB_TAG('v', 'p', 'a', 'l') : HB_TAG('p', 'a', 'l', 't');
+ struct Cmp {
+ bool Equals(const hb_feature_t& a, const hb_tag_t& b) const {
+ return a.tag == b;
+ }
+ };
+ constexpr auto NoIndex = nsTArray<hb_feature_t>::NoIndex;
+ nsTArray<hb_feature_t>::index_type i = features.IndexOf(kern, 0, Cmp());
+ if (i == NoIndex) {
+ // Kerning was not explicitly set; override harfbuzz's default to disable
+ // it.
+ features.AppendElement(hb_feature_t{kern, 0, HB_FEATURE_GLOBAL_START,
+ HB_FEATURE_GLOBAL_END});
+ } else if (features[i].value) {
+ // If kerning was explicitly enabled), we also turn on proportional
+ // alternates, as per the OpenType feature registry.
+ // Bug 1798297: for the Yu Gothic UI font, we don't do this, because its
+ // 'palt' feature produces badly-spaced (overcrowded) kana glyphs.
+ if (!entry->FamilyName().EqualsLiteral("Yu Gothic UI")) {
+ if (features.IndexOf(alt, 0, Cmp()) == NoIndex) {
+ features.AppendElement(hb_feature_t{alt, 1, HB_FEATURE_GLOBAL_START,
+ HB_FEATURE_GLOBAL_END});
+ }
+ }
+ }
+ }
+
+ bool isRightToLeft = aShapedText->IsRightToLeft();
+
+ hb_buffer_set_direction(
+ mBuffer, aVertical
+ ? HB_DIRECTION_TTB
+ : (isRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR));
+ hb_script_t scriptTag;
+ if (aShapedText->GetFlags() & gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT) {
+ scriptTag = sMathScript;
+ } else {
+ scriptTag = GetHBScriptUsedForShaping(aScript);
+ }
+ hb_buffer_set_script(mBuffer, scriptTag);
+
+ hb_language_t language;
+ if (style->languageOverride) {
+ language = hb_ot_tag_to_language(style->languageOverride);
+ } else if (entry->mLanguageOverride) {
+ language = hb_ot_tag_to_language(entry->mLanguageOverride);
+ } else if (aLanguage) {
+ nsCString langString;
+ aLanguage->ToUTF8String(langString);
+ language = hb_language_from_string(langString.get(), langString.Length());
+ } else {
+ language = hb_ot_tag_to_language(HB_OT_TAG_DEFAULT_LANGUAGE);
+ }
+ hb_buffer_set_language(mBuffer, language);
+
+ uint32_t length = aLength;
+ hb_buffer_add_utf16(mBuffer, reinterpret_cast<const uint16_t*>(aText), length,
+ 0, length);
+
+ hb_shape(mHBFont, mBuffer, features.Elements(), features.Length());
+
+ if (isRightToLeft) {
+ hb_buffer_reverse(mBuffer);
+ }
+
+ nsresult rv = SetGlyphsFromRun(aShapedText, aOffset, aLength, aText,
+ aVertical, aRounding);
+
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "failed to store glyphs into gfxShapedWord");
+ hb_buffer_clear_contents(mBuffer);
+
+ return NS_SUCCEEDED(rv);
+}
+
+#define SMALL_GLYPH_RUN \
+ 128 // some testing indicates that 90%+ of text runs
+ // will fit without requiring separate allocation
+ // for charToGlyphArray
+
+nsresult gfxHarfBuzzShaper::SetGlyphsFromRun(gfxShapedText* aShapedText,
+ uint32_t aOffset, uint32_t aLength,
+ const char16_t* aText,
+ bool aVertical,
+ RoundingFlags aRounding) {
+ typedef gfxShapedText::CompressedGlyph CompressedGlyph;
+
+ uint32_t numGlyphs;
+ const hb_glyph_info_t* ginfo = hb_buffer_get_glyph_infos(mBuffer, &numGlyphs);
+ if (numGlyphs == 0) {
+ return NS_OK;
+ }
+
+ AutoTArray<gfxTextRun::DetailedGlyph, 1> detailedGlyphs;
+
+ uint32_t wordLength = aLength;
+ static const int32_t NO_GLYPH = -1;
+ AutoTArray<int32_t, SMALL_GLYPH_RUN> charToGlyphArray;
+ if (!charToGlyphArray.SetLength(wordLength, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ int32_t* charToGlyph = charToGlyphArray.Elements();
+ for (uint32_t offset = 0; offset < wordLength; ++offset) {
+ charToGlyph[offset] = NO_GLYPH;
+ }
+
+ for (uint32_t i = 0; i < numGlyphs; ++i) {
+ uint32_t loc = ginfo[i].cluster;
+ if (loc < wordLength) {
+ charToGlyph[loc] = i;
+ }
+ }
+
+ int32_t glyphStart = 0; // looking for a clump that starts at this glyph
+ int32_t charStart = 0; // and this char index within the range of the run
+
+ bool roundI, roundB;
+ if (aVertical) {
+ roundI = bool(aRounding & RoundingFlags::kRoundY);
+ roundB = bool(aRounding & RoundingFlags::kRoundX);
+ } else {
+ roundI = bool(aRounding & RoundingFlags::kRoundX);
+ roundB = bool(aRounding & RoundingFlags::kRoundY);
+ }
+
+ int32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit();
+ CompressedGlyph* charGlyphs = aShapedText->GetCharacterGlyphs() + aOffset;
+
+ // factor to convert 16.16 fixed-point pixels to app units
+ // (only used if not rounding)
+ double hb2appUnits = FixedToFloat(aShapedText->GetAppUnitsPerDevUnit());
+
+ // Residual from rounding of previous advance, for use in rounding the
+ // subsequent offset or advance appropriately. 16.16 fixed-point
+ //
+ // When rounding, the goal is to make the distance between glyphs and
+ // their base glyph equal to the integral number of pixels closest to that
+ // suggested by that shaper.
+ // i.e. posInfo[n].x_advance - posInfo[n].x_offset + posInfo[n+1].x_offset
+ //
+ // The value of the residual is the part of the desired distance that has
+ // not been included in integer offsets.
+ hb_position_t residual = 0;
+
+ // keep track of y-position to set glyph offsets if needed
+ nscoord bPos = 0;
+
+ const hb_glyph_position_t* posInfo =
+ hb_buffer_get_glyph_positions(mBuffer, nullptr);
+
+ while (glyphStart < int32_t(numGlyphs)) {
+ int32_t charEnd = ginfo[glyphStart].cluster;
+ int32_t glyphEnd = glyphStart;
+ int32_t charLimit = wordLength;
+ while (charEnd < charLimit) {
+ // This is normally executed once for each iteration of the outer loop,
+ // but in unusual cases where the character/glyph association is complex,
+ // the initial character range might correspond to a non-contiguous
+ // glyph range with "holes" in it. If so, we will repeat this loop to
+ // extend the character range until we have a contiguous glyph sequence.
+ charEnd += 1;
+ while (charEnd != charLimit && charToGlyph[charEnd] == NO_GLYPH) {
+ charEnd += 1;
+ }
+
+ // find the maximum glyph index covered by the clump so far
+ for (int32_t i = charStart; i < charEnd; ++i) {
+ if (charToGlyph[i] != NO_GLYPH) {
+ glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1);
+ // update extent of glyph range
+ }
+ }
+
+ if (glyphEnd == glyphStart + 1) {
+ // for the common case of a single-glyph clump,
+ // we can skip the following checks
+ break;
+ }
+
+ if (glyphEnd == glyphStart) {
+ // no glyphs, try to extend the clump
+ continue;
+ }
+
+ // check whether all glyphs in the range are associated with the
+ // characters in our clump; if not, we have a discontinuous range, and
+ // should extend it unless we've reached the end of the text
+ bool allGlyphsAreWithinCluster = true;
+ for (int32_t i = glyphStart; i < glyphEnd; ++i) {
+ int32_t glyphCharIndex = ginfo[i].cluster;
+ if (glyphCharIndex < charStart || glyphCharIndex >= charEnd) {
+ allGlyphsAreWithinCluster = false;
+ break;
+ }
+ }
+ if (allGlyphsAreWithinCluster) {
+ break;
+ }
+ }
+
+ NS_ASSERTION(glyphStart < glyphEnd,
+ "character/glyph clump contains no glyphs!");
+ NS_ASSERTION(charStart != charEnd,
+ "character/glyph clump contains no characters!");
+
+ // Now charStart..charEnd is a ligature clump, corresponding to
+ // glyphStart..glyphEnd; Set baseCharIndex to the char we'll actually attach
+ // the glyphs to (1st of ligature), and endCharIndex to the limit (position
+ // beyond the last char), adjusting for the offset of the stringRange
+ // relative to the textRun.
+ int32_t baseCharIndex, endCharIndex;
+ while (charEnd < int32_t(wordLength) && charToGlyph[charEnd] == NO_GLYPH)
+ charEnd++;
+ baseCharIndex = charStart;
+ endCharIndex = charEnd;
+
+ // Then we check if the clump falls outside our actual string range;
+ // if so, just go to the next.
+ if (baseCharIndex >= int32_t(wordLength)) {
+ glyphStart = glyphEnd;
+ charStart = charEnd;
+ continue;
+ }
+ // Ensure we won't try to go beyond the valid length of the textRun's text
+ endCharIndex = std::min<int32_t>(endCharIndex, wordLength);
+
+ // Now we're ready to set the glyph info in the textRun
+ int32_t glyphsInClump = glyphEnd - glyphStart;
+
+ // Check for default-ignorable char that didn't get filtered, combined,
+ // etc by the shaping process, and remove from the run.
+ // (This may be done within harfbuzz eventually.)
+ if (glyphsInClump == 1 && baseCharIndex + 1 == endCharIndex &&
+ aShapedText->FilterIfIgnorable(aOffset + baseCharIndex,
+ aText[baseCharIndex])) {
+ glyphStart = glyphEnd;
+ charStart = charEnd;
+ continue;
+ }
+
+ // HarfBuzz gives us physical x- and y-coordinates, but we will store
+ // them as logical inline- and block-direction values in the textrun.
+
+ hb_position_t i_offset, i_advance; // inline-direction offset/advance
+ hb_position_t b_offset, b_advance; // block-direction offset/advance
+ if (aVertical) {
+ // our coordinate directions are the opposite of harfbuzz's
+ // when doing top-to-bottom shaping
+ i_offset = -posInfo[glyphStart].y_offset;
+ i_advance = -posInfo[glyphStart].y_advance;
+ b_offset = -posInfo[glyphStart].x_offset;
+ b_advance = -posInfo[glyphStart].x_advance;
+ } else {
+ i_offset = posInfo[glyphStart].x_offset;
+ i_advance = posInfo[glyphStart].x_advance;
+ b_offset = posInfo[glyphStart].y_offset;
+ b_advance = posInfo[glyphStart].y_advance;
+ }
+
+ nscoord iOffset, advance;
+ if (roundI) {
+ iOffset = appUnitsPerDevUnit * FixedToIntRound(i_offset + residual);
+ // Desired distance from the base glyph to the next reference point.
+ hb_position_t width = i_advance - i_offset;
+ int intWidth = FixedToIntRound(width);
+ residual = width - FloatToFixed(intWidth);
+ advance = appUnitsPerDevUnit * intWidth + iOffset;
+ } else {
+ iOffset = floor(hb2appUnits * i_offset + 0.5);
+ advance = floor(hb2appUnits * i_advance + 0.5);
+ }
+ // Check if it's a simple one-to-one mapping
+ if (glyphsInClump == 1 &&
+ CompressedGlyph::IsSimpleGlyphID(ginfo[glyphStart].codepoint) &&
+ CompressedGlyph::IsSimpleAdvance(advance) &&
+ charGlyphs[baseCharIndex].IsClusterStart() && iOffset == 0 &&
+ b_offset == 0 && b_advance == 0 && bPos == 0) {
+ charGlyphs[baseCharIndex].SetSimpleGlyph(advance,
+ ginfo[glyphStart].codepoint);
+ } else {
+ // Collect all glyphs in a list to be assigned to the first char;
+ // there must be at least one in the clump, and we already measured
+ // its advance, hence the placement of the loop-exit test and the
+ // measurement of the next glyph.
+ while (1) {
+ gfxTextRun::DetailedGlyph* details = detailedGlyphs.AppendElement();
+ details->mGlyphID = ginfo[glyphStart].codepoint;
+
+ details->mAdvance = advance;
+
+ if (aVertical) {
+ details->mOffset.x =
+ bPos - (roundB ? appUnitsPerDevUnit * FixedToIntRound(b_offset)
+ : floor(hb2appUnits * b_offset + 0.5));
+ details->mOffset.y = iOffset;
+ } else {
+ details->mOffset.x = iOffset;
+ details->mOffset.y =
+ bPos - (roundB ? appUnitsPerDevUnit * FixedToIntRound(b_offset)
+ : floor(hb2appUnits * b_offset + 0.5));
+ }
+
+ if (b_advance != 0) {
+ bPos -= roundB ? appUnitsPerDevUnit * FixedToIntRound(b_advance)
+ : floor(hb2appUnits * b_advance + 0.5);
+ }
+ if (++glyphStart >= glyphEnd) {
+ break;
+ }
+
+ if (aVertical) {
+ i_offset = -posInfo[glyphStart].y_offset;
+ i_advance = -posInfo[glyphStart].y_advance;
+ b_offset = -posInfo[glyphStart].x_offset;
+ b_advance = -posInfo[glyphStart].x_advance;
+ } else {
+ i_offset = posInfo[glyphStart].x_offset;
+ i_advance = posInfo[glyphStart].x_advance;
+ b_offset = posInfo[glyphStart].y_offset;
+ b_advance = posInfo[glyphStart].y_advance;
+ }
+
+ if (roundI) {
+ iOffset = appUnitsPerDevUnit * FixedToIntRound(i_offset + residual);
+ // Desired distance to the next reference point. The
+ // residual is considered here, and includes the residual
+ // from the base glyph offset and subsequent advances, so
+ // that the distance from the base glyph is optimized
+ // rather than the distance from combining marks.
+ i_advance += residual;
+ int intAdvance = FixedToIntRound(i_advance);
+ residual = i_advance - FloatToFixed(intAdvance);
+ advance = appUnitsPerDevUnit * intAdvance;
+ } else {
+ iOffset = floor(hb2appUnits * i_offset + 0.5);
+ advance = floor(hb2appUnits * i_advance + 0.5);
+ }
+ }
+
+ aShapedText->SetDetailedGlyphs(aOffset + baseCharIndex,
+ detailedGlyphs.Length(),
+ detailedGlyphs.Elements());
+
+ detailedGlyphs.Clear();
+ }
+
+ // the rest of the chars in the group are ligature continuations,
+ // no associated glyphs
+ while (++baseCharIndex != endCharIndex &&
+ baseCharIndex < int32_t(wordLength)) {
+ CompressedGlyph& g = charGlyphs[baseCharIndex];
+ NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph");
+ g.SetComplex(g.IsClusterStart(), false);
+ }
+
+ glyphStart = glyphEnd;
+ charStart = charEnd;
+ }
+
+ return NS_OK;
+}
diff --git a/gfx/thebes/gfxHarfBuzzShaper.h b/gfx/thebes/gfxHarfBuzzShaper.h
new file mode 100644
index 0000000000..ef58d0eebd
--- /dev/null
+++ b/gfx/thebes/gfxHarfBuzzShaper.h
@@ -0,0 +1,203 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_HARFBUZZSHAPER_H
+#define GFX_HARFBUZZSHAPER_H
+
+#include "gfxFont.h"
+
+#include "harfbuzz/hb.h"
+#include "nsUnicodeProperties.h"
+#include "mozilla/gfx/2D.h"
+
+class gfxHarfBuzzShaper : public gfxFontShaper {
+ public:
+ explicit gfxHarfBuzzShaper(gfxFont* aFont);
+ virtual ~gfxHarfBuzzShaper();
+
+ /*
+ * For HarfBuzz font callback functions, font_data is a ptr to a
+ * FontCallbackData struct
+ */
+ struct FontCallbackData {
+ gfxHarfBuzzShaper* mShaper;
+ };
+
+ // Initializes the shaper and returns whether this was successful.
+ bool Initialize();
+
+ // Returns whether the shaper has been successfully initialized.
+ bool IsInitialized() const { return mHBFont != nullptr; }
+
+ bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText,
+ uint32_t aOffset, uint32_t aLength, Script aScript,
+ nsAtom* aLanguage, bool aVertical, RoundingFlags aRounding,
+ gfxShapedText* aShapedText) override;
+
+ // get a given font table in harfbuzz blob form
+ hb_blob_t* GetFontTable(hb_tag_t aTag) const;
+
+ // map unicode character to glyph ID
+ hb_codepoint_t GetNominalGlyph(hb_codepoint_t unicode) const;
+ hb_codepoint_t GetVariationGlyph(hb_codepoint_t unicode,
+ hb_codepoint_t variation_selector) const;
+
+ // get harfbuzz glyph advance, in font design units
+ hb_position_t GetGlyphHAdvance(hb_codepoint_t glyph) const;
+
+ // Get vertical glyph advance, or -1 if not available; caller should check
+ // for a negative result and provide a fallback or fail, as appropriate.
+ hb_position_t GetGlyphVAdvance(hb_codepoint_t glyph);
+
+ void GetGlyphVOrigin(hb_codepoint_t aGlyph, hb_position_t* aX,
+ hb_position_t* aY) const;
+
+ // get harfbuzz horizontal advance in 16.16 fixed point format.
+ static hb_position_t HBGetGlyphHAdvance(hb_font_t* font, void* font_data,
+ hb_codepoint_t glyph,
+ void* user_data);
+
+ // get harfbuzz vertical advance in 16.16 fixed point format.
+ static hb_position_t HBGetGlyphVAdvance(hb_font_t* font, void* font_data,
+ hb_codepoint_t glyph,
+ void* user_data);
+
+ static hb_bool_t HBGetGlyphVOrigin(hb_font_t* font, void* font_data,
+ hb_codepoint_t glyph, hb_position_t* x,
+ hb_position_t* y, void* user_data);
+
+ hb_position_t GetHKerning(uint16_t aFirstGlyph, uint16_t aSecondGlyph) const;
+
+ hb_bool_t GetGlyphExtents(hb_codepoint_t aGlyph,
+ hb_glyph_extents_t* aExtents) const;
+
+ bool UseVerticalPresentationForms() const {
+ return mUseVerticalPresentationForms;
+ }
+
+ static hb_script_t GetHBScriptUsedForShaping(Script aScript) {
+ // Decide what harfbuzz script code will be used for shaping
+ hb_script_t hbScript;
+ if (aScript <= Script::INHERITED) {
+ // For unresolved "common" or "inherited" runs,
+ // default to Latin for now.
+ hbScript = HB_SCRIPT_LATIN;
+ } else {
+ hbScript = hb_script_t(mozilla::unicode::GetScriptTagForCode(aScript));
+ }
+ return hbScript;
+ }
+
+ static hb_codepoint_t GetVerticalPresentationForm(hb_codepoint_t aUnicode);
+
+ // Create an hb_font corresponding to the given gfxFont instance, with size
+ // and variations set appropriately. If aFontFuncs and aCallbackData are
+ // provided, they may be used as harfbuzz font callbacks for advances, glyph
+ // bounds, etc; if not, the built-in hb_ot font functions will be used.
+ static hb_font_t* CreateHBFont(gfxFont* aFont,
+ hb_font_funcs_t* aFontFuncs = nullptr,
+ FontCallbackData* aCallbackData = nullptr);
+
+ hb_font_t* GetHBFont() const { return mHBFont; }
+ hb_face_t* GetHBFace() const { return hb_font_get_face(mHBFont); }
+
+ protected:
+ nsresult SetGlyphsFromRun(gfxShapedText* aShapedText, uint32_t aOffset,
+ uint32_t aLength, const char16_t* aText,
+ bool aVertical, RoundingFlags aRounding);
+
+ // retrieve glyph positions, applying advance adjustments and attachments
+ // returns results in appUnits
+ nscoord GetGlyphPositions(gfxContext* aContext, nsTArray<nsPoint>& aPositions,
+ uint32_t aAppUnitsPerDevUnit);
+
+ void InitializeVertical();
+ bool LoadHmtxTable();
+
+ struct Glyf { // we only need the bounding-box at the beginning
+ // of the glyph record, not the actual outline data
+ mozilla::AutoSwap_PRInt16 numberOfContours;
+ mozilla::AutoSwap_PRInt16 xMin;
+ mozilla::AutoSwap_PRInt16 yMin;
+ mozilla::AutoSwap_PRInt16 xMax;
+ mozilla::AutoSwap_PRInt16 yMax;
+ };
+
+ const Glyf* FindGlyf(hb_codepoint_t aGlyph, bool* aEmptyGlyf) const;
+
+ // size-specific font object, owned by the gfxHarfBuzzShaper
+ hb_font_t* mHBFont;
+
+ // harfbuzz buffer for the shaping process
+ hb_buffer_t* mBuffer;
+
+ FontCallbackData mCallbackData;
+
+ // Following table references etc are declared "mutable" because the
+ // harfbuzz callback functions take a const ptr to the shaper, but
+ // wish to cache tables here to avoid repeatedly looking them up
+ // in the font.
+
+ // Old-style TrueType kern table, if we're not doing GPOS kerning
+ mutable hb_blob_t* mKernTable;
+
+ // Cached copy of the hmtx table.
+ mutable hb_blob_t* mHmtxTable;
+
+ // For vertical fonts, cached vmtx and VORG table, if present.
+ mutable hb_blob_t* mVmtxTable;
+ mutable hb_blob_t* mVORGTable;
+ // And for vertical TrueType (not CFF) fonts that have vmtx,
+ // we also use loca and glyf to get glyph bounding boxes.
+ mutable hb_blob_t* mLocaTable;
+ mutable hb_blob_t* mGlyfTable;
+
+ // Cached pointer to cmap subtable to be used for char-to-glyph mapping.
+ // This comes from GetFontTablePtr; if it is non-null, our destructor
+ // must call ReleaseFontTablePtr to avoid permanently caching the table.
+ mutable hb_blob_t* mCmapTable;
+ mutable int32_t mCmapFormat;
+ mutable uint32_t mSubtableOffset;
+ mutable uint32_t mUVSTableOffset;
+
+ // Cached copy of numLongMetrics field from the hhea table,
+ // for use when looking up glyph metrics; initialized to 0 by the
+ // constructor so we can tell it hasn't been set yet.
+ // This is a signed value so that we can use -1 to indicate
+ // an error (if the hhea table was not available).
+ mutable int32_t mNumLongHMetrics;
+ // Similarly for vhea if it's a vertical font.
+ mutable int32_t mNumLongVMetrics;
+
+ // Default y-coordinate for glyph vertical origin, used if the font
+ // does not actually have vertical-layout metrics.
+ mutable gfxFloat mDefaultVOrg;
+
+ // Whether the font implements GetGlyph, or we should read tables
+ // directly
+ bool mUseFontGetGlyph;
+
+ // Whether the font is an MS Symbol-encoded font, in which case we will
+ // try remapping U+0020..00FF to U+F020..F0FF for characters in the U+00xx
+ // range that are otherwise unsupported.
+ bool mIsSymbolFont;
+
+ // Whether the font implements GetGlyphWidth, or we should read tables
+ // directly to get ideal widths
+ bool mUseFontGlyphWidths;
+
+ bool mInitialized;
+ bool mVerticalInitialized;
+
+ // Whether to use vertical presentation forms for CJK characters
+ // when available (only set if the 'vert' feature is not available).
+ bool mUseVerticalPresentationForms;
+
+ // these are set from the FindGlyf callback on first use of the glyf data
+ mutable bool mLoadedLocaGlyf;
+ mutable bool mLocaLongOffsets;
+};
+
+#endif /* GFX_HARFBUZZSHAPER_H */
diff --git a/gfx/thebes/gfxImageSurface.cpp b/gfx/thebes/gfxImageSurface.cpp
new file mode 100644
index 0000000000..3cd285dac7
--- /dev/null
+++ b/gfx/thebes/gfxImageSurface.cpp
@@ -0,0 +1,326 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/MemoryReporting.h"
+#if defined(HAVE_POSIX_MEMALIGN)
+# include "gfxAlphaRecovery.h"
+#endif
+#include "gfxImageSurface.h"
+
+#include "cairo.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/HelpersCairo.h"
+#include "gfx2DGlue.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+gfxImageSurface::gfxImageSurface()
+ : mSize(0, 0),
+ mOwnsData(false),
+ mData(nullptr),
+ mFormat(SurfaceFormat::UNKNOWN),
+ mStride(0) {}
+
+void gfxImageSurface::InitFromSurface(cairo_surface_t* csurf) {
+ if (!csurf || cairo_surface_status(csurf)) {
+ MakeInvalid();
+ return;
+ }
+
+ mSize.width = cairo_image_surface_get_width(csurf);
+ mSize.height = cairo_image_surface_get_height(csurf);
+ mData = cairo_image_surface_get_data(csurf);
+ mFormat = CairoFormatToGfxFormat(cairo_image_surface_get_format(csurf));
+ mOwnsData = false;
+ mStride = cairo_image_surface_get_stride(csurf);
+
+ Init(csurf, true);
+}
+
+gfxImageSurface::gfxImageSurface(unsigned char* aData, const IntSize& aSize,
+ long aStride, gfxImageFormat aFormat) {
+ InitWithData(aData, aSize, aStride, aFormat);
+}
+
+void gfxImageSurface::MakeInvalid() {
+ mSize = IntSize(-1, -1);
+ mData = nullptr;
+ mStride = 0;
+}
+
+void gfxImageSurface::InitWithData(unsigned char* aData, const IntSize& aSize,
+ long aStride, gfxImageFormat aFormat) {
+ mSize = aSize;
+ mOwnsData = false;
+ mData = aData;
+ mFormat = aFormat;
+ mStride = aStride;
+
+ if (!Factory::CheckSurfaceSize(aSize)) MakeInvalid();
+
+ cairo_format_t cformat = GfxFormatToCairoFormat(mFormat);
+ cairo_surface_t* surface = cairo_image_surface_create_for_data(
+ (unsigned char*)mData, cformat, mSize.width, mSize.height, mStride);
+
+ // cairo_image_surface_create_for_data can return a 'null' surface
+ // in out of memory conditions. The gfxASurface::Init call checks
+ // the surface it receives to see if there is an error with the
+ // surface and handles it appropriately. That is why there is
+ // no check here.
+ Init(surface);
+}
+
+static void* TryAllocAlignedBytes(size_t aSize) {
+ // Use fallible allocators here
+#if defined(HAVE_POSIX_MEMALIGN)
+ void* ptr;
+ // Try to align for fast alpha recovery. This should only help
+ // cairo too, can't hurt.
+ return posix_memalign(&ptr, 1 << gfxAlphaRecovery::GoodAlignmentLog2(), aSize)
+ ? nullptr
+ : ptr;
+#else
+ // Oh well, hope that luck is with us in the allocator
+ return malloc(aSize);
+#endif
+}
+
+gfxImageSurface::gfxImageSurface(const IntSize& size, gfxImageFormat format,
+ bool aClear)
+ : mSize(size), mData(nullptr), mFormat(format) {
+ AllocateAndInit(0, 0, aClear);
+}
+
+void gfxImageSurface::AllocateAndInit(long aStride, int32_t aMinimalAllocation,
+ bool aClear) {
+ // The callers should set mSize and mFormat.
+ MOZ_ASSERT(!mData);
+ mData = nullptr;
+ mOwnsData = false;
+
+ mStride = aStride > 0 ? aStride : ComputeStride();
+ if (aMinimalAllocation < mSize.height * mStride)
+ aMinimalAllocation = mSize.height * mStride;
+
+ if (!Factory::CheckSurfaceSize(mSize)) MakeInvalid();
+
+ // if we have a zero-sized surface, just leave mData nullptr
+ if (mSize.height * mStride > 0) {
+ // This can fail to allocate memory aligned as we requested,
+ // or it can fail to allocate any memory at all.
+ mData = (unsigned char*)TryAllocAlignedBytes(aMinimalAllocation);
+ if (!mData) return;
+ if (aClear) memset(mData, 0, aMinimalAllocation);
+ }
+
+ mOwnsData = true;
+
+ cairo_format_t cformat = GfxFormatToCairoFormat(mFormat);
+ cairo_surface_t* surface = cairo_image_surface_create_for_data(
+ (unsigned char*)mData, cformat, mSize.width, mSize.height, mStride);
+
+ Init(surface);
+
+ if (mSurfaceValid) {
+ RecordMemoryUsed(mSize.height * ComputeStride() + sizeof(gfxImageSurface));
+ }
+}
+
+gfxImageSurface::gfxImageSurface(const IntSize& size, gfxImageFormat format,
+ long aStride, int32_t aExtraBytes, bool aClear)
+ : mSize(size), mData(nullptr), mFormat(format) {
+ AllocateAndInit(aStride, aExtraBytes, aClear);
+}
+
+gfxImageSurface::gfxImageSurface(cairo_surface_t* csurf) {
+ mSize.width = cairo_image_surface_get_width(csurf);
+ mSize.height = cairo_image_surface_get_height(csurf);
+ mData = cairo_image_surface_get_data(csurf);
+ mFormat = CairoFormatToGfxFormat(cairo_image_surface_get_format(csurf));
+ mOwnsData = false;
+ mStride = cairo_image_surface_get_stride(csurf);
+
+ Init(csurf, true);
+}
+
+gfxImageSurface::~gfxImageSurface() {
+ if (mOwnsData) free(mData);
+}
+
+/*static*/
+long gfxImageSurface::ComputeStride(const IntSize& aSize,
+ gfxImageFormat aFormat) {
+ long stride;
+
+ if (aFormat == SurfaceFormat::A8R8G8B8_UINT32)
+ stride = aSize.width * 4;
+ else if (aFormat == SurfaceFormat::X8R8G8B8_UINT32)
+ stride = aSize.width * 4;
+ else if (aFormat == SurfaceFormat::R5G6B5_UINT16)
+ stride = aSize.width * 2;
+ else if (aFormat == SurfaceFormat::A8)
+ stride = aSize.width;
+ else {
+ NS_WARNING("Unknown format specified to gfxImageSurface!");
+ stride = aSize.width * 4;
+ }
+
+ stride = ((stride + 3) / 4) * 4;
+
+ return stride;
+}
+
+size_t gfxImageSurface::SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t n = gfxASurface::SizeOfExcludingThis(aMallocSizeOf);
+ if (mOwnsData) {
+ n += aMallocSizeOf(mData);
+ }
+ return n;
+}
+
+size_t gfxImageSurface::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+bool gfxImageSurface::SizeOfIsMeasured() const { return true; }
+
+// helper function for the CopyFrom methods
+static void CopyForStride(unsigned char* aDest, unsigned char* aSrc,
+ const IntSize& aSize, long aDestStride,
+ long aSrcStride) {
+ if (aDestStride == aSrcStride) {
+ memcpy(aDest, aSrc, aSrcStride * aSize.height);
+ } else {
+ int lineSize = std::min(aDestStride, aSrcStride);
+ for (int i = 0; i < aSize.height; i++) {
+ unsigned char* src = aSrc + aSrcStride * i;
+ unsigned char* dst = aDest + aDestStride * i;
+
+ memcpy(dst, src, lineSize);
+ }
+ }
+}
+
+// helper function for the CopyFrom methods
+static bool FormatsAreCompatible(gfxImageFormat a1, gfxImageFormat a2) {
+ if (a1 != a2 &&
+ !(a1 == SurfaceFormat::A8R8G8B8_UINT32 &&
+ a2 == SurfaceFormat::X8R8G8B8_UINT32) &&
+ !(a1 == SurfaceFormat::X8R8G8B8_UINT32 &&
+ a2 == SurfaceFormat::A8R8G8B8_UINT32)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool gfxImageSurface::CopyFrom(SourceSurface* aSurface) {
+ RefPtr<DataSourceSurface> data = aSurface->GetDataSurface();
+
+ if (!data) {
+ return false;
+ }
+
+ IntSize size(data->GetSize().width, data->GetSize().height);
+ if (size != mSize) {
+ return false;
+ }
+
+ if (!FormatsAreCompatible(SurfaceFormatToImageFormat(aSurface->GetFormat()),
+ mFormat)) {
+ return false;
+ }
+
+ DataSourceSurface::ScopedMap map(data, DataSourceSurface::READ);
+ CopyForStride(mData, map.GetData(), size, mStride, map.GetStride());
+
+ return true;
+}
+
+bool gfxImageSurface::CopyFrom(gfxImageSurface* other) {
+ if (other->mSize != mSize) {
+ return false;
+ }
+
+ if (!FormatsAreCompatible(other->mFormat, mFormat)) {
+ return false;
+ }
+
+ CopyForStride(mData, other->mData, mSize, mStride, other->mStride);
+
+ return true;
+}
+
+bool gfxImageSurface::CopyTo(SourceSurface* aSurface) {
+ RefPtr<DataSourceSurface> data = aSurface->GetDataSurface();
+
+ if (!data) {
+ return false;
+ }
+
+ IntSize size(data->GetSize().width, data->GetSize().height);
+ if (size != mSize) {
+ return false;
+ }
+
+ if (!FormatsAreCompatible(SurfaceFormatToImageFormat(aSurface->GetFormat()),
+ mFormat)) {
+ return false;
+ }
+
+ DataSourceSurface::ScopedMap map(data, DataSourceSurface::READ_WRITE);
+ CopyForStride(map.GetData(), mData, size, map.GetStride(), mStride);
+
+ return true;
+}
+
+already_AddRefed<DataSourceSurface>
+gfxImageSurface::CopyToB8G8R8A8DataSourceSurface() {
+ RefPtr<DataSourceSurface> dataSurface = Factory::CreateDataSourceSurface(
+ IntSize(GetSize().width, GetSize().height), SurfaceFormat::B8G8R8A8);
+ if (dataSurface) {
+ CopyTo(dataSurface);
+ }
+ return dataSurface.forget();
+}
+
+already_AddRefed<gfxSubimageSurface> gfxImageSurface::GetSubimage(
+ const gfxRect& aRect) {
+ gfxRect r(aRect);
+ r.Round();
+ MOZ_ASSERT(gfxRect(0, 0, mSize.width, mSize.height).Contains(r));
+
+ gfxImageFormat format = Format();
+
+ unsigned char* subData =
+ Data() + (Stride() * (int)r.Y()) +
+ (int)r.X() * gfxASurface::BytePerPixelFromFormat(Format());
+
+ if (format == SurfaceFormat::A8R8G8B8_UINT32 &&
+ GetOpaqueRect().Contains(aRect)) {
+ format = SurfaceFormat::X8R8G8B8_UINT32;
+ }
+
+ RefPtr<gfxSubimageSurface> image = new gfxSubimageSurface(
+ this, subData, IntSize((int)r.Width(), (int)r.Height()), format);
+
+ return image.forget();
+}
+
+gfxSubimageSurface::gfxSubimageSurface(gfxImageSurface* aParent,
+ unsigned char* aData,
+ const IntSize& aSize,
+ gfxImageFormat aFormat)
+ : gfxImageSurface(aData, aSize, aParent->Stride(), aFormat),
+ mParent(aParent) {}
+
+already_AddRefed<gfxImageSurface> gfxImageSurface::GetAsImageSurface() {
+ RefPtr<gfxImageSurface> surface = this;
+ return surface.forget();
+}
diff --git a/gfx/thebes/gfxImageSurface.h b/gfx/thebes/gfxImageSurface.h
new file mode 100644
index 0000000000..eefc233738
--- /dev/null
+++ b/gfx/thebes/gfxImageSurface.h
@@ -0,0 +1,194 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_IMAGESURFACE_H
+#define GFX_IMAGESURFACE_H
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/RefPtr.h"
+#include "gfxASurface.h"
+#include "nsSize.h"
+
+// ARGB -- raw buffer.. wont be changed.. good for storing data.
+
+class gfxSubimageSurface;
+
+namespace mozilla {
+namespace gfx {
+class DataSourceSurface;
+class SourceSurface;
+} // namespace gfx
+} // namespace mozilla
+
+/**
+ * A raw image buffer. The format can be set in the constructor. Its main
+ * purpose is for storing read-only images and using it as a source surface,
+ * but it can also be drawn to.
+ */
+class gfxImageSurface : public gfxASurface {
+ public:
+ /**
+ * Construct an image surface around an existing buffer of image data.
+ * @param aData A buffer containing the image data
+ * @param aSize The size of the buffer
+ * @param aStride The stride of the buffer
+ * @param format Format of the data
+ *
+ * @see gfxImageFormat
+ */
+ gfxImageSurface(unsigned char* aData, const mozilla::gfx::IntSize& aSize,
+ long aStride, gfxImageFormat aFormat);
+
+ /**
+ * Construct an image surface.
+ * @param aSize The size of the buffer
+ * @param format Format of the data
+ *
+ * @see gfxImageFormat
+ */
+ gfxImageSurface(const mozilla::gfx::IntSize& size, gfxImageFormat format,
+ bool aClear = true);
+
+ /**
+ * Construct an image surface, with a specified stride and allowing the
+ * allocation of more memory than required for the storage of the surface
+ * itself. When aStride and aMinimalAllocation are <=0, this constructor
+ * is the equivalent of the preceeding one.
+ *
+ * @param format Format of the data
+ * @param aSize The size of the buffer
+ * @param aStride The stride of the buffer - if <=0, use ComputeStride()
+ * @param aMinimalAllocation Allocate at least this many bytes. If smaller
+ * than width * stride, or width*stride <=0, this value is ignored.
+ * @param aClear
+ *
+ * @see gfxImageFormat
+ */
+ gfxImageSurface(const mozilla::gfx::IntSize& aSize, gfxImageFormat aFormat,
+ long aStride, int32_t aMinimalAllocation, bool aClear);
+
+ explicit gfxImageSurface(cairo_surface_t* csurf);
+
+ virtual ~gfxImageSurface();
+
+ // ImageSurface methods
+ gfxImageFormat Format() const { return mFormat; }
+
+ virtual const mozilla::gfx::IntSize GetSize() const override { return mSize; }
+ int32_t Width() const {
+ if (mSize.width < 0) {
+ return 0;
+ }
+ return mSize.width;
+ }
+ int32_t Height() const {
+ if (mSize.height < 0) {
+ return 0;
+ }
+ return mSize.height;
+ }
+
+ /**
+ * Distance in bytes between the start of a line and the start of the
+ * next line.
+ */
+ int32_t Stride() const { return mStride; }
+ /**
+ * Returns a pointer for the image data. Users of this function can
+ * write to it, but must not attempt to free the buffer.
+ */
+ unsigned char* Data() const {
+ return mData;
+ } // delete this data under us and die.
+ /**
+ * Returns the total size of the image data.
+ */
+ int32_t GetDataSize() const {
+ if (mStride < 0 || mSize.height < 0) {
+ return 0;
+ }
+ return mStride * mSize.height;
+ }
+
+ /* Fast copy from another image surface; returns TRUE if successful, FALSE
+ * otherwise */
+ bool CopyFrom(gfxImageSurface* other);
+
+ /**
+ * Fast copy from a source surface; returns TRUE if successful, FALSE
+ * otherwise Assumes that the format of this surface is compatable with
+ * aSurface
+ */
+ bool CopyFrom(mozilla::gfx::SourceSurface* aSurface);
+
+ /**
+ * Fast copy to a source surface; returns TRUE if successful, FALSE otherwise
+ * Assumes that the format of this surface is compatible with aSurface
+ */
+ bool CopyTo(mozilla::gfx::SourceSurface* aSurface);
+
+ /**
+ * Copy to a Moz2D DataSourceSurface.
+ * Marked as virtual so that browsercomps can access this method.
+ */
+ virtual already_AddRefed<mozilla::gfx::DataSourceSurface>
+ CopyToB8G8R8A8DataSourceSurface();
+
+ /* return new Subimage with pointing to original image starting from aRect.pos
+ * and size of aRect.size. New subimage keeping current image reference
+ */
+ already_AddRefed<gfxSubimageSurface> GetSubimage(const gfxRect& aRect);
+
+ virtual already_AddRefed<gfxImageSurface> GetAsImageSurface() override;
+
+ /** See gfxASurface.h. */
+ static long ComputeStride(const mozilla::gfx::IntSize&, gfxImageFormat);
+
+ virtual size_t SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const override;
+ virtual size_t SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const override;
+ virtual bool SizeOfIsMeasured() const override;
+
+ protected:
+ gfxImageSurface();
+ void InitWithData(unsigned char* aData, const mozilla::gfx::IntSize& aSize,
+ long aStride, gfxImageFormat aFormat);
+ /**
+ * See the parameters to the matching constructor. This should only
+ * be called once, in the constructor, which has already set mSize
+ * and mFormat.
+ */
+ void AllocateAndInit(long aStride, int32_t aMinimalAllocation, bool aClear);
+ void InitFromSurface(cairo_surface_t* csurf);
+
+ long ComputeStride() const {
+ if (mSize.height < 0 || mSize.width < 0) {
+ return 0;
+ }
+ return ComputeStride(mSize, mFormat);
+ }
+
+ void MakeInvalid();
+
+ mozilla::gfx::IntSize mSize;
+ bool mOwnsData;
+ unsigned char* mData;
+ gfxImageFormat mFormat;
+ long mStride;
+};
+
+class gfxSubimageSurface : public gfxImageSurface {
+ protected:
+ friend class gfxImageSurface;
+ gfxSubimageSurface(gfxImageSurface* aParent, unsigned char* aData,
+ const mozilla::gfx::IntSize& aSize,
+ gfxImageFormat aFormat);
+
+ private:
+ RefPtr<gfxImageSurface> mParent;
+};
+
+#endif /* GFX_IMAGESURFACE_H */
diff --git a/gfx/thebes/gfxLanguageTagList.cpp b/gfx/thebes/gfxLanguageTagList.cpp
new file mode 100644
index 0000000000..0994fc1a17
--- /dev/null
+++ b/gfx/thebes/gfxLanguageTagList.cpp
@@ -0,0 +1,7901 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Derived from the IANA language subtag registry by genLanguageTagList.pl.
+ *
+ * Created on Mon Nov 7 14:52:44 2011.
+ *
+ * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * *
+ */
+
+// Based on IANA registry dated 2011-08-25
+
+static const uint32_t sLanguageTagList[] = {
+ TRUETYPE_TAG('a', 'a', 0, 0), // aa = Afar
+ TRUETYPE_TAG('a', 'b', 0, 0), // ab = Abkhazian
+ TRUETYPE_TAG('a', 'e', 0, 0), // ae = Avestan
+ TRUETYPE_TAG('a', 'f', 0, 0), // af = Afrikaans
+ TRUETYPE_TAG('a', 'k', 0, 0), // ak = Akan
+ TRUETYPE_TAG('a', 'm', 0, 0), // am = Amharic
+ TRUETYPE_TAG('a', 'n', 0, 0), // an = Aragonese
+ TRUETYPE_TAG('a', 'r', 0, 0), // ar = Arabic
+ TRUETYPE_TAG('a', 's', 0, 0), // as = Assamese
+ TRUETYPE_TAG('a', 'v', 0, 0), // av = Avaric
+ TRUETYPE_TAG('a', 'y', 0, 0), // ay = Aymara
+ TRUETYPE_TAG('a', 'z', 0, 0), // az = Azerbaijani
+ TRUETYPE_TAG('b', 'a', 0, 0), // ba = Bashkir
+ TRUETYPE_TAG('b', 'e', 0, 0), // be = Belarusian
+ TRUETYPE_TAG('b', 'g', 0, 0), // bg = Bulgarian
+ TRUETYPE_TAG('b', 'h', 0, 0), // bh = Bihari languages
+ TRUETYPE_TAG('b', 'i', 0, 0), // bi = Bislama
+ TRUETYPE_TAG('b', 'm', 0, 0), // bm = Bambara
+ TRUETYPE_TAG('b', 'n', 0, 0), // bn = Bengali
+ TRUETYPE_TAG('b', 'o', 0, 0), // bo = Tibetan
+ TRUETYPE_TAG('b', 'r', 0, 0), // br = Breton
+ TRUETYPE_TAG('b', 's', 0, 0), // bs = Bosnian
+ TRUETYPE_TAG('c', 'a', 0, 0), // ca = Catalan
+ TRUETYPE_TAG('c', 'e', 0, 0), // ce = Chechen
+ TRUETYPE_TAG('c', 'h', 0, 0), // ch = Chamorro
+ TRUETYPE_TAG('c', 'o', 0, 0), // co = Corsican
+ TRUETYPE_TAG('c', 'r', 0, 0), // cr = Cree
+ TRUETYPE_TAG('c', 's', 0, 0), // cs = Czech
+ TRUETYPE_TAG('c', 'u', 0, 0), // cu = Church Slavic
+ TRUETYPE_TAG('c', 'v', 0, 0), // cv = Chuvash
+ TRUETYPE_TAG('c', 'y', 0, 0), // cy = Welsh
+ TRUETYPE_TAG('d', 'a', 0, 0), // da = Danish
+ TRUETYPE_TAG('d', 'e', 0, 0), // de = German
+ TRUETYPE_TAG('d', 'v', 0, 0), // dv = Dhivehi
+ TRUETYPE_TAG('d', 'z', 0, 0), // dz = Dzongkha
+ TRUETYPE_TAG('e', 'e', 0, 0), // ee = Ewe
+ TRUETYPE_TAG('e', 'l', 0, 0), // el = Modern Greek (1453-)
+ TRUETYPE_TAG('e', 'n', 0, 0), // en = English
+ TRUETYPE_TAG('e', 'o', 0, 0), // eo = Esperanto
+ TRUETYPE_TAG('e', 's', 0, 0), // es = Spanish
+ TRUETYPE_TAG('e', 't', 0, 0), // et = Estonian
+ TRUETYPE_TAG('e', 'u', 0, 0), // eu = Basque
+ TRUETYPE_TAG('f', 'a', 0, 0), // fa = Persian
+ TRUETYPE_TAG('f', 'f', 0, 0), // ff = Fulah
+ TRUETYPE_TAG('f', 'i', 0, 0), // fi = Finnish
+ TRUETYPE_TAG('f', 'j', 0, 0), // fj = Fijian
+ TRUETYPE_TAG('f', 'o', 0, 0), // fo = Faroese
+ TRUETYPE_TAG('f', 'r', 0, 0), // fr = French
+ TRUETYPE_TAG('f', 'y', 0, 0), // fy = Western Frisian
+ TRUETYPE_TAG('g', 'a', 0, 0), // ga = Irish
+ TRUETYPE_TAG('g', 'd', 0, 0), // gd = Scottish Gaelic
+ TRUETYPE_TAG('g', 'l', 0, 0), // gl = Galician
+ TRUETYPE_TAG('g', 'n', 0, 0), // gn = Guarani
+ TRUETYPE_TAG('g', 'u', 0, 0), // gu = Gujarati
+ TRUETYPE_TAG('g', 'v', 0, 0), // gv = Manx
+ TRUETYPE_TAG('h', 'a', 0, 0), // ha = Hausa
+ TRUETYPE_TAG('h', 'e', 0, 0), // he = Hebrew
+ TRUETYPE_TAG('h', 'i', 0, 0), // hi = Hindi
+ TRUETYPE_TAG('h', 'o', 0, 0), // ho = Hiri Motu
+ TRUETYPE_TAG('h', 'r', 0, 0), // hr = Croatian
+ TRUETYPE_TAG('h', 't', 0, 0), // ht = Haitian
+ TRUETYPE_TAG('h', 'u', 0, 0), // hu = Hungarian
+ TRUETYPE_TAG('h', 'y', 0, 0), // hy = Armenian
+ TRUETYPE_TAG('h', 'z', 0, 0), // hz = Herero
+ TRUETYPE_TAG('i', 'a', 0,
+ 0), // ia = Interlingua (International Auxiliary Language
+ TRUETYPE_TAG('i', 'd', 0, 0), // id = Indonesian
+ TRUETYPE_TAG('i', 'e', 0, 0), // ie = Interlingue
+ TRUETYPE_TAG('i', 'g', 0, 0), // ig = Igbo
+ TRUETYPE_TAG('i', 'i', 0, 0), // ii = Sichuan Yi
+ TRUETYPE_TAG('i', 'k', 0, 0), // ik = Inupiaq
+ TRUETYPE_TAG('i', 'n', 0, 0), // in = Indonesian
+ TRUETYPE_TAG('i', 'o', 0, 0), // io = Ido
+ TRUETYPE_TAG('i', 's', 0, 0), // is = Icelandic
+ TRUETYPE_TAG('i', 't', 0, 0), // it = Italian
+ TRUETYPE_TAG('i', 'u', 0, 0), // iu = Inuktitut
+ TRUETYPE_TAG('i', 'w', 0, 0), // iw = Hebrew
+ TRUETYPE_TAG('j', 'a', 0, 0), // ja = Japanese
+ TRUETYPE_TAG('j', 'i', 0, 0), // ji = Yiddish
+ TRUETYPE_TAG('j', 'v', 0, 0), // jv = Javanese
+ TRUETYPE_TAG('j', 'w', 0, 0), // jw = Javanese
+ TRUETYPE_TAG('k', 'a', 0, 0), // ka = Georgian
+ TRUETYPE_TAG('k', 'g', 0, 0), // kg = Kongo
+ TRUETYPE_TAG('k', 'i', 0, 0), // ki = Kikuyu
+ TRUETYPE_TAG('k', 'j', 0, 0), // kj = Kuanyama
+ TRUETYPE_TAG('k', 'k', 0, 0), // kk = Kazakh
+ TRUETYPE_TAG('k', 'l', 0, 0), // kl = Kalaallisut
+ TRUETYPE_TAG('k', 'm', 0, 0), // km = Central Khmer
+ TRUETYPE_TAG('k', 'n', 0, 0), // kn = Kannada
+ TRUETYPE_TAG('k', 'o', 0, 0), // ko = Korean
+ TRUETYPE_TAG('k', 'r', 0, 0), // kr = Kanuri
+ TRUETYPE_TAG('k', 's', 0, 0), // ks = Kashmiri
+ TRUETYPE_TAG('k', 'u', 0, 0), // ku = Kurdish
+ TRUETYPE_TAG('k', 'v', 0, 0), // kv = Komi
+ TRUETYPE_TAG('k', 'w', 0, 0), // kw = Cornish
+ TRUETYPE_TAG('k', 'y', 0, 0), // ky = Kirghiz
+ TRUETYPE_TAG('l', 'a', 0, 0), // la = Latin
+ TRUETYPE_TAG('l', 'b', 0, 0), // lb = Luxembourgish
+ TRUETYPE_TAG('l', 'g', 0, 0), // lg = Ganda
+ TRUETYPE_TAG('l', 'i', 0, 0), // li = Limburgan
+ TRUETYPE_TAG('l', 'n', 0, 0), // ln = Lingala
+ TRUETYPE_TAG('l', 'o', 0, 0), // lo = Lao
+ TRUETYPE_TAG('l', 't', 0, 0), // lt = Lithuanian
+ TRUETYPE_TAG('l', 'u', 0, 0), // lu = Luba-Katanga
+ TRUETYPE_TAG('l', 'v', 0, 0), // lv = Latvian
+ TRUETYPE_TAG('m', 'g', 0, 0), // mg = Malagasy
+ TRUETYPE_TAG('m', 'h', 0, 0), // mh = Marshallese
+ TRUETYPE_TAG('m', 'i', 0, 0), // mi = Maori
+ TRUETYPE_TAG('m', 'k', 0, 0), // mk = Macedonian
+ TRUETYPE_TAG('m', 'l', 0, 0), // ml = Malayalam
+ TRUETYPE_TAG('m', 'n', 0, 0), // mn = Mongolian
+ TRUETYPE_TAG('m', 'o', 0, 0), // mo = Moldavian
+ TRUETYPE_TAG('m', 'r', 0, 0), // mr = Marathi
+ TRUETYPE_TAG('m', 's', 0, 0), // ms = Malay (macrolanguage)
+ TRUETYPE_TAG('m', 't', 0, 0), // mt = Maltese
+ TRUETYPE_TAG('m', 'y', 0, 0), // my = Burmese
+ TRUETYPE_TAG('n', 'a', 0, 0), // na = Nauru
+ TRUETYPE_TAG('n', 'b', 0, 0), // nb = Norwegian Bokmål
+ TRUETYPE_TAG('n', 'd', 0, 0), // nd = North Ndebele
+ TRUETYPE_TAG('n', 'e', 0, 0), // ne = Nepali
+ TRUETYPE_TAG('n', 'g', 0, 0), // ng = Ndonga
+ TRUETYPE_TAG('n', 'l', 0, 0), // nl = Dutch
+ TRUETYPE_TAG('n', 'n', 0, 0), // nn = Norwegian Nynorsk
+ TRUETYPE_TAG('n', 'o', 0, 0), // no = Norwegian
+ TRUETYPE_TAG('n', 'r', 0, 0), // nr = South Ndebele
+ TRUETYPE_TAG('n', 'v', 0, 0), // nv = Navajo
+ TRUETYPE_TAG('n', 'y', 0, 0), // ny = Nyanja
+ TRUETYPE_TAG('o', 'c', 0, 0), // oc = Occitan (post 1500)
+ TRUETYPE_TAG('o', 'j', 0, 0), // oj = Ojibwa
+ TRUETYPE_TAG('o', 'm', 0, 0), // om = Oromo
+ TRUETYPE_TAG('o', 'r', 0, 0), // or = Oriya
+ TRUETYPE_TAG('o', 's', 0, 0), // os = Ossetian
+ TRUETYPE_TAG('p', 'a', 0, 0), // pa = Panjabi
+ TRUETYPE_TAG('p', 'i', 0, 0), // pi = Pali
+ TRUETYPE_TAG('p', 'l', 0, 0), // pl = Polish
+ TRUETYPE_TAG('p', 's', 0, 0), // ps = Pushto
+ TRUETYPE_TAG('p', 't', 0, 0), // pt = Portuguese
+ TRUETYPE_TAG('q', 'u', 0, 0), // qu = Quechua
+ TRUETYPE_TAG('r', 'm', 0, 0), // rm = Romansh
+ TRUETYPE_TAG('r', 'n', 0, 0), // rn = Rundi
+ TRUETYPE_TAG('r', 'o', 0, 0), // ro = Romanian
+ TRUETYPE_TAG('r', 'u', 0, 0), // ru = Russian
+ TRUETYPE_TAG('r', 'w', 0, 0), // rw = Kinyarwanda
+ TRUETYPE_TAG('s', 'a', 0, 0), // sa = Sanskrit
+ TRUETYPE_TAG('s', 'c', 0, 0), // sc = Sardinian
+ TRUETYPE_TAG('s', 'd', 0, 0), // sd = Sindhi
+ TRUETYPE_TAG('s', 'e', 0, 0), // se = Northern Sami
+ TRUETYPE_TAG('s', 'g', 0, 0), // sg = Sango
+ TRUETYPE_TAG('s', 'h', 0, 0), // sh = Serbo-Croatian
+ TRUETYPE_TAG('s', 'i', 0, 0), // si = Sinhala
+ TRUETYPE_TAG('s', 'k', 0, 0), // sk = Slovak
+ TRUETYPE_TAG('s', 'l', 0, 0), // sl = Slovenian
+ TRUETYPE_TAG('s', 'm', 0, 0), // sm = Samoan
+ TRUETYPE_TAG('s', 'n', 0, 0), // sn = Shona
+ TRUETYPE_TAG('s', 'o', 0, 0), // so = Somali
+ TRUETYPE_TAG('s', 'q', 0, 0), // sq = Albanian
+ TRUETYPE_TAG('s', 'r', 0, 0), // sr = Serbian
+ TRUETYPE_TAG('s', 's', 0, 0), // ss = Swati
+ TRUETYPE_TAG('s', 't', 0, 0), // st = Southern Sotho
+ TRUETYPE_TAG('s', 'u', 0, 0), // su = Sundanese
+ TRUETYPE_TAG('s', 'v', 0, 0), // sv = Swedish
+ TRUETYPE_TAG('s', 'w', 0, 0), // sw = Swahili (macrolanguage)
+ TRUETYPE_TAG('t', 'a', 0, 0), // ta = Tamil
+ TRUETYPE_TAG('t', 'e', 0, 0), // te = Telugu
+ TRUETYPE_TAG('t', 'g', 0, 0), // tg = Tajik
+ TRUETYPE_TAG('t', 'h', 0, 0), // th = Thai
+ TRUETYPE_TAG('t', 'i', 0, 0), // ti = Tigrinya
+ TRUETYPE_TAG('t', 'k', 0, 0), // tk = Turkmen
+ TRUETYPE_TAG('t', 'l', 0, 0), // tl = Tagalog
+ TRUETYPE_TAG('t', 'n', 0, 0), // tn = Tswana
+ TRUETYPE_TAG('t', 'o', 0, 0), // to = Tonga (Tonga Islands)
+ TRUETYPE_TAG('t', 'r', 0, 0), // tr = Turkish
+ TRUETYPE_TAG('t', 's', 0, 0), // ts = Tsonga
+ TRUETYPE_TAG('t', 't', 0, 0), // tt = Tatar
+ TRUETYPE_TAG('t', 'w', 0, 0), // tw = Twi
+ TRUETYPE_TAG('t', 'y', 0, 0), // ty = Tahitian
+ TRUETYPE_TAG('u', 'g', 0, 0), // ug = Uighur
+ TRUETYPE_TAG('u', 'k', 0, 0), // uk = Ukrainian
+ TRUETYPE_TAG('u', 'r', 0, 0), // ur = Urdu
+ TRUETYPE_TAG('u', 'z', 0, 0), // uz = Uzbek
+ TRUETYPE_TAG('v', 'e', 0, 0), // ve = Venda
+ TRUETYPE_TAG('v', 'i', 0, 0), // vi = Vietnamese
+ TRUETYPE_TAG('v', 'o', 0, 0), // vo = Volapük
+ TRUETYPE_TAG('w', 'a', 0, 0), // wa = Walloon
+ TRUETYPE_TAG('w', 'o', 0, 0), // wo = Wolof
+ TRUETYPE_TAG('x', 'h', 0, 0), // xh = Xhosa
+ TRUETYPE_TAG('y', 'i', 0, 0), // yi = Yiddish
+ TRUETYPE_TAG('y', 'o', 0, 0), // yo = Yoruba
+ TRUETYPE_TAG('z', 'a', 0, 0), // za = Zhuang
+ TRUETYPE_TAG('z', 'h', 0, 0), // zh = Chinese
+ TRUETYPE_TAG('z', 'u', 0, 0), // zu = Zulu
+ TRUETYPE_TAG('a', 'a', 'a', 0), // aaa = Ghotuo
+ TRUETYPE_TAG('a', 'a', 'b', 0), // aab = Alumu-Tesu
+ TRUETYPE_TAG('a', 'a', 'c', 0), // aac = Ari
+ TRUETYPE_TAG('a', 'a', 'd', 0), // aad = Amal
+ TRUETYPE_TAG('a', 'a', 'e', 0), // aae = Arbëreshë Albanian
+ TRUETYPE_TAG('a', 'a', 'f', 0), // aaf = Aranadan
+ TRUETYPE_TAG('a', 'a', 'g', 0), // aag = Ambrak
+ TRUETYPE_TAG('a', 'a', 'h', 0), // aah = Abu' Arapesh
+ TRUETYPE_TAG('a', 'a', 'i', 0), // aai = Arifama-Miniafia
+ TRUETYPE_TAG('a', 'a', 'k', 0), // aak = Ankave
+ TRUETYPE_TAG('a', 'a', 'l', 0), // aal = Afade
+ TRUETYPE_TAG('a', 'a', 'm', 0), // aam = Aramanik
+ TRUETYPE_TAG('a', 'a', 'n', 0), // aan = Anambé
+ TRUETYPE_TAG('a', 'a', 'o', 0), // aao = Algerian Saharan Arabic
+ TRUETYPE_TAG('a', 'a', 'p', 0), // aap = Pará Arára
+ TRUETYPE_TAG('a', 'a', 'q', 0), // aaq = Eastern Abnaki
+ TRUETYPE_TAG('a', 'a', 's', 0), // aas = Aasáx
+ TRUETYPE_TAG('a', 'a', 't', 0), // aat = Arvanitika Albanian
+ TRUETYPE_TAG('a', 'a', 'u', 0), // aau = Abau
+ TRUETYPE_TAG('a', 'a', 'v', 0), // aav = Austro-Asiatic languages
+ TRUETYPE_TAG('a', 'a', 'w', 0), // aaw = Solong
+ TRUETYPE_TAG('a', 'a', 'x', 0), // aax = Mandobo Atas
+ TRUETYPE_TAG('a', 'a', 'z', 0), // aaz = Amarasi
+ TRUETYPE_TAG('a', 'b', 'a', 0), // aba = Abé
+ TRUETYPE_TAG('a', 'b', 'b', 0), // abb = Bankon
+ TRUETYPE_TAG('a', 'b', 'c', 0), // abc = Ambala Ayta
+ TRUETYPE_TAG('a', 'b', 'd', 0), // abd = Manide
+ TRUETYPE_TAG('a', 'b', 'e', 0), // abe = Western Abnaki
+ TRUETYPE_TAG('a', 'b', 'f', 0), // abf = Abai Sungai
+ TRUETYPE_TAG('a', 'b', 'g', 0), // abg = Abaga
+ TRUETYPE_TAG('a', 'b', 'h', 0), // abh = Tajiki Arabic
+ TRUETYPE_TAG('a', 'b', 'i', 0), // abi = Abidji
+ TRUETYPE_TAG('a', 'b', 'j', 0), // abj = Aka-Bea
+ TRUETYPE_TAG('a', 'b', 'l', 0), // abl = Lampung Nyo
+ TRUETYPE_TAG('a', 'b', 'm', 0), // abm = Abanyom
+ TRUETYPE_TAG('a', 'b', 'n', 0), // abn = Abua
+ TRUETYPE_TAG('a', 'b', 'o', 0), // abo = Abon
+ TRUETYPE_TAG('a', 'b', 'p', 0), // abp = Abellen Ayta
+ TRUETYPE_TAG('a', 'b', 'q', 0), // abq = Abaza
+ TRUETYPE_TAG('a', 'b', 'r', 0), // abr = Abron
+ TRUETYPE_TAG('a', 'b', 's', 0), // abs = Ambonese Malay
+ TRUETYPE_TAG('a', 'b', 't', 0), // abt = Ambulas
+ TRUETYPE_TAG('a', 'b', 'u', 0), // abu = Abure
+ TRUETYPE_TAG('a', 'b', 'v', 0), // abv = Baharna Arabic
+ TRUETYPE_TAG('a', 'b', 'w', 0), // abw = Pal
+ TRUETYPE_TAG('a', 'b', 'x', 0), // abx = Inabaknon
+ TRUETYPE_TAG('a', 'b', 'y', 0), // aby = Aneme Wake
+ TRUETYPE_TAG('a', 'b', 'z', 0), // abz = Abui
+ TRUETYPE_TAG('a', 'c', 'a', 0), // aca = Achagua
+ TRUETYPE_TAG('a', 'c', 'b', 0), // acb = Áncá
+ TRUETYPE_TAG('a', 'c', 'd', 0), // acd = Gikyode
+ TRUETYPE_TAG('a', 'c', 'e', 0), // ace = Achinese
+ TRUETYPE_TAG('a', 'c', 'f', 0), // acf = Saint Lucian Creole French
+ TRUETYPE_TAG('a', 'c', 'h', 0), // ach = Acoli
+ TRUETYPE_TAG('a', 'c', 'i', 0), // aci = Aka-Cari
+ TRUETYPE_TAG('a', 'c', 'k', 0), // ack = Aka-Kora
+ TRUETYPE_TAG('a', 'c', 'l', 0), // acl = Akar-Bale
+ TRUETYPE_TAG('a', 'c', 'm', 0), // acm = Mesopotamian Arabic
+ TRUETYPE_TAG('a', 'c', 'n', 0), // acn = Achang
+ TRUETYPE_TAG('a', 'c', 'p', 0), // acp = Eastern Acipa
+ TRUETYPE_TAG('a', 'c', 'q', 0), // acq = Ta'izzi-Adeni Arabic
+ TRUETYPE_TAG('a', 'c', 'r', 0), // acr = Achi
+ TRUETYPE_TAG('a', 'c', 's', 0), // acs = Acroá
+ TRUETYPE_TAG('a', 'c', 't', 0), // act = Achterhoeks
+ TRUETYPE_TAG('a', 'c', 'u', 0), // acu = Achuar-Shiwiar
+ TRUETYPE_TAG('a', 'c', 'v', 0), // acv = Achumawi
+ TRUETYPE_TAG('a', 'c', 'w', 0), // acw = Hijazi Arabic
+ TRUETYPE_TAG('a', 'c', 'x', 0), // acx = Omani Arabic
+ TRUETYPE_TAG('a', 'c', 'y', 0), // acy = Cypriot Arabic
+ TRUETYPE_TAG('a', 'c', 'z', 0), // acz = Acheron
+ TRUETYPE_TAG('a', 'd', 'a', 0), // ada = Adangme
+ TRUETYPE_TAG('a', 'd', 'b', 0), // adb = Adabe
+ TRUETYPE_TAG('a', 'd', 'd', 0), // add = Dzodinka
+ TRUETYPE_TAG('a', 'd', 'e', 0), // ade = Adele
+ TRUETYPE_TAG('a', 'd', 'f', 0), // adf = Dhofari Arabic
+ TRUETYPE_TAG('a', 'd', 'g', 0), // adg = Andegerebinha
+ TRUETYPE_TAG('a', 'd', 'h', 0), // adh = Adhola
+ TRUETYPE_TAG('a', 'd', 'i', 0), // adi = Adi
+ TRUETYPE_TAG('a', 'd', 'j', 0), // adj = Adioukrou
+ TRUETYPE_TAG('a', 'd', 'l', 0), // adl = Galo
+ TRUETYPE_TAG('a', 'd', 'n', 0), // adn = Adang
+ TRUETYPE_TAG('a', 'd', 'o', 0), // ado = Abu
+ TRUETYPE_TAG('a', 'd', 'p', 0), // adp = Adap
+ TRUETYPE_TAG('a', 'd', 'q', 0), // adq = Adangbe
+ TRUETYPE_TAG('a', 'd', 'r', 0), // adr = Adonara
+ TRUETYPE_TAG('a', 'd', 's', 0), // ads = Adamorobe Sign Language
+ TRUETYPE_TAG('a', 'd', 't', 0), // adt = Adnyamathanha
+ TRUETYPE_TAG('a', 'd', 'u', 0), // adu = Aduge
+ TRUETYPE_TAG('a', 'd', 'w', 0), // adw = Amundava
+ TRUETYPE_TAG('a', 'd', 'x', 0), // adx = Amdo Tibetan
+ TRUETYPE_TAG('a', 'd', 'y', 0), // ady = Adyghe
+ TRUETYPE_TAG('a', 'd', 'z', 0), // adz = Adzera
+ TRUETYPE_TAG('a', 'e', 'a', 0), // aea = Areba
+ TRUETYPE_TAG('a', 'e', 'b', 0), // aeb = Tunisian Arabic
+ TRUETYPE_TAG('a', 'e', 'c', 0), // aec = Saidi Arabic
+ TRUETYPE_TAG('a', 'e', 'd', 0), // aed = Argentine Sign Language
+ TRUETYPE_TAG('a', 'e', 'e', 0), // aee = Northeast Pashayi
+ TRUETYPE_TAG('a', 'e', 'k', 0), // aek = Haeke
+ TRUETYPE_TAG('a', 'e', 'l', 0), // ael = Ambele
+ TRUETYPE_TAG('a', 'e', 'm', 0), // aem = Arem
+ TRUETYPE_TAG('a', 'e', 'n', 0), // aen = Armenian Sign Language
+ TRUETYPE_TAG('a', 'e', 'q', 0), // aeq = Aer
+ TRUETYPE_TAG('a', 'e', 'r', 0), // aer = Eastern Arrernte
+ TRUETYPE_TAG('a', 'e', 's', 0), // aes = Alsea
+ TRUETYPE_TAG('a', 'e', 'u', 0), // aeu = Akeu
+ TRUETYPE_TAG('a', 'e', 'w', 0), // aew = Ambakich
+ TRUETYPE_TAG('a', 'e', 'y', 0), // aey = Amele
+ TRUETYPE_TAG('a', 'e', 'z', 0), // aez = Aeka
+ TRUETYPE_TAG('a', 'f', 'a', 0), // afa = Afro-Asiatic languages
+ TRUETYPE_TAG('a', 'f', 'b', 0), // afb = Gulf Arabic
+ TRUETYPE_TAG('a', 'f', 'd', 0), // afd = Andai
+ TRUETYPE_TAG('a', 'f', 'e', 0), // afe = Putukwam
+ TRUETYPE_TAG('a', 'f', 'g', 0), // afg = Afghan Sign Language
+ TRUETYPE_TAG('a', 'f', 'h', 0), // afh = Afrihili
+ TRUETYPE_TAG('a', 'f', 'i', 0), // afi = Akrukay
+ TRUETYPE_TAG('a', 'f', 'k', 0), // afk = Nanubae
+ TRUETYPE_TAG('a', 'f', 'n', 0), // afn = Defaka
+ TRUETYPE_TAG('a', 'f', 'o', 0), // afo = Eloyi
+ TRUETYPE_TAG('a', 'f', 'p', 0), // afp = Tapei
+ TRUETYPE_TAG('a', 'f', 's', 0), // afs = Afro-Seminole Creole
+ TRUETYPE_TAG('a', 'f', 't', 0), // aft = Afitti
+ TRUETYPE_TAG('a', 'f', 'u', 0), // afu = Awutu
+ TRUETYPE_TAG('a', 'f', 'z', 0), // afz = Obokuitai
+ TRUETYPE_TAG('a', 'g', 'a', 0), // aga = Aguano
+ TRUETYPE_TAG('a', 'g', 'b', 0), // agb = Legbo
+ TRUETYPE_TAG('a', 'g', 'c', 0), // agc = Agatu
+ TRUETYPE_TAG('a', 'g', 'd', 0), // agd = Agarabi
+ TRUETYPE_TAG('a', 'g', 'e', 0), // age = Angal
+ TRUETYPE_TAG('a', 'g', 'f', 0), // agf = Arguni
+ TRUETYPE_TAG('a', 'g', 'g', 0), // agg = Angor
+ TRUETYPE_TAG('a', 'g', 'h', 0), // agh = Ngelima
+ TRUETYPE_TAG('a', 'g', 'i', 0), // agi = Agariya
+ TRUETYPE_TAG('a', 'g', 'j', 0), // agj = Argobba
+ TRUETYPE_TAG('a', 'g', 'k', 0), // agk = Isarog Agta
+ TRUETYPE_TAG('a', 'g', 'l', 0), // agl = Fembe
+ TRUETYPE_TAG('a', 'g', 'm', 0), // agm = Angaataha
+ TRUETYPE_TAG('a', 'g', 'n', 0), // agn = Agutaynen
+ TRUETYPE_TAG('a', 'g', 'o', 0), // ago = Tainae
+ TRUETYPE_TAG('a', 'g', 'p', 0), // agp = Paranan
+ TRUETYPE_TAG('a', 'g', 'q', 0), // agq = Aghem
+ TRUETYPE_TAG('a', 'g', 'r', 0), // agr = Aguaruna
+ TRUETYPE_TAG('a', 'g', 's', 0), // ags = Esimbi
+ TRUETYPE_TAG('a', 'g', 't', 0), // agt = Central Cagayan Agta
+ TRUETYPE_TAG('a', 'g', 'u', 0), // agu = Aguacateco
+ TRUETYPE_TAG('a', 'g', 'v', 0), // agv = Remontado Dumagat
+ TRUETYPE_TAG('a', 'g', 'w', 0), // agw = Kahua
+ TRUETYPE_TAG('a', 'g', 'x', 0), // agx = Aghul
+ TRUETYPE_TAG('a', 'g', 'y', 0), // agy = Southern Alta
+ TRUETYPE_TAG('a', 'g', 'z', 0), // agz = Mt. Iriga Agta
+ TRUETYPE_TAG('a', 'h', 'a', 0), // aha = Ahanta
+ TRUETYPE_TAG('a', 'h', 'b', 0), // ahb = Axamb
+ TRUETYPE_TAG('a', 'h', 'g', 0), // ahg = Qimant
+ TRUETYPE_TAG('a', 'h', 'h', 0), // ahh = Aghu
+ TRUETYPE_TAG('a', 'h', 'i', 0), // ahi = Tiagbamrin Aizi
+ TRUETYPE_TAG('a', 'h', 'k', 0), // ahk = Akha
+ TRUETYPE_TAG('a', 'h', 'l', 0), // ahl = Igo
+ TRUETYPE_TAG('a', 'h', 'm', 0), // ahm = Mobumrin Aizi
+ TRUETYPE_TAG('a', 'h', 'n', 0), // ahn = Àhàn
+ TRUETYPE_TAG('a', 'h', 'o', 0), // aho = Ahom
+ TRUETYPE_TAG('a', 'h', 'p', 0), // ahp = Aproumu Aizi
+ TRUETYPE_TAG('a', 'h', 'r', 0), // ahr = Ahirani
+ TRUETYPE_TAG('a', 'h', 's', 0), // ahs = Ashe
+ TRUETYPE_TAG('a', 'h', 't', 0), // aht = Ahtena
+ TRUETYPE_TAG('a', 'i', 'a', 0), // aia = Arosi
+ TRUETYPE_TAG('a', 'i', 'b', 0), // aib = Ainu (China)
+ TRUETYPE_TAG('a', 'i', 'c', 0), // aic = Ainbai
+ TRUETYPE_TAG('a', 'i', 'd', 0), // aid = Alngith
+ TRUETYPE_TAG('a', 'i', 'e', 0), // aie = Amara
+ TRUETYPE_TAG('a', 'i', 'f', 0), // aif = Agi
+ TRUETYPE_TAG('a', 'i', 'g', 0), // aig = Antigua and Barbuda Creole English
+ TRUETYPE_TAG('a', 'i', 'h', 0), // aih = Ai-Cham
+ TRUETYPE_TAG('a', 'i', 'i', 0), // aii = Assyrian Neo-Aramaic
+ TRUETYPE_TAG('a', 'i', 'j', 0), // aij = Lishanid Noshan
+ TRUETYPE_TAG('a', 'i', 'k', 0), // aik = Ake
+ TRUETYPE_TAG('a', 'i', 'l', 0), // ail = Aimele
+ TRUETYPE_TAG('a', 'i', 'm', 0), // aim = Aimol
+ TRUETYPE_TAG('a', 'i', 'n', 0), // ain = Ainu (Japan)
+ TRUETYPE_TAG('a', 'i', 'o', 0), // aio = Aiton
+ TRUETYPE_TAG('a', 'i', 'p', 0), // aip = Burumakok
+ TRUETYPE_TAG('a', 'i', 'q', 0), // aiq = Aimaq
+ TRUETYPE_TAG('a', 'i', 'r', 0), // air = Airoran
+ TRUETYPE_TAG('a', 'i', 's', 0), // ais = Nataoran Amis
+ TRUETYPE_TAG('a', 'i', 't', 0), // ait = Arikem
+ TRUETYPE_TAG('a', 'i', 'w', 0), // aiw = Aari
+ TRUETYPE_TAG('a', 'i', 'x', 0), // aix = Aighon
+ TRUETYPE_TAG('a', 'i', 'y', 0), // aiy = Ali
+ TRUETYPE_TAG('a', 'j', 'a', 0), // aja = Aja (Sudan)
+ TRUETYPE_TAG('a', 'j', 'g', 0), // ajg = Aja (Benin)
+ TRUETYPE_TAG('a', 'j', 'i', 0), // aji = Ajië
+ TRUETYPE_TAG('a', 'j', 'p', 0), // ajp = South Levantine Arabic
+ TRUETYPE_TAG('a', 'j', 't', 0), // ajt = Judeo-Tunisian Arabic
+ TRUETYPE_TAG('a', 'j', 'u', 0), // aju = Judeo-Moroccan Arabic
+ TRUETYPE_TAG('a', 'j', 'w', 0), // ajw = Ajawa
+ TRUETYPE_TAG('a', 'j', 'z', 0), // ajz = Amri Karbi
+ TRUETYPE_TAG('a', 'k', 'b', 0), // akb = Batak Angkola
+ TRUETYPE_TAG('a', 'k', 'c', 0), // akc = Mpur
+ TRUETYPE_TAG('a', 'k', 'd', 0), // akd = Ukpet-Ehom
+ TRUETYPE_TAG('a', 'k', 'e', 0), // ake = Akawaio
+ TRUETYPE_TAG('a', 'k', 'f', 0), // akf = Akpa
+ TRUETYPE_TAG('a', 'k', 'g', 0), // akg = Anakalangu
+ TRUETYPE_TAG('a', 'k', 'h', 0), // akh = Angal Heneng
+ TRUETYPE_TAG('a', 'k', 'i', 0), // aki = Aiome
+ TRUETYPE_TAG('a', 'k', 'j', 0), // akj = Aka-Jeru
+ TRUETYPE_TAG('a', 'k', 'k', 0), // akk = Akkadian
+ TRUETYPE_TAG('a', 'k', 'l', 0), // akl = Aklanon
+ TRUETYPE_TAG('a', 'k', 'm', 0), // akm = Aka-Bo
+ TRUETYPE_TAG('a', 'k', 'o', 0), // ako = Akurio
+ TRUETYPE_TAG('a', 'k', 'p', 0), // akp = Siwu
+ TRUETYPE_TAG('a', 'k', 'q', 0), // akq = Ak
+ TRUETYPE_TAG('a', 'k', 'r', 0), // akr = Araki
+ TRUETYPE_TAG('a', 'k', 's', 0), // aks = Akaselem
+ TRUETYPE_TAG('a', 'k', 't', 0), // akt = Akolet
+ TRUETYPE_TAG('a', 'k', 'u', 0), // aku = Akum
+ TRUETYPE_TAG('a', 'k', 'v', 0), // akv = Akhvakh
+ TRUETYPE_TAG('a', 'k', 'w', 0), // akw = Akwa
+ TRUETYPE_TAG('a', 'k', 'x', 0), // akx = Aka-Kede
+ TRUETYPE_TAG('a', 'k', 'y', 0), // aky = Aka-Kol
+ TRUETYPE_TAG('a', 'k', 'z', 0), // akz = Alabama
+ TRUETYPE_TAG('a', 'l', 'a', 0), // ala = Alago
+ TRUETYPE_TAG('a', 'l', 'c', 0), // alc = Qawasqar
+ TRUETYPE_TAG('a', 'l', 'd', 0), // ald = Alladian
+ TRUETYPE_TAG('a', 'l', 'e', 0), // ale = Aleut
+ TRUETYPE_TAG('a', 'l', 'f', 0), // alf = Alege
+ TRUETYPE_TAG('a', 'l', 'g', 0), // alg = Algonquian languages
+ TRUETYPE_TAG('a', 'l', 'h', 0), // alh = Alawa
+ TRUETYPE_TAG('a', 'l', 'i', 0), // ali = Amaimon
+ TRUETYPE_TAG('a', 'l', 'j', 0), // alj = Alangan
+ TRUETYPE_TAG('a', 'l', 'k', 0), // alk = Alak
+ TRUETYPE_TAG('a', 'l', 'l', 0), // all = Allar
+ TRUETYPE_TAG('a', 'l', 'm', 0), // alm = Amblong
+ TRUETYPE_TAG('a', 'l', 'n', 0), // aln = Gheg Albanian
+ TRUETYPE_TAG('a', 'l', 'o', 0), // alo = Larike-Wakasihu
+ TRUETYPE_TAG('a', 'l', 'p', 0), // alp = Alune
+ TRUETYPE_TAG('a', 'l', 'q', 0), // alq = Algonquin
+ TRUETYPE_TAG('a', 'l', 'r', 0), // alr = Alutor
+ TRUETYPE_TAG('a', 'l', 's', 0), // als = Tosk Albanian
+ TRUETYPE_TAG('a', 'l', 't', 0), // alt = Southern Altai
+ TRUETYPE_TAG('a', 'l', 'u', 0), // alu = 'Are'are
+ TRUETYPE_TAG('a', 'l', 'v', 0), // alv = Atlantic-Congo languages
+ TRUETYPE_TAG('a', 'l', 'w', 0), // alw = Alaba-K’abeena
+ TRUETYPE_TAG('a', 'l', 'x', 0), // alx = Amol
+ TRUETYPE_TAG('a', 'l', 'y', 0), // aly = Alyawarr
+ TRUETYPE_TAG('a', 'l', 'z', 0), // alz = Alur
+ TRUETYPE_TAG('a', 'm', 'a', 0), // ama = Amanayé
+ TRUETYPE_TAG('a', 'm', 'b', 0), // amb = Ambo
+ TRUETYPE_TAG('a', 'm', 'c', 0), // amc = Amahuaca
+ TRUETYPE_TAG('a', 'm', 'e', 0), // ame = Yanesha'
+ TRUETYPE_TAG('a', 'm', 'f', 0), // amf = Hamer-Banna
+ TRUETYPE_TAG('a', 'm', 'g', 0), // amg = Amarag
+ TRUETYPE_TAG('a', 'm', 'i', 0), // ami = Amis
+ TRUETYPE_TAG('a', 'm', 'j', 0), // amj = Amdang
+ TRUETYPE_TAG('a', 'm', 'k', 0), // amk = Ambai
+ TRUETYPE_TAG('a', 'm', 'l', 0), // aml = War-Jaintia
+ TRUETYPE_TAG('a', 'm', 'm', 0), // amm = Ama (Papua New Guinea)
+ TRUETYPE_TAG('a', 'm', 'n', 0), // amn = Amanab
+ TRUETYPE_TAG('a', 'm', 'o', 0), // amo = Amo
+ TRUETYPE_TAG('a', 'm', 'p', 0), // amp = Alamblak
+ TRUETYPE_TAG('a', 'm', 'q', 0), // amq = Amahai
+ TRUETYPE_TAG('a', 'm', 'r', 0), // amr = Amarakaeri
+ TRUETYPE_TAG('a', 'm', 's', 0), // ams = Southern Amami-Oshima
+ TRUETYPE_TAG('a', 'm', 't', 0), // amt = Amto
+ TRUETYPE_TAG('a', 'm', 'u', 0), // amu = Guerrero Amuzgo
+ TRUETYPE_TAG('a', 'm', 'v', 0), // amv = Ambelau
+ TRUETYPE_TAG('a', 'm', 'w', 0), // amw = Western Neo-Aramaic
+ TRUETYPE_TAG('a', 'm', 'x', 0), // amx = Anmatyerre
+ TRUETYPE_TAG('a', 'm', 'y', 0), // amy = Ami
+ TRUETYPE_TAG('a', 'm', 'z', 0), // amz = Atampaya
+ TRUETYPE_TAG('a', 'n', 'a', 0), // ana = Andaqui
+ TRUETYPE_TAG('a', 'n', 'b', 0), // anb = Andoa
+ TRUETYPE_TAG('a', 'n', 'c', 0), // anc = Ngas
+ TRUETYPE_TAG('a', 'n', 'd', 0), // and = Ansus
+ TRUETYPE_TAG('a', 'n', 'e', 0), // ane = Xârâcùù
+ TRUETYPE_TAG('a', 'n', 'f', 0), // anf = Animere
+ TRUETYPE_TAG('a', 'n', 'g', 0), // ang = Old English (ca. 450-1100)
+ TRUETYPE_TAG('a', 'n', 'h', 0), // anh = Nend
+ TRUETYPE_TAG('a', 'n', 'i', 0), // ani = Andi
+ TRUETYPE_TAG('a', 'n', 'j', 0), // anj = Anor
+ TRUETYPE_TAG('a', 'n', 'k', 0), // ank = Goemai
+ TRUETYPE_TAG('a', 'n', 'l', 0), // anl = Anu
+ TRUETYPE_TAG('a', 'n', 'm', 0), // anm = Anal
+ TRUETYPE_TAG('a', 'n', 'n', 0), // ann = Obolo
+ TRUETYPE_TAG('a', 'n', 'o', 0), // ano = Andoque
+ TRUETYPE_TAG('a', 'n', 'p', 0), // anp = Angika
+ TRUETYPE_TAG('a', 'n', 'q', 0), // anq = Jarawa (India)
+ TRUETYPE_TAG('a', 'n', 'r', 0), // anr = Andh
+ TRUETYPE_TAG('a', 'n', 's', 0), // ans = Anserma
+ TRUETYPE_TAG('a', 'n', 't', 0), // ant = Antakarinya
+ TRUETYPE_TAG('a', 'n', 'u', 0), // anu = Anuak
+ TRUETYPE_TAG('a', 'n', 'v', 0), // anv = Denya
+ TRUETYPE_TAG('a', 'n', 'w', 0), // anw = Anaang
+ TRUETYPE_TAG('a', 'n', 'x', 0), // anx = Andra-Hus
+ TRUETYPE_TAG('a', 'n', 'y', 0), // any = Anyin
+ TRUETYPE_TAG('a', 'n', 'z', 0), // anz = Anem
+ TRUETYPE_TAG('a', 'o', 'a', 0), // aoa = Angolar
+ TRUETYPE_TAG('a', 'o', 'b', 0), // aob = Abom
+ TRUETYPE_TAG('a', 'o', 'c', 0), // aoc = Pemon
+ TRUETYPE_TAG('a', 'o', 'd', 0), // aod = Andarum
+ TRUETYPE_TAG('a', 'o', 'e', 0), // aoe = Angal Enen
+ TRUETYPE_TAG('a', 'o', 'f', 0), // aof = Bragat
+ TRUETYPE_TAG('a', 'o', 'g', 0), // aog = Angoram
+ TRUETYPE_TAG('a', 'o', 'h', 0), // aoh = Arma
+ TRUETYPE_TAG('a', 'o', 'i', 0), // aoi = Anindilyakwa
+ TRUETYPE_TAG('a', 'o', 'j', 0), // aoj = Mufian
+ TRUETYPE_TAG('a', 'o', 'k', 0), // aok = Arhö
+ TRUETYPE_TAG('a', 'o', 'l', 0), // aol = Alor
+ TRUETYPE_TAG('a', 'o', 'm', 0), // aom = Ömie
+ TRUETYPE_TAG('a', 'o', 'n', 0), // aon = Bumbita Arapesh
+ TRUETYPE_TAG('a', 'o', 'r', 0), // aor = Aore
+ TRUETYPE_TAG('a', 'o', 's', 0), // aos = Taikat
+ TRUETYPE_TAG('a', 'o', 't', 0), // aot = A'tong
+ TRUETYPE_TAG('a', 'o', 'x', 0), // aox = Atorada
+ TRUETYPE_TAG('a', 'o', 'z', 0), // aoz = Uab Meto
+ TRUETYPE_TAG('a', 'p', 'a', 0), // apa = Apache languages
+ TRUETYPE_TAG('a', 'p', 'b', 0), // apb = Sa'a
+ TRUETYPE_TAG('a', 'p', 'c', 0), // apc = North Levantine Arabic
+ TRUETYPE_TAG('a', 'p', 'd', 0), // apd = Sudanese Arabic
+ TRUETYPE_TAG('a', 'p', 'e', 0), // ape = Bukiyip
+ TRUETYPE_TAG('a', 'p', 'f', 0), // apf = Pahanan Agta
+ TRUETYPE_TAG('a', 'p', 'g', 0), // apg = Ampanang
+ TRUETYPE_TAG('a', 'p', 'h', 0), // aph = Athpariya
+ TRUETYPE_TAG('a', 'p', 'i', 0), // api = Apiaká
+ TRUETYPE_TAG('a', 'p', 'j', 0), // apj = Jicarilla Apache
+ TRUETYPE_TAG('a', 'p', 'k', 0), // apk = Kiowa Apache
+ TRUETYPE_TAG('a', 'p', 'l', 0), // apl = Lipan Apache
+ TRUETYPE_TAG('a', 'p', 'm', 0), // apm = Mescalero-Chiricahua Apache
+ TRUETYPE_TAG('a', 'p', 'n', 0), // apn = Apinayé
+ TRUETYPE_TAG('a', 'p', 'o', 0), // apo = Ambul
+ TRUETYPE_TAG('a', 'p', 'p', 0), // app = Apma
+ TRUETYPE_TAG('a', 'p', 'q', 0), // apq = A-Pucikwar
+ TRUETYPE_TAG('a', 'p', 'r', 0), // apr = Arop-Lokep
+ TRUETYPE_TAG('a', 'p', 's', 0), // aps = Arop-Sissano
+ TRUETYPE_TAG('a', 'p', 't', 0), // apt = Apatani
+ TRUETYPE_TAG('a', 'p', 'u', 0), // apu = Apurinã
+ TRUETYPE_TAG('a', 'p', 'v', 0), // apv = Alapmunte
+ TRUETYPE_TAG('a', 'p', 'w', 0), // apw = Western Apache
+ TRUETYPE_TAG('a', 'p', 'x', 0), // apx = Aputai
+ TRUETYPE_TAG('a', 'p', 'y', 0), // apy = Apalaí
+ TRUETYPE_TAG('a', 'p', 'z', 0), // apz = Safeyoka
+ TRUETYPE_TAG('a', 'q', 'a', 0), // aqa = Alacalufan languages
+ TRUETYPE_TAG('a', 'q', 'c', 0), // aqc = Archi
+ TRUETYPE_TAG('a', 'q', 'd', 0), // aqd = Ampari Dogon
+ TRUETYPE_TAG('a', 'q', 'g', 0), // aqg = Arigidi
+ TRUETYPE_TAG('a', 'q', 'l', 0), // aql = Algic languages
+ TRUETYPE_TAG('a', 'q', 'm', 0), // aqm = Atohwaim
+ TRUETYPE_TAG('a', 'q', 'n', 0), // aqn = Northern Alta
+ TRUETYPE_TAG('a', 'q', 'p', 0), // aqp = Atakapa
+ TRUETYPE_TAG('a', 'q', 'r', 0), // aqr = Arhâ
+ TRUETYPE_TAG('a', 'q', 'z', 0), // aqz = Akuntsu
+ TRUETYPE_TAG('a', 'r', 'b', 0), // arb = Standard Arabic
+ TRUETYPE_TAG('a', 'r', 'c', 0), // arc = Official Aramaic (700-300 BCE)
+ TRUETYPE_TAG('a', 'r', 'd', 0), // ard = Arabana
+ TRUETYPE_TAG('a', 'r', 'e', 0), // are = Western Arrarnta
+ TRUETYPE_TAG('a', 'r', 'h', 0), // arh = Arhuaco
+ TRUETYPE_TAG('a', 'r', 'i', 0), // ari = Arikara
+ TRUETYPE_TAG('a', 'r', 'j', 0), // arj = Arapaso
+ TRUETYPE_TAG('a', 'r', 'k', 0), // ark = Arikapú
+ TRUETYPE_TAG('a', 'r', 'l', 0), // arl = Arabela
+ TRUETYPE_TAG('a', 'r', 'n', 0), // arn = Mapudungun
+ TRUETYPE_TAG('a', 'r', 'o', 0), // aro = Araona
+ TRUETYPE_TAG('a', 'r', 'p', 0), // arp = Arapaho
+ TRUETYPE_TAG('a', 'r', 'q', 0), // arq = Algerian Arabic
+ TRUETYPE_TAG('a', 'r', 'r', 0), // arr = Karo (Brazil)
+ TRUETYPE_TAG('a', 'r', 's', 0), // ars = Najdi Arabic
+ TRUETYPE_TAG('a', 'r', 't', 0), // art = Artificial languages
+ TRUETYPE_TAG('a', 'r', 'u', 0), // aru = Aruá (Amazonas State)
+ TRUETYPE_TAG('a', 'r', 'v', 0), // arv = Arbore
+ TRUETYPE_TAG('a', 'r', 'w', 0), // arw = Arawak
+ TRUETYPE_TAG('a', 'r', 'x', 0), // arx = Aruá (Rodonia State)
+ TRUETYPE_TAG('a', 'r', 'y', 0), // ary = Moroccan Arabic
+ TRUETYPE_TAG('a', 'r', 'z', 0), // arz = Egyptian Arabic
+ TRUETYPE_TAG('a', 's', 'a', 0), // asa = Asu (Tanzania)
+ TRUETYPE_TAG('a', 's', 'b', 0), // asb = Assiniboine
+ TRUETYPE_TAG('a', 's', 'c', 0), // asc = Casuarina Coast Asmat
+ TRUETYPE_TAG('a', 's', 'd', 0), // asd = Asas
+ TRUETYPE_TAG('a', 's', 'e', 0), // ase = American Sign Language
+ TRUETYPE_TAG('a', 's', 'f', 0), // asf = Australian Sign Language
+ TRUETYPE_TAG('a', 's', 'g', 0), // asg = Cishingini
+ TRUETYPE_TAG('a', 's', 'h', 0), // ash = Abishira
+ TRUETYPE_TAG('a', 's', 'i', 0), // asi = Buruwai
+ TRUETYPE_TAG('a', 's', 'j', 0), // asj = Nsari
+ TRUETYPE_TAG('a', 's', 'k', 0), // ask = Ashkun
+ TRUETYPE_TAG('a', 's', 'l', 0), // asl = Asilulu
+ TRUETYPE_TAG('a', 's', 'n', 0), // asn = Xingú Asuriní
+ TRUETYPE_TAG('a', 's', 'o', 0), // aso = Dano
+ TRUETYPE_TAG('a', 's', 'p', 0), // asp = Algerian Sign Language
+ TRUETYPE_TAG('a', 's', 'q', 0), // asq = Austrian Sign Language
+ TRUETYPE_TAG('a', 's', 'r', 0), // asr = Asuri
+ TRUETYPE_TAG('a', 's', 's', 0), // ass = Ipulo
+ TRUETYPE_TAG('a', 's', 't', 0), // ast = Asturian
+ TRUETYPE_TAG('a', 's', 'u', 0), // asu = Tocantins Asurini
+ TRUETYPE_TAG('a', 's', 'v', 0), // asv = Asoa
+ TRUETYPE_TAG('a', 's', 'w',
+ 0), // asw = Australian Aborigines Sign Language
+ TRUETYPE_TAG('a', 's', 'x', 0), // asx = Muratayak
+ TRUETYPE_TAG('a', 's', 'y', 0), // asy = Yaosakor Asmat
+ TRUETYPE_TAG('a', 's', 'z', 0), // asz = As
+ TRUETYPE_TAG('a', 't', 'a', 0), // ata = Pele-Ata
+ TRUETYPE_TAG('a', 't', 'b', 0), // atb = Zaiwa
+ TRUETYPE_TAG('a', 't', 'c', 0), // atc = Atsahuaca
+ TRUETYPE_TAG('a', 't', 'd', 0), // atd = Ata Manobo
+ TRUETYPE_TAG('a', 't', 'e', 0), // ate = Atemble
+ TRUETYPE_TAG('a', 't', 'g', 0), // atg = Ivbie North-Okpela-Arhe
+ TRUETYPE_TAG('a', 't', 'h', 0), // ath = Athapascan languages
+ TRUETYPE_TAG('a', 't', 'i', 0), // ati = Attié
+ TRUETYPE_TAG('a', 't', 'j', 0), // atj = Atikamekw
+ TRUETYPE_TAG('a', 't', 'k', 0), // atk = Ati
+ TRUETYPE_TAG('a', 't', 'l', 0), // atl = Mt. Iraya Agta
+ TRUETYPE_TAG('a', 't', 'm', 0), // atm = Ata
+ TRUETYPE_TAG('a', 't', 'n', 0), // atn = Ashtiani
+ TRUETYPE_TAG('a', 't', 'o', 0), // ato = Atong
+ TRUETYPE_TAG('a', 't', 'p', 0), // atp = Pudtol Atta
+ TRUETYPE_TAG('a', 't', 'q', 0), // atq = Aralle-Tabulahan
+ TRUETYPE_TAG('a', 't', 'r', 0), // atr = Waimiri-Atroari
+ TRUETYPE_TAG('a', 't', 's', 0), // ats = Gros Ventre
+ TRUETYPE_TAG('a', 't', 't', 0), // att = Pamplona Atta
+ TRUETYPE_TAG('a', 't', 'u', 0), // atu = Reel
+ TRUETYPE_TAG('a', 't', 'v', 0), // atv = Northern Altai
+ TRUETYPE_TAG('a', 't', 'w', 0), // atw = Atsugewi
+ TRUETYPE_TAG('a', 't', 'x', 0), // atx = Arutani
+ TRUETYPE_TAG('a', 't', 'y', 0), // aty = Aneityum
+ TRUETYPE_TAG('a', 't', 'z', 0), // atz = Arta
+ TRUETYPE_TAG('a', 'u', 'a', 0), // aua = Asumboa
+ TRUETYPE_TAG('a', 'u', 'b', 0), // aub = Alugu
+ TRUETYPE_TAG('a', 'u', 'c', 0), // auc = Waorani
+ TRUETYPE_TAG('a', 'u', 'd', 0), // aud = Anuta
+ TRUETYPE_TAG('a', 'u', 'e', 0), // aue = =/Kx'au//'ein
+ TRUETYPE_TAG('a', 'u', 'f', 0), // auf = Arauan languages
+ TRUETYPE_TAG('a', 'u', 'g', 0), // aug = Aguna
+ TRUETYPE_TAG('a', 'u', 'h', 0), // auh = Aushi
+ TRUETYPE_TAG('a', 'u', 'i', 0), // aui = Anuki
+ TRUETYPE_TAG('a', 'u', 'j', 0), // auj = Awjilah
+ TRUETYPE_TAG('a', 'u', 'k', 0), // auk = Heyo
+ TRUETYPE_TAG('a', 'u', 'l', 0), // aul = Aulua
+ TRUETYPE_TAG('a', 'u', 'm', 0), // aum = Asu (Nigeria)
+ TRUETYPE_TAG('a', 'u', 'n', 0), // aun = Molmo One
+ TRUETYPE_TAG('a', 'u', 'o', 0), // auo = Auyokawa
+ TRUETYPE_TAG('a', 'u', 'p', 0), // aup = Makayam
+ TRUETYPE_TAG('a', 'u', 'q', 0), // auq = Anus
+ TRUETYPE_TAG('a', 'u', 'r', 0), // aur = Aruek
+ TRUETYPE_TAG('a', 'u', 's', 0), // aus = Australian languages
+ TRUETYPE_TAG('a', 'u', 't', 0), // aut = Austral
+ TRUETYPE_TAG('a', 'u', 'u', 0), // auu = Auye
+ TRUETYPE_TAG('a', 'u', 'w', 0), // auw = Awyi
+ TRUETYPE_TAG('a', 'u', 'x', 0), // aux = Aurá
+ TRUETYPE_TAG('a', 'u', 'y', 0), // auy = Awiyaana
+ TRUETYPE_TAG('a', 'u', 'z', 0), // auz = Uzbeki Arabic
+ TRUETYPE_TAG('a', 'v', 'b', 0), // avb = Avau
+ TRUETYPE_TAG('a', 'v', 'd', 0), // avd = Alviri-Vidari
+ TRUETYPE_TAG('a', 'v', 'i', 0), // avi = Avikam
+ TRUETYPE_TAG('a', 'v', 'k', 0), // avk = Kotava
+ TRUETYPE_TAG('a', 'v', 'l', 0), // avl = Eastern Egyptian Bedawi Arabic
+ TRUETYPE_TAG('a', 'v', 'n', 0), // avn = Avatime
+ TRUETYPE_TAG('a', 'v', 'o', 0), // avo = Agavotaguerra
+ TRUETYPE_TAG('a', 'v', 's', 0), // avs = Aushiri
+ TRUETYPE_TAG('a', 'v', 't', 0), // avt = Au
+ TRUETYPE_TAG('a', 'v', 'u', 0), // avu = Avokaya
+ TRUETYPE_TAG('a', 'v', 'v', 0), // avv = Avá-Canoeiro
+ TRUETYPE_TAG('a', 'w', 'a', 0), // awa = Awadhi
+ TRUETYPE_TAG('a', 'w', 'b', 0), // awb = Awa (Papua New Guinea)
+ TRUETYPE_TAG('a', 'w', 'c', 0), // awc = Cicipu
+ TRUETYPE_TAG('a', 'w', 'd', 0), // awd = Arawakan languages
+ TRUETYPE_TAG('a', 'w', 'e', 0), // awe = Awetí
+ TRUETYPE_TAG('a', 'w', 'h', 0), // awh = Awbono
+ TRUETYPE_TAG('a', 'w', 'i', 0), // awi = Aekyom
+ TRUETYPE_TAG('a', 'w', 'k', 0), // awk = Awabakal
+ TRUETYPE_TAG('a', 'w', 'm', 0), // awm = Arawum
+ TRUETYPE_TAG('a', 'w', 'n', 0), // awn = Awngi
+ TRUETYPE_TAG('a', 'w', 'o', 0), // awo = Awak
+ TRUETYPE_TAG('a', 'w', 'r', 0), // awr = Awera
+ TRUETYPE_TAG('a', 'w', 's', 0), // aws = South Awyu
+ TRUETYPE_TAG('a', 'w', 't', 0), // awt = Araweté
+ TRUETYPE_TAG('a', 'w', 'u', 0), // awu = Central Awyu
+ TRUETYPE_TAG('a', 'w', 'v', 0), // awv = Jair Awyu
+ TRUETYPE_TAG('a', 'w', 'w', 0), // aww = Awun
+ TRUETYPE_TAG('a', 'w', 'x', 0), // awx = Awara
+ TRUETYPE_TAG('a', 'w', 'y', 0), // awy = Edera Awyu
+ TRUETYPE_TAG('a', 'x', 'b', 0), // axb = Abipon
+ TRUETYPE_TAG('a', 'x', 'g', 0), // axg = Mato Grosso Arára
+ TRUETYPE_TAG('a', 'x', 'k', 0), // axk = Yaka (Central African Republic)
+ TRUETYPE_TAG('a', 'x', 'm', 0), // axm = Middle Armenian
+ TRUETYPE_TAG('a', 'x', 'x', 0), // axx = Xaragure
+ TRUETYPE_TAG('a', 'y', 'a', 0), // aya = Awar
+ TRUETYPE_TAG('a', 'y', 'b', 0), // ayb = Ayizo Gbe
+ TRUETYPE_TAG('a', 'y', 'c', 0), // ayc = Southern Aymara
+ TRUETYPE_TAG('a', 'y', 'd', 0), // ayd = Ayabadhu
+ TRUETYPE_TAG('a', 'y', 'e', 0), // aye = Ayere
+ TRUETYPE_TAG('a', 'y', 'g', 0), // ayg = Ginyanga
+ TRUETYPE_TAG('a', 'y', 'h', 0), // ayh = Hadrami Arabic
+ TRUETYPE_TAG('a', 'y', 'i', 0), // ayi = Leyigha
+ TRUETYPE_TAG('a', 'y', 'k', 0), // ayk = Akuku
+ TRUETYPE_TAG('a', 'y', 'l', 0), // ayl = Libyan Arabic
+ TRUETYPE_TAG('a', 'y', 'n', 0), // ayn = Sanaani Arabic
+ TRUETYPE_TAG('a', 'y', 'o', 0), // ayo = Ayoreo
+ TRUETYPE_TAG('a', 'y', 'p', 0), // ayp = North Mesopotamian Arabic
+ TRUETYPE_TAG('a', 'y', 'q', 0), // ayq = Ayi (Papua New Guinea)
+ TRUETYPE_TAG('a', 'y', 'r', 0), // ayr = Central Aymara
+ TRUETYPE_TAG('a', 'y', 's', 0), // ays = Sorsogon Ayta
+ TRUETYPE_TAG('a', 'y', 't', 0), // ayt = Magbukun Ayta
+ TRUETYPE_TAG('a', 'y', 'u', 0), // ayu = Ayu
+ TRUETYPE_TAG('a', 'y', 'x', 0), // ayx = Ayi (China)
+ TRUETYPE_TAG('a', 'y', 'y', 0), // ayy = Tayabas Ayta
+ TRUETYPE_TAG('a', 'y', 'z', 0), // ayz = Mai Brat
+ TRUETYPE_TAG('a', 'z', 'a', 0), // aza = Azha
+ TRUETYPE_TAG('a', 'z', 'b', 0), // azb = South Azerbaijani
+ TRUETYPE_TAG('a', 'z', 'c', 0), // azc = Uto-Aztecan languages
+ TRUETYPE_TAG('a', 'z', 'g', 0), // azg = San Pedro Amuzgos Amuzgo
+ TRUETYPE_TAG('a', 'z', 'j', 0), // azj = North Azerbaijani
+ TRUETYPE_TAG('a', 'z', 'm', 0), // azm = Ipalapa Amuzgo
+ TRUETYPE_TAG('a', 'z', 'o', 0), // azo = Awing
+ TRUETYPE_TAG('a', 'z', 't', 0), // azt = Faire Atta
+ TRUETYPE_TAG('a', 'z', 'z', 0), // azz = Highland Puebla Nahuatl
+ TRUETYPE_TAG('b', 'a', 'a', 0), // baa = Babatana
+ TRUETYPE_TAG('b', 'a', 'b', 0), // bab = Bainouk-Gunyuño
+ TRUETYPE_TAG('b', 'a', 'c', 0), // bac = Badui
+ TRUETYPE_TAG('b', 'a', 'd', 0), // bad = Banda languages
+ TRUETYPE_TAG('b', 'a', 'e', 0), // bae = Baré
+ TRUETYPE_TAG('b', 'a', 'f', 0), // baf = Nubaca
+ TRUETYPE_TAG('b', 'a', 'g', 0), // bag = Tuki
+ TRUETYPE_TAG('b', 'a', 'h', 0), // bah = Bahamas Creole English
+ TRUETYPE_TAG('b', 'a', 'i', 0), // bai = Bamileke languages
+ TRUETYPE_TAG('b', 'a', 'j', 0), // baj = Barakai
+ TRUETYPE_TAG('b', 'a', 'l', 0), // bal = Baluchi
+ TRUETYPE_TAG('b', 'a', 'n', 0), // ban = Balinese
+ TRUETYPE_TAG('b', 'a', 'o', 0), // bao = Waimaha
+ TRUETYPE_TAG('b', 'a', 'p', 0), // bap = Bantawa
+ TRUETYPE_TAG('b', 'a', 'r', 0), // bar = Bavarian
+ TRUETYPE_TAG('b', 'a', 's', 0), // bas = Basa (Cameroon)
+ TRUETYPE_TAG('b', 'a', 't', 0), // bat = Baltic languages
+ TRUETYPE_TAG('b', 'a', 'u', 0), // bau = Bada (Nigeria)
+ TRUETYPE_TAG('b', 'a', 'v', 0), // bav = Vengo
+ TRUETYPE_TAG('b', 'a', 'w', 0), // baw = Bambili-Bambui
+ TRUETYPE_TAG('b', 'a', 'x', 0), // bax = Bamun
+ TRUETYPE_TAG('b', 'a', 'y', 0), // bay = Batuley
+ TRUETYPE_TAG('b', 'a', 'z', 0), // baz = Tunen
+ TRUETYPE_TAG('b', 'b', 'a', 0), // bba = Baatonum
+ TRUETYPE_TAG('b', 'b', 'b', 0), // bbb = Barai
+ TRUETYPE_TAG('b', 'b', 'c', 0), // bbc = Batak Toba
+ TRUETYPE_TAG('b', 'b', 'd', 0), // bbd = Bau
+ TRUETYPE_TAG('b', 'b', 'e', 0), // bbe = Bangba
+ TRUETYPE_TAG('b', 'b', 'f', 0), // bbf = Baibai
+ TRUETYPE_TAG('b', 'b', 'g', 0), // bbg = Barama
+ TRUETYPE_TAG('b', 'b', 'h', 0), // bbh = Bugan
+ TRUETYPE_TAG('b', 'b', 'i', 0), // bbi = Barombi
+ TRUETYPE_TAG('b', 'b', 'j', 0), // bbj = Ghomálá'
+ TRUETYPE_TAG('b', 'b', 'k', 0), // bbk = Babanki
+ TRUETYPE_TAG('b', 'b', 'l', 0), // bbl = Bats
+ TRUETYPE_TAG('b', 'b', 'm', 0), // bbm = Babango
+ TRUETYPE_TAG('b', 'b', 'n', 0), // bbn = Uneapa
+ TRUETYPE_TAG('b', 'b', 'o', 0), // bbo = Northern Bobo Madaré
+ TRUETYPE_TAG('b', 'b', 'p', 0), // bbp = West Central Banda
+ TRUETYPE_TAG('b', 'b', 'q', 0), // bbq = Bamali
+ TRUETYPE_TAG('b', 'b', 'r', 0), // bbr = Girawa
+ TRUETYPE_TAG('b', 'b', 's', 0), // bbs = Bakpinka
+ TRUETYPE_TAG('b', 'b', 't', 0), // bbt = Mburku
+ TRUETYPE_TAG('b', 'b', 'u', 0), // bbu = Kulung (Nigeria)
+ TRUETYPE_TAG('b', 'b', 'v', 0), // bbv = Karnai
+ TRUETYPE_TAG('b', 'b', 'w', 0), // bbw = Baba
+ TRUETYPE_TAG('b', 'b', 'x', 0), // bbx = Bubia
+ TRUETYPE_TAG('b', 'b', 'y', 0), // bby = Befang
+ TRUETYPE_TAG('b', 'b', 'z', 0), // bbz = Babalia Creole Arabic
+ TRUETYPE_TAG('b', 'c', 'a', 0), // bca = Central Bai
+ TRUETYPE_TAG('b', 'c', 'b', 0), // bcb = Bainouk-Samik
+ TRUETYPE_TAG('b', 'c', 'c', 0), // bcc = Southern Balochi
+ TRUETYPE_TAG('b', 'c', 'd', 0), // bcd = North Babar
+ TRUETYPE_TAG('b', 'c', 'e', 0), // bce = Bamenyam
+ TRUETYPE_TAG('b', 'c', 'f', 0), // bcf = Bamu
+ TRUETYPE_TAG('b', 'c', 'g', 0), // bcg = Baga Binari
+ TRUETYPE_TAG('b', 'c', 'h', 0), // bch = Bariai
+ TRUETYPE_TAG('b', 'c', 'i', 0), // bci = Baoulé
+ TRUETYPE_TAG('b', 'c', 'j', 0), // bcj = Bardi
+ TRUETYPE_TAG('b', 'c', 'k', 0), // bck = Bunaba
+ TRUETYPE_TAG('b', 'c', 'l', 0), // bcl = Central Bicolano
+ TRUETYPE_TAG('b', 'c', 'm', 0), // bcm = Bannoni
+ TRUETYPE_TAG('b', 'c', 'n', 0), // bcn = Bali (Nigeria)
+ TRUETYPE_TAG('b', 'c', 'o', 0), // bco = Kaluli
+ TRUETYPE_TAG('b', 'c', 'p',
+ 0), // bcp = Bali (Democratic Republic of Congo)
+ TRUETYPE_TAG('b', 'c', 'q', 0), // bcq = Bench
+ TRUETYPE_TAG('b', 'c', 'r', 0), // bcr = Babine
+ TRUETYPE_TAG('b', 'c', 's', 0), // bcs = Kohumono
+ TRUETYPE_TAG('b', 'c', 't', 0), // bct = Bendi
+ TRUETYPE_TAG('b', 'c', 'u', 0), // bcu = Awad Bing
+ TRUETYPE_TAG('b', 'c', 'v', 0), // bcv = Shoo-Minda-Nye
+ TRUETYPE_TAG('b', 'c', 'w', 0), // bcw = Bana
+ TRUETYPE_TAG('b', 'c', 'y', 0), // bcy = Bacama
+ TRUETYPE_TAG('b', 'c', 'z', 0), // bcz = Bainouk-Gunyaamolo
+ TRUETYPE_TAG('b', 'd', 'a', 0), // bda = Bayot
+ TRUETYPE_TAG('b', 'd', 'b', 0), // bdb = Basap
+ TRUETYPE_TAG('b', 'd', 'c', 0), // bdc = Emberá-Baudó
+ TRUETYPE_TAG('b', 'd', 'd', 0), // bdd = Bunama
+ TRUETYPE_TAG('b', 'd', 'e', 0), // bde = Bade
+ TRUETYPE_TAG('b', 'd', 'f', 0), // bdf = Biage
+ TRUETYPE_TAG('b', 'd', 'g', 0), // bdg = Bonggi
+ TRUETYPE_TAG('b', 'd', 'h', 0), // bdh = Baka (Sudan)
+ TRUETYPE_TAG('b', 'd', 'i', 0), // bdi = Burun
+ TRUETYPE_TAG('b', 'd', 'j', 0), // bdj = Bai
+ TRUETYPE_TAG('b', 'd', 'k', 0), // bdk = Budukh
+ TRUETYPE_TAG('b', 'd', 'l', 0), // bdl = Indonesian Bajau
+ TRUETYPE_TAG('b', 'd', 'm', 0), // bdm = Buduma
+ TRUETYPE_TAG('b', 'd', 'n', 0), // bdn = Baldemu
+ TRUETYPE_TAG('b', 'd', 'o', 0), // bdo = Morom
+ TRUETYPE_TAG('b', 'd', 'p', 0), // bdp = Bende
+ TRUETYPE_TAG('b', 'd', 'q', 0), // bdq = Bahnar
+ TRUETYPE_TAG('b', 'd', 'r', 0), // bdr = West Coast Bajau
+ TRUETYPE_TAG('b', 'd', 's', 0), // bds = Burunge
+ TRUETYPE_TAG('b', 'd', 't', 0), // bdt = Bokoto
+ TRUETYPE_TAG('b', 'd', 'u', 0), // bdu = Oroko
+ TRUETYPE_TAG('b', 'd', 'v', 0), // bdv = Bodo Parja
+ TRUETYPE_TAG('b', 'd', 'w', 0), // bdw = Baham
+ TRUETYPE_TAG('b', 'd', 'x', 0), // bdx = Budong-Budong
+ TRUETYPE_TAG('b', 'd', 'y', 0), // bdy = Bandjalang
+ TRUETYPE_TAG('b', 'd', 'z', 0), // bdz = Badeshi
+ TRUETYPE_TAG('b', 'e', 'a', 0), // bea = Beaver
+ TRUETYPE_TAG('b', 'e', 'b', 0), // beb = Bebele
+ TRUETYPE_TAG('b', 'e', 'c', 0), // bec = Iceve-Maci
+ TRUETYPE_TAG('b', 'e', 'd', 0), // bed = Bedoanas
+ TRUETYPE_TAG('b', 'e', 'e', 0), // bee = Byangsi
+ TRUETYPE_TAG('b', 'e', 'f', 0), // bef = Benabena
+ TRUETYPE_TAG('b', 'e', 'g', 0), // beg = Belait
+ TRUETYPE_TAG('b', 'e', 'h', 0), // beh = Biali
+ TRUETYPE_TAG('b', 'e', 'i', 0), // bei = Bekati'
+ TRUETYPE_TAG('b', 'e', 'j', 0), // bej = Beja
+ TRUETYPE_TAG('b', 'e', 'k', 0), // bek = Bebeli
+ TRUETYPE_TAG('b', 'e', 'm', 0), // bem = Bemba (Zambia)
+ TRUETYPE_TAG('b', 'e', 'o', 0), // beo = Beami
+ TRUETYPE_TAG('b', 'e', 'p', 0), // bep = Besoa
+ TRUETYPE_TAG('b', 'e', 'q', 0), // beq = Beembe
+ TRUETYPE_TAG('b', 'e', 'r', 0), // ber = Berber languages
+ TRUETYPE_TAG('b', 'e', 's', 0), // bes = Besme
+ TRUETYPE_TAG('b', 'e', 't', 0), // bet = Guiberoua Béte
+ TRUETYPE_TAG('b', 'e', 'u', 0), // beu = Blagar
+ TRUETYPE_TAG('b', 'e', 'v', 0), // bev = Daloa Bété
+ TRUETYPE_TAG('b', 'e', 'w', 0), // bew = Betawi
+ TRUETYPE_TAG('b', 'e', 'x', 0), // bex = Jur Modo
+ TRUETYPE_TAG('b', 'e', 'y', 0), // bey = Beli (Papua New Guinea)
+ TRUETYPE_TAG('b', 'e', 'z', 0), // bez = Bena (Tanzania)
+ TRUETYPE_TAG('b', 'f', 'a', 0), // bfa = Bari
+ TRUETYPE_TAG('b', 'f', 'b', 0), // bfb = Pauri Bareli
+ TRUETYPE_TAG('b', 'f', 'c', 0), // bfc = Northern Bai
+ TRUETYPE_TAG('b', 'f', 'd', 0), // bfd = Bafut
+ TRUETYPE_TAG('b', 'f', 'e', 0), // bfe = Betaf
+ TRUETYPE_TAG('b', 'f', 'f', 0), // bff = Bofi
+ TRUETYPE_TAG('b', 'f', 'g', 0), // bfg = Busang Kayan
+ TRUETYPE_TAG('b', 'f', 'h', 0), // bfh = Blafe
+ TRUETYPE_TAG('b', 'f', 'i', 0), // bfi = British Sign Language
+ TRUETYPE_TAG('b', 'f', 'j', 0), // bfj = Bafanji
+ TRUETYPE_TAG('b', 'f', 'k', 0), // bfk = Ban Khor Sign Language
+ TRUETYPE_TAG('b', 'f', 'l', 0), // bfl = Banda-Ndélé
+ TRUETYPE_TAG('b', 'f', 'm', 0), // bfm = Mmen
+ TRUETYPE_TAG('b', 'f', 'n', 0), // bfn = Bunak
+ TRUETYPE_TAG('b', 'f', 'o', 0), // bfo = Malba Birifor
+ TRUETYPE_TAG('b', 'f', 'p', 0), // bfp = Beba
+ TRUETYPE_TAG('b', 'f', 'q', 0), // bfq = Badaga
+ TRUETYPE_TAG('b', 'f', 'r', 0), // bfr = Bazigar
+ TRUETYPE_TAG('b', 'f', 's', 0), // bfs = Southern Bai
+ TRUETYPE_TAG('b', 'f', 't', 0), // bft = Balti
+ TRUETYPE_TAG('b', 'f', 'u', 0), // bfu = Gahri
+ TRUETYPE_TAG('b', 'f', 'w', 0), // bfw = Bondo
+ TRUETYPE_TAG('b', 'f', 'x', 0), // bfx = Bantayanon
+ TRUETYPE_TAG('b', 'f', 'y', 0), // bfy = Bagheli
+ TRUETYPE_TAG('b', 'f', 'z', 0), // bfz = Mahasu Pahari
+ TRUETYPE_TAG('b', 'g', 'a', 0), // bga = Gwamhi-Wuri
+ TRUETYPE_TAG('b', 'g', 'b', 0), // bgb = Bobongko
+ TRUETYPE_TAG('b', 'g', 'c', 0), // bgc = Haryanvi
+ TRUETYPE_TAG('b', 'g', 'd', 0), // bgd = Rathwi Bareli
+ TRUETYPE_TAG('b', 'g', 'e', 0), // bge = Bauria
+ TRUETYPE_TAG('b', 'g', 'f', 0), // bgf = Bangandu
+ TRUETYPE_TAG('b', 'g', 'g', 0), // bgg = Bugun
+ TRUETYPE_TAG('b', 'g', 'i', 0), // bgi = Giangan
+ TRUETYPE_TAG('b', 'g', 'j', 0), // bgj = Bangolan
+ TRUETYPE_TAG('b', 'g', 'k', 0), // bgk = Bit
+ TRUETYPE_TAG('b', 'g', 'l', 0), // bgl = Bo (Laos)
+ TRUETYPE_TAG('b', 'g', 'm', 0), // bgm = Baga Mboteni
+ TRUETYPE_TAG('b', 'g', 'n', 0), // bgn = Western Balochi
+ TRUETYPE_TAG('b', 'g', 'o', 0), // bgo = Baga Koga
+ TRUETYPE_TAG('b', 'g', 'p', 0), // bgp = Eastern Balochi
+ TRUETYPE_TAG('b', 'g', 'q', 0), // bgq = Bagri
+ TRUETYPE_TAG('b', 'g', 'r', 0), // bgr = Bawm Chin
+ TRUETYPE_TAG('b', 'g', 's', 0), // bgs = Tagabawa
+ TRUETYPE_TAG('b', 'g', 't', 0), // bgt = Bughotu
+ TRUETYPE_TAG('b', 'g', 'u', 0), // bgu = Mbongno
+ TRUETYPE_TAG('b', 'g', 'v', 0), // bgv = Warkay-Bipim
+ TRUETYPE_TAG('b', 'g', 'w', 0), // bgw = Bhatri
+ TRUETYPE_TAG('b', 'g', 'x', 0), // bgx = Balkan Gagauz Turkish
+ TRUETYPE_TAG('b', 'g', 'y', 0), // bgy = Benggoi
+ TRUETYPE_TAG('b', 'g', 'z', 0), // bgz = Banggai
+ TRUETYPE_TAG('b', 'h', 'a', 0), // bha = Bharia
+ TRUETYPE_TAG('b', 'h', 'b', 0), // bhb = Bhili
+ TRUETYPE_TAG('b', 'h', 'c', 0), // bhc = Biga
+ TRUETYPE_TAG('b', 'h', 'd', 0), // bhd = Bhadrawahi
+ TRUETYPE_TAG('b', 'h', 'e', 0), // bhe = Bhaya
+ TRUETYPE_TAG('b', 'h', 'f', 0), // bhf = Odiai
+ TRUETYPE_TAG('b', 'h', 'g', 0), // bhg = Binandere
+ TRUETYPE_TAG('b', 'h', 'h', 0), // bhh = Bukharic
+ TRUETYPE_TAG('b', 'h', 'i', 0), // bhi = Bhilali
+ TRUETYPE_TAG('b', 'h', 'j', 0), // bhj = Bahing
+ TRUETYPE_TAG('b', 'h', 'k', 0), // bhk = Albay Bicolano
+ TRUETYPE_TAG('b', 'h', 'l', 0), // bhl = Bimin
+ TRUETYPE_TAG('b', 'h', 'm', 0), // bhm = Bathari
+ TRUETYPE_TAG('b', 'h', 'n', 0), // bhn = Bohtan Neo-Aramaic
+ TRUETYPE_TAG('b', 'h', 'o', 0), // bho = Bhojpuri
+ TRUETYPE_TAG('b', 'h', 'p', 0), // bhp = Bima
+ TRUETYPE_TAG('b', 'h', 'q', 0), // bhq = Tukang Besi South
+ TRUETYPE_TAG('b', 'h', 'r', 0), // bhr = Bara Malagasy
+ TRUETYPE_TAG('b', 'h', 's', 0), // bhs = Buwal
+ TRUETYPE_TAG('b', 'h', 't', 0), // bht = Bhattiyali
+ TRUETYPE_TAG('b', 'h', 'u', 0), // bhu = Bhunjia
+ TRUETYPE_TAG('b', 'h', 'v', 0), // bhv = Bahau
+ TRUETYPE_TAG('b', 'h', 'w', 0), // bhw = Biak
+ TRUETYPE_TAG('b', 'h', 'x', 0), // bhx = Bhalay
+ TRUETYPE_TAG('b', 'h', 'y', 0), // bhy = Bhele
+ TRUETYPE_TAG('b', 'h', 'z', 0), // bhz = Bada (Indonesia)
+ TRUETYPE_TAG('b', 'i', 'a', 0), // bia = Badimaya
+ TRUETYPE_TAG('b', 'i', 'b', 0), // bib = Bissa
+ TRUETYPE_TAG('b', 'i', 'c', 0), // bic = Bikaru
+ TRUETYPE_TAG('b', 'i', 'd', 0), // bid = Bidiyo
+ TRUETYPE_TAG('b', 'i', 'e', 0), // bie = Bepour
+ TRUETYPE_TAG('b', 'i', 'f', 0), // bif = Biafada
+ TRUETYPE_TAG('b', 'i', 'g', 0), // big = Biangai
+ TRUETYPE_TAG('b', 'i', 'j', 0), // bij = Vaghat-Ya-Bijim-Legeri
+ TRUETYPE_TAG('b', 'i', 'k', 0), // bik = Bikol
+ TRUETYPE_TAG('b', 'i', 'l', 0), // bil = Bile
+ TRUETYPE_TAG('b', 'i', 'm', 0), // bim = Bimoba
+ TRUETYPE_TAG('b', 'i', 'n', 0), // bin = Bini
+ TRUETYPE_TAG('b', 'i', 'o', 0), // bio = Nai
+ TRUETYPE_TAG('b', 'i', 'p', 0), // bip = Bila
+ TRUETYPE_TAG('b', 'i', 'q', 0), // biq = Bipi
+ TRUETYPE_TAG('b', 'i', 'r', 0), // bir = Bisorio
+ TRUETYPE_TAG('b', 'i', 't', 0), // bit = Berinomo
+ TRUETYPE_TAG('b', 'i', 'u', 0), // biu = Biete
+ TRUETYPE_TAG('b', 'i', 'v', 0), // biv = Southern Birifor
+ TRUETYPE_TAG('b', 'i', 'w', 0), // biw = Kol (Cameroon)
+ TRUETYPE_TAG('b', 'i', 'x', 0), // bix = Bijori
+ TRUETYPE_TAG('b', 'i', 'y', 0), // biy = Birhor
+ TRUETYPE_TAG('b', 'i', 'z', 0), // biz = Baloi
+ TRUETYPE_TAG('b', 'j', 'a', 0), // bja = Budza
+ TRUETYPE_TAG('b', 'j', 'b', 0), // bjb = Banggarla
+ TRUETYPE_TAG('b', 'j', 'c', 0), // bjc = Bariji
+ TRUETYPE_TAG('b', 'j', 'd', 0), // bjd = Bandjigali
+ TRUETYPE_TAG('b', 'j', 'e', 0), // bje = Biao-Jiao Mien
+ TRUETYPE_TAG('b', 'j', 'f', 0), // bjf = Barzani Jewish Neo-Aramaic
+ TRUETYPE_TAG('b', 'j', 'g', 0), // bjg = Bidyogo
+ TRUETYPE_TAG('b', 'j', 'h', 0), // bjh = Bahinemo
+ TRUETYPE_TAG('b', 'j', 'i', 0), // bji = Burji
+ TRUETYPE_TAG('b', 'j', 'j', 0), // bjj = Kanauji
+ TRUETYPE_TAG('b', 'j', 'k', 0), // bjk = Barok
+ TRUETYPE_TAG('b', 'j', 'l', 0), // bjl = Bulu (Papua New Guinea)
+ TRUETYPE_TAG('b', 'j', 'm', 0), // bjm = Bajelani
+ TRUETYPE_TAG('b', 'j', 'n', 0), // bjn = Banjar
+ TRUETYPE_TAG('b', 'j', 'o', 0), // bjo = Mid-Southern Banda
+ TRUETYPE_TAG('b', 'j', 'q', 0), // bjq = Southern Betsimisaraka Malagasy
+ TRUETYPE_TAG('b', 'j', 'r', 0), // bjr = Binumarien
+ TRUETYPE_TAG('b', 'j', 's', 0), // bjs = Bajan
+ TRUETYPE_TAG('b', 'j', 't', 0), // bjt = Balanta-Ganja
+ TRUETYPE_TAG('b', 'j', 'u', 0), // bju = Busuu
+ TRUETYPE_TAG('b', 'j', 'v', 0), // bjv = Bedjond
+ TRUETYPE_TAG('b', 'j', 'w', 0), // bjw = Bakwé
+ TRUETYPE_TAG('b', 'j', 'x', 0), // bjx = Banao Itneg
+ TRUETYPE_TAG('b', 'j', 'y', 0), // bjy = Bayali
+ TRUETYPE_TAG('b', 'j', 'z', 0), // bjz = Baruga
+ TRUETYPE_TAG('b', 'k', 'a', 0), // bka = Kyak
+ TRUETYPE_TAG('b', 'k', 'b', 0), // bkb = Finallig
+ TRUETYPE_TAG('b', 'k', 'c', 0), // bkc = Baka (Cameroon)
+ TRUETYPE_TAG('b', 'k', 'd', 0), // bkd = Binukid
+ TRUETYPE_TAG('b', 'k', 'f', 0), // bkf = Beeke
+ TRUETYPE_TAG('b', 'k', 'g', 0), // bkg = Buraka
+ TRUETYPE_TAG('b', 'k', 'h', 0), // bkh = Bakoko
+ TRUETYPE_TAG('b', 'k', 'i', 0), // bki = Baki
+ TRUETYPE_TAG('b', 'k', 'j', 0), // bkj = Pande
+ TRUETYPE_TAG('b', 'k', 'k', 0), // bkk = Brokskat
+ TRUETYPE_TAG('b', 'k', 'l', 0), // bkl = Berik
+ TRUETYPE_TAG('b', 'k', 'm', 0), // bkm = Kom (Cameroon)
+ TRUETYPE_TAG('b', 'k', 'n', 0), // bkn = Bukitan
+ TRUETYPE_TAG('b', 'k', 'o', 0), // bko = Kwa'
+ TRUETYPE_TAG('b', 'k', 'p',
+ 0), // bkp = Boko (Democratic Republic of Congo)
+ TRUETYPE_TAG('b', 'k', 'q', 0), // bkq = Bakairí
+ TRUETYPE_TAG('b', 'k', 'r', 0), // bkr = Bakumpai
+ TRUETYPE_TAG('b', 'k', 's', 0), // bks = Northern Sorsoganon
+ TRUETYPE_TAG('b', 'k', 't', 0), // bkt = Boloki
+ TRUETYPE_TAG('b', 'k', 'u', 0), // bku = Buhid
+ TRUETYPE_TAG('b', 'k', 'v', 0), // bkv = Bekwarra
+ TRUETYPE_TAG('b', 'k', 'w', 0), // bkw = Bekwel
+ TRUETYPE_TAG('b', 'k', 'x', 0), // bkx = Baikeno
+ TRUETYPE_TAG('b', 'k', 'y', 0), // bky = Bokyi
+ TRUETYPE_TAG('b', 'k', 'z', 0), // bkz = Bungku
+ TRUETYPE_TAG('b', 'l', 'a', 0), // bla = Siksika
+ TRUETYPE_TAG('b', 'l', 'b', 0), // blb = Bilua
+ TRUETYPE_TAG('b', 'l', 'c', 0), // blc = Bella Coola
+ TRUETYPE_TAG('b', 'l', 'd', 0), // bld = Bolango
+ TRUETYPE_TAG('b', 'l', 'e', 0), // ble = Balanta-Kentohe
+ TRUETYPE_TAG('b', 'l', 'f', 0), // blf = Buol
+ TRUETYPE_TAG('b', 'l', 'g', 0), // blg = Balau
+ TRUETYPE_TAG('b', 'l', 'h', 0), // blh = Kuwaa
+ TRUETYPE_TAG('b', 'l', 'i', 0), // bli = Bolia
+ TRUETYPE_TAG('b', 'l', 'j', 0), // blj = Bolongan
+ TRUETYPE_TAG('b', 'l', 'k', 0), // blk = Pa'o Karen
+ TRUETYPE_TAG('b', 'l', 'l', 0), // bll = Biloxi
+ TRUETYPE_TAG('b', 'l', 'm', 0), // blm = Beli (Sudan)
+ TRUETYPE_TAG('b', 'l', 'n', 0), // bln = Southern Catanduanes Bicolano
+ TRUETYPE_TAG('b', 'l', 'o', 0), // blo = Anii
+ TRUETYPE_TAG('b', 'l', 'p', 0), // blp = Blablanga
+ TRUETYPE_TAG('b', 'l', 'q', 0), // blq = Baluan-Pam
+ TRUETYPE_TAG('b', 'l', 'r', 0), // blr = Blang
+ TRUETYPE_TAG('b', 'l', 's', 0), // bls = Balaesang
+ TRUETYPE_TAG('b', 'l', 't', 0), // blt = Tai Dam
+ TRUETYPE_TAG('b', 'l', 'v', 0), // blv = Bolo
+ TRUETYPE_TAG('b', 'l', 'w', 0), // blw = Balangao
+ TRUETYPE_TAG('b', 'l', 'x', 0), // blx = Mag-Indi Ayta
+ TRUETYPE_TAG('b', 'l', 'y', 0), // bly = Notre
+ TRUETYPE_TAG('b', 'l', 'z', 0), // blz = Balantak
+ TRUETYPE_TAG('b', 'm', 'a', 0), // bma = Lame
+ TRUETYPE_TAG('b', 'm', 'b', 0), // bmb = Bembe
+ TRUETYPE_TAG('b', 'm', 'c', 0), // bmc = Biem
+ TRUETYPE_TAG('b', 'm', 'd', 0), // bmd = Baga Manduri
+ TRUETYPE_TAG('b', 'm', 'e', 0), // bme = Limassa
+ TRUETYPE_TAG('b', 'm', 'f', 0), // bmf = Bom
+ TRUETYPE_TAG('b', 'm', 'g', 0), // bmg = Bamwe
+ TRUETYPE_TAG('b', 'm', 'h', 0), // bmh = Kein
+ TRUETYPE_TAG('b', 'm', 'i', 0), // bmi = Bagirmi
+ TRUETYPE_TAG('b', 'm', 'j', 0), // bmj = Bote-Majhi
+ TRUETYPE_TAG('b', 'm', 'k', 0), // bmk = Ghayavi
+ TRUETYPE_TAG('b', 'm', 'l', 0), // bml = Bomboli
+ TRUETYPE_TAG('b', 'm', 'm', 0), // bmm = Northern Betsimisaraka Malagasy
+ TRUETYPE_TAG('b', 'm', 'n', 0), // bmn = Bina (Papua New Guinea)
+ TRUETYPE_TAG('b', 'm', 'o', 0), // bmo = Bambalang
+ TRUETYPE_TAG('b', 'm', 'p', 0), // bmp = Bulgebi
+ TRUETYPE_TAG('b', 'm', 'q', 0), // bmq = Bomu
+ TRUETYPE_TAG('b', 'm', 'r', 0), // bmr = Muinane
+ TRUETYPE_TAG('b', 'm', 's', 0), // bms = Bilma Kanuri
+ TRUETYPE_TAG('b', 'm', 't', 0), // bmt = Biao Mon
+ TRUETYPE_TAG('b', 'm', 'u', 0), // bmu = Somba-Siawari
+ TRUETYPE_TAG('b', 'm', 'v', 0), // bmv = Bum
+ TRUETYPE_TAG('b', 'm', 'w', 0), // bmw = Bomwali
+ TRUETYPE_TAG('b', 'm', 'x', 0), // bmx = Baimak
+ TRUETYPE_TAG('b', 'm', 'y',
+ 0), // bmy = Bemba (Democratic Republic of Congo)
+ TRUETYPE_TAG('b', 'm', 'z', 0), // bmz = Baramu
+ TRUETYPE_TAG('b', 'n', 'a', 0), // bna = Bonerate
+ TRUETYPE_TAG('b', 'n', 'b', 0), // bnb = Bookan
+ TRUETYPE_TAG('b', 'n', 'c', 0), // bnc = Bontok
+ TRUETYPE_TAG('b', 'n', 'd', 0), // bnd = Banda (Indonesia)
+ TRUETYPE_TAG('b', 'n', 'e', 0), // bne = Bintauna
+ TRUETYPE_TAG('b', 'n', 'f', 0), // bnf = Masiwang
+ TRUETYPE_TAG('b', 'n', 'g', 0), // bng = Benga
+ TRUETYPE_TAG('b', 'n', 'i', 0), // bni = Bangi
+ TRUETYPE_TAG('b', 'n', 'j', 0), // bnj = Eastern Tawbuid
+ TRUETYPE_TAG('b', 'n', 'k', 0), // bnk = Bierebo
+ TRUETYPE_TAG('b', 'n', 'l', 0), // bnl = Boon
+ TRUETYPE_TAG('b', 'n', 'm', 0), // bnm = Batanga
+ TRUETYPE_TAG('b', 'n', 'n', 0), // bnn = Bunun
+ TRUETYPE_TAG('b', 'n', 'o', 0), // bno = Bantoanon
+ TRUETYPE_TAG('b', 'n', 'p', 0), // bnp = Bola
+ TRUETYPE_TAG('b', 'n', 'q', 0), // bnq = Bantik
+ TRUETYPE_TAG('b', 'n', 'r', 0), // bnr = Butmas-Tur
+ TRUETYPE_TAG('b', 'n', 's', 0), // bns = Bundeli
+ TRUETYPE_TAG('b', 'n', 't', 0), // bnt = Bantu languages
+ TRUETYPE_TAG('b', 'n', 'u', 0), // bnu = Bentong
+ TRUETYPE_TAG('b', 'n', 'v', 0), // bnv = Bonerif
+ TRUETYPE_TAG('b', 'n', 'w', 0), // bnw = Bisis
+ TRUETYPE_TAG('b', 'n', 'x', 0), // bnx = Bangubangu
+ TRUETYPE_TAG('b', 'n', 'y', 0), // bny = Bintulu
+ TRUETYPE_TAG('b', 'n', 'z', 0), // bnz = Beezen
+ TRUETYPE_TAG('b', 'o', 'a', 0), // boa = Bora
+ TRUETYPE_TAG('b', 'o', 'b', 0), // bob = Aweer
+ TRUETYPE_TAG('b', 'o', 'e', 0), // boe = Mundabli
+ TRUETYPE_TAG('b', 'o', 'f', 0), // bof = Bolon
+ TRUETYPE_TAG('b', 'o', 'g', 0), // bog = Bamako Sign Language
+ TRUETYPE_TAG('b', 'o', 'h', 0), // boh = Boma
+ TRUETYPE_TAG('b', 'o', 'i', 0), // boi = Barbareño
+ TRUETYPE_TAG('b', 'o', 'j', 0), // boj = Anjam
+ TRUETYPE_TAG('b', 'o', 'k', 0), // bok = Bonjo
+ TRUETYPE_TAG('b', 'o', 'l', 0), // bol = Bole
+ TRUETYPE_TAG('b', 'o', 'm', 0), // bom = Berom
+ TRUETYPE_TAG('b', 'o', 'n', 0), // bon = Bine
+ TRUETYPE_TAG('b', 'o', 'o', 0), // boo = Tiemacèwè Bozo
+ TRUETYPE_TAG('b', 'o', 'p', 0), // bop = Bonkiman
+ TRUETYPE_TAG('b', 'o', 'q', 0), // boq = Bogaya
+ TRUETYPE_TAG('b', 'o', 'r', 0), // bor = Borôro
+ TRUETYPE_TAG('b', 'o', 't', 0), // bot = Bongo
+ TRUETYPE_TAG('b', 'o', 'u', 0), // bou = Bondei
+ TRUETYPE_TAG('b', 'o', 'v', 0), // bov = Tuwuli
+ TRUETYPE_TAG('b', 'o', 'w', 0), // bow = Rema
+ TRUETYPE_TAG('b', 'o', 'x', 0), // box = Buamu
+ TRUETYPE_TAG('b', 'o', 'y', 0), // boy = Bodo (Central African Republic)
+ TRUETYPE_TAG('b', 'o', 'z', 0), // boz = Tiéyaxo Bozo
+ TRUETYPE_TAG('b', 'p', 'a', 0), // bpa = Dakaka
+ TRUETYPE_TAG('b', 'p', 'b', 0), // bpb = Barbacoas
+ TRUETYPE_TAG('b', 'p', 'd', 0), // bpd = Banda-Banda
+ TRUETYPE_TAG('b', 'p', 'g', 0), // bpg = Bonggo
+ TRUETYPE_TAG('b', 'p', 'h', 0), // bph = Botlikh
+ TRUETYPE_TAG('b', 'p', 'i', 0), // bpi = Bagupi
+ TRUETYPE_TAG('b', 'p', 'j', 0), // bpj = Binji
+ TRUETYPE_TAG('b', 'p', 'k', 0), // bpk = Orowe
+ TRUETYPE_TAG('b', 'p', 'l', 0), // bpl = Broome Pearling Lugger Pidgin
+ TRUETYPE_TAG('b', 'p', 'm', 0), // bpm = Biyom
+ TRUETYPE_TAG('b', 'p', 'n', 0), // bpn = Dzao Min
+ TRUETYPE_TAG('b', 'p', 'o', 0), // bpo = Anasi
+ TRUETYPE_TAG('b', 'p', 'p', 0), // bpp = Kaure
+ TRUETYPE_TAG('b', 'p', 'q', 0), // bpq = Banda Malay
+ TRUETYPE_TAG('b', 'p', 'r', 0), // bpr = Koronadal Blaan
+ TRUETYPE_TAG('b', 'p', 's', 0), // bps = Sarangani Blaan
+ TRUETYPE_TAG('b', 'p', 't', 0), // bpt = Barrow Point
+ TRUETYPE_TAG('b', 'p', 'u', 0), // bpu = Bongu
+ TRUETYPE_TAG('b', 'p', 'v', 0), // bpv = Bian Marind
+ TRUETYPE_TAG('b', 'p', 'w', 0), // bpw = Bo (Papua New Guinea)
+ TRUETYPE_TAG('b', 'p', 'x', 0), // bpx = Palya Bareli
+ TRUETYPE_TAG('b', 'p', 'y', 0), // bpy = Bishnupriya
+ TRUETYPE_TAG('b', 'p', 'z', 0), // bpz = Bilba
+ TRUETYPE_TAG('b', 'q', 'a', 0), // bqa = Tchumbuli
+ TRUETYPE_TAG('b', 'q', 'b', 0), // bqb = Bagusa
+ TRUETYPE_TAG('b', 'q', 'c', 0), // bqc = Boko (Benin)
+ TRUETYPE_TAG('b', 'q', 'd', 0), // bqd = Bung
+ TRUETYPE_TAG('b', 'q', 'f', 0), // bqf = Baga Kaloum
+ TRUETYPE_TAG('b', 'q', 'g', 0), // bqg = Bago-Kusuntu
+ TRUETYPE_TAG('b', 'q', 'h', 0), // bqh = Baima
+ TRUETYPE_TAG('b', 'q', 'i', 0), // bqi = Bakhtiari
+ TRUETYPE_TAG('b', 'q', 'j', 0), // bqj = Bandial
+ TRUETYPE_TAG('b', 'q', 'k', 0), // bqk = Banda-Mbrès
+ TRUETYPE_TAG('b', 'q', 'l', 0), // bql = Bilakura
+ TRUETYPE_TAG('b', 'q', 'm', 0), // bqm = Wumboko
+ TRUETYPE_TAG('b', 'q', 'n', 0), // bqn = Bulgarian Sign Language
+ TRUETYPE_TAG('b', 'q', 'o', 0), // bqo = Balo
+ TRUETYPE_TAG('b', 'q', 'p', 0), // bqp = Busa
+ TRUETYPE_TAG('b', 'q', 'q', 0), // bqq = Biritai
+ TRUETYPE_TAG('b', 'q', 'r', 0), // bqr = Burusu
+ TRUETYPE_TAG('b', 'q', 's', 0), // bqs = Bosngun
+ TRUETYPE_TAG('b', 'q', 't', 0), // bqt = Bamukumbit
+ TRUETYPE_TAG('b', 'q', 'u', 0), // bqu = Boguru
+ TRUETYPE_TAG('b', 'q', 'v', 0), // bqv = Begbere-Ejar
+ TRUETYPE_TAG('b', 'q', 'w', 0), // bqw = Buru (Nigeria)
+ TRUETYPE_TAG('b', 'q', 'x', 0), // bqx = Baangi
+ TRUETYPE_TAG('b', 'q', 'y', 0), // bqy = Bengkala Sign Language
+ TRUETYPE_TAG('b', 'q', 'z', 0), // bqz = Bakaka
+ TRUETYPE_TAG('b', 'r', 'a', 0), // bra = Braj
+ TRUETYPE_TAG('b', 'r', 'b', 0), // brb = Lave
+ TRUETYPE_TAG('b', 'r', 'c', 0), // brc = Berbice Creole Dutch
+ TRUETYPE_TAG('b', 'r', 'd', 0), // brd = Baraamu
+ TRUETYPE_TAG('b', 'r', 'f', 0), // brf = Bera
+ TRUETYPE_TAG('b', 'r', 'g', 0), // brg = Baure
+ TRUETYPE_TAG('b', 'r', 'h', 0), // brh = Brahui
+ TRUETYPE_TAG('b', 'r', 'i', 0), // bri = Mokpwe
+ TRUETYPE_TAG('b', 'r', 'j', 0), // brj = Bieria
+ TRUETYPE_TAG('b', 'r', 'k', 0), // brk = Birked
+ TRUETYPE_TAG('b', 'r', 'l', 0), // brl = Birwa
+ TRUETYPE_TAG('b', 'r', 'm', 0), // brm = Barambu
+ TRUETYPE_TAG('b', 'r', 'n', 0), // brn = Boruca
+ TRUETYPE_TAG('b', 'r', 'o', 0), // bro = Brokkat
+ TRUETYPE_TAG('b', 'r', 'p', 0), // brp = Barapasi
+ TRUETYPE_TAG('b', 'r', 'q', 0), // brq = Breri
+ TRUETYPE_TAG('b', 'r', 'r', 0), // brr = Birao
+ TRUETYPE_TAG('b', 'r', 's', 0), // brs = Baras
+ TRUETYPE_TAG('b', 'r', 't', 0), // brt = Bitare
+ TRUETYPE_TAG('b', 'r', 'u', 0), // bru = Eastern Bru
+ TRUETYPE_TAG('b', 'r', 'v', 0), // brv = Western Bru
+ TRUETYPE_TAG('b', 'r', 'w', 0), // brw = Bellari
+ TRUETYPE_TAG('b', 'r', 'x', 0), // brx = Bodo (India)
+ TRUETYPE_TAG('b', 'r', 'y', 0), // bry = Burui
+ TRUETYPE_TAG('b', 'r', 'z', 0), // brz = Bilbil
+ TRUETYPE_TAG('b', 's', 'a', 0), // bsa = Abinomn
+ TRUETYPE_TAG('b', 's', 'b', 0), // bsb = Brunei Bisaya
+ TRUETYPE_TAG('b', 's', 'c', 0), // bsc = Bassari
+ TRUETYPE_TAG('b', 's', 'e', 0), // bse = Wushi
+ TRUETYPE_TAG('b', 's', 'f', 0), // bsf = Bauchi
+ TRUETYPE_TAG('b', 's', 'g', 0), // bsg = Bashkardi
+ TRUETYPE_TAG('b', 's', 'h', 0), // bsh = Kati
+ TRUETYPE_TAG('b', 's', 'i', 0), // bsi = Bassossi
+ TRUETYPE_TAG('b', 's', 'j', 0), // bsj = Bangwinji
+ TRUETYPE_TAG('b', 's', 'k', 0), // bsk = Burushaski
+ TRUETYPE_TAG('b', 's', 'l', 0), // bsl = Basa-Gumna
+ TRUETYPE_TAG('b', 's', 'm', 0), // bsm = Busami
+ TRUETYPE_TAG('b', 's', 'n', 0), // bsn = Barasana-Eduria
+ TRUETYPE_TAG('b', 's', 'o', 0), // bso = Buso
+ TRUETYPE_TAG('b', 's', 'p', 0), // bsp = Baga Sitemu
+ TRUETYPE_TAG('b', 's', 'q', 0), // bsq = Bassa
+ TRUETYPE_TAG('b', 's', 'r', 0), // bsr = Bassa-Kontagora
+ TRUETYPE_TAG('b', 's', 's', 0), // bss = Akoose
+ TRUETYPE_TAG('b', 's', 't', 0), // bst = Basketo
+ TRUETYPE_TAG('b', 's', 'u', 0), // bsu = Bahonsuai
+ TRUETYPE_TAG('b', 's', 'v', 0), // bsv = Baga Sobané
+ TRUETYPE_TAG('b', 's', 'w', 0), // bsw = Baiso
+ TRUETYPE_TAG('b', 's', 'x', 0), // bsx = Yangkam
+ TRUETYPE_TAG('b', 's', 'y', 0), // bsy = Sabah Bisaya
+ TRUETYPE_TAG('b', 't', 'a', 0), // bta = Bata
+ TRUETYPE_TAG('b', 't', 'b', 0), // btb = Beti (Cameroon)
+ TRUETYPE_TAG('b', 't', 'c', 0), // btc = Bati (Cameroon)
+ TRUETYPE_TAG('b', 't', 'd', 0), // btd = Batak Dairi
+ TRUETYPE_TAG('b', 't', 'e', 0), // bte = Gamo-Ningi
+ TRUETYPE_TAG('b', 't', 'f', 0), // btf = Birgit
+ TRUETYPE_TAG('b', 't', 'g', 0), // btg = Gagnoa Bété
+ TRUETYPE_TAG('b', 't', 'h', 0), // bth = Biatah Bidayuh
+ TRUETYPE_TAG('b', 't', 'i', 0), // bti = Burate
+ TRUETYPE_TAG('b', 't', 'j', 0), // btj = Bacanese Malay
+ TRUETYPE_TAG('b', 't', 'k', 0), // btk = Batak languages
+ TRUETYPE_TAG('b', 't', 'l', 0), // btl = Bhatola
+ TRUETYPE_TAG('b', 't', 'm', 0), // btm = Batak Mandailing
+ TRUETYPE_TAG('b', 't', 'n', 0), // btn = Ratagnon
+ TRUETYPE_TAG('b', 't', 'o', 0), // bto = Rinconada Bikol
+ TRUETYPE_TAG('b', 't', 'p', 0), // btp = Budibud
+ TRUETYPE_TAG('b', 't', 'q', 0), // btq = Batek
+ TRUETYPE_TAG('b', 't', 'r', 0), // btr = Baetora
+ TRUETYPE_TAG('b', 't', 's', 0), // bts = Batak Simalungun
+ TRUETYPE_TAG('b', 't', 't', 0), // btt = Bete-Bendi
+ TRUETYPE_TAG('b', 't', 'u', 0), // btu = Batu
+ TRUETYPE_TAG('b', 't', 'v', 0), // btv = Bateri
+ TRUETYPE_TAG('b', 't', 'w', 0), // btw = Butuanon
+ TRUETYPE_TAG('b', 't', 'x', 0), // btx = Batak Karo
+ TRUETYPE_TAG('b', 't', 'y', 0), // bty = Bobot
+ TRUETYPE_TAG('b', 't', 'z', 0), // btz = Batak Alas-Kluet
+ TRUETYPE_TAG('b', 'u', 'a', 0), // bua = Buriat
+ TRUETYPE_TAG('b', 'u', 'b', 0), // bub = Bua
+ TRUETYPE_TAG('b', 'u', 'c', 0), // buc = Bushi
+ TRUETYPE_TAG('b', 'u', 'd', 0), // bud = Ntcham
+ TRUETYPE_TAG('b', 'u', 'e', 0), // bue = Beothuk
+ TRUETYPE_TAG('b', 'u', 'f', 0), // buf = Bushoong
+ TRUETYPE_TAG('b', 'u', 'g', 0), // bug = Buginese
+ TRUETYPE_TAG('b', 'u', 'h', 0), // buh = Younuo Bunu
+ TRUETYPE_TAG('b', 'u', 'i', 0), // bui = Bongili
+ TRUETYPE_TAG('b', 'u', 'j', 0), // buj = Basa-Gurmana
+ TRUETYPE_TAG('b', 'u', 'k', 0), // buk = Bugawac
+ TRUETYPE_TAG('b', 'u', 'm', 0), // bum = Bulu (Cameroon)
+ TRUETYPE_TAG('b', 'u', 'n', 0), // bun = Sherbro
+ TRUETYPE_TAG('b', 'u', 'o', 0), // buo = Terei
+ TRUETYPE_TAG('b', 'u', 'p', 0), // bup = Busoa
+ TRUETYPE_TAG('b', 'u', 'q', 0), // buq = Brem
+ TRUETYPE_TAG('b', 'u', 's', 0), // bus = Bokobaru
+ TRUETYPE_TAG('b', 'u', 't', 0), // but = Bungain
+ TRUETYPE_TAG('b', 'u', 'u', 0), // buu = Budu
+ TRUETYPE_TAG('b', 'u', 'v', 0), // buv = Bun
+ TRUETYPE_TAG('b', 'u', 'w', 0), // buw = Bubi
+ TRUETYPE_TAG('b', 'u', 'x', 0), // bux = Boghom
+ TRUETYPE_TAG('b', 'u', 'y', 0), // buy = Bullom So
+ TRUETYPE_TAG('b', 'u', 'z', 0), // buz = Bukwen
+ TRUETYPE_TAG('b', 'v', 'a', 0), // bva = Barein
+ TRUETYPE_TAG('b', 'v', 'b', 0), // bvb = Bube
+ TRUETYPE_TAG('b', 'v', 'c', 0), // bvc = Baelelea
+ TRUETYPE_TAG('b', 'v', 'd', 0), // bvd = Baeggu
+ TRUETYPE_TAG('b', 'v', 'e', 0), // bve = Berau Malay
+ TRUETYPE_TAG('b', 'v', 'f', 0), // bvf = Boor
+ TRUETYPE_TAG('b', 'v', 'g', 0), // bvg = Bonkeng
+ TRUETYPE_TAG('b', 'v', 'h', 0), // bvh = Bure
+ TRUETYPE_TAG('b', 'v', 'i', 0), // bvi = Belanda Viri
+ TRUETYPE_TAG('b', 'v', 'j', 0), // bvj = Baan
+ TRUETYPE_TAG('b', 'v', 'k', 0), // bvk = Bukat
+ TRUETYPE_TAG('b', 'v', 'l', 0), // bvl = Bolivian Sign Language
+ TRUETYPE_TAG('b', 'v', 'm', 0), // bvm = Bamunka
+ TRUETYPE_TAG('b', 'v', 'n', 0), // bvn = Buna
+ TRUETYPE_TAG('b', 'v', 'o', 0), // bvo = Bolgo
+ TRUETYPE_TAG('b', 'v', 'q', 0), // bvq = Birri
+ TRUETYPE_TAG('b', 'v', 'r', 0), // bvr = Burarra
+ TRUETYPE_TAG('b', 'v', 't', 0), // bvt = Bati (Indonesia)
+ TRUETYPE_TAG('b', 'v', 'u', 0), // bvu = Bukit Malay
+ TRUETYPE_TAG('b', 'v', 'v', 0), // bvv = Baniva
+ TRUETYPE_TAG('b', 'v', 'w', 0), // bvw = Boga
+ TRUETYPE_TAG('b', 'v', 'x', 0), // bvx = Dibole
+ TRUETYPE_TAG('b', 'v', 'y', 0), // bvy = Baybayanon
+ TRUETYPE_TAG('b', 'v', 'z', 0), // bvz = Bauzi
+ TRUETYPE_TAG('b', 'w', 'a', 0), // bwa = Bwatoo
+ TRUETYPE_TAG('b', 'w', 'b', 0), // bwb = Namosi-Naitasiri-Serua
+ TRUETYPE_TAG('b', 'w', 'c', 0), // bwc = Bwile
+ TRUETYPE_TAG('b', 'w', 'd', 0), // bwd = Bwaidoka
+ TRUETYPE_TAG('b', 'w', 'e', 0), // bwe = Bwe Karen
+ TRUETYPE_TAG('b', 'w', 'f', 0), // bwf = Boselewa
+ TRUETYPE_TAG('b', 'w', 'g', 0), // bwg = Barwe
+ TRUETYPE_TAG('b', 'w', 'h', 0), // bwh = Bishuo
+ TRUETYPE_TAG('b', 'w', 'i', 0), // bwi = Baniwa
+ TRUETYPE_TAG('b', 'w', 'j', 0), // bwj = Láá Láá Bwamu
+ TRUETYPE_TAG('b', 'w', 'k', 0), // bwk = Bauwaki
+ TRUETYPE_TAG('b', 'w', 'l', 0), // bwl = Bwela
+ TRUETYPE_TAG('b', 'w', 'm', 0), // bwm = Biwat
+ TRUETYPE_TAG('b', 'w', 'n', 0), // bwn = Wunai Bunu
+ TRUETYPE_TAG('b', 'w', 'o', 0), // bwo = Boro (Ethiopia)
+ TRUETYPE_TAG('b', 'w', 'p', 0), // bwp = Mandobo Bawah
+ TRUETYPE_TAG('b', 'w', 'q', 0), // bwq = Southern Bobo Madaré
+ TRUETYPE_TAG('b', 'w', 'r', 0), // bwr = Bura-Pabir
+ TRUETYPE_TAG('b', 'w', 's', 0), // bws = Bomboma
+ TRUETYPE_TAG('b', 'w', 't', 0), // bwt = Bafaw-Balong
+ TRUETYPE_TAG('b', 'w', 'u', 0), // bwu = Buli (Ghana)
+ TRUETYPE_TAG('b', 'w', 'w', 0), // bww = Bwa
+ TRUETYPE_TAG('b', 'w', 'x', 0), // bwx = Bu-Nao Bunu
+ TRUETYPE_TAG('b', 'w', 'y', 0), // bwy = Cwi Bwamu
+ TRUETYPE_TAG('b', 'w', 'z', 0), // bwz = Bwisi
+ TRUETYPE_TAG('b', 'x', 'a', 0), // bxa = Bauro
+ TRUETYPE_TAG('b', 'x', 'b', 0), // bxb = Belanda Bor
+ TRUETYPE_TAG('b', 'x', 'c', 0), // bxc = Molengue
+ TRUETYPE_TAG('b', 'x', 'd', 0), // bxd = Pela
+ TRUETYPE_TAG('b', 'x', 'e', 0), // bxe = Birale
+ TRUETYPE_TAG('b', 'x', 'f', 0), // bxf = Bilur
+ TRUETYPE_TAG('b', 'x', 'g', 0), // bxg = Bangala
+ TRUETYPE_TAG('b', 'x', 'h', 0), // bxh = Buhutu
+ TRUETYPE_TAG('b', 'x', 'i', 0), // bxi = Pirlatapa
+ TRUETYPE_TAG('b', 'x', 'j', 0), // bxj = Bayungu
+ TRUETYPE_TAG('b', 'x', 'k', 0), // bxk = Bukusu
+ TRUETYPE_TAG('b', 'x', 'l', 0), // bxl = Jalkunan
+ TRUETYPE_TAG('b', 'x', 'm', 0), // bxm = Mongolia Buriat
+ TRUETYPE_TAG('b', 'x', 'n', 0), // bxn = Burduna
+ TRUETYPE_TAG('b', 'x', 'o', 0), // bxo = Barikanchi
+ TRUETYPE_TAG('b', 'x', 'p', 0), // bxp = Bebil
+ TRUETYPE_TAG('b', 'x', 'q', 0), // bxq = Beele
+ TRUETYPE_TAG('b', 'x', 'r', 0), // bxr = Russia Buriat
+ TRUETYPE_TAG('b', 'x', 's', 0), // bxs = Busam
+ TRUETYPE_TAG('b', 'x', 'u', 0), // bxu = China Buriat
+ TRUETYPE_TAG('b', 'x', 'v', 0), // bxv = Berakou
+ TRUETYPE_TAG('b', 'x', 'w', 0), // bxw = Bankagooma
+ TRUETYPE_TAG('b', 'x', 'x',
+ 0), // bxx = Borna (Democratic Republic of Congo)
+ TRUETYPE_TAG('b', 'x', 'z', 0), // bxz = Binahari
+ TRUETYPE_TAG('b', 'y', 'a', 0), // bya = Batak
+ TRUETYPE_TAG('b', 'y', 'b', 0), // byb = Bikya
+ TRUETYPE_TAG('b', 'y', 'c', 0), // byc = Ubaghara
+ TRUETYPE_TAG('b', 'y', 'd', 0), // byd = Benyadu'
+ TRUETYPE_TAG('b', 'y', 'e', 0), // bye = Pouye
+ TRUETYPE_TAG('b', 'y', 'f', 0), // byf = Bete
+ TRUETYPE_TAG('b', 'y', 'g', 0), // byg = Baygo
+ TRUETYPE_TAG('b', 'y', 'h', 0), // byh = Bhujel
+ TRUETYPE_TAG('b', 'y', 'i', 0), // byi = Buyu
+ TRUETYPE_TAG('b', 'y', 'j', 0), // byj = Bina (Nigeria)
+ TRUETYPE_TAG('b', 'y', 'k', 0), // byk = Biao
+ TRUETYPE_TAG('b', 'y', 'l', 0), // byl = Bayono
+ TRUETYPE_TAG('b', 'y', 'm', 0), // bym = Bidyara
+ TRUETYPE_TAG('b', 'y', 'n', 0), // byn = Bilin
+ TRUETYPE_TAG('b', 'y', 'o', 0), // byo = Biyo
+ TRUETYPE_TAG('b', 'y', 'p', 0), // byp = Bumaji
+ TRUETYPE_TAG('b', 'y', 'q', 0), // byq = Basay
+ TRUETYPE_TAG('b', 'y', 'r', 0), // byr = Baruya
+ TRUETYPE_TAG('b', 'y', 's', 0), // bys = Burak
+ TRUETYPE_TAG('b', 'y', 't', 0), // byt = Berti
+ TRUETYPE_TAG('b', 'y', 'v', 0), // byv = Medumba
+ TRUETYPE_TAG('b', 'y', 'w', 0), // byw = Belhariya
+ TRUETYPE_TAG('b', 'y', 'x', 0), // byx = Qaqet
+ TRUETYPE_TAG('b', 'y', 'y', 0), // byy = Buya
+ TRUETYPE_TAG('b', 'y', 'z', 0), // byz = Banaro
+ TRUETYPE_TAG('b', 'z', 'a', 0), // bza = Bandi
+ TRUETYPE_TAG('b', 'z', 'b', 0), // bzb = Andio
+ TRUETYPE_TAG('b', 'z', 'c', 0), // bzc = Southern Betsimisaraka Malagasy
+ TRUETYPE_TAG('b', 'z', 'd', 0), // bzd = Bribri
+ TRUETYPE_TAG('b', 'z', 'e', 0), // bze = Jenaama Bozo
+ TRUETYPE_TAG('b', 'z', 'f', 0), // bzf = Boikin
+ TRUETYPE_TAG('b', 'z', 'g', 0), // bzg = Babuza
+ TRUETYPE_TAG('b', 'z', 'h', 0), // bzh = Mapos Buang
+ TRUETYPE_TAG('b', 'z', 'i', 0), // bzi = Bisu
+ TRUETYPE_TAG('b', 'z', 'j', 0), // bzj = Belize Kriol English
+ TRUETYPE_TAG('b', 'z', 'k', 0), // bzk = Nicaragua Creole English
+ TRUETYPE_TAG('b', 'z', 'l', 0), // bzl = Boano (Sulawesi)
+ TRUETYPE_TAG('b', 'z', 'm', 0), // bzm = Bolondo
+ TRUETYPE_TAG('b', 'z', 'n', 0), // bzn = Boano (Maluku)
+ TRUETYPE_TAG('b', 'z', 'o', 0), // bzo = Bozaba
+ TRUETYPE_TAG('b', 'z', 'p', 0), // bzp = Kemberano
+ TRUETYPE_TAG('b', 'z', 'q', 0), // bzq = Buli (Indonesia)
+ TRUETYPE_TAG('b', 'z', 'r', 0), // bzr = Biri
+ TRUETYPE_TAG('b', 'z', 's', 0), // bzs = Brazilian Sign Language
+ TRUETYPE_TAG('b', 'z', 't', 0), // bzt = Brithenig
+ TRUETYPE_TAG('b', 'z', 'u', 0), // bzu = Burmeso
+ TRUETYPE_TAG('b', 'z', 'v', 0), // bzv = Bebe
+ TRUETYPE_TAG('b', 'z', 'w', 0), // bzw = Basa (Nigeria)
+ TRUETYPE_TAG('b', 'z', 'x', 0), // bzx = Kɛlɛngaxo Bozo
+ TRUETYPE_TAG('b', 'z', 'y', 0), // bzy = Obanliku
+ TRUETYPE_TAG('b', 'z', 'z', 0), // bzz = Evant
+ TRUETYPE_TAG('c', 'a', 'a', 0), // caa = Chortí
+ TRUETYPE_TAG('c', 'a', 'b', 0), // cab = Garifuna
+ TRUETYPE_TAG('c', 'a', 'c', 0), // cac = Chuj
+ TRUETYPE_TAG('c', 'a', 'd', 0), // cad = Caddo
+ TRUETYPE_TAG('c', 'a', 'e', 0), // cae = Lehar
+ TRUETYPE_TAG('c', 'a', 'f', 0), // caf = Southern Carrier
+ TRUETYPE_TAG('c', 'a', 'g', 0), // cag = Nivaclé
+ TRUETYPE_TAG('c', 'a', 'h', 0), // cah = Cahuarano
+ TRUETYPE_TAG('c', 'a', 'i', 0), // cai = Central American Indian languages
+ TRUETYPE_TAG('c', 'a', 'j', 0), // caj = Chané
+ TRUETYPE_TAG('c', 'a', 'k', 0), // cak = Kaqchikel
+ TRUETYPE_TAG('c', 'a', 'l', 0), // cal = Carolinian
+ TRUETYPE_TAG('c', 'a', 'm', 0), // cam = Cemuhî
+ TRUETYPE_TAG('c', 'a', 'n', 0), // can = Chambri
+ TRUETYPE_TAG('c', 'a', 'o', 0), // cao = Chácobo
+ TRUETYPE_TAG('c', 'a', 'p', 0), // cap = Chipaya
+ TRUETYPE_TAG('c', 'a', 'q', 0), // caq = Car Nicobarese
+ TRUETYPE_TAG('c', 'a', 'r', 0), // car = Galibi Carib
+ TRUETYPE_TAG('c', 'a', 's', 0), // cas = Tsimané
+ TRUETYPE_TAG('c', 'a', 'u', 0), // cau = Caucasian languages
+ TRUETYPE_TAG('c', 'a', 'v', 0), // cav = Cavineña
+ TRUETYPE_TAG('c', 'a', 'w', 0), // caw = Callawalla
+ TRUETYPE_TAG('c', 'a', 'x', 0), // cax = Chiquitano
+ TRUETYPE_TAG('c', 'a', 'y', 0), // cay = Cayuga
+ TRUETYPE_TAG('c', 'a', 'z', 0), // caz = Canichana
+ TRUETYPE_TAG('c', 'b', 'a', 0), // cba = Chibchan languages
+ TRUETYPE_TAG('c', 'b', 'b', 0), // cbb = Cabiyarí
+ TRUETYPE_TAG('c', 'b', 'c', 0), // cbc = Carapana
+ TRUETYPE_TAG('c', 'b', 'd', 0), // cbd = Carijona
+ TRUETYPE_TAG('c', 'b', 'e', 0), // cbe = Chipiajes
+ TRUETYPE_TAG('c', 'b', 'g', 0), // cbg = Chimila
+ TRUETYPE_TAG('c', 'b', 'h', 0), // cbh = Cagua
+ TRUETYPE_TAG('c', 'b', 'i', 0), // cbi = Chachi
+ TRUETYPE_TAG('c', 'b', 'j', 0), // cbj = Ede Cabe
+ TRUETYPE_TAG('c', 'b', 'k', 0), // cbk = Chavacano
+ TRUETYPE_TAG('c', 'b', 'l', 0), // cbl = Bualkhaw Chin
+ TRUETYPE_TAG('c', 'b', 'n', 0), // cbn = Nyahkur
+ TRUETYPE_TAG('c', 'b', 'o', 0), // cbo = Izora
+ TRUETYPE_TAG('c', 'b', 'r', 0), // cbr = Cashibo-Cacataibo
+ TRUETYPE_TAG('c', 'b', 's', 0), // cbs = Cashinahua
+ TRUETYPE_TAG('c', 'b', 't', 0), // cbt = Chayahuita
+ TRUETYPE_TAG('c', 'b', 'u', 0), // cbu = Candoshi-Shapra
+ TRUETYPE_TAG('c', 'b', 'v', 0), // cbv = Cacua
+ TRUETYPE_TAG('c', 'b', 'w', 0), // cbw = Kinabalian
+ TRUETYPE_TAG('c', 'b', 'y', 0), // cby = Carabayo
+ TRUETYPE_TAG('c', 'c', 'a', 0), // cca = Cauca
+ TRUETYPE_TAG('c', 'c', 'c', 0), // ccc = Chamicuro
+ TRUETYPE_TAG('c', 'c', 'd', 0), // ccd = Cafundo Creole
+ TRUETYPE_TAG('c', 'c', 'e', 0), // cce = Chopi
+ TRUETYPE_TAG('c', 'c', 'g', 0), // ccg = Samba Daka
+ TRUETYPE_TAG('c', 'c', 'h', 0), // cch = Atsam
+ TRUETYPE_TAG('c', 'c', 'j', 0), // ccj = Kasanga
+ TRUETYPE_TAG('c', 'c', 'l', 0), // ccl = Cutchi-Swahili
+ TRUETYPE_TAG('c', 'c', 'm', 0), // ccm = Malaccan Creole Malay
+ TRUETYPE_TAG('c', 'c', 'n', 0), // ccn = North Caucasian languages
+ TRUETYPE_TAG('c', 'c', 'o', 0), // cco = Comaltepec Chinantec
+ TRUETYPE_TAG('c', 'c', 'p', 0), // ccp = Chakma
+ TRUETYPE_TAG('c', 'c', 'q', 0), // ccq = Chaungtha
+ TRUETYPE_TAG('c', 'c', 'r', 0), // ccr = Cacaopera
+ TRUETYPE_TAG('c', 'c', 's', 0), // ccs = South Caucasian languages
+ TRUETYPE_TAG('c', 'd', 'a', 0), // cda = Choni
+ TRUETYPE_TAG('c', 'd', 'c', 0), // cdc = Chadic languages
+ TRUETYPE_TAG('c', 'd', 'd', 0), // cdd = Caddoan languages
+ TRUETYPE_TAG('c', 'd', 'e', 0), // cde = Chenchu
+ TRUETYPE_TAG('c', 'd', 'f', 0), // cdf = Chiru
+ TRUETYPE_TAG('c', 'd', 'g', 0), // cdg = Chamari
+ TRUETYPE_TAG('c', 'd', 'h', 0), // cdh = Chambeali
+ TRUETYPE_TAG('c', 'd', 'i', 0), // cdi = Chodri
+ TRUETYPE_TAG('c', 'd', 'j', 0), // cdj = Churahi
+ TRUETYPE_TAG('c', 'd', 'm', 0), // cdm = Chepang
+ TRUETYPE_TAG('c', 'd', 'n', 0), // cdn = Chaudangsi
+ TRUETYPE_TAG('c', 'd', 'o', 0), // cdo = Min Dong Chinese
+ TRUETYPE_TAG('c', 'd', 'r', 0), // cdr = Cinda-Regi-Tiyal
+ TRUETYPE_TAG('c', 'd', 's', 0), // cds = Chadian Sign Language
+ TRUETYPE_TAG('c', 'd', 'y', 0), // cdy = Chadong
+ TRUETYPE_TAG('c', 'd', 'z', 0), // cdz = Koda
+ TRUETYPE_TAG('c', 'e', 'a', 0), // cea = Lower Chehalis
+ TRUETYPE_TAG('c', 'e', 'b', 0), // ceb = Cebuano
+ TRUETYPE_TAG('c', 'e', 'g', 0), // ceg = Chamacoco
+ TRUETYPE_TAG('c', 'e', 'l', 0), // cel = Celtic languages
+ TRUETYPE_TAG('c', 'e', 'n', 0), // cen = Cen
+ TRUETYPE_TAG('c', 'e', 't', 0), // cet = Centúúm
+ TRUETYPE_TAG('c', 'f', 'a', 0), // cfa = Dijim-Bwilim
+ TRUETYPE_TAG('c', 'f', 'd', 0), // cfd = Cara
+ TRUETYPE_TAG('c', 'f', 'g', 0), // cfg = Como Karim
+ TRUETYPE_TAG('c', 'f', 'm', 0), // cfm = Falam Chin
+ TRUETYPE_TAG('c', 'g', 'a', 0), // cga = Changriwa
+ TRUETYPE_TAG('c', 'g', 'c', 0), // cgc = Kagayanen
+ TRUETYPE_TAG('c', 'g', 'g', 0), // cgg = Chiga
+ TRUETYPE_TAG('c', 'g', 'k', 0), // cgk = Chocangacakha
+ TRUETYPE_TAG('c', 'h', 'b', 0), // chb = Chibcha
+ TRUETYPE_TAG('c', 'h', 'c', 0), // chc = Catawba
+ TRUETYPE_TAG('c', 'h', 'd', 0), // chd = Highland Oaxaca Chontal
+ TRUETYPE_TAG('c', 'h', 'f', 0), // chf = Tabasco Chontal
+ TRUETYPE_TAG('c', 'h', 'g', 0), // chg = Chagatai
+ TRUETYPE_TAG('c', 'h', 'h', 0), // chh = Chinook
+ TRUETYPE_TAG('c', 'h', 'j', 0), // chj = Ojitlán Chinantec
+ TRUETYPE_TAG('c', 'h', 'k', 0), // chk = Chuukese
+ TRUETYPE_TAG('c', 'h', 'l', 0), // chl = Cahuilla
+ TRUETYPE_TAG('c', 'h', 'm', 0), // chm = Mari (Russia)
+ TRUETYPE_TAG('c', 'h', 'n', 0), // chn = Chinook jargon
+ TRUETYPE_TAG('c', 'h', 'o', 0), // cho = Choctaw
+ TRUETYPE_TAG('c', 'h', 'p', 0), // chp = Chipewyan
+ TRUETYPE_TAG('c', 'h', 'q', 0), // chq = Quiotepec Chinantec
+ TRUETYPE_TAG('c', 'h', 'r', 0), // chr = Cherokee
+ TRUETYPE_TAG('c', 'h', 't', 0), // cht = Cholón
+ TRUETYPE_TAG('c', 'h', 'w', 0), // chw = Chuwabu
+ TRUETYPE_TAG('c', 'h', 'x', 0), // chx = Chantyal
+ TRUETYPE_TAG('c', 'h', 'y', 0), // chy = Cheyenne
+ TRUETYPE_TAG('c', 'h', 'z', 0), // chz = Ozumacín Chinantec
+ TRUETYPE_TAG('c', 'i', 'a', 0), // cia = Cia-Cia
+ TRUETYPE_TAG('c', 'i', 'b', 0), // cib = Ci Gbe
+ TRUETYPE_TAG('c', 'i', 'c', 0), // cic = Chickasaw
+ TRUETYPE_TAG('c', 'i', 'd', 0), // cid = Chimariko
+ TRUETYPE_TAG('c', 'i', 'e', 0), // cie = Cineni
+ TRUETYPE_TAG('c', 'i', 'h', 0), // cih = Chinali
+ TRUETYPE_TAG('c', 'i', 'k', 0), // cik = Chitkuli Kinnauri
+ TRUETYPE_TAG('c', 'i', 'm', 0), // cim = Cimbrian
+ TRUETYPE_TAG('c', 'i', 'n', 0), // cin = Cinta Larga
+ TRUETYPE_TAG('c', 'i', 'p', 0), // cip = Chiapanec
+ TRUETYPE_TAG('c', 'i', 'r', 0), // cir = Tiri
+ TRUETYPE_TAG('c', 'i', 'w', 0), // ciw = Chippewa
+ TRUETYPE_TAG('c', 'i', 'y', 0), // ciy = Chaima
+ TRUETYPE_TAG('c', 'j', 'a', 0), // cja = Western Cham
+ TRUETYPE_TAG('c', 'j', 'e', 0), // cje = Chru
+ TRUETYPE_TAG('c', 'j', 'h', 0), // cjh = Upper Chehalis
+ TRUETYPE_TAG('c', 'j', 'i', 0), // cji = Chamalal
+ TRUETYPE_TAG('c', 'j', 'k', 0), // cjk = Chokwe
+ TRUETYPE_TAG('c', 'j', 'm', 0), // cjm = Eastern Cham
+ TRUETYPE_TAG('c', 'j', 'n', 0), // cjn = Chenapian
+ TRUETYPE_TAG('c', 'j', 'o', 0), // cjo = Ashéninka Pajonal
+ TRUETYPE_TAG('c', 'j', 'p', 0), // cjp = Cabécar
+ TRUETYPE_TAG('c', 'j', 'r', 0), // cjr = Chorotega
+ TRUETYPE_TAG('c', 'j', 's', 0), // cjs = Shor
+ TRUETYPE_TAG('c', 'j', 'v', 0), // cjv = Chuave
+ TRUETYPE_TAG('c', 'j', 'y', 0), // cjy = Jinyu Chinese
+ TRUETYPE_TAG('c', 'k', 'a', 0), // cka = Khumi Awa Chin
+ TRUETYPE_TAG('c', 'k', 'b', 0), // ckb = Central Kurdish
+ TRUETYPE_TAG('c', 'k', 'h', 0), // ckh = Chak
+ TRUETYPE_TAG('c', 'k', 'l', 0), // ckl = Cibak
+ TRUETYPE_TAG('c', 'k', 'o', 0), // cko = Anufo
+ TRUETYPE_TAG('c', 'k', 'q', 0), // ckq = Kajakse
+ TRUETYPE_TAG('c', 'k', 'r', 0), // ckr = Kairak
+ TRUETYPE_TAG('c', 'k', 's', 0), // cks = Tayo
+ TRUETYPE_TAG('c', 'k', 't', 0), // ckt = Chukot
+ TRUETYPE_TAG('c', 'k', 'u', 0), // cku = Koasati
+ TRUETYPE_TAG('c', 'k', 'v', 0), // ckv = Kavalan
+ TRUETYPE_TAG('c', 'k', 'x', 0), // ckx = Caka
+ TRUETYPE_TAG('c', 'k', 'y', 0), // cky = Cakfem-Mushere
+ TRUETYPE_TAG('c', 'k', 'z', 0), // ckz = Cakchiquel-Quiché Mixed Language
+ TRUETYPE_TAG('c', 'l', 'a', 0), // cla = Ron
+ TRUETYPE_TAG('c', 'l', 'c', 0), // clc = Chilcotin
+ TRUETYPE_TAG('c', 'l', 'd', 0), // cld = Chaldean Neo-Aramaic
+ TRUETYPE_TAG('c', 'l', 'e', 0), // cle = Lealao Chinantec
+ TRUETYPE_TAG('c', 'l', 'h', 0), // clh = Chilisso
+ TRUETYPE_TAG('c', 'l', 'i', 0), // cli = Chakali
+ TRUETYPE_TAG('c', 'l', 'k', 0), // clk = Idu-Mishmi
+ TRUETYPE_TAG('c', 'l', 'l', 0), // cll = Chala
+ TRUETYPE_TAG('c', 'l', 'm', 0), // clm = Clallam
+ TRUETYPE_TAG('c', 'l', 'o', 0), // clo = Lowland Oaxaca Chontal
+ TRUETYPE_TAG('c', 'l', 'u', 0), // clu = Caluyanun
+ TRUETYPE_TAG('c', 'l', 'w', 0), // clw = Chulym
+ TRUETYPE_TAG('c', 'l', 'y', 0), // cly = Eastern Highland Chatino
+ TRUETYPE_TAG('c', 'm', 'a', 0), // cma = Maa
+ TRUETYPE_TAG('c', 'm', 'c', 0), // cmc = Chamic languages
+ TRUETYPE_TAG('c', 'm', 'e', 0), // cme = Cerma
+ TRUETYPE_TAG('c', 'm', 'g', 0), // cmg = Classical Mongolian
+ TRUETYPE_TAG('c', 'm', 'i', 0), // cmi = Emberá-Chamí
+ TRUETYPE_TAG('c', 'm', 'k', 0), // cmk = Chimakum
+ TRUETYPE_TAG('c', 'm', 'l', 0), // cml = Campalagian
+ TRUETYPE_TAG('c', 'm', 'm', 0), // cmm = Michigamea
+ TRUETYPE_TAG('c', 'm', 'n', 0), // cmn = Mandarin Chinese
+ TRUETYPE_TAG('c', 'm', 'o', 0), // cmo = Central Mnong
+ TRUETYPE_TAG('c', 'm', 'r', 0), // cmr = Mro Chin
+ TRUETYPE_TAG('c', 'm', 's', 0), // cms = Messapic
+ TRUETYPE_TAG('c', 'm', 't', 0), // cmt = Camtho
+ TRUETYPE_TAG('c', 'n', 'a', 0), // cna = Changthang
+ TRUETYPE_TAG('c', 'n', 'b', 0), // cnb = Chinbon Chin
+ TRUETYPE_TAG('c', 'n', 'c', 0), // cnc = Côông
+ TRUETYPE_TAG('c', 'n', 'g', 0), // cng = Northern Qiang
+ TRUETYPE_TAG('c', 'n', 'h', 0), // cnh = Haka Chin
+ TRUETYPE_TAG('c', 'n', 'i', 0), // cni = Asháninka
+ TRUETYPE_TAG('c', 'n', 'k', 0), // cnk = Khumi Chin
+ TRUETYPE_TAG('c', 'n', 'l', 0), // cnl = Lalana Chinantec
+ TRUETYPE_TAG('c', 'n', 'o', 0), // cno = Con
+ TRUETYPE_TAG('c', 'n', 's', 0), // cns = Central Asmat
+ TRUETYPE_TAG('c', 'n', 't', 0), // cnt = Tepetotutla Chinantec
+ TRUETYPE_TAG('c', 'n', 'u', 0), // cnu = Chenoua
+ TRUETYPE_TAG('c', 'n', 'w', 0), // cnw = Ngawn Chin
+ TRUETYPE_TAG('c', 'n', 'x', 0), // cnx = Middle Cornish
+ TRUETYPE_TAG('c', 'o', 'a', 0), // coa = Cocos Islands Malay
+ TRUETYPE_TAG('c', 'o', 'b', 0), // cob = Chicomuceltec
+ TRUETYPE_TAG('c', 'o', 'c', 0), // coc = Cocopa
+ TRUETYPE_TAG('c', 'o', 'd', 0), // cod = Cocama-Cocamilla
+ TRUETYPE_TAG('c', 'o', 'e', 0), // coe = Koreguaje
+ TRUETYPE_TAG('c', 'o', 'f', 0), // cof = Colorado
+ TRUETYPE_TAG('c', 'o', 'g', 0), // cog = Chong
+ TRUETYPE_TAG('c', 'o', 'h', 0), // coh = Chonyi-Dzihana-Kauma
+ TRUETYPE_TAG('c', 'o', 'j', 0), // coj = Cochimi
+ TRUETYPE_TAG('c', 'o', 'k', 0), // cok = Santa Teresa Cora
+ TRUETYPE_TAG('c', 'o', 'l', 0), // col = Columbia-Wenatchi
+ TRUETYPE_TAG('c', 'o', 'm', 0), // com = Comanche
+ TRUETYPE_TAG('c', 'o', 'n', 0), // con = Cofán
+ TRUETYPE_TAG('c', 'o', 'o', 0), // coo = Comox
+ TRUETYPE_TAG('c', 'o', 'p', 0), // cop = Coptic
+ TRUETYPE_TAG('c', 'o', 'q', 0), // coq = Coquille
+ TRUETYPE_TAG('c', 'o', 't', 0), // cot = Caquinte
+ TRUETYPE_TAG('c', 'o', 'u', 0), // cou = Wamey
+ TRUETYPE_TAG('c', 'o', 'v', 0), // cov = Cao Miao
+ TRUETYPE_TAG('c', 'o', 'w', 0), // cow = Cowlitz
+ TRUETYPE_TAG('c', 'o', 'x', 0), // cox = Nanti
+ TRUETYPE_TAG('c', 'o', 'y', 0), // coy = Coyaima
+ TRUETYPE_TAG('c', 'o', 'z', 0), // coz = Chochotec
+ TRUETYPE_TAG('c', 'p', 'a', 0), // cpa = Palantla Chinantec
+ TRUETYPE_TAG('c', 'p', 'b', 0), // cpb = Ucayali-Yurúa Ashéninka
+ TRUETYPE_TAG('c', 'p', 'c', 0), // cpc = Ajyíninka Apurucayali
+ TRUETYPE_TAG('c', 'p', 'e', 0), // cpe = English-based creoles and pidgins
+ TRUETYPE_TAG('c', 'p', 'f', 0), // cpf = French-based creoles and pidgins
+ TRUETYPE_TAG('c', 'p', 'g', 0), // cpg = Cappadocian Greek
+ TRUETYPE_TAG('c', 'p', 'i', 0), // cpi = Chinese Pidgin English
+ TRUETYPE_TAG('c', 'p', 'n', 0), // cpn = Cherepon
+ TRUETYPE_TAG('c', 'p', 'p',
+ 0), // cpp = Portuguese-based creoles and pidgins
+ TRUETYPE_TAG('c', 'p', 's', 0), // cps = Capiznon
+ TRUETYPE_TAG('c', 'p', 'u', 0), // cpu = Pichis Ashéninka
+ TRUETYPE_TAG('c', 'p', 'x', 0), // cpx = Pu-Xian Chinese
+ TRUETYPE_TAG('c', 'p', 'y', 0), // cpy = South Ucayali Ashéninka
+ TRUETYPE_TAG('c', 'q', 'd', 0), // cqd = Chuanqiandian Cluster Miao
+ TRUETYPE_TAG('c', 'q', 'u', 0), // cqu = Chilean Quechua
+ TRUETYPE_TAG('c', 'r', 'a', 0), // cra = Chara
+ TRUETYPE_TAG('c', 'r', 'b', 0), // crb = Island Carib
+ TRUETYPE_TAG('c', 'r', 'c', 0), // crc = Lonwolwol
+ TRUETYPE_TAG('c', 'r', 'd', 0), // crd = Coeur d'Alene
+ TRUETYPE_TAG('c', 'r', 'f', 0), // crf = Caramanta
+ TRUETYPE_TAG('c', 'r', 'g', 0), // crg = Michif
+ TRUETYPE_TAG('c', 'r', 'h', 0), // crh = Crimean Tatar
+ TRUETYPE_TAG('c', 'r', 'i', 0), // cri = Sãotomense
+ TRUETYPE_TAG('c', 'r', 'j', 0), // crj = Southern East Cree
+ TRUETYPE_TAG('c', 'r', 'k', 0), // crk = Plains Cree
+ TRUETYPE_TAG('c', 'r', 'l', 0), // crl = Northern East Cree
+ TRUETYPE_TAG('c', 'r', 'm', 0), // crm = Moose Cree
+ TRUETYPE_TAG('c', 'r', 'n', 0), // crn = El Nayar Cora
+ TRUETYPE_TAG('c', 'r', 'o', 0), // cro = Crow
+ TRUETYPE_TAG('c', 'r', 'p', 0), // crp = Creoles and pidgins
+ TRUETYPE_TAG('c', 'r', 'q', 0), // crq = Iyo'wujwa Chorote
+ TRUETYPE_TAG('c', 'r', 'r', 0), // crr = Carolina Algonquian
+ TRUETYPE_TAG('c', 'r', 's', 0), // crs = Seselwa Creole French
+ TRUETYPE_TAG('c', 'r', 't', 0), // crt = Iyojwa'ja Chorote
+ TRUETYPE_TAG('c', 'r', 'v', 0), // crv = Chaura
+ TRUETYPE_TAG('c', 'r', 'w', 0), // crw = Chrau
+ TRUETYPE_TAG('c', 'r', 'x', 0), // crx = Carrier
+ TRUETYPE_TAG('c', 'r', 'y', 0), // cry = Cori
+ TRUETYPE_TAG('c', 'r', 'z', 0), // crz = Cruzeño
+ TRUETYPE_TAG('c', 's', 'a', 0), // csa = Chiltepec Chinantec
+ TRUETYPE_TAG('c', 's', 'b', 0), // csb = Kashubian
+ TRUETYPE_TAG('c', 's', 'c', 0), // csc = Catalan Sign Language
+ TRUETYPE_TAG('c', 's', 'd', 0), // csd = Chiangmai Sign Language
+ TRUETYPE_TAG('c', 's', 'e', 0), // cse = Czech Sign Language
+ TRUETYPE_TAG('c', 's', 'f', 0), // csf = Cuba Sign Language
+ TRUETYPE_TAG('c', 's', 'g', 0), // csg = Chilean Sign Language
+ TRUETYPE_TAG('c', 's', 'h', 0), // csh = Asho Chin
+ TRUETYPE_TAG('c', 's', 'i', 0), // csi = Coast Miwok
+ TRUETYPE_TAG('c', 's', 'k', 0), // csk = Jola-Kasa
+ TRUETYPE_TAG('c', 's', 'l', 0), // csl = Chinese Sign Language
+ TRUETYPE_TAG('c', 's', 'm', 0), // csm = Central Sierra Miwok
+ TRUETYPE_TAG('c', 's', 'n', 0), // csn = Colombian Sign Language
+ TRUETYPE_TAG('c', 's', 'o', 0), // cso = Sochiapam Chinantec
+ TRUETYPE_TAG('c', 's', 'q', 0), // csq = Croatia Sign Language
+ TRUETYPE_TAG('c', 's', 'r', 0), // csr = Costa Rican Sign Language
+ TRUETYPE_TAG('c', 's', 's', 0), // css = Southern Ohlone
+ TRUETYPE_TAG('c', 's', 't', 0), // cst = Northern Ohlone
+ TRUETYPE_TAG('c', 's', 'u', 0), // csu = Central Sudanic languages
+ TRUETYPE_TAG('c', 's', 'w', 0), // csw = Swampy Cree
+ TRUETYPE_TAG('c', 's', 'y', 0), // csy = Siyin Chin
+ TRUETYPE_TAG('c', 's', 'z', 0), // csz = Coos
+ TRUETYPE_TAG('c', 't', 'a', 0), // cta = Tataltepec Chatino
+ TRUETYPE_TAG('c', 't', 'c', 0), // ctc = Chetco
+ TRUETYPE_TAG('c', 't', 'd', 0), // ctd = Tedim Chin
+ TRUETYPE_TAG('c', 't', 'e', 0), // cte = Tepinapa Chinantec
+ TRUETYPE_TAG('c', 't', 'g', 0), // ctg = Chittagonian
+ TRUETYPE_TAG('c', 't', 'l', 0), // ctl = Tlacoatzintepec Chinantec
+ TRUETYPE_TAG('c', 't', 'm', 0), // ctm = Chitimacha
+ TRUETYPE_TAG('c', 't', 'n', 0), // ctn = Chhintange
+ TRUETYPE_TAG('c', 't', 'o', 0), // cto = Emberá-Catío
+ TRUETYPE_TAG('c', 't', 'p', 0), // ctp = Western Highland Chatino
+ TRUETYPE_TAG('c', 't', 's', 0), // cts = Northern Catanduanes Bicolano
+ TRUETYPE_TAG('c', 't', 't', 0), // ctt = Wayanad Chetti
+ TRUETYPE_TAG('c', 't', 'u', 0), // ctu = Chol
+ TRUETYPE_TAG('c', 't', 'z', 0), // ctz = Zacatepec Chatino
+ TRUETYPE_TAG('c', 'u', 'a', 0), // cua = Cua
+ TRUETYPE_TAG('c', 'u', 'b', 0), // cub = Cubeo
+ TRUETYPE_TAG('c', 'u', 'c', 0), // cuc = Usila Chinantec
+ TRUETYPE_TAG('c', 'u', 'g', 0), // cug = Cung
+ TRUETYPE_TAG('c', 'u', 'h', 0), // cuh = Chuka
+ TRUETYPE_TAG('c', 'u', 'i', 0), // cui = Cuiba
+ TRUETYPE_TAG('c', 'u', 'j', 0), // cuj = Mashco Piro
+ TRUETYPE_TAG('c', 'u', 'k', 0), // cuk = San Blas Kuna
+ TRUETYPE_TAG('c', 'u', 'l', 0), // cul = Culina
+ TRUETYPE_TAG('c', 'u', 'm', 0), // cum = Cumeral
+ TRUETYPE_TAG('c', 'u', 'o', 0), // cuo = Cumanagoto
+ TRUETYPE_TAG('c', 'u', 'p', 0), // cup = Cupeño
+ TRUETYPE_TAG('c', 'u', 'q', 0), // cuq = Cun
+ TRUETYPE_TAG('c', 'u', 'r', 0), // cur = Chhulung
+ TRUETYPE_TAG('c', 'u', 's', 0), // cus = Cushitic languages
+ TRUETYPE_TAG('c', 'u', 't', 0), // cut = Teutila Cuicatec
+ TRUETYPE_TAG('c', 'u', 'u', 0), // cuu = Tai Ya
+ TRUETYPE_TAG('c', 'u', 'v', 0), // cuv = Cuvok
+ TRUETYPE_TAG('c', 'u', 'w', 0), // cuw = Chukwa
+ TRUETYPE_TAG('c', 'u', 'x', 0), // cux = Tepeuxila Cuicatec
+ TRUETYPE_TAG('c', 'v', 'g', 0), // cvg = Chug
+ TRUETYPE_TAG('c', 'v', 'n', 0), // cvn = Valle Nacional Chinantec
+ TRUETYPE_TAG('c', 'w', 'a', 0), // cwa = Kabwa
+ TRUETYPE_TAG('c', 'w', 'b', 0), // cwb = Maindo
+ TRUETYPE_TAG('c', 'w', 'd', 0), // cwd = Woods Cree
+ TRUETYPE_TAG('c', 'w', 'e', 0), // cwe = Kwere
+ TRUETYPE_TAG('c', 'w', 'g', 0), // cwg = Chewong
+ TRUETYPE_TAG('c', 'w', 't', 0), // cwt = Kuwaataay
+ TRUETYPE_TAG('c', 'y', 'a', 0), // cya = Nopala Chatino
+ TRUETYPE_TAG('c', 'y', 'b', 0), // cyb = Cayubaba
+ TRUETYPE_TAG('c', 'y', 'o', 0), // cyo = Cuyonon
+ TRUETYPE_TAG('c', 'z', 'h', 0), // czh = Huizhou Chinese
+ TRUETYPE_TAG('c', 'z', 'k', 0), // czk = Knaanic
+ TRUETYPE_TAG('c', 'z', 'n', 0), // czn = Zenzontepec Chatino
+ TRUETYPE_TAG('c', 'z', 'o', 0), // czo = Min Zhong Chinese
+ TRUETYPE_TAG('c', 'z', 't', 0), // czt = Zotung Chin
+ TRUETYPE_TAG('d', 'a', 'a', 0), // daa = Dangaléat
+ TRUETYPE_TAG('d', 'a', 'c', 0), // dac = Dambi
+ TRUETYPE_TAG('d', 'a', 'd', 0), // dad = Marik
+ TRUETYPE_TAG('d', 'a', 'e', 0), // dae = Duupa
+ TRUETYPE_TAG('d', 'a', 'f', 0), // daf = Dan
+ TRUETYPE_TAG('d', 'a', 'g', 0), // dag = Dagbani
+ TRUETYPE_TAG('d', 'a', 'h', 0), // dah = Gwahatike
+ TRUETYPE_TAG('d', 'a', 'i', 0), // dai = Day
+ TRUETYPE_TAG('d', 'a', 'j', 0), // daj = Dar Fur Daju
+ TRUETYPE_TAG('d', 'a', 'k', 0), // dak = Dakota
+ TRUETYPE_TAG('d', 'a', 'l', 0), // dal = Dahalo
+ TRUETYPE_TAG('d', 'a', 'm', 0), // dam = Damakawa
+ TRUETYPE_TAG('d', 'a', 'o', 0), // dao = Daai Chin
+ TRUETYPE_TAG('d', 'a', 'p', 0), // dap = Nisi (India)
+ TRUETYPE_TAG('d', 'a', 'q', 0), // daq = Dandami Maria
+ TRUETYPE_TAG('d', 'a', 'r', 0), // dar = Dargwa
+ TRUETYPE_TAG('d', 'a', 's', 0), // das = Daho-Doo
+ TRUETYPE_TAG('d', 'a', 'u', 0), // dau = Dar Sila Daju
+ TRUETYPE_TAG('d', 'a', 'v', 0), // dav = Taita
+ TRUETYPE_TAG('d', 'a', 'w', 0), // daw = Davawenyo
+ TRUETYPE_TAG('d', 'a', 'x', 0), // dax = Dayi
+ TRUETYPE_TAG('d', 'a', 'y', 0), // day = Land Dayak languages
+ TRUETYPE_TAG('d', 'a', 'z', 0), // daz = Dao
+ TRUETYPE_TAG('d', 'b', 'a', 0), // dba = Bangi Me
+ TRUETYPE_TAG('d', 'b', 'b', 0), // dbb = Deno
+ TRUETYPE_TAG('d', 'b', 'd', 0), // dbd = Dadiya
+ TRUETYPE_TAG('d', 'b', 'e', 0), // dbe = Dabe
+ TRUETYPE_TAG('d', 'b', 'f', 0), // dbf = Edopi
+ TRUETYPE_TAG('d', 'b', 'g', 0), // dbg = Dogul Dom Dogon
+ TRUETYPE_TAG('d', 'b', 'i', 0), // dbi = Doka
+ TRUETYPE_TAG('d', 'b', 'j', 0), // dbj = Ida'an
+ TRUETYPE_TAG('d', 'b', 'l', 0), // dbl = Dyirbal
+ TRUETYPE_TAG('d', 'b', 'm', 0), // dbm = Duguri
+ TRUETYPE_TAG('d', 'b', 'n', 0), // dbn = Duriankere
+ TRUETYPE_TAG('d', 'b', 'o', 0), // dbo = Dulbu
+ TRUETYPE_TAG('d', 'b', 'p', 0), // dbp = Duwai
+ TRUETYPE_TAG('d', 'b', 'q', 0), // dbq = Daba
+ TRUETYPE_TAG('d', 'b', 'r', 0), // dbr = Dabarre
+ TRUETYPE_TAG('d', 'b', 'u', 0), // dbu = Bondum Dom Dogon
+ TRUETYPE_TAG('d', 'b', 'v', 0), // dbv = Dungu
+ TRUETYPE_TAG('d', 'b', 'y', 0), // dby = Dibiyaso
+ TRUETYPE_TAG('d', 'c', 'c', 0), // dcc = Deccan
+ TRUETYPE_TAG('d', 'c', 'r', 0), // dcr = Negerhollands
+ TRUETYPE_TAG('d', 'd', 'd', 0), // ddd = Dongotono
+ TRUETYPE_TAG('d', 'd', 'e', 0), // dde = Doondo
+ TRUETYPE_TAG('d', 'd', 'g', 0), // ddg = Fataluku
+ TRUETYPE_TAG('d', 'd', 'i', 0), // ddi = West Goodenough
+ TRUETYPE_TAG('d', 'd', 'j', 0), // ddj = Jaru
+ TRUETYPE_TAG('d', 'd', 'n', 0), // ddn = Dendi (Benin)
+ TRUETYPE_TAG('d', 'd', 'o', 0), // ddo = Dido
+ TRUETYPE_TAG('d', 'd', 's', 0), // dds = Donno So Dogon
+ TRUETYPE_TAG('d', 'd', 'w', 0), // ddw = Dawera-Daweloor
+ TRUETYPE_TAG('d', 'e', 'c', 0), // dec = Dagik
+ TRUETYPE_TAG('d', 'e', 'd', 0), // ded = Dedua
+ TRUETYPE_TAG('d', 'e', 'e', 0), // dee = Dewoin
+ TRUETYPE_TAG('d', 'e', 'f', 0), // def = Dezfuli
+ TRUETYPE_TAG('d', 'e', 'g', 0), // deg = Degema
+ TRUETYPE_TAG('d', 'e', 'h', 0), // deh = Dehwari
+ TRUETYPE_TAG('d', 'e', 'i', 0), // dei = Demisa
+ TRUETYPE_TAG('d', 'e', 'k', 0), // dek = Dek
+ TRUETYPE_TAG('d', 'e', 'l', 0), // del = Delaware
+ TRUETYPE_TAG('d', 'e', 'm', 0), // dem = Dem
+ TRUETYPE_TAG('d', 'e', 'n', 0), // den = Slave (Athapascan)
+ TRUETYPE_TAG('d', 'e', 'p', 0), // dep = Pidgin Delaware
+ TRUETYPE_TAG('d', 'e', 'q', 0), // deq = Dendi (Central African Republic)
+ TRUETYPE_TAG('d', 'e', 'r', 0), // der = Deori
+ TRUETYPE_TAG('d', 'e', 's', 0), // des = Desano
+ TRUETYPE_TAG('d', 'e', 'v', 0), // dev = Domung
+ TRUETYPE_TAG('d', 'e', 'z', 0), // dez = Dengese
+ TRUETYPE_TAG('d', 'g', 'a', 0), // dga = Southern Dagaare
+ TRUETYPE_TAG('d', 'g', 'b', 0), // dgb = Bunoge Dogon
+ TRUETYPE_TAG('d', 'g', 'c', 0), // dgc = Casiguran Dumagat Agta
+ TRUETYPE_TAG('d', 'g', 'd', 0), // dgd = Dagaari Dioula
+ TRUETYPE_TAG('d', 'g', 'e', 0), // dge = Degenan
+ TRUETYPE_TAG('d', 'g', 'g', 0), // dgg = Doga
+ TRUETYPE_TAG('d', 'g', 'h', 0), // dgh = Dghwede
+ TRUETYPE_TAG('d', 'g', 'i', 0), // dgi = Northern Dagara
+ TRUETYPE_TAG('d', 'g', 'k', 0), // dgk = Dagba
+ TRUETYPE_TAG('d', 'g', 'n', 0), // dgn = Dagoman
+ TRUETYPE_TAG('d', 'g', 'o', 0), // dgo = Dogri (individual language)
+ TRUETYPE_TAG('d', 'g', 'r', 0), // dgr = Dogrib
+ TRUETYPE_TAG('d', 'g', 's', 0), // dgs = Dogoso
+ TRUETYPE_TAG('d', 'g', 'u', 0), // dgu = Degaru
+ TRUETYPE_TAG('d', 'g', 'x', 0), // dgx = Doghoro
+ TRUETYPE_TAG('d', 'g', 'z', 0), // dgz = Daga
+ TRUETYPE_TAG('d', 'h', 'a', 0), // dha = Dhanwar (India)
+ TRUETYPE_TAG('d', 'h', 'd', 0), // dhd = Dhundari
+ TRUETYPE_TAG('d', 'h', 'g', 0), // dhg = Dhangu
+ TRUETYPE_TAG('d', 'h', 'i', 0), // dhi = Dhimal
+ TRUETYPE_TAG('d', 'h', 'l', 0), // dhl = Dhalandji
+ TRUETYPE_TAG('d', 'h', 'm', 0), // dhm = Zemba
+ TRUETYPE_TAG('d', 'h', 'n', 0), // dhn = Dhanki
+ TRUETYPE_TAG('d', 'h', 'o', 0), // dho = Dhodia
+ TRUETYPE_TAG('d', 'h', 'r', 0), // dhr = Dhargari
+ TRUETYPE_TAG('d', 'h', 's', 0), // dhs = Dhaiso
+ TRUETYPE_TAG('d', 'h', 'u', 0), // dhu = Dhurga
+ TRUETYPE_TAG('d', 'h', 'v', 0), // dhv = Dehu
+ TRUETYPE_TAG('d', 'h', 'w', 0), // dhw = Dhanwar (Nepal)
+ TRUETYPE_TAG('d', 'i', 'a', 0), // dia = Dia
+ TRUETYPE_TAG('d', 'i', 'b', 0), // dib = South Central Dinka
+ TRUETYPE_TAG('d', 'i', 'c', 0), // dic = Lakota Dida
+ TRUETYPE_TAG('d', 'i', 'd', 0), // did = Didinga
+ TRUETYPE_TAG('d', 'i', 'f', 0), // dif = Dieri
+ TRUETYPE_TAG('d', 'i', 'g', 0), // dig = Digo
+ TRUETYPE_TAG('d', 'i', 'h', 0), // dih = Kumiai
+ TRUETYPE_TAG('d', 'i', 'i', 0), // dii = Dimbong
+ TRUETYPE_TAG('d', 'i', 'j', 0), // dij = Dai
+ TRUETYPE_TAG('d', 'i', 'k', 0), // dik = Southwestern Dinka
+ TRUETYPE_TAG('d', 'i', 'l', 0), // dil = Dilling
+ TRUETYPE_TAG('d', 'i', 'm', 0), // dim = Dime
+ TRUETYPE_TAG('d', 'i', 'n', 0), // din = Dinka
+ TRUETYPE_TAG('d', 'i', 'o', 0), // dio = Dibo
+ TRUETYPE_TAG('d', 'i', 'p', 0), // dip = Northeastern Dinka
+ TRUETYPE_TAG('d', 'i', 'q', 0), // diq = Dimli (individual language)
+ TRUETYPE_TAG('d', 'i', 'r', 0), // dir = Dirim
+ TRUETYPE_TAG('d', 'i', 's', 0), // dis = Dimasa
+ TRUETYPE_TAG('d', 'i', 't', 0), // dit = Dirari
+ TRUETYPE_TAG('d', 'i', 'u', 0), // diu = Diriku
+ TRUETYPE_TAG('d', 'i', 'w', 0), // diw = Northwestern Dinka
+ TRUETYPE_TAG('d', 'i', 'x', 0), // dix = Dixon Reef
+ TRUETYPE_TAG('d', 'i', 'y', 0), // diy = Diuwe
+ TRUETYPE_TAG('d', 'i', 'z', 0), // diz = Ding
+ TRUETYPE_TAG('d', 'j', 'b', 0), // djb = Djinba
+ TRUETYPE_TAG('d', 'j', 'c', 0), // djc = Dar Daju Daju
+ TRUETYPE_TAG('d', 'j', 'd', 0), // djd = Djamindjung
+ TRUETYPE_TAG('d', 'j', 'e', 0), // dje = Zarma
+ TRUETYPE_TAG('d', 'j', 'f', 0), // djf = Djangun
+ TRUETYPE_TAG('d', 'j', 'i', 0), // dji = Djinang
+ TRUETYPE_TAG('d', 'j', 'j', 0), // djj = Djeebbana
+ TRUETYPE_TAG('d', 'j', 'k', 0), // djk = Eastern Maroon Creole
+ TRUETYPE_TAG('d', 'j', 'l', 0), // djl = Djiwarli
+ TRUETYPE_TAG('d', 'j', 'm', 0), // djm = Jamsay Dogon
+ TRUETYPE_TAG('d', 'j', 'n', 0), // djn = Djauan
+ TRUETYPE_TAG('d', 'j', 'o', 0), // djo = Jangkang
+ TRUETYPE_TAG('d', 'j', 'r', 0), // djr = Djambarrpuyngu
+ TRUETYPE_TAG('d', 'j', 'u', 0), // dju = Kapriman
+ TRUETYPE_TAG('d', 'j', 'w', 0), // djw = Djawi
+ TRUETYPE_TAG('d', 'k', 'a', 0), // dka = Dakpakha
+ TRUETYPE_TAG('d', 'k', 'k', 0), // dkk = Dakka
+ TRUETYPE_TAG('d', 'k', 'l', 0), // dkl = Kolum So Dogon
+ TRUETYPE_TAG('d', 'k', 'r', 0), // dkr = Kuijau
+ TRUETYPE_TAG('d', 'k', 's', 0), // dks = Southeastern Dinka
+ TRUETYPE_TAG('d', 'k', 'x', 0), // dkx = Mazagway
+ TRUETYPE_TAG('d', 'l', 'g', 0), // dlg = Dolgan
+ TRUETYPE_TAG('d', 'l', 'm', 0), // dlm = Dalmatian
+ TRUETYPE_TAG('d', 'l', 'n', 0), // dln = Darlong
+ TRUETYPE_TAG('d', 'm', 'a', 0), // dma = Duma
+ TRUETYPE_TAG('d', 'm', 'b', 0), // dmb = Mombo Dogon
+ TRUETYPE_TAG('d', 'm', 'c', 0), // dmc = Dimir
+ TRUETYPE_TAG('d', 'm', 'e', 0), // dme = Dugwor
+ TRUETYPE_TAG('d', 'm', 'g', 0), // dmg = Upper Kinabatangan
+ TRUETYPE_TAG('d', 'm', 'k', 0), // dmk = Domaaki
+ TRUETYPE_TAG('d', 'm', 'l', 0), // dml = Dameli
+ TRUETYPE_TAG('d', 'm', 'm', 0), // dmm = Dama
+ TRUETYPE_TAG('d', 'm', 'n', 0), // dmn = Mande languages
+ TRUETYPE_TAG('d', 'm', 'o', 0), // dmo = Kemezung
+ TRUETYPE_TAG('d', 'm', 'r', 0), // dmr = East Damar
+ TRUETYPE_TAG('d', 'm', 's', 0), // dms = Dampelas
+ TRUETYPE_TAG('d', 'm', 'u', 0), // dmu = Dubu
+ TRUETYPE_TAG('d', 'm', 'v', 0), // dmv = Dumpas
+ TRUETYPE_TAG('d', 'm', 'x', 0), // dmx = Dema
+ TRUETYPE_TAG('d', 'm', 'y', 0), // dmy = Demta
+ TRUETYPE_TAG('d', 'n', 'a', 0), // dna = Upper Grand Valley Dani
+ TRUETYPE_TAG('d', 'n', 'd', 0), // dnd = Daonda
+ TRUETYPE_TAG('d', 'n', 'e', 0), // dne = Ndendeule
+ TRUETYPE_TAG('d', 'n', 'g', 0), // dng = Dungan
+ TRUETYPE_TAG('d', 'n', 'i', 0), // dni = Lower Grand Valley Dani
+ TRUETYPE_TAG('d', 'n', 'k', 0), // dnk = Dengka
+ TRUETYPE_TAG('d', 'n', 'n', 0), // dnn = Dzùùngoo
+ TRUETYPE_TAG('d', 'n', 'r', 0), // dnr = Danaru
+ TRUETYPE_TAG('d', 'n', 't', 0), // dnt = Mid Grand Valley Dani
+ TRUETYPE_TAG('d', 'n', 'u', 0), // dnu = Danau
+ TRUETYPE_TAG('d', 'n', 'w', 0), // dnw = Western Dani
+ TRUETYPE_TAG('d', 'n', 'y', 0), // dny = Dení
+ TRUETYPE_TAG('d', 'o', 'a', 0), // doa = Dom
+ TRUETYPE_TAG('d', 'o', 'b', 0), // dob = Dobu
+ TRUETYPE_TAG('d', 'o', 'c', 0), // doc = Northern Dong
+ TRUETYPE_TAG('d', 'o', 'e', 0), // doe = Doe
+ TRUETYPE_TAG('d', 'o', 'f', 0), // dof = Domu
+ TRUETYPE_TAG('d', 'o', 'h', 0), // doh = Dong
+ TRUETYPE_TAG('d', 'o', 'i', 0), // doi = Dogri (macrolanguage)
+ TRUETYPE_TAG('d', 'o', 'k', 0), // dok = Dondo
+ TRUETYPE_TAG('d', 'o', 'l', 0), // dol = Doso
+ TRUETYPE_TAG('d', 'o', 'n', 0), // don = Toura (Papua New Guinea)
+ TRUETYPE_TAG('d', 'o', 'o', 0), // doo = Dongo
+ TRUETYPE_TAG('d', 'o', 'p', 0), // dop = Lukpa
+ TRUETYPE_TAG('d', 'o', 'q', 0), // doq = Dominican Sign Language
+ TRUETYPE_TAG('d', 'o', 'r', 0), // dor = Dori'o
+ TRUETYPE_TAG('d', 'o', 's', 0), // dos = Dogosé
+ TRUETYPE_TAG('d', 'o', 't', 0), // dot = Dass
+ TRUETYPE_TAG('d', 'o', 'v', 0), // dov = Dombe
+ TRUETYPE_TAG('d', 'o', 'w', 0), // dow = Doyayo
+ TRUETYPE_TAG('d', 'o', 'x', 0), // dox = Bussa
+ TRUETYPE_TAG('d', 'o', 'y', 0), // doy = Dompo
+ TRUETYPE_TAG('d', 'o', 'z', 0), // doz = Dorze
+ TRUETYPE_TAG('d', 'p', 'p', 0), // dpp = Papar
+ TRUETYPE_TAG('d', 'r', 'a', 0), // dra = Dravidian languages
+ TRUETYPE_TAG('d', 'r', 'b', 0), // drb = Dair
+ TRUETYPE_TAG('d', 'r', 'c', 0), // drc = Minderico
+ TRUETYPE_TAG('d', 'r', 'd', 0), // drd = Darmiya
+ TRUETYPE_TAG('d', 'r', 'e', 0), // dre = Dolpo
+ TRUETYPE_TAG('d', 'r', 'g', 0), // drg = Rungus
+ TRUETYPE_TAG('d', 'r', 'h', 0), // drh = Darkhat
+ TRUETYPE_TAG('d', 'r', 'i', 0), // dri = C'lela
+ TRUETYPE_TAG('d', 'r', 'l', 0), // drl = Darling
+ TRUETYPE_TAG('d', 'r', 'n', 0), // drn = West Damar
+ TRUETYPE_TAG('d', 'r', 'o', 0), // dro = Daro-Matu Melanau
+ TRUETYPE_TAG('d', 'r', 'q', 0), // drq = Dura
+ TRUETYPE_TAG('d', 'r', 'r', 0), // drr = Dororo
+ TRUETYPE_TAG('d', 'r', 's', 0), // drs = Gedeo
+ TRUETYPE_TAG('d', 'r', 't', 0), // drt = Drents
+ TRUETYPE_TAG('d', 'r', 'u', 0), // dru = Rukai
+ TRUETYPE_TAG('d', 'r', 'w', 0), // drw = Darwazi
+ TRUETYPE_TAG('d', 'r', 'y', 0), // dry = Darai
+ TRUETYPE_TAG('d', 's', 'b', 0), // dsb = Lower Sorbian
+ TRUETYPE_TAG('d', 's', 'e', 0), // dse = Dutch Sign Language
+ TRUETYPE_TAG('d', 's', 'h', 0), // dsh = Daasanach
+ TRUETYPE_TAG('d', 's', 'i', 0), // dsi = Disa
+ TRUETYPE_TAG('d', 's', 'l', 0), // dsl = Danish Sign Language
+ TRUETYPE_TAG('d', 's', 'n', 0), // dsn = Dusner
+ TRUETYPE_TAG('d', 's', 'o', 0), // dso = Desiya
+ TRUETYPE_TAG('d', 's', 'q', 0), // dsq = Tadaksahak
+ TRUETYPE_TAG('d', 't', 'a', 0), // dta = Daur
+ TRUETYPE_TAG('d', 't', 'b', 0), // dtb = Labuk-Kinabatangan Kadazan
+ TRUETYPE_TAG('d', 't', 'd', 0), // dtd = Ditidaht
+ TRUETYPE_TAG('d', 't', 'i', 0), // dti = Ana Tinga Dogon
+ TRUETYPE_TAG('d', 't', 'k', 0), // dtk = Tene Kan Dogon
+ TRUETYPE_TAG('d', 't', 'm', 0), // dtm = Tomo Kan Dogon
+ TRUETYPE_TAG('d', 't', 'p', 0), // dtp = Central Dusun
+ TRUETYPE_TAG('d', 't', 'r', 0), // dtr = Lotud
+ TRUETYPE_TAG('d', 't', 's', 0), // dts = Toro So Dogon
+ TRUETYPE_TAG('d', 't', 't', 0), // dtt = Toro Tegu Dogon
+ TRUETYPE_TAG('d', 't', 'u', 0), // dtu = Tebul Ure Dogon
+ TRUETYPE_TAG('d', 'u', 'a', 0), // dua = Duala
+ TRUETYPE_TAG('d', 'u', 'b', 0), // dub = Dubli
+ TRUETYPE_TAG('d', 'u', 'c', 0), // duc = Duna
+ TRUETYPE_TAG('d', 'u', 'd', 0), // dud = Hun-Saare
+ TRUETYPE_TAG('d', 'u', 'e', 0), // due = Umiray Dumaget Agta
+ TRUETYPE_TAG('d', 'u', 'f', 0), // duf = Dumbea
+ TRUETYPE_TAG('d', 'u', 'g', 0), // dug = Duruma
+ TRUETYPE_TAG('d', 'u', 'h', 0), // duh = Dungra Bhil
+ TRUETYPE_TAG('d', 'u', 'i', 0), // dui = Dumun
+ TRUETYPE_TAG('d', 'u', 'j', 0), // duj = Dhuwal
+ TRUETYPE_TAG('d', 'u', 'k', 0), // duk = Uyajitaya
+ TRUETYPE_TAG('d', 'u', 'l', 0), // dul = Alabat Island Agta
+ TRUETYPE_TAG('d', 'u', 'm', 0), // dum = Middle Dutch (ca. 1050-1350)
+ TRUETYPE_TAG('d', 'u', 'n', 0), // dun = Dusun Deyah
+ TRUETYPE_TAG('d', 'u', 'o', 0), // duo = Dupaninan Agta
+ TRUETYPE_TAG('d', 'u', 'p', 0), // dup = Duano
+ TRUETYPE_TAG('d', 'u', 'q', 0), // duq = Dusun Malang
+ TRUETYPE_TAG('d', 'u', 'r', 0), // dur = Dii
+ TRUETYPE_TAG('d', 'u', 's', 0), // dus = Dumi
+ TRUETYPE_TAG('d', 'u', 'u', 0), // duu = Drung
+ TRUETYPE_TAG('d', 'u', 'v', 0), // duv = Duvle
+ TRUETYPE_TAG('d', 'u', 'w', 0), // duw = Dusun Witu
+ TRUETYPE_TAG('d', 'u', 'x', 0), // dux = Duungooma
+ TRUETYPE_TAG('d', 'u', 'y', 0), // duy = Dicamay Agta
+ TRUETYPE_TAG('d', 'u', 'z', 0), // duz = Duli
+ TRUETYPE_TAG('d', 'v', 'a', 0), // dva = Duau
+ TRUETYPE_TAG('d', 'w', 'a', 0), // dwa = Diri
+ TRUETYPE_TAG('d', 'w', 'l', 0), // dwl = Walo Kumbe Dogon
+ TRUETYPE_TAG('d', 'w', 'r', 0), // dwr = Dawro
+ TRUETYPE_TAG('d', 'w', 's', 0), // dws = Dutton World Speedwords
+ TRUETYPE_TAG('d', 'w', 'w', 0), // dww = Dawawa
+ TRUETYPE_TAG('d', 'y', 'a', 0), // dya = Dyan
+ TRUETYPE_TAG('d', 'y', 'b', 0), // dyb = Dyaberdyaber
+ TRUETYPE_TAG('d', 'y', 'd', 0), // dyd = Dyugun
+ TRUETYPE_TAG('d', 'y', 'g', 0), // dyg = Villa Viciosa Agta
+ TRUETYPE_TAG('d', 'y', 'i', 0), // dyi = Djimini Senoufo
+ TRUETYPE_TAG('d', 'y', 'm', 0), // dym = Yanda Dom Dogon
+ TRUETYPE_TAG('d', 'y', 'n', 0), // dyn = Dyangadi
+ TRUETYPE_TAG('d', 'y', 'o', 0), // dyo = Jola-Fonyi
+ TRUETYPE_TAG('d', 'y', 'u', 0), // dyu = Dyula
+ TRUETYPE_TAG('d', 'y', 'y', 0), // dyy = Dyaabugay
+ TRUETYPE_TAG('d', 'z', 'a', 0), // dza = Tunzu
+ TRUETYPE_TAG('d', 'z', 'd', 0), // dzd = Daza
+ TRUETYPE_TAG('d', 'z', 'g', 0), // dzg = Dazaga
+ TRUETYPE_TAG('d', 'z', 'l', 0), // dzl = Dzalakha
+ TRUETYPE_TAG('d', 'z', 'n', 0), // dzn = Dzando
+ TRUETYPE_TAG('e', 'b', 'g', 0), // ebg = Ebughu
+ TRUETYPE_TAG('e', 'b', 'k', 0), // ebk = Eastern Bontok
+ TRUETYPE_TAG('e', 'b', 'o', 0), // ebo = Teke-Ebo
+ TRUETYPE_TAG('e', 'b', 'r', 0), // ebr = Ebrié
+ TRUETYPE_TAG('e', 'b', 'u', 0), // ebu = Embu
+ TRUETYPE_TAG('e', 'c', 'r', 0), // ecr = Eteocretan
+ TRUETYPE_TAG('e', 'c', 's', 0), // ecs = Ecuadorian Sign Language
+ TRUETYPE_TAG('e', 'c', 'y', 0), // ecy = Eteocypriot
+ TRUETYPE_TAG('e', 'e', 'e', 0), // eee = E
+ TRUETYPE_TAG('e', 'f', 'a', 0), // efa = Efai
+ TRUETYPE_TAG('e', 'f', 'e', 0), // efe = Efe
+ TRUETYPE_TAG('e', 'f', 'i', 0), // efi = Efik
+ TRUETYPE_TAG('e', 'g', 'a', 0), // ega = Ega
+ TRUETYPE_TAG('e', 'g', 'l', 0), // egl = Emilian
+ TRUETYPE_TAG('e', 'g', 'o', 0), // ego = Eggon
+ TRUETYPE_TAG('e', 'g', 'x', 0), // egx = Egyptian languages
+ TRUETYPE_TAG('e', 'g', 'y', 0), // egy = Egyptian (Ancient)
+ TRUETYPE_TAG('e', 'h', 'u', 0), // ehu = Ehueun
+ TRUETYPE_TAG('e', 'i', 'p', 0), // eip = Eipomek
+ TRUETYPE_TAG('e', 'i', 't', 0), // eit = Eitiep
+ TRUETYPE_TAG('e', 'i', 'v', 0), // eiv = Askopan
+ TRUETYPE_TAG('e', 'j', 'a', 0), // eja = Ejamat
+ TRUETYPE_TAG('e', 'k', 'a', 0), // eka = Ekajuk
+ TRUETYPE_TAG('e', 'k', 'e', 0), // eke = Ekit
+ TRUETYPE_TAG('e', 'k', 'g', 0), // ekg = Ekari
+ TRUETYPE_TAG('e', 'k', 'i', 0), // eki = Eki
+ TRUETYPE_TAG('e', 'k', 'k', 0), // ekk = Standard Estonian
+ TRUETYPE_TAG('e', 'k', 'l', 0), // ekl = Kol
+ TRUETYPE_TAG('e', 'k', 'm', 0), // ekm = Elip
+ TRUETYPE_TAG('e', 'k', 'o', 0), // eko = Koti
+ TRUETYPE_TAG('e', 'k', 'p', 0), // ekp = Ekpeye
+ TRUETYPE_TAG('e', 'k', 'r', 0), // ekr = Yace
+ TRUETYPE_TAG('e', 'k', 'y', 0), // eky = Eastern Kayah
+ TRUETYPE_TAG('e', 'l', 'e', 0), // ele = Elepi
+ TRUETYPE_TAG('e', 'l', 'h', 0), // elh = El Hugeirat
+ TRUETYPE_TAG('e', 'l', 'i', 0), // eli = Nding
+ TRUETYPE_TAG('e', 'l', 'k', 0), // elk = Elkei
+ TRUETYPE_TAG('e', 'l', 'm', 0), // elm = Eleme
+ TRUETYPE_TAG('e', 'l', 'o', 0), // elo = El Molo
+ TRUETYPE_TAG('e', 'l', 'p', 0), // elp = Elpaputih
+ TRUETYPE_TAG('e', 'l', 'u', 0), // elu = Elu
+ TRUETYPE_TAG('e', 'l', 'x', 0), // elx = Elamite
+ TRUETYPE_TAG('e', 'm', 'a', 0), // ema = Emai-Iuleha-Ora
+ TRUETYPE_TAG('e', 'm', 'b', 0), // emb = Embaloh
+ TRUETYPE_TAG('e', 'm', 'e', 0), // eme = Emerillon
+ TRUETYPE_TAG('e', 'm', 'g', 0), // emg = Eastern Meohang
+ TRUETYPE_TAG('e', 'm', 'i', 0), // emi = Mussau-Emira
+ TRUETYPE_TAG('e', 'm', 'k', 0), // emk = Eastern Maninkakan
+ TRUETYPE_TAG('e', 'm', 'm', 0), // emm = Mamulique
+ TRUETYPE_TAG('e', 'm', 'n', 0), // emn = Eman
+ TRUETYPE_TAG('e', 'm', 'o', 0), // emo = Emok
+ TRUETYPE_TAG('e', 'm', 'p', 0), // emp = Northern Emberá
+ TRUETYPE_TAG('e', 'm', 's', 0), // ems = Pacific Gulf Yupik
+ TRUETYPE_TAG('e', 'm', 'u', 0), // emu = Eastern Muria
+ TRUETYPE_TAG('e', 'm', 'w', 0), // emw = Emplawas
+ TRUETYPE_TAG('e', 'm', 'x', 0), // emx = Erromintxela
+ TRUETYPE_TAG('e', 'm', 'y', 0), // emy = Epigraphic Mayan
+ TRUETYPE_TAG('e', 'n', 'a', 0), // ena = Apali
+ TRUETYPE_TAG('e', 'n', 'b', 0), // enb = Markweeta
+ TRUETYPE_TAG('e', 'n', 'c', 0), // enc = En
+ TRUETYPE_TAG('e', 'n', 'd', 0), // end = Ende
+ TRUETYPE_TAG('e', 'n', 'f', 0), // enf = Forest Enets
+ TRUETYPE_TAG('e', 'n', 'h', 0), // enh = Tundra Enets
+ TRUETYPE_TAG('e', 'n', 'm', 0), // enm = Middle English (1100-1500)
+ TRUETYPE_TAG('e', 'n', 'n', 0), // enn = Engenni
+ TRUETYPE_TAG('e', 'n', 'o', 0), // eno = Enggano
+ TRUETYPE_TAG('e', 'n', 'q', 0), // enq = Enga
+ TRUETYPE_TAG('e', 'n', 'r', 0), // enr = Emumu
+ TRUETYPE_TAG('e', 'n', 'u', 0), // enu = Enu
+ TRUETYPE_TAG('e', 'n', 'v', 0), // env = Enwan (Edu State)
+ TRUETYPE_TAG('e', 'n', 'w', 0), // enw = Enwan (Akwa Ibom State)
+ TRUETYPE_TAG('e', 'o', 't', 0), // eot = Beti (Côte d'Ivoire)
+ TRUETYPE_TAG('e', 'p', 'i', 0), // epi = Epie
+ TRUETYPE_TAG('e', 'r', 'a', 0), // era = Eravallan
+ TRUETYPE_TAG('e', 'r', 'g', 0), // erg = Sie
+ TRUETYPE_TAG('e', 'r', 'h', 0), // erh = Eruwa
+ TRUETYPE_TAG('e', 'r', 'i', 0), // eri = Ogea
+ TRUETYPE_TAG('e', 'r', 'k', 0), // erk = South Efate
+ TRUETYPE_TAG('e', 'r', 'o', 0), // ero = Horpa
+ TRUETYPE_TAG('e', 'r', 'r', 0), // err = Erre
+ TRUETYPE_TAG('e', 'r', 's', 0), // ers = Ersu
+ TRUETYPE_TAG('e', 'r', 't', 0), // ert = Eritai
+ TRUETYPE_TAG('e', 'r', 'w', 0), // erw = Erokwanas
+ TRUETYPE_TAG('e', 's', 'e', 0), // ese = Ese Ejja
+ TRUETYPE_TAG('e', 's', 'h', 0), // esh = Eshtehardi
+ TRUETYPE_TAG('e', 's', 'i', 0), // esi = North Alaskan Inupiatun
+ TRUETYPE_TAG('e', 's', 'k', 0), // esk = Northwest Alaska Inupiatun
+ TRUETYPE_TAG('e', 's', 'l', 0), // esl = Egypt Sign Language
+ TRUETYPE_TAG('e', 's', 'm', 0), // esm = Esuma
+ TRUETYPE_TAG('e', 's', 'n', 0), // esn = Salvadoran Sign Language
+ TRUETYPE_TAG('e', 's', 'o', 0), // eso = Estonian Sign Language
+ TRUETYPE_TAG('e', 's', 'q', 0), // esq = Esselen
+ TRUETYPE_TAG('e', 's', 's', 0), // ess = Central Siberian Yupik
+ TRUETYPE_TAG('e', 's', 'u', 0), // esu = Central Yupik
+ TRUETYPE_TAG('e', 's', 'x', 0), // esx = Eskimo-Aleut languages
+ TRUETYPE_TAG('e', 't', 'b', 0), // etb = Etebi
+ TRUETYPE_TAG('e', 't', 'c', 0), // etc = Etchemin
+ TRUETYPE_TAG('e', 't', 'h', 0), // eth = Ethiopian Sign Language
+ TRUETYPE_TAG('e', 't', 'n', 0), // etn = Eton (Vanuatu)
+ TRUETYPE_TAG('e', 't', 'o', 0), // eto = Eton (Cameroon)
+ TRUETYPE_TAG('e', 't', 'r', 0), // etr = Edolo
+ TRUETYPE_TAG('e', 't', 's', 0), // ets = Yekhee
+ TRUETYPE_TAG('e', 't', 't', 0), // ett = Etruscan
+ TRUETYPE_TAG('e', 't', 'u', 0), // etu = Ejagham
+ TRUETYPE_TAG('e', 't', 'x', 0), // etx = Eten
+ TRUETYPE_TAG('e', 't', 'z', 0), // etz = Semimi
+ TRUETYPE_TAG('e', 'u', 'q', 0), // euq = Basque (family)
+ TRUETYPE_TAG('e', 'v', 'e', 0), // eve = Even
+ TRUETYPE_TAG('e', 'v', 'h', 0), // evh = Uvbie
+ TRUETYPE_TAG('e', 'v', 'n', 0), // evn = Evenki
+ TRUETYPE_TAG('e', 'w', 'o', 0), // ewo = Ewondo
+ TRUETYPE_TAG('e', 'x', 't', 0), // ext = Extremaduran
+ TRUETYPE_TAG('e', 'y', 'a', 0), // eya = Eyak
+ TRUETYPE_TAG('e', 'y', 'o', 0), // eyo = Keiyo
+ TRUETYPE_TAG('e', 'z', 'e', 0), // eze = Uzekwe
+ TRUETYPE_TAG('f', 'a', 'a', 0), // faa = Fasu
+ TRUETYPE_TAG('f', 'a', 'b', 0), // fab = Fa D'ambu
+ TRUETYPE_TAG('f', 'a', 'd', 0), // fad = Wagi
+ TRUETYPE_TAG('f', 'a', 'f', 0), // faf = Fagani
+ TRUETYPE_TAG('f', 'a', 'g', 0), // fag = Finongan
+ TRUETYPE_TAG('f', 'a', 'h', 0), // fah = Baissa Fali
+ TRUETYPE_TAG('f', 'a', 'i', 0), // fai = Faiwol
+ TRUETYPE_TAG('f', 'a', 'j', 0), // faj = Faita
+ TRUETYPE_TAG('f', 'a', 'k', 0), // fak = Fang (Cameroon)
+ TRUETYPE_TAG('f', 'a', 'l', 0), // fal = South Fali
+ TRUETYPE_TAG('f', 'a', 'm', 0), // fam = Fam
+ TRUETYPE_TAG('f', 'a', 'n', 0), // fan = Fang (Equatorial Guinea)
+ TRUETYPE_TAG('f', 'a', 'p', 0), // fap = Palor
+ TRUETYPE_TAG('f', 'a', 'r', 0), // far = Fataleka
+ TRUETYPE_TAG('f', 'a', 't', 0), // fat = Fanti
+ TRUETYPE_TAG('f', 'a', 'u', 0), // fau = Fayu
+ TRUETYPE_TAG('f', 'a', 'x', 0), // fax = Fala
+ TRUETYPE_TAG('f', 'a', 'y', 0), // fay = Southwestern Fars
+ TRUETYPE_TAG('f', 'a', 'z', 0), // faz = Northwestern Fars
+ TRUETYPE_TAG('f', 'b', 'l', 0), // fbl = West Albay Bikol
+ TRUETYPE_TAG('f', 'c', 's', 0), // fcs = Quebec Sign Language
+ TRUETYPE_TAG('f', 'e', 'r', 0), // fer = Feroge
+ TRUETYPE_TAG('f', 'f', 'i', 0), // ffi = Foia Foia
+ TRUETYPE_TAG('f', 'f', 'm', 0), // ffm = Maasina Fulfulde
+ TRUETYPE_TAG('f', 'g', 'r', 0), // fgr = Fongoro
+ TRUETYPE_TAG('f', 'i', 'a', 0), // fia = Nobiin
+ TRUETYPE_TAG('f', 'i', 'e', 0), // fie = Fyer
+ TRUETYPE_TAG('f', 'i', 'l', 0), // fil = Filipino
+ TRUETYPE_TAG('f', 'i', 'p', 0), // fip = Fipa
+ TRUETYPE_TAG('f', 'i', 'r', 0), // fir = Firan
+ TRUETYPE_TAG('f', 'i', 't', 0), // fit = Tornedalen Finnish
+ TRUETYPE_TAG('f', 'i', 'u', 0), // fiu = Finno-Ugrian languages
+ TRUETYPE_TAG('f', 'i', 'w', 0), // fiw = Fiwaga
+ TRUETYPE_TAG('f', 'k', 'v', 0), // fkv = Kven Finnish
+ TRUETYPE_TAG('f', 'l', 'a', 0), // fla = Kalispel-Pend d'Oreille
+ TRUETYPE_TAG('f', 'l', 'h', 0), // flh = Foau
+ TRUETYPE_TAG('f', 'l', 'i', 0), // fli = Fali
+ TRUETYPE_TAG('f', 'l', 'l', 0), // fll = North Fali
+ TRUETYPE_TAG('f', 'l', 'n', 0), // fln = Flinders Island
+ TRUETYPE_TAG('f', 'l', 'r', 0), // flr = Fuliiru
+ TRUETYPE_TAG('f', 'l', 'y', 0), // fly = Tsotsitaal
+ TRUETYPE_TAG('f', 'm', 'p', 0), // fmp = Fe'fe'
+ TRUETYPE_TAG('f', 'm', 'u', 0), // fmu = Far Western Muria
+ TRUETYPE_TAG('f', 'n', 'g', 0), // fng = Fanagalo
+ TRUETYPE_TAG('f', 'n', 'i', 0), // fni = Fania
+ TRUETYPE_TAG('f', 'o', 'd', 0), // fod = Foodo
+ TRUETYPE_TAG('f', 'o', 'i', 0), // foi = Foi
+ TRUETYPE_TAG('f', 'o', 'm', 0), // fom = Foma
+ TRUETYPE_TAG('f', 'o', 'n', 0), // fon = Fon
+ TRUETYPE_TAG('f', 'o', 'r', 0), // for = Fore
+ TRUETYPE_TAG('f', 'o', 's', 0), // fos = Siraya
+ TRUETYPE_TAG('f', 'o', 'x', 0), // fox = Formosan languages
+ TRUETYPE_TAG('f', 'p', 'e', 0), // fpe = Fernando Po Creole English
+ TRUETYPE_TAG('f', 'q', 's', 0), // fqs = Fas
+ TRUETYPE_TAG('f', 'r', 'c', 0), // frc = Cajun French
+ TRUETYPE_TAG('f', 'r', 'd', 0), // frd = Fordata
+ TRUETYPE_TAG('f', 'r', 'k', 0), // frk = Frankish
+ TRUETYPE_TAG('f', 'r', 'm', 0), // frm = Middle French (ca. 1400-1600)
+ TRUETYPE_TAG('f', 'r', 'o', 0), // fro = Old French (842-ca. 1400)
+ TRUETYPE_TAG('f', 'r', 'p', 0), // frp = Arpitan
+ TRUETYPE_TAG('f', 'r', 'q', 0), // frq = Forak
+ TRUETYPE_TAG('f', 'r', 'r', 0), // frr = Northern Frisian
+ TRUETYPE_TAG('f', 'r', 's', 0), // frs = Eastern Frisian
+ TRUETYPE_TAG('f', 'r', 't', 0), // frt = Fortsenal
+ TRUETYPE_TAG('f', 's', 'e', 0), // fse = Finnish Sign Language
+ TRUETYPE_TAG('f', 's', 'l', 0), // fsl = French Sign Language
+ TRUETYPE_TAG('f', 's', 's', 0), // fss = Finland-Swedish Sign Language
+ TRUETYPE_TAG('f', 'u', 'b', 0), // fub = Adamawa Fulfulde
+ TRUETYPE_TAG('f', 'u', 'c', 0), // fuc = Pulaar
+ TRUETYPE_TAG('f', 'u', 'd', 0), // fud = East Futuna
+ TRUETYPE_TAG('f', 'u', 'e', 0), // fue = Borgu Fulfulde
+ TRUETYPE_TAG('f', 'u', 'f', 0), // fuf = Pular
+ TRUETYPE_TAG('f', 'u', 'h', 0), // fuh = Western Niger Fulfulde
+ TRUETYPE_TAG('f', 'u', 'i', 0), // fui = Bagirmi Fulfulde
+ TRUETYPE_TAG('f', 'u', 'j', 0), // fuj = Ko
+ TRUETYPE_TAG('f', 'u', 'm', 0), // fum = Fum
+ TRUETYPE_TAG('f', 'u', 'n', 0), // fun = Fulniô
+ TRUETYPE_TAG('f', 'u', 'q', 0), // fuq = Central-Eastern Niger Fulfulde
+ TRUETYPE_TAG('f', 'u', 'r', 0), // fur = Friulian
+ TRUETYPE_TAG('f', 'u', 't', 0), // fut = Futuna-Aniwa
+ TRUETYPE_TAG('f', 'u', 'u', 0), // fuu = Furu
+ TRUETYPE_TAG('f', 'u', 'v', 0), // fuv = Nigerian Fulfulde
+ TRUETYPE_TAG('f', 'u', 'y', 0), // fuy = Fuyug
+ TRUETYPE_TAG('f', 'v', 'r', 0), // fvr = Fur
+ TRUETYPE_TAG('f', 'w', 'a', 0), // fwa = Fwâi
+ TRUETYPE_TAG('f', 'w', 'e', 0), // fwe = Fwe
+ TRUETYPE_TAG('g', 'a', 'a', 0), // gaa = Ga
+ TRUETYPE_TAG('g', 'a', 'b', 0), // gab = Gabri
+ TRUETYPE_TAG('g', 'a', 'c', 0), // gac = Mixed Great Andamanese
+ TRUETYPE_TAG('g', 'a', 'd', 0), // gad = Gaddang
+ TRUETYPE_TAG('g', 'a', 'e', 0), // gae = Guarequena
+ TRUETYPE_TAG('g', 'a', 'f', 0), // gaf = Gende
+ TRUETYPE_TAG('g', 'a', 'g', 0), // gag = Gagauz
+ TRUETYPE_TAG('g', 'a', 'h', 0), // gah = Alekano
+ TRUETYPE_TAG('g', 'a', 'i', 0), // gai = Borei
+ TRUETYPE_TAG('g', 'a', 'j', 0), // gaj = Gadsup
+ TRUETYPE_TAG('g', 'a', 'k', 0), // gak = Gamkonora
+ TRUETYPE_TAG('g', 'a', 'l', 0), // gal = Galoli
+ TRUETYPE_TAG('g', 'a', 'm', 0), // gam = Kandawo
+ TRUETYPE_TAG('g', 'a', 'n', 0), // gan = Gan Chinese
+ TRUETYPE_TAG('g', 'a', 'o', 0), // gao = Gants
+ TRUETYPE_TAG('g', 'a', 'p', 0), // gap = Gal
+ TRUETYPE_TAG('g', 'a', 'q', 0), // gaq = Gata'
+ TRUETYPE_TAG('g', 'a', 'r', 0), // gar = Galeya
+ TRUETYPE_TAG('g', 'a', 's', 0), // gas = Adiwasi Garasia
+ TRUETYPE_TAG('g', 'a', 't', 0), // gat = Kenati
+ TRUETYPE_TAG('g', 'a', 'u', 0), // gau = Mudhili Gadaba
+ TRUETYPE_TAG('g', 'a', 'v', 0), // gav = Gabutamon
+ TRUETYPE_TAG('g', 'a', 'w', 0), // gaw = Nobonob
+ TRUETYPE_TAG('g', 'a', 'x', 0), // gax = Borana-Arsi-Guji Oromo
+ TRUETYPE_TAG('g', 'a', 'y', 0), // gay = Gayo
+ TRUETYPE_TAG('g', 'a', 'z', 0), // gaz = West Central Oromo
+ TRUETYPE_TAG('g', 'b', 'a', 0), // gba = Gbaya (Central African Republic)
+ TRUETYPE_TAG('g', 'b', 'b', 0), // gbb = Kaytetye
+ TRUETYPE_TAG('g', 'b', 'c', 0), // gbc = Garawa
+ TRUETYPE_TAG('g', 'b', 'd', 0), // gbd = Karadjeri
+ TRUETYPE_TAG('g', 'b', 'e', 0), // gbe = Niksek
+ TRUETYPE_TAG('g', 'b', 'f', 0), // gbf = Gaikundi
+ TRUETYPE_TAG('g', 'b', 'g', 0), // gbg = Gbanziri
+ TRUETYPE_TAG('g', 'b', 'h', 0), // gbh = Defi Gbe
+ TRUETYPE_TAG('g', 'b', 'i', 0), // gbi = Galela
+ TRUETYPE_TAG('g', 'b', 'j', 0), // gbj = Bodo Gadaba
+ TRUETYPE_TAG('g', 'b', 'k', 0), // gbk = Gaddi
+ TRUETYPE_TAG('g', 'b', 'l', 0), // gbl = Gamit
+ TRUETYPE_TAG('g', 'b', 'm', 0), // gbm = Garhwali
+ TRUETYPE_TAG('g', 'b', 'n', 0), // gbn = Mo'da
+ TRUETYPE_TAG('g', 'b', 'o', 0), // gbo = Northern Grebo
+ TRUETYPE_TAG('g', 'b', 'p', 0), // gbp = Gbaya-Bossangoa
+ TRUETYPE_TAG('g', 'b', 'q', 0), // gbq = Gbaya-Bozoum
+ TRUETYPE_TAG('g', 'b', 'r', 0), // gbr = Gbagyi
+ TRUETYPE_TAG('g', 'b', 's', 0), // gbs = Gbesi Gbe
+ TRUETYPE_TAG('g', 'b', 'u', 0), // gbu = Gagadu
+ TRUETYPE_TAG('g', 'b', 'v', 0), // gbv = Gbanu
+ TRUETYPE_TAG('g', 'b', 'x', 0), // gbx = Eastern Xwla Gbe
+ TRUETYPE_TAG('g', 'b', 'y', 0), // gby = Gbari
+ TRUETYPE_TAG('g', 'b', 'z', 0), // gbz = Zoroastrian Dari
+ TRUETYPE_TAG('g', 'c', 'c', 0), // gcc = Mali
+ TRUETYPE_TAG('g', 'c', 'd', 0), // gcd = Ganggalida
+ TRUETYPE_TAG('g', 'c', 'e', 0), // gce = Galice
+ TRUETYPE_TAG('g', 'c', 'f', 0), // gcf = Guadeloupean Creole French
+ TRUETYPE_TAG('g', 'c', 'l', 0), // gcl = Grenadian Creole English
+ TRUETYPE_TAG('g', 'c', 'n', 0), // gcn = Gaina
+ TRUETYPE_TAG('g', 'c', 'r', 0), // gcr = Guianese Creole French
+ TRUETYPE_TAG('g', 'c', 't', 0), // gct = Colonia Tovar German
+ TRUETYPE_TAG('g', 'd', 'a', 0), // gda = Gade Lohar
+ TRUETYPE_TAG('g', 'd', 'b', 0), // gdb = Pottangi Ollar Gadaba
+ TRUETYPE_TAG('g', 'd', 'c', 0), // gdc = Gugu Badhun
+ TRUETYPE_TAG('g', 'd', 'd', 0), // gdd = Gedaged
+ TRUETYPE_TAG('g', 'd', 'e', 0), // gde = Gude
+ TRUETYPE_TAG('g', 'd', 'f', 0), // gdf = Guduf-Gava
+ TRUETYPE_TAG('g', 'd', 'g', 0), // gdg = Ga'dang
+ TRUETYPE_TAG('g', 'd', 'h', 0), // gdh = Gadjerawang
+ TRUETYPE_TAG('g', 'd', 'i', 0), // gdi = Gundi
+ TRUETYPE_TAG('g', 'd', 'j', 0), // gdj = Gurdjar
+ TRUETYPE_TAG('g', 'd', 'k', 0), // gdk = Gadang
+ TRUETYPE_TAG('g', 'd', 'l', 0), // gdl = Dirasha
+ TRUETYPE_TAG('g', 'd', 'm', 0), // gdm = Laal
+ TRUETYPE_TAG('g', 'd', 'n', 0), // gdn = Umanakaina
+ TRUETYPE_TAG('g', 'd', 'o', 0), // gdo = Ghodoberi
+ TRUETYPE_TAG('g', 'd', 'q', 0), // gdq = Mehri
+ TRUETYPE_TAG('g', 'd', 'r', 0), // gdr = Wipi
+ TRUETYPE_TAG('g', 'd', 'u', 0), // gdu = Gudu
+ TRUETYPE_TAG('g', 'd', 'x', 0), // gdx = Godwari
+ TRUETYPE_TAG('g', 'e', 'a', 0), // gea = Geruma
+ TRUETYPE_TAG('g', 'e', 'b', 0), // geb = Kire
+ TRUETYPE_TAG('g', 'e', 'c', 0), // gec = Gboloo Grebo
+ TRUETYPE_TAG('g', 'e', 'd', 0), // ged = Gade
+ TRUETYPE_TAG('g', 'e', 'g', 0), // geg = Gengle
+ TRUETYPE_TAG('g', 'e', 'h', 0), // geh = Hutterite German
+ TRUETYPE_TAG('g', 'e', 'i', 0), // gei = Gebe
+ TRUETYPE_TAG('g', 'e', 'j', 0), // gej = Gen
+ TRUETYPE_TAG('g', 'e', 'k', 0), // gek = Yiwom
+ TRUETYPE_TAG('g', 'e', 'l', 0), // gel = ut-Ma'in
+ TRUETYPE_TAG('g', 'e', 'm', 0), // gem = Germanic languages
+ TRUETYPE_TAG('g', 'e', 'q', 0), // geq = Geme
+ TRUETYPE_TAG('g', 'e', 's', 0), // ges = Geser-Gorom
+ TRUETYPE_TAG('g', 'e', 'w', 0), // gew = Gera
+ TRUETYPE_TAG('g', 'e', 'x', 0), // gex = Garre
+ TRUETYPE_TAG('g', 'e', 'y', 0), // gey = Enya
+ TRUETYPE_TAG('g', 'e', 'z', 0), // gez = Geez
+ TRUETYPE_TAG('g', 'f', 'k', 0), // gfk = Patpatar
+ TRUETYPE_TAG('g', 'f', 't', 0), // gft = Gafat
+ TRUETYPE_TAG('g', 'g', 'a', 0), // gga = Gao
+ TRUETYPE_TAG('g', 'g', 'b', 0), // ggb = Gbii
+ TRUETYPE_TAG('g', 'g', 'd', 0), // ggd = Gugadj
+ TRUETYPE_TAG('g', 'g', 'e', 0), // gge = Guragone
+ TRUETYPE_TAG('g', 'g', 'g', 0), // ggg = Gurgula
+ TRUETYPE_TAG('g', 'g', 'k', 0), // ggk = Kungarakany
+ TRUETYPE_TAG('g', 'g', 'l', 0), // ggl = Ganglau
+ TRUETYPE_TAG('g', 'g', 'n', 0), // ggn = Eastern Gurung
+ TRUETYPE_TAG('g', 'g', 'o', 0), // ggo = Southern Gondi
+ TRUETYPE_TAG('g', 'g', 'r', 0), // ggr = Aghu Tharnggalu
+ TRUETYPE_TAG('g', 'g', 't', 0), // ggt = Gitua
+ TRUETYPE_TAG('g', 'g', 'u', 0), // ggu = Gagu
+ TRUETYPE_TAG('g', 'g', 'w', 0), // ggw = Gogodala
+ TRUETYPE_TAG('g', 'h', 'a', 0), // gha = Ghadamès
+ TRUETYPE_TAG('g', 'h', 'c', 0), // ghc = Hiberno-Scottish Gaelic
+ TRUETYPE_TAG('g', 'h', 'e', 0), // ghe = Southern Ghale
+ TRUETYPE_TAG('g', 'h', 'h', 0), // ghh = Northern Ghale
+ TRUETYPE_TAG('g', 'h', 'k', 0), // ghk = Geko Karen
+ TRUETYPE_TAG('g', 'h', 'l', 0), // ghl = Ghulfan
+ TRUETYPE_TAG('g', 'h', 'n', 0), // ghn = Ghanongga
+ TRUETYPE_TAG('g', 'h', 'o', 0), // gho = Ghomara
+ TRUETYPE_TAG('g', 'h', 'r', 0), // ghr = Ghera
+ TRUETYPE_TAG('g', 'h', 's', 0), // ghs = Guhu-Samane
+ TRUETYPE_TAG('g', 'h', 't', 0), // ght = Kutang Ghale
+ TRUETYPE_TAG('g', 'i', 'a', 0), // gia = Kitja
+ TRUETYPE_TAG('g', 'i', 'b', 0), // gib = Gibanawa
+ TRUETYPE_TAG('g', 'i', 'c', 0), // gic = Gail
+ TRUETYPE_TAG('g', 'i', 'd', 0), // gid = Gidar
+ TRUETYPE_TAG('g', 'i', 'g', 0), // gig = Goaria
+ TRUETYPE_TAG('g', 'i', 'l', 0), // gil = Gilbertese
+ TRUETYPE_TAG('g', 'i', 'm', 0), // gim = Gimi (Eastern Highlands)
+ TRUETYPE_TAG('g', 'i', 'n', 0), // gin = Hinukh
+ TRUETYPE_TAG('g', 'i', 'o', 0), // gio = Gelao
+ TRUETYPE_TAG('g', 'i', 'p', 0), // gip = Gimi (West New Britain)
+ TRUETYPE_TAG('g', 'i', 'q', 0), // giq = Green Gelao
+ TRUETYPE_TAG('g', 'i', 'r', 0), // gir = Red Gelao
+ TRUETYPE_TAG('g', 'i', 's', 0), // gis = North Giziga
+ TRUETYPE_TAG('g', 'i', 't', 0), // git = Gitxsan
+ TRUETYPE_TAG('g', 'i', 'w', 0), // giw = White Gelao
+ TRUETYPE_TAG('g', 'i', 'x', 0), // gix = Gilima
+ TRUETYPE_TAG('g', 'i', 'y', 0), // giy = Giyug
+ TRUETYPE_TAG('g', 'i', 'z', 0), // giz = South Giziga
+ TRUETYPE_TAG('g', 'j', 'i', 0), // gji = Geji
+ TRUETYPE_TAG('g', 'j', 'k', 0), // gjk = Kachi Koli
+ TRUETYPE_TAG('g', 'j', 'n', 0), // gjn = Gonja
+ TRUETYPE_TAG('g', 'j', 'u', 0), // gju = Gujari
+ TRUETYPE_TAG('g', 'k', 'a', 0), // gka = Guya
+ TRUETYPE_TAG('g', 'k', 'e', 0), // gke = Ndai
+ TRUETYPE_TAG('g', 'k', 'n', 0), // gkn = Gokana
+ TRUETYPE_TAG('g', 'k', 'p', 0), // gkp = Guinea Kpelle
+ TRUETYPE_TAG('g', 'l', 'c', 0), // glc = Bon Gula
+ TRUETYPE_TAG('g', 'l', 'd', 0), // gld = Nanai
+ TRUETYPE_TAG('g', 'l', 'h', 0), // glh = Northwest Pashayi
+ TRUETYPE_TAG('g', 'l', 'i', 0), // gli = Guliguli
+ TRUETYPE_TAG('g', 'l', 'j', 0), // glj = Gula Iro
+ TRUETYPE_TAG('g', 'l', 'k', 0), // glk = Gilaki
+ TRUETYPE_TAG('g', 'l', 'o', 0), // glo = Galambu
+ TRUETYPE_TAG('g', 'l', 'r', 0), // glr = Glaro-Twabo
+ TRUETYPE_TAG('g', 'l', 'u', 0), // glu = Gula (Chad)
+ TRUETYPE_TAG('g', 'l', 'w', 0), // glw = Glavda
+ TRUETYPE_TAG('g', 'l', 'y', 0), // gly = Gule
+ TRUETYPE_TAG('g', 'm', 'a', 0), // gma = Gambera
+ TRUETYPE_TAG('g', 'm', 'b', 0), // gmb = Gula'alaa
+ TRUETYPE_TAG('g', 'm', 'd', 0), // gmd = Mághdì
+ TRUETYPE_TAG('g', 'm', 'e', 0), // gme = East Germanic languages
+ TRUETYPE_TAG('g', 'm', 'h', 0), // gmh = Middle High German (ca. 1050-1500)
+ TRUETYPE_TAG('g', 'm', 'l', 0), // gml = Middle Low German
+ TRUETYPE_TAG('g', 'm', 'm', 0), // gmm = Gbaya-Mbodomo
+ TRUETYPE_TAG('g', 'm', 'n', 0), // gmn = Gimnime
+ TRUETYPE_TAG('g', 'm', 'q', 0), // gmq = North Germanic languages
+ TRUETYPE_TAG('g', 'm', 'u', 0), // gmu = Gumalu
+ TRUETYPE_TAG('g', 'm', 'v', 0), // gmv = Gamo
+ TRUETYPE_TAG('g', 'm', 'w', 0), // gmw = West Germanic languages
+ TRUETYPE_TAG('g', 'm', 'x', 0), // gmx = Magoma
+ TRUETYPE_TAG('g', 'm', 'y', 0), // gmy = Mycenaean Greek
+ TRUETYPE_TAG('g', 'n', 'a', 0), // gna = Kaansa
+ TRUETYPE_TAG('g', 'n', 'b', 0), // gnb = Gangte
+ TRUETYPE_TAG('g', 'n', 'c', 0), // gnc = Guanche
+ TRUETYPE_TAG('g', 'n', 'd', 0), // gnd = Zulgo-Gemzek
+ TRUETYPE_TAG('g', 'n', 'e', 0), // gne = Ganang
+ TRUETYPE_TAG('g', 'n', 'g', 0), // gng = Ngangam
+ TRUETYPE_TAG('g', 'n', 'h', 0), // gnh = Lere
+ TRUETYPE_TAG('g', 'n', 'i', 0), // gni = Gooniyandi
+ TRUETYPE_TAG('g', 'n', 'k', 0), // gnk = //Gana
+ TRUETYPE_TAG('g', 'n', 'l', 0), // gnl = Gangulu
+ TRUETYPE_TAG('g', 'n', 'm', 0), // gnm = Ginuman
+ TRUETYPE_TAG('g', 'n', 'n', 0), // gnn = Gumatj
+ TRUETYPE_TAG('g', 'n', 'o', 0), // gno = Northern Gondi
+ TRUETYPE_TAG('g', 'n', 'q', 0), // gnq = Gana
+ TRUETYPE_TAG('g', 'n', 'r', 0), // gnr = Gureng Gureng
+ TRUETYPE_TAG('g', 'n', 't', 0), // gnt = Guntai
+ TRUETYPE_TAG('g', 'n', 'u', 0), // gnu = Gnau
+ TRUETYPE_TAG('g', 'n', 'w', 0), // gnw = Western Bolivian Guaraní
+ TRUETYPE_TAG('g', 'n', 'z', 0), // gnz = Ganzi
+ TRUETYPE_TAG('g', 'o', 'a', 0), // goa = Guro
+ TRUETYPE_TAG('g', 'o', 'b', 0), // gob = Playero
+ TRUETYPE_TAG('g', 'o', 'c', 0), // goc = Gorakor
+ TRUETYPE_TAG('g', 'o', 'd', 0), // god = Godié
+ TRUETYPE_TAG('g', 'o', 'e', 0), // goe = Gongduk
+ TRUETYPE_TAG('g', 'o', 'f', 0), // gof = Gofa
+ TRUETYPE_TAG('g', 'o', 'g', 0), // gog = Gogo
+ TRUETYPE_TAG('g', 'o', 'h', 0), // goh = Old High German (ca. 750-1050)
+ TRUETYPE_TAG('g', 'o', 'i', 0), // goi = Gobasi
+ TRUETYPE_TAG('g', 'o', 'j', 0), // goj = Gowlan
+ TRUETYPE_TAG('g', 'o', 'k', 0), // gok = Gowli
+ TRUETYPE_TAG('g', 'o', 'l', 0), // gol = Gola
+ TRUETYPE_TAG('g', 'o', 'm', 0), // gom = Goan Konkani
+ TRUETYPE_TAG('g', 'o', 'n', 0), // gon = Gondi
+ TRUETYPE_TAG('g', 'o', 'o', 0), // goo = Gone Dau
+ TRUETYPE_TAG('g', 'o', 'p', 0), // gop = Yeretuar
+ TRUETYPE_TAG('g', 'o', 'q', 0), // goq = Gorap
+ TRUETYPE_TAG('g', 'o', 'r', 0), // gor = Gorontalo
+ TRUETYPE_TAG('g', 'o', 's', 0), // gos = Gronings
+ TRUETYPE_TAG('g', 'o', 't', 0), // got = Gothic
+ TRUETYPE_TAG('g', 'o', 'u', 0), // gou = Gavar
+ TRUETYPE_TAG('g', 'o', 'w', 0), // gow = Gorowa
+ TRUETYPE_TAG('g', 'o', 'x', 0), // gox = Gobu
+ TRUETYPE_TAG('g', 'o', 'y', 0), // goy = Goundo
+ TRUETYPE_TAG('g', 'o', 'z', 0), // goz = Gozarkhani
+ TRUETYPE_TAG('g', 'p', 'a', 0), // gpa = Gupa-Abawa
+ TRUETYPE_TAG('g', 'p', 'n', 0), // gpn = Taiap
+ TRUETYPE_TAG('g', 'q', 'a', 0), // gqa = Ga'anda
+ TRUETYPE_TAG('g', 'q', 'i', 0), // gqi = Guiqiong
+ TRUETYPE_TAG('g', 'q', 'n', 0), // gqn = Guana (Brazil)
+ TRUETYPE_TAG('g', 'q', 'r', 0), // gqr = Gor
+ TRUETYPE_TAG('g', 'r', 'a', 0), // gra = Rajput Garasia
+ TRUETYPE_TAG('g', 'r', 'b', 0), // grb = Grebo
+ TRUETYPE_TAG('g', 'r', 'c', 0), // grc = Ancient Greek (to 1453)
+ TRUETYPE_TAG('g', 'r', 'd', 0), // grd = Guruntum-Mbaaru
+ TRUETYPE_TAG('g', 'r', 'g', 0), // grg = Madi
+ TRUETYPE_TAG('g', 'r', 'h', 0), // grh = Gbiri-Niragu
+ TRUETYPE_TAG('g', 'r', 'i', 0), // gri = Ghari
+ TRUETYPE_TAG('g', 'r', 'j', 0), // grj = Southern Grebo
+ TRUETYPE_TAG('g', 'r', 'k', 0), // grk = Greek languages
+ TRUETYPE_TAG('g', 'r', 'm', 0), // grm = Kota Marudu Talantang
+ TRUETYPE_TAG('g', 'r', 'o', 0), // gro = Groma
+ TRUETYPE_TAG('g', 'r', 'q', 0), // grq = Gorovu
+ TRUETYPE_TAG('g', 'r', 'r', 0), // grr = Taznatit
+ TRUETYPE_TAG('g', 'r', 's', 0), // grs = Gresi
+ TRUETYPE_TAG('g', 'r', 't', 0), // grt = Garo
+ TRUETYPE_TAG('g', 'r', 'u', 0), // gru = Kistane
+ TRUETYPE_TAG('g', 'r', 'v', 0), // grv = Central Grebo
+ TRUETYPE_TAG('g', 'r', 'w', 0), // grw = Gweda
+ TRUETYPE_TAG('g', 'r', 'x', 0), // grx = Guriaso
+ TRUETYPE_TAG('g', 'r', 'y', 0), // gry = Barclayville Grebo
+ TRUETYPE_TAG('g', 'r', 'z', 0), // grz = Guramalum
+ TRUETYPE_TAG('g', 's', 'e', 0), // gse = Ghanaian Sign Language
+ TRUETYPE_TAG('g', 's', 'g', 0), // gsg = German Sign Language
+ TRUETYPE_TAG('g', 's', 'l', 0), // gsl = Gusilay
+ TRUETYPE_TAG('g', 's', 'm', 0), // gsm = Guatemalan Sign Language
+ TRUETYPE_TAG('g', 's', 'n', 0), // gsn = Gusan
+ TRUETYPE_TAG('g', 's', 'o', 0), // gso = Southwest Gbaya
+ TRUETYPE_TAG('g', 's', 'p', 0), // gsp = Wasembo
+ TRUETYPE_TAG('g', 's', 's', 0), // gss = Greek Sign Language
+ TRUETYPE_TAG('g', 's', 'w', 0), // gsw = Swiss German
+ TRUETYPE_TAG('g', 't', 'a', 0), // gta = Guató
+ TRUETYPE_TAG('g', 't', 'i', 0), // gti = Gbati-ri
+ TRUETYPE_TAG('g', 'u', 'a', 0), // gua = Shiki
+ TRUETYPE_TAG('g', 'u', 'b', 0), // gub = Guajajára
+ TRUETYPE_TAG('g', 'u', 'c', 0), // guc = Wayuu
+ TRUETYPE_TAG('g', 'u', 'd', 0), // gud = Yocoboué Dida
+ TRUETYPE_TAG('g', 'u', 'e', 0), // gue = Gurinji
+ TRUETYPE_TAG('g', 'u', 'f', 0), // guf = Gupapuyngu
+ TRUETYPE_TAG('g', 'u', 'g', 0), // gug = Paraguayan Guaraní
+ TRUETYPE_TAG('g', 'u', 'h', 0), // guh = Guahibo
+ TRUETYPE_TAG('g', 'u', 'i', 0), // gui = Eastern Bolivian Guaraní
+ TRUETYPE_TAG('g', 'u', 'k', 0), // guk = Gumuz
+ TRUETYPE_TAG('g', 'u', 'l', 0), // gul = Sea Island Creole English
+ TRUETYPE_TAG('g', 'u', 'm', 0), // gum = Guambiano
+ TRUETYPE_TAG('g', 'u', 'n', 0), // gun = Mbyá Guaraní
+ TRUETYPE_TAG('g', 'u', 'o', 0), // guo = Guayabero
+ TRUETYPE_TAG('g', 'u', 'p', 0), // gup = Gunwinggu
+ TRUETYPE_TAG('g', 'u', 'q', 0), // guq = Aché
+ TRUETYPE_TAG('g', 'u', 'r', 0), // gur = Farefare
+ TRUETYPE_TAG('g', 'u', 's', 0), // gus = Guinean Sign Language
+ TRUETYPE_TAG('g', 'u', 't', 0), // gut = Maléku Jaíka
+ TRUETYPE_TAG('g', 'u', 'u', 0), // guu = Yanomamö
+ TRUETYPE_TAG('g', 'u', 'v', 0), // guv = Gey
+ TRUETYPE_TAG('g', 'u', 'w', 0), // guw = Gun
+ TRUETYPE_TAG('g', 'u', 'x', 0), // gux = Gourmanchéma
+ TRUETYPE_TAG('g', 'u', 'z', 0), // guz = Gusii
+ TRUETYPE_TAG('g', 'v', 'a', 0), // gva = Guana (Paraguay)
+ TRUETYPE_TAG('g', 'v', 'c', 0), // gvc = Guanano
+ TRUETYPE_TAG('g', 'v', 'e', 0), // gve = Duwet
+ TRUETYPE_TAG('g', 'v', 'f', 0), // gvf = Golin
+ TRUETYPE_TAG('g', 'v', 'j', 0), // gvj = Guajá
+ TRUETYPE_TAG('g', 'v', 'l', 0), // gvl = Gulay
+ TRUETYPE_TAG('g', 'v', 'm', 0), // gvm = Gurmana
+ TRUETYPE_TAG('g', 'v', 'n', 0), // gvn = Kuku-Yalanji
+ TRUETYPE_TAG('g', 'v', 'o', 0), // gvo = Gavião Do Jiparaná
+ TRUETYPE_TAG('g', 'v', 'p', 0), // gvp = Pará Gavião
+ TRUETYPE_TAG('g', 'v', 'r', 0), // gvr = Western Gurung
+ TRUETYPE_TAG('g', 'v', 's', 0), // gvs = Gumawana
+ TRUETYPE_TAG('g', 'v', 'y', 0), // gvy = Guyani
+ TRUETYPE_TAG('g', 'w', 'a', 0), // gwa = Mbato
+ TRUETYPE_TAG('g', 'w', 'b', 0), // gwb = Gwa
+ TRUETYPE_TAG('g', 'w', 'c', 0), // gwc = Kalami
+ TRUETYPE_TAG('g', 'w', 'd', 0), // gwd = Gawwada
+ TRUETYPE_TAG('g', 'w', 'e', 0), // gwe = Gweno
+ TRUETYPE_TAG('g', 'w', 'f', 0), // gwf = Gowro
+ TRUETYPE_TAG('g', 'w', 'g', 0), // gwg = Moo
+ TRUETYPE_TAG('g', 'w', 'i', 0), // gwi = Gwichʼin
+ TRUETYPE_TAG('g', 'w', 'j', 0), // gwj = /Gwi
+ TRUETYPE_TAG('g', 'w', 'n', 0), // gwn = Gwandara
+ TRUETYPE_TAG('g', 'w', 'r', 0), // gwr = Gwere
+ TRUETYPE_TAG('g', 'w', 't', 0), // gwt = Gawar-Bati
+ TRUETYPE_TAG('g', 'w', 'u', 0), // gwu = Guwamu
+ TRUETYPE_TAG('g', 'w', 'w', 0), // gww = Kwini
+ TRUETYPE_TAG('g', 'w', 'x', 0), // gwx = Gua
+ TRUETYPE_TAG('g', 'x', 'x', 0), // gxx = Wè Southern
+ TRUETYPE_TAG('g', 'y', 'a', 0), // gya = Northwest Gbaya
+ TRUETYPE_TAG('g', 'y', 'b', 0), // gyb = Garus
+ TRUETYPE_TAG('g', 'y', 'd', 0), // gyd = Kayardild
+ TRUETYPE_TAG('g', 'y', 'e', 0), // gye = Gyem
+ TRUETYPE_TAG('g', 'y', 'f', 0), // gyf = Gungabula
+ TRUETYPE_TAG('g', 'y', 'g', 0), // gyg = Gbayi
+ TRUETYPE_TAG('g', 'y', 'i', 0), // gyi = Gyele
+ TRUETYPE_TAG('g', 'y', 'l', 0), // gyl = Gayil
+ TRUETYPE_TAG('g', 'y', 'm', 0), // gym = Ngäbere
+ TRUETYPE_TAG('g', 'y', 'n', 0), // gyn = Guyanese Creole English
+ TRUETYPE_TAG('g', 'y', 'r', 0), // gyr = Guarayu
+ TRUETYPE_TAG('g', 'y', 'y', 0), // gyy = Gunya
+ TRUETYPE_TAG('g', 'z', 'a', 0), // gza = Ganza
+ TRUETYPE_TAG('g', 'z', 'i', 0), // gzi = Gazi
+ TRUETYPE_TAG('g', 'z', 'n', 0), // gzn = Gane
+ TRUETYPE_TAG('h', 'a', 'a', 0), // haa = Han
+ TRUETYPE_TAG('h', 'a', 'b', 0), // hab = Hanoi Sign Language
+ TRUETYPE_TAG('h', 'a', 'c', 0), // hac = Gurani
+ TRUETYPE_TAG('h', 'a', 'd', 0), // had = Hatam
+ TRUETYPE_TAG('h', 'a', 'e', 0), // hae = Eastern Oromo
+ TRUETYPE_TAG('h', 'a', 'f', 0), // haf = Haiphong Sign Language
+ TRUETYPE_TAG('h', 'a', 'g', 0), // hag = Hanga
+ TRUETYPE_TAG('h', 'a', 'h', 0), // hah = Hahon
+ TRUETYPE_TAG('h', 'a', 'i', 0), // hai = Haida
+ TRUETYPE_TAG('h', 'a', 'j', 0), // haj = Hajong
+ TRUETYPE_TAG('h', 'a', 'k', 0), // hak = Hakka Chinese
+ TRUETYPE_TAG('h', 'a', 'l', 0), // hal = Halang
+ TRUETYPE_TAG('h', 'a', 'm', 0), // ham = Hewa
+ TRUETYPE_TAG('h', 'a', 'n', 0), // han = Hangaza
+ TRUETYPE_TAG('h', 'a', 'o', 0), // hao = Hakö
+ TRUETYPE_TAG('h', 'a', 'p', 0), // hap = Hupla
+ TRUETYPE_TAG('h', 'a', 'q', 0), // haq = Ha
+ TRUETYPE_TAG('h', 'a', 'r', 0), // har = Harari
+ TRUETYPE_TAG('h', 'a', 's', 0), // has = Haisla
+ TRUETYPE_TAG('h', 'a', 'v', 0), // hav = Havu
+ TRUETYPE_TAG('h', 'a', 'w', 0), // haw = Hawaiian
+ TRUETYPE_TAG('h', 'a', 'x', 0), // hax = Southern Haida
+ TRUETYPE_TAG('h', 'a', 'y', 0), // hay = Haya
+ TRUETYPE_TAG('h', 'a', 'z', 0), // haz = Hazaragi
+ TRUETYPE_TAG('h', 'b', 'a', 0), // hba = Hamba
+ TRUETYPE_TAG('h', 'b', 'b', 0), // hbb = Huba
+ TRUETYPE_TAG('h', 'b', 'n', 0), // hbn = Heiban
+ TRUETYPE_TAG('h', 'b', 'o', 0), // hbo = Ancient Hebrew
+ TRUETYPE_TAG('h', 'b', 'u', 0), // hbu = Habu
+ TRUETYPE_TAG('h', 'c', 'a', 0), // hca = Andaman Creole Hindi
+ TRUETYPE_TAG('h', 'c', 'h', 0), // hch = Huichol
+ TRUETYPE_TAG('h', 'd', 'n', 0), // hdn = Northern Haida
+ TRUETYPE_TAG('h', 'd', 's', 0), // hds = Honduras Sign Language
+ TRUETYPE_TAG('h', 'd', 'y', 0), // hdy = Hadiyya
+ TRUETYPE_TAG('h', 'e', 'a', 0), // hea = Northern Qiandong Miao
+ TRUETYPE_TAG('h', 'e', 'd', 0), // hed = Herdé
+ TRUETYPE_TAG('h', 'e', 'g', 0), // heg = Helong
+ TRUETYPE_TAG('h', 'e', 'h', 0), // heh = Hehe
+ TRUETYPE_TAG('h', 'e', 'i', 0), // hei = Heiltsuk
+ TRUETYPE_TAG('h', 'e', 'm', 0), // hem = Hemba
+ TRUETYPE_TAG('h', 'g', 'm', 0), // hgm = Hai//om
+ TRUETYPE_TAG('h', 'g', 'w', 0), // hgw = Haigwai
+ TRUETYPE_TAG('h', 'h', 'i', 0), // hhi = Hoia Hoia
+ TRUETYPE_TAG('h', 'h', 'r', 0), // hhr = Kerak
+ TRUETYPE_TAG('h', 'h', 'y', 0), // hhy = Hoyahoya
+ TRUETYPE_TAG('h', 'i', 'a', 0), // hia = Lamang
+ TRUETYPE_TAG('h', 'i', 'b', 0), // hib = Hibito
+ TRUETYPE_TAG('h', 'i', 'd', 0), // hid = Hidatsa
+ TRUETYPE_TAG('h', 'i', 'f', 0), // hif = Fiji Hindi
+ TRUETYPE_TAG('h', 'i', 'g', 0), // hig = Kamwe
+ TRUETYPE_TAG('h', 'i', 'h', 0), // hih = Pamosu
+ TRUETYPE_TAG('h', 'i', 'i', 0), // hii = Hinduri
+ TRUETYPE_TAG('h', 'i', 'j', 0), // hij = Hijuk
+ TRUETYPE_TAG('h', 'i', 'k', 0), // hik = Seit-Kaitetu
+ TRUETYPE_TAG('h', 'i', 'l', 0), // hil = Hiligaynon
+ TRUETYPE_TAG('h', 'i', 'm', 0), // him = Himachali languages
+ TRUETYPE_TAG('h', 'i', 'o', 0), // hio = Tsoa
+ TRUETYPE_TAG('h', 'i', 'r', 0), // hir = Himarimã
+ TRUETYPE_TAG('h', 'i', 't', 0), // hit = Hittite
+ TRUETYPE_TAG('h', 'i', 'w', 0), // hiw = Hiw
+ TRUETYPE_TAG('h', 'i', 'x', 0), // hix = Hixkaryána
+ TRUETYPE_TAG('h', 'j', 'i', 0), // hji = Haji
+ TRUETYPE_TAG('h', 'k', 'a', 0), // hka = Kahe
+ TRUETYPE_TAG('h', 'k', 'e', 0), // hke = Hunde
+ TRUETYPE_TAG('h', 'k', 'k', 0), // hkk = Hunjara-Kaina Ke
+ TRUETYPE_TAG('h', 'k', 's', 0), // hks = Hong Kong Sign Language
+ TRUETYPE_TAG('h', 'l', 'a', 0), // hla = Halia
+ TRUETYPE_TAG('h', 'l', 'b', 0), // hlb = Halbi
+ TRUETYPE_TAG('h', 'l', 'd', 0), // hld = Halang Doan
+ TRUETYPE_TAG('h', 'l', 'e', 0), // hle = Hlersu
+ TRUETYPE_TAG('h', 'l', 't', 0), // hlt = Nga La
+ TRUETYPE_TAG('h', 'l', 'u', 0), // hlu = Hieroglyphic Luwian
+ TRUETYPE_TAG('h', 'm', 'a', 0), // hma = Southern Mashan Hmong
+ TRUETYPE_TAG('h', 'm', 'b', 0), // hmb = Humburi Senni Songhay
+ TRUETYPE_TAG('h', 'm', 'c', 0), // hmc = Central Huishui Hmong
+ TRUETYPE_TAG('h', 'm', 'd', 0), // hmd = Large Flowery Miao
+ TRUETYPE_TAG('h', 'm', 'e', 0), // hme = Eastern Huishui Hmong
+ TRUETYPE_TAG('h', 'm', 'f', 0), // hmf = Hmong Don
+ TRUETYPE_TAG('h', 'm', 'g', 0), // hmg = Southwestern Guiyang Hmong
+ TRUETYPE_TAG('h', 'm', 'h', 0), // hmh = Southwestern Huishui Hmong
+ TRUETYPE_TAG('h', 'm', 'i', 0), // hmi = Northern Huishui Hmong
+ TRUETYPE_TAG('h', 'm', 'j', 0), // hmj = Ge
+ TRUETYPE_TAG('h', 'm', 'k', 0), // hmk = Maek
+ TRUETYPE_TAG('h', 'm', 'l', 0), // hml = Luopohe Hmong
+ TRUETYPE_TAG('h', 'm', 'm', 0), // hmm = Central Mashan Hmong
+ TRUETYPE_TAG('h', 'm', 'n', 0), // hmn = Hmong
+ TRUETYPE_TAG('h', 'm', 'p', 0), // hmp = Northern Mashan Hmong
+ TRUETYPE_TAG('h', 'm', 'q', 0), // hmq = Eastern Qiandong Miao
+ TRUETYPE_TAG('h', 'm', 'r', 0), // hmr = Hmar
+ TRUETYPE_TAG('h', 'm', 's', 0), // hms = Southern Qiandong Miao
+ TRUETYPE_TAG('h', 'm', 't', 0), // hmt = Hamtai
+ TRUETYPE_TAG('h', 'm', 'u', 0), // hmu = Hamap
+ TRUETYPE_TAG('h', 'm', 'v', 0), // hmv = Hmong Dô
+ TRUETYPE_TAG('h', 'm', 'w', 0), // hmw = Western Mashan Hmong
+ TRUETYPE_TAG('h', 'm', 'x', 0), // hmx = Hmong-Mien languages
+ TRUETYPE_TAG('h', 'm', 'y', 0), // hmy = Southern Guiyang Hmong
+ TRUETYPE_TAG('h', 'm', 'z', 0), // hmz = Hmong Shua
+ TRUETYPE_TAG('h', 'n', 'a', 0), // hna = Mina (Cameroon)
+ TRUETYPE_TAG('h', 'n', 'd', 0), // hnd = Southern Hindko
+ TRUETYPE_TAG('h', 'n', 'e', 0), // hne = Chhattisgarhi
+ TRUETYPE_TAG('h', 'n', 'h', 0), // hnh = //Ani
+ TRUETYPE_TAG('h', 'n', 'i', 0), // hni = Hani
+ TRUETYPE_TAG('h', 'n', 'j', 0), // hnj = Hmong Njua
+ TRUETYPE_TAG('h', 'n', 'n', 0), // hnn = Hanunoo
+ TRUETYPE_TAG('h', 'n', 'o', 0), // hno = Northern Hindko
+ TRUETYPE_TAG('h', 'n', 's', 0), // hns = Caribbean Hindustani
+ TRUETYPE_TAG('h', 'n', 'u', 0), // hnu = Hung
+ TRUETYPE_TAG('h', 'o', 'a', 0), // hoa = Hoava
+ TRUETYPE_TAG('h', 'o', 'b', 0), // hob = Mari (Madang Province)
+ TRUETYPE_TAG('h', 'o', 'c', 0), // hoc = Ho
+ TRUETYPE_TAG('h', 'o', 'd', 0), // hod = Holma
+ TRUETYPE_TAG('h', 'o', 'e', 0), // hoe = Horom
+ TRUETYPE_TAG('h', 'o', 'h', 0), // hoh = Hobyót
+ TRUETYPE_TAG('h', 'o', 'i', 0), // hoi = Holikachuk
+ TRUETYPE_TAG('h', 'o', 'j', 0), // hoj = Hadothi
+ TRUETYPE_TAG('h', 'o', 'k', 0), // hok = Hokan languages
+ TRUETYPE_TAG('h', 'o', 'l', 0), // hol = Holu
+ TRUETYPE_TAG('h', 'o', 'm', 0), // hom = Homa
+ TRUETYPE_TAG('h', 'o', 'o', 0), // hoo = Holoholo
+ TRUETYPE_TAG('h', 'o', 'p', 0), // hop = Hopi
+ TRUETYPE_TAG('h', 'o', 'r', 0), // hor = Horo
+ TRUETYPE_TAG('h', 'o', 's', 0), // hos = Ho Chi Minh City Sign Language
+ TRUETYPE_TAG('h', 'o', 't', 0), // hot = Hote
+ TRUETYPE_TAG('h', 'o', 'v', 0), // hov = Hovongan
+ TRUETYPE_TAG('h', 'o', 'w', 0), // how = Honi
+ TRUETYPE_TAG('h', 'o', 'y', 0), // hoy = Holiya
+ TRUETYPE_TAG('h', 'o', 'z', 0), // hoz = Hozo
+ TRUETYPE_TAG('h', 'p', 'o', 0), // hpo = Hpon
+ TRUETYPE_TAG('h', 'p', 's', 0), // hps = Hawai'i Pidgin Sign Language
+ TRUETYPE_TAG('h', 'r', 'a', 0), // hra = Hrangkhol
+ TRUETYPE_TAG('h', 'r', 'e', 0), // hre = Hre
+ TRUETYPE_TAG('h', 'r', 'k', 0), // hrk = Haruku
+ TRUETYPE_TAG('h', 'r', 'm', 0), // hrm = Horned Miao
+ TRUETYPE_TAG('h', 'r', 'o', 0), // hro = Haroi
+ TRUETYPE_TAG('h', 'r', 'r', 0), // hrr = Horuru
+ TRUETYPE_TAG('h', 'r', 't', 0), // hrt = Hértevin
+ TRUETYPE_TAG('h', 'r', 'u', 0), // hru = Hruso
+ TRUETYPE_TAG('h', 'r', 'x', 0), // hrx = Hunsrik
+ TRUETYPE_TAG('h', 'r', 'z', 0), // hrz = Harzani
+ TRUETYPE_TAG('h', 's', 'b', 0), // hsb = Upper Sorbian
+ TRUETYPE_TAG('h', 's', 'h', 0), // hsh = Hungarian Sign Language
+ TRUETYPE_TAG('h', 's', 'l', 0), // hsl = Hausa Sign Language
+ TRUETYPE_TAG('h', 's', 'n', 0), // hsn = Xiang Chinese
+ TRUETYPE_TAG('h', 's', 's', 0), // hss = Harsusi
+ TRUETYPE_TAG('h', 't', 'i', 0), // hti = Hoti
+ TRUETYPE_TAG('h', 't', 'o', 0), // hto = Minica Huitoto
+ TRUETYPE_TAG('h', 't', 's', 0), // hts = Hadza
+ TRUETYPE_TAG('h', 't', 'u', 0), // htu = Hitu
+ TRUETYPE_TAG('h', 't', 'x', 0), // htx = Middle Hittite
+ TRUETYPE_TAG('h', 'u', 'b', 0), // hub = Huambisa
+ TRUETYPE_TAG('h', 'u', 'c', 0), // huc = =/Hua
+ TRUETYPE_TAG('h', 'u', 'd', 0), // hud = Huaulu
+ TRUETYPE_TAG('h', 'u', 'e', 0), // hue = San Francisco Del Mar Huave
+ TRUETYPE_TAG('h', 'u', 'f', 0), // huf = Humene
+ TRUETYPE_TAG('h', 'u', 'g', 0), // hug = Huachipaeri
+ TRUETYPE_TAG('h', 'u', 'h', 0), // huh = Huilliche
+ TRUETYPE_TAG('h', 'u', 'i', 0), // hui = Huli
+ TRUETYPE_TAG('h', 'u', 'j', 0), // huj = Northern Guiyang Hmong
+ TRUETYPE_TAG('h', 'u', 'k', 0), // huk = Hulung
+ TRUETYPE_TAG('h', 'u', 'l', 0), // hul = Hula
+ TRUETYPE_TAG('h', 'u', 'm', 0), // hum = Hungana
+ TRUETYPE_TAG('h', 'u', 'o', 0), // huo = Hu
+ TRUETYPE_TAG('h', 'u', 'p', 0), // hup = Hupa
+ TRUETYPE_TAG('h', 'u', 'q', 0), // huq = Tsat
+ TRUETYPE_TAG('h', 'u', 'r', 0), // hur = Halkomelem
+ TRUETYPE_TAG('h', 'u', 's', 0), // hus = Huastec
+ TRUETYPE_TAG('h', 'u', 't', 0), // hut = Humla
+ TRUETYPE_TAG('h', 'u', 'u', 0), // huu = Murui Huitoto
+ TRUETYPE_TAG('h', 'u', 'v', 0), // huv = San Mateo Del Mar Huave
+ TRUETYPE_TAG('h', 'u', 'w', 0), // huw = Hukumina
+ TRUETYPE_TAG('h', 'u', 'x', 0), // hux = Nüpode Huitoto
+ TRUETYPE_TAG('h', 'u', 'y', 0), // huy = Hulaulá
+ TRUETYPE_TAG('h', 'u', 'z', 0), // huz = Hunzib
+ TRUETYPE_TAG('h', 'v', 'c', 0), // hvc = Haitian Vodoun Culture Language
+ TRUETYPE_TAG('h', 'v', 'e', 0), // hve = San Dionisio Del Mar Huave
+ TRUETYPE_TAG('h', 'v', 'k', 0), // hvk = Haveke
+ TRUETYPE_TAG('h', 'v', 'n', 0), // hvn = Sabu
+ TRUETYPE_TAG('h', 'v', 'v', 0), // hvv = Santa María Del Mar Huave
+ TRUETYPE_TAG('h', 'w', 'a', 0), // hwa = Wané
+ TRUETYPE_TAG('h', 'w', 'c', 0), // hwc = Hawai'i Creole English
+ TRUETYPE_TAG('h', 'w', 'o', 0), // hwo = Hwana
+ TRUETYPE_TAG('h', 'y', 'a', 0), // hya = Hya
+ TRUETYPE_TAG('h', 'y', 'x', 0), // hyx = Armenian (family)
+ TRUETYPE_TAG('i', 'a', 'i', 0), // iai = Iaai
+ TRUETYPE_TAG('i', 'a', 'n', 0), // ian = Iatmul
+ TRUETYPE_TAG('i', 'a', 'p', 0), // iap = Iapama
+ TRUETYPE_TAG('i', 'a', 'r', 0), // iar = Purari
+ TRUETYPE_TAG('i', 'b', 'a', 0), // iba = Iban
+ TRUETYPE_TAG('i', 'b', 'b', 0), // ibb = Ibibio
+ TRUETYPE_TAG('i', 'b', 'd', 0), // ibd = Iwaidja
+ TRUETYPE_TAG('i', 'b', 'e', 0), // ibe = Akpes
+ TRUETYPE_TAG('i', 'b', 'g', 0), // ibg = Ibanag
+ TRUETYPE_TAG('i', 'b', 'i', 0), // ibi = Ibilo
+ TRUETYPE_TAG('i', 'b', 'l', 0), // ibl = Ibaloi
+ TRUETYPE_TAG('i', 'b', 'm', 0), // ibm = Agoi
+ TRUETYPE_TAG('i', 'b', 'n', 0), // ibn = Ibino
+ TRUETYPE_TAG('i', 'b', 'r', 0), // ibr = Ibuoro
+ TRUETYPE_TAG('i', 'b', 'u', 0), // ibu = Ibu
+ TRUETYPE_TAG('i', 'b', 'y', 0), // iby = Ibani
+ TRUETYPE_TAG('i', 'c', 'a', 0), // ica = Ede Ica
+ TRUETYPE_TAG('i', 'c', 'h', 0), // ich = Etkywan
+ TRUETYPE_TAG('i', 'c', 'l', 0), // icl = Icelandic Sign Language
+ TRUETYPE_TAG('i', 'c', 'r', 0), // icr = Islander Creole English
+ TRUETYPE_TAG('i', 'd', 'a', 0), // ida = Idakho-Isukha-Tiriki
+ TRUETYPE_TAG('i', 'd', 'b', 0), // idb = Indo-Portuguese
+ TRUETYPE_TAG('i', 'd', 'c', 0), // idc = Idon
+ TRUETYPE_TAG('i', 'd', 'd', 0), // idd = Ede Idaca
+ TRUETYPE_TAG('i', 'd', 'e', 0), // ide = Idere
+ TRUETYPE_TAG('i', 'd', 'i', 0), // idi = Idi
+ TRUETYPE_TAG('i', 'd', 'r', 0), // idr = Indri
+ TRUETYPE_TAG('i', 'd', 's', 0), // ids = Idesa
+ TRUETYPE_TAG('i', 'd', 't', 0), // idt = Idaté
+ TRUETYPE_TAG('i', 'd', 'u', 0), // idu = Idoma
+ TRUETYPE_TAG('i', 'f', 'a', 0), // ifa = Amganad Ifugao
+ TRUETYPE_TAG('i', 'f', 'b', 0), // ifb = Batad Ifugao
+ TRUETYPE_TAG('i', 'f', 'e', 0), // ife = Ifè
+ TRUETYPE_TAG('i', 'f', 'f', 0), // iff = Ifo
+ TRUETYPE_TAG('i', 'f', 'k', 0), // ifk = Tuwali Ifugao
+ TRUETYPE_TAG('i', 'f', 'm', 0), // ifm = Teke-Fuumu
+ TRUETYPE_TAG('i', 'f', 'u', 0), // ifu = Mayoyao Ifugao
+ TRUETYPE_TAG('i', 'f', 'y', 0), // ify = Keley-I Kallahan
+ TRUETYPE_TAG('i', 'g', 'b', 0), // igb = Ebira
+ TRUETYPE_TAG('i', 'g', 'e', 0), // ige = Igede
+ TRUETYPE_TAG('i', 'g', 'g', 0), // igg = Igana
+ TRUETYPE_TAG('i', 'g', 'l', 0), // igl = Igala
+ TRUETYPE_TAG('i', 'g', 'm', 0), // igm = Kanggape
+ TRUETYPE_TAG('i', 'g', 'n', 0), // ign = Ignaciano
+ TRUETYPE_TAG('i', 'g', 'o', 0), // igo = Isebe
+ TRUETYPE_TAG('i', 'g', 's', 0), // igs = Interglossa
+ TRUETYPE_TAG('i', 'g', 'w', 0), // igw = Igwe
+ TRUETYPE_TAG('i', 'h', 'b', 0), // ihb = Iha Based Pidgin
+ TRUETYPE_TAG('i', 'h', 'i', 0), // ihi = Ihievbe
+ TRUETYPE_TAG('i', 'h', 'p', 0), // ihp = Iha
+ TRUETYPE_TAG('i', 'i', 'r', 0), // iir = Indo-Iranian languages
+ TRUETYPE_TAG('i', 'j', 'c', 0), // ijc = Izon
+ TRUETYPE_TAG('i', 'j', 'e', 0), // ije = Biseni
+ TRUETYPE_TAG('i', 'j', 'j', 0), // ijj = Ede Ije
+ TRUETYPE_TAG('i', 'j', 'n', 0), // ijn = Kalabari
+ TRUETYPE_TAG('i', 'j', 'o', 0), // ijo = Ijo languages
+ TRUETYPE_TAG('i', 'j', 's', 0), // ijs = Southeast Ijo
+ TRUETYPE_TAG('i', 'k', 'e', 0), // ike = Eastern Canadian Inuktitut
+ TRUETYPE_TAG('i', 'k', 'i', 0), // iki = Iko
+ TRUETYPE_TAG('i', 'k', 'k', 0), // ikk = Ika
+ TRUETYPE_TAG('i', 'k', 'l', 0), // ikl = Ikulu
+ TRUETYPE_TAG('i', 'k', 'o', 0), // iko = Olulumo-Ikom
+ TRUETYPE_TAG('i', 'k', 'p', 0), // ikp = Ikpeshi
+ TRUETYPE_TAG('i', 'k', 't', 0), // ikt = Western Canadian Inuktitut
+ TRUETYPE_TAG('i', 'k', 'v', 0), // ikv = Iku-Gora-Ankwa
+ TRUETYPE_TAG('i', 'k', 'w', 0), // ikw = Ikwere
+ TRUETYPE_TAG('i', 'k', 'x', 0), // ikx = Ik
+ TRUETYPE_TAG('i', 'k', 'z', 0), // ikz = Ikizu
+ TRUETYPE_TAG('i', 'l', 'a', 0), // ila = Ile Ape
+ TRUETYPE_TAG('i', 'l', 'b', 0), // ilb = Ila
+ TRUETYPE_TAG('i', 'l', 'g', 0), // ilg = Garig-Ilgar
+ TRUETYPE_TAG('i', 'l', 'i', 0), // ili = Ili Turki
+ TRUETYPE_TAG('i', 'l', 'k', 0), // ilk = Ilongot
+ TRUETYPE_TAG('i', 'l', 'l', 0), // ill = Iranun
+ TRUETYPE_TAG('i', 'l', 'o', 0), // ilo = Iloko
+ TRUETYPE_TAG('i', 'l', 's', 0), // ils = International Sign
+ TRUETYPE_TAG('i', 'l', 'u', 0), // ilu = Ili'uun
+ TRUETYPE_TAG('i', 'l', 'v', 0), // ilv = Ilue
+ TRUETYPE_TAG('i', 'l', 'w', 0), // ilw = Talur
+ TRUETYPE_TAG('i', 'm', 'a', 0), // ima = Mala Malasar
+ TRUETYPE_TAG('i', 'm', 'e', 0), // ime = Imeraguen
+ TRUETYPE_TAG('i', 'm', 'i', 0), // imi = Anamgura
+ TRUETYPE_TAG('i', 'm', 'l', 0), // iml = Miluk
+ TRUETYPE_TAG('i', 'm', 'n', 0), // imn = Imonda
+ TRUETYPE_TAG('i', 'm', 'o', 0), // imo = Imbongu
+ TRUETYPE_TAG('i', 'm', 'r', 0), // imr = Imroing
+ TRUETYPE_TAG('i', 'm', 's', 0), // ims = Marsian
+ TRUETYPE_TAG('i', 'm', 'y', 0), // imy = Milyan
+ TRUETYPE_TAG('i', 'n', 'b', 0), // inb = Inga
+ TRUETYPE_TAG('i', 'n', 'c', 0), // inc = Indic languages
+ TRUETYPE_TAG('i', 'n', 'e', 0), // ine = Indo-European languages
+ TRUETYPE_TAG('i', 'n', 'g', 0), // ing = Degexit'an
+ TRUETYPE_TAG('i', 'n', 'h', 0), // inh = Ingush
+ TRUETYPE_TAG('i', 'n', 'j', 0), // inj = Jungle Inga
+ TRUETYPE_TAG('i', 'n', 'l', 0), // inl = Indonesian Sign Language
+ TRUETYPE_TAG('i', 'n', 'm', 0), // inm = Minaean
+ TRUETYPE_TAG('i', 'n', 'n', 0), // inn = Isinai
+ TRUETYPE_TAG('i', 'n', 'o', 0), // ino = Inoke-Yate
+ TRUETYPE_TAG('i', 'n', 'p', 0), // inp = Iñapari
+ TRUETYPE_TAG('i', 'n', 's', 0), // ins = Indian Sign Language
+ TRUETYPE_TAG('i', 'n', 't', 0), // int = Intha
+ TRUETYPE_TAG('i', 'n', 'z', 0), // inz = Ineseño
+ TRUETYPE_TAG('i', 'o', 'r', 0), // ior = Inor
+ TRUETYPE_TAG('i', 'o', 'u', 0), // iou = Tuma-Irumu
+ TRUETYPE_TAG('i', 'o', 'w', 0), // iow = Iowa-Oto
+ TRUETYPE_TAG('i', 'p', 'i', 0), // ipi = Ipili
+ TRUETYPE_TAG('i', 'p', 'o', 0), // ipo = Ipiko
+ TRUETYPE_TAG('i', 'q', 'u', 0), // iqu = Iquito
+ TRUETYPE_TAG('i', 'r', 'a', 0), // ira = Iranian languages
+ TRUETYPE_TAG('i', 'r', 'e', 0), // ire = Iresim
+ TRUETYPE_TAG('i', 'r', 'h', 0), // irh = Irarutu
+ TRUETYPE_TAG('i', 'r', 'i', 0), // iri = Irigwe
+ TRUETYPE_TAG('i', 'r', 'k', 0), // irk = Iraqw
+ TRUETYPE_TAG('i', 'r', 'n', 0), // irn = Irántxe
+ TRUETYPE_TAG('i', 'r', 'o', 0), // iro = Iroquoian languages
+ TRUETYPE_TAG('i', 'r', 'r', 0), // irr = Ir
+ TRUETYPE_TAG('i', 'r', 'u', 0), // iru = Irula
+ TRUETYPE_TAG('i', 'r', 'x', 0), // irx = Kamberau
+ TRUETYPE_TAG('i', 'r', 'y', 0), // iry = Iraya
+ TRUETYPE_TAG('i', 's', 'a', 0), // isa = Isabi
+ TRUETYPE_TAG('i', 's', 'c', 0), // isc = Isconahua
+ TRUETYPE_TAG('i', 's', 'd', 0), // isd = Isnag
+ TRUETYPE_TAG('i', 's', 'e', 0), // ise = Italian Sign Language
+ TRUETYPE_TAG('i', 's', 'g', 0), // isg = Irish Sign Language
+ TRUETYPE_TAG('i', 's', 'h', 0), // ish = Esan
+ TRUETYPE_TAG('i', 's', 'i', 0), // isi = Nkem-Nkum
+ TRUETYPE_TAG('i', 's', 'k', 0), // isk = Ishkashimi
+ TRUETYPE_TAG('i', 's', 'm', 0), // ism = Masimasi
+ TRUETYPE_TAG('i', 's', 'n', 0), // isn = Isanzu
+ TRUETYPE_TAG('i', 's', 'o', 0), // iso = Isoko
+ TRUETYPE_TAG('i', 's', 'r', 0), // isr = Israeli Sign Language
+ TRUETYPE_TAG('i', 's', 't', 0), // ist = Istriot
+ TRUETYPE_TAG('i', 's', 'u', 0), // isu = Isu (Menchum Division)
+ TRUETYPE_TAG('i', 't', 'b', 0), // itb = Binongan Itneg
+ TRUETYPE_TAG('i', 't', 'c', 0), // itc = Italic languages
+ TRUETYPE_TAG('i', 't', 'e', 0), // ite = Itene
+ TRUETYPE_TAG('i', 't', 'i', 0), // iti = Inlaod Itneg
+ TRUETYPE_TAG('i', 't', 'k', 0), // itk = Judeo-Italian
+ TRUETYPE_TAG('i', 't', 'l', 0), // itl = Itelmen
+ TRUETYPE_TAG('i', 't', 'm', 0), // itm = Itu Mbon Uzo
+ TRUETYPE_TAG('i', 't', 'o', 0), // ito = Itonama
+ TRUETYPE_TAG('i', 't', 'r', 0), // itr = Iteri
+ TRUETYPE_TAG('i', 't', 's', 0), // its = Isekiri
+ TRUETYPE_TAG('i', 't', 't', 0), // itt = Maeng Itneg
+ TRUETYPE_TAG('i', 't', 'v', 0), // itv = Itawit
+ TRUETYPE_TAG('i', 't', 'w', 0), // itw = Ito
+ TRUETYPE_TAG('i', 't', 'x', 0), // itx = Itik
+ TRUETYPE_TAG('i', 't', 'y', 0), // ity = Moyadan Itneg
+ TRUETYPE_TAG('i', 't', 'z', 0), // itz = Itzá
+ TRUETYPE_TAG('i', 'u', 'm', 0), // ium = Iu Mien
+ TRUETYPE_TAG('i', 'v', 'b', 0), // ivb = Ibatan
+ TRUETYPE_TAG('i', 'v', 'v', 0), // ivv = Ivatan
+ TRUETYPE_TAG('i', 'w', 'k', 0), // iwk = I-Wak
+ TRUETYPE_TAG('i', 'w', 'm', 0), // iwm = Iwam
+ TRUETYPE_TAG('i', 'w', 'o', 0), // iwo = Iwur
+ TRUETYPE_TAG('i', 'w', 's', 0), // iws = Sepik Iwam
+ TRUETYPE_TAG('i', 'x', 'c', 0), // ixc = Ixcatec
+ TRUETYPE_TAG('i', 'x', 'l', 0), // ixl = Ixil
+ TRUETYPE_TAG('i', 'y', 'a', 0), // iya = Iyayu
+ TRUETYPE_TAG('i', 'y', 'o', 0), // iyo = Mesaka
+ TRUETYPE_TAG('i', 'y', 'x', 0), // iyx = Yaka (Congo)
+ TRUETYPE_TAG('i', 'z', 'h', 0), // izh = Ingrian
+ TRUETYPE_TAG('i', 'z', 'i', 0), // izi = Izi-Ezaa-Ikwo-Mgbo
+ TRUETYPE_TAG('i', 'z', 'r', 0), // izr = Izere
+ TRUETYPE_TAG('j', 'a', 'a', 0), // jaa = Jamamadí
+ TRUETYPE_TAG('j', 'a', 'b', 0), // jab = Hyam
+ TRUETYPE_TAG('j', 'a', 'c', 0), // jac = Popti'
+ TRUETYPE_TAG('j', 'a', 'd', 0), // jad = Jahanka
+ TRUETYPE_TAG('j', 'a', 'e', 0), // jae = Yabem
+ TRUETYPE_TAG('j', 'a', 'f', 0), // jaf = Jara
+ TRUETYPE_TAG('j', 'a', 'h', 0), // jah = Jah Hut
+ TRUETYPE_TAG('j', 'a', 'j', 0), // jaj = Zazao
+ TRUETYPE_TAG('j', 'a', 'k', 0), // jak = Jakun
+ TRUETYPE_TAG('j', 'a', 'l', 0), // jal = Yalahatan
+ TRUETYPE_TAG('j', 'a', 'm', 0), // jam = Jamaican Creole English
+ TRUETYPE_TAG('j', 'a', 'o', 0), // jao = Yanyuwa
+ TRUETYPE_TAG('j', 'a', 'q', 0), // jaq = Yaqay
+ TRUETYPE_TAG('j', 'a', 'r', 0), // jar = Jarawa (Nigeria)
+ TRUETYPE_TAG('j', 'a', 's', 0), // jas = New Caledonian Javanese
+ TRUETYPE_TAG('j', 'a', 't', 0), // jat = Jakati
+ TRUETYPE_TAG('j', 'a', 'u', 0), // jau = Yaur
+ TRUETYPE_TAG('j', 'a', 'x', 0), // jax = Jambi Malay
+ TRUETYPE_TAG('j', 'a', 'y', 0), // jay = Yan-nhangu
+ TRUETYPE_TAG('j', 'a', 'z', 0), // jaz = Jawe
+ TRUETYPE_TAG('j', 'b', 'e', 0), // jbe = Judeo-Berber
+ TRUETYPE_TAG('j', 'b', 'j', 0), // jbj = Arandai
+ TRUETYPE_TAG('j', 'b', 'n', 0), // jbn = Nafusi
+ TRUETYPE_TAG('j', 'b', 'o', 0), // jbo = Lojban
+ TRUETYPE_TAG('j', 'b', 'r', 0), // jbr = Jofotek-Bromnya
+ TRUETYPE_TAG('j', 'b', 't', 0), // jbt = Jabutí
+ TRUETYPE_TAG('j', 'b', 'u', 0), // jbu = Jukun Takum
+ TRUETYPE_TAG('j', 'c', 's', 0), // jcs = Jamaican Country Sign Language
+ TRUETYPE_TAG('j', 'c', 't', 0), // jct = Krymchak
+ TRUETYPE_TAG('j', 'd', 'a', 0), // jda = Jad
+ TRUETYPE_TAG('j', 'd', 'g', 0), // jdg = Jadgali
+ TRUETYPE_TAG('j', 'd', 't', 0), // jdt = Judeo-Tat
+ TRUETYPE_TAG('j', 'e', 'b', 0), // jeb = Jebero
+ TRUETYPE_TAG('j', 'e', 'e', 0), // jee = Jerung
+ TRUETYPE_TAG('j', 'e', 'g', 0), // jeg = Jeng
+ TRUETYPE_TAG('j', 'e', 'h', 0), // jeh = Jeh
+ TRUETYPE_TAG('j', 'e', 'i', 0), // jei = Yei
+ TRUETYPE_TAG('j', 'e', 'k', 0), // jek = Jeri Kuo
+ TRUETYPE_TAG('j', 'e', 'l', 0), // jel = Yelmek
+ TRUETYPE_TAG('j', 'e', 'n', 0), // jen = Dza
+ TRUETYPE_TAG('j', 'e', 'r', 0), // jer = Jere
+ TRUETYPE_TAG('j', 'e', 't', 0), // jet = Manem
+ TRUETYPE_TAG('j', 'e', 'u', 0), // jeu = Jonkor Bourmataguil
+ TRUETYPE_TAG('j', 'g', 'b', 0), // jgb = Ngbee
+ TRUETYPE_TAG('j', 'g', 'e', 0), // jge = Judeo-Georgian
+ TRUETYPE_TAG('j', 'g', 'o', 0), // jgo = Ngomba
+ TRUETYPE_TAG('j', 'h', 'i', 0), // jhi = Jehai
+ TRUETYPE_TAG('j', 'h', 's', 0), // jhs = Jhankot Sign Language
+ TRUETYPE_TAG('j', 'i', 'a', 0), // jia = Jina
+ TRUETYPE_TAG('j', 'i', 'b', 0), // jib = Jibu
+ TRUETYPE_TAG('j', 'i', 'c', 0), // jic = Tol
+ TRUETYPE_TAG('j', 'i', 'd', 0), // jid = Bu
+ TRUETYPE_TAG('j', 'i', 'e', 0), // jie = Jilbe
+ TRUETYPE_TAG('j', 'i', 'g', 0), // jig = Djingili
+ TRUETYPE_TAG('j', 'i', 'h', 0), // jih = Shangzhai
+ TRUETYPE_TAG('j', 'i', 'i', 0), // jii = Jiiddu
+ TRUETYPE_TAG('j', 'i', 'l', 0), // jil = Jilim
+ TRUETYPE_TAG('j', 'i', 'm', 0), // jim = Jimi (Cameroon)
+ TRUETYPE_TAG('j', 'i', 'o', 0), // jio = Jiamao
+ TRUETYPE_TAG('j', 'i', 'q', 0), // jiq = Guanyinqiao
+ TRUETYPE_TAG('j', 'i', 't', 0), // jit = Jita
+ TRUETYPE_TAG('j', 'i', 'u', 0), // jiu = Youle Jinuo
+ TRUETYPE_TAG('j', 'i', 'v', 0), // jiv = Shuar
+ TRUETYPE_TAG('j', 'i', 'y', 0), // jiy = Buyuan Jinuo
+ TRUETYPE_TAG('j', 'k', 'o', 0), // jko = Kubo
+ TRUETYPE_TAG('j', 'k', 'u', 0), // jku = Labir
+ TRUETYPE_TAG('j', 'l', 'e', 0), // jle = Ngile
+ TRUETYPE_TAG('j', 'l', 's', 0), // jls = Jamaican Sign Language
+ TRUETYPE_TAG('j', 'm', 'a', 0), // jma = Dima
+ TRUETYPE_TAG('j', 'm', 'b', 0), // jmb = Zumbun
+ TRUETYPE_TAG('j', 'm', 'c', 0), // jmc = Machame
+ TRUETYPE_TAG('j', 'm', 'd', 0), // jmd = Yamdena
+ TRUETYPE_TAG('j', 'm', 'i', 0), // jmi = Jimi (Nigeria)
+ TRUETYPE_TAG('j', 'm', 'l', 0), // jml = Jumli
+ TRUETYPE_TAG('j', 'm', 'n', 0), // jmn = Makuri Naga
+ TRUETYPE_TAG('j', 'm', 'r', 0), // jmr = Kamara
+ TRUETYPE_TAG('j', 'm', 's', 0), // jms = Mashi (Nigeria)
+ TRUETYPE_TAG('j', 'm', 'x', 0), // jmx = Western Juxtlahuaca Mixtec
+ TRUETYPE_TAG('j', 'n', 'a', 0), // jna = Jangshung
+ TRUETYPE_TAG('j', 'n', 'd', 0), // jnd = Jandavra
+ TRUETYPE_TAG('j', 'n', 'g', 0), // jng = Yangman
+ TRUETYPE_TAG('j', 'n', 'i', 0), // jni = Janji
+ TRUETYPE_TAG('j', 'n', 'j', 0), // jnj = Yemsa
+ TRUETYPE_TAG('j', 'n', 'l', 0), // jnl = Rawat
+ TRUETYPE_TAG('j', 'n', 's', 0), // jns = Jaunsari
+ TRUETYPE_TAG('j', 'o', 'b', 0), // job = Joba
+ TRUETYPE_TAG('j', 'o', 'd', 0), // jod = Wojenaka
+ TRUETYPE_TAG('j', 'o', 'r', 0), // jor = Jorá
+ TRUETYPE_TAG('j', 'o', 's', 0), // jos = Jordanian Sign Language
+ TRUETYPE_TAG('j', 'o', 'w', 0), // jow = Jowulu
+ TRUETYPE_TAG('j', 'p', 'a', 0), // jpa = Jewish Palestinian Aramaic
+ TRUETYPE_TAG('j', 'p', 'r', 0), // jpr = Judeo-Persian
+ TRUETYPE_TAG('j', 'p', 'x', 0), // jpx = Japanese (family)
+ TRUETYPE_TAG('j', 'q', 'r', 0), // jqr = Jaqaru
+ TRUETYPE_TAG('j', 'r', 'a', 0), // jra = Jarai
+ TRUETYPE_TAG('j', 'r', 'b', 0), // jrb = Judeo-Arabic
+ TRUETYPE_TAG('j', 'r', 'r', 0), // jrr = Jiru
+ TRUETYPE_TAG('j', 'r', 't', 0), // jrt = Jorto
+ TRUETYPE_TAG('j', 'r', 'u', 0), // jru = Japrería
+ TRUETYPE_TAG('j', 's', 'l', 0), // jsl = Japanese Sign Language
+ TRUETYPE_TAG('j', 'u', 'a', 0), // jua = Júma
+ TRUETYPE_TAG('j', 'u', 'b', 0), // jub = Wannu
+ TRUETYPE_TAG('j', 'u', 'c', 0), // juc = Jurchen
+ TRUETYPE_TAG('j', 'u', 'd', 0), // jud = Worodougou
+ TRUETYPE_TAG('j', 'u', 'h', 0), // juh = Hõne
+ TRUETYPE_TAG('j', 'u', 'k', 0), // juk = Wapan
+ TRUETYPE_TAG('j', 'u', 'l', 0), // jul = Jirel
+ TRUETYPE_TAG('j', 'u', 'm', 0), // jum = Jumjum
+ TRUETYPE_TAG('j', 'u', 'n', 0), // jun = Juang
+ TRUETYPE_TAG('j', 'u', 'o', 0), // juo = Jiba
+ TRUETYPE_TAG('j', 'u', 'p', 0), // jup = Hupdë
+ TRUETYPE_TAG('j', 'u', 'r', 0), // jur = Jurúna
+ TRUETYPE_TAG('j', 'u', 's', 0), // jus = Jumla Sign Language
+ TRUETYPE_TAG('j', 'u', 't', 0), // jut = Jutish
+ TRUETYPE_TAG('j', 'u', 'u', 0), // juu = Ju
+ TRUETYPE_TAG('j', 'u', 'w', 0), // juw = Wãpha
+ TRUETYPE_TAG('j', 'u', 'y', 0), // juy = Juray
+ TRUETYPE_TAG('j', 'v', 'd', 0), // jvd = Javindo
+ TRUETYPE_TAG('j', 'v', 'n', 0), // jvn = Caribbean Javanese
+ TRUETYPE_TAG('j', 'w', 'i', 0), // jwi = Jwira-Pepesa
+ TRUETYPE_TAG('j', 'y', 'a', 0), // jya = Jiarong
+ TRUETYPE_TAG('j', 'y', 'e', 0), // jye = Judeo-Yemeni Arabic
+ TRUETYPE_TAG('j', 'y', 'y', 0), // jyy = Jaya
+ TRUETYPE_TAG('k', 'a', 'a', 0), // kaa = Kara-Kalpak
+ TRUETYPE_TAG('k', 'a', 'b', 0), // kab = Kabyle
+ TRUETYPE_TAG('k', 'a', 'c', 0), // kac = Kachin
+ TRUETYPE_TAG('k', 'a', 'd', 0), // kad = Kadara
+ TRUETYPE_TAG('k', 'a', 'e', 0), // kae = Ketangalan
+ TRUETYPE_TAG('k', 'a', 'f', 0), // kaf = Katso
+ TRUETYPE_TAG('k', 'a', 'g', 0), // kag = Kajaman
+ TRUETYPE_TAG('k', 'a', 'h', 0), // kah = Kara (Central African Republic)
+ TRUETYPE_TAG('k', 'a', 'i', 0), // kai = Karekare
+ TRUETYPE_TAG('k', 'a', 'j', 0), // kaj = Jju
+ TRUETYPE_TAG('k', 'a', 'k', 0), // kak = Kayapa Kallahan
+ TRUETYPE_TAG('k', 'a', 'm', 0), // kam = Kamba (Kenya)
+ TRUETYPE_TAG('k', 'a', 'o', 0), // kao = Xaasongaxango
+ TRUETYPE_TAG('k', 'a', 'p', 0), // kap = Bezhta
+ TRUETYPE_TAG('k', 'a', 'q', 0), // kaq = Capanahua
+ TRUETYPE_TAG('k', 'a', 'r', 0), // kar = Karen languages
+ TRUETYPE_TAG('k', 'a', 'v', 0), // kav = Katukína
+ TRUETYPE_TAG('k', 'a', 'w', 0), // kaw = Kawi
+ TRUETYPE_TAG('k', 'a', 'x', 0), // kax = Kao
+ TRUETYPE_TAG('k', 'a', 'y', 0), // kay = Kamayurá
+ TRUETYPE_TAG('k', 'b', 'a', 0), // kba = Kalarko
+ TRUETYPE_TAG('k', 'b', 'b', 0), // kbb = Kaxuiâna
+ TRUETYPE_TAG('k', 'b', 'c', 0), // kbc = Kadiwéu
+ TRUETYPE_TAG('k', 'b', 'd', 0), // kbd = Kabardian
+ TRUETYPE_TAG('k', 'b', 'e', 0), // kbe = Kanju
+ TRUETYPE_TAG('k', 'b', 'f', 0), // kbf = Kakauhua
+ TRUETYPE_TAG('k', 'b', 'g', 0), // kbg = Khamba
+ TRUETYPE_TAG('k', 'b', 'h', 0), // kbh = Camsá
+ TRUETYPE_TAG('k', 'b', 'i', 0), // kbi = Kaptiau
+ TRUETYPE_TAG('k', 'b', 'j', 0), // kbj = Kari
+ TRUETYPE_TAG('k', 'b', 'k', 0), // kbk = Grass Koiari
+ TRUETYPE_TAG('k', 'b', 'l', 0), // kbl = Kanembu
+ TRUETYPE_TAG('k', 'b', 'm', 0), // kbm = Iwal
+ TRUETYPE_TAG('k', 'b', 'n', 0), // kbn = Kare (Central African Republic)
+ TRUETYPE_TAG('k', 'b', 'o', 0), // kbo = Keliko
+ TRUETYPE_TAG('k', 'b', 'p', 0), // kbp = Kabiyè
+ TRUETYPE_TAG('k', 'b', 'q', 0), // kbq = Kamano
+ TRUETYPE_TAG('k', 'b', 'r', 0), // kbr = Kafa
+ TRUETYPE_TAG('k', 'b', 's', 0), // kbs = Kande
+ TRUETYPE_TAG('k', 'b', 't', 0), // kbt = Abadi
+ TRUETYPE_TAG('k', 'b', 'u', 0), // kbu = Kabutra
+ TRUETYPE_TAG('k', 'b', 'v', 0), // kbv = Dera (Indonesia)
+ TRUETYPE_TAG('k', 'b', 'w', 0), // kbw = Kaiep
+ TRUETYPE_TAG('k', 'b', 'x', 0), // kbx = Ap Ma
+ TRUETYPE_TAG('k', 'b', 'y', 0), // kby = Manga Kanuri
+ TRUETYPE_TAG('k', 'b', 'z', 0), // kbz = Duhwa
+ TRUETYPE_TAG('k', 'c', 'a', 0), // kca = Khanty
+ TRUETYPE_TAG('k', 'c', 'b', 0), // kcb = Kawacha
+ TRUETYPE_TAG('k', 'c', 'c', 0), // kcc = Lubila
+ TRUETYPE_TAG('k', 'c', 'd', 0), // kcd = Ngkâlmpw Kanum
+ TRUETYPE_TAG('k', 'c', 'e', 0), // kce = Kaivi
+ TRUETYPE_TAG('k', 'c', 'f', 0), // kcf = Ukaan
+ TRUETYPE_TAG('k', 'c', 'g', 0), // kcg = Tyap
+ TRUETYPE_TAG('k', 'c', 'h', 0), // kch = Vono
+ TRUETYPE_TAG('k', 'c', 'i', 0), // kci = Kamantan
+ TRUETYPE_TAG('k', 'c', 'j', 0), // kcj = Kobiana
+ TRUETYPE_TAG('k', 'c', 'k', 0), // kck = Kalanga
+ TRUETYPE_TAG('k', 'c', 'l', 0), // kcl = Kela (Papua New Guinea)
+ TRUETYPE_TAG('k', 'c', 'm', 0), // kcm = Gula (Central African Republic)
+ TRUETYPE_TAG('k', 'c', 'n', 0), // kcn = Nubi
+ TRUETYPE_TAG('k', 'c', 'o', 0), // kco = Kinalakna
+ TRUETYPE_TAG('k', 'c', 'p', 0), // kcp = Kanga
+ TRUETYPE_TAG('k', 'c', 'q', 0), // kcq = Kamo
+ TRUETYPE_TAG('k', 'c', 'r', 0), // kcr = Katla
+ TRUETYPE_TAG('k', 'c', 's', 0), // kcs = Koenoem
+ TRUETYPE_TAG('k', 'c', 't', 0), // kct = Kaian
+ TRUETYPE_TAG('k', 'c', 'u', 0), // kcu = Kami (Tanzania)
+ TRUETYPE_TAG('k', 'c', 'v', 0), // kcv = Kete
+ TRUETYPE_TAG('k', 'c', 'w', 0), // kcw = Kabwari
+ TRUETYPE_TAG('k', 'c', 'x', 0), // kcx = Kachama-Ganjule
+ TRUETYPE_TAG('k', 'c', 'y', 0), // kcy = Korandje
+ TRUETYPE_TAG('k', 'c', 'z', 0), // kcz = Konongo
+ TRUETYPE_TAG('k', 'd', 'a', 0), // kda = Worimi
+ TRUETYPE_TAG('k', 'd', 'c', 0), // kdc = Kutu
+ TRUETYPE_TAG('k', 'd', 'd', 0), // kdd = Yankunytjatjara
+ TRUETYPE_TAG('k', 'd', 'e', 0), // kde = Makonde
+ TRUETYPE_TAG('k', 'd', 'f', 0), // kdf = Mamusi
+ TRUETYPE_TAG('k', 'd', 'g', 0), // kdg = Seba
+ TRUETYPE_TAG('k', 'd', 'h', 0), // kdh = Tem
+ TRUETYPE_TAG('k', 'd', 'i', 0), // kdi = Kumam
+ TRUETYPE_TAG('k', 'd', 'j', 0), // kdj = Karamojong
+ TRUETYPE_TAG('k', 'd', 'k', 0), // kdk = Numee
+ TRUETYPE_TAG('k', 'd', 'l', 0), // kdl = Tsikimba
+ TRUETYPE_TAG('k', 'd', 'm', 0), // kdm = Kagoma
+ TRUETYPE_TAG('k', 'd', 'n', 0), // kdn = Kunda
+ TRUETYPE_TAG('k', 'd', 'o', 0), // kdo = Kordofanian languages
+ TRUETYPE_TAG('k', 'd', 'p', 0), // kdp = Kaningdon-Nindem
+ TRUETYPE_TAG('k', 'd', 'q', 0), // kdq = Koch
+ TRUETYPE_TAG('k', 'd', 'r', 0), // kdr = Karaim
+ TRUETYPE_TAG('k', 'd', 't', 0), // kdt = Kuy
+ TRUETYPE_TAG('k', 'd', 'u', 0), // kdu = Kadaru
+ TRUETYPE_TAG('k', 'd', 'v', 0), // kdv = Kado
+ TRUETYPE_TAG('k', 'd', 'w', 0), // kdw = Koneraw
+ TRUETYPE_TAG('k', 'd', 'x', 0), // kdx = Kam
+ TRUETYPE_TAG('k', 'd', 'y', 0), // kdy = Keder
+ TRUETYPE_TAG('k', 'd', 'z', 0), // kdz = Kwaja
+ TRUETYPE_TAG('k', 'e', 'a', 0), // kea = Kabuverdianu
+ TRUETYPE_TAG('k', 'e', 'b', 0), // keb = Kélé
+ TRUETYPE_TAG('k', 'e', 'c', 0), // kec = Keiga
+ TRUETYPE_TAG('k', 'e', 'd', 0), // ked = Kerewe
+ TRUETYPE_TAG('k', 'e', 'e', 0), // kee = Eastern Keres
+ TRUETYPE_TAG('k', 'e', 'f', 0), // kef = Kpessi
+ TRUETYPE_TAG('k', 'e', 'g', 0), // keg = Tese
+ TRUETYPE_TAG('k', 'e', 'h', 0), // keh = Keak
+ TRUETYPE_TAG('k', 'e', 'i', 0), // kei = Kei
+ TRUETYPE_TAG('k', 'e', 'j', 0), // kej = Kadar
+ TRUETYPE_TAG('k', 'e', 'k', 0), // kek = Kekchí
+ TRUETYPE_TAG('k', 'e', 'l',
+ 0), // kel = Kela (Democratic Republic of Congo)
+ TRUETYPE_TAG('k', 'e', 'm', 0), // kem = Kemak
+ TRUETYPE_TAG('k', 'e', 'n', 0), // ken = Kenyang
+ TRUETYPE_TAG('k', 'e', 'o', 0), // keo = Kakwa
+ TRUETYPE_TAG('k', 'e', 'p', 0), // kep = Kaikadi
+ TRUETYPE_TAG('k', 'e', 'q', 0), // keq = Kamar
+ TRUETYPE_TAG('k', 'e', 'r', 0), // ker = Kera
+ TRUETYPE_TAG('k', 'e', 's', 0), // kes = Kugbo
+ TRUETYPE_TAG('k', 'e', 't', 0), // ket = Ket
+ TRUETYPE_TAG('k', 'e', 'u', 0), // keu = Akebu
+ TRUETYPE_TAG('k', 'e', 'v', 0), // kev = Kanikkaran
+ TRUETYPE_TAG('k', 'e', 'w', 0), // kew = West Kewa
+ TRUETYPE_TAG('k', 'e', 'x', 0), // kex = Kukna
+ TRUETYPE_TAG('k', 'e', 'y', 0), // key = Kupia
+ TRUETYPE_TAG('k', 'e', 'z', 0), // kez = Kukele
+ TRUETYPE_TAG('k', 'f', 'a', 0), // kfa = Kodava
+ TRUETYPE_TAG('k', 'f', 'b', 0), // kfb = Northwestern Kolami
+ TRUETYPE_TAG('k', 'f', 'c', 0), // kfc = Konda-Dora
+ TRUETYPE_TAG('k', 'f', 'd', 0), // kfd = Korra Koraga
+ TRUETYPE_TAG('k', 'f', 'e', 0), // kfe = Kota (India)
+ TRUETYPE_TAG('k', 'f', 'f', 0), // kff = Koya
+ TRUETYPE_TAG('k', 'f', 'g', 0), // kfg = Kudiya
+ TRUETYPE_TAG('k', 'f', 'h', 0), // kfh = Kurichiya
+ TRUETYPE_TAG('k', 'f', 'i', 0), // kfi = Kannada Kurumba
+ TRUETYPE_TAG('k', 'f', 'j', 0), // kfj = Kemiehua
+ TRUETYPE_TAG('k', 'f', 'k', 0), // kfk = Kinnauri
+ TRUETYPE_TAG('k', 'f', 'l', 0), // kfl = Kung
+ TRUETYPE_TAG('k', 'f', 'm', 0), // kfm = Khunsari
+ TRUETYPE_TAG('k', 'f', 'n', 0), // kfn = Kuk
+ TRUETYPE_TAG('k', 'f', 'o', 0), // kfo = Koro (Côte d'Ivoire)
+ TRUETYPE_TAG('k', 'f', 'p', 0), // kfp = Korwa
+ TRUETYPE_TAG('k', 'f', 'q', 0), // kfq = Korku
+ TRUETYPE_TAG('k', 'f', 'r', 0), // kfr = Kachchi
+ TRUETYPE_TAG('k', 'f', 's', 0), // kfs = Bilaspuri
+ TRUETYPE_TAG('k', 'f', 't', 0), // kft = Kanjari
+ TRUETYPE_TAG('k', 'f', 'u', 0), // kfu = Katkari
+ TRUETYPE_TAG('k', 'f', 'v', 0), // kfv = Kurmukar
+ TRUETYPE_TAG('k', 'f', 'w', 0), // kfw = Kharam Naga
+ TRUETYPE_TAG('k', 'f', 'x', 0), // kfx = Kullu Pahari
+ TRUETYPE_TAG('k', 'f', 'y', 0), // kfy = Kumaoni
+ TRUETYPE_TAG('k', 'f', 'z', 0), // kfz = Koromfé
+ TRUETYPE_TAG('k', 'g', 'a', 0), // kga = Koyaga
+ TRUETYPE_TAG('k', 'g', 'b', 0), // kgb = Kawe
+ TRUETYPE_TAG('k', 'g', 'c', 0), // kgc = Kasseng
+ TRUETYPE_TAG('k', 'g', 'd', 0), // kgd = Kataang
+ TRUETYPE_TAG('k', 'g', 'e', 0), // kge = Komering
+ TRUETYPE_TAG('k', 'g', 'f', 0), // kgf = Kube
+ TRUETYPE_TAG('k', 'g', 'g', 0), // kgg = Kusunda
+ TRUETYPE_TAG('k', 'g', 'h', 0), // kgh = Upper Tanudan Kalinga
+ TRUETYPE_TAG('k', 'g', 'i', 0), // kgi = Selangor Sign Language
+ TRUETYPE_TAG('k', 'g', 'j', 0), // kgj = Gamale Kham
+ TRUETYPE_TAG('k', 'g', 'k', 0), // kgk = Kaiwá
+ TRUETYPE_TAG('k', 'g', 'l', 0), // kgl = Kunggari
+ TRUETYPE_TAG('k', 'g', 'm', 0), // kgm = Karipúna
+ TRUETYPE_TAG('k', 'g', 'n', 0), // kgn = Karingani
+ TRUETYPE_TAG('k', 'g', 'o', 0), // kgo = Krongo
+ TRUETYPE_TAG('k', 'g', 'p', 0), // kgp = Kaingang
+ TRUETYPE_TAG('k', 'g', 'q', 0), // kgq = Kamoro
+ TRUETYPE_TAG('k', 'g', 'r', 0), // kgr = Abun
+ TRUETYPE_TAG('k', 'g', 's', 0), // kgs = Kumbainggar
+ TRUETYPE_TAG('k', 'g', 't', 0), // kgt = Somyev
+ TRUETYPE_TAG('k', 'g', 'u', 0), // kgu = Kobol
+ TRUETYPE_TAG('k', 'g', 'v', 0), // kgv = Karas
+ TRUETYPE_TAG('k', 'g', 'w', 0), // kgw = Karon Dori
+ TRUETYPE_TAG('k', 'g', 'x', 0), // kgx = Kamaru
+ TRUETYPE_TAG('k', 'g', 'y', 0), // kgy = Kyerung
+ TRUETYPE_TAG('k', 'h', 'a', 0), // kha = Khasi
+ TRUETYPE_TAG('k', 'h', 'b', 0), // khb = Lü
+ TRUETYPE_TAG('k', 'h', 'c', 0), // khc = Tukang Besi North
+ TRUETYPE_TAG('k', 'h', 'd', 0), // khd = Bädi Kanum
+ TRUETYPE_TAG('k', 'h', 'e', 0), // khe = Korowai
+ TRUETYPE_TAG('k', 'h', 'f', 0), // khf = Khuen
+ TRUETYPE_TAG('k', 'h', 'g', 0), // khg = Khams Tibetan
+ TRUETYPE_TAG('k', 'h', 'h', 0), // khh = Kehu
+ TRUETYPE_TAG('k', 'h', 'i', 0), // khi = Khoisan languages
+ TRUETYPE_TAG('k', 'h', 'j', 0), // khj = Kuturmi
+ TRUETYPE_TAG('k', 'h', 'k', 0), // khk = Halh Mongolian
+ TRUETYPE_TAG('k', 'h', 'l', 0), // khl = Lusi
+ TRUETYPE_TAG('k', 'h', 'n', 0), // khn = Khandesi
+ TRUETYPE_TAG('k', 'h', 'o', 0), // kho = Khotanese
+ TRUETYPE_TAG('k', 'h', 'p', 0), // khp = Kapori
+ TRUETYPE_TAG('k', 'h', 'q', 0), // khq = Koyra Chiini Songhay
+ TRUETYPE_TAG('k', 'h', 'r', 0), // khr = Kharia
+ TRUETYPE_TAG('k', 'h', 's', 0), // khs = Kasua
+ TRUETYPE_TAG('k', 'h', 't', 0), // kht = Khamti
+ TRUETYPE_TAG('k', 'h', 'u', 0), // khu = Nkhumbi
+ TRUETYPE_TAG('k', 'h', 'v', 0), // khv = Khvarshi
+ TRUETYPE_TAG('k', 'h', 'w', 0), // khw = Khowar
+ TRUETYPE_TAG('k', 'h', 'x', 0), // khx = Kanu
+ TRUETYPE_TAG('k', 'h', 'y',
+ 0), // khy = Kele (Democratic Republic of Congo)
+ TRUETYPE_TAG('k', 'h', 'z', 0), // khz = Keapara
+ TRUETYPE_TAG('k', 'i', 'a', 0), // kia = Kim
+ TRUETYPE_TAG('k', 'i', 'b', 0), // kib = Koalib
+ TRUETYPE_TAG('k', 'i', 'c', 0), // kic = Kickapoo
+ TRUETYPE_TAG('k', 'i', 'd', 0), // kid = Koshin
+ TRUETYPE_TAG('k', 'i', 'e', 0), // kie = Kibet
+ TRUETYPE_TAG('k', 'i', 'f', 0), // kif = Eastern Parbate Kham
+ TRUETYPE_TAG('k', 'i', 'g', 0), // kig = Kimaama
+ TRUETYPE_TAG('k', 'i', 'h', 0), // kih = Kilmeri
+ TRUETYPE_TAG('k', 'i', 'i', 0), // kii = Kitsai
+ TRUETYPE_TAG('k', 'i', 'j', 0), // kij = Kilivila
+ TRUETYPE_TAG('k', 'i', 'l', 0), // kil = Kariya
+ TRUETYPE_TAG('k', 'i', 'm', 0), // kim = Karagas
+ TRUETYPE_TAG('k', 'i', 'o', 0), // kio = Kiowa
+ TRUETYPE_TAG('k', 'i', 'p', 0), // kip = Sheshi Kham
+ TRUETYPE_TAG('k', 'i', 'q', 0), // kiq = Kosadle
+ TRUETYPE_TAG('k', 'i', 's', 0), // kis = Kis
+ TRUETYPE_TAG('k', 'i', 't', 0), // kit = Agob
+ TRUETYPE_TAG('k', 'i', 'u', 0), // kiu = Kirmanjki (individual language)
+ TRUETYPE_TAG('k', 'i', 'v', 0), // kiv = Kimbu
+ TRUETYPE_TAG('k', 'i', 'w', 0), // kiw = Northeast Kiwai
+ TRUETYPE_TAG('k', 'i', 'x', 0), // kix = Khiamniungan Naga
+ TRUETYPE_TAG('k', 'i', 'y', 0), // kiy = Kirikiri
+ TRUETYPE_TAG('k', 'i', 'z', 0), // kiz = Kisi
+ TRUETYPE_TAG('k', 'j', 'a', 0), // kja = Mlap
+ TRUETYPE_TAG('k', 'j', 'b', 0), // kjb = Q'anjob'al
+ TRUETYPE_TAG('k', 'j', 'c', 0), // kjc = Coastal Konjo
+ TRUETYPE_TAG('k', 'j', 'd', 0), // kjd = Southern Kiwai
+ TRUETYPE_TAG('k', 'j', 'e', 0), // kje = Kisar
+ TRUETYPE_TAG('k', 'j', 'f', 0), // kjf = Khalaj
+ TRUETYPE_TAG('k', 'j', 'g', 0), // kjg = Khmu
+ TRUETYPE_TAG('k', 'j', 'h', 0), // kjh = Khakas
+ TRUETYPE_TAG('k', 'j', 'i', 0), // kji = Zabana
+ TRUETYPE_TAG('k', 'j', 'j', 0), // kjj = Khinalugh
+ TRUETYPE_TAG('k', 'j', 'k', 0), // kjk = Highland Konjo
+ TRUETYPE_TAG('k', 'j', 'l', 0), // kjl = Western Parbate Kham
+ TRUETYPE_TAG('k', 'j', 'm', 0), // kjm = Kháng
+ TRUETYPE_TAG('k', 'j', 'n', 0), // kjn = Kunjen
+ TRUETYPE_TAG('k', 'j', 'o', 0), // kjo = Harijan Kinnauri
+ TRUETYPE_TAG('k', 'j', 'p', 0), // kjp = Pwo Eastern Karen
+ TRUETYPE_TAG('k', 'j', 'q', 0), // kjq = Western Keres
+ TRUETYPE_TAG('k', 'j', 'r', 0), // kjr = Kurudu
+ TRUETYPE_TAG('k', 'j', 's', 0), // kjs = East Kewa
+ TRUETYPE_TAG('k', 'j', 't', 0), // kjt = Phrae Pwo Karen
+ TRUETYPE_TAG('k', 'j', 'u', 0), // kju = Kashaya
+ TRUETYPE_TAG('k', 'j', 'x', 0), // kjx = Ramopa
+ TRUETYPE_TAG('k', 'j', 'y', 0), // kjy = Erave
+ TRUETYPE_TAG('k', 'j', 'z', 0), // kjz = Bumthangkha
+ TRUETYPE_TAG('k', 'k', 'a', 0), // kka = Kakanda
+ TRUETYPE_TAG('k', 'k', 'b', 0), // kkb = Kwerisa
+ TRUETYPE_TAG('k', 'k', 'c', 0), // kkc = Odoodee
+ TRUETYPE_TAG('k', 'k', 'd', 0), // kkd = Kinuku
+ TRUETYPE_TAG('k', 'k', 'e', 0), // kke = Kakabe
+ TRUETYPE_TAG('k', 'k', 'f', 0), // kkf = Kalaktang Monpa
+ TRUETYPE_TAG('k', 'k', 'g', 0), // kkg = Mabaka Valley Kalinga
+ TRUETYPE_TAG('k', 'k', 'h', 0), // kkh = Khün
+ TRUETYPE_TAG('k', 'k', 'i', 0), // kki = Kagulu
+ TRUETYPE_TAG('k', 'k', 'j', 0), // kkj = Kako
+ TRUETYPE_TAG('k', 'k', 'k', 0), // kkk = Kokota
+ TRUETYPE_TAG('k', 'k', 'l', 0), // kkl = Kosarek Yale
+ TRUETYPE_TAG('k', 'k', 'm', 0), // kkm = Kiong
+ TRUETYPE_TAG('k', 'k', 'n', 0), // kkn = Kon Keu
+ TRUETYPE_TAG('k', 'k', 'o', 0), // kko = Karko
+ TRUETYPE_TAG('k', 'k', 'p', 0), // kkp = Gugubera
+ TRUETYPE_TAG('k', 'k', 'q', 0), // kkq = Kaiku
+ TRUETYPE_TAG('k', 'k', 'r', 0), // kkr = Kir-Balar
+ TRUETYPE_TAG('k', 'k', 's', 0), // kks = Giiwo
+ TRUETYPE_TAG('k', 'k', 't', 0), // kkt = Koi
+ TRUETYPE_TAG('k', 'k', 'u', 0), // kku = Tumi
+ TRUETYPE_TAG('k', 'k', 'v', 0), // kkv = Kangean
+ TRUETYPE_TAG('k', 'k', 'w', 0), // kkw = Teke-Kukuya
+ TRUETYPE_TAG('k', 'k', 'x', 0), // kkx = Kohin
+ TRUETYPE_TAG('k', 'k', 'y', 0), // kky = Guguyimidjir
+ TRUETYPE_TAG('k', 'k', 'z', 0), // kkz = Kaska
+ TRUETYPE_TAG('k', 'l', 'a', 0), // kla = Klamath-Modoc
+ TRUETYPE_TAG('k', 'l', 'b', 0), // klb = Kiliwa
+ TRUETYPE_TAG('k', 'l', 'c', 0), // klc = Kolbila
+ TRUETYPE_TAG('k', 'l', 'd', 0), // kld = Gamilaraay
+ TRUETYPE_TAG('k', 'l', 'e', 0), // kle = Kulung (Nepal)
+ TRUETYPE_TAG('k', 'l', 'f', 0), // klf = Kendeje
+ TRUETYPE_TAG('k', 'l', 'g', 0), // klg = Tagakaulo
+ TRUETYPE_TAG('k', 'l', 'h', 0), // klh = Weliki
+ TRUETYPE_TAG('k', 'l', 'i', 0), // kli = Kalumpang
+ TRUETYPE_TAG('k', 'l', 'j', 0), // klj = Turkic Khalaj
+ TRUETYPE_TAG('k', 'l', 'k', 0), // klk = Kono (Nigeria)
+ TRUETYPE_TAG('k', 'l', 'l', 0), // kll = Kagan Kalagan
+ TRUETYPE_TAG('k', 'l', 'm', 0), // klm = Migum
+ TRUETYPE_TAG('k', 'l', 'n', 0), // kln = Kalenjin
+ TRUETYPE_TAG('k', 'l', 'o', 0), // klo = Kapya
+ TRUETYPE_TAG('k', 'l', 'p', 0), // klp = Kamasa
+ TRUETYPE_TAG('k', 'l', 'q', 0), // klq = Rumu
+ TRUETYPE_TAG('k', 'l', 'r', 0), // klr = Khaling
+ TRUETYPE_TAG('k', 'l', 's', 0), // kls = Kalasha
+ TRUETYPE_TAG('k', 'l', 't', 0), // klt = Nukna
+ TRUETYPE_TAG('k', 'l', 'u', 0), // klu = Klao
+ TRUETYPE_TAG('k', 'l', 'v', 0), // klv = Maskelynes
+ TRUETYPE_TAG('k', 'l', 'w', 0), // klw = Lindu
+ TRUETYPE_TAG('k', 'l', 'x', 0), // klx = Koluwawa
+ TRUETYPE_TAG('k', 'l', 'y', 0), // kly = Kalao
+ TRUETYPE_TAG('k', 'l', 'z', 0), // klz = Kabola
+ TRUETYPE_TAG('k', 'm', 'a', 0), // kma = Konni
+ TRUETYPE_TAG('k', 'm', 'b', 0), // kmb = Kimbundu
+ TRUETYPE_TAG('k', 'm', 'c', 0), // kmc = Southern Dong
+ TRUETYPE_TAG('k', 'm', 'd', 0), // kmd = Majukayang Kalinga
+ TRUETYPE_TAG('k', 'm', 'e', 0), // kme = Bakole
+ TRUETYPE_TAG('k', 'm', 'f', 0), // kmf = Kare (Papua New Guinea)
+ TRUETYPE_TAG('k', 'm', 'g', 0), // kmg = Kâte
+ TRUETYPE_TAG('k', 'm', 'h', 0), // kmh = Kalam
+ TRUETYPE_TAG('k', 'm', 'i', 0), // kmi = Kami (Nigeria)
+ TRUETYPE_TAG('k', 'm', 'j', 0), // kmj = Kumarbhag Paharia
+ TRUETYPE_TAG('k', 'm', 'k', 0), // kmk = Limos Kalinga
+ TRUETYPE_TAG('k', 'm', 'l', 0), // kml = Lower Tanudan Kalinga
+ TRUETYPE_TAG('k', 'm', 'm', 0), // kmm = Kom (India)
+ TRUETYPE_TAG('k', 'm', 'n', 0), // kmn = Awtuw
+ TRUETYPE_TAG('k', 'm', 'o', 0), // kmo = Kwoma
+ TRUETYPE_TAG('k', 'm', 'p', 0), // kmp = Gimme
+ TRUETYPE_TAG('k', 'm', 'q', 0), // kmq = Kwama
+ TRUETYPE_TAG('k', 'm', 'r', 0), // kmr = Northern Kurdish
+ TRUETYPE_TAG('k', 'm', 's', 0), // kms = Kamasau
+ TRUETYPE_TAG('k', 'm', 't', 0), // kmt = Kemtuik
+ TRUETYPE_TAG('k', 'm', 'u', 0), // kmu = Kanite
+ TRUETYPE_TAG('k', 'm', 'v', 0), // kmv = Karipúna Creole French
+ TRUETYPE_TAG('k', 'm', 'w',
+ 0), // kmw = Komo (Democratic Republic of Congo)
+ TRUETYPE_TAG('k', 'm', 'x', 0), // kmx = Waboda
+ TRUETYPE_TAG('k', 'm', 'y', 0), // kmy = Koma
+ TRUETYPE_TAG('k', 'm', 'z', 0), // kmz = Khorasani Turkish
+ TRUETYPE_TAG('k', 'n', 'a', 0), // kna = Dera (Nigeria)
+ TRUETYPE_TAG('k', 'n', 'b', 0), // knb = Lubuagan Kalinga
+ TRUETYPE_TAG('k', 'n', 'c', 0), // knc = Central Kanuri
+ TRUETYPE_TAG('k', 'n', 'd', 0), // knd = Konda
+ TRUETYPE_TAG('k', 'n', 'e', 0), // kne = Kankanaey
+ TRUETYPE_TAG('k', 'n', 'f', 0), // knf = Mankanya
+ TRUETYPE_TAG('k', 'n', 'g', 0), // kng = Koongo
+ TRUETYPE_TAG('k', 'n', 'i', 0), // kni = Kanufi
+ TRUETYPE_TAG('k', 'n', 'j', 0), // knj = Western Kanjobal
+ TRUETYPE_TAG('k', 'n', 'k', 0), // knk = Kuranko
+ TRUETYPE_TAG('k', 'n', 'l', 0), // knl = Keninjal
+ TRUETYPE_TAG('k', 'n', 'm', 0), // knm = Kanamarí
+ TRUETYPE_TAG('k', 'n', 'n', 0), // knn = Konkani (individual language)
+ TRUETYPE_TAG('k', 'n', 'o', 0), // kno = Kono (Sierra Leone)
+ TRUETYPE_TAG('k', 'n', 'p', 0), // knp = Kwanja
+ TRUETYPE_TAG('k', 'n', 'q', 0), // knq = Kintaq
+ TRUETYPE_TAG('k', 'n', 'r', 0), // knr = Kaningra
+ TRUETYPE_TAG('k', 'n', 's', 0), // kns = Kensiu
+ TRUETYPE_TAG('k', 'n', 't', 0), // knt = Panoan Katukína
+ TRUETYPE_TAG('k', 'n', 'u', 0), // knu = Kono (Guinea)
+ TRUETYPE_TAG('k', 'n', 'v', 0), // knv = Tabo
+ TRUETYPE_TAG('k', 'n', 'w', 0), // knw = Kung-Ekoka
+ TRUETYPE_TAG('k', 'n', 'x', 0), // knx = Kendayan
+ TRUETYPE_TAG('k', 'n', 'y', 0), // kny = Kanyok
+ TRUETYPE_TAG('k', 'n', 'z', 0), // knz = Kalamsé
+ TRUETYPE_TAG('k', 'o', 'a', 0), // koa = Konomala
+ TRUETYPE_TAG('k', 'o', 'c', 0), // koc = Kpati
+ TRUETYPE_TAG('k', 'o', 'd', 0), // kod = Kodi
+ TRUETYPE_TAG('k', 'o', 'e', 0), // koe = Kacipo-Balesi
+ TRUETYPE_TAG('k', 'o', 'f', 0), // kof = Kubi
+ TRUETYPE_TAG('k', 'o', 'g', 0), // kog = Cogui
+ TRUETYPE_TAG('k', 'o', 'h', 0), // koh = Koyo
+ TRUETYPE_TAG('k', 'o', 'i', 0), // koi = Komi-Permyak
+ TRUETYPE_TAG('k', 'o', 'j', 0), // koj = Sara Dunjo
+ TRUETYPE_TAG('k', 'o', 'k', 0), // kok = Konkani (macrolanguage)
+ TRUETYPE_TAG('k', 'o', 'l', 0), // kol = Kol (Papua New Guinea)
+ TRUETYPE_TAG('k', 'o', 'o', 0), // koo = Konzo
+ TRUETYPE_TAG('k', 'o', 'p', 0), // kop = Waube
+ TRUETYPE_TAG('k', 'o', 'q', 0), // koq = Kota (Gabon)
+ TRUETYPE_TAG('k', 'o', 's', 0), // kos = Kosraean
+ TRUETYPE_TAG('k', 'o', 't', 0), // kot = Lagwan
+ TRUETYPE_TAG('k', 'o', 'u', 0), // kou = Koke
+ TRUETYPE_TAG('k', 'o', 'v', 0), // kov = Kudu-Camo
+ TRUETYPE_TAG('k', 'o', 'w', 0), // kow = Kugama
+ TRUETYPE_TAG('k', 'o', 'x', 0), // kox = Coxima
+ TRUETYPE_TAG('k', 'o', 'y', 0), // koy = Koyukon
+ TRUETYPE_TAG('k', 'o', 'z', 0), // koz = Korak
+ TRUETYPE_TAG('k', 'p', 'a', 0), // kpa = Kutto
+ TRUETYPE_TAG('k', 'p', 'b', 0), // kpb = Mullu Kurumba
+ TRUETYPE_TAG('k', 'p', 'c', 0), // kpc = Curripaco
+ TRUETYPE_TAG('k', 'p', 'd', 0), // kpd = Koba
+ TRUETYPE_TAG('k', 'p', 'e', 0), // kpe = Kpelle
+ TRUETYPE_TAG('k', 'p', 'f', 0), // kpf = Komba
+ TRUETYPE_TAG('k', 'p', 'g', 0), // kpg = Kapingamarangi
+ TRUETYPE_TAG('k', 'p', 'h', 0), // kph = Kplang
+ TRUETYPE_TAG('k', 'p', 'i', 0), // kpi = Kofei
+ TRUETYPE_TAG('k', 'p', 'j', 0), // kpj = Karajá
+ TRUETYPE_TAG('k', 'p', 'k', 0), // kpk = Kpan
+ TRUETYPE_TAG('k', 'p', 'l', 0), // kpl = Kpala
+ TRUETYPE_TAG('k', 'p', 'm', 0), // kpm = Koho
+ TRUETYPE_TAG('k', 'p', 'n', 0), // kpn = Kepkiriwát
+ TRUETYPE_TAG('k', 'p', 'o', 0), // kpo = Ikposo
+ TRUETYPE_TAG('k', 'p', 'p', 0), // kpp = Paku Karen
+ TRUETYPE_TAG('k', 'p', 'q', 0), // kpq = Korupun-Sela
+ TRUETYPE_TAG('k', 'p', 'r', 0), // kpr = Korafe-Yegha
+ TRUETYPE_TAG('k', 'p', 's', 0), // kps = Tehit
+ TRUETYPE_TAG('k', 'p', 't', 0), // kpt = Karata
+ TRUETYPE_TAG('k', 'p', 'u', 0), // kpu = Kafoa
+ TRUETYPE_TAG('k', 'p', 'v', 0), // kpv = Komi-Zyrian
+ TRUETYPE_TAG('k', 'p', 'w', 0), // kpw = Kobon
+ TRUETYPE_TAG('k', 'p', 'x', 0), // kpx = Mountain Koiali
+ TRUETYPE_TAG('k', 'p', 'y', 0), // kpy = Koryak
+ TRUETYPE_TAG('k', 'p', 'z', 0), // kpz = Kupsabiny
+ TRUETYPE_TAG('k', 'q', 'a', 0), // kqa = Mum
+ TRUETYPE_TAG('k', 'q', 'b', 0), // kqb = Kovai
+ TRUETYPE_TAG('k', 'q', 'c', 0), // kqc = Doromu-Koki
+ TRUETYPE_TAG('k', 'q', 'd', 0), // kqd = Koy Sanjaq Surat
+ TRUETYPE_TAG('k', 'q', 'e', 0), // kqe = Kalagan
+ TRUETYPE_TAG('k', 'q', 'f', 0), // kqf = Kakabai
+ TRUETYPE_TAG('k', 'q', 'g', 0), // kqg = Khe
+ TRUETYPE_TAG('k', 'q', 'h', 0), // kqh = Kisankasa
+ TRUETYPE_TAG('k', 'q', 'i', 0), // kqi = Koitabu
+ TRUETYPE_TAG('k', 'q', 'j', 0), // kqj = Koromira
+ TRUETYPE_TAG('k', 'q', 'k', 0), // kqk = Kotafon Gbe
+ TRUETYPE_TAG('k', 'q', 'l', 0), // kql = Kyenele
+ TRUETYPE_TAG('k', 'q', 'm', 0), // kqm = Khisa
+ TRUETYPE_TAG('k', 'q', 'n', 0), // kqn = Kaonde
+ TRUETYPE_TAG('k', 'q', 'o', 0), // kqo = Eastern Krahn
+ TRUETYPE_TAG('k', 'q', 'p', 0), // kqp = Kimré
+ TRUETYPE_TAG('k', 'q', 'q', 0), // kqq = Krenak
+ TRUETYPE_TAG('k', 'q', 'r', 0), // kqr = Kimaragang
+ TRUETYPE_TAG('k', 'q', 's', 0), // kqs = Northern Kissi
+ TRUETYPE_TAG('k', 'q', 't', 0), // kqt = Klias River Kadazan
+ TRUETYPE_TAG('k', 'q', 'u', 0), // kqu = Seroa
+ TRUETYPE_TAG('k', 'q', 'v', 0), // kqv = Okolod
+ TRUETYPE_TAG('k', 'q', 'w', 0), // kqw = Kandas
+ TRUETYPE_TAG('k', 'q', 'x', 0), // kqx = Mser
+ TRUETYPE_TAG('k', 'q', 'y', 0), // kqy = Koorete
+ TRUETYPE_TAG('k', 'q', 'z', 0), // kqz = Korana
+ TRUETYPE_TAG('k', 'r', 'a', 0), // kra = Kumhali
+ TRUETYPE_TAG('k', 'r', 'b', 0), // krb = Karkin
+ TRUETYPE_TAG('k', 'r', 'c', 0), // krc = Karachay-Balkar
+ TRUETYPE_TAG('k', 'r', 'd', 0), // krd = Kairui-Midiki
+ TRUETYPE_TAG('k', 'r', 'e', 0), // kre = Panará
+ TRUETYPE_TAG('k', 'r', 'f', 0), // krf = Koro (Vanuatu)
+ TRUETYPE_TAG('k', 'r', 'h', 0), // krh = Kurama
+ TRUETYPE_TAG('k', 'r', 'i', 0), // kri = Krio
+ TRUETYPE_TAG('k', 'r', 'j', 0), // krj = Kinaray-A
+ TRUETYPE_TAG('k', 'r', 'k', 0), // krk = Kerek
+ TRUETYPE_TAG('k', 'r', 'l', 0), // krl = Karelian
+ TRUETYPE_TAG('k', 'r', 'm', 0), // krm = Krim
+ TRUETYPE_TAG('k', 'r', 'n', 0), // krn = Sapo
+ TRUETYPE_TAG('k', 'r', 'o', 0), // kro = Kru languages
+ TRUETYPE_TAG('k', 'r', 'p', 0), // krp = Korop
+ TRUETYPE_TAG('k', 'r', 'r', 0), // krr = Kru'ng 2
+ TRUETYPE_TAG('k', 'r', 's', 0), // krs = Gbaya (Sudan)
+ TRUETYPE_TAG('k', 'r', 't', 0), // krt = Tumari Kanuri
+ TRUETYPE_TAG('k', 'r', 'u', 0), // kru = Kurukh
+ TRUETYPE_TAG('k', 'r', 'v', 0), // krv = Kavet
+ TRUETYPE_TAG('k', 'r', 'w', 0), // krw = Western Krahn
+ TRUETYPE_TAG('k', 'r', 'x', 0), // krx = Karon
+ TRUETYPE_TAG('k', 'r', 'y', 0), // kry = Kryts
+ TRUETYPE_TAG('k', 'r', 'z', 0), // krz = Sota Kanum
+ TRUETYPE_TAG('k', 's', 'a', 0), // ksa = Shuwa-Zamani
+ TRUETYPE_TAG('k', 's', 'b', 0), // ksb = Shambala
+ TRUETYPE_TAG('k', 's', 'c', 0), // ksc = Southern Kalinga
+ TRUETYPE_TAG('k', 's', 'd', 0), // ksd = Kuanua
+ TRUETYPE_TAG('k', 's', 'e', 0), // kse = Kuni
+ TRUETYPE_TAG('k', 's', 'f', 0), // ksf = Bafia
+ TRUETYPE_TAG('k', 's', 'g', 0), // ksg = Kusaghe
+ TRUETYPE_TAG('k', 's', 'h', 0), // ksh = Kölsch
+ TRUETYPE_TAG('k', 's', 'i', 0), // ksi = Krisa
+ TRUETYPE_TAG('k', 's', 'j', 0), // ksj = Uare
+ TRUETYPE_TAG('k', 's', 'k', 0), // ksk = Kansa
+ TRUETYPE_TAG('k', 's', 'l', 0), // ksl = Kumalu
+ TRUETYPE_TAG('k', 's', 'm', 0), // ksm = Kumba
+ TRUETYPE_TAG('k', 's', 'n', 0), // ksn = Kasiguranin
+ TRUETYPE_TAG('k', 's', 'o', 0), // kso = Kofa
+ TRUETYPE_TAG('k', 's', 'p', 0), // ksp = Kaba
+ TRUETYPE_TAG('k', 's', 'q', 0), // ksq = Kwaami
+ TRUETYPE_TAG('k', 's', 'r', 0), // ksr = Borong
+ TRUETYPE_TAG('k', 's', 's', 0), // kss = Southern Kisi
+ TRUETYPE_TAG('k', 's', 't', 0), // kst = Winyé
+ TRUETYPE_TAG('k', 's', 'u', 0), // ksu = Khamyang
+ TRUETYPE_TAG('k', 's', 'v', 0), // ksv = Kusu
+ TRUETYPE_TAG('k', 's', 'w', 0), // ksw = S'gaw Karen
+ TRUETYPE_TAG('k', 's', 'x', 0), // ksx = Kedang
+ TRUETYPE_TAG('k', 's', 'y', 0), // ksy = Kharia Thar
+ TRUETYPE_TAG('k', 's', 'z', 0), // ksz = Kodaku
+ TRUETYPE_TAG('k', 't', 'a', 0), // kta = Katua
+ TRUETYPE_TAG('k', 't', 'b', 0), // ktb = Kambaata
+ TRUETYPE_TAG('k', 't', 'c', 0), // ktc = Kholok
+ TRUETYPE_TAG('k', 't', 'd', 0), // ktd = Kokata
+ TRUETYPE_TAG('k', 't', 'e', 0), // kte = Nubri
+ TRUETYPE_TAG('k', 't', 'f', 0), // ktf = Kwami
+ TRUETYPE_TAG('k', 't', 'g', 0), // ktg = Kalkutung
+ TRUETYPE_TAG('k', 't', 'h', 0), // kth = Karanga
+ TRUETYPE_TAG('k', 't', 'i', 0), // kti = North Muyu
+ TRUETYPE_TAG('k', 't', 'j', 0), // ktj = Plapo Krumen
+ TRUETYPE_TAG('k', 't', 'k', 0), // ktk = Kaniet
+ TRUETYPE_TAG('k', 't', 'l', 0), // ktl = Koroshi
+ TRUETYPE_TAG('k', 't', 'm', 0), // ktm = Kurti
+ TRUETYPE_TAG('k', 't', 'n', 0), // ktn = Karitiâna
+ TRUETYPE_TAG('k', 't', 'o', 0), // kto = Kuot
+ TRUETYPE_TAG('k', 't', 'p', 0), // ktp = Kaduo
+ TRUETYPE_TAG('k', 't', 'q', 0), // ktq = Katabaga
+ TRUETYPE_TAG('k', 't', 'r', 0), // ktr = Kota Marudu Tinagas
+ TRUETYPE_TAG('k', 't', 's', 0), // kts = South Muyu
+ TRUETYPE_TAG('k', 't', 't', 0), // ktt = Ketum
+ TRUETYPE_TAG('k', 't', 'u',
+ 0), // ktu = Kituba (Democratic Republic of Congo)
+ TRUETYPE_TAG('k', 't', 'v', 0), // ktv = Eastern Katu
+ TRUETYPE_TAG('k', 't', 'w', 0), // ktw = Kato
+ TRUETYPE_TAG('k', 't', 'x', 0), // ktx = Kaxararí
+ TRUETYPE_TAG('k', 't', 'y', 0), // kty = Kango (Bas-Uélé District)
+ TRUETYPE_TAG('k', 't', 'z', 0), // ktz = Ju/'hoan
+ TRUETYPE_TAG('k', 'u', 'b', 0), // kub = Kutep
+ TRUETYPE_TAG('k', 'u', 'c', 0), // kuc = Kwinsu
+ TRUETYPE_TAG('k', 'u', 'd', 0), // kud = 'Auhelawa
+ TRUETYPE_TAG('k', 'u', 'e', 0), // kue = Kuman
+ TRUETYPE_TAG('k', 'u', 'f', 0), // kuf = Western Katu
+ TRUETYPE_TAG('k', 'u', 'g', 0), // kug = Kupa
+ TRUETYPE_TAG('k', 'u', 'h', 0), // kuh = Kushi
+ TRUETYPE_TAG('k', 'u', 'i', 0), // kui = Kuikúro-Kalapálo
+ TRUETYPE_TAG('k', 'u', 'j', 0), // kuj = Kuria
+ TRUETYPE_TAG('k', 'u', 'k', 0), // kuk = Kepo'
+ TRUETYPE_TAG('k', 'u', 'l', 0), // kul = Kulere
+ TRUETYPE_TAG('k', 'u', 'm', 0), // kum = Kumyk
+ TRUETYPE_TAG('k', 'u', 'n', 0), // kun = Kunama
+ TRUETYPE_TAG('k', 'u', 'o', 0), // kuo = Kumukio
+ TRUETYPE_TAG('k', 'u', 'p', 0), // kup = Kunimaipa
+ TRUETYPE_TAG('k', 'u', 'q', 0), // kuq = Karipuna
+ TRUETYPE_TAG('k', 'u', 's', 0), // kus = Kusaal
+ TRUETYPE_TAG('k', 'u', 't', 0), // kut = Kutenai
+ TRUETYPE_TAG('k', 'u', 'u', 0), // kuu = Upper Kuskokwim
+ TRUETYPE_TAG('k', 'u', 'v', 0), // kuv = Kur
+ TRUETYPE_TAG('k', 'u', 'w', 0), // kuw = Kpagua
+ TRUETYPE_TAG('k', 'u', 'x', 0), // kux = Kukatja
+ TRUETYPE_TAG('k', 'u', 'y', 0), // kuy = Kuuku-Ya'u
+ TRUETYPE_TAG('k', 'u', 'z', 0), // kuz = Kunza
+ TRUETYPE_TAG('k', 'v', 'a', 0), // kva = Bagvalal
+ TRUETYPE_TAG('k', 'v', 'b', 0), // kvb = Kubu
+ TRUETYPE_TAG('k', 'v', 'c', 0), // kvc = Kove
+ TRUETYPE_TAG('k', 'v', 'd', 0), // kvd = Kui (Indonesia)
+ TRUETYPE_TAG('k', 'v', 'e', 0), // kve = Kalabakan
+ TRUETYPE_TAG('k', 'v', 'f', 0), // kvf = Kabalai
+ TRUETYPE_TAG('k', 'v', 'g', 0), // kvg = Kuni-Boazi
+ TRUETYPE_TAG('k', 'v', 'h', 0), // kvh = Komodo
+ TRUETYPE_TAG('k', 'v', 'i', 0), // kvi = Kwang
+ TRUETYPE_TAG('k', 'v', 'j', 0), // kvj = Psikye
+ TRUETYPE_TAG('k', 'v', 'k', 0), // kvk = Korean Sign Language
+ TRUETYPE_TAG('k', 'v', 'l', 0), // kvl = Brek Karen
+ TRUETYPE_TAG('k', 'v', 'm', 0), // kvm = Kendem
+ TRUETYPE_TAG('k', 'v', 'n', 0), // kvn = Border Kuna
+ TRUETYPE_TAG('k', 'v', 'o', 0), // kvo = Dobel
+ TRUETYPE_TAG('k', 'v', 'p', 0), // kvp = Kompane
+ TRUETYPE_TAG('k', 'v', 'q', 0), // kvq = Geba Karen
+ TRUETYPE_TAG('k', 'v', 'r', 0), // kvr = Kerinci
+ TRUETYPE_TAG('k', 'v', 's', 0), // kvs = Kunggara
+ TRUETYPE_TAG('k', 'v', 't', 0), // kvt = Lahta Karen
+ TRUETYPE_TAG('k', 'v', 'u', 0), // kvu = Yinbaw Karen
+ TRUETYPE_TAG('k', 'v', 'v', 0), // kvv = Kola
+ TRUETYPE_TAG('k', 'v', 'w', 0), // kvw = Wersing
+ TRUETYPE_TAG('k', 'v', 'x', 0), // kvx = Parkari Koli
+ TRUETYPE_TAG('k', 'v', 'y', 0), // kvy = Yintale Karen
+ TRUETYPE_TAG('k', 'v', 'z', 0), // kvz = Tsakwambo
+ TRUETYPE_TAG('k', 'w', 'a', 0), // kwa = Dâw
+ TRUETYPE_TAG('k', 'w', 'b', 0), // kwb = Kwa
+ TRUETYPE_TAG('k', 'w', 'c', 0), // kwc = Likwala
+ TRUETYPE_TAG('k', 'w', 'd', 0), // kwd = Kwaio
+ TRUETYPE_TAG('k', 'w', 'e', 0), // kwe = Kwerba
+ TRUETYPE_TAG('k', 'w', 'f', 0), // kwf = Kwara'ae
+ TRUETYPE_TAG('k', 'w', 'g', 0), // kwg = Sara Kaba Deme
+ TRUETYPE_TAG('k', 'w', 'h', 0), // kwh = Kowiai
+ TRUETYPE_TAG('k', 'w', 'i', 0), // kwi = Awa-Cuaiquer
+ TRUETYPE_TAG('k', 'w', 'j', 0), // kwj = Kwanga
+ TRUETYPE_TAG('k', 'w', 'k', 0), // kwk = Kwakiutl
+ TRUETYPE_TAG('k', 'w', 'l', 0), // kwl = Kofyar
+ TRUETYPE_TAG('k', 'w', 'm', 0), // kwm = Kwambi
+ TRUETYPE_TAG('k', 'w', 'n', 0), // kwn = Kwangali
+ TRUETYPE_TAG('k', 'w', 'o', 0), // kwo = Kwomtari
+ TRUETYPE_TAG('k', 'w', 'p', 0), // kwp = Kodia
+ TRUETYPE_TAG('k', 'w', 'q', 0), // kwq = Kwak
+ TRUETYPE_TAG('k', 'w', 'r', 0), // kwr = Kwer
+ TRUETYPE_TAG('k', 'w', 's', 0), // kws = Kwese
+ TRUETYPE_TAG('k', 'w', 't', 0), // kwt = Kwesten
+ TRUETYPE_TAG('k', 'w', 'u', 0), // kwu = Kwakum
+ TRUETYPE_TAG('k', 'w', 'v', 0), // kwv = Sara Kaba Náà
+ TRUETYPE_TAG('k', 'w', 'w', 0), // kww = Kwinti
+ TRUETYPE_TAG('k', 'w', 'x', 0), // kwx = Khirwar
+ TRUETYPE_TAG('k', 'w', 'y', 0), // kwy = San Salvador Kongo
+ TRUETYPE_TAG('k', 'w', 'z', 0), // kwz = Kwadi
+ TRUETYPE_TAG('k', 'x', 'a', 0), // kxa = Kairiru
+ TRUETYPE_TAG('k', 'x', 'b', 0), // kxb = Krobu
+ TRUETYPE_TAG('k', 'x', 'c', 0), // kxc = Konso
+ TRUETYPE_TAG('k', 'x', 'd', 0), // kxd = Brunei
+ TRUETYPE_TAG('k', 'x', 'e', 0), // kxe = Kakihum
+ TRUETYPE_TAG('k', 'x', 'f', 0), // kxf = Manumanaw Karen
+ TRUETYPE_TAG('k', 'x', 'h', 0), // kxh = Karo (Ethiopia)
+ TRUETYPE_TAG('k', 'x', 'i', 0), // kxi = Keningau Murut
+ TRUETYPE_TAG('k', 'x', 'j', 0), // kxj = Kulfa
+ TRUETYPE_TAG('k', 'x', 'k', 0), // kxk = Zayein Karen
+ TRUETYPE_TAG('k', 'x', 'l', 0), // kxl = Nepali Kurux
+ TRUETYPE_TAG('k', 'x', 'm', 0), // kxm = Northern Khmer
+ TRUETYPE_TAG('k', 'x', 'n', 0), // kxn = Kanowit-Tanjong Melanau
+ TRUETYPE_TAG('k', 'x', 'o', 0), // kxo = Kanoé
+ TRUETYPE_TAG('k', 'x', 'p', 0), // kxp = Wadiyara Koli
+ TRUETYPE_TAG('k', 'x', 'q', 0), // kxq = Smärky Kanum
+ TRUETYPE_TAG('k', 'x', 'r', 0), // kxr = Koro (Papua New Guinea)
+ TRUETYPE_TAG('k', 'x', 's', 0), // kxs = Kangjia
+ TRUETYPE_TAG('k', 'x', 't', 0), // kxt = Koiwat
+ TRUETYPE_TAG('k', 'x', 'u', 0), // kxu = Kui (India)
+ TRUETYPE_TAG('k', 'x', 'v', 0), // kxv = Kuvi
+ TRUETYPE_TAG('k', 'x', 'w', 0), // kxw = Konai
+ TRUETYPE_TAG('k', 'x', 'x', 0), // kxx = Likuba
+ TRUETYPE_TAG('k', 'x', 'y', 0), // kxy = Kayong
+ TRUETYPE_TAG('k', 'x', 'z', 0), // kxz = Kerewo
+ TRUETYPE_TAG('k', 'y', 'a', 0), // kya = Kwaya
+ TRUETYPE_TAG('k', 'y', 'b', 0), // kyb = Butbut Kalinga
+ TRUETYPE_TAG('k', 'y', 'c', 0), // kyc = Kyaka
+ TRUETYPE_TAG('k', 'y', 'd', 0), // kyd = Karey
+ TRUETYPE_TAG('k', 'y', 'e', 0), // kye = Krache
+ TRUETYPE_TAG('k', 'y', 'f', 0), // kyf = Kouya
+ TRUETYPE_TAG('k', 'y', 'g', 0), // kyg = Keyagana
+ TRUETYPE_TAG('k', 'y', 'h', 0), // kyh = Karok
+ TRUETYPE_TAG('k', 'y', 'i', 0), // kyi = Kiput
+ TRUETYPE_TAG('k', 'y', 'j', 0), // kyj = Karao
+ TRUETYPE_TAG('k', 'y', 'k', 0), // kyk = Kamayo
+ TRUETYPE_TAG('k', 'y', 'l', 0), // kyl = Kalapuya
+ TRUETYPE_TAG('k', 'y', 'm', 0), // kym = Kpatili
+ TRUETYPE_TAG('k', 'y', 'n', 0), // kyn = Northern Binukidnon
+ TRUETYPE_TAG('k', 'y', 'o', 0), // kyo = Kelon
+ TRUETYPE_TAG('k', 'y', 'p', 0), // kyp = Kang
+ TRUETYPE_TAG('k', 'y', 'q', 0), // kyq = Kenga
+ TRUETYPE_TAG('k', 'y', 'r', 0), // kyr = Kuruáya
+ TRUETYPE_TAG('k', 'y', 's', 0), // kys = Baram Kayan
+ TRUETYPE_TAG('k', 'y', 't', 0), // kyt = Kayagar
+ TRUETYPE_TAG('k', 'y', 'u', 0), // kyu = Western Kayah
+ TRUETYPE_TAG('k', 'y', 'v', 0), // kyv = Kayort
+ TRUETYPE_TAG('k', 'y', 'w', 0), // kyw = Kudmali
+ TRUETYPE_TAG('k', 'y', 'x', 0), // kyx = Rapoisi
+ TRUETYPE_TAG('k', 'y', 'y', 0), // kyy = Kambaira
+ TRUETYPE_TAG('k', 'y', 'z', 0), // kyz = Kayabí
+ TRUETYPE_TAG('k', 'z', 'a', 0), // kza = Western Karaboro
+ TRUETYPE_TAG('k', 'z', 'b', 0), // kzb = Kaibobo
+ TRUETYPE_TAG('k', 'z', 'c', 0), // kzc = Bondoukou Kulango
+ TRUETYPE_TAG('k', 'z', 'd', 0), // kzd = Kadai
+ TRUETYPE_TAG('k', 'z', 'e', 0), // kze = Kosena
+ TRUETYPE_TAG('k', 'z', 'f', 0), // kzf = Da'a Kaili
+ TRUETYPE_TAG('k', 'z', 'g', 0), // kzg = Kikai
+ TRUETYPE_TAG('k', 'z', 'h', 0), // kzh = Kenuzi-Dongola
+ TRUETYPE_TAG('k', 'z', 'i', 0), // kzi = Kelabit
+ TRUETYPE_TAG('k', 'z', 'j', 0), // kzj = Coastal Kadazan
+ TRUETYPE_TAG('k', 'z', 'k', 0), // kzk = Kazukuru
+ TRUETYPE_TAG('k', 'z', 'l', 0), // kzl = Kayeli
+ TRUETYPE_TAG('k', 'z', 'm', 0), // kzm = Kais
+ TRUETYPE_TAG('k', 'z', 'n', 0), // kzn = Kokola
+ TRUETYPE_TAG('k', 'z', 'o', 0), // kzo = Kaningi
+ TRUETYPE_TAG('k', 'z', 'p', 0), // kzp = Kaidipang
+ TRUETYPE_TAG('k', 'z', 'q', 0), // kzq = Kaike
+ TRUETYPE_TAG('k', 'z', 'r', 0), // kzr = Karang
+ TRUETYPE_TAG('k', 'z', 's', 0), // kzs = Sugut Dusun
+ TRUETYPE_TAG('k', 'z', 't', 0), // kzt = Tambunan Dusun
+ TRUETYPE_TAG('k', 'z', 'u', 0), // kzu = Kayupulau
+ TRUETYPE_TAG('k', 'z', 'v', 0), // kzv = Komyandaret
+ TRUETYPE_TAG('k', 'z', 'w', 0), // kzw = Karirí-Xocó
+ TRUETYPE_TAG('k', 'z', 'x', 0), // kzx = Kamarian
+ TRUETYPE_TAG('k', 'z', 'y', 0), // kzy = Kango (Tshopo District)
+ TRUETYPE_TAG('k', 'z', 'z', 0), // kzz = Kalabra
+ TRUETYPE_TAG('l', 'a', 'a', 0), // laa = Southern Subanen
+ TRUETYPE_TAG('l', 'a', 'b', 0), // lab = Linear A
+ TRUETYPE_TAG('l', 'a', 'c', 0), // lac = Lacandon
+ TRUETYPE_TAG('l', 'a', 'd', 0), // lad = Ladino
+ TRUETYPE_TAG('l', 'a', 'e', 0), // lae = Pattani
+ TRUETYPE_TAG('l', 'a', 'f', 0), // laf = Lafofa
+ TRUETYPE_TAG('l', 'a', 'g', 0), // lag = Langi
+ TRUETYPE_TAG('l', 'a', 'h', 0), // lah = Lahnda
+ TRUETYPE_TAG('l', 'a', 'i', 0), // lai = Lambya
+ TRUETYPE_TAG('l', 'a', 'j', 0), // laj = Lango (Uganda)
+ TRUETYPE_TAG('l', 'a', 'k', 0), // lak = Laka (Nigeria)
+ TRUETYPE_TAG('l', 'a', 'l', 0), // lal = Lalia
+ TRUETYPE_TAG('l', 'a', 'm', 0), // lam = Lamba
+ TRUETYPE_TAG('l', 'a', 'n', 0), // lan = Laru
+ TRUETYPE_TAG('l', 'a', 'p', 0), // lap = Laka (Chad)
+ TRUETYPE_TAG('l', 'a', 'q', 0), // laq = Qabiao
+ TRUETYPE_TAG('l', 'a', 'r', 0), // lar = Larteh
+ TRUETYPE_TAG('l', 'a', 's', 0), // las = Lama (Togo)
+ TRUETYPE_TAG('l', 'a', 'u', 0), // lau = Laba
+ TRUETYPE_TAG('l', 'a', 'w', 0), // law = Lauje
+ TRUETYPE_TAG('l', 'a', 'x', 0), // lax = Tiwa
+ TRUETYPE_TAG('l', 'a', 'y', 0), // lay = Lama (Myanmar)
+ TRUETYPE_TAG('l', 'a', 'z', 0), // laz = Aribwatsa
+ TRUETYPE_TAG('l', 'b', 'a', 0), // lba = Lui
+ TRUETYPE_TAG('l', 'b', 'b', 0), // lbb = Label
+ TRUETYPE_TAG('l', 'b', 'c', 0), // lbc = Lakkia
+ TRUETYPE_TAG('l', 'b', 'e', 0), // lbe = Lak
+ TRUETYPE_TAG('l', 'b', 'f', 0), // lbf = Tinani
+ TRUETYPE_TAG('l', 'b', 'g', 0), // lbg = Laopang
+ TRUETYPE_TAG('l', 'b', 'i', 0), // lbi = La'bi
+ TRUETYPE_TAG('l', 'b', 'j', 0), // lbj = Ladakhi
+ TRUETYPE_TAG('l', 'b', 'k', 0), // lbk = Central Bontok
+ TRUETYPE_TAG('l', 'b', 'l', 0), // lbl = Libon Bikol
+ TRUETYPE_TAG('l', 'b', 'm', 0), // lbm = Lodhi
+ TRUETYPE_TAG('l', 'b', 'n', 0), // lbn = Lamet
+ TRUETYPE_TAG('l', 'b', 'o', 0), // lbo = Laven
+ TRUETYPE_TAG('l', 'b', 'q', 0), // lbq = Wampar
+ TRUETYPE_TAG('l', 'b', 'r', 0), // lbr = Northern Lorung
+ TRUETYPE_TAG('l', 'b', 's', 0), // lbs = Libyan Sign Language
+ TRUETYPE_TAG('l', 'b', 't', 0), // lbt = Lachi
+ TRUETYPE_TAG('l', 'b', 'u', 0), // lbu = Labu
+ TRUETYPE_TAG('l', 'b', 'v', 0), // lbv = Lavatbura-Lamusong
+ TRUETYPE_TAG('l', 'b', 'w', 0), // lbw = Tolaki
+ TRUETYPE_TAG('l', 'b', 'x', 0), // lbx = Lawangan
+ TRUETYPE_TAG('l', 'b', 'y', 0), // lby = Lamu-Lamu
+ TRUETYPE_TAG('l', 'b', 'z', 0), // lbz = Lardil
+ TRUETYPE_TAG('l', 'c', 'c', 0), // lcc = Legenyem
+ TRUETYPE_TAG('l', 'c', 'd', 0), // lcd = Lola
+ TRUETYPE_TAG('l', 'c', 'e', 0), // lce = Loncong
+ TRUETYPE_TAG('l', 'c', 'f', 0), // lcf = Lubu
+ TRUETYPE_TAG('l', 'c', 'h', 0), // lch = Luchazi
+ TRUETYPE_TAG('l', 'c', 'l', 0), // lcl = Lisela
+ TRUETYPE_TAG('l', 'c', 'm', 0), // lcm = Tungag
+ TRUETYPE_TAG('l', 'c', 'p', 0), // lcp = Western Lawa
+ TRUETYPE_TAG('l', 'c', 'q', 0), // lcq = Luhu
+ TRUETYPE_TAG('l', 'c', 's', 0), // lcs = Lisabata-Nuniali
+ TRUETYPE_TAG('l', 'd', 'b', 0), // ldb = Idun
+ TRUETYPE_TAG('l', 'd', 'd', 0), // ldd = Luri
+ TRUETYPE_TAG('l', 'd', 'g', 0), // ldg = Lenyima
+ TRUETYPE_TAG('l', 'd', 'h', 0), // ldh = Lamja-Dengsa-Tola
+ TRUETYPE_TAG('l', 'd', 'i', 0), // ldi = Laari
+ TRUETYPE_TAG('l', 'd', 'j', 0), // ldj = Lemoro
+ TRUETYPE_TAG('l', 'd', 'k', 0), // ldk = Leelau
+ TRUETYPE_TAG('l', 'd', 'l', 0), // ldl = Kaan
+ TRUETYPE_TAG('l', 'd', 'm', 0), // ldm = Landoma
+ TRUETYPE_TAG('l', 'd', 'n', 0), // ldn = Láadan
+ TRUETYPE_TAG('l', 'd', 'o', 0), // ldo = Loo
+ TRUETYPE_TAG('l', 'd', 'p', 0), // ldp = Tso
+ TRUETYPE_TAG('l', 'd', 'q', 0), // ldq = Lufu
+ TRUETYPE_TAG('l', 'e', 'a', 0), // lea = Lega-Shabunda
+ TRUETYPE_TAG('l', 'e', 'b', 0), // leb = Lala-Bisa
+ TRUETYPE_TAG('l', 'e', 'c', 0), // lec = Leco
+ TRUETYPE_TAG('l', 'e', 'd', 0), // led = Lendu
+ TRUETYPE_TAG('l', 'e', 'e', 0), // lee = Lyélé
+ TRUETYPE_TAG('l', 'e', 'f', 0), // lef = Lelemi
+ TRUETYPE_TAG('l', 'e', 'g', 0), // leg = Lengua
+ TRUETYPE_TAG('l', 'e', 'h', 0), // leh = Lenje
+ TRUETYPE_TAG('l', 'e', 'i', 0), // lei = Lemio
+ TRUETYPE_TAG('l', 'e', 'j', 0), // lej = Lengola
+ TRUETYPE_TAG('l', 'e', 'k', 0), // lek = Leipon
+ TRUETYPE_TAG('l', 'e', 'l',
+ 0), // lel = Lele (Democratic Republic of Congo)
+ TRUETYPE_TAG('l', 'e', 'm', 0), // lem = Nomaande
+ TRUETYPE_TAG('l', 'e', 'n', 0), // len = Lenca
+ TRUETYPE_TAG('l', 'e', 'o', 0), // leo = Leti (Cameroon)
+ TRUETYPE_TAG('l', 'e', 'p', 0), // lep = Lepcha
+ TRUETYPE_TAG('l', 'e', 'q', 0), // leq = Lembena
+ TRUETYPE_TAG('l', 'e', 'r', 0), // ler = Lenkau
+ TRUETYPE_TAG('l', 'e', 's', 0), // les = Lese
+ TRUETYPE_TAG('l', 'e', 't', 0), // let = Lesing-Gelimi
+ TRUETYPE_TAG('l', 'e', 'u', 0), // leu = Kara (Papua New Guinea)
+ TRUETYPE_TAG('l', 'e', 'v', 0), // lev = Lamma
+ TRUETYPE_TAG('l', 'e', 'w', 0), // lew = Ledo Kaili
+ TRUETYPE_TAG('l', 'e', 'x', 0), // lex = Luang
+ TRUETYPE_TAG('l', 'e', 'y', 0), // ley = Lemolang
+ TRUETYPE_TAG('l', 'e', 'z', 0), // lez = Lezghian
+ TRUETYPE_TAG('l', 'f', 'a', 0), // lfa = Lefa
+ TRUETYPE_TAG('l', 'f', 'n', 0), // lfn = Lingua Franca Nova
+ TRUETYPE_TAG('l', 'g', 'a', 0), // lga = Lungga
+ TRUETYPE_TAG('l', 'g', 'b', 0), // lgb = Laghu
+ TRUETYPE_TAG('l', 'g', 'g', 0), // lgg = Lugbara
+ TRUETYPE_TAG('l', 'g', 'h', 0), // lgh = Laghuu
+ TRUETYPE_TAG('l', 'g', 'i', 0), // lgi = Lengilu
+ TRUETYPE_TAG('l', 'g', 'k', 0), // lgk = Lingarak
+ TRUETYPE_TAG('l', 'g', 'l', 0), // lgl = Wala
+ TRUETYPE_TAG('l', 'g', 'm', 0), // lgm = Lega-Mwenga
+ TRUETYPE_TAG('l', 'g', 'n', 0), // lgn = Opuuo
+ TRUETYPE_TAG('l', 'g', 'q', 0), // lgq = Logba
+ TRUETYPE_TAG('l', 'g', 'r', 0), // lgr = Lengo
+ TRUETYPE_TAG('l', 'g', 't', 0), // lgt = Pahi
+ TRUETYPE_TAG('l', 'g', 'u', 0), // lgu = Longgu
+ TRUETYPE_TAG('l', 'g', 'z', 0), // lgz = Ligenza
+ TRUETYPE_TAG('l', 'h', 'a', 0), // lha = Laha (Viet Nam)
+ TRUETYPE_TAG('l', 'h', 'h', 0), // lhh = Laha (Indonesia)
+ TRUETYPE_TAG('l', 'h', 'i', 0), // lhi = Lahu Shi
+ TRUETYPE_TAG('l', 'h', 'l', 0), // lhl = Lahul Lohar
+ TRUETYPE_TAG('l', 'h', 'm', 0), // lhm = Lhomi
+ TRUETYPE_TAG('l', 'h', 'n', 0), // lhn = Lahanan
+ TRUETYPE_TAG('l', 'h', 'p', 0), // lhp = Lhokpu
+ TRUETYPE_TAG('l', 'h', 's', 0), // lhs = Mlahsö
+ TRUETYPE_TAG('l', 'h', 't', 0), // lht = Lo-Toga
+ TRUETYPE_TAG('l', 'h', 'u', 0), // lhu = Lahu
+ TRUETYPE_TAG('l', 'i', 'a', 0), // lia = West-Central Limba
+ TRUETYPE_TAG('l', 'i', 'b', 0), // lib = Likum
+ TRUETYPE_TAG('l', 'i', 'c', 0), // lic = Hlai
+ TRUETYPE_TAG('l', 'i', 'd', 0), // lid = Nyindrou
+ TRUETYPE_TAG('l', 'i', 'e', 0), // lie = Likila
+ TRUETYPE_TAG('l', 'i', 'f', 0), // lif = Limbu
+ TRUETYPE_TAG('l', 'i', 'g', 0), // lig = Ligbi
+ TRUETYPE_TAG('l', 'i', 'h', 0), // lih = Lihir
+ TRUETYPE_TAG('l', 'i', 'i', 0), // lii = Lingkhim
+ TRUETYPE_TAG('l', 'i', 'j', 0), // lij = Ligurian
+ TRUETYPE_TAG('l', 'i', 'k', 0), // lik = Lika
+ TRUETYPE_TAG('l', 'i', 'l', 0), // lil = Lillooet
+ TRUETYPE_TAG('l', 'i', 'o', 0), // lio = Liki
+ TRUETYPE_TAG('l', 'i', 'p', 0), // lip = Sekpele
+ TRUETYPE_TAG('l', 'i', 'q', 0), // liq = Libido
+ TRUETYPE_TAG('l', 'i', 'r', 0), // lir = Liberian English
+ TRUETYPE_TAG('l', 'i', 's', 0), // lis = Lisu
+ TRUETYPE_TAG('l', 'i', 'u', 0), // liu = Logorik
+ TRUETYPE_TAG('l', 'i', 'v', 0), // liv = Liv
+ TRUETYPE_TAG('l', 'i', 'w', 0), // liw = Col
+ TRUETYPE_TAG('l', 'i', 'x', 0), // lix = Liabuku
+ TRUETYPE_TAG('l', 'i', 'y', 0), // liy = Banda-Bambari
+ TRUETYPE_TAG('l', 'i', 'z', 0), // liz = Libinza
+ TRUETYPE_TAG('l', 'j', 'e', 0), // lje = Rampi
+ TRUETYPE_TAG('l', 'j', 'i', 0), // lji = Laiyolo
+ TRUETYPE_TAG('l', 'j', 'l', 0), // ljl = Li'o
+ TRUETYPE_TAG('l', 'j', 'p', 0), // ljp = Lampung Api
+ TRUETYPE_TAG('l', 'k', 'a', 0), // lka = Lakalei
+ TRUETYPE_TAG('l', 'k', 'b', 0), // lkb = Kabras
+ TRUETYPE_TAG('l', 'k', 'c', 0), // lkc = Kucong
+ TRUETYPE_TAG('l', 'k', 'd', 0), // lkd = Lakondê
+ TRUETYPE_TAG('l', 'k', 'e', 0), // lke = Kenyi
+ TRUETYPE_TAG('l', 'k', 'h', 0), // lkh = Lakha
+ TRUETYPE_TAG('l', 'k', 'i', 0), // lki = Laki
+ TRUETYPE_TAG('l', 'k', 'j', 0), // lkj = Remun
+ TRUETYPE_TAG('l', 'k', 'l', 0), // lkl = Laeko-Libuat
+ TRUETYPE_TAG('l', 'k', 'n', 0), // lkn = Lakon
+ TRUETYPE_TAG('l', 'k', 'o', 0), // lko = Khayo
+ TRUETYPE_TAG('l', 'k', 'r', 0), // lkr = Päri
+ TRUETYPE_TAG('l', 'k', 's', 0), // lks = Kisa
+ TRUETYPE_TAG('l', 'k', 't', 0), // lkt = Lakota
+ TRUETYPE_TAG('l', 'k', 'y', 0), // lky = Lokoya
+ TRUETYPE_TAG('l', 'l', 'a', 0), // lla = Lala-Roba
+ TRUETYPE_TAG('l', 'l', 'b', 0), // llb = Lolo
+ TRUETYPE_TAG('l', 'l', 'c', 0), // llc = Lele (Guinea)
+ TRUETYPE_TAG('l', 'l', 'd', 0), // lld = Ladin
+ TRUETYPE_TAG('l', 'l', 'e', 0), // lle = Lele (Papua New Guinea)
+ TRUETYPE_TAG('l', 'l', 'f', 0), // llf = Hermit
+ TRUETYPE_TAG('l', 'l', 'g', 0), // llg = Lole
+ TRUETYPE_TAG('l', 'l', 'h', 0), // llh = Lamu
+ TRUETYPE_TAG('l', 'l', 'i', 0), // lli = Teke-Laali
+ TRUETYPE_TAG('l', 'l', 'k', 0), // llk = Lelak
+ TRUETYPE_TAG('l', 'l', 'l', 0), // lll = Lilau
+ TRUETYPE_TAG('l', 'l', 'm', 0), // llm = Lasalimu
+ TRUETYPE_TAG('l', 'l', 'n', 0), // lln = Lele (Chad)
+ TRUETYPE_TAG('l', 'l', 'o', 0), // llo = Khlor
+ TRUETYPE_TAG('l', 'l', 'p', 0), // llp = North Efate
+ TRUETYPE_TAG('l', 'l', 'q', 0), // llq = Lolak
+ TRUETYPE_TAG('l', 'l', 's', 0), // lls = Lithuanian Sign Language
+ TRUETYPE_TAG('l', 'l', 'u', 0), // llu = Lau
+ TRUETYPE_TAG('l', 'l', 'x', 0), // llx = Lauan
+ TRUETYPE_TAG('l', 'm', 'a', 0), // lma = East Limba
+ TRUETYPE_TAG('l', 'm', 'b', 0), // lmb = Merei
+ TRUETYPE_TAG('l', 'm', 'c', 0), // lmc = Limilngan
+ TRUETYPE_TAG('l', 'm', 'd', 0), // lmd = Lumun
+ TRUETYPE_TAG('l', 'm', 'e', 0), // lme = Pévé
+ TRUETYPE_TAG('l', 'm', 'f', 0), // lmf = South Lembata
+ TRUETYPE_TAG('l', 'm', 'g', 0), // lmg = Lamogai
+ TRUETYPE_TAG('l', 'm', 'h', 0), // lmh = Lambichhong
+ TRUETYPE_TAG('l', 'm', 'i', 0), // lmi = Lombi
+ TRUETYPE_TAG('l', 'm', 'j', 0), // lmj = West Lembata
+ TRUETYPE_TAG('l', 'm', 'k', 0), // lmk = Lamkang
+ TRUETYPE_TAG('l', 'm', 'l', 0), // lml = Hano
+ TRUETYPE_TAG('l', 'm', 'm', 0), // lmm = Lamam
+ TRUETYPE_TAG('l', 'm', 'n', 0), // lmn = Lambadi
+ TRUETYPE_TAG('l', 'm', 'o', 0), // lmo = Lombard
+ TRUETYPE_TAG('l', 'm', 'p', 0), // lmp = Limbum
+ TRUETYPE_TAG('l', 'm', 'q', 0), // lmq = Lamatuka
+ TRUETYPE_TAG('l', 'm', 'r', 0), // lmr = Lamalera
+ TRUETYPE_TAG('l', 'm', 'u', 0), // lmu = Lamenu
+ TRUETYPE_TAG('l', 'm', 'v', 0), // lmv = Lomaiviti
+ TRUETYPE_TAG('l', 'm', 'w', 0), // lmw = Lake Miwok
+ TRUETYPE_TAG('l', 'm', 'x', 0), // lmx = Laimbue
+ TRUETYPE_TAG('l', 'm', 'y', 0), // lmy = Lamboya
+ TRUETYPE_TAG('l', 'm', 'z', 0), // lmz = Lumbee
+ TRUETYPE_TAG('l', 'n', 'a', 0), // lna = Langbashe
+ TRUETYPE_TAG('l', 'n', 'b', 0), // lnb = Mbalanhu
+ TRUETYPE_TAG('l', 'n', 'd', 0), // lnd = Lundayeh
+ TRUETYPE_TAG('l', 'n', 'g', 0), // lng = Langobardic
+ TRUETYPE_TAG('l', 'n', 'h', 0), // lnh = Lanoh
+ TRUETYPE_TAG('l', 'n', 'i', 0), // lni = Daantanai'
+ TRUETYPE_TAG('l', 'n', 'j', 0), // lnj = Leningitij
+ TRUETYPE_TAG('l', 'n', 'l', 0), // lnl = South Central Banda
+ TRUETYPE_TAG('l', 'n', 'm', 0), // lnm = Langam
+ TRUETYPE_TAG('l', 'n', 'n', 0), // lnn = Lorediakarkar
+ TRUETYPE_TAG('l', 'n', 'o', 0), // lno = Lango (Sudan)
+ TRUETYPE_TAG('l', 'n', 's', 0), // lns = Lamnso'
+ TRUETYPE_TAG('l', 'n', 'u', 0), // lnu = Longuda
+ TRUETYPE_TAG('l', 'n', 'z', 0), // lnz = Lonzo
+ TRUETYPE_TAG('l', 'o', 'a', 0), // loa = Loloda
+ TRUETYPE_TAG('l', 'o', 'b', 0), // lob = Lobi
+ TRUETYPE_TAG('l', 'o', 'c', 0), // loc = Inonhan
+ TRUETYPE_TAG('l', 'o', 'e', 0), // loe = Saluan
+ TRUETYPE_TAG('l', 'o', 'f', 0), // lof = Logol
+ TRUETYPE_TAG('l', 'o', 'g', 0), // log = Logo
+ TRUETYPE_TAG('l', 'o', 'h', 0), // loh = Narim
+ TRUETYPE_TAG('l', 'o', 'i', 0), // loi = Loma (Côte d'Ivoire)
+ TRUETYPE_TAG('l', 'o', 'j', 0), // loj = Lou
+ TRUETYPE_TAG('l', 'o', 'k', 0), // lok = Loko
+ TRUETYPE_TAG('l', 'o', 'l', 0), // lol = Mongo
+ TRUETYPE_TAG('l', 'o', 'm', 0), // lom = Loma (Liberia)
+ TRUETYPE_TAG('l', 'o', 'n', 0), // lon = Malawi Lomwe
+ TRUETYPE_TAG('l', 'o', 'o', 0), // loo = Lombo
+ TRUETYPE_TAG('l', 'o', 'p', 0), // lop = Lopa
+ TRUETYPE_TAG('l', 'o', 'q', 0), // loq = Lobala
+ TRUETYPE_TAG('l', 'o', 'r', 0), // lor = Téén
+ TRUETYPE_TAG('l', 'o', 's', 0), // los = Loniu
+ TRUETYPE_TAG('l', 'o', 't', 0), // lot = Otuho
+ TRUETYPE_TAG('l', 'o', 'u', 0), // lou = Louisiana Creole French
+ TRUETYPE_TAG('l', 'o', 'v', 0), // lov = Lopi
+ TRUETYPE_TAG('l', 'o', 'w', 0), // low = Tampias Lobu
+ TRUETYPE_TAG('l', 'o', 'x', 0), // lox = Loun
+ TRUETYPE_TAG('l', 'o', 'y', 0), // loy = Lowa
+ TRUETYPE_TAG('l', 'o', 'z', 0), // loz = Lozi
+ TRUETYPE_TAG('l', 'p', 'a', 0), // lpa = Lelepa
+ TRUETYPE_TAG('l', 'p', 'e', 0), // lpe = Lepki
+ TRUETYPE_TAG('l', 'p', 'n', 0), // lpn = Long Phuri Naga
+ TRUETYPE_TAG('l', 'p', 'o', 0), // lpo = Lipo
+ TRUETYPE_TAG('l', 'p', 'x', 0), // lpx = Lopit
+ TRUETYPE_TAG('l', 'r', 'a', 0), // lra = Rara Bakati'
+ TRUETYPE_TAG('l', 'r', 'c', 0), // lrc = Northern Luri
+ TRUETYPE_TAG('l', 'r', 'e', 0), // lre = Laurentian
+ TRUETYPE_TAG('l', 'r', 'g', 0), // lrg = Laragia
+ TRUETYPE_TAG('l', 'r', 'i', 0), // lri = Marachi
+ TRUETYPE_TAG('l', 'r', 'k', 0), // lrk = Loarki
+ TRUETYPE_TAG('l', 'r', 'l', 0), // lrl = Lari
+ TRUETYPE_TAG('l', 'r', 'm', 0), // lrm = Marama
+ TRUETYPE_TAG('l', 'r', 'n', 0), // lrn = Lorang
+ TRUETYPE_TAG('l', 'r', 'o', 0), // lro = Laro
+ TRUETYPE_TAG('l', 'r', 'r', 0), // lrr = Southern Lorung
+ TRUETYPE_TAG('l', 'r', 't', 0), // lrt = Larantuka Malay
+ TRUETYPE_TAG('l', 'r', 'v', 0), // lrv = Larevat
+ TRUETYPE_TAG('l', 'r', 'z', 0), // lrz = Lemerig
+ TRUETYPE_TAG('l', 's', 'a', 0), // lsa = Lasgerdi
+ TRUETYPE_TAG('l', 's', 'd', 0), // lsd = Lishana Deni
+ TRUETYPE_TAG('l', 's', 'e', 0), // lse = Lusengo
+ TRUETYPE_TAG('l', 's', 'g', 0), // lsg = Lyons Sign Language
+ TRUETYPE_TAG('l', 's', 'h', 0), // lsh = Lish
+ TRUETYPE_TAG('l', 's', 'i', 0), // lsi = Lashi
+ TRUETYPE_TAG('l', 's', 'l', 0), // lsl = Latvian Sign Language
+ TRUETYPE_TAG('l', 's', 'm', 0), // lsm = Saamia
+ TRUETYPE_TAG('l', 's', 'o', 0), // lso = Laos Sign Language
+ TRUETYPE_TAG('l', 's', 'p', 0), // lsp = Panamanian Sign Language
+ TRUETYPE_TAG('l', 's', 'r', 0), // lsr = Aruop
+ TRUETYPE_TAG('l', 's', 's', 0), // lss = Lasi
+ TRUETYPE_TAG('l', 's', 't', 0), // lst = Trinidad and Tobago Sign Language
+ TRUETYPE_TAG('l', 's', 'y', 0), // lsy = Mauritian Sign Language
+ TRUETYPE_TAG('l', 't', 'c', 0), // ltc = Late Middle Chinese
+ TRUETYPE_TAG('l', 't', 'g', 0), // ltg = Latgalian
+ TRUETYPE_TAG('l', 't', 'i', 0), // lti = Leti (Indonesia)
+ TRUETYPE_TAG('l', 't', 'n', 0), // ltn = Latundê
+ TRUETYPE_TAG('l', 't', 'o', 0), // lto = Tsotso
+ TRUETYPE_TAG('l', 't', 's', 0), // lts = Tachoni
+ TRUETYPE_TAG('l', 't', 'u', 0), // ltu = Latu
+ TRUETYPE_TAG('l', 'u', 'a', 0), // lua = Luba-Lulua
+ TRUETYPE_TAG('l', 'u', 'c', 0), // luc = Aringa
+ TRUETYPE_TAG('l', 'u', 'd', 0), // lud = Ludian
+ TRUETYPE_TAG('l', 'u', 'e', 0), // lue = Luvale
+ TRUETYPE_TAG('l', 'u', 'f', 0), // luf = Laua
+ TRUETYPE_TAG('l', 'u', 'i', 0), // lui = Luiseno
+ TRUETYPE_TAG('l', 'u', 'j', 0), // luj = Luna
+ TRUETYPE_TAG('l', 'u', 'k', 0), // luk = Lunanakha
+ TRUETYPE_TAG('l', 'u', 'l', 0), // lul = Olu'bo
+ TRUETYPE_TAG('l', 'u', 'm', 0), // lum = Luimbi
+ TRUETYPE_TAG('l', 'u', 'n', 0), // lun = Lunda
+ TRUETYPE_TAG('l', 'u', 'o', 0), // luo = Luo (Kenya and Tanzania)
+ TRUETYPE_TAG('l', 'u', 'p', 0), // lup = Lumbu
+ TRUETYPE_TAG('l', 'u', 'q', 0), // luq = Lucumi
+ TRUETYPE_TAG('l', 'u', 'r', 0), // lur = Laura
+ TRUETYPE_TAG('l', 'u', 's', 0), // lus = Lushai
+ TRUETYPE_TAG('l', 'u', 't', 0), // lut = Lushootseed
+ TRUETYPE_TAG('l', 'u', 'u', 0), // luu = Lumba-Yakkha
+ TRUETYPE_TAG('l', 'u', 'v', 0), // luv = Luwati
+ TRUETYPE_TAG('l', 'u', 'w', 0), // luw = Luo (Cameroon)
+ TRUETYPE_TAG('l', 'u', 'y', 0), // luy = Luyia
+ TRUETYPE_TAG('l', 'u', 'z', 0), // luz = Southern Luri
+ TRUETYPE_TAG('l', 'v', 'a', 0), // lva = Maku'a
+ TRUETYPE_TAG('l', 'v', 'k', 0), // lvk = Lavukaleve
+ TRUETYPE_TAG('l', 'v', 's', 0), // lvs = Standard Latvian
+ TRUETYPE_TAG('l', 'v', 'u', 0), // lvu = Levuka
+ TRUETYPE_TAG('l', 'w', 'a', 0), // lwa = Lwalu
+ TRUETYPE_TAG('l', 'w', 'e', 0), // lwe = Lewo Eleng
+ TRUETYPE_TAG('l', 'w', 'g', 0), // lwg = Wanga
+ TRUETYPE_TAG('l', 'w', 'h', 0), // lwh = White Lachi
+ TRUETYPE_TAG('l', 'w', 'l', 0), // lwl = Eastern Lawa
+ TRUETYPE_TAG('l', 'w', 'm', 0), // lwm = Laomian
+ TRUETYPE_TAG('l', 'w', 'o', 0), // lwo = Luwo
+ TRUETYPE_TAG('l', 'w', 't', 0), // lwt = Lewotobi
+ TRUETYPE_TAG('l', 'w', 'w', 0), // lww = Lewo
+ TRUETYPE_TAG('l', 'y', 'a', 0), // lya = Layakha
+ TRUETYPE_TAG('l', 'y', 'g', 0), // lyg = Lyngngam
+ TRUETYPE_TAG('l', 'y', 'n', 0), // lyn = Luyana
+ TRUETYPE_TAG('l', 'z', 'h', 0), // lzh = Literary Chinese
+ TRUETYPE_TAG('l', 'z', 'l', 0), // lzl = Litzlitz
+ TRUETYPE_TAG('l', 'z', 'n', 0), // lzn = Leinong Naga
+ TRUETYPE_TAG('l', 'z', 'z', 0), // lzz = Laz
+ TRUETYPE_TAG('m', 'a', 'a', 0), // maa = San Jerónimo Tecóatl Mazatec
+ TRUETYPE_TAG('m', 'a', 'b', 0), // mab = Yutanduchi Mixtec
+ TRUETYPE_TAG('m', 'a', 'd', 0), // mad = Madurese
+ TRUETYPE_TAG('m', 'a', 'e', 0), // mae = Bo-Rukul
+ TRUETYPE_TAG('m', 'a', 'f', 0), // maf = Mafa
+ TRUETYPE_TAG('m', 'a', 'g', 0), // mag = Magahi
+ TRUETYPE_TAG('m', 'a', 'i', 0), // mai = Maithili
+ TRUETYPE_TAG('m', 'a', 'j', 0), // maj = Jalapa De Díaz Mazatec
+ TRUETYPE_TAG('m', 'a', 'k', 0), // mak = Makasar
+ TRUETYPE_TAG('m', 'a', 'm', 0), // mam = Mam
+ TRUETYPE_TAG('m', 'a', 'n', 0), // man = Mandingo
+ TRUETYPE_TAG('m', 'a', 'p', 0), // map = Austronesian languages
+ TRUETYPE_TAG('m', 'a', 'q', 0), // maq = Chiquihuitlán Mazatec
+ TRUETYPE_TAG('m', 'a', 's', 0), // mas = Masai
+ TRUETYPE_TAG('m', 'a', 't', 0), // mat = San Francisco Matlatzinca
+ TRUETYPE_TAG('m', 'a', 'u', 0), // mau = Huautla Mazatec
+ TRUETYPE_TAG('m', 'a', 'v', 0), // mav = Sateré-Mawé
+ TRUETYPE_TAG('m', 'a', 'w', 0), // maw = Mampruli
+ TRUETYPE_TAG('m', 'a', 'x', 0), // max = North Moluccan Malay
+ TRUETYPE_TAG('m', 'a', 'z', 0), // maz = Central Mazahua
+ TRUETYPE_TAG('m', 'b', 'a', 0), // mba = Higaonon
+ TRUETYPE_TAG('m', 'b', 'b', 0), // mbb = Western Bukidnon Manobo
+ TRUETYPE_TAG('m', 'b', 'c', 0), // mbc = Macushi
+ TRUETYPE_TAG('m', 'b', 'd', 0), // mbd = Dibabawon Manobo
+ TRUETYPE_TAG('m', 'b', 'e', 0), // mbe = Molale
+ TRUETYPE_TAG('m', 'b', 'f', 0), // mbf = Baba Malay
+ TRUETYPE_TAG('m', 'b', 'h', 0), // mbh = Mangseng
+ TRUETYPE_TAG('m', 'b', 'i', 0), // mbi = Ilianen Manobo
+ TRUETYPE_TAG('m', 'b', 'j', 0), // mbj = Nadëb
+ TRUETYPE_TAG('m', 'b', 'k', 0), // mbk = Malol
+ TRUETYPE_TAG('m', 'b', 'l', 0), // mbl = Maxakalí
+ TRUETYPE_TAG('m', 'b', 'm', 0), // mbm = Ombamba
+ TRUETYPE_TAG('m', 'b', 'n', 0), // mbn = Macaguán
+ TRUETYPE_TAG('m', 'b', 'o', 0), // mbo = Mbo (Cameroon)
+ TRUETYPE_TAG('m', 'b', 'p', 0), // mbp = Malayo
+ TRUETYPE_TAG('m', 'b', 'q', 0), // mbq = Maisin
+ TRUETYPE_TAG('m', 'b', 'r', 0), // mbr = Nukak Makú
+ TRUETYPE_TAG('m', 'b', 's', 0), // mbs = Sarangani Manobo
+ TRUETYPE_TAG('m', 'b', 't', 0), // mbt = Matigsalug Manobo
+ TRUETYPE_TAG('m', 'b', 'u', 0), // mbu = Mbula-Bwazza
+ TRUETYPE_TAG('m', 'b', 'v', 0), // mbv = Mbulungish
+ TRUETYPE_TAG('m', 'b', 'w', 0), // mbw = Maring
+ TRUETYPE_TAG('m', 'b', 'x', 0), // mbx = Mari (East Sepik Province)
+ TRUETYPE_TAG('m', 'b', 'y', 0), // mby = Memoni
+ TRUETYPE_TAG('m', 'b', 'z', 0), // mbz = Amoltepec Mixtec
+ TRUETYPE_TAG('m', 'c', 'a', 0), // mca = Maca
+ TRUETYPE_TAG('m', 'c', 'b', 0), // mcb = Machiguenga
+ TRUETYPE_TAG('m', 'c', 'c', 0), // mcc = Bitur
+ TRUETYPE_TAG('m', 'c', 'd', 0), // mcd = Sharanahua
+ TRUETYPE_TAG('m', 'c', 'e', 0), // mce = Itundujia Mixtec
+ TRUETYPE_TAG('m', 'c', 'f', 0), // mcf = Matsés
+ TRUETYPE_TAG('m', 'c', 'g', 0), // mcg = Mapoyo
+ TRUETYPE_TAG('m', 'c', 'h', 0), // mch = Maquiritari
+ TRUETYPE_TAG('m', 'c', 'i', 0), // mci = Mese
+ TRUETYPE_TAG('m', 'c', 'j', 0), // mcj = Mvanip
+ TRUETYPE_TAG('m', 'c', 'k', 0), // mck = Mbunda
+ TRUETYPE_TAG('m', 'c', 'l', 0), // mcl = Macaguaje
+ TRUETYPE_TAG('m', 'c', 'm', 0), // mcm = Malaccan Creole Portuguese
+ TRUETYPE_TAG('m', 'c', 'n', 0), // mcn = Masana
+ TRUETYPE_TAG('m', 'c', 'o', 0), // mco = Coatlán Mixe
+ TRUETYPE_TAG('m', 'c', 'p', 0), // mcp = Makaa
+ TRUETYPE_TAG('m', 'c', 'q', 0), // mcq = Ese
+ TRUETYPE_TAG('m', 'c', 'r', 0), // mcr = Menya
+ TRUETYPE_TAG('m', 'c', 's', 0), // mcs = Mambai
+ TRUETYPE_TAG('m', 'c', 't', 0), // mct = Mengisa
+ TRUETYPE_TAG('m', 'c', 'u', 0), // mcu = Cameroon Mambila
+ TRUETYPE_TAG('m', 'c', 'v', 0), // mcv = Minanibai
+ TRUETYPE_TAG('m', 'c', 'w', 0), // mcw = Mawa (Chad)
+ TRUETYPE_TAG('m', 'c', 'x', 0), // mcx = Mpiemo
+ TRUETYPE_TAG('m', 'c', 'y', 0), // mcy = South Watut
+ TRUETYPE_TAG('m', 'c', 'z', 0), // mcz = Mawan
+ TRUETYPE_TAG('m', 'd', 'a', 0), // mda = Mada (Nigeria)
+ TRUETYPE_TAG('m', 'd', 'b', 0), // mdb = Morigi
+ TRUETYPE_TAG('m', 'd', 'c', 0), // mdc = Male (Papua New Guinea)
+ TRUETYPE_TAG('m', 'd', 'd', 0), // mdd = Mbum
+ TRUETYPE_TAG('m', 'd', 'e', 0), // mde = Maba (Chad)
+ TRUETYPE_TAG('m', 'd', 'f', 0), // mdf = Moksha
+ TRUETYPE_TAG('m', 'd', 'g', 0), // mdg = Massalat
+ TRUETYPE_TAG('m', 'd', 'h', 0), // mdh = Maguindanaon
+ TRUETYPE_TAG('m', 'd', 'i', 0), // mdi = Mamvu
+ TRUETYPE_TAG('m', 'd', 'j', 0), // mdj = Mangbetu
+ TRUETYPE_TAG('m', 'd', 'k', 0), // mdk = Mangbutu
+ TRUETYPE_TAG('m', 'd', 'l', 0), // mdl = Maltese Sign Language
+ TRUETYPE_TAG('m', 'd', 'm', 0), // mdm = Mayogo
+ TRUETYPE_TAG('m', 'd', 'n', 0), // mdn = Mbati
+ TRUETYPE_TAG('m', 'd', 'p', 0), // mdp = Mbala
+ TRUETYPE_TAG('m', 'd', 'q', 0), // mdq = Mbole
+ TRUETYPE_TAG('m', 'd', 'r', 0), // mdr = Mandar
+ TRUETYPE_TAG('m', 'd', 's', 0), // mds = Maria (Papua New Guinea)
+ TRUETYPE_TAG('m', 'd', 't', 0), // mdt = Mbere
+ TRUETYPE_TAG('m', 'd', 'u', 0), // mdu = Mboko
+ TRUETYPE_TAG('m', 'd', 'v', 0), // mdv = Santa Lucía Monteverde Mixtec
+ TRUETYPE_TAG('m', 'd', 'w', 0), // mdw = Mbosi
+ TRUETYPE_TAG('m', 'd', 'x', 0), // mdx = Dizin
+ TRUETYPE_TAG('m', 'd', 'y', 0), // mdy = Male (Ethiopia)
+ TRUETYPE_TAG('m', 'd', 'z', 0), // mdz = Suruí Do Pará
+ TRUETYPE_TAG('m', 'e', 'a', 0), // mea = Menka
+ TRUETYPE_TAG('m', 'e', 'b', 0), // meb = Ikobi-Mena
+ TRUETYPE_TAG('m', 'e', 'c', 0), // mec = Mara
+ TRUETYPE_TAG('m', 'e', 'd', 0), // med = Melpa
+ TRUETYPE_TAG('m', 'e', 'e', 0), // mee = Mengen
+ TRUETYPE_TAG('m', 'e', 'f', 0), // mef = Megam
+ TRUETYPE_TAG('m', 'e', 'g', 0), // meg = Mea
+ TRUETYPE_TAG('m', 'e', 'h', 0), // meh = Southwestern Tlaxiaco Mixtec
+ TRUETYPE_TAG('m', 'e', 'i', 0), // mei = Midob
+ TRUETYPE_TAG('m', 'e', 'j', 0), // mej = Meyah
+ TRUETYPE_TAG('m', 'e', 'k', 0), // mek = Mekeo
+ TRUETYPE_TAG('m', 'e', 'l', 0), // mel = Central Melanau
+ TRUETYPE_TAG('m', 'e', 'm', 0), // mem = Mangala
+ TRUETYPE_TAG('m', 'e', 'n', 0), // men = Mende (Sierra Leone)
+ TRUETYPE_TAG('m', 'e', 'o', 0), // meo = Kedah Malay
+ TRUETYPE_TAG('m', 'e', 'p', 0), // mep = Miriwung
+ TRUETYPE_TAG('m', 'e', 'q', 0), // meq = Merey
+ TRUETYPE_TAG('m', 'e', 'r', 0), // mer = Meru
+ TRUETYPE_TAG('m', 'e', 's', 0), // mes = Masmaje
+ TRUETYPE_TAG('m', 'e', 't', 0), // met = Mato
+ TRUETYPE_TAG('m', 'e', 'u', 0), // meu = Motu
+ TRUETYPE_TAG('m', 'e', 'v', 0), // mev = Mann
+ TRUETYPE_TAG('m', 'e', 'w', 0), // mew = Maaka
+ TRUETYPE_TAG('m', 'e', 'y', 0), // mey = Hassaniyya
+ TRUETYPE_TAG('m', 'e', 'z', 0), // mez = Menominee
+ TRUETYPE_TAG('m', 'f', 'a', 0), // mfa = Pattani Malay
+ TRUETYPE_TAG('m', 'f', 'b', 0), // mfb = Bangka
+ TRUETYPE_TAG('m', 'f', 'c', 0), // mfc = Mba
+ TRUETYPE_TAG('m', 'f', 'd', 0), // mfd = Mendankwe-Nkwen
+ TRUETYPE_TAG('m', 'f', 'e', 0), // mfe = Morisyen
+ TRUETYPE_TAG('m', 'f', 'f', 0), // mff = Naki
+ TRUETYPE_TAG('m', 'f', 'g', 0), // mfg = Mixifore
+ TRUETYPE_TAG('m', 'f', 'h', 0), // mfh = Matal
+ TRUETYPE_TAG('m', 'f', 'i', 0), // mfi = Wandala
+ TRUETYPE_TAG('m', 'f', 'j', 0), // mfj = Mefele
+ TRUETYPE_TAG('m', 'f', 'k', 0), // mfk = North Mofu
+ TRUETYPE_TAG('m', 'f', 'l', 0), // mfl = Putai
+ TRUETYPE_TAG('m', 'f', 'm', 0), // mfm = Marghi South
+ TRUETYPE_TAG('m', 'f', 'n', 0), // mfn = Cross River Mbembe
+ TRUETYPE_TAG('m', 'f', 'o', 0), // mfo = Mbe
+ TRUETYPE_TAG('m', 'f', 'p', 0), // mfp = Makassar Malay
+ TRUETYPE_TAG('m', 'f', 'q', 0), // mfq = Moba
+ TRUETYPE_TAG('m', 'f', 'r', 0), // mfr = Marithiel
+ TRUETYPE_TAG('m', 'f', 's', 0), // mfs = Mexican Sign Language
+ TRUETYPE_TAG('m', 'f', 't', 0), // mft = Mokerang
+ TRUETYPE_TAG('m', 'f', 'u', 0), // mfu = Mbwela
+ TRUETYPE_TAG('m', 'f', 'v', 0), // mfv = Mandjak
+ TRUETYPE_TAG('m', 'f', 'w', 0), // mfw = Mulaha
+ TRUETYPE_TAG('m', 'f', 'x', 0), // mfx = Melo
+ TRUETYPE_TAG('m', 'f', 'y', 0), // mfy = Mayo
+ TRUETYPE_TAG('m', 'f', 'z', 0), // mfz = Mabaan
+ TRUETYPE_TAG('m', 'g', 'a', 0), // mga = Middle Irish (900-1200)
+ TRUETYPE_TAG('m', 'g', 'b', 0), // mgb = Mararit
+ TRUETYPE_TAG('m', 'g', 'c', 0), // mgc = Morokodo
+ TRUETYPE_TAG('m', 'g', 'd', 0), // mgd = Moru
+ TRUETYPE_TAG('m', 'g', 'e', 0), // mge = Mango
+ TRUETYPE_TAG('m', 'g', 'f', 0), // mgf = Maklew
+ TRUETYPE_TAG('m', 'g', 'g', 0), // mgg = Mpongmpong
+ TRUETYPE_TAG('m', 'g', 'h', 0), // mgh = Makhuwa-Meetto
+ TRUETYPE_TAG('m', 'g', 'i', 0), // mgi = Lijili
+ TRUETYPE_TAG('m', 'g', 'j', 0), // mgj = Abureni
+ TRUETYPE_TAG('m', 'g', 'k', 0), // mgk = Mawes
+ TRUETYPE_TAG('m', 'g', 'l', 0), // mgl = Maleu-Kilenge
+ TRUETYPE_TAG('m', 'g', 'm', 0), // mgm = Mambae
+ TRUETYPE_TAG('m', 'g', 'n', 0), // mgn = Mbangi
+ TRUETYPE_TAG('m', 'g', 'o', 0), // mgo = Meta'
+ TRUETYPE_TAG('m', 'g', 'p', 0), // mgp = Eastern Magar
+ TRUETYPE_TAG('m', 'g', 'q', 0), // mgq = Malila
+ TRUETYPE_TAG('m', 'g', 'r', 0), // mgr = Mambwe-Lungu
+ TRUETYPE_TAG('m', 'g', 's', 0), // mgs = Manda (Tanzania)
+ TRUETYPE_TAG('m', 'g', 't', 0), // mgt = Mongol
+ TRUETYPE_TAG('m', 'g', 'u', 0), // mgu = Mailu
+ TRUETYPE_TAG('m', 'g', 'v', 0), // mgv = Matengo
+ TRUETYPE_TAG('m', 'g', 'w', 0), // mgw = Matumbi
+ TRUETYPE_TAG('m', 'g', 'x', 0), // mgx = Omati
+ TRUETYPE_TAG('m', 'g', 'y', 0), // mgy = Mbunga
+ TRUETYPE_TAG('m', 'g', 'z', 0), // mgz = Mbugwe
+ TRUETYPE_TAG('m', 'h', 'a', 0), // mha = Manda (India)
+ TRUETYPE_TAG('m', 'h', 'b', 0), // mhb = Mahongwe
+ TRUETYPE_TAG('m', 'h', 'c', 0), // mhc = Mocho
+ TRUETYPE_TAG('m', 'h', 'd', 0), // mhd = Mbugu
+ TRUETYPE_TAG('m', 'h', 'e', 0), // mhe = Besisi
+ TRUETYPE_TAG('m', 'h', 'f', 0), // mhf = Mamaa
+ TRUETYPE_TAG('m', 'h', 'g', 0), // mhg = Margu
+ TRUETYPE_TAG('m', 'h', 'h', 0), // mhh = Maskoy Pidgin
+ TRUETYPE_TAG('m', 'h', 'i', 0), // mhi = Ma'di
+ TRUETYPE_TAG('m', 'h', 'j', 0), // mhj = Mogholi
+ TRUETYPE_TAG('m', 'h', 'k', 0), // mhk = Mungaka
+ TRUETYPE_TAG('m', 'h', 'l', 0), // mhl = Mauwake
+ TRUETYPE_TAG('m', 'h', 'm', 0), // mhm = Makhuwa-Moniga
+ TRUETYPE_TAG('m', 'h', 'n', 0), // mhn = Mócheno
+ TRUETYPE_TAG('m', 'h', 'o', 0), // mho = Mashi (Zambia)
+ TRUETYPE_TAG('m', 'h', 'p', 0), // mhp = Balinese Malay
+ TRUETYPE_TAG('m', 'h', 'q', 0), // mhq = Mandan
+ TRUETYPE_TAG('m', 'h', 'r', 0), // mhr = Eastern Mari
+ TRUETYPE_TAG('m', 'h', 's', 0), // mhs = Buru (Indonesia)
+ TRUETYPE_TAG('m', 'h', 't', 0), // mht = Mandahuaca
+ TRUETYPE_TAG('m', 'h', 'u', 0), // mhu = Digaro-Mishmi
+ TRUETYPE_TAG('m', 'h', 'w', 0), // mhw = Mbukushu
+ TRUETYPE_TAG('m', 'h', 'x', 0), // mhx = Maru
+ TRUETYPE_TAG('m', 'h', 'y', 0), // mhy = Ma'anyan
+ TRUETYPE_TAG('m', 'h', 'z', 0), // mhz = Mor (Mor Islands)
+ TRUETYPE_TAG('m', 'i', 'a', 0), // mia = Miami
+ TRUETYPE_TAG('m', 'i', 'b', 0), // mib = Atatláhuca Mixtec
+ TRUETYPE_TAG('m', 'i', 'c', 0), // mic = Mi'kmaq
+ TRUETYPE_TAG('m', 'i', 'd', 0), // mid = Mandaic
+ TRUETYPE_TAG('m', 'i', 'e', 0), // mie = Ocotepec Mixtec
+ TRUETYPE_TAG('m', 'i', 'f', 0), // mif = Mofu-Gudur
+ TRUETYPE_TAG('m', 'i', 'g', 0), // mig = San Miguel El Grande Mixtec
+ TRUETYPE_TAG('m', 'i', 'h', 0), // mih = Chayuco Mixtec
+ TRUETYPE_TAG('m', 'i', 'i', 0), // mii = Chigmecatitlán Mixtec
+ TRUETYPE_TAG('m', 'i', 'j', 0), // mij = Abar
+ TRUETYPE_TAG('m', 'i', 'k', 0), // mik = Mikasuki
+ TRUETYPE_TAG('m', 'i', 'l', 0), // mil = Peñoles Mixtec
+ TRUETYPE_TAG('m', 'i', 'm', 0), // mim = Alacatlatzala Mixtec
+ TRUETYPE_TAG('m', 'i', 'n', 0), // min = Minangkabau
+ TRUETYPE_TAG('m', 'i', 'o', 0), // mio = Pinotepa Nacional Mixtec
+ TRUETYPE_TAG('m', 'i', 'p', 0), // mip = Apasco-Apoala Mixtec
+ TRUETYPE_TAG('m', 'i', 'q', 0), // miq = Mískito
+ TRUETYPE_TAG('m', 'i', 'r', 0), // mir = Isthmus Mixe
+ TRUETYPE_TAG('m', 'i', 's', 0), // mis = Uncoded languages
+ TRUETYPE_TAG('m', 'i', 't', 0), // mit = Southern Puebla Mixtec
+ TRUETYPE_TAG('m', 'i', 'u', 0), // miu = Cacaloxtepec Mixtec
+ TRUETYPE_TAG('m', 'i', 'w', 0), // miw = Akoye
+ TRUETYPE_TAG('m', 'i', 'x', 0), // mix = Mixtepec Mixtec
+ TRUETYPE_TAG('m', 'i', 'y', 0), // miy = Ayutla Mixtec
+ TRUETYPE_TAG('m', 'i', 'z', 0), // miz = Coatzospan Mixtec
+ TRUETYPE_TAG('m', 'j', 'a', 0), // mja = Mahei
+ TRUETYPE_TAG('m', 'j', 'c', 0), // mjc = San Juan Colorado Mixtec
+ TRUETYPE_TAG('m', 'j', 'd', 0), // mjd = Northwest Maidu
+ TRUETYPE_TAG('m', 'j', 'e', 0), // mje = Muskum
+ TRUETYPE_TAG('m', 'j', 'g', 0), // mjg = Tu
+ TRUETYPE_TAG('m', 'j', 'h', 0), // mjh = Mwera (Nyasa)
+ TRUETYPE_TAG('m', 'j', 'i', 0), // mji = Kim Mun
+ TRUETYPE_TAG('m', 'j', 'j', 0), // mjj = Mawak
+ TRUETYPE_TAG('m', 'j', 'k', 0), // mjk = Matukar
+ TRUETYPE_TAG('m', 'j', 'l', 0), // mjl = Mandeali
+ TRUETYPE_TAG('m', 'j', 'm', 0), // mjm = Medebur
+ TRUETYPE_TAG('m', 'j', 'n', 0), // mjn = Ma (Papua New Guinea)
+ TRUETYPE_TAG('m', 'j', 'o', 0), // mjo = Malankuravan
+ TRUETYPE_TAG('m', 'j', 'p', 0), // mjp = Malapandaram
+ TRUETYPE_TAG('m', 'j', 'q', 0), // mjq = Malaryan
+ TRUETYPE_TAG('m', 'j', 'r', 0), // mjr = Malavedan
+ TRUETYPE_TAG('m', 'j', 's', 0), // mjs = Miship
+ TRUETYPE_TAG('m', 'j', 't', 0), // mjt = Sauria Paharia
+ TRUETYPE_TAG('m', 'j', 'u', 0), // mju = Manna-Dora
+ TRUETYPE_TAG('m', 'j', 'v', 0), // mjv = Mannan
+ TRUETYPE_TAG('m', 'j', 'w', 0), // mjw = Karbi
+ TRUETYPE_TAG('m', 'j', 'x', 0), // mjx = Mahali
+ TRUETYPE_TAG('m', 'j', 'y', 0), // mjy = Mahican
+ TRUETYPE_TAG('m', 'j', 'z', 0), // mjz = Majhi
+ TRUETYPE_TAG('m', 'k', 'a', 0), // mka = Mbre
+ TRUETYPE_TAG('m', 'k', 'b', 0), // mkb = Mal Paharia
+ TRUETYPE_TAG('m', 'k', 'c', 0), // mkc = Siliput
+ TRUETYPE_TAG('m', 'k', 'e', 0), // mke = Mawchi
+ TRUETYPE_TAG('m', 'k', 'f', 0), // mkf = Miya
+ TRUETYPE_TAG('m', 'k', 'g', 0), // mkg = Mak (China)
+ TRUETYPE_TAG('m', 'k', 'h', 0), // mkh = Mon-Khmer languages
+ TRUETYPE_TAG('m', 'k', 'i', 0), // mki = Dhatki
+ TRUETYPE_TAG('m', 'k', 'j', 0), // mkj = Mokilese
+ TRUETYPE_TAG('m', 'k', 'k', 0), // mkk = Byep
+ TRUETYPE_TAG('m', 'k', 'l', 0), // mkl = Mokole
+ TRUETYPE_TAG('m', 'k', 'm', 0), // mkm = Moklen
+ TRUETYPE_TAG('m', 'k', 'n', 0), // mkn = Kupang Malay
+ TRUETYPE_TAG('m', 'k', 'o', 0), // mko = Mingang Doso
+ TRUETYPE_TAG('m', 'k', 'p', 0), // mkp = Moikodi
+ TRUETYPE_TAG('m', 'k', 'q', 0), // mkq = Bay Miwok
+ TRUETYPE_TAG('m', 'k', 'r', 0), // mkr = Malas
+ TRUETYPE_TAG('m', 'k', 's', 0), // mks = Silacayoapan Mixtec
+ TRUETYPE_TAG('m', 'k', 't', 0), // mkt = Vamale
+ TRUETYPE_TAG('m', 'k', 'u', 0), // mku = Konyanka Maninka
+ TRUETYPE_TAG('m', 'k', 'v', 0), // mkv = Mafea
+ TRUETYPE_TAG('m', 'k', 'w', 0), // mkw = Kituba (Congo)
+ TRUETYPE_TAG('m', 'k', 'x', 0), // mkx = Kinamiging Manobo
+ TRUETYPE_TAG('m', 'k', 'y', 0), // mky = East Makian
+ TRUETYPE_TAG('m', 'k', 'z', 0), // mkz = Makasae
+ TRUETYPE_TAG('m', 'l', 'a', 0), // mla = Malo
+ TRUETYPE_TAG('m', 'l', 'b', 0), // mlb = Mbule
+ TRUETYPE_TAG('m', 'l', 'c', 0), // mlc = Cao Lan
+ TRUETYPE_TAG('m', 'l', 'd', 0), // mld = Malakhel
+ TRUETYPE_TAG('m', 'l', 'e', 0), // mle = Manambu
+ TRUETYPE_TAG('m', 'l', 'f', 0), // mlf = Mal
+ TRUETYPE_TAG('m', 'l', 'h', 0), // mlh = Mape
+ TRUETYPE_TAG('m', 'l', 'i', 0), // mli = Malimpung
+ TRUETYPE_TAG('m', 'l', 'j', 0), // mlj = Miltu
+ TRUETYPE_TAG('m', 'l', 'k', 0), // mlk = Ilwana
+ TRUETYPE_TAG('m', 'l', 'l', 0), // mll = Malua Bay
+ TRUETYPE_TAG('m', 'l', 'm', 0), // mlm = Mulam
+ TRUETYPE_TAG('m', 'l', 'n', 0), // mln = Malango
+ TRUETYPE_TAG('m', 'l', 'o', 0), // mlo = Mlomp
+ TRUETYPE_TAG('m', 'l', 'p', 0), // mlp = Bargam
+ TRUETYPE_TAG('m', 'l', 'q', 0), // mlq = Western Maninkakan
+ TRUETYPE_TAG('m', 'l', 'r', 0), // mlr = Vame
+ TRUETYPE_TAG('m', 'l', 's', 0), // mls = Masalit
+ TRUETYPE_TAG('m', 'l', 'u', 0), // mlu = To'abaita
+ TRUETYPE_TAG('m', 'l', 'v', 0), // mlv = Motlav
+ TRUETYPE_TAG('m', 'l', 'w', 0), // mlw = Moloko
+ TRUETYPE_TAG('m', 'l', 'x', 0), // mlx = Malfaxal
+ TRUETYPE_TAG('m', 'l', 'z', 0), // mlz = Malaynon
+ TRUETYPE_TAG('m', 'm', 'a', 0), // mma = Mama
+ TRUETYPE_TAG('m', 'm', 'b', 0), // mmb = Momina
+ TRUETYPE_TAG('m', 'm', 'c', 0), // mmc = Michoacán Mazahua
+ TRUETYPE_TAG('m', 'm', 'd', 0), // mmd = Maonan
+ TRUETYPE_TAG('m', 'm', 'e', 0), // mme = Mae
+ TRUETYPE_TAG('m', 'm', 'f', 0), // mmf = Mundat
+ TRUETYPE_TAG('m', 'm', 'g', 0), // mmg = North Ambrym
+ TRUETYPE_TAG('m', 'm', 'h', 0), // mmh = Mehináku
+ TRUETYPE_TAG('m', 'm', 'i', 0), // mmi = Musar
+ TRUETYPE_TAG('m', 'm', 'j', 0), // mmj = Majhwar
+ TRUETYPE_TAG('m', 'm', 'k', 0), // mmk = Mukha-Dora
+ TRUETYPE_TAG('m', 'm', 'l', 0), // mml = Man Met
+ TRUETYPE_TAG('m', 'm', 'm', 0), // mmm = Maii
+ TRUETYPE_TAG('m', 'm', 'n', 0), // mmn = Mamanwa
+ TRUETYPE_TAG('m', 'm', 'o', 0), // mmo = Mangga Buang
+ TRUETYPE_TAG('m', 'm', 'p', 0), // mmp = Siawi
+ TRUETYPE_TAG('m', 'm', 'q', 0), // mmq = Musak
+ TRUETYPE_TAG('m', 'm', 'r', 0), // mmr = Western Xiangxi Miao
+ TRUETYPE_TAG('m', 'm', 't', 0), // mmt = Malalamai
+ TRUETYPE_TAG('m', 'm', 'u', 0), // mmu = Mmaala
+ TRUETYPE_TAG('m', 'm', 'v', 0), // mmv = Miriti
+ TRUETYPE_TAG('m', 'm', 'w', 0), // mmw = Emae
+ TRUETYPE_TAG('m', 'm', 'x', 0), // mmx = Madak
+ TRUETYPE_TAG('m', 'm', 'y', 0), // mmy = Migaama
+ TRUETYPE_TAG('m', 'm', 'z', 0), // mmz = Mabaale
+ TRUETYPE_TAG('m', 'n', 'a', 0), // mna = Mbula
+ TRUETYPE_TAG('m', 'n', 'b', 0), // mnb = Muna
+ TRUETYPE_TAG('m', 'n', 'c', 0), // mnc = Manchu
+ TRUETYPE_TAG('m', 'n', 'd', 0), // mnd = Mondé
+ TRUETYPE_TAG('m', 'n', 'e', 0), // mne = Naba
+ TRUETYPE_TAG('m', 'n', 'f', 0), // mnf = Mundani
+ TRUETYPE_TAG('m', 'n', 'g', 0), // mng = Eastern Mnong
+ TRUETYPE_TAG('m', 'n', 'h',
+ 0), // mnh = Mono (Democratic Republic of Congo)
+ TRUETYPE_TAG('m', 'n', 'i', 0), // mni = Manipuri
+ TRUETYPE_TAG('m', 'n', 'j', 0), // mnj = Munji
+ TRUETYPE_TAG('m', 'n', 'k', 0), // mnk = Mandinka
+ TRUETYPE_TAG('m', 'n', 'l', 0), // mnl = Tiale
+ TRUETYPE_TAG('m', 'n', 'm', 0), // mnm = Mapena
+ TRUETYPE_TAG('m', 'n', 'n', 0), // mnn = Southern Mnong
+ TRUETYPE_TAG('m', 'n', 'o', 0), // mno = Manobo languages
+ TRUETYPE_TAG('m', 'n', 'p', 0), // mnp = Min Bei Chinese
+ TRUETYPE_TAG('m', 'n', 'q', 0), // mnq = Minriq
+ TRUETYPE_TAG('m', 'n', 'r', 0), // mnr = Mono (USA)
+ TRUETYPE_TAG('m', 'n', 's', 0), // mns = Mansi
+ TRUETYPE_TAG('m', 'n', 't', 0), // mnt = Maykulan
+ TRUETYPE_TAG('m', 'n', 'u', 0), // mnu = Mer
+ TRUETYPE_TAG('m', 'n', 'v', 0), // mnv = Rennell-Bellona
+ TRUETYPE_TAG('m', 'n', 'w', 0), // mnw = Mon
+ TRUETYPE_TAG('m', 'n', 'x', 0), // mnx = Manikion
+ TRUETYPE_TAG('m', 'n', 'y', 0), // mny = Manyawa
+ TRUETYPE_TAG('m', 'n', 'z', 0), // mnz = Moni
+ TRUETYPE_TAG('m', 'o', 'a', 0), // moa = Mwan
+ TRUETYPE_TAG('m', 'o', 'c', 0), // moc = Mocoví
+ TRUETYPE_TAG('m', 'o', 'd', 0), // mod = Mobilian
+ TRUETYPE_TAG('m', 'o', 'e', 0), // moe = Montagnais
+ TRUETYPE_TAG('m', 'o', 'f', 0), // mof = Mohegan-Montauk-Narragansett
+ TRUETYPE_TAG('m', 'o', 'g', 0), // mog = Mongondow
+ TRUETYPE_TAG('m', 'o', 'h', 0), // moh = Mohawk
+ TRUETYPE_TAG('m', 'o', 'i', 0), // moi = Mboi
+ TRUETYPE_TAG('m', 'o', 'j', 0), // moj = Monzombo
+ TRUETYPE_TAG('m', 'o', 'k', 0), // mok = Morori
+ TRUETYPE_TAG('m', 'o', 'm', 0), // mom = Mangue
+ TRUETYPE_TAG('m', 'o', 'o', 0), // moo = Monom
+ TRUETYPE_TAG('m', 'o', 'p', 0), // mop = Mopán Maya
+ TRUETYPE_TAG('m', 'o', 'q', 0), // moq = Mor (Bomberai Peninsula)
+ TRUETYPE_TAG('m', 'o', 'r', 0), // mor = Moro
+ TRUETYPE_TAG('m', 'o', 's', 0), // mos = Mossi
+ TRUETYPE_TAG('m', 'o', 't', 0), // mot = Barí
+ TRUETYPE_TAG('m', 'o', 'u', 0), // mou = Mogum
+ TRUETYPE_TAG('m', 'o', 'v', 0), // mov = Mohave
+ TRUETYPE_TAG('m', 'o', 'w', 0), // mow = Moi (Congo)
+ TRUETYPE_TAG('m', 'o', 'x', 0), // mox = Molima
+ TRUETYPE_TAG('m', 'o', 'y', 0), // moy = Shekkacho
+ TRUETYPE_TAG('m', 'o', 'z', 0), // moz = Mukulu
+ TRUETYPE_TAG('m', 'p', 'a', 0), // mpa = Mpoto
+ TRUETYPE_TAG('m', 'p', 'b', 0), // mpb = Mullukmulluk
+ TRUETYPE_TAG('m', 'p', 'c', 0), // mpc = Mangarayi
+ TRUETYPE_TAG('m', 'p', 'd', 0), // mpd = Machinere
+ TRUETYPE_TAG('m', 'p', 'e', 0), // mpe = Majang
+ TRUETYPE_TAG('m', 'p', 'g', 0), // mpg = Marba
+ TRUETYPE_TAG('m', 'p', 'h', 0), // mph = Maung
+ TRUETYPE_TAG('m', 'p', 'i', 0), // mpi = Mpade
+ TRUETYPE_TAG('m', 'p', 'j', 0), // mpj = Martu Wangka
+ TRUETYPE_TAG('m', 'p', 'k', 0), // mpk = Mbara (Chad)
+ TRUETYPE_TAG('m', 'p', 'l', 0), // mpl = Middle Watut
+ TRUETYPE_TAG('m', 'p', 'm', 0), // mpm = Yosondúa Mixtec
+ TRUETYPE_TAG('m', 'p', 'n', 0), // mpn = Mindiri
+ TRUETYPE_TAG('m', 'p', 'o', 0), // mpo = Miu
+ TRUETYPE_TAG('m', 'p', 'p', 0), // mpp = Migabac
+ TRUETYPE_TAG('m', 'p', 'q', 0), // mpq = Matís
+ TRUETYPE_TAG('m', 'p', 'r', 0), // mpr = Vangunu
+ TRUETYPE_TAG('m', 'p', 's', 0), // mps = Dadibi
+ TRUETYPE_TAG('m', 'p', 't', 0), // mpt = Mian
+ TRUETYPE_TAG('m', 'p', 'u', 0), // mpu = Makuráp
+ TRUETYPE_TAG('m', 'p', 'v', 0), // mpv = Mungkip
+ TRUETYPE_TAG('m', 'p', 'w', 0), // mpw = Mapidian
+ TRUETYPE_TAG('m', 'p', 'x', 0), // mpx = Misima-Paneati
+ TRUETYPE_TAG('m', 'p', 'y', 0), // mpy = Mapia
+ TRUETYPE_TAG('m', 'p', 'z', 0), // mpz = Mpi
+ TRUETYPE_TAG('m', 'q', 'a', 0), // mqa = Maba (Indonesia)
+ TRUETYPE_TAG('m', 'q', 'b', 0), // mqb = Mbuko
+ TRUETYPE_TAG('m', 'q', 'c', 0), // mqc = Mangole
+ TRUETYPE_TAG('m', 'q', 'e', 0), // mqe = Matepi
+ TRUETYPE_TAG('m', 'q', 'f', 0), // mqf = Momuna
+ TRUETYPE_TAG('m', 'q', 'g', 0), // mqg = Kota Bangun Kutai Malay
+ TRUETYPE_TAG('m', 'q', 'h', 0), // mqh = Tlazoyaltepec Mixtec
+ TRUETYPE_TAG('m', 'q', 'i', 0), // mqi = Mariri
+ TRUETYPE_TAG('m', 'q', 'j', 0), // mqj = Mamasa
+ TRUETYPE_TAG('m', 'q', 'k', 0), // mqk = Rajah Kabunsuwan Manobo
+ TRUETYPE_TAG('m', 'q', 'l', 0), // mql = Mbelime
+ TRUETYPE_TAG('m', 'q', 'm', 0), // mqm = South Marquesan
+ TRUETYPE_TAG('m', 'q', 'n', 0), // mqn = Moronene
+ TRUETYPE_TAG('m', 'q', 'o', 0), // mqo = Modole
+ TRUETYPE_TAG('m', 'q', 'p', 0), // mqp = Manipa
+ TRUETYPE_TAG('m', 'q', 'q', 0), // mqq = Minokok
+ TRUETYPE_TAG('m', 'q', 'r', 0), // mqr = Mander
+ TRUETYPE_TAG('m', 'q', 's', 0), // mqs = West Makian
+ TRUETYPE_TAG('m', 'q', 't', 0), // mqt = Mok
+ TRUETYPE_TAG('m', 'q', 'u', 0), // mqu = Mandari
+ TRUETYPE_TAG('m', 'q', 'v', 0), // mqv = Mosimo
+ TRUETYPE_TAG('m', 'q', 'w', 0), // mqw = Murupi
+ TRUETYPE_TAG('m', 'q', 'x', 0), // mqx = Mamuju
+ TRUETYPE_TAG('m', 'q', 'y', 0), // mqy = Manggarai
+ TRUETYPE_TAG('m', 'q', 'z', 0), // mqz = Malasanga
+ TRUETYPE_TAG('m', 'r', 'a', 0), // mra = Mlabri
+ TRUETYPE_TAG('m', 'r', 'b', 0), // mrb = Marino
+ TRUETYPE_TAG('m', 'r', 'c', 0), // mrc = Maricopa
+ TRUETYPE_TAG('m', 'r', 'd', 0), // mrd = Western Magar
+ TRUETYPE_TAG('m', 'r', 'e', 0), // mre = Martha's Vineyard Sign Language
+ TRUETYPE_TAG('m', 'r', 'f', 0), // mrf = Elseng
+ TRUETYPE_TAG('m', 'r', 'g', 0), // mrg = Mising
+ TRUETYPE_TAG('m', 'r', 'h', 0), // mrh = Mara Chin
+ TRUETYPE_TAG('m', 'r', 'j', 0), // mrj = Western Mari
+ TRUETYPE_TAG('m', 'r', 'k', 0), // mrk = Hmwaveke
+ TRUETYPE_TAG('m', 'r', 'l', 0), // mrl = Mortlockese
+ TRUETYPE_TAG('m', 'r', 'm', 0), // mrm = Merlav
+ TRUETYPE_TAG('m', 'r', 'n', 0), // mrn = Cheke Holo
+ TRUETYPE_TAG('m', 'r', 'o', 0), // mro = Mru
+ TRUETYPE_TAG('m', 'r', 'p', 0), // mrp = Morouas
+ TRUETYPE_TAG('m', 'r', 'q', 0), // mrq = North Marquesan
+ TRUETYPE_TAG('m', 'r', 'r', 0), // mrr = Maria (India)
+ TRUETYPE_TAG('m', 'r', 's', 0), // mrs = Maragus
+ TRUETYPE_TAG('m', 'r', 't', 0), // mrt = Marghi Central
+ TRUETYPE_TAG('m', 'r', 'u', 0), // mru = Mono (Cameroon)
+ TRUETYPE_TAG('m', 'r', 'v', 0), // mrv = Mangareva
+ TRUETYPE_TAG('m', 'r', 'w', 0), // mrw = Maranao
+ TRUETYPE_TAG('m', 'r', 'x', 0), // mrx = Maremgi
+ TRUETYPE_TAG('m', 'r', 'y', 0), // mry = Mandaya
+ TRUETYPE_TAG('m', 'r', 'z', 0), // mrz = Marind
+ TRUETYPE_TAG('m', 's', 'b', 0), // msb = Masbatenyo
+ TRUETYPE_TAG('m', 's', 'c', 0), // msc = Sankaran Maninka
+ TRUETYPE_TAG('m', 's', 'd', 0), // msd = Yucatec Maya Sign Language
+ TRUETYPE_TAG('m', 's', 'e', 0), // mse = Musey
+ TRUETYPE_TAG('m', 's', 'f', 0), // msf = Mekwei
+ TRUETYPE_TAG('m', 's', 'g', 0), // msg = Moraid
+ TRUETYPE_TAG('m', 's', 'h', 0), // msh = Masikoro Malagasy
+ TRUETYPE_TAG('m', 's', 'i', 0), // msi = Sabah Malay
+ TRUETYPE_TAG('m', 's', 'j', 0), // msj = Ma (Democratic Republic of Congo)
+ TRUETYPE_TAG('m', 's', 'k', 0), // msk = Mansaka
+ TRUETYPE_TAG('m', 's', 'l', 0), // msl = Molof
+ TRUETYPE_TAG('m', 's', 'm', 0), // msm = Agusan Manobo
+ TRUETYPE_TAG('m', 's', 'n', 0), // msn = Vurës
+ TRUETYPE_TAG('m', 's', 'o', 0), // mso = Mombum
+ TRUETYPE_TAG('m', 's', 'p', 0), // msp = Maritsauá
+ TRUETYPE_TAG('m', 's', 'q', 0), // msq = Caac
+ TRUETYPE_TAG('m', 's', 'r', 0), // msr = Mongolian Sign Language
+ TRUETYPE_TAG('m', 's', 's', 0), // mss = West Masela
+ TRUETYPE_TAG('m', 's', 't', 0), // mst = Cataelano Mandaya
+ TRUETYPE_TAG('m', 's', 'u', 0), // msu = Musom
+ TRUETYPE_TAG('m', 's', 'v', 0), // msv = Maslam
+ TRUETYPE_TAG('m', 's', 'w', 0), // msw = Mansoanka
+ TRUETYPE_TAG('m', 's', 'x', 0), // msx = Moresada
+ TRUETYPE_TAG('m', 's', 'y', 0), // msy = Aruamu
+ TRUETYPE_TAG('m', 's', 'z', 0), // msz = Momare
+ TRUETYPE_TAG('m', 't', 'a', 0), // mta = Cotabato Manobo
+ TRUETYPE_TAG('m', 't', 'b', 0), // mtb = Anyin Morofo
+ TRUETYPE_TAG('m', 't', 'c', 0), // mtc = Munit
+ TRUETYPE_TAG('m', 't', 'd', 0), // mtd = Mualang
+ TRUETYPE_TAG('m', 't', 'e', 0), // mte = Mono (Solomon Islands)
+ TRUETYPE_TAG('m', 't', 'f', 0), // mtf = Murik (Papua New Guinea)
+ TRUETYPE_TAG('m', 't', 'g', 0), // mtg = Una
+ TRUETYPE_TAG('m', 't', 'h', 0), // mth = Munggui
+ TRUETYPE_TAG('m', 't', 'i', 0), // mti = Maiwa (Papua New Guinea)
+ TRUETYPE_TAG('m', 't', 'j', 0), // mtj = Moskona
+ TRUETYPE_TAG('m', 't', 'k', 0), // mtk = Mbe'
+ TRUETYPE_TAG('m', 't', 'l', 0), // mtl = Montol
+ TRUETYPE_TAG('m', 't', 'm', 0), // mtm = Mator
+ TRUETYPE_TAG('m', 't', 'n', 0), // mtn = Matagalpa
+ TRUETYPE_TAG('m', 't', 'o', 0), // mto = Totontepec Mixe
+ TRUETYPE_TAG('m', 't', 'p', 0), // mtp = Wichí Lhamtés Nocten
+ TRUETYPE_TAG('m', 't', 'q', 0), // mtq = Muong
+ TRUETYPE_TAG('m', 't', 'r', 0), // mtr = Mewari
+ TRUETYPE_TAG('m', 't', 's', 0), // mts = Yora
+ TRUETYPE_TAG('m', 't', 't', 0), // mtt = Mota
+ TRUETYPE_TAG('m', 't', 'u', 0), // mtu = Tututepec Mixtec
+ TRUETYPE_TAG('m', 't', 'v', 0), // mtv = Asaro'o
+ TRUETYPE_TAG('m', 't', 'w', 0), // mtw = Southern Binukidnon
+ TRUETYPE_TAG('m', 't', 'x', 0), // mtx = Tidaá Mixtec
+ TRUETYPE_TAG('m', 't', 'y', 0), // mty = Nabi
+ TRUETYPE_TAG('m', 'u', 'a', 0), // mua = Mundang
+ TRUETYPE_TAG('m', 'u', 'b', 0), // mub = Mubi
+ TRUETYPE_TAG('m', 'u', 'c', 0), // muc = Mbu'
+ TRUETYPE_TAG('m', 'u', 'd', 0), // mud = Mednyj Aleut
+ TRUETYPE_TAG('m', 'u', 'e', 0), // mue = Media Lengua
+ TRUETYPE_TAG('m', 'u', 'g', 0), // mug = Musgu
+ TRUETYPE_TAG('m', 'u', 'h', 0), // muh = Mündü
+ TRUETYPE_TAG('m', 'u', 'i', 0), // mui = Musi
+ TRUETYPE_TAG('m', 'u', 'j', 0), // muj = Mabire
+ TRUETYPE_TAG('m', 'u', 'k', 0), // muk = Mugom
+ TRUETYPE_TAG('m', 'u', 'l', 0), // mul = Multiple languages
+ TRUETYPE_TAG('m', 'u', 'm', 0), // mum = Maiwala
+ TRUETYPE_TAG('m', 'u', 'n', 0), // mun = Munda languages
+ TRUETYPE_TAG('m', 'u', 'o', 0), // muo = Nyong
+ TRUETYPE_TAG('m', 'u', 'p', 0), // mup = Malvi
+ TRUETYPE_TAG('m', 'u', 'q', 0), // muq = Eastern Xiangxi Miao
+ TRUETYPE_TAG('m', 'u', 'r', 0), // mur = Murle
+ TRUETYPE_TAG('m', 'u', 's', 0), // mus = Creek
+ TRUETYPE_TAG('m', 'u', 't', 0), // mut = Western Muria
+ TRUETYPE_TAG('m', 'u', 'u', 0), // muu = Yaaku
+ TRUETYPE_TAG('m', 'u', 'v', 0), // muv = Muthuvan
+ TRUETYPE_TAG('m', 'u', 'x', 0), // mux = Bo-Ung
+ TRUETYPE_TAG('m', 'u', 'y', 0), // muy = Muyang
+ TRUETYPE_TAG('m', 'u', 'z', 0), // muz = Mursi
+ TRUETYPE_TAG('m', 'v', 'a', 0), // mva = Manam
+ TRUETYPE_TAG('m', 'v', 'b', 0), // mvb = Mattole
+ TRUETYPE_TAG('m', 'v', 'd', 0), // mvd = Mamboru
+ TRUETYPE_TAG('m', 'v', 'e', 0), // mve = Marwari (Pakistan)
+ TRUETYPE_TAG('m', 'v', 'f', 0), // mvf = Peripheral Mongolian
+ TRUETYPE_TAG('m', 'v', 'g', 0), // mvg = Yucuañe Mixtec
+ TRUETYPE_TAG('m', 'v', 'h', 0), // mvh = Mire
+ TRUETYPE_TAG('m', 'v', 'i', 0), // mvi = Miyako
+ TRUETYPE_TAG('m', 'v', 'k', 0), // mvk = Mekmek
+ TRUETYPE_TAG('m', 'v', 'l', 0), // mvl = Mbara (Australia)
+ TRUETYPE_TAG('m', 'v', 'm', 0), // mvm = Muya
+ TRUETYPE_TAG('m', 'v', 'n', 0), // mvn = Minaveha
+ TRUETYPE_TAG('m', 'v', 'o', 0), // mvo = Marovo
+ TRUETYPE_TAG('m', 'v', 'p', 0), // mvp = Duri
+ TRUETYPE_TAG('m', 'v', 'q', 0), // mvq = Moere
+ TRUETYPE_TAG('m', 'v', 'r', 0), // mvr = Marau
+ TRUETYPE_TAG('m', 'v', 's', 0), // mvs = Massep
+ TRUETYPE_TAG('m', 'v', 't', 0), // mvt = Mpotovoro
+ TRUETYPE_TAG('m', 'v', 'u', 0), // mvu = Marfa
+ TRUETYPE_TAG('m', 'v', 'v', 0), // mvv = Tagal Murut
+ TRUETYPE_TAG('m', 'v', 'w', 0), // mvw = Machinga
+ TRUETYPE_TAG('m', 'v', 'x', 0), // mvx = Meoswar
+ TRUETYPE_TAG('m', 'v', 'y', 0), // mvy = Indus Kohistani
+ TRUETYPE_TAG('m', 'v', 'z', 0), // mvz = Mesqan
+ TRUETYPE_TAG('m', 'w', 'a', 0), // mwa = Mwatebu
+ TRUETYPE_TAG('m', 'w', 'b', 0), // mwb = Juwal
+ TRUETYPE_TAG('m', 'w', 'c', 0), // mwc = Are
+ TRUETYPE_TAG('m', 'w', 'd', 0), // mwd = Mudbura
+ TRUETYPE_TAG('m', 'w', 'e', 0), // mwe = Mwera (Chimwera)
+ TRUETYPE_TAG('m', 'w', 'f', 0), // mwf = Murrinh-Patha
+ TRUETYPE_TAG('m', 'w', 'g', 0), // mwg = Aiklep
+ TRUETYPE_TAG('m', 'w', 'h', 0), // mwh = Mouk-Aria
+ TRUETYPE_TAG('m', 'w', 'i', 0), // mwi = Labo
+ TRUETYPE_TAG('m', 'w', 'j', 0), // mwj = Maligo
+ TRUETYPE_TAG('m', 'w', 'k', 0), // mwk = Kita Maninkakan
+ TRUETYPE_TAG('m', 'w', 'l', 0), // mwl = Mirandese
+ TRUETYPE_TAG('m', 'w', 'm', 0), // mwm = Sar
+ TRUETYPE_TAG('m', 'w', 'n', 0), // mwn = Nyamwanga
+ TRUETYPE_TAG('m', 'w', 'o', 0), // mwo = Central Maewo
+ TRUETYPE_TAG('m', 'w', 'p', 0), // mwp = Kala Lagaw Ya
+ TRUETYPE_TAG('m', 'w', 'q', 0), // mwq = Mün Chin
+ TRUETYPE_TAG('m', 'w', 'r', 0), // mwr = Marwari
+ TRUETYPE_TAG('m', 'w', 's', 0), // mws = Mwimbi-Muthambi
+ TRUETYPE_TAG('m', 'w', 't', 0), // mwt = Moken
+ TRUETYPE_TAG('m', 'w', 'u', 0), // mwu = Mittu
+ TRUETYPE_TAG('m', 'w', 'v', 0), // mwv = Mentawai
+ TRUETYPE_TAG('m', 'w', 'w', 0), // mww = Hmong Daw
+ TRUETYPE_TAG('m', 'w', 'x', 0), // mwx = Mediak
+ TRUETYPE_TAG('m', 'w', 'y', 0), // mwy = Mosiro
+ TRUETYPE_TAG('m', 'w', 'z', 0), // mwz = Moingi
+ TRUETYPE_TAG('m', 'x', 'a', 0), // mxa = Northwest Oaxaca Mixtec
+ TRUETYPE_TAG('m', 'x', 'b', 0), // mxb = Tezoatlán Mixtec
+ TRUETYPE_TAG('m', 'x', 'c', 0), // mxc = Manyika
+ TRUETYPE_TAG('m', 'x', 'd', 0), // mxd = Modang
+ TRUETYPE_TAG('m', 'x', 'e', 0), // mxe = Mele-Fila
+ TRUETYPE_TAG('m', 'x', 'f', 0), // mxf = Malgbe
+ TRUETYPE_TAG('m', 'x', 'g', 0), // mxg = Mbangala
+ TRUETYPE_TAG('m', 'x', 'h', 0), // mxh = Mvuba
+ TRUETYPE_TAG('m', 'x', 'i', 0), // mxi = Mozarabic
+ TRUETYPE_TAG('m', 'x', 'j', 0), // mxj = Miju-Mishmi
+ TRUETYPE_TAG('m', 'x', 'k', 0), // mxk = Monumbo
+ TRUETYPE_TAG('m', 'x', 'l', 0), // mxl = Maxi Gbe
+ TRUETYPE_TAG('m', 'x', 'm', 0), // mxm = Meramera
+ TRUETYPE_TAG('m', 'x', 'n', 0), // mxn = Moi (Indonesia)
+ TRUETYPE_TAG('m', 'x', 'o', 0), // mxo = Mbowe
+ TRUETYPE_TAG('m', 'x', 'p', 0), // mxp = Tlahuitoltepec Mixe
+ TRUETYPE_TAG('m', 'x', 'q', 0), // mxq = Juquila Mixe
+ TRUETYPE_TAG('m', 'x', 'r', 0), // mxr = Murik (Malaysia)
+ TRUETYPE_TAG('m', 'x', 's', 0), // mxs = Huitepec Mixtec
+ TRUETYPE_TAG('m', 'x', 't', 0), // mxt = Jamiltepec Mixtec
+ TRUETYPE_TAG('m', 'x', 'u', 0), // mxu = Mada (Cameroon)
+ TRUETYPE_TAG('m', 'x', 'v', 0), // mxv = Metlatónoc Mixtec
+ TRUETYPE_TAG('m', 'x', 'w', 0), // mxw = Namo
+ TRUETYPE_TAG('m', 'x', 'x', 0), // mxx = Mahou
+ TRUETYPE_TAG('m', 'x', 'y', 0), // mxy = Southeastern Nochixtlán Mixtec
+ TRUETYPE_TAG('m', 'x', 'z', 0), // mxz = Central Masela
+ TRUETYPE_TAG('m', 'y', 'b', 0), // myb = Mbay
+ TRUETYPE_TAG('m', 'y', 'c', 0), // myc = Mayeka
+ TRUETYPE_TAG('m', 'y', 'd', 0), // myd = Maramba
+ TRUETYPE_TAG('m', 'y', 'e', 0), // mye = Myene
+ TRUETYPE_TAG('m', 'y', 'f', 0), // myf = Bambassi
+ TRUETYPE_TAG('m', 'y', 'g', 0), // myg = Manta
+ TRUETYPE_TAG('m', 'y', 'h', 0), // myh = Makah
+ TRUETYPE_TAG('m', 'y', 'i', 0), // myi = Mina (India)
+ TRUETYPE_TAG('m', 'y', 'j', 0), // myj = Mangayat
+ TRUETYPE_TAG('m', 'y', 'k', 0), // myk = Mamara Senoufo
+ TRUETYPE_TAG('m', 'y', 'l', 0), // myl = Moma
+ TRUETYPE_TAG('m', 'y', 'm', 0), // mym = Me'en
+ TRUETYPE_TAG('m', 'y', 'n', 0), // myn = Mayan languages
+ TRUETYPE_TAG('m', 'y', 'o', 0), // myo = Anfillo
+ TRUETYPE_TAG('m', 'y', 'p', 0), // myp = Pirahã
+ TRUETYPE_TAG('m', 'y', 'q', 0), // myq = Forest Maninka
+ TRUETYPE_TAG('m', 'y', 'r', 0), // myr = Muniche
+ TRUETYPE_TAG('m', 'y', 's', 0), // mys = Mesmes
+ TRUETYPE_TAG('m', 'y', 't', 0), // myt = Sangab Mandaya
+ TRUETYPE_TAG('m', 'y', 'u', 0), // myu = Mundurukú
+ TRUETYPE_TAG('m', 'y', 'v', 0), // myv = Erzya
+ TRUETYPE_TAG('m', 'y', 'w', 0), // myw = Muyuw
+ TRUETYPE_TAG('m', 'y', 'x', 0), // myx = Masaaba
+ TRUETYPE_TAG('m', 'y', 'y', 0), // myy = Macuna
+ TRUETYPE_TAG('m', 'y', 'z', 0), // myz = Classical Mandaic
+ TRUETYPE_TAG('m', 'z', 'a', 0), // mza = Santa María Zacatepec Mixtec
+ TRUETYPE_TAG('m', 'z', 'b', 0), // mzb = Tumzabt
+ TRUETYPE_TAG('m', 'z', 'c', 0), // mzc = Madagascar Sign Language
+ TRUETYPE_TAG('m', 'z', 'd', 0), // mzd = Malimba
+ TRUETYPE_TAG('m', 'z', 'e', 0), // mze = Morawa
+ TRUETYPE_TAG('m', 'z', 'g', 0), // mzg = Monastic Sign Language
+ TRUETYPE_TAG('m', 'z', 'h', 0), // mzh = Wichí Lhamtés Güisnay
+ TRUETYPE_TAG('m', 'z', 'i', 0), // mzi = Ixcatlán Mazatec
+ TRUETYPE_TAG('m', 'z', 'j', 0), // mzj = Manya
+ TRUETYPE_TAG('m', 'z', 'k', 0), // mzk = Nigeria Mambila
+ TRUETYPE_TAG('m', 'z', 'l', 0), // mzl = Mazatlán Mixe
+ TRUETYPE_TAG('m', 'z', 'm', 0), // mzm = Mumuye
+ TRUETYPE_TAG('m', 'z', 'n', 0), // mzn = Mazanderani
+ TRUETYPE_TAG('m', 'z', 'o', 0), // mzo = Matipuhy
+ TRUETYPE_TAG('m', 'z', 'p', 0), // mzp = Movima
+ TRUETYPE_TAG('m', 'z', 'q', 0), // mzq = Mori Atas
+ TRUETYPE_TAG('m', 'z', 'r', 0), // mzr = Marúbo
+ TRUETYPE_TAG('m', 'z', 's', 0), // mzs = Macanese
+ TRUETYPE_TAG('m', 'z', 't', 0), // mzt = Mintil
+ TRUETYPE_TAG('m', 'z', 'u', 0), // mzu = Inapang
+ TRUETYPE_TAG('m', 'z', 'v', 0), // mzv = Manza
+ TRUETYPE_TAG('m', 'z', 'w', 0), // mzw = Deg
+ TRUETYPE_TAG('m', 'z', 'x', 0), // mzx = Mawayana
+ TRUETYPE_TAG('m', 'z', 'y', 0), // mzy = Mozambican Sign Language
+ TRUETYPE_TAG('m', 'z', 'z', 0), // mzz = Maiadomu
+ TRUETYPE_TAG('n', 'a', 'a', 0), // naa = Namla
+ TRUETYPE_TAG('n', 'a', 'b', 0), // nab = Southern Nambikuára
+ TRUETYPE_TAG('n', 'a', 'c', 0), // nac = Narak
+ TRUETYPE_TAG('n', 'a', 'd', 0), // nad = Nijadali
+ TRUETYPE_TAG('n', 'a', 'e', 0), // nae = Naka'ela
+ TRUETYPE_TAG('n', 'a', 'f', 0), // naf = Nabak
+ TRUETYPE_TAG('n', 'a', 'g', 0), // nag = Naga Pidgin
+ TRUETYPE_TAG('n', 'a', 'h', 0), // nah = Nahuatl languages
+ TRUETYPE_TAG('n', 'a', 'i', 0), // nai = North American Indian languages
+ TRUETYPE_TAG('n', 'a', 'j', 0), // naj = Nalu
+ TRUETYPE_TAG('n', 'a', 'k', 0), // nak = Nakanai
+ TRUETYPE_TAG('n', 'a', 'l', 0), // nal = Nalik
+ TRUETYPE_TAG('n', 'a', 'm', 0), // nam = Nangikurrunggurr
+ TRUETYPE_TAG('n', 'a', 'n', 0), // nan = Min Nan Chinese
+ TRUETYPE_TAG('n', 'a', 'o', 0), // nao = Naaba
+ TRUETYPE_TAG('n', 'a', 'p', 0), // nap = Neapolitan
+ TRUETYPE_TAG('n', 'a', 'q', 0), // naq = Nama (Namibia)
+ TRUETYPE_TAG('n', 'a', 'r', 0), // nar = Iguta
+ TRUETYPE_TAG('n', 'a', 's', 0), // nas = Naasioi
+ TRUETYPE_TAG('n', 'a', 't', 0), // nat = Hungworo
+ TRUETYPE_TAG('n', 'a', 'w', 0), // naw = Nawuri
+ TRUETYPE_TAG('n', 'a', 'x', 0), // nax = Nakwi
+ TRUETYPE_TAG('n', 'a', 'y', 0), // nay = Narrinyeri
+ TRUETYPE_TAG('n', 'a', 'z', 0), // naz = Coatepec Nahuatl
+ TRUETYPE_TAG('n', 'b', 'a', 0), // nba = Nyemba
+ TRUETYPE_TAG('n', 'b', 'b', 0), // nbb = Ndoe
+ TRUETYPE_TAG('n', 'b', 'c', 0), // nbc = Chang Naga
+ TRUETYPE_TAG('n', 'b', 'd', 0), // nbd = Ngbinda
+ TRUETYPE_TAG('n', 'b', 'e', 0), // nbe = Konyak Naga
+ TRUETYPE_TAG('n', 'b', 'f', 0), // nbf = Naxi
+ TRUETYPE_TAG('n', 'b', 'g', 0), // nbg = Nagarchal
+ TRUETYPE_TAG('n', 'b', 'h', 0), // nbh = Ngamo
+ TRUETYPE_TAG('n', 'b', 'i', 0), // nbi = Mao Naga
+ TRUETYPE_TAG('n', 'b', 'j', 0), // nbj = Ngarinman
+ TRUETYPE_TAG('n', 'b', 'k', 0), // nbk = Nake
+ TRUETYPE_TAG('n', 'b', 'm', 0), // nbm = Ngbaka Ma'bo
+ TRUETYPE_TAG('n', 'b', 'n', 0), // nbn = Kuri
+ TRUETYPE_TAG('n', 'b', 'o', 0), // nbo = Nkukoli
+ TRUETYPE_TAG('n', 'b', 'p', 0), // nbp = Nnam
+ TRUETYPE_TAG('n', 'b', 'q', 0), // nbq = Nggem
+ TRUETYPE_TAG('n', 'b', 'r', 0), // nbr = Numana-Nunku-Gbantu-Numbu
+ TRUETYPE_TAG('n', 'b', 's', 0), // nbs = Namibian Sign Language
+ TRUETYPE_TAG('n', 'b', 't', 0), // nbt = Na
+ TRUETYPE_TAG('n', 'b', 'u', 0), // nbu = Rongmei Naga
+ TRUETYPE_TAG('n', 'b', 'v', 0), // nbv = Ngamambo
+ TRUETYPE_TAG('n', 'b', 'w', 0), // nbw = Southern Ngbandi
+ TRUETYPE_TAG('n', 'b', 'x', 0), // nbx = Ngura
+ TRUETYPE_TAG('n', 'b', 'y', 0), // nby = Ningera
+ TRUETYPE_TAG('n', 'c', 'a', 0), // nca = Iyo
+ TRUETYPE_TAG('n', 'c', 'b', 0), // ncb = Central Nicobarese
+ TRUETYPE_TAG('n', 'c', 'c', 0), // ncc = Ponam
+ TRUETYPE_TAG('n', 'c', 'd', 0), // ncd = Nachering
+ TRUETYPE_TAG('n', 'c', 'e', 0), // nce = Yale
+ TRUETYPE_TAG('n', 'c', 'f', 0), // ncf = Notsi
+ TRUETYPE_TAG('n', 'c', 'g', 0), // ncg = Nisga'a
+ TRUETYPE_TAG('n', 'c', 'h', 0), // nch = Central Huasteca Nahuatl
+ TRUETYPE_TAG('n', 'c', 'i', 0), // nci = Classical Nahuatl
+ TRUETYPE_TAG('n', 'c', 'j', 0), // ncj = Northern Puebla Nahuatl
+ TRUETYPE_TAG('n', 'c', 'k', 0), // nck = Nakara
+ TRUETYPE_TAG('n', 'c', 'l', 0), // ncl = Michoacán Nahuatl
+ TRUETYPE_TAG('n', 'c', 'm', 0), // ncm = Nambo
+ TRUETYPE_TAG('n', 'c', 'n', 0), // ncn = Nauna
+ TRUETYPE_TAG('n', 'c', 'o', 0), // nco = Sibe
+ TRUETYPE_TAG('n', 'c', 'p', 0), // ncp = Ndaktup
+ TRUETYPE_TAG('n', 'c', 'r', 0), // ncr = Ncane
+ TRUETYPE_TAG('n', 'c', 's', 0), // ncs = Nicaraguan Sign Language
+ TRUETYPE_TAG('n', 'c', 't', 0), // nct = Chothe Naga
+ TRUETYPE_TAG('n', 'c', 'u', 0), // ncu = Chumburung
+ TRUETYPE_TAG('n', 'c', 'x', 0), // ncx = Central Puebla Nahuatl
+ TRUETYPE_TAG('n', 'c', 'z', 0), // ncz = Natchez
+ TRUETYPE_TAG('n', 'd', 'a', 0), // nda = Ndasa
+ TRUETYPE_TAG('n', 'd', 'b', 0), // ndb = Kenswei Nsei
+ TRUETYPE_TAG('n', 'd', 'c', 0), // ndc = Ndau
+ TRUETYPE_TAG('n', 'd', 'd', 0), // ndd = Nde-Nsele-Nta
+ TRUETYPE_TAG('n', 'd', 'f', 0), // ndf = Nadruvian
+ TRUETYPE_TAG('n', 'd', 'g', 0), // ndg = Ndengereko
+ TRUETYPE_TAG('n', 'd', 'h', 0), // ndh = Ndali
+ TRUETYPE_TAG('n', 'd', 'i', 0), // ndi = Samba Leko
+ TRUETYPE_TAG('n', 'd', 'j', 0), // ndj = Ndamba
+ TRUETYPE_TAG('n', 'd', 'k', 0), // ndk = Ndaka
+ TRUETYPE_TAG('n', 'd', 'l', 0), // ndl = Ndolo
+ TRUETYPE_TAG('n', 'd', 'm', 0), // ndm = Ndam
+ TRUETYPE_TAG('n', 'd', 'n', 0), // ndn = Ngundi
+ TRUETYPE_TAG('n', 'd', 'p', 0), // ndp = Ndo
+ TRUETYPE_TAG('n', 'd', 'q', 0), // ndq = Ndombe
+ TRUETYPE_TAG('n', 'd', 'r', 0), // ndr = Ndoola
+ TRUETYPE_TAG('n', 'd', 's', 0), // nds = Low German
+ TRUETYPE_TAG('n', 'd', 't', 0), // ndt = Ndunga
+ TRUETYPE_TAG('n', 'd', 'u', 0), // ndu = Dugun
+ TRUETYPE_TAG('n', 'd', 'v', 0), // ndv = Ndut
+ TRUETYPE_TAG('n', 'd', 'w', 0), // ndw = Ndobo
+ TRUETYPE_TAG('n', 'd', 'x', 0), // ndx = Nduga
+ TRUETYPE_TAG('n', 'd', 'y', 0), // ndy = Lutos
+ TRUETYPE_TAG('n', 'd', 'z', 0), // ndz = Ndogo
+ TRUETYPE_TAG('n', 'e', 'a', 0), // nea = Eastern Ngad'a
+ TRUETYPE_TAG('n', 'e', 'b', 0), // neb = Toura (Côte d'Ivoire)
+ TRUETYPE_TAG('n', 'e', 'c', 0), // nec = Nedebang
+ TRUETYPE_TAG('n', 'e', 'd', 0), // ned = Nde-Gbite
+ TRUETYPE_TAG('n', 'e', 'e', 0), // nee = Nêlêmwa-Nixumwak
+ TRUETYPE_TAG('n', 'e', 'f', 0), // nef = Nefamese
+ TRUETYPE_TAG('n', 'e', 'g', 0), // neg = Negidal
+ TRUETYPE_TAG('n', 'e', 'h', 0), // neh = Nyenkha
+ TRUETYPE_TAG('n', 'e', 'i', 0), // nei = Neo-Hittite
+ TRUETYPE_TAG('n', 'e', 'j', 0), // nej = Neko
+ TRUETYPE_TAG('n', 'e', 'k', 0), // nek = Neku
+ TRUETYPE_TAG('n', 'e', 'm', 0), // nem = Nemi
+ TRUETYPE_TAG('n', 'e', 'n', 0), // nen = Nengone
+ TRUETYPE_TAG('n', 'e', 'o', 0), // neo = Ná-Meo
+ TRUETYPE_TAG('n', 'e', 'q', 0), // neq = North Central Mixe
+ TRUETYPE_TAG('n', 'e', 'r', 0), // ner = Yahadian
+ TRUETYPE_TAG('n', 'e', 's', 0), // nes = Bhoti Kinnauri
+ TRUETYPE_TAG('n', 'e', 't', 0), // net = Nete
+ TRUETYPE_TAG('n', 'e', 'v', 0), // nev = Nyaheun
+ TRUETYPE_TAG('n', 'e', 'w', 0), // new = Newari
+ TRUETYPE_TAG('n', 'e', 'x', 0), // nex = Neme
+ TRUETYPE_TAG('n', 'e', 'y', 0), // ney = Neyo
+ TRUETYPE_TAG('n', 'e', 'z', 0), // nez = Nez Perce
+ TRUETYPE_TAG('n', 'f', 'a', 0), // nfa = Dhao
+ TRUETYPE_TAG('n', 'f', 'd', 0), // nfd = Ahwai
+ TRUETYPE_TAG('n', 'f', 'l', 0), // nfl = Ayiwo
+ TRUETYPE_TAG('n', 'f', 'r', 0), // nfr = Nafaanra
+ TRUETYPE_TAG('n', 'f', 'u', 0), // nfu = Mfumte
+ TRUETYPE_TAG('n', 'g', 'a', 0), // nga = Ngbaka
+ TRUETYPE_TAG('n', 'g', 'b', 0), // ngb = Northern Ngbandi
+ TRUETYPE_TAG('n', 'g', 'c',
+ 0), // ngc = Ngombe (Democratic Republic of Congo)
+ TRUETYPE_TAG('n', 'g', 'd', 0), // ngd = Ngando (Central African Republic)
+ TRUETYPE_TAG('n', 'g', 'e', 0), // nge = Ngemba
+ TRUETYPE_TAG('n', 'g', 'f', 0), // ngf = Trans-New Guinea languages
+ TRUETYPE_TAG('n', 'g', 'g', 0), // ngg = Ngbaka Manza
+ TRUETYPE_TAG('n', 'g', 'h', 0), // ngh = N/u
+ TRUETYPE_TAG('n', 'g', 'i', 0), // ngi = Ngizim
+ TRUETYPE_TAG('n', 'g', 'j', 0), // ngj = Ngie
+ TRUETYPE_TAG('n', 'g', 'k', 0), // ngk = Ngalkbun
+ TRUETYPE_TAG('n', 'g', 'l', 0), // ngl = Lomwe
+ TRUETYPE_TAG('n', 'g', 'm', 0), // ngm = Ngatik Men's Creole
+ TRUETYPE_TAG('n', 'g', 'n', 0), // ngn = Ngwo
+ TRUETYPE_TAG('n', 'g', 'o', 0), // ngo = Ngoni
+ TRUETYPE_TAG('n', 'g', 'p', 0), // ngp = Ngulu
+ TRUETYPE_TAG('n', 'g', 'q', 0), // ngq = Ngurimi
+ TRUETYPE_TAG('n', 'g', 'r', 0), // ngr = Nanggu
+ TRUETYPE_TAG('n', 'g', 's', 0), // ngs = Gvoko
+ TRUETYPE_TAG('n', 'g', 't', 0), // ngt = Ngeq
+ TRUETYPE_TAG('n', 'g', 'u', 0), // ngu = Guerrero Nahuatl
+ TRUETYPE_TAG('n', 'g', 'v', 0), // ngv = Nagumi
+ TRUETYPE_TAG('n', 'g', 'w', 0), // ngw = Ngwaba
+ TRUETYPE_TAG('n', 'g', 'x', 0), // ngx = Nggwahyi
+ TRUETYPE_TAG('n', 'g', 'y', 0), // ngy = Tibea
+ TRUETYPE_TAG('n', 'g', 'z', 0), // ngz = Ngungwel
+ TRUETYPE_TAG('n', 'h', 'a', 0), // nha = Nhanda
+ TRUETYPE_TAG('n', 'h', 'b', 0), // nhb = Beng
+ TRUETYPE_TAG('n', 'h', 'c', 0), // nhc = Tabasco Nahuatl
+ TRUETYPE_TAG('n', 'h', 'd', 0), // nhd = Chiripá
+ TRUETYPE_TAG('n', 'h', 'e', 0), // nhe = Eastern Huasteca Nahuatl
+ TRUETYPE_TAG('n', 'h', 'f', 0), // nhf = Nhuwala
+ TRUETYPE_TAG('n', 'h', 'g', 0), // nhg = Tetelcingo Nahuatl
+ TRUETYPE_TAG('n', 'h', 'h', 0), // nhh = Nahari
+ TRUETYPE_TAG('n', 'h', 'i',
+ 0), // nhi = Zacatlán-Ahuacatlán-Tepetzintla Nahuatl
+ TRUETYPE_TAG('n', 'h', 'k', 0), // nhk = Isthmus-Cosoleacaque Nahuatl
+ TRUETYPE_TAG('n', 'h', 'm', 0), // nhm = Morelos Nahuatl
+ TRUETYPE_TAG('n', 'h', 'n', 0), // nhn = Central Nahuatl
+ TRUETYPE_TAG('n', 'h', 'o', 0), // nho = Takuu
+ TRUETYPE_TAG('n', 'h', 'p', 0), // nhp = Isthmus-Pajapan Nahuatl
+ TRUETYPE_TAG('n', 'h', 'q', 0), // nhq = Huaxcaleca Nahuatl
+ TRUETYPE_TAG('n', 'h', 'r', 0), // nhr = Naro
+ TRUETYPE_TAG('n', 'h', 't', 0), // nht = Ometepec Nahuatl
+ TRUETYPE_TAG('n', 'h', 'u', 0), // nhu = Noone
+ TRUETYPE_TAG('n', 'h', 'v', 0), // nhv = Temascaltepec Nahuatl
+ TRUETYPE_TAG('n', 'h', 'w', 0), // nhw = Western Huasteca Nahuatl
+ TRUETYPE_TAG('n', 'h', 'x', 0), // nhx = Isthmus-Mecayapan Nahuatl
+ TRUETYPE_TAG('n', 'h', 'y', 0), // nhy = Northern Oaxaca Nahuatl
+ TRUETYPE_TAG('n', 'h', 'z', 0), // nhz = Santa María La Alta Nahuatl
+ TRUETYPE_TAG('n', 'i', 'a', 0), // nia = Nias
+ TRUETYPE_TAG('n', 'i', 'b', 0), // nib = Nakame
+ TRUETYPE_TAG('n', 'i', 'c', 0), // nic = Niger-Kordofanian languages
+ TRUETYPE_TAG('n', 'i', 'd', 0), // nid = Ngandi
+ TRUETYPE_TAG('n', 'i', 'e', 0), // nie = Niellim
+ TRUETYPE_TAG('n', 'i', 'f', 0), // nif = Nek
+ TRUETYPE_TAG('n', 'i', 'g', 0), // nig = Ngalakan
+ TRUETYPE_TAG('n', 'i', 'h', 0), // nih = Nyiha (Tanzania)
+ TRUETYPE_TAG('n', 'i', 'i', 0), // nii = Nii
+ TRUETYPE_TAG('n', 'i', 'j', 0), // nij = Ngaju
+ TRUETYPE_TAG('n', 'i', 'k', 0), // nik = Southern Nicobarese
+ TRUETYPE_TAG('n', 'i', 'l', 0), // nil = Nila
+ TRUETYPE_TAG('n', 'i', 'm', 0), // nim = Nilamba
+ TRUETYPE_TAG('n', 'i', 'n', 0), // nin = Ninzo
+ TRUETYPE_TAG('n', 'i', 'o', 0), // nio = Nganasan
+ TRUETYPE_TAG('n', 'i', 'q', 0), // niq = Nandi
+ TRUETYPE_TAG('n', 'i', 'r', 0), // nir = Nimboran
+ TRUETYPE_TAG('n', 'i', 's', 0), // nis = Nimi
+ TRUETYPE_TAG('n', 'i', 't', 0), // nit = Southeastern Kolami
+ TRUETYPE_TAG('n', 'i', 'u', 0), // niu = Niuean
+ TRUETYPE_TAG('n', 'i', 'v', 0), // niv = Gilyak
+ TRUETYPE_TAG('n', 'i', 'w', 0), // niw = Nimo
+ TRUETYPE_TAG('n', 'i', 'x', 0), // nix = Hema
+ TRUETYPE_TAG('n', 'i', 'y', 0), // niy = Ngiti
+ TRUETYPE_TAG('n', 'i', 'z', 0), // niz = Ningil
+ TRUETYPE_TAG('n', 'j', 'a', 0), // nja = Nzanyi
+ TRUETYPE_TAG('n', 'j', 'b', 0), // njb = Nocte Naga
+ TRUETYPE_TAG('n', 'j', 'd', 0), // njd = Ndonde Hamba
+ TRUETYPE_TAG('n', 'j', 'h', 0), // njh = Lotha Naga
+ TRUETYPE_TAG('n', 'j', 'i', 0), // nji = Gudanji
+ TRUETYPE_TAG('n', 'j', 'j', 0), // njj = Njen
+ TRUETYPE_TAG('n', 'j', 'l', 0), // njl = Njalgulgule
+ TRUETYPE_TAG('n', 'j', 'm', 0), // njm = Angami Naga
+ TRUETYPE_TAG('n', 'j', 'n', 0), // njn = Liangmai Naga
+ TRUETYPE_TAG('n', 'j', 'o', 0), // njo = Ao Naga
+ TRUETYPE_TAG('n', 'j', 'r', 0), // njr = Njerep
+ TRUETYPE_TAG('n', 'j', 's', 0), // njs = Nisa
+ TRUETYPE_TAG('n', 'j', 't', 0), // njt = Ndyuka-Trio Pidgin
+ TRUETYPE_TAG('n', 'j', 'u', 0), // nju = Ngadjunmaya
+ TRUETYPE_TAG('n', 'j', 'x', 0), // njx = Kunyi
+ TRUETYPE_TAG('n', 'j', 'y', 0), // njy = Njyem
+ TRUETYPE_TAG('n', 'k', 'a', 0), // nka = Nkoya
+ TRUETYPE_TAG('n', 'k', 'b', 0), // nkb = Khoibu Naga
+ TRUETYPE_TAG('n', 'k', 'c', 0), // nkc = Nkongho
+ TRUETYPE_TAG('n', 'k', 'd', 0), // nkd = Koireng
+ TRUETYPE_TAG('n', 'k', 'e', 0), // nke = Duke
+ TRUETYPE_TAG('n', 'k', 'f', 0), // nkf = Inpui Naga
+ TRUETYPE_TAG('n', 'k', 'g', 0), // nkg = Nekgini
+ TRUETYPE_TAG('n', 'k', 'h', 0), // nkh = Khezha Naga
+ TRUETYPE_TAG('n', 'k', 'i', 0), // nki = Thangal Naga
+ TRUETYPE_TAG('n', 'k', 'j', 0), // nkj = Nakai
+ TRUETYPE_TAG('n', 'k', 'k', 0), // nkk = Nokuku
+ TRUETYPE_TAG('n', 'k', 'm', 0), // nkm = Namat
+ TRUETYPE_TAG('n', 'k', 'n', 0), // nkn = Nkangala
+ TRUETYPE_TAG('n', 'k', 'o', 0), // nko = Nkonya
+ TRUETYPE_TAG('n', 'k', 'p', 0), // nkp = Niuatoputapu
+ TRUETYPE_TAG('n', 'k', 'q', 0), // nkq = Nkami
+ TRUETYPE_TAG('n', 'k', 'r', 0), // nkr = Nukuoro
+ TRUETYPE_TAG('n', 'k', 's', 0), // nks = North Asmat
+ TRUETYPE_TAG('n', 'k', 't', 0), // nkt = Nyika (Tanzania)
+ TRUETYPE_TAG('n', 'k', 'u', 0), // nku = Bouna Kulango
+ TRUETYPE_TAG('n', 'k', 'v', 0), // nkv = Nyika (Malawi and Zambia)
+ TRUETYPE_TAG('n', 'k', 'w', 0), // nkw = Nkutu
+ TRUETYPE_TAG('n', 'k', 'x', 0), // nkx = Nkoroo
+ TRUETYPE_TAG('n', 'k', 'z', 0), // nkz = Nkari
+ TRUETYPE_TAG('n', 'l', 'a', 0), // nla = Ngombale
+ TRUETYPE_TAG('n', 'l', 'c', 0), // nlc = Nalca
+ TRUETYPE_TAG('n', 'l', 'e', 0), // nle = East Nyala
+ TRUETYPE_TAG('n', 'l', 'g', 0), // nlg = Gela
+ TRUETYPE_TAG('n', 'l', 'i', 0), // nli = Grangali
+ TRUETYPE_TAG('n', 'l', 'j', 0), // nlj = Nyali
+ TRUETYPE_TAG('n', 'l', 'k', 0), // nlk = Ninia Yali
+ TRUETYPE_TAG('n', 'l', 'l', 0), // nll = Nihali
+ TRUETYPE_TAG('n', 'l', 'n', 0), // nln = Durango Nahuatl
+ TRUETYPE_TAG('n', 'l', 'o', 0), // nlo = Ngul
+ TRUETYPE_TAG('n', 'l', 'r', 0), // nlr = Ngarla
+ TRUETYPE_TAG('n', 'l', 'u', 0), // nlu = Nchumbulu
+ TRUETYPE_TAG('n', 'l', 'v', 0), // nlv = Orizaba Nahuatl
+ TRUETYPE_TAG('n', 'l', 'x', 0), // nlx = Nahali
+ TRUETYPE_TAG('n', 'l', 'y', 0), // nly = Nyamal
+ TRUETYPE_TAG('n', 'l', 'z', 0), // nlz = Nalögo
+ TRUETYPE_TAG('n', 'm', 'a', 0), // nma = Maram Naga
+ TRUETYPE_TAG('n', 'm', 'b', 0), // nmb = Big Nambas
+ TRUETYPE_TAG('n', 'm', 'c', 0), // nmc = Ngam
+ TRUETYPE_TAG('n', 'm', 'd', 0), // nmd = Ndumu
+ TRUETYPE_TAG('n', 'm', 'e', 0), // nme = Mzieme Naga
+ TRUETYPE_TAG('n', 'm', 'f', 0), // nmf = Tangkhul Naga
+ TRUETYPE_TAG('n', 'm', 'g', 0), // nmg = Kwasio
+ TRUETYPE_TAG('n', 'm', 'h', 0), // nmh = Monsang Naga
+ TRUETYPE_TAG('n', 'm', 'i', 0), // nmi = Nyam
+ TRUETYPE_TAG('n', 'm', 'j', 0), // nmj = Ngombe (Central African Republic)
+ TRUETYPE_TAG('n', 'm', 'k', 0), // nmk = Namakura
+ TRUETYPE_TAG('n', 'm', 'l', 0), // nml = Ndemli
+ TRUETYPE_TAG('n', 'm', 'm', 0), // nmm = Manangba
+ TRUETYPE_TAG('n', 'm', 'n', 0), // nmn = !Xóõ
+ TRUETYPE_TAG('n', 'm', 'o', 0), // nmo = Moyon Naga
+ TRUETYPE_TAG('n', 'm', 'p', 0), // nmp = Nimanbur
+ TRUETYPE_TAG('n', 'm', 'q', 0), // nmq = Nambya
+ TRUETYPE_TAG('n', 'm', 'r', 0), // nmr = Nimbari
+ TRUETYPE_TAG('n', 'm', 's', 0), // nms = Letemboi
+ TRUETYPE_TAG('n', 'm', 't', 0), // nmt = Namonuito
+ TRUETYPE_TAG('n', 'm', 'u', 0), // nmu = Northeast Maidu
+ TRUETYPE_TAG('n', 'm', 'v', 0), // nmv = Ngamini
+ TRUETYPE_TAG('n', 'm', 'w', 0), // nmw = Nimoa
+ TRUETYPE_TAG('n', 'm', 'x', 0), // nmx = Nama (Papua New Guinea)
+ TRUETYPE_TAG('n', 'm', 'y', 0), // nmy = Namuyi
+ TRUETYPE_TAG('n', 'm', 'z', 0), // nmz = Nawdm
+ TRUETYPE_TAG('n', 'n', 'a', 0), // nna = Nyangumarta
+ TRUETYPE_TAG('n', 'n', 'b', 0), // nnb = Nande
+ TRUETYPE_TAG('n', 'n', 'c', 0), // nnc = Nancere
+ TRUETYPE_TAG('n', 'n', 'd', 0), // nnd = West Ambae
+ TRUETYPE_TAG('n', 'n', 'e', 0), // nne = Ngandyera
+ TRUETYPE_TAG('n', 'n', 'f', 0), // nnf = Ngaing
+ TRUETYPE_TAG('n', 'n', 'g', 0), // nng = Maring Naga
+ TRUETYPE_TAG('n', 'n', 'h', 0), // nnh = Ngiemboon
+ TRUETYPE_TAG('n', 'n', 'i', 0), // nni = North Nuaulu
+ TRUETYPE_TAG('n', 'n', 'j', 0), // nnj = Nyangatom
+ TRUETYPE_TAG('n', 'n', 'k', 0), // nnk = Nankina
+ TRUETYPE_TAG('n', 'n', 'l', 0), // nnl = Northern Rengma Naga
+ TRUETYPE_TAG('n', 'n', 'm', 0), // nnm = Namia
+ TRUETYPE_TAG('n', 'n', 'n', 0), // nnn = Ngete
+ TRUETYPE_TAG('n', 'n', 'p', 0), // nnp = Wancho Naga
+ TRUETYPE_TAG('n', 'n', 'q', 0), // nnq = Ngindo
+ TRUETYPE_TAG('n', 'n', 'r', 0), // nnr = Narungga
+ TRUETYPE_TAG('n', 'n', 's', 0), // nns = Ningye
+ TRUETYPE_TAG('n', 'n', 't', 0), // nnt = Nanticoke
+ TRUETYPE_TAG('n', 'n', 'u', 0), // nnu = Dwang
+ TRUETYPE_TAG('n', 'n', 'v', 0), // nnv = Nugunu (Australia)
+ TRUETYPE_TAG('n', 'n', 'w', 0), // nnw = Southern Nuni
+ TRUETYPE_TAG('n', 'n', 'x', 0), // nnx = Ngong
+ TRUETYPE_TAG('n', 'n', 'y', 0), // nny = Nyangga
+ TRUETYPE_TAG('n', 'n', 'z', 0), // nnz = Nda'nda'
+ TRUETYPE_TAG('n', 'o', 'a', 0), // noa = Woun Meu
+ TRUETYPE_TAG('n', 'o', 'c', 0), // noc = Nuk
+ TRUETYPE_TAG('n', 'o', 'd', 0), // nod = Northern Thai
+ TRUETYPE_TAG('n', 'o', 'e', 0), // noe = Nimadi
+ TRUETYPE_TAG('n', 'o', 'f', 0), // nof = Nomane
+ TRUETYPE_TAG('n', 'o', 'g', 0), // nog = Nogai
+ TRUETYPE_TAG('n', 'o', 'h', 0), // noh = Nomu
+ TRUETYPE_TAG('n', 'o', 'i', 0), // noi = Noiri
+ TRUETYPE_TAG('n', 'o', 'j', 0), // noj = Nonuya
+ TRUETYPE_TAG('n', 'o', 'k', 0), // nok = Nooksack
+ TRUETYPE_TAG('n', 'o', 'm', 0), // nom = Nocamán
+ TRUETYPE_TAG('n', 'o', 'n', 0), // non = Old Norse
+ TRUETYPE_TAG('n', 'o', 'o', 0), // noo = Nootka
+ TRUETYPE_TAG('n', 'o', 'p', 0), // nop = Numanggang
+ TRUETYPE_TAG('n', 'o', 'q', 0), // noq = Ngongo
+ TRUETYPE_TAG('n', 'o', 's', 0), // nos = Eastern Nisu
+ TRUETYPE_TAG('n', 'o', 't', 0), // not = Nomatsiguenga
+ TRUETYPE_TAG('n', 'o', 'u', 0), // nou = Ewage-Notu
+ TRUETYPE_TAG('n', 'o', 'v', 0), // nov = Novial
+ TRUETYPE_TAG('n', 'o', 'w', 0), // now = Nyambo
+ TRUETYPE_TAG('n', 'o', 'y', 0), // noy = Noy
+ TRUETYPE_TAG('n', 'o', 'z', 0), // noz = Nayi
+ TRUETYPE_TAG('n', 'p', 'a', 0), // npa = Nar Phu
+ TRUETYPE_TAG('n', 'p', 'b', 0), // npb = Nupbikha
+ TRUETYPE_TAG('n', 'p', 'h', 0), // nph = Phom Naga
+ TRUETYPE_TAG('n', 'p', 'l', 0), // npl = Southeastern Puebla Nahuatl
+ TRUETYPE_TAG('n', 'p', 'n', 0), // npn = Mondropolon
+ TRUETYPE_TAG('n', 'p', 'o', 0), // npo = Pochuri Naga
+ TRUETYPE_TAG('n', 'p', 's', 0), // nps = Nipsan
+ TRUETYPE_TAG('n', 'p', 'u', 0), // npu = Puimei Naga
+ TRUETYPE_TAG('n', 'p', 'y', 0), // npy = Napu
+ TRUETYPE_TAG('n', 'q', 'g', 0), // nqg = Southern Nago
+ TRUETYPE_TAG('n', 'q', 'k', 0), // nqk = Kura Ede Nago
+ TRUETYPE_TAG('n', 'q', 'm', 0), // nqm = Ndom
+ TRUETYPE_TAG('n', 'q', 'n', 0), // nqn = Nen
+ TRUETYPE_TAG('n', 'q', 'o', 0), // nqo = N'Ko
+ TRUETYPE_TAG('n', 'r', 'a', 0), // nra = Ngom
+ TRUETYPE_TAG('n', 'r', 'b', 0), // nrb = Nara
+ TRUETYPE_TAG('n', 'r', 'c', 0), // nrc = Noric
+ TRUETYPE_TAG('n', 'r', 'e', 0), // nre = Southern Rengma Naga
+ TRUETYPE_TAG('n', 'r', 'g', 0), // nrg = Narango
+ TRUETYPE_TAG('n', 'r', 'i', 0), // nri = Chokri Naga
+ TRUETYPE_TAG('n', 'r', 'l', 0), // nrl = Ngarluma
+ TRUETYPE_TAG('n', 'r', 'm', 0), // nrm = Narom
+ TRUETYPE_TAG('n', 'r', 'n', 0), // nrn = Norn
+ TRUETYPE_TAG('n', 'r', 'p', 0), // nrp = North Picene
+ TRUETYPE_TAG('n', 'r', 'r', 0), // nrr = Norra
+ TRUETYPE_TAG('n', 'r', 't', 0), // nrt = Northern Kalapuya
+ TRUETYPE_TAG('n', 'r', 'u', 0), // nru = Narua
+ TRUETYPE_TAG('n', 'r', 'x', 0), // nrx = Ngurmbur
+ TRUETYPE_TAG('n', 'r', 'z', 0), // nrz = Lala
+ TRUETYPE_TAG('n', 's', 'a', 0), // nsa = Sangtam Naga
+ TRUETYPE_TAG('n', 's', 'c', 0), // nsc = Nshi
+ TRUETYPE_TAG('n', 's', 'd', 0), // nsd = Southern Nisu
+ TRUETYPE_TAG('n', 's', 'e', 0), // nse = Nsenga
+ TRUETYPE_TAG('n', 's', 'g', 0), // nsg = Ngasa
+ TRUETYPE_TAG('n', 's', 'h', 0), // nsh = Ngoshie
+ TRUETYPE_TAG('n', 's', 'i', 0), // nsi = Nigerian Sign Language
+ TRUETYPE_TAG('n', 's', 'k', 0), // nsk = Naskapi
+ TRUETYPE_TAG('n', 's', 'l', 0), // nsl = Norwegian Sign Language
+ TRUETYPE_TAG('n', 's', 'm', 0), // nsm = Sumi Naga
+ TRUETYPE_TAG('n', 's', 'n', 0), // nsn = Nehan
+ TRUETYPE_TAG('n', 's', 'o', 0), // nso = Pedi
+ TRUETYPE_TAG('n', 's', 'p', 0), // nsp = Nepalese Sign Language
+ TRUETYPE_TAG('n', 's', 'q', 0), // nsq = Northern Sierra Miwok
+ TRUETYPE_TAG('n', 's', 'r', 0), // nsr = Maritime Sign Language
+ TRUETYPE_TAG('n', 's', 's', 0), // nss = Nali
+ TRUETYPE_TAG('n', 's', 't', 0), // nst = Tase Naga
+ TRUETYPE_TAG('n', 's', 'u', 0), // nsu = Sierra Negra Nahuatl
+ TRUETYPE_TAG('n', 's', 'v', 0), // nsv = Southwestern Nisu
+ TRUETYPE_TAG('n', 's', 'w', 0), // nsw = Navut
+ TRUETYPE_TAG('n', 's', 'x', 0), // nsx = Nsongo
+ TRUETYPE_TAG('n', 's', 'y', 0), // nsy = Nasal
+ TRUETYPE_TAG('n', 's', 'z', 0), // nsz = Nisenan
+ TRUETYPE_TAG('n', 't', 'e', 0), // nte = Nathembo
+ TRUETYPE_TAG('n', 't', 'i', 0), // nti = Natioro
+ TRUETYPE_TAG('n', 't', 'j', 0), // ntj = Ngaanyatjarra
+ TRUETYPE_TAG('n', 't', 'k', 0), // ntk = Ikoma-Nata-Isenye
+ TRUETYPE_TAG('n', 't', 'm', 0), // ntm = Nateni
+ TRUETYPE_TAG('n', 't', 'o', 0), // nto = Ntomba
+ TRUETYPE_TAG('n', 't', 'p', 0), // ntp = Northern Tepehuan
+ TRUETYPE_TAG('n', 't', 'r', 0), // ntr = Delo
+ TRUETYPE_TAG('n', 't', 's', 0), // nts = Natagaimas
+ TRUETYPE_TAG('n', 't', 'u', 0), // ntu = Natügu
+ TRUETYPE_TAG('n', 't', 'w', 0), // ntw = Nottoway
+ TRUETYPE_TAG('n', 't', 'y', 0), // nty = Mantsi
+ TRUETYPE_TAG('n', 't', 'z', 0), // ntz = Natanzi
+ TRUETYPE_TAG('n', 'u', 'a', 0), // nua = Yuaga
+ TRUETYPE_TAG('n', 'u', 'b', 0), // nub = Nubian languages
+ TRUETYPE_TAG('n', 'u', 'c', 0), // nuc = Nukuini
+ TRUETYPE_TAG('n', 'u', 'd', 0), // nud = Ngala
+ TRUETYPE_TAG('n', 'u', 'e', 0), // nue = Ngundu
+ TRUETYPE_TAG('n', 'u', 'f', 0), // nuf = Nusu
+ TRUETYPE_TAG('n', 'u', 'g', 0), // nug = Nungali
+ TRUETYPE_TAG('n', 'u', 'h', 0), // nuh = Ndunda
+ TRUETYPE_TAG('n', 'u', 'i', 0), // nui = Ngumbi
+ TRUETYPE_TAG('n', 'u', 'j', 0), // nuj = Nyole
+ TRUETYPE_TAG('n', 'u', 'k', 0), // nuk = Nuu-chah-nulth
+ TRUETYPE_TAG('n', 'u', 'l', 0), // nul = Nusa Laut
+ TRUETYPE_TAG('n', 'u', 'm', 0), // num = Niuafo'ou
+ TRUETYPE_TAG('n', 'u', 'n', 0), // nun = Anong
+ TRUETYPE_TAG('n', 'u', 'o', 0), // nuo = Nguôn
+ TRUETYPE_TAG('n', 'u', 'p', 0), // nup = Nupe-Nupe-Tako
+ TRUETYPE_TAG('n', 'u', 'q', 0), // nuq = Nukumanu
+ TRUETYPE_TAG('n', 'u', 'r', 0), // nur = Nukuria
+ TRUETYPE_TAG('n', 'u', 's', 0), // nus = Nuer
+ TRUETYPE_TAG('n', 'u', 't', 0), // nut = Nung (Viet Nam)
+ TRUETYPE_TAG('n', 'u', 'u', 0), // nuu = Ngbundu
+ TRUETYPE_TAG('n', 'u', 'v', 0), // nuv = Northern Nuni
+ TRUETYPE_TAG('n', 'u', 'w', 0), // nuw = Nguluwan
+ TRUETYPE_TAG('n', 'u', 'x', 0), // nux = Mehek
+ TRUETYPE_TAG('n', 'u', 'y', 0), // nuy = Nunggubuyu
+ TRUETYPE_TAG('n', 'u', 'z', 0), // nuz = Tlamacazapa Nahuatl
+ TRUETYPE_TAG('n', 'v', 'h', 0), // nvh = Nasarian
+ TRUETYPE_TAG('n', 'v', 'm', 0), // nvm = Namiae
+ TRUETYPE_TAG('n', 'w', 'a', 0), // nwa = Nawathinehena
+ TRUETYPE_TAG('n', 'w', 'b', 0), // nwb = Nyabwa
+ TRUETYPE_TAG('n', 'w', 'c', 0), // nwc = Classical Newari
+ TRUETYPE_TAG('n', 'w', 'e', 0), // nwe = Ngwe
+ TRUETYPE_TAG('n', 'w', 'i', 0), // nwi = Southwest Tanna
+ TRUETYPE_TAG('n', 'w', 'm', 0), // nwm = Nyamusa-Molo
+ TRUETYPE_TAG('n', 'w', 'r', 0), // nwr = Nawaru
+ TRUETYPE_TAG('n', 'w', 'x', 0), // nwx = Middle Newar
+ TRUETYPE_TAG('n', 'w', 'y', 0), // nwy = Nottoway-Meherrin
+ TRUETYPE_TAG('n', 'x', 'a', 0), // nxa = Nauete
+ TRUETYPE_TAG('n', 'x', 'd',
+ 0), // nxd = Ngando (Democratic Republic of Congo)
+ TRUETYPE_TAG('n', 'x', 'e', 0), // nxe = Nage
+ TRUETYPE_TAG('n', 'x', 'g', 0), // nxg = Ngad'a
+ TRUETYPE_TAG('n', 'x', 'i', 0), // nxi = Nindi
+ TRUETYPE_TAG('n', 'x', 'l', 0), // nxl = South Nuaulu
+ TRUETYPE_TAG('n', 'x', 'm', 0), // nxm = Numidian
+ TRUETYPE_TAG('n', 'x', 'n', 0), // nxn = Ngawun
+ TRUETYPE_TAG('n', 'x', 'q', 0), // nxq = Naxi
+ TRUETYPE_TAG('n', 'x', 'r', 0), // nxr = Ninggerum
+ TRUETYPE_TAG('n', 'x', 'u', 0), // nxu = Narau
+ TRUETYPE_TAG('n', 'x', 'x', 0), // nxx = Nafri
+ TRUETYPE_TAG('n', 'y', 'b', 0), // nyb = Nyangbo
+ TRUETYPE_TAG('n', 'y', 'c', 0), // nyc = Nyanga-li
+ TRUETYPE_TAG('n', 'y', 'd', 0), // nyd = Nyore
+ TRUETYPE_TAG('n', 'y', 'e', 0), // nye = Nyengo
+ TRUETYPE_TAG('n', 'y', 'f', 0), // nyf = Giryama
+ TRUETYPE_TAG('n', 'y', 'g', 0), // nyg = Nyindu
+ TRUETYPE_TAG('n', 'y', 'h', 0), // nyh = Nyigina
+ TRUETYPE_TAG('n', 'y', 'i', 0), // nyi = Ama (Sudan)
+ TRUETYPE_TAG('n', 'y', 'j', 0), // nyj = Nyanga
+ TRUETYPE_TAG('n', 'y', 'k', 0), // nyk = Nyaneka
+ TRUETYPE_TAG('n', 'y', 'l', 0), // nyl = Nyeu
+ TRUETYPE_TAG('n', 'y', 'm', 0), // nym = Nyamwezi
+ TRUETYPE_TAG('n', 'y', 'n', 0), // nyn = Nyankole
+ TRUETYPE_TAG('n', 'y', 'o', 0), // nyo = Nyoro
+ TRUETYPE_TAG('n', 'y', 'p', 0), // nyp = Nyang'i
+ TRUETYPE_TAG('n', 'y', 'q', 0), // nyq = Nayini
+ TRUETYPE_TAG('n', 'y', 'r', 0), // nyr = Nyiha (Malawi)
+ TRUETYPE_TAG('n', 'y', 's', 0), // nys = Nyunga
+ TRUETYPE_TAG('n', 'y', 't', 0), // nyt = Nyawaygi
+ TRUETYPE_TAG('n', 'y', 'u', 0), // nyu = Nyungwe
+ TRUETYPE_TAG('n', 'y', 'v', 0), // nyv = Nyulnyul
+ TRUETYPE_TAG('n', 'y', 'w', 0), // nyw = Nyaw
+ TRUETYPE_TAG('n', 'y', 'x', 0), // nyx = Nganyaywana
+ TRUETYPE_TAG('n', 'y', 'y', 0), // nyy = Nyakyusa-Ngonde
+ TRUETYPE_TAG('n', 'z', 'a', 0), // nza = Tigon Mbembe
+ TRUETYPE_TAG('n', 'z', 'b', 0), // nzb = Njebi
+ TRUETYPE_TAG('n', 'z', 'i', 0), // nzi = Nzima
+ TRUETYPE_TAG('n', 'z', 'k', 0), // nzk = Nzakara
+ TRUETYPE_TAG('n', 'z', 'm', 0), // nzm = Zeme Naga
+ TRUETYPE_TAG('n', 'z', 's', 0), // nzs = New Zealand Sign Language
+ TRUETYPE_TAG('n', 'z', 'u', 0), // nzu = Teke-Nzikou
+ TRUETYPE_TAG('n', 'z', 'y', 0), // nzy = Nzakambay
+ TRUETYPE_TAG('n', 'z', 'z', 0), // nzz = Nanga Dama Dogon
+ TRUETYPE_TAG('o', 'a', 'a', 0), // oaa = Orok
+ TRUETYPE_TAG('o', 'a', 'c', 0), // oac = Oroch
+ TRUETYPE_TAG('o', 'a', 'r', 0), // oar = Old Aramaic (up to 700 BCE)
+ TRUETYPE_TAG('o', 'a', 'v', 0), // oav = Old Avar
+ TRUETYPE_TAG('o', 'b', 'i', 0), // obi = Obispeño
+ TRUETYPE_TAG('o', 'b', 'k', 0), // obk = Southern Bontok
+ TRUETYPE_TAG('o', 'b', 'l', 0), // obl = Oblo
+ TRUETYPE_TAG('o', 'b', 'm', 0), // obm = Moabite
+ TRUETYPE_TAG('o', 'b', 'o', 0), // obo = Obo Manobo
+ TRUETYPE_TAG('o', 'b', 'r', 0), // obr = Old Burmese
+ TRUETYPE_TAG('o', 'b', 't', 0), // obt = Old Breton
+ TRUETYPE_TAG('o', 'b', 'u', 0), // obu = Obulom
+ TRUETYPE_TAG('o', 'c', 'a', 0), // oca = Ocaina
+ TRUETYPE_TAG('o', 'c', 'h', 0), // och = Old Chinese
+ TRUETYPE_TAG('o', 'c', 'o', 0), // oco = Old Cornish
+ TRUETYPE_TAG('o', 'c', 'u', 0), // ocu = Atzingo Matlatzinca
+ TRUETYPE_TAG('o', 'd', 'a', 0), // oda = Odut
+ TRUETYPE_TAG('o', 'd', 'k', 0), // odk = Od
+ TRUETYPE_TAG('o', 'd', 't', 0), // odt = Old Dutch
+ TRUETYPE_TAG('o', 'd', 'u', 0), // odu = Odual
+ TRUETYPE_TAG('o', 'f', 'o', 0), // ofo = Ofo
+ TRUETYPE_TAG('o', 'f', 's', 0), // ofs = Old Frisian
+ TRUETYPE_TAG('o', 'f', 'u', 0), // ofu = Efutop
+ TRUETYPE_TAG('o', 'g', 'b', 0), // ogb = Ogbia
+ TRUETYPE_TAG('o', 'g', 'c', 0), // ogc = Ogbah
+ TRUETYPE_TAG('o', 'g', 'e', 0), // oge = Old Georgian
+ TRUETYPE_TAG('o', 'g', 'g', 0), // ogg = Ogbogolo
+ TRUETYPE_TAG('o', 'g', 'o', 0), // ogo = Khana
+ TRUETYPE_TAG('o', 'g', 'u', 0), // ogu = Ogbronuagum
+ TRUETYPE_TAG('o', 'h', 't', 0), // oht = Old Hittite
+ TRUETYPE_TAG('o', 'h', 'u', 0), // ohu = Old Hungarian
+ TRUETYPE_TAG('o', 'i', 'a', 0), // oia = Oirata
+ TRUETYPE_TAG('o', 'i', 'n', 0), // oin = Inebu One
+ TRUETYPE_TAG('o', 'j', 'b', 0), // ojb = Northwestern Ojibwa
+ TRUETYPE_TAG('o', 'j', 'c', 0), // ojc = Central Ojibwa
+ TRUETYPE_TAG('o', 'j', 'g', 0), // ojg = Eastern Ojibwa
+ TRUETYPE_TAG('o', 'j', 'p', 0), // ojp = Old Japanese
+ TRUETYPE_TAG('o', 'j', 's', 0), // ojs = Severn Ojibwa
+ TRUETYPE_TAG('o', 'j', 'v', 0), // ojv = Ontong Java
+ TRUETYPE_TAG('o', 'j', 'w', 0), // ojw = Western Ojibwa
+ TRUETYPE_TAG('o', 'k', 'a', 0), // oka = Okanagan
+ TRUETYPE_TAG('o', 'k', 'b', 0), // okb = Okobo
+ TRUETYPE_TAG('o', 'k', 'd', 0), // okd = Okodia
+ TRUETYPE_TAG('o', 'k', 'e', 0), // oke = Okpe (Southwestern Edo)
+ TRUETYPE_TAG('o', 'k', 'h', 0), // okh = Koresh-e Rostam
+ TRUETYPE_TAG('o', 'k', 'i', 0), // oki = Okiek
+ TRUETYPE_TAG('o', 'k', 'j', 0), // okj = Oko-Juwoi
+ TRUETYPE_TAG('o', 'k', 'k', 0), // okk = Kwamtim One
+ TRUETYPE_TAG('o', 'k', 'l', 0), // okl = Old Kentish Sign Language
+ TRUETYPE_TAG('o', 'k', 'm', 0), // okm = Middle Korean (10th-16th cent.)
+ TRUETYPE_TAG('o', 'k', 'n', 0), // okn = Oki-No-Erabu
+ TRUETYPE_TAG('o', 'k', 'o', 0), // oko = Old Korean (3rd-9th cent.)
+ TRUETYPE_TAG('o', 'k', 'r', 0), // okr = Kirike
+ TRUETYPE_TAG('o', 'k', 's', 0), // oks = Oko-Eni-Osayen
+ TRUETYPE_TAG('o', 'k', 'u', 0), // oku = Oku
+ TRUETYPE_TAG('o', 'k', 'v', 0), // okv = Orokaiva
+ TRUETYPE_TAG('o', 'k', 'x', 0), // okx = Okpe (Northwestern Edo)
+ TRUETYPE_TAG('o', 'l', 'a', 0), // ola = Walungge
+ TRUETYPE_TAG('o', 'l', 'd', 0), // old = Mochi
+ TRUETYPE_TAG('o', 'l', 'e', 0), // ole = Olekha
+ TRUETYPE_TAG('o', 'l', 'm', 0), // olm = Oloma
+ TRUETYPE_TAG('o', 'l', 'o', 0), // olo = Livvi
+ TRUETYPE_TAG('o', 'l', 'r', 0), // olr = Olrat
+ TRUETYPE_TAG('o', 'm', 'a', 0), // oma = Omaha-Ponca
+ TRUETYPE_TAG('o', 'm', 'b', 0), // omb = East Ambae
+ TRUETYPE_TAG('o', 'm', 'c', 0), // omc = Mochica
+ TRUETYPE_TAG('o', 'm', 'e', 0), // ome = Omejes
+ TRUETYPE_TAG('o', 'm', 'g', 0), // omg = Omagua
+ TRUETYPE_TAG('o', 'm', 'i', 0), // omi = Omi
+ TRUETYPE_TAG('o', 'm', 'k', 0), // omk = Omok
+ TRUETYPE_TAG('o', 'm', 'l', 0), // oml = Ombo
+ TRUETYPE_TAG('o', 'm', 'n', 0), // omn = Minoan
+ TRUETYPE_TAG('o', 'm', 'o', 0), // omo = Utarmbung
+ TRUETYPE_TAG('o', 'm', 'p', 0), // omp = Old Manipuri
+ TRUETYPE_TAG('o', 'm', 'q', 0), // omq = Oto-Manguean languages
+ TRUETYPE_TAG('o', 'm', 'r', 0), // omr = Old Marathi
+ TRUETYPE_TAG('o', 'm', 't', 0), // omt = Omotik
+ TRUETYPE_TAG('o', 'm', 'u', 0), // omu = Omurano
+ TRUETYPE_TAG('o', 'm', 'v', 0), // omv = Omotic languages
+ TRUETYPE_TAG('o', 'm', 'w', 0), // omw = South Tairora
+ TRUETYPE_TAG('o', 'm', 'x', 0), // omx = Old Mon
+ TRUETYPE_TAG('o', 'n', 'a', 0), // ona = Ona
+ TRUETYPE_TAG('o', 'n', 'b', 0), // onb = Lingao
+ TRUETYPE_TAG('o', 'n', 'e', 0), // one = Oneida
+ TRUETYPE_TAG('o', 'n', 'g', 0), // ong = Olo
+ TRUETYPE_TAG('o', 'n', 'i', 0), // oni = Onin
+ TRUETYPE_TAG('o', 'n', 'j', 0), // onj = Onjob
+ TRUETYPE_TAG('o', 'n', 'k', 0), // onk = Kabore One
+ TRUETYPE_TAG('o', 'n', 'n', 0), // onn = Onobasulu
+ TRUETYPE_TAG('o', 'n', 'o', 0), // ono = Onondaga
+ TRUETYPE_TAG('o', 'n', 'p', 0), // onp = Sartang
+ TRUETYPE_TAG('o', 'n', 'r', 0), // onr = Northern One
+ TRUETYPE_TAG('o', 'n', 's', 0), // ons = Ono
+ TRUETYPE_TAG('o', 'n', 't', 0), // ont = Ontenu
+ TRUETYPE_TAG('o', 'n', 'u', 0), // onu = Unua
+ TRUETYPE_TAG('o', 'n', 'w', 0), // onw = Old Nubian
+ TRUETYPE_TAG('o', 'n', 'x', 0), // onx = Onin Based Pidgin
+ TRUETYPE_TAG('o', 'o', 'd', 0), // ood = Tohono O'odham
+ TRUETYPE_TAG('o', 'o', 'g', 0), // oog = Ong
+ TRUETYPE_TAG('o', 'o', 'n', 0), // oon = Önge
+ TRUETYPE_TAG('o', 'o', 'r', 0), // oor = Oorlams
+ TRUETYPE_TAG('o', 'o', 's', 0), // oos = Old Ossetic
+ TRUETYPE_TAG('o', 'p', 'a', 0), // opa = Okpamheri
+ TRUETYPE_TAG('o', 'p', 'k', 0), // opk = Kopkaka
+ TRUETYPE_TAG('o', 'p', 'm', 0), // opm = Oksapmin
+ TRUETYPE_TAG('o', 'p', 'o', 0), // opo = Opao
+ TRUETYPE_TAG('o', 'p', 't', 0), // opt = Opata
+ TRUETYPE_TAG('o', 'p', 'y', 0), // opy = Ofayé
+ TRUETYPE_TAG('o', 'r', 'a', 0), // ora = Oroha
+ TRUETYPE_TAG('o', 'r', 'c', 0), // orc = Orma
+ TRUETYPE_TAG('o', 'r', 'e', 0), // ore = Orejón
+ TRUETYPE_TAG('o', 'r', 'g', 0), // org = Oring
+ TRUETYPE_TAG('o', 'r', 'h', 0), // orh = Oroqen
+ TRUETYPE_TAG('o', 'r', 'n', 0), // orn = Orang Kanaq
+ TRUETYPE_TAG('o', 'r', 'o', 0), // oro = Orokolo
+ TRUETYPE_TAG('o', 'r', 'r', 0), // orr = Oruma
+ TRUETYPE_TAG('o', 'r', 's', 0), // ors = Orang Seletar
+ TRUETYPE_TAG('o', 'r', 't', 0), // ort = Adivasi Oriya
+ TRUETYPE_TAG('o', 'r', 'u', 0), // oru = Ormuri
+ TRUETYPE_TAG('o', 'r', 'v', 0), // orv = Old Russian
+ TRUETYPE_TAG('o', 'r', 'w', 0), // orw = Oro Win
+ TRUETYPE_TAG('o', 'r', 'x', 0), // orx = Oro
+ TRUETYPE_TAG('o', 'r', 'z', 0), // orz = Ormu
+ TRUETYPE_TAG('o', 's', 'a', 0), // osa = Osage
+ TRUETYPE_TAG('o', 's', 'c', 0), // osc = Oscan
+ TRUETYPE_TAG('o', 's', 'i', 0), // osi = Osing
+ TRUETYPE_TAG('o', 's', 'o', 0), // oso = Ososo
+ TRUETYPE_TAG('o', 's', 'p', 0), // osp = Old Spanish
+ TRUETYPE_TAG('o', 's', 't', 0), // ost = Osatu
+ TRUETYPE_TAG('o', 's', 'u', 0), // osu = Southern One
+ TRUETYPE_TAG('o', 's', 'x', 0), // osx = Old Saxon
+ TRUETYPE_TAG('o', 't', 'a', 0), // ota = Ottoman Turkish (1500-1928)
+ TRUETYPE_TAG('o', 't', 'b', 0), // otb = Old Tibetan
+ TRUETYPE_TAG('o', 't', 'd', 0), // otd = Ot Danum
+ TRUETYPE_TAG('o', 't', 'e', 0), // ote = Mezquital Otomi
+ TRUETYPE_TAG('o', 't', 'i', 0), // oti = Oti
+ TRUETYPE_TAG('o', 't', 'k', 0), // otk = Old Turkish
+ TRUETYPE_TAG('o', 't', 'l', 0), // otl = Tilapa Otomi
+ TRUETYPE_TAG('o', 't', 'm', 0), // otm = Eastern Highland Otomi
+ TRUETYPE_TAG('o', 't', 'n', 0), // otn = Tenango Otomi
+ TRUETYPE_TAG('o', 't', 'o', 0), // oto = Otomian languages
+ TRUETYPE_TAG('o', 't', 'q', 0), // otq = Querétaro Otomi
+ TRUETYPE_TAG('o', 't', 'r', 0), // otr = Otoro
+ TRUETYPE_TAG('o', 't', 's', 0), // ots = Estado de México Otomi
+ TRUETYPE_TAG('o', 't', 't', 0), // ott = Temoaya Otomi
+ TRUETYPE_TAG('o', 't', 'u', 0), // otu = Otuke
+ TRUETYPE_TAG('o', 't', 'w', 0), // otw = Ottawa
+ TRUETYPE_TAG('o', 't', 'x', 0), // otx = Texcatepec Otomi
+ TRUETYPE_TAG('o', 't', 'y', 0), // oty = Old Tamil
+ TRUETYPE_TAG('o', 't', 'z', 0), // otz = Ixtenco Otomi
+ TRUETYPE_TAG('o', 'u', 'a', 0), // oua = Tagargrent
+ TRUETYPE_TAG('o', 'u', 'b', 0), // oub = Glio-Oubi
+ TRUETYPE_TAG('o', 'u', 'e', 0), // oue = Oune
+ TRUETYPE_TAG('o', 'u', 'i', 0), // oui = Old Uighur
+ TRUETYPE_TAG('o', 'u', 'm', 0), // oum = Ouma
+ TRUETYPE_TAG('o', 'u', 'n', 0), // oun = !O!ung
+ TRUETYPE_TAG('o', 'w', 'i', 0), // owi = Owiniga
+ TRUETYPE_TAG('o', 'w', 'l', 0), // owl = Old Welsh
+ TRUETYPE_TAG('o', 'y', 'b', 0), // oyb = Oy
+ TRUETYPE_TAG('o', 'y', 'd', 0), // oyd = Oyda
+ TRUETYPE_TAG('o', 'y', 'm', 0), // oym = Wayampi
+ TRUETYPE_TAG('o', 'y', 'y', 0), // oyy = Oya'oya
+ TRUETYPE_TAG('o', 'z', 'm', 0), // ozm = Koonzime
+ TRUETYPE_TAG('p', 'a', 'a', 0), // paa = Papuan languages
+ TRUETYPE_TAG('p', 'a', 'b', 0), // pab = Parecís
+ TRUETYPE_TAG('p', 'a', 'c', 0), // pac = Pacoh
+ TRUETYPE_TAG('p', 'a', 'd', 0), // pad = Paumarí
+ TRUETYPE_TAG('p', 'a', 'e', 0), // pae = Pagibete
+ TRUETYPE_TAG('p', 'a', 'f', 0), // paf = Paranawát
+ TRUETYPE_TAG('p', 'a', 'g', 0), // pag = Pangasinan
+ TRUETYPE_TAG('p', 'a', 'h', 0), // pah = Tenharim
+ TRUETYPE_TAG('p', 'a', 'i', 0), // pai = Pe
+ TRUETYPE_TAG('p', 'a', 'k', 0), // pak = Parakanã
+ TRUETYPE_TAG('p', 'a', 'l', 0), // pal = Pahlavi
+ TRUETYPE_TAG('p', 'a', 'm', 0), // pam = Pampanga
+ TRUETYPE_TAG('p', 'a', 'o', 0), // pao = Northern Paiute
+ TRUETYPE_TAG('p', 'a', 'p', 0), // pap = Papiamento
+ TRUETYPE_TAG('p', 'a', 'q', 0), // paq = Parya
+ TRUETYPE_TAG('p', 'a', 'r', 0), // par = Panamint
+ TRUETYPE_TAG('p', 'a', 's', 0), // pas = Papasena
+ TRUETYPE_TAG('p', 'a', 't', 0), // pat = Papitalai
+ TRUETYPE_TAG('p', 'a', 'u', 0), // pau = Palauan
+ TRUETYPE_TAG('p', 'a', 'v', 0), // pav = Pakaásnovos
+ TRUETYPE_TAG('p', 'a', 'w', 0), // paw = Pawnee
+ TRUETYPE_TAG('p', 'a', 'x', 0), // pax = Pankararé
+ TRUETYPE_TAG('p', 'a', 'y', 0), // pay = Pech
+ TRUETYPE_TAG('p', 'a', 'z', 0), // paz = Pankararú
+ TRUETYPE_TAG('p', 'b', 'b', 0), // pbb = Páez
+ TRUETYPE_TAG('p', 'b', 'c', 0), // pbc = Patamona
+ TRUETYPE_TAG('p', 'b', 'e', 0), // pbe = Mezontla Popoloca
+ TRUETYPE_TAG('p', 'b', 'f', 0), // pbf = Coyotepec Popoloca
+ TRUETYPE_TAG('p', 'b', 'g', 0), // pbg = Paraujano
+ TRUETYPE_TAG('p', 'b', 'h', 0), // pbh = E'ñapa Woromaipu
+ TRUETYPE_TAG('p', 'b', 'i', 0), // pbi = Parkwa
+ TRUETYPE_TAG('p', 'b', 'l', 0), // pbl = Mak (Nigeria)
+ TRUETYPE_TAG('p', 'b', 'n', 0), // pbn = Kpasam
+ TRUETYPE_TAG('p', 'b', 'o', 0), // pbo = Papel
+ TRUETYPE_TAG('p', 'b', 'p', 0), // pbp = Badyara
+ TRUETYPE_TAG('p', 'b', 'r', 0), // pbr = Pangwa
+ TRUETYPE_TAG('p', 'b', 's', 0), // pbs = Central Pame
+ TRUETYPE_TAG('p', 'b', 't', 0), // pbt = Southern Pashto
+ TRUETYPE_TAG('p', 'b', 'u', 0), // pbu = Northern Pashto
+ TRUETYPE_TAG('p', 'b', 'v', 0), // pbv = Pnar
+ TRUETYPE_TAG('p', 'b', 'y', 0), // pby = Pyu
+ TRUETYPE_TAG('p', 'b', 'z', 0), // pbz = Palu
+ TRUETYPE_TAG('p', 'c', 'a', 0), // pca = Santa Inés Ahuatempan Popoloca
+ TRUETYPE_TAG('p', 'c', 'b', 0), // pcb = Pear
+ TRUETYPE_TAG('p', 'c', 'c', 0), // pcc = Bouyei
+ TRUETYPE_TAG('p', 'c', 'd', 0), // pcd = Picard
+ TRUETYPE_TAG('p', 'c', 'e', 0), // pce = Ruching Palaung
+ TRUETYPE_TAG('p', 'c', 'f', 0), // pcf = Paliyan
+ TRUETYPE_TAG('p', 'c', 'g', 0), // pcg = Paniya
+ TRUETYPE_TAG('p', 'c', 'h', 0), // pch = Pardhan
+ TRUETYPE_TAG('p', 'c', 'i', 0), // pci = Duruwa
+ TRUETYPE_TAG('p', 'c', 'j', 0), // pcj = Parenga
+ TRUETYPE_TAG('p', 'c', 'k', 0), // pck = Paite Chin
+ TRUETYPE_TAG('p', 'c', 'l', 0), // pcl = Pardhi
+ TRUETYPE_TAG('p', 'c', 'm', 0), // pcm = Nigerian Pidgin
+ TRUETYPE_TAG('p', 'c', 'n', 0), // pcn = Piti
+ TRUETYPE_TAG('p', 'c', 'p', 0), // pcp = Pacahuara
+ TRUETYPE_TAG('p', 'c', 'r', 0), // pcr = Panang
+ TRUETYPE_TAG('p', 'c', 'w', 0), // pcw = Pyapun
+ TRUETYPE_TAG('p', 'd', 'a', 0), // pda = Anam
+ TRUETYPE_TAG('p', 'd', 'c', 0), // pdc = Pennsylvania German
+ TRUETYPE_TAG('p', 'd', 'i', 0), // pdi = Pa Di
+ TRUETYPE_TAG('p', 'd', 'n', 0), // pdn = Podena
+ TRUETYPE_TAG('p', 'd', 'o', 0), // pdo = Padoe
+ TRUETYPE_TAG('p', 'd', 't', 0), // pdt = Plautdietsch
+ TRUETYPE_TAG('p', 'd', 'u', 0), // pdu = Kayan
+ TRUETYPE_TAG('p', 'e', 'a', 0), // pea = Peranakan Indonesian
+ TRUETYPE_TAG('p', 'e', 'b', 0), // peb = Eastern Pomo
+ TRUETYPE_TAG('p', 'e', 'd', 0), // ped = Mala (Papua New Guinea)
+ TRUETYPE_TAG('p', 'e', 'e', 0), // pee = Taje
+ TRUETYPE_TAG('p', 'e', 'f', 0), // pef = Northeastern Pomo
+ TRUETYPE_TAG('p', 'e', 'g', 0), // peg = Pengo
+ TRUETYPE_TAG('p', 'e', 'h', 0), // peh = Bonan
+ TRUETYPE_TAG('p', 'e', 'i', 0), // pei = Chichimeca-Jonaz
+ TRUETYPE_TAG('p', 'e', 'j', 0), // pej = Northern Pomo
+ TRUETYPE_TAG('p', 'e', 'k', 0), // pek = Penchal
+ TRUETYPE_TAG('p', 'e', 'l', 0), // pel = Pekal
+ TRUETYPE_TAG('p', 'e', 'm', 0), // pem = Phende
+ TRUETYPE_TAG('p', 'e', 'o', 0), // peo = Old Persian (ca. 600-400 B.C.)
+ TRUETYPE_TAG('p', 'e', 'p', 0), // pep = Kunja
+ TRUETYPE_TAG('p', 'e', 'q', 0), // peq = Southern Pomo
+ TRUETYPE_TAG('p', 'e', 's', 0), // pes = Iranian Persian
+ TRUETYPE_TAG('p', 'e', 'v', 0), // pev = Pémono
+ TRUETYPE_TAG('p', 'e', 'x', 0), // pex = Petats
+ TRUETYPE_TAG('p', 'e', 'y', 0), // pey = Petjo
+ TRUETYPE_TAG('p', 'e', 'z', 0), // pez = Eastern Penan
+ TRUETYPE_TAG('p', 'f', 'a', 0), // pfa = Pááfang
+ TRUETYPE_TAG('p', 'f', 'e', 0), // pfe = Peere
+ TRUETYPE_TAG('p', 'f', 'l', 0), // pfl = Pfaelzisch
+ TRUETYPE_TAG('p', 'g', 'a', 0), // pga = Sudanese Creole Arabic
+ TRUETYPE_TAG('p', 'g', 'g', 0), // pgg = Pangwali
+ TRUETYPE_TAG('p', 'g', 'i', 0), // pgi = Pagi
+ TRUETYPE_TAG('p', 'g', 'k', 0), // pgk = Rerep
+ TRUETYPE_TAG('p', 'g', 'l', 0), // pgl = Primitive Irish
+ TRUETYPE_TAG('p', 'g', 'n', 0), // pgn = Paelignian
+ TRUETYPE_TAG('p', 'g', 's', 0), // pgs = Pangseng
+ TRUETYPE_TAG('p', 'g', 'u', 0), // pgu = Pagu
+ TRUETYPE_TAG('p', 'g', 'y', 0), // pgy = Pongyong
+ TRUETYPE_TAG('p', 'h', 'a', 0), // pha = Pa-Hng
+ TRUETYPE_TAG('p', 'h', 'd', 0), // phd = Phudagi
+ TRUETYPE_TAG('p', 'h', 'g', 0), // phg = Phuong
+ TRUETYPE_TAG('p', 'h', 'h', 0), // phh = Phukha
+ TRUETYPE_TAG('p', 'h', 'i', 0), // phi = Philippine languages
+ TRUETYPE_TAG('p', 'h', 'k', 0), // phk = Phake
+ TRUETYPE_TAG('p', 'h', 'l', 0), // phl = Phalura
+ TRUETYPE_TAG('p', 'h', 'm', 0), // phm = Phimbi
+ TRUETYPE_TAG('p', 'h', 'n', 0), // phn = Phoenician
+ TRUETYPE_TAG('p', 'h', 'o', 0), // pho = Phunoi
+ TRUETYPE_TAG('p', 'h', 'q', 0), // phq = Phana'
+ TRUETYPE_TAG('p', 'h', 'r', 0), // phr = Pahari-Potwari
+ TRUETYPE_TAG('p', 'h', 't', 0), // pht = Phu Thai
+ TRUETYPE_TAG('p', 'h', 'u', 0), // phu = Phuan
+ TRUETYPE_TAG('p', 'h', 'v', 0), // phv = Pahlavani
+ TRUETYPE_TAG('p', 'h', 'w', 0), // phw = Phangduwali
+ TRUETYPE_TAG('p', 'i', 'a', 0), // pia = Pima Bajo
+ TRUETYPE_TAG('p', 'i', 'b', 0), // pib = Yine
+ TRUETYPE_TAG('p', 'i', 'c', 0), // pic = Pinji
+ TRUETYPE_TAG('p', 'i', 'd', 0), // pid = Piaroa
+ TRUETYPE_TAG('p', 'i', 'e', 0), // pie = Piro
+ TRUETYPE_TAG('p', 'i', 'f', 0), // pif = Pingelapese
+ TRUETYPE_TAG('p', 'i', 'g', 0), // pig = Pisabo
+ TRUETYPE_TAG('p', 'i', 'h', 0), // pih = Pitcairn-Norfolk
+ TRUETYPE_TAG('p', 'i', 'i', 0), // pii = Pini
+ TRUETYPE_TAG('p', 'i', 'j', 0), // pij = Pijao
+ TRUETYPE_TAG('p', 'i', 'l', 0), // pil = Yom
+ TRUETYPE_TAG('p', 'i', 'm', 0), // pim = Powhatan
+ TRUETYPE_TAG('p', 'i', 'n', 0), // pin = Piame
+ TRUETYPE_TAG('p', 'i', 'o', 0), // pio = Piapoco
+ TRUETYPE_TAG('p', 'i', 'p', 0), // pip = Pero
+ TRUETYPE_TAG('p', 'i', 'r', 0), // pir = Piratapuyo
+ TRUETYPE_TAG('p', 'i', 's', 0), // pis = Pijin
+ TRUETYPE_TAG('p', 'i', 't', 0), // pit = Pitta Pitta
+ TRUETYPE_TAG('p', 'i', 'u', 0), // piu = Pintupi-Luritja
+ TRUETYPE_TAG('p', 'i', 'v', 0), // piv = Pileni
+ TRUETYPE_TAG('p', 'i', 'w', 0), // piw = Pimbwe
+ TRUETYPE_TAG('p', 'i', 'x', 0), // pix = Piu
+ TRUETYPE_TAG('p', 'i', 'y', 0), // piy = Piya-Kwonci
+ TRUETYPE_TAG('p', 'i', 'z', 0), // piz = Pije
+ TRUETYPE_TAG('p', 'j', 't', 0), // pjt = Pitjantjatjara
+ TRUETYPE_TAG('p', 'k', 'a', 0), // pka = Ardhamāgadhī Prākrit
+ TRUETYPE_TAG('p', 'k', 'b', 0), // pkb = Pokomo
+ TRUETYPE_TAG('p', 'k', 'c', 0), // pkc = Paekche
+ TRUETYPE_TAG('p', 'k', 'g', 0), // pkg = Pak-Tong
+ TRUETYPE_TAG('p', 'k', 'h', 0), // pkh = Pankhu
+ TRUETYPE_TAG('p', 'k', 'n', 0), // pkn = Pakanha
+ TRUETYPE_TAG('p', 'k', 'o', 0), // pko = Pökoot
+ TRUETYPE_TAG('p', 'k', 'p', 0), // pkp = Pukapuka
+ TRUETYPE_TAG('p', 'k', 'r', 0), // pkr = Attapady Kurumba
+ TRUETYPE_TAG('p', 'k', 's', 0), // pks = Pakistan Sign Language
+ TRUETYPE_TAG('p', 'k', 't', 0), // pkt = Maleng
+ TRUETYPE_TAG('p', 'k', 'u', 0), // pku = Paku
+ TRUETYPE_TAG('p', 'l', 'a', 0), // pla = Miani
+ TRUETYPE_TAG('p', 'l', 'b', 0), // plb = Polonombauk
+ TRUETYPE_TAG('p', 'l', 'c', 0), // plc = Central Palawano
+ TRUETYPE_TAG('p', 'l', 'd', 0), // pld = Polari
+ TRUETYPE_TAG('p', 'l', 'e', 0), // ple = Palu'e
+ TRUETYPE_TAG('p', 'l', 'f',
+ 0), // plf = Central Malayo-Polynesian languages
+ TRUETYPE_TAG('p', 'l', 'g', 0), // plg = Pilagá
+ TRUETYPE_TAG('p', 'l', 'h', 0), // plh = Paulohi
+ TRUETYPE_TAG('p', 'l', 'j', 0), // plj = Polci
+ TRUETYPE_TAG('p', 'l', 'k', 0), // plk = Kohistani Shina
+ TRUETYPE_TAG('p', 'l', 'l', 0), // pll = Shwe Palaung
+ TRUETYPE_TAG('p', 'l', 'n', 0), // pln = Palenquero
+ TRUETYPE_TAG('p', 'l', 'o', 0), // plo = Oluta Popoluca
+ TRUETYPE_TAG('p', 'l', 'p', 0), // plp = Palpa
+ TRUETYPE_TAG('p', 'l', 'q', 0), // plq = Palaic
+ TRUETYPE_TAG('p', 'l', 'r', 0), // plr = Palaka Senoufo
+ TRUETYPE_TAG('p', 'l', 's', 0), // pls = San Marcos Tlalcoyalco Popoloca
+ TRUETYPE_TAG('p', 'l', 't', 0), // plt = Plateau Malagasy
+ TRUETYPE_TAG('p', 'l', 'u', 0), // plu = Palikúr
+ TRUETYPE_TAG('p', 'l', 'v', 0), // plv = Southwest Palawano
+ TRUETYPE_TAG('p', 'l', 'w', 0), // plw = Brooke's Point Palawano
+ TRUETYPE_TAG('p', 'l', 'y', 0), // ply = Bolyu
+ TRUETYPE_TAG('p', 'l', 'z', 0), // plz = Paluan
+ TRUETYPE_TAG('p', 'm', 'a', 0), // pma = Paama
+ TRUETYPE_TAG('p', 'm', 'b', 0), // pmb = Pambia
+ TRUETYPE_TAG('p', 'm', 'c', 0), // pmc = Palumata
+ TRUETYPE_TAG('p', 'm', 'e', 0), // pme = Pwaamei
+ TRUETYPE_TAG('p', 'm', 'f', 0), // pmf = Pamona
+ TRUETYPE_TAG('p', 'm', 'h', 0), // pmh = Māhārāṣṭri Prākrit
+ TRUETYPE_TAG('p', 'm', 'i', 0), // pmi = Northern Pumi
+ TRUETYPE_TAG('p', 'm', 'j', 0), // pmj = Southern Pumi
+ TRUETYPE_TAG('p', 'm', 'k', 0), // pmk = Pamlico
+ TRUETYPE_TAG('p', 'm', 'l', 0), // pml = Lingua Franca
+ TRUETYPE_TAG('p', 'm', 'm', 0), // pmm = Pomo
+ TRUETYPE_TAG('p', 'm', 'n', 0), // pmn = Pam
+ TRUETYPE_TAG('p', 'm', 'o', 0), // pmo = Pom
+ TRUETYPE_TAG('p', 'm', 'q', 0), // pmq = Northern Pame
+ TRUETYPE_TAG('p', 'm', 'r', 0), // pmr = Paynamar
+ TRUETYPE_TAG('p', 'm', 's', 0), // pms = Piemontese
+ TRUETYPE_TAG('p', 'm', 't', 0), // pmt = Tuamotuan
+ TRUETYPE_TAG('p', 'm', 'u', 0), // pmu = Mirpur Panjabi
+ TRUETYPE_TAG('p', 'm', 'w', 0), // pmw = Plains Miwok
+ TRUETYPE_TAG('p', 'm', 'x', 0), // pmx = Poumei Naga
+ TRUETYPE_TAG('p', 'm', 'y', 0), // pmy = Papuan Malay
+ TRUETYPE_TAG('p', 'm', 'z', 0), // pmz = Southern Pame
+ TRUETYPE_TAG('p', 'n', 'a', 0), // pna = Punan Bah-Biau
+ TRUETYPE_TAG('p', 'n', 'b', 0), // pnb = Western Panjabi
+ TRUETYPE_TAG('p', 'n', 'c', 0), // pnc = Pannei
+ TRUETYPE_TAG('p', 'n', 'e', 0), // pne = Western Penan
+ TRUETYPE_TAG('p', 'n', 'g', 0), // png = Pongu
+ TRUETYPE_TAG('p', 'n', 'h', 0), // pnh = Penrhyn
+ TRUETYPE_TAG('p', 'n', 'i', 0), // pni = Aoheng
+ TRUETYPE_TAG('p', 'n', 'm', 0), // pnm = Punan Batu 1
+ TRUETYPE_TAG('p', 'n', 'n', 0), // pnn = Pinai-Hagahai
+ TRUETYPE_TAG('p', 'n', 'o', 0), // pno = Panobo
+ TRUETYPE_TAG('p', 'n', 'p', 0), // pnp = Pancana
+ TRUETYPE_TAG('p', 'n', 'q', 0), // pnq = Pana (Burkina Faso)
+ TRUETYPE_TAG('p', 'n', 'r', 0), // pnr = Panim
+ TRUETYPE_TAG('p', 'n', 's', 0), // pns = Ponosakan
+ TRUETYPE_TAG('p', 'n', 't', 0), // pnt = Pontic
+ TRUETYPE_TAG('p', 'n', 'u', 0), // pnu = Jiongnai Bunu
+ TRUETYPE_TAG('p', 'n', 'v', 0), // pnv = Pinigura
+ TRUETYPE_TAG('p', 'n', 'w', 0), // pnw = Panytyima
+ TRUETYPE_TAG('p', 'n', 'x', 0), // pnx = Phong-Kniang
+ TRUETYPE_TAG('p', 'n', 'y', 0), // pny = Pinyin
+ TRUETYPE_TAG('p', 'n', 'z', 0), // pnz = Pana (Central African Republic)
+ TRUETYPE_TAG('p', 'o', 'c', 0), // poc = Poqomam
+ TRUETYPE_TAG('p', 'o', 'd', 0), // pod = Ponares
+ TRUETYPE_TAG('p', 'o', 'e', 0), // poe = San Juan Atzingo Popoloca
+ TRUETYPE_TAG('p', 'o', 'f', 0), // pof = Poke
+ TRUETYPE_TAG('p', 'o', 'g', 0), // pog = Potiguára
+ TRUETYPE_TAG('p', 'o', 'h', 0), // poh = Poqomchi'
+ TRUETYPE_TAG('p', 'o', 'i', 0), // poi = Highland Popoluca
+ TRUETYPE_TAG('p', 'o', 'k', 0), // pok = Pokangá
+ TRUETYPE_TAG('p', 'o', 'm', 0), // pom = Southeastern Pomo
+ TRUETYPE_TAG('p', 'o', 'n', 0), // pon = Pohnpeian
+ TRUETYPE_TAG('p', 'o', 'o', 0), // poo = Central Pomo
+ TRUETYPE_TAG('p', 'o', 'p', 0), // pop = Pwapwa
+ TRUETYPE_TAG('p', 'o', 'q', 0), // poq = Texistepec Popoluca
+ TRUETYPE_TAG('p', 'o', 's', 0), // pos = Sayula Popoluca
+ TRUETYPE_TAG('p', 'o', 't', 0), // pot = Potawatomi
+ TRUETYPE_TAG('p', 'o', 'v', 0), // pov = Upper Guinea Crioulo
+ TRUETYPE_TAG('p', 'o', 'w', 0), // pow = San Felipe Otlaltepec Popoloca
+ TRUETYPE_TAG('p', 'o', 'x', 0), // pox = Polabian
+ TRUETYPE_TAG('p', 'o', 'y', 0), // poy = Pogolo
+ TRUETYPE_TAG('p', 'o', 'z', 0), // poz = Malayo-Polynesian languages
+ TRUETYPE_TAG('p', 'p', 'a', 0), // ppa = Pao
+ TRUETYPE_TAG('p', 'p', 'e', 0), // ppe = Papi
+ TRUETYPE_TAG('p', 'p', 'i', 0), // ppi = Paipai
+ TRUETYPE_TAG('p', 'p', 'k', 0), // ppk = Uma
+ TRUETYPE_TAG('p', 'p', 'l', 0), // ppl = Pipil
+ TRUETYPE_TAG('p', 'p', 'm', 0), // ppm = Papuma
+ TRUETYPE_TAG('p', 'p', 'n', 0), // ppn = Papapana
+ TRUETYPE_TAG('p', 'p', 'o', 0), // ppo = Folopa
+ TRUETYPE_TAG('p', 'p', 'p', 0), // ppp = Pelende
+ TRUETYPE_TAG('p', 'p', 'q', 0), // ppq = Pei
+ TRUETYPE_TAG('p', 'p', 'r', 0), // ppr = Piru
+ TRUETYPE_TAG('p', 'p', 's', 0), // pps = San Luís Temalacayuca Popoloca
+ TRUETYPE_TAG('p', 'p', 't', 0), // ppt = Pare
+ TRUETYPE_TAG('p', 'p', 'u', 0), // ppu = Papora
+ TRUETYPE_TAG('p', 'q', 'a', 0), // pqa = Pa'a
+ TRUETYPE_TAG('p', 'q', 'e',
+ 0), // pqe = Eastern Malayo-Polynesian languages
+ TRUETYPE_TAG('p', 'q', 'm', 0), // pqm = Malecite-Passamaquoddy
+ TRUETYPE_TAG('p', 'q', 'w',
+ 0), // pqw = Western Malayo-Polynesian languages
+ TRUETYPE_TAG('p', 'r', 'a', 0), // pra = Prakrit languages
+ TRUETYPE_TAG('p', 'r', 'b', 0), // prb = Lua'
+ TRUETYPE_TAG('p', 'r', 'c', 0), // prc = Parachi
+ TRUETYPE_TAG('p', 'r', 'd', 0), // prd = Parsi-Dari
+ TRUETYPE_TAG('p', 'r', 'e', 0), // pre = Principense
+ TRUETYPE_TAG('p', 'r', 'f', 0), // prf = Paranan
+ TRUETYPE_TAG('p', 'r', 'g', 0), // prg = Prussian
+ TRUETYPE_TAG('p', 'r', 'h', 0), // prh = Porohanon
+ TRUETYPE_TAG('p', 'r', 'i', 0), // pri = Paicî
+ TRUETYPE_TAG('p', 'r', 'k', 0), // prk = Parauk
+ TRUETYPE_TAG('p', 'r', 'l', 0), // prl = Peruvian Sign Language
+ TRUETYPE_TAG('p', 'r', 'm', 0), // prm = Kibiri
+ TRUETYPE_TAG('p', 'r', 'n', 0), // prn = Prasuni
+ TRUETYPE_TAG('p', 'r', 'o', 0), // pro = Old Provençal (to 1500)
+ TRUETYPE_TAG('p', 'r', 'p', 0), // prp = Parsi
+ TRUETYPE_TAG('p', 'r', 'q', 0), // prq = Ashéninka Perené
+ TRUETYPE_TAG('p', 'r', 'r', 0), // prr = Puri
+ TRUETYPE_TAG('p', 'r', 's', 0), // prs = Dari
+ TRUETYPE_TAG('p', 'r', 't', 0), // prt = Phai
+ TRUETYPE_TAG('p', 'r', 'u', 0), // pru = Puragi
+ TRUETYPE_TAG('p', 'r', 'w', 0), // prw = Parawen
+ TRUETYPE_TAG('p', 'r', 'x', 0), // prx = Purik
+ TRUETYPE_TAG('p', 'r', 'y', 0), // pry = Pray 3
+ TRUETYPE_TAG('p', 'r', 'z', 0), // prz = Providencia Sign Language
+ TRUETYPE_TAG('p', 's', 'a', 0), // psa = Asue Awyu
+ TRUETYPE_TAG('p', 's', 'c', 0), // psc = Persian Sign Language
+ TRUETYPE_TAG('p', 's', 'd', 0), // psd = Plains Indian Sign Language
+ TRUETYPE_TAG('p', 's', 'e', 0), // pse = Central Malay
+ TRUETYPE_TAG('p', 's', 'g', 0), // psg = Penang Sign Language
+ TRUETYPE_TAG('p', 's', 'h', 0), // psh = Southwest Pashayi
+ TRUETYPE_TAG('p', 's', 'i', 0), // psi = Southeast Pashayi
+ TRUETYPE_TAG('p', 's', 'l', 0), // psl = Puerto Rican Sign Language
+ TRUETYPE_TAG('p', 's', 'm', 0), // psm = Pauserna
+ TRUETYPE_TAG('p', 's', 'n', 0), // psn = Panasuan
+ TRUETYPE_TAG('p', 's', 'o', 0), // pso = Polish Sign Language
+ TRUETYPE_TAG('p', 's', 'p', 0), // psp = Philippine Sign Language
+ TRUETYPE_TAG('p', 's', 'q', 0), // psq = Pasi
+ TRUETYPE_TAG('p', 's', 'r', 0), // psr = Portuguese Sign Language
+ TRUETYPE_TAG('p', 's', 's', 0), // pss = Kaulong
+ TRUETYPE_TAG('p', 's', 't', 0), // pst = Central Pashto
+ TRUETYPE_TAG('p', 's', 'u', 0), // psu = Sauraseni Prākrit
+ TRUETYPE_TAG('p', 's', 'w', 0), // psw = Port Sandwich
+ TRUETYPE_TAG('p', 's', 'y', 0), // psy = Piscataway
+ TRUETYPE_TAG('p', 't', 'a', 0), // pta = Pai Tavytera
+ TRUETYPE_TAG('p', 't', 'h', 0), // pth = Pataxó Hã-Ha-Hãe
+ TRUETYPE_TAG('p', 't', 'i', 0), // pti = Pintiini
+ TRUETYPE_TAG('p', 't', 'n', 0), // ptn = Patani
+ TRUETYPE_TAG('p', 't', 'o', 0), // pto = Zo'é
+ TRUETYPE_TAG('p', 't', 'p', 0), // ptp = Patep
+ TRUETYPE_TAG('p', 't', 'r', 0), // ptr = Piamatsina
+ TRUETYPE_TAG('p', 't', 't', 0), // ptt = Enrekang
+ TRUETYPE_TAG('p', 't', 'u', 0), // ptu = Bambam
+ TRUETYPE_TAG('p', 't', 'v', 0), // ptv = Port Vato
+ TRUETYPE_TAG('p', 't', 'w', 0), // ptw = Pentlatch
+ TRUETYPE_TAG('p', 't', 'y', 0), // pty = Pathiya
+ TRUETYPE_TAG('p', 'u', 'a', 0), // pua = Western Highland Purepecha
+ TRUETYPE_TAG('p', 'u', 'b', 0), // pub = Purum
+ TRUETYPE_TAG('p', 'u', 'c', 0), // puc = Punan Merap
+ TRUETYPE_TAG('p', 'u', 'd', 0), // pud = Punan Aput
+ TRUETYPE_TAG('p', 'u', 'e', 0), // pue = Puelche
+ TRUETYPE_TAG('p', 'u', 'f', 0), // puf = Punan Merah
+ TRUETYPE_TAG('p', 'u', 'g', 0), // pug = Phuie
+ TRUETYPE_TAG('p', 'u', 'i', 0), // pui = Puinave
+ TRUETYPE_TAG('p', 'u', 'j', 0), // puj = Punan Tubu
+ TRUETYPE_TAG('p', 'u', 'k', 0), // puk = Pu Ko
+ TRUETYPE_TAG('p', 'u', 'm', 0), // pum = Puma
+ TRUETYPE_TAG('p', 'u', 'o', 0), // puo = Puoc
+ TRUETYPE_TAG('p', 'u', 'p', 0), // pup = Pulabu
+ TRUETYPE_TAG('p', 'u', 'q', 0), // puq = Puquina
+ TRUETYPE_TAG('p', 'u', 'r', 0), // pur = Puruborá
+ TRUETYPE_TAG('p', 'u', 't', 0), // put = Putoh
+ TRUETYPE_TAG('p', 'u', 'u', 0), // puu = Punu
+ TRUETYPE_TAG('p', 'u', 'w', 0), // puw = Puluwatese
+ TRUETYPE_TAG('p', 'u', 'x', 0), // pux = Puare
+ TRUETYPE_TAG('p', 'u', 'y', 0), // puy = Purisimeño
+ TRUETYPE_TAG('p', 'u', 'z', 0), // puz = Purum Naga
+ TRUETYPE_TAG('p', 'w', 'a', 0), // pwa = Pawaia
+ TRUETYPE_TAG('p', 'w', 'b', 0), // pwb = Panawa
+ TRUETYPE_TAG('p', 'w', 'g', 0), // pwg = Gapapaiwa
+ TRUETYPE_TAG('p', 'w', 'm', 0), // pwm = Molbog
+ TRUETYPE_TAG('p', 'w', 'n', 0), // pwn = Paiwan
+ TRUETYPE_TAG('p', 'w', 'o', 0), // pwo = Pwo Western Karen
+ TRUETYPE_TAG('p', 'w', 'r', 0), // pwr = Powari
+ TRUETYPE_TAG('p', 'w', 'w', 0), // pww = Pwo Northern Karen
+ TRUETYPE_TAG('p', 'x', 'm', 0), // pxm = Quetzaltepec Mixe
+ TRUETYPE_TAG('p', 'y', 'e', 0), // pye = Pye Krumen
+ TRUETYPE_TAG('p', 'y', 'm', 0), // pym = Fyam
+ TRUETYPE_TAG('p', 'y', 'n', 0), // pyn = Poyanáwa
+ TRUETYPE_TAG('p', 'y', 's', 0), // pys = Paraguayan Sign Language
+ TRUETYPE_TAG('p', 'y', 'u', 0), // pyu = Puyuma
+ TRUETYPE_TAG('p', 'y', 'x', 0), // pyx = Pyu (Myanmar)
+ TRUETYPE_TAG('p', 'y', 'y', 0), // pyy = Pyen
+ TRUETYPE_TAG('p', 'z', 'n', 0), // pzn = Para Naga = Private use
+ TRUETYPE_TAG('q', 'u', 'a', 0), // qua = Quapaw
+ TRUETYPE_TAG('q', 'u', 'b', 0), // qub = Huallaga Huánuco Quechua
+ TRUETYPE_TAG('q', 'u', 'c', 0), // quc = K'iche'
+ TRUETYPE_TAG('q', 'u', 'd', 0), // qud = Calderón Highland Quichua
+ TRUETYPE_TAG('q', 'u', 'f', 0), // quf = Lambayeque Quechua
+ TRUETYPE_TAG('q', 'u', 'g', 0), // qug = Chimborazo Highland Quichua
+ TRUETYPE_TAG('q', 'u', 'h', 0), // quh = South Bolivian Quechua
+ TRUETYPE_TAG('q', 'u', 'i', 0), // qui = Quileute
+ TRUETYPE_TAG('q', 'u', 'k', 0), // quk = Chachapoyas Quechua
+ TRUETYPE_TAG('q', 'u', 'l', 0), // qul = North Bolivian Quechua
+ TRUETYPE_TAG('q', 'u', 'm', 0), // qum = Sipacapense
+ TRUETYPE_TAG('q', 'u', 'n', 0), // qun = Quinault
+ TRUETYPE_TAG('q', 'u', 'p', 0), // qup = Southern Pastaza Quechua
+ TRUETYPE_TAG('q', 'u', 'q', 0), // quq = Quinqui
+ TRUETYPE_TAG('q', 'u', 'r', 0), // qur = Yanahuanca Pasco Quechua
+ TRUETYPE_TAG('q', 'u', 's', 0), // qus = Santiago del Estero Quichua
+ TRUETYPE_TAG('q', 'u', 'v', 0), // quv = Sacapulteco
+ TRUETYPE_TAG('q', 'u', 'w', 0), // quw = Tena Lowland Quichua
+ TRUETYPE_TAG('q', 'u', 'x', 0), // qux = Yauyos Quechua
+ TRUETYPE_TAG('q', 'u', 'y', 0), // quy = Ayacucho Quechua
+ TRUETYPE_TAG('q', 'u', 'z', 0), // quz = Cusco Quechua
+ TRUETYPE_TAG('q', 'v', 'a', 0), // qva = Ambo-Pasco Quechua
+ TRUETYPE_TAG('q', 'v', 'c', 0), // qvc = Cajamarca Quechua
+ TRUETYPE_TAG('q', 'v', 'e', 0), // qve = Eastern Apurímac Quechua
+ TRUETYPE_TAG('q', 'v', 'h',
+ 0), // qvh = Huamalíes-Dos de Mayo Huánuco Quechua
+ TRUETYPE_TAG('q', 'v', 'i', 0), // qvi = Imbabura Highland Quichua
+ TRUETYPE_TAG('q', 'v', 'j', 0), // qvj = Loja Highland Quichua
+ TRUETYPE_TAG('q', 'v', 'l', 0), // qvl = Cajatambo North Lima Quechua
+ TRUETYPE_TAG('q', 'v', 'm',
+ 0), // qvm = Margos-Yarowilca-Lauricocha Quechua
+ TRUETYPE_TAG('q', 'v', 'n', 0), // qvn = North Junín Quechua
+ TRUETYPE_TAG('q', 'v', 'o', 0), // qvo = Napo Lowland Quechua
+ TRUETYPE_TAG('q', 'v', 'p', 0), // qvp = Pacaraos Quechua
+ TRUETYPE_TAG('q', 'v', 's', 0), // qvs = San Martín Quechua
+ TRUETYPE_TAG('q', 'v', 'w', 0), // qvw = Huaylla Wanca Quechua
+ TRUETYPE_TAG('q', 'v', 'y', 0), // qvy = Queyu
+ TRUETYPE_TAG('q', 'v', 'z', 0), // qvz = Northern Pastaza Quichua
+ TRUETYPE_TAG('q', 'w', 'a', 0), // qwa = Corongo Ancash Quechua
+ TRUETYPE_TAG('q', 'w', 'c', 0), // qwc = Classical Quechua
+ TRUETYPE_TAG('q', 'w', 'e', 0), // qwe = Quechuan (family)
+ TRUETYPE_TAG('q', 'w', 'h', 0), // qwh = Huaylas Ancash Quechua
+ TRUETYPE_TAG('q', 'w', 'm', 0), // qwm = Kuman (Russia)
+ TRUETYPE_TAG('q', 'w', 's', 0), // qws = Sihuas Ancash Quechua
+ TRUETYPE_TAG('q', 'w', 't', 0), // qwt = Kwalhioqua-Tlatskanai
+ TRUETYPE_TAG('q', 'x', 'a', 0), // qxa = Chiquián Ancash Quechua
+ TRUETYPE_TAG('q', 'x', 'c', 0), // qxc = Chincha Quechua
+ TRUETYPE_TAG('q', 'x', 'h', 0), // qxh = Panao Huánuco Quechua
+ TRUETYPE_TAG('q', 'x', 'l', 0), // qxl = Salasaca Highland Quichua
+ TRUETYPE_TAG('q', 'x', 'n', 0), // qxn = Northern Conchucos Ancash Quechua
+ TRUETYPE_TAG('q', 'x', 'o', 0), // qxo = Southern Conchucos Ancash Quechua
+ TRUETYPE_TAG('q', 'x', 'p', 0), // qxp = Puno Quechua
+ TRUETYPE_TAG('q', 'x', 'q', 0), // qxq = Qashqa'i
+ TRUETYPE_TAG('q', 'x', 'r', 0), // qxr = Cañar Highland Quichua
+ TRUETYPE_TAG('q', 'x', 's', 0), // qxs = Southern Qiang
+ TRUETYPE_TAG('q', 'x', 't', 0), // qxt = Santa Ana de Tusi Pasco Quechua
+ TRUETYPE_TAG('q', 'x', 'u', 0), // qxu = Arequipa-La Unión Quechua
+ TRUETYPE_TAG('q', 'x', 'w', 0), // qxw = Jauja Wanca Quechua
+ TRUETYPE_TAG('q', 'y', 'a', 0), // qya = Quenya
+ TRUETYPE_TAG('q', 'y', 'p', 0), // qyp = Quiripi
+ TRUETYPE_TAG('r', 'a', 'a', 0), // raa = Dungmali
+ TRUETYPE_TAG('r', 'a', 'b', 0), // rab = Camling
+ TRUETYPE_TAG('r', 'a', 'c', 0), // rac = Rasawa
+ TRUETYPE_TAG('r', 'a', 'd', 0), // rad = Rade
+ TRUETYPE_TAG('r', 'a', 'f', 0), // raf = Western Meohang
+ TRUETYPE_TAG('r', 'a', 'g', 0), // rag = Logooli
+ TRUETYPE_TAG('r', 'a', 'h', 0), // rah = Rabha
+ TRUETYPE_TAG('r', 'a', 'i', 0), // rai = Ramoaaina
+ TRUETYPE_TAG('r', 'a', 'j', 0), // raj = Rajasthani
+ TRUETYPE_TAG('r', 'a', 'k', 0), // rak = Tulu-Bohuai
+ TRUETYPE_TAG('r', 'a', 'l', 0), // ral = Ralte
+ TRUETYPE_TAG('r', 'a', 'm', 0), // ram = Canela
+ TRUETYPE_TAG('r', 'a', 'n', 0), // ran = Riantana
+ TRUETYPE_TAG('r', 'a', 'o', 0), // rao = Rao
+ TRUETYPE_TAG('r', 'a', 'p', 0), // rap = Rapanui
+ TRUETYPE_TAG('r', 'a', 'q', 0), // raq = Saam
+ TRUETYPE_TAG('r', 'a', 'r', 0), // rar = Rarotongan
+ TRUETYPE_TAG('r', 'a', 's', 0), // ras = Tegali
+ TRUETYPE_TAG('r', 'a', 't', 0), // rat = Razajerdi
+ TRUETYPE_TAG('r', 'a', 'u', 0), // rau = Raute
+ TRUETYPE_TAG('r', 'a', 'v', 0), // rav = Sampang
+ TRUETYPE_TAG('r', 'a', 'w', 0), // raw = Rawang
+ TRUETYPE_TAG('r', 'a', 'x', 0), // rax = Rang
+ TRUETYPE_TAG('r', 'a', 'y', 0), // ray = Rapa
+ TRUETYPE_TAG('r', 'a', 'z', 0), // raz = Rahambuu
+ TRUETYPE_TAG('r', 'b', 'b', 0), // rbb = Rumai Palaung
+ TRUETYPE_TAG('r', 'b', 'k', 0), // rbk = Northern Bontok
+ TRUETYPE_TAG('r', 'b', 'l', 0), // rbl = Miraya Bikol
+ TRUETYPE_TAG('r', 'c', 'f', 0), // rcf = Réunion Creole French
+ TRUETYPE_TAG('r', 'd', 'b', 0), // rdb = Rudbari
+ TRUETYPE_TAG('r', 'e', 'a', 0), // rea = Rerau
+ TRUETYPE_TAG('r', 'e', 'b', 0), // reb = Rembong
+ TRUETYPE_TAG('r', 'e', 'e', 0), // ree = Rejang Kayan
+ TRUETYPE_TAG('r', 'e', 'g', 0), // reg = Kara (Tanzania)
+ TRUETYPE_TAG('r', 'e', 'i', 0), // rei = Reli
+ TRUETYPE_TAG('r', 'e', 'j', 0), // rej = Rejang
+ TRUETYPE_TAG('r', 'e', 'l', 0), // rel = Rendille
+ TRUETYPE_TAG('r', 'e', 'm', 0), // rem = Remo
+ TRUETYPE_TAG('r', 'e', 'n', 0), // ren = Rengao
+ TRUETYPE_TAG('r', 'e', 'r', 0), // rer = Rer Bare
+ TRUETYPE_TAG('r', 'e', 's', 0), // res = Reshe
+ TRUETYPE_TAG('r', 'e', 't', 0), // ret = Retta
+ TRUETYPE_TAG('r', 'e', 'y', 0), // rey = Reyesano
+ TRUETYPE_TAG('r', 'g', 'a', 0), // rga = Roria
+ TRUETYPE_TAG('r', 'g', 'e', 0), // rge = Romano-Greek
+ TRUETYPE_TAG('r', 'g', 'k', 0), // rgk = Rangkas
+ TRUETYPE_TAG('r', 'g', 'n', 0), // rgn = Romagnol
+ TRUETYPE_TAG('r', 'g', 'r', 0), // rgr = Resígaro
+ TRUETYPE_TAG('r', 'g', 's', 0), // rgs = Southern Roglai
+ TRUETYPE_TAG('r', 'g', 'u', 0), // rgu = Ringgou
+ TRUETYPE_TAG('r', 'h', 'g', 0), // rhg = Rohingya
+ TRUETYPE_TAG('r', 'h', 'p', 0), // rhp = Yahang
+ TRUETYPE_TAG('r', 'i', 'a', 0), // ria = Riang (India)
+ TRUETYPE_TAG('r', 'i', 'e', 0), // rie = Rien
+ TRUETYPE_TAG('r', 'i', 'f', 0), // rif = Tarifit
+ TRUETYPE_TAG('r', 'i', 'l', 0), // ril = Riang (Myanmar)
+ TRUETYPE_TAG('r', 'i', 'm', 0), // rim = Nyaturu
+ TRUETYPE_TAG('r', 'i', 'n', 0), // rin = Nungu
+ TRUETYPE_TAG('r', 'i', 'r', 0), // rir = Ribun
+ TRUETYPE_TAG('r', 'i', 't', 0), // rit = Ritarungo
+ TRUETYPE_TAG('r', 'i', 'u', 0), // riu = Riung
+ TRUETYPE_TAG('r', 'j', 'g', 0), // rjg = Rajong
+ TRUETYPE_TAG('r', 'j', 'i', 0), // rji = Raji
+ TRUETYPE_TAG('r', 'j', 's', 0), // rjs = Rajbanshi
+ TRUETYPE_TAG('r', 'k', 'a', 0), // rka = Kraol
+ TRUETYPE_TAG('r', 'k', 'b', 0), // rkb = Rikbaktsa
+ TRUETYPE_TAG('r', 'k', 'h', 0), // rkh = Rakahanga-Manihiki
+ TRUETYPE_TAG('r', 'k', 'i', 0), // rki = Rakhine
+ TRUETYPE_TAG('r', 'k', 'm', 0), // rkm = Marka
+ TRUETYPE_TAG('r', 'k', 't', 0), // rkt = Rangpuri
+ TRUETYPE_TAG('r', 'm', 'a', 0), // rma = Rama
+ TRUETYPE_TAG('r', 'm', 'b', 0), // rmb = Rembarunga
+ TRUETYPE_TAG('r', 'm', 'c', 0), // rmc = Carpathian Romani
+ TRUETYPE_TAG('r', 'm', 'd', 0), // rmd = Traveller Danish
+ TRUETYPE_TAG('r', 'm', 'e', 0), // rme = Angloromani
+ TRUETYPE_TAG('r', 'm', 'f', 0), // rmf = Kalo Finnish Romani
+ TRUETYPE_TAG('r', 'm', 'g', 0), // rmg = Traveller Norwegian
+ TRUETYPE_TAG('r', 'm', 'h', 0), // rmh = Murkim
+ TRUETYPE_TAG('r', 'm', 'i', 0), // rmi = Lomavren
+ TRUETYPE_TAG('r', 'm', 'k', 0), // rmk = Romkun
+ TRUETYPE_TAG('r', 'm', 'l', 0), // rml = Baltic Romani
+ TRUETYPE_TAG('r', 'm', 'm', 0), // rmm = Roma
+ TRUETYPE_TAG('r', 'm', 'n', 0), // rmn = Balkan Romani
+ TRUETYPE_TAG('r', 'm', 'o', 0), // rmo = Sinte Romani
+ TRUETYPE_TAG('r', 'm', 'p', 0), // rmp = Rempi
+ TRUETYPE_TAG('r', 'm', 'q', 0), // rmq = Caló
+ TRUETYPE_TAG('r', 'm', 'r', 0), // rmr = Caló
+ TRUETYPE_TAG('r', 'm', 's', 0), // rms = Romanian Sign Language
+ TRUETYPE_TAG('r', 'm', 't', 0), // rmt = Domari
+ TRUETYPE_TAG('r', 'm', 'u', 0), // rmu = Tavringer Romani
+ TRUETYPE_TAG('r', 'm', 'v', 0), // rmv = Romanova
+ TRUETYPE_TAG('r', 'm', 'w', 0), // rmw = Welsh Romani
+ TRUETYPE_TAG('r', 'm', 'x', 0), // rmx = Romam
+ TRUETYPE_TAG('r', 'm', 'y', 0), // rmy = Vlax Romani
+ TRUETYPE_TAG('r', 'm', 'z', 0), // rmz = Marma
+ TRUETYPE_TAG('r', 'n', 'a', 0), // rna = Runa
+ TRUETYPE_TAG('r', 'n', 'd', 0), // rnd = Ruund
+ TRUETYPE_TAG('r', 'n', 'g', 0), // rng = Ronga
+ TRUETYPE_TAG('r', 'n', 'l', 0), // rnl = Ranglong
+ TRUETYPE_TAG('r', 'n', 'n', 0), // rnn = Roon
+ TRUETYPE_TAG('r', 'n', 'p', 0), // rnp = Rongpo
+ TRUETYPE_TAG('r', 'n', 'w', 0), // rnw = Rungwa
+ TRUETYPE_TAG('r', 'o', 'a', 0), // roa = Romance languages
+ TRUETYPE_TAG('r', 'o', 'b', 0), // rob = Tae'
+ TRUETYPE_TAG('r', 'o', 'c', 0), // roc = Cacgia Roglai
+ TRUETYPE_TAG('r', 'o', 'd', 0), // rod = Rogo
+ TRUETYPE_TAG('r', 'o', 'e', 0), // roe = Ronji
+ TRUETYPE_TAG('r', 'o', 'f', 0), // rof = Rombo
+ TRUETYPE_TAG('r', 'o', 'g', 0), // rog = Northern Roglai
+ TRUETYPE_TAG('r', 'o', 'l', 0), // rol = Romblomanon
+ TRUETYPE_TAG('r', 'o', 'm', 0), // rom = Romany
+ TRUETYPE_TAG('r', 'o', 'o', 0), // roo = Rotokas
+ TRUETYPE_TAG('r', 'o', 'p', 0), // rop = Kriol
+ TRUETYPE_TAG('r', 'o', 'r', 0), // ror = Rongga
+ TRUETYPE_TAG('r', 'o', 'u', 0), // rou = Runga
+ TRUETYPE_TAG('r', 'o', 'w', 0), // row = Dela-Oenale
+ TRUETYPE_TAG('r', 'p', 'n', 0), // rpn = Repanbitip
+ TRUETYPE_TAG('r', 'p', 't', 0), // rpt = Rapting
+ TRUETYPE_TAG('r', 'r', 'i', 0), // rri = Ririo
+ TRUETYPE_TAG('r', 'r', 'o', 0), // rro = Waima
+ TRUETYPE_TAG('r', 's', 'b', 0), // rsb = Romano-Serbian
+ TRUETYPE_TAG('r', 's', 'i', 0), // rsi = Rennellese Sign Language
+ TRUETYPE_TAG('r', 's', 'l', 0), // rsl = Russian Sign Language
+ TRUETYPE_TAG('r', 't', 'h', 0), // rth = Ratahan
+ TRUETYPE_TAG('r', 't', 'm', 0), // rtm = Rotuman
+ TRUETYPE_TAG('r', 't', 'w', 0), // rtw = Rathawi
+ TRUETYPE_TAG('r', 'u', 'b', 0), // rub = Gungu
+ TRUETYPE_TAG('r', 'u', 'c', 0), // ruc = Ruuli
+ TRUETYPE_TAG('r', 'u', 'e', 0), // rue = Rusyn
+ TRUETYPE_TAG('r', 'u', 'f', 0), // ruf = Luguru
+ TRUETYPE_TAG('r', 'u', 'g', 0), // rug = Roviana
+ TRUETYPE_TAG('r', 'u', 'h', 0), // ruh = Ruga
+ TRUETYPE_TAG('r', 'u', 'i', 0), // rui = Rufiji
+ TRUETYPE_TAG('r', 'u', 'k', 0), // ruk = Che
+ TRUETYPE_TAG('r', 'u', 'o', 0), // ruo = Istro Romanian
+ TRUETYPE_TAG('r', 'u', 'p', 0), // rup = Macedo-Romanian
+ TRUETYPE_TAG('r', 'u', 'q', 0), // ruq = Megleno Romanian
+ TRUETYPE_TAG('r', 'u', 't', 0), // rut = Rutul
+ TRUETYPE_TAG('r', 'u', 'u', 0), // ruu = Lanas Lobu
+ TRUETYPE_TAG('r', 'u', 'y', 0), // ruy = Mala (Nigeria)
+ TRUETYPE_TAG('r', 'u', 'z', 0), // ruz = Ruma
+ TRUETYPE_TAG('r', 'w', 'a', 0), // rwa = Rawo
+ TRUETYPE_TAG('r', 'w', 'k', 0), // rwk = Rwa
+ TRUETYPE_TAG('r', 'w', 'm', 0), // rwm = Amba (Uganda)
+ TRUETYPE_TAG('r', 'w', 'o', 0), // rwo = Rawa
+ TRUETYPE_TAG('r', 'w', 'r', 0), // rwr = Marwari (India)
+ TRUETYPE_TAG('r', 'y', 'n', 0), // ryn = Northern Amami-Oshima
+ TRUETYPE_TAG('r', 'y', 's', 0), // rys = Yaeyama
+ TRUETYPE_TAG('r', 'y', 'u', 0), // ryu = Central Okinawan
+ TRUETYPE_TAG('s', 'a', 'a', 0), // saa = Saba
+ TRUETYPE_TAG('s', 'a', 'b', 0), // sab = Buglere
+ TRUETYPE_TAG('s', 'a', 'c', 0), // sac = Meskwaki
+ TRUETYPE_TAG('s', 'a', 'd', 0), // sad = Sandawe
+ TRUETYPE_TAG('s', 'a', 'e', 0), // sae = Sabanê
+ TRUETYPE_TAG('s', 'a', 'f', 0), // saf = Safaliba
+ TRUETYPE_TAG('s', 'a', 'h', 0), // sah = Yakut
+ TRUETYPE_TAG('s', 'a', 'i', 0), // sai = South American Indian languages
+ TRUETYPE_TAG('s', 'a', 'j', 0), // saj = Sahu
+ TRUETYPE_TAG('s', 'a', 'k', 0), // sak = Sake
+ TRUETYPE_TAG('s', 'a', 'l', 0), // sal = Salishan languages
+ TRUETYPE_TAG('s', 'a', 'm', 0), // sam = Samaritan Aramaic
+ TRUETYPE_TAG('s', 'a', 'o', 0), // sao = Sause
+ TRUETYPE_TAG('s', 'a', 'p', 0), // sap = Sanapaná
+ TRUETYPE_TAG('s', 'a', 'q', 0), // saq = Samburu
+ TRUETYPE_TAG('s', 'a', 'r', 0), // sar = Saraveca
+ TRUETYPE_TAG('s', 'a', 's', 0), // sas = Sasak
+ TRUETYPE_TAG('s', 'a', 't', 0), // sat = Santali
+ TRUETYPE_TAG('s', 'a', 'u', 0), // sau = Saleman
+ TRUETYPE_TAG('s', 'a', 'v', 0), // sav = Saafi-Saafi
+ TRUETYPE_TAG('s', 'a', 'w', 0), // saw = Sawi
+ TRUETYPE_TAG('s', 'a', 'x', 0), // sax = Sa
+ TRUETYPE_TAG('s', 'a', 'y', 0), // say = Saya
+ TRUETYPE_TAG('s', 'a', 'z', 0), // saz = Saurashtra
+ TRUETYPE_TAG('s', 'b', 'a', 0), // sba = Ngambay
+ TRUETYPE_TAG('s', 'b', 'b', 0), // sbb = Simbo
+ TRUETYPE_TAG('s', 'b', 'c', 0), // sbc = Kele (Papua New Guinea)
+ TRUETYPE_TAG('s', 'b', 'd', 0), // sbd = Southern Samo
+ TRUETYPE_TAG('s', 'b', 'e', 0), // sbe = Saliba
+ TRUETYPE_TAG('s', 'b', 'f', 0), // sbf = Shabo
+ TRUETYPE_TAG('s', 'b', 'g', 0), // sbg = Seget
+ TRUETYPE_TAG('s', 'b', 'h', 0), // sbh = Sori-Harengan
+ TRUETYPE_TAG('s', 'b', 'i', 0), // sbi = Seti
+ TRUETYPE_TAG('s', 'b', 'j', 0), // sbj = Surbakhal
+ TRUETYPE_TAG('s', 'b', 'k', 0), // sbk = Safwa
+ TRUETYPE_TAG('s', 'b', 'l', 0), // sbl = Botolan Sambal
+ TRUETYPE_TAG('s', 'b', 'm', 0), // sbm = Sagala
+ TRUETYPE_TAG('s', 'b', 'n', 0), // sbn = Sindhi Bhil
+ TRUETYPE_TAG('s', 'b', 'o', 0), // sbo = Sabüm
+ TRUETYPE_TAG('s', 'b', 'p', 0), // sbp = Sangu (Tanzania)
+ TRUETYPE_TAG('s', 'b', 'q', 0), // sbq = Sileibi
+ TRUETYPE_TAG('s', 'b', 'r', 0), // sbr = Sembakung Murut
+ TRUETYPE_TAG('s', 'b', 's', 0), // sbs = Subiya
+ TRUETYPE_TAG('s', 'b', 't', 0), // sbt = Kimki
+ TRUETYPE_TAG('s', 'b', 'u', 0), // sbu = Stod Bhoti
+ TRUETYPE_TAG('s', 'b', 'v', 0), // sbv = Sabine
+ TRUETYPE_TAG('s', 'b', 'w', 0), // sbw = Simba
+ TRUETYPE_TAG('s', 'b', 'x', 0), // sbx = Seberuang
+ TRUETYPE_TAG('s', 'b', 'y', 0), // sby = Soli
+ TRUETYPE_TAG('s', 'b', 'z', 0), // sbz = Sara Kaba
+ TRUETYPE_TAG('s', 'c', 'a', 0), // sca = Sansu
+ TRUETYPE_TAG('s', 'c', 'b', 0), // scb = Chut
+ TRUETYPE_TAG('s', 'c', 'e', 0), // sce = Dongxiang
+ TRUETYPE_TAG('s', 'c', 'f', 0), // scf = San Miguel Creole French
+ TRUETYPE_TAG('s', 'c', 'g', 0), // scg = Sanggau
+ TRUETYPE_TAG('s', 'c', 'h', 0), // sch = Sakachep
+ TRUETYPE_TAG('s', 'c', 'i', 0), // sci = Sri Lankan Creole Malay
+ TRUETYPE_TAG('s', 'c', 'k', 0), // sck = Sadri
+ TRUETYPE_TAG('s', 'c', 'l', 0), // scl = Shina
+ TRUETYPE_TAG('s', 'c', 'n', 0), // scn = Sicilian
+ TRUETYPE_TAG('s', 'c', 'o', 0), // sco = Scots
+ TRUETYPE_TAG('s', 'c', 'p', 0), // scp = Helambu Sherpa
+ TRUETYPE_TAG('s', 'c', 'q', 0), // scq = Sa'och
+ TRUETYPE_TAG('s', 'c', 's', 0), // scs = North Slavey
+ TRUETYPE_TAG('s', 'c', 'u', 0), // scu = Shumcho
+ TRUETYPE_TAG('s', 'c', 'v', 0), // scv = Sheni
+ TRUETYPE_TAG('s', 'c', 'w', 0), // scw = Sha
+ TRUETYPE_TAG('s', 'c', 'x', 0), // scx = Sicel
+ TRUETYPE_TAG('s', 'd', 'a', 0), // sda = Toraja-Sa'dan
+ TRUETYPE_TAG('s', 'd', 'b', 0), // sdb = Shabak
+ TRUETYPE_TAG('s', 'd', 'c', 0), // sdc = Sassarese Sardinian
+ TRUETYPE_TAG('s', 'd', 'e', 0), // sde = Surubu
+ TRUETYPE_TAG('s', 'd', 'f', 0), // sdf = Sarli
+ TRUETYPE_TAG('s', 'd', 'g', 0), // sdg = Savi
+ TRUETYPE_TAG('s', 'd', 'h', 0), // sdh = Southern Kurdish
+ TRUETYPE_TAG('s', 'd', 'j', 0), // sdj = Suundi
+ TRUETYPE_TAG('s', 'd', 'k', 0), // sdk = Sos Kundi
+ TRUETYPE_TAG('s', 'd', 'l', 0), // sdl = Saudi Arabian Sign Language
+ TRUETYPE_TAG('s', 'd', 'm', 0), // sdm = Semandang
+ TRUETYPE_TAG('s', 'd', 'n', 0), // sdn = Gallurese Sardinian
+ TRUETYPE_TAG('s', 'd', 'o', 0), // sdo = Bukar-Sadung Bidayuh
+ TRUETYPE_TAG('s', 'd', 'p', 0), // sdp = Sherdukpen
+ TRUETYPE_TAG('s', 'd', 'r', 0), // sdr = Oraon Sadri
+ TRUETYPE_TAG('s', 'd', 's', 0), // sds = Sened
+ TRUETYPE_TAG('s', 'd', 't', 0), // sdt = Shuadit
+ TRUETYPE_TAG('s', 'd', 'u', 0), // sdu = Sarudu
+ TRUETYPE_TAG('s', 'd', 'v', 0), // sdv = Eastern Sudanic languages
+ TRUETYPE_TAG('s', 'd', 'x', 0), // sdx = Sibu Melanau
+ TRUETYPE_TAG('s', 'd', 'z', 0), // sdz = Sallands
+ TRUETYPE_TAG('s', 'e', 'a', 0), // sea = Semai
+ TRUETYPE_TAG('s', 'e', 'b', 0), // seb = Shempire Senoufo
+ TRUETYPE_TAG('s', 'e', 'c', 0), // sec = Sechelt
+ TRUETYPE_TAG('s', 'e', 'd', 0), // sed = Sedang
+ TRUETYPE_TAG('s', 'e', 'e', 0), // see = Seneca
+ TRUETYPE_TAG('s', 'e', 'f', 0), // sef = Cebaara Senoufo
+ TRUETYPE_TAG('s', 'e', 'g', 0), // seg = Segeju
+ TRUETYPE_TAG('s', 'e', 'h', 0), // seh = Sena
+ TRUETYPE_TAG('s', 'e', 'i', 0), // sei = Seri
+ TRUETYPE_TAG('s', 'e', 'j', 0), // sej = Sene
+ TRUETYPE_TAG('s', 'e', 'k', 0), // sek = Sekani
+ TRUETYPE_TAG('s', 'e', 'l', 0), // sel = Selkup
+ TRUETYPE_TAG('s', 'e', 'm', 0), // sem = Semitic languages
+ TRUETYPE_TAG('s', 'e', 'n', 0), // sen = Nanerigé Sénoufo
+ TRUETYPE_TAG('s', 'e', 'o', 0), // seo = Suarmin
+ TRUETYPE_TAG('s', 'e', 'p', 0), // sep = Sìcìté Sénoufo
+ TRUETYPE_TAG('s', 'e', 'q', 0), // seq = Senara Sénoufo
+ TRUETYPE_TAG('s', 'e', 'r', 0), // ser = Serrano
+ TRUETYPE_TAG('s', 'e', 's', 0), // ses = Koyraboro Senni Songhai
+ TRUETYPE_TAG('s', 'e', 't', 0), // set = Sentani
+ TRUETYPE_TAG('s', 'e', 'u', 0), // seu = Serui-Laut
+ TRUETYPE_TAG('s', 'e', 'v', 0), // sev = Nyarafolo Senoufo
+ TRUETYPE_TAG('s', 'e', 'w', 0), // sew = Sewa Bay
+ TRUETYPE_TAG('s', 'e', 'y', 0), // sey = Secoya
+ TRUETYPE_TAG('s', 'e', 'z', 0), // sez = Senthang Chin
+ TRUETYPE_TAG('s', 'f', 'b',
+ 0), // sfb = Langue des signes de Belgique Francophone
+ TRUETYPE_TAG('s', 'f', 'm', 0), // sfm = Small Flowery Miao
+ TRUETYPE_TAG('s', 'f', 's', 0), // sfs = South African Sign Language
+ TRUETYPE_TAG('s', 'f', 'w', 0), // sfw = Sehwi
+ TRUETYPE_TAG('s', 'g', 'a', 0), // sga = Old Irish (to 900)
+ TRUETYPE_TAG('s', 'g', 'b', 0), // sgb = Mag-antsi Ayta
+ TRUETYPE_TAG('s', 'g', 'c', 0), // sgc = Kipsigis
+ TRUETYPE_TAG('s', 'g', 'd', 0), // sgd = Surigaonon
+ TRUETYPE_TAG('s', 'g', 'e', 0), // sge = Segai
+ TRUETYPE_TAG('s', 'g', 'g', 0), // sgg = Swiss-German Sign Language
+ TRUETYPE_TAG('s', 'g', 'h', 0), // sgh = Shughni
+ TRUETYPE_TAG('s', 'g', 'i', 0), // sgi = Suga
+ TRUETYPE_TAG('s', 'g', 'k', 0), // sgk = Sangkong
+ TRUETYPE_TAG('s', 'g', 'l', 0), // sgl = Sanglechi-Ishkashimi
+ TRUETYPE_TAG('s', 'g', 'm', 0), // sgm = Singa
+ TRUETYPE_TAG('s', 'g', 'n', 0), // sgn = Sign languages
+ TRUETYPE_TAG('s', 'g', 'o', 0), // sgo = Songa
+ TRUETYPE_TAG('s', 'g', 'p', 0), // sgp = Singpho
+ TRUETYPE_TAG('s', 'g', 'r', 0), // sgr = Sangisari
+ TRUETYPE_TAG('s', 'g', 's', 0), // sgs = Samogitian
+ TRUETYPE_TAG('s', 'g', 't', 0), // sgt = Brokpake
+ TRUETYPE_TAG('s', 'g', 'u', 0), // sgu = Salas
+ TRUETYPE_TAG('s', 'g', 'w', 0), // sgw = Sebat Bet Gurage
+ TRUETYPE_TAG('s', 'g', 'x', 0), // sgx = Sierra Leone Sign Language
+ TRUETYPE_TAG('s', 'g', 'y', 0), // sgy = Sanglechi
+ TRUETYPE_TAG('s', 'g', 'z', 0), // sgz = Sursurunga
+ TRUETYPE_TAG('s', 'h', 'a', 0), // sha = Shall-Zwall
+ TRUETYPE_TAG('s', 'h', 'b', 0), // shb = Ninam
+ TRUETYPE_TAG('s', 'h', 'c', 0), // shc = Sonde
+ TRUETYPE_TAG('s', 'h', 'd', 0), // shd = Kundal Shahi
+ TRUETYPE_TAG('s', 'h', 'e', 0), // she = Sheko
+ TRUETYPE_TAG('s', 'h', 'g', 0), // shg = Shua
+ TRUETYPE_TAG('s', 'h', 'h', 0), // shh = Shoshoni
+ TRUETYPE_TAG('s', 'h', 'i', 0), // shi = Tachelhit
+ TRUETYPE_TAG('s', 'h', 'j', 0), // shj = Shatt
+ TRUETYPE_TAG('s', 'h', 'k', 0), // shk = Shilluk
+ TRUETYPE_TAG('s', 'h', 'l', 0), // shl = Shendu
+ TRUETYPE_TAG('s', 'h', 'm', 0), // shm = Shahrudi
+ TRUETYPE_TAG('s', 'h', 'n', 0), // shn = Shan
+ TRUETYPE_TAG('s', 'h', 'o', 0), // sho = Shanga
+ TRUETYPE_TAG('s', 'h', 'p', 0), // shp = Shipibo-Conibo
+ TRUETYPE_TAG('s', 'h', 'q', 0), // shq = Sala
+ TRUETYPE_TAG('s', 'h', 'r', 0), // shr = Shi
+ TRUETYPE_TAG('s', 'h', 's', 0), // shs = Shuswap
+ TRUETYPE_TAG('s', 'h', 't', 0), // sht = Shasta
+ TRUETYPE_TAG('s', 'h', 'u', 0), // shu = Chadian Arabic
+ TRUETYPE_TAG('s', 'h', 'v', 0), // shv = Shehri
+ TRUETYPE_TAG('s', 'h', 'w', 0), // shw = Shwai
+ TRUETYPE_TAG('s', 'h', 'x', 0), // shx = She
+ TRUETYPE_TAG('s', 'h', 'y', 0), // shy = Tachawit
+ TRUETYPE_TAG('s', 'h', 'z', 0), // shz = Syenara Senoufo
+ TRUETYPE_TAG('s', 'i', 'a', 0), // sia = Akkala Sami
+ TRUETYPE_TAG('s', 'i', 'b', 0), // sib = Sebop
+ TRUETYPE_TAG('s', 'i', 'd', 0), // sid = Sidamo
+ TRUETYPE_TAG('s', 'i', 'e', 0), // sie = Simaa
+ TRUETYPE_TAG('s', 'i', 'f', 0), // sif = Siamou
+ TRUETYPE_TAG('s', 'i', 'g', 0), // sig = Paasaal
+ TRUETYPE_TAG('s', 'i', 'h', 0), // sih = Zire
+ TRUETYPE_TAG('s', 'i', 'i', 0), // sii = Shom Peng
+ TRUETYPE_TAG('s', 'i', 'j', 0), // sij = Numbami
+ TRUETYPE_TAG('s', 'i', 'k', 0), // sik = Sikiana
+ TRUETYPE_TAG('s', 'i', 'l', 0), // sil = Tumulung Sisaala
+ TRUETYPE_TAG('s', 'i', 'm', 0), // sim = Mende (Papua New Guinea)
+ TRUETYPE_TAG('s', 'i', 'o', 0), // sio = Siouan languages
+ TRUETYPE_TAG('s', 'i', 'p', 0), // sip = Sikkimese
+ TRUETYPE_TAG('s', 'i', 'q', 0), // siq = Sonia
+ TRUETYPE_TAG('s', 'i', 'r', 0), // sir = Siri
+ TRUETYPE_TAG('s', 'i', 's', 0), // sis = Siuslaw
+ TRUETYPE_TAG('s', 'i', 't', 0), // sit = Sino-Tibetan languages
+ TRUETYPE_TAG('s', 'i', 'u', 0), // siu = Sinagen
+ TRUETYPE_TAG('s', 'i', 'v', 0), // siv = Sumariup
+ TRUETYPE_TAG('s', 'i', 'w', 0), // siw = Siwai
+ TRUETYPE_TAG('s', 'i', 'x', 0), // six = Sumau
+ TRUETYPE_TAG('s', 'i', 'y', 0), // siy = Sivandi
+ TRUETYPE_TAG('s', 'i', 'z', 0), // siz = Siwi
+ TRUETYPE_TAG('s', 'j', 'a', 0), // sja = Epena
+ TRUETYPE_TAG('s', 'j', 'b', 0), // sjb = Sajau Basap
+ TRUETYPE_TAG('s', 'j', 'd', 0), // sjd = Kildin Sami
+ TRUETYPE_TAG('s', 'j', 'e', 0), // sje = Pite Sami
+ TRUETYPE_TAG('s', 'j', 'g', 0), // sjg = Assangori
+ TRUETYPE_TAG('s', 'j', 'k', 0), // sjk = Kemi Sami
+ TRUETYPE_TAG('s', 'j', 'l', 0), // sjl = Sajalong
+ TRUETYPE_TAG('s', 'j', 'm', 0), // sjm = Mapun
+ TRUETYPE_TAG('s', 'j', 'n', 0), // sjn = Sindarin
+ TRUETYPE_TAG('s', 'j', 'o', 0), // sjo = Xibe
+ TRUETYPE_TAG('s', 'j', 'p', 0), // sjp = Surjapuri
+ TRUETYPE_TAG('s', 'j', 'r', 0), // sjr = Siar-Lak
+ TRUETYPE_TAG('s', 'j', 's', 0), // sjs = Senhaja De Srair
+ TRUETYPE_TAG('s', 'j', 't', 0), // sjt = Ter Sami
+ TRUETYPE_TAG('s', 'j', 'u', 0), // sju = Ume Sami
+ TRUETYPE_TAG('s', 'j', 'w', 0), // sjw = Shawnee
+ TRUETYPE_TAG('s', 'k', 'a', 0), // ska = Skagit
+ TRUETYPE_TAG('s', 'k', 'b', 0), // skb = Saek
+ TRUETYPE_TAG('s', 'k', 'c', 0), // skc = Sauk
+ TRUETYPE_TAG('s', 'k', 'd', 0), // skd = Southern Sierra Miwok
+ TRUETYPE_TAG('s', 'k', 'e', 0), // ske = Seke (Vanuatu)
+ TRUETYPE_TAG('s', 'k', 'f', 0), // skf = Sakirabiá
+ TRUETYPE_TAG('s', 'k', 'g', 0), // skg = Sakalava Malagasy
+ TRUETYPE_TAG('s', 'k', 'h', 0), // skh = Sikule
+ TRUETYPE_TAG('s', 'k', 'i', 0), // ski = Sika
+ TRUETYPE_TAG('s', 'k', 'j', 0), // skj = Seke (Nepal)
+ TRUETYPE_TAG('s', 'k', 'k', 0), // skk = Sok
+ TRUETYPE_TAG('s', 'k', 'm', 0), // skm = Sakam
+ TRUETYPE_TAG('s', 'k', 'n', 0), // skn = Kolibugan Subanon
+ TRUETYPE_TAG('s', 'k', 'o', 0), // sko = Seko Tengah
+ TRUETYPE_TAG('s', 'k', 'p', 0), // skp = Sekapan
+ TRUETYPE_TAG('s', 'k', 'q', 0), // skq = Sininkere
+ TRUETYPE_TAG('s', 'k', 'r', 0), // skr = Seraiki
+ TRUETYPE_TAG('s', 'k', 's', 0), // sks = Maia
+ TRUETYPE_TAG('s', 'k', 't', 0), // skt = Sakata
+ TRUETYPE_TAG('s', 'k', 'u', 0), // sku = Sakao
+ TRUETYPE_TAG('s', 'k', 'v', 0), // skv = Skou
+ TRUETYPE_TAG('s', 'k', 'w', 0), // skw = Skepi Creole Dutch
+ TRUETYPE_TAG('s', 'k', 'x', 0), // skx = Seko Padang
+ TRUETYPE_TAG('s', 'k', 'y', 0), // sky = Sikaiana
+ TRUETYPE_TAG('s', 'k', 'z', 0), // skz = Sekar
+ TRUETYPE_TAG('s', 'l', 'a', 0), // sla = Slavic languages
+ TRUETYPE_TAG('s', 'l', 'c', 0), // slc = Sáliba
+ TRUETYPE_TAG('s', 'l', 'd', 0), // sld = Sissala
+ TRUETYPE_TAG('s', 'l', 'e', 0), // sle = Sholaga
+ TRUETYPE_TAG('s', 'l', 'f', 0), // slf = Swiss-Italian Sign Language
+ TRUETYPE_TAG('s', 'l', 'g', 0), // slg = Selungai Murut
+ TRUETYPE_TAG('s', 'l', 'h', 0), // slh = Southern Puget Sound Salish
+ TRUETYPE_TAG('s', 'l', 'i', 0), // sli = Lower Silesian
+ TRUETYPE_TAG('s', 'l', 'j', 0), // slj = Salumá
+ TRUETYPE_TAG('s', 'l', 'l', 0), // sll = Salt-Yui
+ TRUETYPE_TAG('s', 'l', 'm', 0), // slm = Pangutaran Sama
+ TRUETYPE_TAG('s', 'l', 'n', 0), // sln = Salinan
+ TRUETYPE_TAG('s', 'l', 'p', 0), // slp = Lamaholot
+ TRUETYPE_TAG('s', 'l', 'q', 0), // slq = Salchuq
+ TRUETYPE_TAG('s', 'l', 'r', 0), // slr = Salar
+ TRUETYPE_TAG('s', 'l', 's', 0), // sls = Singapore Sign Language
+ TRUETYPE_TAG('s', 'l', 't', 0), // slt = Sila
+ TRUETYPE_TAG('s', 'l', 'u', 0), // slu = Selaru
+ TRUETYPE_TAG('s', 'l', 'w', 0), // slw = Sialum
+ TRUETYPE_TAG('s', 'l', 'x', 0), // slx = Salampasu
+ TRUETYPE_TAG('s', 'l', 'y', 0), // sly = Selayar
+ TRUETYPE_TAG('s', 'l', 'z', 0), // slz = Ma'ya
+ TRUETYPE_TAG('s', 'm', 'a', 0), // sma = Southern Sami
+ TRUETYPE_TAG('s', 'm', 'b', 0), // smb = Simbari
+ TRUETYPE_TAG('s', 'm', 'c', 0), // smc = Som
+ TRUETYPE_TAG('s', 'm', 'd', 0), // smd = Sama
+ TRUETYPE_TAG('s', 'm', 'f', 0), // smf = Auwe
+ TRUETYPE_TAG('s', 'm', 'g', 0), // smg = Simbali
+ TRUETYPE_TAG('s', 'm', 'h', 0), // smh = Samei
+ TRUETYPE_TAG('s', 'm', 'i', 0), // smi = Sami languages
+ TRUETYPE_TAG('s', 'm', 'j', 0), // smj = Lule Sami
+ TRUETYPE_TAG('s', 'm', 'k', 0), // smk = Bolinao
+ TRUETYPE_TAG('s', 'm', 'l', 0), // sml = Central Sama
+ TRUETYPE_TAG('s', 'm', 'm', 0), // smm = Musasa
+ TRUETYPE_TAG('s', 'm', 'n', 0), // smn = Inari Sami
+ TRUETYPE_TAG('s', 'm', 'p', 0), // smp = Samaritan
+ TRUETYPE_TAG('s', 'm', 'q', 0), // smq = Samo
+ TRUETYPE_TAG('s', 'm', 'r', 0), // smr = Simeulue
+ TRUETYPE_TAG('s', 'm', 's', 0), // sms = Skolt Sami
+ TRUETYPE_TAG('s', 'm', 't', 0), // smt = Simte
+ TRUETYPE_TAG('s', 'm', 'u', 0), // smu = Somray
+ TRUETYPE_TAG('s', 'm', 'v', 0), // smv = Samvedi
+ TRUETYPE_TAG('s', 'm', 'w', 0), // smw = Sumbawa
+ TRUETYPE_TAG('s', 'm', 'x', 0), // smx = Samba
+ TRUETYPE_TAG('s', 'm', 'y', 0), // smy = Semnani
+ TRUETYPE_TAG('s', 'm', 'z', 0), // smz = Simeku
+ TRUETYPE_TAG('s', 'n', 'b', 0), // snb = Sebuyau
+ TRUETYPE_TAG('s', 'n', 'c', 0), // snc = Sinaugoro
+ TRUETYPE_TAG('s', 'n', 'e', 0), // sne = Bau Bidayuh
+ TRUETYPE_TAG('s', 'n', 'f', 0), // snf = Noon
+ TRUETYPE_TAG('s', 'n', 'g',
+ 0), // sng = Sanga (Democratic Republic of Congo)
+ TRUETYPE_TAG('s', 'n', 'h', 0), // snh = Shinabo
+ TRUETYPE_TAG('s', 'n', 'i', 0), // sni = Sensi
+ TRUETYPE_TAG('s', 'n', 'j', 0), // snj = Riverain Sango
+ TRUETYPE_TAG('s', 'n', 'k', 0), // snk = Soninke
+ TRUETYPE_TAG('s', 'n', 'l', 0), // snl = Sangil
+ TRUETYPE_TAG('s', 'n', 'm', 0), // snm = Southern Ma'di
+ TRUETYPE_TAG('s', 'n', 'n', 0), // snn = Siona
+ TRUETYPE_TAG('s', 'n', 'o', 0), // sno = Snohomish
+ TRUETYPE_TAG('s', 'n', 'p', 0), // snp = Siane
+ TRUETYPE_TAG('s', 'n', 'q', 0), // snq = Sangu (Gabon)
+ TRUETYPE_TAG('s', 'n', 'r', 0), // snr = Sihan
+ TRUETYPE_TAG('s', 'n', 's', 0), // sns = South West Bay
+ TRUETYPE_TAG('s', 'n', 'u', 0), // snu = Senggi
+ TRUETYPE_TAG('s', 'n', 'v', 0), // snv = Sa'ban
+ TRUETYPE_TAG('s', 'n', 'w', 0), // snw = Selee
+ TRUETYPE_TAG('s', 'n', 'x', 0), // snx = Sam
+ TRUETYPE_TAG('s', 'n', 'y', 0), // sny = Saniyo-Hiyewe
+ TRUETYPE_TAG('s', 'n', 'z', 0), // snz = Sinsauru
+ TRUETYPE_TAG('s', 'o', 'a', 0), // soa = Thai Song
+ TRUETYPE_TAG('s', 'o', 'b', 0), // sob = Sobei
+ TRUETYPE_TAG('s', 'o', 'c', 0), // soc = So (Democratic Republic of Congo)
+ TRUETYPE_TAG('s', 'o', 'd', 0), // sod = Songoora
+ TRUETYPE_TAG('s', 'o', 'e', 0), // soe = Songomeno
+ TRUETYPE_TAG('s', 'o', 'g', 0), // sog = Sogdian
+ TRUETYPE_TAG('s', 'o', 'h', 0), // soh = Aka
+ TRUETYPE_TAG('s', 'o', 'i', 0), // soi = Sonha
+ TRUETYPE_TAG('s', 'o', 'j', 0), // soj = Soi
+ TRUETYPE_TAG('s', 'o', 'k', 0), // sok = Sokoro
+ TRUETYPE_TAG('s', 'o', 'l', 0), // sol = Solos
+ TRUETYPE_TAG('s', 'o', 'n', 0), // son = Songhai languages
+ TRUETYPE_TAG('s', 'o', 'o', 0), // soo = Songo
+ TRUETYPE_TAG('s', 'o', 'p', 0), // sop = Songe
+ TRUETYPE_TAG('s', 'o', 'q', 0), // soq = Kanasi
+ TRUETYPE_TAG('s', 'o', 'r', 0), // sor = Somrai
+ TRUETYPE_TAG('s', 'o', 's', 0), // sos = Seeku
+ TRUETYPE_TAG('s', 'o', 'u', 0), // sou = Southern Thai
+ TRUETYPE_TAG('s', 'o', 'v', 0), // sov = Sonsorol
+ TRUETYPE_TAG('s', 'o', 'w', 0), // sow = Sowanda
+ TRUETYPE_TAG('s', 'o', 'x', 0), // sox = So (Cameroon)
+ TRUETYPE_TAG('s', 'o', 'y', 0), // soy = Miyobe
+ TRUETYPE_TAG('s', 'o', 'z', 0), // soz = Temi
+ TRUETYPE_TAG('s', 'p', 'b', 0), // spb = Sepa (Indonesia)
+ TRUETYPE_TAG('s', 'p', 'c', 0), // spc = Sapé
+ TRUETYPE_TAG('s', 'p', 'd', 0), // spd = Saep
+ TRUETYPE_TAG('s', 'p', 'e', 0), // spe = Sepa (Papua New Guinea)
+ TRUETYPE_TAG('s', 'p', 'g', 0), // spg = Sian
+ TRUETYPE_TAG('s', 'p', 'i', 0), // spi = Saponi
+ TRUETYPE_TAG('s', 'p', 'k', 0), // spk = Sengo
+ TRUETYPE_TAG('s', 'p', 'l', 0), // spl = Selepet
+ TRUETYPE_TAG('s', 'p', 'm', 0), // spm = Sepen
+ TRUETYPE_TAG('s', 'p', 'o', 0), // spo = Spokane
+ TRUETYPE_TAG('s', 'p', 'p', 0), // spp = Supyire Senoufo
+ TRUETYPE_TAG('s', 'p', 'q', 0), // spq = Loreto-Ucayali Spanish
+ TRUETYPE_TAG('s', 'p', 'r', 0), // spr = Saparua
+ TRUETYPE_TAG('s', 'p', 's', 0), // sps = Saposa
+ TRUETYPE_TAG('s', 'p', 't', 0), // spt = Spiti Bhoti
+ TRUETYPE_TAG('s', 'p', 'u', 0), // spu = Sapuan
+ TRUETYPE_TAG('s', 'p', 'x', 0), // spx = South Picene
+ TRUETYPE_TAG('s', 'p', 'y', 0), // spy = Sabaot
+ TRUETYPE_TAG('s', 'q', 'a', 0), // sqa = Shama-Sambuga
+ TRUETYPE_TAG('s', 'q', 'h', 0), // sqh = Shau
+ TRUETYPE_TAG('s', 'q', 'j', 0), // sqj = Albanian languages
+ TRUETYPE_TAG('s', 'q', 'm', 0), // sqm = Suma
+ TRUETYPE_TAG('s', 'q', 'n', 0), // sqn = Susquehannock
+ TRUETYPE_TAG('s', 'q', 'o', 0), // sqo = Sorkhei
+ TRUETYPE_TAG('s', 'q', 'q', 0), // sqq = Sou
+ TRUETYPE_TAG('s', 'q', 'r', 0), // sqr = Siculo Arabic
+ TRUETYPE_TAG('s', 'q', 's', 0), // sqs = Sri Lankan Sign Language
+ TRUETYPE_TAG('s', 'q', 't', 0), // sqt = Soqotri
+ TRUETYPE_TAG('s', 'q', 'u', 0), // squ = Squamish
+ TRUETYPE_TAG('s', 'r', 'a', 0), // sra = Saruga
+ TRUETYPE_TAG('s', 'r', 'b', 0), // srb = Sora
+ TRUETYPE_TAG('s', 'r', 'c', 0), // src = Logudorese Sardinian
+ TRUETYPE_TAG('s', 'r', 'e', 0), // sre = Sara
+ TRUETYPE_TAG('s', 'r', 'f', 0), // srf = Nafi
+ TRUETYPE_TAG('s', 'r', 'g', 0), // srg = Sulod
+ TRUETYPE_TAG('s', 'r', 'h', 0), // srh = Sarikoli
+ TRUETYPE_TAG('s', 'r', 'i', 0), // sri = Siriano
+ TRUETYPE_TAG('s', 'r', 'k', 0), // srk = Serudung Murut
+ TRUETYPE_TAG('s', 'r', 'l', 0), // srl = Isirawa
+ TRUETYPE_TAG('s', 'r', 'm', 0), // srm = Saramaccan
+ TRUETYPE_TAG('s', 'r', 'n', 0), // srn = Sranan Tongo
+ TRUETYPE_TAG('s', 'r', 'o', 0), // sro = Campidanese Sardinian
+ TRUETYPE_TAG('s', 'r', 'q', 0), // srq = Sirionó
+ TRUETYPE_TAG('s', 'r', 'r', 0), // srr = Serer
+ TRUETYPE_TAG('s', 'r', 's', 0), // srs = Sarsi
+ TRUETYPE_TAG('s', 'r', 't', 0), // srt = Sauri
+ TRUETYPE_TAG('s', 'r', 'u', 0), // sru = Suruí
+ TRUETYPE_TAG('s', 'r', 'v', 0), // srv = Southern Sorsoganon
+ TRUETYPE_TAG('s', 'r', 'w', 0), // srw = Serua
+ TRUETYPE_TAG('s', 'r', 'x', 0), // srx = Sirmauri
+ TRUETYPE_TAG('s', 'r', 'y', 0), // sry = Sera
+ TRUETYPE_TAG('s', 'r', 'z', 0), // srz = Shahmirzadi
+ TRUETYPE_TAG('s', 's', 'a', 0), // ssa = Nilo-Saharan languages
+ TRUETYPE_TAG('s', 's', 'b', 0), // ssb = Southern Sama
+ TRUETYPE_TAG('s', 's', 'c', 0), // ssc = Suba-Simbiti
+ TRUETYPE_TAG('s', 's', 'd', 0), // ssd = Siroi
+ TRUETYPE_TAG('s', 's', 'e', 0), // sse = Balangingi
+ TRUETYPE_TAG('s', 's', 'f', 0), // ssf = Thao
+ TRUETYPE_TAG('s', 's', 'g', 0), // ssg = Seimat
+ TRUETYPE_TAG('s', 's', 'h', 0), // ssh = Shihhi Arabic
+ TRUETYPE_TAG('s', 's', 'i', 0), // ssi = Sansi
+ TRUETYPE_TAG('s', 's', 'j', 0), // ssj = Sausi
+ TRUETYPE_TAG('s', 's', 'k', 0), // ssk = Sunam
+ TRUETYPE_TAG('s', 's', 'l', 0), // ssl = Western Sisaala
+ TRUETYPE_TAG('s', 's', 'm', 0), // ssm = Semnam
+ TRUETYPE_TAG('s', 's', 'n', 0), // ssn = Waata
+ TRUETYPE_TAG('s', 's', 'o', 0), // sso = Sissano
+ TRUETYPE_TAG('s', 's', 'p', 0), // ssp = Spanish Sign Language
+ TRUETYPE_TAG('s', 's', 'q', 0), // ssq = So'a
+ TRUETYPE_TAG('s', 's', 'r', 0), // ssr = Swiss-French Sign Language
+ TRUETYPE_TAG('s', 's', 's', 0), // sss = Sô
+ TRUETYPE_TAG('s', 's', 't', 0), // sst = Sinasina
+ TRUETYPE_TAG('s', 's', 'u', 0), // ssu = Susuami
+ TRUETYPE_TAG('s', 's', 'v', 0), // ssv = Shark Bay
+ TRUETYPE_TAG('s', 's', 'x', 0), // ssx = Samberigi
+ TRUETYPE_TAG('s', 's', 'y', 0), // ssy = Saho
+ TRUETYPE_TAG('s', 's', 'z', 0), // ssz = Sengseng
+ TRUETYPE_TAG('s', 't', 'a', 0), // sta = Settla
+ TRUETYPE_TAG('s', 't', 'b', 0), // stb = Northern Subanen
+ TRUETYPE_TAG('s', 't', 'd', 0), // std = Sentinel
+ TRUETYPE_TAG('s', 't', 'e', 0), // ste = Liana-Seti
+ TRUETYPE_TAG('s', 't', 'f', 0), // stf = Seta
+ TRUETYPE_TAG('s', 't', 'g', 0), // stg = Trieng
+ TRUETYPE_TAG('s', 't', 'h', 0), // sth = Shelta
+ TRUETYPE_TAG('s', 't', 'i', 0), // sti = Bulo Stieng
+ TRUETYPE_TAG('s', 't', 'j', 0), // stj = Matya Samo
+ TRUETYPE_TAG('s', 't', 'k', 0), // stk = Arammba
+ TRUETYPE_TAG('s', 't', 'l', 0), // stl = Stellingwerfs
+ TRUETYPE_TAG('s', 't', 'm', 0), // stm = Setaman
+ TRUETYPE_TAG('s', 't', 'n', 0), // stn = Owa
+ TRUETYPE_TAG('s', 't', 'o', 0), // sto = Stoney
+ TRUETYPE_TAG('s', 't', 'p', 0), // stp = Southeastern Tepehuan
+ TRUETYPE_TAG('s', 't', 'q', 0), // stq = Saterfriesisch
+ TRUETYPE_TAG('s', 't', 'r', 0), // str = Straits Salish
+ TRUETYPE_TAG('s', 't', 's', 0), // sts = Shumashti
+ TRUETYPE_TAG('s', 't', 't', 0), // stt = Budeh Stieng
+ TRUETYPE_TAG('s', 't', 'u', 0), // stu = Samtao
+ TRUETYPE_TAG('s', 't', 'v', 0), // stv = Silt'e
+ TRUETYPE_TAG('s', 't', 'w', 0), // stw = Satawalese
+ TRUETYPE_TAG('s', 'u', 'a', 0), // sua = Sulka
+ TRUETYPE_TAG('s', 'u', 'b', 0), // sub = Suku
+ TRUETYPE_TAG('s', 'u', 'c', 0), // suc = Western Subanon
+ TRUETYPE_TAG('s', 'u', 'e', 0), // sue = Suena
+ TRUETYPE_TAG('s', 'u', 'g', 0), // sug = Suganga
+ TRUETYPE_TAG('s', 'u', 'i', 0), // sui = Suki
+ TRUETYPE_TAG('s', 'u', 'j', 0), // suj = Shubi
+ TRUETYPE_TAG('s', 'u', 'k', 0), // suk = Sukuma
+ TRUETYPE_TAG('s', 'u', 'l', 0), // sul = Surigaonon
+ TRUETYPE_TAG('s', 'u', 'm', 0), // sum = Sumo-Mayangna
+ TRUETYPE_TAG('s', 'u', 'q', 0), // suq = Suri
+ TRUETYPE_TAG('s', 'u', 'r', 0), // sur = Mwaghavul
+ TRUETYPE_TAG('s', 'u', 's', 0), // sus = Susu
+ TRUETYPE_TAG('s', 'u', 't', 0), // sut = Subtiaba
+ TRUETYPE_TAG('s', 'u', 'v', 0), // suv = Sulung
+ TRUETYPE_TAG('s', 'u', 'w', 0), // suw = Sumbwa
+ TRUETYPE_TAG('s', 'u', 'x', 0), // sux = Sumerian
+ TRUETYPE_TAG('s', 'u', 'y', 0), // suy = Suyá
+ TRUETYPE_TAG('s', 'u', 'z', 0), // suz = Sunwar
+ TRUETYPE_TAG('s', 'v', 'a', 0), // sva = Svan
+ TRUETYPE_TAG('s', 'v', 'b', 0), // svb = Ulau-Suain
+ TRUETYPE_TAG('s', 'v', 'c', 0), // svc = Vincentian Creole English
+ TRUETYPE_TAG('s', 'v', 'e', 0), // sve = Serili
+ TRUETYPE_TAG('s', 'v', 'k', 0), // svk = Slovakian Sign Language
+ TRUETYPE_TAG('s', 'v', 'r', 0), // svr = Savara
+ TRUETYPE_TAG('s', 'v', 's', 0), // svs = Savosavo
+ TRUETYPE_TAG('s', 'v', 'x', 0), // svx = Skalvian
+ TRUETYPE_TAG('s', 'w', 'b', 0), // swb = Maore Comorian
+ TRUETYPE_TAG('s', 'w', 'c', 0), // swc = Congo Swahili
+ TRUETYPE_TAG('s', 'w', 'f', 0), // swf = Sere
+ TRUETYPE_TAG('s', 'w', 'g', 0), // swg = Swabian
+ TRUETYPE_TAG('s', 'w', 'h', 0), // swh = Swahili (individual language)
+ TRUETYPE_TAG('s', 'w', 'i', 0), // swi = Sui
+ TRUETYPE_TAG('s', 'w', 'j', 0), // swj = Sira
+ TRUETYPE_TAG('s', 'w', 'k', 0), // swk = Malawi Sena
+ TRUETYPE_TAG('s', 'w', 'l', 0), // swl = Swedish Sign Language
+ TRUETYPE_TAG('s', 'w', 'm', 0), // swm = Samosa
+ TRUETYPE_TAG('s', 'w', 'n', 0), // swn = Sawknah
+ TRUETYPE_TAG('s', 'w', 'o', 0), // swo = Shanenawa
+ TRUETYPE_TAG('s', 'w', 'p', 0), // swp = Suau
+ TRUETYPE_TAG('s', 'w', 'q', 0), // swq = Sharwa
+ TRUETYPE_TAG('s', 'w', 'r', 0), // swr = Saweru
+ TRUETYPE_TAG('s', 'w', 's', 0), // sws = Seluwasan
+ TRUETYPE_TAG('s', 'w', 't', 0), // swt = Sawila
+ TRUETYPE_TAG('s', 'w', 'u', 0), // swu = Suwawa
+ TRUETYPE_TAG('s', 'w', 'v', 0), // swv = Shekhawati
+ TRUETYPE_TAG('s', 'w', 'w', 0), // sww = Sowa
+ TRUETYPE_TAG('s', 'w', 'x', 0), // swx = Suruahá
+ TRUETYPE_TAG('s', 'w', 'y', 0), // swy = Sarua
+ TRUETYPE_TAG('s', 'x', 'b', 0), // sxb = Suba
+ TRUETYPE_TAG('s', 'x', 'c', 0), // sxc = Sicanian
+ TRUETYPE_TAG('s', 'x', 'e', 0), // sxe = Sighu
+ TRUETYPE_TAG('s', 'x', 'g', 0), // sxg = Shixing
+ TRUETYPE_TAG('s', 'x', 'k', 0), // sxk = Southern Kalapuya
+ TRUETYPE_TAG('s', 'x', 'l', 0), // sxl = Selian
+ TRUETYPE_TAG('s', 'x', 'm', 0), // sxm = Samre
+ TRUETYPE_TAG('s', 'x', 'n', 0), // sxn = Sangir
+ TRUETYPE_TAG('s', 'x', 'o', 0), // sxo = Sorothaptic
+ TRUETYPE_TAG('s', 'x', 'r', 0), // sxr = Saaroa
+ TRUETYPE_TAG('s', 'x', 's', 0), // sxs = Sasaru
+ TRUETYPE_TAG('s', 'x', 'u', 0), // sxu = Upper Saxon
+ TRUETYPE_TAG('s', 'x', 'w', 0), // sxw = Saxwe Gbe
+ TRUETYPE_TAG('s', 'y', 'a', 0), // sya = Siang
+ TRUETYPE_TAG('s', 'y', 'b', 0), // syb = Central Subanen
+ TRUETYPE_TAG('s', 'y', 'c', 0), // syc = Classical Syriac
+ TRUETYPE_TAG('s', 'y', 'd', 0), // syd = Samoyedic languages
+ TRUETYPE_TAG('s', 'y', 'i', 0), // syi = Seki
+ TRUETYPE_TAG('s', 'y', 'k', 0), // syk = Sukur
+ TRUETYPE_TAG('s', 'y', 'l', 0), // syl = Sylheti
+ TRUETYPE_TAG('s', 'y', 'm', 0), // sym = Maya Samo
+ TRUETYPE_TAG('s', 'y', 'n', 0), // syn = Senaya
+ TRUETYPE_TAG('s', 'y', 'o', 0), // syo = Suoy
+ TRUETYPE_TAG('s', 'y', 'r', 0), // syr = Syriac
+ TRUETYPE_TAG('s', 'y', 's', 0), // sys = Sinyar
+ TRUETYPE_TAG('s', 'y', 'w', 0), // syw = Kagate
+ TRUETYPE_TAG('s', 'y', 'y', 0), // syy = Al-Sayyid Bedouin Sign Language
+ TRUETYPE_TAG('s', 'z', 'a', 0), // sza = Semelai
+ TRUETYPE_TAG('s', 'z', 'b', 0), // szb = Ngalum
+ TRUETYPE_TAG('s', 'z', 'c', 0), // szc = Semaq Beri
+ TRUETYPE_TAG('s', 'z', 'd', 0), // szd = Seru
+ TRUETYPE_TAG('s', 'z', 'e', 0), // sze = Seze
+ TRUETYPE_TAG('s', 'z', 'g', 0), // szg = Sengele
+ TRUETYPE_TAG('s', 'z', 'l', 0), // szl = Silesian
+ TRUETYPE_TAG('s', 'z', 'n', 0), // szn = Sula
+ TRUETYPE_TAG('s', 'z', 'p', 0), // szp = Suabo
+ TRUETYPE_TAG('s', 'z', 'v', 0), // szv = Isu (Fako Division)
+ TRUETYPE_TAG('s', 'z', 'w', 0), // szw = Sawai
+ TRUETYPE_TAG('t', 'a', 'a', 0), // taa = Lower Tanana
+ TRUETYPE_TAG('t', 'a', 'b', 0), // tab = Tabassaran
+ TRUETYPE_TAG('t', 'a', 'c', 0), // tac = Lowland Tarahumara
+ TRUETYPE_TAG('t', 'a', 'd', 0), // tad = Tause
+ TRUETYPE_TAG('t', 'a', 'e', 0), // tae = Tariana
+ TRUETYPE_TAG('t', 'a', 'f', 0), // taf = Tapirapé
+ TRUETYPE_TAG('t', 'a', 'g', 0), // tag = Tagoi
+ TRUETYPE_TAG('t', 'a', 'i', 0), // tai = Tai languages
+ TRUETYPE_TAG('t', 'a', 'j', 0), // taj = Eastern Tamang
+ TRUETYPE_TAG('t', 'a', 'k', 0), // tak = Tala
+ TRUETYPE_TAG('t', 'a', 'l', 0), // tal = Tal
+ TRUETYPE_TAG('t', 'a', 'n', 0), // tan = Tangale
+ TRUETYPE_TAG('t', 'a', 'o', 0), // tao = Yami
+ TRUETYPE_TAG('t', 'a', 'p', 0), // tap = Taabwa
+ TRUETYPE_TAG('t', 'a', 'q', 0), // taq = Tamasheq
+ TRUETYPE_TAG('t', 'a', 'r', 0), // tar = Central Tarahumara
+ TRUETYPE_TAG('t', 'a', 's', 0), // tas = Tay Boi
+ TRUETYPE_TAG('t', 'a', 'u', 0), // tau = Upper Tanana
+ TRUETYPE_TAG('t', 'a', 'v', 0), // tav = Tatuyo
+ TRUETYPE_TAG('t', 'a', 'w', 0), // taw = Tai
+ TRUETYPE_TAG('t', 'a', 'x', 0), // tax = Tamki
+ TRUETYPE_TAG('t', 'a', 'y', 0), // tay = Atayal
+ TRUETYPE_TAG('t', 'a', 'z', 0), // taz = Tocho
+ TRUETYPE_TAG('t', 'b', 'a', 0), // tba = Aikanã
+ TRUETYPE_TAG('t', 'b', 'b', 0), // tbb = Tapeba
+ TRUETYPE_TAG('t', 'b', 'c', 0), // tbc = Takia
+ TRUETYPE_TAG('t', 'b', 'd', 0), // tbd = Kaki Ae
+ TRUETYPE_TAG('t', 'b', 'e', 0), // tbe = Tanimbili
+ TRUETYPE_TAG('t', 'b', 'f', 0), // tbf = Mandara
+ TRUETYPE_TAG('t', 'b', 'g', 0), // tbg = North Tairora
+ TRUETYPE_TAG('t', 'b', 'h', 0), // tbh = Thurawal
+ TRUETYPE_TAG('t', 'b', 'i', 0), // tbi = Gaam
+ TRUETYPE_TAG('t', 'b', 'j', 0), // tbj = Tiang
+ TRUETYPE_TAG('t', 'b', 'k', 0), // tbk = Calamian Tagbanwa
+ TRUETYPE_TAG('t', 'b', 'l', 0), // tbl = Tboli
+ TRUETYPE_TAG('t', 'b', 'm', 0), // tbm = Tagbu
+ TRUETYPE_TAG('t', 'b', 'n', 0), // tbn = Barro Negro Tunebo
+ TRUETYPE_TAG('t', 'b', 'o', 0), // tbo = Tawala
+ TRUETYPE_TAG('t', 'b', 'p', 0), // tbp = Taworta
+ TRUETYPE_TAG('t', 'b', 'q', 0), // tbq = Tibeto-Burman languages
+ TRUETYPE_TAG('t', 'b', 'r', 0), // tbr = Tumtum
+ TRUETYPE_TAG('t', 'b', 's', 0), // tbs = Tanguat
+ TRUETYPE_TAG('t', 'b', 't', 0), // tbt = Tembo (Kitembo)
+ TRUETYPE_TAG('t', 'b', 'u', 0), // tbu = Tubar
+ TRUETYPE_TAG('t', 'b', 'v', 0), // tbv = Tobo
+ TRUETYPE_TAG('t', 'b', 'w', 0), // tbw = Tagbanwa
+ TRUETYPE_TAG('t', 'b', 'x', 0), // tbx = Kapin
+ TRUETYPE_TAG('t', 'b', 'y', 0), // tby = Tabaru
+ TRUETYPE_TAG('t', 'b', 'z', 0), // tbz = Ditammari
+ TRUETYPE_TAG('t', 'c', 'a', 0), // tca = Ticuna
+ TRUETYPE_TAG('t', 'c', 'b', 0), // tcb = Tanacross
+ TRUETYPE_TAG('t', 'c', 'c', 0), // tcc = Datooga
+ TRUETYPE_TAG('t', 'c', 'd', 0), // tcd = Tafi
+ TRUETYPE_TAG('t', 'c', 'e', 0), // tce = Southern Tutchone
+ TRUETYPE_TAG('t', 'c', 'f', 0), // tcf = Malinaltepec Me'phaa
+ TRUETYPE_TAG('t', 'c', 'g', 0), // tcg = Tamagario
+ TRUETYPE_TAG('t', 'c', 'h', 0), // tch = Turks And Caicos Creole English
+ TRUETYPE_TAG('t', 'c', 'i', 0), // tci = Wára
+ TRUETYPE_TAG('t', 'c', 'k', 0), // tck = Tchitchege
+ TRUETYPE_TAG('t', 'c', 'l', 0), // tcl = Taman (Myanmar)
+ TRUETYPE_TAG('t', 'c', 'm', 0), // tcm = Tanahmerah
+ TRUETYPE_TAG('t', 'c', 'n', 0), // tcn = Tichurong
+ TRUETYPE_TAG('t', 'c', 'o', 0), // tco = Taungyo
+ TRUETYPE_TAG('t', 'c', 'p', 0), // tcp = Tawr Chin
+ TRUETYPE_TAG('t', 'c', 'q', 0), // tcq = Kaiy
+ TRUETYPE_TAG('t', 'c', 's', 0), // tcs = Torres Strait Creole
+ TRUETYPE_TAG('t', 'c', 't', 0), // tct = T'en
+ TRUETYPE_TAG('t', 'c', 'u', 0), // tcu = Southeastern Tarahumara
+ TRUETYPE_TAG('t', 'c', 'w', 0), // tcw = Tecpatlán Totonac
+ TRUETYPE_TAG('t', 'c', 'x', 0), // tcx = Toda
+ TRUETYPE_TAG('t', 'c', 'y', 0), // tcy = Tulu
+ TRUETYPE_TAG('t', 'c', 'z', 0), // tcz = Thado Chin
+ TRUETYPE_TAG('t', 'd', 'a', 0), // tda = Tagdal
+ TRUETYPE_TAG('t', 'd', 'b', 0), // tdb = Panchpargania
+ TRUETYPE_TAG('t', 'd', 'c', 0), // tdc = Emberá-Tadó
+ TRUETYPE_TAG('t', 'd', 'd', 0), // tdd = Tai Nüa
+ TRUETYPE_TAG('t', 'd', 'e', 0), // tde = Tiranige Diga Dogon
+ TRUETYPE_TAG('t', 'd', 'f', 0), // tdf = Talieng
+ TRUETYPE_TAG('t', 'd', 'g', 0), // tdg = Western Tamang
+ TRUETYPE_TAG('t', 'd', 'h', 0), // tdh = Thulung
+ TRUETYPE_TAG('t', 'd', 'i', 0), // tdi = Tomadino
+ TRUETYPE_TAG('t', 'd', 'j', 0), // tdj = Tajio
+ TRUETYPE_TAG('t', 'd', 'k', 0), // tdk = Tambas
+ TRUETYPE_TAG('t', 'd', 'l', 0), // tdl = Sur
+ TRUETYPE_TAG('t', 'd', 'n', 0), // tdn = Tondano
+ TRUETYPE_TAG('t', 'd', 'o', 0), // tdo = Teme
+ TRUETYPE_TAG('t', 'd', 'q', 0), // tdq = Tita
+ TRUETYPE_TAG('t', 'd', 'r', 0), // tdr = Todrah
+ TRUETYPE_TAG('t', 'd', 's', 0), // tds = Doutai
+ TRUETYPE_TAG('t', 'd', 't', 0), // tdt = Tetun Dili
+ TRUETYPE_TAG('t', 'd', 'u', 0), // tdu = Tempasuk Dusun
+ TRUETYPE_TAG('t', 'd', 'v', 0), // tdv = Toro
+ TRUETYPE_TAG('t', 'd', 'x', 0), // tdx = Tandroy-Mahafaly Malagasy
+ TRUETYPE_TAG('t', 'd', 'y', 0), // tdy = Tadyawan
+ TRUETYPE_TAG('t', 'e', 'a', 0), // tea = Temiar
+ TRUETYPE_TAG('t', 'e', 'b', 0), // teb = Tetete
+ TRUETYPE_TAG('t', 'e', 'c', 0), // tec = Terik
+ TRUETYPE_TAG('t', 'e', 'd', 0), // ted = Tepo Krumen
+ TRUETYPE_TAG('t', 'e', 'e', 0), // tee = Huehuetla Tepehua
+ TRUETYPE_TAG('t', 'e', 'f', 0), // tef = Teressa
+ TRUETYPE_TAG('t', 'e', 'g', 0), // teg = Teke-Tege
+ TRUETYPE_TAG('t', 'e', 'h', 0), // teh = Tehuelche
+ TRUETYPE_TAG('t', 'e', 'i', 0), // tei = Torricelli
+ TRUETYPE_TAG('t', 'e', 'k', 0), // tek = Ibali Teke
+ TRUETYPE_TAG('t', 'e', 'm', 0), // tem = Timne
+ TRUETYPE_TAG('t', 'e', 'n', 0), // ten = Tama (Colombia)
+ TRUETYPE_TAG('t', 'e', 'o', 0), // teo = Teso
+ TRUETYPE_TAG('t', 'e', 'p', 0), // tep = Tepecano
+ TRUETYPE_TAG('t', 'e', 'q', 0), // teq = Temein
+ TRUETYPE_TAG('t', 'e', 'r', 0), // ter = Tereno
+ TRUETYPE_TAG('t', 'e', 's', 0), // tes = Tengger
+ TRUETYPE_TAG('t', 'e', 't', 0), // tet = Tetum
+ TRUETYPE_TAG('t', 'e', 'u', 0), // teu = Soo
+ TRUETYPE_TAG('t', 'e', 'v', 0), // tev = Teor
+ TRUETYPE_TAG('t', 'e', 'w', 0), // tew = Tewa (USA)
+ TRUETYPE_TAG('t', 'e', 'x', 0), // tex = Tennet
+ TRUETYPE_TAG('t', 'e', 'y', 0), // tey = Tulishi
+ TRUETYPE_TAG('t', 'f', 'i', 0), // tfi = Tofin Gbe
+ TRUETYPE_TAG('t', 'f', 'n', 0), // tfn = Tanaina
+ TRUETYPE_TAG('t', 'f', 'o', 0), // tfo = Tefaro
+ TRUETYPE_TAG('t', 'f', 'r', 0), // tfr = Teribe
+ TRUETYPE_TAG('t', 'f', 't', 0), // tft = Ternate
+ TRUETYPE_TAG('t', 'g', 'a', 0), // tga = Sagalla
+ TRUETYPE_TAG('t', 'g', 'b', 0), // tgb = Tobilung
+ TRUETYPE_TAG('t', 'g', 'c', 0), // tgc = Tigak
+ TRUETYPE_TAG('t', 'g', 'd', 0), // tgd = Ciwogai
+ TRUETYPE_TAG('t', 'g', 'e', 0), // tge = Eastern Gorkha Tamang
+ TRUETYPE_TAG('t', 'g', 'f', 0), // tgf = Chalikha
+ TRUETYPE_TAG('t', 'g', 'g', 0), // tgg = Tangga
+ TRUETYPE_TAG('t', 'g', 'h', 0), // tgh = Tobagonian Creole English
+ TRUETYPE_TAG('t', 'g', 'i', 0), // tgi = Lawunuia
+ TRUETYPE_TAG('t', 'g', 'n', 0), // tgn = Tandaganon
+ TRUETYPE_TAG('t', 'g', 'o', 0), // tgo = Sudest
+ TRUETYPE_TAG('t', 'g', 'p', 0), // tgp = Tangoa
+ TRUETYPE_TAG('t', 'g', 'q', 0), // tgq = Tring
+ TRUETYPE_TAG('t', 'g', 'r', 0), // tgr = Tareng
+ TRUETYPE_TAG('t', 'g', 's', 0), // tgs = Nume
+ TRUETYPE_TAG('t', 'g', 't', 0), // tgt = Central Tagbanwa
+ TRUETYPE_TAG('t', 'g', 'u', 0), // tgu = Tanggu
+ TRUETYPE_TAG('t', 'g', 'v', 0), // tgv = Tingui-Boto
+ TRUETYPE_TAG('t', 'g', 'w', 0), // tgw = Tagwana Senoufo
+ TRUETYPE_TAG('t', 'g', 'x', 0), // tgx = Tagish
+ TRUETYPE_TAG('t', 'g', 'y', 0), // tgy = Togoyo
+ TRUETYPE_TAG('t', 'h', 'c', 0), // thc = Tai Hang Tong
+ TRUETYPE_TAG('t', 'h', 'd', 0), // thd = Thayore
+ TRUETYPE_TAG('t', 'h', 'e', 0), // the = Chitwania Tharu
+ TRUETYPE_TAG('t', 'h', 'f', 0), // thf = Thangmi
+ TRUETYPE_TAG('t', 'h', 'h', 0), // thh = Northern Tarahumara
+ TRUETYPE_TAG('t', 'h', 'i', 0), // thi = Tai Long
+ TRUETYPE_TAG('t', 'h', 'k', 0), // thk = Tharaka
+ TRUETYPE_TAG('t', 'h', 'l', 0), // thl = Dangaura Tharu
+ TRUETYPE_TAG('t', 'h', 'm', 0), // thm = Aheu
+ TRUETYPE_TAG('t', 'h', 'n', 0), // thn = Thachanadan
+ TRUETYPE_TAG('t', 'h', 'p', 0), // thp = Thompson
+ TRUETYPE_TAG('t', 'h', 'q', 0), // thq = Kochila Tharu
+ TRUETYPE_TAG('t', 'h', 'r', 0), // thr = Rana Tharu
+ TRUETYPE_TAG('t', 'h', 's', 0), // ths = Thakali
+ TRUETYPE_TAG('t', 'h', 't', 0), // tht = Tahltan
+ TRUETYPE_TAG('t', 'h', 'u', 0), // thu = Thuri
+ TRUETYPE_TAG('t', 'h', 'v', 0), // thv = Tahaggart Tamahaq
+ TRUETYPE_TAG('t', 'h', 'w', 0), // thw = Thudam
+ TRUETYPE_TAG('t', 'h', 'x', 0), // thx = The
+ TRUETYPE_TAG('t', 'h', 'y', 0), // thy = Tha
+ TRUETYPE_TAG('t', 'h', 'z', 0), // thz = Tayart Tamajeq
+ TRUETYPE_TAG('t', 'i', 'a', 0), // tia = Tidikelt Tamazight
+ TRUETYPE_TAG('t', 'i', 'c', 0), // tic = Tira
+ TRUETYPE_TAG('t', 'i', 'd', 0), // tid = Tidong
+ TRUETYPE_TAG('t', 'i', 'e', 0), // tie = Tingal
+ TRUETYPE_TAG('t', 'i', 'f', 0), // tif = Tifal
+ TRUETYPE_TAG('t', 'i', 'g', 0), // tig = Tigre
+ TRUETYPE_TAG('t', 'i', 'h', 0), // tih = Timugon Murut
+ TRUETYPE_TAG('t', 'i', 'i', 0), // tii = Tiene
+ TRUETYPE_TAG('t', 'i', 'j', 0), // tij = Tilung
+ TRUETYPE_TAG('t', 'i', 'k', 0), // tik = Tikar
+ TRUETYPE_TAG('t', 'i', 'l', 0), // til = Tillamook
+ TRUETYPE_TAG('t', 'i', 'm', 0), // tim = Timbe
+ TRUETYPE_TAG('t', 'i', 'n', 0), // tin = Tindi
+ TRUETYPE_TAG('t', 'i', 'o', 0), // tio = Teop
+ TRUETYPE_TAG('t', 'i', 'p', 0), // tip = Trimuris
+ TRUETYPE_TAG('t', 'i', 'q', 0), // tiq = Tiéfo
+ TRUETYPE_TAG('t', 'i', 's', 0), // tis = Masadiit Itneg
+ TRUETYPE_TAG('t', 'i', 't', 0), // tit = Tinigua
+ TRUETYPE_TAG('t', 'i', 'u', 0), // tiu = Adasen
+ TRUETYPE_TAG('t', 'i', 'v', 0), // tiv = Tiv
+ TRUETYPE_TAG('t', 'i', 'w', 0), // tiw = Tiwi
+ TRUETYPE_TAG('t', 'i', 'x', 0), // tix = Southern Tiwa
+ TRUETYPE_TAG('t', 'i', 'y', 0), // tiy = Tiruray
+ TRUETYPE_TAG('t', 'i', 'z', 0), // tiz = Tai Hongjin
+ TRUETYPE_TAG('t', 'j', 'a', 0), // tja = Tajuasohn
+ TRUETYPE_TAG('t', 'j', 'g', 0), // tjg = Tunjung
+ TRUETYPE_TAG('t', 'j', 'i', 0), // tji = Northern Tujia
+ TRUETYPE_TAG('t', 'j', 'm', 0), // tjm = Timucua
+ TRUETYPE_TAG('t', 'j', 'n', 0), // tjn = Tonjon
+ TRUETYPE_TAG('t', 'j', 'o', 0), // tjo = Temacine Tamazight
+ TRUETYPE_TAG('t', 'j', 's', 0), // tjs = Southern Tujia
+ TRUETYPE_TAG('t', 'j', 'u', 0), // tju = Tjurruru
+ TRUETYPE_TAG('t', 'k', 'a', 0), // tka = Truká
+ TRUETYPE_TAG('t', 'k', 'b', 0), // tkb = Buksa
+ TRUETYPE_TAG('t', 'k', 'd', 0), // tkd = Tukudede
+ TRUETYPE_TAG('t', 'k', 'e', 0), // tke = Takwane
+ TRUETYPE_TAG('t', 'k', 'f', 0), // tkf = Tukumanféd
+ TRUETYPE_TAG('t', 'k', 'g', 0), // tkg = Tesaka Malagasy
+ TRUETYPE_TAG('t', 'k', 'k', 0), // tkk = Takpa
+ TRUETYPE_TAG('t', 'k', 'l', 0), // tkl = Tokelau
+ TRUETYPE_TAG('t', 'k', 'm', 0), // tkm = Takelma
+ TRUETYPE_TAG('t', 'k', 'n', 0), // tkn = Toku-No-Shima
+ TRUETYPE_TAG('t', 'k', 'p', 0), // tkp = Tikopia
+ TRUETYPE_TAG('t', 'k', 'q', 0), // tkq = Tee
+ TRUETYPE_TAG('t', 'k', 'r', 0), // tkr = Tsakhur
+ TRUETYPE_TAG('t', 'k', 's', 0), // tks = Takestani
+ TRUETYPE_TAG('t', 'k', 't', 0), // tkt = Kathoriya Tharu
+ TRUETYPE_TAG('t', 'k', 'u', 0), // tku = Upper Necaxa Totonac
+ TRUETYPE_TAG('t', 'k', 'w', 0), // tkw = Teanu
+ TRUETYPE_TAG('t', 'k', 'x', 0), // tkx = Tangko
+ TRUETYPE_TAG('t', 'k', 'z', 0), // tkz = Takua
+ TRUETYPE_TAG('t', 'l', 'a', 0), // tla = Southwestern Tepehuan
+ TRUETYPE_TAG('t', 'l', 'b', 0), // tlb = Tobelo
+ TRUETYPE_TAG('t', 'l', 'c', 0), // tlc = Yecuatla Totonac
+ TRUETYPE_TAG('t', 'l', 'd', 0), // tld = Talaud
+ TRUETYPE_TAG('t', 'l', 'f', 0), // tlf = Telefol
+ TRUETYPE_TAG('t', 'l', 'g', 0), // tlg = Tofanma
+ TRUETYPE_TAG('t', 'l', 'h', 0), // tlh = Klingon
+ TRUETYPE_TAG('t', 'l', 'i', 0), // tli = Tlingit
+ TRUETYPE_TAG('t', 'l', 'j', 0), // tlj = Talinga-Bwisi
+ TRUETYPE_TAG('t', 'l', 'k', 0), // tlk = Taloki
+ TRUETYPE_TAG('t', 'l', 'l', 0), // tll = Tetela
+ TRUETYPE_TAG('t', 'l', 'm', 0), // tlm = Tolomako
+ TRUETYPE_TAG('t', 'l', 'n', 0), // tln = Talondo'
+ TRUETYPE_TAG('t', 'l', 'o', 0), // tlo = Talodi
+ TRUETYPE_TAG('t', 'l', 'p', 0), // tlp = Filomena Mata-Coahuitlán Totonac
+ TRUETYPE_TAG('t', 'l', 'q', 0), // tlq = Tai Loi
+ TRUETYPE_TAG('t', 'l', 'r', 0), // tlr = Talise
+ TRUETYPE_TAG('t', 'l', 's', 0), // tls = Tambotalo
+ TRUETYPE_TAG('t', 'l', 't', 0), // tlt = Teluti
+ TRUETYPE_TAG('t', 'l', 'u', 0), // tlu = Tulehu
+ TRUETYPE_TAG('t', 'l', 'v', 0), // tlv = Taliabu
+ TRUETYPE_TAG('t', 'l', 'w', 0), // tlw = South Wemale
+ TRUETYPE_TAG('t', 'l', 'x', 0), // tlx = Khehek
+ TRUETYPE_TAG('t', 'l', 'y', 0), // tly = Talysh
+ TRUETYPE_TAG('t', 'm', 'a', 0), // tma = Tama (Chad)
+ TRUETYPE_TAG('t', 'm', 'b', 0), // tmb = Katbol
+ TRUETYPE_TAG('t', 'm', 'c', 0), // tmc = Tumak
+ TRUETYPE_TAG('t', 'm', 'd', 0), // tmd = Haruai
+ TRUETYPE_TAG('t', 'm', 'e', 0), // tme = Tremembé
+ TRUETYPE_TAG('t', 'm', 'f', 0), // tmf = Toba-Maskoy
+ TRUETYPE_TAG('t', 'm', 'g', 0), // tmg = Ternateño
+ TRUETYPE_TAG('t', 'm', 'h', 0), // tmh = Tamashek
+ TRUETYPE_TAG('t', 'm', 'i', 0), // tmi = Tutuba
+ TRUETYPE_TAG('t', 'm', 'j', 0), // tmj = Samarokena
+ TRUETYPE_TAG('t', 'm', 'k', 0), // tmk = Northwestern Tamang
+ TRUETYPE_TAG('t', 'm', 'l', 0), // tml = Tamnim Citak
+ TRUETYPE_TAG('t', 'm', 'm', 0), // tmm = Tai Thanh
+ TRUETYPE_TAG('t', 'm', 'n', 0), // tmn = Taman (Indonesia)
+ TRUETYPE_TAG('t', 'm', 'o', 0), // tmo = Temoq
+ TRUETYPE_TAG('t', 'm', 'p', 0), // tmp = Tai Mène
+ TRUETYPE_TAG('t', 'm', 'q', 0), // tmq = Tumleo
+ TRUETYPE_TAG('t', 'm', 'r',
+ 0), // tmr = Jewish Babylonian Aramaic (ca. 200-1200 CE)
+ TRUETYPE_TAG('t', 'm', 's', 0), // tms = Tima
+ TRUETYPE_TAG('t', 'm', 't', 0), // tmt = Tasmate
+ TRUETYPE_TAG('t', 'm', 'u', 0), // tmu = Iau
+ TRUETYPE_TAG('t', 'm', 'v', 0), // tmv = Tembo (Motembo)
+ TRUETYPE_TAG('t', 'm', 'w', 0), // tmw = Temuan
+ TRUETYPE_TAG('t', 'm', 'y', 0), // tmy = Tami
+ TRUETYPE_TAG('t', 'm', 'z', 0), // tmz = Tamanaku
+ TRUETYPE_TAG('t', 'n', 'a', 0), // tna = Tacana
+ TRUETYPE_TAG('t', 'n', 'b', 0), // tnb = Western Tunebo
+ TRUETYPE_TAG('t', 'n', 'c', 0), // tnc = Tanimuca-Retuarã
+ TRUETYPE_TAG('t', 'n', 'd', 0), // tnd = Angosturas Tunebo
+ TRUETYPE_TAG('t', 'n', 'e', 0), // tne = Tinoc Kallahan
+ TRUETYPE_TAG('t', 'n', 'f', 0), // tnf = Tangshewi
+ TRUETYPE_TAG('t', 'n', 'g', 0), // tng = Tobanga
+ TRUETYPE_TAG('t', 'n', 'h', 0), // tnh = Maiani
+ TRUETYPE_TAG('t', 'n', 'i', 0), // tni = Tandia
+ TRUETYPE_TAG('t', 'n', 'k', 0), // tnk = Kwamera
+ TRUETYPE_TAG('t', 'n', 'l', 0), // tnl = Lenakel
+ TRUETYPE_TAG('t', 'n', 'm', 0), // tnm = Tabla
+ TRUETYPE_TAG('t', 'n', 'n', 0), // tnn = North Tanna
+ TRUETYPE_TAG('t', 'n', 'o', 0), // tno = Toromono
+ TRUETYPE_TAG('t', 'n', 'p', 0), // tnp = Whitesands
+ TRUETYPE_TAG('t', 'n', 'q', 0), // tnq = Taino
+ TRUETYPE_TAG('t', 'n', 'r', 0), // tnr = Bedik
+ TRUETYPE_TAG('t', 'n', 's', 0), // tns = Tenis
+ TRUETYPE_TAG('t', 'n', 't', 0), // tnt = Tontemboan
+ TRUETYPE_TAG('t', 'n', 'u', 0), // tnu = Tay Khang
+ TRUETYPE_TAG('t', 'n', 'v', 0), // tnv = Tangchangya
+ TRUETYPE_TAG('t', 'n', 'w', 0), // tnw = Tonsawang
+ TRUETYPE_TAG('t', 'n', 'x', 0), // tnx = Tanema
+ TRUETYPE_TAG('t', 'n', 'y', 0), // tny = Tongwe
+ TRUETYPE_TAG('t', 'n', 'z', 0), // tnz = Tonga (Thailand)
+ TRUETYPE_TAG('t', 'o', 'b', 0), // tob = Toba
+ TRUETYPE_TAG('t', 'o', 'c', 0), // toc = Coyutla Totonac
+ TRUETYPE_TAG('t', 'o', 'd', 0), // tod = Toma
+ TRUETYPE_TAG('t', 'o', 'e', 0), // toe = Tomedes
+ TRUETYPE_TAG('t', 'o', 'f', 0), // tof = Gizrra
+ TRUETYPE_TAG('t', 'o', 'g', 0), // tog = Tonga (Nyasa)
+ TRUETYPE_TAG('t', 'o', 'h', 0), // toh = Gitonga
+ TRUETYPE_TAG('t', 'o', 'i', 0), // toi = Tonga (Zambia)
+ TRUETYPE_TAG('t', 'o', 'j', 0), // toj = Tojolabal
+ TRUETYPE_TAG('t', 'o', 'l', 0), // tol = Tolowa
+ TRUETYPE_TAG('t', 'o', 'm', 0), // tom = Tombulu
+ TRUETYPE_TAG('t', 'o', 'o', 0), // too = Xicotepec De Juárez Totonac
+ TRUETYPE_TAG('t', 'o', 'p', 0), // top = Papantla Totonac
+ TRUETYPE_TAG('t', 'o', 'q', 0), // toq = Toposa
+ TRUETYPE_TAG('t', 'o', 'r', 0), // tor = Togbo-Vara Banda
+ TRUETYPE_TAG('t', 'o', 's', 0), // tos = Highland Totonac
+ TRUETYPE_TAG('t', 'o', 'u', 0), // tou = Tho
+ TRUETYPE_TAG('t', 'o', 'v', 0), // tov = Upper Taromi
+ TRUETYPE_TAG('t', 'o', 'w', 0), // tow = Jemez
+ TRUETYPE_TAG('t', 'o', 'x', 0), // tox = Tobian
+ TRUETYPE_TAG('t', 'o', 'y', 0), // toy = Topoiyo
+ TRUETYPE_TAG('t', 'o', 'z', 0), // toz = To
+ TRUETYPE_TAG('t', 'p', 'a', 0), // tpa = Taupota
+ TRUETYPE_TAG('t', 'p', 'c', 0), // tpc = Azoyú Me'phaa
+ TRUETYPE_TAG('t', 'p', 'e', 0), // tpe = Tippera
+ TRUETYPE_TAG('t', 'p', 'f', 0), // tpf = Tarpia
+ TRUETYPE_TAG('t', 'p', 'g', 0), // tpg = Kula
+ TRUETYPE_TAG('t', 'p', 'i', 0), // tpi = Tok Pisin
+ TRUETYPE_TAG('t', 'p', 'j', 0), // tpj = Tapieté
+ TRUETYPE_TAG('t', 'p', 'k', 0), // tpk = Tupinikin
+ TRUETYPE_TAG('t', 'p', 'l', 0), // tpl = Tlacoapa Me'phaa
+ TRUETYPE_TAG('t', 'p', 'm', 0), // tpm = Tampulma
+ TRUETYPE_TAG('t', 'p', 'n', 0), // tpn = Tupinambá
+ TRUETYPE_TAG('t', 'p', 'o', 0), // tpo = Tai Pao
+ TRUETYPE_TAG('t', 'p', 'p', 0), // tpp = Pisaflores Tepehua
+ TRUETYPE_TAG('t', 'p', 'q', 0), // tpq = Tukpa
+ TRUETYPE_TAG('t', 'p', 'r', 0), // tpr = Tuparí
+ TRUETYPE_TAG('t', 'p', 't', 0), // tpt = Tlachichilco Tepehua
+ TRUETYPE_TAG('t', 'p', 'u', 0), // tpu = Tampuan
+ TRUETYPE_TAG('t', 'p', 'v', 0), // tpv = Tanapag
+ TRUETYPE_TAG('t', 'p', 'w', 0), // tpw = Tupí
+ TRUETYPE_TAG('t', 'p', 'x', 0), // tpx = Acatepec Me'phaa
+ TRUETYPE_TAG('t', 'p', 'y', 0), // tpy = Trumai
+ TRUETYPE_TAG('t', 'p', 'z', 0), // tpz = Tinputz
+ TRUETYPE_TAG('t', 'q', 'b', 0), // tqb = Tembé
+ TRUETYPE_TAG('t', 'q', 'l', 0), // tql = Lehali
+ TRUETYPE_TAG('t', 'q', 'm', 0), // tqm = Turumsa
+ TRUETYPE_TAG('t', 'q', 'n', 0), // tqn = Tenino
+ TRUETYPE_TAG('t', 'q', 'o', 0), // tqo = Toaripi
+ TRUETYPE_TAG('t', 'q', 'p', 0), // tqp = Tomoip
+ TRUETYPE_TAG('t', 'q', 'q', 0), // tqq = Tunni
+ TRUETYPE_TAG('t', 'q', 'r', 0), // tqr = Torona
+ TRUETYPE_TAG('t', 'q', 't', 0), // tqt = Western Totonac
+ TRUETYPE_TAG('t', 'q', 'u', 0), // tqu = Touo
+ TRUETYPE_TAG('t', 'q', 'w', 0), // tqw = Tonkawa
+ TRUETYPE_TAG('t', 'r', 'a', 0), // tra = Tirahi
+ TRUETYPE_TAG('t', 'r', 'b', 0), // trb = Terebu
+ TRUETYPE_TAG('t', 'r', 'c', 0), // trc = Copala Triqui
+ TRUETYPE_TAG('t', 'r', 'd', 0), // trd = Turi
+ TRUETYPE_TAG('t', 'r', 'e', 0), // tre = East Tarangan
+ TRUETYPE_TAG('t', 'r', 'f', 0), // trf = Trinidadian Creole English
+ TRUETYPE_TAG('t', 'r', 'g', 0), // trg = Lishán Didán
+ TRUETYPE_TAG('t', 'r', 'h', 0), // trh = Turaka
+ TRUETYPE_TAG('t', 'r', 'i', 0), // tri = Trió
+ TRUETYPE_TAG('t', 'r', 'j', 0), // trj = Toram
+ TRUETYPE_TAG('t', 'r', 'k', 0), // trk = Turkic languages
+ TRUETYPE_TAG('t', 'r', 'l', 0), // trl = Traveller Scottish
+ TRUETYPE_TAG('t', 'r', 'm', 0), // trm = Tregami
+ TRUETYPE_TAG('t', 'r', 'n', 0), // trn = Trinitario
+ TRUETYPE_TAG('t', 'r', 'o', 0), // tro = Tarao Naga
+ TRUETYPE_TAG('t', 'r', 'p', 0), // trp = Kok Borok
+ TRUETYPE_TAG('t', 'r', 'q', 0), // trq = San Martín Itunyoso Triqui
+ TRUETYPE_TAG('t', 'r', 'r', 0), // trr = Taushiro
+ TRUETYPE_TAG('t', 'r', 's', 0), // trs = Chicahuaxtla Triqui
+ TRUETYPE_TAG('t', 'r', 't', 0), // trt = Tunggare
+ TRUETYPE_TAG('t', 'r', 'u', 0), // tru = Turoyo
+ TRUETYPE_TAG('t', 'r', 'v', 0), // trv = Taroko
+ TRUETYPE_TAG('t', 'r', 'w', 0), // trw = Torwali
+ TRUETYPE_TAG('t', 'r', 'x', 0), // trx = Tringgus-Sembaan Bidayuh
+ TRUETYPE_TAG('t', 'r', 'y', 0), // try = Turung
+ TRUETYPE_TAG('t', 'r', 'z', 0), // trz = Torá
+ TRUETYPE_TAG('t', 's', 'a', 0), // tsa = Tsaangi
+ TRUETYPE_TAG('t', 's', 'b', 0), // tsb = Tsamai
+ TRUETYPE_TAG('t', 's', 'c', 0), // tsc = Tswa
+ TRUETYPE_TAG('t', 's', 'd', 0), // tsd = Tsakonian
+ TRUETYPE_TAG('t', 's', 'e', 0), // tse = Tunisian Sign Language
+ TRUETYPE_TAG('t', 's', 'f', 0), // tsf = Southwestern Tamang
+ TRUETYPE_TAG('t', 's', 'g', 0), // tsg = Tausug
+ TRUETYPE_TAG('t', 's', 'h', 0), // tsh = Tsuvan
+ TRUETYPE_TAG('t', 's', 'i', 0), // tsi = Tsimshian
+ TRUETYPE_TAG('t', 's', 'j', 0), // tsj = Tshangla
+ TRUETYPE_TAG('t', 's', 'k', 0), // tsk = Tseku
+ TRUETYPE_TAG('t', 's', 'l', 0), // tsl = Ts'ün-Lao
+ TRUETYPE_TAG('t', 's', 'm', 0), // tsm = Turkish Sign Language
+ TRUETYPE_TAG('t', 's', 'p', 0), // tsp = Northern Toussian
+ TRUETYPE_TAG('t', 's', 'q', 0), // tsq = Thai Sign Language
+ TRUETYPE_TAG('t', 's', 'r', 0), // tsr = Akei
+ TRUETYPE_TAG('t', 's', 's', 0), // tss = Taiwan Sign Language
+ TRUETYPE_TAG('t', 's', 't', 0), // tst = Tondi Songway Kiini
+ TRUETYPE_TAG('t', 's', 'u', 0), // tsu = Tsou
+ TRUETYPE_TAG('t', 's', 'v', 0), // tsv = Tsogo
+ TRUETYPE_TAG('t', 's', 'w', 0), // tsw = Tsishingini
+ TRUETYPE_TAG('t', 's', 'x', 0), // tsx = Mubami
+ TRUETYPE_TAG('t', 's', 'y', 0), // tsy = Tebul Sign Language
+ TRUETYPE_TAG('t', 's', 'z', 0), // tsz = Purepecha
+ TRUETYPE_TAG('t', 't', 'a', 0), // tta = Tutelo
+ TRUETYPE_TAG('t', 't', 'b', 0), // ttb = Gaa
+ TRUETYPE_TAG('t', 't', 'c', 0), // ttc = Tektiteko
+ TRUETYPE_TAG('t', 't', 'd', 0), // ttd = Tauade
+ TRUETYPE_TAG('t', 't', 'e', 0), // tte = Bwanabwana
+ TRUETYPE_TAG('t', 't', 'f', 0), // ttf = Tuotomb
+ TRUETYPE_TAG('t', 't', 'g', 0), // ttg = Tutong
+ TRUETYPE_TAG('t', 't', 'h', 0), // tth = Upper Ta'oih
+ TRUETYPE_TAG('t', 't', 'i', 0), // tti = Tobati
+ TRUETYPE_TAG('t', 't', 'j', 0), // ttj = Tooro
+ TRUETYPE_TAG('t', 't', 'k', 0), // ttk = Totoro
+ TRUETYPE_TAG('t', 't', 'l', 0), // ttl = Totela
+ TRUETYPE_TAG('t', 't', 'm', 0), // ttm = Northern Tutchone
+ TRUETYPE_TAG('t', 't', 'n', 0), // ttn = Towei
+ TRUETYPE_TAG('t', 't', 'o', 0), // tto = Lower Ta'oih
+ TRUETYPE_TAG('t', 't', 'p', 0), // ttp = Tombelala
+ TRUETYPE_TAG('t', 't', 'q', 0), // ttq = Tawallammat Tamajaq
+ TRUETYPE_TAG('t', 't', 'r', 0), // ttr = Tera
+ TRUETYPE_TAG('t', 't', 's', 0), // tts = Northeastern Thai
+ TRUETYPE_TAG('t', 't', 't', 0), // ttt = Muslim Tat
+ TRUETYPE_TAG('t', 't', 'u', 0), // ttu = Torau
+ TRUETYPE_TAG('t', 't', 'v', 0), // ttv = Titan
+ TRUETYPE_TAG('t', 't', 'w', 0), // ttw = Long Wat
+ TRUETYPE_TAG('t', 't', 'y', 0), // tty = Sikaritai
+ TRUETYPE_TAG('t', 't', 'z', 0), // ttz = Tsum
+ TRUETYPE_TAG('t', 'u', 'a', 0), // tua = Wiarumus
+ TRUETYPE_TAG('t', 'u', 'b', 0), // tub = Tübatulabal
+ TRUETYPE_TAG('t', 'u', 'c', 0), // tuc = Mutu
+ TRUETYPE_TAG('t', 'u', 'd', 0), // tud = Tuxá
+ TRUETYPE_TAG('t', 'u', 'e', 0), // tue = Tuyuca
+ TRUETYPE_TAG('t', 'u', 'f', 0), // tuf = Central Tunebo
+ TRUETYPE_TAG('t', 'u', 'g', 0), // tug = Tunia
+ TRUETYPE_TAG('t', 'u', 'h', 0), // tuh = Taulil
+ TRUETYPE_TAG('t', 'u', 'i', 0), // tui = Tupuri
+ TRUETYPE_TAG('t', 'u', 'j', 0), // tuj = Tugutil
+ TRUETYPE_TAG('t', 'u', 'l', 0), // tul = Tula
+ TRUETYPE_TAG('t', 'u', 'm', 0), // tum = Tumbuka
+ TRUETYPE_TAG('t', 'u', 'n', 0), // tun = Tunica
+ TRUETYPE_TAG('t', 'u', 'o', 0), // tuo = Tucano
+ TRUETYPE_TAG('t', 'u', 'p', 0), // tup = Tupi languages
+ TRUETYPE_TAG('t', 'u', 'q', 0), // tuq = Tedaga
+ TRUETYPE_TAG('t', 'u', 's', 0), // tus = Tuscarora
+ TRUETYPE_TAG('t', 'u', 't', 0), // tut = Altaic languages
+ TRUETYPE_TAG('t', 'u', 'u', 0), // tuu = Tututni
+ TRUETYPE_TAG('t', 'u', 'v', 0), // tuv = Turkana
+ TRUETYPE_TAG('t', 'u', 'w', 0), // tuw = Tungus languages
+ TRUETYPE_TAG('t', 'u', 'x', 0), // tux = Tuxináwa
+ TRUETYPE_TAG('t', 'u', 'y', 0), // tuy = Tugen
+ TRUETYPE_TAG('t', 'u', 'z', 0), // tuz = Turka
+ TRUETYPE_TAG('t', 'v', 'a', 0), // tva = Vaghua
+ TRUETYPE_TAG('t', 'v', 'd', 0), // tvd = Tsuvadi
+ TRUETYPE_TAG('t', 'v', 'e', 0), // tve = Te'un
+ TRUETYPE_TAG('t', 'v', 'k', 0), // tvk = Southeast Ambrym
+ TRUETYPE_TAG('t', 'v', 'l', 0), // tvl = Tuvalu
+ TRUETYPE_TAG('t', 'v', 'm', 0), // tvm = Tela-Masbuar
+ TRUETYPE_TAG('t', 'v', 'n', 0), // tvn = Tavoyan
+ TRUETYPE_TAG('t', 'v', 'o', 0), // tvo = Tidore
+ TRUETYPE_TAG('t', 'v', 's', 0), // tvs = Taveta
+ TRUETYPE_TAG('t', 'v', 't', 0), // tvt = Tutsa Naga
+ TRUETYPE_TAG('t', 'v', 'w', 0), // tvw = Sedoa
+ TRUETYPE_TAG('t', 'v', 'y', 0), // tvy = Timor Pidgin
+ TRUETYPE_TAG('t', 'w', 'a', 0), // twa = Twana
+ TRUETYPE_TAG('t', 'w', 'b', 0), // twb = Western Tawbuid
+ TRUETYPE_TAG('t', 'w', 'c', 0), // twc = Teshenawa
+ TRUETYPE_TAG('t', 'w', 'd', 0), // twd = Twents
+ TRUETYPE_TAG('t', 'w', 'e', 0), // twe = Tewa (Indonesia)
+ TRUETYPE_TAG('t', 'w', 'f', 0), // twf = Northern Tiwa
+ TRUETYPE_TAG('t', 'w', 'g', 0), // twg = Tereweng
+ TRUETYPE_TAG('t', 'w', 'h', 0), // twh = Tai Dón
+ TRUETYPE_TAG('t', 'w', 'l', 0), // twl = Tawara
+ TRUETYPE_TAG('t', 'w', 'm', 0), // twm = Tawang Monpa
+ TRUETYPE_TAG('t', 'w', 'n', 0), // twn = Twendi
+ TRUETYPE_TAG('t', 'w', 'o', 0), // two = Tswapong
+ TRUETYPE_TAG('t', 'w', 'p', 0), // twp = Ere
+ TRUETYPE_TAG('t', 'w', 'q', 0), // twq = Tasawaq
+ TRUETYPE_TAG('t', 'w', 'r', 0), // twr = Southwestern Tarahumara
+ TRUETYPE_TAG('t', 'w', 't', 0), // twt = Turiwára
+ TRUETYPE_TAG('t', 'w', 'u', 0), // twu = Termanu
+ TRUETYPE_TAG('t', 'w', 'w', 0), // tww = Tuwari
+ TRUETYPE_TAG('t', 'w', 'x', 0), // twx = Tewe
+ TRUETYPE_TAG('t', 'w', 'y', 0), // twy = Tawoyan
+ TRUETYPE_TAG('t', 'x', 'a', 0), // txa = Tombonuo
+ TRUETYPE_TAG('t', 'x', 'b', 0), // txb = Tokharian B
+ TRUETYPE_TAG('t', 'x', 'c', 0), // txc = Tsetsaut
+ TRUETYPE_TAG('t', 'x', 'e', 0), // txe = Totoli
+ TRUETYPE_TAG('t', 'x', 'g', 0), // txg = Tangut
+ TRUETYPE_TAG('t', 'x', 'h', 0), // txh = Thracian
+ TRUETYPE_TAG('t', 'x', 'i', 0), // txi = Ikpeng
+ TRUETYPE_TAG('t', 'x', 'm', 0), // txm = Tomini
+ TRUETYPE_TAG('t', 'x', 'n', 0), // txn = West Tarangan
+ TRUETYPE_TAG('t', 'x', 'o', 0), // txo = Toto
+ TRUETYPE_TAG('t', 'x', 'q', 0), // txq = Tii
+ TRUETYPE_TAG('t', 'x', 'r', 0), // txr = Tartessian
+ TRUETYPE_TAG('t', 'x', 's', 0), // txs = Tonsea
+ TRUETYPE_TAG('t', 'x', 't', 0), // txt = Citak
+ TRUETYPE_TAG('t', 'x', 'u', 0), // txu = Kayapó
+ TRUETYPE_TAG('t', 'x', 'x', 0), // txx = Tatana
+ TRUETYPE_TAG('t', 'x', 'y', 0), // txy = Tanosy Malagasy
+ TRUETYPE_TAG('t', 'y', 'a', 0), // tya = Tauya
+ TRUETYPE_TAG('t', 'y', 'e', 0), // tye = Kyenga
+ TRUETYPE_TAG('t', 'y', 'h', 0), // tyh = O'du
+ TRUETYPE_TAG('t', 'y', 'i', 0), // tyi = Teke-Tsaayi
+ TRUETYPE_TAG('t', 'y', 'j', 0), // tyj = Tai Do
+ TRUETYPE_TAG('t', 'y', 'l', 0), // tyl = Thu Lao
+ TRUETYPE_TAG('t', 'y', 'n', 0), // tyn = Kombai
+ TRUETYPE_TAG('t', 'y', 'p', 0), // typ = Thaypan
+ TRUETYPE_TAG('t', 'y', 'r', 0), // tyr = Tai Daeng
+ TRUETYPE_TAG('t', 'y', 's', 0), // tys = Tày Sa Pa
+ TRUETYPE_TAG('t', 'y', 't', 0), // tyt = Tày Tac
+ TRUETYPE_TAG('t', 'y', 'u', 0), // tyu = Kua
+ TRUETYPE_TAG('t', 'y', 'v', 0), // tyv = Tuvinian
+ TRUETYPE_TAG('t', 'y', 'x', 0), // tyx = Teke-Tyee
+ TRUETYPE_TAG('t', 'y', 'z', 0), // tyz = Tày
+ TRUETYPE_TAG('t', 'z', 'a', 0), // tza = Tanzanian Sign Language
+ TRUETYPE_TAG('t', 'z', 'h', 0), // tzh = Tzeltal
+ TRUETYPE_TAG('t', 'z', 'j', 0), // tzj = Tz'utujil
+ TRUETYPE_TAG('t', 'z', 'm', 0), // tzm = Central Atlas Tamazight
+ TRUETYPE_TAG('t', 'z', 'n', 0), // tzn = Tugun
+ TRUETYPE_TAG('t', 'z', 'o', 0), // tzo = Tzotzil
+ TRUETYPE_TAG('t', 'z', 'x', 0), // tzx = Tabriak
+ TRUETYPE_TAG('u', 'a', 'm', 0), // uam = Uamué
+ TRUETYPE_TAG('u', 'a', 'n', 0), // uan = Kuan
+ TRUETYPE_TAG('u', 'a', 'r', 0), // uar = Tairuma
+ TRUETYPE_TAG('u', 'b', 'a', 0), // uba = Ubang
+ TRUETYPE_TAG('u', 'b', 'i', 0), // ubi = Ubi
+ TRUETYPE_TAG('u', 'b', 'l', 0), // ubl = Buhi'non Bikol
+ TRUETYPE_TAG('u', 'b', 'r', 0), // ubr = Ubir
+ TRUETYPE_TAG('u', 'b', 'u', 0), // ubu = Umbu-Ungu
+ TRUETYPE_TAG('u', 'b', 'y', 0), // uby = Ubykh
+ TRUETYPE_TAG('u', 'd', 'a', 0), // uda = Uda
+ TRUETYPE_TAG('u', 'd', 'e', 0), // ude = Udihe
+ TRUETYPE_TAG('u', 'd', 'g', 0), // udg = Muduga
+ TRUETYPE_TAG('u', 'd', 'i', 0), // udi = Udi
+ TRUETYPE_TAG('u', 'd', 'j', 0), // udj = Ujir
+ TRUETYPE_TAG('u', 'd', 'l', 0), // udl = Wuzlam
+ TRUETYPE_TAG('u', 'd', 'm', 0), // udm = Udmurt
+ TRUETYPE_TAG('u', 'd', 'u', 0), // udu = Uduk
+ TRUETYPE_TAG('u', 'e', 's', 0), // ues = Kioko
+ TRUETYPE_TAG('u', 'f', 'i', 0), // ufi = Ufim
+ TRUETYPE_TAG('u', 'g', 'a', 0), // uga = Ugaritic
+ TRUETYPE_TAG('u', 'g', 'b', 0), // ugb = Kuku-Ugbanh
+ TRUETYPE_TAG('u', 'g', 'e', 0), // uge = Ughele
+ TRUETYPE_TAG('u', 'g', 'n', 0), // ugn = Ugandan Sign Language
+ TRUETYPE_TAG('u', 'g', 'o', 0), // ugo = Ugong
+ TRUETYPE_TAG('u', 'g', 'y', 0), // ugy = Uruguayan Sign Language
+ TRUETYPE_TAG('u', 'h', 'a', 0), // uha = Uhami
+ TRUETYPE_TAG('u', 'h', 'n', 0), // uhn = Damal
+ TRUETYPE_TAG('u', 'i', 's', 0), // uis = Uisai
+ TRUETYPE_TAG('u', 'i', 'v', 0), // uiv = Iyive
+ TRUETYPE_TAG('u', 'j', 'i', 0), // uji = Tanjijili
+ TRUETYPE_TAG('u', 'k', 'a', 0), // uka = Kaburi
+ TRUETYPE_TAG('u', 'k', 'g', 0), // ukg = Ukuriguma
+ TRUETYPE_TAG('u', 'k', 'h', 0), // ukh = Ukhwejo
+ TRUETYPE_TAG('u', 'k', 'l', 0), // ukl = Ukrainian Sign Language
+ TRUETYPE_TAG('u', 'k', 'p', 0), // ukp = Ukpe-Bayobiri
+ TRUETYPE_TAG('u', 'k', 'q', 0), // ukq = Ukwa
+ TRUETYPE_TAG('u', 'k', 's', 0), // uks = Urubú-Kaapor Sign Language
+ TRUETYPE_TAG('u', 'k', 'u', 0), // uku = Ukue
+ TRUETYPE_TAG('u', 'k', 'w', 0), // ukw = Ukwuani-Aboh-Ndoni
+ TRUETYPE_TAG('u', 'l', 'a', 0), // ula = Fungwa
+ TRUETYPE_TAG('u', 'l', 'b', 0), // ulb = Ulukwumi
+ TRUETYPE_TAG('u', 'l', 'c', 0), // ulc = Ulch
+ TRUETYPE_TAG('u', 'l', 'f', 0), // ulf = Usku
+ TRUETYPE_TAG('u', 'l', 'i', 0), // uli = Ulithian
+ TRUETYPE_TAG('u', 'l', 'k', 0), // ulk = Meriam
+ TRUETYPE_TAG('u', 'l', 'l', 0), // ull = Ullatan
+ TRUETYPE_TAG('u', 'l', 'm', 0), // ulm = Ulumanda'
+ TRUETYPE_TAG('u', 'l', 'n', 0), // uln = Unserdeutsch
+ TRUETYPE_TAG('u', 'l', 'u', 0), // ulu = Uma' Lung
+ TRUETYPE_TAG('u', 'l', 'w', 0), // ulw = Ulwa
+ TRUETYPE_TAG('u', 'm', 'a', 0), // uma = Umatilla
+ TRUETYPE_TAG('u', 'm', 'b', 0), // umb = Umbundu
+ TRUETYPE_TAG('u', 'm', 'c', 0), // umc = Marrucinian
+ TRUETYPE_TAG('u', 'm', 'd', 0), // umd = Umbindhamu
+ TRUETYPE_TAG('u', 'm', 'g', 0), // umg = Umbuygamu
+ TRUETYPE_TAG('u', 'm', 'i', 0), // umi = Ukit
+ TRUETYPE_TAG('u', 'm', 'm', 0), // umm = Umon
+ TRUETYPE_TAG('u', 'm', 'n', 0), // umn = Makyan Naga
+ TRUETYPE_TAG('u', 'm', 'o', 0), // umo = Umotína
+ TRUETYPE_TAG('u', 'm', 'p', 0), // ump = Umpila
+ TRUETYPE_TAG('u', 'm', 'r', 0), // umr = Umbugarla
+ TRUETYPE_TAG('u', 'm', 's', 0), // ums = Pendau
+ TRUETYPE_TAG('u', 'm', 'u', 0), // umu = Munsee
+ TRUETYPE_TAG('u', 'n', 'a', 0), // una = North Watut
+ TRUETYPE_TAG('u', 'n', 'd', 0), // und = Undetermined
+ TRUETYPE_TAG('u', 'n', 'e', 0), // une = Uneme
+ TRUETYPE_TAG('u', 'n', 'g', 0), // ung = Ngarinyin
+ TRUETYPE_TAG('u', 'n', 'k', 0), // unk = Enawené-Nawé
+ TRUETYPE_TAG('u', 'n', 'm', 0), // unm = Unami
+ TRUETYPE_TAG('u', 'n', 'p', 0), // unp = Worora
+ TRUETYPE_TAG('u', 'n', 'r', 0), // unr = Mundari
+ TRUETYPE_TAG('u', 'n', 'x', 0), // unx = Munda
+ TRUETYPE_TAG('u', 'n', 'z', 0), // unz = Unde Kaili
+ TRUETYPE_TAG('u', 'o', 'k', 0), // uok = Uokha
+ TRUETYPE_TAG('u', 'p', 'i', 0), // upi = Umeda
+ TRUETYPE_TAG('u', 'p', 'v', 0), // upv = Uripiv-Wala-Rano-Atchin
+ TRUETYPE_TAG('u', 'r', 'a', 0), // ura = Urarina
+ TRUETYPE_TAG('u', 'r', 'b', 0), // urb = Urubú-Kaapor
+ TRUETYPE_TAG('u', 'r', 'c', 0), // urc = Urningangg
+ TRUETYPE_TAG('u', 'r', 'e', 0), // ure = Uru
+ TRUETYPE_TAG('u', 'r', 'f', 0), // urf = Uradhi
+ TRUETYPE_TAG('u', 'r', 'g', 0), // urg = Urigina
+ TRUETYPE_TAG('u', 'r', 'h', 0), // urh = Urhobo
+ TRUETYPE_TAG('u', 'r', 'i', 0), // uri = Urim
+ TRUETYPE_TAG('u', 'r', 'j', 0), // urj = Uralic languages
+ TRUETYPE_TAG('u', 'r', 'k', 0), // urk = Urak Lawoi'
+ TRUETYPE_TAG('u', 'r', 'l', 0), // url = Urali
+ TRUETYPE_TAG('u', 'r', 'm', 0), // urm = Urapmin
+ TRUETYPE_TAG('u', 'r', 'n', 0), // urn = Uruangnirin
+ TRUETYPE_TAG('u', 'r', 'o', 0), // uro = Ura (Papua New Guinea)
+ TRUETYPE_TAG('u', 'r', 'p', 0), // urp = Uru-Pa-In
+ TRUETYPE_TAG('u', 'r', 'r', 0), // urr = Lehalurup
+ TRUETYPE_TAG('u', 'r', 't', 0), // urt = Urat
+ TRUETYPE_TAG('u', 'r', 'u', 0), // uru = Urumi
+ TRUETYPE_TAG('u', 'r', 'v', 0), // urv = Uruava
+ TRUETYPE_TAG('u', 'r', 'w', 0), // urw = Sop
+ TRUETYPE_TAG('u', 'r', 'x', 0), // urx = Urimo
+ TRUETYPE_TAG('u', 'r', 'y', 0), // ury = Orya
+ TRUETYPE_TAG('u', 'r', 'z', 0), // urz = Uru-Eu-Wau-Wau
+ TRUETYPE_TAG('u', 's', 'a', 0), // usa = Usarufa
+ TRUETYPE_TAG('u', 's', 'h', 0), // ush = Ushojo
+ TRUETYPE_TAG('u', 's', 'i', 0), // usi = Usui
+ TRUETYPE_TAG('u', 's', 'k', 0), // usk = Usaghade
+ TRUETYPE_TAG('u', 's', 'p', 0), // usp = Uspanteco
+ TRUETYPE_TAG('u', 's', 'u', 0), // usu = Uya
+ TRUETYPE_TAG('u', 't', 'a', 0), // uta = Otank
+ TRUETYPE_TAG('u', 't', 'e', 0), // ute = Ute-Southern Paiute
+ TRUETYPE_TAG('u', 't', 'p', 0), // utp = Amba (Solomon Islands)
+ TRUETYPE_TAG('u', 't', 'r', 0), // utr = Etulo
+ TRUETYPE_TAG('u', 't', 'u', 0), // utu = Utu
+ TRUETYPE_TAG('u', 'u', 'm', 0), // uum = Urum
+ TRUETYPE_TAG('u', 'u', 'n', 0), // uun = Kulon-Pazeh
+ TRUETYPE_TAG('u', 'u', 'r', 0), // uur = Ura (Vanuatu)
+ TRUETYPE_TAG('u', 'u', 'u', 0), // uuu = U
+ TRUETYPE_TAG('u', 'v', 'e', 0), // uve = West Uvean
+ TRUETYPE_TAG('u', 'v', 'h', 0), // uvh = Uri
+ TRUETYPE_TAG('u', 'v', 'l', 0), // uvl = Lote
+ TRUETYPE_TAG('u', 'w', 'a', 0), // uwa = Kuku-Uwanh
+ TRUETYPE_TAG('u', 'y', 'a', 0), // uya = Doko-Uyanga
+ TRUETYPE_TAG('u', 'z', 'n', 0), // uzn = Northern Uzbek
+ TRUETYPE_TAG('u', 'z', 's', 0), // uzs = Southern Uzbek
+ TRUETYPE_TAG('v', 'a', 'a', 0), // vaa = Vaagri Booli
+ TRUETYPE_TAG('v', 'a', 'e', 0), // vae = Vale
+ TRUETYPE_TAG('v', 'a', 'f', 0), // vaf = Vafsi
+ TRUETYPE_TAG('v', 'a', 'g', 0), // vag = Vagla
+ TRUETYPE_TAG('v', 'a', 'h', 0), // vah = Varhadi-Nagpuri
+ TRUETYPE_TAG('v', 'a', 'i', 0), // vai = Vai
+ TRUETYPE_TAG('v', 'a', 'j', 0), // vaj = Vasekela Bushman
+ TRUETYPE_TAG('v', 'a', 'l', 0), // val = Vehes
+ TRUETYPE_TAG('v', 'a', 'm', 0), // vam = Vanimo
+ TRUETYPE_TAG('v', 'a', 'n', 0), // van = Valman
+ TRUETYPE_TAG('v', 'a', 'o', 0), // vao = Vao
+ TRUETYPE_TAG('v', 'a', 'p', 0), // vap = Vaiphei
+ TRUETYPE_TAG('v', 'a', 'r', 0), // var = Huarijio
+ TRUETYPE_TAG('v', 'a', 's', 0), // vas = Vasavi
+ TRUETYPE_TAG('v', 'a', 'u', 0), // vau = Vanuma
+ TRUETYPE_TAG('v', 'a', 'v', 0), // vav = Varli
+ TRUETYPE_TAG('v', 'a', 'y', 0), // vay = Wayu
+ TRUETYPE_TAG('v', 'b', 'b', 0), // vbb = Southeast Babar
+ TRUETYPE_TAG('v', 'b', 'k', 0), // vbk = Southwestern Bontok
+ TRUETYPE_TAG('v', 'e', 'c', 0), // vec = Venetian
+ TRUETYPE_TAG('v', 'e', 'd', 0), // ved = Veddah
+ TRUETYPE_TAG('v', 'e', 'l', 0), // vel = Veluws
+ TRUETYPE_TAG('v', 'e', 'm', 0), // vem = Vemgo-Mabas
+ TRUETYPE_TAG('v', 'e', 'o', 0), // veo = Ventureño
+ TRUETYPE_TAG('v', 'e', 'p', 0), // vep = Veps
+ TRUETYPE_TAG('v', 'e', 'r', 0), // ver = Mom Jango
+ TRUETYPE_TAG('v', 'g', 'r', 0), // vgr = Vaghri
+ TRUETYPE_TAG('v', 'g', 't', 0), // vgt = Vlaamse Gebarentaal
+ TRUETYPE_TAG('v', 'i', 'c', 0), // vic = Virgin Islands Creole English
+ TRUETYPE_TAG('v', 'i', 'd', 0), // vid = Vidunda
+ TRUETYPE_TAG('v', 'i', 'f', 0), // vif = Vili
+ TRUETYPE_TAG('v', 'i', 'g', 0), // vig = Viemo
+ TRUETYPE_TAG('v', 'i', 'l', 0), // vil = Vilela
+ TRUETYPE_TAG('v', 'i', 'n', 0), // vin = Vinza
+ TRUETYPE_TAG('v', 'i', 's', 0), // vis = Vishavan
+ TRUETYPE_TAG('v', 'i', 't', 0), // vit = Viti
+ TRUETYPE_TAG('v', 'i', 'v', 0), // viv = Iduna
+ TRUETYPE_TAG('v', 'k', 'a', 0), // vka = Kariyarra
+ TRUETYPE_TAG('v', 'k', 'i', 0), // vki = Ija-Zuba
+ TRUETYPE_TAG('v', 'k', 'j', 0), // vkj = Kujarge
+ TRUETYPE_TAG('v', 'k', 'k', 0), // vkk = Kaur
+ TRUETYPE_TAG('v', 'k', 'l', 0), // vkl = Kulisusu
+ TRUETYPE_TAG('v', 'k', 'm', 0), // vkm = Kamakan
+ TRUETYPE_TAG('v', 'k', 'o', 0), // vko = Kodeoha
+ TRUETYPE_TAG('v', 'k', 'p', 0), // vkp = Korlai Creole Portuguese
+ TRUETYPE_TAG('v', 'k', 't', 0), // vkt = Tenggarong Kutai Malay
+ TRUETYPE_TAG('v', 'k', 'u', 0), // vku = Kurrama
+ TRUETYPE_TAG('v', 'l', 'p', 0), // vlp = Valpei
+ TRUETYPE_TAG('v', 'l', 's', 0), // vls = Vlaams
+ TRUETYPE_TAG('v', 'm', 'a', 0), // vma = Martuyhunira
+ TRUETYPE_TAG('v', 'm', 'b', 0), // vmb = Mbabaram
+ TRUETYPE_TAG('v', 'm', 'c', 0), // vmc = Juxtlahuaca Mixtec
+ TRUETYPE_TAG('v', 'm', 'd', 0), // vmd = Mudu Koraga
+ TRUETYPE_TAG('v', 'm', 'e', 0), // vme = East Masela
+ TRUETYPE_TAG('v', 'm', 'f', 0), // vmf = Mainfränkisch
+ TRUETYPE_TAG('v', 'm', 'g', 0), // vmg = Minigir
+ TRUETYPE_TAG('v', 'm', 'h', 0), // vmh = Maraghei
+ TRUETYPE_TAG('v', 'm', 'i', 0), // vmi = Miwa
+ TRUETYPE_TAG('v', 'm', 'j', 0), // vmj = Ixtayutla Mixtec
+ TRUETYPE_TAG('v', 'm', 'k', 0), // vmk = Makhuwa-Shirima
+ TRUETYPE_TAG('v', 'm', 'l', 0), // vml = Malgana
+ TRUETYPE_TAG('v', 'm', 'm', 0), // vmm = Mitlatongo Mixtec
+ TRUETYPE_TAG('v', 'm', 'p', 0), // vmp = Soyaltepec Mazatec
+ TRUETYPE_TAG('v', 'm', 'q', 0), // vmq = Soyaltepec Mixtec
+ TRUETYPE_TAG('v', 'm', 'r', 0), // vmr = Marenje
+ TRUETYPE_TAG('v', 'm', 's', 0), // vms = Moksela
+ TRUETYPE_TAG('v', 'm', 'u', 0), // vmu = Muluridyi
+ TRUETYPE_TAG('v', 'm', 'v', 0), // vmv = Valley Maidu
+ TRUETYPE_TAG('v', 'm', 'w', 0), // vmw = Makhuwa
+ TRUETYPE_TAG('v', 'm', 'x', 0), // vmx = Tamazola Mixtec
+ TRUETYPE_TAG('v', 'm', 'y', 0), // vmy = Ayautla Mazatec
+ TRUETYPE_TAG('v', 'm', 'z', 0), // vmz = Mazatlán Mazatec
+ TRUETYPE_TAG('v', 'n', 'k', 0), // vnk = Vano
+ TRUETYPE_TAG('v', 'n', 'm', 0), // vnm = Vinmavis
+ TRUETYPE_TAG('v', 'n', 'p', 0), // vnp = Vunapu
+ TRUETYPE_TAG('v', 'o', 'r', 0), // vor = Voro
+ TRUETYPE_TAG('v', 'o', 't', 0), // vot = Votic
+ TRUETYPE_TAG('v', 'r', 'a', 0), // vra = Vera'a
+ TRUETYPE_TAG('v', 'r', 'o', 0), // vro = Võro
+ TRUETYPE_TAG('v', 'r', 's', 0), // vrs = Varisi
+ TRUETYPE_TAG('v', 'r', 't', 0), // vrt = Burmbar
+ TRUETYPE_TAG('v', 's', 'i', 0), // vsi = Moldova Sign Language
+ TRUETYPE_TAG('v', 's', 'l', 0), // vsl = Venezuelan Sign Language
+ TRUETYPE_TAG('v', 's', 'v', 0), // vsv = Valencian Sign Language
+ TRUETYPE_TAG('v', 't', 'o', 0), // vto = Vitou
+ TRUETYPE_TAG('v', 'u', 'm', 0), // vum = Vumbu
+ TRUETYPE_TAG('v', 'u', 'n', 0), // vun = Vunjo
+ TRUETYPE_TAG('v', 'u', 't', 0), // vut = Vute
+ TRUETYPE_TAG('v', 'w', 'a', 0), // vwa = Awa (China)
+ TRUETYPE_TAG('w', 'a', 'a', 0), // waa = Walla Walla
+ TRUETYPE_TAG('w', 'a', 'b', 0), // wab = Wab
+ TRUETYPE_TAG('w', 'a', 'c', 0), // wac = Wasco-Wishram
+ TRUETYPE_TAG('w', 'a', 'd', 0), // wad = Wandamen
+ TRUETYPE_TAG('w', 'a', 'e', 0), // wae = Walser
+ TRUETYPE_TAG('w', 'a', 'f', 0), // waf = Wakoná
+ TRUETYPE_TAG('w', 'a', 'g', 0), // wag = Wa'ema
+ TRUETYPE_TAG('w', 'a', 'h', 0), // wah = Watubela
+ TRUETYPE_TAG('w', 'a', 'i', 0), // wai = Wares
+ TRUETYPE_TAG('w', 'a', 'j', 0), // waj = Waffa
+ TRUETYPE_TAG('w', 'a', 'k', 0), // wak = Wakashan languages
+ TRUETYPE_TAG('w', 'a', 'l', 0), // wal = Wolaytta
+ TRUETYPE_TAG('w', 'a', 'm', 0), // wam = Wampanoag
+ TRUETYPE_TAG('w', 'a', 'n', 0), // wan = Wan
+ TRUETYPE_TAG('w', 'a', 'o', 0), // wao = Wappo
+ TRUETYPE_TAG('w', 'a', 'p', 0), // wap = Wapishana
+ TRUETYPE_TAG('w', 'a', 'q', 0), // waq = Wageman
+ TRUETYPE_TAG('w', 'a', 'r', 0), // war = Waray (Philippines)
+ TRUETYPE_TAG('w', 'a', 's', 0), // was = Washo
+ TRUETYPE_TAG('w', 'a', 't', 0), // wat = Kaninuwa
+ TRUETYPE_TAG('w', 'a', 'u', 0), // wau = Waurá
+ TRUETYPE_TAG('w', 'a', 'v', 0), // wav = Waka
+ TRUETYPE_TAG('w', 'a', 'w', 0), // waw = Waiwai
+ TRUETYPE_TAG('w', 'a', 'x', 0), // wax = Watam
+ TRUETYPE_TAG('w', 'a', 'y', 0), // way = Wayana
+ TRUETYPE_TAG('w', 'a', 'z', 0), // waz = Wampur
+ TRUETYPE_TAG('w', 'b', 'a', 0), // wba = Warao
+ TRUETYPE_TAG('w', 'b', 'b', 0), // wbb = Wabo
+ TRUETYPE_TAG('w', 'b', 'e', 0), // wbe = Waritai
+ TRUETYPE_TAG('w', 'b', 'f', 0), // wbf = Wara
+ TRUETYPE_TAG('w', 'b', 'h', 0), // wbh = Wanda
+ TRUETYPE_TAG('w', 'b', 'i', 0), // wbi = Vwanji
+ TRUETYPE_TAG('w', 'b', 'j', 0), // wbj = Alagwa
+ TRUETYPE_TAG('w', 'b', 'k', 0), // wbk = Waigali
+ TRUETYPE_TAG('w', 'b', 'l', 0), // wbl = Wakhi
+ TRUETYPE_TAG('w', 'b', 'm', 0), // wbm = Wa
+ TRUETYPE_TAG('w', 'b', 'p', 0), // wbp = Warlpiri
+ TRUETYPE_TAG('w', 'b', 'q', 0), // wbq = Waddar
+ TRUETYPE_TAG('w', 'b', 'r', 0), // wbr = Wagdi
+ TRUETYPE_TAG('w', 'b', 't', 0), // wbt = Wanman
+ TRUETYPE_TAG('w', 'b', 'v', 0), // wbv = Wajarri
+ TRUETYPE_TAG('w', 'b', 'w', 0), // wbw = Woi
+ TRUETYPE_TAG('w', 'c', 'a', 0), // wca = Yanomámi
+ TRUETYPE_TAG('w', 'c', 'i', 0), // wci = Waci Gbe
+ TRUETYPE_TAG('w', 'd', 'd', 0), // wdd = Wandji
+ TRUETYPE_TAG('w', 'd', 'g', 0), // wdg = Wadaginam
+ TRUETYPE_TAG('w', 'd', 'j', 0), // wdj = Wadjiginy
+ TRUETYPE_TAG('w', 'd', 'u', 0), // wdu = Wadjigu
+ TRUETYPE_TAG('w', 'e', 'a', 0), // wea = Wewaw
+ TRUETYPE_TAG('w', 'e', 'c', 0), // wec = Wè Western
+ TRUETYPE_TAG('w', 'e', 'd', 0), // wed = Wedau
+ TRUETYPE_TAG('w', 'e', 'h', 0), // weh = Weh
+ TRUETYPE_TAG('w', 'e', 'i', 0), // wei = Kiunum
+ TRUETYPE_TAG('w', 'e', 'm', 0), // wem = Weme Gbe
+ TRUETYPE_TAG('w', 'e', 'n', 0), // wen = Sorbian languages
+ TRUETYPE_TAG('w', 'e', 'o', 0), // weo = North Wemale
+ TRUETYPE_TAG('w', 'e', 'p', 0), // wep = Westphalien
+ TRUETYPE_TAG('w', 'e', 'r', 0), // wer = Weri
+ TRUETYPE_TAG('w', 'e', 's', 0), // wes = Cameroon Pidgin
+ TRUETYPE_TAG('w', 'e', 't', 0), // wet = Perai
+ TRUETYPE_TAG('w', 'e', 'u', 0), // weu = Welaung
+ TRUETYPE_TAG('w', 'e', 'w', 0), // wew = Wejewa
+ TRUETYPE_TAG('w', 'f', 'g', 0), // wfg = Yafi
+ TRUETYPE_TAG('w', 'g', 'a', 0), // wga = Wagaya
+ TRUETYPE_TAG('w', 'g', 'b', 0), // wgb = Wagawaga
+ TRUETYPE_TAG('w', 'g', 'g', 0), // wgg = Wangganguru
+ TRUETYPE_TAG('w', 'g', 'i', 0), // wgi = Wahgi
+ TRUETYPE_TAG('w', 'g', 'o', 0), // wgo = Waigeo
+ TRUETYPE_TAG('w', 'g', 'w', 0), // wgw = Wagawaga
+ TRUETYPE_TAG('w', 'g', 'y', 0), // wgy = Warrgamay
+ TRUETYPE_TAG('w', 'h', 'a', 0), // wha = Manusela
+ TRUETYPE_TAG('w', 'h', 'g', 0), // whg = North Wahgi
+ TRUETYPE_TAG('w', 'h', 'k', 0), // whk = Wahau Kenyah
+ TRUETYPE_TAG('w', 'h', 'u', 0), // whu = Wahau Kayan
+ TRUETYPE_TAG('w', 'i', 'b', 0), // wib = Southern Toussian
+ TRUETYPE_TAG('w', 'i', 'c', 0), // wic = Wichita
+ TRUETYPE_TAG('w', 'i', 'e', 0), // wie = Wik-Epa
+ TRUETYPE_TAG('w', 'i', 'f', 0), // wif = Wik-Keyangan
+ TRUETYPE_TAG('w', 'i', 'g', 0), // wig = Wik-Ngathana
+ TRUETYPE_TAG('w', 'i', 'h', 0), // wih = Wik-Me'anha
+ TRUETYPE_TAG('w', 'i', 'i', 0), // wii = Minidien
+ TRUETYPE_TAG('w', 'i', 'j', 0), // wij = Wik-Iiyanh
+ TRUETYPE_TAG('w', 'i', 'k', 0), // wik = Wikalkan
+ TRUETYPE_TAG('w', 'i', 'l', 0), // wil = Wilawila
+ TRUETYPE_TAG('w', 'i', 'm', 0), // wim = Wik-Mungkan
+ TRUETYPE_TAG('w', 'i', 'n', 0), // win = Ho-Chunk
+ TRUETYPE_TAG('w', 'i', 'r', 0), // wir = Wiraféd
+ TRUETYPE_TAG('w', 'i', 't', 0), // wit = Wintu
+ TRUETYPE_TAG('w', 'i', 'u', 0), // wiu = Wiru
+ TRUETYPE_TAG('w', 'i', 'v', 0), // wiv = Muduapa
+ TRUETYPE_TAG('w', 'i', 'w', 0), // wiw = Wirangu
+ TRUETYPE_TAG('w', 'i', 'y', 0), // wiy = Wiyot
+ TRUETYPE_TAG('w', 'j', 'a', 0), // wja = Waja
+ TRUETYPE_TAG('w', 'j', 'i', 0), // wji = Warji
+ TRUETYPE_TAG('w', 'k', 'a', 0), // wka = Kw'adza
+ TRUETYPE_TAG('w', 'k', 'b', 0), // wkb = Kumbaran
+ TRUETYPE_TAG('w', 'k', 'd', 0), // wkd = Wakde
+ TRUETYPE_TAG('w', 'k', 'l', 0), // wkl = Kalanadi
+ TRUETYPE_TAG('w', 'k', 'u', 0), // wku = Kunduvadi
+ TRUETYPE_TAG('w', 'k', 'w', 0), // wkw = Wakawaka
+ TRUETYPE_TAG('w', 'l', 'a', 0), // wla = Walio
+ TRUETYPE_TAG('w', 'l', 'c', 0), // wlc = Mwali Comorian
+ TRUETYPE_TAG('w', 'l', 'e', 0), // wle = Wolane
+ TRUETYPE_TAG('w', 'l', 'g', 0), // wlg = Kunbarlang
+ TRUETYPE_TAG('w', 'l', 'i', 0), // wli = Waioli
+ TRUETYPE_TAG('w', 'l', 'k', 0), // wlk = Wailaki
+ TRUETYPE_TAG('w', 'l', 'l', 0), // wll = Wali (Sudan)
+ TRUETYPE_TAG('w', 'l', 'm', 0), // wlm = Middle Welsh
+ TRUETYPE_TAG('w', 'l', 'o', 0), // wlo = Wolio
+ TRUETYPE_TAG('w', 'l', 'r', 0), // wlr = Wailapa
+ TRUETYPE_TAG('w', 'l', 's', 0), // wls = Wallisian
+ TRUETYPE_TAG('w', 'l', 'u', 0), // wlu = Wuliwuli
+ TRUETYPE_TAG('w', 'l', 'v', 0), // wlv = Wichí Lhamtés Vejoz
+ TRUETYPE_TAG('w', 'l', 'w', 0), // wlw = Walak
+ TRUETYPE_TAG('w', 'l', 'x', 0), // wlx = Wali (Ghana)
+ TRUETYPE_TAG('w', 'l', 'y', 0), // wly = Waling
+ TRUETYPE_TAG('w', 'm', 'a', 0), // wma = Mawa (Nigeria)
+ TRUETYPE_TAG('w', 'm', 'b', 0), // wmb = Wambaya
+ TRUETYPE_TAG('w', 'm', 'c', 0), // wmc = Wamas
+ TRUETYPE_TAG('w', 'm', 'd', 0), // wmd = Mamaindé
+ TRUETYPE_TAG('w', 'm', 'e', 0), // wme = Wambule
+ TRUETYPE_TAG('w', 'm', 'h', 0), // wmh = Waima'a
+ TRUETYPE_TAG('w', 'm', 'i', 0), // wmi = Wamin
+ TRUETYPE_TAG('w', 'm', 'm', 0), // wmm = Maiwa (Indonesia)
+ TRUETYPE_TAG('w', 'm', 'n', 0), // wmn = Waamwang
+ TRUETYPE_TAG('w', 'm', 'o', 0), // wmo = Wom (Papua New Guinea)
+ TRUETYPE_TAG('w', 'm', 's', 0), // wms = Wambon
+ TRUETYPE_TAG('w', 'm', 't', 0), // wmt = Walmajarri
+ TRUETYPE_TAG('w', 'm', 'w', 0), // wmw = Mwani
+ TRUETYPE_TAG('w', 'm', 'x', 0), // wmx = Womo
+ TRUETYPE_TAG('w', 'n', 'b', 0), // wnb = Wanambre
+ TRUETYPE_TAG('w', 'n', 'c', 0), // wnc = Wantoat
+ TRUETYPE_TAG('w', 'n', 'd', 0), // wnd = Wandarang
+ TRUETYPE_TAG('w', 'n', 'e', 0), // wne = Waneci
+ TRUETYPE_TAG('w', 'n', 'g', 0), // wng = Wanggom
+ TRUETYPE_TAG('w', 'n', 'i', 0), // wni = Ndzwani Comorian
+ TRUETYPE_TAG('w', 'n', 'k', 0), // wnk = Wanukaka
+ TRUETYPE_TAG('w', 'n', 'm', 0), // wnm = Wanggamala
+ TRUETYPE_TAG('w', 'n', 'o', 0), // wno = Wano
+ TRUETYPE_TAG('w', 'n', 'p', 0), // wnp = Wanap
+ TRUETYPE_TAG('w', 'n', 'u', 0), // wnu = Usan
+ TRUETYPE_TAG('w', 'o', 'a', 0), // woa = Tyaraity
+ TRUETYPE_TAG('w', 'o', 'b', 0), // wob = Wè Northern
+ TRUETYPE_TAG('w', 'o', 'c', 0), // woc = Wogeo
+ TRUETYPE_TAG('w', 'o', 'd', 0), // wod = Wolani
+ TRUETYPE_TAG('w', 'o', 'e', 0), // woe = Woleaian
+ TRUETYPE_TAG('w', 'o', 'f', 0), // wof = Gambian Wolof
+ TRUETYPE_TAG('w', 'o', 'g', 0), // wog = Wogamusin
+ TRUETYPE_TAG('w', 'o', 'i', 0), // woi = Kamang
+ TRUETYPE_TAG('w', 'o', 'k', 0), // wok = Longto
+ TRUETYPE_TAG('w', 'o', 'm', 0), // wom = Wom (Nigeria)
+ TRUETYPE_TAG('w', 'o', 'n', 0), // won = Wongo
+ TRUETYPE_TAG('w', 'o', 'o', 0), // woo = Manombai
+ TRUETYPE_TAG('w', 'o', 'r', 0), // wor = Woria
+ TRUETYPE_TAG('w', 'o', 's', 0), // wos = Hanga Hundi
+ TRUETYPE_TAG('w', 'o', 'w', 0), // wow = Wawonii
+ TRUETYPE_TAG('w', 'o', 'y', 0), // woy = Weyto
+ TRUETYPE_TAG('w', 'p', 'c', 0), // wpc = Maco
+ TRUETYPE_TAG('w', 'r', 'a', 0), // wra = Warapu
+ TRUETYPE_TAG('w', 'r', 'b', 0), // wrb = Warluwara
+ TRUETYPE_TAG('w', 'r', 'd', 0), // wrd = Warduji
+ TRUETYPE_TAG('w', 'r', 'g', 0), // wrg = Warungu
+ TRUETYPE_TAG('w', 'r', 'h', 0), // wrh = Wiradhuri
+ TRUETYPE_TAG('w', 'r', 'i', 0), // wri = Wariyangga
+ TRUETYPE_TAG('w', 'r', 'l', 0), // wrl = Warlmanpa
+ TRUETYPE_TAG('w', 'r', 'm', 0), // wrm = Warumungu
+ TRUETYPE_TAG('w', 'r', 'n', 0), // wrn = Warnang
+ TRUETYPE_TAG('w', 'r', 'p', 0), // wrp = Waropen
+ TRUETYPE_TAG('w', 'r', 'r', 0), // wrr = Wardaman
+ TRUETYPE_TAG('w', 'r', 's', 0), // wrs = Waris
+ TRUETYPE_TAG('w', 'r', 'u', 0), // wru = Waru
+ TRUETYPE_TAG('w', 'r', 'v', 0), // wrv = Waruna
+ TRUETYPE_TAG('w', 'r', 'w', 0), // wrw = Gugu Warra
+ TRUETYPE_TAG('w', 'r', 'x', 0), // wrx = Wae Rana
+ TRUETYPE_TAG('w', 'r', 'y', 0), // wry = Merwari
+ TRUETYPE_TAG('w', 'r', 'z', 0), // wrz = Waray (Australia)
+ TRUETYPE_TAG('w', 's', 'a', 0), // wsa = Warembori
+ TRUETYPE_TAG('w', 's', 'i', 0), // wsi = Wusi
+ TRUETYPE_TAG('w', 's', 'k', 0), // wsk = Waskia
+ TRUETYPE_TAG('w', 's', 'r', 0), // wsr = Owenia
+ TRUETYPE_TAG('w', 's', 's', 0), // wss = Wasa
+ TRUETYPE_TAG('w', 's', 'u', 0), // wsu = Wasu
+ TRUETYPE_TAG('w', 's', 'v', 0), // wsv = Wotapuri-Katarqalai
+ TRUETYPE_TAG('w', 't', 'f', 0), // wtf = Watiwa
+ TRUETYPE_TAG('w', 't', 'i', 0), // wti = Berta
+ TRUETYPE_TAG('w', 't', 'k', 0), // wtk = Watakataui
+ TRUETYPE_TAG('w', 't', 'm', 0), // wtm = Mewati
+ TRUETYPE_TAG('w', 't', 'w', 0), // wtw = Wotu
+ TRUETYPE_TAG('w', 'u', 'a', 0), // wua = Wikngenchera
+ TRUETYPE_TAG('w', 'u', 'b', 0), // wub = Wunambal
+ TRUETYPE_TAG('w', 'u', 'd', 0), // wud = Wudu
+ TRUETYPE_TAG('w', 'u', 'h', 0), // wuh = Wutunhua
+ TRUETYPE_TAG('w', 'u', 'l', 0), // wul = Silimo
+ TRUETYPE_TAG('w', 'u', 'm', 0), // wum = Wumbvu
+ TRUETYPE_TAG('w', 'u', 'n', 0), // wun = Bungu
+ TRUETYPE_TAG('w', 'u', 'r', 0), // wur = Wurrugu
+ TRUETYPE_TAG('w', 'u', 't', 0), // wut = Wutung
+ TRUETYPE_TAG('w', 'u', 'u', 0), // wuu = Wu Chinese
+ TRUETYPE_TAG('w', 'u', 'v', 0), // wuv = Wuvulu-Aua
+ TRUETYPE_TAG('w', 'u', 'x', 0), // wux = Wulna
+ TRUETYPE_TAG('w', 'u', 'y', 0), // wuy = Wauyai
+ TRUETYPE_TAG('w', 'w', 'a', 0), // wwa = Waama
+ TRUETYPE_TAG('w', 'w', 'o', 0), // wwo = Wetamut
+ TRUETYPE_TAG('w', 'w', 'r', 0), // wwr = Warrwa
+ TRUETYPE_TAG('w', 'w', 'w', 0), // www = Wawa
+ TRUETYPE_TAG('w', 'x', 'a', 0), // wxa = Waxianghua
+ TRUETYPE_TAG('w', 'y', 'a', 0), // wya = Wyandot
+ TRUETYPE_TAG('w', 'y', 'b', 0), // wyb = Wangaaybuwan-Ngiyambaa
+ TRUETYPE_TAG('w', 'y', 'm', 0), // wym = Wymysorys
+ TRUETYPE_TAG('w', 'y', 'r', 0), // wyr = Wayoró
+ TRUETYPE_TAG('w', 'y', 'y', 0), // wyy = Western Fijian
+ TRUETYPE_TAG('x', 'a', 'a', 0), // xaa = Andalusian Arabic
+ TRUETYPE_TAG('x', 'a', 'b', 0), // xab = Sambe
+ TRUETYPE_TAG('x', 'a', 'c', 0), // xac = Kachari
+ TRUETYPE_TAG('x', 'a', 'd', 0), // xad = Adai
+ TRUETYPE_TAG('x', 'a', 'e', 0), // xae = Aequian
+ TRUETYPE_TAG('x', 'a', 'g', 0), // xag = Aghwan
+ TRUETYPE_TAG('x', 'a', 'i', 0), // xai = Kaimbé
+ TRUETYPE_TAG('x', 'a', 'l', 0), // xal = Kalmyk
+ TRUETYPE_TAG('x', 'a', 'm', 0), // xam = /Xam
+ TRUETYPE_TAG('x', 'a', 'n', 0), // xan = Xamtanga
+ TRUETYPE_TAG('x', 'a', 'o', 0), // xao = Khao
+ TRUETYPE_TAG('x', 'a', 'p', 0), // xap = Apalachee
+ TRUETYPE_TAG('x', 'a', 'q', 0), // xaq = Aquitanian
+ TRUETYPE_TAG('x', 'a', 'r', 0), // xar = Karami
+ TRUETYPE_TAG('x', 'a', 's', 0), // xas = Kamas
+ TRUETYPE_TAG('x', 'a', 't', 0), // xat = Katawixi
+ TRUETYPE_TAG('x', 'a', 'u', 0), // xau = Kauwera
+ TRUETYPE_TAG('x', 'a', 'v', 0), // xav = Xavánte
+ TRUETYPE_TAG('x', 'a', 'w', 0), // xaw = Kawaiisu
+ TRUETYPE_TAG('x', 'a', 'y', 0), // xay = Kayan Mahakam
+ TRUETYPE_TAG('x', 'b', 'a', 0), // xba = Kamba (Brazil)
+ TRUETYPE_TAG('x', 'b', 'b', 0), // xbb = Lower Burdekin
+ TRUETYPE_TAG('x', 'b', 'c', 0), // xbc = Bactrian
+ TRUETYPE_TAG('x', 'b', 'i', 0), // xbi = Kombio
+ TRUETYPE_TAG('x', 'b', 'm', 0), // xbm = Middle Breton
+ TRUETYPE_TAG('x', 'b', 'n', 0), // xbn = Kenaboi
+ TRUETYPE_TAG('x', 'b', 'o', 0), // xbo = Bolgarian
+ TRUETYPE_TAG('x', 'b', 'r', 0), // xbr = Kambera
+ TRUETYPE_TAG('x', 'b', 'w', 0), // xbw = Kambiwá
+ TRUETYPE_TAG('x', 'b', 'x', 0), // xbx = Kabixí
+ TRUETYPE_TAG('x', 'c', 'b', 0), // xcb = Cumbric
+ TRUETYPE_TAG('x', 'c', 'c', 0), // xcc = Camunic
+ TRUETYPE_TAG('x', 'c', 'e', 0), // xce = Celtiberian
+ TRUETYPE_TAG('x', 'c', 'g', 0), // xcg = Cisalpine Gaulish
+ TRUETYPE_TAG('x', 'c', 'h', 0), // xch = Chemakum
+ TRUETYPE_TAG('x', 'c', 'l', 0), // xcl = Classical Armenian
+ TRUETYPE_TAG('x', 'c', 'm', 0), // xcm = Comecrudo
+ TRUETYPE_TAG('x', 'c', 'n', 0), // xcn = Cotoname
+ TRUETYPE_TAG('x', 'c', 'o', 0), // xco = Chorasmian
+ TRUETYPE_TAG('x', 'c', 'r', 0), // xcr = Carian
+ TRUETYPE_TAG('x', 'c', 't', 0), // xct = Classical Tibetan
+ TRUETYPE_TAG('x', 'c', 'u', 0), // xcu = Curonian
+ TRUETYPE_TAG('x', 'c', 'v', 0), // xcv = Chuvantsy
+ TRUETYPE_TAG('x', 'c', 'w', 0), // xcw = Coahuilteco
+ TRUETYPE_TAG('x', 'c', 'y', 0), // xcy = Cayuse
+ TRUETYPE_TAG('x', 'd', 'c', 0), // xdc = Dacian
+ TRUETYPE_TAG('x', 'd', 'm', 0), // xdm = Edomite
+ TRUETYPE_TAG('x', 'd', 'y', 0), // xdy = Malayic Dayak
+ TRUETYPE_TAG('x', 'e', 'b', 0), // xeb = Eblan
+ TRUETYPE_TAG('x', 'e', 'd', 0), // xed = Hdi
+ TRUETYPE_TAG('x', 'e', 'g', 0), // xeg = //Xegwi
+ TRUETYPE_TAG('x', 'e', 'l', 0), // xel = Kelo
+ TRUETYPE_TAG('x', 'e', 'm', 0), // xem = Kembayan
+ TRUETYPE_TAG('x', 'e', 'p', 0), // xep = Epi-Olmec
+ TRUETYPE_TAG('x', 'e', 'r', 0), // xer = Xerénte
+ TRUETYPE_TAG('x', 'e', 's', 0), // xes = Kesawai
+ TRUETYPE_TAG('x', 'e', 't', 0), // xet = Xetá
+ TRUETYPE_TAG('x', 'e', 'u', 0), // xeu = Keoru-Ahia
+ TRUETYPE_TAG('x', 'f', 'a', 0), // xfa = Faliscan
+ TRUETYPE_TAG('x', 'g', 'a', 0), // xga = Galatian
+ TRUETYPE_TAG('x', 'g', 'f', 0), // xgf = Gabrielino-Fernandeño
+ TRUETYPE_TAG('x', 'g', 'l', 0), // xgl = Galindan
+ TRUETYPE_TAG('x', 'g', 'n', 0), // xgn = Mongolian languages
+ TRUETYPE_TAG('x', 'g', 'r', 0), // xgr = Garza
+ TRUETYPE_TAG('x', 'h', 'a', 0), // xha = Harami
+ TRUETYPE_TAG('x', 'h', 'c', 0), // xhc = Hunnic
+ TRUETYPE_TAG('x', 'h', 'd', 0), // xhd = Hadrami
+ TRUETYPE_TAG('x', 'h', 'e', 0), // xhe = Khetrani
+ TRUETYPE_TAG('x', 'h', 'r', 0), // xhr = Hernican
+ TRUETYPE_TAG('x', 'h', 't', 0), // xht = Hattic
+ TRUETYPE_TAG('x', 'h', 'u', 0), // xhu = Hurrian
+ TRUETYPE_TAG('x', 'h', 'v', 0), // xhv = Khua
+ TRUETYPE_TAG('x', 'i', 'a', 0), // xia = Xiandao
+ TRUETYPE_TAG('x', 'i', 'b', 0), // xib = Iberian
+ TRUETYPE_TAG('x', 'i', 'i', 0), // xii = Xiri
+ TRUETYPE_TAG('x', 'i', 'l', 0), // xil = Illyrian
+ TRUETYPE_TAG('x', 'i', 'n', 0), // xin = Xinca
+ TRUETYPE_TAG('x', 'i', 'p', 0), // xip = Xipináwa
+ TRUETYPE_TAG('x', 'i', 'r', 0), // xir = Xiriâna
+ TRUETYPE_TAG('x', 'i', 'v', 0), // xiv = Indus Valley Language
+ TRUETYPE_TAG('x', 'i', 'y', 0), // xiy = Xipaya
+ TRUETYPE_TAG('x', 'k', 'a', 0), // xka = Kalkoti
+ TRUETYPE_TAG('x', 'k', 'b', 0), // xkb = Northern Nago
+ TRUETYPE_TAG('x', 'k', 'c', 0), // xkc = Kho'ini
+ TRUETYPE_TAG('x', 'k', 'd', 0), // xkd = Mendalam Kayan
+ TRUETYPE_TAG('x', 'k', 'e', 0), // xke = Kereho
+ TRUETYPE_TAG('x', 'k', 'f', 0), // xkf = Khengkha
+ TRUETYPE_TAG('x', 'k', 'g', 0), // xkg = Kagoro
+ TRUETYPE_TAG('x', 'k', 'h', 0), // xkh = Karahawyana
+ TRUETYPE_TAG('x', 'k', 'i', 0), // xki = Kenyan Sign Language
+ TRUETYPE_TAG('x', 'k', 'j', 0), // xkj = Kajali
+ TRUETYPE_TAG('x', 'k', 'k', 0), // xkk = Kaco'
+ TRUETYPE_TAG('x', 'k', 'l', 0), // xkl = Mainstream Kenyah
+ TRUETYPE_TAG('x', 'k', 'n', 0), // xkn = Kayan River Kayan
+ TRUETYPE_TAG('x', 'k', 'o', 0), // xko = Kiorr
+ TRUETYPE_TAG('x', 'k', 'p', 0), // xkp = Kabatei
+ TRUETYPE_TAG('x', 'k', 'q', 0), // xkq = Koroni
+ TRUETYPE_TAG('x', 'k', 'r', 0), // xkr = Xakriabá
+ TRUETYPE_TAG('x', 'k', 's', 0), // xks = Kumbewaha
+ TRUETYPE_TAG('x', 'k', 't', 0), // xkt = Kantosi
+ TRUETYPE_TAG('x', 'k', 'u', 0), // xku = Kaamba
+ TRUETYPE_TAG('x', 'k', 'v', 0), // xkv = Kgalagadi
+ TRUETYPE_TAG('x', 'k', 'w', 0), // xkw = Kembra
+ TRUETYPE_TAG('x', 'k', 'x', 0), // xkx = Karore
+ TRUETYPE_TAG('x', 'k', 'y', 0), // xky = Uma' Lasan
+ TRUETYPE_TAG('x', 'k', 'z', 0), // xkz = Kurtokha
+ TRUETYPE_TAG('x', 'l', 'a', 0), // xla = Kamula
+ TRUETYPE_TAG('x', 'l', 'b', 0), // xlb = Loup B
+ TRUETYPE_TAG('x', 'l', 'c', 0), // xlc = Lycian
+ TRUETYPE_TAG('x', 'l', 'd', 0), // xld = Lydian
+ TRUETYPE_TAG('x', 'l', 'e', 0), // xle = Lemnian
+ TRUETYPE_TAG('x', 'l', 'g', 0), // xlg = Ligurian (Ancient)
+ TRUETYPE_TAG('x', 'l', 'i', 0), // xli = Liburnian
+ TRUETYPE_TAG('x', 'l', 'n', 0), // xln = Alanic
+ TRUETYPE_TAG('x', 'l', 'o', 0), // xlo = Loup A
+ TRUETYPE_TAG('x', 'l', 'p', 0), // xlp = Lepontic
+ TRUETYPE_TAG('x', 'l', 's', 0), // xls = Lusitanian
+ TRUETYPE_TAG('x', 'l', 'u', 0), // xlu = Cuneiform Luwian
+ TRUETYPE_TAG('x', 'l', 'y', 0), // xly = Elymian
+ TRUETYPE_TAG('x', 'm', 'a', 0), // xma = Mushungulu
+ TRUETYPE_TAG('x', 'm', 'b', 0), // xmb = Mbonga
+ TRUETYPE_TAG('x', 'm', 'c', 0), // xmc = Makhuwa-Marrevone
+ TRUETYPE_TAG('x', 'm', 'd', 0), // xmd = Mbudum
+ TRUETYPE_TAG('x', 'm', 'e', 0), // xme = Median
+ TRUETYPE_TAG('x', 'm', 'f', 0), // xmf = Mingrelian
+ TRUETYPE_TAG('x', 'm', 'g', 0), // xmg = Mengaka
+ TRUETYPE_TAG('x', 'm', 'h', 0), // xmh = Kuku-Muminh
+ TRUETYPE_TAG('x', 'm', 'j', 0), // xmj = Majera
+ TRUETYPE_TAG('x', 'm', 'k', 0), // xmk = Ancient Macedonian
+ TRUETYPE_TAG('x', 'm', 'l', 0), // xml = Malaysian Sign Language
+ TRUETYPE_TAG('x', 'm', 'm', 0), // xmm = Manado Malay
+ TRUETYPE_TAG('x', 'm', 'n', 0), // xmn = Manichaean Middle Persian
+ TRUETYPE_TAG('x', 'm', 'o', 0), // xmo = Morerebi
+ TRUETYPE_TAG('x', 'm', 'p', 0), // xmp = Kuku-Mu'inh
+ TRUETYPE_TAG('x', 'm', 'q', 0), // xmq = Kuku-Mangk
+ TRUETYPE_TAG('x', 'm', 'r', 0), // xmr = Meroitic
+ TRUETYPE_TAG('x', 'm', 's', 0), // xms = Moroccan Sign Language
+ TRUETYPE_TAG('x', 'm', 't', 0), // xmt = Matbat
+ TRUETYPE_TAG('x', 'm', 'u', 0), // xmu = Kamu
+ TRUETYPE_TAG('x', 'm', 'v', 0), // xmv = Antankarana Malagasy
+ TRUETYPE_TAG('x', 'm', 'w', 0), // xmw = Tsimihety Malagasy
+ TRUETYPE_TAG('x', 'm', 'x', 0), // xmx = Maden
+ TRUETYPE_TAG('x', 'm', 'y', 0), // xmy = Mayaguduna
+ TRUETYPE_TAG('x', 'm', 'z', 0), // xmz = Mori Bawah
+ TRUETYPE_TAG('x', 'n', 'a', 0), // xna = Ancient North Arabian
+ TRUETYPE_TAG('x', 'n', 'b', 0), // xnb = Kanakanabu
+ TRUETYPE_TAG('x', 'n', 'd', 0), // xnd = Na-Dene languages
+ TRUETYPE_TAG('x', 'n', 'g', 0), // xng = Middle Mongolian
+ TRUETYPE_TAG('x', 'n', 'h', 0), // xnh = Kuanhua
+ TRUETYPE_TAG('x', 'n', 'n', 0), // xnn = Northern Kankanay
+ TRUETYPE_TAG('x', 'n', 'o', 0), // xno = Anglo-Norman
+ TRUETYPE_TAG('x', 'n', 'r', 0), // xnr = Kangri
+ TRUETYPE_TAG('x', 'n', 's', 0), // xns = Kanashi
+ TRUETYPE_TAG('x', 'n', 't', 0), // xnt = Narragansett
+ TRUETYPE_TAG('x', 'o', 'c', 0), // xoc = O'chi'chi'
+ TRUETYPE_TAG('x', 'o', 'd', 0), // xod = Kokoda
+ TRUETYPE_TAG('x', 'o', 'g', 0), // xog = Soga
+ TRUETYPE_TAG('x', 'o', 'i', 0), // xoi = Kominimung
+ TRUETYPE_TAG('x', 'o', 'k', 0), // xok = Xokleng
+ TRUETYPE_TAG('x', 'o', 'm', 0), // xom = Komo (Sudan)
+ TRUETYPE_TAG('x', 'o', 'n', 0), // xon = Konkomba
+ TRUETYPE_TAG('x', 'o', 'o', 0), // xoo = Xukurú
+ TRUETYPE_TAG('x', 'o', 'p', 0), // xop = Kopar
+ TRUETYPE_TAG('x', 'o', 'r', 0), // xor = Korubo
+ TRUETYPE_TAG('x', 'o', 'w', 0), // xow = Kowaki
+ TRUETYPE_TAG('x', 'p', 'c', 0), // xpc = Pecheneg
+ TRUETYPE_TAG('x', 'p', 'e', 0), // xpe = Liberia Kpelle
+ TRUETYPE_TAG('x', 'p', 'g', 0), // xpg = Phrygian
+ TRUETYPE_TAG('x', 'p', 'i', 0), // xpi = Pictish
+ TRUETYPE_TAG('x', 'p', 'k', 0), // xpk = Kulina Pano
+ TRUETYPE_TAG('x', 'p', 'm', 0), // xpm = Pumpokol
+ TRUETYPE_TAG('x', 'p', 'n', 0), // xpn = Kapinawá
+ TRUETYPE_TAG('x', 'p', 'o', 0), // xpo = Pochutec
+ TRUETYPE_TAG('x', 'p', 'p', 0), // xpp = Puyo-Paekche
+ TRUETYPE_TAG('x', 'p', 'q', 0), // xpq = Mohegan-Pequot
+ TRUETYPE_TAG('x', 'p', 'r', 0), // xpr = Parthian
+ TRUETYPE_TAG('x', 'p', 's', 0), // xps = Pisidian
+ TRUETYPE_TAG('x', 'p', 'u', 0), // xpu = Punic
+ TRUETYPE_TAG('x', 'p', 'y', 0), // xpy = Puyo
+ TRUETYPE_TAG('x', 'q', 'a', 0), // xqa = Karakhanid
+ TRUETYPE_TAG('x', 'q', 't', 0), // xqt = Qatabanian
+ TRUETYPE_TAG('x', 'r', 'a', 0), // xra = Krahô
+ TRUETYPE_TAG('x', 'r', 'b', 0), // xrb = Eastern Karaboro
+ TRUETYPE_TAG('x', 'r', 'e', 0), // xre = Kreye
+ TRUETYPE_TAG('x', 'r', 'i', 0), // xri = Krikati-Timbira
+ TRUETYPE_TAG('x', 'r', 'm', 0), // xrm = Armazic
+ TRUETYPE_TAG('x', 'r', 'n', 0), // xrn = Arin
+ TRUETYPE_TAG('x', 'r', 'r', 0), // xrr = Raetic
+ TRUETYPE_TAG('x', 'r', 't', 0), // xrt = Aranama-Tamique
+ TRUETYPE_TAG('x', 'r', 'u', 0), // xru = Marriammu
+ TRUETYPE_TAG('x', 'r', 'w', 0), // xrw = Karawa
+ TRUETYPE_TAG('x', 's', 'a', 0), // xsa = Sabaean
+ TRUETYPE_TAG('x', 's', 'b', 0), // xsb = Tinà Sambal
+ TRUETYPE_TAG('x', 's', 'c', 0), // xsc = Scythian
+ TRUETYPE_TAG('x', 's', 'd', 0), // xsd = Sidetic
+ TRUETYPE_TAG('x', 's', 'e', 0), // xse = Sempan
+ TRUETYPE_TAG('x', 's', 'h', 0), // xsh = Shamang
+ TRUETYPE_TAG('x', 's', 'i', 0), // xsi = Sio
+ TRUETYPE_TAG('x', 's', 'j', 0), // xsj = Subi
+ TRUETYPE_TAG('x', 's', 'l', 0), // xsl = South Slavey
+ TRUETYPE_TAG('x', 's', 'm', 0), // xsm = Kasem
+ TRUETYPE_TAG('x', 's', 'n', 0), // xsn = Sanga (Nigeria)
+ TRUETYPE_TAG('x', 's', 'o', 0), // xso = Solano
+ TRUETYPE_TAG('x', 's', 'p', 0), // xsp = Silopi
+ TRUETYPE_TAG('x', 's', 'q', 0), // xsq = Makhuwa-Saka
+ TRUETYPE_TAG('x', 's', 'r', 0), // xsr = Sherpa
+ TRUETYPE_TAG('x', 's', 's', 0), // xss = Assan
+ TRUETYPE_TAG('x', 's', 'u', 0), // xsu = Sanumá
+ TRUETYPE_TAG('x', 's', 'v', 0), // xsv = Sudovian
+ TRUETYPE_TAG('x', 's', 'y', 0), // xsy = Saisiyat
+ TRUETYPE_TAG('x', 't', 'a', 0), // xta = Alcozauca Mixtec
+ TRUETYPE_TAG('x', 't', 'b', 0), // xtb = Chazumba Mixtec
+ TRUETYPE_TAG('x', 't', 'c', 0), // xtc = Katcha-Kadugli-Miri
+ TRUETYPE_TAG('x', 't', 'd', 0), // xtd = Diuxi-Tilantongo Mixtec
+ TRUETYPE_TAG('x', 't', 'e', 0), // xte = Ketengban
+ TRUETYPE_TAG('x', 't', 'g', 0), // xtg = Transalpine Gaulish
+ TRUETYPE_TAG('x', 't', 'i', 0), // xti = Sinicahua Mixtec
+ TRUETYPE_TAG('x', 't', 'j', 0), // xtj = San Juan Teita Mixtec
+ TRUETYPE_TAG('x', 't', 'l', 0), // xtl = Tijaltepec Mixtec
+ TRUETYPE_TAG('x', 't', 'm', 0), // xtm = Magdalena Peñasco Mixtec
+ TRUETYPE_TAG('x', 't', 'n', 0), // xtn = Northern Tlaxiaco Mixtec
+ TRUETYPE_TAG('x', 't', 'o', 0), // xto = Tokharian A
+ TRUETYPE_TAG('x', 't', 'p', 0), // xtp = San Miguel Piedras Mixtec
+ TRUETYPE_TAG('x', 't', 'q', 0), // xtq = Tumshuqese
+ TRUETYPE_TAG('x', 't', 'r', 0), // xtr = Early Tripuri
+ TRUETYPE_TAG('x', 't', 's', 0), // xts = Sindihui Mixtec
+ TRUETYPE_TAG('x', 't', 't', 0), // xtt = Tacahua Mixtec
+ TRUETYPE_TAG('x', 't', 'u', 0), // xtu = Cuyamecalco Mixtec
+ TRUETYPE_TAG('x', 't', 'w', 0), // xtw = Tawandê
+ TRUETYPE_TAG('x', 't', 'y', 0), // xty = Yoloxochitl Mixtec
+ TRUETYPE_TAG('x', 't', 'z', 0), // xtz = Tasmanian
+ TRUETYPE_TAG('x', 'u', 'a', 0), // xua = Alu Kurumba
+ TRUETYPE_TAG('x', 'u', 'b', 0), // xub = Betta Kurumba
+ TRUETYPE_TAG('x', 'u', 'g', 0), // xug = Kunigami
+ TRUETYPE_TAG('x', 'u', 'j', 0), // xuj = Jennu Kurumba
+ TRUETYPE_TAG('x', 'u', 'm', 0), // xum = Umbrian
+ TRUETYPE_TAG('x', 'u', 'o', 0), // xuo = Kuo
+ TRUETYPE_TAG('x', 'u', 'p', 0), // xup = Upper Umpqua
+ TRUETYPE_TAG('x', 'u', 'r', 0), // xur = Urartian
+ TRUETYPE_TAG('x', 'u', 't', 0), // xut = Kuthant
+ TRUETYPE_TAG('x', 'u', 'u', 0), // xuu = Kxoe
+ TRUETYPE_TAG('x', 'v', 'e', 0), // xve = Venetic
+ TRUETYPE_TAG('x', 'v', 'i', 0), // xvi = Kamviri
+ TRUETYPE_TAG('x', 'v', 'n', 0), // xvn = Vandalic
+ TRUETYPE_TAG('x', 'v', 'o', 0), // xvo = Volscian
+ TRUETYPE_TAG('x', 'v', 's', 0), // xvs = Vestinian
+ TRUETYPE_TAG('x', 'w', 'a', 0), // xwa = Kwaza
+ TRUETYPE_TAG('x', 'w', 'c', 0), // xwc = Woccon
+ TRUETYPE_TAG('x', 'w', 'e', 0), // xwe = Xwela Gbe
+ TRUETYPE_TAG('x', 'w', 'g', 0), // xwg = Kwegu
+ TRUETYPE_TAG('x', 'w', 'l', 0), // xwl = Western Xwla Gbe
+ TRUETYPE_TAG('x', 'w', 'o', 0), // xwo = Written Oirat
+ TRUETYPE_TAG('x', 'w', 'r', 0), // xwr = Kwerba Mamberamo
+ TRUETYPE_TAG('x', 'x', 'b', 0), // xxb = Boro (Ghana)
+ TRUETYPE_TAG('x', 'x', 'k', 0), // xxk = Ke'o
+ TRUETYPE_TAG('x', 'x', 'r', 0), // xxr = Koropó
+ TRUETYPE_TAG('x', 'x', 't', 0), // xxt = Tambora
+ TRUETYPE_TAG('x', 'y', 'l', 0), // xyl = Yalakalore
+ TRUETYPE_TAG('x', 'z', 'h', 0), // xzh = Zhang-Zhung
+ TRUETYPE_TAG('x', 'z', 'm', 0), // xzm = Zemgalian
+ TRUETYPE_TAG('x', 'z', 'p', 0), // xzp = Ancient Zapotec
+ TRUETYPE_TAG('y', 'a', 'a', 0), // yaa = Yaminahua
+ TRUETYPE_TAG('y', 'a', 'b', 0), // yab = Yuhup
+ TRUETYPE_TAG('y', 'a', 'c', 0), // yac = Pass Valley Yali
+ TRUETYPE_TAG('y', 'a', 'd', 0), // yad = Yagua
+ TRUETYPE_TAG('y', 'a', 'e', 0), // yae = Pumé
+ TRUETYPE_TAG('y', 'a', 'f',
+ 0), // yaf = Yaka (Democratic Republic of Congo)
+ TRUETYPE_TAG('y', 'a', 'g', 0), // yag = Yámana
+ TRUETYPE_TAG('y', 'a', 'h', 0), // yah = Yazgulyam
+ TRUETYPE_TAG('y', 'a', 'i', 0), // yai = Yagnobi
+ TRUETYPE_TAG('y', 'a', 'j', 0), // yaj = Banda-Yangere
+ TRUETYPE_TAG('y', 'a', 'k', 0), // yak = Yakama
+ TRUETYPE_TAG('y', 'a', 'l', 0), // yal = Yalunka
+ TRUETYPE_TAG('y', 'a', 'm', 0), // yam = Yamba
+ TRUETYPE_TAG('y', 'a', 'n', 0), // yan = Mayangna
+ TRUETYPE_TAG('y', 'a', 'o', 0), // yao = Yao
+ TRUETYPE_TAG('y', 'a', 'p', 0), // yap = Yapese
+ TRUETYPE_TAG('y', 'a', 'q', 0), // yaq = Yaqui
+ TRUETYPE_TAG('y', 'a', 'r', 0), // yar = Yabarana
+ TRUETYPE_TAG('y', 'a', 's', 0), // yas = Nugunu (Cameroon)
+ TRUETYPE_TAG('y', 'a', 't', 0), // yat = Yambeta
+ TRUETYPE_TAG('y', 'a', 'u', 0), // yau = Yuwana
+ TRUETYPE_TAG('y', 'a', 'v', 0), // yav = Yangben
+ TRUETYPE_TAG('y', 'a', 'w', 0), // yaw = Yawalapití
+ TRUETYPE_TAG('y', 'a', 'x', 0), // yax = Yauma
+ TRUETYPE_TAG('y', 'a', 'y', 0), // yay = Agwagwune
+ TRUETYPE_TAG('y', 'a', 'z', 0), // yaz = Lokaa
+ TRUETYPE_TAG('y', 'b', 'a', 0), // yba = Yala
+ TRUETYPE_TAG('y', 'b', 'b', 0), // ybb = Yemba
+ TRUETYPE_TAG('y', 'b', 'd', 0), // ybd = Yangbye
+ TRUETYPE_TAG('y', 'b', 'e', 0), // ybe = West Yugur
+ TRUETYPE_TAG('y', 'b', 'h', 0), // ybh = Yakha
+ TRUETYPE_TAG('y', 'b', 'i', 0), // ybi = Yamphu
+ TRUETYPE_TAG('y', 'b', 'j', 0), // ybj = Hasha
+ TRUETYPE_TAG('y', 'b', 'k', 0), // ybk = Bokha
+ TRUETYPE_TAG('y', 'b', 'l', 0), // ybl = Yukuben
+ TRUETYPE_TAG('y', 'b', 'm', 0), // ybm = Yaben
+ TRUETYPE_TAG('y', 'b', 'n', 0), // ybn = Yabaâna
+ TRUETYPE_TAG('y', 'b', 'o', 0), // ybo = Yabong
+ TRUETYPE_TAG('y', 'b', 'x', 0), // ybx = Yawiyo
+ TRUETYPE_TAG('y', 'b', 'y', 0), // yby = Yaweyuha
+ TRUETYPE_TAG('y', 'c', 'h', 0), // ych = Chesu
+ TRUETYPE_TAG('y', 'c', 'l', 0), // ycl = Lolopo
+ TRUETYPE_TAG('y', 'c', 'n', 0), // ycn = Yucuna
+ TRUETYPE_TAG('y', 'c', 'p', 0), // ycp = Chepya
+ TRUETYPE_TAG('y', 'd', 'd', 0), // ydd = Eastern Yiddish
+ TRUETYPE_TAG('y', 'd', 'e', 0), // yde = Yangum Dey
+ TRUETYPE_TAG('y', 'd', 'g', 0), // ydg = Yidgha
+ TRUETYPE_TAG('y', 'd', 'k', 0), // ydk = Yoidik
+ TRUETYPE_TAG('y', 'd', 's', 0), // yds = Yiddish Sign Language
+ TRUETYPE_TAG('y', 'e', 'a', 0), // yea = Ravula
+ TRUETYPE_TAG('y', 'e', 'c', 0), // yec = Yeniche
+ TRUETYPE_TAG('y', 'e', 'e', 0), // yee = Yimas
+ TRUETYPE_TAG('y', 'e', 'i', 0), // yei = Yeni
+ TRUETYPE_TAG('y', 'e', 'j', 0), // yej = Yevanic
+ TRUETYPE_TAG('y', 'e', 'l', 0), // yel = Yela
+ TRUETYPE_TAG('y', 'e', 'n', 0), // yen = Yendang
+ TRUETYPE_TAG('y', 'e', 'r', 0), // yer = Tarok
+ TRUETYPE_TAG('y', 'e', 's', 0), // yes = Yeskwa
+ TRUETYPE_TAG('y', 'e', 't', 0), // yet = Yetfa
+ TRUETYPE_TAG('y', 'e', 'u', 0), // yeu = Yerukula
+ TRUETYPE_TAG('y', 'e', 'v', 0), // yev = Yapunda
+ TRUETYPE_TAG('y', 'e', 'y', 0), // yey = Yeyi
+ TRUETYPE_TAG('y', 'g', 'l', 0), // ygl = Yangum Gel
+ TRUETYPE_TAG('y', 'g', 'm', 0), // ygm = Yagomi
+ TRUETYPE_TAG('y', 'g', 'p', 0), // ygp = Gepo
+ TRUETYPE_TAG('y', 'g', 'r', 0), // ygr = Yagaria
+ TRUETYPE_TAG('y', 'g', 'w', 0), // ygw = Yagwoia
+ TRUETYPE_TAG('y', 'h', 'a', 0), // yha = Baha Buyang
+ TRUETYPE_TAG('y', 'h', 'd', 0), // yhd = Judeo-Iraqi Arabic
+ TRUETYPE_TAG('y', 'h', 'l', 0), // yhl = Hlepho Phowa
+ TRUETYPE_TAG('y', 'i', 'a', 0), // yia = Yinggarda
+ TRUETYPE_TAG('y', 'i', 'f', 0), // yif = Ache
+ TRUETYPE_TAG('y', 'i', 'g', 0), // yig = Wusa Nasu
+ TRUETYPE_TAG('y', 'i', 'h', 0), // yih = Western Yiddish
+ TRUETYPE_TAG('y', 'i', 'i', 0), // yii = Yidiny
+ TRUETYPE_TAG('y', 'i', 'j', 0), // yij = Yindjibarndi
+ TRUETYPE_TAG('y', 'i', 'k', 0), // yik = Dongshanba Lalo
+ TRUETYPE_TAG('y', 'i', 'l', 0), // yil = Yindjilandji
+ TRUETYPE_TAG('y', 'i', 'm', 0), // yim = Yimchungru Naga
+ TRUETYPE_TAG('y', 'i', 'n', 0), // yin = Yinchia
+ TRUETYPE_TAG('y', 'i', 'p', 0), // yip = Pholo
+ TRUETYPE_TAG('y', 'i', 'q', 0), // yiq = Miqie
+ TRUETYPE_TAG('y', 'i', 'r', 0), // yir = North Awyu
+ TRUETYPE_TAG('y', 'i', 's', 0), // yis = Yis
+ TRUETYPE_TAG('y', 'i', 't', 0), // yit = Eastern Lalu
+ TRUETYPE_TAG('y', 'i', 'u', 0), // yiu = Awu
+ TRUETYPE_TAG('y', 'i', 'v', 0), // yiv = Northern Nisu
+ TRUETYPE_TAG('y', 'i', 'x', 0), // yix = Axi Yi
+ TRUETYPE_TAG('y', 'i', 'y', 0), // yiy = Yir Yoront
+ TRUETYPE_TAG('y', 'i', 'z', 0), // yiz = Azhe
+ TRUETYPE_TAG('y', 'k', 'a', 0), // yka = Yakan
+ TRUETYPE_TAG('y', 'k', 'g', 0), // ykg = Northern Yukaghir
+ TRUETYPE_TAG('y', 'k', 'i', 0), // yki = Yoke
+ TRUETYPE_TAG('y', 'k', 'k', 0), // ykk = Yakaikeke
+ TRUETYPE_TAG('y', 'k', 'l', 0), // ykl = Khlula
+ TRUETYPE_TAG('y', 'k', 'm', 0), // ykm = Kap
+ TRUETYPE_TAG('y', 'k', 'o', 0), // yko = Yasa
+ TRUETYPE_TAG('y', 'k', 'r', 0), // ykr = Yekora
+ TRUETYPE_TAG('y', 'k', 't', 0), // ykt = Kathu
+ TRUETYPE_TAG('y', 'k', 'y', 0), // yky = Yakoma
+ TRUETYPE_TAG('y', 'l', 'a', 0), // yla = Yaul
+ TRUETYPE_TAG('y', 'l', 'b', 0), // ylb = Yaleba
+ TRUETYPE_TAG('y', 'l', 'e', 0), // yle = Yele
+ TRUETYPE_TAG('y', 'l', 'g', 0), // ylg = Yelogu
+ TRUETYPE_TAG('y', 'l', 'i', 0), // yli = Angguruk Yali
+ TRUETYPE_TAG('y', 'l', 'l', 0), // yll = Yil
+ TRUETYPE_TAG('y', 'l', 'm', 0), // ylm = Limi
+ TRUETYPE_TAG('y', 'l', 'n', 0), // yln = Langnian Buyang
+ TRUETYPE_TAG('y', 'l', 'o', 0), // ylo = Naluo Yi
+ TRUETYPE_TAG('y', 'l', 'r', 0), // ylr = Yalarnnga
+ TRUETYPE_TAG('y', 'l', 'u', 0), // ylu = Aribwaung
+ TRUETYPE_TAG('y', 'l', 'y', 0), // yly = Nyâlayu
+ TRUETYPE_TAG('y', 'm', 'a', 0), // yma = Yamphe
+ TRUETYPE_TAG('y', 'm', 'b', 0), // ymb = Yambes
+ TRUETYPE_TAG('y', 'm', 'c', 0), // ymc = Southern Muji
+ TRUETYPE_TAG('y', 'm', 'd', 0), // ymd = Muda
+ TRUETYPE_TAG('y', 'm', 'e', 0), // yme = Yameo
+ TRUETYPE_TAG('y', 'm', 'g', 0), // ymg = Yamongeri
+ TRUETYPE_TAG('y', 'm', 'h', 0), // ymh = Mili
+ TRUETYPE_TAG('y', 'm', 'i', 0), // ymi = Moji
+ TRUETYPE_TAG('y', 'm', 'k', 0), // ymk = Makwe
+ TRUETYPE_TAG('y', 'm', 'l', 0), // yml = Iamalele
+ TRUETYPE_TAG('y', 'm', 'm', 0), // ymm = Maay
+ TRUETYPE_TAG('y', 'm', 'n', 0), // ymn = Yamna
+ TRUETYPE_TAG('y', 'm', 'o', 0), // ymo = Yangum Mon
+ TRUETYPE_TAG('y', 'm', 'p', 0), // ymp = Yamap
+ TRUETYPE_TAG('y', 'm', 'q', 0), // ymq = Qila Muji
+ TRUETYPE_TAG('y', 'm', 'r', 0), // ymr = Malasar
+ TRUETYPE_TAG('y', 'm', 's', 0), // yms = Mysian
+ TRUETYPE_TAG('y', 'm', 't', 0), // ymt = Mator-Taygi-Karagas
+ TRUETYPE_TAG('y', 'm', 'x', 0), // ymx = Northern Muji
+ TRUETYPE_TAG('y', 'm', 'z', 0), // ymz = Muzi
+ TRUETYPE_TAG('y', 'n', 'a', 0), // yna = Aluo
+ TRUETYPE_TAG('y', 'n', 'd', 0), // ynd = Yandruwandha
+ TRUETYPE_TAG('y', 'n', 'e', 0), // yne = Lang'e
+ TRUETYPE_TAG('y', 'n', 'g', 0), // yng = Yango
+ TRUETYPE_TAG('y', 'n', 'h', 0), // ynh = Yangho
+ TRUETYPE_TAG('y', 'n', 'k', 0), // ynk = Naukan Yupik
+ TRUETYPE_TAG('y', 'n', 'l', 0), // ynl = Yangulam
+ TRUETYPE_TAG('y', 'n', 'n', 0), // ynn = Yana
+ TRUETYPE_TAG('y', 'n', 'o', 0), // yno = Yong
+ TRUETYPE_TAG('y', 'n', 's', 0), // yns = Yansi
+ TRUETYPE_TAG('y', 'n', 'u', 0), // ynu = Yahuna
+ TRUETYPE_TAG('y', 'o', 'b', 0), // yob = Yoba
+ TRUETYPE_TAG('y', 'o', 'g', 0), // yog = Yogad
+ TRUETYPE_TAG('y', 'o', 'i', 0), // yoi = Yonaguni
+ TRUETYPE_TAG('y', 'o', 'k', 0), // yok = Yokuts
+ TRUETYPE_TAG('y', 'o', 'l', 0), // yol = Yola
+ TRUETYPE_TAG('y', 'o', 'm', 0), // yom = Yombe
+ TRUETYPE_TAG('y', 'o', 'n', 0), // yon = Yongkom
+ TRUETYPE_TAG('y', 'o', 's', 0), // yos = Yos
+ TRUETYPE_TAG('y', 'o', 'x', 0), // yox = Yoron
+ TRUETYPE_TAG('y', 'o', 'y', 0), // yoy = Yoy
+ TRUETYPE_TAG('y', 'p', 'a', 0), // ypa = Phala
+ TRUETYPE_TAG('y', 'p', 'b', 0), // ypb = Labo Phowa
+ TRUETYPE_TAG('y', 'p', 'g', 0), // ypg = Phola
+ TRUETYPE_TAG('y', 'p', 'h', 0), // yph = Phupha
+ TRUETYPE_TAG('y', 'p', 'k', 0), // ypk = Yupik languages
+ TRUETYPE_TAG('y', 'p', 'm', 0), // ypm = Phuma
+ TRUETYPE_TAG('y', 'p', 'n', 0), // ypn = Ani Phowa
+ TRUETYPE_TAG('y', 'p', 'o', 0), // ypo = Alo Phola
+ TRUETYPE_TAG('y', 'p', 'p', 0), // ypp = Phupa
+ TRUETYPE_TAG('y', 'p', 'z', 0), // ypz = Phuza
+ TRUETYPE_TAG('y', 'r', 'a', 0), // yra = Yerakai
+ TRUETYPE_TAG('y', 'r', 'b', 0), // yrb = Yareba
+ TRUETYPE_TAG('y', 'r', 'e', 0), // yre = Yaouré
+ TRUETYPE_TAG('y', 'r', 'i', 0), // yri = Yarí
+ TRUETYPE_TAG('y', 'r', 'k', 0), // yrk = Nenets
+ TRUETYPE_TAG('y', 'r', 'l', 0), // yrl = Nhengatu
+ TRUETYPE_TAG('y', 'r', 'n', 0), // yrn = Yerong
+ TRUETYPE_TAG('y', 'r', 's', 0), // yrs = Yarsun
+ TRUETYPE_TAG('y', 'r', 'w', 0), // yrw = Yarawata
+ TRUETYPE_TAG('y', 's', 'c', 0), // ysc = Yassic
+ TRUETYPE_TAG('y', 's', 'd', 0), // ysd = Samatao
+ TRUETYPE_TAG('y', 's', 'l', 0), // ysl = Yugoslavian Sign Language
+ TRUETYPE_TAG('y', 's', 'n', 0), // ysn = Sani
+ TRUETYPE_TAG('y', 's', 'o', 0), // yso = Nisi (China)
+ TRUETYPE_TAG('y', 's', 'p', 0), // ysp = Southern Lolopo
+ TRUETYPE_TAG('y', 's', 'r', 0), // ysr = Sirenik Yupik
+ TRUETYPE_TAG('y', 's', 's', 0), // yss = Yessan-Mayo
+ TRUETYPE_TAG('y', 's', 'y', 0), // ysy = Sanie
+ TRUETYPE_TAG('y', 't', 'a', 0), // yta = Talu
+ TRUETYPE_TAG('y', 't', 'l', 0), // ytl = Tanglang
+ TRUETYPE_TAG('y', 't', 'p', 0), // ytp = Thopho
+ TRUETYPE_TAG('y', 't', 'w', 0), // ytw = Yout Wam
+ TRUETYPE_TAG('y', 'u', 'a', 0), // yua = Yucateco
+ TRUETYPE_TAG('y', 'u', 'b', 0), // yub = Yugambal
+ TRUETYPE_TAG('y', 'u', 'c', 0), // yuc = Yuchi
+ TRUETYPE_TAG('y', 'u', 'd', 0), // yud = Judeo-Tripolitanian Arabic
+ TRUETYPE_TAG('y', 'u', 'e', 0), // yue = Yue Chinese
+ TRUETYPE_TAG('y', 'u', 'f', 0), // yuf = Havasupai-Walapai-Yavapai
+ TRUETYPE_TAG('y', 'u', 'g', 0), // yug = Yug
+ TRUETYPE_TAG('y', 'u', 'i', 0), // yui = Yurutí
+ TRUETYPE_TAG('y', 'u', 'j', 0), // yuj = Karkar-Yuri
+ TRUETYPE_TAG('y', 'u', 'k', 0), // yuk = Yuki
+ TRUETYPE_TAG('y', 'u', 'l', 0), // yul = Yulu
+ TRUETYPE_TAG('y', 'u', 'm', 0), // yum = Quechan
+ TRUETYPE_TAG('y', 'u', 'n', 0), // yun = Bena (Nigeria)
+ TRUETYPE_TAG('y', 'u', 'p', 0), // yup = Yukpa
+ TRUETYPE_TAG('y', 'u', 'q', 0), // yuq = Yuqui
+ TRUETYPE_TAG('y', 'u', 'r', 0), // yur = Yurok
+ TRUETYPE_TAG('y', 'u', 't', 0), // yut = Yopno
+ TRUETYPE_TAG('y', 'u', 'u', 0), // yuu = Yugh
+ TRUETYPE_TAG('y', 'u', 'w', 0), // yuw = Yau (Morobe Province)
+ TRUETYPE_TAG('y', 'u', 'x', 0), // yux = Southern Yukaghir
+ TRUETYPE_TAG('y', 'u', 'y', 0), // yuy = East Yugur
+ TRUETYPE_TAG('y', 'u', 'z', 0), // yuz = Yuracare
+ TRUETYPE_TAG('y', 'v', 'a', 0), // yva = Yawa
+ TRUETYPE_TAG('y', 'v', 't', 0), // yvt = Yavitero
+ TRUETYPE_TAG('y', 'w', 'a', 0), // ywa = Kalou
+ TRUETYPE_TAG('y', 'w', 'l', 0), // ywl = Western Lalu
+ TRUETYPE_TAG('y', 'w', 'n', 0), // ywn = Yawanawa
+ TRUETYPE_TAG('y', 'w', 'q', 0), // ywq = Wuding-Luquan Yi
+ TRUETYPE_TAG('y', 'w', 'r', 0), // ywr = Yawuru
+ TRUETYPE_TAG('y', 'w', 't', 0), // ywt = Xishanba Lalo
+ TRUETYPE_TAG('y', 'w', 'u', 0), // ywu = Wumeng Nasu
+ TRUETYPE_TAG('y', 'w', 'w', 0), // yww = Yawarawarga
+ TRUETYPE_TAG('y', 'y', 'u', 0), // yyu = Yau (Sandaun Province)
+ TRUETYPE_TAG('y', 'y', 'z', 0), // yyz = Ayizi
+ TRUETYPE_TAG('y', 'z', 'g', 0), // yzg = E'ma Buyang
+ TRUETYPE_TAG('y', 'z', 'k', 0), // yzk = Zokhuo
+ TRUETYPE_TAG('z', 'a', 'a', 0), // zaa = Sierra de Juárez Zapotec
+ TRUETYPE_TAG('z', 'a', 'b', 0), // zab = San Juan Guelavía Zapotec
+ TRUETYPE_TAG('z', 'a', 'c', 0), // zac = Ocotlán Zapotec
+ TRUETYPE_TAG('z', 'a', 'd', 0), // zad = Cajonos Zapotec
+ TRUETYPE_TAG('z', 'a', 'e', 0), // zae = Yareni Zapotec
+ TRUETYPE_TAG('z', 'a', 'f', 0), // zaf = Ayoquesco Zapotec
+ TRUETYPE_TAG('z', 'a', 'g', 0), // zag = Zaghawa
+ TRUETYPE_TAG('z', 'a', 'h', 0), // zah = Zangwal
+ TRUETYPE_TAG('z', 'a', 'i', 0), // zai = Isthmus Zapotec
+ TRUETYPE_TAG('z', 'a', 'j', 0), // zaj = Zaramo
+ TRUETYPE_TAG('z', 'a', 'k', 0), // zak = Zanaki
+ TRUETYPE_TAG('z', 'a', 'l', 0), // zal = Zauzou
+ TRUETYPE_TAG('z', 'a', 'm', 0), // zam = Miahuatlán Zapotec
+ TRUETYPE_TAG('z', 'a', 'o', 0), // zao = Ozolotepec Zapotec
+ TRUETYPE_TAG('z', 'a', 'p', 0), // zap = Zapotec
+ TRUETYPE_TAG('z', 'a', 'q', 0), // zaq = Aloápam Zapotec
+ TRUETYPE_TAG('z', 'a', 'r', 0), // zar = Rincón Zapotec
+ TRUETYPE_TAG('z', 'a', 's', 0), // zas = Santo Domingo Albarradas Zapotec
+ TRUETYPE_TAG('z', 'a', 't', 0), // zat = Tabaa Zapotec
+ TRUETYPE_TAG('z', 'a', 'u', 0), // zau = Zangskari
+ TRUETYPE_TAG('z', 'a', 'v', 0), // zav = Yatzachi Zapotec
+ TRUETYPE_TAG('z', 'a', 'w', 0), // zaw = Mitla Zapotec
+ TRUETYPE_TAG('z', 'a', 'x', 0), // zax = Xadani Zapotec
+ TRUETYPE_TAG('z', 'a', 'y', 0), // zay = Zayse-Zergulla
+ TRUETYPE_TAG('z', 'a', 'z', 0), // zaz = Zari
+ TRUETYPE_TAG('z', 'b', 'c', 0), // zbc = Central Berawan
+ TRUETYPE_TAG('z', 'b', 'e', 0), // zbe = East Berawan
+ TRUETYPE_TAG('z', 'b', 'l', 0), // zbl = Blissymbols
+ TRUETYPE_TAG('z', 'b', 't', 0), // zbt = Batui
+ TRUETYPE_TAG('z', 'b', 'w', 0), // zbw = West Berawan
+ TRUETYPE_TAG('z', 'c', 'a', 0), // zca = Coatecas Altas Zapotec
+ TRUETYPE_TAG('z', 'c', 'h', 0), // zch = Central Hongshuihe Zhuang
+ TRUETYPE_TAG('z', 'd', 'j', 0), // zdj = Ngazidja Comorian
+ TRUETYPE_TAG('z', 'e', 'a', 0), // zea = Zeeuws
+ TRUETYPE_TAG('z', 'e', 'g', 0), // zeg = Zenag
+ TRUETYPE_TAG('z', 'e', 'h', 0), // zeh = Eastern Hongshuihe Zhuang
+ TRUETYPE_TAG('z', 'e', 'n', 0), // zen = Zenaga
+ TRUETYPE_TAG('z', 'g', 'a', 0), // zga = Kinga
+ TRUETYPE_TAG('z', 'g', 'b', 0), // zgb = Guibei Zhuang
+ TRUETYPE_TAG('z', 'g', 'm', 0), // zgm = Minz Zhuang
+ TRUETYPE_TAG('z', 'g', 'n', 0), // zgn = Guibian Zhuang
+ TRUETYPE_TAG('z', 'g', 'r', 0), // zgr = Magori
+ TRUETYPE_TAG('z', 'h', 'b', 0), // zhb = Zhaba
+ TRUETYPE_TAG('z', 'h', 'd', 0), // zhd = Dai Zhuang
+ TRUETYPE_TAG('z', 'h', 'i', 0), // zhi = Zhire
+ TRUETYPE_TAG('z', 'h', 'n', 0), // zhn = Nong Zhuang
+ TRUETYPE_TAG('z', 'h', 'w', 0), // zhw = Zhoa
+ TRUETYPE_TAG('z', 'h', 'x', 0), // zhx = Chinese (family)
+ TRUETYPE_TAG('z', 'i', 'a', 0), // zia = Zia
+ TRUETYPE_TAG('z', 'i', 'b', 0), // zib = Zimbabwe Sign Language
+ TRUETYPE_TAG('z', 'i', 'k', 0), // zik = Zimakani
+ TRUETYPE_TAG('z', 'i', 'l', 0), // zil = Zialo
+ TRUETYPE_TAG('z', 'i', 'm', 0), // zim = Mesme
+ TRUETYPE_TAG('z', 'i', 'n', 0), // zin = Zinza
+ TRUETYPE_TAG('z', 'i', 'r', 0), // zir = Ziriya
+ TRUETYPE_TAG('z', 'i', 'w', 0), // ziw = Zigula
+ TRUETYPE_TAG('z', 'i', 'z', 0), // ziz = Zizilivakan
+ TRUETYPE_TAG('z', 'k', 'a', 0), // zka = Kaimbulawa
+ TRUETYPE_TAG('z', 'k', 'b', 0), // zkb = Koibal
+ TRUETYPE_TAG('z', 'k', 'g', 0), // zkg = Koguryo
+ TRUETYPE_TAG('z', 'k', 'h', 0), // zkh = Khorezmian
+ TRUETYPE_TAG('z', 'k', 'k', 0), // zkk = Karankawa
+ TRUETYPE_TAG('z', 'k', 'o', 0), // zko = Kott
+ TRUETYPE_TAG('z', 'k', 'p', 0), // zkp = São Paulo Kaingáng
+ TRUETYPE_TAG('z', 'k', 'r', 0), // zkr = Zakhring
+ TRUETYPE_TAG('z', 'k', 't', 0), // zkt = Kitan
+ TRUETYPE_TAG('z', 'k', 'u', 0), // zku = Kaurna
+ TRUETYPE_TAG('z', 'k', 'v', 0), // zkv = Krevinian
+ TRUETYPE_TAG('z', 'k', 'z', 0), // zkz = Khazar
+ TRUETYPE_TAG('z', 'l', 'e', 0), // zle = East Slavic languages
+ TRUETYPE_TAG('z', 'l', 'j', 0), // zlj = Liujiang Zhuang
+ TRUETYPE_TAG('z', 'l', 'm', 0), // zlm = Malay (individual language)
+ TRUETYPE_TAG('z', 'l', 'n', 0), // zln = Lianshan Zhuang
+ TRUETYPE_TAG('z', 'l', 'q', 0), // zlq = Liuqian Zhuang
+ TRUETYPE_TAG('z', 'l', 's', 0), // zls = South Slavic languages
+ TRUETYPE_TAG('z', 'l', 'w', 0), // zlw = West Slavic languages
+ TRUETYPE_TAG('z', 'm', 'a', 0), // zma = Manda (Australia)
+ TRUETYPE_TAG('z', 'm', 'b', 0), // zmb = Zimba
+ TRUETYPE_TAG('z', 'm', 'c', 0), // zmc = Margany
+ TRUETYPE_TAG('z', 'm', 'd', 0), // zmd = Maridan
+ TRUETYPE_TAG('z', 'm', 'e', 0), // zme = Mangerr
+ TRUETYPE_TAG('z', 'm', 'f', 0), // zmf = Mfinu
+ TRUETYPE_TAG('z', 'm', 'g', 0), // zmg = Marti Ke
+ TRUETYPE_TAG('z', 'm', 'h', 0), // zmh = Makolkol
+ TRUETYPE_TAG('z', 'm', 'i', 0), // zmi = Negeri Sembilan Malay
+ TRUETYPE_TAG('z', 'm', 'j', 0), // zmj = Maridjabin
+ TRUETYPE_TAG('z', 'm', 'k', 0), // zmk = Mandandanyi
+ TRUETYPE_TAG('z', 'm', 'l', 0), // zml = Madngele
+ TRUETYPE_TAG('z', 'm', 'm', 0), // zmm = Marimanindji
+ TRUETYPE_TAG('z', 'm', 'n', 0), // zmn = Mbangwe
+ TRUETYPE_TAG('z', 'm', 'o', 0), // zmo = Molo
+ TRUETYPE_TAG('z', 'm', 'p', 0), // zmp = Mpuono
+ TRUETYPE_TAG('z', 'm', 'q', 0), // zmq = Mituku
+ TRUETYPE_TAG('z', 'm', 'r', 0), // zmr = Maranunggu
+ TRUETYPE_TAG('z', 'm', 's', 0), // zms = Mbesa
+ TRUETYPE_TAG('z', 'm', 't', 0), // zmt = Maringarr
+ TRUETYPE_TAG('z', 'm', 'u', 0), // zmu = Muruwari
+ TRUETYPE_TAG('z', 'm', 'v', 0), // zmv = Mbariman-Gudhinma
+ TRUETYPE_TAG('z', 'm', 'w', 0), // zmw = Mbo (Democratic Republic of Congo)
+ TRUETYPE_TAG('z', 'm', 'x', 0), // zmx = Bomitaba
+ TRUETYPE_TAG('z', 'm', 'y', 0), // zmy = Mariyedi
+ TRUETYPE_TAG('z', 'm', 'z', 0), // zmz = Mbandja
+ TRUETYPE_TAG('z', 'n', 'a', 0), // zna = Zan Gula
+ TRUETYPE_TAG('z', 'n', 'd', 0), // znd = Zande languages
+ TRUETYPE_TAG('z', 'n', 'e', 0), // zne = Zande (individual language)
+ TRUETYPE_TAG('z', 'n', 'g', 0), // zng = Mang
+ TRUETYPE_TAG('z', 'n', 'k', 0), // znk = Manangkari
+ TRUETYPE_TAG('z', 'n', 's', 0), // zns = Mangas
+ TRUETYPE_TAG('z', 'o', 'c', 0), // zoc = Copainalá Zoque
+ TRUETYPE_TAG('z', 'o', 'h', 0), // zoh = Chimalapa Zoque
+ TRUETYPE_TAG('z', 'o', 'm', 0), // zom = Zou
+ TRUETYPE_TAG('z', 'o', 'o', 0), // zoo = Asunción Mixtepec Zapotec
+ TRUETYPE_TAG('z', 'o', 'q', 0), // zoq = Tabasco Zoque
+ TRUETYPE_TAG('z', 'o', 'r', 0), // zor = Rayón Zoque
+ TRUETYPE_TAG('z', 'o', 's', 0), // zos = Francisco León Zoque
+ TRUETYPE_TAG('z', 'p', 'a', 0), // zpa = Lachiguiri Zapotec
+ TRUETYPE_TAG('z', 'p', 'b', 0), // zpb = Yautepec Zapotec
+ TRUETYPE_TAG('z', 'p', 'c', 0), // zpc = Choapan Zapotec
+ TRUETYPE_TAG('z', 'p', 'd', 0), // zpd = Southeastern Ixtlán Zapotec
+ TRUETYPE_TAG('z', 'p', 'e', 0), // zpe = Petapa Zapotec
+ TRUETYPE_TAG('z', 'p', 'f', 0), // zpf = San Pedro Quiatoni Zapotec
+ TRUETYPE_TAG('z', 'p', 'g', 0), // zpg = Guevea De Humboldt Zapotec
+ TRUETYPE_TAG('z', 'p', 'h', 0), // zph = Totomachapan Zapotec
+ TRUETYPE_TAG('z', 'p', 'i', 0), // zpi = Santa María Quiegolani Zapotec
+ TRUETYPE_TAG('z', 'p', 'j', 0), // zpj = Quiavicuzas Zapotec
+ TRUETYPE_TAG('z', 'p', 'k', 0), // zpk = Tlacolulita Zapotec
+ TRUETYPE_TAG('z', 'p', 'l', 0), // zpl = Lachixío Zapotec
+ TRUETYPE_TAG('z', 'p', 'm', 0), // zpm = Mixtepec Zapotec
+ TRUETYPE_TAG('z', 'p', 'n', 0), // zpn = Santa Inés Yatzechi Zapotec
+ TRUETYPE_TAG('z', 'p', 'o', 0), // zpo = Amatlán Zapotec
+ TRUETYPE_TAG('z', 'p', 'p', 0), // zpp = El Alto Zapotec
+ TRUETYPE_TAG('z', 'p', 'q', 0), // zpq = Zoogocho Zapotec
+ TRUETYPE_TAG('z', 'p', 'r', 0), // zpr = Santiago Xanica Zapotec
+ TRUETYPE_TAG('z', 'p', 's', 0), // zps = Coatlán Zapotec
+ TRUETYPE_TAG('z', 'p', 't', 0), // zpt = San Vicente Coatlán Zapotec
+ TRUETYPE_TAG('z', 'p', 'u', 0), // zpu = Yalálag Zapotec
+ TRUETYPE_TAG('z', 'p', 'v', 0), // zpv = Chichicapan Zapotec
+ TRUETYPE_TAG('z', 'p', 'w', 0), // zpw = Zaniza Zapotec
+ TRUETYPE_TAG('z', 'p', 'x', 0), // zpx = San Baltazar Loxicha Zapotec
+ TRUETYPE_TAG('z', 'p', 'y', 0), // zpy = Mazaltepec Zapotec
+ TRUETYPE_TAG('z', 'p', 'z', 0), // zpz = Texmelucan Zapotec
+ TRUETYPE_TAG('z', 'q', 'e', 0), // zqe = Qiubei Zhuang
+ TRUETYPE_TAG('z', 'r', 'a', 0), // zra = Kara (Korea)
+ TRUETYPE_TAG('z', 'r', 'g', 0), // zrg = Mirgan
+ TRUETYPE_TAG('z', 'r', 'n', 0), // zrn = Zerenkel
+ TRUETYPE_TAG('z', 'r', 'o', 0), // zro = Záparo
+ TRUETYPE_TAG('z', 'r', 'p', 0), // zrp = Zarphatic
+ TRUETYPE_TAG('z', 'r', 's', 0), // zrs = Mairasi
+ TRUETYPE_TAG('z', 's', 'a', 0), // zsa = Sarasira
+ TRUETYPE_TAG('z', 's', 'k', 0), // zsk = Kaskean
+ TRUETYPE_TAG('z', 's', 'l', 0), // zsl = Zambian Sign Language
+ TRUETYPE_TAG('z', 's', 'm', 0), // zsm = Standard Malay
+ TRUETYPE_TAG('z', 's', 'r', 0), // zsr = Southern Rincon Zapotec
+ TRUETYPE_TAG('z', 's', 'u', 0), // zsu = Sukurum
+ TRUETYPE_TAG('z', 't', 'e', 0), // zte = Elotepec Zapotec
+ TRUETYPE_TAG('z', 't', 'g', 0), // ztg = Xanaguía Zapotec
+ TRUETYPE_TAG('z', 't', 'l', 0), // ztl = Lapaguía-Guivini Zapotec
+ TRUETYPE_TAG('z', 't', 'm', 0), // ztm = San Agustín Mixtepec Zapotec
+ TRUETYPE_TAG('z', 't', 'n', 0), // ztn = Santa Catarina Albarradas Zapotec
+ TRUETYPE_TAG('z', 't', 'p', 0), // ztp = Loxicha Zapotec
+ TRUETYPE_TAG('z', 't', 'q', 0), // ztq = Quioquitani-Quierí Zapotec
+ TRUETYPE_TAG('z', 't', 's', 0), // zts = Tilquiapan Zapotec
+ TRUETYPE_TAG('z', 't', 't', 0), // ztt = Tejalapan Zapotec
+ TRUETYPE_TAG('z', 't', 'u', 0), // ztu = Güilá Zapotec
+ TRUETYPE_TAG('z', 't', 'x', 0), // ztx = Zaachila Zapotec
+ TRUETYPE_TAG('z', 't', 'y', 0), // zty = Yatee Zapotec
+ TRUETYPE_TAG('z', 'u', 'a', 0), // zua = Zeem
+ TRUETYPE_TAG('z', 'u', 'h', 0), // zuh = Tokano
+ TRUETYPE_TAG('z', 'u', 'm', 0), // zum = Kumzari
+ TRUETYPE_TAG('z', 'u', 'n', 0), // zun = Zuni
+ TRUETYPE_TAG('z', 'u', 'y', 0), // zuy = Zumaya
+ TRUETYPE_TAG('z', 'w', 'a', 0), // zwa = Zay
+ TRUETYPE_TAG('z', 'x', 'x', 0), // zxx = No linguistic content
+ TRUETYPE_TAG('z', 'y', 'b', 0), // zyb = Yongbei Zhuang
+ TRUETYPE_TAG('z', 'y', 'g', 0), // zyg = Yang Zhuang
+ TRUETYPE_TAG('z', 'y', 'j', 0), // zyj = Youjiang Zhuang
+ TRUETYPE_TAG('z', 'y', 'n', 0), // zyn = Yongnan Zhuang
+ TRUETYPE_TAG('z', 'y', 'p', 0), // zyp = Zyphe
+ TRUETYPE_TAG('z', 'z', 'a', 0), // zza = Zaza
+ TRUETYPE_TAG('z', 'z', 'j', 0), // zzj = Zuojiang Zhuang
+ 0x0 // end of language code list
+};
+
+/*
+ * * * * * This file contains MACHINE-GENERATED DATA, do not edit! * * * * *
+ */
diff --git a/gfx/thebes/gfxLineSegment.h b/gfx/thebes/gfxLineSegment.h
new file mode 100644
index 0000000000..5b9479ede5
--- /dev/null
+++ b/gfx/thebes/gfxLineSegment.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_LINESEGMENT_H
+#define GFX_LINESEGMENT_H
+
+#include "gfxTypes.h"
+#include "gfxPoint.h"
+
+struct gfxLineSegment {
+ gfxLineSegment() : mStart(gfxPoint()), mEnd(gfxPoint()) {}
+ gfxLineSegment(const gfxPoint& aStart, const gfxPoint& aEnd)
+ : mStart(aStart), mEnd(aEnd) {}
+
+ bool PointsOnSameSide(const gfxPoint& aOne, const gfxPoint& aTwo) {
+ // Solve the equation
+ // y - mStart.y - ((mEnd.y - mStart.y)/(mEnd.x - mStart.x))(x - mStart.x)
+ // for both points
+
+ gfxFloat deltaY = (mEnd.y - mStart.y);
+ gfxFloat deltaX = (mEnd.x - mStart.x);
+
+ gfxFloat one = deltaX * (aOne.y - mStart.y) - deltaY * (aOne.x - mStart.x);
+ gfxFloat two = deltaX * (aTwo.y - mStart.y) - deltaY * (aTwo.x - mStart.x);
+
+ // If both results have the same sign, then we're on the correct side of the
+ // line. 0 (on the line) is always considered in.
+
+ if ((one >= 0 && two >= 0) || (one <= 0 && two <= 0)) return true;
+ return false;
+ }
+
+ /**
+ * Determines if two line segments intersect, and returns the intersection
+ * point in aIntersection if they do.
+ *
+ * Coincident lines are considered not intersecting as they don't have an
+ * intersection point.
+ */
+ bool Intersects(const gfxLineSegment& aOther, gfxPoint& aIntersection) {
+ gfxFloat denominator =
+ (aOther.mEnd.y - aOther.mStart.y).value * (mEnd.x - mStart.x).value -
+ (aOther.mEnd.x - aOther.mStart.x).value * (mEnd.y - mStart.y).value;
+
+ // Parallel or coincident. We treat coincident as not intersecting since
+ // these lines are guaranteed to have corners that intersect instead.
+ if (!denominator) {
+ return false;
+ }
+
+ gfxFloat anumerator = (aOther.mEnd.x - aOther.mStart.x).value *
+ (mStart.y - aOther.mStart.y).value -
+ (aOther.mEnd.y - aOther.mStart.y).value *
+ (mStart.x - aOther.mStart.x).value;
+
+ gfxFloat bnumerator =
+ (mEnd.x - mStart.x).value * (mStart.y - aOther.mStart.y).value -
+ (mEnd.y - mStart.y).value * (mStart.x - aOther.mStart.x).value;
+
+ gfxFloat ua = anumerator / denominator;
+ gfxFloat ub = bnumerator / denominator;
+
+ if (ua <= 0.0 || ua >= 1.0 || ub <= 0.0 || ub >= 1.0) {
+ // Intersection is outside of the segment
+ return false;
+ }
+
+ aIntersection = mStart + (mEnd - mStart) * ua;
+ return true;
+ }
+
+ gfxPoint mStart;
+ gfxPoint mEnd;
+};
+
+#endif /* GFX_LINESEGMENT_H */
diff --git a/gfx/thebes/gfxMacFont.cpp b/gfx/thebes/gfxMacFont.cpp
new file mode 100644
index 0000000000..2952ba5bbb
--- /dev/null
+++ b/gfx/thebes/gfxMacFont.cpp
@@ -0,0 +1,581 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxMacFont.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/gfx/ScaledFontMac.h"
+
+#include "gfxCoreTextShaper.h"
+#include <algorithm>
+#include "gfxPlatformMac.h"
+#include "gfxContext.h"
+#include "gfxFontUtils.h"
+#include "gfxHarfBuzzShaper.h"
+#include "gfxMacPlatformFontList.h"
+#include "gfxFontConstants.h"
+#include "gfxTextRun.h"
+#include "gfxUtils.h"
+#include "nsCocoaFeatures.h"
+#include "AppleUtils.h"
+#include "cairo-quartz.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+template <class T>
+struct TagEquals {
+ bool Equals(const T& aIter, uint32_t aTag) const {
+ return aIter.mTag == aTag;
+ }
+};
+
+gfxMacFont::gfxMacFont(const RefPtr<UnscaledFontMac>& aUnscaledFont,
+ MacOSFontEntry* aFontEntry,
+ const gfxFontStyle* aFontStyle)
+ : gfxFont(aUnscaledFont, aFontEntry, aFontStyle),
+ mCGFont(nullptr),
+ mCTFont(nullptr),
+ mFontSmoothingBackgroundColor(aFontStyle->fontSmoothingBackgroundColor),
+ mVariationFont(aFontEntry->HasVariations()) {
+ mApplySyntheticBold = aFontStyle->NeedsSyntheticBold(aFontEntry);
+
+ if (mVariationFont) {
+ CGFontRef baseFont = aUnscaledFont->GetFont();
+ if (!baseFont) {
+ mIsValid = false;
+ return;
+ }
+
+ // Get the variation settings needed to instantiate the fontEntry
+ // for a particular fontStyle.
+ AutoTArray<gfxFontVariation, 4> vars;
+ aFontEntry->GetVariationsForStyle(vars, *aFontStyle);
+
+ if (aFontEntry->HasOpticalSize()) {
+ // Because of a Core Text bug, we need to ensure that if the font has
+ // an 'opsz' axis, it is always explicitly set, and NOT to the font's
+ // default value. (See bug 1457417, bug 1478720.)
+ // We record the result of searching the font's axes in the font entry,
+ // so that this only has to be done by the first instance created for
+ // a given font resource.
+ const uint32_t kOpszTag = HB_TAG('o', 'p', 's', 'z');
+ const float kOpszFudgeAmount = 0.01f;
+
+ // Record the opsz axis details in the font entry, if not already done.
+ if (!aFontEntry->mOpszAxis.mTag) {
+ AutoTArray<gfxFontVariationAxis, 4> axes;
+ aFontEntry->GetVariationAxes(axes);
+ auto index =
+ axes.IndexOf(kOpszTag, 0, TagEquals<gfxFontVariationAxis>());
+ MOZ_ASSERT(index != axes.NoIndex);
+ if (index != axes.NoIndex) {
+ const auto& axis = axes[index];
+ aFontEntry->mOpszAxis = axis;
+ // Pick a slightly-adjusted version of the default that we'll
+ // use to work around Core Text's habit of ignoring any attempt
+ // to explicitly set the default value.
+ aFontEntry->mAdjustedDefaultOpsz =
+ axis.mDefaultValue == axis.mMinValue
+ ? axis.mDefaultValue + kOpszFudgeAmount
+ : axis.mDefaultValue - kOpszFudgeAmount;
+ }
+ }
+
+ // Add 'opsz' if not present, or tweak its value if it looks too close
+ // to the default (after clamping to the font's available range).
+ auto index = vars.IndexOf(kOpszTag, 0, TagEquals<gfxFontVariation>());
+ if (index == vars.NoIndex) {
+ // No explicit opsz; set to the font's default.
+ vars.AppendElement(
+ gfxFontVariation{kOpszTag, aFontEntry->mAdjustedDefaultOpsz});
+ } else {
+ // An 'opsz' value was already present; use it, but adjust if necessary
+ // to a "safe" value that Core Text won't ignore.
+ auto& value = vars[index].mValue;
+ auto& axis = aFontEntry->mOpszAxis;
+ value = fmin(fmax(value, axis.mMinValue), axis.mMaxValue);
+ if (std::abs(value - axis.mDefaultValue) < kOpszFudgeAmount) {
+ value = aFontEntry->mAdjustedDefaultOpsz;
+ }
+ }
+ }
+
+ mCGFont = UnscaledFontMac::CreateCGFontWithVariations(
+ baseFont, aUnscaledFont->CGAxesCache(), aUnscaledFont->CTAxesCache(),
+ vars.Length(), vars.Elements());
+ if (!mCGFont) {
+ ::CFRetain(baseFont);
+ mCGFont = baseFont;
+ }
+ } else {
+ mCGFont = aUnscaledFont->GetFont();
+ if (!mCGFont) {
+ mIsValid = false;
+ return;
+ }
+ ::CFRetain(mCGFont);
+ }
+
+ // InitMetrics will handle the sizeAdjust factor and set mAdjustedSize
+ InitMetrics();
+ if (!mIsValid) {
+ return;
+ }
+
+ // turn off font anti-aliasing based on user pref setting
+ if (mAdjustedSize <=
+ (gfxFloat)gfxPlatformMac::GetPlatform()->GetAntiAliasingThreshold()) {
+ mAntialiasOption = kAntialiasNone;
+ } else if (mStyle.useGrayscaleAntialiasing) {
+ mAntialiasOption = kAntialiasGrayscale;
+ }
+}
+
+gfxMacFont::~gfxMacFont() {
+ if (mCGFont) {
+ ::CFRelease(mCGFont);
+ }
+ if (mCTFont) {
+ ::CFRelease(mCTFont);
+ }
+}
+
+bool gfxMacFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText,
+ uint32_t aOffset, uint32_t aLength, Script aScript,
+ nsAtom* aLanguage, bool aVertical,
+ RoundingFlags aRounding,
+ gfxShapedText* aShapedText) {
+ if (!mIsValid) {
+ NS_WARNING("invalid font! expect incorrect text rendering");
+ return false;
+ }
+
+ // Currently, we don't support vertical shaping via CoreText,
+ // so we ignore RequiresAATLayout if vertical is requested.
+ auto macFontEntry = static_cast<MacOSFontEntry*>(GetFontEntry());
+ if (macFontEntry->RequiresAATLayout() && !aVertical &&
+ StaticPrefs::gfx_font_rendering_coretext_enabled()) {
+ if (!mCoreTextShaper) {
+ mCoreTextShaper = MakeUnique<gfxCoreTextShaper>(this);
+ }
+ if (mCoreTextShaper->ShapeText(aDrawTarget, aText, aOffset, aLength,
+ aScript, aLanguage, aVertical, aRounding,
+ aShapedText)) {
+ PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical,
+ aShapedText);
+ if (GetFontEntry()->HasTrackingTable()) {
+ // Convert font size from device pixels back to CSS px
+ // to use in selecting tracking value
+ float trackSize = GetAdjustedSize() *
+ aShapedText->GetAppUnitsPerDevUnit() /
+ AppUnitsPerCSSPixel();
+ float tracking =
+ GetFontEntry()->TrackingForCSSPx(trackSize) * mFUnitsConvFactor;
+ // Applying tracking is a lot like the adjustment we do for
+ // synthetic bold: we want to apply between clusters, not to
+ // non-spacing glyphs within a cluster. So we can reuse that
+ // helper here.
+ aShapedText->AdjustAdvancesForSyntheticBold(tracking, aOffset, aLength);
+ }
+ return true;
+ }
+ }
+
+ return gfxFont::ShapeText(aDrawTarget, aText, aOffset, aLength, aScript,
+ aLanguage, aVertical, aRounding, aShapedText);
+}
+
+gfxFont::RunMetrics gfxMacFont::Measure(const gfxTextRun* aTextRun,
+ uint32_t aStart, uint32_t aEnd,
+ BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget,
+ Spacing* aSpacing,
+ gfx::ShapedTextFlags aOrientation) {
+ gfxFont::RunMetrics metrics =
+ gfxFont::Measure(aTextRun, aStart, aEnd, aBoundingBoxType, aRefDrawTarget,
+ aSpacing, aOrientation);
+
+ // if aBoundingBoxType is not TIGHT_HINTED_OUTLINE_EXTENTS then we need to add
+ // a pixel column each side of the bounding box in case of antialiasing
+ // "bleed"
+ if (aBoundingBoxType != TIGHT_HINTED_OUTLINE_EXTENTS &&
+ metrics.mBoundingBox.width > 0) {
+ metrics.mBoundingBox.x -= aTextRun->GetAppUnitsPerDevUnit();
+ metrics.mBoundingBox.width += aTextRun->GetAppUnitsPerDevUnit() * 2;
+ }
+
+ return metrics;
+}
+
+void gfxMacFont::InitMetrics() {
+ mIsValid = false;
+ ::memset(&mMetrics, 0, sizeof(mMetrics));
+
+ uint32_t upem = 0;
+
+ // try to get unitsPerEm from sfnt head table, to avoid calling CGFont
+ // if possible (bug 574368) and because CGFontGetUnitsPerEm does not
+ // return the true value for OpenType/CFF fonts (it normalizes to 1000,
+ // which then leads to metrics errors when we read the 'hmtx' table to
+ // get glyph advances for HarfBuzz, see bug 580863)
+ AutoCFRelease<CFDataRef> headData =
+ ::CGFontCopyTableForTag(mCGFont, TRUETYPE_TAG('h', 'e', 'a', 'd'));
+ if (headData) {
+ if (size_t(::CFDataGetLength(headData)) >= sizeof(HeadTable)) {
+ const HeadTable* head =
+ reinterpret_cast<const HeadTable*>(::CFDataGetBytePtr(headData));
+ upem = head->unitsPerEm;
+ }
+ }
+ if (!upem) {
+ upem = ::CGFontGetUnitsPerEm(mCGFont);
+ }
+
+ if (upem < 16 || upem > 16384) {
+ // See http://www.microsoft.com/typography/otspec/head.htm
+#ifdef DEBUG
+ char warnBuf[1024];
+ SprintfLiteral(warnBuf,
+ "Bad font metrics for: %s (invalid unitsPerEm value)",
+ mFontEntry->Name().get());
+ NS_WARNING(warnBuf);
+#endif
+ return;
+ }
+
+ // Apply any size-adjust from the font enty to the given size; this may be
+ // re-adjusted below if font-size-adjust is in effect.
+ mAdjustedSize = GetAdjustedSize();
+ mFUnitsConvFactor = mAdjustedSize / upem;
+
+ // For CFF fonts, when scaling values read from CGFont* APIs, we need to
+ // use CG's idea of unitsPerEm, which may differ from the "true" value in
+ // the head table of the font (see bug 580863)
+ gfxFloat cgConvFactor;
+ if (static_cast<MacOSFontEntry*>(mFontEntry.get())->IsCFF()) {
+ cgConvFactor = mAdjustedSize / ::CGFontGetUnitsPerEm(mCGFont);
+ } else {
+ cgConvFactor = mFUnitsConvFactor;
+ }
+
+ // Try to read 'sfnt' metrics; for local, non-sfnt fonts ONLY, fall back to
+ // platform APIs. The InitMetrics...() functions will set mIsValid on success.
+ if (!InitMetricsFromSfntTables(mMetrics) &&
+ (!mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont())) {
+ InitMetricsFromPlatform();
+ }
+ if (!mIsValid) {
+ return;
+ }
+
+ if (mMetrics.xHeight == 0.0) {
+ mMetrics.xHeight = ::CGFontGetXHeight(mCGFont) * cgConvFactor;
+ }
+ if (mMetrics.capHeight == 0.0) {
+ mMetrics.capHeight = ::CGFontGetCapHeight(mCGFont) * cgConvFactor;
+ }
+
+ AutoCFRelease<CFDataRef> cmap =
+ ::CGFontCopyTableForTag(mCGFont, TRUETYPE_TAG('c', 'm', 'a', 'p'));
+
+ uint32_t glyphID;
+ mMetrics.zeroWidth = GetCharWidth(cmap, '0', &glyphID, cgConvFactor);
+ if (glyphID == 0) {
+ mMetrics.zeroWidth = -1.0; // indicates not found
+ }
+
+ if (FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) !=
+ FontSizeAdjust::Tag::None &&
+ mStyle.sizeAdjust >= 0.0 && GetAdjustedSize() > 0.0) {
+ // apply font-size-adjust, and recalculate metrics
+ gfxFloat aspect;
+ switch (FontSizeAdjust::Tag(mStyle.sizeAdjustBasis)) {
+ default:
+ MOZ_ASSERT_UNREACHABLE("unhandled sizeAdjustBasis?");
+ aspect = 0.0;
+ break;
+ case FontSizeAdjust::Tag::ExHeight:
+ aspect = mMetrics.xHeight / mAdjustedSize;
+ break;
+ case FontSizeAdjust::Tag::CapHeight:
+ aspect = mMetrics.capHeight / mAdjustedSize;
+ break;
+ case FontSizeAdjust::Tag::ChWidth:
+ aspect =
+ mMetrics.zeroWidth < 0.0 ? 0.5 : mMetrics.zeroWidth / mAdjustedSize;
+ break;
+ case FontSizeAdjust::Tag::IcWidth:
+ case FontSizeAdjust::Tag::IcHeight: {
+ bool vertical = FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) ==
+ FontSizeAdjust::Tag::IcHeight;
+ gfxFloat advance = GetCharAdvance(kWaterIdeograph, vertical);
+ aspect = advance > 0.0 ? advance / mAdjustedSize : 1.0;
+ break;
+ }
+ }
+ if (aspect > 0.0) {
+ // If we created a shaper above (to measure glyphs), discard it so we
+ // get a new one for the adjusted scaling.
+ delete mHarfBuzzShaper.exchange(nullptr);
+ mAdjustedSize = mStyle.GetAdjustedSize(aspect);
+ mFUnitsConvFactor = mAdjustedSize / upem;
+ if (static_cast<MacOSFontEntry*>(mFontEntry.get())->IsCFF()) {
+ cgConvFactor = mAdjustedSize / ::CGFontGetUnitsPerEm(mCGFont);
+ } else {
+ cgConvFactor = mFUnitsConvFactor;
+ }
+ mMetrics.xHeight = 0.0;
+ if (!InitMetricsFromSfntTables(mMetrics) &&
+ (!mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont())) {
+ InitMetricsFromPlatform();
+ }
+ if (!mIsValid) {
+ // this shouldn't happen, as we succeeded earlier before applying
+ // the size-adjust factor! But check anyway, for paranoia's sake.
+ return;
+ }
+ // Update metrics from the re-scaled font.
+ if (mMetrics.xHeight == 0.0) {
+ mMetrics.xHeight = ::CGFontGetXHeight(mCGFont) * cgConvFactor;
+ }
+ if (mMetrics.capHeight == 0.0) {
+ mMetrics.capHeight = ::CGFontGetCapHeight(mCGFont) * cgConvFactor;
+ }
+ mMetrics.zeroWidth = GetCharWidth(cmap, '0', &glyphID, cgConvFactor);
+ if (glyphID == 0) {
+ mMetrics.zeroWidth = -1.0; // indicates not found
+ }
+ }
+ }
+
+ // Once we reach here, we've got basic metrics and set mIsValid = TRUE;
+ // there should be no further points of actual failure in InitMetrics().
+ // (If one is introduced, be sure to reset mIsValid to FALSE!)
+
+ mMetrics.emHeight = mAdjustedSize;
+
+ // Measure/calculate additional metrics, independent of whether we used
+ // the tables directly or ATS metrics APIs
+
+ if (mMetrics.aveCharWidth <= 0) {
+ mMetrics.aveCharWidth = GetCharWidth(cmap, 'x', &glyphID, cgConvFactor);
+ if (glyphID == 0) {
+ // we didn't find 'x', so use maxAdvance rather than zero
+ mMetrics.aveCharWidth = mMetrics.maxAdvance;
+ }
+ }
+
+ mMetrics.spaceWidth = GetCharWidth(cmap, ' ', &glyphID, cgConvFactor);
+ if (glyphID == 0) {
+ // no space glyph?!
+ mMetrics.spaceWidth = mMetrics.aveCharWidth;
+ }
+ mSpaceGlyph = glyphID;
+
+ mMetrics.ideographicWidth =
+ GetCharWidth(cmap, kWaterIdeograph, &glyphID, cgConvFactor);
+ if (glyphID == 0) {
+ // Indicate "not found".
+ mMetrics.ideographicWidth = -1.0;
+ }
+
+ CalculateDerivedMetrics(mMetrics);
+
+ SanitizeMetrics(&mMetrics, mFontEntry->mIsBadUnderlineFont);
+
+ if (ApplySyntheticBold()) {
+ auto delta = GetSyntheticBoldOffset();
+ mMetrics.spaceWidth += delta;
+ mMetrics.aveCharWidth += delta;
+ mMetrics.maxAdvance += delta;
+ if (mMetrics.zeroWidth > 0) {
+ mMetrics.zeroWidth += delta;
+ }
+ if (mMetrics.ideographicWidth > 0) {
+ mMetrics.ideographicWidth += delta;
+ }
+ }
+
+#if 0
+ fprintf (stderr, "Font: %p (%s) size: %f\n", this,
+ NS_ConvertUTF16toUTF8(GetName()).get(), mStyle.size);
+// fprintf (stderr, " fbounds.origin.x %f y %f size.width %f height %f\n", fbounds.origin.x, fbounds.origin.y, fbounds.size.width, fbounds.size.height);
+ fprintf (stderr, " emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent);
+ fprintf (stderr, " maxAscent: %f maxDescent: %f maxAdvance: %f\n", mMetrics.maxAscent, mMetrics.maxDescent, mMetrics.maxAdvance);
+ fprintf (stderr, " internalLeading: %f externalLeading: %f\n", mMetrics.internalLeading, mMetrics.externalLeading);
+ fprintf (stderr, " spaceWidth: %f aveCharWidth: %f xHeight: %f capHeight: %f\n", mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.xHeight, mMetrics.capHeight);
+ fprintf (stderr, " uOff: %f uSize: %f stOff: %f stSize: %f\n", mMetrics.underlineOffset, mMetrics.underlineSize, mMetrics.strikeoutOffset, mMetrics.strikeoutSize);
+#endif
+}
+
+gfxFloat gfxMacFont::GetCharWidth(CFDataRef aCmap, char16_t aUniChar,
+ uint32_t* aGlyphID, gfxFloat aConvFactor) {
+ CGGlyph glyph = 0;
+
+ if (aCmap) {
+ glyph = gfxFontUtils::MapCharToGlyph(::CFDataGetBytePtr(aCmap),
+ ::CFDataGetLength(aCmap), aUniChar);
+ }
+
+ if (aGlyphID) {
+ *aGlyphID = glyph;
+ }
+
+ if (glyph) {
+ int advance;
+ if (::CGFontGetGlyphAdvances(mCGFont, &glyph, 1, &advance)) {
+ return advance * aConvFactor;
+ }
+ }
+
+ return 0;
+}
+
+int32_t gfxMacFont::GetGlyphWidth(uint16_t aGID) {
+ if (mVariationFont) {
+ // Avoid a potential Core Text crash (bug 1450209) by using
+ // CoreGraphics glyph advance API. This is inferior for 'sbix'
+ // fonts, but those won't have variations, so it's OK.
+ int cgAdvance;
+ if (::CGFontGetGlyphAdvances(mCGFont, &aGID, 1, &cgAdvance)) {
+ return cgAdvance * mFUnitsConvFactor * 0x10000;
+ }
+ }
+
+ if (!mCTFont) {
+ bool isInstalledFont =
+ !mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont();
+ mCTFont = CreateCTFontFromCGFontWithVariations(mCGFont, mAdjustedSize,
+ isInstalledFont);
+ if (!mCTFont) { // shouldn't happen, but let's be safe
+ NS_WARNING("failed to create CTFontRef to measure glyph width");
+ return 0;
+ }
+ }
+
+ CGSize advance;
+ ::CTFontGetAdvancesForGlyphs(mCTFont, kCTFontOrientationDefault, &aGID,
+ &advance, 1);
+ return advance.width * 0x10000;
+}
+
+bool gfxMacFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) {
+ CGRect bb;
+ if (!::CGFontGetGlyphBBoxes(mCGFont, &aGID, 1, &bb)) {
+ return false;
+ }
+
+ // broken fonts can return incorrect bounds for some null characters,
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=534260
+ if (bb.origin.x == -32767 && bb.origin.y == -32767 &&
+ bb.size.width == 65534 && bb.size.height == 65534) {
+ *aBounds = gfxRect(0, 0, 0, 0);
+ return true;
+ }
+
+ gfxRect bounds(bb.origin.x, -(bb.origin.y + bb.size.height), bb.size.width,
+ bb.size.height);
+ bounds.Scale(mFUnitsConvFactor);
+ *aBounds = bounds;
+ return true;
+}
+
+// Try to initialize font metrics via platform APIs (CG/CT),
+// and set mIsValid = TRUE on success.
+// We ONLY call this for local (platform) fonts that are not sfnt format;
+// for sfnts, including ALL downloadable fonts, we prefer to use
+// InitMetricsFromSfntTables and avoid platform APIs.
+void gfxMacFont::InitMetricsFromPlatform() {
+ AutoCFRelease<CTFontRef> ctFont =
+ ::CTFontCreateWithGraphicsFont(mCGFont, mAdjustedSize, nullptr, nullptr);
+ if (!ctFont) {
+ return;
+ }
+
+ mMetrics.underlineOffset = ::CTFontGetUnderlinePosition(ctFont);
+ mMetrics.underlineSize = ::CTFontGetUnderlineThickness(ctFont);
+
+ mMetrics.externalLeading = ::CTFontGetLeading(ctFont);
+
+ mMetrics.maxAscent = ::CTFontGetAscent(ctFont);
+ mMetrics.maxDescent = ::CTFontGetDescent(ctFont);
+
+ // this is not strictly correct, but neither CTFont nor CGFont seems to
+ // provide maxAdvance, unless we were to iterate over all the glyphs
+ // (which isn't worth the cost here)
+ CGRect r = ::CTFontGetBoundingBox(ctFont);
+ mMetrics.maxAdvance = r.size.width;
+
+ // aveCharWidth is also not provided, so leave it at zero
+ // (fallback code in gfxMacFont::InitMetrics will then try measuring 'x');
+ // this could lead to less-than-"perfect" text field sizing when width is
+ // specified as a number of characters, and the font in use is a non-sfnt
+ // legacy font, but that's a sufficiently obscure edge case that we can
+ // ignore the potential discrepancy.
+ mMetrics.aveCharWidth = 0;
+
+ mMetrics.xHeight = ::CTFontGetXHeight(ctFont);
+ mMetrics.capHeight = ::CTFontGetCapHeight(ctFont);
+
+ mIsValid = true;
+}
+
+already_AddRefed<ScaledFont> gfxMacFont::GetScaledFont(
+ const TextRunDrawParams& aRunParams) {
+ if (ScaledFont* scaledFont = mAzureScaledFont) {
+ return do_AddRef(scaledFont);
+ }
+
+ gfxFontEntry* fe = GetFontEntry();
+ bool hasColorGlyphs = fe->HasColorBitmapTable() || fe->TryGetColorGlyphs();
+ RefPtr<ScaledFont> newScaledFont = Factory::CreateScaledFontForMacFont(
+ GetCGFontRef(), GetUnscaledFont(), GetAdjustedSize(),
+ ToDeviceColor(mFontSmoothingBackgroundColor),
+ !mStyle.useGrayscaleAntialiasing, ApplySyntheticBold(), hasColorGlyphs);
+ if (!newScaledFont) {
+ return nullptr;
+ }
+
+ InitializeScaledFont(newScaledFont);
+
+ if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) {
+ Unused << newScaledFont.forget();
+ }
+ ScaledFont* scaledFont = mAzureScaledFont;
+ return do_AddRef(scaledFont);
+}
+
+bool gfxMacFont::ShouldRoundXOffset(cairo_t* aCairo) const {
+ // Quartz surfaces implement show_glyphs for Quartz fonts
+ return aCairo && cairo_surface_get_type(cairo_get_target(aCairo)) !=
+ CAIRO_SURFACE_TYPE_QUARTZ;
+}
+
+bool gfxMacFont::UseNativeColrFontSupport() const {
+ /*
+ if (nsCocoaFeatures::OnHighSierraOrLater()) {
+ auto* colr = GetFontEntry()->GetCOLR();
+ if (colr && COLRFonts::GetColrTableVersion(colr) == 0) {
+ return true;
+ }
+ }
+ */
+ return false;
+}
+
+void gfxMacFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const {
+ gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+ // mCGFont is shared with the font entry, so not counted here;
+}
+
+void gfxMacFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const {
+ aSizes->mFontInstances += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
diff --git a/gfx/thebes/gfxMacFont.h b/gfx/thebes/gfxMacFont.h
new file mode 100644
index 0000000000..b1f3fb4f35
--- /dev/null
+++ b/gfx/thebes/gfxMacFont.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_MACFONT_H
+#define GFX_MACFONT_H
+
+#include "mozilla/MemoryReporting.h"
+#include "gfxFont.h"
+#include <ApplicationServices/ApplicationServices.h>
+
+#include "mozilla/gfx/UnscaledFontMac.h"
+
+class MacOSFontEntry;
+
+class gfxMacFont final : public gfxFont {
+ public:
+ gfxMacFont(const RefPtr<mozilla::gfx::UnscaledFontMac>& aUnscaledFont,
+ MacOSFontEntry* aFontEntry, const gfxFontStyle* aFontStyle);
+
+ CGFontRef GetCGFontRef() const { return mCGFont; }
+
+ /* override Measure to add padding for antialiasing */
+ RunMetrics Measure(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd,
+ BoundingBoxType aBoundingBoxType,
+ DrawTarget* aDrawTargetForTightBoundingBox,
+ Spacing* aSpacing,
+ mozilla::gfx::ShapedTextFlags aOrientation) override;
+
+ // We need to provide hinted (non-linear) glyph widths if using a font
+ // with embedded color bitmaps (Apple Color Emoji), as Core Text renders
+ // the glyphs with non-linear scaling at small pixel sizes.
+ bool ProvidesGlyphWidths() const override {
+ return mVariationFont ||
+ mFontEntry->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'));
+ }
+
+ int32_t GetGlyphWidth(uint16_t aGID) override;
+
+ bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override;
+
+ already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont(
+ const TextRunDrawParams& aRunParams) override;
+
+ bool ShouldRoundXOffset(cairo_t* aCairo) const override;
+
+ void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const override;
+ void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const override;
+
+ FontType GetType() const override { return FONT_TYPE_MAC; }
+
+ bool UseNativeColrFontSupport() const override;
+
+ protected:
+ ~gfxMacFont() override;
+
+ const Metrics& GetHorizontalMetrics() const override { return mMetrics; }
+
+ // override to prefer CoreText shaping with fonts that depend on AAT
+ bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText,
+ uint32_t aOffset, uint32_t aLength, Script aScript,
+ nsAtom* aLanguage, bool aVertical, RoundingFlags aRounding,
+ gfxShapedText* aShapedText) override;
+
+ void InitMetrics();
+ void InitMetricsFromPlatform();
+
+ // Get width and glyph ID for a character; uses aConvFactor
+ // to convert font units as returned by CG to actual dimensions
+ gfxFloat GetCharWidth(CFDataRef aCmap, char16_t aUniChar, uint32_t* aGlyphID,
+ gfxFloat aConvFactor);
+
+ // a strong reference to the CoreGraphics font
+ CGFontRef mCGFont;
+
+ // a Core Text font reference, created only if we're using CT to measure
+ // glyph widths; otherwise null.
+ CTFontRef mCTFont;
+
+ mozilla::UniquePtr<gfxFontShaper> mCoreTextShaper;
+
+ Metrics mMetrics;
+ nscolor mFontSmoothingBackgroundColor;
+
+ bool mVariationFont; // true if font has OpenType variations
+};
+
+#endif /* GFX_MACFONT_H */
diff --git a/gfx/thebes/gfxMacPlatformFontList.h b/gfx/thebes/gfxMacPlatformFontList.h
new file mode 100644
index 0000000000..518c58d3af
--- /dev/null
+++ b/gfx/thebes/gfxMacPlatformFontList.h
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef gfxMacPlatformFontList_H_
+#define gfxMacPlatformFontList_H_
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsRefPtrHashtable.h"
+
+#include "gfxPlatformFontList.h"
+#include "gfxPlatform.h"
+#include "gfxPlatformMac.h"
+
+#include "nsUnicharUtils.h"
+#include "nsTArray.h"
+#include "mozilla/LookAndFeel.h"
+
+#include "mozilla/gfx/UnscaledFontMac.h"
+
+class gfxMacPlatformFontList;
+
+// a single member of a font family (i.e. a single face, such as Times Italic)
+class MacOSFontEntry final : public gfxFontEntry {
+ public:
+ friend class gfxMacPlatformFontList;
+ friend class gfxMacFont;
+
+ MacOSFontEntry(const nsACString& aPostscriptName, WeightRange aWeight,
+ bool aIsStandardFace = false, double aSizeHint = 0.0);
+
+ // for use with data fonts
+ MacOSFontEntry(const nsACString& aPostscriptName, CGFontRef aFontRef,
+ WeightRange aWeight, StretchRange aStretch,
+ SlantStyleRange aStyle, bool aIsDataUserFont, bool aIsLocal);
+
+ virtual ~MacOSFontEntry() { ::CGFontRelease(mFontRef); }
+
+ gfxFontEntry* Clone() const override;
+
+ // Return a non-owning reference to our CGFont; caller must not release it.
+ // This will cause the fontEntry to create & retain a CGFont for the life
+ // of the entry.
+ // Note that in the case of a broken font, this could return null.
+ CGFontRef GetFontRef();
+
+ // Return a new reference to our CGFont. Caller is responsible to release
+ // this reference.
+ // (If the entry has a cached CGFont, this just bumps its refcount and
+ // returns it; if not, the instance returned will be owned solely by the
+ // caller.)
+ // Note that in the case of a broken font, this could return null.
+ CGFontRef CreateOrCopyFontRef();
+
+ // override gfxFontEntry table access function to bypass table cache,
+ // use CGFontRef API to get direct access to system font data
+ hb_blob_t* GetFontTable(uint32_t aTag) override;
+
+ void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const override;
+
+ nsresult ReadCMAP(FontInfoData* aFontInfoData = nullptr) override;
+
+ bool RequiresAATLayout() const { return mRequiresAAT; }
+
+ bool HasVariations() override;
+ void GetVariationAxes(
+ nsTArray<gfxFontVariationAxis>& aVariationAxes) override;
+ void GetVariationInstances(
+ nsTArray<gfxFontVariationInstance>& aInstances) override;
+
+ bool IsCFF();
+
+ bool SupportsOpenTypeFeature(Script aScript, uint32_t aFeatureTag) override;
+
+ protected:
+ gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle) override;
+
+ bool HasFontTable(uint32_t aTableTag) override;
+
+ static void DestroyBlobFunc(void* aUserData);
+
+ CGFontRef
+ mFontRef; // owning reference to the CGFont, released on destruction
+
+ double mSizeHint;
+
+ bool mFontRefInitialized;
+ bool mRequiresAAT;
+ bool mIsCFF;
+ bool mIsCFFInitialized;
+ bool mHasVariations;
+ bool mHasVariationsInitialized;
+ bool mHasAATSmallCaps;
+ bool mHasAATSmallCapsInitialized;
+
+ // To work around Core Text's mishandling of the default value for 'opsz',
+ // we need to record whether the font has an a optical size axis, what its
+ // range and default values are, and a usable close-to-default alternative.
+ // (See bug 1457417 for details.)
+ // These fields are used by gfxMacFont, but stored in the font entry so
+ // that only a single font instance needs to inspect the available
+ // variations.
+ gfxFontVariationAxis mOpszAxis;
+ float mAdjustedDefaultOpsz;
+
+ nsTHashtable<nsUint32HashKey> mAvailableTables;
+
+ mozilla::ThreadSafeWeakPtr<mozilla::gfx::UnscaledFontMac> mUnscaledFont;
+};
+
+class gfxMacPlatformFontList final : public gfxPlatformFontList {
+ using FontFamilyListEntry = mozilla::dom::SystemFontListEntry;
+
+ public:
+ static gfxMacPlatformFontList* PlatformFontList() {
+ return static_cast<gfxMacPlatformFontList*>(
+ gfxPlatformFontList::PlatformFontList());
+ }
+
+ gfxFontFamily* CreateFontFamily(const nsACString& aName,
+ FontVisibility aVisibility) const override;
+
+ static int32_t AppleWeightToCSSWeight(int32_t aAppleWeight);
+
+ gfxFontEntry* LookupLocalFont(nsPresContext* aPresContext,
+ const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry) override;
+
+ gfxFontEntry* MakePlatformFont(const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry,
+ const uint8_t* aFontData,
+ uint32_t aLength) override;
+
+ bool FindAndAddFamiliesLocked(
+ nsPresContext* aPresContext, mozilla::StyleGenericFontFamily aGeneric,
+ const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput,
+ FindFamiliesFlags aFlags, gfxFontStyle* aStyle = nullptr,
+ nsAtom* aLanguage = nullptr, gfxFloat aDevToCssSize = 1.0)
+ MOZ_REQUIRES(mLock) override;
+
+ // lookup the system font for a particular system font type and set
+ // the name and style characteristics
+ void LookupSystemFont(mozilla::LookAndFeel::FontID aSystemFontID,
+ nsACString& aSystemFontName, gfxFontStyle& aFontStyle);
+
+ // Values for the entryType field in FontFamilyListEntry records passed
+ // from chrome to content process.
+ enum FontFamilyEntryType {
+ kStandardFontFamily = 0, // a standard installed font family
+ kTextSizeSystemFontFamily = 1, // name of 'system' font at text sizes
+ kDisplaySizeSystemFontFamily = 2 // 'system' font at display sizes
+ };
+ void ReadSystemFontList(mozilla::dom::SystemFontList*);
+
+ protected:
+ FontFamily GetDefaultFontForPlatform(nsPresContext* aPresContext,
+ const gfxFontStyle* aStyle,
+ nsAtom* aLanguage = nullptr)
+ MOZ_REQUIRES(mLock) override;
+
+ private:
+ friend class gfxPlatformMac;
+
+ gfxMacPlatformFontList();
+ virtual ~gfxMacPlatformFontList();
+
+ // initialize font lists
+ nsresult InitFontListForPlatform() MOZ_REQUIRES(mLock) override;
+ void InitSharedFontListForPlatform() MOZ_REQUIRES(mLock) override;
+
+ // handle commonly used fonts for which the name table should be loaded at
+ // startup
+ void PreloadNamesList() MOZ_REQUIRES(mLock);
+
+ // special case font faces treated as font families (set via prefs)
+ void InitSingleFaceList() MOZ_REQUIRES(mLock);
+ void InitAliasesForSingleFaceList() MOZ_REQUIRES(mLock);
+
+ // initialize system fonts
+ void InitSystemFontNames() MOZ_REQUIRES(mLock);
+
+ // helper function to lookup in both hidden system fonts and normal fonts
+ gfxFontFamily* FindSystemFontFamily(const nsACString& aFamily)
+ MOZ_REQUIRES(mLock);
+
+ FontVisibility GetVisibilityForFamily(const nsACString& aName) const;
+
+ static void RegisteredFontsChangedNotificationCallback(
+ CFNotificationCenterRef center, void* observer, CFStringRef name,
+ const void* object, CFDictionaryRef userInfo);
+
+ // attempt to use platform-specific fallback for the given character
+ // return null if no usable result found
+ gfxFontEntry* PlatformGlobalFontFallback(nsPresContext* aPresContext,
+ const uint32_t aCh,
+ Script aRunScript,
+ const gfxFontStyle* aMatchStyle,
+ FontFamily& aMatchedFamily)
+ MOZ_REQUIRES(mLock) override;
+
+ bool UsesSystemFallback() override { return true; }
+
+ already_AddRefed<FontInfoData> CreateFontInfoData() override;
+
+ // Add the specified family to mFontFamilies.
+ // Ideally we'd use NSString* instead of CFStringRef here, but this header
+ // file is included in .cpp files, so we can't use objective C classes here.
+ // But CFStringRef and NSString* are the same thing anyway (they're
+ // toll-free bridged).
+ void AddFamily(CFStringRef aFamily) MOZ_REQUIRES(mLock);
+
+ void AddFamily(const nsACString& aFamilyName, FontVisibility aVisibility)
+ MOZ_REQUIRES(mLock);
+
+ static void ActivateFontsFromDir(
+ const nsACString& aDir,
+ nsTHashSet<nsCStringHashKey>* aLoadedFamilies = nullptr);
+
+ gfxFontEntry* CreateFontEntry(
+ mozilla::fontlist::Face* aFace,
+ const mozilla::fontlist::Family* aFamily) override;
+
+ void GetFacesInitDataForFamily(
+ const mozilla::fontlist::Family* aFamily,
+ nsTArray<mozilla::fontlist::Face::InitData>& aFaces,
+ bool aLoadCmaps) const override;
+
+ void ReadFaceNamesForFamily(mozilla::fontlist::Family* aFamily,
+ bool aNeedFullnamePostscriptNames)
+ MOZ_REQUIRES(mLock) override;
+
+#ifdef MOZ_BUNDLED_FONTS
+ void ActivateBundledFonts();
+#endif
+
+ enum { kATSGenerationInitial = -1 };
+
+ // default font for use with system-wide font fallback
+ CTFontRef mDefaultFont;
+
+ // font families that -apple-system maps to
+ // Pre-10.11 this was always a single font family, such as Lucida Grande
+ // or Helvetica Neue. For OSX 10.11, Apple uses pair of families
+ // for the UI, one for text sizes and another for display sizes
+ bool mUseSizeSensitiveSystemFont;
+ nsCString mSystemTextFontFamilyName;
+ nsCString mSystemDisplayFontFamilyName; // only used on OSX 10.11
+
+ nsTArray<nsCString> mSingleFaceFonts;
+ nsTArray<nsCString> mPreloadFonts;
+
+#ifdef MOZ_BUNDLED_FONTS
+ nsTHashSet<nsCStringHashKey> mBundledFamilies;
+#endif
+};
+
+#endif /* gfxMacPlatformFontList_H_ */
diff --git a/gfx/thebes/gfxMacPlatformFontList.mm b/gfx/thebes/gfxMacPlatformFontList.mm
new file mode 100644
index 0000000000..b95c6fa1d1
--- /dev/null
+++ b/gfx/thebes/gfxMacPlatformFontList.mm
@@ -0,0 +1,2242 @@
+/* -*- Mode: ObjC; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: BSD
+ *
+ * Copyright (C) 2006-2009 Mozilla Corporation. All rights reserved.
+ *
+ * Contributor(s):
+ * Vladimir Vukicevic <vladimir@pobox.com>
+ * Masayuki Nakano <masayuki@d-toybox.com>
+ * John Daggett <jdaggett@mozilla.com>
+ * Jonathan Kew <jfkthame@gmail.com>
+ *
+ * Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "mozilla/Logging.h"
+
+#include <algorithm>
+
+#import <AppKit/AppKit.h>
+
+#include "gfxFontConstants.h"
+#include "gfxPlatformMac.h"
+#include "gfxMacPlatformFontList.h"
+#include "gfxMacFont.h"
+#include "gfxUserFontSet.h"
+#include "SharedFontList-impl.h"
+
+#include "harfbuzz/hb.h"
+
+#include "AppleUtils.h"
+#include "MainThreadUtils.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsIDirectoryEnumerator.h"
+#include "nsCharTraits.h"
+#include "nsCocoaFeatures.h"
+#include "nsCocoaUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsTArray.h"
+
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/gfx/2D.h"
+
+#include <unistd.h>
+#include <time.h>
+#include <dlfcn.h>
+
+#include "StandardFonts-macos.inc"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+// Building with newer macOS SDKs can cause a bunch of font-family names to be hidden
+// from the Core Text API we use to enumerate available fonts. Because some content still
+// benefits from having these names recognized, we forcibly include them in the list.
+// Some day we might want to drop support for these.
+#define USE_DEPRECATED_FONT_FAMILY_NAMES 1
+
+#if USE_DEPRECATED_FONT_FAMILY_NAMES
+// List generated by diffing the arrays returned by CTFontManagerCopyAvailableFontFamilyNames()
+// when built with MACOSX_DEPLOYMENT_TARGET=10.12 vs 11.0, to identify the font family names
+// that Core Text is treating as "deprecated" and hiding from the app on newer systems.
+constexpr nsLiteralCString kDeprecatedFontFamilies[] = {
+ // Dot-prefixed font families are supposed to be hidden from the user-visible
+ // font list anyhow, so we don't need to add them here.
+ // ".Al Bayan PUA"_ns,
+ // ".Al Nile PUA"_ns,
+ // ".Al Tarikh PUA"_ns,
+ // ".Apple Color Emoji UI"_ns,
+ // ".Apple SD Gothic NeoI"_ns,
+ // ".Aqua Kana"_ns,
+ // ".Arial Hebrew Desk Interface"_ns,
+ // ".Baghdad PUA"_ns,
+ // ".Beirut PUA"_ns,
+ // ".Damascus PUA"_ns,
+ // ".DecoType Naskh PUA"_ns,
+ // ".Diwan Kufi PUA"_ns,
+ // ".Farah PUA"_ns,
+ // ".Geeza Pro Interface"_ns,
+ // ".Geeza Pro PUA"_ns,
+ // ".Helvetica LT MM"_ns,
+ // ".Hiragino Kaku Gothic Interface"_ns,
+ // ".Hiragino Sans GB Interface"_ns,
+ // ".Keyboard"_ns,
+ // ".KufiStandardGK PUA"_ns,
+ // ".LastResort"_ns,
+ // ".Lucida Grande UI"_ns,
+ // ".Muna PUA"_ns,
+ // ".Nadeem PUA"_ns,
+ // ".New York"_ns,
+ // ".Noto Nastaliq Urdu UI"_ns,
+ // ".PingFang HK"_ns,
+ // ".PingFang SC"_ns,
+ // ".PingFang TC"_ns,
+ // ".Sana PUA"_ns,
+ // ".Savoye LET CC."_ns,
+ // ".SF Arabic"_ns,
+ // ".SF Compact Rounded"_ns,
+ // ".SF Compact"_ns,
+ // ".SF NS Mono"_ns,
+ // ".SF NS Rounded"_ns,
+ // ".SF NS"_ns,
+ // ".Times LT MM"_ns,
+ "Hiragino Kaku Gothic Pro"_ns,
+ "Hiragino Kaku Gothic ProN"_ns,
+ "Hiragino Kaku Gothic Std"_ns,
+ "Hiragino Kaku Gothic StdN"_ns,
+ "Hiragino Maru Gothic Pro"_ns,
+ "Hiragino Mincho Pro"_ns,
+ "Iowan Old Style"_ns,
+ "Noto Sans Adlam"_ns,
+ "Noto Sans Armenian"_ns,
+ "Noto Sans Avestan"_ns,
+ "Noto Sans Bamum"_ns,
+ "Noto Sans Bassa Vah"_ns,
+ "Noto Sans Batak"_ns,
+ "Noto Sans Bhaiksuki"_ns,
+ "Noto Sans Brahmi"_ns,
+ "Noto Sans Buginese"_ns,
+ "Noto Sans Buhid"_ns,
+ "Noto Sans Carian"_ns,
+ "Noto Sans Caucasian Albanian"_ns,
+ "Noto Sans Chakma"_ns,
+ "Noto Sans Cham"_ns,
+ "Noto Sans Coptic"_ns,
+ "Noto Sans Cuneiform"_ns,
+ "Noto Sans Cypriot"_ns,
+ "Noto Sans Duployan"_ns,
+ "Noto Sans Egyptian Hieroglyphs"_ns,
+ "Noto Sans Elbasan"_ns,
+ "Noto Sans Glagolitic"_ns,
+ "Noto Sans Gothic"_ns,
+ "Noto Sans Gunjala Gondi"_ns,
+ "Noto Sans Hanifi Rohingya"_ns,
+ "Noto Sans Hanunoo"_ns,
+ "Noto Sans Hatran"_ns,
+ "Noto Sans Imperial Aramaic"_ns,
+ "Noto Sans Inscriptional Pahlavi"_ns,
+ "Noto Sans Inscriptional Parthian"_ns,
+ "Noto Sans Javanese"_ns,
+ "Noto Sans Kaithi"_ns,
+ "Noto Sans Kayah Li"_ns,
+ "Noto Sans Kharoshthi"_ns,
+ "Noto Sans Khojki"_ns,
+ "Noto Sans Khudawadi"_ns,
+ "Noto Sans Lepcha"_ns,
+ "Noto Sans Limbu"_ns,
+ "Noto Sans Linear A"_ns,
+ "Noto Sans Linear B"_ns,
+ "Noto Sans Lisu"_ns,
+ "Noto Sans Lycian"_ns,
+ "Noto Sans Lydian"_ns,
+ "Noto Sans Mahajani"_ns,
+ "Noto Sans Mandaic"_ns,
+ "Noto Sans Manichaean"_ns,
+ "Noto Sans Marchen"_ns,
+ "Noto Sans Masaram Gondi"_ns,
+ "Noto Sans Meetei Mayek"_ns,
+ "Noto Sans Mende Kikakui"_ns,
+ "Noto Sans Meroitic"_ns,
+ "Noto Sans Miao"_ns,
+ "Noto Sans Modi"_ns,
+ "Noto Sans Mongolian"_ns,
+ "Noto Sans Mro"_ns,
+ "Noto Sans Multani"_ns,
+ "Noto Sans Nabataean"_ns,
+ "Noto Sans New Tai Lue"_ns,
+ "Noto Sans Newa"_ns,
+ "Noto Sans NKo"_ns,
+ "Noto Sans Ol Chiki"_ns,
+ "Noto Sans Old Hungarian"_ns,
+ "Noto Sans Old Italic"_ns,
+ "Noto Sans Old North Arabian"_ns,
+ "Noto Sans Old Permic"_ns,
+ "Noto Sans Old Persian"_ns,
+ "Noto Sans Old South Arabian"_ns,
+ "Noto Sans Old Turkic"_ns,
+ "Noto Sans Osage"_ns,
+ "Noto Sans Osmanya"_ns,
+ "Noto Sans Pahawh Hmong"_ns,
+ "Noto Sans Palmyrene"_ns,
+ "Noto Sans Pau Cin Hau"_ns,
+ "Noto Sans PhagsPa"_ns,
+ "Noto Sans Phoenician"_ns,
+ "Noto Sans Psalter Pahlavi"_ns,
+ "Noto Sans Rejang"_ns,
+ "Noto Sans Samaritan"_ns,
+ "Noto Sans Saurashtra"_ns,
+ "Noto Sans Sharada"_ns,
+ "Noto Sans Siddham"_ns,
+ "Noto Sans Sora Sompeng"_ns,
+ "Noto Sans Sundanese"_ns,
+ "Noto Sans Syloti Nagri"_ns,
+ "Noto Sans Syriac"_ns,
+ "Noto Sans Tagalog"_ns,
+ "Noto Sans Tagbanwa"_ns,
+ "Noto Sans Tai Le"_ns,
+ "Noto Sans Tai Tham"_ns,
+ "Noto Sans Tai Viet"_ns,
+ "Noto Sans Takri"_ns,
+ "Noto Sans Thaana"_ns,
+ "Noto Sans Tifinagh"_ns,
+ "Noto Sans Tirhuta"_ns,
+ "Noto Sans Ugaritic"_ns,
+ "Noto Sans Vai"_ns,
+ "Noto Sans Wancho"_ns,
+ "Noto Sans Warang Citi"_ns,
+ "Noto Sans Yi"_ns,
+ "Noto Sans Zawgyi"_ns,
+ "Noto Serif Ahom"_ns,
+ "Noto Serif Balinese"_ns,
+ "Noto Serif Yezidi"_ns,
+ "Athelas"_ns,
+ "Courier"_ns,
+ "Marion"_ns,
+ "Seravek"_ns,
+ "Superclarendon"_ns,
+ "Times"_ns,
+};
+#endif // USE_DEPRECATED_FONT_FAMILY_NAMES
+
+// indexes into the NSArray objects that the Cocoa font manager returns
+// as the available members of a family
+#define INDEX_FONT_POSTSCRIPT_NAME 0
+#define INDEX_FONT_FACE_NAME 1
+#define INDEX_FONT_WEIGHT 2
+#define INDEX_FONT_TRAITS 3
+
+static const int kAppleMaxWeight = 14;
+static const int kAppleExtraLightWeight = 3;
+static const int kAppleUltraLightWeight = 2;
+
+static const int gAppleWeightToCSSWeight[] = {
+ 0,
+ 1, // 1.
+ 1, // 2. W1, ultralight
+ 2, // 3. W2, extralight
+ 3, // 4. W3, light
+ 4, // 5. W4, semilight
+ 5, // 6. W5, medium
+ 6, // 7.
+ 6, // 8. W6, semibold
+ 7, // 9. W7, bold
+ 8, // 10. W8, extrabold
+ 8, // 11.
+ 9, // 12. W9, ultrabold
+ 9, // 13
+ 9 // 14
+};
+
+// cache Cocoa's "shared font manager" for performance
+static NSFontManager* sFontManager;
+
+static void GetStringForNSString(const NSString* aSrc, nsAString& aDest) {
+ aDest.SetLength([aSrc length]);
+ [aSrc getCharacters:reinterpret_cast<unichar*>(aDest.BeginWriting())
+ range:NSMakeRange(0, [aSrc length])];
+}
+
+static NSString* GetNSStringForString(const nsAString& aSrc) {
+ return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(aSrc.BeginReading())
+ length:aSrc.Length()];
+}
+
+#define LOG_FONTLIST(args) \
+ MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), mozilla::LogLevel::Debug, args)
+#define LOG_FONTLIST_ENABLED() \
+ MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), mozilla::LogLevel::Debug)
+#define LOG_CMAPDATA_ENABLED() \
+ MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_cmapdata), mozilla::LogLevel::Debug)
+
+#pragma mark -
+
+// Complex scripts will not render correctly unless appropriate AAT or OT
+// layout tables are present.
+// For OpenType, we also check that the GSUB table supports the relevant
+// script tag, to avoid using things like Arial Unicode MS for Lao (it has
+// the characters, but lacks OpenType support).
+
+// TODO: consider whether we should move this to gfxFontEntry and do similar
+// cmap-masking on other platforms to avoid using fonts that won't shape
+// properly.
+
+nsresult MacOSFontEntry::ReadCMAP(FontInfoData* aFontInfoData) {
+ // attempt this once, if errors occur leave a blank cmap
+ if (mCharacterMap || mShmemCharacterMap) {
+ return NS_OK;
+ }
+
+ RefPtr<gfxCharacterMap> charmap;
+ nsresult rv;
+
+ uint32_t uvsOffset = 0;
+ if (aFontInfoData && (charmap = GetCMAPFromFontInfo(aFontInfoData, uvsOffset))) {
+ rv = NS_OK;
+ } else {
+ uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p');
+ charmap = new gfxCharacterMap();
+ AutoTable cmapTable(this, kCMAP);
+
+ if (cmapTable) {
+ uint32_t cmapLen;
+ const uint8_t* cmapData =
+ reinterpret_cast<const uint8_t*>(hb_blob_get_data(cmapTable, &cmapLen));
+ rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, uvsOffset);
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+ mUVSOffset.exchange(uvsOffset);
+
+ if (NS_SUCCEEDED(rv) && !mIsDataUserFont && !HasGraphiteTables()) {
+ // For downloadable fonts, trust the author and don't
+ // try to munge the cmap based on script shaping support.
+
+ // We also assume a Graphite font knows what it's doing,
+ // and provides whatever shaping is needed for the
+ // characters it supports, so only check/clear the
+ // complex-script ranges for non-Graphite fonts
+
+ // for layout support, check for the presence of mort/morx/kerx and/or
+ // opentype layout tables
+ bool hasAATLayout = HasFontTable(TRUETYPE_TAG('m', 'o', 'r', 'x')) ||
+ HasFontTable(TRUETYPE_TAG('m', 'o', 'r', 't'));
+ bool hasAppleKerning = HasFontTable(TRUETYPE_TAG('k', 'e', 'r', 'x'));
+ bool hasGSUB = HasFontTable(TRUETYPE_TAG('G', 'S', 'U', 'B'));
+ bool hasGPOS = HasFontTable(TRUETYPE_TAG('G', 'P', 'O', 'S'));
+ if ((hasAATLayout && !(hasGSUB || hasGPOS)) || hasAppleKerning) {
+ mRequiresAAT = true; // prefer CoreText if font has no OTL tables,
+ // or if it uses the Apple-specific 'kerx'
+ // variant of kerning table
+ }
+
+ for (const ScriptRange* sr = gfxPlatformFontList::sComplexScriptRanges; sr->rangeStart; sr++) {
+ // check to see if the cmap includes complex script codepoints
+ if (charmap->TestRange(sr->rangeStart, sr->rangeEnd)) {
+ if (hasAATLayout) {
+ // prefer CoreText for Apple's complex-script fonts,
+ // even if they also have some OpenType tables
+ // (e.g. Geeza Pro Bold on 10.6; see bug 614903)
+ mRequiresAAT = true;
+ // and don't mask off complex-script ranges, we assume
+ // the AAT tables will provide the necessary shaping
+ continue;
+ }
+
+ // We check for GSUB here, as GPOS alone would not be ok.
+ if (hasGSUB && SupportsScriptInGSUB(sr->tags, sr->numTags)) {
+ continue;
+ }
+
+ charmap->ClearRange(sr->rangeStart, sr->rangeEnd);
+ }
+ }
+
+ // Bug 1360309, 1393624: several of Apple's Chinese fonts have spurious
+ // blank glyphs for obscure Tibetan and Arabic-script codepoints.
+ // Blocklist these so that font fallback will not use them.
+ if (mRequiresAAT &&
+ (FamilyName().EqualsLiteral("Songti SC") || FamilyName().EqualsLiteral("Songti TC") ||
+ FamilyName().EqualsLiteral("STSong") ||
+ // Bug 1390980: on 10.11, the Kaiti fonts are also affected.
+ FamilyName().EqualsLiteral("Kaiti SC") || FamilyName().EqualsLiteral("Kaiti TC") ||
+ FamilyName().EqualsLiteral("STKaiti"))) {
+ charmap->ClearRange(0x0f6b, 0x0f70);
+ charmap->ClearRange(0x0f8c, 0x0f8f);
+ charmap->clear(0x0f98);
+ charmap->clear(0x0fbd);
+ charmap->ClearRange(0x0fcd, 0x0fff);
+ charmap->clear(0x0620);
+ charmap->clear(0x065f);
+ charmap->ClearRange(0x06ee, 0x06ef);
+ charmap->clear(0x06ff);
+ }
+ }
+
+ bool setCharMap = true;
+ if (NS_SUCCEEDED(rv)) {
+ gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
+ fontlist::FontList* sharedFontList = pfl->SharedFontList();
+ if (!IsUserFont() && mShmemFace) {
+ mShmemFace->SetCharacterMap(sharedFontList, charmap); // async
+ if (TrySetShmemCharacterMap()) {
+ setCharMap = false;
+ }
+ } else {
+ charmap = pfl->FindCharMap(charmap);
+ }
+ mHasCmapTable = true;
+ } else {
+ // if error occurred, initialize to null cmap
+ charmap = new gfxCharacterMap();
+ mHasCmapTable = false;
+ }
+ if (setCharMap) {
+ // Temporarily retain charmap, until the shared version is
+ // ready for use.
+ if (mCharacterMap.compareExchange(nullptr, charmap.get())) {
+ charmap.get()->AddRef();
+ }
+ }
+
+ LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %zu hash: %8.8x%s\n", mName.get(),
+ charmap->SizeOfIncludingThis(moz_malloc_size_of), charmap->mHash,
+ mCharacterMap == charmap ? " new" : ""));
+ if (LOG_CMAPDATA_ENABLED()) {
+ char prefix[256];
+ SprintfLiteral(prefix, "(cmapdata) name: %.220s", mName.get());
+ charmap->Dump(prefix, eGfxLog_cmapdata);
+ }
+
+ return rv;
+}
+
+gfxFont* MacOSFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle) {
+ RefPtr<UnscaledFontMac> unscaledFont(mUnscaledFont);
+ if (!unscaledFont) {
+ CGFontRef baseFont = GetFontRef();
+ if (!baseFont) {
+ return nullptr;
+ }
+ unscaledFont = new UnscaledFontMac(baseFont, mIsDataUserFont);
+ mUnscaledFont = unscaledFont;
+ }
+
+ return new gfxMacFont(unscaledFont, this, aFontStyle);
+}
+
+bool MacOSFontEntry::HasVariations() {
+ if (!mHasVariationsInitialized) {
+ mHasVariationsInitialized = true;
+ mHasVariations =
+ gfxPlatform::HasVariationFontSupport() && HasFontTable(TRUETYPE_TAG('f', 'v', 'a', 'r'));
+ }
+
+ return mHasVariations;
+}
+
+void MacOSFontEntry::GetVariationAxes(nsTArray<gfxFontVariationAxis>& aVariationAxes) {
+ // We could do this by creating a CTFont and calling CTFontCopyVariationAxes,
+ // but it is expensive to instantiate a CTFont for every face just to set up
+ // the axis information.
+ // Instead we use gfxFontUtils to read the font tables directly.
+ gfxFontUtils::GetVariationData(this, &aVariationAxes, nullptr);
+}
+
+void MacOSFontEntry::GetVariationInstances(nsTArray<gfxFontVariationInstance>& aInstances) {
+ // Core Text doesn't offer API for this, so we use gfxFontUtils to read the
+ // font tables directly.
+ gfxFontUtils::GetVariationData(this, nullptr, &aInstances);
+}
+
+bool MacOSFontEntry::IsCFF() {
+ if (!mIsCFFInitialized) {
+ mIsCFFInitialized = true;
+ mIsCFF = HasFontTable(TRUETYPE_TAG('C', 'F', 'F', ' '));
+ }
+
+ return mIsCFF;
+}
+
+MacOSFontEntry::MacOSFontEntry(const nsACString& aPostscriptName, WeightRange aWeight,
+ bool aIsStandardFace, double aSizeHint)
+ : gfxFontEntry(aPostscriptName, aIsStandardFace),
+ mFontRef(NULL),
+ mSizeHint(aSizeHint),
+ mFontRefInitialized(false),
+ mRequiresAAT(false),
+ mIsCFF(false),
+ mIsCFFInitialized(false),
+ mHasVariations(false),
+ mHasVariationsInitialized(false),
+ mHasAATSmallCaps(false),
+ mHasAATSmallCapsInitialized(false) {
+ mWeightRange = aWeight;
+ mOpszAxis.mTag = 0;
+}
+
+MacOSFontEntry::MacOSFontEntry(const nsACString& aPostscriptName, CGFontRef aFontRef,
+ WeightRange aWeight, StretchRange aStretch, SlantStyleRange aStyle,
+ bool aIsDataUserFont, bool aIsLocalUserFont)
+ : gfxFontEntry(aPostscriptName, false),
+ mFontRef(NULL),
+ mSizeHint(0.0),
+ mFontRefInitialized(false),
+ mRequiresAAT(false),
+ mIsCFF(false),
+ mIsCFFInitialized(false),
+ mHasVariations(false),
+ mHasVariationsInitialized(false),
+ mHasAATSmallCaps(false),
+ mHasAATSmallCapsInitialized(false) {
+ mFontRef = aFontRef;
+ mFontRefInitialized = true;
+ ::CFRetain(mFontRef);
+
+ mWeightRange = aWeight;
+ mStretchRange = aStretch;
+ mFixedPitch = false; // xxx - do we need this for downloaded fonts?
+ mStyleRange = aStyle;
+ mOpszAxis.mTag = 0;
+
+ NS_ASSERTION(!(aIsDataUserFont && aIsLocalUserFont),
+ "userfont is either a data font or a local font");
+ mIsDataUserFont = aIsDataUserFont;
+ mIsLocalUserFont = aIsLocalUserFont;
+}
+
+gfxFontEntry* MacOSFontEntry::Clone() const {
+ MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!");
+ MacOSFontEntry* fe = new MacOSFontEntry(Name(), Weight(), mStandardFace, mSizeHint);
+ fe->mStyleRange = mStyleRange;
+ fe->mStretchRange = mStretchRange;
+ fe->mFixedPitch = mFixedPitch;
+ return fe;
+}
+
+CGFontRef MacOSFontEntry::GetFontRef() {
+ if (!mFontRefInitialized) {
+ // Cache the CGFontRef, to be released by our destructor.
+ mFontRef = CreateOrCopyFontRef();
+ mFontRefInitialized = true;
+ }
+ // Return a non-retained reference; caller does not need to release.
+ return mFontRef;
+}
+
+CGFontRef MacOSFontEntry::CreateOrCopyFontRef() {
+ if (mFontRef) {
+ // We have a cached CGFont, just add a reference. Caller must
+ // release, but we'll still own our reference.
+ ::CGFontRetain(mFontRef);
+ return mFontRef;
+ }
+
+ CrashReporter::AutoAnnotateCrashReport autoFontName(CrashReporter::Annotation::FontName, mName);
+
+ // Create a new CGFont; caller will own the only reference to it.
+ NSString* psname = GetNSStringForString(NS_ConvertUTF8toUTF16(mName));
+ CGFontRef ref = CGFontCreateWithFontName(CFStringRef(psname));
+ if (!ref) {
+ // This happens on macOS 10.12 for font entry names that start with
+ // .AppleSystemUIFont. For those fonts, we need to go through NSFont
+ // to get the correct CGFontRef.
+ // Both the Text and the Display variant of the display font use
+ // .AppleSystemUIFontSomethingSomething as their member names.
+ // That's why we're carrying along mSizeHint to this place so that
+ // we get the variant that we want for this family.
+ NSFont* font = [NSFont fontWithName:psname size:mSizeHint];
+ if (font) {
+ ref = CTFontCopyGraphicsFont((CTFontRef)font, nullptr);
+ }
+ }
+ return ref; // Not saved in mFontRef; caller will own the reference
+}
+
+// For a logging build, we wrap the CFDataRef in a FontTableRec so that we can
+// use the MOZ_COUNT_[CD]TOR macros in it. A release build without logging
+// does not get this overhead.
+class FontTableRec {
+ public:
+ explicit FontTableRec(CFDataRef aDataRef) : mDataRef(aDataRef) { MOZ_COUNT_CTOR(FontTableRec); }
+
+ ~FontTableRec() {
+ MOZ_COUNT_DTOR(FontTableRec);
+ ::CFRelease(mDataRef);
+ }
+
+ private:
+ CFDataRef mDataRef;
+};
+
+/*static*/ void MacOSFontEntry::DestroyBlobFunc(void* aUserData) {
+#ifdef NS_BUILD_REFCNT_LOGGING
+ FontTableRec* ftr = static_cast<FontTableRec*>(aUserData);
+ delete ftr;
+#else
+ ::CFRelease((CFDataRef)aUserData);
+#endif
+}
+
+hb_blob_t* MacOSFontEntry::GetFontTable(uint32_t aTag) {
+ AutoCFRelease<CGFontRef> fontRef = CreateOrCopyFontRef();
+ if (!fontRef) {
+ return nullptr;
+ }
+
+ CFDataRef dataRef = ::CGFontCopyTableForTag(fontRef, aTag);
+ if (dataRef) {
+ return hb_blob_create((const char*)::CFDataGetBytePtr(dataRef), ::CFDataGetLength(dataRef),
+ HB_MEMORY_MODE_READONLY,
+#ifdef NS_BUILD_REFCNT_LOGGING
+ new FontTableRec(dataRef),
+#else
+ (void*)dataRef,
+#endif
+ DestroyBlobFunc);
+ }
+
+ return nullptr;
+}
+
+bool MacOSFontEntry::HasFontTable(uint32_t aTableTag) {
+ if (mAvailableTables.Count() == 0) {
+ nsAutoreleasePool localPool;
+
+ AutoCFRelease<CGFontRef> fontRef = CreateOrCopyFontRef();
+ if (!fontRef) {
+ return false;
+ }
+ AutoCFRelease<CFArrayRef> tags = ::CGFontCopyTableTags(fontRef);
+ if (!tags) {
+ return false;
+ }
+ int numTags = (int)::CFArrayGetCount(tags);
+ for (int t = 0; t < numTags; t++) {
+ uint32_t tag = (uint32_t)(uintptr_t)::CFArrayGetValueAtIndex(tags, t);
+ mAvailableTables.PutEntry(tag);
+ }
+ }
+
+ return mAvailableTables.GetEntry(aTableTag);
+}
+
+static bool CheckForAATSmallCaps(CFArrayRef aFeatures) {
+ // Walk the array of feature descriptors from the font, and see whether
+ // a small-caps feature setting is available.
+ // Just bail out (returning false) if at any point we fail to find the
+ // expected dictionary keys, etc; if the font has bad data, we don't even
+ // try to search the rest of it.
+ auto numFeatures = CFArrayGetCount(aFeatures);
+ for (auto f = 0; f < numFeatures; ++f) {
+ auto featureDict = (CFDictionaryRef)CFArrayGetValueAtIndex(aFeatures, f);
+ if (!featureDict) {
+ return false;
+ }
+ auto featureNum =
+ (CFNumberRef)CFDictionaryGetValue(featureDict, CFSTR("CTFeatureTypeIdentifier"));
+ if (!featureNum) {
+ return false;
+ }
+ int16_t featureType;
+ if (!CFNumberGetValue(featureNum, kCFNumberSInt16Type, &featureType)) {
+ return false;
+ }
+ if (featureType == kLetterCaseType || featureType == kLowerCaseType) {
+ // Which selector to look for, depending whether we've found the
+ // legacy LetterCase feature or the new LowerCase one.
+ const uint16_t smallCaps =
+ (featureType == kLetterCaseType) ? kSmallCapsSelector : kLowerCaseSmallCapsSelector;
+ auto selectors =
+ (CFArrayRef)CFDictionaryGetValue(featureDict, CFSTR("CTFeatureTypeSelectors"));
+ if (!selectors) {
+ return false;
+ }
+ auto numSelectors = CFArrayGetCount(selectors);
+ for (auto s = 0; s < numSelectors; s++) {
+ auto selectorDict = (CFDictionaryRef)CFArrayGetValueAtIndex(selectors, s);
+ if (!selectorDict) {
+ return false;
+ }
+ auto selectorNum =
+ (CFNumberRef)CFDictionaryGetValue(selectorDict, CFSTR("CTFeatureSelectorIdentifier"));
+ if (!selectorNum) {
+ return false;
+ }
+ int16_t selectorValue;
+ if (!CFNumberGetValue(selectorNum, kCFNumberSInt16Type, &selectorValue)) {
+ return false;
+ }
+ if (selectorValue == smallCaps) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+bool MacOSFontEntry::SupportsOpenTypeFeature(Script aScript, uint32_t aFeatureTag) {
+ // If we're going to shape with Core Text, we don't support added
+ // OpenType features (aside from any CT applies by default), except
+ // for 'smcp' which we map to an AAT feature selector.
+ if (RequiresAATLayout()) {
+ if (aFeatureTag != HB_TAG('s', 'm', 'c', 'p')) {
+ return false;
+ }
+ if (mHasAATSmallCapsInitialized) {
+ return mHasAATSmallCaps;
+ }
+ mHasAATSmallCapsInitialized = true;
+ CGFontRef cgFont = GetFontRef();
+ if (!cgFont) {
+ return mHasAATSmallCaps;
+ }
+
+ CrashReporter::AutoAnnotateCrashReport autoFontName(CrashReporter::Annotation::FontName,
+ FamilyName());
+
+ AutoCFRelease<CTFontRef> ctFont = CTFontCreateWithGraphicsFont(cgFont, 0.0, nullptr, nullptr);
+ if (ctFont) {
+ AutoCFRelease<CFArrayRef> features = CTFontCopyFeatures(ctFont);
+ if (features) {
+ mHasAATSmallCaps = CheckForAATSmallCaps(features);
+ }
+ }
+ return mHasAATSmallCaps;
+ }
+ return gfxFontEntry::SupportsOpenTypeFeature(aScript, aFeatureTag);
+}
+
+void MacOSFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ aSizes->mFontListSize += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
+
+/* gfxMacFontFamily */
+#pragma mark -
+
+class gfxMacFontFamily final : public gfxFontFamily {
+ public:
+ gfxMacFontFamily(const nsACString& aName, FontVisibility aVisibility, double aSizeHint = 0.0)
+ : gfxFontFamily(aName, aVisibility), mSizeHint(aSizeHint) {}
+
+ gfxMacFontFamily(const nsACString& aName, NSFont* aSystemFont)
+ : gfxFontFamily(aName, FontVisibility::Unknown), mForSystemFont(aSystemFont) {
+ // I don't think the system font instance is at much risk of being deleted,
+ // but to be on the safe side let's retain a reference until we're finished
+ // using it for lazy initialization.
+ [mForSystemFont retain];
+ }
+
+ virtual ~gfxMacFontFamily() = default;
+
+ void LocalizedName(nsACString& aLocalizedName) override;
+
+ void FindStyleVariationsLocked(FontInfoData* aFontInfoData = nullptr)
+ MOZ_REQUIRES(mLock) override;
+
+ protected:
+ double mSizeHint = 0.0;
+
+ // If non-null, this is a family representing the system UI font, and should use
+ // the given NSFont as the basis for initialization as the normal font-manager APIs
+ // based on family name won't handle it.
+ NSFont* mForSystemFont = nullptr;
+};
+
+void gfxMacFontFamily::LocalizedName(nsACString& aLocalizedName) {
+ nsAutoreleasePool localPool;
+
+ // It's unsafe to call HasOtherFamilyNames off the main thread because
+ // it entrains FindStyleVariations, which calls GetWeightOverride, which
+ // retrieves prefs. And the pref names can change (via user overrides),
+ // so we can't use StaticPrefs to access them.
+ if (NS_IsMainThread() && !HasOtherFamilyNames()) {
+ aLocalizedName = mName;
+ return;
+ }
+
+ NSString* family = GetNSStringForString(NS_ConvertUTF8toUTF16(mName));
+ NSString* localized = [sFontManager localizedNameForFamily:family face:nil];
+
+ if (localized) {
+ nsAutoString locName;
+ GetStringForNSString(localized, locName);
+ CopyUTF16toUTF8(locName, aLocalizedName);
+ return;
+ }
+
+ // failed to get localized name, just use the canonical one
+ aLocalizedName = mName;
+}
+
+// Return the CSS weight value to use for the given face, overriding what
+// AppKit gives us (used to adjust families with bad weight values, see
+// bug 931426).
+// A return value of 0 indicates no override - use the existing weight.
+static inline int GetWeightOverride(const nsAString& aPSName) {
+ nsAutoCString prefName("font.weight-override.");
+ // The PostScript name is required to be ASCII; if it's not, the font is
+ // broken anyway, so we really don't care that this is lossy.
+ LossyAppendUTF16toASCII(aPSName, prefName);
+ return Preferences::GetInt(prefName.get(), 0);
+}
+
+void gfxMacFontFamily::FindStyleVariationsLocked(FontInfoData* aFontInfoData) {
+ if (mHasStyles) {
+ return;
+ }
+
+ AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("gfxMacFontFamily::FindStyleVariations", LAYOUT, mName);
+
+ nsAutoreleasePool localPool;
+
+ if (mForSystemFont) {
+ MOZ_ASSERT(gfxPlatform::HasVariationFontSupport());
+
+ auto addToFamily = [&](NSFont* aNSFont) MOZ_REQUIRES(mLock) {
+ NSString* psNameNS = [[aNSFont fontDescriptor] postscriptName];
+ nsAutoString nameUTF16;
+ nsAutoCString psName;
+ nsCocoaUtils::GetStringForNSString(psNameNS, nameUTF16);
+ CopyUTF16toUTF8(nameUTF16, psName);
+
+ auto* fe = new MacOSFontEntry(psName, WeightRange(FontWeight::NORMAL), true, 0.0);
+
+ // Set the appropriate style, assuming it may not have a variation range.
+ fe->mStyleRange = SlantStyleRange(
+ ([[aNSFont fontDescriptor] symbolicTraits] & NSFontItalicTrait) ? FontSlantStyle::ITALIC
+ : FontSlantStyle::NORMAL);
+
+ // Set up weight (and width, if present) ranges.
+ fe->SetupVariationRanges();
+ AddFontEntryLocked(fe);
+ };
+
+ addToFamily(mForSystemFont);
+
+ // See if there is a corresponding italic face, and add it to the family.
+ NSFont* italicFont = [sFontManager convertFont:mForSystemFont toHaveTrait:NSItalicFontMask];
+ if (italicFont != mForSystemFont) {
+ addToFamily(italicFont);
+ }
+
+ [mForSystemFont release];
+ mForSystemFont = nullptr;
+ SetHasStyles(true);
+
+ return;
+ }
+
+ NSString* family = GetNSStringForString(NS_ConvertUTF8toUTF16(mName));
+
+ // create a font entry for each face
+ NSArray* fontfaces = [sFontManager
+ availableMembersOfFontFamily:family]; // returns an array of [psname, style name, weight,
+ // traits] elements, goofy api
+ int faceCount = [fontfaces count];
+ int faceIndex;
+
+ for (faceIndex = 0; faceIndex < faceCount; faceIndex++) {
+ NSArray* face = [fontfaces objectAtIndex:faceIndex];
+ NSString* psname = [face objectAtIndex:INDEX_FONT_POSTSCRIPT_NAME];
+ int32_t appKitWeight = [[face objectAtIndex:INDEX_FONT_WEIGHT] unsignedIntValue];
+ uint32_t macTraits = [[face objectAtIndex:INDEX_FONT_TRAITS] unsignedIntValue];
+ NSString* facename = [face objectAtIndex:INDEX_FONT_FACE_NAME];
+ bool isStandardFace = false;
+
+ if (appKitWeight == kAppleExtraLightWeight) {
+ // if the facename contains UltraLight, set the weight to the ultralight weight value
+ NSRange range = [facename rangeOfString:@"ultralight" options:NSCaseInsensitiveSearch];
+ if (range.location != NSNotFound) {
+ appKitWeight = kAppleUltraLightWeight;
+ }
+ }
+
+ // make a nsString
+ nsAutoString postscriptFontName;
+ GetStringForNSString(psname, postscriptFontName);
+
+ int32_t cssWeight = gfxMacPlatformFontList::AppleWeightToCSSWeight(appKitWeight);
+ // If we are on the startup-time InitFontList thread, we skip this as it
+ // wants to retrieve faces for the default font family, but cannot safely
+ // call the Preferences APIs. Fortunately, the default font doesn't actually
+ // depend on a weight override pref.
+ if (!gfxPlatformFontList::IsInitFontListThread()) {
+ int32_t weightOverride = GetWeightOverride(postscriptFontName);
+ if (weightOverride) {
+ // scale down and clamp, to get a value from 1..9
+ cssWeight = ((weightOverride + 50) / 100);
+ cssWeight = std::max(1, std::min(cssWeight, 9));
+ }
+ }
+ cssWeight *= 100; // scale up to CSS values
+
+ if ([facename isEqualToString:@"Regular"] || [facename isEqualToString:@"Bold"] ||
+ [facename isEqualToString:@"Italic"] || [facename isEqualToString:@"Oblique"] ||
+ [facename isEqualToString:@"Bold Italic"] || [facename isEqualToString:@"Bold Oblique"]) {
+ isStandardFace = true;
+ }
+
+ // create a font entry
+ MacOSFontEntry* fontEntry =
+ new MacOSFontEntry(NS_ConvertUTF16toUTF8(postscriptFontName),
+ WeightRange(FontWeight::FromInt(cssWeight)), isStandardFace, mSizeHint);
+ if (!fontEntry) {
+ break;
+ }
+
+ // set additional properties based on the traits reported by Cocoa
+ if (macTraits & (NSCondensedFontMask | NSNarrowFontMask | NSCompressedFontMask)) {
+ fontEntry->mStretchRange = StretchRange(FontStretch::CONDENSED);
+ } else if (macTraits & NSExpandedFontMask) {
+ fontEntry->mStretchRange = StretchRange(FontStretch::EXPANDED);
+ }
+ // Cocoa fails to set the Italic traits bit for HelveticaLightItalic,
+ // at least (see bug 611855), so check for style name endings as well
+ if ((macTraits & NSItalicFontMask) || [facename hasSuffix:@"Italic"] ||
+ [facename hasSuffix:@"Oblique"]) {
+ fontEntry->mStyleRange = SlantStyleRange(FontSlantStyle::ITALIC);
+ }
+ if (macTraits & NSFixedPitchFontMask) {
+ fontEntry->mFixedPitch = true;
+ }
+
+ if (gfxPlatform::HasVariationFontSupport()) {
+ fontEntry->SetupVariationRanges();
+ }
+
+ if (LOG_FONTLIST_ENABLED()) {
+ nsAutoCString weightString;
+ fontEntry->Weight().ToString(weightString);
+ nsAutoCString stretchString;
+ fontEntry->Stretch().ToString(stretchString);
+ LOG_FONTLIST(("(fontlist) added (%s) to family (%s)"
+ " with style: %s weight: %s stretch: %s"
+ " (apple-weight: %d macTraits: %8.8x)",
+ fontEntry->Name().get(), Name().get(),
+ fontEntry->IsItalic() ? "italic" : "normal", weightString.get(),
+ stretchString.get(), appKitWeight, macTraits));
+ }
+
+ // insert into font entry array of family
+ AddFontEntryLocked(fontEntry);
+ }
+
+ SortAvailableFonts();
+ SetHasStyles(true);
+
+ if (mIsBadUnderlineFamily) {
+ SetBadUnderlineFonts();
+ }
+
+ CheckForSimpleFamily();
+}
+
+/* gfxSingleFaceMacFontFamily */
+#pragma mark -
+
+class gfxSingleFaceMacFontFamily final : public gfxFontFamily {
+ public:
+ gfxSingleFaceMacFontFamily(const nsACString& aName, FontVisibility aVisibility)
+ : gfxFontFamily(aName, aVisibility) {
+ mFaceNamesInitialized = true; // omit from face name lists
+ }
+
+ virtual ~gfxSingleFaceMacFontFamily() = default;
+
+ void FindStyleVariationsLocked(FontInfoData* aFontInfoData = nullptr)
+ MOZ_REQUIRES(mLock) override{};
+
+ void LocalizedName(nsACString& aLocalizedName) override;
+
+ void ReadOtherFamilyNames(gfxPlatformFontList* aPlatformFontList) override;
+
+ bool IsSingleFaceFamily() const override { return true; }
+};
+
+void gfxSingleFaceMacFontFamily::LocalizedName(nsACString& aLocalizedName) {
+ nsAutoreleasePool localPool;
+
+ AutoReadLock lock(mLock);
+
+ if (!HasOtherFamilyNames()) {
+ aLocalizedName = mName;
+ return;
+ }
+
+ gfxFontEntry* fe = mAvailableFonts[0];
+ NSFont* font = [NSFont fontWithName:GetNSStringForString(NS_ConvertUTF8toUTF16(fe->Name()))
+ size:0.0];
+ if (font) {
+ NSString* localized = [font displayName];
+ if (localized) {
+ nsAutoString locName;
+ GetStringForNSString(localized, locName);
+ CopyUTF16toUTF8(locName, aLocalizedName);
+ return;
+ }
+ }
+
+ // failed to get localized name, just use the canonical one
+ aLocalizedName = mName;
+}
+
+void gfxSingleFaceMacFontFamily::ReadOtherFamilyNames(gfxPlatformFontList* aPlatformFontList) {
+ AutoWriteLock lock(mLock);
+ if (mOtherFamilyNamesInitialized) {
+ return;
+ }
+
+ gfxFontEntry* fe = mAvailableFonts[0];
+ if (!fe) {
+ return;
+ }
+
+ const uint32_t kNAME = TRUETYPE_TAG('n', 'a', 'm', 'e');
+
+ gfxFontEntry::AutoTable nameTable(fe, kNAME);
+ if (!nameTable) {
+ return;
+ }
+
+ mHasOtherFamilyNames = ReadOtherFamilyNamesForFace(aPlatformFontList, nameTable, true);
+
+ mOtherFamilyNamesInitialized = true;
+}
+
+/* gfxMacPlatformFontList */
+#pragma mark -
+
+gfxMacPlatformFontList::gfxMacPlatformFontList()
+ : gfxPlatformFontList(false), mDefaultFont(nullptr), mUseSizeSensitiveSystemFont(false) {
+ CheckFamilyList(kBaseFonts);
+
+#ifdef MOZ_BUNDLED_FONTS
+ // We activate bundled fonts if the pref is > 0 (on) or < 0 (auto), only an
+ // explicit value of 0 (off) will disable them.
+ if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) {
+ TimeStamp start = TimeStamp::Now();
+ ActivateBundledFonts();
+ TimeStamp end = TimeStamp::Now();
+ Telemetry::Accumulate(Telemetry::FONTLIST_BUNDLEDFONTS_ACTIVATE,
+ (end - start).ToMilliseconds());
+ }
+#endif
+
+ // cache this in a static variable so that MacOSFontFamily objects
+ // don't have to repeatedly look it up
+ sFontManager = [NSFontManager sharedFontManager];
+
+ // Load the font-list preferences now, so that we don't have to do it from
+ // Init[Shared]FontListForPlatform, which may be called off-main-thread.
+ gfxFontUtils::GetPrefsFontList("font.single-face-list", mSingleFaceFonts);
+ gfxFontUtils::GetPrefsFontList("font.preload-names-list", mPreloadFonts);
+}
+
+gfxMacPlatformFontList::~gfxMacPlatformFontList() {
+ AutoLock lock(mLock);
+
+ if (XRE_IsParentProcess()) {
+ ::CFNotificationCenterRemoveObserver(::CFNotificationCenterGetLocalCenter(), this,
+ kCTFontManagerRegisteredFontsChangedNotification, 0);
+ }
+
+ if (mDefaultFont) {
+ ::CFRelease(mDefaultFont);
+ }
+}
+
+void gfxMacPlatformFontList::AddFamily(const nsACString& aFamilyName, FontVisibility aVisibility) {
+ double sizeHint = 0.0;
+ if (aVisibility == FontVisibility::Hidden && mUseSizeSensitiveSystemFont &&
+ mSystemDisplayFontFamilyName.Equals(aFamilyName)) {
+ sizeHint = 128.0;
+ }
+
+ nsAutoCString key;
+ ToLowerCase(aFamilyName, key);
+
+ RefPtr<gfxFontFamily> familyEntry = new gfxMacFontFamily(aFamilyName, aVisibility, sizeHint);
+ mFontFamilies.InsertOrUpdate(key, RefPtr{familyEntry});
+
+ // check the bad underline blocklist
+ if (mBadUnderlineFamilyNames.ContainsSorted(key)) {
+ familyEntry->SetBadUnderlineFamily();
+ }
+}
+
+FontVisibility gfxMacPlatformFontList::GetVisibilityForFamily(const nsACString& aName) const {
+ if (aName[0] == '.' || aName.LowerCaseEqualsLiteral("lastresort")) {
+ return FontVisibility::Hidden;
+ }
+ if (FamilyInList(aName, kBaseFonts)) {
+ return FontVisibility::Base;
+ }
+#ifdef MOZ_BUNDLED_FONTS
+ if (mBundledFamilies.Contains(aName)) {
+ return FontVisibility::Base;
+ }
+#endif
+ return FontVisibility::User;
+}
+
+void gfxMacPlatformFontList::AddFamily(CFStringRef aFamily) {
+ NSString* family = (NSString*)aFamily;
+
+ // CTFontManager includes weird internal family names and
+ // LastResort, skip over those
+ if (!family || [family caseInsensitiveCompare:@"LastResort"] == NSOrderedSame ||
+ [family caseInsensitiveCompare:@".LastResort"] == NSOrderedSame) {
+ return;
+ }
+
+ nsAutoString familyName;
+ nsCocoaUtils::GetStringForNSString(family, familyName);
+
+ NS_ConvertUTF16toUTF8 nameUtf8(familyName);
+ AddFamily(nameUtf8, GetVisibilityForFamily(nameUtf8));
+}
+
+/* static */
+void gfxMacPlatformFontList::ActivateFontsFromDir(const nsACString& aDir,
+ nsTHashSet<nsCStringHashKey>* aLoadedFamilies) {
+ AutoCFRelease<CFURLRef> directory = CFURLCreateFromFileSystemRepresentation(
+ kCFAllocatorDefault, (const UInt8*)nsPromiseFlatCString(aDir).get(), aDir.Length(), true);
+ if (!directory) {
+ return;
+ }
+ AutoCFRelease<CFURLEnumeratorRef> enumerator = CFURLEnumeratorCreateForDirectoryURL(
+ kCFAllocatorDefault, directory, kCFURLEnumeratorDefaultBehavior, nullptr);
+ if (!enumerator) {
+ return;
+ }
+ AutoCFRelease<CFMutableArrayRef> urls =
+ ::CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+ if (!urls) {
+ return;
+ }
+
+ CFURLRef url;
+ CFURLEnumeratorResult result;
+ do {
+ result = CFURLEnumeratorGetNextURL(enumerator, &url, nullptr);
+ if (result != kCFURLEnumeratorSuccess) {
+ continue;
+ }
+ CFArrayAppendValue(urls, url);
+
+ if (!aLoadedFamilies) {
+ continue;
+ }
+ AutoCFRelease<CFArrayRef> descriptors = CTFontManagerCreateFontDescriptorsFromURL(url);
+ if (!descriptors || !CFArrayGetCount(descriptors)) {
+ continue;
+ }
+ CTFontDescriptorRef desc = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descriptors, 0);
+ AutoCFRelease<CFStringRef> name =
+ (CFStringRef)CTFontDescriptorCopyAttribute(desc, kCTFontFamilyNameAttribute);
+ nsAutoCString key;
+ key.SetLength((CFStringGetLength(name) + 1) * 3);
+ if (CFStringGetCString(name, key.BeginWriting(), key.Length(), kCFStringEncodingUTF8)) {
+ key.SetLength(strlen(key.get()));
+ aLoadedFamilies->Insert(key);
+ }
+ } while (result != kCFURLEnumeratorEnd);
+
+ CTFontManagerRegisterFontsForURLs(urls, kCTFontManagerScopeProcess, nullptr);
+}
+
+void gfxMacPlatformFontList::ReadSystemFontList(dom::SystemFontList* aList)
+ MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ // Note: We rely on the records for mSystemTextFontFamilyName and
+ // mSystemDisplayFontFamilyName (if present) being *before* the main
+ // font list, so that those names are known in the content process
+ // by the time we add the actual family records to the font list.
+ aList->entries().AppendElement(FontFamilyListEntry(
+ mSystemTextFontFamilyName, FontVisibility::Unknown, kTextSizeSystemFontFamily));
+ if (mUseSizeSensitiveSystemFont) {
+ aList->entries().AppendElement(FontFamilyListEntry(
+ mSystemDisplayFontFamilyName, FontVisibility::Unknown, kDisplaySizeSystemFontFamily));
+ }
+ // Now collect the list of available families, with visibility attributes.
+ for (auto f = mFontFamilies.Iter(); !f.Done(); f.Next()) {
+ auto macFamily = f.Data().get();
+ if (macFamily->IsSingleFaceFamily()) {
+ continue; // skip, this will be recreated separately in the child
+ }
+ aList->entries().AppendElement(
+ FontFamilyListEntry(macFamily->Name(), macFamily->Visibility(), kStandardFontFamily));
+ }
+}
+
+void gfxMacPlatformFontList::PreloadNamesList() {
+ uint32_t numFonts = mPreloadFonts.Length();
+ for (uint32_t i = 0; i < numFonts; i++) {
+ nsAutoCString key;
+ GenerateFontListKey(mPreloadFonts[i], key);
+
+ // only search canonical names!
+ gfxFontFamily* familyEntry = mFontFamilies.GetWeak(key);
+ if (familyEntry) {
+ familyEntry->ReadOtherFamilyNames(this);
+ }
+ }
+}
+
+#if USE_DEPRECATED_FONT_FAMILY_NAMES
+static bool DeprecatedFamilyIsAvailable(const nsACString& aName) {
+ NSString* family = GetNSStringForString(NS_ConvertUTF8toUTF16(aName));
+ return [[sFontManager availableMembersOfFontFamily:family] count] > 0;
+}
+#endif
+
+nsresult gfxMacPlatformFontList::InitFontListForPlatform() {
+ nsAutoreleasePool localPool;
+
+ // The font registration thread was created early in startup, to give the
+ // system a head start on activating all the supplemental-language fonts.
+ // Here, we need to wait until it has finished its work.
+ gfxPlatformMac::WaitForFontRegistration();
+
+ Telemetry::AutoTimer<Telemetry::MAC_INITFONTLIST_TOTAL> timer;
+
+ InitSystemFontNames();
+
+ if (XRE_IsParentProcess()) {
+ static bool firstTime = true;
+ if (firstTime) {
+ ::CFNotificationCenterAddObserver(::CFNotificationCenterGetLocalCenter(), this,
+ RegisteredFontsChangedNotificationCallback,
+ kCTFontManagerRegisteredFontsChangedNotification, 0,
+ CFNotificationSuspensionBehaviorDeliverImmediately);
+ firstTime = false;
+ }
+
+ // We're not a content process, so get the available fonts directly
+ // from Core Text.
+ AutoCFRelease<CFArrayRef> familyNames = CTFontManagerCopyAvailableFontFamilyNames();
+ for (NSString* familyName in (NSArray*)(CFArrayRef)familyNames) {
+ AddFamily((CFStringRef)familyName);
+ }
+#if USE_DEPRECATED_FONT_FAMILY_NAMES
+ for (const auto& name : kDeprecatedFontFamilies) {
+ if (DeprecatedFamilyIsAvailable(name)) {
+ AddFamily(name, GetVisibilityForFamily(name));
+ }
+ }
+#endif
+ } else {
+ // Content process: use font list passed from the chrome process via
+ // the GetXPCOMProcessAttributes message, because it's much faster than
+ // querying Core Text again in the child.
+ auto& fontList = dom::ContentChild::GetSingleton()->SystemFontList();
+ for (FontFamilyListEntry& ffe : fontList.entries()) {
+ switch (ffe.entryType()) {
+ case kStandardFontFamily:
+ // On Catalina or later, we pre-initialize system font-family entries
+ // in InitSystemFontNames(), so we can just skip them here.
+ if (nsCocoaFeatures::OnCatalinaOrLater() &&
+ (ffe.familyName() == mSystemTextFontFamilyName ||
+ ffe.familyName() == mSystemDisplayFontFamilyName)) {
+ continue;
+ }
+ AddFamily(ffe.familyName(), ffe.visibility());
+ break;
+ case kTextSizeSystemFontFamily:
+ mSystemTextFontFamilyName = ffe.familyName();
+ break;
+ case kDisplaySizeSystemFontFamily:
+ mSystemDisplayFontFamilyName = ffe.familyName();
+ mUseSizeSensitiveSystemFont = true;
+ break;
+ }
+ }
+ fontList.entries().Clear();
+ }
+
+ InitSingleFaceList();
+
+ // to avoid full search of font name tables, seed the other names table with localized names from
+ // some of the prefs fonts which are accessed via their localized names. changes in the pref
+ // fonts will only cause a font lookup miss earlier. this is a simple optimization, it's not
+ // required for correctness
+ PreloadNamesList();
+
+ // start the delayed cmap loader
+ GetPrefsAndStartLoader();
+
+ return NS_OK;
+}
+
+void gfxMacPlatformFontList::InitSharedFontListForPlatform() {
+ nsAutoreleasePool localPool;
+
+ gfxPlatformMac::WaitForFontRegistration();
+
+ InitSystemFontNames();
+
+ if (XRE_IsParentProcess()) {
+ // Only the parent process listens for OS font-changed notifications;
+ // after rebuilding its list, it will update the content processes.
+ static bool firstTime = true;
+ if (firstTime) {
+ ::CFNotificationCenterAddObserver(::CFNotificationCenterGetLocalCenter(), this,
+ RegisteredFontsChangedNotificationCallback,
+ kCTFontManagerRegisteredFontsChangedNotification, 0,
+ CFNotificationSuspensionBehaviorDeliverImmediately);
+ firstTime = false;
+ }
+
+ AutoCFRelease<CFArrayRef> familyNames = CTFontManagerCopyAvailableFontFamilyNames();
+ nsTArray<fontlist::Family::InitData> families;
+ families.SetCapacity(CFArrayGetCount(familyNames)
+#if USE_DEPRECATED_FONT_FAMILY_NAMES
+ + ArrayLength(kDeprecatedFontFamilies)
+#endif
+ );
+ for (NSString* familyName in (NSArray*)(CFArrayRef)familyNames) {
+ nsAutoString name16;
+ GetStringForNSString(familyName, name16);
+ NS_ConvertUTF16toUTF8 name(name16);
+ nsAutoCString key;
+ GenerateFontListKey(name, key);
+ families.AppendElement(fontlist::Family::InitData(key, name, fontlist::Family::kNoIndex,
+ GetVisibilityForFamily(name)));
+ }
+#if USE_DEPRECATED_FONT_FAMILY_NAMES
+ for (const nsACString& name : kDeprecatedFontFamilies) {
+ if (DeprecatedFamilyIsAvailable(name)) {
+ nsAutoCString key;
+ GenerateFontListKey(name, key);
+ families.AppendElement(fontlist::Family::InitData(key, name, fontlist::Family::kNoIndex,
+ GetVisibilityForFamily(name)));
+ }
+ }
+#endif
+ SharedFontList()->SetFamilyNames(families);
+ InitAliasesForSingleFaceList();
+ GetPrefsAndStartLoader();
+ }
+}
+
+void gfxMacPlatformFontList::InitAliasesForSingleFaceList() {
+ for (const auto& familyName : mSingleFaceFonts) {
+ LOG_FONTLIST(("(fontlist-singleface) face name: %s\n", familyName.get()));
+ // Each entry in the "single face families" list is expected to be a
+ // colon-separated pair of FaceName:Family,
+ // where FaceName is the individual face name (psname) of a font
+ // that should be exposed as a separate family name,
+ // and Family is the standard family to which that face belongs.
+ // The only such face listed by default is
+ // Osaka-Mono:Osaka
+ auto colon = familyName.FindChar(':');
+ if (colon == kNotFound) {
+ continue;
+ }
+
+ // Look for the parent family in the main font family list,
+ // and ensure we have loaded its list of available faces.
+ nsAutoCString key;
+ GenerateFontListKey(Substring(familyName, colon + 1), key);
+ fontlist::Family* family = SharedFontList()->FindFamily(key);
+ if (!family) {
+ // The parent family is not present, so just ignore this entry.
+ continue;
+ }
+ if (!family->IsInitialized()) {
+ if (!gfxPlatformFontList::InitializeFamily(family)) {
+ // This shouldn't ever fail, but if it does, we can safely ignore it.
+ MOZ_ASSERT(false, "failed to initialize font family");
+ continue;
+ }
+ }
+
+ // Truncate the entry from prefs at the colon, so now it is just the
+ // desired single-face-family name.
+ nsAutoCString aliasName(Substring(familyName, 0, colon));
+
+ // Look through the family's faces to see if this one is present.
+ fontlist::FontList* list = SharedFontList();
+ const fontlist::Pointer* facePtrs = family->Faces(list);
+ for (size_t i = 0; i < family->NumFaces(); i++) {
+ if (facePtrs[i].IsNull()) {
+ continue;
+ }
+ auto* face = facePtrs[i].ToPtr<const fontlist::Face>(list);
+ if (face->mDescriptor.AsString(list).Equals(aliasName)) {
+ // Found it! Create an entry in the Alias table.
+ GenerateFontListKey(aliasName, key);
+ if (SharedFontList()->FindFamily(key) || mAliasTable.Get(key)) {
+ // If the family name is already known, something's misconfigured;
+ // just ignore it.
+ MOZ_ASSERT(false, "single-face family already known");
+ break;
+ }
+ auto aliasData = mAliasTable.GetOrInsertNew(key);
+ // The "alias" here isn't based on an existing family, so we don't call
+ // aliasData->InitFromFamily(); the various flags are left as defaults.
+ aliasData->mFaces.AppendElement(facePtrs[i]);
+ aliasData->mBaseFamily = aliasName;
+ aliasData->mVisibility = family->Visibility();
+ break;
+ }
+ }
+ }
+ if (!mAliasTable.IsEmpty()) {
+ // This will be updated when the font loader completes, but we require
+ // at least the Osaka-Mono alias to be available immediately.
+ SharedFontList()->SetAliases(mAliasTable);
+ }
+}
+
+void gfxMacPlatformFontList::InitSingleFaceList() {
+ for (const auto& familyName : mSingleFaceFonts) {
+ LOG_FONTLIST(("(fontlist-singleface) face name: %s\n", familyName.get()));
+ // Each entry in the "single face families" list is expected to be a
+ // colon-separated pair of FaceName:Family,
+ // where FaceName is the individual face name (psname) of a font
+ // that should be exposed as a separate family name,
+ // and Family is the standard family to which that face belongs.
+ // The only such face listed by default is
+ // Osaka-Mono:Osaka
+ auto colon = familyName.FindChar(':');
+ if (colon == kNotFound) {
+ continue;
+ }
+
+ // Look for the parent family in the main font family list,
+ // and ensure we have loaded its list of available faces.
+ nsAutoCString key(Substring(familyName, colon + 1));
+ ToLowerCase(key);
+ gfxFontFamily* family = mFontFamilies.GetWeak(key);
+ if (!family || family->IsHidden()) {
+ continue;
+ }
+ family->FindStyleVariations();
+
+ // Truncate the entry from prefs at the colon, so now it is just the
+ // desired single-face-family name.
+ nsAutoCString aliasName(Substring(familyName, 0, colon));
+
+ // Look through the family's faces to see if this one is present.
+ const gfxFontEntry* fe = nullptr;
+ family->ReadLock();
+ for (const auto& face : family->GetFontList()) {
+ if (face->Name().Equals(aliasName)) {
+ fe = face;
+ break;
+ }
+ }
+ family->ReadUnlock();
+ if (!fe) {
+ continue;
+ }
+
+ // We found the correct face, so create the single-face family record.
+ GenerateFontListKey(aliasName, key);
+ LOG_FONTLIST(("(fontlist-singleface) family name: %s, key: %s\n", aliasName.get(), key.get()));
+
+ // add only if doesn't exist already
+ if (!mFontFamilies.GetWeak(key)) {
+ RefPtr<gfxFontFamily> familyEntry =
+ new gfxSingleFaceMacFontFamily(aliasName, family->Visibility());
+ // We need a separate font entry, because its family name will
+ // differ from the one we found in the main list.
+ MacOSFontEntry* fontEntry = new MacOSFontEntry(
+ fe->Name(), fe->Weight(), true, static_cast<const MacOSFontEntry*>(fe)->mSizeHint);
+ familyEntry->AddFontEntry(fontEntry);
+ familyEntry->SetHasStyles(true);
+ mFontFamilies.InsertOrUpdate(key, std::move(familyEntry));
+ LOG_FONTLIST(
+ ("(fontlist-singleface) added new family: %s, key: %s\n", aliasName.get(), key.get()));
+ }
+ }
+}
+
+// System fonts under OSX may contain weird "meta" names but if we create
+// a new font using just the Postscript name, the NSFont api returns an object
+// with the actual real family name. For example, under OSX 10.11:
+//
+// [[NSFont menuFontOfSize:8.0] familyName] ==> .AppleSystemUIFont
+// [[NSFont fontWithName:[[[NSFont menuFontOfSize:8.0] fontDescriptor] postscriptName]
+// size:8.0] familyName] ==> .SF NS Text
+
+static NSString* GetRealFamilyName(NSFont* aFont) {
+ NSString* psName = [[aFont fontDescriptor] postscriptName];
+ // With newer macOS versions and SDKs (e.g. when compiled against SDK 10.15),
+ // [NSFont fontWithName:] fails for hidden system fonts, because the underlying
+ // Core Text functions it uses reject such names and tell us to use the special
+ // CTFontCreateUIFontForLanguage API instead.
+ // To work around this, as we don't yet work directly with the CTFontUIFontType
+ // identifiers, we create a Core Graphics font (as it doesn't reject system font
+ // names), and use this to create a Core Text font that we can query for the
+ // family name.
+ // Eventually we should move to using CTFontUIFontType constants to identify
+ // system fonts, and eliminate the need to instantiate them (indirectly) from
+ // their postscript names.
+ AutoCFRelease<CGFontRef> cgFont = CGFontCreateWithFontName(CFStringRef(psName));
+ if (!cgFont) {
+ return [aFont familyName];
+ }
+
+ AutoCFRelease<CTFontRef> ctFont = CTFontCreateWithGraphicsFont(cgFont, 0.0, nullptr, nullptr);
+ if (!ctFont) {
+ return [aFont familyName];
+ }
+ NSString* familyName = (NSString*)CTFontCopyFamilyName(ctFont);
+
+ return [familyName autorelease];
+}
+
+// System fonts under OSX 10.11 use a combination of two families, one
+// for text sizes and another for larger, display sizes. Each has a
+// different number of weights. There aren't efficient API's for looking
+// this information up, so hard code the logic here but confirm via
+// debug assertions that the logic is correct.
+
+const CGFloat kTextDisplayCrossover = 20.0; // use text family below this size
+
+void gfxMacPlatformFontList::InitSystemFontNames() {
+ // On Catalina+, the system font uses optical sizing rather than individual
+ // faces, so we don't need to look for a separate display-sized face.
+ mUseSizeSensitiveSystemFont = !nsCocoaFeatures::OnCatalinaOrLater();
+
+ // text font family
+ NSFont* sys = [NSFont systemFontOfSize:0.0];
+ NSString* textFamilyName = GetRealFamilyName(sys);
+ nsAutoString familyName;
+ nsCocoaUtils::GetStringForNSString(textFamilyName, familyName);
+ CopyUTF16toUTF8(familyName, mSystemTextFontFamilyName);
+
+ // On Catalina or later, we store an in-process gfxFontFamily for the system font
+ // even if using the shared fontlist to manage "normal" fonts, because the hidden
+ // system fonts may be excluded from the font list altogether.
+ if (nsCocoaFeatures::OnCatalinaOrLater()) {
+ // This family will be populated based on the given NSFont.
+ RefPtr<gfxFontFamily> fam = new gfxMacFontFamily(mSystemTextFontFamilyName, sys);
+ if (fam) {
+ nsAutoCString key;
+ GenerateFontListKey(mSystemTextFontFamilyName, key);
+ mFontFamilies.InsertOrUpdate(key, std::move(fam));
+ }
+ }
+
+ // display font family, if on OSX 10.11 - 10.14
+ if (mUseSizeSensitiveSystemFont) {
+ NSFont* displaySys = [NSFont systemFontOfSize:128.0];
+ NSString* displayFamilyName = GetRealFamilyName(displaySys);
+ if ([displayFamilyName isEqualToString:textFamilyName]) {
+ mUseSizeSensitiveSystemFont = false;
+ } else {
+ nsCocoaUtils::GetStringForNSString(displayFamilyName, familyName);
+ CopyUTF16toUTF8(familyName, mSystemDisplayFontFamilyName);
+ }
+ }
+
+#ifdef DEBUG
+ // different system font API's always map to the same family under OSX, so
+ // just assume that and emit a warning if that ever changes
+ NSString* sysFamily = GetRealFamilyName([NSFont systemFontOfSize:0.0]);
+ if ([sysFamily compare:GetRealFamilyName([NSFont boldSystemFontOfSize:0.0])] != NSOrderedSame ||
+ [sysFamily compare:GetRealFamilyName([NSFont controlContentFontOfSize:0.0])] !=
+ NSOrderedSame ||
+ [sysFamily compare:GetRealFamilyName([NSFont menuBarFontOfSize:0.0])] != NSOrderedSame ||
+ [sysFamily compare:GetRealFamilyName([NSFont toolTipsFontOfSize:0.0])] != NSOrderedSame) {
+ NS_WARNING("system font types map to different font families"
+ " -- please log a bug!!");
+ }
+#endif
+}
+
+gfxFontFamily* gfxMacPlatformFontList::FindSystemFontFamily(const nsACString& aFamily) {
+ nsAutoCString key;
+ GenerateFontListKey(aFamily, key);
+
+ gfxFontFamily* familyEntry;
+ if ((familyEntry = mFontFamilies.GetWeak(key))) {
+ return CheckFamily(familyEntry);
+ }
+
+ return nullptr;
+}
+
+void gfxMacPlatformFontList::RegisteredFontsChangedNotificationCallback(
+ CFNotificationCenterRef center, void* observer, CFStringRef name, const void* object,
+ CFDictionaryRef userInfo) {
+ if (!::CFEqual(name, kCTFontManagerRegisteredFontsChangedNotification)) {
+ return;
+ }
+
+ gfxMacPlatformFontList* fl = static_cast<gfxMacPlatformFontList*>(observer);
+ if (!fl->IsInitialized()) {
+ return;
+ }
+
+ // xxx - should be carefully pruning the list of fonts, not rebuilding it from scratch
+ fl->UpdateFontList();
+
+ gfxPlatform::ForceGlobalReflow(gfxPlatform::NeedsReframe::Yes);
+ dom::ContentParent::NotifyUpdatedFonts(true);
+}
+
+gfxFontEntry* gfxMacPlatformFontList::PlatformGlobalFontFallback(nsPresContext* aPresContext,
+ const uint32_t aCh,
+ Script aRunScript,
+ const gfxFontStyle* aMatchStyle,
+ FontFamily& aMatchedFamily) {
+ CFStringRef str;
+ UniChar ch[2];
+ CFIndex length = 1;
+
+ if (IS_IN_BMP(aCh)) {
+ ch[0] = aCh;
+ str = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, ch, 1, kCFAllocatorNull);
+ } else {
+ ch[0] = H_SURROGATE(aCh);
+ ch[1] = L_SURROGATE(aCh);
+ str = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, ch, 2, kCFAllocatorNull);
+ if (!str) {
+ return nullptr;
+ }
+ length = 2;
+ }
+
+ // use CoreText to find the fallback family
+
+ gfxFontEntry* fontEntry = nullptr;
+ bool cantUseFallbackFont = false;
+
+ if (!mDefaultFont) {
+ mDefaultFont = ::CTFontCreateWithName(CFSTR("LucidaGrande"), 12.f, NULL);
+ }
+
+ AutoCFRelease<CTFontRef> fallback =
+ ::CTFontCreateForString(mDefaultFont, str, ::CFRangeMake(0, length));
+
+ if (fallback) {
+ AutoCFRelease<CFStringRef> familyNameRef = ::CTFontCopyFamilyName(fallback);
+
+ if (familyNameRef &&
+ ::CFStringCompare(familyNameRef, CFSTR("LastResort"), kCFCompareCaseInsensitive) !=
+ kCFCompareEqualTo &&
+ ::CFStringCompare(familyNameRef, CFSTR(".LastResort"), kCFCompareCaseInsensitive) !=
+ kCFCompareEqualTo) {
+ AutoTArray<UniChar, 1024> buffer;
+ CFIndex familyNameLen = ::CFStringGetLength(familyNameRef);
+ buffer.SetLength(familyNameLen + 1);
+ ::CFStringGetCharacters(familyNameRef, ::CFRangeMake(0, familyNameLen), buffer.Elements());
+ buffer[familyNameLen] = 0;
+ NS_ConvertUTF16toUTF8 familyNameString(reinterpret_cast<char16_t*>(buffer.Elements()),
+ familyNameLen);
+
+ if (SharedFontList()) {
+ fontlist::Family* family = FindSharedFamily(aPresContext, familyNameString);
+ if (family) {
+ fontlist::Face* face = family->FindFaceForStyle(SharedFontList(), *aMatchStyle);
+ if (face) {
+ fontEntry = GetOrCreateFontEntryLocked(face, family);
+ }
+ if (fontEntry) {
+ if (fontEntry->HasCharacter(aCh)) {
+ aMatchedFamily = FontFamily(family);
+ } else {
+ fontEntry = nullptr;
+ cantUseFallbackFont = true;
+ }
+ }
+ }
+ }
+
+ // The macOS system font does not appear in the shared font list, so if
+ // we didn't find the fallback font above, we should also check for an
+ // unshared fontFamily in the system list.
+ if (!fontEntry) {
+ gfxFontFamily* family = FindSystemFontFamily(familyNameString);
+ if (family) {
+ fontEntry = family->FindFontForStyle(*aMatchStyle);
+ if (fontEntry) {
+ if (fontEntry->HasCharacter(aCh)) {
+ aMatchedFamily = FontFamily(family);
+ } else {
+ fontEntry = nullptr;
+ cantUseFallbackFont = true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (cantUseFallbackFont) {
+ Telemetry::Accumulate(Telemetry::BAD_FALLBACK_FONT, cantUseFallbackFont);
+ }
+
+ ::CFRelease(str);
+
+ return fontEntry;
+}
+
+FontFamily gfxMacPlatformFontList::GetDefaultFontForPlatform(nsPresContext* aPresContext,
+ const gfxFontStyle* aStyle,
+ nsAtom* aLanguage) {
+ nsAutoreleasePool localPool;
+
+ NSString* defaultFamily = [[NSFont userFontOfSize:aStyle->size] familyName];
+ nsAutoString familyName;
+
+ GetStringForNSString(defaultFamily, familyName);
+ return FindFamily(aPresContext, NS_ConvertUTF16toUTF8(familyName));
+}
+
+int32_t gfxMacPlatformFontList::AppleWeightToCSSWeight(int32_t aAppleWeight) {
+ if (aAppleWeight < 1)
+ aAppleWeight = 1;
+ else if (aAppleWeight > kAppleMaxWeight)
+ aAppleWeight = kAppleMaxWeight;
+ return gAppleWeightToCSSWeight[aAppleWeight];
+}
+
+gfxFontEntry* gfxMacPlatformFontList::LookupLocalFont(nsPresContext* aPresContext,
+ const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry) {
+ if (aFontName.IsEmpty() || aFontName[0] == '.') {
+ return nullptr;
+ }
+
+ AutoLock lock(mLock);
+
+ nsAutoreleasePool localPool;
+
+ // Bug 567552 - disable auto-activation of fonts on first call to
+ // LookupLocalFont.
+ static bool firstTime = true;
+ if (firstTime) {
+ firstTime = false;
+ // get the main bundle identifier
+ CFBundleRef mainBundle = ::CFBundleGetMainBundle();
+ CFStringRef mainBundleID = nullptr;
+ if (mainBundle) {
+ mainBundleID = ::CFBundleGetIdentifier(mainBundle);
+ }
+ // Bug 969388 and bug 922590 - mainBundlID as null is sometimes problematic.
+ if (!mainBundleID) {
+ NS_WARNING("missing bundle ID, packaging set up incorrectly");
+ } else {
+ CTFontManagerSetAutoActivationSetting(mainBundleID, kCTFontManagerAutoActivationDisabled);
+ }
+ }
+
+ CrashReporter::AutoAnnotateCrashReport autoFontName(CrashReporter::Annotation::FontName,
+ aFontName);
+
+ NSString* faceName = GetNSStringForString(NS_ConvertUTF8toUTF16(aFontName));
+
+ // lookup face based on postscript or full name
+ AutoCFRelease<CGFontRef> fontRef = CGFontCreateWithFontName(CFStringRef(faceName));
+ if (!fontRef) {
+ return nullptr;
+ }
+
+ // It's possible for CGFontCreateWithFontName to return a font that has been
+ // deactivated/uninstalled, or a font that is excluded from the font list due
+ // to CSS font-visibility restriction. So we need to check whether this font is
+ // allowed to be used.
+
+ // CGFontRef doesn't offer a family-name API, so we go via a CTFontRef.
+ AutoCFRelease<CTFontRef> ctFont = CTFontCreateWithGraphicsFont(fontRef, 0.0, nullptr, nullptr);
+ if (!ctFont) {
+ return nullptr;
+ }
+ AutoCFRelease<CFStringRef> name = CTFontCopyFamilyName(ctFont);
+
+ // Convert the family name to a key suitable for font-list lookup (8-bit, lowercased).
+ nsAutoCString key;
+ // CFStringGetLength is in UTF-16 code units. The maximum this count can expand
+ // when converted to UTF-8 is 3x. We add 1 to ensure there will also be space for
+ // null-termination of the resulting C string.
+ key.SetLength((CFStringGetLength(name) + 1) * 3);
+ if (!CFStringGetCString(name, key.BeginWriting(), key.Length(), kCFStringEncodingUTF8)) {
+ // This shouldn't ever happen, but if it does we just bail.
+ NS_WARNING("Failed to get family name?");
+ key.Truncate(0);
+ }
+ if (key.IsEmpty()) {
+ return nullptr;
+ }
+ // Reset our string length to match the actual C string we got, which will usually
+ // be much shorter than the maximal buffer we allocated.
+ key.Truncate(strlen(key.get()));
+ ToLowerCase(key);
+ // If the family can't be looked up, this font is not available for use.
+ FontFamily family = FindFamily(aPresContext, key);
+ if (family.IsNull()) {
+ return nullptr;
+ }
+
+ return new MacOSFontEntry(aFontName, fontRef, aWeightForEntry, aStretchForEntry, aStyleForEntry,
+ false, true);
+}
+
+static void ReleaseData(void* info, const void* data, size_t size) { free((void*)data); }
+
+gfxFontEntry* gfxMacPlatformFontList::MakePlatformFont(const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry,
+ const uint8_t* aFontData, uint32_t aLength) {
+ NS_ASSERTION(aFontData, "MakePlatformFont called with null data");
+
+ // create the font entry
+ nsAutoString uniqueName;
+
+ nsresult rv = gfxFontUtils::MakeUniqueUserFontName(uniqueName);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ CrashReporter::AutoAnnotateCrashReport autoFontName(CrashReporter::Annotation::FontName,
+ aFontName);
+
+ AutoCFRelease<CGDataProviderRef> provider =
+ ::CGDataProviderCreateWithData(nullptr, aFontData, aLength, &ReleaseData);
+ AutoCFRelease<CGFontRef> fontRef = ::CGFontCreateWithDataProvider(provider);
+ if (!fontRef) {
+ return nullptr;
+ }
+
+ auto newFontEntry =
+ MakeUnique<MacOSFontEntry>(NS_ConvertUTF16toUTF8(uniqueName), fontRef, aWeightForEntry,
+ aStretchForEntry, aStyleForEntry, true, false);
+ return newFontEntry.release();
+}
+
+// Webkit code uses a system font meta name, so mimic that here
+// WebCore/platform/graphics/mac/FontCacheMac.mm
+static const char kSystemFont_system[] = "-apple-system";
+
+bool gfxMacPlatformFontList::FindAndAddFamiliesLocked(
+ nsPresContext* aPresContext, StyleGenericFontFamily aGeneric, const nsACString& aFamily,
+ nsTArray<FamilyAndGeneric>* aOutput, FindFamiliesFlags aFlags, gfxFontStyle* aStyle,
+ nsAtom* aLanguage, gfxFloat aDevToCssSize) {
+ if (aFamily.EqualsLiteral(kSystemFont_system)) {
+ // Search for special system font name, -apple-system. This is not done via
+ // the shared fontlist on Catalina or later, because the hidden system font
+ // may not be included there; we create a separate gfxFontFamily to manage
+ // this family.
+ const nsCString& systemFontFamilyName =
+ mUseSizeSensitiveSystemFont && aStyle &&
+ (aStyle->size * aDevToCssSize) >= kTextDisplayCrossover
+ ? mSystemDisplayFontFamilyName
+ : mSystemTextFontFamilyName;
+ if (SharedFontList() && !nsCocoaFeatures::OnCatalinaOrLater()) {
+ FindFamiliesFlags flags = aFlags | FindFamiliesFlags::eSearchHiddenFamilies;
+ return gfxPlatformFontList::FindAndAddFamiliesLocked(aPresContext, aGeneric,
+ systemFontFamilyName, aOutput, flags,
+ aStyle, aLanguage, aDevToCssSize);
+ } else {
+ if (auto* fam = FindSystemFontFamily(systemFontFamilyName)) {
+ aOutput->AppendElement(fam);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ return gfxPlatformFontList::FindAndAddFamiliesLocked(aPresContext, aGeneric, aFamily, aOutput,
+ aFlags, aStyle, aLanguage, aDevToCssSize);
+}
+
+void gfxMacPlatformFontList::LookupSystemFont(LookAndFeel::FontID aSystemFontID,
+ nsACString& aSystemFontName,
+ gfxFontStyle& aFontStyle) {
+ // Provide a local pool because we may be called from stylo threads.
+ nsAutoreleasePool localPool;
+
+ // code moved here from widget/cocoa/nsLookAndFeel.mm
+ NSFont* font = nullptr;
+ char* systemFontName = nullptr;
+ switch (aSystemFontID) {
+ case LookAndFeel::FontID::MessageBox:
+ case LookAndFeel::FontID::StatusBar:
+ case LookAndFeel::FontID::MozList:
+ case LookAndFeel::FontID::MozField:
+ case LookAndFeel::FontID::MozButton:
+ font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
+ systemFontName = (char*)kSystemFont_system;
+ break;
+
+ case LookAndFeel::FontID::SmallCaption:
+ font = [NSFont boldSystemFontOfSize:[NSFont smallSystemFontSize]];
+ systemFontName = (char*)kSystemFont_system;
+ break;
+
+ case LookAndFeel::FontID::Icon: // used in urlbar; tried labelFont, but too small
+ font = [NSFont controlContentFontOfSize:0.0];
+ systemFontName = (char*)kSystemFont_system;
+ break;
+
+ case LookAndFeel::FontID::MozPullDownMenu:
+ font = [NSFont menuBarFontOfSize:0.0];
+ systemFontName = (char*)kSystemFont_system;
+ break;
+
+ case LookAndFeel::FontID::Caption:
+ case LookAndFeel::FontID::Menu:
+ default:
+ font = [NSFont systemFontOfSize:0.0];
+ systemFontName = (char*)kSystemFont_system;
+ break;
+ }
+ NS_ASSERTION(font, "system font not set");
+ NS_ASSERTION(systemFontName, "system font name not set");
+
+ if (systemFontName) {
+ aSystemFontName.AssignASCII(systemFontName);
+ }
+
+ NSFontSymbolicTraits traits = [[font fontDescriptor] symbolicTraits];
+ aFontStyle.style = (traits & NSFontItalicTrait) ? FontSlantStyle::ITALIC : FontSlantStyle::NORMAL;
+ aFontStyle.weight = (traits & NSFontBoldTrait) ? FontWeight::BOLD : FontWeight::NORMAL;
+ aFontStyle.stretch = (traits & NSFontExpandedTrait) ? FontStretch::EXPANDED
+ : (traits & NSFontCondensedTrait) ? FontStretch::CONDENSED
+ : FontStretch::NORMAL;
+ aFontStyle.size = [font pointSize];
+ aFontStyle.systemFont = true;
+}
+
+// used to load system-wide font info on off-main thread
+class MacFontInfo final : public FontInfoData {
+ public:
+ MacFontInfo(bool aLoadOtherNames, bool aLoadFaceNames, bool aLoadCmaps, RecursiveMutex& aLock)
+ : FontInfoData(aLoadOtherNames, aLoadFaceNames, aLoadCmaps), mLock(aLock) {}
+
+ virtual ~MacFontInfo() = default;
+
+ virtual void Load() {
+ nsAutoreleasePool localPool;
+ FontInfoData::Load();
+ }
+
+ // loads font data for all members of a given family
+ virtual void LoadFontFamilyData(const nsACString& aFamilyName);
+
+ RecursiveMutex& mLock;
+};
+
+void MacFontInfo::LoadFontFamilyData(const nsACString& aFamilyName) {
+ CrashReporter::AutoAnnotateCrashReport autoFontName(CrashReporter::Annotation::FontName,
+ aFamilyName);
+ // Prevent this from running concurrently with CGFont operations on the main thread,
+ // because the macOS font cache is fragile with concurrent access. This appears to be
+ // a vulnerability within CoreText in versions of macOS before macOS 13. In time, we
+ // can remove this lock.
+ RecursiveMutexAutoLock lock(mLock);
+
+ // family name ==> CTFontDescriptor
+ NSString* famName = GetNSStringForString(NS_ConvertUTF8toUTF16(aFamilyName));
+ CFStringRef family = CFStringRef(famName);
+
+ AutoCFRelease<CFMutableDictionaryRef> attr = CFDictionaryCreateMutable(
+ NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, family);
+ AutoCFRelease<CTFontDescriptorRef> fd = CTFontDescriptorCreateWithAttributes(attr);
+ AutoCFRelease<CFArrayRef> matchingFonts = CTFontDescriptorCreateMatchingFontDescriptors(fd, NULL);
+ if (!matchingFonts) {
+ return;
+ }
+
+ nsTArray<nsCString> otherFamilyNames;
+ bool hasOtherFamilyNames = true;
+
+ // iterate over faces in the family
+ int f, numFaces = (int)CFArrayGetCount(matchingFonts);
+ for (f = 0; f < numFaces; f++) {
+ mLoadStats.fonts++;
+
+ CTFontDescriptorRef faceDesc = (CTFontDescriptorRef)CFArrayGetValueAtIndex(matchingFonts, f);
+ if (!faceDesc) {
+ continue;
+ }
+ AutoCFRelease<CTFontRef> fontRef = CTFontCreateWithFontDescriptor(faceDesc, 0.0, nullptr);
+ if (!fontRef) {
+ NS_WARNING("failed to create a CTFontRef");
+ continue;
+ }
+
+ if (mLoadCmaps) {
+ // face name
+ AutoCFRelease<CFStringRef> faceName =
+ (CFStringRef)CTFontDescriptorCopyAttribute(faceDesc, kCTFontNameAttribute);
+
+ AutoTArray<UniChar, 1024> buffer;
+ CFIndex len = CFStringGetLength(faceName);
+ buffer.SetLength(len + 1);
+ CFStringGetCharacters(faceName, ::CFRangeMake(0, len), buffer.Elements());
+ buffer[len] = 0;
+ NS_ConvertUTF16toUTF8 fontName(reinterpret_cast<char16_t*>(buffer.Elements()), len);
+
+ // load the cmap data
+ FontFaceData fontData;
+ AutoCFRelease<CFDataRef> cmapTable =
+ CTFontCopyTable(fontRef, kCTFontTableCmap, kCTFontTableOptionNoOptions);
+
+ if (cmapTable) {
+ const uint8_t* cmapData = (const uint8_t*)CFDataGetBytePtr(cmapTable);
+ uint32_t cmapLen = CFDataGetLength(cmapTable);
+ RefPtr<gfxCharacterMap> charmap = new gfxCharacterMap();
+ uint32_t offset;
+ nsresult rv;
+
+ rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, offset);
+ if (NS_SUCCEEDED(rv)) {
+ fontData.mCharacterMap = charmap;
+ fontData.mUVSOffset = offset;
+ mLoadStats.cmaps++;
+ }
+ }
+
+ mFontFaceData.InsertOrUpdate(fontName, fontData);
+ }
+
+ if (mLoadOtherNames && hasOtherFamilyNames) {
+ AutoCFRelease<CFDataRef> nameTable =
+ CTFontCopyTable(fontRef, kCTFontTableName, kCTFontTableOptionNoOptions);
+
+ if (nameTable) {
+ const char* nameData = (const char*)CFDataGetBytePtr(nameTable);
+ uint32_t nameLen = CFDataGetLength(nameTable);
+ gfxFontUtils::ReadOtherFamilyNamesForFace(aFamilyName, nameData, nameLen, otherFamilyNames,
+ false);
+ hasOtherFamilyNames = otherFamilyNames.Length() != 0;
+ }
+ }
+ }
+
+ // if found other names, insert them in the hash table
+ if (otherFamilyNames.Length() != 0) {
+ mOtherFamilyNames.InsertOrUpdate(aFamilyName, otherFamilyNames);
+ mLoadStats.othernames += otherFamilyNames.Length();
+ }
+}
+
+already_AddRefed<FontInfoData> gfxMacPlatformFontList::CreateFontInfoData() {
+ bool loadCmaps =
+ !UsesSystemFallback() || gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback();
+
+ mLock.AssertCurrentThreadIn();
+ RefPtr<MacFontInfo> fi = new MacFontInfo(true, NeedFullnamePostscriptNames(), loadCmaps, mLock);
+ return fi.forget();
+}
+
+gfxFontFamily* gfxMacPlatformFontList::CreateFontFamily(const nsACString& aName,
+ FontVisibility aVisibility) const {
+ return new gfxMacFontFamily(aName, aVisibility);
+}
+
+gfxFontEntry* gfxMacPlatformFontList::CreateFontEntry(fontlist::Face* aFace,
+ const fontlist::Family* aFamily) {
+ MacOSFontEntry* fe =
+ new MacOSFontEntry(aFace->mDescriptor.AsString(SharedFontList()), aFace->mWeight, false,
+ 0.0); // XXX standardFace, sizeHint
+ fe->InitializeFrom(aFace, aFamily);
+ return fe;
+}
+
+void gfxMacPlatformFontList::GetFacesInitDataForFamily(const fontlist::Family* aFamily,
+ nsTArray<fontlist::Face::InitData>& aFaces,
+ bool aLoadCmaps) const {
+ nsAutoreleasePool localPool;
+
+ auto name = aFamily->Key().AsString(SharedFontList());
+ NSString* family = GetNSStringForString(NS_ConvertUTF8toUTF16(name));
+
+ CrashReporter::AutoAnnotateCrashReport autoFontName(CrashReporter::Annotation::FontName, name);
+
+ // returns an array of [psname, style name, weight, traits] elements, goofy api
+ NSArray* fontfaces = [sFontManager availableMembersOfFontFamily:family];
+ int faceCount = [fontfaces count];
+ for (int faceIndex = 0; faceIndex < faceCount; faceIndex++) {
+ NSArray* face = [fontfaces objectAtIndex:faceIndex];
+ NSString* psname = [face objectAtIndex:INDEX_FONT_POSTSCRIPT_NAME];
+ int32_t appKitWeight = [[face objectAtIndex:INDEX_FONT_WEIGHT] unsignedIntValue];
+ uint32_t macTraits = [[face objectAtIndex:INDEX_FONT_TRAITS] unsignedIntValue];
+ NSString* facename = [face objectAtIndex:INDEX_FONT_FACE_NAME];
+
+ if (appKitWeight == kAppleExtraLightWeight) {
+ // if the facename contains UltraLight, set the weight to the ultralight weight value
+ NSRange range = [facename rangeOfString:@"ultralight" options:NSCaseInsensitiveSearch];
+ if (range.location != NSNotFound) {
+ appKitWeight = kAppleUltraLightWeight;
+ }
+ }
+
+ // make a nsString
+ nsAutoString postscriptFontName;
+ GetStringForNSString(psname, postscriptFontName);
+
+ int32_t cssWeight = gfxMacPlatformFontList::AppleWeightToCSSWeight(appKitWeight);
+ if (PR_GetCurrentThread() != sInitFontListThread) {
+ int32_t weightOverride = GetWeightOverride(postscriptFontName);
+ if (weightOverride) {
+ // scale down and clamp, to get a value from 1..9
+ cssWeight = ((weightOverride + 50) / 100);
+ cssWeight = std::max(1, std::min(cssWeight, 9));
+ }
+ }
+ cssWeight *= 100; // scale up to CSS values
+
+ StretchRange stretch(FontStretch::NORMAL);
+ if (macTraits & (NSCondensedFontMask | NSNarrowFontMask | NSCompressedFontMask)) {
+ stretch = StretchRange(FontStretch::CONDENSED);
+ } else if (macTraits & NSExpandedFontMask) {
+ stretch = StretchRange(FontStretch::EXPANDED);
+ }
+ // Cocoa fails to set the Italic traits bit for HelveticaLightItalic,
+ // at least (see bug 611855), so check for style name endings as well
+ SlantStyleRange slantStyle(FontSlantStyle::NORMAL);
+ if ((macTraits & NSItalicFontMask) || [facename hasSuffix:@"Italic"] ||
+ [facename hasSuffix:@"Oblique"]) {
+ slantStyle = SlantStyleRange(FontSlantStyle::ITALIC);
+ }
+
+ bool fixedPitch = (macTraits & NSFixedPitchFontMask) ? true : false;
+
+ RefPtr<gfxCharacterMap> charmap;
+ if (aLoadCmaps) {
+ AutoCFRelease<CGFontRef> font = CGFontCreateWithFontName(CFStringRef(psname));
+ if (font) {
+ uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p');
+ AutoCFRelease<CFDataRef> data = CGFontCopyTableForTag(font, kCMAP);
+ if (data) {
+ uint32_t offset;
+ charmap = new gfxCharacterMap();
+ gfxFontUtils::ReadCMAP(CFDataGetBytePtr(data), CFDataGetLength(data), *charmap, offset);
+ }
+ }
+ }
+
+ // Ensure that a face named "Regular" goes to the front of the list, so it
+ // will take precedence over other faces with the same style attributes but
+ // a different name (such as "Outline").
+ auto data = fontlist::Face::InitData{
+ NS_ConvertUTF16toUTF8(postscriptFontName),
+ 0,
+ fixedPitch,
+ WeightRange(FontWeight::FromInt(cssWeight)),
+ stretch,
+ slantStyle,
+ charmap,
+ };
+ if ([facename caseInsensitiveCompare:@"Regular"] == NSOrderedSame) {
+ aFaces.InsertElementAt(0, std::move(data));
+ } else {
+ aFaces.AppendElement(std::move(data));
+ }
+ }
+}
+
+void gfxMacPlatformFontList::ReadFaceNamesForFamily(fontlist::Family* aFamily,
+ bool aNeedFullnamePostscriptNames) {
+ if (!aFamily->IsInitialized()) {
+ if (!InitializeFamily(aFamily)) {
+ return;
+ }
+ }
+ const uint32_t kNAME = TRUETYPE_TAG('n', 'a', 'm', 'e');
+ fontlist::FontList* list = SharedFontList();
+ nsAutoCString canonicalName(aFamily->DisplayName().AsString(list));
+ const auto* facePtrs = aFamily->Faces(list);
+ for (uint32_t i = 0, n = aFamily->NumFaces(); i < n; i++) {
+ auto* face = facePtrs[i].ToPtr<const fontlist::Face>(list);
+ if (!face) {
+ continue;
+ }
+ nsAutoCString name(face->mDescriptor.AsString(list));
+ // We create a temporary MacOSFontEntry just to read family names from the
+ // 'name' table in the font resource. The style attributes here are ignored
+ // as this entry is not used for font style matching.
+ // The size hint might be used to select which face is accessed in the case
+ // of the macOS UI font; see MacOSFontEntry::GetFontRef(). We pass 16.0 in
+ // order to get a standard text-size face in this case, although it's
+ // unlikely to matter for the purpose of just reading family names.
+ auto fe = MakeUnique<MacOSFontEntry>(name, WeightRange(FontWeight::NORMAL), false, 16.0);
+ if (!fe) {
+ continue;
+ }
+ gfxFontEntry::AutoTable nameTable(fe.get(), kNAME);
+ if (!nameTable) {
+ continue;
+ }
+ uint32_t dataLength;
+ const char* nameData = hb_blob_get_data(nameTable, &dataLength);
+ AutoTArray<nsCString, 4> otherFamilyNames;
+ gfxFontUtils::ReadOtherFamilyNamesForFace(canonicalName, nameData, dataLength, otherFamilyNames,
+ false);
+ for (const auto& alias : otherFamilyNames) {
+ nsAutoCString key;
+ GenerateFontListKey(alias, key);
+ auto aliasData = mAliasTable.GetOrInsertNew(key);
+ aliasData->InitFromFamily(aFamily, canonicalName);
+ aliasData->mFaces.AppendElement(facePtrs[i]);
+ }
+ }
+}
+
+#ifdef MOZ_BUNDLED_FONTS
+void gfxMacPlatformFontList::ActivateBundledFonts() {
+ nsCOMPtr<nsIFile> localDir;
+ if (NS_FAILED(NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir)))) {
+ return;
+ }
+ if (NS_FAILED(localDir->Append(u"fonts"_ns))) {
+ return;
+ }
+ nsAutoCString path;
+ if (NS_FAILED(localDir->GetNativePath(path))) {
+ return;
+ }
+ ActivateFontsFromDir(path, &mBundledFamilies);
+}
+#endif
diff --git a/gfx/thebes/gfxMacUtils.cpp b/gfx/thebes/gfxMacUtils.cpp
new file mode 100644
index 0000000000..f52ca0910f
--- /dev/null
+++ b/gfx/thebes/gfxMacUtils.cpp
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxMacUtils.h"
+#include <CoreVideo/CoreVideo.h>
+
+/* static */ CFStringRef gfxMacUtils::CFStringForTransferFunction(
+ mozilla::gfx::TransferFunction aTransferFunction) {
+#if !defined(MAC_OS_VERSION_10_13) || \
+ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_VERSION_10_13
+ CFStringRef kCVImageBufferTransferFunction_sRGB = CFSTR("IEC_sRGB");
+ CFStringRef kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ =
+ CFSTR("SMPTE_ST_2084_PQ");
+ CFStringRef kCVImageBufferTransferFunction_ITU_R_2100_HLG =
+ CFSTR("ITU_R_2100_HLG");
+#endif
+
+ switch (aTransferFunction) {
+ case mozilla::gfx::TransferFunction::BT709:
+ return kCVImageBufferTransferFunction_ITU_R_709_2;
+
+ case mozilla::gfx::TransferFunction::SRGB:
+ return kCVImageBufferTransferFunction_sRGB;
+
+ case mozilla::gfx::TransferFunction::PQ:
+ return kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ;
+
+ case mozilla::gfx::TransferFunction::HLG:
+ return kCVImageBufferTransferFunction_ITU_R_2100_HLG;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown TransferFunction.");
+ return kCVImageBufferTransferFunction_ITU_R_709_2;
+ }
+}
diff --git a/gfx/thebes/gfxMacUtils.h b/gfx/thebes/gfxMacUtils.h
new file mode 100644
index 0000000000..b256f7bde1
--- /dev/null
+++ b/gfx/thebes/gfxMacUtils.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_MAC_UTILS_H
+#define GFX_MAC_UTILS_H
+
+#include <CoreFoundation/CoreFoundation.h>
+#include "mozilla/gfx/2D.h"
+
+class gfxMacUtils {
+ public:
+ // This takes a TransferFunction and returns a constant CFStringRef, which
+ // uses get semantics and does not need to be released.
+ static CFStringRef CFStringForTransferFunction(
+ mozilla::gfx::TransferFunction);
+};
+
+#endif
diff --git a/gfx/thebes/gfxMathTable.cpp b/gfx/thebes/gfxMathTable.cpp
new file mode 100644
index 0000000000..6a27efff21
--- /dev/null
+++ b/gfx/thebes/gfxMathTable.cpp
@@ -0,0 +1,200 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxMathTable.h"
+
+#include "harfbuzz/hb.h"
+#include "harfbuzz/hb-ot.h"
+
+#define FloatToFixed(f) (65536 * (f))
+#define FixedToFloat(f) ((f) * (1.0 / 65536.0))
+
+using namespace mozilla;
+
+gfxMathTable::gfxMathTable(hb_face_t* aFace, gfxFloat aSize) {
+ mMathVariantCache.vertical = false;
+ mHBFont = hb_font_create(aFace);
+ if (mHBFont) {
+ hb_font_set_ppem(mHBFont, aSize, aSize);
+ uint32_t scale = FloatToFixed(aSize);
+ hb_font_set_scale(mHBFont, scale, scale);
+ }
+
+ mMathVariantCache.glyphID = 0;
+ ClearCache();
+}
+
+gfxMathTable::~gfxMathTable() {
+ if (mHBFont) {
+ hb_font_destroy(mHBFont);
+ }
+}
+
+gfxFloat gfxMathTable::Constant(MathConstant aConstant) const {
+ int32_t value = hb_ot_math_get_constant(
+ mHBFont, static_cast<hb_ot_math_constant_t>(aConstant));
+ if (aConstant == ScriptPercentScaleDown ||
+ aConstant == ScriptScriptPercentScaleDown ||
+ aConstant == RadicalDegreeBottomRaisePercent) {
+ return value / 100.0;
+ }
+ return FixedToFloat(value);
+}
+
+gfxFloat gfxMathTable::ItalicsCorrection(uint32_t aGlyphID) const {
+ return FixedToFloat(
+ hb_ot_math_get_glyph_italics_correction(mHBFont, aGlyphID));
+}
+
+uint32_t gfxMathTable::VariantsSize(uint32_t aGlyphID, bool aVertical,
+ uint16_t aSize) const {
+ UpdateMathVariantCache(aGlyphID, aVertical);
+ if (aSize < kMaxCachedSizeCount) {
+ return mMathVariantCache.sizes[aSize];
+ }
+
+ // If the size index exceeds the cache size, we just read the value with
+ // hb_ot_math_get_glyph_variants.
+ hb_direction_t direction = aVertical ? HB_DIRECTION_BTT : HB_DIRECTION_LTR;
+ hb_ot_math_glyph_variant_t variant;
+ unsigned int count = 1;
+ hb_ot_math_get_glyph_variants(mHBFont, aGlyphID, direction, aSize, &count,
+ &variant);
+ return count > 0 ? variant.glyph : 0;
+}
+
+bool gfxMathTable::VariantsParts(uint32_t aGlyphID, bool aVertical,
+ uint32_t aGlyphs[4]) const {
+ UpdateMathVariantCache(aGlyphID, aVertical);
+ memcpy(aGlyphs, mMathVariantCache.parts, sizeof(mMathVariantCache.parts));
+ return mMathVariantCache.arePartsValid;
+}
+
+void gfxMathTable::ClearCache() const {
+ memset(mMathVariantCache.sizes, 0, sizeof(mMathVariantCache.sizes));
+ memset(mMathVariantCache.parts, 0, sizeof(mMathVariantCache.parts));
+ mMathVariantCache.arePartsValid = false;
+}
+
+void gfxMathTable::UpdateMathVariantCache(uint32_t aGlyphID,
+ bool aVertical) const {
+ if (aGlyphID == mMathVariantCache.glyphID &&
+ aVertical == mMathVariantCache.vertical)
+ return;
+
+ mMathVariantCache.glyphID = aGlyphID;
+ mMathVariantCache.vertical = aVertical;
+ ClearCache();
+
+ // Cache the first size variants.
+ hb_direction_t direction = aVertical ? HB_DIRECTION_BTT : HB_DIRECTION_LTR;
+ hb_ot_math_glyph_variant_t variant[kMaxCachedSizeCount];
+ unsigned int count = kMaxCachedSizeCount;
+ hb_ot_math_get_glyph_variants(mHBFont, aGlyphID, direction, 0, &count,
+ variant);
+ for (unsigned int i = 0; i < count; i++) {
+ mMathVariantCache.sizes[i] = variant[i].glyph;
+ }
+
+ // Try and cache the parts of the glyph assembly.
+ // XXXfredw The structure of the Open Type Math table is a bit more general
+ // than the one currently used by the nsMathMLChar code, so we try to fallback
+ // in reasonable way. We use the approach of the copyComponents function in
+ // github.com/mathjax/MathJax-dev/blob/master/fonts/OpenTypeMath/fontUtil.py
+ //
+ // The nsMathMLChar code can use at most 3 non extender pieces (aGlyphs[0],
+ // aGlyphs[1] and aGlyphs[2]) and the extenders between these pieces should
+ // all be the same (aGlyphs[4]). Also, the parts of vertical assembly are
+ // stored from bottom to top in the Open Type MATH table while they are
+ // stored from top to bottom in nsMathMLChar.
+
+ hb_ot_math_glyph_part_t parts[5];
+ count = MOZ_ARRAY_LENGTH(parts);
+ unsigned int offset = 0;
+ if (hb_ot_math_get_glyph_assembly(mHBFont, aGlyphID, direction, offset,
+ &count, parts,
+ NULL) > MOZ_ARRAY_LENGTH(parts))
+ return; // Not supported: Too many pieces.
+ if (count <= 0) return; // Not supported: No pieces.
+
+ // Count the number of non extender pieces
+ uint16_t nonExtenderCount = 0;
+ for (uint16_t i = 0; i < count; i++) {
+ if (!(parts[i].flags & HB_MATH_GLYPH_PART_FLAG_EXTENDER)) {
+ nonExtenderCount++;
+ }
+ }
+ if (nonExtenderCount > 3) {
+ // Not supported: too many pieces
+ return;
+ }
+
+ // Now browse the list of pieces
+
+ // 0 = look for a left/bottom glyph
+ // 1 = look for an extender between left/bottom and mid
+ // 2 = look for a middle glyph
+ // 3 = look for an extender between middle and right/top
+ // 4 = look for a right/top glyph
+ // 5 = no more piece expected
+ uint8_t state = 0;
+
+ // First extender char found.
+ uint32_t extenderChar = 0;
+
+ for (uint16_t i = 0; i < count; i++) {
+ bool isExtender = parts[i].flags & HB_MATH_GLYPH_PART_FLAG_EXTENDER;
+ uint32_t glyph = parts[i].glyph;
+
+ if ((state == 1 || state == 2) && nonExtenderCount < 3) {
+ // do not try to find a middle glyph
+ state += 2;
+ }
+
+ if (isExtender) {
+ if (!extenderChar) {
+ extenderChar = glyph;
+ mMathVariantCache.parts[3] = extenderChar;
+ } else if (extenderChar != glyph) {
+ // Not supported: different extenders
+ return;
+ }
+
+ if (state == 0) { // or state == 1
+ // ignore left/bottom piece and multiple successive extenders
+ state = 1;
+ } else if (state == 2) { // or state == 3
+ // ignore middle piece and multiple successive extenders
+ state = 3;
+ } else if (state >= 4) {
+ // Not supported: unexpected extender
+ return;
+ }
+
+ continue;
+ }
+
+ if (state == 0) {
+ // copy left/bottom part
+ mMathVariantCache.parts[aVertical ? 2 : 0] = glyph;
+ state = 1;
+ continue;
+ }
+
+ if (state == 1 || state == 2) {
+ // copy middle part
+ mMathVariantCache.parts[1] = glyph;
+ state = 3;
+ continue;
+ }
+
+ if (state == 3 || state == 4) {
+ // copy right/top part
+ mMathVariantCache.parts[aVertical ? 0 : 2] = glyph;
+ state = 5;
+ }
+ }
+
+ mMathVariantCache.arePartsValid = true;
+}
diff --git a/gfx/thebes/gfxMathTable.h b/gfx/thebes/gfxMathTable.h
new file mode 100644
index 0000000000..bb7595c36d
--- /dev/null
+++ b/gfx/thebes/gfxMathTable.h
@@ -0,0 +1,152 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_MATH_TABLE_H
+#define GFX_MATH_TABLE_H
+
+#include "gfxFont.h"
+
+/**
+ * Used by |gfxFont| to represent the MATH table of an OpenType font.
+ * Each |gfxFont| owns at most one |gfxMathTable| instance.
+ */
+class gfxMathTable {
+ public:
+ /**
+ * @param aFace The HarfBuzz face containing the math table.
+ * @param aSize The font size to pass to HarfBuzz.
+ */
+ gfxMathTable(hb_face_t* aFace, gfxFloat aSize);
+
+ /**
+ * Releases our reference to the MATH table and cleans up everything else.
+ */
+ ~gfxMathTable();
+
+ enum MathConstant {
+ // The order of the constants must match the order of the fields
+ // defined in the MATH table.
+ ScriptPercentScaleDown,
+ ScriptScriptPercentScaleDown,
+ DelimitedSubFormulaMinHeight,
+ DisplayOperatorMinHeight,
+ MathLeading,
+ AxisHeight,
+ AccentBaseHeight,
+ FlattenedAccentBaseHeight,
+ SubscriptShiftDown,
+ SubscriptTopMax,
+ SubscriptBaselineDropMin,
+ SuperscriptShiftUp,
+ SuperscriptShiftUpCramped,
+ SuperscriptBottomMin,
+ SuperscriptBaselineDropMax,
+ SubSuperscriptGapMin,
+ SuperscriptBottomMaxWithSubscript,
+ SpaceAfterScript,
+ UpperLimitGapMin,
+ UpperLimitBaselineRiseMin,
+ LowerLimitGapMin,
+ LowerLimitBaselineDropMin,
+ StackTopShiftUp,
+ StackTopDisplayStyleShiftUp,
+ StackBottomShiftDown,
+ StackBottomDisplayStyleShiftDown,
+ StackGapMin,
+ StackDisplayStyleGapMin,
+ StretchStackTopShiftUp,
+ StretchStackBottomShiftDown,
+ StretchStackGapAboveMin,
+ StretchStackGapBelowMin,
+ FractionNumeratorShiftUp,
+ FractionNumeratorDisplayStyleShiftUp,
+ FractionDenominatorShiftDown,
+ FractionDenominatorDisplayStyleShiftDown,
+ FractionNumeratorGapMin,
+ FractionNumDisplayStyleGapMin,
+ FractionRuleThickness,
+ FractionDenominatorGapMin,
+ FractionDenomDisplayStyleGapMin,
+ SkewedFractionHorizontalGap,
+ SkewedFractionVerticalGap,
+ OverbarVerticalGap,
+ OverbarRuleThickness,
+ OverbarExtraAscender,
+ UnderbarVerticalGap,
+ UnderbarRuleThickness,
+ UnderbarExtraDescender,
+ RadicalVerticalGap,
+ RadicalDisplayStyleVerticalGap,
+ RadicalRuleThickness,
+ RadicalExtraAscender,
+ RadicalKernBeforeDegree,
+ RadicalKernAfterDegree,
+ RadicalDegreeBottomRaisePercent
+ };
+
+ /**
+ * Returns the value of the specified constant from the MATH table.
+ */
+ gfxFloat Constant(MathConstant aConstant) const;
+
+ /**
+ * Returns the value of the specified constant in app units.
+ */
+ nscoord Constant(MathConstant aConstant,
+ uint32_t aAppUnitsPerDevPixel) const {
+ return NSToCoordRound(Constant(aConstant) * aAppUnitsPerDevPixel);
+ }
+
+ /**
+ * If the MATH table contains an italic correction for that glyph, this
+ * function returns the corresponding value. Otherwise it returns 0.
+ */
+ gfxFloat ItalicsCorrection(uint32_t aGlyphID) const;
+
+ /**
+ * @param aGlyphID glyph index of the character we want to stretch
+ * @param aVertical direction of the stretching (vertical/horizontal)
+ * @param aSize the desired size variant
+ *
+ * Returns the glyph index of the desired size variant or 0 if there is not
+ * any such size variant.
+ */
+ uint32_t VariantsSize(uint32_t aGlyphID, bool aVertical,
+ uint16_t aSize) const;
+
+ /**
+ * @param aGlyphID glyph index of the character we want to stretch
+ * @param aVertical direction of the stretching (vertical/horizontal)
+ * @param aGlyphs pre-allocated buffer of 4 elements where the glyph
+ * indexes (or 0 for absent parts) will be stored. The parts are stored in
+ * the order expected by the nsMathMLChar: Top (or Left), Middle, Bottom
+ * (or Right), Glue.
+ *
+ * Tries to fill-in aGlyphs with the relevant glyph indexes and returns
+ * whether the operation was successful. The function returns false if
+ * there is not any assembly for the character we want to stretch or if
+ * the format is not supported by the nsMathMLChar code.
+ *
+ */
+ bool VariantsParts(uint32_t aGlyphID, bool aVertical,
+ uint32_t aGlyphs[4]) const;
+
+ private:
+ // size-specific font object, owned by the gfxMathTable
+ hb_font_t* mHBFont;
+
+ static const unsigned int kMaxCachedSizeCount = 10;
+ struct MathVariantCacheEntry {
+ uint32_t glyphID;
+ bool vertical;
+ uint32_t sizes[kMaxCachedSizeCount];
+ uint32_t parts[4];
+ bool arePartsValid;
+ };
+ mutable MathVariantCacheEntry mMathVariantCache;
+ void ClearCache() const;
+ void UpdateMathVariantCache(uint32_t aGlyphID, bool aVertical) const;
+};
+
+#endif
diff --git a/gfx/thebes/gfxMatrix.h b/gfx/thebes/gfxMatrix.h
new file mode 100644
index 0000000000..7ff90c8f39
--- /dev/null
+++ b/gfx/thebes/gfxMatrix.h
@@ -0,0 +1,13 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_MATRIX_H
+#define GFX_MATRIX_H
+
+#include "mozilla/gfx/MatrixFwd.h"
+
+typedef mozilla::gfx::MatrixDouble gfxMatrix;
+
+#endif /* GFX_MATRIX_H */
diff --git a/gfx/thebes/gfxOTSUtils.h b/gfx/thebes/gfxOTSUtils.h
new file mode 100644
index 0000000000..a4e573f867
--- /dev/null
+++ b/gfx/thebes/gfxOTSUtils.h
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_OTS_UTILS_H
+#define GFX_OTS_UTILS_H
+
+#include "gfxFontUtils.h"
+
+#include "opentype-sanitiser.h"
+
+struct gfxOTSMozAlloc {
+ void* Grow(void* aPtr, size_t aLength) { return moz_xrealloc(aPtr, aLength); }
+ void* ShrinkToFit(void* aPtr, size_t aLength) {
+ return moz_xrealloc(aPtr, aLength);
+ }
+ void Free(void* aPtr) { free(aPtr); }
+};
+
+// Based on ots::ExpandingMemoryStream from ots-memory-stream.h,
+// adapted to use Mozilla allocators and to allow the final
+// memory buffer to be adopted by the client.
+template <typename AllocT = gfxOTSMozAlloc>
+class gfxOTSExpandingMemoryStream : public ots::OTSStream {
+ public:
+ // limit output/expansion to 256MB by default
+ enum { DEFAULT_LIMIT = 256 * 1024 * 1024 };
+
+ explicit gfxOTSExpandingMemoryStream(size_t initial,
+ size_t limit = DEFAULT_LIMIT)
+ : mLength(initial), mLimit(limit), mOff(0) {
+ mPtr = mAlloc.Grow(nullptr, mLength);
+ }
+
+ ~gfxOTSExpandingMemoryStream() { mAlloc.Free(mPtr); }
+
+ size_t size() override { return mLimit; }
+
+ // Return the buffer, resized to fit its contents (as it may have been
+ // over-allocated during growth), and give up ownership of it so the
+ // caller becomes responsible to call free() when finished with it.
+ auto forget() {
+ auto p = mAlloc.ShrinkToFit(mPtr, mOff);
+ mPtr = nullptr;
+ return p;
+ }
+
+ bool WriteRaw(const void* data, size_t length) override {
+ if ((mOff + length > mLength) ||
+ (mLength > std::numeric_limits<size_t>::max() - mOff)) {
+ if (mLength == mLimit) {
+ return false;
+ }
+ size_t newLength = (mLength + 1) * 2;
+ if (newLength < mLength) {
+ return false;
+ }
+ if (newLength > mLimit) {
+ newLength = mLimit;
+ }
+ mPtr = mAlloc.Grow(mPtr, newLength);
+ mLength = newLength;
+ return WriteRaw(data, length);
+ }
+ std::memcpy(static_cast<char*>(mPtr) + mOff, data, length);
+ mOff += length;
+ return true;
+ }
+
+ bool Seek(off_t position) override {
+ if (position < 0) {
+ return false;
+ }
+ if (static_cast<size_t>(position) > mLength) {
+ return false;
+ }
+ mOff = position;
+ return true;
+ }
+
+ off_t Tell() const override { return mOff; }
+
+ private:
+ AllocT mAlloc;
+ void* mPtr;
+ size_t mLength;
+ const size_t mLimit;
+ off_t mOff;
+};
+
+class MOZ_STACK_CLASS gfxOTSContext : public ots::OTSContext {
+ public:
+ gfxOTSContext() {
+ using namespace mozilla;
+
+ // Whether to apply OTS validation to OpenType Layout tables
+ mCheckOTLTables = StaticPrefs::gfx_downloadable_fonts_otl_validation();
+ // Whether to preserve Variation tables in downloaded fonts
+ mCheckVariationTables =
+ StaticPrefs::gfx_downloadable_fonts_validate_variation_tables();
+ // Whether to preserve color bitmap glyphs
+ mKeepColorBitmaps =
+ StaticPrefs::gfx_downloadable_fonts_keep_color_bitmaps();
+ // Whether to preserve SVG glyphs (which can be expensive in Core Text,
+ // so better to drop them if we're not going to render them anyhow).
+ mKeepSVG = StaticPrefs::gfx_font_rendering_opentype_svg_enabled();
+ }
+
+ virtual ots::TableAction GetTableAction(uint32_t aTag) override {
+ // Pass through or validate OTL and Variation tables, depending on prefs.
+ if ((!mCheckOTLTables && (aTag == TRUETYPE_TAG('G', 'D', 'E', 'F') ||
+ aTag == TRUETYPE_TAG('G', 'P', 'O', 'S') ||
+ aTag == TRUETYPE_TAG('G', 'S', 'U', 'B')))) {
+ return ots::TABLE_ACTION_PASSTHRU;
+ }
+ auto isVariationTable = [](uint32_t aTag) -> bool {
+ return aTag == TRUETYPE_TAG('a', 'v', 'a', 'r') ||
+ aTag == TRUETYPE_TAG('c', 'v', 'a', 'r') ||
+ aTag == TRUETYPE_TAG('f', 'v', 'a', 'r') ||
+ aTag == TRUETYPE_TAG('g', 'v', 'a', 'r') ||
+ aTag == TRUETYPE_TAG('H', 'V', 'A', 'R') ||
+ aTag == TRUETYPE_TAG('M', 'V', 'A', 'R') ||
+ aTag == TRUETYPE_TAG('S', 'T', 'A', 'T') ||
+ aTag == TRUETYPE_TAG('V', 'V', 'A', 'R');
+ };
+ if (!mCheckVariationTables && isVariationTable(aTag)) {
+ return ots::TABLE_ACTION_PASSTHRU;
+ }
+ if (!gfxPlatform::HasVariationFontSupport() && isVariationTable(aTag)) {
+ return ots::TABLE_ACTION_DROP;
+ }
+ // Preserve SVG table if OpenType-SVG rendering is enabled.
+ if (aTag == TRUETYPE_TAG('S', 'V', 'G', ' ')) {
+ return mKeepSVG ? ots::TABLE_ACTION_PASSTHRU : ots::TABLE_ACTION_DROP;
+ }
+ if (mKeepColorBitmaps && (aTag == TRUETYPE_TAG('C', 'B', 'D', 'T') ||
+ aTag == TRUETYPE_TAG('C', 'B', 'L', 'C'))) {
+ return ots::TABLE_ACTION_PASSTHRU;
+ }
+ return ots::TABLE_ACTION_DEFAULT;
+ }
+
+ static size_t GuessSanitizedFontSize(size_t aLength,
+ gfxUserFontType aFontType,
+ bool aStrict = true) {
+ switch (aFontType) {
+ case GFX_USERFONT_UNKNOWN:
+ // If being permissive of unknown types, make a reasonable guess
+ // at how much room the sanitized font may take, if it passes. Just
+ // enough extra space to accomodate some growth without excessive
+ // bloat in case of large fonts. 1.5x is a reasonable compromise
+ // for growable vectors in general.
+ return aStrict || !aLength ? 0 : (aLength * 3) / 2;
+ case GFX_USERFONT_WOFF:
+ return aLength * 2;
+ case GFX_USERFONT_WOFF2:
+ return aLength * 3;
+ default:
+ return aLength;
+ }
+ }
+
+ static size_t GuessSanitizedFontSize(const uint8_t* aData, size_t aLength,
+ bool aStrict = true) {
+ gfxUserFontType fontType =
+ gfxFontUtils::DetermineFontDataType(aData, aLength);
+ return GuessSanitizedFontSize(aLength, fontType, aStrict);
+ }
+
+ private:
+ bool mCheckOTLTables;
+ bool mCheckVariationTables;
+ bool mKeepColorBitmaps;
+ bool mKeepSVG;
+};
+
+#endif /* GFX_OTS_UTILS_H */
diff --git a/gfx/thebes/gfxPattern.cpp b/gfx/thebes/gfxPattern.cpp
new file mode 100644
index 0000000000..709776dbed
--- /dev/null
+++ b/gfx/thebes/gfxPattern.cpp
@@ -0,0 +1,203 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxPattern.h"
+
+#include "gfxUtils.h"
+#include "gfxTypes.h"
+#include "gfxPlatform.h"
+#include "gfx2DGlue.h"
+#include "gfxGradientCache.h"
+#include "mozilla/gfx/2D.h"
+
+#include "cairo.h"
+
+#include <vector>
+
+using namespace mozilla::gfx;
+
+gfxPattern::gfxPattern(const DeviceColor& aColor) : mExtend(ExtendMode::CLAMP) {
+ mGfxPattern.InitColorPattern(aColor);
+}
+
+// linear
+gfxPattern::gfxPattern(gfxFloat x0, gfxFloat y0, gfxFloat x1, gfxFloat y1)
+ : mExtend(ExtendMode::CLAMP) {
+ mGfxPattern.InitLinearGradientPattern(Point(x0, y0), Point(x1, y1), nullptr);
+}
+
+// radial
+gfxPattern::gfxPattern(gfxFloat cx0, gfxFloat cy0, gfxFloat radius0,
+ gfxFloat cx1, gfxFloat cy1, gfxFloat radius1)
+ : mExtend(ExtendMode::CLAMP) {
+ mGfxPattern.InitRadialGradientPattern(Point(cx0, cy0), Point(cx1, cy1),
+ radius0, radius1, nullptr);
+}
+
+// conic
+gfxPattern::gfxPattern(gfxFloat cx, gfxFloat cy, gfxFloat angle,
+ gfxFloat startOffset, gfxFloat endOffset)
+ : mExtend(ExtendMode::CLAMP) {
+ mGfxPattern.InitConicGradientPattern(Point(cx, cy), angle, startOffset,
+ endOffset, nullptr);
+}
+
+// Azure
+gfxPattern::gfxPattern(SourceSurface* aSurface,
+ const Matrix& aPatternToUserSpace)
+ : mPatternToUserSpace(aPatternToUserSpace), mExtend(ExtendMode::CLAMP) {
+ mGfxPattern.InitSurfacePattern(
+ aSurface, mExtend, Matrix(), // matrix is overridden in GetPattern()
+ mozilla::gfx::SamplingFilter::GOOD);
+}
+
+void gfxPattern::AddColorStop(gfxFloat offset, const DeviceColor& c) {
+ if (mGfxPattern.GetPattern()->GetType() != PatternType::LINEAR_GRADIENT &&
+ mGfxPattern.GetPattern()->GetType() != PatternType::RADIAL_GRADIENT &&
+ mGfxPattern.GetPattern()->GetType() != PatternType::CONIC_GRADIENT) {
+ return;
+ }
+
+ mStops = nullptr;
+
+ GradientStop stop;
+ stop.offset = offset;
+ stop.color = c;
+ mStopsList.AppendElement(stop);
+}
+
+void gfxPattern::SetColorStops(GradientStops* aStops) { mStops = aStops; }
+
+void gfxPattern::CacheColorStops(const DrawTarget* aDT) {
+ mStops = gfxGradientCache::GetOrCreateGradientStops(aDT, mStopsList, mExtend);
+}
+
+void gfxPattern::SetMatrix(const gfxMatrix& aPatternToUserSpace) {
+ mPatternToUserSpace = ToMatrix(aPatternToUserSpace);
+ // Cairo-pattern matrices specify the conversion from DrawTarget to pattern
+ // space. Azure pattern matrices specify the conversion from pattern to
+ // DrawTarget space.
+ mPatternToUserSpace.Invert();
+}
+
+gfxMatrix gfxPattern::GetMatrix() const {
+ // invert at the higher precision of gfxMatrix
+ // cause we need to convert at some point anyways
+ gfxMatrix mat = ThebesMatrix(mPatternToUserSpace);
+ mat.Invert();
+ return mat;
+}
+
+gfxMatrix gfxPattern::GetInverseMatrix() const {
+ return ThebesMatrix(mPatternToUserSpace);
+}
+
+Pattern* gfxPattern::GetPattern(const DrawTarget* aTarget,
+ const Matrix* aOriginalUserToDevice) {
+ Matrix patternToUser = mPatternToUserSpace;
+
+ if (aOriginalUserToDevice &&
+ !aOriginalUserToDevice->FuzzyEquals(aTarget->GetTransform())) {
+ // mPatternToUserSpace maps from pattern space to the original user space,
+ // but aTarget now has a transform to a different user space. In order for
+ // the Pattern* that we return to be usable in aTarget's new user space we
+ // need the Pattern's mMatrix to be the transform from pattern space to
+ // aTarget's -new- user space. That transform is equivalent to the
+ // transform from pattern space to original user space (patternToUser),
+ // multiplied by the transform from original user space to device space,
+ // multiplied by the transform from device space to current user space.
+
+ Matrix deviceToCurrentUser = aTarget->GetTransform();
+ deviceToCurrentUser.Invert();
+
+ patternToUser =
+ patternToUser * *aOriginalUserToDevice * deviceToCurrentUser;
+ }
+ patternToUser.NudgeToIntegers();
+
+ if (!mStops && !mStopsList.IsEmpty()) {
+ mStops = aTarget->CreateGradientStops(mStopsList.Elements(),
+ mStopsList.Length(), mExtend);
+ }
+
+ switch (mGfxPattern.GetPattern()->GetType()) {
+ case PatternType::SURFACE: {
+ SurfacePattern* surfacePattern =
+ static_cast<SurfacePattern*>(mGfxPattern.GetPattern());
+ surfacePattern->mMatrix = patternToUser;
+ surfacePattern->mExtendMode = mExtend;
+ break;
+ }
+ case PatternType::LINEAR_GRADIENT: {
+ LinearGradientPattern* linearGradientPattern =
+ static_cast<LinearGradientPattern*>(mGfxPattern.GetPattern());
+ linearGradientPattern->mMatrix = patternToUser;
+ linearGradientPattern->mStops = mStops;
+ break;
+ }
+ case PatternType::RADIAL_GRADIENT: {
+ RadialGradientPattern* radialGradientPattern =
+ static_cast<RadialGradientPattern*>(mGfxPattern.GetPattern());
+ radialGradientPattern->mMatrix = patternToUser;
+ radialGradientPattern->mStops = mStops;
+ break;
+ }
+ case PatternType::CONIC_GRADIENT: {
+ ConicGradientPattern* conicGradientPattern =
+ static_cast<ConicGradientPattern*>(mGfxPattern.GetPattern());
+ conicGradientPattern->mMatrix = patternToUser;
+ conicGradientPattern->mStops = mStops;
+ break;
+ }
+ default:
+ /* Reassure the compiler we are handling all the enum values. */
+ break;
+ }
+
+ return mGfxPattern.GetPattern();
+}
+
+void gfxPattern::SetExtend(ExtendMode aExtend) {
+ mExtend = aExtend;
+ mStops = nullptr;
+}
+
+bool gfxPattern::IsOpaque() {
+ if (mGfxPattern.GetPattern()->GetType() != PatternType::SURFACE) {
+ return false;
+ }
+
+ if (static_cast<SurfacePattern*>(mGfxPattern.GetPattern())
+ ->mSurface->GetFormat() == SurfaceFormat::B8G8R8X8) {
+ return true;
+ }
+ return false;
+}
+
+void gfxPattern::SetSamplingFilter(mozilla::gfx::SamplingFilter filter) {
+ if (mGfxPattern.GetPattern()->GetType() != PatternType::SURFACE) {
+ return;
+ }
+
+ static_cast<SurfacePattern*>(mGfxPattern.GetPattern())->mSamplingFilter =
+ filter;
+}
+
+SamplingFilter gfxPattern::SamplingFilter() const {
+ if (mGfxPattern.GetPattern()->GetType() != PatternType::SURFACE) {
+ return mozilla::gfx::SamplingFilter::GOOD;
+ }
+ return static_cast<const SurfacePattern*>(mGfxPattern.GetPattern())
+ ->mSamplingFilter;
+}
+
+bool gfxPattern::GetSolidColor(DeviceColor& aColorOut) {
+ if (mGfxPattern.GetPattern()->GetType() == PatternType::COLOR) {
+ aColorOut = static_cast<ColorPattern*>(mGfxPattern.GetPattern())->mColor;
+ return true;
+ }
+
+ return false;
+}
diff --git a/gfx/thebes/gfxPattern.h b/gfx/thebes/gfxPattern.h
new file mode 100644
index 0000000000..cc8a7d5231
--- /dev/null
+++ b/gfx/thebes/gfxPattern.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_PATTERN_H
+#define GFX_PATTERN_H
+
+#include "gfxTypes.h"
+
+#include "gfxMatrix.h"
+#include "mozilla/Alignment.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/PatternHelpers.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+
+typedef struct _cairo_pattern cairo_pattern_t;
+
+class gfxPattern final {
+ NS_INLINE_DECL_REFCOUNTING(gfxPattern)
+
+ public:
+ explicit gfxPattern(const mozilla::gfx::DeviceColor& aColor);
+ // gradients
+ gfxPattern(gfxFloat x0, gfxFloat y0, gfxFloat x1, gfxFloat y1); // linear
+ gfxPattern(gfxFloat cx0, gfxFloat cy0, gfxFloat radius0, gfxFloat cx1,
+ gfxFloat cy1, gfxFloat radius1); // radial
+ gfxPattern(gfxFloat cx, gfxFloat cy, gfxFloat angle, gfxFloat startOffset,
+ gfxFloat endOffset); // conic
+ gfxPattern(mozilla::gfx::SourceSurface* aSurface,
+ const mozilla::gfx::Matrix& aPatternToUserSpace);
+
+ void AddColorStop(gfxFloat offset, const mozilla::gfx::DeviceColor& c);
+ void SetColorStops(mozilla::gfx::GradientStops* aStops);
+
+ // This should only be called on a cairo pattern that we want to use with
+ // Azure. We will read back the color stops from cairo and try to look
+ // them up in the cache.
+ void CacheColorStops(const mozilla::gfx::DrawTarget* aDT);
+
+ void SetMatrix(const gfxMatrix& matrix);
+ gfxMatrix GetMatrix() const;
+ gfxMatrix GetInverseMatrix() const;
+
+ /* Get an Azure Pattern for the current Cairo pattern. aPattern transform
+ * specifies the transform that was set on the DrawTarget when the pattern
+ * was set. When this is nullptr it is assumed the transform is identical
+ * to the current transform.
+ */
+ mozilla::gfx::Pattern* GetPattern(
+ const mozilla::gfx::DrawTarget* aTarget,
+ const mozilla::gfx::Matrix* aOriginalUserToDevice = nullptr);
+ bool IsOpaque();
+
+ // clamp, repeat, reflect
+ void SetExtend(mozilla::gfx::ExtendMode aExtend);
+
+ void SetSamplingFilter(mozilla::gfx::SamplingFilter aSamplingFilter);
+ mozilla::gfx::SamplingFilter SamplingFilter() const;
+
+ /* returns TRUE if it succeeded */
+ bool GetSolidColor(mozilla::gfx::DeviceColor& aColorOut);
+
+ private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~gfxPattern() = default;
+
+ mozilla::gfx::GeneralPattern mGfxPattern;
+ RefPtr<mozilla::gfx::SourceSurface> mSourceSurface;
+ mozilla::gfx::Matrix mPatternToUserSpace;
+ RefPtr<mozilla::gfx::GradientStops> mStops;
+ nsTArray<mozilla::gfx::GradientStop> mStopsList;
+ mozilla::gfx::ExtendMode mExtend;
+};
+
+#endif /* GFX_PATTERN_H */
diff --git a/gfx/thebes/gfxPlatform.cpp b/gfx/thebes/gfxPlatform.cpp
new file mode 100644
index 0000000000..c9293bd0db
--- /dev/null
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -0,0 +1,3902 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/RDDProcessManager.h"
+#include "mozilla/image/ImageMemoryReporter.h"
+#include "mozilla/layers/CompositorManagerChild.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/ImageBridgeChild.h"
+#include "mozilla/layers/ISurfaceAllocator.h" // for GfxMemoryImageReporter
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/layers/RemoteTextureMap.h"
+#include "mozilla/layers/VideoBridgeParent.h"
+#include "mozilla/webrender/RenderThread.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "mozilla/webrender/webrender_ffi.h"
+#include "mozilla/gfx/BuildConstants.h"
+#include "mozilla/gfx/gfxConfigManager.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "mozilla/gfx/GraphicsMessages.h"
+#include "mozilla/gfx/CanvasManagerChild.h"
+#include "mozilla/gfx/CanvasManagerParent.h"
+#include "mozilla/gfx/CanvasRenderThread.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/EnumTypeTraits.h"
+#include "mozilla/StaticPrefs_accessibility.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/StaticPrefs_bidi.h"
+#include "mozilla/StaticPrefs_canvas.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_layers.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StaticPrefs_webgl.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Unused.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Base64.h"
+#include "mozilla/VsyncDispatcher.h"
+
+#include "mozilla/Logging.h"
+#include "mozilla/Components.h"
+#include "nsAppRunner.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsCSSProps.h"
+#include "nsContentUtils.h"
+
+#include "gfxCrashReporterUtils.h"
+#include "gfxPlatform.h"
+#include "gfxPlatformWorker.h"
+
+#include "gfxBlur.h"
+#include "gfxEnv.h"
+#include "gfxTextRun.h"
+#include "gfxUserFontSet.h"
+#include "gfxConfig.h"
+#include "GfxDriverInfo.h"
+#include "VRProcessManager.h"
+#include "VRThread.h"
+
+#ifdef XP_WIN
+# include <process.h>
+# define getpid _getpid
+#else
+# include <unistd.h>
+#endif
+
+#include "nsXULAppAPI.h"
+#include "nsIXULAppInfo.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+
+#if defined(XP_WIN)
+# include "gfxWindowsPlatform.h"
+# include "mozilla/widget/WinWindowOcclusionTracker.h"
+#elif defined(XP_MACOSX)
+# include "gfxPlatformMac.h"
+# include "gfxQuartzSurface.h"
+# include "nsCocoaFeatures.h"
+#elif defined(MOZ_WIDGET_GTK)
+# include "gfxPlatformGtk.h"
+#elif defined(ANDROID)
+# include "gfxAndroidPlatform.h"
+#endif
+#if defined(MOZ_WIDGET_ANDROID)
+# include "mozilla/jni/Utils.h" // for IsFennec
+#endif
+
+#ifdef XP_WIN
+# include "mozilla/WindowsVersion.h"
+# include "WinUtils.h"
+#endif
+
+#include "nsGkAtoms.h"
+#include "gfxPlatformFontList.h"
+#include "gfxContext.h"
+#include "gfxImageSurface.h"
+#include "nsUnicodeProperties.h"
+#include "harfbuzz/hb.h"
+#include "gfxGraphiteShaper.h"
+#include "gfx2DGlue.h"
+#include "gfxGradientCache.h"
+#include "gfxUtils.h" // for NextPowerOfTwo
+#include "gfxFontMissingGlyphs.h"
+
+#include "nsExceptionHandler.h"
+#include "nsServiceManagerUtils.h"
+#include "nsTArray.h"
+#include "nsIObserverService.h"
+#include "mozilla/widget/Screen.h"
+#include "mozilla/widget/ScreenManager.h"
+#include "MainThreadUtils.h"
+
+#include "nsWeakReference.h"
+
+#include "cairo.h"
+#include "qcms.h"
+
+#include "imgITools.h"
+
+#include "nsCRT.h"
+#include "GLContext.h"
+#include "GLContextProvider.h"
+#include "mozilla/gfx/Logging.h"
+
+#ifdef __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wshadow"
+#endif
+#include "skia/include/core/SkGraphics.h"
+#ifdef MOZ_ENABLE_FREETYPE
+# include "skia/include/ports/SkTypeface_cairo.h"
+#endif
+#include "mozilla/gfx/SkMemoryReporter.h"
+#ifdef __GNUC__
+# pragma GCC diagnostic pop // -Wshadow
+#endif
+static const uint32_t kDefaultGlyphCacheSize = -1;
+
+#include "mozilla/Preferences.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+
+#include "nsAlgorithm.h"
+#include "nsIGfxInfo.h"
+#include "nsIXULRuntime.h"
+#include "VsyncSource.h"
+#include "SoftwareVsyncSource.h"
+#include "nscore.h" // for NS_FREE_PERMANENT_DATA
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/TouchEvent.h"
+#include "gfxVR.h"
+#include "VRManager.h"
+#include "VRManagerChild.h"
+#include "mozilla/gfx/GPUParent.h"
+#include "prsystem.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/SourceSurfaceCairo.h"
+
+using namespace mozilla;
+using namespace mozilla::layers;
+using namespace mozilla::gl;
+using namespace mozilla::gfx;
+
+gfxPlatform* gPlatform = nullptr;
+static bool gEverInitialized = false;
+
+const ContentDeviceData* gContentDeviceInitData = nullptr;
+
+Atomic<bool, MemoryOrdering::ReleaseAcquire> gfxPlatform::gCMSInitialized;
+CMSMode gfxPlatform::gCMSMode = CMSMode::Off;
+
+// These two may point to the same profile
+qcms_profile* gfxPlatform::gCMSOutputProfile = nullptr;
+qcms_profile* gfxPlatform::gCMSsRGBProfile = nullptr;
+
+qcms_transform* gfxPlatform::gCMSRGBTransform = nullptr;
+qcms_transform* gfxPlatform::gCMSInverseRGBTransform = nullptr;
+qcms_transform* gfxPlatform::gCMSRGBATransform = nullptr;
+qcms_transform* gfxPlatform::gCMSBGRATransform = nullptr;
+
+/// This override of the LogForwarder, initially used for the critical graphics
+/// errors, is sending the log to the crash annotations as well, but only
+/// if the capacity set with the method below is >= 2. We always retain the
+/// very first critical message, and the latest capacity-1 messages are
+/// rotated through. Note that we don't expect the total number of times
+/// this gets called to be large - it is meant for critical errors only.
+
+class CrashStatsLogForwarder : public mozilla::gfx::LogForwarder {
+ public:
+ explicit CrashStatsLogForwarder(CrashReporter::Annotation aKey);
+ void Log(const std::string& aString) override;
+ void CrashAction(LogReason aReason) override;
+ bool UpdateStringsVector(const std::string& aString) override;
+
+ LoggingRecord LoggingRecordCopy() override;
+
+ void SetCircularBufferSize(uint32_t aCapacity);
+
+ private:
+ // Helper for the Log()
+ void UpdateCrashReport();
+
+ private:
+ LoggingRecord mBuffer;
+ CrashReporter::Annotation mCrashCriticalKey;
+ uint32_t mMaxCapacity;
+ int32_t mIndex;
+ Mutex mMutex MOZ_UNANNOTATED;
+};
+
+CrashStatsLogForwarder::CrashStatsLogForwarder(CrashReporter::Annotation aKey)
+ : mBuffer(),
+ mCrashCriticalKey(aKey),
+ mMaxCapacity(0),
+ mIndex(-1),
+ mMutex("CrashStatsLogForwarder") {}
+
+void CrashStatsLogForwarder::SetCircularBufferSize(uint32_t aCapacity) {
+ MutexAutoLock lock(mMutex);
+
+ mMaxCapacity = aCapacity;
+ mBuffer.reserve(static_cast<size_t>(aCapacity));
+}
+
+LoggingRecord CrashStatsLogForwarder::LoggingRecordCopy() {
+ MutexAutoLock lock(mMutex);
+ return mBuffer;
+}
+
+bool CrashStatsLogForwarder::UpdateStringsVector(const std::string& aString) {
+ // We want at least the first one and the last one. Otherwise, no point.
+ if (mMaxCapacity < 2) {
+ return false;
+ }
+
+ mIndex += 1;
+ MOZ_ASSERT(mIndex >= 0);
+
+ // index will count 0, 1, 2, ..., max-1, 1, 2, ..., max-1, 1, 2, ...
+ int32_t index = mIndex ? (mIndex - 1) % (mMaxCapacity - 1) + 1 : 0;
+ MOZ_ASSERT(index >= 0 && index < (int32_t)mMaxCapacity);
+ MOZ_ASSERT(index <= mIndex && index <= (int32_t)mBuffer.size());
+
+ double tStamp = (TimeStamp::NowLoRes() - TimeStamp::ProcessCreation())
+ .ToSecondsSigDigits();
+
+ // Checking for index >= mBuffer.size(), rather than index == mBuffer.size()
+ // just out of paranoia, but we know index <= mBuffer.size().
+ LoggingRecordEntry newEntry(mIndex, aString, tStamp);
+ if (index >= static_cast<int32_t>(mBuffer.size())) {
+ mBuffer.push_back(newEntry);
+ } else {
+ mBuffer[index] = newEntry;
+ }
+ return true;
+}
+
+void CrashStatsLogForwarder::UpdateCrashReport() {
+ std::stringstream message;
+ std::string logAnnotation;
+
+ switch (XRE_GetProcessType()) {
+ case GeckoProcessType_Default:
+ logAnnotation = "|[";
+ break;
+ case GeckoProcessType_Content:
+ logAnnotation = "|[C";
+ break;
+ case GeckoProcessType_GPU:
+ logAnnotation = "|[G";
+ break;
+ default:
+ logAnnotation = "|[X";
+ break;
+ }
+
+ for (auto& it : mBuffer) {
+ message << logAnnotation << std::get<0>(it) << "]" << std::get<1>(it)
+ << " (t=" << std::get<2>(it) << ") ";
+ }
+
+ nsCString reportString(message.str().c_str());
+ nsresult annotated =
+ CrashReporter::AnnotateCrashReport(mCrashCriticalKey, reportString);
+
+ if (annotated != NS_OK) {
+ printf("Crash Annotation %s: %s",
+ CrashReporter::AnnotationToString(mCrashCriticalKey),
+ message.str().c_str());
+ }
+}
+
+class LogForwarderEvent : public Runnable {
+ virtual ~LogForwarderEvent() = default;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(LogForwarderEvent, Runnable)
+
+ explicit LogForwarderEvent(const nsCString& aMessage)
+ : mozilla::Runnable("LogForwarderEvent"), mMessage(aMessage) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread() &&
+ (XRE_IsContentProcess() || XRE_IsGPUProcess()));
+
+ if (XRE_IsContentProcess()) {
+ dom::ContentChild* cc = dom::ContentChild::GetSingleton();
+ Unused << cc->SendGraphicsError(mMessage);
+ } else if (XRE_IsGPUProcess()) {
+ GPUParent* gp = GPUParent::GetSingleton();
+ Unused << gp->SendGraphicsError(mMessage);
+ }
+
+ return NS_OK;
+ }
+
+ protected:
+ nsCString mMessage;
+};
+
+void CrashStatsLogForwarder::Log(const std::string& aString) {
+ MutexAutoLock lock(mMutex);
+
+ if (UpdateStringsVector(aString)) {
+ UpdateCrashReport();
+ }
+
+ // Add it to the parent strings
+ if (!XRE_IsParentProcess()) {
+ nsCString stringToSend(aString.c_str());
+ if (NS_IsMainThread()) {
+ if (XRE_IsContentProcess()) {
+ dom::ContentChild* cc = dom::ContentChild::GetSingleton();
+ Unused << cc->SendGraphicsError(stringToSend);
+ } else if (XRE_IsGPUProcess()) {
+ GPUParent* gp = GPUParent::GetSingleton();
+ Unused << gp->SendGraphicsError(stringToSend);
+ }
+ } else {
+ nsCOMPtr<nsIRunnable> r1 = new LogForwarderEvent(stringToSend);
+ NS_DispatchToMainThread(r1);
+ }
+ }
+}
+
+class CrashTelemetryEvent : public Runnable {
+ virtual ~CrashTelemetryEvent() = default;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(CrashTelemetryEvent, Runnable)
+
+ explicit CrashTelemetryEvent(uint32_t aReason)
+ : mozilla::Runnable("CrashTelemetryEvent"), mReason(aReason) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ Telemetry::Accumulate(Telemetry::GFX_CRASH, mReason);
+ return NS_OK;
+ }
+
+ protected:
+ uint32_t mReason;
+};
+
+void CrashStatsLogForwarder::CrashAction(LogReason aReason) {
+#ifndef RELEASE_OR_BETA
+ // Non-release builds crash by default, but will use telemetry
+ // if this environment variable is present.
+ static bool useTelemetry = gfxEnv::MOZ_GFX_CRASH_TELEMETRY();
+#else
+ // Release builds use telemetry by default, but will crash instead
+ // if this environment variable is present.
+ static bool useTelemetry = !gfxEnv::MOZ_GFX_CRASH_MOZ_CRASH();
+#endif
+
+ if (useTelemetry) {
+ // The callers need to assure that aReason is in the range
+ // that the telemetry call below supports.
+ if (NS_IsMainThread()) {
+ Telemetry::Accumulate(Telemetry::GFX_CRASH, (uint32_t)aReason);
+ } else {
+ nsCOMPtr<nsIRunnable> r1 = new CrashTelemetryEvent((uint32_t)aReason);
+ NS_DispatchToMainThread(r1);
+ }
+ } else {
+ // ignoring aReason, we can get the information we need from the stack
+ MOZ_CRASH("GFX_CRASH");
+ }
+}
+
+#define GFX_DOWNLOADABLE_FONTS_ENABLED "gfx.downloadable_fonts.enabled"
+
+#define GFX_PREF_FALLBACK_USE_CMAPS \
+ "gfx.font_rendering.fallback.always_use_cmaps"
+
+#define GFX_PREF_OPENTYPE_SVG "gfx.font_rendering.opentype_svg.enabled"
+
+#define GFX_PREF_WORD_CACHE_CHARLIMIT "gfx.font_rendering.wordcache.charlimit"
+#define GFX_PREF_WORD_CACHE_MAXENTRIES "gfx.font_rendering.wordcache.maxentries"
+
+#define GFX_PREF_GRAPHITE_SHAPING "gfx.font_rendering.graphite.enabled"
+#if defined(XP_MACOSX)
+# define GFX_PREF_CORETEXT_SHAPING "gfx.font_rendering.coretext.enabled"
+#endif
+
+#define FONT_VARIATIONS_PREF "layout.css.font-variations.enabled"
+
+static const char* kObservedPrefs[] = {"gfx.downloadable_fonts.",
+ "gfx.font_rendering.", nullptr};
+
+static void FontPrefChanged(const char* aPref, void* aData) {
+ MOZ_ASSERT(aPref);
+ NS_ASSERTION(gfxPlatform::GetPlatform(), "the singleton instance has gone");
+ gfxPlatform::GetPlatform()->FontsPrefsChanged(aPref);
+}
+
+void gfxPlatform::OnMemoryPressure(layers::MemoryPressureReason aWhy) {
+ Factory::PurgeAllCaches();
+ gfxGradientCache::PurgeAllCaches();
+ gfxFontMissingGlyphs::Purge();
+ PurgeSkiaFontCache();
+ if (XRE_IsParentProcess()) {
+ layers::CompositorManagerChild* manager =
+ CompositorManagerChild::GetInstance();
+ if (manager) {
+ manager->SendNotifyMemoryPressure();
+ }
+ }
+}
+
+gfxPlatform::gfxPlatform()
+ : mAzureCanvasBackendCollector(this, &gfxPlatform::GetAzureBackendInfo),
+ mApzSupportCollector(this, &gfxPlatform::GetApzSupportInfo),
+ mFrameStatsCollector(this, &gfxPlatform::GetFrameStats),
+ mCMSInfoCollector(this, &gfxPlatform::GetCMSSupportInfo),
+ mDisplayInfoCollector(this, &gfxPlatform::GetDisplayInfo),
+ mOverlayInfoCollector(this, &gfxPlatform::GetOverlayInfo),
+ mSwapChainInfoCollector(this, &gfxPlatform::GetSwapChainInfo),
+ mCompositorBackend(layers::LayersBackend::LAYERS_NONE),
+ mScreenDepth(0) {
+ mAllowDownloadableFonts = UNINITIALIZED_VALUE;
+
+ InitBackendPrefs(GetBackendPrefs());
+ VRManager::ManagerInit();
+}
+
+gfxPlatform* gfxPlatform::GetPlatform() {
+ if (!gPlatform) {
+ MOZ_RELEASE_ASSERT(!XRE_IsContentProcess(),
+ "Content Process should have called InitChild() before "
+ "first GetPlatform()");
+ Init();
+ }
+ return gPlatform;
+}
+
+bool gfxPlatform::Initialized() { return !!gPlatform; }
+
+/* static */
+void gfxPlatform::InitChild(const ContentDeviceData& aData) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+ MOZ_RELEASE_ASSERT(!gPlatform,
+ "InitChild() should be called before first GetPlatform()");
+ // Make the provided initial ContentDeviceData available to the init
+ // routines, so they don't have to do a sync request from the parent.
+ gContentDeviceInitData = &aData;
+ Init();
+ gContentDeviceInitData = nullptr;
+}
+
+#define WR_DEBUG_PREF "gfx.webrender.debug"
+
+static void SwapIntervalPrefChangeCallback(const char* aPrefName, void*) {
+ bool egl = Preferences::GetBool("gfx.swap-interval.egl", false);
+ bool glx = Preferences::GetBool("gfx.swap-interval.glx", false);
+ gfxVars::SetSwapIntervalEGL(egl);
+ gfxVars::SetSwapIntervalGLX(glx);
+}
+
+static void WebRendeProfilerUIPrefChangeCallback(const char* aPrefName, void*) {
+ nsCString uiString;
+ if (NS_SUCCEEDED(Preferences::GetCString("gfx.webrender.debug.profiler-ui",
+ uiString))) {
+ gfxVars::SetWebRenderProfilerUI(uiString);
+ }
+}
+
+// List of boolean dynamic parameter for WebRender.
+//
+// The parameters in this list are:
+// - The pref name.
+// - The BoolParameter enum variant (see webrender_api/src/lib.rs)
+// - A default value.
+#define WR_BOOL_PARAMETER_LIST(_) \
+ _("gfx.webrender.batched-texture-uploads", \
+ wr::BoolParameter::BatchedUploads, true) \
+ _("gfx.webrender.draw-calls-for-texture-copy", \
+ wr::BoolParameter::DrawCallsForTextureCopy, true) \
+ _("gfx.webrender.pbo-uploads", wr::BoolParameter::PboUploads, true) \
+ _("gfx.webrender.multithreading", wr::BoolParameter::Multithreading, true)
+
+static void WebRenderBoolParameterChangeCallback(const char*, void*) {
+ uint32_t bits = 0;
+
+#define WR_BOOL_PARAMETER(name, key, default_val) \
+ if (Preferences::GetBool(name, default_val)) { \
+ bits |= 1 << (uint32_t)key; \
+ }
+
+ WR_BOOL_PARAMETER_LIST(WR_BOOL_PARAMETER)
+#undef WR_BOOL_PARAMETER
+
+ gfx::gfxVars::SetWebRenderBoolParameters(bits);
+}
+
+static void RegisterWebRenderBoolParamCallback() {
+#define WR_BOOL_PARAMETER(name, _key, _default_val) \
+ Preferences::RegisterCallback(WebRenderBoolParameterChangeCallback, name);
+
+ WR_BOOL_PARAMETER_LIST(WR_BOOL_PARAMETER)
+#undef WR_BOOL_PARAMETER
+
+ WebRenderBoolParameterChangeCallback(nullptr, nullptr);
+}
+
+static void WebRenderDebugPrefChangeCallback(const char* aPrefName, void*) {
+ wr::DebugFlags flags{0};
+#define GFX_WEBRENDER_DEBUG(suffix, bit) \
+ if (Preferences::GetBool(WR_DEBUG_PREF suffix, false)) { \
+ flags |= (bit); \
+ }
+
+ GFX_WEBRENDER_DEBUG(".profiler", wr::DebugFlags::PROFILER_DBG)
+ GFX_WEBRENDER_DEBUG(".render-targets", wr::DebugFlags::RENDER_TARGET_DBG)
+ GFX_WEBRENDER_DEBUG(".texture-cache", wr::DebugFlags::TEXTURE_CACHE_DBG)
+ GFX_WEBRENDER_DEBUG(".gpu-time-queries", wr::DebugFlags::GPU_TIME_QUERIES)
+ GFX_WEBRENDER_DEBUG(".gpu-sample-queries", wr::DebugFlags::GPU_SAMPLE_QUERIES)
+ GFX_WEBRENDER_DEBUG(".disable-batching", wr::DebugFlags::DISABLE_BATCHING)
+ GFX_WEBRENDER_DEBUG(".epochs", wr::DebugFlags::EPOCHS)
+ GFX_WEBRENDER_DEBUG(".smart-profiler", wr::DebugFlags::SMART_PROFILER)
+ GFX_WEBRENDER_DEBUG(".echo-driver-messages",
+ wr::DebugFlags::ECHO_DRIVER_MESSAGES)
+ GFX_WEBRENDER_DEBUG(".show-overdraw", wr::DebugFlags::SHOW_OVERDRAW)
+ GFX_WEBRENDER_DEBUG(".gpu-cache", wr::DebugFlags::GPU_CACHE_DBG)
+ GFX_WEBRENDER_DEBUG(".texture-cache.clear-evicted",
+ wr::DebugFlags::TEXTURE_CACHE_DBG_CLEAR_EVICTED)
+ GFX_WEBRENDER_DEBUG(".picture-caching", wr::DebugFlags::PICTURE_CACHING_DBG)
+ GFX_WEBRENDER_DEBUG(".force-picture-invalidation",
+ wr::DebugFlags::FORCE_PICTURE_INVALIDATION)
+ GFX_WEBRENDER_DEBUG(".primitives", wr::DebugFlags::PRIMITIVE_DBG)
+ // Bit 18 is for the zoom display, which requires the mouse position and thus
+ // currently only works in wrench.
+ GFX_WEBRENDER_DEBUG(".small-screen", wr::DebugFlags::SMALL_SCREEN)
+ GFX_WEBRENDER_DEBUG(".disable-opaque-pass",
+ wr::DebugFlags::DISABLE_OPAQUE_PASS)
+ GFX_WEBRENDER_DEBUG(".disable-alpha-pass", wr::DebugFlags::DISABLE_ALPHA_PASS)
+ GFX_WEBRENDER_DEBUG(".disable-clip-masks", wr::DebugFlags::DISABLE_CLIP_MASKS)
+ GFX_WEBRENDER_DEBUG(".disable-text-prims", wr::DebugFlags::DISABLE_TEXT_PRIMS)
+ GFX_WEBRENDER_DEBUG(".disable-gradient-prims",
+ wr::DebugFlags::DISABLE_GRADIENT_PRIMS)
+ GFX_WEBRENDER_DEBUG(".obscure-images", wr::DebugFlags::OBSCURE_IMAGES)
+ GFX_WEBRENDER_DEBUG(".glyph-flashing", wr::DebugFlags::GLYPH_FLASHING)
+ GFX_WEBRENDER_DEBUG(".capture-profiler", wr::DebugFlags::PROFILER_CAPTURE)
+ GFX_WEBRENDER_DEBUG(".window-visibility",
+ wr::DebugFlags::WINDOW_VISIBILITY_DBG)
+ GFX_WEBRENDER_DEBUG(".restrict-blob-size", wr::DebugFlags::RESTRICT_BLOB_SIZE)
+#undef GFX_WEBRENDER_DEBUG
+ gfx::gfxVars::SetWebRenderDebugFlags(flags.bits);
+}
+
+static void WebRenderQualityPrefChangeCallback(const char* aPref, void*) {
+ gfxPlatform::GetPlatform()->UpdateForceSubpixelAAWherePossible();
+}
+
+static void WebRenderBatchingPrefChangeCallback(const char* aPrefName, void*) {
+ uint32_t count = Preferences::GetUint(
+ StaticPrefs::GetPrefName_gfx_webrender_batching_lookback(), 10);
+
+ gfx::gfxVars::SetWebRenderBatchingLookback(count);
+}
+
+static void WebRenderBlobTileSizePrefChangeCallback(const char* aPrefName,
+ void*) {
+ uint32_t tileSize = Preferences::GetUint(
+ StaticPrefs::GetPrefName_gfx_webrender_blob_tile_size(), 256);
+ gfx::gfxVars::SetWebRenderBlobTileSize(tileSize);
+}
+
+static void WebRenderUploadThresholdPrefChangeCallback(const char* aPrefName,
+ void*) {
+ int value = Preferences::GetInt(
+ StaticPrefs::GetPrefName_gfx_webrender_batched_upload_threshold(),
+ 512 * 512);
+
+ gfxVars::SetWebRenderBatchedUploadThreshold(value);
+}
+
+static uint32_t GetSkiaGlyphCacheSize() {
+ // Only increase font cache size on non-android to save memory.
+#if !defined(MOZ_WIDGET_ANDROID)
+ // 10mb as the default pref cache size on desktop due to talos perf tweaking.
+ // Chromium uses 20mb and skia default uses 2mb.
+ // We don't need to change the font cache count since we usually
+ // cache thrash due to asian character sets in talos.
+ // Only increase memory on the content process
+ uint32_t cacheSize =
+ StaticPrefs::gfx_content_skia_font_cache_size_AtStartup() * 1024 * 1024;
+ if (mozilla::BrowserTabsRemoteAutostart()) {
+ return XRE_IsContentProcess() ? cacheSize : kDefaultGlyphCacheSize;
+ }
+
+ return cacheSize;
+#else
+ return kDefaultGlyphCacheSize;
+#endif // MOZ_WIDGET_ANDROID
+}
+
+class WebRenderMemoryReporter final : public nsIMemoryReporter {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+
+ private:
+ ~WebRenderMemoryReporter() = default;
+};
+
+// Memory reporter for WebRender.
+//
+// The reporting within WebRender is manual and incomplete. We could do a much
+// more thorough job by depending on the malloc_size_of crate, but integrating
+// that into WebRender is tricky [1].
+//
+// So the idea is to start with manual reporting for the large allocations
+// detected by DMD, and see how much that can cover in practice (which may
+// require a few rounds of iteration). If that approach turns out to be
+// fundamentally insufficient, we can either duplicate more of the
+// malloc_size_of functionality in WebRender, or deal with the complexity of a
+// gecko-only crate dependency.
+//
+// [1] See https://bugzilla.mozilla.org/show_bug.cgi?id=1480293#c1
+struct WebRenderMemoryReporterHelper {
+ WebRenderMemoryReporterHelper(nsIHandleReportCallback* aCallback,
+ nsISupports* aData)
+ : mCallback(aCallback), mData(aData) {}
+ nsCOMPtr<nsIHandleReportCallback> mCallback;
+ nsCOMPtr<nsISupports> mData;
+
+ void Report(size_t aBytes, const char* aName) const {
+ nsPrintfCString path("explicit/gfx/webrender/%s", aName);
+ nsCString desc("CPU heap memory used by WebRender"_ns);
+ ReportInternal(aBytes, path, desc, nsIMemoryReporter::KIND_HEAP);
+ }
+
+ void ReportTexture(size_t aBytes, const char* aName) const {
+ nsPrintfCString path("gfx/webrender/textures/%s", aName);
+ nsCString desc("GPU texture memory used by WebRender"_ns);
+ ReportInternal(aBytes, path, desc, nsIMemoryReporter::KIND_OTHER);
+ }
+
+ void ReportTotalGPUBytes(size_t aBytes) const {
+ nsCString path("gfx/webrender/total-gpu-bytes"_ns);
+ nsCString desc(nsLiteralCString(
+ "Total GPU bytes used by WebRender (should match textures/ sum)"));
+ ReportInternal(aBytes, path, desc, nsIMemoryReporter::KIND_OTHER);
+ }
+
+ void ReportInternal(size_t aBytes, nsACString& aPath, nsACString& aDesc,
+ int32_t aKind) const {
+ // Generally, memory reporters pass the empty string as the process name to
+ // indicate "current process". However, if we're using a GPU process, the
+ // measurements will actually take place in that process, and it's easier to
+ // just note that here rather than trying to invoke the memory reporter in
+ // the GPU process.
+ nsAutoCString processName;
+ if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
+ GPUParent::GetGPUProcessName(processName);
+ }
+
+ mCallback->Callback(processName, aPath, aKind,
+ nsIMemoryReporter::UNITS_BYTES, aBytes, aDesc, mData);
+ }
+};
+
+static void FinishAsyncMemoryReport() {
+ nsCOMPtr<nsIMemoryReporterManager> imgr =
+ do_GetService("@mozilla.org/memory-reporter-manager;1");
+ if (imgr) {
+ imgr->EndReport();
+ }
+}
+
+// clang-format off
+// (For some reason, clang-format gets the second macro right, but totally mangles the first).
+#define REPORT_INTERNER(id) \
+ helper.Report(aReport.interning.interners.id, \
+ "interning/" #id "/interners");
+// clang-format on
+
+#define REPORT_DATA_STORE(id) \
+ helper.Report(aReport.interning.data_stores.id, \
+ "interning/" #id "/data-stores");
+
+NS_IMPL_ISUPPORTS(WebRenderMemoryReporter, nsIMemoryReporter)
+
+NS_IMETHODIMP
+WebRenderMemoryReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+ layers::CompositorManagerChild* manager =
+ CompositorManagerChild::GetInstance();
+ if (!manager) {
+ FinishAsyncMemoryReport();
+ return NS_OK;
+ }
+
+ WebRenderMemoryReporterHelper helper(aHandleReport, aData);
+ manager->SendReportMemory(
+ [=](wr::MemoryReport aReport) {
+ // CPU Memory.
+ helper.Report(aReport.clip_stores, "clip-stores");
+ helper.Report(aReport.gpu_cache_metadata, "gpu-cache/metadata");
+ helper.Report(aReport.gpu_cache_cpu_mirror, "gpu-cache/cpu-mirror");
+ helper.Report(aReport.render_tasks, "render-tasks");
+ helper.Report(aReport.hit_testers, "hit-testers");
+ helper.Report(aReport.fonts, "resource-cache/fonts");
+ helper.Report(aReport.weak_fonts, "resource-cache/weak-fonts");
+ helper.Report(aReport.images, "resource-cache/images");
+ helper.Report(aReport.rasterized_blobs,
+ "resource-cache/rasterized-blobs");
+ helper.Report(aReport.texture_cache_structures,
+ "texture-cache/structures");
+ helper.Report(aReport.shader_cache, "shader-cache");
+ helper.Report(aReport.display_list, "display-list");
+ helper.Report(aReport.swgl, "swgl");
+ helper.Report(aReport.upload_staging_memory, "upload-stagin-memory");
+
+ WEBRENDER_FOR_EACH_INTERNER(REPORT_INTERNER);
+ WEBRENDER_FOR_EACH_INTERNER(REPORT_DATA_STORE);
+
+ // GPU Memory.
+ helper.ReportTexture(aReport.gpu_cache_textures, "gpu-cache");
+ helper.ReportTexture(aReport.vertex_data_textures, "vertex-data");
+ helper.ReportTexture(aReport.render_target_textures, "render-targets");
+ helper.ReportTexture(aReport.depth_target_textures, "depth-targets");
+ helper.ReportTexture(aReport.picture_tile_textures, "picture-tiles");
+ helper.ReportTexture(aReport.atlas_textures, "texture-cache/atlas");
+ helper.ReportTexture(aReport.standalone_textures,
+ "texture-cache/standalone");
+ helper.ReportTexture(aReport.texture_upload_pbos,
+ "texture-upload-pbos");
+ helper.ReportTexture(aReport.swap_chain, "swap-chains");
+ helper.ReportTexture(aReport.render_texture_hosts,
+ "render-texture-hosts");
+ helper.ReportTexture(aReport.upload_staging_textures,
+ "upload-staging-textures");
+
+ FinishAsyncMemoryReport();
+ },
+ [](mozilla::ipc::ResponseRejectReason&& aReason) {
+ FinishAsyncMemoryReport();
+ });
+
+ return NS_OK;
+}
+
+#undef REPORT_INTERNER
+#undef REPORT_DATA_STORE
+
+std::atomic<int8_t> gfxPlatform::sHasVariationFontSupport = -1;
+
+bool gfxPlatform::HasVariationFontSupport() {
+ // We record the status here: 0 for not supported, 1 for supported.
+ if (sHasVariationFontSupport < 0) {
+ // It doesn't actually matter if we race with another thread setting this,
+ // as any thread will set it to the same value.
+#if defined(XP_WIN)
+ sHasVariationFontSupport = gfxWindowsPlatform::CheckVariationFontSupport();
+#elif defined(XP_MACOSX)
+ sHasVariationFontSupport = gfxPlatformMac::CheckVariationFontSupport();
+#elif defined(MOZ_WIDGET_GTK)
+ sHasVariationFontSupport = gfxPlatformGtk::CheckVariationFontSupport();
+#elif defined(ANDROID)
+ sHasVariationFontSupport = gfxAndroidPlatform::CheckVariationFontSupport();
+#else
+# error "No gfxPlatform implementation available"
+#endif
+ }
+ return sHasVariationFontSupport > 0;
+}
+
+void gfxPlatform::Init() {
+ MOZ_RELEASE_ASSERT(!XRE_IsGPUProcess(), "GFX: Not allowed in GPU process.");
+ MOZ_RELEASE_ASSERT(!XRE_IsRDDProcess(), "GFX: Not allowed in RDD process.");
+ MOZ_RELEASE_ASSERT(NS_IsMainThread(), "GFX: Not in main thread.");
+
+ if (gEverInitialized) {
+ MOZ_CRASH("Already started???");
+ }
+ gEverInitialized = true;
+
+ gfxVars::Initialize();
+
+ gfxConfig::Init();
+
+ if (XRE_IsParentProcess()) {
+ GPUProcessManager::Initialize();
+ RDDProcessManager::Initialize();
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ gfxVars::SetGREDirectory(nsString());
+ } else {
+ nsAutoString path;
+ file->GetPath(path);
+ gfxVars::SetGREDirectory(nsString(path));
+ }
+ }
+
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsIFile> profDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_PROFILE_DIR_STARTUP,
+ getter_AddRefs(profDir));
+ if (NS_FAILED(rv)) {
+ gfxVars::SetProfDirectory(nsString());
+ } else {
+ nsAutoString path;
+ profDir->GetPath(path);
+ gfxVars::SetProfDirectory(nsString(path));
+ }
+
+ nsAutoCString path;
+ Preferences::GetCString("layers.windowrecording.path", path);
+ gfxVars::SetLayersWindowRecordingPath(path);
+
+ if (gFxREmbedded) {
+ gfxVars::SetFxREmbedded(true);
+ }
+ }
+
+ // Drop a note in the crash report if we end up forcing an option that could
+ // destabilize things. New items should be appended at the end (of an
+ // existing or in a new section), so that we don't have to know the version to
+ // interpret these cryptic strings.
+ {
+ nsAutoCString forcedPrefs;
+ // D2D prefs
+ forcedPrefs.AppendPrintf(
+ "FP(D%d%d", StaticPrefs::gfx_direct2d_disabled_AtStartup(),
+ StaticPrefs::gfx_direct2d_force_enabled_AtStartup());
+ // Layers prefs
+ forcedPrefs.AppendPrintf(
+ "-L%d%d%d%d",
+ StaticPrefs::layers_amd_switchable_gfx_enabled_AtStartup(),
+ StaticPrefs::layers_acceleration_disabled_AtStartup_DoNotUseDirectly(),
+ StaticPrefs::
+ layers_acceleration_force_enabled_AtStartup_DoNotUseDirectly(),
+ StaticPrefs::layers_d3d11_force_warp_AtStartup());
+ // WebGL prefs
+ forcedPrefs.AppendPrintf(
+ "-W%d%d%d%d%d%d%d", StaticPrefs::webgl_angle_force_d3d11(),
+ StaticPrefs::webgl_angle_force_warp(), StaticPrefs::webgl_disabled(),
+ StaticPrefs::webgl_disable_angle(), StaticPrefs::webgl_dxgl_enabled(),
+ StaticPrefs::webgl_force_enabled(), StaticPrefs::webgl_msaa_force());
+ // Prefs that don't fit into any of the other sections
+ forcedPrefs.AppendPrintf("-T%d%d%d) ",
+ StaticPrefs::gfx_android_rgb16_force_AtStartup(),
+ StaticPrefs::gfx_canvas_accelerated(),
+ StaticPrefs::layers_force_shmem_tiles_AtStartup());
+ ScopedGfxFeatureReporter::AppNote(forcedPrefs);
+ }
+
+ InitMoz2DLogging();
+
+ /* Initialize the GfxInfo service.
+ * Note: we can't call functions on GfxInfo that depend
+ * on gPlatform until after it has been initialized
+ * below. GfxInfo initialization annotates our
+ * crash reports so we want to do it before
+ * we try to load any drivers and do device detection
+ * incase that code crashes. See bug #591561. */
+ nsCOMPtr<nsIGfxInfo> gfxInfo;
+ /* this currently will only succeed on Windows */
+ gfxInfo = components::GfxInfo::Service();
+
+ if (XRE_IsParentProcess()) {
+ // Some gfxVars must be initialized prior gPlatform for coherent results.
+ gfxVars::SetDXInterop2Blocked(IsDXInterop2Blocked());
+ gfxVars::SetDXNV12Blocked(IsDXNV12Blocked());
+ gfxVars::SetDXP010Blocked(IsDXP010Blocked());
+ gfxVars::SetDXP016Blocked(IsDXP016Blocked());
+ }
+
+#if defined(XP_WIN)
+ gPlatform = new gfxWindowsPlatform;
+#elif defined(XP_MACOSX)
+ gPlatform = new gfxPlatformMac;
+#elif defined(MOZ_WIDGET_GTK)
+ gPlatform = new gfxPlatformGtk;
+#elif defined(ANDROID)
+ gPlatform = new gfxAndroidPlatform;
+#else
+# error "No gfxPlatform implementation available"
+#endif
+ gPlatform->PopulateScreenInfo();
+ gPlatform->InitAcceleration();
+ gPlatform->InitWebRenderConfig();
+
+ gPlatform->InitHardwareVideoConfig();
+ gPlatform->InitWebGLConfig();
+ gPlatform->InitWebGPUConfig();
+ gPlatform->InitWindowOcclusionConfig();
+ gPlatform->InitBackdropFilterConfig();
+ gPlatform->InitAcceleratedCanvas2DConfig();
+
+#if defined(XP_WIN)
+ // When using WebRender, we defer initialization of the D3D11 devices until
+ // the (rare) cases where they're used. Note that the GPU process where
+ // WebRender runs doesn't initialize gfxPlatform and performs explicit
+ // initialization of the bits it needs.
+ if (XRE_IsParentProcess() && !gfxConfig::IsEnabled(Feature::GPU_PROCESS) &&
+ StaticPrefs::
+ gfx_webrender_enabled_no_gpu_process_with_angle_win_AtStartup()) {
+ gPlatform->EnsureDevicesInitialized();
+ }
+#endif
+
+ if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
+ GPUProcessManager* gpu = GPUProcessManager::Get();
+ Unused << gpu->LaunchGPUProcess();
+ }
+
+ if (XRE_IsParentProcess()) {
+ // Create the global vsync source and dispatcher.
+ RefPtr<VsyncSource> vsyncSource =
+ gfxPlatform::ForceSoftwareVsync()
+ ? gPlatform->GetSoftwareVsyncSource()
+ : gPlatform->GetGlobalHardwareVsyncSource();
+ gPlatform->mVsyncDispatcher = new VsyncDispatcher(vsyncSource);
+
+ // Listen for layout.frame_rate pref changes.
+ Preferences::RegisterCallback(
+ gfxPlatform::ReInitFrameRate,
+ nsDependentCString(StaticPrefs::GetPrefName_layout_frame_rate()));
+ Preferences::RegisterCallback(
+ gfxPlatform::ReInitFrameRate,
+ nsDependentCString(
+ StaticPrefs::GetPrefName_privacy_resistFingerprinting()));
+ }
+
+ // Create the sRGB to output display profile transforms. They can be accessed
+ // off the main thread so we want to avoid a race condition.
+ InitializeCMS();
+
+ SkGraphics::Init();
+#ifdef MOZ_ENABLE_FREETYPE
+ SkInitCairoFT(gPlatform->FontHintingEnabled());
+#endif
+ gfxGradientCache::Init();
+
+ InitLayersIPC();
+
+ // This *create* the platform font list instance, but may not *initialize* it
+ // yet if the gfx.font-list.lazy-init.enabled pref is set. The first *use*
+ // of the list will ensure it is initialized.
+ if (!gPlatform->CreatePlatformFontList()) {
+ MOZ_CRASH("Could not initialize gfxPlatformFontList");
+ }
+
+ gPlatform->mScreenReferenceDrawTarget =
+ gPlatform->CreateOffscreenContentDrawTarget(IntSize(1, 1),
+ SurfaceFormat::B8G8R8A8);
+ if (!gPlatform->mScreenReferenceDrawTarget ||
+ !gPlatform->mScreenReferenceDrawTarget->IsValid()) {
+ // If TDR is detected, create a draw target with software backend
+ // and it should be replaced later when the process gets the device
+ // reset notification.
+ if (!gPlatform->DidRenderingDeviceReset()) {
+ gfxCriticalError() << "Could not initialize mScreenReferenceDrawTarget";
+ }
+ }
+
+ if (NS_FAILED(gfxFontCache::Init())) {
+ MOZ_CRASH("Could not initialize gfxFontCache");
+ }
+
+ Preferences::RegisterPrefixCallbacks(FontPrefChanged, kObservedPrefs);
+
+ GLContext::PlatformStartup();
+
+ // Listen to memory pressure event so we can purge DrawTarget caches
+ gPlatform->mMemoryPressureObserver =
+ layers::MemoryPressureObserver::Create(gPlatform);
+
+ // Request the imgITools service, implicitly initializing ImageLib.
+ nsCOMPtr<imgITools> imgTools = do_GetService("@mozilla.org/image/tools;1");
+ if (!imgTools) {
+ MOZ_CRASH("Could not initialize ImageLib");
+ }
+
+ RegisterStrongMemoryReporter(new GfxMemoryImageReporter());
+ if (XRE_IsParentProcess()) {
+ RegisterStrongAsyncMemoryReporter(new WebRenderMemoryReporter());
+ }
+
+ RegisterStrongMemoryReporter(new SkMemoryReporter());
+
+ uint32_t skiaCacheSize = GetSkiaGlyphCacheSize();
+ if (skiaCacheSize != kDefaultGlyphCacheSize) {
+ SkGraphics::SetFontCacheLimit(skiaCacheSize);
+ }
+
+ InitNullMetadata();
+ InitOpenGLConfig();
+
+ if (XRE_IsParentProcess()) {
+ Preferences::Unlock(FONT_VARIATIONS_PREF);
+ if (!gfxPlatform::HasVariationFontSupport()) {
+ // Ensure variation fonts are disabled and the pref is locked.
+ Preferences::SetBool(FONT_VARIATIONS_PREF, false, PrefValueKind::Default);
+ Preferences::SetBool(FONT_VARIATIONS_PREF, false);
+ Preferences::Lock(FONT_VARIATIONS_PREF);
+ }
+ }
+
+ if (XRE_IsParentProcess()) {
+ ReportTelemetry();
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, "gfx-features-ready", nullptr);
+ }
+}
+
+void gfxPlatform::ReportTelemetry() {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess(),
+ "GFX: Only allowed to be called from parent process.");
+
+ nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
+
+ {
+ auto& screenManager = widget::ScreenManager::GetSingleton();
+ const uint32_t screenCount = screenManager.CurrentScreenList().Length();
+ RefPtr<widget::Screen> primaryScreen = screenManager.GetPrimaryScreen();
+ const LayoutDeviceIntRect rect = primaryScreen->GetRect();
+
+ Telemetry::ScalarSet(Telemetry::ScalarID::GFX_DISPLAY_COUNT, screenCount);
+ Telemetry::ScalarSet(Telemetry::ScalarID::GFX_DISPLAY_PRIMARY_HEIGHT,
+ uint32_t(rect.Height()));
+ Telemetry::ScalarSet(Telemetry::ScalarID::GFX_DISPLAY_PRIMARY_WIDTH,
+ uint32_t(rect.Width()));
+ }
+
+ nsString adapterDesc;
+ gfxInfo->GetAdapterDescription(adapterDesc);
+ Telemetry::ScalarSet(Telemetry::ScalarID::GFX_ADAPTER_DESCRIPTION,
+ adapterDesc);
+
+ nsString adapterVendorId;
+ gfxInfo->GetAdapterVendorID(adapterVendorId);
+ Telemetry::ScalarSet(Telemetry::ScalarID::GFX_ADAPTER_VENDOR_ID,
+ adapterVendorId);
+
+ nsString adapterDeviceId;
+ gfxInfo->GetAdapterDeviceID(adapterDeviceId);
+ Telemetry::ScalarSet(Telemetry::ScalarID::GFX_ADAPTER_DEVICE_ID,
+ adapterDeviceId);
+
+ nsString adapterSubsystemId;
+ gfxInfo->GetAdapterSubsysID(adapterSubsystemId);
+ Telemetry::ScalarSet(Telemetry::ScalarID::GFX_ADAPTER_SUBSYSTEM_ID,
+ adapterSubsystemId);
+
+ uint32_t adapterRam = 0;
+ gfxInfo->GetAdapterRAM(&adapterRam);
+ Telemetry::ScalarSet(Telemetry::ScalarID::GFX_ADAPTER_RAM, adapterRam);
+
+ nsString adapterDriver;
+ gfxInfo->GetAdapterDriver(adapterDriver);
+ Telemetry::ScalarSet(Telemetry::ScalarID::GFX_ADAPTER_DRIVER_FILES,
+ adapterDriver);
+
+ nsString adapterDriverVendor;
+ gfxInfo->GetAdapterDriverVendor(adapterDriverVendor);
+ Telemetry::ScalarSet(Telemetry::ScalarID::GFX_ADAPTER_DRIVER_VENDOR,
+ adapterDriverVendor);
+
+ nsString adapterDriverVersion;
+ gfxInfo->GetAdapterDriverVersion(adapterDriverVersion);
+ Telemetry::ScalarSet(Telemetry::ScalarID::GFX_ADAPTER_DRIVER_VERSION,
+ adapterDriverVersion);
+
+ nsString adapterDriverDate;
+ gfxInfo->GetAdapterDriverDate(adapterDriverDate);
+ Telemetry::ScalarSet(Telemetry::ScalarID::GFX_ADAPTER_DRIVER_DATE,
+ adapterDriverDate);
+
+ Telemetry::ScalarSet(Telemetry::ScalarID::GFX_HEADLESS, IsHeadless());
+
+ MOZ_ASSERT(gPlatform, "Need gPlatform to generate some telemetry.");
+ Telemetry::ScalarSet(Telemetry::ScalarID::GFX_SUPPORTS_HDR,
+ gPlatform->SupportsHDR());
+}
+
+static bool IsFeatureSupported(long aFeature, bool aDefault) {
+ nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
+ nsCString blockId;
+ int32_t status;
+ if (!NS_SUCCEEDED(gfxInfo->GetFeatureStatus(aFeature, blockId, &status))) {
+ return aDefault;
+ }
+ return status == nsIGfxInfo::FEATURE_STATUS_OK;
+}
+
+/* static*/
+bool gfxPlatform::IsDXInterop2Blocked() {
+ return !IsFeatureSupported(nsIGfxInfo::FEATURE_DX_INTEROP2, false);
+}
+
+/* static*/
+bool gfxPlatform::IsDXNV12Blocked() {
+ return !IsFeatureSupported(nsIGfxInfo::FEATURE_DX_NV12, false);
+}
+
+/* static*/
+bool gfxPlatform::IsDXP010Blocked() {
+ return !IsFeatureSupported(nsIGfxInfo::FEATURE_DX_P010, false);
+}
+
+/* static*/
+bool gfxPlatform::IsDXP016Blocked() {
+ return !IsFeatureSupported(nsIGfxInfo::FEATURE_DX_P016, false);
+}
+
+/* static */
+int32_t gfxPlatform::MaxTextureSize() {
+ // Make sure we don't completely break rendering because of a typo in the
+ // pref or whatnot.
+ const int32_t kMinSizePref = 2048;
+ return std::max(
+ kMinSizePref,
+ StaticPrefs::gfx_max_texture_size_AtStartup_DoNotUseDirectly());
+}
+
+/* static */
+int32_t gfxPlatform::MaxAllocSize() {
+ // Make sure we don't completely break rendering because of a typo in the
+ // pref or whatnot.
+ const int32_t kMinAllocPref = 10000000;
+ return std::max(kMinAllocPref,
+ StaticPrefs::gfx_max_alloc_size_AtStartup_DoNotUseDirectly());
+}
+
+/* static */
+void gfxPlatform::InitMoz2DLogging() {
+ auto fwd = new CrashStatsLogForwarder(
+ CrashReporter::Annotation::GraphicsCriticalError);
+ fwd->SetCircularBufferSize(StaticPrefs::gfx_logging_crash_length_AtStartup());
+
+ mozilla::gfx::Config cfg;
+ cfg.mLogForwarder = fwd;
+ cfg.mMaxTextureSize = gfxPlatform::MaxTextureSize();
+ cfg.mMaxAllocSize = gfxPlatform::MaxAllocSize();
+
+ gfx::Factory::Init(cfg);
+}
+
+/* static */
+bool gfxPlatform::IsHeadless() {
+ static bool initialized = false;
+ static bool headless = false;
+ if (!initialized) {
+ initialized = true;
+ headless = PR_GetEnv("MOZ_HEADLESS");
+ }
+ return headless;
+}
+
+/* static */
+bool gfxPlatform::UseRemoteCanvas() {
+ return XRE_IsContentProcess() && gfx::gfxVars::RemoteCanvasEnabled();
+}
+
+/* static */
+bool gfxPlatform::IsBackendAccelerated(
+ const mozilla::gfx::BackendType aBackendType) {
+ return aBackendType == BackendType::DIRECT2D ||
+ aBackendType == BackendType::DIRECT2D1_1;
+}
+
+/* static */
+bool gfxPlatform::CanMigrateMacGPUs() {
+ int32_t pMigration = StaticPrefs::gfx_compositor_gpu_migration();
+
+ bool forceDisable = pMigration == 0;
+ bool forceEnable = pMigration == 2;
+
+ return forceEnable || !forceDisable;
+}
+
+static bool sLayersIPCIsUp = false;
+
+/* static */
+void gfxPlatform::InitNullMetadata() {
+ ScrollMetadata::sNullMetadata = new ScrollMetadata();
+ ClearOnShutdown(&ScrollMetadata::sNullMetadata);
+}
+
+void gfxPlatform::Shutdown() {
+ // In some cases, gPlatform may not be created but Shutdown() called,
+ // e.g., during xpcshell tests.
+ if (!gPlatform) {
+ return;
+ }
+
+ MOZ_ASSERT(!sLayersIPCIsUp);
+
+ // These may be called before the corresponding subsystems have actually
+ // started up. That's OK, they can handle it.
+ gfxFontCache::Shutdown();
+ gfxGradientCache::Shutdown();
+ gfxAlphaBoxBlur::ShutdownBlurCache();
+ gfxGraphiteShaper::Shutdown();
+ gfxPlatformFontList::Shutdown();
+ gfxFontMissingGlyphs::Shutdown();
+
+ // Free the various non-null transforms and loaded profiles
+ ShutdownCMS();
+
+ Preferences::UnregisterPrefixCallbacks(FontPrefChanged, kObservedPrefs);
+
+ NS_ASSERTION(gPlatform->mMemoryPressureObserver,
+ "mMemoryPressureObserver has already gone");
+ if (gPlatform->mMemoryPressureObserver) {
+ gPlatform->mMemoryPressureObserver->Unregister();
+ gPlatform->mMemoryPressureObserver = nullptr;
+ }
+
+ if (XRE_IsParentProcess()) {
+ if (gPlatform->mGlobalHardwareVsyncSource) {
+ gPlatform->mGlobalHardwareVsyncSource->Shutdown();
+ }
+ if (gPlatform->mSoftwareVsyncSource &&
+ gPlatform->mSoftwareVsyncSource !=
+ gPlatform->mGlobalHardwareVsyncSource) {
+ gPlatform->mSoftwareVsyncSource->Shutdown();
+ }
+ }
+
+ gPlatform->mGlobalHardwareVsyncSource = nullptr;
+ gPlatform->mSoftwareVsyncSource = nullptr;
+ gPlatform->mVsyncDispatcher = nullptr;
+
+ // Shut down the default GL context provider.
+ GLContextProvider::Shutdown();
+
+#if defined(XP_WIN)
+ // The above shutdown calls operate on the available context providers on
+ // most platforms. Windows is a "special snowflake", though, and has three
+ // context providers available, so we have to shut all of them down.
+ // We should only support the default GL provider on Windows; then, this
+ // could go away. Unfortunately, we currently support WGL (the default) for
+ // WebGL on Optimus.
+ GLContextProviderEGL::Shutdown();
+#endif
+
+ if (XRE_IsParentProcess()) {
+ GPUProcessManager::Shutdown();
+ VRProcessManager::Shutdown();
+ RDDProcessManager::Shutdown();
+ }
+
+ gfx::Factory::ShutDown();
+ gfxVars::Shutdown();
+ gfxFont::DestroySingletons();
+
+ gfxConfig::Shutdown();
+
+ gPlatform->WillShutdown();
+
+ delete gPlatform;
+ gPlatform = nullptr;
+}
+
+/* static */
+void gfxPlatform::InitLayersIPC() {
+ if (sLayersIPCIsUp) {
+ return;
+ }
+ sLayersIPCIsUp = true;
+
+ if (XRE_IsParentProcess()) {
+#if defined(XP_WIN)
+ if (gfxConfig::IsEnabled(gfx::Feature::WINDOW_OCCLUSION)) {
+ widget::WinWindowOcclusionTracker::Ensure();
+ }
+#endif
+ if (!gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
+ RemoteTextureMap::Init();
+ if (gfxVars::UseCanvasRenderThread()) {
+ gfx::CanvasRenderThread::Start();
+ }
+ wr::RenderThread::Start(GPUProcessManager::Get()->AllocateNamespace());
+ image::ImageMemoryReporter::InitForWebRender();
+ }
+
+ layers::CompositorThreadHolder::Start();
+ }
+}
+
+/* static */
+void gfxPlatform::ShutdownLayersIPC() {
+ if (!sLayersIPCIsUp) {
+ return;
+ }
+ sLayersIPCIsUp = false;
+
+ if (XRE_IsContentProcess()) {
+ gfx::VRManagerChild::ShutDown();
+ gfx::CanvasManagerChild::Shutdown();
+ // cf bug 1215265.
+ if (StaticPrefs::layers_child_process_shutdown()) {
+ layers::CompositorManagerChild::Shutdown();
+ layers::ImageBridgeChild::ShutDown();
+ }
+
+ } else if (XRE_IsParentProcess()) {
+ VideoBridgeParent::Shutdown();
+ RDDProcessManager::RDDProcessShutdown();
+ gfx::VRManagerChild::ShutDown();
+ gfx::CanvasManagerChild::Shutdown();
+ layers::CompositorManagerChild::Shutdown();
+ layers::ImageBridgeChild::ShutDown();
+ // This could be running on either the Compositor or the Renderer thread.
+ gfx::CanvasManagerParent::Shutdown();
+ // This has to happen after shutting down the child protocols.
+ layers::CompositorThreadHolder::Shutdown();
+ RemoteTextureMap::Shutdown();
+ image::ImageMemoryReporter::ShutdownForWebRender();
+ // There is a case that RenderThread exists when UseWebRender() is
+ // false. This could happen when WebRender was fallbacked to compositor.
+ if (wr::RenderThread::Get()) {
+ wr::RenderThread::ShutDown();
+
+ Preferences::UnregisterCallback(WebRenderDebugPrefChangeCallback,
+ WR_DEBUG_PREF);
+ Preferences::UnregisterCallback(WebRendeProfilerUIPrefChangeCallback,
+ "gfx.webrender.debug.profiler-ui");
+ Preferences::UnregisterCallback(
+ WebRenderBlobTileSizePrefChangeCallback,
+ nsDependentCString(
+ StaticPrefs::GetPrefName_gfx_webrender_blob_tile_size()));
+ }
+ if (gfx::CanvasRenderThread::Get()) {
+ gfx::CanvasRenderThread::ShutDown();
+ }
+#if defined(XP_WIN)
+ widget::WinWindowOcclusionTracker::ShutDown();
+#endif
+ } else {
+ // TODO: There are other kind of processes and we should make sure gfx
+ // stuff is either not created there or shut down properly.
+ }
+}
+
+void gfxPlatform::WillShutdown() {
+ // Destoy these first in case they depend on backend-specific resources.
+ // Otherwise, the backend's destructor would be called before the
+ // base gfxPlatform destructor.
+ mScreenReferenceSurface = nullptr;
+ mScreenReferenceDrawTarget = nullptr;
+
+ // Always clear out the Skia font cache here, in case it is referencing any
+ // SharedFTFaces that would otherwise outlive destruction of the FT_Library
+ // that owns them.
+ SkGraphics::PurgeFontCache();
+
+ // The cairo folks think we should only clean up in debug builds,
+ // but we're generally in the habit of trying to shut down as
+ // cleanly as possible even in production code, so call this
+ // cairo_debug_* function unconditionally.
+ //
+ // because cairo can assert and thus crash on shutdown, don't do this in
+ // release builds
+#ifdef NS_FREE_PERMANENT_DATA
+ cairo_debug_reset_static_data();
+#endif
+}
+
+gfxPlatform::~gfxPlatform() = default;
+
+/* static */
+already_AddRefed<DrawTarget> gfxPlatform::CreateDrawTargetForSurface(
+ gfxASurface* aSurface, const IntSize& aSize) {
+ SurfaceFormat format = aSurface->GetSurfaceFormat();
+ RefPtr<DrawTarget> drawTarget = Factory::CreateDrawTargetForCairoSurface(
+ aSurface->CairoSurface(), aSize, &format);
+ if (!drawTarget) {
+ gfxWarning() << "gfxPlatform::CreateDrawTargetForSurface failed in "
+ "CreateDrawTargetForCairoSurface";
+ return nullptr;
+ }
+ return drawTarget.forget();
+}
+
+cairo_user_data_key_t kSourceSurface;
+
+/**
+ * Record the backend that was used to construct the SourceSurface.
+ * When getting the cached SourceSurface for a gfxASurface/DrawTarget pair,
+ * we check to make sure the DrawTarget's backend matches the backend
+ * for the cached SourceSurface, and only use it if they match. This
+ * can avoid expensive and unnecessary readbacks.
+ */
+struct SourceSurfaceUserData {
+ RefPtr<SourceSurface> mSrcSurface;
+ BackendType mBackendType;
+};
+
+static void SourceBufferDestroy(void* srcSurfUD) {
+ delete static_cast<SourceSurfaceUserData*>(srcSurfUD);
+}
+
+UserDataKey kThebesSurface;
+
+struct DependentSourceSurfaceUserData {
+ RefPtr<gfxASurface> mSurface;
+};
+
+static void SourceSurfaceDestroyed(void* aData) {
+ delete static_cast<DependentSourceSurfaceUserData*>(aData);
+}
+
+void gfxPlatform::ClearSourceSurfaceForSurface(gfxASurface* aSurface) {
+ aSurface->SetData(&kSourceSurface, nullptr, nullptr);
+}
+
+/* static */
+already_AddRefed<SourceSurface> gfxPlatform::GetSourceSurfaceForSurface(
+ RefPtr<DrawTarget> aTarget, gfxASurface* aSurface, bool aIsPlugin) {
+ if (!aSurface->CairoSurface() || aSurface->CairoStatus()) {
+ return nullptr;
+ }
+
+ if (!aTarget) {
+ aTarget = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+ }
+
+ void* userData = aSurface->GetData(&kSourceSurface);
+
+ if (userData) {
+ SourceSurfaceUserData* surf = static_cast<SourceSurfaceUserData*>(userData);
+
+ if (surf->mSrcSurface->IsValid() &&
+ surf->mBackendType == aTarget->GetBackendType()) {
+ RefPtr<SourceSurface> srcSurface(surf->mSrcSurface);
+ return srcSurface.forget();
+ }
+ // We can just continue here as when setting new user data the destroy
+ // function will be called for the old user data.
+ }
+
+ SurfaceFormat format = aSurface->GetSurfaceFormat();
+
+ if (aTarget->GetBackendType() == BackendType::CAIRO) {
+ // If we're going to be used with a CAIRO DrawTarget, then just create a
+ // SourceSurfaceCairo since we don't know the underlying type of the CAIRO
+ // DrawTarget and can't pick a better surface type. Doing this also avoids
+ // readback of aSurface's surface into memory if, for example, aSurface
+ // wraps an xlib cairo surface (which can be important to avoid a major
+ // slowdown).
+ //
+ // We return here regardless of whether CreateSourceSurfaceFromNativeSurface
+ // succeeds or not since we don't expect to be able to do any better below
+ // if it fails.
+ //
+ // Note that the returned SourceSurfaceCairo holds a strong reference to
+ // the cairo_surface_t* that it wraps, which essencially means it holds a
+ // strong reference to aSurface since aSurface shares its
+ // cairo_surface_t*'s reference count variable. As a result we can't cache
+ // srcBuffer on aSurface (see below) since aSurface would then hold a
+ // strong reference back to srcBuffer, creating a reference loop and a
+ // memory leak. Not caching is fine since wrapping is cheap enough (no
+ // copying) so we can just wrap again next time we're called.
+ return Factory::CreateSourceSurfaceForCairoSurface(
+ aSurface->CairoSurface(), aSurface->GetSize(), format);
+ }
+
+ RefPtr<SourceSurface> srcBuffer;
+
+ // Currently no other DrawTarget types implement
+ // CreateSourceSurfaceFromNativeSurface
+
+ if (!srcBuffer) {
+ // If aSurface wraps data, we can create a SourceSurfaceRawData that wraps
+ // the same data, then optimize it for aTarget:
+ RefPtr<DataSourceSurface> surf = GetWrappedDataSourceSurface(aSurface);
+ if (surf) {
+ srcBuffer = aIsPlugin
+ ? aTarget->OptimizeSourceSurfaceForUnknownAlpha(surf)
+ : aTarget->OptimizeSourceSurface(surf);
+
+ if (srcBuffer == surf) {
+ // GetWrappedDataSourceSurface returns a SourceSurface that holds a
+ // strong reference to aSurface since it wraps aSurface's data and
+ // needs it to stay alive. As a result we can't cache srcBuffer on
+ // aSurface (below) since aSurface would then hold a strong reference
+ // back to srcBuffer, creating a reference loop and a memory leak. Not
+ // caching is fine since wrapping is cheap enough (no copying) so we
+ // can just wrap again next time we're called.
+ //
+ // Note that the check below doesn't catch this since srcBuffer will be
+ // a SourceSurfaceRawData object (even if aSurface is not a
+ // gfxImageSurface object), which is why we need this separate check.
+ return srcBuffer.forget();
+ }
+ }
+ }
+
+ if (!srcBuffer) {
+ MOZ_ASSERT(aTarget->GetBackendType() != BackendType::CAIRO,
+ "We already tried CreateSourceSurfaceFromNativeSurface with a "
+ "DrawTargetCairo above");
+ // We've run out of performant options. We now try creating a SourceSurface
+ // using a temporary DrawTargetCairo and then optimizing it to aTarget's
+ // actual type. The CreateSourceSurfaceFromNativeSurface() call will
+ // likely create a DataSourceSurface (possibly involving copying and/or
+ // readback), and the OptimizeSourceSurface may well copy again and upload
+ // to the GPU. So, while this code path is rarely hit, hitting it may be
+ // very slow.
+ srcBuffer = Factory::CreateSourceSurfaceForCairoSurface(
+ aSurface->CairoSurface(), aSurface->GetSize(), format);
+ if (srcBuffer) {
+ srcBuffer = aTarget->OptimizeSourceSurface(srcBuffer);
+ }
+ }
+
+ if (!srcBuffer) {
+ return nullptr;
+ }
+
+ if ((srcBuffer->GetType() == SurfaceType::CAIRO &&
+ static_cast<SourceSurfaceCairo*>(srcBuffer.get())->GetSurface() ==
+ aSurface->CairoSurface()) ||
+ (srcBuffer->GetType() == SurfaceType::CAIRO_IMAGE &&
+ static_cast<DataSourceSurfaceCairo*>(srcBuffer.get())->GetSurface() ==
+ aSurface->CairoSurface())) {
+ // See the "Note that the returned SourceSurfaceCairo..." comment above.
+ return srcBuffer.forget();
+ }
+
+ // Add user data to aSurface so we can cache lookups in the future.
+ auto* srcSurfUD = new SourceSurfaceUserData;
+ srcSurfUD->mBackendType = aTarget->GetBackendType();
+ srcSurfUD->mSrcSurface = srcBuffer;
+ aSurface->SetData(&kSourceSurface, srcSurfUD, SourceBufferDestroy);
+
+ return srcBuffer.forget();
+}
+
+already_AddRefed<DataSourceSurface> gfxPlatform::GetWrappedDataSourceSurface(
+ gfxASurface* aSurface) {
+ RefPtr<gfxImageSurface> image = aSurface->GetAsImageSurface();
+ if (!image) {
+ return nullptr;
+ }
+ RefPtr<DataSourceSurface> result = Factory::CreateWrappingDataSourceSurface(
+ image->Data(), image->Stride(), image->GetSize(),
+ ImageFormatToSurfaceFormat(image->Format()));
+
+ if (!result) {
+ return nullptr;
+ }
+
+ // If we wrapped the underlying data of aSurface, then we need to add user
+ // data to make sure aSurface stays alive until we are done with the data.
+ auto* srcSurfUD = new DependentSourceSurfaceUserData;
+ srcSurfUD->mSurface = aSurface;
+ result->AddUserData(&kThebesSurface, srcSurfUD, SourceSurfaceDestroyed);
+
+ return result.forget();
+}
+
+void gfxPlatform::PopulateScreenInfo() {
+ nsCOMPtr<nsIScreenManager> manager =
+ do_GetService("@mozilla.org/gfx/screenmanager;1");
+ MOZ_ASSERT(manager, "failed to get nsIScreenManager");
+
+ nsCOMPtr<nsIScreen> screen;
+ manager->GetPrimaryScreen(getter_AddRefs(screen));
+ if (!screen) {
+ // This can happen in xpcshell, for instance
+ return;
+ }
+
+ screen->GetColorDepth(&mScreenDepth);
+ if (XRE_IsParentProcess()) {
+ gfxVars::SetScreenDepth(mScreenDepth);
+ }
+
+ int left, top;
+ screen->GetRect(&left, &top, &mScreenSize.width, &mScreenSize.height);
+}
+
+bool gfxPlatform::SupportsAzureContentForDrawTarget(DrawTarget* aTarget) {
+ if (!aTarget || !aTarget->IsValid()) {
+ return false;
+ }
+
+ return SupportsAzureContentForType(aTarget->GetBackendType());
+}
+
+void gfxPlatform::PurgeSkiaFontCache() {
+ if (gfxPlatform::GetPlatform()->GetDefaultContentBackend() ==
+ BackendType::SKIA) {
+ SkGraphics::PurgeFontCache();
+ }
+}
+
+already_AddRefed<DrawTarget> gfxPlatform::CreateDrawTargetForBackend(
+ BackendType aBackend, const IntSize& aSize, SurfaceFormat aFormat) {
+ // There is a bunch of knowledge in the gfxPlatform heirarchy about how to
+ // create the best offscreen surface for the current system and situation. We
+ // can easily take advantage of this for the Cairo backend, so that's what we
+ // do.
+ // mozilla::gfx::Factory can get away without having all this knowledge for
+ // now, but this might need to change in the future (using
+ // CreateOffscreenSurface() and CreateDrawTargetForSurface() for all
+ // backends).
+ if (aBackend == BackendType::CAIRO) {
+ RefPtr<gfxASurface> surf =
+ CreateOffscreenSurface(aSize, SurfaceFormatToImageFormat(aFormat));
+ if (!surf || surf->CairoStatus()) {
+ return nullptr;
+ }
+ return CreateDrawTargetForSurface(surf, aSize);
+ }
+ return Factory::CreateDrawTarget(aBackend, aSize, aFormat);
+}
+
+already_AddRefed<DrawTarget> gfxPlatform::CreateOffscreenCanvasDrawTarget(
+ const IntSize& aSize, SurfaceFormat aFormat) {
+ NS_ASSERTION(mPreferredCanvasBackend != BackendType::NONE, "No backend.");
+
+ // If we are using remote canvas we don't want to use acceleration in
+ // canvas DrawTargets we are not remoting, so we always use the fallback
+ // software one.
+ if (!gfxPlatform::UseRemoteCanvas() ||
+ !gfxPlatform::IsBackendAccelerated(mPreferredCanvasBackend)) {
+ RefPtr<DrawTarget> target =
+ CreateDrawTargetForBackend(mPreferredCanvasBackend, aSize, aFormat);
+ if (target || mFallbackCanvasBackend == BackendType::NONE) {
+ return target.forget();
+ }
+ }
+
+#ifdef XP_WIN
+ // On Windows, the fallback backend (Cairo) should use its image backend.
+ return Factory::CreateDrawTarget(mFallbackCanvasBackend, aSize, aFormat);
+#else
+ return CreateDrawTargetForBackend(mFallbackCanvasBackend, aSize, aFormat);
+#endif
+}
+
+already_AddRefed<DrawTarget> gfxPlatform::CreateOffscreenContentDrawTarget(
+ const IntSize& aSize, SurfaceFormat aFormat, bool aFallback) {
+ BackendType backend = (aFallback) ? mSoftwareBackend : mContentBackend;
+ NS_ASSERTION(backend != BackendType::NONE, "No backend.");
+ RefPtr<DrawTarget> dt = CreateDrawTargetForBackend(backend, aSize, aFormat);
+
+ if (!dt) {
+ return nullptr;
+ }
+
+ // We'd prefer this to take proper care and return a CaptureDT, but for the
+ // moment since we can't and this means we're going to be drawing on the main
+ // thread force it's initialization. See bug 1526045 and bug 1521368.
+ dt->ClearRect(gfx::Rect());
+ if (!dt->IsValid()) {
+ return nullptr;
+ }
+ return dt.forget();
+}
+
+already_AddRefed<DrawTarget> gfxPlatform::CreateSimilarSoftwareDrawTarget(
+ DrawTarget* aDT, const IntSize& aSize, SurfaceFormat aFormat) {
+ RefPtr<DrawTarget> dt;
+
+ if (Factory::DoesBackendSupportDataDrawtarget(aDT->GetBackendType())) {
+ dt = aDT->CreateSimilarDrawTarget(aSize, aFormat);
+ } else {
+ BackendType backendType = BackendType::SKIA;
+ dt = Factory::CreateDrawTarget(backendType, aSize, aFormat);
+ }
+
+ return dt.forget();
+}
+
+/* static */
+already_AddRefed<DrawTarget> gfxPlatform::CreateDrawTargetForData(
+ unsigned char* aData, const IntSize& aSize, int32_t aStride,
+ SurfaceFormat aFormat, bool aUninitialized) {
+ BackendType backendType = gfxVars::ContentBackend();
+ NS_ASSERTION(backendType != BackendType::NONE, "No backend.");
+
+ if (!Factory::DoesBackendSupportDataDrawtarget(backendType)) {
+ backendType = BackendType::SKIA;
+ }
+
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
+ backendType, aData, aSize, aStride, aFormat, aUninitialized);
+
+ return dt.forget();
+}
+
+/* static */
+BackendType gfxPlatform::BackendTypeForName(const nsCString& aName) {
+ if (aName.EqualsLiteral("cairo")) return BackendType::CAIRO;
+ if (aName.EqualsLiteral("skia")) return BackendType::SKIA;
+ if (aName.EqualsLiteral("direct2d")) return BackendType::DIRECT2D;
+ if (aName.EqualsLiteral("direct2d1.1")) return BackendType::DIRECT2D1_1;
+ return BackendType::NONE;
+}
+
+nsresult gfxPlatform::GetFontList(nsAtom* aLangGroup,
+ const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts) {
+ gfxPlatformFontList::PlatformFontList()->GetFontList(
+ aLangGroup, aGenericFamily, aListOfFonts);
+ return NS_OK;
+}
+
+nsresult gfxPlatform::UpdateFontList(bool aFullRebuild) {
+ gfxPlatformFontList::PlatformFontList()->UpdateFontList(aFullRebuild);
+ return NS_OK;
+}
+
+void gfxPlatform::GetStandardFamilyName(const nsCString& aFontName,
+ nsACString& aFamilyName) {
+ gfxPlatformFontList::PlatformFontList()->GetStandardFamilyName(aFontName,
+ aFamilyName);
+}
+
+nsAutoCString gfxPlatform::GetDefaultFontName(
+ const nsACString& aLangGroup, const nsACString& aGenericFamily) {
+ // To benefit from Return Value Optimization, all paths here must return
+ // this one variable:
+ nsAutoCString result;
+
+ auto* pfl = gfxPlatformFontList::PlatformFontList();
+ FamilyAndGeneric fam = pfl->GetDefaultFontFamily(aLangGroup, aGenericFamily);
+ if (!pfl->GetLocalizedFamilyName(fam.mFamily, result)) {
+ NS_WARNING("missing default font-family name");
+ }
+
+ return result;
+}
+
+bool gfxPlatform::DownloadableFontsEnabled() {
+ if (mAllowDownloadableFonts == UNINITIALIZED_VALUE) {
+ mAllowDownloadableFonts =
+ Preferences::GetBool(GFX_DOWNLOADABLE_FONTS_ENABLED, false);
+ }
+
+ return mAllowDownloadableFonts;
+}
+
+bool gfxPlatform::UseCmapsDuringSystemFallback() {
+ return StaticPrefs::gfx_font_rendering_fallback_always_use_cmaps();
+}
+
+bool gfxPlatform::OpenTypeSVGEnabled() {
+ return StaticPrefs::gfx_font_rendering_opentype_svg_enabled();
+}
+
+uint32_t gfxPlatform::WordCacheCharLimit() {
+ return StaticPrefs::gfx_font_rendering_wordcache_charlimit();
+}
+
+uint32_t gfxPlatform::WordCacheMaxEntries() {
+ return StaticPrefs::gfx_font_rendering_wordcache_maxentries();
+}
+
+bool gfxPlatform::UseGraphiteShaping() {
+ return StaticPrefs::gfx_font_rendering_graphite_enabled();
+}
+
+bool gfxPlatform::IsFontFormatSupported(
+ StyleFontFaceSourceFormatKeyword aFormatHint,
+ StyleFontFaceSourceTechFlags aTechFlags) {
+ // By default, font resources are assumed to be supported; but if the format
+ // hint or technology flags explicitly indicate something we don't support,
+ // then return false.
+ switch (aFormatHint) {
+ case StyleFontFaceSourceFormatKeyword::None:
+ break;
+ case StyleFontFaceSourceFormatKeyword::Collection:
+ return false;
+ case StyleFontFaceSourceFormatKeyword::Opentype:
+ case StyleFontFaceSourceFormatKeyword::Truetype:
+ break;
+ case StyleFontFaceSourceFormatKeyword::EmbeddedOpentype:
+ return false;
+ case StyleFontFaceSourceFormatKeyword::Svg:
+ return false;
+ case StyleFontFaceSourceFormatKeyword::Woff:
+ break;
+ case StyleFontFaceSourceFormatKeyword::Woff2:
+ break;
+ case StyleFontFaceSourceFormatKeyword::Unknown:
+ return false;
+ default:
+ MOZ_ASSERT_UNREACHABLE("bad format hint!");
+ return false;
+ }
+ StyleFontFaceSourceTechFlags unsupportedTechnologies =
+ StyleFontFaceSourceTechFlags::INCREMENTAL |
+ StyleFontFaceSourceTechFlags::COLOR_SBIX;
+ if (!StaticPrefs::gfx_downloadable_fonts_keep_color_bitmaps()) {
+ unsupportedTechnologies |= StyleFontFaceSourceTechFlags::COLOR_CBDT;
+ }
+ if (!StaticPrefs::gfx_font_rendering_colr_v1_enabled()) {
+ unsupportedTechnologies |= StyleFontFaceSourceTechFlags::COLOR_COLRV1;
+ }
+ if (!StaticPrefs::layout_css_font_palette_enabled()) {
+ unsupportedTechnologies |= StyleFontFaceSourceTechFlags::PALETTES;
+ }
+ if (!StaticPrefs::layout_css_font_variations_enabled()) {
+ unsupportedTechnologies |= StyleFontFaceSourceTechFlags::VARIATIONS;
+ }
+ if (aTechFlags & unsupportedTechnologies) {
+ return false;
+ }
+ return true;
+}
+
+bool gfxPlatform::IsKnownIconFontFamily(const nsAtom* aFamilyName) const {
+ return gfxPlatformFontList::PlatformFontList()->IsKnownIconFontFamily(
+ aFamilyName);
+}
+
+gfxFontEntry* gfxPlatform::LookupLocalFont(nsPresContext* aPresContext,
+ const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry) {
+ return gfxPlatformFontList::PlatformFontList()->LookupLocalFont(
+ aPresContext, aFontName, aWeightForEntry, aStretchForEntry,
+ aStyleForEntry);
+}
+
+gfxFontEntry* gfxPlatform::MakePlatformFont(const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry,
+ const uint8_t* aFontData,
+ uint32_t aLength) {
+ return gfxPlatformFontList::PlatformFontList()->MakePlatformFont(
+ aFontName, aWeightForEntry, aStretchForEntry, aStyleForEntry, aFontData,
+ aLength);
+}
+
+BackendPrefsData gfxPlatform::GetBackendPrefs() const {
+ BackendPrefsData data;
+
+ data.mCanvasBitmask = BackendTypeBit(BackendType::SKIA);
+ data.mContentBitmask = BackendTypeBit(BackendType::SKIA);
+
+#ifdef MOZ_WIDGET_GTK
+ data.mCanvasBitmask |= BackendTypeBit(BackendType::CAIRO);
+ data.mContentBitmask |= BackendTypeBit(BackendType::CAIRO);
+#endif
+
+ data.mCanvasDefault = BackendType::SKIA;
+ data.mContentDefault = BackendType::SKIA;
+
+ return data;
+}
+
+void gfxPlatform::InitBackendPrefs(BackendPrefsData&& aPrefsData) {
+ mPreferredCanvasBackend = GetCanvasBackendPref(aPrefsData.mCanvasBitmask);
+ if (mPreferredCanvasBackend == BackendType::NONE) {
+ mPreferredCanvasBackend = aPrefsData.mCanvasDefault;
+ }
+
+ if (mPreferredCanvasBackend == BackendType::DIRECT2D1_1) {
+ // Falling back to D2D 1.0 won't help us here. When D2D 1.1 DT creation
+ // fails it means the surface was too big or there's something wrong with
+ // the device. D2D 1.0 will encounter a similar situation.
+ mFallbackCanvasBackend = GetCanvasBackendPref(
+ aPrefsData.mCanvasBitmask & ~(BackendTypeBit(mPreferredCanvasBackend) |
+ BackendTypeBit(BackendType::DIRECT2D)));
+ } else {
+ mFallbackCanvasBackend = GetCanvasBackendPref(
+ aPrefsData.mCanvasBitmask & ~BackendTypeBit(mPreferredCanvasBackend));
+ }
+
+ mContentBackendBitmask = aPrefsData.mContentBitmask;
+ mContentBackend = GetContentBackendPref(mContentBackendBitmask);
+ if (mContentBackend == BackendType::NONE) {
+ mContentBackend = aPrefsData.mContentDefault;
+ // mContentBackendBitmask is our canonical reference for supported
+ // backends so we need to add the default if we are using it and
+ // overriding the prefs.
+ mContentBackendBitmask |= BackendTypeBit(aPrefsData.mContentDefault);
+ }
+
+ uint32_t swBackendBits = BackendTypeBit(BackendType::SKIA);
+#ifdef MOZ_WIDGET_GTK
+ swBackendBits |= BackendTypeBit(BackendType::CAIRO);
+#endif
+ mSoftwareBackend = GetContentBackendPref(swBackendBits);
+ if (mSoftwareBackend == BackendType::NONE) {
+ mSoftwareBackend = BackendType::SKIA;
+ }
+
+ // If we don't have a fallback canvas backend then use the same software
+ // fallback as content.
+ if (mFallbackCanvasBackend == BackendType::NONE) {
+ mFallbackCanvasBackend = mSoftwareBackend;
+ }
+
+ if (XRE_IsParentProcess()) {
+ gfxVars::SetContentBackend(mContentBackend);
+ gfxVars::SetSoftwareBackend(mSoftwareBackend);
+ }
+}
+
+/* static */
+BackendType gfxPlatform::GetCanvasBackendPref(uint32_t aBackendBitmask) {
+ return GetBackendPref("gfx.canvas.azure.backends", aBackendBitmask);
+}
+
+/* static */
+BackendType gfxPlatform::GetContentBackendPref(uint32_t& aBackendBitmask) {
+ return GetBackendPref("gfx.content.azure.backends", aBackendBitmask);
+}
+
+/* static */
+BackendType gfxPlatform::GetBackendPref(const char* aBackendPrefName,
+ uint32_t& aBackendBitmask) {
+ nsTArray<nsCString> backendList;
+ nsAutoCString prefString;
+ if (NS_SUCCEEDED(Preferences::GetCString(aBackendPrefName, prefString))) {
+ ParseString(prefString, ',', backendList);
+ }
+
+ uint32_t allowedBackends = 0;
+ BackendType result = BackendType::NONE;
+ for (uint32_t i = 0; i < backendList.Length(); ++i) {
+ BackendType type = BackendTypeForName(backendList[i]);
+ if (BackendTypeBit(type) & aBackendBitmask) {
+ allowedBackends |= BackendTypeBit(type);
+ if (result == BackendType::NONE) {
+ result = type;
+ }
+ }
+ }
+
+ aBackendBitmask = allowedBackends;
+ return result;
+}
+
+bool gfxPlatform::InSafeMode() {
+ static bool sSafeModeInitialized = false;
+ static bool sInSafeMode = false;
+
+ if (!sSafeModeInitialized) {
+ sSafeModeInitialized = true;
+ nsCOMPtr<nsIXULRuntime> xr = do_GetService("@mozilla.org/xre/runtime;1");
+ if (xr) {
+ xr->GetInSafeMode(&sInSafeMode);
+ }
+ }
+ return sInSafeMode;
+}
+
+bool gfxPlatform::OffMainThreadCompositingEnabled() {
+ return UsesOffMainThreadCompositing();
+}
+
+void gfxPlatform::SetCMSModeOverride(CMSMode aMode) {
+ MOZ_ASSERT(gCMSInitialized);
+ gCMSMode = aMode;
+}
+
+int gfxPlatform::GetRenderingIntent() {
+ // StaticPrefList.yaml is using 0 as the default for the rendering
+ // intent preference, based on that being the value for
+ // QCMS_INTENT_DEFAULT. Assert here to catch if that ever
+ // changes and we can then figure out what to do about it.
+ MOZ_ASSERT(QCMS_INTENT_DEFAULT == 0);
+
+ /* Try to query the pref system for a rendering intent. */
+ int32_t pIntent = StaticPrefs::gfx_color_management_rendering_intent();
+ if ((pIntent < QCMS_INTENT_MIN) || (pIntent > QCMS_INTENT_MAX)) {
+ /* If the pref is out of range, use embedded profile. */
+ pIntent = -1;
+ }
+ return pIntent;
+}
+
+DeviceColor gfxPlatform::TransformPixel(const sRGBColor& in,
+ qcms_transform* transform) {
+ if (transform) {
+ /* we want the bytes in RGB order */
+#ifdef IS_LITTLE_ENDIAN
+ /* ABGR puts the bytes in |RGBA| order on little endian */
+ uint32_t packed = in.ToABGR();
+ qcms_transform_data(transform, (uint8_t*)&packed, (uint8_t*)&packed, 1);
+ auto out = DeviceColor::FromABGR(packed);
+#else
+ /* ARGB puts the bytes in |ARGB| order on big endian */
+ uint32_t packed = in.UnusualToARGB();
+ /* add one to move past the alpha byte */
+ qcms_transform_data(transform, (uint8_t*)&packed + 1, (uint8_t*)&packed + 1,
+ 1);
+ auto out = DeviceColor::UnusualFromARGB(packed);
+#endif
+ out.a = in.a;
+ return out;
+ }
+ return DeviceColor(in.r, in.g, in.b, in.a);
+}
+
+nsTArray<uint8_t> gfxPlatform::GetPrefCMSOutputProfileData() {
+ const auto mirror = StaticPrefs::gfx_color_management_display_profile();
+ const auto fname = *mirror;
+ if (fname == "") {
+ return nsTArray<uint8_t>();
+ }
+
+ void* mem = nullptr;
+ size_t size = 0;
+ qcms_data_from_path(fname.get(), &mem, &size);
+
+ nsTArray<uint8_t> result;
+
+ if (mem) {
+ result.AppendElements(static_cast<uint8_t*>(mem), size);
+ free(mem);
+ }
+
+ return result;
+}
+
+const mozilla::gfx::ContentDeviceData* gfxPlatform::GetInitContentDeviceData() {
+ return gContentDeviceInitData;
+}
+
+CMSMode GfxColorManagementMode() {
+ const auto mode = StaticPrefs::gfx_color_management_mode();
+ if (mode >= 0 && mode < UnderlyingValue(CMSMode::AllCount)) {
+ return CMSMode(mode);
+ }
+ return CMSMode::Off;
+}
+
+void gfxPlatform::InitializeCMS() {
+ if (gCMSInitialized) {
+ return;
+ }
+
+ if (XRE_IsGPUProcess()) {
+ // Colors in the GPU process should already be managed, so we don't need to
+ // perform color management there.
+ gCMSInitialized = true;
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(),
+ "CMS should be initialized on the main thread");
+ if (MOZ_UNLIKELY(!NS_IsMainThread())) {
+ return;
+ }
+
+ gCMSMode = GfxColorManagementMode();
+
+ gCMSsRGBProfile = qcms_profile_sRGB();
+
+ /* Determine if we're using the internal override to force sRGB as
+ an output profile for reftests. See Bug 452125.
+
+ Note that we don't normally (outside of tests) set a default value
+ of this preference, which means nsIPrefBranch::GetBoolPref will
+ typically throw (and leave its out-param untouched).
+ */
+ if (StaticPrefs::gfx_color_management_force_srgb() ||
+ StaticPrefs::gfx_color_management_native_srgb()) {
+ gCMSOutputProfile = gCMSsRGBProfile;
+ }
+
+ if (!gCMSOutputProfile) {
+ nsTArray<uint8_t> outputProfileData =
+ gfxPlatform::GetPlatform()->GetPlatformCMSOutputProfileData();
+ if (!outputProfileData.IsEmpty()) {
+ gCMSOutputProfile = qcms_profile_from_memory_curves_only(
+ outputProfileData.Elements(), outputProfileData.Length());
+ }
+ }
+
+ /* Determine if the profile looks bogus. If so, close the profile
+ * and use sRGB instead. See bug 460629, */
+ if (gCMSOutputProfile && qcms_profile_is_bogus(gCMSOutputProfile)) {
+ NS_ASSERTION(gCMSOutputProfile != gCMSsRGBProfile,
+ "Builtin sRGB profile tagged as bogus!!!");
+ qcms_profile_release(gCMSOutputProfile);
+ gCMSOutputProfile = nullptr;
+ }
+
+ if (!gCMSOutputProfile) {
+ gCMSOutputProfile = gCMSsRGBProfile;
+ }
+
+ /* Precache the LUT16 Interpolations for the output profile. See
+ bug 444661 for details. */
+ qcms_profile_precache_output_transform(gCMSOutputProfile);
+
+ // Create the RGB transform.
+ gCMSRGBTransform =
+ qcms_transform_create(gCMSsRGBProfile, QCMS_DATA_RGB_8, gCMSOutputProfile,
+ QCMS_DATA_RGB_8, QCMS_INTENT_PERCEPTUAL);
+
+ // And the inverse.
+ gCMSInverseRGBTransform =
+ qcms_transform_create(gCMSOutputProfile, QCMS_DATA_RGB_8, gCMSsRGBProfile,
+ QCMS_DATA_RGB_8, QCMS_INTENT_PERCEPTUAL);
+
+ // The RGBA transform.
+ gCMSRGBATransform = qcms_transform_create(gCMSsRGBProfile, QCMS_DATA_RGBA_8,
+ gCMSOutputProfile, QCMS_DATA_RGBA_8,
+ QCMS_INTENT_PERCEPTUAL);
+
+ // And the BGRA one.
+ gCMSBGRATransform = qcms_transform_create(gCMSsRGBProfile, QCMS_DATA_BGRA_8,
+ gCMSOutputProfile, QCMS_DATA_BGRA_8,
+ QCMS_INTENT_PERCEPTUAL);
+
+ // FIXME: We only enable iccv4 after we create the platform profile, to
+ // wallpaper over bug 1697787.
+ //
+ // This should happen ideally right after setting gCMSMode.
+ if (StaticPrefs::gfx_color_management_enablev4()) {
+ qcms_enable_iccv4();
+ }
+
+ gCMSInitialized = true;
+}
+
+qcms_transform* gfxPlatform::GetCMSOSRGBATransform() {
+ switch (SurfaceFormat::OS_RGBA) {
+ case SurfaceFormat::B8G8R8A8:
+ return GetCMSBGRATransform();
+ case SurfaceFormat::R8G8B8A8:
+ return GetCMSRGBATransform();
+ default:
+ // We do not support color management with big endian.
+ return nullptr;
+ }
+}
+
+qcms_data_type gfxPlatform::GetCMSOSRGBAType() {
+ switch (SurfaceFormat::OS_RGBA) {
+ case SurfaceFormat::B8G8R8A8:
+ return QCMS_DATA_BGRA_8;
+ case SurfaceFormat::R8G8B8A8:
+ return QCMS_DATA_RGBA_8;
+ default:
+ // We do not support color management with big endian.
+ return QCMS_DATA_RGBA_8;
+ }
+}
+
+/* Shuts down various transforms and profiles for CMS. */
+void gfxPlatform::ShutdownCMS() {
+ if (gCMSRGBTransform) {
+ qcms_transform_release(gCMSRGBTransform);
+ gCMSRGBTransform = nullptr;
+ }
+ if (gCMSInverseRGBTransform) {
+ qcms_transform_release(gCMSInverseRGBTransform);
+ gCMSInverseRGBTransform = nullptr;
+ }
+ if (gCMSRGBATransform) {
+ qcms_transform_release(gCMSRGBATransform);
+ gCMSRGBATransform = nullptr;
+ }
+ if (gCMSBGRATransform) {
+ qcms_transform_release(gCMSBGRATransform);
+ gCMSBGRATransform = nullptr;
+ }
+ if (gCMSOutputProfile) {
+ qcms_profile_release(gCMSOutputProfile);
+
+ // handle the aliased case
+ if (gCMSsRGBProfile == gCMSOutputProfile) {
+ gCMSsRGBProfile = nullptr;
+ }
+ gCMSOutputProfile = nullptr;
+ }
+ if (gCMSsRGBProfile) {
+ qcms_profile_release(gCMSsRGBProfile);
+ gCMSsRGBProfile = nullptr;
+ }
+
+ // Reset the state variables
+ gCMSMode = CMSMode::Off;
+ gCMSInitialized = false;
+}
+
+uint32_t gfxPlatform::GetBidiNumeralOption() {
+ return StaticPrefs::bidi_numeral();
+}
+
+/* static */
+void gfxPlatform::FlushFontAndWordCaches() {
+ gfxFontCache* fontCache = gfxFontCache::GetCache();
+ if (fontCache) {
+ fontCache->Flush();
+ }
+
+ gfxPlatform::PurgeSkiaFontCache();
+}
+
+/* static */
+void gfxPlatform::ForceGlobalReflow(NeedsReframe aNeedsReframe,
+ BroadcastToChildren aBroadcastToChildren) {
+ MOZ_ASSERT(NS_IsMainThread());
+ const bool reframe = aNeedsReframe == NeedsReframe::Yes;
+ // Send a notification that will be observed by PresShells in this process
+ // only.
+ if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
+ char16_t needsReframe[] = {char16_t(reframe), 0};
+ obs->NotifyObservers(nullptr, "font-info-updated", needsReframe);
+ }
+ if (XRE_IsParentProcess() &&
+ aBroadcastToChildren == BroadcastToChildren::Yes) {
+ // Propagate the change to child processes.
+ for (auto* process :
+ dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) {
+ Unused << process->SendForceGlobalReflow(reframe);
+ }
+ }
+}
+
+void gfxPlatform::FontsPrefsChanged(const char* aPref) {
+ NS_ASSERTION(aPref != nullptr, "null preference");
+ if (!strcmp(GFX_DOWNLOADABLE_FONTS_ENABLED, aPref)) {
+ mAllowDownloadableFonts = UNINITIALIZED_VALUE;
+ } else if (!strcmp(GFX_PREF_WORD_CACHE_CHARLIMIT, aPref) ||
+ !strcmp(GFX_PREF_WORD_CACHE_MAXENTRIES, aPref) ||
+ !strcmp(GFX_PREF_GRAPHITE_SHAPING, aPref)) {
+ FlushFontAndWordCaches();
+ } else if (
+#if defined(XP_MACOSX)
+ !strcmp(GFX_PREF_CORETEXT_SHAPING, aPref) ||
+#endif
+ !strcmp("gfx.font_rendering.ahem_antialias_none", aPref)) {
+ FlushFontAndWordCaches();
+ } else if (!strcmp(GFX_PREF_OPENTYPE_SVG, aPref)) {
+ gfxFontCache::GetCache()->Flush();
+ gfxFontCache::GetCache()->NotifyGlyphsChanged();
+ }
+}
+
+mozilla::LogModule* gfxPlatform::GetLog(eGfxLog aWhichLog) {
+ // logs shared across gfx
+ static LazyLogModule sFontlistLog("fontlist");
+ static LazyLogModule sFontInitLog("fontinit");
+ static LazyLogModule sTextrunLog("textrun");
+ static LazyLogModule sTextrunuiLog("textrunui");
+ static LazyLogModule sCmapDataLog("cmapdata");
+ static LazyLogModule sTextPerfLog("textperf");
+
+ switch (aWhichLog) {
+ case eGfxLog_fontlist:
+ return sFontlistLog;
+ case eGfxLog_fontinit:
+ return sFontInitLog;
+ case eGfxLog_textrun:
+ return sTextrunLog;
+ case eGfxLog_textrunui:
+ return sTextrunuiLog;
+ case eGfxLog_cmapdata:
+ return sCmapDataLog;
+ case eGfxLog_textperf:
+ return sTextPerfLog;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Unexpected log type");
+ return nullptr;
+}
+
+RefPtr<mozilla::gfx::DrawTarget> gfxPlatform::ScreenReferenceDrawTarget() {
+ MOZ_ASSERT_IF(XRE_IsContentProcess(), NS_IsMainThread());
+ return (mScreenReferenceDrawTarget)
+ ? mScreenReferenceDrawTarget
+ : gPlatform->CreateOffscreenContentDrawTarget(
+ IntSize(1, 1), SurfaceFormat::B8G8R8A8, true);
+}
+
+/* static */ RefPtr<mozilla::gfx::DrawTarget>
+gfxPlatform::ThreadLocalScreenReferenceDrawTarget() {
+ if (NS_IsMainThread() && gPlatform) {
+ return gPlatform->ScreenReferenceDrawTarget();
+ }
+
+ gfxPlatformWorker* platformWorker = gfxPlatformWorker::Get();
+ if (platformWorker) {
+ return platformWorker->ScreenReferenceDrawTarget();
+ }
+
+ return Factory::CreateDrawTarget(BackendType::SKIA, IntSize(1, 1),
+ SurfaceFormat::B8G8R8A8);
+}
+
+mozilla::gfx::SurfaceFormat gfxPlatform::Optimal2DFormatForContent(
+ gfxContentType aContent) {
+ switch (aContent) {
+ case gfxContentType::COLOR:
+ switch (GetOffscreenFormat()) {
+ case SurfaceFormat::A8R8G8B8_UINT32:
+ return mozilla::gfx::SurfaceFormat::B8G8R8A8;
+ case SurfaceFormat::X8R8G8B8_UINT32:
+ return mozilla::gfx::SurfaceFormat::B8G8R8X8;
+ case SurfaceFormat::R5G6B5_UINT16:
+ return mozilla::gfx::SurfaceFormat::R5G6B5_UINT16;
+ default:
+ MOZ_ASSERT_UNREACHABLE(
+ "unknown gfxImageFormat for "
+ "gfxContentType::COLOR");
+ return mozilla::gfx::SurfaceFormat::B8G8R8A8;
+ }
+ case gfxContentType::ALPHA:
+ return mozilla::gfx::SurfaceFormat::A8;
+ case gfxContentType::COLOR_ALPHA:
+ return mozilla::gfx::SurfaceFormat::B8G8R8A8;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown gfxContentType");
+ return mozilla::gfx::SurfaceFormat::B8G8R8A8;
+ }
+}
+
+gfxImageFormat gfxPlatform::OptimalFormatForContent(gfxContentType aContent) {
+ switch (aContent) {
+ case gfxContentType::COLOR:
+ return GetOffscreenFormat();
+ case gfxContentType::ALPHA:
+ return SurfaceFormat::A8;
+ case gfxContentType::COLOR_ALPHA:
+ return SurfaceFormat::A8R8G8B8_UINT32;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown gfxContentType");
+ return SurfaceFormat::A8R8G8B8_UINT32;
+ }
+}
+
+/**
+ * There are a number of layers acceleration (or layers in general) preferences
+ * that should be consistent for the lifetime of the application (bug 840967).
+ * As such, we will evaluate them all as soon as one of them is evaluated
+ * and remember the values. Changing these preferences during the run will
+ * not have any effect until we restart.
+ */
+static mozilla::Atomic<bool> sLayersSupportsHardwareVideoDecoding(false);
+static bool sLayersHardwareVideoDecodingFailed = false;
+
+static mozilla::Atomic<bool> sLayersAccelerationPrefsInitialized(false);
+
+static void VideoDecodingFailedChangedCallback(const char* aPref, void*) {
+ sLayersHardwareVideoDecodingFailed = Preferences::GetBool(aPref, false);
+ gfxPlatform::GetPlatform()->UpdateCanUseHardwareVideoDecoding();
+}
+
+void gfxPlatform::UpdateCanUseHardwareVideoDecoding() {
+ if (XRE_IsParentProcess()) {
+ gfxVars::SetCanUseHardwareVideoDecoding(CanUseHardwareVideoDecoding());
+ }
+}
+
+void gfxPlatform::UpdateForceSubpixelAAWherePossible() {
+ bool forceSubpixelAAWherePossible =
+ StaticPrefs::gfx_webrender_quality_force_subpixel_aa_where_possible();
+ gfxVars::SetForceSubpixelAAWherePossible(forceSubpixelAAWherePossible);
+}
+
+void gfxPlatform::InitAcceleration() {
+ if (sLayersAccelerationPrefsInitialized) {
+ return;
+ }
+
+ InitCompositorAccelerationPrefs();
+
+ // If this is called for the first time on a non-main thread, we're screwed.
+ // At the moment there's no explicit guarantee that the main thread calls
+ // this before the compositor thread, but let's at least make the assumption
+ // explicit.
+ MOZ_ASSERT(NS_IsMainThread(), "can only initialize prefs on the main thread");
+
+#ifndef MOZ_WIDGET_GTK
+ nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
+ nsCString discardFailureId;
+ int32_t status;
+#endif
+
+ if (XRE_IsParentProcess()) {
+ gfxVars::SetBrowserTabsRemoteAutostart(BrowserTabsRemoteAutostart());
+ gfxVars::SetOffscreenFormat(GetOffscreenFormat());
+ gfxVars::SetRequiresAcceleratedGLContextForCompositorOGL(
+ RequiresAcceleratedGLContextForCompositorOGL());
+#ifdef XP_WIN
+ if (NS_SUCCEEDED(
+ gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_D3D11_KEYED_MUTEX,
+ discardFailureId, &status))) {
+ gfxVars::SetAllowD3D11KeyedMutex(status == nsIGfxInfo::FEATURE_STATUS_OK);
+ } else {
+ // If we couldn't properly evaluate the status, err on the side
+ // of caution and give this functionality to the user.
+ gfxCriticalNote << "Cannot evaluate keyed mutex feature status";
+ gfxVars::SetAllowD3D11KeyedMutex(true);
+ }
+ if (StaticPrefs::gfx_direct3d11_use_double_buffering() &&
+ IsWin10OrLater()) {
+ gfxVars::SetUseDoubleBufferingWithCompositor(true);
+ }
+#endif
+ }
+
+ if (StaticPrefs::media_hardware_video_decoding_enabled_AtStartup()) {
+#ifdef MOZ_WIDGET_GTK
+ sLayersSupportsHardwareVideoDecoding =
+ gfxPlatformGtk::GetPlatform()->InitVAAPIConfig(
+ StaticPrefs::
+ media_hardware_video_decoding_force_enabled_AtStartup() ||
+ StaticPrefs::media_ffmpeg_vaapi_enabled_AtStartup());
+#else
+ if (
+# ifdef XP_WIN
+ Preferences::GetBool("media.wmf.dxva.enabled", true) &&
+# endif
+ NS_SUCCEEDED(gfxInfo->GetFeatureStatus(
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, discardFailureId,
+ &status))) {
+ if (status == nsIGfxInfo::FEATURE_STATUS_OK ||
+ StaticPrefs::
+ media_hardware_video_decoding_force_enabled_AtStartup()) {
+ sLayersSupportsHardwareVideoDecoding = true;
+ }
+ }
+#endif
+ } else if (XRE_IsParentProcess()) {
+ FeatureState& feature =
+ gfxConfig::GetFeature(Feature::HARDWARE_VIDEO_DECODING);
+ feature.EnableByDefault();
+ feature.UserDisable("User disabled via pref",
+ "FEATURE_HARDWARE_VIDEO_DECODING_PREF_DISABLED"_ns);
+ }
+
+ sLayersAccelerationPrefsInitialized = true;
+
+ if (XRE_IsParentProcess()) {
+ Preferences::RegisterCallbackAndCall(
+ VideoDecodingFailedChangedCallback,
+ "media.hardware-video-decoding.failed");
+ InitGPUProcessPrefs();
+
+ gfxVars::SetRemoteCanvasEnabled(StaticPrefs::gfx_canvas_remote() &&
+ gfxConfig::IsEnabled(Feature::GPU_PROCESS));
+ }
+}
+
+void gfxPlatform::InitGPUProcessPrefs() {
+ // We want to hide this from about:support, so only set a default if the
+ // pref is known to be true.
+ if (!StaticPrefs::layers_gpu_process_enabled_AtStartup() &&
+ !StaticPrefs::layers_gpu_process_force_enabled_AtStartup()) {
+ return;
+ }
+
+ FeatureState& gpuProc = gfxConfig::GetFeature(Feature::GPU_PROCESS);
+
+ // We require E10S - otherwise, there is very little benefit to the GPU
+ // process, since the UI process must still use acceleration for
+ // performance.
+ if (!BrowserTabsRemoteAutostart()) {
+ gpuProc.DisableByDefault(FeatureStatus::Unavailable,
+ "Multi-process mode is not enabled",
+ "FEATURE_FAILURE_NO_E10S"_ns);
+ } else {
+ gpuProc.SetDefaultFromPref(
+ StaticPrefs::GetPrefName_layers_gpu_process_enabled(), true,
+ StaticPrefs::GetPrefDefault_layers_gpu_process_enabled());
+ }
+
+ if (StaticPrefs::layers_gpu_process_force_enabled_AtStartup()) {
+ gpuProc.UserForceEnable("User force-enabled via pref");
+ }
+
+ nsCString message;
+ nsCString failureId;
+ if (!gfxPlatform::IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_GPU_PROCESS,
+ &message, failureId)) {
+ gpuProc.Disable(FeatureStatus::Blocklisted, message.get(), failureId);
+ return;
+ }
+
+ if (IsHeadless()) {
+ gpuProc.ForceDisable(FeatureStatus::Blocked, "Headless mode is enabled",
+ "FEATURE_FAILURE_HEADLESS_MODE"_ns);
+ return;
+ }
+ if (InSafeMode()) {
+ gpuProc.ForceDisable(FeatureStatus::Blocked, "Safe-mode is enabled",
+ "FEATURE_FAILURE_SAFE_MODE"_ns);
+ return;
+ }
+
+ InitPlatformGPUProcessPrefs();
+}
+
+void gfxPlatform::InitCompositorAccelerationPrefs() {
+ const char* acceleratedEnv = PR_GetEnv("MOZ_ACCELERATED");
+
+ FeatureState& feature = gfxConfig::GetFeature(Feature::HW_COMPOSITING);
+
+ // Base value - does the platform allow acceleration?
+ if (feature.SetDefault(AccelerateLayersByDefault(), FeatureStatus::Blocked,
+ "Acceleration blocked by platform")) {
+ if (StaticPrefs::
+ layers_acceleration_disabled_AtStartup_DoNotUseDirectly()) {
+ feature.UserDisable("Disabled by layers.acceleration.disabled=true",
+ "FEATURE_FAILURE_COMP_PREF"_ns);
+ } else if (acceleratedEnv && *acceleratedEnv == '0') {
+ feature.UserDisable("Disabled by envvar", "FEATURE_FAILURE_COMP_ENV"_ns);
+ }
+ } else {
+ if (acceleratedEnv && *acceleratedEnv == '1') {
+ feature.UserEnable("Enabled by envvar");
+ }
+ }
+
+ // This has specific meaning elsewhere, so we always record it.
+ if (StaticPrefs::
+ layers_acceleration_force_enabled_AtStartup_DoNotUseDirectly()) {
+ feature.UserForceEnable("Force-enabled by pref");
+ }
+
+ // Safe, headless, and record/replay modes override everything.
+ if (InSafeMode()) {
+ feature.ForceDisable(FeatureStatus::Blocked,
+ "Acceleration blocked by safe-mode",
+ "FEATURE_FAILURE_COMP_SAFEMODE"_ns);
+ }
+ if (IsHeadless()) {
+ feature.ForceDisable(FeatureStatus::Blocked,
+ "Acceleration blocked by headless mode",
+ "FEATURE_FAILURE_COMP_HEADLESSMODE"_ns);
+ }
+}
+
+/*static*/
+bool gfxPlatform::WebRenderPrefEnabled() {
+ return StaticPrefs::gfx_webrender_all_AtStartup() ||
+ StaticPrefs::gfx_webrender_enabled_AtStartup_DoNotUseDirectly();
+}
+
+/*static*/
+bool gfxPlatform::WebRenderEnvvarEnabled() {
+ const char* env = PR_GetEnv("MOZ_WEBRENDER");
+ return (env && *env == '1');
+}
+
+/* static */ const char* gfxPlatform::WebRenderResourcePathOverride() {
+ const char* resourcePath = PR_GetEnv("WR_RESOURCE_PATH");
+ if (!resourcePath || resourcePath[0] == '\0') {
+ return nullptr;
+ }
+ return resourcePath;
+}
+
+void gfxPlatform::InitWebRenderConfig() {
+ bool prefEnabled = WebRenderPrefEnabled();
+ bool envvarEnabled = WebRenderEnvvarEnabled();
+
+ // WR? WR+ => means WR was enabled via gfx.webrender.all.qualified on
+ // qualified hardware
+ // WR! WR+ => means WR was enabled via gfx.webrender.{all,enabled} or
+ // envvar, possibly on unqualified hardware
+ // In all cases WR- means WR was not enabled, for one of many possible
+ // reasons. Prior to bug 1523788 landing the gfx.webrender.{all,enabled}
+ // prefs only worked on Nightly so keep that in mind when looking at older
+ // crash reports.
+ ScopedGfxFeatureReporter reporter("WR", prefEnabled || envvarEnabled);
+ if (!XRE_IsParentProcess()) {
+ // The parent process runs through all the real decision-making code
+ // later in this function. For other processes we still want to report
+ // the state of the feature for crash reports.
+ reporter.SetSuccessful();
+ return;
+ }
+
+ // Update the gfxConfig feature states.
+ gfxConfigManager manager;
+ manager.Init();
+ manager.ConfigureWebRender();
+
+ bool hasHardware = gfxConfig::IsEnabled(Feature::WEBRENDER);
+
+#ifdef MOZ_WIDGET_GTK
+ // We require a hardware driver to back the GL context unless the user forced
+ // on WebRender.
+ if (!gfxConfig::IsForcedOnByUser(Feature::WEBRENDER) &&
+ StaticPrefs::gfx_webrender_reject_software_driver_AtStartup()) {
+ gfxVars::SetWebRenderRequiresHardwareDriver(true);
+ }
+#endif
+
+#ifdef XP_WIN
+ if (gfxConfig::IsEnabled(Feature::WEBRENDER_ANGLE)) {
+ gfxVars::SetUseWebRenderANGLE(true);
+ }
+#endif
+
+ if (gfxConfig::IsEnabled(Feature::WEBRENDER_SHADER_CACHE)) {
+ gfxVars::SetUseWebRenderProgramBinaryDisk(true);
+ }
+
+ gfxVars::SetUseWebRenderOptimizedShaders(
+ gfxConfig::IsEnabled(Feature::WEBRENDER_OPTIMIZED_SHADERS));
+
+ gfxVars::SetUseSoftwareWebRender(!hasHardware);
+
+ Preferences::RegisterPrefixCallbackAndCall(SwapIntervalPrefChangeCallback,
+ "gfx.swap-interval");
+
+ reporter.SetSuccessful();
+
+ Preferences::RegisterPrefixCallbackAndCall(WebRenderDebugPrefChangeCallback,
+ WR_DEBUG_PREF);
+
+ RegisterWebRenderBoolParamCallback();
+
+ Preferences::RegisterPrefixCallbackAndCall(
+ WebRendeProfilerUIPrefChangeCallback, "gfx.webrender.debug.profiler-ui");
+ Preferences::RegisterCallback(
+ WebRenderQualityPrefChangeCallback,
+ nsDependentCString(
+ StaticPrefs::
+ GetPrefName_gfx_webrender_quality_force_subpixel_aa_where_possible()));
+
+ Preferences::RegisterCallback(
+ WebRenderBatchingPrefChangeCallback,
+ nsDependentCString(
+ StaticPrefs::GetPrefName_gfx_webrender_batching_lookback()));
+
+ Preferences::RegisterCallbackAndCall(
+ WebRenderBlobTileSizePrefChangeCallback,
+ nsDependentCString(
+ StaticPrefs::GetPrefName_gfx_webrender_blob_tile_size()));
+
+ Preferences::RegisterCallbackAndCall(
+ WebRenderUploadThresholdPrefChangeCallback,
+ nsDependentCString(
+ StaticPrefs::GetPrefName_gfx_webrender_batched_upload_threshold()));
+
+ if (WebRenderResourcePathOverride()) {
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::IsWebRenderResourcePathOverridden, true);
+ }
+
+ UpdateForceSubpixelAAWherePossible();
+
+#ifdef XP_WIN
+ if (gfxConfig::IsEnabled(Feature::WEBRENDER_DCOMP_PRESENT)) {
+ gfxVars::SetUseWebRenderDCompWin(true);
+ }
+ if (StaticPrefs::gfx_webrender_software_d3d11_AtStartup()) {
+ gfxVars::SetAllowSoftwareWebRenderD3D11(true);
+ }
+
+ const bool overlaySupported =
+ IsWin10AnniversaryUpdateOrLater() &&
+ gfxConfig::IsEnabled(Feature::WEBRENDER_COMPOSITOR);
+ MOZ_ASSERT_IF(overlaySupported,
+ gfxConfig::IsEnabled(Feature::WEBRENDER_DCOMP_PRESENT));
+
+ bool useVideoOverlay = false;
+ if (StaticPrefs::gfx_webrender_dcomp_video_overlay_win_AtStartup()) {
+ if (overlaySupported) {
+ useVideoOverlay = true;
+ }
+
+ if (useVideoOverlay &&
+ !StaticPrefs::
+ gfx_webrender_dcomp_video_overlay_win_force_enabled_AtStartup()) {
+ nsCString failureId;
+ int32_t status;
+ const nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
+ if (NS_FAILED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_VIDEO_OVERLAY,
+ failureId, &status))) {
+ FeatureState& feature = gfxConfig::GetFeature(Feature::VIDEO_OVERLAY);
+ feature.DisableByDefault(FeatureStatus::BlockedNoGfxInfo,
+ "gfxInfo is broken",
+ "FEATURE_FAILURE_WR_NO_GFX_INFO"_ns);
+ useVideoOverlay = false;
+ } else {
+ if (status != nsIGfxInfo::FEATURE_ALLOW_ALWAYS) {
+ FeatureState& feature = gfxConfig::GetFeature(Feature::VIDEO_OVERLAY);
+ feature.DisableByDefault(FeatureStatus::Blocked,
+ "Blocklisted by gfxInfo", failureId);
+ useVideoOverlay = false;
+ }
+ }
+ }
+ } else if (overlaySupported) {
+ FeatureState& feature = gfxConfig::GetFeature(Feature::VIDEO_OVERLAY);
+ feature.DisableByDefault(FeatureStatus::Blocked, "Disabled by pref",
+ "FEATURE_FAILURE_DISABLED_BY_PREF"_ns);
+ }
+
+ if (useVideoOverlay) {
+ FeatureState& feature = gfxConfig::GetFeature(Feature::VIDEO_OVERLAY);
+ feature.EnableByDefault();
+ gfxVars::SetUseWebRenderDCompVideoOverlayWin(true);
+ }
+
+ if (useVideoOverlay &&
+ StaticPrefs::gfx_webrender_dcomp_video_sw_overlay_win_AtStartup()) {
+ gfxVars::SetUseWebRenderDCompSwVideoOverlayWin(true);
+ }
+
+ bool useHwVideoZeroCopy = false;
+ if (StaticPrefs::media_wmf_zero_copy_nv12_textures_AtStartup()) {
+ // XXX relax limitation to Windows 8.1
+ if (IsWin10OrLater() && hasHardware) {
+ useHwVideoZeroCopy = true;
+ }
+
+ if (useHwVideoZeroCopy &&
+ !StaticPrefs::
+ media_wmf_zero_copy_nv12_textures_force_enabled_AtStartup()) {
+ nsCString failureId;
+ int32_t status;
+ const nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
+ if (NS_FAILED(gfxInfo->GetFeatureStatus(
+ nsIGfxInfo::FEATURE_HW_DECODED_VIDEO_ZERO_COPY, failureId,
+ &status))) {
+ FeatureState& feature =
+ gfxConfig::GetFeature(Feature::HW_DECODED_VIDEO_ZERO_COPY);
+ feature.DisableByDefault(FeatureStatus::BlockedNoGfxInfo,
+ "gfxInfo is broken",
+ "FEATURE_FAILURE_WR_NO_GFX_INFO"_ns);
+ useHwVideoZeroCopy = false;
+ } else {
+ if (status != nsIGfxInfo::FEATURE_ALLOW_ALWAYS) {
+ FeatureState& feature =
+ gfxConfig::GetFeature(Feature::HW_DECODED_VIDEO_ZERO_COPY);
+ feature.DisableByDefault(FeatureStatus::Blocked,
+ "Blocklisted by gfxInfo", failureId);
+ useHwVideoZeroCopy = false;
+ }
+ }
+ }
+ }
+
+ if (useHwVideoZeroCopy) {
+ FeatureState& feature =
+ gfxConfig::GetFeature(Feature::HW_DECODED_VIDEO_ZERO_COPY);
+ feature.EnableByDefault();
+ gfxVars::SetHwDecodedVideoZeroCopy(true);
+ }
+
+ bool reuseDecoderDevice = false;
+ if (StaticPrefs::gfx_direct3d11_reuse_decoder_device_AtStartup()) {
+ reuseDecoderDevice = true;
+
+ if (reuseDecoderDevice &&
+ !StaticPrefs::
+ gfx_direct3d11_reuse_decoder_device_force_enabled_AtStartup()) {
+ nsCString failureId;
+ int32_t status;
+ const nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
+ if (NS_FAILED(gfxInfo->GetFeatureStatus(
+ nsIGfxInfo::FEATURE_REUSE_DECODER_DEVICE, failureId, &status))) {
+ FeatureState& feature =
+ gfxConfig::GetFeature(Feature::REUSE_DECODER_DEVICE);
+ feature.DisableByDefault(FeatureStatus::BlockedNoGfxInfo,
+ "gfxInfo is broken",
+ "FEATURE_FAILURE_WR_NO_GFX_INFO"_ns);
+ reuseDecoderDevice = false;
+ } else {
+ if (status != nsIGfxInfo::FEATURE_STATUS_OK) {
+ FeatureState& feature =
+ gfxConfig::GetFeature(Feature::REUSE_DECODER_DEVICE);
+ feature.DisableByDefault(FeatureStatus::Blocked,
+ "Blocklisted by gfxInfo", failureId);
+ reuseDecoderDevice = false;
+ }
+ }
+ }
+ }
+
+ if (reuseDecoderDevice) {
+ FeatureState& feature =
+ gfxConfig::GetFeature(Feature::REUSE_DECODER_DEVICE);
+ feature.EnableByDefault();
+ gfxVars::SetReuseDecoderDevice(true);
+ }
+
+ if (Preferences::GetBool("gfx.webrender.flip-sequential", false)) {
+ if (gfxVars::UseWebRenderANGLE()) {
+ gfxVars::SetUseWebRenderFlipSequentialWin(true);
+ }
+ }
+ if (Preferences::GetBool("gfx.webrender.triple-buffering.enabled", false)) {
+ if (gfxVars::UseWebRenderDCompWin() ||
+ gfxVars::UseWebRenderFlipSequentialWin()) {
+ gfxVars::SetUseWebRenderTripleBufferingWin(true);
+ }
+ }
+#endif
+
+ if (gfxConfig::IsEnabled(Feature::WEBRENDER_COMPOSITOR)) {
+ gfxVars::SetUseWebRenderCompositor(true);
+ }
+
+ Telemetry::ScalarSet(
+ Telemetry::ScalarID::GFX_OS_COMPOSITOR,
+ gfx::gfxConfig::IsEnabled(gfx::Feature::WEBRENDER_COMPOSITOR));
+
+ if (gfxConfig::IsEnabled(Feature::WEBRENDER_PARTIAL)) {
+ gfxVars::SetWebRenderMaxPartialPresentRects(
+ StaticPrefs::gfx_webrender_max_partial_present_rects_AtStartup());
+ }
+
+ // Set features that affect WR's RendererOptions
+ gfxVars::SetUseGLSwizzle(
+ IsFeatureSupported(nsIGfxInfo::FEATURE_GL_SWIZZLE, true));
+ gfxVars::SetUseWebRenderScissoredCacheClears(gfx::gfxConfig::IsEnabled(
+ gfx::Feature::WEBRENDER_SCISSORED_CACHE_CLEARS));
+
+ // The RemoveShaderCacheFromDiskIfNecessary() needs to be called after
+ // WebRenderConfig initialization.
+ gfxUtils::RemoveShaderCacheFromDiskIfNecessary();
+}
+
+void gfxPlatform::InitHardwareVideoConfig() {
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+#ifdef MOZ_WIDGET_GTK
+ // We don't want to expose codec info if whole HW decoding is disabled.
+ if (!sLayersSupportsHardwareVideoDecoding) {
+ return;
+ }
+#endif
+
+ nsCString message;
+ nsCString failureId;
+
+ FeatureState& featureVP8 = gfxConfig::GetFeature(Feature::VP8_HW_DECODE);
+ featureVP8.EnableByDefault();
+
+ if (!IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_VP8_HW_DECODE, &message,
+ failureId)) {
+ featureVP8.Disable(FeatureStatus::Blocklisted, message.get(), failureId);
+ }
+ gfxVars::SetUseVP8HwDecode(featureVP8.IsEnabled());
+
+ FeatureState& featureVP9 = gfxConfig::GetFeature(Feature::VP9_HW_DECODE);
+ featureVP9.EnableByDefault();
+
+ if (!IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_VP9_HW_DECODE, &message,
+ failureId)) {
+ featureVP9.Disable(FeatureStatus::Blocklisted, message.get(), failureId);
+ }
+ gfxVars::SetUseVP9HwDecode(featureVP9.IsEnabled());
+
+ // H264_HW_DECODE/AV1_HW_DECODE is used on Linux only right now.
+#ifdef MOZ_WIDGET_GTK
+ FeatureState& featureH264 = gfxConfig::GetFeature(Feature::H264_HW_DECODE);
+ featureH264.EnableByDefault();
+
+ if (!IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_H264_HW_DECODE, &message,
+ failureId)) {
+ featureH264.Disable(FeatureStatus::Blocklisted, message.get(), failureId);
+ }
+ gfxVars::SetUseH264HwDecode(featureH264.IsEnabled());
+
+ FeatureState& featureAV1 = gfxConfig::GetFeature(Feature::AV1_HW_DECODE);
+ featureAV1.EnableByDefault();
+
+ if (!IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_AV1_HW_DECODE, &message,
+ failureId)) {
+ featureAV1.Disable(FeatureStatus::Blocklisted, message.get(), failureId);
+ }
+ gfxVars::SetUseAV1HwDecode(featureAV1.IsEnabled());
+#endif
+}
+
+void gfxPlatform::InitWebGLConfig() {
+ if (!XRE_IsParentProcess()) return;
+
+ const nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
+
+ const auto IsFeatureOk = [&](const int32_t feature) {
+ nsCString discardFailureId;
+ int32_t status;
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(
+ gfxInfo->GetFeatureStatus(feature, discardFailureId, &status)));
+ return (status == nsIGfxInfo::FEATURE_STATUS_OK);
+ };
+
+ gfxVars::SetAllowWebgl2(IsFeatureOk(nsIGfxInfo::FEATURE_WEBGL2));
+ gfxVars::SetWebglAllowWindowsNativeGl(
+ IsFeatureOk(nsIGfxInfo::FEATURE_WEBGL_OPENGL));
+ gfxVars::SetAllowWebglAccelAngle(
+ IsFeatureOk(nsIGfxInfo::FEATURE_WEBGL_ANGLE));
+ gfxVars::SetWebglUseHardware(
+ IsFeatureOk(nsIGfxInfo::FEATURE_WEBGL_USE_HARDWARE));
+
+ if (kIsMacOS) {
+ // Avoid crash for Intel HD Graphics 3000 on OSX. (Bug 1413269)
+ nsString vendorID, deviceID;
+ gfxInfo->GetAdapterVendorID(vendorID);
+ gfxInfo->GetAdapterDeviceID(deviceID);
+ if (vendorID.EqualsLiteral("0x8086") &&
+ (deviceID.EqualsLiteral("0x0116") ||
+ deviceID.EqualsLiteral("0x0126"))) {
+ gfxVars::SetWebglAllowCoreProfile(false);
+ }
+ }
+
+ bool allowWebGLOop =
+ IsFeatureOk(nsIGfxInfo::FEATURE_ALLOW_WEBGL_OUT_OF_PROCESS);
+ if (!kIsAndroid) {
+ gfxVars::SetAllowWebglOop(allowWebGLOop);
+ } else {
+ // On android, enable out-of-process WebGL only when GPU process exists.
+ gfxVars::SetAllowWebglOop(allowWebGLOop &&
+ gfxConfig::IsEnabled(Feature::GPU_PROCESS));
+ // Enable gl::SharedSurface of AndroidHardwareBuffer when API version is 26+
+ // and out-of-process WebGL is enabled.
+#ifdef MOZ_WIDGET_ANDROID
+ if (gfxVars::AllowWebglOop() && jni::GetAPIVersion() >= 26 &&
+ StaticPrefs::webgl_out_of_process_enable_ahardwarebuffer_AtStartup()) {
+ gfxVars::SetUseAHardwareBufferSharedSurfaceWebglOop(true);
+ }
+#endif
+ }
+
+ bool threadsafeGL = IsFeatureOk(nsIGfxInfo::FEATURE_THREADSAFE_GL);
+ threadsafeGL |= StaticPrefs::webgl_threadsafe_gl_force_enabled_AtStartup();
+ threadsafeGL &= !StaticPrefs::webgl_threadsafe_gl_force_disabled_AtStartup();
+ gfxVars::SetSupportsThreadsafeGL(threadsafeGL);
+
+ FeatureState& feature =
+ gfxConfig::GetFeature(Feature::CANVAS_RENDERER_THREAD);
+ if (!threadsafeGL) {
+ feature.DisableByDefault(FeatureStatus::Blocked, "Thread unsafe GL",
+ "FEATURE_FAILURE_THREAD_UNSAFE_GL"_ns);
+ } else if (!StaticPrefs::webgl_use_canvas_render_thread_AtStartup()) {
+ feature.DisableByDefault(FeatureStatus::Blocked, "Disabled by pref",
+ "FEATURE_FAILURE_DISABLED_BY_PREF"_ns);
+ } else {
+ feature.EnableByDefault();
+ }
+ gfxVars::SetUseCanvasRenderThread(feature.IsEnabled());
+
+ bool webglOopAsyncPresentForceSync =
+ !gfxVars::UseCanvasRenderThread() ||
+ StaticPrefs::webgl_out_of_process_async_present_force_sync();
+ gfxVars::SetWebglOopAsyncPresentForceSync(webglOopAsyncPresentForceSync);
+
+ if (kIsAndroid) {
+ // Don't enable robust buffer access on Adreno 620 and 630 devices.
+ // It causes the linking of some shaders to fail. See bug 1485441 and
+ // bug 1810693.
+ nsAutoString renderer;
+ gfxInfo->GetAdapterDeviceID(renderer);
+ if ((renderer.Find(u"Adreno (TM) 620") != -1) ||
+ (renderer.Find(u"Adreno (TM) 630") != -1)) {
+ gfxVars::SetAllowEglRbab(false);
+ }
+ }
+
+ if (kIsWayland || kIsX11) {
+ nsCString discardFailureId;
+ int32_t status;
+ FeatureState& feature =
+ gfxConfig::GetFeature(Feature::DMABUF_SURFACE_EXPORT);
+ if (NS_FAILED(
+ gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DMABUF_SURFACE_EXPORT,
+ discardFailureId, &status)) ||
+ status != nsIGfxInfo::FEATURE_STATUS_OK) {
+ feature.DisableByDefault(FeatureStatus::Blocked, "Blocklisted by gfxInfo",
+ discardFailureId);
+ gfxVars::SetUseDMABufSurfaceExport(false);
+ } else {
+ feature.EnableByDefault();
+ }
+ }
+}
+
+void gfxPlatform::InitWebGPUConfig() {
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ FeatureState& feature = gfxConfig::GetFeature(Feature::WEBGPU);
+ feature.EnableByDefault();
+
+ nsCString message;
+ nsCString failureId;
+ if (!IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_WEBGPU, &message, failureId)) {
+ if (StaticPrefs::gfx_webgpu_ignore_blocklist_AtStartup()) {
+ feature.UserForceEnable(
+ "Ignoring blocklist entry because of gfx.webgpu.force-enabled:true.");
+ }
+
+ feature.Disable(FeatureStatus::Blocklisted, message.get(), failureId);
+ }
+
+#ifdef RELEASE_OR_BETA
+ feature.ForceDisable(FeatureStatus::Blocked,
+ "WebGPU cannot be enabled in release or beta",
+ "WEBGPU_DISABLE_RELEASE_OR_BETA"_ns);
+#endif
+
+ gfxVars::SetAllowWebGPU(feature.IsEnabled());
+}
+
+#ifdef XP_WIN
+static void WindowOcclusionPrefChangeCallback(const char* aPref, void*) {
+ const char* env = PR_GetEnv("MOZ_WINDOW_OCCLUSION");
+ if (env) {
+ // env has a higher priority than pref.
+ return;
+ }
+
+ FeatureState& feature = gfxConfig::GetFeature(Feature::WINDOW_OCCLUSION);
+ bool enabled =
+ StaticPrefs::widget_windows_window_occlusion_tracking_enabled();
+
+ printf_stderr("Dynamically enable window occlusion %d\n", enabled);
+
+ // Update feature before calling WinUtils::EnableWindowOcclusion()
+ if (enabled) {
+ feature.UserEnable("User enabled by pref");
+ } else {
+ feature.UserDisable("User disabled via pref",
+ "FEATURE_FAILURE_PREF_DISABLED"_ns);
+ }
+ widget::WinUtils::EnableWindowOcclusion(enabled);
+}
+#endif
+
+void gfxPlatform::InitWindowOcclusionConfig() {
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+#ifdef XP_WIN
+ FeatureState& feature = gfxConfig::GetFeature(Feature::WINDOW_OCCLUSION);
+ feature.SetDefaultFromPref(
+ StaticPrefs::
+ GetPrefName_widget_windows_window_occlusion_tracking_enabled(),
+ true,
+ StaticPrefs::
+ GetPrefDefault_widget_windows_window_occlusion_tracking_enabled());
+
+ const char* env = PR_GetEnv("MOZ_WINDOW_OCCLUSION");
+ if (env) {
+ if (*env == '1') {
+ feature.UserForceEnable("Force enabled by envvar");
+ } else {
+ feature.UserDisable("Force disabled by envvar",
+ "FEATURE_FAILURE_OCCL_ENV"_ns);
+ }
+ }
+
+ Preferences::RegisterCallback(
+ WindowOcclusionPrefChangeCallback,
+ nsDependentCString(
+ StaticPrefs::
+ GetPrefName_widget_windows_window_occlusion_tracking_enabled()));
+#endif
+}
+
+static void BackdropFilterPrefChangeCallback(const char*, void*) {
+ FeatureState& feature = gfxConfig::GetFeature(Feature::BACKDROP_FILTER);
+
+ // We need to reset because the user status needs to be set before the
+ // environment status, but the environment status comes from the blocklist,
+ // and the user status can be updated after the fact.
+ feature.Reset();
+ feature.EnableByDefault();
+
+ if (StaticPrefs::layout_css_backdrop_filter_force_enabled()) {
+ feature.UserForceEnable("Force enabled by pref");
+ }
+
+ nsCString message;
+ nsCString failureId;
+ if (!gfxPlatform::IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_BACKDROP_FILTER,
+ &message, failureId)) {
+ feature.Disable(FeatureStatus::Blocklisted, message.get(), failureId);
+ }
+
+ // This may still be gated by the layout.css.backdrop-filter.enabled pref but
+ // the test infrastructure is very sensitive to how changes to that pref
+ // propagate, so we don't include them in the gfxVars/gfxFeature.
+ gfxVars::SetAllowBackdropFilter(feature.IsEnabled());
+}
+
+void gfxPlatform::InitBackdropFilterConfig() {
+ // This would ideally be in the nsCSSProps code
+ // but nsCSSProps is initialized before gfxPlatform
+ // so it has to be done here.
+ gfxVars::AddReceiver(&nsCSSProps::GfxVarReceiver());
+
+ if (!XRE_IsParentProcess()) {
+ // gfxVars doesn't notify receivers when initialized on content processes
+ // we need to explicitly recompute backdrop-filter's enabled state here.
+ nsCSSProps::RecomputeEnabledState(
+ StaticPrefs::GetPrefName_layout_css_backdrop_filter_enabled());
+ return;
+ }
+
+ BackdropFilterPrefChangeCallback(nullptr, nullptr);
+
+ Preferences::RegisterCallback(
+ BackdropFilterPrefChangeCallback,
+ nsDependentCString(
+ StaticPrefs::GetPrefName_layout_css_backdrop_filter_force_enabled()));
+}
+
+static void AcceleratedCanvas2DPrefChangeCallback(const char*, void*) {
+ FeatureState& feature = gfxConfig::GetFeature(Feature::ACCELERATED_CANVAS2D);
+
+ // Reset to track toggling prefs and ensure force-enable does not happen
+ // after blocklist.
+ feature.Reset();
+
+ // gfx.canvas.accelerated pref controls whether platform enables the feature,
+ // but it still allows blocklisting to override it later.
+ feature.SetDefaultFromPref(
+ StaticPrefs::GetPrefName_gfx_canvas_accelerated(), true,
+ StaticPrefs::GetPrefDefault_gfx_canvas_accelerated());
+
+ // gfx.canvas.accelerated.force-enabled overrides the blocklist.
+ if (StaticPrefs::gfx_canvas_accelerated_force_enabled()) {
+ feature.UserForceEnable("Force-enabled by pref");
+ }
+
+ if (kIsAndroid && !gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
+ feature.Disable(FeatureStatus::Blocked, "Disabled by GPU Process disabled",
+ "FEATURE_FAILURE_DISABLED_BY_GPU_PROCESS_DISABLED"_ns);
+ } else if (!gfxConfig::IsEnabled(Feature::WEBRENDER)) {
+ // There isn't much benefit to accelerating Canvas2D if we can't accelerate
+ // WebRender itself.
+ feature.Disable(FeatureStatus::Blocked, "Disabled by Software WebRender",
+ "FEATURE_FAILURE_DISABLED_BY_SOFTWARE_WEBRENDER"_ns);
+ }
+
+ // Check if blocklisted despite the default pref.
+ nsCString message;
+ nsCString failureId;
+ if (!gfxPlatform::IsGfxInfoStatusOkay(
+ nsIGfxInfo::FEATURE_ACCELERATED_CANVAS2D, &message, failureId)) {
+ feature.Disable(FeatureStatus::Blocklisted, message.get(), failureId);
+ }
+
+ gfxVars::SetUseAcceleratedCanvas2D(feature.IsEnabled());
+}
+
+void gfxPlatform::InitAcceleratedCanvas2DConfig() {
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ // Decide during pref changes whether or not to enable acceleration. This
+ // allows easily toggling acceleration on and off to test performance.
+ AcceleratedCanvas2DPrefChangeCallback(nullptr, nullptr);
+
+ Preferences::RegisterCallback(
+ AcceleratedCanvas2DPrefChangeCallback,
+ nsDependentCString(StaticPrefs::GetPrefName_gfx_canvas_accelerated()));
+ Preferences::RegisterCallback(
+ AcceleratedCanvas2DPrefChangeCallback,
+ nsDependentCString(
+ StaticPrefs::GetPrefName_gfx_canvas_accelerated_force_enabled()));
+}
+
+bool gfxPlatform::CanUseHardwareVideoDecoding() {
+ // this function is called from the compositor thread, so it is not
+ // safe to init the prefs etc. from here.
+ MOZ_ASSERT(sLayersAccelerationPrefsInitialized);
+ return sLayersSupportsHardwareVideoDecoding &&
+ !sLayersHardwareVideoDecodingFailed;
+}
+
+bool gfxPlatform::AccelerateLayersByDefault() {
+#if defined(MOZ_GL_PROVIDER) || defined(MOZ_WIDGET_UIKIT)
+ return true;
+#else
+ return false;
+#endif
+}
+
+/* static */
+bool gfxPlatform::UsesOffMainThreadCompositing() {
+ if (XRE_GetProcessType() == GeckoProcessType_GPU) {
+ return true;
+ }
+
+ static bool firstTime = true;
+ static bool result = false;
+
+ if (firstTime) {
+ MOZ_ASSERT(sLayersAccelerationPrefsInitialized);
+ result = gfxVars::BrowserTabsRemoteAutostart() ||
+ !StaticPrefs::
+ layers_offmainthreadcomposition_force_disabled_AtStartup();
+#if defined(MOZ_WIDGET_GTK)
+ // Linux users who chose OpenGL are being included in OMTC
+ result |= StaticPrefs::
+ layers_acceleration_force_enabled_AtStartup_DoNotUseDirectly();
+
+#endif
+ firstTime = false;
+ }
+
+ return result;
+}
+
+RefPtr<mozilla::VsyncDispatcher> gfxPlatform::GetGlobalVsyncDispatcher() {
+ MOZ_ASSERT(mVsyncDispatcher,
+ "mVsyncDispatcher should have been initialized by ReInitFrameRate "
+ "during gfxPlatform init");
+ MOZ_ASSERT(XRE_IsParentProcess());
+ return mVsyncDispatcher;
+}
+
+already_AddRefed<mozilla::gfx::VsyncSource>
+gfxPlatform::GetGlobalHardwareVsyncSource() {
+ if (!mGlobalHardwareVsyncSource) {
+ mGlobalHardwareVsyncSource = CreateGlobalHardwareVsyncSource();
+ }
+ return do_AddRef(mGlobalHardwareVsyncSource);
+}
+
+/***
+ * The preference "layout.frame_rate" has 3 meanings depending on the value:
+ *
+ * -1 = Auto (default), use hardware vsync or software vsync @ 60 hz if hw
+ * vsync fails.
+ * 0 = ASAP mode - used during talos testing.
+ * X = Software vsync at a rate of X times per second.
+ */
+already_AddRefed<mozilla::gfx::VsyncSource>
+gfxPlatform::GetSoftwareVsyncSource() {
+ if (!mSoftwareVsyncSource) {
+ double rateInMS = 1000.0 / (double)gfxPlatform::GetSoftwareVsyncRate();
+ mSoftwareVsyncSource = new mozilla::gfx::SoftwareVsyncSource(
+ TimeDuration::FromMilliseconds(rateInMS));
+ }
+ return do_AddRef(mSoftwareVsyncSource);
+}
+
+/* static */
+bool gfxPlatform::IsInLayoutAsapMode() {
+ // There are 2 modes of ASAP mode.
+ // 1 is that the refresh driver and compositor are in lock step
+ // the second is that the compositor goes ASAP and the refresh driver
+ // goes at whatever the configurated rate is. This only checks the version
+ // talos uses, which is the refresh driver and compositor are in lockstep.
+ // Ignore privacy_resistFingerprinting to preserve ASAP mode there.
+ return StaticPrefs::layout_frame_rate() == 0;
+}
+
+static int LayoutFrameRateFromPrefs() {
+ auto val = StaticPrefs::layout_frame_rate();
+ if (nsContentUtils::ShouldResistFingerprinting(
+ "The frame rate is a global property.")) {
+ val = 60;
+ }
+ return val;
+}
+
+/* static */
+bool gfxPlatform::ForceSoftwareVsync() {
+ return LayoutFrameRateFromPrefs() > 0;
+}
+
+/* static */
+int gfxPlatform::GetSoftwareVsyncRate() {
+ int preferenceRate = LayoutFrameRateFromPrefs();
+ if (preferenceRate <= 0) {
+ return gfxPlatform::GetDefaultFrameRate();
+ }
+ return preferenceRate;
+}
+
+/* static */
+int gfxPlatform::GetDefaultFrameRate() { return 60; }
+
+/* static */
+void gfxPlatform::ReInitFrameRate(const char* aPrefIgnored,
+ void* aDataIgnored) {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+
+ if (gPlatform->mSoftwareVsyncSource) {
+ // Update the rate of the existing software vsync source.
+ double rateInMS = 1000.0 / (double)gfxPlatform::GetSoftwareVsyncRate();
+ gPlatform->mSoftwareVsyncSource->SetVsyncRate(
+ TimeDuration::FromMilliseconds(rateInMS));
+ }
+
+ // Swap out the dispatcher's underlying source.
+ RefPtr<VsyncSource> vsyncSource =
+ gfxPlatform::ForceSoftwareVsync()
+ ? gPlatform->GetSoftwareVsyncSource()
+ : gPlatform->GetGlobalHardwareVsyncSource();
+ gPlatform->mVsyncDispatcher->SetVsyncSource(vsyncSource);
+}
+
+const char* gfxPlatform::GetAzureCanvasBackend() const {
+ BackendType backend{};
+
+ if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
+ // Assume content process' backend prefs.
+ BackendPrefsData data = GetBackendPrefs();
+ backend = GetCanvasBackendPref(data.mCanvasBitmask);
+ if (backend == BackendType::NONE) {
+ backend = data.mCanvasDefault;
+ }
+ } else {
+ backend = mPreferredCanvasBackend;
+ }
+
+ return GetBackendName(backend);
+}
+
+const char* gfxPlatform::GetAzureContentBackend() const {
+ BackendType backend{};
+
+ if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
+ // Assume content process' backend prefs.
+ BackendPrefsData data = GetBackendPrefs();
+ backend = GetContentBackendPref(data.mContentBitmask);
+ if (backend == BackendType::NONE) {
+ backend = data.mContentDefault;
+ }
+ } else {
+ backend = mContentBackend;
+ }
+
+ return GetBackendName(backend);
+}
+
+void gfxPlatform::GetAzureBackendInfo(mozilla::widget::InfoObject& aObj) {
+ if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
+ aObj.DefineProperty("AzureCanvasBackend (UI Process)",
+ GetBackendName(mPreferredCanvasBackend));
+ aObj.DefineProperty("AzureFallbackCanvasBackend (UI Process)",
+ GetBackendName(mFallbackCanvasBackend));
+ aObj.DefineProperty("AzureContentBackend (UI Process)",
+ GetBackendName(mContentBackend));
+ } else {
+ aObj.DefineProperty("AzureFallbackCanvasBackend",
+ GetBackendName(mFallbackCanvasBackend));
+ }
+
+ aObj.DefineProperty("AzureCanvasBackend", GetAzureCanvasBackend());
+ aObj.DefineProperty("AzureContentBackend", GetAzureContentBackend());
+}
+
+void gfxPlatform::GetApzSupportInfo(mozilla::widget::InfoObject& aObj) {
+ if (!gfxPlatform::AsyncPanZoomEnabled()) {
+ return;
+ }
+
+ if (SupportsApzWheelInput()) {
+ aObj.DefineProperty("ApzWheelInput", 1);
+ }
+
+ if (SupportsApzTouchInput()) {
+ aObj.DefineProperty("ApzTouchInput", 1);
+ }
+
+ if (SupportsApzDragInput()) {
+ aObj.DefineProperty("ApzDragInput", 1);
+ }
+
+ if (SupportsApzKeyboardInput() &&
+ !StaticPrefs::accessibility_browsewithcaret()) {
+ aObj.DefineProperty("ApzKeyboardInput", 1);
+ }
+
+ if (SupportsApzAutoscrolling()) {
+ aObj.DefineProperty("ApzAutoscrollInput", 1);
+ }
+
+ if (SupportsApzZooming()) {
+ aObj.DefineProperty("ApzZoomingInput", 1);
+ }
+}
+
+void gfxPlatform::GetFrameStats(mozilla::widget::InfoObject& aObj) {
+ uint32_t i = 0;
+ for (FrameStats& f : mFrameStats) {
+ nsPrintfCString name("Slow Frame #%02u", ++i);
+
+ nsPrintfCString value(
+ "Frame %" PRIu64
+ "(%s) CONTENT_FRAME_TIME %d - Transaction start %f, main-thread time "
+ "%f, full paint time %f, Skipped composites %u, Composite start %f, "
+ "Resource upload time %f, GPU cache upload time %f, Render time %f, "
+ "Composite time %f",
+ f.id().mId, f.url().get(), f.contentFrameTime(),
+ (f.transactionStart() - f.refreshStart()).ToMilliseconds(),
+ (f.fwdTime() - f.transactionStart()).ToMilliseconds(),
+ f.sceneBuiltTime()
+ ? (f.sceneBuiltTime() - f.transactionStart()).ToMilliseconds()
+ : 0.0,
+ f.skippedComposites(),
+ (f.compositeStart() - f.refreshStart()).ToMilliseconds(),
+ f.resourceUploadTime(), f.gpuCacheUploadTime(),
+ (f.compositeEnd() - f.renderStart()).ToMilliseconds(),
+ (f.compositeEnd() - f.compositeStart()).ToMilliseconds());
+ aObj.DefineProperty(name.get(), value.get());
+ }
+}
+
+void gfxPlatform::GetCMSSupportInfo(mozilla::widget::InfoObject& aObj) {
+ nsTArray<uint8_t> outputProfileData =
+ gfxPlatform::GetPlatform()->GetPlatformCMSOutputProfileData();
+ if (outputProfileData.IsEmpty()) {
+ nsPrintfCString msg("Empty profile data");
+ aObj.DefineProperty("CMSOutputProfile", msg.get());
+ return;
+ }
+
+ // Some profiles can be quite large. We don't want to include giant profiles
+ // by default in about:support. For now, we only accept less than 8kiB.
+ const size_t kMaxProfileSize = 8192;
+ if (outputProfileData.Length() >= kMaxProfileSize) {
+ nsPrintfCString msg("%zu bytes, too large", outputProfileData.Length());
+ aObj.DefineProperty("CMSOutputProfile", msg.get());
+ return;
+ }
+
+ nsString encodedProfile;
+ nsresult rv =
+ Base64Encode(reinterpret_cast<const char*>(outputProfileData.Elements()),
+ outputProfileData.Length(), encodedProfile);
+ if (!NS_SUCCEEDED(rv)) {
+ nsPrintfCString msg("base64 encode failed 0x%08x",
+ static_cast<uint32_t>(rv));
+ aObj.DefineProperty("CMSOutputProfile", msg.get());
+ return;
+ }
+
+ aObj.DefineProperty("CMSOutputProfile", encodedProfile);
+}
+
+void gfxPlatform::GetDisplayInfo(mozilla::widget::InfoObject& aObj) {
+ auto& screens = widget::ScreenManager::GetSingleton().CurrentScreenList();
+ aObj.DefineProperty("DisplayCount", screens.Length());
+
+ size_t i = 0;
+ for (auto& screen : screens) {
+ const LayoutDeviceIntRect rect = screen->GetRect();
+ nsPrintfCString value("%dx%d@%dHz scales:%f|%f", rect.width, rect.height,
+ screen->GetRefreshRate(),
+ screen->GetContentsScaleFactor(),
+ screen->GetDefaultCSSScaleFactor());
+
+ aObj.DefineProperty(nsPrintfCString("Display%zu", i++).get(),
+ NS_ConvertUTF8toUTF16(value));
+ }
+
+ // Platform display info is only currently used for about:support and getting
+ // it might fail in a child process anyway.
+ if (XRE_IsParentProcess()) {
+ GetPlatformDisplayInfo(aObj);
+ }
+}
+
+void gfxPlatform::GetOverlayInfo(mozilla::widget::InfoObject& aObj) {
+ if (mOverlayInfo.isNothing()) {
+ return;
+ }
+
+ auto toString = [](mozilla::layers::OverlaySupportType aType) -> const char* {
+ switch (aType) {
+ case mozilla::layers::OverlaySupportType::None:
+ return "None";
+ case mozilla::layers::OverlaySupportType::Software:
+ return "Software";
+ case mozilla::layers::OverlaySupportType::Direct:
+ return "Direct";
+ case mozilla::layers::OverlaySupportType::Scaling:
+ return "Scaling";
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected to be called");
+ }
+ MOZ_CRASH("Incomplete switch");
+ };
+
+ nsPrintfCString value("NV12=%s YUV2=%s BGRA8=%s RGB10A2=%s",
+ toString(mOverlayInfo.ref().mNv12Overlay),
+ toString(mOverlayInfo.ref().mYuy2Overlay),
+ toString(mOverlayInfo.ref().mBgra8Overlay),
+ toString(mOverlayInfo.ref().mRgb10a2Overlay));
+
+ aObj.DefineProperty("OverlaySupport", NS_ConvertUTF8toUTF16(value));
+}
+
+void gfxPlatform::GetSwapChainInfo(mozilla::widget::InfoObject& aObj) {
+ if (mSwapChainInfo.isNothing()) {
+ return;
+ }
+
+ auto toString = [](bool aTearingSupported) -> const char* {
+ if (aTearingSupported) {
+ return "Supported";
+ }
+ return "Not Supported";
+ };
+
+ nsPrintfCString value("%s", toString(mSwapChainInfo.ref().mTearingSupported));
+
+ aObj.DefineProperty("SwapChainTearingSupport", NS_ConvertUTF8toUTF16(value));
+}
+
+class FrameStatsComparator {
+ public:
+ bool Equals(const FrameStats& aA, const FrameStats& aB) const {
+ return aA.contentFrameTime() == aB.contentFrameTime();
+ }
+ // Reverse the condition here since we want the array sorted largest to
+ // smallest.
+ bool LessThan(const FrameStats& aA, const FrameStats& aB) const {
+ return aA.contentFrameTime() > aB.contentFrameTime();
+ }
+};
+
+void gfxPlatform::NotifyFrameStats(nsTArray<FrameStats>&& aFrameStats) {
+ if (!StaticPrefs::gfx_logging_slow_frames_enabled_AtStartup()) {
+ return;
+ }
+
+ FrameStatsComparator comp;
+ for (FrameStats& f : aFrameStats) {
+ mFrameStats.InsertElementSorted(f, comp);
+ }
+ if (mFrameStats.Length() > 10) {
+ mFrameStats.SetLength(10);
+ }
+}
+
+/*static*/
+uint32_t gfxPlatform::TargetFrameRate() {
+ if (gPlatform && gPlatform->mVsyncDispatcher) {
+ return round(1000.0 /
+ gPlatform->mVsyncDispatcher->GetVsyncRate().ToMilliseconds());
+ }
+ return 0;
+}
+
+/* static */
+bool gfxPlatform::UseDesktopZoomingScrollbars() {
+ return StaticPrefs::apz_allow_zooming() &&
+ !StaticPrefs::apz_force_disable_desktop_zooming_scrollbars();
+}
+
+/*static*/
+bool gfxPlatform::AsyncPanZoomEnabled() {
+#if !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_WIDGET_UIKIT)
+ // For XUL applications (everything but Firefox on Android)
+ // we only want to use APZ when E10S is enabled. If
+ // we ever get input events off the main thread we can consider relaxing
+ // this requirement.
+ if (!BrowserTabsRemoteAutostart()) {
+ return false;
+ }
+#endif
+#ifdef MOZ_WIDGET_ANDROID
+ return true;
+#else
+ // If Fission is enabled, OOP iframes require APZ for hittest. So, we
+ // need to forcibly enable APZ in that case for avoiding users confused.
+ if (FissionAutostart()) {
+ return true;
+ }
+ return StaticPrefs::
+ layers_async_pan_zoom_enabled_AtStartup_DoNotUseDirectly();
+#endif
+}
+
+/*static*/
+bool gfxPlatform::PerfWarnings() {
+ return StaticPrefs::gfx_perf_warnings_enabled();
+}
+
+void gfxPlatform::NotifyCompositorCreated(LayersBackend aBackend) {
+ if (mCompositorBackend == aBackend) {
+ return;
+ }
+
+ if (mCompositorBackend != LayersBackend::LAYERS_NONE) {
+ gfxCriticalNote << "Compositors might be mixed (" << int(mCompositorBackend)
+ << "," << int(aBackend) << ")";
+ }
+
+ // Set the backend before we notify so it's available immediately.
+ mCompositorBackend = aBackend;
+
+ if (XRE_IsParentProcess()) {
+ Telemetry::ScalarSet(
+ Telemetry::ScalarID::GFX_COMPOSITOR,
+ NS_ConvertUTF8toUTF16(GetLayersBackendName(mCompositorBackend)));
+
+ nsCString geckoVersion;
+ nsCOMPtr<nsIXULAppInfo> app = do_GetService("@mozilla.org/xre/app-info;1");
+ if (app) {
+ app->GetVersion(geckoVersion);
+ }
+ Telemetry::ScalarSet(Telemetry::ScalarID::GFX_LAST_COMPOSITOR_GECKO_VERSION,
+ NS_ConvertASCIItoUTF16(geckoVersion));
+
+ Telemetry::ScalarSet(
+ Telemetry::ScalarID::GFX_FEATURE_WEBRENDER,
+ NS_ConvertUTF8toUTF16(gfxConfig::GetFeature(gfx::Feature::WEBRENDER)
+ .GetStatusAndFailureIdString()));
+ }
+
+ // Notify that we created a compositor, so telemetry can update.
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("gfxPlatform::NotifyCompositorCreated", [] {
+ if (nsCOMPtr<nsIObserverService> obsvc =
+ services::GetObserverService()) {
+ obsvc->NotifyObservers(nullptr, "compositor:created", nullptr);
+ }
+ }));
+}
+
+/* static */
+bool gfxPlatform::FallbackFromAcceleration(FeatureStatus aStatus,
+ const char* aMessage,
+ const nsACString& aFailureId,
+ bool aCrashAfterFinalFallback) {
+ // We always want to ensure (Hardware) WebRender is disabled.
+ if (gfxConfig::IsEnabled(Feature::WEBRENDER)) {
+ gfxConfig::GetFeature(Feature::WEBRENDER)
+ .ForceDisable(aStatus, aMessage, aFailureId);
+ }
+
+ // Determine whether or not we are allowed to use Software WebRender in
+ // fallback without the GPU process. Either the pref is false, or the feature
+ // is enabled and we are currently still using it.
+ bool swglFallbackAllowed =
+ !StaticPrefs::
+ gfx_webrender_fallback_software_requires_gpu_process_AtStartup() ||
+ gfxConfig::IsEnabled(Feature::GPU_PROCESS);
+
+#ifdef XP_WIN
+ // Before we disable D3D11 and HW_COMPOSITING, we should check if we can
+ // fallback from WebRender to Software WebRender + D3D11 compositing.
+ if (StaticPrefs::gfx_webrender_fallback_software_d3d11_AtStartup() &&
+ swglFallbackAllowed && gfxVars::AllowSoftwareWebRenderD3D11() &&
+ gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING) &&
+ !gfxVars::UseSoftwareWebRender()) {
+ // Fallback to Software WebRender + D3D11 compositing.
+ gfxCriticalNote << "Fallback WR to SW-WR + D3D11";
+ gfxVars::SetUseSoftwareWebRender(true);
+ return true;
+ }
+
+ if (StaticPrefs::gfx_webrender_fallback_software_d3d11_AtStartup() &&
+ swglFallbackAllowed && gfxVars::AllowSoftwareWebRenderD3D11() &&
+ gfxVars::UseSoftwareWebRender()) {
+ // Fallback from Software WebRender + D3D11 to Software WebRender.
+ gfxCriticalNote << "Fallback SW-WR + D3D11 to SW-WR";
+ gfxVars::SetAllowSoftwareWebRenderD3D11(false);
+ return true;
+ }
+
+ // We aren't using Software WebRender + D3D11 compositing, so turn off the
+ // D3D11 and D2D.
+ if (gfxConfig::IsEnabled(Feature::DIRECT2D)) {
+ gfxConfig::GetFeature(Feature::DIRECT2D)
+ .ForceDisable(aStatus, aMessage, aFailureId);
+ }
+ if (gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
+ gfxConfig::GetFeature(Feature::D3D11_COMPOSITING)
+ .ForceDisable(aStatus, aMessage, aFailureId);
+ }
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID
+ // Non-Android wants to fallback to Software WebRender or Basic. Android wants
+ // to fallback to OpenGL.
+ if (gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) {
+ gfxConfig::GetFeature(Feature::HW_COMPOSITING)
+ .ForceDisable(aStatus, aMessage, aFailureId);
+ }
+#endif
+
+ if (StaticPrefs::gfx_webrender_fallback_software_AtStartup() &&
+ swglFallbackAllowed && !gfxVars::UseSoftwareWebRender()) {
+ // Fallback from WebRender to Software WebRender.
+ gfxCriticalNote << "Fallback WR to SW-WR";
+ gfxVars::SetUseSoftwareWebRender(true);
+ return true;
+ }
+
+ if (!gfxVars::UseSoftwareWebRender()) {
+ // Software WebRender may be disabled due to a startup issue with the
+ // blocklist, despite it being our only fallback option based on the prefs.
+ // If WebRender is unable to be initialized, this means that user would
+ // otherwise get stuck with WebRender. As such, force a switch to Software
+ // WebRender in this case.
+ gfxCriticalNoteOnce << "Fallback WR to SW-WR, forced";
+ gfxVars::SetUseSoftwareWebRender(true);
+ return true;
+ }
+
+ if (aCrashAfterFinalFallback) {
+ MOZ_CRASH("Fallback configurations exhausted");
+ }
+
+ // Continue using Software WebRender (disabled fallback to Basic).
+ gfxCriticalNoteOnce << "Fallback remains SW-WR";
+ return false;
+}
+
+/* static */
+void gfxPlatform::DisableGPUProcess() {
+ gfxVars::SetRemoteCanvasEnabled(false);
+ if (kIsAndroid) {
+ // On android, enable out-of-process WebGL only when GPU process exists.
+ gfxVars::SetAllowWebglOop(false);
+ // On android, enable accelerated canvas only when GPU process exists.
+ gfxVars::SetUseAcceleratedCanvas2D(false);
+ gfxConfig::Disable(Feature::ACCELERATED_CANVAS2D, FeatureStatus::Blocked,
+ "Disabled by GPU Process disabled",
+ "FEATURE_FAILURE_DISABLED_BY_GPU_PROCESS_DISABLED"_ns);
+ }
+
+ RemoteTextureMap::Init();
+ if (gfxVars::UseCanvasRenderThread()) {
+ gfx::CanvasRenderThread::Start();
+ }
+ // We need to initialize the parent process to prepare for WebRender if we
+ // did not end up disabling it, despite losing the GPU process.
+ wr::RenderThread::Start(GPUProcessManager::Get()->AllocateNamespace());
+ image::ImageMemoryReporter::InitForWebRender();
+}
+
+void gfxPlatform::FetchAndImportContentDeviceData() {
+ MOZ_ASSERT(XRE_IsContentProcess());
+
+ if (gContentDeviceInitData) {
+ ImportContentDeviceData(*gContentDeviceInitData);
+ return;
+ }
+
+ mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton();
+
+ mozilla::gfx::ContentDeviceData data;
+ cc->SendGetGraphicsDeviceInitData(&data);
+
+ ImportContentDeviceData(data);
+}
+
+void gfxPlatform::ImportContentDeviceData(
+ const mozilla::gfx::ContentDeviceData& aData) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+
+ const DevicePrefs& prefs = aData.prefs();
+ gfxConfig::Inherit(Feature::HW_COMPOSITING, prefs.hwCompositing());
+ gfxConfig::Inherit(Feature::OPENGL_COMPOSITING, prefs.oglCompositing());
+}
+
+void gfxPlatform::BuildContentDeviceData(
+ mozilla::gfx::ContentDeviceData* aOut) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // Make sure our settings are synchronized from the GPU process.
+ DebugOnly<nsresult> rv = GPUProcessManager::Get()->EnsureGPUReady();
+ MOZ_ASSERT(rv != NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
+
+ aOut->prefs().hwCompositing() = gfxConfig::GetValue(Feature::HW_COMPOSITING);
+ aOut->prefs().oglCompositing() =
+ gfxConfig::GetValue(Feature::OPENGL_COMPOSITING);
+}
+
+void gfxPlatform::ImportGPUDeviceData(
+ const mozilla::gfx::GPUDeviceData& aData) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ gfxConfig::ImportChange(Feature::OPENGL_COMPOSITING, aData.oglCompositing());
+}
+
+bool gfxPlatform::SupportsApzTouchInput() const {
+ return dom::TouchEvent::PrefEnabled(nullptr);
+}
+
+bool gfxPlatform::SupportsApzDragInput() const {
+ return StaticPrefs::apz_drag_enabled();
+}
+
+bool gfxPlatform::SupportsApzKeyboardInput() const {
+ return StaticPrefs::apz_keyboard_enabled_AtStartup();
+}
+
+bool gfxPlatform::SupportsApzAutoscrolling() const {
+ return StaticPrefs::apz_autoscroll_enabled();
+}
+
+bool gfxPlatform::SupportsApzZooming() const {
+ return StaticPrefs::apz_allow_zooming();
+}
+
+void gfxPlatform::InitOpenGLConfig() {
+#ifdef XP_WIN
+ // Don't enable by default on Windows, since it could show up in about:support
+ // even though it'll never get used. Only attempt if user enables the pref
+ if (!Preferences::GetBool("layers.prefer-opengl")) {
+ return;
+ }
+#endif
+
+ FeatureState& openGLFeature =
+ gfxConfig::GetFeature(Feature::OPENGL_COMPOSITING);
+
+ // Check to see hw comp supported
+ if (!gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) {
+ openGLFeature.DisableByDefault(FeatureStatus::Unavailable,
+ "Hardware compositing is disabled",
+ "FEATURE_FAILURE_OPENGL_NEED_HWCOMP"_ns);
+ return;
+ }
+
+#ifdef XP_WIN
+ openGLFeature.SetDefaultFromPref(
+ StaticPrefs::GetPrefName_layers_prefer_opengl(), true,
+ StaticPrefs::GetPrefDefault_layers_prefer_opengl());
+#else
+ openGLFeature.EnableByDefault();
+#endif
+
+ // When layers acceleration is force-enabled, enable it even for blocklisted
+ // devices.
+ if (StaticPrefs::
+ layers_acceleration_force_enabled_AtStartup_DoNotUseDirectly()) {
+ openGLFeature.UserForceEnable("Force-enabled by pref");
+ return;
+ }
+
+ nsCString message;
+ nsCString failureId;
+ if (!IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_OPENGL_LAYERS, &message,
+ failureId)) {
+ openGLFeature.Disable(FeatureStatus::Blocklisted, message.get(), failureId);
+ }
+}
+
+bool gfxPlatform::IsGfxInfoStatusOkay(int32_t aFeature, nsCString* aOutMessage,
+ nsCString& aFailureId) {
+ nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
+ if (!gfxInfo) {
+ return true;
+ }
+
+ int32_t status;
+ if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(aFeature, aFailureId, &status)) &&
+ status != nsIGfxInfo::FEATURE_STATUS_OK) {
+ aOutMessage->AssignLiteral("#BLOCKLIST_");
+ aOutMessage->AppendASCII(aFailureId.get());
+ return false;
+ }
+
+ return true;
+}
diff --git a/gfx/thebes/gfxPlatform.h b/gfx/thebes/gfxPlatform.h
new file mode 100644
index 0000000000..db9b8fd436
--- /dev/null
+++ b/gfx/thebes/gfxPlatform.h
@@ -0,0 +1,1034 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_PLATFORM_H
+#define GFX_PLATFORM_H
+
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/intl/UnicodeScriptCodes.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+
+#include "gfxTelemetry.h"
+#include "gfxTypes.h"
+#include "gfxSkipChars.h"
+
+#include "qcms.h"
+
+#include "mozilla/RefPtr.h"
+#include "GfxInfoCollector.h"
+
+#include "mozilla/Maybe.h"
+#include "mozilla/layers/CompositorTypes.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "mozilla/layers/MemoryPressureObserver.h"
+#include "mozilla/layers/OverlayInfo.h"
+
+class gfxASurface;
+class gfxFont;
+class gfxFontGroup;
+struct gfxFontStyle;
+class gfxUserFontSet;
+class gfxFontEntry;
+class gfxPlatformFontList;
+class gfxTextRun;
+class nsIURI;
+class nsAtom;
+class nsIObserver;
+class nsPresContext;
+class SRGBOverrideObserver;
+class gfxTextPerfMetrics;
+typedef struct FT_LibraryRec_* FT_Library;
+
+namespace mozilla {
+struct StyleFontFamilyList;
+class LogModule;
+class VsyncDispatcher;
+namespace layers {
+class FrameStats;
+}
+namespace gfx {
+class DrawTarget;
+class SourceSurface;
+class DataSourceSurface;
+class ScaledFont;
+class VsyncSource;
+class SoftwareVsyncSource;
+class ContentDeviceData;
+class GPUDeviceData;
+class FeatureState;
+
+inline uint32_t BackendTypeBit(BackendType b) { return 1 << uint8_t(b); }
+
+} // namespace gfx
+namespace dom {
+class SystemFontListEntry;
+class SystemFontList;
+} // namespace dom
+} // namespace mozilla
+
+#define MOZ_PERFORMANCE_WARNING(module, ...) \
+ do { \
+ if (gfxPlatform::PerfWarnings()) { \
+ printf_stderr("[" module "] " __VA_ARGS__); \
+ } \
+ } while (0)
+
+enum class CMSMode : int32_t {
+ Off = 0, // No color management
+ All = 1, // Color manage everything
+ TaggedOnly = 2, // Color manage tagged Images Only
+ AllCount = 3
+};
+
+enum eGfxLog {
+ // all font enumerations, localized names, fullname/psnames, cmap loads
+ eGfxLog_fontlist = 0,
+ // timing info on font initialization
+ eGfxLog_fontinit = 1,
+ // dump text runs, font matching, system fallback for content
+ eGfxLog_textrun = 2,
+ // dump text runs, font matching, system fallback for chrome
+ eGfxLog_textrunui = 3,
+ // dump cmap coverage data as they are loaded
+ eGfxLog_cmapdata = 4,
+ // text perf data
+ eGfxLog_textperf = 5
+};
+
+// Used during font matching to express a preference, if any, for whether
+// to use a font that will present a color or monochrome glyph.
+enum class eFontPresentation : uint8_t {
+ // Character does not have the emoji property, so no special heuristics
+ // apply during font selection.
+ Any = 0,
+ // Character is potentially emoji, but Text-style presentation has been
+ // explicitly requested using VS15.
+ Text = 1,
+ // Character has Emoji-style presentation by default (but an author-
+ // provided webfont will be used even if it is not color).
+ EmojiDefault = 2,
+ // Character explicitly requires Emoji-style presentation due to VS16 or
+ // skin-tone codepoint.
+ EmojiExplicit = 3
+};
+
+inline bool PrefersColor(eFontPresentation aPresentation) {
+ return aPresentation >= eFontPresentation::EmojiDefault;
+}
+
+// when searching through pref langs, max number of pref langs
+const uint32_t kMaxLenPrefLangList = 32;
+
+#define UNINITIALIZED_VALUE (-1)
+
+inline const char* GetBackendName(mozilla::gfx::BackendType aBackend) {
+ switch (aBackend) {
+ case mozilla::gfx::BackendType::DIRECT2D:
+ return "direct2d";
+ case mozilla::gfx::BackendType::CAIRO:
+ return "cairo";
+ case mozilla::gfx::BackendType::SKIA:
+ return "skia";
+ case mozilla::gfx::BackendType::RECORDING:
+ return "recording";
+ case mozilla::gfx::BackendType::DIRECT2D1_1:
+ return "direct2d 1.1";
+ case mozilla::gfx::BackendType::WEBRENDER_TEXT:
+ return "webrender text";
+ case mozilla::gfx::BackendType::NONE:
+ return "none";
+ case mozilla::gfx::BackendType::WEBGL:
+ return "webgl";
+ case mozilla::gfx::BackendType::BACKEND_LAST:
+ return "invalid";
+ }
+ MOZ_CRASH("Incomplete switch");
+}
+
+enum class DeviceResetReason {
+ OK = 0, // No reset.
+ HUNG, // Windows specific, guilty device reset.
+ REMOVED, // Windows specific, device removed or driver upgraded.
+ RESET, // Guilty device reset.
+ DRIVER_ERROR, // Innocent device reset.
+ INVALID_CALL, // Windows specific, guilty device reset.
+ OUT_OF_MEMORY,
+ FORCED_RESET, // Simulated device reset.
+ OTHER, // Unrecognized reason for device reset.
+ D3D9_RESET, // Windows specific, not used.
+ NVIDIA_VIDEO, // Linux specific, NVIDIA video memory was reset.
+ UNKNOWN, // GL specific, unknown if guilty or innocent.
+};
+
+enum class ForcedDeviceResetReason {
+ OPENSHAREDHANDLE = 0,
+ COMPOSITOR_UPDATED,
+};
+
+struct BackendPrefsData {
+ uint32_t mCanvasBitmask = 0;
+ mozilla::gfx::BackendType mCanvasDefault = mozilla::gfx::BackendType::NONE;
+ uint32_t mContentBitmask = 0;
+ mozilla::gfx::BackendType mContentDefault = mozilla::gfx::BackendType::NONE;
+};
+
+class gfxPlatform : public mozilla::layers::MemoryPressureListener {
+ friend class SRGBOverrideObserver;
+
+ public:
+ typedef mozilla::StretchRange StretchRange;
+ typedef mozilla::SlantStyleRange SlantStyleRange;
+ typedef mozilla::WeightRange WeightRange;
+ typedef mozilla::gfx::sRGBColor sRGBColor;
+ typedef mozilla::gfx::DeviceColor DeviceColor;
+ typedef mozilla::gfx::DataSourceSurface DataSourceSurface;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::IntSize IntSize;
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+ typedef mozilla::intl::Script Script;
+
+ /**
+ * Return a pointer to the current active platform.
+ * This is a singleton; it contains mostly convenience
+ * functions to obtain platform-specific objects.
+ */
+ static gfxPlatform* GetPlatform();
+
+ /**
+ * Returns whether or not graphics has been initialized yet. This is
+ * intended for Telemetry where we don't necessarily want to initialize
+ * graphics just to observe its state.
+ */
+ static bool Initialized();
+
+ /**
+ * Shut down Thebes.
+ * Init() arranges for this to be called at an appropriate time.
+ */
+ static void Shutdown();
+
+ /**
+ * Initialize gfxPlatform (if not already done) in a child process, with
+ * the provided ContentDeviceData.
+ */
+ static void InitChild(const mozilla::gfx::ContentDeviceData& aData);
+
+ static void InitLayersIPC();
+ static void ShutdownLayersIPC();
+
+ /**
+ * Initialize ScrollMetadata statics. Does not depend on gfxPlatform.
+ */
+ static void InitNullMetadata();
+
+ static int32_t MaxTextureSize();
+ static int32_t MaxAllocSize();
+ static void InitMoz2DLogging();
+
+ static bool IsHeadless();
+
+ static bool UseRemoteCanvas();
+
+ static bool IsBackendAccelerated(
+ const mozilla::gfx::BackendType aBackendType);
+
+ static bool CanMigrateMacGPUs();
+
+ /**
+ * Create an offscreen surface of the given dimensions
+ * and image format.
+ */
+ virtual already_AddRefed<gfxASurface> CreateOffscreenSurface(
+ const IntSize& aSize, gfxImageFormat aFormat) = 0;
+
+ /**
+ * Beware that this method may return DrawTargets which are not fully
+ * supported on the current platform and might fail silently in subtle ways.
+ * This is a massive potential footgun. You should only use these methods for
+ * canvas drawing really. Use extreme caution if you use them for content
+ * where you are not 100% sure we support the DrawTarget we get back. See
+ * SupportsAzureContentForDrawTarget.
+ */
+ static already_AddRefed<DrawTarget> CreateDrawTargetForSurface(
+ gfxASurface* aSurface, const mozilla::gfx::IntSize& aSize);
+
+ /*
+ * Creates a SourceSurface for a gfxASurface. This function does no caching,
+ * so the caller should cache the gfxASurface if it will be used frequently.
+ * The returned surface keeps a reference to aTarget, so it is OK to keep the
+ * surface, even if aTarget changes.
+ * aTarget should not keep a reference to the returned surface because that
+ * will cause a cycle.
+ *
+ * This function is static so that it can be accessed from outside the main
+ * process.
+ *
+ * aIsPlugin is used to tell the backend that they can optimize this surface
+ * specifically because it's used for a plugin. This is mostly for Skia.
+ */
+ static already_AddRefed<SourceSurface> GetSourceSurfaceForSurface(
+ RefPtr<mozilla::gfx::DrawTarget> aTarget, gfxASurface* aSurface,
+ bool aIsPlugin = false);
+
+ static void ClearSourceSurfaceForSurface(gfxASurface* aSurface);
+
+ static already_AddRefed<DataSourceSurface> GetWrappedDataSourceSurface(
+ gfxASurface* aSurface);
+
+ already_AddRefed<DrawTarget> CreateOffscreenContentDrawTarget(
+ const mozilla::gfx::IntSize& aSize, mozilla::gfx::SurfaceFormat aFormat,
+ bool aFallback = false);
+
+ already_AddRefed<DrawTarget> CreateOffscreenCanvasDrawTarget(
+ const mozilla::gfx::IntSize& aSize, mozilla::gfx::SurfaceFormat aFormat);
+
+ already_AddRefed<DrawTarget> CreateSimilarSoftwareDrawTarget(
+ DrawTarget* aDT, const IntSize& aSize,
+ mozilla::gfx::SurfaceFormat aFormat);
+
+ static already_AddRefed<DrawTarget> CreateDrawTargetForData(
+ unsigned char* aData, const mozilla::gfx::IntSize& aSize, int32_t aStride,
+ mozilla::gfx::SurfaceFormat aFormat, bool aUninitialized = false);
+
+ /**
+ * Returns true if we should use Azure to render content with aTarget. For
+ * example, it is possible that we are using Direct2D for rendering and thus
+ * using Azure. But we want to render to a CairoDrawTarget, in which case
+ * SupportsAzureContent will return true but SupportsAzureContentForDrawTarget
+ * will return false.
+ */
+ bool SupportsAzureContentForDrawTarget(mozilla::gfx::DrawTarget* aTarget);
+
+ bool SupportsAzureContentForType(mozilla::gfx::BackendType aType) {
+ return BackendTypeBit(aType) & mContentBackendBitmask;
+ }
+
+ static bool AsyncPanZoomEnabled();
+
+ const char* GetAzureCanvasBackend() const;
+ const char* GetAzureContentBackend() const;
+
+ void GetAzureBackendInfo(mozilla::widget::InfoObject& aObj);
+ void GetApzSupportInfo(mozilla::widget::InfoObject& aObj);
+ void GetFrameStats(mozilla::widget::InfoObject& aObj);
+ void GetCMSSupportInfo(mozilla::widget::InfoObject& aObj);
+ void GetDisplayInfo(mozilla::widget::InfoObject& aObj);
+ void GetOverlayInfo(mozilla::widget::InfoObject& aObj);
+ void GetSwapChainInfo(mozilla::widget::InfoObject& aObj);
+
+ // Get the default content backend that will be used with the default
+ // compositor. If the compositor is known when calling this function,
+ // GetContentBackendFor() should be called instead.
+ mozilla::gfx::BackendType GetDefaultContentBackend() const {
+ return mContentBackend;
+ }
+
+ /// Return the software backend to use by default.
+ mozilla::gfx::BackendType GetSoftwareBackend() { return mSoftwareBackend; }
+
+ // Return the best content backend available that is compatible with the
+ // given layers backend.
+ virtual mozilla::gfx::BackendType GetContentBackendFor(
+ mozilla::layers::LayersBackend aLayers) {
+ return mContentBackend;
+ }
+
+ virtual mozilla::gfx::BackendType GetPreferredCanvasBackend() {
+ return mPreferredCanvasBackend;
+ }
+ mozilla::gfx::BackendType GetFallbackCanvasBackend() {
+ return mFallbackCanvasBackend;
+ }
+
+ /*
+ * Font bits
+ */
+
+ /**
+ * Fill aListOfFonts with the results of querying the list of font names
+ * that correspond to the given language group or generic font family
+ * (or both, or neither).
+ */
+ virtual nsresult GetFontList(nsAtom* aLangGroup,
+ const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts);
+
+ /**
+ * Fill aFontList with a list of SystemFontListEntry records for the
+ * available fonts on the platform; used to pass the list from chrome to
+ * content process. Currently implemented only on MacOSX and Linux.
+ */
+ virtual void ReadSystemFontList(mozilla::dom::SystemFontList*){};
+
+ /**
+ * Rebuilds the system font lists (if aFullRebuild is true), or just notifies
+ * content that the list has changed but existing memory mappings are still
+ * valid (aFullRebuild is false).
+ */
+ nsresult UpdateFontList(bool aFullRebuild = true);
+
+ /**
+ * Create the platform font-list object (gfxPlatformFontList concrete
+ * subclass). This function is responsible to create the appropriate subclass
+ * of gfxPlatformFontList *and* to call its InitFontList() method.
+ */
+ virtual bool CreatePlatformFontList() = 0;
+
+ /**
+ * Resolving a font name to family name. The result MUST be in the result of
+ * GetFontList(). If the name doesn't in the system, aFamilyName will be empty
+ * string, but not failed.
+ */
+ void GetStandardFamilyName(const nsCString& aFontName,
+ nsACString& aFamilyName);
+
+ /**
+ * Returns default font name (localized family name) for aLangGroup and
+ * aGenericFamily. The result is typically the first font in
+ * font.name-list.<aGenericFamily>.<aLangGroup>. However, if it's not
+ * available in the system, this may return second or later font in the
+ * pref. If there are no available fonts in the pref, returns empty string.
+ */
+ nsAutoCString GetDefaultFontName(const nsACString& aLangGroup,
+ const nsACString& aGenericFamily);
+
+ /**
+ * Look up a local platform font using the full font face name.
+ * (Needed to support @font-face src local().)
+ * Ownership of the returned gfxFontEntry is passed to the caller,
+ * who must either AddRef() or delete.
+ */
+ gfxFontEntry* LookupLocalFont(nsPresContext* aPresContext,
+ const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry);
+
+ /**
+ * Activate a platform font. (Needed to support @font-face src url().)
+ * aFontData is a NS_Malloc'ed block that must be freed by this function
+ * (or responsibility passed on) when it is no longer needed; the caller
+ * will NOT free it.
+ * Ownership of the returned gfxFontEntry is passed to the caller,
+ * who must either AddRef() or delete.
+ */
+ gfxFontEntry* MakePlatformFont(const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry,
+ const uint8_t* aFontData, uint32_t aLength);
+
+ /**
+ * Whether to allow downloadable fonts via @font-face rules
+ */
+ bool DownloadableFontsEnabled();
+
+ /**
+ * True when hinting should be enabled. This setting shouldn't
+ * change per gecko process, while the process is live. If so the
+ * results are not defined.
+ *
+ * NB: this bit is only honored by the FT2 backend, currently.
+ */
+ virtual bool FontHintingEnabled() { return true; }
+
+ /**
+ * True when zooming should not require reflow, so glyph metrics and
+ * positioning should not be adjusted for device pixels.
+ * If this is TRUE, then FontHintingEnabled() should be FALSE,
+ * but the converse is not necessarily required;
+ *
+ * Like FontHintingEnabled (above), this setting shouldn't
+ * change per gecko process, while the process is live. If so the
+ * results are not defined.
+ *
+ * NB: this bit is only honored by the FT2 backend, currently.
+ */
+ virtual bool RequiresLinearZoom() { return false; }
+
+ /**
+ * Whether the frame->StyleFont().mFont.smoothing field is respected by
+ * text rendering on this platform.
+ */
+ virtual bool RespectsFontStyleSmoothing() const { return false; }
+
+ /**
+ * Whether to check all font cmaps during system font fallback
+ */
+ bool UseCmapsDuringSystemFallback();
+
+ /**
+ * Whether to render SVG glyphs within an OpenType font wrapper
+ */
+ bool OpenTypeSVGEnabled();
+
+ /**
+ * Max character length of words in the word cache
+ */
+ uint32_t WordCacheCharLimit();
+
+ /**
+ * Max number of entries in word cache
+ */
+ uint32_t WordCacheMaxEntries();
+
+ /**
+ * Whether to use the SIL Graphite rendering engine
+ * (for fonts that include Graphite tables)
+ */
+ bool UseGraphiteShaping();
+
+ // Check whether format is supported on a platform (if unclear, returns true).
+ // Default implementation checks for "common" formats that we support across
+ // all platforms, but individual platform implementations may override.
+ virtual bool IsFontFormatSupported(
+ mozilla::StyleFontFaceSourceFormatKeyword aFormatHint,
+ mozilla::StyleFontFaceSourceTechFlags aTechFlags);
+
+ bool IsKnownIconFontFamily(const nsAtom* aFamilyName) const;
+
+ virtual bool DidRenderingDeviceReset(
+ DeviceResetReason* aResetReason = nullptr) {
+ return false;
+ }
+
+ // returns a list of commonly used fonts for a given character
+ // these are *possible* matches, no cmap-checking is done at this level
+ virtual void GetCommonFallbackFonts(uint32_t /*aCh*/, Script /*aRunScript*/,
+ eFontPresentation /*aPresentation*/,
+ nsTArray<const char*>& /*aFontList*/) {
+ // platform-specific override, by default do nothing
+ }
+
+ // Are we in safe mode?
+ static bool InSafeMode();
+
+ static bool OffMainThreadCompositingEnabled();
+
+ void UpdateCanUseHardwareVideoDecoding();
+
+ /**
+ * Are we going to try color management?
+ */
+ static CMSMode GetCMSMode() {
+ EnsureCMSInitialized();
+ return gCMSMode;
+ }
+
+ /**
+ * Used only for testing. Override the pref setting.
+ */
+ static void SetCMSModeOverride(CMSMode aMode);
+
+ /**
+ * Determines the rendering intent for color management.
+ *
+ * If the value in the pref gfx.color_management.rendering_intent is a
+ * valid rendering intent as defined in gfx/qcms/qcms.h, that
+ * value is returned. Otherwise, -1 is returned and the embedded intent
+ * should be used.
+ *
+ * See bug 444014 for details.
+ */
+ static int GetRenderingIntent();
+
+ /**
+ * Convert a pixel using a cms transform in an endian-aware manner.
+ */
+ static DeviceColor TransformPixel(const sRGBColor& in,
+ qcms_transform* transform);
+
+ /**
+ * Return the output device ICC profile.
+ */
+ static qcms_profile* GetCMSOutputProfile() {
+ EnsureCMSInitialized();
+ return gCMSOutputProfile;
+ }
+
+ /**
+ * Return the sRGB ICC profile.
+ */
+ static qcms_profile* GetCMSsRGBProfile() {
+ EnsureCMSInitialized();
+ return gCMSsRGBProfile;
+ }
+
+ /**
+ * Return sRGB -> output device transform.
+ */
+ static qcms_transform* GetCMSRGBTransform() {
+ EnsureCMSInitialized();
+ return gCMSRGBTransform;
+ }
+
+ /**
+ * Return output -> sRGB device transform.
+ */
+ static qcms_transform* GetCMSInverseRGBTransform() {
+ MOZ_ASSERT(gCMSInitialized);
+ return gCMSInverseRGBTransform;
+ }
+
+ /**
+ * Return sRGBA -> output device transform.
+ */
+ static qcms_transform* GetCMSRGBATransform() {
+ MOZ_ASSERT(gCMSInitialized);
+ return gCMSRGBATransform;
+ }
+
+ /**
+ * Return sBGRA -> output device transform.
+ */
+ static qcms_transform* GetCMSBGRATransform() {
+ MOZ_ASSERT(gCMSInitialized);
+ return gCMSBGRATransform;
+ }
+
+ /**
+ * Return OS RGBA -> output device transform.
+ */
+ static qcms_transform* GetCMSOSRGBATransform();
+
+ /**
+ * Return OS RGBA QCMS type.
+ */
+ static qcms_data_type GetCMSOSRGBAType();
+
+ virtual void FontsPrefsChanged(const char* aPref);
+
+ uint32_t GetBidiNumeralOption();
+
+ /**
+ * Force all presContexts to reflow (and reframe if needed).
+ *
+ * This is used when something about platform settings changes that might have
+ * an effect on layout, such as font rendering settings that influence
+ * metrics, or installed fonts.
+ *
+ * By default it also broadcast it to child processes, but some callers might
+ * not need it if they implement their own notification.
+ */
+ enum class NeedsReframe : bool { No, Yes };
+ enum class BroadcastToChildren : bool { No, Yes };
+ static void ForceGlobalReflow(NeedsReframe,
+ BroadcastToChildren = BroadcastToChildren::Yes);
+
+ static void FlushFontAndWordCaches();
+
+ /**
+ * Returns a 1x1 DrawTarget that can be used for measuring text etc. as
+ * it would measure if rendered on-screen. Guaranteed to return a
+ * non-null and valid DrawTarget.
+ */
+ RefPtr<mozilla::gfx::DrawTarget> ScreenReferenceDrawTarget();
+
+ static RefPtr<mozilla::gfx::DrawTarget>
+ ThreadLocalScreenReferenceDrawTarget();
+
+ virtual mozilla::gfx::SurfaceFormat Optimal2DFormatForContent(
+ gfxContentType aContent);
+
+ virtual gfxImageFormat OptimalFormatForContent(gfxContentType aContent);
+
+ virtual gfxImageFormat GetOffscreenFormat() {
+ return mozilla::gfx::SurfaceFormat::X8R8G8B8_UINT32;
+ }
+
+ /**
+ * Returns a logger if one is available and logging is enabled
+ */
+ static mozilla::LogModule* GetLog(eGfxLog aWhichLog);
+
+ int GetScreenDepth() const { return mScreenDepth; }
+ mozilla::gfx::IntSize GetScreenSize() const { return mScreenSize; }
+
+ static void PurgeSkiaFontCache();
+
+ static bool UsesOffMainThreadCompositing();
+
+ /**
+ * Returns the global vsync dispatcher. There is only one global vsync
+ * dispatcher and it stays around for the entire lifetime of the process.
+ * Must only be called in the parent process.
+ */
+ RefPtr<mozilla::VsyncDispatcher> GetGlobalVsyncDispatcher();
+
+ /**
+ * True if layout rendering should use ASAP mode, which means
+ * the refresh driver and compositor should render ASAP.
+ * Used for talos testing purposes
+ */
+ static bool IsInLayoutAsapMode();
+
+ /**
+ * Returns whether or not a custom vsync rate is set.
+ */
+ static bool ForceSoftwareVsync();
+
+ /**
+ * Returns the software vsync rate to use.
+ */
+ static int GetSoftwareVsyncRate();
+
+ /**
+ * Returns the default frame rate for the refresh driver / software vsync.
+ */
+ static int GetDefaultFrameRate();
+
+ /**
+ * Update the frame rate (called e.g. after pref changes).
+ */
+ static void ReInitFrameRate(const char* aPrefIgnored, void* aDataIgnored);
+
+ /**
+ * Update force subpixel AA quality setting (called after pref
+ * changes).
+ */
+ void UpdateForceSubpixelAAWherePossible();
+
+ /**
+ * Used to test which input types are handled via APZ.
+ */
+ virtual bool SupportsApzWheelInput() const { return false; }
+ bool SupportsApzTouchInput() const;
+ bool SupportsApzDragInput() const;
+ bool SupportsApzKeyboardInput() const;
+ bool SupportsApzAutoscrolling() const;
+ bool SupportsApzZooming() const;
+
+ // If a device reset has occurred, schedule any necessary paints in the
+ // widget. This should only be used within nsRefreshDriver.
+ virtual void SchedulePaintIfDeviceReset() {}
+
+ /**
+ * Helper method, creates a draw target for a specific Azure backend.
+ * Used by CreateOffscreenDrawTarget.
+ */
+ already_AddRefed<DrawTarget> CreateDrawTargetForBackend(
+ mozilla::gfx::BackendType aBackend, const mozilla::gfx::IntSize& aSize,
+ mozilla::gfx::SurfaceFormat aFormat);
+
+ /**
+ * Wrapper around StaticPrefs::gfx_perf_warnings_enabled().
+ * Extracted into a function to avoid including StaticPrefs_gfx.h from this
+ * file.
+ */
+ static bool PerfWarnings();
+
+ static void DisableGPUProcess();
+
+ void NotifyCompositorCreated(mozilla::layers::LayersBackend aBackend);
+ mozilla::layers::LayersBackend GetCompositorBackend() const {
+ return mCompositorBackend;
+ }
+
+ virtual void CompositorUpdated() {}
+
+ // Plugin async drawing support.
+ virtual bool SupportsPluginDirectBitmapDrawing() { return false; }
+
+ // Some platforms don't support CompositorOGL in an unaccelerated OpenGL
+ // context. These platforms should return true here.
+ virtual bool RequiresAcceleratedGLContextForCompositorOGL() const {
+ return false;
+ }
+
+ /**
+ * Check the blocklist for a feature. Returns false if the feature is blocked
+ * with an appropriate message and failure ID.
+ * */
+ static bool IsGfxInfoStatusOkay(int32_t aFeature, nsCString* aOutMessage,
+ nsCString& aFailureId);
+
+ const gfxSkipChars& EmptySkipChars() const { return kEmptySkipChars; }
+
+ /**
+ * Returns a buffer containing the CMS output profile data. The way this
+ * is obtained is platform-specific.
+ */
+ virtual nsTArray<uint8_t> GetPlatformCMSOutputProfileData() {
+ return GetPrefCMSOutputProfileData();
+ }
+
+ /**
+ * Return information on how child processes should initialize graphics
+ * devices.
+ */
+ virtual void BuildContentDeviceData(mozilla::gfx::ContentDeviceData* aOut);
+
+ /**
+ * Imports settings from the GPU process. This should only be called through
+ * GPUProcessManager, in the UI process.
+ */
+ virtual void ImportGPUDeviceData(const mozilla::gfx::GPUDeviceData& aData);
+
+ void SetOverlayInfo(const mozilla::layers::OverlayInfo& aInfo) {
+ mOverlayInfo = mozilla::Some(aInfo);
+ }
+
+ void SetSwapChainInfo(const mozilla::layers::SwapChainInfo& aInfo) {
+ mSwapChainInfo = mozilla::Some(aInfo);
+ }
+
+ static bool HasVariationFontSupport();
+
+ // you probably want to use gfxVars::UseWebRender() instead of this
+ static bool WebRenderPrefEnabled();
+ // you probably want to use gfxVars::UseWebRender() instead of this
+ static bool WebRenderEnvvarEnabled();
+
+ static const char* WebRenderResourcePathOverride();
+
+ // Returns true if we would like to keep the GPU process if possible.
+ // If aCrashAfterFinalFallback is true then crash if we have already
+ // exhausted all of our fallback options. Otherwise we remain on the final
+ // fallback configuration.
+ static bool FallbackFromAcceleration(mozilla::gfx::FeatureStatus aStatus,
+ const char* aMessage,
+ const nsACString& aFailureId,
+ bool aCrashAfterFinalFallback = false);
+
+ void NotifyFrameStats(nsTArray<mozilla::layers::FrameStats>&& aFrameStats);
+
+ virtual void OnMemoryPressure(
+ mozilla::layers::MemoryPressureReason aWhy) override;
+
+ virtual void EnsureDevicesInitialized(){};
+ virtual bool DevicesInitialized() { return true; };
+
+ virtual bool IsWaylandDisplay() { return false; }
+
+ static uint32_t TargetFrameRate();
+
+ static bool UseDesktopZoomingScrollbars();
+
+ virtual bool SupportsHDR() { return false; }
+
+ protected:
+ gfxPlatform();
+ virtual ~gfxPlatform();
+
+ virtual void InitAcceleration();
+ virtual void InitWebRenderConfig();
+ void InitHardwareVideoConfig();
+ virtual void InitWebGLConfig();
+ virtual void InitWebGPUConfig();
+ virtual void InitWindowOcclusionConfig();
+ void InitBackdropFilterConfig();
+ void InitAcceleratedCanvas2DConfig();
+
+ virtual void GetPlatformDisplayInfo(mozilla::widget::InfoObject& aObj) {}
+
+ /**
+ * Called immediately before deleting the gfxPlatform object.
+ */
+ virtual void WillShutdown();
+
+ // Return a hardware vsync source for this platform.
+ already_AddRefed<mozilla::gfx::VsyncSource> GetGlobalHardwareVsyncSource();
+
+ // Return a software vsync source (which uses a timer internally).
+ // Can be used as a fallback for platforms without hardware vsync,
+ // and when the layout.frame_rate pref is set to a non-negative value.
+ already_AddRefed<mozilla::gfx::VsyncSource> GetSoftwareVsyncSource();
+
+ // Create the platform-specific global vsync source. Can fall back to
+ // GetSoftwareVsyncSource().
+ virtual already_AddRefed<mozilla::gfx::VsyncSource>
+ CreateGlobalHardwareVsyncSource() = 0;
+
+ // Returns whether or not layers should be accelerated by default on this
+ // platform.
+ virtual bool AccelerateLayersByDefault();
+
+ // Returns preferences of canvas and content backends.
+ virtual BackendPrefsData GetBackendPrefs() const;
+
+ /**
+ * Initialise the preferred and fallback canvas backends
+ * aBackendBitmask specifies the backends which are acceptable to the caller.
+ * The backend used is determined by aBackendBitmask and the order specified
+ * by the gfx.canvas.azure.backends pref.
+ */
+ void InitBackendPrefs(BackendPrefsData&& aPrefsData);
+
+ /**
+ * Content-process only. Requests device preferences from the parent process
+ * and updates any cached settings.
+ */
+ void FetchAndImportContentDeviceData();
+ virtual void ImportContentDeviceData(
+ const mozilla::gfx::ContentDeviceData& aData);
+
+ public:
+ /**
+ * Returns the contents of the file pointed to by the
+ * gfx.color_management.display_profile pref, if set.
+ * Returns an empty array if not set, or if an error occurs
+ */
+ static nsTArray<uint8_t> GetPrefCMSOutputProfileData();
+
+ protected:
+ /**
+ * If inside a child process and currently being initialized by the
+ * SetXPCOMProcessAttributes message, this can be used by subclasses to
+ * retrieve the ContentDeviceData passed by the message
+ *
+ * If not currently being initialized, will return nullptr. In this case,
+ * child should send a sync message to ask parent for color profile
+ */
+ const mozilla::gfx::ContentDeviceData* GetInitContentDeviceData();
+
+ /**
+ * Increase the global device counter after a device has been removed/reset.
+ */
+ void BumpDeviceCounter();
+
+ /**
+ * returns the first backend named in the pref gfx.canvas.azure.backends
+ * which is a component of aBackendBitmask, a bitmask of backend types
+ */
+ static mozilla::gfx::BackendType GetCanvasBackendPref(
+ uint32_t aBackendBitmask);
+
+ /**
+ * returns the first backend named in the pref gfx.content.azure.backend
+ * which is a component of aBackendBitmask, a bitmask of backend types
+ */
+ static mozilla::gfx::BackendType GetContentBackendPref(
+ uint32_t& aBackendBitmask);
+
+ /**
+ * Will return the first backend named in aBackendPrefName
+ * allowed by aBackendBitmask, a bitmask of backend types.
+ * It also modifies aBackendBitmask to only include backends that are
+ * allowed given the prefs.
+ */
+ static mozilla::gfx::BackendType GetBackendPref(const char* aBackendPrefName,
+ uint32_t& aBackendBitmask);
+ /**
+ * Decode the backend enumberation from a string.
+ */
+ static mozilla::gfx::BackendType BackendTypeForName(const nsCString& aName);
+
+ virtual bool CanUseHardwareVideoDecoding();
+
+ int8_t mAllowDownloadableFonts;
+
+ // Whether the platform supports rendering OpenType font variations
+ static std::atomic<int8_t> sHasVariationFontSupport;
+
+ // The global vsync dispatcher. Only non-null in the parent process.
+ // Its underlying VsyncSource is either mGlobalHardwareVsyncSource
+ // or mSoftwareVsyncSource.
+ RefPtr<mozilla::VsyncDispatcher> mVsyncDispatcher;
+
+ // Cached software vsync source. Only non-null in the parent process,
+ // and only after the first time GetHardwareVsyncSource has been called.
+ RefPtr<mozilla::gfx::VsyncSource> mGlobalHardwareVsyncSource;
+
+ // Cached software vsync source. Only non-null in the parent process,
+ // and only after the first time GetSoftwareVsyncSource has been called.
+ // Used as a fallback source if hardware vsync is not available,
+ // or when the layout.frame_rate pref is set.
+ RefPtr<mozilla::gfx::SoftwareVsyncSource> mSoftwareVsyncSource;
+
+ RefPtr<mozilla::gfx::DrawTarget> mScreenReferenceDrawTarget;
+
+ private:
+ /**
+ * Start up Thebes.
+ */
+ static void Init();
+
+ static void InitOpenGLConfig();
+
+ static mozilla::Atomic<bool, mozilla::MemoryOrdering::ReleaseAcquire>
+ gCMSInitialized;
+ static CMSMode gCMSMode;
+
+ // These two may point to the same profile
+ static qcms_profile* gCMSOutputProfile;
+ static qcms_profile* gCMSsRGBProfile;
+
+ static qcms_transform* gCMSRGBTransform;
+ static qcms_transform* gCMSInverseRGBTransform;
+ static qcms_transform* gCMSRGBATransform;
+ static qcms_transform* gCMSBGRATransform;
+
+ inline static void EnsureCMSInitialized() {
+ if (MOZ_UNLIKELY(!gCMSInitialized)) {
+ InitializeCMS();
+ }
+ }
+
+ static void InitializeCMS();
+ static void ShutdownCMS();
+
+ /**
+ * This uses nsIScreenManager to determine the screen size and color depth
+ */
+ void PopulateScreenInfo();
+
+ void InitCompositorAccelerationPrefs();
+ void InitGPUProcessPrefs();
+ virtual void InitPlatformGPUProcessPrefs() {}
+
+ // Gather telemetry data about the Gfx Platform and send it
+ static void ReportTelemetry();
+
+ static bool IsDXInterop2Blocked();
+ static bool IsDXNV12Blocked();
+ static bool IsDXP010Blocked();
+ static bool IsDXP016Blocked();
+
+ RefPtr<gfxASurface> mScreenReferenceSurface;
+ RefPtr<mozilla::layers::MemoryPressureObserver> mMemoryPressureObserver;
+
+ // The preferred draw target backend to use for canvas
+ mozilla::gfx::BackendType mPreferredCanvasBackend;
+ // The fallback draw target backend to use for canvas, if the preferred
+ // backend fails
+ mozilla::gfx::BackendType mFallbackCanvasBackend;
+ // The backend to use for content
+ mozilla::gfx::BackendType mContentBackend;
+ // The backend to use when we need it not to be accelerated.
+ mozilla::gfx::BackendType mSoftwareBackend;
+ // Bitmask of backend types we can use to render content
+ uint32_t mContentBackendBitmask;
+
+ mozilla::widget::GfxInfoCollector<gfxPlatform> mAzureCanvasBackendCollector;
+ mozilla::widget::GfxInfoCollector<gfxPlatform> mApzSupportCollector;
+ mozilla::widget::GfxInfoCollector<gfxPlatform> mFrameStatsCollector;
+ mozilla::widget::GfxInfoCollector<gfxPlatform> mCMSInfoCollector;
+ mozilla::widget::GfxInfoCollector<gfxPlatform> mDisplayInfoCollector;
+ mozilla::widget::GfxInfoCollector<gfxPlatform> mOverlayInfoCollector;
+ mozilla::widget::GfxInfoCollector<gfxPlatform> mSwapChainInfoCollector;
+
+ nsTArray<mozilla::layers::FrameStats> mFrameStats;
+
+ // Backend that we are compositing with. NONE, if no compositor has been
+ // created yet.
+ mozilla::layers::LayersBackend mCompositorBackend;
+
+ int32_t mScreenDepth;
+ mozilla::gfx::IntSize mScreenSize;
+
+ mozilla::Maybe<mozilla::layers::OverlayInfo> mOverlayInfo;
+ mozilla::Maybe<mozilla::layers::SwapChainInfo> mSwapChainInfo;
+
+ // An instance of gfxSkipChars which is empty. It is used as the
+ // basis for error-case iterators.
+ const gfxSkipChars kEmptySkipChars;
+};
+
+CMSMode GfxColorManagementMode();
+
+#endif /* GFX_PLATFORM_H */
diff --git a/gfx/thebes/gfxPlatformFontList.cpp b/gfx/thebes/gfxPlatformFontList.cpp
new file mode 100644
index 0000000000..1131ecb513
--- /dev/null
+++ b/gfx/thebes/gfxPlatformFontList.cpp
@@ -0,0 +1,3174 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Logging.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/intl/Locale.h"
+#include "mozilla/intl/LocaleService.h"
+#include "mozilla/intl/OSPreferences.h"
+
+#include "gfxPlatformFontList.h"
+#include "gfxTextRun.h"
+#include "gfxUserFontSet.h"
+#include "SharedFontList-impl.h"
+
+#include "GeckoProfiler.h"
+#include "nsCRT.h"
+#include "nsGkAtoms.h"
+#include "nsPresContext.h"
+#include "nsServiceManagerUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsUnicodeProperties.h"
+#include "nsXULAppAPI.h"
+
+#include "mozilla/AppShutdown.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/BlobImpl.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ContentProcessMessageManager.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/ipc/FileDescriptorUtils.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/Unused.h"
+
+#include "base/eintr_wrapper.h"
+
+#include <locale.h>
+#include <numeric>
+
+using namespace mozilla;
+using mozilla::intl::Locale;
+using mozilla::intl::LocaleParser;
+using mozilla::intl::LocaleService;
+using mozilla::intl::OSPreferences;
+
+#define LOG_FONTLIST(args) \
+ MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug, args)
+#define LOG_FONTLIST_ENABLED() \
+ MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug)
+#define LOG_FONTINIT(args) \
+ MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug, args)
+#define LOG_FONTINIT_ENABLED() \
+ MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug)
+
+gfxPlatformFontList* gfxPlatformFontList::sPlatformFontList = nullptr;
+
+// Character ranges that require complex-script shaping support in the font,
+// and so should be masked out by ReadCMAP if the necessary layout tables
+// are not present.
+// Currently used by the Mac and FT2 implementations only, but probably should
+// be supported on Windows as well.
+const gfxFontEntry::ScriptRange gfxPlatformFontList::sComplexScriptRanges[] = {
+ // Actually, now that harfbuzz supports presentation-forms shaping for
+ // Arabic, we can render it without layout tables. So maybe we don't
+ // want to mask the basic Arabic block here?
+ // This affects the arabic-fallback-*.html reftests, which rely on
+ // loading a font that *doesn't* have any GSUB table.
+ {0x0600, 0x060B, 1, {TRUETYPE_TAG('a', 'r', 'a', 'b'), 0, 0}},
+ // skip 060C Arabic comma, also used by N'Ko etc
+ {0x060D, 0x061A, 1, {TRUETYPE_TAG('a', 'r', 'a', 'b'), 0, 0}},
+ // skip 061B Arabic semicolon, also used by N'Ko etc
+ {0x061C, 0x061E, 1, {TRUETYPE_TAG('a', 'r', 'a', 'b'), 0, 0}},
+ // skip 061F Arabic question mark, also used by N'Ko etc
+ {0x0620, 0x063F, 1, {TRUETYPE_TAG('a', 'r', 'a', 'b'), 0, 0}},
+ // skip 0640 Arabic tatweel (for syriac, adlam, etc)
+ {0x0641, 0x06D3, 1, {TRUETYPE_TAG('a', 'r', 'a', 'b'), 0, 0}},
+ // skip 06D4 Arabic full stop (for hanifi rohingya)
+ {0x06D5, 0x06FF, 1, {TRUETYPE_TAG('a', 'r', 'a', 'b'), 0, 0}},
+ {0x0700, 0x074F, 1, {TRUETYPE_TAG('s', 'y', 'r', 'c'), 0, 0}},
+ {0x0750, 0x077F, 1, {TRUETYPE_TAG('a', 'r', 'a', 'b'), 0, 0}},
+ {0x08A0, 0x08FF, 1, {TRUETYPE_TAG('a', 'r', 'a', 'b'), 0, 0}},
+ {0x0900,
+ 0x0963,
+ 2,
+ {TRUETYPE_TAG('d', 'e', 'v', '2'), TRUETYPE_TAG('d', 'e', 'v', 'a'), 0}},
+ // skip 0964 DEVANAGARI DANDA and 0965 DEVANAGARI DOUBLE DANDA, shared by
+ // various other Indic writing systems
+ {0x0966,
+ 0x097F,
+ 2,
+ {TRUETYPE_TAG('d', 'e', 'v', '2'), TRUETYPE_TAG('d', 'e', 'v', 'a'), 0}},
+ {0x0980,
+ 0x09FF,
+ 2,
+ {TRUETYPE_TAG('b', 'n', 'g', '2'), TRUETYPE_TAG('b', 'e', 'n', 'g'), 0}},
+ {0x0A00,
+ 0x0A7F,
+ 2,
+ {TRUETYPE_TAG('g', 'u', 'r', '2'), TRUETYPE_TAG('g', 'u', 'r', 'u'), 0}},
+ {0x0A80,
+ 0x0AFF,
+ 2,
+ {TRUETYPE_TAG('g', 'j', 'r', '2'), TRUETYPE_TAG('g', 'u', 'j', 'r'), 0}},
+ {0x0B00,
+ 0x0B7F,
+ 2,
+ {TRUETYPE_TAG('o', 'r', 'y', '2'), TRUETYPE_TAG('o', 'r', 'y', 'a'), 0}},
+ {0x0B80,
+ 0x0BFF,
+ 2,
+ {TRUETYPE_TAG('t', 'm', 'l', '2'), TRUETYPE_TAG('t', 'a', 'm', 'l'), 0}},
+ {0x0C00,
+ 0x0C7F,
+ 2,
+ {TRUETYPE_TAG('t', 'e', 'l', '2'), TRUETYPE_TAG('t', 'e', 'l', 'u'), 0}},
+ {0x0C80,
+ 0x0CFF,
+ 2,
+ {TRUETYPE_TAG('k', 'n', 'd', '2'), TRUETYPE_TAG('k', 'n', 'd', 'a'), 0}},
+ {0x0D00,
+ 0x0D7F,
+ 2,
+ {TRUETYPE_TAG('m', 'l', 'm', '2'), TRUETYPE_TAG('m', 'l', 'y', 'm'), 0}},
+ {0x0D80, 0x0DFF, 1, {TRUETYPE_TAG('s', 'i', 'n', 'h'), 0, 0}},
+ {0x0E80, 0x0EFF, 1, {TRUETYPE_TAG('l', 'a', 'o', ' '), 0, 0}},
+ {0x0F00, 0x0FFF, 1, {TRUETYPE_TAG('t', 'i', 'b', 't'), 0, 0}},
+ {0x1000,
+ 0x109f,
+ 2,
+ {TRUETYPE_TAG('m', 'y', 'm', 'r'), TRUETYPE_TAG('m', 'y', 'm', '2'), 0}},
+ {0x1780, 0x17ff, 1, {TRUETYPE_TAG('k', 'h', 'm', 'r'), 0, 0}},
+ // Khmer Symbols (19e0..19ff) don't seem to need any special shaping
+ {0xaa60,
+ 0xaa7f,
+ 2,
+ {TRUETYPE_TAG('m', 'y', 'm', 'r'), TRUETYPE_TAG('m', 'y', 'm', '2'), 0}},
+ // Thai seems to be "renderable" without AAT morphing tables
+ {0, 0, 0, {0, 0, 0}} // terminator
+};
+
+static const char* kObservedPrefs[] = {
+ "font.", "font.name-list.", "intl.accept_languages", // hmmmm...
+ "browser.display.use_document_fonts.icon_font_allowlist", nullptr};
+
+static const char kFontSystemWhitelistPref[] = "font.system.whitelist";
+
+static const char kCJKFallbackOrderPref[] = "font.cjk_pref_fallback_order";
+
+// Pref for the list of icon font families that still get to override the
+// default font from prefs, even when use_document_fonts is disabled.
+// (This is to enable ligature-based icon fonts to keep working.)
+static const char kIconFontsPref[] =
+ "browser.display.use_document_fonts.icon_font_allowlist";
+
+// xxx - this can probably be eliminated by reworking pref font handling code
+static const char* gPrefLangNames[] = {
+#define FONT_PREF_LANG(enum_id_, str_, atom_id_) str_
+#include "gfxFontPrefLangList.h"
+#undef FONT_PREF_LANG
+};
+
+static_assert(MOZ_ARRAY_LENGTH(gPrefLangNames) == uint32_t(eFontPrefLang_Count),
+ "size of pref lang name array doesn't match pref lang enum size");
+
+class gfxFontListPrefObserver final : public nsIObserver {
+ ~gfxFontListPrefObserver() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+};
+
+static void FontListPrefChanged(const char* aPref, void* aData = nullptr) {
+ // XXX this could be made to only clear out the cache for the prefs that were
+ // changed but it probably isn't that big a deal.
+ gfxPlatformFontList::PlatformFontList()->ClearLangGroupPrefFonts();
+ gfxPlatformFontList::PlatformFontList()->LoadIconFontOverrideList();
+ gfxFontCache::GetCache()->Flush();
+}
+
+static gfxFontListPrefObserver* gFontListPrefObserver = nullptr;
+
+NS_IMPL_ISUPPORTS(gfxFontListPrefObserver, nsIObserver)
+
+#define LOCALES_CHANGED_TOPIC "intl:system-locales-changed"
+
+NS_IMETHODIMP
+gfxFontListPrefObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ NS_ASSERTION(!strcmp(aTopic, LOCALES_CHANGED_TOPIC), "invalid topic");
+ FontListPrefChanged(nullptr);
+
+ if (XRE_IsParentProcess()) {
+ gfxPlatform::ForceGlobalReflow(gfxPlatform::NeedsReframe::No);
+ }
+ return NS_OK;
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(FontListMallocSizeOf)
+
+NS_IMPL_ISUPPORTS(gfxPlatformFontList::MemoryReporter, nsIMemoryReporter)
+
+NS_IMETHODIMP
+gfxPlatformFontList::MemoryReporter::CollectReports(
+ nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) {
+ FontListSizes sizes;
+ sizes.mFontListSize = 0;
+ sizes.mFontTableCacheSize = 0;
+ sizes.mCharMapsSize = 0;
+ sizes.mLoaderSize = 0;
+ sizes.mSharedSize = 0;
+
+ gfxPlatformFontList::PlatformFontList()->AddSizeOfIncludingThis(
+ &FontListMallocSizeOf, &sizes);
+
+ MOZ_COLLECT_REPORT(
+ "explicit/gfx/font-list", KIND_HEAP, UNITS_BYTES, sizes.mFontListSize,
+ "Memory used to manage the list of font families and faces.");
+
+ MOZ_COLLECT_REPORT(
+ "explicit/gfx/font-charmaps", KIND_HEAP, UNITS_BYTES, sizes.mCharMapsSize,
+ "Memory used to record the character coverage of individual fonts.");
+
+ if (sizes.mFontTableCacheSize) {
+ MOZ_COLLECT_REPORT(
+ "explicit/gfx/font-tables", KIND_HEAP, UNITS_BYTES,
+ sizes.mFontTableCacheSize,
+ "Memory used for cached font metrics and layout tables.");
+ }
+
+ if (sizes.mLoaderSize) {
+ MOZ_COLLECT_REPORT("explicit/gfx/font-loader", KIND_HEAP, UNITS_BYTES,
+ sizes.mLoaderSize,
+ "Memory used for (platform-specific) font loader.");
+ }
+
+ if (sizes.mSharedSize) {
+ MOZ_COLLECT_REPORT(
+ "font-list-shmem", KIND_NONHEAP, UNITS_BYTES, sizes.mSharedSize,
+ "Shared memory for system font list and character coverage data.");
+ }
+
+ return NS_OK;
+}
+
+PRThread* gfxPlatformFontList::sInitFontListThread = nullptr;
+
+static void InitFontListCallback(void* aFontList) {
+ AUTO_PROFILER_REGISTER_THREAD("InitFontList");
+ PR_SetCurrentThreadName("InitFontList");
+
+ if (!static_cast<gfxPlatformFontList*>(aFontList)->InitFontList()) {
+ gfxPlatformFontList::Shutdown();
+ }
+}
+
+/* static */
+bool gfxPlatformFontList::Initialize(gfxPlatformFontList* aList) {
+ sPlatformFontList = aList;
+ if (XRE_IsParentProcess() &&
+ StaticPrefs::gfx_font_list_omt_enabled_AtStartup() &&
+ StaticPrefs::gfx_e10s_font_list_shared_AtStartup() &&
+ !gfxPlatform::InSafeMode()) {
+ sInitFontListThread = PR_CreateThread(
+ PR_USER_THREAD, InitFontListCallback, aList, PR_PRIORITY_NORMAL,
+ PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);
+ return true;
+ }
+ if (aList->InitFontList()) {
+ return true;
+ }
+ Shutdown();
+ return false;
+}
+
+gfxPlatformFontList::gfxPlatformFontList(bool aNeedFullnamePostscriptNames)
+ : mLock("gfxPlatformFontList lock"),
+ mFontFamilies(64),
+ mOtherFamilyNames(16),
+ mSharedCmaps(8) {
+ if (aNeedFullnamePostscriptNames) {
+ mExtraNames = MakeUnique<ExtraNames>();
+ }
+
+ mLangService = nsLanguageAtomService::GetService();
+
+ LoadBadUnderlineList();
+ LoadIconFontOverrideList();
+
+ mFontPrefs = MakeUnique<FontPrefs>();
+
+ gfxFontUtils::GetPrefsFontList(kFontSystemWhitelistPref, mEnabledFontsList);
+ mFontFamilyWhitelistActive = !mEnabledFontsList.IsEmpty();
+
+ // pref changes notification setup
+ NS_ASSERTION(!gFontListPrefObserver,
+ "There has been font list pref observer already");
+ gFontListPrefObserver = new gfxFontListPrefObserver();
+ NS_ADDREF(gFontListPrefObserver);
+
+ Preferences::RegisterPrefixCallbacks(FontListPrefChanged, kObservedPrefs);
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->AddObserver(gFontListPrefObserver, LOCALES_CHANGED_TOPIC, false);
+ }
+
+ // Only the parent process listens for whitelist changes; it will then
+ // notify its children to rebuild their font lists.
+ if (XRE_IsParentProcess()) {
+ Preferences::RegisterCallback(FontWhitelistPrefChanged,
+ kFontSystemWhitelistPref);
+ }
+
+ RegisterStrongMemoryReporter(new MemoryReporter());
+
+ // initialize lang group pref font defaults (i.e. serif/sans-serif)
+ mDefaultGenericsLangGroup.AppendElements(ArrayLength(gPrefLangNames));
+ for (uint32_t i = 0; i < ArrayLength(gPrefLangNames); i++) {
+ nsAutoCString prefDefaultFontType("font.default.");
+ prefDefaultFontType.Append(GetPrefLangName(eFontPrefLang(i)));
+ nsAutoCString serifOrSans;
+ Preferences::GetCString(prefDefaultFontType.get(), serifOrSans);
+ if (serifOrSans.EqualsLiteral("sans-serif")) {
+ mDefaultGenericsLangGroup[i] = StyleGenericFontFamily::SansSerif;
+ } else {
+ mDefaultGenericsLangGroup[i] = StyleGenericFontFamily::Serif;
+ }
+ }
+}
+
+gfxPlatformFontList::~gfxPlatformFontList() {
+ // We take the lock here because it's possible the InitFontList thread is
+ // still running, in which case we need to wait for it to finish; this will
+ // block until the lock becomes available, ensuring we don't destroy things
+ // the initialization thread is using.
+ AutoLock lock(mLock);
+
+ // We can't just do mSharedCmaps.Clear() here because removing each item from
+ // the table would drop its last reference, and its Release() method would
+ // then call back to MaybeRemoveCmap to search for it, which we can't do
+ // while in the middle of clearing the table.
+ // So we first clear the "shared" flag in each entry, so Release() won't try
+ // to re-find them in the table.
+ for (auto iter = mSharedCmaps.ConstIter(); !iter.Done(); iter.Next()) {
+ iter.Get()->mCharMap->ClearSharedFlag();
+ }
+ mSharedCmaps.Clear();
+
+ ClearLangGroupPrefFontsLocked();
+
+ NS_ASSERTION(gFontListPrefObserver, "There is no font list pref observer");
+
+ Preferences::UnregisterPrefixCallbacks(FontListPrefChanged, kObservedPrefs);
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(gFontListPrefObserver, LOCALES_CHANGED_TOPIC);
+ }
+
+ if (XRE_IsParentProcess()) {
+ Preferences::UnregisterCallback(FontWhitelistPrefChanged,
+ kFontSystemWhitelistPref);
+ }
+ NS_RELEASE(gFontListPrefObserver);
+}
+
+/* static */
+void gfxPlatformFontList::FontWhitelistPrefChanged(const char* aPref,
+ void* aClosure) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ auto* pfl = gfxPlatformFontList::PlatformFontList();
+ pfl->UpdateFontList(true);
+ dom::ContentParent::NotifyUpdatedFonts(true);
+}
+
+void gfxPlatformFontList::ApplyWhitelist() {
+ uint32_t numFonts = mEnabledFontsList.Length();
+ if (!mFontFamilyWhitelistActive) {
+ return;
+ }
+ nsTHashSet<nsCString> familyNamesWhitelist;
+ for (uint32_t i = 0; i < numFonts; i++) {
+ nsAutoCString key;
+ ToLowerCase(mEnabledFontsList[i], key);
+ familyNamesWhitelist.Insert(key);
+ }
+ AutoTArray<RefPtr<gfxFontFamily>, 128> accepted;
+ bool whitelistedFontFound = false;
+ for (const auto& entry : mFontFamilies) {
+ if (entry.GetData()->IsHidden()) {
+ // Hidden system fonts are exempt from whitelisting, but don't count
+ // towards determining whether we "kept" any (user-visible) fonts
+ accepted.AppendElement(entry.GetData());
+ continue;
+ }
+ nsAutoCString fontFamilyName(entry.GetKey());
+ ToLowerCase(fontFamilyName);
+ if (familyNamesWhitelist.Contains(fontFamilyName)) {
+ accepted.AppendElement(entry.GetData());
+ whitelistedFontFound = true;
+ }
+ }
+ if (!whitelistedFontFound) {
+ // No whitelisted fonts found! Ignore the whitelist.
+ return;
+ }
+ // Replace the original full list with the accepted subset.
+ mFontFamilies.Clear();
+ for (auto& f : accepted) {
+ nsAutoCString fontFamilyName(f->Name());
+ ToLowerCase(fontFamilyName);
+ mFontFamilies.InsertOrUpdate(fontFamilyName, std::move(f));
+ }
+}
+
+void gfxPlatformFontList::ApplyWhitelist(
+ nsTArray<fontlist::Family::InitData>& aFamilies) {
+ mLock.AssertCurrentThreadIn();
+ if (!mFontFamilyWhitelistActive) {
+ return;
+ }
+ nsTHashSet<nsCString> familyNamesWhitelist;
+ for (const auto& item : mEnabledFontsList) {
+ nsAutoCString key;
+ ToLowerCase(item, key);
+ familyNamesWhitelist.Insert(key);
+ }
+ AutoTArray<fontlist::Family::InitData, 128> accepted;
+ bool keptNonHidden = false;
+ for (auto& f : aFamilies) {
+ if (f.mVisibility == FontVisibility::Hidden ||
+ familyNamesWhitelist.Contains(f.mKey)) {
+ accepted.AppendElement(f);
+ if (f.mVisibility != FontVisibility::Hidden) {
+ keptNonHidden = true;
+ }
+ }
+ }
+ if (!keptNonHidden) {
+ // No (visible) families were whitelisted: ignore the whitelist
+ // and just leave the fontlist unchanged.
+ return;
+ }
+ aFamilies = std::move(accepted);
+}
+
+bool gfxPlatformFontList::FamilyInList(const nsACString& aName,
+ const char* aList[], size_t aCount) {
+ size_t result;
+ return BinarySearchIf(
+ aList, 0, aCount,
+ [&](const char* const aVal) -> int {
+ return nsCaseInsensitiveUTF8StringComparator(
+ aName.BeginReading(), aVal, aName.Length(), strlen(aVal));
+ },
+ &result);
+}
+
+void gfxPlatformFontList::CheckFamilyList(const char* aList[], size_t aCount) {
+#ifdef DEBUG
+ MOZ_ASSERT(aCount > 0, "empty font family list?");
+ const char* a = aList[0];
+ uint32_t aLen = strlen(a);
+ for (size_t i = 1; i < aCount; ++i) {
+ const char* b = aList[i];
+ uint32_t bLen = strlen(b);
+ MOZ_ASSERT(nsCaseInsensitiveUTF8StringComparator(a, b, aLen, bLen) < 0,
+ "incorrectly sorted font family list!");
+ a = b;
+ aLen = bLen;
+ }
+#endif
+}
+
+bool gfxPlatformFontList::AddWithLegacyFamilyName(const nsACString& aLegacyName,
+ gfxFontEntry* aFontEntry,
+ FontVisibility aVisibility) {
+ mLock.AssertCurrentThreadIn();
+ bool added = false;
+ nsAutoCString key;
+ ToLowerCase(aLegacyName, key);
+ mOtherFamilyNames
+ .LookupOrInsertWith(key,
+ [&] {
+ RefPtr<gfxFontFamily> family =
+ CreateFontFamily(aLegacyName, aVisibility);
+ // We don't want the family to search for faces,
+ // we're adding them directly here.
+ family->SetHasStyles(true);
+ // And we don't want it to attempt to search for
+ // legacy names, because we've already done that
+ // (and this is the result).
+ family->SetCheckedForLegacyFamilyNames(true);
+ added = true;
+ return family;
+ })
+ ->AddFontEntry(aFontEntry->Clone());
+ return added;
+}
+
+bool gfxPlatformFontList::InitFontList() {
+ // If the startup font-list-init thread is still running, we need to wait
+ // for it to finish before trying to reinitialize here.
+ if (sInitFontListThread && !IsInitFontListThread()) {
+ PR_JoinThread(sInitFontListThread);
+ sInitFontListThread = nullptr;
+ }
+
+ AutoLock lock(mLock);
+
+ if (LOG_FONTINIT_ENABLED()) {
+ LOG_FONTINIT(("(fontinit) system fontlist initialization\n"));
+ }
+
+ if (IsInitialized()) {
+ // Font-list reinitialization always occurs on the main thread, in response
+ // to a change notification; it's only the initial creation during startup
+ // that may be on another thread.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Rebuilding fontlist so clear out font/word caches.
+ gfxFontCache* fontCache = gfxFontCache::GetCache();
+ if (fontCache) {
+ fontCache->FlushShapedWordCaches();
+ fontCache->Flush();
+ }
+
+ gfxPlatform::PurgeSkiaFontCache();
+
+ // There's no need to broadcast this reflow request to child processes, as
+ // ContentParent::NotifyUpdatedFonts deals with it by re-entering into this
+ // function on child processes.
+ ForceGlobalReflowLocked(gfxPlatform::NeedsReframe::Yes,
+ gfxPlatform::BroadcastToChildren::No);
+
+ mAliasTable.Clear();
+ mLocalNameTable.Clear();
+ mIconFontsSet.Clear();
+
+ CancelLoadCmapsTask();
+ mStartedLoadingCmapsFrom = 0xffffffffu;
+
+ CancelInitOtherFamilyNamesTask();
+ mFontFamilies.Clear();
+ mOtherFamilyNames.Clear();
+ mOtherFamilyNamesInitialized = false;
+
+ if (mExtraNames) {
+ mExtraNames->mFullnames.Clear();
+ mExtraNames->mPostscriptNames.Clear();
+ }
+ mFaceNameListsInitialized = false;
+ ClearLangGroupPrefFontsLocked();
+ CancelLoader();
+
+ // Clear cached family records that will no longer be valid.
+ for (auto& f : mReplacementCharFallbackFamily) {
+ f = FontFamily();
+ }
+
+ gfxFontUtils::GetPrefsFontList(kFontSystemWhitelistPref, mEnabledFontsList);
+ mFontFamilyWhitelistActive = !mEnabledFontsList.IsEmpty();
+
+ LoadIconFontOverrideList();
+ }
+
+ // From here, gfxPlatformFontList::IsInitialized will return true,
+ // unless InitFontListForPlatform() fails and we reset it below.
+ mFontlistInitCount++;
+
+ InitializeCodepointsWithNoFonts();
+
+ // Try to initialize the cross-process shared font list if enabled by prefs,
+ // but not if we're running in Safe Mode.
+ if (StaticPrefs::gfx_e10s_font_list_shared_AtStartup() &&
+ !gfxPlatform::InSafeMode()) {
+ for (const auto& entry : mFontEntries.Values()) {
+ if (!entry) {
+ continue;
+ }
+ AutoWriteLock lock(entry->mLock);
+ entry->mShmemCharacterMap = nullptr;
+ entry->mShmemFace = nullptr;
+ entry->mFamilyName.Truncate();
+ }
+ mFontEntries.Clear();
+ mShmemCharMaps.Clear();
+ bool oldSharedList = mSharedFontList != nullptr;
+ mSharedFontList.reset(new fontlist::FontList(mFontlistInitCount));
+ InitSharedFontListForPlatform();
+ if (mSharedFontList && mSharedFontList->Initialized()) {
+ if (mLocalNameTable.Count()) {
+ SharedFontList()->SetLocalNames(mLocalNameTable);
+ mLocalNameTable.Clear();
+ }
+ } else {
+ // something went wrong, fall back to in-process list
+ gfxCriticalNote << "Failed to initialize shared font list, "
+ "falling back to in-process list.";
+ mSharedFontList.reset(nullptr);
+ }
+ if (oldSharedList && XRE_IsParentProcess()) {
+ // notify all children of the change
+ if (NS_IsMainThread()) {
+ dom::ContentParent::NotifyUpdatedFonts(true);
+ } else {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "NotifyUpdatedFonts callback",
+ [] { dom::ContentParent::NotifyUpdatedFonts(true); }));
+ }
+ }
+ }
+
+ if (!SharedFontList()) {
+ if (NS_FAILED(InitFontListForPlatform())) {
+ mFontlistInitCount = 0;
+ return false;
+ }
+ ApplyWhitelist();
+ }
+
+ // Set up mDefaultFontEntry as a "last resort" default that we can use
+ // to avoid crashing if the font list is otherwise unusable.
+ gfxFontStyle defStyle;
+ FontFamily fam = GetDefaultFontLocked(nullptr, &defStyle);
+ gfxFontEntry* fe;
+ if (fam.mShared) {
+ auto face = fam.mShared->FindFaceForStyle(SharedFontList(), defStyle);
+ fe = face ? GetOrCreateFontEntryLocked(face, fam.mShared) : nullptr;
+ } else {
+ fe = fam.mUnshared->FindFontForStyle(defStyle);
+ }
+ mDefaultFontEntry = fe;
+
+ return true;
+}
+
+void gfxPlatformFontList::LoadIconFontOverrideList() {
+ mIconFontsSet.Clear();
+ AutoTArray<nsCString, 20> iconFontsList;
+ gfxFontUtils::GetPrefsFontList(kIconFontsPref, iconFontsList);
+ for (auto& name : iconFontsList) {
+ ToLowerCase(name);
+ mIconFontsSet.Insert(name);
+ }
+}
+
+void gfxPlatformFontList::InitializeCodepointsWithNoFonts() {
+ auto& first = mCodepointsWithNoFonts[FontVisibility(0)];
+ for (auto& bitset : mCodepointsWithNoFonts) {
+ if (&bitset == &first) {
+ bitset.reset();
+ bitset.SetRange(0, 0x1f); // C0 controls
+ bitset.SetRange(0x7f, 0x9f); // C1 controls
+ bitset.SetRange(0xE000, 0xF8FF); // PUA
+ bitset.SetRange(0xF0000, 0x10FFFD); // Supplementary PUA
+ bitset.SetRange(0xfdd0, 0xfdef); // noncharacters
+ for (unsigned i = 0; i <= 0x100000; i += 0x10000) {
+ bitset.SetRange(i + 0xfffe, i + 0xffff); // noncharacters
+ }
+ bitset.Compact();
+ } else {
+ bitset = first;
+ }
+ }
+}
+
+void gfxPlatformFontList::FontListChanged() {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ AutoLock lock(mLock);
+ InitializeCodepointsWithNoFonts();
+ if (SharedFontList()) {
+ // If we're using a shared local face-name list, this may have changed
+ // such that existing font entries held by user font sets are no longer
+ // safe to use: ensure they all get flushed.
+ RebuildLocalFonts(/*aForgetLocalFaces*/ true);
+ }
+ ForceGlobalReflowLocked(gfxPlatform::NeedsReframe::Yes);
+}
+
+void gfxPlatformFontList::GenerateFontListKey(const nsACString& aKeyName,
+ nsACString& aResult) {
+ aResult = aKeyName;
+ ToLowerCase(aResult);
+}
+
+// Used if a stylo thread wants to trigger InitOtherFamilyNames in the main
+// process: we can't do IPC from the stylo thread so we post this to the main
+// thread instead.
+class InitOtherFamilyNamesForStylo : public mozilla::Runnable {
+ public:
+ explicit InitOtherFamilyNamesForStylo(bool aDeferOtherFamilyNamesLoading)
+ : Runnable("gfxPlatformFontList::InitOtherFamilyNamesForStylo"),
+ mDefer(aDeferOtherFamilyNamesLoading) {}
+
+ NS_IMETHOD Run() override {
+ auto pfl = gfxPlatformFontList::PlatformFontList();
+ auto list = pfl->SharedFontList();
+ if (!list) {
+ return NS_OK;
+ }
+ bool initialized = false;
+ dom::ContentChild::GetSingleton()->SendInitOtherFamilyNames(
+ list->GetGeneration(), mDefer, &initialized);
+ pfl->mOtherFamilyNamesInitialized.compareExchange(false, initialized);
+ return NS_OK;
+ }
+
+ private:
+ bool mDefer;
+};
+
+#define OTHERNAMES_TIMEOUT 200
+
+bool gfxPlatformFontList::InitOtherFamilyNames(
+ bool aDeferOtherFamilyNamesLoading) {
+ if (mOtherFamilyNamesInitialized) {
+ return true;
+ }
+
+ if (SharedFontList() && !XRE_IsParentProcess()) {
+ if (NS_IsMainThread()) {
+ bool initialized;
+ dom::ContentChild::GetSingleton()->SendInitOtherFamilyNames(
+ SharedFontList()->GetGeneration(), aDeferOtherFamilyNamesLoading,
+ &initialized);
+ mOtherFamilyNamesInitialized.compareExchange(false, initialized);
+ } else {
+ NS_DispatchToMainThread(
+ new InitOtherFamilyNamesForStylo(aDeferOtherFamilyNamesLoading));
+ }
+ return mOtherFamilyNamesInitialized;
+ }
+
+ // If the font loader delay has been set to zero, we don't defer loading
+ // additional family names (regardless of the aDefer... parameter), as we
+ // take this to mean availability of font info is to be prioritized over
+ // potential startup perf or main-thread jank.
+ // (This is used so we can reliably run reftests that depend on localized
+ // font-family names being available.)
+ if (aDeferOtherFamilyNamesLoading &&
+ StaticPrefs::gfx_font_loader_delay_AtStartup() > 0) {
+ if (!mPendingOtherFamilyNameTask) {
+ RefPtr<mozilla::CancelableRunnable> task =
+ new InitOtherFamilyNamesRunnable();
+ mPendingOtherFamilyNameTask = task;
+ NS_DispatchToMainThreadQueue(task.forget(), EventQueuePriority::Idle);
+ }
+ } else {
+ InitOtherFamilyNamesInternal(false);
+ }
+ return mOtherFamilyNamesInitialized;
+}
+
+// time limit for loading facename lists (ms)
+#define NAMELIST_TIMEOUT 200
+
+gfxFontEntry* gfxPlatformFontList::SearchFamiliesForFaceName(
+ const nsACString& aFaceName) {
+ TimeStamp start = TimeStamp::Now();
+ bool timedOut = false;
+ // if mFirstChar is not 0, only load facenames for families
+ // that start with this character
+ char16_t firstChar = 0;
+ gfxFontEntry* lookup = nullptr;
+
+ // iterate over familes starting with the same letter
+ firstChar = ToLowerCase(aFaceName.CharAt(0));
+
+ for (const auto& entry : mFontFamilies) {
+ nsCStringHashKey::KeyType key = entry.GetKey();
+ const RefPtr<gfxFontFamily>& family = entry.GetData();
+
+ // when filtering, skip names that don't start with the filter character
+ if (firstChar && ToLowerCase(key.CharAt(0)) != firstChar) {
+ continue;
+ }
+
+ family->ReadFaceNames(this, NeedFullnamePostscriptNames());
+
+ TimeDuration elapsed = TimeStamp::Now() - start;
+ if (elapsed.ToMilliseconds() > NAMELIST_TIMEOUT) {
+ timedOut = true;
+ break;
+ }
+ }
+
+ lookup = FindFaceName(aFaceName);
+
+ TimeStamp end = TimeStamp::Now();
+ Telemetry::AccumulateTimeDelta(Telemetry::FONTLIST_INITFACENAMELISTS, start,
+ end);
+ if (LOG_FONTINIT_ENABLED()) {
+ TimeDuration elapsed = end - start;
+ LOG_FONTINIT(("(fontinit) SearchFamiliesForFaceName took %8.2f ms %s %s",
+ elapsed.ToMilliseconds(), (lookup ? "found name" : ""),
+ (timedOut ? "timeout" : "")));
+ }
+
+ return lookup;
+}
+
+gfxFontEntry* gfxPlatformFontList::FindFaceName(const nsACString& aFaceName) {
+ gfxFontEntry* lookup;
+
+ // lookup in name lookup tables, return null if not found
+ if (mExtraNames &&
+ ((lookup = mExtraNames->mPostscriptNames.GetWeak(aFaceName)) ||
+ (lookup = mExtraNames->mFullnames.GetWeak(aFaceName)))) {
+ return lookup;
+ }
+
+ return nullptr;
+}
+
+gfxFontEntry* gfxPlatformFontList::LookupInFaceNameLists(
+ const nsACString& aFaceName) {
+ gfxFontEntry* lookup = nullptr;
+
+ // initialize facename lookup tables if needed
+ // note: this can terminate early or time out, in which case
+ // mFaceNameListsInitialized remains false
+ if (!mFaceNameListsInitialized) {
+ lookup = SearchFamiliesForFaceName(aFaceName);
+ if (lookup) {
+ return lookup;
+ }
+ }
+
+ // lookup in name lookup tables, return null if not found
+ if (!(lookup = FindFaceName(aFaceName))) {
+ // names not completely initialized, so keep track of lookup misses
+ if (!mFaceNameListsInitialized) {
+ if (!mFaceNamesMissed) {
+ mFaceNamesMissed = MakeUnique<nsTHashSet<nsCString>>(2);
+ }
+ mFaceNamesMissed->Insert(aFaceName);
+ }
+ }
+
+ return lookup;
+}
+
+gfxFontEntry* gfxPlatformFontList::LookupInSharedFaceNameList(
+ nsPresContext* aPresContext, const nsACString& aFaceName,
+ WeightRange aWeightForEntry, StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry) {
+ nsAutoCString keyName(aFaceName);
+ ToLowerCase(keyName);
+ fontlist::FontList* list = SharedFontList();
+ fontlist::Family* family = nullptr;
+ fontlist::Face* face = nullptr;
+ if (list->NumLocalFaces()) {
+ fontlist::LocalFaceRec* rec = list->FindLocalFace(keyName);
+ if (rec) {
+ auto* families = list->Families();
+ if (families) {
+ family = &families[rec->mFamilyIndex];
+ face = family->Faces(list)[rec->mFaceIndex].ToPtr<fontlist::Face>(list);
+ }
+ }
+ } else {
+ list->SearchForLocalFace(keyName, &family, &face);
+ }
+ if (!face || !family) {
+ return nullptr;
+ }
+ FontVisibility level =
+ aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User;
+ if (!IsVisibleToCSS(*family, level)) {
+ if (aPresContext) {
+ aPresContext->ReportBlockedFontFamily(*family);
+ }
+ return nullptr;
+ }
+ gfxFontEntry* fe = CreateFontEntry(face, family);
+ if (fe) {
+ fe->mIsLocalUserFont = true;
+ fe->mWeightRange = aWeightForEntry;
+ fe->mStretchRange = aStretchForEntry;
+ fe->mStyleRange = aStyleForEntry;
+ }
+ return fe;
+}
+
+void gfxPlatformFontList::LoadBadUnderlineList() {
+ gfxFontUtils::GetPrefsFontList("font.blacklist.underline_offset",
+ mBadUnderlineFamilyNames);
+ for (auto& fam : mBadUnderlineFamilyNames) {
+ ToLowerCase(fam);
+ }
+ mBadUnderlineFamilyNames.Compact();
+ mBadUnderlineFamilyNames.Sort();
+}
+
+void gfxPlatformFontList::UpdateFontList(bool aFullRebuild) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (aFullRebuild) {
+ InitFontList();
+ AutoLock lock(mLock);
+ RebuildLocalFonts();
+ } else {
+ // The font list isn't being fully rebuilt, we're just being notified that
+ // character maps have been updated and so font fallback needs to be re-
+ // done. We only care about this if we have previously encountered a
+ // fallback that required cmaps that were not yet available, and so we
+ // asked for the async cmap loader to run.
+ AutoLock lock(mLock);
+ if (mStartedLoadingCmapsFrom != 0xffffffffu) {
+ InitializeCodepointsWithNoFonts();
+ mStartedLoadingCmapsFrom = 0xffffffffu;
+ ForceGlobalReflowLocked(gfxPlatform::NeedsReframe::No);
+ }
+ }
+}
+
+bool gfxPlatformFontList::IsVisibleToCSS(const gfxFontFamily& aFamily,
+ FontVisibility aVisibility) const {
+ return aFamily.Visibility() <= aVisibility || IsFontFamilyWhitelistActive();
+}
+
+bool gfxPlatformFontList::IsVisibleToCSS(const fontlist::Family& aFamily,
+ FontVisibility aVisibility) const {
+ return aFamily.Visibility() <= aVisibility || IsFontFamilyWhitelistActive();
+}
+
+void gfxPlatformFontList::GetFontList(nsAtom* aLangGroup,
+ const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts) {
+ AutoLock lock(mLock);
+
+ if (SharedFontList()) {
+ fontlist::FontList* list = SharedFontList();
+ const fontlist::Family* families = list->Families();
+ if (families) {
+ for (uint32_t i = 0; i < list->NumFamilies(); i++) {
+ auto& f = families[i];
+ if (!IsVisibleToCSS(f, FontVisibility::User) || f.IsAltLocaleFamily()) {
+ continue;
+ }
+ // XXX TODO: filter families for aGenericFamily, if supported by
+ // platform
+ aListOfFonts.AppendElement(
+ NS_ConvertUTF8toUTF16(list->LocalizedFamilyName(&f)));
+ }
+ }
+ return;
+ }
+
+ for (const RefPtr<gfxFontFamily>& family : mFontFamilies.Values()) {
+ if (!IsVisibleToCSS(*family, FontVisibility::User)) {
+ continue;
+ }
+ if (family->FilterForFontList(aLangGroup, aGenericFamily)) {
+ nsAutoCString localizedFamilyName;
+ family->LocalizedName(localizedFamilyName);
+ aListOfFonts.AppendElement(NS_ConvertUTF8toUTF16(localizedFamilyName));
+ }
+ }
+
+ aListOfFonts.Sort();
+ aListOfFonts.Compact();
+}
+
+void gfxPlatformFontList::GetFontFamilyList(
+ nsTArray<RefPtr<gfxFontFamily>>& aFamilyArray) {
+ AutoLock lock(mLock);
+ MOZ_ASSERT(aFamilyArray.IsEmpty());
+ // This doesn't use ToArray, because the caller passes an AutoTArray.
+ aFamilyArray.SetCapacity(mFontFamilies.Count());
+ for (const auto& family : mFontFamilies.Values()) {
+ aFamilyArray.AppendElement(family);
+ }
+}
+
+already_AddRefed<gfxFont> gfxPlatformFontList::SystemFindFontForChar(
+ nsPresContext* aPresContext, uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript, eFontPresentation aPresentation,
+ const gfxFontStyle* aStyle, FontVisibility* aVisibility) {
+ AutoLock lock(mLock);
+ FontVisibility level =
+ aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User;
+ MOZ_ASSERT(!mCodepointsWithNoFonts[level].test(aCh),
+ "don't call for codepoints already known to be unsupported");
+
+ // Try to short-circuit font fallback for U+FFFD, used to represent
+ // encoding errors: just use cached family from last time U+FFFD was seen.
+ // This helps speed up pages with lots of encoding errors, binary-as-text,
+ // etc.
+ if (aCh == 0xFFFD) {
+ gfxFontEntry* fontEntry = nullptr;
+ auto& fallbackFamily = mReplacementCharFallbackFamily[level];
+ if (fallbackFamily.mShared) {
+ fontlist::Face* face =
+ fallbackFamily.mShared->FindFaceForStyle(SharedFontList(), *aStyle);
+ if (face) {
+ fontEntry = GetOrCreateFontEntryLocked(face, fallbackFamily.mShared);
+ *aVisibility = fallbackFamily.mShared->Visibility();
+ }
+ } else if (fallbackFamily.mUnshared) {
+ fontEntry = fallbackFamily.mUnshared->FindFontForStyle(*aStyle);
+ *aVisibility = fallbackFamily.mUnshared->Visibility();
+ }
+
+ // this should never fail, as we must have found U+FFFD in order to set
+ // mReplacementCharFallbackFamily[...] at all, but better play it safe
+ if (fontEntry && fontEntry->HasCharacter(aCh)) {
+ return fontEntry->FindOrMakeFont(aStyle);
+ }
+ }
+
+ TimeStamp start = TimeStamp::Now();
+
+ // search commonly available fonts
+ bool common = true;
+ FontFamily fallbackFamily;
+ RefPtr<gfxFont> candidate =
+ CommonFontFallback(aPresContext, aCh, aNextCh, aRunScript, aPresentation,
+ aStyle, fallbackFamily);
+ RefPtr<gfxFont> font;
+ if (candidate) {
+ if (aPresentation == eFontPresentation::Any) {
+ font = std::move(candidate);
+ } else {
+ bool hasColorGlyph = candidate->HasColorGlyphFor(aCh, aNextCh);
+ if (hasColorGlyph == PrefersColor(aPresentation)) {
+ font = std::move(candidate);
+ }
+ }
+ }
+
+ // If we didn't find a common font, or it was not the preferred type (color
+ // or monochrome), do system-wide fallback (except for specials).
+ uint32_t cmapCount = 0;
+ if (!font) {
+ common = false;
+ font = GlobalFontFallback(aPresContext, aCh, aNextCh, aRunScript,
+ aPresentation, aStyle, cmapCount, fallbackFamily);
+ // If the font we found doesn't match the requested type, and we also found
+ // a candidate above, prefer that one.
+ if (font && aPresentation != eFontPresentation::Any && candidate) {
+ bool hasColorGlyph = font->HasColorGlyphFor(aCh, aNextCh);
+ if (hasColorGlyph != PrefersColor(aPresentation)) {
+ font = std::move(candidate);
+ }
+ }
+ }
+ TimeDuration elapsed = TimeStamp::Now() - start;
+
+ LogModule* log = gfxPlatform::GetLog(eGfxLog_textrun);
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Warning))) {
+ Script script = intl::UnicodeProperties::GetScriptCode(aCh);
+ MOZ_LOG(log, LogLevel::Warning,
+ ("(textrun-systemfallback-%s) char: u+%6.6x "
+ "script: %d match: [%s]"
+ " time: %dus cmaps: %d\n",
+ (common ? "common" : "global"), aCh, static_cast<int>(script),
+ (font ? font->GetFontEntry()->Name().get() : "<none>"),
+ int32_t(elapsed.ToMicroseconds()), cmapCount));
+ }
+
+ // no match? add to set of non-matching codepoints
+ if (!font) {
+ mCodepointsWithNoFonts[level].set(aCh);
+ } else {
+ *aVisibility = fallbackFamily.mShared
+ ? fallbackFamily.mShared->Visibility()
+ : fallbackFamily.mUnshared->Visibility();
+ if (aCh == 0xFFFD) {
+ mReplacementCharFallbackFamily[level] = fallbackFamily;
+ }
+ }
+
+ // track system fallback time
+ static bool first = true;
+ int32_t intElapsed =
+ int32_t(first ? elapsed.ToMilliseconds() : elapsed.ToMicroseconds());
+ Telemetry::Accumulate((first ? Telemetry::SYSTEM_FONT_FALLBACK_FIRST
+ : Telemetry::SYSTEM_FONT_FALLBACK),
+ intElapsed);
+ first = false;
+
+ // track the script for which fallback occurred (incremented one make it
+ // 1-based)
+ Telemetry::Accumulate(Telemetry::SYSTEM_FONT_FALLBACK_SCRIPT,
+ int(aRunScript) + 1);
+
+ return font.forget();
+}
+
+#define NUM_FALLBACK_FONTS 8
+
+already_AddRefed<gfxFont> gfxPlatformFontList::CommonFontFallback(
+ nsPresContext* aPresContext, uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript, eFontPresentation aPresentation,
+ const gfxFontStyle* aMatchStyle, FontFamily& aMatchedFamily) {
+ AutoTArray<const char*, NUM_FALLBACK_FONTS> defaultFallbacks;
+ gfxPlatform::GetPlatform()->GetCommonFallbackFonts(
+ aCh, aRunScript, aPresentation, defaultFallbacks);
+ GlobalFontMatch data(aCh, aNextCh, *aMatchStyle, aPresentation);
+ FontVisibility level =
+ aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User;
+
+ // If a color-emoji presentation is requested, we will check any font found
+ // to see if it can provide this; if not, we'll remember it as a possible
+ // candidate but search the remainder of the list for a better choice.
+ RefPtr<gfxFont> candidateFont;
+ FontFamily candidateFamily;
+ auto check = [&](gfxFontEntry* aFontEntry,
+ FontFamily aFamily) -> already_AddRefed<gfxFont> {
+ RefPtr<gfxFont> font = aFontEntry->FindOrMakeFont(aMatchStyle);
+ if (aPresentation < eFontPresentation::EmojiDefault ||
+ font->HasColorGlyphFor(aCh, aNextCh)) {
+ aMatchedFamily = aFamily;
+ return font.forget();
+ }
+ // We want a color glyph but this font only has monochrome; remember it
+ // (unless we already have a candidate) but continue to search.
+ if (!candidateFont) {
+ candidateFont = std::move(font);
+ candidateFamily = aFamily;
+ }
+ return nullptr;
+ };
+
+ if (SharedFontList()) {
+ for (const auto name : defaultFallbacks) {
+ fontlist::Family* family =
+ FindSharedFamily(aPresContext, nsDependentCString(name));
+ if (!family || !IsVisibleToCSS(*family, level)) {
+ continue;
+ }
+ // XXX(jfkthame) Should we fire the async cmap-loader here, or let it
+ // always do a potential sync initialization of the family?
+ family->SearchAllFontsForChar(SharedFontList(), &data);
+ if (data.mBestMatch) {
+ RefPtr<gfxFont> font = check(data.mBestMatch, FontFamily(family));
+ if (font) {
+ return font.forget();
+ }
+ }
+ }
+ } else {
+ for (const auto name : defaultFallbacks) {
+ gfxFontFamily* fallback =
+ FindFamilyByCanonicalName(nsDependentCString(name));
+ if (!fallback || !IsVisibleToCSS(*fallback, level)) {
+ continue;
+ }
+ fallback->FindFontForChar(&data);
+ if (data.mBestMatch) {
+ RefPtr<gfxFont> font = check(data.mBestMatch, FontFamily(fallback));
+ if (font) {
+ return font.forget();
+ }
+ }
+ }
+ }
+
+ // If we had a candidate that supports the character, but doesn't have the
+ // desired emoji-style glyph, we'll return it anyhow as nothing better was
+ // found.
+ if (candidateFont) {
+ aMatchedFamily = candidateFamily;
+ return candidateFont.forget();
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<gfxFont> gfxPlatformFontList::GlobalFontFallback(
+ nsPresContext* aPresContext, uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript, eFontPresentation aPresentation,
+ const gfxFontStyle* aMatchStyle, uint32_t& aCmapCount,
+ FontFamily& aMatchedFamily) {
+ bool useCmaps = IsFontFamilyWhitelistActive() ||
+ gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback();
+ FontVisibility level =
+ aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User;
+ if (!useCmaps) {
+ // Allow platform-specific fallback code to try and find a usable font
+ gfxFontEntry* fe = PlatformGlobalFontFallback(aPresContext, aCh, aRunScript,
+ aMatchStyle, aMatchedFamily);
+ if (fe) {
+ if (aMatchedFamily.mShared) {
+ if (IsVisibleToCSS(*aMatchedFamily.mShared, level)) {
+ RefPtr<gfxFont> font = fe->FindOrMakeFont(aMatchStyle);
+ if (font) {
+ if (aPresentation == eFontPresentation::Any) {
+ return font.forget();
+ }
+ bool hasColorGlyph = font->HasColorGlyphFor(aCh, aNextCh);
+ if (hasColorGlyph == PrefersColor(aPresentation)) {
+ return font.forget();
+ }
+ }
+ }
+ } else {
+ if (IsVisibleToCSS(*aMatchedFamily.mUnshared, level)) {
+ RefPtr<gfxFont> font = fe->FindOrMakeFont(aMatchStyle);
+ if (font) {
+ if (aPresentation == eFontPresentation::Any) {
+ return font.forget();
+ }
+ bool hasColorGlyph = font->HasColorGlyphFor(aCh, aNextCh);
+ if (hasColorGlyph == PrefersColor(aPresentation)) {
+ return font.forget();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // otherwise, try to find it among local fonts
+ GlobalFontMatch data(aCh, aNextCh, *aMatchStyle, aPresentation);
+ if (SharedFontList()) {
+ fontlist::Family* families = SharedFontList()->Families();
+ if (families) {
+ for (uint32_t i = 0; i < SharedFontList()->NumFamilies(); i++) {
+ fontlist::Family& family = families[i];
+ if (!IsVisibleToCSS(family, level)) {
+ continue;
+ }
+ if (!family.IsFullyInitialized() &&
+ StaticPrefs::gfx_font_rendering_fallback_async() &&
+ !XRE_IsParentProcess()) {
+ // Start loading all the missing charmaps; but this is async,
+ // so for now we just continue, ignoring this family.
+ StartCmapLoadingFromFamily(i);
+ } else {
+ family.SearchAllFontsForChar(SharedFontList(), &data);
+ if (data.mMatchDistance == 0.0) {
+ // no better style match is possible, so stop searching
+ break;
+ }
+ }
+ }
+ if (data.mBestMatch) {
+ aMatchedFamily = FontFamily(data.mMatchedSharedFamily);
+ return data.mBestMatch->FindOrMakeFont(aMatchStyle);
+ }
+ }
+ } else {
+ // iterate over all font families to find a font that support the
+ // character
+ for (const RefPtr<gfxFontFamily>& family : mFontFamilies.Values()) {
+ if (!IsVisibleToCSS(*family, level)) {
+ continue;
+ }
+ // evaluate all fonts in this family for a match
+ family->FindFontForChar(&data);
+ if (data.mMatchDistance == 0.0) {
+ // no better style match is possible, so stop searching
+ break;
+ }
+ }
+
+ aCmapCount = data.mCmapsTested;
+ if (data.mBestMatch) {
+ aMatchedFamily = FontFamily(data.mMatchedFamily);
+ return data.mBestMatch->FindOrMakeFont(aMatchStyle);
+ }
+ }
+
+ return nullptr;
+}
+
+class StartCmapLoadingRunnable : public mozilla::Runnable {
+ public:
+ explicit StartCmapLoadingRunnable(uint32_t aStartIndex)
+ : Runnable("gfxPlatformFontList::StartCmapLoadingRunnable"),
+ mStartIndex(aStartIndex) {}
+
+ NS_IMETHOD Run() override {
+ auto* pfl = gfxPlatformFontList::PlatformFontList();
+ auto* list = pfl->SharedFontList();
+ if (!list) {
+ return NS_OK;
+ }
+ if (mStartIndex >= list->NumFamilies()) {
+ return NS_OK;
+ }
+ if (XRE_IsParentProcess()) {
+ pfl->StartCmapLoading(list->GetGeneration(), mStartIndex);
+ } else {
+ dom::ContentChild::GetSingleton()->SendStartCmapLoading(
+ list->GetGeneration(), mStartIndex);
+ }
+ return NS_OK;
+ }
+
+ private:
+ uint32_t mStartIndex;
+};
+
+void gfxPlatformFontList::StartCmapLoadingFromFamily(uint32_t aStartIndex) {
+ AutoLock lock(mLock);
+ if (aStartIndex > mStartedLoadingCmapsFrom) {
+ // We already initiated cmap-loading from somewhere earlier in the list;
+ // no need to do it again here.
+ return;
+ }
+ mStartedLoadingCmapsFrom = aStartIndex;
+
+ // If we're already on the main thread, don't bother dispatching a runnable
+ // here to kick off the loading process, just do it directly.
+ if (NS_IsMainThread()) {
+ auto* list = SharedFontList();
+ if (XRE_IsParentProcess()) {
+ StartCmapLoading(list->GetGeneration(), aStartIndex);
+ } else {
+ dom::ContentChild::GetSingleton()->SendStartCmapLoading(
+ list->GetGeneration(), aStartIndex);
+ }
+ } else {
+ NS_DispatchToMainThread(new StartCmapLoadingRunnable(aStartIndex));
+ }
+}
+
+class LoadCmapsRunnable : public CancelableRunnable {
+ class WillShutdownObserver : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ explicit WillShutdownObserver(LoadCmapsRunnable* aRunnable)
+ : mRunnable(aRunnable) {}
+
+ void Remove() {
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
+ }
+ mRunnable = nullptr;
+ }
+
+ protected:
+ virtual ~WillShutdownObserver() = default;
+
+ LoadCmapsRunnable* mRunnable;
+ };
+
+ public:
+ explicit LoadCmapsRunnable(uint32_t aGeneration, uint32_t aFamilyIndex)
+ : CancelableRunnable("gfxPlatformFontList::LoadCmapsRunnable"),
+ mGeneration(aGeneration),
+ mStartIndex(aFamilyIndex),
+ mIndex(aFamilyIndex) {
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ mObserver = new WillShutdownObserver(this);
+ obs->AddObserver(mObserver, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false);
+ }
+ }
+
+ virtual ~LoadCmapsRunnable() {
+ if (mObserver) {
+ mObserver->Remove();
+ }
+ }
+
+ // Reset the current family index, if the value passed is earlier than our
+ // original starting position. We don't "reset" if it would move the current
+ // position forward, or back into the already-scanned range.
+ // We could optimize further by remembering the current position reached,
+ // and then skipping ahead from the original start, but it doesn't seem worth
+ // extra complexity for a task that usually only happens once, and already-
+ // processed families will be skipped pretty quickly in Run() anyhow.
+ void MaybeResetIndex(uint32_t aFamilyIndex) {
+ if (aFamilyIndex < mStartIndex) {
+ mStartIndex = aFamilyIndex;
+ mIndex = aFamilyIndex;
+ }
+ }
+
+ nsresult Cancel() override {
+ mIsCanceled = true;
+ return NS_OK;
+ }
+
+ NS_IMETHOD Run() override {
+ if (mIsCanceled) {
+ return NS_OK;
+ }
+ auto* pfl = gfxPlatformFontList::PlatformFontList();
+ auto* list = pfl->SharedFontList();
+ MOZ_ASSERT(list);
+ if (!list) {
+ return NS_OK;
+ }
+ if (mGeneration != list->GetGeneration()) {
+ return NS_OK;
+ }
+ uint32_t numFamilies = list->NumFamilies();
+ if (mIndex >= numFamilies) {
+ return NS_OK;
+ }
+ auto* families = list->Families();
+ // Skip any families that are already initialized.
+ while (mIndex < numFamilies && families[mIndex].IsFullyInitialized()) {
+ ++mIndex;
+ }
+ // Fully process one family, and advance index.
+ if (mIndex < numFamilies) {
+ Unused << pfl->InitializeFamily(&families[mIndex], true);
+ ++mIndex;
+ }
+ // If there are more families to initialize, post ourselves back to the
+ // idle queue to handle the next one; otherwise we're finished and we need
+ // to notify content processes to update their rendering.
+ if (mIndex < numFamilies) {
+ RefPtr<CancelableRunnable> task = this;
+ NS_DispatchToMainThreadQueue(task.forget(), EventQueuePriority::Idle);
+ } else {
+ pfl->Lock();
+ pfl->CancelLoadCmapsTask();
+ pfl->InitializeCodepointsWithNoFonts();
+ dom::ContentParent::NotifyUpdatedFonts(false);
+ pfl->Unlock();
+ }
+ return NS_OK;
+ }
+
+ private:
+ uint32_t mGeneration;
+ uint32_t mStartIndex;
+ uint32_t mIndex;
+ bool mIsCanceled = false;
+
+ RefPtr<WillShutdownObserver> mObserver;
+};
+
+NS_IMPL_ISUPPORTS(LoadCmapsRunnable::WillShutdownObserver, nsIObserver)
+
+NS_IMETHODIMP
+LoadCmapsRunnable::WillShutdownObserver::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ if (!nsCRT::strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID)) {
+ if (mRunnable) {
+ mRunnable->Cancel();
+ }
+ } else {
+ MOZ_ASSERT_UNREACHABLE("unexpected notification topic");
+ }
+ return NS_OK;
+}
+
+void gfxPlatformFontList::StartCmapLoading(uint32_t aGeneration,
+ uint32_t aStartIndex) {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+ if (aGeneration != SharedFontList()->GetGeneration()) {
+ return;
+ }
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ return;
+ }
+ if (mLoadCmapsRunnable) {
+ // We already have a runnable; just make sure it covers the full range of
+ // families needed.
+ static_cast<LoadCmapsRunnable*>(mLoadCmapsRunnable.get())
+ ->MaybeResetIndex(aStartIndex);
+ return;
+ }
+ mLoadCmapsRunnable = new LoadCmapsRunnable(aGeneration, aStartIndex);
+ RefPtr<CancelableRunnable> task = mLoadCmapsRunnable;
+ NS_DispatchToMainThreadQueue(task.forget(), EventQueuePriority::Idle);
+}
+
+gfxFontFamily* gfxPlatformFontList::CheckFamily(gfxFontFamily* aFamily) {
+ if (aFamily && !aFamily->HasStyles()) {
+ aFamily->FindStyleVariations();
+ }
+
+ if (aFamily && aFamily->FontListLength() == 0) {
+ // Failed to load any faces for this family, so discard it.
+ nsAutoCString key;
+ GenerateFontListKey(aFamily->Name(), key);
+ mFontFamilies.Remove(key);
+ return nullptr;
+ }
+
+ return aFamily;
+}
+
+bool gfxPlatformFontList::FindAndAddFamiliesLocked(
+ nsPresContext* aPresContext, StyleGenericFontFamily aGeneric,
+ const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput,
+ FindFamiliesFlags aFlags, gfxFontStyle* aStyle, nsAtom* aLanguage,
+ gfxFloat aDevToCssSize) {
+ nsAutoCString key;
+ GenerateFontListKey(aFamily, key);
+
+ bool allowHidden = bool(aFlags & FindFamiliesFlags::eSearchHiddenFamilies);
+ FontVisibility visibilityLevel =
+ aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User;
+
+ if (SharedFontList()) {
+ fontlist::Family* family = SharedFontList()->FindFamily(key);
+ // If not found, and other family names have not yet been initialized,
+ // initialize the rest of the list and try again. This is done lazily
+ // since reading name table entries is expensive.
+ // Although ASCII localized family names are possible they don't occur
+ // in practice, so avoid pulling in names at startup.
+ if (!family && !mOtherFamilyNamesInitialized) {
+ bool triggerLoading = true;
+ bool mayDefer =
+ !(aFlags & FindFamiliesFlags::eForceOtherFamilyNamesLoading);
+ if (IsAscii(key)) {
+ // If `key` is an ASCII name, only trigger loading if it includes a
+ // space, and the "base" name (up to the last space) exists as a known
+ // family, so that this might be a legacy styled-family name.
+ const char* data = key.BeginReading();
+ int32_t index = key.Length();
+ while (--index > 0) {
+ if (data[index] == ' ') {
+ break;
+ }
+ }
+ if (index <= 0 ||
+ !SharedFontList()->FindFamily(nsAutoCString(key.get(), index))) {
+ triggerLoading = false;
+ }
+ }
+ if (triggerLoading) {
+ if (InitOtherFamilyNames(mayDefer)) {
+ family = SharedFontList()->FindFamily(key);
+ }
+ }
+ if (!family && !mOtherFamilyNamesInitialized &&
+ !(aFlags & FindFamiliesFlags::eNoAddToNamesMissedWhenSearching)) {
+ AddToMissedNames(key);
+ }
+ }
+ // Check whether the family we found is actually allowed to be looked up,
+ // according to current font-visibility prefs.
+ if (family) {
+ bool visible = IsVisibleToCSS(*family, visibilityLevel);
+ if (visible || (allowHidden && family->IsHidden())) {
+ aOutput->AppendElement(FamilyAndGeneric(family, aGeneric));
+ return true;
+ }
+ if (aPresContext) {
+ aPresContext->ReportBlockedFontFamily(*family);
+ }
+ }
+ return false;
+ }
+
+ NS_ASSERTION(mFontFamilies.Count() != 0,
+ "system font list was not initialized correctly");
+
+ auto isBlockedByVisibilityLevel = [=](gfxFontFamily* aFamily) -> bool {
+ bool visible = IsVisibleToCSS(*aFamily, visibilityLevel);
+ if (visible || (allowHidden && aFamily->IsHidden())) {
+ return false;
+ }
+ if (aPresContext) {
+ aPresContext->ReportBlockedFontFamily(*aFamily);
+ }
+ return true;
+ };
+
+ // lookup in canonical (i.e. English) family name list
+ gfxFontFamily* familyEntry = mFontFamilies.GetWeak(key);
+ if (familyEntry) {
+ if (isBlockedByVisibilityLevel(familyEntry)) {
+ return false;
+ }
+ }
+
+ // if not found, lookup in other family names list (mostly localized names)
+ if (!familyEntry) {
+ familyEntry = mOtherFamilyNames.GetWeak(key);
+ }
+ if (familyEntry) {
+ if (isBlockedByVisibilityLevel(familyEntry)) {
+ return false;
+ }
+ }
+
+ // if still not found and other family names not yet fully initialized,
+ // initialize the rest of the list and try again. this is done lazily
+ // since reading name table entries is expensive.
+ // although ASCII localized family names are possible they don't occur
+ // in practice so avoid pulling in names at startup
+ if (!familyEntry && !mOtherFamilyNamesInitialized && !IsAscii(aFamily)) {
+ InitOtherFamilyNames(
+ !(aFlags & FindFamiliesFlags::eForceOtherFamilyNamesLoading));
+ familyEntry = mOtherFamilyNames.GetWeak(key);
+ if (!familyEntry && !mOtherFamilyNamesInitialized &&
+ !(aFlags & FindFamiliesFlags::eNoAddToNamesMissedWhenSearching)) {
+ // localized family names load timed out, add name to list of
+ // names to check after localized names are loaded
+ AddToMissedNames(key);
+ }
+ if (familyEntry) {
+ if (isBlockedByVisibilityLevel(familyEntry)) {
+ return false;
+ }
+ }
+ }
+
+ familyEntry = CheckFamily(familyEntry);
+
+ // If we failed to find the requested family, check for a space in the
+ // name; if found, and if the "base" name (up to the last space) exists
+ // as a family, then this might be a legacy GDI-style family name for
+ // an additional weight/width. Try searching the faces of the base family
+ // and create any corresponding legacy families.
+ if (!familyEntry &&
+ !(aFlags & FindFamiliesFlags::eNoSearchForLegacyFamilyNames)) {
+ // We don't have nsAString::RFindChar, so look for a space manually
+ const char* data = aFamily.BeginReading();
+ int32_t index = aFamily.Length();
+ while (--index > 0) {
+ if (data[index] == ' ') {
+ break;
+ }
+ }
+ if (index > 0) {
+ gfxFontFamily* base =
+ FindUnsharedFamily(aPresContext, Substring(aFamily, 0, index),
+ FindFamiliesFlags::eNoSearchForLegacyFamilyNames);
+ // If we found the "base" family name, and if it has members with
+ // legacy names, this will add corresponding font-family entries to
+ // the mOtherFamilyNames list; then retry the legacy-family search.
+ if (base && base->CheckForLegacyFamilyNames(this)) {
+ familyEntry = mOtherFamilyNames.GetWeak(key);
+ }
+ if (familyEntry) {
+ if (isBlockedByVisibilityLevel(familyEntry)) {
+ return false;
+ }
+ }
+ }
+ }
+
+ if (familyEntry) {
+ aOutput->AppendElement(FamilyAndGeneric(familyEntry, aGeneric));
+ return true;
+ }
+
+ return false;
+}
+
+void gfxPlatformFontList::AddToMissedNames(const nsCString& aKey) {
+ if (!mOtherNamesMissed) {
+ mOtherNamesMissed = MakeUnique<nsTHashSet<nsCString>>(2);
+ }
+ mOtherNamesMissed->Insert(aKey);
+}
+
+fontlist::Family* gfxPlatformFontList::FindSharedFamily(
+ nsPresContext* aPresContext, const nsACString& aFamily,
+ FindFamiliesFlags aFlags, gfxFontStyle* aStyle, nsAtom* aLanguage,
+ gfxFloat aDevToCss) {
+ if (!SharedFontList()) {
+ return nullptr;
+ }
+ AutoTArray<FamilyAndGeneric, 1> families;
+ if (!FindAndAddFamiliesLocked(aPresContext, StyleGenericFontFamily::None,
+ aFamily, &families, aFlags, aStyle, aLanguage,
+ aDevToCss) ||
+ !families[0].mFamily.mShared) {
+ return nullptr;
+ }
+ fontlist::Family* family = families[0].mFamily.mShared;
+ if (!family->IsInitialized()) {
+ if (!InitializeFamily(family)) {
+ return nullptr;
+ }
+ }
+ return family;
+}
+
+class InitializeFamilyRunnable : public mozilla::Runnable {
+ public:
+ explicit InitializeFamilyRunnable(uint32_t aFamilyIndex, bool aLoadCmaps)
+ : Runnable("gfxPlatformFontList::InitializeFamilyRunnable"),
+ mIndex(aFamilyIndex),
+ mLoadCmaps(aLoadCmaps) {}
+
+ NS_IMETHOD Run() override {
+ auto list = gfxPlatformFontList::PlatformFontList()->SharedFontList();
+ if (!list) {
+ return NS_OK;
+ }
+ if (mIndex >= list->NumFamilies()) {
+ // Out of range? Maybe the list got reinitialized since this request
+ // was posted - just ignore it.
+ return NS_OK;
+ }
+ dom::ContentChild::GetSingleton()->SendInitializeFamily(
+ list->GetGeneration(), mIndex, mLoadCmaps);
+ return NS_OK;
+ }
+
+ private:
+ uint32_t mIndex;
+ bool mLoadCmaps;
+};
+
+bool gfxPlatformFontList::InitializeFamily(fontlist::Family* aFamily,
+ bool aLoadCmaps) {
+ MOZ_ASSERT(SharedFontList());
+ auto list = SharedFontList();
+ if (!XRE_IsParentProcess()) {
+ auto* families = list->Families();
+ if (!families) {
+ return false;
+ }
+ uint32_t index = aFamily - families;
+ if (index >= list->NumFamilies()) {
+ return false;
+ }
+ if (NS_IsMainThread()) {
+ dom::ContentChild::GetSingleton()->SendInitializeFamily(
+ list->GetGeneration(), index, aLoadCmaps);
+ } else {
+ NS_DispatchToMainThread(new InitializeFamilyRunnable(index, aLoadCmaps));
+ }
+ return aFamily->IsInitialized();
+ }
+
+ if (!aFamily->IsInitialized()) {
+ // The usual case: we're being asked to populate the face list.
+ AutoTArray<fontlist::Face::InitData, 16> faceList;
+ GetFacesInitDataForFamily(aFamily, faceList, aLoadCmaps);
+ aFamily->AddFaces(list, faceList);
+ } else {
+ // The family's face list was already initialized, but if aLoadCmaps is
+ // true we also want to eagerly load character maps. This is used when a
+ // child process is doing SearchAllFontsForChar, to have the parent load
+ // all the cmaps at once and reduce IPC traffic (and content-process file
+ // access overhead, which is crippling for DirectWrite on Windows).
+ if (aLoadCmaps) {
+ auto* faces = aFamily->Faces(list);
+ if (faces) {
+ for (size_t i = 0; i < aFamily->NumFaces(); i++) {
+ auto* face = faces[i].ToPtr<fontlist::Face>(list);
+ if (face && face->mCharacterMap.IsNull()) {
+ // We don't want to cache this font entry, as the parent will most
+ // likely never use it again; it's just to populate the charmap for
+ // the benefit of the child process.
+ RefPtr<gfxFontEntry> fe = CreateFontEntry(face, aFamily);
+ if (fe) {
+ fe->ReadCMAP();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (aLoadCmaps && aFamily->IsInitialized()) {
+ aFamily->SetupFamilyCharMap(list);
+ }
+
+ return aFamily->IsInitialized();
+}
+
+gfxFontEntry* gfxPlatformFontList::FindFontForFamily(
+ nsPresContext* aPresContext, const nsACString& aFamily,
+ const gfxFontStyle* aStyle) {
+ AutoLock lock(mLock);
+
+ nsAutoCString key;
+ GenerateFontListKey(aFamily, key);
+
+ FontFamily family = FindFamily(aPresContext, key);
+ if (family.IsNull()) {
+ return nullptr;
+ }
+ if (family.mShared) {
+ auto face = family.mShared->FindFaceForStyle(SharedFontList(), *aStyle);
+ if (!face) {
+ return nullptr;
+ }
+ return GetOrCreateFontEntryLocked(face, family.mShared);
+ }
+ return family.mUnshared->FindFontForStyle(*aStyle);
+}
+
+gfxFontEntry* gfxPlatformFontList::GetOrCreateFontEntryLocked(
+ fontlist::Face* aFace, const fontlist::Family* aFamily) {
+ return mFontEntries
+ .LookupOrInsertWith(aFace,
+ [=] { return CreateFontEntry(aFace, aFamily); })
+ .get();
+}
+
+void gfxPlatformFontList::AddOtherFamilyNames(
+ gfxFontFamily* aFamilyEntry, const nsTArray<nsCString>& aOtherFamilyNames) {
+ AutoLock lock(mLock);
+
+ for (const auto& name : aOtherFamilyNames) {
+ nsAutoCString key;
+ GenerateFontListKey(name, key);
+
+ mOtherFamilyNames.LookupOrInsertWith(key, [&] {
+ LOG_FONTLIST(
+ ("(fontlist-otherfamily) canonical family: %s, other family: %s\n",
+ aFamilyEntry->Name().get(), name.get()));
+ if (mBadUnderlineFamilyNames.ContainsSorted(key)) {
+ aFamilyEntry->SetBadUnderlineFamily();
+ }
+ return RefPtr{aFamilyEntry};
+ });
+ }
+}
+
+void gfxPlatformFontList::AddFullnameLocked(gfxFontEntry* aFontEntry,
+ const nsCString& aFullname) {
+ mExtraNames->mFullnames.LookupOrInsertWith(aFullname, [&] {
+ LOG_FONTLIST(("(fontlist-fullname) name: %s, fullname: %s\n",
+ aFontEntry->Name().get(), aFullname.get()));
+ return RefPtr{aFontEntry};
+ });
+}
+
+void gfxPlatformFontList::AddPostscriptNameLocked(
+ gfxFontEntry* aFontEntry, const nsCString& aPostscriptName) {
+ mExtraNames->mPostscriptNames.LookupOrInsertWith(aPostscriptName, [&] {
+ LOG_FONTLIST(("(fontlist-postscript) name: %s, psname: %s\n",
+ aFontEntry->Name().get(), aPostscriptName.get()));
+ return RefPtr{aFontEntry};
+ });
+}
+
+bool gfxPlatformFontList::GetStandardFamilyName(const nsCString& aFontName,
+ nsACString& aFamilyName) {
+ AutoLock lock(mLock);
+ FontFamily family = FindFamily(nullptr, aFontName);
+ if (family.IsNull()) {
+ return false;
+ }
+ return GetLocalizedFamilyName(family, aFamilyName);
+}
+
+bool gfxPlatformFontList::GetLocalizedFamilyName(const FontFamily& aFamily,
+ nsACString& aFamilyName) {
+ if (aFamily.mShared) {
+ aFamilyName = SharedFontList()->LocalizedFamilyName(aFamily.mShared);
+ return true;
+ }
+ if (aFamily.mUnshared) {
+ aFamily.mUnshared->LocalizedName(aFamilyName);
+ return true;
+ }
+ return false; // leaving the aFamilyName outparam untouched
+}
+
+FamilyAndGeneric gfxPlatformFontList::GetDefaultFontFamily(
+ const nsACString& aLangGroup, const nsACString& aGenericFamily) {
+ if (NS_WARN_IF(aLangGroup.IsEmpty()) ||
+ NS_WARN_IF(aGenericFamily.IsEmpty())) {
+ return FamilyAndGeneric();
+ }
+
+ AutoLock lock(mLock);
+
+ nsAutoCString value;
+ AutoTArray<nsCString, 4> names;
+ if (mFontPrefs->LookupNameList(PrefName(aGenericFamily, aLangGroup), value)) {
+ gfxFontUtils::ParseFontList(value, names);
+ }
+
+ for (const nsCString& name : names) {
+ FontFamily family = FindFamily(nullptr, name);
+ if (!family.IsNull()) {
+ return FamilyAndGeneric(family);
+ }
+ }
+
+ return FamilyAndGeneric();
+}
+
+ShmemCharMapHashEntry::ShmemCharMapHashEntry(const gfxSparseBitSet* aCharMap)
+ : mList(gfxPlatformFontList::PlatformFontList()->SharedFontList()),
+ mCharMap(),
+ mHash(aCharMap->GetChecksum()) {
+ size_t len = SharedBitSet::RequiredSize(*aCharMap);
+ mCharMap = mList->Alloc(len);
+ SharedBitSet::Create(mCharMap.ToPtr(mList, len), len, *aCharMap);
+}
+
+fontlist::Pointer gfxPlatformFontList::GetShmemCharMapLocked(
+ const gfxSparseBitSet* aCmap) {
+ auto* entry = mShmemCharMaps.GetEntry(aCmap);
+ if (!entry) {
+ entry = mShmemCharMaps.PutEntry(aCmap);
+ }
+ return entry->GetCharMap();
+}
+
+// Lookup aCmap in the shared cmap set, adding if not already present.
+// This is the only way for a reference to a gfxCharacterMap to be acquired
+// by another thread than its original creator.
+already_AddRefed<gfxCharacterMap> gfxPlatformFontList::FindCharMap(
+ gfxCharacterMap* aCmap) {
+ // Lock to prevent potentially racing against MaybeRemoveCmap.
+ AutoLock lock(mLock);
+
+ // Find existing entry or insert a new one (which will add a reference).
+ aCmap->CalcHash();
+ aCmap->mShared = true; // Set the shared flag in preparation for adding
+ // to the global table.
+ RefPtr cmap = mSharedCmaps.PutEntry(aCmap)->GetKey();
+
+ // If we ended up finding a different, pre-existing entry, clear the
+ // shared flag on this one so that it'll get deleted on Release().
+ if (cmap.get() != aCmap) {
+ aCmap->mShared = false;
+ }
+
+ return cmap.forget();
+}
+
+// Potentially remove the charmap from the shared cmap set. This is called
+// when a user of the charmap drops a reference and the refcount goes to 1;
+// in that case, it is possible our shared set is the only remaining user
+// of the object, and we should remove it.
+// Note that aCharMap might have already been freed, so we must not try to
+// dereference it until we have checked that it's still present in our table.
+void gfxPlatformFontList::MaybeRemoveCmap(gfxCharacterMap* aCharMap) {
+ // Lock so that nobody else can get a reference via FindCharMap while we're
+ // checking here.
+ AutoLock lock(mLock);
+
+ // Skip lookups during teardown.
+ if (!mSharedCmaps.Count()) {
+ return;
+ }
+
+ // aCharMap needs to match the entry and be the same ptr and still have a
+ // refcount of exactly 1 (i.e. we hold the only reference) before removing.
+ // If we're racing another thread, it might already have been removed, in
+ // which case GetEntry will not find it and we won't try to dereference the
+ // already-freed pointer.
+ CharMapHashKey* found =
+ mSharedCmaps.GetEntry(const_cast<gfxCharacterMap*>(aCharMap));
+ if (found && found->GetKey() == aCharMap && aCharMap->RefCount() == 1) {
+ // Forget our reference to the object that's being deleted, without
+ // calling Release() on it.
+ Unused << found->mCharMap.forget();
+
+ // Do the deletion.
+ delete aCharMap;
+
+ // Log this as a "Release" to keep leak-checking correct.
+ NS_LOG_RELEASE(aCharMap, 0, "gfxCharacterMap");
+
+ mSharedCmaps.RemoveEntry(found);
+ }
+}
+
+static void GetSystemUIFontFamilies([[maybe_unused]] nsAtom* aLangGroup,
+ nsTArray<nsCString>& aFamilies) {
+ // TODO: On macOS, use CTCreateUIFontForLanguage or such thing (though the
+ // code below ends up using [NSFont systemFontOfSize: 0.0].
+ nsFont systemFont;
+ gfxFontStyle fontStyle;
+ nsAutoString systemFontName;
+ if (!LookAndFeel::GetFont(StyleSystemFont::Menu, systemFontName, fontStyle)) {
+ return;
+ }
+ systemFontName.Trim("\"'");
+ CopyUTF16toUTF8(systemFontName, *aFamilies.AppendElement());
+}
+
+void gfxPlatformFontList::ResolveGenericFontNames(
+ nsPresContext* aPresContext, StyleGenericFontFamily aGenericType,
+ eFontPrefLang aPrefLang, PrefFontList* aGenericFamilies) {
+ const char* langGroupStr = GetPrefLangName(aPrefLang);
+ const char* generic = GetGenericName(aGenericType);
+
+ if (!generic) {
+ return;
+ }
+
+ AutoTArray<nsCString, 4> genericFamilies;
+
+ // load family for "font.name.generic.lang"
+ PrefName prefName(generic, langGroupStr);
+ nsAutoCString value;
+ if (mFontPrefs->LookupName(prefName, value)) {
+ gfxFontUtils::ParseFontList(value, genericFamilies);
+ }
+
+ // load fonts for "font.name-list.generic.lang"
+ if (mFontPrefs->LookupNameList(prefName, value)) {
+ gfxFontUtils::ParseFontList(value, genericFamilies);
+ }
+
+ nsAtom* langGroup = GetLangGroupForPrefLang(aPrefLang);
+ MOZ_ASSERT(langGroup, "null lang group for pref lang");
+
+ if (aGenericType == StyleGenericFontFamily::SystemUi) {
+ GetSystemUIFontFamilies(langGroup, genericFamilies);
+ }
+
+ GetFontFamiliesFromGenericFamilies(
+ aPresContext, aGenericType, genericFamilies, langGroup, aGenericFamilies);
+
+#if 0 // dump out generic mappings
+ printf("%s ===> ", NamePref(generic, langGroupStr).get());
+ for (uint32_t k = 0; k < aGenericFamilies->Length(); k++) {
+ if (k > 0) printf(", ");
+ printf("%s", (*aGenericFamilies)[k].mIsShared
+ ? (*aGenericFamilies)[k].mShared->DisplayName().AsString(SharedFontList()).get()
+ : (*aGenericFamilies)[k].mUnshared->Name().get());
+ }
+ printf("\n");
+#endif
+}
+
+void gfxPlatformFontList::ResolveEmojiFontNames(
+ nsPresContext* aPresContext, PrefFontList* aGenericFamilies) {
+ // emoji preference has no lang name
+ AutoTArray<nsCString, 4> genericFamilies;
+
+ nsAutoCString value;
+ if (mFontPrefs->LookupNameList(PrefName("emoji", ""), value)) {
+ gfxFontUtils::ParseFontList(value, genericFamilies);
+ }
+
+ GetFontFamiliesFromGenericFamilies(
+ aPresContext, StyleGenericFontFamily::MozEmoji, genericFamilies, nullptr,
+ aGenericFamilies);
+}
+
+void gfxPlatformFontList::GetFontFamiliesFromGenericFamilies(
+ nsPresContext* aPresContext, StyleGenericFontFamily aGenericType,
+ nsTArray<nsCString>& aGenericNameFamilies, nsAtom* aLangGroup,
+ PrefFontList* aGenericFamilies) {
+ // lookup and add platform fonts uniquely
+ for (const nsCString& genericFamily : aGenericNameFamilies) {
+ AutoTArray<FamilyAndGeneric, 10> families;
+ FindAndAddFamiliesLocked(aPresContext, aGenericType, genericFamily,
+ &families, FindFamiliesFlags(0), nullptr,
+ aLangGroup);
+ for (const FamilyAndGeneric& f : families) {
+ if (!aGenericFamilies->Contains(f.mFamily)) {
+ aGenericFamilies->AppendElement(f.mFamily);
+ }
+ }
+ }
+}
+
+gfxPlatformFontList::PrefFontList*
+gfxPlatformFontList::GetPrefFontsLangGroupLocked(
+ nsPresContext* aPresContext, StyleGenericFontFamily aGenericType,
+ eFontPrefLang aPrefLang) {
+ if (aGenericType == StyleGenericFontFamily::MozEmoji ||
+ aPrefLang == eFontPrefLang_Emoji) {
+ // Emoji font has no lang
+ PrefFontList* prefFonts = mEmojiPrefFont.get();
+ if (MOZ_UNLIKELY(!prefFonts)) {
+ prefFonts = new PrefFontList;
+ ResolveEmojiFontNames(aPresContext, prefFonts);
+ mEmojiPrefFont.reset(prefFonts);
+ }
+ return prefFonts;
+ }
+
+ auto index = static_cast<size_t>(aGenericType);
+ PrefFontList* prefFonts = mLangGroupPrefFonts[aPrefLang][index].get();
+ if (MOZ_UNLIKELY(!prefFonts)) {
+ prefFonts = new PrefFontList;
+ ResolveGenericFontNames(aPresContext, aGenericType, aPrefLang, prefFonts);
+ mLangGroupPrefFonts[aPrefLang][index].reset(prefFonts);
+ }
+ return prefFonts;
+}
+
+void gfxPlatformFontList::AddGenericFonts(
+ nsPresContext* aPresContext, StyleGenericFontFamily aGenericType,
+ nsAtom* aLanguage, nsTArray<FamilyAndGeneric>& aFamilyList) {
+ AutoLock lock(mLock);
+
+ // map lang ==> langGroup
+ nsAtom* langGroup = GetLangGroup(aLanguage);
+
+ // langGroup ==> prefLang
+ eFontPrefLang prefLang = GetFontPrefLangFor(langGroup);
+
+ // lookup pref fonts
+ PrefFontList* prefFonts =
+ GetPrefFontsLangGroupLocked(aPresContext, aGenericType, prefLang);
+
+ if (!prefFonts->IsEmpty()) {
+ aFamilyList.SetCapacity(aFamilyList.Length() + prefFonts->Length());
+ for (auto& f : *prefFonts) {
+ aFamilyList.AppendElement(FamilyAndGeneric(f, aGenericType));
+ }
+ }
+}
+
+static nsAtom* PrefLangToLangGroups(uint32_t aIndex) {
+ // static array here avoids static constructor
+ static nsAtom* gPrefLangToLangGroups[] = {
+#define FONT_PREF_LANG(enum_id_, str_, atom_id_) nsGkAtoms::atom_id_
+#include "gfxFontPrefLangList.h"
+#undef FONT_PREF_LANG
+ };
+
+ return aIndex < ArrayLength(gPrefLangToLangGroups)
+ ? gPrefLangToLangGroups[aIndex]
+ : nsGkAtoms::Unicode;
+}
+
+eFontPrefLang gfxPlatformFontList::GetFontPrefLangFor(const char* aLang) {
+ if (!aLang || !aLang[0]) {
+ return eFontPrefLang_Others;
+ }
+ for (uint32_t i = 0; i < ArrayLength(gPrefLangNames); ++i) {
+ if (!nsCRT::strcasecmp(gPrefLangNames[i], aLang)) {
+ return eFontPrefLang(i);
+ }
+ }
+ return eFontPrefLang_Others;
+}
+
+eFontPrefLang gfxPlatformFontList::GetFontPrefLangFor(nsAtom* aLang) {
+ if (!aLang) return eFontPrefLang_Others;
+ nsAutoCString lang;
+ aLang->ToUTF8String(lang);
+ return GetFontPrefLangFor(lang.get());
+}
+
+nsAtom* gfxPlatformFontList::GetLangGroupForPrefLang(eFontPrefLang aLang) {
+ // the special CJK set pref lang should be resolved into separate
+ // calls to individual CJK pref langs before getting here
+ NS_ASSERTION(aLang != eFontPrefLang_CJKSet, "unresolved CJK set pref lang");
+
+ return PrefLangToLangGroups(uint32_t(aLang));
+}
+
+const char* gfxPlatformFontList::GetPrefLangName(eFontPrefLang aLang) {
+ if (uint32_t(aLang) < ArrayLength(gPrefLangNames)) {
+ return gPrefLangNames[uint32_t(aLang)];
+ }
+ return nullptr;
+}
+
+eFontPrefLang gfxPlatformFontList::GetFontPrefLangFor(uint32_t aCh) {
+ switch (ublock_getCode(aCh)) {
+ case UBLOCK_BASIC_LATIN:
+ case UBLOCK_LATIN_1_SUPPLEMENT:
+ case UBLOCK_LATIN_EXTENDED_A:
+ case UBLOCK_LATIN_EXTENDED_B:
+ case UBLOCK_IPA_EXTENSIONS:
+ case UBLOCK_SPACING_MODIFIER_LETTERS:
+ case UBLOCK_LATIN_EXTENDED_ADDITIONAL:
+ case UBLOCK_LATIN_EXTENDED_C:
+ case UBLOCK_LATIN_EXTENDED_D:
+ case UBLOCK_LATIN_EXTENDED_E:
+ case UBLOCK_PHONETIC_EXTENSIONS:
+ return eFontPrefLang_Western;
+ case UBLOCK_GREEK:
+ case UBLOCK_GREEK_EXTENDED:
+ return eFontPrefLang_Greek;
+ case UBLOCK_CYRILLIC:
+ case UBLOCK_CYRILLIC_SUPPLEMENT:
+ case UBLOCK_CYRILLIC_EXTENDED_A:
+ case UBLOCK_CYRILLIC_EXTENDED_B:
+ case UBLOCK_CYRILLIC_EXTENDED_C:
+ return eFontPrefLang_Cyrillic;
+ case UBLOCK_ARMENIAN:
+ return eFontPrefLang_Armenian;
+ case UBLOCK_HEBREW:
+ return eFontPrefLang_Hebrew;
+ case UBLOCK_ARABIC:
+ case UBLOCK_ARABIC_PRESENTATION_FORMS_A:
+ case UBLOCK_ARABIC_PRESENTATION_FORMS_B:
+ case UBLOCK_ARABIC_SUPPLEMENT:
+ case UBLOCK_ARABIC_EXTENDED_A:
+ case UBLOCK_ARABIC_MATHEMATICAL_ALPHABETIC_SYMBOLS:
+ return eFontPrefLang_Arabic;
+ case UBLOCK_DEVANAGARI:
+ case UBLOCK_DEVANAGARI_EXTENDED:
+ return eFontPrefLang_Devanagari;
+ case UBLOCK_BENGALI:
+ return eFontPrefLang_Bengali;
+ case UBLOCK_GURMUKHI:
+ return eFontPrefLang_Gurmukhi;
+ case UBLOCK_GUJARATI:
+ return eFontPrefLang_Gujarati;
+ case UBLOCK_ORIYA:
+ return eFontPrefLang_Oriya;
+ case UBLOCK_TAMIL:
+ return eFontPrefLang_Tamil;
+ case UBLOCK_TELUGU:
+ return eFontPrefLang_Telugu;
+ case UBLOCK_KANNADA:
+ return eFontPrefLang_Kannada;
+ case UBLOCK_MALAYALAM:
+ return eFontPrefLang_Malayalam;
+ case UBLOCK_SINHALA:
+ case UBLOCK_SINHALA_ARCHAIC_NUMBERS:
+ return eFontPrefLang_Sinhala;
+ case UBLOCK_THAI:
+ return eFontPrefLang_Thai;
+ case UBLOCK_TIBETAN:
+ return eFontPrefLang_Tibetan;
+ case UBLOCK_GEORGIAN:
+ case UBLOCK_GEORGIAN_SUPPLEMENT:
+ case UBLOCK_GEORGIAN_EXTENDED:
+ return eFontPrefLang_Georgian;
+ case UBLOCK_HANGUL_JAMO:
+ case UBLOCK_HANGUL_COMPATIBILITY_JAMO:
+ case UBLOCK_HANGUL_SYLLABLES:
+ case UBLOCK_HANGUL_JAMO_EXTENDED_A:
+ case UBLOCK_HANGUL_JAMO_EXTENDED_B:
+ return eFontPrefLang_Korean;
+ case UBLOCK_ETHIOPIC:
+ case UBLOCK_ETHIOPIC_EXTENDED:
+ case UBLOCK_ETHIOPIC_SUPPLEMENT:
+ case UBLOCK_ETHIOPIC_EXTENDED_A:
+ return eFontPrefLang_Ethiopic;
+ case UBLOCK_UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS:
+ case UBLOCK_UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED:
+ return eFontPrefLang_Canadian;
+ case UBLOCK_KHMER:
+ case UBLOCK_KHMER_SYMBOLS:
+ return eFontPrefLang_Khmer;
+ case UBLOCK_CJK_RADICALS_SUPPLEMENT:
+ case UBLOCK_KANGXI_RADICALS:
+ case UBLOCK_IDEOGRAPHIC_DESCRIPTION_CHARACTERS:
+ case UBLOCK_CJK_SYMBOLS_AND_PUNCTUATION:
+ case UBLOCK_HIRAGANA:
+ case UBLOCK_KATAKANA:
+ case UBLOCK_BOPOMOFO:
+ case UBLOCK_KANBUN:
+ case UBLOCK_BOPOMOFO_EXTENDED:
+ case UBLOCK_ENCLOSED_CJK_LETTERS_AND_MONTHS:
+ case UBLOCK_CJK_COMPATIBILITY:
+ case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A:
+ case UBLOCK_CJK_UNIFIED_IDEOGRAPHS:
+ case UBLOCK_CJK_COMPATIBILITY_IDEOGRAPHS:
+ case UBLOCK_CJK_COMPATIBILITY_FORMS:
+ case UBLOCK_SMALL_FORM_VARIANTS:
+ case UBLOCK_HALFWIDTH_AND_FULLWIDTH_FORMS:
+ case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B:
+ case UBLOCK_CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT:
+ case UBLOCK_KATAKANA_PHONETIC_EXTENSIONS:
+ case UBLOCK_CJK_STROKES:
+ case UBLOCK_VERTICAL_FORMS:
+ case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_C:
+ case UBLOCK_KANA_SUPPLEMENT:
+ case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_D:
+ case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_E:
+ case UBLOCK_IDEOGRAPHIC_SYMBOLS_AND_PUNCTUATION:
+ case UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_F:
+ case UBLOCK_KANA_EXTENDED_A:
+ return eFontPrefLang_CJKSet;
+ case UBLOCK_MATHEMATICAL_OPERATORS:
+ case UBLOCK_MATHEMATICAL_ALPHANUMERIC_SYMBOLS:
+ case UBLOCK_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_A:
+ case UBLOCK_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_B:
+ case UBLOCK_SUPPLEMENTAL_MATHEMATICAL_OPERATORS:
+ return eFontPrefLang_Mathematics;
+ default:
+ return eFontPrefLang_Others;
+ }
+}
+
+bool gfxPlatformFontList::IsLangCJK(eFontPrefLang aLang) {
+ switch (aLang) {
+ case eFontPrefLang_Japanese:
+ case eFontPrefLang_ChineseTW:
+ case eFontPrefLang_ChineseCN:
+ case eFontPrefLang_ChineseHK:
+ case eFontPrefLang_Korean:
+ case eFontPrefLang_CJKSet:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void gfxPlatformFontList::GetLangPrefs(eFontPrefLang aPrefLangs[],
+ uint32_t& aLen, eFontPrefLang aCharLang,
+ eFontPrefLang aPageLang) {
+ AutoLock lock(mLock);
+ if (IsLangCJK(aCharLang)) {
+ AppendCJKPrefLangs(aPrefLangs, aLen, aCharLang, aPageLang);
+ } else {
+ AppendPrefLang(aPrefLangs, aLen, aCharLang);
+ }
+
+ AppendPrefLang(aPrefLangs, aLen, eFontPrefLang_Others);
+}
+
+void gfxPlatformFontList::AppendCJKPrefLangs(eFontPrefLang aPrefLangs[],
+ uint32_t& aLen,
+ eFontPrefLang aCharLang,
+ eFontPrefLang aPageLang) {
+ // prefer the lang specified by the page *if* CJK
+ if (IsLangCJK(aPageLang)) {
+ AppendPrefLang(aPrefLangs, aLen, aPageLang);
+ }
+
+ // if not set up, set up the default CJK order, based on accept lang
+ // settings and locale
+ if (mCJKPrefLangs.Length() == 0) {
+ // temp array
+ eFontPrefLang tempPrefLangs[kMaxLenPrefLangList];
+ uint32_t tempLen = 0;
+
+ // Add the CJK pref fonts from accept languages, the order should be same
+ // order. We use gfxFontUtils::GetPrefsFontList to read the list even
+ // though it's not actually a list of fonts but of lang codes; the format
+ // is the same.
+ AutoTArray<nsCString, 5> list;
+ gfxFontUtils::GetPrefsFontList("intl.accept_languages", list, true);
+ for (const auto& lang : list) {
+ eFontPrefLang fpl = GetFontPrefLangFor(lang.get());
+ switch (fpl) {
+ case eFontPrefLang_Japanese:
+ case eFontPrefLang_Korean:
+ case eFontPrefLang_ChineseCN:
+ case eFontPrefLang_ChineseHK:
+ case eFontPrefLang_ChineseTW:
+ AppendPrefLang(tempPrefLangs, tempLen, fpl);
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Try using app's locale
+ nsAutoCString localeStr;
+ LocaleService::GetInstance()->GetAppLocaleAsBCP47(localeStr);
+
+ {
+ Locale locale;
+ if (LocaleParser::TryParse(localeStr, locale).isOk() &&
+ locale.Canonicalize().isOk()) {
+ if (locale.Language().EqualTo("ja")) {
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Japanese);
+ } else if (locale.Language().EqualTo("zh")) {
+ if (locale.Region().EqualTo("CN")) {
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseCN);
+ } else if (locale.Region().EqualTo("TW")) {
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseTW);
+ } else if (locale.Region().EqualTo("HK")) {
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseHK);
+ }
+ } else if (locale.Language().EqualTo("ko")) {
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Korean);
+ }
+ }
+ }
+
+ // Then add the known CJK prefs in order of system preferred locales
+ AutoTArray<nsCString, 5> prefLocales;
+ prefLocales.AppendElement("ja"_ns);
+ prefLocales.AppendElement("zh-CN"_ns);
+ prefLocales.AppendElement("zh-TW"_ns);
+ prefLocales.AppendElement("zh-HK"_ns);
+ prefLocales.AppendElement("ko"_ns);
+
+ AutoTArray<nsCString, 16> sysLocales;
+ AutoTArray<nsCString, 16> negLocales;
+ if (NS_SUCCEEDED(
+ OSPreferences::GetInstance()->GetSystemLocales(sysLocales))) {
+ LocaleService::GetInstance()->NegotiateLanguages(
+ sysLocales, prefLocales, ""_ns,
+ LocaleService::kLangNegStrategyFiltering, negLocales);
+ for (const auto& localeStr : negLocales) {
+ Locale locale;
+ if (LocaleParser::TryParse(localeStr, locale).isOk() &&
+ locale.Canonicalize().isOk()) {
+ if (locale.Language().EqualTo("ja")) {
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Japanese);
+ } else if (locale.Language().EqualTo("zh")) {
+ if (locale.Region().EqualTo("CN")) {
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseCN);
+ } else if (locale.Region().EqualTo("TW")) {
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseTW);
+ } else if (locale.Region().EqualTo("HK")) {
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseHK);
+ }
+ } else if (locale.Language().EqualTo("ko")) {
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Korean);
+ }
+ }
+ }
+ }
+
+ // Last resort... set up CJK font prefs in the order listed by the user-
+ // configurable ordering pref.
+ gfxFontUtils::GetPrefsFontList(kCJKFallbackOrderPref, list);
+ for (const auto& item : list) {
+ eFontPrefLang fpl = GetFontPrefLangFor(item.get());
+ switch (fpl) {
+ case eFontPrefLang_Japanese:
+ case eFontPrefLang_Korean:
+ case eFontPrefLang_ChineseCN:
+ case eFontPrefLang_ChineseHK:
+ case eFontPrefLang_ChineseTW:
+ AppendPrefLang(tempPrefLangs, tempLen, fpl);
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Truly-last resort... try Chinese font prefs before Japanese because
+ // they tend to have more complete character coverage, and therefore less
+ // risk of "ransom-note" effects.
+ // (If the kCJKFallbackOrderPref was fully populated, as it is by default,
+ // this will do nothing as all these values are already present.)
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseCN);
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseHK);
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseTW);
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Japanese);
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Korean);
+
+ // copy into the cached array
+ for (const auto lang : Span<eFontPrefLang>(tempPrefLangs, tempLen)) {
+ mCJKPrefLangs.AppendElement(lang);
+ }
+ }
+
+ // append in cached CJK langs
+ for (const auto lang : mCJKPrefLangs) {
+ AppendPrefLang(aPrefLangs, aLen, eFontPrefLang(lang));
+ }
+}
+
+void gfxPlatformFontList::AppendPrefLang(eFontPrefLang aPrefLangs[],
+ uint32_t& aLen,
+ eFontPrefLang aAddLang) {
+ if (aLen >= kMaxLenPrefLangList) {
+ return;
+ }
+
+ // If the lang is already present, just ignore the addition.
+ for (const auto lang : Span<eFontPrefLang>(aPrefLangs, aLen)) {
+ if (lang == aAddLang) {
+ return;
+ }
+ }
+
+ aPrefLangs[aLen++] = aAddLang;
+}
+
+StyleGenericFontFamily gfxPlatformFontList::GetDefaultGeneric(
+ eFontPrefLang aLang) {
+ if (aLang == eFontPrefLang_Emoji) {
+ return StyleGenericFontFamily::MozEmoji;
+ }
+
+ AutoLock lock(mLock);
+
+ if (uint32_t(aLang) < ArrayLength(gPrefLangNames)) {
+ return mDefaultGenericsLangGroup[uint32_t(aLang)];
+ }
+ return StyleGenericFontFamily::Serif;
+}
+
+FontFamily gfxPlatformFontList::GetDefaultFont(nsPresContext* aPresContext,
+ const gfxFontStyle* aStyle) {
+ AutoLock lock(mLock);
+ return GetDefaultFontLocked(aPresContext, aStyle);
+}
+
+FontFamily gfxPlatformFontList::GetDefaultFontLocked(
+ nsPresContext* aPresContext, const gfxFontStyle* aStyle) {
+ FontFamily family = GetDefaultFontForPlatform(aPresContext, aStyle);
+ if (!family.IsNull()) {
+ return family;
+ }
+ // Something has gone wrong and we were unable to retrieve a default font
+ // from the platform. (Likely the whitelist has blocked all potential
+ // default fonts.) As a last resort, we return the first font in our list.
+ if (SharedFontList()) {
+ MOZ_RELEASE_ASSERT(SharedFontList()->NumFamilies() > 0);
+ return FontFamily(SharedFontList()->Families());
+ }
+ MOZ_RELEASE_ASSERT(mFontFamilies.Count() > 0);
+ return FontFamily(mFontFamilies.ConstIter().Data());
+}
+
+void gfxPlatformFontList::GetFontFamilyNames(
+ nsTArray<nsCString>& aFontFamilyNames) {
+ if (SharedFontList()) {
+ fontlist::FontList* list = SharedFontList();
+ const fontlist::Family* families = list->Families();
+ if (families) {
+ for (uint32_t i = 0, n = list->NumFamilies(); i < n; i++) {
+ const fontlist::Family& family = families[i];
+ if (!family.IsHidden()) {
+ aFontFamilyNames.AppendElement(family.DisplayName().AsString(list));
+ }
+ }
+ }
+ } else {
+ for (const RefPtr<gfxFontFamily>& family : mFontFamilies.Values()) {
+ if (!family->IsHidden()) {
+ aFontFamilyNames.AppendElement(family->Name());
+ }
+ }
+ }
+}
+
+nsAtom* gfxPlatformFontList::GetLangGroup(nsAtom* aLanguage) {
+ // map lang ==> langGroup
+ nsAtom* langGroup = nullptr;
+ if (aLanguage) {
+ langGroup = mLangService->GetLanguageGroup(aLanguage);
+ }
+ if (!langGroup) {
+ langGroup = nsGkAtoms::Unicode;
+ }
+ return langGroup;
+}
+
+/* static */ const char* gfxPlatformFontList::GetGenericName(
+ StyleGenericFontFamily aGenericType) {
+ // type should be standard generic type at this point
+ // map generic type to string
+ switch (aGenericType) {
+ case StyleGenericFontFamily::Serif:
+ return "serif";
+ case StyleGenericFontFamily::SansSerif:
+ return "sans-serif";
+ case StyleGenericFontFamily::Monospace:
+ return "monospace";
+ case StyleGenericFontFamily::Cursive:
+ return "cursive";
+ case StyleGenericFontFamily::Fantasy:
+ return "fantasy";
+ case StyleGenericFontFamily::SystemUi:
+ return "system-ui";
+ case StyleGenericFontFamily::MozEmoji:
+ return "-moz-emoji";
+ case StyleGenericFontFamily::None:
+ break;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown generic");
+ return nullptr;
+}
+
+void gfxPlatformFontList::InitLoader() {
+ GetFontFamilyNames(mFontInfo->mFontFamiliesToLoad);
+ mStartIndex = 0;
+ mNumFamilies = mFontInfo->mFontFamiliesToLoad.Length();
+ memset(&(mFontInfo->mLoadStats), 0, sizeof(mFontInfo->mLoadStats));
+}
+
+#define FONT_LOADER_MAX_TIMESLICE \
+ 20 // max time for one pass through RunLoader = 20ms
+
+bool gfxPlatformFontList::LoadFontInfo() {
+ AutoLock lock(mLock);
+ TimeStamp start = TimeStamp::Now();
+ uint32_t i, endIndex = mNumFamilies;
+ fontlist::FontList* list = SharedFontList();
+ bool loadCmaps =
+ !list && (!UsesSystemFallback() ||
+ gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback());
+
+ // for each font family, load in various font info
+ for (i = mStartIndex; i < endIndex; i++) {
+ nsAutoCString key;
+ GenerateFontListKey(mFontInfo->mFontFamiliesToLoad[i], key);
+
+ if (list) {
+ fontlist::Family* family = list->FindFamily(key);
+ if (!family) {
+ continue;
+ }
+ ReadFaceNamesForFamily(family, NeedFullnamePostscriptNames());
+ } else {
+ // lookup in canonical (i.e. English) family name list
+ gfxFontFamily* familyEntry = mFontFamilies.GetWeak(key);
+ if (!familyEntry) {
+ continue;
+ }
+
+ // read in face names
+ familyEntry->ReadFaceNames(this, NeedFullnamePostscriptNames(),
+ mFontInfo);
+
+ // load the cmaps if needed
+ if (loadCmaps) {
+ familyEntry->ReadAllCMAPs(mFontInfo);
+ }
+ }
+
+ // Limit the time spent reading fonts in one pass, unless the font-loader
+ // delay was set to zero, in which case we run to completion even if it
+ // causes some jank.
+ if (StaticPrefs::gfx_font_loader_delay_AtStartup() > 0) {
+ TimeDuration elapsed = TimeStamp::Now() - start;
+ if (elapsed.ToMilliseconds() > FONT_LOADER_MAX_TIMESLICE &&
+ i + 1 != endIndex) {
+ endIndex = i + 1;
+ break;
+ }
+ }
+ }
+
+ mStartIndex = endIndex;
+ bool done = mStartIndex >= mNumFamilies;
+
+ if (LOG_FONTINIT_ENABLED()) {
+ TimeDuration elapsed = TimeStamp::Now() - start;
+ LOG_FONTINIT(("(fontinit) fontloader load pass %8.2f ms done %s\n",
+ elapsed.ToMilliseconds(), (done ? "true" : "false")));
+ }
+
+ if (done) {
+ mOtherFamilyNamesInitialized = true;
+ CancelInitOtherFamilyNamesTask();
+ mFaceNameListsInitialized = true;
+ }
+
+ return done;
+}
+
+void gfxPlatformFontList::CleanupLoader() {
+ AutoLock lock(mLock);
+
+ mFontFamiliesToLoad.Clear();
+ mNumFamilies = 0;
+ bool rebuilt = false, forceReflow = false;
+
+ // if had missed face names that are now available, force reflow all
+ if (mFaceNamesMissed) {
+ rebuilt = std::any_of(mFaceNamesMissed->cbegin(), mFaceNamesMissed->cend(),
+ [&](const auto& key) {
+ mLock.AssertCurrentThreadIn();
+ return FindFaceName(key);
+ });
+ if (rebuilt) {
+ RebuildLocalFonts();
+ }
+
+ mFaceNamesMissed = nullptr;
+ }
+
+ if (mOtherNamesMissed) {
+ forceReflow = std::any_of(
+ mOtherNamesMissed->cbegin(), mOtherNamesMissed->cend(),
+ [&](const auto& key) {
+ mLock.AssertCurrentThreadIn();
+ return FindUnsharedFamily(
+ nullptr, key,
+ (FindFamiliesFlags::eForceOtherFamilyNamesLoading |
+ FindFamiliesFlags::eNoAddToNamesMissedWhenSearching));
+ });
+ if (forceReflow) {
+ ForceGlobalReflowLocked(gfxPlatform::NeedsReframe::No);
+ }
+
+ mOtherNamesMissed = nullptr;
+ }
+
+ if (LOG_FONTINIT_ENABLED() && mFontInfo) {
+ LOG_FONTINIT(
+ ("(fontinit) fontloader load thread took %8.2f ms "
+ "%d families %d fonts %d cmaps "
+ "%d facenames %d othernames %s %s",
+ mLoadTime.ToMilliseconds(), mFontInfo->mLoadStats.families,
+ mFontInfo->mLoadStats.fonts, mFontInfo->mLoadStats.cmaps,
+ mFontInfo->mLoadStats.facenames, mFontInfo->mLoadStats.othernames,
+ (rebuilt ? "(userfont sets rebuilt)" : ""),
+ (forceReflow ? "(global reflow)" : "")));
+ }
+
+ gfxFontInfoLoader::CleanupLoader();
+}
+
+void gfxPlatformFontList::ForceGlobalReflowLocked(
+ gfxPlatform::NeedsReframe aNeedsReframe,
+ gfxPlatform::BroadcastToChildren aBroadcastToChildren) {
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "gfxPlatformFontList::ForceGlobalReflowLocked",
+ [aNeedsReframe, aBroadcastToChildren] {
+ gfxPlatform::ForceGlobalReflow(aNeedsReframe, aBroadcastToChildren);
+ }));
+ return;
+ }
+
+ AutoUnlock unlock(mLock);
+ gfxPlatform::ForceGlobalReflow(aNeedsReframe, aBroadcastToChildren);
+}
+
+void gfxPlatformFontList::GetPrefsAndStartLoader() {
+ // If we're already in shutdown, there's no point in starting this, and it
+ // could trigger an assertion if we try to use the Thread Manager too late.
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ return;
+ }
+ uint32_t delay = std::max(1u, StaticPrefs::gfx_font_loader_delay_AtStartup());
+ if (NS_IsMainThread()) {
+ StartLoader(delay);
+ } else {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "StartLoader callback", [delay, fontList = this] {
+ fontList->Lock();
+ fontList->StartLoader(delay);
+ fontList->Unlock();
+ }));
+ }
+}
+
+void gfxPlatformFontList::RebuildLocalFonts(bool aForgetLocalFaces) {
+ for (auto* fontset : mUserFontSetList) {
+ if (aForgetLocalFaces) {
+ fontset->ForgetLocalFaces();
+ }
+ fontset->RebuildLocalRules();
+ }
+}
+
+void gfxPlatformFontList::ClearLangGroupPrefFontsLocked() {
+ for (uint32_t i = eFontPrefLang_First;
+ i < eFontPrefLang_First + eFontPrefLang_Count; i++) {
+ auto& prefFontsLangGroup = mLangGroupPrefFonts[i];
+ for (auto& pref : prefFontsLangGroup) {
+ pref = nullptr;
+ }
+ }
+ mCJKPrefLangs.Clear();
+ mEmojiPrefFont = nullptr;
+
+ // Create a new FontPrefs and replace the existing one.
+ mFontPrefs = MakeUnique<FontPrefs>();
+}
+
+// Support for memory reporting
+
+// this is also used by subclasses that hold additional font tables
+/*static*/
+size_t gfxPlatformFontList::SizeOfFontFamilyTableExcludingThis(
+ const FontFamilyTable& aTable, MallocSizeOf aMallocSizeOf) {
+ return std::accumulate(
+ aTable.Keys().cbegin(), aTable.Keys().cend(),
+ aTable.ShallowSizeOfExcludingThis(aMallocSizeOf),
+ [&](size_t oldValue, const nsACString& key) {
+ // We don't count the size of the family here, because this is an
+ // *extra* reference to a family that will have already been counted in
+ // the main list.
+ return oldValue + key.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ });
+}
+
+/*static*/
+size_t gfxPlatformFontList::SizeOfFontEntryTableExcludingThis(
+ const FontEntryTable& aTable, MallocSizeOf aMallocSizeOf) {
+ return std::accumulate(
+ aTable.Keys().cbegin(), aTable.Keys().cend(),
+ aTable.ShallowSizeOfExcludingThis(aMallocSizeOf),
+ [&](size_t oldValue, const nsACString& key) {
+ // The font itself is counted by its owning family; here we only care
+ // about the names stored in the hashtable keys.
+
+ return oldValue + key.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ });
+}
+
+void gfxPlatformFontList::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ AutoLock lock(mLock);
+
+ aSizes->mFontListSize +=
+ mFontFamilies.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (const auto& entry : mFontFamilies) {
+ aSizes->mFontListSize +=
+ entry.GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ entry.GetData()->AddSizeOfIncludingThis(aMallocSizeOf, aSizes);
+ }
+
+ aSizes->mFontListSize +=
+ SizeOfFontFamilyTableExcludingThis(mOtherFamilyNames, aMallocSizeOf);
+
+ if (mExtraNames) {
+ aSizes->mFontListSize += SizeOfFontEntryTableExcludingThis(
+ mExtraNames->mFullnames, aMallocSizeOf);
+ aSizes->mFontListSize += SizeOfFontEntryTableExcludingThis(
+ mExtraNames->mPostscriptNames, aMallocSizeOf);
+ }
+
+ for (uint32_t i = eFontPrefLang_First;
+ i < eFontPrefLang_First + eFontPrefLang_Count; i++) {
+ auto& prefFontsLangGroup = mLangGroupPrefFonts[i];
+ for (const UniquePtr<PrefFontList>& pf : prefFontsLangGroup) {
+ if (pf) {
+ aSizes->mFontListSize += pf->ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+ }
+ }
+
+ for (const auto& bitset : mCodepointsWithNoFonts) {
+ aSizes->mFontListSize += bitset.SizeOfExcludingThis(aMallocSizeOf);
+ }
+ aSizes->mFontListSize +=
+ mFontFamiliesToLoad.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ aSizes->mFontListSize +=
+ mBadUnderlineFamilyNames.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (const auto& i : mBadUnderlineFamilyNames) {
+ aSizes->mFontListSize += i.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ }
+
+ aSizes->mFontListSize +=
+ mSharedCmaps.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (const auto& entry : mSharedCmaps) {
+ aSizes->mCharMapsSize += entry.GetKey()->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ aSizes->mFontListSize +=
+ mFontEntries.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (const auto& entry : mFontEntries.Values()) {
+ if (entry) {
+ entry->AddSizeOfIncludingThis(aMallocSizeOf, aSizes);
+ }
+ }
+
+ if (SharedFontList()) {
+ aSizes->mFontListSize +=
+ SharedFontList()->SizeOfIncludingThis(aMallocSizeOf);
+ if (XRE_IsParentProcess()) {
+ aSizes->mSharedSize += SharedFontList()->AllocatedShmemSize();
+ }
+ }
+}
+
+void gfxPlatformFontList::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ aSizes->mFontListSize += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
+
+void gfxPlatformFontList::InitOtherFamilyNamesInternal(
+ bool aDeferOtherFamilyNamesLoading) {
+ if (mOtherFamilyNamesInitialized) {
+ return;
+ }
+
+ AutoLock lock(mLock);
+
+ if (aDeferOtherFamilyNamesLoading) {
+ TimeStamp start = TimeStamp::Now();
+ bool timedOut = false;
+
+ auto list = SharedFontList();
+ if (list) {
+ // If the gfxFontInfoLoader task is not yet running, kick it off now so
+ // that it will load remaining names etc as soon as idle time permits.
+ if (mState == stateInitial || mState == stateTimerOnDelay) {
+ StartLoader(0);
+ timedOut = true;
+ }
+ } else {
+ for (const RefPtr<gfxFontFamily>& family : mFontFamilies.Values()) {
+ family->ReadOtherFamilyNames(this);
+ TimeDuration elapsed = TimeStamp::Now() - start;
+ if (elapsed.ToMilliseconds() > OTHERNAMES_TIMEOUT) {
+ timedOut = true;
+ break;
+ }
+ }
+ }
+
+ if (!timedOut) {
+ mOtherFamilyNamesInitialized = true;
+ CancelInitOtherFamilyNamesTask();
+ }
+ TimeStamp end = TimeStamp::Now();
+ Telemetry::AccumulateTimeDelta(Telemetry::FONTLIST_INITOTHERFAMILYNAMES,
+ start, end);
+
+ if (LOG_FONTINIT_ENABLED()) {
+ TimeDuration elapsed = end - start;
+ LOG_FONTINIT(("(fontinit) InitOtherFamilyNames took %8.2f ms %s",
+ elapsed.ToMilliseconds(), (timedOut ? "timeout" : "")));
+ }
+ } else {
+ TimeStamp start = TimeStamp::Now();
+
+ auto list = SharedFontList();
+ if (list) {
+ for (auto& f : mozilla::Range<fontlist::Family>(list->Families(),
+ list->NumFamilies())) {
+ ReadFaceNamesForFamily(&f, false);
+ }
+ } else {
+ for (const RefPtr<gfxFontFamily>& family : mFontFamilies.Values()) {
+ family->ReadOtherFamilyNames(this);
+ }
+ }
+
+ mOtherFamilyNamesInitialized = true;
+ CancelInitOtherFamilyNamesTask();
+
+ TimeStamp end = TimeStamp::Now();
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::FONTLIST_INITOTHERFAMILYNAMES_NO_DEFERRING, start, end);
+
+ if (LOG_FONTINIT_ENABLED()) {
+ TimeDuration elapsed = end - start;
+ LOG_FONTINIT(
+ ("(fontinit) InitOtherFamilyNames without deferring took %8.2f ms",
+ elapsed.ToMilliseconds()));
+ }
+ }
+}
+
+void gfxPlatformFontList::CancelInitOtherFamilyNamesTask() {
+ if (mPendingOtherFamilyNameTask) {
+ mPendingOtherFamilyNameTask->Cancel();
+ mPendingOtherFamilyNameTask = nullptr;
+ }
+ auto list = SharedFontList();
+ if (list && XRE_IsParentProcess()) {
+ bool forceReflow = false;
+ if (!mAliasTable.IsEmpty()) {
+ list->SetAliases(mAliasTable);
+ mAliasTable.Clear();
+ forceReflow = true;
+ }
+ if (mLocalNameTable.Count()) {
+ list->SetLocalNames(mLocalNameTable);
+ mLocalNameTable.Clear();
+ forceReflow = true;
+ }
+ if (forceReflow) {
+ dom::ContentParent::BroadcastFontListChanged();
+ }
+ }
+}
+
+void gfxPlatformFontList::ShareFontListShmBlockToProcess(
+ uint32_t aGeneration, uint32_t aIndex, base::ProcessId aPid,
+ base::SharedMemoryHandle* aOut) {
+ auto list = SharedFontList();
+ if (!list) {
+ return;
+ }
+ if (!aGeneration || list->GetGeneration() == aGeneration) {
+ list->ShareShmBlockToProcess(aIndex, aPid, aOut);
+ } else {
+ *aOut = base::SharedMemory::NULLHandle();
+ }
+}
+
+void gfxPlatformFontList::ShareFontListToProcess(
+ nsTArray<base::SharedMemoryHandle>* aBlocks, base::ProcessId aPid) {
+ auto list = SharedFontList();
+ if (list) {
+ list->ShareBlocksToProcess(aBlocks, aPid);
+ }
+}
+
+base::SharedMemoryHandle gfxPlatformFontList::ShareShmBlockToProcess(
+ uint32_t aIndex, base::ProcessId aPid) {
+ MOZ_RELEASE_ASSERT(SharedFontList());
+ return SharedFontList()->ShareBlockToProcess(aIndex, aPid);
+}
+
+void gfxPlatformFontList::ShmBlockAdded(uint32_t aGeneration, uint32_t aIndex,
+ base::SharedMemoryHandle aHandle) {
+ if (SharedFontList()) {
+ AutoLock lock(mLock);
+ SharedFontList()->ShmBlockAdded(aGeneration, aIndex, std::move(aHandle));
+ }
+}
+
+void gfxPlatformFontList::InitializeFamily(uint32_t aGeneration,
+ uint32_t aFamilyIndex,
+ bool aLoadCmaps) {
+ auto list = SharedFontList();
+ MOZ_ASSERT(list);
+ if (!list) {
+ return;
+ }
+ if (list->GetGeneration() != aGeneration) {
+ return;
+ }
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ return;
+ }
+ if (aFamilyIndex >= list->NumFamilies()) {
+ return;
+ }
+ fontlist::Family* family = list->Families() + aFamilyIndex;
+ if (!family->IsInitialized() || aLoadCmaps) {
+ Unused << InitializeFamily(family, aLoadCmaps);
+ }
+}
+
+void gfxPlatformFontList::SetCharacterMap(uint32_t aGeneration,
+ const fontlist::Pointer& aFacePtr,
+ const gfxSparseBitSet& aMap) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ auto list = SharedFontList();
+ MOZ_ASSERT(list);
+ if (!list) {
+ return;
+ }
+ if (list->GetGeneration() != aGeneration) {
+ return;
+ }
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ return;
+ }
+ auto* face = aFacePtr.ToPtr<fontlist::Face>(list);
+ if (face) {
+ face->mCharacterMap = GetShmemCharMap(&aMap);
+ }
+}
+
+void gfxPlatformFontList::SetupFamilyCharMap(
+ uint32_t aGeneration, const fontlist::Pointer& aFamilyPtr) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ auto list = SharedFontList();
+ MOZ_ASSERT(list);
+ if (!list) {
+ return;
+ }
+ if (list->GetGeneration() != aGeneration) {
+ return;
+ }
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ return;
+ }
+
+ // aFamilyPtr was passed from a content process which may not be trusted,
+ // so we cannot assume it is valid or safe to use. If the Pointer value is
+ // bad, we must not crash or do anything bad, just bail out.
+ // (In general, if the child process was trying to use an invalid pointer it
+ // should have hit the MOZ_DIAGNOSTIC_ASSERT in FontList::ToSharedPointer
+ // rather than passing a null or bad pointer to the parent.)
+
+ auto* family = aFamilyPtr.ToPtr<fontlist::Family>(list);
+ if (!family) {
+ // Unable to resolve to a native pointer (or it was null).
+ NS_WARNING("unexpected null Family pointer");
+ return;
+ }
+
+ // Validate the pointer before trying to use it: check that it points to a
+ // correctly-aligned offset within the Families() or AliasFamilies() array.
+ // We just assert (in debug builds only) on failure, and return safely.
+ // A misaligned pointer here would indicate a buggy (or compromised) child
+ // process, but crashing the parent would be unnecessary and does not yield
+ // any useful insight.
+ if (family >= list->Families() &&
+ family < list->Families() + list->NumFamilies()) {
+ size_t offset = (char*)family - (char*)list->Families();
+ if (offset % sizeof(fontlist::Family) != 0) {
+ MOZ_ASSERT(false, "misaligned Family pointer");
+ return;
+ }
+ } else if (family >= list->AliasFamilies() &&
+ family < list->AliasFamilies() + list->NumAliases()) {
+ size_t offset = (char*)family - (char*)list->AliasFamilies();
+ if (offset % sizeof(fontlist::Family) != 0) {
+ MOZ_ASSERT(false, "misaligned Family pointer");
+ return;
+ }
+ } else {
+ MOZ_ASSERT(false, "not a valid Family or AliasFamily pointer");
+ return;
+ }
+
+ family->SetupFamilyCharMap(list);
+}
+
+bool gfxPlatformFontList::InitOtherFamilyNames(uint32_t aGeneration,
+ bool aDefer) {
+ auto list = SharedFontList();
+ MOZ_ASSERT(list);
+ if (!list) {
+ return false;
+ }
+ if (list->GetGeneration() != aGeneration) {
+ return false;
+ }
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ return false;
+ }
+ return InitOtherFamilyNames(aDefer);
+}
+
+uint32_t gfxPlatformFontList::GetGeneration() const {
+ return SharedFontList() ? SharedFontList()->GetGeneration() : 0;
+}
+
+gfxPlatformFontList::FontPrefs::FontPrefs() {
+ // This must be created on the main thread, so that we can safely use the
+ // Preferences service. Once created, it can be read from any thread.
+ MOZ_ASSERT(NS_IsMainThread());
+ Init();
+}
+
+void gfxPlatformFontList::FontPrefs::Init() {
+ nsIPrefBranch* prefRootBranch = Preferences::GetRootBranch();
+ if (!prefRootBranch) {
+ return;
+ }
+ nsTArray<nsCString> prefNames;
+ if (NS_SUCCEEDED(prefRootBranch->GetChildList(kNamePrefix, prefNames))) {
+ for (auto& prefName : prefNames) {
+ nsAutoCString value;
+ if (NS_SUCCEEDED(Preferences::GetCString(prefName.get(), value))) {
+ nsAutoCString pref(Substring(prefName, sizeof(kNamePrefix) - 1));
+ mFontName.InsertOrUpdate(pref, value);
+ }
+ }
+ }
+ if (NS_SUCCEEDED(prefRootBranch->GetChildList(kNameListPrefix, prefNames))) {
+ for (auto& prefName : prefNames) {
+ nsAutoCString value;
+ if (NS_SUCCEEDED(Preferences::GetCString(prefName.get(), value))) {
+ nsAutoCString pref(Substring(prefName, sizeof(kNameListPrefix) - 1));
+ mFontNameList.InsertOrUpdate(pref, value);
+ }
+ }
+ }
+ mEmojiHasUserValue = Preferences::HasUserValue("font.name-list.emoji");
+}
+
+bool gfxPlatformFontList::FontPrefs::LookupName(const nsACString& aPref,
+ nsACString& aValue) const {
+ if (const auto& value = mFontName.Lookup(aPref)) {
+ aValue = *value;
+ return true;
+ }
+ return false;
+}
+
+bool gfxPlatformFontList::FontPrefs::LookupNameList(const nsACString& aPref,
+ nsACString& aValue) const {
+ if (const auto& value = mFontNameList.Lookup(aPref)) {
+ aValue = *value;
+ return true;
+ }
+ return false;
+}
+
+bool gfxPlatformFontList::IsKnownIconFontFamily(
+ const nsAtom* aFamilyName) const {
+ nsAtomCString fam(aFamilyName);
+ ToLowerCase(fam);
+ return mIconFontsSet.Contains(fam);
+}
+
+#undef LOG
+#undef LOG_ENABLED
diff --git a/gfx/thebes/gfxPlatformFontList.h b/gfx/thebes/gfxPlatformFontList.h
new file mode 100644
index 0000000000..930026d8cc
--- /dev/null
+++ b/gfx/thebes/gfxPlatformFontList.h
@@ -0,0 +1,1067 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFXPLATFORMFONTLIST_H_
+#define GFXPLATFORMFONTLIST_H_
+
+#include "nsClassHashtable.h"
+#include "nsTHashMap.h"
+#include "nsTHashSet.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTHashtable.h"
+
+#include "gfxFontUtils.h"
+#include "gfxFontInfoLoader.h"
+#include "gfxFont.h"
+#include "gfxFontConstants.h"
+#include "gfxPlatform.h"
+#include "SharedFontList.h"
+
+#include "nsIMemoryReporter.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EnumeratedArray.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/RangedArray.h"
+#include "mozilla/RecursiveMutex.h"
+#include "nsLanguageAtomService.h"
+
+#include "base/shared_memory.h"
+
+namespace mozilla {
+namespace fontlist {
+struct AliasData;
+}
+} // namespace mozilla
+
+class CharMapHashKey : public PLDHashEntryHdr {
+ public:
+ typedef gfxCharacterMap* KeyType;
+ typedef const gfxCharacterMap* KeyTypePointer;
+
+ explicit CharMapHashKey(const gfxCharacterMap* aCharMap)
+ : mCharMap(const_cast<gfxCharacterMap*>(aCharMap)) {
+ MOZ_COUNT_CTOR(CharMapHashKey);
+ }
+ CharMapHashKey(const CharMapHashKey& toCopy) : mCharMap(toCopy.mCharMap) {
+ MOZ_COUNT_CTOR(CharMapHashKey);
+ }
+ MOZ_COUNTED_DTOR(CharMapHashKey)
+
+ gfxCharacterMap* GetKey() const { return mCharMap.get(); }
+
+ bool KeyEquals(const gfxCharacterMap* aCharMap) const {
+ MOZ_ASSERT(!aCharMap->mBuildOnTheFly && !mCharMap->mBuildOnTheFly,
+ "custom cmap used in shared cmap hashtable");
+ // cmaps built on the fly never match
+ if (aCharMap->mHash != mCharMap->mHash) {
+ return false;
+ }
+ return mCharMap->Equals(aCharMap);
+ }
+
+ static const gfxCharacterMap* KeyToPointer(gfxCharacterMap* aCharMap) {
+ return aCharMap;
+ }
+ static PLDHashNumber HashKey(const gfxCharacterMap* aCharMap) {
+ return aCharMap->mHash;
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ protected:
+ friend class gfxPlatformFontList;
+
+ // gfxCharacterMap::Release() will notify us when the refcount of a
+ // charmap drops to 1; at that point, we'll lock the cache, check if
+ // the charmap is owned by the cache and this is still the only ref,
+ // and if so, delete it.
+ RefPtr<gfxCharacterMap> mCharMap;
+};
+
+/**
+ * A helper class used to create a SharedBitSet instance in a FontList's shared
+ * memory, while ensuring that we avoid bloating memory by avoiding creating
+ * duplicate instances.
+ */
+class ShmemCharMapHashEntry final : public PLDHashEntryHdr {
+ public:
+ typedef const gfxSparseBitSet* KeyType;
+ typedef const gfxSparseBitSet* KeyTypePointer;
+
+ /**
+ * Creation from a gfxSparseBitSet creates not only the ShmemCharMapHashEntry
+ * itself, but also a SharedBitSet in shared memory.
+ * Only the parent process creates and manages these entries.
+ */
+ explicit ShmemCharMapHashEntry(const gfxSparseBitSet* aCharMap);
+
+ ShmemCharMapHashEntry(ShmemCharMapHashEntry&&) = default;
+ ShmemCharMapHashEntry& operator=(ShmemCharMapHashEntry&&) = default;
+
+ /**
+ * Return a shared-memory Pointer that refers to the wrapped SharedBitSet.
+ * This can be passed to content processes to give them access to the same
+ * SharedBitSet as the parent stored.
+ */
+ mozilla::fontlist::Pointer GetCharMap() const { return mCharMap; }
+
+ bool KeyEquals(KeyType aCharMap) const {
+ // mHash is a 32-bit Adler checksum of the bitset; if it doesn't match we
+ // can immediately reject it as non-matching, but if it is equal we still
+ // need to do a full equality check below.
+ if (mHash != aCharMap->GetChecksum()) {
+ return false;
+ }
+
+ return mCharMap.ToPtr<const SharedBitSet>(mList)->Equals(aCharMap);
+ }
+
+ static KeyTypePointer KeyToPointer(KeyType aCharMap) { return aCharMap; }
+ static PLDHashNumber HashKey(KeyType aCharMap) {
+ return aCharMap->GetChecksum();
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ // charMaps are stored in the shared memory that FontList objects point to,
+ // and are never deleted until the FontList (all referencing font lists,
+ // actually) have gone away.
+ mozilla::fontlist::FontList* mList;
+ mozilla::fontlist::Pointer mCharMap;
+ uint32_t mHash;
+};
+
+// gfxPlatformFontList is an abstract class for the global font list on the
+// system; concrete subclasses for each platform implement the actual interface
+// to the system fonts. This class exists because we cannot rely on the platform
+// font-finding APIs to behave in sensible/similar ways, particularly with rich,
+// complex OpenType families, so we do our own font family/style management here
+// instead.
+
+// Much of this is based on the old gfxQuartzFontCache, but adapted for use on
+// all platforms.
+
+struct FontListSizes {
+ uint32_t mFontListSize; // size of the font list and dependent objects
+ // (font family and face names, etc), but NOT
+ // including the font table cache and the cmaps
+ uint32_t
+ mFontTableCacheSize; // memory used for the gfxFontEntry table caches
+ uint32_t mCharMapsSize; // memory used for cmap coverage info
+ uint32_t mLoaderSize; // memory used for (platform-specific) loader
+ uint32_t mSharedSize; // shared-memory use (reported by parent only)
+};
+
+class gfxUserFontSet;
+
+class gfxPlatformFontList : public gfxFontInfoLoader {
+ friend class InitOtherFamilyNamesRunnable;
+
+ public:
+ typedef mozilla::StretchRange StretchRange;
+ typedef mozilla::SlantStyleRange SlantStyleRange;
+ typedef mozilla::WeightRange WeightRange;
+ typedef mozilla::intl::Script Script;
+
+ using AutoLock = mozilla::RecursiveMutexAutoLock;
+ using AutoUnlock = mozilla::RecursiveMutexAutoUnlock;
+
+ // Class used to hold cached copies of the font-name prefs, so that they can
+ // be accessed from non-main-thread callers who are not allowed to touch the
+ // Preferences service.
+ class FontPrefs final {
+ public:
+ using HashMap = nsTHashMap<nsCStringHashKey, nsCString>;
+
+ FontPrefs();
+ ~FontPrefs() = default;
+
+ FontPrefs(const FontPrefs& aOther) = delete;
+ FontPrefs& operator=(const FontPrefs& aOther) = delete;
+
+ // Lookup the font.name.<foo> or font.name-list.<foo> pref for a given
+ // generic+langgroup pair.
+ bool LookupName(const nsACString& aPref, nsACString& aValue) const;
+ bool LookupNameList(const nsACString& aPref, nsACString& aValue) const;
+
+ // Does the font.name-list.emoji pref have a user-set value?
+ bool EmojiHasUserValue() const { return mEmojiHasUserValue; }
+
+ // Expose iterators over all the defined prefs of each type.
+ HashMap::ConstIterator NameIter() const { return mFontName.ConstIter(); }
+ HashMap::ConstIterator NameListIter() const {
+ return mFontNameList.ConstIter();
+ }
+
+ private:
+ static constexpr char kNamePrefix[] = "font.name.";
+ static constexpr char kNameListPrefix[] = "font.name-list.";
+
+ void Init();
+
+ HashMap mFontName;
+ HashMap mFontNameList;
+ bool mEmojiHasUserValue = false;
+ };
+
+ // For font family lists loaded from user preferences (prefs such as
+ // font.name-list.<generic>.<langGroup>) that map CSS generics to
+ // platform-specific font families.
+ typedef nsTArray<FontFamily> PrefFontList;
+
+ static gfxPlatformFontList* PlatformFontList() {
+ // If there is a font-list initialization thread, we need to let it run
+ // to completion before the font list can be used for anything else.
+ if (sInitFontListThread) {
+ // If we're currently on the initialization thread, just continue;
+ // otherwise wait for it to finish.
+ if (IsInitFontListThread()) {
+ return sPlatformFontList;
+ }
+ PR_JoinThread(sInitFontListThread);
+ sInitFontListThread = nullptr;
+ // If font-list initialization failed, the thread will have cleared
+ // the static sPlatformFontList pointer; we cannot proceed without any
+ // usable fonts.
+ if (!sPlatformFontList) {
+ MOZ_CRASH("Could not initialize gfxPlatformFontList");
+ }
+ }
+ if (!sPlatformFontList->IsInitialized()) {
+ if (!sPlatformFontList->InitFontList()) {
+ MOZ_CRASH("Could not initialize gfxPlatformFontList");
+ }
+ }
+ return sPlatformFontList;
+ }
+
+ static bool Initialize(gfxPlatformFontList* aList);
+
+ static void Shutdown() {
+ delete sPlatformFontList;
+ sPlatformFontList = nullptr;
+ }
+
+ bool IsInitialized() const { return mFontlistInitCount; }
+
+ virtual ~gfxPlatformFontList();
+
+ // Initialize font lists; return true on success, false if something fails.
+ bool InitFontList();
+
+ void FontListChanged();
+
+ /**
+ * Gathers (from a platform's underlying font system) the information needed
+ * to initialize a fontlist::Family with its Face members.
+ */
+ virtual void GetFacesInitDataForFamily(
+ const mozilla::fontlist::Family* aFamily,
+ nsTArray<mozilla::fontlist::Face::InitData>& aFaces,
+ bool aLoadCmaps) const {}
+
+ virtual void GetFontList(nsAtom* aLangGroup, const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts);
+
+ // Pass false to notify content that the shared font list has been modified
+ // but not completely invalidated.
+ void UpdateFontList(bool aFullRebuild = true);
+
+ void ClearLangGroupPrefFonts() {
+ AutoLock lock(mLock);
+ ClearLangGroupPrefFontsLocked();
+ }
+ virtual void ClearLangGroupPrefFontsLocked() MOZ_REQUIRES(mLock);
+
+ void GetFontFamilyList(nsTArray<RefPtr<gfxFontFamily>>& aFamilyArray);
+
+ already_AddRefed<gfxFont> SystemFindFontForChar(
+ nsPresContext* aPresContext, uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript, eFontPresentation aPresentation,
+ const gfxFontStyle* aStyle, FontVisibility* aVisibility);
+
+ // Flags to control optional behaviors in FindAndAddFamilies. The sense
+ // of the bit flags have been chosen such that the default parameter of
+ // FindFamiliesFlags(0) in FindFamily will give the most commonly-desired
+ // behavior, and only a few callsites need to explicitly pass other values.
+ enum class FindFamiliesFlags {
+ // If set, "other" (e.g. localized) family names should be loaded
+ // immediately; if clear, InitOtherFamilyNames is allowed to defer
+ // loading to avoid blocking.
+ eForceOtherFamilyNamesLoading = 1 << 0,
+
+ // If set, FindAndAddFamilies should not check for legacy "styled
+ // family" names to add to the font list. This is used to avoid
+ // a recursive search when using FindFamily to find a potential base
+ // family name for a styled variant.
+ eNoSearchForLegacyFamilyNames = 1 << 1,
+
+ // If set, FindAndAddFamilies will not add a missing entry to
+ // mOtherNamesMissed
+ eNoAddToNamesMissedWhenSearching = 1 << 2,
+
+ // If set, the family name was quoted and so must not be treated as a CSS
+ // generic.
+ eQuotedFamilyName = 1 << 3,
+
+ // If set, "hidden" font families (like ".SF NS Text" on macOS) are
+ // searched in addition to standard user-visible families.
+ eSearchHiddenFamilies = 1 << 4,
+ };
+
+ // Find family(ies) matching aFamily and append to the aOutput array
+ // (there may be multiple results in the case of fontconfig aliases, etc).
+ // Return true if any match was found and appended, false if none.
+ bool FindAndAddFamilies(
+ nsPresContext* aPresContext, mozilla::StyleGenericFontFamily aGeneric,
+ const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput,
+ FindFamiliesFlags aFlags, gfxFontStyle* aStyle = nullptr,
+ nsAtom* aLanguage = nullptr, gfxFloat aDevToCssSize = 1.0) {
+ AutoLock lock(mLock);
+ return FindAndAddFamiliesLocked(aPresContext, aGeneric, aFamily, aOutput,
+ aFlags, aStyle, aLanguage, aDevToCssSize);
+ }
+ virtual bool FindAndAddFamiliesLocked(
+ nsPresContext* aPresContext, mozilla::StyleGenericFontFamily aGeneric,
+ const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput,
+ FindFamiliesFlags aFlags, gfxFontStyle* aStyle = nullptr,
+ nsAtom* aLanguage = nullptr, gfxFloat aDevToCssSize = 1.0)
+ MOZ_REQUIRES(mLock);
+
+ gfxFontEntry* FindFontForFamily(nsPresContext* aPresContext,
+ const nsACString& aFamily,
+ const gfxFontStyle* aStyle);
+
+ mozilla::fontlist::FontList* SharedFontList() const {
+ return mSharedFontList.get();
+ }
+
+ // Create a handle for a single shmem block (identified by index) ready to
+ // be shared to the given processId.
+ void ShareFontListShmBlockToProcess(uint32_t aGeneration, uint32_t aIndex,
+ base::ProcessId aPid,
+ base::SharedMemoryHandle* aOut);
+
+ // Populate the array aBlocks with the complete list of shmem handles ready
+ // to be shared to the given processId.
+ void ShareFontListToProcess(nsTArray<base::SharedMemoryHandle>* aBlocks,
+ base::ProcessId aPid);
+
+ void ShmBlockAdded(uint32_t aGeneration, uint32_t aIndex,
+ base::SharedMemoryHandle aHandle);
+
+ base::SharedMemoryHandle ShareShmBlockToProcess(uint32_t aIndex,
+ base::ProcessId aPid);
+
+ void SetCharacterMap(uint32_t aGeneration,
+ const mozilla::fontlist::Pointer& aFacePtr,
+ const gfxSparseBitSet& aMap);
+
+ void SetupFamilyCharMap(uint32_t aGeneration,
+ const mozilla::fontlist::Pointer& aFamilyPtr);
+
+ // Start the async cmap loading process, if not already under way, from the
+ // given family index. (For use in any process that needs font lookups.)
+ void StartCmapLoadingFromFamily(uint32_t aStartIndex);
+
+ // [Parent] Handle request from content process to start cmap loading.
+ void StartCmapLoading(uint32_t aGeneration, uint32_t aStartIndex);
+
+ void CancelLoadCmapsTask() {
+ if (mLoadCmapsRunnable) {
+ mLoadCmapsRunnable->Cancel();
+ mLoadCmapsRunnable = nullptr;
+ }
+ }
+
+ // Populate aFamily with face records, and if aLoadCmaps is true, also load
+ // their character maps (rather than leaving this to be done lazily).
+ // Note that even when aFamily->IsInitialized() is true, it can make sense
+ // to call InitializeFamily again if passing aLoadCmaps=true, in order to
+ // ensure cmaps are loaded.
+ [[nodiscard]] bool InitializeFamily(mozilla::fontlist::Family* aFamily,
+ bool aLoadCmaps = false);
+ void InitializeFamily(uint32_t aGeneration, uint32_t aFamilyIndex,
+ bool aLoadCmaps);
+
+ // name lookup table methods
+
+ void AddOtherFamilyNames(gfxFontFamily* aFamilyEntry,
+ const nsTArray<nsCString>& aOtherFamilyNames);
+
+ void AddFullname(gfxFontEntry* aFontEntry, const nsCString& aFullname) {
+ AutoLock lock(mLock);
+ AddFullnameLocked(aFontEntry, aFullname);
+ }
+ void AddFullnameLocked(gfxFontEntry* aFontEntry, const nsCString& aFullname)
+ MOZ_REQUIRES(mLock);
+
+ void AddPostscriptName(gfxFontEntry* aFontEntry,
+ const nsCString& aPostscriptName) {
+ AutoLock lock(mLock);
+ AddPostscriptNameLocked(aFontEntry, aPostscriptName);
+ }
+ void AddPostscriptNameLocked(gfxFontEntry* aFontEntry,
+ const nsCString& aPostscriptName)
+ MOZ_REQUIRES(mLock);
+
+ bool NeedFullnamePostscriptNames() { return mExtraNames != nullptr; }
+
+ /**
+ * Read PSName and FullName of the given face, for src:local lookup,
+ * returning true if actually implemented and succeeded.
+ */
+ virtual bool ReadFaceNames(const mozilla::fontlist::Family* aFamily,
+ const mozilla::fontlist::Face* aFace,
+ nsCString& aPSName, nsCString& aFullName) {
+ return false;
+ }
+
+ // initialize localized family names
+ bool InitOtherFamilyNames(bool aDeferOtherFamilyNamesLoading);
+ bool InitOtherFamilyNames(uint32_t aGeneration, bool aDefer);
+
+ // pure virtual functions, to be provided by concrete subclasses
+
+ // get the system default font family
+ FontFamily GetDefaultFont(nsPresContext* aPresContext,
+ const gfxFontStyle* aStyle);
+ FontFamily GetDefaultFontLocked(nsPresContext* aPresContext,
+ const gfxFontStyle* aStyle)
+ MOZ_REQUIRES(mLock);
+
+ // get the "ultimate" default font, for use if the font list is otherwise
+ // unusable (e.g. in the middle of being updated)
+ gfxFontEntry* GetDefaultFontEntry() {
+ AutoLock lock(mLock);
+ return mDefaultFontEntry.get();
+ }
+
+ /**
+ * Look up a font by name on the host platform.
+ *
+ * Note that the style attributes (weight, stretch, style) are NOT used in
+ * selecting the platform font, which is looked up by name only; these are
+ * values to be recorded in the new font entry.
+ */
+ virtual gfxFontEntry* LookupLocalFont(nsPresContext* aPresContext,
+ const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry) = 0;
+
+ /**
+ * Create a new platform font from downloaded data (@font-face).
+ *
+ * Note that the style attributes (weight, stretch, style) are NOT related
+ * (necessarily) to any values within the font resource itself; these are
+ * values to be recorded in the new font entry and used for face selection,
+ * in place of whatever inherent style attributes the resource may have.
+ *
+ * This method takes ownership of the data block passed in as aFontData,
+ * and must ensure it is free()'d when no longer required.
+ */
+ virtual gfxFontEntry* MakePlatformFont(const nsACString& aFontName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry,
+ const uint8_t* aFontData,
+ uint32_t aLength) = 0;
+
+ // get the standard family name on the platform for a given font name
+ // (platforms may override, eg Mac)
+ virtual bool GetStandardFamilyName(const nsCString& aFontName,
+ nsACString& aFamilyName);
+
+ // Get the localized family name for a given font family.
+ bool GetLocalizedFamilyName(const FontFamily& aFamily,
+ nsACString& aFamilyName);
+
+ // get the default font name which is available on the system from
+ // font.name-list.*. if there are no available fonts in the pref,
+ // returns an empty FamilyAndGeneric record.
+ FamilyAndGeneric GetDefaultFontFamily(const nsACString& aLangGroup,
+ const nsACString& aGenericFamily);
+
+ virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const;
+ virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const;
+
+ mozilla::fontlist::Pointer GetShmemCharMap(const gfxSparseBitSet* aCmap) {
+ AutoLock lock(mLock);
+ return GetShmemCharMapLocked(aCmap);
+ }
+ mozilla::fontlist::Pointer GetShmemCharMapLocked(const gfxSparseBitSet* aCmap)
+ MOZ_REQUIRES(mLock);
+
+ // Search for existing cmap that matches the input; return the input if no
+ // match is found.
+ already_AddRefed<gfxCharacterMap> FindCharMap(gfxCharacterMap* aCmap);
+
+ // Remove the cmap from the shared cmap set if it holds the only remaining
+ // reference to the object.
+ void MaybeRemoveCmap(gfxCharacterMap* aCharMap);
+
+ // Keep track of userfont sets to notify when global fontlist changes occur.
+ void AddUserFontSet(gfxUserFontSet* aUserFontSet) {
+ AutoLock lock(mLock);
+ mUserFontSetList.Insert(aUserFontSet);
+ }
+
+ void RemoveUserFontSet(gfxUserFontSet* aUserFontSet) {
+ AutoLock lock(mLock);
+ mUserFontSetList.Remove(aUserFontSet);
+ }
+
+ static const gfxFontEntry::ScriptRange sComplexScriptRanges[];
+
+ void GetFontlistInitInfo(uint32_t& aNumInits, uint32_t& aLoaderState) {
+ aNumInits = mFontlistInitCount;
+ aLoaderState = (uint32_t)mState;
+ }
+
+ virtual void AddGenericFonts(nsPresContext* aPresContext,
+ mozilla::StyleGenericFontFamily aGenericType,
+ nsAtom* aLanguage,
+ nsTArray<FamilyAndGeneric>& aFamilyList);
+
+ /**
+ * Given a Face from the shared font list, return a gfxFontEntry usable
+ * by the current process. This returns a cached entry if available,
+ * otherwise it calls the (platform-specific) CreateFontEntry method to
+ * make one, and adds it to the cache.
+ */
+ gfxFontEntry* GetOrCreateFontEntry(mozilla::fontlist::Face* aFace,
+ const mozilla::fontlist::Family* aFamily) {
+ AutoLock lock(mLock);
+ return GetOrCreateFontEntryLocked(aFace, aFamily);
+ }
+ gfxFontEntry* GetOrCreateFontEntryLocked(
+ mozilla::fontlist::Face* aFace, const mozilla::fontlist::Family* aFamily)
+ MOZ_REQUIRES(mLock);
+
+ const FontPrefs* GetFontPrefs() const MOZ_REQUIRES(mLock) {
+ return mFontPrefs.get();
+ }
+
+ bool EmojiPrefHasUserValue() const {
+ AutoLock lock(mLock);
+ return mFontPrefs->EmojiHasUserValue();
+ }
+
+ PrefFontList* GetPrefFontsLangGroup(
+ nsPresContext* aPresContext, mozilla::StyleGenericFontFamily aGenericType,
+ eFontPrefLang aPrefLang) {
+ AutoLock lock(mLock);
+ return GetPrefFontsLangGroupLocked(aPresContext, aGenericType, aPrefLang);
+ }
+ PrefFontList* GetPrefFontsLangGroupLocked(
+ nsPresContext* aPresContext, mozilla::StyleGenericFontFamily aGenericType,
+ eFontPrefLang aPrefLang) MOZ_REQUIRES(mLock);
+
+ // in some situations, need to make decisions about ambiguous characters, may
+ // need to look at multiple pref langs
+ void GetLangPrefs(eFontPrefLang aPrefLangs[], uint32_t& aLen,
+ eFontPrefLang aCharLang, eFontPrefLang aPageLang);
+
+ // convert a lang group to enum constant (i.e. "zh-TW" ==>
+ // eFontPrefLang_ChineseTW)
+ static eFontPrefLang GetFontPrefLangFor(const char* aLang);
+
+ // convert a lang group atom to enum constant
+ static eFontPrefLang GetFontPrefLangFor(nsAtom* aLang);
+
+ // convert an enum constant to a lang group atom
+ static nsAtom* GetLangGroupForPrefLang(eFontPrefLang aLang);
+
+ // convert a enum constant to lang group string (i.e. eFontPrefLang_ChineseTW
+ // ==> "zh-TW")
+ static const char* GetPrefLangName(eFontPrefLang aLang);
+
+ // map a char code to a font language for Preferences
+ static eFontPrefLang GetFontPrefLangFor(uint32_t aCh);
+
+ // returns true if a pref lang is CJK
+ static bool IsLangCJK(eFontPrefLang aLang);
+
+ // helper method to add a pref lang to an array, if not already in array
+ static void AppendPrefLang(eFontPrefLang aPrefLangs[], uint32_t& aLen,
+ eFontPrefLang aAddLang);
+
+ // default serif/sans-serif choice based on font.default.xxx prefs
+ mozilla::StyleGenericFontFamily GetDefaultGeneric(eFontPrefLang aLang);
+
+ // Returns true if the font family whitelist is not empty. In this case we
+ // ignore the "CSS visibility level"; only the given fonts are present in
+ // the browser's font list.
+ bool IsFontFamilyWhitelistActive() const {
+ return mFontFamilyWhitelistActive;
+ };
+
+ static void FontWhitelistPrefChanged(const char* aPref, void* aClosure);
+
+ bool AddWithLegacyFamilyName(const nsACString& aLegacyName,
+ gfxFontEntry* aFontEntry,
+ FontVisibility aVisibility);
+
+ static const char* GetGenericName(
+ mozilla::StyleGenericFontFamily aGenericType);
+
+ bool SkipFontFallbackForChar(FontVisibility aVisibility, uint32_t aCh) const {
+ AutoLock lock(mLock);
+ return mCodepointsWithNoFonts[aVisibility].test(aCh);
+ }
+
+ // Return whether the given font-family record should be visible to CSS,
+ // in a context with the given FontVisibility setting.
+ bool IsVisibleToCSS(const gfxFontFamily& aFamily,
+ FontVisibility aVisibility) const;
+ bool IsVisibleToCSS(const mozilla::fontlist::Family& aFamily,
+ FontVisibility aVisibility) const;
+
+ // (Re-)initialize the set of codepoints that we know cannot be rendered.
+ void InitializeCodepointsWithNoFonts() MOZ_REQUIRES(mLock);
+
+ // If using the shared font list, returns a generation count that is
+ // incremented if/when the platform list is reinitialized (e.g. because
+ // fonts are installed/removed while the browser is running), such that
+ // existing references to shared font family or face objects and character
+ // maps will no longer be valid.
+ // (The legacy (non-shared) list just returns 0 here.)
+ uint32_t GetGeneration() const;
+
+ // Sometimes we need to know if we're on the InitFontList startup thread.
+ static bool IsInitFontListThread() {
+ return PR_GetCurrentThread() == sInitFontListThread;
+ }
+
+ bool IsKnownIconFontFamily(const nsAtom* aFamilyName) const;
+ void LoadIconFontOverrideList();
+
+ void Lock() MOZ_CAPABILITY_ACQUIRE(mLock) { mLock.Lock(); }
+ void Unlock() MOZ_CAPABILITY_RELEASE(mLock) { mLock.Unlock(); }
+
+ // This is only public because some external callers want to be able to
+ // assert about the locked status.
+ mutable mozilla::RecursiveMutex mLock;
+
+ protected:
+ friend class mozilla::fontlist::FontList;
+ friend class InitOtherFamilyNamesForStylo;
+
+ template <size_t N>
+ static bool FamilyInList(const nsACString& aName, const char* (&aList)[N]) {
+ return FamilyInList(aName, aList, N);
+ }
+ static bool FamilyInList(const nsACString& aName, const char* aList[],
+ size_t aCount);
+
+ // Check list is correctly sorted (in debug build only; no-op on release).
+ template <size_t N>
+ static void CheckFamilyList(const char* (&aList)[N]) {
+ CheckFamilyList(aList, N);
+ }
+ static void CheckFamilyList(const char* aList[], size_t aCount);
+
+ class InitOtherFamilyNamesRunnable : public mozilla::CancelableRunnable {
+ public:
+ InitOtherFamilyNamesRunnable()
+ : CancelableRunnable(
+ "gfxPlatformFontList::InitOtherFamilyNamesRunnable"),
+ mIsCanceled(false) {}
+
+ NS_IMETHOD Run() override {
+ if (mIsCanceled) {
+ return NS_OK;
+ }
+
+ gfxPlatformFontList* fontList = gfxPlatformFontList::PlatformFontList();
+ if (!fontList) {
+ return NS_OK;
+ }
+
+ fontList->InitOtherFamilyNamesInternal(true);
+
+ return NS_OK;
+ }
+
+ nsresult Cancel() override {
+ mIsCanceled = true;
+
+ return NS_OK;
+ }
+
+ private:
+ bool mIsCanceled;
+ };
+
+ class MemoryReporter final : public nsIMemoryReporter {
+ ~MemoryReporter() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+ };
+
+ class PrefName final : public nsAutoCString {
+ void Init(const nsACString& aGeneric, const nsACString& aLangGroup) {
+ Assign(aGeneric);
+ if (!aLangGroup.IsEmpty()) {
+ Append('.');
+ Append(aLangGroup);
+ }
+ }
+
+ public:
+ PrefName(const nsACString& aGeneric, const nsACString& aLangGroup) {
+ Init(aGeneric, aLangGroup);
+ }
+
+ PrefName(const char* aGeneric, const char* aLangGroup) {
+ Init(nsDependentCString(aGeneric), nsDependentCString(aLangGroup));
+ }
+
+ PrefName(const char* aGeneric, nsAtom* aLangGroup) {
+ if (aLangGroup) {
+ Init(nsDependentCString(aGeneric), nsAtomCString(aLangGroup));
+ } else {
+ Init(nsDependentCString(aGeneric), nsAutoCString());
+ }
+ }
+ };
+
+ explicit gfxPlatformFontList(bool aNeedFullnamePostscriptNames = true);
+
+ static gfxPlatformFontList* sPlatformFontList;
+
+ /**
+ * Convenience method to return the first matching family (if any) as found
+ * by FindAndAddFamilies(). The family will be initialized (synchronously)
+ * if this has not already been done, so the returned pointer, if non-null,
+ * is ready for use.
+ */
+ mozilla::fontlist::Family* FindSharedFamily(
+ nsPresContext* aPresContext, const nsACString& aFamily,
+ FindFamiliesFlags aFlags = FindFamiliesFlags(0),
+ gfxFontStyle* aStyle = nullptr, nsAtom* aLanguage = nullptr,
+ gfxFloat aDevToCssSize = 1.0) MOZ_REQUIRES(mLock);
+
+ gfxFontFamily* FindUnsharedFamily(
+ nsPresContext* aPresContext, const nsACString& aFamily,
+ FindFamiliesFlags aFlags = FindFamiliesFlags(0),
+ gfxFontStyle* aStyle = nullptr, nsAtom* aLanguage = nullptr,
+ gfxFloat aDevToCssSize = 1.0) MOZ_REQUIRES(mLock) {
+ if (SharedFontList()) {
+ return nullptr;
+ }
+ AutoTArray<FamilyAndGeneric, 1> families;
+ if (FindAndAddFamiliesLocked(
+ aPresContext, mozilla::StyleGenericFontFamily::None, aFamily,
+ &families, aFlags, aStyle, aLanguage, aDevToCssSize)) {
+ return families[0].mFamily.mUnshared;
+ }
+ return nullptr;
+ }
+
+ FontFamily FindFamily(nsPresContext* aPresContext, const nsACString& aFamily,
+ FindFamiliesFlags aFlags = FindFamiliesFlags(0),
+ gfxFontStyle* aStyle = nullptr,
+ nsAtom* aLanguage = nullptr,
+ gfxFloat aDevToCssSize = 1.0) MOZ_REQUIRES(mLock) {
+ if (SharedFontList()) {
+ return FontFamily(FindSharedFamily(aPresContext, aFamily, aFlags, aStyle,
+ aLanguage, aDevToCssSize));
+ }
+ return FontFamily(FindUnsharedFamily(aPresContext, aFamily, aFlags, aStyle,
+ aLanguage, aDevToCssSize));
+ }
+
+ // Lookup family name in global family list without substitutions or
+ // localized family name lookup. Used for common font fallback families.
+ gfxFontFamily* FindFamilyByCanonicalName(const nsACString& aFamily)
+ MOZ_REQUIRES(mLock) {
+ nsAutoCString key;
+ gfxFontFamily* familyEntry;
+ GenerateFontListKey(aFamily, key);
+ if ((familyEntry = mFontFamilies.GetWeak(key))) {
+ return CheckFamily(familyEntry);
+ }
+ return nullptr;
+ }
+
+ // returns default font for a given character, null otherwise
+ already_AddRefed<gfxFont> CommonFontFallback(nsPresContext* aPresContext,
+ uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript,
+ eFontPresentation aPresentation,
+ const gfxFontStyle* aMatchStyle,
+ FontFamily& aMatchedFamily)
+ MOZ_REQUIRES(mLock);
+
+ // Search fonts system-wide for a given character, null if not found.
+ already_AddRefed<gfxFont> GlobalFontFallback(
+ nsPresContext* aPresContext, uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript, eFontPresentation aPresentation,
+ const gfxFontStyle* aMatchStyle, uint32_t& aCmapCount,
+ FontFamily& aMatchedFamily) MOZ_REQUIRES(mLock);
+
+ // Platform-specific implementation of global font fallback, if any;
+ // this may return nullptr in which case the default cmap-based fallback
+ // will be performed.
+ virtual gfxFontEntry* PlatformGlobalFontFallback(
+ nsPresContext* aPresContext, const uint32_t aCh, Script aRunScript,
+ const gfxFontStyle* aMatchStyle, FontFamily& aMatchedFamily) {
+ return nullptr;
+ }
+
+ // whether system-based font fallback is used or not
+ // if system fallback is used, no need to load all cmaps
+ virtual bool UsesSystemFallback() { return false; }
+
+ void AppendCJKPrefLangs(eFontPrefLang aPrefLangs[], uint32_t& aLen,
+ eFontPrefLang aCharLang, eFontPrefLang aPageLang)
+ MOZ_REQUIRES(mLock);
+
+ // verifies that a family contains a non-zero font count
+ gfxFontFamily* CheckFamily(gfxFontFamily* aFamily) MOZ_REQUIRES(mLock);
+
+ // initialize localized family names
+ void InitOtherFamilyNamesInternal(bool aDeferOtherFamilyNamesLoading);
+ void CancelInitOtherFamilyNamesTask();
+
+ void AddToMissedNames(const nsCString& aKey) MOZ_REQUIRES(mLock);
+
+ // search through font families, looking for a given name, initializing
+ // facename lists along the way. first checks all families with names
+ // close to face name, then searchs all families if not found.
+ gfxFontEntry* SearchFamiliesForFaceName(const nsACString& aFaceName)
+ MOZ_REQUIRES(mLock);
+
+ // helper method for finding fullname/postscript names in facename lists
+ gfxFontEntry* FindFaceName(const nsACString& aFaceName) MOZ_REQUIRES(mLock);
+
+ // look up a font by name, for cases where platform font list
+ // maintains explicit mappings of fullname/psname ==> font
+ virtual gfxFontEntry* LookupInFaceNameLists(const nsACString& aFaceName)
+ MOZ_REQUIRES(mLock);
+
+ gfxFontEntry* LookupInSharedFaceNameList(nsPresContext* aPresContext,
+ const nsACString& aFaceName,
+ WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry)
+ MOZ_REQUIRES(mLock);
+
+ // load the bad underline blocklist from pref.
+ void LoadBadUnderlineList();
+
+ void GenerateFontListKey(const nsACString& aKeyName, nsACString& aResult);
+
+ virtual void GetFontFamilyNames(nsTArray<nsCString>& aFontFamilyNames)
+ MOZ_REQUIRES(mLock);
+
+ // helper function to map lang to lang group
+ nsAtom* GetLangGroup(nsAtom* aLanguage);
+
+ // gfxFontInfoLoader overrides, used to load in font cmaps
+ void InitLoader() MOZ_REQUIRES(mLock) override;
+ bool LoadFontInfo() override;
+ void CleanupLoader() override;
+
+ void ForceGlobalReflowLocked(
+ gfxPlatform::NeedsReframe aNeedsReframe,
+ gfxPlatform::BroadcastToChildren aBroadcastToChildren =
+ gfxPlatform::BroadcastToChildren::Yes) MOZ_REQUIRES(mLock);
+
+ // read the loader initialization prefs, and start it
+ void GetPrefsAndStartLoader();
+
+ // If aForgetLocalFaces is true, all gfxFontEntries for src:local fonts must
+ // be discarded (not potentially reused to satisfy the rebuilt rules),
+ // because they may no longer be valid.
+ void RebuildLocalFonts(bool aForgetLocalFaces = false) MOZ_REQUIRES(mLock);
+
+ void ResolveGenericFontNames(nsPresContext* aPresContext,
+ mozilla::StyleGenericFontFamily aGenericType,
+ eFontPrefLang aPrefLang,
+ PrefFontList* aGenericFamilies)
+ MOZ_REQUIRES(mLock);
+
+ void ResolveEmojiFontNames(nsPresContext* aPresContext,
+ PrefFontList* aGenericFamilies)
+ MOZ_REQUIRES(mLock);
+
+ void GetFontFamiliesFromGenericFamilies(
+ nsPresContext* aPresContext, mozilla::StyleGenericFontFamily aGenericType,
+ nsTArray<nsCString>& aGenericNameFamilies, nsAtom* aLangGroup,
+ PrefFontList* aFontFamilies) MOZ_REQUIRES(mLock);
+
+ virtual nsresult InitFontListForPlatform() MOZ_REQUIRES(mLock) = 0;
+ virtual void InitSharedFontListForPlatform() MOZ_REQUIRES(mLock) {}
+
+ virtual gfxFontEntry* CreateFontEntry(
+ mozilla::fontlist::Face* aFace,
+ const mozilla::fontlist::Family* aFamily) {
+ return nullptr;
+ }
+
+ /**
+ * Methods to apply the font.system.whitelist anti-fingerprinting pref,
+ * by filtering the list of installed fonts so that only whitelisted families
+ * are exposed.
+ * There are separate implementations of this for the per-process font list
+ * and for the shared-memory font list.
+ */
+ void ApplyWhitelist() MOZ_REQUIRES(mLock);
+ void ApplyWhitelist(nsTArray<mozilla::fontlist::Family::InitData>& aFamilies);
+
+ // Create a new gfxFontFamily of the appropriate subclass for the platform,
+ // used when AddWithLegacyFamilyName needs to create a new family.
+ virtual gfxFontFamily* CreateFontFamily(const nsACString& aName,
+ FontVisibility aVisibility) const = 0;
+
+ /**
+ * For the post-startup font info loader task.
+ * Perform platform-specific work to read alternate names (if any) for a
+ * font family, recording them in mAliasTable. Once alternate names have been
+ * loaded for all families, the accumulated records are stored in the shared
+ * font list's mAliases list.
+ * Some platforms (currently Linux/fontconfig) may load alternate names as
+ * part of initially populating the font list with family records, in which
+ * case this method is unused.
+ */
+ virtual void ReadFaceNamesForFamily(mozilla::fontlist::Family* aFamily,
+ bool aNeedFullnamePostscriptNames)
+ MOZ_REQUIRES(mLock) {}
+
+ typedef nsRefPtrHashtable<nsCStringHashKey, gfxFontFamily> FontFamilyTable;
+ typedef nsRefPtrHashtable<nsCStringHashKey, gfxFontEntry> FontEntryTable;
+
+ // used by memory reporter to accumulate sizes of family names in the table
+ static size_t SizeOfFontFamilyTableExcludingThis(
+ const FontFamilyTable& aTable, mozilla::MallocSizeOf aMallocSizeOf);
+ static size_t SizeOfFontEntryTableExcludingThis(
+ const FontEntryTable& aTable, mozilla::MallocSizeOf aMallocSizeOf);
+
+ // Platform-specific helper for GetDefaultFont(...).
+ virtual FontFamily GetDefaultFontForPlatform(nsPresContext* aPresContext,
+ const gfxFontStyle* aStyle,
+ nsAtom* aLanguage = nullptr)
+ MOZ_REQUIRES(mLock) = 0;
+
+ // canonical family name ==> family entry (unique, one name per family entry)
+ FontFamilyTable mFontFamilies MOZ_GUARDED_BY(mLock);
+
+ // other family name ==> family entry (not unique, can have multiple names per
+ // family entry, only names *other* than the canonical names are stored here)
+ FontFamilyTable mOtherFamilyNames MOZ_GUARDED_BY(mLock);
+
+ // flag set after InitOtherFamilyNames is called upon first name lookup miss
+ mozilla::Atomic<bool> mOtherFamilyNamesInitialized;
+
+ // The pending InitOtherFamilyNames() task.
+ RefPtr<mozilla::CancelableRunnable> mPendingOtherFamilyNameTask;
+
+ // flag set after fullname and Postcript name lists are populated
+ mozilla::Atomic<bool> mFaceNameListsInitialized;
+
+ struct ExtraNames {
+ ExtraNames() = default;
+
+ // fullname ==> font entry (unique, one name per font entry)
+ FontEntryTable mFullnames{64};
+ // Postscript name ==> font entry (unique, one name per font entry)
+ FontEntryTable mPostscriptNames{64};
+ };
+ // The lock is needed to guard access to the actual name tables, but does not
+ // need to be held to just test whether mExtraNames is non-null as it is set
+ // during initialization before other threads have a chance to see it.
+ mozilla::UniquePtr<ExtraNames> mExtraNames MOZ_PT_GUARDED_BY(mLock);
+
+ // face names missed when face name loading takes a long time
+ mozilla::UniquePtr<nsTHashSet<nsCString>> mFaceNamesMissed
+ MOZ_GUARDED_BY(mLock);
+
+ // localized family names missed when face name loading takes a long time
+ mozilla::UniquePtr<nsTHashSet<nsCString>> mOtherNamesMissed
+ MOZ_GUARDED_BY(mLock);
+
+ typedef mozilla::RangedArray<mozilla::UniquePtr<PrefFontList>,
+ size_t(mozilla::StyleGenericFontFamily::None),
+ size_t(
+ mozilla::StyleGenericFontFamily::MozEmoji)>
+ PrefFontsForLangGroup;
+ mozilla::RangedArray<PrefFontsForLangGroup, eFontPrefLang_First,
+ eFontPrefLang_Count>
+ mLangGroupPrefFonts MOZ_GUARDED_BY(mLock);
+ mozilla::UniquePtr<PrefFontList> mEmojiPrefFont MOZ_GUARDED_BY(mLock);
+
+ // When system-wide font lookup fails for a character, cache it to skip future
+ // searches. This is an array of bitsets, one for each FontVisibility level.
+ mozilla::EnumeratedArray<FontVisibility, FontVisibility::Count,
+ gfxSparseBitSet>
+ mCodepointsWithNoFonts MOZ_GUARDED_BY(mLock);
+
+ // the family to use for U+FFFD fallback, to avoid expensive search every time
+ // on pages with lots of problems
+ mozilla::EnumeratedArray<FontVisibility, FontVisibility::Count, FontFamily>
+ mReplacementCharFallbackFamily MOZ_GUARDED_BY(mLock);
+
+ // Sorted array of lowercased family names; use ContainsSorted to test
+ nsTArray<nsCString> mBadUnderlineFamilyNames;
+
+ // character map data shared across families
+ // contains weak ptrs to cmaps shared by font entry objects
+ nsTHashtable<CharMapHashKey> mSharedCmaps MOZ_GUARDED_BY(mLock);
+
+ nsTHashtable<ShmemCharMapHashEntry> mShmemCharMaps MOZ_GUARDED_BY(mLock);
+
+ // data used as part of the font cmap loading process
+ nsTArray<RefPtr<gfxFontFamily>> mFontFamiliesToLoad MOZ_GUARDED_BY(mLock);
+ uint32_t mStartIndex MOZ_GUARDED_BY(mLock) = 0;
+ uint32_t mNumFamilies MOZ_GUARDED_BY(mLock) = 0;
+
+ // xxx - info for diagnosing no default font aborts
+ // see bugs 636957, 1070983, 1189129
+ uint32_t mFontlistInitCount = 0; // num times InitFontList called
+
+ nsTHashSet<gfxUserFontSet*> mUserFontSetList MOZ_GUARDED_BY(mLock);
+
+ nsLanguageAtomService* mLangService = nullptr;
+
+ nsTArray<uint32_t> mCJKPrefLangs MOZ_GUARDED_BY(mLock);
+ nsTArray<mozilla::StyleGenericFontFamily> mDefaultGenericsLangGroup
+ MOZ_GUARDED_BY(mLock);
+
+ nsTArray<nsCString> mEnabledFontsList;
+ nsTHashSet<nsCString> mIconFontsSet;
+
+ mozilla::UniquePtr<mozilla::fontlist::FontList> mSharedFontList;
+
+ nsClassHashtable<nsCStringHashKey, mozilla::fontlist::AliasData> mAliasTable;
+ nsTHashMap<nsCStringHashKey, mozilla::fontlist::LocalFaceRec::InitData>
+ mLocalNameTable;
+
+ nsRefPtrHashtable<nsPtrHashKey<const mozilla::fontlist::Face>, gfxFontEntry>
+ mFontEntries MOZ_GUARDED_BY(mLock);
+
+ mozilla::UniquePtr<FontPrefs> mFontPrefs;
+
+ RefPtr<gfxFontEntry> mDefaultFontEntry MOZ_GUARDED_BY(mLock);
+
+ RefPtr<mozilla::CancelableRunnable> mLoadCmapsRunnable;
+ uint32_t mStartedLoadingCmapsFrom MOZ_GUARDED_BY(mLock) = 0xffffffffu;
+
+ bool mFontFamilyWhitelistActive = false;
+
+ static PRThread* sInitFontListThread;
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(gfxPlatformFontList::FindFamiliesFlags)
+
+#endif /* GFXPLATFORMFONTLIST_H_ */
diff --git a/gfx/thebes/gfxPlatformGtk.cpp b/gfx/thebes/gfxPlatformGtk.cpp
new file mode 100644
index 0000000000..ee2362c73c
--- /dev/null
+++ b/gfx/thebes/gfxPlatformGtk.cpp
@@ -0,0 +1,1031 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define PANGO_ENABLE_BACKEND
+#define PANGO_ENABLE_ENGINE
+
+#include "gfxPlatformGtk.h"
+
+#include <gtk/gtk.h>
+#include <fontconfig/fontconfig.h>
+
+#include "base/task.h"
+#include "base/thread.h"
+#include "base/message_loop.h"
+#include "cairo.h"
+#include "gfx2DGlue.h"
+#include "gfxFcPlatformFontList.h"
+#include "gfxConfig.h"
+#include "gfxContext.h"
+#include "gfxImageSurface.h"
+#include "gfxUserFontSet.h"
+#include "gfxUtils.h"
+#include "gfxFT2FontBase.h"
+#include "gfxTextRun.h"
+#include "GLContextProvider.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Components.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/StaticPrefs_layers.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "nsAppRunner.h"
+#include "nsIGfxInfo.h"
+#include "nsMathUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsUnicodeProperties.h"
+#include "prenv.h"
+#include "VsyncSource.h"
+#include "mozilla/WidgetUtilsGtk.h"
+
+#ifdef MOZ_X11
+# include "mozilla/gfx/XlibDisplay.h"
+# include <gdk/gdkx.h>
+# include <X11/extensions/Xrandr.h>
+# include "cairo-xlib.h"
+# include "gfxXlibSurface.h"
+# include "GLContextGLX.h"
+# include "GLXLibrary.h"
+# include "mozilla/X11Util.h"
+# include "SoftwareVsyncSource.h"
+
+/* Undefine the Status from Xlib since it will conflict with system headers on
+ * OSX */
+# if defined(__APPLE__) && defined(Status)
+# undef Status
+# endif
+#endif /* MOZ_X11 */
+
+#ifdef MOZ_WAYLAND
+# include <gdk/gdkwayland.h>
+# include "mozilla/widget/nsWaylandDisplay.h"
+# include "mozilla/widget/DMABufLibWrapper.h"
+# include "mozilla/StaticPrefs_widget.h"
+#endif
+
+#define GDK_PIXMAP_SIZE_MAX 32767
+
+#define GFX_PREF_MAX_GENERIC_SUBSTITUTIONS \
+ "gfx.font_rendering.fontconfig.max_generic_substitutions"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::unicode;
+using namespace mozilla::widget;
+
+static FT_Library gPlatformFTLibrary = nullptr;
+static int32_t sDPI;
+
+static void screen_resolution_changed(GdkScreen* aScreen, GParamSpec* aPspec,
+ gpointer aClosure) {
+ sDPI = 0;
+}
+
+#if defined(MOZ_X11)
+// TODO(aosmond): The envvar is deprecated. We should remove it once EGL is the
+// default in release.
+static bool IsX11EGLEnvvarEnabled() {
+ const char* eglPref = PR_GetEnv("MOZ_X11_EGL");
+ return (eglPref && *eglPref);
+}
+#endif
+
+gfxPlatformGtk::gfxPlatformGtk() {
+ if (!gfxPlatform::IsHeadless()) {
+ gtk_init(nullptr, nullptr);
+ }
+
+ mIsX11Display = gfxPlatform::IsHeadless() ? false : GdkIsX11Display();
+ if (XRE_IsParentProcess()) {
+ InitX11EGLConfig();
+ if (IsWaylandDisplay() || gfxConfig::IsEnabled(Feature::X11_EGL)) {
+ gfxVars::SetUseEGL(true);
+ }
+ InitDmabufConfig();
+ if (gfxConfig::IsEnabled(Feature::DMABUF)) {
+ gfxVars::SetUseDMABuf(true);
+ }
+ }
+
+ InitBackendPrefs(GetBackendPrefs());
+
+ gPlatformFTLibrary = Factory::NewFTLibrary();
+ MOZ_RELEASE_ASSERT(gPlatformFTLibrary);
+ Factory::SetFTLibrary(gPlatformFTLibrary);
+
+ GdkScreen* gdkScreen = gdk_screen_get_default();
+ if (gdkScreen) {
+ g_signal_connect(gdkScreen, "notify::resolution",
+ G_CALLBACK(screen_resolution_changed), nullptr);
+ }
+
+ // Bug 1714483: Force disable FXAA Antialiasing on NV drivers. This is a
+ // temporary workaround for a driver bug.
+ PR_SetEnv("__GL_ALLOW_FXAA_USAGE=0");
+}
+
+gfxPlatformGtk::~gfxPlatformGtk() {
+ Factory::ReleaseFTLibrary(gPlatformFTLibrary);
+ gPlatformFTLibrary = nullptr;
+}
+
+void gfxPlatformGtk::InitX11EGLConfig() {
+ FeatureState& feature = gfxConfig::GetFeature(Feature::X11_EGL);
+#ifdef MOZ_X11
+ feature.EnableByDefault();
+
+ if (StaticPrefs::gfx_x11_egl_force_enabled_AtStartup()) {
+ feature.UserForceEnable("Force enabled by pref");
+ } else if (IsX11EGLEnvvarEnabled()) {
+ feature.UserForceEnable("Force enabled by envvar");
+ } else if (StaticPrefs::gfx_x11_egl_force_disabled_AtStartup()) {
+ feature.UserDisable("Force disabled by pref",
+ "FEATURE_FAILURE_USER_FORCE_DISABLED"_ns);
+ }
+
+ nsCString failureId;
+ int32_t status;
+ nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
+ if (NS_FAILED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_X11_EGL,
+ failureId, &status))) {
+ feature.Disable(FeatureStatus::BlockedNoGfxInfo, "gfxInfo is broken",
+ "FEATURE_FAILURE_NO_GFX_INFO"_ns);
+ } else if (status != nsIGfxInfo::FEATURE_STATUS_OK) {
+ feature.Disable(FeatureStatus::Blocklisted, "Blocklisted by gfxInfo",
+ failureId);
+ }
+
+ nsAutoString testType;
+ gfxInfo->GetTestType(testType);
+ // We can only use X11/EGL if we actually found the EGL library and
+ // successfully use it to determine system information in glxtest.
+ if (testType != u"EGL") {
+ feature.ForceDisable(FeatureStatus::Broken, "glxtest could not use EGL",
+ "FEATURE_FAILURE_GLXTEST_NO_EGL"_ns);
+ }
+
+ if (feature.IsEnabled() && IsX11Display()) {
+ // Enabling glthread crashes on X11/EGL, see bug 1670545
+ PR_SetEnv("mesa_glthread=false");
+ }
+#else
+ feature.DisableByDefault(FeatureStatus::Unavailable, "X11 support missing",
+ "FEATURE_FAILURE_NO_X11"_ns);
+#endif
+}
+
+void gfxPlatformGtk::InitDmabufConfig() {
+ FeatureState& feature = gfxConfig::GetFeature(Feature::DMABUF);
+#ifdef MOZ_WAYLAND
+ feature.EnableByDefault();
+
+ if (StaticPrefs::widget_dmabuf_force_enabled_AtStartup()) {
+ feature.UserForceEnable("Force enabled by pref");
+ }
+
+ nsCString failureId;
+ int32_t status;
+ nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
+ if (NS_FAILED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DMABUF, failureId,
+ &status))) {
+ feature.Disable(FeatureStatus::BlockedNoGfxInfo, "gfxInfo is broken",
+ "FEATURE_FAILURE_NO_GFX_INFO"_ns);
+ } else if (status != nsIGfxInfo::FEATURE_STATUS_OK) {
+ feature.Disable(FeatureStatus::Blocklisted, "Blocklisted by gfxInfo",
+ failureId);
+ }
+
+ if (!gfxVars::UseEGL()) {
+ feature.ForceDisable(FeatureStatus::Unavailable, "Requires EGL",
+ "FEATURE_FAILURE_REQUIRES_EGL"_ns);
+ }
+
+ if (feature.IsEnabled()) {
+ nsAutoCString drmRenderDevice;
+ gfxInfo->GetDrmRenderDevice(drmRenderDevice);
+ gfxVars::SetDrmRenderDevice(drmRenderDevice);
+
+ if (!GetDMABufDevice()->IsEnabled(failureId)) {
+ feature.ForceDisable(FeatureStatus::Failed, "Failed to configure",
+ failureId);
+ }
+ }
+#else
+ feature.DisableByDefault(FeatureStatus::Unavailable,
+ "Wayland support missing",
+ "FEATURE_FAILURE_NO_WAYLAND"_ns);
+#endif
+}
+
+bool gfxPlatformGtk::InitVAAPIConfig(bool aForceEnabledByUser) {
+ FeatureState& feature =
+ gfxConfig::GetFeature(Feature::HARDWARE_VIDEO_DECODING);
+ // We're already configured in parent process
+ if (!XRE_IsParentProcess()) {
+ return feature.IsEnabled();
+ }
+#ifdef MOZ_WAYLAND
+ feature.EnableByDefault();
+
+ int32_t status = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+ nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
+ nsCString failureId;
+ if (NS_FAILED(gfxInfo->GetFeatureStatus(
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, failureId, &status))) {
+ feature.Disable(FeatureStatus::BlockedNoGfxInfo, "gfxInfo is broken",
+ "FEATURE_FAILURE_NO_GFX_INFO"_ns);
+ } else if (status == nsIGfxInfo::FEATURE_BLOCKED_PLATFORM_TEST) {
+ feature.ForceDisable(FeatureStatus::Unavailable,
+ "Force disabled by gfxInfo", failureId);
+ } else if (status != nsIGfxInfo::FEATURE_STATUS_OK) {
+ feature.Disable(FeatureStatus::Blocklisted, "Blocklisted by gfxInfo",
+ failureId);
+ }
+ if (aForceEnabledByUser) {
+ feature.UserForceEnable("Force enabled by pref");
+ }
+ if (!gfxVars::UseEGL()) {
+ feature.ForceDisable(FeatureStatus::Unavailable, "Requires EGL",
+ "FEATURE_FAILURE_REQUIRES_EGL"_ns);
+ }
+
+ if (!gfxVars::WebglUseHardware()) {
+ feature.Disable(FeatureStatus::Blocklisted,
+ "DMABuf disabled with software rendering", failureId);
+ }
+
+ // Configure zero-copy playback feature.
+ if (feature.IsEnabled()) {
+ FeatureState& featureZeroCopy =
+ gfxConfig::GetFeature(Feature::HW_DECODED_VIDEO_ZERO_COPY);
+
+ featureZeroCopy.EnableByDefault();
+ uint32_t state =
+ StaticPrefs::media_ffmpeg_vaapi_force_surface_zero_copy_AtStartup();
+ if (state == 0) {
+ featureZeroCopy.UserDisable("Force disable by pref",
+ "FEATURE_FAILURE_USER_FORCE_DISABLED"_ns);
+ } else if (state == 1) {
+ featureZeroCopy.UserEnable("Force enabled by pref");
+ } else {
+ nsCString failureId;
+ int32_t status = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+ nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
+ if (NS_FAILED(gfxInfo->GetFeatureStatus(
+ nsIGfxInfo::FEATURE_HW_DECODED_VIDEO_ZERO_COPY, failureId,
+ &status))) {
+ featureZeroCopy.Disable(FeatureStatus::BlockedNoGfxInfo,
+ "gfxInfo is broken",
+ "FEATURE_FAILURE_NO_GFX_INFO"_ns);
+ } else if (status == nsIGfxInfo::FEATURE_BLOCKED_PLATFORM_TEST) {
+ featureZeroCopy.ForceDisable(FeatureStatus::Unavailable,
+ "Force disabled by gfxInfo", failureId);
+ } else if (status != nsIGfxInfo::FEATURE_ALLOW_ALWAYS) {
+ featureZeroCopy.Disable(FeatureStatus::Blocklisted,
+ "Blocklisted by gfxInfo", failureId);
+ }
+ }
+ if (featureZeroCopy.IsEnabled()) {
+ gfxVars::SetHwDecodedVideoZeroCopy(true);
+ }
+ }
+#else
+ feature.DisableByDefault(FeatureStatus::Unavailable,
+ "Wayland support missing",
+ "FEATURE_FAILURE_NO_WAYLAND"_ns);
+#endif
+ return feature.IsEnabled();
+}
+
+void gfxPlatformGtk::InitWebRenderConfig() {
+ gfxPlatform::InitWebRenderConfig();
+
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ FeatureState& feature = gfxConfig::GetFeature(Feature::WEBRENDER_COMPOSITOR);
+#ifdef RELEASE_OR_BETA
+ feature.ForceDisable(FeatureStatus::Blocked,
+ "Cannot be enabled in release or beta",
+ "FEATURE_FAILURE_DISABLE_RELEASE_OR_BETA"_ns);
+#else
+ if (feature.IsEnabled()) {
+ if (!IsWaylandDisplay()) {
+ feature.ForceDisable(FeatureStatus::Unavailable,
+ "Wayland support missing",
+ "FEATURE_FAILURE_NO_WAYLAND"_ns);
+ }
+# ifdef MOZ_WAYLAND
+ else if (gfxConfig::IsEnabled(Feature::WEBRENDER) &&
+ !gfxConfig::IsEnabled(Feature::DMABUF)) {
+ // We use zwp_linux_dmabuf_v1 and GBM directly to manage FBOs. In theory
+ // this is also possible vie EGLstreams, but we don't bother to implement
+ // it as recent NVidia drivers support GBM and DMABuf as well.
+ feature.ForceDisable(FeatureStatus::Unavailable,
+ "Hardware Webrender requires DMAbuf support",
+ "FEATURE_FAILURE_NO_DMABUF"_ns);
+ } else if (!widget::WaylandDisplayGet()->GetViewporter()) {
+ feature.ForceDisable(FeatureStatus::Unavailable,
+ "Requires wp_viewporter protocol support",
+ "FEATURE_FAILURE_REQUIRES_WPVIEWPORTER"_ns);
+ }
+# endif // MOZ_WAYLAND
+ }
+#endif // RELEASE_OR_BETA
+
+ gfxVars::SetUseWebRenderCompositor(feature.IsEnabled());
+}
+
+void gfxPlatformGtk::InitPlatformGPUProcessPrefs() {
+#ifdef MOZ_WAYLAND
+ if (IsWaylandDisplay()) {
+ FeatureState& gpuProc = gfxConfig::GetFeature(Feature::GPU_PROCESS);
+ gpuProc.ForceDisable(FeatureStatus::Blocked,
+ "Wayland does not work in the GPU process",
+ "FEATURE_FAILURE_WAYLAND"_ns);
+ }
+#endif
+}
+
+already_AddRefed<gfxASurface> gfxPlatformGtk::CreateOffscreenSurface(
+ const IntSize& aSize, gfxImageFormat aFormat) {
+ if (!Factory::AllowedSurfaceSize(aSize)) {
+ return nullptr;
+ }
+
+ RefPtr<gfxASurface> newSurface;
+ bool needsClear = true;
+ // XXX we really need a different interface here, something that passes
+ // in more context, including the display and/or target surface type that
+ // we should try to match
+ GdkScreen* gdkScreen = gdk_screen_get_default();
+ if (gdkScreen) {
+ newSurface = new gfxImageSurface(aSize, aFormat);
+ // The gfxImageSurface ctor zeroes this for us, no need to
+ // waste time clearing again
+ needsClear = false;
+ }
+
+ if (!newSurface) {
+ // We couldn't create a native surface for whatever reason;
+ // e.g., no display, no RENDER, bad size, etc.
+ // Fall back to image surface for the data.
+ newSurface = new gfxImageSurface(aSize, aFormat);
+ }
+
+ if (newSurface->CairoStatus()) {
+ newSurface = nullptr; // surface isn't valid for some reason
+ }
+
+ if (newSurface && needsClear) {
+ gfxUtils::ClearThebesSurface(newSurface);
+ }
+
+ return newSurface.forget();
+}
+
+nsresult gfxPlatformGtk::GetFontList(nsAtom* aLangGroup,
+ const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts) {
+ gfxPlatformFontList::PlatformFontList()->GetFontList(
+ aLangGroup, aGenericFamily, aListOfFonts);
+ return NS_OK;
+}
+
+// xxx - this is ubuntu centric, need to go through other distros and flesh
+// out a more general list
+static const char kFontDejaVuSans[] = "DejaVu Sans";
+static const char kFontDejaVuSerif[] = "DejaVu Serif";
+static const char kFontFreeSans[] = "FreeSans";
+static const char kFontFreeSerif[] = "FreeSerif";
+static const char kFontTakaoPGothic[] = "TakaoPGothic";
+static const char kFontTwemojiMozilla[] = "Twemoji Mozilla";
+static const char kFontDroidSansFallback[] = "Droid Sans Fallback";
+static const char kFontWenQuanYiMicroHei[] = "WenQuanYi Micro Hei";
+static const char kFontNanumGothic[] = "NanumGothic";
+static const char kFontSymbola[] = "Symbola";
+static const char kFontNotoSansSymbols[] = "Noto Sans Symbols";
+static const char kFontNotoSansSymbols2[] = "Noto Sans Symbols2";
+
+void gfxPlatformGtk::GetCommonFallbackFonts(uint32_t aCh, Script aRunScript,
+ eFontPresentation aPresentation,
+ nsTArray<const char*>& aFontList) {
+ if (PrefersColor(aPresentation)) {
+ aFontList.AppendElement(kFontTwemojiMozilla);
+ }
+
+ aFontList.AppendElement(kFontDejaVuSerif);
+ aFontList.AppendElement(kFontFreeSerif);
+ aFontList.AppendElement(kFontDejaVuSans);
+ aFontList.AppendElement(kFontFreeSans);
+ aFontList.AppendElement(kFontSymbola);
+ aFontList.AppendElement(kFontNotoSansSymbols);
+ aFontList.AppendElement(kFontNotoSansSymbols2);
+
+ // add fonts for CJK ranges
+ // xxx - this isn't really correct, should use the same CJK font ordering
+ // as the pref font code
+ if (aCh >= 0x3000 && ((aCh < 0xe000) || (aCh >= 0xf900 && aCh < 0xfff0) ||
+ ((aCh >> 16) == 2))) {
+ aFontList.AppendElement(kFontTakaoPGothic);
+ aFontList.AppendElement(kFontDroidSansFallback);
+ aFontList.AppendElement(kFontWenQuanYiMicroHei);
+ aFontList.AppendElement(kFontNanumGothic);
+ }
+}
+
+void gfxPlatformGtk::ReadSystemFontList(
+ mozilla::dom::SystemFontList* retValue) {
+ gfxFcPlatformFontList::PlatformFontList()->ReadSystemFontList(retValue);
+}
+
+bool gfxPlatformGtk::CreatePlatformFontList() {
+ return gfxPlatformFontList::Initialize(new gfxFcPlatformFontList);
+}
+
+int32_t gfxPlatformGtk::GetFontScaleDPI() {
+ MOZ_ASSERT(XRE_IsParentProcess(),
+ "You can access this via LookAndFeel if you need it in child "
+ "processes");
+ if (MOZ_LIKELY(sDPI != 0)) {
+ return sDPI;
+ }
+ GdkScreen* screen = gdk_screen_get_default();
+ // Ensure settings in config files are processed.
+ gtk_settings_get_for_screen(screen);
+ int32_t dpi = int32_t(round(gdk_screen_get_resolution(screen)));
+ if (dpi <= 0) {
+ // Fall back to something reasonable
+ dpi = 96;
+ }
+ sDPI = dpi;
+ return dpi;
+}
+
+double gfxPlatformGtk::GetFontScaleFactor() {
+ // Integer scale factors work well with GTK window scaling, image scaling, and
+ // pixel alignment, but there is a range where 1 is too small and 2 is too
+ // big.
+ //
+ // An additional step of 1.5 is added because this is common scale on WINNT
+ // and at this ratio the advantages of larger rendering outweigh the
+ // disadvantages from scaling and pixel mis-alignment.
+ //
+ // A similar step for 1.25 is added as well, because this is the scale that
+ // "Large text" settings use in gnome, and it seems worth to allow, especially
+ // on already-hidpi environments.
+ int32_t dpi = GetFontScaleDPI();
+ if (dpi < 120) {
+ return 1.0;
+ }
+ if (dpi < 132) {
+ return 1.25;
+ }
+ if (dpi < 168) {
+ return 1.5;
+ }
+ return round(dpi / 96.0);
+}
+
+gfxImageFormat gfxPlatformGtk::GetOffscreenFormat() {
+ // Make sure there is a screen
+ GdkScreen* screen = gdk_screen_get_default();
+ if (screen && gdk_visual_get_depth(gdk_visual_get_system()) == 16) {
+ return SurfaceFormat::R5G6B5_UINT16;
+ }
+
+ return SurfaceFormat::X8R8G8B8_UINT32;
+}
+
+void gfxPlatformGtk::FontsPrefsChanged(const char* aPref) {
+ // only checking for generic substitions, pass other changes up
+ if (strcmp(GFX_PREF_MAX_GENERIC_SUBSTITUTIONS, aPref) != 0) {
+ gfxPlatform::FontsPrefsChanged(aPref);
+ return;
+ }
+
+ gfxFcPlatformFontList* pfl = gfxFcPlatformFontList::PlatformFontList();
+ pfl->ClearGenericMappings();
+ FlushFontAndWordCaches();
+}
+
+bool gfxPlatformGtk::AccelerateLayersByDefault() { return true; }
+
+#if defined(MOZ_X11)
+
+static nsTArray<uint8_t> GetDisplayICCProfile(Display* dpy, Window& root) {
+ const char kIccProfileAtomName[] = "_ICC_PROFILE";
+ Atom iccAtom = XInternAtom(dpy, kIccProfileAtomName, TRUE);
+ if (!iccAtom) {
+ return nsTArray<uint8_t>();
+ }
+
+ Atom retAtom;
+ int retFormat;
+ unsigned long retLength, retAfter;
+ unsigned char* retProperty;
+
+ if (XGetWindowProperty(dpy, root, iccAtom, 0, INT_MAX /* length */, X11False,
+ AnyPropertyType, &retAtom, &retFormat, &retLength,
+ &retAfter, &retProperty) != Success) {
+ return nsTArray<uint8_t>();
+ }
+
+ nsTArray<uint8_t> result;
+
+ if (retLength > 0) {
+ result.AppendElements(static_cast<uint8_t*>(retProperty), retLength);
+ }
+
+ XFree(retProperty);
+
+ return result;
+}
+
+nsTArray<uint8_t> gfxPlatformGtk::GetPlatformCMSOutputProfileData() {
+ nsTArray<uint8_t> prefProfileData = GetPrefCMSOutputProfileData();
+ if (!prefProfileData.IsEmpty()) {
+ return prefProfileData;
+ }
+
+ if (XRE_IsContentProcess()) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // This will be passed in during InitChild so we can avoid sending a
+ // sync message back to the parent during init.
+ const mozilla::gfx::ContentDeviceData* contentDeviceData =
+ GetInitContentDeviceData();
+ if (contentDeviceData) {
+ // On Windows, we assert that the profile isn't empty, but on
+ // Linux it can legitimately be empty if the display isn't
+ // calibrated. Thus, no assertion here.
+ return contentDeviceData->cmsOutputProfileData().Clone();
+ }
+
+ // Otherwise we need to ask the parent for the updated color profile
+ mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton();
+ nsTArray<uint8_t> result;
+ Unused << cc->SendGetOutputColorProfileData(&result);
+ return result;
+ }
+
+ if (!mIsX11Display) {
+ return nsTArray<uint8_t>();
+ }
+
+ GdkDisplay* display = gdk_display_get_default();
+ Display* dpy = GDK_DISPLAY_XDISPLAY(display);
+ // In xpcshell tests, we never initialize X and hence don't have a Display.
+ // In this case, there's no output colour management to be done, so we just
+ // return with nullptr.
+ if (!dpy) {
+ return nsTArray<uint8_t>();
+ }
+
+ Window root = gdk_x11_get_default_root_xwindow();
+
+ // First try ICC Profile
+ nsTArray<uint8_t> iccResult = GetDisplayICCProfile(dpy, root);
+ if (!iccResult.IsEmpty()) {
+ return iccResult;
+ }
+
+ // If ICC doesn't work, then try EDID
+ const char kEdid1AtomName[] = "XFree86_DDC_EDID1_RAWDATA";
+ Atom edidAtom = XInternAtom(dpy, kEdid1AtomName, TRUE);
+ if (!edidAtom) {
+ return nsTArray<uint8_t>();
+ }
+
+ Atom retAtom;
+ int retFormat;
+ unsigned long retLength, retAfter;
+ unsigned char* retProperty;
+
+ if (XGetWindowProperty(dpy, root, edidAtom, 0, 32, X11False, AnyPropertyType,
+ &retAtom, &retFormat, &retLength, &retAfter,
+ &retProperty) != Success) {
+ return nsTArray<uint8_t>();
+ }
+
+ if (retLength != 128) {
+ return nsTArray<uint8_t>();
+ }
+
+ // Format documented in "VESA E-EDID Implementation Guide"
+ float gamma = (100 + (float)retProperty[0x17]) / 100.0f;
+
+ qcms_CIE_xyY whitePoint;
+ whitePoint.x =
+ ((retProperty[0x21] << 2) | (retProperty[0x1a] >> 2 & 3)) / 1024.0;
+ whitePoint.y =
+ ((retProperty[0x22] << 2) | (retProperty[0x1a] >> 0 & 3)) / 1024.0;
+ whitePoint.Y = 1.0;
+
+ qcms_CIE_xyYTRIPLE primaries;
+ primaries.red.x =
+ ((retProperty[0x1b] << 2) | (retProperty[0x19] >> 6 & 3)) / 1024.0;
+ primaries.red.y =
+ ((retProperty[0x1c] << 2) | (retProperty[0x19] >> 4 & 3)) / 1024.0;
+ primaries.red.Y = 1.0;
+
+ primaries.green.x =
+ ((retProperty[0x1d] << 2) | (retProperty[0x19] >> 2 & 3)) / 1024.0;
+ primaries.green.y =
+ ((retProperty[0x1e] << 2) | (retProperty[0x19] >> 0 & 3)) / 1024.0;
+ primaries.green.Y = 1.0;
+
+ primaries.blue.x =
+ ((retProperty[0x1f] << 2) | (retProperty[0x1a] >> 6 & 3)) / 1024.0;
+ primaries.blue.y =
+ ((retProperty[0x20] << 2) | (retProperty[0x1a] >> 4 & 3)) / 1024.0;
+ primaries.blue.Y = 1.0;
+
+ XFree(retProperty);
+
+ void* mem = nullptr;
+ size_t size = 0;
+ qcms_data_create_rgb_with_gamma(whitePoint, primaries, gamma, &mem, &size);
+ if (!mem) {
+ return nsTArray<uint8_t>();
+ }
+
+ nsTArray<uint8_t> result;
+ result.AppendElements(static_cast<uint8_t*>(mem), size);
+ free(mem);
+
+ // XXX: It seems like we get wrong colors when using this constructed profile:
+ // See bug 1696819. For now just forget that we made it.
+ return nsTArray<uint8_t>();
+}
+
+#else // defined(MOZ_X11)
+
+nsTArray<uint8_t> gfxPlatformGtk::GetPlatformCMSOutputProfileData() {
+ return nsTArray<uint8_t>();
+}
+
+#endif
+
+bool gfxPlatformGtk::CheckVariationFontSupport() {
+ // Although there was some variation/multiple-master support in FreeType
+ // in older versions, it seems too incomplete/unstable for us to use
+ // until at least 2.7.1.
+ FT_Int major, minor, patch;
+ FT_Library_Version(Factory::GetFTLibrary(), &major, &minor, &patch);
+ return major * 1000000 + minor * 1000 + patch >= 2007001;
+}
+
+#ifdef MOZ_X11
+
+class GtkVsyncSource final : public VsyncSource {
+ public:
+ GtkVsyncSource()
+ : mGLContext(nullptr),
+ mXDisplay(nullptr),
+ mSetupLock("GLXVsyncSetupLock"),
+ mVsyncThread("GLXVsyncThread"),
+ mVsyncTask(nullptr),
+ mVsyncEnabledLock("GLXVsyncEnabledLock"),
+ mVsyncEnabled(false) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ virtual ~GtkVsyncSource() { MOZ_ASSERT(NS_IsMainThread()); }
+
+ // Sets up the display's GL context on a worker thread.
+ // Required as GLContexts may only be used by the creating thread.
+ // Returns true if setup was a success.
+ bool Setup() {
+ MonitorAutoLock lock(mSetupLock);
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mVsyncThread.Start()) return false;
+
+ RefPtr<Runnable> vsyncSetup =
+ NewRunnableMethod("GtkVsyncSource::SetupGLContext", this,
+ &GtkVsyncSource::SetupGLContext);
+ mVsyncThread.message_loop()->PostTask(vsyncSetup.forget());
+ // Wait until the setup has completed.
+ lock.Wait();
+ return mGLContext != nullptr;
+ }
+
+ // Called on the Vsync thread to setup the GL context.
+ void SetupGLContext() {
+ MonitorAutoLock lock(mSetupLock);
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!mGLContext, "GLContext already setup!");
+
+ // Create video sync timer on a separate Display to prevent locking the
+ // main thread X display.
+ mXDisplay = XOpenDisplay(nullptr);
+ if (!mXDisplay) {
+ lock.NotifyAll();
+ return;
+ }
+
+ // Most compositors wait for vsync events on the root window.
+ Window root = DefaultRootWindow(mXDisplay);
+ int screen = DefaultScreen(mXDisplay);
+
+ ScopedXFree<GLXFBConfig> cfgs;
+ GLXFBConfig config;
+ int visid;
+ bool forWebRender = false;
+ if (!gl::GLContextGLX::FindFBConfigForWindow(
+ mXDisplay, screen, root, &cfgs, &config, &visid, forWebRender)) {
+ lock.NotifyAll();
+ return;
+ }
+
+ mGLContext = gl::GLContextGLX::CreateGLContext(
+ {}, gfx::XlibDisplay::Borrow(mXDisplay), root, config);
+
+ if (!mGLContext) {
+ lock.NotifyAll();
+ return;
+ }
+
+ mGLContext->MakeCurrent();
+
+ // Test that SGI_video_sync lets us get the counter.
+ unsigned int syncCounter = 0;
+ if (gl::sGLXLibrary.fGetVideoSync(&syncCounter) != 0) {
+ mGLContext = nullptr;
+ }
+
+ lock.NotifyAll();
+ }
+
+ virtual void EnableVsync() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mGLContext, "GLContext not setup!");
+
+ MonitorAutoLock lock(mVsyncEnabledLock);
+ if (mVsyncEnabled) {
+ return;
+ }
+ mVsyncEnabled = true;
+
+ // If the task has not nulled itself out, it hasn't yet realized
+ // that vsync was disabled earlier, so continue its execution.
+ if (!mVsyncTask) {
+ mVsyncTask = NewRunnableMethod("GtkVsyncSource::RunVsync", this,
+ &GtkVsyncSource::RunVsync);
+ RefPtr<Runnable> addrefedTask = mVsyncTask;
+ mVsyncThread.message_loop()->PostTask(addrefedTask.forget());
+ }
+ }
+
+ virtual void DisableVsync() override {
+ MonitorAutoLock lock(mVsyncEnabledLock);
+ mVsyncEnabled = false;
+ }
+
+ virtual bool IsVsyncEnabled() override {
+ MonitorAutoLock lock(mVsyncEnabledLock);
+ return mVsyncEnabled;
+ }
+
+ virtual void Shutdown() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ DisableVsync();
+
+ // Cleanup thread-specific resources before shutting down.
+ RefPtr<Runnable> shutdownTask = NewRunnableMethod(
+ "GtkVsyncSource::Cleanup", this, &GtkVsyncSource::Cleanup);
+ mVsyncThread.message_loop()->PostTask(shutdownTask.forget());
+
+ // Stop, waiting for the cleanup task to finish execution.
+ mVsyncThread.Stop();
+ }
+
+ private:
+ void RunVsync() {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ mGLContext->MakeCurrent();
+
+ unsigned int syncCounter = 0;
+ gl::sGLXLibrary.fGetVideoSync(&syncCounter);
+ for (;;) {
+ {
+ MonitorAutoLock lock(mVsyncEnabledLock);
+ if (!mVsyncEnabled) {
+ mVsyncTask = nullptr;
+ return;
+ }
+ }
+
+ TimeStamp lastVsync = TimeStamp::Now();
+ bool useSoftware = false;
+
+ // Wait until the video sync counter reaches the next value by waiting
+ // until the parity of the counter value changes.
+ unsigned int nextSync = syncCounter + 1;
+ int status;
+ if ((status = gl::sGLXLibrary.fWaitVideoSync(2, (int)nextSync % 2,
+ &syncCounter)) != 0) {
+ gfxWarningOnce() << "glXWaitVideoSync returned " << status;
+ useSoftware = true;
+ }
+
+ if (syncCounter == (nextSync - 1)) {
+ gfxWarningOnce()
+ << "glXWaitVideoSync failed to increment the sync counter.";
+ useSoftware = true;
+ }
+
+ if (useSoftware) {
+ double remaining =
+ (1000.f / 60.f) - (TimeStamp::Now() - lastVsync).ToMilliseconds();
+ if (remaining > 0) {
+ AUTO_PROFILER_THREAD_SLEEP;
+ PlatformThread::Sleep((int)remaining);
+ }
+ }
+
+ lastVsync = TimeStamp::Now();
+ TimeStamp outputTime = lastVsync + GetVsyncRate();
+ NotifyVsync(lastVsync, outputTime);
+ }
+ }
+
+ void Cleanup() {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ mGLContext = nullptr;
+ if (mXDisplay) XCloseDisplay(mXDisplay);
+ }
+
+ // Owned by the vsync thread.
+ RefPtr<gl::GLContextGLX> mGLContext;
+ _XDisplay* mXDisplay;
+ Monitor mSetupLock MOZ_UNANNOTATED;
+ base::Thread mVsyncThread;
+ RefPtr<Runnable> mVsyncTask;
+ Monitor mVsyncEnabledLock MOZ_UNANNOTATED;
+ bool mVsyncEnabled;
+};
+
+class XrandrSoftwareVsyncSource final
+ : public mozilla::gfx::SoftwareVsyncSource {
+ public:
+ XrandrSoftwareVsyncSource() : SoftwareVsyncSource(ComputeVsyncRate()) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ GdkScreen* defaultScreen = gdk_screen_get_default();
+ g_signal_connect(defaultScreen, "monitors-changed",
+ G_CALLBACK(monitors_changed), this);
+ }
+
+ private:
+ // Request the current refresh rate via xrandr. It is hard to find the
+ // "correct" one, thus choose the highest one, assuming this will usually
+ // give the best user experience.
+ static mozilla::TimeDuration ComputeVsyncRate() {
+ struct _XDisplay* dpy = gdk_x11_get_default_xdisplay();
+
+ // Use the default software refresh rate as lower bound. Allowing lower
+ // rates makes a bunch of tests start to fail on CI. The main goal of this
+ // VsyncSource is to support refresh rates greater than the default one.
+ double highestRefreshRate = gfxPlatform::GetSoftwareVsyncRate();
+
+ // When running on remote X11 the xrandr version may be stuck on an
+ // ancient version. There are still setups using remote X11 out there, so
+ // make sure we don't crash.
+ int eventBase, errorBase, major, minor;
+ if (XRRQueryExtension(dpy, &eventBase, &errorBase) &&
+ XRRQueryVersion(dpy, &major, &minor) &&
+ (major > 1 || (major == 1 && minor >= 3))) {
+ Window root = gdk_x11_get_default_root_xwindow();
+ XRRScreenResources* res = XRRGetScreenResourcesCurrent(dpy, root);
+
+ if (res) {
+ // We can't use refresh rates far below the default one (60Hz) because
+ // otherwise random CI tests start to fail. However, many users have
+ // screens just below the default rate, e.g. 59.95Hz. So slightly
+ // decrease the lower bound.
+ highestRefreshRate -= 1.0;
+
+ for (int i = 0; i < res->noutput; i++) {
+ XRROutputInfo* outputInfo =
+ XRRGetOutputInfo(dpy, res, res->outputs[i]);
+ if (outputInfo) {
+ if (outputInfo->crtc) {
+ XRRCrtcInfo* crtcInfo =
+ XRRGetCrtcInfo(dpy, res, outputInfo->crtc);
+ if (crtcInfo) {
+ for (int j = 0; j < res->nmode; j++) {
+ if (res->modes[j].id == crtcInfo->mode) {
+ double refreshRate = mode_refresh(&res->modes[j]);
+ if (refreshRate > highestRefreshRate) {
+ highestRefreshRate = refreshRate;
+ }
+ break;
+ }
+ }
+
+ XRRFreeCrtcInfo(crtcInfo);
+ }
+ }
+
+ XRRFreeOutputInfo(outputInfo);
+ }
+ }
+ }
+ XRRFreeScreenResources(res);
+ }
+
+ const double rate = 1000.0 / highestRefreshRate;
+ return mozilla::TimeDuration::FromMilliseconds(rate);
+ }
+
+ static void monitors_changed(GdkScreen* aScreen, gpointer aClosure) {
+ XrandrSoftwareVsyncSource* self =
+ static_cast<XrandrSoftwareVsyncSource*>(aClosure);
+ self->SetVsyncRate(ComputeVsyncRate());
+ }
+
+ // from xrandr.c
+ static double mode_refresh(const XRRModeInfo* mode_info) {
+ double rate;
+ double vTotal = mode_info->vTotal;
+
+ if (mode_info->modeFlags & RR_DoubleScan) {
+ /* doublescan doubles the number of lines */
+ vTotal *= 2;
+ }
+
+ if (mode_info->modeFlags & RR_Interlace) {
+ /* interlace splits the frame into two fields */
+ /* the field rate is what is typically reported by monitors */
+ vTotal /= 2;
+ }
+
+ if (mode_info->hTotal && vTotal) {
+ rate = ((double)mode_info->dotClock /
+ ((double)mode_info->hTotal * (double)vTotal));
+ } else {
+ rate = 0;
+ }
+ return rate;
+ }
+};
+#endif
+
+already_AddRefed<gfx::VsyncSource>
+gfxPlatformGtk::CreateGlobalHardwareVsyncSource() {
+#ifdef MOZ_X11
+ if (IsHeadless() || IsWaylandDisplay()) {
+ // On Wayland we can not create a global hardware based vsync source, thus
+ // use a software based one here. We create window specific ones later.
+ return GetSoftwareVsyncSource();
+ }
+
+ nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
+ nsString windowProtocol;
+ gfxInfo->GetWindowProtocol(windowProtocol);
+ bool isXwayland = windowProtocol.Find(u"xwayland") != -1;
+ nsString adapterDriverVendor;
+ gfxInfo->GetAdapterDriverVendor(adapterDriverVendor);
+ bool isMesa = adapterDriverVendor.Find(u"mesa") != -1;
+
+ // Only use GLX vsync when the OpenGL compositor / WebRender is being used.
+ // The extra cost of initializing a GLX context while blocking the main thread
+ // is not worth it when using basic composition. Do not use it on Xwayland, as
+ // Xwayland will give us a software timer as we are listening for the root
+ // window, which does not have a Wayland equivalent. Don't call
+ // gl::sGLXLibrary.SupportsVideoSync() when EGL is used as NVIDIA drivers
+ // refuse to use EGL GL context when GLX was initialized first and fail
+ // silently.
+ if (gfxConfig::IsEnabled(Feature::HW_COMPOSITING) && !isXwayland &&
+ (!gfxVars::UseEGL() || isMesa) &&
+ gl::sGLXLibrary.SupportsVideoSync(DefaultXDisplay())) {
+ RefPtr<GtkVsyncSource> vsyncSource = new GtkVsyncSource();
+ if (!vsyncSource->Setup()) {
+ NS_WARNING("Failed to setup GLContext, falling back to software vsync.");
+ return GetSoftwareVsyncSource();
+ }
+ return vsyncSource.forget();
+ }
+
+ RefPtr<VsyncSource> softwareVsync = new XrandrSoftwareVsyncSource();
+ return softwareVsync.forget();
+#else
+ return GetSoftwareVsyncSource();
+#endif
+}
+
+void gfxPlatformGtk::BuildContentDeviceData(ContentDeviceData* aOut) {
+ gfxPlatform::BuildContentDeviceData(aOut);
+
+ aOut->cmsOutputProfileData() = GetPlatformCMSOutputProfileData();
+}
diff --git a/gfx/thebes/gfxPlatformGtk.h b/gfx/thebes/gfxPlatformGtk.h
new file mode 100644
index 0000000000..ffcd5e43e6
--- /dev/null
+++ b/gfx/thebes/gfxPlatformGtk.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_PLATFORM_GTK_H
+#define GFX_PLATFORM_GTK_H
+
+#include "gfxPlatform.h"
+#include "nsAutoRef.h"
+#include "nsTArray.h"
+#include "mozilla/gfx/gfxVars.h"
+
+#ifdef MOZ_X11
+struct _XDisplay;
+typedef struct _XDisplay Display;
+#endif // MOZ_X11
+
+class gfxPlatformGtk final : public gfxPlatform {
+ friend class gfxPlatform;
+
+ public:
+ gfxPlatformGtk();
+ virtual ~gfxPlatformGtk();
+
+ static gfxPlatformGtk* GetPlatform() {
+ return (gfxPlatformGtk*)gfxPlatform::GetPlatform();
+ }
+
+ void ReadSystemFontList(mozilla::dom::SystemFontList* retValue) override;
+
+ already_AddRefed<gfxASurface> CreateOffscreenSurface(
+ const IntSize& aSize, gfxImageFormat aFormat) override;
+
+ nsresult GetFontList(nsAtom* aLangGroup, const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts) override;
+
+ void GetCommonFallbackFonts(uint32_t aCh, Script aRunScript,
+ eFontPresentation aPresentation,
+ nsTArray<const char*>& aFontList) override;
+
+ bool CreatePlatformFontList() override;
+
+ static int32_t GetFontScaleDPI();
+ static double GetFontScaleFactor();
+
+ gfxImageFormat GetOffscreenFormat() override;
+
+ bool SupportsApzWheelInput() const override { return true; }
+
+ void FontsPrefsChanged(const char* aPref) override;
+
+ bool SupportsPluginDirectBitmapDrawing() override { return true; }
+
+ bool AccelerateLayersByDefault() override;
+
+ already_AddRefed<mozilla::gfx::VsyncSource> CreateGlobalHardwareVsyncSource()
+ override;
+
+ bool IsX11Display() { return mIsX11Display; }
+ bool IsWaylandDisplay() override {
+ return !mIsX11Display && !gfxPlatform::IsHeadless();
+ }
+
+ static bool CheckVariationFontSupport();
+
+ protected:
+ void InitX11EGLConfig();
+ void InitDmabufConfig();
+ bool InitVAAPIConfig(bool aForceEnabledByUser);
+ void InitPlatformGPUProcessPrefs() override;
+ void InitWebRenderConfig() override;
+ void BuildContentDeviceData(mozilla::gfx::ContentDeviceData* aOut) override;
+
+ private:
+ nsTArray<uint8_t> GetPlatformCMSOutputProfileData() override;
+
+ bool mIsX11Display;
+};
+
+#endif /* GFX_PLATFORM_GTK_H */
diff --git a/gfx/thebes/gfxPlatformMac.cpp b/gfx/thebes/gfxPlatformMac.cpp
new file mode 100644
index 0000000000..b71fe3345c
--- /dev/null
+++ b/gfx/thebes/gfxPlatformMac.cpp
@@ -0,0 +1,1030 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxPlatformMac.h"
+
+#include "gfxQuartzSurface.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/gfx/2D.h"
+
+#include "gfxMacPlatformFontList.h"
+#include "gfxMacFont.h"
+#include "gfxCoreTextShaper.h"
+#include "gfxTextRun.h"
+#include "gfxUserFontSet.h"
+#include "gfxConfig.h"
+
+#include "AppleUtils.h"
+#include "nsTArray.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/VsyncDispatcher.h"
+#include "nsCocoaFeatures.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIFile.h"
+#include "nsUnicodeProperties.h"
+#include "qcms.h"
+#include "gfx2DGlue.h"
+#include "GeckoProfiler.h"
+#include "nsThreadUtils.h"
+
+#ifdef MOZ_BUNDLED_FONTS
+# include "mozilla/Telemetry.h"
+# include "nsDirectoryServiceDefs.h"
+# include "mozilla/StaticPrefs_gfx.h"
+#endif
+
+#include <dlfcn.h>
+#include <CoreVideo/CoreVideo.h>
+
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/SurfacePool.h"
+#include "VsyncSource.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::unicode;
+
+using mozilla::dom::SystemFontList;
+
+// A bunch of fonts for "additional language support" are shipped in a
+// "Language Support" directory, and don't show up in the standard font
+// list returned by CTFontManagerCopyAvailableFontFamilyNames unless
+// we explicitly activate them.
+static const nsLiteralCString kLangFontsDirs[] = {
+ "/Library/Application Support/Apple/Fonts/Language Support"_ns,
+ "/System/Library/Fonts/Supplemental"_ns};
+
+/* static */
+void gfxPlatformMac::FontRegistrationCallback(void* aUnused) {
+ AUTO_PROFILER_REGISTER_THREAD("RegisterFonts");
+ PR_SetCurrentThreadName("RegisterFonts");
+
+ for (const auto& dir : kLangFontsDirs) {
+ gfxMacPlatformFontList::ActivateFontsFromDir(dir);
+ }
+}
+
+PRThread* gfxPlatformMac::sFontRegistrationThread = nullptr;
+
+/* This is called from XPCOM_Init during startup (before gfxPlatform has been
+ initialized), so that it can kick off the font activation on a secondary
+ thread, and hope that it'll be finished by the time we're ready to build
+ our font list. */
+/* static */
+void gfxPlatformMac::RegisterSupplementalFonts() {
+ if (XRE_IsParentProcess()) {
+ sFontRegistrationThread = PR_CreateThread(
+ PR_USER_THREAD, FontRegistrationCallback, nullptr, PR_PRIORITY_NORMAL,
+ PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);
+ } else if (!nsCocoaFeatures::OnCatalinaOrLater()) {
+ // On Catalina+, it appears to be sufficient to activate fonts in the
+ // parent process; they are then also usable in child processes. But on
+ // pre-Catalina systems we need to explicitly activate them in each child
+ // process (per bug 1704273).
+ //
+ // But at least on 10.14 (Mojave), doing font registration on a separate
+ // thread in the content process seems crashy (bug 1708821), despite the
+ // CTFontManager.h header claiming that it's thread-safe. So we just do it
+ // immediately on the main thread, and accept the startup-time hit (sigh).
+ for (const auto& dir : kLangFontsDirs) {
+ gfxMacPlatformFontList::ActivateFontsFromDir(dir);
+ }
+ }
+}
+
+/* static */
+void gfxPlatformMac::WaitForFontRegistration() {
+ if (sFontRegistrationThread) {
+ PR_JoinThread(sFontRegistrationThread);
+ sFontRegistrationThread = nullptr;
+ }
+}
+
+gfxPlatformMac::gfxPlatformMac() {
+ mFontAntiAliasingThreshold = ReadAntiAliasingThreshold();
+
+ InitBackendPrefs(GetBackendPrefs());
+}
+
+gfxPlatformMac::~gfxPlatformMac() { gfxCoreTextShaper::Shutdown(); }
+
+BackendPrefsData gfxPlatformMac::GetBackendPrefs() const {
+ BackendPrefsData data;
+
+ data.mCanvasBitmask = BackendTypeBit(BackendType::SKIA);
+ data.mContentBitmask = BackendTypeBit(BackendType::SKIA);
+ data.mCanvasDefault = BackendType::SKIA;
+ data.mContentDefault = BackendType::SKIA;
+
+ return data;
+}
+
+bool gfxPlatformMac::CreatePlatformFontList() {
+ return gfxPlatformFontList::Initialize(new gfxMacPlatformFontList);
+}
+
+void gfxPlatformMac::ReadSystemFontList(SystemFontList* aFontList) {
+ gfxMacPlatformFontList::PlatformFontList()->ReadSystemFontList(aFontList);
+}
+
+already_AddRefed<gfxASurface> gfxPlatformMac::CreateOffscreenSurface(
+ const IntSize& aSize, gfxImageFormat aFormat) {
+ if (!Factory::AllowedSurfaceSize(aSize)) {
+ return nullptr;
+ }
+
+ RefPtr<gfxASurface> newSurface = new gfxQuartzSurface(aSize, aFormat);
+ return newSurface.forget();
+}
+
+void gfxPlatformMac::GetCommonFallbackFonts(uint32_t aCh, Script aRunScript,
+ eFontPresentation aPresentation,
+ nsTArray<const char*>& aFontList) {
+ if (PrefersColor(aPresentation)) {
+ aFontList.AppendElement("Apple Color Emoji");
+ }
+
+ switch (aRunScript) {
+ case Script::INVALID:
+ case Script::NUM_SCRIPT_CODES:
+ // Ensure the switch covers all the Script enum values.
+ MOZ_ASSERT_UNREACHABLE("bad script code");
+ break;
+
+ case Script::COMMON:
+ case Script::INHERITED:
+ // In most cases, COMMON and INHERITED characters will be merged into
+ // their context, but if they occur without any specific script context
+ // we'll just try common default fonts here.
+ case Script::LATIN:
+ case Script::CYRILLIC:
+ case Script::GREEK:
+ aFontList.AppendElement("Lucida Grande");
+ break;
+
+ case Script::MATHEMATICAL_NOTATION:
+ case Script::SYMBOLS:
+ case Script::SYMBOLS_EMOJI:
+ // Not currently returned by script run resolution (but see below, after
+ // the switch).
+ break;
+
+ // CJK-related script codes are a bit troublesome because of unification;
+ // we'll probably just get HAN much of the time, so the choice of which
+ // language font to try for fallback is rather arbitrary. Usually, though,
+ // we hope that font prefs will have handled this earlier.
+ case Script::BOPOMOFO:
+ case Script::HAN_WITH_BOPOMOFO:
+ case Script::SIMPLIFIED_HAN:
+ case Script::HAN:
+ aFontList.AppendElement("Songti SC");
+ if (aCh > 0x10000) {
+ // macOS installations with MS Office may have these -ExtB fonts
+ aFontList.AppendElement("SimSun-ExtB");
+ }
+ break;
+
+ // Currently, we don't resolve script runs to this value, but we may do so
+ // in future if we get better at handling things like `lang=zh-Hant`, not
+ // just resolving based on the Unicode text.
+ case Script::TRADITIONAL_HAN:
+ aFontList.AppendElement("Songti TC");
+ if (aCh > 0x10000) {
+ // macOS installations with MS Office may have these -ExtB fonts
+ aFontList.AppendElement("MingLiU-ExtB");
+ }
+ break;
+
+ case Script::HIRAGANA:
+ case Script::KATAKANA:
+ case Script::KATAKANA_OR_HIRAGANA:
+ case Script::JAPANESE:
+ aFontList.AppendElement("Hiragino Sans");
+ aFontList.AppendElement("Hiragino Kaku Gothic ProN");
+ break;
+
+ case Script::JAMO:
+ case Script::KOREAN:
+ case Script::HANGUL:
+ aFontList.AppendElement("Nanum Gothic");
+ aFontList.AppendElement("Apple SD Gothic Neo");
+ break;
+
+ // For most other scripts, macOS comes with a default font we can use.
+ case Script::ARABIC:
+ aFontList.AppendElement("Geeza Pro");
+ break;
+ case Script::ARMENIAN:
+ aFontList.AppendElement("Mshtakan");
+ break;
+ case Script::BENGALI:
+ aFontList.AppendElement("Bangla Sangam MN");
+ break;
+ case Script::CHEROKEE:
+ aFontList.AppendElement("Plantagenet Cherokee");
+ break;
+ case Script::COPTIC:
+ aFontList.AppendElement("Noto Sans Coptic");
+ break;
+ case Script::DESERET:
+ aFontList.AppendElement("Baskerville");
+ break;
+ case Script::DEVANAGARI:
+ aFontList.AppendElement("Devanagari Sangam MN");
+ break;
+ case Script::ETHIOPIC:
+ aFontList.AppendElement("Kefa");
+ break;
+ case Script::GEORGIAN:
+ aFontList.AppendElement("Helvetica");
+ break;
+ case Script::GOTHIC:
+ aFontList.AppendElement("Noto Sans Gothic");
+ break;
+ case Script::GUJARATI:
+ aFontList.AppendElement("Gujarati Sangam MN");
+ break;
+ case Script::GURMUKHI:
+ aFontList.AppendElement("Gurmukhi MN");
+ break;
+ case Script::HEBREW:
+ aFontList.AppendElement("Lucida Grande");
+ break;
+ case Script::KANNADA:
+ aFontList.AppendElement("Kannada MN");
+ break;
+ case Script::KHMER:
+ aFontList.AppendElement("Khmer MN");
+ break;
+ case Script::LAO:
+ aFontList.AppendElement("Lao MN");
+ break;
+ case Script::MALAYALAM:
+ aFontList.AppendElement("Malayalam Sangam MN");
+ break;
+ case Script::MONGOLIAN:
+ aFontList.AppendElement("Noto Sans Mongolian");
+ break;
+ case Script::MYANMAR:
+ aFontList.AppendElement("Myanmar MN");
+ break;
+ case Script::OGHAM:
+ aFontList.AppendElement("Noto Sans Ogham");
+ break;
+ case Script::OLD_ITALIC:
+ aFontList.AppendElement("Noto Sans Old Italic");
+ break;
+ case Script::ORIYA:
+ aFontList.AppendElement("Oriya Sangam MN");
+ break;
+ case Script::RUNIC:
+ aFontList.AppendElement("Noto Sans Runic");
+ break;
+ case Script::SINHALA:
+ aFontList.AppendElement("Sinhala Sangam MN");
+ break;
+ case Script::SYRIAC:
+ aFontList.AppendElement("Noto Sans Syriac");
+ break;
+ case Script::TAMIL:
+ aFontList.AppendElement("Tamil MN");
+ break;
+ case Script::TELUGU:
+ aFontList.AppendElement("Telugu MN");
+ break;
+ case Script::THAANA:
+ aFontList.AppendElement("Noto Sans Thaana");
+ break;
+ case Script::THAI:
+ aFontList.AppendElement("Thonburi");
+ break;
+ case Script::TIBETAN:
+ aFontList.AppendElement("Kailasa");
+ break;
+ case Script::CANADIAN_ABORIGINAL:
+ aFontList.AppendElement("Euphemia UCAS");
+ break;
+ case Script::YI:
+ aFontList.AppendElement("Noto Sans Yi");
+ aFontList.AppendElement("STHeiti");
+ break;
+ case Script::TAGALOG:
+ aFontList.AppendElement("Noto Sans Tagalog");
+ break;
+ case Script::HANUNOO:
+ aFontList.AppendElement("Noto Sans Hanunoo");
+ break;
+ case Script::BUHID:
+ aFontList.AppendElement("Noto Sans Buhid");
+ break;
+ case Script::TAGBANWA:
+ aFontList.AppendElement("Noto Sans Tagbanwa");
+ break;
+ case Script::BRAILLE:
+ aFontList.AppendElement("Apple Braille");
+ break;
+ case Script::CYPRIOT:
+ aFontList.AppendElement("Noto Sans Cypriot");
+ break;
+ case Script::LIMBU:
+ aFontList.AppendElement("Noto Sans Limbu");
+ break;
+ case Script::LINEAR_B:
+ aFontList.AppendElement("Noto Sans Linear B");
+ break;
+ case Script::OSMANYA:
+ aFontList.AppendElement("Noto Sans Osmanya");
+ break;
+ case Script::SHAVIAN:
+ aFontList.AppendElement("Noto Sans Shavian");
+ break;
+ case Script::TAI_LE:
+ aFontList.AppendElement("Noto Sans Tai Le");
+ break;
+ case Script::UGARITIC:
+ aFontList.AppendElement("Noto Sans Ugaritic");
+ break;
+ case Script::BUGINESE:
+ aFontList.AppendElement("Noto Sans Buginese");
+ break;
+ case Script::GLAGOLITIC:
+ aFontList.AppendElement("Noto Sans Glagolitic");
+ break;
+ case Script::KHAROSHTHI:
+ aFontList.AppendElement("Noto Sans Kharoshthi");
+ break;
+ case Script::SYLOTI_NAGRI:
+ aFontList.AppendElement("Noto Sans Syloti Nagri");
+ break;
+ case Script::NEW_TAI_LUE:
+ aFontList.AppendElement("Noto Sans New Tai Lue");
+ break;
+ case Script::TIFINAGH:
+ aFontList.AppendElement("Noto Sans Tifinagh");
+ break;
+ case Script::OLD_PERSIAN:
+ aFontList.AppendElement("Noto Sans Old Persian");
+ break;
+ case Script::BALINESE:
+ aFontList.AppendElement("Noto Sans Balinese");
+ break;
+ case Script::BATAK:
+ aFontList.AppendElement("Noto Sans Batak");
+ break;
+ case Script::BRAHMI:
+ aFontList.AppendElement("Noto Sans Brahmi");
+ break;
+ case Script::CHAM:
+ aFontList.AppendElement("Noto Sans Cham");
+ break;
+ case Script::EGYPTIAN_HIEROGLYPHS:
+ aFontList.AppendElement("Noto Sans Egyptian Hieroglyphs");
+ break;
+ case Script::PAHAWH_HMONG:
+ aFontList.AppendElement("Noto Sans Pahawh Hmong");
+ break;
+ case Script::OLD_HUNGARIAN:
+ aFontList.AppendElement("Noto Sans Old Hungarian");
+ break;
+ case Script::JAVANESE:
+ aFontList.AppendElement("Noto Sans Javanese");
+ break;
+ case Script::KAYAH_LI:
+ aFontList.AppendElement("Noto Sans Kayah Li");
+ break;
+ case Script::LEPCHA:
+ aFontList.AppendElement("Noto Sans Lepcha");
+ break;
+ case Script::LINEAR_A:
+ aFontList.AppendElement("Noto Sans Linear A");
+ break;
+ case Script::MANDAIC:
+ aFontList.AppendElement("Noto Sans Mandaic");
+ break;
+ case Script::NKO:
+ aFontList.AppendElement("Noto Sans NKo");
+ break;
+ case Script::OLD_TURKIC:
+ aFontList.AppendElement("Noto Sans Old Turkic");
+ break;
+ case Script::OLD_PERMIC:
+ aFontList.AppendElement("Noto Sans Old Permic");
+ break;
+ case Script::PHAGS_PA:
+ aFontList.AppendElement("Noto Sans PhagsPa");
+ break;
+ case Script::PHOENICIAN:
+ aFontList.AppendElement("Noto Sans Phoenician");
+ break;
+ case Script::MIAO:
+ aFontList.AppendElement("Noto Sans Miao");
+ break;
+ case Script::VAI:
+ aFontList.AppendElement("Noto Sans Vai");
+ break;
+ case Script::CUNEIFORM:
+ aFontList.AppendElement("Noto Sans Cuneiform");
+ break;
+ case Script::CARIAN:
+ aFontList.AppendElement("Noto Sans Carian");
+ break;
+ case Script::TAI_THAM:
+ aFontList.AppendElement("Noto Sans Tai Tham");
+ break;
+ case Script::LYCIAN:
+ aFontList.AppendElement("Noto Sans Lycian");
+ break;
+ case Script::LYDIAN:
+ aFontList.AppendElement("Noto Sans Lydian");
+ break;
+ case Script::OL_CHIKI:
+ aFontList.AppendElement("Noto Sans Ol Chiki");
+ break;
+ case Script::REJANG:
+ aFontList.AppendElement("Noto Sans Rejang");
+ break;
+ case Script::SAURASHTRA:
+ aFontList.AppendElement("Noto Sans Saurashtra");
+ break;
+ case Script::SUNDANESE:
+ aFontList.AppendElement("Noto Sans Sundanese");
+ break;
+ case Script::MEETEI_MAYEK:
+ aFontList.AppendElement("Noto Sans Meetei Mayek");
+ break;
+ case Script::IMPERIAL_ARAMAIC:
+ aFontList.AppendElement("Noto Sans Imperial Aramaic");
+ break;
+ case Script::AVESTAN:
+ aFontList.AppendElement("Noto Sans Avestan");
+ break;
+ case Script::CHAKMA:
+ aFontList.AppendElement("Noto Sans Chakma");
+ break;
+ case Script::KAITHI:
+ aFontList.AppendElement("Noto Sans Kaithi");
+ break;
+ case Script::MANICHAEAN:
+ aFontList.AppendElement("Noto Sans Manichaean");
+ break;
+ case Script::INSCRIPTIONAL_PAHLAVI:
+ aFontList.AppendElement("Noto Sans Inscriptional Pahlavi");
+ break;
+ case Script::PSALTER_PAHLAVI:
+ aFontList.AppendElement("Noto Sans Psalter Pahlavi");
+ break;
+ case Script::INSCRIPTIONAL_PARTHIAN:
+ aFontList.AppendElement("Noto Sans Inscriptional Parthian");
+ break;
+ case Script::SAMARITAN:
+ aFontList.AppendElement("Noto Sans Samaritan");
+ break;
+ case Script::TAI_VIET:
+ aFontList.AppendElement("Noto Sans Tai Viet");
+ break;
+ case Script::BAMUM:
+ aFontList.AppendElement("Noto Sans Bamum");
+ break;
+ case Script::LISU:
+ aFontList.AppendElement("Noto Sans Lisu");
+ break;
+ case Script::OLD_SOUTH_ARABIAN:
+ aFontList.AppendElement("Noto Sans Old South Arabian");
+ break;
+ case Script::BASSA_VAH:
+ aFontList.AppendElement("Noto Sans Bassa Vah");
+ break;
+ case Script::DUPLOYAN:
+ aFontList.AppendElement("Noto Sans Duployan");
+ break;
+ case Script::ELBASAN:
+ aFontList.AppendElement("Noto Sans Elbasan");
+ break;
+ case Script::GRANTHA:
+ aFontList.AppendElement("Noto Sans Grantha");
+ break;
+ case Script::MENDE_KIKAKUI:
+ aFontList.AppendElement("Noto Sans Mende Kikakui");
+ break;
+ case Script::MEROITIC_CURSIVE:
+ case Script::MEROITIC_HIEROGLYPHS:
+ aFontList.AppendElement("Noto Sans Meroitic");
+ break;
+ case Script::OLD_NORTH_ARABIAN:
+ aFontList.AppendElement("Noto Sans Old North Arabian");
+ break;
+ case Script::NABATAEAN:
+ aFontList.AppendElement("Noto Sans Nabataean");
+ break;
+ case Script::PALMYRENE:
+ aFontList.AppendElement("Noto Sans Palmyrene");
+ break;
+ case Script::KHUDAWADI:
+ aFontList.AppendElement("Noto Sans Khudawadi");
+ break;
+ case Script::WARANG_CITI:
+ aFontList.AppendElement("Noto Sans Warang Citi");
+ break;
+ case Script::MRO:
+ aFontList.AppendElement("Noto Sans Mro");
+ break;
+ case Script::SHARADA:
+ aFontList.AppendElement("Noto Sans Sharada");
+ break;
+ case Script::SORA_SOMPENG:
+ aFontList.AppendElement("Noto Sans Sora Sompeng");
+ break;
+ case Script::TAKRI:
+ aFontList.AppendElement("Noto Sans Takri");
+ break;
+ case Script::KHOJKI:
+ aFontList.AppendElement("Noto Sans Khojki");
+ break;
+ case Script::TIRHUTA:
+ aFontList.AppendElement("Noto Sans Tirhuta");
+ break;
+ case Script::CAUCASIAN_ALBANIAN:
+ aFontList.AppendElement("Noto Sans Caucasian Albanian");
+ break;
+ case Script::MAHAJANI:
+ aFontList.AppendElement("Noto Sans Mahajani");
+ break;
+ case Script::AHOM:
+ aFontList.AppendElement("Noto Serif Ahom");
+ break;
+ case Script::HATRAN:
+ aFontList.AppendElement("Noto Sans Hatran");
+ break;
+ case Script::MODI:
+ aFontList.AppendElement("Noto Sans Modi");
+ break;
+ case Script::MULTANI:
+ aFontList.AppendElement("Noto Sans Multani");
+ break;
+ case Script::PAU_CIN_HAU:
+ aFontList.AppendElement("Noto Sans Pau Cin Hau");
+ break;
+ case Script::SIDDHAM:
+ aFontList.AppendElement("Noto Sans Siddham");
+ break;
+ case Script::ADLAM:
+ aFontList.AppendElement("Noto Sans Adlam");
+ break;
+ case Script::BHAIKSUKI:
+ aFontList.AppendElement("Noto Sans Bhaiksuki");
+ break;
+ case Script::MARCHEN:
+ aFontList.AppendElement("Noto Sans Marchen");
+ break;
+ case Script::NEWA:
+ aFontList.AppendElement("Noto Sans Newa");
+ break;
+ case Script::OSAGE:
+ aFontList.AppendElement("Noto Sans Osage");
+ break;
+ case Script::HANIFI_ROHINGYA:
+ aFontList.AppendElement("Noto Sans Hanifi Rohingya");
+ break;
+ case Script::WANCHO:
+ aFontList.AppendElement("Noto Sans Wancho");
+ break;
+
+ // Script codes for which no commonly-installed font is currently known.
+ // Probably future macOS versions will add Noto fonts for many of these,
+ // so we should watch for updates.
+ case Script::OLD_CHURCH_SLAVONIC_CYRILLIC:
+ case Script::DEMOTIC_EGYPTIAN:
+ case Script::HIERATIC_EGYPTIAN:
+ case Script::BLISSYMBOLS:
+ case Script::CIRTH:
+ case Script::KHUTSURI:
+ case Script::HARAPPAN_INDUS:
+ case Script::LATIN_FRAKTUR:
+ case Script::LATIN_GAELIC:
+ case Script::MAYAN_HIEROGLYPHS:
+ case Script::RONGORONGO:
+ case Script::SARATI:
+ case Script::ESTRANGELO_SYRIAC:
+ case Script::WESTERN_SYRIAC:
+ case Script::EASTERN_SYRIAC:
+ case Script::TENGWAR:
+ case Script::VISIBLE_SPEECH:
+ case Script::UNWRITTEN_LANGUAGES:
+ case Script::UNKNOWN:
+ case Script::SIGNWRITING:
+ case Script::MOON:
+ case Script::BOOK_PAHLAVI:
+ case Script::NAKHI_GEBA:
+ case Script::KPELLE:
+ case Script::LOMA:
+ case Script::AFAKA:
+ case Script::JURCHEN:
+ case Script::NUSHU:
+ case Script::TANGUT:
+ case Script::WOLEAI:
+ case Script::ANATOLIAN_HIEROGLYPHS:
+ case Script::MASARAM_GONDI:
+ case Script::SOYOMBO:
+ case Script::ZANABAZAR_SQUARE:
+ case Script::DOGRA:
+ case Script::GUNJALA_GONDI:
+ case Script::MAKASAR:
+ case Script::MEDEFAIDRIN:
+ case Script::SOGDIAN:
+ case Script::OLD_SOGDIAN:
+ case Script::ELYMAIC:
+ case Script::NYIAKENG_PUACHUE_HMONG:
+ case Script::NANDINAGARI:
+ case Script::CHORASMIAN:
+ case Script::DIVES_AKURU:
+ case Script::KHITAN_SMALL_SCRIPT:
+ case Script::YEZIDI:
+ case Script::CYPRO_MINOAN:
+ case Script::OLD_UYGHUR:
+ case Script::TANGSA:
+ case Script::TOTO:
+ case Script::VITHKUQI:
+ case Script::KAWI:
+ case Script::NAG_MUNDARI:
+ break;
+ }
+
+ // Symbols/dingbats are generally Script=COMMON but may be resolved to any
+ // surrounding script run. So we'll always append a couple of likely fonts
+ // for such characters.
+ const uint32_t b = aCh >> 8;
+ if (aRunScript == Script::COMMON || // Stray COMMON chars not resolved
+ (b >= 0x20 && b <= 0x2b) || b == 0x2e || // BMP symbols/punctuation/etc
+ GetGenCategory(aCh) == nsUGenCategory::kSymbol ||
+ GetGenCategory(aCh) == nsUGenCategory::kPunctuation) {
+ if (b == 0x27) {
+ aFontList.AppendElement("Zapf Dingbats");
+ }
+ aFontList.AppendElement("Geneva");
+ aFontList.AppendElement("STIXGeneral");
+ aFontList.AppendElement("Apple Symbols");
+ // Japanese fonts also cover a lot of miscellaneous symbols
+ aFontList.AppendElement("Hiragino Sans");
+ aFontList.AppendElement("Hiragino Kaku Gothic ProN");
+ }
+
+ // Arial Unicode MS has lots of glyphs for obscure characters; try it as a
+ // last resort.
+ aFontList.AppendElement("Arial Unicode MS");
+}
+
+/*static*/
+void gfxPlatformMac::LookupSystemFont(
+ mozilla::LookAndFeel::FontID aSystemFontID, nsACString& aSystemFontName,
+ gfxFontStyle& aFontStyle) {
+ gfxMacPlatformFontList* pfl = gfxMacPlatformFontList::PlatformFontList();
+ return pfl->LookupSystemFont(aSystemFontID, aSystemFontName, aFontStyle);
+}
+
+uint32_t gfxPlatformMac::ReadAntiAliasingThreshold() {
+ uint32_t threshold = 0; // default == no threshold
+
+ // first read prefs flag to determine whether to use the setting or not
+ bool useAntiAliasingThreshold =
+ Preferences::GetBool("gfx.use_text_smoothing_setting", false);
+
+ // if the pref setting is disabled, return 0 which effectively disables this
+ // feature
+ if (!useAntiAliasingThreshold) return threshold;
+
+ // value set via Appearance pref panel, "Turn off text smoothing for font
+ // sizes xxx and smaller"
+ CFNumberRef prefValue = (CFNumberRef)CFPreferencesCopyAppValue(
+ CFSTR("AppleAntiAliasingThreshold"), kCFPreferencesCurrentApplication);
+
+ if (prefValue) {
+ if (!CFNumberGetValue(prefValue, kCFNumberIntType, &threshold)) {
+ threshold = 0;
+ }
+ CFRelease(prefValue);
+ }
+
+ return threshold;
+}
+
+bool gfxPlatformMac::AccelerateLayersByDefault() { return true; }
+
+// This is the renderer output callback function, called on the vsync thread
+static CVReturn VsyncCallback(CVDisplayLinkRef aDisplayLink,
+ const CVTimeStamp* aNow,
+ const CVTimeStamp* aOutputTime,
+ CVOptionFlags aFlagsIn, CVOptionFlags* aFlagsOut,
+ void* aDisplayLinkContext);
+
+class OSXVsyncSource final : public VsyncSource {
+ public:
+ OSXVsyncSource()
+ : mDisplayLink(nullptr, "OSXVsyncSource::OSXDisplay::mDisplayLink") {
+ MOZ_ASSERT(NS_IsMainThread());
+ mTimer = NS_NewTimer();
+ CGDisplayRegisterReconfigurationCallback(DisplayReconfigurationCallback,
+ this);
+ }
+
+ virtual ~OSXVsyncSource() {
+ MOZ_ASSERT(NS_IsMainThread());
+ CGDisplayRemoveReconfigurationCallback(DisplayReconfigurationCallback,
+ this);
+ }
+
+ static void RetryEnableVsync(nsITimer* aTimer, void* aOsxVsyncSource) {
+ MOZ_ASSERT(NS_IsMainThread());
+ OSXVsyncSource* osxVsyncSource =
+ static_cast<OSXVsyncSource*>(aOsxVsyncSource);
+ MOZ_ASSERT(osxVsyncSource);
+ osxVsyncSource->EnableVsync();
+ }
+
+ void EnableVsync() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (IsVsyncEnabled()) {
+ return;
+ }
+
+ auto displayLink = mDisplayLink.Lock();
+
+ // Create a display link capable of being used with all active displays
+ // TODO: See if we need to create an active DisplayLink for each monitor
+ // in multi-monitor situations. According to the docs, it is compatible
+ // with all displays running on the computer But if we have different
+ // monitors at different display rates, we may hit issues.
+ CVReturn retval = CVDisplayLinkCreateWithActiveCGDisplays(&*displayLink);
+
+ // Workaround for bug 1201401: CVDisplayLinkCreateWithCGDisplays()
+ // (called by CVDisplayLinkCreateWithActiveCGDisplays()) sometimes
+ // creates a CVDisplayLinkRef with an uninitialized (nulled) internal
+ // pointer. If we continue to use this CVDisplayLinkRef, we will
+ // eventually crash in CVCGDisplayLink::getDisplayTimes(), where the
+ // internal pointer is dereferenced. Fortunately, when this happens
+ // another internal variable is also left uninitialized (zeroed),
+ // which is accessible via CVDisplayLinkGetCurrentCGDisplay(). In
+ // normal conditions the current display is never zero.
+ if ((retval == kCVReturnSuccess) &&
+ (CVDisplayLinkGetCurrentCGDisplay(*displayLink) == 0)) {
+ retval = kCVReturnInvalidDisplay;
+ }
+
+ if (retval != kCVReturnSuccess) {
+ NS_WARNING(
+ "Could not create a display link with all active displays. "
+ "Retrying");
+ CVDisplayLinkRelease(*displayLink);
+ *displayLink = nullptr;
+
+ // bug 1142708 - When coming back from sleep,
+ // or when changing displays, active displays may not be ready yet,
+ // even if listening for the kIOMessageSystemHasPoweredOn event
+ // from OS X sleep notifications.
+ // Active displays are those that are drawable.
+ // bug 1144638 - When changing display configurations and getting
+ // notifications from CGDisplayReconfigurationCallBack, the
+ // callback gets called twice for each active display
+ // so it's difficult to know when all displays are active.
+ // Instead, try again soon. The delay is arbitrary. 100ms chosen
+ // because on a late 2013 15" retina, it takes about that
+ // long to come back up from sleep.
+ uint32_t delay = 100;
+ mTimer->InitWithNamedFuncCallback(RetryEnableVsync, this, delay,
+ nsITimer::TYPE_ONE_SHOT,
+ "RetryEnableVsync");
+ return;
+ }
+
+ if (CVDisplayLinkSetOutputCallback(*displayLink, &VsyncCallback, this) !=
+ kCVReturnSuccess) {
+ NS_WARNING("Could not set displaylink output callback");
+ CVDisplayLinkRelease(*displayLink);
+ *displayLink = nullptr;
+ return;
+ }
+
+ mPreviousTimestamp = TimeStamp::Now();
+ if (CVDisplayLinkStart(*displayLink) != kCVReturnSuccess) {
+ NS_WARNING("Could not activate the display link");
+ CVDisplayLinkRelease(*displayLink);
+ *displayLink = nullptr;
+ }
+
+ CVTime vsyncRate =
+ CVDisplayLinkGetNominalOutputVideoRefreshPeriod(*displayLink);
+ if (vsyncRate.flags & kCVTimeIsIndefinite) {
+ NS_WARNING("Could not get vsync rate, setting to 60.");
+ mVsyncRate = TimeDuration::FromMilliseconds(1000.0 / 60.0);
+ } else {
+ int64_t timeValue = vsyncRate.timeValue;
+ int64_t timeScale = vsyncRate.timeScale;
+ const int milliseconds = 1000;
+ float rateInMs = ((double)timeValue / (double)timeScale) * milliseconds;
+ mVsyncRate = TimeDuration::FromMilliseconds(rateInMs);
+ }
+ }
+
+ void DisableVsync() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!IsVsyncEnabled()) {
+ return;
+ }
+
+ // Release the display link
+ auto displayLink = mDisplayLink.Lock();
+ if (*displayLink) {
+ CVDisplayLinkRelease(*displayLink);
+ *displayLink = nullptr;
+ }
+ }
+
+ bool IsVsyncEnabled() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ auto displayLink = mDisplayLink.Lock();
+ return *displayLink != nullptr;
+ }
+
+ TimeDuration GetVsyncRate() override { return mVsyncRate; }
+
+ void Shutdown() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ mTimer->Cancel();
+ mTimer = nullptr;
+ DisableVsync();
+ }
+
+ // The vsync timestamps given by the CVDisplayLinkCallback are
+ // in the future for the NEXT frame. Large parts of Gecko, such
+ // as animations assume a timestamp at either now or in the past.
+ // Normalize the timestamps given to the VsyncDispatchers to the vsync
+ // that just occured, not the vsync that is upcoming.
+ TimeStamp mPreviousTimestamp;
+
+ private:
+ static void DisplayReconfigurationCallback(CGDirectDisplayID aDisplay,
+ CGDisplayChangeSummaryFlags aFlags,
+ void* aUserInfo) {
+ static_cast<OSXVsyncSource*>(aUserInfo)->OnDisplayReconfiguration(aDisplay,
+ aFlags);
+ }
+
+ void OnDisplayReconfiguration(CGDirectDisplayID aDisplay,
+ CGDisplayChangeSummaryFlags aFlags) {
+ // Display reconfiguration notifications are fired in two phases: Before
+ // the reconfiguration and after the reconfiguration.
+ // All displays are notified before (with a "BeginConfiguration" flag),
+ // and the reconfigured displays are notified again after the
+ // configuration.
+ if (aFlags & kCGDisplayBeginConfigurationFlag) {
+ // We're only interested in the "after" notification, for the display
+ // link's current display.
+ return;
+ }
+
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ bool didReconfigureCurrentDisplayLinkDisplay = false;
+ { // scope for lock
+ auto displayLink = mDisplayLink.Lock();
+ didReconfigureCurrentDisplayLinkDisplay =
+ *displayLink &&
+ CVDisplayLinkGetCurrentCGDisplay(*displayLink) == aDisplay;
+ }
+
+ if (didReconfigureCurrentDisplayLinkDisplay) {
+ // The link's current display has been reconfigured.
+ // Recreate the display link, because otherwise it may be stuck with a
+ // "removed" display forever and never notify us again.
+ DisableVsync();
+ EnableVsync();
+ }
+ }
+
+ // Accessed from main thread and from display reconfiguration callback
+ // thread... which also happens to be the main thread.
+ DataMutex<CVDisplayLinkRef> mDisplayLink;
+
+ // Accessed only from the main thread.
+ RefPtr<nsITimer> mTimer;
+ TimeDuration mVsyncRate;
+}; // OSXVsyncSource
+
+static CVReturn VsyncCallback(CVDisplayLinkRef aDisplayLink,
+ const CVTimeStamp* aNow,
+ const CVTimeStamp* aOutputTime,
+ CVOptionFlags aFlagsIn, CVOptionFlags* aFlagsOut,
+ void* aDisplayLinkContext) {
+ // Executed on OS X hardware vsync thread
+ OSXVsyncSource* vsyncSource = (OSXVsyncSource*)aDisplayLinkContext;
+
+ mozilla::TimeStamp outputTime =
+ mozilla::TimeStamp::FromSystemTime(aOutputTime->hostTime);
+ mozilla::TimeStamp nextVsync = outputTime;
+ mozilla::TimeStamp previousVsync = vsyncSource->mPreviousTimestamp;
+ mozilla::TimeStamp now = TimeStamp::Now();
+
+ // Snow leopard sometimes sends vsync timestamps very far in the past.
+ // Normalize the vsync timestamps to now.
+ if (nextVsync <= previousVsync) {
+ nextVsync = now;
+ previousVsync = now;
+ } else if (now < previousVsync) {
+ // Bug 1158321 - The VsyncCallback can sometimes execute before the reported
+ // vsync time. In those cases, normalize the timestamp to Now() as sending
+ // timestamps in the future has undefined behavior. See the comment above
+ // OSXVsyncSource::mPreviousTimestamp
+ previousVsync = now;
+ }
+
+ vsyncSource->mPreviousTimestamp = nextVsync;
+
+ vsyncSource->NotifyVsync(previousVsync, outputTime);
+ return kCVReturnSuccess;
+}
+
+already_AddRefed<mozilla::gfx::VsyncSource>
+gfxPlatformMac::CreateGlobalHardwareVsyncSource() {
+ RefPtr<VsyncSource> osxVsyncSource = new OSXVsyncSource();
+ osxVsyncSource->EnableVsync();
+ if (!osxVsyncSource->IsVsyncEnabled()) {
+ NS_WARNING(
+ "OS X Vsync source not enabled. Falling back to software vsync.");
+ return GetSoftwareVsyncSource();
+ }
+
+ osxVsyncSource->DisableVsync();
+ return osxVsyncSource.forget();
+}
+
+bool gfxPlatformMac::SupportsHDR() {
+ // HDR has 3 requirements:
+ // 1) high peak brightness
+ // 2) high contrast ratio
+ // 3) color depth > 24
+ if (GetScreenDepth() <= 24) {
+ return false;
+ }
+ // Screen is capable. Is the OS capable?
+#ifdef EARLY_BETA_OR_EARLIER
+ // More-or-less supported in Catalina.
+ return nsCocoaFeatures::OnCatalinaOrLater();
+#else
+ // Definitely supported in Big Sur.
+ return nsCocoaFeatures::OnBigSurOrLater();
+#endif
+}
+
+nsTArray<uint8_t> gfxPlatformMac::GetPlatformCMSOutputProfileData() {
+ nsTArray<uint8_t> prefProfileData = GetPrefCMSOutputProfileData();
+ if (!prefProfileData.IsEmpty()) {
+ return prefProfileData;
+ }
+
+ CGColorSpaceRef cspace = ::CGDisplayCopyColorSpace(::CGMainDisplayID());
+ if (!cspace) {
+ cspace = ::CGColorSpaceCreateDeviceRGB();
+ }
+ if (!cspace) {
+ return nsTArray<uint8_t>();
+ }
+
+ CFDataRef iccp = ::CGColorSpaceCopyICCProfile(cspace);
+
+ ::CFRelease(cspace);
+
+ if (!iccp) {
+ return nsTArray<uint8_t>();
+ }
+
+ // copy to external buffer
+ size_t size = static_cast<size_t>(::CFDataGetLength(iccp));
+
+ nsTArray<uint8_t> result;
+
+ if (size > 0) {
+ result.AppendElements(::CFDataGetBytePtr(iccp), size);
+ }
+
+ ::CFRelease(iccp);
+
+ return result;
+}
+
+bool gfxPlatformMac::CheckVariationFontSupport() {
+ // We don't allow variation fonts to be enabled before 10.13,
+ // as although the Core Text APIs existed, they are known to be
+ // fairly buggy.
+ // (Note that Safari also requires 10.13 for variation-font support.)
+ return nsCocoaFeatures::OnHighSierraOrLater();
+}
+
+void gfxPlatformMac::InitPlatformGPUProcessPrefs() {
+ FeatureState& gpuProc = gfxConfig::GetFeature(Feature::GPU_PROCESS);
+ gpuProc.ForceDisable(FeatureStatus::Blocked,
+ "GPU process does not work on Mac",
+ "FEATURE_FAILURE_MAC_GPU_PROC"_ns);
+}
diff --git a/gfx/thebes/gfxPlatformMac.h b/gfx/thebes/gfxPlatformMac.h
new file mode 100644
index 0000000000..29bbd7e877
--- /dev/null
+++ b/gfx/thebes/gfxPlatformMac.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_PLATFORM_MAC_H
+#define GFX_PLATFORM_MAC_H
+
+#include "nsTArrayForwardDeclare.h"
+#include "gfxPlatform.h"
+#include "mozilla/LookAndFeel.h"
+
+namespace mozilla {
+namespace gfx {
+class DrawTarget;
+class VsyncSource;
+} // namespace gfx
+} // namespace mozilla
+
+class gfxPlatformMac : public gfxPlatform {
+ public:
+ gfxPlatformMac();
+ virtual ~gfxPlatformMac();
+
+ // Call early in startup to register the macOS supplemental language fonts
+ // so that they're usable by the browser. This is intended to be called as
+ // early as possible, before most services etc are initialized; it starts
+ // a separate thread to register the fonts, because this is quite slow.
+ static void RegisterSupplementalFonts();
+
+ // Call from the main thread at the point where we need to start using the
+ // font list; this will wait (if necessary) for the registration thread to
+ // finish.
+ static void WaitForFontRegistration();
+
+ static gfxPlatformMac* GetPlatform() {
+ return (gfxPlatformMac*)gfxPlatform::GetPlatform();
+ }
+
+ already_AddRefed<gfxASurface> CreateOffscreenSurface(
+ const IntSize& aSize, gfxImageFormat aFormat) override;
+
+ bool CreatePlatformFontList() override;
+
+ void ReadSystemFontList(mozilla::dom::SystemFontList* aFontList) override;
+
+ void GetCommonFallbackFonts(uint32_t aCh, Script aRunScript,
+ eFontPresentation aPresentation,
+ nsTArray<const char*>& aFontList) override;
+
+ // lookup the system font for a particular system font type and set
+ // the name and style characteristics
+ static void LookupSystemFont(mozilla::LookAndFeel::FontID aSystemFontID,
+ nsACString& aSystemFontName,
+ gfxFontStyle& aFontStyle);
+
+ bool SupportsApzWheelInput() const override { return true; }
+
+ bool RespectsFontStyleSmoothing() const override {
+ // gfxMacFont respects the font smoothing hint.
+ return true;
+ }
+
+ bool RequiresAcceleratedGLContextForCompositorOGL() const override {
+ // On OS X in a VM, unaccelerated CompositorOGL shows black flashes, so we
+ // require accelerated GL for CompositorOGL but allow unaccelerated GL for
+ // BasicCompositor.
+ return true;
+ }
+
+ already_AddRefed<mozilla::gfx::VsyncSource> CreateGlobalHardwareVsyncSource()
+ override;
+
+ // lower threshold on font anti-aliasing
+ uint32_t GetAntiAliasingThreshold() { return mFontAntiAliasingThreshold; }
+
+ static bool CheckVariationFontSupport();
+
+ bool SupportsHDR() override;
+
+ protected:
+ bool AccelerateLayersByDefault() override;
+
+ BackendPrefsData GetBackendPrefs() const override;
+
+ void InitPlatformGPUProcessPrefs() override;
+
+ private:
+ nsTArray<uint8_t> GetPlatformCMSOutputProfileData() override;
+
+ // read in the pref value for the lower threshold on font anti-aliasing
+ static uint32_t ReadAntiAliasingThreshold();
+
+ static void FontRegistrationCallback(void* aUnused);
+
+ uint32_t mFontAntiAliasingThreshold;
+
+ static PRThread* sFontRegistrationThread;
+};
+
+#endif /* GFX_PLATFORM_MAC_H */
diff --git a/gfx/thebes/gfxPlatformWorker.cpp b/gfx/thebes/gfxPlatformWorker.cpp
new file mode 100644
index 0000000000..1a6183236e
--- /dev/null
+++ b/gfx/thebes/gfxPlatformWorker.cpp
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxPlatformWorker.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/ThreadLocal.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+MOZ_THREAD_LOCAL(gfxPlatformWorker*) gfxPlatformWorker::sInstance;
+
+/* static */ gfxPlatformWorker* gfxPlatformWorker::Get() {
+ if (!sInstance.init()) {
+ return nullptr;
+ }
+
+ gfxPlatformWorker* instance = sInstance.get();
+ if (instance) {
+ return instance;
+ }
+
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ if (!workerPrivate) {
+ return nullptr;
+ }
+
+ RefPtr<WeakWorkerRef> workerRef = WeakWorkerRef::Create(
+ workerPrivate, []() { gfxPlatformWorker::Shutdown(); });
+ if (!workerRef) {
+ return nullptr;
+ }
+
+ instance = new gfxPlatformWorker(std::move(workerRef));
+ sInstance.set(instance);
+ return instance;
+}
+
+/* static */ void gfxPlatformWorker::Shutdown() {
+ if (!sInstance.init()) {
+ return;
+ }
+
+ gfxPlatformWorker* instance = sInstance.get();
+ if (!instance) {
+ return;
+ }
+
+ sInstance.set(nullptr);
+ delete instance;
+}
+
+gfxPlatformWorker::gfxPlatformWorker(RefPtr<WeakWorkerRef>&& aWorkerRef)
+ : mWorkerRef(std::move(aWorkerRef)) {}
+
+gfxPlatformWorker::~gfxPlatformWorker() = default;
+
+RefPtr<mozilla::gfx::DrawTarget>
+gfxPlatformWorker::ScreenReferenceDrawTarget() {
+ if (!mScreenReferenceDrawTarget) {
+ mScreenReferenceDrawTarget = Factory::CreateDrawTarget(
+ BackendType::SKIA, IntSize(1, 1), SurfaceFormat::B8G8R8A8);
+ }
+ return mScreenReferenceDrawTarget;
+}
diff --git a/gfx/thebes/gfxPlatformWorker.h b/gfx/thebes/gfxPlatformWorker.h
new file mode 100644
index 0000000000..047c7c9989
--- /dev/null
+++ b/gfx/thebes/gfxPlatformWorker.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_PLATFORM_WORKER_H
+#define GFX_PLATFORM_WORKER_H
+
+#include "mozilla/ThreadLocal.h"
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+namespace dom {
+class WeakWorkerRef;
+} // namespace dom
+
+namespace gfx {
+class DrawTarget;
+} // namespace gfx
+} // namespace mozilla
+
+/**
+ * Threadlocal instance of gfxPlatform data that may be used/shared on a DOM
+ * worker thread.
+ */
+class gfxPlatformWorker final {
+ public:
+ static gfxPlatformWorker* Get();
+ static void Shutdown();
+
+ RefPtr<mozilla::gfx::DrawTarget> ScreenReferenceDrawTarget();
+
+ private:
+ explicit gfxPlatformWorker(RefPtr<mozilla::dom::WeakWorkerRef>&& aWorkerRef);
+ ~gfxPlatformWorker();
+
+ static MOZ_THREAD_LOCAL(gfxPlatformWorker*) sInstance;
+
+ RefPtr<mozilla::dom::WeakWorkerRef> mWorkerRef;
+
+ RefPtr<mozilla::gfx::DrawTarget> mScreenReferenceDrawTarget;
+};
+
+#endif // GFX_PLATFORM_WORKER_H
diff --git a/gfx/thebes/gfxPoint.h b/gfx/thebes/gfxPoint.h
new file mode 100644
index 0000000000..624a06aaad
--- /dev/null
+++ b/gfx/thebes/gfxPoint.h
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_POINT_H
+#define GFX_POINT_H
+
+#include "mozilla/gfx/Point.h"
+
+typedef mozilla::gfx::SizeDouble gfxSize;
+typedef mozilla::gfx::PointDouble gfxPoint;
+
+#endif /* GFX_POINT_H */
diff --git a/gfx/thebes/gfxQuad.h b/gfx/thebes/gfxQuad.h
new file mode 100644
index 0000000000..d1c9b1c55f
--- /dev/null
+++ b/gfx/thebes/gfxQuad.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_QUAD_H
+#define GFX_QUAD_H
+
+#include "gfxTypes.h"
+#include "gfxRect.h"
+#include "gfxLineSegment.h"
+#include <algorithm>
+
+struct gfxQuad {
+ gfxQuad(const gfxPoint& aOne, const gfxPoint& aTwo, const gfxPoint& aThree,
+ const gfxPoint& aFour) {
+ mPoints[0] = aOne;
+ mPoints[1] = aTwo;
+ mPoints[2] = aThree;
+ mPoints[3] = aFour;
+ }
+
+ bool Contains(const gfxPoint& aPoint) {
+ return (gfxLineSegment(mPoints[0], mPoints[1])
+ .PointsOnSameSide(aPoint, mPoints[2]) &&
+ gfxLineSegment(mPoints[1], mPoints[2])
+ .PointsOnSameSide(aPoint, mPoints[3]) &&
+ gfxLineSegment(mPoints[2], mPoints[3])
+ .PointsOnSameSide(aPoint, mPoints[0]) &&
+ gfxLineSegment(mPoints[3], mPoints[0])
+ .PointsOnSameSide(aPoint, mPoints[1]));
+ }
+
+ gfxRect GetBounds() {
+ gfxFloat min_x, max_x;
+ gfxFloat min_y, max_y;
+
+ min_x = max_x = mPoints[0].x;
+ min_y = max_y = mPoints[0].y;
+
+ for (int i = 1; i < 4; i++) {
+ min_x = std::min(mPoints[i].x.value, min_x);
+ max_x = std::max(mPoints[i].x.value, max_x);
+ min_y = std::min(mPoints[i].y.value, min_y);
+ max_y = std::max(mPoints[i].y.value, max_y);
+ }
+ return gfxRect(min_x, min_y, max_x - min_x, max_y - min_y);
+ }
+
+ gfxPoint mPoints[4];
+};
+
+#endif /* GFX_QUAD_H */
diff --git a/gfx/thebes/gfxQuartzNativeDrawing.cpp b/gfx/thebes/gfxQuartzNativeDrawing.cpp
new file mode 100644
index 0000000000..bd8658df24
--- /dev/null
+++ b/gfx/thebes/gfxQuartzNativeDrawing.cpp
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxQuartzNativeDrawing.h"
+#include "gfxPlatform.h"
+#include "mozilla/gfx/Helpers.h"
+
+using namespace mozilla::gfx;
+using namespace mozilla;
+
+gfxQuartzNativeDrawing::gfxQuartzNativeDrawing(DrawTarget& aDrawTarget,
+ const Rect& nativeRect)
+ : mDrawTarget(&aDrawTarget), mNativeRect(nativeRect), mCGContext(nullptr) {}
+
+CGContextRef gfxQuartzNativeDrawing::BeginNativeDrawing() {
+ NS_ASSERTION(!mCGContext,
+ "BeginNativeDrawing called when drawing already in progress");
+
+ DrawTarget* dt = mDrawTarget;
+ if (dt->IsTiledDrawTarget() || dt->GetBackendType() != BackendType::SKIA ||
+ dt->IsRecording()) {
+ // We need a DrawTarget that we can get a CGContextRef from:
+ Matrix transform = dt->GetTransform();
+
+ mNativeRect = transform.TransformBounds(mNativeRect);
+ mNativeRect.RoundOut();
+ if (mNativeRect.IsEmpty()) {
+ return nullptr;
+ }
+
+ mTempDrawTarget = Factory::CreateDrawTarget(
+ BackendType::SKIA,
+ IntSize::Truncate(mNativeRect.width, mNativeRect.height),
+ SurfaceFormat::B8G8R8A8);
+ if (!mTempDrawTarget) {
+ return nullptr;
+ }
+
+ transform.PostTranslate(-mNativeRect.x, -mNativeRect.y);
+ mTempDrawTarget->SetTransform(transform);
+
+ dt = mTempDrawTarget;
+ } else {
+ // Clip the DT in case BorrowedCGContext needs to create a new layer.
+ // This prevents it from creating a new layer the size of the window.
+ // But make sure that this clip is device pixel aligned.
+ Matrix transform = dt->GetTransform();
+
+ Rect deviceRect = transform.TransformBounds(mNativeRect);
+ deviceRect.RoundOut();
+ mNativeRect = transform.Inverse().TransformBounds(deviceRect);
+ mDrawTarget->PushClipRect(mNativeRect);
+ }
+
+ MOZ_ASSERT(dt->GetBackendType() == BackendType::SKIA);
+ mCGContext = mBorrowedContext.Init(dt);
+
+ if (NS_WARN_IF(!mCGContext)) {
+ // Failed borrowing CG context, so we need to clean up.
+ if (!mTempDrawTarget) {
+ mDrawTarget->PopClip();
+ }
+ return nullptr;
+ }
+
+ return mCGContext;
+}
+
+void gfxQuartzNativeDrawing::EndNativeDrawing() {
+ NS_ASSERTION(mCGContext,
+ "EndNativeDrawing called without BeginNativeDrawing");
+
+ mBorrowedContext.Finish();
+ if (mTempDrawTarget) {
+ RefPtr<SourceSurface> source = mTempDrawTarget->Snapshot();
+
+ AutoRestoreTransform autoRestore(mDrawTarget);
+ mDrawTarget->SetTransform(Matrix());
+ mDrawTarget->DrawSurface(source, mNativeRect,
+ Rect(0, 0, mNativeRect.width, mNativeRect.height));
+ } else {
+ mDrawTarget->PopClip();
+ }
+}
diff --git a/gfx/thebes/gfxQuartzNativeDrawing.h b/gfx/thebes/gfxQuartzNativeDrawing.h
new file mode 100644
index 0000000000..9e8b0bede6
--- /dev/null
+++ b/gfx/thebes/gfxQuartzNativeDrawing.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _GFXQUARTZNATIVEDRAWING_H_
+#define _GFXQUARTZNATIVEDRAWING_H_
+
+#include "mozilla/Attributes.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/BorrowedContext.h"
+#include "mozilla/RefPtr.h"
+
+class gfxQuartzNativeDrawing {
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::Rect Rect;
+
+ public:
+ /* Create native Quartz drawing for a rectangle bounded by
+ * nativeRect.
+ *
+ * Typical usage looks like:
+ *
+ * gfxQuartzNativeDrawing nativeDraw(ctx, nativeRect);
+ * CGContextRef cgContext = nativeDraw.BeginNativeDrawing();
+ * if (!cgContext)
+ * return NS_ERROR_FAILURE;
+ *
+ * ... call Quartz operations on CGContextRef to draw to nativeRect ...
+ *
+ * nativeDraw.EndNativeDrawing();
+ *
+ * aNativeRect is the size of the surface (in Quartz/Cocoa points) that
+ * will be created _if_ the gfxQuartzNativeDrawing decides to create a new
+ * surface and CGContext for its drawing operations, which it then
+ * composites into the target DrawTarget.
+ *
+ * (Note that aNativeRect will be ignored if the gfxQuartzNativeDrawing
+ * uses the target DrawTarget directly.)
+ *
+ * The optional aBackingScale parameter is a scaling factor that will be
+ * applied when creating and rendering into such a temporary surface.
+ */
+ gfxQuartzNativeDrawing(DrawTarget& aDrawTarget, const Rect& aNativeRect);
+
+ /* Returns a CGContextRef which may be used for native drawing. This
+ * CGContextRef is valid until EndNativeDrawing is called; if it is used
+ * for drawing after that time, the result is undefined. */
+ CGContextRef BeginNativeDrawing();
+
+ /* Marks the end of native drawing */
+ void EndNativeDrawing();
+
+ private:
+ // don't allow copying via construction or assignment
+ gfxQuartzNativeDrawing(const gfxQuartzNativeDrawing&) = delete;
+ const gfxQuartzNativeDrawing& operator=(const gfxQuartzNativeDrawing&) =
+ delete;
+
+ // Final destination context
+ RefPtr<DrawTarget> mDrawTarget;
+ RefPtr<DrawTarget> mTempDrawTarget;
+ mozilla::gfx::BorrowedCGContext mBorrowedContext;
+ mozilla::gfx::Rect mNativeRect;
+
+ // saved state
+ CGContextRef mCGContext;
+};
+
+#endif
diff --git a/gfx/thebes/gfxQuartzSurface.cpp b/gfx/thebes/gfxQuartzSurface.cpp
new file mode 100644
index 0000000000..a11167db52
--- /dev/null
+++ b/gfx/thebes/gfxQuartzSurface.cpp
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxQuartzSurface.h"
+#include "gfxContext.h"
+#include "gfxImageSurface.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/HelpersCairo.h"
+
+#include "cairo-quartz.h"
+
+void gfxQuartzSurface::MakeInvalid() { mSize = mozilla::gfx::IntSize(-1, -1); }
+
+gfxQuartzSurface::gfxQuartzSurface(const mozilla::gfx::IntSize& desiredSize,
+ gfxImageFormat format)
+ : mCGContext(nullptr), mSize(desiredSize) {
+ if (!mozilla::gfx::Factory::CheckSurfaceSize(desiredSize)) MakeInvalid();
+
+ unsigned int width = static_cast<unsigned int>(mSize.width);
+ unsigned int height = static_cast<unsigned int>(mSize.height);
+
+ cairo_format_t cformat = GfxFormatToCairoFormat(format);
+ cairo_surface_t* surf = cairo_quartz_surface_create(cformat, width, height);
+
+ mCGContext = cairo_quartz_surface_get_cg_context(surf);
+
+ CGContextRetain(mCGContext);
+
+ Init(surf);
+ if (mSurfaceValid) {
+ RecordMemoryUsed(mSize.height * 4 + sizeof(gfxQuartzSurface));
+ }
+}
+
+gfxQuartzSurface::gfxQuartzSurface(CGContextRef context,
+ const mozilla::gfx::IntSize& size)
+ : mCGContext(context), mSize(size) {
+ if (!mozilla::gfx::Factory::CheckSurfaceSize(size)) MakeInvalid();
+
+ unsigned int width = static_cast<unsigned int>(mSize.width);
+ unsigned int height = static_cast<unsigned int>(mSize.height);
+
+ cairo_surface_t* surf =
+ cairo_quartz_surface_create_for_cg_context(context, width, height);
+
+ CGContextRetain(mCGContext);
+
+ Init(surf);
+ if (mSurfaceValid) {
+ RecordMemoryUsed(mSize.height * 4 + sizeof(gfxQuartzSurface));
+ }
+}
+
+gfxQuartzSurface::gfxQuartzSurface(cairo_surface_t* csurf,
+ const mozilla::gfx::IntSize& aSize)
+ : mSize(aSize) {
+ mCGContext = cairo_quartz_surface_get_cg_context(csurf);
+ CGContextRetain(mCGContext);
+
+ Init(csurf, true);
+}
+
+already_AddRefed<gfxImageSurface> gfxQuartzSurface::GetAsImageSurface() {
+ cairo_surface_t* surface = cairo_quartz_surface_get_image(mSurface);
+ if (!surface || cairo_surface_status(surface)) return nullptr;
+
+ RefPtr<gfxASurface> img = Wrap(surface);
+
+ // cairo_quartz_surface_get_image returns a referenced image, and thebes
+ // shares the refcounts of Cairo surfaces. However, Wrap also adds a
+ // reference to the image. We need to remove one of these references
+ // explicitly so we don't leak.
+ img.get()->Release();
+
+ img->SetOpaqueRect(GetOpaqueRect());
+
+ return img.forget().downcast<gfxImageSurface>();
+}
+
+gfxQuartzSurface::~gfxQuartzSurface() { CGContextRelease(mCGContext); }
diff --git a/gfx/thebes/gfxQuartzSurface.h b/gfx/thebes/gfxQuartzSurface.h
new file mode 100644
index 0000000000..fbc081197b
--- /dev/null
+++ b/gfx/thebes/gfxQuartzSurface.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_QUARTZSURFACE_H
+#define GFX_QUARTZSURFACE_H
+
+#include "gfxASurface.h"
+#include "nsSize.h"
+#include "gfxPoint.h"
+
+#include <Carbon/Carbon.h>
+
+class gfxContext;
+class gfxImageSurface;
+
+class gfxQuartzSurface : public gfxASurface {
+ public:
+ gfxQuartzSurface(const mozilla::gfx::IntSize&, gfxImageFormat format);
+ gfxQuartzSurface(CGContextRef context, const mozilla::gfx::IntSize& size);
+ gfxQuartzSurface(cairo_surface_t* csurf, const mozilla::gfx::IntSize& aSize);
+
+ virtual ~gfxQuartzSurface();
+
+ virtual const mozilla::gfx::IntSize GetSize() const { return mSize; }
+
+ CGContextRef GetCGContext() { return mCGContext; }
+
+ already_AddRefed<gfxImageSurface> GetAsImageSurface();
+
+ protected:
+ void MakeInvalid();
+
+ CGContextRef mCGContext;
+ mozilla::gfx::IntSize mSize;
+};
+
+#endif /* GFX_QUARTZSURFACE_H */
diff --git a/gfx/thebes/gfxQuaternion.h b/gfx/thebes/gfxQuaternion.h
new file mode 100644
index 0000000000..4180f7a7b4
--- /dev/null
+++ b/gfx/thebes/gfxQuaternion.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_QUATERNION_H
+#define GFX_QUATERNION_H
+
+#include "mozilla/gfx/BasePoint4D.h"
+#include "mozilla/gfx/Matrix.h"
+#include "nsAlgorithm.h"
+#include <algorithm>
+
+struct gfxQuaternion
+ : public mozilla::gfx::BasePoint4D<gfxFloat, gfxQuaternion> {
+ typedef mozilla::gfx::BasePoint4D<gfxFloat, gfxQuaternion> Super;
+
+ gfxQuaternion() : Super() {}
+ gfxQuaternion(gfxFloat aX, gfxFloat aY, gfxFloat aZ, gfxFloat aW)
+ : Super(aX, aY, aZ, aW) {}
+
+ explicit gfxQuaternion(const mozilla::gfx::Matrix4x4& aMatrix) {
+ w = 0.5 *
+ sqrt(std::max(1 + aMatrix[0][0] + aMatrix[1][1] + aMatrix[2][2], 0.0f));
+ x = 0.5 *
+ sqrt(std::max(1 + aMatrix[0][0] - aMatrix[1][1] - aMatrix[2][2], 0.0f));
+ y = 0.5 *
+ sqrt(std::max(1 - aMatrix[0][0] + aMatrix[1][1] - aMatrix[2][2], 0.0f));
+ z = 0.5 *
+ sqrt(std::max(1 - aMatrix[0][0] - aMatrix[1][1] + aMatrix[2][2], 0.0f));
+
+ if (aMatrix[2][1] > aMatrix[1][2]) x = -x;
+ if (aMatrix[0][2] > aMatrix[2][0]) y = -y;
+ if (aMatrix[1][0] > aMatrix[0][1]) z = -z;
+ }
+
+ // Convert from |direction axis, angle| pair to gfxQuaternion.
+ //
+ // Reference:
+ // https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation
+ //
+ // if the direction axis is (x, y, z) = xi + yj + zk,
+ // and the angle is |theta|, this formula can be done using
+ // an extension of Euler's formula:
+ // q = cos(theta/2) + (xi + yj + zk)(sin(theta/2))
+ // = cos(theta/2) +
+ // x*sin(theta/2)i + y*sin(theta/2)j + z*sin(theta/2)k
+ // Note: aDirection should be an unit vector and
+ // the unit of aAngle should be Radian.
+ gfxQuaternion(const mozilla::gfx::Point3D& aDirection, gfxFloat aAngle) {
+ MOZ_ASSERT(mozilla::gfx::FuzzyEqual(aDirection.Length(), 1.0f),
+ "aDirection should be an unit vector");
+ x = aDirection.x * sin(aAngle / 2.0);
+ y = aDirection.y * sin(aAngle / 2.0);
+ z = aDirection.z * sin(aAngle / 2.0);
+ w = cos(aAngle / 2.0);
+ }
+
+ gfxQuaternion Slerp(const gfxQuaternion& aOther, gfxFloat aCoeff) const {
+ gfxFloat dot = mozilla::clamped(DotProduct(aOther), -1.0, 1.0);
+ if (dot == 1.0) {
+ return *this;
+ }
+
+ gfxFloat theta = acos(dot);
+ gfxFloat rsintheta = 1 / sqrt(1 - dot * dot);
+ gfxFloat rightWeight = sin(aCoeff * theta) * rsintheta;
+
+ gfxQuaternion left = *this;
+ gfxQuaternion right = aOther;
+
+ left *= cos(aCoeff * theta) - dot * rightWeight;
+ right *= rightWeight;
+
+ return left + right;
+ }
+
+ using Super::operator*=;
+
+ // Quaternion multiplication
+ // Reference:
+ // https://en.wikipedia.org/wiki/Quaternion#Ordered_list_form
+ //
+ // (w1, x1, y1, z1)(w2, x2, y2, z2) = (w1w2 - x1x2 - y1y2 - z1z2,
+ // w1x2 + x1w2 + y1z2 - z1y2,
+ // w1y2 - x1z2 + y1w2 + z1x2,
+ // w1z2 + x1y2 - y1x2 + z1w2)
+ gfxQuaternion operator*(const gfxQuaternion& aOther) const {
+ return gfxQuaternion(
+ w * aOther.x + x * aOther.w + y * aOther.z - z * aOther.y,
+ w * aOther.y - x * aOther.z + y * aOther.w + z * aOther.x,
+ w * aOther.z + x * aOther.y - y * aOther.x + z * aOther.w,
+ w * aOther.w - x * aOther.x - y * aOther.y - z * aOther.z);
+ }
+ gfxQuaternion& operator*=(const gfxQuaternion& aOther) {
+ *this = *this * aOther;
+ return *this;
+ }
+
+ mozilla::gfx::Matrix4x4 ToMatrix() const {
+ mozilla::gfx::Matrix4x4 temp;
+
+ temp[0][0] = 1 - 2 * (y * y + z * z);
+ temp[0][1] = 2 * (x * y + w * z);
+ temp[0][2] = 2 * (x * z - w * y);
+ temp[1][0] = 2 * (x * y - w * z);
+ temp[1][1] = 1 - 2 * (x * x + z * z);
+ temp[1][2] = 2 * (y * z + w * x);
+ temp[2][0] = 2 * (x * z + w * y);
+ temp[2][1] = 2 * (y * z - w * x);
+ temp[2][2] = 1 - 2 * (x * x + y * y);
+
+ return temp;
+ }
+};
+
+#endif /* GFX_QUATERNION_H */
diff --git a/gfx/thebes/gfxRect.h b/gfx/thebes/gfxRect.h
new file mode 100644
index 0000000000..f93fc77ca4
--- /dev/null
+++ b/gfx/thebes/gfxRect.h
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_RECT_H
+#define GFX_RECT_H
+
+#include "mozilla/gfx/Rect.h"
+
+typedef mozilla::gfx::MarginDouble gfxMargin;
+typedef mozilla::gfx::RectDouble gfxRect;
+
+#endif /* GFX_RECT_H */
diff --git a/gfx/thebes/gfxSVGGlyphs.cpp b/gfx/thebes/gfxSVGGlyphs.cpp
new file mode 100644
index 0000000000..32cd3601ed
--- /dev/null
+++ b/gfx/thebes/gfxSVGGlyphs.cpp
@@ -0,0 +1,466 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxSVGGlyphs.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SMILAnimationController.h"
+#include "mozilla/SVGContextPaint.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ImageTracker.h"
+#include "mozilla/dom/SVGDocument.h"
+#include "nsError.h"
+#include "nsString.h"
+#include "nsICategoryManager.h"
+#include "nsIDocumentLoaderFactory.h"
+#include "nsIContentViewer.h"
+#include "nsIStreamListener.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNetUtil.h"
+#include "nsIInputStream.h"
+#include "nsStringStream.h"
+#include "nsStreamUtils.h"
+#include "nsIPrincipal.h"
+#include "nsContentUtils.h"
+#include "gfxFont.h"
+#include "gfxContext.h"
+#include "harfbuzz/hb.h"
+#include "zlib.h"
+
+#define SVG_CONTENT_TYPE "image/svg+xml"_ns
+#define UTF8_CHARSET "utf-8"_ns
+
+using namespace mozilla;
+using mozilla::dom::Document;
+using mozilla::dom::Element;
+
+/* static */
+const mozilla::gfx::DeviceColor SimpleTextContextPaint::sZero;
+
+gfxSVGGlyphs::gfxSVGGlyphs(hb_blob_t* aSVGTable, gfxFontEntry* aFontEntry)
+ : mSVGData(aSVGTable), mFontEntry(aFontEntry) {
+ unsigned int length;
+ const char* svgData = hb_blob_get_data(mSVGData, &length);
+ mHeader = reinterpret_cast<const Header*>(svgData);
+ mDocIndex = nullptr;
+
+ if (sizeof(Header) <= length && uint16_t(mHeader->mVersion) == 0 &&
+ uint64_t(mHeader->mDocIndexOffset) + 2 <= length) {
+ const DocIndex* docIndex =
+ reinterpret_cast<const DocIndex*>(svgData + mHeader->mDocIndexOffset);
+ // Limit the number of documents to avoid overflow
+ if (uint64_t(mHeader->mDocIndexOffset) + 2 +
+ uint16_t(docIndex->mNumEntries) * sizeof(IndexEntry) <=
+ length) {
+ mDocIndex = docIndex;
+ }
+ }
+}
+
+gfxSVGGlyphs::~gfxSVGGlyphs() { hb_blob_destroy(mSVGData); }
+
+void gfxSVGGlyphs::DidRefresh() { mFontEntry->NotifyGlyphsChanged(); }
+
+/*
+ * Comparison operator for finding a range containing a given glyph ID. Simply
+ * checks whether |key| is less (greater) than every element of |range|, in
+ * which case return |key| < |range| (|key| > |range|). Otherwise |key| is in
+ * |range|, in which case return equality.
+ * The total ordering here is guaranteed by
+ * (1) the index ranges being disjoint; and
+ * (2) the (sole) key always being a singleton, so intersection => containment
+ * (note that this is wrong if we have more than one intersection or two
+ * sets intersecting of size > 1 -- so... don't do that)
+ */
+/* static */
+int gfxSVGGlyphs::CompareIndexEntries(const void* aKey, const void* aEntry) {
+ const uint32_t key = *(uint32_t*)aKey;
+ const IndexEntry* entry = (const IndexEntry*)aEntry;
+
+ if (key < uint16_t(entry->mStartGlyph)) {
+ return -1;
+ }
+ if (key > uint16_t(entry->mEndGlyph)) {
+ return 1;
+ }
+ return 0;
+}
+
+gfxSVGGlyphsDocument* gfxSVGGlyphs::FindOrCreateGlyphsDocument(
+ uint32_t aGlyphId) {
+ if (!mDocIndex) {
+ // Invalid table
+ return nullptr;
+ }
+
+ IndexEntry* entry = (IndexEntry*)bsearch(
+ &aGlyphId, mDocIndex->mEntries, uint16_t(mDocIndex->mNumEntries),
+ sizeof(IndexEntry), CompareIndexEntries);
+ if (!entry) {
+ return nullptr;
+ }
+
+ return mGlyphDocs.WithEntryHandle(
+ entry->mDocOffset, [&](auto&& glyphDocsEntry) -> gfxSVGGlyphsDocument* {
+ if (!glyphDocsEntry) {
+ unsigned int length;
+ const uint8_t* data =
+ (const uint8_t*)hb_blob_get_data(mSVGData, &length);
+ if (entry->mDocOffset > 0 && uint64_t(mHeader->mDocIndexOffset) +
+ entry->mDocOffset +
+ entry->mDocLength <=
+ length) {
+ return glyphDocsEntry
+ .Insert(MakeUnique<gfxSVGGlyphsDocument>(
+ data + mHeader->mDocIndexOffset + entry->mDocOffset,
+ entry->mDocLength, this))
+ .get();
+ }
+
+ return nullptr;
+ }
+
+ return glyphDocsEntry->get();
+ });
+}
+
+nsresult gfxSVGGlyphsDocument::SetupPresentation() {
+ nsCOMPtr<nsICategoryManager> catMan =
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
+ nsCString contractId;
+ nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers",
+ "image/svg+xml", contractId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
+ do_GetService(contractId.get());
+ NS_ASSERTION(docLoaderFactory, "Couldn't get DocumentLoaderFactory");
+
+ nsCOMPtr<nsIContentViewer> viewer;
+ rv = docLoaderFactory->CreateInstanceForDocument(nullptr, mDocument, nullptr,
+ getter_AddRefs(viewer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ auto upem = mOwner->FontEntry()->UnitsPerEm();
+ rv = viewer->Init(nullptr, gfx::IntRect(0, 0, upem, upem), nullptr);
+ if (NS_SUCCEEDED(rv)) {
+ rv = viewer->Open(nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ RefPtr<PresShell> presShell = viewer->GetPresShell();
+ if (!presShell->DidInitialize()) {
+ rv = presShell->Initialize();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mDocument->FlushPendingNotifications(FlushType::Layout);
+
+ if (mDocument->HasAnimationController()) {
+ mDocument->GetAnimationController()->Resume(SMILTimeContainer::PAUSE_IMAGE);
+ }
+ mDocument->ImageTracker()->SetAnimatingState(true);
+
+ mViewer = viewer;
+ mPresShell = presShell;
+ mPresShell->AddPostRefreshObserver(this);
+
+ return NS_OK;
+}
+
+void gfxSVGGlyphsDocument::DidRefresh() { mOwner->DidRefresh(); }
+
+/**
+ * Walk the DOM tree to find all glyph elements and insert them into the lookup
+ * table
+ * @param aElem The element to search from
+ */
+void gfxSVGGlyphsDocument::FindGlyphElements(Element* aElem) {
+ for (nsIContent* child = aElem->GetLastChild(); child;
+ child = child->GetPreviousSibling()) {
+ if (!child->IsElement()) {
+ continue;
+ }
+ FindGlyphElements(child->AsElement());
+ }
+
+ InsertGlyphId(aElem);
+}
+
+/**
+ * If there exists an SVG glyph with the specified glyph id, render it and
+ * return true If no such glyph exists, or in the case of an error return false
+ * @param aContext The thebes aContext to draw to
+ * @param aGlyphId The glyph id
+ * @return true iff rendering succeeded
+ */
+void gfxSVGGlyphs::RenderGlyph(gfxContext* aContext, uint32_t aGlyphId,
+ SVGContextPaint* aContextPaint) {
+ gfxContextAutoSaveRestore aContextRestorer(aContext);
+
+ Element* glyph = mGlyphIdMap.Get(aGlyphId);
+ MOZ_ASSERT(glyph, "No glyph element. Should check with HasSVGGlyph() first!");
+
+ AutoSetRestoreSVGContextPaint autoSetRestore(aContextPaint,
+ glyph->OwnerDoc());
+
+ SVGUtils::PaintSVGGlyph(glyph, aContext);
+
+#if DEBUG
+ // This will not have any effect, because we're about to restore the state
+ // via the aContextRestorer destructor, but it prevents debug builds from
+ // asserting if it turns out that PaintSVGGlyph didn't actually do anything.
+ // This happens if the SVG document consists of just an image, and the image
+ // hasn't finished loading yet so we can't draw it.
+ aContext->SetOp(gfx::CompositionOp::OP_OVER);
+#endif
+}
+
+bool gfxSVGGlyphs::GetGlyphExtents(uint32_t aGlyphId,
+ const gfxMatrix& aSVGToAppSpace,
+ gfxRect* aResult) {
+ Element* glyph = mGlyphIdMap.Get(aGlyphId);
+ NS_ASSERTION(glyph,
+ "No glyph element. Should check with HasSVGGlyph() first!");
+
+ return SVGUtils::GetSVGGlyphExtents(glyph, aSVGToAppSpace, aResult);
+}
+
+Element* gfxSVGGlyphs::GetGlyphElement(uint32_t aGlyphId) {
+ return mGlyphIdMap.LookupOrInsertWith(aGlyphId, [&] {
+ Element* elem = nullptr;
+ if (gfxSVGGlyphsDocument* set = FindOrCreateGlyphsDocument(aGlyphId)) {
+ elem = set->GetGlyphElement(aGlyphId);
+ }
+ return elem;
+ });
+}
+
+bool gfxSVGGlyphs::HasSVGGlyph(uint32_t aGlyphId) {
+ return !!GetGlyphElement(aGlyphId);
+}
+
+size_t gfxSVGGlyphs::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ // We don't include the size of mSVGData here, because (depending on the
+ // font backend implementation) it will either wrap a block of data owned
+ // by the system (and potentially shared), or a table that's in our font
+ // table cache and therefore already counted.
+ size_t result = aMallocSizeOf(this) +
+ mGlyphDocs.ShallowSizeOfExcludingThis(aMallocSizeOf) +
+ mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (const auto& entry : mGlyphDocs.Values()) {
+ result += entry->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return result;
+}
+
+Element* gfxSVGGlyphsDocument::GetGlyphElement(uint32_t aGlyphId) {
+ return mGlyphIdMap.Get(aGlyphId);
+}
+
+gfxSVGGlyphsDocument::gfxSVGGlyphsDocument(const uint8_t* aBuffer,
+ uint32_t aBufLen,
+ gfxSVGGlyphs* aSVGGlyphs)
+ : mOwner(aSVGGlyphs) {
+ if (aBufLen >= 14 && aBuffer[0] == 31 && aBuffer[1] == 139) {
+ // It's a gzip-compressed document; decompress it before parsing.
+ // The original length (modulo 2^32) is found in the last 4 bytes
+ // of the data, stored in little-endian format. We read it as
+ // individual bytes to avoid possible alignment issues.
+ // (Note that if the original length was >2^32, then origLen here
+ // will be incorrect; but then the inflate() call will not return
+ // Z_STREAM_END and we'll bail out safely.)
+ size_t origLen = (size_t(aBuffer[aBufLen - 1]) << 24) +
+ (size_t(aBuffer[aBufLen - 2]) << 16) +
+ (size_t(aBuffer[aBufLen - 3]) << 8) +
+ size_t(aBuffer[aBufLen - 4]);
+ AutoTArray<uint8_t, 4096> outBuf;
+ if (outBuf.SetLength(origLen, mozilla::fallible)) {
+ z_stream s = {0};
+ s.next_in = const_cast<Byte*>(aBuffer);
+ s.avail_in = aBufLen;
+ s.next_out = outBuf.Elements();
+ s.avail_out = outBuf.Length();
+ // The magic number 16 here is the zlib flag to expect gzip format,
+ // see http://www.zlib.net/manual.html#Advanced
+ if (Z_OK == inflateInit2(&s, 16 + MAX_WBITS)) {
+ int result = inflate(&s, Z_FINISH);
+ if (Z_STREAM_END == result) {
+ MOZ_ASSERT(size_t(s.next_out - outBuf.Elements()) == origLen);
+ ParseDocument(outBuf.Elements(), outBuf.Length());
+ } else {
+ NS_WARNING("Failed to decompress SVG glyphs document");
+ }
+ inflateEnd(&s);
+ }
+ } else {
+ NS_WARNING("Failed to allocate memory for SVG glyphs document");
+ }
+ } else {
+ ParseDocument(aBuffer, aBufLen);
+ }
+
+ if (!mDocument) {
+ NS_WARNING("Could not parse SVG glyphs document");
+ return;
+ }
+
+ Element* root = mDocument->GetRootElement();
+ if (!root) {
+ NS_WARNING("Could not parse SVG glyphs document");
+ return;
+ }
+
+ nsresult rv = SetupPresentation();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Couldn't setup presentation for SVG glyphs document");
+ return;
+ }
+
+ FindGlyphElements(root);
+}
+
+gfxSVGGlyphsDocument::~gfxSVGGlyphsDocument() {
+ if (mDocument) {
+ mDocument->OnPageHide(false, nullptr);
+ }
+ if (mPresShell) {
+ mPresShell->RemovePostRefreshObserver(this);
+ }
+ if (mViewer) {
+ mViewer->Close(nullptr);
+ mViewer->Destroy();
+ }
+}
+
+static nsresult CreateBufferedStream(const uint8_t* aBuffer, uint32_t aBufLen,
+ nsCOMPtr<nsIInputStream>& aResult) {
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewByteInputStream(
+ getter_AddRefs(stream),
+ Span(reinterpret_cast<const char*>(aBuffer), aBufLen),
+ NS_ASSIGNMENT_DEPEND);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> aBufferedStream;
+ if (!NS_InputStreamIsBuffered(stream)) {
+ rv = NS_NewBufferedInputStream(getter_AddRefs(aBufferedStream),
+ stream.forget(), 4096);
+ NS_ENSURE_SUCCESS(rv, rv);
+ stream = aBufferedStream;
+ }
+
+ aResult = stream;
+
+ return NS_OK;
+}
+
+nsresult gfxSVGGlyphsDocument::ParseDocument(const uint8_t* aBuffer,
+ uint32_t aBufLen) {
+ // Mostly pulled from nsDOMParser::ParseFromStream
+
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = CreateBufferedStream(aBuffer, aBufLen, stream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We just need a dummy URI.
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), "moz-svg-glyphs://"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> principal =
+ NullPrincipal::CreateWithoutOriginAttributes();
+
+ RefPtr<Document> document;
+ rv = NS_NewDOMDocument(getter_AddRefs(document),
+ u""_ns, // aNamespaceURI
+ u""_ns, // aQualifiedName
+ nullptr, // aDoctype
+ uri, uri, principal,
+ false, // aLoadedAsData
+ nullptr, // aEventObject
+ DocumentFlavorSVG);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewInputStreamChannel(
+ getter_AddRefs(channel), uri,
+ nullptr, // aStream
+ principal, nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL,
+ nsIContentPolicy::TYPE_OTHER, SVG_CONTENT_TYPE, UTF8_CHARSET);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set this early because various decisions during page-load depend on it.
+ document->SetIsBeingUsedAsImage();
+ document->SetIsSVGGlyphsDocument();
+ document->SetReadyStateInternal(Document::READYSTATE_UNINITIALIZED);
+
+ nsCOMPtr<nsIStreamListener> listener;
+ rv = document->StartDocumentLoad("external-resource", channel,
+ nullptr, // aLoadGroup
+ nullptr, // aContainer
+ getter_AddRefs(listener), true /* aReset */);
+ if (NS_FAILED(rv) || !listener) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = listener->OnStartRequest(channel);
+ if (NS_FAILED(rv)) {
+ channel->Cancel(rv);
+ }
+
+ nsresult status;
+ channel->GetStatus(&status);
+ if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(status)) {
+ rv = listener->OnDataAvailable(channel, stream, 0, aBufLen);
+ if (NS_FAILED(rv)) {
+ channel->Cancel(rv);
+ }
+ channel->GetStatus(&status);
+ }
+
+ rv = listener->OnStopRequest(channel, status);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ document.swap(mDocument);
+
+ return NS_OK;
+}
+
+void gfxSVGGlyphsDocument::InsertGlyphId(Element* aGlyphElement) {
+ nsAutoString glyphIdStr;
+ static const uint32_t glyphPrefixLength = 5;
+ // The maximum glyph ID is 65535 so the maximum length of the numeric part
+ // is 5.
+ if (!aGlyphElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, glyphIdStr) ||
+ !StringBeginsWith(glyphIdStr, u"glyph"_ns) ||
+ glyphIdStr.Length() > glyphPrefixLength + 5) {
+ return;
+ }
+
+ uint32_t id = 0;
+ for (uint32_t i = glyphPrefixLength; i < glyphIdStr.Length(); ++i) {
+ char16_t ch = glyphIdStr.CharAt(i);
+ if (ch < '0' || ch > '9') {
+ return;
+ }
+ if (ch == '0' && i == glyphPrefixLength) {
+ return;
+ }
+ id = id * 10 + (ch - '0');
+ }
+
+ mGlyphIdMap.InsertOrUpdate(id, aGlyphElement);
+}
+
+size_t gfxSVGGlyphsDocument::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) +
+ mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
+}
diff --git a/gfx/thebes/gfxSVGGlyphs.h b/gfx/thebes/gfxSVGGlyphs.h
new file mode 100644
index 0000000000..f17d887bce
--- /dev/null
+++ b/gfx/thebes/gfxSVGGlyphs.h
@@ -0,0 +1,239 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_SVG_GLYPHS_WRAPPER_H
+#define GFX_SVG_GLYPHS_WRAPPER_H
+
+#include "gfxFontUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "nsString.h"
+#include "nsClassHashtable.h"
+#include "nsBaseHashtable.h"
+#include "nsHashKeys.h"
+#include "gfxPattern.h"
+#include "mozilla/gfx/UserData.h"
+#include "mozilla/SVGContextPaint.h"
+#include "nsRefreshObservers.h"
+
+class nsIContentViewer;
+class gfxSVGGlyphs;
+
+namespace mozilla {
+class PresShell;
+class SVGContextPaint;
+namespace dom {
+class Document;
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * Wraps an SVG document contained in the SVG table of an OpenType font.
+ * There may be multiple SVG documents in an SVG table which we lazily parse
+ * so we have an instance of this class for every document in the SVG table
+ * which contains a glyph ID which has been used
+ * Finds and looks up elements contained in the SVG document which have glyph
+ * mappings to be drawn by gfxSVGGlyphs
+ */
+class gfxSVGGlyphsDocument final : public nsAPostRefreshObserver {
+ typedef mozilla::dom::Element Element;
+
+ public:
+ gfxSVGGlyphsDocument(const uint8_t* aBuffer, uint32_t aBufLen,
+ gfxSVGGlyphs* aSVGGlyphs);
+
+ Element* GetGlyphElement(uint32_t aGlyphId);
+
+ ~gfxSVGGlyphsDocument();
+
+ void DidRefresh() override;
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ nsresult ParseDocument(const uint8_t* aBuffer, uint32_t aBufLen);
+
+ nsresult SetupPresentation();
+
+ void FindGlyphElements(Element* aElement);
+
+ void InsertGlyphId(Element* aGlyphElement);
+
+ // Weak so as not to create a cycle. mOwner owns us so this can't dangle.
+ gfxSVGGlyphs* mOwner;
+ RefPtr<mozilla::dom::Document> mDocument;
+ nsCOMPtr<nsIContentViewer> mViewer;
+ RefPtr<mozilla::PresShell> mPresShell;
+
+ nsBaseHashtable<nsUint32HashKey, Element*, Element*> mGlyphIdMap;
+};
+
+/**
+ * Used by |gfxFontEntry| to represent the SVG table of an OpenType font.
+ * Handles lazy parsing of the SVG documents in the table, looking up SVG glyphs
+ * and rendering SVG glyphs.
+ * Each |gfxFontEntry| owns at most one |gfxSVGGlyphs| instance.
+ */
+class gfxSVGGlyphs {
+ private:
+ typedef mozilla::dom::Element Element;
+
+ public:
+ /**
+ * @param aSVGTable The SVG table from the OpenType font
+ *
+ * The gfxSVGGlyphs object takes over ownership of the blob references
+ * that are passed in, and will hb_blob_destroy() them when finished;
+ * the caller should -not- destroy these references.
+ */
+ gfxSVGGlyphs(hb_blob_t* aSVGTable, gfxFontEntry* aFontEntry);
+
+ /**
+ * Releases our references to the SVG table and cleans up everything else.
+ */
+ ~gfxSVGGlyphs();
+
+ /**
+ * This is called when the refresh driver has ticked.
+ */
+ void DidRefresh();
+
+ /**
+ * Find the |gfxSVGGlyphsDocument| containing an SVG glyph for |aGlyphId|.
+ * If |aGlyphId| does not map to an SVG document, return null.
+ * If a |gfxSVGGlyphsDocument| has not been created for the document, create
+ * one.
+ */
+ gfxSVGGlyphsDocument* FindOrCreateGlyphsDocument(uint32_t aGlyphId);
+
+ /**
+ * Return true iff there is an SVG glyph for |aGlyphId|
+ */
+ bool HasSVGGlyph(uint32_t aGlyphId);
+
+ /**
+ * Render the SVG glyph for |aGlyphId|
+ * @param aContextPaint Information on text context paints.
+ * See |SVGContextPaint|.
+ */
+ void RenderGlyph(gfxContext* aContext, uint32_t aGlyphId,
+ mozilla::SVGContextPaint* aContextPaint);
+
+ /**
+ * Get the extents for the SVG glyph associated with |aGlyphId|
+ * @param aSVGToAppSpace The matrix mapping the SVG glyph space to the
+ * target context space
+ */
+ bool GetGlyphExtents(uint32_t aGlyphId, const gfxMatrix& aSVGToAppSpace,
+ gfxRect* aResult);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ gfxFontEntry* FontEntry() const { return mFontEntry; }
+
+ private:
+ Element* GetGlyphElement(uint32_t aGlyphId);
+
+ nsClassHashtable<nsUint32HashKey, gfxSVGGlyphsDocument> mGlyphDocs;
+ nsBaseHashtable<nsUint32HashKey, Element*, Element*> mGlyphIdMap;
+
+ hb_blob_t* mSVGData;
+
+ // pointer to the font entry that owns this gfxSVGGlyphs object
+ gfxFontEntry* MOZ_NON_OWNING_REF mFontEntry;
+
+ const struct Header {
+ mozilla::AutoSwap_PRUint16 mVersion;
+ mozilla::AutoSwap_PRUint32 mDocIndexOffset;
+ mozilla::AutoSwap_PRUint32 mColorPalettesOffset;
+ }* mHeader;
+
+ struct IndexEntry {
+ mozilla::AutoSwap_PRUint16 mStartGlyph;
+ mozilla::AutoSwap_PRUint16 mEndGlyph;
+ mozilla::AutoSwap_PRUint32 mDocOffset;
+ mozilla::AutoSwap_PRUint32 mDocLength;
+ };
+
+ const struct DocIndex {
+ mozilla::AutoSwap_PRUint16 mNumEntries;
+ IndexEntry mEntries[1]; /* actual length = mNumEntries */
+ }* mDocIndex;
+
+ static int CompareIndexEntries(const void* _a, const void* _b);
+};
+
+/**
+ * XXX This is a complete hack and should die (see bug 1291494).
+ *
+ * This class is used when code fails to pass through an SVGContextPaint from
+ * the context in which we are painting. In that case we create one of these
+ * as a fallback and have it wrap the gfxContext's current gfxPattern and
+ * pretend that that is the paint context's fill pattern. In some contexts
+ * that will be the case, in others it will not. As we convert more code to
+ * Moz2D the less likely it is that this hack will work. It will also make
+ * converting to Moz2D harder.
+ */
+class SimpleTextContextPaint : public mozilla::SVGContextPaint {
+ private:
+ static const mozilla::gfx::DeviceColor sZero;
+
+ static gfxMatrix SetupDeviceToPatternMatrix(gfxPattern* aPattern,
+ const gfxMatrix& aCTM) {
+ if (!aPattern) {
+ return gfxMatrix();
+ }
+ gfxMatrix deviceToUser = aCTM;
+ if (!deviceToUser.Invert()) {
+ return gfxMatrix(0, 0, 0, 0, 0, 0); // singular
+ }
+ return deviceToUser * aPattern->GetMatrix();
+ }
+
+ public:
+ SimpleTextContextPaint(gfxPattern* aFillPattern, gfxPattern* aStrokePattern,
+ const gfxMatrix& aCTM)
+ : mFillPattern(aFillPattern ? aFillPattern : new gfxPattern(sZero)),
+ mStrokePattern(aStrokePattern ? aStrokePattern
+ : new gfxPattern(sZero)) {
+ mFillMatrix = SetupDeviceToPatternMatrix(aFillPattern, aCTM);
+ mStrokeMatrix = SetupDeviceToPatternMatrix(aStrokePattern, aCTM);
+ }
+
+ already_AddRefed<gfxPattern> GetFillPattern(
+ const DrawTarget* aDrawTarget, float aOpacity, const gfxMatrix& aCTM,
+ imgDrawingParams& aImgParams) override {
+ if (mFillPattern) {
+ mFillPattern->SetMatrix(aCTM * mFillMatrix);
+ }
+ RefPtr<gfxPattern> fillPattern = mFillPattern;
+ return fillPattern.forget();
+ }
+
+ already_AddRefed<gfxPattern> GetStrokePattern(
+ const DrawTarget* aDrawTarget, float aOpacity, const gfxMatrix& aCTM,
+ imgDrawingParams& aImgParams) override {
+ if (mStrokePattern) {
+ mStrokePattern->SetMatrix(aCTM * mStrokeMatrix);
+ }
+ RefPtr<gfxPattern> strokePattern = mStrokePattern;
+ return strokePattern.forget();
+ }
+
+ float GetFillOpacity() const override { return mFillPattern ? 1.0f : 0.0f; }
+
+ float GetStrokeOpacity() const override {
+ return mStrokePattern ? 1.0f : 0.0f;
+ }
+
+ private:
+ RefPtr<gfxPattern> mFillPattern;
+ RefPtr<gfxPattern> mStrokePattern;
+
+ // Device space to pattern space transforms
+ gfxMatrix mFillMatrix;
+ gfxMatrix mStrokeMatrix;
+};
+
+#endif
diff --git a/gfx/thebes/gfxScriptItemizer.cpp b/gfx/thebes/gfxScriptItemizer.cpp
new file mode 100644
index 0000000000..29a2b4d0ed
--- /dev/null
+++ b/gfx/thebes/gfxScriptItemizer.cpp
@@ -0,0 +1,256 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file is based on usc_impl.c from ICU 4.2.0.1, slightly adapted
+ * for use within Mozilla Gecko, separate from a standard ICU build.
+ *
+ * The original ICU license of the code follows:
+ *
+ * ICU License - ICU 1.8.1 and later
+ *
+ * COPYRIGHT AND PERMISSION NOTICE
+ *
+ * Copyright (c) 1995-2009 International Business Machines Corporation and
+ * others
+ *
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, provided that the above copyright notice(s) and this
+ * permission notice appear in all copies of the Software and that both the
+ * above copyright notice(s) and this permission notice appear in supporting
+ * documentation.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
+ * BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
+ * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+ * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of a copyright holder shall
+ * not be used in advertising or otherwise to promote the sale, use or other
+ * dealings in this Software without prior written authorization of the
+ * copyright holder.
+ *
+ * All trademarks and registered trademarks mentioned herein are the property
+ * of their respective owners.
+ */
+
+#include "gfxScriptItemizer.h"
+#include "mozilla/intl/UnicodeProperties.h"
+#include "nsCharTraits.h"
+#include "nsUnicodeProperties.h"
+#include "harfbuzz/hb.h"
+
+using namespace mozilla::intl;
+using namespace mozilla::unicode;
+
+#define MOD(sp) ((sp) % PAREN_STACK_DEPTH)
+#define LIMIT_INC(sp) \
+ (((sp) < PAREN_STACK_DEPTH) ? (sp) + 1 : PAREN_STACK_DEPTH)
+#define INC(sp, count) (MOD((sp) + (count)))
+#define INC1(sp) (INC(sp, 1))
+#define DEC(sp, count) (MOD((sp) + PAREN_STACK_DEPTH - (count)))
+#define DEC1(sp) (DEC(sp, 1))
+#define STACK_IS_EMPTY() (pushCount <= 0)
+#define STACK_IS_NOT_EMPTY() (!STACK_IS_EMPTY())
+#define TOP() (parenStack[parenSP])
+#define SYNC_FIXUP() (fixupCount = 0)
+
+void gfxScriptItemizer::push(uint32_t endPairChar, Script newScriptCode) {
+ pushCount = LIMIT_INC(pushCount);
+ fixupCount = LIMIT_INC(fixupCount);
+
+ parenSP = INC1(parenSP);
+ parenStack[parenSP].endPairChar = endPairChar;
+ parenStack[parenSP].scriptCode = newScriptCode;
+}
+
+void gfxScriptItemizer::pop() {
+ if (STACK_IS_EMPTY()) {
+ return;
+ }
+
+ if (fixupCount > 0) {
+ fixupCount -= 1;
+ }
+
+ pushCount -= 1;
+ parenSP = DEC1(parenSP);
+
+ /* If the stack is now empty, reset the stack
+ pointers to their initial values.
+ */
+ if (STACK_IS_EMPTY()) {
+ parenSP = -1;
+ }
+}
+
+void gfxScriptItemizer::fixup(Script newScriptCode) {
+ int32_t fixupSP = DEC(parenSP, fixupCount);
+
+ while (fixupCount-- > 0) {
+ fixupSP = INC1(fixupSP);
+ parenStack[fixupSP].scriptCode = newScriptCode;
+ }
+}
+
+static inline bool CanMergeWithContext(Script aScript) {
+ return aScript <= Script::INHERITED || aScript == Script::UNKNOWN;
+}
+
+// We regard the current char as having the same script as the in-progress run
+// if either script is Common/Inherited/Unknown, or if the run script appears
+// in the character's ScriptExtensions, or if the char is a cluster extender.
+static inline bool SameScript(Script runScript, Script currCharScript,
+ uint32_t aCurrCh) {
+ return CanMergeWithContext(runScript) ||
+ CanMergeWithContext(currCharScript) || currCharScript == runScript ||
+ IsClusterExtender(aCurrCh) ||
+ UnicodeProperties::HasScript(aCurrCh, runScript);
+}
+
+gfxScriptItemizer::gfxScriptItemizer(const char16_t* src, uint32_t length)
+ : textPtr(src), textLength(length) {
+ reset();
+}
+
+void gfxScriptItemizer::SetText(const char16_t* src, uint32_t length) {
+ textPtr = src;
+ textLength = length;
+
+ reset();
+}
+
+bool gfxScriptItemizer::Next(uint32_t& aRunStart, uint32_t& aRunLimit,
+ Script& aRunScript) {
+ /* if we've fallen off the end of the text, we're done */
+ if (scriptLimit >= textLength) {
+ return false;
+ }
+
+ SYNC_FIXUP();
+ scriptCode = Script::COMMON;
+ Script fallbackScript = Script::UNKNOWN;
+
+ for (scriptStart = scriptLimit; scriptLimit < textLength; scriptLimit += 1) {
+ uint32_t ch;
+ Script sc;
+ uint32_t startOfChar = scriptLimit;
+
+ ch = textPtr[scriptLimit];
+
+ /* decode UTF-16 (may be surrogate pair) */
+ if (NS_IS_HIGH_SURROGATE(ch) && scriptLimit < textLength - 1) {
+ uint32_t low = textPtr[scriptLimit + 1];
+ if (NS_IS_LOW_SURROGATE(low)) {
+ ch = SURROGATE_TO_UCS4(ch, low);
+ scriptLimit += 1;
+ }
+ }
+
+ // Initialize gc to UNASSIGNED; we'll only set it to the true GC
+ // if the character has script=COMMON, otherwise we don't care.
+ uint8_t gc = HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED;
+
+ sc = UnicodeProperties::GetScriptCode(ch);
+ if (sc == Script::COMMON) {
+ /*
+ * Paired character handling:
+ *
+ * if it's an open character, push it onto the stack.
+ * if it's a close character, find the matching open on the
+ * stack, and use that script code. Any non-matching open
+ * characters above it on the stack will be popped.
+ *
+ * We only do this if the script is COMMON; for chars with
+ * specific script assignments, we just use them as-is.
+ */
+ gc = GetGeneralCategory(ch);
+ if (gc == HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION) {
+ uint32_t endPairChar = UnicodeProperties::CharMirror(ch);
+ if (endPairChar != ch) {
+ push(endPairChar, scriptCode);
+ }
+ } else if (gc == HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION &&
+ UnicodeProperties::IsMirrored(ch)) {
+ while (STACK_IS_NOT_EMPTY() && TOP().endPairChar != ch) {
+ pop();
+ }
+
+ if (STACK_IS_NOT_EMPTY()) {
+ sc = TOP().scriptCode;
+ }
+ }
+ }
+
+ // Both Hiragana and Katakana are shaped as OpenType 'kana'. Merge them
+ // here to avoid script-run breaks and allow kerning to apply between the
+ // two alphabets.
+ if (sc == Script::HIRAGANA) {
+ sc = Script::KATAKANA;
+ }
+
+ if (SameScript(scriptCode, sc, ch)) {
+ if (scriptCode == Script::COMMON) {
+ // If we have not yet resolved a specific scriptCode for the run,
+ // check whether this character provides it.
+ if (!CanMergeWithContext(sc)) {
+ // Use this character's script.
+ scriptCode = sc;
+ fixup(scriptCode);
+ } else if (fallbackScript == Script::UNKNOWN) {
+ // See if the character has a ScriptExtensions property we can
+ // store for use in the event the run remains unresolved.
+ UnicodeProperties::ScriptExtensionVector extensions;
+ auto extResult = UnicodeProperties::GetExtensions(ch, extensions);
+ if (extResult.isOk()) {
+ Script ext = Script(extensions[0]);
+ if (!CanMergeWithContext(ext)) {
+ fallbackScript = ext;
+ }
+ }
+ }
+ }
+
+ /*
+ * if this character is a close paired character,
+ * pop the matching open character from the stack
+ */
+ if (gc == HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION &&
+ UnicodeProperties::IsMirrored(ch)) {
+ pop();
+ }
+ } else {
+ /*
+ * reset scriptLimit in case it was advanced during reading a
+ * multiple-code-unit character
+ */
+ scriptLimit = startOfChar;
+
+ break;
+ }
+ }
+
+ aRunStart = scriptStart;
+ aRunLimit = scriptLimit;
+
+ if (scriptCode == Script::COMMON && fallbackScript != Script::UNKNOWN) {
+ aRunScript = fallbackScript;
+ } else {
+ aRunScript = scriptCode;
+ }
+
+ return true;
+}
diff --git a/gfx/thebes/gfxScriptItemizer.h b/gfx/thebes/gfxScriptItemizer.h
new file mode 100644
index 0000000000..6deb37e19c
--- /dev/null
+++ b/gfx/thebes/gfxScriptItemizer.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file is based on usc_impl.c from ICU 4.2.0.1, slightly adapted
+ * for use within Mozilla Gecko, separate from a standard ICU build.
+ *
+ * The original ICU license of the code follows:
+ *
+ * ICU License - ICU 1.8.1 and later
+ *
+ * COPYRIGHT AND PERMISSION NOTICE
+ *
+ * Copyright (c) 1995-2009 International Business Machines Corporation and
+ * others
+ *
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, provided that the above copyright notice(s) and this
+ * permission notice appear in all copies of the Software and that both the
+ * above copyright notice(s) and this permission notice appear in supporting
+ * documentation.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
+ * BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
+ * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+ * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of a copyright holder shall
+ * not be used in advertising or otherwise to promote the sale, use or other
+ * dealings in this Software without prior written authorization of the
+ * copyright holder.
+ *
+ * All trademarks and registered trademarks mentioned herein are the property
+ * of their respective owners.
+ */
+
+#ifndef GFX_SCRIPTITEMIZER_H
+#define GFX_SCRIPTITEMIZER_H
+
+#include <stdint.h>
+#include "mozilla/intl/UnicodeScriptCodes.h"
+
+#define PAREN_STACK_DEPTH 32
+
+class gfxScriptItemizer {
+ public:
+ typedef mozilla::intl::Script Script;
+
+ gfxScriptItemizer(const char16_t* src, uint32_t length);
+
+ void SetText(const char16_t* src, uint32_t length);
+
+ bool Next(uint32_t& aRunStart, uint32_t& aRunLimit, Script& aRunScript);
+
+ protected:
+ void reset() {
+ scriptStart = 0;
+ scriptLimit = 0;
+ scriptCode = Script::INVALID;
+ parenSP = -1;
+ pushCount = 0;
+ fixupCount = 0;
+ }
+
+ void push(uint32_t endPairChar, Script newScriptCode);
+ void pop();
+ void fixup(Script newScriptCode);
+
+ struct ParenStackEntry {
+ uint32_t endPairChar;
+ Script scriptCode;
+ };
+
+ const char16_t* textPtr;
+ uint32_t textLength;
+
+ uint32_t scriptStart;
+ uint32_t scriptLimit;
+ Script scriptCode;
+
+ struct ParenStackEntry parenStack[PAREN_STACK_DEPTH];
+ uint32_t parenSP;
+ uint32_t pushCount;
+ uint32_t fixupCount;
+};
+
+#endif /* GFX_SCRIPTITEMIZER_H */
diff --git a/gfx/thebes/gfxSharedImageSurface.h b/gfx/thebes/gfxSharedImageSurface.h
new file mode 100644
index 0000000000..b8bf461ed1
--- /dev/null
+++ b/gfx/thebes/gfxSharedImageSurface.h
@@ -0,0 +1,27 @@
+// vim:set ts=4 sts=2 sw=2 et cin:
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_SHARED_IMAGESURFACE_H
+#define GFX_SHARED_IMAGESURFACE_H
+
+#include "gfxBaseSharedMemorySurface.h"
+
+class gfxSharedImageSurface
+ : public gfxBaseSharedMemorySurface<gfxImageSurface,
+ gfxSharedImageSurface> {
+ typedef gfxBaseSharedMemorySurface<gfxImageSurface, gfxSharedImageSurface>
+ Super;
+ friend class gfxBaseSharedMemorySurface<gfxImageSurface,
+ gfxSharedImageSurface>;
+
+ private:
+ gfxSharedImageSurface(const mozilla::gfx::IntSize& aSize, long aStride,
+ gfxImageFormat aFormat,
+ const mozilla::ipc::Shmem& aShmem)
+ : Super(aSize, aStride, aFormat, aShmem) {}
+};
+
+#endif /* GFX_SHARED_IMAGESURFACE_H */
diff --git a/gfx/thebes/gfxSkipChars.cpp b/gfx/thebes/gfxSkipChars.cpp
new file mode 100644
index 0000000000..cd230efe74
--- /dev/null
+++ b/gfx/thebes/gfxSkipChars.cpp
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxSkipChars.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/gfx/Logging.h"
+
+struct SkippedRangeStartComparator {
+ const uint32_t mOffset;
+ explicit SkippedRangeStartComparator(const uint32_t aOffset)
+ : mOffset(aOffset) {}
+ int operator()(const gfxSkipChars::SkippedRange& aRange) const {
+ return (mOffset < aRange.Start()) ? -1 : 1;
+ }
+};
+
+void gfxSkipCharsIterator::SetOriginalOffset(int32_t aOffset) {
+ aOffset += mOriginalStringToSkipCharsOffset;
+ if (MOZ_UNLIKELY(uint32_t(aOffset) > mSkipChars->mCharCount)) {
+ gfxCriticalError() << "invalid offset " << aOffset
+ << " for gfxSkipChars length " << mSkipChars->mCharCount;
+ aOffset = mSkipChars->mCharCount;
+ }
+
+ mOriginalStringOffset = aOffset;
+
+ const uint32_t rangeCount = mSkipChars->mRanges.Length();
+ if (rangeCount == 0) {
+ mSkippedStringOffset = aOffset;
+ return;
+ }
+
+ // at start of string?
+ if (aOffset == 0) {
+ mSkippedStringOffset = 0;
+ mCurrentRangeIndex =
+ rangeCount && mSkipChars->mRanges[0].Start() == 0 ? 0 : -1;
+ return;
+ }
+
+ // find the range that includes or precedes aOffset
+ const nsTArray<gfxSkipChars::SkippedRange>& ranges = mSkipChars->mRanges;
+ size_t idx;
+ mozilla::BinarySearchIf(ranges, 0, rangeCount,
+ SkippedRangeStartComparator(aOffset), &idx);
+
+ if (idx == rangeCount) {
+ mCurrentRangeIndex = rangeCount - 1;
+ } else if (uint32_t(aOffset) < ranges[idx].Start()) {
+ mCurrentRangeIndex = idx - 1;
+ if (mCurrentRangeIndex == -1) {
+ mSkippedStringOffset = aOffset;
+ return;
+ }
+ } else {
+ mCurrentRangeIndex = idx;
+ }
+
+ const gfxSkipChars::SkippedRange& r = ranges[mCurrentRangeIndex];
+ if (uint32_t(aOffset) < r.End()) {
+ mSkippedStringOffset = r.SkippedOffset();
+ return;
+ }
+
+ mSkippedStringOffset = aOffset - r.NextDelta();
+}
+
+struct SkippedRangeOffsetComparator {
+ const uint32_t mOffset;
+ explicit SkippedRangeOffsetComparator(const uint32_t aOffset)
+ : mOffset(aOffset) {}
+ int operator()(const gfxSkipChars::SkippedRange& aRange) const {
+ return (mOffset < aRange.SkippedOffset()) ? -1 : 1;
+ }
+};
+
+void gfxSkipCharsIterator::SetSkippedOffset(uint32_t aOffset) {
+ NS_ASSERTION(
+ (mSkipChars->mRanges.IsEmpty() && aOffset <= mSkipChars->mCharCount) ||
+ (aOffset <= mSkipChars->LastRange().SkippedOffset() +
+ mSkipChars->mCharCount -
+ mSkipChars->LastRange().End()),
+ "Invalid skipped offset");
+ mSkippedStringOffset = aOffset;
+
+ uint32_t rangeCount = mSkipChars->mRanges.Length();
+ if (rangeCount == 0) {
+ mOriginalStringOffset = aOffset;
+ return;
+ }
+
+ const nsTArray<gfxSkipChars::SkippedRange>& ranges = mSkipChars->mRanges;
+ size_t idx;
+ mozilla::BinarySearchIf(ranges, 0, rangeCount,
+ SkippedRangeOffsetComparator(aOffset), &idx);
+
+ if (idx == rangeCount) {
+ mCurrentRangeIndex = rangeCount - 1;
+ } else if (aOffset < ranges[idx].SkippedOffset()) {
+ mCurrentRangeIndex = idx - 1;
+ if (mCurrentRangeIndex == -1) {
+ mOriginalStringOffset = aOffset;
+ return;
+ }
+ } else {
+ mCurrentRangeIndex = idx;
+ }
+
+ const gfxSkipChars::SkippedRange& r = ranges[mCurrentRangeIndex];
+ mOriginalStringOffset = r.End() + aOffset - r.SkippedOffset();
+}
+
+bool gfxSkipCharsIterator::IsOriginalCharSkipped(int32_t* aRunLength) const {
+ if (mCurrentRangeIndex == -1) {
+ // we're before the first skipped range (if any)
+ if (aRunLength) {
+ uint32_t end = mSkipChars->mRanges.IsEmpty()
+ ? mSkipChars->mCharCount
+ : mSkipChars->mRanges[0].Start();
+ *aRunLength = end - mOriginalStringOffset;
+ }
+ return mSkipChars->mCharCount == uint32_t(mOriginalStringOffset);
+ }
+
+ const gfxSkipChars::SkippedRange& range =
+ mSkipChars->mRanges[mCurrentRangeIndex];
+
+ if (uint32_t(mOriginalStringOffset) < range.End()) {
+ if (aRunLength) {
+ *aRunLength = range.End() - mOriginalStringOffset;
+ }
+ return true;
+ }
+
+ if (aRunLength) {
+ uint32_t end =
+ uint32_t(mCurrentRangeIndex) + 1 < mSkipChars->mRanges.Length()
+ ? mSkipChars->mRanges[mCurrentRangeIndex + 1].Start()
+ : mSkipChars->mCharCount;
+ *aRunLength = end - mOriginalStringOffset;
+ }
+
+ return mSkipChars->mCharCount == uint32_t(mOriginalStringOffset);
+}
diff --git a/gfx/thebes/gfxSkipChars.h b/gfx/thebes/gfxSkipChars.h
new file mode 100644
index 0000000000..39b46c2806
--- /dev/null
+++ b/gfx/thebes/gfxSkipChars.h
@@ -0,0 +1,253 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_SKIP_CHARS_H
+#define GFX_SKIP_CHARS_H
+
+#include "mozilla/Attributes.h"
+#include "nsTArray.h"
+
+/*
+ * gfxSkipChars is a data structure representing a list of characters that
+ * have been skipped. The initial string is called the "original string"
+ * and after skipping some characters, the result is called the "skipped
+ * string". gfxSkipChars provides efficient ways to translate between offsets in
+ * the original string and the skipped string. It is used by textrun code to
+ * keep track of offsets before and after text transformations such as
+ * whitespace compression and control code deletion.
+ */
+
+/**
+ * The gfxSkipChars is represented as a sorted array of skipped ranges.
+ *
+ * A freshly-created gfxSkipChars means "all chars kept".
+ */
+class gfxSkipChars {
+ friend struct SkippedRangeStartComparator;
+ friend struct SkippedRangeOffsetComparator;
+
+ private:
+ class SkippedRange {
+ public:
+ SkippedRange(uint32_t aOffset, uint32_t aLength, uint32_t aDelta)
+ : mOffset(aOffset), mLength(aLength), mDelta(aDelta) {}
+
+ uint32_t Start() const { return mOffset; }
+
+ uint32_t End() const { return mOffset + mLength; }
+
+ uint32_t Length() const { return mLength; }
+
+ uint32_t SkippedOffset() const { return mOffset - mDelta; }
+
+ uint32_t Delta() const { return mDelta; }
+
+ uint32_t NextDelta() const { return mDelta + mLength; }
+
+ void Extend(uint32_t aChars) { mLength += aChars; }
+
+ private:
+ uint32_t mOffset; // original-string offset at which we want to skip
+ uint32_t mLength; // number of skipped chars at this offset
+ uint32_t mDelta; // sum of lengths of preceding skipped-ranges
+ };
+
+ public:
+ gfxSkipChars() : mCharCount(0) {}
+
+ void SkipChars(uint32_t aChars) {
+ NS_ASSERTION(mCharCount + aChars > mCharCount, "Character count overflow");
+ uint32_t rangeCount = mRanges.Length();
+ uint32_t delta = 0;
+ if (rangeCount > 0) {
+ SkippedRange& lastRange = mRanges[rangeCount - 1];
+ if (lastRange.End() == mCharCount) {
+ lastRange.Extend(aChars);
+ mCharCount += aChars;
+ return;
+ }
+ delta = lastRange.NextDelta();
+ }
+ mRanges.AppendElement(SkippedRange(mCharCount, aChars, delta));
+ mCharCount += aChars;
+ }
+
+ void KeepChars(uint32_t aChars) {
+ NS_ASSERTION(mCharCount + aChars > mCharCount, "Character count overflow");
+ mCharCount += aChars;
+ }
+
+ void SkipChar() { SkipChars(1); }
+
+ void KeepChar() { KeepChars(1); }
+
+ void TakeFrom(gfxSkipChars* aSkipChars) {
+ mRanges = std::move(aSkipChars->mRanges);
+ mCharCount = aSkipChars->mCharCount;
+ aSkipChars->mCharCount = 0;
+ }
+
+ int32_t GetOriginalCharCount() const { return mCharCount; }
+
+ const SkippedRange& LastRange() const {
+ // this is only valid if mRanges is non-empty; no assertion here
+ // because nsTArray will already assert if we abuse it
+ return mRanges[mRanges.Length() - 1];
+ }
+
+ friend class gfxSkipCharsIterator;
+
+ private:
+ nsTArray<SkippedRange> mRanges;
+ uint32_t mCharCount;
+};
+
+/**
+ * A gfxSkipCharsIterator represents a position in the original string. It lets
+ * you map efficiently to and from positions in the string after skipped
+ * characters have been removed. You can also specify an offset that is added to
+ * all incoming original string offsets and subtracted from all outgoing
+ * original string offsets --- useful when the gfxSkipChars corresponds to
+ * something offset from the original DOM coordinates, which it often does for
+ * gfxTextRuns.
+ *
+ * The current positions (in both the original and skipped strings) are
+ * always constrained to be >= 0 and <= the string length. When the position
+ * is equal to the string length, it is at the end of the string. The current
+ * positions do not include any aOriginalStringToSkipCharsOffset.
+ *
+ * When the position in the original string corresponds to a skipped character,
+ * the skipped-characters offset is the offset of the next unskipped character,
+ * or the skipped-characters string length if there is no next unskipped
+ * character.
+ */
+class MOZ_STACK_CLASS gfxSkipCharsIterator {
+ public:
+ /**
+ * @param aOriginalStringToSkipCharsOffset add this to all incoming and
+ * outgoing original string offsets
+ */
+ gfxSkipCharsIterator(const gfxSkipChars& aSkipChars,
+ int32_t aOriginalStringToSkipCharsOffset,
+ int32_t aOriginalStringOffset)
+ : mSkipChars(&aSkipChars),
+ mOriginalStringOffset(0),
+ mSkippedStringOffset(0),
+ mCurrentRangeIndex(-1),
+ mOriginalStringToSkipCharsOffset(aOriginalStringToSkipCharsOffset) {
+ SetOriginalOffset(aOriginalStringOffset);
+ }
+
+ explicit gfxSkipCharsIterator(const gfxSkipChars& aSkipChars,
+ int32_t aOriginalStringToSkipCharsOffset = 0)
+ : mSkipChars(&aSkipChars),
+ mOriginalStringOffset(0),
+ mSkippedStringOffset(0),
+ mOriginalStringToSkipCharsOffset(aOriginalStringToSkipCharsOffset) {
+ mCurrentRangeIndex =
+ mSkipChars->mRanges.IsEmpty() || mSkipChars->mRanges[0].Start() > 0 ? -1
+ : 0;
+ }
+
+ gfxSkipCharsIterator(const gfxSkipCharsIterator& aIterator) = default;
+
+ /**
+ * The empty constructor creates an object that is useless until it is
+ * assigned.
+ */
+ gfxSkipCharsIterator()
+ : mSkipChars(nullptr),
+ mOriginalStringOffset(0),
+ mSkippedStringOffset(0),
+ mCurrentRangeIndex(0),
+ mOriginalStringToSkipCharsOffset(0) {}
+
+ /**
+ * Return true if this iterator is properly initialized and usable.
+ */
+ bool IsInitialized() const { return mSkipChars != nullptr; }
+
+ /**
+ * Set the iterator to aOriginalStringOffset in the original string.
+ * This can efficiently move forward or backward from the current position.
+ * aOriginalStringOffset is clamped to [0,originalStringLength].
+ */
+ void SetOriginalOffset(int32_t aOriginalStringOffset);
+
+ /**
+ * Set the iterator to aSkippedStringOffset in the skipped string.
+ * This can efficiently move forward or backward from the current position.
+ * aSkippedStringOffset is clamped to [0,skippedStringLength].
+ */
+ void SetSkippedOffset(uint32_t aSkippedStringOffset);
+
+ uint32_t ConvertOriginalToSkipped(int32_t aOriginalStringOffset) {
+ SetOriginalOffset(aOriginalStringOffset);
+ return GetSkippedOffset();
+ }
+
+ int32_t ConvertSkippedToOriginal(uint32_t aSkippedStringOffset) {
+ SetSkippedOffset(aSkippedStringOffset);
+ return GetOriginalOffset();
+ }
+
+ /**
+ * Test if the character at the current position in the original string
+ * is skipped or not. If aRunLength is non-null, then *aRunLength is set
+ * to a number of characters all of which are either skipped or not, starting
+ * at this character. When the current position is at the end of the original
+ * string, we return true and *aRunLength is set to zero.
+ */
+ bool IsOriginalCharSkipped(int32_t* aRunLength = nullptr) const;
+
+ void AdvanceOriginal(int32_t aDelta) {
+ SetOriginalOffset(GetOriginalOffset() + aDelta);
+ }
+
+ void AdvanceSkipped(int32_t aDelta) {
+ SetSkippedOffset(GetSkippedOffset() + aDelta);
+ }
+
+ /**
+ * @return the offset within the original string
+ */
+ int32_t GetOriginalOffset() const {
+ return mOriginalStringOffset - mOriginalStringToSkipCharsOffset;
+ }
+
+ /**
+ * @return the offset within the skipped string corresponding to the
+ * current position in the original string. If the current position
+ * in the original string is a character that is skipped, then we return
+ * the position corresponding to the first non-skipped character in the
+ * original string after the current position, or the length of the skipped
+ * string if there is no such character.
+ */
+ uint32_t GetSkippedOffset() const { return mSkippedStringOffset; }
+
+ int32_t GetOriginalEnd() const {
+ return mSkipChars->GetOriginalCharCount() -
+ mOriginalStringToSkipCharsOffset;
+ }
+
+ private:
+ const gfxSkipChars* mSkipChars;
+
+ // Current position
+ int32_t mOriginalStringOffset;
+ uint32_t mSkippedStringOffset;
+
+ // Index of the last skippedRange that precedes or contains the current
+ // position in the original string.
+ // If index == -1 then we are before the first skipped char.
+ int32_t mCurrentRangeIndex;
+
+ // This offset is added to map from "skipped+unskipped characters in
+ // the original DOM string" character space to "skipped+unskipped
+ // characters in the textrun's gfxSkipChars" character space
+ int32_t mOriginalStringToSkipCharsOffset;
+};
+
+#endif /*GFX_SKIP_CHARS_H*/
diff --git a/gfx/thebes/gfxTextRun.cpp b/gfx/thebes/gfxTextRun.cpp
new file mode 100644
index 0000000000..447594ba66
--- /dev/null
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -0,0 +1,3852 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxTextRun.h"
+#include "gfxGlyphExtents.h"
+#include "gfxHarfBuzzShaper.h"
+#include "gfxPlatformFontList.h"
+#include "gfxUserFontSet.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticPresData.h"
+
+#include "gfxContext.h"
+#include "gfxFontConstants.h"
+#include "gfxFontMissingGlyphs.h"
+#include "gfxScriptItemizer.h"
+#include "nsUnicodeProperties.h"
+#include "nsStyleConsts.h"
+#include "nsStyleUtil.h"
+#include "mozilla/Likely.h"
+#include "gfx2DGlue.h"
+#include "mozilla/gfx/Logging.h" // for gfxCriticalError
+#include "mozilla/intl/String.h"
+#include "mozilla/intl/UnicodeProperties.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "SharedFontList-impl.h"
+#include "TextDrawTarget.h"
+
+#ifdef XP_WIN
+# include "gfxWindowsPlatform.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::intl;
+using namespace mozilla::unicode;
+using mozilla::services::GetObserverService;
+
+static const char16_t kEllipsisChar[] = {0x2026, 0x0};
+static const char16_t kASCIIPeriodsChar[] = {'.', '.', '.', 0x0};
+
+#ifdef DEBUG_roc
+# define DEBUG_TEXT_RUN_STORAGE_METRICS
+#endif
+
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+extern uint32_t gTextRunStorageHighWaterMark;
+extern uint32_t gTextRunStorage;
+extern uint32_t gFontCount;
+extern uint32_t gGlyphExtentsCount;
+extern uint32_t gGlyphExtentsWidthsTotalSize;
+extern uint32_t gGlyphExtentsSetupEagerSimple;
+extern uint32_t gGlyphExtentsSetupEagerTight;
+extern uint32_t gGlyphExtentsSetupLazyTight;
+extern uint32_t gGlyphExtentsSetupFallBackToTight;
+#endif
+
+void gfxTextRun::GlyphRunIterator::NextRun() {
+ if (mReverse) {
+ if (mGlyphRun == mTextRun->mGlyphRuns.begin()) {
+ mGlyphRun = nullptr;
+ return;
+ }
+ --mGlyphRun;
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(mGlyphRun != mTextRun->mGlyphRuns.end());
+ ++mGlyphRun;
+ if (mGlyphRun == mTextRun->mGlyphRuns.end()) {
+ mGlyphRun = nullptr;
+ return;
+ }
+ }
+ if (mGlyphRun->mCharacterOffset >= mEndOffset) {
+ mGlyphRun = nullptr;
+ return;
+ }
+ uint32_t glyphRunEndOffset = mGlyphRun == mTextRun->mGlyphRuns.end() - 1
+ ? mTextRun->GetLength()
+ : (mGlyphRun + 1)->mCharacterOffset;
+ if (glyphRunEndOffset < mStartOffset) {
+ mGlyphRun = nullptr;
+ return;
+ }
+ mStringEnd = std::min(mEndOffset, glyphRunEndOffset);
+ mStringStart = std::max(mStartOffset, mGlyphRun->mCharacterOffset);
+}
+
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+static void AccountStorageForTextRun(gfxTextRun* aTextRun, int32_t aSign) {
+ // Ignores detailed glyphs... we don't know when those have been constructed
+ // Also ignores gfxSkipChars dynamic storage (which won't be anything
+ // for preformatted text)
+ // Also ignores GlyphRun array, again because it hasn't been constructed
+ // by the time this gets called. If there's only one glyphrun that's stored
+ // directly in the textrun anyway so no additional overhead.
+ uint32_t length = aTextRun->GetLength();
+ int32_t bytes = length * sizeof(gfxTextRun::CompressedGlyph);
+ bytes += sizeof(gfxTextRun);
+ gTextRunStorage += bytes * aSign;
+ gTextRunStorageHighWaterMark =
+ std::max(gTextRunStorageHighWaterMark, gTextRunStorage);
+}
+#endif
+
+bool gfxTextRun::NeedsGlyphExtents() const {
+ if (GetFlags() & gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX) {
+ return true;
+ }
+ for (const auto& run : mGlyphRuns) {
+ if (run.mFont->GetFontEntry()->IsUserFont()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Helper for textRun creation to preallocate storage for glyph records;
+// this function returns a pointer to the newly-allocated glyph storage.
+// Returns nullptr if allocation fails.
+void* gfxTextRun::AllocateStorageForTextRun(size_t aSize, uint32_t aLength) {
+ // Allocate the storage we need, returning nullptr on failure rather than
+ // throwing an exception (because web content can create huge runs).
+ void* storage = malloc(aSize + aLength * sizeof(CompressedGlyph));
+ if (!storage) {
+ NS_WARNING("failed to allocate storage for text run!");
+ return nullptr;
+ }
+
+ // Initialize the glyph storage (beyond aSize) to zero
+ memset(reinterpret_cast<char*>(storage) + aSize, 0,
+ aLength * sizeof(CompressedGlyph));
+
+ return storage;
+}
+
+already_AddRefed<gfxTextRun> gfxTextRun::Create(
+ const gfxTextRunFactory::Parameters* aParams, uint32_t aLength,
+ gfxFontGroup* aFontGroup, gfx::ShapedTextFlags aFlags,
+ nsTextFrameUtils::Flags aFlags2) {
+ void* storage = AllocateStorageForTextRun(sizeof(gfxTextRun), aLength);
+ if (!storage) {
+ return nullptr;
+ }
+
+ RefPtr<gfxTextRun> result =
+ new (storage) gfxTextRun(aParams, aLength, aFontGroup, aFlags, aFlags2);
+ return result.forget();
+}
+
+gfxTextRun::gfxTextRun(const gfxTextRunFactory::Parameters* aParams,
+ uint32_t aLength, gfxFontGroup* aFontGroup,
+ gfx::ShapedTextFlags aFlags,
+ nsTextFrameUtils::Flags aFlags2)
+ : gfxShapedText(aLength, aFlags, aParams->mAppUnitsPerDevUnit),
+ mUserData(aParams->mUserData),
+ mFontGroup(aFontGroup),
+ mFlags2(aFlags2),
+ mReleasedFontGroup(false),
+ mReleasedFontGroupSkippedDrawing(false),
+ mShapingState(eShapingState_Normal) {
+ NS_ASSERTION(mAppUnitsPerDevUnit > 0, "Invalid app unit scale");
+ NS_ADDREF(mFontGroup);
+
+#ifndef RELEASE_OR_BETA
+ gfxTextPerfMetrics* tp = aFontGroup->GetTextPerfMetrics();
+ if (tp) {
+ tp->current.textrunConst++;
+ }
+#endif
+
+ mCharacterGlyphs = reinterpret_cast<CompressedGlyph*>(this + 1);
+
+ if (aParams->mSkipChars) {
+ mSkipChars.TakeFrom(aParams->mSkipChars);
+ }
+
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+ AccountStorageForTextRun(this, 1);
+#endif
+
+ mDontSkipDrawing =
+ !!(aFlags2 & nsTextFrameUtils::Flags::DontSkipDrawingForPendingUserFonts);
+}
+
+gfxTextRun::~gfxTextRun() {
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+ AccountStorageForTextRun(this, -1);
+#endif
+#ifdef DEBUG
+ // Make it easy to detect a dead text run
+ mFlags = ~gfx::ShapedTextFlags();
+ mFlags2 = ~nsTextFrameUtils::Flags();
+#endif
+
+ // The cached ellipsis textrun (if any) in a fontgroup will have already
+ // been told to release its reference to the group, so we mustn't do that
+ // again here.
+ if (!mReleasedFontGroup) {
+#ifndef RELEASE_OR_BETA
+ gfxTextPerfMetrics* tp = mFontGroup->GetTextPerfMetrics();
+ if (tp) {
+ tp->current.textrunDestr++;
+ }
+#endif
+ NS_RELEASE(mFontGroup);
+ }
+}
+
+void gfxTextRun::ReleaseFontGroup() {
+ NS_ASSERTION(!mReleasedFontGroup, "doubly released!");
+
+ // After dropping our reference to the font group, we'll no longer be able
+ // to get up-to-date results for ShouldSkipDrawing(). Store the current
+ // value in mReleasedFontGroupSkippedDrawing.
+ //
+ // (It doesn't actually matter that we can't get up-to-date results for
+ // ShouldSkipDrawing(), since the only text runs that we call
+ // ReleaseFontGroup() for are ellipsis text runs, and we ask the font
+ // group for a new ellipsis text run each time we want to draw one,
+ // and ensure that the cached one is cleared in ClearCachedData() when
+ // font loading status changes.)
+ mReleasedFontGroupSkippedDrawing = mFontGroup->ShouldSkipDrawing();
+
+ NS_RELEASE(mFontGroup);
+ mReleasedFontGroup = true;
+}
+
+bool gfxTextRun::SetPotentialLineBreaks(Range aRange,
+ const uint8_t* aBreakBefore) {
+ NS_ASSERTION(aRange.end <= GetLength(), "Overflow");
+
+ uint32_t changed = 0;
+ CompressedGlyph* cg = mCharacterGlyphs + aRange.start;
+ const CompressedGlyph* const end = cg + aRange.Length();
+ while (cg < end) {
+ uint8_t canBreak = *aBreakBefore++;
+ if (canBreak && !cg->IsClusterStart()) {
+ // XXX If we replace the line-breaker with one based more closely
+ // on UAX#14 (e.g. using ICU), this may not be needed any more.
+ // Avoid possible breaks inside a cluster, EXCEPT when the previous
+ // character was a space (compare UAX#14 rules LB9, LB10).
+ if (cg == mCharacterGlyphs || !(cg - 1)->CharIsSpace()) {
+ canBreak = CompressedGlyph::FLAG_BREAK_TYPE_NONE;
+ }
+ }
+ changed |= cg->SetCanBreakBefore(canBreak);
+ ++cg;
+ }
+ return changed != 0;
+}
+
+gfxTextRun::LigatureData gfxTextRun::ComputeLigatureData(
+ Range aPartRange, const PropertyProvider* aProvider) const {
+ NS_ASSERTION(aPartRange.start < aPartRange.end,
+ "Computing ligature data for empty range");
+ NS_ASSERTION(aPartRange.end <= GetLength(), "Character length overflow");
+
+ LigatureData result;
+ const CompressedGlyph* charGlyphs = mCharacterGlyphs;
+
+ uint32_t i;
+ for (i = aPartRange.start; !charGlyphs[i].IsLigatureGroupStart(); --i) {
+ NS_ASSERTION(i > 0, "Ligature at the start of the run??");
+ }
+ result.mRange.start = i;
+ for (i = aPartRange.start + 1;
+ i < GetLength() && !charGlyphs[i].IsLigatureGroupStart(); ++i) {
+ }
+ result.mRange.end = i;
+
+ int32_t ligatureWidth = GetAdvanceForGlyphs(result.mRange);
+ // Count the number of started clusters we have seen
+ uint32_t totalClusterCount = 0;
+ uint32_t partClusterIndex = 0;
+ uint32_t partClusterCount = 0;
+ for (i = result.mRange.start; i < result.mRange.end; ++i) {
+ // Treat the first character of the ligature as the start of a
+ // cluster for our purposes of allocating ligature width to its
+ // characters.
+ if (i == result.mRange.start || charGlyphs[i].IsClusterStart()) {
+ ++totalClusterCount;
+ if (i < aPartRange.start) {
+ ++partClusterIndex;
+ } else if (i < aPartRange.end) {
+ ++partClusterCount;
+ }
+ }
+ }
+ NS_ASSERTION(totalClusterCount > 0, "Ligature involving no clusters??");
+ result.mPartAdvance = partClusterIndex * (ligatureWidth / totalClusterCount);
+ result.mPartWidth = partClusterCount * (ligatureWidth / totalClusterCount);
+
+ // Any rounding errors are apportioned to the final part of the ligature,
+ // so that measuring all parts of a ligature and summing them is equal to
+ // the ligature width.
+ if (aPartRange.end == result.mRange.end) {
+ gfxFloat allParts = totalClusterCount * (ligatureWidth / totalClusterCount);
+ result.mPartWidth += ligatureWidth - allParts;
+ }
+
+ if (partClusterCount == 0) {
+ // nothing to draw
+ result.mClipBeforePart = result.mClipAfterPart = true;
+ } else {
+ // Determine whether we should clip before or after this part when
+ // drawing its slice of the ligature.
+ // We need to clip before the part if any cluster is drawn before
+ // this part.
+ result.mClipBeforePart = partClusterIndex > 0;
+ // We need to clip after the part if any cluster is drawn after
+ // this part.
+ result.mClipAfterPart =
+ partClusterIndex + partClusterCount < totalClusterCount;
+ }
+
+ if (aProvider && (mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) {
+ gfxFont::Spacing spacing;
+ if (aPartRange.start == result.mRange.start) {
+ aProvider->GetSpacing(Range(aPartRange.start, aPartRange.start + 1),
+ &spacing);
+ result.mPartWidth += spacing.mBefore;
+ }
+ if (aPartRange.end == result.mRange.end) {
+ aProvider->GetSpacing(Range(aPartRange.end - 1, aPartRange.end),
+ &spacing);
+ result.mPartWidth += spacing.mAfter;
+ }
+ }
+
+ return result;
+}
+
+gfxFloat gfxTextRun::ComputePartialLigatureWidth(
+ Range aPartRange, const PropertyProvider* aProvider) const {
+ if (aPartRange.start >= aPartRange.end) return 0;
+ LigatureData data = ComputeLigatureData(aPartRange, aProvider);
+ return data.mPartWidth;
+}
+
+int32_t gfxTextRun::GetAdvanceForGlyphs(Range aRange) const {
+ int32_t advance = 0;
+ for (auto i = aRange.start; i < aRange.end; ++i) {
+ advance += GetAdvanceForGlyph(i);
+ }
+ return advance;
+}
+
+static void GetAdjustedSpacing(
+ const gfxTextRun* aTextRun, gfxTextRun::Range aRange,
+ const gfxTextRun::PropertyProvider& aProvider,
+ gfxTextRun::PropertyProvider::Spacing* aSpacing) {
+ if (aRange.start >= aRange.end) {
+ return;
+ }
+
+ aProvider.GetSpacing(aRange, aSpacing);
+
+#ifdef DEBUG
+ // Check to see if we have spacing inside ligatures
+
+ const gfxTextRun::CompressedGlyph* charGlyphs =
+ aTextRun->GetCharacterGlyphs();
+ uint32_t i;
+
+ for (i = aRange.start; i < aRange.end; ++i) {
+ if (!charGlyphs[i].IsLigatureGroupStart()) {
+ NS_ASSERTION(i == aRange.start || aSpacing[i - aRange.start].mBefore == 0,
+ "Before-spacing inside a ligature!");
+ NS_ASSERTION(
+ i - 1 <= aRange.start || aSpacing[i - 1 - aRange.start].mAfter == 0,
+ "After-spacing inside a ligature!");
+ }
+ }
+#endif
+}
+
+bool gfxTextRun::GetAdjustedSpacingArray(
+ Range aRange, const PropertyProvider* aProvider, Range aSpacingRange,
+ nsTArray<PropertyProvider::Spacing>* aSpacing) const {
+ if (!aProvider || !(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) {
+ return false;
+ }
+ if (!aSpacing->AppendElements(aRange.Length(), fallible)) {
+ return false;
+ }
+ auto spacingOffset = aSpacingRange.start - aRange.start;
+ memset(aSpacing->Elements(), 0, sizeof(gfxFont::Spacing) * spacingOffset);
+ GetAdjustedSpacing(this, aSpacingRange, *aProvider,
+ aSpacing->Elements() + spacingOffset);
+ memset(aSpacing->Elements() + spacingOffset + aSpacingRange.Length(), 0,
+ sizeof(gfxFont::Spacing) * (aRange.end - aSpacingRange.end));
+ return true;
+}
+
+bool gfxTextRun::ShrinkToLigatureBoundaries(Range* aRange) const {
+ if (aRange->start >= aRange->end) {
+ return false;
+ }
+
+ const CompressedGlyph* charGlyphs = mCharacterGlyphs;
+ bool adjusted = false;
+ while (aRange->start < aRange->end &&
+ !charGlyphs[aRange->start].IsLigatureGroupStart()) {
+ ++aRange->start;
+ adjusted = true;
+ }
+ if (aRange->end < GetLength()) {
+ while (aRange->end > aRange->start &&
+ !charGlyphs[aRange->end].IsLigatureGroupStart()) {
+ --aRange->end;
+ adjusted = true;
+ }
+ }
+ return adjusted;
+}
+
+void gfxTextRun::DrawGlyphs(gfxFont* aFont, Range aRange, gfx::Point* aPt,
+ const PropertyProvider* aProvider,
+ Range aSpacingRange, TextRunDrawParams& aParams,
+ gfx::ShapedTextFlags aOrientation) const {
+ AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer;
+ bool haveSpacing =
+ GetAdjustedSpacingArray(aRange, aProvider, aSpacingRange, &spacingBuffer);
+ aParams.spacing = haveSpacing ? spacingBuffer.Elements() : nullptr;
+ aFont->Draw(this, aRange.start, aRange.end, aPt, aParams, aOrientation);
+}
+
+static void ClipPartialLigature(const gfxTextRun* aTextRun, gfxFloat* aStart,
+ gfxFloat* aEnd, gfxFloat aOrigin,
+ gfxTextRun::LigatureData* aLigature) {
+ if (aLigature->mClipBeforePart) {
+ if (aTextRun->IsRightToLeft()) {
+ *aEnd = std::min(*aEnd, aOrigin);
+ } else {
+ *aStart = std::max(*aStart, aOrigin);
+ }
+ }
+ if (aLigature->mClipAfterPart) {
+ gfxFloat endEdge =
+ aOrigin + aTextRun->GetDirection() * aLigature->mPartWidth;
+ if (aTextRun->IsRightToLeft()) {
+ *aStart = std::max(*aStart, endEdge);
+ } else {
+ *aEnd = std::min(*aEnd, endEdge);
+ }
+ }
+}
+
+void gfxTextRun::DrawPartialLigature(gfxFont* aFont, Range aRange,
+ gfx::Point* aPt,
+ const PropertyProvider* aProvider,
+ TextRunDrawParams& aParams,
+ gfx::ShapedTextFlags aOrientation) const {
+ if (aRange.start >= aRange.end) {
+ return;
+ }
+
+ // Draw partial ligature. We hack this by clipping the ligature.
+ LigatureData data = ComputeLigatureData(aRange, aProvider);
+ gfxRect clipExtents = aParams.context->GetClipExtents();
+ gfxFloat start, end;
+ if (aParams.isVerticalRun) {
+ start = clipExtents.Y() * mAppUnitsPerDevUnit;
+ end = clipExtents.YMost() * mAppUnitsPerDevUnit;
+ ClipPartialLigature(this, &start, &end, aPt->y, &data);
+ } else {
+ start = clipExtents.X() * mAppUnitsPerDevUnit;
+ end = clipExtents.XMost() * mAppUnitsPerDevUnit;
+ ClipPartialLigature(this, &start, &end, aPt->x, &data);
+ }
+
+ gfxClipAutoSaveRestore autoSaveClip(aParams.context);
+ {
+ // use division here to ensure that when the rect is aligned on multiples
+ // of mAppUnitsPerDevUnit, we clip to true device unit boundaries.
+ // Also, make sure we snap the rectangle to device pixels.
+ Rect clipRect =
+ aParams.isVerticalRun
+ ? Rect(clipExtents.X(), start / mAppUnitsPerDevUnit,
+ clipExtents.Width(), (end - start) / mAppUnitsPerDevUnit)
+ : Rect(start / mAppUnitsPerDevUnit, clipExtents.Y(),
+ (end - start) / mAppUnitsPerDevUnit, clipExtents.Height());
+ MaybeSnapToDevicePixels(clipRect, *aParams.dt, true);
+
+ autoSaveClip.Clip(clipRect);
+ }
+
+ gfx::Point pt;
+ if (aParams.isVerticalRun) {
+ pt = Point(aPt->x, aPt->y - aParams.direction * data.mPartAdvance);
+ } else {
+ pt = Point(aPt->x - aParams.direction * data.mPartAdvance, aPt->y);
+ }
+
+ DrawGlyphs(aFont, data.mRange, &pt, aProvider, aRange, aParams, aOrientation);
+
+ if (aParams.isVerticalRun) {
+ aPt->y += aParams.direction * data.mPartWidth;
+ } else {
+ aPt->x += aParams.direction * data.mPartWidth;
+ }
+}
+
+// Returns true if the font has synthetic bolding enabled,
+// or is a color font (COLR/SVG/sbix/CBDT), false otherwise. This is used to
+// check whether the text run needs to be explicitly composited in order to
+// support opacity.
+static bool HasSyntheticBoldOrColor(gfxFont* aFont) {
+ if (aFont->ApplySyntheticBold()) {
+ return true;
+ }
+ gfxFontEntry* fe = aFont->GetFontEntry();
+ if (fe->TryGetSVGData(aFont) || fe->TryGetColorGlyphs()) {
+ return true;
+ }
+#if defined(XP_MACOSX) // sbix fonts only supported via Core Text
+ if (fe->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) {
+ return true;
+ }
+#endif
+ return false;
+}
+
+// helper class for double-buffering drawing with non-opaque color
+struct MOZ_STACK_CLASS BufferAlphaColor {
+ explicit BufferAlphaColor(gfxContext* aContext) : mContext(aContext) {}
+
+ ~BufferAlphaColor() = default;
+
+ void PushSolidColor(const gfxRect& aBounds, const DeviceColor& aAlphaColor,
+ uint32_t appsPerDevUnit) {
+ mContext->Save();
+ mContext->SnappedClip(gfxRect(
+ aBounds.X() / appsPerDevUnit, aBounds.Y() / appsPerDevUnit,
+ aBounds.Width() / appsPerDevUnit, aBounds.Height() / appsPerDevUnit));
+ mContext->SetDeviceColor(
+ DeviceColor(aAlphaColor.r, aAlphaColor.g, aAlphaColor.b));
+ mContext->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, aAlphaColor.a);
+ }
+
+ void PopAlpha() {
+ // pop the text, using the color alpha as the opacity
+ mContext->PopGroupAndBlend();
+ mContext->Restore();
+ }
+
+ gfxContext* mContext;
+};
+
+void gfxTextRun::Draw(const Range aRange, const gfx::Point aPt,
+ const DrawParams& aParams) const {
+ NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range");
+ NS_ASSERTION(aParams.drawMode == DrawMode::GLYPH_PATH ||
+ !(aParams.drawMode & DrawMode::GLYPH_PATH),
+ "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or "
+ "GLYPH_STROKE_UNDERNEATH");
+ NS_ASSERTION(aParams.drawMode == DrawMode::GLYPH_PATH || !aParams.callbacks,
+ "callback must not be specified unless using GLYPH_PATH");
+
+ bool skipDrawing =
+ !mDontSkipDrawing && (mFontGroup ? mFontGroup->ShouldSkipDrawing()
+ : mReleasedFontGroupSkippedDrawing);
+ auto* textDrawer = aParams.context->GetTextDrawer();
+ if (aParams.drawMode & DrawMode::GLYPH_FILL) {
+ DeviceColor currentColor;
+ if (aParams.context->GetDeviceColor(currentColor) && currentColor.a == 0 &&
+ !textDrawer) {
+ skipDrawing = true;
+ }
+ }
+
+ gfxFloat direction = GetDirection();
+
+ if (skipDrawing) {
+ // We don't need to draw anything;
+ // but if the caller wants advance width, we need to compute it here
+ if (aParams.advanceWidth) {
+ gfxTextRun::Metrics metrics =
+ MeasureText(aRange, gfxFont::LOOSE_INK_EXTENTS,
+ aParams.context->GetDrawTarget(), aParams.provider);
+ *aParams.advanceWidth = metrics.mAdvanceWidth * direction;
+ }
+
+ // return without drawing
+ return;
+ }
+
+ // synthetic bolding draws glyphs twice ==> colors with opacity won't draw
+ // correctly unless first drawn without alpha
+ BufferAlphaColor syntheticBoldBuffer(aParams.context);
+ DeviceColor currentColor;
+ bool mayNeedBuffering =
+ aParams.drawMode & DrawMode::GLYPH_FILL &&
+ aParams.context->HasNonOpaqueNonTransparentColor(currentColor) &&
+ !textDrawer;
+
+ // If we need to double-buffer, we'll need to measure the text first to
+ // get the bounds of the area of interest. Ideally we'd do that just for
+ // the specific glyph run(s) that need buffering, but because of bug
+ // 1612610 we currently use the extent of the entire range even when
+ // just buffering a subrange. So we'll measure the full range once and
+ // keep the metrics on hand for any subsequent subranges.
+ gfxTextRun::Metrics metrics;
+ bool gotMetrics = false;
+
+ // Set up parameters that will be constant across all glyph runs we need
+ // to draw, regardless of the font used.
+ TextRunDrawParams params;
+ params.context = aParams.context;
+ params.devPerApp = 1.0 / double(GetAppUnitsPerDevUnit());
+ params.isVerticalRun = IsVertical();
+ params.isRTL = IsRightToLeft();
+ params.direction = direction;
+ params.strokeOpts = aParams.strokeOpts;
+ params.textStrokeColor = aParams.textStrokeColor;
+ params.fontPalette = aParams.fontPalette;
+ params.paletteValueSet = aParams.paletteValueSet;
+ params.textStrokePattern = aParams.textStrokePattern;
+ params.drawOpts = aParams.drawOpts;
+ params.drawMode = aParams.drawMode;
+ params.callbacks = aParams.callbacks;
+ params.runContextPaint = aParams.contextPaint;
+ params.paintSVGGlyphs =
+ !aParams.callbacks || aParams.callbacks->mShouldPaintSVGGlyphs;
+ params.dt = aParams.context->GetDrawTarget();
+ params.textDrawer = textDrawer;
+ if (textDrawer) {
+ params.clipRect = textDrawer->GeckoClipRect();
+ }
+ params.allowGDI = aParams.allowGDI;
+
+ gfxFloat advance = 0.0;
+ gfx::Point pt = aPt;
+
+ for (GlyphRunIterator iter(this, aRange); !iter.AtEnd(); iter.NextRun()) {
+ gfxFont* font = iter.GlyphRun()->mFont;
+ Range runRange(iter.StringStart(), iter.StringEnd());
+
+ bool needToRestore = false;
+ if (mayNeedBuffering && HasSyntheticBoldOrColor(font)) {
+ needToRestore = true;
+ if (!gotMetrics) {
+ // Measure text; use the bounding box to determine the area we need
+ // to buffer. We measure the entire range, rather than just the glyph
+ // run that we're actually handling, because of bug 1612610: if the
+ // bounding box passed to PushSolidColor does not intersect the
+ // drawTarget's current clip, the skia backend fails to clip properly.
+ // This means we may use a larger buffer than actually needed, but is
+ // otherwise harmless.
+ metrics = MeasureText(aRange, gfxFont::LOOSE_INK_EXTENTS, params.dt,
+ aParams.provider);
+ if (IsRightToLeft()) {
+ metrics.mBoundingBox.MoveBy(
+ gfxPoint(aPt.x - metrics.mAdvanceWidth, aPt.y));
+ } else {
+ metrics.mBoundingBox.MoveBy(gfxPoint(aPt.x, aPt.y));
+ }
+ gotMetrics = true;
+ }
+ syntheticBoldBuffer.PushSolidColor(metrics.mBoundingBox, currentColor,
+ GetAppUnitsPerDevUnit());
+ }
+
+ Range ligatureRange(runRange);
+ bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange);
+
+ bool drawPartial =
+ adjusted &&
+ ((aParams.drawMode & (DrawMode::GLYPH_FILL | DrawMode::GLYPH_STROKE)) ||
+ (aParams.drawMode == DrawMode::GLYPH_PATH && aParams.callbacks));
+ gfx::Point origPt = pt;
+
+ if (drawPartial) {
+ DrawPartialLigature(font, Range(runRange.start, ligatureRange.start), &pt,
+ aParams.provider, params,
+ iter.GlyphRun()->mOrientation);
+ }
+
+ DrawGlyphs(font, ligatureRange, &pt, aParams.provider, ligatureRange,
+ params, iter.GlyphRun()->mOrientation);
+
+ if (drawPartial) {
+ DrawPartialLigature(font, Range(ligatureRange.end, runRange.end), &pt,
+ aParams.provider, params,
+ iter.GlyphRun()->mOrientation);
+ }
+
+ if (params.isVerticalRun) {
+ advance += (pt.y - origPt.y) * params.direction;
+ } else {
+ advance += (pt.x - origPt.x) * params.direction;
+ }
+
+ // composite result when synthetic bolding used
+ if (needToRestore) {
+ syntheticBoldBuffer.PopAlpha();
+ }
+ }
+
+ if (aParams.advanceWidth) {
+ *aParams.advanceWidth = advance;
+ }
+}
+
+// This method is mostly parallel to Draw().
+void gfxTextRun::DrawEmphasisMarks(gfxContext* aContext, gfxTextRun* aMark,
+ gfxFloat aMarkAdvance, gfx::Point aPt,
+ Range aRange,
+ const PropertyProvider* aProvider) const {
+ MOZ_ASSERT(aRange.end <= GetLength());
+
+ EmphasisMarkDrawParams params;
+ params.context = aContext;
+ params.mark = aMark;
+ params.advance = aMarkAdvance;
+ params.direction = GetDirection();
+ params.isVertical = IsVertical();
+
+ float& inlineCoord = params.isVertical ? aPt.y.value : aPt.x.value;
+ float direction = params.direction;
+
+ for (GlyphRunIterator iter(this, aRange); !iter.AtEnd(); iter.NextRun()) {
+ gfxFont* font = iter.GlyphRun()->mFont;
+ uint32_t start = iter.StringStart();
+ uint32_t end = iter.StringEnd();
+ Range ligatureRange(start, end);
+ bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange);
+
+ if (adjusted) {
+ inlineCoord +=
+ direction * ComputePartialLigatureWidth(
+ Range(start, ligatureRange.start), aProvider);
+ }
+
+ AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer;
+ bool haveSpacing = GetAdjustedSpacingArray(ligatureRange, aProvider,
+ ligatureRange, &spacingBuffer);
+ params.spacing = haveSpacing ? spacingBuffer.Elements() : nullptr;
+ font->DrawEmphasisMarks(this, &aPt, ligatureRange.start,
+ ligatureRange.Length(), params);
+
+ if (adjusted) {
+ inlineCoord += direction * ComputePartialLigatureWidth(
+ Range(ligatureRange.end, end), aProvider);
+ }
+ }
+}
+
+void gfxTextRun::AccumulateMetricsForRun(
+ gfxFont* aFont, Range aRange, gfxFont::BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget, const PropertyProvider* aProvider,
+ Range aSpacingRange, gfx::ShapedTextFlags aOrientation,
+ Metrics* aMetrics) const {
+ AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer;
+ bool haveSpacing =
+ GetAdjustedSpacingArray(aRange, aProvider, aSpacingRange, &spacingBuffer);
+ Metrics metrics = aFont->Measure(
+ this, aRange.start, aRange.end, aBoundingBoxType, aRefDrawTarget,
+ haveSpacing ? spacingBuffer.Elements() : nullptr, aOrientation);
+ aMetrics->CombineWith(metrics, IsRightToLeft());
+}
+
+void gfxTextRun::AccumulatePartialLigatureMetrics(
+ gfxFont* aFont, Range aRange, gfxFont::BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget, const PropertyProvider* aProvider,
+ gfx::ShapedTextFlags aOrientation, Metrics* aMetrics) const {
+ if (aRange.start >= aRange.end) return;
+
+ // Measure partial ligature. We hack this by clipping the metrics in the
+ // same way we clip the drawing.
+ LigatureData data = ComputeLigatureData(aRange, aProvider);
+
+ // First measure the complete ligature
+ Metrics metrics;
+ AccumulateMetricsForRun(aFont, data.mRange, aBoundingBoxType, aRefDrawTarget,
+ aProvider, aRange, aOrientation, &metrics);
+
+ // Clip the bounding box to the ligature part
+ gfxFloat bboxLeft = metrics.mBoundingBox.X();
+ gfxFloat bboxRight = metrics.mBoundingBox.XMost();
+ // Where we are going to start "drawing" relative to our left baseline origin
+ gfxFloat origin =
+ IsRightToLeft() ? metrics.mAdvanceWidth - data.mPartAdvance : 0;
+ ClipPartialLigature(this, &bboxLeft, &bboxRight, origin, &data);
+ metrics.mBoundingBox.SetBoxX(bboxLeft, bboxRight);
+
+ // mBoundingBox is now relative to the left baseline origin for the entire
+ // ligature. Shift it left.
+ metrics.mBoundingBox.MoveByX(
+ -(IsRightToLeft()
+ ? metrics.mAdvanceWidth - (data.mPartAdvance + data.mPartWidth)
+ : data.mPartAdvance));
+ metrics.mAdvanceWidth = data.mPartWidth;
+
+ aMetrics->CombineWith(metrics, IsRightToLeft());
+}
+
+gfxTextRun::Metrics gfxTextRun::MeasureText(
+ Range aRange, gfxFont::BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget, const PropertyProvider* aProvider) const {
+ NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range");
+
+ Metrics accumulatedMetrics;
+ for (GlyphRunIterator iter(this, aRange); !iter.AtEnd(); iter.NextRun()) {
+ gfxFont* font = iter.GlyphRun()->mFont;
+ uint32_t start = iter.StringStart();
+ uint32_t end = iter.StringEnd();
+ Range ligatureRange(start, end);
+ bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange);
+
+ if (adjusted) {
+ AccumulatePartialLigatureMetrics(font, Range(start, ligatureRange.start),
+ aBoundingBoxType, aRefDrawTarget,
+ aProvider, iter.GlyphRun()->mOrientation,
+ &accumulatedMetrics);
+ }
+
+ // XXX This sucks. We have to get glyph extents just so we can detect
+ // glyphs outside the font box, even when aBoundingBoxType is LOOSE,
+ // even though in almost all cases we could get correct results just
+ // by getting some ascent/descent from the font and using our stored
+ // advance widths.
+ AccumulateMetricsForRun(font, ligatureRange, aBoundingBoxType,
+ aRefDrawTarget, aProvider, ligatureRange,
+ iter.GlyphRun()->mOrientation, &accumulatedMetrics);
+
+ if (adjusted) {
+ AccumulatePartialLigatureMetrics(
+ font, Range(ligatureRange.end, end), aBoundingBoxType, aRefDrawTarget,
+ aProvider, iter.GlyphRun()->mOrientation, &accumulatedMetrics);
+ }
+ }
+
+ return accumulatedMetrics;
+}
+
+void gfxTextRun::GetLineHeightMetrics(Range aRange, gfxFloat& aAscent,
+ gfxFloat& aDescent) const {
+ Metrics accumulatedMetrics;
+ for (GlyphRunIterator iter(this, aRange); !iter.AtEnd(); iter.NextRun()) {
+ gfxFont* font = iter.GlyphRun()->mFont;
+ auto metrics =
+ font->Measure(this, 0, 0, gfxFont::LOOSE_INK_EXTENTS, nullptr, nullptr,
+ iter.GlyphRun()->mOrientation);
+ accumulatedMetrics.CombineWith(metrics, false);
+ }
+ aAscent = accumulatedMetrics.mAscent;
+ aDescent = accumulatedMetrics.mDescent;
+}
+
+#define MEASUREMENT_BUFFER_SIZE 100
+
+void gfxTextRun::ClassifyAutoHyphenations(uint32_t aStart, Range aRange,
+ nsTArray<HyphenType>& aHyphenBuffer,
+ HyphenationState* aWordState) {
+ MOZ_ASSERT(
+ aRange.end - aStart <= aHyphenBuffer.Length() && aRange.start >= aStart,
+ "Range out of bounds");
+ MOZ_ASSERT(aWordState->mostRecentBoundary >= aStart,
+ "Unexpected aMostRecentWordBoundary!!");
+
+ uint32_t start =
+ std::min<uint32_t>(aRange.start, aWordState->mostRecentBoundary);
+
+ for (uint32_t i = start; i < aRange.end; ++i) {
+ if (aHyphenBuffer[i - aStart] == HyphenType::Explicit &&
+ !aWordState->hasExplicitHyphen) {
+ aWordState->hasExplicitHyphen = true;
+ }
+ if (!aWordState->hasManualHyphen &&
+ (aHyphenBuffer[i - aStart] == HyphenType::Soft ||
+ aHyphenBuffer[i - aStart] == HyphenType::Explicit)) {
+ aWordState->hasManualHyphen = true;
+ // This is the first manual hyphen in the current word. We can only
+ // know if the current word has a manual hyphen until now. So, we need
+ // to run a sub loop to update the auto hyphens between the start of
+ // the current word and this manual hyphen.
+ if (aWordState->hasAutoHyphen) {
+ for (uint32_t j = aWordState->mostRecentBoundary; j < i; j++) {
+ if (aHyphenBuffer[j - aStart] ==
+ HyphenType::AutoWithoutManualInSameWord) {
+ aHyphenBuffer[j - aStart] = HyphenType::AutoWithManualInSameWord;
+ }
+ }
+ }
+ }
+ if (aHyphenBuffer[i - aStart] == HyphenType::AutoWithoutManualInSameWord) {
+ if (!aWordState->hasAutoHyphen) {
+ aWordState->hasAutoHyphen = true;
+ }
+ if (aWordState->hasManualHyphen) {
+ aHyphenBuffer[i - aStart] = HyphenType::AutoWithManualInSameWord;
+ }
+ }
+
+ // If we're at the word boundary, clear/reset couple states.
+ if (mCharacterGlyphs[i].CharIsSpace() || mCharacterGlyphs[i].CharIsTab() ||
+ mCharacterGlyphs[i].CharIsNewline() ||
+ // Since we will not have a boundary in the end of the string, let's
+ // call the end of the string a special case for word boundary.
+ i == GetLength() - 1) {
+ // We can only get to know whether we should raise/clear an explicit
+ // manual hyphen until we get to the end of a word, because this depends
+ // on whether there exists at least one auto hyphen in the same word.
+ if (!aWordState->hasAutoHyphen && aWordState->hasExplicitHyphen) {
+ for (uint32_t j = aWordState->mostRecentBoundary; j <= i; j++) {
+ if (aHyphenBuffer[j - aStart] == HyphenType::Explicit) {
+ aHyphenBuffer[j - aStart] = HyphenType::None;
+ }
+ }
+ }
+ aWordState->mostRecentBoundary = i;
+ aWordState->hasManualHyphen = false;
+ aWordState->hasAutoHyphen = false;
+ aWordState->hasExplicitHyphen = false;
+ }
+ }
+}
+
+uint32_t gfxTextRun::BreakAndMeasureText(
+ uint32_t aStart, uint32_t aMaxLength, bool aLineBreakBefore,
+ gfxFloat aWidth, const PropertyProvider& aProvider,
+ SuppressBreak aSuppressBreak, gfxFont::BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget, bool aCanWordWrap, bool aCanWhitespaceWrap,
+ TrimmableWS* aOutTrimmableWhitespace, Metrics& aOutMetrics,
+ bool& aOutUsedHyphenation, uint32_t& aOutLastBreak,
+ gfxBreakPriority& aBreakPriority) {
+ aMaxLength = std::min(aMaxLength, GetLength() - aStart);
+
+ NS_ASSERTION(aStart + aMaxLength <= GetLength(), "Substring out of range");
+
+ Range bufferRange(
+ aStart, aStart + std::min<uint32_t>(aMaxLength, MEASUREMENT_BUFFER_SIZE));
+ PropertyProvider::Spacing spacingBuffer[MEASUREMENT_BUFFER_SIZE];
+ bool haveSpacing = !!(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING);
+ if (haveSpacing) {
+ GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer);
+ }
+ AutoTArray<HyphenType, 4096> hyphenBuffer;
+ HyphenationState wordState;
+ wordState.mostRecentBoundary = aStart;
+ bool haveHyphenation =
+ (aProvider.GetHyphensOption() == StyleHyphens::Auto ||
+ (aProvider.GetHyphensOption() == StyleHyphens::Manual &&
+ !!(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS)));
+ if (haveHyphenation) {
+ if (hyphenBuffer.AppendElements(bufferRange.Length(), fallible)) {
+ aProvider.GetHyphenationBreaks(bufferRange, hyphenBuffer.Elements());
+ if (aProvider.GetHyphensOption() == StyleHyphens::Auto) {
+ ClassifyAutoHyphenations(aStart, bufferRange, hyphenBuffer, &wordState);
+ }
+ } else {
+ haveHyphenation = false;
+ }
+ }
+
+ gfxFloat width = 0;
+ gfxFloat advance = 0;
+ // The number of space characters that can be trimmed or hang at a soft-wrap
+ uint32_t trimmableChars = 0;
+ // The amount of space removed by ignoring trimmableChars
+ gfxFloat trimmableAdvance = 0;
+ int32_t lastBreak = -1;
+ int32_t lastBreakTrimmableChars = -1;
+ gfxFloat lastBreakTrimmableAdvance = -1;
+ // Cache the last candidate break
+ int32_t lastCandidateBreak = -1;
+ int32_t lastCandidateBreakTrimmableChars = -1;
+ gfxFloat lastCandidateBreakTrimmableAdvance = -1;
+ bool lastCandidateBreakUsedHyphenation = false;
+ gfxBreakPriority lastCandidateBreakPriority = gfxBreakPriority::eNoBreak;
+ bool aborted = false;
+ uint32_t end = aStart + aMaxLength;
+ bool lastBreakUsedHyphenation = false;
+ Range ligatureRange(aStart, end);
+ ShrinkToLigatureBoundaries(&ligatureRange);
+
+ // We may need to move `i` backwards in the following loop, and re-scan
+ // part of the textrun; we'll use `rescanLimit` so we can tell when that
+ // is happening: if `i < rescanLimit` then we're rescanning.
+ uint32_t rescanLimit = aStart;
+ for (uint32_t i = aStart; i < end; ++i) {
+ if (i >= bufferRange.end) {
+ // Fetch more spacing and hyphenation data
+ uint32_t oldHyphenBufferLength = hyphenBuffer.Length();
+ bufferRange.start = i;
+ bufferRange.end =
+ std::min(aStart + aMaxLength, i + MEASUREMENT_BUFFER_SIZE);
+ // For spacing, we always overwrite the old data with the newly
+ // fetched one. However, for hyphenation, hyphenation data sometimes
+ // depends on the context in every word (if "hyphens: auto" is set).
+ // To ensure we get enough information between neighboring buffers,
+ // we grow the hyphenBuffer instead of overwrite it.
+ // NOTE that this means bufferRange does not correspond to the
+ // entire hyphenBuffer, but only to the most recently added portion.
+ // Therefore, we need to add the old length to hyphenBuffer.Elements()
+ // when getting more data.
+ if (haveSpacing) {
+ GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer);
+ }
+ if (haveHyphenation) {
+ if (hyphenBuffer.AppendElements(bufferRange.Length(), fallible)) {
+ aProvider.GetHyphenationBreaks(
+ bufferRange, hyphenBuffer.Elements() + oldHyphenBufferLength);
+ if (aProvider.GetHyphensOption() == StyleHyphens::Auto) {
+ uint32_t prevMostRecentWordBoundary = wordState.mostRecentBoundary;
+ ClassifyAutoHyphenations(aStart, bufferRange, hyphenBuffer,
+ &wordState);
+ // If the buffer boundary is in the middle of a word,
+ // we need to go back to the start of the current word.
+ // So, we can correct the wrong candidates that we set
+ // in the previous runs of the loop.
+ if (prevMostRecentWordBoundary < oldHyphenBufferLength) {
+ rescanLimit = i;
+ i = prevMostRecentWordBoundary - 1;
+ continue;
+ }
+ }
+ } else {
+ haveHyphenation = false;
+ }
+ }
+ }
+
+ // There can't be a word-wrap break opportunity at the beginning of the
+ // line: if the width is too small for even one character to fit, it
+ // could be the first and last break opportunity on the line, and that
+ // would trigger an infinite loop.
+ if (aSuppressBreak != eSuppressAllBreaks &&
+ (aSuppressBreak != eSuppressInitialBreak || i > aStart)) {
+ bool atNaturalBreak = mCharacterGlyphs[i].CanBreakBefore() == 1;
+ // atHyphenationBreak indicates we're at a "soft" hyphen, where an extra
+ // hyphen glyph will need to be painted. It is NOT set for breaks at an
+ // explicit hyphen present in the text.
+ //
+ // NOTE(emilio): If you change this condition you also need to change
+ // nsTextFrame::AddInlineMinISizeForFlow to match.
+ bool atHyphenationBreak = !atNaturalBreak && haveHyphenation &&
+ IsOptionalHyphenBreak(hyphenBuffer[i - aStart]);
+ bool atAutoHyphenWithManualHyphenInSameWord =
+ atHyphenationBreak &&
+ hyphenBuffer[i - aStart] == HyphenType::AutoWithManualInSameWord;
+ bool atBreak = atNaturalBreak || atHyphenationBreak;
+ bool wordWrapping = aCanWordWrap &&
+ mCharacterGlyphs[i].IsClusterStart() &&
+ aBreakPriority <= gfxBreakPriority::eWordWrapBreak;
+
+ bool whitespaceWrapping = false;
+ if (i > aStart) {
+ // The spec says the breaking opportunity is *after* whitespace.
+ auto const& g = mCharacterGlyphs[i - 1];
+ whitespaceWrapping =
+ aCanWhitespaceWrap &&
+ (g.CharIsSpace() || g.CharIsTab() || g.CharIsNewline());
+ }
+
+ if (atBreak || wordWrapping || whitespaceWrapping) {
+ gfxFloat hyphenatedAdvance = advance;
+ if (atHyphenationBreak) {
+ hyphenatedAdvance += aProvider.GetHyphenWidth();
+ }
+
+ if (lastBreak < 0 ||
+ width + hyphenatedAdvance - trimmableAdvance <= aWidth) {
+ // We can break here.
+ lastBreak = i;
+ lastBreakTrimmableChars = trimmableChars;
+ lastBreakTrimmableAdvance = trimmableAdvance;
+ lastBreakUsedHyphenation = atHyphenationBreak;
+ aBreakPriority = (atBreak || whitespaceWrapping)
+ ? gfxBreakPriority::eNormalBreak
+ : gfxBreakPriority::eWordWrapBreak;
+ }
+
+ width += advance;
+ advance = 0;
+ if (width - trimmableAdvance > aWidth) {
+ // No more text fits. Abort
+ aborted = true;
+ break;
+ }
+ // There are various kinds of break opportunities:
+ // 1. word wrap break,
+ // 2. natural break,
+ // 3. manual hyphenation break,
+ // 4. auto hyphenation break without any manual hyphenation
+ // in the same word,
+ // 5. auto hyphenation break with another manual hyphenation
+ // in the same word.
+ // Allow all of them except the last one to be a candidate.
+ // So, we can ensure that we don't use an automatic
+ // hyphenation opportunity within a word that contains another
+ // manual hyphenation, unless it is the only choice.
+ if (wordWrapping || !atAutoHyphenWithManualHyphenInSameWord) {
+ lastCandidateBreak = lastBreak;
+ lastCandidateBreakTrimmableChars = lastBreakTrimmableChars;
+ lastCandidateBreakTrimmableAdvance = lastBreakTrimmableAdvance;
+ lastCandidateBreakUsedHyphenation = lastBreakUsedHyphenation;
+ lastCandidateBreakPriority = aBreakPriority;
+ }
+ }
+ }
+
+ // If we're re-scanning part of a word (to re-process potential
+ // hyphenation types) then we don't want to accumulate widths again
+ // for the characters that were already added to `advance`.
+ if (i < rescanLimit) {
+ continue;
+ }
+
+ gfxFloat charAdvance;
+ if (i >= ligatureRange.start && i < ligatureRange.end) {
+ charAdvance = GetAdvanceForGlyphs(Range(i, i + 1));
+ if (haveSpacing) {
+ PropertyProvider::Spacing* space =
+ &spacingBuffer[i - bufferRange.start];
+ charAdvance += space->mBefore + space->mAfter;
+ }
+ } else {
+ charAdvance = ComputePartialLigatureWidth(Range(i, i + 1), &aProvider);
+ }
+
+ advance += charAdvance;
+ if (aOutTrimmableWhitespace) {
+ if (mCharacterGlyphs[i].CharIsSpace()) {
+ ++trimmableChars;
+ trimmableAdvance += charAdvance;
+ } else {
+ trimmableAdvance = 0;
+ trimmableChars = 0;
+ }
+ }
+ }
+
+ if (!aborted) {
+ width += advance;
+ }
+
+ // There are three possibilities:
+ // 1) all the text fit (width <= aWidth)
+ // 2) some of the text fit up to a break opportunity (width > aWidth &&
+ // lastBreak >= 0)
+ // 3) none of the text fits before a break opportunity (width > aWidth &&
+ // lastBreak < 0)
+ uint32_t charsFit;
+ aOutUsedHyphenation = false;
+ if (width - trimmableAdvance <= aWidth) {
+ charsFit = aMaxLength;
+ } else if (lastBreak >= 0) {
+ if (lastCandidateBreak >= 0 && lastCandidateBreak != lastBreak) {
+ lastBreak = lastCandidateBreak;
+ lastBreakTrimmableChars = lastCandidateBreakTrimmableChars;
+ lastBreakTrimmableAdvance = lastCandidateBreakTrimmableAdvance;
+ lastBreakUsedHyphenation = lastCandidateBreakUsedHyphenation;
+ aBreakPriority = lastCandidateBreakPriority;
+ }
+ charsFit = lastBreak - aStart;
+ trimmableChars = lastBreakTrimmableChars;
+ trimmableAdvance = lastBreakTrimmableAdvance;
+ aOutUsedHyphenation = lastBreakUsedHyphenation;
+ } else {
+ charsFit = aMaxLength;
+ }
+
+ // Get the overall metrics of the range that fit (including any potentially
+ // trimmable or hanging whitespace).
+ aOutMetrics = MeasureText(Range(aStart, aStart + charsFit), aBoundingBoxType,
+ aRefDrawTarget, &aProvider);
+
+ if (aOutTrimmableWhitespace) {
+ aOutTrimmableWhitespace->mAdvance = trimmableAdvance;
+ aOutTrimmableWhitespace->mCount = trimmableChars;
+ }
+
+ if (charsFit == aMaxLength) {
+ if (lastBreak < 0) {
+ aOutLastBreak = UINT32_MAX;
+ } else {
+ aOutLastBreak = lastBreak - aStart;
+ }
+ }
+
+ return charsFit;
+}
+
+gfxFloat gfxTextRun::GetAdvanceWidth(
+ Range aRange, const PropertyProvider* aProvider,
+ PropertyProvider::Spacing* aSpacing) const {
+ NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range");
+
+ Range ligatureRange = aRange;
+ bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange);
+
+ gfxFloat result =
+ adjusted ? ComputePartialLigatureWidth(
+ Range(aRange.start, ligatureRange.start), aProvider) +
+ ComputePartialLigatureWidth(
+ Range(ligatureRange.end, aRange.end), aProvider)
+ : 0.0;
+
+ if (aSpacing) {
+ aSpacing->mBefore = aSpacing->mAfter = 0;
+ }
+
+ // Account for all remaining spacing here. This is more efficient than
+ // processing it along with the glyphs.
+ if (aProvider && (mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) {
+ uint32_t i;
+ AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer;
+ if (spacingBuffer.AppendElements(aRange.Length(), fallible)) {
+ GetAdjustedSpacing(this, ligatureRange, *aProvider,
+ spacingBuffer.Elements());
+ for (i = 0; i < ligatureRange.Length(); ++i) {
+ PropertyProvider::Spacing* space = &spacingBuffer[i];
+ result += space->mBefore + space->mAfter;
+ }
+ if (aSpacing) {
+ aSpacing->mBefore = spacingBuffer[0].mBefore;
+ aSpacing->mAfter = spacingBuffer.LastElement().mAfter;
+ }
+ }
+ }
+
+ return result + GetAdvanceForGlyphs(ligatureRange);
+}
+
+gfxFloat gfxTextRun::GetMinAdvanceWidth(Range aRange) {
+ MOZ_ASSERT(aRange.end <= GetLength(), "Substring out of range");
+
+ Range ligatureRange = aRange;
+ bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange);
+
+ gfxFloat result =
+ adjusted
+ ? std::max(ComputePartialLigatureWidth(
+ Range(aRange.start, ligatureRange.start), nullptr),
+ ComputePartialLigatureWidth(
+ Range(ligatureRange.end, aRange.end), nullptr))
+ : 0.0;
+
+ // Compute min advance width by assuming each grapheme cluster takes its own
+ // line.
+ gfxFloat clusterAdvance = 0;
+ for (uint32_t i = ligatureRange.start; i < ligatureRange.end; ++i) {
+ if (mCharacterGlyphs[i].CharIsSpace()) {
+ // Skip space char to prevent its advance width contributing to the
+ // result. That is, don't consider a space can be in its own line.
+ continue;
+ }
+ clusterAdvance += GetAdvanceForGlyph(i);
+ if (i + 1 == ligatureRange.end || IsClusterStart(i + 1)) {
+ result = std::max(result, clusterAdvance);
+ clusterAdvance = 0;
+ }
+ }
+
+ return result;
+}
+
+bool gfxTextRun::SetLineBreaks(Range aRange, bool aLineBreakBefore,
+ bool aLineBreakAfter,
+ gfxFloat* aAdvanceWidthDelta) {
+ // Do nothing because our shaping does not currently take linebreaks into
+ // account. There is no change in advance width.
+ if (aAdvanceWidthDelta) {
+ *aAdvanceWidthDelta = 0;
+ }
+ return false;
+}
+
+const gfxTextRun::GlyphRun* gfxTextRun::FindFirstGlyphRunContaining(
+ uint32_t aOffset) const {
+ MOZ_ASSERT(aOffset <= GetLength(), "Bad offset looking for glyphrun");
+ MOZ_ASSERT(GetLength() == 0 || !mGlyphRuns.IsEmpty(),
+ "non-empty text but no glyph runs present!");
+ if (mGlyphRuns.Length() <= 1) {
+ return mGlyphRuns.begin();
+ }
+ if (aOffset == GetLength()) {
+ return mGlyphRuns.end() - 1;
+ }
+ const auto* start = mGlyphRuns.begin();
+ const auto* limit = mGlyphRuns.end();
+ while (limit - start > 1) {
+ const auto* mid = start + (limit - start) / 2;
+ if (mid->mCharacterOffset <= aOffset) {
+ start = mid;
+ } else {
+ limit = mid;
+ }
+ }
+ MOZ_ASSERT(start->mCharacterOffset <= aOffset,
+ "Hmm, something went wrong, aOffset should have been found");
+ return start;
+}
+
+void gfxTextRun::AddGlyphRun(gfxFont* aFont, FontMatchType aMatchType,
+ uint32_t aUTF16Offset, bool aForceNewRun,
+ gfx::ShapedTextFlags aOrientation, bool aIsCJK) {
+ MOZ_ASSERT(aFont, "adding glyph run for null font!");
+ MOZ_ASSERT(aOrientation != gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED,
+ "mixed orientation should have been resolved");
+ if (!aFont) {
+ return;
+ }
+
+ if (mGlyphRuns.IsEmpty()) {
+ mGlyphRuns.AppendElement(
+ GlyphRun{aFont, aUTF16Offset, aOrientation, aMatchType, aIsCJK});
+ return;
+ }
+
+ uint32_t numGlyphRuns = mGlyphRuns.Length();
+ if (!aForceNewRun) {
+ GlyphRun* lastGlyphRun = &mGlyphRuns.LastElement();
+
+ MOZ_ASSERT(lastGlyphRun->mCharacterOffset <= aUTF16Offset,
+ "Glyph runs out of order (and run not forced)");
+
+ // Don't append a run if the font is already the one we want
+ if (lastGlyphRun->Matches(aFont, aOrientation, aIsCJK, aMatchType)) {
+ return;
+ }
+
+ // If the offset has not changed, avoid leaving a zero-length run
+ // by overwriting the last entry instead of appending...
+ if (lastGlyphRun->mCharacterOffset == aUTF16Offset) {
+ // ...except that if the run before the last entry had the same
+ // font as the new one wants, merge with it instead of creating
+ // adjacent runs with the same font
+ if (numGlyphRuns > 1 && mGlyphRuns[numGlyphRuns - 2].Matches(
+ aFont, aOrientation, aIsCJK, aMatchType)) {
+ mGlyphRuns.TruncateLength(numGlyphRuns - 1);
+ return;
+ }
+
+ lastGlyphRun->SetProperties(aFont, aOrientation, aIsCJK, aMatchType);
+ return;
+ }
+ }
+
+ MOZ_ASSERT(
+ aForceNewRun || numGlyphRuns > 0 || aUTF16Offset == 0,
+ "First run doesn't cover the first character (and run not forced)?");
+
+ mGlyphRuns.AppendElement(
+ GlyphRun{aFont, aUTF16Offset, aOrientation, aMatchType, aIsCJK});
+}
+
+void gfxTextRun::SanitizeGlyphRuns() {
+ if (mGlyphRuns.Length() < 2) {
+ return;
+ }
+
+ auto& runs = mGlyphRuns.Array();
+
+ // The runs are almost certain to be already sorted, so it's worth avoiding
+ // the Sort() call if possible.
+ bool isSorted = true;
+ uint32_t prevOffset = 0;
+ for (const auto& r : runs) {
+ if (r.mCharacterOffset < prevOffset) {
+ isSorted = false;
+ break;
+ }
+ prevOffset = r.mCharacterOffset;
+ }
+ if (!isSorted) {
+ runs.Sort(GlyphRunOffsetComparator());
+ }
+
+ // Coalesce adjacent glyph runs that have the same properties, and eliminate
+ // any empty runs.
+ GlyphRun* prevRun = nullptr;
+ const CompressedGlyph* charGlyphs = mCharacterGlyphs;
+
+ runs.RemoveElementsBy([&](GlyphRun& aRun) -> bool {
+ // First run is always retained.
+ if (!prevRun) {
+ prevRun = &aRun;
+ return false;
+ }
+
+ // Merge any run whose properties match its predecessor.
+ if (prevRun->Matches(aRun.mFont, aRun.mOrientation, aRun.mIsCJK,
+ aRun.mMatchType)) {
+ return true;
+ }
+
+ if (prevRun->mCharacterOffset >= aRun.mCharacterOffset) {
+ // Preceding run is empty (or has become so due to the adjusting for
+ // ligature boundaries), so we will overwrite it with this one, which
+ // will then be discarded.
+ *prevRun = aRun;
+ return true;
+ }
+
+ // If any glyph run starts with ligature-continuation characters, we need to
+ // advance it to the first "real" character to avoid drawing partial
+ // ligature glyphs from wrong font (seen with U+FEFF in reftest 474417-1, as
+ // Core Text eliminates the glyph, which makes it appear as if a ligature
+ // has been formed)
+ while (charGlyphs[aRun.mCharacterOffset].IsLigatureContinuation() &&
+ aRun.mCharacterOffset < GetLength()) {
+ aRun.mCharacterOffset++;
+ }
+
+ // We're keeping another run, so update prevRun pointer to refer to it (in
+ // its new position).
+ ++prevRun;
+ return false;
+ });
+
+ MOZ_ASSERT(prevRun == &runs.LastElement(), "lost track of prevRun!");
+
+ // Drop any trailing empty run.
+ if (runs.Length() > 1 && prevRun->mCharacterOffset == GetLength()) {
+ runs.RemoveLastElement();
+ }
+
+ MOZ_ASSERT(!runs.IsEmpty());
+ if (runs.Length() == 1) {
+ mGlyphRuns.ConvertToElement();
+ }
+}
+
+void gfxTextRun::CopyGlyphDataFrom(gfxShapedWord* aShapedWord,
+ uint32_t aOffset) {
+ uint32_t wordLen = aShapedWord->GetLength();
+ MOZ_ASSERT(aOffset + wordLen <= GetLength(), "word overruns end of textrun");
+
+ CompressedGlyph* charGlyphs = GetCharacterGlyphs();
+ const CompressedGlyph* wordGlyphs = aShapedWord->GetCharacterGlyphs();
+ if (aShapedWord->HasDetailedGlyphs()) {
+ for (uint32_t i = 0; i < wordLen; ++i, ++aOffset) {
+ const CompressedGlyph& g = wordGlyphs[i];
+ if (!g.IsSimpleGlyph()) {
+ const DetailedGlyph* details =
+ g.GetGlyphCount() > 0 ? aShapedWord->GetDetailedGlyphs(i) : nullptr;
+ SetDetailedGlyphs(aOffset, g.GetGlyphCount(), details);
+ }
+ charGlyphs[aOffset] = g;
+ }
+ } else {
+ memcpy(charGlyphs + aOffset, wordGlyphs, wordLen * sizeof(CompressedGlyph));
+ }
+}
+
+void gfxTextRun::CopyGlyphDataFrom(gfxTextRun* aSource, Range aRange,
+ uint32_t aDest) {
+ MOZ_ASSERT(aRange.end <= aSource->GetLength(),
+ "Source substring out of range");
+ MOZ_ASSERT(aDest + aRange.Length() <= GetLength(),
+ "Destination substring out of range");
+
+ if (aSource->mDontSkipDrawing) {
+ mDontSkipDrawing = true;
+ }
+
+ // Copy base glyph data, and DetailedGlyph data where present
+ const CompressedGlyph* srcGlyphs = aSource->mCharacterGlyphs + aRange.start;
+ CompressedGlyph* dstGlyphs = mCharacterGlyphs + aDest;
+ for (uint32_t i = 0; i < aRange.Length(); ++i) {
+ CompressedGlyph g = srcGlyphs[i];
+ g.SetCanBreakBefore(!g.IsClusterStart()
+ ? CompressedGlyph::FLAG_BREAK_TYPE_NONE
+ : dstGlyphs[i].CanBreakBefore());
+ if (!g.IsSimpleGlyph()) {
+ uint32_t count = g.GetGlyphCount();
+ if (count > 0) {
+ // DetailedGlyphs allocation is infallible, so this should never be
+ // null unless the source textrun is somehow broken.
+ DetailedGlyph* src = aSource->GetDetailedGlyphs(i + aRange.start);
+ MOZ_ASSERT(src, "missing DetailedGlyphs?");
+ if (src) {
+ DetailedGlyph* dst = AllocateDetailedGlyphs(i + aDest, count);
+ ::memcpy(dst, src, count * sizeof(DetailedGlyph));
+ } else {
+ g.SetMissing();
+ }
+ }
+ }
+ dstGlyphs[i] = g;
+ }
+
+ // Copy glyph runs
+#ifdef DEBUG
+ GlyphRun* prevRun = nullptr;
+#endif
+ for (GlyphRunIterator iter(aSource, aRange); !iter.AtEnd(); iter.NextRun()) {
+ gfxFont* font = iter.GlyphRun()->mFont;
+ MOZ_ASSERT(!prevRun || !prevRun->Matches(iter.GlyphRun()->mFont,
+ iter.GlyphRun()->mOrientation,
+ iter.GlyphRun()->mIsCJK,
+ FontMatchType::Kind::kUnspecified),
+ "Glyphruns not coalesced?");
+#ifdef DEBUG
+ prevRun = const_cast<GlyphRun*>(iter.GlyphRun());
+ uint32_t end = iter.StringEnd();
+#endif
+ uint32_t start = iter.StringStart();
+
+ // These used to be NS_ASSERTION()s, but WARNING is more appropriate.
+ // Although it's unusual (and not desirable), it's possible for us to assign
+ // different fonts to a base character and a following diacritic.
+ // Example on OSX 10.5/10.6 with default fonts installed:
+ // data:text/html,<p style="font-family:helvetica, arial, sans-serif;">
+ // &%23x043E;&%23x0486;&%23x20;&%23x043E;&%23x0486;
+ // This means the rendering of the cluster will probably not be very good,
+ // but it's the best we can do for now if the specified font only covered
+ // the initial base character and not its applied marks.
+ NS_WARNING_ASSERTION(aSource->IsClusterStart(start),
+ "Started font run in the middle of a cluster");
+ NS_WARNING_ASSERTION(
+ end == aSource->GetLength() || aSource->IsClusterStart(end),
+ "Ended font run in the middle of a cluster");
+
+ AddGlyphRun(font, iter.GlyphRun()->mMatchType, start - aRange.start + aDest,
+ false, iter.GlyphRun()->mOrientation, iter.GlyphRun()->mIsCJK);
+ }
+}
+
+void gfxTextRun::ClearGlyphsAndCharacters() {
+ ResetGlyphRuns();
+ memset(reinterpret_cast<char*>(mCharacterGlyphs), 0,
+ mLength * sizeof(CompressedGlyph));
+ mDetailedGlyphs = nullptr;
+}
+
+void gfxTextRun::SetSpaceGlyph(gfxFont* aFont, DrawTarget* aDrawTarget,
+ uint32_t aCharIndex,
+ gfx::ShapedTextFlags aOrientation) {
+ if (SetSpaceGlyphIfSimple(aFont, aCharIndex, ' ', aOrientation)) {
+ return;
+ }
+
+ gfx::ShapedTextFlags flags =
+ gfx::ShapedTextFlags::TEXT_IS_8BIT | aOrientation;
+ bool vertical =
+ !!(GetFlags() & gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT);
+ gfxFontShaper::RoundingFlags roundingFlags =
+ aFont->GetRoundOffsetsToPixels(aDrawTarget);
+ aFont->ProcessSingleSpaceShapedWord(
+ aDrawTarget, vertical, mAppUnitsPerDevUnit, flags, roundingFlags,
+ [&](gfxShapedWord* aShapedWord) {
+ const GlyphRun* prevRun = TrailingGlyphRun();
+ bool isCJK = prevRun && prevRun->mFont == aFont &&
+ prevRun->mOrientation == aOrientation
+ ? prevRun->mIsCJK
+ : false;
+ AddGlyphRun(aFont, FontMatchType::Kind::kUnspecified, aCharIndex, false,
+ aOrientation, isCJK);
+ CopyGlyphDataFrom(aShapedWord, aCharIndex);
+ GetCharacterGlyphs()[aCharIndex].SetIsSpace();
+ });
+}
+
+bool gfxTextRun::SetSpaceGlyphIfSimple(gfxFont* aFont, uint32_t aCharIndex,
+ char16_t aSpaceChar,
+ gfx::ShapedTextFlags aOrientation) {
+ uint32_t spaceGlyph = aFont->GetSpaceGlyph();
+ if (!spaceGlyph || !CompressedGlyph::IsSimpleGlyphID(spaceGlyph)) {
+ return false;
+ }
+
+ gfxFont::Orientation fontOrientation =
+ (aOrientation & gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT)
+ ? nsFontMetrics::eVertical
+ : nsFontMetrics::eHorizontal;
+ uint32_t spaceWidthAppUnits = NS_lroundf(
+ aFont->GetMetrics(fontOrientation).spaceWidth * mAppUnitsPerDevUnit);
+ if (!CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits)) {
+ return false;
+ }
+
+ const GlyphRun* prevRun = TrailingGlyphRun();
+ bool isCJK = prevRun && prevRun->mFont == aFont &&
+ prevRun->mOrientation == aOrientation
+ ? prevRun->mIsCJK
+ : false;
+ AddGlyphRun(aFont, FontMatchType::Kind::kUnspecified, aCharIndex, false,
+ aOrientation, isCJK);
+ CompressedGlyph g =
+ CompressedGlyph::MakeSimpleGlyph(spaceWidthAppUnits, spaceGlyph);
+ if (aSpaceChar == ' ') {
+ g.SetIsSpace();
+ }
+ GetCharacterGlyphs()[aCharIndex] = g;
+ return true;
+}
+
+void gfxTextRun::FetchGlyphExtents(DrawTarget* aRefDrawTarget) const {
+ bool needsGlyphExtents = NeedsGlyphExtents();
+ if (!needsGlyphExtents && !mDetailedGlyphs) {
+ return;
+ }
+
+ uint32_t runCount;
+ const GlyphRun* glyphRuns = GetGlyphRuns(&runCount);
+ CompressedGlyph* charGlyphs = mCharacterGlyphs;
+ for (uint32_t i = 0; i < runCount; ++i) {
+ const GlyphRun& run = glyphRuns[i];
+ gfxFont* font = run.mFont;
+ if (MOZ_UNLIKELY(font->GetStyle()->AdjustedSizeMustBeZero())) {
+ continue;
+ }
+
+ uint32_t start = run.mCharacterOffset;
+ uint32_t end =
+ i + 1 < runCount ? glyphRuns[i + 1].mCharacterOffset : GetLength();
+ gfxGlyphExtents* extents =
+ font->GetOrCreateGlyphExtents(mAppUnitsPerDevUnit);
+
+ AutoReadLock lock(extents->mLock);
+ for (uint32_t j = start; j < end; ++j) {
+ const gfxTextRun::CompressedGlyph* glyphData = &charGlyphs[j];
+ if (glyphData->IsSimpleGlyph()) {
+ // If we're in speed mode, don't set up glyph extents here; we'll
+ // just return "optimistic" glyph bounds later
+ if (needsGlyphExtents) {
+ uint32_t glyphIndex = glyphData->GetSimpleGlyph();
+ if (!extents->IsGlyphKnownLocked(glyphIndex)) {
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+ ++gGlyphExtentsSetupEagerSimple;
+#endif
+ extents->mLock.ReadUnlock();
+ font->SetupGlyphExtents(aRefDrawTarget, glyphIndex, false, extents);
+ extents->mLock.ReadLock();
+ }
+ }
+ } else if (!glyphData->IsMissing()) {
+ uint32_t glyphCount = glyphData->GetGlyphCount();
+ if (glyphCount == 0) {
+ continue;
+ }
+ const gfxTextRun::DetailedGlyph* details = GetDetailedGlyphs(j);
+ if (!details) {
+ continue;
+ }
+ for (uint32_t k = 0; k < glyphCount; ++k, ++details) {
+ uint32_t glyphIndex = details->mGlyphID;
+ if (!extents->IsGlyphKnownWithTightExtentsLocked(glyphIndex)) {
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+ ++gGlyphExtentsSetupEagerTight;
+#endif
+ extents->mLock.ReadUnlock();
+ font->SetupGlyphExtents(aRefDrawTarget, glyphIndex, true, extents);
+ extents->mLock.ReadLock();
+ }
+ }
+ }
+ }
+ }
+}
+
+size_t gfxTextRun::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) {
+ size_t total = mGlyphRuns.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ if (mDetailedGlyphs) {
+ total += mDetailedGlyphs->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return total;
+}
+
+size_t gfxTextRun::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+#ifdef DEBUG_FRAME_DUMP
+void gfxTextRun::Dump(FILE* out) {
+# define APPEND_FLAG(string_, enum_, field_, flag_) \
+ if (field_ & enum_::flag_) { \
+ string_.AppendPrintf(remaining != field_ ? " %s" : "%s", #flag_); \
+ remaining &= ~enum_::flag_; \
+ }
+# define APPEND_FLAGS(string_, enum_, field_, flags_) \
+ { \
+ auto remaining = field_; \
+ MOZ_FOR_EACH(APPEND_FLAG, (string_, enum_, field_, ), flags_) \
+ if (int(remaining)) { \
+ string_.AppendPrintf(" %s(0x%0x)", #enum_, int(remaining)); \
+ } \
+ }
+
+ nsCString flagsString;
+ ShapedTextFlags orient = mFlags & ShapedTextFlags::TEXT_ORIENT_MASK;
+ ShapedTextFlags otherFlags = mFlags & ~ShapedTextFlags::TEXT_ORIENT_MASK;
+ APPEND_FLAGS(flagsString, ShapedTextFlags, otherFlags,
+ (TEXT_IS_RTL, TEXT_ENABLE_SPACING, TEXT_IS_8BIT,
+ TEXT_ENABLE_HYPHEN_BREAKS, TEXT_NEED_BOUNDING_BOX,
+ TEXT_DISABLE_OPTIONAL_LIGATURES, TEXT_OPTIMIZE_SPEED,
+ TEXT_HIDE_CONTROL_CHARACTERS, TEXT_TRAILING_ARABICCHAR,
+ TEXT_INCOMING_ARABICCHAR, TEXT_USE_MATH_SCRIPT))
+
+ if (orient != ShapedTextFlags::TEXT_ORIENT_HORIZONTAL &&
+ !flagsString.IsEmpty()) {
+ flagsString += ' ';
+ }
+
+ switch (orient) {
+ case ShapedTextFlags::TEXT_ORIENT_HORIZONTAL:
+ break;
+ case ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT:
+ flagsString += "TEXT_ORIENT_VERTICAL_UPRIGHT";
+ break;
+ case ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT:
+ flagsString += "TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT";
+ break;
+ case ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED:
+ flagsString += "TEXT_ORIENT_VERTICAL_MIXED";
+ break;
+ case ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT:
+ flagsString += "TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT";
+ break;
+ default:
+ flagsString.AppendPrintf("UNKNOWN_TEXT_ORIENT_MASK(0x%0x)", int(orient));
+ break;
+ }
+
+ nsCString flags2String;
+ APPEND_FLAGS(
+ flags2String, nsTextFrameUtils::Flags, mFlags2,
+ (HasTab, HasShy, DontSkipDrawingForPendingUserFonts, IsSimpleFlow,
+ IncomingWhitespace, TrailingWhitespace, CompressedLeadingWhitespace,
+ NoBreaks, IsTransformed, HasTrailingBreak, IsSingleCharMi,
+ MightHaveGlyphChanges, RunSizeAccounted))
+
+# undef APPEND_FLAGS
+# undef APPEND_FLAG
+
+ nsAutoCString lang;
+ mFontGroup->Language()->ToUTF8String(lang);
+ fprintf(out, "gfxTextRun@%p (length %u) [%s] [%s] [%s]\n", this, mLength,
+ flagsString.get(), flags2String.get(), lang.get());
+
+ fprintf(out, " Glyph runs:\n");
+ for (const auto& run : mGlyphRuns) {
+ gfxFont* font = run.mFont;
+ const gfxFontStyle* style = font->GetStyle();
+ nsAutoCString styleString;
+ style->style.ToString(styleString);
+ fprintf(out, " offset=%d %s %f/%g/%s\n", run.mCharacterOffset,
+ font->GetName().get(), style->size, style->weight.ToFloat(),
+ styleString.get());
+ }
+
+ fprintf(out, " Glyphs:\n");
+ for (uint32_t i = 0; i < mLength; ++i) {
+ auto glyphData = GetCharacterGlyphs()[i];
+
+ nsCString line;
+ line.AppendPrintf(" [%d] 0x%p %s", i, GetCharacterGlyphs() + i,
+ glyphData.IsSimpleGlyph() ? "simple" : "detailed");
+
+ if (glyphData.IsSimpleGlyph()) {
+ line.AppendPrintf(" id=%d adv=%d", glyphData.GetSimpleGlyph(),
+ glyphData.GetSimpleAdvance());
+ } else {
+ uint32_t count = glyphData.GetGlyphCount();
+ if (count) {
+ line += " ids=";
+ for (uint32_t j = 0; j < count; j++) {
+ line.AppendPrintf(j ? ",%d" : "%d", GetDetailedGlyphs(i)[j].mGlyphID);
+ }
+ line += " advs=";
+ for (uint32_t j = 0; j < count; j++) {
+ line.AppendPrintf(j ? ",%d" : "%d", GetDetailedGlyphs(i)[j].mAdvance);
+ }
+ line += " offsets=";
+ for (uint32_t j = 0; j < count; j++) {
+ auto offset = GetDetailedGlyphs(i)[j].mOffset;
+ line.AppendPrintf(j ? ",(%g,%g)" : "(%g,%g)", offset.x.value,
+ offset.y.value);
+ }
+ } else {
+ line += " (no glyphs)";
+ }
+ }
+
+ if (glyphData.CharIsSpace()) {
+ line += " CHAR_IS_SPACE";
+ }
+ if (glyphData.CharIsTab()) {
+ line += " CHAR_IS_TAB";
+ }
+ if (glyphData.CharIsNewline()) {
+ line += " CHAR_IS_NEWLINE";
+ }
+ if (glyphData.CharIsFormattingControl()) {
+ line += " CHAR_IS_FORMATTING_CONTROL";
+ }
+ if (glyphData.CharTypeFlags() &
+ CompressedGlyph::FLAG_CHAR_NO_EMPHASIS_MARK) {
+ line += " CHAR_NO_EMPHASIS_MARK";
+ }
+
+ if (!glyphData.IsSimpleGlyph()) {
+ if (!glyphData.IsMissing()) {
+ line += " NOT_MISSING";
+ }
+ if (!glyphData.IsClusterStart()) {
+ line += " NOT_IS_CLUSTER_START";
+ }
+ if (!glyphData.IsLigatureGroupStart()) {
+ line += " NOT_LIGATURE_GROUP_START";
+ }
+ }
+
+ switch (glyphData.CanBreakBefore()) {
+ case CompressedGlyph::FLAG_BREAK_TYPE_NORMAL:
+ line += " BREAK_TYPE_NORMAL";
+ break;
+ case CompressedGlyph::FLAG_BREAK_TYPE_HYPHEN:
+ line += " BREAK_TYPE_HYPHEN";
+ break;
+ }
+
+ fprintf(out, "%s\n", line.get());
+ }
+}
+#endif
+
+gfxFontGroup::gfxFontGroup(nsPresContext* aPresContext,
+ const StyleFontFamilyList& aFontFamilyList,
+ const gfxFontStyle* aStyle, nsAtom* aLanguage,
+ bool aExplicitLanguage,
+ gfxTextPerfMetrics* aTextPerf,
+ gfxUserFontSet* aUserFontSet, gfxFloat aDevToCssSize,
+ StyleFontVariantEmoji aVariantEmoji)
+ : mPresContext(aPresContext), // Note that aPresContext may be null!
+ mFamilyList(aFontFamilyList),
+ mStyle(*aStyle),
+ mLanguage(aLanguage),
+ mUnderlineOffset(UNDERLINE_OFFSET_NOT_SET),
+ mHyphenWidth(-1),
+ mDevToCssSize(aDevToCssSize),
+ mUserFontSet(aUserFontSet),
+ mTextPerf(aTextPerf),
+ mLastPrefLang(eFontPrefLang_Western),
+ mPageLang(gfxPlatformFontList::GetFontPrefLangFor(aLanguage)),
+ mLastPrefFirstFont(false),
+ mSkipDrawing(false),
+ mExplicitLanguage(aExplicitLanguage) {
+ switch (aVariantEmoji) {
+ case StyleFontVariantEmoji::Normal:
+ case StyleFontVariantEmoji::Unicode:
+ break;
+ case StyleFontVariantEmoji::Text:
+ mEmojiPresentation = eFontPresentation::Text;
+ break;
+ case StyleFontVariantEmoji::Emoji:
+ mEmojiPresentation = eFontPresentation::EmojiExplicit;
+ break;
+ }
+ // We don't use SetUserFontSet() here, as we want to unconditionally call
+ // BuildFontList() rather than only do UpdateUserFonts() if it changed.
+ mCurrGeneration = GetGeneration();
+ BuildFontList();
+}
+
+gfxFontGroup::~gfxFontGroup() {
+ // Should not be dropped by stylo
+ MOZ_ASSERT(!Servo_IsWorkerThread());
+}
+
+static StyleGenericFontFamily GetDefaultGeneric(nsAtom* aLanguage) {
+ return StaticPresData::Get()
+ ->GetFontPrefsForLang(aLanguage)
+ ->GetDefaultGeneric();
+}
+
+void gfxFontGroup::BuildFontList() {
+ // initialize fonts in the font family list
+ AutoTArray<FamilyAndGeneric, 10> fonts;
+ gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
+ mFontListGeneration = pfl->GetGeneration();
+
+ // lookup fonts in the fontlist
+ for (const StyleSingleFontFamily& name : mFamilyList.list.AsSpan()) {
+ if (name.IsFamilyName()) {
+ const auto& familyName = name.AsFamilyName();
+ AddPlatformFont(nsAtomCString(familyName.name.AsAtom()),
+ familyName.syntax == StyleFontFamilyNameSyntax::Quoted,
+ fonts);
+ } else {
+ MOZ_ASSERT(name.IsGeneric());
+ const StyleGenericFontFamily generic = name.AsGeneric();
+ // system-ui is usually a single family, so it doesn't work great as
+ // fallback. Prefer the following generic or the language default instead.
+ if (mFallbackGeneric == StyleGenericFontFamily::None &&
+ generic != StyleGenericFontFamily::SystemUi) {
+ mFallbackGeneric = generic;
+ }
+ pfl->AddGenericFonts(mPresContext, generic, mLanguage, fonts);
+ if (mTextPerf) {
+ mTextPerf->current.genericLookups++;
+ }
+ }
+ }
+
+ // If necessary, append default language generic onto the end.
+ if (mFallbackGeneric == StyleGenericFontFamily::None && !mStyle.systemFont) {
+ auto defaultLanguageGeneric = GetDefaultGeneric(mLanguage);
+
+ pfl->AddGenericFonts(mPresContext, defaultLanguageGeneric, mLanguage,
+ fonts);
+ if (mTextPerf) {
+ mTextPerf->current.genericLookups++;
+ }
+ }
+
+ // build the fontlist from the specified families
+ for (const auto& f : fonts) {
+ if (f.mFamily.mShared) {
+ AddFamilyToFontList(f.mFamily.mShared, f.mGeneric);
+ } else {
+ AddFamilyToFontList(f.mFamily.mUnshared, f.mGeneric);
+ }
+ }
+}
+
+void gfxFontGroup::AddPlatformFont(const nsACString& aName, bool aQuotedName,
+ nsTArray<FamilyAndGeneric>& aFamilyList) {
+ // First, look up in the user font set...
+ // If the fontSet matches the family, we must not look for a platform
+ // font of the same name, even if we fail to actually get a fontEntry
+ // here; we'll fall back to the next name in the CSS font-family list.
+ if (mUserFontSet) {
+ // Add userfonts to the fontlist whether already loaded
+ // or not. Loading is initiated during font matching.
+ RefPtr<gfxFontFamily> family = mUserFontSet->LookupFamily(aName);
+ if (family) {
+ aFamilyList.AppendElement(std::move(family));
+ return;
+ }
+ }
+
+ // Not known in the user font set ==> check system fonts
+ gfxPlatformFontList::PlatformFontList()->FindAndAddFamilies(
+ mPresContext, StyleGenericFontFamily::None, aName, &aFamilyList,
+ aQuotedName ? gfxPlatformFontList::FindFamiliesFlags::eQuotedFamilyName
+ : gfxPlatformFontList::FindFamiliesFlags(0),
+ &mStyle, mLanguage.get(), mDevToCssSize);
+}
+
+void gfxFontGroup::AddFamilyToFontList(gfxFontFamily* aFamily,
+ StyleGenericFontFamily aGeneric) {
+ if (!aFamily) {
+ MOZ_ASSERT_UNREACHABLE("don't try to add a null font family!");
+ return;
+ }
+ AutoTArray<gfxFontEntry*, 4> fontEntryList;
+ aFamily->FindAllFontsForStyle(mStyle, fontEntryList);
+ // add these to the fontlist
+ for (gfxFontEntry* fe : fontEntryList) {
+ if (!HasFont(fe)) {
+ FamilyFace ff(aFamily, fe, aGeneric);
+ if (fe->mIsUserFontContainer) {
+ ff.CheckState(mSkipDrawing);
+ }
+ mFonts.AppendElement(ff);
+ }
+ }
+ // for a family marked as "check fallback faces", only mark the last
+ // entry so that fallbacks for a family are only checked once
+ if (aFamily->CheckForFallbackFaces() && !fontEntryList.IsEmpty() &&
+ !mFonts.IsEmpty()) {
+ mFonts.LastElement().SetCheckForFallbackFaces();
+ }
+}
+
+void gfxFontGroup::AddFamilyToFontList(fontlist::Family* aFamily,
+ StyleGenericFontFamily aGeneric) {
+ gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
+ if (!aFamily->IsInitialized()) {
+ if (ServoStyleSet* set = gfxFontUtils::CurrentServoStyleSet()) {
+ // If we need to initialize a Family record, but we're on a style
+ // worker thread, we have to defer it.
+ set->AppendTask(PostTraversalTask::InitializeFamily(aFamily));
+ set->AppendTask(PostTraversalTask::FontInfoUpdate(set));
+ return;
+ }
+ if (!pfl->InitializeFamily(aFamily)) {
+ return;
+ }
+ }
+ AutoTArray<fontlist::Face*, 4> faceList;
+ aFamily->FindAllFacesForStyle(pfl->SharedFontList(), mStyle, faceList);
+ for (auto* face : faceList) {
+ gfxFontEntry* fe = pfl->GetOrCreateFontEntry(face, aFamily);
+ if (fe && !HasFont(fe)) {
+ FamilyFace ff(aFamily, fe, aGeneric);
+ mFonts.AppendElement(ff);
+ }
+ }
+}
+
+bool gfxFontGroup::HasFont(const gfxFontEntry* aFontEntry) {
+ for (auto& f : mFonts) {
+ if (f.FontEntry() == aFontEntry) {
+ return true;
+ }
+ }
+ return false;
+}
+
+already_AddRefed<gfxFont> gfxFontGroup::GetFontAt(int32_t i, uint32_t aCh,
+ bool* aLoading) {
+ if (uint32_t(i) >= mFonts.Length()) {
+ return nullptr;
+ }
+
+ FamilyFace& ff = mFonts[i];
+ if (ff.IsInvalid() || ff.IsLoading()) {
+ return nullptr;
+ }
+
+ RefPtr<gfxFont> font = ff.Font();
+ if (!font) {
+ gfxFontEntry* fe = ff.FontEntry();
+ if (!fe) {
+ return nullptr;
+ }
+ gfxCharacterMap* unicodeRangeMap = nullptr;
+ if (fe->mIsUserFontContainer) {
+ gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe);
+ if (ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED &&
+ ufe->CharacterInUnicodeRange(aCh) && !*aLoading) {
+ ufe->Load();
+ ff.CheckState(mSkipDrawing);
+ *aLoading = ff.IsLoading();
+ }
+ fe = ufe->GetPlatformFontEntry();
+ if (!fe) {
+ return nullptr;
+ }
+ unicodeRangeMap = ufe->GetUnicodeRangeMap();
+ }
+ font = fe->FindOrMakeFont(&mStyle, unicodeRangeMap);
+ if (!font || !font->Valid()) {
+ ff.SetInvalid();
+ return nullptr;
+ }
+ ff.SetFont(font);
+ }
+ return font.forget();
+}
+
+void gfxFontGroup::FamilyFace::CheckState(bool& aSkipDrawing) {
+ gfxFontEntry* fe = FontEntry();
+ if (!fe) {
+ return;
+ }
+ if (fe->mIsUserFontContainer) {
+ gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe);
+ gfxUserFontEntry::UserFontLoadState state = ufe->LoadState();
+ switch (state) {
+ case gfxUserFontEntry::STATUS_LOAD_PENDING:
+ case gfxUserFontEntry::STATUS_LOADING:
+ SetLoading(true);
+ break;
+ case gfxUserFontEntry::STATUS_FAILED:
+ SetInvalid();
+ // fall-thru to the default case
+ [[fallthrough]];
+ default:
+ SetLoading(false);
+ }
+ if (ufe->WaitForUserFont()) {
+ aSkipDrawing = true;
+ }
+ }
+}
+
+bool gfxFontGroup::FamilyFace::EqualsUserFont(
+ const gfxUserFontEntry* aUserFont) const {
+ gfxFontEntry* fe = FontEntry();
+ // if there's a font, the entry is the underlying platform font
+ if (mFontCreated) {
+ gfxFontEntry* pfe = aUserFont->GetPlatformFontEntry();
+ if (pfe == fe) {
+ return true;
+ }
+ } else if (fe == aUserFont) {
+ return true;
+ }
+ return false;
+}
+
+static nsAutoCString FamilyListToString(
+ const StyleFontFamilyList& aFamilyList) {
+ return StringJoin(","_ns, aFamilyList.list.AsSpan(),
+ [](nsACString& dst, const StyleSingleFontFamily& name) {
+ name.AppendToString(dst);
+ });
+}
+
+already_AddRefed<gfxFont> gfxFontGroup::GetDefaultFont() {
+ if (mDefaultFont) {
+ return do_AddRef(mDefaultFont);
+ }
+
+ gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
+ FontFamily family = pfl->GetDefaultFont(mPresContext, &mStyle);
+ MOZ_ASSERT(!family.IsNull(),
+ "invalid default font returned by GetDefaultFont");
+
+ gfxFontEntry* fe = nullptr;
+ if (family.mShared) {
+ fontlist::Family* fam = family.mShared;
+ if (!fam->IsInitialized()) {
+ // If this fails, FindFaceForStyle will just safely return nullptr
+ Unused << pfl->InitializeFamily(fam);
+ }
+ fontlist::Face* face = fam->FindFaceForStyle(pfl->SharedFontList(), mStyle);
+ if (face) {
+ fe = pfl->GetOrCreateFontEntry(face, fam);
+ }
+ } else {
+ fe = family.mUnshared->FindFontForStyle(mStyle);
+ }
+ if (fe) {
+ mDefaultFont = fe->FindOrMakeFont(&mStyle);
+ }
+
+ uint32_t numInits, loaderState;
+ pfl->GetFontlistInitInfo(numInits, loaderState);
+
+ MOZ_ASSERT(numInits != 0,
+ "must initialize system fontlist before getting default font!");
+
+ uint32_t numFonts = 0;
+ if (!mDefaultFont) {
+ // Try for a "font of last resort...."
+ // Because an empty font list would be Really Bad for later code
+ // that assumes it will be able to get valid metrics for layout,
+ // just look for the first usable font and put in the list.
+ // (see bug 554544)
+ if (pfl->SharedFontList()) {
+ fontlist::FontList* list = pfl->SharedFontList();
+ numFonts = list->NumFamilies();
+ fontlist::Family* families = list->Families();
+ for (uint32_t i = 0; i < numFonts; ++i) {
+ fontlist::Family* fam = &families[i];
+ if (!fam->IsInitialized()) {
+ Unused << pfl->InitializeFamily(fam);
+ }
+ fontlist::Face* face =
+ fam->FindFaceForStyle(pfl->SharedFontList(), mStyle);
+ if (face) {
+ fe = pfl->GetOrCreateFontEntry(face, fam);
+ if (fe) {
+ mDefaultFont = fe->FindOrMakeFont(&mStyle);
+ if (mDefaultFont) {
+ break;
+ }
+ NS_WARNING("FindOrMakeFont failed");
+ }
+ }
+ }
+ } else {
+ AutoTArray<RefPtr<gfxFontFamily>, 200> familyList;
+ pfl->GetFontFamilyList(familyList);
+ numFonts = familyList.Length();
+ for (uint32_t i = 0; i < numFonts; ++i) {
+ gfxFontEntry* fe = familyList[i]->FindFontForStyle(mStyle, true);
+ if (fe) {
+ mDefaultFont = fe->FindOrMakeFont(&mStyle);
+ if (mDefaultFont) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (!mDefaultFont && pfl->SharedFontList() && !XRE_IsParentProcess()) {
+ // If we're a content process, it's possible this is failing because the
+ // chrome process has just updated the shared font list and we haven't yet
+ // refreshed our reference to it. If that's the case, update and retry.
+ // But if we're not on the main thread, we can't do this, so just use
+ // the platform default font directly.
+ if (NS_IsMainThread()) {
+ uint32_t oldGeneration = pfl->SharedFontList()->GetGeneration();
+ pfl->UpdateFontList();
+ if (pfl->SharedFontList()->GetGeneration() != oldGeneration) {
+ return GetDefaultFont();
+ }
+ }
+ }
+
+ if (!mDefaultFont) {
+ // We must have failed to find anything usable in our font-family list,
+ // or it's badly broken. One more last-ditch effort to make a font:
+ gfxFontEntry* fe = pfl->GetDefaultFontEntry();
+ if (fe) {
+ RefPtr<gfxFont> f = fe->FindOrMakeFont(&mStyle);
+ if (f) {
+ return f.forget();
+ }
+ }
+ }
+
+ if (!mDefaultFont) {
+ // an empty font list at this point is fatal; we're not going to
+ // be able to do even the most basic layout operations
+
+ // annotate crash report with fontlist info
+ nsAutoCString fontInitInfo;
+ fontInitInfo.AppendPrintf("no fonts - init: %d fonts: %d loader: %d",
+ numInits, numFonts, loaderState);
+#ifdef XP_WIN
+ bool dwriteEnabled = gfxWindowsPlatform::GetPlatform()->DWriteEnabled();
+ double upTime = (double)GetTickCount();
+ fontInitInfo.AppendPrintf(" backend: %s system-uptime: %9.3f sec",
+ dwriteEnabled ? "directwrite" : "gdi",
+ upTime / 1000);
+#endif
+ gfxCriticalError() << fontInitInfo.get();
+
+ char msg[256]; // CHECK buffer length if revising message below
+ SprintfLiteral(msg, "unable to find a usable font (%.220s)",
+ FamilyListToString(mFamilyList).get());
+ MOZ_CRASH_UNSAFE(msg);
+ }
+
+ return do_AddRef(mDefaultFont);
+}
+
+already_AddRefed<gfxFont> gfxFontGroup::GetFirstValidFont(
+ uint32_t aCh, StyleGenericFontFamily* aGeneric, bool* aIsFirst) {
+ // Ensure cached font instances are valid.
+ CheckForUpdatedPlatformList();
+
+ uint32_t count = mFonts.Length();
+ bool loading = false;
+
+ // Check whether the font supports the given character, unless the char is
+ // SPACE, in which case it is not required to be present in the font, but
+ // we must still check if it was excluded by a unicode-range descriptor.
+ auto isValidForChar = [](gfxFont* aFont, uint32_t aCh) -> bool {
+ if (!aFont) {
+ return false;
+ }
+ if (aCh == 0x20) {
+ if (const auto* unicodeRange = aFont->GetUnicodeRangeMap()) {
+ return unicodeRange->test(aCh);
+ }
+ return true;
+ }
+ return aFont->HasCharacter(aCh);
+ };
+
+ for (uint32_t i = 0; i < count; ++i) {
+ FamilyFace& ff = mFonts[i];
+ if (ff.IsInvalid()) {
+ continue;
+ }
+
+ // already have a font?
+ RefPtr<gfxFont> font = ff.Font();
+ if (isValidForChar(font, aCh)) {
+ if (aGeneric) {
+ *aGeneric = ff.Generic();
+ }
+ if (aIsFirst) {
+ *aIsFirst = (i == 0);
+ }
+ return font.forget();
+ }
+
+ // Need to build a font, loading userfont if not loaded. In
+ // cases where unicode range might apply, use the character
+ // provided.
+ gfxFontEntry* fe = ff.FontEntry();
+ if (fe && fe->mIsUserFontContainer) {
+ gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe);
+ bool inRange = ufe->CharacterInUnicodeRange(aCh);
+ if (inRange) {
+ if (!loading &&
+ ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED) {
+ ufe->Load();
+ ff.CheckState(mSkipDrawing);
+ }
+ if (ff.IsLoading()) {
+ loading = true;
+ }
+ }
+ if (ufe->LoadState() != gfxUserFontEntry::STATUS_LOADED || !inRange) {
+ continue;
+ }
+ }
+
+ font = GetFontAt(i, aCh, &loading);
+ if (isValidForChar(font, aCh)) {
+ if (aGeneric) {
+ *aGeneric = ff.Generic();
+ }
+ if (aIsFirst) {
+ *aIsFirst = (i == 0);
+ }
+ return font.forget();
+ }
+ }
+ if (aGeneric) {
+ *aGeneric = StyleGenericFontFamily::None;
+ }
+ if (aIsFirst) {
+ *aIsFirst = false;
+ }
+ return GetDefaultFont();
+}
+
+already_AddRefed<gfxFont> gfxFontGroup::GetFirstMathFont() {
+ uint32_t count = mFonts.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ RefPtr<gfxFont> font = GetFontAt(i);
+ if (font && font->TryGetMathTable()) {
+ return font.forget();
+ }
+ }
+ return nullptr;
+}
+
+bool gfxFontGroup::IsInvalidChar(uint8_t ch) {
+ return ((ch & 0x7f) < 0x20 || ch == 0x7f);
+}
+
+bool gfxFontGroup::IsInvalidChar(char16_t ch) {
+ // All printable 7-bit ASCII values are OK
+ if (ch >= ' ' && ch < 0x7f) {
+ return false;
+ }
+ // No point in sending non-printing control chars through font shaping
+ if (ch <= 0x9f) {
+ return true;
+ }
+ // Word-separating format/bidi control characters are not shaped as part
+ // of words.
+ return (((ch & 0xFF00) == 0x2000 /* Unicode control character */ &&
+ (ch == 0x200B /*ZWSP*/ || ch == 0x2028 /*LSEP*/ ||
+ ch == 0x2029 /*PSEP*/ || ch == 0x2060 /*WJ*/)) ||
+ ch == 0xfeff /*ZWNBSP*/ || IsBidiControl(ch));
+}
+
+already_AddRefed<gfxTextRun> gfxFontGroup::MakeEmptyTextRun(
+ const Parameters* aParams, gfx::ShapedTextFlags aFlags,
+ nsTextFrameUtils::Flags aFlags2) {
+ aFlags |= ShapedTextFlags::TEXT_IS_8BIT;
+ return gfxTextRun::Create(aParams, 0, this, aFlags, aFlags2);
+}
+
+already_AddRefed<gfxTextRun> gfxFontGroup::MakeSpaceTextRun(
+ const Parameters* aParams, gfx::ShapedTextFlags aFlags,
+ nsTextFrameUtils::Flags aFlags2) {
+ aFlags |= ShapedTextFlags::TEXT_IS_8BIT;
+
+ RefPtr<gfxTextRun> textRun =
+ gfxTextRun::Create(aParams, 1, this, aFlags, aFlags2);
+ if (!textRun) {
+ return nullptr;
+ }
+
+ gfx::ShapedTextFlags orientation = aFlags & ShapedTextFlags::TEXT_ORIENT_MASK;
+ if (orientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) {
+ orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
+ }
+
+ RefPtr<gfxFont> font = GetFirstValidFont();
+ if (MOZ_UNLIKELY(GetStyle()->AdjustedSizeMustBeZero())) {
+ // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle
+ // them, and always create at least size 1 fonts, i.e. they still
+ // render something for size 0 fonts.
+ textRun->AddGlyphRun(font, FontMatchType::Kind::kUnspecified, 0, false,
+ orientation, false);
+ } else {
+ if (font->GetSpaceGlyph()) {
+ // Normally, the font has a cached space glyph, so we can avoid
+ // the cost of calling FindFontForChar.
+ textRun->SetSpaceGlyph(font, aParams->mDrawTarget, 0, orientation);
+ } else {
+ // In case the primary font doesn't have <space> (bug 970891),
+ // find one that does.
+ FontMatchType matchType;
+ RefPtr<gfxFont> spaceFont =
+ FindFontForChar(' ', 0, 0, Script::LATIN, nullptr, &matchType);
+ if (spaceFont) {
+ textRun->SetSpaceGlyph(spaceFont, aParams->mDrawTarget, 0, orientation);
+ }
+ }
+ }
+
+ // Note that the gfxGlyphExtents glyph bounds storage for the font will
+ // always contain an entry for the font's space glyph, so we don't have
+ // to call FetchGlyphExtents here.
+ return textRun.forget();
+}
+
+template <typename T>
+already_AddRefed<gfxTextRun> gfxFontGroup::MakeBlankTextRun(
+ const T* aString, uint32_t aLength, const Parameters* aParams,
+ gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2) {
+ RefPtr<gfxTextRun> textRun =
+ gfxTextRun::Create(aParams, aLength, this, aFlags, aFlags2);
+ if (!textRun) {
+ return nullptr;
+ }
+
+ gfx::ShapedTextFlags orientation = aFlags & ShapedTextFlags::TEXT_ORIENT_MASK;
+ if (orientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) {
+ orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
+ }
+ RefPtr<gfxFont> font = GetFirstValidFont();
+ textRun->AddGlyphRun(font, FontMatchType::Kind::kUnspecified, 0, false,
+ orientation, false);
+
+ textRun->SetupClusterBoundaries(0, aString, aLength);
+
+ for (uint32_t i = 0; i < aLength; i++) {
+ if (aString[i] == '\n') {
+ textRun->SetIsNewline(i);
+ } else if (aString[i] == '\t') {
+ textRun->SetIsTab(i);
+ }
+ }
+
+ return textRun.forget();
+}
+
+already_AddRefed<gfxTextRun> gfxFontGroup::MakeHyphenTextRun(
+ DrawTarget* aDrawTarget, gfx::ShapedTextFlags aFlags,
+ uint32_t aAppUnitsPerDevUnit) {
+ // only use U+2010 if it is supported by the first font in the group;
+ // it's better to use ASCII '-' from the primary font than to fall back to
+ // U+2010 from some other, possibly poorly-matching face
+ static const char16_t hyphen = 0x2010;
+ RefPtr<gfxFont> font = GetFirstValidFont(uint32_t(hyphen));
+ if (font->HasCharacter(hyphen)) {
+ return MakeTextRun(&hyphen, 1, aDrawTarget, aAppUnitsPerDevUnit, aFlags,
+ nsTextFrameUtils::Flags(), nullptr);
+ }
+
+ static const uint8_t dash = '-';
+ return MakeTextRun(&dash, 1, aDrawTarget, aAppUnitsPerDevUnit, aFlags,
+ nsTextFrameUtils::Flags(), nullptr);
+}
+
+gfxFloat gfxFontGroup::GetHyphenWidth(
+ const gfxTextRun::PropertyProvider* aProvider) {
+ if (mHyphenWidth < 0) {
+ RefPtr<DrawTarget> dt(aProvider->GetDrawTarget());
+ if (dt) {
+ RefPtr<gfxTextRun> hyphRun(
+ MakeHyphenTextRun(dt, aProvider->GetShapedTextFlags(),
+ aProvider->GetAppUnitsPerDevUnit()));
+ mHyphenWidth = hyphRun.get() ? hyphRun->GetAdvanceWidth() : 0;
+ }
+ }
+ return mHyphenWidth;
+}
+
+template <typename T>
+already_AddRefed<gfxTextRun> gfxFontGroup::MakeTextRun(
+ const T* aString, uint32_t aLength, const Parameters* aParams,
+ gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2,
+ gfxMissingFontRecorder* aMFR) {
+ if (aLength == 0) {
+ return MakeEmptyTextRun(aParams, aFlags, aFlags2);
+ }
+ if (aLength == 1 && aString[0] == ' ') {
+ return MakeSpaceTextRun(aParams, aFlags, aFlags2);
+ }
+
+ if (sizeof(T) == 1) {
+ aFlags |= ShapedTextFlags::TEXT_IS_8BIT;
+ }
+
+ if (MOZ_UNLIKELY(GetStyle()->AdjustedSizeMustBeZero())) {
+ // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle
+ // them, and always create at least size 1 fonts, i.e. they still
+ // render something for size 0 fonts.
+ return MakeBlankTextRun(aString, aLength, aParams, aFlags, aFlags2);
+ }
+
+ RefPtr<gfxTextRun> textRun =
+ gfxTextRun::Create(aParams, aLength, this, aFlags, aFlags2);
+ if (!textRun) {
+ return nullptr;
+ }
+
+ InitTextRun(aParams->mDrawTarget, textRun.get(), aString, aLength, aMFR);
+
+ textRun->FetchGlyphExtents(aParams->mDrawTarget);
+
+ return textRun.forget();
+}
+
+// MakeTextRun instantiations (needed by Linux64 base-toolchain build).
+template already_AddRefed<gfxTextRun> gfxFontGroup::MakeTextRun(
+ const uint8_t* aString, uint32_t aLength, const Parameters* aParams,
+ gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2,
+ gfxMissingFontRecorder* aMFR);
+template already_AddRefed<gfxTextRun> gfxFontGroup::MakeTextRun(
+ const char16_t* aString, uint32_t aLength, const Parameters* aParams,
+ gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2,
+ gfxMissingFontRecorder* aMFR);
+
+template <typename T>
+void gfxFontGroup::InitTextRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
+ const T* aString, uint32_t aLength,
+ gfxMissingFontRecorder* aMFR) {
+ NS_ASSERTION(aLength > 0, "don't call InitTextRun for a zero-length run");
+
+ // we need to do numeral processing even on 8-bit text,
+ // in case we're converting Western to Hindi/Arabic digits
+ uint32_t numOption = gfxPlatform::GetPlatform()->GetBidiNumeralOption();
+ UniquePtr<char16_t[]> transformedString;
+ if (numOption != IBMBIDI_NUMERAL_NOMINAL) {
+ // scan the string for numerals that may need to be transformed;
+ // if we find any, we'll make a local copy here and use that for
+ // font matching and glyph generation/shaping
+ bool prevIsArabic =
+ !!(aTextRun->GetFlags() & ShapedTextFlags::TEXT_INCOMING_ARABICCHAR);
+ for (uint32_t i = 0; i < aLength; ++i) {
+ char16_t origCh = aString[i];
+ char16_t newCh = HandleNumberInChar(origCh, prevIsArabic, numOption);
+ if (newCh != origCh) {
+ if (!transformedString) {
+ transformedString = MakeUnique<char16_t[]>(aLength);
+ if constexpr (sizeof(T) == sizeof(char16_t)) {
+ memcpy(transformedString.get(), aString, i * sizeof(char16_t));
+ } else {
+ for (uint32_t j = 0; j < i; ++j) {
+ transformedString[j] = aString[j];
+ }
+ }
+ }
+ }
+ if (transformedString) {
+ transformedString[i] = newCh;
+ }
+ prevIsArabic = IS_ARABIC_CHAR(newCh);
+ }
+ }
+
+ LogModule* log = mStyle.systemFont ? gfxPlatform::GetLog(eGfxLog_textrunui)
+ : gfxPlatform::GetLog(eGfxLog_textrun);
+
+ // variant fallback handling may end up passing through this twice
+ bool redo;
+ do {
+ redo = false;
+
+ if (sizeof(T) == sizeof(uint8_t) && !transformedString) {
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Warning))) {
+ nsAutoCString lang;
+ mLanguage->ToUTF8String(lang);
+ nsAutoCString str((const char*)aString, aLength);
+ nsAutoCString styleString;
+ mStyle.style.ToString(styleString);
+ auto defaultLanguageGeneric = GetDefaultGeneric(mLanguage);
+ MOZ_LOG(
+ log, LogLevel::Warning,
+ ("(%s) fontgroup: [%s] default: %s lang: %s script: %d "
+ "len %d weight: %g stretch: %g%% style: %s size: %6.2f %zu-byte "
+ "TEXTRUN [%s] ENDTEXTRUN\n",
+ (mStyle.systemFont ? "textrunui" : "textrun"),
+ FamilyListToString(mFamilyList).get(),
+ (defaultLanguageGeneric == StyleGenericFontFamily::Serif
+ ? "serif"
+ : (defaultLanguageGeneric == StyleGenericFontFamily::SansSerif
+ ? "sans-serif"
+ : "none")),
+ lang.get(), static_cast<int>(Script::LATIN), aLength,
+ mStyle.weight.ToFloat(), mStyle.stretch.ToFloat(),
+ styleString.get(), mStyle.size, sizeof(T), str.get()));
+ }
+
+ // the text is still purely 8-bit; bypass the script-run itemizer
+ // and treat it as a single Latin run
+ InitScriptRun(aDrawTarget, aTextRun, aString, 0, aLength, Script::LATIN,
+ aMFR);
+ } else {
+ const char16_t* textPtr;
+ if (transformedString) {
+ textPtr = transformedString.get();
+ } else {
+ // typecast to avoid compilation error for the 8-bit version,
+ // even though this is dead code in that case
+ textPtr = reinterpret_cast<const char16_t*>(aString);
+ }
+
+ // split into script runs so that script can potentially influence
+ // the font matching process below
+ gfxScriptItemizer scriptRuns(textPtr, aLength);
+
+ uint32_t runStart = 0, runLimit = aLength;
+ Script runScript = Script::LATIN;
+ while (scriptRuns.Next(runStart, runLimit, runScript)) {
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Warning))) {
+ nsAutoCString lang;
+ mLanguage->ToUTF8String(lang);
+ nsAutoCString styleString;
+ mStyle.style.ToString(styleString);
+ auto defaultLanguageGeneric = GetDefaultGeneric(mLanguage);
+ uint32_t runLen = runLimit - runStart;
+ MOZ_LOG(log, LogLevel::Warning,
+ ("(%s) fontgroup: [%s] default: %s lang: %s script: %d "
+ "len %d weight: %g stretch: %g%% style: %s size: %6.2f "
+ "%zu-byte TEXTRUN [%s] ENDTEXTRUN\n",
+ (mStyle.systemFont ? "textrunui" : "textrun"),
+ FamilyListToString(mFamilyList).get(),
+ (defaultLanguageGeneric == StyleGenericFontFamily::Serif
+ ? "serif"
+ : (defaultLanguageGeneric ==
+ StyleGenericFontFamily::SansSerif
+ ? "sans-serif"
+ : "none")),
+ lang.get(), static_cast<int>(runScript), runLen,
+ mStyle.weight.ToFloat(), mStyle.stretch.ToFloat(),
+ styleString.get(), mStyle.size, sizeof(T),
+ NS_ConvertUTF16toUTF8(textPtr + runStart, runLen).get()));
+ }
+
+ InitScriptRun(aDrawTarget, aTextRun, textPtr + runStart, runStart,
+ runLimit - runStart, runScript, aMFR);
+ }
+ }
+
+ // if shaping was aborted due to lack of feature support, clear out
+ // glyph runs and redo shaping with fallback forced on
+ if (aTextRun->GetShapingState() == gfxTextRun::eShapingState_Aborted) {
+ redo = true;
+ aTextRun->SetShapingState(gfxTextRun::eShapingState_ForceFallbackFeature);
+ aTextRun->ClearGlyphsAndCharacters();
+ }
+
+ } while (redo);
+
+ if (sizeof(T) == sizeof(char16_t) && aLength > 0) {
+ gfxTextRun::CompressedGlyph* glyph = aTextRun->GetCharacterGlyphs();
+ if (!glyph->IsSimpleGlyph()) {
+ glyph->SetClusterStart(true);
+ }
+ }
+
+ // It's possible for CoreText to omit glyph runs if it decides they contain
+ // only invisibles (e.g., U+FEFF, see reftest 474417-1). In this case, we
+ // need to eliminate them from the glyph run array to avoid drawing "partial
+ // ligatures" with the wrong font.
+ // We don't do this during InitScriptRun (or gfxFont::InitTextRun) because
+ // it will iterate back over all glyphruns in the textrun, which leads to
+ // pathologically-bad perf in the case where a textrun contains many script
+ // changes (see bug 680402) - we'd end up re-sanitizing all the earlier runs
+ // every time a new script subrun is processed.
+ aTextRun->SanitizeGlyphRuns();
+}
+
+static inline bool IsPUA(uint32_t aUSV) {
+ // We could look up the General Category of the codepoint here,
+ // but it's simpler to check PUA codepoint ranges.
+ return (aUSV >= 0xE000 && aUSV <= 0xF8FF) || (aUSV >= 0xF0000);
+}
+
+template <typename T>
+void gfxFontGroup::InitScriptRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
+ const T* aString, // text for this script run,
+ // not the entire textrun
+ uint32_t aOffset, // position of the script
+ // run within the textrun
+ uint32_t aLength, // length of the script run
+ Script aRunScript,
+ gfxMissingFontRecorder* aMFR) {
+ NS_ASSERTION(aLength > 0, "don't call InitScriptRun for a 0-length run");
+ NS_ASSERTION(aTextRun->GetShapingState() != gfxTextRun::eShapingState_Aborted,
+ "don't call InitScriptRun with aborted shaping state");
+
+ // confirm the load state of userfonts in the list
+ if (mUserFontSet && mCurrGeneration != mUserFontSet->GetGeneration()) {
+ UpdateUserFonts();
+ }
+
+ RefPtr<gfxFont> mainFont = GetFirstValidFont();
+
+ ShapedTextFlags orientation =
+ aTextRun->GetFlags() & ShapedTextFlags::TEXT_ORIENT_MASK;
+
+ if (orientation != ShapedTextFlags::TEXT_ORIENT_HORIZONTAL &&
+ (aRunScript == Script::MONGOLIAN || aRunScript == Script::PHAGS_PA)) {
+ // Mongolian and Phags-pa text should ignore text-orientation and
+ // always render in its "native" vertical mode, implemented by fonts
+ // as sideways-right (i.e as if shaped horizontally, and then the
+ // entire line is rotated to render vertically). Therefore, we ignore
+ // the aOrientation value from the textrun's flags, and make all
+ // vertical Mongolian/Phags-pa use sideways-right.
+ orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
+ }
+
+ uint32_t runStart = 0;
+ AutoTArray<TextRange, 3> fontRanges;
+ ComputeRanges(fontRanges, aString, aLength, aRunScript, orientation);
+ uint32_t numRanges = fontRanges.Length();
+ bool missingChars = false;
+ bool isCJK = gfxTextRun::IsCJKScript(aRunScript);
+
+ for (uint32_t r = 0; r < numRanges; r++) {
+ const TextRange& range = fontRanges[r];
+ uint32_t matchedLength = range.Length();
+ RefPtr<gfxFont> matchedFont = range.font;
+ // create the glyph run for this range
+ if (matchedFont && mStyle.noFallbackVariantFeatures) {
+ // common case - just do glyph layout and record the
+ // resulting positioned glyphs
+ aTextRun->AddGlyphRun(matchedFont, range.matchType, aOffset + runStart,
+ (matchedLength > 0), range.orientation, isCJK);
+ if (!matchedFont->SplitAndInitTextRun(
+ aDrawTarget, aTextRun, aString + runStart, aOffset + runStart,
+ matchedLength, aRunScript, mLanguage, range.orientation)) {
+ // glyph layout failed! treat as missing glyphs
+ matchedFont = nullptr;
+ }
+ } else if (matchedFont) {
+ // shape with some variant feature that requires fallback handling
+ bool petiteToSmallCaps = false;
+ bool syntheticLower = false;
+ bool syntheticUpper = false;
+
+ if (mStyle.variantSubSuper != NS_FONT_VARIANT_POSITION_NORMAL &&
+ (aTextRun->GetShapingState() ==
+ gfxTextRun::eShapingState_ForceFallbackFeature ||
+ !matchedFont->SupportsSubSuperscript(mStyle.variantSubSuper, aString,
+ aLength, aRunScript))) {
+ // fallback for subscript/superscript variant glyphs
+
+ // if the feature was already used, abort and force
+ // fallback across the entire textrun
+ gfxTextRun::ShapingState ss = aTextRun->GetShapingState();
+
+ if (ss == gfxTextRun::eShapingState_Normal) {
+ aTextRun->SetShapingState(
+ gfxTextRun::eShapingState_ShapingWithFallback);
+ } else if (ss == gfxTextRun::eShapingState_ShapingWithFeature) {
+ aTextRun->SetShapingState(gfxTextRun::eShapingState_Aborted);
+ return;
+ }
+
+ RefPtr<gfxFont> subSuperFont = matchedFont->GetSubSuperscriptFont(
+ aTextRun->GetAppUnitsPerDevUnit());
+ aTextRun->AddGlyphRun(subSuperFont, range.matchType, aOffset + runStart,
+ (matchedLength > 0), range.orientation, isCJK);
+ if (!subSuperFont->SplitAndInitTextRun(
+ aDrawTarget, aTextRun, aString + runStart, aOffset + runStart,
+ matchedLength, aRunScript, mLanguage, range.orientation)) {
+ // glyph layout failed! treat as missing glyphs
+ matchedFont = nullptr;
+ }
+ } else if (mStyle.variantCaps != NS_FONT_VARIANT_CAPS_NORMAL &&
+ mStyle.allowSyntheticSmallCaps &&
+ !matchedFont->SupportsVariantCaps(
+ aRunScript, mStyle.variantCaps, petiteToSmallCaps,
+ syntheticLower, syntheticUpper)) {
+ // fallback for small-caps variant glyphs
+ if (!matchedFont->InitFakeSmallCapsRun(
+ mPresContext, aDrawTarget, aTextRun, aString + runStart,
+ aOffset + runStart, matchedLength, range.matchType,
+ range.orientation, aRunScript,
+ mExplicitLanguage ? mLanguage.get() : nullptr, syntheticLower,
+ syntheticUpper)) {
+ matchedFont = nullptr;
+ }
+ } else {
+ // shape normally with variant feature enabled
+ gfxTextRun::ShapingState ss = aTextRun->GetShapingState();
+
+ // adjust the shaping state if necessary
+ if (ss == gfxTextRun::eShapingState_Normal) {
+ aTextRun->SetShapingState(
+ gfxTextRun::eShapingState_ShapingWithFeature);
+ } else if (ss == gfxTextRun::eShapingState_ShapingWithFallback) {
+ // already have shaping results using fallback, need to redo
+ aTextRun->SetShapingState(gfxTextRun::eShapingState_Aborted);
+ return;
+ }
+
+ // do glyph layout and record the resulting positioned glyphs
+ aTextRun->AddGlyphRun(matchedFont, range.matchType, aOffset + runStart,
+ (matchedLength > 0), range.orientation, isCJK);
+ if (!matchedFont->SplitAndInitTextRun(
+ aDrawTarget, aTextRun, aString + runStart, aOffset + runStart,
+ matchedLength, aRunScript, mLanguage, range.orientation)) {
+ // glyph layout failed! treat as missing glyphs
+ matchedFont = nullptr;
+ }
+ }
+ } else {
+ aTextRun->AddGlyphRun(mainFont, FontMatchType::Kind::kFontGroup,
+ aOffset + runStart, (matchedLength > 0),
+ range.orientation, isCJK);
+ }
+
+ if (!matchedFont) {
+ // We need to set cluster boundaries (and mark spaces) so that
+ // surrogate pairs, combining characters, etc behave properly,
+ // even if we don't have glyphs for them
+ aTextRun->SetupClusterBoundaries(aOffset + runStart, aString + runStart,
+ matchedLength);
+
+ // various "missing" characters may need special handling,
+ // so we check for them here
+ uint32_t runLimit = runStart + matchedLength;
+ for (uint32_t index = runStart; index < runLimit; index++) {
+ T ch = aString[index];
+
+ // tab and newline are not to be displayed as hexboxes,
+ // but do need to be recorded in the textrun
+ if (ch == '\n') {
+ aTextRun->SetIsNewline(aOffset + index);
+ continue;
+ }
+ if (ch == '\t') {
+ aTextRun->SetIsTab(aOffset + index);
+ continue;
+ }
+
+ // for 16-bit textruns only, check for surrogate pairs and
+ // special Unicode spaces; omit these checks in 8-bit runs
+ if constexpr (sizeof(T) == sizeof(char16_t)) {
+ if (index + 1 < aLength &&
+ NS_IS_SURROGATE_PAIR(ch, aString[index + 1])) {
+ uint32_t usv = SURROGATE_TO_UCS4(ch, aString[index + 1]);
+ aTextRun->SetMissingGlyph(aOffset + index, usv, mainFont);
+ index++;
+ if (!mSkipDrawing && !IsPUA(usv)) {
+ missingChars = true;
+ }
+ continue;
+ }
+
+ // check if this is a known Unicode whitespace character that
+ // we can render using the space glyph with a custom width
+ gfxFloat wid = mainFont->SynthesizeSpaceWidth(ch);
+ if (wid >= 0.0) {
+ nscoord advance =
+ aTextRun->GetAppUnitsPerDevUnit() * floor(wid + 0.5);
+ if (gfxShapedText::CompressedGlyph::IsSimpleAdvance(advance)) {
+ aTextRun->GetCharacterGlyphs()[aOffset + index].SetSimpleGlyph(
+ advance, mainFont->GetSpaceGlyph());
+ } else {
+ gfxTextRun::DetailedGlyph detailedGlyph;
+ detailedGlyph.mGlyphID = mainFont->GetSpaceGlyph();
+ detailedGlyph.mAdvance = advance;
+ aTextRun->SetDetailedGlyphs(aOffset + index, 1, &detailedGlyph);
+ }
+ continue;
+ }
+ }
+
+ if (IsInvalidChar(ch)) {
+ // invalid chars are left as zero-width/invisible
+ continue;
+ }
+
+ // record char code so we can draw a box with the Unicode value
+ aTextRun->SetMissingGlyph(aOffset + index, ch, mainFont);
+ if (!mSkipDrawing && !IsPUA(ch)) {
+ missingChars = true;
+ }
+ }
+ }
+
+ runStart += matchedLength;
+ }
+
+ if (aMFR && missingChars) {
+ aMFR->RecordScript(aRunScript);
+ }
+}
+
+gfxTextRun* gfxFontGroup::GetEllipsisTextRun(
+ int32_t aAppUnitsPerDevPixel, gfx::ShapedTextFlags aFlags,
+ LazyReferenceDrawTargetGetter& aRefDrawTargetGetter) {
+ MOZ_ASSERT(!(aFlags & ~ShapedTextFlags::TEXT_ORIENT_MASK),
+ "flags here should only be used to specify orientation");
+ if (mCachedEllipsisTextRun &&
+ (mCachedEllipsisTextRun->GetFlags() &
+ ShapedTextFlags::TEXT_ORIENT_MASK) == aFlags &&
+ mCachedEllipsisTextRun->GetAppUnitsPerDevUnit() == aAppUnitsPerDevPixel) {
+ return mCachedEllipsisTextRun.get();
+ }
+
+ // Use a Unicode ellipsis if the font supports it,
+ // otherwise use three ASCII periods as fallback.
+ RefPtr<gfxFont> firstFont = GetFirstValidFont();
+ nsString ellipsis =
+ firstFont->HasCharacter(kEllipsisChar[0])
+ ? nsDependentString(kEllipsisChar, ArrayLength(kEllipsisChar) - 1)
+ : nsDependentString(kASCIIPeriodsChar,
+ ArrayLength(kASCIIPeriodsChar) - 1);
+
+ RefPtr<DrawTarget> refDT = aRefDrawTargetGetter.GetRefDrawTarget();
+ Parameters params = {refDT, nullptr, nullptr,
+ nullptr, 0, aAppUnitsPerDevPixel};
+ mCachedEllipsisTextRun =
+ MakeTextRun(ellipsis.BeginReading(), ellipsis.Length(), &params, aFlags,
+ nsTextFrameUtils::Flags(), nullptr);
+ if (!mCachedEllipsisTextRun) {
+ return nullptr;
+ }
+ // don't let the presence of a cached ellipsis textrun prolong the
+ // fontgroup's life
+ mCachedEllipsisTextRun->ReleaseFontGroup();
+ return mCachedEllipsisTextRun.get();
+}
+
+already_AddRefed<gfxFont> gfxFontGroup::FindFallbackFaceForChar(
+ gfxFontFamily* aFamily, uint32_t aCh, uint32_t aNextCh,
+ eFontPresentation aPresentation) {
+ GlobalFontMatch data(aCh, aNextCh, mStyle, aPresentation);
+ aFamily->SearchAllFontsForChar(&data);
+ gfxFontEntry* fe = data.mBestMatch;
+ if (!fe) {
+ return nullptr;
+ }
+ return fe->FindOrMakeFont(&mStyle);
+}
+
+already_AddRefed<gfxFont> gfxFontGroup::FindFallbackFaceForChar(
+ fontlist::Family* aFamily, uint32_t aCh, uint32_t aNextCh,
+ eFontPresentation aPresentation) {
+ auto* pfl = gfxPlatformFontList::PlatformFontList();
+ auto* list = pfl->SharedFontList();
+
+ // If async fallback is enabled, and the family isn't fully initialized yet,
+ // just start the async cmap loading and return.
+ if (!aFamily->IsFullyInitialized() &&
+ StaticPrefs::gfx_font_rendering_fallback_async() &&
+ !XRE_IsParentProcess()) {
+ pfl->StartCmapLoadingFromFamily(aFamily - list->Families());
+ return nullptr;
+ }
+
+ GlobalFontMatch data(aCh, aNextCh, mStyle, aPresentation);
+ aFamily->SearchAllFontsForChar(list, &data);
+ gfxFontEntry* fe = data.mBestMatch;
+ if (!fe) {
+ return nullptr;
+ }
+ return fe->FindOrMakeFont(&mStyle);
+}
+
+already_AddRefed<gfxFont> gfxFontGroup::FindFallbackFaceForChar(
+ const FamilyFace& aFamily, uint32_t aCh, uint32_t aNextCh,
+ eFontPresentation aPresentation) {
+ if (aFamily.IsSharedFamily()) {
+ return FindFallbackFaceForChar(aFamily.SharedFamily(), aCh, aNextCh,
+ aPresentation);
+ }
+ return FindFallbackFaceForChar(aFamily.OwnedFamily(), aCh, aNextCh,
+ aPresentation);
+}
+
+gfxFloat gfxFontGroup::GetUnderlineOffset() {
+ if (mUnderlineOffset == UNDERLINE_OFFSET_NOT_SET) {
+ // if the fontlist contains a bad underline font, make the underline
+ // offset the min of the first valid font and bad font underline offsets
+ uint32_t len = mFonts.Length();
+ for (uint32_t i = 0; i < len; i++) {
+ FamilyFace& ff = mFonts[i];
+ gfxFontEntry* fe = ff.FontEntry();
+ if (!fe) {
+ continue;
+ }
+ if (!fe->mIsUserFontContainer && !fe->IsUserFont() &&
+ ((ff.IsSharedFamily() && ff.SharedFamily() &&
+ ff.SharedFamily()->IsBadUnderlineFamily()) ||
+ (!ff.IsSharedFamily() && ff.OwnedFamily() &&
+ ff.OwnedFamily()->IsBadUnderlineFamily()))) {
+ RefPtr<gfxFont> font = GetFontAt(i);
+ if (!font) {
+ continue;
+ }
+ gfxFloat bad =
+ font->GetMetrics(nsFontMetrics::eHorizontal).underlineOffset;
+ RefPtr<gfxFont> firstValidFont = GetFirstValidFont();
+ gfxFloat first = firstValidFont->GetMetrics(nsFontMetrics::eHorizontal)
+ .underlineOffset;
+ mUnderlineOffset = std::min(first, bad);
+ return mUnderlineOffset;
+ }
+ }
+
+ // no bad underline fonts, use the first valid font's metric
+ RefPtr<gfxFont> firstValidFont = GetFirstValidFont();
+ mUnderlineOffset =
+ firstValidFont->GetMetrics(nsFontMetrics::eHorizontal).underlineOffset;
+ }
+
+ return mUnderlineOffset;
+}
+
+#define NARROW_NO_BREAK_SPACE 0x202fu
+
+already_AddRefed<gfxFont> gfxFontGroup::FindFontForChar(
+ uint32_t aCh, uint32_t aPrevCh, uint32_t aNextCh, Script aRunScript,
+ gfxFont* aPrevMatchedFont, FontMatchType* aMatchType) {
+ // If the char is a cluster extender, we want to use the same font as the
+ // preceding character if possible. This is preferable to using the font
+ // group because it avoids breaks in shaping within a cluster.
+ if (aPrevMatchedFont && IsClusterExtender(aCh)) {
+ if (aPrevMatchedFont->HasCharacter(aCh) || IsDefaultIgnorable(aCh)) {
+ return do_AddRef(aPrevMatchedFont);
+ }
+ // Check if this char and preceding char can compose; if so, is the
+ // combination supported by the current font.
+ uint32_t composed = intl::String::ComposePairNFC(aPrevCh, aCh);
+ if (composed > 0 && aPrevMatchedFont->HasCharacter(composed)) {
+ return do_AddRef(aPrevMatchedFont);
+ }
+ }
+
+ // Special cases for NNBSP (as used in Mongolian):
+ if (aCh == NARROW_NO_BREAK_SPACE) {
+ // If there is no preceding character, try the font that we'd use
+ // for the next char (unless it's just another NNBSP; we don't try
+ // to look ahead through a whole run of them).
+ if (!aPrevCh && aNextCh && aNextCh != NARROW_NO_BREAK_SPACE) {
+ RefPtr<gfxFont> nextFont = FindFontForChar(aNextCh, 0, 0, aRunScript,
+ aPrevMatchedFont, aMatchType);
+ if (nextFont && nextFont->HasCharacter(aCh)) {
+ return nextFont.forget();
+ }
+ }
+ // Otherwise, treat NNBSP like a cluster extender (as above) and try
+ // to continue the preceding font run.
+ if (aPrevMatchedFont && aPrevMatchedFont->HasCharacter(aCh)) {
+ return do_AddRef(aPrevMatchedFont);
+ }
+ }
+
+ // To optimize common cases, try the first font in the font-group
+ // before going into the more detailed checks below
+ uint32_t fontListLength = mFonts.Length();
+ uint32_t nextIndex = 0;
+ bool isJoinControl = gfxFontUtils::IsJoinControl(aCh);
+ bool wasJoinCauser = gfxFontUtils::IsJoinCauser(aPrevCh);
+ bool isVarSelector = gfxFontUtils::IsVarSelector(aCh);
+ bool nextIsVarSelector = gfxFontUtils::IsVarSelector(aNextCh);
+
+ // For Unicode hyphens, if not supported in the font then we'll try for
+ // the ASCII hyphen-minus as a fallback.
+ // Similarly, for NBSP we try normal <space> as a fallback.
+ uint32_t fallbackChar = (aCh == 0x2010 || aCh == 0x2011) ? '-'
+ : (aCh == 0x00A0) ? ' '
+ : 0;
+
+ // Whether we've seen a font that is currently loading a resource that may
+ // provide this character (so we should not start a new load).
+ bool loading = false;
+
+ // Do we need to explicitly look for a font that does or does not provide a
+ // color glyph for the given character?
+ // For characters with no `EMOJI` property, we'll use whatever the family
+ // list calls for; but if it's a potential emoji codepoint, we need to check
+ // if there's a variation selector specifically asking for Text-style or
+ // Emoji-style rendering and look for a suitable font.
+ eFontPresentation presentation = eFontPresentation::Any;
+ EmojiPresentation emojiPresentation = GetEmojiPresentation(aCh);
+ if (emojiPresentation != TextOnly) {
+ // Default presentation from the font-variant-emoji property.
+ presentation = mEmojiPresentation;
+ // If the prefer-emoji selector is present, or if it's a default-emoji
+ // char and the prefer-text selector is NOT present, or if there's a
+ // skin-tone modifier, we specifically look for a font with a color
+ // glyph.
+ // If the prefer-text selector is present, we specifically look for a
+ // font that will provide a monochrome glyph.
+ // Otherwise, we'll accept either color or monochrome font-family
+ // entries, so that a color font can be explicitly applied via font-
+ // family even to characters that are not inherently emoji-style.
+ if (aNextCh == kVariationSelector16 ||
+ (aNextCh >= kEmojiSkinToneFirst && aNextCh <= kEmojiSkinToneLast) ||
+ gfxFontUtils::IsEmojiFlagAndTag(aCh, aNextCh)) {
+ // Emoji presentation is explicitly requested by a variation selector
+ // or the presence of a skin-tone codepoint.
+ presentation = eFontPresentation::EmojiExplicit;
+ } else if (emojiPresentation == EmojiPresentation::EmojiDefault &&
+ aNextCh != kVariationSelector15) {
+ // Emoji presentation is the default for this Unicode character. but we
+ // will allow an explicitly-specified webfont to apply to it,
+ // regardless of its glyph type.
+ presentation = eFontPresentation::EmojiDefault;
+ } else if (aNextCh == kVariationSelector15) {
+ // Text presentation is explicitly requested.
+ presentation = eFontPresentation::Text;
+ }
+ }
+
+ if (!isJoinControl && !wasJoinCauser && !isVarSelector &&
+ !nextIsVarSelector && presentation == eFontPresentation::Any) {
+ RefPtr<gfxFont> firstFont = GetFontAt(0, aCh, &loading);
+ if (firstFont) {
+ if (firstFont->HasCharacter(aCh) ||
+ (fallbackChar && firstFont->HasCharacter(fallbackChar))) {
+ *aMatchType = {FontMatchType::Kind::kFontGroup, mFonts[0].Generic()};
+ return firstFont.forget();
+ }
+
+ RefPtr<gfxFont> font;
+ if (mFonts[0].CheckForFallbackFaces()) {
+ font = FindFallbackFaceForChar(mFonts[0], aCh, aNextCh, presentation);
+ } else if (!firstFont->GetFontEntry()->IsUserFont()) {
+ // For platform fonts (but not userfonts), we may need to do
+ // fallback within the family to handle cases where some faces
+ // such as Italic or Black have reduced character sets compared
+ // to the family's Regular face.
+ font = FindFallbackFaceForChar(mFonts[0], aCh, aNextCh, presentation);
+ }
+ if (font) {
+ *aMatchType = {FontMatchType::Kind::kFontGroup, mFonts[0].Generic()};
+ return font.forget();
+ }
+ } else {
+ if (fontListLength > 0) {
+ loading = loading || mFonts[0].IsLoadingFor(aCh);
+ }
+ }
+
+ // we don't need to check the first font again below
+ ++nextIndex;
+ }
+
+ if (aPrevMatchedFont) {
+ // Don't switch fonts for control characters, regardless of
+ // whether they are present in the current font, as they won't
+ // actually be rendered (see bug 716229)
+ if (isJoinControl ||
+ GetGeneralCategory(aCh) == HB_UNICODE_GENERAL_CATEGORY_CONTROL) {
+ return do_AddRef(aPrevMatchedFont);
+ }
+
+ // if previous character was a join-causer (ZWJ),
+ // use the same font as the previous range if we can
+ if (wasJoinCauser) {
+ if (aPrevMatchedFont->HasCharacter(aCh)) {
+ return do_AddRef(aPrevMatchedFont);
+ }
+ }
+ }
+
+ // If this character is a variation selector or default-ignorable, use the
+ // previous font regardless of whether it supports the codepoint or not.
+ // (We don't want to unnecessarily split glyph runs, and the character will
+ // not be visibly rendered.)
+ if (isVarSelector || IsDefaultIgnorable(aCh)) {
+ return do_AddRef(aPrevMatchedFont);
+ }
+
+ // Used to remember the first "candidate" font that would provide a fallback
+ // text-style rendering if no color glyph can be found.
+ // If we decide NOT to return this font, we must AddRef/Release it to ensure
+ // that it goes into the global font cache as a candidate for deletion.
+ // This is done for us by CheckCandidate, but any code path that returns
+ // WITHOUT calling CheckCandidate needs to handle it explicitly.
+ RefPtr<gfxFont> candidateFont;
+ FontMatchType candidateMatchType;
+
+ // Handle a candidate font that could support the character, returning true
+ // if we should go ahead and return |f|, false to continue searching.
+ // If there is already a saved candidate font, and the new candidate is
+ // accepted, we AddRef/Release the existing candidate so it won't leak.
+ auto CheckCandidate = [&](gfxFont* f, FontMatchType t) -> bool {
+ // If no preference, then just accept the font.
+ if (presentation == eFontPresentation::Any ||
+ (presentation == eFontPresentation::EmojiDefault &&
+ f->GetFontEntry()->IsUserFont())) {
+ *aMatchType = t;
+ return true;
+ }
+ // Does the candidate font provide a color glyph for the current character?
+ bool hasColorGlyph = f->HasColorGlyphFor(aCh, aNextCh);
+ // If the provided glyph matches the preference, accept the font.
+ if (hasColorGlyph == PrefersColor(presentation)) {
+ *aMatchType = t;
+ return true;
+ }
+ // If the character was a TextDefault char, but the next char is VS16,
+ // and the font is a COLR font that supports both these codepoints, then
+ // we'll assume it knows what it is doing (eg Twemoji Mozilla keycap
+ // sequences).
+ // TODO: reconsider all this as part of any fix for bug 543200.
+ if (aNextCh == kVariationSelector16 && emojiPresentation == TextDefault &&
+ f->HasCharacter(aNextCh) && f->GetFontEntry()->TryGetColorGlyphs()) {
+ return true;
+ }
+ // Otherwise, remember the first potential fallback, but keep searching.
+ if (!candidateFont) {
+ candidateFont = f;
+ candidateMatchType = t;
+ }
+ return false;
+ };
+
+ // 1. check remaining fonts in the font group
+ for (uint32_t i = nextIndex; i < fontListLength; i++) {
+ FamilyFace& ff = mFonts[i];
+ if (ff.IsInvalid() || ff.IsLoading()) {
+ if (ff.IsLoadingFor(aCh)) {
+ loading = true;
+ }
+ continue;
+ }
+
+ RefPtr<gfxFont> font = ff.Font();
+ if (font) {
+ // if available, use already-made gfxFont and check for character
+ if (font->HasCharacter(aCh) ||
+ (fallbackChar && font->HasCharacter(fallbackChar))) {
+ if (CheckCandidate(font,
+ {FontMatchType::Kind::kFontGroup, ff.Generic()})) {
+ return font.forget();
+ }
+ }
+ } else {
+ // don't have a gfxFont yet, test charmap before instantiating
+ gfxFontEntry* fe = ff.FontEntry();
+ if (fe && fe->mIsUserFontContainer) {
+ // for userfonts, need to test both the unicode range map and
+ // the cmap of the platform font entry
+ gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe);
+
+ // never match a character outside the defined unicode range
+ if (!ufe->CharacterInUnicodeRange(aCh)) {
+ continue;
+ }
+
+ // Load if not already loaded, unless we've already seen an in-
+ // progress load that is expected to satisfy this request.
+ if (!loading &&
+ ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED) {
+ ufe->Load();
+ ff.CheckState(mSkipDrawing);
+ }
+
+ if (ff.IsLoading()) {
+ loading = true;
+ }
+
+ gfxFontEntry* pfe = ufe->GetPlatformFontEntry();
+ if (pfe && (pfe->HasCharacter(aCh) ||
+ (fallbackChar && pfe->HasCharacter(fallbackChar)))) {
+ font = GetFontAt(i, aCh, &loading);
+ if (font) {
+ if (CheckCandidate(font, {FontMatchType::Kind::kFontGroup,
+ mFonts[i].Generic()})) {
+ return font.forget();
+ }
+ }
+ }
+ } else if (fe && (fe->HasCharacter(aCh) ||
+ (fallbackChar && fe->HasCharacter(fallbackChar)))) {
+ // for normal platform fonts, after checking the cmap
+ // build the font via GetFontAt
+ font = GetFontAt(i, aCh, &loading);
+ if (font) {
+ if (CheckCandidate(font, {FontMatchType::Kind::kFontGroup,
+ mFonts[i].Generic()})) {
+ return font.forget();
+ }
+ }
+ }
+ }
+
+ // check other family faces if needed
+ if (ff.CheckForFallbackFaces()) {
+#ifdef DEBUG
+ if (i > 0) {
+ fontlist::FontList* list =
+ gfxPlatformFontList::PlatformFontList()->SharedFontList();
+ nsCString s1 = mFonts[i - 1].IsSharedFamily()
+ ? mFonts[i - 1].SharedFamily()->Key().AsString(list)
+ : mFonts[i - 1].OwnedFamily()->Name();
+ nsCString s2 = ff.IsSharedFamily()
+ ? ff.SharedFamily()->Key().AsString(list)
+ : ff.OwnedFamily()->Name();
+ MOZ_ASSERT(!mFonts[i - 1].CheckForFallbackFaces() || !s1.Equals(s2),
+ "should only do fallback once per font family");
+ }
+#endif
+ font = FindFallbackFaceForChar(ff, aCh, aNextCh, presentation);
+ if (font) {
+ if (CheckCandidate(font,
+ {FontMatchType::Kind::kFontGroup, ff.Generic()})) {
+ return font.forget();
+ }
+ }
+ } else {
+ // For platform fonts, but not user fonts, consider intra-family
+ // fallback to handle styles with reduced character sets (see
+ // also above).
+ gfxFontEntry* fe = ff.FontEntry();
+ if (fe && !fe->mIsUserFontContainer && !fe->IsUserFont()) {
+ font = FindFallbackFaceForChar(ff, aCh, aNextCh, presentation);
+ if (font) {
+ if (CheckCandidate(font,
+ {FontMatchType::Kind::kFontGroup, ff.Generic()})) {
+ return font.forget();
+ }
+ }
+ }
+ }
+ }
+
+ if (fontListLength == 0) {
+ RefPtr<gfxFont> defaultFont = GetDefaultFont();
+ if (defaultFont->HasCharacter(aCh) ||
+ (fallbackChar && defaultFont->HasCharacter(fallbackChar))) {
+ if (CheckCandidate(defaultFont, FontMatchType::Kind::kFontGroup)) {
+ return defaultFont.forget();
+ }
+ }
+ }
+
+ // If character is in Private Use Area, or is unassigned in Unicode, don't do
+ // matching against pref or system fonts. We only support such codepoints
+ // when used with an explicitly-specified font, as they have no standard/
+ // interoperable meaning.
+ // Also don't attempt any fallback for control characters or noncharacters,
+ // where we won't be rendering a glyph anyhow, or for codepoints where global
+ // fallback has already noted a failure.
+ FontVisibility level =
+ mPresContext ? mPresContext->GetFontVisibility() : FontVisibility::User;
+ auto* pfl = gfxPlatformFontList::PlatformFontList();
+ if (pfl->SkipFontFallbackForChar(level, aCh) ||
+ (!StaticPrefs::gfx_font_rendering_fallback_unassigned_chars() &&
+ GetGeneralCategory(aCh) == HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED)) {
+ if (candidateFont) {
+ *aMatchType = candidateMatchType;
+ }
+ return candidateFont.forget();
+ }
+
+ // 2. search pref fonts
+ RefPtr<gfxFont> font = WhichPrefFontSupportsChar(aCh, aNextCh, presentation);
+ if (font) {
+ if (PrefersColor(presentation) && pfl->EmojiPrefHasUserValue()) {
+ // For emoji, always accept the font from preferences if it's explicitly
+ // user-set, even if it isn't actually a color-emoji font, as some users
+ // may want to set their emoji font preference to a monochrome font like
+ // Symbola.
+ // So a user-provided font.name-list.emoji preference takes precedence
+ // over the Unicode presentation style here.
+ RefPtr<gfxFont> autoRefDeref(candidateFont);
+ *aMatchType = FontMatchType::Kind::kPrefsFallback;
+ return font.forget();
+ }
+ if (CheckCandidate(font, FontMatchType::Kind::kPrefsFallback)) {
+ return font.forget();
+ }
+ }
+
+ // For fallback searches, we don't want to use a color-emoji font unless
+ // emoji-style presentation is specifically required, so we map Any to
+ // Text here.
+ if (presentation == eFontPresentation::Any) {
+ presentation = eFontPresentation::Text;
+ }
+
+ // 3. use fallback fonts
+ // -- before searching for something else check the font used for the
+ // previous character
+ if (aPrevMatchedFont &&
+ (aPrevMatchedFont->HasCharacter(aCh) ||
+ (fallbackChar && aPrevMatchedFont->HasCharacter(fallbackChar)))) {
+ if (CheckCandidate(aPrevMatchedFont,
+ FontMatchType::Kind::kSystemFallback)) {
+ return do_AddRef(aPrevMatchedFont);
+ }
+ }
+
+ // for known "space" characters, don't do a full system-fallback search;
+ // we'll synthesize appropriate-width spaces instead of missing-glyph boxes
+ font = GetFirstValidFont();
+ if (GetGeneralCategory(aCh) == HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR &&
+ font->SynthesizeSpaceWidth(aCh) >= 0.0) {
+ return nullptr;
+ }
+
+ // -- otherwise look for other stuff
+ font = WhichSystemFontSupportsChar(aCh, aNextCh, aRunScript, presentation);
+ if (font) {
+ if (CheckCandidate(font, FontMatchType::Kind::kSystemFallback)) {
+ return font.forget();
+ }
+ }
+ if (candidateFont) {
+ *aMatchType = candidateMatchType;
+ }
+ return candidateFont.forget();
+}
+
+template <typename T>
+void gfxFontGroup::ComputeRanges(nsTArray<TextRange>& aRanges, const T* aString,
+ uint32_t aLength, Script aRunScript,
+ gfx::ShapedTextFlags aOrientation) {
+ NS_ASSERTION(aRanges.Length() == 0, "aRanges must be initially empty");
+ NS_ASSERTION(aLength > 0, "don't call ComputeRanges for zero-length text");
+
+ uint32_t prevCh = 0;
+ uint32_t nextCh = aString[0];
+ if constexpr (sizeof(T) == sizeof(char16_t)) {
+ if (aLength > 1 && NS_IS_SURROGATE_PAIR(nextCh, aString[1])) {
+ nextCh = SURROGATE_TO_UCS4(nextCh, aString[1]);
+ }
+ }
+ int32_t lastRangeIndex = -1;
+
+ // initialize prevFont to the group's primary font, so that this will be
+ // used for string-initial control chars, etc rather than risk hitting font
+ // fallback for these (bug 716229)
+ StyleGenericFontFamily generic = StyleGenericFontFamily::None;
+ RefPtr<gfxFont> prevFont = GetFirstValidFont(' ', &generic);
+
+ // if we use the initial value of prevFont, we treat this as a match from
+ // the font group; fixes bug 978313
+ FontMatchType matchType = {FontMatchType::Kind::kFontGroup, generic};
+
+ for (uint32_t i = 0; i < aLength; i++) {
+ const uint32_t origI = i; // save off in case we increase for surrogate
+
+ // set up current ch
+ uint32_t ch = nextCh;
+
+ // Get next char (if any) so that FindFontForChar can look ahead
+ // for a possible variation selector.
+
+ if constexpr (sizeof(T) == sizeof(char16_t)) {
+ // In 16-bit case only, check for surrogate pairs.
+ if (ch > 0xffffu) {
+ i++;
+ }
+ if (i < aLength - 1) {
+ nextCh = aString[i + 1];
+ if (i + 2 < aLength && NS_IS_SURROGATE_PAIR(nextCh, aString[i + 2])) {
+ nextCh = SURROGATE_TO_UCS4(nextCh, aString[i + 2]);
+ }
+ } else {
+ nextCh = 0;
+ }
+ } else {
+ // 8-bit case is trivial.
+ nextCh = i < aLength - 1 ? aString[i + 1] : 0;
+ }
+
+ RefPtr<gfxFont> font;
+
+ // Find the font for this char; but try to avoid calling the expensive
+ // FindFontForChar method for the most common case, where the first
+ // font in the list supports the current char, and it is not one of
+ // the special cases where FindFontForChar will attempt to propagate
+ // the font selected for an adjacent character.
+ if ((font = GetFontAt(0, ch)) != nullptr && font->HasCharacter(ch) &&
+ (sizeof(T) == sizeof(uint8_t) ||
+ (!IsClusterExtender(ch) && ch != NARROW_NO_BREAK_SPACE &&
+ !gfxFontUtils::IsJoinControl(ch) &&
+ !gfxFontUtils::IsJoinCauser(prevCh) &&
+ !gfxFontUtils::IsVarSelector(ch) &&
+ GetEmojiPresentation(ch) == TextOnly))) {
+ matchType = {FontMatchType::Kind::kFontGroup, mFonts[0].Generic()};
+ } else {
+ font =
+ FindFontForChar(ch, prevCh, nextCh, aRunScript, prevFont, &matchType);
+ }
+
+#ifndef RELEASE_OR_BETA
+ if (MOZ_UNLIKELY(mTextPerf)) {
+ if (matchType.kind == FontMatchType::Kind::kPrefsFallback) {
+ mTextPerf->current.fallbackPrefs++;
+ } else if (matchType.kind == FontMatchType::Kind::kSystemFallback) {
+ mTextPerf->current.fallbackSystem++;
+ }
+ }
+#endif
+
+ prevCh = ch;
+
+ ShapedTextFlags orient = aOrientation;
+ if (aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) {
+ // For CSS text-orientation:mixed, we need to resolve orientation
+ // on a per-character basis using the UTR50 orientation property.
+ switch (GetVerticalOrientation(ch)) {
+ case VERTICAL_ORIENTATION_U:
+ case VERTICAL_ORIENTATION_Tu:
+ orient = ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
+ break;
+ case VERTICAL_ORIENTATION_Tr: {
+ // We check for a vertical presentation form first as that's
+ // likely to be cheaper than inspecting lookups to see if the
+ // 'vert' feature is going to handle this character, and if the
+ // presentation form is available then it will be used as
+ // fallback if needed, so it's OK if the feature is missing.
+ //
+ // Because "common" CJK punctuation characters in isolation will be
+ // resolved to Bopomofo script (as the first script listed in their
+ // ScriptExtensions property), but this is not always well supported
+ // by fonts' OpenType tables, we also try Han script; harfbuzz will
+ // apply a 'vert' feature from any available script (see
+ // https://github.com/harfbuzz/harfbuzz/issues/63) when shaping,
+ // so this is OK. It's not quite as general as what harfbuzz does
+ // (it will find the feature in *any* script), but should be enough
+ // for likely real-world examples.
+ uint32_t v = gfxHarfBuzzShaper::GetVerticalPresentationForm(ch);
+ const uint32_t kVert = HB_TAG('v', 'e', 'r', 't');
+ orient = (!font || (v && font->HasCharacter(v)) ||
+ font->FeatureWillHandleChar(aRunScript, kVert, ch) ||
+ (aRunScript == Script::BOPOMOFO &&
+ font->FeatureWillHandleChar(Script::HAN, kVert, ch)))
+ ? ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
+ : ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
+ break;
+ }
+ case VERTICAL_ORIENTATION_R:
+ orient = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
+ break;
+ }
+ }
+
+ if (lastRangeIndex == -1) {
+ // first char ==> make a new range
+ aRanges.AppendElement(TextRange(0, 1, font, matchType, orient));
+ lastRangeIndex++;
+ prevFont = std::move(font);
+ } else {
+ // if font or orientation has changed, make a new range...
+ // unless ch is a variation selector (bug 1248248)
+ TextRange& prevRange = aRanges[lastRangeIndex];
+ if (prevRange.font != font ||
+ (prevRange.orientation != orient && !IsClusterExtender(ch))) {
+ // close out the previous range
+ prevRange.end = origI;
+ aRanges.AppendElement(TextRange(origI, i + 1, font, matchType, orient));
+ lastRangeIndex++;
+
+ // update prevFont for the next match, *unless* we switched
+ // fonts on a ZWJ, in which case propagating the changed font
+ // is probably not a good idea (see bug 619511)
+ if (sizeof(T) == sizeof(uint8_t) || !gfxFontUtils::IsJoinCauser(ch)) {
+ prevFont = std::move(font);
+ }
+ } else {
+ prevRange.matchType |= matchType;
+ }
+ }
+ }
+
+ aRanges[lastRangeIndex].end = aLength;
+
+#ifndef RELEASE_OR_BETA
+ LogModule* log = mStyle.systemFont ? gfxPlatform::GetLog(eGfxLog_textrunui)
+ : gfxPlatform::GetLog(eGfxLog_textrun);
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Debug))) {
+ nsAutoCString lang;
+ mLanguage->ToUTF8String(lang);
+ auto defaultLanguageGeneric = GetDefaultGeneric(mLanguage);
+
+ // collect the font matched for each range
+ nsAutoCString fontMatches;
+ for (size_t i = 0, i_end = aRanges.Length(); i < i_end; i++) {
+ const TextRange& r = aRanges[i];
+ nsAutoCString matchTypes;
+ if (r.matchType.kind & FontMatchType::Kind::kFontGroup) {
+ matchTypes.AppendLiteral("list");
+ }
+ if (r.matchType.kind & FontMatchType::Kind::kPrefsFallback) {
+ if (!matchTypes.IsEmpty()) {
+ matchTypes.AppendLiteral(",");
+ }
+ matchTypes.AppendLiteral("prefs");
+ }
+ if (r.matchType.kind & FontMatchType::Kind::kSystemFallback) {
+ if (!matchTypes.IsEmpty()) {
+ matchTypes.AppendLiteral(",");
+ }
+ matchTypes.AppendLiteral("sys");
+ }
+ fontMatches.AppendPrintf(
+ " [%u:%u] %.200s (%s)", r.start, r.end,
+ (r.font.get() ? r.font->GetName().get() : "<null>"),
+ matchTypes.get());
+ }
+ MOZ_LOG(log, LogLevel::Debug,
+ ("(%s-fontmatching) fontgroup: [%s] default: %s lang: %s script: %d"
+ "%s\n",
+ (mStyle.systemFont ? "textrunui" : "textrun"),
+ FamilyListToString(mFamilyList).get(),
+ (defaultLanguageGeneric == StyleGenericFontFamily::Serif
+ ? "serif"
+ : (defaultLanguageGeneric == StyleGenericFontFamily::SansSerif
+ ? "sans-serif"
+ : "none")),
+ lang.get(), static_cast<int>(aRunScript), fontMatches.get()));
+ }
+#endif
+}
+
+gfxUserFontSet* gfxFontGroup::GetUserFontSet() { return mUserFontSet; }
+
+void gfxFontGroup::SetUserFontSet(gfxUserFontSet* aUserFontSet) {
+ if (aUserFontSet == mUserFontSet) {
+ return;
+ }
+ mUserFontSet = aUserFontSet;
+ mCurrGeneration = GetGeneration() - 1;
+ UpdateUserFonts();
+}
+
+uint64_t gfxFontGroup::GetGeneration() {
+ if (!mUserFontSet) return 0;
+ return mUserFontSet->GetGeneration();
+}
+
+uint64_t gfxFontGroup::GetRebuildGeneration() {
+ if (!mUserFontSet) return 0;
+ return mUserFontSet->GetRebuildGeneration();
+}
+
+void gfxFontGroup::UpdateUserFonts() {
+ if (mCurrGeneration < GetRebuildGeneration()) {
+ // fonts in userfont set changed, need to redo the fontlist
+ mFonts.Clear();
+ ClearCachedData();
+ BuildFontList();
+ mCurrGeneration = GetGeneration();
+ } else if (mCurrGeneration != GetGeneration()) {
+ // load state change occurred, verify load state and validity of fonts
+ ClearCachedData();
+
+ uint32_t len = mFonts.Length();
+ for (uint32_t i = 0; i < len; i++) {
+ FamilyFace& ff = mFonts[i];
+ if (ff.Font() || !ff.IsUserFontContainer()) {
+ continue;
+ }
+ ff.CheckState(mSkipDrawing);
+ }
+
+ mCurrGeneration = GetGeneration();
+ }
+}
+
+bool gfxFontGroup::ContainsUserFont(const gfxUserFontEntry* aUserFont) {
+ UpdateUserFonts();
+ // search through the fonts list for a specific user font
+ uint32_t len = mFonts.Length();
+ for (uint32_t i = 0; i < len; i++) {
+ FamilyFace& ff = mFonts[i];
+ if (ff.EqualsUserFont(aUserFont)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+already_AddRefed<gfxFont> gfxFontGroup::WhichPrefFontSupportsChar(
+ uint32_t aCh, uint32_t aNextCh, eFontPresentation aPresentation) {
+ eFontPrefLang charLang;
+ gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
+
+ if (PrefersColor(aPresentation)) {
+ charLang = eFontPrefLang_Emoji;
+ } else {
+ // get the pref font list if it hasn't been set up already
+ charLang = pfl->GetFontPrefLangFor(aCh);
+ }
+
+ // if the last pref font was the first family in the pref list, no need to
+ // recheck through a list of families
+ if (mLastPrefFont && charLang == mLastPrefLang && mLastPrefFirstFont &&
+ mLastPrefFont->HasCharacter(aCh)) {
+ return do_AddRef(mLastPrefFont);
+ }
+
+ // based on char lang and page lang, set up list of pref lang fonts to check
+ eFontPrefLang prefLangs[kMaxLenPrefLangList];
+ uint32_t i, numLangs = 0;
+
+ pfl->GetLangPrefs(prefLangs, numLangs, charLang, mPageLang);
+
+ for (i = 0; i < numLangs; i++) {
+ eFontPrefLang currentLang = prefLangs[i];
+ StyleGenericFontFamily generic =
+ mFallbackGeneric != StyleGenericFontFamily::None
+ ? mFallbackGeneric
+ : pfl->GetDefaultGeneric(currentLang);
+ gfxPlatformFontList::PrefFontList* families =
+ pfl->GetPrefFontsLangGroup(mPresContext, generic, currentLang);
+ NS_ASSERTION(families, "no pref font families found");
+
+ // find the first pref font that includes the character
+ uint32_t j, numPrefs;
+ numPrefs = families->Length();
+ for (j = 0; j < numPrefs; j++) {
+ // look up the appropriate face
+ FontFamily family = (*families)[j];
+ if (family.IsNull()) {
+ continue;
+ }
+
+ // if a pref font is used, it's likely to be used again in the same text
+ // run. the style doesn't change so the face lookup can be cached rather
+ // than calling FindOrMakeFont repeatedly. speeds up FindFontForChar
+ // lookup times for subsequent pref font lookups
+ if (family == mLastPrefFamily && mLastPrefFont->HasCharacter(aCh)) {
+ return do_AddRef(mLastPrefFont);
+ }
+
+ gfxFontEntry* fe = nullptr;
+ if (family.mShared) {
+ fontlist::Family* fam = family.mShared;
+ if (!fam->IsInitialized()) {
+ Unused << pfl->InitializeFamily(fam);
+ }
+ fontlist::Face* face =
+ fam->FindFaceForStyle(pfl->SharedFontList(), mStyle);
+ if (face) {
+ fe = pfl->GetOrCreateFontEntry(face, fam);
+ }
+ } else {
+ fe = family.mUnshared->FindFontForStyle(mStyle);
+ }
+ if (!fe) {
+ continue;
+ }
+
+ // if ch in cmap, create and return a gfxFont
+ RefPtr<gfxFont> prefFont;
+ if (fe->HasCharacter(aCh)) {
+ prefFont = fe->FindOrMakeFont(&mStyle);
+ if (!prefFont) {
+ continue;
+ }
+ if (aPresentation == eFontPresentation::EmojiExplicit &&
+ !prefFont->HasColorGlyphFor(aCh, aNextCh)) {
+ continue;
+ }
+ }
+
+ // If the char was not available, see if we can fall back to an
+ // alternative face in the same family.
+ if (!prefFont) {
+ prefFont = family.mShared
+ ? FindFallbackFaceForChar(family.mShared, aCh, aNextCh,
+ aPresentation)
+ : FindFallbackFaceForChar(family.mUnshared, aCh, aNextCh,
+ aPresentation);
+ }
+ if (prefFont) {
+ mLastPrefFamily = family;
+ mLastPrefFont = prefFont;
+ mLastPrefLang = charLang;
+ mLastPrefFirstFont = (i == 0 && j == 0);
+ return prefFont.forget();
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<gfxFont> gfxFontGroup::WhichSystemFontSupportsChar(
+ uint32_t aCh, uint32_t aNextCh, Script aRunScript,
+ eFontPresentation aPresentation) {
+ FontVisibility visibility;
+ return gfxPlatformFontList::PlatformFontList()->SystemFindFontForChar(
+ mPresContext, aCh, aNextCh, aRunScript, aPresentation, &mStyle,
+ &visibility);
+}
+
+gfxFont::Metrics gfxFontGroup::GetMetricsForCSSUnits(
+ gfxFont::Orientation aOrientation) {
+ bool isFirst;
+ RefPtr<gfxFont> font = GetFirstValidFont(0x20, nullptr, &isFirst);
+ auto metrics = font->GetMetrics(aOrientation);
+
+ // If the font we used to get metrics was not the first in the list,
+ // or if it doesn't support the ZERO character, check for the font that
+ // does support ZERO and use its metrics for the 'ch' unit.
+ if (!isFirst || !font->HasCharacter('0')) {
+ RefPtr<gfxFont> zeroFont = GetFirstValidFont('0');
+ if (zeroFont != font) {
+ const auto& zeroMetrics = zeroFont->GetMetrics(aOrientation);
+ metrics.zeroWidth = zeroMetrics.zeroWidth;
+ }
+ }
+
+ // Likewise for the WATER ideograph character used as the basis for 'ic'.
+ if (!isFirst || !font->HasCharacter(0x6C34)) {
+ RefPtr<gfxFont> icFont = GetFirstValidFont(0x6C34);
+ if (icFont != font) {
+ const auto& icMetrics = icFont->GetMetrics(aOrientation);
+ metrics.ideographicWidth = icMetrics.ideographicWidth;
+ }
+ }
+
+ return metrics;
+}
+
+void gfxMissingFontRecorder::Flush() {
+ static bool mNotifiedFontsInitialized = false;
+ static uint32_t mNotifiedFonts[gfxMissingFontRecorder::kNumScriptBitsWords];
+ if (!mNotifiedFontsInitialized) {
+ memset(&mNotifiedFonts, 0, sizeof(mNotifiedFonts));
+ mNotifiedFontsInitialized = true;
+ }
+
+ nsAutoString fontNeeded;
+ for (uint32_t i = 0; i < kNumScriptBitsWords; ++i) {
+ mMissingFonts[i] &= ~mNotifiedFonts[i];
+ if (!mMissingFonts[i]) {
+ continue;
+ }
+ for (uint32_t j = 0; j < 32; ++j) {
+ if (!(mMissingFonts[i] & (1 << j))) {
+ continue;
+ }
+ mNotifiedFonts[i] |= (1 << j);
+ if (!fontNeeded.IsEmpty()) {
+ fontNeeded.Append(char16_t(','));
+ }
+ uint32_t sc = i * 32 + j;
+ MOZ_ASSERT(sc < static_cast<uint32_t>(Script::NUM_SCRIPT_CODES),
+ "how did we set the bit for an invalid script code?");
+ uint32_t tag = GetScriptTagForCode(static_cast<Script>(sc));
+ fontNeeded.Append(char16_t(tag >> 24));
+ fontNeeded.Append(char16_t((tag >> 16) & 0xff));
+ fontNeeded.Append(char16_t((tag >> 8) & 0xff));
+ fontNeeded.Append(char16_t(tag & 0xff));
+ }
+ mMissingFonts[i] = 0;
+ }
+ if (!fontNeeded.IsEmpty()) {
+ nsCOMPtr<nsIObserverService> service = GetObserverService();
+ service->NotifyObservers(nullptr, "font-needed", fontNeeded.get());
+ }
+}
diff --git a/gfx/thebes/gfxTextRun.h b/gfx/thebes/gfxTextRun.h
new file mode 100644
index 0000000000..ce58b8c123
--- /dev/null
+++ b/gfx/thebes/gfxTextRun.h
@@ -0,0 +1,1535 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=4 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_TEXTRUN_H
+#define GFX_TEXTRUN_H
+
+#include <stdint.h>
+
+#include "gfxTypes.h"
+#include "gfxPoint.h"
+#include "gfxFont.h"
+#include "gfxFontConstants.h"
+#include "gfxSkipChars.h"
+#include "gfxPlatform.h"
+#include "gfxPlatformFontList.h"
+#include "gfxUserFontSet.h"
+#include "gfxUtils.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/intl/UnicodeScriptCodes.h"
+#include "nsPoint.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsTHashSet.h"
+#include "nsTextFrameUtils.h"
+#include "DrawMode.h"
+#include "harfbuzz/hb.h"
+#include "nsColor.h"
+#include "nsFrameList.h"
+#include "X11UndefineNone.h"
+
+#ifdef DEBUG_FRAME_DUMP
+# include <stdio.h>
+#endif
+
+class gfxContext;
+class gfxFontGroup;
+class nsAtom;
+class nsLanguageAtomService;
+class gfxMissingFontRecorder;
+
+namespace mozilla {
+class PostTraversalTask;
+class SVGContextPaint;
+enum class StyleHyphens : uint8_t;
+}; // namespace mozilla
+
+/**
+ * Callback for Draw() to use when drawing text with mode
+ * DrawMode::GLYPH_PATH.
+ */
+struct MOZ_STACK_CLASS gfxTextRunDrawCallbacks {
+ /**
+ * Constructs a new DrawCallbacks object.
+ *
+ * @param aShouldPaintSVGGlyphs If true, SVG glyphs will be painted. If
+ * false, SVG glyphs will not be painted; fallback plain glyphs are not
+ * emitted either.
+ */
+ explicit gfxTextRunDrawCallbacks(bool aShouldPaintSVGGlyphs = false)
+ : mShouldPaintSVGGlyphs(aShouldPaintSVGGlyphs) {}
+
+ /**
+ * Called when a path has been emitted to the gfxContext when
+ * painting a text run. This can be called any number of times,
+ * due to partial ligatures and intervening SVG glyphs.
+ */
+ virtual void NotifyGlyphPathEmitted() = 0;
+
+ bool mShouldPaintSVGGlyphs;
+};
+
+/**
+ * gfxTextRun is an abstraction for drawing and measuring substrings of a run
+ * of text. It stores runs of positioned glyph data, each run having a single
+ * gfxFont. The glyphs are associated with a string of source text, and the
+ * gfxTextRun APIs take parameters that are offsets into that source text.
+ *
+ * gfxTextRuns are mostly immutable. The only things that can change are
+ * inter-cluster spacing and line break placement. Spacing is always obtained
+ * lazily by methods that need it, it is not cached. Line breaks are stored
+ * persistently (insofar as they affect the shaping of glyphs; gfxTextRun does
+ * not actually do anything to explicitly account for line breaks). Initially
+ * there are no line breaks. The textrun can record line breaks before or after
+ * any given cluster. (Line breaks specified inside clusters are ignored.)
+ *
+ * It is important that zero-length substrings are handled correctly. This will
+ * be on the test!
+ */
+class gfxTextRun : public gfxShapedText {
+ NS_INLINE_DECL_REFCOUNTING(gfxTextRun);
+
+ protected:
+ // Override operator delete to properly free the object that was
+ // allocated via malloc.
+ void operator delete(void* p) { free(p); }
+
+ virtual ~gfxTextRun();
+
+ public:
+ typedef gfxFont::RunMetrics Metrics;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ // Public textrun API for general use
+
+ bool IsClusterStart(uint32_t aPos) const {
+ MOZ_ASSERT(aPos < GetLength());
+ return mCharacterGlyphs[aPos].IsClusterStart();
+ }
+ bool IsLigatureGroupStart(uint32_t aPos) const {
+ MOZ_ASSERT(aPos < GetLength());
+ return mCharacterGlyphs[aPos].IsLigatureGroupStart();
+ }
+ bool CanBreakLineBefore(uint32_t aPos) const {
+ return CanBreakBefore(aPos) == CompressedGlyph::FLAG_BREAK_TYPE_NORMAL;
+ }
+ bool CanHyphenateBefore(uint32_t aPos) const {
+ return CanBreakBefore(aPos) == CompressedGlyph::FLAG_BREAK_TYPE_HYPHEN;
+ }
+
+ // Returns a gfxShapedText::CompressedGlyph::FLAG_BREAK_TYPE_* value
+ // as defined in gfxFont.h (may be NONE, NORMAL or HYPHEN).
+ uint8_t CanBreakBefore(uint32_t aPos) const {
+ MOZ_ASSERT(aPos < GetLength());
+ return mCharacterGlyphs[aPos].CanBreakBefore();
+ }
+
+ bool CharIsSpace(uint32_t aPos) const {
+ MOZ_ASSERT(aPos < GetLength());
+ return mCharacterGlyphs[aPos].CharIsSpace();
+ }
+ bool CharIsTab(uint32_t aPos) const {
+ MOZ_ASSERT(aPos < GetLength());
+ return mCharacterGlyphs[aPos].CharIsTab();
+ }
+ bool CharIsNewline(uint32_t aPos) const {
+ MOZ_ASSERT(aPos < GetLength());
+ return mCharacterGlyphs[aPos].CharIsNewline();
+ }
+ bool CharMayHaveEmphasisMark(uint32_t aPos) const {
+ MOZ_ASSERT(aPos < GetLength());
+ return mCharacterGlyphs[aPos].CharMayHaveEmphasisMark();
+ }
+ bool CharIsFormattingControl(uint32_t aPos) const {
+ MOZ_ASSERT(aPos < GetLength());
+ return mCharacterGlyphs[aPos].CharIsFormattingControl();
+ }
+
+ // All offsets are in terms of the string passed into MakeTextRun.
+
+ // Describe range [start, end) of a text run. The range is
+ // restricted to grapheme cluster boundaries.
+ struct Range {
+ uint32_t start;
+ uint32_t end;
+ uint32_t Length() const { return end - start; }
+
+ Range() : start(0), end(0) {}
+ Range(uint32_t aStart, uint32_t aEnd) : start(aStart), end(aEnd) {}
+ explicit Range(const gfxTextRun* aTextRun)
+ : start(0), end(aTextRun->GetLength()) {}
+ };
+
+ // All coordinates are in layout/app units
+
+ /**
+ * Set the potential linebreaks for a substring of the textrun. These are
+ * the "allow break before" points. Initially, there are no potential
+ * linebreaks.
+ *
+ * This can change glyphs and/or geometry! Some textruns' shapes
+ * depend on potential line breaks (e.g., title-case-converting textruns).
+ * This function is virtual so that those textruns can reshape themselves.
+ *
+ * @return true if this changed the linebreaks, false if the new line
+ * breaks are the same as the old
+ */
+ virtual bool SetPotentialLineBreaks(Range aRange,
+ const uint8_t* aBreakBefore);
+
+ enum class HyphenType : uint8_t {
+ // Code in BreakAndMeasureText depends on the ordering of these values!
+ None,
+ Explicit,
+ Soft,
+ AutoWithManualInSameWord,
+ AutoWithoutManualInSameWord
+ };
+
+ static bool IsOptionalHyphenBreak(HyphenType aType) {
+ return aType >= HyphenType::Soft;
+ }
+
+ struct HyphenationState {
+ uint32_t mostRecentBoundary = 0;
+ bool hasManualHyphen = false;
+ bool hasExplicitHyphen = false;
+ bool hasAutoHyphen = false;
+ };
+
+ /**
+ * Layout provides PropertyProvider objects. These allow detection of
+ * potential line break points and computation of spacing. We pass the data
+ * this way to allow lazy data acquisition; for example BreakAndMeasureText
+ * will want to only ask for properties of text it's actually looking at.
+ *
+ * NOTE that requested spacing may not actually be applied, if the textrun
+ * is unable to apply it in some context. Exception: spacing around a
+ * whitespace character MUST always be applied.
+ */
+ class PropertyProvider {
+ public:
+ // Detect hyphenation break opportunities in the given range; breaks
+ // not at cluster boundaries will be ignored.
+ virtual void GetHyphenationBreaks(Range aRange,
+ HyphenType* aBreakBefore) const = 0;
+
+ // Returns the provider's hyphenation setting, so callers can decide
+ // whether it is necessary to call GetHyphenationBreaks.
+ // Result is an StyleHyphens value.
+ virtual mozilla::StyleHyphens GetHyphensOption() const = 0;
+
+ // Returns the extra width that will be consumed by a hyphen. This should
+ // be constant for a given textrun.
+ virtual gfxFloat GetHyphenWidth() const = 0;
+
+ // Return orientation flags to be used when creating a hyphen textrun.
+ virtual mozilla::gfx::ShapedTextFlags GetShapedTextFlags() const = 0;
+
+ typedef gfxFont::Spacing Spacing;
+
+ /**
+ * Get the spacing around the indicated characters. Spacing must be zero
+ * inside clusters. In other words, if character i is not
+ * CLUSTER_START, then character i-1 must have zero after-spacing and
+ * character i must have zero before-spacing.
+ */
+ virtual void GetSpacing(Range aRange, Spacing* aSpacing) const = 0;
+
+ // Returns a gfxContext that can be used to measure the hyphen glyph.
+ // Only called if the hyphen width is requested.
+ virtual already_AddRefed<DrawTarget> GetDrawTarget() const = 0;
+
+ // Return the appUnitsPerDevUnit value to be used when measuring.
+ // Only called if the hyphen width is requested.
+ virtual uint32_t GetAppUnitsPerDevUnit() const = 0;
+ };
+
+ struct MOZ_STACK_CLASS DrawParams {
+ gfxContext* context;
+ DrawMode drawMode = DrawMode::GLYPH_FILL;
+ nscolor textStrokeColor = 0;
+ nsAtom* fontPalette = nullptr;
+ mozilla::gfx::FontPaletteValueSet* paletteValueSet = nullptr;
+ gfxPattern* textStrokePattern = nullptr;
+ const mozilla::gfx::StrokeOptions* strokeOpts = nullptr;
+ const mozilla::gfx::DrawOptions* drawOpts = nullptr;
+ const PropertyProvider* provider = nullptr;
+ // If non-null, the advance width of the substring is set.
+ gfxFloat* advanceWidth = nullptr;
+ mozilla::SVGContextPaint* contextPaint = nullptr;
+ gfxTextRunDrawCallbacks* callbacks = nullptr;
+ bool allowGDI = true;
+ explicit DrawParams(gfxContext* aContext) : context(aContext) {}
+ };
+
+ /**
+ * Draws a substring. Uses only GetSpacing from aBreakProvider.
+ * The provided point is the baseline origin on the left of the string
+ * for LTR, on the right of the string for RTL.
+ *
+ * Drawing should respect advance widths in the sense that for LTR runs,
+ * Draw(Range(start, middle), pt, ...) followed by
+ * Draw(Range(middle, end), gfxPoint(pt.x + advance, pt.y), ...)
+ * should have the same effect as
+ * Draw(Range(start, end), pt, ...)
+ *
+ * For RTL runs the rule is:
+ * Draw(Range(middle, end), pt, ...) followed by
+ * Draw(Range(start, middle), gfxPoint(pt.x + advance, pt.y), ...)
+ * should have the same effect as
+ * Draw(Range(start, end), pt, ...)
+ *
+ * Glyphs should be drawn in logical content order, which can be significant
+ * if they overlap (perhaps due to negative spacing).
+ */
+ void Draw(const Range aRange, const mozilla::gfx::Point aPt,
+ const DrawParams& aParams) const;
+
+ /**
+ * Draws the emphasis marks for this text run. Uses only GetSpacing
+ * from aProvider. The provided point is the baseline origin of the
+ * line of emphasis marks.
+ */
+ void DrawEmphasisMarks(gfxContext* aContext, gfxTextRun* aMark,
+ gfxFloat aMarkAdvance, mozilla::gfx::Point aPt,
+ Range aRange, const PropertyProvider* aProvider) const;
+
+ /**
+ * Computes the ReflowMetrics for a substring.
+ * Uses GetSpacing from aBreakProvider.
+ * @param aBoundingBoxType which kind of bounding box (loose/tight)
+ */
+ Metrics MeasureText(Range aRange, gfxFont::BoundingBoxType aBoundingBoxType,
+ DrawTarget* aDrawTargetForTightBoundingBox,
+ const PropertyProvider* aProvider) const;
+
+ Metrics MeasureText(gfxFont::BoundingBoxType aBoundingBoxType,
+ DrawTarget* aDrawTargetForTightBoundingBox,
+ const PropertyProvider* aProvider = nullptr) const {
+ return MeasureText(Range(this), aBoundingBoxType,
+ aDrawTargetForTightBoundingBox, aProvider);
+ }
+
+ void GetLineHeightMetrics(Range aRange, gfxFloat& aAscent,
+ gfxFloat& aDescent) const;
+ void GetLineHeightMetrics(gfxFloat& aAscent, gfxFloat& aDescent) const {
+ GetLineHeightMetrics(Range(this), aAscent, aDescent);
+ }
+
+ /**
+ * Computes just the advance width for a substring.
+ * Uses GetSpacing from aBreakProvider.
+ * If aSpacing is not null, the spacing attached before and after
+ * the substring would be returned in it. NOTE: the spacing is
+ * included in the advance width.
+ */
+ gfxFloat GetAdvanceWidth(Range aRange, const PropertyProvider* aProvider,
+ PropertyProvider::Spacing* aSpacing = nullptr) const;
+
+ gfxFloat GetAdvanceWidth() const {
+ return GetAdvanceWidth(Range(this), nullptr);
+ }
+
+ /**
+ * Computes the minimum advance width for a substring assuming line
+ * breaking is allowed everywhere.
+ */
+ gfxFloat GetMinAdvanceWidth(Range aRange);
+
+ /**
+ * Clear all stored line breaks for the given range (both before and after),
+ * and then set the line-break state before aRange.start to aBreakBefore and
+ * after the last cluster to aBreakAfter.
+ *
+ * We require that before and after line breaks be consistent. For clusters
+ * i and i+1, we require that if there is a break after cluster i, a break
+ * will be specified before cluster i+1. This may be temporarily violated
+ * (e.g. after reflowing line L and before reflowing line L+1); to handle
+ * these temporary violations, we say that there is a break betwen i and i+1
+ * if a break is specified after i OR a break is specified before i+1.
+ *
+ * This can change textrun geometry! The existence of a linebreak can affect
+ * the advance width of the cluster before the break (when kerning) or the
+ * geometry of one cluster before the break or any number of clusters
+ * after the break. (The one-cluster-before-the-break limit is somewhat
+ * arbitrary; if some scripts require breaking it, then we need to
+ * alter nsTextFrame::TrimTrailingWhitespace, perhaps drastically becase
+ * it could affect the layout of frames before it...)
+ *
+ * We return true if glyphs or geometry changed, false otherwise. This
+ * function is virtual so that gfxTextRun subclasses can reshape
+ * properly.
+ *
+ * @param aAdvanceWidthDelta if non-null, returns the change in advance
+ * width of the given range.
+ */
+ virtual bool SetLineBreaks(Range aRange, bool aLineBreakBefore,
+ bool aLineBreakAfter,
+ gfxFloat* aAdvanceWidthDelta);
+
+ enum SuppressBreak {
+ eNoSuppressBreak,
+ // Measure the range of text as if there is no break before it.
+ eSuppressInitialBreak,
+ // Measure the range of text as if it contains no break
+ eSuppressAllBreaks
+ };
+
+ void ClassifyAutoHyphenations(uint32_t aStart, Range aRange,
+ nsTArray<HyphenType>& aHyphenBuffer,
+ HyphenationState* aWordState);
+
+ // Struct used by BreakAndMeasureText to return the amount of trimmable
+ // trailing whitespace included in the run.
+ struct TrimmableWS {
+ mozilla::gfx::Float mAdvance = 0;
+ uint32_t mCount = 0;
+ };
+
+ /**
+ * Finds the longest substring that will fit into the given width.
+ * Uses GetHyphenationBreaks and GetSpacing from aProvider.
+ * Guarantees the following:
+ * -- 0 <= result <= aMaxLength
+ * -- result is the maximal value of N such that either
+ * N < aMaxLength && line break at N &&
+ * GetAdvanceWidth(Range(aStart, N), aProvider) <= aWidth
+ * OR N < aMaxLength && hyphen break at N &&
+ * GetAdvanceWidth(Range(aStart, N), aProvider) +
+ * GetHyphenWidth() <= aWidth
+ * OR N == aMaxLength &&
+ * GetAdvanceWidth(Range(aStart, N), aProvider) <= aWidth
+ * where GetAdvanceWidth assumes the effect of
+ * SetLineBreaks(Range(aStart, N),
+ * aLineBreakBefore, N < aMaxLength, aProvider)
+ * -- if no such N exists, then result is the smallest N such that
+ * N < aMaxLength && line break at N
+ * OR N < aMaxLength && hyphen break at N
+ * OR N == aMaxLength
+ *
+ * The call has the effect of
+ * SetLineBreaks(Range(aStart, result), aLineBreakBefore,
+ * result < aMaxLength, aProvider)
+ * and the returned metrics and the invariants above reflect this.
+ *
+ * @param aMaxLength this can be UINT32_MAX, in which case the length used
+ * is up to the end of the string
+ * @param aLineBreakBefore set to true if and only if there is an actual
+ * line break at the start of this string.
+ * @param aSuppressBreak what break should be suppressed.
+ * @param aOutTrimmableWhitespace if non-null, returns the advance of any
+ * run of trailing spaces that might be trimmed if the run ends up at
+ * end-of-line.
+ * Trimmable spaces are still counted in the "characters fit" result, and
+ * contribute to the returned Metrics values.
+ * @param aOutMetrics we fill this in for the returned substring.
+ * If a hyphenation break was used, the hyphen is NOT included in the returned
+ * metrics.
+ * @param aBoundingBoxType whether to make the bounding box in aMetrics tight
+ * @param aRefDrawTarget a reference DrawTarget to get the tight bounding box,
+ * if requested
+ * @param aOutUsedHyphenation records if we selected a hyphenation break
+ * @param aOutLastBreak if result is aMaxLength, we set this to
+ * the maximal N such that
+ * N < aMaxLength && line break at N &&
+ * GetAdvanceWidth(Range(aStart, N), aProvider) <= aWidth
+ * OR N < aMaxLength && hyphen break at N &&
+ * GetAdvanceWidth(Range(aStart, N), aProvider) +
+ * GetHyphenWidth() <= aWidth
+ * or UINT32_MAX if no such N exists, where GetAdvanceWidth assumes
+ * the effect of
+ * SetLineBreaks(Range(aStart, N), aLineBreakBefore,
+ * N < aMaxLength, aProvider)
+ *
+ * @param aCanWordWrap true if we can break between any two grapheme
+ * clusters. This is set by overflow-wrap|word-wrap: break-word
+ *
+ * @param aBreakPriority in/out the priority of the break opportunity
+ * saved in the line. If we are prioritizing break opportunities, we will
+ * not set a break with a lower priority. @see gfxBreakPriority.
+ *
+ * Note that negative advance widths are possible especially if negative
+ * spacing is provided.
+ */
+ uint32_t BreakAndMeasureText(
+ uint32_t aStart, uint32_t aMaxLength, bool aLineBreakBefore,
+ gfxFloat aWidth, const PropertyProvider& aProvider,
+ SuppressBreak aSuppressBreak, gfxFont::BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget, bool aCanWordWrap, bool aCanWhitespaceWrap,
+ // Output parameters:
+ TrimmableWS* aOutTrimmableWhitespace, // may be null
+ Metrics& aOutMetrics, bool& aOutUsedHyphenation, uint32_t& aOutLastBreak,
+ // In/out:
+ gfxBreakPriority& aBreakPriority);
+
+ // Utility getters
+
+ void* GetUserData() const { return mUserData; }
+ void SetUserData(void* aUserData) { mUserData = aUserData; }
+
+ void SetFlagBits(nsTextFrameUtils::Flags aFlags) { mFlags2 |= aFlags; }
+ void ClearFlagBits(nsTextFrameUtils::Flags aFlags) { mFlags2 &= ~aFlags; }
+ const gfxSkipChars& GetSkipChars() const { return mSkipChars; }
+ gfxFontGroup* GetFontGroup() const { return mFontGroup; }
+
+ // Call this, don't call "new gfxTextRun" directly. This does custom
+ // allocation and initialization
+ static already_AddRefed<gfxTextRun> Create(
+ const gfxTextRunFactory::Parameters* aParams, uint32_t aLength,
+ gfxFontGroup* aFontGroup, mozilla::gfx::ShapedTextFlags aFlags,
+ nsTextFrameUtils::Flags aFlags2);
+
+ // The text is divided into GlyphRuns as necessary. (In the vast majority
+ // of cases, a gfxTextRun contains just a single GlyphRun.)
+ struct GlyphRun {
+ RefPtr<gfxFont> mFont; // never null in a valid GlyphRun
+ uint32_t mCharacterOffset; // into original UTF16 string
+ mozilla::gfx::ShapedTextFlags
+ mOrientation; // gfxTextRunFactory::TEXT_ORIENT_* value
+ FontMatchType mMatchType;
+ bool mIsCJK; // Whether the text was a CJK script run (used to decide if
+ // text-decoration-skip-ink should not be applied)
+
+ // Set up the properties (but NOT offset) of the GlyphRun.
+ void SetProperties(gfxFont* aFont,
+ mozilla::gfx::ShapedTextFlags aOrientation, bool aIsCJK,
+ FontMatchType aMatchType) {
+ mFont = aFont;
+ mOrientation = aOrientation;
+ mIsCJK = aIsCJK;
+ mMatchType = aMatchType;
+ }
+
+ // Return whether the GlyphRun matches the given properties;
+ // the given FontMatchType will be added to the run if not present.
+ bool Matches(gfxFont* aFont, mozilla::gfx::ShapedTextFlags aOrientation,
+ bool aIsCJK, FontMatchType aMatchType) {
+ if (mFont == aFont && mOrientation == aOrientation && mIsCJK == aIsCJK) {
+ mMatchType.kind |= aMatchType.kind;
+ if (mMatchType.generic == mozilla::StyleGenericFontFamily::None) {
+ mMatchType.generic = aMatchType.generic;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ bool IsSidewaysLeft() const {
+ return (mOrientation & mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_MASK) ==
+ mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT;
+ }
+
+ bool IsSidewaysRight() const {
+ return (mOrientation & mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_MASK) ==
+ mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
+ }
+ };
+
+ // Script run codes that we will mark as CJK to suppress skip-ink behavior.
+ static inline bool IsCJKScript(Script aScript) {
+ switch (aScript) {
+ case Script::BOPOMOFO:
+ case Script::HAN:
+ case Script::HANGUL:
+ case Script::HIRAGANA:
+ case Script::KATAKANA:
+ case Script::KATAKANA_OR_HIRAGANA:
+ case Script::SIMPLIFIED_HAN:
+ case Script::TRADITIONAL_HAN:
+ case Script::JAPANESE:
+ case Script::KOREAN:
+ case Script::HAN_WITH_BOPOMOFO:
+ case Script::JAMO:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ class MOZ_STACK_CLASS GlyphRunIterator {
+ public:
+ GlyphRunIterator(const gfxTextRun* aTextRun, Range aRange,
+ bool aReverse = false)
+ : mTextRun(aTextRun),
+ mStartOffset(aRange.start),
+ mEndOffset(aRange.end),
+ mReverse(aReverse) {
+ mGlyphRun = mTextRun->FindFirstGlyphRunContaining(
+ aReverse ? aRange.end - 1 : aRange.start);
+ if (!mGlyphRun) {
+ mStringEnd = mStringStart = mStartOffset;
+ return;
+ }
+ uint32_t glyphRunEndOffset = mGlyphRun == mTextRun->mGlyphRuns.end() - 1
+ ? mTextRun->GetLength()
+ : (mGlyphRun + 1)->mCharacterOffset;
+ mStringEnd = std::min(mEndOffset, glyphRunEndOffset);
+ mStringStart = std::max(mStartOffset, mGlyphRun->mCharacterOffset);
+ }
+ void NextRun();
+ bool AtEnd() const { return mGlyphRun == nullptr; }
+ const struct GlyphRun* GlyphRun() const { return mGlyphRun; }
+ uint32_t StringStart() const { return mStringStart; }
+ uint32_t StringEnd() const { return mStringEnd; }
+
+ private:
+ const gfxTextRun* mTextRun;
+ const struct GlyphRun* mGlyphRun;
+ uint32_t mStringStart;
+ uint32_t mStringEnd;
+ uint32_t mStartOffset;
+ uint32_t mEndOffset;
+ bool mReverse;
+ };
+
+ class GlyphRunOffsetComparator {
+ public:
+ bool Equals(const GlyphRun& a, const GlyphRun& b) const {
+ return a.mCharacterOffset == b.mCharacterOffset;
+ }
+
+ bool LessThan(const GlyphRun& a, const GlyphRun& b) const {
+ return a.mCharacterOffset < b.mCharacterOffset;
+ }
+ };
+
+ // API for setting up the textrun glyphs. Should only be called by
+ // things that construct textruns.
+ /**
+ * We've found a run of text that should use a particular font. Call this
+ * only during initialization when font substitution has been computed.
+ * Call it before setting up the glyphs for the characters in this run;
+ * SetMissingGlyph requires that the correct glyphrun be installed.
+ *
+ * If aForceNewRun, a new glyph run will be added, even if the
+ * previously added run uses the same font. If glyph runs are
+ * added out of strictly increasing aStartCharIndex order (via
+ * force), then SortGlyphRuns must be called after all glyph runs
+ * are added before any further operations are performed with this
+ * TextRun.
+ */
+ void AddGlyphRun(gfxFont* aFont, FontMatchType aMatchType,
+ uint32_t aUTF16Offset, bool aForceNewRun,
+ mozilla::gfx::ShapedTextFlags aOrientation, bool aIsCJK);
+ void ResetGlyphRuns() { mGlyphRuns.Clear(); }
+ void SanitizeGlyphRuns();
+
+ const CompressedGlyph* GetCharacterGlyphs() const final {
+ MOZ_ASSERT(mCharacterGlyphs, "failed to initialize mCharacterGlyphs");
+ return mCharacterGlyphs;
+ }
+ CompressedGlyph* GetCharacterGlyphs() final {
+ MOZ_ASSERT(mCharacterGlyphs, "failed to initialize mCharacterGlyphs");
+ return mCharacterGlyphs;
+ }
+
+ // clean out results from shaping in progress, used for fallback scenarios
+ void ClearGlyphsAndCharacters();
+
+ void SetSpaceGlyph(gfxFont* aFont, DrawTarget* aDrawTarget,
+ uint32_t aCharIndex,
+ mozilla::gfx::ShapedTextFlags aOrientation);
+
+ // Set the glyph data for the given character index to the font's
+ // space glyph, IF this can be done as a "simple" glyph record
+ // (not requiring a DetailedGlyph entry). This avoids the need to call
+ // the font shaper and go through the shaped-word cache for most spaces.
+ //
+ // The parameter aSpaceChar is the original character code for which
+ // this space glyph is being used; if this is U+0020, we need to record
+ // that it could be trimmed at a run edge, whereas other kinds of space
+ // (currently just U+00A0) would not be trimmable/breakable.
+ //
+ // Returns true if it was able to set simple glyph data for the space;
+ // if it returns false, the caller needs to fall back to some other
+ // means to create the necessary (detailed) glyph data.
+ bool SetSpaceGlyphIfSimple(gfxFont* aFont, uint32_t aCharIndex,
+ char16_t aSpaceChar,
+ mozilla::gfx::ShapedTextFlags aOrientation);
+
+ // Record the positions of specific characters that layout may need to
+ // detect in the textrun, even though it doesn't have an explicit copy
+ // of the original text. These are recorded using flag bits in the
+ // CompressedGlyph record; if necessary, we convert "simple" glyph records
+ // to "complex" ones as the Tab and Newline flags are not present in
+ // simple CompressedGlyph records.
+ void SetIsTab(uint32_t aIndex) { EnsureComplexGlyph(aIndex).SetIsTab(); }
+ void SetIsNewline(uint32_t aIndex) {
+ EnsureComplexGlyph(aIndex).SetIsNewline();
+ }
+ void SetNoEmphasisMark(uint32_t aIndex) {
+ EnsureComplexGlyph(aIndex).SetNoEmphasisMark();
+ }
+ void SetIsFormattingControl(uint32_t aIndex) {
+ EnsureComplexGlyph(aIndex).SetIsFormattingControl();
+ }
+
+ /**
+ * Prefetch all the glyph extents needed to ensure that Measure calls
+ * on this textrun not requesting tight boundingBoxes will succeed. Note
+ * that some glyph extents might not be fetched due to OOM or other
+ * errors.
+ */
+ void FetchGlyphExtents(DrawTarget* aRefDrawTarget) const;
+
+ const GlyphRun* GetGlyphRuns(uint32_t* aNumGlyphRuns) const {
+ *aNumGlyphRuns = mGlyphRuns.Length();
+ return mGlyphRuns.begin();
+ }
+
+ uint32_t GlyphRunCount() const { return mGlyphRuns.Length(); }
+
+ const GlyphRun* TrailingGlyphRun() const {
+ return mGlyphRuns.IsEmpty() ? nullptr : mGlyphRuns.end() - 1;
+ }
+
+ // Returns the GlyphRun containing the given offset.
+ // (Returns mGlyphRuns.end()-1 when aOffset is mCharacterCount; returns
+ // nullptr if textrun is empty and no glyph runs are present.)
+ const GlyphRun* FindFirstGlyphRunContaining(uint32_t aOffset) const;
+
+ // Copy glyph data from a ShapedWord into this textrun.
+ void CopyGlyphDataFrom(gfxShapedWord* aSource, uint32_t aStart);
+
+ // Copy glyph data for a range of characters from aSource to this
+ // textrun.
+ void CopyGlyphDataFrom(gfxTextRun* aSource, Range aRange, uint32_t aDest);
+
+ // Tell the textrun to release its reference to its creating gfxFontGroup
+ // immediately, rather than on destruction. This is used for textruns
+ // that are actually owned by a gfxFontGroup, so that they don't keep it
+ // permanently alive due to a circular reference. (The caller of this is
+ // taking responsibility for ensuring the textrun will not outlive its
+ // mFontGroup.)
+ void ReleaseFontGroup();
+
+ struct LigatureData {
+ // textrun range of the containing ligature
+ Range mRange;
+ // appunits advance to the start of the ligature part within the ligature;
+ // never includes any spacing
+ gfxFloat mPartAdvance;
+ // appunits width of the ligature part; includes before-spacing
+ // when the part is at the start of the ligature, and after-spacing
+ // when the part is as the end of the ligature
+ gfxFloat mPartWidth;
+
+ bool mClipBeforePart;
+ bool mClipAfterPart;
+ };
+
+ // return storage used by this run, for memory reporter;
+ // nsTransformedTextRun needs to override this as it holds additional data
+ virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+ MOZ_MUST_OVERRIDE;
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+ MOZ_MUST_OVERRIDE;
+
+ nsTextFrameUtils::Flags GetFlags2() const { return mFlags2; }
+
+ // Get the size, if it hasn't already been gotten, marking as it goes.
+ size_t MaybeSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
+ if (mFlags2 & nsTextFrameUtils::Flags::RunSizeAccounted) {
+ return 0;
+ }
+ mFlags2 |= nsTextFrameUtils::Flags::RunSizeAccounted;
+ return SizeOfIncludingThis(aMallocSizeOf);
+ }
+ void ResetSizeOfAccountingFlags() {
+ mFlags2 &= ~nsTextFrameUtils::Flags::RunSizeAccounted;
+ }
+
+ // shaping state - for some font features, fallback is required that
+ // affects the entire run. for example, fallback for one script/font
+ // portion of a textrun requires fallback to be applied to the entire run
+
+ enum ShapingState : uint8_t {
+ eShapingState_Normal, // default state
+ eShapingState_ShapingWithFeature, // have shaped with feature
+ eShapingState_ShapingWithFallback, // have shaped with fallback
+ eShapingState_Aborted, // abort initial iteration
+ eShapingState_ForceFallbackFeature // redo with fallback forced on
+ };
+
+ ShapingState GetShapingState() const { return mShapingState; }
+ void SetShapingState(ShapingState aShapingState) {
+ mShapingState = aShapingState;
+ }
+
+ int32_t GetAdvanceForGlyph(uint32_t aIndex) const {
+ const CompressedGlyph& glyphData = mCharacterGlyphs[aIndex];
+ if (glyphData.IsSimpleGlyph()) {
+ return glyphData.GetSimpleAdvance();
+ }
+ uint32_t glyphCount = glyphData.GetGlyphCount();
+ if (!glyphCount) {
+ return 0;
+ }
+ const DetailedGlyph* details = GetDetailedGlyphs(aIndex);
+ int32_t advance = 0;
+ for (uint32_t j = 0; j < glyphCount; ++j, ++details) {
+ advance += details->mAdvance;
+ }
+ return advance;
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ void Dump(FILE* aOutput = stderr);
+#endif
+
+ protected:
+ /**
+ * Create a textrun, and set its mCharacterGlyphs to point immediately
+ * after the base object; this is ONLY used in conjunction with placement
+ * new, after allocating a block large enough for the glyph records to
+ * follow the base textrun object.
+ */
+ gfxTextRun(const gfxTextRunFactory::Parameters* aParams, uint32_t aLength,
+ gfxFontGroup* aFontGroup, mozilla::gfx::ShapedTextFlags aFlags,
+ nsTextFrameUtils::Flags aFlags2);
+
+ // Whether we need to fetch actual glyph extents from the fonts.
+ bool NeedsGlyphExtents() const;
+
+ /**
+ * Helper for the Create() factory method to allocate the required
+ * glyph storage for a textrun object with the basic size aSize,
+ * plus room for aLength glyph records.
+ */
+ static void* AllocateStorageForTextRun(size_t aSize, uint32_t aLength);
+
+ // Pointer to the array of CompressedGlyph records; must be initialized
+ // when the object is constructed.
+ CompressedGlyph* mCharacterGlyphs;
+
+ private:
+ // **** general helpers ****
+
+ // Get the total advance for a range of glyphs.
+ int32_t GetAdvanceForGlyphs(Range aRange) const;
+
+ // Spacing for characters outside the range aSpacingStart/aSpacingEnd
+ // is assumed to be zero; such characters are not passed to aProvider.
+ // This is useful to protect aProvider from being passed character indices
+ // it is not currently able to handle.
+ bool GetAdjustedSpacingArray(
+ Range aRange, const PropertyProvider* aProvider, Range aSpacingRange,
+ nsTArray<PropertyProvider::Spacing>* aSpacing) const;
+
+ CompressedGlyph& EnsureComplexGlyph(uint32_t aIndex) {
+ gfxShapedText::EnsureComplexGlyph(aIndex, mCharacterGlyphs[aIndex]);
+ return mCharacterGlyphs[aIndex];
+ }
+
+ // **** ligature helpers ****
+ // (Platforms do the actual ligaturization, but we need to do a bunch of stuff
+ // to handle requests that begin or end inside a ligature)
+
+ // if aProvider is null then mBeforeSpacing and mAfterSpacing are set to zero
+ LigatureData ComputeLigatureData(Range aPartRange,
+ const PropertyProvider* aProvider) const;
+ gfxFloat ComputePartialLigatureWidth(Range aPartRange,
+ const PropertyProvider* aProvider) const;
+ void DrawPartialLigature(gfxFont* aFont, Range aRange,
+ mozilla::gfx::Point* aPt,
+ const PropertyProvider* aProvider,
+ TextRunDrawParams& aParams,
+ mozilla::gfx::ShapedTextFlags aOrientation) const;
+ // Advance aRange.start to the start of the nearest ligature, back
+ // up aRange.end to the nearest ligature end; may result in
+ // aRange->start == aRange->end.
+ // Returns whether any adjustment was made.
+ bool ShrinkToLigatureBoundaries(Range* aRange) const;
+ // result in appunits
+ gfxFloat GetPartialLigatureWidth(Range aRange,
+ const PropertyProvider* aProvider) const;
+ void AccumulatePartialLigatureMetrics(
+ gfxFont* aFont, Range aRange, gfxFont::BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget, const PropertyProvider* aProvider,
+ mozilla::gfx::ShapedTextFlags aOrientation, Metrics* aMetrics) const;
+
+ // **** measurement helper ****
+ void AccumulateMetricsForRun(gfxFont* aFont, Range aRange,
+ gfxFont::BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget,
+ const PropertyProvider* aProvider,
+ Range aSpacingRange,
+ mozilla::gfx::ShapedTextFlags aOrientation,
+ Metrics* aMetrics) const;
+
+ // **** drawing helper ****
+ void DrawGlyphs(gfxFont* aFont, Range aRange, mozilla::gfx::Point* aPt,
+ const PropertyProvider* aProvider, Range aSpacingRange,
+ TextRunDrawParams& aParams,
+ mozilla::gfx::ShapedTextFlags aOrientation) const;
+
+ // The textrun holds either a single GlyphRun -or- an array.
+ mozilla::ElementOrArray<GlyphRun> mGlyphRuns;
+
+ void* mUserData;
+
+ // mFontGroup is usually a strong reference, but refcounting is managed
+ // manually because it may be explicitly released by ReleaseFontGroup()
+ // in the case where the font group actually owns the textrun.
+ gfxFontGroup* MOZ_OWNING_REF mFontGroup;
+
+ gfxSkipChars mSkipChars;
+
+ nsTextFrameUtils::Flags
+ mFlags2; // additional flags (see also gfxShapedText::mFlags)
+
+ bool mDontSkipDrawing; // true if the text run must not skip drawing, even if
+ // waiting for a user font download, e.g. because we
+ // are using it to draw canvas text
+ bool mReleasedFontGroup; // we already called NS_RELEASE on
+ // mFontGroup, so don't do it again
+ bool mReleasedFontGroupSkippedDrawing; // whether our old mFontGroup value
+ // was set to skip drawing
+
+ // shaping state for handling variant fallback features
+ // such as subscript/superscript variant glyphs
+ ShapingState mShapingState;
+};
+
+class gfxFontGroup final : public gfxTextRunFactory {
+ public:
+ typedef mozilla::intl::Script Script;
+ typedef gfxShapedText::CompressedGlyph CompressedGlyph;
+
+ static void
+ Shutdown(); // platform must call this to release the languageAtomService
+
+ gfxFontGroup(nsPresContext* aPresContext,
+ const mozilla::StyleFontFamilyList& aFontFamilyList,
+ const gfxFontStyle* aStyle, nsAtom* aLanguage,
+ bool aExplicitLanguage, gfxTextPerfMetrics* aTextPerf,
+ gfxUserFontSet* aUserFontSet, gfxFloat aDevToCssSize,
+ StyleFontVariantEmoji aVariantEmoji);
+
+ virtual ~gfxFontGroup();
+
+ gfxFontGroup(const gfxFontGroup& aOther) = delete;
+
+ // Returns first valid font in the fontlist or default font.
+ // Initiates userfont loads if userfont not loaded.
+ // aGeneric: if non-null, returns the CSS generic type that was mapped to
+ // this font
+ // aIsFirst: if non-null, returns whether the font was first in the list
+ already_AddRefed<gfxFont> GetFirstValidFont(
+ uint32_t aCh = 0x20, mozilla::StyleGenericFontFamily* aGeneric = nullptr,
+ bool* aIsFirst = nullptr);
+
+ // Returns the first font in the font-group that has an OpenType MATH table,
+ // or null if no such font is available. The GetMathConstant methods may be
+ // called on the returned font.
+ already_AddRefed<gfxFont> GetFirstMathFont();
+
+ const gfxFontStyle* GetStyle() const { return &mStyle; }
+
+ // Get the presContext for which this fontGroup was constructed. This may be
+ // null! (In the case of canvas not connected to a document.)
+ nsPresContext* GetPresContext() const { return mPresContext; }
+
+ /**
+ * The listed characters should be treated as invisible and zero-width
+ * when creating textruns.
+ */
+ static bool IsInvalidChar(uint8_t ch);
+ static bool IsInvalidChar(char16_t ch);
+
+ /**
+ * Make a textrun for a given string.
+ * If aText is not persistent (aFlags & TEXT_IS_PERSISTENT), the
+ * textrun will copy it.
+ * This calls FetchGlyphExtents on the textrun.
+ */
+ template <typename T>
+ already_AddRefed<gfxTextRun> MakeTextRun(const T* aString, uint32_t aLength,
+ const Parameters* aParams,
+ mozilla::gfx::ShapedTextFlags aFlags,
+ nsTextFrameUtils::Flags aFlags2,
+ gfxMissingFontRecorder* aMFR);
+
+ /**
+ * Textrun creation helper for clients that don't want to pass
+ * a full Parameters record.
+ */
+ template <typename T>
+ already_AddRefed<gfxTextRun> MakeTextRun(const T* aString, uint32_t aLength,
+ DrawTarget* aRefDrawTarget,
+ int32_t aAppUnitsPerDevUnit,
+ mozilla::gfx::ShapedTextFlags aFlags,
+ nsTextFrameUtils::Flags aFlags2,
+ gfxMissingFontRecorder* aMFR) {
+ gfxTextRunFactory::Parameters params = {
+ aRefDrawTarget, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevUnit};
+ return MakeTextRun(aString, aLength, &params, aFlags, aFlags2, aMFR);
+ }
+
+ // Get the (possibly-cached) width of the hyphen character.
+ gfxFloat GetHyphenWidth(const gfxTextRun::PropertyProvider* aProvider);
+
+ /**
+ * Make a text run representing a single hyphen character.
+ * This will use U+2010 HYPHEN if available in the first font,
+ * otherwise fall back to U+002D HYPHEN-MINUS.
+ * The caller is responsible for deleting the returned text run
+ * when no longer required.
+ */
+ already_AddRefed<gfxTextRun> MakeHyphenTextRun(
+ DrawTarget* aDrawTarget, mozilla::gfx::ShapedTextFlags aFlags,
+ uint32_t aAppUnitsPerDevUnit);
+
+ /**
+ * Check whether a given font (specified by its gfxFontEntry)
+ * is already in the fontgroup's list of actual fonts
+ */
+ bool HasFont(const gfxFontEntry* aFontEntry);
+
+ // This returns the preferred underline for this font group.
+ // Some CJK fonts have wrong underline offset in its metrics.
+ // If this group has such "bad" font, each platform's gfxFontGroup
+ // initialized mUnderlineOffset. The value should be lower value of
+ // first font's metrics and the bad font's metrics. Otherwise, this
+ // returns from first font's metrics.
+ static constexpr gfxFloat UNDERLINE_OFFSET_NOT_SET = INT16_MAX;
+ gfxFloat GetUnderlineOffset();
+
+ already_AddRefed<gfxFont> FindFontForChar(uint32_t ch, uint32_t prevCh,
+ uint32_t aNextCh, Script aRunScript,
+ gfxFont* aPrevMatchedFont,
+ FontMatchType* aMatchType);
+
+ gfxUserFontSet* GetUserFontSet();
+
+ // With downloadable fonts, the composition of the font group can change as
+ // fonts are downloaded for each change in state of the user font set, the
+ // generation value is bumped to avoid picking up previously created text runs
+ // in the text run word cache. For font groups based on stylesheets with no
+ // @font-face rule, this always returns 0.
+ uint64_t GetGeneration();
+
+ // generation of the latest fontset rebuild, 0 when no fontset present
+ uint64_t GetRebuildGeneration();
+
+ // used when logging text performance
+ gfxTextPerfMetrics* GetTextPerfMetrics() const { return mTextPerf; }
+
+ // This will call UpdateUserFonts() if the user font set is changed.
+ void SetUserFontSet(gfxUserFontSet* aUserFontSet);
+
+ void ClearCachedData() {
+ mUnderlineOffset = UNDERLINE_OFFSET_NOT_SET;
+ mSkipDrawing = false;
+ mHyphenWidth = -1;
+ mCachedEllipsisTextRun = nullptr;
+ }
+
+ // If there is a user font set, check to see whether the font list or any
+ // caches need updating.
+ void UpdateUserFonts();
+
+ // search for a specific userfont in the list of fonts
+ bool ContainsUserFont(const gfxUserFontEntry* aUserFont);
+
+ bool ShouldSkipDrawing() const { return mSkipDrawing; }
+
+ class LazyReferenceDrawTargetGetter {
+ public:
+ virtual already_AddRefed<DrawTarget> GetRefDrawTarget() = 0;
+ };
+ // The gfxFontGroup keeps ownership of this textrun.
+ // It is only guaranteed to exist until the next call to GetEllipsisTextRun
+ // (which might use a different appUnitsPerDev value or flags) for the font
+ // group, or until UpdateUserFonts is called, or the fontgroup is destroyed.
+ // Get it/use it/forget it :) - don't keep a reference that might go stale.
+ gfxTextRun* GetEllipsisTextRun(
+ int32_t aAppUnitsPerDevPixel, mozilla::gfx::ShapedTextFlags aFlags,
+ LazyReferenceDrawTargetGetter& aRefDrawTargetGetter);
+
+ void CheckForUpdatedPlatformList() {
+ auto* pfl = gfxPlatformFontList::PlatformFontList();
+ if (mFontListGeneration != pfl->GetGeneration()) {
+ // Forget cached fonts that may no longer be valid.
+ mLastPrefFamily = FontFamily();
+ mLastPrefFont = nullptr;
+ mDefaultFont = nullptr;
+ mFonts.Clear();
+ BuildFontList();
+ }
+ }
+
+ nsAtom* Language() const { return mLanguage.get(); }
+
+ // Get font metrics to be used as the basis for CSS font-relative units.
+ // Note that these may be a "composite" of metrics from multiple fonts,
+ // because the 'ch' and 'ic' units depend on the font that would be used
+ // to render specific characters, not simply the "first available" font.
+ // https://drafts.csswg.org/css-values-4/#ch
+ // https://drafts.csswg.org/css-values-4/#ic
+ gfxFont::Metrics GetMetricsForCSSUnits(gfxFont::Orientation aOrientation);
+
+ protected:
+ friend class mozilla::PostTraversalTask;
+
+ struct TextRange {
+ TextRange(uint32_t aStart, uint32_t aEnd, gfxFont* aFont,
+ FontMatchType aMatchType,
+ mozilla::gfx::ShapedTextFlags aOrientation)
+ : start(aStart),
+ end(aEnd),
+ font(aFont),
+ matchType(aMatchType),
+ orientation(aOrientation) {}
+ uint32_t Length() const { return end - start; }
+ uint32_t start, end;
+ RefPtr<gfxFont> font;
+ FontMatchType matchType;
+ mozilla::gfx::ShapedTextFlags orientation;
+ };
+
+ // search through pref fonts for a character, return nullptr if no matching
+ // pref font
+ already_AddRefed<gfxFont> WhichPrefFontSupportsChar(
+ uint32_t aCh, uint32_t aNextCh, eFontPresentation aPresentation);
+
+ already_AddRefed<gfxFont> WhichSystemFontSupportsChar(
+ uint32_t aCh, uint32_t aNextCh, Script aRunScript,
+ eFontPresentation aPresentation);
+
+ template <typename T>
+ void ComputeRanges(nsTArray<TextRange>& aRanges, const T* aString,
+ uint32_t aLength, Script aRunScript,
+ mozilla::gfx::ShapedTextFlags aOrientation);
+
+ class FamilyFace {
+ public:
+ FamilyFace()
+ : mOwnedFamily(nullptr),
+ mFontEntry(nullptr),
+ mGeneric(mozilla::StyleGenericFontFamily::None),
+ mFontCreated(false),
+ mLoading(false),
+ mInvalid(false),
+ mCheckForFallbackFaces(false),
+ mIsSharedFamily(false),
+ mHasFontEntry(false) {}
+
+ FamilyFace(gfxFontFamily* aFamily, gfxFont* aFont,
+ mozilla::StyleGenericFontFamily aGeneric)
+ : mOwnedFamily(aFamily),
+ mGeneric(aGeneric),
+ mFontCreated(true),
+ mLoading(false),
+ mInvalid(false),
+ mCheckForFallbackFaces(false),
+ mIsSharedFamily(false),
+ mHasFontEntry(false) {
+ NS_ASSERTION(aFont, "font pointer must not be null");
+ NS_ASSERTION(!aFamily || aFamily->ContainsFace(aFont->GetFontEntry()),
+ "font is not a member of the given family");
+ NS_IF_ADDREF(aFamily);
+ mFont = aFont;
+ NS_ADDREF(aFont);
+ }
+
+ FamilyFace(gfxFontFamily* aFamily, gfxFontEntry* aFontEntry,
+ mozilla::StyleGenericFontFamily aGeneric)
+ : mOwnedFamily(aFamily),
+ mGeneric(aGeneric),
+ mFontCreated(false),
+ mLoading(false),
+ mInvalid(false),
+ mCheckForFallbackFaces(false),
+ mIsSharedFamily(false),
+ mHasFontEntry(true) {
+ NS_ASSERTION(aFontEntry, "font entry pointer must not be null");
+ NS_ASSERTION(!aFamily || aFamily->ContainsFace(aFontEntry),
+ "font is not a member of the given family");
+ NS_IF_ADDREF(aFamily);
+ mFontEntry = aFontEntry;
+ NS_ADDREF(aFontEntry);
+ }
+
+ FamilyFace(mozilla::fontlist::Family* aFamily, gfxFontEntry* aFontEntry,
+ mozilla::StyleGenericFontFamily aGeneric)
+ : mSharedFamily(aFamily),
+ mGeneric(aGeneric),
+ mFontCreated(false),
+ mLoading(false),
+ mInvalid(false),
+ mCheckForFallbackFaces(false),
+ mIsSharedFamily(true),
+ mHasFontEntry(true) {
+ MOZ_ASSERT(aFamily && aFontEntry && aFontEntry->mShmemFace);
+ mFontEntry = aFontEntry;
+ NS_ADDREF(aFontEntry);
+ }
+
+ FamilyFace(const FamilyFace& aOtherFamilyFace)
+ : mGeneric(aOtherFamilyFace.mGeneric),
+ mFontCreated(aOtherFamilyFace.mFontCreated),
+ mLoading(aOtherFamilyFace.mLoading),
+ mInvalid(aOtherFamilyFace.mInvalid),
+ mCheckForFallbackFaces(aOtherFamilyFace.mCheckForFallbackFaces),
+ mIsSharedFamily(aOtherFamilyFace.mIsSharedFamily),
+ mHasFontEntry(aOtherFamilyFace.mHasFontEntry) {
+ if (mIsSharedFamily) {
+ mSharedFamily = aOtherFamilyFace.mSharedFamily;
+ if (mFontCreated) {
+ mFont = aOtherFamilyFace.mFont;
+ NS_ADDREF(mFont);
+ } else if (mHasFontEntry) {
+ mFontEntry = aOtherFamilyFace.mFontEntry;
+ NS_ADDREF(mFontEntry);
+ } else {
+ mSharedFace = aOtherFamilyFace.mSharedFace;
+ }
+ } else {
+ mOwnedFamily = aOtherFamilyFace.mOwnedFamily;
+ NS_IF_ADDREF(mOwnedFamily);
+ if (mFontCreated) {
+ mFont = aOtherFamilyFace.mFont;
+ NS_ADDREF(mFont);
+ } else {
+ mFontEntry = aOtherFamilyFace.mFontEntry;
+ NS_IF_ADDREF(mFontEntry);
+ }
+ }
+ }
+
+ ~FamilyFace() {
+ if (mFontCreated) {
+ NS_RELEASE(mFont);
+ }
+ if (!mIsSharedFamily) {
+ NS_IF_RELEASE(mOwnedFamily);
+ }
+ if (mHasFontEntry) {
+ NS_RELEASE(mFontEntry);
+ }
+ }
+
+ FamilyFace& operator=(const FamilyFace& aOther) {
+ if (mFontCreated) {
+ NS_RELEASE(mFont);
+ }
+ if (!mIsSharedFamily) {
+ NS_IF_RELEASE(mOwnedFamily);
+ }
+ if (mHasFontEntry) {
+ NS_RELEASE(mFontEntry);
+ }
+
+ mGeneric = aOther.mGeneric;
+ mFontCreated = aOther.mFontCreated;
+ mLoading = aOther.mLoading;
+ mInvalid = aOther.mInvalid;
+ mIsSharedFamily = aOther.mIsSharedFamily;
+ mHasFontEntry = aOther.mHasFontEntry;
+
+ if (mIsSharedFamily) {
+ mSharedFamily = aOther.mSharedFamily;
+ if (mFontCreated) {
+ mFont = aOther.mFont;
+ NS_ADDREF(mFont);
+ } else if (mHasFontEntry) {
+ mFontEntry = aOther.mFontEntry;
+ NS_ADDREF(mFontEntry);
+ } else {
+ mSharedFace = aOther.mSharedFace;
+ }
+ } else {
+ mOwnedFamily = aOther.mOwnedFamily;
+ NS_IF_ADDREF(mOwnedFamily);
+ if (mFontCreated) {
+ mFont = aOther.mFont;
+ NS_ADDREF(mFont);
+ } else {
+ mFontEntry = aOther.mFontEntry;
+ NS_IF_ADDREF(mFontEntry);
+ }
+ }
+
+ return *this;
+ }
+
+ gfxFontFamily* OwnedFamily() const {
+ MOZ_ASSERT(!mIsSharedFamily);
+ return mOwnedFamily;
+ }
+ mozilla::fontlist::Family* SharedFamily() const {
+ MOZ_ASSERT(mIsSharedFamily);
+ return mSharedFamily;
+ }
+ gfxFont* Font() const { return mFontCreated ? mFont : nullptr; }
+
+ gfxFontEntry* FontEntry() const {
+ if (mFontCreated) {
+ return mFont->GetFontEntry();
+ }
+ if (mHasFontEntry) {
+ return mFontEntry;
+ }
+ if (mIsSharedFamily) {
+ return gfxPlatformFontList::PlatformFontList()->GetOrCreateFontEntry(
+ mSharedFace, SharedFamily());
+ }
+ return nullptr;
+ }
+
+ mozilla::StyleGenericFontFamily Generic() const { return mGeneric; }
+
+ bool IsSharedFamily() const { return mIsSharedFamily; }
+ bool IsUserFontContainer() const {
+ gfxFontEntry* fe = FontEntry();
+ return fe && fe->mIsUserFontContainer;
+ }
+ bool IsLoading() const { return mLoading; }
+ bool IsInvalid() const { return mInvalid; }
+ void CheckState(bool& aSkipDrawing);
+ void SetLoading(bool aIsLoading) { mLoading = aIsLoading; }
+ void SetInvalid() { mInvalid = true; }
+ bool CheckForFallbackFaces() const { return mCheckForFallbackFaces; }
+ void SetCheckForFallbackFaces() { mCheckForFallbackFaces = true; }
+
+ // Return true if we're currently loading (or waiting for) a resource that
+ // may support the given character.
+ bool IsLoadingFor(uint32_t aCh) {
+ if (!IsLoading()) {
+ return false;
+ }
+ MOZ_ASSERT(IsUserFontContainer());
+ auto* ufe = static_cast<gfxUserFontEntry*>(FontEntry());
+ return ufe && ufe->CharacterInUnicodeRange(aCh);
+ }
+
+ void SetFont(gfxFont* aFont) {
+ NS_ASSERTION(aFont, "font pointer must not be null");
+ NS_ADDREF(aFont);
+ if (mFontCreated) {
+ NS_RELEASE(mFont);
+ } else if (mHasFontEntry) {
+ NS_RELEASE(mFontEntry);
+ mHasFontEntry = false;
+ }
+ mFont = aFont;
+ mFontCreated = true;
+ mLoading = false;
+ }
+
+ bool EqualsUserFont(const gfxUserFontEntry* aUserFont) const;
+
+ private:
+ union {
+ gfxFontFamily* MOZ_OWNING_REF mOwnedFamily;
+ mozilla::fontlist::Family* MOZ_NON_OWNING_REF mSharedFamily;
+ };
+ // either a font or a font entry exists
+ union {
+ // Whichever of these fields is actually present will be a strong
+ // reference, with refcounting handled manually.
+ gfxFont* MOZ_OWNING_REF mFont;
+ gfxFontEntry* MOZ_OWNING_REF mFontEntry;
+ mozilla::fontlist::Face* MOZ_NON_OWNING_REF mSharedFace;
+ };
+ mozilla::StyleGenericFontFamily mGeneric;
+ bool mFontCreated : 1;
+ bool mLoading : 1;
+ bool mInvalid : 1;
+ bool mCheckForFallbackFaces : 1;
+ bool mIsSharedFamily : 1;
+ bool mHasFontEntry : 1;
+ };
+
+ nsPresContext* mPresContext = nullptr;
+
+ // List of font families, either named or generic.
+ // Generic names map to system pref fonts based on language.
+ mozilla::StyleFontFamilyList mFamilyList;
+
+ // Fontlist containing a font entry for each family found. gfxFont objects
+ // are created as needed and userfont loads are initiated when needed.
+ // Code should be careful about addressing this array directly.
+ nsTArray<FamilyFace> mFonts;
+
+ RefPtr<gfxFont> mDefaultFont;
+ gfxFontStyle mStyle;
+
+ RefPtr<nsAtom> mLanguage;
+
+ gfxFloat mUnderlineOffset;
+ gfxFloat mHyphenWidth;
+ gfxFloat mDevToCssSize;
+
+ RefPtr<gfxUserFontSet> mUserFontSet;
+ uint64_t mCurrGeneration; // track the current user font set generation,
+ // rebuild font list if needed
+
+ gfxTextPerfMetrics* mTextPerf;
+
+ // Cache a textrun representing an ellipsis (useful for CSS text-overflow)
+ // at a specific appUnitsPerDevPixel size and orientation
+ RefPtr<gfxTextRun> mCachedEllipsisTextRun;
+
+ // cache the most recent pref font to avoid general pref font lookup
+ FontFamily mLastPrefFamily;
+ RefPtr<gfxFont> mLastPrefFont;
+ eFontPrefLang mLastPrefLang; // lang group for last pref font
+ eFontPrefLang mPageLang;
+ bool mLastPrefFirstFont; // is this the first font in the list of pref fonts
+ // for this lang group?
+
+ bool mSkipDrawing; // hide text while waiting for a font
+ // download to complete (or fallback
+ // timer to fire)
+
+ bool mExplicitLanguage; // Does mLanguage come from an explicit attribute?
+
+ eFontPresentation mEmojiPresentation = eFontPresentation::Any;
+
+ // Generic font family used to select among font prefs during fallback.
+ mozilla::StyleGenericFontFamily mFallbackGeneric =
+ mozilla::StyleGenericFontFamily::None;
+
+ uint32_t mFontListGeneration = 0; // platform font list generation for this
+ // fontgroup
+
+ /**
+ * Textrun creation short-cuts for special cases where we don't need to
+ * call a font shaper to generate glyphs.
+ */
+ already_AddRefed<gfxTextRun> MakeEmptyTextRun(
+ const Parameters* aParams, mozilla::gfx::ShapedTextFlags aFlags,
+ nsTextFrameUtils::Flags aFlags2);
+
+ already_AddRefed<gfxTextRun> MakeSpaceTextRun(
+ const Parameters* aParams, mozilla::gfx::ShapedTextFlags aFlags,
+ nsTextFrameUtils::Flags aFlags2);
+
+ template <typename T>
+ already_AddRefed<gfxTextRun> MakeBlankTextRun(
+ const T* aString, uint32_t aLength, const Parameters* aParams,
+ mozilla::gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2);
+
+ // Initialize the list of fonts
+ void BuildFontList();
+
+ // Get the font at index i within the fontlist, for character aCh (in case
+ // of fonts with multiple resources and unicode-range partitioning).
+ // Will initiate userfont load if not already loaded.
+ // May return null if userfont not loaded or if font invalid.
+ // If *aLoading is true, a relevant resource is already being loaded so no
+ // new download will be initiated; if a download is started, *aLoading will
+ // be set to true on return.
+ already_AddRefed<gfxFont> GetFontAt(int32_t i, uint32_t aCh, bool* aLoading);
+
+ // Simplified version of GetFontAt() for use where we just need a font for
+ // metrics, math layout tables, etc.
+ already_AddRefed<gfxFont> GetFontAt(int32_t i, uint32_t aCh = 0x20) {
+ bool loading = false;
+ return GetFontAt(i, aCh, &loading);
+ }
+
+ // will always return a font or force a shutdown
+ already_AddRefed<gfxFont> GetDefaultFont();
+
+ // Init this font group's font metrics. If there no bad fonts, you don't need
+ // to call this. But if there are one or more bad fonts which have bad
+ // underline offset, you should call this with the *first* bad font.
+ void InitMetricsForBadFont(gfxFont* aBadFont);
+
+ // Set up the textrun glyphs for an entire text run:
+ // find script runs, and then call InitScriptRun for each
+ template <typename T>
+ void InitTextRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
+ const T* aString, uint32_t aLength,
+ gfxMissingFontRecorder* aMFR);
+
+ // InitTextRun helper to handle a single script run, by finding font ranges
+ // and calling each font's InitTextRun() as appropriate
+ template <typename T>
+ void InitScriptRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
+ const T* aString, uint32_t aScriptRunStart,
+ uint32_t aScriptRunEnd, Script aRunScript,
+ gfxMissingFontRecorder* aMFR);
+
+ // Helper for font-matching:
+ // search all faces in a family for a fallback in cases where it's unclear
+ // whether the family might have a font for a given character
+ already_AddRefed<gfxFont> FindFallbackFaceForChar(
+ const FamilyFace& aFamily, uint32_t aCh, uint32_t aNextCh,
+ eFontPresentation aPresentation);
+
+ already_AddRefed<gfxFont> FindFallbackFaceForChar(
+ mozilla::fontlist::Family* aFamily, uint32_t aCh, uint32_t aNextCh,
+ eFontPresentation aPresentation);
+
+ already_AddRefed<gfxFont> FindFallbackFaceForChar(
+ gfxFontFamily* aFamily, uint32_t aCh, uint32_t aNextCh,
+ eFontPresentation aPresentation);
+
+ // helper methods for looking up fonts
+
+ // lookup and add a font with a given name (i.e. *not* a generic!)
+ void AddPlatformFont(const nsACString& aName, bool aQuotedName,
+ nsTArray<FamilyAndGeneric>& aFamilyList);
+
+ // do style selection and add entries to list
+ void AddFamilyToFontList(gfxFontFamily* aFamily,
+ mozilla::StyleGenericFontFamily aGeneric);
+ void AddFamilyToFontList(mozilla::fontlist::Family* aFamily,
+ mozilla::StyleGenericFontFamily aGeneric);
+};
+
+// A "missing font recorder" is to be used during text-run creation to keep
+// a record of any scripts encountered for which font coverage was lacking;
+// when Flush() is called, it sends a notification that front-end code can use
+// to download fonts on demand (or whatever else it wants to do).
+
+#define GFX_MISSING_FONTS_NOTIFY_PREF "gfx.missing_fonts.notify"
+
+class gfxMissingFontRecorder {
+ public:
+ gfxMissingFontRecorder() {
+ MOZ_COUNT_CTOR(gfxMissingFontRecorder);
+ memset(&mMissingFonts, 0, sizeof(mMissingFonts));
+ }
+
+ ~gfxMissingFontRecorder() {
+#ifdef DEBUG
+ for (uint32_t i = 0; i < kNumScriptBitsWords; i++) {
+ NS_ASSERTION(mMissingFonts[i] == 0,
+ "failed to flush the missing-font recorder");
+ }
+#endif
+ MOZ_COUNT_DTOR(gfxMissingFontRecorder);
+ }
+
+ // record this script code in our mMissingFonts bitset
+ void RecordScript(mozilla::intl::Script aScriptCode) {
+ mMissingFonts[static_cast<uint32_t>(aScriptCode) >> 5] |=
+ (1 << (static_cast<uint32_t>(aScriptCode) & 0x1f));
+ }
+
+ // send a notification of any missing-scripts that have been
+ // recorded, and clear the mMissingFonts set for re-use
+ void Flush();
+
+ // forget any missing-scripts that have been recorded up to now;
+ // called before discarding a recorder we no longer care about
+ void Clear() { memset(&mMissingFonts, 0, sizeof(mMissingFonts)); }
+
+ private:
+ // Number of 32-bit words needed for the missing-script flags
+ static const uint32_t kNumScriptBitsWords =
+ ((static_cast<int>(mozilla::intl::Script::NUM_SCRIPT_CODES) + 31) / 32);
+ uint32_t mMissingFonts[kNumScriptBitsWords];
+};
+
+#endif
diff --git a/gfx/thebes/gfxTypes.h b/gfx/thebes/gfxTypes.h
new file mode 100644
index 0000000000..0e08c21547
--- /dev/null
+++ b/gfx/thebes/gfxTypes.h
@@ -0,0 +1,163 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_TYPES_H
+#define GFX_TYPES_H
+
+#include <stdint.h>
+#include "mozilla/TypedEnumBits.h"
+
+namespace mozilla {
+enum class StyleGenericFontFamily : uint32_t;
+}
+
+typedef struct _cairo_surface cairo_surface_t;
+typedef struct _cairo_user_data_key cairo_user_data_key_t;
+
+typedef void (*thebes_destroy_func_t)(void* data);
+
+/**
+ * Currently needs to be 'double' for Cairo compatibility. Could
+ * become 'float', perhaps, in some configurations.
+ */
+typedef double gfxFloat;
+
+/**
+ * Priority of a line break opportunity.
+ *
+ * eNoBreak The line has no break opportunities
+ * eWordWrapBreak The line has a break opportunity only within a word. With
+ * overflow-wrap|word-wrap: break-word we will break at this
+ * point only if there are no other break opportunities in the
+ * line.
+ * eNormalBreak The line has a break opportunity determined by the standard
+ * line-breaking algorithm.
+ *
+ * Future expansion: split eNormalBreak into multiple priorities, e.g.
+ * punctuation break and whitespace break (bug 389710).
+ * As and when we implement it, text-wrap: unrestricted will
+ * mean that priorities are ignored and all line-break
+ * opportunities are equal.
+ *
+ * @see gfxTextRun::BreakAndMeasureText
+ * @see nsLineLayout::NotifyOptionalBreakPosition
+ */
+enum class gfxBreakPriority { eNoBreak = 0, eWordWrapBreak, eNormalBreak };
+
+enum class gfxSurfaceType {
+ Image,
+ PDF,
+ PS,
+ Xlib,
+ Xcb,
+ Glitz, // unused, but needed for cairo parity
+ Quartz,
+ Win32,
+ BeOS,
+ DirectFB, // unused, but needed for cairo parity
+ SVG,
+ OS2,
+ Win32Printing,
+ QuartzImage,
+ Script,
+ QPainter,
+ Recording,
+ VG,
+ GL,
+ DRM,
+ Tee,
+ XML,
+ Skia,
+ Subsurface,
+ Max
+};
+
+enum class gfxContentType {
+ COLOR = 0x1000,
+ ALPHA = 0x2000,
+ COLOR_ALPHA = 0x3000,
+ SENTINEL = 0xffff
+};
+
+enum class gfxAlphaType {
+ Opaque,
+ Premult,
+ NonPremult,
+};
+
+/**
+ * Type used to record how a particular font is selected during the font-
+ * matching process, so that this can be exposed to the Inspector.
+ */
+struct FontMatchType {
+ enum class Kind : uint8_t {
+ kUnspecified = 0,
+ kFontGroup = 1,
+ kPrefsFallback = 1 << 1,
+ kSystemFallback = 1 << 2,
+ };
+
+ inline FontMatchType& operator|=(const FontMatchType& aOther);
+
+ bool operator==(const FontMatchType& aOther) const {
+ return kind == aOther.kind && generic == aOther.generic;
+ }
+
+ bool operator!=(const FontMatchType& aOther) const {
+ return !(*this == aOther);
+ }
+
+ MOZ_IMPLICIT FontMatchType() = default;
+ MOZ_IMPLICIT FontMatchType(Kind aKind) : kind(aKind) {}
+ FontMatchType(Kind aKind, mozilla::StyleGenericFontFamily aGeneric)
+ : kind(aKind), generic(aGeneric) {}
+
+ Kind kind = static_cast<Kind>(0);
+ // Using 0 to avoid pulling ServoStyleConsts.h everywhere.
+ mozilla::StyleGenericFontFamily generic = mozilla::StyleGenericFontFamily(0);
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(FontMatchType::Kind)
+
+FontMatchType& FontMatchType::operator|=(const FontMatchType& aOther) {
+ kind |= aOther.kind;
+ // We only keep track of one generic.
+ if (generic != aOther.generic) {
+ generic = mozilla::StyleGenericFontFamily(0);
+ }
+ return *this;
+}
+
+// Installation status (base system / langpack / user-installed) may determine
+// whether the font is visible to CSS font-family or src:local() lookups.
+// (Exactly what these mean and how accurate they are may be vary across
+// platforms -- e.g. on Linux there is no clear "base" set of fonts.)
+enum class FontVisibility : uint8_t {
+ Unknown = 0, // No categorization of families available on this system
+ Base = 1, // Standard part of the base OS installation
+ LangPack = 2, // From an optional OS component such as language support
+ User = 3, // User-installed font (or installed by another app, etc)
+ Hidden = 4, // Internal system font, should never exposed to users
+ Webfont = 5, // Webfont defined by @font-face
+ Count = 6, // Count of values, for IPC serialization
+};
+
+struct HwStretchingSupport {
+ uint32_t mBoth;
+ uint32_t mWindowOnly;
+ uint32_t mFullScreenOnly;
+ uint32_t mNone;
+ uint32_t mError;
+
+ HwStretchingSupport()
+ : mBoth(0), mWindowOnly(0), mFullScreenOnly(0), mNone(0), mError(0) {}
+
+ bool IsFullySupported() const {
+ return mBoth > 0 && mWindowOnly == 0 && mFullScreenOnly == 0 &&
+ mNone == 0 && mError == 0;
+ }
+};
+
+#endif /* GFX_TYPES_H */
diff --git a/gfx/thebes/gfxUserFontSet.cpp b/gfx/thebes/gfxUserFontSet.cpp
new file mode 100644
index 0000000000..1d83a34977
--- /dev/null
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -0,0 +1,1408 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Logging.h"
+
+#include "gfxUserFontSet.h"
+#include "gfxPlatform.h"
+#include "gfxFontConstants.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/gfx/2D.h"
+#include "gfxPlatformFontList.h"
+#include "mozilla/PostTraversalTask.h"
+#include "gfxOTSUtils.h"
+#include "nsIFontLoadCompleteCallback.h"
+#include "nsProxyRelease.h"
+#include "nsTHashSet.h"
+
+using namespace mozilla;
+
+mozilla::LogModule* gfxUserFontSet::GetUserFontsLog() {
+ static LazyLogModule sLog("userfonts");
+ return sLog;
+}
+
+#define LOG(args) \
+ MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, args)
+#define LOG_ENABLED() \
+ MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug)
+
+static Atomic<uint64_t> sFontSetGeneration(0);
+
+gfxUserFontEntry::gfxUserFontEntry(nsTArray<gfxFontFaceSrc>&& aFontFaceSrcList,
+ gfxUserFontAttributes&& aAttr)
+ : gfxFontEntry("userfont"_ns),
+ mUserFontLoadState(STATUS_NOT_LOADED),
+ mFontDataLoadingState(NOT_LOADING),
+ mSeenLocalSource(false),
+ mUnsupportedFormat(false),
+ mFontDisplay(aAttr.mFontDisplay),
+ mLoader(nullptr) {
+ mIsUserFontContainer = true;
+ mSrcList = std::move(aFontFaceSrcList);
+ mCurrentSrcIndex = 0;
+ mWeightRange = aAttr.mWeight;
+ mStretchRange = aAttr.mStretch;
+ mStyleRange = aAttr.mStyle;
+ mFeatureSettings = std::move(aAttr.mFeatureSettings);
+ mVariationSettings = std::move(aAttr.mVariationSettings);
+ mLanguageOverride = aAttr.mLanguageOverride;
+ SetUnicodeRangeMap(std::move(aAttr.mUnicodeRanges));
+ mRangeFlags = aAttr.mRangeFlags;
+ mAscentOverride = aAttr.mAscentOverride;
+ mDescentOverride = aAttr.mDescentOverride;
+ mLineGapOverride = aAttr.mLineGapOverride;
+ mSizeAdjust = aAttr.mSizeAdjust;
+ mFamilyName = aAttr.mFamilyName;
+}
+
+void gfxUserFontEntry::UpdateAttributes(gfxUserFontAttributes&& aAttr) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Remove the entry from the user font cache, if present there, as the cache
+ // key may no longer be correct with the new attributes.
+ gfxUserFontSet::UserFontCache::ForgetFont(this);
+
+ mFontDisplay = aAttr.mFontDisplay;
+ mWeightRange = aAttr.mWeight;
+ mStretchRange = aAttr.mStretch;
+ mStyleRange = aAttr.mStyle;
+ mFeatureSettings = std::move(aAttr.mFeatureSettings);
+ mVariationSettings = std::move(aAttr.mVariationSettings);
+ mLanguageOverride = aAttr.mLanguageOverride;
+ SetUnicodeRangeMap(std::move(aAttr.mUnicodeRanges));
+ mRangeFlags = aAttr.mRangeFlags;
+ mAscentOverride = aAttr.mAscentOverride;
+ mDescentOverride = aAttr.mDescentOverride;
+ mLineGapOverride = aAttr.mLineGapOverride;
+ mSizeAdjust = aAttr.mSizeAdjust;
+}
+
+gfxUserFontEntry::~gfxUserFontEntry() {
+ // Assert that we don't drop any gfxUserFontEntry objects during a Servo
+ // traversal, since PostTraversalTask objects can hold raw pointers to
+ // gfxUserFontEntry objects.
+ MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal());
+}
+
+bool gfxUserFontEntry::Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
+ const gfxUserFontAttributes& aAttr) {
+ return mWeightRange == aAttr.mWeight && mStretchRange == aAttr.mStretch &&
+ mStyleRange == aAttr.mStyle &&
+ mFeatureSettings == aAttr.mFeatureSettings &&
+ mVariationSettings == aAttr.mVariationSettings &&
+ mLanguageOverride == aAttr.mLanguageOverride &&
+ mSrcList == aFontFaceSrcList && mFontDisplay == aAttr.mFontDisplay &&
+ mRangeFlags == aAttr.mRangeFlags &&
+ mAscentOverride == aAttr.mAscentOverride &&
+ mDescentOverride == aAttr.mDescentOverride &&
+ mLineGapOverride == aAttr.mLineGapOverride &&
+ mSizeAdjust == aAttr.mSizeAdjust &&
+ ((!aAttr.mUnicodeRanges && !mCharacterMap) ||
+ (aAttr.mUnicodeRanges && mCharacterMap &&
+ GetCharacterMap()->Equals(aAttr.mUnicodeRanges)));
+}
+
+gfxFont* gfxUserFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle) {
+ MOZ_ASSERT_UNREACHABLE(
+ "should only be creating a gfxFont"
+ " with an actual platform font entry");
+
+ // userfont entry is a container, can't create font from the container
+ return nullptr;
+}
+
+class MOZ_STACK_CLASS gfxOTSMessageContext : public gfxOTSContext {
+ public:
+ virtual ~gfxOTSMessageContext() {
+ MOZ_ASSERT(mMessages.IsEmpty(), "should have called TakeMessages");
+ }
+
+ virtual void Message(int level, const char* format,
+ ...) MSGFUNC_FMT_ATTR override {
+ va_list va;
+ va_start(va, format);
+
+ nsCString msg;
+ msg.AppendVprintf(format, va);
+
+ va_end(va);
+
+ if (level > 0) {
+ // For warnings (rather than errors that cause the font to fail),
+ // we only report the first instance of any given message.
+ if (!mWarningsIssued.EnsureInserted(msg)) {
+ return;
+ }
+ }
+
+ mMessages.AppendElement(gfxUserFontEntry::OTSMessage{msg, level});
+ }
+
+ bool Process(ots::OTSStream* aOutput, const uint8_t* aInput, size_t aLength,
+ nsTArray<gfxUserFontEntry::OTSMessage>& aMessages) {
+ bool ok = ots::OTSContext::Process(aOutput, aInput, aLength);
+ aMessages = TakeMessages();
+ return ok;
+ }
+
+ nsTArray<gfxUserFontEntry::OTSMessage>&& TakeMessages() {
+ return std::move(mMessages);
+ }
+
+ private:
+ nsTHashSet<nsCString> mWarningsIssued;
+ nsTArray<gfxUserFontEntry::OTSMessage> mMessages;
+};
+
+// Call the OTS library to sanitize an sfnt before attempting to use it.
+// Returns a newly-allocated block, or nullptr in case of fatal errors.
+const uint8_t* gfxUserFontEntry::SanitizeOpenTypeData(
+ const uint8_t* aData, uint32_t aLength, uint32_t& aSanitaryLength,
+ gfxUserFontType& aFontType, nsTArray<OTSMessage>& aMessages) {
+ aFontType = gfxFontUtils::DetermineFontDataType(aData, aLength);
+ Telemetry::Accumulate(Telemetry::WEBFONT_FONTTYPE, uint32_t(aFontType));
+
+ size_t lengthHint = gfxOTSContext::GuessSanitizedFontSize(aLength, aFontType);
+ if (!lengthHint) {
+ aSanitaryLength = 0;
+ return nullptr;
+ }
+
+ gfxOTSExpandingMemoryStream<gfxOTSMozAlloc> output(lengthHint);
+
+ gfxOTSMessageContext otsContext;
+ if (!otsContext.Process(&output, aData, aLength, aMessages)) {
+ // Failed to decode/sanitize the font, so discard it.
+ aSanitaryLength = 0;
+ return nullptr;
+ }
+
+ aSanitaryLength = output.Tell();
+ return static_cast<const uint8_t*>(output.forget());
+}
+
+void gfxUserFontEntry::StoreUserFontData(gfxFontEntry* aFontEntry,
+ uint32_t aSrcIndex, bool aPrivate,
+ const nsACString& aOriginalName,
+ FallibleTArray<uint8_t>* aMetadata,
+ uint32_t aMetaOrigLen,
+ uint8_t aCompression) {
+ if (!aFontEntry->mUserFontData) {
+ aFontEntry->mUserFontData = MakeUnique<gfxUserFontData>();
+ }
+ gfxUserFontData* userFontData = aFontEntry->mUserFontData.get();
+ userFontData->mSrcIndex = aSrcIndex;
+ const gfxFontFaceSrc& src = mSrcList[aSrcIndex];
+ switch (src.mSourceType) {
+ case gfxFontFaceSrc::eSourceType_Local:
+ userFontData->mLocalName = src.mLocalName;
+ break;
+ case gfxFontFaceSrc::eSourceType_URL:
+ userFontData->mURI = src.mURI;
+ userFontData->mPrincipal = mPrincipal;
+ break;
+ case gfxFontFaceSrc::eSourceType_Buffer:
+ userFontData->mIsBuffer = true;
+ break;
+ }
+ userFontData->mPrivate = aPrivate;
+ userFontData->mTechFlags = src.mTechFlags;
+ userFontData->mFormatHint = src.mFormatHint;
+ userFontData->mRealName = aOriginalName;
+ if (aMetadata) {
+ userFontData->mMetadata = std::move(*aMetadata);
+ userFontData->mMetaOrigLen = aMetaOrigLen;
+ userFontData->mCompression = aCompression;
+ }
+}
+
+size_t gfxUserFontData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) +
+ mMetadata.ShallowSizeOfExcludingThis(aMallocSizeOf) +
+ mLocalName.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mRealName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ // Not counting mURI and mPrincipal, as those will be shared.
+}
+
+/*virtual*/
+gfxUserFontFamily::~gfxUserFontFamily() {
+ // Should not be dropped by stylo
+ MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal());
+}
+
+already_AddRefed<gfxFontSrcPrincipal> gfxFontFaceSrc::LoadPrincipal(
+ const gfxUserFontSet& aFontSet) const {
+ MOZ_ASSERT(mSourceType == eSourceType_URL);
+ if (mUseOriginPrincipal) {
+ MOZ_ASSERT(mOriginPrincipal);
+ return RefPtr{mOriginPrincipal}.forget();
+ }
+ return aFontSet.GetStandardFontLoadPrincipal();
+}
+
+void gfxUserFontEntry::GetFamilyNameAndURIForLogging(uint32_t aSrcIndex,
+ nsACString& aFamilyName,
+ nsACString& aURI) {
+ aFamilyName = mFamilyName;
+
+ aURI.Truncate();
+ if (aSrcIndex >= mSrcList.Length()) {
+ aURI.AppendLiteral("(end of source list)");
+ } else {
+ if (mSrcList[aSrcIndex].mURI) {
+ mSrcList[aSrcIndex].mURI->GetSpec(aURI);
+ // If the source URI was very long, elide the middle of it.
+ // In principle, the byte-oriented chopping here could leave us
+ // with partial UTF-8 characters at the point where we cut it,
+ // but it really doesn't matter as this is just for logging.
+ const uint32_t kMaxURILengthForLogging = 256;
+ // UTF-8 ellipsis, with spaces to allow additional wrap opportunities
+ // in the resulting log message
+ const char kEllipsis[] = {' ', '\xE2', '\x80', '\xA6', ' '};
+ if (aURI.Length() > kMaxURILengthForLogging) {
+ aURI.Replace(kMaxURILengthForLogging / 2,
+ aURI.Length() - kMaxURILengthForLogging, kEllipsis,
+ ArrayLength(kEllipsis));
+ }
+ } else {
+ aURI.AppendLiteral("(invalid URI)");
+ }
+ }
+}
+
+struct WOFFHeader {
+ AutoSwap_PRUint32 signature;
+ AutoSwap_PRUint32 flavor;
+ AutoSwap_PRUint32 length;
+ AutoSwap_PRUint16 numTables;
+ AutoSwap_PRUint16 reserved;
+ AutoSwap_PRUint32 totalSfntSize;
+ AutoSwap_PRUint16 majorVersion;
+ AutoSwap_PRUint16 minorVersion;
+ AutoSwap_PRUint32 metaOffset;
+ AutoSwap_PRUint32 metaCompLen;
+ AutoSwap_PRUint32 metaOrigLen;
+ AutoSwap_PRUint32 privOffset;
+ AutoSwap_PRUint32 privLen;
+};
+
+struct WOFF2Header {
+ AutoSwap_PRUint32 signature;
+ AutoSwap_PRUint32 flavor;
+ AutoSwap_PRUint32 length;
+ AutoSwap_PRUint16 numTables;
+ AutoSwap_PRUint16 reserved;
+ AutoSwap_PRUint32 totalSfntSize;
+ AutoSwap_PRUint32 totalCompressedSize;
+ AutoSwap_PRUint16 majorVersion;
+ AutoSwap_PRUint16 minorVersion;
+ AutoSwap_PRUint32 metaOffset;
+ AutoSwap_PRUint32 metaCompLen;
+ AutoSwap_PRUint32 metaOrigLen;
+ AutoSwap_PRUint32 privOffset;
+ AutoSwap_PRUint32 privLen;
+};
+
+template <typename HeaderT>
+void CopyWOFFMetadata(const uint8_t* aFontData, uint32_t aLength,
+ FallibleTArray<uint8_t>* aMetadata,
+ uint32_t* aMetaOrigLen) {
+ // This function may be called with arbitrary, unvalidated "font" data
+ // from @font-face, so it needs to be careful to bounds-check, etc.,
+ // before trying to read anything.
+ // This just saves a copy of the compressed data block; it does NOT check
+ // that the block can be successfully decompressed, or that it contains
+ // well-formed/valid XML metadata.
+ if (aLength < sizeof(HeaderT)) {
+ return;
+ }
+ const HeaderT* woff = reinterpret_cast<const HeaderT*>(aFontData);
+ uint32_t metaOffset = woff->metaOffset;
+ uint32_t metaCompLen = woff->metaCompLen;
+ if (!metaOffset || !metaCompLen || !woff->metaOrigLen) {
+ return;
+ }
+ if (metaOffset >= aLength || metaCompLen > aLength - metaOffset) {
+ return;
+ }
+ if (!aMetadata->SetLength(woff->metaCompLen, fallible)) {
+ return;
+ }
+ memcpy(aMetadata->Elements(), aFontData + metaOffset, metaCompLen);
+ *aMetaOrigLen = woff->metaOrigLen;
+}
+
+void gfxUserFontEntry::LoadNextSrc() {
+ NS_ASSERTION(mCurrentSrcIndex < mSrcList.Length(),
+ "already at the end of the src list for user font");
+ NS_ASSERTION((mUserFontLoadState == STATUS_NOT_LOADED ||
+ mUserFontLoadState == STATUS_LOAD_PENDING ||
+ mUserFontLoadState == STATUS_LOADING) &&
+ mFontDataLoadingState < LOADING_FAILED,
+ "attempting to load a font that has either completed or failed");
+
+ if (mUserFontLoadState == STATUS_NOT_LOADED) {
+ SetLoadState(STATUS_LOADING);
+ mFontDataLoadingState = LOADING_STARTED;
+ mUnsupportedFormat = false;
+ } else {
+ // we were already loading; move to the next source,
+ // but don't reset state - if we've already timed out,
+ // that counts against the new download
+ mCurrentSrcIndex++;
+ }
+
+ DoLoadNextSrc(false);
+}
+
+void gfxUserFontEntry::ContinueLoad() {
+ MOZ_ASSERT(mUserFontLoadState == STATUS_LOAD_PENDING);
+ MOZ_ASSERT(mSrcList[mCurrentSrcIndex].mSourceType ==
+ gfxFontFaceSrc::eSourceType_URL);
+
+ SetLoadState(STATUS_LOADING);
+ DoLoadNextSrc(true);
+ if (LoadState() != STATUS_LOADING) {
+ MOZ_ASSERT(mUserFontLoadState != STATUS_LOAD_PENDING,
+ "Not in parallel traversal, shouldn't get LOAD_PENDING again");
+ // Loading is synchronously finished (loaded from cache or failed). We
+ // need to increment the generation so that we flush the style data to
+ // use the new loaded font face.
+ // Without parallel traversal, we would simply get the right font data
+ // after the first call to DoLoadNextSrc() in this case, so we don't need
+ // to touch the generation to trigger another restyle.
+ // XXX We may want to return synchronously in parallel traversal in those
+ // cases as well if possible, so that we don't have an additional restyle.
+ // That doesn't work currently because Document::GetDocShell (called from
+ // FontFaceSet::CheckFontLoad) dereferences a weak pointer, which is not
+ // allowed in parallel traversal.
+ IncrementGeneration();
+ }
+}
+
+static bool IgnorePrincipal(gfxFontSrcURI* aURI) {
+ return aURI->InheritsSecurityContext();
+}
+
+void gfxUserFontEntry::DoLoadNextSrc(bool aForceAsync) {
+ RefPtr<gfxUserFontSet> fontSet = GetUserFontSet();
+ if (NS_WARN_IF(!fontSet)) {
+ LOG(("userfonts (%p) failed expired font set for (%s)\n", fontSet.get(),
+ mFamilyName.get()));
+ mFontDataLoadingState = LOADING_FAILED;
+ SetLoadState(STATUS_FAILED);
+ return;
+ }
+
+ uint32_t numSrc = mSrcList.Length();
+
+ // load each src entry in turn, until a local face is found
+ // or a download begins successfully
+ while (mCurrentSrcIndex < numSrc) {
+ gfxFontFaceSrc& currSrc = mSrcList[mCurrentSrcIndex];
+
+ // src local ==> lookup and load immediately
+
+ if (currSrc.mSourceType == gfxFontFaceSrc::eSourceType_Local) {
+ // Don't look up local fonts if the font whitelist is being used.
+ gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
+ gfxFontEntry* fe = nullptr;
+ if (!pfl->IsFontFamilyWhitelistActive()) {
+ fe = gfxPlatform::GetPlatform()->LookupLocalFont(
+ fontSet->GetPresContext(), currSrc.mLocalName, Weight(), Stretch(),
+ SlantStyle());
+ // Note that we've attempted a local lookup, even if it failed,
+ // as this means we are dependent on any updates to the font list.
+ mSeenLocalSource = true;
+ nsTArray<RefPtr<gfxUserFontSet>> fontSets;
+ GetUserFontSets(fontSets);
+ for (gfxUserFontSet* fontSet : fontSets) {
+ // We need to note on each gfxUserFontSet that contains the user
+ // font entry that we used a local() rule.
+ fontSet->SetLocalRulesUsed();
+ }
+ }
+ if (fe) {
+ LOG(("userfonts (%p) [src %d] loaded local: (%s) for (%s) gen: %8.8x\n",
+ fontSet.get(), mCurrentSrcIndex, currSrc.mLocalName.get(),
+ mFamilyName.get(), uint32_t(fontSet->mGeneration)));
+ fe->mFeatureSettings.AppendElements(mFeatureSettings);
+ fe->mVariationSettings.AppendElements(mVariationSettings);
+ fe->mLanguageOverride = mLanguageOverride;
+ fe->mFamilyName = mFamilyName;
+ fe->mRangeFlags = mRangeFlags;
+ fe->mAscentOverride = mAscentOverride;
+ fe->mDescentOverride = mDescentOverride;
+ fe->mLineGapOverride = mLineGapOverride;
+ fe->mSizeAdjust = mSizeAdjust;
+ // For src:local(), we don't care whether the request is from
+ // a private window as there's no issue of caching resources;
+ // local fonts are just available all the time.
+ StoreUserFontData(fe, mCurrentSrcIndex, false, nsCString(), nullptr, 0,
+ gfxUserFontData::kUnknownCompression);
+ mPlatformFontEntry = fe;
+ SetLoadState(STATUS_LOADED);
+ Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE,
+ currSrc.mSourceType + 1);
+ return;
+ } else {
+ LOG(("userfonts (%p) [src %d] failed local: (%s) for (%s)\n",
+ fontSet.get(), mCurrentSrcIndex, currSrc.mLocalName.get(),
+ mFamilyName.get()));
+ }
+ }
+
+ // src url ==> start the load process
+ else if (currSrc.mSourceType == gfxFontFaceSrc::eSourceType_URL) {
+ if (gfxPlatform::GetPlatform()->IsFontFormatSupported(
+ currSrc.mFormatHint, currSrc.mTechFlags)) {
+ if (ServoStyleSet* set = gfxFontUtils::CurrentServoStyleSet()) {
+ // Only support style worker threads synchronously getting
+ // entries from the font cache when it's not a data: URI
+ // @font-face that came from UA or user sheets, since we
+ // were not able to call IsFontLoadAllowed ahead of time
+ // for these entries.
+ if (currSrc.mUseOriginPrincipal && IgnorePrincipal(currSrc.mURI)) {
+ set->AppendTask(PostTraversalTask::LoadFontEntry(this));
+ SetLoadState(STATUS_LOAD_PENDING);
+ return;
+ }
+ }
+
+ // see if we have an existing entry for this source
+ gfxFontEntry* fe =
+ gfxUserFontSet::UserFontCache::GetFont(currSrc, *this);
+ if (fe) {
+ mPlatformFontEntry = fe;
+ SetLoadState(STATUS_LOADED);
+ if (LOG_ENABLED()) {
+ LOG(
+ ("userfonts (%p) [src %d] "
+ "loaded uri from cache: (%s) for (%s)\n",
+ fontSet.get(), mCurrentSrcIndex,
+ currSrc.mURI->GetSpecOrDefault().get(), mFamilyName.get()));
+ }
+ return;
+ }
+
+ if (ServoStyleSet* set = gfxFontUtils::CurrentServoStyleSet()) {
+ // If we need to start a font load and we're on a style
+ // worker thread, we have to defer it.
+ set->AppendTask(PostTraversalTask::LoadFontEntry(this));
+ SetLoadState(STATUS_LOAD_PENDING);
+ return;
+ }
+
+ // record the principal we should use for the load for use when
+ // creating a channel and when caching the loaded entry.
+ mPrincipal = currSrc.LoadPrincipal(*fontSet);
+
+ bool loadDoesntSpin = !aForceAsync && currSrc.mURI->SyncLoadIsOK();
+
+ if (loadDoesntSpin) {
+ uint8_t* buffer = nullptr;
+ uint32_t bufferLength = 0;
+
+ // sync load font immediately
+ nsresult rv =
+ fontSet->SyncLoadFontData(this, &currSrc, buffer, bufferLength);
+
+ if (NS_SUCCEEDED(rv) &&
+ LoadPlatformFontSync(mCurrentSrcIndex, buffer, bufferLength)) {
+ SetLoadState(STATUS_LOADED);
+ Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE,
+ currSrc.mSourceType + 1);
+ return;
+ } else {
+ fontSet->LogMessage(this, mCurrentSrcIndex, "font load failed",
+ nsIScriptError::errorFlag, rv);
+ }
+
+ } else {
+ // otherwise load font async
+ nsresult rv = fontSet->StartLoad(this, mCurrentSrcIndex);
+ bool loadOK = NS_SUCCEEDED(rv);
+
+ if (loadOK) {
+ if (LOG_ENABLED()) {
+ LOG(("userfonts (%p) [src %d] loading uri: (%s) for (%s)\n",
+ fontSet.get(), mCurrentSrcIndex,
+ currSrc.mURI->GetSpecOrDefault().get(), mFamilyName.get()));
+ }
+ return;
+ } else {
+ fontSet->LogMessage(this, mCurrentSrcIndex,
+ "failed to start download",
+ nsIScriptError::errorFlag, rv);
+ }
+ }
+ } else {
+ // We don't log a warning to the web console yet,
+ // as another source may load successfully
+ mUnsupportedFormat = true;
+ }
+ }
+
+ // FontFace buffer ==> load immediately
+
+ else {
+ MOZ_ASSERT(currSrc.mSourceType == gfxFontFaceSrc::eSourceType_Buffer);
+
+ uint8_t* buffer = nullptr;
+ uint32_t bufferLength = 0;
+
+ // sync load font immediately
+ currSrc.mBuffer->TakeBuffer(buffer, bufferLength);
+ if (buffer &&
+ LoadPlatformFontSync(mCurrentSrcIndex, buffer, bufferLength)) {
+ // LoadPlatformFontSync takes ownership of the buffer, so no need
+ // to free it here.
+ SetLoadState(STATUS_LOADED);
+ Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE,
+ currSrc.mSourceType + 1);
+ return;
+ } else {
+ fontSet->LogMessage(this, mCurrentSrcIndex, "font load failed",
+ nsIScriptError::errorFlag);
+ }
+ }
+
+ mCurrentSrcIndex++;
+ }
+
+ if (mUnsupportedFormat) {
+ fontSet->LogMessage(this, mCurrentSrcIndex, "no supported format found",
+ nsIScriptError::warningFlag);
+ }
+
+ // all src's failed; mark this entry as unusable (so fallback will occur)
+ LOG(("userfonts (%p) failed all src for (%s)\n", fontSet.get(),
+ mFamilyName.get()));
+ mFontDataLoadingState = LOADING_FAILED;
+ SetLoadState(STATUS_FAILED);
+}
+
+void gfxUserFontEntry::SetLoadState(UserFontLoadState aLoadState) {
+ mUserFontLoadState = aLoadState;
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(UserFontMallocSizeOfOnAlloc)
+
+bool gfxUserFontEntry::LoadPlatformFontSync(uint32_t aSrcIndex,
+ const uint8_t* aFontData,
+ uint32_t aLength) {
+ AUTO_PROFILER_LABEL("gfxUserFontEntry::LoadPlatformFontSync", OTHER);
+ NS_ASSERTION((mUserFontLoadState == STATUS_NOT_LOADED ||
+ mUserFontLoadState == STATUS_LOAD_PENDING ||
+ mUserFontLoadState == STATUS_LOADING) &&
+ mFontDataLoadingState < LOADING_FAILED,
+ "attempting to load a font that has either completed or failed");
+
+ // Unwrap/decompress/sanitize or otherwise munge the downloaded data
+ // to make a usable sfnt structure.
+
+ // Call the OTS sanitizer; this will also decode WOFF to sfnt
+ // if necessary. The original data in aFontData is left unchanged.
+ uint32_t sanitaryLen;
+ gfxUserFontType fontType;
+ nsTArray<OTSMessage> messages;
+ const uint8_t* sanitaryData =
+ SanitizeOpenTypeData(aFontData, aLength, sanitaryLen, fontType, messages);
+
+ return LoadPlatformFont(aSrcIndex, aFontData, aLength, fontType, sanitaryData,
+ sanitaryLen, std::move(messages));
+}
+
+void gfxUserFontEntry::StartPlatformFontLoadOnBackgroundThread(
+ uint32_t aSrcIndex, const uint8_t* aFontData, uint32_t aLength,
+ nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> aCallback) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ uint32_t sanitaryLen;
+ gfxUserFontType fontType;
+ nsTArray<OTSMessage> messages;
+ const uint8_t* sanitaryData =
+ SanitizeOpenTypeData(aFontData, aLength, sanitaryLen, fontType, messages);
+
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod<uint32_t, const uint8_t*, uint32_t, gfxUserFontType,
+ const uint8_t*, uint32_t, nsTArray<OTSMessage>&&,
+ nsMainThreadPtrHandle<nsIFontLoadCompleteCallback>>(
+ "gfxUserFontEntry::ContinuePlatformFontLoadOnMainThread", this,
+ &gfxUserFontEntry::ContinuePlatformFontLoadOnMainThread, aSrcIndex,
+ aFontData, aLength, fontType, sanitaryData, sanitaryLen,
+ std::move(messages), aCallback);
+ NS_DispatchToMainThread(event.forget());
+}
+
+bool gfxUserFontEntry::LoadPlatformFont(uint32_t aSrcIndex,
+ const uint8_t* aOriginalFontData,
+ uint32_t aOriginalLength,
+ gfxUserFontType aFontType,
+ const uint8_t* aSanitizedFontData,
+ uint32_t aSanitizedLength,
+ nsTArray<OTSMessage>&& aMessages) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<gfxUserFontSet> fontSet = GetUserFontSet();
+ if (NS_WARN_IF(!fontSet)) {
+ free((void*)aOriginalFontData);
+ return false;
+ }
+
+ for (const auto& msg : aMessages) {
+ fontSet->LogMessage(this, aSrcIndex, msg.mMessage.get(),
+ msg.mLevel > 0 ? nsIScriptError::warningFlag
+ : nsIScriptError::errorFlag);
+ }
+
+ if (!aSanitizedFontData) {
+ fontSet->LogMessage(this, aSrcIndex, "rejected by sanitizer");
+ } else {
+ // Check whether aSanitizedFontData is a known OpenType format; it might be
+ // a TrueType Collection, which OTS would accept but we don't yet
+ // know how to handle. If so, discard.
+ if (gfxFontUtils::DetermineFontDataType(
+ aSanitizedFontData, aSanitizedLength) != GFX_USERFONT_OPENTYPE) {
+ fontSet->LogMessage(this, aSrcIndex, "not a supported OpenType format");
+ free((void*)aSanitizedFontData);
+ aSanitizedFontData = nullptr;
+ }
+ }
+
+ // Because platform font activation code may replace the name table
+ // in the font with a synthetic one, we save the original name so that
+ // it can be reported via the InspectorUtils API.
+ nsAutoCString originalFullName;
+
+ gfxFontEntry* fe = nullptr;
+ uint32_t fontCompressionRatio = 0;
+ size_t computedSize = 0;
+
+ if (aSanitizedFontData) {
+ if (aSanitizedLength) {
+ fontCompressionRatio =
+ uint32_t(100.0 * aOriginalLength / aSanitizedLength + 0.5);
+ if (aFontType == GFX_USERFONT_WOFF || aFontType == GFX_USERFONT_WOFF2) {
+ Telemetry::Accumulate(aFontType == GFX_USERFONT_WOFF
+ ? Telemetry::WEBFONT_COMPRESSION_WOFF
+ : Telemetry::WEBFONT_COMPRESSION_WOFF2,
+ fontCompressionRatio);
+ }
+ }
+
+ // The sanitizer ensures that we have a valid sfnt and a usable
+ // name table, so this should never fail unless we're out of
+ // memory, and GetFullNameFromSFNT is not directly exposed to
+ // arbitrary/malicious data from the web.
+ gfxFontUtils::GetFullNameFromSFNT(aSanitizedFontData, aSanitizedLength,
+ originalFullName);
+
+ // Record size for memory reporting purposes. We measure this now
+ // because by the time we potentially want to collect reports, this
+ // data block may have been handed off to opaque OS font APIs that
+ // don't allow us to retrieve or measure it directly.
+ // The *OnAlloc function will also tell DMD about this block, as the
+ // OS font code may hold on to it for an extended period.
+ computedSize = UserFontMallocSizeOfOnAlloc(aSanitizedFontData);
+
+ // Here ownership of aSanitizedFontData is passed to the platform,
+ // which will delete it when no longer required
+ fe = gfxPlatform::GetPlatform()->MakePlatformFont(
+ mName, Weight(), Stretch(), SlantStyle(), aSanitizedFontData,
+ aSanitizedLength);
+ if (!fe) {
+ fontSet->LogMessage(this, aSrcIndex, "not usable by platform");
+ }
+ }
+
+ if (fe) {
+ fe->mComputedSizeOfUserFont = computedSize;
+
+ // Save a copy of the metadata block (if present) for InspectorUtils
+ // to use if required. Ownership of the metadata block will be passed
+ // to the gfxUserFontData record below.
+ FallibleTArray<uint8_t> metadata;
+ uint32_t metaOrigLen = 0;
+ uint8_t compression = gfxUserFontData::kUnknownCompression;
+ if (aFontType == GFX_USERFONT_WOFF) {
+ CopyWOFFMetadata<WOFFHeader>(aOriginalFontData, aOriginalLength,
+ &metadata, &metaOrigLen);
+ compression = gfxUserFontData::kZlibCompression;
+ } else if (aFontType == GFX_USERFONT_WOFF2) {
+ CopyWOFFMetadata<WOFF2Header>(aOriginalFontData, aOriginalLength,
+ &metadata, &metaOrigLen);
+ compression = gfxUserFontData::kBrotliCompression;
+ }
+
+ // copy OpenType feature/language settings from the userfont entry to the
+ // newly-created font entry
+ fe->mFeatureSettings.AppendElements(mFeatureSettings);
+ fe->mVariationSettings.AppendElements(mVariationSettings);
+ fe->mLanguageOverride = mLanguageOverride;
+ fe->mFamilyName = mFamilyName;
+ fe->mRangeFlags = mRangeFlags;
+ fe->mAscentOverride = mAscentOverride;
+ fe->mDescentOverride = mDescentOverride;
+ fe->mLineGapOverride = mLineGapOverride;
+ fe->mSizeAdjust = mSizeAdjust;
+ StoreUserFontData(fe, aSrcIndex, fontSet->GetPrivateBrowsing(),
+ originalFullName, &metadata, metaOrigLen, compression);
+ if (LOG_ENABLED()) {
+ LOG((
+ "userfonts (%p) [src %d] loaded uri: (%s) for (%s) "
+ "(%p) gen: %8.8x compress: %d%%\n",
+ fontSet.get(), aSrcIndex,
+ mSrcList[aSrcIndex].mURI->GetSpecOrDefault().get(), mFamilyName.get(),
+ this, uint32_t(fontSet->mGeneration), fontCompressionRatio));
+ }
+ mPlatformFontEntry = fe;
+ SetLoadState(STATUS_LOADED);
+ gfxUserFontSet::UserFontCache::CacheFont(fe);
+ } else {
+ if (LOG_ENABLED()) {
+ LOG(
+ ("userfonts (%p) [src %d] failed uri: (%s) for (%s)"
+ " error making platform font\n",
+ fontSet.get(), aSrcIndex,
+ mSrcList[aSrcIndex].mURI->GetSpecOrDefault().get(),
+ mFamilyName.get()));
+ }
+ }
+
+ // The downloaded data can now be discarded; the font entry is using the
+ // sanitized copy
+ free((void*)aOriginalFontData);
+
+ return fe != nullptr;
+}
+
+void gfxUserFontEntry::Load() {
+ if (mUserFontLoadState == STATUS_NOT_LOADED) {
+ LoadNextSrc();
+ }
+}
+
+void gfxUserFontEntry::IncrementGeneration() {
+ nsTArray<RefPtr<gfxUserFontSet>> fontSets;
+ GetUserFontSets(fontSets);
+ for (gfxUserFontSet* fontSet : fontSets) {
+ fontSet->IncrementGeneration();
+ }
+}
+
+// This is called when a font download finishes.
+// Ownership of aFontData passes in here, and the font set must
+// ensure that it is eventually deleted via free().
+void gfxUserFontEntry::FontDataDownloadComplete(
+ uint32_t aSrcIndex, const uint8_t* aFontData, uint32_t aLength,
+ nsresult aDownloadStatus, nsIFontLoadCompleteCallback* aCallback) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // forget about the loader, as we no longer potentially need to cancel it
+ // if the entry is obsoleted
+ mLoader = nullptr;
+
+ // download successful, make platform font using font data
+ if (NS_SUCCEEDED(aDownloadStatus) &&
+ mFontDataLoadingState != LOADING_TIMED_OUT) {
+ if (StaticPrefs::gfx_downloadable_fonts_sanitize_omt()) {
+ LoadPlatformFontAsync(aSrcIndex, aFontData, aLength, aCallback);
+ } else {
+ bool loaded = LoadPlatformFontSync(aSrcIndex, aFontData, aLength);
+ aFontData = nullptr;
+ if (loaded) {
+ IncrementGeneration();
+ aCallback->FontLoadComplete();
+ } else {
+ FontLoadFailed(aCallback);
+ }
+ }
+ return;
+ }
+
+ RefPtr<gfxUserFontSet> fontSet = GetUserFontSet();
+ if (fontSet) {
+ // download failed or font-display timeout passed
+ if (mFontDataLoadingState == LOADING_TIMED_OUT) {
+ fontSet->LogMessage(this, aSrcIndex,
+ "font-display timeout, webfont not used",
+ nsIScriptError::infoFlag, aDownloadStatus);
+ } else {
+ fontSet->LogMessage(this, aSrcIndex, "download failed",
+ nsIScriptError::errorFlag, aDownloadStatus);
+ }
+ }
+
+ if (aFontData) {
+ free((void*)aFontData);
+ }
+
+ FontLoadFailed(aCallback);
+}
+
+void gfxUserFontEntry::LoadPlatformFontAsync(
+ uint32_t aSrcIndex, const uint8_t* aFontData, uint32_t aLength,
+ nsIFontLoadCompleteCallback* aCallback) {
+ nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> cb(
+ new nsMainThreadPtrHolder<nsIFontLoadCompleteCallback>("FontLoader",
+ aCallback));
+
+ // Do the OpenType sanitization over on the font loading thread. Once that is
+ // complete, we'll continue in ContinuePlatformFontLoadOnMainThread.
+ //
+ // We hold a strong reference to the gfxUserFontSet during this work, since
+ // the document might be closed while we are OMT, and release it at the end
+ // of ContinuePlatformFontLoadOnMainThread.
+ //
+ // If the set has already been freed, then the loading will fail when we
+ // resume on the main thread.
+
+ MOZ_ASSERT(!mLoadingFontSet);
+ mLoadingFontSet = GetUserFontSet();
+
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod<uint32_t, const uint8_t*, uint32_t,
+ nsMainThreadPtrHandle<nsIFontLoadCompleteCallback>>(
+ "gfxUserFontEntry::StartPlatformFontLoadOnBackgroundThread", this,
+ &gfxUserFontEntry::StartPlatformFontLoadOnBackgroundThread, aSrcIndex,
+ aFontData, aLength, cb);
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchBackgroundTask(event.forget()));
+}
+
+void gfxUserFontEntry::ContinuePlatformFontLoadOnMainThread(
+ uint32_t aSrcIndex, const uint8_t* aOriginalFontData,
+ uint32_t aOriginalLength, gfxUserFontType aFontType,
+ const uint8_t* aSanitizedFontData, uint32_t aSanitizedLength,
+ nsTArray<OTSMessage>&& aMessages,
+ nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> aCallback) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ bool loaded = LoadPlatformFont(aSrcIndex, aOriginalFontData, aOriginalLength,
+ aFontType, aSanitizedFontData,
+ aSanitizedLength, std::move(aMessages));
+ aOriginalFontData = nullptr;
+ aSanitizedFontData = nullptr;
+
+ if (loaded) {
+ IncrementGeneration();
+ aCallback->FontLoadComplete();
+ } else {
+ FontLoadFailed(aCallback);
+ }
+
+ // Set in LoadPlatformFontAsync. If it is null, then the font set should have
+ // already been freed and we would not succeed in loading the font.
+ MOZ_ASSERT_IF(loaded, mLoadingFontSet);
+ mLoadingFontSet = nullptr;
+}
+
+void gfxUserFontEntry::FontLoadFailed(nsIFontLoadCompleteCallback* aCallback) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Error occurred. Make sure the FontFace's promise is rejected if the
+ // load timed out, or else load the next src.
+ if (mFontDataLoadingState == LOADING_TIMED_OUT) {
+ mFontDataLoadingState = LOADING_FAILED;
+ SetLoadState(STATUS_FAILED);
+ } else {
+ LoadNextSrc();
+ }
+
+ // We ignore the status returned by LoadNext();
+ // even if loading failed, we need to bump the font-set generation
+ // and return true in order to trigger reflow, so that fallback
+ // will be used where the text was "masked" by the pending download
+ IncrementGeneration();
+ aCallback->FontLoadComplete();
+}
+
+void gfxUserFontEntry::GetUserFontSets(
+ nsTArray<RefPtr<gfxUserFontSet>>& aResult) {
+ aResult.Clear();
+ RefPtr<gfxUserFontSet> fontSet = GetUserFontSet();
+ if (fontSet) {
+ aResult.AppendElement(std::move(fontSet));
+ }
+}
+
+gfxUserFontSet::gfxUserFontSet()
+ : mFontFamilies(4),
+ mRebuildGeneration(0),
+ mLocalRulesUsed(false),
+ mRebuildLocalRules(false),
+ mDownloadCount(0),
+ mDownloadSize(0) {
+ IncrementGeneration(true);
+ gfxPlatformFontList* fp = gfxPlatformFontList::PlatformFontList();
+ if (fp) {
+ fp->AddUserFontSet(this);
+ }
+}
+
+gfxUserFontSet::~gfxUserFontSet() { Destroy(); }
+
+void gfxUserFontSet::Destroy() {
+ gfxPlatformFontList* fp = gfxPlatformFontList::PlatformFontList();
+ if (fp) {
+ fp->RemoveUserFontSet(this);
+ }
+
+ mFontFamilies.Clear();
+}
+
+already_AddRefed<gfxUserFontEntry> gfxUserFontSet::FindOrCreateUserFontEntry(
+ nsTArray<gfxFontFaceSrc>&& aFontFaceSrcList,
+ gfxUserFontAttributes&& aAttr) {
+ RefPtr<gfxUserFontEntry> entry;
+
+ // If there's already a userfont entry in the family whose descriptors all
+ // match, we can just move it to the end of the list instead of adding a new
+ // face that will always "shadow" the old one.
+ // Note that we can't do this for platform font entries, even if the
+ // style descriptors match, as they might have had a different source list,
+ // but we no longer have the old source list available to check.
+ RefPtr<gfxUserFontFamily> family = LookupFamily(aAttr.mFamilyName);
+ if (family) {
+ entry = FindExistingUserFontEntry(family, aFontFaceSrcList, aAttr);
+ }
+
+ if (!entry) {
+ entry = CreateUserFontEntry(std::move(aFontFaceSrcList), std::move(aAttr));
+ }
+
+ return entry.forget();
+}
+
+gfxUserFontEntry* gfxUserFontSet::FindExistingUserFontEntry(
+ gfxUserFontFamily* aFamily,
+ const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
+ const gfxUserFontAttributes& aAttr) {
+ aFamily->ReadLock();
+ const auto& fontList = aFamily->GetFontList();
+ gfxUserFontEntry* result = nullptr;
+
+ for (const auto& font : fontList) {
+ if (!font->mIsUserFontContainer) {
+ continue;
+ }
+
+ gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(font.get());
+ if (ufe->Matches(aFontFaceSrcList, aAttr)) {
+ result = ufe;
+ break;
+ }
+ }
+ aFamily->ReadUnlock();
+
+ return result;
+}
+
+void gfxUserFontSet::AddUserFontEntry(const nsCString& aFamilyName,
+ gfxUserFontEntry* aUserFontEntry) {
+ RefPtr<gfxUserFontFamily> family = GetFamily(aFamilyName);
+ family->AddFontEntry(aUserFontEntry);
+
+ if (LOG_ENABLED()) {
+ nsAutoCString weightString;
+ aUserFontEntry->Weight().ToString(weightString);
+ nsAutoCString stretchString;
+ aUserFontEntry->Stretch().ToString(stretchString);
+ LOG(
+ ("userfonts (%p) added to \"%s\" (%p) style: %s weight: %s "
+ "stretch: %s display: %d",
+ this, aFamilyName.get(), aUserFontEntry,
+ (aUserFontEntry->IsItalic()
+ ? "italic"
+ : (aUserFontEntry->IsOblique() ? "oblique" : "normal")),
+ weightString.get(), stretchString.get(),
+ static_cast<int>(aUserFontEntry->GetFontDisplay())));
+ }
+}
+
+void gfxUserFontSet::IncrementGeneration(bool aIsRebuild) {
+ // add one, increment again if zero
+ do {
+ mGeneration = ++sFontSetGeneration;
+ } while (mGeneration == 0);
+ if (aIsRebuild) {
+ mRebuildGeneration = mGeneration;
+ }
+}
+
+void gfxUserFontSet::RebuildLocalRules() {
+ if (mLocalRulesUsed) {
+ mRebuildLocalRules = true;
+ DoRebuildUserFontSet();
+ }
+}
+
+already_AddRefed<gfxUserFontFamily> gfxUserFontSet::LookupFamily(
+ const nsACString& aFamilyName) const {
+ nsAutoCString key(aFamilyName);
+ ToLowerCase(key);
+
+ return mFontFamilies.Get(key);
+}
+
+already_AddRefed<gfxUserFontFamily> gfxUserFontSet::GetFamily(
+ const nsACString& aFamilyName) {
+ nsAutoCString key(aFamilyName);
+ ToLowerCase(key);
+
+ return do_AddRef(mFontFamilies.GetOrInsertNew(key, aFamilyName));
+}
+
+void gfxUserFontSet::ForgetLocalFaces() {
+ for (const auto& fam : mFontFamilies.Values()) {
+ ForgetLocalFace(fam);
+ }
+}
+
+void gfxUserFontSet::ForgetLocalFace(gfxUserFontFamily* aFontFamily) {
+ aFontFamily->ReadLock();
+ const auto& fonts = aFontFamily->GetFontList();
+ for (const auto& f : fonts) {
+ auto ufe = static_cast<gfxUserFontEntry*>(f.get());
+ // If the user font entry has loaded an entry using src:local(),
+ // discard it as no longer valid.
+ if (ufe->GetPlatformFontEntry() &&
+ ufe->GetPlatformFontEntry()->IsLocalUserFont()) {
+ ufe->mPlatformFontEntry = nullptr;
+ }
+ // We need to re-evaluate the source list in the context of the new
+ // platform fontlist, whether or not the entry actually used a local()
+ // source last time, as one might be newly available.
+ if (ufe->mSeenLocalSource) {
+ ufe->LoadCanceled();
+ }
+ }
+ aFontFamily->ReadUnlock();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// gfxUserFontSet::UserFontCache - re-use platform font entries for user fonts
+// across pages/fontsets rather than instantiating new platform fonts.
+//
+// Entries are added to this cache when a platform font is instantiated from
+// downloaded data, and removed when the platform font entry is destroyed.
+// We don't need to use a timed expiration scheme here because the gfxFontEntry
+// for a downloaded font will be kept alive by its corresponding gfxFont
+// instance(s) until they are deleted, and *that* happens using an expiration
+// tracker (gfxFontCache). The result is that the downloaded font instances
+// recorded here will persist between pages and can get reused (provided the
+// source URI and principal match, of course).
+///////////////////////////////////////////////////////////////////////////////
+
+nsTHashtable<gfxUserFontSet::UserFontCache::Entry>*
+ gfxUserFontSet::UserFontCache::sUserFonts = nullptr;
+
+NS_IMPL_ISUPPORTS(gfxUserFontSet::UserFontCache::Flusher, nsIObserver)
+
+NS_IMETHODIMP
+gfxUserFontSet::UserFontCache::Flusher::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ if (!sUserFonts) {
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "cacheservice:empty-cache")) {
+ for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) {
+ i.Remove();
+ }
+ } else if (!strcmp(aTopic, "last-pb-context-exited")) {
+ for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) {
+ if (i.Get()->IsPrivate()) {
+ i.Remove();
+ }
+ }
+ } else if (!strcmp(aTopic, "xpcom-shutdown")) {
+ for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) {
+ i.Get()->GetFontEntry()->DisconnectSVG();
+ }
+ } else {
+ MOZ_ASSERT_UNREACHABLE("unexpected topic");
+ }
+
+ return NS_OK;
+}
+
+bool gfxUserFontSet::UserFontCache::Entry::KeyEquals(
+ const KeyTypePointer aKey) const {
+ const gfxFontEntry* fe = aKey->mFontEntry;
+
+ if (!mURI->Equals(aKey->mURI)) {
+ return false;
+ }
+
+ // For data: URIs, we don't care about the principal; otherwise, check it.
+ if (!IgnorePrincipal(mURI)) {
+ NS_ASSERTION(mPrincipal && aKey->mPrincipal,
+ "only data: URIs are allowed to omit the principal");
+ if (!mPrincipal->Equals(aKey->mPrincipal)) {
+ return false;
+ }
+ }
+
+ if (mPrivate != aKey->mPrivate) {
+ return false;
+ }
+
+ if (mFontEntry->SlantStyle() != fe->SlantStyle() ||
+ mFontEntry->Weight() != fe->Weight() ||
+ mFontEntry->Stretch() != fe->Stretch() ||
+ mFontEntry->mRangeFlags != fe->mRangeFlags ||
+ mFontEntry->mFeatureSettings != fe->mFeatureSettings ||
+ mFontEntry->mVariationSettings != fe->mVariationSettings ||
+ mFontEntry->mLanguageOverride != fe->mLanguageOverride ||
+ mFontEntry->mAscentOverride != fe->mAscentOverride ||
+ mFontEntry->mDescentOverride != fe->mDescentOverride ||
+ mFontEntry->mLineGapOverride != fe->mLineGapOverride ||
+ mFontEntry->mSizeAdjust != fe->mSizeAdjust ||
+ mFontEntry->mFamilyName != fe->mFamilyName) {
+ return false;
+ }
+
+ return true;
+}
+
+void gfxUserFontSet::UserFontCache::CacheFont(gfxFontEntry* aFontEntry) {
+ NS_ASSERTION(aFontEntry->mFamilyName.Length() != 0,
+ "caching a font associated with no family yet");
+
+ // if caching is disabled, simply return
+ if (Preferences::GetBool("gfx.downloadable_fonts.disable_cache")) {
+ return;
+ }
+
+ gfxUserFontData* data = aFontEntry->mUserFontData.get();
+ if (data->mIsBuffer) {
+#ifdef DEBUG_USERFONT_CACHE
+ printf("userfontcache skipped fontentry with buffer source: %p\n",
+ aFontEntry);
+#endif
+ return;
+ }
+
+ if (!sUserFonts) {
+ sUserFonts = new nsTHashtable<Entry>;
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ Flusher* flusher = new Flusher;
+ obs->AddObserver(flusher, "cacheservice:empty-cache", false);
+ obs->AddObserver(flusher, "last-pb-context-exited", false);
+ obs->AddObserver(flusher, "xpcom-shutdown", false);
+ }
+
+ // Create and register a memory reporter for sUserFonts.
+ // This reporter is never unregistered, but that's OK because
+ // the reporter checks whether sUserFonts is null, so it would
+ // be safe to call even after UserFontCache::Shutdown has deleted
+ // the cache.
+ RegisterStrongMemoryReporter(new MemoryReporter());
+ }
+
+ // For data: URIs, the principal is ignored; anyone who has the same
+ // data: URI is able to load it and get an equivalent font.
+ // Otherwise, the principal is used as part of the cache key.
+ gfxFontSrcPrincipal* principal;
+ if (IgnorePrincipal(data->mURI)) {
+ principal = nullptr;
+ } else {
+ principal = data->mPrincipal;
+ }
+ sUserFonts->PutEntry(Key(data->mURI, principal, aFontEntry, data->mPrivate));
+
+#ifdef DEBUG_USERFONT_CACHE
+ printf("userfontcache added fontentry: %p\n", aFontEntry);
+ Dump();
+#endif
+}
+
+void gfxUserFontSet::UserFontCache::ForgetFont(gfxFontEntry* aFontEntry) {
+ if (!sUserFonts) {
+ // if we've already deleted the cache (i.e. during shutdown),
+ // just ignore this
+ return;
+ }
+
+ // We can't simply use RemoveEntry here because it's possible the principal
+ // may have changed since the font was cached, in which case the lookup
+ // would no longer find the entry (bug 838105).
+ for (auto i = sUserFonts->Iter(); !i.Done(); i.Next()) {
+ if (i.Get()->GetFontEntry() == aFontEntry) {
+ i.Remove();
+ }
+ }
+
+#ifdef DEBUG_USERFONT_CACHE
+ printf("userfontcache removed fontentry: %p\n", aFontEntry);
+ Dump();
+#endif
+}
+
+gfxFontEntry* gfxUserFontSet::UserFontCache::GetFont(
+ const gfxFontFaceSrc& aSrc, const gfxUserFontEntry& aUserFontEntry) {
+ if (!sUserFonts ||
+ Preferences::GetBool("gfx.downloadable_fonts.disable_cache")) {
+ return nullptr;
+ }
+
+ RefPtr<gfxUserFontSet> srcFontSet = aUserFontEntry.GetUserFontSet();
+ if (NS_WARN_IF(!srcFontSet) || srcFontSet->BypassCache()) {
+ return nullptr;
+ }
+
+ // Ignore principal when looking up a data: URI.
+ RefPtr<gfxFontSrcPrincipal> principal =
+ IgnorePrincipal(aSrc.mURI) ? nullptr : aSrc.LoadPrincipal(*srcFontSet);
+
+ Entry* entry = sUserFonts->GetEntry(
+ Key(aSrc.mURI, principal, const_cast<gfxUserFontEntry*>(&aUserFontEntry),
+ srcFontSet->GetPrivateBrowsing()));
+ if (!entry) {
+ return nullptr;
+ }
+
+ // We have to perform another content policy check here to prevent
+ // cache poisoning. E.g. a.com loads a font into the cache but
+ // b.com has a CSP not allowing any fonts to be loaded.
+ if (!srcFontSet->IsFontLoadAllowed(aSrc)) {
+ return nullptr;
+ }
+
+ return entry->GetFontEntry();
+}
+
+void gfxUserFontSet::UserFontCache::Shutdown() {
+ if (sUserFonts) {
+ delete sUserFonts;
+ sUserFonts = nullptr;
+ }
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(UserFontsMallocSizeOf)
+
+void gfxUserFontSet::UserFontCache::Entry::ReportMemory(
+ nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) {
+ MOZ_ASSERT(mFontEntry);
+ nsAutoCString path("explicit/gfx/user-fonts/font(");
+
+ if (aAnonymize) {
+ path.AppendPrintf("<anonymized-%p>", this);
+ } else {
+ path.AppendPrintf("family=%s", mFontEntry->mFamilyName.get());
+ if (mURI) {
+ nsCString spec = mURI->GetSpecOrDefault();
+ spec.ReplaceChar('/', '\\');
+ // Some fonts are loaded using horrendously-long data: URIs;
+ // truncate those before reporting them.
+ if (mURI->get()->SchemeIs("data") && spec.Length() > 255) {
+ spec.Truncate(252);
+ spec.AppendLiteral("...");
+ }
+ path.AppendPrintf(", url=%s", spec.get());
+ }
+ if (mPrincipal) {
+ nsAutoCString spec;
+ mPrincipal->NodePrincipal()->GetAsciiSpec(spec);
+ if (!spec.IsEmpty()) {
+ // Include a clue as to who loaded this resource. (Note
+ // that because of font entry sharing, other pages may now
+ // be using this resource, and the original page may not
+ // even be loaded any longer.)
+ spec.ReplaceChar('/', '\\');
+ path.AppendPrintf(", principal=%s", spec.get());
+ }
+ }
+ }
+ path.Append(')');
+
+ aHandleReport->Callback(
+ ""_ns, path, nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES,
+ mFontEntry->ComputedSizeOfExcludingThis(UserFontsMallocSizeOf),
+ "Memory used by @font-face resource."_ns, aData);
+}
+
+NS_IMPL_ISUPPORTS(gfxUserFontSet::UserFontCache::MemoryReporter,
+ nsIMemoryReporter)
+
+NS_IMETHODIMP
+gfxUserFontSet::UserFontCache::MemoryReporter::CollectReports(
+ nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) {
+ if (!sUserFonts) {
+ return NS_OK;
+ }
+
+ for (auto it = sUserFonts->Iter(); !it.Done(); it.Next()) {
+ it.Get()->ReportMemory(aHandleReport, aData, aAnonymize);
+ }
+
+ MOZ_COLLECT_REPORT(
+ "explicit/gfx/user-fonts/cache-overhead", KIND_HEAP, UNITS_BYTES,
+ sUserFonts->ShallowSizeOfIncludingThis(UserFontsMallocSizeOf),
+ "Memory used by the @font-face cache, not counting the actual font "
+ "resources.");
+
+ return NS_OK;
+}
+
+#ifdef DEBUG_USERFONT_CACHE
+
+void gfxUserFontSet::UserFontCache::Entry::Dump() {
+ nsresult rv;
+
+ nsAutoCString principalURISpec("(null)");
+ bool setDomain = false;
+
+ if (mPrincipal) {
+ nsCOMPtr<nsIURI> principalURI;
+ rv = mPrincipal->NodePrincipal()->GetURI(getter_AddRefs(principalURI));
+ if (NS_SUCCEEDED(rv)) {
+ principalURI->GetSpec(principalURISpec);
+ }
+
+ nsCOMPtr<nsIURI> domainURI;
+ mPrincipal->NodePrincipal()->GetDomain(getter_AddRefs(domainURI));
+ if (domainURI) {
+ setDomain = true;
+ }
+ }
+
+ NS_ASSERTION(mURI, "null URI in userfont cache entry");
+
+ printf(
+ "userfontcache fontEntry: %p fonturihash: %8.8x "
+ "family: %s domainset: %s principal: [%s]\n",
+ mFontEntry, mURI->Hash(), mFontEntry->FamilyName().get(),
+ setDomain ? "true" : "false", principalURISpec.get());
+}
+
+void gfxUserFontSet::UserFontCache::Dump() {
+ if (!sUserFonts) {
+ return;
+ }
+
+ printf("userfontcache dump count: %d ========\n", sUserFonts->Count());
+ for (auto it = sUserFonts->Iter(); !it.Done(); it.Next()) {
+ it.Get()->Dump();
+ }
+ printf("userfontcache dump ==================\n");
+}
+
+#endif
+
+#undef LOG
+#undef LOG_ENABLED
diff --git a/gfx/thebes/gfxUserFontSet.h b/gfx/thebes/gfxUserFontSet.h
new file mode 100644
index 0000000000..a713542baf
--- /dev/null
+++ b/gfx/thebes/gfxUserFontSet.h
@@ -0,0 +1,792 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_USER_FONT_SET_H
+#define GFX_USER_FONT_SET_H
+
+#include <new>
+#include "PLDHashTable.h"
+#include "gfxFontEntry.h"
+#include "gfxFontUtils.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsIMemoryReporter.h"
+#include "nsIObserver.h"
+#include "nsIScriptError.h"
+#include "nsISupports.h"
+#include "nsRefPtrHashtable.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nscore.h"
+
+// Only needed for function bodies.
+#include <utility> // for move, forward
+#include "MainThreadUtils.h" // for NS_IsMainThread
+#include "gfxFontFeatures.h" // for gfxFontFeature
+#include "gfxFontSrcPrincipal.h" // for gfxFontSrcPrincipal
+#include "gfxFontSrcURI.h" // for gfxFontSrcURI
+#include "mozilla/Assertions.h" // for AssertionConditionType, MOZ_ASSERT_HELPER2, MOZ_ASSERT, MOZ_ASSERT_UNREACHABLE, MOZ_ASSER...
+#include "mozilla/HashFunctions.h" // for HashBytes, HashGeneric
+#include "mozilla/TimeStamp.h" // for TimeStamp
+#include "mozilla/gfx/FontVariation.h" // for FontVariation
+#include "nsDebug.h" // for NS_WARNING
+#include "nsIReferrerInfo.h" // for nsIReferrerInfo
+
+class gfxFont;
+class gfxUserFontSet;
+class nsIFontLoadCompleteCallback;
+class nsIRunnable;
+struct gfxFontStyle;
+struct gfxFontVariationAxis;
+struct gfxFontVariationInstance;
+template <class T>
+class nsMainThreadPtrHandle;
+
+namespace mozilla {
+class LogModule;
+class PostTraversalTask;
+enum class StyleFontDisplay : uint8_t;
+} // namespace mozilla
+class nsFontFaceLoader;
+
+// #define DEBUG_USERFONT_CACHE
+
+class gfxFontFaceBufferSource {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxFontFaceBufferSource)
+ public:
+ virtual void TakeBuffer(uint8_t*& aBuffer, uint32_t& aLength) = 0;
+
+ protected:
+ virtual ~gfxFontFaceBufferSource() = default;
+};
+
+// parsed CSS @font-face rule information
+// lifetime: from when @font-face rule processed until font is loaded
+struct gfxFontFaceSrc {
+ enum SourceType { eSourceType_Local, eSourceType_URL, eSourceType_Buffer };
+
+ SourceType mSourceType;
+
+ // if url, whether to use the origin principal or not
+ bool mUseOriginPrincipal = false;
+
+ // Required font technologies.
+ mozilla::StyleFontFaceSourceTechFlags mTechFlags;
+
+ // Format hint, if any was specified.
+ mozilla::StyleFontFaceSourceFormatKeyword mFormatHint;
+
+ nsCString mLocalName; // full font name if local
+ RefPtr<gfxFontSrcURI> mURI; // uri if url
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo; // referrer info if url
+ RefPtr<gfxFontSrcPrincipal>
+ mOriginPrincipal; // principal if url and mUseOriginPrincipal
+
+ RefPtr<gfxFontFaceBufferSource> mBuffer;
+
+ // The principal that should be used for the load. Should only be used for
+ // URL sources.
+ already_AddRefed<gfxFontSrcPrincipal> LoadPrincipal(
+ const gfxUserFontSet&) const;
+};
+
+inline bool operator==(const gfxFontFaceSrc& a, const gfxFontFaceSrc& b) {
+ if (a.mSourceType != b.mSourceType) {
+ return false;
+ }
+ switch (a.mSourceType) {
+ case gfxFontFaceSrc::eSourceType_Local:
+ return a.mLocalName == b.mLocalName;
+ case gfxFontFaceSrc::eSourceType_URL: {
+ if (a.mUseOriginPrincipal != b.mUseOriginPrincipal) {
+ return false;
+ }
+ if (a.mUseOriginPrincipal) {
+ if (!a.mOriginPrincipal->Equals(b.mOriginPrincipal)) {
+ return false;
+ }
+ }
+ bool equals;
+ return a.mFormatHint == b.mFormatHint && a.mTechFlags == b.mTechFlags &&
+ (a.mURI == b.mURI || a.mURI->Equals(b.mURI)) &&
+ NS_SUCCEEDED(a.mReferrerInfo->Equals(b.mReferrerInfo, &equals)) &&
+ equals;
+ }
+ case gfxFontFaceSrc::eSourceType_Buffer:
+ return a.mBuffer == b.mBuffer;
+ }
+ NS_WARNING("unexpected mSourceType");
+ return false;
+}
+
+// Subclassed to store platform-specific code cleaned out when font entry is
+// deleted.
+// Lifetime: from when platform font is created until it is deactivated.
+// If the platform does not need to add any platform-specific code/data here,
+// then the gfxUserFontSet will allocate a base gfxUserFontData and attach
+// to the entry to track the basic user font info fields here.
+class gfxUserFontData {
+ public:
+ gfxUserFontData()
+ : mSrcIndex(0),
+ mMetaOrigLen(0),
+ mTechFlags(mozilla::StyleFontFaceSourceTechFlags::Empty()),
+ mFormatHint(mozilla::StyleFontFaceSourceFormatKeyword::None),
+ mCompression(kUnknownCompression),
+ mPrivate(false),
+ mIsBuffer(false) {}
+ virtual ~gfxUserFontData() = default;
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ nsTArray<uint8_t> mMetadata; // woff metadata block (compressed), if any
+ RefPtr<gfxFontSrcURI> mURI; // URI of the source, if it was url()
+ RefPtr<gfxFontSrcPrincipal>
+ mPrincipal; // principal for the download, if url()
+ nsCString mLocalName; // font name used for the source, if local()
+ nsCString mRealName; // original fullname from the font resource
+ uint32_t mSrcIndex; // index in the rule's source list
+ uint32_t mMetaOrigLen; // length needed to decompress metadata
+ mozilla::StyleFontFaceSourceTechFlags mTechFlags; // required font tech
+ mozilla::StyleFontFaceSourceFormatKeyword
+ mFormatHint; // format hint for the source used, if any
+ uint8_t mCompression; // compression type
+ bool mPrivate; // whether font belongs to a private window
+ bool mIsBuffer; // whether the font source was a buffer
+
+ enum {
+ kUnknownCompression = 0,
+ kZlibCompression = 1,
+ kBrotliCompression = 2
+ };
+};
+
+// initially contains a set of userfont font entry objects, replaced with
+// platform/user fonts as downloaded
+
+class gfxUserFontFamily : public gfxFontFamily {
+ public:
+ friend class gfxUserFontSet;
+
+ explicit gfxUserFontFamily(const nsACString& aName)
+ : gfxFontFamily(aName, FontVisibility::Webfont) {}
+
+ virtual ~gfxUserFontFamily();
+
+ // add the given font entry to the end of the family's list
+ void AddFontEntry(gfxFontEntry* aFontEntry) {
+ mozilla::AutoWriteLock lock(mLock);
+ MOZ_ASSERT(!mIsSimpleFamily, "not valid for user-font families");
+ // keep ref while removing existing entry
+ RefPtr<gfxFontEntry> fe = aFontEntry;
+ // remove existing entry, if already present
+ mAvailableFonts.RemoveElement(aFontEntry);
+ // insert at the beginning so that the last-defined font is the first
+ // one in the fontlist used for matching, as per CSS Fonts spec
+ mAvailableFonts.InsertElementAt(0, aFontEntry);
+
+ if (aFontEntry->mFamilyName.IsEmpty()) {
+ aFontEntry->mFamilyName = Name();
+ } else {
+#ifdef DEBUG
+ nsCString thisName = Name();
+ nsCString entryName = aFontEntry->mFamilyName;
+ ToLowerCase(thisName);
+ ToLowerCase(entryName);
+ MOZ_ASSERT(thisName.Equals(entryName));
+#endif
+ }
+ ResetCharacterMap();
+ }
+
+ void RemoveFontEntry(gfxFontEntry* aFontEntry) {
+ mozilla::AutoWriteLock lock(mLock);
+ MOZ_ASSERT(!mIsSimpleFamily, "not valid for user-font families");
+ mAvailableFonts.RemoveElement(aFontEntry);
+ }
+
+ // Remove all font entries from the family
+ void DetachFontEntries() {
+ mozilla::AutoWriteLock lock(mLock);
+ mAvailableFonts.Clear();
+ }
+};
+
+class gfxUserFontEntry;
+class gfxOTSMessageContext;
+
+struct gfxUserFontAttributes {
+ using FontStretch = mozilla::FontStretch;
+ using StretchRange = mozilla::StretchRange;
+ using FontSlantStyle = mozilla::FontSlantStyle;
+ using SlantStyleRange = mozilla::SlantStyleRange;
+ using FontWeight = mozilla::FontWeight;
+ using WeightRange = mozilla::WeightRange;
+ using StyleFontFaceSourceListComponent =
+ mozilla::StyleFontFaceSourceListComponent;
+ using RangeFlags = gfxFontEntry::RangeFlags;
+
+ WeightRange mWeight = WeightRange(FontWeight::NORMAL);
+ StretchRange mStretch = StretchRange(FontStretch::NORMAL);
+ SlantStyleRange mStyle = SlantStyleRange(FontSlantStyle::NORMAL);
+ RangeFlags mRangeFlags = RangeFlags::eAutoWeight | RangeFlags::eAutoStretch |
+ RangeFlags::eAutoSlantStyle;
+ mozilla::StyleFontDisplay mFontDisplay = mozilla::StyleFontDisplay::Auto;
+ float mAscentOverride = -1.0;
+ float mDescentOverride = -1.0;
+ float mLineGapOverride = -1.0;
+ float mSizeAdjust = 1.0;
+ uint32_t mLanguageOverride = NO_FONT_LANGUAGE_OVERRIDE;
+ nsTArray<gfxFontFeature> mFeatureSettings;
+ nsTArray<gfxFontVariation> mVariationSettings;
+ RefPtr<gfxCharacterMap> mUnicodeRanges;
+
+ nsCString mFamilyName;
+ AutoTArray<StyleFontFaceSourceListComponent, 8> mSources;
+};
+
+class gfxUserFontSet {
+ friend class gfxUserFontEntry;
+ friend class gfxOTSMessageContext;
+
+ public:
+ using FontStretch = mozilla::FontStretch;
+ using StretchRange = mozilla::StretchRange;
+ using FontSlantStyle = mozilla::FontSlantStyle;
+ using SlantStyleRange = mozilla::SlantStyleRange;
+ using FontWeight = mozilla::FontWeight;
+ using WeightRange = mozilla::WeightRange;
+ using RangeFlags = gfxFontEntry::RangeFlags;
+
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ gfxUserFontSet();
+
+ void Destroy();
+
+ // creates a font face without adding it to a particular family
+ // weight - [100, 900] (multiples of 100)
+ // stretch = [FontStretch::UltraCondensed(), FontStretch::UltraExpanded()]
+ // italic style = constants in gfxFontConstants.h, e.g. NS_FONT_STYLE_NORMAL
+ // language override = result of calling
+ // nsLayoutUtils::ParseFontLanguageOverride
+ // TODO: support for unicode ranges not yet implemented
+ virtual already_AddRefed<gfxUserFontEntry> CreateUserFontEntry(
+ nsTArray<gfxFontFaceSrc>&& aFontFaceSrcList,
+ gfxUserFontAttributes&& aAttr) = 0;
+
+ // creates a font face for the specified family, or returns an existing
+ // matching entry on the family if there is one
+ already_AddRefed<gfxUserFontEntry> FindOrCreateUserFontEntry(
+ nsTArray<gfxFontFaceSrc>&& aFontFaceSrcList,
+ gfxUserFontAttributes&& aAttr);
+
+ // add in a font face for which we have the gfxUserFontEntry already
+ void AddUserFontEntry(const nsCString& aFamilyName,
+ gfxUserFontEntry* aUserFontEntry);
+
+ // Look up and return the gfxUserFontFamily in mFontFamilies with
+ // the given name
+ virtual already_AddRefed<gfxUserFontFamily> LookupFamily(
+ const nsACString& aName) const;
+
+ virtual already_AddRefed<gfxFontSrcPrincipal> GetStandardFontLoadPrincipal()
+ const = 0;
+ virtual nsPresContext* GetPresContext() const = 0;
+
+ // check whether content policies allow the given URI to load.
+ virtual bool IsFontLoadAllowed(const gfxFontFaceSrc&) = 0;
+
+ // initialize the process that loads external font data, which upon
+ // completion will call FontDataDownloadComplete method
+ virtual nsresult StartLoad(gfxUserFontEntry* aUserFontEntry,
+ uint32_t aSrcIndex) = 0;
+
+ // generation - each time a face is loaded, generation is
+ // incremented so that the change can be recognized
+ uint64_t GetGeneration() { return mGeneration; }
+
+ // increment the generation on font load
+ void IncrementGeneration(bool aIsRebuild = false);
+
+ // Generation is bumped on font loads but that doesn't affect name-style
+ // mappings. Rebuilds do however affect name-style mappings so need to
+ // lookup fontlists again when that happens.
+ uint64_t GetRebuildGeneration() { return mRebuildGeneration; }
+
+ // rebuild if local rules have been used
+ void RebuildLocalRules();
+
+ // Discard any font entries created for src:local(), so that they will
+ // be reloaded next time they're needed. This is called when the platform
+ // font list has changed, which means local font entries that were set up
+ // may no longer be valid.
+ virtual void ForgetLocalFaces();
+
+ class UserFontCache {
+ public:
+ // Record a loaded user-font in the cache. This requires that the
+ // font-entry's userFontData has been set up already, as it relies
+ // on the URI and Principal recorded there.
+ static void CacheFont(gfxFontEntry* aFontEntry);
+
+ // The given gfxFontEntry is being destroyed, so remove any record that
+ // refers to it.
+ static void ForgetFont(gfxFontEntry* aFontEntry);
+
+ // Return the gfxFontEntry corresponding to a given URI and principal,
+ // and the features of the given userfont entry, or nullptr if none is
+ // available. The aPrivate flag is set for requests coming from private
+ // windows, so we can avoid leaking fonts cached in private windows mode out
+ // to normal windows.
+ static gfxFontEntry* GetFont(const gfxFontFaceSrc&,
+ const gfxUserFontEntry&);
+
+ // Clear everything so that we don't leak URIs and Principals.
+ static void Shutdown();
+
+ // Memory-reporting support.
+ class MemoryReporter final : public nsIMemoryReporter {
+ private:
+ ~MemoryReporter() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+ };
+
+#ifdef DEBUG_USERFONT_CACHE
+ // dump contents
+ static void Dump();
+#endif
+
+ private:
+ // Helper that we use to observe the empty-cache notification
+ // from nsICacheService.
+ class Flusher : public nsIObserver {
+ virtual ~Flusher() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ Flusher() = default;
+ };
+
+ // Key used to look up entries in the user-font cache.
+ // Note that key comparison does *not* use the mFontEntry field
+ // as a whole; it only compares specific fields within the entry
+ // (weight/width/style/features) that could affect font selection
+ // or rendering, and that must match between a font-set's userfont
+ // entry and the corresponding "real" font entry.
+ struct Key {
+ RefPtr<gfxFontSrcURI> mURI;
+ RefPtr<gfxFontSrcPrincipal> mPrincipal; // use nullptr with data: URLs
+ // The font entry MUST notify the cache when it is destroyed
+ // (by calling ForgetFont()).
+ gfxFontEntry* MOZ_NON_OWNING_REF mFontEntry;
+ bool mPrivate;
+
+ Key(gfxFontSrcURI* aURI, gfxFontSrcPrincipal* aPrincipal,
+ gfxFontEntry* aFontEntry, bool aPrivate)
+ : mURI(aURI),
+ mPrincipal(aPrincipal),
+ mFontEntry(aFontEntry),
+ mPrivate(aPrivate) {}
+ };
+
+ class Entry : public PLDHashEntryHdr {
+ public:
+ typedef const Key& KeyType;
+ typedef const Key* KeyTypePointer;
+
+ explicit Entry(KeyTypePointer aKey)
+ : mURI(aKey->mURI),
+ mPrincipal(aKey->mPrincipal),
+ mFontEntry(aKey->mFontEntry),
+ mPrivate(aKey->mPrivate) {}
+
+ Entry(Entry&& aOther)
+ : PLDHashEntryHdr(std::move(aOther)),
+ mURI(std::move(aOther.mURI)),
+ mPrincipal(std::move(aOther.mPrincipal)),
+ mFontEntry(std::move(aOther.mFontEntry)),
+ mPrivate(std::move(aOther.mPrivate)) {}
+
+ ~Entry() = default;
+
+ bool KeyEquals(const KeyTypePointer aKey) const;
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+
+ static PLDHashNumber HashKey(const KeyTypePointer aKey) {
+ PLDHashNumber principalHash =
+ aKey->mPrincipal ? aKey->mPrincipal->Hash() : 0;
+ return mozilla::HashGeneric(
+ principalHash + int(aKey->mPrivate), aKey->mURI->Hash(),
+ HashFeatures(aKey->mFontEntry->mFeatureSettings),
+ HashVariations(aKey->mFontEntry->mVariationSettings),
+ mozilla::HashString(aKey->mFontEntry->mFamilyName),
+ aKey->mFontEntry->Weight().AsScalar(),
+ aKey->mFontEntry->SlantStyle().AsScalar(),
+ aKey->mFontEntry->Stretch().AsScalar(),
+ aKey->mFontEntry->mRangeFlags, aKey->mFontEntry->mLanguageOverride);
+ }
+
+ enum { ALLOW_MEMMOVE = false };
+
+ gfxFontSrcURI* GetURI() const { return mURI; }
+ gfxFontSrcPrincipal* GetPrincipal() const { return mPrincipal; }
+ gfxFontEntry* GetFontEntry() const { return mFontEntry; }
+ bool IsPrivate() const { return mPrivate; }
+
+ void ReportMemory(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize);
+
+#ifdef DEBUG_USERFONT_CACHE
+ void Dump();
+#endif
+
+ private:
+ static uint32_t HashFeatures(const nsTArray<gfxFontFeature>& aFeatures) {
+ return mozilla::HashBytes(aFeatures.Elements(),
+ aFeatures.Length() * sizeof(gfxFontFeature));
+ }
+
+ static uint32_t HashVariations(
+ const nsTArray<mozilla::gfx::FontVariation>& aVariations) {
+ return mozilla::HashBytes(
+ aVariations.Elements(),
+ aVariations.Length() * sizeof(mozilla::gfx::FontVariation));
+ }
+
+ RefPtr<gfxFontSrcURI> mURI;
+ RefPtr<gfxFontSrcPrincipal> mPrincipal; // or nullptr for data: URLs
+
+ // The "real" font entry corresponding to this downloaded font.
+ // The font entry MUST notify the cache when it is destroyed
+ // (by calling ForgetFont()).
+ gfxFontEntry* MOZ_NON_OWNING_REF mFontEntry;
+
+ // Whether this font was loaded from a private window.
+ bool mPrivate;
+ };
+
+ static nsTHashtable<Entry>* sUserFonts;
+ };
+
+ void SetLocalRulesUsed() { mLocalRulesUsed = true; }
+
+ static mozilla::LogModule* GetUserFontsLog();
+
+ // record statistics about font completion
+ virtual void RecordFontLoadDone(uint32_t aFontSize,
+ mozilla::TimeStamp aDoneTime) {}
+
+ void GetLoadStatistics(uint32_t& aLoadCount, uint64_t& aLoadSize) const {
+ aLoadCount = mDownloadCount;
+ aLoadSize = mDownloadSize;
+ }
+
+ protected:
+ // Protected destructor, to discourage deletion outside of Release():
+ virtual ~gfxUserFontSet();
+
+ // Return whether the font set is associated with a private-browsing tab.
+ virtual bool GetPrivateBrowsing() = 0;
+
+ // Return whether the font set is associated with a document that was
+ // shift-reloaded, for example, and thus should bypass the font cache.
+ virtual bool BypassCache() = 0;
+
+ // parse data for a data URL
+ virtual nsresult SyncLoadFontData(gfxUserFontEntry* aFontToLoad,
+ const gfxFontFaceSrc* aFontFaceSrc,
+ uint8_t*& aBuffer,
+ uint32_t& aBufferLength) = 0;
+
+ // report a problem of some kind (implemented in nsUserFontSet)
+ virtual nsresult LogMessage(gfxUserFontEntry* aUserFontEntry,
+ uint32_t aSrcIndex, const char* aMessage,
+ uint32_t aFlags = nsIScriptError::errorFlag,
+ nsresult aStatus = NS_OK) = 0;
+
+ // helper method for performing the actual userfont set rebuild
+ virtual void DoRebuildUserFontSet() = 0;
+
+ // helper method for FindOrCreateUserFontEntry
+ gfxUserFontEntry* FindExistingUserFontEntry(
+ gfxUserFontFamily* aFamily,
+ const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
+ const gfxUserFontAttributes& aAttr);
+
+ // creates a new gfxUserFontFamily in mFontFamilies, or returns an existing
+ // family if there is one
+ virtual already_AddRefed<gfxUserFontFamily> GetFamily(
+ const nsACString& aFamilyName);
+
+ void ForgetLocalFace(gfxUserFontFamily* aFontFamily);
+
+ // font families defined by @font-face rules
+ nsRefPtrHashtable<nsCStringHashKey, gfxUserFontFamily> mFontFamilies;
+
+ uint64_t mGeneration; // bumped on any font load change
+ uint64_t mRebuildGeneration; // only bumped on rebuilds
+
+ // true when local names have been looked up, false otherwise
+ bool mLocalRulesUsed;
+
+ // true when rules using local names need to be redone
+ bool mRebuildLocalRules;
+
+ // performance stats
+ uint32_t mDownloadCount;
+ uint64_t mDownloadSize;
+};
+
+// acts a placeholder until the real font is downloaded
+
+class gfxUserFontEntry : public gfxFontEntry {
+ friend class mozilla::PostTraversalTask;
+ friend class gfxUserFontSet;
+ friend class nsUserFontSet;
+ friend class nsFontFaceLoader;
+ friend class gfxOTSMessageContext;
+
+ public:
+ enum UserFontLoadState {
+ STATUS_NOT_LOADED = 0,
+ STATUS_LOAD_PENDING,
+ STATUS_LOADING,
+ STATUS_LOADED,
+ STATUS_FAILED
+ };
+
+ gfxUserFontEntry(nsTArray<gfxFontFaceSrc>&& aFontFaceSrcList,
+ gfxUserFontAttributes&& aAttr);
+
+ ~gfxUserFontEntry() override;
+
+ // Update the attributes of the entry to the given values, without disturbing
+ // the associated platform font entry or in-progress downloads.
+ void UpdateAttributes(gfxUserFontAttributes&& aAttr);
+
+ // Return whether the entry matches the given list of attributes
+ bool Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
+ const gfxUserFontAttributes& aAttr);
+
+ gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle) override;
+
+ gfxFontEntry* GetPlatformFontEntry() const { return mPlatformFontEntry; }
+
+ // is the font loading or loaded, or did it fail?
+ UserFontLoadState LoadState() const { return mUserFontLoadState; }
+
+ void LoadCanceled() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mUserFontLoadState = STATUS_NOT_LOADED;
+ mFontDataLoadingState = NOT_LOADING;
+ mLoader = nullptr;
+ // Reset mCurrentSrcIndex so that all potential sources are re-considered.
+ mCurrentSrcIndex = 0;
+ mSeenLocalSource = false;
+ }
+
+ // whether to wait before using fallback font or not
+ bool WaitForUserFont() const {
+ return (mUserFontLoadState == STATUS_LOAD_PENDING ||
+ mUserFontLoadState == STATUS_LOADING) &&
+ mFontDataLoadingState < LOADING_SLOWLY;
+ }
+
+ // For userfonts, cmap is used to store the unicode range data,
+ // and is inert once set, so locking is not required here.
+ // no cmap ==> all codepoints permitted
+ bool CharacterInUnicodeRange(uint32_t ch) const {
+ if (const auto* map = GetUnicodeRangeMap()) {
+ return map->test(ch);
+ }
+ return true;
+ }
+
+ gfxCharacterMap* GetUnicodeRangeMap() const { return GetCharacterMap(); }
+ void SetUnicodeRangeMap(RefPtr<gfxCharacterMap>&& aCharMap) {
+ auto* oldCmap = GetUnicodeRangeMap();
+ if (oldCmap != aCharMap) {
+ auto* newCmap = aCharMap.forget().take();
+ if (mCharacterMap.compareExchange(oldCmap, newCmap)) {
+ NS_IF_RELEASE(oldCmap);
+ } else {
+ NS_IF_RELEASE(newCmap);
+ }
+ }
+ }
+
+ mozilla::StyleFontDisplay GetFontDisplay() const { return mFontDisplay; }
+
+ // load the font - starts the loading of sources which continues until
+ // a valid font resource is found or all sources fail
+ void Load();
+
+ // methods to expose some information to FontFaceSet::UserFontSet
+ // since we can't make that class a friend
+ void SetLoader(nsFontFaceLoader* aLoader) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mLoader = aLoader;
+ }
+
+ nsFontFaceLoader* GetLoader() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mLoader;
+ }
+
+ gfxFontSrcPrincipal* GetPrincipal() const { return mPrincipal; }
+ void GetFamilyNameAndURIForLogging(uint32_t aSrcIndex,
+ nsACString& aFamilyName, nsACString& aURI);
+
+ gfxFontEntry* Clone() const override {
+ MOZ_ASSERT_UNREACHABLE("cannot Clone user fonts");
+ return nullptr;
+ }
+
+ virtual already_AddRefed<gfxUserFontSet> GetUserFontSet() const = 0;
+
+ const nsTArray<gfxFontFaceSrc>& SourceList() const { return mSrcList; }
+
+ // Returns a weak reference to the requested source record, which is owned
+ // by the gfxUserFontEntry.
+ const gfxFontFaceSrc& SourceAt(uint32_t aSrcIndex) const {
+ return mSrcList[aSrcIndex];
+ }
+
+ // The variation-query APIs should not be called on placeholders.
+ bool HasVariations() override {
+ MOZ_ASSERT_UNREACHABLE("not meaningful for a userfont placeholder");
+ return false;
+ }
+ void GetVariationAxes(nsTArray<gfxFontVariationAxis>&) override {
+ MOZ_ASSERT_UNREACHABLE("not meaningful for a userfont placeholder");
+ }
+ void GetVariationInstances(nsTArray<gfxFontVariationInstance>&) override {
+ MOZ_ASSERT_UNREACHABLE("not meaningful for a userfont placeholder");
+ }
+
+ protected:
+ struct OTSMessage {
+ nsCString mMessage;
+ int mLevel; // see OTSContext in gfx/ots/include/opentype-sanitizer.h
+ };
+
+ const uint8_t* SanitizeOpenTypeData(const uint8_t* aData, uint32_t aLength,
+ uint32_t& aSanitaryLength,
+ gfxUserFontType& aFontType,
+ nsTArray<OTSMessage>& aMessages);
+
+ // attempt to load the next resource in the src list.
+ void LoadNextSrc();
+ void ContinueLoad();
+ void DoLoadNextSrc(bool aForceAsync);
+
+ // change the load state
+ virtual void SetLoadState(UserFontLoadState aLoadState);
+
+ // when download has been completed, pass back data here
+ // aDownloadStatus == NS_OK ==> download succeeded, error otherwise
+ // Ownership of aFontData is passed in here; the font set must
+ // ensure that it is eventually deleted with free().
+ void FontDataDownloadComplete(uint32_t aSrcIndex, const uint8_t* aFontData,
+ uint32_t aLength, nsresult aDownloadStatus,
+ nsIFontLoadCompleteCallback* aCallback);
+
+ // helper method for creating a platform font
+ // returns true if platform font creation successful
+ // Ownership of aFontData is passed in here; the font must
+ // ensure that it is eventually deleted with free().
+ bool LoadPlatformFontSync(uint32_t aSrcIndex, const uint8_t* aFontData,
+ uint32_t aLength);
+
+ void LoadPlatformFontAsync(uint32_t aSrcIndex, const uint8_t* aFontData,
+ uint32_t aLength,
+ nsIFontLoadCompleteCallback* aCallback);
+
+ // helper method for LoadPlatformFontAsync; runs on a background thread
+ void StartPlatformFontLoadOnBackgroundThread(
+ uint32_t aSrcIndex, const uint8_t* aFontData, uint32_t aLength,
+ nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> aCallback);
+
+ // helper method for LoadPlatformFontAsync; runs on the main thread
+ void ContinuePlatformFontLoadOnMainThread(
+ uint32_t aSrcIndex, const uint8_t* aOriginalFontData,
+ uint32_t aOriginalLength, gfxUserFontType aFontType,
+ const uint8_t* aSanitizedFontData, uint32_t aSanitizedLength,
+ nsTArray<OTSMessage>&& aMessages,
+ nsMainThreadPtrHandle<nsIFontLoadCompleteCallback> aCallback);
+
+ // helper method for LoadPlatformFontSync and
+ // ContinuePlatformFontLoadOnMainThread; runs on the main thread
+ bool LoadPlatformFont(uint32_t aSrcIndex, const uint8_t* aOriginalFontData,
+ uint32_t aOriginalLength, gfxUserFontType aFontType,
+ const uint8_t* aSanitizedFontData,
+ uint32_t aSanitizedLength,
+ nsTArray<OTSMessage>&& aMessages);
+
+ // helper method for FontDataDownloadComplete and
+ // ContinuePlatformFontLoadOnMainThread; runs on the main thread
+ void FontLoadFailed(nsIFontLoadCompleteCallback* aCallback);
+
+ // store metadata and src details for current src into aFontEntry
+ void StoreUserFontData(gfxFontEntry* aFontEntry, uint32_t aSrcIndex,
+ bool aPrivate, const nsACString& aOriginalName,
+ FallibleTArray<uint8_t>* aMetadata,
+ uint32_t aMetaOrigLen, uint8_t aCompression);
+
+ // Clears and then adds to aResult all of the user font sets that this user
+ // font entry has been added to. This will at least include the owner of this
+ // user font entry.
+ virtual void GetUserFontSets(nsTArray<RefPtr<gfxUserFontSet>>& aResult);
+
+ // Calls IncrementGeneration() on all user font sets that contain this
+ // user font entry.
+ void IncrementGeneration();
+
+ // general load state
+ UserFontLoadState mUserFontLoadState;
+
+ // detailed load state while font data is loading
+ // used to determine whether to use fallback font or not
+ // note that code depends on the ordering of these values!
+ enum FontDataLoadingState {
+ NOT_LOADING = 0, // not started to load any font resources yet
+ LOADING_STARTED, // loading has started; hide fallback font
+ LOADING_ALMOST_DONE, // timeout happened but we're nearly done,
+ // so keep hiding fallback font
+ LOADING_SLOWLY, // timeout happened and we're not nearly done,
+ // so use the fallback font
+ LOADING_TIMED_OUT, // font load took too long
+ LOADING_FAILED // failed to load any source: use fallback
+ };
+ FontDataLoadingState mFontDataLoadingState;
+
+ bool mSeenLocalSource;
+ bool mUnsupportedFormat;
+ mozilla::StyleFontDisplay mFontDisplay; // timing of userfont fallback
+
+ RefPtr<gfxFontEntry> mPlatformFontEntry;
+ nsTArray<gfxFontFaceSrc> mSrcList;
+ uint32_t mCurrentSrcIndex; // index of src item to be loaded next
+ // This field is managed by the nsFontFaceLoader. In the destructor and
+ // Cancel() methods of nsFontFaceLoader this reference is nulled out.
+ nsFontFaceLoader* MOZ_NON_OWNING_REF
+ mLoader; // current loader for this entry, if any
+ RefPtr<gfxUserFontSet> mLoadingFontSet;
+ RefPtr<gfxFontSrcPrincipal> mPrincipal;
+};
+
+#endif /* GFX_USER_FONT_SET_H */
diff --git a/gfx/thebes/gfxUtils.cpp b/gfx/thebes/gfxUtils.cpp
new file mode 100644
index 0000000000..422b4b38b5
--- /dev/null
+++ b/gfx/thebes/gfxUtils.cpp
@@ -0,0 +1,1820 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxUtils.h"
+
+#include "cairo.h"
+#include "gfxContext.h"
+#include "gfxEnv.h"
+#include "gfxImageSurface.h"
+#include "gfxPlatform.h"
+#include "gfxDrawable.h"
+#include "gfxQuad.h"
+#include "imgIEncoder.h"
+#include "mozilla/Base64.h"
+#include "mozilla/StyleColorInlines.h"
+#include "mozilla/Components.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/ImageEncoder.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/ipc/CrossProcessSemaphore.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/gfx/Swizzle.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/image/nsBMPEncoder.h"
+#include "mozilla/image/nsICOEncoder.h"
+#include "mozilla/image/nsJPEGEncoder.h"
+#include "mozilla/image/nsPNGEncoder.h"
+#include "mozilla/layers/SynchronousTask.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ServoStyleConsts.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/Unused.h"
+#include "mozilla/webrender/webrender_ffi.h"
+#include "nsAppRunner.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIClipboardHelper.h"
+#include "nsIFile.h"
+#include "nsIGfxInfo.h"
+#include "nsMimeTypes.h"
+#include "nsPresContext.h"
+#include "nsRegion.h"
+#include "nsServiceManagerUtils.h"
+#include "nsRFPService.h"
+#include "ImageContainer.h"
+#include "ImageRegion.h"
+#include "gfx2DGlue.h"
+
+#ifdef XP_WIN
+# include "gfxWindowsPlatform.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::image;
+using namespace mozilla::layers;
+using namespace mozilla::gfx;
+
+#undef compress
+#include "mozilla/Compression.h"
+
+using namespace mozilla::Compression;
+extern "C" {
+
+/**
+ * Dump a raw image to the default log. This function is exported
+ * from libxul, so it can be called from any library in addition to
+ * (of course) from a debugger.
+ *
+ * Note: this helper currently assumes that all 2-bytepp images are
+ * r5g6b5, and that all 4-bytepp images are r8g8b8a8.
+ */
+NS_EXPORT
+void mozilla_dump_image(void* bytes, int width, int height, int bytepp,
+ int strideBytes) {
+ if (0 == strideBytes) {
+ strideBytes = width * bytepp;
+ }
+ SurfaceFormat format;
+ // TODO more flexible; parse string?
+ switch (bytepp) {
+ case 2:
+ format = SurfaceFormat::R5G6B5_UINT16;
+ break;
+ case 4:
+ default:
+ format = SurfaceFormat::R8G8B8A8;
+ break;
+ }
+
+ RefPtr<DataSourceSurface> surf = Factory::CreateWrappingDataSourceSurface(
+ (uint8_t*)bytes, strideBytes, IntSize(width, height), format);
+ gfxUtils::DumpAsDataURI(surf);
+}
+}
+
+static bool MapSrcDest(DataSourceSurface* srcSurf, DataSourceSurface* destSurf,
+ DataSourceSurface::MappedSurface* out_srcMap,
+ DataSourceSurface::MappedSurface* out_destMap) {
+ MOZ_ASSERT(srcSurf && destSurf);
+ MOZ_ASSERT(out_srcMap && out_destMap);
+
+ if (srcSurf->GetSize() != destSurf->GetSize()) {
+ MOZ_ASSERT(false, "Width and height must match.");
+ return false;
+ }
+
+ if (srcSurf == destSurf) {
+ DataSourceSurface::MappedSurface map;
+ if (!srcSurf->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
+ NS_WARNING("Couldn't Map srcSurf/destSurf.");
+ return false;
+ }
+
+ *out_srcMap = map;
+ *out_destMap = map;
+ return true;
+ }
+
+ // Map src for reading.
+ DataSourceSurface::MappedSurface srcMap;
+ if (!srcSurf->Map(DataSourceSurface::MapType::READ, &srcMap)) {
+ NS_WARNING("Couldn't Map srcSurf.");
+ return false;
+ }
+
+ // Map dest for writing.
+ DataSourceSurface::MappedSurface destMap;
+ if (!destSurf->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
+ NS_WARNING("Couldn't Map aDest.");
+ srcSurf->Unmap();
+ return false;
+ }
+
+ *out_srcMap = srcMap;
+ *out_destMap = destMap;
+ return true;
+}
+
+static void UnmapSrcDest(DataSourceSurface* srcSurf,
+ DataSourceSurface* destSurf) {
+ if (srcSurf == destSurf) {
+ srcSurf->Unmap();
+ } else {
+ srcSurf->Unmap();
+ destSurf->Unmap();
+ }
+}
+
+bool gfxUtils::PremultiplyDataSurface(DataSourceSurface* srcSurf,
+ DataSourceSurface* destSurf) {
+ MOZ_ASSERT(srcSurf && destSurf);
+
+ DataSourceSurface::MappedSurface srcMap;
+ DataSourceSurface::MappedSurface destMap;
+ if (!MapSrcDest(srcSurf, destSurf, &srcMap, &destMap)) return false;
+
+ PremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(),
+ destMap.mData, destMap.mStride, destSurf->GetFormat(),
+ srcSurf->GetSize());
+
+ UnmapSrcDest(srcSurf, destSurf);
+ return true;
+}
+
+bool gfxUtils::UnpremultiplyDataSurface(DataSourceSurface* srcSurf,
+ DataSourceSurface* destSurf) {
+ MOZ_ASSERT(srcSurf && destSurf);
+
+ DataSourceSurface::MappedSurface srcMap;
+ DataSourceSurface::MappedSurface destMap;
+ if (!MapSrcDest(srcSurf, destSurf, &srcMap, &destMap)) return false;
+
+ UnpremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(),
+ destMap.mData, destMap.mStride, destSurf->GetFormat(),
+ srcSurf->GetSize());
+
+ UnmapSrcDest(srcSurf, destSurf);
+ return true;
+}
+
+static bool MapSrcAndCreateMappedDest(
+ DataSourceSurface* srcSurf, RefPtr<DataSourceSurface>* out_destSurf,
+ DataSourceSurface::MappedSurface* out_srcMap,
+ DataSourceSurface::MappedSurface* out_destMap) {
+ MOZ_ASSERT(srcSurf);
+ MOZ_ASSERT(out_destSurf && out_srcMap && out_destMap);
+
+ // Ok, map source for reading.
+ DataSourceSurface::MappedSurface srcMap;
+ if (!srcSurf->Map(DataSourceSurface::MapType::READ, &srcMap)) {
+ MOZ_ASSERT(false, "Couldn't Map srcSurf.");
+ return false;
+ }
+
+ // Make our dest surface based on the src.
+ RefPtr<DataSourceSurface> destSurf =
+ Factory::CreateDataSourceSurfaceWithStride(
+ srcSurf->GetSize(), srcSurf->GetFormat(), srcMap.mStride);
+ if (NS_WARN_IF(!destSurf)) {
+ return false;
+ }
+
+ DataSourceSurface::MappedSurface destMap;
+ if (!destSurf->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
+ MOZ_ASSERT(false, "Couldn't Map destSurf.");
+ srcSurf->Unmap();
+ return false;
+ }
+
+ *out_destSurf = destSurf;
+ *out_srcMap = srcMap;
+ *out_destMap = destMap;
+ return true;
+}
+
+already_AddRefed<DataSourceSurface> gfxUtils::CreatePremultipliedDataSurface(
+ DataSourceSurface* srcSurf) {
+ RefPtr<DataSourceSurface> destSurf;
+ DataSourceSurface::MappedSurface srcMap;
+ DataSourceSurface::MappedSurface destMap;
+ if (!MapSrcAndCreateMappedDest(srcSurf, &destSurf, &srcMap, &destMap)) {
+ MOZ_ASSERT(false, "MapSrcAndCreateMappedDest failed.");
+ RefPtr<DataSourceSurface> surface(srcSurf);
+ return surface.forget();
+ }
+
+ PremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(),
+ destMap.mData, destMap.mStride, destSurf->GetFormat(),
+ srcSurf->GetSize());
+
+ UnmapSrcDest(srcSurf, destSurf);
+ return destSurf.forget();
+}
+
+already_AddRefed<DataSourceSurface> gfxUtils::CreateUnpremultipliedDataSurface(
+ DataSourceSurface* srcSurf) {
+ RefPtr<DataSourceSurface> destSurf;
+ DataSourceSurface::MappedSurface srcMap;
+ DataSourceSurface::MappedSurface destMap;
+ if (!MapSrcAndCreateMappedDest(srcSurf, &destSurf, &srcMap, &destMap)) {
+ MOZ_ASSERT(false, "MapSrcAndCreateMappedDest failed.");
+ RefPtr<DataSourceSurface> surface(srcSurf);
+ return surface.forget();
+ }
+
+ UnpremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(),
+ destMap.mData, destMap.mStride, destSurf->GetFormat(),
+ srcSurf->GetSize());
+
+ UnmapSrcDest(srcSurf, destSurf);
+ return destSurf.forget();
+}
+
+void gfxUtils::ConvertBGRAtoRGBA(uint8_t* aData, uint32_t aLength) {
+ MOZ_ASSERT((aLength % 4) == 0, "Loop below will pass srcEnd!");
+ SwizzleData(aData, aLength, SurfaceFormat::B8G8R8A8, aData, aLength,
+ SurfaceFormat::R8G8B8A8, IntSize(aLength / 4, 1));
+}
+
+#if !defined(MOZ_GFX_OPTIMIZE_MOBILE)
+/**
+ * This returns the fastest operator to use for solid surfaces which have no
+ * alpha channel or their alpha channel is uniformly opaque.
+ * This differs per render mode.
+ */
+static CompositionOp OptimalFillOp() {
+# ifdef XP_WIN
+ if (gfxWindowsPlatform::GetPlatform()->IsDirect2DBackend()) {
+ // D2D -really- hates operator source.
+ return CompositionOp::OP_OVER;
+ }
+# endif
+ return CompositionOp::OP_SOURCE;
+}
+
+// EXTEND_PAD won't help us here; we have to create a temporary surface to hold
+// the subimage of pixels we're allowed to sample.
+static already_AddRefed<gfxDrawable> CreateSamplingRestrictedDrawable(
+ gfxDrawable* aDrawable, gfxContext* aContext, const ImageRegion& aRegion,
+ const SurfaceFormat aFormat, bool aUseOptimalFillOp) {
+ AUTO_PROFILER_LABEL("CreateSamplingRestrictedDrawable", GRAPHICS);
+
+ DrawTarget* destDrawTarget = aContext->GetDrawTarget();
+ // We've been not using CreateSamplingRestrictedDrawable in a bunch of places
+ // for a while. Let's disable it everywhere and confirm that it's ok to get
+ // rid of.
+ if (destDrawTarget->GetBackendType() == BackendType::DIRECT2D1_1 || (true)) {
+ return nullptr;
+ }
+
+ gfxRect clipExtents = aContext->GetClipExtents();
+
+ // Inflate by one pixel because bilinear filtering will sample at most
+ // one pixel beyond the computed image pixel coordinate.
+ clipExtents.Inflate(1.0);
+
+ gfxRect needed = aRegion.IntersectAndRestrict(clipExtents);
+ needed.RoundOut();
+
+ // if 'needed' is empty, nothing will be drawn since aFill
+ // must be entirely outside the clip region, so it doesn't
+ // matter what we do here, but we should avoid trying to
+ // create a zero-size surface.
+ if (needed.IsEmpty()) return nullptr;
+
+ IntSize size(int32_t(needed.Width()), int32_t(needed.Height()));
+
+ RefPtr<DrawTarget> target =
+ gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(size,
+ aFormat);
+ if (!target || !target->IsValid()) {
+ return nullptr;
+ }
+
+ gfxContext tmpCtx(target);
+
+ if (aUseOptimalFillOp) {
+ tmpCtx.SetOp(OptimalFillOp());
+ }
+ aDrawable->Draw(&tmpCtx, needed - needed.TopLeft(), ExtendMode::REPEAT,
+ SamplingFilter::LINEAR, 1.0,
+ gfxMatrix::Translation(needed.TopLeft()));
+ RefPtr<SourceSurface> surface = target->Snapshot();
+
+ RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(
+ surface, size, gfxMatrix::Translation(-needed.TopLeft()));
+ return drawable.forget();
+}
+#endif // !MOZ_GFX_OPTIMIZE_MOBILE
+
+/* These heuristics are based on
+ * Source/WebCore/platform/graphics/skia/ImageSkia.cpp:computeResamplingMode()
+ */
+#ifdef MOZ_GFX_OPTIMIZE_MOBILE
+static SamplingFilter ReduceResamplingFilter(SamplingFilter aSamplingFilter,
+ int aImgWidth, int aImgHeight,
+ float aSourceWidth,
+ float aSourceHeight) {
+ // Images smaller than this in either direction are considered "small" and
+ // are not resampled ever (see below).
+ const int kSmallImageSizeThreshold = 8;
+
+ // The amount an image can be stretched in a single direction before we
+ // say that it is being stretched so much that it must be a line or
+ // background that doesn't need resampling.
+ const float kLargeStretch = 3.0f;
+
+ if (aImgWidth <= kSmallImageSizeThreshold ||
+ aImgHeight <= kSmallImageSizeThreshold) {
+ // Never resample small images. These are often used for borders and
+ // rules (think 1x1 images used to make lines).
+ return SamplingFilter::POINT;
+ }
+
+ if (aImgHeight * kLargeStretch <= aSourceHeight ||
+ aImgWidth * kLargeStretch <= aSourceWidth) {
+ // Large image tiling detected.
+
+ // Don't resample if it is being tiled a lot in only one direction.
+ // This is trying to catch cases where somebody has created a border
+ // (which might be large) and then is stretching it to fill some part
+ // of the page.
+ if (fabs(aSourceWidth - aImgWidth) / aImgWidth < 0.5 ||
+ fabs(aSourceHeight - aImgHeight) / aImgHeight < 0.5)
+ return SamplingFilter::POINT;
+
+ // The image is growing a lot and in more than one direction. Resampling
+ // is slow and doesn't give us very much when growing a lot.
+ return aSamplingFilter;
+ }
+
+ /* Some notes on other heuristics:
+ The Skia backend also uses nearest for backgrounds that are stretched by
+ a large amount. I'm not sure this is common enough for us to worry about
+ now. It also uses nearest for backgrounds/avoids high quality for images
+ that are very slightly scaled. I'm also not sure that very slightly
+ scaled backgrounds are common enough us to worry about.
+
+ We don't currently have much support for doing high quality interpolation.
+ The only place this currently happens is on Quartz and we don't have as
+ much control over it as would be needed. Webkit avoids using high quality
+ resampling during load. It also avoids high quality if the transformation
+ is not just a scale and translation
+
+ WebKit bug #40045 added code to avoid resampling different parts
+ of an image with different methods by using a resampling hint size.
+ It currently looks unused in WebKit but it's something to watch out for.
+ */
+
+ return aSamplingFilter;
+}
+#else
+static SamplingFilter ReduceResamplingFilter(SamplingFilter aSamplingFilter,
+ int aImgWidth, int aImgHeight,
+ int aSourceWidth,
+ int aSourceHeight) {
+ // Just pass the filter through unchanged
+ return aSamplingFilter;
+}
+#endif
+
+#ifdef MOZ_WIDGET_COCOA
+// Only prescale a temporary surface if we're going to repeat it often.
+// Scaling is expensive on OS X and without prescaling, we'd scale
+// every tile of the repeated rect. However, using a temp surface also
+// potentially uses more memory if the scaled image is large. So only prescale
+// on a temp surface if we know we're going to repeat the image in either the X
+// or Y axis multiple times.
+static bool ShouldUseTempSurface(Rect aImageRect, Rect aNeededRect) {
+ int repeatX = aNeededRect.width / aImageRect.width;
+ int repeatY = aNeededRect.height / aImageRect.height;
+ return (repeatX >= 5) || (repeatY >= 5);
+}
+
+static bool PrescaleAndTileDrawable(gfxDrawable* aDrawable,
+ gfxContext* aContext,
+ const ImageRegion& aRegion, Rect aImageRect,
+ const SamplingFilter aSamplingFilter,
+ const SurfaceFormat aFormat,
+ gfxFloat aOpacity, ExtendMode aExtendMode) {
+ MatrixScales scaleFactor =
+ aContext->CurrentMatrix().ScaleFactors().ConvertTo<float>();
+ Matrix scaleMatrix = Matrix::Scaling(scaleFactor.xScale, scaleFactor.yScale);
+ const float fuzzFactor = 0.01;
+
+ // If we aren't scaling or translating, don't go down this path
+ if ((FuzzyEqual(scaleFactor.xScale, 1.0f, fuzzFactor) &&
+ FuzzyEqual(scaleFactor.yScale, 1.0f, fuzzFactor)) ||
+ aContext->CurrentMatrix().HasNonAxisAlignedTransform()) {
+ return false;
+ }
+
+ gfxRect clipExtents = aContext->GetClipExtents();
+
+ // Inflate by one pixel because bilinear filtering will sample at most
+ // one pixel beyond the computed image pixel coordinate.
+ clipExtents.Inflate(1.0);
+
+ gfxRect needed = aRegion.IntersectAndRestrict(clipExtents);
+ Rect scaledNeededRect = scaleMatrix.TransformBounds(ToRect(needed));
+ scaledNeededRect.RoundOut();
+ if (scaledNeededRect.IsEmpty()) {
+ return false;
+ }
+
+ Rect scaledImageRect = scaleMatrix.TransformBounds(aImageRect);
+ if (!ShouldUseTempSurface(scaledImageRect, scaledNeededRect)) {
+ return false;
+ }
+
+ IntSize scaledImageSize((int32_t)scaledImageRect.width,
+ (int32_t)scaledImageRect.height);
+ if (scaledImageSize.width != scaledImageRect.width ||
+ scaledImageSize.height != scaledImageRect.height) {
+ // If the scaled image isn't pixel aligned, we'll get artifacts
+ // so we have to take the slow path.
+ return false;
+ }
+
+ RefPtr<DrawTarget> scaledDT =
+ gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+ scaledImageSize, aFormat);
+ if (!scaledDT || !scaledDT->IsValid()) {
+ return false;
+ }
+
+ gfxContext tmpCtx(scaledDT);
+
+ scaledDT->SetTransform(scaleMatrix);
+ gfxRect gfxImageRect(aImageRect.x, aImageRect.y, aImageRect.width,
+ aImageRect.height);
+
+ // Since this is just the scaled image, we don't want to repeat anything yet.
+ aDrawable->Draw(&tmpCtx, gfxImageRect, ExtendMode::CLAMP, aSamplingFilter,
+ 1.0, gfxMatrix());
+
+ RefPtr<SourceSurface> scaledImage = scaledDT->Snapshot();
+
+ {
+ gfxContextMatrixAutoSaveRestore autoSR(aContext);
+ Matrix withoutScale = aContext->CurrentMatrix();
+ DrawTarget* destDrawTarget = aContext->GetDrawTarget();
+
+ // The translation still is in scaled units
+ withoutScale.PreScale(1.0f / scaleFactor.xScale, 1.0f / scaleFactor.yScale);
+ aContext->SetMatrix(withoutScale);
+
+ DrawOptions drawOptions(aOpacity, aContext->CurrentOp(),
+ aContext->CurrentAntialiasMode());
+
+ SurfacePattern scaledImagePattern(scaledImage, aExtendMode, Matrix(),
+ aSamplingFilter);
+ destDrawTarget->FillRect(scaledNeededRect, scaledImagePattern, drawOptions);
+ }
+ return true;
+}
+#endif // MOZ_WIDGET_COCOA
+
+/* static */
+void gfxUtils::DrawPixelSnapped(gfxContext* aContext, gfxDrawable* aDrawable,
+ const gfxSize& aImageSize,
+ const ImageRegion& aRegion,
+ const SurfaceFormat aFormat,
+ SamplingFilter aSamplingFilter,
+ uint32_t aImageFlags, gfxFloat aOpacity,
+ bool aUseOptimalFillOp) {
+ AUTO_PROFILER_LABEL("gfxUtils::DrawPixelSnapped", GRAPHICS);
+
+ gfxRect imageRect(gfxPoint(0, 0), aImageSize);
+ gfxRect region(aRegion.Rect());
+ ExtendMode extendMode = aRegion.GetExtendMode();
+
+ RefPtr<gfxDrawable> drawable = aDrawable;
+
+ aSamplingFilter = ReduceResamplingFilter(aSamplingFilter, imageRect.Width(),
+ imageRect.Height(), region.Width(),
+ region.Height());
+
+ // OK now, the hard part left is to account for the subimage sampling
+ // restriction. If all the transforms involved are just integer
+ // translations, then we assume no resampling will occur so there's
+ // nothing to do.
+ // XXX if only we had source-clipping in cairo!
+
+ if (aContext->CurrentMatrix().HasNonIntegerTranslation()) {
+ if ((extendMode != ExtendMode::CLAMP) ||
+ !aRegion.RestrictionContains(imageRect)) {
+ if (drawable->DrawWithSamplingRect(
+ aContext->GetDrawTarget(), aContext->CurrentOp(),
+ aContext->CurrentAntialiasMode(), aRegion.Rect(),
+ aRegion.Restriction(), extendMode, aSamplingFilter, aOpacity)) {
+ return;
+ }
+
+#ifdef MOZ_WIDGET_COCOA
+ if (PrescaleAndTileDrawable(aDrawable, aContext, aRegion,
+ ToRect(imageRect), aSamplingFilter, aFormat,
+ aOpacity, extendMode)) {
+ return;
+ }
+#endif
+
+ // On Mobile, we don't ever want to do this; it has the potential for
+ // allocating very large temporary surfaces, especially since we'll
+ // do full-page snapshots often (see bug 749426).
+#if !defined(MOZ_GFX_OPTIMIZE_MOBILE)
+ RefPtr<gfxDrawable> restrictedDrawable = CreateSamplingRestrictedDrawable(
+ aDrawable, aContext, aRegion, aFormat, aUseOptimalFillOp);
+ if (restrictedDrawable) {
+ drawable.swap(restrictedDrawable);
+
+ // We no longer need to tile: Either we never needed to, or we already
+ // filled a surface with the tiled pattern; this surface can now be
+ // drawn without tiling.
+ extendMode = ExtendMode::CLAMP;
+ }
+#endif
+ }
+ }
+
+ drawable->Draw(aContext, aRegion.Rect(), extendMode, aSamplingFilter,
+ aOpacity, gfxMatrix());
+}
+
+/* static */
+int gfxUtils::ImageFormatToDepth(gfxImageFormat aFormat) {
+ switch (aFormat) {
+ case SurfaceFormat::A8R8G8B8_UINT32:
+ return 32;
+ case SurfaceFormat::X8R8G8B8_UINT32:
+ return 24;
+ case SurfaceFormat::R5G6B5_UINT16:
+ return 16;
+ default:
+ break;
+ }
+ return 0;
+}
+
+/*static*/
+void gfxUtils::ClipToRegion(gfxContext* aContext, const nsIntRegion& aRegion) {
+ aContext->NewPath();
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ const IntRect& r = iter.Get();
+ aContext->Rectangle(gfxRect(r.X(), r.Y(), r.Width(), r.Height()));
+ }
+ aContext->Clip();
+}
+
+/*static*/
+void gfxUtils::ClipToRegion(DrawTarget* aTarget, const nsIntRegion& aRegion) {
+ uint32_t numRects = aRegion.GetNumRects();
+ // If there is only one rect, then the region bounds are equivalent to the
+ // contents. So just use push a single clip rect with the bounds.
+ if (numRects == 1) {
+ aTarget->PushClipRect(Rect(aRegion.GetBounds()));
+ return;
+ }
+
+ // Check if the target's transform will preserve axis-alignment and
+ // pixel-alignment for each rect. For now, just handle the common case
+ // of integer translations.
+ Matrix transform = aTarget->GetTransform();
+ if (transform.IsIntegerTranslation()) {
+ IntPoint translation = RoundedToInt(transform.GetTranslation());
+ AutoTArray<IntRect, 16> rects;
+ rects.SetLength(numRects);
+ uint32_t i = 0;
+ // Build the list of transformed rects by adding in the translation.
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ IntRect rect = iter.Get();
+ rect.MoveBy(translation);
+ rects[i++] = rect;
+ }
+ aTarget->PushDeviceSpaceClipRects(rects.Elements(), rects.Length());
+ } else {
+ // The transform does not produce axis-aligned rects or a rect was not
+ // pixel-aligned. So just build a path with all the rects and clip to it
+ // instead.
+ RefPtr<PathBuilder> pathBuilder = aTarget->CreatePathBuilder();
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ AppendRectToPath(pathBuilder, Rect(iter.Get()));
+ }
+ RefPtr<Path> path = pathBuilder->Finish();
+ aTarget->PushClip(path);
+ }
+}
+
+/*static*/
+float gfxUtils::ClampToScaleFactor(float aVal, bool aRoundDown) {
+ // Arbitary scale factor limitation. We can increase this
+ // for better scaling performance at the cost of worse
+ // quality.
+ static const float kScaleResolution = 2;
+
+ // Negative scaling is just a flip and irrelevant to
+ // our resolution calculation.
+ if (aVal < 0.0) {
+ aVal = -aVal;
+ }
+
+ bool inverse = false;
+ if (aVal < 1.0) {
+ inverse = true;
+ aVal = 1 / aVal;
+ }
+
+ float power = logf(aVal) / logf(kScaleResolution);
+
+ // If power is within 1e-5 of an integer, round to nearest to
+ // prevent floating point errors, otherwise round up to the
+ // next integer value.
+ if (fabs(power - NS_round(power)) < 1e-5) {
+ power = NS_round(power);
+ // Use floor when we are either inverted or rounding down, but
+ // not both.
+ } else if (inverse != aRoundDown) {
+ power = floor(power);
+ // Otherwise, ceil when we are not inverted and not rounding
+ // down, or we are inverted and rounding down.
+ } else {
+ power = ceil(power);
+ }
+
+ float scale = powf(kScaleResolution, power);
+
+ if (inverse) {
+ scale = 1 / scale;
+ }
+
+ return scale;
+}
+
+gfxMatrix gfxUtils::TransformRectToRect(const gfxRect& aFrom,
+ const gfxPoint& aToTopLeft,
+ const gfxPoint& aToTopRight,
+ const gfxPoint& aToBottomRight) {
+ gfxMatrix m;
+ if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) {
+ // Not a rotation, so xy and yx are zero
+ m._21 = m._12 = 0.0;
+ m._11 = (aToBottomRight.x - aToTopLeft.x) / aFrom.Width();
+ m._22 = (aToBottomRight.y - aToTopLeft.y) / aFrom.Height();
+ m._31 = aToTopLeft.x - m._11 * aFrom.X();
+ m._32 = aToTopLeft.y - m._22 * aFrom.Y();
+ } else {
+ NS_ASSERTION(
+ aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x,
+ "Destination rectangle not axis-aligned");
+ m._11 = m._22 = 0.0;
+ m._21 = (aToBottomRight.x - aToTopLeft.x) / aFrom.Height();
+ m._12 = (aToBottomRight.y - aToTopLeft.y) / aFrom.Width();
+ m._31 = aToTopLeft.x - m._21 * aFrom.Y();
+ m._32 = aToTopLeft.y - m._12 * aFrom.X();
+ }
+ return m;
+}
+
+Matrix gfxUtils::TransformRectToRect(const gfxRect& aFrom,
+ const IntPoint& aToTopLeft,
+ const IntPoint& aToTopRight,
+ const IntPoint& aToBottomRight) {
+ Matrix m;
+ if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) {
+ // Not a rotation, so xy and yx are zero
+ m._12 = m._21 = 0.0;
+ m._11 = (aToBottomRight.x - aToTopLeft.x) / aFrom.Width();
+ m._22 = (aToBottomRight.y - aToTopLeft.y) / aFrom.Height();
+ m._31 = aToTopLeft.x - m._11 * aFrom.X();
+ m._32 = aToTopLeft.y - m._22 * aFrom.Y();
+ } else {
+ NS_ASSERTION(
+ aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x,
+ "Destination rectangle not axis-aligned");
+ m._11 = m._22 = 0.0;
+ m._21 = (aToBottomRight.x - aToTopLeft.x) / aFrom.Height();
+ m._12 = (aToBottomRight.y - aToTopLeft.y) / aFrom.Width();
+ m._31 = aToTopLeft.x - m._21 * aFrom.Y();
+ m._32 = aToTopLeft.y - m._12 * aFrom.X();
+ }
+ return m;
+}
+
+/* This function is sort of shitty. We truncate doubles
+ * to ints then convert those ints back to doubles to make sure that
+ * they equal the doubles that we got in. */
+bool gfxUtils::GfxRectToIntRect(const gfxRect& aIn, IntRect* aOut) {
+ *aOut = IntRect(int32_t(aIn.X()), int32_t(aIn.Y()), int32_t(aIn.Width()),
+ int32_t(aIn.Height()));
+ return gfxRect(aOut->X(), aOut->Y(), aOut->Width(), aOut->Height())
+ .IsEqualEdges(aIn);
+}
+
+/* Clamp r to CAIRO_COORD_MIN .. CAIRO_COORD_MAX
+ * these are to be device coordinates.
+ *
+ * Cairo is currently using 24.8 fixed point,
+ * so -2^24 .. 2^24-1 is our valid
+ */
+/*static*/
+void gfxUtils::ConditionRect(gfxRect& aRect) {
+#define CAIRO_COORD_MAX (16777215.0)
+#define CAIRO_COORD_MIN (-16777216.0)
+ // if either x or y is way out of bounds;
+ // note that we don't handle negative w/h here
+ if (aRect.X() > CAIRO_COORD_MAX) {
+ aRect.SetRectX(CAIRO_COORD_MAX, 0.0);
+ }
+
+ if (aRect.Y() > CAIRO_COORD_MAX) {
+ aRect.SetRectY(CAIRO_COORD_MAX, 0.0);
+ }
+
+ if (aRect.X() < CAIRO_COORD_MIN) {
+ aRect.SetWidth(aRect.XMost() - CAIRO_COORD_MIN);
+ if (aRect.Width() < 0.0) {
+ aRect.SetWidth(0.0);
+ }
+ aRect.MoveToX(CAIRO_COORD_MIN);
+ }
+
+ if (aRect.Y() < CAIRO_COORD_MIN) {
+ aRect.SetHeight(aRect.YMost() - CAIRO_COORD_MIN);
+ if (aRect.Height() < 0.0) {
+ aRect.SetHeight(0.0);
+ }
+ aRect.MoveToY(CAIRO_COORD_MIN);
+ }
+
+ if (aRect.XMost() > CAIRO_COORD_MAX) {
+ aRect.SetRightEdge(CAIRO_COORD_MAX);
+ }
+
+ if (aRect.YMost() > CAIRO_COORD_MAX) {
+ aRect.SetBottomEdge(CAIRO_COORD_MAX);
+ }
+#undef CAIRO_COORD_MAX
+#undef CAIRO_COORD_MIN
+}
+
+/*static*/
+gfxQuad gfxUtils::TransformToQuad(const gfxRect& aRect,
+ const mozilla::gfx::Matrix4x4& aMatrix) {
+ gfxPoint points[4];
+
+ points[0] = aMatrix.TransformPoint(aRect.TopLeft());
+ points[1] = aMatrix.TransformPoint(aRect.TopRight());
+ points[2] = aMatrix.TransformPoint(aRect.BottomRight());
+ points[3] = aMatrix.TransformPoint(aRect.BottomLeft());
+
+ // Could this ever result in lines that intersect? I don't think so.
+ return gfxQuad(points[0], points[1], points[2], points[3]);
+}
+
+Matrix4x4 gfxUtils::SnapTransformTranslation(const Matrix4x4& aTransform,
+ Matrix* aResidualTransform) {
+ if (aResidualTransform) {
+ *aResidualTransform = Matrix();
+ }
+
+ Matrix matrix2D;
+ if (aTransform.CanDraw2D(&matrix2D) && !matrix2D.HasNonTranslation() &&
+ matrix2D.HasNonIntegerTranslation()) {
+ return Matrix4x4::From2D(
+ SnapTransformTranslation(matrix2D, aResidualTransform));
+ }
+
+ return SnapTransformTranslation3D(aTransform, aResidualTransform);
+}
+
+Matrix gfxUtils::SnapTransformTranslation(const Matrix& aTransform,
+ Matrix* aResidualTransform) {
+ if (aResidualTransform) {
+ *aResidualTransform = Matrix();
+ }
+
+ if (!aTransform.HasNonTranslation() &&
+ aTransform.HasNonIntegerTranslation()) {
+ auto snappedTranslation = IntPoint::Round(aTransform.GetTranslation());
+ Matrix snappedMatrix =
+ Matrix::Translation(snappedTranslation.x, snappedTranslation.y);
+ if (aResidualTransform) {
+ // set aResidualTransform so that aResidual * snappedMatrix == matrix2D.
+ // (I.e., appying snappedMatrix after aResidualTransform gives the
+ // ideal transform.)
+ *aResidualTransform =
+ Matrix::Translation(aTransform._31 - snappedTranslation.x,
+ aTransform._32 - snappedTranslation.y);
+ }
+ return snappedMatrix;
+ }
+
+ return aTransform;
+}
+
+Matrix4x4 gfxUtils::SnapTransformTranslation3D(const Matrix4x4& aTransform,
+ Matrix* aResidualTransform) {
+ if (aTransform.IsSingular() || aTransform.HasPerspectiveComponent() ||
+ aTransform.HasNonTranslation() ||
+ !aTransform.HasNonIntegerTranslation()) {
+ // For a singular transform, there is no reversed matrix, so we
+ // don't snap it.
+ // For a perspective transform, the content is transformed in
+ // non-linear, so we don't snap it too.
+ return aTransform;
+ }
+
+ // Snap for 3D Transforms
+
+ Point3D transformedOrigin = aTransform.TransformPoint(Point3D());
+
+ // Compute the transformed snap by rounding the values of
+ // transformed origin.
+ auto transformedSnapXY =
+ IntPoint::Round(transformedOrigin.x, transformedOrigin.y);
+ Matrix4x4 inverse = aTransform;
+ inverse.Invert();
+ // see Matrix4x4::ProjectPoint()
+ Float transformedSnapZ =
+ inverse._33 == 0 ? 0
+ : (-(transformedSnapXY.x * inverse._13 +
+ transformedSnapXY.y * inverse._23 + inverse._43) /
+ inverse._33);
+ Point3D transformedSnap =
+ Point3D(transformedSnapXY.x, transformedSnapXY.y, transformedSnapZ);
+ if (transformedOrigin == transformedSnap) {
+ return aTransform;
+ }
+
+ // Compute the snap from the transformed snap.
+ Point3D snap = inverse.TransformPoint(transformedSnap);
+ if (snap.z > 0.001 || snap.z < -0.001) {
+ // Allow some level of accumulated computation error.
+ MOZ_ASSERT(inverse._33 == 0.0);
+ return aTransform;
+ }
+
+ // The difference between the origin and snap is the residual transform.
+ if (aResidualTransform) {
+ // The residual transform is to translate the snap to the origin
+ // of the content buffer.
+ *aResidualTransform = Matrix::Translation(-snap.x, -snap.y);
+ }
+
+ // Translate transformed origin to transformed snap since the
+ // residual transform would trnslate the snap to the origin.
+ Point3D transformedShift = transformedSnap - transformedOrigin;
+ Matrix4x4 result = aTransform;
+ result.PostTranslate(transformedShift.x, transformedShift.y,
+ transformedShift.z);
+
+ // For non-2d transform, residual translation could be more than
+ // 0.5 pixels for every axis.
+
+ return result;
+}
+
+Matrix4x4 gfxUtils::SnapTransform(const Matrix4x4& aTransform,
+ const gfxRect& aSnapRect,
+ Matrix* aResidualTransform) {
+ if (aResidualTransform) {
+ *aResidualTransform = Matrix();
+ }
+
+ Matrix matrix2D;
+ if (aTransform.Is2D(&matrix2D)) {
+ return Matrix4x4::From2D(
+ SnapTransform(matrix2D, aSnapRect, aResidualTransform));
+ }
+ return aTransform;
+}
+
+Matrix gfxUtils::SnapTransform(const Matrix& aTransform,
+ const gfxRect& aSnapRect,
+ Matrix* aResidualTransform) {
+ if (aResidualTransform) {
+ *aResidualTransform = Matrix();
+ }
+
+ if (gfxSize(1.0, 1.0) <= aSnapRect.Size() &&
+ aTransform.PreservesAxisAlignedRectangles()) {
+ auto transformedTopLeft = IntPoint::Round(
+ aTransform.TransformPoint(ToPoint(aSnapRect.TopLeft())));
+ auto transformedTopRight = IntPoint::Round(
+ aTransform.TransformPoint(ToPoint(aSnapRect.TopRight())));
+ auto transformedBottomRight = IntPoint::Round(
+ aTransform.TransformPoint(ToPoint(aSnapRect.BottomRight())));
+
+ Matrix snappedMatrix = gfxUtils::TransformRectToRect(
+ aSnapRect, transformedTopLeft, transformedTopRight,
+ transformedBottomRight);
+
+ if (aResidualTransform && !snappedMatrix.IsSingular()) {
+ // set aResidualTransform so that aResidual * snappedMatrix == matrix2D.
+ // (i.e., appying snappedMatrix after aResidualTransform gives the
+ // ideal transform.
+ Matrix snappedMatrixInverse = snappedMatrix;
+ snappedMatrixInverse.Invert();
+ *aResidualTransform = aTransform * snappedMatrixInverse;
+ }
+ return snappedMatrix;
+ }
+ return aTransform;
+}
+
+/* static */
+void gfxUtils::ClearThebesSurface(gfxASurface* aSurface) {
+ if (aSurface->CairoStatus()) {
+ return;
+ }
+ cairo_surface_t* surf = aSurface->CairoSurface();
+ if (cairo_surface_status(surf)) {
+ return;
+ }
+ cairo_t* ctx = cairo_create(surf);
+ cairo_set_source_rgba(ctx, 0.0, 0.0, 0.0, 0.0);
+ cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE);
+ IntRect bounds(nsIntPoint(0, 0), aSurface->GetSize());
+ cairo_rectangle(ctx, bounds.X(), bounds.Y(), bounds.Width(), bounds.Height());
+ cairo_fill(ctx);
+ cairo_destroy(ctx);
+}
+
+/* static */
+already_AddRefed<DataSourceSurface>
+gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface,
+ SurfaceFormat aFormat) {
+ MOZ_ASSERT(aFormat != aSurface->GetFormat(),
+ "Unnecessary - and very expersive - surface format conversion");
+
+ Rect bounds(0, 0, aSurface->GetSize().width, aSurface->GetSize().height);
+
+ if (!aSurface->IsDataSourceSurface()) {
+ // If the surface is NOT of type DATA then its data is not mapped into main
+ // memory. Format conversion is probably faster on the GPU, and by doing it
+ // there we can avoid any expensive uploads/readbacks except for (possibly)
+ // a single readback due to the unavoidable GetDataSurface() call. Using
+ // CreateOffscreenContentDrawTarget ensures the conversion happens on the
+ // GPU.
+ RefPtr<DrawTarget> dt =
+ gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+ aSurface->GetSize(), aFormat);
+ if (!dt) {
+ gfxWarning() << "gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat "
+ "failed in CreateOffscreenContentDrawTarget";
+ return nullptr;
+ }
+
+ // Using DrawSurface() here rather than CopySurface() because CopySurface
+ // is optimized for memcpy and therefore isn't good for format conversion.
+ // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
+ // generally more optimized.
+ dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(),
+ DrawOptions(1.0f, CompositionOp::OP_OVER));
+ RefPtr<SourceSurface> surface = dt->Snapshot();
+ return surface->GetDataSurface();
+ }
+
+ // If the surface IS of type DATA then it may or may not be in main memory
+ // depending on whether or not it has been mapped yet. We have no way of
+ // knowing, so we can't be sure if it's best to create a data wrapping
+ // DrawTarget for the conversion or an offscreen content DrawTarget. We could
+ // guess it's not mapped and create an offscreen content DrawTarget, but if
+ // it is then we'll end up uploading the surface data, and most likely the
+ // caller is going to be accessing the resulting surface data, resulting in a
+ // readback (both very expensive operations). Alternatively we could guess
+ // the data is mapped and create a data wrapping DrawTarget and, if the
+ // surface is not in main memory, then we will incure a readback. The latter
+ // of these two "wrong choices" is the least costly (a readback, vs an
+ // upload and a readback), and more than likely the DATA surface that we've
+ // been passed actually IS in main memory anyway. For these reasons it's most
+ // likely best to create a data wrapping DrawTarget here to do the format
+ // conversion.
+ RefPtr<DataSourceSurface> dataSurface =
+ Factory::CreateDataSourceSurface(aSurface->GetSize(), aFormat);
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface ||
+ !dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
+ return nullptr;
+ }
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
+ BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride,
+ aFormat);
+ if (!dt) {
+ dataSurface->Unmap();
+ return nullptr;
+ }
+ // Using DrawSurface() here rather than CopySurface() because CopySurface
+ // is optimized for memcpy and therefore isn't good for format conversion.
+ // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
+ // generally more optimized.
+ dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(),
+ DrawOptions(1.0f, CompositionOp::OP_OVER));
+ dataSurface->Unmap();
+ return dataSurface.forget();
+}
+
+const uint32_t gfxUtils::sNumFrameColors = 8;
+
+/* static */
+const gfx::DeviceColor& gfxUtils::GetColorForFrameNumber(
+ uint64_t aFrameNumber) {
+ static bool initialized = false;
+ static gfx::DeviceColor colors[sNumFrameColors];
+
+ if (!initialized) {
+ // This isn't truly device color, but it is just for debug.
+ uint32_t i = 0;
+ colors[i++] = gfx::DeviceColor::FromABGR(0xffff0000);
+ colors[i++] = gfx::DeviceColor::FromABGR(0xffcc00ff);
+ colors[i++] = gfx::DeviceColor::FromABGR(0xff0066cc);
+ colors[i++] = gfx::DeviceColor::FromABGR(0xff00ff00);
+ colors[i++] = gfx::DeviceColor::FromABGR(0xff33ffff);
+ colors[i++] = gfx::DeviceColor::FromABGR(0xffff0099);
+ colors[i++] = gfx::DeviceColor::FromABGR(0xff0000ff);
+ colors[i++] = gfx::DeviceColor::FromABGR(0xff999999);
+ MOZ_ASSERT(i == sNumFrameColors);
+ initialized = true;
+ }
+
+ return colors[aFrameNumber % sNumFrameColors];
+}
+
+// static
+nsresult gfxUtils::EncodeSourceSurfaceAsStream(SourceSurface* aSurface,
+ const ImageType aImageType,
+ const nsAString& aOutputOptions,
+ nsIInputStream** aOutStream) {
+ const IntSize size = aSurface->GetSize();
+ if (size.IsEmpty()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<DataSourceSurface> dataSurface;
+ if (aSurface->GetFormat() != SurfaceFormat::B8G8R8A8) {
+ // FIXME bug 995807 (B8G8R8X8), bug 831898 (R5G6B5)
+ dataSurface = gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(
+ aSurface, SurfaceFormat::B8G8R8A8);
+ } else {
+ dataSurface = aSurface->GetDataSurface();
+ }
+ if (!dataSurface) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<imgIEncoder> encoder = nullptr;
+
+ switch (aImageType) {
+ case ImageType::BMP:
+ encoder = MakeRefPtr<nsBMPEncoder>();
+ break;
+
+ case ImageType::ICO:
+ encoder = MakeRefPtr<nsICOEncoder>();
+ break;
+
+ case ImageType::JPEG:
+ encoder = MakeRefPtr<nsJPEGEncoder>();
+ break;
+
+ case ImageType::PNG:
+ encoder = MakeRefPtr<nsPNGEncoder>();
+ break;
+
+ default:
+ break;
+ }
+
+ MOZ_RELEASE_ASSERT(encoder != nullptr);
+
+ nsresult rv = encoder->InitFromData(
+ map.mData, BufferSizeFromStrideAndHeight(map.mStride, size.height),
+ size.width, size.height, map.mStride, imgIEncoder::INPUT_FORMAT_HOSTARGB,
+ aOutputOptions);
+ dataSurface->Unmap();
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIInputStream> imgStream(encoder);
+ if (!imgStream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ imgStream.forget(aOutStream);
+
+ return NS_OK;
+}
+
+// static
+Maybe<nsTArray<uint8_t>> gfxUtils::EncodeSourceSurfaceAsBytes(
+ SourceSurface* aSurface, const ImageType aImageType,
+ const nsAString& aOutputOptions) {
+ nsCOMPtr<nsIInputStream> imgStream;
+ nsresult rv = EncodeSourceSurfaceAsStream(
+ aSurface, aImageType, aOutputOptions, getter_AddRefs(imgStream));
+ if (NS_FAILED(rv)) {
+ return Nothing();
+ }
+
+ uint64_t bufSize64;
+ rv = imgStream->Available(&bufSize64);
+ if (NS_FAILED(rv) || bufSize64 > UINT32_MAX) {
+ return Nothing();
+ }
+
+ uint32_t bytesLeft = static_cast<uint32_t>(bufSize64);
+
+ nsTArray<uint8_t> imgData;
+ imgData.SetLength(bytesLeft);
+ uint8_t* bytePtr = imgData.Elements();
+
+ while (bytesLeft > 0) {
+ uint32_t bytesRead = 0;
+ rv = imgStream->Read(reinterpret_cast<char*>(bytePtr), bytesLeft,
+ &bytesRead);
+ if (NS_FAILED(rv) || bytesRead == 0) {
+ return Nothing();
+ }
+
+ bytePtr += bytesRead;
+ bytesLeft -= bytesRead;
+ }
+
+#ifdef DEBUG
+
+ // Currently, all implementers of imgIEncoder report their exact size through
+ // nsIInputStream::Available(), but let's explicitly state that we rely on
+ // that behavior for the algorithm above.
+
+ char dummy = 0;
+ uint32_t bytesRead = 0;
+ rv = imgStream->Read(&dummy, 1, &bytesRead);
+ MOZ_ASSERT(NS_SUCCEEDED(rv) && bytesRead == 0);
+
+#endif
+
+ return Some(std::move(imgData));
+}
+
+/* static */
+nsresult gfxUtils::EncodeSourceSurface(SourceSurface* aSurface,
+ const ImageType aImageType,
+ const nsAString& aOutputOptions,
+ BinaryOrData aBinaryOrData, FILE* aFile,
+ nsACString* aStrOut) {
+ MOZ_ASSERT(aBinaryOrData == gfxUtils::eDataURIEncode || aFile || aStrOut,
+ "Copying binary encoding to clipboard not currently supported");
+
+ auto maybeImgData =
+ EncodeSourceSurfaceAsBytes(aSurface, aImageType, aOutputOptions);
+ if (!maybeImgData) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsTArray<uint8_t>& imgData = *maybeImgData;
+
+ if (aBinaryOrData == gfxUtils::eBinaryEncode) {
+ if (aFile) {
+ Unused << fwrite(imgData.Elements(), 1, imgData.Length(), aFile);
+ }
+ return NS_OK;
+ }
+
+ nsCString stringBuf;
+ nsACString& dataURI = aStrOut ? *aStrOut : stringBuf;
+ dataURI.AppendLiteral("data:");
+
+ switch (aImageType) {
+ case ImageType::BMP:
+ dataURI.AppendLiteral(IMAGE_BMP);
+ break;
+
+ case ImageType::ICO:
+ dataURI.AppendLiteral(IMAGE_ICO_MS);
+ break;
+ case ImageType::JPEG:
+ dataURI.AppendLiteral(IMAGE_JPEG);
+ break;
+
+ case ImageType::PNG:
+ dataURI.AppendLiteral(IMAGE_PNG);
+ break;
+
+ default:
+ break;
+ }
+
+ dataURI.AppendLiteral(";base64,");
+ nsresult rv = Base64EncodeAppend(reinterpret_cast<char*>(imgData.Elements()),
+ imgData.Length(), dataURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aFile) {
+#ifdef ANDROID
+ if (aFile == stdout || aFile == stderr) {
+ // ADB logcat cuts off long strings so we will break it down
+ const char* cStr = dataURI.BeginReading();
+ size_t len = strlen(cStr);
+ while (true) {
+ printf_stderr("IMG: %.140s\n", cStr);
+ if (len <= 140) break;
+ len -= 140;
+ cStr += 140;
+ }
+ }
+#endif
+ fprintf(aFile, "%s", dataURI.BeginReading());
+ } else if (!aStrOut) {
+ nsCOMPtr<nsIClipboardHelper> clipboard(
+ do_GetService("@mozilla.org/widget/clipboardhelper;1", &rv));
+ if (clipboard) {
+ clipboard->CopyString(NS_ConvertASCIItoUTF16(dataURI));
+ }
+ }
+ return NS_OK;
+}
+
+static nsCString EncodeSourceSurfaceAsPNGURI(SourceSurface* aSurface) {
+ nsCString string;
+ gfxUtils::EncodeSourceSurface(aSurface, ImageType::PNG, u""_ns,
+ gfxUtils::eDataURIEncode, nullptr, &string);
+ return string;
+}
+
+// https://jdashg.github.io/misc/colors/from-coeffs.html
+const float kBT601NarrowYCbCrToRGB_RowMajor[16] = {
+ 1.16438f, 0.00000f, 1.59603f, -0.87420f, 1.16438f, -0.39176f,
+ -0.81297f, 0.53167f, 1.16438f, 2.01723f, 0.00000f, -1.08563f,
+ 0.00000f, 0.00000f, 0.00000f, 1.00000f};
+const float kBT709NarrowYCbCrToRGB_RowMajor[16] = {
+ 1.16438f, 0.00000f, 1.79274f, -0.97295f, 1.16438f, -0.21325f,
+ -0.53291f, 0.30148f, 1.16438f, 2.11240f, 0.00000f, -1.13340f,
+ 0.00000f, 0.00000f, 0.00000f, 1.00000f};
+const float kBT2020NarrowYCbCrToRGB_RowMajor[16] = {
+ 1.16438f, 0.00000f, 1.67867f, -0.91569f, 1.16438f, -0.18733f,
+ -0.65042f, 0.34746f, 1.16438f, 2.14177f, 0.00000f, -1.14815f,
+ 0.00000f, 0.00000f, 0.00000f, 1.00000f};
+const float kIdentityNarrowYCbCrToRGB_RowMajor[16] = {
+ 0.00000f, 0.00000f, 1.00000f, 0.00000f, 1.00000f, 0.00000f,
+ 0.00000f, 0.00000f, 0.00000f, 1.00000f, 0.00000f, 0.00000f,
+ 0.00000f, 0.00000f, 0.00000f, 1.00000f};
+
+/* static */ const float* gfxUtils::YuvToRgbMatrix4x3RowMajor(
+ gfx::YUVColorSpace aYUVColorSpace) {
+#define X(x) \
+ { x[0], x[1], x[2], 0.0f, x[4], x[5], x[6], 0.0f, x[8], x[9], x[10], 0.0f }
+
+ static const float rec601[12] = X(kBT601NarrowYCbCrToRGB_RowMajor);
+ static const float rec709[12] = X(kBT709NarrowYCbCrToRGB_RowMajor);
+ static const float rec2020[12] = X(kBT2020NarrowYCbCrToRGB_RowMajor);
+ static const float identity[12] = X(kIdentityNarrowYCbCrToRGB_RowMajor);
+
+#undef X
+
+ switch (aYUVColorSpace) {
+ case gfx::YUVColorSpace::BT601:
+ return rec601;
+ case gfx::YUVColorSpace::BT709:
+ return rec709;
+ case gfx::YUVColorSpace::BT2020:
+ return rec2020;
+ case gfx::YUVColorSpace::Identity:
+ return identity;
+ default:
+ MOZ_CRASH("Bad YUVColorSpace");
+ }
+}
+
+/* static */ const float* gfxUtils::YuvToRgbMatrix3x3ColumnMajor(
+ gfx::YUVColorSpace aYUVColorSpace) {
+#define X(x) \
+ { x[0], x[4], x[8], x[1], x[5], x[9], x[2], x[6], x[10] }
+
+ static const float rec601[9] = X(kBT601NarrowYCbCrToRGB_RowMajor);
+ static const float rec709[9] = X(kBT709NarrowYCbCrToRGB_RowMajor);
+ static const float rec2020[9] = X(kBT2020NarrowYCbCrToRGB_RowMajor);
+ static const float identity[9] = X(kIdentityNarrowYCbCrToRGB_RowMajor);
+
+#undef X
+
+ switch (aYUVColorSpace) {
+ case gfx::YUVColorSpace::BT601:
+ return rec601;
+ case YUVColorSpace::BT709:
+ return rec709;
+ case YUVColorSpace::BT2020:
+ return rec2020;
+ case YUVColorSpace::Identity:
+ return identity;
+ default:
+ MOZ_CRASH("Bad YUVColorSpace");
+ }
+}
+
+/* static */ const float* gfxUtils::YuvToRgbMatrix4x4ColumnMajor(
+ YUVColorSpace aYUVColorSpace) {
+#define X(x) \
+ { \
+ x[0], x[4], x[8], x[12], x[1], x[5], x[9], x[13], x[2], x[6], x[10], \
+ x[14], x[3], x[7], x[11], x[15] \
+ }
+
+ static const float rec601[16] = X(kBT601NarrowYCbCrToRGB_RowMajor);
+ static const float rec709[16] = X(kBT709NarrowYCbCrToRGB_RowMajor);
+ static const float rec2020[16] = X(kBT2020NarrowYCbCrToRGB_RowMajor);
+ static const float identity[16] = X(kIdentityNarrowYCbCrToRGB_RowMajor);
+
+#undef X
+
+ switch (aYUVColorSpace) {
+ case YUVColorSpace::BT601:
+ return rec601;
+ case YUVColorSpace::BT709:
+ return rec709;
+ case YUVColorSpace::BT2020:
+ return rec2020;
+ case YUVColorSpace::Identity:
+ return identity;
+ default:
+ MOZ_CRASH("Bad YUVColorSpace");
+ }
+}
+
+// Translate from CICP values to the color spaces we support, or return
+// Nothing() if there is no appropriate match to let the caller choose
+// a default or generate an error.
+//
+// See Rec. ITU-T H.273 (12/2016) for details on CICP
+/* static */ Maybe<gfx::YUVColorSpace> gfxUtils::CicpToColorSpace(
+ const CICP::MatrixCoefficients aMatrixCoefficients,
+ const CICP::ColourPrimaries aColourPrimaries, LazyLogModule& aLogger) {
+ switch (aMatrixCoefficients) {
+ case CICP::MatrixCoefficients::MC_BT2020_NCL:
+ case CICP::MatrixCoefficients::MC_BT2020_CL:
+ return Some(gfx::YUVColorSpace::BT2020);
+ case CICP::MatrixCoefficients::MC_BT601:
+ return Some(gfx::YUVColorSpace::BT601);
+ case CICP::MatrixCoefficients::MC_BT709:
+ return Some(gfx::YUVColorSpace::BT709);
+ case CICP::MatrixCoefficients::MC_IDENTITY:
+ return Some(gfx::YUVColorSpace::Identity);
+ case CICP::MatrixCoefficients::MC_CHROMAT_NCL:
+ case CICP::MatrixCoefficients::MC_CHROMAT_CL:
+ case CICP::MatrixCoefficients::MC_UNSPECIFIED:
+ switch (aColourPrimaries) {
+ case CICP::ColourPrimaries::CP_BT601:
+ return Some(gfx::YUVColorSpace::BT601);
+ case CICP::ColourPrimaries::CP_BT709:
+ return Some(gfx::YUVColorSpace::BT709);
+ case CICP::ColourPrimaries::CP_BT2020:
+ return Some(gfx::YUVColorSpace::BT2020);
+ default:
+ MOZ_LOG(aLogger, LogLevel::Debug,
+ ("Couldn't infer color matrix from primaries: %hhu",
+ aColourPrimaries));
+ return {};
+ }
+ default:
+ MOZ_LOG(aLogger, LogLevel::Debug,
+ ("Unsupported color matrix value: %hhu", aMatrixCoefficients));
+ return {};
+ }
+}
+
+// Translate from CICP values to the color primaries we support, or return
+// Nothing() if there is no appropriate match to let the caller choose
+// a default or generate an error.
+//
+// See Rec. ITU-T H.273 (12/2016) for details on CICP
+/* static */ Maybe<gfx::ColorSpace2> gfxUtils::CicpToColorPrimaries(
+ const CICP::ColourPrimaries aColourPrimaries, LazyLogModule& aLogger) {
+ switch (aColourPrimaries) {
+ case CICP::ColourPrimaries::CP_BT709:
+ return Some(gfx::ColorSpace2::BT709);
+ case CICP::ColourPrimaries::CP_BT2020:
+ return Some(gfx::ColorSpace2::BT2020);
+ default:
+ MOZ_LOG(aLogger, LogLevel::Debug,
+ ("Unsupported color primaries value: %hhu", aColourPrimaries));
+ return {};
+ }
+}
+
+// Translate from CICP values to the transfer functions we support, or return
+// Nothing() if there is no appropriate match.
+//
+/* static */ Maybe<gfx::TransferFunction> gfxUtils::CicpToTransferFunction(
+ const CICP::TransferCharacteristics aTransferCharacteristics) {
+ switch (aTransferCharacteristics) {
+ case CICP::TransferCharacteristics::TC_BT709:
+ return Some(gfx::TransferFunction::BT709);
+
+ case CICP::TransferCharacteristics::TC_SRGB:
+ return Some(gfx::TransferFunction::SRGB);
+
+ case CICP::TransferCharacteristics::TC_SMPTE2084:
+ return Some(gfx::TransferFunction::PQ);
+
+ case CICP::TransferCharacteristics::TC_HLG:
+ return Some(gfx::TransferFunction::HLG);
+
+ default:
+ return {};
+ }
+}
+
+/* static */
+void gfxUtils::WriteAsPNG(SourceSurface* aSurface, const nsAString& aFile) {
+ WriteAsPNG(aSurface, NS_ConvertUTF16toUTF8(aFile).get());
+}
+
+/* static */
+void gfxUtils::WriteAsPNG(SourceSurface* aSurface, const char* aFile) {
+ FILE* file = fopen(aFile, "wb");
+
+ if (!file) {
+ // Maybe the directory doesn't exist; try creating it, then fopen again.
+ nsresult rv = NS_ERROR_FAILURE;
+ nsCOMPtr<nsIFile> comFile = do_CreateInstance("@mozilla.org/file/local;1");
+ if (comFile) {
+ NS_ConvertUTF8toUTF16 utf16path((nsDependentCString(aFile)));
+ rv = comFile->InitWithPath(utf16path);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> dirPath;
+ comFile->GetParent(getter_AddRefs(dirPath));
+ if (dirPath) {
+ rv = dirPath->Create(nsIFile::DIRECTORY_TYPE, 0777);
+ if (NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_ALREADY_EXISTS) {
+ file = fopen(aFile, "wb");
+ }
+ }
+ }
+ }
+ if (!file) {
+ NS_WARNING("Failed to open file to create PNG!");
+ return;
+ }
+ }
+
+ EncodeSourceSurface(aSurface, ImageType::PNG, u""_ns, eBinaryEncode, file);
+ fclose(file);
+}
+
+/* static */
+void gfxUtils::WriteAsPNG(DrawTarget* aDT, const nsAString& aFile) {
+ WriteAsPNG(aDT, NS_ConvertUTF16toUTF8(aFile).get());
+}
+
+/* static */
+void gfxUtils::WriteAsPNG(DrawTarget* aDT, const char* aFile) {
+ RefPtr<SourceSurface> surface = aDT->Snapshot();
+ if (surface) {
+ WriteAsPNG(surface, aFile);
+ } else {
+ NS_WARNING("Failed to get surface!");
+ }
+}
+
+/* static */
+void gfxUtils::DumpAsDataURI(SourceSurface* aSurface, FILE* aFile) {
+ EncodeSourceSurface(aSurface, ImageType::PNG, u""_ns, eDataURIEncode, aFile);
+}
+
+/* static */
+nsCString gfxUtils::GetAsDataURI(SourceSurface* aSurface) {
+ return EncodeSourceSurfaceAsPNGURI(aSurface);
+}
+
+/* static */
+void gfxUtils::DumpAsDataURI(DrawTarget* aDT, FILE* aFile) {
+ RefPtr<SourceSurface> surface = aDT->Snapshot();
+ if (surface) {
+ DumpAsDataURI(surface, aFile);
+ } else {
+ NS_WARNING("Failed to get surface!");
+ }
+}
+
+/* static */
+nsCString gfxUtils::GetAsLZ4Base64Str(DataSourceSurface* aSourceSurface) {
+ DataSourceSurface::ScopedMap map(aSourceSurface, DataSourceSurface::READ);
+ int32_t dataSize = aSourceSurface->GetSize().height * map.GetStride();
+ auto compressedData = MakeUnique<char[]>(LZ4::maxCompressedSize(dataSize));
+ if (compressedData) {
+ int nDataSize =
+ LZ4::compress((char*)map.GetData(), dataSize, compressedData.get());
+ if (nDataSize > 0) {
+ nsCString string;
+ string.AppendPrintf("data:image/lz4bgra;base64,%i,%i,%i,",
+ aSourceSurface->GetSize().width, map.GetStride(),
+ aSourceSurface->GetSize().height);
+ nsresult rv = Base64EncodeAppend(compressedData.get(), nDataSize, string);
+ if (rv == NS_OK) {
+ return string;
+ }
+ }
+ }
+ return {};
+}
+
+/* static */
+nsCString gfxUtils::GetAsDataURI(DrawTarget* aDT) {
+ RefPtr<SourceSurface> surface = aDT->Snapshot();
+ if (surface) {
+ return EncodeSourceSurfaceAsPNGURI(surface);
+ } else {
+ NS_WARNING("Failed to get surface!");
+ return nsCString("");
+ }
+}
+
+/* static */
+void gfxUtils::CopyAsDataURI(SourceSurface* aSurface) {
+ EncodeSourceSurface(aSurface, ImageType::PNG, u""_ns, eDataURIEncode,
+ nullptr);
+}
+
+/* static */
+void gfxUtils::CopyAsDataURI(DrawTarget* aDT) {
+ RefPtr<SourceSurface> surface = aDT->Snapshot();
+ if (surface) {
+ CopyAsDataURI(surface);
+ } else {
+ NS_WARNING("Failed to get surface!");
+ }
+}
+
+/* static */
+UniquePtr<uint8_t[]> gfxUtils::GetImageBuffer(gfx::DataSourceSurface* aSurface,
+ bool aIsAlphaPremultiplied,
+ int32_t* outFormat) {
+ *outFormat = 0;
+
+ DataSourceSurface::MappedSurface map;
+ if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) return nullptr;
+
+ uint32_t bufferSize =
+ aSurface->GetSize().width * aSurface->GetSize().height * 4;
+ auto imageBuffer = MakeUniqueFallible<uint8_t[]>(bufferSize);
+ if (!imageBuffer) {
+ aSurface->Unmap();
+ return nullptr;
+ }
+ memcpy(imageBuffer.get(), map.mData, bufferSize);
+
+ aSurface->Unmap();
+
+ int32_t format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
+ if (!aIsAlphaPremultiplied) {
+ // We need to convert to INPUT_FORMAT_RGBA, otherwise
+ // we are automatically considered premult, and unpremult'd.
+ // Yes, it is THAT silly.
+ // Except for different lossy conversions by color,
+ // we could probably just change the label, and not change the data.
+ gfxUtils::ConvertBGRAtoRGBA(imageBuffer.get(), bufferSize);
+ format = imgIEncoder::INPUT_FORMAT_RGBA;
+ }
+
+ *outFormat = format;
+ return imageBuffer;
+}
+
+/* static */
+UniquePtr<uint8_t[]> gfxUtils::GetImageBufferWithRandomNoise(
+ gfx::DataSourceSurface* aSurface, bool aIsAlphaPremultiplied,
+ nsICookieJarSettings* aCookieJarSettings, int32_t* outFormat) {
+ UniquePtr<uint8_t[]> imageBuffer =
+ GetImageBuffer(aSurface, aIsAlphaPremultiplied, outFormat);
+
+ nsRFPService::RandomizePixels(
+ aCookieJarSettings, imageBuffer.get(),
+ aSurface->GetSize().width * aSurface->GetSize().height * 4,
+ SurfaceFormat::A8R8G8B8_UINT32);
+
+ return imageBuffer;
+}
+
+/* static */
+nsresult gfxUtils::GetInputStream(gfx::DataSourceSurface* aSurface,
+ bool aIsAlphaPremultiplied,
+ const char* aMimeType,
+ const nsAString& aEncoderOptions,
+ nsIInputStream** outStream) {
+ nsCString enccid("@mozilla.org/image/encoder;2?type=");
+ enccid += aMimeType;
+ nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
+ if (!encoder) return NS_ERROR_FAILURE;
+
+ int32_t format = 0;
+ UniquePtr<uint8_t[]> imageBuffer =
+ GetImageBuffer(aSurface, aIsAlphaPremultiplied, &format);
+ if (!imageBuffer) return NS_ERROR_FAILURE;
+
+ return dom::ImageEncoder::GetInputStream(
+ aSurface->GetSize().width, aSurface->GetSize().height, imageBuffer.get(),
+ format, encoder, aEncoderOptions, outStream);
+}
+
+/* static */
+nsresult gfxUtils::GetInputStreamWithRandomNoise(
+ gfx::DataSourceSurface* aSurface, bool aIsAlphaPremultiplied,
+ const char* aMimeType, const nsAString& aEncoderOptions,
+ nsICookieJarSettings* aCookieJarSettings, nsIInputStream** outStream) {
+ nsCString enccid("@mozilla.org/image/encoder;2?type=");
+ enccid += aMimeType;
+ nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
+ if (!encoder) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t format = 0;
+ UniquePtr<uint8_t[]> imageBuffer =
+ GetImageBuffer(aSurface, aIsAlphaPremultiplied, &format);
+ if (!imageBuffer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsRFPService::RandomizePixels(
+ aCookieJarSettings, imageBuffer.get(),
+ aSurface->GetSize().width * aSurface->GetSize().height * 4,
+ SurfaceFormat::A8R8G8B8_UINT32);
+
+ return dom::ImageEncoder::GetInputStream(
+ aSurface->GetSize().width, aSurface->GetSize().height, imageBuffer.get(),
+ format, encoder, aEncoderOptions, outStream);
+}
+
+class GetFeatureStatusWorkerRunnable final
+ : public dom::WorkerMainThreadRunnable {
+ public:
+ GetFeatureStatusWorkerRunnable(dom::WorkerPrivate* workerPrivate,
+ const nsCOMPtr<nsIGfxInfo>& gfxInfo,
+ int32_t feature, nsACString& failureId,
+ int32_t* status)
+ : WorkerMainThreadRunnable(workerPrivate, "GFX :: GetFeatureStatus"_ns),
+ mGfxInfo(gfxInfo),
+ mFeature(feature),
+ mStatus(status),
+ mFailureId(failureId),
+ mNSResult(NS_OK) {}
+
+ bool MainThreadRun() override {
+ if (mGfxInfo) {
+ mNSResult = mGfxInfo->GetFeatureStatus(mFeature, mFailureId, mStatus);
+ }
+ return true;
+ }
+
+ nsresult GetNSResult() const { return mNSResult; }
+
+ protected:
+ ~GetFeatureStatusWorkerRunnable() = default;
+
+ private:
+ nsCOMPtr<nsIGfxInfo> mGfxInfo;
+ int32_t mFeature;
+ int32_t* mStatus;
+ nsACString& mFailureId;
+ nsresult mNSResult;
+};
+
+#define GFX_SHADER_CHECK_BUILD_VERSION_PREF "gfx-shader-check.build-version"
+#define GFX_SHADER_CHECK_PTR_SIZE_PREF "gfx-shader-check.ptr-size"
+#define GFX_SHADER_CHECK_DEVICE_ID_PREF "gfx-shader-check.device-id"
+#define GFX_SHADER_CHECK_DRIVER_VERSION_PREF "gfx-shader-check.driver-version"
+
+/* static */
+void gfxUtils::RemoveShaderCacheFromDiskIfNecessary() {
+ if (!gfxVars::UseWebRenderProgramBinaryDisk()) {
+ return;
+ }
+
+ nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
+
+ // Get current values
+ nsCString buildID(mozilla::PlatformBuildID());
+ int ptrSize = sizeof(void*);
+ nsString deviceID, driverVersion;
+ gfxInfo->GetAdapterDeviceID(deviceID);
+ gfxInfo->GetAdapterDriverVersion(driverVersion);
+
+ // Get pref stored values
+ nsAutoCString buildIDChecked;
+ Preferences::GetCString(GFX_SHADER_CHECK_BUILD_VERSION_PREF, buildIDChecked);
+ int ptrSizeChecked = Preferences::GetInt(GFX_SHADER_CHECK_PTR_SIZE_PREF, 0);
+ nsAutoString deviceIDChecked, driverVersionChecked;
+ Preferences::GetString(GFX_SHADER_CHECK_DEVICE_ID_PREF, deviceIDChecked);
+ Preferences::GetString(GFX_SHADER_CHECK_DRIVER_VERSION_PREF,
+ driverVersionChecked);
+
+ if (buildID == buildIDChecked && ptrSize == ptrSizeChecked &&
+ deviceID == deviceIDChecked && driverVersion == driverVersionChecked) {
+ return;
+ }
+
+ nsAutoString path(gfx::gfxVars::ProfDirectory());
+
+ if (!wr::remove_program_binary_disk_cache(&path)) {
+ // Failed to remove program binary disk cache. The disk cache might have
+ // invalid data. Disable program binary disk cache usage.
+ gfxVars::SetUseWebRenderProgramBinaryDisk(false);
+ return;
+ }
+
+ Preferences::SetCString(GFX_SHADER_CHECK_BUILD_VERSION_PREF, buildID);
+ Preferences::SetInt(GFX_SHADER_CHECK_PTR_SIZE_PREF, ptrSize);
+ Preferences::SetString(GFX_SHADER_CHECK_DEVICE_ID_PREF, deviceID);
+ Preferences::SetString(GFX_SHADER_CHECK_DRIVER_VERSION_PREF, driverVersion);
+}
+
+/* static */
+bool gfxUtils::DumpDisplayList() {
+ return StaticPrefs::layout_display_list_dump() ||
+ (StaticPrefs::layout_display_list_dump_parent() &&
+ XRE_IsParentProcess()) ||
+ (StaticPrefs::layout_display_list_dump_content() &&
+ XRE_IsContentProcess());
+}
+
+FILE* gfxUtils::sDumpPaintFile = stderr;
+
+namespace mozilla {
+namespace gfx {
+
+DeviceColor ToDeviceColor(const sRGBColor& aColor) {
+ // aColor is pass-by-value since to get return value optimization goodness we
+ // need to return the same object from all return points in this function. We
+ // could declare a local Color variable and use that, but we might as well
+ // just use aColor.
+ if (gfxPlatform::GetCMSMode() == CMSMode::All) {
+ qcms_transform* transform = gfxPlatform::GetCMSRGBTransform();
+ if (transform) {
+ return gfxPlatform::TransformPixel(aColor, transform);
+ // Use the original alpha to avoid unnecessary float->byte->float
+ // conversion errors
+ }
+ }
+ return DeviceColor(aColor.r, aColor.g, aColor.b, aColor.a);
+}
+
+DeviceColor ToDeviceColor(nscolor aColor) {
+ return ToDeviceColor(sRGBColor::FromABGR(aColor));
+}
+
+DeviceColor ToDeviceColor(const StyleAbsoluteColor& aColor) {
+ return ToDeviceColor(aColor.ToColor());
+}
+
+sRGBColor ToSRGBColor(const StyleAbsoluteColor& aColor) {
+ auto srgb = aColor.ToColorSpace(StyleColorSpace::Srgb);
+
+ const auto ToComponent = [](float aF) -> float {
+ float component = std::min(std::max(0.0f, aF), 1.0f);
+ if (MOZ_UNLIKELY(!std::isfinite(component))) {
+ return 0.0f;
+ }
+ return component;
+ };
+ return {ToComponent(srgb.components._0), ToComponent(srgb.components._1),
+ ToComponent(srgb.components._2), ToComponent(srgb.alpha)};
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/thebes/gfxUtils.h b/gfx/thebes/gfxUtils.h
new file mode 100644
index 0000000000..a7b9f067eb
--- /dev/null
+++ b/gfx/thebes/gfxUtils.h
@@ -0,0 +1,626 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_UTILS_H
+#define GFX_UTILS_H
+
+#include "gfxMatrix.h"
+#include "gfxRect.h"
+#include "gfxTypes.h"
+#include "ImageTypes.h"
+#include "imgIContainer.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsColor.h"
+#include "nsContentUtils.h"
+#include "nsPrintfCString.h"
+#include "nsRegionFwd.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "qcms.h"
+
+class gfxASurface;
+class gfxDrawable;
+class gfxTextRun;
+struct gfxQuad;
+class nsICookieJarSettings;
+class nsIInputStream;
+class nsIGfxInfo;
+
+namespace mozilla {
+namespace dom {
+class Element;
+} // namespace dom
+namespace layers {
+class WebRenderBridgeChild;
+class GlyphArray;
+struct PlanarYCbCrData;
+class WebRenderCommand;
+} // namespace layers
+namespace image {
+class ImageRegion;
+} // namespace image
+namespace wr {
+class DisplayListBuilder;
+} // namespace wr
+} // namespace mozilla
+
+enum class ImageType {
+ BMP,
+ ICO,
+ JPEG,
+ PNG,
+};
+
+class gfxUtils {
+ public:
+ typedef mozilla::gfx::DataSourceSurface DataSourceSurface;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::IntPoint IntPoint;
+ typedef mozilla::gfx::Matrix Matrix;
+ typedef mozilla::gfx::Matrix4x4 Matrix4x4;
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+ typedef mozilla::gfx::SurfaceFormat SurfaceFormat;
+ typedef mozilla::image::ImageRegion ImageRegion;
+
+ /*
+ * Premultiply or Unpremultiply aSourceSurface, writing the result
+ * to aDestSurface or back into aSourceSurface if aDestSurface is null.
+ *
+ * If aDestSurface is given, it must have identical format, dimensions, and
+ * stride as the source.
+ *
+ * If the source is not SurfaceFormat::A8R8G8B8_UINT32, no operation is
+ * performed. If aDestSurface is given, the data is copied over.
+ */
+ static bool PremultiplyDataSurface(DataSourceSurface* srcSurf,
+ DataSourceSurface* destSurf);
+ static bool UnpremultiplyDataSurface(DataSourceSurface* srcSurf,
+ DataSourceSurface* destSurf);
+
+ static already_AddRefed<DataSourceSurface> CreatePremultipliedDataSurface(
+ DataSourceSurface* srcSurf);
+ static already_AddRefed<DataSourceSurface> CreateUnpremultipliedDataSurface(
+ DataSourceSurface* srcSurf);
+
+ static void ConvertBGRAtoRGBA(uint8_t* aData, uint32_t aLength);
+
+ /**
+ * Draw something drawable while working around limitations like bad support
+ * for EXTEND_PAD, lack of source-clipping, or cairo / pixman bugs with
+ * extreme user-space-to-image-space transforms.
+ *
+ * The input parameters here usually come from the output of our image
+ * snapping algorithm in nsLayoutUtils.cpp.
+ * This method is split from nsLayoutUtils::DrawPixelSnapped to allow for
+ * adjusting the parameters. For example, certain images with transparent
+ * margins only have a drawable subimage. For those images, imgFrame::Draw
+ * will tweak the rects and transforms that it gets from the pixel snapping
+ * algorithm before passing them on to this method.
+ */
+ static void DrawPixelSnapped(gfxContext* aContext, gfxDrawable* aDrawable,
+ const gfxSize& aImageSize,
+ const ImageRegion& aRegion,
+ const mozilla::gfx::SurfaceFormat aFormat,
+ mozilla::gfx::SamplingFilter aSamplingFilter,
+ uint32_t aImageFlags = imgIContainer::FLAG_NONE,
+ gfxFloat aOpacity = 1.0,
+ bool aUseOptimalFillOp = true);
+
+ /**
+ * Clip aContext to the region aRegion.
+ */
+ static void ClipToRegion(gfxContext* aContext, const nsIntRegion& aRegion);
+
+ /**
+ * Clip aTarget to the region aRegion.
+ */
+ static void ClipToRegion(mozilla::gfx::DrawTarget* aTarget,
+ const nsIntRegion& aRegion);
+
+ /*
+ * Convert image format to depth value
+ */
+ static int ImageFormatToDepth(gfxImageFormat aFormat);
+
+ /**
+ * Return the transform matrix that maps aFrom to the rectangle defined by
+ * aToTopLeft/aToTopRight/aToBottomRight. aFrom must be
+ * nonempty and the destination rectangle must be axis-aligned.
+ */
+ static gfxMatrix TransformRectToRect(const gfxRect& aFrom,
+ const gfxPoint& aToTopLeft,
+ const gfxPoint& aToTopRight,
+ const gfxPoint& aToBottomRight);
+
+ static Matrix TransformRectToRect(const gfxRect& aFrom,
+ const IntPoint& aToTopLeft,
+ const IntPoint& aToTopRight,
+ const IntPoint& aToBottomRight);
+
+ /**
+ * If aIn can be represented exactly using an gfx::IntRect (i.e.
+ * integer-aligned edges and coordinates in the int32_t range) then we
+ * set aOut to that rectangle, otherwise return failure.
+ */
+ static bool GfxRectToIntRect(const gfxRect& aIn, mozilla::gfx::IntRect* aOut);
+
+ /* Conditions this border to Cairo's max coordinate space.
+ * The caller can check IsEmpty() after Condition() -- if it's TRUE,
+ * the caller can possibly avoid doing any extra rendering.
+ */
+ static void ConditionRect(gfxRect& aRect);
+
+ /*
+ * Transform this rectangle with aMatrix, resulting in a gfxQuad.
+ */
+ static gfxQuad TransformToQuad(const gfxRect& aRect,
+ const mozilla::gfx::Matrix4x4& aMatrix);
+
+ /**
+ * Return the smallest power of kScaleResolution (2) greater than or equal to
+ * aVal. If aRoundDown is specified, the power of 2 will rather be less than
+ * or equal to aVal.
+ */
+ static float ClampToScaleFactor(float aVal, bool aRoundDown = false);
+
+ /**
+ * We can snap layer transforms for two reasons:
+ * 1) To avoid unnecessary resampling when a transform is a translation
+ * by a non-integer number of pixels.
+ * Snapping the translation to an integer number of pixels avoids
+ * blurring the layer and can be faster to composite.
+ * 2) When a layer is used to render a rectangular object, we need to
+ * emulate the rendering of rectangular inactive content and snap the
+ * edges of the rectangle to pixel boundaries. This is both to ensure
+ * layer rendering is consistent with inactive content rendering, and to
+ * avoid seams.
+ * This function implements type 1 snapping. If aTransform is a 2D
+ * translation, and this layer's layer manager has enabled snapping
+ * (which is the default), return aTransform with the translation snapped
+ * to nearest pixels. Otherwise just return aTransform. Call this when the
+ * layer does not correspond to a single rectangular content object.
+ * This function does not try to snap if aTransform has a scale, because in
+ * that case resampling is inevitable and there's no point in trying to
+ * avoid it. In fact snapping can cause problems because pixel edges in the
+ * layer's content can be rendered unpredictably (jiggling) as the scale
+ * interacts with the snapping of the translation, especially with animated
+ * transforms.
+ * @param aResidualTransform a transform to apply before the result transform
+ * in order to get the results to completely match aTransform.
+ */
+ static Matrix4x4 SnapTransformTranslation(const Matrix4x4& aTransform,
+ Matrix* aResidualTransform);
+ static Matrix SnapTransformTranslation(const Matrix& aTransform,
+ Matrix* aResidualTransform);
+ static Matrix4x4 SnapTransformTranslation3D(const Matrix4x4& aTransform,
+ Matrix* aResidualTransform);
+ /**
+ * See comment for SnapTransformTranslation.
+ * This function implements type 2 snapping. If aTransform is a translation
+ * and/or scale, transform aSnapRect by aTransform, snap to pixel boundaries,
+ * and return the transform that maps aSnapRect to that rect. Otherwise
+ * just return aTransform.
+ * @param aSnapRect a rectangle whose edges should be snapped to pixel
+ * boundaries in the destination surface.
+ * @param aResidualTransform a transform to apply before the result transform
+ * in order to get the results to completely match aTransform.
+ */
+ static Matrix4x4 SnapTransform(const Matrix4x4& aTransform,
+ const gfxRect& aSnapRect,
+ Matrix* aResidualTransform);
+ static Matrix SnapTransform(const Matrix& aTransform,
+ const gfxRect& aSnapRect,
+ Matrix* aResidualTransform);
+
+ /**
+ * Clears surface to aColor (which defaults to transparent black).
+ */
+ static void ClearThebesSurface(gfxASurface* aSurface);
+
+ static const float* YuvToRgbMatrix4x3RowMajor(
+ mozilla::gfx::YUVColorSpace aYUVColorSpace);
+ static const float* YuvToRgbMatrix3x3ColumnMajor(
+ mozilla::gfx::YUVColorSpace aYUVColorSpace);
+ static const float* YuvToRgbMatrix4x4ColumnMajor(
+ mozilla::gfx::YUVColorSpace aYUVColorSpace);
+
+ static mozilla::Maybe<mozilla::gfx::YUVColorSpace> CicpToColorSpace(
+ const mozilla::gfx::CICP::MatrixCoefficients,
+ const mozilla::gfx::CICP::ColourPrimaries,
+ mozilla::LazyLogModule& aLogger);
+
+ static mozilla::Maybe<mozilla::gfx::ColorSpace2> CicpToColorPrimaries(
+ const mozilla::gfx::CICP::ColourPrimaries,
+ mozilla::LazyLogModule& aLogger);
+
+ static mozilla::Maybe<mozilla::gfx::TransferFunction> CicpToTransferFunction(
+ const mozilla::gfx::CICP::TransferCharacteristics);
+
+ /**
+ * Creates a copy of aSurface, but having the SurfaceFormat aFormat.
+ *
+ * This function always creates a new surface. Do not call it if aSurface's
+ * format is the same as aFormat. Such a non-conversion would just be an
+ * unnecessary and wasteful copy (this function asserts to prevent that).
+ *
+ * This function is intended to be called by code that needs to access the
+ * pixel data of the surface, but doesn't want to have lots of branches
+ * to handle different pixel data formats (code which would become out of
+ * date if and when new formats are added). Callers can use this function
+ * to copy the surface to a specified format so that they only have to
+ * handle pixel data in that one format.
+ *
+ * WARNING: There are format conversions that will not be supported by this
+ * function. It very much depends on what the Moz2D backends support. If
+ * the temporary B8G8R8A8 DrawTarget that this function creates has a
+ * backend that supports DrawSurface() calls passing a surface with
+ * aSurface's format it will work. Otherwise it will not.
+ *
+ * *** IMPORTANT PERF NOTE ***
+ *
+ * This function exists partly because format conversion is fraught with
+ * non-obvious performance hazards, so we don't want Moz2D consumers to be
+ * doing their own format conversion. Do not try to do so, or at least read
+ * the comments in this functions implemtation. That said, the copy that
+ * this function carries out has a cost and, although this function tries
+ * to avoid perf hazards such as expensive uploads to/readbacks from the
+ * GPU, it can't guarantee that it always successfully does so. Perf
+ * critical code that can directly handle the common formats that it
+ * encounters in a way that is cheaper than a copy-with-format-conversion
+ * should consider doing so, and only use this function as a fallback to
+ * handle other formats.
+ *
+ * XXXjwatt it would be nice if SourceSurface::GetDataSurface took a
+ * SurfaceFormat argument (with a default argument meaning "use the
+ * existing surface's format") and returned a DataSourceSurface in that
+ * format. (There would then be an issue of callers maybe failing to
+ * realize format conversion may involve expensive copying/uploading/
+ * readback.)
+ */
+ static already_AddRefed<DataSourceSurface>
+ CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface,
+ SurfaceFormat aFormat);
+
+ /**
+ * Return a color that can be used to identify a frame with a given frame
+ * number. The colors will cycle after sNumFrameColors. You can query colors
+ * 0 .. sNumFrameColors-1 to get all the colors back.
+ */
+ static const mozilla::gfx::DeviceColor& GetColorForFrameNumber(
+ uint64_t aFrameNumber);
+ static const uint32_t sNumFrameColors;
+
+ enum BinaryOrData { eBinaryEncode, eDataURIEncode };
+
+ /**
+ * Encodes the given surface to PNG/JPEG/BMP/etc. using imgIEncoder.
+ * If both aFile and aString are null, the encoded data is copied to the
+ * clipboard.
+ *
+ * @param aImageType The image type that the surface is to be encoded to.
+ * Used to create an appropriate imgIEncoder instance to do the encoding.
+ *
+ * @param aOutputOptions Passed directly to imgIEncoder::InitFromData as
+ * the value of the |outputOptions| parameter. Callers are responsible
+ * for making sure that this is a reasonable value for the passed MIME-type
+ * (i.e. for the type of encoder that will be created).
+ *
+ * @aBinaryOrData Flag used to determine if the surface is simply encoded
+ * to the requested binary image format, or if the binary image is
+ * further converted to base-64 and written out as a 'data:' URI.
+ *
+ * @aFile If specified, the encoded data is written out to aFile.
+ *
+ * @aString If specified, the encoded data is written out to aString.
+ *
+ * TODO: Copying to the clipboard as a binary file is not currently
+ * supported.
+ */
+ static nsresult EncodeSourceSurface(SourceSurface* aSurface,
+ const ImageType aImageType,
+ const nsAString& aOutputOptions,
+ BinaryOrData aBinaryOrData, FILE* aFile,
+ nsACString* aString = nullptr);
+
+ /**
+ * Encodes the given surface to PNG/JPEG/BMP/etc. using imgIEncoder
+ * and returns the result as an nsIInputStream.
+ *
+ * @param aSurface The source surface to encode
+ *
+ * @param aImageType The image type that the surface is to be encoded to.
+ * Used to create an appropriate imgIEncoder instance to do the encoding.
+ *
+ * @param aOutputOptions Passed directly to imgIEncoder::InitFromData as
+ * the value of the |outputOptions| parameter. Callers are responsible
+ * for making sure that this is a reasonable value for the passed MIME-type
+ * (i.e. for the type of encoder that will be created).
+ *
+ * @param aOutStream pointer to the output stream
+ *
+ */
+ static nsresult EncodeSourceSurfaceAsStream(SourceSurface* aSurface,
+ const ImageType aImageType,
+ const nsAString& aOutputOptions,
+ nsIInputStream** aOutStream);
+
+ /**
+ * Encodes the given surface to PNG/JPEG/BMP/etc. using imgIEncoder
+ * and returns the result as a vector of bytes
+ *
+ * @param aImageType The image type that the surface is to be encoded to.
+ * Used to create an appropriate imgIEncoder instance to do the encoding.
+ *
+ * @param aOutputOptions Passed directly to imgIEncoder::InitFromData as
+ * the value of the |outputOptions| parameter. Callers are responsible
+ * for making sure that this is a reasonable value for the passed MIME-type
+ * (i.e. for the type of encoder that will be created).
+ *
+ */
+ static mozilla::Maybe<nsTArray<uint8_t>> EncodeSourceSurfaceAsBytes(
+ SourceSurface* aSurface, const ImageType aImageType,
+ const nsAString& aOutputOptions);
+
+ /**
+ * Write as a PNG file to the path aFile.
+ */
+ static void WriteAsPNG(SourceSurface* aSurface, const nsAString& aFile);
+ static void WriteAsPNG(SourceSurface* aSurface, const char* aFile);
+ static void WriteAsPNG(DrawTarget* aDT, const nsAString& aFile);
+ static void WriteAsPNG(DrawTarget* aDT, const char* aFile);
+
+ /**
+ * Dump as a PNG encoded Data URL to a FILE stream (using stdout by
+ * default).
+ *
+ * Rather than giving aFile a default argument we have separate functions
+ * to make them easier to use from a debugger.
+ */
+ static void DumpAsDataURI(SourceSurface* aSourceSurface, FILE* aFile);
+ static inline void DumpAsDataURI(SourceSurface* aSourceSurface) {
+ DumpAsDataURI(aSourceSurface, stdout);
+ }
+ static void DumpAsDataURI(DrawTarget* aDT, FILE* aFile);
+ static inline void DumpAsDataURI(DrawTarget* aDT) {
+ DumpAsDataURI(aDT, stdout);
+ }
+ static nsCString GetAsDataURI(SourceSurface* aSourceSurface);
+ static nsCString GetAsDataURI(DrawTarget* aDT);
+ static nsCString GetAsLZ4Base64Str(DataSourceSurface* aSourceSurface);
+
+ static mozilla::UniquePtr<uint8_t[]> GetImageBuffer(
+ DataSourceSurface* aSurface, bool aIsAlphaPremultiplied,
+ int32_t* outFormat);
+
+ static mozilla::UniquePtr<uint8_t[]> GetImageBufferWithRandomNoise(
+ DataSourceSurface* aSurface, bool aIsAlphaPremultiplied,
+ nsICookieJarSettings* aCookieJarSettings, int32_t* outFormat);
+
+ static nsresult GetInputStream(DataSourceSurface* aSurface,
+ bool aIsAlphaPremultiplied,
+ const char* aMimeType,
+ const nsAString& aEncoderOptions,
+ nsIInputStream** outStream);
+
+ static nsresult GetInputStreamWithRandomNoise(
+ DataSourceSurface* aSurface, bool aIsAlphaPremultiplied,
+ const char* aMimeType, const nsAString& aEncoderOptions,
+ nsICookieJarSettings* aCookieJarSettings, nsIInputStream** outStream);
+
+ static void RemoveShaderCacheFromDiskIfNecessary();
+
+ /**
+ * Copy to the clipboard as a PNG encoded Data URL.
+ */
+ static void CopyAsDataURI(SourceSurface* aSourceSurface);
+ static void CopyAsDataURI(DrawTarget* aDT);
+
+ static bool DumpDisplayList();
+
+ static FILE* sDumpPaintFile;
+};
+
+namespace mozilla {
+
+// Container for either a single element of type T, or an nsTArray<T>.
+// Provides a minimal subset of nsTArray's API, just enough to support use
+// by ContextState for the clipsAndTransforms list, and by gfxTextRun for
+// its mGlyphRuns.
+// Using this instead of a simple nsTArray avoids an extra allocation in the
+// common case where no more than one element is ever added to the list.
+// Unlike an AutoTArray<..., 1>, this class is memmovable and therefore can
+// be used in ContextState without breaking its movability.
+template <typename T>
+class ElementOrArray {
+ union {
+ T mElement;
+ nsTArray<T> mArray;
+ };
+ enum class Tag : uint8_t {
+ Element,
+ Array,
+ } mTag;
+
+ // gfxTextRun::SortGlyphRuns and SanitizeGlyphRuns directly access the array.
+ friend class ::gfxTextRun;
+ nsTArray<T>& Array() {
+ MOZ_DIAGNOSTIC_ASSERT(mTag == Tag::Array);
+ return mArray;
+ }
+
+ public:
+ // Construct as an empty array.
+ ElementOrArray() : mTag(Tag::Array) { new (&mArray) nsTArray<T>(); }
+
+ // For now, don't support copy/move.
+ ElementOrArray(const ElementOrArray&) = delete;
+ ElementOrArray(ElementOrArray&&) = delete;
+
+ ElementOrArray& operator=(const ElementOrArray&) = delete;
+ ElementOrArray& operator=(ElementOrArray&&) = delete;
+
+ // Destroy the appropriate variant.
+ ~ElementOrArray() {
+ switch (mTag) {
+ case Tag::Element:
+ mElement.~T();
+ break;
+ case Tag::Array:
+ mArray.~nsTArray();
+ break;
+ }
+ }
+
+ size_t Length() const { return mTag == Tag::Element ? 1 : mArray.Length(); }
+
+ T* AppendElement(const T& aElement) {
+ switch (mTag) {
+ case Tag::Element: {
+ // Move the existing element into an array, then append the new one.
+ T temp = std::move(mElement);
+ mElement.~T();
+ mTag = Tag::Array;
+ new (&mArray) nsTArray<T>();
+ mArray.AppendElement(std::move(temp));
+ return mArray.AppendElement(aElement);
+ }
+ case Tag::Array: {
+ // If currently empty, just store the element directly.
+ if (mArray.IsEmpty()) {
+ mArray.~nsTArray();
+ mTag = Tag::Element;
+ new (&mElement) T(aElement);
+ return &mElement;
+ }
+ // Otherwise, append it to the array.
+ return mArray.AppendElement(aElement);
+ }
+ default:
+ MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("invalid tag");
+ }
+ }
+
+ const T& LastElement() const {
+ return mTag == Tag::Element ? mElement : mArray.LastElement();
+ }
+
+ T& LastElement() {
+ return mTag == Tag::Element ? mElement : mArray.LastElement();
+ }
+
+ bool IsEmpty() const {
+ return mTag == Tag::Element ? false : mArray.IsEmpty();
+ }
+
+ void TruncateLength(uint32_t aLength = 0) {
+ MOZ_DIAGNOSTIC_ASSERT(aLength <= Length());
+ switch (mTag) {
+ case Tag::Element:
+ if (aLength == 0) {
+ // Destroy the single element, and convert to an empty array.
+ mElement.~T();
+ mTag = Tag::Array;
+ new (&mArray) nsTArray<T>();
+ }
+ break;
+ case Tag::Array:
+ mArray.TruncateLength(aLength);
+ break;
+ }
+ }
+
+ void Clear() {
+ switch (mTag) {
+ case Tag::Element:
+ mElement.~T();
+ mTag = Tag::Array;
+ new (&mArray) nsTArray<T>();
+ break;
+ case Tag::Array:
+ mArray.Clear();
+ break;
+ }
+ }
+
+ // Convert from Array to Element storage. Only to be used when the current
+ // state is a single-element array!
+ void ConvertToElement() {
+ MOZ_DIAGNOSTIC_ASSERT(mTag == Tag::Array && mArray.Length() == 1);
+ T temp = std::move(mArray[0]);
+ mArray.~nsTArray();
+ mTag = Tag::Element;
+ new (&mElement) T(std::move(temp));
+ }
+
+ const T& operator[](uint32_t aIndex) const {
+ MOZ_DIAGNOSTIC_ASSERT(aIndex < Length());
+ return mTag == Tag::Element ? mElement : mArray[aIndex];
+ }
+ T& operator[](uint32_t aIndex) {
+ MOZ_DIAGNOSTIC_ASSERT(aIndex < Length());
+ return mTag == Tag::Element ? mElement : mArray[aIndex];
+ }
+
+ // Simple iterators to support range-for loops.
+ const T* begin() const {
+ return mTag == Tag::Array ? mArray.IsEmpty() ? nullptr : &*mArray.begin()
+ : &mElement;
+ }
+ T* begin() {
+ return mTag == Tag::Array ? mArray.IsEmpty() ? nullptr : &*mArray.begin()
+ : &mElement;
+ }
+
+ const T* end() const {
+ return mTag == Tag::Array ? begin() + mArray.Length() : &mElement + 1;
+ }
+ T* end() {
+ return mTag == Tag::Array ? begin() + mArray.Length() : &mElement + 1;
+ }
+
+ size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
+ return mTag == Tag::Array ? mArray.ShallowSizeOfExcludingThis(aMallocSizeOf)
+ : 0;
+ }
+};
+
+struct StyleAbsoluteColor;
+
+namespace gfx {
+
+/**
+ * If the CMS mode is CMSMode::All, these functions transform the passed
+ * color to a device color using the transform returned by
+ * gfxPlatform::GetCMSRGBTransform(). If the CMS mode is some other value, the
+ * color is returned unchanged (other than a type change to Moz2D Color, if
+ * applicable).
+ */
+DeviceColor ToDeviceColor(const sRGBColor&);
+DeviceColor ToDeviceColor(const StyleAbsoluteColor&);
+DeviceColor ToDeviceColor(nscolor);
+
+sRGBColor ToSRGBColor(const StyleAbsoluteColor&);
+
+/**
+ * Performs a checked multiply of the given width, height, and bytes-per-pixel
+ * values.
+ */
+static inline CheckedInt<uint32_t> SafeBytesForBitmap(uint32_t aWidth,
+ uint32_t aHeight,
+ unsigned aBytesPerPixel) {
+ MOZ_ASSERT(aBytesPerPixel > 0);
+ CheckedInt<uint32_t> width = uint32_t(aWidth);
+ CheckedInt<uint32_t> height = uint32_t(aHeight);
+ return width * height * aBytesPerPixel;
+}
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif
diff --git a/gfx/thebes/gfxWindowsNativeDrawing.cpp b/gfx/thebes/gfxWindowsNativeDrawing.cpp
new file mode 100644
index 0000000000..548ec56869
--- /dev/null
+++ b/gfx/thebes/gfxWindowsNativeDrawing.cpp
@@ -0,0 +1,302 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+
+#include "nsMathUtils.h"
+
+#include "gfxWindowsNativeDrawing.h"
+#include "gfxWindowsSurface.h"
+#include "gfxAlphaRecovery.h"
+#include "gfxPattern.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Helpers.h"
+#include "gfx2DGlue.h"
+
+#include "cairo.h"
+#include "cairo-win32.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+enum {
+ RENDER_STATE_INIT,
+
+ RENDER_STATE_NATIVE_DRAWING,
+ RENDER_STATE_NATIVE_DRAWING_DONE,
+
+ RENDER_STATE_ALPHA_RECOVERY_BLACK,
+ RENDER_STATE_ALPHA_RECOVERY_BLACK_DONE,
+ RENDER_STATE_ALPHA_RECOVERY_WHITE,
+ RENDER_STATE_ALPHA_RECOVERY_WHITE_DONE,
+
+ RENDER_STATE_DONE
+};
+
+gfxWindowsNativeDrawing::gfxWindowsNativeDrawing(gfxContext* ctx,
+ const gfxRect& nativeRect,
+ uint32_t nativeDrawFlags)
+ : mContext(ctx),
+ mNativeRect(nativeRect),
+ mNativeDrawFlags(nativeDrawFlags),
+ mRenderState(RENDER_STATE_INIT) {}
+
+HDC gfxWindowsNativeDrawing::BeginNativeDrawing() {
+ if (mRenderState == RENDER_STATE_INIT) {
+ RefPtr<gfxASurface> surf;
+ DrawTarget* drawTarget = mContext->GetDrawTarget();
+ cairo_t* cairo = nullptr;
+ if (drawTarget->GetBackendType() == BackendType::CAIRO) {
+ cairo = static_cast<cairo_t*>(
+ drawTarget->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT));
+ if (cairo) {
+ cairo_surface_t* s = cairo_get_group_target(cairo);
+ if (s) {
+ mDeviceOffset = mContext->GetDeviceOffset();
+ double sdx, sdy;
+ cairo_surface_get_device_offset(s, &sdx, &sdy);
+ mDeviceOffset.x -= sdx;
+ mDeviceOffset.y -= sdy;
+ surf = gfxASurface::Wrap(s);
+ }
+ }
+ }
+
+ if (surf && surf->CairoStatus() != 0) return nullptr;
+
+ gfxMatrix m = mContext->CurrentMatrixDouble();
+ if (!m.HasNonTranslation())
+ mTransformType = TRANSLATION_ONLY;
+ else if (m.HasNonAxisAlignedTransform())
+ mTransformType = COMPLEX;
+ else
+ mTransformType = AXIS_ALIGNED_SCALE;
+
+ // if this is a native win32 surface, we don't have to
+ // redirect rendering to our own HDC; in some cases,
+ // we may be able to use the HDC from the surface directly.
+ if (surf && ((surf->GetType() == gfxSurfaceType::Win32 ||
+ surf->GetType() == gfxSurfaceType::Win32Printing) &&
+ (surf->GetContentType() == gfxContentType::COLOR ||
+ (surf->GetContentType() == gfxContentType::COLOR_ALPHA &&
+ (mNativeDrawFlags & CAN_DRAW_TO_COLOR_ALPHA))))) {
+ // grab the DC. This can fail if there is a complex clipping path,
+ // in which case we'll have to fall back.
+ mWinSurface = static_cast<gfxWindowsSurface*>(
+ static_cast<gfxASurface*>(surf.get()));
+ mDC = cairo_win32_get_dc_with_clip(cairo);
+
+ if (mDC) {
+ if (mTransformType == TRANSLATION_ONLY) {
+ mRenderState = RENDER_STATE_NATIVE_DRAWING;
+
+ mTranslation = m.GetTranslation();
+ } else if (((mTransformType == AXIS_ALIGNED_SCALE) &&
+ (mNativeDrawFlags & CAN_AXIS_ALIGNED_SCALE)) ||
+ (mNativeDrawFlags & CAN_COMPLEX_TRANSFORM)) {
+ mWorldTransform.eM11 = (FLOAT)m._11;
+ mWorldTransform.eM12 = (FLOAT)m._12;
+ mWorldTransform.eM21 = (FLOAT)m._21;
+ mWorldTransform.eM22 = (FLOAT)m._22;
+ mWorldTransform.eDx = (FLOAT)m._31;
+ mWorldTransform.eDy = (FLOAT)m._32;
+
+ mRenderState = RENDER_STATE_NATIVE_DRAWING;
+ }
+ }
+ }
+
+ // If we couldn't do native drawing, then we have to do two-buffer drawing
+ // and do alpha recovery
+ if (mRenderState == RENDER_STATE_INIT) {
+ mRenderState = RENDER_STATE_ALPHA_RECOVERY_BLACK;
+
+ // We round out our native rect here, that way the snapping will
+ // happen correctly.
+ mNativeRect.RoundOut();
+
+ // we only do the scale bit if we can do an axis aligned
+ // scale; otherwise we scale (if necessary) after
+ // rendering with cairo. Note that if we're doing alpha recovery,
+ // we cannot do a full complex transform with win32 (I mean, we could, but
+ // it would require more code that's not here.)
+ if (mTransformType == TRANSLATION_ONLY ||
+ !(mNativeDrawFlags & CAN_AXIS_ALIGNED_SCALE)) {
+ mScale = MatrixScalesDouble();
+
+ // Add 1 to the surface size; it's guaranteed to not be incorrect,
+ // and it fixes bug 382458
+ // There's probably a better fix, but I haven't figured out
+ // the root cause of the problem.
+ mTempSurfaceSize = IntSize((int32_t)ceil(mNativeRect.Width() + 1),
+ (int32_t)ceil(mNativeRect.Height() + 1));
+ } else {
+ // figure out the scale factors
+ mScale = m.ScaleFactors();
+
+ mWorldTransform.eM11 = (FLOAT)mScale.xScale;
+ mWorldTransform.eM12 = 0.0f;
+ mWorldTransform.eM21 = 0.0f;
+ mWorldTransform.eM22 = (FLOAT)mScale.yScale;
+ mWorldTransform.eDx = 0.0f;
+ mWorldTransform.eDy = 0.0f;
+
+ // See comment above about "+1"
+ mTempSurfaceSize =
+ IntSize((int32_t)ceil(mNativeRect.Width() * mScale.xScale + 1),
+ (int32_t)ceil(mNativeRect.Height() * mScale.yScale + 1));
+ }
+ }
+ }
+
+ if (mRenderState == RENDER_STATE_NATIVE_DRAWING) {
+ // we can just do native drawing directly to the context's surface
+
+ // do we need to use SetWorldTransform?
+ if (mTransformType != TRANSLATION_ONLY) {
+ SetGraphicsMode(mDC, GM_ADVANCED);
+ GetWorldTransform(mDC, &mOldWorldTransform);
+ SetWorldTransform(mDC, &mWorldTransform);
+ }
+ GetViewportOrgEx(mDC, &mOrigViewportOrigin);
+ SetViewportOrgEx(mDC, mOrigViewportOrigin.x - (int)mDeviceOffset.x,
+ mOrigViewportOrigin.y - (int)mDeviceOffset.y, nullptr);
+
+ return mDC;
+ } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_BLACK ||
+ mRenderState == RENDER_STATE_ALPHA_RECOVERY_WHITE) {
+ // we're going to use mWinSurface to create our temporary surface here
+
+ // get us a RGB24 DIB; DIB is important, because
+ // we can later call GetImageSurface on it.
+ mWinSurface = new gfxWindowsSurface(mTempSurfaceSize);
+ mDC = mWinSurface->GetDC();
+
+ RECT r = {0, 0, mTempSurfaceSize.width, mTempSurfaceSize.height};
+ if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_BLACK)
+ FillRect(mDC, &r, (HBRUSH)GetStockObject(BLACK_BRUSH));
+ else
+ FillRect(mDC, &r, (HBRUSH)GetStockObject(WHITE_BRUSH));
+
+ if ((mTransformType != TRANSLATION_ONLY) &&
+ (mNativeDrawFlags & CAN_AXIS_ALIGNED_SCALE)) {
+ SetGraphicsMode(mDC, GM_ADVANCED);
+ SetWorldTransform(mDC, &mWorldTransform);
+ }
+
+ return mDC;
+ } else {
+ NS_ERROR("Bogus render state!");
+ return nullptr;
+ }
+}
+
+bool gfxWindowsNativeDrawing::ShouldRenderAgain() {
+ switch (mRenderState) {
+ case RENDER_STATE_NATIVE_DRAWING_DONE:
+ return false;
+
+ case RENDER_STATE_ALPHA_RECOVERY_BLACK_DONE:
+ mRenderState = RENDER_STATE_ALPHA_RECOVERY_WHITE;
+ return true;
+
+ case RENDER_STATE_ALPHA_RECOVERY_WHITE_DONE:
+ return false;
+
+ default:
+ NS_ERROR(
+ "Invalid RenderState in gfxWindowsNativeDrawing::ShouldRenderAgain");
+ break;
+ }
+
+ return false;
+}
+
+void gfxWindowsNativeDrawing::EndNativeDrawing() {
+ if (mRenderState == RENDER_STATE_NATIVE_DRAWING) {
+ // we drew directly to the HDC in the context; undo our changes
+ SetViewportOrgEx(mDC, mOrigViewportOrigin.x, mOrigViewportOrigin.y,
+ nullptr);
+
+ if (mTransformType != TRANSLATION_ONLY)
+ SetWorldTransform(mDC, &mOldWorldTransform);
+
+ mWinSurface->MarkDirty();
+
+ mRenderState = RENDER_STATE_NATIVE_DRAWING_DONE;
+ } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_BLACK) {
+ mBlackSurface = mWinSurface;
+ mWinSurface = nullptr;
+
+ mRenderState = RENDER_STATE_ALPHA_RECOVERY_BLACK_DONE;
+ } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_WHITE) {
+ mWhiteSurface = mWinSurface;
+ mWinSurface = nullptr;
+
+ mRenderState = RENDER_STATE_ALPHA_RECOVERY_WHITE_DONE;
+ } else {
+ NS_ERROR(
+ "Invalid RenderState in gfxWindowsNativeDrawing::EndNativeDrawing");
+ }
+}
+
+void gfxWindowsNativeDrawing::PaintToContext() {
+ if (mRenderState == RENDER_STATE_NATIVE_DRAWING_DONE) {
+ // nothing to do, it already went to the context
+ mRenderState = RENDER_STATE_DONE;
+ } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_WHITE_DONE) {
+ RefPtr<gfxImageSurface> black = mBlackSurface->GetAsImageSurface();
+ RefPtr<gfxImageSurface> white = mWhiteSurface->GetAsImageSurface();
+ if (!gfxAlphaRecovery::RecoverAlpha(black, white)) {
+ NS_ERROR("Alpha recovery failure");
+ return;
+ }
+ RefPtr<DataSourceSurface> source = Factory::CreateWrappingDataSourceSurface(
+ black->Data(), black->Stride(), black->GetSize(),
+ SurfaceFormat::B8G8R8A8);
+ {
+ DrawTarget* dt = mContext->GetDrawTarget();
+ AutoRestoreTransform autoRestoreTransform(dt);
+
+ Matrix newTransform = dt->GetTransform();
+ newTransform.PreTranslate(ToPoint(mNativeRect.TopLeft()));
+ dt->SetTransform(newTransform);
+
+ Rect rect(Point(0.0, 0.0), ToSize(mNativeRect.Size()));
+ Matrix m = Matrix::Scaling(1.0 / mScale.xScale, 1.0 / mScale.yScale);
+ SurfacePattern pat(source, ExtendMode::CLAMP, m);
+ dt->FillRect(rect, pat);
+ }
+
+ mRenderState = RENDER_STATE_DONE;
+ } else {
+ NS_ERROR("Invalid RenderState in gfxWindowsNativeDrawing::PaintToContext");
+ }
+}
+
+void gfxWindowsNativeDrawing::TransformToNativeRect(const gfxRect& r,
+ RECT& rout) {
+ /* If we're doing native drawing, then we're still in the coordinate space
+ * of the context; otherwise, we're in our own little world,
+ * relative to the passed-in nativeRect.
+ */
+
+ gfxRect roundedRect(r);
+
+ if (mRenderState == RENDER_STATE_NATIVE_DRAWING) {
+ if (mTransformType == TRANSLATION_ONLY) {
+ roundedRect.MoveBy(mTranslation);
+ }
+ } else {
+ roundedRect.MoveBy(-mNativeRect.TopLeft());
+ }
+
+ roundedRect.Round();
+
+ rout.left = LONG(roundedRect.X());
+ rout.right = LONG(roundedRect.XMost());
+ rout.top = LONG(roundedRect.Y());
+ rout.bottom = LONG(roundedRect.YMost());
+}
diff --git a/gfx/thebes/gfxWindowsNativeDrawing.h b/gfx/thebes/gfxWindowsNativeDrawing.h
new file mode 100644
index 0000000000..87323966d9
--- /dev/null
+++ b/gfx/thebes/gfxWindowsNativeDrawing.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _GFXWINDOWSNATIVEDRAWING_H_
+#define _GFXWINDOWSNATIVEDRAWING_H_
+
+#include <windows.h>
+
+#include "gfxContext.h"
+#include "gfxWindowsSurface.h"
+
+class MOZ_STACK_CLASS gfxWindowsNativeDrawing {
+ public:
+ /* Flags for notifying this class what kind of operations the native
+ * drawing supports
+ */
+
+ enum {
+ /* Whether the native drawing can draw to a surface of content COLOR_ALPHA
+ */
+ CAN_DRAW_TO_COLOR_ALPHA = 1 << 0,
+ CANNOT_DRAW_TO_COLOR_ALPHA = 0 << 0,
+
+ /* Whether the native drawing can be scaled using SetWorldTransform */
+ CAN_AXIS_ALIGNED_SCALE = 1 << 1,
+ CANNOT_AXIS_ALIGNED_SCALE = 0 << 1,
+
+ /* Whether the native drawing can be both scaled and rotated arbitrarily
+ using SetWorldTransform */
+ CAN_COMPLEX_TRANSFORM = 1 << 2,
+ CANNOT_COMPLEX_TRANSFORM = 0 << 2,
+ };
+
+ /* Create native win32 drawing for a rectangle bounded by
+ * nativeRect.
+ *
+ * Typical usage looks like:
+ *
+ * gfxWindowsNativeDrawing nativeDraw(ctx, destGfxRect, capabilities);
+ * do {
+ * HDC dc = nativeDraw.BeginNativeDrawing();
+ * if (!dc)
+ * return NS_ERROR_FAILURE;
+ *
+ * RECT winRect;
+ * nativeDraw.TransformToNativeRect(rect, winRect);
+ *
+ * ... call win32 operations on HDC to draw to winRect ...
+ *
+ * nativeDraw.EndNativeDrawing();
+ * } while (nativeDraw.ShouldRenderAgain());
+ * nativeDraw.PaintToContext();
+ */
+ gfxWindowsNativeDrawing(
+ gfxContext* ctx, const gfxRect& nativeRect,
+ uint32_t nativeDrawFlags = CANNOT_DRAW_TO_COLOR_ALPHA |
+ CANNOT_AXIS_ALIGNED_SCALE |
+ CANNOT_COMPLEX_TRANSFORM);
+
+ /* Returns a HDC which may be used for native drawing. This HDC is valid
+ * until EndNativeDrawing is called; if it is used for drawing after that
+ * time, the result is undefined. */
+ HDC BeginNativeDrawing();
+
+ /* Transform the native rect into something valid for rendering
+ * to the HDC. This may or may not change RECT, depending on
+ * whether SetWorldTransform is used or not. */
+ void TransformToNativeRect(const gfxRect& r, RECT& rout);
+
+ /* Marks the end of native drawing */
+ void EndNativeDrawing();
+
+ /* Returns true if the native drawing should be executed again */
+ bool ShouldRenderAgain();
+
+ /* Places the result to the context, if necessary */
+ void PaintToContext();
+
+ private:
+ gfxContext* mContext;
+ gfxRect mNativeRect;
+ uint32_t mNativeDrawFlags;
+
+ // what state the rendering is in
+ uint8_t mRenderState;
+
+ mozilla::gfx::Point mDeviceOffset;
+ RefPtr<gfxPattern> mBlackPattern, mWhitePattern;
+
+ enum TransformType { TRANSLATION_ONLY, AXIS_ALIGNED_SCALE, COMPLEX };
+
+ TransformType mTransformType;
+ gfxPoint mTranslation;
+ mozilla::gfx::MatrixScalesDouble mScale;
+ XFORM mWorldTransform;
+
+ // saved state
+ RefPtr<gfxWindowsSurface> mWinSurface, mBlackSurface, mWhiteSurface;
+ HDC mDC;
+ XFORM mOldWorldTransform;
+ POINT mOrigViewportOrigin;
+ mozilla::gfx::IntSize mTempSurfaceSize;
+};
+
+#endif
diff --git a/gfx/thebes/gfxWindowsPlatform.cpp b/gfx/thebes/gfxWindowsPlatform.cpp
new file mode 100644
index 0000000000..6c88c524c8
--- /dev/null
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -0,0 +1,1956 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define INITGUID // set before devguid.h
+
+#include "gfxWindowsPlatform.h"
+
+#include "cairo.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+
+#include "gfxBlur.h"
+#include "gfxImageSurface.h"
+#include "gfxWindowsSurface.h"
+
+#include "nsUnicharUtils.h"
+#include "nsUnicodeProperties.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ProfilerThreadSleep.h"
+#include "mozilla/Components.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsIGfxInfo.h"
+#include "nsServiceManagerUtils.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Telemetry.h"
+
+#include "plbase64.h"
+#include "nsIXULRuntime.h"
+#include "imgLoader.h"
+
+#include "nsIGfxInfo.h"
+
+#include "gfxCrashReporterUtils.h"
+
+#include "gfxGDIFontList.h"
+#include "gfxGDIFont.h"
+
+#include "mozilla/layers/CanvasChild.h"
+#include "mozilla/layers/CompositorThread.h"
+
+#include "gfxDWriteFontList.h"
+#include "gfxDWriteFonts.h"
+#include "gfxDWriteCommon.h"
+#include <dwrite.h>
+
+#include "gfxTextRun.h"
+#include "gfxUserFontSet.h"
+#include "nsWindowsHelpers.h"
+#include "gfx2DGlue.h"
+
+#include <string>
+
+#include <d3d10_1.h>
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/gfxVars.h"
+
+#include <dwmapi.h>
+#include <d3d11.h>
+#include <d2d1_1.h>
+
+#include "nsIMemoryReporter.h"
+#include <winternl.h>
+#include "d3dkmtQueryStatistics.h"
+
+#include "base/thread.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/StaticPrefs_layers.h"
+#include "gfxConfig.h"
+#include "VsyncSource.h"
+#include "DriverCrashGuard.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/gfx/DisplayConfigWindows.h"
+#include "mozilla/layers/DeviceAttachmentsD3D11.h"
+#include "mozilla/WindowsProcessMitigations.h"
+#include "D3D11Checks.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+using namespace mozilla::image;
+using namespace mozilla::unicode;
+
+DCForMetrics::DCForMetrics() {
+ // Get the whole screen DC:
+ mDC = GetDC(nullptr);
+ SetGraphicsMode(mDC, GM_ADVANCED);
+}
+
+class GfxD2DVramReporter final : public nsIMemoryReporter {
+ ~GfxD2DVramReporter() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ MOZ_COLLECT_REPORT("gfx-d2d-vram-draw-target", KIND_OTHER, UNITS_BYTES,
+ Factory::GetD2DVRAMUsageDrawTarget(),
+ "Video memory used by D2D DrawTargets.");
+
+ MOZ_COLLECT_REPORT("gfx-d2d-vram-source-surface", KIND_OTHER, UNITS_BYTES,
+ Factory::GetD2DVRAMUsageSourceSurface(),
+ "Video memory used by D2D SourceSurfaces.");
+
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(GfxD2DVramReporter, nsIMemoryReporter)
+
+class GPUAdapterReporter final : public nsIMemoryReporter {
+ // Callers must Release the DXGIAdapter after use or risk mem-leak
+ static bool GetDXGIAdapter(IDXGIAdapter** aDXGIAdapter) {
+ RefPtr<ID3D11Device> d3d11Device;
+ RefPtr<IDXGIDevice> dxgiDevice;
+ bool result = false;
+
+ if ((d3d11Device = mozilla::gfx::Factory::GetDirect3D11Device())) {
+ if (d3d11Device->QueryInterface(__uuidof(IDXGIDevice),
+ getter_AddRefs(dxgiDevice)) == S_OK) {
+ result = (dxgiDevice->GetAdapter(aDXGIAdapter) == S_OK);
+ }
+ }
+
+ return result;
+ }
+
+ ~GPUAdapterReporter() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD
+ CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) override {
+ HANDLE ProcessHandle = GetCurrentProcess();
+
+ int64_t dedicatedBytesUsed = 0;
+ int64_t sharedBytesUsed = 0;
+ int64_t committedBytesUsed = 0;
+ IDXGIAdapter* DXGIAdapter;
+
+ HMODULE gdi32Handle;
+ PFND3DKMTQS queryD3DKMTStatistics = nullptr;
+
+ if ((gdi32Handle = LoadLibrary(TEXT("gdi32.dll"))))
+ queryD3DKMTStatistics =
+ (PFND3DKMTQS)GetProcAddress(gdi32Handle, "D3DKMTQueryStatistics");
+
+ if (queryD3DKMTStatistics && GetDXGIAdapter(&DXGIAdapter)) {
+ // Most of this block is understood thanks to wj32's work on Process
+ // Hacker
+
+ DXGI_ADAPTER_DESC adapterDesc;
+ D3DKMTQS queryStatistics;
+
+ DXGIAdapter->GetDesc(&adapterDesc);
+ DXGIAdapter->Release();
+
+ memset(&queryStatistics, 0, sizeof(D3DKMTQS));
+ queryStatistics.Type = D3DKMTQS_PROCESS;
+ queryStatistics.AdapterLuid = adapterDesc.AdapterLuid;
+ queryStatistics.hProcess = ProcessHandle;
+ if (NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) {
+ committedBytesUsed =
+ queryStatistics.QueryResult.ProcessInfo.SystemMemory.BytesAllocated;
+ }
+
+ memset(&queryStatistics, 0, sizeof(D3DKMTQS));
+ queryStatistics.Type = D3DKMTQS_ADAPTER;
+ queryStatistics.AdapterLuid = adapterDesc.AdapterLuid;
+ if (NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) {
+ ULONG i;
+ ULONG segmentCount = queryStatistics.QueryResult.AdapterInfo.NbSegments;
+
+ for (i = 0; i < segmentCount; i++) {
+ memset(&queryStatistics, 0, sizeof(D3DKMTQS));
+ queryStatistics.Type = D3DKMTQS_SEGMENT;
+ queryStatistics.AdapterLuid = adapterDesc.AdapterLuid;
+ queryStatistics.QuerySegment.SegmentId = i;
+
+ if (NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) {
+ bool aperture;
+
+ // SegmentInformation has a different definition in Win7 than later
+ // versions
+ if (!IsWin8OrLater())
+ aperture = queryStatistics.QueryResult.SegmentInfoWin7.Aperture;
+ else
+ aperture = queryStatistics.QueryResult.SegmentInfoWin8.Aperture;
+
+ memset(&queryStatistics, 0, sizeof(D3DKMTQS));
+ queryStatistics.Type = D3DKMTQS_PROCESS_SEGMENT;
+ queryStatistics.AdapterLuid = adapterDesc.AdapterLuid;
+ queryStatistics.hProcess = ProcessHandle;
+ queryStatistics.QueryProcessSegment.SegmentId = i;
+ if (NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) {
+ ULONGLONG bytesCommitted;
+ if (!IsWin8OrLater())
+ bytesCommitted = queryStatistics.QueryResult.ProcessSegmentInfo
+ .Win7.BytesCommitted;
+ else
+ bytesCommitted = queryStatistics.QueryResult.ProcessSegmentInfo
+ .Win8.BytesCommitted;
+ if (aperture)
+ sharedBytesUsed += bytesCommitted;
+ else
+ dedicatedBytesUsed += bytesCommitted;
+ }
+ }
+ }
+ }
+ }
+
+ FreeLibrary(gdi32Handle);
+
+ MOZ_COLLECT_REPORT("gpu-committed", KIND_OTHER, UNITS_BYTES,
+ committedBytesUsed,
+ "Memory committed by the Windows graphics system.");
+
+ MOZ_COLLECT_REPORT(
+ "gpu-dedicated", KIND_OTHER, UNITS_BYTES, dedicatedBytesUsed,
+ "Out-of-process memory allocated for this process in a physical "
+ "GPU adapter's memory.");
+
+ MOZ_COLLECT_REPORT("gpu-shared", KIND_OTHER, UNITS_BYTES, sharedBytesUsed,
+ "In-process memory that is shared with the GPU.");
+
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(GPUAdapterReporter, nsIMemoryReporter)
+
+Atomic<size_t> gfxWindowsPlatform::sD3D11SharedTextures;
+Atomic<size_t> gfxWindowsPlatform::sD3D9SharedTextures;
+
+class D3DSharedTexturesReporter final : public nsIMemoryReporter {
+ ~D3DSharedTexturesReporter() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ if (gfxWindowsPlatform::sD3D11SharedTextures > 0) {
+ MOZ_COLLECT_REPORT("d3d11-shared-textures", KIND_OTHER, UNITS_BYTES,
+ gfxWindowsPlatform::sD3D11SharedTextures,
+ "D3D11 shared textures.");
+ }
+
+ if (gfxWindowsPlatform::sD3D9SharedTextures > 0) {
+ MOZ_COLLECT_REPORT("d3d9-shared-textures", KIND_OTHER, UNITS_BYTES,
+ gfxWindowsPlatform::sD3D9SharedTextures,
+ "D3D9 shared textures.");
+ }
+
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(D3DSharedTexturesReporter, nsIMemoryReporter)
+
+gfxWindowsPlatform::gfxWindowsPlatform()
+ : mRenderMode(RENDER_GDI),
+ mSupportsHDR(false),
+ mDwmCompositionStatus(DwmCompositionStatus::Unknown) {
+ // If win32k is locked down then we can't use COM STA and shouldn't need it.
+ // Also, we won't be using any GPU memory in this process.
+ if (!IsWin32kLockedDown()) {
+ /*
+ * Initialize COM
+ */
+ CoInitialize(nullptr);
+
+ RegisterStrongMemoryReporter(new GfxD2DVramReporter());
+ RegisterStrongMemoryReporter(new GPUAdapterReporter());
+ RegisterStrongMemoryReporter(new D3DSharedTexturesReporter());
+ }
+}
+
+gfxWindowsPlatform::~gfxWindowsPlatform() {
+ mozilla::gfx::Factory::D2DCleanup();
+
+ DeviceManagerDx::Shutdown();
+
+ // We don't initialize COM when win32k is locked down.
+ if (!IsWin32kLockedDown()) {
+ /*
+ * Uninitialize COM
+ */
+ CoUninitialize();
+ }
+}
+
+/* static */
+void gfxWindowsPlatform::InitMemoryReportersForGPUProcess() {
+ MOZ_RELEASE_ASSERT(XRE_IsGPUProcess());
+
+ RegisterStrongMemoryReporter(new GfxD2DVramReporter());
+ RegisterStrongMemoryReporter(new GPUAdapterReporter());
+ RegisterStrongMemoryReporter(new D3DSharedTexturesReporter());
+}
+
+/* static */
+nsresult gfxWindowsPlatform::GetGpuTimeSinceProcessStartInMs(
+ uint64_t* aResult) {
+ // If win32k is locked down then we should not have any GPU processing and
+ // cannot use these APIs either way.
+ if (IsWin32kLockedDown()) {
+ *aResult = 0;
+ return NS_OK;
+ }
+
+ nsModuleHandle module(LoadLibrary(L"gdi32.dll"));
+ if (!module) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ PFND3DKMTQS queryD3DKMTStatistics =
+ (PFND3DKMTQS)GetProcAddress(module, "D3DKMTQueryStatistics");
+ if (!queryD3DKMTStatistics) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ gfx::DeviceManagerDx* dm = DeviceManagerDx::Get();
+ if (!dm) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ D3D11DeviceStatus status;
+ if (!dm->ExportDeviceInfo(&status)) {
+ // Assume that we used 0ms of GPU time if the device manager
+ // doesn't know the device status.
+ *aResult = 0;
+ return NS_OK;
+ }
+
+ const DxgiAdapterDesc& adapterDesc = status.adapter();
+
+ D3DKMTQS queryStatistics;
+ memset(&queryStatistics, 0, sizeof(D3DKMTQS));
+ queryStatistics.Type = D3DKMTQS_ADAPTER;
+ queryStatistics.AdapterLuid = adapterDesc.AdapterLuid;
+ if (!NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint64_t result = 0;
+ ULONG nodeCount = queryStatistics.QueryResult.AdapterInfo.NodeCount;
+ for (ULONG i = 0; i < nodeCount; ++i) {
+ memset(&queryStatistics, 0, sizeof(D3DKMTQS));
+ queryStatistics.Type = D3DKMTQS_PROCESS_NODE;
+ queryStatistics.AdapterLuid = adapterDesc.AdapterLuid;
+ queryStatistics.hProcess = GetCurrentProcess();
+ queryStatistics.QueryProcessNode.NodeId = i;
+ if (NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) {
+ result += queryStatistics.QueryResult.ProcessNodeInformation.RunningTime
+ .QuadPart *
+ 100 / PR_NSEC_PER_MSEC;
+ }
+ }
+
+ *aResult = result;
+ return NS_OK;
+}
+
+static void UpdateANGLEConfig() {
+ if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
+ gfxConfig::Disable(Feature::D3D11_HW_ANGLE, FeatureStatus::Disabled,
+ "D3D11 compositing is disabled",
+ "FEATURE_FAILURE_HW_ANGLE_D3D11_DISABLED"_ns);
+ }
+}
+
+void gfxWindowsPlatform::InitAcceleration() {
+ gfxPlatform::InitAcceleration();
+
+ DeviceManagerDx::Init();
+
+ InitializeConfig();
+ InitGPUProcessSupport();
+ // Ensure devices initialization. SharedSurfaceANGLE and
+ // SharedSurfaceD3D11Interop use them. The devices are lazily initialized
+ // with WebRender to reduce memory usage.
+ // Initialize them now when running non-e10s.
+ if (!BrowserTabsRemoteAutostart()) {
+ EnsureDevicesInitialized();
+ }
+ UpdateANGLEConfig();
+ UpdateRenderMode();
+
+ // If we have Skia and we didn't init dwrite already, do it now.
+ if (!DWriteEnabled() && GetDefaultContentBackend() == BackendType::SKIA) {
+ InitDWriteSupport();
+ }
+ // We need to listen for font setting changes even if DWrite is not used.
+ Factory::SetSystemTextQuality(gfxVars::SystemTextQuality());
+ gfxVars::SetSystemTextQualityListener(
+ gfxDWriteFont::SystemTextQualityChanged);
+
+ if (XRE_IsParentProcess()) {
+ BOOL dwmEnabled = FALSE;
+ if (FAILED(::DwmIsCompositionEnabled(&dwmEnabled)) || !dwmEnabled) {
+ gfxVars::SetDwmCompositionEnabled(false);
+ } else {
+ gfxVars::SetDwmCompositionEnabled(true);
+ }
+ }
+
+ // gfxVars are not atomic, but multiple threads can query DWM status
+ // Therefore, mirror value into an atomic
+ mDwmCompositionStatus = gfxVars::DwmCompositionEnabled()
+ ? DwmCompositionStatus::Enabled
+ : DwmCompositionStatus::Disabled;
+
+ gfxVars::SetDwmCompositionEnabledListener([this] {
+ this->mDwmCompositionStatus = gfxVars::DwmCompositionEnabled()
+ ? DwmCompositionStatus::Enabled
+ : DwmCompositionStatus::Disabled;
+ });
+
+ // CanUseHardwareVideoDecoding depends on DeviceManagerDx state,
+ // so update the cached value now.
+ UpdateCanUseHardwareVideoDecoding();
+ UpdateSupportsHDR();
+
+ RecordStartupTelemetry();
+}
+
+void gfxWindowsPlatform::InitWebRenderConfig() {
+ gfxPlatform::InitWebRenderConfig();
+ UpdateBackendPrefs();
+}
+
+bool gfxWindowsPlatform::CanUseHardwareVideoDecoding() {
+ DeviceManagerDx* dm = DeviceManagerDx::Get();
+ if (!dm) {
+ return false;
+ }
+ if (!dm->TextureSharingWorks()) {
+ return false;
+ }
+ return !dm->IsWARP() && gfxPlatform::CanUseHardwareVideoDecoding();
+}
+
+bool gfxWindowsPlatform::InitDWriteSupport() {
+ mozilla::ScopedGfxFeatureReporter reporter("DWrite");
+ if (!gfxDWriteFont::InitDWriteSupport()) {
+ return false;
+ }
+
+ reporter.SetSuccessful();
+ return true;
+}
+
+bool gfxWindowsPlatform::HandleDeviceReset() {
+ DeviceResetReason resetReason = DeviceResetReason::OK;
+ if (!DidRenderingDeviceReset(&resetReason)) {
+ return false;
+ }
+
+ if (resetReason != DeviceResetReason::FORCED_RESET) {
+ Telemetry::Accumulate(Telemetry::DEVICE_RESET_REASON,
+ uint32_t(resetReason));
+ }
+
+ // Remove devices and adapters.
+ DeviceManagerDx::Get()->ResetDevices();
+
+ imgLoader::NormalLoader()->ClearCache(true);
+ imgLoader::NormalLoader()->ClearCache(false);
+ imgLoader::PrivateBrowsingLoader()->ClearCache(true);
+ imgLoader::PrivateBrowsingLoader()->ClearCache(false);
+ gfxAlphaBoxBlur::ShutdownBlurCache();
+
+ gfxConfig::Reset(Feature::D3D11_COMPOSITING);
+ gfxConfig::Reset(Feature::D3D11_HW_ANGLE);
+ gfxConfig::Reset(Feature::DIRECT2D);
+
+ InitializeConfig();
+ // XXX Add InitWebRenderConfig() calling.
+ if (mInitializedDevices) {
+ InitGPUProcessSupport();
+ InitializeDevices();
+ }
+ UpdateANGLEConfig();
+ return true;
+}
+
+BackendPrefsData gfxWindowsPlatform::GetBackendPrefs() const {
+ BackendPrefsData data;
+
+ data.mCanvasBitmask = BackendTypeBit(BackendType::SKIA);
+ data.mContentBitmask = BackendTypeBit(BackendType::SKIA);
+ data.mCanvasDefault = BackendType::SKIA;
+ data.mContentDefault = BackendType::SKIA;
+
+ if (gfxConfig::IsEnabled(Feature::DIRECT2D)) {
+ data.mCanvasBitmask |= BackendTypeBit(BackendType::DIRECT2D1_1);
+ data.mCanvasDefault = BackendType::DIRECT2D1_1;
+ }
+ return data;
+}
+
+void gfxWindowsPlatform::UpdateBackendPrefs() {
+ BackendPrefsData data = GetBackendPrefs();
+ // Remove DIRECT2D1 preference if D2D1Device does not exist.
+ if (!Factory::HasD2D1Device()) {
+ data.mContentBitmask &= ~BackendTypeBit(BackendType::DIRECT2D1_1);
+ if (data.mContentDefault == BackendType::DIRECT2D1_1) {
+ data.mContentDefault = BackendType::SKIA;
+ }
+
+ // Don't exclude DIRECT2D1_1 if using remote canvas, because DIRECT2D1_1 and
+ // hence the device will be used in the GPU process.
+ if (!gfxPlatform::UseRemoteCanvas()) {
+ data.mCanvasBitmask &= ~BackendTypeBit(BackendType::DIRECT2D1_1);
+ if (data.mCanvasDefault == BackendType::DIRECT2D1_1) {
+ data.mCanvasDefault = BackendType::SKIA;
+ }
+ }
+ }
+ InitBackendPrefs(std::move(data));
+}
+
+bool gfxWindowsPlatform::IsDirect2DBackend() {
+ return GetDefaultContentBackend() == BackendType::DIRECT2D1_1;
+}
+
+void gfxWindowsPlatform::UpdateRenderMode() {
+ bool didReset = HandleDeviceReset();
+
+ UpdateBackendPrefs();
+
+ if (didReset) {
+ mScreenReferenceDrawTarget = CreateOffscreenContentDrawTarget(
+ IntSize(1, 1), SurfaceFormat::B8G8R8A8);
+ if (!mScreenReferenceDrawTarget) {
+ gfxCriticalNote
+ << "Failed to update reference draw target after device reset"
+ << ", D3D11 device:" << hexa(Factory::GetDirect3D11Device().get())
+ << ", D3D11 status:"
+ << FeatureStatusToString(
+ gfxConfig::GetValue(Feature::D3D11_COMPOSITING))
+ << ", D2D1 device:" << hexa(Factory::GetD2D1Device().get())
+ << ", D2D1 status:"
+ << FeatureStatusToString(gfxConfig::GetValue(Feature::DIRECT2D))
+ << ", content:" << int(GetDefaultContentBackend())
+ << ", compositor:" << int(GetCompositorBackend());
+ MOZ_CRASH(
+ "GFX: Failed to update reference draw target after device reset");
+ }
+ }
+}
+
+void gfxWindowsPlatform::UpdateSupportsHDR() {
+ // TODO: This function crashes content processes, for reasons that are not
+ // obvious from the crash reports. For now, this function can only be executed
+ // by the parent process. Therefore SupportsHDR() will always return false for
+ // content processes, as noted in the header.
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ // Set mSupportsHDR to true if any of the DeviceManager outputs have both:
+ // 1) greater than 8-bit color
+ // 2) a colorspace that uses BT2020
+ DeviceManagerDx* dx = DeviceManagerDx::Get();
+ nsTArray<DXGI_OUTPUT_DESC1> outputs = dx->EnumerateOutputs();
+
+ for (auto& output : outputs) {
+ if (output.BitsPerColor <= 8) {
+ continue;
+ }
+
+ switch (output.ColorSpace) {
+ case DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P2020:
+ case DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020:
+ case DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020:
+ case DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020:
+ case DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020:
+ case DXGI_COLOR_SPACE_RGB_STUDIO_G2084_NONE_P2020:
+ case DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_TOPLEFT_P2020:
+ case DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_TOPLEFT_P2020:
+ case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020:
+ case DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020:
+ case DXGI_COLOR_SPACE_YCBCR_FULL_GHLG_TOPLEFT_P2020:
+#ifndef __MINGW32__
+ // Windows MinGW has an older dxgicommon.h that doesn't define
+ // these enums. We'd like to define them ourselves in that case,
+ // but there's no compilable way to add new enums to an existing
+ // enum type. So instead we just don't check for these values.
+ case DXGI_COLOR_SPACE_RGB_STUDIO_G24_NONE_P2020:
+ case DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_LEFT_P2020:
+ case DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_TOPLEFT_P2020:
+#endif
+ mSupportsHDR = true;
+ return;
+ default:
+ break;
+ }
+ }
+
+ mSupportsHDR = false;
+}
+
+mozilla::gfx::BackendType gfxWindowsPlatform::GetContentBackendFor(
+ mozilla::layers::LayersBackend aLayers) {
+ mozilla::gfx::BackendType defaultBackend =
+ gfxPlatform::GetDefaultContentBackend();
+ if (aLayers == LayersBackend::LAYERS_WR &&
+ gfx::gfxVars::UseWebRenderANGLE()) {
+ return defaultBackend;
+ }
+
+ if (defaultBackend == BackendType::DIRECT2D1_1) {
+ // We can't have D2D without D3D11 layers, so fallback to Skia.
+ return BackendType::SKIA;
+ }
+
+ // Otherwise we have some non-accelerated backend and that's ok.
+ return defaultBackend;
+}
+
+mozilla::gfx::BackendType gfxWindowsPlatform::GetPreferredCanvasBackend() {
+ mozilla::gfx::BackendType backend = gfxPlatform::GetPreferredCanvasBackend();
+
+ if (backend == BackendType::DIRECT2D1_1) {
+ if (!gfx::gfxVars::UseWebRenderANGLE()) {
+ // We can't have D2D without ANGLE when WebRender is enabled, so fallback
+ // to Skia.
+ return BackendType::SKIA;
+ }
+
+ // Fall back to software when remote canvas has been deactivated.
+ if (CanvasChild::Deactivated()) {
+ return BackendType::SKIA;
+ }
+ }
+ return backend;
+}
+
+bool gfxWindowsPlatform::CreatePlatformFontList() {
+ // bug 630201 - older pre-RTM versions of Direct2D/DirectWrite cause odd
+ // crashers so block them altogether
+ if (IsNotWin7PreRTM() && DWriteEnabled()) {
+ if (gfxPlatformFontList::Initialize(new gfxDWriteFontList)) {
+ return true;
+ }
+ // DWrite font initialization failed! Don't know why this would happen,
+ // but apparently it can - see bug 594865.
+ // So we're going to fall back to GDI fonts & rendering.
+ DisableD2D(FeatureStatus::Failed, "Failed to initialize fonts",
+ "FEATURE_FAILURE_FONT_FAIL"_ns);
+ }
+
+ // Make sure the static variable is initialized...
+ gfxPlatform::HasVariationFontSupport();
+ // ...then force it to false, even if the Windows version was recent enough
+ // to permit it, as we're using GDI fonts.
+ sHasVariationFontSupport = false;
+
+ return gfxPlatformFontList::Initialize(new gfxGDIFontList);
+}
+
+// This function will permanently disable D2D for the session. It's intended to
+// be used when, after initially chosing to use Direct2D, we encounter a
+// scenario we can't support.
+//
+// This is called during gfxPlatform::Init() so at this point there should be no
+// DrawTargetD2D/1 instances.
+void gfxWindowsPlatform::DisableD2D(FeatureStatus aStatus, const char* aMessage,
+ const nsACString& aFailureId) {
+ gfxConfig::SetFailed(Feature::DIRECT2D, aStatus, aMessage, aFailureId);
+ Factory::SetDirect3D11Device(nullptr);
+ UpdateBackendPrefs();
+}
+
+already_AddRefed<gfxASurface> gfxWindowsPlatform::CreateOffscreenSurface(
+ const IntSize& aSize, gfxImageFormat aFormat) {
+ if (!Factory::AllowedSurfaceSize(aSize)) {
+ return nullptr;
+ }
+
+ RefPtr<gfxASurface> surf = nullptr;
+
+#ifdef CAIRO_HAS_WIN32_SURFACE
+ if (!XRE_IsContentProcess()) {
+ if (mRenderMode == RENDER_GDI || mRenderMode == RENDER_DIRECT2D) {
+ surf = new gfxWindowsSurface(aSize, aFormat);
+ }
+ }
+#endif
+
+ if (!surf || surf->CairoStatus()) {
+ surf = new gfxImageSurface(aSize, aFormat);
+ }
+
+ return surf.forget();
+}
+
+static const char kFontAparajita[] = "Aparajita";
+static const char kFontArabicTypesetting[] = "Arabic Typesetting";
+static const char kFontArial[] = "Arial";
+static const char kFontArialUnicodeMS[] = "Arial Unicode MS";
+static const char kFontCambria[] = "Cambria";
+static const char kFontCambriaMath[] = "Cambria Math";
+static const char kFontEbrima[] = "Ebrima";
+static const char kFontEstrangeloEdessa[] = "Estrangelo Edessa";
+static const char kFontEuphemia[] = "Euphemia";
+static const char kFontGabriola[] = "Gabriola";
+static const char kFontJavaneseText[] = "Javanese Text";
+static const char kFontKhmerUI[] = "Khmer UI";
+static const char kFontLaoUI[] = "Lao UI";
+static const char kFontLeelawadeeUI[] = "Leelawadee UI";
+static const char kFontLucidaSansUnicode[] = "Lucida Sans Unicode";
+static const char kFontMVBoli[] = "MV Boli";
+static const char kFontMalgunGothic[] = "Malgun Gothic";
+static const char kFontMicrosoftJhengHei[] = "Microsoft JhengHei";
+static const char kFontMicrosoftNewTaiLue[] = "Microsoft New Tai Lue";
+static const char kFontMicrosoftPhagsPa[] = "Microsoft PhagsPa";
+static const char kFontMicrosoftTaiLe[] = "Microsoft Tai Le";
+static const char kFontMicrosoftUighur[] = "Microsoft Uighur";
+static const char kFontMicrosoftYaHei[] = "Microsoft YaHei";
+static const char kFontMicrosoftYiBaiti[] = "Microsoft Yi Baiti";
+static const char kFontMeiryo[] = "Meiryo";
+static const char kFontMongolianBaiti[] = "Mongolian Baiti";
+static const char kFontMyanmarText[] = "Myanmar Text";
+static const char kFontNirmalaUI[] = "Nirmala UI";
+static const char kFontNyala[] = "Nyala";
+static const char kFontPlantagenetCherokee[] = "Plantagenet Cherokee";
+static const char kFontSegoeUI[] = "Segoe UI";
+static const char kFontSegoeUIEmoji[] = "Segoe UI Emoji";
+static const char kFontSegoeUISymbol[] = "Segoe UI Symbol";
+static const char kFontSylfaen[] = "Sylfaen";
+static const char kFontTraditionalArabic[] = "Traditional Arabic";
+static const char kFontTwemojiMozilla[] = "Twemoji Mozilla";
+static const char kFontUtsaah[] = "Utsaah";
+static const char kFontYuGothic[] = "Yu Gothic";
+
+void gfxWindowsPlatform::GetCommonFallbackFonts(
+ uint32_t aCh, Script aRunScript, eFontPresentation aPresentation,
+ nsTArray<const char*>& aFontList) {
+ if (PrefersColor(aPresentation)) {
+ aFontList.AppendElement(kFontSegoeUIEmoji);
+ aFontList.AppendElement(kFontTwemojiMozilla);
+ }
+
+ // Arial is used as the default fallback for system fallback
+ aFontList.AppendElement(kFontArial);
+
+ if (!IS_IN_BMP(aCh)) {
+ uint32_t p = aCh >> 16;
+ if (p == 1) { // SMP plane
+ aFontList.AppendElement(kFontSegoeUISymbol);
+ aFontList.AppendElement(kFontEbrima);
+ aFontList.AppendElement(kFontNirmalaUI);
+ aFontList.AppendElement(kFontCambriaMath);
+ }
+ } else {
+ uint32_t b = (aCh >> 8) & 0xff;
+
+ switch (b) {
+ case 0x05:
+ aFontList.AppendElement(kFontEstrangeloEdessa);
+ aFontList.AppendElement(kFontCambria);
+ break;
+ case 0x06:
+ aFontList.AppendElement(kFontMicrosoftUighur);
+ break;
+ case 0x07:
+ aFontList.AppendElement(kFontEstrangeloEdessa);
+ aFontList.AppendElement(kFontMVBoli);
+ aFontList.AppendElement(kFontEbrima);
+ break;
+ case 0x09:
+ aFontList.AppendElement(kFontNirmalaUI);
+ aFontList.AppendElement(kFontUtsaah);
+ aFontList.AppendElement(kFontAparajita);
+ break;
+ case 0x0a:
+ case 0x0b:
+ case 0x0c:
+ case 0x0d:
+ aFontList.AppendElement(kFontNirmalaUI);
+ break;
+ case 0x0e:
+ aFontList.AppendElement(kFontLaoUI);
+ aFontList.AppendElement(kFontLeelawadeeUI);
+ break;
+ case 0x10:
+ aFontList.AppendElement(kFontMyanmarText);
+ break;
+ case 0x11:
+ aFontList.AppendElement(kFontMalgunGothic);
+ break;
+ case 0x12:
+ case 0x13:
+ aFontList.AppendElement(kFontNyala);
+ aFontList.AppendElement(kFontPlantagenetCherokee);
+ break;
+ case 0x14:
+ case 0x15:
+ case 0x16:
+ aFontList.AppendElement(kFontEuphemia);
+ aFontList.AppendElement(kFontSegoeUISymbol);
+ break;
+ case 0x17:
+ aFontList.AppendElement(kFontKhmerUI);
+ aFontList.AppendElement(kFontLeelawadeeUI);
+ break;
+ case 0x18: // Mongolian
+ aFontList.AppendElement(kFontMongolianBaiti);
+ aFontList.AppendElement(kFontEuphemia);
+ break;
+ case 0x19:
+ aFontList.AppendElement(kFontMicrosoftTaiLe);
+ aFontList.AppendElement(kFontMicrosoftNewTaiLue);
+ aFontList.AppendElement(kFontKhmerUI);
+ aFontList.AppendElement(kFontLeelawadeeUI);
+ break;
+ case 0x1a:
+ aFontList.AppendElement(kFontLeelawadeeUI);
+ break;
+ case 0x1c:
+ aFontList.AppendElement(kFontNirmalaUI);
+ break;
+ case 0x20: // Symbol ranges
+ case 0x21:
+ case 0x22:
+ case 0x23:
+ case 0x24:
+ case 0x25:
+ case 0x26:
+ case 0x27:
+ case 0x29:
+ case 0x2a:
+ case 0x2b:
+ case 0x2c:
+ aFontList.AppendElement(kFontSegoeUI);
+ aFontList.AppendElement(kFontSegoeUISymbol);
+ aFontList.AppendElement(kFontCambria);
+ aFontList.AppendElement(kFontMeiryo);
+ aFontList.AppendElement(kFontArial);
+ aFontList.AppendElement(kFontLucidaSansUnicode);
+ aFontList.AppendElement(kFontEbrima);
+ break;
+ case 0x2d:
+ case 0x2e:
+ case 0x2f:
+ aFontList.AppendElement(kFontEbrima);
+ aFontList.AppendElement(kFontNyala);
+ aFontList.AppendElement(kFontSegoeUI);
+ aFontList.AppendElement(kFontSegoeUISymbol);
+ aFontList.AppendElement(kFontMeiryo);
+ break;
+ case 0x28: // Braille
+ aFontList.AppendElement(kFontSegoeUISymbol);
+ break;
+ case 0x30:
+ case 0x31:
+ aFontList.AppendElement(kFontMicrosoftYaHei);
+ break;
+ case 0x32:
+ aFontList.AppendElement(kFontMalgunGothic);
+ break;
+ case 0x4d:
+ aFontList.AppendElement(kFontSegoeUISymbol);
+ break;
+ case 0x9f:
+ aFontList.AppendElement(kFontMicrosoftYaHei);
+ aFontList.AppendElement(kFontYuGothic);
+ break;
+ case 0xa0: // Yi
+ case 0xa1:
+ case 0xa2:
+ case 0xa3:
+ case 0xa4:
+ aFontList.AppendElement(kFontMicrosoftYiBaiti);
+ aFontList.AppendElement(kFontSegoeUI);
+ break;
+ case 0xa5:
+ case 0xa6:
+ case 0xa7:
+ aFontList.AppendElement(kFontEbrima);
+ aFontList.AppendElement(kFontSegoeUI);
+ aFontList.AppendElement(kFontCambriaMath);
+ break;
+ case 0xa8:
+ aFontList.AppendElement(kFontMicrosoftPhagsPa);
+ aFontList.AppendElement(kFontNirmalaUI);
+ break;
+ case 0xa9:
+ aFontList.AppendElement(kFontMalgunGothic);
+ aFontList.AppendElement(kFontJavaneseText);
+ aFontList.AppendElement(kFontLeelawadeeUI);
+ break;
+ case 0xaa:
+ aFontList.AppendElement(kFontMyanmarText);
+ break;
+ case 0xab:
+ aFontList.AppendElement(kFontEbrima);
+ aFontList.AppendElement(kFontNyala);
+ break;
+ case 0xd7:
+ aFontList.AppendElement(kFontMalgunGothic);
+ break;
+ case 0xfb:
+ aFontList.AppendElement(kFontMicrosoftUighur);
+ aFontList.AppendElement(kFontGabriola);
+ aFontList.AppendElement(kFontSylfaen);
+ break;
+ case 0xfc:
+ case 0xfd:
+ aFontList.AppendElement(kFontTraditionalArabic);
+ aFontList.AppendElement(kFontArabicTypesetting);
+ break;
+ case 0xfe:
+ aFontList.AppendElement(kFontTraditionalArabic);
+ aFontList.AppendElement(kFontMicrosoftJhengHei);
+ break;
+ case 0xff:
+ aFontList.AppendElement(kFontMicrosoftJhengHei);
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Arial Unicode MS has lots of glyphs for obscure characters,
+ // use it as a last resort
+ aFontList.AppendElement(kFontArialUnicodeMS);
+
+ // If we didn't begin with the color-emoji fonts, include them here
+ // so that they'll be preferred over user-installed (and possibly
+ // broken) fonts in the global fallback path.
+ if (!PrefersColor(aPresentation)) {
+ aFontList.AppendElement(kFontSegoeUIEmoji);
+ aFontList.AppendElement(kFontTwemojiMozilla);
+ }
+}
+
+bool gfxWindowsPlatform::DidRenderingDeviceReset(
+ DeviceResetReason* aResetReason) {
+ DeviceManagerDx* dm = DeviceManagerDx::Get();
+ if (!dm) {
+ return false;
+ }
+ return dm->HasDeviceReset(aResetReason);
+}
+
+void gfxWindowsPlatform::CompositorUpdated() {
+ DeviceManagerDx::Get()->ForceDeviceReset(
+ ForcedDeviceResetReason::COMPOSITOR_UPDATED);
+ UpdateRenderMode();
+}
+
+BOOL CALLBACK InvalidateWindowForDeviceReset(HWND aWnd, LPARAM aMsg) {
+ RedrawWindow(aWnd, nullptr, nullptr,
+ RDW_INVALIDATE | RDW_INTERNALPAINT | RDW_FRAME);
+ return TRUE;
+}
+
+void gfxWindowsPlatform::SchedulePaintIfDeviceReset() {
+ AUTO_PROFILER_LABEL("gfxWindowsPlatform::SchedulePaintIfDeviceReset", OTHER);
+
+ DeviceResetReason resetReason = DeviceResetReason::OK;
+ if (!DidRenderingDeviceReset(&resetReason)) {
+ return;
+ }
+
+ gfxCriticalNote << "(gfxWindowsPlatform) Detected device reset: "
+ << (int)resetReason;
+
+ if (XRE_IsParentProcess()) {
+ // Trigger an ::OnPaint for each window.
+ ::EnumThreadWindows(GetCurrentThreadId(), InvalidateWindowForDeviceReset,
+ 0);
+ } else {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "gfx::gfxWindowsPlatform::SchedulePaintIfDeviceReset", []() -> void {
+ gfxWindowsPlatform::GetPlatform()->CheckForContentOnlyDeviceReset();
+ }));
+ }
+
+ gfxCriticalNote << "(gfxWindowsPlatform) scheduled device update.";
+}
+
+void gfxWindowsPlatform::CheckForContentOnlyDeviceReset() {
+ if (!DidRenderingDeviceReset()) {
+ return;
+ }
+
+ bool isContentOnlyTDR;
+ D3D11DeviceStatus status;
+
+ DeviceManagerDx::Get()->ExportDeviceInfo(&status);
+ CompositorBridgeChild::Get()->SendCheckContentOnlyTDR(status.sequenceNumber(),
+ &isContentOnlyTDR);
+
+ // The parent process doesn't know about the reset yet, or the reset is
+ // local to our device.
+ if (isContentOnlyTDR) {
+ gfxCriticalNote << "A content-only TDR is detected.";
+ dom::ContentChild* cc = dom::ContentChild::GetSingleton();
+ cc->RecvReinitRenderingForDeviceReset();
+ }
+}
+
+nsTArray<uint8_t> gfxWindowsPlatform::GetPlatformCMSOutputProfileData() {
+ if (XRE_IsContentProcess()) {
+ // This will be passed in during InitChild so we can avoid sending a
+ // sync message back to the parent during init.
+ const mozilla::gfx::ContentDeviceData* contentDeviceData =
+ GetInitContentDeviceData();
+ if (contentDeviceData) {
+ MOZ_ASSERT(!contentDeviceData->cmsOutputProfileData().IsEmpty());
+ return contentDeviceData->cmsOutputProfileData().Clone();
+ }
+
+ // Otherwise we need to ask the parent for the updated color profile
+ mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton();
+ nsTArray<uint8_t> result;
+ Unused << cc->SendGetOutputColorProfileData(&result);
+ return result;
+ }
+
+ return GetPlatformCMSOutputProfileData_Impl();
+}
+
+nsTArray<uint8_t> gfxWindowsPlatform::GetPlatformCMSOutputProfileData_Impl() {
+ static nsTArray<uint8_t> sCached = [&] {
+ // Check override pref first:
+ nsTArray<uint8_t> prefProfileData =
+ gfxPlatform::GetPrefCMSOutputProfileData();
+ if (!prefProfileData.IsEmpty()) {
+ return prefProfileData;
+ }
+
+ // -
+ // Otherwise, create a dummy DC and pull from that.
+
+ HDC dc = ::GetDC(nullptr);
+ if (!dc) {
+ return nsTArray<uint8_t>();
+ }
+
+ WCHAR profilePath[MAX_PATH];
+ DWORD profilePathLen = MAX_PATH;
+
+ bool getProfileResult = ::GetICMProfileW(dc, &profilePathLen, profilePath);
+
+ ::ReleaseDC(nullptr, dc);
+
+ if (!getProfileResult) {
+ return nsTArray<uint8_t>();
+ }
+
+ void* mem = nullptr;
+ size_t size = 0;
+
+ qcms_data_from_unicode_path(profilePath, &mem, &size);
+ if (!mem) {
+ return nsTArray<uint8_t>();
+ }
+
+ nsTArray<uint8_t> result;
+ result.AppendElements(static_cast<uint8_t*>(mem), size);
+
+ free(mem);
+
+ return result;
+ }();
+
+ return sCached.Clone();
+}
+
+void gfxWindowsPlatform::GetDLLVersion(char16ptr_t aDLLPath,
+ nsAString& aVersion) {
+ DWORD versInfoSize, vers[4] = {0};
+ // version info not available case
+ aVersion.AssignLiteral(u"0.0.0.0");
+ versInfoSize = GetFileVersionInfoSizeW(aDLLPath, nullptr);
+ AutoTArray<BYTE, 512> versionInfo;
+
+ if (versInfoSize == 0) {
+ return;
+ }
+
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ versionInfo.AppendElements(uint32_t(versInfoSize));
+
+ if (!GetFileVersionInfoW(aDLLPath, 0, versInfoSize,
+ LPBYTE(versionInfo.Elements()))) {
+ return;
+ }
+
+ UINT len = 0;
+ VS_FIXEDFILEINFO* fileInfo = nullptr;
+ if (!VerQueryValue(LPBYTE(versionInfo.Elements()), TEXT("\\"),
+ (LPVOID*)&fileInfo, &len) ||
+ len == 0 || fileInfo == nullptr) {
+ return;
+ }
+
+ DWORD fileVersMS = fileInfo->dwFileVersionMS;
+ DWORD fileVersLS = fileInfo->dwFileVersionLS;
+
+ vers[0] = HIWORD(fileVersMS);
+ vers[1] = LOWORD(fileVersMS);
+ vers[2] = HIWORD(fileVersLS);
+ vers[3] = LOWORD(fileVersLS);
+
+ char buf[256];
+ SprintfLiteral(buf, "%lu.%lu.%lu.%lu", vers[0], vers[1], vers[2], vers[3]);
+ aVersion.Assign(NS_ConvertUTF8toUTF16(buf));
+}
+
+static BOOL CALLBACK AppendClearTypeParams(HMONITOR aMonitor, HDC, LPRECT,
+ LPARAM aContext) {
+ MONITORINFOEXW monitorInfo;
+ monitorInfo.cbSize = sizeof(MONITORINFOEXW);
+ if (!GetMonitorInfoW(aMonitor, &monitorInfo)) {
+ return TRUE;
+ }
+
+ ClearTypeParameterInfo ctinfo;
+ ctinfo.displayName.Assign(monitorInfo.szDevice);
+
+ RefPtr<IDWriteRenderingParams> renderingParams;
+ HRESULT hr = Factory::GetDWriteFactory()->CreateMonitorRenderingParams(
+ aMonitor, getter_AddRefs(renderingParams));
+ if (FAILED(hr)) {
+ return TRUE;
+ }
+
+ ctinfo.gamma = renderingParams->GetGamma() * 1000;
+ ctinfo.pixelStructure = renderingParams->GetPixelGeometry();
+ ctinfo.clearTypeLevel = renderingParams->GetClearTypeLevel() * 100;
+ ctinfo.enhancedContrast = renderingParams->GetEnhancedContrast() * 100;
+
+ auto* params = reinterpret_cast<nsTArray<ClearTypeParameterInfo>*>(aContext);
+ params->AppendElement(ctinfo);
+ return TRUE;
+}
+
+void gfxWindowsPlatform::GetCleartypeParams(
+ nsTArray<ClearTypeParameterInfo>& aParams) {
+ aParams.Clear();
+ if (!DWriteEnabled()) {
+ return;
+ }
+ EnumDisplayMonitors(nullptr, nullptr, AppendClearTypeParams,
+ reinterpret_cast<LPARAM>(&aParams));
+}
+
+void gfxWindowsPlatform::FontsPrefsChanged(const char* aPref) {
+ bool clearTextFontCaches = true;
+
+ gfxPlatform::FontsPrefsChanged(aPref);
+
+ if (aPref &&
+ !strncmp(GFX_CLEARTYPE_PARAMS, aPref, strlen(GFX_CLEARTYPE_PARAMS))) {
+ gfxDWriteFont::UpdateClearTypeVars();
+ } else {
+ clearTextFontCaches = false;
+ }
+
+ if (clearTextFontCaches) {
+ gfxFontCache* fc = gfxFontCache::GetCache();
+ if (fc) {
+ fc->Flush();
+ }
+ }
+}
+
+bool gfxWindowsPlatform::IsOptimus() {
+ static int knowIsOptimus = -1;
+ if (knowIsOptimus == -1) {
+ // other potential optimus -- nvd3d9wrapx.dll & nvdxgiwrap.dll
+ if (GetModuleHandleA("nvumdshim.dll") ||
+ GetModuleHandleA("nvumdshimx.dll")) {
+ knowIsOptimus = 1;
+ } else {
+ knowIsOptimus = 0;
+ }
+ }
+ return knowIsOptimus;
+}
+/*
+static inline bool
+IsWARPStable()
+{
+ // It seems like nvdxgiwrap makes a mess of WARP. See bug 1154703.
+ if (!IsWin8OrLater() || GetModuleHandleA("nvdxgiwrap.dll")) {
+ return false;
+ }
+ return true;
+}
+*/
+static void InitializeANGLEConfig() {
+ FeatureState& d3d11ANGLE = gfxConfig::GetFeature(Feature::D3D11_HW_ANGLE);
+
+ if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
+ d3d11ANGLE.DisableByDefault(FeatureStatus::Unavailable,
+ "D3D11 compositing is disabled",
+ "FEATURE_FAILURE_HW_ANGLE_D3D11_DISABLED"_ns);
+ return;
+ }
+
+ d3d11ANGLE.EnableByDefault();
+
+ nsCString message;
+ nsCString failureId;
+ if (!gfxPlatform::IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE,
+ &message, failureId)) {
+ d3d11ANGLE.Disable(FeatureStatus::Blocklisted, message.get(), failureId);
+ }
+}
+
+void gfxWindowsPlatform::InitializeDirectDrawConfig() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ FeatureState& ddraw = gfxConfig::GetFeature(Feature::DIRECT_DRAW);
+ ddraw.EnableByDefault();
+}
+
+void gfxWindowsPlatform::InitializeConfig() {
+ if (XRE_IsParentProcess()) {
+ // The parent process first determines which features can be attempted.
+ // This information is relayed to content processes and the GPU process.
+ InitializeD3D11Config();
+ InitializeANGLEConfig();
+ InitializeD2DConfig();
+ } else {
+ FetchAndImportContentDeviceData();
+ InitializeANGLEConfig();
+ }
+}
+
+void gfxWindowsPlatform::InitializeD3D11Config() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING);
+
+ if (!gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) {
+ d3d11.DisableByDefault(FeatureStatus::Unavailable,
+ "Hardware compositing is disabled",
+ "FEATURE_FAILURE_D3D11_NEED_HWCOMP"_ns);
+ return;
+ }
+
+ d3d11.EnableByDefault();
+
+ // Check if the user really, really wants WARP.
+ if (StaticPrefs::layers_d3d11_force_warp_AtStartup()) {
+ // Force D3D11 on even if we disabled it.
+ d3d11.UserForceEnable("User force-enabled WARP");
+ }
+
+ if (!IsWin8OrLater() &&
+ !DeviceManagerDx::Get()->CheckRemotePresentSupport()) {
+ nsCOMPtr<nsIGfxInfo> gfxInfo;
+ gfxInfo = components::GfxInfo::Service();
+ nsAutoString adaptorId;
+ gfxInfo->GetAdapterDeviceID(adaptorId);
+ // Blocklist Intel HD Graphics 510/520/530 on Windows 7 without platform
+ // update due to the crashes in Bug 1351349.
+ if (adaptorId.EqualsLiteral("0x1912") ||
+ adaptorId.EqualsLiteral("0x1916") ||
+ adaptorId.EqualsLiteral("0x1902")) {
+#ifdef RELEASE_OR_BETA
+ d3d11.Disable(FeatureStatus::Blocklisted, "Blocklisted, see bug 1351349",
+ "FEATURE_FAILURE_BUG_1351349"_ns);
+#else
+ Preferences::SetBool("gfx.compositor.clearstate", true);
+#endif
+ }
+ }
+
+ nsCString message;
+ nsCString failureId;
+ if (StaticPrefs::layers_d3d11_enable_blacklist_AtStartup() &&
+ !gfxPlatform::IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS,
+ &message, failureId)) {
+ d3d11.Disable(FeatureStatus::Blocklisted, message.get(), failureId);
+ }
+}
+
+/* static */
+void gfxWindowsPlatform::RecordContentDeviceFailure(
+ TelemetryDeviceCode aDevice) {
+ // If the parent process fails to acquire a device, we record this
+ // normally as part of the environment. The exceptional case we're
+ // looking for here is when the parent process successfully acquires
+ // a device, but the content process fails to acquire the same device.
+ // This would not normally be displayed in about:support.
+ if (!XRE_IsContentProcess()) {
+ return;
+ }
+ Telemetry::Accumulate(Telemetry::GFX_CONTENT_FAILED_TO_ACQUIRE_DEVICE,
+ uint32_t(aDevice));
+}
+
+void gfxWindowsPlatform::RecordStartupTelemetry() {
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ DeviceManagerDx* dx = DeviceManagerDx::Get();
+ nsTArray<DXGI_OUTPUT_DESC1> outputs = dx->EnumerateOutputs();
+
+ uint32_t allSupportedColorSpaces = 0;
+ for (auto& output : outputs) {
+ uint32_t colorSpace = 1 << output.ColorSpace;
+ allSupportedColorSpaces |= colorSpace;
+ }
+
+ Telemetry::ScalarSet(
+ Telemetry::ScalarID::GFX_HDR_WINDOWS_DISPLAY_COLORSPACE_BITFIELD,
+ allSupportedColorSpaces);
+}
+
+// Supports lazy device initialization on Windows, so that WebRender can avoid
+// initializing GPU state and allocating swap chains for most non-GPU processes.
+void gfxWindowsPlatform::EnsureDevicesInitialized() {
+ MOZ_DIAGNOSTIC_ASSERT(!IsWin32kLockedDown());
+
+ if (!mInitializedDevices) {
+ mInitializedDevices = true;
+ InitializeDevices();
+ UpdateBackendPrefs();
+ }
+}
+
+bool gfxWindowsPlatform::DevicesInitialized() { return mInitializedDevices; }
+
+void gfxWindowsPlatform::InitializeDevices() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (XRE_IsParentProcess()) {
+ // If we're the UI process, and the GPU process is enabled, then we don't
+ // initialize any DirectX devices. We do leave them enabled in gfxConfig
+ // though. If the GPU process fails to create these devices it will send
+ // a message back and we'll update their status.
+ if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
+ return;
+ }
+
+ // No GPU process, continue initializing devices as normal.
+ }
+
+ // If acceleration is disabled, we refuse to initialize anything.
+ if (!gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) {
+ return;
+ }
+
+ // If we previously crashed initializing devices, bail out now.
+ D3D11LayersCrashGuard detectCrashes;
+ if (detectCrashes.Crashed()) {
+ gfxConfig::SetFailed(Feature::HW_COMPOSITING,
+ FeatureStatus::CrashedOnStartup,
+ "Crashed during startup in a previous session");
+ gfxConfig::SetFailed(
+ Feature::D3D11_COMPOSITING, FeatureStatus::CrashedOnStartup,
+ "Harware acceleration crashed during startup in a previous session");
+ gfxConfig::SetFailed(
+ Feature::DIRECT2D, FeatureStatus::CrashedOnStartup,
+ "Harware acceleration crashed during startup in a previous session");
+ return;
+ }
+
+ bool shouldUseD2D = gfxConfig::IsEnabled(Feature::DIRECT2D);
+
+ // First, initialize D3D11. If this succeeds we attempt to use Direct2D.
+ InitializeD3D11();
+ InitializeD2D();
+
+ if (!gfxConfig::IsEnabled(Feature::DIRECT2D) && XRE_IsContentProcess() &&
+ shouldUseD2D) {
+ RecordContentDeviceFailure(TelemetryDeviceCode::D2D1);
+ }
+}
+
+void gfxWindowsPlatform::InitializeD3D11() {
+ // This function attempts to initialize our D3D11 devices, if the hardware
+ // is not blocklisted for D3D11 layers. This first attempt will try to create
+ // a hardware accelerated device. If this creation fails or the hardware is
+ // blocklisted, then this function will abort if WARP is disabled, causing us
+ // to fallback to Basic layers. If WARP is not disabled it will use a WARP
+ // device which should always be available on Windows 7 and higher.
+ if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
+ return;
+ }
+
+ DeviceManagerDx* dm = DeviceManagerDx::Get();
+ if (XRE_IsParentProcess()) {
+ if (!dm->CreateCompositorDevices()) {
+ return;
+ }
+ }
+
+ dm->CreateContentDevices();
+
+ // Content process failed to create the d3d11 device while parent process
+ // succeed.
+ if (XRE_IsContentProcess() &&
+ !gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
+ gfxCriticalError()
+ << "[D3D11] Failed to create the D3D11 device in content \
+ process.";
+ }
+}
+
+void gfxWindowsPlatform::InitializeD2DConfig() {
+ FeatureState& d2d1 = gfxConfig::GetFeature(Feature::DIRECT2D);
+
+ if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
+ d2d1.DisableByDefault(FeatureStatus::Unavailable,
+ "Direct2D requires Direct3D 11 compositing",
+ "FEATURE_FAILURE_D2D_D3D11_COMP"_ns);
+ return;
+ }
+
+ d2d1.SetDefaultFromPref(StaticPrefs::GetPrefName_gfx_direct2d_disabled(),
+ false,
+ StaticPrefs::GetPrefDefault_gfx_direct2d_disabled());
+
+ nsCString message;
+ nsCString failureId;
+ if (!gfxPlatform::IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_DIRECT2D, &message,
+ failureId)) {
+ d2d1.Disable(FeatureStatus::Blocklisted, message.get(), failureId);
+ }
+
+ if (!d2d1.IsEnabled() &&
+ StaticPrefs::gfx_direct2d_force_enabled_AtStartup()) {
+ d2d1.UserForceEnable("Force-enabled via user-preference");
+ }
+}
+
+void gfxWindowsPlatform::InitializeD2D() {
+ ScopedGfxFeatureReporter d2d1_1("D2D1.1");
+
+ FeatureState& d2d1 = gfxConfig::GetFeature(Feature::DIRECT2D);
+
+ DeviceManagerDx* dm = DeviceManagerDx::Get();
+
+ // We don't know this value ahead of time, but the user can force-override
+ // it, so we use Disable instead of SetFailed.
+ if (dm->IsWARP()) {
+ d2d1.Disable(FeatureStatus::Blocked,
+ "Direct2D is not compatible with Direct3D11 WARP",
+ "FEATURE_FAILURE_D2D_WARP_BLOCK"_ns);
+ }
+
+ // If we pass all the initial checks, we can proceed to runtime decisions.
+ if (!d2d1.IsEnabled()) {
+ return;
+ }
+
+ if (!Factory::SupportsD2D1()) {
+ d2d1.SetFailed(FeatureStatus::Unavailable,
+ "Failed to acquire a Direct2D 1.1 factory",
+ "FEATURE_FAILURE_D2D_FACTORY"_ns);
+ return;
+ }
+
+ if (!dm->GetContentDevice()) {
+ d2d1.SetFailed(FeatureStatus::Failed,
+ "Failed to acquire a Direct3D 11 content device",
+ "FEATURE_FAILURE_D2D_DEVICE"_ns);
+ return;
+ }
+
+ if (!dm->TextureSharingWorks()) {
+ d2d1.SetFailed(FeatureStatus::Failed,
+ "Direct3D11 device does not support texture sharing",
+ "FEATURE_FAILURE_D2D_TXT_SHARING"_ns);
+ return;
+ }
+
+ // Using Direct2D depends on DWrite support.
+ if (!DWriteEnabled() && !InitDWriteSupport()) {
+ d2d1.SetFailed(FeatureStatus::Failed,
+ "Failed to initialize DirectWrite support",
+ "FEATURE_FAILURE_D2D_DWRITE"_ns);
+ return;
+ }
+
+ // Verify that Direct2D device creation succeeded.
+ RefPtr<ID3D11Device> contentDevice = dm->GetContentDevice();
+ if (!Factory::SetDirect3D11Device(contentDevice)) {
+ d2d1.SetFailed(FeatureStatus::Failed, "Failed to create a Direct2D device",
+ "FEATURE_FAILURE_D2D_CREATE_FAILED"_ns);
+ return;
+ }
+
+ MOZ_ASSERT(d2d1.IsEnabled());
+ d2d1_1.SetSuccessful();
+}
+
+void gfxWindowsPlatform::InitGPUProcessSupport() {
+ FeatureState& gpuProc = gfxConfig::GetFeature(Feature::GPU_PROCESS);
+
+ if (!gpuProc.IsEnabled()) {
+ return;
+ }
+
+ if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
+ // Don't use the GPU process if not using D3D11, unless software
+ // compositor is allowed
+ if (StaticPrefs::layers_gpu_process_allow_software_AtStartup()) {
+ return;
+ }
+ gpuProc.Disable(FeatureStatus::Unavailable,
+ "Not using GPU Process since D3D11 is unavailable",
+ "FEATURE_FAILURE_NO_D3D11"_ns);
+ } else if (!IsWin7SP1OrLater()) {
+ // On Windows 7 Pre-SP1, DXGI 1.2 is not available and remote presentation
+ // for D3D11 will not work. Rather than take a regression we revert back
+ // to in-process rendering.
+ gpuProc.Disable(FeatureStatus::Unavailable,
+ "Windows 7 Pre-SP1 cannot use the GPU process",
+ "FEATURE_FAILURE_OLD_WINDOWS"_ns);
+ } else if (!IsWin8OrLater()) {
+ // Windows 7 SP1 can have DXGI 1.2 only via the Platform Update, so we
+ // explicitly check for that here.
+ if (!DeviceManagerDx::Get()->CheckRemotePresentSupport()) {
+ gpuProc.Disable(FeatureStatus::Unavailable,
+ "GPU Process requires the Windows 7 Platform Update",
+ "FEATURE_FAILURE_PLATFORM_UPDATE"_ns);
+ } else {
+ // Clear anything cached by the above call since we don't need it.
+ DeviceManagerDx::Get()->ResetDevices();
+ }
+ }
+
+ // If we're still enabled at this point, the user set the force-enabled pref.
+}
+
+bool gfxWindowsPlatform::DwmCompositionEnabled() {
+ MOZ_RELEASE_ASSERT(mDwmCompositionStatus != DwmCompositionStatus::Unknown);
+
+ return mDwmCompositionStatus == DwmCompositionStatus::Enabled;
+}
+
+class D3DVsyncSource final : public VsyncSource {
+ public:
+ D3DVsyncSource()
+ : mPrevVsync(TimeStamp::Now()),
+ mVsyncEnabled(false),
+ mWaitVBlankMonitor(NULL),
+ mIsWindows8OrLater(false) {
+ mVsyncThread = new base::Thread("WindowsVsyncThread");
+ MOZ_RELEASE_ASSERT(mVsyncThread->Start(),
+ "GFX: Could not start Windows vsync thread");
+ SetVsyncRate();
+
+ mIsWindows8OrLater = IsWin8OrLater();
+ }
+
+ void SetVsyncRate() {
+ if (!gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
+ mVsyncRate = TimeDuration::FromMilliseconds(1000.0 / 60.0);
+ return;
+ }
+
+ DWM_TIMING_INFO vblankTime;
+ // Make sure to init the cbSize, otherwise GetCompositionTiming will fail
+ vblankTime.cbSize = sizeof(DWM_TIMING_INFO);
+ HRESULT hr = DwmGetCompositionTimingInfo(0, &vblankTime);
+ if (SUCCEEDED(hr)) {
+ UNSIGNED_RATIO refreshRate = vblankTime.rateRefresh;
+ // We get the rate in hertz / time, but we want the rate in ms.
+ float rate =
+ ((float)refreshRate.uiDenominator / (float)refreshRate.uiNumerator) *
+ 1000;
+ mVsyncRate = TimeDuration::FromMilliseconds(rate);
+ } else {
+ mVsyncRate = TimeDuration::FromMilliseconds(1000.0 / 60.0);
+ }
+ }
+
+ virtual void Shutdown() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ DisableVsync();
+ mVsyncThread->Stop();
+ delete mVsyncThread;
+ }
+
+ virtual void EnableVsync() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mVsyncThread->IsRunning());
+ { // scope lock
+ if (mVsyncEnabled) {
+ return;
+ }
+ mVsyncEnabled = true;
+ }
+
+ mVsyncThread->message_loop()->PostTask(NewRunnableMethod(
+ "D3DVsyncSource::VBlankLoop", this, &D3DVsyncSource::VBlankLoop));
+ }
+
+ virtual void DisableVsync() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mVsyncThread->IsRunning());
+ if (!mVsyncEnabled) {
+ return;
+ }
+ mVsyncEnabled = false;
+ }
+
+ virtual bool IsVsyncEnabled() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mVsyncEnabled;
+ }
+
+ virtual TimeDuration GetVsyncRate() override { return mVsyncRate; }
+
+ void ScheduleSoftwareVsync(TimeStamp aVsyncTimestamp) {
+ MOZ_ASSERT(IsInVsyncThread());
+ NS_WARNING(
+ "DwmComposition dynamically disabled, falling back to software "
+ "timers");
+
+ TimeStamp nextVsync = aVsyncTimestamp + mVsyncRate;
+ TimeDuration delay = nextVsync - TimeStamp::Now();
+ if (delay.ToMilliseconds() < 0) {
+ delay = mozilla::TimeDuration::FromMilliseconds(0);
+ }
+
+ mVsyncThread->message_loop()->PostDelayedTask(
+ NewRunnableMethod("D3DVsyncSource::VBlankLoop", this,
+ &D3DVsyncSource::VBlankLoop),
+ delay.ToMilliseconds());
+ }
+
+ // Returns the timestamp for the just happened vsync
+ TimeStamp GetVBlankTime() {
+ TimeStamp vsync = TimeStamp::Now();
+ TimeStamp now = vsync;
+
+ DWM_TIMING_INFO vblankTime;
+ // Make sure to init the cbSize, otherwise
+ // GetCompositionTiming will fail
+ vblankTime.cbSize = sizeof(DWM_TIMING_INFO);
+ HRESULT hr = DwmGetCompositionTimingInfo(0, &vblankTime);
+ if (!SUCCEEDED(hr)) {
+ return vsync;
+ }
+
+ LARGE_INTEGER frequency;
+ QueryPerformanceFrequency(&frequency);
+
+ LARGE_INTEGER qpcNow;
+ QueryPerformanceCounter(&qpcNow);
+
+ const int microseconds = 1000000;
+ int64_t adjust = qpcNow.QuadPart - vblankTime.qpcVBlank;
+ int64_t usAdjust = (adjust * microseconds) / frequency.QuadPart;
+ vsync -= TimeDuration::FromMicroseconds((double)usAdjust);
+
+ if (IsWin10OrLater()) {
+ // On Windows 10 and on, DWMGetCompositionTimingInfo, mostly
+ // reports the upcoming vsync time, which is in the future.
+ // It can also sometimes report a vblank time in the past.
+ // Since large parts of Gecko assume TimeStamps can't be in future,
+ // use the previous vsync.
+
+ // Windows 10 and Intel HD vsync timestamps are messy and
+ // all over the place once in a while. Most of the time,
+ // it reports the upcoming vsync. Sometimes, that upcoming
+ // vsync is in the past. Sometimes that upcoming vsync is before
+ // the previously seen vsync.
+ // In these error cases, normalize to Now();
+ if (vsync >= now) {
+ vsync = vsync - mVsyncRate;
+ }
+ }
+
+ // On Windows 7 and 8, DwmFlush wakes up AFTER qpcVBlankTime
+ // from DWMGetCompositionTimingInfo. We can return the adjusted vsync.
+ if (vsync >= now) {
+ vsync = now;
+ }
+
+ // Our vsync time is some time very far in the past, adjust to Now.
+ // 4 ms is arbitrary, so feel free to pick something else if this isn't
+ // working. See the comment above within IsWin10OrLater().
+ if ((now - vsync).ToMilliseconds() > 4.0) {
+ vsync = now;
+ }
+
+ return vsync;
+ }
+
+ void VBlankLoop() {
+ MOZ_ASSERT(IsInVsyncThread());
+ MOZ_ASSERT(sizeof(int64_t) == sizeof(QPC_TIME));
+
+ TimeStamp vsync = TimeStamp::Now();
+ mPrevVsync = TimeStamp();
+ TimeStamp flushTime = TimeStamp::Now();
+ TimeDuration longVBlank = mVsyncRate * 2;
+
+ for (;;) {
+ { // scope lock
+ if (!mVsyncEnabled) return;
+ }
+
+ // Large parts of gecko assume that the refresh driver timestamp
+ // must be <= Now() and cannot be in the future.
+ MOZ_ASSERT(vsync <= TimeStamp::Now());
+ NotifyVsync(vsync, vsync + mVsyncRate);
+
+ // DwmComposition can be dynamically enabled/disabled
+ // so we have to check every time that it's available.
+ // When it is unavailable, we fallback to software but will try
+ // to get back to dwm rendering once it's re-enabled
+ if (!gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
+ ScheduleSoftwareVsync(vsync);
+ return;
+ }
+
+ HRESULT hr = E_FAIL;
+ if (mIsWindows8OrLater &&
+ !StaticPrefs::gfx_vsync_force_disable_waitforvblank()) {
+ UpdateVBlankOutput();
+ if (mWaitVBlankOutput) {
+ const TimeStamp vblank_begin_wait = TimeStamp::Now();
+ {
+ AUTO_PROFILER_THREAD_SLEEP;
+ hr = mWaitVBlankOutput->WaitForVBlank();
+ }
+ if (SUCCEEDED(hr)) {
+ // vblank might return instantly when running headless,
+ // monitor powering off, etc. Since we're on a dedicated
+ // thread, instant-return should not happen in the normal
+ // case, so catch any odd behavior with a time cutoff:
+ TimeDuration vblank_wait = TimeStamp::Now() - vblank_begin_wait;
+ if (vblank_wait.ToMilliseconds() < 1.0) {
+ hr = E_FAIL; // fall back on old behavior
+ }
+ }
+ }
+ }
+ if (!SUCCEEDED(hr)) {
+ hr = DwmFlush();
+ }
+ if (!SUCCEEDED(hr)) {
+ // DWMFlush isn't working, fallback to software vsync.
+ ScheduleSoftwareVsync(TimeStamp::Now());
+ return;
+ }
+
+ TimeStamp now = TimeStamp::Now();
+ TimeDuration flushDiff = now - flushTime;
+ flushTime = now;
+ if ((flushDiff > longVBlank) || mPrevVsync.IsNull()) {
+ // Our vblank took longer than 2 intervals, readjust our timestamps
+ vsync = GetVBlankTime();
+ mPrevVsync = vsync;
+ } else {
+ // Instead of giving the actual vsync time, a constant interval
+ // between vblanks instead of the noise generated via hardware
+ // is actually what we want. Most apps just care about the diff
+ // between vblanks to animate, so a clean constant interval is
+ // smoother.
+ vsync = mPrevVsync + mVsyncRate;
+ if (vsync > now) {
+ // DWMFlush woke up very early, so readjust our times again
+ vsync = GetVBlankTime();
+ }
+
+ if (vsync <= mPrevVsync) {
+ vsync = TimeStamp::Now();
+ }
+
+ if ((now - vsync).ToMilliseconds() > 2.0) {
+ // Account for time drift here where vsync never quite catches up to
+ // Now and we'd fall ever so slightly further behind Now().
+ vsync = GetVBlankTime();
+ }
+
+ mPrevVsync = vsync;
+ }
+ } // end for
+ }
+ virtual ~D3DVsyncSource() { MOZ_ASSERT(NS_IsMainThread()); }
+
+ private:
+ bool IsInVsyncThread() {
+ return mVsyncThread->thread_id() == PlatformThread::CurrentId();
+ }
+
+ void UpdateVBlankOutput() {
+ HMONITOR primary_monitor =
+ MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY);
+ if (primary_monitor == mWaitVBlankMonitor && mWaitVBlankOutput) {
+ return;
+ }
+
+ mWaitVBlankMonitor = primary_monitor;
+
+ RefPtr<IDXGIOutput> output = nullptr;
+ if (DeviceManagerDx* dx = DeviceManagerDx::Get()) {
+ if (dx->GetOutputFromMonitor(mWaitVBlankMonitor, &output)) {
+ mWaitVBlankOutput = output;
+ return;
+ }
+ }
+
+ // failed to convert a monitor to an output so keep trying
+ mWaitVBlankOutput = nullptr;
+ }
+
+ TimeStamp mPrevVsync;
+ base::Thread* mVsyncThread;
+ TimeDuration mVsyncRate;
+ Atomic<bool> mVsyncEnabled;
+
+ HMONITOR mWaitVBlankMonitor;
+ RefPtr<IDXGIOutput> mWaitVBlankOutput;
+ bool mIsWindows8OrLater;
+}; // D3DVsyncSource
+
+already_AddRefed<mozilla::gfx::VsyncSource>
+gfxWindowsPlatform::CreateGlobalHardwareVsyncSource() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread(), "GFX: Not in main thread.");
+
+ if (!DwmCompositionEnabled()) {
+ NS_WARNING("DWM not enabled, falling back to software vsync");
+ return GetSoftwareVsyncSource();
+ }
+
+ RefPtr<VsyncSource> d3dVsyncSource = new D3DVsyncSource();
+ return d3dVsyncSource.forget();
+}
+
+void gfxWindowsPlatform::ImportGPUDeviceData(
+ const mozilla::gfx::GPUDeviceData& aData) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ gfxPlatform::ImportGPUDeviceData(aData);
+
+ gfxConfig::ImportChange(Feature::D3D11_COMPOSITING, aData.d3d11Compositing());
+
+ DeviceManagerDx* dm = DeviceManagerDx::Get();
+ if (gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
+ dm->ImportDeviceInfo(aData.gpuDevice().ref());
+ } else {
+ // There should be no devices, so this just takes away the device status.
+ dm->ResetDevices();
+
+ // Make sure we disable D2D if content processes might use it.
+ FeatureState& d2d1 = gfxConfig::GetFeature(Feature::DIRECT2D);
+ if (d2d1.IsEnabled()) {
+ d2d1.SetFailed(FeatureStatus::Unavailable,
+ "Direct2D requires Direct3D 11 compositing",
+ "FEATURE_FAILURE_D2D_D3D11_COMP"_ns);
+ }
+ }
+
+ // CanUseHardwareVideoDecoding depends on d3d11 state, so update
+ // the cached value now.
+ UpdateCanUseHardwareVideoDecoding();
+
+ // For completeness (and messaging in about:support). Content recomputes this
+ // on its own, and we won't use ANGLE in the UI process if we're using a GPU
+ // process.
+ UpdateANGLEConfig();
+}
+
+void gfxWindowsPlatform::ImportContentDeviceData(
+ const mozilla::gfx::ContentDeviceData& aData) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+
+ gfxPlatform::ImportContentDeviceData(aData);
+
+ const DevicePrefs& prefs = aData.prefs();
+ gfxConfig::Inherit(Feature::D3D11_COMPOSITING, prefs.d3d11Compositing());
+ gfxConfig::Inherit(Feature::DIRECT2D, prefs.useD2D1());
+
+ if (gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
+ DeviceManagerDx* dm = DeviceManagerDx::Get();
+ dm->ImportDeviceInfo(aData.d3d11());
+ }
+
+ // aData->cmsOutputProfileData() will be read during color profile init,
+ // not as part of this import function
+}
+
+void gfxWindowsPlatform::BuildContentDeviceData(ContentDeviceData* aOut) {
+ // Check for device resets before giving back new graphics information.
+ UpdateRenderMode();
+
+ gfxPlatform::BuildContentDeviceData(aOut);
+
+ const FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING);
+ aOut->prefs().d3d11Compositing() = d3d11.GetValue();
+ aOut->prefs().useD2D1() = gfxConfig::GetValue(Feature::DIRECT2D);
+
+ if (d3d11.IsEnabled()) {
+ DeviceManagerDx* dm = DeviceManagerDx::Get();
+ dm->ExportDeviceInfo(&aOut->d3d11());
+ }
+
+ aOut->cmsOutputProfileData() =
+ gfxPlatform::GetPlatform()->GetPlatformCMSOutputProfileData();
+}
+
+bool gfxWindowsPlatform::CheckVariationFontSupport() {
+ // Variation font support is only available on Fall Creators Update or later.
+ return IsWin10FallCreatorsUpdateOrLater();
+}
+
+void gfxWindowsPlatform::GetPlatformDisplayInfo(
+ mozilla::widget::InfoObject& aObj) {
+ HwStretchingSupport stretch;
+ DeviceManagerDx::Get()->CheckHardwareStretchingSupport(stretch);
+
+ nsPrintfCString stretchValue(
+ "both=%u window-only=%u full-screen-only=%u none=%u error=%u",
+ stretch.mBoth, stretch.mWindowOnly, stretch.mFullScreenOnly,
+ stretch.mNone, stretch.mError);
+ aObj.DefineProperty("HardwareStretching", stretchValue.get());
+
+ ScaledResolutionSet scaled;
+ GetScaledResolutions(scaled);
+ if (scaled.IsEmpty()) {
+ return;
+ }
+
+ aObj.DefineProperty("ScaledResolutionCount", scaled.Length());
+ for (size_t i = 0; i < scaled.Length(); ++i) {
+ auto& s = scaled[i];
+ nsPrintfCString name("ScaledResolution%zu", i);
+ nsPrintfCString value("source %dx%d, target %dx%d", s.first.width,
+ s.first.height, s.second.width, s.second.height);
+ aObj.DefineProperty(name.get(), value.get());
+ }
+}
diff --git a/gfx/thebes/gfxWindowsPlatform.h b/gfx/thebes/gfxWindowsPlatform.h
new file mode 100644
index 0000000000..95315bcaa9
--- /dev/null
+++ b/gfx/thebes/gfxWindowsPlatform.h
@@ -0,0 +1,261 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_WINDOWS_PLATFORM_H
+#define GFX_WINDOWS_PLATFORM_H
+
+#include "gfxCrashReporterUtils.h"
+#include "gfxFontUtils.h"
+#include "gfxWindowsSurface.h"
+#include "gfxFont.h"
+#include "gfxDWriteFonts.h"
+#include "gfxPlatform.h"
+#include "gfxTelemetry.h"
+#include "gfxTypes.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Atomics.h"
+#include "nsTArray.h"
+
+#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
+
+#include <windows.h>
+#include <objbase.h>
+
+#include <dxgi.h>
+
+// This header is available in the June 2010 SDK and in the Win8 SDK
+#include <d3dcommon.h>
+// Win 8.0 SDK types we'll need when building using older sdks.
+#if !defined(D3D_FEATURE_LEVEL_11_1) // defined in the 8.0 SDK only
+# define D3D_FEATURE_LEVEL_11_1 static_cast<D3D_FEATURE_LEVEL>(0xb100)
+# define D3D_FL9_1_REQ_TEXTURE2D_U_OR_V_DIMENSION 2048
+# define D3D_FL9_3_REQ_TEXTURE2D_U_OR_V_DIMENSION 4096
+#endif
+
+namespace mozilla {
+namespace gfx {
+class DrawTarget;
+class FeatureState;
+class DeviceManagerDx;
+} // namespace gfx
+} // namespace mozilla
+struct IDirect3DDevice9;
+struct ID3D11Device;
+struct IDXGIAdapter1;
+
+/**
+ * Utility to get a Windows HDC from a Moz2D DrawTarget. If the DrawTarget is
+ * not backed by a HDC this will get the HDC for the screen device context
+ * instead.
+ */
+class MOZ_STACK_CLASS DCForMetrics final {
+ public:
+ explicit DCForMetrics();
+
+ ~DCForMetrics() { ReleaseDC(nullptr, mDC); }
+
+ operator HDC() { return mDC; }
+
+ private:
+ HDC mDC;
+};
+
+// ClearType parameters set by running ClearType tuner
+struct ClearTypeParameterInfo {
+ ClearTypeParameterInfo()
+ : gamma(-1),
+ pixelStructure(-1),
+ clearTypeLevel(-1),
+ enhancedContrast(-1) {}
+
+ nsString displayName; // typically just 'DISPLAY1'
+ int32_t gamma;
+ int32_t pixelStructure;
+ int32_t clearTypeLevel;
+ int32_t enhancedContrast;
+};
+
+class gfxWindowsPlatform final : public gfxPlatform {
+ friend class mozilla::gfx::DeviceManagerDx;
+
+ public:
+ enum TextRenderingMode {
+ TEXT_RENDERING_NO_CLEARTYPE,
+ TEXT_RENDERING_NORMAL,
+ TEXT_RENDERING_GDI_CLASSIC,
+ TEXT_RENDERING_COUNT
+ };
+
+ gfxWindowsPlatform();
+ virtual ~gfxWindowsPlatform();
+ static gfxWindowsPlatform* GetPlatform() {
+ return (gfxWindowsPlatform*)gfxPlatform::GetPlatform();
+ }
+
+ void EnsureDevicesInitialized() override;
+ bool DevicesInitialized() override;
+
+ bool CreatePlatformFontList() override;
+
+ virtual already_AddRefed<gfxASurface> CreateOffscreenSurface(
+ const IntSize& aSize, gfxImageFormat aFormat) override;
+
+ enum RenderMode {
+ /* Use GDI and windows surfaces */
+ RENDER_GDI = 0,
+
+ /* Use 32bpp image surfaces and call StretchDIBits */
+ RENDER_IMAGE_STRETCH32,
+
+ /* Use 32bpp image surfaces, and do 32->24 conversion before calling
+ StretchDIBits */
+ RENDER_IMAGE_STRETCH24,
+
+ /* Use Direct2D rendering */
+ RENDER_DIRECT2D,
+
+ /* max */
+ RENDER_MODE_MAX
+ };
+
+ bool IsDirect2DBackend();
+
+ /**
+ * Updates render mode with relation to the current preferences and
+ * available devices.
+ */
+ void UpdateRenderMode();
+
+ /**
+ * Verifies a D2D device is present and working, will attempt to create one
+ * it is non-functional or non-existant.
+ *
+ * \param aAttemptForce Attempt to force D2D cairo device creation by using
+ * cairo device creation routines.
+ */
+ void VerifyD2DDevice(bool aAttemptForce);
+
+ void GetCommonFallbackFonts(uint32_t aCh, Script aRunScript,
+ eFontPresentation aPresentation,
+ nsTArray<const char*>& aFontList) override;
+
+ bool CanUseHardwareVideoDecoding() override;
+
+ void CompositorUpdated() override;
+
+ bool DidRenderingDeviceReset(
+ DeviceResetReason* aResetReason = nullptr) override;
+ void SchedulePaintIfDeviceReset() override;
+ void CheckForContentOnlyDeviceReset();
+
+ mozilla::gfx::BackendType GetContentBackendFor(
+ mozilla::layers::LayersBackend aLayers) override;
+
+ mozilla::gfx::BackendType GetPreferredCanvasBackend() override;
+
+ static void GetDLLVersion(char16ptr_t aDLLPath, nsAString& aVersion);
+
+ // returns ClearType tuning information for each display
+ static void GetCleartypeParams(nsTArray<ClearTypeParameterInfo>& aParams);
+
+ void FontsPrefsChanged(const char* aPref) override;
+
+ static inline bool DWriteEnabled() {
+ return !!mozilla::gfx::Factory::GetDWriteFactory();
+ }
+
+ public:
+ static nsresult GetGpuTimeSinceProcessStartInMs(uint64_t* aResult);
+
+ bool DwmCompositionEnabled();
+
+ static bool IsOptimus();
+
+ bool SupportsApzWheelInput() const override { return true; }
+
+ // Recreate devices as needed for a device reset. Returns true if a device
+ // reset occurred.
+ bool HandleDeviceReset();
+ void UpdateBackendPrefs();
+
+ already_AddRefed<mozilla::gfx::VsyncSource> CreateGlobalHardwareVsyncSource()
+ override;
+ static mozilla::Atomic<size_t> sD3D11SharedTextures;
+ static mozilla::Atomic<size_t> sD3D9SharedTextures;
+
+ bool SupportsPluginDirectBitmapDrawing() override { return true; }
+
+ static void RecordContentDeviceFailure(
+ mozilla::gfx::TelemetryDeviceCode aDevice);
+
+ static void InitMemoryReportersForGPUProcess();
+
+ static bool CheckVariationFontSupport();
+
+ // Always false for content processes.
+ bool SupportsHDR() override { return mSupportsHDR; }
+
+ protected:
+ bool AccelerateLayersByDefault() override { return true; }
+
+ nsTArray<uint8_t> GetPlatformCMSOutputProfileData() override;
+
+ public:
+ static nsTArray<uint8_t> GetPlatformCMSOutputProfileData_Impl();
+
+ protected:
+ void GetPlatformDisplayInfo(mozilla::widget::InfoObject& aObj) override;
+
+ void ImportGPUDeviceData(const mozilla::gfx::GPUDeviceData& aData) override;
+ void ImportContentDeviceData(
+ const mozilla::gfx::ContentDeviceData& aData) override;
+ void BuildContentDeviceData(mozilla::gfx::ContentDeviceData* aOut) override;
+
+ BackendPrefsData GetBackendPrefs() const override;
+
+ void UpdateSupportsHDR();
+
+ RenderMode mRenderMode;
+ bool mSupportsHDR;
+
+ private:
+ enum class DwmCompositionStatus : uint32_t {
+ Unknown,
+ Disabled,
+ Enabled,
+ };
+
+ void Init();
+ void InitAcceleration() override;
+ void InitWebRenderConfig() override;
+
+ void InitializeDevices();
+ void InitializeD3D11();
+ void InitializeD2D();
+ bool InitDWriteSupport();
+ void InitGPUProcessSupport();
+
+ void DisableD2D(mozilla::gfx::FeatureStatus aStatus, const char* aMessage,
+ const nsACString& aFailureId);
+
+ void InitializeConfig();
+ void InitializeD3D9Config();
+ void InitializeD3D11Config();
+ void InitializeD2DConfig();
+ void InitializeDirectDrawConfig();
+
+ void RecordStartupTelemetry();
+
+ bool mInitializedDevices = false;
+
+ mozilla::Atomic<DwmCompositionStatus, mozilla::ReleaseAcquire>
+ mDwmCompositionStatus;
+
+ // Cached contents of the output color profile file
+ nsTArray<uint8_t> mCachedOutputColorProfile;
+};
+
+#endif /* GFX_WINDOWS_PLATFORM_H */
diff --git a/gfx/thebes/gfxWindowsSurface.cpp b/gfx/thebes/gfxWindowsSurface.cpp
new file mode 100644
index 0000000000..419c9ee696
--- /dev/null
+++ b/gfx/thebes/gfxWindowsSurface.cpp
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxWindowsSurface.h"
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/HelpersCairo.h"
+#include "mozilla/gfx/Logging.h"
+
+#include "cairo.h"
+#include "cairo-win32.h"
+
+#include "nsString.h"
+
+gfxWindowsSurface::gfxWindowsSurface(HDC dc, uint32_t flags)
+ : mOwnsDC(false), mDC(dc), mWnd(nullptr) {
+ InitWithDC(flags);
+}
+
+void gfxWindowsSurface::MakeInvalid(mozilla::gfx::IntSize& size) {
+ size = mozilla::gfx::IntSize(-1, -1);
+}
+
+gfxWindowsSurface::gfxWindowsSurface(const mozilla::gfx::IntSize& realSize,
+ gfxImageFormat imageFormat)
+ : mOwnsDC(false), mWnd(nullptr) {
+ mozilla::gfx::IntSize size(realSize);
+ if (!mozilla::gfx::Factory::CheckSurfaceSize(size)) MakeInvalid(size);
+
+ cairo_format_t cformat = GfxFormatToCairoFormat(imageFormat);
+ cairo_surface_t* surf =
+ cairo_win32_surface_create_with_dib(cformat, size.width, size.height);
+
+ Init(surf);
+
+ if (CairoStatus() == CAIRO_STATUS_SUCCESS) {
+ mDC = cairo_win32_surface_get_dc(CairoSurface());
+ RecordMemoryUsed(size.width * size.height * 4 + sizeof(gfxWindowsSurface));
+ } else {
+ mDC = nullptr;
+ }
+}
+
+gfxWindowsSurface::gfxWindowsSurface(cairo_surface_t* csurf)
+ : mOwnsDC(false), mWnd(nullptr) {
+ if (cairo_surface_status(csurf) == 0)
+ mDC = cairo_win32_surface_get_dc(csurf);
+ else
+ mDC = nullptr;
+
+ MOZ_ASSERT(cairo_surface_get_type(csurf) !=
+ CAIRO_SURFACE_TYPE_WIN32_PRINTING);
+
+ Init(csurf, true);
+}
+
+void gfxWindowsSurface::InitWithDC(uint32_t flags) {
+ if (flags & FLAG_IS_TRANSPARENT) {
+ Init(cairo_win32_surface_create_with_format(mDC, CAIRO_FORMAT_ARGB32));
+ } else {
+ Init(cairo_win32_surface_create(mDC));
+ }
+}
+
+gfxWindowsSurface::~gfxWindowsSurface() {
+ if (mOwnsDC) {
+ if (mWnd)
+ ::ReleaseDC(mWnd, mDC);
+ else
+ ::DeleteDC(mDC);
+ }
+}
+
+HDC gfxWindowsSurface::GetDC() {
+ return cairo_win32_surface_get_dc(CairoSurface());
+}
+
+already_AddRefed<gfxImageSurface> gfxWindowsSurface::GetAsImageSurface() {
+ if (!mSurfaceValid) {
+ NS_WARNING(
+ "GetImageSurface on an invalid (null) surface; who's calling this "
+ "without checking for surface errors?");
+ return nullptr;
+ }
+
+ NS_ASSERTION(
+ CairoSurface() != nullptr,
+ "CairoSurface() shouldn't be nullptr when mSurfaceValid is TRUE!");
+
+ cairo_surface_t* isurf = cairo_win32_surface_get_image(CairoSurface());
+ if (!isurf) return nullptr;
+
+ RefPtr<gfxImageSurface> result =
+ gfxASurface::Wrap(isurf).downcast<gfxImageSurface>();
+ result->SetOpaqueRect(GetOpaqueRect());
+
+ return result.forget();
+}
+
+const mozilla::gfx::IntSize gfxWindowsSurface::GetSize() const {
+ if (!mSurfaceValid) {
+ NS_WARNING(
+ "GetImageSurface on an invalid (null) surface; who's calling this "
+ "without checking for surface errors?");
+ return mozilla::gfx::IntSize(-1, -1);
+ }
+
+ NS_ASSERTION(
+ mSurface != nullptr,
+ "CairoSurface() shouldn't be nullptr when mSurfaceValid is TRUE!");
+
+ int width, height;
+ if (cairo_win32_surface_get_size(mSurface, &width, &height) !=
+ CAIRO_STATUS_SUCCESS) {
+ return mozilla::gfx::IntSize(-1, -1);
+ }
+
+ return mozilla::gfx::IntSize(width, height);
+}
diff --git a/gfx/thebes/gfxWindowsSurface.h b/gfx/thebes/gfxWindowsSurface.h
new file mode 100644
index 0000000000..3659cab37c
--- /dev/null
+++ b/gfx/thebes/gfxWindowsSurface.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_WINDOWSSURFACE_H
+#define GFX_WINDOWSSURFACE_H
+
+#include "gfxASurface.h"
+#include "gfxImageSurface.h"
+
+/* include windows.h for the HWND and HDC definitions that we need. */
+#include <windows.h>
+
+struct IDirect3DSurface9;
+
+/* undefine LoadImage because our code uses that name */
+#undef LoadImage
+
+class gfxContext;
+
+class gfxWindowsSurface : public gfxASurface {
+ public:
+ enum { FLAG_IS_TRANSPARENT = (1 << 2) };
+
+ explicit gfxWindowsSurface(HDC dc, uint32_t flags = 0);
+
+ // Create a DIB surface
+ explicit gfxWindowsSurface(const mozilla::gfx::IntSize& size,
+ gfxImageFormat imageFormat =
+ mozilla::gfx::SurfaceFormat::X8R8G8B8_UINT32);
+
+ explicit gfxWindowsSurface(cairo_surface_t* csurf);
+
+ void InitWithDC(uint32_t flags);
+
+ virtual ~gfxWindowsSurface();
+
+ HDC GetDC();
+
+ already_AddRefed<gfxImageSurface> GetAsImageSurface();
+
+ const mozilla::gfx::IntSize GetSize() const;
+
+ private:
+ void MakeInvalid(mozilla::gfx::IntSize& size);
+
+ bool mOwnsDC;
+
+ HDC mDC;
+ HWND mWnd;
+};
+
+#endif /* GFX_WINDOWSSURFACE_H */
diff --git a/gfx/thebes/gfxXlibSurface.cpp b/gfx/thebes/gfxXlibSurface.cpp
new file mode 100644
index 0000000000..2d67a8e05a
--- /dev/null
+++ b/gfx/thebes/gfxXlibSurface.cpp
@@ -0,0 +1,412 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxXlibSurface.h"
+
+#include "cairo.h"
+#include "cairo-xlib.h"
+#include <X11/Xlibint.h> /* For XESetCloseDisplay */
+#undef max // Xlibint.h defines this and it breaks std::max
+#undef min // Xlibint.h defines this and it breaks std::min
+#undef Data
+
+#include "nsTArray.h"
+#include "nsAlgorithm.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/Preferences.h"
+#include <algorithm>
+#include "mozilla/CheckedInt.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+gfxXlibSurface::gfxXlibSurface(Display* dpy, Drawable drawable, Visual* visual)
+ : mPixmapTaken(false),
+ mDisplay(XlibDisplay::Borrow(dpy)),
+ mDrawable(drawable) {
+ const gfx::IntSize size = DoSizeQuery();
+ cairo_surface_t* surf =
+ cairo_xlib_surface_create(dpy, drawable, visual, size.width, size.height);
+ Init(surf);
+}
+
+gfxXlibSurface::gfxXlibSurface(Display* dpy, Drawable drawable, Visual* visual,
+ const gfx::IntSize& size)
+ : gfxXlibSurface(XlibDisplay::Borrow(dpy), drawable, visual, size) {}
+
+gfxXlibSurface::gfxXlibSurface(const std::shared_ptr<XlibDisplay>& dpy,
+ Drawable drawable, Visual* visual,
+ const gfx::IntSize& size)
+ : mPixmapTaken(false), mDisplay(dpy), mDrawable(drawable) {
+ NS_ASSERTION(Factory::CheckSurfaceSize(size, XLIB_IMAGE_SIDE_SIZE_LIMIT),
+ "Bad size");
+
+ cairo_surface_t* surf = cairo_xlib_surface_create(*dpy, drawable, visual,
+ size.width, size.height);
+ Init(surf);
+}
+
+gfxXlibSurface::gfxXlibSurface(cairo_surface_t* csurf) : mPixmapTaken(false) {
+ MOZ_ASSERT(cairo_surface_status(csurf) == 0,
+ "Not expecting an error surface");
+
+ mDrawable = cairo_xlib_surface_get_drawable(csurf);
+ mDisplay = XlibDisplay::Borrow(cairo_xlib_surface_get_display(csurf));
+
+ Init(csurf, true);
+}
+
+gfxXlibSurface::~gfxXlibSurface() {
+ // gfxASurface's destructor calls RecordMemoryFreed().
+ if (mPixmapTaken) {
+ XFreePixmap(*mDisplay, mDrawable);
+ }
+}
+
+static Drawable CreatePixmap(Screen* screen, const gfx::IntSize& size,
+ unsigned int depth, Drawable relatedDrawable) {
+ if (!Factory::CheckSurfaceSize(size, XLIB_IMAGE_SIDE_SIZE_LIMIT))
+ return X11None;
+
+ if (relatedDrawable == X11None) {
+ relatedDrawable = RootWindowOfScreen(screen);
+ }
+ Display* dpy = DisplayOfScreen(screen);
+ // X gives us a fatal error if we try to create a pixmap of width
+ // or height 0
+ return XCreatePixmap(dpy, relatedDrawable, std::max(1, size.width),
+ std::max(1, size.height), depth);
+}
+
+void gfxXlibSurface::TakePixmap() {
+ NS_ASSERTION(!mPixmapTaken, "I already own the Pixmap!");
+ mPixmapTaken = true;
+
+ // The bit depth returned from Cairo is technically int, but this is
+ // the last place we'd be worried about that scenario.
+ unsigned int bitDepth = cairo_xlib_surface_get_depth(CairoSurface());
+ MOZ_ASSERT((bitDepth % 8) == 0, "Memory used not recorded correctly");
+
+ // Divide by 8 because surface_get_depth gives us the number of *bits* per
+ // pixel.
+ gfx::IntSize size = GetSize();
+ CheckedInt32 totalBytes =
+ CheckedInt32(size.width) * CheckedInt32(size.height) * (bitDepth / 8);
+
+ // Don't do anything in the "else" case. We could add INT32_MAX, but that
+ // would overflow the memory used counter. It would also mean we tried for
+ // a 2G image. For now, we'll just assert,
+ MOZ_ASSERT(totalBytes.isValid(), "Did not expect to exceed 2Gb image");
+ if (totalBytes.isValid()) {
+ RecordMemoryUsed(totalBytes.value());
+ }
+}
+
+Drawable gfxXlibSurface::ReleasePixmap() {
+ NS_ASSERTION(mPixmapTaken, "I don't own the Pixmap!");
+ mPixmapTaken = false;
+ RecordMemoryFreed();
+ return mDrawable;
+}
+
+static cairo_user_data_key_t gDestroyPixmapKey;
+
+struct DestroyPixmapClosure {
+ DestroyPixmapClosure(Drawable d, Screen* s) : mPixmap(d), mScreen(s) {}
+ Drawable mPixmap;
+ Screen* mScreen;
+};
+
+static void DestroyPixmap(void* data) {
+ DestroyPixmapClosure* closure = static_cast<DestroyPixmapClosure*>(data);
+ XFreePixmap(DisplayOfScreen(closure->mScreen), closure->mPixmap);
+ delete closure;
+}
+
+/* static */
+cairo_surface_t* gfxXlibSurface::CreateCairoSurface(Screen* screen,
+ Visual* visual,
+ const gfx::IntSize& size,
+ Drawable relatedDrawable) {
+ Drawable drawable = CreatePixmap(screen, size, DepthOfVisual(screen, visual),
+ relatedDrawable);
+ if (!drawable) return nullptr;
+
+ cairo_surface_t* surface = cairo_xlib_surface_create(
+ DisplayOfScreen(screen), drawable, visual, size.width, size.height);
+ if (cairo_surface_status(surface)) {
+ cairo_surface_destroy(surface);
+ XFreePixmap(DisplayOfScreen(screen), drawable);
+ return nullptr;
+ }
+
+ DestroyPixmapClosure* closure = new DestroyPixmapClosure(drawable, screen);
+ cairo_surface_set_user_data(surface, &gDestroyPixmapKey, closure,
+ DestroyPixmap);
+ return surface;
+}
+
+/* static */
+already_AddRefed<gfxXlibSurface> gfxXlibSurface::Create(
+ Screen* screen, Visual* visual, const gfx::IntSize& size,
+ Drawable relatedDrawable) {
+ return Create(XlibDisplay::Borrow(DisplayOfScreen(screen)), screen, visual,
+ size, relatedDrawable);
+};
+
+/* static */
+already_AddRefed<gfxXlibSurface> gfxXlibSurface::Create(
+ const std::shared_ptr<XlibDisplay>& display, Screen* screen, Visual* visual,
+ const gfx::IntSize& size, Drawable relatedDrawable) {
+ MOZ_ASSERT(*display == DisplayOfScreen(screen));
+
+ Drawable drawable = CreatePixmap(screen, size, DepthOfVisual(screen, visual),
+ relatedDrawable);
+ if (!drawable) return nullptr;
+
+ RefPtr<gfxXlibSurface> result =
+ new gfxXlibSurface(display, drawable, visual, size);
+ result->TakePixmap();
+
+ if (result->CairoStatus() != 0) return nullptr;
+
+ return result.forget();
+}
+
+void gfxXlibSurface::Finish() { gfxASurface::Finish(); }
+
+const gfx::IntSize gfxXlibSurface::GetSize() const {
+ if (!mSurfaceValid) return gfx::IntSize(0, 0);
+
+ return gfx::IntSize(cairo_xlib_surface_get_width(mSurface),
+ cairo_xlib_surface_get_height(mSurface));
+}
+
+const gfx::IntSize gfxXlibSurface::DoSizeQuery() {
+ // figure out width/height/depth
+ Window root_ignore;
+ int x_ignore, y_ignore;
+ unsigned int bwidth_ignore, width, height, depth;
+
+ XGetGeometry(*mDisplay, mDrawable, &root_ignore, &x_ignore, &y_ignore, &width,
+ &height, &bwidth_ignore, &depth);
+
+ return gfx::IntSize(width, height);
+}
+
+class DisplayTable {
+ public:
+ static bool GetColormapAndVisual(Screen* screen, Visual* visual,
+ Colormap* colormap,
+ Visual** visualForColormap);
+
+ private:
+ struct ColormapEntry {
+ // The Screen is needed here because colormaps (and their visuals) may
+ // only be used on one Screen
+ Screen* mScreen;
+ Visual* mVisual;
+ Colormap mColormap;
+ };
+
+ class DisplayInfo {
+ public:
+ explicit DisplayInfo(Display* display) : mDisplay(display) {}
+ Display* mDisplay;
+ nsTArray<ColormapEntry> mColormapEntries;
+ };
+
+ // Comparator for finding the DisplayInfo
+ class FindDisplay {
+ public:
+ bool Equals(const DisplayInfo& info, const Display* display) const {
+ return info.mDisplay == display;
+ }
+ };
+
+ static int DisplayClosing(Display* display, XExtCodes* codes);
+
+ nsTArray<DisplayInfo> mDisplays;
+ static DisplayTable* sDisplayTable;
+};
+
+DisplayTable* DisplayTable::sDisplayTable;
+
+// Pixmaps don't have a particular associated visual but the pixel values are
+// interpreted according to a visual/colormap pairs.
+//
+// cairo is designed for surfaces with either TrueColor visuals or the
+// default visual (which may not be true color). TrueColor visuals don't
+// really need a colormap because the visual indicates the pixel format,
+// and cairo uses the default visual with the default colormap, so cairo
+// surfaces don't need an explicit colormap.
+//
+// However, some toolkits (e.g. GDK) need a colormap even with TrueColor
+// visuals. We can create a colormap for these visuals, but it will use about
+// 20kB of memory in the server, so we use the default colormap when
+// suitable and share colormaps between surfaces. Another reason for
+// minimizing colormap turnover is that the plugin process must leak resources
+// for each new colormap id when using older GDK libraries (bug 569775).
+//
+// Only the format of the pixels is important for rendering to Pixmaps, so if
+// the format of a visual matches that of the surface, then that visual can be
+// used for rendering to the surface. Multiple visuals can match the same
+// format (but have different GLX properties), so the visual returned may
+// differ from the visual passed in. Colormaps are tied to a visual, so
+// should only be used with their visual.
+
+/* static */
+bool DisplayTable::GetColormapAndVisual(Screen* aScreen, Visual* aVisual,
+ Colormap* aColormap,
+ Visual** aVisualForColormap)
+
+{
+ Display* display = DisplayOfScreen(aScreen);
+
+ // Use the default colormap if the default visual matches.
+ Visual* defaultVisual = DefaultVisualOfScreen(aScreen);
+ if (aVisual == defaultVisual) {
+ *aColormap = DefaultColormapOfScreen(aScreen);
+ *aVisualForColormap = defaultVisual;
+ return true;
+ }
+
+ // Only supporting TrueColor non-default visuals
+ if (!aVisual || aVisual->c_class != TrueColor) return false;
+
+ if (!sDisplayTable) {
+ sDisplayTable = new DisplayTable();
+ }
+
+ nsTArray<DisplayInfo>* displays = &sDisplayTable->mDisplays;
+ size_t d = displays->IndexOf(display, 0, FindDisplay());
+
+ if (d == displays->NoIndex) {
+ d = displays->Length();
+ // Register for notification of display closing, when this info
+ // becomes invalid.
+ XExtCodes* codes = XAddExtension(display);
+ if (!codes) return false;
+
+ XESetCloseDisplay(display, codes->extension, DisplayClosing);
+ // Add a new DisplayInfo.
+ displays->AppendElement(display);
+ }
+
+ nsTArray<ColormapEntry>* entries = &displays->ElementAt(d).mColormapEntries;
+
+ // Only a small number of formats are expected to be used, so just do a
+ // simple linear search.
+ for (uint32_t i = 0; i < entries->Length(); ++i) {
+ const ColormapEntry& entry = entries->ElementAt(i);
+ if (aVisual == entry.mVisual) {
+ *aColormap = entry.mColormap;
+ *aVisualForColormap = entry.mVisual;
+ return true;
+ }
+ }
+
+ // No existing entry. Create a colormap and add an entry.
+ Colormap colormap =
+ XCreateColormap(display, RootWindowOfScreen(aScreen), aVisual, AllocNone);
+ ColormapEntry* newEntry = entries->AppendElement();
+ newEntry->mScreen = aScreen;
+ newEntry->mVisual = aVisual;
+ newEntry->mColormap = colormap;
+
+ *aColormap = colormap;
+ *aVisualForColormap = aVisual;
+ return true;
+}
+
+/* static */
+int DisplayTable::DisplayClosing(Display* display, XExtCodes* codes) {
+ // No need to free the colormaps explicitly as they will be released when
+ // the connection is closed.
+ sDisplayTable->mDisplays.RemoveElement(display, FindDisplay());
+ if (sDisplayTable->mDisplays.Length() == 0) {
+ delete sDisplayTable;
+ sDisplayTable = nullptr;
+ }
+ return 0;
+}
+
+/* static */
+bool gfxXlibSurface::GetColormapAndVisual(cairo_surface_t* aXlibSurface,
+ Colormap* aColormap,
+ Visual** aVisual) {
+ Screen* screen = cairo_xlib_surface_get_screen(aXlibSurface);
+ Visual* visual = cairo_xlib_surface_get_visual(aXlibSurface);
+
+ return DisplayTable::GetColormapAndVisual(screen, visual, aColormap, aVisual);
+}
+
+bool gfxXlibSurface::GetColormapAndVisual(Colormap* aColormap,
+ Visual** aVisual) {
+ if (!mSurfaceValid) return false;
+
+ return GetColormapAndVisual(CairoSurface(), aColormap, aVisual);
+}
+
+/* static */
+int gfxXlibSurface::DepthOfVisual(const Screen* screen, const Visual* visual) {
+ for (int d = 0; d < screen->ndepths; d++) {
+ const Depth& d_info = screen->depths[d];
+ if (visual >= &d_info.visuals[0] &&
+ visual < &d_info.visuals[d_info.nvisuals])
+ return d_info.depth;
+ }
+
+ NS_ERROR("Visual not on Screen.");
+ return 0;
+}
+
+/* static */
+Visual* gfxXlibSurface::FindVisual(Screen* screen, gfxImageFormat format) {
+ int depth;
+ unsigned long red_mask, green_mask, blue_mask;
+ switch (format) {
+ case gfx::SurfaceFormat::A8R8G8B8_UINT32:
+ depth = 32;
+ red_mask = 0xff0000;
+ green_mask = 0xff00;
+ blue_mask = 0xff;
+ break;
+ case gfx::SurfaceFormat::X8R8G8B8_UINT32:
+ depth = 24;
+ red_mask = 0xff0000;
+ green_mask = 0xff00;
+ blue_mask = 0xff;
+ break;
+ case gfx::SurfaceFormat::R5G6B5_UINT16:
+ depth = 16;
+ red_mask = 0xf800;
+ green_mask = 0x7e0;
+ blue_mask = 0x1f;
+ break;
+ case gfx::SurfaceFormat::A8:
+ default:
+ return nullptr;
+ }
+
+ for (int d = 0; d < screen->ndepths; d++) {
+ const Depth& d_info = screen->depths[d];
+ if (d_info.depth != depth) continue;
+
+ for (int v = 0; v < d_info.nvisuals; v++) {
+ Visual* visual = &d_info.visuals[v];
+
+ if (visual->c_class == TrueColor && visual->red_mask == red_mask &&
+ visual->green_mask == green_mask && visual->blue_mask == blue_mask)
+ return visual;
+ }
+ }
+
+ return nullptr;
+}
+
+Screen* gfxXlibSurface::XScreen() {
+ return cairo_xlib_surface_get_screen(CairoSurface());
+}
diff --git a/gfx/thebes/gfxXlibSurface.h b/gfx/thebes/gfxXlibSurface.h
new file mode 100644
index 0000000000..17f2b76782
--- /dev/null
+++ b/gfx/thebes/gfxXlibSurface.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_XLIBSURFACE_H
+#define GFX_XLIBSURFACE_H
+
+#include "gfxASurface.h"
+
+#include <X11/Xlib.h>
+#include "X11UndefineNone.h"
+
+#include "GLXLibrary.h"
+#include "mozilla/gfx/XlibDisplay.h"
+
+#include "nsSize.h"
+
+// Although the dimension parameters in the xCreatePixmapReq wire protocol are
+// 16-bit unsigned integers, the server's CreatePixmap returns BadAlloc if
+// either dimension cannot be represented by a 16-bit *signed* integer.
+#define XLIB_IMAGE_SIDE_SIZE_LIMIT 0x7fff
+
+class gfxXlibSurface final : public gfxASurface {
+ public:
+ // construct a wrapper around the specified drawable with dpy/visual.
+ // Will use XGetGeometry to query the window/pixmap size.
+ gfxXlibSurface(Display* dpy, Drawable drawable, Visual* visual);
+
+ // construct a wrapper around the specified drawable with dpy/visual,
+ // and known width/height.
+ gfxXlibSurface(Display* dpy, Drawable drawable, Visual* visual,
+ const mozilla::gfx::IntSize& size);
+ gfxXlibSurface(const std::shared_ptr<mozilla::gfx::XlibDisplay>& dpy,
+ Drawable drawable, Visual* visual,
+ const mozilla::gfx::IntSize& size);
+
+ explicit gfxXlibSurface(cairo_surface_t* csurf);
+
+ // create a new Pixmap and wrapper surface.
+ // |relatedDrawable| provides a hint to the server for determining whether
+ // the pixmap should be in video or system memory. It must be on
+ // |screen| (if specified).
+ static already_AddRefed<gfxXlibSurface> Create(
+ ::Screen* screen, Visual* visual, const mozilla::gfx::IntSize& size,
+ Drawable relatedDrawable = X11None);
+ static already_AddRefed<gfxXlibSurface> Create(
+ const std::shared_ptr<mozilla::gfx::XlibDisplay>& display,
+ ::Screen* screen, Visual* visual, const mozilla::gfx::IntSize& size,
+ Drawable relatedDrawable = X11None);
+ static cairo_surface_t* CreateCairoSurface(
+ ::Screen* screen, Visual* visual, const mozilla::gfx::IntSize& size,
+ Drawable relatedDrawable = X11None);
+
+ virtual ~gfxXlibSurface();
+
+ void Finish() override;
+
+ const mozilla::gfx::IntSize GetSize() const override;
+
+ Display* XDisplay() { return *mDisplay; }
+ ::Screen* XScreen();
+ Drawable XDrawable() { return mDrawable; }
+
+ static int DepthOfVisual(const ::Screen* screen, const Visual* visual);
+ static Visual* FindVisual(::Screen* screen, gfxImageFormat format);
+ static bool GetColormapAndVisual(cairo_surface_t* aXlibSurface,
+ Colormap* colormap, Visual** visual);
+
+ // take ownership of a passed-in Pixmap, calling XFreePixmap on it
+ // when the gfxXlibSurface is destroyed.
+ void TakePixmap();
+
+ // Release ownership of this surface's Pixmap. This is only valid
+ // on gfxXlibSurfaces for which the user called TakePixmap(), or
+ // on those created by a Create() factory method.
+ Drawable ReleasePixmap();
+
+ // Find a visual and colormap pair suitable for rendering to this surface.
+ bool GetColormapAndVisual(Colormap* colormap, Visual** visual);
+
+ // Return true if cairo will take its slow path when this surface is used
+ // in a pattern with EXTEND_PAD. As a workaround for XRender's RepeatPad
+ // not being implemented correctly on old X servers, cairo avoids XRender
+ // and instead reads back to perform EXTEND_PAD with pixman. Cairo does
+ // this for servers older than xorg-server 1.7.
+ bool IsPadSlow() {
+ // The test here matches that for buggy_pad_reflect in
+ // _cairo_xlib_device_create.
+ return VendorRelease(mDisplay->get()) >= 60700000 ||
+ VendorRelease(mDisplay->get()) < 10699000;
+ }
+
+ protected:
+ // if TakePixmap() has been called on this
+ bool mPixmapTaken;
+
+ std::shared_ptr<mozilla::gfx::XlibDisplay> mDisplay;
+ Drawable mDrawable;
+
+ const mozilla::gfx::IntSize DoSizeQuery();
+};
+
+#endif /* GFX_XLIBSURFACE_H */
diff --git a/gfx/thebes/moz.build b/gfx/thebes/moz.build
new file mode 100644
index 0000000000..71903756ea
--- /dev/null
+++ b/gfx/thebes/moz.build
@@ -0,0 +1,299 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("*Text*"):
+ BUG_COMPONENT = ("Core", "Graphics: Text")
+
+with Files("*DWrite*"):
+ BUG_COMPONENT = ("Core", "Graphics: Text")
+
+XPIDL_SOURCES += [
+ "nsIFontLoadCompleteCallback.idl",
+]
+
+XPIDL_MODULE = "gfx"
+
+EXPORTS += [
+ "COLRFonts.h",
+ "DrawMode.h",
+ "gfx2DGlue.h",
+ "gfxAlphaRecovery.h",
+ "gfxASurface.h",
+ "gfxBaseSharedMemorySurface.h",
+ "gfxBlur.h",
+ "gfxColor.h",
+ "gfxContext.h",
+ "gfxDrawable.h",
+ "gfxEnv.h",
+ "gfxFailure.h",
+ "gfxFont.h",
+ "gfxFontConstants.h",
+ "gfxFontEntry.h",
+ "gfxFontFeatures.h",
+ "gfxFontInfoLoader.h",
+ "gfxFontPrefLangList.h",
+ "gfxFontSrcPrincipal.h",
+ "gfxFontSrcURI.h",
+ "gfxFontUtils.h",
+ "gfxFontVariations.h",
+ "gfxGradientCache.h",
+ "gfxImageSurface.h",
+ "gfxLineSegment.h",
+ "gfxMathTable.h",
+ "gfxMatrix.h",
+ "gfxOTSUtils.h",
+ "gfxPattern.h",
+ "gfxPlatform.h",
+ "gfxPlatformFontList.h",
+ "gfxPlatformWorker.h",
+ "gfxPoint.h",
+ "gfxQuad.h",
+ "gfxQuaternion.h",
+ "gfxRect.h",
+ "gfxSharedImageSurface.h",
+ "gfxSkipChars.h",
+ "gfxSVGGlyphs.h",
+ "gfxTextRun.h",
+ "gfxTypes.h",
+ "gfxUserFontSet.h",
+ "gfxUtils.h",
+ "SharedFontList.h",
+ "SoftwareVsyncSource.h",
+ "ThebesRLBoxTypes.h",
+ "VsyncSource.h",
+]
+
+EXPORTS.mozilla.gfx += [
+ "AllOfDcomp.h",
+ "D3D11Checks.h",
+ "DeviceManagerDx.h",
+ "DisplayConfigWindows.h",
+ "PrintPromise.h",
+ "PrintTarget.h",
+ "PrintTargetThebes.h",
+ "ThebesRLBox.h",
+]
+
+EXPORTS.mozilla.gfx += ["SkMemoryReporter.h"]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
+ EXPORTS += [
+ "gfxAndroidPlatform.h",
+ "gfxFT2FontBase.h",
+ "gfxFT2Fonts.h",
+ ]
+ EXPORTS.mozilla.gfx += [
+ "PrintTargetPDF.h",
+ ]
+ SOURCES += [
+ "gfxAndroidPlatform.cpp",
+ "gfxFT2FontBase.cpp",
+ "gfxFT2FontList.cpp",
+ "gfxFT2Fonts.cpp",
+ "gfxFT2Utils.cpp",
+ "PrintTargetPDF.cpp",
+ ]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ EXPORTS += [
+ "gfxMacUtils.h",
+ "gfxPlatformMac.h",
+ "gfxQuartzNativeDrawing.h",
+ "gfxQuartzSurface.h",
+ ]
+ EXPORTS.mozilla.gfx += [
+ "PrintTargetCG.h",
+ ]
+ SOURCES += [
+ "gfxCoreTextShaper.cpp",
+ "gfxMacFont.cpp",
+ "gfxMacUtils.cpp",
+ "gfxPlatformMac.cpp",
+ "gfxQuartzNativeDrawing.cpp",
+ "gfxQuartzSurface.cpp",
+ "PrintTargetCG.mm",
+ ]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ EXPORTS += [
+ "gfxFT2FontBase.h",
+ "gfxPlatformGtk.h",
+ ]
+ EXPORTS.mozilla.gfx += [
+ "PrintTargetPDF.h",
+ ]
+ SOURCES += [
+ "gfxFcPlatformFontList.cpp",
+ "gfxFT2FontBase.cpp",
+ "gfxFT2Utils.cpp",
+ "gfxPlatformGtk.cpp",
+ "PrintTargetPDF.cpp",
+ ]
+
+ if CONFIG["MOZ_X11"]:
+ EXPORTS += [
+ "gfxXlibSurface.h",
+ ]
+ EXPORTS.mozilla.gfx += [
+ "XlibDisplay.h",
+ ]
+ SOURCES += [
+ "gfxXlibSurface.cpp",
+ "XlibDisplay.cpp",
+ ]
+
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
+ EXPORTS += [
+ "gfxDWriteCommon.h",
+ "gfxDWriteFonts.h",
+ "gfxGDIFont.h",
+ "gfxGDIFontList.h",
+ "gfxWindowsNativeDrawing.h",
+ "gfxWindowsPlatform.h",
+ "gfxWindowsSurface.h",
+ ]
+ EXPORTS.mozilla.gfx += [
+ "PrintTargetPDF.h",
+ "PrintTargetWindows.h",
+ ]
+ SOURCES += [
+ "DisplayConfigWindows.cpp",
+ "gfxDWriteCommon.cpp",
+ "gfxDWriteFonts.cpp",
+ "gfxGDIFont.cpp",
+ "gfxGDIFontList.cpp",
+ "gfxWindowsNativeDrawing.cpp",
+ "gfxWindowsPlatform.cpp",
+ "gfxWindowsSurface.cpp",
+ "PrintTargetPDF.cpp",
+ "PrintTargetWindows.cpp",
+ ]
+ UNIFIED_SOURCES += [
+ "gfxDWriteFontList.cpp",
+ ]
+
+# Are we targeting x86 or x64? If so, build gfxAlphaRecoverySSE2.cpp.
+if CONFIG["INTEL_ARCHITECTURE"]:
+ SOURCES += ["gfxAlphaRecoverySSE2.cpp"]
+ # The file uses SSE2 intrinsics, so it needs special compile flags on some
+ # compilers.
+ SOURCES["gfxAlphaRecoverySSE2.cpp"].flags += CONFIG["SSE2_FLAGS"]
+
+SOURCES += [
+ # Includes mac system header conflicting with point/size,
+ # and includes glxXlibSurface.h which drags in Xrender.h
+ "gfxASurface.cpp",
+ # on X11, gfxDrawable.cpp includes X headers for an old workaround which
+ # we could consider removing soon (affects Ubuntus older than 10.04 LTS)
+ # which currently prevent it from joining UNIFIED_SOURCES.
+ "gfxDrawable.cpp",
+ # gfxFontUtils.cpp and gfxPlatform.cpp include mac system header conflicting with point/size
+ "gfxFontUtils.cpp",
+ "gfxPlatform.cpp",
+ "PrintTarget.cpp",
+ "PrintTargetThebes.cpp",
+]
+
+UNIFIED_SOURCES += [
+ "CJKCompatSVS.cpp",
+ "COLRFonts.cpp",
+ "gfxAlphaRecovery.cpp",
+ "gfxBaseSharedMemorySurface.cpp",
+ "gfxBlur.cpp",
+ "gfxContext.cpp",
+ "gfxFont.cpp",
+ "gfxFontEntry.cpp",
+ "gfxFontFeatures.cpp",
+ "gfxFontInfoLoader.cpp",
+ "gfxFontMissingGlyphs.cpp",
+ "gfxFontSrcPrincipal.cpp",
+ "gfxFontSrcURI.cpp",
+ "gfxGlyphExtents.cpp",
+ "gfxGradientCache.cpp",
+ "gfxGraphiteShaper.cpp",
+ "gfxHarfBuzzShaper.cpp",
+ "gfxImageSurface.cpp",
+ "gfxMathTable.cpp",
+ "gfxPattern.cpp",
+ "gfxPlatformFontList.cpp",
+ "gfxPlatformWorker.cpp",
+ "gfxScriptItemizer.cpp",
+ "gfxSkipChars.cpp",
+ "gfxSVGGlyphs.cpp",
+ "gfxTextRun.cpp",
+ "gfxUserFontSet.cpp",
+ "gfxUtils.cpp",
+ "SharedFontList.cpp",
+ "SoftwareVsyncSource.cpp",
+ "VsyncSource.cpp",
+]
+
+UNIFIED_SOURCES += [
+ "SkMemoryReporter.cpp",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ UNIFIED_SOURCES += [
+ "gfxMacPlatformFontList.mm",
+ ]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
+ UNIFIED_SOURCES += [
+ "D3D11Checks.cpp",
+ ]
+ SOURCES += [
+ "DeviceManagerDx.cpp",
+ ]
+
+if CONFIG["MOZ_ENABLE_SKIA_PDF"]:
+ EXPORTS.mozilla.gfx += [
+ "PrintTargetSkPDF.h",
+ ]
+ SOURCES += [
+ "PrintTargetSkPDF.cpp",
+ ]
+
+# We use ICU for normalization functions:
+USE_LIBS += [
+ "icu",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "!/security/rlbox",
+ "/dom/base",
+ "/dom/media/platforms/apple",
+ "/dom/xml",
+ "/gfx/cairo/cairo/src",
+ "/widget/gtk",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("android", "gtk"):
+ DEFINES["MOZ_ENABLE_FREETYPE"] = True
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
+ for var in ("MOZ_ENABLE_D3D10_LAYER",):
+ if CONFIG[var]:
+ DEFINES[var] = True
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("android"):
+ CXXFLAGS += CONFIG["CAIRO_FT_CFLAGS"]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+ CFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+ CXXFLAGS += CONFIG["MOZ_PANGO_CFLAGS"]
+
+if CONFIG["MOZ_WAYLAND"]:
+ CXXFLAGS += CONFIG["MOZ_WAYLAND_CFLAGS"]
+
+LOCAL_INCLUDES += CONFIG["SKIA_INCLUDES"]
+
+DEFINES["GRAPHITE2_STATIC"] = True
+
+CXXFLAGS += ["-Werror=switch"]
+
+include("/tools/fuzzing/libfuzzer-config.mozbuild")
diff --git a/gfx/thebes/nsIFontLoadCompleteCallback.idl b/gfx/thebes/nsIFontLoadCompleteCallback.idl
new file mode 100644
index 0000000000..4f1df74a79
--- /dev/null
+++ b/gfx/thebes/nsIFontLoadCompleteCallback.idl
@@ -0,0 +1,13 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[uuid(302dbf09-079b-4648-8a06-a0486c1749c0)]
+interface nsIFontLoadCompleteCallback : nsISupports
+{
+ void fontLoadComplete();
+};