summaryrefslogtreecommitdiffstats
path: root/gfx/thebes
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /gfx/thebes
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'gfx/thebes')
-rw-r--r--gfx/thebes/CJKCompatSVS.cpp1039
-rw-r--r--gfx/thebes/ContextStateTracker.cpp137
-rw-r--r--gfx/thebes/ContextStateTracker.h84
-rw-r--r--gfx/thebes/D3D11Checks.cpp412
-rw-r--r--gfx/thebes/D3D11Checks.h30
-rw-r--r--gfx/thebes/DeviceManagerDx.cpp878
-rw-r--r--gfx/thebes/DeviceManagerDx.h154
-rw-r--r--gfx/thebes/DrawMode.h26
-rw-r--r--gfx/thebes/PrintTarget.cpp159
-rw-r--r--gfx/thebes/PrintTarget.h160
-rw-r--r--gfx/thebes/PrintTargetCG.cpp120
-rw-r--r--gfx/thebes/PrintTargetCG.h42
-rw-r--r--gfx/thebes/PrintTargetPDF.cpp85
-rw-r--r--gfx/thebes/PrintTargetPDF.h41
-rw-r--r--gfx/thebes/PrintTargetPS.cpp110
-rw-r--r--gfx/thebes/PrintTargetPS.h54
-rw-r--r--gfx/thebes/PrintTargetRecording.cpp116
-rw-r--r--gfx/thebes/PrintTargetRecording.h43
-rw-r--r--gfx/thebes/PrintTargetThebes.cpp126
-rw-r--r--gfx/thebes/PrintTargetThebes.h57
-rw-r--r--gfx/thebes/PrintTargetWindows.cpp112
-rw-r--r--gfx/thebes/PrintTargetWindows.h43
-rw-r--r--gfx/thebes/RoundedRect.h43
-rw-r--r--gfx/thebes/SoftwareVsyncSource.cpp150
-rw-r--r--gfx/thebes/SoftwareVsyncSource.h62
-rw-r--r--gfx/thebes/VsyncSource.cpp153
-rw-r--r--gfx/thebes/VsyncSource.h84
-rw-r--r--gfx/thebes/cairo-xlib-utils.h119
-rw-r--r--gfx/thebes/d3dkmtQueryStatistics.h168
-rw-r--r--gfx/thebes/genLanguageTagList.pl86
-rw-r--r--gfx/thebes/genTables.py22
-rw-r--r--gfx/thebes/gencjkcisvs.py77
-rw-r--r--gfx/thebes/gfx2DGlue.h131
-rw-r--r--gfx/thebes/gfxASurface.cpp620
-rw-r--r--gfx/thebes/gfxASurface.h204
-rw-r--r--gfx/thebes/gfxAlphaRecovery.cpp53
-rw-r--r--gfx/thebes/gfxAlphaRecovery.h111
-rw-r--r--gfx/thebes/gfxAlphaRecoverySSE2.cpp235
-rw-r--r--gfx/thebes/gfxAndroidPlatform.cpp338
-rw-r--r--gfx/thebes/gfxAndroidPlatform.h85
-rw-r--r--gfx/thebes/gfxBaseSharedMemorySurface.cpp11
-rw-r--r--gfx/thebes/gfxBaseSharedMemorySurface.h199
-rw-r--r--gfx/thebes/gfxBlur.cpp1088
-rw-r--r--gfx/thebes/gfxBlur.h194
-rw-r--r--gfx/thebes/gfxColor.h83
-rw-r--r--gfx/thebes/gfxContext.cpp1257
-rw-r--r--gfx/thebes/gfxContext.h694
-rw-r--r--gfx/thebes/gfxCoreTextShaper.cpp800
-rw-r--r--gfx/thebes/gfxCoreTextShaper.h71
-rw-r--r--gfx/thebes/gfxDWriteCommon.cpp182
-rw-r--r--gfx/thebes/gfxDWriteCommon.h153
-rw-r--r--gfx/thebes/gfxDWriteFontList.cpp1838
-rw-r--r--gfx/thebes/gfxDWriteFontList.h445
-rw-r--r--gfx/thebes/gfxDWriteFonts.cpp709
-rw-r--r--gfx/thebes/gfxDWriteFonts.h107
-rw-r--r--gfx/thebes/gfxDrawable.cpp254
-rw-r--r--gfx/thebes/gfxDrawable.h182
-rw-r--r--gfx/thebes/gfxEnv.h123
-rw-r--r--gfx/thebes/gfxFT2FontBase.cpp217
-rw-r--r--gfx/thebes/gfxFT2FontBase.h45
-rw-r--r--gfx/thebes/gfxFT2FontList.cpp1608
-rw-r--r--gfx/thebes/gfxFT2FontList.h200
-rw-r--r--gfx/thebes/gfxFT2Fonts.cpp227
-rw-r--r--gfx/thebes/gfxFT2Fonts.h84
-rw-r--r--gfx/thebes/gfxFT2Utils.cpp378
-rw-r--r--gfx/thebes/gfxFT2Utils.h94
-rw-r--r--gfx/thebes/gfxFailure.h25
-rw-r--r--gfx/thebes/gfxFcPlatformFontList.cpp1866
-rw-r--r--gfx/thebes/gfxFcPlatformFontList.h330
-rw-r--r--gfx/thebes/gfxFont.cpp4028
-rw-r--r--gfx/thebes/gfxFont.h2223
-rw-r--r--gfx/thebes/gfxFontConstants.h237
-rw-r--r--gfx/thebes/gfxFontEntry.cpp1831
-rw-r--r--gfx/thebes/gfxFontEntry.h777
-rw-r--r--gfx/thebes/gfxFontFamilyList.h364
-rw-r--r--gfx/thebes/gfxFontFeatures.cpp81
-rw-r--r--gfx/thebes/gfxFontFeatures.h126
-rw-r--r--gfx/thebes/gfxFontInfoLoader.cpp281
-rw-r--r--gfx/thebes/gfxFontInfoLoader.h258
-rw-r--r--gfx/thebes/gfxFontMissingGlyphs.cpp288
-rw-r--r--gfx/thebes/gfxFontMissingGlyphs.h55
-rw-r--r--gfx/thebes/gfxFontPrefLangList.h36
-rw-r--r--gfx/thebes/gfxFontTest.cpp8
-rw-r--r--gfx/thebes/gfxFontTest.h84
-rw-r--r--gfx/thebes/gfxFontUtils.cpp1809
-rw-r--r--gfx/thebes/gfxFontUtils.h1027
-rw-r--r--gfx/thebes/gfxFontconfigFonts.cpp2262
-rw-r--r--gfx/thebes/gfxFontconfigFonts.h124
-rw-r--r--gfx/thebes/gfxFontconfigUtils.cpp1100
-rw-r--r--gfx/thebes/gfxFontconfigUtils.h330
-rw-r--r--gfx/thebes/gfxGDIFont.cpp564
-rw-r--r--gfx/thebes/gfxGDIFont.h108
-rw-r--r--gfx/thebes/gfxGDIFontList.cpp1195
-rw-r--r--gfx/thebes/gfxGDIFontList.h354
-rw-r--r--gfx/thebes/gfxGdkNativeRenderer.cpp69
-rw-r--r--gfx/thebes/gfxGdkNativeRenderer.h88
-rw-r--r--gfx/thebes/gfxGlyphExtents.cpp154
-rw-r--r--gfx/thebes/gfxGlyphExtents.h151
-rw-r--r--gfx/thebes/gfxGradientCache.cpp239
-rw-r--r--gfx/thebes/gfxGradientCache.h36
-rw-r--r--gfx/thebes/gfxGraphiteShaper.cpp438
-rw-r--r--gfx/thebes/gfxGraphiteShaper.h59
-rw-r--r--gfx/thebes/gfxHarfBuzzShaper.cpp1818
-rw-r--r--gfx/thebes/gfxHarfBuzzShaper.h190
-rw-r--r--gfx/thebes/gfxImageSurface.cpp379
-rw-r--r--gfx/thebes/gfxImageSurface.h188
-rw-r--r--gfx/thebes/gfxLanguageTagList.cpp7876
-rw-r--r--gfx/thebes/gfxLineSegment.h77
-rw-r--r--gfx/thebes/gfxMacFont.cpp475
-rw-r--r--gfx/thebes/gfxMacFont.h102
-rw-r--r--gfx/thebes/gfxMacPlatformFontList.h182
-rw-r--r--gfx/thebes/gfxMacPlatformFontList.mm1451
-rw-r--r--gfx/thebes/gfxMathTable.cpp210
-rw-r--r--gfx/thebes/gfxMathTable.h155
-rw-r--r--gfx/thebes/gfxMatrix.cpp189
-rw-r--r--gfx/thebes/gfxMatrix.h310
-rw-r--r--gfx/thebes/gfxPattern.cpp218
-rw-r--r--gfx/thebes/gfxPattern.h77
-rw-r--r--gfx/thebes/gfxPlatform.cpp2599
-rw-r--r--gfx/thebes/gfxPlatform.h846
-rw-r--r--gfx/thebes/gfxPlatformFontList.cpp1678
-rw-r--r--gfx/thebes/gfxPlatformFontList.h471
-rw-r--r--gfx/thebes/gfxPlatformGtk.cpp906
-rw-r--r--gfx/thebes/gfxPlatformGtk.h172
-rw-r--r--gfx/thebes/gfxPlatformMac.cpp620
-rw-r--r--gfx/thebes/gfxPlatformMac.h96
-rw-r--r--gfx/thebes/gfxPoint.h70
-rw-r--r--gfx/thebes/gfxPrefs.cpp267
-rw-r--r--gfx/thebes/gfxPrefs.h682
-rw-r--r--gfx/thebes/gfxQuad.h51
-rw-r--r--gfx/thebes/gfxQuartzNativeDrawing.cpp74
-rw-r--r--gfx/thebes/gfxQuartzNativeDrawing.h71
-rw-r--r--gfx/thebes/gfxQuartzSurface.cpp137
-rw-r--r--gfx/thebes/gfxQuartzSurface.h43
-rw-r--r--gfx/thebes/gfxQuaternion.h93
-rw-r--r--gfx/thebes/gfxRect.cpp96
-rw-r--r--gfx/thebes/gfxRect.h148
-rw-r--r--gfx/thebes/gfxSVGGlyphs.cpp463
-rw-r--r--gfx/thebes/gfxSVGGlyphs.h242
-rw-r--r--gfx/thebes/gfxScriptItemizer.cpp229
-rw-r--r--gfx/thebes/gfxScriptItemizer.h102
-rw-r--r--gfx/thebes/gfxSharedImageSurface.h24
-rw-r--r--gfx/thebes/gfxSkipChars.cpp153
-rw-r--r--gfx/thebes/gfxSkipChars.h308
-rw-r--r--gfx/thebes/gfxTextRun.cpp3214
-rw-r--r--gfx/thebes/gfxTextRun.h1229
-rw-r--r--gfx/thebes/gfxTypes.h82
-rw-r--r--gfx/thebes/gfxUserFontSet.cpp1439
-rw-r--r--gfx/thebes/gfxUserFontSet.h716
-rw-r--r--gfx/thebes/gfxUtils.cpp1547
-rw-r--r--gfx/thebes/gfxUtils.h325
-rw-r--r--gfx/thebes/gfxWindowsNativeDrawing.cpp320
-rw-r--r--gfx/thebes/gfxWindowsNativeDrawing.h116
-rwxr-xr-xgfx/thebes/gfxWindowsPlatform.cpp2139
-rw-r--r--gfx/thebes/gfxWindowsPlatform.h281
-rw-r--r--gfx/thebes/gfxWindowsSurface.cpp169
-rw-r--r--gfx/thebes/gfxWindowsSurface.h61
-rw-r--r--gfx/thebes/gfxXlibNativeRenderer.cpp620
-rw-r--r--gfx/thebes/gfxXlibNativeRenderer.h105
-rw-r--r--gfx/thebes/gfxXlibSurface.cpp614
-rw-r--r--gfx/thebes/gfxXlibSurface.h122
-rw-r--r--gfx/thebes/moz.build273
-rw-r--r--gfx/thebes/nsUnicodeRange.cpp419
-rw-r--r--gfx/thebes/nsUnicodeRange.h91
164 files changed, 79437 insertions, 0 deletions
diff --git a/gfx/thebes/CJKCompatSVS.cpp b/gfx/thebes/CJKCompatSVS.cpp
new file mode 100644
index 000000000..e68d98e92
--- /dev/null
+++ b/gfx/thebes/CJKCompatSVS.cpp
@@ -0,0 +1,1039 @@
+// 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/ContextStateTracker.cpp b/gfx/thebes/ContextStateTracker.cpp
new file mode 100644
index 000000000..e3659e5fc
--- /dev/null
+++ b/gfx/thebes/ContextStateTracker.cpp
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "ContextStateTracker.h"
+#include "GLContext.h"
+#ifdef MOZ_ENABLE_PROFILER_SPS
+#include "ProfilerMarkers.h"
+#endif
+
+namespace mozilla {
+
+void
+ContextStateTrackerOGL::PushOGLSection(GLContext* aGL, const char* aSectionName)
+{
+ if (!profiler_feature_active("gpu")) {
+ return;
+ }
+
+ if (!aGL->IsSupported(gl::GLFeature::query_objects)) {
+ return;
+ }
+
+ if (mSectionStack.Length() > 0) {
+ // We need to end the query since we're starting a new section and restore it
+ // when this section is finished.
+ aGL->fEndQuery(LOCAL_GL_TIME_ELAPSED);
+ Top().mCpuTimeEnd = TimeStamp::Now();
+ }
+
+ ContextState newSection(aSectionName);
+
+ GLuint queryObject;
+ aGL->fGenQueries(1, &queryObject);
+ newSection.mStartQueryHandle = queryObject;
+ newSection.mCpuTimeStart = TimeStamp::Now();
+
+ aGL->fBeginQuery(LOCAL_GL_TIME_ELAPSED_EXT, queryObject);
+
+ mSectionStack.AppendElement(newSection);
+}
+
+void
+ContextStateTrackerOGL::PopOGLSection(GLContext* aGL, const char* aSectionName)
+{
+ // We might have ignored a section start if we started profiling
+ // in the middle section. If so we will ignore this unmatched end.
+ if (mSectionStack.Length() == 0) {
+ return;
+ }
+
+ int i = mSectionStack.Length() - 1;
+ MOZ_ASSERT(strcmp(mSectionStack[i].mSectionName, aSectionName) == 0);
+ aGL->fEndQuery(LOCAL_GL_TIME_ELAPSED);
+ mSectionStack[i].mCpuTimeEnd = TimeStamp::Now();
+ mCompletedSections.AppendElement(mSectionStack[i]);
+ mSectionStack.RemoveElementAt(i);
+
+ if (i - 1 >= 0) {
+ const char* sectionToRestore = Top().mSectionName;
+
+ // We need to restore the outer section
+ // Well do this by completing this section and adding a new
+ // one with the same name
+ mCompletedSections.AppendElement(Top());
+ mSectionStack.RemoveElementAt(i - 1);
+
+ ContextState newSection(sectionToRestore);
+
+ GLuint queryObject;
+ aGL->fGenQueries(1, &queryObject);
+ newSection.mStartQueryHandle = queryObject;
+ newSection.mCpuTimeStart = TimeStamp::Now();
+
+ aGL->fBeginQuery(LOCAL_GL_TIME_ELAPSED_EXT, queryObject);
+
+ mSectionStack.AppendElement(newSection);
+ }
+
+ Flush(aGL);
+}
+
+void
+ContextStateTrackerOGL::Flush(GLContext* aGL)
+{
+ TimeStamp now = TimeStamp::Now();
+
+ while (mCompletedSections.Length() != 0) {
+ // On mac we see QUERY_RESULT_AVAILABLE cause a GL flush if we query it
+ // too early. For profiling we rather have the last 200ms of data missing
+ // while causing let's measurement distortions.
+ if (mCompletedSections[0].mCpuTimeEnd + TimeDuration::FromMilliseconds(200) > now) {
+ break;
+ }
+
+ GLuint handle = mCompletedSections[0].mStartQueryHandle;
+
+ // We've waiting 200ms, content rendering at > 20 FPS will be ready. We
+ // shouldn't see any flushes now.
+ GLuint returned = 0;
+ aGL->fGetQueryObjectuiv(handle, LOCAL_GL_QUERY_RESULT_AVAILABLE, &returned);
+
+ if (!returned) {
+ break;
+ }
+
+ GLuint gpuTime = 0;
+ aGL->fGetQueryObjectuiv(handle, LOCAL_GL_QUERY_RESULT, &gpuTime);
+
+ aGL->fDeleteQueries(1, &handle);
+
+#ifdef MOZ_ENABLE_PROFILER_SPS
+ PROFILER_MARKER_PAYLOAD("gpu_timer_query", new GPUMarkerPayload(
+ mCompletedSections[0].mCpuTimeStart,
+ mCompletedSections[0].mCpuTimeEnd,
+ 0,
+ gpuTime
+ ));
+#endif
+
+ mCompletedSections.RemoveElementAt(0);
+ }
+}
+
+void
+ContextStateTrackerOGL::DestroyOGL(GLContext* aGL)
+{
+ while (mCompletedSections.Length() != 0) {
+ GLuint handle = (GLuint)mCompletedSections[0].mStartQueryHandle;
+ aGL->fDeleteQueries(1, &handle);
+ mCompletedSections.RemoveElementAt(0);
+ }
+}
+
+} // namespace mozilla
+
diff --git a/gfx/thebes/ContextStateTracker.h b/gfx/thebes/ContextStateTracker.h
new file mode 100644
index 000000000..01cd3c5b2
--- /dev/null
+++ b/gfx/thebes/ContextStateTracker.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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_CONTEXTSTATETRACKER_H
+#define GFX_CONTEXTSTATETRACKER_H
+
+#include "GLTypes.h"
+#include "mozilla/TimeStamp.h"
+#include "nsTArray.h"
+#include <string.h>
+
+namespace mozilla {
+namespace gl {
+class GLContext;
+} // namespace gl
+
+/**
+ * This class tracks the state of the context for debugging and profiling.
+ * Each section pushes a new stack entry and must be matched by an end section.
+ * All nested section must be ended before ending a parent section.
+ */
+class ContextStateTracker {
+public:
+ ContextStateTracker() {}
+
+private:
+
+ bool IsProfiling() { return true; }
+
+protected:
+ typedef GLuint TimerQueryHandle;
+
+ class ContextState {
+ public:
+ explicit ContextState(const char* aSectionName)
+ : mSectionName(aSectionName)
+ {}
+
+ const char* mSectionName;
+ mozilla::TimeStamp mCpuTimeStart;
+ mozilla::TimeStamp mCpuTimeEnd;
+ TimerQueryHandle mStartQueryHandle;
+ };
+
+ ContextState& Top() {
+ MOZ_ASSERT(mSectionStack.Length());
+ return mSectionStack[mSectionStack.Length() - 1];
+ }
+
+ nsTArray<ContextState> mCompletedSections;
+ nsTArray<ContextState> mSectionStack;
+};
+
+/*
+class ID3D11DeviceContext;
+
+class ContextStateTrackerD3D11 final : public ContextStateTracker {
+public:
+ // TODO Implement me
+ void PushD3D11Section(ID3D11DeviceContext* aCtxt, const char* aSectionName) {}
+ void PopD3D11Section(ID3D11DeviceContext* aCtxt, const char* aSectionName) {}
+ void DestroyD3D11(ID3D11DeviceContext* aCtxt) {}
+
+private:
+ void Flush();
+};
+*/
+
+class ContextStateTrackerOGL final : public ContextStateTracker {
+ typedef mozilla::gl::GLContext GLContext;
+public:
+ void PushOGLSection(GLContext* aGL, const char* aSectionName);
+ void PopOGLSection(GLContext* aGL, const char* aSectionName);
+ void DestroyOGL(GLContext* aGL);
+private:
+ void Flush(GLContext* aGL);
+};
+
+} // namespace mozilla
+
+#endif
+
diff --git a/gfx/thebes/D3D11Checks.cpp b/gfx/thebes/D3D11Checks.cpp
new file mode 100644
index 000000000..b5be58ff5
--- /dev/null
+++ b/gfx/thebes/D3D11Checks.cpp
@@ -0,0 +1,412 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxConfig.h"
+#include "GfxDriverInfo.h"
+#include "gfxPrefs.h"
+#include "gfxWindowsPlatform.h"
+#include "mozilla/RefPtr.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;
+ }
+ int resultColor = *(int*)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 (gfxPrefs::Direct2DForceEnabled() ||
+ 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 (int 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)) {
+ gfxCriticalError(CriticalLog::DefaultOptions(false)) << "DisplayLink: too old version " << displayLinkModuleVersionString.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 (gfxPrefs::Direct2DForceEnabled() ||
+ gfxConfig::IsForcedOnByUser(Feature::HW_COMPOSITING))
+ {
+ return true;
+ }
+
+ if (GetModuleHandleW(L"atidxx32.dll")) {
+ nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
+ if (gfxInfo) {
+ nsString vendorID, vendorID2;
+ gfxInfo->GetAdapterVendorID(vendorID);
+ gfxInfo->GetAdapterVendorID2(vendorID2);
+ if (vendorID.EqualsLiteral("0x8086") && vendorID2.IsEmpty()) {
+ if (!gfxPrefs::LayersAMDSwitchableGfxEnabled()) {
+ 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 = services::GetGfxInfo();
+ nsString vendorID;
+ gfxInfo->GetAdapterVendorID(vendorID);
+ nsresult ec;
+ int32_t vendor = vendorID.ToInteger(&ec, 16);
+ if (vendor != 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;
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/thebes/D3D11Checks.h b/gfx/thebes/D3D11Checks.h
new file mode 100644
index 000000000..07a9d940f
--- /dev/null
+++ b/gfx/thebes/D3D11Checks.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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
+
+struct ID3D11Device;
+struct IDXGIAdapter;
+struct DXGI_ADAPTER_DESC;
+
+namespace mozilla {
+namespace gfx {
+
+struct D3D11Checks
+{
+ 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);
+};
+
+} // namespace gfx
+} // 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 000000000..c194f40f2
--- /dev/null
+++ b/gfx/thebes/DeviceManagerDx.cpp
@@ -0,0 +1,878 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxPrefs.h"
+#include "gfxWindowsPlatform.h"
+#include "mozilla/D3DMessageUtils.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/gfx/GraphicsMessages.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "nsIGfxInfo.h"
+#include <d3d11.h>
+#include <ddraw.h>
+
+namespace mozilla {
+namespace gfx {
+
+using namespace mozilla::widget;
+
+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;
+
+// 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);
+}
+
+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",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D3D11_LIB"));
+ 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",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D3D11_FUNCPTR"));
+ return false;
+ }
+
+ mD3D11Module.steal(module);
+ return true;
+}
+
+void
+DeviceManagerDx::ReleaseD3D11()
+{
+ MOZ_ASSERT(!mCompositorDevice);
+ MOZ_ASSERT(!mContentDevice);
+ MOZ_ASSERT(!mDecoderDevice);
+
+ mD3D11Module.reset();
+ sD3D11CreateDeviceFn = nullptr;
+}
+
+static inline bool
+ProcessOwnsCompositor()
+{
+ return XRE_GetProcessType() == GeckoProcessType_GPU ||
+ (XRE_IsParentProcess() && !gfxConfig::IsEnabled(Feature::GPU_PROCESS));
+}
+
+bool
+DeviceManagerDx::CreateCompositorDevices()
+{
+ MOZ_ASSERT(ProcessOwnsCompositor());
+
+ FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING);
+ MOZ_ASSERT(d3d11.IsEnabled());
+
+ 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);
+ return d3d11.IsEnabled();
+}
+
+void
+DeviceManagerDx::ImportDeviceInfo(const D3D11DeviceStatus& aDeviceStatus)
+{
+ MOZ_ASSERT(!ProcessOwnsCompositor());
+
+ mDeviceStatus = Some(aDeviceStatus);
+}
+
+void
+DeviceManagerDx::ExportDeviceInfo(D3D11DeviceStatus* aOut)
+{
+ // Even though the parent process might not own the compositor, we still
+ // populate DeviceManagerDx with device statistics (for simplicity).
+ // That means it still gets queried for compositor information.
+ MOZ_ASSERT(XRE_IsParentProcess() || XRE_GetProcessType() == GeckoProcessType_GPU);
+
+ if (mDeviceStatus) {
+ *aOut = mDeviceStatus.value();
+ }
+}
+
+void
+DeviceManagerDx::CreateContentDevices()
+{
+ 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();
+ }
+}
+
+IDXGIAdapter1*
+DeviceManagerDx::GetDXGIAdapter()
+{
+ if (mAdapter) {
+ return mAdapter;
+ }
+
+ nsModuleHandle dxgiModule(LoadLibrarySystem32(L"dxgi.dll"));
+ decltype(CreateDXGIFactory1)* createDXGIFactory1 = (decltype(CreateDXGIFactory1)*)
+ GetProcAddress(dxgiModule, "CreateDXGIFactory1");
+
+ if (!createDXGIFactory1) {
+ return nullptr;
+ }
+
+ // Try to use a DXGI 1.1 adapter in order to share resources
+ // across processes.
+ RefPtr<IDXGIFactory1> 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.
+ MOZ_ASSERT(XRE_IsContentProcess());
+
+ // 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 (gfxPrefs::DeviceFailForTesting()) {
+ aD3d11.SetFailed(FeatureStatus::Failed, "Direct3D11 device failure simulated by preference",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D3D11_SIM"));
+ return false;
+ }
+
+ // 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 | 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",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D3D11_DEVICE1"));
+ }
+ return false;
+ }
+
+ if (FAILED(hr) || !device) {
+ if (!aAttemptVideoSupport) {
+ aD3d11.SetFailed(FeatureStatus::Failed, "Failed to acquire a D3D11 device",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D3D11_DEVICE2"));
+ }
+ return false;
+ }
+ if (!D3D11Checks::DoesDeviceWork()) {
+ if (!aAttemptVideoSupport) {
+ aD3d11.SetFailed(FeatureStatus::Broken, "Direct3D11 device was determined to be broken",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D3D11_BROKEN"));
+ }
+ return false;
+ }
+
+ aOutDevice = device;
+ return true;
+}
+
+void
+DeviceManagerDx::CreateCompositorDevice(FeatureState& d3d11)
+{
+ if (gfxPrefs::LayersD3D11ForceWARP()) {
+ CreateWARPCompositorDevice();
+ return;
+ }
+
+ RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapter();
+ if (!adapter) {
+ d3d11.SetFailed(FeatureStatus::Unavailable, "Failed to acquire a DXGI adapter",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D3D11_DXGI"));
+ return;
+ }
+
+ if (XRE_IsGPUProcess() && !D3D11Checks::DoesRemotePresentWork(adapter)) {
+ d3d11.SetFailed(
+ FeatureStatus::Unavailable,
+ "DXGI does not support out-of-process presentation",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D3D11_REMOTE_PRESENT"));
+ 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");
+ }
+ if (D3D11Checks::DoesRenderTargetViewNeedRecreating(device)) {
+ gfxConfig::SetFailed(Feature::D3D11_HW_ANGLE,
+ FeatureStatus::Broken,
+ "RenderTargetViews need recreating");
+ }
+ if (XRE_IsParentProcess()) {
+ // It seems like this may only happen when we're using the NVIDIA gpu
+ D3D11Checks::WarnOnAdapterMismatch(device);
+ }
+
+ int featureLevel = device->GetFeatureLevel();
+ {
+ MutexAutoLock lock(mDeviceLock);
+ mCompositorDevice = device;
+ mDeviceStatus = Some(D3D11DeviceStatus(
+ false,
+ textureSharingWorks,
+ featureLevel,
+ DxgiAdapterDesc::From(desc)));
+ }
+ mCompositorDevice->SetExceptionMode(0);
+}
+
+bool
+DeviceManagerDx::CreateDevice(IDXGIAdapter* aAdapter,
+ D3D_DRIVER_TYPE aDriverType,
+ UINT aFlags,
+ HRESULT& aResOut,
+ RefPtr<ID3D11Device>& aOutDevice)
+{
+ 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;
+ }
+ return true;
+}
+
+void
+DeviceManagerDx::CreateWARPCompositorDevice()
+{
+ ScopedGfxFeatureReporter reporterWARP("D3D11-WARP", gfxPrefs::LayersD3D11ForceWARP());
+ 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",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D3D11_WARP_DEVICE"));
+ }
+
+ 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",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D3D11_WARP_DEVICE2"));
+ 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);
+ }
+
+ DxgiAdapterDesc nullAdapter;
+ PodZero(&nullAdapter);
+
+ int featureLevel = device->GetFeatureLevel();
+ {
+ MutexAutoLock lock(mDeviceLock);
+ mCompositorDevice = device;
+ mDeviceStatus = Some(D3D11DeviceStatus(
+ true,
+ textureSharingWorks,
+ featureLevel,
+ nullAdapter));
+ }
+ mCompositorDevice->SetExceptionMode(0);
+
+ reporterWARP.SetSuccessful();
+}
+
+FeatureStatus
+DeviceManagerDx::CreateContentDevice()
+{
+ RefPtr<IDXGIAdapter1> adapter;
+ if (!mDeviceStatus->isWARP()) {
+ adapter = GetDXGIAdapter();
+ 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);
+ }
+
+ {
+ MutexAutoLock lock(mDeviceLock);
+ 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 isAMD = false;
+ {
+ MutexAutoLock lock(mDeviceLock);
+ if (!mDeviceStatus) {
+ return nullptr;
+ }
+ isAMD = mDeviceStatus->adapter().VendorId == 0x1002;
+ }
+
+ bool reuseDevice = false;
+ if (gfxPrefs::Direct3D11ReuseDecoderDevice() < 0) {
+ // Use the default logic, which is to allow reuse of devices on AMD, but create
+ // separate devices everywhere else.
+ if (isAMD) {
+ reuseDevice = true;
+ }
+ } else if (gfxPrefs::Direct3D11ReuseDecoderDevice() > 0) {
+ reuseDevice = true;
+ }
+
+ if (reuseDevice) {
+ if (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 = GetDXGIAdapter();
+ 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));
+
+ multi->SetMultithreadProtected(TRUE);
+
+ if (reuseDevice) {
+ mDecoderDevice = device;
+ }
+ return device;
+}
+
+void
+DeviceManagerDx::ResetDevices()
+{
+ MutexAutoLock lock(mDeviceLock);
+
+ mAdapter = nullptr;
+ mCompositorDevice = nullptr;
+ mContentDevice = nullptr;
+ mDeviceStatus = Nothing();
+ mDeviceResetReason = Nothing();
+ Factory::SetDirect3D11Device(nullptr);
+}
+
+bool
+DeviceManagerDx::MaybeResetAndReacquireDevices()
+{
+ DeviceResetReason resetReason;
+ if (!HasDeviceReset(&resetReason)) {
+ return false;
+ }
+
+ if (resetReason != DeviceResetReason::FORCED_RESET) {
+ Telemetry::Accumulate(Telemetry::DEVICE_RESET_REASON, uint32_t(resetReason));
+ }
+
+ bool createCompositorDevice = !!mCompositorDevice;
+ bool createContentDevice = !!mContentDevice;
+
+ ResetDevices();
+
+ if (createCompositorDevice && !CreateCompositorDevices()) {
+ // Just stop, don't try anything more
+ return true;
+ }
+ if (createContentDevice) {
+ CreateContentDevices();
+ }
+
+ 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::UNKNOWN;
+}
+
+bool
+DeviceManagerDx::HasDeviceReset(DeviceResetReason* aOutReason)
+{
+ MutexAutoLock lock(mDeviceLock);
+
+ 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)
+{
+ // Caller must own the lock, since we access devices directly, and can be
+ // called from any thread.
+ mDeviceLock.AssertCurrentThreadOwns();
+
+ if (DidDeviceReset(mCompositorDevice, aOutReason) ||
+ DidDeviceReset(mContentDevice, aOutReason))
+ {
+ return true;
+ }
+
+ if (XRE_IsParentProcess() &&
+ NS_IsMainThread() &&
+ gfxPrefs::DeviceResetForTesting())
+ {
+ gfxPrefs::SetDeviceResetForTesting(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);
+ mDeviceResetReason = Some(DeviceResetReason::FORCED_RESET);
+ }
+}
+
+void
+DeviceManagerDx::NotifyD3D9DeviceReset()
+{
+ MutexAutoLock lock(mDeviceLock);
+ mDeviceResetReason = Some(DeviceResetReason::D3D9_RESET);
+}
+
+void
+DeviceManagerDx::DisableD3D11AfterCrash()
+{
+ gfxConfig::Disable(Feature::D3D11_COMPOSITING,
+ FeatureStatus::CrashedInHandler,
+ "Crashed while acquiring a Direct3D11 device",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D3D11_CRASH"));
+ ResetDevices();
+}
+
+RefPtr<ID3D11Device>
+DeviceManagerDx::GetCompositorDevice()
+{
+ MutexAutoLock lock(mDeviceLock);
+ return mCompositorDevice;
+}
+
+RefPtr<ID3D11Device>
+DeviceManagerDx::GetContentDevice()
+{
+ MutexAutoLock lock(mDeviceLock);
+ return mContentDevice;
+}
+
+unsigned
+DeviceManagerDx::GetCompositorFeatureLevel() const
+{
+ 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);
+ if (!mDeviceStatus) {
+ return false;
+ }
+ // Disable this on all Intel devices because of crashes.
+ // See bug 1292923.
+ return mDeviceStatus->adapter().VendorId != 0x8086;
+}
+
+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();
+}
+
+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",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_DDRAW_LIB"));
+ return;
+ }
+
+ sDirectDrawCreateExFn =
+ (decltype(DirectDrawCreateEx)*)GetProcAddress(mDirectDrawDLL, "DirectDrawCreateEx");
+ if (!sDirectDrawCreateExFn) {
+ ddraw.SetFailed(FeatureStatus::Unavailable, "DirectDraw not available on this computer",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_DDRAW_LIB"));
+ 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",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_DDRAW_LIB"));
+ gfxCriticalNote << "DoesCreatingDirectDrawFailed";
+ return;
+ }
+ if (FAILED(hr)) {
+ ddraw.SetFailed(FeatureStatus::Failed, "Failed to create DirectDraw",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_DDRAW_LIB"));
+ gfxCriticalNote << "DoesCreatingDirectDrawFailed " << hexa(hr);
+ return;
+ }
+}
+
+IDirectDraw7*
+DeviceManagerDx::GetDirectDraw()
+{
+ return mDirectDraw;
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/thebes/DeviceManagerDx.h b/gfx/thebes/DeviceManagerDx.h
new file mode 100644
index 000000000..ea6dd4846
--- /dev/null
+++ b/gfx/thebes/DeviceManagerDx.h
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "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 <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
+
+struct ID3D11Device;
+struct IDirectDraw7;
+
+namespace mozilla {
+class ScopedGfxFeatureReporter;
+
+namespace gfx {
+class FeatureState;
+
+class DeviceManagerDx final
+{
+public:
+ static void Init();
+ static void Shutdown();
+
+ DeviceManagerDx();
+
+ static DeviceManagerDx* Get() {
+ return sInstance;
+ }
+
+ RefPtr<ID3D11Device> GetCompositorDevice();
+ RefPtr<ID3D11Device> GetContentDevice();
+ RefPtr<ID3D11Device> CreateDecoderDevice();
+ IDirectDraw7* GetDirectDraw();
+
+ unsigned GetCompositorFeatureLevel() const;
+ bool TextureSharingWorks();
+ bool IsWARP();
+
+ // 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();
+
+ bool CreateCompositorDevices();
+ void CreateContentDevices();
+
+ void ImportDeviceInfo(const D3D11DeviceStatus& aDeviceStatus);
+ void 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);
+ void NotifyD3D9DeviceReset();
+
+private:
+ IDXGIAdapter1 *GetDXGIAdapter();
+
+ void DisableD3D11AfterCrash();
+
+ void CreateCompositorDevice(mozilla::gfx::FeatureState& d3d11);
+ bool CreateCompositorDeviceHelper(
+ mozilla::gfx::FeatureState& aD3d11,
+ IDXGIAdapter1* aAdapter,
+ bool aAttemptVideoSupport,
+ RefPtr<ID3D11Device>& aOutDevice);
+
+ void CreateWARPCompositorDevice();
+
+ mozilla::gfx::FeatureStatus CreateContentDevice();
+
+ bool CreateDevice(IDXGIAdapter* aAdapter,
+ D3D_DRIVER_TYPE aDriverType,
+ UINT aFlags,
+ HRESULT& aResOut,
+ RefPtr<ID3D11Device>& aOutDevice);
+
+ bool ContentAdapterIsParentAdapter(ID3D11Device* device);
+
+ bool LoadD3D11();
+ void ReleaseD3D11();
+
+ // Call GetDeviceRemovedReason on each device until one returns
+ // a failure.
+ bool GetAnyDeviceRemovedReason(DeviceResetReason* aOutReason);
+
+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;
+
+ mozilla::Mutex mDeviceLock;
+ nsTArray<D3D_FEATURE_LEVEL> mFeatureLevels;
+ RefPtr<IDXGIAdapter1> mAdapter;
+ RefPtr<ID3D11Device> mCompositorDevice;
+ RefPtr<ID3D11Device> mContentDevice;
+ RefPtr<ID3D11Device> mDecoderDevice;
+ bool mCompositorDeviceSupportsVideo;
+
+ Maybe<D3D11DeviceStatus> mDeviceStatus;
+
+ nsModuleHandle mDirectDrawDLL;
+ RefPtr<IDirectDraw7> mDirectDraw;
+
+ Maybe<DeviceResetReason> mDeviceResetReason;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // mozilla_gfx_thebes_DeviceManagerDx_h
diff --git a/gfx/thebes/DrawMode.h b/gfx/thebes/DrawMode.h
new file mode 100644
index 000000000..139dae4b0
--- /dev/null
+++ b/gfx/thebes/DrawMode.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/PrintTarget.cpp b/gfx/thebes/PrintTarget.cpp
new file mode 100644
index 000000000..a07751300
--- /dev/null
+++ b/gfx/thebes/PrintTarget.cpp
@@ -0,0 +1,159 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Logging.h"
+
+namespace mozilla {
+namespace 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:
+#ifdef MOZ_TREE_CAIRO
+ if (mCairoSurface &&
+ cairo_surface_get_content(mCairoSurface) != CAIRO_CONTENT_COLOR) {
+ cairo_surface_set_subpixel_antialiasing(mCairoSurface,
+ CAIRO_SUBPIXEL_ANTIALIASING_DISABLED);
+ }
+#endif
+}
+
+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(DrawEventRecorder* aRecorder)
+{
+ if (!mRefDT) {
+ IntSize size(1, 1);
+
+ cairo_surface_t* surface =
+ cairo_surface_create_similar(mCairoSurface,
+ cairo_surface_get_content(mCairoSurface),
+ size.width, size.height);
+
+ if (cairo_surface_status(surface)) {
+ return nullptr;
+ }
+
+ RefPtr<DrawTarget> dt =
+ Factory::CreateDrawTargetForCairoSurface(surface, size);
+
+ // The DT addrefs the surface, so we need drop our own reference to it:
+ cairo_surface_destroy(surface);
+
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+
+ if (aRecorder) {
+ dt = CreateRecordingDrawTarget(aRecorder, dt);
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+ }
+
+ mRefDT = dt.forget();
+ }
+ return do_AddRef(mRefDT);
+}
+
+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);
+ }
+
+ 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 gfx
+} // namespace mozilla
diff --git a/gfx/thebes/PrintTarget.h b/gfx/thebes/PrintTarget.h
new file mode 100644
index 000000000..111981c70
--- /dev/null
+++ b/gfx/thebes/PrintTarget.h
@@ -0,0 +1,160 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "mozilla/RefPtr.h"
+#include "mozilla/gfx/2D.h"
+#include "nsISupportsImpl.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) {
+ 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();
+
+ /**
+ * Returns true if to print landscape our consumers must apply a 90 degrees
+ * rotation to our DrawTarget.
+ */
+ virtual bool RotateNeededForLandscape() const {
+ return false;
+ }
+
+ 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 it is still valid to use after EndPage() has been
+ * called.
+ */
+ virtual already_AddRefed<DrawTarget> GetReferenceDrawTarget(DrawEventRecorder* aRecorder);
+
+protected:
+
+ // Only created via subclass's constructors
+ explicit PrintTarget(cairo_surface_t* aCairoSurface, const IntSize& aSize);
+
+ // Protected because we're refcounted
+ virtual ~PrintTarget();
+
+ 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.cpp b/gfx/thebes/PrintTargetCG.cpp
new file mode 100644
index 000000000..5fe838182
--- /dev/null
+++ b/gfx/thebes/PrintTargetCG.cpp
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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"
+
+namespace mozilla {
+namespace gfx {
+
+PrintTargetCG::PrintTargetCG(cairo_surface_t* aCairoSurface,
+ const IntSize& aSize)
+ : PrintTarget(aCairoSurface, aSize)
+{
+ // TODO: Add memory reporting like gfxQuartzSurface.
+ //RecordMemoryUsed(mSize.height * 4 + sizeof(gfxQuartzSurface));
+}
+
+/* static */ already_AddRefed<PrintTargetCG>
+PrintTargetCG::CreateOrNull(const IntSize& aSize, gfxImageFormat aFormat)
+{
+ if (!Factory::CheckSurfaceSize(aSize)) {
+ return nullptr;
+ }
+
+ unsigned int width = static_cast<unsigned int>(aSize.width);
+ unsigned int height = static_cast<unsigned int>(aSize.height);
+
+ cairo_format_t cformat = GfxFormatToCairoFormat(aFormat);
+ cairo_surface_t* surface =
+ cairo_quartz_surface_create(cformat, width, height);
+
+ if (cairo_surface_status(surface)) {
+ return nullptr;
+ }
+
+ // The new object takes ownership of our surface reference.
+ RefPtr<PrintTargetCG> target = new PrintTargetCG(surface, aSize);
+
+ return target.forget();
+}
+
+/* static */ already_AddRefed<PrintTargetCG>
+PrintTargetCG::CreateOrNull(CGContextRef aContext, const IntSize& aSize)
+{
+ if (!Factory::CheckSurfaceSize(aSize)) {
+ return nullptr;
+ }
+
+ unsigned int width = static_cast<unsigned int>(aSize.width);
+ unsigned int height = static_cast<unsigned int>(aSize.height);
+
+ cairo_surface_t* surface =
+ cairo_quartz_surface_create_for_cg_context(aContext, width, height);
+
+ if (cairo_surface_status(surface)) {
+ return nullptr;
+ }
+
+ // The new object takes ownership of our surface reference.
+ RefPtr<PrintTargetCG> target = new PrintTargetCG(surface, aSize);
+
+ return target.forget();
+}
+
+static size_t
+PutBytesNull(void* info, const void* buffer, size_t count)
+{
+ return count;
+}
+
+already_AddRefed<DrawTarget>
+PrintTargetCG::GetReferenceDrawTarget(DrawEventRecorder* aRecorder)
+{
+ 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;
+ }
+
+ if (aRecorder) {
+ dt = CreateRecordingDrawTarget(aRecorder, dt);
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+ }
+
+ mRefDT = dt.forget();
+ }
+ return do_AddRef(mRefDT);
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/thebes/PrintTargetCG.h b/gfx/thebes/PrintTargetCG.h
new file mode 100644
index 000000000..87dbdbc2c
--- /dev/null
+++ b/gfx/thebes/PrintTargetCG.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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"
+
+namespace mozilla {
+namespace gfx {
+
+/**
+ * CoreGraphics printing target.
+ *
+ * Note that a CGContextRef obtained from PMSessionGetCGGraphicsContext is
+ * valid only for the current page. As a consequence instances of this class
+ * should only be used to print a single page.
+ */
+class PrintTargetCG final : public PrintTarget
+{
+public:
+ static already_AddRefed<PrintTargetCG>
+ CreateOrNull(const IntSize& aSize, gfxImageFormat aFormat);
+
+ static already_AddRefed<PrintTargetCG>
+ CreateOrNull(CGContextRef aContext, const IntSize& aSize);
+
+ virtual already_AddRefed<DrawTarget>
+ GetReferenceDrawTarget(DrawEventRecorder* aRecorder) final;
+
+private:
+ PrintTargetCG(cairo_surface_t* aCairoSurface,
+ const IntSize& aSize);
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_PRINTTARGETCG_H */
diff --git a/gfx/thebes/PrintTargetPDF.cpp b/gfx/thebes/PrintTargetPDF.cpp
new file mode 100644
index 000000000..ce536c07f
--- /dev/null
+++ b/gfx/thebes/PrintTargetPDF.cpp
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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"
+
+namespace mozilla {
+namespace gfx {
+
+static cairo_status_t
+write_func(void *closure, const unsigned char *data, unsigned int length)
+{
+ 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)
+{
+ 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;
+ }
+
+ // 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 NS_OK;
+}
+
+void
+PrintTargetPDF::Finish()
+{
+ if (mIsFinished) {
+ return; // We don't want to call Close() on mStream more than once
+ }
+ PrintTarget::Finish();
+ mStream->Close();
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/thebes/PrintTargetPDF.h b/gfx/thebes/PrintTargetPDF.h
new file mode 100644
index 000000000..dd338c626
--- /dev/null
+++ b/gfx/thebes/PrintTargetPDF.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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);
+
+ virtual nsresult EndPage() override;
+ virtual 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/PrintTargetPS.cpp b/gfx/thebes/PrintTargetPS.cpp
new file mode 100644
index 000000000..5ea75a56c
--- /dev/null
+++ b/gfx/thebes/PrintTargetPS.cpp
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "PrintTargetPS.h"
+
+#include "cairo.h"
+#include "cairo-ps.h"
+
+namespace mozilla {
+namespace gfx {
+
+static cairo_status_t
+write_func(void *closure, const unsigned char *data, unsigned int length)
+{
+ 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;
+}
+
+PrintTargetPS::PrintTargetPS(cairo_surface_t* aCairoSurface,
+ const IntSize& aSize,
+ nsIOutputStream *aStream,
+ PageOrientation aOrientation)
+ : PrintTarget(aCairoSurface, aSize)
+ , mStream(aStream)
+ , mOrientation(aOrientation)
+{
+}
+
+PrintTargetPS::~PrintTargetPS()
+{
+ // 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<PrintTargetPS>
+PrintTargetPS::CreateOrNull(nsIOutputStream *aStream,
+ IntSize aSizeInPoints,
+ PageOrientation aOrientation)
+{
+ // The PS output does not specify the page size so to print landscape we need
+ // to rotate the drawing 90 degrees and print on portrait paper. If printing
+ // landscape, swap the width/height supplied to cairo to select a portrait
+ // print area. Our consumers are responsible for checking
+ // RotateForLandscape() and applying a rotation transform if true.
+ if (aOrientation == LANDSCAPE) {
+ Swap(aSizeInPoints.width, aSizeInPoints.height);
+ }
+
+ cairo_surface_t* surface =
+ cairo_ps_surface_create_for_stream(write_func, (void*)aStream,
+ aSizeInPoints.width,
+ aSizeInPoints.height);
+ if (cairo_surface_status(surface)) {
+ return nullptr;
+ }
+ cairo_ps_surface_restrict_to_level(surface, CAIRO_PS_LEVEL_2);
+
+ // The new object takes ownership of our surface reference.
+ RefPtr<PrintTargetPS> target = new PrintTargetPS(surface, aSizeInPoints,
+ aStream, aOrientation);
+ return target.forget();
+}
+
+nsresult
+PrintTargetPS::BeginPrinting(const nsAString& aTitle,
+ const nsAString& aPrintToFileName)
+{
+ if (mOrientation == PORTRAIT) {
+ cairo_ps_surface_dsc_comment(mCairoSurface, "%%Orientation: Portrait");
+ } else {
+ cairo_ps_surface_dsc_comment(mCairoSurface, "%%Orientation: Landscape");
+ }
+ return NS_OK;
+}
+
+nsresult
+PrintTargetPS::EndPage()
+{
+ cairo_surface_show_page(mCairoSurface);
+ return NS_OK;
+}
+
+void
+PrintTargetPS::Finish()
+{
+ if (mIsFinished) {
+ return; // We don't want to call Close() on mStream more than once
+ }
+ PrintTarget::Finish();
+ mStream->Close();
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/thebes/PrintTargetPS.h b/gfx/thebes/PrintTargetPS.h
new file mode 100644
index 000000000..68c1bd00b
--- /dev/null
+++ b/gfx/thebes/PrintTargetPS.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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_PRINTINGTARGETPS_H
+#define MOZILLA_GFX_PRINTINGTARGETPS_H
+
+#include "nsCOMPtr.h"
+#include "nsIOutputStream.h"
+#include "PrintTarget.h"
+
+namespace mozilla {
+namespace gfx {
+
+/**
+ * PostScript printing target.
+ */
+class PrintTargetPS final : public PrintTarget {
+public:
+ enum PageOrientation {
+ PORTRAIT,
+ LANDSCAPE
+ };
+
+ static already_AddRefed<PrintTargetPS>
+ CreateOrNull(nsIOutputStream *aStream,
+ IntSize aSizeInPoints,
+ PageOrientation aOrientation);
+
+ virtual nsresult BeginPrinting(const nsAString& aTitle,
+ const nsAString& aPrintToFileName) override;
+ virtual nsresult EndPage() override;
+ virtual void Finish() override;
+
+ virtual bool GetRotateForLandscape() {
+ return (mOrientation == LANDSCAPE);
+ }
+
+private:
+ PrintTargetPS(cairo_surface_t* aCairoSurface,
+ const IntSize& aSize,
+ nsIOutputStream *aStream,
+ PageOrientation aOrientation);
+ virtual ~PrintTargetPS();
+
+ nsCOMPtr<nsIOutputStream> mStream;
+ PageOrientation mOrientation;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_PRINTINGTARGETPS_H */
diff --git a/gfx/thebes/PrintTargetRecording.cpp b/gfx/thebes/PrintTargetRecording.cpp
new file mode 100644
index 000000000..e0df17a86
--- /dev/null
+++ b/gfx/thebes/PrintTargetRecording.cpp
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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);
+ }
+
+ 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 000000000..c6512ae99
--- /dev/null
+++ b/gfx/thebes/PrintTargetRecording.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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);
+
+ virtual already_AddRefed<DrawTarget>
+ MakeDrawTarget(const IntSize& aSize,
+ DrawEventRecorder* aRecorder = nullptr) override;
+
+private:
+ PrintTargetRecording(cairo_surface_t* aCairoSurface,
+ const IntSize& aSize);
+
+ already_AddRefed<DrawTarget>
+ CreateRecordingDrawTarget(DrawEventRecorder* aRecorder,
+ DrawTarget* aDrawTarget);
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_PRINTTARGETRECORDING_H */
diff --git a/gfx/thebes/PrintTargetThebes.cpp b/gfx/thebes/PrintTargetThebes.cpp
new file mode 100644
index 000000000..1cacf098f
--- /dev/null
+++ b/gfx/thebes/PrintTargetThebes.cpp
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 {
+namespace 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::GetPlatform()->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(DrawEventRecorder* aRecorder)
+{
+ if (!mRefDT) {
+ RefPtr<gfx::DrawTarget> dt =
+ gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(mGfxSurface, mSize);
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+ if (aRecorder) {
+ dt = CreateRecordingDrawTarget(aRecorder, dt);
+ 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)
+{
+ 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 gfx
+} // namespace mozilla
diff --git a/gfx/thebes/PrintTargetThebes.h b/gfx/thebes/PrintTargetThebes.h
new file mode 100644
index 000000000..aae3c9285
--- /dev/null
+++ b/gfx/thebes/PrintTargetThebes.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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);
+
+ virtual nsresult BeginPrinting(const nsAString& aTitle,
+ const nsAString& aPrintToFileName) override;
+ virtual nsresult EndPrinting() override;
+ virtual nsresult AbortPrinting() override;
+ virtual nsresult BeginPage() override;
+ virtual nsresult EndPage() override;
+ virtual void Finish() override;
+
+ virtual already_AddRefed<DrawTarget>
+ MakeDrawTarget(const IntSize& aSize,
+ DrawEventRecorder* aRecorder = nullptr) override;
+
+ virtual already_AddRefed<DrawTarget> GetReferenceDrawTarget(DrawEventRecorder* aRecorder) 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 000000000..4f22adacf
--- /dev/null
+++ b/gfx/thebes/PrintTargetWindows.cpp
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 cairo surface size - Windows we need to use the printable
+ // area of the page. 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, HORZRES) * POINTS_PER_INCH_FLOAT) / heightDPI;
+ float height =
+ (::GetDeviceCaps(aDC, VERTRES) * 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)
+{
+ 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;
+
+ ::StartDocW(mDC, &docinfo);
+
+ return NS_OK;
+}
+
+nsresult
+PrintTargetWindows::EndPrinting()
+{
+ int result = ::EndDoc(mDC);
+ return (result <= 0) ? NS_ERROR_FAILURE : NS_OK;
+}
+
+nsresult
+PrintTargetWindows::AbortPrinting()
+{
+ int result = ::AbortDoc(mDC);
+ return (result <= 0) ? NS_ERROR_FAILURE : NS_OK;
+}
+
+nsresult
+PrintTargetWindows::BeginPage()
+{
+ int result = ::StartPage(mDC);
+ return (result <= 0) ? NS_ERROR_FAILURE : NS_OK;
+}
+
+nsresult
+PrintTargetWindows::EndPage()
+{
+ cairo_surface_show_page(mCairoSurface);
+ 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 000000000..4f53b3949
--- /dev/null
+++ b/gfx/thebes/PrintTargetWindows.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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);
+
+ virtual nsresult BeginPrinting(const nsAString& aTitle,
+ const nsAString& aPrintToFileName) override;
+ virtual nsresult EndPrinting() override;
+ virtual nsresult AbortPrinting() override;
+ virtual nsresult BeginPage() override;
+ virtual 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/RoundedRect.h b/gfx/thebes/RoundedRect.h
new file mode 100644
index 000000000..6da69275f
--- /dev/null
+++ b/gfx/thebes/RoundedRect.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxRect.h"
+#include "mozilla/gfx/PathHelpers.h"
+
+namespace mozilla {
+/* A rounded rectangle abstraction.
+ *
+ * This can represent a rectangle with a different pair of radii on each corner.
+ *
+ * Note: CoreGraphics and Direct2D only support rounded rectangle with the same
+ * radii on all corners. However, supporting CSS's border-radius requires the extra flexibility. */
+struct RoundedRect {
+ typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
+
+ RoundedRect(gfxRect &aRect, RectCornerRadii &aCorners) : rect(aRect), corners(aCorners) { }
+ void Deflate(gfxFloat aTopWidth, gfxFloat aBottomWidth, gfxFloat aLeftWidth, gfxFloat aRightWidth) {
+ // deflate the internal rect
+ rect.x += aLeftWidth;
+ rect.y += aTopWidth;
+ rect.width = std::max(0., rect.width - aLeftWidth - aRightWidth);
+ rect.height = std::max(0., rect.height - aTopWidth - aBottomWidth);
+
+ corners.radii[NS_CORNER_TOP_LEFT].width = std::max(0., corners.radii[NS_CORNER_TOP_LEFT].width - aLeftWidth);
+ corners.radii[NS_CORNER_TOP_LEFT].height = std::max(0., corners.radii[NS_CORNER_TOP_LEFT].height - aTopWidth);
+
+ corners.radii[NS_CORNER_TOP_RIGHT].width = std::max(0., corners.radii[NS_CORNER_TOP_RIGHT].width - aRightWidth);
+ corners.radii[NS_CORNER_TOP_RIGHT].height = std::max(0., corners.radii[NS_CORNER_TOP_RIGHT].height - aTopWidth);
+
+ corners.radii[NS_CORNER_BOTTOM_LEFT].width = std::max(0., corners.radii[NS_CORNER_BOTTOM_LEFT].width - aLeftWidth);
+ corners.radii[NS_CORNER_BOTTOM_LEFT].height = std::max(0., corners.radii[NS_CORNER_BOTTOM_LEFT].height - aBottomWidth);
+
+ corners.radii[NS_CORNER_BOTTOM_RIGHT].width = std::max(0., corners.radii[NS_CORNER_BOTTOM_RIGHT].width - aRightWidth);
+ corners.radii[NS_CORNER_BOTTOM_RIGHT].height = std::max(0., corners.radii[NS_CORNER_BOTTOM_RIGHT].height - aBottomWidth);
+ }
+ gfxRect rect;
+ RectCornerRadii corners;
+};
+
+} // namespace mozilla
diff --git a/gfx/thebes/SoftwareVsyncSource.cpp b/gfx/thebes/SoftwareVsyncSource.cpp
new file mode 100644
index 000000000..7e92b89ec
--- /dev/null
+++ b/gfx/thebes/SoftwareVsyncSource.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 "SoftwareVsyncSource.h"
+#include "base/task.h"
+#include "gfxPlatform.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+
+SoftwareVsyncSource::SoftwareVsyncSource()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mGlobalDisplay = new SoftwareDisplay();
+}
+
+SoftwareVsyncSource::~SoftwareVsyncSource()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mGlobalDisplay = nullptr;
+}
+
+SoftwareDisplay::SoftwareDisplay()
+ : mVsyncEnabled(false)
+{
+ // Mimic 60 fps
+ MOZ_ASSERT(NS_IsMainThread());
+ const double rate = 1000.0 / (double) gfxPlatform::GetSoftwareVsyncRate();
+ mVsyncRate = mozilla::TimeDuration::FromMilliseconds(rate);
+ mVsyncThread = new base::Thread("SoftwareVsyncThread");
+ MOZ_RELEASE_ASSERT(mVsyncThread->Start(), "GFX: Could not start software vsync thread");
+}
+
+SoftwareDisplay::~SoftwareDisplay() {}
+
+void
+SoftwareDisplay::EnableVsync()
+{
+ MOZ_ASSERT(mVsyncThread->IsRunning());
+ if (NS_IsMainThread()) {
+ if (mVsyncEnabled) {
+ return;
+ }
+ mVsyncEnabled = true;
+
+ mVsyncThread->message_loop()->PostTask(
+ NewRunnableMethod(this, &SoftwareDisplay::EnableVsync));
+ return;
+ }
+
+ MOZ_ASSERT(IsInSoftwareVsyncThread());
+ NotifyVsync(mozilla::TimeStamp::Now());
+}
+
+void
+SoftwareDisplay::DisableVsync()
+{
+ MOZ_ASSERT(mVsyncThread->IsRunning());
+ if (NS_IsMainThread()) {
+ if (!mVsyncEnabled) {
+ return;
+ }
+ mVsyncEnabled = false;
+
+ mVsyncThread->message_loop()->PostTask(
+ NewRunnableMethod(this, &SoftwareDisplay::DisableVsync));
+ return;
+ }
+
+ MOZ_ASSERT(IsInSoftwareVsyncThread());
+ if (mCurrentVsyncTask) {
+ mCurrentVsyncTask->Cancel();
+ mCurrentVsyncTask = nullptr;
+ }
+}
+
+bool
+SoftwareDisplay::IsVsyncEnabled()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return mVsyncEnabled;
+}
+
+bool
+SoftwareDisplay::IsInSoftwareVsyncThread()
+{
+ return mVsyncThread->thread_id() == PlatformThread::CurrentId();
+}
+
+void
+SoftwareDisplay::NotifyVsync(mozilla::TimeStamp aVsyncTimestamp)
+{
+ MOZ_ASSERT(IsInSoftwareVsyncThread());
+
+ mozilla::TimeStamp displayVsyncTime = aVsyncTimestamp;
+ mozilla::TimeStamp now = mozilla::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;
+ }
+
+ Display::NotifyVsync(displayVsyncTime);
+
+ // Prevent skew by still scheduling based on the original
+ // vsync timestamp
+ ScheduleNextVsync(aVsyncTimestamp);
+}
+
+mozilla::TimeDuration
+SoftwareDisplay::GetVsyncRate()
+{
+ return mVsyncRate;
+}
+
+void
+SoftwareDisplay::ScheduleNextVsync(mozilla::TimeStamp aVsyncTimestamp)
+{
+ MOZ_ASSERT(IsInSoftwareVsyncThread());
+ mozilla::TimeStamp nextVsync = aVsyncTimestamp + mVsyncRate;
+ mozilla::TimeDuration delay = nextVsync - mozilla::TimeStamp::Now();
+ if (delay.ToMilliseconds() < 0) {
+ delay = mozilla::TimeDuration::FromMilliseconds(0);
+ nextVsync = mozilla::TimeStamp::Now();
+ }
+
+ mCurrentVsyncTask =
+ NewCancelableRunnableMethod<mozilla::TimeStamp>(this,
+ &SoftwareDisplay::NotifyVsync,
+ nextVsync);
+
+ RefPtr<Runnable> addrefedTask = mCurrentVsyncTask;
+ mVsyncThread->message_loop()->PostDelayedTask(
+ addrefedTask.forget(),
+ delay.ToMilliseconds());
+}
+
+void
+SoftwareDisplay::Shutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ DisableVsync();
+ mVsyncThread->Stop();
+ delete mVsyncThread;
+}
diff --git a/gfx/thebes/SoftwareVsyncSource.h b/gfx/thebes/SoftwareVsyncSource.h
new file mode 100644
index 000000000..c86b2568a
--- /dev/null
+++ b/gfx/thebes/SoftwareVsyncSource.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/Monitor.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "base/thread.h"
+#include "nsISupportsImpl.h"
+#include "VsyncSource.h"
+
+class SoftwareDisplay final : public mozilla::gfx::VsyncSource::Display
+{
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SoftwareDisplay)
+
+public:
+ SoftwareDisplay();
+ virtual void EnableVsync() override;
+ virtual void DisableVsync() override;
+ virtual bool IsVsyncEnabled() override;
+ bool IsInSoftwareVsyncThread();
+ virtual void NotifyVsync(mozilla::TimeStamp aVsyncTimestamp) override;
+ virtual mozilla::TimeDuration GetVsyncRate() override;
+ void ScheduleNextVsync(mozilla::TimeStamp aVsyncTimestamp);
+ void Shutdown() override;
+
+protected:
+ ~SoftwareDisplay();
+
+private:
+ mozilla::TimeDuration mVsyncRate;
+ // Use a chromium thread because nsITimers* fire on the main thread
+ base::Thread* mVsyncThread;
+ RefPtr<mozilla::CancelableRunnable> mCurrentVsyncTask; // only access on vsync thread
+ bool mVsyncEnabled; // Only access on main thread
+}; // SoftwareDisplay
+
+// 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 mozilla::gfx::VsyncSource
+{
+public:
+ SoftwareVsyncSource();
+ ~SoftwareVsyncSource();
+
+ virtual Display& GetGlobalDisplay() override
+ {
+ MOZ_ASSERT(mGlobalDisplay != nullptr);
+ return *mGlobalDisplay;
+ }
+
+private:
+ RefPtr<SoftwareDisplay> mGlobalDisplay;
+};
+
+#endif /* GFX_SOFTWARE_VSYNC_SOURCE_H */
diff --git a/gfx/thebes/VsyncSource.cpp b/gfx/thebes/VsyncSource.cpp
new file mode 100644
index 000000000..cb69db560
--- /dev/null
+++ b/gfx/thebes/VsyncSource.cpp
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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"
+
+namespace mozilla {
+namespace gfx {
+
+void
+VsyncSource::AddCompositorVsyncDispatcher(CompositorVsyncDispatcher* aCompositorVsyncDispatcher)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+ // Just use the global display until we have enough information to get the
+ // corresponding display for compositor.
+ GetGlobalDisplay().AddCompositorVsyncDispatcher(aCompositorVsyncDispatcher);
+}
+
+void
+VsyncSource::RemoveCompositorVsyncDispatcher(CompositorVsyncDispatcher* aCompositorVsyncDispatcher)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+ // See also AddCompositorVsyncDispatcher().
+ GetGlobalDisplay().RemoveCompositorVsyncDispatcher(aCompositorVsyncDispatcher);
+}
+
+RefPtr<RefreshTimerVsyncDispatcher>
+VsyncSource::GetRefreshTimerVsyncDispatcher()
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ // See also AddCompositorVsyncDispatcher().
+ return GetGlobalDisplay().GetRefreshTimerVsyncDispatcher();
+}
+
+VsyncSource::Display::Display()
+ : mDispatcherLock("display dispatcher lock")
+ , mRefreshTimerNeedsVsync(false)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mRefreshTimerVsyncDispatcher = new RefreshTimerVsyncDispatcher();
+}
+
+VsyncSource::Display::~Display()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MutexAutoLock lock(mDispatcherLock);
+ mRefreshTimerVsyncDispatcher = nullptr;
+ mCompositorVsyncDispatchers.Clear();
+}
+
+void
+VsyncSource::Display::NotifyVsync(TimeStamp aVsyncTimestamp)
+{
+ // Called on the vsync thread
+ MutexAutoLock lock(mDispatcherLock);
+
+ for (size_t i = 0; i < mCompositorVsyncDispatchers.Length(); i++) {
+ mCompositorVsyncDispatchers[i]->NotifyVsync(aVsyncTimestamp);
+ }
+
+ mRefreshTimerVsyncDispatcher->NotifyVsync(aVsyncTimestamp);
+}
+
+TimeDuration
+VsyncSource::Display::GetVsyncRate()
+{
+ // If hardware queries fail / are unsupported, we have to just guess.
+ return TimeDuration::FromMilliseconds(1000.0 / 60.0);
+}
+
+void
+VsyncSource::Display::AddCompositorVsyncDispatcher(CompositorVsyncDispatcher* aCompositorVsyncDispatcher)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aCompositorVsyncDispatcher);
+ { // scope lock
+ MutexAutoLock lock(mDispatcherLock);
+ if (!mCompositorVsyncDispatchers.Contains(aCompositorVsyncDispatcher)) {
+ mCompositorVsyncDispatchers.AppendElement(aCompositorVsyncDispatcher);
+ }
+ }
+ UpdateVsyncStatus();
+}
+
+void
+VsyncSource::Display::RemoveCompositorVsyncDispatcher(CompositorVsyncDispatcher* aCompositorVsyncDispatcher)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aCompositorVsyncDispatcher);
+ { // Scope lock
+ MutexAutoLock lock(mDispatcherLock);
+ if (mCompositorVsyncDispatchers.Contains(aCompositorVsyncDispatcher)) {
+ mCompositorVsyncDispatchers.RemoveElement(aCompositorVsyncDispatcher);
+ }
+ }
+ UpdateVsyncStatus();
+}
+
+void
+VsyncSource::Display::NotifyRefreshTimerVsyncStatus(bool aEnable)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mRefreshTimerNeedsVsync = aEnable;
+ UpdateVsyncStatus();
+}
+
+void
+VsyncSource::Display::UpdateVsyncStatus()
+{
+ 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
+ MutexAutoLock lock(mDispatcherLock);
+ enableVsync = !mCompositorVsyncDispatchers.IsEmpty() || mRefreshTimerNeedsVsync;
+ }
+
+ if (enableVsync) {
+ EnableVsync();
+ } else {
+ DisableVsync();
+ }
+
+ if (IsVsyncEnabled() != enableVsync) {
+ NS_WARNING("Vsync status did not change.");
+ }
+}
+
+RefPtr<RefreshTimerVsyncDispatcher>
+VsyncSource::Display::GetRefreshTimerVsyncDispatcher()
+{
+ return mRefreshTimerVsyncDispatcher;
+}
+
+void
+VsyncSource::Shutdown()
+{
+ GetGlobalDisplay().Shutdown();
+}
+
+} //namespace gfx
+} //namespace mozilla
diff --git a/gfx/thebes/VsyncSource.h b/gfx/thebes/VsyncSource.h
new file mode 100644
index 000000000..317f31134
--- /dev/null
+++ b/gfx/thebes/VsyncSource.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/RefPtr.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+class RefreshTimerVsyncDispatcher;
+class CompositorVsyncDispatcher;
+
+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::RefreshTimerVsyncDispatcher RefreshTimerVsyncDispatcher;
+ typedef mozilla::CompositorVsyncDispatcher CompositorVsyncDispatcher;
+
+public:
+ // Controls vsync unique to each display and unique on each platform
+ class Display {
+ public:
+ Display();
+ virtual ~Display();
+
+ // Notified when this display's vsync occurs, on the vsync thread
+ // The aVsyncTimestamp should normalize to the Vsync time that just occured
+ // However, different platforms give different vsync notification times.
+ // b2g - The vsync timestamp of the previous frame that was just displayed
+ // OSX - The vsync timestamp of the upcoming frame, in the future
+ // Windows: It's messy, see gfxWindowsPlatform.
+ // Android: TODO
+ // All platforms should normalize to the vsync that just occured.
+ // Large parts of Gecko assume TimeStamps should not be in the future such as animations
+ virtual void NotifyVsync(TimeStamp aVsyncTimestamp);
+
+ RefPtr<RefreshTimerVsyncDispatcher> GetRefreshTimerVsyncDispatcher();
+
+ void AddCompositorVsyncDispatcher(CompositorVsyncDispatcher* aCompositorVsyncDispatcher);
+ void RemoveCompositorVsyncDispatcher(CompositorVsyncDispatcher* aCompositorVsyncDispatcher);
+ void NotifyRefreshTimerVsyncStatus(bool aEnable);
+ 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;
+
+ private:
+ void UpdateVsyncStatus();
+
+ Mutex mDispatcherLock;
+ bool mRefreshTimerNeedsVsync;
+ nsTArray<RefPtr<CompositorVsyncDispatcher>> mCompositorVsyncDispatchers;
+ RefPtr<RefreshTimerVsyncDispatcher> mRefreshTimerVsyncDispatcher;
+ };
+
+ void AddCompositorVsyncDispatcher(CompositorVsyncDispatcher* aCompositorVsyncDispatcher);
+ void RemoveCompositorVsyncDispatcher(CompositorVsyncDispatcher* aCompositorVsyncDispatcher);
+
+ RefPtr<RefreshTimerVsyncDispatcher> GetRefreshTimerVsyncDispatcher();
+ virtual Display& GetGlobalDisplay() = 0; // Works across all displays
+ void Shutdown();
+
+protected:
+ virtual ~VsyncSource() {}
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* GFX_VSYNCSOURCE_H */
diff --git a/gfx/thebes/cairo-xlib-utils.h b/gfx/thebes/cairo-xlib-utils.h
new file mode 100644
index 000000000..f64a84dce
--- /dev/null
+++ b/gfx/thebes/cairo-xlib-utils.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 000000000..76f34d541
--- /dev/null
+++ b/gfx/thebes/d3dkmtQueryStatistics.h
@@ -0,0 +1,168 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 Filler[4];
+ 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 enum _D3DKMTQS_TYPE
+{
+ D3DKMTQS_ADAPTER = 0,
+ D3DKMTQS_PROCESS = 1,
+ D3DKMTQS_SEGMENT = 3,
+ D3DKMTQS_PROCESS_SEGMENT = 4,
+} 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_RESULT;
+
+typedef struct _D3DKMTQS_QUERY_SEGMENT
+{
+ ULONG SegmentId;
+} D3DKMTQS_QUERY_SEGMENT;
+
+typedef struct _D3DKMTQS
+{
+ D3DKMTQS_TYPE Type;
+ LUID AdapterLuid;
+ HANDLE hProcess;
+ D3DKMTQS_RESULT QueryResult;
+
+ union
+ {
+ D3DKMTQS_QUERY_SEGMENT QuerySegment;
+ D3DKMTQS_QUERY_SEGMENT QueryProcessSegment;
+ };
+} 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 000000000..0fa6c65f8
--- /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/genTables.py b/gfx/thebes/genTables.py
new file mode 100644
index 000000000..0e902c52a
--- /dev/null
+++ b/gfx/thebes/genTables.py
@@ -0,0 +1,22 @@
+#!/usr/bin/python
+
+from __future__ import print_function
+import sys
+
+def table_generator(f):
+ return ",\n".join([", ".join(["0x%2.2x" % h for h in [f(i) for i in range(r,r+16)]]) for r in range(0, 65536, 16)])
+
+def generate(output):
+ output.write("const uint8_t gfxUtils::sPremultiplyTable[256*256] = {\n");
+ output.write(table_generator(lambda i: ((i / 256) * (i % 256) + 254) / 255) + "\n")
+ output.write("};\n");
+ output.write("const uint8_t gfxUtils::sUnpremultiplyTable[256*256] = {\n");
+ output.write(table_generator(lambda i: (i % 256) * 255 / ((i / 256) if (i / 256) > 0 else 255) % 256) + "\n")
+ output.write("};\n");
+
+if __name__ == '__main__':
+ if len(sys.argv) != 2:
+ print("Usage: genTables.py <header.h>", file=sys.stderr)
+ sys.exit(1)
+ with open(sys.argv[1], 'w') as f:
+ generate(f)
diff --git a/gfx/thebes/gencjkcisvs.py b/gfx/thebes/gencjkcisvs.py
new file mode 100644
index 000000000..401ebaa97
--- /dev/null
+++ b/gfx/thebes/gencjkcisvs.py
@@ -0,0 +1,77 @@
+# 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 not 'CJK COMPATIBILITY IDEOGRAPH-' 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 not vs 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 000000000..2d3267b6c
--- /dev/null
+++ b/gfx/thebes/gfx2DGlue.h
@@ -0,0 +1,131 @@
+/* -*- 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 "gfxPlatform.h"
+#include "gfxRect.h"
+#include "gfxMatrix.h"
+#include "gfxContext.h"
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/gfx/2D.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 000000000..31f185596
--- /dev/null
+++ b/gfx/thebes/gfxASurface.cpp
@@ -0,0 +1,620 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "nsMemory.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 "imgIEncoder.h"
+#include "nsComponentManagerUtils.h"
+#include "nsISupportsUtils.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsIClipboardHelper.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);
+ } else {
+ // 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;
+ } else {
+ 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;
+#ifdef MOZ_TREE_CAIRO
+ if (cairo_surface_get_content(surface) != CAIRO_CONTENT_COLOR) {
+ cairo_surface_set_subpixel_antialiasing(surface, CAIRO_SUBPIXEL_ANTIALIASING_DISABLED);
+ }
+#endif
+ }
+}
+
+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, &pt.y);
+ 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<gfxASurface>
+gfxASurface::CreateSimilarSurface(gfxContentType aContent,
+ const IntSize& aSize)
+{
+ if (!mSurface || !mSurfaceValid) {
+ return nullptr;
+ }
+
+ cairo_surface_t *surface =
+ cairo_surface_create_similar(mSurface, cairo_content_t(int(aContent)),
+ aSize.width, aSize.height);
+ if (cairo_surface_status(surface)) {
+ cairo_surface_destroy(surface);
+ return nullptr;
+ }
+
+ RefPtr<gfxASurface> result = Wrap(surface, aSize);
+ cairo_surface_destroy(surface);
+ return result.forget();
+}
+
+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::GetPlatform()->CreateDrawTargetForSurface(imgSurface, IntSize(size.width, size.height));
+ RefPtr<SourceSurface> source = gfxPlatform::GetPlatform()->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);
+}
+
+/* static */
+int32_t
+gfxASurface::FormatStrideForWidth(gfxImageFormat format, int32_t width)
+{
+ cairo_format_t cformat = GfxFormatToCairoFormat(format);
+ return cairo_format_stride_for_width(cformat, (int)width);
+}
+
+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() {}
+
+ // 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;
+ };
+
+ NS_DECL_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(
+ EmptyCString(), 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:
+ NS_NOTREACHED("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 000000000..ca7535e56
--- /dev/null
+++ b/gfx/thebes/gfxASurface.h
@@ -0,0 +1,204 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxTypes.h"
+#include "nscore.h"
+#include "nsSize.h"
+#include "mozilla/gfx/Rect.h"
+
+#ifdef MOZILLA_INTERNAL_API
+#include "nsStringFwd.h"
+#else
+#include "nsStringAPI.h"
+#endif
+
+class gfxImageSurface;
+struct gfxRect;
+struct gfxPoint;
+
+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();
+
+ /**
+ * Create an offscreen surface that can be efficiently copied into
+ * this surface (at least if tiling is not involved).
+ * Returns null on error.
+ */
+ virtual already_AddRefed<gfxASurface> CreateSimilarSurface(gfxContentType aType,
+ const mozilla::gfx::IntSize& aSize);
+
+ /**
+ * 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();
+
+ /* Provide a stride value that will respect all alignment requirements of
+ * the accelerated image-rendering code.
+ */
+ static int32_t FormatStrideForWidth(gfxImageFormat format, int32_t width);
+
+ 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() { }
+ virtual 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 000000000..810fbeffa
--- /dev/null
+++ b/gfx/thebes/gfxAlphaRecovery.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 000000000..9fed1681e
--- /dev/null
+++ b/gfx/thebes/gfxAlphaRecovery.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 000000000..2c8987cbe
--- /dev/null
+++ b/gfx/thebes/gfxAlphaRecoverySSE2.cpp
@@ -0,0 +1,235 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 <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 000000000..f587737b5
--- /dev/null
+++ b/gfx/thebes/gfxAndroidPlatform.cpp
@@ -0,0 +1,338 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 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/CountingAllocatorBase.h"
+#include "mozilla/Preferences.h"
+
+#include "gfx2DGlue.h"
+#include "gfxFT2FontList.h"
+#include "gfxImageSurface.h"
+#include "gfxTextRun.h"
+#include "mozilla/dom/ContentChild.h"
+#include "nsXULAppAPI.h"
+#include "nsIScreen.h"
+#include "nsIScreenManager.h"
+#include "nsILocaleService.h"
+#include "nsServiceManagerUtils.h"
+#include "gfxPrefs.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;
+
+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)
+
+template<> Atomic<size_t> CountingAllocatorBase<FreetypeReporter>::sAmount(0);
+
+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);
+
+ RegisterStrongMemoryReporter(new FreetypeReporter());
+
+ mOffscreenFormat = GetScreenDepth() == 16
+ ? SurfaceFormat::R5G6B5_UINT16
+ : SurfaceFormat::X8R8G8B8_UINT32;
+
+ if (gfxPrefs::AndroidRGB16Force()) {
+ mOffscreenFormat = SurfaceFormat::R5G6B5_UINT16;
+ }
+}
+
+gfxAndroidPlatform::~gfxAndroidPlatform()
+{
+ FT_Done_Library(gPlatformFTLibrary);
+ gPlatformFTLibrary = nullptr;
+}
+
+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;
+
+ do { // to allow 'break' to abandon this block if a call fails
+ nsresult rv;
+ nsCOMPtr<nsILocaleService> ls =
+ do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ nsCOMPtr<nsILocale> appLocale;
+ rv = ls->GetApplicationLocale(getter_AddRefs(appLocale));
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ nsString localeStr;
+ rv = appLocale->
+ GetCategory(NS_LITERAL_STRING(NSILOCALE_MESSAGE), localeStr);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ const nsAString& lang = nsDependentSubstring(localeStr, 0, 2);
+ if (lang.EqualsLiteral("ja")) {
+ sIsJapanese = true;
+ }
+ } while (false);
+ }
+
+ return sIsJapanese;
+}
+
+void
+gfxAndroidPlatform::GetCommonFallbackFonts(uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript,
+ 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 (aNextCh == 0xfe0fu) {
+ // if char is followed by VS16, try for a color emoji glyph
+ aFontList.AppendElement(kNotoColorEmoji);
+ }
+
+ if (!IS_IN_BMP(aCh)) {
+ uint32_t p = aCh >> 16;
+ if (p == 1) { // try color emoji font, unless VS15 (text style) present
+ if (aNextCh != 0xfe0fu && aNextCh != 0xfe0eu) {
+ aFontList.AppendElement(kNotoColorEmoji);
+ }
+ }
+ } else {
+ // 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("Droid Sans Hebrew");
+ aFontList.AppendElement("Droid Sans Armenian");
+ break;
+ case 0x06:
+ aFontList.AppendElement("Droid Sans Arabic");
+ break;
+ case 0x09:
+ aFontList.AppendElement("Noto Sans Devanagari");
+ aFontList.AppendElement("Droid Sans Devanagari");
+ break;
+ case 0x0b:
+ aFontList.AppendElement("Noto Sans Tamil");
+ aFontList.AppendElement("Droid Sans Tamil");
+ break;
+ case 0x0e:
+ aFontList.AppendElement("Noto Sans Thai");
+ aFontList.AppendElement("Droid Sans Thai");
+ break;
+ case 0x10: case 0x2d:
+ aFontList.AppendElement("Droid Sans Georgian");
+ break;
+ case 0x12: case 0x13:
+ aFontList.AppendElement("Droid Sans Ethiopic");
+ 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");
+}
+
+void
+gfxAndroidPlatform::GetSystemFontList(InfallibleTArray<FontListEntry>* retValue)
+{
+ gfxFT2FontList::PlatformFontList()->GetSystemFontList(retValue);
+}
+
+gfxPlatformFontList*
+gfxAndroidPlatform::CreatePlatformFontList()
+{
+ gfxPlatformFontList* list = new gfxFT2FontList();
+ if (NS_SUCCEEDED(list->InitFontList())) {
+ return list;
+ }
+ gfxPlatformFontList::Shutdown();
+ return nullptr;
+}
+
+bool
+gfxAndroidPlatform::IsFontFormatSupported(nsIURI *aFontURI, uint32_t aFormatFlags)
+{
+ // check for strange format flags
+ NS_ASSERTION(!(aFormatFlags & gfxUserFontSet::FLAG_FORMAT_NOT_USED),
+ "strange font format hint set");
+
+ // accept supported formats
+ if (aFormatFlags & gfxUserFontSet::FLAG_FORMATS_COMMON) {
+ return true;
+ }
+
+ // reject all other formats, known and unknown
+ if (aFormatFlags != 0) {
+ return false;
+ }
+
+ // no format hint set, need to look at data
+ return true;
+}
+
+gfxFontGroup *
+gfxAndroidPlatform::CreateFontGroup(const FontFamilyList& aFontFamilyList,
+ const gfxFontStyle* aStyle,
+ gfxTextPerfMetrics* aTextPerf,
+ gfxUserFontSet* aUserFontSet,
+ gfxFloat aDevToCssSize)
+{
+ return new gfxFontGroup(aFontFamilyList, aStyle, aTextPerf,
+ aUserFontSet, aDevToCssSize);
+}
+
+FT_Library
+gfxAndroidPlatform::GetFTLibrary()
+{
+ return gPlatformFTLibrary;
+}
+
+already_AddRefed<ScaledFont>
+gfxAndroidPlatform::GetScaledFontForFont(DrawTarget* aTarget, gfxFont *aFont)
+{
+ return GetScaledFontForFontWithCairoSkia(aTarget, aFont);
+}
+
+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.
+ 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.
+
+ NS_NOTREACHED("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 treat it like B2G and use linear zoom only for the web
+ // browser process, not other apps.
+ return true;
+#endif
+
+ NS_NOTREACHED("oops, what platform is this?");
+ return gfxPlatform::RequiresLinearZoom();
+}
+
+already_AddRefed<mozilla::gfx::VsyncSource>
+gfxAndroidPlatform::CreateHardwareVsyncSource()
+{
+ return gfxPlatform::CreateHardwareVsyncSource();
+}
diff --git a/gfx/thebes/gfxAndroidPlatform.h b/gfx/thebes/gfxAndroidPlatform.h
new file mode 100644
index 000000000..30e7c89ba
--- /dev/null
+++ b/gfx/thebes/gfxAndroidPlatform.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxFT2Fonts.h"
+#include "gfxPlatform.h"
+#include "gfxUserFontSet.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+ namespace dom {
+ class FontListEntry;
+ };
+};
+using mozilla::dom::FontListEntry;
+
+typedef struct FT_LibraryRec_ *FT_Library;
+
+class gfxAndroidPlatform : public gfxPlatform {
+public:
+ gfxAndroidPlatform();
+ virtual ~gfxAndroidPlatform();
+
+ static gfxAndroidPlatform *GetPlatform() {
+ return (gfxAndroidPlatform*) gfxPlatform::GetPlatform();
+ }
+
+ virtual already_AddRefed<gfxASurface>
+ CreateOffscreenSurface(const IntSize& aSize,
+ gfxImageFormat aFormat) override;
+
+ virtual gfxImageFormat GetOffscreenFormat() override { return mOffscreenFormat; }
+
+ already_AddRefed<mozilla::gfx::ScaledFont>
+ GetScaledFontForFont(mozilla::gfx::DrawTarget* aTarget, gfxFont *aFont) override;
+
+ // to support IPC font list (sharing between chrome and content)
+ void GetSystemFontList(InfallibleTArray<FontListEntry>* retValue);
+
+ // platform implementations of font functions
+ virtual bool IsFontFormatSupported(nsIURI *aFontURI, uint32_t aFormatFlags) override;
+ virtual gfxPlatformFontList* CreatePlatformFontList() override;
+
+ virtual void GetCommonFallbackFonts(uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript,
+ nsTArray<const char*>& aFontList) override;
+
+ gfxFontGroup*
+ CreateFontGroup(const mozilla::FontFamilyList& aFontFamilyList,
+ const gfxFontStyle *aStyle,
+ gfxTextPerfMetrics* aTextPerf,
+ gfxUserFontSet *aUserFontSet,
+ gfxFloat aDevToCssSize) override;
+
+ virtual bool FontHintingEnabled() override;
+ virtual bool RequiresLinearZoom() override;
+
+ FT_Library GetFTLibrary();
+
+ virtual bool CanRenderContentToDataSurface() const override {
+ return true;
+ }
+
+ virtual already_AddRefed<mozilla::gfx::VsyncSource> CreateHardwareVsyncSource() override;
+
+ virtual bool SupportsApzTouchInput() const override {
+ return true;
+ }
+
+protected:
+ 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 000000000..7b84e9e6b
--- /dev/null
+++ b/gfx/thebes/gfxBaseSharedMemorySurface.cpp
@@ -0,0 +1,11 @@
+// vim:set ts=4 sts=4 sw=4 et cin:
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 000000000..c9e2ab327
--- /dev/null
+++ b/gfx/thebes/gfxBaseSharedMemorySurface.h
@@ -0,0 +1,199 @@
+// vim:set ts=4 sts=4 sw=4 et cin:
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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,
+ SharedMemory::SharedMemoryType aShmType = SharedMemory::TYPE_BASIC)
+ {
+ return Create<ShmemAllocator, false>(aAllocator, aSize, aFormat, aShmType);
+ }
+
+ /**
+ * 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,
+ SharedMemory::SharedMemoryType aShmType = SharedMemory::TYPE_BASIC)
+ {
+ return Create<ShmemAllocator, true>(aAllocator, aSize, aFormat, aShmType);
+ }
+
+ 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,
+ SharedMemory::SharedMemoryType aShmType)
+ {
+ 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, aShmType, &shmem))
+ return nullptr;
+ } else {
+ if (!aAllocator->AllocUnsafeShmem(size, aShmType, &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 000000000..d5878e161
--- /dev/null
+++ b/gfx/thebes/gfxBlur.cpp
@@ -0,0 +1,1088 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/UniquePtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "nsExpirationTracker.h"
+#include "nsClassHashtable.h"
+#include "gfxUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+gfxAlphaBoxBlur::gfxAlphaBoxBlur()
+{
+}
+
+gfxAlphaBoxBlur::~gfxAlphaBoxBlur()
+{
+ mContext = nullptr;
+}
+
+gfxContext*
+gfxAlphaBoxBlur::Init(const gfxRect& aRect,
+ const IntSize& aSpreadRadius,
+ const IntSize& aBlurRadius,
+ const gfxRect* aDirtyRect,
+ const gfxRect* aSkipRect)
+{
+ mozilla::gfx::Rect rect(Float(aRect.x), Float(aRect.y),
+ Float(aRect.width), Float(aRect.height));
+ IntSize spreadRadius(aSpreadRadius.width, aSpreadRadius.height);
+ IntSize blurRadius(aBlurRadius.width, aBlurRadius.height);
+ UniquePtr<Rect> dirtyRect;
+ if (aDirtyRect) {
+ dirtyRect = MakeUnique<Rect>(Float(aDirtyRect->x),
+ Float(aDirtyRect->y),
+ Float(aDirtyRect->width),
+ Float(aDirtyRect->height));
+ }
+ UniquePtr<Rect> skipRect;
+ if (aSkipRect) {
+ skipRect = MakeUnique<Rect>(Float(aSkipRect->x),
+ Float(aSkipRect->y),
+ Float(aSkipRect->width),
+ Float(aSkipRect->height));
+ }
+
+ mBlur = MakeUnique<AlphaBoxBlur>(rect, spreadRadius, blurRadius, dirtyRect.get(), skipRect.get());
+ size_t blurDataSize = mBlur->GetSurfaceAllocationSize();
+ if (blurDataSize == 0)
+ return nullptr;
+
+ IntSize size = mBlur->GetSize();
+
+ // Make an alpha-only surface to draw on. We will play with the data after
+ // everything is drawn to create a blur effect.
+ mData = MakeUniqueFallible<unsigned char[]>(blurDataSize);
+ if (!mData) {
+ return nullptr;
+ }
+ memset(mData.get(), 0, blurDataSize);
+
+ RefPtr<DrawTarget> dt =
+ gfxPlatform::CreateDrawTargetForData(mData.get(), size,
+ mBlur->GetStride(),
+ SurfaceFormat::A8);
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+
+ IntRect irect = mBlur->GetRect();
+ gfxPoint topleft(irect.TopLeft().x, irect.TopLeft().y);
+
+ mContext = gfxContext::CreateOrNull(dt);
+ MOZ_ASSERT(mContext); // already checked for target above
+ mContext->SetMatrix(gfxMatrix::Translation(-topleft));
+
+ return mContext;
+}
+
+void
+DrawBlur(gfxContext* aDestinationCtx,
+ SourceSurface* aBlur,
+ const IntPoint& aTopLeft,
+ const Rect* aDirtyRect)
+{
+ DrawTarget *dest = aDestinationCtx->GetDrawTarget();
+
+ RefPtr<gfxPattern> thebesPat = aDestinationCtx->GetPattern();
+ Pattern* pat = thebesPat->GetPattern(dest, nullptr);
+
+ Matrix oldTransform = dest->GetTransform();
+ Matrix newTransform = oldTransform;
+ newTransform.PreTranslate(aTopLeft.x, aTopLeft.y);
+
+ // Avoid a semi-expensive clip operation if we can, otherwise
+ // clip to the dirty rect
+ if (aDirtyRect) {
+ dest->PushClipRect(*aDirtyRect);
+ }
+
+ dest->SetTransform(newTransform);
+ dest->MaskSurface(*pat, aBlur, Point(0, 0));
+ dest->SetTransform(oldTransform);
+
+ if (aDirtyRect) {
+ dest->PopClip();
+ }
+}
+
+already_AddRefed<SourceSurface>
+gfxAlphaBoxBlur::DoBlur(DrawTarget* aDT, IntPoint* aTopLeft)
+{
+ mBlur->Blur(mData.get());
+
+ *aTopLeft = mBlur->GetRect().TopLeft();
+
+ return aDT->CreateSourceSurfaceFromData(mData.get(),
+ mBlur->GetSize(),
+ mBlur->GetStride(),
+ SurfaceFormat::A8);
+}
+
+void
+gfxAlphaBoxBlur::Paint(gfxContext* aDestinationCtx)
+{
+ if (!mContext)
+ return;
+
+ DrawTarget *dest = aDestinationCtx->GetDrawTarget();
+ if (!dest) {
+ NS_WARNING("Blurring not supported for Thebes contexts!");
+ return;
+ }
+
+ Rect* dirtyRect = mBlur->GetDirtyRect();
+
+ IntPoint topLeft;
+ RefPtr<SourceSurface> mask = DoBlur(dest, &topLeft);
+ if (!mask) {
+ NS_ERROR("Failed to create mask!");
+ return;
+ }
+
+ DrawBlur(aDestinationCtx, mask, topLeft, dirtyRect);
+}
+
+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;
+ Color mShadowColor;
+ BackendType mBackend;
+ RectCornerRadii mCornerRadii;
+ bool mIsInset;
+
+ // Only used for inset blurs
+ bool mHasBorderRadius;
+ IntSize mInnerMinSize;
+
+ BlurCacheKey(IntSize aMinSize, IntSize aBlurRadius,
+ RectCornerRadii* aCornerRadii, const Color& aShadowColor,
+ BackendType aBackendType)
+ : BlurCacheKey(aMinSize, IntSize(0, 0),
+ aBlurRadius, aCornerRadii,
+ aShadowColor, false,
+ 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)
+ , mHasBorderRadius(aOther->mHasBorderRadius)
+ , mInnerMinSize(aOther->mInnerMinSize)
+ { }
+
+ explicit BlurCacheKey(IntSize aOuterMinSize, IntSize aInnerMinSize,
+ IntSize aBlurRadius,
+ const RectCornerRadii* aCornerRadii,
+ const Color& aShadowColor, bool aIsInset,
+ bool aHasBorderRadius, BackendType aBackendType)
+ : mMinSize(aOuterMinSize)
+ , mBlurRadius(aBlurRadius)
+ , mShadowColor(aShadowColor)
+ , mBackend(aBackendType)
+ , mCornerRadii(aCornerRadii ? *aCornerRadii : RectCornerRadii())
+ , mIsInset(aIsInset)
+ , mHasBorderRadius(aHasBorderRadius)
+ , mInnerMinSize(aInnerMinSize)
+ { }
+
+ 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);
+ hash = AddToHash(hash, HashBytes(&aKey->mHasBorderRadius, sizeof(bool)));
+ }
+ 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 (mHasBorderRadius == aKey->mHasBorderRadius) &&
+ (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, IntMargin aExtendDestBy, const BlurCacheKey& aKey)
+ : mBlur(aBlur)
+ , mExtendDest(aExtendDestBy)
+ , mKey(aKey)
+ {}
+
+ BlurCacheData(const BlurCacheData& aOther)
+ : mBlur(aOther.mBlur)
+ , mExtendDest(aOther.mExtendDest)
+ , mKey(aOther.mKey)
+ { }
+
+ nsExpirationState *GetExpirationState() {
+ return &mExpirationState;
+ }
+
+ nsExpirationState mExpirationState;
+ RefPtr<SourceSurface> mBlur;
+ IntMargin mExtendDest;
+ 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)
+ {
+ RemoveObject(aObject);
+ mHashEntries.Remove(aObject->mKey);
+ }
+
+ BlurCacheData* Lookup(const IntSize aMinSize,
+ const IntSize& aBlurRadius,
+ RectCornerRadii* aCornerRadii,
+ const Color& 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 Color& aShadowColor,
+ const bool& aHasBorderRadius,
+ BackendType aBackendType)
+ {
+ bool insetBoxShadow = true;
+ BlurCacheKey key(aOuterMinSize, aInnerMinSize,
+ aBlurRadius, aCornerRadii,
+ aShadowColor, insetBoxShadow,
+ aHasBorderRadius, aBackendType);
+ BlurCacheData* blur = mHashEntries.Get(key);
+ if (blur) {
+ MarkUsed(blur);
+ }
+
+ return blur;
+ }
+
+ // Returns true if we successfully register the blur in the cache, false
+ // otherwise.
+ bool RegisterEntry(BlurCacheData* aValue)
+ {
+ nsresult rv = AddObject(aValue);
+ 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 things considering we are short on memory
+ // anyway, we probably don't want to retain things.
+ return false;
+ }
+ mHashEntries.Put(aValue->mKey, aValue);
+ return true;
+ }
+
+ 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(RectCornerRadii* aCornerRadii,
+ IntSize aBlurRadius,
+ IntMargin& aSlice,
+ const IntSize& aRectSize)
+{
+ float cornerWidth = 0;
+ float cornerHeight = 0;
+ if (aCornerRadii) {
+ RectCornerRadii corners = *aCornerRadii;
+ for (size_t i = 0; i < 4; i++) {
+ cornerWidth = std::max(cornerWidth, corners[i].width);
+ cornerHeight = std::max(cornerHeight, corners[i].height);
+ }
+ }
+
+ aSlice = IntMargin(ceil(cornerHeight) + aBlurRadius.height,
+ ceil(cornerWidth) + aBlurRadius.width,
+ ceil(cornerHeight) + aBlurRadius.height,
+ ceil(cornerWidth) + aBlurRadius.width);
+
+ IntSize minSize(aSlice.LeftRight() + 1,
+ aSlice.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;
+ aSlice.left = 0;
+ aSlice.right = 0;
+ }
+ if (aRectSize.height < minSize.height) {
+ minSize.height = aRectSize.height;
+ aSlice.top = 0;
+ aSlice.bottom = 0;
+ }
+
+ MOZ_ASSERT(aSlice.LeftRight() <= minSize.width);
+ MOZ_ASSERT(aSlice.TopBottom() <= minSize.height);
+ return minSize;
+}
+
+void
+CacheBlur(DrawTarget& aDT,
+ const IntSize& aMinSize,
+ const IntSize& aBlurRadius,
+ RectCornerRadii* aCornerRadii,
+ const Color& aShadowColor,
+ IntMargin aExtendDest,
+ SourceSurface* aBoxShadow)
+{
+ BlurCacheKey key(aMinSize, aBlurRadius, aCornerRadii, aShadowColor, aDT.GetBackendType());
+ BlurCacheData* data = new BlurCacheData(aBoxShadow, aExtendDest, key);
+ if (!gBlurCache->RegisterEntry(data)) {
+ delete data;
+ }
+}
+
+// Blurs a small surface and creates the mask.
+static already_AddRefed<SourceSurface>
+CreateBlurMask(const IntSize& aMinSize,
+ RectCornerRadii* aCornerRadii,
+ IntSize aBlurRadius,
+ IntMargin& aExtendDestBy,
+ IntMargin& aSliceBorder,
+ DrawTarget& aDestDrawTarget)
+{
+ gfxAlphaBoxBlur blur;
+ IntRect minRect(IntPoint(), aMinSize);
+
+ gfxContext* blurCtx = blur.Init(ThebesRect(Rect(minRect)), IntSize(),
+ aBlurRadius, nullptr, nullptr);
+
+ if (!blurCtx) {
+ return nullptr;
+ }
+
+ DrawTarget* blurDT = blurCtx->GetDrawTarget();
+ ColorPattern black(Color(0.f, 0.f, 0.f, 1.f));
+
+ if (aCornerRadii) {
+ RefPtr<Path> roundedRect =
+ MakePathForRoundedRect(*blurDT, Rect(minRect), *aCornerRadii);
+ blurDT->Fill(roundedRect, black);
+ } else {
+ blurDT->FillRect(Rect(minRect), black);
+ }
+
+ IntPoint topLeft;
+ RefPtr<SourceSurface> result = blur.DoBlur(&aDestDrawTarget, &topLeft);
+ if (!result) {
+ return nullptr;
+ }
+
+ IntRect expandedMinRect(topLeft, result->GetSize());
+ aExtendDestBy = expandedMinRect - minRect;
+ aSliceBorder += aExtendDestBy;
+
+ MOZ_ASSERT(aSliceBorder.LeftRight() <= expandedMinRect.width);
+ MOZ_ASSERT(aSliceBorder.TopBottom() <= expandedMinRect.height);
+
+ return result.forget();
+}
+
+static already_AddRefed<SourceSurface>
+CreateBoxShadow(DrawTarget& aDestDT, SourceSurface* aBlurMask, const Color& aShadowColor)
+{
+ IntSize blurredSize = aBlurMask->GetSize();
+ RefPtr<DrawTarget> boxShadowDT =
+ Factory::CreateDrawTarget(aDestDT.GetBackendType(), blurredSize, SurfaceFormat::B8G8R8A8);
+
+ if (!boxShadowDT) {
+ return nullptr;
+ }
+
+ ColorPattern shadowColor(ToDeviceColor(aShadowColor));
+ boxShadowDT->MaskSurface(shadowColor, aBlurMask, Point(0, 0));
+ return boxShadowDT->Snapshot();
+}
+
+static already_AddRefed<SourceSurface>
+GetBlur(gfxContext* aDestinationCtx,
+ const IntSize& aRectSize,
+ const IntSize& aBlurRadius,
+ RectCornerRadii* aCornerRadii,
+ const Color& aShadowColor,
+ IntMargin& aExtendDestBy,
+ IntMargin& aSlice)
+{
+ if (!gBlurCache) {
+ gBlurCache = new BlurCache();
+ }
+
+ IntSize minSize =
+ ComputeMinSizeForShadowShape(aCornerRadii, aBlurRadius, aSlice, 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.
+ Matrix destMatrix = ToMatrix(aDestinationCtx->CurrentMatrix());
+ bool useDestRect = !destMatrix.IsRectilinear() || destMatrix.HasNonIntegerTranslation();
+ if (useDestRect) {
+ minSize = aRectSize;
+ }
+
+ DrawTarget& destDT = *aDestinationCtx->GetDrawTarget();
+
+ BlurCacheData* cached = gBlurCache->Lookup(minSize, aBlurRadius,
+ aCornerRadii, aShadowColor,
+ destDT.GetBackendType());
+ if (cached && !useDestRect) {
+ // See CreateBlurMask() for these values
+ aExtendDestBy = cached->mExtendDest;
+ aSlice = aSlice + aExtendDestBy;
+ RefPtr<SourceSurface> blur = cached->mBlur;
+ return blur.forget();
+ }
+
+ RefPtr<SourceSurface> blurMask =
+ CreateBlurMask(minSize, aCornerRadii, aBlurRadius, aExtendDestBy, aSlice,
+ destDT);
+
+ if (!blurMask) {
+ return nullptr;
+ }
+
+ RefPtr<SourceSurface> boxShadow = CreateBoxShadow(destDT, blurMask, aShadowColor);
+ if (!boxShadow) {
+ return nullptr;
+ }
+
+ if (useDestRect) {
+ // Since we're just going to paint the actual rect to the destination
+ aSlice.SizeTo(0, 0, 0, 0);
+ } else {
+ CacheBlur(destDT, minSize, aBlurRadius, aCornerRadii, aShadowColor,
+ aExtendDestBy, 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 void
+RepeatOrStretchSurface(DrawTarget& aDT, SourceSurface* aSurface,
+ const Rect& aDest, const Rect& aSrc, Rect& aSkipRect)
+{
+ if (aSkipRect.Contains(aDest)) {
+ return;
+ }
+
+ if ((!aDT.GetTransform().IsRectilinear() &&
+ aDT.GetBackendType() != BackendType::CAIRO) ||
+ (aDT.GetBackendType() == BackendType::DIRECT2D1_1)) {
+ // 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.
+ // Calling FillRect on a D2D backend with a repeating pattern is much slower
+ // than DrawSurface, so special case the D2D backend here.
+ 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, Rect& aSkipRect)
+{
+ if (aSkipRect.Contains(aDest)) {
+ return;
+ }
+
+ aDT.DrawSurface(aSurface, aDest, aSrc);
+}
+
+static void
+DrawBoxShadows(DrawTarget& aDestDrawTarget, SourceSurface* aSourceBlur,
+ Rect aDstOuter, Rect aDstInner, Rect aSrcOuter, Rect aSrcInner,
+ Rect aSkipRect)
+{
+ // 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);
+}
+
+/***
+ * 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,
+ RectCornerRadii* aCornerRadii,
+ const gfxPoint& aBlurStdDev,
+ const Color& aShadowColor,
+ const gfxRect& aDirtyRect,
+ const gfxRect& aSkipRect)
+{
+ IntSize blurRadius = CalculateBlurRadius(aBlurStdDev);
+
+ IntRect rect = RoundedToInt(ToRect(aRect));
+ IntMargin extendDestBy;
+ IntMargin slice;
+
+ RefPtr<SourceSurface> boxShadow = GetBlur(aDestinationCtx,
+ rect.Size(), blurRadius,
+ aCornerRadii, aShadowColor,
+ extendDestBy, slice);
+ 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(), Size(boxShadow->GetSize()));
+ Rect srcInner = srcOuter;
+ srcInner.Deflate(Margin(slice));
+
+ rect.Inflate(extendDestBy);
+ Rect dstOuter(rect);
+ Rect dstInner(rect);
+ dstInner.Deflate(Margin(slice));
+
+ Rect skipRect = ToRect(aSkipRect);
+
+ if (srcInner.IsEqualInterior(srcOuter)) {
+ MOZ_ASSERT(dstInner.IsEqualInterior(dstOuter));
+ // The target rect is smaller than the minimal size so just draw the surface
+ destDrawTarget.DrawSurface(boxShadow, dstInner, srcInner);
+ } else {
+ DrawBoxShadows(destDrawTarget, boxShadow, dstOuter, dstInner,
+ srcOuter, srcInner, skipRect);
+
+ // Middle part
+ RepeatOrStretchSurface(destDrawTarget, boxShadow,
+ RectWithEdgesTRBL(dstInner.Y(), dstInner.XMost(),
+ dstInner.YMost(), dstInner.X()),
+ RectWithEdgesTRBL(srcInner.Y(), srcInner.XMost(),
+ srcInner.YMost(), srcInner.X()),
+ skipRect);
+ }
+
+ // 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 of 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".
+ // On OS X there is an additional problem with turning off AA: CoreGraphics
+ // will not just fill the pixels that have their pixel center inside the
+ // filled shape. Instead, it will fill all the pixels which are partially
+ // covered by the shape. So for pixels on the edge between two adjacent parts,
+ // all those pixels will be painted to by both parts, which looks very bad.
+
+ destDrawTarget.PopClip();
+}
+
+static already_AddRefed<Path>
+GetBoxShadowInsetPath(DrawTarget* aDrawTarget,
+ const Rect aOuterRect, const Rect aInnerRect,
+ const bool aHasBorderRadius, 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 (aHasBorderRadius) {
+ 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 Color& aShadowColor,
+ const bool aHasBorderRadius,
+ const RectCornerRadii& aInnerClipRadii)
+{
+ // 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, aHasBorderRadius,
+ aInnerClipRadii);
+
+ aDestinationCtx->SetPath(shadowPath);
+ aDestinationCtx->Fill();
+}
+
+static void
+CacheInsetBlur(const IntSize aMinOuterSize,
+ const IntSize aMinInnerSize,
+ const IntSize& aBlurRadius,
+ const RectCornerRadii* aCornerRadii,
+ const Color& aShadowColor,
+ const bool& aHasBorderRadius,
+ BackendType aBackendType,
+ SourceSurface* aBoxShadow)
+{
+ bool isInsetBlur = true;
+ BlurCacheKey key(aMinOuterSize, aMinInnerSize,
+ aBlurRadius, aCornerRadii,
+ aShadowColor, isInsetBlur,
+ aHasBorderRadius, aBackendType);
+ IntMargin extendDestBy(0, 0, 0, 0);
+ BlurCacheData* data = new BlurCacheData(aBoxShadow, extendDestBy, key);
+ if (!gBlurCache->RegisterEntry(data)) {
+ delete data;
+ }
+}
+
+already_AddRefed<mozilla::gfx::SourceSurface>
+gfxAlphaBoxBlur::GetInsetBlur(const mozilla::gfx::Rect aOuterRect,
+ const mozilla::gfx::Rect aWhitespaceRect,
+ const bool aIsDestRect,
+ const mozilla::gfx::Color& aShadowColor,
+ const mozilla::gfx::IntSize& aBlurRadius,
+ const bool aHasBorderRadius,
+ const RectCornerRadii& aInnerClipRadii,
+ DrawTarget* aDestDrawTarget)
+{
+ if (!gBlurCache) {
+ gBlurCache = new BlurCache();
+ }
+
+ IntSize outerSize((int)aOuterRect.width, (int)aOuterRect.height);
+ IntSize whitespaceSize((int)aWhitespaceRect.width, (int)aWhitespaceRect.height);
+ BlurCacheData* cached =
+ gBlurCache->LookupInsetBoxShadow(outerSize, whitespaceSize,
+ aBlurRadius, &aInnerClipRadii,
+ aShadowColor, aHasBorderRadius,
+ aDestDrawTarget->GetBackendType());
+
+ if (cached && !aIsDestRect) {
+ // 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;
+ IntSize zeroSpread(0, 0);
+ gfxContext* minGfxContext = Init(ThebesRect(blurRect),
+ zeroSpread, aBlurRadius,
+ nullptr, nullptr);
+ if (!minGfxContext) {
+ return nullptr;
+ }
+
+ // This is really annoying. When we create the AlphaBoxBlur, the gfxContext
+ // 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) {
+ minGfxContext->SetMatrix(gfxMatrix());
+ }
+
+ DrawTarget* minDrawTarget = minGfxContext->GetDrawTarget();
+
+ // Fill in the path between the inside white space / outer rects
+ // NOT the inner frame
+ RefPtr<Path> maskPath =
+ GetBoxShadowInsetPath(minDrawTarget, aOuterRect,
+ aWhitespaceRect, aHasBorderRadius,
+ aInnerClipRadii);
+
+ Color black(0.f, 0.f, 0.f, 1.f);
+ minGfxContext->SetColor(black);
+ minGfxContext->SetPath(maskPath);
+ minGfxContext->Fill();
+
+ // Create the A8 mask
+ IntPoint topLeft;
+ RefPtr<SourceSurface> minMask = DoBlur(minDrawTarget, &topLeft);
+ if (!minMask) {
+ return nullptr;
+ }
+
+ // Fill in with the color we actually wanted
+ RefPtr<SourceSurface> minInsetBlur = CreateBoxShadow(*aDestDrawTarget, minMask, aShadowColor);
+ if (!minInsetBlur) {
+ return nullptr;
+ }
+
+ if (!aIsDestRect) {
+ CacheInsetBlur(outerSize, whitespaceSize,
+ aBlurRadius, &aInnerClipRadii,
+ aShadowColor, aHasBorderRadius,
+ 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 bool aHasBorderRadius,
+ const RectCornerRadii& aInnerClipRadii,
+ const IntSize aBlurRadius,
+ Margin& aOutBlurMargin,
+ Margin& aOutInnerMargin)
+{
+ float cornerWidth = 0;
+ float cornerHeight = 0;
+ if (aHasBorderRadius) {
+ for (size_t i = 0; i < 4; i++) {
+ cornerWidth = std::max(cornerWidth, aInnerClipRadii[i].width);
+ cornerHeight = std::max(cornerHeight, aInnerClipRadii[i].height);
+ }
+ }
+
+ // Only the inside whitespace size cares about the border radius size.
+ // Outer sizes only care about blur.
+ int width = cornerWidth + aBlurRadius.width;
+ int height = cornerHeight + aBlurRadius.height;
+
+ aOutInnerMargin.SizeTo(height, width, height, 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 IntSize aSpreadRadius,
+ const Color& aShadowColor,
+ bool aHasBorderRadius,
+ const RectCornerRadii& aInnerClipRadii,
+ const Rect aSkipRect,
+ const Point aShadowOffset)
+{
+ if ((aBlurRadius.width == 0 && aBlurRadius.height == 0)
+ || aShadowClipRect.IsEmpty()) {
+ FillDestinationPath(aDestinationCtx, aDestinationRect, aShadowClipRect,
+ aShadowColor, aHasBorderRadius, aInnerClipRadii);
+ return;
+ }
+
+ DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
+
+ Margin innerMargin;
+ Margin blurMargin;
+ GetBlurMargins(aHasBorderRadius, aInnerClipRadii, aBlurRadius,
+ blurMargin, innerMargin);
+
+ Rect whitespaceRect;
+ Rect outerRect;
+ bool useDestRect = GetInsetBoxShadowRects(blurMargin, innerMargin, aShadowClipRect,
+ aDestinationRect, whitespaceRect, outerRect);
+
+ RefPtr<SourceSurface> minBlur = GetInsetBlur(outerRect, whitespaceRect, useDestRect, aShadowColor,
+ aBlurRadius, aHasBorderRadius, aInnerClipRadii,
+ destDrawTarget);
+ if (!minBlur) {
+ return;
+ }
+
+ if (useDestRect) {
+ IntSize blurSize = minBlur->GetSize();
+ Rect srcBlur(0, 0, blurSize.width, blurSize.height);
+ Rect destBlur = aDestinationRect;
+
+ // The blur itself expands the rect by the blur margin, so we
+ // have to mimic that here.
+ destBlur.Inflate(blurMargin);
+ MOZ_ASSERT(srcBlur.Size() == 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, false, RectCornerRadii());
+
+ // 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);
+
+ DrawBoxShadows(*destDrawTarget, minBlur,
+ destRect, destInnerRect,
+ srcOuter, srcInner,
+ aSkipRect);
+ }
+}
diff --git a/gfx/thebes/gfxBlur.h b/gfx/thebes/gfxBlur.h
new file mode 100644
index 000000000..9cc5f3716
--- /dev/null
+++ b/gfx/thebes/gfxBlur.h
@@ -0,0 +1,194 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "nsSize.h"
+#include "gfxPoint.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+
+class gfxContext;
+struct gfxRect;
+
+namespace mozilla {
+ namespace gfx {
+ class AlphaBoxBlur;
+ struct Color;
+ 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
+{
+ typedef mozilla::gfx::Color Color;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
+
+public:
+ gfxAlphaBoxBlur();
+
+ ~gfxAlphaBoxBlur();
+
+ /**
+ * Constructs a box blur and initializes the temporary surface.
+ * @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.
+ */
+ gfxContext* Init(const gfxRect& aRect,
+ const mozilla::gfx::IntSize& aSpreadRadius,
+ const mozilla::gfx::IntSize& aBlurRadius,
+ const gfxRect* aDirtyRect,
+ const gfxRect* aSkipRect);
+
+ /**
+ * Returns the context that should be drawn to supply the alpha mask to be
+ * blurred. If the returned surface is null, then there was an error in
+ * its creation.
+ */
+ gfxContext* GetContext()
+ {
+ return mContext;
+ }
+
+ already_AddRefed<mozilla::gfx::SourceSurface>
+ DoBlur(DrawTarget* aDT, mozilla::gfx::IntPoint* aTopLeft);
+
+ /**
+ * 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,
+ RectCornerRadii* aCornerRadii,
+ const gfxPoint& aBlurStdDev,
+ const Color& 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 aSpreadRadius The spread radius in device pixels.
+ * @param aShadowColor The color of the blur.
+ * @param aHasBorderRadius If this element also has a border radius
+ * @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::IntSize aSpreadRadius,
+ const mozilla::gfx::Color& aShadowColor,
+ const bool aHasBorderRadius,
+ 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,
+ const bool aIsDestRect,
+ const mozilla::gfx::Color& aShadowColor,
+ const mozilla::gfx::IntSize& aBlurRadius,
+ const bool aHasBorderRadius,
+ const RectCornerRadii& aInnerClipRadii,
+ DrawTarget* aDestDrawTarget);
+
+ /**
+ * The context of the temporary alpha surface.
+ */
+ RefPtr<gfxContext> mContext;
+
+ /**
+ * The temporary alpha surface.
+ */
+ mozilla::UniquePtr<unsigned char[]> mData;
+
+ /**
+ * The object that actually does the blurring for us.
+ */
+ mozilla::UniquePtr<mozilla::gfx::AlphaBoxBlur> mBlur;
+};
+
+#endif /* GFX_BLUR_H */
diff --git a/gfx/thebes/gfxColor.h b/gfx/thebes/gfxColor.h
new file mode 100644
index 000000000..4ad7d9f06
--- /dev/null
+++ b/gfx/thebes/gfxColor.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/EndianUtils.h" // for mozilla::NativeEndian::swapToBigEndian
+
+/**
+ * GFX_BLOCK_RGB_TO_FRGB(from,to)
+ * sizeof(*from) == sizeof(char)
+ * sizeof(*to) == sizeof(uint32_t)
+ *
+ * Copy 4 pixels at a time, reading blocks of 12 bytes (RGB x4)
+ * and writing blocks of 16 bytes (FRGB x4)
+ */
+#define GFX_BLOCK_RGB_TO_FRGB(from,to) \
+ PR_BEGIN_MACRO \
+ uint32_t m0 = ((uint32_t*)from)[0], \
+ m1 = ((uint32_t*)from)[1], \
+ m2 = ((uint32_t*)from)[2], \
+ rgbr = mozilla::NativeEndian::swapToBigEndian(m0), \
+ gbrg = mozilla::NativeEndian::swapToBigEndian(m1), \
+ brgb = mozilla::NativeEndian::swapToBigEndian(m2), \
+ p0, p1, p2, p3; \
+ p0 = 0xFF000000 | ((rgbr) >> 8); \
+ p1 = 0xFF000000 | ((rgbr) << 16) | ((gbrg) >> 16); \
+ p2 = 0xFF000000 | ((gbrg) << 8) | ((brgb) >> 24); \
+ p3 = 0xFF000000 | (brgb); \
+ to[0] = p0; to[1] = p1; to[2] = p2; to[3] = p3; \
+ PR_END_MACRO
+
+/**
+ * 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) << 24) | ((r) << 16) | ((g) << 8) | (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) << 24) |
+ (gfxPreMultiply(r,a) << 16) |
+ (gfxPreMultiply(g,a) << 8) |
+ (gfxPreMultiply(b,a));
+ }
+}
+
+#endif /* _GFX_COLOR_H */
diff --git a/gfx/thebes/gfxContext.cpp b/gfx/thebes/gfxContext.cpp
new file mode 100644
index 000000000..fd66d5394
--- /dev/null
+++ b/gfx/thebes/gfxContext.cpp
@@ -0,0 +1,1257 @@
+/* -*- 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 "gfxASurface.h"
+#include "gfxPattern.h"
+#include "gfxPlatform.h"
+#include "gfxPrefs.h"
+#include "GeckoProfiler.h"
+#include "gfx2DGlue.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/gfx/DrawTargetTiled.h"
+#include <algorithm>
+
+#if XP_WIN
+#include "gfxWindowsPlatform.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+UserDataKey gfxContext::sDontUseAsSourceKey;
+
+
+PatternFromState::operator mozilla::gfx::Pattern&()
+{
+ gfxContext::AzureState &state = mContext->CurrentState();
+
+ if (state.pattern) {
+ return *state.pattern->GetPattern(mContext->mDT, state.patternTransformChanged ? &state.patternTransform : nullptr);
+ }
+
+ if (state.sourceSurface) {
+ Matrix transform = state.surfTransform;
+
+ if (state.patternTransformChanged) {
+ Matrix mat = mContext->GetDTTransform();
+ if (!mat.Invert()) {
+ mPattern = new (mColorPattern.addr())
+ ColorPattern(Color()); // transparent black to paint nothing
+ return *mPattern;
+ }
+ transform = transform * state.patternTransform * mat;
+ }
+
+ mPattern = new (mSurfacePattern.addr())
+ SurfacePattern(state.sourceSurface, ExtendMode::CLAMP, transform);
+ return *mPattern;
+ }
+
+ mPattern = new (mColorPattern.addr())
+ ColorPattern(state.color);
+ return *mPattern;
+}
+
+
+gfxContext::gfxContext(DrawTarget *aTarget, const Point& aDeviceOffset)
+ : mPathIsRect(false)
+ , mTransformChanged(false)
+ , mDT(aTarget)
+{
+ if (!aTarget) {
+ gfxCriticalError() << "Don't create a gfxContext without a DrawTarget";
+ }
+
+ MOZ_COUNT_CTOR(gfxContext);
+
+ mStateStack.SetLength(1);
+ CurrentState().drawTarget = mDT;
+ CurrentState().deviceOffset = aDeviceOffset;
+ mDT->SetTransform(GetDTTransform());
+}
+
+/* static */ already_AddRefed<gfxContext>
+gfxContext::CreateOrNull(DrawTarget* aTarget,
+ const mozilla::gfx::Point& aDeviceOffset)
+{
+ if (!aTarget || !aTarget->IsValid()) {
+ gfxCriticalNote << "Invalid target in gfxContext::CreateOrNull " << hexa(aTarget);
+ return nullptr;
+ }
+
+ RefPtr<gfxContext> result = new gfxContext(aTarget, aDeviceOffset);
+ return result.forget();
+}
+
+/* static */ already_AddRefed<gfxContext>
+gfxContext::CreatePreservingTransformOrNull(DrawTarget* aTarget)
+{
+ if (!aTarget || !aTarget->IsValid()) {
+ gfxCriticalNote << "Invalid target in gfxContext::CreatePreservingTransformOrNull " << hexa(aTarget);
+ return nullptr;
+ }
+
+ Matrix transform = aTarget->GetTransform();
+ RefPtr<gfxContext> result = new gfxContext(aTarget);
+ result->SetMatrix(ThebesMatrix(transform));
+ return result.forget();
+}
+
+gfxContext::~gfxContext()
+{
+ for (int i = mStateStack.Length() - 1; i >= 0; i--) {
+ for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
+ mStateStack[i].drawTarget->PopClip();
+ }
+ }
+ MOZ_COUNT_DTOR(gfxContext);
+}
+
+void
+gfxContext::Save()
+{
+ CurrentState().transform = mTransform;
+ mStateStack.AppendElement(AzureState(CurrentState()));
+ CurrentState().pushedClips.Clear();
+}
+
+void
+gfxContext::Restore()
+{
+ for (unsigned int c = 0; c < CurrentState().pushedClips.Length(); c++) {
+ mDT->PopClip();
+ }
+
+ mStateStack.RemoveElementAt(mStateStack.Length() - 1);
+
+ mDT = CurrentState().drawTarget;
+
+ ChangeTransform(CurrentState().transform, false);
+}
+
+// drawing
+void
+gfxContext::NewPath()
+{
+ mPath = nullptr;
+ mPathBuilder = nullptr;
+ mPathIsRect = false;
+ mTransformChanged = false;
+}
+
+void
+gfxContext::ClosePath()
+{
+ EnsurePathBuilder();
+ mPathBuilder->Close();
+}
+
+already_AddRefed<Path> gfxContext::GetPath()
+{
+ EnsurePath();
+ RefPtr<Path> path(mPath);
+ return path.forget();
+}
+
+void gfxContext::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;
+}
+
+gfxPoint
+gfxContext::CurrentPoint()
+{
+ EnsurePathBuilder();
+ return ThebesPoint(mPathBuilder->CurrentPoint());
+}
+
+void
+gfxContext::Fill()
+{
+ Fill(PatternFromState(this));
+}
+
+void
+gfxContext::Fill(const Pattern& aPattern)
+{
+ PROFILER_LABEL("gfxContext", "Fill",
+ js::ProfileEntry::Category::GRAPHICS);
+ FillAzure(aPattern, 1.0f);
+}
+
+void
+gfxContext::MoveTo(const gfxPoint& pt)
+{
+ EnsurePathBuilder();
+ mPathBuilder->MoveTo(ToPoint(pt));
+}
+
+void
+gfxContext::LineTo(const gfxPoint& pt)
+{
+ EnsurePathBuilder();
+ mPathBuilder->LineTo(ToPoint(pt));
+}
+
+void
+gfxContext::Line(const gfxPoint& start, const gfxPoint& end)
+{
+ EnsurePathBuilder();
+ mPathBuilder->MoveTo(ToPoint(start));
+ mPathBuilder->LineTo(ToPoint(end));
+}
+
+// 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, true)) {
+ gfxMatrix mat = ThebesMatrix(mTransform);
+ 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();
+}
+
+// transform stuff
+void
+gfxContext::Multiply(const gfxMatrix& matrix)
+{
+ ChangeTransform(ToMatrix(matrix) * mTransform);
+}
+
+void
+gfxContext::SetMatrix(const gfxMatrix& matrix)
+{
+ ChangeTransform(ToMatrix(matrix));
+}
+
+gfxMatrix
+gfxContext::CurrentMatrix() const
+{
+ return ThebesMatrix(mTransform);
+}
+
+gfxPoint
+gfxContext::DeviceToUser(const gfxPoint& point) const
+{
+ return ThebesPoint(mTransform.Inverse().TransformPoint(ToPoint(point)));
+}
+
+Size
+gfxContext::DeviceToUser(const Size& size) const
+{
+ return mTransform.Inverse().TransformSize(size);
+}
+
+gfxRect
+gfxContext::DeviceToUser(const gfxRect& rect) const
+{
+ return ThebesRect(mTransform.Inverse().TransformBounds(ToRect(rect)));
+}
+
+gfxPoint
+gfxContext::UserToDevice(const gfxPoint& point) const
+{
+ return ThebesPoint(mTransform.TransformPoint(ToPoint(point)));
+}
+
+Size
+gfxContext::UserToDevice(const Size& size) const
+{
+ const Matrix &matrix = mTransform;
+
+ Size newSize;
+ newSize.width = size.width * matrix._11 + size.height * matrix._12;
+ newSize.height = size.width * matrix._21 + size.height * matrix._22;
+ return newSize;
+}
+
+gfxRect
+gfxContext::UserToDevice(const gfxRect& rect) const
+{
+ const Matrix &matrix = mTransform;
+ return ThebesRect(matrix.TransformBounds(ToRect(rect)));
+}
+
+bool
+gfxContext::UserToDevicePixelSnapped(gfxRect& rect, 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 = mTransform;
+ 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
+
+ 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)) {
+ 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;
+ }
+
+ return false;
+}
+
+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 = mTransform;
+ 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::SetAntialiasMode(AntialiasMode mode)
+{
+ CurrentState().aaMode = mode;
+}
+
+AntialiasMode
+gfxContext::CurrentAntialiasMode() const
+{
+ return CurrentState().aaMode;
+}
+
+void
+gfxContext::SetDash(gfxFloat *dashes, int ndash, gfxFloat offset)
+{
+ AzureState &state = CurrentState();
+
+ state.dashPattern.SetLength(ndash);
+ for (int i = 0; i < ndash; i++) {
+ state.dashPattern[i] = Float(dashes[i]);
+ }
+ state.strokeOptions.mDashLength = ndash;
+ state.strokeOptions.mDashOffset = Float(offset);
+ state.strokeOptions.mDashPattern = ndash ? state.dashPattern.Elements()
+ : nullptr;
+}
+
+bool
+gfxContext::CurrentDash(FallibleTArray<gfxFloat>& dashes, gfxFloat* offset) const
+{
+ const AzureState &state = CurrentState();
+ int count = state.strokeOptions.mDashLength;
+
+ if (count <= 0 || !dashes.SetLength(count, fallible)) {
+ return false;
+ }
+
+ for (int i = 0; i < count; i++) {
+ dashes[i] = state.dashPattern[i];
+ }
+
+ *offset = state.strokeOptions.mDashOffset;
+
+ return true;
+}
+
+gfxFloat
+gfxContext::CurrentDashOffset() const
+{
+ return CurrentState().strokeOptions.mDashOffset;
+}
+
+void
+gfxContext::SetLineWidth(gfxFloat width)
+{
+ CurrentState().strokeOptions.mLineWidth = Float(width);
+}
+
+gfxFloat
+gfxContext::CurrentLineWidth() const
+{
+ return CurrentState().strokeOptions.mLineWidth;
+}
+
+void
+gfxContext::SetOp(CompositionOp aOp)
+{
+ CurrentState().op = aOp;
+}
+
+CompositionOp
+gfxContext::CurrentOp() const
+{
+ return CurrentState().op;
+}
+
+void
+gfxContext::SetLineCap(CapStyle cap)
+{
+ CurrentState().strokeOptions.mLineCap = cap;
+}
+
+CapStyle
+gfxContext::CurrentLineCap() const
+{
+ return CurrentState().strokeOptions.mLineCap;
+}
+
+void
+gfxContext::SetLineJoin(JoinStyle join)
+{
+ CurrentState().strokeOptions.mLineJoin = join;
+}
+
+JoinStyle
+gfxContext::CurrentLineJoin() const
+{
+ return CurrentState().strokeOptions.mLineJoin;
+}
+
+void
+gfxContext::SetMiterLimit(gfxFloat limit)
+{
+ CurrentState().strokeOptions.mMiterLimit = Float(limit);
+}
+
+gfxFloat
+gfxContext::CurrentMiterLimit() const
+{
+ return CurrentState().strokeOptions.mMiterLimit;
+}
+
+// clipping
+void
+gfxContext::Clip(const Rect& rect)
+{
+ AzureState::PushedClip clip = { nullptr, rect, mTransform };
+ CurrentState().pushedClips.AppendElement(clip);
+ mDT->PushClipRect(rect);
+ NewPath();
+}
+
+void
+gfxContext::Clip(const gfxRect& rect)
+{
+ Clip(ToRect(rect));
+}
+
+void
+gfxContext::Clip(Path* aPath)
+{
+ mDT->PushClip(aPath);
+ AzureState::PushedClip clip = { aPath, Rect(), mTransform };
+ CurrentState().pushedClips.AppendElement(clip);
+}
+
+void
+gfxContext::Clip()
+{
+ if (mPathIsRect) {
+ MOZ_ASSERT(!mTransformChanged);
+
+ AzureState::PushedClip clip = { nullptr, mRect, mTransform };
+ CurrentState().pushedClips.AppendElement(clip);
+ mDT->PushClipRect(mRect);
+ } else {
+ EnsurePath();
+ mDT->PushClip(mPath);
+ AzureState::PushedClip clip = { mPath, Rect(), mTransform };
+ CurrentState().pushedClips.AppendElement(clip);
+ }
+}
+
+void
+gfxContext::PopClip()
+{
+ MOZ_ASSERT(CurrentState().pushedClips.Length() > 0);
+
+ CurrentState().pushedClips.RemoveElementAt(CurrentState().pushedClips.Length() - 1);
+ mDT->PopClip();
+}
+
+gfxRect
+gfxContext::GetClipExtents()
+{
+ Rect rect = GetAzureDeviceSpaceClipBounds();
+
+ if (rect.width == 0 || rect.height == 0) {
+ return gfxRect(0, 0, 0, 0);
+ }
+
+ Matrix mat = mTransform;
+ mat.Invert();
+ rect = mat.TransformBounds(rect);
+
+ return ThebesRect(rect);
+}
+
+bool
+gfxContext::HasComplexClip() const
+{
+ for (int i = mStateStack.Length() - 1; i >= 0; i--) {
+ for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
+ const AzureState::PushedClip &clip = mStateStack[i].pushedClips[c];
+ if (clip.path || !clip.transform.IsRectilinear()) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool
+gfxContext::ExportClip(ClipExporter& aExporter)
+{
+ for (unsigned int i = 0; i < mStateStack.Length(); i++) {
+ for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
+ AzureState::PushedClip &clip = mStateStack[i].pushedClips[c];
+ gfx::Matrix transform = clip.transform;
+ transform.PostTranslate(-GetDeviceOffset());
+
+ aExporter.BeginClip(transform);
+ if (clip.path) {
+ clip.path->StreamToSink(&aExporter);
+ } else {
+ aExporter.MoveTo(clip.rect.TopLeft());
+ aExporter.LineTo(clip.rect.TopRight());
+ aExporter.LineTo(clip.rect.BottomRight());
+ aExporter.LineTo(clip.rect.BottomLeft());
+ aExporter.Close();
+ }
+ aExporter.EndClip();
+ }
+ }
+
+ return true;
+}
+
+bool
+gfxContext::ClipContainsRect(const gfxRect& aRect)
+{
+ // Since we always return false when the clip list contains a
+ // non-rectangular clip or a non-rectilinear transform, our 'total' clip
+ // is always a rectangle if we hit the end of this function.
+ Rect clipBounds(0, 0, Float(mDT->GetSize().width), Float(mDT->GetSize().height));
+
+ for (unsigned int i = 0; i < mStateStack.Length(); i++) {
+ for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
+ AzureState::PushedClip &clip = mStateStack[i].pushedClips[c];
+ if (clip.path || !clip.transform.IsRectilinear()) {
+ // Cairo behavior is we return false if the clip contains a non-
+ // rectangle.
+ return false;
+ } else {
+ Rect clipRect = mTransform.TransformBounds(clip.rect);
+
+ clipBounds.IntersectRect(clipBounds, clipRect);
+ }
+ }
+ }
+
+ return clipBounds.Contains(ToRect(aRect));
+}
+
+// rendering sources
+
+void
+gfxContext::SetColor(const Color& aColor)
+{
+ CurrentState().pattern = nullptr;
+ CurrentState().sourceSurfCairo = nullptr;
+ CurrentState().sourceSurface = nullptr;
+ CurrentState().color = ToDeviceColor(aColor);
+}
+
+void
+gfxContext::SetDeviceColor(const Color& aColor)
+{
+ CurrentState().pattern = nullptr;
+ CurrentState().sourceSurfCairo = nullptr;
+ CurrentState().sourceSurface = nullptr;
+ CurrentState().color = aColor;
+}
+
+bool
+gfxContext::GetDeviceColor(Color& aColorOut)
+{
+ if (CurrentState().sourceSurface) {
+ return false;
+ }
+ if (CurrentState().pattern) {
+ return CurrentState().pattern->GetSolidColor(aColorOut);
+ }
+
+ aColorOut = CurrentState().color;
+ return true;
+}
+
+void
+gfxContext::SetSource(gfxASurface *surface, const gfxPoint& offset)
+{
+ CurrentState().surfTransform = Matrix(1.0f, 0, 0, 1.0f, Float(offset.x), Float(offset.y));
+ CurrentState().pattern = nullptr;
+ CurrentState().patternTransformChanged = false;
+ // Keep the underlying cairo surface around while we keep the
+ // sourceSurface.
+ CurrentState().sourceSurfCairo = surface;
+ CurrentState().sourceSurface =
+ gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface);
+ CurrentState().color = Color(0, 0, 0, 0);
+}
+
+void
+gfxContext::SetPattern(gfxPattern *pattern)
+{
+ CurrentState().sourceSurfCairo = nullptr;
+ CurrentState().sourceSurface = nullptr;
+ CurrentState().patternTransformChanged = false;
+ CurrentState().pattern = pattern;
+}
+
+already_AddRefed<gfxPattern>
+gfxContext::GetPattern()
+{
+ RefPtr<gfxPattern> pat;
+
+ AzureState &state = CurrentState();
+ if (state.pattern) {
+ pat = state.pattern;
+ } else if (state.sourceSurface) {
+ NS_ASSERTION(false, "Ugh, this isn't good.");
+ } else {
+ pat = new gfxPattern(state.color);
+ }
+ return pat.forget();
+}
+
+void
+gfxContext::SetFontSmoothingBackgroundColor(const Color& aColor)
+{
+ CurrentState().fontSmoothingBackgroundColor = aColor;
+}
+
+Color
+gfxContext::GetFontSmoothingBackgroundColor()
+{
+ return CurrentState().fontSmoothingBackgroundColor;
+}
+
+// masking
+void
+gfxContext::Mask(SourceSurface* aSurface, Float aAlpha, const Matrix& aTransform)
+{
+ Matrix old = mTransform;
+ Matrix mat = aTransform * mTransform;
+
+ ChangeTransform(mat);
+ mDT->MaskSurface(PatternFromState(this), aSurface, Point(),
+ DrawOptions(aAlpha, CurrentState().op, CurrentState().aaMode));
+ ChangeTransform(old);
+}
+
+void
+gfxContext::Mask(SourceSurface *surface, float alpha, const Point& offset)
+{
+ // We clip here to bind to the mask surface bounds, see above.
+ mDT->MaskSurface(PatternFromState(this),
+ surface,
+ offset,
+ DrawOptions(alpha, CurrentState().op, CurrentState().aaMode));
+}
+
+void
+gfxContext::Paint(gfxFloat alpha)
+{
+ PROFILER_LABEL("gfxContext", "Paint",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ AzureState &state = CurrentState();
+
+ if (state.sourceSurface && !state.sourceSurfCairo &&
+ !state.patternTransformChanged)
+ {
+ // This is the case where a PopGroupToSource has been done and this
+ // paint is executed without changing the transform or the source.
+ Matrix oldMat = mDT->GetTransform();
+
+ IntSize surfSize = state.sourceSurface->GetSize();
+
+ mDT->SetTransform(Matrix::Translation(-state.deviceOffset.x,
+ -state.deviceOffset.y));
+
+ mDT->DrawSurface(state.sourceSurface,
+ Rect(state.sourceSurfaceDeviceOffset, Size(surfSize.width, surfSize.height)),
+ Rect(Point(), Size(surfSize.width, surfSize.height)),
+ DrawSurfaceOptions(), DrawOptions(alpha, GetOp()));
+ mDT->SetTransform(oldMat);
+ return;
+ }
+
+ Matrix mat = mDT->GetTransform();
+ mat.Invert();
+ Rect paintRect = mat.TransformBounds(Rect(Point(0, 0), Size(mDT->GetSize())));
+
+ mDT->FillRect(paintRect, PatternFromState(this),
+ DrawOptions(Float(alpha), GetOp()));
+}
+
+void
+gfxContext::PushGroupForBlendBack(gfxContentType content, Float aOpacity, SourceSurface* aMask, const Matrix& aMaskTransform)
+{
+ if (gfxPrefs::UseNativePushLayer()) {
+ Save();
+ mDT->PushLayer(content == gfxContentType::COLOR, aOpacity, aMask, aMaskTransform);
+ } else {
+ DrawTarget* oldDT = mDT;
+
+ PushNewDT(content);
+
+ if (oldDT != mDT) {
+ PushClipsToDT(mDT);
+ }
+ mDT->SetTransform(GetDTTransform());
+
+ CurrentState().mBlendOpacity = aOpacity;
+ CurrentState().mBlendMask = aMask;
+#ifdef DEBUG
+ CurrentState().mWasPushedForBlendBack = true;
+#endif
+ CurrentState().mBlendMaskTransform = aMaskTransform;
+ }
+}
+
+static gfxRect
+GetRoundOutDeviceClipExtents(gfxContext* aCtx)
+{
+ gfxContextMatrixAutoSaveRestore save(aCtx);
+ aCtx->SetMatrix(gfxMatrix());
+ gfxRect r = aCtx->GetClipExtents();
+ r.RoundOut();
+ return r;
+}
+
+void
+gfxContext::PushGroupAndCopyBackground(gfxContentType content, Float aOpacity, SourceSurface* aMask, const Matrix& aMaskTransform)
+{
+ IntRect clipExtents;
+ if (mDT->GetFormat() != SurfaceFormat::B8G8R8X8) {
+ gfxRect clipRect = GetRoundOutDeviceClipExtents(this);
+ clipExtents = IntRect::Truncate(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
+ }
+ bool pushOpaqueWithCopiedBG = (mDT->GetFormat() == SurfaceFormat::B8G8R8X8 ||
+ mDT->GetOpaqueRect().Contains(clipExtents)) &&
+ !mDT->GetUserData(&sDontUseAsSourceKey);
+
+ if (gfxPrefs::UseNativePushLayer()) {
+ Save();
+
+ if (pushOpaqueWithCopiedBG) {
+ mDT->PushLayer(true, aOpacity, aMask, aMaskTransform, IntRect(), true);
+ } else {
+ mDT->PushLayer(content == gfxContentType::COLOR, aOpacity, aMask, aMaskTransform, IntRect(), false);
+ }
+ } else {
+ RefPtr<SourceSurface> source;
+ // This snapshot can be nullptr if the DrawTarget is a cairo target that is currently
+ // in an error state.
+ if (pushOpaqueWithCopiedBG && (source = mDT->Snapshot())) {
+ DrawTarget *oldDT = mDT;
+ Point oldDeviceOffset = CurrentState().deviceOffset;
+
+ PushNewDT(gfxContentType::COLOR);
+
+ if (oldDT == mDT) {
+ // Creating new DT failed.
+ return;
+ }
+
+ CurrentState().mBlendOpacity = aOpacity;
+ CurrentState().mBlendMask = aMask;
+#ifdef DEBUG
+ CurrentState().mWasPushedForBlendBack = true;
+#endif
+ CurrentState().mBlendMaskTransform = aMaskTransform;
+
+ Point offset = CurrentState().deviceOffset - oldDeviceOffset;
+ Rect surfRect(0, 0, Float(mDT->GetSize().width), Float(mDT->GetSize().height));
+ Rect sourceRect = surfRect + offset;
+
+ mDT->SetTransform(Matrix());
+
+ // XXX: It's really sad that we have to do this (for performance).
+ // Once DrawTarget gets a PushLayer API we can implement this within
+ // DrawTargetTiled.
+ if (source->GetType() == SurfaceType::TILED) {
+ SnapshotTiled *sourceTiled = static_cast<SnapshotTiled*>(source.get());
+ for (uint32_t i = 0; i < sourceTiled->mSnapshots.size(); i++) {
+ Rect tileSourceRect = sourceRect.Intersect(Rect(sourceTiled->mOrigins[i].x,
+ sourceTiled->mOrigins[i].y,
+ sourceTiled->mSnapshots[i]->GetSize().width,
+ sourceTiled->mSnapshots[i]->GetSize().height));
+
+ if (tileSourceRect.IsEmpty()) {
+ continue;
+ }
+ Rect tileDestRect = tileSourceRect - offset;
+ tileSourceRect -= sourceTiled->mOrigins[i];
+
+ mDT->DrawSurface(sourceTiled->mSnapshots[i], tileDestRect, tileSourceRect);
+ }
+ } else {
+ mDT->DrawSurface(source, surfRect, sourceRect);
+ }
+ mDT->SetOpaqueRect(oldDT->GetOpaqueRect());
+
+ PushClipsToDT(mDT);
+ mDT->SetTransform(GetDTTransform());
+ return;
+ }
+ DrawTarget* oldDT = mDT;
+
+ PushNewDT(content);
+
+ if (oldDT != mDT) {
+ PushClipsToDT(mDT);
+ }
+
+ mDT->SetTransform(GetDTTransform());
+ CurrentState().mBlendOpacity = aOpacity;
+ CurrentState().mBlendMask = aMask;
+#ifdef DEBUG
+ CurrentState().mWasPushedForBlendBack = true;
+#endif
+ CurrentState().mBlendMaskTransform = aMaskTransform;
+ }
+}
+
+void
+gfxContext::PopGroupAndBlend()
+{
+ if (gfxPrefs::UseNativePushLayer()) {
+ mDT->PopLayer();
+ Restore();
+ } else {
+ MOZ_ASSERT(CurrentState().mWasPushedForBlendBack);
+ Float opacity = CurrentState().mBlendOpacity;
+ RefPtr<SourceSurface> mask = CurrentState().mBlendMask;
+ Matrix maskTransform = CurrentState().mBlendMaskTransform;
+
+ RefPtr<SourceSurface> src = mDT->Snapshot();
+ Point deviceOffset = CurrentState().deviceOffset;
+ Restore();
+ CurrentState().sourceSurfCairo = nullptr;
+ CurrentState().sourceSurface = src;
+ CurrentState().sourceSurfaceDeviceOffset = deviceOffset;
+ CurrentState().pattern = nullptr;
+ CurrentState().patternTransformChanged = false;
+
+ Matrix mat = mTransform;
+ mat.Invert();
+ mat.PreTranslate(deviceOffset.x, deviceOffset.y); // device offset translation
+
+ CurrentState().surfTransform = mat;
+
+ CompositionOp oldOp = GetOp();
+ SetOp(CompositionOp::OP_OVER);
+
+ if (mask) {
+ if (!maskTransform.HasNonTranslation()) {
+ Mask(mask, opacity, Point(maskTransform._31, maskTransform._32));
+ } else {
+ Mask(mask, opacity, maskTransform);
+ }
+ } else {
+ Paint(opacity);
+ }
+
+ SetOp(oldOp);
+ }
+}
+
+#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 = mTransform;
+ 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 = mTransform;
+ 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 = mTransform;
+ 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;
+}
+
+void
+gfxContext::FillAzure(const Pattern& aPattern, Float aOpacity)
+{
+ AzureState &state = CurrentState();
+
+ 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(aOpacity));
+ } else {
+ mDT->FillRect(mRect, aPattern, DrawOptions(aOpacity, op, state.aaMode));
+ }
+ } else {
+ EnsurePath();
+ mDT->Fill(mPath, aPattern, DrawOptions(aOpacity, op, state.aaMode));
+ }
+}
+
+void
+gfxContext::PushClipsToDT(DrawTarget *aDT)
+{
+ // Don't need to save the old transform, we'll be setting a new one soon!
+
+ // Push all clips from the bottom of the stack to the clip before ours.
+ for (unsigned int i = 0; i < mStateStack.Length() - 1; i++) {
+ for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
+ aDT->SetTransform(mStateStack[i].pushedClips[c].transform * GetDeviceTransform());
+ if (mStateStack[i].pushedClips[c].path) {
+ aDT->PushClip(mStateStack[i].pushedClips[c].path);
+ } else {
+ aDT->PushClipRect(mStateStack[i].pushedClips[c].rect);
+ }
+ }
+ }
+}
+
+CompositionOp
+gfxContext::GetOp()
+{
+ if (CurrentState().op != CompositionOp::OP_SOURCE) {
+ return CurrentState().op;
+ }
+
+ AzureState &state = CurrentState();
+ if (state.pattern) {
+ if (state.pattern->IsOpaque()) {
+ return CompositionOp::OP_OVER;
+ } else {
+ return CompositionOp::OP_SOURCE;
+ }
+ } else if (state.sourceSurface) {
+ if (state.sourceSurface->GetFormat() == SurfaceFormat::B8G8R8X8) {
+ return CompositionOp::OP_OVER;
+ } else {
+ return CompositionOp::OP_SOURCE;
+ }
+ } else {
+ if (state.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 CurrentState()'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)
+{
+ AzureState &state = CurrentState();
+
+ if (aUpdatePatternTransform && (state.pattern || state.sourceSurface)
+ && !state.patternTransformChanged) {
+ state.patternTransform = GetDTTransform();
+ state.patternTransformChanged = true;
+ }
+
+ if (mPathIsRect) {
+ Matrix invMatrix = aNewMatrix;
+
+ invMatrix.Invert();
+
+ Matrix toNewUS = mTransform * 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 = mTransform;
+ }
+
+ mTransform = aNewMatrix;
+
+ mDT->SetTransform(GetDTTransform());
+}
+
+Rect
+gfxContext::GetAzureDeviceSpaceClipBounds()
+{
+ Rect rect(CurrentState().deviceOffset.x, CurrentState().deviceOffset.y,
+ Float(mDT->GetSize().width), Float(mDT->GetSize().height));
+ for (unsigned int i = 0; i < mStateStack.Length(); i++) {
+ for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
+ AzureState::PushedClip &clip = mStateStack[i].pushedClips[c];
+ if (clip.path) {
+ Rect bounds = clip.path->GetBounds(clip.transform);
+ rect.IntersectRect(rect, bounds);
+ } else {
+ rect.IntersectRect(rect, clip.transform.TransformBounds(clip.rect));
+ }
+ }
+ }
+
+ return rect;
+}
+
+Point
+gfxContext::GetDeviceOffset() const
+{
+ return CurrentState().deviceOffset;
+}
+
+Matrix
+gfxContext::GetDeviceTransform() const
+{
+ return Matrix::Translation(-CurrentState().deviceOffset.x,
+ -CurrentState().deviceOffset.y);
+}
+
+Matrix
+gfxContext::GetDTTransform() const
+{
+ Matrix mat = mTransform;
+ mat._31 -= CurrentState().deviceOffset.x;
+ mat._32 -= CurrentState().deviceOffset.y;
+ return mat;
+}
+
+void
+gfxContext::PushNewDT(gfxContentType content)
+{
+ Rect clipBounds = GetAzureDeviceSpaceClipBounds();
+ clipBounds.RoundOut();
+
+ clipBounds.width = std::max(1.0f, clipBounds.width);
+ clipBounds.height = std::max(1.0f, clipBounds.height);
+
+ SurfaceFormat format = gfxPlatform::GetPlatform()->Optimal2DFormatForContent(content);
+
+ RefPtr<DrawTarget> newDT =
+ mDT->CreateSimilarDrawTarget(IntSize(int32_t(clipBounds.width), int32_t(clipBounds.height)),
+ format);
+
+ if (!newDT) {
+ NS_WARNING("Failed to create DrawTarget of sufficient size.");
+ newDT = mDT->CreateSimilarDrawTarget(IntSize(64, 64), format);
+
+ if (!newDT) {
+ if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()
+#ifdef XP_WIN
+ && !(mDT->GetBackendType() == BackendType::DIRECT2D1_1 &&
+ !DeviceManagerDx::Get()->GetContentDevice())
+#endif
+ ) {
+ // If even this fails.. we're most likely just out of memory!
+ NS_ABORT_OOM(BytesPerPixel(format) * 64 * 64);
+ }
+ newDT = CurrentState().drawTarget;
+ }
+ }
+
+ Save();
+
+ CurrentState().drawTarget = newDT;
+ CurrentState().deviceOffset = clipBounds.TopLeft();
+
+ mDT = newDT;
+}
+
diff --git a/gfx/thebes/gfxContext.h b/gfx/thebes/gfxContext.h
new file mode 100644
index 000000000..49f6a7dca
--- /dev/null
+++ b/gfx/thebes/gfxContext.h
@@ -0,0 +1,694 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxTypes.h"
+
+#include "gfxASurface.h"
+#include "gfxPoint.h"
+#include "gfxRect.h"
+#include "gfxMatrix.h"
+#include "gfxPattern.h"
+#include "nsTArray.h"
+
+#include "mozilla/gfx/2D.h"
+
+typedef struct _cairo cairo_t;
+class GlyphBufferAzure;
+
+namespace mozilla {
+namespace gfx {
+struct RectCornerRadii;
+} // namespace gfx
+} // namespace mozilla
+
+class ClipExporter;
+
+/**
+ * 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, SetColor or SetSource.
+ *
+ * Note that the gfxContext takes coordinates in device pixels,
+ * as opposed to app units.
+ */
+class gfxContext final {
+ typedef mozilla::gfx::CapStyle CapStyle;
+ typedef mozilla::gfx::CompositionOp CompositionOp;
+ typedef mozilla::gfx::JoinStyle JoinStyle;
+ typedef mozilla::gfx::FillRule FillRule;
+ typedef mozilla::gfx::Path Path;
+ typedef mozilla::gfx::Pattern Pattern;
+ typedef mozilla::gfx::Rect Rect;
+ typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
+ typedef mozilla::gfx::Size Size;
+
+ NS_INLINE_DECL_REFCOUNTING(gfxContext)
+
+public:
+ /**
+ * 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 already_AddRefed<gfxContext>
+ CreateOrNull(mozilla::gfx::DrawTarget* aTarget,
+ const mozilla::gfx::Point& aDeviceOffset = mozilla::gfx::Point());
+
+ /**
+ * Create a new gfxContext wrapping aTarget and preserving aTarget's
+ * transform. Note that the transform is moved from aTarget to the resulting
+ * gfxContext, aTarget will no longer have its transform.
+ * If aTarget is null or invalid, nullptr is returned. The caller
+ * is responsible for handling this scenario as appropriate.
+ */
+ static already_AddRefed<gfxContext>
+ CreatePreservingTransformOrNull(mozilla::gfx::DrawTarget* aTarget);
+
+ mozilla::gfx::DrawTarget *GetDrawTarget() { return mDT; }
+
+ /**
+ ** 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();
+ void Fill(const Pattern& aPattern);
+
+ /**
+ * Forgets the current path.
+ */
+ void NewPath();
+
+ /**
+ * Closes the path, i.e. connects the last drawn point to the first one.
+ *
+ * Filling a path will implicitly close it.
+ */
+ void ClosePath();
+
+ /**
+ * Returns the current path.
+ */
+ already_AddRefed<Path> GetPath();
+
+ /**
+ * Sets the given path as the current path.
+ */
+ void SetPath(Path* path);
+
+ /**
+ * Moves the pen to a new point without drawing a line.
+ */
+ void MoveTo(const gfxPoint& pt);
+
+ /**
+ * Returns the current point in the current path.
+ */
+ gfxPoint CurrentPoint();
+
+ /**
+ * Draws a line from the current point to pt.
+ *
+ * @see MoveTo
+ */
+ void LineTo(const gfxPoint& pt);
+
+ // path helpers
+ /**
+ * Draws a line from start to end.
+ */
+ void Line(const gfxPoint& start, const gfxPoint& end); // XXX snapToPixels option?
+
+ /**
+ * Draws the rectangle given by rect.
+ * @param snapToPixels ?
+ */
+ void Rectangle(const gfxRect& rect, bool snapToPixels = false);
+ void SnappedRectangle(const gfxRect& rect) { return Rectangle(rect, true); }
+
+ /**
+ ** 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& other);
+
+ /**
+ * Replaces the current transformation matrix with matrix.
+ */
+ void SetMatrix(const gfxMatrix& matrix);
+
+ /**
+ * Returns the current transformation matrix.
+ */
+ gfxMatrix CurrentMatrix() const;
+
+ /**
+ * Converts a point from device to user coordinates using the inverse
+ * transformation matrix.
+ */
+ gfxPoint DeviceToUser(const gfxPoint& point) const;
+
+ /**
+ * Converts a size from device to user coordinates. This does not apply
+ * translation components of the matrix.
+ */
+ Size DeviceToUser(const Size& size) const;
+
+ /**
+ * 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& rect) const;
+
+ /**
+ * Converts a point from user to device coordinates using the transformation
+ * matrix.
+ */
+ gfxPoint UserToDevice(const gfxPoint& point) const;
+
+ /**
+ * Converts a size from user to device coordinates. This does not apply
+ * translation components of the matrix.
+ */
+ Size UserToDevice(const Size& size) const;
+
+ /**
+ * 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;
+
+ /**
+ * 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.
+ *
+ * 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(gfxRect& rect, bool ignoreScale = false) 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 mozilla::gfx::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(mozilla::gfx::Color& aColorOut);
+
+ /**
+ * 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::Color& aColor);
+
+ /**
+ * Uses a surface for drawing. This is a shorthand for creating a
+ * pattern and setting it.
+ *
+ * @param offset from the source surface, to use only part of it.
+ * May need to make it negative.
+ */
+ void SetSource(gfxASurface *surface, const gfxPoint& offset = gfxPoint(0.0, 0.0));
+
+ /**
+ * Uses a pattern for drawing.
+ */
+ void SetPattern(gfxPattern *pattern);
+
+ /**
+ * Set the color that text drawn on top of transparent pixels should be
+ * anti-aliased into.
+ */
+ void SetFontSmoothingBackgroundColor(const mozilla::gfx::Color& aColor);
+ mozilla::gfx::Color GetFontSmoothingBackgroundColor();
+
+ /**
+ * Get the source pattern (solid color, normal pattern, surface, etc)
+ */
+ already_AddRefed<gfxPattern> GetPattern();
+
+ /**
+ ** Painting
+ **/
+ /**
+ * Paints the current source surface/pattern everywhere in the current
+ * clip region.
+ */
+ void Paint(gfxFloat alpha = 1.0);
+
+ /**
+ ** Painting with a Mask
+ **/
+ /**
+ * Like Paint, except that it only draws the source where pattern is
+ * non-transparent.
+ */
+ void Mask(mozilla::gfx::SourceSurface *aSurface, mozilla::gfx::Float aAlpha, const mozilla::gfx::Matrix& aTransform);
+ void Mask(mozilla::gfx::SourceSurface *aSurface, const mozilla::gfx::Matrix& aTransform) { Mask(aSurface, 1.0f, aTransform); }
+ void Mask(mozilla::gfx::SourceSurface *surface, float alpha = 1.0f, const mozilla::gfx::Point& offset = mozilla::gfx::Point());
+
+ /**
+ ** Line Properties
+ **/
+
+ void SetDash(gfxFloat *dashes, int ndash, gfxFloat offset);
+ // 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<gfxFloat>& dashes, gfxFloat* offset) const;
+ // Returns 0.0 if dashing isn't enabled.
+ gfxFloat CurrentDashOffset() const;
+
+ /**
+ * Sets the line width that's used for line drawing.
+ */
+ void SetLineWidth(gfxFloat width);
+
+ /**
+ * Returns the currently set line width.
+ *
+ * @see SetLineWidth
+ */
+ gfxFloat CurrentLineWidth() const;
+
+ /**
+ * Sets the line caps, i.e. how line endings are drawn.
+ */
+ void SetLineCap(CapStyle cap);
+ CapStyle CurrentLineCap() const;
+
+ /**
+ * Sets the line join, i.e. how the connection between two lines is
+ * drawn.
+ */
+ void SetLineJoin(JoinStyle join);
+ JoinStyle CurrentLineJoin() const;
+
+ void SetMiterLimit(gfxFloat limit);
+ gfxFloat CurrentMiterLimit() const;
+
+ /**
+ * 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 op);
+ CompositionOp CurrentOp() const;
+
+ void SetAntialiasMode(mozilla::gfx::AntialiasMode mode);
+ mozilla::gfx::AntialiasMode CurrentAntialiasMode() const;
+
+ /**
+ ** 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 Rect& rect);
+ void Clip(const gfxRect& rect); // will clip to a rect
+ void Clip(Path* aPath);
+
+ void PopClip();
+
+ /**
+ * This will return the current bounds of the clip region in user
+ * space.
+ */
+ gfxRect GetClipExtents();
+
+ /**
+ * Whether the current clip is not a simple rectangle.
+ */
+ bool HasComplexClip() const;
+
+ /**
+ * Returns true if the given rectangle is fully contained in the current clip.
+ * This is conservative; it may return false even when the given rectangle is
+ * fully contained by the current clip.
+ */
+ bool ClipContainsRect(const gfxRect& aRect);
+
+ /**
+ * Exports the current clip using the provided exporter.
+ */
+ bool ExportClip(ClipExporter& aExporter);
+
+ /**
+ * Groups
+ */
+ void PushGroupForBlendBack(gfxContentType content, mozilla::gfx::Float aOpacity = 1.0f,
+ mozilla::gfx::SourceSurface* aMask = nullptr,
+ const mozilla::gfx::Matrix& aMaskTransform = mozilla::gfx::Matrix());
+
+ /**
+ * Like PushGroupForBlendBack, but if the current surface is gfxContentType::COLOR and
+ * content is gfxContentType::COLOR_ALPHA, makes the pushed surface gfxContentType::COLOR
+ * instead and copies the contents of the current surface to the pushed
+ * surface. This is good for pushing opacity groups, since blending the
+ * group back to the current surface with some alpha applied will give
+ * the correct results and using an opaque pushed surface gives better
+ * quality and performance.
+ */
+ void PushGroupAndCopyBackground(gfxContentType content = gfxContentType::COLOR,
+ mozilla::gfx::Float aOpacity = 1.0f,
+ mozilla::gfx::SourceSurface* aMask = nullptr,
+ const mozilla::gfx::Matrix& aMaskTransform = mozilla::gfx::Matrix());
+ void PopGroupAndBlend();
+
+ mozilla::gfx::Point GetDeviceOffset() const;
+
+#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
+
+ static mozilla::gfx::UserDataKey sDontUseAsSourceKey;
+
+private:
+
+ /**
+ * Initialize this context from a DrawTarget.
+ * Strips any transform from aTarget.
+ * aTarget will be flushed in the gfxContext's destructor. Use the static
+ * ContextForDrawTargetNoTransform() when you want this behavior, as that
+ * version deals with null DrawTarget better.
+ */
+ explicit gfxContext(mozilla::gfx::DrawTarget *aTarget,
+ const mozilla::gfx::Point& aDeviceOffset = mozilla::gfx::Point());
+ ~gfxContext();
+
+ friend class PatternFromState;
+ friend class GlyphBufferAzure;
+
+ typedef mozilla::gfx::Matrix Matrix;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::Color Color;
+ typedef mozilla::gfx::StrokeOptions StrokeOptions;
+ typedef mozilla::gfx::Float Float;
+ typedef mozilla::gfx::PathBuilder PathBuilder;
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+
+ struct AzureState {
+ AzureState()
+ : op(mozilla::gfx::CompositionOp::OP_OVER)
+ , color(0, 0, 0, 1.0f)
+ , aaMode(mozilla::gfx::AntialiasMode::SUBPIXEL)
+ , patternTransformChanged(false)
+ , mBlendOpacity(0.0f)
+ {}
+
+ mozilla::gfx::CompositionOp op;
+ Color color;
+ RefPtr<gfxPattern> pattern;
+ RefPtr<gfxASurface> sourceSurfCairo;
+ RefPtr<SourceSurface> sourceSurface;
+ mozilla::gfx::Point sourceSurfaceDeviceOffset;
+ Matrix surfTransform;
+ Matrix transform;
+ struct PushedClip {
+ RefPtr<Path> path;
+ Rect rect;
+ Matrix transform;
+ };
+ nsTArray<PushedClip> pushedClips;
+ nsTArray<Float> dashPattern;
+ StrokeOptions strokeOptions;
+ RefPtr<DrawTarget> drawTarget;
+ mozilla::gfx::AntialiasMode aaMode;
+ bool patternTransformChanged;
+ Matrix patternTransform;
+ Color fontSmoothingBackgroundColor;
+ // This is used solely for using minimal intermediate surface size.
+ mozilla::gfx::Point deviceOffset;
+ // Support groups
+ mozilla::gfx::Float mBlendOpacity;
+ RefPtr<SourceSurface> mBlendMask;
+ Matrix mBlendMaskTransform;
+#ifdef DEBUG
+ bool mWasPushedForBlendBack;
+#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();
+ void FillAzure(const Pattern& aPattern, mozilla::gfx::Float aOpacity);
+ void PushClipsToDT(mozilla::gfx::DrawTarget *aDT);
+ CompositionOp GetOp();
+ void ChangeTransform(const mozilla::gfx::Matrix &aNewMatrix, bool aUpdatePatternTransform = true);
+ Rect GetAzureDeviceSpaceClipBounds();
+ Matrix GetDeviceTransform() const;
+ Matrix GetDTTransform() const;
+ void PushNewDT(gfxContentType content);
+
+ bool mPathIsRect;
+ bool mTransformChanged;
+ Matrix mPathTransform;
+ Rect mRect;
+ RefPtr<PathBuilder> mPathBuilder;
+ RefPtr<Path> mPath;
+ Matrix mTransform;
+ nsTArray<AzureState> mStateStack;
+
+ AzureState &CurrentState() { return mStateStack[mStateStack.Length() - 1]; }
+ const AzureState &CurrentState() const { return mStateStack[mStateStack.Length() - 1]; }
+
+ RefPtr<DrawTarget> mDT;
+};
+
+/**
+ * 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 gfxContextAutoSaveRestore
+{
+public:
+ gfxContextAutoSaveRestore() : mContext(nullptr) {}
+
+ explicit gfxContextAutoSaveRestore(gfxContext *aContext) : mContext(aContext) {
+ mContext->Save();
+ }
+
+ ~gfxContextAutoSaveRestore() {
+ Restore();
+ }
+
+ void SetContext(gfxContext *aContext) {
+ NS_ASSERTION(!mContext, "Not going to call Restore() on some 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 gfxContextMatrixAutoSaveRestore
+{
+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 gfxMatrix& Matrix()
+ {
+ MOZ_ASSERT(mContext, "mMatrix doesn't contain a useful matrix");
+ return mMatrix;
+ }
+
+ bool HasMatrix() const { return !!mContext; }
+
+private:
+ gfxContext *mContext;
+ gfxMatrix mMatrix;
+};
+
+
+class DrawTargetAutoDisableSubpixelAntialiasing {
+public:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ DrawTargetAutoDisableSubpixelAntialiasing(DrawTarget *aDT, bool aDisable)
+ {
+ if (aDisable) {
+ mDT = aDT;
+ mSubpixelAntialiasingEnabled = mDT->GetPermitSubpixelAA();
+ mDT->SetPermitSubpixelAA(false);
+ }
+ }
+ ~DrawTargetAutoDisableSubpixelAntialiasing()
+ {
+ if (mDT) {
+ mDT->SetPermitSubpixelAA(mSubpixelAntialiasingEnabled);
+ }
+ }
+
+private:
+ RefPtr<DrawTarget> mDT;
+ bool mSubpixelAntialiasingEnabled;
+};
+
+/* 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(gfxContext *aContext) : mContext(aContext), mPattern(nullptr) {}
+ ~PatternFromState() { if (mPattern) { mPattern->~Pattern(); } }
+
+ operator mozilla::gfx::Pattern&();
+
+private:
+ union {
+ mozilla::AlignedStorage2<mozilla::gfx::ColorPattern> mColorPattern;
+ mozilla::AlignedStorage2<mozilla::gfx::SurfacePattern> mSurfacePattern;
+ };
+
+ gfxContext *mContext;
+ mozilla::gfx::Pattern *mPattern;
+};
+
+/* 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 000000000..08217b82f
--- /dev/null
+++ b/gfx/thebes/gfxCoreTextShaper.cpp
@@ -0,0 +1,800 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/UniquePtrExtensions.h"
+
+#include <algorithm>
+
+#include <dlfcn.h>
+
+using namespace mozilla;
+
+// standard font descriptors that we construct the first time they're needed
+CTFontDescriptorRef gfxCoreTextShaper::sDefaultFeaturesDescriptor = nullptr;
+CTFontDescriptorRef gfxCoreTextShaper::sDisableLigaturesDescriptor = nullptr;
+CTFontDescriptorRef gfxCoreTextShaper::sIndicFeaturesDescriptor = nullptr;
+CTFontDescriptorRef gfxCoreTextShaper::sIndicDisableLigaturesDescriptor = nullptr;
+
+static CFStringRef sCTWritingDirectionAttributeName = nullptr;
+
+// See CTStringAttributes.h
+enum {
+ kMyCTWritingDirectionEmbedding = (0 << 1),
+ kMyCTWritingDirectionOverride = (1 << 1)
+};
+
+// Helper to create a CFDictionary with the right attributes for shaping our
+// text, including imposing the given directionality.
+// This will only be called if we're on 10.8 or later.
+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 = kMyCTWritingDirectionOverride |
+ (aRightToLeft ? kCTWritingDirectionRightToLeft
+ : kCTWritingDirectionLeftToRight);
+ CFNumberRef dirNumber =
+ ::CFNumberCreate(kCFAllocatorDefault,
+ kCFNumberSInt16Type, &dirOverride);
+ CFArrayRef dirArray =
+ ::CFArrayCreate(kCFAllocatorDefault,
+ (const void **) &dirNumber, 1,
+ &kCFTypeArrayCallBacks);
+ ::CFRelease(dirNumber);
+ CFTypeRef attrs[] = { kCTFontAttributeName, sCTWritingDirectionAttributeName };
+ CFTypeRef values[] = { mCTFont, dirArray };
+ CFDictionaryRef attrDict =
+ ::CFDictionaryCreate(kCFAllocatorDefault,
+ attrs, values, ArrayLength(attrs),
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ ::CFRelease(dirArray);
+ return attrDict;
+}
+
+CFDictionaryRef
+gfxCoreTextShaper::CreateAttrDictWithoutDirection()
+{
+ CFTypeRef attrs[] = { kCTFontAttributeName };
+ CFTypeRef values[] = { mCTFont };
+ CFDictionaryRef attrDict =
+ ::CFDictionaryCreate(kCFAllocatorDefault,
+ attrs, values, ArrayLength(attrs),
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ return attrDict;
+}
+
+gfxCoreTextShaper::gfxCoreTextShaper(gfxMacFont *aFont)
+ : gfxFontShaper(aFont)
+ , mAttributesDictLTR(nullptr)
+ , mAttributesDictRTL(nullptr)
+{
+ static bool sInitialized = false;
+ if (!sInitialized) {
+ CFStringRef* pstr = (CFStringRef*)
+ dlsym(RTLD_DEFAULT, "kCTWritingDirectionAttributeName");
+ if (pstr) {
+ sCTWritingDirectionAttributeName = *pstr;
+ }
+ sInitialized = true;
+ }
+
+ // Create our CTFontRef
+ mCTFont = CreateCTFontWithFeatures(aFont->GetAdjustedSize(),
+ GetDefaultFeaturesDescriptor());
+}
+
+gfxCoreTextShaper::~gfxCoreTextShaper()
+{
+ if (mAttributesDictLTR) {
+ ::CFRelease(mAttributesDictLTR);
+ }
+ if (mAttributesDictRTL) {
+ ::CFRelease(mAttributesDictRTL);
+ }
+ if (mCTFont) {
+ ::CFRelease(mCTFont);
+ }
+}
+
+static bool
+IsBuggyIndicScript(unicode::Script aScript)
+{
+ return aScript == unicode::Script::BENGALI ||
+ aScript == unicode::Script::KANNADA;
+}
+
+bool
+gfxCoreTextShaper::ShapeText(DrawTarget *aDrawTarget,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ 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);
+ uint32_t length = aLength;
+
+ uint32_t startOffset;
+ CFStringRef stringObj;
+ CFDictionaryRef attrObj;
+
+ if (sCTWritingDirectionAttributeName) {
+ startOffset = 0;
+ stringObj = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
+ text, length,
+ kCFAllocatorNull);
+
+ // Get an attributes dictionary suitable for shaping text in the
+ // current direction, creating it if necessary.
+ attrObj = isRightToLeft ? mAttributesDictRTL : mAttributesDictLTR;
+ if (!attrObj) {
+ attrObj = CreateAttrDict(isRightToLeft);
+ (isRightToLeft ? mAttributesDictRTL : mAttributesDictLTR) = attrObj;
+ }
+ } else {
+ // OS is too old to support kCTWritingDirectionAttributeName:
+ // we need to bidi-wrap the text if the run is RTL,
+ // or if it is an LTR run but may contain (overridden) RTL chars
+ bool bidiWrap = isRightToLeft;
+ if (!bidiWrap && !aShapedText->TextIs8Bit()) {
+ uint32_t i;
+ for (i = 0; i < length; ++i) {
+ if (gfxFontUtils::PotentialRTLChar(aText[i])) {
+ bidiWrap = true;
+ break;
+ }
+ }
+ }
+
+ // If there's a possibility of any bidi, we wrap the text with
+ // direction overrides to ensure neutrals or characters that were
+ // bidi-overridden in HTML behave properly.
+ static const UniChar beginLTR[] = { 0x202d, 0x20 };
+ static const UniChar beginRTL[] = { 0x202e, 0x20 };
+ static const UniChar endBidiWrap[] = { 0x20, 0x2e, 0x202c };
+
+ if (bidiWrap) {
+ startOffset = isRightToLeft ? ArrayLength(beginRTL)
+ : ArrayLength(beginLTR);
+ CFMutableStringRef mutableString =
+ ::CFStringCreateMutable(kCFAllocatorDefault,
+ length + startOffset +
+ ArrayLength(endBidiWrap));
+ ::CFStringAppendCharacters(mutableString,
+ isRightToLeft ? beginRTL : beginLTR,
+ startOffset);
+ ::CFStringAppendCharacters(mutableString, text, length);
+ ::CFStringAppendCharacters(mutableString, endBidiWrap,
+ ArrayLength(endBidiWrap));
+ stringObj = mutableString;
+ } else {
+ startOffset = 0;
+ stringObj =
+ ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
+ text, length,
+ kCFAllocatorNull);
+ }
+
+ // Get an attributes dictionary suitable for shaping text,
+ // creating it if necessary. (This dict is not LTR-specific,
+ // but we use that field to store it anyway.)
+ if (!mAttributesDictLTR) {
+ mAttributesDictLTR = CreateAttrDictWithoutDirection();
+ }
+ attrObj = mAttributesDictLTR;
+ }
+
+ CTFontRef tempCTFont = nullptr;
+ 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.
+ tempCTFont =
+ CreateCTFontWithFeatures(::CTFontGetSize(mCTFont),
+ aShapedText->DisableLigatures()
+ ? GetIndicDisableLigaturesDescriptor()
+ : GetIndicFeaturesDescriptor());
+ } else if (aShapedText->DisableLigatures()) {
+ // For letterspacing (or maybe other situations) we need to make
+ // a copy of the CTFont with the ligature feature disabled.
+ tempCTFont =
+ CreateCTFontWithFeatures(::CTFontGetSize(mCTFont),
+ GetDisableLigaturesDescriptor());
+ }
+
+ // For the disabled-ligature or buggy-indic-font case, we need to replace
+ // the standard CTFont in the attribute dictionary with a tweaked version.
+ CFMutableDictionaryRef mutableAttr = nullptr;
+ if (tempCTFont) {
+ mutableAttr = ::CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 2,
+ attrObj);
+ ::CFDictionaryReplaceValue(mutableAttr,
+ kCTFontAttributeName, tempCTFont);
+ // Having created the dict, we're finished with our temporary
+ // Indic and/or ligature-disabled CTFontRef.
+ ::CFRelease(tempCTFont);
+ 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.
+ // Note that this includes the bidi wrapper, so we have to be careful
+ // not to include the extra glyphs from there
+ bool success = true;
+ for (uint32_t runIndex = 0; runIndex < numRuns; runIndex++) {
+ CTRunRef aCTRun =
+ (CTRunRef)::CFArrayGetValueAtIndex(glyphRuns, runIndex);
+ // If the range is purely within bidi-wrapping text, ignore it.
+ CFRange range = ::CTRunGetStringRange(aCTRun);
+ if (uint32_t(range.location + range.length) <= startOffset ||
+ range.location - startOffset >= aLength) {
+ continue;
+ }
+ 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 - startOffset];
+ 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,
+ startOffset) != 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,
+ int32_t aStringOffset)
+{
+ // The word has been bidi-wrapped; aStringOffset is the number
+ // of chars at the beginning of the CTLine that we should skip.
+ // aCTRun is a glyph run from the CoreText layout process.
+
+ 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
+ // plus directional-override padding
+ // (d) the CTRun currently being processed, which may be a sub-run of the CoreText line
+ // (but may extend beyond the actual font run into the bidi wrapping text).
+ // aStringOffset tells us how many initial characters of the line to ignore.
+
+ // 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 - aStringOffset + stringRange.length <= 0 ||
+ stringRange.location - aStringOffset >= 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;
+ gfxShapedText::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 - aStringOffset + 1;
+ endCharIndex = charStart + stringRange.location - aStringOffset + 1;
+ } else {
+ while (charEnd < stringRange.length && charToGlyph[charEnd] == NO_GLYPH) {
+ charEnd++;
+ }
+ baseCharIndex = charStart + stringRange.location - aStringOffset;
+ endCharIndex = charEnd + stringRange.location - aStringOffset;
+ }
+
+ // 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 (1) {
+ gfxTextRun::DetailedGlyph *details = detailedGlyphs.AppendElement();
+ details->mGlyphID = glyphs[glyphStart];
+ details->mXOffset = 0;
+ details->mYOffset = -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);
+ }
+
+ gfxTextRun::CompressedGlyph textRunGlyph;
+ textRunGlyph.SetComplex(charGlyphs[baseCharIndex].IsClusterStart(),
+ true, detailedGlyphs.Length());
+ aShapedText->SetGlyphs(aOffset + baseCharIndex, textRunGlyph,
+ detailedGlyphs.Elements());
+
+ detailedGlyphs.Clear();
+ }
+
+ // the rest of the chars in the group are ligature continuations, no associated glyphs
+ while (++baseCharIndex != endCharIndex && baseCharIndex < wordLength) {
+ gfxShapedText::CompressedGlyph &shapedTextGlyph = charGlyphs[baseCharIndex];
+ NS_ASSERTION(!shapedTextGlyph.IsSimpleGlyph(), "overwriting a simple glyph");
+ shapedTextGlyph.SetComplex(inOrder && shapedTextGlyph.IsClusterStart(), false, 0);
+ }
+
+ 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 3 // 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::GetDefaultFeaturesDescriptor()
+{
+ if (sDefaultFeaturesDescriptor == nullptr) {
+ const std::pair<SInt16,SInt16> kDefaultFeatures[] = {
+ { kSmartSwashType, kLineInitialSwashesOffSelector },
+ { kSmartSwashType, kLineFinalSwashesOffSelector }
+ };
+ sDefaultFeaturesDescriptor =
+ CreateFontFeaturesDescriptor(kDefaultFeatures,
+ ArrayLength(kDefaultFeatures));
+ }
+ return sDefaultFeaturesDescriptor;
+}
+
+CTFontDescriptorRef
+gfxCoreTextShaper::GetDisableLigaturesDescriptor()
+{
+ if (sDisableLigaturesDescriptor == nullptr) {
+ const std::pair<SInt16,SInt16> kDisableLigatures[] = {
+ { kSmartSwashType, kLineInitialSwashesOffSelector },
+ { kSmartSwashType, kLineFinalSwashesOffSelector },
+ { kLigaturesType, kCommonLigaturesOffSelector }
+ };
+ sDisableLigaturesDescriptor =
+ CreateFontFeaturesDescriptor(kDisableLigatures,
+ ArrayLength(kDisableLigatures));
+ }
+ return sDisableLigaturesDescriptor;
+}
+
+CTFontDescriptorRef
+gfxCoreTextShaper::GetIndicFeaturesDescriptor()
+{
+ if (sIndicFeaturesDescriptor == nullptr) {
+ const std::pair<SInt16,SInt16> kIndicFeatures[] = {
+ { kSmartSwashType, kLineFinalSwashesOffSelector }
+ };
+ sIndicFeaturesDescriptor =
+ CreateFontFeaturesDescriptor(kIndicFeatures,
+ ArrayLength(kIndicFeatures));
+ }
+ return sIndicFeaturesDescriptor;
+}
+
+CTFontDescriptorRef
+gfxCoreTextShaper::GetIndicDisableLigaturesDescriptor()
+{
+ if (sIndicDisableLigaturesDescriptor == nullptr) {
+ const std::pair<SInt16,SInt16> kIndicDisableLigatures[] = {
+ { kSmartSwashType, kLineFinalSwashesOffSelector },
+ { kLigaturesType, kCommonLigaturesOffSelector }
+ };
+ sIndicDisableLigaturesDescriptor =
+ CreateFontFeaturesDescriptor(kIndicDisableLigatures,
+ ArrayLength(kIndicDisableLigatures));
+ }
+ return sIndicDisableLigaturesDescriptor;
+}
+
+CTFontRef
+gfxCoreTextShaper::CreateCTFontWithFeatures(CGFloat aSize,
+ CTFontDescriptorRef aDescriptor)
+{
+ gfxMacFont *f = static_cast<gfxMacFont*>(mFont);
+ return ::CTFontCreateWithGraphicsFont(f->GetCGFontRef(), aSize, nullptr,
+ aDescriptor);
+}
+
+void
+gfxCoreTextShaper::Shutdown() // [static]
+{
+ if (sIndicDisableLigaturesDescriptor != nullptr) {
+ ::CFRelease(sIndicDisableLigaturesDescriptor);
+ sIndicDisableLigaturesDescriptor = nullptr;
+ }
+ if (sIndicFeaturesDescriptor != nullptr) {
+ ::CFRelease(sIndicFeaturesDescriptor);
+ sIndicFeaturesDescriptor = nullptr;
+ }
+ if (sDisableLigaturesDescriptor != nullptr) {
+ ::CFRelease(sDisableLigaturesDescriptor);
+ sDisableLigaturesDescriptor = nullptr;
+ }
+ if (sDefaultFeaturesDescriptor != nullptr) {
+ ::CFRelease(sDefaultFeaturesDescriptor);
+ sDefaultFeaturesDescriptor = nullptr;
+ }
+}
diff --git a/gfx/thebes/gfxCoreTextShaper.h b/gfx/thebes/gfxCoreTextShaper.h
new file mode 100644
index 000000000..8e5d24f91
--- /dev/null
+++ b/gfx/thebes/gfxCoreTextShaper.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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();
+
+ virtual bool ShapeText(DrawTarget *aDrawTarget,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxShapedText *aShapedText);
+
+ // clean up static objects that may have been cached
+ static void Shutdown();
+
+protected:
+ CTFontRef mCTFont;
+
+ // 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,
+ int32_t aStringOffset);
+
+ 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 GetDefaultFeaturesDescriptor();
+ static CTFontDescriptorRef GetDisableLigaturesDescriptor();
+ static CTFontDescriptorRef GetIndicFeaturesDescriptor();
+ static CTFontDescriptorRef GetIndicDisableLigaturesDescriptor();
+
+ // cached font descriptor, created the first time it's needed
+ static CTFontDescriptorRef sDefaultFeaturesDescriptor;
+
+ // cached descriptor for adding disable-ligatures setting to a font
+ static CTFontDescriptorRef sDisableLigaturesDescriptor;
+
+ // feature descriptors for buggy Indic AAT font workaround
+ static CTFontDescriptorRef sIndicFeaturesDescriptor;
+ static CTFontDescriptorRef sIndicDisableLigaturesDescriptor;
+};
+
+#endif /* GFX_CORETEXTSHAPER_H */
diff --git a/gfx/thebes/gfxDWriteCommon.cpp b/gfx/thebes/gfxDWriteCommon.cpp
new file mode 100644
index 000000000..3047818bb
--- /dev/null
+++ b/gfx/thebes/gfxDWriteCommon.cpp
@@ -0,0 +1,182 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/gfx/Logging.h"
+
+static mozilla::Atomic<uint64_t> sNextFontFileKey;
+static std::unordered_map<uint64_t, IDWriteFontFileStream*> 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(FallibleTArray<uint8_t> *aData,
+ 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)()
+ {
+ NS_PRECONDITION(int32_t(mRefCnt) >= 0, "illegal refcnt");
+ ++mRefCnt;
+ return mRefCnt;
+ }
+
+ IFACEMETHOD_(ULONG, Release)()
+ {
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ --mRefCnt;
+ if (mRefCnt == 0) {
+ delete this;
+ return 0;
+ }
+ return mRefCnt;
+ }
+
+ // 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);
+
+private:
+ FallibleTArray<uint8_t> mData;
+ nsAutoRefCnt mRefCnt;
+ uint64_t mFontFileKey;
+};
+
+gfxDWriteFontFileStream::gfxDWriteFontFileStream(FallibleTArray<uint8_t> *aData,
+ uint64_t aFontFileKey)
+ : mFontFileKey(aFontFileKey)
+{
+ mData.SwapElements(*aData);
+}
+
+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;
+ }
+
+ 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(FallibleTArray<uint8_t>& aFontData,
+ IDWriteFontFile** aFontFile,
+ IDWriteFontFileStream** aFontFileStream)
+{
+ MOZ_ASSERT(aFontFile);
+ MOZ_ASSERT(aFontFileStream);
+
+ IDWriteFactory *factory = gfxWindowsPlatform::GetPlatform()->GetDWriteFactory();
+ if (!factory) {
+ gfxCriticalError() << "Failed to get DWrite Factory in CreateCustomFontFile.";
+ return E_FAIL;
+ }
+
+ uint64_t fontFileKey = sNextFontFileKey++;
+ RefPtr<IDWriteFontFileStream> ffsRef = new gfxDWriteFontFileStream(&aFontData, fontFileKey);
+ sFontFileStreams[fontFileKey] = ffsRef;
+
+ 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;
+}
diff --git a/gfx/thebes/gfxDWriteCommon.h b/gfx/thebes/gfxDWriteCommon.h
new file mode 100644
index 000000000..a355b59c6
--- /dev/null
+++ b/gfx/thebes/gfxDWriteCommon.h
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "nscore.h"
+#include "nsIServiceManager.h"
+#include "nsCOMPtr.h"
+#include "cairo-features.h"
+#include "gfxFontConstants.h"
+#include "nsTArray.h"
+#include "gfxWindowsPlatform.h"
+#include "nsIUUIDGenerator.h"
+
+#include <windows.h>
+#include <dwrite.h>
+
+static inline DWRITE_FONT_STRETCH
+DWriteFontStretchFromStretch(int16_t aStretch)
+{
+ switch (aStretch) {
+ case NS_FONT_STRETCH_ULTRA_CONDENSED:
+ return DWRITE_FONT_STRETCH_ULTRA_CONDENSED;
+ case NS_FONT_STRETCH_EXTRA_CONDENSED:
+ return DWRITE_FONT_STRETCH_EXTRA_CONDENSED;
+ case NS_FONT_STRETCH_CONDENSED:
+ return DWRITE_FONT_STRETCH_CONDENSED;
+ case NS_FONT_STRETCH_SEMI_CONDENSED:
+ return DWRITE_FONT_STRETCH_SEMI_CONDENSED;
+ case NS_FONT_STRETCH_NORMAL:
+ return DWRITE_FONT_STRETCH_NORMAL;
+ case NS_FONT_STRETCH_SEMI_EXPANDED:
+ return DWRITE_FONT_STRETCH_SEMI_EXPANDED;
+ case NS_FONT_STRETCH_EXPANDED:
+ return DWRITE_FONT_STRETCH_EXPANDED;
+ case NS_FONT_STRETCH_EXTRA_EXPANDED:
+ return DWRITE_FONT_STRETCH_EXTRA_EXPANDED;
+ case NS_FONT_STRETCH_ULTRA_EXPANDED:
+ return DWRITE_FONT_STRETCH_ULTRA_EXPANDED;
+ default:
+ return DWRITE_FONT_STRETCH_UNDEFINED;
+ }
+}
+
+static inline int16_t
+FontStretchFromDWriteStretch(DWRITE_FONT_STRETCH aStretch)
+{
+ switch (aStretch) {
+ case DWRITE_FONT_STRETCH_ULTRA_CONDENSED:
+ return NS_FONT_STRETCH_ULTRA_CONDENSED;
+ case DWRITE_FONT_STRETCH_EXTRA_CONDENSED:
+ return NS_FONT_STRETCH_EXTRA_CONDENSED;
+ case DWRITE_FONT_STRETCH_CONDENSED:
+ return NS_FONT_STRETCH_CONDENSED;
+ case DWRITE_FONT_STRETCH_SEMI_CONDENSED:
+ return NS_FONT_STRETCH_SEMI_CONDENSED;
+ case DWRITE_FONT_STRETCH_NORMAL:
+ return NS_FONT_STRETCH_NORMAL;
+ case DWRITE_FONT_STRETCH_SEMI_EXPANDED:
+ return NS_FONT_STRETCH_SEMI_EXPANDED;
+ case DWRITE_FONT_STRETCH_EXPANDED:
+ return NS_FONT_STRETCH_EXPANDED;
+ case DWRITE_FONT_STRETCH_EXTRA_EXPANDED:
+ return NS_FONT_STRETCH_EXTRA_EXPANDED;
+ case DWRITE_FONT_STRETCH_ULTRA_EXPANDED:
+ return NS_FONT_STRETCH_ULTRA_EXPANDED;
+ default:
+ return NS_FONT_STRETCH_NORMAL;
+ }
+}
+
+struct ffReferenceKey
+{
+ FallibleTArray<uint8_t> *mArray;
+ nsID mGUID;
+};
+
+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();
+ gfxWindowsPlatform::GetPlatform()->GetDWriteFactory()->
+ RegisterFontFileLoader(mInstance);
+ }
+ return mInstance;
+ }
+
+ /**
+ * Creates a IDWriteFontFile and IDWriteFontFileStream from aFontData.
+ * aFontData will be empty on return as it swaps out the data.
+ *
+ * @param aFontData the font data for the custom font file
+ * @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(FallibleTArray<uint8_t>& aFontData,
+ IDWriteFontFile** aFontFile,
+ IDWriteFontFileStream** aFontFileStream);
+
+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 000000000..d159fddb1
--- /dev/null
+++ b/gfx/thebes/gfxDWriteFontList.cpp
@@ -0,0 +1,1838 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/MemoryReporting.h"
+
+#include "gfxDWriteFontList.h"
+#include "gfxDWriteFonts.h"
+#include "nsUnicharUtils.h"
+#include "nsILocaleService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Telemetry.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsISimpleEnumerator.h"
+
+#include "gfxGDIFontList.h"
+
+#include "nsIWindowsRegKey.h"
+
+#include "harfbuzz/hb.h"
+
+using namespace mozilla;
+
+#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(nsAString &aName)
+{
+ if (aName.Length() >= LF_FACESIZE)
+ aName.Truncate(LF_FACESIZE - 1);
+ ToLowerCase(aName);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// gfxDWriteFontFamily
+
+gfxDWriteFontFamily::~gfxDWriteFontFamily()
+{
+}
+
+static HRESULT
+GetDirectWriteFontName(IDWriteFont *aFont, nsAString& aFontName)
+{
+ HRESULT hr;
+
+ RefPtr<IDWriteLocalizedStrings> names;
+ hr = aFont->GetFaceNames(getter_AddRefs(names));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ BOOL exists;
+ AutoTArray<wchar_t,32> faceName;
+ UINT32 englishIdx = 0;
+ hr = names->FindLocaleName(L"en-us", &englishIdx, &exists);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ if (!exists) {
+ // No english found, use whatever is first in the list.
+ englishIdx = 0;
+ }
+ UINT32 length;
+ hr = names->GetStringLength(englishIdx, &length);
+ if (FAILED(hr)) {
+ return hr;
+ }
+ faceName.SetLength(length + 1);
+ hr = names->GetString(englishIdx, faceName.Elements(), length + 1);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ aFontName.Assign(faceName.Elements());
+ 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,
+ nsAString& aFontName)
+{
+ HRESULT hr;
+
+ BOOL exists;
+ RefPtr<IDWriteLocalizedStrings> infostrings;
+ hr = aFont->GetInformationalStrings(aWhichName, getter_AddRefs(infostrings), &exists);
+ if (FAILED(hr) || !exists) {
+ return E_FAIL;
+ }
+
+ AutoTArray<wchar_t,32> faceName;
+ UINT32 englishIdx = 0;
+ hr = infostrings->FindLocaleName(L"en-us", &englishIdx, &exists);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ if (!exists) {
+ // No english found, use whatever is first in the list.
+ englishIdx = 0;
+ }
+ UINT32 length;
+ hr = infostrings->GetStringLength(englishIdx, &length);
+ if (FAILED(hr)) {
+ return hr;
+ }
+ faceName.SetLength(length + 1);
+ hr = infostrings->GetString(englishIdx, faceName.Elements(), length + 1);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ aFontName.Assign(faceName.Elements());
+ return S_OK;
+}
+
+void
+gfxDWriteFontFamily::FindStyleVariations(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
+ nsString fullID(mName);
+ nsAutoString faceName;
+ hr = GetDirectWriteFontName(font, faceName);
+ if (FAILED(hr)) {
+ continue;
+ }
+ fullID.Append(' ');
+ fullID.Append(faceName);
+
+ gfxDWriteFontEntry *fe = new gfxDWriteFontEntry(fullID, font);
+ fe->SetForceGDIClassic(mForceGDIClassic);
+ AddFontEntry(fe);
+
+ // postscript/fullname if needed
+ nsAutoString 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()) {
+ LOG_FONTLIST(("(fontlist) added (%s) to family (%s)"
+ " with style: %s weight: %d stretch: %d psname: %s fullname: %s",
+ NS_ConvertUTF16toUTF8(fe->Name()).get(),
+ NS_ConvertUTF16toUTF8(Name()).get(),
+ (fe->IsItalic()) ?
+ "italic" : (fe->IsOblique() ? "oblique" : "normal"),
+ fe->Weight(), fe->Stretch(),
+ NS_ConvertUTF16toUTF8(psname).get(),
+ NS_ConvertUTF16toUTF8(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();
+ }
+}
+
+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(nsAString &aLocalizedName)
+{
+ aLocalizedName.AssignLiteral("Unknown Font");
+ HRESULT hr;
+ nsresult rv;
+ nsCOMPtr<nsILocaleService> ls = do_GetService(NS_LOCALESERVICE_CONTRACTID,
+ &rv);
+ nsCOMPtr<nsILocale> locale;
+ rv = ls->GetApplicationLocale(getter_AddRefs(locale));
+ nsString localeName;
+ if (NS_SUCCEEDED(rv)) {
+ rv = locale->GetCategory(NS_LITERAL_STRING(NSILOCALE_MESSAGE),
+ localeName);
+ }
+ if (NS_FAILED(rv)) {
+ localeName.AssignLiteral("en-us");
+ }
+
+ RefPtr<IDWriteLocalizedStrings> names;
+
+ hr = mDWFamily->GetFamilyNames(getter_AddRefs(names));
+ if (FAILED(hr)) {
+ return;
+ }
+ UINT32 idx = 0;
+ BOOL exists;
+ hr = names->FindLocaleName(localeName.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 = nsDependentString(famName.Elements());
+}
+
+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
+
+gfxDWriteFontEntry::~gfxDWriteFontEntry()
+{
+}
+
+bool
+gfxDWriteFontEntry::IsSymbolFont()
+{
+ if (mFont) {
+ return mFont->IsSymbolFont();
+ } else {
+ return false;
+ }
+}
+
+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 && pFontList->UseGDIFontTableAccess() &&
+ !(mStyle && 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)
+{
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS);
+
+ // attempt this once, if errors occur leave a blank cmap
+ if (mCharacterMap) {
+ return NS_OK;
+ }
+
+ RefPtr<gfxCharacterMap> charmap;
+ nsresult rv;
+ bool symbolFont;
+
+ if (aFontInfoData && (charmap = GetCMAPFromFontInfo(aFontInfoData,
+ mUVSOffset,
+ symbolFont))) {
+ rv = NS_OK;
+ } else {
+ uint32_t kCMAP = TRUETYPE_TAG('c','m','a','p');
+ charmap = new gfxCharacterMap();
+ AutoTable cmapTable(this, kCMAP);
+
+ if (cmapTable) {
+ bool unicodeFont = false, symbolFont = false; // currently ignored
+ uint32_t cmapLen;
+ const uint8_t* cmapData =
+ reinterpret_cast<const uint8_t*>(hb_blob_get_data(cmapTable,
+ &cmapLen));
+ rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen,
+ *charmap, mUVSOffset,
+ unicodeFont, symbolFont);
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ mHasCmapTable = NS_SUCCEEDED(rv);
+ if (mHasCmapTable) {
+ // 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();
+ mCharacterMap = pfl->FindCharMap(charmap);
+ } else {
+ // if error occurred, initialize to null cmap
+ mCharacterMap = new gfxCharacterMap();
+ }
+
+ LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %d hash: %8.8x%s\n",
+ NS_ConvertUTF16toUTF8(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",
+ NS_ConvertUTF16toUTF8(mName).get());
+ charmap->Dump(prefix, eGfxLog_cmapdata);
+ }
+
+ return rv;
+}
+
+gfxFont *
+gfxDWriteFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle,
+ bool aNeedsBold)
+{
+ return new gfxDWriteFont(this, aFontStyle, aNeedsBold);
+}
+
+nsresult
+gfxDWriteFontEntry::CreateFontFace(IDWriteFontFace **aFontFace,
+ DWRITE_FONT_SIMULATIONS aSimulations)
+{
+ // 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 = gfxWindowsPlatform::GetPlatform()->GetDWriteFactory()->
+ CreateFontFace(mFaceType,
+ 1,
+ &fontFile,
+ 0,
+ DWRITE_FONT_SIMULATIONS_NONE,
+ getter_AddRefs(mFontFace));
+ } else {
+ NS_NOTREACHED("invalid font entry");
+ return NS_ERROR_FAILURE;
+ }
+ if (FAILED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // check whether we need to add a DWrite simulated style
+ if ((aSimulations & DWRITE_FONT_SIMULATIONS_BOLD) &&
+ !(mFontFace->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD)) {
+ // if so, we need to return not mFontFace itself but a version that
+ // has the Bold simulation - unfortunately, 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 = gfxWindowsPlatform::GetPlatform()->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;
+ }
+
+ // no simulation: we can just add a reference to mFontFace and return that
+ *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');
+ AutoTArray<uint8_t, 128> buffer;
+ if (CopyFontTable(kOS2Tag, buffer) != NS_OK) {
+ return mIsCJK;
+ }
+
+ // 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 (buffer.Length() >= offsetof(OS2Table, sxHeight)) {
+ const OS2Table* os2 =
+ reinterpret_cast<const OS2Table*>(buffer.Elements());
+ 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)
+{
+}
+
+// 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.
+
+gfxFontFamily *
+gfxDWriteFontList::GetDefaultFontForPlatform(const gfxFontStyle *aStyle)
+{
+ nsAutoString resolvedName;
+
+ // try Arial first
+ gfxFontFamily *ff;
+ if ((ff = FindFamily(NS_LITERAL_STRING("Arial")))) {
+ 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(nsDependentString(ncm.lfMessageFont.lfFaceName));
+ if (ff) {
+ return ff;
+ }
+ }
+
+ return nullptr;
+}
+
+gfxFontEntry *
+gfxDWriteFontList::LookupLocalFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle)
+{
+ gfxFontEntry *lookup;
+
+ lookup = LookupInFaceNameLists(aFontName);
+ if (!lookup) {
+ return nullptr;
+ }
+
+ gfxDWriteFontEntry* dwriteLookup = static_cast<gfxDWriteFontEntry*>(lookup);
+ gfxDWriteFontEntry *fe =
+ new gfxDWriteFontEntry(lookup->Name(),
+ dwriteLookup->mFont,
+ aWeight,
+ aStretch,
+ aStyle);
+ fe->SetForceGDIClassic(dwriteLookup->GetForceGDIClassic());
+ return fe;
+}
+
+gfxFontEntry *
+gfxDWriteFontList::MakePlatformFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t* aFontData,
+ uint32_t aLength)
+{
+ nsresult rv;
+ nsAutoString uniqueName;
+ rv = gfxFontUtils::MakeUniqueUserFontName(uniqueName);
+ if (NS_FAILED(rv)) {
+ free((void*)aFontData);
+ return nullptr;
+ }
+
+ FallibleTArray<uint8_t> newFontData;
+
+ rv = gfxFontUtils::RenameFont(uniqueName, aFontData, aLength, &newFontData);
+ free((void*)aFontData);
+
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ RefPtr<IDWriteFontFileStream> fontFileStream;
+ RefPtr<IDWriteFontFile> fontFile;
+ HRESULT hr =
+ gfxDWriteFontFileLoader::CreateCustomFontFile(newFontData,
+ getter_AddRefs(fontFile),
+ getter_AddRefs(fontFileStream));
+
+ if (FAILED(hr)) {
+ NS_WARNING("Failed to create custom font file reference.");
+ return nullptr;
+ }
+
+ BOOL isSupported;
+ DWRITE_FONT_FILE_TYPE fileType;
+ UINT32 numFaces;
+
+ gfxDWriteFontEntry *entry =
+ new gfxDWriteFontEntry(uniqueName,
+ fontFile,
+ fontFileStream,
+ aWeight,
+ aStretch,
+ aStyle);
+
+ fontFile->Analyze(&isSupported, &fileType, &entry->mFaceType, &numFaces);
+ if (!isSupported || numFaces > 1) {
+ // We don't know how to deal with 0 faces either.
+ delete entry;
+ return nullptr;
+ }
+
+ return entry;
+}
+
+enum DWriteInitError {
+ errGDIInterop = 1,
+ errSystemFontCollection = 2,
+ errNoFonts = 3
+};
+
+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();
+
+ hr = gfxWindowsPlatform::GetPlatform()->GetDWriteFactory()->
+ 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
+
+ RefPtr<IDWriteFactory> factory =
+ gfxWindowsPlatform::GetPlatform()->GetDWriteFactory();
+
+ hr = factory->GetSystemFontCollection(getter_AddRefs(mSystemFonts));
+ NS_ASSERTION(SUCCEEDED(hr), "GetSystemFontCollection failed!");
+
+ if (FAILED(hr)) {
+ Telemetry::Accumulate(Telemetry::DWRITEFONT_INIT_PROBLEM,
+ uint32_t(errSystemFontCollection));
+ return NS_ERROR_FAILURE;
+ }
+
+ 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() != 0,
+ "no fonts found in the system fontlist -- holy crap batman!");
+ if (mFontFamilies.Count() == 0) {
+ Telemetry::Accumulate(Telemetry::DWRITEFONT_INIT_PROBLEM,
+ uint32_t(errNoFonts));
+ return NS_ERROR_FAILURE;
+ }
+
+ QueryPerformanceCounter(&t4); // iterate over system fonts
+
+#ifdef MOZ_BUNDLED_FONTS
+ mBundledFonts = CreateBundledFontsCollection(factory);
+ if (mBundledFonts) {
+ GetFontsFromCollection(mBundledFonts);
+ }
+#endif
+
+ 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
+
+ nsAutoString nameGillSans(L"Gill Sans");
+ nsAutoString nameGillSansMT(L"Gill Sans MT");
+ BuildKeyNameFromFontName(nameGillSans);
+ BuildKeyNameFromFontName(nameGillSansMT);
+
+ gfxFontFamily *gillSansFamily = mFontFamilies.GetWeak(nameGillSans);
+ gfxFontFamily *gillSansMTFamily = mFontFamilies.GetWeak(nameGillSansMT);
+
+ if (gillSansFamily && gillSansMTFamily) {
+ gillSansFamily->FindStyleVariations();
+ nsTArray<RefPtr<gfxFontEntry> >& faces = gillSansFamily->GetFontList();
+ uint32_t i;
+
+ bool allUltraBold = true;
+ for (i = 0; i < faces.Length(); i++) {
+ // does the face have 'Ultra Bold' in the name?
+ if (faces[i]->Name().Find(NS_LITERAL_STRING("Ultra Bold")) == -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 (i = 0; i < faces.Length(); i++) {
+ // change the entry's family name to match its adoptive family
+ faces[i]->mFamilyName = gillSansMTFamily->Name();
+ gillSansMTFamily->AddFontEntry(faces[i]);
+
+ if (LOG_FONTLIST_ENABLED()) {
+ gfxFontEntry *fe = faces[i];
+ LOG_FONTLIST(("(fontlist) moved (%s) to family (%s)"
+ " with style: %s weight: %d stretch: %d",
+ NS_ConvertUTF16toUTF8(fe->Name()).get(),
+ NS_ConvertUTF16toUTF8(gillSansMTFamily->Name()).get(),
+ (fe->IsItalic()) ?
+ "italic" : (fe->IsOblique() ? "oblique" : "normal"),
+ fe->Weight(), fe->Stretch()));
+ }
+ }
+
+ // remove Gills Sans
+ mFontFamilies.Remove(nameGillSans);
+ }
+ }
+
+ nsAdoptingCString classicFamilies =
+ Preferences::GetCString("gfx.font_rendering.cleartype_params.force_gdi_classic_for_families");
+ if (classicFamilies) {
+ nsCCharSeparatedTokenizer tokenizer(classicFamilies, ',');
+ while (tokenizer.hasMoreTokens()) {
+ NS_ConvertUTF8toUTF16 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;
+ }
+
+ UINT32 englishIdx = 0;
+
+ BOOL exists;
+ hr = names->FindLocaleName(L"en-us", &englishIdx, &exists);
+ if (FAILED(hr)) {
+ continue;
+ }
+ if (!exists) {
+ // Use 0 index if english is not found.
+ englishIdx = 0;
+ }
+
+ AutoTArray<WCHAR, 32> enName;
+ UINT32 length;
+
+ hr = names->GetStringLength(englishIdx, &length);
+ if (FAILED(hr)) {
+ continue;
+ }
+
+ if (!enName.SetLength(length + 1, fallible)) {
+ // Eeep - running out of memory. Unlikely to end well.
+ continue;
+ }
+
+ hr = names->GetString(englishIdx, enName.Elements(), length + 1);
+ if (FAILED(hr)) {
+ continue;
+ }
+
+ nsAutoString name(enName.Elements());
+ BuildKeyNameFromFontName(name);
+
+ RefPtr<gfxFontFamily> fam;
+
+ if (mFontFamilies.GetWeak(name)) {
+ continue;
+ }
+
+ nsDependentString familyName(enName.Elements());
+
+ fam = new gfxDWriteFontFamily(familyName, family);
+ if (!fam) {
+ continue;
+ }
+
+ if (mBadUnderlineFamilyNames.Contains(name)) {
+ fam->SetBadUnderlineFamily();
+ }
+ mFontFamilies.Put(name, fam);
+
+ // now add other family name localizations, if present
+ uint32_t nameCount = names->GetCount();
+ uint32_t nameIndex;
+
+ 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;
+ }
+
+ nsDependentString locName(localizedName.Elements());
+
+ if (!familyName.Equals(locName)) {
+ AddOtherFamilyName(fam, locName);
+ }
+
+ }
+
+ // at this point, all family names have been read in
+ fam->SetOtherFamilyNamesInitialized();
+ }
+}
+
+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
+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;
+ }
+
+ nsAutoString substituteName((char16_t*) aliasName);
+ nsAutoString actualFontName((char16_t*) actualName);
+ RemoveCharsetFromFontSubstitute(substituteName);
+ BuildKeyNameFromFontName(substituteName);
+ RemoveCharsetFromFontSubstitute(actualFontName);
+ BuildKeyNameFromFontName(actualFontName);
+ gfxFontFamily *ff;
+ if (!actualFontName.IsEmpty() &&
+ (ff = mFontFamilies.GetWeak(actualFontName))) {
+ mFontSubstitutes.Put(substituteName, ff);
+ } else {
+ mNonExistingFonts.AppendElement(substituteName);
+ }
+ }
+ return NS_OK;
+}
+
+struct FontSubstitution {
+ const WCHAR* aliasName;
+ const WCHAR* actualName;
+};
+
+static const FontSubstitution sDirectWriteSubs[] = {
+ { L"MS Sans Serif", L"Microsoft Sans Serif" },
+ { L"MS Serif", L"Times New Roman" },
+ { L"Courier", L"Courier New" },
+ { L"Small Fonts", L"Arial" },
+ { L"Roman", L"Times New Roman" },
+ { L"Script", L"Mistral" }
+};
+
+void
+gfxDWriteFontList::GetDirectWriteSubstitutes()
+{
+ for (uint32_t i = 0; i < ArrayLength(sDirectWriteSubs); ++i) {
+ const FontSubstitution& sub(sDirectWriteSubs[i]);
+ nsAutoString substituteName((char16_t*)sub.aliasName);
+ BuildKeyNameFromFontName(substituteName);
+ if (nullptr != mFontFamilies.GetWeak(substituteName)) {
+ // don't do the substitution if user actually has a usable font
+ // with this name installed
+ continue;
+ }
+ nsAutoString actualFontName((char16_t*)sub.actualName);
+ BuildKeyNameFromFontName(actualFontName);
+ gfxFontFamily *ff;
+ if (nullptr != (ff = mFontFamilies.GetWeak(actualFontName))) {
+ mFontSubstitutes.Put(substituteName, ff);
+ } else {
+ mNonExistingFonts.AppendElement(substituteName);
+ }
+ }
+}
+
+bool
+gfxDWriteFontList::GetStandardFamilyName(const nsAString& aFontName,
+ nsAString& aFamilyName)
+{
+ gfxFontFamily *family = FindFamily(aFontName);
+ if (family) {
+ family->LocalizedName(aFamilyName);
+ return true;
+ }
+
+ return false;
+}
+
+bool
+gfxDWriteFontList::FindAndAddFamilies(const nsAString& aFamily,
+ nsTArray<gfxFontFamily*>* aOutput,
+ gfxFontStyle* aStyle,
+ gfxFloat aDevToCssSize)
+{
+ nsAutoString keyName(aFamily);
+ BuildKeyNameFromFontName(keyName);
+
+ gfxFontFamily *ff = mFontSubstitutes.GetWeak(keyName);
+ if (ff) {
+ aOutput->AppendElement(ff);
+ return true;
+ }
+
+ if (mNonExistingFonts.Contains(keyName)) {
+ return false;
+ }
+
+ return gfxPlatformFontList::FindAndAddFamilies(aFamily, aOutput, aStyle,
+ aDevToCssSize);
+}
+
+void
+gfxDWriteFontList::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const
+{
+ gfxPlatformFontList::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+
+ 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, nsString& 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;
+ }
+
+ UINT32 index = 0;
+ BOOL exists = false;
+
+ hr = familyNames->FindLocaleName(L"en-us", &index, &exists);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ // If the specified locale doesn't exist, select the first on the list.
+ if (!exists) {
+ index = 0;
+ }
+
+ AutoTArray<WCHAR, 32> name;
+ UINT32 length;
+
+ hr = familyNames->GetStringLength(index, &length);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ if (!name.SetLength(length + 1, fallible)) {
+ return E_FAIL;
+ }
+ hr = familyNames->GetString(index, name.Elements(), length + 1);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ aFamilyName.Assign(name.Elements());
+ 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(const uint32_t aCh,
+ Script aRunScript,
+ const gfxFontStyle* aMatchStyle,
+ gfxFontFamily** aMatchedFamily)
+{
+ HRESULT hr;
+
+ RefPtr<IDWriteFactory> dwFactory =
+ gfxWindowsPlatform::GetPlatform()->GetDWriteFactory();
+ if (!dwFactory) {
+ return nullptr;
+ }
+
+ // initialize fallback renderer
+ if (!mFallbackRenderer) {
+ mFallbackRenderer = new DWriteFontFallbackRenderer(dwFactory);
+ }
+
+ // 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
+ hr = fallbackLayout->Draw(nullptr, mFallbackRenderer, 50.0f, 50.0f);
+ if (FAILED(hr)) {
+ return nullptr;
+ }
+
+ gfxFontFamily *family = FindFamily(mFallbackRenderer->FallbackFamilyName());
+ if (family) {
+ gfxFontEntry *fontEntry;
+ bool needsBold; // ignored in the system fallback case
+ fontEntry = family->FindFontForStyle(*aMatchStyle, needsBold);
+ 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() {}
+
+ // loads font data for all members of a given family
+ virtual void LoadFontFamilyData(const nsAString& aFamilyName);
+
+private:
+ RefPtr<IDWriteFontCollection> mSystemFonts;
+#ifdef MOZ_BUNDLED_FONTS
+ RefPtr<IDWriteFontCollection> mBundledFonts;
+#endif
+};
+
+void
+DirectWriteFontInfo::LoadFontFamilyData(const nsAString& aFamilyName)
+{
+ // lookup the family
+ AutoTArray<wchar_t, 32> famName;
+
+ uint32_t len = aFamilyName.Length();
+ if(!famName.SetLength(len + 1, fallible)) {
+ return;
+ }
+ memcpy(famName.Elements(), aFamilyName.BeginReading(), len * sizeof(char16_t));
+ famName[len] = 0;
+
+ HRESULT hr;
+ BOOL exists = false;
+
+ uint32_t index;
+ RefPtr<IDWriteFontFamily> family;
+ hr = mSystemFonts->FindFamilyName(famName.Elements(), &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(famName.Elements(), &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
+ nsString fullID(aFamilyName);
+ nsAutoString 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) {
+ gfxFontUtils::ReadCanonicalName(nameData, nameSize,
+ gfxFontUtils::NAME_ID_FULL,
+ fontData.mFullName);
+ gfxFontUtils::ReadCanonicalName(nameData, nameSize,
+ gfxFontUtils::NAME_ID_POSTSCRIPT,
+ fontData.mPostscriptName);
+ 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)) {
+ bool cmapLoaded = false;
+ bool unicodeFont = false, symbolFont = false;
+ RefPtr<gfxCharacterMap> charmap = new gfxCharacterMap();
+ uint32_t offset;
+
+ if (cmapData &&
+ cmapSize > 0 &&
+ NS_SUCCEEDED(
+ gfxFontUtils::ReadCMAP(cmapData, cmapSize, *charmap,
+ offset, unicodeFont, symbolFont))) {
+ fontData.mCharacterMap = charmap;
+ fontData.mUVSOffset = offset;
+ fontData.mSymbolFont = symbolFont;
+ cmapLoaded = true;
+ mLoadStats.cmaps++;
+ }
+ dwFontFace->ReleaseFontTable(ctx);
+ haveData = haveData || cmapLoaded;
+ }
+ }
+
+ // if have data, load
+ if (haveData) {
+ mFontFaceData.Put(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();
+}
+
+
+#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() {}
+
+ RefPtr<IDWriteFactory> mFactory;
+
+ nsCOMPtr<nsIFile> mFontDir;
+ nsCOMPtr<nsISimpleEnumerator> 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() { }
+};
+
+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(NS_LITERAL_STRING("fonts")))) {
+ 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 000000000..6bf094afa
--- /dev/null
+++ b/gfx/thebes/gfxDWriteFontList.h
@@ -0,0 +1,445 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/MemoryReporting.h"
+#include "gfxDWriteCommon.h"
+
+#include "gfxFont.h"
+#include "gfxUserFontSet.h"
+#include "cairo-win32.h"
+
+#include "gfxPlatformFontList.h"
+#include "gfxPlatform.h"
+#include <algorithm>
+
+
+/**
+ * gfxDWriteFontFamily is a class that describes one of the fonts on the
+ * users system. It holds each gfxDWriteFontEntry (maps more directly to
+ * a font face) which holds font type, charset info and character map info.
+ */
+class gfxDWriteFontEntry;
+
+/**
+ * \brief Class representing directwrite font family.
+ */
+class gfxDWriteFontFamily : public gfxFontFamily
+{
+public:
+ /**
+ * Constructs a new DWriteFont Family.
+ *
+ * \param aName Name identifying the family
+ * \param aFamily IDWriteFontFamily object representing the directwrite
+ * family object.
+ */
+ gfxDWriteFontFamily(const nsAString& aName,
+ IDWriteFontFamily *aFamily)
+ : gfxFontFamily(aName), mDWFamily(aFamily), mForceGDIClassic(false) {}
+ virtual ~gfxDWriteFontFamily();
+
+ void FindStyleVariations(FontInfoData *aFontInfoData = nullptr) final;
+
+ void LocalizedName(nsAString& 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;
+
+protected:
+ /** This font family's directwrite fontfamily object */
+ RefPtr<IDWriteFontFamily> mDWFamily;
+ bool mForceGDIClassic;
+};
+
+/**
+ * \brief Class representing DirectWrite FontEntry (a unique font style/family)
+ */
+class gfxDWriteFontEntry : public gfxFontEntry
+{
+public:
+ /**
+ * Constructs a font entry.
+ *
+ * \param aFaceName The name of the corresponding font face.
+ * \param aFont DirectWrite font object
+ */
+ gfxDWriteFontEntry(const nsAString& aFaceName,
+ IDWriteFont *aFont)
+ : gfxFontEntry(aFaceName), mFont(aFont), mFontFile(nullptr),
+ mForceGDIClassic(false)
+ {
+ DWRITE_FONT_STYLE dwriteStyle = aFont->GetStyle();
+ mStyle = (dwriteStyle == DWRITE_FONT_STYLE_ITALIC ?
+ NS_FONT_STYLE_ITALIC :
+ (dwriteStyle == DWRITE_FONT_STYLE_OBLIQUE ?
+ NS_FONT_STYLE_OBLIQUE : NS_FONT_STYLE_NORMAL));
+ mStretch = FontStretchFromDWriteStretch(aFont->GetStretch());
+ uint16_t weight = NS_ROUNDUP(aFont->GetWeight() - 50, 100);
+
+ weight = std::max<uint16_t>(100, weight);
+ weight = std::min<uint16_t>(900, weight);
+ mWeight = 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 nsAString& aFaceName,
+ IDWriteFont *aFont,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle)
+ : gfxFontEntry(aFaceName), mFont(aFont), mFontFile(nullptr),
+ mForceGDIClassic(false)
+ {
+ mWeight = aWeight;
+ mStretch = aStretch;
+ mStyle = 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 nsAString& aFaceName,
+ IDWriteFontFile *aFontFile,
+ IDWriteFontFileStream *aFontFileStream,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle)
+ : gfxFontEntry(aFaceName), mFont(nullptr), mFontFile(aFontFile),
+ mFontFileStream(aFontFileStream), mForceGDIClassic(false)
+ {
+ mWeight = aWeight;
+ mStretch = aStretch;
+ mStyle = aStyle;
+ mIsDataUserFont = true;
+ mIsCJK = UNINITIALIZED_VALUE;
+ }
+
+ virtual ~gfxDWriteFontEntry();
+
+ virtual bool IsSymbolFont();
+
+ virtual hb_blob_t* GetFontTable(uint32_t aTableTag) override;
+
+ nsresult ReadCMAP(FontInfoData *aFontInfoData = nullptr);
+
+ bool IsCJKFont();
+
+ void SetForceGDIClassic(bool aForce) { mForceGDIClassic = aForce; }
+ bool GetForceGDIClassic() { return mForceGDIClassic; }
+
+ virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const;
+ virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const;
+
+protected:
+ friend class gfxDWriteFont;
+ friend class gfxDWriteFontList;
+
+ virtual nsresult CopyFontTable(uint32_t aTableTag,
+ nsTArray<uint8_t>& aBuffer) override;
+
+ virtual gfxFont *CreateFontInstance(const gfxFontStyle *aFontStyle,
+ bool aNeedsBold);
+
+ nsresult CreateFontFace(
+ IDWriteFontFace **aFontFace,
+ DWRITE_FONT_SIMULATIONS aSimulations = DWRITE_FONT_SIMULATIONS_NONE);
+
+ 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;
+
+ DWRITE_FONT_FACE_TYPE mFaceType;
+
+ int8_t mIsCJK;
+ bool mForceGDIClassic;
+};
+
+// custom text renderer used to determine the fallback font for a given char
+class DWriteFontFallbackRenderer final : public IDWriteTextRenderer
+{
+public:
+ DWriteFontFallbackRenderer(IDWriteFactory *aFactory)
+ : mRefCount(0)
+ {
+ HRESULT hr = S_OK;
+
+ hr = aFactory->GetSystemFontCollection(getter_AddRefs(mSystemFonts));
+ NS_ASSERTION(SUCCEEDED(hr), "GetSystemFontCollection failed!");
+ }
+
+ ~DWriteFontFallbackRenderer()
+ {}
+
+ // 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 nsString& FallbackFamilyName() { return mFamilyName; }
+
+protected:
+ long mRefCount;
+ RefPtr<IDWriteFontCollection> mSystemFonts;
+ nsString mFamilyName;
+};
+
+
+
+class gfxDWriteFontList : public gfxPlatformFontList {
+public:
+ gfxDWriteFontList();
+
+ static gfxDWriteFontList* PlatformFontList() {
+ return static_cast<gfxDWriteFontList*>(sPlatformFontList);
+ }
+
+ // initialize font lists
+ virtual nsresult InitFontListForPlatform() override;
+
+ virtual gfxFontEntry* LookupLocalFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle);
+
+ virtual gfxFontEntry* MakePlatformFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t* aFontData,
+ uint32_t aLength);
+
+ bool GetStandardFamilyName(const nsAString& aFontName,
+ nsAString& aFamilyName);
+
+ IDWriteGdiInterop *GetGDIInterop() { return mGDIInterop; }
+ bool UseGDIFontTableAccess() { return mGDIFontTableAccess; }
+
+ bool FindAndAddFamilies(const nsAString& aFamily,
+ nsTArray<gfxFontFamily*>* aOutput,
+ gfxFontStyle* aStyle = nullptr,
+ gfxFloat aDevToCssSize = 1.0) override;
+
+ gfxFloat GetForceGDIClassicMaxFontSize() { return mForceGDIClassicMaxFontSize; }
+
+ virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const;
+ virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const;
+
+protected:
+ virtual gfxFontFamily*
+ GetDefaultFontForPlatform(const gfxFontStyle* aStyle) override;
+
+ // attempt to use platform-specific fallback for the given character,
+ // return null if no usable result found
+ gfxFontEntry*
+ PlatformGlobalFontFallback(const uint32_t aCh,
+ Script aRunScript,
+ const gfxFontStyle* aMatchStyle,
+ gfxFontFamily** aMatchedFamily) override;
+
+private:
+ friend class gfxDWriteFontFamily;
+
+ nsresult GetFontSubstitutes();
+
+ void GetDirectWriteSubstitutes();
+
+ virtual bool UsesSystemFallback() { return true; }
+
+ void GetFontsFromCollection(IDWriteFontCollection* aCollection);
+
+#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<nsString> mNonExistingFonts;
+
+ /**
+ * Table of font substitutes, we grab this from the registry to get
+ * alternative font names.
+ */
+ FontFamilyTable mFontSubstitutes;
+
+ 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 000000000..96a935245
--- /dev/null
+++ b/gfx/thebes/gfxDWriteFonts.cpp
@@ -0,0 +1,709 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "mozilla/MemoryReporting.h"
+
+#include <algorithm>
+#include "gfxDWriteFontList.h"
+#include "gfxContext.h"
+#include "gfxTextRun.h"
+#include <dwrite.h>
+
+#include "harfbuzz/hb.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+// This is also in gfxGDIFont.cpp. Would be nice to put it somewhere common,
+// but we can't declare it in the gfxFont.h or gfxFontUtils.h headers
+// because those are exported, and the cairo headers aren't.
+static inline cairo_antialias_t
+GetCairoAntialiasOption(gfxFont::AntialiasOption anAntialiasOption)
+{
+ switch (anAntialiasOption) {
+ default:
+ case gfxFont::kAntialiasDefault:
+ return CAIRO_ANTIALIAS_DEFAULT;
+ case gfxFont::kAntialiasNone:
+ return CAIRO_ANTIALIAS_NONE;
+ case gfxFont::kAntialiasGrayscale:
+ return CAIRO_ANTIALIAS_GRAY;
+ case gfxFont::kAntialiasSubpixel:
+ return CAIRO_ANTIALIAS_SUBPIXEL;
+ }
+}
+
+// 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
+
+static bool
+UsingClearType()
+{
+ BOOL fontSmoothing;
+ if (!SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &fontSmoothing, 0) ||
+ !fontSmoothing)
+ {
+ return false;
+ }
+
+ UINT type;
+ if (SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, &type, 0) &&
+ type == FE_FONTSMOOTHINGCLEARTYPE)
+ {
+ return true;
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// gfxDWriteFont
+gfxDWriteFont::gfxDWriteFont(gfxFontEntry *aFontEntry,
+ const gfxFontStyle *aFontStyle,
+ bool aNeedsBold,
+ AntialiasOption anAAOption)
+ : gfxFont(aFontEntry, aFontStyle, anAAOption)
+ , mCairoFontFace(nullptr)
+ , mMetrics(nullptr)
+ , mSpaceGlyph(0)
+ , mNeedsOblique(false)
+ , mNeedsBold(aNeedsBold)
+ , mUseSubpixelPositions(false)
+ , mAllowManualShowGlyphs(true)
+{
+ gfxDWriteFontEntry *fe =
+ static_cast<gfxDWriteFontEntry*>(aFontEntry);
+ nsresult rv;
+ DWRITE_FONT_SIMULATIONS sims = DWRITE_FONT_SIMULATIONS_NONE;
+ if ((GetStyle()->style != NS_FONT_STYLE_NORMAL) &&
+ fe->IsUpright() &&
+ GetStyle()->allowSyntheticStyle) {
+ // For this we always use the font_matrix for uniformity. Not the
+ // DWrite simulation.
+ mNeedsOblique = true;
+ }
+ if (aNeedsBold) {
+ sims |= DWRITE_FONT_SIMULATIONS_BOLD;
+ }
+
+ rv = fe->CreateFontFace(getter_AddRefs(mFontFace), sims);
+
+ if (NS_FAILED(rv)) {
+ mIsValid = false;
+ return;
+ }
+
+ ComputeMetrics(anAAOption);
+}
+
+gfxDWriteFont::~gfxDWriteFont()
+{
+ if (mCairoFontFace) {
+ cairo_font_face_destroy(mCairoFontFace);
+ }
+ if (mScaledFont) {
+ cairo_scaled_font_destroy(mScaledFont);
+ }
+ delete mMetrics;
+}
+
+gfxFont*
+gfxDWriteFont::CopyWithAntialiasOption(AntialiasOption anAAOption)
+{
+ return new gfxDWriteFont(static_cast<gfxDWriteFontEntry*>(mFontEntry.get()),
+ &mStyle, mNeedsBold, anAAOption);
+}
+
+const gfxFont::Metrics&
+gfxDWriteFont::GetHorizontalMetrics()
+{
+ return *mMetrics;
+}
+
+bool
+gfxDWriteFont::GetFakeMetricsForArialBlack(DWRITE_FONT_METRICS *aFontMetrics)
+{
+ gfxFontStyle style(mStyle);
+ style.weight = 700;
+ bool needsBold;
+
+ gfxFontEntry* fe =
+ gfxPlatformFontList::PlatformFontList()->
+ FindFontForFamily(NS_LITERAL_STRING("Arial"), &style, needsBold);
+ if (!fe || fe == mFontEntry) {
+ return false;
+ }
+
+ RefPtr<gfxFont> font = fe->FindOrMakeFont(&style, needsBold);
+ gfxDWriteFont *dwFont = static_cast<gfxDWriteFont*>(font.get());
+ dwFont->mFontFace->GetMetrics(aFontMetrics);
+
+ return true;
+}
+
+void
+gfxDWriteFont::ComputeMetrics(AntialiasOption anAAOption)
+{
+ DWRITE_FONT_METRICS fontMetrics;
+ if (!(mFontEntry->Weight() == 900 &&
+ !mFontEntry->IsUserFont() &&
+ mFontEntry->Name().EqualsLiteral("Arial Black") &&
+ GetFakeMetricsForArialBlack(&fontMetrics)))
+ {
+ mFontFace->GetMetrics(&fontMetrics);
+ }
+
+ if (mStyle.sizeAdjust >= 0.0) {
+ gfxFloat aspect = (gfxFloat)fontMetrics.xHeight /
+ fontMetrics.designUnitsPerEm;
+ mAdjustedSize = mStyle.GetAdjustedSize(aspect);
+ } else {
+ mAdjustedSize = mStyle.size;
+ }
+
+ // 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
+ }
+
+ 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 = new gfxFont::Metrics;
+ ::memset(mMetrics, 0, sizeof(*mMetrics));
+
+ mFUnitsConvFactor = float(mAdjustedSize / fontMetrics.designUnitsPerEm);
+
+ mMetrics->xHeight = fontMetrics.xHeight * mFUnitsConvFactor;
+ mMetrics->capHeight = fontMetrics.capHeight * mFUnitsConvFactor;
+
+ mMetrics->maxAscent = ceil(fontMetrics.ascent * mFUnitsConvFactor);
+ mMetrics->maxDescent = ceil(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;
+ HRESULT hr = mFontFace->GetGlyphIndicesW(&ucs, 1, &glyph);
+ if (FAILED(hr)) {
+ mMetrics->spaceWidth = 0;
+ } else {
+ mSpaceGlyph = glyph;
+ mMetrics->spaceWidth = MeasureGlyphWidth(glyph);
+ }
+
+ // 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) {
+ ucs = L'x';
+ if (SUCCEEDED(mFontFace->GetGlyphIndicesW(&ucs, 1, &glyph))) {
+ mMetrics->aveCharWidth = MeasureGlyphWidth(glyph);
+ }
+ if (mMetrics->aveCharWidth < 1) {
+ // Let's just assume the X is square.
+ mMetrics->aveCharWidth = fontMetrics.xHeight * mFUnitsConvFactor;
+ }
+ }
+
+ ucs = L'0';
+ if (SUCCEEDED(mFontFace->GetGlyphIndicesW(&ucs, 1, &glyph))) {
+ mMetrics->zeroOrAveCharWidth = MeasureGlyphWidth(glyph);
+ }
+ if (mMetrics->zeroOrAveCharWidth < 1) {
+ mMetrics->zeroOrAveCharWidth = mMetrics->aveCharWidth;
+ }
+
+ 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 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 zeroOrAve: %f\n",
+ mMetrics->spaceWidth, mMetrics->aveCharWidth, mMetrics->zeroOrAveCharWidth);
+ 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;
+}
+
+uint32_t
+gfxDWriteFont::GetSpaceGlyph()
+{
+ return mSpaceGlyph;
+}
+
+bool
+gfxDWriteFont::SetupCairoFont(DrawTarget* aDrawTarget)
+{
+ cairo_scaled_font_t *scaledFont = GetCairoScaledFont();
+ if (cairo_scaled_font_status(scaledFont) != CAIRO_STATUS_SUCCESS) {
+ // Don't cairo_set_scaled_font as that would propagate the error to
+ // the cairo_t, precluding any further drawing.
+ return false;
+ }
+ cairo_set_scaled_font(gfxFont::RefCairo(aDrawTarget), scaledFont);
+ return true;
+}
+
+bool
+gfxDWriteFont::IsValid() const
+{
+ return mFontFace != nullptr;
+}
+
+IDWriteFontFace*
+gfxDWriteFont::GetFontFace()
+{
+ return mFontFace.get();
+}
+
+cairo_font_face_t *
+gfxDWriteFont::CairoFontFace()
+{
+ if (!mCairoFontFace) {
+#ifdef CAIRO_HAS_DWRITE_FONT
+ mCairoFontFace =
+ cairo_dwrite_font_face_create_for_dwrite_fontface(
+ ((gfxDWriteFontEntry*)mFontEntry.get())->mFont, mFontFace);
+#endif
+ }
+ return mCairoFontFace;
+}
+
+
+cairo_scaled_font_t *
+gfxDWriteFont::GetCairoScaledFont()
+{
+ if (!mScaledFont) {
+ cairo_matrix_t sizeMatrix;
+ cairo_matrix_t identityMatrix;
+
+ cairo_matrix_init_scale(&sizeMatrix, mAdjustedSize, mAdjustedSize);
+ cairo_matrix_init_identity(&identityMatrix);
+
+ cairo_font_options_t *fontOptions = cairo_font_options_create();
+ if (mNeedsOblique) {
+ double skewfactor = OBLIQUE_SKEW_FACTOR;
+
+ cairo_matrix_t style;
+ cairo_matrix_init(&style,
+ 1, //xx
+ 0, //yx
+ -1 * skewfactor, //xy
+ 1, //yy
+ 0, //x0
+ 0); //y0
+ cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style);
+ }
+
+ if (mAntialiasOption != kAntialiasDefault) {
+ cairo_font_options_set_antialias(fontOptions,
+ GetCairoAntialiasOption(mAntialiasOption));
+ }
+
+ mScaledFont = cairo_scaled_font_create(CairoFontFace(),
+ &sizeMatrix,
+ &identityMatrix,
+ fontOptions);
+ cairo_font_options_destroy(fontOptions);
+
+ cairo_dwrite_scaled_font_allow_manual_show_glyphs(mScaledFont,
+ mAllowManualShowGlyphs);
+
+ cairo_dwrite_scaled_font_set_force_GDI_classic(mScaledFont,
+ GetForceGDIClassic());
+ }
+
+ NS_ASSERTION(mAdjustedSize == 0.0 ||
+ cairo_scaled_font_status(mScaledFont)
+ == CAIRO_STATUS_SUCCESS,
+ "Failed to make scaled font");
+
+ return mScaledFont;
+}
+
+gfxFont::RunMetrics
+gfxDWriteFont::Measure(const gfxTextRun* aTextRun,
+ uint32_t aStart, uint32_t aEnd,
+ BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget,
+ Spacing* aSpacing,
+ uint16_t 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.x -= aTextRun->GetAppUnitsPerDevUnit();
+ metrics.mBoundingBox.width += aTextRun->GetAppUnitsPerDevUnit() * 3;
+ }
+
+ return metrics;
+}
+
+bool
+gfxDWriteFont::ProvidesGlyphWidths() const
+{
+ return !mUseSubpixelPositions ||
+ (mFontFace->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD);
+}
+
+int32_t
+gfxDWriteFont::GetGlyphWidth(DrawTarget& aDrawTarget, uint16_t aGID)
+{
+ if (!mGlyphWidths) {
+ mGlyphWidths = MakeUnique<nsDataHashtable<nsUint32HashKey,int32_t>>(128);
+ }
+
+ int32_t width = -1;
+ if (mGlyphWidths->Get(aGID, &width)) {
+ return width;
+ }
+
+ width = NS_lround(MeasureGlyphWidth(aGID) * 65536.0);
+ mGlyphWidths->Put(aGID, width);
+ return width;
+}
+
+already_AddRefed<GlyphRenderingOptions>
+gfxDWriteFont::GetGlyphRenderingOptions(const TextRunDrawParams* aRunParams)
+{
+ if (UsingClearType()) {
+ return Factory::CreateDWriteGlyphRenderingOptions(
+ gfxWindowsPlatform::GetPlatform()->GetRenderingParams(GetForceGDIClassic() ?
+ gfxWindowsPlatform::TEXT_RENDERING_GDI_CLASSIC : gfxWindowsPlatform::TEXT_RENDERING_NORMAL));
+ } else {
+ return Factory::CreateDWriteGlyphRenderingOptions(gfxWindowsPlatform::GetPlatform()->
+ GetRenderingParams(gfxWindowsPlatform::TEXT_RENDERING_NO_CLEARTYPE));
+ }
+}
+
+bool
+gfxDWriteFont::GetForceGDIClassic()
+{
+ return static_cast<gfxDWriteFontEntry*>(mFontEntry.get())->GetForceGDIClassic() &&
+ cairo_dwrite_get_cleartype_rendering_mode() < 0 &&
+ GetAdjustedSize() <=
+ gfxDWriteFontList::PlatformFontList()->GetForceGDIClassicMaxFontSize();
+}
+
+DWRITE_MEASURING_MODE
+gfxDWriteFont::GetMeasuringMode()
+{
+ return GetForceGDIClassic()
+ ? DWRITE_MEASURING_MODE_GDI_CLASSIC
+ : gfxWindowsPlatform::GetPlatform()->DWriteMeasuringMode();
+}
+
+gfxFloat
+gfxDWriteFont::MeasureGlyphWidth(uint16_t aGlyph)
+{
+ DWRITE_GLYPH_METRICS metrics;
+ HRESULT hr;
+ 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);
+ }
+ }
+ return 0;
+}
+
+void
+gfxDWriteFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const
+{
+ gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+ aSizes->mFontInstances += aMallocSizeOf(mMetrics);
+ 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(mozilla::gfx::DrawTarget *aTarget)
+{
+ bool wantCairo = aTarget->GetBackendType() == BackendType::CAIRO;
+ if (mAzureScaledFont && mAzureScaledFontIsCairo == wantCairo) {
+ RefPtr<ScaledFont> scaledFont(mAzureScaledFont);
+ return scaledFont.forget();
+ }
+
+ NativeFont nativeFont;
+ nativeFont.mType = NativeFontType::DWRITE_FONT_FACE;
+ nativeFont.mFont = GetFontFace();
+
+ if (wantCairo) {
+ mAzureScaledFont = Factory::CreateScaledFontWithCairo(nativeFont,
+ GetAdjustedSize(),
+ GetCairoScaledFont());
+ } else if (aTarget->GetBackendType() == BackendType::SKIA) {
+ gfxDWriteFontEntry *fe =
+ static_cast<gfxDWriteFontEntry*>(mFontEntry.get());
+ bool useEmbeddedBitmap = (fe->IsCJKFont() && HasBitmapStrikeForSize(NS_lround(mAdjustedSize)));
+
+ const gfxFontStyle* fontStyle = GetStyle();
+ mAzureScaledFont =
+ Factory::CreateScaledFontForDWriteFont(mFontFace, fontStyle,
+ GetAdjustedSize(),
+ useEmbeddedBitmap,
+ GetForceGDIClassic());
+ } else {
+ mAzureScaledFont = Factory::CreateScaledFontForNativeFont(nativeFont,
+ GetAdjustedSize());
+ }
+
+ mAzureScaledFontIsCairo = wantCairo;
+
+ RefPtr<ScaledFont> scaledFont(mAzureScaledFont);
+ return scaledFont.forget();
+}
diff --git a/gfx/thebes/gfxDWriteFonts.h b/gfx/thebes/gfxDWriteFonts.h
new file mode 100644
index 000000000..72ac7a2b5
--- /dev/null
+++ b/gfx/thebes/gfxDWriteFonts.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/MemoryReporting.h"
+#include "mozilla/UniquePtr.h"
+#include <dwrite.h>
+
+#include "gfxFont.h"
+#include "gfxUserFontSet.h"
+#include "cairo-win32.h"
+
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+
+/**
+ * \brief Class representing a font face for a font entry.
+ */
+class gfxDWriteFont : public gfxFont
+{
+public:
+ gfxDWriteFont(gfxFontEntry *aFontEntry,
+ const gfxFontStyle *aFontStyle,
+ bool aNeedsBold = false,
+ AntialiasOption = kAntialiasDefault);
+ ~gfxDWriteFont();
+
+ virtual gfxFont*
+ CopyWithAntialiasOption(AntialiasOption anAAOption) override;
+
+ virtual uint32_t GetSpaceGlyph() override;
+
+ virtual bool SetupCairoFont(DrawTarget* aDrawTarget) override;
+
+ virtual bool AllowSubpixelAA() override
+ { return mAllowManualShowGlyphs; }
+
+ bool IsValid() const;
+
+ IDWriteFontFace *GetFontFace();
+
+ /* override Measure to add padding for antialiasing */
+ virtual RunMetrics Measure(const gfxTextRun *aTextRun,
+ uint32_t aStart, uint32_t aEnd,
+ BoundingBoxType aBoundingBoxType,
+ DrawTarget *aDrawTargetForTightBoundingBox,
+ Spacing *aSpacing,
+ uint16_t aOrientation) override;
+
+ virtual bool ProvidesGlyphWidths() const override;
+
+ virtual int32_t GetGlyphWidth(DrawTarget& aDrawTarget,
+ uint16_t aGID) override;
+
+ virtual already_AddRefed<mozilla::gfx::GlyphRenderingOptions>
+ GetGlyphRenderingOptions(const TextRunDrawParams* aRunParams = nullptr) override;
+
+ virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const override;
+ virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const override;
+
+ virtual FontType GetType() const override { return FONT_TYPE_DWRITE; }
+
+ virtual already_AddRefed<mozilla::gfx::ScaledFont>
+ GetScaledFont(mozilla::gfx::DrawTarget *aTarget) override;
+
+ virtual cairo_scaled_font_t *GetCairoScaledFont() override;
+
+protected:
+ virtual const Metrics& GetHorizontalMetrics() override;
+
+ bool GetFakeMetricsForArialBlack(DWRITE_FONT_METRICS *aFontMetrics);
+
+ void ComputeMetrics(AntialiasOption anAAOption);
+
+ bool HasBitmapStrikeForSize(uint32_t aSize);
+
+ cairo_font_face_t *CairoFontFace();
+
+ gfxFloat MeasureGlyphWidth(uint16_t aGlyph);
+
+ DWRITE_MEASURING_MODE GetMeasuringMode();
+ bool GetForceGDIClassic();
+
+ RefPtr<IDWriteFontFace> mFontFace;
+ cairo_font_face_t *mCairoFontFace;
+
+ Metrics *mMetrics;
+
+ // cache of glyph widths in 16.16 fixed-point pixels
+ mozilla::UniquePtr<nsDataHashtable<nsUint32HashKey,int32_t>> mGlyphWidths;
+
+ uint32_t mSpaceGlyph;
+
+ bool mNeedsOblique;
+ bool mNeedsBold;
+ bool mUseSubpixelPositions;
+ bool mAllowManualShowGlyphs;
+ bool mAzureScaledFontIsCairo;
+};
+
+#endif
diff --git a/gfx/thebes/gfxDrawable.cpp b/gfx/thebes/gfxDrawable.cpp
new file mode 100644
index 000000000..7d25cc975
--- /dev/null
+++ b/gfx/thebes/gfxDrawable.cpp
@@ -0,0 +1,254 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxASurface.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(const SamplingFilter aSamplingFilter)
+{
+ SurfaceFormat format =
+ gfxPlatform::GetPlatform()->Optimal2DFormatForContent(gfxContentType::COLOR_ALPHA);
+ RefPtr<DrawTarget> dt =
+ gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(mSize,
+ format);
+ if (!dt || !dt->IsValid())
+ return nullptr;
+
+ RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(dt);
+ MOZ_ASSERT(ctx); // already checked for target above
+ 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(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()
+{
+}
+
+class DrawingCallbackFromDrawable : public gfxDrawingCallback {
+public:
+ explicit DrawingCallbackFromDrawable(gfxDrawable* aDrawable)
+ : mDrawable(aDrawable) {
+ NS_ASSERTION(aDrawable, "aDrawable is null!");
+ }
+
+ virtual ~DrawingCallbackFromDrawable() {}
+
+ virtual bool operator()(gfxContext* aContext,
+ const gfxRect& aFillRect,
+ const SamplingFilter aSamplingFilter,
+ const gfxMatrix& aTransform = gfxMatrix())
+ {
+ 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 000000000..487bd9c54
--- /dev/null
+++ b/gfx/thebes/gfxDrawable.h
@@ -0,0 +1,182 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Types.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() {}
+
+ 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() {}
+
+ 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());
+
+ 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);
+
+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() {}
+
+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() {}
+
+ 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());
+
+protected:
+ already_AddRefed<gfxSurfaceDrawable>
+ MakeSurfaceDrawable(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());
+
+
+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 000000000..18c109151
--- /dev/null
+++ b/gfx/thebes/gfxEnv.h
@@ -0,0 +1,123 @@
+/* -*- 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 "prenv.h"
+
+// 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_DISABLE_CONTEXT_SHARING_GLX",DisableContextSharingGLX);
+// means that you can call
+// bool var = gfxEnv::DisableContextSharingGLX();
+// and that the value will be checked only once, first time we call it, then cached.
+
+#define DECL_GFX_ENV(Env, Name) \
+ static bool Name() { \
+ static bool isSet = IsEnvSet(Env);\
+ return isSet; \
+ }
+
+class gfxEnv final
+{
+public:
+ // 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.
+
+ // Debugging inside of ContainerLayerComposite
+ DECL_GFX_ENV("DUMP_DEBUG", DumpDebug);
+
+ // OpenGL shader debugging in OGLShaderProgram, in DEBUG only
+ DECL_GFX_ENV("MOZ_DEBUG_SHADERS", DebugShaders);
+
+ // Disabling context sharing in GLContextProviderGLX
+ DECL_GFX_ENV("MOZ_DISABLE_CONTEXT_SHARING_GLX", DisableContextSharingGlx);
+
+ // Disabling the crash guard in DriverCrashGuard
+ DECL_GFX_ENV("MOZ_DISABLE_CRASH_GUARD", DisableCrashGuard);
+ DECL_GFX_ENV("MOZ_FORCE_CRASH_GUARD_NIGHTLY", ForceCrashGuardNightly);
+
+ // We force present to work around some Windows bugs - disable that if this
+ // environment variable is set.
+ DECL_GFX_ENV("MOZ_DISABLE_FORCE_PRESENT", DisableForcePresent);
+
+ // 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", DumpCompositorTextures);
+
+ // Dumping the layer list in LayerSorter
+ DECL_GFX_ENV("MOZ_DUMP_LAYER_SORT_LIST", DumpLayerSortList);
+
+ // Paint dumping, only when MOZ_DUMP_PAINTING is defined.
+ DECL_GFX_ENV("MOZ_DUMP_PAINT", DumpPaint);
+ DECL_GFX_ENV("MOZ_DUMP_PAINT_INTERMEDIATE", DumpPaintIntermediate);
+ DECL_GFX_ENV("MOZ_DUMP_PAINT_ITEMS", DumpPaintItems);
+ DECL_GFX_ENV("MOZ_DUMP_PAINT_TO_FILE", DumpPaintToFile);
+
+ // Force double buffering in ContentClient
+ DECL_GFX_ENV("MOZ_FORCE_DOUBLE_BUFFERING", ForceDoubleBuffering);
+
+ // Force gfxDevCrash to use MOZ_CRASH in Beta and Release
+ DECL_GFX_ENV("MOZ_GFX_CRASH_MOZ_CRASH", GfxDevCrashMozCrash);
+ // Force gfxDevCrash to use telemetry in Nightly and Aurora
+ DECL_GFX_ENV("MOZ_GFX_CRASH_TELEMETRY", GfxDevCrashTelemetry);
+
+ DECL_GFX_ENV("MOZ_GFX_VR_NO_DISTORTION", VRNoDistortion);
+
+ // Debugging in GLContext
+ DECL_GFX_ENV("MOZ_GL_DEBUG", GlDebug);
+ DECL_GFX_ENV("MOZ_GL_DEBUG_VERBOSE", GlDebugVerbose);
+ DECL_GFX_ENV("MOZ_GL_DEBUG_ABORT_ON_ERROR", GlDebugAbortOnError);
+
+ // Count GL extensions
+ DECL_GFX_ENV("MOZ_GL_DUMP_EXTS", GlDumpExtensions);
+
+ // Very noisy GLContext and GLContextProviderELG
+ DECL_GFX_ENV("MOZ_GL_SPEW", GlSpew);
+
+ // Do extra work before and after each GLX call in GLContextProviderGLX
+ DECL_GFX_ENV("MOZ_GLX_DEBUG", GlxDebug);
+
+ // Use X compositing
+ DECL_GFX_ENV("MOZ_LAYERS_ENABLE_XLIB_SURFACES", LayersEnableXlibSurfaces);
+
+ // GL compositing on Windows
+ DECL_GFX_ENV("MOZ_LAYERS_PREFER_EGL", LayersPreferEGL);
+
+ // Offscreen GL context for main layer manager
+ DECL_GFX_ENV("MOZ_LAYERS_PREFER_OFFSCREEN", LayersPreferOffscreen);
+
+ // Stop the VR rendering
+ DECL_GFX_ENV("NO_VR_RENDERING", NoVRRendering);
+
+ // WARNING:
+ // 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.
+
+private:
+ // Helper function, can be re-used in the other macros
+ static bool IsEnvSet(const char* aName) {
+ const char* val = PR_GetEnv(aName);
+ return (val != 0 && *val != '\0');
+ }
+
+ gfxEnv() {};
+ ~gfxEnv() {};
+ gfxEnv(const gfxEnv&) = delete;
+ gfxEnv& operator=(const gfxEnv&) = delete;
+};
+
+#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 000000000..241f28f63
--- /dev/null
+++ b/gfx/thebes/gfxFT2FontBase.cpp
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxFontConstants.h"
+#include "gfxFontUtils.h"
+
+using namespace mozilla::gfx;
+
+gfxFT2FontBase::gfxFT2FontBase(cairo_scaled_font_t *aScaledFont,
+ gfxFontEntry *aFontEntry,
+ const gfxFontStyle *aFontStyle)
+ : gfxFont(aFontEntry, aFontStyle, kAntialiasDefault, aScaledFont),
+ mSpaceGlyph(0),
+ mHasMetrics(false)
+{
+ cairo_scaled_font_reference(mScaledFont);
+ gfxFT2LockedFace face(this);
+ mFUnitsConvFactor = face.XScale();
+}
+
+gfxFT2FontBase::~gfxFT2FontBase()
+{
+ cairo_scaled_font_destroy(mScaledFont);
+}
+
+uint32_t
+gfxFT2FontBase::GetGlyph(uint32_t aCharCode)
+{
+ // FcFreeTypeCharIndex needs to lock the FT_Face and can end up searching
+ // through all the postscript glyph names in the font. Therefore use a
+ // lightweight cache, which is stored on the cairo_font_face_t.
+
+ cairo_font_face_t *face =
+ cairo_scaled_font_get_font_face(CairoScaledFont());
+
+ if (cairo_font_face_status(face) != CAIRO_STATUS_SUCCESS)
+ return 0;
+
+ // 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.
+
+ struct CmapCacheSlot {
+ uint32_t mCharCode;
+ uint32_t mGlyphIndex;
+ };
+ const uint32_t kNumSlots = 256;
+ static cairo_user_data_key_t sCmapCacheKey;
+
+ CmapCacheSlot *slots = static_cast<CmapCacheSlot*>
+ (cairo_font_face_get_user_data(face, &sCmapCacheKey));
+
+ if (!slots) {
+ // cairo's caches can keep some cairo_font_faces alive past our last
+ // destroy, so the destroy function (free) for the cache must be
+ // callable from cairo without any assumptions about what other
+ // modules have not been shutdown.
+ slots = static_cast<CmapCacheSlot*>
+ (calloc(kNumSlots, sizeof(CmapCacheSlot)));
+ if (!slots)
+ return 0;
+
+ cairo_status_t status =
+ cairo_font_face_set_user_data(face, &sCmapCacheKey, slots, free);
+ if (status != CAIRO_STATUS_SUCCESS) { // OOM
+ free(slots);
+ return 0;
+ }
+
+ // 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.
+ slots[0].mCharCode = 1;
+ }
+
+ CmapCacheSlot *slot = &slots[aCharCode % kNumSlots];
+ if (slot->mCharCode != aCharCode) {
+ slot->mCharCode = aCharCode;
+ slot->mGlyphIndex = gfxFT2LockedFace(this).GetGlyph(aCharCode);
+ }
+
+ return slot->mGlyphIndex;
+}
+
+void
+gfxFT2FontBase::GetGlyphExtents(uint32_t aGlyph, cairo_text_extents_t* aExtents)
+{
+ NS_PRECONDITION(aExtents != nullptr, "aExtents must not be NULL");
+
+ cairo_glyph_t glyphs[1];
+ glyphs[0].index = aGlyph;
+ glyphs[0].x = 0.0;
+ glyphs[0].y = 0.0;
+ // cairo does some caching for us here but perhaps a small gain could be
+ // made by caching more. It is usually only the advance that is needed,
+ // so caching only the advance could allow many requests to be cached with
+ // little memory use. Ideally this cache would be merged with
+ // gfxGlyphExtents.
+ cairo_scaled_font_glyph_extents(CairoScaledFont(), glyphs, 1, aExtents);
+}
+
+const gfxFont::Metrics&
+gfxFT2FontBase::GetHorizontalMetrics()
+{
+ if (mHasMetrics)
+ return mMetrics;
+
+ if (MOZ_UNLIKELY(GetStyle()->size <= 0.0) ||
+ MOZ_UNLIKELY(GetStyle()->sizeAdjust == 0.0)) {
+ new(&mMetrics) gfxFont::Metrics(); // zero initialize
+ mSpaceGlyph = GetGlyph(' ');
+ } else {
+ gfxFT2LockedFace face(this);
+ face.GetMetrics(&mMetrics, &mSpaceGlyph);
+ }
+
+ 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", NS_ConvertUTF16toUTF8(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, " uOff: %f uSize: %f stOff: %f stSize: %f\n", mMetrics.underlineOffset, mMetrics.underlineSize, mMetrics.strikeoutOffset, mMetrics.strikeoutSize);
+#endif
+
+ mHasMetrics = true;
+ return mMetrics;
+}
+
+// Get the glyphID of a space
+uint32_t
+gfxFT2FontBase::GetSpaceGlyph()
+{
+ GetHorizontalMetrics();
+ return mSpaceGlyph;
+}
+
+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);
+}
+
+int32_t
+gfxFT2FontBase::GetGlyphWidth(DrawTarget& aDrawTarget, uint16_t aGID)
+{
+ cairo_text_extents_t extents;
+ GetGlyphExtents(aGID, &extents);
+ // convert to 16.16 fixed point
+ return NS_lround(0x10000 * extents.x_advance);
+}
+
+bool
+gfxFT2FontBase::SetupCairoFont(DrawTarget* aDrawTarget)
+{
+ // The scaled font ctm is not relevant right here because
+ // cairo_set_scaled_font does not record the scaled font itself, but
+ // merely the font_face, font_matrix, font_options. The scaled_font used
+ // for the target can be different from the scaled_font passed to
+ // cairo_set_scaled_font. (Unfortunately we have measured only for an
+ // identity ctm.)
+ cairo_scaled_font_t *cairoFont = CairoScaledFont();
+
+ if (cairo_scaled_font_status(cairoFont) != CAIRO_STATUS_SUCCESS) {
+ // Don't cairo_set_scaled_font as that would propagate the error to
+ // the cairo_t, precluding any further drawing.
+ return false;
+ }
+ // Thoughts on which font_options to set on the context:
+ //
+ // cairoFont has been created for screen rendering.
+ //
+ // When the context is being used for screen rendering, we should set
+ // font_options such that the same scaled_font gets used (when the ctm is
+ // the same). The use of explicit font_options recorded in
+ // CreateScaledFont ensures that this will happen.
+ //
+ // XXXkt: For pdf and ps surfaces, I don't know whether it's better to
+ // remove surface-specific options, or try to draw with the same
+ // scaled_font that was used to measure. As the same font_face is being
+ // used, its font_options will often override some values anyway (unless
+ // perhaps we remove those from the FcPattern at face creation).
+ //
+ // I can't see any significant difference in printing, irrespective of
+ // what is set here. It's too late to change things here as measuring has
+ // already taken place. We should really be measuring with a different
+ // font for pdf and ps surfaces (bug 403513).
+ cairo_set_scaled_font(gfxFont::RefCairo(aDrawTarget), cairoFont);
+ return true;
+}
diff --git a/gfx/thebes/gfxFT2FontBase.h b/gfx/thebes/gfxFT2FontBase.h
new file mode 100644
index 000000000..498c74ff9
--- /dev/null
+++ b/gfx/thebes/gfxFT2FontBase.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "cairo.h"
+#include "gfxContext.h"
+#include "gfxFont.h"
+#include "mozilla/gfx/2D.h"
+
+class gfxFT2FontBase : public gfxFont {
+public:
+ gfxFT2FontBase(cairo_scaled_font_t *aScaledFont,
+ gfxFontEntry *aFontEntry,
+ const gfxFontStyle *aFontStyle);
+ virtual ~gfxFT2FontBase();
+
+ uint32_t GetGlyph(uint32_t aCharCode);
+ void GetGlyphExtents(uint32_t aGlyph,
+ cairo_text_extents_t* aExtents);
+ virtual uint32_t GetSpaceGlyph() override;
+ virtual bool ProvidesGetGlyph() const override { return true; }
+ virtual uint32_t GetGlyph(uint32_t unicode,
+ uint32_t variation_selector) override;
+ virtual bool ProvidesGlyphWidths() const override { return true; }
+ virtual int32_t GetGlyphWidth(DrawTarget& aDrawTarget,
+ uint16_t aGID) override;
+
+ cairo_scaled_font_t *CairoScaledFont() { return mScaledFont; };
+ virtual bool SetupCairoFont(DrawTarget* aDrawTarget) override;
+
+ virtual FontType GetType() const override { return FONT_TYPE_FT2; }
+
+protected:
+ virtual const Metrics& GetHorizontalMetrics() override;
+
+ uint32_t mSpaceGlyph;
+ bool mHasMetrics;
+ Metrics mMetrics;
+};
+
+#endif /* GFX_FT2FONTBASE_H */
diff --git a/gfx/thebes/gfxFT2FontList.cpp b/gfx/thebes/gfxFT2FontList.cpp
new file mode 100644
index 000000000..8a652df0d
--- /dev/null
+++ b/gfx/thebes/gfxFT2FontList.cpp
@@ -0,0 +1,1608 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "nsIInputStream.h"
+#define gfxToolkitPlatform gfxAndroidPlatform
+
+#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 "cairo-ft.h"
+
+#include "gfxFT2FontList.h"
+#include "gfxFT2Fonts.h"
+#include "gfxUserFontSet.h"
+#include "gfxFontUtils.h"
+
+#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 "nsISimpleEnumerator.h"
+#include "nsIMemory.h"
+#include "gfxFontConstants.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/scache/StartupCache.h"
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+using namespace mozilla;
+
+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 cairo_user_data_key_t sFTUserFontDataKey;
+
+static __inline void
+BuildKeyNameFromFontName(nsAString &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.
+class AutoFTFace {
+public:
+ AutoFTFace(FT2FontEntry* aFontEntry)
+ : mFace(nullptr), mFontDataBuf(nullptr), mOwnsFace(false)
+ {
+ if (aFontEntry->mFTFace) {
+ mFace = aFontEntry->mFTFace;
+ return;
+ }
+
+ NS_ASSERTION(!aFontEntry->mFilename.IsEmpty(),
+ "can't use AutoFTFace for fonts without a filename");
+ FT_Library ft = gfxToolkitPlatform::GetPlatform()->GetFTLibrary();
+
+ // 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.
+ if (aFontEntry->mFilename[0] != '/') {
+ RefPtr<nsZipArchive> reader =
+ Omnijar::GetReader(Omnijar::Type::GRE);
+ nsZipItem *item = reader->GetItem(aFontEntry->mFilename.get());
+ NS_ASSERTION(item, "failed to find zip entry");
+
+ uint32_t bufSize = item->RealSize();
+ mFontDataBuf = static_cast<uint8_t*>(malloc(bufSize));
+ if (mFontDataBuf) {
+ nsZipCursor cursor(item, reader, mFontDataBuf, bufSize);
+ cursor.Copy(&bufSize);
+ NS_ASSERTION(bufSize == item->RealSize(),
+ "error reading bundled font");
+
+ if (FT_Err_Ok != FT_New_Memory_Face(ft, mFontDataBuf, bufSize,
+ aFontEntry->mFTFontIndex,
+ &mFace)) {
+ NS_WARNING("failed to create freetype face");
+ }
+ }
+ } else {
+ if (FT_Err_Ok != FT_New_Face(ft, aFontEntry->mFilename.get(),
+ aFontEntry->mFTFontIndex, &mFace)) {
+ NS_WARNING("failed to create freetype face");
+ }
+ }
+ if (FT_Err_Ok != FT_Select_Charmap(mFace, FT_ENCODING_UNICODE)) {
+ NS_WARNING("failed to select Unicode charmap");
+ }
+ mOwnsFace = true;
+ }
+
+ ~AutoFTFace() {
+ if (mFace && mOwnsFace) {
+ FT_Done_Face(mFace);
+ if (mFontDataBuf) {
+ free(mFontDataBuf);
+ }
+ }
+ }
+
+ operator FT_Face() { return mFace; }
+
+ // If we 'forget' the FT_Face (used when ownership is handed over to Cairo),
+ // we do -not- free the mFontDataBuf (if used); that also becomes the
+ // responsibility of the new owner of the face.
+ FT_Face forget() {
+ NS_ASSERTION(mOwnsFace, "can't forget() when we didn't own the face");
+ mOwnsFace = false;
+ return mFace;
+ }
+
+ const uint8_t* FontData() const { return mFontDataBuf; }
+
+private:
+ FT_Face mFace;
+ uint8_t* mFontDataBuf; // Uncompressed data (for fonts stored in a JAR),
+ // or null for fonts instantiated from a file.
+ // If non-null, this must survive as long as the
+ // FT_Face.
+ bool mOwnsFace;
+};
+
+/*
+ * 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.
+ */
+
+cairo_scaled_font_t *
+FT2FontEntry::CreateScaledFont(const gfxFontStyle *aStyle)
+{
+ cairo_font_face_t *cairoFace = CairoFontFace();
+ if (!cairoFace) {
+ return nullptr;
+ }
+
+ cairo_scaled_font_t *scaledFont = nullptr;
+
+ cairo_matrix_t sizeMatrix;
+ cairo_matrix_t identityMatrix;
+
+ // XXX deal with adjusted size
+ cairo_matrix_init_scale(&sizeMatrix, aStyle->size, aStyle->size);
+ cairo_matrix_init_identity(&identityMatrix);
+
+ // synthetic oblique by skewing via the font matrix
+ bool needsOblique = IsUpright() &&
+ aStyle->style != NS_FONT_STYLE_NORMAL &&
+ aStyle->allowSyntheticStyle;
+
+ if (needsOblique) {
+ cairo_matrix_t style;
+ cairo_matrix_init(&style,
+ 1, //xx
+ 0, //yx
+ -1 * OBLIQUE_SKEW_FACTOR, //xy
+ 1, //yy
+ 0, //x0
+ 0); //y0
+ cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style);
+ }
+
+ cairo_font_options_t *fontOptions = cairo_font_options_create();
+
+ if (gfxPlatform::GetPlatform()->RequiresLinearZoom()) {
+ cairo_font_options_set_hint_metrics(fontOptions, CAIRO_HINT_METRICS_OFF);
+ }
+
+ scaledFont = cairo_scaled_font_create(cairoFace,
+ &sizeMatrix,
+ &identityMatrix, fontOptions);
+ cairo_font_options_destroy(fontOptions);
+
+ NS_ASSERTION(cairo_scaled_font_status(scaledFont) == CAIRO_STATUS_SUCCESS,
+ "Failed to make scaled font");
+
+ return scaledFont;
+}
+
+FT2FontEntry::~FT2FontEntry()
+{
+ // Do nothing for mFTFace here since FTFontDestroyFunc is called by cairo.
+ mFTFace = nullptr;
+
+#ifndef ANDROID
+ if (mFontFace) {
+ cairo_font_face_destroy(mFontFace);
+ mFontFace = nullptr;
+ }
+#endif
+}
+
+gfxFont*
+FT2FontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold)
+{
+ cairo_scaled_font_t *scaledFont = CreateScaledFont(aFontStyle);
+ if (!scaledFont) {
+ return nullptr;
+ }
+ gfxFont *font = new gfxFT2Font(scaledFont, this, aFontStyle, aNeedsBold);
+ cairo_scaled_font_destroy(scaledFont);
+ return font;
+}
+
+/* static */
+FT2FontEntry*
+FT2FontEntry::CreateFontEntry(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t 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.
+ FT_Face face;
+ FT_Error error =
+ FT_New_Memory_Face(gfxToolkitPlatform::GetPlatform()->GetFTLibrary(),
+ aFontData, aLength, 0, &face);
+ if (error != FT_Err_Ok) {
+ free((void*)aFontData);
+ return nullptr;
+ }
+ if (FT_Err_Ok != FT_Select_Charmap(face, FT_ENCODING_UNICODE)) {
+ FT_Done_Face(face);
+ free((void*)aFontData);
+ 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(face, nullptr, 0, aFontName,
+ aFontData);
+ if (fe) {
+ fe->mStyle = aStyle;
+ fe->mWeight = aWeight;
+ fe->mStretch = aStretch;
+ fe->mIsDataUserFont = true;
+ }
+ return fe;
+}
+
+class FTUserFontData {
+public:
+ FTUserFontData(FT_Face aFace, const uint8_t* aData)
+ : mFace(aFace), mFontData(aData)
+ {
+ }
+
+ ~FTUserFontData()
+ {
+ FT_Done_Face(mFace);
+ if (mFontData) {
+ free((void*)mFontData);
+ }
+ }
+
+ const uint8_t *FontData() const { return mFontData; }
+
+private:
+ FT_Face mFace;
+ const uint8_t *mFontData;
+};
+
+static void
+FTFontDestroyFunc(void *data)
+{
+ FTUserFontData *userFontData = static_cast<FTUserFontData*>(data);
+ delete userFontData;
+}
+
+/* static */
+FT2FontEntry*
+FT2FontEntry::CreateFontEntry(const FontListEntry& aFLE)
+{
+ FT2FontEntry *fe = new FT2FontEntry(aFLE.faceName());
+ fe->mFilename = aFLE.filepath();
+ fe->mFTFontIndex = aFLE.index();
+ fe->mWeight = aFLE.weight();
+ fe->mStretch = aFLE.stretch();
+ fe->mStyle = (aFLE.italic() ? NS_FONT_STYLE_ITALIC : NS_FONT_STYLE_NORMAL);
+ return fe;
+}
+
+// Helpers to extract font entry properties from an FT_Face
+static bool
+FTFaceIsItalic(FT_Face aFace)
+{
+ return !!(aFace->style_flags & FT_STYLE_FLAG_ITALIC);
+}
+
+static uint16_t
+FTFaceGetWeight(FT_Face aFace)
+{
+ TT_OS2 *os2 = static_cast<TT_OS2*>(FT_Get_Sfnt_Table(aFace, ft_sfnt_os2));
+ uint16_t os2weight = 0;
+ if (os2 && os2->version != 0xffff) {
+ // Technically, only 100 to 900 are valid, but some fonts
+ // have this set wrong -- e.g. "Microsoft Logo Bold Italic" has
+ // it set to 6 instead of 600. We try to be nice and handle that
+ // as well.
+ if (os2->usWeightClass >= 100 && os2->usWeightClass <= 900) {
+ os2weight = os2->usWeightClass;
+ } else if (os2->usWeightClass >= 1 && os2->usWeightClass <= 9) {
+ os2weight = os2->usWeightClass * 100;
+ }
+ }
+
+ uint16_t result;
+ if (os2weight != 0) {
+ result = os2weight;
+ } else if (aFace->style_flags & FT_STYLE_FLAG_BOLD) {
+ result = 700;
+ } else {
+ result = 400;
+ }
+
+ NS_ASSERTION(result >= 100 && result <= 900, "Invalid weight in font!");
+
+ return result;
+}
+
+// Used to create the font entry for installed faces on the device,
+// when iterating over the fonts directories.
+// We use the FT_Face to retrieve the details needed for the font entry,
+// but unless we have been passed font data (i.e. for a user font),
+// we do *not* save a reference to it, nor create a cairo face,
+// as we don't want to keep a freetype face for every installed font
+// permanently in memory.
+/* static */
+FT2FontEntry*
+FT2FontEntry::CreateFontEntry(FT_Face aFace,
+ const char* aFilename, uint8_t aIndex,
+ const nsAString& aName,
+ const uint8_t* aFontData)
+{
+ FT2FontEntry *fe = new FT2FontEntry(aName);
+ fe->mStyle = (FTFaceIsItalic(aFace) ?
+ NS_FONT_STYLE_ITALIC : NS_FONT_STYLE_NORMAL);
+ fe->mWeight = FTFaceGetWeight(aFace);
+ fe->mFilename = aFilename;
+ fe->mFTFontIndex = aIndex;
+
+ if (aFontData) {
+ fe->mFTFace = aFace;
+ int flags = gfxPlatform::GetPlatform()->FontHintingEnabled() ?
+ FT_LOAD_DEFAULT :
+ (FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING);
+ fe->mFontFace = cairo_ft_font_face_create_for_ft_face(aFace, flags);
+ FTUserFontData *userFontData = new FTUserFontData(aFace, aFontData);
+ cairo_font_face_set_user_data(fe->mFontFace, &sFTUserFontDataKey,
+ userFontData, FTFontDestroyFunc);
+ }
+
+ return fe;
+}
+
+// construct font entry name for an installed font from names in the FT_Face,
+// and then create our FT2FontEntry
+static FT2FontEntry*
+CreateNamedFontEntry(FT_Face aFace, const char* aFilename, uint8_t aIndex)
+{
+ if (!aFace->family_name) {
+ return nullptr;
+ }
+ nsAutoString fontName;
+ AppendUTF8toUTF16(aFace->family_name, fontName);
+ if (aFace->style_name && strcmp("Regular", aFace->style_name)) {
+ fontName.Append(' ');
+ AppendUTF8toUTF16(aFace->style_name, fontName);
+ }
+ return FT2FontEntry::CreateFontEntry(aFace, aFilename, aIndex, fontName);
+}
+
+FT2FontEntry*
+gfxFT2Font::GetFontEntry()
+{
+ return static_cast<FT2FontEntry*> (mFontEntry.get());
+}
+
+cairo_font_face_t *
+FT2FontEntry::CairoFontFace()
+{
+ if (!mFontFace) {
+ AutoFTFace face(this);
+ if (!face) {
+ return nullptr;
+ }
+ mFTFace = face.forget();
+ int flags = gfxPlatform::GetPlatform()->FontHintingEnabled() ?
+ FT_LOAD_DEFAULT :
+ (FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING);
+ mFontFace = cairo_ft_font_face_create_for_ft_face(face, flags);
+ FTUserFontData *userFontData = new FTUserFontData(face, face.FontData());
+ cairo_font_face_set_user_data(mFontFace, &sFTUserFontDataKey,
+ userFontData, FTFontDestroyFunc);
+ }
+ return mFontFace;
+}
+
+// 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) {
+ return NS_OK;
+ }
+
+ RefPtr<gfxCharacterMap> charmap = new gfxCharacterMap();
+
+ AutoTArray<uint8_t, 16384> buffer;
+ nsresult rv = CopyFontTable(TTAG_cmap, buffer);
+
+ if (NS_SUCCEEDED(rv)) {
+ bool unicodeFont;
+ bool symbolFont;
+ rv = gfxFontUtils::ReadCMAP(buffer.Elements(), buffer.Length(),
+ *charmap, mUVSOffset,
+ unicodeFont, symbolFont);
+ }
+
+ if (NS_SUCCEEDED(rv) && !HasGraphiteTables()) {
+ // We 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)) {
+ 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
+
+ mHasCmapTable = NS_SUCCEEDED(rv);
+ if (mHasCmapTable) {
+ gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList();
+ mCharacterMap = pfl->FindCharMap(charmap);
+ } else {
+ // if error occurred, initialize to null cmap
+ mCharacterMap = new gfxCharacterMap();
+ }
+ return rv;
+}
+
+nsresult
+FT2FontEntry::CopyFontTable(uint32_t aTableTag, nsTArray<uint8_t>& aBuffer)
+{
+ AutoFTFace face(this);
+ if (!face) {
+ return NS_ERROR_FAILURE;
+ }
+
+ FT_Error status;
+ FT_ULong len = 0;
+ status = FT_Load_Sfnt_Table(face, aTableTag, 0, nullptr, &len);
+ if (status != FT_Err_Ok || len == 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!aBuffer.SetLength(len, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ uint8_t *buf = aBuffer.Elements();
+ status = FT_Load_Sfnt_Table(face, aTableTag, 0, buf, &len);
+ NS_ENSURE_TRUE(status == FT_Err_Ok, NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+hb_blob_t*
+FT2FontEntry::GetFontTable(uint32_t aTableTag)
+{
+ if (mFontFace) {
+ // 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
+ FTUserFontData *userFontData = static_cast<FTUserFontData*>(
+ cairo_font_face_get_user_data(mFontFace, &sFTUserFontDataKey));
+ if (userFontData && userFontData->FontData()) {
+ return gfxFontUtils::GetTableFromFontData(userFontData->FontData(),
+ aTableTag);
+ }
+ }
+
+ // otherwise, use the default method (which in turn will call our
+ // implementation of CopyFontTable)
+ return gfxFontEntry::GetFontTable(aTableTag);
+}
+
+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(InfallibleTArray<FontListEntry>* aFontList,
+ Visibility aVisibility)
+{
+ 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(), fe->Stretch(),
+ fe->mStyle,
+ fe->mFTFontIndex,
+ aVisibility == kHidden));
+ }
+}
+
+/*
+ * 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"
+
+class FontNameCache {
+public:
+ // 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()
+ {
+ if (!mWriteNeeded || !mCache) {
+ return;
+ }
+
+ nsAutoCString buf;
+ for (auto iter = mMap.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<FNCMapEntry*>(iter.Get());
+ if (!entry->mFileExists) {
+ // skip writing entries for files that are no longer present
+ continue;
+ }
+ buf.Append(entry->mFilename);
+ buf.Append(';');
+ buf.Append(entry->mFaces);
+ buf.Append(';');
+ buf.AppendInt(entry->mTimestamp);
+ buf.Append(';');
+ buf.AppendInt(entry->mFilesize);
+ buf.Append(';');
+ }
+ mCache->PutBuffer(CACHE_KEY, buf.get(), buf.Length() + 1);
+ }
+
+ // This may be called more than once (if we re-load the font list).
+ void Init()
+ {
+ if (!mCache) {
+ return;
+ }
+
+ uint32_t size;
+ UniquePtr<char[]> buf;
+ if (NS_FAILED(mCache->GetBuffer(CACHE_KEY, &buf, &size))) {
+ return;
+ }
+
+ LOG(("got: %s from the cache", nsDependentCString(buf.get(), size).get()));
+
+ mMap.Clear();
+ mWriteNeeded = false;
+
+ const char* beginning = buf.get();
+ const char* end = strchr(beginning, ';');
+ while (end) {
+ nsCString filename(beginning, end - beginning);
+ beginning = end + 1;
+ if (!(end = strchr(beginning, ';'))) {
+ break;
+ }
+ nsCString faceList(beginning, end - beginning);
+ beginning = end + 1;
+ if (!(end = strchr(beginning, ';'))) {
+ break;
+ }
+ uint32_t timestamp = strtoul(beginning, nullptr, 10);
+ beginning = end + 1;
+ if (!(end = strchr(beginning, ';'))) {
+ break;
+ }
+ uint32_t filesize = strtoul(beginning, 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;
+ }
+
+ beginning = end + 1;
+ end = strchr(beginning, ';');
+ }
+ }
+
+ 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;
+
+ typedef struct : public PLDHashEntryHdr {
+ public:
+ nsCString mFilename;
+ uint32_t mTimestamp;
+ uint32_t mFilesize;
+ nsCString mFaces;
+ bool mFileExists;
+ } FNCMapEntry;
+
+ 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)
+ { }
+
+protected:
+ virtual ~WillShutdownObserver()
+ { }
+
+ 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 {
+ NS_NOTREACHED("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()
+{
+ if (mObserver) {
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(mObserver, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
+ }
+ mObserver = nullptr;
+ }
+}
+
+void
+gfxFT2FontList::AppendFacesFromCachedFaceList(
+ const nsCString& aFileName,
+ const nsCString& aFaceList,
+ StandardFile aStdFile,
+ FT2FontFamily::Visibility aVisibility)
+{
+ const char *beginning = aFaceList.get();
+ const char *end = strchr(beginning, ',');
+ while (end) {
+ NS_ConvertUTF8toUTF16 familyName(beginning, end - beginning);
+ ToLowerCase(familyName);
+ beginning = end + 1;
+ if (!(end = strchr(beginning, ','))) {
+ break;
+ }
+ NS_ConvertUTF8toUTF16 faceName(beginning, end - beginning);
+ beginning = end + 1;
+ if (!(end = strchr(beginning, ','))) {
+ break;
+ }
+ uint32_t index = strtoul(beginning, nullptr, 10);
+ beginning = end + 1;
+ if (!(end = strchr(beginning, ','))) {
+ break;
+ }
+ bool italic = (*beginning != '0');
+ beginning = end + 1;
+ if (!(end = strchr(beginning, ','))) {
+ break;
+ }
+ uint32_t weight = strtoul(beginning, nullptr, 10);
+ beginning = end + 1;
+ if (!(end = strchr(beginning, ','))) {
+ break;
+ }
+ int32_t stretch = strtol(beginning, nullptr, 10);
+
+ FontListEntry fle(familyName, faceName, aFileName,
+ weight, stretch, italic, index,
+ aVisibility == FT2FontFamily::kHidden);
+ AppendFaceFromFontListEntry(fle, aStdFile);
+
+ beginning = end + 1;
+ end = strchr(beginning, ',');
+ }
+}
+
+static void
+AppendToFaceList(nsCString& aFaceList,
+ nsAString& aFamilyName, FT2FontEntry* aFontEntry)
+{
+ aFaceList.Append(NS_ConvertUTF16toUTF8(aFamilyName));
+ aFaceList.Append(',');
+ aFaceList.Append(NS_ConvertUTF16toUTF8(aFontEntry->Name()));
+ aFaceList.Append(',');
+ aFaceList.AppendInt(aFontEntry->mFTFontIndex);
+ aFaceList.Append(',');
+ aFaceList.Append(aFontEntry->IsItalic() ? '1' : '0');
+ aFaceList.Append(',');
+ aFaceList.AppendInt(aFontEntry->Weight());
+ aFaceList.Append(',');
+ aFaceList.AppendInt(aFontEntry->Stretch());
+ aFaceList.Append(',');
+}
+
+void
+FT2FontEntry::CheckForBrokenFont(gfxFontFamily *aFamily)
+{
+ // note if the family is in the "bad underline" blacklist
+ if (aFamily->IsBadUnderlineFamily()) {
+ mIsBadUnderlineFont = true;
+ }
+
+ // bug 721719 - set the IgnoreGSUB flag on entries for Roboto
+ // because of unwanted on-by-default "ae" ligature.
+ // (See also AppendFaceFromFontListEntry.)
+ if (aFamily->Name().EqualsLiteral("roboto")) {
+ mIgnoreGSUB = true;
+ }
+
+ // 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
+ else if (aFamily->Name().EqualsLiteral("droid sans arabic")) {
+ AutoFTFace face(this);
+ if (face) {
+ const TT_Header *head = static_cast<const TT_Header*>
+ (FT_Get_Sfnt_Table(face, ft_sfnt_head));
+ if (head && head->CheckSum_Adjust == 0xe445242) {
+ mIgnoreGSUB = true;
+ }
+ }
+ }
+}
+
+void
+gfxFT2FontList::AppendFacesFromFontFile(const nsCString& aFileName,
+ FontNameCache *aCache,
+ StandardFile aStdFile,
+ FT2FontFamily::Visibility aVisibility)
+{
+ 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 &&
+ s.st_mtime == timestamp && s.st_size == filesize)
+ {
+ LOG(("using cached font info for %s", aFileName.get()));
+ AppendFacesFromCachedFaceList(aFileName, cachedFaceList, aStdFile,
+ aVisibility);
+ return;
+ }
+
+ FT_Library ftLibrary = gfxAndroidPlatform::GetPlatform()->GetFTLibrary();
+ FT_Face dummy;
+ if (FT_Err_Ok == FT_New_Face(ftLibrary, aFileName.get(), -1, &dummy)) {
+ LOG(("reading font info via FreeType for %s", aFileName.get()));
+ nsCString newFaceList;
+ timestamp = s.st_mtime;
+ filesize = s.st_size;
+ for (FT_Long i = 0; i < dummy->num_faces; i++) {
+ FT_Face face;
+ if (FT_Err_Ok != FT_New_Face(ftLibrary, aFileName.get(), i, &face)) {
+ continue;
+ }
+ AddFaceToList(aFileName, i, aStdFile, aVisibility, face, newFaceList);
+ FT_Done_Face(face);
+ }
+ FT_Done_Face(dummy);
+ if (aCache && 0 == statRetval && !newFaceList.IsEmpty()) {
+ aCache->CacheFileInfo(aFileName, newFaceList, timestamp, filesize);
+ }
+ }
+}
+
+void
+gfxFT2FontList::FindFontsInOmnijar(FontNameCache *aCache)
+{
+ bool jarChanged = false;
+
+ mozilla::scache::StartupCache* cache =
+ mozilla::scache::StartupCache::GetSingleton();
+ UniquePtr<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 > *(int64_t*)cachedModifiedTimeBuf.get()) {
+ 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;
+ }
+ }
+}
+
+// Given the freetype 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,
+ FT2FontFamily::Visibility aVisibility,
+ FT_Face aFace,
+ nsCString& aFaceList)
+{
+ if (FT_Err_Ok != FT_Select_Charmap(aFace, FT_ENCODING_UNICODE)) {
+ // ignore faces that don't support a Unicode charmap
+ return;
+ }
+
+ // build the font entry name and create an FT2FontEntry,
+ // but do -not- keep a reference to the FT_Face
+ RefPtr<FT2FontEntry> fe =
+ CreateNamedFontEntry(aFace, aEntryName.get(), aIndex);
+
+ auto& fontFamilies =
+ (aVisibility == FT2FontFamily::kHidden) ? mHiddenFontFamilies :
+ mFontFamilies;
+
+ if (fe) {
+ NS_ConvertUTF8toUTF16 name(aFace->family_name);
+ BuildKeyNameFromFontName(name);
+ RefPtr<gfxFontFamily> family = fontFamilies.GetWeak(name);
+ if (!family) {
+ family = new FT2FontFamily(name);
+ fontFamilies.Put(name, family);
+ if (mSkipSpaceLookupCheckFamilies.Contains(name)) {
+ family->SetSkipSpaceFeatureCheck(true);
+ }
+ if (mBadUnderlineFamilyNames.Contains(name)) {
+ family->SetBadUnderlineFamily();
+ }
+ }
+ fe->mStandardFace = (aStdFile == kStandard);
+ family->AddFontEntry(fe);
+
+ fe->CheckForBrokenFont(family);
+
+ AppendToFaceList(aFaceList, name, fe);
+ if (LOG_ENABLED()) {
+ LOG(("(fontinit) added (%s) to family (%s)"
+ " with style: %s weight: %d stretch: %d",
+ NS_ConvertUTF16toUTF8(fe->Name()).get(),
+ NS_ConvertUTF16toUTF8(family->Name()).get(),
+ fe->IsItalic() ? "italic" : "normal",
+ fe->Weight(), fe->Stretch()));
+ }
+ }
+}
+
+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) {
+ AppendFacesFromCachedFaceList(aEntryName, faceList);
+ 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.
+ auto buf = MakeUniqueFallible<uint8_t[]>(bufSize);
+ if (!buf) {
+ return;
+ }
+
+ nsZipCursor cursor(item, aArchive, buf.get(), bufSize);
+ uint8_t* data = cursor.Copy(&bufSize);
+ NS_ASSERTION(data && bufSize == item->RealSize(),
+ "error reading bundled font");
+ if (!data) {
+ return;
+ }
+
+ FT_Library ftLibrary = gfxAndroidPlatform::GetPlatform()->GetFTLibrary();
+
+ FT_Face dummy;
+ if (FT_Err_Ok != FT_New_Memory_Face(ftLibrary, buf.get(), bufSize, 0, &dummy)) {
+ return;
+ }
+
+ for (FT_Long i = 0; i < dummy->num_faces; i++) {
+ FT_Face face;
+ if (FT_Err_Ok != FT_New_Memory_Face(ftLibrary, buf.get(), bufSize, i, &face)) {
+ continue;
+ }
+ AddFaceToList(aEntryName, i, kStandard, FT2FontFamily::kVisible,
+ face, faceList);
+ FT_Done_Face(face);
+ }
+
+ FT_Done_Face(dummy);
+
+ if (aCache && !faceList.IsEmpty()) {
+ aCache->CacheFileInfo(aEntryName, faceList, 0, bufSize);
+ }
+}
+
+// Called on each family after all fonts are added to the list;
+// this will sort faces to give priority to "standard" font files
+// if aUserArg is non-null (i.e. we're using it as a boolean flag)
+static void
+FinalizeFamilyMemberList(nsStringHashKey::KeyType aKey,
+ RefPtr<gfxFontFamily>& aFamily,
+ bool aSortFaces)
+{
+ gfxFontFamily *family = aFamily.get();
+
+ family->SetHasStyles(true);
+
+ if (aSortFaces) {
+ family->SortAvailableFonts();
+ }
+ family->CheckForSimpleFamily();
+}
+
+void
+gfxFT2FontList::FindFonts()
+{
+ gfxFontCache *fc = gfxFontCache::GetCache();
+ if (fc)
+ fc->AgeAllGenerations();
+ ClearLangGroupPrefFonts();
+ mCodepointsWithNoFonts.reset();
+
+ mCodepointsWithNoFonts.SetRange(0,0x1f); // C0 controls
+ mCodepointsWithNoFonts.SetRange(0x7f,0x9f); // C1 controls
+
+ if (!XRE_IsParentProcess()) {
+ // Content process: ask the Chrome process to give us the list
+ InfallibleTArray<FontListEntry> fonts;
+ mozilla::dom::ContentChild::GetSingleton()->SendReadFontList(&fonts);
+ for (uint32_t i = 0, n = fonts.Length(); i < n; ++i) {
+ // We don't need to identify "standard" font files here,
+ // as the faces are already sorted.
+ AppendFaceFromFontListEntry(fonts[i], kUnknown);
+ }
+ // Passing null for userdata tells Finalize that it does not need
+ // to sort faces (because they were already sorted by chrome,
+ // so we just maintain the existing order)
+ for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
+ nsStringHashKey::KeyType key = iter.Key();
+ RefPtr<gfxFontFamily>& family = iter.Data();
+ FinalizeFamilyMemberList(key, family, /* aSortFaces */ false);
+ }
+ for (auto iter = mHiddenFontFamilies.Iter(); !iter.Done(); iter.Next()) {
+ nsStringHashKey::KeyType key = iter.Key();
+ RefPtr<gfxFontFamily>& family = iter.Data();
+ FinalizeFamilyMemberList(key, family, /* aSortFaces */ false );
+ }
+
+ LOG(("got font list from chrome process: %d faces in %d families "
+ "and %d in hidden families",
+ fonts.Length(), mFontFamilies.Count(),
+ mHiddenFontFamilies.Count()));
+ return;
+ }
+
+ // Chrome process: get the cached list (if any)
+ if (!mFontNameCache) {
+ mFontNameCache = MakeUnique<FontNameCache>();
+ }
+ mFontNameCache->Init();
+
+ // ANDROID_ROOT is the root of the android system, typically /system;
+ // font files are in /$ANDROID_ROOT/fonts/
+ nsCString root;
+ char *androidRoot = PR_GetEnv("ANDROID_ROOT");
+ if (androidRoot) {
+ root = androidRoot;
+ } else {
+ root = NS_LITERAL_CSTRING("/system");
+ }
+ root.AppendLiteral("/fonts");
+
+ FindFontsInDir(root, mFontNameCache.get(), FT2FontFamily::kVisible);
+
+ if (mFontFamilies.Count() == 0) {
+ // if we can't find/read the font directory, we are doomed!
+ NS_RUNTIMEABORT("Could not read the system fonts directory");
+ }
+
+ // 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.)
+ bool lowmem;
+ nsCOMPtr<nsIMemory> mem = nsMemory::GetGlobalMemoryService();
+ if ((NS_SUCCEEDED(mem->IsLowMemoryPlatform(&lowmem)) && !lowmem &&
+ Preferences::GetBool("gfx.bundled_fonts.enabled")) ||
+ Preferences::GetBool("gfx.bundled_fonts.force-enabled")) {
+ FindFontsInOmnijar(mFontNameCache.get());
+ }
+
+ // 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(NS_LITERAL_CSTRING("fonts"));
+ nsCString localPath;
+ if (NS_SUCCEEDED(appDir->GetNativePath(localPath))) {
+ FindFontsInDir(localPath, mFontNameCache.get(),
+ FT2FontFamily::kVisible);
+ }
+ }
+ }
+
+ // 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(NS_LITERAL_STRING("fonts")))) {
+ nsCString localPath;
+ rv = localDir->GetNativePath(localPath);
+ if (NS_SUCCEEDED(rv)) {
+ FindFontsInDir(localPath, mFontNameCache.get(),
+ FT2FontFamily::kVisible);
+ }
+ }
+
+ // Finalize the families by sorting faces into standard order
+ // and marking "simple" families.
+ // Passing non-null userData here says that we want faces to be sorted.
+ for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
+ nsStringHashKey::KeyType key = iter.Key();
+ RefPtr<gfxFontFamily>& family = iter.Data();
+ FinalizeFamilyMemberList(key, family, /* aSortFaces */ true);
+ }
+ for (auto iter = mHiddenFontFamilies.Iter(); !iter.Done(); iter.Next()) {
+ nsStringHashKey::KeyType key = iter.Key();
+ RefPtr<gfxFontFamily>& family = iter.Data();
+ FinalizeFamilyMemberList(key, family, /* aSortFaces */ true);
+ }
+}
+
+void
+gfxFT2FontList::FindFontsInDir(const nsCString& aDir,
+ FontNameCache *aFNC,
+ FT2FontFamily::Visibility aVisibility)
+{
+ 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",
+ "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;
+ }
+
+ nsCString 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,
+ aVisibility);
+ }
+ }
+
+ closedir(d);
+}
+
+void
+gfxFT2FontList::AppendFaceFromFontListEntry(const FontListEntry& aFLE,
+ StandardFile aStdFile)
+{
+ FT2FontEntry* fe = FT2FontEntry::CreateFontEntry(aFLE);
+ if (fe) {
+ auto& fontFamilies =
+ aFLE.isHidden() ? mHiddenFontFamilies : mFontFamilies;
+ fe->mStandardFace = (aStdFile == kStandard);
+ nsAutoString name(aFLE.familyName());
+ RefPtr<gfxFontFamily> family = fontFamilies.GetWeak(name);
+ if (!family) {
+ family = new FT2FontFamily(name);
+ fontFamilies.Put(name, family);
+ if (mSkipSpaceLookupCheckFamilies.Contains(name)) {
+ family->SetSkipSpaceFeatureCheck(true);
+ }
+ if (mBadUnderlineFamilyNames.Contains(name)) {
+ family->SetBadUnderlineFamily();
+ }
+ }
+ family->AddFontEntry(fe);
+
+ fe->CheckForBrokenFont(family);
+ }
+}
+
+void
+gfxFT2FontList::GetSystemFontList(InfallibleTArray<FontListEntry>* retValue)
+{
+ for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
+ auto family = static_cast<FT2FontFamily*>(iter.Data().get());
+ family->AddFacesToFontList(retValue, FT2FontFamily::kVisible);
+ }
+ for (auto iter = mHiddenFontFamilies.Iter(); !iter.Done(); iter.Next()) {
+ auto family = static_cast<FT2FontFamily*>(iter.Data().get());
+ family->AddFacesToFontList(retValue, FT2FontFamily::kHidden);
+ }
+}
+
+static void
+LoadSkipSpaceLookupCheck(nsTHashtable<nsStringHashKey>& aSkipSpaceLookupCheck)
+{
+ AutoTArray<nsString, 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.PutEntry(skiplist[i]);
+ }
+}
+
+void
+PreloadAsUserFontFaces(nsStringHashKey::KeyType aKey,
+ RefPtr<gfxFontFamily>& aFamily)
+{
+ gfxFontFamily *family = aFamily.get();
+
+ auto& faces = family->GetFontList();
+ size_t count = faces.Length();
+ for (size_t i = 0; i < count; ++i) {
+ FT2FontEntry* fe = static_cast<FT2FontEntry*>(faces[i].get());
+ if (fe->mFTFontIndex != 0) {
+ NS_NOTREACHED("don't try to preload a multi-face font");
+ continue;
+ }
+
+ // XXX Should we move the i/o here off the main thread?
+
+ // Map the font data in fe->mFilename, so we can calculate its CRC32.
+ int fd = open(fe->mFilename.get(), O_RDONLY);
+ if (fd < 0) {
+ continue;
+ }
+ struct stat buf;
+ if (fstat(fd, &buf) != 0 || buf.st_size < 12) {
+ close(fd);
+ continue;
+ }
+ char* data = static_cast<char*>(
+ mmap(0, buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0));
+ close(fd);
+ if (data == MAP_FAILED) {
+ continue;
+ }
+
+ // Calculate CRC32
+ uint32_t crc = crc32(0, nullptr, 0);
+ crc = crc32(crc, (Bytef*)data, buf.st_size);
+ munmap(data, buf.st_size);
+
+#if 0
+ ALOG("\n**** Preloading family [%s] face [%s] CRC32 [0x%08x]",
+ NS_ConvertUTF16toUTF8(family->Name()).get(),
+ fe->mFilename.get(),
+ crc);
+#endif
+
+ fe->mUserFontData = MakeUnique<gfxUserFontData>();
+ fe->mUserFontData->mRealName = fe->Name();
+ fe->mUserFontData->mCRC32 = crc;
+ fe->mUserFontData->mLength = buf.st_size;
+
+ // Stash it persistently in the user-font cache.
+ gfxUserFontSet::UserFontCache::CacheFont(
+ fe, gfxUserFontSet::UserFontCache::kPersistent);
+ }
+}
+
+nsresult
+gfxFT2FontList::InitFontListForPlatform()
+{
+ // reset hidden font list
+ mHiddenFontFamilies.Clear();
+
+ LoadSkipSpaceLookupCheck(mSkipSpaceLookupCheckFamilies);
+
+ FindFonts();
+
+ for (auto iter = mHiddenFontFamilies.Iter(); !iter.Done(); iter.Next()) {
+ nsStringHashKey::KeyType key = iter.Key();
+ RefPtr<gfxFontFamily>& family = iter.Data();
+ PreloadAsUserFontFaces(key, family);
+ }
+ return NS_OK;
+}
+
+// called for each family name, based on the assumption that the
+// first part of the full name is the family name
+
+gfxFontEntry*
+gfxFT2FontList::LookupLocalFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle)
+{
+ // walk over list of names
+ FT2FontEntry* fontEntry = nullptr;
+ nsString fullName(aFontName);
+
+ // Note that we only check mFontFamilies here, not mHiddenFontFamilies;
+ // hence @font-face { src:local(...) } will not find hidden fonts.
+ for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
+ // Check family name, based on the assumption that the
+ // first part of the full name is the family name
+ RefPtr<gfxFontFamily>& fontFamily = iter.Data();
+
+ // does the family name match up to the length of the family name?
+ const nsString& family = fontFamily->Name();
+ nsString fullNameFamily;
+
+ fullName.Left(fullNameFamily, family.Length());
+
+ // if so, iterate over faces in this family to see if there is a match
+ if (family.Equals(fullNameFamily, nsCaseInsensitiveStringComparator())) {
+ nsTArray<RefPtr<gfxFontEntry> >& fontList = fontFamily->GetFontList();
+ int index, len = fontList.Length();
+ for (index = 0; index < len; index++) {
+ gfxFontEntry* fe = fontList[index];
+ if (!fe) {
+ continue;
+ }
+ if (fe->Name().Equals(fullName,
+ nsCaseInsensitiveStringComparator())) {
+ 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
+ fontEntry->CairoFontFace();
+ if (!fontEntry->mFTFace) {
+ return nullptr;
+ }
+
+ FT2FontEntry* fe =
+ FT2FontEntry::CreateFontEntry(fontEntry->mFTFace,
+ fontEntry->mFilename.get(),
+ fontEntry->mFTFontIndex,
+ fontEntry->Name(), nullptr);
+ if (fe) {
+ fe->mStyle = aStyle;
+ fe->mWeight = aWeight;
+ fe->mStretch = aStretch;
+ fe->mIsLocalUserFont = true;
+ }
+
+ return fe;
+}
+
+gfxFontFamily*
+gfxFT2FontList::GetDefaultFontForPlatform(const gfxFontStyle* aStyle)
+{
+ gfxFontFamily *ff = nullptr;
+#if defined(MOZ_WIDGET_ANDROID)
+ ff = FindFamily(NS_LITERAL_STRING("Roboto"));
+ if (!ff) {
+ ff = FindFamily(NS_LITERAL_STRING("Droid Sans"));
+ }
+#endif
+ /* TODO: what about Qt or other platforms that may use this? */
+ return ff;
+}
+
+gfxFontEntry*
+gfxFT2FontList::MakePlatformFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ 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, aWeight, aStretch,
+ aStyle, aFontData, aLength);
+}
+
+void
+gfxFT2FontList::GetFontFamilyList(nsTArray<RefPtr<gfxFontFamily> >& aFamilyArray)
+{
+ for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<gfxFontFamily>& family = iter.Data();
+ aFamilyArray.AppendElement(family);
+ }
+ for (auto iter = mHiddenFontFamilies.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<gfxFontFamily>& family = iter.Data();
+ aFamilyArray.AppendElement(family);
+ }
+}
+
+void
+gfxFT2FontList::WillShutdown()
+{
+ mozilla::scache::StartupCache* cache =
+ mozilla::scache::StartupCache::GetSingleton();
+ if (cache && mJarModifiedTime > 0) {
+ cache->PutBuffer(JAR_LAST_MODIFED_TIME,
+ (char*)&mJarModifiedTime, sizeof(mJarModifiedTime));
+ }
+ mFontNameCache = nullptr;
+}
diff --git a/gfx/thebes/gfxFT2FontList.h b/gfx/thebes/gfxFT2FontList.h
new file mode 100644
index 000000000..63187ba26
--- /dev/null
+++ b/gfx/thebes/gfxFT2FontList.h
@@ -0,0 +1,200 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxPlatformFontList.h"
+
+namespace mozilla {
+ namespace dom {
+ class FontListEntry;
+ };
+};
+using mozilla::dom::FontListEntry;
+
+class FontNameCache;
+typedef struct FT_FaceRec_* FT_Face;
+class nsZipArchive;
+
+class FT2FontEntry : public gfxFontEntry
+{
+public:
+ FT2FontEntry(const nsAString& aFaceName) :
+ gfxFontEntry(aFaceName),
+ mFTFace(nullptr),
+ mFontFace(nullptr),
+ mFTFontIndex(0)
+ {
+ }
+
+ ~FT2FontEntry();
+
+ const nsString& GetName() const {
+ return Name();
+ }
+
+ // create a font entry for a downloaded font
+ static FT2FontEntry*
+ CreateFontEntry(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t 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 for a given freetype face; if it is an installed font,
+ // also record the filename and index.
+ // aFontData (if non-nullptr) is NS_Malloc'ed data that aFace depends on,
+ // to be freed after the face is destroyed
+ static FT2FontEntry*
+ CreateFontEntry(FT_Face aFace,
+ const char *aFilename, uint8_t aIndex,
+ const nsAString& aName,
+ const uint8_t* aFontData = nullptr);
+
+ virtual gfxFont *CreateFontInstance(const gfxFontStyle *aFontStyle,
+ bool aNeedsBold) override;
+
+ // Create (if necessary) and return the cairo_font_face for this font.
+ // This may fail and return null, so caller must be prepared to handle this.
+ cairo_font_face_t *CairoFontFace();
+
+ // Create a cairo_scaled_font for this face, with the given style.
+ // This may fail and return null, so caller must be prepared to handle this.
+ cairo_scaled_font_t *CreateScaledFont(const gfxFontStyle *aStyle);
+
+ nsresult ReadCMAP(FontInfoData *aFontInfoData = nullptr) override;
+
+ virtual hb_blob_t* GetFontTable(uint32_t aTableTag) override;
+
+ virtual nsresult CopyFontTable(uint32_t aTableTag,
+ nsTArray<uint8_t>& aBuffer) 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);
+
+ virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const override;
+ virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const override;
+
+ FT_Face mFTFace;
+ cairo_font_face_t *mFontFace;
+
+ nsCString mFilename;
+ uint8_t mFTFontIndex;
+};
+
+class FT2FontFamily : public gfxFontFamily
+{
+public:
+ // Flags to indicate whether a font should be "visible" in the global
+ // font list (available for use in font-family), or "hidden" (available
+ // only to support a matching data: URI used in @font-face).
+ typedef enum {
+ kVisible,
+ kHidden
+ } Visibility;
+
+ FT2FontFamily(const nsAString& aName) :
+ gfxFontFamily(aName) { }
+
+ // Append this family's faces to the IPC fontlist
+ void AddFacesToFontList(InfallibleTArray<FontListEntry>* aFontList,
+ Visibility aVisibility);
+};
+
+class gfxFT2FontList : public gfxPlatformFontList
+{
+public:
+ gfxFT2FontList();
+ virtual ~gfxFT2FontList();
+
+ virtual gfxFontEntry* LookupLocalFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle);
+
+ virtual gfxFontEntry* MakePlatformFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t* aFontData,
+ uint32_t aLength);
+
+ void GetSystemFontList(InfallibleTArray<FontListEntry>* retValue);
+
+ static gfxFT2FontList* PlatformFontList() {
+ return static_cast<gfxFT2FontList*>(gfxPlatformFontList::PlatformFontList());
+ }
+
+ virtual void GetFontFamilyList(nsTArray<RefPtr<gfxFontFamily> >& aFamilyArray);
+
+ void WillShutdown();
+
+protected:
+ typedef enum {
+ kUnknown,
+ kStandard
+ } StandardFile;
+
+ // initialize font lists
+ virtual nsresult InitFontListForPlatform() override;
+
+ void AppendFaceFromFontListEntry(const FontListEntry& aFLE,
+ StandardFile aStdFile);
+
+ void AppendFacesFromFontFile(const nsCString& aFileName,
+ FontNameCache *aCache,
+ StandardFile aStdFile,
+ FT2FontFamily::Visibility aVisibility);
+
+ void AppendFacesFromOmnijarEntry(nsZipArchive *aReader,
+ const nsCString& aEntryName,
+ FontNameCache *aCache,
+ bool aJarChanged);
+
+ // the defaults here are suitable for reading bundled fonts from omnijar
+ void AppendFacesFromCachedFaceList(const nsCString& aFileName,
+ const nsCString& aFaceList,
+ StandardFile aStdFile = kStandard,
+ FT2FontFamily::Visibility aVisibility =
+ FT2FontFamily::kVisible);
+
+ void AddFaceToList(const nsCString& aEntryName, uint32_t aIndex,
+ StandardFile aStdFile,
+ FT2FontFamily::Visibility aVisibility,
+ FT_Face aFace, nsCString& aFaceList);
+
+ void FindFonts();
+
+ void FindFontsInOmnijar(FontNameCache *aCache);
+
+ void FindFontsInDir(const nsCString& aDir, FontNameCache* aFNC,
+ FT2FontFamily::Visibility aVisibility);
+
+ virtual gfxFontFamily*
+ GetDefaultFontForPlatform(const gfxFontStyle* aStyle) override;
+
+ nsTHashtable<nsStringHashKey> mSkipSpaceLookupCheckFamilies;
+
+private:
+ FontFamilyTable mHiddenFontFamilies;
+
+ mozilla::UniquePtr<FontNameCache> mFontNameCache;
+ int64_t mJarModifiedTime;
+ nsCOMPtr<nsIObserver> mObserver;
+};
+
+#endif /* GFX_FT2FONTLIST_H */
diff --git a/gfx/thebes/gfxFT2Fonts.cpp b/gfx/thebes/gfxFT2Fonts.cpp
new file mode 100644
index 000000000..190254191
--- /dev/null
+++ b/gfx/thebes/gfxFT2Fonts.cpp
@@ -0,0 +1,227 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "nsUnicodeRange.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"
+
+/**
+ * gfxFT2Font
+ */
+
+bool
+gfxFT2Font::ShapeText(DrawTarget *aDrawTarget,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxShapedText *aShapedText)
+{
+ if (!gfxFont::ShapeText(aDrawTarget, aText, aOffset, aLength, aScript,
+ aVertical, 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)
+{
+ 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();
+
+ gfxShapedText::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(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(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 &&
+ gfxShapedText::CompressedGlyph::IsSimpleAdvance(advance) &&
+ gfxShapedText::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;
+ details.mXOffset = 0;
+ details.mYOffset = 0;
+ gfxShapedText::CompressedGlyph g;
+ g.SetComplex(charGlyphs[aOffset].IsClusterStart(), true, 1);
+ aShapedText->SetGlyphs(aOffset, g, &details);
+ }
+ }
+}
+
+gfxFT2Font::gfxFT2Font(cairo_scaled_font_t *aCairoFont,
+ FT2FontEntry *aFontEntry,
+ const gfxFontStyle *aFontStyle,
+ bool aNeedsBold)
+ : gfxFT2FontBase(aCairoFont, aFontEntry, aFontStyle)
+ , mCharGlyphCache(32)
+{
+ NS_ASSERTION(mFontEntry, "Unable to find font entry for font. Something is whack.");
+ mApplySyntheticBold = aNeedsBold;
+}
+
+gfxFT2Font::~gfxFT2Font()
+{
+}
+
+void
+gfxFT2Font::FillGlyphDataForChar(uint32_t ch, CachedGlyphData *gd)
+{
+ gfxFT2LockedFace faceLock(this);
+ FT_Face face = faceLock.get();
+
+ if (!face->charmap || face->charmap->encoding != FT_ENCODING_UNICODE) {
+ FT_Select_Charmap(face, FT_ENCODING_UNICODE);
+ }
+ 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_Int32 flags = gfxPlatform::GetPlatform()->FontHintingEnabled() ?
+ FT_LOAD_DEFAULT :
+ (FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING);
+ FT_Error err = FT_Load_Glyph(face, gid, flags);
+
+ 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;
+}
+
+void
+gfxFT2Font::AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const
+{
+ gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+ aSizes->mFontInstances +=
+ mCharGlyphCache.ShallowSizeOfExcludingThis(aMallocSizeOf);
+}
+
+void
+gfxFT2Font::AddSizeOfIncludingThis(mozilla::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 000000000..8e4691c06
--- /dev/null
+++ b/gfx/thebes/gfxFT2Fonts.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "cairo.h"
+#include "gfxTypes.h"
+#include "gfxFont.h"
+#include "gfxFT2FontBase.h"
+#include "gfxContext.h"
+#include "gfxFontUtils.h"
+#include "gfxUserFontSet.h"
+
+class FT2FontEntry;
+
+class gfxFT2Font : public gfxFT2FontBase {
+public: // new functions
+ gfxFT2Font(cairo_scaled_font_t *aCairoFont,
+ FT2FontEntry *aFontEntry,
+ const gfxFontStyle *aFontStyle,
+ bool aNeedsBold);
+ virtual ~gfxFT2Font ();
+
+ FT2FontEntry *GetFontEntry();
+
+ struct CachedGlyphData {
+ CachedGlyphData()
+ : glyphIndex(0xffffffffU) { }
+
+ CachedGlyphData(uint32_t gid)
+ : glyphIndex(gid) { }
+
+ uint32_t glyphIndex;
+ int32_t lsbDelta;
+ int32_t rsbDelta;
+ int32_t xAdvance;
+ };
+
+ const CachedGlyphData* GetGlyphDataForChar(uint32_t ch) {
+ CharGlyphMapEntryType *entry = mCharGlyphCache.PutEntry(ch);
+
+ if (!entry)
+ return nullptr;
+
+ if (entry->mData.glyphIndex == 0xffffffffU) {
+ // this is a new entry, fill it
+ FillGlyphDataForChar(ch, &entry->mData);
+ }
+
+ return &entry->mData;
+ }
+
+ virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const override;
+ virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const override;
+
+protected:
+ virtual bool ShapeText(DrawTarget *aDrawTarget,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxShapedText *aShapedText) override;
+
+ void FillGlyphDataForChar(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 000000000..a544a8cb4
--- /dev/null
+++ b/gfx/thebes/gfxFT2Utils.cpp
@@ -0,0 +1,378 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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"
+#include FT_TRUETYPE_TAGS_H
+#include FT_TRUETYPE_TABLES_H
+#include <algorithm>
+
+#ifdef HAVE_FONTCONFIG_FCFREETYPE_H
+#include <fontconfig/fcfreetype.h>
+#endif
+
+#include "prlink.h"
+
+// 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;
+}
+
+void
+gfxFT2LockedFace::GetMetrics(gfxFont::Metrics* aMetrics,
+ uint32_t* aSpaceGlyph)
+{
+ NS_PRECONDITION(aMetrics != nullptr, "aMetrics must not be NULL");
+ NS_PRECONDITION(aSpaceGlyph != nullptr, "aSpaceGlyph must not be NULL");
+
+ if (MOZ_UNLIKELY(!mFace)) {
+ // No face. This unfortunate situation might happen if the font
+ // file is (re)moved at the wrong time.
+ const gfxFloat emHeight = mGfxFont->GetStyle()->size;
+ aMetrics->emHeight = emHeight;
+ aMetrics->maxAscent = aMetrics->emAscent = 0.8 * emHeight;
+ aMetrics->maxDescent = aMetrics->emDescent = 0.2 * emHeight;
+ aMetrics->maxHeight = emHeight;
+ aMetrics->internalLeading = 0.0;
+ aMetrics->externalLeading = 0.2 * emHeight;
+ const gfxFloat spaceWidth = 0.5 * emHeight;
+ aMetrics->spaceWidth = spaceWidth;
+ aMetrics->maxAdvance = spaceWidth;
+ aMetrics->aveCharWidth = spaceWidth;
+ aMetrics->zeroOrAveCharWidth = spaceWidth;
+ const gfxFloat xHeight = 0.5 * emHeight;
+ aMetrics->xHeight = xHeight;
+ aMetrics->capHeight = aMetrics->maxAscent;
+ const gfxFloat underlineSize = emHeight / 14.0;
+ aMetrics->underlineSize = underlineSize;
+ aMetrics->underlineOffset = -underlineSize;
+ aMetrics->strikeoutOffset = 0.25 * emHeight;
+ aMetrics->strikeoutSize = underlineSize;
+
+ *aSpaceGlyph = 0;
+ return;
+ }
+
+ const FT_Size_Metrics& ftMetrics = mFace->size->metrics;
+
+ 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(mFace)) {
+ // 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.
+ yScale = FLOAT_FROM_26_6(FLOAT_FROM_16_16(ftMetrics.y_scale));
+ emHeight = mFace->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.
+ const TT_Header* head =
+ static_cast<TT_Header*>(FT_Get_Sfnt_Table(mFace, ft_sfnt_head));
+ if (head) {
+ gfxFloat emUnit = head->Units_Per_EM;
+ yScale = emHeight / emUnit;
+ }
+ }
+
+ TT_OS2 *os2 =
+ static_cast<TT_OS2*>(FT_Get_Sfnt_Table(mFace, ft_sfnt_os2));
+
+ aMetrics->maxAscent = FLOAT_FROM_26_6(ftMetrics.ascender);
+ aMetrics->maxDescent = -FLOAT_FROM_26_6(ftMetrics.descender);
+ aMetrics->maxAdvance = FLOAT_FROM_26_6(ftMetrics.max_advance);
+
+ gfxFloat lineHeight;
+ if (os2 && os2->sTypoAscender && yScale > 0.0) {
+ aMetrics->emAscent = os2->sTypoAscender * yScale;
+ aMetrics->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) {
+ aMetrics->maxAscent = NS_round(aMetrics->emAscent);
+ aMetrics->maxDescent = NS_round(aMetrics->emDescent);
+ } else {
+ // maxAscent/maxDescent get used for frame heights, and some fonts
+ // don't have the HHEA table ascent/descent set (bug 279032).
+ // We use NS_round here to parallel the pixel-rounded values that
+ // freetype gives us for ftMetrics.ascender/descender.
+ aMetrics->maxAscent =
+ std::max(aMetrics->maxAscent, NS_round(aMetrics->emAscent));
+ aMetrics->maxDescent =
+ std::max(aMetrics->maxDescent, NS_round(aMetrics->emDescent));
+ }
+ } else {
+ aMetrics->emAscent = aMetrics->maxAscent;
+ aMetrics->emDescent = aMetrics->maxDescent;
+ lineHeight = FLOAT_FROM_26_6(ftMetrics.height);
+ }
+
+ cairo_text_extents_t extents;
+ *aSpaceGlyph = GetCharExtents(' ', &extents);
+ if (*aSpaceGlyph) {
+ aMetrics->spaceWidth = extents.x_advance;
+ } else {
+ aMetrics->spaceWidth = aMetrics->maxAdvance; // guess
+ }
+
+ aMetrics->zeroOrAveCharWidth = 0.0;
+ if (GetCharExtents('0', &extents)) {
+ aMetrics->zeroOrAveCharWidth = extents.x_advance;
+ }
+
+ // Prefering a measured x over sxHeight because sxHeight doesn't consider
+ // hinting, but maybe the x extents are not quite right in some fancy
+ // script fonts. CSS 2.1 suggests possibly using the height of an "o",
+ // which would have a more consistent glyph across fonts.
+ if (GetCharExtents('x', &extents) && extents.y_bearing < 0.0) {
+ aMetrics->xHeight = -extents.y_bearing;
+ aMetrics->aveCharWidth = extents.x_advance;
+ } else {
+ if (os2 && os2->sxHeight && yScale > 0.0) {
+ aMetrics->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."
+ aMetrics->xHeight = 0.5 * emHeight;
+ }
+ aMetrics->aveCharWidth = 0.0; // updated below
+ }
+
+ if (GetCharExtents('H', &extents) && extents.y_bearing < 0.0) {
+ aMetrics->capHeight = -extents.y_bearing;
+ } else {
+ if (os2 && os2->sCapHeight && yScale > 0.0) {
+ aMetrics->capHeight = os2->sCapHeight * yScale;
+ } else {
+ aMetrics->capHeight = aMetrics->maxAscent;
+ }
+ }
+
+ // 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.
+ gfxFloat avgCharWidth =
+ ScaleRoundDesignUnits(os2->xAvgCharWidth, ftMetrics.x_scale);
+ aMetrics->aveCharWidth =
+ std::max(aMetrics->aveCharWidth, avgCharWidth);
+ }
+ aMetrics->aveCharWidth =
+ std::max(aMetrics->aveCharWidth, aMetrics->zeroOrAveCharWidth);
+ if (aMetrics->aveCharWidth == 0.0) {
+ aMetrics->aveCharWidth = aMetrics->spaceWidth;
+ }
+ if (aMetrics->zeroOrAveCharWidth == 0.0) {
+ aMetrics->zeroOrAveCharWidth = aMetrics->aveCharWidth;
+ }
+ // Apparently hinting can mean that max_advance is not always accurate.
+ aMetrics->maxAdvance =
+ std::max(aMetrics->maxAdvance, aMetrics->aveCharWidth);
+
+ // 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 (mFace->underline_position && mFace->underline_thickness && yScale > 0.0) {
+ aMetrics->underlineSize = mFace->underline_thickness * yScale;
+ TT_Postscript *post = static_cast<TT_Postscript*>
+ (FT_Get_Sfnt_Table(mFace, ft_sfnt_post));
+ if (post && post->underlinePosition) {
+ aMetrics->underlineOffset = post->underlinePosition * yScale;
+ } else {
+ aMetrics->underlineOffset = mFace->underline_position * yScale
+ + 0.5 * aMetrics->underlineSize;
+ }
+ } else { // No underline info.
+ // Imitate Pango.
+ aMetrics->underlineSize = emHeight / 14.0;
+ aMetrics->underlineOffset = -aMetrics->underlineSize;
+ }
+
+ if (os2 && os2->yStrikeoutSize && os2->yStrikeoutPosition && yScale > 0.0) {
+ aMetrics->strikeoutSize = os2->yStrikeoutSize * yScale;
+ aMetrics->strikeoutOffset = os2->yStrikeoutPosition * yScale;
+ } else { // No strikeout info.
+ aMetrics->strikeoutSize = aMetrics->underlineSize;
+ // Use OpenType spec's suggested position for Roman font.
+ aMetrics->strikeoutOffset = emHeight * 409.0 / 2048.0
+ + 0.5 * aMetrics->strikeoutSize;
+ }
+ SnapLineToPixels(aMetrics->strikeoutOffset, aMetrics->strikeoutSize);
+
+ aMetrics->maxHeight = aMetrics->maxAscent + aMetrics->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.
+ aMetrics->emHeight = floor(emHeight + 0.5);
+
+ // maxHeight will normally be an integer, but round anyway in case
+ // FreeType is configured differently.
+ aMetrics->internalLeading =
+ floor(aMetrics->maxHeight - aMetrics->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, aMetrics->maxHeight) + 0.5);
+ aMetrics->externalLeading =
+ lineHeight - aMetrics->internalLeading - aMetrics->emHeight;
+
+ // Ensure emAscent + emDescent == emHeight
+ gfxFloat sum = aMetrics->emAscent + aMetrics->emDescent;
+ aMetrics->emAscent = sum > 0.0 ?
+ aMetrics->emAscent * aMetrics->emHeight / sum : 0.0;
+ aMetrics->emDescent = aMetrics->emHeight - aMetrics->emAscent;
+}
+
+uint32_t
+gfxFT2LockedFace::GetGlyph(uint32_t aCharCode)
+{
+ if (MOZ_UNLIKELY(!mFace))
+ return 0;
+
+#ifdef HAVE_FONTCONFIG_FCFREETYPE_H
+ // 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. (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) {
+ FT_Select_Charmap(mFace, FT_ENCODING_UNICODE);
+ }
+
+ return FcFreeTypeCharIndex(mFace, aCharCode);
+#else
+ return FT_Get_Char_Index(mFace, aCharCode);
+#endif
+}
+
+typedef FT_UInt (*GetCharVariantFunction)(FT_Face face,
+ FT_ULong charcode,
+ FT_ULong variantSelector);
+
+uint32_t
+gfxFT2LockedFace::GetUVSGlyph(uint32_t aCharCode, uint32_t aVariantSelector)
+{
+ NS_PRECONDITION(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 HAVE_FONTCONFIG_FCFREETYPE_H
+ // 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);
+}
+
+uint32_t
+gfxFT2LockedFace::GetCharExtents(char aChar, cairo_text_extents_t* aExtents)
+{
+ NS_PRECONDITION(aExtents != nullptr, "aExtents must not be NULL");
+
+ if (!mFace)
+ return 0;
+
+ FT_UInt gid = mGfxFont->GetGlyph(aChar);
+ if (gid) {
+ mGfxFont->GetGlyphExtents(gid, aExtents);
+ }
+
+ return gid;
+}
+
+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;
+}
diff --git a/gfx/thebes/gfxFT2Utils.h b/gfx/thebes/gfxFT2Utils.h
new file mode 100644
index 000000000..35958bbf7
--- /dev/null
+++ b/gfx/thebes/gfxFT2Utils.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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;
+
+class gfxFT2LockedFace {
+public:
+ explicit gfxFT2LockedFace(gfxFT2FontBase *aFont) :
+ mGfxFont(aFont),
+ mFace(cairo_ft_scaled_font_lock_face(aFont->CairoScaledFont()))
+ { }
+ ~gfxFT2LockedFace()
+ {
+ if (mFace) {
+ cairo_ft_scaled_font_unlock_face(mGfxFont->CairoScaledFont());
+ }
+ }
+
+ 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);
+
+ void GetMetrics(gfxFont::Metrics* aMetrics, uint32_t* aSpaceGlyph);
+
+ // A scale factor for use in converting horizontal metrics from font units
+ // to pixels.
+ gfxFloat XScale()
+ {
+ if (MOZ_UNLIKELY(!mFace))
+ return 0.0;
+
+ const FT_Size_Metrics& ftMetrics = mFace->size->metrics;
+
+ if (FT_IS_SCALABLE(mFace)) {
+ // Prefer FT_Size_Metrics::x_scale to x_ppem as x_ppem does not
+ // have subpixel accuracy.
+ //
+ // FT_Size_Metrics::x_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.
+ return FLOAT_FROM_26_6(FLOAT_FROM_16_16(ftMetrics.x_scale));
+ }
+
+ // Not scalable.
+ // FT_Size_Metrics doc says x_scale is "only relevant for scalable
+ // font formats".
+ return gfxFloat(ftMetrics.x_ppem) / gfxFloat(mFace->units_per_EM);
+ }
+
+protected:
+ /**
+ * 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. aExtents is only set when this returns a non-zero glyph id.
+ */
+ uint32_t GetCharExtents(char aChar, cairo_text_extents_t* aExtents);
+
+ typedef FT_UInt (*CharVariantFunction)(FT_Face face,
+ FT_ULong charcode,
+ FT_ULong variantSelector);
+ CharVariantFunction FindCharVariantFunction();
+
+ RefPtr<gfxFT2FontBase> mGfxFont;
+ FT_Face mFace;
+};
+
+#endif /* GFX_FT2UTILS_H */
diff --git a/gfx/thebes/gfxFailure.h b/gfx/thebes/gfxFailure.h
new file mode 100644
index 000000000..b35ffba39
--- /dev/null
+++ b/gfx/thebes/gfxFailure.h
@@ -0,0 +1,25 @@
+/* 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 000000000..601e7a90c
--- /dev/null
+++ b/gfx/thebes/gfxFcPlatformFontList.cpp
@@ -0,0 +1,1866 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxFontFamilyList.h"
+#include "gfxFT2Utils.h"
+#include "gfxPlatform.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/TimeStamp.h"
+#include "nsGkAtoms.h"
+#include "nsILanguageAtomService.h"
+#include "nsUnicodeProperties.h"
+#include "nsUnicodeRange.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsCharSeparatedTokenizer.h"
+
+#include "mozilla/gfx/HelpersCairo.h"
+
+#include <fontconfig/fcfreetype.h>
+
+#ifdef MOZ_WIDGET_GTK
+#include <gdk/gdk.h>
+#include "gfxPlatformGtk.h"
+#endif
+
+#ifdef MOZ_X11
+#include "mozilla/X11Util.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::unicode;
+
+#ifndef FC_POSTSCRIPT_NAME
+#define FC_POSTSCRIPT_NAME "postscriptname" /* String */
+#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);
+}
+
+FT_Library gfxFcPlatformFontList::sCairoFTLibrary = nullptr;
+
+static cairo_user_data_key_t sFcFontlistUserFontDataKey;
+
+// 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 nsAString& aFamilyName,
+ nsAString& aPostscriptName, nsAString& aFullname)
+{
+ // get the Postscript name
+ FcChar8* psname;
+ if (FcPatternGetString(aFont, FC_POSTSCRIPT_NAME, 0, &psname) == FcResultMatch) {
+ AppendUTF8toUTF16(ToCharPtr(psname), aPostscriptName);
+ }
+
+ // 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) {
+ AppendUTF8toUTF16(ToCharPtr(fullname), aFullname);
+ }
+
+ // if have fullname, done
+ if (!aFullname.IsEmpty()) {
+ return;
+ }
+
+ // otherwise, set the fullname to family + style name [en] and use that
+ aFullname.Append(aFamilyName);
+
+ // figure out the en style name
+ en = FindCanonicalNameIndex(aFont, FC_STYLELANG);
+ nsAutoString style;
+ FcChar8* stylename = nullptr;
+ FcPatternGetString(aFont, FC_STYLE, en, &stylename);
+ if (stylename) {
+ AppendUTF8toUTF16(ToCharPtr(stylename), style);
+ }
+
+ if (!style.IsEmpty() && !style.EqualsLiteral("Regular")) {
+ aFullname.Append(' ');
+ aFullname.Append(style);
+ }
+}
+
+static uint16_t
+MapFcWeight(int aFcWeight)
+{
+ if (aFcWeight <= (FC_WEIGHT_THIN + FC_WEIGHT_EXTRALIGHT) / 2) {
+ return 100;
+ } else if (aFcWeight <= (FC_WEIGHT_EXTRALIGHT + FC_WEIGHT_LIGHT) / 2) {
+ return 200;
+ } else if (aFcWeight <= (FC_WEIGHT_LIGHT + FC_WEIGHT_BOOK) / 2) {
+ return 300;
+ } else if (aFcWeight <= (FC_WEIGHT_REGULAR + FC_WEIGHT_MEDIUM) / 2) {
+ // This includes FC_WEIGHT_BOOK
+ return 400;
+ } else if (aFcWeight <= (FC_WEIGHT_MEDIUM + FC_WEIGHT_DEMIBOLD) / 2) {
+ return 500;
+ } else if (aFcWeight <= (FC_WEIGHT_DEMIBOLD + FC_WEIGHT_BOLD) / 2) {
+ return 600;
+ } else if (aFcWeight <= (FC_WEIGHT_BOLD + FC_WEIGHT_EXTRABOLD) / 2) {
+ return 700;
+ } else if (aFcWeight <= (FC_WEIGHT_EXTRABOLD + FC_WEIGHT_BLACK) / 2) {
+ return 800;
+ } else if (aFcWeight <= FC_WEIGHT_BLACK) {
+ return 900;
+ }
+
+ // including FC_WEIGHT_EXTRABLACK
+ return 901;
+}
+
+static int16_t
+MapFcWidth(int aFcWidth)
+{
+ if (aFcWidth <= (FC_WIDTH_ULTRACONDENSED + FC_WIDTH_EXTRACONDENSED) / 2) {
+ return NS_FONT_STRETCH_ULTRA_CONDENSED;
+ }
+ if (aFcWidth <= (FC_WIDTH_EXTRACONDENSED + FC_WIDTH_CONDENSED) / 2) {
+ return NS_FONT_STRETCH_EXTRA_CONDENSED;
+ }
+ if (aFcWidth <= (FC_WIDTH_CONDENSED + FC_WIDTH_SEMICONDENSED) / 2) {
+ return NS_FONT_STRETCH_CONDENSED;
+ }
+ if (aFcWidth <= (FC_WIDTH_SEMICONDENSED + FC_WIDTH_NORMAL) / 2) {
+ return NS_FONT_STRETCH_SEMI_CONDENSED;
+ }
+ if (aFcWidth <= (FC_WIDTH_NORMAL + FC_WIDTH_SEMIEXPANDED) / 2) {
+ return NS_FONT_STRETCH_NORMAL;
+ }
+ if (aFcWidth <= (FC_WIDTH_SEMIEXPANDED + FC_WIDTH_EXPANDED) / 2) {
+ return NS_FONT_STRETCH_SEMI_EXPANDED;
+ }
+ if (aFcWidth <= (FC_WIDTH_EXPANDED + FC_WIDTH_EXTRAEXPANDED) / 2) {
+ return NS_FONT_STRETCH_EXPANDED;
+ }
+ if (aFcWidth <= (FC_WIDTH_EXTRAEXPANDED + FC_WIDTH_ULTRAEXPANDED) / 2) {
+ return NS_FONT_STRETCH_EXTRA_EXPANDED;
+ }
+ return NS_FONT_STRETCH_ULTRA_EXPANDED;
+}
+
+gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsAString& aFaceName,
+ FcPattern* aFontPattern,
+ bool aIgnoreFcCharmap)
+ : gfxFontEntry(aFaceName), mFontPattern(aFontPattern),
+ mFTFace(nullptr), mFTFaceInitialized(false),
+ mIgnoreFcCharmap(aIgnoreFcCharmap),
+ mAspect(0.0), mFontData(nullptr)
+{
+ // italic
+ int slant;
+ if (FcPatternGetInteger(aFontPattern, FC_SLANT, 0, &slant) != FcResultMatch) {
+ slant = FC_SLANT_ROMAN;
+ }
+ if (slant == FC_SLANT_OBLIQUE) {
+ mStyle = NS_FONT_STYLE_OBLIQUE;
+ } else if (slant > 0) {
+ mStyle = NS_FONT_STYLE_ITALIC;
+ }
+
+ // weight
+ int weight;
+ if (FcPatternGetInteger(aFontPattern, FC_WEIGHT, 0, &weight) != FcResultMatch) {
+ weight = FC_WEIGHT_REGULAR;
+ }
+ mWeight = MapFcWeight(weight);
+
+ // width
+ int width;
+ if (FcPatternGetInteger(aFontPattern, FC_WIDTH, 0, &width) != FcResultMatch) {
+ width = FC_WIDTH_NORMAL;
+ }
+ mStretch = MapFcWidth(width);
+}
+
+gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsAString& aFaceName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t *aData,
+ FT_Face aFace)
+ : gfxFontEntry(aFaceName),
+ mFTFace(aFace), mFTFaceInitialized(true),
+ mIgnoreFcCharmap(true),
+ mAspect(0.0), mFontData(aData)
+{
+ mWeight = aWeight;
+ mStyle = aStyle;
+ mStretch = aStretch;
+ mIsDataUserFont = true;
+
+ // 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.
+ mFontPattern = FcFreeTypeQueryFace(mFTFace, ToFcChar8Ptr(""), 0, nullptr);
+ // given that we have a FT_Face, not really sure this is possible...
+ if (!mFontPattern) {
+ mFontPattern = FcPatternCreate();
+ }
+ FcPatternDel(mFontPattern, FC_FILE);
+ FcPatternDel(mFontPattern, FC_INDEX);
+
+ // Make a new pattern and store the face in it so that cairo uses
+ // that when creating a cairo font face.
+ FcPatternAddFTFace(mFontPattern, FC_FT_FACE, mFTFace);
+
+ mUserFontData = new FTUserFontData(mFTFace, mFontData);
+}
+
+gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsAString& aFaceName,
+ FcPattern* aFontPattern,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle)
+ : gfxFontEntry(aFaceName), mFontPattern(aFontPattern),
+ mFTFace(nullptr), mFTFaceInitialized(false),
+ mAspect(0.0), mFontData(nullptr)
+{
+ mWeight = aWeight;
+ mStyle = aStyle;
+ mStretch = 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;
+}
+
+gfxFontconfigFontEntry::~gfxFontconfigFontEntry()
+{
+}
+
+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
+gfxFontconfigFontEntry::SupportsLangGroup(nsIAtom *aLangGroup) const
+{
+ if (!aLangGroup || aLangGroup == nsGkAtoms::Unicode) {
+ return true;
+ }
+
+ nsAutoCString fcLang;
+ gfxFcPlatformFontList* pfl = gfxFcPlatformFontList::PlatformFontList();
+ pfl->GetSampleLangForGroup(aLangGroup, fcLang);
+ if (fcLang.IsEmpty()) {
+ return true;
+ }
+
+ // is lang included in the underlying pattern?
+ return PatternHasLang(mFontPattern, ToFcChar8Ptr(fcLang.get()));
+}
+
+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;
+ bool symbolFont = false; // currently ignored
+
+ if (aFontInfoData && (charmap = GetCMAPFromFontInfo(aFontInfoData,
+ mUVSOffset,
+ symbolFont))) {
+ rv = NS_OK;
+ } else {
+ uint32_t kCMAP = TRUETYPE_TAG('c','m','a','p');
+ charmap = new gfxCharacterMap();
+ AutoTable cmapTable(this, kCMAP);
+
+ if (cmapTable) {
+ bool unicodeFont = false; // currently ignored
+ uint32_t cmapLen;
+ const uint8_t* cmapData =
+ reinterpret_cast<const uint8_t*>(hb_blob_get_data(cmapTable,
+ &cmapLen));
+ rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen,
+ *charmap, mUVSOffset,
+ unicodeFont, symbolFont);
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ mHasCmapTable = NS_SUCCEEDED(rv);
+ if (mHasCmapTable) {
+ gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList();
+ mCharacterMap = pfl->FindCharMap(charmap);
+ } else {
+ // if error occurred, initialize to null cmap
+ mCharacterMap = new gfxCharacterMap();
+ }
+
+ LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %d hash: %8.8x%s\n",
+ NS_ConvertUTF16toUTF8(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",
+ NS_ConvertUTF16toUTF8(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);
+}
+
+hb_blob_t*
+gfxFontconfigFontEntry::GetFontTable(uint32_t aTableTag)
+{
+ // for data fonts, read directly from the font data
+ if (mFontData) {
+ return gfxFontUtils::GetTableFromFontData(mFontData, aTableTag);
+ }
+
+ return gfxFontEntry::GetFontTable(aTableTag);
+}
+
+void
+gfxFontconfigFontEntry::MaybeReleaseFTFace()
+{
+ // don't release if either HB or Gr face still exists
+ if (mHBFace || mGrFace) {
+ return;
+ }
+ // only close out FT_Face for system fonts, not for data fonts
+ if (!mIsDataUserFont) {
+ if (mFTFace) {
+ FT_Done_Face(mFTFace);
+ mFTFace = nullptr;
+ }
+ mFTFaceInitialized = false;
+ }
+}
+
+void
+gfxFontconfigFontEntry::ForgetHBFace()
+{
+ gfxFontEntry::ForgetHBFace();
+ MaybeReleaseFTFace();
+}
+
+void
+gfxFontconfigFontEntry::ReleaseGrFace(gr_face* aFace)
+{
+ gfxFontEntry::ReleaseGrFace(aFace);
+ MaybeReleaseFTFace();
+}
+
+double
+gfxFontconfigFontEntry::GetAspect()
+{
+ if (mAspect == 0.0) {
+ // default to aspect = 0.5
+ mAspect = 0.5;
+
+ // create a font to calculate x-height / em-height
+ gfxFontStyle s;
+ s.size = 100.0; // pick large size to avoid possible hinting artifacts
+ RefPtr<gfxFont> font = FindOrMakeFont(&s, false);
+ if (font) {
+ const gfxFont::Metrics& metrics =
+ font->GetMetrics(gfxFont::eHorizontal);
+
+ // The factor of 0.1 ensures that xHeight is sane so fonts don't
+ // become huge. Strictly ">" ensures that xHeight and emHeight are
+ // not both zero.
+ if (metrics.xHeight > 0.1 * metrics.emHeight) {
+ mAspect = metrics.xHeight / metrics.emHeight;
+ }
+ }
+ }
+ return mAspect;
+}
+
+static void
+PrepareFontOptions(FcPattern* aPattern,
+ cairo_font_options_t* aFontOptions)
+{
+ NS_ASSERTION(aFontOptions, "null font options passed to PrepareFontOptions");
+
+ // 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 for
+ // SetupCairoFont (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.
+ if (printing) {
+ cairo_font_options_set_hint_metrics(aFontOptions, CAIRO_HINT_METRICS_OFF);
+ } else {
+ cairo_font_options_set_hint_metrics(aFontOptions, 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;
+ }
+
+ cairo_hint_style_t hint_style;
+ if (printing || !hinting) {
+ hint_style = CAIRO_HINT_STYLE_NONE;
+ } else {
+ int fc_hintstyle;
+ if (FcPatternGetInteger(aPattern, FC_HINT_STYLE,
+ 0, &fc_hintstyle) != FcResultMatch) {
+ fc_hintstyle = FC_HINT_FULL;
+ }
+ switch (fc_hintstyle) {
+ case FC_HINT_NONE:
+ hint_style = CAIRO_HINT_STYLE_NONE;
+ break;
+ case FC_HINT_SLIGHT:
+ hint_style = CAIRO_HINT_STYLE_SLIGHT;
+ break;
+ case FC_HINT_MEDIUM:
+ default: // This fallback mirrors _get_pattern_ft_options in cairo.
+ hint_style = CAIRO_HINT_STYLE_MEDIUM;
+ break;
+ case FC_HINT_FULL:
+ hint_style = CAIRO_HINT_STYLE_FULL;
+ break;
+ }
+ }
+ cairo_font_options_set_hint_style(aFontOptions, hint_style);
+
+ int rgba;
+ if (FcPatternGetInteger(aPattern,
+ FC_RGBA, 0, &rgba) != FcResultMatch) {
+ rgba = FC_RGBA_UNKNOWN;
+ }
+ cairo_subpixel_order_t subpixel_order = CAIRO_SUBPIXEL_ORDER_DEFAULT;
+ switch (rgba) {
+ case FC_RGBA_UNKNOWN:
+ case FC_RGBA_NONE:
+ default:
+ // There is no CAIRO_SUBPIXEL_ORDER_NONE. Subpixel antialiasing
+ // is disabled through cairo_antialias_t.
+ rgba = FC_RGBA_NONE;
+ // subpixel_order won't be used by the font as we won't use
+ // CAIRO_ANTIALIAS_SUBPIXEL, but don't leave it at default for
+ // caching reasons described above. Fall through:
+ MOZ_FALLTHROUGH;
+ case FC_RGBA_RGB:
+ subpixel_order = CAIRO_SUBPIXEL_ORDER_RGB;
+ break;
+ case FC_RGBA_BGR:
+ subpixel_order = CAIRO_SUBPIXEL_ORDER_BGR;
+ break;
+ case FC_RGBA_VRGB:
+ subpixel_order = CAIRO_SUBPIXEL_ORDER_VRGB;
+ break;
+ case FC_RGBA_VBGR:
+ subpixel_order = CAIRO_SUBPIXEL_ORDER_VBGR;
+ break;
+ }
+ cairo_font_options_set_subpixel_order(aFontOptions, subpixel_order);
+
+ FcBool fc_antialias;
+ if (FcPatternGetBool(aPattern,
+ FC_ANTIALIAS, 0, &fc_antialias) != FcResultMatch) {
+ fc_antialias = FcTrue;
+ }
+ cairo_antialias_t antialias;
+ if (!fc_antialias) {
+ antialias = CAIRO_ANTIALIAS_NONE;
+ } else if (rgba == FC_RGBA_NONE) {
+ antialias = CAIRO_ANTIALIAS_GRAY;
+ } else {
+ antialias = CAIRO_ANTIALIAS_SUBPIXEL;
+ }
+ cairo_font_options_set_antialias(aFontOptions, antialias);
+}
+
+cairo_scaled_font_t*
+gfxFontconfigFontEntry::CreateScaledFont(FcPattern* aRenderPattern,
+ gfxFloat aAdjustedSize,
+ const gfxFontStyle *aStyle,
+ bool aNeedsBold)
+{
+ if (aNeedsBold) {
+ FcPatternAddBool(aRenderPattern, FC_EMBOLDEN, FcTrue);
+ }
+
+ // synthetic oblique by skewing via the font matrix
+ bool needsOblique = IsUpright() &&
+ aStyle->style != NS_FONT_STYLE_NORMAL &&
+ aStyle->allowSyntheticStyle;
+
+ if (needsOblique) {
+ // disable embedded bitmaps (mimics behavior in 90-synthetic.conf)
+ FcPatternDel(aRenderPattern, FC_EMBEDDED_BITMAP);
+ FcPatternAddBool(aRenderPattern, FC_EMBEDDED_BITMAP, FcFalse);
+ }
+
+ cairo_font_face_t *face =
+ cairo_ft_font_face_create_for_pattern(aRenderPattern);
+
+ if (mFontData) {
+ // for data fonts, add the face/data pointer to the cairo font face
+ // so that it gets deleted whenever cairo decides
+ NS_ASSERTION(mFTFace, "FT_Face is null when setting user data");
+ NS_ASSERTION(mUserFontData, "user font data is null when setting user data");
+ cairo_font_face_set_user_data(face,
+ &sFcFontlistUserFontDataKey,
+ new FTUserFontDataRef(mUserFontData),
+ FTUserFontDataRef::Destroy);
+ }
+
+ cairo_scaled_font_t *scaledFont = nullptr;
+
+ cairo_matrix_t sizeMatrix;
+ cairo_matrix_t identityMatrix;
+
+ cairo_matrix_init_scale(&sizeMatrix, aAdjustedSize, aAdjustedSize);
+ cairo_matrix_init_identity(&identityMatrix);
+
+ if (needsOblique) {
+ const double kSkewFactor = OBLIQUE_SKEW_FACTOR;
+
+ cairo_matrix_t style;
+ cairo_matrix_init(&style,
+ 1, //xx
+ 0, //yx
+ -1 * kSkewFactor, //xy
+ 1, //yy
+ 0, //x0
+ 0); //y0
+ cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style);
+ }
+
+ cairo_font_options_t *fontOptions = cairo_font_options_create();
+ PrepareFontOptions(aRenderPattern, fontOptions);
+
+ scaledFont = cairo_scaled_font_create(face, &sizeMatrix,
+ &identityMatrix, fontOptions);
+ cairo_font_options_destroy(fontOptions);
+
+ NS_ASSERTION(cairo_scaled_font_status(scaledFont) == CAIRO_STATUS_SUCCESS,
+ "Failed to make scaled font");
+
+ cairo_font_face_destroy(face);
+
+ return scaledFont;
+}
+
+#ifdef MOZ_WIDGET_GTK
+// defintion included below
+static void ApplyGdkScreenFontOptions(FcPattern *aPattern);
+#endif
+
+#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);
+ } else {
+#ifdef MOZ_WIDGET_GTK
+ ApplyGdkScreenFontOptions(aPattern);
+#endif
+
+#ifdef MOZ_X11
+ FcValue value;
+ int lcdfilter;
+ if (FcPatternGet(aPattern, FC_LCD_FILTER, 0, &value)
+ == FcResultNoMatch &&
+ GetXftInt(DefaultXDisplay(), "lcdfilter", &lcdfilter)) {
+ FcPatternAddInteger(aPattern, FC_LCD_FILTER, lcdfilter);
+ }
+#endif
+ }
+
+ FcDefaultSubstitute(aPattern);
+}
+
+static inline gfxFloat
+SizeForStyle(gfxFontconfigFontEntry* aEntry, const gfxFontStyle& aStyle)
+{
+ return aStyle.sizeAdjust >= 0.0 ?
+ aStyle.GetAdjustedSize(aEntry->GetAspect()) :
+ aStyle.size;
+}
+
+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;
+ }
+ }
+ return bestSize;
+}
+
+gfxFont*
+gfxFontconfigFontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle,
+ bool aNeedsBold)
+{
+ nsAutoRef<FcPattern> pattern(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);
+
+ PreparePattern(pattern, aFontStyle->printerFont);
+ nsAutoRef<FcPattern> renderPattern
+ (FcFontRenderPrepare(nullptr, pattern, mFontPattern));
+ if (!renderPattern) {
+ NS_WARNING("Failed to prepare Fontconfig pattern for font instance");
+ return nullptr;
+ }
+
+ cairo_scaled_font_t* scaledFont =
+ CreateScaledFont(renderPattern, size, aFontStyle, aNeedsBold);
+ gfxFont* newFont =
+ new gfxFontconfigFont(scaledFont, renderPattern, size,
+ this, aFontStyle, aNeedsBold);
+ cairo_scaled_font_destroy(scaledFont);
+
+ return newFont;
+}
+
+nsresult
+gfxFontconfigFontEntry::CopyFontTable(uint32_t aTableTag,
+ nsTArray<uint8_t>& aBuffer)
+{
+ NS_ASSERTION(!mIsDataUserFont,
+ "data fonts should be reading tables directly from memory");
+
+ if (!mFTFaceInitialized) {
+ mFTFaceInitialized = true;
+ FcChar8 *filename;
+ if (FcPatternGetString(mFontPattern, FC_FILE, 0, &filename) != FcResultMatch) {
+ return NS_ERROR_FAILURE;
+ }
+ int index;
+ if (FcPatternGetInteger(mFontPattern, FC_INDEX, 0, &index) != FcResultMatch) {
+ index = 0; // default to 0 if not found in pattern
+ }
+ if (FT_New_Face(gfxFcPlatformFontList::GetFTLibrary(),
+ (const char*)filename, index, &mFTFace) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (!mFTFace) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ FT_ULong length = 0;
+ if (FT_Load_Sfnt_Table(mFTFace, aTableTag, 0, nullptr, &length) != 0) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (!aBuffer.SetLength(length, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ if (FT_Load_Sfnt_Table(mFTFace, aTableTag, 0, aBuffer.Elements(), &length) != 0) {
+ aBuffer.Clear();
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+void
+gfxFontconfigFontFamily::FindStyleVariations(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
+ nsAutoString psname, fullname;
+ GetFaceNames(face, mName, psname, fullname);
+ const nsAutoString& faceName = !psname.IsEmpty() ? psname : fullname;
+
+ gfxFontconfigFontEntry *fontEntry =
+ new gfxFontconfigFontEntry(faceName, face, mContainsAppFonts);
+ AddFontEntry(fontEntry);
+
+ if (fontEntry->IsUpright() &&
+ fontEntry->Weight() == NS_FONT_WEIGHT_NORMAL &&
+ fontEntry->Stretch() == NS_FONT_STRETCH_NORMAL) {
+ numRegularFaces++;
+ }
+
+ if (LOG_FONTLIST_ENABLED()) {
+ LOG_FONTLIST(("(fontlist) added (%s) to family (%s)"
+ " with style: %s weight: %d stretch: %d"
+ " psname: %s fullname: %s",
+ NS_ConvertUTF16toUTF8(fontEntry->Name()).get(),
+ NS_ConvertUTF16toUTF8(Name()).get(),
+ (fontEntry->IsItalic()) ?
+ "italic" : (fontEntry->IsOblique() ? "oblique" : "normal"),
+ fontEntry->Weight(), fontEntry->Stretch(),
+ NS_ConvertUTF16toUTF8(psname).get(),
+ NS_ConvertUTF16toUTF8(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);
+}
+
+void
+gfxFontconfigFontFamily::AddFontPattern(FcPattern* aFontPattern)
+{
+ NS_ASSERTION(!mHasStyles,
+ "font patterns must not be added to already enumerated families");
+
+ FcBool scalable;
+ if (FcPatternGetBool(aFontPattern, FC_SCALABLE, 0, &scalable) != FcResultMatch ||
+ !scalable) {
+ mHasNonScalableFaces = true;
+ }
+
+ nsCountedRef<FcPattern> pattern(aFontPattern);
+ mFontPatterns.AppendElement(pattern);
+}
+
+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)
+{
+ 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 (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& aNeedsSyntheticBold)
+{
+ gfxFontFamily::FindAllFontsForStyle(aFontStyle,
+ aFontEntryList,
+ aNeedsSyntheticBold);
+
+ 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 tolernace, 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);
+ // 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->mStyle != entry->mStyle) {
+ // 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);
+ }
+}
+
+gfxFontconfigFont::gfxFontconfigFont(cairo_scaled_font_t *aScaledFont,
+ FcPattern *aPattern,
+ gfxFloat aAdjustedSize,
+ gfxFontEntry *aFontEntry,
+ const gfxFontStyle *aFontStyle,
+ bool aNeedsBold) :
+ gfxFontconfigFontBase(aScaledFont, aPattern, aFontEntry, aFontStyle)
+{
+ mAdjustedSize = aAdjustedSize;
+}
+
+gfxFontconfigFont::~gfxFontconfigFont()
+{
+}
+
+gfxFcPlatformFontList::gfxFcPlatformFontList()
+ : mLocalNames(64)
+ , mGenericMappings(32)
+ , mFcSubstituteCache(64)
+ , mLastConfig(nullptr)
+ , mAlwaysUseFontconfigGenerics(true)
+{
+ // if the rescan interval is set, start the timer
+ int rescanInterval = FcConfigGetRescanInterval(nullptr);
+ if (rescanInterval) {
+ mLastConfig = FcConfigGetCurrent();
+ mCheckFontUpdatesTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (mCheckFontUpdatesTimer) {
+ mCheckFontUpdatesTimer->
+ InitWithFuncCallback(CheckFontUpdates, this,
+ (rescanInterval + 1) * 1000,
+ nsITimer::TYPE_REPEATING_SLACK);
+ } else {
+ NS_WARNING("Failure to create font updates timer");
+ }
+ }
+
+#ifdef MOZ_BUNDLED_FONTS
+ mBundledFontsInitialized = false;
+#endif
+}
+
+gfxFcPlatformFontList::~gfxFcPlatformFontList()
+{
+ if (mCheckFontUpdatesTimer) {
+ mCheckFontUpdatesTimer->Cancel();
+ mCheckFontUpdatesTimer = nullptr;
+ }
+}
+
+void
+gfxFcPlatformFontList::AddFontSetFamilies(FcFontSet* aFontSet, bool aAppFonts)
+{
+ // This iterates over the fonts in a font set and adds in gfxFontFamily
+ // objects for each family. The patterns for individual fonts are not
+ // copied here. When a family is actually used, the fonts in the family
+ // are enumerated and the patterns copied. Note that we're explicitly
+ // excluding non-scalable fonts such as X11 bitmap fonts, which
+ // Chrome Skia/Webkit code does also.
+
+ if (!aFontSet) {
+ NS_WARNING("AddFontSetFamilies called with a null font set.");
+ return;
+ }
+
+ FcChar8* lastFamilyName = (FcChar8*)"";
+ RefPtr<gfxFontconfigFontFamily> fontFamily;
+ nsAutoString familyName;
+ for (int f = 0; f < aFontSet->nfont; f++) {
+ FcPattern* font = aFontSet->fonts[f];
+
+ // get canonical name
+ uint32_t cIndex = FindCanonicalNameIndex(font, FC_FAMILYLANG);
+ FcChar8* canonical = nullptr;
+ FcPatternGetString(font, FC_FAMILY, cIndex, &canonical);
+ if (!canonical) {
+ continue;
+ }
+
+ // same as the last one? no need to add a new family, skip
+ if (FcStrCmp(canonical, lastFamilyName) != 0) {
+ lastFamilyName = canonical;
+
+ // add new family if one doesn't already exist
+ familyName.Truncate();
+ AppendUTF8toUTF16(ToCharPtr(canonical), familyName);
+ nsAutoString keyName(familyName);
+ ToLowerCase(keyName);
+
+ fontFamily = static_cast<gfxFontconfigFontFamily*>
+ (mFontFamilies.GetWeak(keyName));
+ if (!fontFamily) {
+ fontFamily = new gfxFontconfigFontFamily(familyName);
+ mFontFamilies.Put(keyName, fontFamily);
+ }
+ // 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) {
+ fontFamily->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);
+ while (FcPatternGetString(font, FC_FAMILY, n, &otherName) == FcResultMatch) {
+ NS_ConvertUTF8toUTF16 otherFamilyName(ToCharPtr(otherName));
+ AddOtherFamilyName(fontFamily, otherFamilyName);
+ n++;
+ if (n == int(cIndex)) {
+ n++; // skip over canonical name
+ }
+ }
+ }
+
+ NS_ASSERTION(fontFamily, "font must belong to a font family");
+ fontFamily->AddFontPattern(font);
+
+ // map the psname, fullname ==> font family for local font lookups
+ nsAutoString psname, fullname;
+ GetFaceNames(font, familyName, psname, fullname);
+ if (!psname.IsEmpty()) {
+ ToLowerCase(psname);
+ mLocalNames.Put(psname, font);
+ }
+ if (!fullname.IsEmpty()) {
+ ToLowerCase(fullname);
+ mLocalNames.Put(fullname, font);
+ }
+ }
+}
+
+nsresult
+gfxFcPlatformFontList::InitFontListForPlatform()
+{
+ mLastConfig = FcConfigGetCurrent();
+
+ mLocalNames.Clear();
+ mFcSubstituteCache.Clear();
+
+ // iterate over available fonts
+ FcFontSet* systemFonts = FcConfigGetFonts(nullptr, FcSetSystem);
+ AddFontSetFamilies(systemFonts, /* aAppFonts = */ false);
+ mAlwaysUseFontconfigGenerics = PrefFontListsUseOnlyGenerics();
+
+#ifdef MOZ_BUNDLED_FONTS
+ ActivateBundledFonts();
+ FcFontSet* appFonts = FcConfigGetFonts(nullptr, FcSetApplication);
+ AddFontSetFamilies(appFonts, /* aAppFonts = */ true);
+#endif
+
+ mOtherFamilyNamesInitialized = true;
+
+ return NS_OK;
+}
+
+// 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, nsIAtom *aLangGroup)
+{
+ aListOfFonts.Clear();
+
+ nsAutoRef<FcPattern> pat(FcPatternCreate());
+ if (!pat) {
+ return;
+ }
+
+ nsAutoRef<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);
+ if (!fcLang.IsEmpty()) {
+ FcPatternAddString(pat, FC_LANG, ToFcChar8Ptr(fcLang.get()));
+ }
+
+ nsAutoRef<FcFontSet> fs(FcFontList(nullptr, pat, os));
+ 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(family, strFamily);
+ if (aListOfFonts.Contains(strFamily)) {
+ continue;
+ }
+
+ aListOfFonts.AppendElement(strFamily);
+ }
+
+ aListOfFonts.Sort();
+}
+
+void
+gfxFcPlatformFontList::GetFontList(nsIAtom *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
+ NS_NOTREACHED("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, NS_LITERAL_STRING("monospace"));
+ if (sansSerif)
+ aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("sans-serif"));
+ if (serif)
+ aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("serif"));
+}
+
+gfxFontFamily*
+gfxFcPlatformFontList::GetDefaultFontForPlatform(const gfxFontStyle* aStyle)
+{
+ // 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(NS_LITERAL_STRING("-moz-default"), aStyle->language);
+ NS_ASSERTION(prefFonts, "null list of generic fonts");
+ if (prefFonts && !prefFonts->IsEmpty()) {
+ return (*prefFonts)[0];
+ }
+ return nullptr;
+}
+
+gfxFontEntry*
+gfxFcPlatformFontList::LookupLocalFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle)
+{
+ nsAutoString keyName(aFontName);
+ ToLowerCase(keyName);
+
+ // if name is not in the global list, done
+ FcPattern* fontPattern = mLocalNames.Get(keyName);
+ if (!fontPattern) {
+ return nullptr;
+ }
+
+ return new gfxFontconfigFontEntry(aFontName, fontPattern,
+ aWeight, aStretch, aStyle);
+}
+
+gfxFontEntry*
+gfxFcPlatformFontList::MakePlatformFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t* aFontData,
+ uint32_t aLength)
+{
+ FT_Face face;
+ FT_Error error =
+ FT_New_Memory_Face(gfxFcPlatformFontList::GetFTLibrary(),
+ aFontData, aLength, 0, &face);
+ if (error != FT_Err_Ok) {
+ NS_Free((void*)aFontData);
+ return nullptr;
+ }
+ if (FT_Err_Ok != FT_Select_Charmap(face, FT_ENCODING_UNICODE)) {
+ FT_Done_Face(face);
+ NS_Free((void*)aFontData);
+ return nullptr;
+ }
+
+ return new gfxFontconfigFontEntry(aFontName, aWeight, aStretch,
+ aStyle, aFontData, face);
+}
+
+bool
+gfxFcPlatformFontList::FindAndAddFamilies(const nsAString& aFamily,
+ nsTArray<gfxFontFamily*>* aOutput,
+ gfxFontStyle* aStyle,
+ gfxFloat aDevToCssSize)
+{
+ nsAutoString familyName(aFamily);
+ ToLowerCase(familyName);
+ nsIAtom* language = (aStyle ? aStyle->language.get() : nullptr);
+
+ // 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::FontFamilyName::Convert(familyName).IsGeneric()) {
+ PrefFontList* prefFonts = FindGenericFamilies(familyName, language);
+ 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. So check the cache first:
+ NS_ConvertUTF16toUTF8 familyToFind(familyName);
+ AutoTArray<gfxFontFamily*,10> cachedFamilies;
+ if (mFcSubstituteCache.Get(familyToFind, &cachedFamilies)) {
+ if (cachedFamilies.IsEmpty()) {
+ return false;
+ }
+ aOutput->AppendElements(cachedFamilies);
+ return true;
+ }
+
+ // It wasn't in the cache, so we need to ask fontconfig...
+ const FcChar8* kSentinelName = ToFcChar8Ptr("-moz-sentinel");
+ FcChar8* sentinelFirstFamily = nullptr;
+ nsAutoRef<FcPattern> sentinelSubst(FcPatternCreate());
+ FcPatternAddString(sentinelSubst, FC_FAMILY, kSentinelName);
+ FcConfigSubstitute(nullptr, sentinelSubst, FcMatchPattern);
+ FcPatternGetString(sentinelSubst, FC_FAMILY, 0, &sentinelFirstFamily);
+
+ // substitutions for font, -moz-sentinel pattern
+ nsAutoRef<FcPattern> fontWithSentinel(FcPatternCreate());
+ FcPatternAddString(fontWithSentinel, FC_FAMILY,
+ ToFcChar8Ptr(familyToFind.get()));
+ FcPatternAddString(fontWithSentinel, FC_FAMILY, kSentinelName);
+ FcConfigSubstitute(nullptr, fontWithSentinel, FcMatchPattern);
+
+ // Add all font family matches until reaching the sentinel.
+ FcChar8* substName = nullptr;
+ for (int i = 0;
+ FcPatternGetString(fontWithSentinel, FC_FAMILY,
+ i, &substName) == FcResultMatch;
+ i++)
+ {
+ NS_ConvertUTF8toUTF16 subst(ToCharPtr(substName));
+ if (sentinelFirstFamily &&
+ FcStrCmp(substName, sentinelFirstFamily) == 0) {
+ break;
+ }
+ gfxPlatformFontList::FindAndAddFamilies(subst, &cachedFamilies);
+ }
+
+ // Cache the resulting list, so we don't have to do this again.
+ mFcSubstituteCache.Put(familyToFind, cachedFamilies);
+
+ if (cachedFamilies.IsEmpty()) {
+ return false;
+ }
+ aOutput->AppendElements(cachedFamilies);
+ return true;
+}
+
+bool
+gfxFcPlatformFontList::GetStandardFamilyName(const nsAString& aFontName,
+ nsAString& 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;
+ }
+
+ nsAutoRef<FcPattern> pat(FcPatternCreate());
+ if (!pat) {
+ return true;
+ }
+
+ nsAutoRef<FcObjectSet> os(FcObjectSetBuild(FC_FAMILY, nullptr));
+ if (!os) {
+ return true;
+ }
+
+ // add the family name to the pattern
+ NS_ConvertUTF16toUTF8 familyName(aFontName);
+ FcPatternAddString(pat, FC_FAMILY, ToFcChar8Ptr(familyName.get()));
+
+ nsAutoRef<FcFontSet> givenFS(FcFontList(nullptr, pat, os));
+ 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 (familyName.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());
+
+ nsAutoRef<FcFontSet> candidateFS(FcFontList(nullptr, pat, os));
+ 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) {
+ AppendUTF8toUTF16(candidates[j], aFamilyName);
+ return true;
+ }
+ }
+
+ // didn't find localized name, leave family name blank
+ return true;
+}
+
+static const char kFontNamePrefix[] = "font.name.";
+
+void
+gfxFcPlatformFontList::AddGenericFonts(mozilla::FontFamilyType aGenericType,
+ nsIAtom* aLanguage,
+ nsTArray<gfxFontFamily*>& aFamilyList)
+{
+ bool usePrefFontList = false;
+
+ // treat -moz-fixed as monospace
+ if (aGenericType == eFamily_moz_fixed) {
+ aGenericType = eFamily_monospace;
+ }
+
+ 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
+ NS_ConvertASCIItoUTF16 genericToLookup(generic);
+ if ((!mAlwaysUseFontconfigGenerics && aLanguage) ||
+ aLanguage == nsGkAtoms::x_math) {
+ nsIAtom* langGroup = GetLangGroup(aLanguage);
+ nsAutoCString langGroupStr;
+ if (langGroup) {
+ langGroup->ToUTF8String(langGroupStr);
+ }
+ nsAutoCString prefFontName(kFontNamePrefix);
+ prefFontName.Append(generic);
+ prefFontName.Append('.');
+ prefFontName.Append(langGroupStr);
+ nsAdoptingString fontlistValue = Preferences::GetString(prefFontName.get());
+ if (fontlistValue) {
+ if (!fontlistValue.EqualsLiteral("serif") &&
+ !fontlistValue.EqualsLiteral("sans-serif") &&
+ !fontlistValue.EqualsLiteral("monospace")) {
+ usePrefFontList = true;
+ } else {
+ // serif, sans-serif or monospace was specified
+ genericToLookup.Assign(fontlistValue);
+ }
+ }
+ }
+
+ // when pref fonts exist, use standard pref font lookup
+ if (usePrefFontList) {
+ return gfxPlatformFontList::AddGenericFonts(aGenericType,
+ aLanguage,
+ aFamilyList);
+ }
+
+ PrefFontList* prefFonts = FindGenericFamilies(genericToLookup, aLanguage);
+ NS_ASSERTION(prefFonts, "null generic font list");
+ aFamilyList.AppendElements(*prefFonts);
+}
+
+void
+gfxFcPlatformFontList::ClearLangGroupPrefFonts()
+{
+ ClearGenericMappings();
+ gfxPlatformFontList::ClearLangGroupPrefFonts();
+ mAlwaysUseFontconfigGenerics = PrefFontListsUseOnlyGenerics();
+}
+
+/* static */ FT_Library
+gfxFcPlatformFontList::GetFTLibrary()
+{
+ if (!sCairoFTLibrary) {
+ // Use cairo's FT_Library so that cairo takes care of shutdown of the
+ // FT_Library after it has destroyed its font_faces, and FT_Done_Face
+ // has been called on each FT_Face, at least until this bug is fixed:
+ // https://bugs.freedesktop.org/show_bug.cgi?id=18857
+ //
+ // Cairo keeps it's own FT_Library object for creating FT_Face
+ // instances, so use that. There's no simple API for accessing this
+ // so use the hacky method below of making a font and extracting
+ // the library pointer from that.
+
+ bool needsBold;
+ gfxFontStyle style;
+ gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
+ gfxFontFamily* family = pfl->GetDefaultFont(&style);
+ NS_ASSERTION(family, "couldn't find a default font family");
+ gfxFontEntry* fe = family->FindFontForStyle(style, needsBold);
+ if (!fe) {
+ return nullptr;
+ }
+ RefPtr<gfxFont> font = fe->FindOrMakeFont(&style, false);
+ if (!font) {
+ return nullptr;
+ }
+
+ gfxFT2FontBase* ft2Font = reinterpret_cast<gfxFT2FontBase*>(font.get());
+ gfxFT2LockedFace face(ft2Font);
+ if (!face.get()) {
+ return nullptr;
+ }
+
+ sCairoFTLibrary = face.get()->glyph->library;
+ }
+
+ return sCairoFTLibrary;
+}
+
+gfxPlatformFontList::PrefFontList*
+gfxFcPlatformFontList::FindGenericFamilies(const nsAString& aGeneric,
+ nsIAtom* aLanguage)
+{
+ // set up name
+ NS_ConvertUTF16toUTF8 generic(aGeneric);
+
+ nsAutoCString fcLang;
+ GetSampleLangForGroup(aLanguage, fcLang);
+ ToLowerCase(fcLang);
+
+ nsAutoCString genericLang(generic);
+ if (fcLang.Length() > 0) {
+ genericLang.Append('-');
+ }
+ genericLang.Append(fcLang);
+
+ // try to get the family from the cache
+ PrefFontList* prefFonts = mGenericMappings.Get(genericLang);
+ if (prefFonts) {
+ return prefFonts;
+ }
+
+ // if not found, ask fontconfig to pick the appropriate font
+ nsAutoRef<FcPattern> genericPattern(FcPatternCreate());
+ FcPatternAddString(genericPattern, FC_FAMILY,
+ ToFcChar8Ptr(generic.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;
+ nsAutoRef<FcFontSet> faces(FcFontSort(nullptr, genericPattern, FcFalse,
+ nullptr, &result));
+
+ if (!faces) {
+ return nullptr;
+ }
+
+ // -- select the fonts to be used for the generic
+ prefFonts = new PrefFontList; // can be empty but in practice won't happen
+ uint32_t limit = gfxPlatformGtk::GetPlatform()->MaxGenericSubstitions();
+ 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) {
+ NS_ConvertUTF8toUTF16 mappedGenericName(ToCharPtr(mappedGeneric));
+ AutoTArray<gfxFontFamily*,1> genericFamilies;
+ if (gfxPlatformFontList::FindAndAddFamilies(mappedGenericName,
+ &genericFamilies)) {
+ MOZ_ASSERT(genericFamilies.Length() == 1,
+ "expected a single family");
+ if (!prefFonts->Contains(genericFamilies[0])) {
+ prefFonts->AppendElement(genericFamilies[0]);
+ 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);
+ }
+
+ mGenericMappings.Put(genericLang, prefFonts);
+ return prefFonts;
+}
+
+bool
+gfxFcPlatformFontList::PrefFontListsUseOnlyGenerics()
+{
+ bool prefFontsUseOnlyGenerics = true;
+ uint32_t count;
+ char** names;
+ nsresult rv = Preferences::GetRootBranch()->
+ GetChildList(kFontNamePrefix, &count, &names);
+ if (NS_SUCCEEDED(rv) && count) {
+ for (size_t i = 0; i < count; i++) {
+ // 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)
+
+ nsDependentCString prefName(names[i] +
+ ArrayLength(kFontNamePrefix) - 1);
+ nsCCharSeparatedTokenizer tokenizer(prefName, '.');
+ const nsDependentCSubstring& generic = tokenizer.nextToken();
+ const nsDependentCSubstring& langGroup = tokenizer.nextToken();
+ nsAdoptingCString fontPrefValue = Preferences::GetCString(names[i]);
+
+ if (!langGroup.EqualsLiteral("x-math") &&
+ !generic.Equals(fontPrefValue)) {
+ prefFontsUseOnlyGenerics = false;
+ break;
+ }
+ }
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, names);
+ }
+ return prefFontsUseOnlyGenerics;
+}
+
+/* static */ void
+gfxFcPlatformFontList::CheckFontUpdates(nsITimer *aTimer, void *aThis)
+{
+ // 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();
+ pfl->ForceGlobalReflow();
+ }
+}
+
+#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(NS_LITERAL_STRING("fonts")))) {
+ 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
+/***************************************************************************
+ *
+ * This function must be last in the file because it uses the system cairo
+ * library. Above this point the cairo library used is the tree cairo if
+ * MOZ_TREE_CAIRO.
+ */
+
+#if MOZ_TREE_CAIRO
+// Tree cairo symbols have different names. Disable their activation through
+// preprocessor macros.
+#undef cairo_ft_font_options_substitute
+
+// 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);
+}
+#endif
+
+static void
+ApplyGdkScreenFontOptions(FcPattern *aPattern)
+{
+ const cairo_font_options_t *options =
+ gdk_screen_get_font_options(gdk_screen_get_default());
+
+ cairo_ft_font_options_substitute(options, aPattern);
+}
+
+#endif // MOZ_WIDGET_GTK
diff --git a/gfx/thebes/gfxFcPlatformFontList.h b/gfx/thebes/gfxFcPlatformFontList.h
new file mode 100644
index 000000000..1bc35021e
--- /dev/null
+++ b/gfx/thebes/gfxFcPlatformFontList.h
@@ -0,0 +1,330 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxFont.h"
+#include "gfxFontEntry.h"
+#include "gfxFT2FontBase.h"
+#include "gfxPlatformFontList.h"
+#include "mozilla/mozalloc.h"
+#include "nsClassHashtable.h"
+
+#include <fontconfig/fontconfig.h>
+#include "ft2build.h"
+#include FT_FREETYPE_H
+#include FT_TRUETYPE_TABLES_H
+#include <cairo.h>
+#include <cairo-ft.h>
+
+#include "gfxFontconfigUtils.h" // xxx - only for nsAutoRefTraits<FcPattern>, etc.
+
+template <>
+class nsAutoRefTraits<FcObjectSet> : public nsPointerRefTraits<FcObjectSet>
+{
+public:
+ static void Release(FcObjectSet *ptr) { FcObjectSetDestroy(ptr); }
+};
+
+template <>
+class nsAutoRefTraits<FcConfig> : public nsPointerRefTraits<FcConfig>
+{
+public:
+ static void Release(FcConfig *ptr) { FcConfigDestroy(ptr); }
+ static void AddRef(FcConfig *ptr) { FcConfigReference(ptr); }
+};
+
+// Helper classes used for clearning out user font data when cairo font
+// face is destroyed. Since multiple faces may use the same data, be
+// careful to assure that the data is only cleared out when all uses
+// expire. The font entry object contains a refptr to FTUserFontData and
+// each cairo font created from that font entry contains a
+// FTUserFontDataRef with a refptr to that same FTUserFontData object.
+
+class FTUserFontData {
+public:
+ NS_INLINE_DECL_REFCOUNTING(FTUserFontData)
+
+ explicit FTUserFontData(FT_Face aFace, const uint8_t* aData)
+ : mFace(aFace), mFontData(aData)
+ {
+ }
+
+ const uint8_t *FontData() const { return mFontData; }
+
+private:
+ ~FTUserFontData()
+ {
+ FT_Done_Face(mFace);
+ if (mFontData) {
+ NS_Free((void*)mFontData);
+ }
+ }
+
+ FT_Face mFace;
+ const uint8_t *mFontData;
+};
+
+class FTUserFontDataRef {
+public:
+ explicit FTUserFontDataRef(FTUserFontData *aUserFontData)
+ : mUserFontData(aUserFontData)
+ {
+ }
+
+ static void Destroy(void* aData) {
+ FTUserFontDataRef* aUserFontDataRef =
+ static_cast<FTUserFontDataRef*>(aData);
+ delete aUserFontDataRef;
+ }
+
+private:
+ RefPtr<FTUserFontData> mUserFontData;
+};
+
+// 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 : public gfxFontEntry {
+public:
+ // used for system fonts with explicit patterns
+ explicit gfxFontconfigFontEntry(const nsAString& 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 nsAString& aFaceName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t *aData,
+ FT_Face aFace);
+
+ // used for @font-face local system fonts with explicit patterns
+ explicit gfxFontconfigFontEntry(const nsAString& aFaceName,
+ FcPattern* aFontPattern,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle);
+
+ FcPattern* GetPattern() { return mFontPattern; }
+
+ bool SupportsLangGroup(nsIAtom *aLangGroup) const override;
+
+ nsresult ReadCMAP(FontInfoData *aFontInfoData = nullptr) override;
+ bool TestCharacterMap(uint32_t aCh) override;
+
+ hb_blob_t* GetFontTable(uint32_t aTableTag) override;
+
+ void ForgetHBFace() override;
+ void ReleaseGrFace(gr_face* aFace) override;
+
+ double GetAspect();
+
+protected:
+ virtual ~gfxFontconfigFontEntry();
+
+ gfxFont *CreateFontInstance(const gfxFontStyle *aFontStyle,
+ bool aNeedsBold) override;
+
+ // helper method for creating cairo font from pattern
+ cairo_scaled_font_t*
+ CreateScaledFont(FcPattern* aRenderPattern,
+ gfxFloat aAdjustedSize,
+ const gfxFontStyle *aStyle,
+ bool aNeedsBold);
+
+ // override to pull data from FTFace
+ virtual nsresult
+ CopyFontTable(uint32_t aTableTag,
+ nsTArray<uint8_t>& aBuffer) override;
+
+ // if HB or GR faces are gone, close down the FT_Face
+ void MaybeReleaseFTFace();
+
+ // pattern for a single face of a family
+ nsCountedRef<FcPattern> mFontPattern;
+
+ // user font data, when needed
+ RefPtr<FTUserFontData> mUserFontData;
+
+ // FTFace - initialized when needed
+ FT_Face mFTFace;
+ 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;
+
+ double mAspect;
+
+ // data font
+ const uint8_t* mFontData;
+};
+
+class gfxFontconfigFontFamily : public gfxFontFamily {
+public:
+ explicit gfxFontconfigFontFamily(const nsAString& aName) :
+ gfxFontFamily(aName),
+ mContainsAppFonts(false),
+ mHasNonScalableFaces(false)
+ { }
+
+ void FindStyleVariations(FontInfoData *aFontInfoData = nullptr) override;
+
+ // Families are constructed initially with just references to patterns.
+ // When necessary, these are enumerated within FindStyleVariations.
+ void AddFontPattern(FcPattern* aFontPattern);
+
+ void SetFamilyContainsAppFonts(bool aContainsAppFonts)
+ {
+ mContainsAppFonts = aContainsAppFonts;
+ }
+
+ void
+ FindAllFontsForStyle(const gfxFontStyle& aFontStyle,
+ nsTArray<gfxFontEntry*>& aFontEntryList,
+ bool& aNeedsSyntheticBold) override;
+
+protected:
+ virtual ~gfxFontconfigFontFamily() { }
+
+ nsTArray<nsCountedRef<FcPattern> > mFontPatterns;
+
+ bool mContainsAppFonts;
+ bool mHasNonScalableFaces;
+};
+
+class gfxFontconfigFont : public gfxFontconfigFontBase {
+public:
+ gfxFontconfigFont(cairo_scaled_font_t *aScaledFont,
+ FcPattern *aPattern,
+ gfxFloat aAdjustedSize,
+ gfxFontEntry *aFontEntry,
+ const gfxFontStyle *aFontStyle,
+ bool aNeedsBold);
+
+protected:
+ virtual ~gfxFontconfigFont();
+};
+
+class nsILanguageAtomService;
+
+class gfxFcPlatformFontList : public gfxPlatformFontList {
+public:
+ gfxFcPlatformFontList();
+
+ static gfxFcPlatformFontList* PlatformFontList() {
+ return static_cast<gfxFcPlatformFontList*>(sPlatformFontList);
+ }
+
+ // initialize font lists
+ virtual nsresult InitFontListForPlatform() override;
+
+ void GetFontList(nsIAtom *aLangGroup,
+ const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts) override;
+
+
+ gfxFontEntry*
+ LookupLocalFont(const nsAString& aFontName, uint16_t aWeight,
+ int16_t aStretch, uint8_t aStyle) override;
+
+ gfxFontEntry*
+ MakePlatformFont(const nsAString& aFontName, uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t* aFontData,
+ uint32_t aLength) override;
+
+ bool FindAndAddFamilies(const nsAString& aFamily,
+ nsTArray<gfxFontFamily*>* aOutput,
+ gfxFontStyle* aStyle = nullptr,
+ gfxFloat aDevToCssSize = 1.0) override;
+
+ bool GetStandardFamilyName(const nsAString& aFontName,
+ nsAString& aFamilyName) override;
+
+ FcConfig* GetLastConfig() const { return mLastConfig; }
+
+ // override to use fontconfig lookup for generics
+ void AddGenericFonts(mozilla::FontFamilyType aGenericType,
+ nsIAtom* aLanguage,
+ nsTArray<gfxFontFamily*>& aFamilyList) override;
+
+ void ClearLangGroupPrefFonts() override;
+
+ // clear out cached generic-lang ==> family-list mappings
+ void ClearGenericMappings() {
+ mGenericMappings.Clear();
+ }
+
+ static FT_Library GetFTLibrary();
+
+protected:
+ virtual ~gfxFcPlatformFontList();
+
+ // Add all the font families found in a font set.
+ // aAppFonts indicates whether this is the system or application fontset.
+ void AddFontSetFamilies(FcFontSet* aFontSet, bool aAppFonts);
+
+ // figure out which families fontconfig maps a generic to
+ // (aGeneric assumed already lowercase)
+ PrefFontList* FindGenericFamilies(const nsAString& aGeneric,
+ nsIAtom* aLanguage);
+
+ // are all pref font settings set to use fontconfig generics?
+ bool PrefFontListsUseOnlyGenerics();
+
+ static void CheckFontUpdates(nsITimer *aTimer, void *aThis);
+
+ virtual gfxFontFamily*
+ GetDefaultFontForPlatform(const gfxFontStyle* aStyle) override;
+
+#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
+ nsBaseHashtable<nsStringHashKey,
+ nsCountedRef<FcPattern>,
+ 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.
+ nsDataHashtable<nsCStringHashKey,
+ nsTArray<gfxFontFamily*>> mFcSubstituteCache;
+
+ nsCOMPtr<nsITimer> mCheckFontUpdatesTimer;
+ nsCountedRef<FcConfig> mLastConfig;
+
+ // 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 sCairoFTLibrary;
+};
+
+#endif /* GFXPLATFORMFONTLIST_H_ */
diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp
new file mode 100644
index 000000000..d0b747fff
--- /dev/null
+++ b/gfx/thebes/gfxFont.cpp
@@ -0,0 +1,4028 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/gfx/2D.h"
+#include "mozilla/MathAlgorithms.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 "nsIUGenCategory.h"
+#include "nsSpecialCasingData.h"
+#include "nsTextRunTransformations.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 "GreekCasing.h"
+
+#include "cairo.h"
+
+#include "harfbuzz/hb.h"
+#include "harfbuzz/hb-ot.h"
+#include "graphite2/Font.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)
+
+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 {
+ NS_NOTREACHED("unexpected notification topic");
+ }
+ return NS_OK;
+}
+
+nsresult
+gfxFontCache::Init()
+{
+ NS_ASSERTION(!gGlobalCache, "Where did this come from?");
+ gGlobalCache = new gfxFontCache();
+ 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()
+ : nsExpirationTracker<gfxFont,3>(FONT_TIMEOUT_SECONDS * 1000,
+ "gfxFontCache")
+{
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ if (obs) {
+ obs->AddObserver(new Observer, "memory-pressure", false);
+ }
+
+#ifndef RELEASE_OR_BETA
+ // Currently disabled for release builds, due to unexplained crashes
+ // during expiration; see bug 717175 & 894798.
+ mWordCacheExpirationTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (mWordCacheExpirationTimer) {
+ mWordCacheExpirationTimer->
+ InitWithFuncCallback(WordCacheExpirationTimerCallback, this,
+ SHAPED_WORD_TIMEOUT_SECONDS * 1000,
+ nsITimer::TYPE_REPEATING_SLACK);
+ }
+#endif
+}
+
+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 that has a zero refcount, so we don't leak them.
+ AgeAllGenerations();
+ // All fonts should be gone.
+ NS_WARNING_ASSERTION(mFonts.Count() == 0,
+ "Fonts still alive while shutting down gfxFontCache");
+ // Note that we have to delete everything through the expiration
+ // tracker, since there might be fonts not in the hashtable but in
+ // the tracker.
+}
+
+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)
+{
+ 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;
+ return font.forget();
+}
+
+void
+gfxFontCache::AddNew(gfxFont *aFont)
+{
+ Key key(aFont->GetFontEntry(), aFont->GetStyle(),
+ aFont->GetUnicodeRangeMap());
+ HashEntry *entry = mFonts.PutEntry(key);
+ if (!entry)
+ return;
+ gfxFont *oldFont = 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));
+ // If someone's asked us to replace an existing font entry, then that's a
+ // bit weird, but let it happen, and expire the old font if it's not used.
+ if (oldFont && oldFont->GetExpirationState()->IsTracked()) {
+ // if oldFont == aFont, recount should be > 0,
+ // so we shouldn't be here.
+ NS_ASSERTION(aFont != oldFont, "new font is tracked for expiry!");
+ NotifyExpired(oldFont);
+ }
+}
+
+void
+gfxFontCache::NotifyReleased(gfxFont *aFont)
+{
+ nsresult rv = AddObject(aFont);
+ if (NS_FAILED(rv)) {
+ // We couldn't track it for some reason. Kill it now.
+ DestroyFont(aFont);
+ }
+ // Note that we might have fonts that aren't in the hashtable, perhaps because
+ // of OOM adding to the hashtable or because someone did an AddNew where
+ // we already had a font. These fonts are added to the expiration tracker
+ // anyway, even though Lookup can't resurrect them. Eventually they will
+ // expire and be deleted.
+}
+
+void
+gfxFontCache::NotifyExpired(gfxFont *aFont)
+{
+ aFont->ClearCachedWords();
+ RemoveObject(aFont);
+ DestroyFont(aFont);
+}
+
+void
+gfxFontCache::DestroyFont(gfxFont *aFont)
+{
+ Key key(aFont->GetFontEntry(), aFont->GetStyle(),
+ aFont->GetUnicodeRangeMap());
+ HashEntry *entry = mFonts.GetEntry(key);
+ if (entry && entry->mFont == aFont) {
+ mFonts.RemoveEntry(entry);
+ }
+ NS_ASSERTION(aFont->GetRefCount() == 0,
+ "Destroying with non-zero ref count!");
+ delete aFont;
+}
+
+/*static*/
+void
+gfxFontCache::WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache)
+{
+ gfxFontCache* cache = static_cast<gfxFontCache*>(aCache);
+ for (auto it = cache->mFonts.Iter(); !it.Done(); it.Next()) {
+ it.Get()->mFont->AgeCachedWords();
+ }
+}
+
+void
+gfxFontCache::FlushShapedWordCaches()
+{
+ for (auto it = mFonts.Iter(); !it.Done(); it.Next()) {
+ it.Get()->mFont->ClearCachedWords();
+ }
+}
+
+void
+gfxFontCache::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const
+{
+ // TODO: add the overhead of the expiration tracker (generation arrays)
+
+ aSizes->mFontInstances += mFonts.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = mFonts.ConstIter(); !iter.Done(); iter.Next()) {
+ iter.Get()->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(gfxFontFeatureValueSet *featureLookup,
+ const nsAString& aFamily,
+ const nsTArray<gfxAlternateValue>& altValue,
+ nsTArray<gfxFontFeature>& aFontFeatures)
+{
+ uint32_t numAlternates = altValue.Length();
+ for (uint32_t i = 0; i < numAlternates; i++) {
+ const gfxAlternateValue& av = altValue.ElementAt(i);
+ AutoTArray<uint32_t,4> values;
+
+ // map <family, name, feature> ==> <values>
+ bool found =
+ featureLookup->GetFontFeatureValuesFor(aFamily, av.alternate,
+ av.value, values);
+ uint32_t numValues = values.Length();
+
+ // nothing defined, skip
+ if (!found || numValues == 0) {
+ continue;
+ }
+
+ gfxFontFeature feature;
+ if (av.alternate == NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT) {
+ NS_ASSERTION(numValues <= 2,
+ "too many values allowed for character-variant");
+ // character-variant(12 3) ==> 'cv12' = 3
+ uint32_t nn = values.ElementAt(0);
+ // ignore values greater than 99
+ if (nn == 0 || nn > MAX_CVXX_VALUE) {
+ continue;
+ }
+ feature.mValue = 1;
+ if (numValues > 1) {
+ feature.mValue = values.ElementAt(1);
+ }
+ feature.mTag = HB_TAG('c','v',('0' + nn / 10), ('0' + nn % 10));
+ aFontFeatures.AppendElement(feature);
+
+ } else if (av.alternate == NS_FONT_VARIANT_ALTERNATES_STYLESET) {
+ // styleset(1 2 7) ==> 'ss01' = 1, 'ss02' = 1, 'ss07' = 1
+ feature.mValue = 1;
+ for (uint32_t v = 0; v < numValues; v++) {
+ uint32_t nn = values.ElementAt(v);
+ if (nn == 0 || nn > MAX_SSXX_VALUE) {
+ continue;
+ }
+ feature.mTag = HB_TAG('s','s',('0' + nn / 10), ('0' + nn % 10));
+ aFontFeatures.AppendElement(feature);
+ }
+
+ } else {
+ NS_ASSERTION(numValues == 1,
+ "too many values for font-specific font-variant-alternates");
+ feature.mValue = values.ElementAt(0);
+
+ switch (av.alternate) {
+ case NS_FONT_VARIANT_ALTERNATES_STYLISTIC: // salt
+ feature.mTag = HB_TAG('s','a','l','t');
+ break;
+ case NS_FONT_VARIANT_ALTERNATES_SWASH: // swsh, cswh
+ feature.mTag = HB_TAG('s','w','s','h');
+ aFontFeatures.AppendElement(feature);
+ feature.mTag = HB_TAG('c','s','w','h');
+ break;
+ case NS_FONT_VARIANT_ALTERNATES_ORNAMENTS: // ornm
+ feature.mTag = HB_TAG('o','r','n','m');
+ break;
+ case NS_FONT_VARIANT_ALTERNATES_ANNOTATION: // nalt
+ feature.mTag = HB_TAG('n','a','l','t');
+ break;
+ default:
+ feature.mTag = 0;
+ break;
+ }
+
+ NS_ASSERTION(feature.mTag, "unsupported alternate type");
+ if (!feature.mTag) {
+ continue;
+ }
+ aFontFeatures.AppendElement(feature);
+ }
+ }
+}
+
+/* static */ void
+gfxFontShaper::MergeFontFeatures(
+ const gfxFontStyle *aStyle,
+ const nsTArray<gfxFontFeature>& aFontFeatures,
+ bool aDisableLigatures,
+ const nsAString& aFamilyName,
+ bool aAddSmallCaps,
+ void (*aHandleFeature)(const uint32_t&, uint32_t&, void*),
+ void* aHandleFeatureData)
+{
+ uint32_t numAlts = aStyle->alternateValues.Length();
+ 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 &&
+ numAlts == 0) {
+ return;
+ }
+
+ nsDataHashtable<nsUint32HashKey,uint32_t> mergedFeatures;
+
+ // Ligature features are enabled by default in the generic shaper,
+ // so we explicitly turn them off if necessary (for letter-spacing)
+ if (aDisableLigatures) {
+ mergedFeatures.Put(HB_TAG('l','i','g','a'), 0);
+ mergedFeatures.Put(HB_TAG('c','l','i','g'), 0);
+ }
+
+ // add feature values from font
+ uint32_t i, count;
+
+ count = aFontFeatures.Length();
+ for (i = 0; i < count; i++) {
+ const gfxFontFeature& feature = aFontFeatures.ElementAt(i);
+ mergedFeatures.Put(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.Put(HB_TAG('c','2','s','c'), 1);
+ // fall through to the small-caps case
+ MOZ_FALLTHROUGH;
+
+ case NS_FONT_VARIANT_CAPS_SMALLCAPS:
+ mergedFeatures.Put(HB_TAG('s','m','c','p'), 1);
+ break;
+
+ case NS_FONT_VARIANT_CAPS_ALLPETITE:
+ mergedFeatures.Put(aAddSmallCaps ? HB_TAG('c','2','s','c') :
+ HB_TAG('c','2','p','c'), 1);
+ // fall through to the petite-caps case
+ MOZ_FALLTHROUGH;
+
+ case NS_FONT_VARIANT_CAPS_PETITECAPS:
+ mergedFeatures.Put(aAddSmallCaps ? HB_TAG('s','m','c','p') :
+ HB_TAG('p','c','a','p'), 1);
+ break;
+
+ case NS_FONT_VARIANT_CAPS_TITLING:
+ mergedFeatures.Put(HB_TAG('t','i','t','l'), 1);
+ break;
+
+ case NS_FONT_VARIANT_CAPS_UNICASE:
+ mergedFeatures.Put(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.Put(HB_TAG('s','u','p','s'), 1);
+ break;
+ case NS_FONT_VARIANT_POSITION_SUB:
+ mergedFeatures.Put(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 && numAlts > 0) {
+ AutoTArray<gfxFontFeature,4> featureList;
+
+ // insert list of alternate feature settings
+ LookupAlternateValues(aStyle->featureValueLookup, aFamilyName,
+ aStyle->alternateValues, featureList);
+
+ count = featureList.Length();
+ for (i = 0; i < count; i++) {
+ const gfxFontFeature& feature = featureList.ElementAt(i);
+ mergedFeatures.Put(feature.mTag, feature.mValue);
+ }
+ }
+
+ // add feature values from style rules
+ count = styleRuleFeatures.Length();
+ for (i = 0; i < count; i++) {
+ const gfxFontFeature& feature = styleRuleFeatures.ElementAt(i);
+ mergedFeatures.Put(feature.mTag, feature.mValue);
+ }
+
+ if (mergedFeatures.Count() != 0) {
+ for (auto iter = mergedFeatures.Iter(); !iter.Done(); iter.Next()) {
+ aHandleFeature(iter.Key(), iter.Data(), aHandleFeatureData);
+ }
+ }
+}
+
+// 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.
+//
+/* static */ void
+gfxFontShaper::GetRoundOffsetsToPixels(DrawTarget* aDrawTarget,
+ bool* aRoundX, bool* aRoundY)
+{
+ *aRoundX = false;
+ // 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 return false for non-cairo contexts.
+ if (aDrawTarget->GetTransform().HasNonTranslation()) {
+ *aRoundY = false;
+ return;
+ }
+
+ // All raster backends snap glyphs to pixels vertically.
+ // Print backends set CAIRO_HINT_METRICS_OFF.
+ *aRoundY = true;
+
+ cairo_t* cr = gfxFont::RefCairo(aDrawTarget);
+ cairo_scaled_font_t *scaled_font = cairo_get_scaled_font(cr);
+
+ // bug 1198921 - this sometimes fails under Windows for whatver reason
+ NS_ASSERTION(scaled_font, "null cairo scaled font should never be returned "
+ "by cairo_get_scaled_font");
+ if (!scaled_font) {
+ *aRoundX = true; // default to the same as the fallback path below
+ return;
+ }
+
+ // Sometimes hint metrics gets set for us, most notably for printing.
+ cairo_font_options_t *font_options = cairo_font_options_create();
+ cairo_scaled_font_get_font_options(scaled_font, font_options);
+ cairo_hint_metrics_t hint_metrics =
+ cairo_font_options_get_hint_metrics(font_options);
+ cairo_font_options_destroy(font_options);
+
+ switch (hint_metrics) {
+ case CAIRO_HINT_METRICS_OFF:
+ *aRoundY = false;
+ return;
+ case CAIRO_HINT_METRICS_DEFAULT:
+ // Here we mimic what cairo surface/font backends do. Printing
+ // surfaces have already been handled by hint_metrics. The
+ // fallback show_glyphs implementation composites pixel-aligned
+ // glyph surfaces, so we just pick surface/font combinations that
+ // override this.
+ switch (cairo_scaled_font_get_type(scaled_font)) {
+#if CAIRO_HAS_DWRITE_FONT // dwrite backend is not in std cairo releases yet
+ case CAIRO_FONT_TYPE_DWRITE:
+ // show_glyphs is implemented on the font and so is used for
+ // all surface types; however, it may pixel-snap depending on
+ // the dwrite rendering mode
+ if (!cairo_dwrite_scaled_font_get_force_GDI_classic(scaled_font) &&
+ gfxWindowsPlatform::GetPlatform()->DWriteMeasuringMode() ==
+ DWRITE_MEASURING_MODE_NATURAL) {
+ return;
+ }
+ MOZ_FALLTHROUGH;
+#endif
+ case CAIRO_FONT_TYPE_QUARTZ:
+ // Quartz surfaces implement show_glyphs for Quartz fonts
+ if (cairo_surface_get_type(cairo_get_target(cr)) ==
+ CAIRO_SURFACE_TYPE_QUARTZ) {
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case CAIRO_HINT_METRICS_ON:
+ break;
+ }
+ *aRoundX = true;
+}
+
+void
+gfxShapedText::SetupClusterBoundaries(uint32_t aOffset,
+ const char16_t *aString,
+ uint32_t aLength)
+{
+ CompressedGlyph *glyphs = GetCharacterGlyphs() + aOffset;
+
+ gfxTextRun::CompressedGlyph extendCluster;
+ extendCluster.SetComplex(false, true, 0);
+
+ ClusterIterator iter(aString, aLength);
+
+ // the ClusterIterator won't be able to tell us if the string
+ // _begins_ with a cluster-extender, so we handle that here
+ if (aLength) {
+ uint32_t ch = *aString;
+ if (aLength > 1 && NS_IS_HIGH_SURROGATE(ch) &&
+ NS_IS_LOW_SURROGATE(aString[1])) {
+ ch = SURROGATE_TO_UCS4(ch, aString[1]);
+ }
+ if (IsClusterExtender(ch)) {
+ *glyphs = extendCluster;
+ }
+ }
+
+ while (!iter.AtEnd()) {
+ if (*iter == char16_t(' ')) {
+ glyphs->SetIsSpace();
+ }
+ // advance iter to the next cluster-start (or end of text)
+ iter.Next();
+ // step past the first char of the cluster
+ aString++;
+ glyphs++;
+ // mark all the rest as cluster-continuations
+ while (aString < iter) {
+ *glyphs = extendCluster;
+ glyphs++;
+ aString++;
+ }
+ }
+}
+
+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::SetGlyphs(uint32_t aIndex, CompressedGlyph aGlyph,
+ const DetailedGlyph *aGlyphs)
+{
+ NS_ASSERTION(!aGlyph.IsSimpleGlyph(), "Simple glyphs not handled here");
+ NS_ASSERTION(aIndex > 0 || aGlyph.IsLigatureGroupStart(),
+ "First character can't be a ligature continuation!");
+
+ uint32_t glyphCount = aGlyph.GetGlyphCount();
+ if (glyphCount > 0) {
+ DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, glyphCount);
+ memcpy(details, aGlyphs, sizeof(DetailedGlyph)*glyphCount);
+ }
+ GetCharacterGlyphs()[aIndex] = aGlyph;
+}
+
+#define ZWNJ 0x200C
+#define ZWJ 0x200D
+static inline bool
+IsDefaultIgnorable(uint32_t aChar)
+{
+ return GetIdentifierModification(aChar) == XIDMOD_DEFAULT_IGNORABLE ||
+ aChar == ZWNJ || aChar == ZWJ;
+}
+
+void
+gfxShapedText::SetMissingGlyph(uint32_t aIndex, uint32_t aChar, gfxFont *aFont)
+{
+ uint8_t category = GetGeneralCategory(aChar);
+ if (category >= HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK &&
+ category <= HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK)
+ {
+ GetCharacterGlyphs()[aIndex].SetComplex(false, true, 0);
+ }
+
+ DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
+
+ details->mGlyphID = aChar;
+ if (IsDefaultIgnorable(aChar)) {
+ // Setting advance width to zero will prevent drawing the hexbox
+ details->mAdvance = 0;
+ } else {
+ gfxFloat width =
+ std::max(aFont->GetMetrics(gfxFont::eHorizontal).aveCharWidth,
+ gfxFloat(gfxFontMissingGlyphs::GetDesiredMinWidth(aChar,
+ mAppUnitsPerDevUnit)));
+ details->mAdvance = uint32_t(width * mAppUnitsPerDevUnit);
+ }
+ details->mXOffset = 0;
+ details->mYOffset = 0;
+ GetCharacterGlyphs()[aIndex].SetMissing(1);
+}
+
+bool
+gfxShapedText::FilterIfIgnorable(uint32_t aIndex, uint32_t aCh)
+{
+ if (IsDefaultIgnorable(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.
+ if (GetGenCategory(aCh) == nsIUGenCategory::kLetter &&
+ aIndex + 1 < GetLength() &&
+ !GetCharacterGlyphs()[aIndex + 1].IsClusterStart()) {
+ return false;
+ }
+ DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
+ details->mGlyphID = aCh;
+ details->mAdvance = 0;
+ details->mXOffset = 0;
+ details->mYOffset = 0;
+ GetCharacterGlyphs()[aIndex].SetMissing(1);
+ return true;
+ }
+ return false;
+}
+
+void
+gfxShapedText::AdjustAdvancesForSyntheticBold(float aSynBoldOffset,
+ uint32_t aOffset,
+ uint32_t aLength)
+{
+ uint32_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() + synAppUnitOffset;
+ if (CompressedGlyph::IsSimpleAdvance(advance)) {
+ glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph());
+ } else {
+ // rare case, tested by making this the default
+ uint32_t glyphIndex = glyphData->GetSimpleGlyph();
+ glyphData->SetComplex(true, true, 1);
+ DetailedGlyph detail = {glyphIndex, advance, 0, 0};
+ SetGlyphs(i, *glyphData, &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()) {
+ details[0].mAdvance += synAppUnitOffset;
+ } else {
+ details[detailedLength - 1].mAdvance += synAppUnitOffset;
+ }
+ }
+ }
+ }
+}
+
+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(gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
+ AntialiasOption anAAOption, cairo_scaled_font_t *aScaledFont) :
+ mScaledFont(aScaledFont),
+ mFontEntry(aFontEntry), mIsValid(true),
+ mApplySyntheticBold(false),
+ mMathInitialized(false),
+ mStyle(*aFontStyle),
+ mAdjustedSize(0.0),
+ mFUnitsConvFactor(-1.0f), // negative to indicate "not yet initialized"
+ mAntialiasOption(anAAOption)
+{
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+ ++gFontCount;
+#endif
+ mKerningSet = HasFeatureSet(HB_TAG('k','e','r','n'), mKerningEnabled);
+}
+
+gfxFont::~gfxFont()
+{
+ mFontEntry->NotifyFontDestroyed(this);
+
+ if (mGlyphChangeObservers) {
+ for (auto it = mGlyphChangeObservers->Iter(); !it.Done(); it.Next()) {
+ it.Get()->GetKey()->ForgetFont();
+ }
+ }
+}
+
+gfxFloat
+gfxFont::GetGlyphHAdvance(DrawTarget* aDrawTarget, uint16_t aGID)
+{
+ if (!SetupCairoFont(aDrawTarget)) {
+ return 0;
+ }
+ if (ProvidesGlyphWidths()) {
+ return GetGlyphWidth(*aDrawTarget, aGID) / 65536.0;
+ }
+ if (mFUnitsConvFactor < 0.0f) {
+ GetMetrics(eHorizontal);
+ }
+ NS_ASSERTION(mFUnitsConvFactor >= 0.0f,
+ "missing font unit conversion factor");
+ if (!mHarfBuzzShaper) {
+ mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this);
+ }
+ gfxHarfBuzzShaper* shaper =
+ static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get());
+ if (!shaper->Initialize()) {
+ return 0;
+ }
+ return shaper->GetGlyphHAdvance(aGID) / 65536.0;
+}
+
+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 nsTHashtable<nsUint32HashKey>&
+ 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.GetEntry(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 nsTHashtable<nsUint32HashKey>&
+ 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();
+ nsTHashtable<nsUint32HashKey> specificFeature;
+
+ specificFeature.PutEntry(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);
+}
+
+nsDataHashtable<nsUint32HashKey,Script> *gfxFont::sScriptTagToCode = nullptr;
+nsTHashtable<nsUint32HashKey> *gfxFont::sDefaultFeatures = nullptr;
+
+static inline bool
+HasSubstitution(uint32_t *aBitVector, 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()
+{
+ mFontEntry->mHasSpaceFeaturesInitialized = true;
+
+ bool log = LOG_FONTINIT_ENABLED();
+ TimeStamp start;
+ if (MOZ_UNLIKELY(log)) {
+ start = TimeStamp::Now();
+ }
+
+ bool result = false;
+
+ uint32_t spaceGlyph = GetSpaceGlyph();
+ if (!spaceGlyph) {
+ return;
+ }
+
+ hb_face_t *face = GetFontEntry()->GetHBFace();
+
+ // GSUB lookups - examine per script
+ if (hb_ot_layout_has_substitution(face)) {
+
+ // set up the script ==> code hashtable if needed
+ if (!sScriptTagToCode) {
+ sScriptTagToCode =
+ new nsDataHashtable<nsUint32HashKey,
+ Script>(size_t(Script::NUM_SCRIPT_CODES));
+ sScriptTagToCode->Put(HB_TAG('D','F','L','T'), Script::COMMON);
+ for (Script s = Script::ARABIC; s < Script::NUM_SCRIPT_CODES;
+ s = Script(static_cast<int>(s) + 1)) {
+ hb_script_t scriptTag = hb_script_t(GetScriptTagForCode(s));
+ hb_tag_t s1, s2;
+ hb_ot_tags_from_script(scriptTag, &s1, &s2);
+ sScriptTagToCode->Put(s1, s);
+ if (s2 != HB_OT_TAG_DEFAULT_SCRIPT) {
+ sScriptTagToCode->Put(s2, s);
+ }
+ }
+
+ uint32_t numDefaultFeatures = ArrayLength(defaultFeatures);
+ sDefaultFeatures =
+ new nsTHashtable<nsUint32HashKey>(numDefaultFeatures);
+ for (uint32_t i = 0; i < numDefaultFeatures; i++) {
+ sDefaultFeatures->PutEntry(defaultFeatures[i]);
+ }
+ }
+
+ // 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) ||
+ !sScriptTagToCode->Get(scriptTags[i], &s))
+ {
+ continue;
+ }
+ result = true;
+ 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
+ mFontEntry->mHasSpaceFeaturesKerning = false;
+ mFontEntry->mHasSpaceFeaturesNonKerning = false;
+
+ 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 || hasNonKerning) {
+ result = true;
+ }
+ mFontEntry->mHasSpaceFeaturesKerning = hasKerning;
+ mFontEntry->mHasSpaceFeaturesNonKerning = hasNonKerning;
+ }
+
+ hb_face_destroy(face);
+ mFontEntry->mHasSpaceFeatures = result;
+
+ 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",
+ NS_ConvertUTF16toUTF8(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->mHasSpaceFeaturesKerning ? "true" : "false"),
+ (mFontEntry->mHasSpaceFeaturesNonKerning ? "true" : "false"),
+ elapsed.ToMilliseconds()
+ ));
+ }
+}
+
+bool
+gfxFont::HasSubstitutionRulesWithSpaceLookups(Script aRunScript)
+{
+ NS_ASSERTION(GetFontEntry()->mHasSpaceFeaturesInitialized,
+ "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;
+}
+
+bool
+gfxFont::SpaceMayParticipateInShaping(Script aRunScript)
+{
+ // 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.
+ if (!mFontEntry->mHasSpaceFeaturesInitialized) {
+ CheckForFeaturesInvolvingSpace();
+ }
+
+ if (!mFontEntry->mHasSpaceFeatures) {
+ return false;
+ }
+
+ // if font has substitution rules or non-kerning positioning rules
+ // that involve spaces, bypass
+ if (HasSubstitutionRulesWithSpaceLookups(aRunScript) ||
+ mFontEntry->mHasSpaceFeaturesNonKerning) {
+ 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 && mFontEntry->mHasSpaceFeaturesKerning) {
+ 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;
+ }
+
+ if (!mHarfBuzzShaper) {
+ mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this);
+ }
+ gfxHarfBuzzShaper* shaper =
+ static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get());
+ if (!shaper->Initialize()) {
+ 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_HIGH_SURROGATE(ch) &&
+ NS_IS_LOW_SURROGATE(aString[i + 1])) {
+ i++;
+ ch = SURROGATE_TO_UCS4(ch, aString[i]);
+ }
+
+ if (ch == 0xa0) {
+ ch = ' ';
+ }
+
+ 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::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;
+}
+
+/**
+ * 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
+{
+public:
+ GlyphBufferAzure(const TextRunDrawParams& aRunParams,
+ const FontDrawParams& aFontParams)
+ : mRunParams(aRunParams)
+ , mFontParams(aFontParams)
+ , mNumGlyphs(0)
+ {
+ }
+
+ ~GlyphBufferAzure()
+ {
+ Flush(true); // flush any remaining buffered glyphs
+ }
+
+ void OutputGlyph(uint32_t aGlyphID, const gfxPoint& aPt)
+ {
+ Glyph *glyph = AppendGlyph();
+ glyph->mIndex = aGlyphID;
+ glyph->mPosition.x = aPt.x;
+ glyph->mPosition.y = aPt.y;
+ glyph->mPosition = mFontParams.matInv.TransformPoint(glyph->mPosition);
+ Flush(false); // this will flush only if the buffer is full
+ }
+
+ const TextRunDrawParams& mRunParams;
+ const FontDrawParams& mFontParams;
+
+private:
+#define GLYPH_BUFFER_SIZE (2048/sizeof(Glyph))
+
+ Glyph *AppendGlyph()
+ {
+ return &mGlyphBuffer[mNumGlyphs++];
+ }
+
+ static DrawMode
+ GetStrokeMode(DrawMode aMode)
+ {
+ return aMode & (DrawMode::GLYPH_STROKE |
+ DrawMode::GLYPH_STROKE_UNDERNEATH);
+ }
+
+ // Render the buffered glyphs to the draw target and clear the buffer.
+ // This actually flushes the glyphs only if the buffer is full, or if the
+ // aFinish parameter is true; otherwise it simply returns.
+ void Flush(bool aFinish)
+ {
+ // Ensure there's enough room for a glyph to be added to the buffer
+ if ((!aFinish && mNumGlyphs < GLYPH_BUFFER_SIZE) || !mNumGlyphs) {
+ return;
+ }
+
+ if (mRunParams.isRTL) {
+ Glyph *begin = &mGlyphBuffer[0];
+ Glyph *end = &mGlyphBuffer[mNumGlyphs];
+ std::reverse(begin, end);
+ }
+
+ gfx::GlyphBuffer buf;
+ buf.mGlyphs = mGlyphBuffer;
+ buf.mNumGlyphs = mNumGlyphs;
+
+ gfxContext::AzureState state = mRunParams.context->CurrentState();
+ if (mRunParams.drawMode & DrawMode::GLYPH_FILL) {
+ if (state.pattern || mFontParams.contextPaint) {
+ Pattern *pat;
+
+ RefPtr<gfxPattern> fillPattern;
+ if (!mFontParams.contextPaint ||
+ !(fillPattern = mFontParams.contextPaint->GetFillPattern(
+ mRunParams.context->GetDrawTarget(),
+ mRunParams.context->CurrentMatrix()))) {
+ if (state.pattern) {
+ pat = state.pattern->GetPattern(mRunParams.dt,
+ state.patternTransformChanged ?
+ &state.patternTransform : nullptr);
+ } else {
+ pat = nullptr;
+ }
+ } else {
+ pat = fillPattern->GetPattern(mRunParams.dt);
+ }
+
+ if (pat) {
+ Matrix saved;
+ Matrix *mat = nullptr;
+ if (mFontParams.passedInvMatrix) {
+ // The brush matrix needs to be multiplied with the
+ // inverted matrix as well, to move the brush into the
+ // space of the glyphs.
+
+ // This relies on the returned Pattern not to be reused
+ // by others, but regenerated on GetPattern calls. This
+ // is true!
+ if (pat->GetType() == PatternType::LINEAR_GRADIENT) {
+ mat = &static_cast<LinearGradientPattern*>(pat)->mMatrix;
+ } else if (pat->GetType() == PatternType::RADIAL_GRADIENT) {
+ mat = &static_cast<RadialGradientPattern*>(pat)->mMatrix;
+ } else if (pat->GetType() == PatternType::SURFACE) {
+ mat = &static_cast<SurfacePattern*>(pat)->mMatrix;
+ }
+
+ if (mat) {
+ saved = *mat;
+ *mat = (*mat) * (*mFontParams.passedInvMatrix);
+ }
+ }
+
+ mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf,
+ *pat, mFontParams.drawOptions,
+ mFontParams.renderingOptions);
+
+ if (mat) {
+ *mat = saved;
+ }
+ }
+ } else if (state.sourceSurface) {
+ mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf,
+ SurfacePattern(state.sourceSurface,
+ ExtendMode::CLAMP,
+ state.surfTransform),
+ mFontParams.drawOptions,
+ mFontParams.renderingOptions);
+ } else {
+ mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf,
+ ColorPattern(state.color),
+ mFontParams.drawOptions,
+ mFontParams.renderingOptions);
+ }
+ }
+ if (GetStrokeMode(mRunParams.drawMode) == DrawMode::GLYPH_STROKE &&
+ mRunParams.strokeOpts) {
+ Pattern *pat;
+ if (mRunParams.textStrokePattern) {
+ pat = mRunParams.textStrokePattern->GetPattern(
+ mRunParams.dt, state.patternTransformChanged
+ ? &state.patternTransform
+ : nullptr);
+
+ if (pat) {
+ Matrix saved;
+ Matrix *mat = nullptr;
+ if (mFontParams.passedInvMatrix) {
+ // The brush matrix needs to be multiplied with the
+ // inverted matrix as well, to move the brush into the
+ // space of the glyphs.
+
+ // This relies on the returned Pattern not to be reused
+ // by others, but regenerated on GetPattern calls. This
+ // is true!
+ if (pat->GetType() == PatternType::LINEAR_GRADIENT) {
+ mat = &static_cast<LinearGradientPattern*>(pat)->mMatrix;
+ } else if (pat->GetType() == PatternType::RADIAL_GRADIENT) {
+ mat = &static_cast<RadialGradientPattern*>(pat)->mMatrix;
+ } else if (pat->GetType() == PatternType::SURFACE) {
+ mat = &static_cast<SurfacePattern*>(pat)->mMatrix;
+ }
+
+ if (mat) {
+ saved = *mat;
+ *mat = (*mat) * (*mFontParams.passedInvMatrix);
+ }
+ }
+ FlushStroke(buf, *pat);
+
+ if (mat) {
+ *mat = saved;
+ }
+ }
+ } else {
+ FlushStroke(buf,
+ ColorPattern(
+ Color::FromABGR(mRunParams.textStrokeColor)));
+ }
+ }
+ if (mRunParams.drawMode & DrawMode::GLYPH_PATH) {
+ mRunParams.context->EnsurePathBuilder();
+ Matrix mat = mRunParams.dt->GetTransform();
+ mFontParams.scaledFont->CopyGlyphsToBuilder(
+ buf, mRunParams.context->mPathBuilder, &mat);
+ }
+
+ mNumGlyphs = 0;
+ }
+
+ void FlushStroke(gfx::GlyphBuffer& aBuf, const Pattern& aPattern)
+ {
+ RefPtr<Path> path =
+ mFontParams.scaledFont->GetPathForGlyphs(aBuf, mRunParams.dt);
+ mRunParams.dt->Stroke(path, aPattern, *mRunParams.strokeOpts,
+ (mRunParams.drawOpts) ? *mRunParams.drawOpts
+ : DrawOptions());
+ }
+
+ Glyph mGlyphBuffer[GLYPH_BUFFER_SIZE];
+ unsigned int mNumGlyphs;
+
+#undef GLYPH_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.
+
+double
+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;
+ }
+
+ double m = sqrt(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 an individual glyph at a specific location.
+// *aPt is the glyph position in appUnits; it is converted to device
+// coordinates (devPt) here.
+void
+gfxFont::DrawOneGlyph(uint32_t aGlyphID, double aAdvance, gfxPoint *aPt,
+ GlyphBufferAzure& aBuffer, bool *aEmittedGlyphs) const
+{
+ const TextRunDrawParams& runParams(aBuffer.mRunParams);
+ const FontDrawParams& fontParams(aBuffer.mFontParams);
+
+ double glyphX, glyphY;
+ if (fontParams.isVerticalFont) {
+ glyphX = aPt->x;
+ if (runParams.isRTL) {
+ aPt->y -= aAdvance;
+ glyphY = aPt->y;
+ } else {
+ glyphY = aPt->y;
+ aPt->y += aAdvance;
+ }
+ } else {
+ glyphY = aPt->y;
+ if (runParams.isRTL) {
+ aPt->x -= aAdvance;
+ glyphX = aPt->x;
+ } else {
+ glyphX = aPt->x;
+ aPt->x += aAdvance;
+ }
+ }
+ gfxPoint devPt(ToDeviceUnits(glyphX, runParams.devPerApp),
+ ToDeviceUnits(glyphY, runParams.devPerApp));
+
+ 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, devPt,
+ aGlyphID, fontParams.contextPaint,
+ runParams.callbacks, *aEmittedGlyphs)) {
+ return;
+ }
+ }
+
+ if (fontParams.haveColorGlyphs &&
+ RenderColorGlyph(runParams.dt, runParams.context,
+ fontParams.scaledFont, fontParams.renderingOptions,
+ fontParams.drawOptions,
+ fontParams.matInv.TransformPoint(gfx::Point(devPt.x, devPt.y)),
+ 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);
+ }
+
+ *aEmittedGlyphs = true;
+}
+
+// Draw a run of CharacterGlyph records from the given offset in aShapedText.
+// Returns true if glyph paths were actually emitted.
+bool
+gfxFont::DrawGlyphs(const gfxShapedText *aShapedText,
+ uint32_t aOffset, // offset in the textrun
+ uint32_t aCount, // length of run to draw
+ gfxPoint *aPt,
+ const TextRunDrawParams& aRunParams,
+ const FontDrawParams& aFontParams)
+{
+ bool emittedGlyphs = false;
+ GlyphBufferAzure buffer(aRunParams, aFontParams);
+
+ gfxFloat& inlineCoord = aFontParams.isVerticalFont ? aPt->y : aPt->x;
+
+ if (aRunParams.spacing) {
+ inlineCoord += aRunParams.isRTL ? -aRunParams.spacing[0].mBefore
+ : aRunParams.spacing[0].mBefore;
+ }
+
+ const gfxShapedText::CompressedGlyph *glyphData =
+ &aShapedText->GetCharacterGlyphs()[aOffset];
+
+ for (uint32_t i = 0; i < aCount; ++i, ++glyphData) {
+ if (glyphData->IsSimpleGlyph()) {
+ DrawOneGlyph(glyphData->GetSimpleGlyph(),
+ glyphData->GetSimpleAdvance(),
+ aPt, buffer, &emittedGlyphs);
+ } else {
+ uint32_t glyphCount = glyphData->GetGlyphCount();
+ if (glyphCount > 0) {
+ const gfxShapedText::DetailedGlyph *details =
+ aShapedText->GetDetailedGlyphs(aOffset + i);
+ NS_ASSERTION(details, "detailedGlyph should not be missing!");
+ for (uint32_t j = 0; j < glyphCount; ++j, ++details) {
+ double advance = details->mAdvance;
+
+ if (glyphData->IsMissing()) {
+ // Default-ignorable chars will have zero advance width;
+ // we don't have to draw the hexbox for them.
+ if (aRunParams.drawMode != DrawMode::GLYPH_PATH &&
+ advance > 0) {
+ double glyphX = aPt->x;
+ double glyphY = aPt->y;
+ if (aRunParams.isRTL) {
+ if (aFontParams.isVerticalFont) {
+ glyphY -= advance;
+ } else {
+ glyphX -= advance;
+ }
+ }
+ Point pt(Float(ToDeviceUnits(glyphX, aRunParams.devPerApp)),
+ Float(ToDeviceUnits(glyphY, aRunParams.devPerApp)));
+ Float advanceDevUnits =
+ Float(ToDeviceUnits(advance, aRunParams.devPerApp));
+ Float height = GetMetrics(eHorizontal).maxAscent;
+ Rect glyphRect = aFontParams.isVerticalFont ?
+ 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 remove
+ // this before drawing the hexbox. (Bug 983985)
+ Matrix oldMat;
+ if (aFontParams.passedInvMatrix) {
+ oldMat = aRunParams.dt->GetTransform();
+ aRunParams.dt->SetTransform(
+ *aFontParams.passedInvMatrix * oldMat);
+ }
+
+ gfxFontMissingGlyphs::DrawMissingGlyph(
+ details->mGlyphID, glyphRect, *aRunParams.dt,
+ PatternFromState(aRunParams.context),
+ aShapedText->GetAppUnitsPerDevUnit());
+
+ // Restore the matrix, if we modified it before
+ // drawing the hexbox.
+ if (aFontParams.passedInvMatrix) {
+ aRunParams.dt->SetTransform(oldMat);
+ }
+ }
+ } else {
+ gfxPoint glyphXY(*aPt);
+ if (aFontParams.isVerticalFont) {
+ glyphXY.x += details->mYOffset;
+ glyphXY.y += details->mXOffset;
+ } else {
+ glyphXY.x += details->mXOffset;
+ glyphXY.y += details->mYOffset;
+ }
+ DrawOneGlyph(details->mGlyphID, advance, &glyphXY,
+ buffer, &emittedGlyphs);
+ }
+
+ inlineCoord += aRunParams.isRTL ? -advance : advance;
+ }
+ }
+ }
+
+ if (aRunParams.spacing) {
+ double space = aRunParams.spacing[i].mAfter;
+ if (i + 1 < aCount) {
+ space += aRunParams.spacing[i + 1].mBefore;
+ }
+ inlineCoord += aRunParams.isRTL ? -space : space;
+ }
+ }
+
+ return emittedGlyphs;
+}
+
+// This method is mostly parallel to DrawGlyphs.
+void
+gfxFont::DrawEmphasisMarks(const gfxTextRun* aShapedText, gfxPoint* aPt,
+ uint32_t aOffset, uint32_t aCount,
+ const EmphasisMarkDrawParams& aParams)
+{
+ gfxFloat& inlineCoord = aParams.isVertical ? aPt->y : aPt->x;
+ gfxTextRun::Range markRange(aParams.mark);
+ gfxTextRun::DrawParams params(aParams.context);
+
+ gfxFloat clusterStart = -std::numeric_limits<gfxFloat>::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<gfxFloat>::infinity()) {
+ clusterStart = inlineCoord;
+ }
+ if (aShapedText->CharMayHaveEmphasisMark(idx)) {
+ shouldDrawEmphasisMark = true;
+ }
+ inlineCoord += aParams.direction * aShapedText->GetAdvanceForGlyph(idx);
+ if (shouldDrawEmphasisMark &&
+ (i + 1 == aCount || aShapedText->IsClusterStart(idx + 1))) {
+ gfxFloat clusterAdvance = inlineCoord - clusterStart;
+ // Move the coord backward to get the needed start point.
+ gfxFloat 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;
+ }
+ }
+}
+
+void
+gfxFont::Draw(const gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd,
+ gfxPoint *aPt, const TextRunDrawParams& aRunParams,
+ uint16_t 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.dt);
+ if (!fontParams.scaledFont) {
+ return;
+ }
+
+ fontParams.haveSVGGlyphs = GetFontEntry()->TryGetSVGData(this);
+ fontParams.haveColorGlyphs = GetFontEntry()->TryGetColorGlyphs();
+ fontParams.contextPaint = aRunParams.runContextPaint;
+ fontParams.isVerticalFont =
+ aOrientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
+
+ bool sideways = false;
+ gfxPoint origPt = *aPt;
+ if (aRunParams.isVerticalRun && !fontParams.isVerticalFont) {
+ sideways = true;
+ aRunParams.context->Save();
+ gfxPoint p(aPt->x * aRunParams.devPerApp,
+ aPt->y * aRunParams.devPerApp);
+ const Metrics& metrics = GetMetrics(eHorizontal);
+ // Get a matrix we can use to draw the (horizontally-shaped) textrun
+ // with 90-degree CW rotation.
+ const gfxFloat
+ rotation = (aOrientation ==
+ gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT)
+ ? -M_PI / 2.0 : M_PI / 2.0;
+ gfxMatrix mat =
+ aRunParams.context->CurrentMatrix().
+ Translate(p). // translate origin for rotation
+ Rotate(rotation). // turn 90deg CCW (sideways-left) or CW (*-right)
+ Translate(-p); // undo the translation
+
+ // 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()) {
+ gfxPoint baseAdj(0, (metrics.emAscent - metrics.emDescent) / 2);
+ mat.Translate(baseAdj);
+ }
+
+ aRunParams.context->SetMatrix(mat);
+ }
+
+ UniquePtr<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.reset(
+ new SimpleTextContextPaint(fillPattern, nullptr,
+ aRunParams.context->CurrentMatrix()));
+ fontParams.contextPaint = contextPaint.get();
+ }
+
+ // Synthetic-bold strikes are each offset one device pixel in run direction.
+ // (these values are only needed if IsSyntheticBold() is true)
+ if (IsSyntheticBold()) {
+ double xscale = CalcXScale(aRunParams.context->GetDrawTarget());
+ fontParams.synBoldOnePixelOffset = aRunParams.direction * xscale;
+ if (xscale != 0.0) {
+ // use as many strikes as needed for the the increased advance
+ fontParams.extraStrikes =
+ std::max(1, NS_lroundf(GetSyntheticBoldOffset() / xscale));
+ }
+ } else {
+ fontParams.synBoldOnePixelOffset = 0;
+ fontParams.extraStrikes = 0;
+ }
+
+ bool oldSubpixelAA = aRunParams.dt->GetPermitSubpixelAA();
+ if (!AllowSubpixelAA()) {
+ aRunParams.dt->SetPermitSubpixelAA(false);
+ }
+
+ Matrix mat;
+ Matrix oldMat = aRunParams.dt->GetTransform();
+
+ // This is nullptr when we have inverse-transformed glyphs and we need
+ // to transform the Brush inside flush.
+ fontParams.passedInvMatrix = nullptr;
+
+ fontParams.renderingOptions = GetGlyphRenderingOptions(&aRunParams);
+ fontParams.drawOptions.mAntialiasMode = Get2DAAMode(mAntialiasOption);
+
+ // The cairo DrawTarget backend uses the cairo_scaled_font directly
+ // and so has the font skew matrix applied already.
+ if (mScaledFont &&
+ aRunParams.dt->GetBackendType() != BackendType::CAIRO) {
+ cairo_matrix_t matrix;
+ cairo_scaled_font_get_font_matrix(mScaledFont, &matrix);
+ if (matrix.xy != 0) {
+ // If this matrix applies a skew, which can happen when drawing
+ // oblique fonts, we will set the DrawTarget matrix to apply the
+ // skew. We'll need to move the glyphs by the inverse of the skew to
+ // get the glyphs positioned correctly in the new device space
+ // though, since the font matrix should only be applied to drawing
+ // the glyphs, and not to their position.
+ mat = Matrix(matrix.xx, matrix.yx,
+ matrix.xy, matrix.yy,
+ matrix.x0, matrix.y0);
+
+ mat._11 = mat._22 = 1.0;
+ mat._21 /= GetAdjustedSize();
+
+ aRunParams.dt->SetTransform(mat * oldMat);
+
+ fontParams.matInv = mat;
+ fontParams.matInv.Invert();
+
+ fontParams.passedInvMatrix = &fontParams.matInv;
+ }
+ }
+
+ gfxFloat& baseline = fontParams.isVerticalFont ? aPt->x : aPt->y;
+ gfxFloat origBaseline = baseline;
+ if (mStyle.baselineOffset != 0.0) {
+ baseline +=
+ mStyle.baselineOffset * aTextRun->GetAppUnitsPerDevUnit();
+ }
+
+ bool emittedGlyphs =
+ DrawGlyphs(aTextRun, aStart, aEnd - aStart, aPt,
+ aRunParams, fontParams);
+
+ baseline = origBaseline;
+
+ if (aRunParams.callbacks && emittedGlyphs) {
+ aRunParams.callbacks->NotifyGlyphPathEmitted();
+ }
+
+ aRunParams.dt->SetTransform(oldMat);
+ aRunParams.dt->SetPermitSubpixelAA(oldSubpixelAA);
+
+ if (sideways) {
+ aRunParams.context->Restore();
+ // adjust updated aPt to account for the transform we were using
+ gfxFloat advance = aPt->x - origPt.x;
+ if (aOrientation ==
+ gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT) {
+ *aPt = gfxPoint(origPt.x, origPt.y - advance);
+ } else {
+ *aPt = gfxPoint(origPt.x, origPt.y + advance);
+ }
+ }
+}
+
+bool
+gfxFont::RenderSVGGlyph(gfxContext *aContext, gfxPoint aPoint,
+ uint32_t aGlyphId, SVGContextPaint* aContextPaint) const
+{
+ if (!GetFontEntry()->HasSVGGlyph(aGlyphId)) {
+ return false;
+ }
+
+ const gfxFloat devUnitsPerSVGUnit =
+ GetAdjustedSize() / GetFontEntry()->UnitsPerEm();
+ gfxContextMatrixAutoSaveRestore matrixRestore(aContext);
+
+ aContext->Save();
+ aContext->SetMatrix(
+ aContext->CurrentMatrix().Translate(aPoint.x, aPoint.y).
+ Scale(devUnitsPerSVGUnit, devUnitsPerSVGUnit));
+
+ aContextPaint->InitStrokeGeometry(aContext, devUnitsPerSVGUnit);
+
+ bool rv = GetFontEntry()->RenderSVGGlyph(aContext, aGlyphId,
+ aContextPaint);
+ aContext->Restore();
+ aContext->NewPath();
+ return rv;
+}
+
+bool
+gfxFont::RenderSVGGlyph(gfxContext *aContext, gfxPoint aPoint,
+ uint32_t aGlyphId, SVGContextPaint* aContextPaint,
+ gfxTextRunDrawCallbacks *aCallbacks,
+ bool& aEmittedGlyphs) const
+{
+ if (aCallbacks && aEmittedGlyphs) {
+ aCallbacks->NotifyGlyphPathEmitted();
+ aEmittedGlyphs = false;
+ }
+ return RenderSVGGlyph(aContext, aPoint, aGlyphId, aContextPaint);
+}
+
+bool
+gfxFont::RenderColorGlyph(DrawTarget* aDrawTarget,
+ gfxContext* aContext,
+ mozilla::gfx::ScaledFont* scaledFont,
+ GlyphRenderingOptions* aRenderingOptions,
+ mozilla::gfx::DrawOptions aDrawOptions,
+ const mozilla::gfx::Point& aPoint,
+ uint32_t aGlyphId) const
+{
+ AutoTArray<uint16_t, 8> layerGlyphs;
+ AutoTArray<mozilla::gfx::Color, 8> layerColors;
+
+ mozilla::gfx::Color defaultColor;
+ if (!aContext->GetDeviceColor(defaultColor)) {
+ defaultColor = mozilla::gfx::Color(0, 0, 0);
+ }
+ if (!GetFontEntry()->GetColorLayersInfo(aGlyphId, defaultColor,
+ layerGlyphs, layerColors)) {
+ return false;
+ }
+
+ for (uint32_t layerIndex = 0; layerIndex < layerGlyphs.Length();
+ layerIndex++) {
+ Glyph glyph;
+ glyph.mIndex = layerGlyphs[layerIndex];
+ glyph.mPosition = aPoint;
+
+ mozilla::gfx::GlyphBuffer buffer;
+ buffer.mGlyphs = &glyph;
+ buffer.mNumGlyphs = 1;
+
+ aDrawTarget->FillGlyphs(scaledFont, buffer,
+ ColorPattern(layerColors[layerIndex]),
+ aDrawOptions, aRenderingOptions);
+ }
+ return true;
+}
+
+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() & gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX) ||
+ aFont->GetFontEntry()->IsUserFont();
+}
+
+bool
+gfxFont::IsSpaceGlyphInvisible(DrawTarget* aRefDrawTarget,
+ const gfxTextRun* aTextRun)
+{
+ if (!mFontEntry->mSpaceGlyphIsInvisibleInitialized &&
+ GetAdjustedSize() >= 1.0) {
+ gfxGlyphExtents *extents =
+ GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit());
+ gfxRect glyphExtents;
+ mFontEntry->mSpaceGlyphIsInvisible =
+ extents->GetTightGlyphExtentsAppUnits(this, aRefDrawTarget,
+ GetSpaceGlyph(), &glyphExtents) &&
+ glyphExtents.IsEmpty();
+ mFontEntry->mSpaceGlyphIsInvisibleInitialized = true;
+ }
+ return mFontEntry->mSpaceGlyphIsInvisible;
+}
+
+gfxFont::RunMetrics
+gfxFont::Measure(const gfxTextRun *aTextRun,
+ uint32_t aStart, uint32_t aEnd,
+ BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget,
+ Spacing *aSpacing,
+ uint16_t 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) {
+ if (!mNonAAFont) {
+ mNonAAFont.reset(CopyWithAntialiasOption(kAntialiasNone));
+ }
+ // if font subclass doesn't implement CopyWithAntialiasOption(),
+ // it will return null and we'll proceed to use the existing font
+ if (mNonAAFont) {
+ return mNonAAFont->Measure(aTextRun, aStart, aEnd,
+ TIGHT_HINTED_OUTLINE_EXTENTS,
+ aRefDrawTarget, aSpacing, aOrientation);
+ }
+ }
+
+ const int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
+ // Current position in appunits
+ gfxFont::Orientation orientation =
+ aOrientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT
+ ? eVertical : eHorizontal;
+ const gfxFont::Metrics& fontMetrics = GetMetrics(orientation);
+
+ gfxFloat baselineOffset = 0;
+ if (aTextRun->UseCenterBaseline() && orientation == 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;
+ const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs();
+ bool isRTL = aTextRun->IsRightToLeft();
+ double direction = aTextRun->GetDirection();
+ bool needsGlyphExtents = NeedsGlyphExtents(this, aTextRun);
+ gfxGlyphExtents *extents =
+ ((aBoundingBoxType == LOOSE_INK_EXTENTS &&
+ !needsGlyphExtents &&
+ !aTextRun->HasDetailedGlyphs()) ||
+ (MOZ_UNLIKELY(GetStyle()->sizeAdjust == 0.0)) ||
+ (MOZ_UNLIKELY(GetStyle()->size == 0))) ? nullptr
+ : GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit());
+ double x = 0;
+ if (aSpacing) {
+ x += direction*aSpacing[0].mBefore;
+ }
+ uint32_t spaceGlyph = GetSpaceGlyph();
+ bool allGlyphsInvisible = true;
+ uint32_t i;
+ for (i = aStart; i < aEnd; ++i) {
+ const gfxTextRun::CompressedGlyph *glyphData = &charGlyphs[i];
+ if (glyphData->IsSimpleGlyph()) {
+ double advance = glyphData->GetSimpleAdvance();
+ uint32_t glyphIndex = glyphData->GetSimpleGlyph();
+ if (glyphIndex != spaceGlyph ||
+ !IsSpaceGlyphInvisible(aRefDrawTarget, aTextRun)) {
+ 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 || needsGlyphExtents) &&
+ extents){
+ uint16_t extentsWidth = extents->GetContainedGlyphWidthAppUnits(glyphIndex);
+ if (extentsWidth != gfxGlyphExtents::INVALID_WIDTH &&
+ aBoundingBoxType == LOOSE_INK_EXTENTS) {
+ UnionRange(x, &advanceMin, &advanceMax);
+ UnionRange(x + direction*extentsWidth, &advanceMin, &advanceMax);
+ } else {
+ gfxRect glyphRect;
+ if (!extents->GetTightGlyphExtentsAppUnits(this,
+ aRefDrawTarget, glyphIndex, &glyphRect)) {
+ glyphRect = gfxRect(0, metrics.mBoundingBox.Y(),
+ advance, metrics.mBoundingBox.Height());
+ }
+ if (orientation == eVertical) {
+ Swap(glyphRect.x, glyphRect.y);
+ Swap(glyphRect.width, glyphRect.height);
+ }
+ if (isRTL) {
+ glyphRect -= gfxPoint(advance, 0);
+ }
+ glyphRect += gfxPoint(x, 0);
+ metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect);
+ }
+ }
+ x += direction*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;
+ gfxPoint glyphPt(x + details->mXOffset, details->mYOffset);
+ double advance = details->mAdvance;
+ gfxRect glyphRect;
+ if (glyphData->IsMissing() || !extents ||
+ !extents->GetTightGlyphExtentsAppUnits(this,
+ aRefDrawTarget, glyphIndex, &glyphRect)) {
+ // We might have failed to get glyph extents due to
+ // OOM or something
+ glyphRect = gfxRect(0, -metrics.mAscent,
+ advance, metrics.mAscent + metrics.mDescent);
+ }
+ if (orientation == eVertical) {
+ Swap(glyphRect.x, glyphRect.y);
+ Swap(glyphRect.width, glyphRect.height);
+ }
+ if (isRTL) {
+ glyphRect -= gfxPoint(advance, 0);
+ }
+ glyphRect += glyphPt;
+ metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect);
+ x += direction*advance;
+ }
+ }
+ }
+ // Every other glyph type is ignored
+ if (aSpacing) {
+ double space = aSpacing[i - aStart].mAfter;
+ if (i + 1 < aEnd) {
+ space += aSpacing[i + 1 - aStart].mBefore;
+ }
+ x += direction*space;
+ }
+ }
+
+ if (allGlyphsInvisible) {
+ metrics.mBoundingBox.SetEmpty();
+ } else {
+ if (aBoundingBoxType == LOOSE_INK_EXTENTS) {
+ UnionRange(x, &advanceMin, &advanceMax);
+ gfxRect fontBox(advanceMin, -metrics.mAscent,
+ advanceMax - advanceMin, metrics.mAscent + metrics.mDescent);
+ metrics.mBoundingBox = metrics.mBoundingBox.Union(fontBox);
+ }
+ if (isRTL) {
+ metrics.mBoundingBox -= gfxPoint(x, 0);
+ }
+ }
+
+ // 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.
+ if (mStyle.style != NS_FONT_STYLE_NORMAL &&
+ mFontEntry->IsUpright() &&
+ mStyle.allowSyntheticStyle) {
+ gfxFloat extendLeftEdge =
+ ceil(OBLIQUE_SKEW_FACTOR * metrics.mBoundingBox.YMost());
+ gfxFloat extendRightEdge =
+ ceil(OBLIQUE_SKEW_FACTOR * -metrics.mBoundingBox.y);
+ metrics.mBoundingBox.width += extendLeftEdge + extendRightEdge;
+ metrics.mBoundingBox.x -= extendLeftEdge;
+ }
+
+ if (baselineOffset != 0) {
+ metrics.mAscent -= baselineOffset;
+ metrics.mDescent += baselineOffset;
+ metrics.mBoundingBox.y += baselineOffset;
+ }
+
+ metrics.mAdvanceWidth = x*direction;
+ return metrics;
+}
+
+void
+gfxFont::AgeCachedWords()
+{
+ 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();
+ }
+ }
+ }
+}
+
+void
+gfxFont::NotifyGlyphsChanged()
+{
+ uint32_t i, count = mGlyphExtentsArray.Length();
+ for (i = 0; i < count; ++i) {
+ // Flush cached extents array
+ mGlyphExtentsArray[i]->NotifyGlyphsChanged();
+ }
+
+ if (mGlyphChangeObservers) {
+ for (auto it = mGlyphChangeObservers->Iter(); !it.Done(); it.Next()) {
+ it.Get()->GetKey()->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>
+gfxShapedWord*
+gfxFont::GetShapedWord(DrawTarget *aDrawTarget,
+ const T *aText,
+ uint32_t aLength,
+ uint32_t aHash,
+ Script aRunScript,
+ bool aVertical,
+ int32_t aAppUnitsPerDevUnit,
+ uint32_t aFlags,
+ gfxTextPerfMetrics *aTextPerf GFX_MAYBE_UNUSED)
+{
+ // if the cache is getting too big, flush it and start over
+ uint32_t wordCacheMaxEntries =
+ gfxPlatform::GetPlatform()->WordCacheMaxEntries();
+ if (mWordCache->Count() > wordCacheMaxEntries) {
+ NS_WARNING("flushing shaped-word cache");
+ ClearCachedWords();
+ }
+
+ // if there's a cached entry for this word, just return it
+ CacheHashKey key(aText, aLength, aHash,
+ aRunScript,
+ aAppUnitsPerDevUnit,
+ aFlags);
+
+ CacheHashEntry *entry = mWordCache->PutEntry(key);
+ if (!entry) {
+ NS_WARNING("failed to create word cache entry - expect missing text");
+ return nullptr;
+ }
+ gfxShapedWord* sw = entry->mShapedWord.get();
+
+ bool isContent = !mStyle.systemFont;
+
+ if (sw) {
+ sw->ResetAge();
+ Telemetry::Accumulate((isContent ? Telemetry::WORD_CACHE_HITS_CONTENT :
+ Telemetry::WORD_CACHE_HITS_CHROME),
+ aLength);
+#ifndef RELEASE_OR_BETA
+ if (aTextPerf) {
+ aTextPerf->current.wordCacheHit++;
+ }
+#endif
+ return sw;
+ }
+
+ Telemetry::Accumulate((isContent ? Telemetry::WORD_CACHE_MISSES_CONTENT :
+ Telemetry::WORD_CACHE_MISSES_CHROME),
+ aLength);
+#ifndef RELEASE_OR_BETA
+ if (aTextPerf) {
+ aTextPerf->current.wordCacheMiss++;
+ }
+#endif
+
+ sw = gfxShapedWord::Create(aText, aLength, aRunScript, aAppUnitsPerDevUnit,
+ aFlags);
+ entry->mShapedWord.reset(sw);
+ if (!sw) {
+ NS_WARNING("failed to create gfxShapedWord - expect missing text");
+ return nullptr;
+ }
+
+ DebugOnly<bool> ok =
+ ShapeText(aDrawTarget, aText, 0, aLength, aRunScript, aVertical, sw);
+
+ NS_WARNING_ASSERTION(ok, "failed to shape word - expect garbled text");
+
+ return sw;
+}
+
+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->GetAppUnitsPerDevUnit() != aKey->mAppUnitsPerDevUnit ||
+ sw->GetScript() != aKey->mScript) {
+ 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 & gfxTextRunFactory::TEXT_IS_8BIT) == 0 &&
+ !aKey->mTextIs8Bit, "didn't expect 8-bit text here");
+ return (0 == memcmp(sw->TextUnicode(), aKey->mText.mDouble,
+ aKey->mLength * sizeof(char16_t)));
+}
+
+bool
+gfxFont::ShapeText(DrawTarget *aDrawTarget,
+ const uint8_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ 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, aVertical, aShapedText);
+}
+
+bool
+gfxFont::ShapeText(DrawTarget *aDrawTarget,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxShapedText *aShapedText)
+{
+ bool ok = false;
+
+ // XXX Currently, we do all vertical shaping through harfbuzz.
+ // Vertical graphite support may be wanted as a future enhancement.
+ if (FontCanSupportGraphite() && !aVertical) {
+ if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
+ if (!mGraphiteShaper) {
+ mGraphiteShaper = MakeUnique<gfxGraphiteShaper>(this);
+ }
+ ok = mGraphiteShaper->ShapeText(aDrawTarget, aText, aOffset, aLength,
+ aScript, aVertical, aShapedText);
+ }
+ }
+
+ if (!ok) {
+ if (!mHarfBuzzShaper) {
+ mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this);
+ }
+ ok = mHarfBuzzShaper->ShapeText(aDrawTarget, aText, aOffset, aLength,
+ aScript, aVertical, aShapedText);
+ }
+
+ NS_WARNING_ASSERTION(ok, "shaper failed, expect scrambled or missing text");
+
+ PostShapingFixup(aDrawTarget, aText, aOffset, aLength,
+ aVertical, aShapedText);
+
+ return ok;
+}
+
+void
+gfxFont::PostShapingFixup(DrawTarget* aDrawTarget,
+ const char16_t* aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ bool aVertical,
+ gfxShapedText* aShapedText)
+{
+ if (IsSyntheticBold()) {
+ const Metrics& metrics =
+ GetMetrics(aVertical ? eVertical : eHorizontal);
+ if (metrics.maxAdvance > metrics.aveCharWidth) {
+ float synBoldOffset =
+ GetSyntheticBoldOffset() * CalcXScale(aDrawTarget);
+ aShapedText->AdjustAdvancesForSyntheticBold(synBoldOffset,
+ 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,
+ bool aVertical,
+ 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 (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_LOW_SURROGATE(aText[fragLen]) &&
+ NS_IS_HIGH_SURROGATE(aText[fragLen - 1])) {
+ --fragLen;
+ }
+ }
+ }
+ }
+
+ ok = ShapeText(aDrawTarget, aText, aOffset, fragLen, aScript, aVertical,
+ 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,
+ bool aVertical,
+ 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, aVertical, 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 (IsInvalidControlChar(ch) &&
+ !(aTextRun->GetFlags() & gfxTextRunFactory::TEXT_HIDE_CONTROL_CHARACTERS)) {
+ if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) {
+ ShapeFragmentWithoutWordCache(aDrawTarget, aText + i,
+ aOffset + i, 1,
+ aScript, aVertical, 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,
+ bool aVertical)
+{
+ if (aRunLength == 0) {
+ return true;
+ }
+
+ gfxTextPerfMetrics *tp = nullptr;
+
+#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();
+
+ // 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.
+ if (SpaceMayParticipateInShaping(aRunScript)) {
+ if (aRunLength > wordCacheCharLimit ||
+ HasSpaces(aString, aRunLength)) {
+ TEXT_PERF_INCR(tp, wordCacheSpaceRules);
+ return ShapeTextWithoutWordCache(aDrawTarget, aString,
+ aRunStart, aRunLength,
+ aRunScript, aVertical,
+ aTextRun);
+ }
+ }
+
+ InitWordCache();
+
+ // the only flags we care about for ShapedWord construction/caching
+ uint32_t flags = aTextRun->GetFlags();
+ flags &= (gfxTextRunFactory::TEXT_IS_RTL |
+ gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES |
+ gfxTextRunFactory::TEXT_USE_MATH_SCRIPT |
+ gfxTextRunFactory::TEXT_ORIENT_MASK);
+ if (sizeof(T) == sizeof(uint8_t)) {
+ flags |= gfxTextRunFactory::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,
+ aVertical,
+ aTextRun);
+ if (!ok) {
+ return false;
+ }
+ } else if (length > 0) {
+ uint32_t 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 |= gfxTextRunFactory::TEXT_IS_8BIT;
+ }
+ }
+ gfxShapedWord* sw = GetShapedWord(aDrawTarget,
+ aString + wordStart, length,
+ hash, aRunScript, aVertical,
+ appUnitsPerDevUnit,
+ wordFlags, tp);
+ if (sw) {
+ aTextRun->CopyGlyphDataFrom(sw, aRunStart + wordStart);
+ } else {
+ return false; // failed, presumably out of memory?
+ }
+ }
+
+ if (boundary) {
+ // word was terminated by a space: add that to the textrun
+ uint16_t orientation = flags & gfxTextRunFactory::TEXT_ORIENT_MASK;
+ if (orientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_MIXED) {
+ orientation = aVertical ?
+ gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT :
+ gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
+ }
+ if (boundary != ' ' ||
+ !aTextRun->SetSpaceGlyphIfSimple(this, aRunStart + i, ch,
+ orientation)) {
+ // 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!");
+ gfxShapedWord *sw =
+ GetShapedWord(aDrawTarget, &boundary, 1,
+ gfxShapedWord::HashMix(0, boundary),
+ aRunScript, aVertical, appUnitsPerDevUnit,
+ flags | gfxTextRunFactory::TEXT_IS_8BIT, tp);
+ if (sw) {
+ aTextRun->CopyGlyphDataFrom(sw, aRunStart + i);
+ } else {
+ 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 (IsInvalidControlChar(ch) &&
+ !(aTextRun->GetFlags() & gfxTextRunFactory::TEXT_HIDE_CONTROL_CHARACTERS)) {
+ if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) {
+ ShapeFragmentWithoutWordCache(aDrawTarget, aString + i,
+ aRunStart + i, 1,
+ aRunScript, aVertical, 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,
+ bool aVertical);
+template bool
+gfxFont::SplitAndInitTextRun(DrawTarget *aDrawTarget,
+ gfxTextRun *aTextRun,
+ const char16_t *aString,
+ uint32_t aRunStart,
+ uint32_t aRunLength,
+ Script aRunScript,
+ bool aVertical);
+
+template<>
+bool
+gfxFont::InitFakeSmallCapsRun(DrawTarget *aDrawTarget,
+ gfxTextRun *aTextRun,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ uint8_t aMatchType,
+ uint16_t aOrientation,
+ Script aScript,
+ 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;
+ }
+
+ enum RunCaseAction {
+ kNoChange,
+ kUppercaseReduce,
+ kUppercase
+ };
+
+ RunCaseAction runAction = kNoChange;
+ uint32_t runStart = 0;
+ bool vertical =
+ aOrientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
+
+ 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 (NS_IS_HIGH_SURROGATE(ch) && i < aLength - 1 &&
+ NS_IS_LOW_SURROGATE(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 (mStyle.explicitLanguage &&
+ mStyle.language == 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;
+ uint32_t ch2 = mozilla::GreekCasing::UpperCase(ch, state);
+ if (ch != ch2 && !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);
+ if (!f->SplitAndInitTextRun(aDrawTarget, aTextRun,
+ aText + runStart,
+ aOffset + runStart, runLength,
+ aScript, vertical)) {
+ ok = false;
+ }
+ break;
+
+ case kUppercaseReduce:
+ // use reduced-size font, then fall through to uppercase the text
+ f = smallCapsFont;
+ MOZ_FALLTHROUGH;
+
+ case kUppercase:
+ // apply uppercase transform to the string
+ nsDependentSubstring origString(aText + runStart, runLength);
+ nsAutoString convertedString;
+ AutoTArray<bool,50> charsToMergeArray;
+ AutoTArray<bool,50> deletedCharsArray;
+
+ bool mergeNeeded = nsCaseTransformTextRunFactory::
+ TransformString(origString,
+ convertedString,
+ true,
+ mStyle.explicitLanguage
+ ? mStyle.language.get() : nullptr,
+ 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(), 0));
+ tempRun->AddGlyphRun(f, aMatchType, 0, true, aOrientation);
+ if (!f->SplitAndInitTextRun(aDrawTarget, tempRun.get(),
+ convertedString.BeginReading(),
+ 0, convertedString.Length(),
+ aScript, vertical)) {
+ ok = false;
+ } else {
+ RefPtr<gfxTextRun> mergedRun(
+ gfxTextRun::Create(&params, runLength,
+ aTextRun->GetFontGroup(), 0));
+ 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);
+ if (!f->SplitAndInitTextRun(aDrawTarget, aTextRun,
+ convertedString.BeginReading(),
+ aOffset + runStart, runLength,
+ aScript, vertical)) {
+ ok = false;
+ }
+ }
+ break;
+ }
+
+ runStart = i;
+ }
+
+ i += extraCodeUnits;
+ if (i < aLength) {
+ runAction = chAction;
+ }
+ }
+
+ return ok;
+}
+
+template<>
+bool
+gfxFont::InitFakeSmallCapsRun(DrawTarget *aDrawTarget,
+ gfxTextRun *aTextRun,
+ const uint8_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ uint8_t aMatchType,
+ uint16_t aOrientation,
+ Script aScript,
+ bool aSyntheticLower,
+ bool aSyntheticUpper)
+{
+ NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aText),
+ aLength);
+ return InitFakeSmallCapsRun(aDrawTarget, aTextRun, static_cast<const char16_t*>(unicodeString.get()),
+ aOffset, aLength, aMatchType, aOrientation,
+ aScript, aSyntheticLower, aSyntheticUpper);
+}
+
+already_AddRefed<gfxFont>
+gfxFont::GetSmallCapsFont()
+{
+ gfxFontStyle style(*GetStyle());
+ style.size *= SMALL_CAPS_SCALE_FACTOR;
+ style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL;
+ gfxFontEntry* fe = GetFontEntry();
+ bool needsBold = style.weight >= 600 && !fe->IsBold();
+ return fe->FindOrMakeFont(&style, needsBold, mUnicodeRangeMap);
+}
+
+already_AddRefed<gfxFont>
+gfxFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel)
+{
+ gfxFontStyle style(*GetStyle());
+ style.AdjustForSubSuperscript(aAppUnitsPerDevPixel);
+ gfxFontEntry* fe = GetFontEntry();
+ bool needsBold = style.weight >= 600 && !fe->IsBold();
+ return fe->FindOrMakeFont(&style, needsBold, mUnicodeRangeMap);
+}
+
+static void
+DestroyRefCairo(void* aData)
+{
+ cairo_t* refCairo = static_cast<cairo_t*>(aData);
+ MOZ_ASSERT(refCairo);
+ cairo_destroy(refCairo);
+}
+
+/* static */ cairo_t *
+gfxFont::RefCairo(DrawTarget* aDT)
+{
+ // DrawTargets that don't use a Cairo backend can be given a 1x1 "reference"
+ // |cairo_t*|, stored in the DrawTarget's user data, for doing font-related
+ // operations.
+ static UserDataKey sRefCairo;
+
+ cairo_t* refCairo = nullptr;
+ if (aDT->GetBackendType() == BackendType::CAIRO) {
+ refCairo = static_cast<cairo_t*>
+ (aDT->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT));
+ if (refCairo) {
+ return refCairo;
+ }
+ }
+
+ refCairo = static_cast<cairo_t*>(aDT->GetUserData(&sRefCairo));
+ if (!refCairo) {
+ refCairo = cairo_create(gfxPlatform::GetPlatform()->ScreenReferenceSurface()->CairoSurface());
+ aDT->AddUserData(&sRefCairo, refCairo, DestroyRefCairo);
+ }
+
+ return refCairo;
+}
+
+gfxGlyphExtents *
+gfxFont::GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit) {
+ uint32_t i, count = mGlyphExtentsArray.Length();
+ for (i = 0; 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, &svgBounds)) {
+ gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit();
+ aExtents->SetTightGlyphExtents(aGlyphID,
+ gfxRect(svgBounds.x * d2a,
+ svgBounds.y * d2a,
+ svgBounds.width * d2a,
+ svgBounds.height * d2a));
+ return;
+ }
+
+ cairo_glyph_t glyph;
+ glyph.index = aGlyphID;
+ glyph.x = 0;
+ glyph.y = 0;
+ cairo_text_extents_t extents;
+ cairo_glyph_extents(gfxFont::RefCairo(aDrawTarget), &glyph, 1, &extents);
+
+ const Metrics& fontMetrics = GetMetrics(eHorizontal);
+ int32_t appUnitsPerDevUnit = aExtents->GetAppUnitsPerDevUnit();
+ if (!aNeedTight && extents.x_bearing >= 0 &&
+ extents.y_bearing >= -fontMetrics.maxAscent &&
+ extents.height + extents.y_bearing <= fontMetrics.maxDescent) {
+ uint32_t appUnitsWidth =
+ uint32_t(ceil((extents.x_bearing + extents.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;
+ gfxRect bounds(extents.x_bearing*d2a, extents.y_bearing*d2a,
+ extents.width*d2a, extents.height*d2a);
+ aExtents->SetTightGlyphExtents(aGlyphID, bounds);
+}
+
+// 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 kPostTableTag = TRUETYPE_TAG('p','o','s','t');
+ 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 to get vertical extents
+ 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);
+ SET_SIGNED(maxAscent, hhea->ascender);
+ SET_SIGNED(maxDescent, -int16_t(hhea->descender));
+ SET_SIGNED(externalLeading, hhea->lineGap);
+
+ // 'post' table is required for underline metrics
+ gfxFontEntry::AutoTable postTable(mFontEntry, kPostTableTag);
+ if (!postTable) {
+ return true; // no 'post' table -> sfnt is not valid
+ }
+ const PostTable *post =
+ reinterpret_cast<const PostTable*>(hb_blob_get_data(postTable, &len));
+ if (len < offsetof(PostTable, underlineThickness) + sizeof(uint16_t)) {
+ return true; // bad post table -> sfnt is not valid
+ }
+
+ SET_SIGNED(underlineOffset, post->underlinePosition);
+ SET_UNSIGNED(underlineSize, post->underlineThickness);
+
+ // '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));
+ // 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) {
+ SET_SIGNED(xHeight, os2->sxHeight);
+ }
+ if (len >= offsetof(OS2Table, sCapHeight) + sizeof(int16_t) &&
+ int16_t(os2->sCapHeight) > 0) {
+ SET_SIGNED(capHeight, os2->sCapHeight);
+ }
+ }
+ // this should always be present in any valid OS/2 of any version
+ if (len >= offsetof(OS2Table, sTypoLineGap) + sizeof(int16_t)) {
+ SET_SIGNED(aveCharWidth, os2->xAvgCharWidth);
+ SET_SIGNED(strikeoutSize, os2->yStrikeoutSize);
+ SET_SIGNED(strikeoutOffset, os2->yStrikeoutPosition);
+
+ // for fonts with USE_TYPO_METRICS set in the fsSelection field,
+ // let the OS/2 sTypo* metrics override those from the hhea table
+ // (see http://www.microsoft.com/typography/otspec/os2.htm#fss)
+ const uint16_t kUseTypoMetricsMask = 1 << 7;
+ if (uint16_t(os2->fsSelection) & kUseTypoMetricsMask) {
+ SET_SIGNED(maxAscent, os2->sTypoAscender);
+ SET_SIGNED(maxDescent, - int16_t(os2->sTypoDescender));
+ SET_SIGNED(externalLeading, os2->sTypoLineGap);
+ }
+ }
+ }
+
+#undef SET_SIGNED
+#undef SET_UNSIGNED
+
+ 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.size == 0.0 || mStyle.sizeAdjust == 0.0) {
+ memset(aMetrics, 0, sizeof(gfxFont::Metrics));
+ return;
+ }
+
+ 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).
+const gfxFont::Metrics*
+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;
+
+ Metrics* 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) {
+ 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.
+ 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);
+ }
+ }
+ }
+
+ // 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.
+ 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)) {
+ 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.
+ // We synthesize our own positions, as font metrics don't provide these
+ // for vertical layout.
+ metrics->underlineSize = std::max(1.0, metrics->underlineSize);
+ metrics->underlineOffset = - metrics->maxDescent - 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->zeroOrAveCharWidth = metrics->aveCharWidth;
+ metrics->maxHeight = metrics->maxAscent + metrics->maxDescent;
+ metrics->xHeight = metrics->emHeight / 2;
+ metrics->capHeight = metrics->maxAscent;
+
+ return 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(eHorizontal).zeroOrAveCharWidth; // figure space
+ case 0x2008: return GetMetrics(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
+ default: return -1.0;
+ }
+}
+
+void
+gfxFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const
+{
+ 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)
+{
+ if (!mGlyphChangeObservers) {
+ mGlyphChangeObservers.reset(
+ new nsTHashtable<nsPtrHashKey<GlyphChangeObserver>>);
+ }
+ mGlyphChangeObservers->PutEntry(aObserver);
+}
+
+void
+gfxFont::RemoveGlyphChangeObserver(GlyphChangeObserver *aObserver)
+{
+ NS_ASSERTION(mGlyphChangeObservers, "No observers registered");
+ NS_ASSERTION(mGlyphChangeObservers->Contains(aObserver), "Observer not registered");
+ mGlyphChangeObservers->RemoveEntry(aObserver);
+}
+
+
+#define DEFAULT_PIXEL_FONT_SIZE 16.0f
+
+/*static*/ uint32_t
+gfxFontStyle::ParseFontLanguageOverride(const nsString& aLangTag)
+{
+ if (!aLangTag.Length() || aLangTag.Length() > 4) {
+ return NO_FONT_LANGUAGE_OVERRIDE;
+ }
+ uint32_t index, result = 0;
+ for (index = 0; index < aLangTag.Length(); ++index) {
+ char16_t ch = aLangTag[index];
+ if (!nsCRT::IsAscii(ch)) { // valid tags are pure ASCII
+ return NO_FONT_LANGUAGE_OVERRIDE;
+ }
+ result = (result << 8) + ch;
+ }
+ while (index++ < 4) {
+ result = (result << 8) + 0x20;
+ }
+ return result;
+}
+
+gfxFontStyle::gfxFontStyle() :
+ language(nsGkAtoms::x_western),
+ size(DEFAULT_PIXEL_FONT_SIZE), sizeAdjust(-1.0f), baselineOffset(0.0f),
+ languageOverride(NO_FONT_LANGUAGE_OVERRIDE),
+ weight(NS_FONT_WEIGHT_NORMAL), stretch(NS_FONT_STRETCH_NORMAL),
+ systemFont(true), printerFont(false), useGrayscaleAntialiasing(false),
+ style(NS_FONT_STYLE_NORMAL),
+ allowSyntheticWeight(true), allowSyntheticStyle(true),
+ noFallbackVariantFeatures(true),
+ explicitLanguage(false),
+ variantCaps(NS_FONT_VARIANT_CAPS_NORMAL),
+ variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL)
+{
+}
+
+gfxFontStyle::gfxFontStyle(uint8_t aStyle, uint16_t aWeight, int16_t aStretch,
+ gfxFloat aSize,
+ nsIAtom *aLanguage, bool aExplicitLanguage,
+ float aSizeAdjust, bool aSystemFont,
+ bool aPrinterFont,
+ bool aAllowWeightSynthesis,
+ bool aAllowStyleSynthesis,
+ const nsString& aLanguageOverride):
+ language(aLanguage),
+ size(aSize), sizeAdjust(aSizeAdjust), baselineOffset(0.0f),
+ languageOverride(ParseFontLanguageOverride(aLanguageOverride)),
+ weight(aWeight), stretch(aStretch),
+ systemFont(aSystemFont), printerFont(aPrinterFont),
+ useGrayscaleAntialiasing(false),
+ style(aStyle),
+ allowSyntheticWeight(aAllowWeightSynthesis),
+ allowSyntheticStyle(aAllowStyleSynthesis),
+ noFallbackVariantFeatures(true),
+ explicitLanguage(aExplicitLanguage),
+ variantCaps(NS_FONT_VARIANT_CAPS_NORMAL),
+ variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL)
+{
+ MOZ_ASSERT(!mozilla::IsNaN(size));
+ MOZ_ASSERT(!mozilla::IsNaN(sizeAdjust));
+
+ if (weight > 900)
+ weight = 900;
+ if (weight < 100)
+ weight = 100;
+
+ if (size >= FONT_MAX_SIZE) {
+ size = FONT_MAX_SIZE;
+ sizeAdjust = -1.0f;
+ } else if (size < 0.0) {
+ NS_WARNING("negative font size");
+ size = 0.0;
+ }
+
+ if (!language) {
+ NS_WARNING("null language");
+ language = nsGkAtoms::x_western;
+ }
+}
+
+gfxFontStyle::gfxFontStyle(const gfxFontStyle& aStyle) :
+ language(aStyle.language),
+ featureValueLookup(aStyle.featureValueLookup),
+ size(aStyle.size), sizeAdjust(aStyle.sizeAdjust),
+ baselineOffset(aStyle.baselineOffset),
+ languageOverride(aStyle.languageOverride),
+ weight(aStyle.weight), stretch(aStyle.stretch),
+ systemFont(aStyle.systemFont), printerFont(aStyle.printerFont),
+ useGrayscaleAntialiasing(aStyle.useGrayscaleAntialiasing),
+ style(aStyle.style),
+ allowSyntheticWeight(aStyle.allowSyntheticWeight),
+ allowSyntheticStyle(aStyle.allowSyntheticStyle),
+ noFallbackVariantFeatures(aStyle.noFallbackVariantFeatures),
+ explicitLanguage(aStyle.explicitLanguage),
+ variantCaps(aStyle.variantCaps),
+ variantSubSuper(aStyle.variantSubSuper)
+{
+ featureSettings.AppendElements(aStyle.featureSettings);
+ alternateValues.AppendElements(aStyle.alternateValues);
+}
+
+int8_t
+gfxFontStyle::ComputeWeight() const
+{
+ int8_t baseWeight = (weight + 50) / 100;
+
+ if (baseWeight < 0)
+ baseWeight = 0;
+ if (baseWeight > 9)
+ baseWeight = 9;
+
+ return baseWeight;
+}
+
+void
+gfxFontStyle::AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel)
+{
+ NS_PRECONDITION(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) {
+ mMathInitialized = true;
+
+ hb_face_t *face = GetFontEntry()->GetHBFace();
+ if (face) {
+ if (hb_ot_math_has_data(face)) {
+ mMathTable = MakeUnique<gfxMathTable>(face, GetAdjustedSize());
+ }
+ hb_face_destroy(face);
+ }
+ }
+
+ return !!mMathTable;
+}
diff --git a/gfx/thebes/gfxFont.h b/gfx/thebes/gfxFont.h
new file mode 100644
index 000000000..ead0b7666
--- /dev/null
+++ b/gfx/thebes/gfxFont.h
@@ -0,0 +1,2223 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 et sw=4 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 "gfxTypes.h"
+#include "gfxFontEntry.h"
+#include "nsString.h"
+#include "gfxPoint.h"
+#include "gfxPattern.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "gfxRect.h"
+#include "nsExpirationTracker.h"
+#include "gfxPlatform.h"
+#include "nsIAtom.h"
+#include "mozilla/HashFunctions.h"
+#include "nsIMemoryReporter.h"
+#include "nsIObserver.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Attributes.h"
+#include <algorithm>
+#include "DrawMode.h"
+#include "nsDataHashtable.h"
+#include "harfbuzz/hb.h"
+#include "mozilla/gfx/2D.h"
+#include "nsColor.h"
+
+typedef struct _cairo cairo_t;
+typedef struct _cairo_scaled_font cairo_scaled_font_t;
+//typedef struct gr_face gr_face;
+
+#ifdef DEBUG
+#include <stdio.h>
+#endif
+
+class gfxContext;
+class gfxTextRun;
+class gfxFont;
+class gfxGlyphExtents;
+class gfxShapedText;
+class gfxShapedWord;
+class gfxSkipChars;
+class gfxMathTable;
+
+#define FONT_MAX_SIZE 2000.0
+
+#define NO_FONT_LANGUAGE_OVERRIDE 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.3
+#elif defined(MOZ_WIDGET_GTK)
+#define OBLIQUE_SKEW_FACTOR 0.2
+#else
+#define OBLIQUE_SKEW_FACTOR 0.25
+#endif
+
+struct gfxTextRunDrawCallbacks;
+
+namespace mozilla {
+class SVGContextPaint;
+namespace gfx {
+class GlyphRenderingOptions;
+} // namespace gfx
+} // namespace mozilla
+
+struct gfxFontStyle {
+ gfxFontStyle();
+ gfxFontStyle(uint8_t aStyle, uint16_t aWeight, int16_t aStretch,
+ gfxFloat aSize, nsIAtom *aLanguage, bool aExplicitLanguage,
+ float aSizeAdjust, bool aSystemFont,
+ bool aPrinterFont,
+ bool aWeightSynthesis, bool aStyleSynthesis,
+ const nsString& aLanguageOverride);
+ gfxFontStyle(const gfxFontStyle& aStyle);
+
+ // the language (may be an internal langGroup code rather than an actual
+ // language code) specified in the document or element's lang property,
+ // or inferred from the charset
+ RefPtr<nsIAtom> language;
+
+ // Features are composed of (1) features from style rules (2) features
+ // from feature setttings rules and (3) family-specific features. (1) and
+ // (3) are guaranteed to be mutually exclusive
+
+ // custom opentype feature settings
+ nsTArray<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
+ nsTArray<gfxAlternateValue> alternateValues;
+
+ // -- object used to look these up once the font is matched
+ RefPtr<gfxFontFeatureValueSet> featureValueLookup;
+
+ // The logical size of the font, in pixels
+ gfxFloat size;
+
+ // 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 weight of the font: 100, 200, ... 900.
+ uint16_t weight;
+
+ // The stretch of the font (the sum of various NS_FONT_STRETCH_*
+ // constants; see gfxFontConstants.h).
+ int8_t stretch;
+
+ // 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;
+
+ // The style of font (normal, italic, oblique)
+ uint8_t style : 2;
+
+ // Whether synthetic styles are allowed
+ bool allowSyntheticWeight : 1;
+ bool allowSyntheticStyle : 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;
+
+ // whether the |language| field comes from explicit lang tagging in the
+ // document, or was inferred from charset/system locale
+ bool explicitLanguage : 1;
+
+ // caps variant (small-caps, petite-caps, etc.)
+ uint8_t variantCaps;
+
+ // sub/superscript variant
+ uint8_t variantSubSuper;
+
+ // Return the final adjusted font size for the given aspect ratio.
+ // Not meant to be called when sizeAdjust = -1.0.
+ gfxFloat GetAdjustedSize(gfxFloat aspect) const {
+ NS_ASSERTION(sizeAdjust >= 0.0, "Not meant to be called when sizeAdjust = -1.0");
+ gfxFloat adjustedSize = std::max(NS_round(size*(sizeAdjust/aspect)), 1.0);
+ return std::min(adjustedSize, FONT_MAX_SIZE);
+ }
+
+ PLDHashNumber Hash() const {
+ return ((style + (systemFont << 7) +
+ (weight << 8)) + uint32_t(size*1000) + uint32_t(sizeAdjust*1000)) ^
+ nsISupportsHashKey::HashKey(language);
+ }
+
+ int8_t ComputeWeight() const;
+
+ // Adjust this style to simulate sub/superscript (as requested in the
+ // variantSubSuper field) using size and baselineOffset instead.
+ void AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel);
+
+ bool Equals(const gfxFontStyle& other) const {
+ return
+ (*reinterpret_cast<const uint64_t*>(&size) ==
+ *reinterpret_cast<const uint64_t*>(&other.size)) &&
+ (style == other.style) &&
+ (variantCaps == other.variantCaps) &&
+ (variantSubSuper == other.variantSubSuper) &&
+ (allowSyntheticWeight == other.allowSyntheticWeight) &&
+ (allowSyntheticStyle == other.allowSyntheticStyle) &&
+ (systemFont == other.systemFont) &&
+ (printerFont == other.printerFont) &&
+ (useGrayscaleAntialiasing == other.useGrayscaleAntialiasing) &&
+ (explicitLanguage == other.explicitLanguage) &&
+ (weight == other.weight) &&
+ (stretch == other.stretch) &&
+ (language == other.language) &&
+ (baselineOffset == other.baselineOffset) &&
+ (*reinterpret_cast<const uint32_t*>(&sizeAdjust) ==
+ *reinterpret_cast<const uint32_t*>(&other.sizeAdjust)) &&
+ (featureSettings == other.featureSettings) &&
+ (languageOverride == other.languageOverride) &&
+ (alternateValues == other.alternateValues) &&
+ (featureValueLookup == other.featureValueLookup);
+ }
+
+ static void ParseFontFeatureSettings(const nsString& aFeatureString,
+ nsTArray<gfxFontFeature>& aFeatures);
+
+ static uint32_t ParseFontLanguageOverride(const nsString& aLangTag);
+};
+
+struct gfxTextRange {
+ enum {
+ // flags for recording the kind of font-matching that was used
+ kFontGroup = 0x0001,
+ kPrefsFallback = 0x0002,
+ kSystemFallback = 0x0004
+ };
+ gfxTextRange(uint32_t aStart, uint32_t aEnd,
+ gfxFont* aFont, uint8_t aMatchType,
+ uint16_t 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;
+ uint8_t matchType;
+ uint16_t orientation;
+};
+
+
+/**
+ * Font cache design:
+ *
+ * The mFonts hashtable contains most fonts, indexed by (gfxFontEntry*, style).
+ * It does not add a reference to the fonts it contains.
+ * When a font's refcount decreases to zero, instead of deleting it we
+ * add it to our expiration tracker.
+ * The expiration tracker tracks fonts with zero refcount. After a certain
+ * period of time, such fonts expire and are deleted.
+ *
+ * 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 nsExpirationTracker<gfxFont,3> {
+public:
+ enum {
+ FONT_TIMEOUT_SECONDS = 10,
+ SHAPED_WORD_TIMEOUT_SECONDS = 60
+ };
+
+ gfxFontCache();
+ ~gfxFontCache();
+
+ /*
+ * 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 an addrefed pointer, or 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; we'll forget about the old one.
+ void AddNew(gfxFont *aFont);
+
+ // The font's refcount has gone to zero; give ownership of it to
+ // the cache. We delete it if it's not acquired again after a certain
+ // amount of time.
+ void NotifyReleased(gfxFont *aFont);
+
+ // This gets called when the timeout has expired on a zero-refcount
+ // font; we just delete it.
+ virtual void NotifyExpired(gfxFont *aFont) override;
+
+ // 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() {
+ mFonts.Clear();
+ AgeAllGenerations();
+ }
+
+ void FlushShapedWordCaches();
+
+ void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const;
+ void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const;
+
+protected:
+ class MemoryReporter final : public nsIMemoryReporter
+ {
+ ~MemoryReporter() {}
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+ };
+
+ // Observer for notifications that the font cache cares about
+ class Observer final
+ : public nsIObserver
+ {
+ ~Observer() {}
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ };
+
+ void DestroyFont(gfxFont *aFont);
+
+ static gfxFontCache *gGlobalCache;
+
+ struct 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) : mFont(nullptr) { }
+ HashEntry(const HashEntry& toCopy) : mFont(toCopy.mFont) { }
+ ~HashEntry() { }
+
+ 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* mFont;
+ };
+
+ nsTHashtable<HashEntry> mFonts;
+
+ static void WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache);
+ nsCOMPtr<nsITimer> mWordCacheExpirationTimer;
+};
+
+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));
+ }
+};
+
+class gfxTextRunFactory {
+ NS_INLINE_DECL_REFCOUNTING(gfxTextRunFactory)
+
+public:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ // Flags in the mask 0xFFFF0000 are reserved for textrun clients
+ // Flags in the mask 0x0000F000 are reserved for per-platform fonts
+ // Flags in the mask 0x00000FFF are set by the textrun creator.
+ enum {
+ CACHE_TEXT_FLAGS = 0xF0000000,
+ USER_TEXT_FLAGS = 0x0FFF0000,
+ TEXTRUN_TEXT_FLAGS = 0x0000FFFF,
+ SETTABLE_FLAGS = CACHE_TEXT_FLAGS | USER_TEXT_FLAGS,
+
+ /**
+ * When set, the text string pointer used to create the text run
+ * is guaranteed to be available during the lifetime of the text run.
+ */
+ TEXT_IS_PERSISTENT = 0x0001,
+ /**
+ * When set, the text is known to be all-ASCII (< 128).
+ */
+ TEXT_IS_ASCII = 0x0002,
+ /**
+ * When set, the text is RTL.
+ */
+ TEXT_IS_RTL = 0x0004,
+ /**
+ * When set, spacing is enabled and the textrun needs to call GetSpacing
+ * on the spacing provider.
+ */
+ TEXT_ENABLE_SPACING = 0x0008,
+ /**
+ * When set, GetHyphenationBreaks may return true for some character
+ * positions, otherwise it will always return false for all characters.
+ */
+ TEXT_ENABLE_HYPHEN_BREAKS = 0x0010,
+ /**
+ * When set, the text has no characters above 255 and it is stored
+ * in the textrun in 8-bit format.
+ */
+ TEXT_IS_8BIT = 0x0020,
+ /**
+ * 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 = 0x0040,
+ /**
+ * When set, optional ligatures are disabled. Ligatures that are
+ * required for legible text should still be enabled.
+ */
+ TEXT_DISABLE_OPTIONAL_LIGATURES = 0x0080,
+ /**
+ * 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 = 0x0100,
+ /**
+ * For internal use by the memory reporter when accounting for
+ * storage used by textruns.
+ * Because the reporter may visit each textrun multiple times while
+ * walking the frame trees and textrun cache, it needs to mark
+ * textruns that have been seen so as to avoid multiple-accounting.
+ */
+ TEXT_RUN_SIZE_ACCOUNTED = 0x0200,
+ /**
+ * When set, the textrun should discard control characters instead of
+ * turning them into hexboxes.
+ */
+ TEXT_HIDE_CONTROL_CHARACTERS = 0x0400,
+
+ /**
+ * 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 = 0xF000,
+ TEXT_ORIENT_HORIZONTAL = 0x0000,
+ TEXT_ORIENT_VERTICAL_UPRIGHT = 0x1000,
+ TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT = 0x2000,
+ TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT = 0x4000,
+ TEXT_ORIENT_VERTICAL_MIXED = 0x8000,
+
+ /**
+ * 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 = 0x20000000,
+ /**
+ * 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 = 0x40000000,
+
+ // Set if the textrun should use the OpenType 'math' script.
+ TEXT_USE_MATH_SCRIPT = 0x80000000,
+ };
+
+ /**
+ * This record contains all the parameters needed to initialize a textrun.
+ */
+ struct 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::unicode::Script Script;
+
+ explicit gfxFontShaper(gfxFont *aFont)
+ : mFont(aFont)
+ {
+ NS_ASSERTION(aFont, "shaper requires a valid font!");
+ }
+
+ virtual ~gfxFontShaper() { }
+
+ // 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,
+ bool aVertical,
+ gfxShapedText *aShapedText) = 0;
+
+ gfxFont *GetFont() const { return mFont; }
+
+ static void
+ MergeFontFeatures(const gfxFontStyle *aStyle,
+ const nsTArray<gfxFontFeature>& aFontFeatures,
+ bool aDisableLigatures,
+ const nsAString& aFamilyName,
+ bool aAddSmallCaps,
+ void (*aHandleFeature)(const uint32_t&,
+ uint32_t&, void*),
+ void* aHandleFeatureData);
+
+protected:
+ // Work out whether cairo will snap inter-glyph spacing to pixels.
+ static void GetRoundOffsetsToPixels(DrawTarget* aDrawTarget,
+ bool* aRoundX, bool* aRoundY);
+
+ // 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;
+};
+
+
+/*
+ * 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::unicode::Script Script;
+
+ gfxShapedText(uint32_t aLength, uint32_t aFlags,
+ int32_t aAppUnitsPerDevUnit)
+ : mLength(aLength)
+ , mFlags(aFlags)
+ , mAppUnitsPerDevUnit(aAppUnitsPerDevUnit)
+ { }
+
+ virtual ~gfxShapedText() { }
+
+ /**
+ * 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:
+ CompressedGlyph() { mValue = 0; }
+
+ 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,
+
+ // 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,
+
+ // The advance is stored in appunits
+ ADVANCE_MASK = 0x0FFF0000U,
+ ADVANCE_SHIFT = 16,
+
+ GLYPH_MASK = 0x0000FFFFU,
+
+ // Non-simple glyphs may or may not have glyph data in the
+ // corresponding mDetailedGlyphs entry. They have the following
+ // flag bits:
+
+ // 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 = 0x01,
+ FLAG_NOT_CLUSTER_START = 0x02,
+ FLAG_NOT_LIGATURE_GROUP_START = 0x04,
+
+ FLAG_CHAR_IS_TAB = 0x08,
+ FLAG_CHAR_IS_NEWLINE = 0x10,
+ // 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 = 0x20,
+ CHAR_TYPE_FLAGS_MASK = 0x38,
+
+ GLYPH_COUNT_MASK = 0x00FFFF00U,
+ GLYPH_COUNT_SHIFT = 8
+ };
+
+ // "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) != 0; }
+ uint32_t GetSimpleAdvance() const { return (mValue & ADVANCE_MASK) >> ADVANCE_SHIFT; }
+ uint32_t GetSimpleGlyph() const { return mValue & GLYPH_MASK; }
+
+ bool IsMissing() const { return (mValue & (FLAG_NOT_MISSING|FLAG_IS_SIMPLE_GLYPH)) == 0; }
+ bool IsClusterStart() const {
+ return (mValue & FLAG_IS_SIMPLE_GLYPH) || !(mValue & FLAG_NOT_CLUSTER_START);
+ }
+ bool IsLigatureGroupStart() const {
+ return (mValue & FLAG_IS_SIMPLE_GLYPH) || !(mValue & FLAG_NOT_LIGATURE_GROUP_START);
+ }
+ bool IsLigatureContinuation() const {
+ return (mValue & FLAG_IS_SIMPLE_GLYPH) == 0 &&
+ (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) != 0;
+ }
+
+ bool CharIsTab() const {
+ return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_TAB) != 0;
+ }
+ bool CharIsNewline() const {
+ return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_NEWLINE) != 0;
+ }
+ bool CharMayHaveEmphasisMark() const {
+ return !CharIsSpace() &&
+ (IsSimpleGlyph() || !(mValue & FLAG_CHAR_NO_EMPHASIS_MARK));
+ }
+
+ uint32_t CharTypeFlags() const {
+ return IsSimpleGlyph() ? 0 : (mValue & CHAR_TYPE_FLAGS_MASK);
+ }
+
+ void SetClusterStart(bool aIsClusterStart) {
+ NS_ASSERTION(!IsSimpleGlyph(),
+ "can't call SetClusterStart on simple glyphs");
+ 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) {
+ NS_ASSERTION(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;
+ }
+
+ CompressedGlyph& SetSimpleGlyph(uint32_t aAdvanceAppUnits, uint32_t aGlyph) {
+ NS_ASSERTION(IsSimpleAdvance(aAdvanceAppUnits), "Advance overflow");
+ NS_ASSERTION(IsSimpleGlyphID(aGlyph), "Glyph overflow");
+ NS_ASSERTION(!CharTypeFlags(), "Char type flags lost");
+ mValue = (mValue & (FLAGS_CAN_BREAK_BEFORE | FLAG_CHAR_IS_SPACE)) |
+ FLAG_IS_SIMPLE_GLYPH |
+ (aAdvanceAppUnits << ADVANCE_SHIFT) | aGlyph;
+ return *this;
+ }
+ CompressedGlyph& SetComplex(bool aClusterStart, bool aLigatureStart,
+ uint32_t aGlyphCount) {
+ mValue = (mValue & (FLAGS_CAN_BREAK_BEFORE | FLAG_CHAR_IS_SPACE)) |
+ FLAG_NOT_MISSING |
+ CharTypeFlags() |
+ (aClusterStart ? 0 : FLAG_NOT_CLUSTER_START) |
+ (aLigatureStart ? 0 : FLAG_NOT_LIGATURE_GROUP_START) |
+ (aGlyphCount << GLYPH_COUNT_SHIFT);
+ return *this;
+ }
+ /**
+ * Missing glyphs are treated as ligature group starts; don't mess with
+ * the cluster-start flag (see bugs 618870 and 619286).
+ */
+ CompressedGlyph& SetMissing(uint32_t aGlyphCount) {
+ mValue = (mValue & (FLAGS_CAN_BREAK_BEFORE | FLAG_NOT_CLUSTER_START |
+ FLAG_CHAR_IS_SPACE)) |
+ CharTypeFlags() |
+ (aGlyphCount << GLYPH_COUNT_SHIFT);
+ return *this;
+ }
+ uint32_t GetGlyphCount() const {
+ NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
+ return (mValue & GLYPH_COUNT_MASK) >> GLYPH_COUNT_SHIFT;
+ }
+
+ void SetIsSpace() {
+ mValue |= FLAG_CHAR_IS_SPACE;
+ }
+ void SetIsTab() {
+ NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
+ mValue |= FLAG_CHAR_IS_TAB;
+ }
+ void SetIsNewline() {
+ NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
+ mValue |= FLAG_CHAR_IS_NEWLINE;
+ }
+ void SetNoEmphasisMark() {
+ NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
+ mValue |= FLAG_CHAR_NO_EMPHASIS_MARK;
+ }
+
+ 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, x-offset and y-offset of the glyph, in appunits
+ * mAdvance is in the text direction (RTL or LTR)
+ * mXOffset is always from left to right
+ * mYOffset is always from top to bottom */
+ int32_t mAdvance;
+ float mXOffset, mYOffset;
+ };
+
+ void SetGlyphs(uint32_t aCharIndex, CompressedGlyph aGlyph,
+ 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);
+
+ uint32_t GetFlags() const {
+ return mFlags;
+ }
+
+ bool IsVertical() const {
+ return (GetFlags() & gfxTextRunFactory::TEXT_ORIENT_MASK) !=
+ gfxTextRunFactory::TEXT_ORIENT_HORIZONTAL;
+ }
+
+ bool UseCenterBaseline() const {
+ uint32_t orient = GetFlags() & gfxTextRunFactory::TEXT_ORIENT_MASK;
+ return orient == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_MIXED ||
+ orient == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
+ }
+
+ bool IsRightToLeft() const {
+ return (GetFlags() & gfxTextRunFactory::TEXT_IS_RTL) != 0;
+ }
+
+ bool IsSidewaysLeft() const {
+ return (GetFlags() & gfxTextRunFactory::TEXT_ORIENT_MASK) ==
+ gfxTextRunFactory::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() &
+ gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES) != 0;
+ }
+
+ bool TextIs8Bit() const {
+ return (GetFlags() & gfxTextRunFactory::TEXT_IS_8BIT) != 0;
+ }
+
+ 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(),
+ 0, 0
+ };
+ SetGlyphs(aIndex, CompressedGlyph().SetComplex(true, true, 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()
+ : mLastUsed(0)
+ { }
+
+ // 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;
+ };
+
+ mozilla::UniquePtr<DetailedGlyphStore> mDetailedGlyphs;
+
+ // Number of char16_t characters and CompressedGlyph glyph records
+ uint32_t mLength;
+
+ // Shaping flags (direction, ligature-suppression)
+ uint32_t mFlags;
+
+ int32_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::unicode::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,
+ int32_t aAppUnitsPerDevUnit,
+ uint32_t aFlags) {
+ 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,
+ aAppUnitsPerDevUnit, aFlags);
+ }
+
+ static gfxShapedWord* Create(const char16_t *aText, uint32_t aLength,
+ Script aRunScript,
+ int32_t aAppUnitsPerDevUnit,
+ uint32_t aFlags) {
+ 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 & gfxTextRunFactory::TEXT_IS_8BIT) {
+ nsAutoCString narrowText;
+ LossyAppendUTF16toASCII(nsDependentSubstring(aText, aLength),
+ narrowText);
+ return Create((const uint8_t*)(narrowText.BeginReading()),
+ aLength, aRunScript, aAppUnitsPerDevUnit, aFlags);
+ }
+
+ 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,
+ aAppUnitsPerDevUnit, aFlags);
+ }
+
+ // Override operator delete to properly free the object that was
+ // allocated via malloc.
+ void operator delete(void* p) {
+ free(p);
+ }
+
+ virtual const CompressedGlyph *GetCharacterGlyphs() const override {
+ return &mCharGlyphsStorage[0];
+ }
+ virtual 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;
+ }
+
+ 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,
+ int32_t aAppUnitsPerDevUnit, uint32_t aFlags)
+ : gfxShapedText(aLength, aFlags | gfxTextRunFactory::TEXT_IS_8BIT,
+ aAppUnitsPerDevUnit)
+ , mScript(aRunScript)
+ , 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,
+ int32_t aAppUnitsPerDevUnit, uint32_t aFlags)
+ : gfxShapedText(aLength, aFlags, aAppUnitsPerDevUnit)
+ , mScript(aRunScript)
+ , 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);
+ }
+
+ Script mScript;
+
+ 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:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::unicode::Script Script;
+ typedef mozilla::SVGContextPaint SVGContextPaint;
+
+public:
+ nsrefcnt AddRef(void) {
+ NS_PRECONDITION(int32_t(mRefCnt) >= 0, "illegal refcnt");
+ if (mExpirationState.IsTracked()) {
+ gfxFontCache::GetCache()->RemoveObject(this);
+ }
+ ++mRefCnt;
+ NS_LOG_ADDREF(this, mRefCnt, "gfxFont", sizeof(*this));
+ return mRefCnt;
+ }
+ nsrefcnt Release(void) {
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "gfxFont");
+ if (mRefCnt == 0) {
+ NotifyReleased();
+ // |this| may have been deleted.
+ return 0;
+ }
+ return mRefCnt;
+ }
+
+ int32_t GetRefCount() { return mRefCnt; }
+
+ // options to specify the kind of AA to be used when creating a font
+ typedef enum {
+ kAntialiasDefault,
+ kAntialiasNone,
+ kAntialiasGrayscale,
+ kAntialiasSubpixel
+ } AntialiasOption;
+
+protected:
+ nsAutoRefCnt mRefCnt;
+ cairo_scaled_font_t *mScaledFont;
+
+ void NotifyReleased() {
+ gfxFontCache *cache = gfxFontCache::GetCache();
+ if (cache) {
+ // Don't delete just yet; return the object to the cache for
+ // possibly recycling within some time limit
+ cache->NotifyReleased(this);
+ } else {
+ // The cache may have already been shut down.
+ delete this;
+ }
+ }
+
+ gfxFont(gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
+ AntialiasOption anAAOption = kAntialiasDefault,
+ cairo_scaled_font_t *aScaledFont = nullptr);
+
+public:
+ virtual ~gfxFont();
+
+ 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 nsString& GetName() const { return mFontEntry->Name(); }
+ const gfxFontStyle *GetStyle() const { return &mStyle; }
+
+ virtual cairo_scaled_font_t* GetCairoScaledFont() { return mScaledFont; }
+
+ virtual gfxFont* CopyWithAntialiasOption(AntialiasOption anAAOption) {
+ // platforms where this actually matters should override
+ return nullptr;
+ }
+
+ gfxFloat GetAdjustedSize() const {
+ return mAdjustedSize > 0.0
+ ? mAdjustedSize
+ : (mStyle.sizeAdjust == 0.0 ? 0.0 : mStyle.size);
+ }
+
+ 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() {
+ return mFontEntry->HasCmapTable();
+ }
+
+ // check whether this is an sfnt we can potentially use with Graphite
+ bool FontCanSupportGraphite() {
+ 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() {
+ 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);
+
+ // 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 horizontal advance of a glyph.
+ gfxFloat GetGlyphHAdvance(DrawTarget* aDrawTarget, uint16_t aGID);
+
+ // Return Azure GlyphRenderingOptions for drawing this font.
+ virtual already_AddRefed<mozilla::gfx::GlyphRenderingOptions>
+ GetGlyphRenderingOptions(const TextRunDrawParams* aRunParams = nullptr)
+ { return nullptr; }
+
+ gfxFloat SynthesizeSpaceWidth(uint32_t aCh);
+
+ // 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 zeroOrAveCharWidth; // width of '0', or if there is
+ // no '0' glyph in this font,
+ // equal to .aveCharWidth
+ };
+
+ enum Orientation {
+ eHorizontal,
+ eVertical
+ };
+
+ const Metrics& GetMetrics(Orientation aOrientation)
+ {
+ if (aOrientation == eHorizontal) {
+ return GetHorizontalMetrics();
+ }
+ if (!mVerticalMetrics) {
+ mVerticalMetrics.reset(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,
+ gfxPoint *aPt, const TextRunDrawParams& aRunParams,
+ uint16_t 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, gfxPoint* 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, uint16_t 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
+ virtual uint32_t GetSpaceGlyph() = 0;
+
+ gfxGlyphExtents *GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit);
+
+ // You need to call SetupCairoFont on aDrawTarget just before calling this.
+ virtual void SetupGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphID,
+ bool aNeedTight, gfxGlyphExtents *aExtents);
+
+ // This is called by the default Draw() implementation above.
+ virtual bool SetupCairoFont(DrawTarget* aDrawTarget) = 0;
+
+ virtual bool AllowSubpixelAA() { return true; }
+
+ bool IsSyntheticBold() { return mApplySyntheticBold; }
+
+ // 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() {
+ 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) {
+ 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) {
+ if (!mIsValid) {
+ return 0;
+ }
+ return mFontEntry->GetUVSGlyph(aCh, aVS);
+ }
+
+ template<typename T>
+ bool InitFakeSmallCapsRun(DrawTarget *aDrawTarget,
+ gfxTextRun *aTextRun,
+ const T *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ uint8_t aMatchType,
+ uint16_t aOrientation,
+ Script aScript,
+ 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,
+ bool aVertical);
+
+ // Get a ShapedWord representing the given text (either 8- or 16-bit)
+ // for use in setting up a gfxTextRun.
+ template<typename T>
+ gfxShapedWord* GetShapedWord(DrawTarget *aDrawTarget,
+ const T *aText,
+ uint32_t aLength,
+ uint32_t aHash,
+ Script aRunScript,
+ bool aVertical,
+ int32_t aAppUnitsPerDevUnit,
+ uint32_t aFlags,
+ gfxTextPerfMetrics *aTextPerf);
+
+ // Ensure the ShapedWord cache is initialized. This MUST be called before
+ // any attempt to use GetShapedWord().
+ void InitWordCache() {
+ if (!mWordCache) {
+ mWordCache = mozilla::MakeUnique<nsTHashtable<CacheHashEntry>>();
+ }
+ }
+
+ // 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
+ void AgeCachedWords();
+
+ // Discard all cached word records; called on memory-pressure notification.
+ void ClearCachedWords() {
+ if (mWordCache) {
+ mWordCache->Clear();
+ }
+ }
+
+ // Glyph rendering/geometry has changed, so invalidate data as necessary.
+ void NotifyGlyphsChanged();
+
+ 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;
+
+ virtual already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont(DrawTarget* aTarget)
+ { return gfxPlatform::GetPlatform()->GetScaledFontForFont(aTarget, this); }
+
+ bool KerningDisabled() {
+ 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()
+ {
+ // 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() {
+ MOZ_RELEASE_ASSERT(mMathTable, "A successful call to TryGetMathTable() must be performed before calling this function");
+ return mMathTable.get();
+ }
+
+ // return a cloned font resized and offset to simulate sub/superscript glyphs
+ virtual already_AddRefed<gfxFont>
+ GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel);
+
+ /**
+ * Return the reference cairo_t object from aDT.
+ */
+ static cairo_t* RefCairo(mozilla::gfx::DrawTarget* aDT);
+
+protected:
+ virtual const Metrics& GetHorizontalMetrics() = 0;
+
+ const Metrics* CreateVerticalMetrics();
+
+ // Output a single glyph at *aPt, which is updated by the glyph's advance.
+ // 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).
+ void DrawOneGlyph(uint32_t aGlyphID,
+ double aAdvance,
+ gfxPoint *aPt,
+ GlyphBufferAzure& aBuffer,
+ bool *aEmittedGlyphs) const;
+
+ // 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.
+ bool DrawGlyphs(const gfxShapedText *aShapedText,
+ uint32_t aOffset, // offset in the textrun
+ uint32_t aCount, // length of run to draw
+ gfxPoint *aPt,
+ const TextRunDrawParams& aRunParams,
+ const FontDrawParams& aFontParams);
+
+ // 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).
+ // Default implementation relies on gfxFontEntry::CreateFontInstance;
+ // backends that don't implement that will need to override this and use
+ // an alternative technique. (gfxFontconfigFonts, I'm looking at you...)
+ virtual already_AddRefed<gfxFont> GetSmallCapsFont();
+
+ // 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(DrawTarget& aDrawTarget, uint16_t aGID) {
+ return -1;
+ }
+
+ 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);
+
+ // do spaces participate in shaping rules? if so, can't used word cache
+ bool SpaceMayParticipateInShaping(Script aRunScript);
+
+ // 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,
+ bool aVertical,
+ 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,
+ bool aVertical,
+ 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,
+ bool aVertical,
+ 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,
+ bool aVertical,
+ gfxTextRun *aTextRun);
+
+ void CheckForFeaturesInvolvingSpace();
+
+ // 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 nsDataHashtable<nsUint32HashKey,Script> *sScriptTagToCode;
+ static nsTHashtable<nsUint32HashKey> *sDefaultFeatures;
+
+ RefPtr<gfxFontEntry> mFontEntry;
+
+ struct CacheHashKey {
+ union {
+ const uint8_t *mSingle;
+ const char16_t *mDouble;
+ } mText;
+ uint32_t mLength;
+ uint32_t mFlags;
+ Script mScript;
+ int32_t mAppUnitsPerDevUnit;
+ PLDHashNumber mHashKey;
+ bool mTextIs8Bit;
+
+ CacheHashKey(const uint8_t *aText, uint32_t aLength,
+ uint32_t aStringHash,
+ Script aScriptCode, int32_t aAppUnitsPerDevUnit,
+ uint32_t aFlags)
+ : mLength(aLength),
+ mFlags(aFlags),
+ mScript(aScriptCode),
+ mAppUnitsPerDevUnit(aAppUnitsPerDevUnit),
+ mHashKey(aStringHash + static_cast<int32_t>(aScriptCode) +
+ aAppUnitsPerDevUnit * 0x100 + aFlags * 0x10000),
+ mTextIs8Bit(true)
+ {
+ NS_ASSERTION(aFlags & gfxTextRunFactory::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, int32_t aAppUnitsPerDevUnit,
+ uint32_t aFlags)
+ : mLength(aLength),
+ mFlags(aFlags),
+ mScript(aScriptCode),
+ mAppUnitsPerDevUnit(aAppUnitsPerDevUnit),
+ mHashKey(aStringHash + static_cast<int32_t>(aScriptCode) +
+ aAppUnitsPerDevUnit * 0x100 + aFlags * 0x10000),
+ mTextIs8Bit(false)
+ {
+ // 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& toCopy) { NS_ERROR("Should not be called"); }
+ ~CacheHashEntry() { }
+
+ 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;
+
+ static const uint32_t kShapedWordCacheMaxAge = 3;
+
+ 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?
+
+ bool mMathInitialized; // TryGetMathTable() called?
+
+ nsExpirationState mExpirationState;
+ gfxFontStyle mStyle;
+ nsTArray<mozilla::UniquePtr<gfxGlyphExtents>> mGlyphExtentsArray;
+ mozilla::UniquePtr<nsTHashtable<nsPtrHashKey<GlyphChangeObserver>>>
+ mGlyphChangeObservers;
+
+ 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;
+
+ // the AA setting requested for this font - may affect glyph bounds
+ AntialiasOption mAntialiasOption;
+
+ // a copy of the font without antialiasing, if needed for separate
+ // measurement by mathml code
+ mozilla::UniquePtr<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::UniquePtr<gfxFontShaper> mHarfBuzzShaper;
+ mozilla::UniquePtr<gfxFontShaper> mGraphiteShaper;
+
+ // if a userfont with unicode-range specified, contains map of *possible*
+ // ranges supported by font
+ RefPtr<gfxCharacterMap> mUnicodeRangeMap;
+
+ RefPtr<mozilla::gfx::ScaledFont> mAzureScaledFont;
+
+ // For vertical metrics, created on demand.
+ mozilla::UniquePtr<const Metrics> mVerticalMetrics;
+
+ // Table used for MathML layout.
+ mozilla::UniquePtr<gfxMathTable> mMathTable;
+
+ // 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, gfxPoint aPoint,
+ uint32_t aGlyphId, SVGContextPaint* aContextPaint) const;
+ bool RenderSVGGlyph(gfxContext *aContext, gfxPoint aPoint,
+ uint32_t aGlyphId, SVGContextPaint* aContextPaint,
+ gfxTextRunDrawCallbacks *aCallbacks,
+ bool& aEmittedGlyphs) const;
+
+ bool RenderColorGlyph(DrawTarget* aDrawTarget,
+ gfxContext* aContext,
+ mozilla::gfx::ScaledFont* scaledFont,
+ mozilla::gfx::GlyphRenderingOptions* renderingOptions,
+ mozilla::gfx::DrawOptions drawOptions,
+ const mozilla::gfx::Point& aPoint,
+ uint32_t aGlyphId) const;
+
+ // 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 double 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 TextRunDrawParams {
+ RefPtr<mozilla::gfx::DrawTarget> dt;
+ gfxContext *context;
+ gfxFont::Spacing *spacing;
+ gfxTextRunDrawCallbacks *callbacks;
+ mozilla::SVGContextPaint *runContextPaint;
+ mozilla::gfx::Color fontSmoothingBGColor;
+ gfxFloat direction;
+ double devPerApp;
+ nscolor textStrokeColor;
+ gfxPattern *textStrokePattern;
+ const mozilla::gfx::StrokeOptions *strokeOpts;
+ const mozilla::gfx::DrawOptions *drawOpts;
+ DrawMode drawMode;
+ bool isVerticalRun;
+ bool isRTL;
+ bool paintSVGGlyphs;
+};
+
+struct FontDrawParams {
+ RefPtr<mozilla::gfx::ScaledFont> scaledFont;
+ RefPtr<mozilla::gfx::GlyphRenderingOptions> renderingOptions;
+ mozilla::SVGContextPaint *contextPaint;
+ mozilla::gfx::Matrix *passedInvMatrix;
+ mozilla::gfx::Matrix matInv;
+ double synBoldOnePixelOffset;
+ int32_t extraStrikes;
+ mozilla::gfx::DrawOptions drawOptions;
+ bool isVerticalFont;
+ bool haveSVGGlyphs;
+ bool haveColorGlyphs;
+};
+
+struct 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 000000000..5aa1eb0c7
--- /dev/null
+++ b/gfx/thebes/gfxFontConstants.h
@@ -0,0 +1,237 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 (-4)
+#define NS_FONT_STRETCH_EXTRA_CONDENSED (-3)
+#define NS_FONT_STRETCH_CONDENSED (-2)
+#define NS_FONT_STRETCH_SEMI_CONDENSED (-1)
+#define NS_FONT_STRETCH_NORMAL 0
+#define NS_FONT_STRETCH_SEMI_EXPANDED 1
+#define NS_FONT_STRETCH_EXPANDED 2
+#define NS_FONT_STRETCH_EXTRA_EXPANDED 3
+#define NS_FONT_STRETCH_ULTRA_EXPANDED 4
+
+#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_SYNTHESIS_WEIGHT 0x1
+#define NS_FONT_SYNTHESIS_STYLE 0x2
+
+#define NS_FONT_DISPLAY_AUTO 0
+#define NS_FONT_DISPLAY_BLOCK 1
+#define NS_FONT_DISPLAY_SWAP 2
+#define NS_FONT_DISPLAY_FALLBACK 3
+#define NS_FONT_DISPLAY_OPTIONAL 4
+
+enum {
+ eFeatureAlternates_historical,
+ eFeatureAlternates_stylistic,
+ eFeatureAlternates_styleset,
+ eFeatureAlternates_character_variant,
+ eFeatureAlternates_swash,
+ eFeatureAlternates_ornaments,
+ eFeatureAlternates_annotation,
+
+ eFeatureAlternates_numFeatures
+};
+
+// alternates - simple enumerated values
+#define NS_FONT_VARIANT_ALTERNATES_HISTORICAL (1 << eFeatureAlternates_historical)
+
+// alternates - values that use functional syntax
+#define NS_FONT_VARIANT_ALTERNATES_STYLISTIC (1 << eFeatureAlternates_stylistic)
+#define NS_FONT_VARIANT_ALTERNATES_STYLESET (1 << eFeatureAlternates_styleset)
+#define NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT (1 << eFeatureAlternates_character_variant)
+#define NS_FONT_VARIANT_ALTERNATES_SWASH (1 << eFeatureAlternates_swash)
+#define NS_FONT_VARIANT_ALTERNATES_ORNAMENTS (1 << eFeatureAlternates_ornaments)
+#define NS_FONT_VARIANT_ALTERNATES_ANNOTATION (1 << eFeatureAlternates_annotation)
+
+#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
+
+enum {
+ eFeatureEastAsian_jis78,
+ eFeatureEastAsian_jis83,
+ eFeatureEastAsian_jis90,
+ eFeatureEastAsian_jis04,
+ eFeatureEastAsian_simplified,
+ eFeatureEastAsian_traditional,
+ eFeatureEastAsian_full_width,
+ eFeatureEastAsian_prop_width,
+ eFeatureEastAsian_ruby,
+
+ eFeatureEastAsian_numFeatures
+};
+
+#define NS_FONT_VARIANT_EAST_ASIAN_JIS78 (1 << eFeatureEastAsian_jis78)
+#define NS_FONT_VARIANT_EAST_ASIAN_JIS83 (1 << eFeatureEastAsian_jis83)
+#define NS_FONT_VARIANT_EAST_ASIAN_JIS90 (1 << eFeatureEastAsian_jis90)
+#define NS_FONT_VARIANT_EAST_ASIAN_JIS04 (1 << eFeatureEastAsian_jis04)
+#define NS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED (1 << eFeatureEastAsian_simplified)
+#define NS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL (1 << eFeatureEastAsian_traditional)
+#define NS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH (1 << eFeatureEastAsian_full_width)
+#define NS_FONT_VARIANT_EAST_ASIAN_PROP_WIDTH (1 << eFeatureEastAsian_prop_width)
+#define NS_FONT_VARIANT_EAST_ASIAN_RUBY (1 << eFeatureEastAsian_ruby)
+
+#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 )
+
+enum {
+ eFeatureLigatures_none,
+ eFeatureLigatures_common,
+ eFeatureLigatures_no_common,
+ eFeatureLigatures_discretionary,
+ eFeatureLigatures_no_discretionary,
+ eFeatureLigatures_historical,
+ eFeatureLigatures_no_historical,
+ eFeatureLigatures_contextual,
+ eFeatureLigatures_no_contextual,
+
+ eFeatureLigatures_numFeatures
+};
+
+#define NS_FONT_VARIANT_LIGATURES_NONE (1 << eFeatureLigatures_none)
+#define NS_FONT_VARIANT_LIGATURES_COMMON (1 << eFeatureLigatures_common)
+#define NS_FONT_VARIANT_LIGATURES_NO_COMMON (1 << eFeatureLigatures_no_common)
+#define NS_FONT_VARIANT_LIGATURES_DISCRETIONARY (1 << eFeatureLigatures_discretionary)
+#define NS_FONT_VARIANT_LIGATURES_NO_DISCRETIONARY (1 << eFeatureLigatures_no_discretionary)
+#define NS_FONT_VARIANT_LIGATURES_HISTORICAL (1 << eFeatureLigatures_historical)
+#define NS_FONT_VARIANT_LIGATURES_NO_HISTORICAL (1 << eFeatureLigatures_no_historical)
+#define NS_FONT_VARIANT_LIGATURES_CONTEXTUAL (1 << eFeatureLigatures_contextual)
+#define NS_FONT_VARIANT_LIGATURES_NO_CONTEXTUAL (1 << eFeatureLigatures_no_contextual)
+
+#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
+
+enum {
+ eFeatureNumeric_lining,
+ eFeatureNumeric_oldstyle,
+ eFeatureNumeric_proportional,
+ eFeatureNumeric_tabular,
+ eFeatureNumeric_diagonal_fractions,
+ eFeatureNumeric_stacked_fractions,
+ eFeatureNumeric_slashedzero,
+ eFeatureNumeric_ordinal,
+
+ eFeatureNumeric_numFeatures
+};
+
+#define NS_FONT_VARIANT_NUMERIC_LINING (1 << eFeatureNumeric_lining)
+#define NS_FONT_VARIANT_NUMERIC_OLDSTYLE (1 << eFeatureNumeric_oldstyle)
+#define NS_FONT_VARIANT_NUMERIC_PROPORTIONAL (1 << eFeatureNumeric_proportional)
+#define NS_FONT_VARIANT_NUMERIC_TABULAR (1 << eFeatureNumeric_tabular)
+#define NS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS (1 << eFeatureNumeric_diagonal_fractions)
+#define NS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS (1 << eFeatureNumeric_stacked_fractions)
+#define NS_FONT_VARIANT_NUMERIC_SLASHZERO (1 << eFeatureNumeric_slashedzero)
+#define NS_FONT_VARIANT_NUMERIC_ORDINAL (1 << eFeatureNumeric_ordinal)
+
+#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
+
+// 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_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 000000000..f33d6a9d8
--- /dev/null
+++ b/gfx/thebes/gfxFontEntry.cpp
@@ -0,0 +1,1831 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "mozilla/MathAlgorithms.h"
+
+#include "mozilla/Logging.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsILanguageAtomService.h"
+
+#include "gfxFontEntry.h"
+#include "gfxTextRun.h"
+#include "gfxPlatform.h"
+#include "nsGkAtoms.h"
+
+#include "gfxTypes.h"
+#include "gfxContext.h"
+#include "gfxFontConstants.h"
+#include "gfxHarfBuzzShaper.h"
+#include "gfxUserFontSet.h"
+#include "gfxPlatformFontList.h"
+#include "nsUnicodeProperties.h"
+#include "nsMathUtils.h"
+#include "nsBidiUtils.h"
+#include "nsUnicodeRange.h"
+#include "nsStyleConsts.h"
+#include "mozilla/AppUnits.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
+#include "gfxSVGGlyphs.h"
+#include "gfx2DGlue.h"
+
+#include "cairo.h"
+
+#include "harfbuzz/hb.h"
+#include "harfbuzz/hb-ot.h"
+#include "graphite2/Font.h"
+
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::unicode;
+using mozilla::services::GetObserverService;
+
+void
+gfxCharacterMap::NotifyReleased()
+{
+ gfxPlatformFontList *fontlist = gfxPlatformFontList::PlatformFontList();
+ if (mShared) {
+ fontlist->RemoveCmap(this);
+ }
+ delete this;
+}
+
+gfxFontEntry::gfxFontEntry() :
+ mStyle(NS_FONT_STYLE_NORMAL), mFixedPitch(false),
+ mIsValid(true),
+ mIsBadUnderlineFont(false),
+ mIsUserFontContainer(false),
+ mIsDataUserFont(false),
+ mIsLocalUserFont(false),
+ mStandardFace(false),
+ mSymbolFont(false),
+ mIgnoreGDEF(false),
+ mIgnoreGSUB(false),
+ mSVGInitialized(false),
+ mHasSpaceFeaturesInitialized(false),
+ mHasSpaceFeatures(false),
+ mHasSpaceFeaturesKerning(false),
+ mHasSpaceFeaturesNonKerning(false),
+ mSkipDefaultFeatureSpaceCheck(false),
+ mGraphiteSpaceContextualsInitialized(false),
+ mHasGraphiteSpaceContextuals(false),
+ mSpaceGlyphIsInvisible(false),
+ mSpaceGlyphIsInvisibleInitialized(false),
+ mCheckedForGraphiteTables(false),
+ mHasCmapTable(false),
+ mGrFaceInitialized(false),
+ mCheckedForColorGlyph(false),
+ mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL),
+ mUVSOffset(0), mUVSData(nullptr),
+ mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE),
+ mCOLR(nullptr),
+ mCPAL(nullptr),
+ mUnitsPerEm(0),
+ mHBFace(nullptr),
+ mGrFace(nullptr),
+ mGrFaceRefCnt(0),
+ mComputedSizeOfUserFont(0)
+{
+ memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures));
+ memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures));
+}
+
+gfxFontEntry::gfxFontEntry(const nsAString& aName, bool aIsStandardFace) :
+ mName(aName), mStyle(NS_FONT_STYLE_NORMAL), mFixedPitch(false),
+ mIsValid(true),
+ mIsBadUnderlineFont(false),
+ mIsUserFontContainer(false),
+ mIsDataUserFont(false),
+ mIsLocalUserFont(false), mStandardFace(aIsStandardFace),
+ mSymbolFont(false),
+ mIgnoreGDEF(false),
+ mIgnoreGSUB(false),
+ mSVGInitialized(false),
+ mHasSpaceFeaturesInitialized(false),
+ mHasSpaceFeatures(false),
+ mHasSpaceFeaturesKerning(false),
+ mHasSpaceFeaturesNonKerning(false),
+ mSkipDefaultFeatureSpaceCheck(false),
+ mGraphiteSpaceContextualsInitialized(false),
+ mHasGraphiteSpaceContextuals(false),
+ mSpaceGlyphIsInvisible(false),
+ mSpaceGlyphIsInvisibleInitialized(false),
+ mCheckedForGraphiteTables(false),
+ mHasCmapTable(false),
+ mGrFaceInitialized(false),
+ mCheckedForColorGlyph(false),
+ mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL),
+ mUVSOffset(0), mUVSData(nullptr),
+ mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE),
+ mCOLR(nullptr),
+ mCPAL(nullptr),
+ mUnitsPerEm(0),
+ mHBFace(nullptr),
+ mGrFace(nullptr),
+ mGrFaceRefCnt(0),
+ mComputedSizeOfUserFont(0)
+{
+ memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures));
+ memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures));
+}
+
+gfxFontEntry::~gfxFontEntry()
+{
+ if (mCOLR) {
+ hb_blob_destroy(mCOLR);
+ }
+
+ if (mCPAL) {
+ hb_blob_destroy(mCPAL);
+ }
+
+ // 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);
+ }
+ }
+
+ // 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);
+}
+
+bool gfxFontEntry::IsSymbolFont()
+{
+ return mSymbolFont;
+}
+
+bool gfxFontEntry::TestCharacterMap(uint32_t aCh)
+{
+ if (!mCharacterMap) {
+ ReadCMAP();
+ NS_ASSERTION(mCharacterMap, "failed to initialize character map");
+ }
+ return mCharacterMap->test(aCh);
+}
+
+nsresult gfxFontEntry::InitializeUVSMap()
+{
+ // mUVSOffset will not be initialized
+ // until cmap is initialized.
+ if (!mCharacterMap) {
+ ReadCMAP();
+ NS_ASSERTION(mCharacterMap, "failed to initialize character map");
+ }
+
+ if (!mUVSOffset) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mUVSData) {
+ const uint32_t kCmapTag = TRUETYPE_TAG('c','m','a','p');
+ AutoTable cmapTable(this, kCmapTag);
+ if (!cmapTable) {
+ mUVSOffset = 0; // don't bother to read the table again
+ return NS_ERROR_FAILURE;
+ }
+
+ UniquePtr<uint8_t[]> uvsData;
+ unsigned int cmapLen;
+ const char* cmapData = hb_blob_get_data(cmapTable, &cmapLen);
+ nsresult rv = gfxFontUtils::ReadCMAPTableFormat14(
+ (const uint8_t*)cmapData + mUVSOffset,
+ cmapLen - mUVSOffset, uvsData);
+
+ if (NS_FAILED(rv)) {
+ mUVSOffset = 0; // don't bother to read the table again
+ return rv;
+ }
+
+ mUVSData = Move(uvsData);
+ }
+
+ return NS_OK;
+}
+
+uint16_t gfxFontEntry::GetUVSGlyph(uint32_t aCh, uint32_t aVS)
+{
+ InitializeUVSMap();
+
+ if (mUVSData) {
+ return gfxFontUtils::MapUVSToGlyphFormat14(mUVSData.get(), aCh, aVS);
+ }
+
+ return 0;
+}
+
+bool gfxFontEntry::SupportsScriptInGSUB(const hb_tag_t* aScriptTags)
+{
+ hb_face_t *face = GetHBFace();
+ if (!face) {
+ return false;
+ }
+
+ unsigned int index;
+ hb_tag_t chosenScript;
+ bool found =
+ hb_ot_layout_table_choose_script(face, TRUETYPE_TAG('G','S','U','B'),
+ aScriptTags, &index, &chosenScript);
+ hb_face_destroy(face);
+
+ return found && chosenScript != TRUETYPE_TAG('D','F','L','T');
+}
+
+nsresult gfxFontEntry::ReadCMAP(FontInfoData *aFontInfoData)
+{
+ NS_ASSERTION(false, "using default no-op implementation of ReadCMAP");
+ mCharacterMap = new gfxCharacterMap();
+ return NS_OK;
+}
+
+nsString
+gfxFontEntry::RealFaceName()
+{
+ AutoTable nameTable(this, TRUETYPE_TAG('n','a','m','e'));
+ if (nameTable) {
+ nsAutoString name;
+ nsresult rv = gfxFontUtils::GetFullNameFromTable(nameTable, name);
+ if (NS_SUCCEEDED(rv)) {
+ return name;
+ }
+ }
+ return Name();
+}
+
+already_AddRefed<gfxFont>
+gfxFontEntry::FindOrMakeFont(const gfxFontStyle *aStyle,
+ bool aNeedsBold,
+ gfxCharacterMap* aUnicodeRangeMap)
+{
+ // the font entry name is the psname, not the family name
+ RefPtr<gfxFont> font =
+ gfxFontCache::GetCache()->Lookup(this, aStyle, aUnicodeRangeMap);
+
+ if (!font) {
+ gfxFont *newFont = CreateFontInstance(aStyle, aNeedsBold);
+ if (!newFont)
+ return nullptr;
+ if (!newFont->Valid()) {
+ delete newFont;
+ return nullptr;
+ }
+ font = newFont;
+ font->SetUnicodeRangeMap(aUnicodeRangeMap);
+ gfxFontCache::GetCache()->AddNew(font);
+ }
+ return font.forget();
+}
+
+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 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 mSVGGlyphs->HasSVGGlyph(aGlyphId);
+}
+
+bool
+gfxFontEntry::GetSVGGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphId,
+ gfxRect *aResult)
+{
+ MOZ_ASSERT(mSVGInitialized,
+ "SVG data has not yet been loaded. TryGetSVGData() first.");
+ MOZ_ASSERT(mUnitsPerEm >= kMinUPEM && mUnitsPerEm <= kMaxUPEM,
+ "font has invalid unitsPerEm");
+
+ cairo_matrix_t fontMatrix;
+ cairo_get_font_matrix(gfxFont::RefCairo(aDrawTarget), &fontMatrix);
+
+ gfxMatrix svgToAppSpace(fontMatrix.xx, fontMatrix.yx,
+ fontMatrix.xy, fontMatrix.yy,
+ fontMatrix.x0, fontMatrix.y0);
+ svgToAppSpace.Scale(1.0f / mUnitsPerEm, 1.0f / mUnitsPerEm);
+
+ return mSVGGlyphs->GetGlyphExtents(aGlyphId, svgToAppSpace, aResult);
+}
+
+bool
+gfxFontEntry::RenderSVGGlyph(gfxContext *aContext, uint32_t aGlyphId,
+ SVGContextPaint* aContextPaint)
+{
+ NS_ASSERTION(mSVGInitialized, "SVG data has not yet been loaded. TryGetSVGData() first.");
+ return mSVGGlyphs->RenderGlyph(aContext, aGlyphId, aContextPaint);
+}
+
+bool
+gfxFontEntry::TryGetSVGData(gfxFont* aFont)
+{
+ if (!gfxPlatform::GetPlatform()->OpenTypeSVGEnabled()) {
+ return false;
+ }
+
+ if (!mSVGInitialized) {
+ mSVGInitialized = true;
+
+ // If UnitsPerEm is not known/valid, we can't use SVG glyphs
+ if (UnitsPerEm() == kInvalidUPEM) {
+ 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) {
+ return false;
+ }
+
+ // gfxSVGGlyphs will hb_blob_destroy() the table when it is finished
+ // with it.
+ mSVGGlyphs = MakeUnique<gfxSVGGlyphs>(svgTable, this);
+ }
+
+ if (mSVGGlyphs && !mFontsUsingSVGGlyphs.Contains(aFont)) {
+ mFontsUsingSVGGlyphs.AppendElement(aFont);
+ }
+
+ return !!mSVGGlyphs;
+}
+
+void
+gfxFontEntry::NotifyFontDestroyed(gfxFont* aFont)
+{
+ mFontsUsingSVGGlyphs.RemoveElement(aFont);
+}
+
+void
+gfxFontEntry::NotifyGlyphsChanged()
+{
+ for (uint32_t i = 0, count = mFontsUsingSVGGlyphs.Length(); i < count; ++i) {
+ gfxFont* font = mFontsUsingSVGGlyphs[i];
+ font->NotifyGlyphsChanged();
+ }
+}
+
+bool
+gfxFontEntry::TryGetColorGlyphs()
+{
+ if (mCheckedForColorGlyph) {
+ return (mCOLR && mCPAL);
+ }
+
+ mCheckedForColorGlyph = true;
+
+ mCOLR = GetFontTable(TRUETYPE_TAG('C', 'O', 'L', 'R'));
+ if (!mCOLR) {
+ return false;
+ }
+
+ mCPAL = GetFontTable(TRUETYPE_TAG('C', 'P', 'A', 'L'));
+ if (!mCPAL) {
+ hb_blob_destroy(mCOLR);
+ mCOLR = nullptr;
+ return false;
+ }
+
+ // validation COLR and CPAL table
+ if (gfxFontUtils::ValidateColorGlyphs(mCOLR, mCPAL)) {
+ return true;
+ }
+
+ hb_blob_destroy(mCOLR);
+ hb_blob_destroy(mCPAL);
+ mCOLR = nullptr;
+ mCPAL = nullptr;
+ return false;
+}
+
+/**
+ * FontTableBlobData
+ *
+ * See FontTableHashEntry for the general strategy.
+ */
+
+class gfxFontEntry::FontTableBlobData {
+public:
+ explicit FontTableBlobData(nsTArray<uint8_t>&& aBuffer)
+ : mTableData(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(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 if (mBlob) {
+ 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)
+{
+ if (!mFontTableCache) {
+ // we do this here rather than on fontEntry construction
+ // because not all shapers will access the table cache at all
+ mFontTableCache = MakeUnique<nsTHashtable<FontTableHashEntry>>(8);
+ }
+
+ FontTableHashEntry *entry = mFontTableCache->GetEntry(aTag);
+ if (!entry) {
+ return false;
+ }
+
+ *aBlob = entry->GetBlob();
+ return true;
+}
+
+hb_blob_t *
+gfxFontEntry::ShareFontTableAndGetBlob(uint32_t aTag,
+ nsTArray<uint8_t>* aBuffer)
+{
+ if (MOZ_UNLIKELY(!mFontTableCache)) {
+ // we do this here rather than on fontEntry construction
+ // because not all shapers will access the table cache at all
+ mFontTableCache = MakeUnique<nsTHashtable<FontTableHashEntry>>(8);
+ }
+
+ FontTableHashEntry *entry = mFontTableCache->PutEntry(aTag);
+ if (MOZ_UNLIKELY(!entry)) { // OOM
+ return nullptr;
+ }
+
+ if (!aBuffer) {
+ // ensure the entry is null
+ entry->Clear();
+ return nullptr;
+ }
+
+ return entry->ShareTableAndGetBlob(Move(*aBuffer), mFontTableCache.get());
+}
+
+already_AddRefed<gfxCharacterMap>
+gfxFontEntry::GetCMAPFromFontInfo(FontInfoData *aFontInfoData,
+ uint32_t& aUVSOffset,
+ bool& aSymbolFont)
+{
+ if (!aFontInfoData || !aFontInfoData->mLoadCmaps) {
+ return nullptr;
+ }
+
+ return aFontInfoData->GetCMAP(mName, aUVSOffset, aSymbolFont);
+}
+
+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*/ void
+gfxFontEntry::HBFaceDeletedCallback(void *aUserData)
+{
+ gfxFontEntry *fe = static_cast<gfxFontEntry*>(aUserData);
+ fe->ForgetHBFace();
+}
+
+void
+gfxFontEntry::ForgetHBFace()
+{
+ mHBFace = nullptr;
+}
+
+hb_face_t*
+gfxFontEntry::GetHBFace()
+{
+ if (!mHBFace) {
+ mHBFace = hb_face_create_for_tables(HBGetTable, this,
+ HBFaceDeletedCallback);
+ return mHBFace;
+ }
+ return hb_face_reference(mHBFace);
+}
+
+/*static*/ const void*
+gfxFontEntry::GrGetTable(const void *aAppFaceHandle, unsigned int aName,
+ size_t *aLen)
+{
+ gfxFontEntry *fontEntry =
+ static_cast<gfxFontEntry*>(const_cast<void*>(aAppFaceHandle));
+ hb_blob_t *blob = fontEntry->GetFontTable(aName);
+ if (blob) {
+ unsigned int blobLength;
+ const void *tableData = hb_blob_get_data(blob, &blobLength);
+ fontEntry->mGrTableMap->Put(tableData, blob);
+ *aLen = blobLength;
+ return tableData;
+ }
+ *aLen = 0;
+ return nullptr;
+}
+
+/*static*/ void
+gfxFontEntry::GrReleaseTable(const void *aAppFaceHandle,
+ const void *aTableBuffer)
+{
+ gfxFontEntry *fontEntry =
+ static_cast<gfxFontEntry*>(const_cast<void*>(aAppFaceHandle));
+ void *data;
+ if (fontEntry->mGrTableMap->Get(aTableBuffer, &data)) {
+ fontEntry->mGrTableMap->Remove(aTableBuffer);
+ hb_blob_destroy(static_cast<hb_blob_t*>(data));
+ }
+}
+
+gr_face*
+gfxFontEntry::GetGrFace()
+{
+ if (!mGrFaceInitialized) {
+ gr_face_ops faceOps = {
+ sizeof(gr_face_ops),
+ GrGetTable,
+ GrReleaseTable
+ };
+ mGrTableMap = new nsDataHashtable<nsPtrHashKey<const void>,void*>;
+ mGrFace = gr_make_face_with_ops(this, &faceOps, gr_face_default);
+ mGrFaceInitialized = true;
+ }
+ ++mGrFaceRefCnt;
+ return mGrFace;
+}
+
+void
+gfxFontEntry::ReleaseGrFace(gr_face *aFace)
+{
+ MOZ_ASSERT(aFace == mGrFace); // sanity-check
+ MOZ_ASSERT(mGrFaceRefCnt > 0);
+ if (--mGrFaceRefCnt == 0) {
+ gr_face_destroy(mGrFace);
+ mGrFace = nullptr;
+ mGrFaceInitialized = false;
+ delete mGrTableMap;
+ mGrTableMap = nullptr;
+ }
+}
+
+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;
+}
+
+void
+gfxFontEntry::CheckForGraphiteTables()
+{
+ mHasGraphiteTables = HasFontTable(TRUETYPE_TAG('S','i','l','f'));
+}
+
+bool
+gfxFontEntry::HasGraphiteSpaceContextuals()
+{
+ if (!mGraphiteSpaceContextualsInitialized) {
+ gr_face* face = GetGrFace();
+ if (face) {
+ const gr_faceinfo* faceInfo = gr_face_info(face, 0);
+ mHasGraphiteSpaceContextuals =
+ faceInfo->space_contextuals != gr_faceinfo::gr_space_none;
+ }
+ ReleaseGrFace(face); // always balance GetGrFace, even if face is null
+ mGraphiteSpaceContextualsInitialized = true;
+ }
+ return mHasGraphiteSpaceContextuals;
+}
+
+#define FEATURE_SCRIPT_MASK 0x000000ff // script index replaces low byte of tag
+
+static_assert(int(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)
+{
+ if (!mSupportedFeatures) {
+ mSupportedFeatures = MakeUnique<nsDataHashtable<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);
+ bool result;
+ if (mSupportedFeatures->Get(scriptFeature, &result)) {
+ return result;
+ }
+
+ result = false;
+
+ hb_face_t *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
+ hb_tag_t scriptTags[4] = {
+ HB_TAG_NONE,
+ HB_TAG_NONE,
+ HB_TAG_NONE,
+ HB_TAG_NONE
+ };
+ hb_ot_tags_from_script(hbScript, &scriptTags[0], &scriptTags[1]);
+
+ // Replace the first remaining NONE with DEFAULT
+ hb_tag_t* scriptTag = &scriptTags[0];
+ while (*scriptTag != HB_TAG_NONE) {
+ ++scriptTag;
+ }
+ *scriptTag = 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');
+ scriptTag = &scriptTags[0];
+ while (*scriptTag != HB_TAG_NONE) {
+ unsigned int scriptIndex;
+ if (hb_ot_layout_table_find_script(face, kGSUB, *scriptTag,
+ &scriptIndex)) {
+ if (hb_ot_layout_language_find_feature(face, kGSUB,
+ scriptIndex,
+ HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX,
+ aFeatureTag, nullptr)) {
+ result = true;
+ }
+ break;
+ }
+ ++scriptTag;
+ }
+ }
+
+ hb_face_destroy(face);
+
+ mSupportedFeatures->Put(scriptFeature, result);
+
+ return result;
+}
+
+const hb_set_t*
+gfxFontEntry::InputsForOpenTypeFeature(Script aScript, uint32_t aFeatureTag)
+{
+ if (!mFeatureInputs) {
+ mFeatureInputs = MakeUnique<nsDataHashtable<nsUint32HashKey,hb_set_t*>>();
+ }
+
+ NS_ASSERTION(aFeatureTag == HB_TAG('s','u','p','s') ||
+ aFeatureTag == HB_TAG('s','u','b','s'),
+ "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();
+
+ hb_face_t *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
+ hb_tag_t scriptTags[4] = {
+ HB_TAG_NONE,
+ HB_TAG_NONE,
+ HB_TAG_NONE,
+ HB_TAG_NONE
+ };
+ hb_ot_tags_from_script(hbScript, &scriptTags[0], &scriptTags[1]);
+
+ // Replace the first remaining NONE with DEFAULT
+ hb_tag_t* scriptTag = &scriptTags[0];
+ while (*scriptTag != HB_TAG_NONE) {
+ ++scriptTag;
+ }
+ *scriptTag = HB_OT_TAG_DEFAULT_SCRIPT;
+
+ 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);
+ }
+
+ hb_face_destroy(face);
+
+ mFeatureInputs->Put(scriptFeature, inputGlyphs);
+ return inputGlyphs;
+}
+
+bool
+gfxFontEntry::SupportsGraphiteFeature(uint32_t aFeatureTag)
+{
+ if (!mSupportedFeatures) {
+ mSupportedFeatures = MakeUnique<nsDataHashtable<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;
+ }
+
+ gr_face* face = GetGrFace();
+ result = face ? gr_face_find_fref(face, aFeatureTag) != nullptr : false;
+ ReleaseGrFace(face);
+
+ mSupportedFeatures->Put(scriptFeature, result);
+
+ return result;
+}
+
+bool
+gfxFontEntry::GetColorLayersInfo(uint32_t aGlyphId,
+ const mozilla::gfx::Color& aDefaultColor,
+ nsTArray<uint16_t>& aLayerGlyphs,
+ nsTArray<mozilla::gfx::Color>& aLayerColors)
+{
+ return gfxFontUtils::GetColorGlyphLayers(mCOLR,
+ mCPAL,
+ aGlyphId,
+ aDefaultColor,
+ aLayerGlyphs,
+ aLayerColors);
+}
+
+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 && mCharacterMap->mBuildOnTheFly) {
+ aSizes->mCharMapsSize +=
+ mCharacterMap->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ if (mFontTableCache) {
+ aSizes->mFontTableCacheSize +=
+ mFontTableCache->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ // If the font has UVS data, we count that as part of the character map.
+ if (mUVSData) {
+ aSizes->mCharMapsSize += aMallocSizeOf(mUVSData.get());
+ }
+
+ // 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 +=
+ mSVGGlyphs->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ if (mSupportedFeatures) {
+ aSizes->mFontTableCacheSize +=
+ mSupportedFeatures->ShallowSizeOfIncludingThis(aMallocSizeOf);
+ }
+ if (mFeatureInputs) {
+ aSizes->mFontTableCacheSize +=
+ mFeatureInputs->ShallowSizeOfIncludingThis(aMallocSizeOf);
+ 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()
+{
+ 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& aNeedsSyntheticBold)
+{
+ AutoTArray<gfxFontEntry*,4> matched;
+ FindAllFontsForStyle(aFontStyle, matched, aNeedsSyntheticBold);
+ if (!matched.IsEmpty()) {
+ return matched[0];
+ }
+ return nullptr;
+}
+
+#define STYLE_SHIFT 2 // number of bits to contain style distance
+
+// style distance ==> [0,2]
+static inline uint32_t
+StyleDistance(uint32_t aFontStyle, uint32_t aTargetStyle)
+{
+ if (aFontStyle == aTargetStyle) {
+ return 0; // styles match exactly ==> 0
+ }
+ if (aFontStyle == NS_FONT_STYLE_NORMAL ||
+ aTargetStyle == NS_FONT_STYLE_NORMAL) {
+ return 2; // one is normal (but not the other) ==> 2
+ }
+ return 1; // neither is normal; must be italic vs oblique ==> 1
+}
+
+#define REVERSE_STRETCH_DISTANCE 5
+
+// stretch distance ==> [0,13]
+static inline uint32_t
+StretchDistance(int16_t aFontStretch, int16_t aTargetStretch)
+{
+ int32_t distance = 0;
+ if (aTargetStretch != aFontStretch) {
+ // stretch values are in the range -4 .. +4
+ // if aTargetStretch is positive, we prefer more-positive values;
+ // if zero or negative, prefer more-negative
+ if (aTargetStretch > 0) {
+ distance = (aFontStretch - aTargetStretch);
+ } else {
+ distance = (aTargetStretch - aFontStretch);
+ }
+ // if the computed "distance" here is negative, it means that
+ // aFontEntry lies in the "non-preferred" direction from aTargetStretch,
+ // so we treat that as larger than any preferred-direction distance
+ // (max possible is 4) by adding an extra 5 to the absolute value
+ if (distance < 0) {
+ distance = -distance + REVERSE_STRETCH_DISTANCE;
+ }
+ }
+ return uint32_t(distance);
+}
+
+// CSS currently limits font weights to multiples of 100 but the weight
+// matching code below does not assume this.
+//
+// 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.
+
+#define REVERSE_WEIGHT_DISTANCE 600
+#define WEIGHT_SHIFT 11 // number of bits to contain weight distance
+
+// weight distance ==> [0,1598]
+static inline uint32_t
+WeightDistance(uint32_t aFontWeight, uint32_t aTargetWeight)
+{
+ // Compute a measure of the "distance" between the requested
+ // weight and the given fontEntry
+
+ int32_t distance = 0, addedDistance = 0;
+ if (aTargetWeight != aFontWeight) {
+ if (aTargetWeight > 500) {
+ distance = aFontWeight - aTargetWeight;
+ } else if (aTargetWeight < 400) {
+ distance = aTargetWeight - aFontWeight;
+ } else {
+ // special case - target is between 400 and 500
+
+ // font weights between 400 and 500 are close
+ if (aFontWeight >= 400 && aFontWeight <= 500) {
+ if (aFontWeight < aTargetWeight) {
+ distance = 500 - aFontWeight;
+ } else {
+ distance = aFontWeight - aTargetWeight;
+ }
+ } else {
+ // font weights outside use rule for target weights < 400 with
+ // added distance to separate from font weights in
+ // the [400..500] range
+ distance = aTargetWeight - aFontWeight;
+ addedDistance = 100;
+ }
+ }
+ if (distance < 0) {
+ distance = -distance + REVERSE_WEIGHT_DISTANCE;
+ }
+ distance += addedDistance;
+ }
+ return uint32_t(distance);
+}
+
+#define MAX_DISTANCE 0xffffffff
+
+static inline uint32_t
+WeightStyleStretchDistance(gfxFontEntry* aFontEntry,
+ const gfxFontStyle& aTargetStyle)
+{
+ // weight/style/stretch priority: stretch >> style >> weight
+ uint32_t stretchDist =
+ StretchDistance(aFontEntry->mStretch, aTargetStyle.stretch);
+ uint32_t styleDist = StyleDistance(aFontEntry->mStyle, aTargetStyle.style);
+ uint32_t weightDist =
+ WeightDistance(aFontEntry->Weight(), aTargetStyle.weight);
+
+ NS_ASSERTION(weightDist < (1 << WEIGHT_SHIFT), "weight value out of bounds");
+ NS_ASSERTION(styleDist < (1 << STYLE_SHIFT), "slope value out of bounds");
+
+ return (stretchDist << (STYLE_SHIFT + WEIGHT_SHIFT)) |
+ (styleDist << WEIGHT_SHIFT) |
+ weightDist;
+}
+
+void
+gfxFontFamily::FindAllFontsForStyle(const gfxFontStyle& aFontStyle,
+ nsTArray<gfxFontEntry*>& aFontEntryList,
+ bool& aNeedsSyntheticBold)
+{
+ if (!mHasStyles) {
+ FindStyleVariations(); // collect faces for the family, if not already done
+ }
+
+ NS_ASSERTION(mAvailableFonts.Length() > 0, "font family with no faces!");
+ NS_ASSERTION(aFontEntryList.IsEmpty(), "non-empty fontlist passed in");
+
+ aNeedsSyntheticBold = false;
+
+ int8_t baseWeight = aFontStyle.ComputeWeight();
+ bool wantBold = baseWeight >= 6;
+ 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];
+ aNeedsSyntheticBold =
+ wantBold && !fe->IsBold() && aFontStyle.allowSyntheticWeight;
+ 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 wantItalic = (aFontStyle.style != NS_FONT_STYLE_NORMAL);
+ uint8_t faceIndex = (wantItalic ? kItalicMask : 0) |
+ (wantBold ? kBoldMask : 0);
+
+ // if the desired style is available, return it directly
+ fe = mAvailableFonts[faceIndex];
+ if (fe) {
+ // no need to set aNeedsSyntheticBold here as we matched the boldness request
+ 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) {
+ aNeedsSyntheticBold =
+ wantBold && !fe->IsBold() &&
+ aFontStyle.allowSyntheticWeight;
+ aFontEntryList.AppendElement(fe);
+ return;
+ }
+ }
+
+ // this can't happen unless we have totally broken the font-list manager!
+ NS_NOTREACHED("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.
+
+ uint32_t minDistance = MAX_DISTANCE;
+ 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
+ uint32_t 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);
+ if (!matched->IsBold() && aFontStyle.weight >= 600 &&
+ aFontStyle.allowSyntheticWeight) {
+ aNeedsSyntheticBold = true;
+ }
+ }
+}
+
+void
+gfxFontFamily::CheckForSimpleFamily()
+{
+ // 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;
+ }
+
+ int16_t firstStretch = mAvailableFonts[0]->Stretch();
+
+ 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;
+ }
+ uint8_t faceIndex = (fe->IsItalic() ? kItalicMask : 0) |
+ (fe->Weight() >= 600 ? 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) {
+ 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(nsAString& aLocalizedName)
+{
+ // just return the primary name; subclasses should override
+ aLocalizedName = mName;
+}
+
+// metric for how close a given font matches a style
+static int32_t
+CalcStyleMatch(gfxFontEntry *aFontEntry, const gfxFontStyle *aStyle)
+{
+ int32_t rank = 0;
+ if (aStyle) {
+ // italics
+ bool wantUpright = (aStyle->style == NS_FONT_STYLE_NORMAL);
+ if (aFontEntry->IsUpright() == wantUpright) {
+ rank += 10;
+ }
+
+ // measure of closeness of weight to the desired value
+ rank += 9 - DeprecatedAbs(aFontEntry->Weight() / 100 - aStyle->ComputeWeight());
+ } else {
+ // if no font to match, prefer non-bold, non-italic fonts
+ if (aFontEntry->IsUpright()) {
+ rank += 3;
+ }
+ if (!aFontEntry->IsBold()) {
+ rank += 2;
+ }
+ }
+
+ return rank;
+}
+
+#define RANK_MATCHED_CMAP 20
+
+void
+gfxFontFamily::FindFontForChar(GlobalFontMatch *aMatchData)
+{
+ if (mFamilyCharacterMapInitialized && !TestCharacterMap(aMatchData->mCh)) {
+ // none of the faces in the family support the required char,
+ // so bail out immediately
+ return;
+ }
+
+ bool needsBold;
+ gfxFontEntry *fe =
+ FindFontForStyle(aMatchData->mStyle ? *aMatchData->mStyle
+ : gfxFontStyle(),
+ needsBold);
+
+ if (fe && !fe->SkipDuringSystemFallback()) {
+ int32_t rank = 0;
+
+ if (fe->HasCharacter(aMatchData->mCh)) {
+ rank += RANK_MATCHED_CMAP;
+ aMatchData->mCount++;
+
+ LogModule* log = gfxPlatform::GetLog(eGfxLog_textrun);
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Debug))) {
+ uint32_t unicodeRange = FindCharUnicodeRange(aMatchData->mCh);
+ Script script = GetScriptCode(aMatchData->mCh);
+ MOZ_LOG(log, LogLevel::Debug,\
+ ("(textrun-systemfallback-fonts) char: u+%6.6x "
+ "unicode-range: %d script: %d match: [%s]\n",
+ aMatchData->mCh,
+ unicodeRange, int(script),
+ NS_ConvertUTF16toUTF8(fe->Name()).get()));
+ }
+ }
+
+ aMatchData->mCmapsTested++;
+ if (rank == 0) {
+ return;
+ }
+
+ // omitting from original windows code -- family name, lang group, pitch
+ // not available in current FontEntry implementation
+ rank += CalcStyleMatch(fe, aMatchData->mStyle);
+
+ // xxx - add whether AAT font with morphing info for specific lang groups
+
+ if (rank > aMatchData->mMatchRank
+ || (rank == aMatchData->mMatchRank &&
+ Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0))
+ {
+ aMatchData->mBestMatch = fe;
+ aMatchData->mMatchedFamily = this;
+ aMatchData->mMatchRank = rank;
+ }
+ }
+}
+
+void
+gfxFontFamily::SearchAllFontsForChar(GlobalFontMatch *aMatchData)
+{
+ uint32_t i, numFonts = mAvailableFonts.Length();
+ for (i = 0; i < numFonts; i++) {
+ gfxFontEntry *fe = mAvailableFonts[i];
+ if (fe && fe->HasCharacter(aMatchData->mCh)) {
+ int32_t rank = RANK_MATCHED_CMAP;
+ rank += CalcStyleMatch(fe, aMatchData->mStyle);
+ if (rank > aMatchData->mMatchRank
+ || (rank == aMatchData->mMatchRank &&
+ Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0))
+ {
+ aMatchData->mBestMatch = fe;
+ aMatchData->mMatchedFamily = this;
+ aMatchData->mMatchRank = rank;
+ }
+ }
+ }
+}
+
+/*static*/ void
+gfxFontFamily::ReadOtherFamilyNamesForFace(const nsAString& aFamilyName,
+ const char *aNameData,
+ uint32_t aDataLength,
+ nsTArray<nsString>& aOtherFamilyNames,
+ bool useFullName)
+{
+ 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;
+ }
+
+ 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; // 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 == gfxFontUtils::NAME_ID_FULL) ||
+ (!useFullName && (nameID == gfxFontUtils::NAME_ID_FAMILY ||
+ nameID == gfxFontUtils::NAME_ID_PREFERRED_FAMILY))) {
+ nsAutoString otherFamilyName;
+ bool ok = gfxFontUtils::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.AppendElement(otherFamilyName);
+ }
+ }
+ }
+}
+
+// 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<nsString,4> otherFamilyNames;
+
+ ReadOtherFamilyNamesForFace(mName, nameData, dataLength,
+ otherFamilyNames, useFullName);
+
+ uint32_t n = otherFamilyNames.Length();
+ for (uint32_t i = 0; i < n; i++) {
+ aPlatformFontList->AddOtherFamilyName(this, otherFamilyNames[i]);
+ }
+
+ return n != 0;
+}
+
+void
+gfxFontFamily::ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList)
+{
+ if (mOtherFamilyNamesInitialized)
+ return;
+ mOtherFamilyNamesInitialized = true;
+
+ FindStyleVariations();
+
+ // 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);
+ }
+}
+
+void
+gfxFontFamily::ReadFaceNames(gfxPlatformFontList *aPlatformFontList,
+ bool aNeedFullnamePostscriptNames,
+ FontInfoData *aFontInfoData)
+{
+ // if all needed names have already been read, skip
+ if (mOtherFamilyNamesInitialized &&
+ (mFaceNamesInitialized || !aNeedFullnamePostscriptNames))
+ return;
+
+ bool asyncFontLoaderDisabled = false;
+
+ if (!mOtherFamilyNamesInitialized &&
+ aFontInfoData &&
+ aFontInfoData->mLoadOtherNames &&
+ !asyncFontLoaderDisabled)
+ {
+ AutoTArray<nsString,4> otherFamilyNames;
+ bool foundOtherNames =
+ aFontInfoData->GetOtherFamilyNames(mName, otherFamilyNames);
+ if (foundOtherNames) {
+ uint32_t i, n = otherFamilyNames.Length();
+ for (i = 0; i < n; i++) {
+ aPlatformFontList->AddOtherFamilyName(this, otherFamilyNames[i]);
+ }
+ }
+ mOtherFamilyNamesInitialized = true;
+ }
+
+ // if all needed data has been initialized, return
+ if (mOtherFamilyNamesInitialized &&
+ (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) {
+ return;
+ }
+
+ FindStyleVariations(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;
+ }
+
+ nsAutoString fullname, psname;
+ bool foundFaceNames = false;
+ if (!mFaceNamesInitialized &&
+ aNeedFullnamePostscriptNames &&
+ aFontInfoData &&
+ aFontInfoData->mLoadFaceNames) {
+ aFontInfoData->GetFaceNames(fe->Name(), fullname, psname);
+ if (!fullname.IsEmpty()) {
+ aPlatformFontList->AddFullname(fe, fullname);
+ }
+ if (!psname.IsEmpty()) {
+ aPlatformFontList->AddPostscriptName(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->AddFullname(fe, fullname);
+ }
+
+ if (gfxFontUtils::ReadCanonicalName(
+ nameTable, gfxFontUtils::NAME_ID_POSTSCRIPT, psname) == NS_OK)
+ {
+ aPlatformFontList->AddPostscriptName(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 nsAString& aPostscriptName)
+{
+ // find the font using a simple linear search
+ uint32_t numFonts = mAvailableFonts.Length();
+ for (uint32_t i = 0; i < numFonts; i++) {
+ gfxFontEntry *fe = mAvailableFonts[i].get();
+ if (fe && fe->Name() == aPostscriptName)
+ return fe;
+ }
+ return nullptr;
+}
+
+void
+gfxFontFamily::ReadAllCMAPs(FontInfoData *aFontInfoData)
+{
+ FindStyleVariations(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->mCharacterMap));
+ }
+ mFamilyCharacterMap.Compact();
+ mFamilyCharacterMapInitialized = true;
+}
+
+void
+gfxFontFamily::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const
+{
+ 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 000000000..7f0e7215b
--- /dev/null
+++ b/gfx/thebes/gfxFontEntry.h
@@ -0,0 +1,777 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxTypes.h"
+#include "nsString.h"
+#include "gfxFontConstants.h"
+#include "gfxFontFeatures.h"
+#include "gfxFontUtils.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsUnicodeScriptCodes.h"
+#include "nsDataHashtable.h"
+#include "harfbuzz/hb.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/UniquePtr.h"
+
+typedef struct gr_face gr_face;
+
+#ifdef DEBUG
+#include <stdio.h>
+#endif
+
+struct gfxFontStyle;
+class gfxContext;
+class gfxFont;
+class gfxFontFamily;
+class gfxUserFontData;
+class gfxSVGGlyphs;
+class FontInfoData;
+struct FontListSizes;
+class nsIAtom;
+
+namespace mozilla {
+class SVGContextPaint;
+};
+
+class gfxCharacterMap : public gfxSparseBitSet {
+public:
+ nsrefcnt AddRef() {
+ NS_PRECONDITION(int32_t(mRefCnt) >= 0, "illegal refcnt");
+ ++mRefCnt;
+ NS_LOG_ADDREF(this, mRefCnt, "gfxCharacterMap", sizeof(*this));
+ return mRefCnt;
+ }
+
+ nsrefcnt Release() {
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "gfxCharacterMap");
+ if (mRefCnt == 0) {
+ NotifyReleased();
+ // |this| has been deleted.
+ return 0;
+ }
+ return mRefCnt;
+ }
+
+ gfxCharacterMap() :
+ mHash(0), mBuildOnTheFly(false), mShared(false)
+ { }
+
+ explicit gfxCharacterMap(const gfxSparseBitSet& aOther) :
+ gfxSparseBitSet(aOther),
+ mHash(0), mBuildOnTheFly(false), mShared(false)
+ { }
+
+ void CalcHash() { mHash = GetChecksum(); }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return gfxSparseBitSet::SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ // hash of the cmap bitvector
+ uint32_t mHash;
+
+ // if cmap is built on the fly it's never shared
+ bool mBuildOnTheFly;
+
+ // cmap is shared globally
+ bool mShared;
+
+protected:
+ void NotifyReleased();
+
+ nsAutoRefCnt mRefCnt;
+
+private:
+ gfxCharacterMap(const gfxCharacterMap&);
+ gfxCharacterMap& operator=(const gfxCharacterMap&);
+};
+
+class gfxFontEntry {
+public:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::unicode::Script Script;
+
+ NS_INLINE_DECL_REFCOUNTING(gfxFontEntry)
+
+ explicit gfxFontEntry(const nsAString& aName, bool aIsStandardFace = false);
+
+ // unique name for the face, *not* the family; not necessarily the
+ // "real" or user-friendly name, may be an internal identifier
+ const nsString& Name() const { return mName; }
+
+ // family name
+ const nsString& 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 nsString RealFaceName();
+
+ uint16_t Weight() const { return mWeight; }
+ int16_t Stretch() const { return mStretch; }
+
+ bool IsUserFont() const { return mIsDataUserFont || mIsLocalUserFont; }
+ bool IsLocalUserFont() const { return mIsLocalUserFont; }
+ bool IsFixedPitch() const { return mFixedPitch; }
+ bool IsItalic() const { return mStyle == NS_FONT_STYLE_ITALIC; }
+ bool IsOblique() const { return mStyle == NS_FONT_STYLE_OBLIQUE; }
+ bool IsUpright() const { return mStyle == NS_FONT_STYLE_NORMAL; }
+ bool IsBold() const { return mWeight >= 600; } // bold == weights 600 and above
+ bool IgnoreGDEF() const { return mIgnoreGDEF; }
+ bool IgnoreGSUB() const { return mIgnoreGSUB; }
+
+ // 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 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 IsSymbolFont();
+
+ virtual bool HasFontTable(uint32_t aTableTag);
+
+ inline bool HasGraphiteTables() {
+ if (!mCheckedForGraphiteTables) {
+ CheckForGraphiteTables();
+ mCheckedForGraphiteTables = true;
+ }
+ return mHasGraphiteTables;
+ }
+
+ inline bool HasCmapTable() {
+ if (!mCharacterMap) {
+ ReadCMAP();
+ NS_ASSERTION(mCharacterMap, "failed to initialize character map");
+ }
+ return mHasCmapTable;
+ }
+
+ inline bool HasCharacter(uint32_t ch) {
+ if (mCharacterMap && mCharacterMap->test(ch)) {
+ return true;
+ }
+ return TestCharacterMap(ch);
+ }
+
+ virtual bool SkipDuringSystemFallback() { return false; }
+ nsresult InitializeUVSMap();
+ 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(gfxFont* aFont);
+ bool HasSVGGlyph(uint32_t aGlyphId);
+ bool GetSVGGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphId,
+ gfxRect *aResult);
+ bool 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 GetColorLayersInfo(uint32_t aGlyphId,
+ const mozilla::gfx::Color& aDefaultColor,
+ nsTArray<uint16_t>& layerGlyphs,
+ nsTArray<mozilla::gfx::Color>& layerColors);
+
+ virtual bool MatchesGenericFamily(const nsACString& aGeneric) const {
+ return true;
+ }
+ virtual bool SupportsLangGroup(nsIAtom *aLangGroup) const {
+ return true;
+ }
+
+ // 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() {
+ if (mBlob) {
+ 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;
+ };
+
+ already_AddRefed<gfxFont>
+ FindOrMakeFont(const gfxFontStyle *aStyle,
+ bool aNeedsBold,
+ 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.
+
+ // Get HarfBuzz face corresponding to this font file.
+ // Caller must release with hb_face_destroy() when finished with it,
+ // and the font entry will be notified via ForgetHBFace.
+ hb_face_t* GetHBFace();
+ virtual void ForgetHBFace();
+
+ // Get Graphite face corresponding to this font file.
+ // Caller must call gfxFontEntry::ReleaseGrFace when finished with it.
+ gr_face* GetGrFace();
+ virtual void ReleaseGrFace(gr_face* aFace);
+
+ // Does the font have graphite contextuals that involve the space glyph
+ // (and therefore we should bypass the word cache)?
+ bool 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;
+ hb_tag_t tags[3]; // one or two OpenType script tags to check,
+ // plus a NULL terminator
+ };
+
+ bool SupportsScriptInGSUB(const hb_tag_t* aScriptTags);
+
+ nsString mName;
+ nsString mFamilyName;
+
+ uint8_t mStyle : 2; // italic/oblique
+ bool mFixedPitch : 1;
+ bool mIsValid : 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 mSymbolFont : 1;
+ bool mIgnoreGDEF : 1;
+ bool mIgnoreGSUB : 1;
+ bool mSVGInitialized : 1;
+ bool mHasSpaceFeaturesInitialized : 1;
+ bool mHasSpaceFeatures : 1;
+ bool mHasSpaceFeaturesKerning : 1;
+ bool mHasSpaceFeaturesNonKerning : 1;
+ bool mSkipDefaultFeatureSpaceCheck : 1;
+ bool mGraphiteSpaceContextualsInitialized : 1;
+ bool mHasGraphiteSpaceContextuals : 1;
+ bool mSpaceGlyphIsInvisible : 1;
+ bool mSpaceGlyphIsInvisibleInitialized : 1;
+ bool mHasGraphiteTables : 1;
+ bool mCheckedForGraphiteTables : 1;
+ bool mHasCmapTable : 1;
+ bool mGrFaceInitialized : 1;
+ bool mCheckedForColorGlyph : 1;
+
+ // 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];
+
+ uint16_t mWeight;
+ int16_t mStretch;
+
+ RefPtr<gfxCharacterMap> mCharacterMap;
+ uint32_t mUVSOffset;
+ mozilla::UniquePtr<uint8_t[]> mUVSData;
+ mozilla::UniquePtr<gfxUserFontData> mUserFontData;
+ mozilla::UniquePtr<gfxSVGGlyphs> mSVGGlyphs;
+ // list of gfxFonts that are using SVG glyphs
+ nsTArray<gfxFont*> mFontsUsingSVGGlyphs;
+ nsTArray<gfxFontFeature> mFeatureSettings;
+ mozilla::UniquePtr<nsDataHashtable<nsUint32HashKey,bool>> mSupportedFeatures;
+ mozilla::UniquePtr<nsDataHashtable<nsUint32HashKey,hb_set_t*>> mFeatureInputs;
+ uint32_t mLanguageOverride;
+
+ // Color Layer font support
+ hb_blob_t* mCOLR;
+ hb_blob_t* mCPAL;
+
+protected:
+ friend class gfxPlatformFontList;
+ friend class gfxMacPlatformFontList;
+ friend class gfxUserFcFontEntry;
+ friend class gfxFontFamily;
+ friend class gfxSingleFaceMacFontFamily;
+ friend class gfxUserFontEntry;
+
+ gfxFontEntry();
+
+ // Protected destructor, to discourage deletion outside of Release():
+ virtual ~gfxFontEntry();
+
+ virtual gfxFont *CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold) {
+ NS_NOTREACHED("oops, somebody didn't override CreateFontInstance");
+ return nullptr;
+ }
+
+ virtual void CheckForGraphiteTables();
+
+ // 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) {
+ NS_NOTREACHED("forgot to override either GetFontTable or CopyFontTable?");
+ return NS_ERROR_FAILURE;
+ }
+
+ // lookup the cmap in cached font data
+ virtual already_AddRefed<gfxCharacterMap>
+ GetCMAPFromFontInfo(FontInfoData *aFontInfoData,
+ uint32_t& aUVSOffset,
+ bool& aSymbolFont);
+
+ // helper for HasCharacter(), which is what client code should call
+ virtual bool TestCharacterMap(uint32_t aCh);
+
+ // Font's unitsPerEm from the 'head' table, if available (will be set to
+ // kInvalidUPEM for non-sfnt font formats)
+ uint16_t mUnitsPerEm;
+
+ // 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.
+ 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);
+
+ // 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.
+ gr_face* mGrFace;
+
+ // hashtable to map raw table data ptr back to its owning blob, for use by
+ // graphite table-release callback
+ nsDataHashtable<nsPtrHashKey<const void>,void*>* mGrTableMap;
+
+ // number of current users of this entry's mGrFace
+ nsrefcnt mGrFaceRefCnt;
+
+ static const void* GrGetTable(const void *aAppFaceHandle,
+ unsigned int aName,
+ size_t *aLen);
+ static void GrReleaseTable(const void *aAppFaceHandle,
+ const void *aTableBuffer);
+
+ // 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;
+
+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(mozilla::Move(toMove))
+ , mSharedBlobData(mozilla::Move(toMove.mSharedBlobData))
+ , mBlob(mozilla::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;
+ };
+
+ mozilla::UniquePtr<nsTHashtable<FontTableHashEntry> > mFontTableCache;
+
+ gfxFontEntry(const gfxFontEntry&);
+ gfxFontEntry& operator=(const gfxFontEntry&);
+};
+
+
+// used when iterating over all fonts looking for a match for a given character
+struct GlobalFontMatch {
+ GlobalFontMatch(const uint32_t aCharacter,
+ mozilla::unicode::Script aRunScript,
+ const gfxFontStyle *aStyle) :
+ mCh(aCharacter), mRunScript(aRunScript), mStyle(aStyle),
+ mMatchRank(0), mCount(0), mCmapsTested(0)
+ {
+
+ }
+
+ const uint32_t mCh; // codepoint to be matched
+ mozilla::unicode::Script mRunScript; // Unicode script for the codepoint
+ const gfxFontStyle* mStyle; // style to match
+ int32_t mMatchRank; // metric indicating closest match
+ RefPtr<gfxFontEntry> mBestMatch; // current best match
+ RefPtr<gfxFontFamily> mMatchedFamily; // the family it belongs to
+ uint32_t mCount; // number of fonts matched
+ uint32_t mCmapsTested; // number of cmaps tested
+};
+
+class gfxFontFamily {
+public:
+ NS_INLINE_DECL_REFCOUNTING(gfxFontFamily)
+
+ explicit gfxFontFamily(const nsAString& aName) :
+ mName(aName),
+ mOtherFamilyNamesInitialized(false),
+ mHasOtherFamilyNames(false),
+ mFaceNamesInitialized(false),
+ mHasStyles(false),
+ mIsSimpleFamily(false),
+ mIsBadUnderlineFamily(false),
+ mFamilyCharacterMapInitialized(false),
+ mSkipDefaultFeatureSpaceCheck(false),
+ mCheckForFallbackFaces(false)
+ { }
+
+ const nsString& Name() { return mName; }
+
+ virtual void LocalizedName(nsAString& aLocalizedName);
+ virtual bool HasOtherFamilyNames();
+
+ nsTArray<RefPtr<gfxFontEntry> >& GetFontList() { return mAvailableFonts; }
+
+ void AddFontEntry(RefPtr<gfxFontEntry> aFontEntry) {
+ // 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);
+ }
+
+ // note that the styles for this family have been added
+ bool HasStyles() { return mHasStyles; }
+ void SetHasStyles(bool aHasStyles) { mHasStyles = aHasStyles; }
+
+ // 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).
+ // aNeedsSyntheticBold is set to true when synthetic bolding is
+ // needed, false otherwise
+ gfxFontEntry *FindFontForStyle(const gfxFontStyle& aFontStyle,
+ bool& aNeedsSyntheticBold);
+
+ virtual void
+ FindAllFontsForStyle(const gfxFontStyle& aFontStyle,
+ nsTArray<gfxFontEntry*>& aFontEntryList,
+ bool& aNeedsSyntheticBold);
+
+ // checks for a matching font within the family
+ // used as part of the font fallback process
+ 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);
+
+ // helper method for reading localized family names from the name table
+ // of a single face
+ static void ReadOtherFamilyNamesForFace(const nsAString& aFamilyName,
+ const char *aNameData,
+ uint32_t aDataLength,
+ nsTArray<nsString>& aOtherFamilyNames,
+ bool useFullName);
+
+ // 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
+ virtual void ReadFaceNames(gfxPlatformFontList *aPlatformFontList,
+ bool aNeedFullnamePostscriptNames,
+ FontInfoData *aFontInfoData = nullptr);
+
+ // find faces belonging to this family (platform implementations override this;
+ // should be made pure virtual once all subclasses have been updated)
+ virtual void FindStyleVariations(FontInfoData *aFontInfoData = nullptr) { }
+
+ // search for a specific face using the Postscript name
+ gfxFontEntry* FindFont(const nsAString& aPostscriptName);
+
+ // read in cmaps for all the faces
+ void ReadAllCMAPs(FontInfoData *aFontInfoData = nullptr);
+
+ bool TestCharacterMap(uint32_t aCh) {
+ if (!mFamilyCharacterMapInitialized) {
+ ReadAllCMAPs();
+ }
+ return mFamilyCharacterMap.test(aCh);
+ }
+
+ void ResetCharacterMap() {
+ mFamilyCharacterMap.reset();
+ mFamilyCharacterMapInitialized = false;
+ }
+
+ // mark this family as being in the "bad" underline offset blacklist
+ void SetBadUnderlineFamily() {
+ mIsBadUnderlineFamily = true;
+ if (mHasStyles) {
+ SetBadUnderlineFonts();
+ }
+ }
+
+ bool IsBadUnderlineFamily() const { return mIsBadUnderlineFamily; }
+ bool CheckForFallbackFaces() const { return mCheckForFallbackFaces; }
+
+ // sort available fonts to put preferred (standard) faces towards the end
+ void SortAvailableFonts();
+
+ // 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();
+
+ // 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;
+ }
+
+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 blacklist.
+ void SetBadUnderlineFonts() {
+ uint32_t i, numFonts = mAvailableFonts.Length();
+ for (i = 0; i < numFonts; i++) {
+ if (mAvailableFonts[i]) {
+ mAvailableFonts[i]->mIsBadUnderlineFont = true;
+ }
+ }
+ }
+
+ nsString mName;
+ nsTArray<RefPtr<gfxFontEntry> > mAvailableFonts;
+ gfxSparseBitSet mFamilyCharacterMap;
+ bool mOtherFamilyNamesInitialized : 1;
+ bool mHasOtherFamilyNames : 1;
+ bool mFaceNamesInitialized : 1;
+ bool mHasStyles : 1;
+ bool mIsSimpleFamily : 1;
+ bool mIsBadUnderlineFamily : 1;
+ bool mFamilyCharacterMapInitialized : 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
+ };
+};
+
+#endif
diff --git a/gfx/thebes/gfxFontFamilyList.h b/gfx/thebes/gfxFontFamilyList.h
new file mode 100644
index 000000000..e240102e0
--- /dev/null
+++ b/gfx/thebes/gfxFontFamilyList.h
@@ -0,0 +1,364 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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_FAMILY_LIST_H
+#define GFX_FONT_FAMILY_LIST_H
+
+#include "nsDebug.h"
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+#include "nsUnicharUtils.h"
+#include "nsTArray.h"
+#include "mozilla/MemoryReporting.h"
+
+namespace mozilla {
+
+/**
+ * type of font family name, either a name (e.g. Helvetica) or a
+ * generic (e.g. serif, sans-serif), with the ability to distinguish
+ * between unquoted and quoted names for serializaiton
+ */
+
+enum FontFamilyType : uint32_t {
+ eFamily_none = 0, // used when finding generics
+
+ // explicitly named font family (e.g. Helvetica)
+ eFamily_named,
+ eFamily_named_quoted,
+
+ // generics
+ eFamily_serif, // pref font code relies on this ordering!!!
+ eFamily_sans_serif,
+ eFamily_monospace,
+ eFamily_cursive,
+ eFamily_fantasy,
+
+ // special
+ eFamily_moz_variable,
+ eFamily_moz_fixed,
+
+ eFamily_generic_first = eFamily_serif,
+ eFamily_generic_last = eFamily_fantasy,
+ eFamily_generic_count = (eFamily_fantasy - eFamily_serif + 1)
+};
+
+enum QuotedName { eQuotedName, eUnquotedName };
+
+/**
+ * font family name, a string for the name if not a generic and
+ * a font type indicated named family or which generic family
+ */
+
+struct FontFamilyName final {
+ FontFamilyName()
+ : mType(eFamily_named)
+ {}
+
+ // named font family - e.g. Helvetica
+ explicit FontFamilyName(const nsAString& aFamilyName,
+ QuotedName aQuoted = eUnquotedName) {
+ mType = (aQuoted == eQuotedName) ? eFamily_named_quoted : eFamily_named;
+ mName = aFamilyName;
+ }
+
+ // generic font family - e.g. sans-serif
+ explicit FontFamilyName(FontFamilyType aType) {
+ NS_ASSERTION(aType != eFamily_named &&
+ aType != eFamily_named_quoted &&
+ aType != eFamily_none,
+ "expected a generic font type");
+ mName.Truncate();
+ mType = aType;
+ }
+
+ FontFamilyName(const FontFamilyName& aCopy) {
+ mType = aCopy.mType;
+ mName = aCopy.mName;
+ }
+
+ bool IsNamed() const {
+ return mType == eFamily_named || mType == eFamily_named_quoted;
+ }
+
+ bool IsGeneric() const {
+ return !IsNamed();
+ }
+
+ void AppendToString(nsAString& aFamilyList, bool aQuotes = true) const {
+ switch (mType) {
+ case eFamily_named:
+ aFamilyList.Append(mName);
+ break;
+ case eFamily_named_quoted:
+ if (aQuotes) {
+ aFamilyList.Append('"');
+ }
+ aFamilyList.Append(mName);
+ if (aQuotes) {
+ aFamilyList.Append('"');
+ }
+ break;
+ case eFamily_serif:
+ aFamilyList.AppendLiteral("serif");
+ break;
+ case eFamily_sans_serif:
+ aFamilyList.AppendLiteral("sans-serif");
+ break;
+ case eFamily_monospace:
+ aFamilyList.AppendLiteral("monospace");
+ break;
+ case eFamily_cursive:
+ aFamilyList.AppendLiteral("cursive");
+ break;
+ case eFamily_fantasy:
+ aFamilyList.AppendLiteral("fantasy");
+ break;
+ case eFamily_moz_fixed:
+ aFamilyList.AppendLiteral("-moz-fixed");
+ break;
+ default:
+ break;
+ }
+ }
+
+ // helper method that converts generic names to the right enum value
+ static FontFamilyName
+ Convert(const nsAString& aFamilyOrGenericName) {
+ // should only be passed a single font - not entirely correct, a family
+ // *could* have a comma in it but in practice never does so
+ // for debug purposes this is fine
+ NS_ASSERTION(aFamilyOrGenericName.FindChar(',') == -1,
+ "Convert method should only be passed a single family name");
+
+ FontFamilyType genericType = eFamily_none;
+ if (aFamilyOrGenericName.LowerCaseEqualsLiteral("serif")) {
+ genericType = eFamily_serif;
+ } else if (aFamilyOrGenericName.LowerCaseEqualsLiteral("sans-serif")) {
+ genericType = eFamily_sans_serif;
+ } else if (aFamilyOrGenericName.LowerCaseEqualsLiteral("monospace")) {
+ genericType = eFamily_monospace;
+ } else if (aFamilyOrGenericName.LowerCaseEqualsLiteral("cursive")) {
+ genericType = eFamily_cursive;
+ } else if (aFamilyOrGenericName.LowerCaseEqualsLiteral("fantasy")) {
+ genericType = eFamily_fantasy;
+ } else if (aFamilyOrGenericName.LowerCaseEqualsLiteral("-moz-fixed")) {
+ genericType = eFamily_moz_fixed;
+ } else {
+ return FontFamilyName(aFamilyOrGenericName, eUnquotedName);
+ }
+
+ return FontFamilyName(genericType);
+ }
+
+ // memory reporting
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ }
+
+ FontFamilyType mType;
+ nsString mName; // empty if mType != eFamily_named
+};
+
+inline bool
+operator==(const FontFamilyName& a, const FontFamilyName& b) {
+ return a.mType == b.mType && a.mName == b.mName;
+}
+
+/**
+ * font family list, array of font families and a default font type.
+ * font family names are either named strings or generics. the default
+ * font type is used to preserve the variable font fallback behavior
+ */
+
+class FontFamilyList {
+public:
+ FontFamilyList()
+ : mDefaultFontType(eFamily_none)
+ {
+ }
+
+ explicit FontFamilyList(FontFamilyType aGenericType)
+ : mDefaultFontType(eFamily_none)
+ {
+ Append(FontFamilyName(aGenericType));
+ }
+
+ FontFamilyList(const nsAString& aFamilyName,
+ QuotedName aQuoted)
+ : mDefaultFontType(eFamily_none)
+ {
+ Append(FontFamilyName(aFamilyName, aQuoted));
+ }
+
+ FontFamilyList(const FontFamilyList& aOther)
+ : mFontlist(aOther.mFontlist)
+ , mDefaultFontType(aOther.mDefaultFontType)
+ {
+ }
+
+ void Append(const FontFamilyName& aFamilyName) {
+ mFontlist.AppendElement(aFamilyName);
+ }
+
+ void Append(const nsTArray<nsString>& aFamilyNameList) {
+ uint32_t len = aFamilyNameList.Length();
+ for (uint32_t i = 0; i < len; i++) {
+ mFontlist.AppendElement(FontFamilyName(aFamilyNameList[i],
+ eUnquotedName));
+ }
+ }
+
+ void Clear() {
+ mFontlist.Clear();
+ }
+
+ uint32_t Length() const {
+ return mFontlist.Length();
+ }
+
+ bool IsEmpty() const {
+ return mFontlist.IsEmpty();
+ }
+
+ const nsTArray<FontFamilyName>& GetFontlist() const {
+ return mFontlist;
+ }
+
+ bool Equals(const FontFamilyList& aFontlist) const {
+ return mFontlist == aFontlist.mFontlist &&
+ mDefaultFontType == aFontlist.mDefaultFontType;
+ }
+
+ FontFamilyType FirstGeneric() const {
+ uint32_t len = mFontlist.Length();
+ for (uint32_t i = 0; i < len; i++) {
+ const FontFamilyName& name = mFontlist[i];
+ if (name.IsGeneric()) {
+ return name.mType;
+ }
+ }
+ return eFamily_none;
+ }
+
+ bool HasGeneric() const {
+ return FirstGeneric() != eFamily_none;
+ }
+
+ bool HasDefaultGeneric() const {
+ uint32_t len = mFontlist.Length();
+ for (uint32_t i = 0; i < len; i++) {
+ const FontFamilyName& name = mFontlist[i];
+ if (name.mType == mDefaultFontType) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Find the first generic (but ignoring cursive and fantasy, as they are
+ // rarely configured in any useful way) in the list.
+ // If found, move it to the start and return true; else return false.
+ bool PrioritizeFirstGeneric() {
+ uint32_t len = mFontlist.Length();
+ for (uint32_t i = 0; i < len; i++) {
+ const FontFamilyName name = mFontlist[i];
+ if (name.IsGeneric()) {
+ if (name.mType == eFamily_cursive ||
+ name.mType == eFamily_fantasy) {
+ continue;
+ }
+ if (i > 0) {
+ mFontlist.RemoveElementAt(i);
+ mFontlist.InsertElementAt(0, name);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void PrependGeneric(FontFamilyType aType) {
+ mFontlist.InsertElementAt(0, FontFamilyName(aType));
+ }
+
+ void ToString(nsAString& aFamilyList,
+ bool aQuotes = true,
+ bool aIncludeDefault = false) const {
+ aFamilyList.Truncate();
+ uint32_t len = mFontlist.Length();
+ for (uint32_t i = 0; i < len; i++) {
+ if (i != 0) {
+ aFamilyList.Append(',');
+ }
+ const FontFamilyName& name = mFontlist[i];
+ name.AppendToString(aFamilyList, aQuotes);
+ }
+ if (aIncludeDefault && mDefaultFontType != eFamily_none) {
+ if (!aFamilyList.IsEmpty()) {
+ aFamilyList.Append(',');
+ }
+ if (mDefaultFontType == eFamily_serif) {
+ aFamilyList.AppendLiteral("serif");
+ } else {
+ aFamilyList.AppendLiteral("sans-serif");
+ }
+ }
+ }
+
+ // searches for a specific non-generic name, lowercase comparison
+ bool Contains(const nsAString& aFamilyName) const {
+ uint32_t len = mFontlist.Length();
+ nsAutoString fam(aFamilyName);
+ ToLowerCase(fam);
+ for (uint32_t i = 0; i < len; i++) {
+ const FontFamilyName& name = mFontlist[i];
+ if (name.mType != eFamily_named &&
+ name.mType != eFamily_named_quoted) {
+ continue;
+ }
+ nsAutoString listname(name.mName);
+ ToLowerCase(listname);
+ if (listname.Equals(fam)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ FontFamilyType GetDefaultFontType() const { return mDefaultFontType; }
+ void SetDefaultFontType(FontFamilyType aType) {
+ NS_ASSERTION(aType == eFamily_none || aType == eFamily_serif ||
+ aType == eFamily_sans_serif,
+ "default font type must be either serif or sans-serif");
+ mDefaultFontType = aType;
+ }
+
+ // memory reporting
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+ n += mFontlist.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (size_t i = 0; i < mFontlist.Length(); i++) {
+ n += mFontlist[i].SizeOfExcludingThis(aMallocSizeOf);
+ }
+ return n;
+ }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+private:
+ nsTArray<FontFamilyName> mFontlist;
+ FontFamilyType mDefaultFontType; // none, serif or sans-serif
+};
+
+inline bool
+operator==(const FontFamilyList& a, const FontFamilyList& b) {
+ return a.Equals(b);
+}
+
+} // namespace mozilla
+
+#endif /* GFX_FONT_FAMILY_LIST_H */
diff --git a/gfx/thebes/gfxFontFeatures.cpp b/gfx/thebes/gfxFontFeatures.cpp
new file mode 100644
index 000000000..dcc5e369a
--- /dev/null
+++ b/gfx/thebes/gfxFontFeatures.cpp
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "nsUnicharUtils.h"
+#include "nsHashKeys.h"
+
+using namespace mozilla;
+
+gfxFontFeatureValueSet::gfxFontFeatureValueSet()
+ : mFontFeatureValues(8)
+{
+}
+
+bool
+gfxFontFeatureValueSet::GetFontFeatureValuesFor(const nsAString& aFamily,
+ uint32_t aVariantProperty,
+ const nsAString& aName,
+ nsTArray<uint32_t>& aValues)
+{
+ nsAutoString family(aFamily), name(aName);
+ ToLowerCase(family);
+ ToLowerCase(name);
+ FeatureValueHashKey key(family, aVariantProperty, name);
+
+ aValues.Clear();
+ FeatureValueHashEntry *entry = mFontFeatureValues.GetEntry(key);
+ if (entry) {
+ NS_ASSERTION(entry->mValues.Length() > 0,
+ "null array of font feature values");
+ aValues.AppendElements(entry->mValues);
+ return true;
+ }
+
+ return false;
+}
+
+
+void
+gfxFontFeatureValueSet::AddFontFeatureValues(const nsAString& aFamily,
+ const nsTArray<gfxFontFeatureValueSet::FeatureValues>& aValues)
+{
+ nsAutoString family(aFamily);
+ ToLowerCase(family);
+
+ uint32_t i, numFeatureValues = aValues.Length();
+ for (i = 0; i < numFeatureValues; i++) {
+ const FeatureValues& fv = aValues.ElementAt(i);
+ uint32_t alternate = fv.alternate;
+ uint32_t j, numValues = fv.valuelist.Length();
+ for (j = 0; j < numValues; j++) {
+ const ValueList& v = fv.valuelist.ElementAt(j);
+ nsAutoString name(v.name);
+ ToLowerCase(name);
+ FeatureValueHashKey key(family, alternate, name);
+ FeatureValueHashEntry *entry = mFontFeatureValues.PutEntry(key);
+ entry->mKey = key;
+ entry->mValues = v.featureSelectors;
+ }
+ }
+}
+
+bool
+gfxFontFeatureValueSet::FeatureValueHashEntry::KeyEquals(
+ const KeyTypePointer aKey) const
+{
+ return aKey->mPropVal == mKey.mPropVal &&
+ aKey->mFamily.Equals(mKey.mFamily) &&
+ aKey->mName.Equals(mKey.mName);
+}
+
+PLDHashNumber
+gfxFontFeatureValueSet::FeatureValueHashEntry::HashKey(
+ const KeyTypePointer aKey)
+{
+ return HashString(aKey->mFamily) + HashString(aKey->mName) +
+ aKey->mPropVal * uint32_t(0xdeadbeef);
+}
+
diff --git a/gfx/thebes/gfxFontFeatures.h b/gfx/thebes/gfxFontFeatures.h
new file mode 100644
index 000000000..121a001b2
--- /dev/null
+++ b/gfx/thebes/gfxFontFeatures.h
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* 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 "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);
+}
+
+struct gfxAlternateValue {
+ uint32_t alternate; // constants in gfxFontConstants.h
+ nsString value; // string value to be looked up
+};
+
+inline bool
+operator<(const gfxAlternateValue& a, const gfxAlternateValue& b)
+{
+ return (a.alternate < b.alternate) ||
+ ((a.alternate == b.alternate) && (a.value < b.value));
+}
+
+inline bool
+operator==(const gfxAlternateValue& a, const gfxAlternateValue& b)
+{
+ return (a.alternate == b.alternate) && (a.value == b.value);
+}
+
+class gfxFontFeatureValueSet final {
+public:
+ NS_INLINE_DECL_REFCOUNTING(gfxFontFeatureValueSet)
+
+ gfxFontFeatureValueSet();
+
+ struct ValueList {
+ ValueList(const nsAString& aName, const nsTArray<uint32_t>& aSelectors)
+ : name(aName), featureSelectors(aSelectors)
+ {}
+ nsString name;
+ nsTArray<uint32_t> featureSelectors;
+ };
+
+ struct FeatureValues {
+ uint32_t alternate;
+ nsTArray<ValueList> valuelist;
+ };
+
+ // returns true if found, false otherwise
+ bool
+ GetFontFeatureValuesFor(const nsAString& aFamily,
+ uint32_t aVariantProperty,
+ const nsAString& aName,
+ nsTArray<uint32_t>& aValues);
+ void
+ AddFontFeatureValues(const nsAString& aFamily,
+ const nsTArray<gfxFontFeatureValueSet::FeatureValues>& aValues);
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~gfxFontFeatureValueSet() {}
+
+ struct FeatureValueHashKey {
+ nsString mFamily;
+ uint32_t mPropVal;
+ nsString mName;
+
+ FeatureValueHashKey()
+ : mPropVal(0)
+ { }
+ FeatureValueHashKey(const nsAString& aFamily,
+ uint32_t aPropVal,
+ const nsAString& aName)
+ : mFamily(aFamily), mPropVal(aPropVal), mName(aName)
+ { }
+ FeatureValueHashKey(const FeatureValueHashKey& aKey)
+ : mFamily(aKey.mFamily), mPropVal(aKey.mPropVal), mName(aKey.mName)
+ { }
+ };
+
+ class FeatureValueHashEntry : public PLDHashEntryHdr {
+ public:
+ typedef const FeatureValueHashKey &KeyType;
+ typedef const FeatureValueHashKey *KeyTypePointer;
+
+ explicit FeatureValueHashEntry(KeyTypePointer aKey) { }
+ FeatureValueHashEntry(const FeatureValueHashEntry& toCopy)
+ {
+ NS_ERROR("Should not be called");
+ }
+ ~FeatureValueHashEntry() { }
+
+ 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 000000000..a53c96369
--- /dev/null
+++ b/gfx/thebes/gfxFontInfoLoader.cpp
@@ -0,0 +1,281 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsCRT.h"
+#include "nsIObserverService.h"
+#include "nsThreadUtils.h" // for nsRunnable
+#include "gfxPlatformFontList.h"
+
+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 " <<
+ NS_ConvertUTF16toUTF8(mFontFamiliesToLoad[i]).get();
+ }
+ }
+
+ mLoadTime = TimeStamp::Now() - start;
+}
+
+class FontInfoLoadCompleteEvent : public Runnable {
+ virtual ~FontInfoLoadCompleteEvent() {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ explicit FontInfoLoadCompleteEvent(FontInfoData *aFontInfo) :
+ mFontInfo(aFontInfo)
+ {}
+
+ NS_IMETHOD Run() override;
+
+ RefPtr<FontInfoData> mFontInfo;
+};
+
+class AsyncFontInfoLoader : public Runnable {
+ virtual ~AsyncFontInfoLoader() {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ explicit AsyncFontInfoLoader(FontInfoData *aFontInfo) :
+ mFontInfo(aFontInfo)
+ {
+ mCompleteEvent = new FontInfoLoadCompleteEvent(aFontInfo);
+ }
+
+ NS_IMETHOD Run() override;
+
+ RefPtr<FontInfoData> mFontInfo;
+ RefPtr<FontInfoLoadCompleteEvent> mCompleteEvent;
+};
+
+class ShutdownThreadEvent : public Runnable {
+ virtual ~ShutdownThreadEvent() {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ explicit ShutdownThreadEvent(nsIThread* aThread) : mThread(aThread) {}
+ NS_IMETHOD Run() override {
+ mThread->Shutdown();
+ return NS_OK;
+ }
+ nsCOMPtr<nsIThread> mThread;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED0(ShutdownThreadEvent, Runnable);
+
+// 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;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(FontInfoLoadCompleteEvent, Runnable);
+
+// 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_INHERITED0(AsyncFontInfoLoader, Runnable);
+
+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")) {
+ mLoader->CancelLoader();
+ } else {
+ NS_NOTREACHED("unexpected notification topic");
+ }
+ return NS_OK;
+}
+
+void
+gfxFontInfoLoader::StartLoader(uint32_t aDelay, uint32_t aInterval)
+{
+ mInterval = aInterval;
+
+ NS_ASSERTION(!mFontInfo,
+ "fontinfo should be null when starting font loader");
+
+ // sanity check
+ if (mState != stateInitial &&
+ mState != stateTimerOff &&
+ mState != stateTimerOnDelay) {
+ CancelLoader();
+ }
+
+ // set up timer
+ if (!mTimer) {
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (!mTimer) {
+ NS_WARNING("Failure to create font info loader timer");
+ return;
+ }
+ }
+
+ AddShutdownObserver();
+
+ // delay? ==> start async thread after a delay
+ if (aDelay) {
+ mState = stateTimerOnDelay;
+ mTimer->InitWithFuncCallback(DelayedStartCallback, this, aDelay,
+ nsITimer::TYPE_ONE_SHOT);
+ return;
+ }
+
+ mFontInfo = CreateFontInfoData();
+
+ // initialize
+ InitLoader();
+
+ // start async load
+ nsresult rv = NS_NewNamedThread("Font Loader",
+ getter_AddRefs(mFontLoaderThread),
+ nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ 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()));
+ }
+}
+
+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;
+
+ // try to load all font data immediately
+ if (LoadFontInfo()) {
+ CancelLoader();
+ return;
+ }
+
+ // not all work completed ==> run load on interval
+ mState = stateTimerOnInterval;
+ mTimer->InitWithFuncCallback(LoadFontInfoCallback, this, mInterval,
+ nsITimer::TYPE_REPEATING_SLACK);
+}
+
+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();
+}
+
+void
+gfxFontInfoLoader::LoadFontInfoTimerFire()
+{
+ if (mState == stateTimerOnDelay) {
+ mState = stateTimerOnInterval;
+ mTimer->SetDelay(mInterval);
+ }
+
+ bool done = LoadFontInfo();
+ if (done) {
+ CancelLoader();
+ }
+}
+
+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);
+ }
+}
+
+void
+gfxFontInfoLoader::RemoveShutdownObserver()
+{
+ if (mObserver) {
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(mObserver, "quit-application");
+ mObserver = nullptr;
+ }
+ }
+}
diff --git a/gfx/thebes/gfxFontInfoLoader.h b/gfx/thebes/gfxFontInfoLoader.h
new file mode 100644
index 000000000..40b655aa5
--- /dev/null
+++ b/gfx/thebes/gfxFontInfoLoader.h
@@ -0,0 +1,258 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "nsRefPtrHashtable.h"
+#include "nsString.h"
+#include "gfxFont.h"
+#include "nsIRunnable.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/TimeStamp.h"
+#include "nsISupportsImpl.h"
+
+// data retrieved for a given face
+
+struct FontFaceData {
+ FontFaceData() : mUVSOffset(0), mSymbolFont(false) {}
+
+ FontFaceData(const FontFaceData& aFontFaceData) {
+ mFullName = aFontFaceData.mFullName;
+ mPostscriptName = aFontFaceData.mPostscriptName;
+ mCharacterMap = aFontFaceData.mCharacterMap;
+ mUVSOffset = aFontFaceData.mUVSOffset;
+ mSymbolFont = aFontFaceData.mSymbolFont;
+ }
+
+ nsString mFullName;
+ nsString mPostscriptName;
+ RefPtr<gfxCharacterMap> mCharacterMap;
+ uint32_t mUVSOffset;
+ bool mSymbolFont;
+};
+
+// 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():
+ virtual ~FontInfoData() {
+ MOZ_COUNT_DTOR(FontInfoData);
+ }
+
+public:
+ virtual void Load();
+
+ // loads font data for all fonts of a given family
+ // (called on async thread)
+ virtual void LoadFontFamilyData(const nsAString& 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 nsAString& aFontName,
+ uint32_t& aUVSOffset,
+ bool& aSymbolFont)
+ {
+ FontFaceData faceData;
+ if (!mFontFaceData.Get(aFontName, &faceData) ||
+ !faceData.mCharacterMap) {
+ return nullptr;
+ }
+
+ aUVSOffset = faceData.mUVSOffset;
+ aSymbolFont = faceData.mSymbolFont;
+ RefPtr<gfxCharacterMap> cmap = faceData.mCharacterMap;
+ return cmap.forget();
+ }
+
+ // fetches fullname/postscript names from cached font data
+ virtual void GetFaceNames(const nsAString& aFontName,
+ nsAString& aFullName,
+ nsAString& aPostscriptName)
+ {
+ FontFaceData faceData;
+ if (!mFontFaceData.Get(aFontName, &faceData)) {
+ return;
+ }
+
+ aFullName = faceData.mFullName;
+ aPostscriptName = faceData.mPostscriptName;
+ }
+
+ // fetches localized family name data from cached font data
+ virtual bool GetOtherFamilyNames(const nsAString& aFamilyName,
+ nsTArray<nsString>& aOtherFamilyNames)
+ {
+ return mOtherFamilyNames.Get(aFamilyName, &aOtherFamilyNames);
+ }
+
+ nsTArray<nsString> 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
+ nsDataHashtable<nsStringHashKey, FontFaceData> mFontFaceData;
+
+ // canonical family name ==> array of localized family names
+ nsDataHashtable<nsStringHashKey, nsTArray<nsString> > 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 regular
+// intervals to prevent tying up the main thread
+
+class gfxFontInfoLoader {
+public:
+
+ // state transitions:
+ // initial ---StartLoader with delay---> timer on delay
+ // initial ---StartLoader without delay---> timer on interval
+ // timer on delay ---LoaderTimerFire---> timer on interval
+ // timer on delay ---CancelLoader---> timer off
+ // timer on interval ---CancelLoader---> timer off
+ // timer off ---StartLoader with delay---> timer on delay
+ // timer off ---StartLoader without delay---> timer on interval
+ typedef enum {
+ stateInitial,
+ stateTimerOnDelay,
+ stateAsyncLoad,
+ stateTimerOnInterval,
+ stateTimerOff
+ } TimerState;
+
+ gfxFontInfoLoader() :
+ mInterval(0), mState(stateInitial)
+ {
+ MOZ_COUNT_CTOR(gfxFontInfoLoader);
+ }
+
+ virtual ~gfxFontInfoLoader();
+
+ // start timer with an initial delay, then call Run method at regular intervals
+ void StartLoader(uint32_t aDelay, uint32_t aInterval);
+
+ // Finalize - async load complete, transfer data (on intervals if necessary)
+ virtual void FinalizeLoader(FontInfoData *aFontInfo);
+
+ // cancel the timer and cleanup
+ void CancelLoader();
+
+ uint32_t GetInterval() { return mInterval; }
+
+protected:
+ class ShutdownObserver : public nsIObserver
+ {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ explicit ShutdownObserver(gfxFontInfoLoader *aLoader)
+ : mLoader(aLoader)
+ { }
+
+ protected:
+ virtual ~ShutdownObserver()
+ { }
+
+ 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;
+ }
+
+ // Timer interval callbacks
+ static void LoadFontInfoCallback(nsITimer *aTimer, void *aThis) {
+ gfxFontInfoLoader *loader = static_cast<gfxFontInfoLoader*>(aThis);
+ loader->LoadFontInfoTimerFire();
+ }
+
+ static void DelayedStartCallback(nsITimer *aTimer, void *aThis) {
+ gfxFontInfoLoader *loader = static_cast<gfxFontInfoLoader*>(aThis);
+ loader->StartLoader(0, loader->GetInterval());
+ }
+
+ void LoadFontInfoTimerFire();
+
+ void AddShutdownObserver();
+ void RemoveShutdownObserver();
+
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsIObserver> mObserver;
+ nsCOMPtr<nsIThread> mFontLoaderThread;
+ uint32_t mInterval;
+ 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 000000000..92ea5e967
--- /dev/null
+++ b/gfx/thebes/gfxFontMissingGlyphs.cpp
@@ -0,0 +1,288 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/RefPtr.h"
+#include "nsDeviceContext.h"
+#include "nsLayoutUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+#define CHAR_BITS(b00, b01, b02, b10, b11, b12, b20, b21, b22, b30, b31, b32, b40, b41, b42) \
+ ((b00 << 0) | (b01 << 1) | (b02 << 2) | (b10 << 3) | (b11 << 4) | (b12 << 5) | \
+ (b20 << 6) | (b21 << 7) | (b22 << 8) | (b30 << 9) | (b31 << 10) | (b32 << 11) | \
+ (b40 << 12) | (b41 << 13) | (b42 << 14))
+
+static const uint16_t glyphMicroFont[16] = {
+ CHAR_BITS(0, 1, 0,
+ 1, 0, 1,
+ 1, 0, 1,
+ 1, 0, 1,
+ 0, 1, 0),
+ CHAR_BITS(0, 1, 0,
+ 0, 1, 0,
+ 0, 1, 0,
+ 0, 1, 0,
+ 0, 1, 0),
+ CHAR_BITS(1, 1, 1,
+ 0, 0, 1,
+ 1, 1, 1,
+ 1, 0, 0,
+ 1, 1, 1),
+ CHAR_BITS(1, 1, 1,
+ 0, 0, 1,
+ 1, 1, 1,
+ 0, 0, 1,
+ 1, 1, 1),
+ CHAR_BITS(1, 0, 1,
+ 1, 0, 1,
+ 1, 1, 1,
+ 0, 0, 1,
+ 0, 0, 1),
+ CHAR_BITS(1, 1, 1,
+ 1, 0, 0,
+ 1, 1, 1,
+ 0, 0, 1,
+ 1, 1, 1),
+ CHAR_BITS(1, 1, 1,
+ 1, 0, 0,
+ 1, 1, 1,
+ 1, 0, 1,
+ 1, 1, 1),
+ CHAR_BITS(1, 1, 1,
+ 0, 0, 1,
+ 0, 0, 1,
+ 0, 0, 1,
+ 0, 0, 1),
+ CHAR_BITS(0, 1, 0,
+ 1, 0, 1,
+ 0, 1, 0,
+ 1, 0, 1,
+ 0, 1, 0),
+ CHAR_BITS(1, 1, 1,
+ 1, 0, 1,
+ 1, 1, 1,
+ 0, 0, 1,
+ 0, 0, 1),
+ CHAR_BITS(1, 1, 1,
+ 1, 0, 1,
+ 1, 1, 1,
+ 1, 0, 1,
+ 1, 0, 1),
+ CHAR_BITS(1, 1, 0,
+ 1, 0, 1,
+ 1, 1, 0,
+ 1, 0, 1,
+ 1, 1, 0),
+ CHAR_BITS(0, 1, 1,
+ 1, 0, 0,
+ 1, 0, 0,
+ 1, 0, 0,
+ 0, 1, 1),
+ CHAR_BITS(1, 1, 0,
+ 1, 0, 1,
+ 1, 0, 1,
+ 1, 0, 1,
+ 1, 1, 0),
+ CHAR_BITS(1, 1, 1,
+ 1, 0, 0,
+ 1, 1, 1,
+ 1, 0, 0,
+ 1, 1, 1),
+ CHAR_BITS(1, 1, 1,
+ 1, 0, 0,
+ 1, 1, 1,
+ 1, 0, 0,
+ 1, 0, 0)
+};
+
+/* 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;
+/**
+ * Draw a single hex character using the current color. A nice way to do this
+ * would be to fill in an A8 image surface and then use it as a mask
+ * to paint the current color. Tragically this doesn't currently work with the
+ * Quartz cairo backend which doesn't generally support masking with surfaces.
+ * So for now we just paint a bunch of rectangles...
+ */
+#ifndef MOZ_GFX_OPTIMIZE_MOBILE
+static void
+DrawHexChar(uint32_t aDigit, const Point& aPt, DrawTarget& aDrawTarget,
+ const Pattern &aPattern)
+{
+ // To avoid the potential for seams showing between rects when we're under
+ // a transform we concat all the rects into a PathBuilder and fill the
+ // resulting Path (rather than using DrawTarget::FillRect).
+ RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
+ uint32_t glyphBits = glyphMicroFont[aDigit];
+ for (int y = 0; y < MINIFONT_HEIGHT; ++y) {
+ for (int x = 0; x < MINIFONT_WIDTH; ++x) {
+ if (glyphBits & 1) {
+ Rect r(aPt.x + x, aPt.y + y, 1, 1);
+ MaybeSnapToDevicePixels(r, aDrawTarget, true);
+ builder->MoveTo(r.TopLeft());
+ builder->LineTo(r.TopRight());
+ builder->LineTo(r.BottomRight());
+ builder->LineTo(r.BottomLeft());
+ builder->Close();
+ }
+ glyphBits >>= 1;
+ }
+ }
+ RefPtr<Path> path = builder->Finish();
+ aDrawTarget.Fill(path, aPattern);
+}
+#endif // MOZ_GFX_OPTIMIZE_MOBILE
+
+void
+gfxFontMissingGlyphs::DrawMissingGlyph(uint32_t aChar,
+ const Rect& aRect,
+ DrawTarget& aDrawTarget,
+ const Pattern& aPattern,
+ uint32_t aAppUnitsPerDevPixel)
+{
+ // If we're currently drawing with some kind of pattern, we just draw the
+ // missing-glyph data in black.
+ ColorPattern color = aPattern.GetType() == PatternType::COLOR ?
+ static_cast<const ColorPattern&>(aPattern) :
+ ColorPattern(ToDeviceColor(Color(0.f, 0.f, 0.f, 1.f)));
+
+ // 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 = aRect.X() + BOX_HORIZONTAL_INSET + halfBorderWidth;
+ Float borderRight = aRect.XMost() - BOX_HORIZONTAL_INSET - halfBorderWidth;
+ Rect borderStrokeRect(borderLeft, aRect.Y() + halfBorderWidth,
+ borderRight - borderLeft,
+ aRect.Height() - 2.0 * halfBorderWidth);
+ if (!borderStrokeRect.IsEmpty()) {
+ ColorPattern adjustedColor = color;
+ color.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
+ Point center = aRect.Center();
+ Float halfGap = HEX_CHAR_GAP / 2.f;
+ Float top = -(MINIFONT_HEIGHT + halfGap);
+ // We always want integer scaling, otherwise the "bitmap" glyphs will look
+ // even uglier than usual when zoomed
+ int32_t devPixelsPerCSSPx =
+ std::max<int32_t>(1, nsDeviceContext::AppUnitsPerCSSPixel() /
+ aAppUnitsPerDevPixel);
+ AutoRestoreTransform autoRestoreTransform(&aDrawTarget);
+ aDrawTarget.SetTransform(
+ aDrawTarget.GetTransform().PreTranslate(center).
+ PreScale(devPixelsPerCSSPx,
+ devPixelsPerCSSPx));
+ if (aChar < 0x10000) {
+ if (aRect.Width() >= 2 * (MINIFONT_WIDTH + HEX_CHAR_GAP) &&
+ aRect.Height() >= 2 * MINIFONT_HEIGHT + HEX_CHAR_GAP) {
+ // Draw 4 digits for BMP
+ Float left = -(MINIFONT_WIDTH + halfGap);
+ DrawHexChar((aChar >> 12) & 0xF,
+ Point(left, top), aDrawTarget, color);
+ DrawHexChar((aChar >> 8) & 0xF,
+ Point(halfGap, top), aDrawTarget, color);
+ DrawHexChar((aChar >> 4) & 0xF,
+ Point(left, halfGap), aDrawTarget, color);
+ DrawHexChar(aChar & 0xF,
+ Point(halfGap, halfGap), aDrawTarget, color);
+ }
+ } else {
+ if (aRect.Width() >= 3 * (MINIFONT_WIDTH + HEX_CHAR_GAP) &&
+ aRect.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,
+ Point(first, top), aDrawTarget, color);
+ DrawHexChar((aChar >> 16) & 0xF,
+ Point(second, top), aDrawTarget, color);
+ DrawHexChar((aChar >> 12) & 0xF,
+ Point(third, top), aDrawTarget, color);
+ DrawHexChar((aChar >> 8) & 0xF,
+ Point(first, halfGap), aDrawTarget, color);
+ DrawHexChar((aChar >> 4) & 0xF,
+ Point(second, halfGap), aDrawTarget, color);
+ DrawHexChar(aChar & 0xF,
+ Point(third, halfGap), aDrawTarget, color);
+ }
+ }
+#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(nsDeviceContext::AppUnitsPerCSSPixel()) / aAppUnitsPerDevPixel;
+ return width;
+}
diff --git a/gfx/thebes/gfxFontMissingGlyphs.h b/gfx/thebes/gfxFontMissingGlyphs.h
new file mode 100644
index 000000000..1814a89a7
--- /dev/null
+++ b/gfx/thebes/gfxFontMissingGlyphs.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/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::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 aAppUnitsPerDevPixel the appUnits to devPixel ratio we're using,
+ * (so we can scale glyphs to a sensible size)
+ */
+ static void DrawMissingGlyph(uint32_t aChar,
+ const Rect& aRect,
+ DrawTarget& aDrawTarget,
+ const Pattern& aPattern,
+ uint32_t aAppUnitsPerDevPixel);
+ /**
+ * @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);
+};
+
+#endif
diff --git a/gfx/thebes/gfxFontPrefLangList.h b/gfx/thebes/gfxFontPrefLangList.h
new file mode 100644
index 000000000..804b2b17e
--- /dev/null
+++ b/gfx/thebes/gfxFontPrefLangList.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/gfxFontTest.cpp b/gfx/thebes/gfxFontTest.cpp
new file mode 100644
index 000000000..b42189ca9
--- /dev/null
+++ b/gfx/thebes/gfxFontTest.cpp
@@ -0,0 +1,8 @@
+/* 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 "gfxFontTest.h"
+
+gfxFontTestStore* gfxFontTestStore::sCurrentStore = nullptr;
diff --git a/gfx/thebes/gfxFontTest.h b/gfx/thebes/gfxFontTest.h
new file mode 100644
index 000000000..fa7cbbcd2
--- /dev/null
+++ b/gfx/thebes/gfxFontTest.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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_TEST_H
+#define GFX_FONT_TEST_H
+
+#include "nsString.h"
+#include "nsTArray.h"
+
+#include "cairo.h"
+
+struct gfxFontTestItem {
+ gfxFontTestItem(const nsCString& fontName,
+ cairo_glyph_t *cglyphs, int nglyphs)
+ : platformFont(fontName)
+ {
+ glyphs = new cairo_glyph_t[nglyphs];
+ memcpy (glyphs, cglyphs, sizeof(cairo_glyph_t) * nglyphs);
+ num_glyphs = nglyphs;
+ }
+
+ gfxFontTestItem(const gfxFontTestItem& other) {
+ platformFont = other.platformFont;
+ num_glyphs = other.num_glyphs;
+ glyphs = new cairo_glyph_t[num_glyphs];
+ memcpy (glyphs, other.glyphs, sizeof(cairo_glyph_t) * num_glyphs);
+ }
+
+ ~gfxFontTestItem() {
+ delete [] glyphs;
+ }
+
+ nsCString platformFont;
+ cairo_glyph_t *glyphs;
+ int num_glyphs;
+};
+
+
+class gfxFontTestStore {
+public:
+ gfxFontTestStore() { }
+
+ void AddItem (const nsCString& fontString,
+ cairo_glyph_t *cglyphs, int nglyphs)
+ {
+ items.AppendElement(gfxFontTestItem(fontString, cglyphs, nglyphs));
+ }
+
+ void AddItem (const nsString& fontString,
+ cairo_glyph_t *cglyphs, int nglyphs)
+ {
+ items.AppendElement(gfxFontTestItem(NS_ConvertUTF16toUTF8(fontString), cglyphs, nglyphs));
+ }
+
+ nsTArray<gfxFontTestItem> items;
+
+public:
+ static gfxFontTestStore *CurrentStore() {
+ return sCurrentStore;
+ }
+
+ static gfxFontTestStore *NewStore() {
+ if (sCurrentStore)
+ delete sCurrentStore;
+
+ sCurrentStore = new gfxFontTestStore;
+ return sCurrentStore;
+ }
+
+ static void DeleteStore() {
+ if (sCurrentStore)
+ delete sCurrentStore;
+
+ sCurrentStore = nullptr;
+ }
+
+protected:
+ static gfxFontTestStore *sCurrentStore;
+};
+
+
+#endif /* GFX_FONT_TEST_H */
diff --git a/gfx/thebes/gfxFontUtils.cpp b/gfx/thebes/gfxFontUtils.cpp
new file mode 100644
index 000000000..cb505e87b
--- /dev/null
+++ b/gfx/thebes/gfxFontUtils.cpp
@@ -0,0 +1,1809 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsServiceManagerUtils.h"
+
+#include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/Sprintf.h"
+
+#include "nsCOMPtr.h"
+#include "nsIUUIDGenerator.h"
+#include "nsIUnicodeDecoder.h"
+
+#include "harfbuzz/hb.h"
+
+#include "plbase64.h"
+#include "mozilla/Logging.h"
+
+#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
+{
+ NS_ASSERTION(mBlocks.DebugGetHeader(), "mHdr is null, this is bad");
+ uint32_t b, numBlocks = mBlocks.Length();
+
+ for (b = 0; b < numBlocks; b++) {
+ Block *block = mBlocks[b].get();
+ if (!block) {
+ continue;
+ }
+ const int BUFSIZE = 256;
+ char outStr[BUFSIZE];
+ int index = 0;
+ index += snprintf(&outStr[index], BUFSIZE - index, "%s u+%6.6x [", aPrefix, (b << BLOCK_INDEX_SHIFT));
+ 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, " ");
+ }
+ index += 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::ReadCMAPTableFormat12(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(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,
+ 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)
+{
+ 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 {
+ // const uint16_t idDelta = ReadShortAt16(idDeltas, i); // Unused: self-documenting.
+ 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);
+ }
+ }
+ }
+ }
+ }
+
+ aCharacterMap.Compact();
+
+ return NS_OK;
+}
+
+nsresult
+gfxFontUtils::ReadCMAPTableFormat14(const uint8_t *aBuf, uint32_t aLength,
+ UniquePtr<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;
+ }
+ }
+ }
+
+ aTable = MakeUnique<uint8_t[]>(tablelen);
+ memcpy(aTable.get(), aBuf, tablelen);
+
+ 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 *aSymbolEncoding)
+{
+ 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 (!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;
+ *aSymbolEncoding = true;
+ break;
+ } else if (format == 4 && acceptableFormat4(platformID, encodingID, keepFormat)) {
+ keepFormat = format;
+ *aTableOffset = offset;
+ *aSymbolEncoding = false;
+ } else if ((format == 10 || format == 12) &&
+ acceptableUCS4Encoding(platformID, encodingID, keepFormat)) {
+ keepFormat = format;
+ *aTableOffset = offset;
+ *aSymbolEncoding = false;
+ 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,
+ bool& aUnicodeFont, bool& aSymbolFont)
+{
+ uint32_t offset;
+ bool symbol;
+ uint32_t format = FindPreferredSubtable(aBuf, aBufLength,
+ &offset, &aUVSOffset, &symbol);
+
+ switch (format) {
+ case 4:
+ if (symbol) {
+ aUnicodeFont = false;
+ aSymbolFont = true;
+ } else {
+ aUnicodeFont = true;
+ aSymbolFont = false;
+ }
+ return ReadCMAPTableFormat4(aBuf + offset, aBufLength - offset,
+ aCharacterMap);
+
+ case 10:
+ aUnicodeFont = true;
+ aSymbolFont = false;
+ return ReadCMAPTableFormat10(aBuf + offset, aBufLength - offset,
+ aCharacterMap);
+
+ case 12:
+ aUnicodeFont = true;
+ aSymbolFont = false;
+ return ReadCMAPTableFormat12(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 {
+ 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 {
+ 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, char16_t aCh)
+{
+ const Format4Cmap *cmap4 = reinterpret_cast<const Format4Cmap*>(aBuf);
+ uint16_t segCount;
+ const AutoSwap_PRUint16 *endCodes;
+ const AutoSwap_PRUint16 *startCodes;
+ const AutoSwap_PRUint16 *idDelta;
+ const AutoSwap_PRUint16 *idRangeOffset;
+ uint16_t probe;
+ uint16_t rangeShiftOver2;
+ uint16_t index;
+
+ segCount = (uint16_t)(cmap4->segCountX2) / 2;
+
+ endCodes = &cmap4->arrays[0];
+ startCodes = &cmap4->arrays[segCount + 1]; // +1 for reserved word between arrays
+ idDelta = &startCodes[segCount];
+ idRangeOffset = &idDelta[segCount];
+
+ probe = 1 << (uint16_t)(cmap4->entrySelector);
+ rangeShiftOver2 = (uint16_t)(cmap4->rangeShift) / 2;
+
+ if ((uint16_t)(startCodes[rangeShiftOver2]) <= aCh) {
+ index = rangeShiftOver2;
+ } else {
+ index = 0;
+ }
+
+ while (probe > 1) {
+ probe >>= 1;
+ if ((uint16_t)(startCodes[index + probe]) <= aCh) {
+ index += probe;
+ }
+ }
+
+ if (aCh >= (uint16_t)(startCodes[index]) && aCh <= (uint16_t)(endCodes[index])) {
+ uint16_t result;
+ if ((uint16_t)(idRangeOffset[index]) == 0) {
+ result = aCh;
+ } else {
+ uint16_t offset = aCh - (uint16_t)(startCodes[index]);
+ const AutoSwap_PRUint16 *glyphIndexTable =
+ (const AutoSwap_PRUint16*)((const char*)&idRangeOffset[index] +
+ (uint16_t)(idRangeOffset[index]));
+ result = glyphIndexTable[offset];
+ }
+
+ // note that this is unsigned 16-bit arithmetic, and may wrap around
+ result += (uint16_t)(idDelta[index]);
+ 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::MapCharToGlyphFormat12(const uint8_t *aBuf, uint32_t aCh)
+{
+ const Format12CmapHeader *cmap12 =
+ reinterpret_cast<const Format12CmapHeader*>(aBuf);
+
+ // We know that numGroups is within range for the subtable size
+ // because it was checked by ReadCMAPTableFormat12.
+ 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
+ startCharCode = groups[range].startCharCode;
+ if (startCharCode <= aCh && groups[range].endCharCode >= aCh) {
+ return groups[range].startGlyphId + aCh - startCharCode;
+ }
+
+ // 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 symbol;
+ uint32_t format = FindPreferredSubtable(aCmapBuf, aBufLength, &offset,
+ &uvsOffset, &symbol);
+
+ uint32_t gid;
+ switch (format) {
+ case 4:
+ gid = aUnicode < UNICODE_BMP_LIMIT ?
+ MapCharToGlyphFormat4(aCmapBuf + offset, char16_t(aUnicode)) : 0;
+ break;
+ case 10:
+ gid = MapCharToGlyphFormat10(aCmapBuf + offset, aUnicode);
+ break;
+ case 12:
+ gid = MapCharToGlyphFormat12(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,
+ char16_t(aUnicode));
+ }
+ break;
+ case 10:
+ varGID = MapCharToGlyphFormat10(aCmapBuf + offset,
+ aUnicode);
+ break;
+ case 12:
+ varGID = MapCharToGlyphFormat12(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 nsAString& aFamilyList,
+ nsTArray<nsString>& aFontList)
+{
+ const char16_t kComma = char16_t(',');
+
+ // append each font name to the list
+ nsAutoString fontname;
+ const char16_t *p, *p_end;
+ aFamilyList.BeginReading(p);
+ aFamilyList.EndReading(p_end);
+
+ while (p < p_end) {
+ const char16_t *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
+ aFontList.AppendElement(fontname);
+ ++p;
+ }
+}
+
+void gfxFontUtils::AppendPrefsFontList(const char *aPrefName,
+ nsTArray<nsString>& aFontList)
+{
+ // get the list of single-face font families
+ nsAdoptingString fontlistValue = Preferences::GetString(aPrefName);
+ if (!fontlistValue) {
+ return;
+ }
+
+ ParseFontList(fontlistValue, aFontList);
+}
+
+void gfxFontUtils::GetPrefsFontList(const char *aPrefName,
+ nsTArray<nsString>& aFontList)
+{
+ aFontList.Clear();
+ AppendPrefsFontList(aPrefName, 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');
+}
+
+// Copy and swap UTF-16 values, assume no surrogate pairs, can be in place.
+// aInBuf and aOutBuf are NOT necessarily 16-bit-aligned, so we should avoid
+// accessing them directly as uint16_t* values.
+// aLen is count of UTF-16 values, so the byte buffers are twice that.
+static void
+CopySwapUTF16(const char* aInBuf, char* aOutBuf, uint32_t aLen)
+{
+ const char* end = aInBuf + aLen * 2;
+ while (aInBuf < end) {
+ uint8_t b0 = *aInBuf++;
+ *aOutBuf++ = *aInBuf++;
+ *aOutBuf++ = b0;
+ }
+}
+
+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
+ 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 (Preferences::GetBool(GFX_PREF_WOFF2_ENABLED) &&
+ 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,
+ nsAString& aFullName)
+{
+ aFullName.AssignLiteral("(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);
+
+ hb_blob_t *nameBlob =
+ hb_blob_create((const char*)aFontData + dirEntry->offset, len,
+ HB_MEMORY_MODE_READONLY, nullptr, nullptr);
+ nsresult rv = GetFullNameFromTable(nameBlob, aFullName);
+ hb_blob_destroy(nameBlob);
+
+ return rv;
+}
+
+nsresult
+gfxFontUtils::GetFullNameFromTable(hb_blob_t *aNameTable,
+ nsAString& aFullName)
+{
+ nsAutoString 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()) {
+ nsAutoString 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,
+ nsAString& aFullName)
+{
+ nsAutoString name;
+ nsresult rv =
+ gfxFontUtils::ReadCanonicalName(aNameTable,
+ gfxFontUtils::NAME_ID_FAMILY,
+ name);
+ if (NS_SUCCEEDED(rv) && !name.IsEmpty()) {
+ aFullName = 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<nsString>& aNames)
+{
+ return ReadNames(aNameData, aDataLen, aNameID, LANG_ALL,
+ aPlatformID, aNames);
+}
+
+nsresult
+gfxFontUtils::ReadCanonicalName(hb_blob_t *aNameTable, uint32_t aNameID,
+ nsString& 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, nsString& aName)
+{
+ nsresult rv;
+
+ nsTArray<nsString> 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.
+
+#define ANY 0xffff
+const gfxFontUtils::MacFontNameCharsetMapping gfxFontUtils::gMacFontNameCharsets[] =
+{
+ { ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_ENGLISH, "macintosh" },
+ { ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_ICELANDIC, "x-mac-icelandic" },
+ { ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_TURKISH, "x-mac-turkish" },
+ { ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_POLISH, "x-mac-ce" },
+ { ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_ROMANIAN, "x-mac-romanian" },
+ { ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_CZECH, "x-mac-ce" },
+ { ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_SLOVAK, "x-mac-ce" },
+ { ENCODING_ID_MAC_ROMAN, ANY, "macintosh" },
+ { ENCODING_ID_MAC_JAPANESE, LANG_ID_MAC_JAPANESE, "Shift_JIS" },
+ { ENCODING_ID_MAC_JAPANESE, ANY, "Shift_JIS" },
+ { ENCODING_ID_MAC_TRAD_CHINESE, LANG_ID_MAC_TRAD_CHINESE, "Big5" },
+ { ENCODING_ID_MAC_TRAD_CHINESE, ANY, "Big5" },
+ { ENCODING_ID_MAC_KOREAN, LANG_ID_MAC_KOREAN, "EUC-KR" },
+ { ENCODING_ID_MAC_KOREAN, ANY, "EUC-KR" },
+ { ENCODING_ID_MAC_ARABIC, LANG_ID_MAC_ARABIC, "x-mac-arabic" },
+ { ENCODING_ID_MAC_ARABIC, LANG_ID_MAC_URDU, "x-mac-farsi" },
+ { ENCODING_ID_MAC_ARABIC, LANG_ID_MAC_FARSI, "x-mac-farsi" },
+ { ENCODING_ID_MAC_ARABIC, ANY, "x-mac-arabic" },
+ { ENCODING_ID_MAC_HEBREW, LANG_ID_MAC_HEBREW, "x-mac-hebrew" },
+ { ENCODING_ID_MAC_HEBREW, ANY, "x-mac-hebrew" },
+ { ENCODING_ID_MAC_GREEK, ANY, "x-mac-greek" },
+ { ENCODING_ID_MAC_CYRILLIC, ANY, "x-mac-cyrillic" },
+ { ENCODING_ID_MAC_DEVANAGARI, ANY, "x-mac-devanagari"},
+ { ENCODING_ID_MAC_GURMUKHI, ANY, "x-mac-gurmukhi" },
+ { ENCODING_ID_MAC_GUJARATI, ANY, "x-mac-gujarati" },
+ { ENCODING_ID_MAC_SIMP_CHINESE, LANG_ID_MAC_SIMP_CHINESE, "gb18030" },
+ { ENCODING_ID_MAC_SIMP_CHINESE, ANY, "gb18030" }
+};
+
+const char* gfxFontUtils::gISOFontNameCharsets[] =
+{
+ /* 0 */ "windows-1252", /* US-ASCII */
+ /* 1 */ nullptr , /* spec says "ISO 10646" but does not specify encoding form! */
+ /* 2 */ "windows-1252" /* ISO-8859-1 */
+};
+
+const char* gfxFontUtils::gMSFontNameCharsets[] =
+{
+ /* [0] ENCODING_ID_MICROSOFT_SYMBOL */ "" ,
+ /* [1] ENCODING_ID_MICROSOFT_UNICODEBMP */ "" ,
+ /* [2] ENCODING_ID_MICROSOFT_SHIFTJIS */ "Shift_JIS" ,
+ /* [3] ENCODING_ID_MICROSOFT_PRC */ nullptr ,
+ /* [4] ENCODING_ID_MICROSOFT_BIG5 */ "Big5" ,
+ /* [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 */ ""
+};
+
+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 name of the charset we should use to decode a font name
+// given the name table attributes.
+// Special return values:
+// "" charset is UTF16BE, no need for a converter
+// nullptr unknown charset, do not attempt conversion
+const char*
+gfxFontUtils::GetCharsetForFontName(uint16_t aPlatform, uint16_t aScript, uint16_t aLanguage)
+{
+ switch (aPlatform)
+ {
+ case PLATFORM_ID_UNICODE:
+ return "";
+
+ 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].mCharsetName;
+ }
+
+ // 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;
+}
+
+// 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, nsAString& aName)
+{
+ if (aByteLen <= 0) {
+ NS_WARNING("empty font name");
+ aName.SetLength(0);
+ return true;
+ }
+
+ const char *csName = GetCharsetForFontName(aPlatformCode, aScriptCode, aLangCode);
+
+ if (!csName) {
+ // 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 (csName[0] == 0) {
+ // empty charset name: data is utf16be, no need to instantiate a converter
+ uint32_t strLen = aByteLen / 2;
+ aName.SetLength(strLen);
+#ifdef IS_LITTLE_ENDIAN
+ CopySwapUTF16(aNameData, reinterpret_cast<char*>(aName.BeginWriting()),
+ strLen);
+#else
+ memcpy(aName.BeginWriting(), aNameData, strLen * 2);
+#endif
+ return true;
+ }
+
+ nsCOMPtr<nsIUnicodeDecoder> decoder =
+ mozilla::dom::EncodingUtils::DecoderForEncoding(csName);
+ if (!decoder) {
+ NS_WARNING("failed to get the decoder for a font name string");
+ return false;
+ }
+
+ int32_t destLength;
+ nsresult rv = decoder->GetMaxLength(aNameData, aByteLen, &destLength);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("decoder->GetMaxLength failed, invalid font name?");
+ return false;
+ }
+
+ // make space for the converted string
+ aName.SetLength(destLength);
+ rv = decoder->Convert(aNameData, &aByteLen,
+ aName.BeginWriting(), &destLength);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("decoder->Convert failed, invalid font name?");
+ return false;
+ }
+ aName.Truncate(destLength); // set the actual length
+
+ return true;
+}
+
+nsresult
+gfxFontUtils::ReadNames(const char *aNameData, uint32_t aDataLen,
+ uint32_t aNameID,
+ int32_t aLangID, int32_t aPlatformID,
+ nsTArray<nsString>& 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
+ nsAutoString 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;
+}
+
+#pragma pack(1)
+
+struct COLRBaseGlyphRecord {
+ AutoSwap_PRUint16 glyphId;
+ AutoSwap_PRUint16 firstLayerIndex;
+ AutoSwap_PRUint16 numLayers;
+};
+
+struct COLRLayerRecord {
+ AutoSwap_PRUint16 glyphId;
+ AutoSwap_PRUint16 paletteEntryIndex;
+};
+
+struct CPALColorRecord {
+ uint8_t blue;
+ uint8_t green;
+ uint8_t red;
+ uint8_t alpha;
+};
+
+#pragma pack()
+
+bool
+gfxFontUtils::ValidateColorGlyphs(hb_blob_t* aCOLR, hb_blob_t* aCPAL)
+{
+ unsigned int colrLength;
+ const COLRHeader* colr =
+ reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &colrLength));
+ unsigned int cpalLength;
+ const CPALHeaderVersion0* cpal =
+ reinterpret_cast<const CPALHeaderVersion0*>(hb_blob_get_data(aCPAL, &cpalLength));
+
+ if (!colr || !cpal || !colrLength || !cpalLength) {
+ return false;
+ }
+
+ if (uint16_t(colr->version) != 0 || uint16_t(cpal->version) != 0) {
+ // We only support version 0 headers.
+ return false;
+ }
+
+ const uint32_t offsetBaseGlyphRecord = colr->offsetBaseGlyphRecord;
+ const uint16_t numBaseGlyphRecord = colr->numBaseGlyphRecord;
+ const uint32_t offsetLayerRecord = colr->offsetLayerRecord;
+ const uint16_t numLayerRecords = colr->numLayerRecords;
+
+ const uint32_t offsetFirstColorRecord = cpal->offsetFirstColorRecord;
+ const uint16_t numColorRecords = cpal->numColorRecords;
+ const uint32_t numPaletteEntries = cpal->numPaletteEntries;
+
+ if (offsetBaseGlyphRecord >= colrLength) {
+ return false;
+ }
+
+ if (offsetLayerRecord >= colrLength) {
+ return false;
+ }
+
+ if (offsetFirstColorRecord >= cpalLength) {
+ return false;
+ }
+
+ if (!numPaletteEntries) {
+ return false;
+ }
+
+ if (sizeof(COLRBaseGlyphRecord) * numBaseGlyphRecord >
+ colrLength - offsetBaseGlyphRecord) {
+ // COLR base glyph record will be overflow
+ return false;
+ }
+
+ if (sizeof(COLRLayerRecord) * numLayerRecords >
+ colrLength - offsetLayerRecord) {
+ // COLR layer record will be overflow
+ return false;
+ }
+
+ if (sizeof(CPALColorRecord) * numColorRecords >
+ cpalLength - offsetFirstColorRecord) {
+ // CPAL color record will be overflow
+ return false;
+ }
+
+ if (numPaletteEntries * uint16_t(cpal->numPalettes) != numColorRecords ) {
+ // palette of CPAL color record will be overflow.
+ return false;
+ }
+
+ uint16_t lastGlyphId = 0;
+ const COLRBaseGlyphRecord* baseGlyph =
+ reinterpret_cast<const COLRBaseGlyphRecord*>(
+ reinterpret_cast<const uint8_t*>(colr) + offsetBaseGlyphRecord);
+
+ for (uint16_t i = 0; i < numBaseGlyphRecord; i++, baseGlyph++) {
+ const uint32_t firstLayerIndex = baseGlyph->firstLayerIndex;
+ const uint16_t numLayers = baseGlyph->numLayers;
+ const uint16_t glyphId = baseGlyph->glyphId;
+
+ if (lastGlyphId && lastGlyphId >= glyphId) {
+ // glyphId must be sorted
+ return false;
+ }
+ lastGlyphId = glyphId;
+
+ if (!numLayers) {
+ // no layer
+ return false;
+ }
+ if (firstLayerIndex + numLayers > numLayerRecords) {
+ // layer length of target glyph is overflow
+ return false;
+ }
+ }
+
+ const COLRLayerRecord* layer =
+ reinterpret_cast<const COLRLayerRecord*>(
+ reinterpret_cast<const uint8_t*>(colr) + offsetLayerRecord);
+
+ for (uint16_t i = 0; i < numLayerRecords; i++, layer++) {
+ if (uint16_t(layer->paletteEntryIndex) >= numPaletteEntries &&
+ uint16_t(layer->paletteEntryIndex) != 0xFFFF) {
+ // CPAL palette entry record is overflow
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static int
+CompareBaseGlyph(const void* key, const void* data)
+{
+ uint32_t glyphId = (uint32_t)(uintptr_t)key;
+ const COLRBaseGlyphRecord* baseGlyph =
+ reinterpret_cast<const COLRBaseGlyphRecord*>(data);
+ uint32_t baseGlyphId = uint16_t(baseGlyph->glyphId);
+
+ if (baseGlyphId == glyphId) {
+ return 0;
+ }
+
+ return baseGlyphId > glyphId ? -1 : 1;
+}
+
+static
+COLRBaseGlyphRecord*
+LookForBaseGlyphRecord(const COLRHeader* aCOLR, uint32_t aGlyphId)
+{
+ const uint8_t* baseGlyphRecords =
+ reinterpret_cast<const uint8_t*>(aCOLR) +
+ uint32_t(aCOLR->offsetBaseGlyphRecord);
+ // BaseGlyphRecord is sorted by glyphId
+ return reinterpret_cast<COLRBaseGlyphRecord*>(
+ bsearch((void*)(uintptr_t)aGlyphId,
+ baseGlyphRecords,
+ uint16_t(aCOLR->numBaseGlyphRecord),
+ sizeof(COLRBaseGlyphRecord),
+ CompareBaseGlyph));
+}
+
+bool
+gfxFontUtils::GetColorGlyphLayers(hb_blob_t* aCOLR,
+ hb_blob_t* aCPAL,
+ uint32_t aGlyphId,
+ const mozilla::gfx::Color& aDefaultColor,
+ nsTArray<uint16_t>& aGlyphs,
+ nsTArray<mozilla::gfx::Color>& aColors)
+{
+ unsigned int blobLength;
+ const COLRHeader* colr =
+ reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR,
+ &blobLength));
+ MOZ_ASSERT(colr, "Cannot get COLR raw data");
+ MOZ_ASSERT(blobLength, "Found COLR data, but length is 0");
+
+ COLRBaseGlyphRecord* baseGlyph = LookForBaseGlyphRecord(colr, aGlyphId);
+ if (!baseGlyph) {
+ return false;
+ }
+
+ const CPALHeaderVersion0* cpal =
+ reinterpret_cast<const CPALHeaderVersion0*>(
+ hb_blob_get_data(aCPAL, &blobLength));
+ MOZ_ASSERT(cpal, "Cannot get CPAL raw data");
+ MOZ_ASSERT(blobLength, "Found CPAL data, but length is 0");
+
+ const COLRLayerRecord* layer =
+ reinterpret_cast<const COLRLayerRecord*>(
+ reinterpret_cast<const uint8_t*>(colr) +
+ uint32_t(colr->offsetLayerRecord) +
+ sizeof(COLRLayerRecord) * uint16_t(baseGlyph->firstLayerIndex));
+ const uint16_t numLayers = baseGlyph->numLayers;
+ const uint32_t offsetFirstColorRecord = cpal->offsetFirstColorRecord;
+
+ for (uint16_t layerIndex = 0; layerIndex < numLayers; layerIndex++) {
+ aGlyphs.AppendElement(uint16_t(layer->glyphId));
+ if (uint16_t(layer->paletteEntryIndex) == 0xFFFF) {
+ aColors.AppendElement(aDefaultColor);
+ } else {
+ const CPALColorRecord* color =
+ reinterpret_cast<const CPALColorRecord*>(
+ reinterpret_cast<const uint8_t*>(cpal) +
+ offsetFirstColorRecord +
+ sizeof(CPALColorRecord) * uint16_t(layer->paletteEntryIndex));
+ aColors.AppendElement(mozilla::gfx::Color(color->red / 255.0,
+ color->green / 255.0,
+ color->blue / 255.0,
+ color->alpha / 255.0));
+ }
+ layer++;
+ }
+ return true;
+}
+
+#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
+
+#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 000000000..dd6a76558
--- /dev/null
+++ b/gfx/thebes/gfxFontUtils.h
@@ -0,0 +1,1027 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxPlatform.h"
+#include "nsComponentManagerUtils.h"
+#include "nsTArray.h"
+#include "mozilla/Likely.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/UniquePtr.h"
+
+#include "zlib.h"
+#include <algorithm>
+
+/* Bug 341128 - w32api defines min/max which causes problems with <bitset> */
+#ifdef __MINGW32__
+#undef min
+#undef max
+#endif
+
+typedef struct hb_blob_t hb_blob_t;
+
+class gfxSparseBitSet {
+private:
+ enum { BLOCK_SIZE = 32 }; // ==> 256 codepoints per block
+ enum { BLOCK_SIZE_BITS = BLOCK_SIZE * 8 };
+ enum { BLOCK_INDEX_SHIFT = 8 };
+
+ struct Block {
+ Block(const Block& aBlock) { memcpy(mBits, aBlock.mBits, sizeof(mBits)); }
+ explicit Block(unsigned char memsetValue = 0) { memset(mBits, memsetValue, BLOCK_SIZE); }
+ uint8_t mBits[BLOCK_SIZE];
+ };
+
+public:
+ gfxSparseBitSet() { }
+ gfxSparseBitSet(const gfxSparseBitSet& aBitset) {
+ uint32_t len = aBitset.mBlocks.Length();
+ mBlocks.AppendElements(len);
+ for (uint32_t i = 0; i < len; ++i) {
+ Block *block = aBitset.mBlocks[i].get();
+ if (block) {
+ mBlocks[i] = mozilla::MakeUnique<Block>(*block);
+ }
+ }
+ }
+
+ bool Equals(const gfxSparseBitSet *aOther) const {
+ if (mBlocks.Length() != aOther->mBlocks.Length()) {
+ return false;
+ }
+ size_t n = mBlocks.Length();
+ for (size_t i = 0; i < n; ++i) {
+ const Block *b1 = mBlocks[i].get();
+ const Block *b2 = aOther->mBlocks[i].get();
+ if (!b1 != !b2) {
+ return false;
+ }
+ if (!b1) {
+ continue;
+ }
+ if (memcmp(&b1->mBits, &b2->mBits, BLOCK_SIZE) != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool test(uint32_t aIndex) const {
+ NS_ASSERTION(mBlocks.DebugGetHeader(), "mHdr is null, this is bad");
+ uint32_t blockIndex = aIndex/BLOCK_SIZE_BITS;
+ if (blockIndex >= mBlocks.Length()) {
+ return false;
+ }
+ const Block *block = mBlocks[blockIndex].get();
+ if (!block) {
+ return false;
+ }
+ 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) {
+ uint32_t startBlock, endBlock, blockLen;
+
+ // start point is beyond the end of the block array? return false immediately
+ startBlock = aStart >> BLOCK_INDEX_SHIFT;
+ blockLen = mBlocks.Length();
+ if (startBlock >= blockLen) return false;
+
+ // check for blocks in range, if none, return false
+ uint32_t blockIndex;
+ bool hasBlocksInRange = false;
+
+ endBlock = aEnd >> BLOCK_INDEX_SHIFT;
+ for (blockIndex = startBlock; blockIndex <= endBlock; blockIndex++) {
+ if (blockIndex < blockLen && mBlocks[blockIndex]) {
+ hasBlocksInRange = true;
+ }
+ }
+ if (!hasBlocksInRange) {
+ return false;
+ }
+
+ Block *block;
+ uint32_t i, start, end;
+
+ // first block, check bits
+ if ((block = mBlocks[startBlock].get())) {
+ start = aStart;
+ end = std::min(aEnd, ((startBlock+1) << BLOCK_INDEX_SHIFT) - 1);
+ for (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 (blockIndex = startBlock + 1; blockIndex < endBlock; blockIndex++) {
+ uint32_t index;
+
+ if (blockIndex >= blockLen ||
+ !(block = mBlocks[blockIndex].get())) {
+ continue;
+ }
+ for (index = 0; index < BLOCK_SIZE; index++) {
+ if (block->mBits[index]) {
+ return true;
+ }
+ }
+ }
+
+ // last block, check bits
+ if (endBlock < blockLen && (block = mBlocks[endBlock].get())) {
+ start = endBlock << BLOCK_INDEX_SHIFT;
+ end = aEnd;
+ for (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 blockIndex = aIndex/BLOCK_SIZE_BITS;
+ if (blockIndex >= mBlocks.Length()) {
+ mBlocks.AppendElements(blockIndex + 1 - mBlocks.Length());
+ }
+ Block *block = mBlocks[blockIndex].get();
+ if (!block) {
+ block = new Block;
+ mBlocks[blockIndex].reset(block);
+ }
+ 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;
+
+ if (endIndex >= mBlocks.Length()) {
+ uint32_t numNewBlocks = endIndex + 1 - mBlocks.Length();
+ mBlocks.AppendElements(numNewBlocks);
+ }
+
+ 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;
+
+ Block *block = mBlocks[i].get();
+ if (!block) {
+ bool fullBlock =
+ (aStart <= blockFirstBit && aEnd >= blockLastBit);
+
+ block = new Block(fullBlock ? 0xFF : 0);
+ mBlocks[i].reset(block);
+
+ if (fullBlock) {
+ continue;
+ }
+ }
+
+ 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 blockIndex = aIndex/BLOCK_SIZE_BITS;
+ if (blockIndex >= mBlocks.Length()) {
+ mBlocks.AppendElements(blockIndex + 1 - mBlocks.Length());
+ }
+ Block *block = mBlocks[blockIndex].get();
+ if (!block) {
+ return;
+ }
+ 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;
+
+ if (endIndex >= mBlocks.Length()) {
+ uint32_t numNewBlocks = endIndex + 1 - mBlocks.Length();
+ mBlocks.AppendElements(numNewBlocks);
+ }
+
+ for (uint32_t i = startIndex; i <= endIndex; ++i) {
+ const uint32_t blockFirstBit = i * BLOCK_SIZE_BITS;
+
+ Block *block = mBlocks[i].get();
+ if (!block) {
+ // any nonexistent block is implicitly all clear,
+ // so there's no need to even create it
+ continue;
+ }
+
+ 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 {
+ size_t total = mBlocks.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (uint32_t i = 0; i < mBlocks.Length(); i++) {
+ if (mBlocks[i]) {
+ total += aMallocSizeOf(mBlocks[i].get());
+ }
+ }
+ return total;
+ }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ // clear out all blocks in the array
+ void reset() {
+ uint32_t i;
+ for (i = 0; i < mBlocks.Length(); i++) {
+ mBlocks[i] = nullptr;
+ }
+ }
+
+ // 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.mBlocks.Length();
+ if (blockCount > mBlocks.Length()) {
+ uint32_t needed = blockCount - mBlocks.Length();
+ mBlocks.AppendElements(needed);
+ }
+ // 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.mBlocks[i]) {
+ continue;
+ }
+ // if the block is missing in this set, just copy the other
+ if (!mBlocks[i]) {
+ mBlocks[i] = mozilla::MakeUnique<Block>(*aBitset.mBlocks[i]);
+ continue;
+ }
+ // else set existing block to the union of both
+ uint32_t *dst = reinterpret_cast<uint32_t*>(mBlocks[i]->mBits);
+ const uint32_t *src =
+ reinterpret_cast<const uint32_t*>(aBitset.mBlocks[i]->mBits);
+ for (uint32_t j = 0; j < BLOCK_SIZE / 4; ++j) {
+ dst[j] |= src[j];
+ }
+ }
+ }
+
+ void Compact() {
+ mBlocks.Compact();
+ }
+
+ uint32_t GetChecksum() const {
+ uint32_t check = adler32(0, Z_NULL, 0);
+ for (uint32_t i = 0; i < mBlocks.Length(); i++) {
+ if (mBlocks[i]) {
+ const Block *block = mBlocks[i].get();
+ check = adler32(check, (uint8_t*) (&i), 4);
+ check = adler32(check, (uint8_t*) block, sizeof(Block));
+ }
+ }
+ return check;
+ }
+
+private:
+ nsTArray<mozilla::UniquePtr<Block>> mBlocks;
+};
+
+#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() { }
+ 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 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;
+};
+
+struct COLRHeader {
+ AutoSwap_PRUint16 version;
+ AutoSwap_PRUint16 numBaseGlyphRecord;
+ AutoSwap_PRUint32 offsetBaseGlyphRecord;
+ AutoSwap_PRUint32 offsetLayerRecord;
+ AutoSwap_PRUint16 numLayerRecords;
+};
+
+struct CPALHeaderVersion0 {
+ AutoSwap_PRUint16 version;
+ AutoSwap_PRUint16 numPaletteEntries;
+ AutoSwap_PRUint16 numPalettes;
+ AutoSwap_PRUint16 numColorRecords;
+ AutoSwap_PRUint32 offsetFirstColorRecord;
+};
+
+#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
+};
+#define GFX_PREF_WOFF2_ENABLED "gfx.downloadable_fonts.woff2.enabled"
+
+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).
+ };
+
+ // 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 (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 (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
+ ReadCMAPTableFormat12(const uint8_t *aBuf, uint32_t aLength,
+ gfxSparseBitSet& aCharacterMap);
+
+ static nsresult
+ ReadCMAPTableFormat4(const uint8_t *aBuf, uint32_t aLength,
+ gfxSparseBitSet& aCharacterMap);
+
+ static nsresult
+ ReadCMAPTableFormat14(const uint8_t *aBuf, uint32_t aLength,
+ mozilla::UniquePtr<uint8_t[]>& aTable);
+
+ static uint32_t
+ FindPreferredSubtable(const uint8_t *aBuf, uint32_t aBufLength,
+ uint32_t *aTableOffset, uint32_t *aUVSTableOffset,
+ bool *aSymbolEncoding);
+
+ static nsresult
+ ReadCMAP(const uint8_t *aBuf, uint32_t aBufLength,
+ gfxSparseBitSet& aCharacterMap,
+ uint32_t& aUVSOffset,
+ bool& aUnicodeFont, bool& aSymbolFont);
+
+ static uint32_t
+ MapCharToGlyphFormat4(const uint8_t *aBuf, char16_t aCh);
+
+ static uint32_t
+ MapCharToGlyphFormat10(const uint8_t *aBuf, uint32_t aCh);
+
+ static uint32_t
+ MapCharToGlyphFormat12(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);
+
+#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,
+ nsAString& 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,
+ nsAString& aFullName);
+
+ // helper to get family name from name table
+ static nsresult
+ GetFamilyNameFromTable(hb_blob_t *aNameTable,
+ nsAString& 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<nsString>& 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,
+ nsString& aName);
+
+ static nsresult
+ ReadCanonicalName(const char *aNameData, uint32_t aDataLen,
+ uint32_t aNameID, nsString& 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, nsAString& dest);
+
+ static inline bool IsJoinCauser(uint32_t ch) {
+ return (ch == 0x200D);
+ }
+
+ static inline bool IsJoinControl(uint32_t ch) {
+ return (ch == 0x200C || ch == 0x200D);
+ }
+
+ 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 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 nsAString& aFamilyList,
+ nsTArray<nsString>& aFontList);
+
+ // for a given font list pref name, append list of font names
+ static void AppendPrefsFontList(const char *aPrefName,
+ nsTArray<nsString>& aFontList);
+
+ // for a given font list pref name, initialize a list of font names
+ static void GetPrefsFontList(const char *aPrefName,
+ nsTArray<nsString>& aFontList);
+
+ // generate a unique font name
+ static nsresult MakeUniqueUserFontName(nsAString& aName);
+
+ // for color layer from glyph using COLR and CPAL tables
+ static bool ValidateColorGlyphs(hb_blob_t* aCOLR, hb_blob_t* aCPAL);
+ static bool GetColorGlyphLayers(hb_blob_t* aCOLR,
+ hb_blob_t* aCPAL,
+ uint32_t aGlyphId,
+ const mozilla::gfx::Color& aDefaultColor,
+ nsTArray<uint16_t> &aGlyphs,
+ nsTArray<mozilla::gfx::Color> &aColors);
+
+protected:
+ friend struct MacCharsetMappingComparator;
+
+ static nsresult
+ ReadNames(const char *aNameData, uint32_t aDataLen, uint32_t aNameID,
+ int32_t aLangID, int32_t aPlatformID, nsTArray<nsString>& aNames);
+
+ // convert opentype name-table platform/encoding/language values to a charset name
+ // we can use to convert the name data to unicode, or "" if data is UTF16BE
+ static const char*
+ GetCharsetForFontName(uint16_t aPlatform, uint16_t aScript, uint16_t aLanguage);
+
+ struct MacFontNameCharsetMapping {
+ uint16_t mEncoding;
+ uint16_t mLanguage;
+ const char *mCharsetName;
+
+ bool operator<(const MacFontNameCharsetMapping& rhs) const {
+ return (mEncoding < rhs.mEncoding) ||
+ ((mEncoding == rhs.mEncoding) && (mLanguage < rhs.mLanguage));
+ }
+ };
+ static const MacFontNameCharsetMapping gMacFontNameCharsets[];
+ static const char* gISOFontNameCharsets[];
+ static const char* gMSFontNameCharsets[];
+};
+
+#endif /* GFX_FONT_UTILS_H */
diff --git a/gfx/thebes/gfxFontconfigFonts.cpp b/gfx/thebes/gfxFontconfigFonts.cpp
new file mode 100644
index 000000000..bbcbbabf9
--- /dev/null
+++ b/gfx/thebes/gfxFontconfigFonts.cpp
@@ -0,0 +1,2262 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "prlink.h"
+#include "gfxTypes.h"
+
+#include "nsTArray.h"
+
+#include "gfxContext.h"
+#ifdef MOZ_WIDGET_GTK
+#include "gfxPlatformGtk.h"
+#endif
+#include "gfxFontconfigFonts.h"
+#include "gfxFT2FontBase.h"
+#include "gfxFT2Utils.h"
+#include "harfbuzz/hb.h"
+#include "harfbuzz/hb-glib.h"
+#include "harfbuzz/hb-ot.h"
+#include "nsUnicodeProperties.h"
+#include "nsUnicodeScriptCodes.h"
+#include "gfxFontconfigUtils.h"
+#include "gfxUserFontSet.h"
+#include "gfxFontConstants.h"
+#include "nsGkAtoms.h"
+#include "nsILanguageAtomService.h"
+#include "nsServiceManagerUtils.h"
+
+#include <cairo.h>
+#include <cairo-ft.h>
+#include "mozilla/gfx/HelpersCairo.h"
+
+#include <fontconfig/fcfreetype.h>
+#include <pango/pango.h>
+
+#include FT_TRUETYPE_TABLES_H
+
+#ifdef MOZ_WIDGET_GTK
+#include <gdk/gdk.h>
+#endif
+
+#include <math.h>
+
+using namespace mozilla;
+using namespace mozilla::unicode;
+
+#define PRINTING_FC_PROPERTY "gfx.printing"
+
+static PangoLanguage *GuessPangoLanguage(nsIAtom *aLanguage);
+
+static cairo_scaled_font_t *
+CreateScaledFont(FcPattern *aPattern, cairo_font_face_t *aFace);
+
+static FT_Library gFTLibrary;
+
+// FC_FAMILYLANG and FC_FULLNAME were introduced in fontconfig-2.2.97
+// and so fontconfig-2.3.0 (2005).
+#ifndef FC_FAMILYLANG
+#define FC_FAMILYLANG "familylang"
+#endif
+#ifndef FC_FULLNAME
+#define FC_FULLNAME "fullname"
+#endif
+
+static PRFuncPtr
+FindFunctionSymbol(const char *name)
+{
+ PRLibrary *lib = nullptr;
+ PRFuncPtr result = PR_FindFunctionSymbolAndLibrary(name, &lib);
+ if (lib) {
+ PR_UnloadLibrary(lib);
+ }
+
+ return result;
+}
+
+static bool HasChar(FcPattern *aFont, FcChar32 wc)
+{
+ FcCharSet *charset = nullptr;
+ FcPatternGetCharSet(aFont, FC_CHARSET, 0, &charset);
+
+ return charset && FcCharSetHasChar(charset, wc);
+}
+
+/**
+ * gfxFcFontEntry:
+ *
+ * An abstract base class of for gfxFontEntry implementations used by
+ * gfxFcFont and gfxUserFontSet.
+ */
+
+class gfxFcFontEntry : public gfxFontEntry {
+public:
+ // For all FontEntrys attached to gfxFcFonts, there will be only one
+ // pattern in this array. This is always a font pattern, not a fully
+ // resolved pattern. gfxFcFont only uses this to construct a PangoFont.
+ //
+ // FontEntrys for src:local() fonts in gfxUserFontSet may return more than
+ // one pattern. (See comment in gfxUserFcFontEntry.)
+ const nsTArray< nsCountedRef<FcPattern> >& GetPatterns()
+ {
+ return mPatterns;
+ }
+
+ static gfxFcFontEntry *LookupFontEntry(cairo_font_face_t *aFace)
+ {
+ return static_cast<gfxFcFontEntry*>
+ (cairo_font_face_get_user_data(aFace, &sFontEntryKey));
+ }
+
+ // override the gfxFontEntry impl to read the name from fontconfig
+ // instead of trying to get the 'name' table, as we don't implement
+ // GetFontTable() here
+ virtual nsString RealFaceName();
+
+ // This is needed to make gfxFontEntry::HasCharacter(aCh) work.
+ virtual bool TestCharacterMap(uint32_t aCh)
+ {
+ for (uint32_t i = 0; i < mPatterns.Length(); ++i) {
+ if (HasChar(mPatterns[i], aCh)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+protected:
+ explicit gfxFcFontEntry(const nsAString& aName)
+ : gfxFontEntry(aName)
+ {
+ }
+
+ // One pattern is the common case and some subclasses rely on successful
+ // addition of the first element to the array.
+ AutoTArray<nsCountedRef<FcPattern>,1> mPatterns;
+
+ static cairo_user_data_key_t sFontEntryKey;
+};
+
+cairo_user_data_key_t gfxFcFontEntry::sFontEntryKey;
+
+nsString
+gfxFcFontEntry::RealFaceName()
+{
+ FcChar8 *name;
+ if (!mPatterns.IsEmpty()) {
+ if (FcPatternGetString(mPatterns[0],
+ FC_FULLNAME, 0, &name) == FcResultMatch) {
+ return NS_ConvertUTF8toUTF16((const char*)name);
+ }
+ if (FcPatternGetString(mPatterns[0],
+ FC_FAMILY, 0, &name) == FcResultMatch) {
+ NS_ConvertUTF8toUTF16 result((const char*)name);
+ if (FcPatternGetString(mPatterns[0],
+ FC_STYLE, 0, &name) == FcResultMatch) {
+ result.Append(' ');
+ AppendUTF8toUTF16((const char*)name, result);
+ }
+ return result;
+ }
+ }
+ // fall back to gfxFontEntry implementation (only works for sfnt fonts)
+ return gfxFontEntry::RealFaceName();
+}
+
+/**
+ * gfxSystemFcFontEntry:
+ *
+ * An implementation of gfxFcFontEntry used by gfxFcFonts for system fonts,
+ * including those from regular family-name based font selection as well as
+ * those from src:local().
+ *
+ * All gfxFcFonts using the same cairo_font_face_t share the same FontEntry.
+ */
+
+class gfxSystemFcFontEntry : public gfxFcFontEntry {
+public:
+ // For memory efficiency, aFontPattern should be a font pattern,
+ // not a fully resolved pattern.
+ gfxSystemFcFontEntry(cairo_font_face_t *aFontFace,
+ FcPattern *aFontPattern,
+ const nsAString& aName)
+ : gfxFcFontEntry(aName), mFontFace(aFontFace),
+ mFTFace(nullptr), mFTFaceInitialized(false)
+ {
+ cairo_font_face_reference(mFontFace);
+ cairo_font_face_set_user_data(mFontFace, &sFontEntryKey, this, nullptr);
+
+ // mPatterns is an AutoTArray with 1 space always available, so the
+ // AppendElement always succeeds.
+ // FIXME: Make this infallible after bug 968520 is done.
+ MOZ_ALWAYS_TRUE(mPatterns.AppendElement(fallible));
+ mPatterns[0] = aFontPattern;
+
+ FcChar8 *name;
+ if (FcPatternGetString(aFontPattern,
+ FC_FAMILY, 0, &name) == FcResultMatch) {
+ mFamilyName = NS_ConvertUTF8toUTF16((const char*)name);
+ }
+ }
+
+ ~gfxSystemFcFontEntry()
+ {
+ cairo_font_face_set_user_data(mFontFace,
+ &sFontEntryKey,
+ nullptr,
+ nullptr);
+ cairo_font_face_destroy(mFontFace);
+ }
+
+ virtual void ForgetHBFace() override;
+ virtual void ReleaseGrFace(gr_face* aFace) override;
+
+protected:
+ virtual nsresult
+ CopyFontTable(uint32_t aTableTag, nsTArray<uint8_t>& aBuffer) override;
+
+ void MaybeReleaseFTFace();
+
+private:
+ cairo_font_face_t *mFontFace;
+ FT_Face mFTFace;
+ bool mFTFaceInitialized;
+};
+
+nsresult
+gfxSystemFcFontEntry::CopyFontTable(uint32_t aTableTag,
+ nsTArray<uint8_t>& aBuffer)
+{
+ if (!mFTFaceInitialized) {
+ mFTFaceInitialized = true;
+ FcChar8 *filename;
+ if (FcPatternGetString(mPatterns[0], FC_FILE, 0, &filename) != FcResultMatch) {
+ return NS_ERROR_FAILURE;
+ }
+ int index;
+ if (FcPatternGetInteger(mPatterns[0], FC_INDEX, 0, &index) != FcResultMatch) {
+ index = 0; // default to 0 if not found in pattern
+ }
+ if (FT_New_Face(gfxPangoFontGroup::GetFTLibrary(),
+ (const char*)filename, index, &mFTFace) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (!mFTFace) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ FT_ULong length = 0;
+ if (FT_Load_Sfnt_Table(mFTFace, aTableTag, 0, nullptr, &length) != 0) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (!aBuffer.SetLength(length, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ if (FT_Load_Sfnt_Table(mFTFace, aTableTag, 0, aBuffer.Elements(), &length) != 0) {
+ aBuffer.Clear();
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+void
+gfxSystemFcFontEntry::MaybeReleaseFTFace()
+{
+ // don't release if either HB or Gr face still exists
+ if (mHBFace || mGrFace) {
+ return;
+ }
+ if (mFTFace) {
+ FT_Done_Face(mFTFace);
+ mFTFace = nullptr;
+ }
+ mFTFaceInitialized = false;
+}
+
+void
+gfxSystemFcFontEntry::ForgetHBFace()
+{
+ gfxFontEntry::ForgetHBFace();
+ MaybeReleaseFTFace();
+}
+
+void
+gfxSystemFcFontEntry::ReleaseGrFace(gr_face* aFace)
+{
+ gfxFontEntry::ReleaseGrFace(aFace);
+ MaybeReleaseFTFace();
+}
+
+// A namespace for @font-face family names in FcPatterns so that fontconfig
+// aliases do not pick up families from @font-face rules and so that
+// fontconfig rules can distinguish between web fonts and platform fonts.
+// http://lists.freedesktop.org/archives/fontconfig/2008-November/003037.html
+#define FONT_FACE_FAMILY_PREFIX "@font-face:"
+
+/**
+ * gfxUserFcFontEntry:
+ *
+ * An abstract class for objects in a gfxUserFontSet that can provide
+ * FcPattern* handles to fonts.
+ *
+ * Separate implementations of this class support local fonts from src:local()
+ * and web fonts from src:url().
+ */
+
+// There is a one-to-one correspondence between gfxUserFcFontEntry objects and
+// @font-face rules, but sometimes a one-to-many correspondence between font
+// entries and font patterns.
+//
+// http://www.w3.org/TR/2002/WD-css3-webfonts-20020802#font-descriptions
+// provided a font-size descriptor to specify the sizes supported by the face,
+// but the "Editor's Draft 27 June 2008"
+// http://dev.w3.org/csswg/css3-fonts/#font-resources does not provide such a
+// descriptor, and Mozilla does not recognize such a descriptor.
+//
+// Font face names used in src:local() also do not usually specify a size.
+//
+// PCF format fonts have each size in a different file, and each of these
+// files is referenced by its own pattern, but really these are each
+// different sizes of one face with one name.
+//
+// Multiple patterns in an entry also effectively deals with a set of
+// PostScript Type 1 font files that all have the same face name but are in
+// several files because of the limit on the number of glyphs in a Type 1 font
+// file. (e.g. Computer Modern.)
+
+class gfxUserFcFontEntry : public gfxFcFontEntry {
+protected:
+ explicit gfxUserFcFontEntry(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle)
+ : gfxFcFontEntry(aFontName)
+ {
+ mStyle = aStyle;
+ mWeight = aWeight;
+ mStretch = aStretch;
+ }
+
+ // Helper function to change a pattern so that it matches the CSS style
+ // descriptors and so gets properly sorted in font selection. This also
+ // avoids synthetic style effects being added by the renderer when the
+ // style of the font itself does not match the descriptor provided by the
+ // author.
+ void AdjustPatternToCSS(FcPattern *aPattern);
+};
+
+void
+gfxUserFcFontEntry::AdjustPatternToCSS(FcPattern *aPattern)
+{
+ int fontWeight = -1;
+ FcPatternGetInteger(aPattern, FC_WEIGHT, 0, &fontWeight);
+ int cssWeight = gfxFontconfigUtils::FcWeightForBaseWeight(mWeight / 100);
+ if (cssWeight != fontWeight) {
+ FcPatternDel(aPattern, FC_WEIGHT);
+ FcPatternAddInteger(aPattern, FC_WEIGHT, cssWeight);
+ }
+
+ int fontSlant;
+ FcResult res = FcPatternGetInteger(aPattern, FC_SLANT, 0, &fontSlant);
+ // gfxFontEntry doesn't understand the difference between oblique
+ // and italic.
+ if (res != FcResultMatch ||
+ IsItalic() != (fontSlant != FC_SLANT_ROMAN)) {
+ FcPatternDel(aPattern, FC_SLANT);
+ FcPatternAddInteger(aPattern, FC_SLANT,
+ IsItalic() ? FC_SLANT_OBLIQUE : FC_SLANT_ROMAN);
+ }
+
+ int fontWidth = -1;
+ FcPatternGetInteger(aPattern, FC_WIDTH, 0, &fontWidth);
+ int cssWidth = gfxFontconfigUtils::FcWidthForThebesStretch(mStretch);
+ if (cssWidth != fontWidth) {
+ FcPatternDel(aPattern, FC_WIDTH);
+ FcPatternAddInteger(aPattern, FC_WIDTH, cssWidth);
+ }
+
+ // Ensure that there is a fullname property (if there is a family
+ // property) so that fontconfig rules can identify the real name of the
+ // font, because the family property will be replaced.
+ FcChar8 *unused;
+ if (FcPatternGetString(aPattern,
+ FC_FULLNAME, 0, &unused) == FcResultNoMatch) {
+ nsAutoCString fullname;
+ if (gfxFontconfigUtils::GetFullnameFromFamilyAndStyle(aPattern,
+ &fullname)) {
+ FcPatternAddString(aPattern, FC_FULLNAME,
+ gfxFontconfigUtils::ToFcChar8(fullname));
+ }
+ }
+
+ nsAutoCString family;
+ family.Append(FONT_FACE_FAMILY_PREFIX);
+ AppendUTF16toUTF8(Name(), family);
+
+ FcPatternDel(aPattern, FC_FAMILY);
+ FcPatternDel(aPattern, FC_FAMILYLANG);
+ FcPatternAddString(aPattern, FC_FAMILY,
+ gfxFontconfigUtils::ToFcChar8(family));
+}
+
+/**
+ * gfxLocalFcFontEntry:
+ *
+ * An implementation of gfxUserFcFontEntry for local fonts from src:local().
+ *
+ * This class is used only in gfxUserFontSet and for providing FcPattern*
+ * handles to system fonts for font selection. gfxFcFonts created from these
+ * patterns will use gfxSystemFcFontEntrys, which may be shared with
+ * gfxFcFonts from regular family-name based font selection.
+ */
+
+class gfxLocalFcFontEntry : public gfxUserFcFontEntry {
+public:
+ gfxLocalFcFontEntry(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const nsTArray< nsCountedRef<FcPattern> >& aPatterns)
+ : gfxUserFcFontEntry(aFontName, aWeight, aStretch, aStyle)
+ {
+ if (!mPatterns.SetCapacity(aPatterns.Length(), fallible))
+ return; // OOM
+
+ for (uint32_t i = 0; i < aPatterns.Length(); ++i) {
+ FcPattern *pattern = FcPatternDuplicate(aPatterns.ElementAt(i));
+ if (!pattern)
+ return; // OOM
+
+ AdjustPatternToCSS(pattern);
+
+ // FIXME: Make this infallible after bug 968520 is done.
+ MOZ_ALWAYS_TRUE(mPatterns.AppendElement(fallible));
+ mPatterns[i].own(pattern);
+ }
+ mIsLocalUserFont = true;
+ }
+};
+
+/**
+ * gfxDownloadedFcFontEntry:
+ *
+ * An implementation of gfxFcFontEntry for web fonts from src:url().
+ *
+ * When a cairo_font_face_t is created for these fonts, the cairo_font_face_t
+ * keeps a reference to the FontEntry to keep the font data alive.
+ */
+
+class gfxDownloadedFcFontEntry : public gfxUserFcFontEntry {
+public:
+ // This takes ownership of the face and its underlying data
+ gfxDownloadedFcFontEntry(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t *aData, FT_Face aFace)
+ : gfxUserFcFontEntry(aFontName, aWeight, aStretch, aStyle),
+ mFontData(aData), mFace(aFace)
+ {
+ NS_PRECONDITION(aFace != nullptr, "aFace is NULL!");
+ mIsDataUserFont = true;
+ InitPattern();
+ }
+
+ virtual ~gfxDownloadedFcFontEntry();
+
+ // Returns true on success
+ bool SetCairoFace(cairo_font_face_t *aFace);
+
+ virtual hb_blob_t* GetFontTable(uint32_t aTableTag) override;
+
+protected:
+ void InitPattern();
+
+ // mFontData holds the data used to instantiate the FT_Face;
+ // this has to persist until we are finished with the face,
+ // then be released with free().
+ const uint8_t* mFontData;
+
+ FT_Face mFace;
+};
+
+// A property for recording gfxDownloadedFcFontEntrys on FcPatterns.
+static const char *kFontEntryFcProp = "-moz-font-entry";
+
+static FcBool AddDownloadedFontEntry(FcPattern *aPattern,
+ gfxDownloadedFcFontEntry *aFontEntry)
+{
+ FcValue value;
+ value.type = FcTypeFTFace; // void* field of union
+ value.u.f = aFontEntry;
+
+ return FcPatternAdd(aPattern, kFontEntryFcProp, value, FcFalse);
+}
+
+static FcBool DelDownloadedFontEntry(FcPattern *aPattern)
+{
+ return FcPatternDel(aPattern, kFontEntryFcProp);
+}
+
+static gfxDownloadedFcFontEntry *GetDownloadedFontEntry(FcPattern *aPattern)
+{
+ FcValue value;
+ if (FcPatternGet(aPattern, kFontEntryFcProp, 0, &value) != FcResultMatch)
+ return nullptr;
+
+ if (value.type != FcTypeFTFace) {
+ NS_NOTREACHED("Wrong type for -moz-font-entry font property");
+ return nullptr;
+ }
+
+ return static_cast<gfxDownloadedFcFontEntry*>(value.u.f);
+}
+
+gfxDownloadedFcFontEntry::~gfxDownloadedFcFontEntry()
+{
+ if (mPatterns.Length() != 0) {
+ // Remove back reference to this font entry and the face in case
+ // anyone holds a reference to the pattern.
+ NS_ASSERTION(mPatterns.Length() == 1,
+ "More than one pattern in gfxDownloadedFcFontEntry!");
+ DelDownloadedFontEntry(mPatterns[0]);
+ FcPatternDel(mPatterns[0], FC_FT_FACE);
+ }
+ FT_Done_Face(mFace);
+ free((void*)mFontData);
+}
+
+typedef FcPattern* (*QueryFaceFunction)(const FT_Face face,
+ const FcChar8 *file, int id,
+ FcBlanks *blanks);
+
+void
+gfxDownloadedFcFontEntry::InitPattern()
+{
+ static QueryFaceFunction sQueryFacePtr =
+ reinterpret_cast<QueryFaceFunction>
+ (FindFunctionSymbol("FcFreeTypeQueryFace"));
+ FcPattern *pattern;
+
+ // FcFreeTypeQueryFace is the same function used to construct patterns for
+ // system fonts and so is the preferred function to use for this purpose.
+ // This will set up the langset property, which helps with sorting, and
+ // the foundry, fullname, and fontversion properties, which properly
+ // identify the font to fontconfig rules. However, FcFreeTypeQueryFace is
+ // available only from fontconfig-2.4.2 (December 2006). (CentOS 5.0 has
+ // fontconfig-2.4.1.)
+ if (sQueryFacePtr) {
+ // 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.
+ pattern =
+ (*sQueryFacePtr)(mFace,
+ gfxFontconfigUtils::ToFcChar8(""),
+ 0,
+ nullptr);
+ if (!pattern)
+ // Either OOM, or fontconfig chose to skip this font because it
+ // has "no encoded characters", which I think means "BDF and PCF
+ // fonts which are not in Unicode (or the effectively equivalent
+ // ISO Latin-1) encoding".
+ return;
+
+ // These properties don't make sense for this face without a file.
+ FcPatternDel(pattern, FC_FILE);
+ FcPatternDel(pattern, FC_INDEX);
+
+ } else {
+ // Do the minimum necessary to construct a pattern for sorting.
+
+ // FC_CHARSET is vital to determine which characters are supported.
+ nsAutoRef<FcCharSet> charset(FcFreeTypeCharSet(mFace, nullptr));
+ // If there are no characters then assume we don't know how to read
+ // this font.
+ if (!charset || FcCharSetCount(charset) == 0)
+ return;
+
+ pattern = FcPatternCreate();
+ FcPatternAddCharSet(pattern, FC_CHARSET, charset);
+
+ // FC_PIXEL_SIZE can be important for font selection of fixed-size
+ // fonts.
+ if (!(mFace->face_flags & FT_FACE_FLAG_SCALABLE)) {
+ for (FT_Int i = 0; i < mFace->num_fixed_sizes; ++i) {
+#if HAVE_FT_BITMAP_SIZE_Y_PPEM
+ double size = FLOAT_FROM_26_6(mFace->available_sizes[i].y_ppem);
+#else
+ double size = mFace->available_sizes[i].height;
+#endif
+ FcPatternAddDouble (pattern, FC_PIXEL_SIZE, size);
+ }
+
+ // Not sure whether this is important;
+ // imitating FcFreeTypeQueryFace:
+ FcPatternAddBool (pattern, FC_ANTIALIAS, FcFalse);
+ }
+
+ // Setting up the FC_LANGSET property is very difficult with the APIs
+ // available prior to FcFreeTypeQueryFace. Having no FC_LANGSET
+ // property seems better than having a property with an empty LangSet.
+ // With no FC_LANGSET property, fontconfig sort functions will
+ // consider this face to have the same priority as (otherwise equal)
+ // faces that have support for the primary requested language, but
+ // will not consider any language to have been satisfied (and so will
+ // continue to look for a face with language support in fallback
+ // fonts).
+ }
+
+ AdjustPatternToCSS(pattern);
+
+ FcPatternAddFTFace(pattern, FC_FT_FACE, mFace);
+ AddDownloadedFontEntry(pattern, this);
+
+ // There is never more than one pattern
+ // FIXME: Make this infallible after bug 968520 is done.
+ MOZ_ALWAYS_TRUE(mPatterns.AppendElement(fallible));
+ mPatterns[0].own(pattern);
+}
+
+static void ReleaseDownloadedFontEntry(void *data)
+{
+ gfxDownloadedFcFontEntry *downloadedFontEntry =
+ static_cast<gfxDownloadedFcFontEntry*>(data);
+ NS_RELEASE(downloadedFontEntry);
+}
+
+bool gfxDownloadedFcFontEntry::SetCairoFace(cairo_font_face_t *aFace)
+{
+ if (CAIRO_STATUS_SUCCESS !=
+ cairo_font_face_set_user_data(aFace, &sFontEntryKey, this,
+ ReleaseDownloadedFontEntry))
+ return false;
+
+ // Hold a reference to this font entry to keep the font face data.
+ NS_ADDREF(this);
+ return true;
+}
+
+hb_blob_t *
+gfxDownloadedFcFontEntry::GetFontTable(uint32_t aTableTag)
+{
+ // The entry already owns the (sanitized) sfnt data in mFontData,
+ // so we can just return a blob that "wraps" the appropriate chunk of it.
+ // The blob should not attempt to free its data, as the entire sfnt data
+ // will be freed when the font entry is deleted.
+ return gfxFontUtils::GetTableFromFontData(mFontData, aTableTag);
+}
+
+/*
+ * gfxFcFont
+ *
+ * This is a gfxFont implementation using a CAIRO_FONT_TYPE_FT
+ * cairo_scaled_font created from an FcPattern.
+ */
+
+class gfxFcFont : public gfxFontconfigFontBase {
+public:
+ virtual ~gfxFcFont();
+ static already_AddRefed<gfxFcFont>
+ GetOrMakeFont(FcPattern *aRequestedPattern, FcPattern *aFontPattern,
+ const gfxFontStyle *aFontStyle);
+
+ // return a cloned font resized and offset to simulate sub/superscript glyphs
+ virtual already_AddRefed<gfxFont>
+ GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel) override;
+
+protected:
+ virtual already_AddRefed<gfxFont> MakeScaledFont(gfxFontStyle *aFontStyle,
+ gfxFloat aFontScale);
+ virtual already_AddRefed<gfxFont> GetSmallCapsFont() override;
+
+private:
+ gfxFcFont(cairo_scaled_font_t *aCairoFont,
+ FcPattern *aPattern,
+ gfxFcFontEntry *aFontEntry,
+ const gfxFontStyle *aFontStyle);
+
+ // key for locating a gfxFcFont corresponding to a cairo_scaled_font
+ static cairo_user_data_key_t sGfxFontKey;
+};
+
+/**
+ * gfxFcFontSet:
+ *
+ * Translation from a desired FcPattern to a sorted set of font references
+ * (fontconfig cache data) and (when needed) fonts.
+ */
+
+class gfxFcFontSet final {
+public:
+ NS_INLINE_DECL_REFCOUNTING(gfxFcFontSet)
+
+ explicit gfxFcFontSet(FcPattern *aPattern,
+ gfxUserFontSet *aUserFontSet)
+ : mSortPattern(aPattern), mUserFontSet(aUserFontSet),
+ mFcFontsTrimmed(0),
+ mHaveFallbackFonts(false)
+ {
+ bool waitForUserFont;
+ mFcFontSet = SortPreferredFonts(waitForUserFont);
+ mWaitingForUserFont = waitForUserFont;
+ }
+
+ // A reference is held by the FontSet.
+ // The caller may add a ref to keep the font alive longer than the FontSet.
+ gfxFcFont *GetFontAt(uint32_t i, const gfxFontStyle *aFontStyle)
+ {
+ if (i >= mFonts.Length() || !mFonts[i].mFont) {
+ // GetFontPatternAt sets up mFonts
+ FcPattern *fontPattern = GetFontPatternAt(i);
+ if (!fontPattern)
+ return nullptr;
+
+ mFonts[i].mFont =
+ gfxFcFont::GetOrMakeFont(mSortPattern, fontPattern,
+ aFontStyle);
+ }
+ return mFonts[i].mFont;
+ }
+
+ FcPattern *GetFontPatternAt(uint32_t i);
+
+ bool WaitingForUserFont() const {
+ return mWaitingForUserFont;
+ }
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~gfxFcFontSet()
+ {
+ }
+
+ nsReturnRef<FcFontSet> SortPreferredFonts(bool& aWaitForUserFont);
+ nsReturnRef<FcFontSet> SortFallbackFonts();
+
+ struct FontEntry {
+ explicit FontEntry(FcPattern *aPattern) : mPattern(aPattern) {}
+ nsCountedRef<FcPattern> mPattern;
+ RefPtr<gfxFcFont> mFont;
+ };
+
+ struct LangSupportEntry {
+ LangSupportEntry(FcChar8 *aLang, FcLangResult aSupport) :
+ mLang(aLang), mBestSupport(aSupport) {}
+ FcChar8 *mLang;
+ FcLangResult mBestSupport;
+ };
+
+public:
+ // public for nsTArray
+ class LangComparator {
+ public:
+ bool Equals(const LangSupportEntry& a, const FcChar8 *b) const
+ {
+ return FcStrCmpIgnoreCase(a.mLang, b) == 0;
+ }
+ };
+
+private:
+ // The requested pattern
+ nsCountedRef<FcPattern> mSortPattern;
+ // Fonts from @font-face rules
+ RefPtr<gfxUserFontSet> mUserFontSet;
+ // A (trimmed) list of font patterns and fonts that is built up as
+ // required.
+ nsTArray<FontEntry> mFonts;
+ // Holds a list of font patterns that will be trimmed. This is first set
+ // to a list of preferred fonts. Then, if/when all the preferred fonts
+ // have been trimmed and added to mFonts, this is set to a list of
+ // fallback fonts.
+ nsAutoRef<FcFontSet> mFcFontSet;
+ // The set of characters supported by the fonts in mFonts.
+ nsAutoRef<FcCharSet> mCharSet;
+ // The index of the next font in mFcFontSet that has not yet been
+ // considered for mFonts.
+ int mFcFontsTrimmed;
+ // True iff fallback fonts are either stored in mFcFontSet or have been
+ // trimmed and added to mFonts (so that mFcFontSet is nullptr).
+ bool mHaveFallbackFonts;
+ // True iff there was a user font set with pending downloads,
+ // so the set may be updated when downloads complete
+ bool mWaitingForUserFont;
+};
+
+// Find the FcPattern for an @font-face font suitable for CSS family |aFamily|
+// and style |aStyle| properties.
+static const nsTArray< nsCountedRef<FcPattern> >*
+FindFontPatterns(gfxUserFontSet *mUserFontSet,
+ const nsACString &aFamily, uint8_t aStyle,
+ uint16_t aWeight, int16_t aStretch,
+ bool& aWaitForUserFont)
+{
+ // Convert to UTF16
+ NS_ConvertUTF8toUTF16 utf16Family(aFamily);
+
+ // needsBold is not used here. Instead synthetic bold is enabled through
+ // FcFontRenderPrepare when the weight in the requested pattern is
+ // compared against the weight in the font pattern.
+ bool needsBold;
+
+ gfxFontStyle style;
+ style.style = aStyle;
+ style.weight = aWeight;
+ style.stretch = aStretch;
+
+ gfxUserFcFontEntry *fontEntry = nullptr;
+ gfxFontFamily *family = mUserFontSet->LookupFamily(utf16Family);
+ if (family) {
+ gfxUserFontEntry* userFontEntry =
+ mUserFontSet->FindUserFontEntryAndLoad(family, style, needsBold,
+ aWaitForUserFont);
+ if (userFontEntry) {
+ fontEntry = static_cast<gfxUserFcFontEntry*>
+ (userFontEntry->GetPlatformFontEntry());
+ }
+
+ // Accept synthetic oblique for italic and oblique.
+ // xxx - this isn't really ideal behavior, for docs that only use a
+ // single italic face it will also pull down the normal face
+ // and probably never use it
+ if (!fontEntry && aStyle != NS_FONT_STYLE_NORMAL) {
+ style.style = NS_FONT_STYLE_NORMAL;
+ userFontEntry =
+ mUserFontSet->FindUserFontEntryAndLoad(family, style,
+ needsBold,
+ aWaitForUserFont);
+ if (userFontEntry) {
+ fontEntry = static_cast<gfxUserFcFontEntry*>
+ (userFontEntry->GetPlatformFontEntry());
+ }
+ }
+ }
+
+ if (!fontEntry) {
+ return nullptr;
+ }
+
+ return &fontEntry->GetPatterns();
+}
+
+typedef FcBool (*FcPatternRemoveFunction)(FcPattern *p, const char *object,
+ int id);
+
+// FcPatternRemove is available in fontconfig-2.3.0 (2005)
+static FcBool
+moz_FcPatternRemove(FcPattern *p, const char *object, int id)
+{
+ static FcPatternRemoveFunction sFcPatternRemovePtr =
+ reinterpret_cast<FcPatternRemoveFunction>
+ (FindFunctionSymbol("FcPatternRemove"));
+
+ if (!sFcPatternRemovePtr)
+ return FcFalse;
+
+ return (*sFcPatternRemovePtr)(p, object, id);
+}
+
+// fontconfig prefers a matching family or lang to pixelsize of bitmap
+// fonts. CSS suggests a tolerance of 20% on pixelsize.
+static bool
+SizeIsAcceptable(FcPattern *aFont, double aRequestedSize)
+{
+ double size;
+ int v = 0;
+ while (FcPatternGetDouble(aFont,
+ FC_PIXEL_SIZE, v, &size) == FcResultMatch) {
+ ++v;
+ if (5.0 * fabs(size - aRequestedSize) < aRequestedSize)
+ return true;
+ }
+
+ // No size means scalable
+ return v == 0;
+}
+
+// Sorting only the preferred fonts first usually saves having to sort through
+// every font on the system.
+nsReturnRef<FcFontSet>
+gfxFcFontSet::SortPreferredFonts(bool &aWaitForUserFont)
+{
+ aWaitForUserFont = false;
+
+ gfxFontconfigUtils *utils = gfxFontconfigUtils::GetFontconfigUtils();
+ if (!utils)
+ return nsReturnRef<FcFontSet>();
+
+ // The list of families in mSortPattern has values with both weak and
+ // strong bindings. Values with strong bindings should be preferred.
+ // Values with weak bindings are default fonts that should be considered
+ // only when the font provides the best support for a requested language
+ // or after other fonts have satisfied all the requested languages.
+ //
+ // There are no direct fontconfig APIs to get the binding type. The
+ // binding only takes effect in the sort and match functions.
+
+ // |requiredLangs| is a list of requested languages that have not yet been
+ // satisfied. gfxFontconfigUtils only sets one FC_LANG property value,
+ // but FcConfigSubstitute may add more values (e.g. prepending "en" to
+ // "ja" will use western fonts to render Latin/Arabic numerals in Japanese
+ // text.)
+ AutoTArray<LangSupportEntry,10> requiredLangs;
+ for (int v = 0; ; ++v) {
+ FcChar8 *lang;
+ FcResult result = FcPatternGetString(mSortPattern, FC_LANG, v, &lang);
+ if (result != FcResultMatch) {
+ // No need to check FcPatternGetLangSet() because
+ // gfxFontconfigUtils sets only a string value for FC_LANG and
+ // FcConfigSubstitute cannot add LangSets.
+ NS_ASSERTION(result != FcResultTypeMismatch,
+ "Expected a string for FC_LANG");
+ break;
+ }
+
+ if (!requiredLangs.Contains(lang, LangComparator())) {
+ FcLangResult bestLangSupport = utils->GetBestLangSupport(lang);
+ if (bestLangSupport != FcLangDifferentLang) {
+ requiredLangs.
+ AppendElement(LangSupportEntry(lang, bestLangSupport));
+ }
+ }
+ }
+
+ nsAutoRef<FcFontSet> fontSet(FcFontSetCreate());
+ if (!fontSet)
+ return fontSet.out();
+
+ // FcDefaultSubstitute() ensures a slant on mSortPattern, but, if that ever
+ // doesn't happen, Roman will be used.
+ int requestedSlant = FC_SLANT_ROMAN;
+ FcPatternGetInteger(mSortPattern, FC_SLANT, 0, &requestedSlant);
+ double requestedSize = -1.0;
+ FcPatternGetDouble(mSortPattern, FC_PIXEL_SIZE, 0, &requestedSize);
+
+ nsTHashtable<gfxFontconfigUtils::DepFcStrEntry> existingFamilies(32);
+ FcChar8 *family;
+ for (int v = 0;
+ FcPatternGetString(mSortPattern,
+ FC_FAMILY, v, &family) == FcResultMatch; ++v) {
+ const nsTArray< nsCountedRef<FcPattern> > *familyFonts = nullptr;
+
+ // Is this an @font-face family?
+ bool isUserFont = false;
+ if (mUserFontSet) {
+ // Have some @font-face definitions
+
+ nsDependentCString cFamily(gfxFontconfigUtils::ToCString(family));
+ NS_NAMED_LITERAL_CSTRING(userPrefix, FONT_FACE_FAMILY_PREFIX);
+
+ if (StringBeginsWith(cFamily, userPrefix)) {
+ isUserFont = true;
+
+ // Trim off the prefix
+ nsDependentCSubstring cssFamily(cFamily, userPrefix.Length());
+
+ uint8_t thebesStyle =
+ gfxFontconfigUtils::FcSlantToThebesStyle(requestedSlant);
+ uint16_t thebesWeight =
+ gfxFontconfigUtils::GetThebesWeight(mSortPattern);
+ int16_t thebesStretch =
+ gfxFontconfigUtils::GetThebesStretch(mSortPattern);
+
+ bool waitForUserFont;
+ familyFonts = FindFontPatterns(mUserFontSet, cssFamily,
+ thebesStyle,
+ thebesWeight, thebesStretch,
+ waitForUserFont);
+ if (waitForUserFont) {
+ aWaitForUserFont = true;
+ }
+ }
+ }
+
+ if (!isUserFont) {
+ familyFonts = &utils->GetFontsForFamily(family);
+ }
+
+ if (!familyFonts || familyFonts->Length() == 0) {
+ // There are no fonts matching this family, so there is no point
+ // in searching for this family in the FontSort.
+ //
+ // Perhaps the original pattern should be retained for
+ // FcFontRenderPrepare. However, the only a useful config
+ // substitution test against missing families that i can imagine
+ // would only be interested in the preferred family
+ // (qual="first"), so always keep the first family and use the
+ // same pattern for Sort and RenderPrepare.
+ if (v != 0 && moz_FcPatternRemove(mSortPattern, FC_FAMILY, v)) {
+ --v;
+ }
+ continue;
+ }
+
+ // Aliases seem to often end up occurring more than once, but
+ // duplicate families can't be removed from the sort pattern without
+ // knowing whether duplicates have the same binding.
+ gfxFontconfigUtils::DepFcStrEntry *familyEntry =
+ existingFamilies.PutEntry(family);
+ if (familyEntry) {
+ if (familyEntry->mKey) // old entry
+ continue;
+
+ familyEntry->mKey = family; // initialize new entry
+ }
+
+ for (uint32_t f = 0; f < familyFonts->Length(); ++f) {
+ FcPattern *font = familyFonts->ElementAt(f);
+
+ // Fix up the family name of user-font patterns, as the same
+ // font entry may be used (via the UserFontCache) for multiple
+ // CSS family names
+ if (isUserFont) {
+ font = FcPatternDuplicate(font);
+ FcPatternDel(font, FC_FAMILY);
+ FcPatternAddString(font, FC_FAMILY, family);
+ }
+
+ // User fonts are already filtered by slant (but not size) in
+ // mUserFontSet->FindUserFontEntry().
+ if (requestedSize != -1.0 && !SizeIsAcceptable(font, requestedSize))
+ continue;
+
+ for (uint32_t r = 0; r < requiredLangs.Length(); ++r) {
+ const LangSupportEntry& langEntry = requiredLangs[r];
+ FcLangResult support =
+ gfxFontconfigUtils::GetLangSupport(font, langEntry.mLang);
+ if (support <= langEntry.mBestSupport) { // lower is better
+ requiredLangs.RemoveElementAt(r);
+ --r;
+ }
+ }
+
+ // FcFontSetDestroy will remove a reference but FcFontSetAdd
+ // does _not_ take a reference!
+ if (FcFontSetAdd(fontSet, font)) {
+ // We don't add a reference here for user fonts, because we're
+ // using a local clone of the pattern (see above) in order to
+ // override the family name
+ if (!isUserFont) {
+ FcPatternReference(font);
+ }
+ }
+ }
+ }
+
+ FcPattern *truncateMarker = nullptr;
+ for (uint32_t r = 0; r < requiredLangs.Length(); ++r) {
+ const nsTArray< nsCountedRef<FcPattern> >& langFonts =
+ utils->GetFontsForLang(requiredLangs[r].mLang);
+
+ bool haveLangFont = false;
+ for (uint32_t f = 0; f < langFonts.Length(); ++f) {
+ FcPattern *font = langFonts[f];
+ if (requestedSize != -1.0 && !SizeIsAcceptable(font, requestedSize))
+ continue;
+
+ haveLangFont = true;
+ if (FcFontSetAdd(fontSet, font)) {
+ FcPatternReference(font);
+ }
+ }
+
+ if (!haveLangFont && langFonts.Length() > 0) {
+ // There is a font that supports this language but it didn't pass
+ // the slant and size criteria. Weak default font families should
+ // not be considered until the language has been satisfied.
+ //
+ // Insert a font that supports the language so that it will mark
+ // the position of fonts from weak families in the sorted set and
+ // they can be removed. The language and weak families will be
+ // considered in the fallback fonts, which use fontconfig's
+ // algorithm.
+ //
+ // Of the fonts that don't meet slant and size criteria, strong
+ // default font families should be considered before (other) fonts
+ // for this language, so this marker font will be removed (as well
+ // as the fonts from weak families), and strong families will be
+ // reconsidered in the fallback fonts.
+ FcPattern *font = langFonts[0];
+ if (FcFontSetAdd(fontSet, font)) {
+ FcPatternReference(font);
+ truncateMarker = font;
+ }
+ break;
+ }
+ }
+
+ FcFontSet *sets[1] = { fontSet };
+ FcResult result;
+#ifdef SOLARIS
+ // Get around a crash of FcFontSetSort when FcConfig is nullptr
+ // Solaris's FcFontSetSort needs an FcConfig (bug 474758)
+ fontSet.own(FcFontSetSort(FcConfigGetCurrent(), sets, 1, mSortPattern,
+ FcFalse, nullptr, &result));
+#else
+ fontSet.own(FcFontSetSort(nullptr, sets, 1, mSortPattern,
+ FcFalse, nullptr, &result));
+#endif
+
+ if (truncateMarker != nullptr && fontSet) {
+ nsAutoRef<FcFontSet> truncatedSet(FcFontSetCreate());
+
+ for (int f = 0; f < fontSet->nfont; ++f) {
+ FcPattern *font = fontSet->fonts[f];
+ if (font == truncateMarker)
+ break;
+
+ if (FcFontSetAdd(truncatedSet, font)) {
+ FcPatternReference(font);
+ }
+ }
+
+ fontSet.steal(truncatedSet);
+ }
+
+ return fontSet.out();
+}
+
+nsReturnRef<FcFontSet>
+gfxFcFontSet::SortFallbackFonts()
+{
+ // Setting trim to FcTrue would provide a much smaller (~ 1/10) FcFontSet,
+ // but would take much longer due to comparing all the character sets.
+ //
+ // The references to fonts in this FcFontSet are almost free
+ // as they are pointers into mmaped cache files.
+ //
+ // GetFontPatternAt() will trim lazily if and as needed, which will also
+ // remove duplicates of preferred fonts.
+ FcResult result;
+ return nsReturnRef<FcFontSet>(FcFontSort(nullptr, mSortPattern,
+ FcFalse, nullptr, &result));
+}
+
+// GetFontAt relies on this setting up all patterns up to |i|.
+FcPattern *
+gfxFcFontSet::GetFontPatternAt(uint32_t i)
+{
+ while (i >= mFonts.Length()) {
+ while (!mFcFontSet) {
+ if (mHaveFallbackFonts)
+ return nullptr;
+
+ mFcFontSet = SortFallbackFonts();
+ mHaveFallbackFonts = true;
+ mFcFontsTrimmed = 0;
+ // Loop to test that mFcFontSet is non-nullptr.
+ }
+
+ while (mFcFontsTrimmed < mFcFontSet->nfont) {
+ FcPattern *font = mFcFontSet->fonts[mFcFontsTrimmed];
+ ++mFcFontsTrimmed;
+
+ if (mFonts.Length() != 0) {
+ // See if the next font provides support for any extra
+ // characters. Most often the next font is not going to
+ // support more characters so check for a SubSet first before
+ // allocating a new CharSet with Union.
+ FcCharSet *supportedChars = mCharSet;
+ if (!supportedChars) {
+ FcPatternGetCharSet(mFonts[mFonts.Length() - 1].mPattern,
+ FC_CHARSET, 0, &supportedChars);
+ }
+
+ if (supportedChars) {
+ FcCharSet *newChars = nullptr;
+ FcPatternGetCharSet(font, FC_CHARSET, 0, &newChars);
+ if (newChars) {
+ if (FcCharSetIsSubset(newChars, supportedChars))
+ continue;
+
+ mCharSet.own(FcCharSetUnion(supportedChars, newChars));
+ } else if (!mCharSet) {
+ mCharSet.own(FcCharSetCopy(supportedChars));
+ }
+ }
+ }
+
+ mFonts.AppendElement(font);
+ if (mFonts.Length() >= i)
+ break;
+ }
+
+ if (mFcFontsTrimmed == mFcFontSet->nfont) {
+ // finished with this font set
+ mFcFontSet.reset();
+ }
+ }
+
+ return mFonts[i].mPattern;
+}
+
+#ifdef MOZ_WIDGET_GTK
+static void ApplyGdkScreenFontOptions(FcPattern *aPattern);
+#endif
+
+// Apply user settings and defaults to pattern in preparation for matching.
+static void
+PrepareSortPattern(FcPattern *aPattern, double aFallbackSize,
+ double aSizeAdjustFactor, 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);
+ } else {
+#ifdef MOZ_WIDGET_GTK
+ ApplyGdkScreenFontOptions(aPattern);
+#endif
+ }
+
+ // Protect against any fontconfig settings that may have incorrectly
+ // modified the pixelsize, and consider aSizeAdjustFactor.
+ double size = aFallbackSize;
+ if (FcPatternGetDouble(aPattern, FC_PIXEL_SIZE, 0, &size) != FcResultMatch
+ || aSizeAdjustFactor != 1.0) {
+ FcPatternDel(aPattern, FC_PIXEL_SIZE);
+ FcPatternAddDouble(aPattern, FC_PIXEL_SIZE, size * aSizeAdjustFactor);
+ }
+
+ FcDefaultSubstitute(aPattern);
+}
+
+/**
+ ** gfxPangoFontGroup
+ **/
+
+gfxPangoFontGroup::gfxPangoFontGroup(const FontFamilyList& aFontFamilyList,
+ const gfxFontStyle *aStyle,
+ gfxUserFontSet *aUserFontSet,
+ gfxFloat aDevToCssSize)
+ : gfxFontGroup(aFontFamilyList, aStyle, nullptr, aUserFontSet, aDevToCssSize),
+ mPangoLanguage(GuessPangoLanguage(aStyle->language))
+{
+ // This language is passed to the font for shaping.
+ // Shaping doesn't know about lang groups so make it a real language.
+ if (mPangoLanguage) {
+ mStyle.language = NS_Atomize(pango_language_to_string(mPangoLanguage));
+ }
+
+ // dummy entry, will be replaced when actually needed
+ mFonts.AppendElement(FamilyFace());
+ mSkipUpdateUserFonts = true;
+}
+
+gfxPangoFontGroup::~gfxPangoFontGroup()
+{
+}
+
+gfxFontGroup *
+gfxPangoFontGroup::Copy(const gfxFontStyle *aStyle)
+{
+ return new gfxPangoFontGroup(mFamilyList, aStyle, mUserFontSet, mDevToCssSize);
+}
+
+void
+gfxPangoFontGroup::FindGenericFontsPFG(FontFamilyType aGenericType,
+ nsIAtom *aLanguage,
+ void *aClosure)
+{
+ AutoTArray<nsString, 5> resolvedGenerics;
+ ResolveGenericFontNamesPFG(aGenericType, aLanguage, resolvedGenerics);
+ uint32_t g = 0, numGenerics = resolvedGenerics.Length();
+ for (g = 0; g < numGenerics; g++) {
+ FindPlatformFontPFG(resolvedGenerics[g], false, aClosure);
+ }
+}
+
+/* static */ void
+gfxPangoFontGroup::ResolveGenericFontNamesPFG(FontFamilyType aGenericType,
+ nsIAtom *aLanguage,
+ nsTArray<nsString>& aGenericFamilies)
+{
+ static const char kGeneric_serif[] = "serif";
+ static const char kGeneric_sans_serif[] = "sans-serif";
+ static const char kGeneric_monospace[] = "monospace";
+ static const char kGeneric_cursive[] = "cursive";
+ static const char kGeneric_fantasy[] = "fantasy";
+
+ // treat -moz-fixed as monospace
+ if (aGenericType == eFamily_moz_fixed) {
+ aGenericType = eFamily_monospace;
+ }
+
+ // type should be standard generic type at this point
+ NS_ASSERTION(aGenericType >= eFamily_serif &&
+ aGenericType <= eFamily_fantasy,
+ "standard generic font family type required");
+
+ // create the lang string
+ nsIAtom *langGroupAtom = nullptr;
+ nsAutoCString langGroupString;
+ if (aLanguage) {
+ if (!gLangService) {
+ CallGetService(NS_LANGUAGEATOMSERVICE_CONTRACTID, &gLangService);
+ }
+ if (gLangService) {
+ nsresult rv;
+ langGroupAtom = gLangService->GetLanguageGroup(aLanguage, &rv);
+ }
+ }
+ if (!langGroupAtom) {
+ langGroupAtom = nsGkAtoms::Unicode;
+ }
+ langGroupAtom->ToUTF8String(langGroupString);
+
+ // map generic type to string
+ const char *generic = nullptr;
+ switch (aGenericType) {
+ case eFamily_serif:
+ generic = kGeneric_serif;
+ break;
+ case eFamily_sans_serif:
+ generic = kGeneric_sans_serif;
+ break;
+ case eFamily_monospace:
+ generic = kGeneric_monospace;
+ break;
+ case eFamily_cursive:
+ generic = kGeneric_cursive;
+ break;
+ case eFamily_fantasy:
+ generic = kGeneric_fantasy;
+ break;
+ default:
+ break;
+ }
+
+ if (!generic) {
+ return;
+ }
+
+ aGenericFamilies.Clear();
+
+ // load family for "font.name.generic.lang"
+ nsAutoCString prefFontName("font.name.");
+ prefFontName.Append(generic);
+ prefFontName.Append('.');
+ prefFontName.Append(langGroupString);
+ gfxFontUtils::AppendPrefsFontList(prefFontName.get(),
+ aGenericFamilies);
+
+ // if lang has pref fonts, also load fonts for "font.name-list.generic.lang"
+ if (!aGenericFamilies.IsEmpty()) {
+ nsAutoCString prefFontListName("font.name-list.");
+ prefFontListName.Append(generic);
+ prefFontListName.Append('.');
+ prefFontListName.Append(langGroupString);
+ gfxFontUtils::AppendPrefsFontList(prefFontListName.get(),
+ aGenericFamilies);
+ }
+
+#if 0 // dump out generic mappings
+ printf("%s ===> ", prefFontName.get());
+ for (uint32_t k = 0; k < aGenericFamilies.Length(); k++) {
+ if (k > 0) printf(", ");
+ printf("%s", NS_ConvertUTF16toUTF8(aGenericFamilies[k]).get());
+ }
+ printf("\n");
+#endif
+}
+
+void gfxPangoFontGroup::EnumerateFontListPFG(nsIAtom *aLanguage, void *aClosure)
+{
+ // initialize fonts in the font family list
+ const nsTArray<FontFamilyName>& fontlist = mFamilyList.GetFontlist();
+
+ // lookup fonts in the fontlist
+ uint32_t i, numFonts = fontlist.Length();
+ for (i = 0; i < numFonts; i++) {
+ const FontFamilyName& name = fontlist[i];
+ if (name.IsNamed()) {
+ FindPlatformFontPFG(name.mName, true, aClosure);
+ } else {
+ FindGenericFontsPFG(name.mType, aLanguage, aClosure);
+ }
+ }
+
+ // if necessary, append default generic onto the end
+ if (mFamilyList.GetDefaultFontType() != eFamily_none &&
+ !mFamilyList.HasDefaultGeneric()) {
+ FindGenericFontsPFG(mFamilyList.GetDefaultFontType(),
+ aLanguage, aClosure);
+ }
+}
+
+void
+gfxPangoFontGroup::FindPlatformFontPFG(const nsAString& fontName,
+ bool aUseFontSet,
+ void *aClosure)
+{
+ nsTArray<nsString> *list = static_cast<nsTArray<nsString>*>(aClosure);
+
+ if (!list->Contains(fontName)) {
+ // names present in the user fontset are not matched against system fonts
+ if (aUseFontSet && mUserFontSet && mUserFontSet->HasFamily(fontName)) {
+ nsAutoString userFontName =
+ NS_LITERAL_STRING(FONT_FACE_FAMILY_PREFIX) + fontName;
+ list->AppendElement(userFontName);
+ } else {
+ list->AppendElement(fontName);
+ }
+ }
+}
+
+gfxFcFont *
+gfxPangoFontGroup::GetBaseFont()
+{
+ if (mFonts[0].Font() == nullptr) {
+ gfxFont* font = GetBaseFontSet()->GetFontAt(0, GetStyle());
+ mFonts[0] = FamilyFace(nullptr, font);
+ }
+
+ return static_cast<gfxFcFont*>(mFonts[0].Font());
+}
+
+gfxFont*
+gfxPangoFontGroup::GetFirstValidFont(uint32_t aCh)
+{
+ return GetFontAt(0);
+}
+
+gfxFont *
+gfxPangoFontGroup::GetFontAt(int32_t i, uint32_t aCh)
+{
+ // If it turns out to be hard for all clients that cache font
+ // groups to call UpdateUserFonts at appropriate times, we could
+ // instead consider just calling UpdateUserFonts from someplace
+ // more central (such as here).
+ NS_ASSERTION(!mUserFontSet || mCurrGeneration == GetGeneration(),
+ "Whoever was caching this font group should have "
+ "called UpdateUserFonts on it");
+
+ NS_PRECONDITION(i == 0, "Only have one font");
+
+ return GetBaseFont();
+}
+
+void
+gfxPangoFontGroup::UpdateUserFonts()
+{
+ uint64_t newGeneration = GetGeneration();
+ if (newGeneration == mCurrGeneration)
+ return;
+
+ mFonts[0] = FamilyFace();
+ mFontSets.Clear();
+ ClearCachedData();
+ mCurrGeneration = newGeneration;
+}
+
+already_AddRefed<gfxFcFontSet>
+gfxPangoFontGroup::MakeFontSet(PangoLanguage *aLang, gfxFloat aSizeAdjustFactor,
+ nsAutoRef<FcPattern> *aMatchPattern)
+{
+ const char *lang = pango_language_to_string(aLang);
+
+ RefPtr<nsIAtom> langGroup;
+ if (aLang != mPangoLanguage) {
+ // Set up langGroup for Mozilla's font prefs.
+ langGroup = NS_Atomize(lang);
+ }
+
+ AutoTArray<nsString, 20> fcFamilyList;
+ EnumerateFontListPFG(langGroup ? langGroup.get() : mStyle.language.get(),
+ &fcFamilyList);
+
+ // To consider: A fontset cache here could be helpful.
+
+ // Get a pattern suitable for matching.
+ nsAutoRef<FcPattern> pattern
+ (gfxFontconfigUtils::NewPattern(fcFamilyList, mStyle, lang));
+
+ PrepareSortPattern(pattern, mStyle.size, aSizeAdjustFactor, mStyle.printerFont);
+
+ RefPtr<gfxFcFontSet> fontset =
+ new gfxFcFontSet(pattern, mUserFontSet);
+
+ mSkipDrawing = fontset->WaitingForUserFont();
+
+ if (aMatchPattern)
+ aMatchPattern->steal(pattern);
+
+ return fontset.forget();
+}
+
+gfxPangoFontGroup::
+FontSetByLangEntry::FontSetByLangEntry(PangoLanguage *aLang,
+ gfxFcFontSet *aFontSet)
+ : mLang(aLang), mFontSet(aFontSet)
+{
+}
+
+gfxFcFontSet *
+gfxPangoFontGroup::GetFontSet(PangoLanguage *aLang)
+{
+ GetBaseFontSet(); // sets mSizeAdjustFactor and mFontSets[0]
+
+ if (!aLang)
+ return mFontSets[0].mFontSet;
+
+ for (uint32_t i = 0; i < mFontSets.Length(); ++i) {
+ if (mFontSets[i].mLang == aLang)
+ return mFontSets[i].mFontSet;
+ }
+
+ RefPtr<gfxFcFontSet> fontSet =
+ MakeFontSet(aLang, mSizeAdjustFactor);
+ mFontSets.AppendElement(FontSetByLangEntry(aLang, fontSet));
+
+ return fontSet;
+}
+
+already_AddRefed<gfxFont>
+gfxPangoFontGroup::FindFontForChar(uint32_t aCh, uint32_t aPrevCh,
+ uint32_t aNextCh, Script aRunScript,
+ gfxFont *aPrevMatchedFont,
+ uint8_t *aMatchType)
+{
+ 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)
+ uint8_t category = GetGeneralCategory(aCh);
+ if (category == HB_UNICODE_GENERAL_CATEGORY_CONTROL) {
+ return RefPtr<gfxFont>(aPrevMatchedFont).forget();
+ }
+
+ // if this character is a join-control or the previous is a join-causer,
+ // use the same font as the previous range if we can
+ if (gfxFontUtils::IsJoinControl(aCh) ||
+ gfxFontUtils::IsJoinCauser(aPrevCh)) {
+ if (aPrevMatchedFont->HasCharacter(aCh)) {
+ return RefPtr<gfxFont>(aPrevMatchedFont).forget();
+ }
+ }
+ }
+
+ // if this character is a variation selector,
+ // use the previous font regardless of whether it supports VS or not.
+ // otherwise the text run will be divided.
+ if (gfxFontUtils::IsVarSelector(aCh)) {
+ if (aPrevMatchedFont) {
+ return RefPtr<gfxFont>(aPrevMatchedFont).forget();
+ }
+ // VS alone. it's meaningless to search different fonts
+ return nullptr;
+ }
+
+ // The real fonts that fontconfig provides for generic/fallback families
+ // depend on the language used, so a different FontSet is used for each
+ // language (except for the variation below).
+ //
+ // With most fontconfig configurations any real family names prior to a
+ // fontconfig generic with corresponding fonts installed will still lead
+ // to the same leading fonts in each FontSet.
+ //
+ // There is an inefficiency here therefore because the same base FontSet
+ // could often be used if these real families support the character.
+ // However, with fontconfig aliases, it is difficult to distinguish
+ // where exactly alias fonts end and generic/fallback fonts begin.
+ //
+ // The variation from pure language-based matching used here is that the
+ // same primary/base font is always used irrespective of the language.
+ // This provides that SCRIPT_COMMON characters are consistently rendered
+ // with the same font (bug 339513 and bug 416725). This is particularly
+ // important with the word cache as script can't be reliably determined
+ // from surrounding words. It also often avoids the unnecessary extra
+ // FontSet efficiency mentioned above.
+ //
+ // However, in two situations, the base font is not checked before the
+ // language-specific FontSet.
+ //
+ // 1. When we don't have a language to make a good choice for
+ // the base font.
+ //
+ // 2. For system fonts, use the default Pango behavior to give
+ // consistency with other apps. This is relevant when un-localized
+ // builds are run in non-Latin locales. This special-case probably
+ // wouldn't be necessary but for bug 91190.
+
+ gfxFcFontSet *fontSet = GetBaseFontSet();
+ uint32_t nextFont = 0;
+ FcPattern *basePattern = nullptr;
+ if (!mStyle.systemFont && mPangoLanguage) {
+ basePattern = fontSet->GetFontPatternAt(0);
+ if (HasChar(basePattern, aCh)) {
+ *aMatchType = gfxTextRange::kFontGroup;
+ return RefPtr<gfxFont>(GetBaseFont()).forget();
+ }
+
+ nextFont = 1;
+ }
+
+ // Our MOZ_SCRIPT_* codes may not match the PangoScript enumeration values
+ // (if we're using ICU's codes), so convert by mapping through ISO 15924 tag.
+ // Note that PangoScript is defined to be compatible with GUnicodeScript:
+ // https://developer.gnome.org/pango/stable/pango-Scripts-and-Languages.html#PangoScript
+ const hb_tag_t scriptTag = GetScriptTagForCode(aRunScript);
+ const PangoScript script =
+ (const PangoScript)hb_glib_script_from_script(hb_script_from_iso15924_tag(scriptTag));
+
+ // Might be nice to call pango_language_includes_script only once for the
+ // run rather than for each character.
+ PangoLanguage *scriptLang;
+ if ((!basePattern ||
+ !pango_language_includes_script(mPangoLanguage, script)) &&
+ (scriptLang = pango_script_get_sample_language(script))) {
+ fontSet = GetFontSet(scriptLang);
+ nextFont = 0;
+ }
+
+ for (uint32_t i = nextFont;
+ FcPattern *pattern = fontSet->GetFontPatternAt(i);
+ ++i) {
+ if (pattern == basePattern) {
+ continue; // already checked basePattern
+ }
+
+ if (HasChar(pattern, aCh)) {
+ *aMatchType = gfxTextRange::kFontGroup;
+ return RefPtr<gfxFont>(fontSet->GetFontAt(i, GetStyle())).forget();
+ }
+ }
+
+ return nullptr;
+}
+
+/**
+ ** gfxFcFont
+ **/
+
+cairo_user_data_key_t gfxFcFont::sGfxFontKey;
+
+gfxFcFont::gfxFcFont(cairo_scaled_font_t *aCairoFont,
+ FcPattern *aPattern,
+ gfxFcFontEntry *aFontEntry,
+ const gfxFontStyle *aFontStyle)
+ : gfxFontconfigFontBase(aCairoFont, aPattern, aFontEntry, aFontStyle)
+{
+ cairo_scaled_font_set_user_data(mScaledFont, &sGfxFontKey, this, nullptr);
+}
+
+gfxFcFont::~gfxFcFont()
+{
+ cairo_scaled_font_set_user_data(mScaledFont,
+ &sGfxFontKey,
+ nullptr,
+ nullptr);
+}
+
+already_AddRefed<gfxFont>
+gfxFcFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel)
+{
+ gfxFontStyle style(*GetStyle());
+ style.AdjustForSubSuperscript(aAppUnitsPerDevPixel);
+ return MakeScaledFont(&style, style.size / GetStyle()->size);
+}
+
+already_AddRefed<gfxFont>
+gfxFcFont::MakeScaledFont(gfxFontStyle *aFontStyle, gfxFloat aScaleFactor)
+{
+ gfxFcFontEntry* fe = static_cast<gfxFcFontEntry*>(GetFontEntry());
+ RefPtr<gfxFont> font =
+ gfxFontCache::GetCache()->Lookup(fe, aFontStyle, nullptr);
+ if (font) {
+ return font.forget();
+ }
+
+ cairo_matrix_t fontMatrix;
+ cairo_scaled_font_get_font_matrix(mScaledFont, &fontMatrix);
+ cairo_matrix_scale(&fontMatrix, aScaleFactor, aScaleFactor);
+
+ cairo_matrix_t ctm;
+ cairo_scaled_font_get_ctm(mScaledFont, &ctm);
+
+ cairo_font_options_t *options = cairo_font_options_create();
+ cairo_scaled_font_get_font_options(mScaledFont, options);
+
+ cairo_scaled_font_t *newFont =
+ cairo_scaled_font_create(cairo_scaled_font_get_font_face(mScaledFont),
+ &fontMatrix, &ctm, options);
+ cairo_font_options_destroy(options);
+
+ font = new gfxFcFont(newFont, GetPattern(), fe, aFontStyle);
+ gfxFontCache::GetCache()->AddNew(font);
+ cairo_scaled_font_destroy(newFont);
+
+ return font.forget();
+}
+
+already_AddRefed<gfxFont>
+gfxFcFont::GetSmallCapsFont()
+{
+ gfxFontStyle style(*GetStyle());
+ style.size *= SMALL_CAPS_SCALE_FACTOR;
+ style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL;
+ return MakeScaledFont(&style, SMALL_CAPS_SCALE_FACTOR);
+}
+
+/* static */ void
+gfxPangoFontGroup::Shutdown()
+{
+ // Resetting gFTLibrary in case this is wanted again after a
+ // cairo_debug_reset_static_data.
+ gFTLibrary = nullptr;
+}
+
+/* static */ gfxFontEntry *
+gfxPangoFontGroup::NewFontEntry(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle)
+{
+ gfxFontconfigUtils *utils = gfxFontconfigUtils::GetFontconfigUtils();
+ if (!utils)
+ return nullptr;
+
+ // The font face name from @font-face { src: local() } is not well
+ // defined.
+ //
+ // On MS Windows, this name gets compared with
+ // ENUMLOGFONTEXW::elfFullName, which for OpenType fonts seems to be the
+ // full font name from the name table. For CFF OpenType fonts this is the
+ // same as the PostScript name, but for TrueType fonts it is usually
+ // different.
+ //
+ // On Mac, the font face name is compared with the PostScript name, even
+ // for TrueType fonts.
+ //
+ // Fontconfig only records the full font names, so the behavior here
+ // follows that on MS Windows. However, to provide the possibility
+ // of aliases to compensate for variations, the font face name is passed
+ // through FcConfigSubstitute.
+
+ nsAutoRef<FcPattern> pattern(FcPatternCreate());
+ if (!pattern)
+ return nullptr;
+
+ NS_ConvertUTF16toUTF8 fullname(aFontName);
+ FcPatternAddString(pattern, FC_FULLNAME,
+ gfxFontconfigUtils::ToFcChar8(fullname));
+ FcConfigSubstitute(nullptr, pattern, FcMatchPattern);
+
+ FcChar8 *name;
+ for (int v = 0;
+ FcPatternGetString(pattern, FC_FULLNAME, v, &name) == FcResultMatch;
+ ++v) {
+ const nsTArray< nsCountedRef<FcPattern> >& fonts =
+ utils->GetFontsForFullname(name);
+
+ if (fonts.Length() != 0)
+ return new gfxLocalFcFontEntry(aFontName,
+ aWeight,
+ aStretch,
+ aStyle,
+ fonts);
+ }
+
+ return nullptr;
+}
+
+/* static */ FT_Library
+gfxPangoFontGroup::GetFTLibrary()
+{
+ if (!gFTLibrary) {
+ // Use cairo's FT_Library so that cairo takes care of shutdown of the
+ // FT_Library after it has destroyed its font_faces, and FT_Done_Face
+ // has been called on each FT_Face, at least until this bug is fixed:
+ // https://bugs.freedesktop.org/show_bug.cgi?id=18857
+ //
+ // Cairo's FT_Library can be obtained from any cairo_scaled_font. The
+ // font properties requested here are chosen to get an FT_Face that is
+ // likely to be also used elsewhere.
+ gfxFontStyle style;
+ RefPtr<gfxPangoFontGroup> fontGroup =
+ new gfxPangoFontGroup(FontFamilyList(eFamily_sans_serif),
+ &style, nullptr, 1.0);
+
+ gfxFcFont *font = fontGroup->GetBaseFont();
+ if (!font)
+ return nullptr;
+
+ gfxFT2LockedFace face(font);
+ if (!face.get())
+ return nullptr;
+
+ gFTLibrary = face.get()->glyph->library;
+ }
+
+ return gFTLibrary;
+}
+
+/* static */ gfxFontEntry *
+gfxPangoFontGroup::NewFontEntry(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t* aFontData,
+ uint32_t aLength)
+{
+ // Ownership of aFontData is passed in here, and transferred to the
+ // new fontEntry, which will release it when no longer needed.
+
+ // Using face_index = 0 for the first face in the font, as we have no
+ // other information. FT_New_Memory_Face checks for a nullptr FT_Library.
+ FT_Face face;
+ FT_Error error =
+ FT_New_Memory_Face(GetFTLibrary(), aFontData, aLength, 0, &face);
+ if (error != 0) {
+ free((void*)aFontData);
+ return nullptr;
+ }
+
+ return new gfxDownloadedFcFontEntry(aFontName, aWeight,
+ aStretch, aStyle,
+ aFontData, face);
+}
+
+
+static double
+GetPixelSize(FcPattern *aPattern)
+{
+ double size;
+ if (FcPatternGetDouble(aPattern,
+ FC_PIXEL_SIZE, 0, &size) == FcResultMatch)
+ return size;
+
+ NS_NOTREACHED("No size on pattern");
+ return 0.0;
+}
+
+/**
+ * The following gfxFcFonts are accessed from the cairo_scaled_font or created
+ * from the FcPattern, not from the gfxFontCache hash table. The gfxFontCache
+ * hash table is keyed by desired family and style, whereas here we only know
+ * actual family and style. There may be more than one of these fonts with
+ * the same family and style, but different PangoFont and actual font face.
+ *
+ * The point of this is to record the exact font face for gfxTextRun glyph
+ * indices. The style of this font does not necessarily represent the exact
+ * gfxFontStyle used to build the text run. Notably, the language is not
+ * recorded.
+ */
+
+/* static */
+already_AddRefed<gfxFcFont>
+gfxFcFont::GetOrMakeFont(FcPattern *aRequestedPattern, FcPattern *aFontPattern,
+ const gfxFontStyle *aFontStyle)
+{
+ nsAutoRef<FcPattern> renderPattern
+ (FcFontRenderPrepare(nullptr, aRequestedPattern, aFontPattern));
+
+ // If synthetic bold/italic is not allowed by the style, adjust the
+ // resulting pattern to match the actual properties of the font.
+ if (!aFontStyle->allowSyntheticWeight) {
+ int weight;
+ if (FcPatternGetInteger(aFontPattern, FC_WEIGHT, 0,
+ &weight) == FcResultMatch) {
+ FcPatternDel(renderPattern, FC_WEIGHT);
+ FcPatternAddInteger(renderPattern, FC_WEIGHT, weight);
+ }
+ }
+ if (!aFontStyle->allowSyntheticStyle) {
+ int slant;
+ if (FcPatternGetInteger(aFontPattern, FC_SLANT, 0,
+ &slant) == FcResultMatch) {
+ FcPatternDel(renderPattern, FC_SLANT);
+ FcPatternAddInteger(renderPattern, FC_SLANT, slant);
+ }
+ }
+
+ cairo_font_face_t *face =
+ cairo_ft_font_face_create_for_pattern(renderPattern);
+
+ // Reuse an existing font entry if available.
+ RefPtr<gfxFcFontEntry> fe = gfxFcFontEntry::LookupFontEntry(face);
+ if (!fe) {
+ gfxDownloadedFcFontEntry *downloadedFontEntry =
+ GetDownloadedFontEntry(aFontPattern);
+ if (downloadedFontEntry) {
+ // Web font
+ fe = downloadedFontEntry;
+ if (cairo_font_face_status(face) == CAIRO_STATUS_SUCCESS) {
+ // cairo_font_face_t is using the web font data.
+ // Hold a reference to the font entry to keep the font face
+ // data.
+ if (!downloadedFontEntry->SetCairoFace(face)) {
+ // OOM. Let cairo pick a fallback font
+ cairo_font_face_destroy(face);
+ face = cairo_ft_font_face_create_for_pattern(aRequestedPattern);
+ fe = gfxFcFontEntry::LookupFontEntry(face);
+ }
+ }
+ }
+ if (!fe) {
+ // Get a unique name for the font face from the file and id.
+ nsAutoString name;
+ FcChar8 *fc_file;
+ if (FcPatternGetString(renderPattern,
+ FC_FILE, 0, &fc_file) == FcResultMatch) {
+ int index;
+ if (FcPatternGetInteger(renderPattern,
+ FC_INDEX, 0, &index) != FcResultMatch) {
+ // cairo defaults to 0.
+ index = 0;
+ }
+
+ AppendUTF8toUTF16(gfxFontconfigUtils::ToCString(fc_file), name);
+ if (index != 0) {
+ name.Append('/');
+ name.AppendInt(index);
+ }
+ }
+
+ fe = new gfxSystemFcFontEntry(face, aFontPattern, name);
+ }
+ }
+
+ gfxFontStyle style(*aFontStyle);
+ style.size = GetPixelSize(renderPattern);
+ style.style = gfxFontconfigUtils::GetThebesStyle(renderPattern);
+ style.weight = gfxFontconfigUtils::GetThebesWeight(renderPattern);
+
+ RefPtr<gfxFont> font =
+ gfxFontCache::GetCache()->Lookup(fe, &style, nullptr);
+ if (!font) {
+ // Note that a file/index pair (or FT_Face) and the gfxFontStyle are
+ // not necessarily enough to provide a key that will describe a unique
+ // font. cairoFont contains information from renderPattern, which is a
+ // fully resolved pattern from FcFontRenderPrepare.
+ // FcFontRenderPrepare takes the requested pattern and the face
+ // pattern as input and can modify elements of the resulting pattern
+ // that affect rendering but are not included in the gfxFontStyle.
+ cairo_scaled_font_t *cairoFont = CreateScaledFont(renderPattern, face);
+ font = new gfxFcFont(cairoFont, renderPattern, fe, &style);
+ gfxFontCache::GetCache()->AddNew(font);
+ cairo_scaled_font_destroy(cairoFont);
+ }
+
+ cairo_font_face_destroy(face);
+
+ RefPtr<gfxFcFont> retval(static_cast<gfxFcFont*>(font.get()));
+ return retval.forget();
+}
+
+gfxFcFontSet *
+gfxPangoFontGroup::GetBaseFontSet()
+{
+ if (mFontSets.Length() > 0)
+ return mFontSets[0].mFontSet;
+
+ mSizeAdjustFactor = 1.0; // will be adjusted below if necessary
+ nsAutoRef<FcPattern> pattern;
+ RefPtr<gfxFcFontSet> fontSet =
+ MakeFontSet(mPangoLanguage, mSizeAdjustFactor, &pattern);
+
+ double size = GetPixelSize(pattern);
+ if (size != 0.0 && mStyle.sizeAdjust > 0.0) {
+ gfxFcFont *font = fontSet->GetFontAt(0, GetStyle());
+ if (font) {
+ const gfxFont::Metrics& metrics =
+ font->GetMetrics(gfxFont::eHorizontal); // XXX vertical?
+
+ // The factor of 0.1 ensures that xHeight is sane so fonts don't
+ // become huge. Strictly ">" ensures that xHeight and emHeight are
+ // not both zero.
+ if (metrics.xHeight > 0.1 * metrics.emHeight) {
+ mSizeAdjustFactor =
+ mStyle.sizeAdjust * metrics.emHeight / metrics.xHeight;
+
+ size *= mSizeAdjustFactor;
+ FcPatternDel(pattern, FC_PIXEL_SIZE);
+ FcPatternAddDouble(pattern, FC_PIXEL_SIZE, size);
+
+ fontSet = new gfxFcFontSet(pattern, mUserFontSet);
+ }
+ }
+ }
+
+ PangoLanguage *pangoLang = mPangoLanguage;
+ FcChar8 *fcLang;
+ if (!pangoLang &&
+ FcPatternGetString(pattern, FC_LANG, 0, &fcLang) == FcResultMatch) {
+ pangoLang =
+ pango_language_from_string(gfxFontconfigUtils::ToCString(fcLang));
+ }
+
+ mFontSets.AppendElement(FontSetByLangEntry(pangoLang, fontSet));
+
+ return fontSet;
+}
+
+/**
+ ** gfxTextRun
+ *
+ * A serious problem:
+ *
+ * -- We draw with a font that's hinted for the CTM, but we measure with a font
+ * hinted to the identity matrix, so our "bounding metrics" may not be accurate.
+ *
+ **/
+
+// This will fetch an existing scaled_font if one exists.
+static cairo_scaled_font_t *
+CreateScaledFont(FcPattern *aPattern, cairo_font_face_t *aFace)
+{
+ double size = GetPixelSize(aPattern);
+
+ cairo_matrix_t fontMatrix;
+ FcMatrix *fcMatrix;
+ if (FcPatternGetMatrix(aPattern, FC_MATRIX, 0, &fcMatrix) == FcResultMatch)
+ cairo_matrix_init(&fontMatrix, fcMatrix->xx, -fcMatrix->yx, -fcMatrix->xy, fcMatrix->yy, 0, 0);
+ else
+ cairo_matrix_init_identity(&fontMatrix);
+ cairo_matrix_scale(&fontMatrix, size, size);
+
+ FcBool printing;
+ if (FcPatternGetBool(aPattern, PRINTING_FC_PROPERTY, 0, &printing) != FcResultMatch) {
+ printing = FcFalse;
+ }
+
+ // The cairo_scaled_font is created with a unit ctm so that metrics and
+ // positions are in user space, but this means that hinting effects will
+ // not be estimated accurately for non-unit transformations.
+ cairo_matrix_t identityMatrix;
+ cairo_matrix_init_identity(&identityMatrix);
+
+ // Font options are set explicitly here to improve cairo's caching
+ // behavior and to record the relevant parts of the pattern for
+ // SetupCairoFont (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.
+ cairo_font_options_t *fontOptions = cairo_font_options_create();
+
+ // 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.
+ if (printing) {
+ cairo_font_options_set_hint_metrics(fontOptions, CAIRO_HINT_METRICS_OFF);
+ } else {
+ cairo_font_options_set_hint_metrics(fontOptions, 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;
+ }
+
+ cairo_hint_style_t hint_style;
+ if (printing || !hinting) {
+ hint_style = CAIRO_HINT_STYLE_NONE;
+ } else {
+#ifdef FC_HINT_STYLE // FC_HINT_STYLE is available from fontconfig 2.2.91.
+ int fc_hintstyle;
+ if (FcPatternGetInteger(aPattern, FC_HINT_STYLE,
+ 0, &fc_hintstyle ) != FcResultMatch) {
+ fc_hintstyle = FC_HINT_FULL;
+ }
+ switch (fc_hintstyle) {
+ case FC_HINT_NONE:
+ hint_style = CAIRO_HINT_STYLE_NONE;
+ break;
+ case FC_HINT_SLIGHT:
+ hint_style = CAIRO_HINT_STYLE_SLIGHT;
+ break;
+ case FC_HINT_MEDIUM:
+ default: // This fallback mirrors _get_pattern_ft_options in cairo.
+ hint_style = CAIRO_HINT_STYLE_MEDIUM;
+ break;
+ case FC_HINT_FULL:
+ hint_style = CAIRO_HINT_STYLE_FULL;
+ break;
+ }
+#else // no FC_HINT_STYLE
+ hint_style = CAIRO_HINT_STYLE_FULL;
+#endif
+ }
+ cairo_font_options_set_hint_style(fontOptions, hint_style);
+
+ int rgba;
+ if (FcPatternGetInteger(aPattern,
+ FC_RGBA, 0, &rgba) != FcResultMatch) {
+ rgba = FC_RGBA_UNKNOWN;
+ }
+ cairo_subpixel_order_t subpixel_order = CAIRO_SUBPIXEL_ORDER_DEFAULT;
+ switch (rgba) {
+ case FC_RGBA_UNKNOWN:
+ case FC_RGBA_NONE:
+ default:
+ // There is no CAIRO_SUBPIXEL_ORDER_NONE. Subpixel antialiasing
+ // is disabled through cairo_antialias_t.
+ rgba = FC_RGBA_NONE;
+ // subpixel_order won't be used by the font as we won't use
+ // CAIRO_ANTIALIAS_SUBPIXEL, but don't leave it at default for
+ // caching reasons described above. Fall through:
+ MOZ_FALLTHROUGH;
+ case FC_RGBA_RGB:
+ subpixel_order = CAIRO_SUBPIXEL_ORDER_RGB;
+ break;
+ case FC_RGBA_BGR:
+ subpixel_order = CAIRO_SUBPIXEL_ORDER_BGR;
+ break;
+ case FC_RGBA_VRGB:
+ subpixel_order = CAIRO_SUBPIXEL_ORDER_VRGB;
+ break;
+ case FC_RGBA_VBGR:
+ subpixel_order = CAIRO_SUBPIXEL_ORDER_VBGR;
+ break;
+ }
+ cairo_font_options_set_subpixel_order(fontOptions, subpixel_order);
+
+ FcBool fc_antialias;
+ if (FcPatternGetBool(aPattern,
+ FC_ANTIALIAS, 0, &fc_antialias) != FcResultMatch) {
+ fc_antialias = FcTrue;
+ }
+ cairo_antialias_t antialias;
+ if (!fc_antialias) {
+ antialias = CAIRO_ANTIALIAS_NONE;
+ } else if (rgba == FC_RGBA_NONE) {
+ antialias = CAIRO_ANTIALIAS_GRAY;
+ } else {
+ antialias = CAIRO_ANTIALIAS_SUBPIXEL;
+ }
+ cairo_font_options_set_antialias(fontOptions, antialias);
+
+ cairo_scaled_font_t *scaledFont =
+ cairo_scaled_font_create(aFace, &fontMatrix, &identityMatrix,
+ fontOptions);
+
+ cairo_font_options_destroy(fontOptions);
+
+ NS_ASSERTION(cairo_scaled_font_status(scaledFont) == CAIRO_STATUS_SUCCESS,
+ "Failed to create scaled font");
+ return scaledFont;
+}
+
+/* static */
+PangoLanguage *
+GuessPangoLanguage(nsIAtom *aLanguage)
+{
+ if (!aLanguage)
+ return nullptr;
+
+ // Pango and fontconfig won't understand mozilla's internal langGroups, so
+ // find a real language.
+ nsAutoCString lang;
+ gfxFontconfigUtils::GetSampleLangForGroup(aLanguage, &lang);
+ if (lang.IsEmpty())
+ return nullptr;
+
+ return pango_language_from_string(lang.get());
+}
+
+#ifdef MOZ_WIDGET_GTK
+/***************************************************************************
+ *
+ * This function must be last in the file because it uses the system cairo
+ * library. Above this point the cairo library used is the tree cairo if
+ * MOZ_TREE_CAIRO.
+ */
+
+#if MOZ_TREE_CAIRO
+// Tree cairo symbols have different names. Disable their activation through
+// preprocessor macros.
+#undef cairo_ft_font_options_substitute
+
+// 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);
+}
+#endif
+
+static void
+ApplyGdkScreenFontOptions(FcPattern *aPattern)
+{
+ const cairo_font_options_t *options =
+ gdk_screen_get_font_options(gdk_screen_get_default());
+
+ cairo_ft_font_options_substitute(options, aPattern);
+}
+
+#endif // MOZ_WIDGET_GTK
+
diff --git a/gfx/thebes/gfxFontconfigFonts.h b/gfx/thebes/gfxFontconfigFonts.h
new file mode 100644
index 000000000..cea9d0dbf
--- /dev/null
+++ b/gfx/thebes/gfxFontconfigFonts.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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_FONTCONFIG_FONTS_H
+#define GFX_FONTCONFIG_FONTS_H
+
+#include "cairo.h"
+#include "gfxTypes.h"
+#include "gfxTextRun.h"
+
+#include "nsAutoRef.h"
+#include "nsTArray.h"
+
+#include <pango/pango.h>
+
+class gfxFcFontSet;
+class gfxFcFont;
+typedef struct _FcPattern FcPattern;
+typedef struct FT_FaceRec_* FT_Face;
+typedef struct FT_LibraryRec_ *FT_Library;
+
+class gfxPangoFontGroup : public gfxFontGroup {
+public:
+ gfxPangoFontGroup(const mozilla::FontFamilyList& aFontFamilyList,
+ const gfxFontStyle *aStyle,
+ gfxUserFontSet *aUserFontSet,
+ gfxFloat aDevToCssSize);
+ virtual ~gfxPangoFontGroup();
+
+ virtual gfxFontGroup *Copy(const gfxFontStyle *aStyle);
+
+ virtual gfxFont* GetFirstValidFont(uint32_t aCh = 0x20);
+
+ virtual void UpdateUserFonts();
+
+ virtual already_AddRefed<gfxFont>
+ FindFontForChar(uint32_t aCh, uint32_t aPrevCh, uint32_t aNextCh,
+ Script aRunScript, gfxFont *aPrevMatchedFont,
+ uint8_t *aMatchType);
+
+ static void Shutdown();
+
+ // Used for @font-face { src: local(); }
+ static gfxFontEntry *NewFontEntry(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle);
+ // Used for @font-face { src: url(); }
+ static gfxFontEntry *NewFontEntry(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t* aFontData,
+ uint32_t aLength);
+
+private:
+
+ virtual gfxFont *GetFontAt(int32_t i, uint32_t aCh = 0x20);
+
+ // @param aLang [in] language to use for pref fonts and system default font
+ // selection, or nullptr for the language guessed from the
+ // gfxFontStyle.
+ // The FontGroup holds a reference to this set.
+ gfxFcFontSet *GetFontSet(PangoLanguage *aLang = nullptr);
+
+ class FontSetByLangEntry {
+ public:
+ FontSetByLangEntry(PangoLanguage *aLang, gfxFcFontSet *aFontSet);
+ PangoLanguage *mLang;
+ RefPtr<gfxFcFontSet> mFontSet;
+ };
+ // There is only one of entry in this array unless characters from scripts
+ // of other languages are measured.
+ AutoTArray<FontSetByLangEntry,1> mFontSets;
+
+ gfxFloat mSizeAdjustFactor;
+ PangoLanguage *mPangoLanguage;
+
+ // @param aLang [in] language to use for pref fonts and system font
+ // resolution, or nullptr to guess a language from the gfxFontStyle.
+ // @param aMatchPattern [out] if non-nullptr, will return the pattern used.
+ already_AddRefed<gfxFcFontSet>
+ MakeFontSet(PangoLanguage *aLang, gfxFloat aSizeAdjustFactor,
+ nsAutoRef<FcPattern> *aMatchPattern = nullptr);
+
+ gfxFcFontSet *GetBaseFontSet();
+ gfxFcFont *GetBaseFont();
+
+ gfxFloat GetSizeAdjustFactor()
+ {
+ if (mFontSets.Length() == 0)
+ GetBaseFontSet();
+ return mSizeAdjustFactor;
+ }
+
+ // old helper methods from gfxFontGroup, moved here so that those methods
+ // can be revamped without affecting the legacy code here
+
+ // iterate over the fontlist, lookup names and expand generics
+ void EnumerateFontListPFG(nsIAtom *aLanguage, void *aClosure);
+
+ // expand a generic to a list of specific names based on prefs
+ void FindGenericFontsPFG(mozilla::FontFamilyType aGenericType,
+ nsIAtom *aLanguage,
+ void *aClosure);
+
+ // lookup and add a font with a given name (i.e. *not* a generic!)
+ void FindPlatformFontPFG(const nsAString& aName,
+ bool aUseFontSet,
+ void *aClosure);
+
+ static void
+ ResolveGenericFontNamesPFG(mozilla::FontFamilyType aGenericType,
+ nsIAtom *aLanguage,
+ nsTArray<nsString>& aGenericFamilies);
+
+
+ friend class gfxSystemFcFontEntry;
+ static FT_Library GetFTLibrary();
+};
+
+#endif /* GFX_FONTCONFIG_FONTS_H */
diff --git a/gfx/thebes/gfxFontconfigUtils.cpp b/gfx/thebes/gfxFontconfigUtils.cpp
new file mode 100644
index 000000000..5bf606c13
--- /dev/null
+++ b/gfx/thebes/gfxFontconfigUtils.cpp
@@ -0,0 +1,1100 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxFontconfigUtils.h"
+#include "gfxFont.h"
+#include "nsGkAtoms.h"
+
+#include <locale.h>
+#include <fontconfig/fontconfig.h>
+
+#include "nsServiceManagerUtils.h"
+#include "nsILanguageAtomService.h"
+#include "nsTArray.h"
+#include "mozilla/Preferences.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+
+#include "nsIAtom.h"
+#include "nsCRT.h"
+#include "gfxFontConstants.h"
+#include "mozilla/gfx/2D.h"
+
+using namespace mozilla;
+
+/* static */ gfxFontconfigUtils* gfxFontconfigUtils::sUtils = nullptr;
+static nsILanguageAtomService* gLangService = nullptr;
+
+/* static */ void
+gfxFontconfigUtils::Shutdown() {
+ if (sUtils) {
+ delete sUtils;
+ sUtils = nullptr;
+ }
+ NS_IF_RELEASE(gLangService);
+}
+
+/* static */ uint8_t
+gfxFontconfigUtils::FcSlantToThebesStyle(int aFcSlant)
+{
+ switch (aFcSlant) {
+ case FC_SLANT_ITALIC:
+ return NS_FONT_STYLE_ITALIC;
+ case FC_SLANT_OBLIQUE:
+ return NS_FONT_STYLE_OBLIQUE;
+ default:
+ return NS_FONT_STYLE_NORMAL;
+ }
+}
+
+/* static */ uint8_t
+gfxFontconfigUtils::GetThebesStyle(FcPattern *aPattern)
+{
+ int slant;
+ if (FcPatternGetInteger(aPattern, FC_SLANT, 0, &slant) != FcResultMatch) {
+ return NS_FONT_STYLE_NORMAL;
+ }
+
+ return FcSlantToThebesStyle(slant);
+}
+
+/* static */ int
+gfxFontconfigUtils::GetFcSlant(const gfxFontStyle& aFontStyle)
+{
+ if (aFontStyle.style == NS_FONT_STYLE_ITALIC)
+ return FC_SLANT_ITALIC;
+ if (aFontStyle.style == NS_FONT_STYLE_OBLIQUE)
+ return FC_SLANT_OBLIQUE;
+
+ return FC_SLANT_ROMAN;
+}
+
+// OS/2 weight classes were introduced in fontconfig-2.1.93 (2003).
+#ifndef FC_WEIGHT_THIN
+#define FC_WEIGHT_THIN 0 // 2.1.93
+#define FC_WEIGHT_EXTRALIGHT 40 // 2.1.93
+#define FC_WEIGHT_REGULAR 80 // 2.1.93
+#define FC_WEIGHT_EXTRABOLD 205 // 2.1.93
+#endif
+// book was introduced in fontconfig-2.2.90 (and so fontconfig-2.3.0 in 2005)
+#ifndef FC_WEIGHT_BOOK
+#define FC_WEIGHT_BOOK 75
+#endif
+// extra black was introduced in fontconfig-2.4.91 (2007)
+#ifndef FC_WEIGHT_EXTRABLACK
+#define FC_WEIGHT_EXTRABLACK 215
+#endif
+
+/* static */ uint16_t
+gfxFontconfigUtils::GetThebesWeight(FcPattern *aPattern)
+{
+ int weight;
+ if (FcPatternGetInteger(aPattern, FC_WEIGHT, 0, &weight) != FcResultMatch)
+ return NS_FONT_WEIGHT_NORMAL;
+
+ if (weight <= (FC_WEIGHT_THIN + FC_WEIGHT_EXTRALIGHT) / 2)
+ return 100;
+ if (weight <= (FC_WEIGHT_EXTRALIGHT + FC_WEIGHT_LIGHT) / 2)
+ return 200;
+ if (weight <= (FC_WEIGHT_LIGHT + FC_WEIGHT_BOOK) / 2)
+ return 300;
+ if (weight <= (FC_WEIGHT_REGULAR + FC_WEIGHT_MEDIUM) / 2)
+ // This includes FC_WEIGHT_BOOK
+ return 400;
+ if (weight <= (FC_WEIGHT_MEDIUM + FC_WEIGHT_DEMIBOLD) / 2)
+ return 500;
+ if (weight <= (FC_WEIGHT_DEMIBOLD + FC_WEIGHT_BOLD) / 2)
+ return 600;
+ if (weight <= (FC_WEIGHT_BOLD + FC_WEIGHT_EXTRABOLD) / 2)
+ return 700;
+ if (weight <= (FC_WEIGHT_EXTRABOLD + FC_WEIGHT_BLACK) / 2)
+ return 800;
+ if (weight <= FC_WEIGHT_BLACK)
+ return 900;
+
+ // including FC_WEIGHT_EXTRABLACK
+ return 901;
+}
+
+/* static */ int
+gfxFontconfigUtils::FcWeightForBaseWeight(int8_t aBaseWeight)
+{
+ NS_PRECONDITION(aBaseWeight >= 0 && aBaseWeight <= 10,
+ "base weight out of range");
+
+ switch (aBaseWeight) {
+ case 2:
+ return FC_WEIGHT_EXTRALIGHT;
+ case 3:
+ return FC_WEIGHT_LIGHT;
+ case 4:
+ return FC_WEIGHT_REGULAR;
+ case 5:
+ return FC_WEIGHT_MEDIUM;
+ case 6:
+ return FC_WEIGHT_DEMIBOLD;
+ case 7:
+ return FC_WEIGHT_BOLD;
+ case 8:
+ return FC_WEIGHT_EXTRABOLD;
+ case 9:
+ return FC_WEIGHT_BLACK;
+ }
+
+ // extremes
+ return aBaseWeight < 2 ? FC_WEIGHT_THIN : FC_WEIGHT_EXTRABLACK;
+}
+
+/* static */ int16_t
+gfxFontconfigUtils::GetThebesStretch(FcPattern *aPattern)
+{
+ int width;
+ if (FcPatternGetInteger(aPattern, FC_WIDTH, 0, &width) != FcResultMatch) {
+ return NS_FONT_STRETCH_NORMAL;
+ }
+
+ if (width <= (FC_WIDTH_ULTRACONDENSED + FC_WIDTH_EXTRACONDENSED) / 2) {
+ return NS_FONT_STRETCH_ULTRA_CONDENSED;
+ }
+ if (width <= (FC_WIDTH_EXTRACONDENSED + FC_WIDTH_CONDENSED) / 2) {
+ return NS_FONT_STRETCH_EXTRA_CONDENSED;
+ }
+ if (width <= (FC_WIDTH_CONDENSED + FC_WIDTH_SEMICONDENSED) / 2) {
+ return NS_FONT_STRETCH_CONDENSED;
+ }
+ if (width <= (FC_WIDTH_SEMICONDENSED + FC_WIDTH_NORMAL) / 2) {
+ return NS_FONT_STRETCH_SEMI_CONDENSED;
+ }
+ if (width <= (FC_WIDTH_NORMAL + FC_WIDTH_SEMIEXPANDED) / 2) {
+ return NS_FONT_STRETCH_NORMAL;
+ }
+ if (width <= (FC_WIDTH_SEMIEXPANDED + FC_WIDTH_EXPANDED) / 2) {
+ return NS_FONT_STRETCH_SEMI_EXPANDED;
+ }
+ if (width <= (FC_WIDTH_EXPANDED + FC_WIDTH_EXTRAEXPANDED) / 2) {
+ return NS_FONT_STRETCH_EXPANDED;
+ }
+ if (width <= (FC_WIDTH_EXTRAEXPANDED + FC_WIDTH_ULTRAEXPANDED) / 2) {
+ return NS_FONT_STRETCH_EXTRA_EXPANDED;
+ }
+ return NS_FONT_STRETCH_ULTRA_EXPANDED;
+}
+
+/* static */ int
+gfxFontconfigUtils::FcWidthForThebesStretch(int16_t aStretch)
+{
+ switch (aStretch) {
+ default: // this will catch "normal" (0) as well as out-of-range values
+ return FC_WIDTH_NORMAL;
+ case NS_FONT_STRETCH_ULTRA_CONDENSED:
+ return FC_WIDTH_ULTRACONDENSED;
+ case NS_FONT_STRETCH_EXTRA_CONDENSED:
+ return FC_WIDTH_EXTRACONDENSED;
+ case NS_FONT_STRETCH_CONDENSED:
+ return FC_WIDTH_CONDENSED;
+ case NS_FONT_STRETCH_SEMI_CONDENSED:
+ return FC_WIDTH_SEMICONDENSED;
+ case NS_FONT_STRETCH_SEMI_EXPANDED:
+ return FC_WIDTH_SEMIEXPANDED;
+ case NS_FONT_STRETCH_EXPANDED:
+ return FC_WIDTH_EXPANDED;
+ case NS_FONT_STRETCH_EXTRA_EXPANDED:
+ return FC_WIDTH_EXTRAEXPANDED;
+ case NS_FONT_STRETCH_ULTRA_EXPANDED:
+ return FC_WIDTH_ULTRAEXPANDED;
+ }
+}
+
+// This makes a guess at an FC_WEIGHT corresponding to a base weight and
+// offset (without any knowledge of which weights are available).
+
+/* static */ int
+GuessFcWeight(const gfxFontStyle& aFontStyle)
+{
+ /*
+ * weights come in two parts crammed into one
+ * integer -- the "base" weight is weight / 100,
+ * the rest of the value is the "offset" from that
+ * weight -- the number of steps to move to adjust
+ * the weight in the list of supported font weights,
+ * this value can be negative or positive.
+ */
+ int8_t weight = aFontStyle.ComputeWeight();
+
+ // ComputeWeight trimmed the range of weights for us
+ NS_ASSERTION(weight >= 0 && weight <= 10,
+ "base weight out of range");
+
+ return gfxFontconfigUtils::FcWeightForBaseWeight(weight);
+}
+
+static void
+AddString(FcPattern *aPattern, const char *object, const char *aString)
+{
+ FcPatternAddString(aPattern, object,
+ gfxFontconfigUtils::ToFcChar8(aString));
+}
+
+static void
+AddWeakString(FcPattern *aPattern, const char *object, const char *aString)
+{
+ FcValue value;
+ value.type = FcTypeString;
+ value.u.s = gfxFontconfigUtils::ToFcChar8(aString);
+
+ FcPatternAddWeak(aPattern, object, value, FcTrue);
+}
+
+static void
+AddLangGroup(FcPattern *aPattern, nsIAtom *aLangGroup)
+{
+ // Translate from mozilla's internal mapping into fontconfig's
+ nsAutoCString lang;
+ gfxFontconfigUtils::GetSampleLangForGroup(aLangGroup, &lang);
+
+ if (!lang.IsEmpty()) {
+ AddString(aPattern, FC_LANG, lang.get());
+ }
+}
+
+nsReturnRef<FcPattern>
+gfxFontconfigUtils::NewPattern(const nsTArray<nsString>& aFamilies,
+ const gfxFontStyle& aFontStyle,
+ const char *aLang)
+{
+ static const char* sFontconfigGenerics[] =
+ { "sans-serif", "serif", "monospace", "fantasy", "cursive" };
+
+ nsAutoRef<FcPattern> pattern(FcPatternCreate());
+ if (!pattern)
+ return nsReturnRef<FcPattern>();
+
+ FcPatternAddDouble(pattern, FC_PIXEL_SIZE, aFontStyle.size);
+ FcPatternAddInteger(pattern, FC_SLANT, GetFcSlant(aFontStyle));
+ FcPatternAddInteger(pattern, FC_WEIGHT, GuessFcWeight(aFontStyle));
+ FcPatternAddInteger(pattern, FC_WIDTH, FcWidthForThebesStretch(aFontStyle.stretch));
+
+ if (aLang) {
+ AddString(pattern, FC_LANG, aLang);
+ }
+
+ bool useWeakBinding = false;
+ for (uint32_t i = 0; i < aFamilies.Length(); ++i) {
+ NS_ConvertUTF16toUTF8 family(aFamilies[i]);
+ if (!useWeakBinding) {
+ AddString(pattern, FC_FAMILY, family.get());
+
+ // fontconfig generic families are typically implemented with weak
+ // aliases (so that the preferred font depends on language).
+ // However, this would give them lower priority than subsequent
+ // non-generic families in the list. To ensure that subsequent
+ // families do not have a higher priority, they are given weak
+ // bindings.
+ for (uint32_t g = 0;
+ g < ArrayLength(sFontconfigGenerics);
+ ++g) {
+ if (0 == FcStrCmpIgnoreCase(ToFcChar8(sFontconfigGenerics[g]),
+ ToFcChar8(family.get()))) {
+ useWeakBinding = true;
+ break;
+ }
+ }
+ } else {
+ AddWeakString(pattern, FC_FAMILY, family.get());
+ }
+ }
+
+ return pattern.out();
+}
+
+gfxFontconfigUtils::gfxFontconfigUtils()
+ : mFontsByFamily(32)
+ , mFontsByFullname(32)
+ , mLangSupportTable(32)
+ , mLastConfig(nullptr)
+#ifdef MOZ_BUNDLED_FONTS
+ , mBundledFontsInitialized(false)
+#endif
+{
+ UpdateFontListInternal();
+}
+
+nsresult
+gfxFontconfigUtils::GetFontList(nsIAtom *aLangGroup,
+ const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts)
+{
+ aListOfFonts.Clear();
+
+ nsTArray<nsCString> fonts;
+ nsresult rv = GetFontListInternal(fonts, aLangGroup);
+ if (NS_FAILED(rv))
+ return rv;
+
+ for (uint32_t i = 0; i < fonts.Length(); ++i) {
+ aListOfFonts.AppendElement(NS_ConvertUTF8toUTF16(fonts[i]));
+ }
+
+ aListOfFonts.Sort();
+
+ int32_t serif = 0, sansSerif = 0, monospace = 0;
+
+ // Fontconfig supports 3 generic fonts, "serif", "sans-serif", and
+ // "monospace", slightly different from CSS's 5.
+ if (aGenericFamily.IsEmpty())
+ serif = sansSerif = monospace = 1;
+ else if (aGenericFamily.LowerCaseEqualsLiteral("serif"))
+ serif = 1;
+ else if (aGenericFamily.LowerCaseEqualsLiteral("sans-serif"))
+ sansSerif = 1;
+ else if (aGenericFamily.LowerCaseEqualsLiteral("monospace"))
+ monospace = 1;
+ else if (aGenericFamily.LowerCaseEqualsLiteral("cursive") ||
+ aGenericFamily.LowerCaseEqualsLiteral("fantasy"))
+ serif = sansSerif = 1;
+ else
+ NS_NOTREACHED("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, NS_LITERAL_STRING("monospace"));
+ if (sansSerif)
+ aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("sans-serif"));
+ if (serif)
+ aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("serif"));
+
+ return NS_OK;
+}
+
+struct MozLangGroupData {
+ nsIAtom* 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_telu, "te" },
+ { nsGkAtoms::x_tibt, "bo" },
+ { nsGkAtoms::Unicode, 0 },
+};
+
+static bool
+TryLangForGroup(const nsACString& aOSLang, nsIAtom *aLangGroup,
+ nsACString *aFcLang)
+{
+ // 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".
+ // nsILanguageAtomService takes languages in the form language-subtag,
+ // where subtag may be a territory. fontconfig and nsILanguageAtomService
+ // 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;
+ }
+
+ nsIAtom *atom =
+ gLangService->LookupLanguage(*aFcLang);
+
+ return atom == aLangGroup;
+}
+
+/* static */ void
+gfxFontconfigUtils::GetSampleLangForGroup(nsIAtom *aLangGroup,
+ nsACString *aFcLang)
+{
+ NS_PRECONDITION(aFcLang != nullptr, "aFcLang must not be NULL");
+
+ const MozLangGroupData *langGroup = nullptr;
+
+ for (unsigned int i = 0; i < ArrayLength(MozLangGroups); ++i) {
+ if (aLangGroup == MozLangGroups[i].mozLangGroup) {
+ langGroup = &MozLangGroups[i];
+ break;
+ }
+ }
+
+ if (!langGroup) {
+ // Not a special mozilla language group.
+ // Use aLangGroup as a language code.
+ aLangGroup->ToUTF8String(*aFcLang);
+ return;
+ }
+
+ // Check the environment for the users preferred language that corresponds
+ // to this langGroup.
+ if (!gLangService) {
+ CallGetService(NS_LANGUAGEATOMSERVICE_CONTRACTID, &gLangService);
+ }
+
+ if (gLangService) {
+ 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),
+ aLangGroup, aFcLang))
+ return;
+
+ if (*pos == '\0')
+ break;
+
+ languages = pos + 1;
+ }
+ }
+ }
+ const char *ctype = setlocale(LC_CTYPE, nullptr);
+ if (ctype &&
+ TryLangForGroup(nsDependentCString(ctype), aLangGroup, aFcLang))
+ return;
+ }
+
+ if (langGroup->defaultLang) {
+ aFcLang->Assign(langGroup->defaultLang);
+ } else {
+ aFcLang->Truncate();
+ }
+}
+
+nsresult
+gfxFontconfigUtils::GetFontListInternal(nsTArray<nsCString>& aListOfFonts,
+ nsIAtom *aLangGroup)
+{
+ FcPattern *pat = nullptr;
+ FcObjectSet *os = nullptr;
+ FcFontSet *fs = nullptr;
+ nsresult rv = NS_ERROR_FAILURE;
+
+ aListOfFonts.Clear();
+
+ pat = FcPatternCreate();
+ if (!pat)
+ goto end;
+
+ os = FcObjectSetBuild(FC_FAMILY, nullptr);
+ if (!os)
+ goto end;
+
+ // take the pattern and add the lang group to it
+ if (aLangGroup) {
+ AddLangGroup(pat, aLangGroup);
+ }
+
+ fs = FcFontList(nullptr, pat, os);
+ if (!fs)
+ goto end;
+
+ for (int i = 0; i < fs->nfont; i++) {
+ char *family;
+
+ if (FcPatternGetString(fs->fonts[i], FC_FAMILY, 0,
+ (FcChar8 **) &family) != FcResultMatch)
+ {
+ continue;
+ }
+
+ // Remove duplicates...
+ nsAutoCString strFamily(family);
+ if (aListOfFonts.Contains(strFamily))
+ continue;
+
+ aListOfFonts.AppendElement(strFamily);
+ }
+
+ rv = NS_OK;
+
+ end:
+ if (NS_FAILED(rv))
+ aListOfFonts.Clear();
+
+ if (pat)
+ FcPatternDestroy(pat);
+ if (os)
+ FcObjectSetDestroy(os);
+ if (fs)
+ FcFontSetDestroy(fs);
+
+ return rv;
+}
+
+nsresult
+gfxFontconfigUtils::UpdateFontList()
+{
+ return UpdateFontListInternal(true);
+}
+
+nsresult
+gfxFontconfigUtils::UpdateFontListInternal(bool aForce)
+{
+ if (!aForce) {
+ // This checks periodically according to fontconfig's configured
+ // <rescan> interval.
+ FcInitBringUptoDate();
+ } else if (!FcConfigUptoDate(nullptr)) { // check now with aForce
+ mLastConfig = nullptr;
+ FcInitReinitialize();
+ }
+
+ // FcInitReinitialize() (used by FcInitBringUptoDate) creates a new config
+ // before destroying the old config, so the only way that we'd miss an
+ // update is if fontconfig did more than one update and the memory for the
+ // most recent config happened to be at the same location as the original
+ // config.
+ FcConfig *currentConfig = FcConfigGetCurrent();
+ if (currentConfig == mLastConfig)
+ return NS_OK;
+
+#ifdef MOZ_BUNDLED_FONTS
+ ActivateBundledFonts();
+#endif
+
+ // These FcFontSets are owned by fontconfig
+ FcFontSet *fontSets[] = {
+ FcConfigGetFonts(currentConfig, FcSetSystem)
+#ifdef MOZ_BUNDLED_FONTS
+ , FcConfigGetFonts(currentConfig, FcSetApplication)
+#endif
+ };
+
+ mFontsByFamily.Clear();
+ mFontsByFullname.Clear();
+ mLangSupportTable.Clear();
+
+ // Record the existing font families
+ for (unsigned fs = 0; fs < ArrayLength(fontSets); ++fs) {
+ FcFontSet *fontSet = fontSets[fs];
+ if (!fontSet) { // the application set might not exist
+ continue;
+ }
+ for (int f = 0; f < fontSet->nfont; ++f) {
+ FcPattern *font = fontSet->fonts[f];
+
+ FcChar8 *family;
+ for (int v = 0;
+ FcPatternGetString(font, FC_FAMILY, v, &family) == FcResultMatch;
+ ++v) {
+ FontsByFcStrEntry *entry = mFontsByFamily.PutEntry(family);
+ if (entry) {
+ bool added = entry->AddFont(font);
+
+ if (!entry->mKey) {
+ // The reference to the font pattern keeps the pointer
+ // to string for the key valid. If adding the font
+ // failed then the entry must be removed.
+ if (added) {
+ entry->mKey = family;
+ } else {
+ mFontsByFamily.RemoveEntry(entry);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ mLastConfig = currentConfig;
+ return NS_OK;
+}
+
+nsresult
+gfxFontconfigUtils::GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName)
+{
+ aFamilyName.Truncate();
+
+ // The fontconfig has generic family names in the font list.
+ if (aFontName.EqualsLiteral("serif") ||
+ aFontName.EqualsLiteral("sans-serif") ||
+ aFontName.EqualsLiteral("monospace")) {
+ aFamilyName.Assign(aFontName);
+ return NS_OK;
+ }
+
+ nsresult rv = UpdateFontListInternal();
+ if (NS_FAILED(rv))
+ return rv;
+
+ NS_ConvertUTF16toUTF8 fontname(aFontName);
+
+ // return empty string if no such family exists
+ if (!IsExistingFamily(fontname))
+ return NS_OK;
+
+ FcPattern *pat = nullptr;
+ FcObjectSet *os = nullptr;
+ FcFontSet *givenFS = nullptr;
+ nsTArray<nsCString> candidates;
+ FcFontSet *candidateFS = nullptr;
+ rv = NS_ERROR_FAILURE;
+
+ pat = FcPatternCreate();
+ if (!pat)
+ goto end;
+
+ FcPatternAddString(pat, FC_FAMILY, (FcChar8 *)fontname.get());
+
+ os = FcObjectSetBuild(FC_FAMILY, FC_FILE, FC_INDEX, nullptr);
+ if (!os)
+ goto end;
+
+ givenFS = FcFontList(nullptr, pat, os);
+ if (!givenFS)
+ goto end;
+
+ // The first value associated with a FC_FAMILY property is the family
+ // returned by GetFontList(), so use this value if appropriate.
+
+ // See if there is a font face with first family equal to the given family.
+ 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 (fontname.Equals(first)) {
+ aFamilyName.Assign(aFontName);
+ rv = NS_OK;
+ goto end;
+ }
+ }
+ }
+
+ // See if any of the first family names represent the same set of font
+ // faces as the given family.
+ for (uint32_t j = 0; j < candidates.Length(); ++j) {
+ FcPatternDel(pat, FC_FAMILY);
+ FcPatternAddString(pat, FC_FAMILY, (FcChar8 *)candidates[j].get());
+
+ candidateFS = FcFontList(nullptr, pat, os);
+ if (!candidateFS)
+ goto end;
+
+ 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) {
+ AppendUTF8toUTF16(candidates[j], aFamilyName);
+ rv = NS_OK;
+ goto end;
+ }
+ }
+
+ // No match found; return empty string.
+ rv = NS_OK;
+
+ end:
+ if (pat)
+ FcPatternDestroy(pat);
+ if (os)
+ FcObjectSetDestroy(os);
+ if (givenFS)
+ FcFontSetDestroy(givenFS);
+ if (candidateFS)
+ FcFontSetDestroy(candidateFS);
+
+ return rv;
+}
+
+bool
+gfxFontconfigUtils::IsExistingFamily(const nsCString& aFamilyName)
+{
+ return mFontsByFamily.GetEntry(ToFcChar8(aFamilyName)) != nullptr;
+}
+
+const nsTArray< nsCountedRef<FcPattern> >&
+gfxFontconfigUtils::GetFontsForFamily(const FcChar8 *aFamilyName)
+{
+ FontsByFcStrEntry *entry = mFontsByFamily.GetEntry(aFamilyName);
+
+ if (!entry)
+ return mEmptyPatternArray;
+
+ return entry->GetFonts();
+}
+
+// Fontconfig only provides a fullname property for fonts in formats with SFNT
+// wrappers. For other font formats (including PCF and PS Type 1), a fullname
+// must be generated from the family and style properties. Only the first
+// family and style is checked, but that should be OK, as I don't expect
+// non-SFNT fonts to have multiple families or styles.
+bool
+gfxFontconfigUtils::GetFullnameFromFamilyAndStyle(FcPattern *aFont,
+ nsACString *aFullname)
+{
+ FcChar8 *family;
+ if (FcPatternGetString(aFont, FC_FAMILY, 0, &family) != FcResultMatch)
+ return false;
+
+ aFullname->Truncate();
+ aFullname->Append(ToCString(family));
+
+ FcChar8 *style;
+ if (FcPatternGetString(aFont, FC_STYLE, 0, &style) == FcResultMatch &&
+ strcmp(ToCString(style), "Regular") != 0) {
+ aFullname->Append(' ');
+ aFullname->Append(ToCString(style));
+ }
+
+ return true;
+}
+
+bool
+gfxFontconfigUtils::FontsByFullnameEntry::KeyEquals(KeyTypePointer aKey) const
+{
+ const FcChar8 *key = mKey;
+ // If mKey is nullptr, key comes from the style and family of the first
+ // font.
+ nsAutoCString fullname;
+ if (!key) {
+ NS_ASSERTION(mFonts.Length(), "No font in FontsByFullnameEntry!");
+ GetFullnameFromFamilyAndStyle(mFonts[0], &fullname);
+
+ key = ToFcChar8(fullname);
+ }
+
+ return FcStrCmpIgnoreCase(aKey, key) == 0;
+}
+
+void
+gfxFontconfigUtils::AddFullnameEntries()
+{
+ // These FcFontSets are owned by fontconfig
+ FcFontSet *fontSets[] = {
+ FcConfigGetFonts(nullptr, FcSetSystem)
+#ifdef MOZ_BUNDLED_FONTS
+ , FcConfigGetFonts(nullptr, FcSetApplication)
+#endif
+ };
+
+ for (unsigned fs = 0; fs < ArrayLength(fontSets); ++fs) {
+ FcFontSet *fontSet = fontSets[fs];
+ if (!fontSet) {
+ continue;
+ }
+ // Record the existing font families
+ for (int f = 0; f < fontSet->nfont; ++f) {
+ FcPattern *font = fontSet->fonts[f];
+
+ int v = 0;
+ FcChar8 *fullname;
+ while (FcPatternGetString(font,
+ FC_FULLNAME, v, &fullname) == FcResultMatch) {
+ FontsByFullnameEntry *entry =
+ mFontsByFullname.PutEntry(fullname);
+ if (entry) {
+ // entry always has space for one font, so the first
+ // AddFont will always succeed, and so the entry will
+ // always have a font from which to obtain the key.
+ bool added = entry->AddFont(font);
+ // The key may be nullptr either if this is the first
+ // font, or if the first font does not have a fullname
+ // property, and so the key is obtained from the font.
+ // Set the key in both cases. The check that AddFont
+ // succeeded is required for the second case.
+ if (!entry->mKey && added) {
+ entry->mKey = fullname;
+ }
+ }
+
+ ++v;
+ }
+
+ // Fontconfig does not provide a fullname property for all fonts.
+ if (v == 0) {
+ nsAutoCString name;
+ if (!GetFullnameFromFamilyAndStyle(font, &name))
+ continue;
+
+ FontsByFullnameEntry *entry =
+ mFontsByFullname.PutEntry(ToFcChar8(name));
+ if (entry) {
+ entry->AddFont(font);
+ // Either entry->mKey has been set for a previous font or it
+ // remains nullptr to indicate that the key is obtained from
+ // the first font.
+ }
+ }
+ }
+ }
+}
+
+const nsTArray< nsCountedRef<FcPattern> >&
+gfxFontconfigUtils::GetFontsForFullname(const FcChar8 *aFullname)
+{
+ if (mFontsByFullname.Count() == 0) {
+ AddFullnameEntries();
+ }
+
+ FontsByFullnameEntry *entry = mFontsByFullname.GetEntry(aFullname);
+
+ if (!entry)
+ return mEmptyPatternArray;
+
+ return entry->GetFonts();
+}
+
+static FcLangResult
+CompareLangString(const FcChar8 *aLangA, const FcChar8 *aLangB) {
+ FcLangResult result = FcLangDifferentLang;
+ for (uint32_t i = 0; ; ++i) {
+ FcChar8 a = FcToLower(aLangA[i]);
+ FcChar8 b = FcToLower(aLangB[i]);
+
+ if (a != b) {
+ if ((a == '\0' && b == '-') || (a == '-' && b == '\0'))
+ return FcLangDifferentCountry;
+
+ return result;
+ }
+ if (a == '\0')
+ return FcLangEqual;
+
+ if (a == '-') {
+ result = FcLangDifferentCountry;
+ }
+ }
+}
+
+/* static */
+FcLangResult
+gfxFontconfigUtils::GetLangSupport(FcPattern *aFont, const FcChar8 *aLang)
+{
+ // When fontconfig builds a pattern for a system font, it will set a
+ // single LangSet property value for the font. That value may be removed
+ // and additional string values may be added through FcConfigSubsitute
+ // with FcMatchScan. Values that are neither LangSet nor string are
+ // considered errors in fontconfig sort and match functions.
+ //
+ // If no string nor LangSet value is found, then either the font is a
+ // system font and the LangSet has been removed through FcConfigSubsitute,
+ // or the font is a web font and its language support is unknown.
+ // Returning FcLangDifferentLang for these fonts ensures that this font
+ // will not be assumed to satisfy the language, and so language will be
+ // prioritized in sorting fallback fonts.
+ FcValue value;
+ FcLangResult best = FcLangDifferentLang;
+ for (int v = 0;
+ FcPatternGet(aFont, FC_LANG, v, &value) == FcResultMatch;
+ ++v) {
+
+ FcLangResult support;
+ switch (value.type) {
+ case FcTypeLangSet:
+ support = FcLangSetHasLang(value.u.l, aLang);
+ break;
+ case FcTypeString:
+ support = CompareLangString(value.u.s, aLang);
+ break;
+ default:
+ // error. continue to see if there is a useful value.
+ continue;
+ }
+
+ if (support < best) { // lower is better
+ if (support == FcLangEqual)
+ return support;
+ best = support;
+ }
+ }
+
+ return best;
+}
+
+gfxFontconfigUtils::LangSupportEntry *
+gfxFontconfigUtils::GetLangSupportEntry(const FcChar8 *aLang, bool aWithFonts)
+{
+ // Currently any unrecognized languages from documents will be converted
+ // to x-unicode by nsILanguageAtomService, so there is a limit on the
+ // langugages that will be added here. Reconsider when/if document
+ // languages are passed to this routine.
+
+ LangSupportEntry *entry = mLangSupportTable.PutEntry(aLang);
+ if (!entry)
+ return nullptr;
+
+ FcLangResult best = FcLangDifferentLang;
+
+ if (!entry->IsKeyInitialized()) {
+ entry->InitKey(aLang);
+ } else {
+ // mSupport is already initialized.
+ if (!aWithFonts)
+ return entry;
+
+ best = entry->mSupport;
+ // If there is support for this language, an empty font list indicates
+ // that the list hasn't been initialized yet.
+ if (best == FcLangDifferentLang || entry->mFonts.Length() > 0)
+ return entry;
+ }
+
+ // These FcFontSets are owned by fontconfig
+ FcFontSet *fontSets[] = {
+ FcConfigGetFonts(nullptr, FcSetSystem)
+#ifdef MOZ_BUNDLED_FONTS
+ , FcConfigGetFonts(nullptr, FcSetApplication)
+#endif
+ };
+
+ AutoTArray<FcPattern*,100> fonts;
+
+ for (unsigned fs = 0; fs < ArrayLength(fontSets); ++fs) {
+ FcFontSet *fontSet = fontSets[fs];
+ if (!fontSet) {
+ continue;
+ }
+ for (int f = 0; f < fontSet->nfont; ++f) {
+ FcPattern *font = fontSet->fonts[f];
+
+ FcLangResult support = GetLangSupport(font, aLang);
+
+ if (support < best) { // lower is better
+ best = support;
+ if (aWithFonts) {
+ fonts.Clear();
+ } else if (best == FcLangEqual) {
+ break;
+ }
+ }
+
+ // The font list in the LangSupportEntry is expected to be used
+ // only when no default fonts support the language. There would
+ // be a large number of fonts in entries for languages using Latin
+ // script but these do not need to be created because default
+ // fonts already support these languages.
+ if (aWithFonts && support != FcLangDifferentLang &&
+ support == best) {
+ fonts.AppendElement(font);
+ }
+ }
+ }
+
+ entry->mSupport = best;
+ if (aWithFonts) {
+ if (fonts.Length() != 0) {
+ entry->mFonts.AppendElements(fonts.Elements(), fonts.Length());
+ } else if (best != FcLangDifferentLang) {
+ // Previously there was a font that supported this language at the
+ // level of entry->mSupport, but it has now disappeared. At least
+ // entry->mSupport needs to be recalculated, but this is an
+ // indication that the set of installed fonts has changed, so
+ // update all caches.
+ mLastConfig = nullptr; // invalidates caches
+ UpdateFontListInternal(true);
+ return GetLangSupportEntry(aLang, aWithFonts);
+ }
+ }
+
+ return entry;
+}
+
+FcLangResult
+gfxFontconfigUtils::GetBestLangSupport(const FcChar8 *aLang)
+{
+ UpdateFontListInternal();
+
+ LangSupportEntry *entry = GetLangSupportEntry(aLang, false);
+ if (!entry)
+ return FcLangEqual;
+
+ return entry->mSupport;
+}
+
+const nsTArray< nsCountedRef<FcPattern> >&
+gfxFontconfigUtils::GetFontsForLang(const FcChar8 *aLang)
+{
+ LangSupportEntry *entry = GetLangSupportEntry(aLang, true);
+ if (!entry)
+ return mEmptyPatternArray;
+
+ return entry->mFonts;
+}
+
+#ifdef MOZ_BUNDLED_FONTS
+
+void
+gfxFontconfigUtils::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(NS_LITERAL_STRING("fonts")))) {
+ return;
+ }
+ bool isDir;
+ if (NS_FAILED(localDir->IsDirectory(&isDir)) || !isDir) {
+ return;
+ }
+ if (NS_FAILED(localDir->GetNativePath(mBundledFontsPath))) {
+ return;
+ }
+ }
+ if (!mBundledFontsPath.IsEmpty()) {
+ FcConfigAppFontAddDir(nullptr, (const FcChar8*)mBundledFontsPath.get());
+ }
+}
+
+#endif
+
+gfxFontconfigFontBase::gfxFontconfigFontBase(cairo_scaled_font_t *aScaledFont,
+ FcPattern *aPattern,
+ gfxFontEntry *aFontEntry,
+ const gfxFontStyle *aFontStyle)
+ : gfxFT2FontBase(aScaledFont, aFontEntry, aFontStyle)
+ , mPattern(aPattern)
+{
+}
+
diff --git a/gfx/thebes/gfxFontconfigUtils.h b/gfx/thebes/gfxFontconfigUtils.h
new file mode 100644
index 000000000..eee69e481
--- /dev/null
+++ b/gfx/thebes/gfxFontconfigUtils.h
@@ -0,0 +1,330 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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_FONTCONFIG_UTILS_H
+#define GFX_FONTCONFIG_UTILS_H
+
+#include "gfxPlatform.h"
+
+#include "mozilla/MathAlgorithms.h"
+#include "nsAutoRef.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+#include "nsISupportsImpl.h"
+#include "gfxFT2FontBase.h"
+
+#include <fontconfig/fontconfig.h>
+
+
+template <>
+class nsAutoRefTraits<FcPattern> : public nsPointerRefTraits<FcPattern>
+{
+public:
+ static void Release(FcPattern *ptr) { FcPatternDestroy(ptr); }
+ static void AddRef(FcPattern *ptr) { FcPatternReference(ptr); }
+};
+
+template <>
+class nsAutoRefTraits<FcFontSet> : public nsPointerRefTraits<FcFontSet>
+{
+public:
+ static void Release(FcFontSet *ptr) { FcFontSetDestroy(ptr); }
+};
+
+template <>
+class nsAutoRefTraits<FcCharSet> : public nsPointerRefTraits<FcCharSet>
+{
+public:
+ static void Release(FcCharSet *ptr) { FcCharSetDestroy(ptr); }
+};
+
+class gfxIgnoreCaseCStringComparator
+{
+ public:
+ bool Equals(const nsACString& a, const nsACString& b) const
+ {
+ return nsCString(a).Equals(b, nsCaseInsensitiveCStringComparator());
+ }
+
+ bool LessThan(const nsACString& a, const nsACString& b) const
+ {
+ return a < b;
+ }
+};
+
+class gfxFontconfigUtils {
+public:
+ gfxFontconfigUtils();
+
+ static gfxFontconfigUtils* GetFontconfigUtils() {
+ if (!sUtils)
+ sUtils = new gfxFontconfigUtils();
+ return sUtils;
+ }
+
+ static void Shutdown();
+
+ nsresult GetFontList(nsIAtom *aLangGroup,
+ const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts);
+
+ nsresult UpdateFontList();
+
+ nsresult GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName);
+
+ const nsTArray< nsCountedRef<FcPattern> >&
+ GetFontsForFamily(const FcChar8 *aFamilyName);
+
+ const nsTArray< nsCountedRef<FcPattern> >&
+ GetFontsForFullname(const FcChar8 *aFullname);
+
+ // Returns the best support that any font offers for |aLang|.
+ FcLangResult GetBestLangSupport(const FcChar8 *aLang);
+ // Returns the fonts offering this best level of support.
+ const nsTArray< nsCountedRef<FcPattern> >&
+ GetFontsForLang(const FcChar8 *aLang);
+
+ // Retuns the language support for a fontconfig font pattern
+ static FcLangResult GetLangSupport(FcPattern *aFont, const FcChar8 *aLang);
+
+ // Conversions between FcChar8*, which is unsigned char*,
+ // and (signed) char*, that check the type of the argument.
+ static const FcChar8 *ToFcChar8(const char *aCharPtr)
+ {
+ return reinterpret_cast<const FcChar8*>(aCharPtr);
+ }
+ static const FcChar8 *ToFcChar8(const nsCString& aCString)
+ {
+ return ToFcChar8(aCString.get());
+ }
+ static const char *ToCString(const FcChar8 *aChar8Ptr)
+ {
+ return reinterpret_cast<const char*>(aChar8Ptr);
+ }
+
+ static uint8_t FcSlantToThebesStyle(int aFcSlant);
+ static uint8_t GetThebesStyle(FcPattern *aPattern); // slant
+ static uint16_t GetThebesWeight(FcPattern *aPattern);
+ static int16_t GetThebesStretch(FcPattern *aPattern);
+
+ static int GetFcSlant(const gfxFontStyle& aFontStyle);
+ // Returns a precise FC_WEIGHT from |aBaseWeight|,
+ // which is a CSS absolute weight / 100.
+ static int FcWeightForBaseWeight(int8_t aBaseWeight);
+
+ static int FcWidthForThebesStretch(int16_t aStretch);
+
+ static bool GetFullnameFromFamilyAndStyle(FcPattern *aFont,
+ nsACString *aFullname);
+
+ // This doesn't consider which faces exist, and so initializes the pattern
+ // using a guessed weight, and doesn't consider sizeAdjust.
+ static nsReturnRef<FcPattern>
+ NewPattern(const nsTArray<nsString>& aFamilies,
+ const gfxFontStyle& aFontStyle, const char *aLang);
+
+ /**
+ * @param aLangGroup [in] a Mozilla langGroup.
+ * @param aFcLang [out] returns a language suitable for fontconfig
+ * matching |aLangGroup| or an empty string if no match is found.
+ */
+ static void GetSampleLangForGroup(nsIAtom *aLangGroup,
+ nsACString *aFcLang);
+
+protected:
+ // Base class for hash table entries with case-insensitive FcChar8
+ // string keys.
+ class FcStrEntryBase : public PLDHashEntryHdr {
+ public:
+ typedef const FcChar8 *KeyType;
+ typedef const FcChar8 *KeyTypePointer;
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; }
+ // Case-insensitive hash.
+ //
+ // fontconfig always ignores case of ASCII characters in family
+ // names and languages, but treatment of whitespace in families is
+ // not consistent. FcFontSort and FcFontMatch ignore whitespace
+ // except for whitespace in the first character, while FcFontList
+ // and config subsitution tests require whitespace to match
+ // exactly. CSS 2.1 implies that whitespace is important in the
+ // font-family property. FcStrCmpIgnoreCase considers whitespace
+ // important.
+ static PLDHashNumber HashKey(const FcChar8 *aKey) {
+ uint32_t hash = 0;
+ for (const FcChar8 *c = aKey; *c != '\0'; ++c) {
+ hash = mozilla::RotateLeft(hash, 3) ^ FcToLower(*c);
+ }
+ return hash;
+ }
+ enum { ALLOW_MEMMOVE = true };
+ };
+
+public:
+ // Hash entry with a dependent const FcChar8* pointer to an external
+ // string for a key (and no data). The user must ensure that the string
+ // associated with the pointer is not destroyed. This entry type is
+ // useful for family name keys as the family name string is held in the
+ // font pattern.
+ class DepFcStrEntry : public FcStrEntryBase {
+ public:
+ // When constructing a new entry in the hashtable, the key is left
+ // nullptr. The caller of PutEntry() must fill in mKey when nullptr.
+ // This provides a mechanism for the caller of PutEntry() to determine
+ // whether the entry has been initialized.
+ explicit DepFcStrEntry(KeyTypePointer aName)
+ : mKey(nullptr) { }
+
+ DepFcStrEntry(const DepFcStrEntry& toCopy)
+ : mKey(toCopy.mKey) { }
+
+ bool KeyEquals(KeyTypePointer aKey) const {
+ return FcStrCmpIgnoreCase(aKey, mKey) == 0;
+ }
+
+ const FcChar8 *mKey;
+ };
+
+ // Hash entry that uses a copy of an FcChar8 string to store the key.
+ // This entry type is useful for language keys, as languages are usually
+ // not stored as strings in font patterns.
+ class CopiedFcStrEntry : public FcStrEntryBase {
+ public:
+ // When constructing a new entry in the hashtable, the key is void.
+ // The caller of PutEntry() must call InitKey() when IsKeyInitialized()
+ // returns false. This provides a mechanism for the caller of
+ // PutEntry() to determine whether the entry has been initialized.
+ explicit CopiedFcStrEntry(KeyTypePointer aName) {
+ mKey.SetIsVoid(true);
+ }
+
+ CopiedFcStrEntry(const CopiedFcStrEntry& toCopy)
+ : mKey(toCopy.mKey) { }
+
+ bool KeyEquals(KeyTypePointer aKey) const {
+ return FcStrCmpIgnoreCase(aKey, ToFcChar8(mKey)) == 0;
+ }
+
+ bool IsKeyInitialized() { return !mKey.IsVoid(); }
+ void InitKey(const FcChar8* aKey) { mKey.Assign(ToCString(aKey)); }
+
+ private:
+ nsCString mKey;
+ };
+
+protected:
+ class FontsByFcStrEntry : public DepFcStrEntry {
+ public:
+ explicit FontsByFcStrEntry(KeyTypePointer aName)
+ : DepFcStrEntry(aName) { }
+
+ FontsByFcStrEntry(const FontsByFcStrEntry& toCopy)
+ : DepFcStrEntry(toCopy), mFonts(toCopy.mFonts) { }
+
+ bool AddFont(FcPattern *aFont) {
+ return mFonts.AppendElement(aFont) != nullptr;
+ }
+ const nsTArray< nsCountedRef<FcPattern> >& GetFonts() {
+ return mFonts;
+ }
+ private:
+ nsTArray< nsCountedRef<FcPattern> > mFonts;
+ };
+
+ // FontsByFullnameEntry is similar to FontsByFcStrEntry (used for
+ // mFontsByFamily) except for two differences:
+ //
+ // * The font does not always contain a single string for the fullname, so
+ // the key is sometimes a combination of family and style.
+ //
+ // * There is usually only one font.
+ class FontsByFullnameEntry : public DepFcStrEntry {
+ public:
+ // When constructing a new entry in the hashtable, the key is left
+ // nullptr. The caller of PutEntry() is must fill in mKey when adding
+ // the first font if the key is not derived from the family and style.
+ // If the key is derived from family and style, a font must be added.
+ explicit FontsByFullnameEntry(KeyTypePointer aName)
+ : DepFcStrEntry(aName) { }
+
+ FontsByFullnameEntry(const FontsByFullnameEntry& toCopy)
+ : DepFcStrEntry(toCopy), mFonts(toCopy.mFonts) { }
+
+ bool KeyEquals(KeyTypePointer aKey) const;
+
+ bool AddFont(FcPattern *aFont) {
+ return mFonts.AppendElement(aFont) != nullptr;
+ }
+ const nsTArray< nsCountedRef<FcPattern> >& GetFonts() {
+ return mFonts;
+ }
+
+ // Don't memmove the AutoTArray.
+ enum { ALLOW_MEMMOVE = false };
+ private:
+ // There is usually only one font, but sometimes more.
+ AutoTArray<nsCountedRef<FcPattern>,1> mFonts;
+ };
+
+ class LangSupportEntry : public CopiedFcStrEntry {
+ public:
+ explicit LangSupportEntry(KeyTypePointer aName)
+ : CopiedFcStrEntry(aName) { }
+
+ LangSupportEntry(const LangSupportEntry& toCopy)
+ : CopiedFcStrEntry(toCopy), mSupport(toCopy.mSupport) { }
+
+ FcLangResult mSupport;
+ nsTArray< nsCountedRef<FcPattern> > mFonts;
+ };
+
+ static gfxFontconfigUtils* sUtils;
+
+ bool IsExistingFamily(const nsCString& aFamilyName);
+
+ nsresult GetFontListInternal(nsTArray<nsCString>& aListOfFonts,
+ nsIAtom *aLangGroup);
+ nsresult UpdateFontListInternal(bool aForce = false);
+
+ void AddFullnameEntries();
+
+ LangSupportEntry *GetLangSupportEntry(const FcChar8 *aLang,
+ bool aWithFonts);
+
+ // mFontsByFamily and mFontsByFullname contain entries only for families
+ // and fullnames for which there are fonts.
+ nsTHashtable<FontsByFcStrEntry> mFontsByFamily;
+ nsTHashtable<FontsByFullnameEntry> mFontsByFullname;
+ // mLangSupportTable contains an entry for each language that has been
+ // looked up through GetLangSupportEntry, even when the language is not
+ // supported.
+ nsTHashtable<LangSupportEntry> mLangSupportTable;
+ const nsTArray< nsCountedRef<FcPattern> > mEmptyPatternArray;
+
+ FcConfig *mLastConfig;
+
+#ifdef MOZ_BUNDLED_FONTS
+ void ActivateBundledFonts();
+
+ nsCString mBundledFontsPath;
+ bool mBundledFontsInitialized;
+#endif
+};
+
+class gfxFontconfigFontBase : public gfxFT2FontBase {
+public:
+ gfxFontconfigFontBase(cairo_scaled_font_t *aScaledFont,
+ FcPattern *aPattern,
+ gfxFontEntry *aFontEntry,
+ const gfxFontStyle *aFontStyle);
+
+ virtual FontType GetType() const override { return FONT_TYPE_FONTCONFIG; }
+ virtual FcPattern *GetPattern() const { return mPattern; }
+
+private:
+ nsCountedRef<FcPattern> mPattern;
+};
+
+#endif /* GFX_FONTCONFIG_UTILS_H */
diff --git a/gfx/thebes/gfxGDIFont.cpp b/gfx/thebes/gfxGDIFont.cpp
new file mode 100644
index 000000000..e6ceeaf6d
--- /dev/null
+++ b/gfx/thebes/gfxGDIFont.cpp
@@ -0,0 +1,564 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxTextRun.h"
+
+#include "cairo-win32.h"
+
+#define ROUND(x) floor((x) + 0.5)
+
+using namespace mozilla;
+using namespace mozilla::unicode;
+
+static inline cairo_antialias_t
+GetCairoAntialiasOption(gfxFont::AntialiasOption anAntialiasOption)
+{
+ switch (anAntialiasOption) {
+ default:
+ case gfxFont::kAntialiasDefault:
+ return CAIRO_ANTIALIAS_DEFAULT;
+ case gfxFont::kAntialiasNone:
+ return CAIRO_ANTIALIAS_NONE;
+ case gfxFont::kAntialiasGrayscale:
+ return CAIRO_ANTIALIAS_GRAY;
+ case gfxFont::kAntialiasSubpixel:
+ return CAIRO_ANTIALIAS_SUBPIXEL;
+ }
+}
+
+gfxGDIFont::gfxGDIFont(GDIFontEntry *aFontEntry,
+ const gfxFontStyle *aFontStyle,
+ bool aNeedsBold,
+ AntialiasOption anAAOption)
+ : gfxFont(aFontEntry, aFontStyle, anAAOption),
+ mFont(nullptr),
+ mFontFace(nullptr),
+ mMetrics(nullptr),
+ mSpaceGlyph(0),
+ mNeedsBold(aNeedsBold),
+ mScriptCache(nullptr)
+{
+ Initialize();
+}
+
+gfxGDIFont::~gfxGDIFont()
+{
+ if (mScaledFont) {
+ cairo_scaled_font_destroy(mScaledFont);
+ }
+ if (mFontFace) {
+ cairo_font_face_destroy(mFontFace);
+ }
+ if (mFont) {
+ ::DeleteObject(mFont);
+ }
+ if (mScriptCache) {
+ ScriptFreeCache(&mScriptCache);
+ }
+ delete mMetrics;
+}
+
+gfxFont*
+gfxGDIFont::CopyWithAntialiasOption(AntialiasOption anAAOption)
+{
+ return new gfxGDIFont(static_cast<GDIFontEntry*>(mFontEntry.get()),
+ &mStyle, mNeedsBold, anAAOption);
+}
+
+bool
+gfxGDIFont::ShapeText(DrawTarget *aDrawTarget,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxShapedText *aShapedText)
+{
+ if (!mIsValid) {
+ NS_WARNING("invalid font! expect incorrect text rendering");
+ return false;
+ }
+
+ // Ensure the cairo font is set up, so there's no risk it'll fall back to
+ // creating a "toy" font internally (see bug 544617).
+ // We must check that this succeeded, otherwise we risk cairo creating the
+ // wrong kind of font internally as a fallback (bug 744480).
+ if (!SetupCairoFont(aDrawTarget)) {
+ return false;
+ }
+
+ return gfxFont::ShapeText(aDrawTarget, aText, aOffset, aLength, aScript,
+ aVertical, aShapedText);
+}
+
+const gfxFont::Metrics&
+gfxGDIFont::GetHorizontalMetrics()
+{
+ return *mMetrics;
+}
+
+uint32_t
+gfxGDIFont::GetSpaceGlyph()
+{
+ return mSpaceGlyph;
+}
+
+bool
+gfxGDIFont::SetupCairoFont(DrawTarget* aDrawTarget)
+{
+ if (!mScaledFont ||
+ cairo_scaled_font_status(mScaledFont) != CAIRO_STATUS_SUCCESS) {
+ // Don't cairo_set_scaled_font as that would propagate the error to
+ // the cairo_t, precluding any further drawing.
+ return false;
+ }
+ cairo_set_scaled_font(gfxFont::RefCairo(aDrawTarget), mScaledFont);
+ return true;
+}
+
+gfxFont::RunMetrics
+gfxGDIFont::Measure(const gfxTextRun *aTextRun,
+ uint32_t aStart, uint32_t aEnd,
+ BoundingBoxType aBoundingBoxType,
+ DrawTarget *aRefDrawTarget,
+ Spacing *aSpacing,
+ uint16_t 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.x -= aTextRun->GetAppUnitsPerDevUnit();
+ metrics.mBoundingBox.width += aTextRun->GetAppUnitsPerDevUnit() * 3;
+ }
+
+ return metrics;
+}
+
+void
+gfxGDIFont::Initialize()
+{
+ NS_ASSERTION(!mMetrics, "re-creating metrics? this will leak");
+
+ LOGFONTW logFont;
+
+ // Figure out if we want to do synthetic oblique styling.
+ GDIFontEntry* fe = static_cast<GDIFontEntry*>(GetFontEntry());
+ bool wantFakeItalic = mStyle.style != NS_FONT_STYLE_NORMAL &&
+ fe->IsUpright() && mStyle.allowSyntheticStyle;
+
+ // If the font's family has an actual italic face (but font matching
+ // didn't choose it), we have to use a cairo transform instead of asking
+ // GDI to italicize, because that would use a different face and result
+ // in a possible glyph ID mismatch between shaping and rendering.
+ //
+ // We use the mFamilyHasItalicFace flag in the entry in case of user fonts,
+ // where the *CSS* family may not know about italic faces that are present
+ // in the *GDI* family, and which GDI would use if we asked it to perform
+ // the "italicization".
+ bool useCairoFakeItalic = wantFakeItalic && fe->mFamilyHasItalicFace;
+
+ if (mAdjustedSize == 0.0) {
+ mAdjustedSize = mStyle.size;
+ if (mStyle.sizeAdjust > 0.0 && mAdjustedSize > 0.0) {
+ // to implement font-size-adjust, we first create the "unadjusted" font
+ FillLogFont(logFont, mAdjustedSize,
+ wantFakeItalic && !useCairoFakeItalic);
+ 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->xHeight > 0.0 && mMetrics->emHeight > 0.0) {
+ gfxFloat aspect = mMetrics->xHeight / mMetrics->emHeight;
+ mAdjustedSize = mStyle.GetAdjustedSize(aspect);
+ }
+
+ // delete the temporary font and metrics
+ ::DeleteObject(mFont);
+ mFont = nullptr;
+ delete mMetrics;
+ mMetrics = nullptr;
+ } else if (mStyle.sizeAdjust == 0.0) {
+ 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 (mNeedsBold && GetFontEntry()->IsLocalUserFont()) {
+ mApplySyntheticBold = true;
+ }
+
+ // this may end up being zero
+ mAdjustedSize = ROUND(mAdjustedSize);
+ FillLogFont(logFont, mAdjustedSize, wantFakeItalic && !useCairoFakeItalic);
+ mFont = ::CreateFontIndirectW(&logFont);
+
+ mMetrics = new gfxFont::Metrics;
+ ::memset(mMetrics, 0, sizeof(*mMetrics));
+
+ 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;
+ }
+
+ // 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->zeroOrAveCharWidth = ROUND(size.cx);
+ } else {
+ mMetrics->zeroOrAveCharWidth = mMetrics->aveCharWidth;
+ }
+
+ SanitizeMetrics(mMetrics, GetFontEntry()->mIsBadUnderlineFont);
+ } else {
+ mFUnitsConvFactor = 0.0; // zero-sized font: all values scale to zero
+ }
+
+ if (IsSyntheticBold()) {
+ mMetrics->aveCharWidth += GetSyntheticBoldOffset();
+ mMetrics->maxAdvance += GetSyntheticBoldOffset();
+ }
+
+ mFontFace = cairo_win32_font_face_create_for_logfontw_hfont(&logFont,
+ mFont);
+
+ cairo_matrix_t sizeMatrix, ctm;
+ cairo_matrix_init_identity(&ctm);
+ cairo_matrix_init_scale(&sizeMatrix, mAdjustedSize, mAdjustedSize);
+
+ if (useCairoFakeItalic) {
+ // Skew the matrix to do fake italic if it wasn't already applied
+ // via the LOGFONT
+ double skewfactor = OBLIQUE_SKEW_FACTOR;
+ cairo_matrix_t style;
+ cairo_matrix_init(&style,
+ 1, //xx
+ 0, //yx
+ -1 * skewfactor, //xy
+ 1, //yy
+ 0, //x0
+ 0); //y0
+ cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style);
+ }
+
+ cairo_font_options_t *fontOptions = cairo_font_options_create();
+ if (mAntialiasOption != kAntialiasDefault) {
+ cairo_font_options_set_antialias(fontOptions,
+ GetCairoAntialiasOption(mAntialiasOption));
+ }
+ mScaledFont = cairo_scaled_font_create(mFontFace, &sizeMatrix,
+ &ctm, fontOptions);
+ cairo_font_options_destroy(fontOptions);
+
+ if (!mScaledFont ||
+ cairo_scaled_font_status(mScaledFont) != CAIRO_STATUS_SUCCESS) {
+#ifdef DEBUG
+ char warnBuf[1024];
+ SprintfLiteral(warnBuf, "Failed to create scaled font: %s status: %d",
+ NS_ConvertUTF16toUTF8(mFontEntry->Name()).get(),
+ mScaledFont ? cairo_scaled_font_status(mScaledFont) : 0);
+ NS_WARNING(warnBuf);
+#endif
+ mIsValid = false;
+ } else {
+ mIsValid = true;
+ }
+
+#if 0
+ printf("Font: %p (%s) size: %f adjusted size: %f valid: %s\n", this,
+ NS_ConvertUTF16toUTF8(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,
+ bool aUseGDIFakeItalic)
+{
+ GDIFontEntry *fe = static_cast<GDIFontEntry*>(GetFontEntry());
+
+ uint16_t 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 = mNeedsBold ? 700 : 200;
+ }
+ } else {
+ weight = mNeedsBold ? 700 : fe->Weight();
+ }
+
+ fe->FillLogFont(&aLogFont, weight, aSize,
+ (mAntialiasOption == kAntialiasSubpixel) ? true : false);
+
+ // If GDI synthetic italic is wanted, force the lfItalic field to true
+ if (aUseGDIFakeItalic) {
+ aLogFont.lfItalic = 1;
+ }
+}
+
+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<nsDataHashtable<nsUint32HashKey,uint32_t>>(64);
+ }
+
+ uint32_t gid;
+ if (mGlyphIDs->Get(aUnicode, &gid)) {
+ return gid;
+ }
+
+ wchar_t ch = aUnicode;
+ WORD glyph;
+ DWORD 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).
+ ret = GetGlyphIndicesW(dc.GetDC(), &ch, 1, &glyph,
+ GGI_MARK_NONEXISTING_GLYPHS);
+ if (ret == GDI_ERROR || glyph == 0xFFFF) {
+ glyph = 0;
+ }
+ }
+ }
+
+ mGlyphIDs->Put(aUnicode, glyph);
+ return glyph;
+}
+
+int32_t
+gfxGDIFont::GetGlyphWidth(DrawTarget& aDrawTarget, uint16_t aGID)
+{
+ if (!mGlyphWidths) {
+ mGlyphWidths = MakeUnique<nsDataHashtable<nsUint32HashKey,int32_t>>(128);
+ }
+
+ int32_t width;
+ if (mGlyphWidths->Get(aGID, &width)) {
+ return width;
+ }
+
+ DCFromDrawTarget dc(aDrawTarget);
+ AutoSelectFont fs(dc, GetHFONT());
+
+ int devWidth;
+ if (GetCharWidthI(dc, aGID, 1, nullptr, &devWidth)) {
+ // clamp value to range [0..0x7fff], and convert to 16.16 fixed-point
+ devWidth = std::min(std::max(0, devWidth), 0x7fff);
+ width = devWidth << 16;
+ mGlyphWidths->Put(aGID, width);
+ return width;
+ }
+
+ return -1;
+}
+
+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 000000000..9d8ac9552
--- /dev/null
+++ b/gfx/thebes/gfxGDIFont.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "nsDataHashtable.h"
+#include "nsHashKeys.h"
+
+#include "cairo.h"
+#include "usp10.h"
+
+class gfxGDIFont : public gfxFont
+{
+public:
+ gfxGDIFont(GDIFontEntry *aFontEntry,
+ const gfxFontStyle *aFontStyle,
+ bool aNeedsBold,
+ AntialiasOption anAAOption = kAntialiasDefault);
+
+ virtual ~gfxGDIFont();
+
+ HFONT GetHFONT() { return mFont; }
+
+ cairo_font_face_t *CairoFontFace() { return mFontFace; }
+ cairo_scaled_font_t *CairoScaledFont() { return mScaledFont; }
+
+ /* overrides for the pure virtual methods in gfxFont */
+ virtual uint32_t GetSpaceGlyph() override;
+
+ virtual bool SetupCairoFont(DrawTarget* aDrawTarget) override;
+
+ /* override Measure to add padding for antialiasing */
+ virtual RunMetrics Measure(const gfxTextRun *aTextRun,
+ uint32_t aStart, uint32_t aEnd,
+ BoundingBoxType aBoundingBoxType,
+ DrawTarget *aDrawTargetForTightBoundingBox,
+ Spacing *aSpacing,
+ uint16_t aOrientation) override;
+
+ /* required for MathML to suppress effects of ClearType "padding" */
+ virtual gfxFont*
+ CopyWithAntialiasOption(AntialiasOption anAAOption) 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.
+ virtual bool ProvidesGetGlyph() const override {
+ return !mFontEntry->HasCmapTable();
+ }
+
+ virtual uint32_t GetGlyph(uint32_t aUnicode,
+ uint32_t aVarSelector) override;
+
+ virtual bool ProvidesGlyphWidths() const override { return true; }
+
+ // get hinted glyph width in pixels as 16.16 fixed-point value
+ virtual int32_t GetGlyphWidth(DrawTarget& aDrawTarget,
+ uint16_t aGID) override;
+
+ virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const;
+ virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const;
+
+ virtual FontType GetType() const override { return FONT_TYPE_GDI; }
+
+protected:
+ virtual const Metrics& GetHorizontalMetrics() override;
+
+ /* override to ensure the cairo font is set up properly */
+ virtual bool ShapeText(DrawTarget *aDrawTarget,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxShapedText *aShapedText) override;
+
+ void Initialize(); // creates metrics and Cairo fonts
+
+ // Fill the given LOGFONT record according to our style, but don't adjust
+ // the lfItalic field if we're going to use a cairo transform for fake
+ // italics.
+ void FillLogFont(LOGFONTW& aLogFont, gfxFloat aSize, bool aUseGDIFakeItalic);
+
+ HFONT mFont;
+ cairo_font_face_t *mFontFace;
+
+ Metrics *mMetrics;
+ uint32_t mSpaceGlyph;
+
+ bool mNeedsBold;
+
+ // cache of glyph IDs (used for non-sfnt fonts only)
+ mozilla::UniquePtr<nsDataHashtable<nsUint32HashKey,uint32_t> > mGlyphIDs;
+ SCRIPT_CACHE mScriptCache;
+
+ // cache of glyph widths in 16.16 fixed-point pixels
+ mozilla::UniquePtr<nsDataHashtable<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 000000000..d80c49356
--- /dev/null
+++ b/gfx/thebes/gfxGDIFontList.cpp
@@ -0,0 +1,1195 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/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 "nsISimpleEnumerator.h"
+#include "nsIWindowsRegKey.h"
+#include "gfxFontConstants.h"
+#include "GeckoProfiler.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/WindowsVersion.h"
+
+#include <usp10.h>
+
+using namespace mozilla;
+
+#define ROUND(x) floor((x) + 0.5)
+
+
+#ifndef CLEARTYPE_QUALITY
+#define CLEARTYPE_QUALITY 5
+#endif
+
+#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:
+ 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 nsAString& aFaceName,
+ gfxWindowsFontType aFontType,
+ uint8_t aStyle, uint16_t aWeight,
+ int16_t aStretch,
+ gfxUserFontData *aUserFontData,
+ bool aFamilyHasItalicFace)
+ : gfxFontEntry(aFaceName),
+ mWindowsFamily(0), mWindowsPitch(0),
+ mFontType(aFontType),
+ mForceGDI(false),
+ mFamilyHasItalicFace(aFamilyHasItalicFace),
+ mCharset(), mUnicodeRanges()
+{
+ mUserFontData.reset(aUserFontData);
+ mStyle = aStyle;
+ mWeight = aWeight;
+ mStretch = aStretch;
+ if (IsType1())
+ mForceGDI = true;
+ mIsDataUserFont = aUserFontData != nullptr;
+
+ InitLogFont(aFaceName, aFontType);
+}
+
+nsresult
+GDIFontEntry::ReadCMAP(FontInfoData *aFontInfoData)
+{
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::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)
+ {
+ mCharacterMap = new gfxCharacterMap();
+ mCharacterMap->mBuildOnTheFly = true;
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<gfxCharacterMap> charmap;
+ nsresult rv;
+ bool unicodeFont = false, symbolFont = false;
+
+ if (aFontInfoData && (charmap = GetCMAPFromFontInfo(aFontInfoData,
+ mUVSOffset,
+ symbolFont))) {
+ mSymbolFont = symbolFont;
+ 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, mUVSOffset,
+ unicodeFont, symbolFont);
+ }
+ mSymbolFont = symbolFont;
+ }
+
+ mHasCmapTable = NS_SUCCEEDED(rv);
+ if (mHasCmapTable) {
+ gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList();
+ mCharacterMap = pfl->FindCharMap(charmap);
+ } else {
+ // if error occurred, initialize to null cmap
+ mCharacterMap = 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
+ mCharacterMap->mBuildOnTheFly = true;
+ }
+
+ LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %d hash: %8.8x%s\n",
+ NS_ConvertUTF16toUTF8(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",
+ NS_ConvertUTF16toUTF8(mName).get());
+ charmap->Dump(prefix, eGfxLog_cmapdata);
+ }
+
+ return rv;
+}
+
+bool
+GDIFontEntry::IsSymbolFont()
+{
+ // initialize cmap first
+ HasCmapTable();
+ return mSymbolFont;
+}
+
+gfxFont *
+GDIFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle, bool aNeedsBold)
+{
+ bool isXP = !IsVistaOrLater();
+
+ bool useClearType = isXP && !aFontStyle->systemFont &&
+ (gfxWindowsPlatform::GetPlatform()->UseClearTypeAlways() ||
+ (mIsDataUserFont &&
+ gfxWindowsPlatform::GetPlatform()->UseClearTypeForDownloadableFonts()));
+
+ return new gfxGDIFont(this, aFontStyle, aNeedsBold,
+ (useClearType ? gfxFont::kAntialiasSubpixel
+ : gfxFont::kAntialiasDefault));
+}
+
+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;
+}
+
+void
+GDIFontEntry::FillLogFont(LOGFONTW *aLogFont,
+ uint16_t aWeight, gfxFloat aSize,
+ bool aUseCleartype)
+{
+ 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) {
+ 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;
+ }
+
+ aLogFont->lfQuality = (aUseCleartype ? CLEARTYPE_QUALITY : DEFAULT_QUALITY);
+}
+
+#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 (mCharacterMap->mBuildOnTheFly) {
+ if (aCh > 0xFFFF)
+ return false;
+
+ // previous code was using the group style
+ gfxFontStyle fakeStyle;
+ if (!IsUpright()) {
+ fakeStyle.style = NS_FONT_STYLE_ITALIC;
+ }
+ fakeStyle.weight = mWeight * 100;
+
+ RefPtr<gfxFont> tempFont = FindOrMakeFont(&fakeStyle, false);
+ 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) {
+ mCharacterMap->set(aCh);
+ return true;
+ }
+ } else {
+ // font had a cmap so simply check that
+ return mCharacterMap->test(aCh);
+ }
+
+ return false;
+}
+
+void
+GDIFontEntry::InitLogFont(const nsAString& 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 = mWeight;
+
+ int len = std::min<int>(aName.Length(), LF_FACESIZE - 1);
+ memcpy(&mLogFont.lfFaceName, aName.BeginReading(), len * sizeof(char16_t));
+ mLogFont.lfFaceName[len] = '\0';
+}
+
+GDIFontEntry*
+GDIFontEntry::CreateFontEntry(const nsAString& aName,
+ gfxWindowsFontType aFontType,
+ uint8_t aStyle,
+ uint16_t aWeight, int16_t aStretch,
+ gfxUserFontData* aUserFontData,
+ bool aFamilyHasItalicFace)
+{
+ // jtdfix - need to set charset, unicode ranges, pitch/family
+
+ GDIFontEntry *fe = new GDIFontEntry(aName, aFontType, aStyle,
+ aWeight, aStretch, aUserFontData,
+ aFamilyHasItalicFace);
+
+ return fe;
+}
+
+void
+GDIFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const
+{
+ aSizes->mFontListSize += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
+
+/***************************************************************
+ *
+ * GDIFontFamily
+ *
+ */
+
+int CALLBACK
+GDIFontFamily::FamilyAddStylesProc(const ENUMLOGFONTEXW *lpelfe,
+ const NEWTEXTMETRICEXW *nmetrics,
+ DWORD fontType, LPARAM data)
+{
+ const NEWTEXTMETRICW& metrics = nmetrics->ntmTm;
+ LOGFONTW logFont = lpelfe->elfLogFont;
+ GDIFontFamily *ff = reinterpret_cast<GDIFontFamily*>(data);
+
+ // 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->mWeight == logFont.lfWeight &&
+ fe->IsItalic() == (logFont.lfItalic == 0xFF)) {
+ // update the charset bit here since this could be different
+ fe->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.
+ uint8_t italicStyle = (logFont.lfItalic == 0xFF ?
+ NS_FONT_STYLE_ITALIC : NS_FONT_STYLE_NORMAL);
+ fe = GDIFontEntry::CreateFontEntry(nsDependentString(lpelfe->elfFullName),
+ feType, italicStyle,
+ (uint16_t) (logFont.lfWeight), 0,
+ nullptr, false);
+ if (!fe)
+ return 1;
+
+ ff->AddFontEntry(fe);
+
+ // mark the charset bit
+ fe->mCharset.set(metrics.tmCharSet);
+
+ fe->mWindowsFamily = logFont.lfPitchAndFamily & 0xF0;
+ fe->mWindowsPitch = logFont.lfPitchAndFamily & 0x0F;
+
+ if (nmetrics->ntmFontSig.fsUsb[0] != 0x00000000 &&
+ nmetrics->ntmFontSig.fsUsb[1] != 0x00000000 &&
+ nmetrics->ntmFontSig.fsUsb[2] != 0x00000000 &&
+ nmetrics->ntmFontSig.fsUsb[3] != 0x00000000) {
+
+ // set the unicode ranges
+ uint32_t x = 0;
+ for (uint32_t i = 0; i < 4; ++i) {
+ DWORD range = nmetrics->ntmFontSig.fsUsb[i];
+ for (uint32_t k = 0; k < 32; ++k) {
+ fe->mUnicodeRanges.set(x++, (range & (1 << k)) != 0);
+ }
+ }
+ }
+
+ if (LOG_FONTLIST_ENABLED()) {
+ LOG_FONTLIST(("(fontlist) added (%s) to family (%s)"
+ " with style: %s weight: %d stretch: %d",
+ NS_ConvertUTF16toUTF8(fe->Name()).get(),
+ NS_ConvertUTF16toUTF8(ff->Name()).get(),
+ (logFont.lfItalic == 0xff) ? "italic" : "normal",
+ logFont.lfWeight, fe->Stretch()));
+ }
+ return 1;
+}
+
+void
+GDIFontFamily::FindStyleVariations(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;
+ uint32_t l = std::min<uint32_t>(mName.Length(), LF_FACESIZE - 1);
+ memcpy(logFont.lfFaceName, mName.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\"",
+ NS_ConvertUTF16toUTF8(mName).get()));
+ }
+
+ ReleaseDC(nullptr, hdc);
+
+ if (mIsBadUnderlineFamily) {
+ SetBadUnderlineFonts();
+ }
+
+ // check for existence of italic face(s); if present, set the
+ // FamilyHasItalic flag on all faces so that we'll know *not*
+ // to use GDI's fake-italic effect with them
+ size_t count = mAvailableFonts.Length();
+ for (size_t i = 0; i < count; ++i) {
+ if (mAvailableFonts[i]->IsItalic()) {
+ for (uint32_t j = 0; j < count; ++j) {
+ static_cast<GDIFontEntry*>(mAvailableFonts[j].get())->
+ mFamilyHasItalicFace = true;
+ }
+ break;
+ }
+ }
+}
+
+/***************************************************************
+ *
+ * gfxGDIFontList
+ *
+ */
+
+gfxGDIFontList::gfxGDIFontList()
+ : mFontSubstitutes(32)
+{
+#ifdef MOZ_BUNDLED_FONTS
+ ActivateBundledFonts();
+#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;
+ if (!actualFontName.IsEmpty() &&
+ (ff = mFontFamilies.GetWeak(actualFontName))) {
+ mFontSubstitutes.Put(substituteName, ff);
+ } else {
+ mNonExistingFonts.AppendElement(substituteName);
+ }
+ }
+
+ // "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);
+ if (!mFontSubstitutes.GetWeak(substituteName)) {
+ gfxFontFamily *ff;
+ nsAutoString actualFontName;
+ actualFontName.AssignLiteral("Courier New");
+ BuildKeyNameFromFontName(actualFontName);
+ ff = mFontFamilies.GetWeak(actualFontName);
+ if (ff) {
+ mFontSubstitutes.Put(substituteName, 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;
+ int result = 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 LOGFONTW& lf = lpelfe->elfLogFont;
+
+ if (lf.lfFaceName[0] == '@') {
+ return 1;
+ }
+
+ nsAutoString name(lf.lfFaceName);
+ BuildKeyNameFromFontName(name);
+
+ gfxGDIFontList *fontList = PlatformFontList();
+
+ if (!fontList->mFontFamilies.GetWeak(name)) {
+ nsDependentString faceName(lf.lfFaceName);
+ RefPtr<gfxFontFamily> family = new GDIFontFamily(faceName);
+ fontList->mFontFamilies.Put(name, 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.Contains(name))
+ family->SetBadUnderlineFamily();
+ }
+
+ return 1;
+}
+
+gfxFontEntry*
+gfxGDIFontList::LookupLocalFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle)
+{
+ gfxFontEntry *lookup;
+
+ 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->mStyle, lookup->mWeight, aStretch, nullptr,
+ static_cast<GDIFontEntry*>(lookup)->mFamilyHasItalicFace);
+
+ if (!fe)
+ return nullptr;
+
+ fe->mIsLocalUserFont = true;
+
+ // make the new font entry match the userfont entry style characteristics
+ fe->mWeight = (aWeight == 0 ? 400 : aWeight);
+ fe->mStyle = aStyle;
+
+ 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 nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ 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 {
+ 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);
+ uint16_t w = (aWeight == 0 ? 400 : aWeight);
+
+ GDIFontEntry *fe = GDIFontEntry::CreateFontEntry(uniqueName,
+ gfxWindowsFontType(isCFF ? GFX_FONT_TYPE_PS_OPENTYPE : GFX_FONT_TYPE_TRUETYPE) /*type*/,
+ aStyle, w, aStretch, winUserFontData, false);
+
+ if (!fe)
+ return fe;
+
+ fe->mIsDataUserFont = true;
+
+ // Uniscribe doesn't place CFF fonts loaded privately
+ // via AddFontMemResourceEx on XP/Vista
+ if (isCFF && !IsWin7OrLater()) {
+ fe->mForceGDI = true;
+ }
+
+ return fe;
+}
+
+bool
+gfxGDIFontList::FindAndAddFamilies(const nsAString& aFamily,
+ nsTArray<gfxFontFamily*>* aOutput,
+ gfxFontStyle* aStyle,
+ gfxFloat aDevToCssSize)
+{
+ nsAutoString keyName(aFamily);
+ BuildKeyNameFromFontName(keyName);
+
+ gfxFontFamily *ff = mFontSubstitutes.GetWeak(keyName);
+ if (ff) {
+ aOutput->AppendElement(ff);
+ return true;
+ }
+
+ if (mNonExistingFonts.Contains(keyName)) {
+ return false;
+ }
+
+ return gfxPlatformFontList::FindAndAddFamilies(aFamily, aOutput, aStyle,
+ aDevToCssSize);
+}
+
+gfxFontFamily*
+gfxGDIFontList::GetDefaultFontForPlatform(const gfxFontStyle* aStyle)
+{
+ gfxFontFamily *ff = nullptr;
+
+ // 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(nsDependentString(ncm.lfMessageFont.lfFaceName));
+ if (ff) {
+ 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(nsDependentString(logFont.lfFaceName));
+ }
+
+ return ff;
+}
+
+void
+gfxGDIFontList::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const
+{
+ gfxPlatformFontList::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+ 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() {}
+
+ 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 nsAString& 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 nsAString& aFamilyName,
+ GDIFontInfo& aFontInfo)
+ : mFamilyName(aFamilyName), mFontInfo(aFontInfo)
+ {}
+
+ nsString mFamilyName;
+ nsTArray<nsString> mOtherFamilyNames;
+ GDIFontInfo& mFontInfo;
+ nsString 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;
+ nsDependentString 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) {
+ gfxFontFamily::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);
+ bool cmapLoaded = false;
+ bool unicodeFont = false, symbolFont = false;
+ RefPtr<gfxCharacterMap> charmap = new gfxCharacterMap();
+ uint32_t offset;
+
+ if (NS_SUCCEEDED(gfxFontUtils::ReadCMAP(cmapData.Elements(),
+ cmapSize, *charmap,
+ offset, unicodeFont,
+ symbolFont))) {
+ fontData.mCharacterMap = charmap;
+ fontData.mUVSOffset = offset;
+ fontData.mSymbolFont = symbolFont;
+ cmapLoaded = true;
+ famData->mFontInfo.mLoadStats.cmaps++;
+ }
+ }
+ }
+
+ if (cmapLoaded || nameDataLoaded) {
+ famData->mFontInfo.mFontFaceData.Put(fontName, fontData);
+ }
+
+ return famData->mFontInfo.mCanceled ? 0 : 1;
+}
+
+void
+GDIFontInfo::LoadFontFamilyData(const nsAString& aFamilyName)
+{
+ // iterate over the family
+ LOGFONTW logFont;
+ memset(&logFont, 0, sizeof(LOGFONTW));
+ logFont.lfCharSet = DEFAULT_CHARSET;
+ logFont.lfPitchAndFamily = 0;
+ uint32_t l = std::min<uint32_t>(aFamilyName.Length(), LF_FACESIZE - 1);
+ memcpy(logFont.lfFaceName, aFamilyName.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.Put(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();
+}
+
+#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(NS_LITERAL_STRING("fonts")))) {
+ return;
+ }
+ bool isDir;
+ if (NS_FAILED(localDir->IsDirectory(&isDir)) || !isDir) {
+ return;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> e;
+ rv = localDir->GetDirectoryEntries(getter_AddRefs(e));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ bool hasMore;
+ while (NS_SUCCEEDED(e->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> entry;
+ if (NS_FAILED(e->GetNext(getter_AddRefs(entry)))) {
+ break;
+ }
+ nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
+ if (!file) {
+ continue;
+ }
+ 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 000000000..ffb513d64
--- /dev/null
+++ b/gfx/thebes/gfxGDIFontList.h
@@ -0,0 +1,354 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/MemoryReporting.h"
+#include "gfxWindowsPlatform.h"
+#include "gfxPlatformFontList.h"
+#include "nsGkAtoms.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 : public gfxFontEntry
+{
+public:
+ LPLOGFONTW GetLogFont() { return &mLogFont; }
+
+ nsresult ReadCMAP(FontInfoData *aFontInfoData = nullptr);
+
+ virtual bool IsSymbolFont();
+
+ void FillLogFont(LOGFONTW *aLogFont, uint16_t aWeight, gfxFloat aSize,
+ bool aUseCleartype);
+
+ 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);
+ }
+
+ virtual 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;
+ }
+
+ virtual bool SupportsLangGroup(nsIAtom* aLangGroup) const override {
+ 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;
+ }
+
+ virtual bool SupportsRange(uint8_t range) {
+ return mUnicodeRanges.test(range);
+ }
+
+ virtual bool SkipDuringSystemFallback() {
+ return !HasCmapTable(); // explicitly skip non-SFNT fonts
+ }
+
+ virtual bool TestCharacterMap(uint32_t aCh);
+
+ virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const;
+
+ // create a font entry for a font with a given name
+ static GDIFontEntry* CreateFontEntry(const nsAString& aName,
+ gfxWindowsFontType aFontType,
+ uint8_t aStyle,
+ uint16_t aWeight, int16_t aStretch,
+ gfxUserFontData* aUserFontData,
+ bool aFamilyHasItalicFace);
+
+ // create a font entry for a font referenced by its fullname
+ static GDIFontEntry* LoadLocalFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle);
+
+ uint8_t mWindowsFamily;
+ uint8_t mWindowsPitch;
+
+ gfxWindowsFontType mFontType;
+ bool mForceGDI : 1;
+
+ // For src:local user-fonts, we keep track of whether the platform family
+ // contains an italic face, because in this case we can't safely ask GDI
+ // to create synthetic italics (oblique) via the LOGFONT.
+ // (For other types of font, this is just set to false.)
+ bool mFamilyHasItalicFace : 1;
+
+ gfxSparseBitSet mCharset;
+ gfxSparseBitSet mUnicodeRanges;
+
+protected:
+ friend class gfxWindowsFont;
+
+ GDIFontEntry(const nsAString& aFaceName, gfxWindowsFontType aFontType,
+ uint8_t aStyle, uint16_t aWeight, int16_t aStretch,
+ gfxUserFontData *aUserFontData, bool aFamilyHasItalicFace);
+
+ void InitLogFont(const nsAString& aName, gfxWindowsFontType aFontType);
+
+ virtual gfxFont *CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold);
+
+ virtual nsresult CopyFontTable(uint32_t aTableTag,
+ nsTArray<uint8_t>& aBuffer) override;
+
+ LOGFONTW mLogFont;
+};
+
+// a single font family, referencing one or more faces
+class GDIFontFamily : public gfxFontFamily
+{
+public:
+ GDIFontFamily(nsAString &aName) :
+ gfxFontFamily(aName) {}
+
+ virtual void FindStyleVariations(FontInfoData *aFontInfoData = nullptr);
+
+private:
+ static int CALLBACK FamilyAddStylesProc(const ENUMLOGFONTEXW *lpelfe,
+ const NEWTEXTMETRICEXW *nmetrics,
+ DWORD fontType, LPARAM data);
+};
+
+class gfxGDIFontList : public gfxPlatformFontList {
+public:
+ static gfxGDIFontList* PlatformFontList() {
+ return static_cast<gfxGDIFontList*>(sPlatformFontList);
+ }
+
+ // initialize font lists
+ virtual nsresult InitFontListForPlatform() override;
+
+ bool FindAndAddFamilies(const nsAString& aFamily,
+ nsTArray<gfxFontFamily*>* aOutput,
+ gfxFontStyle* aStyle = nullptr,
+ gfxFloat aDevToCssSize = 1.0) override;
+
+ virtual gfxFontEntry* LookupLocalFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle);
+
+ virtual gfxFontEntry* MakePlatformFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t* aFontData,
+ uint32_t aLength);
+
+ virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const;
+ virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const;
+
+protected:
+ virtual gfxFontFamily*
+ GetDefaultFontForPlatform(const gfxFontStyle* aStyle) override;
+
+private:
+ friend class gfxWindowsPlatform;
+
+ gfxGDIFontList();
+
+ nsresult GetFontSubstitutes();
+
+ static int CALLBACK EnumFontFamExProc(ENUMLOGFONTEXW *lpelfe,
+ NEWTEXTMETRICEXW *lpntme,
+ DWORD fontType,
+ LPARAM lParam);
+
+ virtual already_AddRefed<FontInfoData> CreateFontInfoData();
+
+#ifdef MOZ_BUNDLED_FONTS
+ void ActivateBundledFonts();
+#endif
+
+ FontFamilyTable mFontSubstitutes;
+ nsTArray<nsString> mNonExistingFonts;
+};
+
+#endif /* GFX_GDIFONTLIST_H */
diff --git a/gfx/thebes/gfxGdkNativeRenderer.cpp b/gfx/thebes/gfxGdkNativeRenderer.cpp
new file mode 100644
index 000000000..f8964ad8f
--- /dev/null
+++ b/gfx/thebes/gfxGdkNativeRenderer.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxGdkNativeRenderer.h"
+#include "gfxContext.h"
+#include "gfxPlatformGtk.h"
+
+#ifdef MOZ_X11
+#include <gdk/gdkx.h>
+#include "cairo-xlib.h"
+#include "gfxXlibSurface.h"
+
+#if (MOZ_WIDGET_GTK == 2)
+nsresult
+gfxGdkNativeRenderer::DrawWithXlib(cairo_surface_t* surface,
+ nsIntPoint offset,
+ mozilla::gfx::IntRect* clipRects, uint32_t numClipRects)
+{
+ GdkDrawable *drawable = gfxPlatformGtk::GetGdkDrawable(surface);
+ if (!drawable) {
+ int depth = cairo_xlib_surface_get_depth(surface);
+ GdkScreen* screen = gdk_colormap_get_screen(mColormap);
+ drawable =
+ gdk_pixmap_foreign_new_for_screen(screen, cairo_xlib_surface_get_drawable(surface),
+ cairo_xlib_surface_get_width(surface),
+ cairo_xlib_surface_get_height(surface),
+ depth);
+ if (!drawable)
+ return NS_ERROR_FAILURE;
+
+ gdk_drawable_set_colormap(drawable, mColormap);
+ gfxPlatformGtk::SetGdkDrawable(surface, drawable);
+ g_object_unref(drawable); // The drawable now belongs to |surface|.
+ }
+
+ GdkRectangle clipRect;
+ if (numClipRects) {
+ NS_ASSERTION(numClipRects == 1, "Too many clip rects");
+ clipRect.x = clipRects[0].x;
+ clipRect.y = clipRects[0].y;
+ clipRect.width = clipRects[0].width;
+ clipRect.height = clipRects[0].height;
+ }
+
+ return DrawWithGDK(drawable, offset.x, offset.y,
+ numClipRects ? &clipRect : nullptr, numClipRects);
+}
+
+void
+gfxGdkNativeRenderer::Draw(gfxContext* ctx, mozilla::gfx::IntSize size,
+ uint32_t flags, GdkColormap* colormap)
+{
+ mColormap = colormap;
+
+ Visual* visual =
+ gdk_x11_visual_get_xvisual(gdk_colormap_get_visual(colormap));
+ Screen* screen =
+ gdk_x11_screen_get_xscreen(gdk_colormap_get_screen(colormap));
+
+ gfxXlibNativeRenderer::Draw(ctx, size, flags, screen, visual);
+}
+
+#else
+// TODO GTK3
+#endif
+
+#endif
diff --git a/gfx/thebes/gfxGdkNativeRenderer.h b/gfx/thebes/gfxGdkNativeRenderer.h
new file mode 100644
index 000000000..d95b1fef0
--- /dev/null
+++ b/gfx/thebes/gfxGdkNativeRenderer.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 GFXGDKNATIVERENDER_H_
+#define GFXGDKNATIVERENDER_H_
+
+#include <gdk/gdk.h>
+#include "nsSize.h"
+#ifdef MOZ_X11
+#include "gfxXlibNativeRenderer.h"
+#endif
+
+class gfxContext;
+
+/**
+ * This class lets us take code that draws into an GDK drawable and lets us
+ * use it to draw into any Thebes context. The user should subclass this class,
+ * override DrawWithGDK, and then call Draw(). The drawing will be subjected
+ * to all Thebes transformations, clipping etc.
+ */
+class gfxGdkNativeRenderer
+#ifdef MOZ_X11
+ : private gfxXlibNativeRenderer
+#endif
+{
+public:
+ /**
+ * Perform the native drawing.
+ * @param offsetX draw at this offset into the given drawable
+ * @param offsetY draw at this offset into the given drawable
+ * @param clipRects an array of rects; clip to the union
+ * @param numClipRects the number of rects in the array, or zero if
+ * no clipping is required
+ */
+
+#if (MOZ_WIDGET_GTK == 2)
+ virtual nsresult DrawWithGDK(GdkDrawable * drawable, gint offsetX,
+ gint offsetY, GdkRectangle * clipRects, uint32_t numClipRects) = 0;
+#endif
+
+ enum {
+ // If set, then Draw() is opaque, i.e., every pixel in the intersection
+ // of the clipRect and (offset.x,offset.y,bounds.width,bounds.height)
+ // will be set and there is no dependence on what the existing pixels
+ // in the drawable are set to.
+ DRAW_IS_OPAQUE =
+#ifdef MOZ_X11
+ gfxXlibNativeRenderer::DRAW_IS_OPAQUE
+#else
+ 0x1
+#endif
+ // If set, then numClipRects can be zero or one.
+ // If not set, then numClipRects will be zero.
+ , DRAW_SUPPORTS_CLIP_RECT =
+#ifdef MOZ_X11
+ gfxXlibNativeRenderer::DRAW_SUPPORTS_CLIP_RECT
+#else
+ 0x2
+#endif
+ };
+
+ /**
+ * @param flags see above
+ * @param bounds Draw()'s drawing is guaranteed to be restricted to
+ * the rectangle (offset.x,offset.y,bounds.width,bounds.height)
+ * @param dpy a display to use for the drawing if ctx doesn't have one
+ */
+#if (MOZ_WIDGET_GTK == 2)
+ void Draw(gfxContext* ctx, mozilla::gfx::IntSize size,
+ uint32_t flags, GdkColormap* colormap);
+#endif
+
+private:
+#ifdef MOZ_X11
+ // for gfxXlibNativeRenderer:
+ virtual nsresult DrawWithXlib(cairo_surface_t* surface,
+ nsIntPoint offset,
+ mozilla::gfx::IntRect* clipRects, uint32_t numClipRects) override;
+
+#if (MOZ_WIDGET_GTK == 2)
+ GdkColormap *mColormap;
+#endif
+#endif
+};
+
+#endif /*GFXGDKNATIVERENDER_H_*/
diff --git a/gfx/thebes/gfxGlyphExtents.cpp b/gfx/thebes/gfxGlyphExtents.cpp
new file mode 100644
index 000000000..cb8f5838b
--- /dev/null
+++ b/gfx/thebes/gfxGlyphExtents.cpp
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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::GetTightGlyphExtentsAppUnits(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;
+ }
+
+ if (aFont->SetupCairoFont(aDrawTarget)) {
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+ ++gGlyphExtentsSetupLazyTight;
+#endif
+ aFont->SetupGlyphExtents(aDrawTarget, aGlyphID, true, this);
+ 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)
+{
+ 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
+{
+ 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 000000000..7da6d9192
--- /dev/null
+++ b/gfx/thebes/gfxGlyphExtents.h
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
+#include "mozilla/MemoryReporting.h"
+
+class gfxContext;
+struct gfxRect;
+
+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) {
+ MOZ_COUNT_CTOR(gfxGlyphExtents);
+ }
+ ~gfxGlyphExtents();
+
+ enum { INVALID_WIDTH = 0xFFFF };
+
+ void NotifyGlyphsChanged() {
+ 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 GetContainedGlyphWidthAppUnits(uint32_t aGlyphID) const {
+ return mContainedGlyphWidths.Get(aGlyphID);
+ }
+
+ bool IsGlyphKnown(uint32_t aGlyphID) const {
+ return mContainedGlyphWidths.Get(aGlyphID) != INVALID_WIDTH ||
+ mTightGlyphExtents.GetEntry(aGlyphID) != nullptr;
+ }
+
+ bool IsGlyphKnownWithTightExtents(uint32_t aGlyphID) const {
+ 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 GetTightGlyphExtentsAppUnits(gfxFont* aFont,
+ DrawTarget* aDrawTarget, uint32_t aGlyphID, gfxRect* aExtents);
+
+ void SetContainedGlyphWidthAppUnits(uint32_t aGlyphID, uint16_t aWidth) {
+ 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) {}
+ HashEntry(const HashEntry& toCopy) : nsUint32HashKey(toCopy) {
+ x = toCopy.x; y = toCopy.y; width = toCopy.width; height = toCopy.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;
+ nsTHashtable<HashEntry> mTightGlyphExtents;
+ int32_t mAppUnitsPerDevUnit;
+
+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 000000000..fa25bd78c
--- /dev/null
+++ b/gfx/thebes/gfxGradientCache.cpp
@@ -0,0 +1,239 @@
+/* -*- 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 "mozilla/gfx/2D.h"
+#include "nsTArray.h"
+#include "PLDHashTable.h"
+#include "nsExpirationTracker.h"
+#include "nsClassHashtable.h"
+#include "mozilla/Telemetry.h"
+#include "gfxGradientCache.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 nsTArray<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)
+ { }
+
+ 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, const GradientCacheKey& aKey)
+ : mStops(aStops),
+ mKey(aKey)
+ {}
+
+ GradientCacheData(const GradientCacheData& aOther)
+ : mStops(aOther.mStops),
+ mKey(aOther.mKey)
+ { }
+
+ nsExpirationState *GetExpirationState() {
+ return &mExpirationState;
+ }
+
+ nsExpirationState mExpirationState;
+ const RefPtr<GradientStops> mStops;
+ GradientCacheKey mKey;
+};
+
+/**
+ * This class implements a cache with no maximum size, that retains the
+ * gfxPatterns used to draw the gradients.
+ *
+ * The key is the nsStyleGradient that defines the gradient, and the size of the
+ * gradient.
+ *
+ * The value is the gfxPattern, and whether or not we perform an optimization
+ * based on the actual gradient property.
+ *
+ * An entry stays in the cache as long as it is used often. As long as a cache
+ * entry is in the cache, all the references it has are guaranteed to be valid:
+ * the nsStyleRect for the key, the gfxPattern for the value.
+ */
+class GradientCache final : public nsExpirationTracker<GradientCacheData,4>
+{
+ public:
+ GradientCache()
+ : nsExpirationTracker<GradientCacheData,4>(MAX_GENERATION_MS,
+ "GradientCache")
+ {
+ srand(time(nullptr));
+ mTimerPeriod = rand() % MAX_GENERATION_MS + 1;
+ Telemetry::Accumulate(Telemetry::GRADIENT_RETENTION_TIME, mTimerPeriod);
+ }
+
+ virtual void NotifyExpired(GradientCacheData* aObject)
+ {
+ // This will free the gfxPattern.
+ RemoveObject(aObject);
+ mHashEntries.Remove(aObject->mKey);
+ }
+
+ GradientCacheData* Lookup(const nsTArray<GradientStop>& aStops, ExtendMode aExtend, BackendType aBackendType)
+ {
+ GradientCacheData* gradient =
+ mHashEntries.Get(GradientCacheKey(aStops, aExtend, aBackendType));
+
+ if (gradient) {
+ MarkUsed(gradient);
+ }
+
+ return gradient;
+ }
+
+ // Returns true if we successfully register the gradient in the cache, false
+ // otherwise.
+ bool RegisterEntry(GradientCacheData* aValue)
+ {
+ nsresult rv = AddObject(aValue);
+ 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 things considering we are short on memory
+ // anyway, we probably don't want to retain things.
+ return false;
+ }
+ mHashEntries.Put(aValue->mKey, aValue);
+ return true;
+ }
+
+ protected:
+ uint32_t mTimerPeriod;
+ static const uint32_t MAX_GENERATION_MS = 10000;
+ /**
+ * FIXME use nsTHashtable to avoid duplicating the GradientCacheKey.
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47
+ */
+ nsClassHashtable<GradientCacheKey, GradientCacheData> mHashEntries;
+};
+
+static GradientCache* gGradientCache = nullptr;
+
+GradientStops *
+gfxGradientCache::GetGradientStops(const DrawTarget *aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend)
+{
+ if (!gGradientCache) {
+ gGradientCache = new GradientCache();
+ }
+ GradientCacheData* cached =
+ gGradientCache->Lookup(aStops, aExtend, aDT->GetBackendType());
+ if (cached && cached->mStops) {
+ if (!cached->mStops->IsValid()) {
+ gGradientCache->NotifyExpired(cached);
+ } else {
+ return cached->mStops;
+ }
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<GradientStops>
+gfxGradientCache::GetOrCreateGradientStops(const DrawTarget *aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend)
+{
+ if (aDT->IsRecording()) {
+ return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), aExtend);
+ }
+
+ RefPtr<GradientStops> gs = GetGradientStops(aDT, aStops, aExtend);
+ if (!gs) {
+ gs = aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), aExtend);
+ if (!gs) {
+ return nullptr;
+ }
+ GradientCacheData *cached =
+ new GradientCacheData(gs, GradientCacheKey(aStops, aExtend,
+ aDT->GetBackendType()));
+ if (!gGradientCache->RegisterEntry(cached)) {
+ delete cached;
+ }
+ }
+ return gs.forget();
+}
+
+void
+gfxGradientCache::PurgeAllCaches()
+{
+ if (gGradientCache) {
+ gGradientCache->AgeAllGenerations();
+ }
+}
+
+void
+gfxGradientCache::Shutdown()
+{
+ delete gGradientCache;
+ gGradientCache = nullptr;
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/thebes/gfxGradientCache.h b/gfx/thebes/gfxGradientCache.h
new file mode 100644
index 000000000..70ae258eb
--- /dev/null
+++ b/gfx/thebes/gfxGradientCache.h
@@ -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/. */
+
+#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 gfx::GradientStops*
+ GetGradientStops(const gfx::DrawTarget *aDT,
+ nsTArray<gfx::GradientStop>& aStops,
+ gfx::ExtendMode aExtend);
+
+ 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 000000000..aeebf30f2
--- /dev/null
+++ b/gfx/thebes/gfxGraphiteShaper.cpp
@@ -0,0 +1,438 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/Segment.h"
+
+#include "harfbuzz/hb.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))
+
+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()),
+ mGrFont(nullptr), mFallbackToSmallCaps(false)
+{
+ mCallbackData.mFont = aFont;
+}
+
+gfxGraphiteShaper::~gfxGraphiteShaper()
+{
+ if (mGrFont) {
+ gr_font_destroy(mGrFont);
+ }
+ mFont->GetFontEntry()->ReleaseGrFace(mGrFace);
+}
+
+/*static*/ float
+gfxGraphiteShaper::GrGetAdvance(const void* appFontHandle, uint16_t glyphid)
+{
+ const CallbackData *cb =
+ static_cast<const CallbackData*>(appFontHandle);
+ return FixedToFloat(cb->mFont->GetGlyphWidth(*cb->mDrawTarget, glyphid));
+}
+
+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 {
+ gr_face *mFace;
+ gr_feature_val *mFeatures;
+};
+
+static void
+AddFeature(const uint32_t& aTag, uint32_t& aValue, void *aUserArg)
+{
+ GrFontFeatures *f = static_cast<GrFontFeatures*>(aUserArg);
+
+ const gr_feature_ref* fref = gr_face_find_fref(f->mFace, aTag);
+ if (fref) {
+ gr_fref_set_feature_value(fref, aValue, f->mFeatures);
+ }
+}
+
+bool
+gfxGraphiteShaper::ShapeText(DrawTarget *aDrawTarget,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxShapedText *aShapedText)
+{
+ // some font back-ends require this in order to get proper hinted metrics
+ if (!mFont->SetupCairoFont(aDrawTarget)) {
+ return false;
+ }
+
+ mCallbackData.mDrawTarget = aDrawTarget;
+
+ const gfxFontStyle *style = mFont->GetStyle();
+
+ if (!mGrFont) {
+ if (!mGrFace) {
+ return false;
+ }
+
+ if (mFont->ProvidesGlyphWidths()) {
+ gr_font_ops ops = {
+ sizeof(gr_font_ops),
+ &GrGetAdvance,
+ nullptr // vertical text not yet implemented
+ };
+ mGrFont = gr_make_font_with_ops(mFont->GetAdjustedSize(),
+ &mCallbackData, &ops, mGrFace);
+ } else {
+ mGrFont = gr_make_font(mFont->GetAdjustedSize(), mGrFace);
+ }
+
+ if (!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 (style->explicitLanguage) {
+ nsAutoCString langString;
+ style->language->ToUTF8String(langString);
+ grLang = GetGraphiteTagForLang(langString);
+ }
+ gr_feature_val *grFeatures = gr_face_featureval_for_lang(mGrFace, grLang);
+
+ // insert any merged features into Graphite feature list
+ GrFontFeatures f = {mGrFace, grFeatures};
+ 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 = gr_count_unicode_characters(gr_utf16,
+ aText, aText + aLength,
+ nullptr);
+ gr_bidirtl grBidi = gr_bidirtl(aShapedText->IsRightToLeft()
+ ? (gr_rtl | gr_nobidi) : gr_nobidi);
+ gr_segment *seg = gr_make_seg(mGrFont, mGrFace, 0, grFeatures,
+ gr_utf16, aText, numChars, grBidi);
+
+ gr_featureval_destroy(grFeatures);
+
+ if (!seg) {
+ return false;
+ }
+
+ nsresult rv = SetGlyphsFromSegment(aDrawTarget, aShapedText, aOffset, aLength,
+ aText, seg);
+
+ gr_seg_destroy(seg);
+
+ return NS_SUCCEEDED(rv);
+}
+
+#define SMALL_GLYPH_RUN 256 // avoid heap allocation of per-glyph data arrays
+ // for short (typical) runs up to this length
+
+struct Cluster {
+ uint32_t baseChar; // in UTF16 code units, not Unicode character indices
+ uint32_t baseGlyph;
+ uint32_t nChars; // UTF16 code units
+ uint32_t nGlyphs;
+ Cluster() : baseChar(0), baseGlyph(0), nChars(0), nGlyphs(0) { }
+};
+
+nsresult
+gfxGraphiteShaper::SetGlyphsFromSegment(DrawTarget *aDrawTarget,
+ gfxShapedText *aShapedText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ const char16_t *aText,
+ gr_segment *aSegment)
+{
+ int32_t dev2appUnits = aShapedText->GetAppUnitsPerDevUnit();
+ bool rtl = aShapedText->IsRightToLeft();
+
+ uint32_t glyphCount = gr_seg_n_slots(aSegment);
+
+ // identify clusters; graphite may have reordered/expanded/ligated glyphs.
+ AutoTArray<Cluster,SMALL_GLYPH_RUN> clusters;
+ AutoTArray<uint16_t,SMALL_GLYPH_RUN> gids;
+ AutoTArray<float,SMALL_GLYPH_RUN> xLocs;
+ AutoTArray<float,SMALL_GLYPH_RUN> yLocs;
+
+ if (!clusters.SetLength(aLength, fallible) ||
+ !gids.SetLength(glyphCount, fallible) ||
+ !xLocs.SetLength(glyphCount, fallible) ||
+ !yLocs.SetLength(glyphCount, fallible))
+ {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // walk through the glyph slots and check which original character
+ // each is associated with
+ uint32_t gIndex = 0; // glyph slot index
+ uint32_t cIndex = 0; // current cluster index
+ for (const gr_slot *slot = gr_seg_first_slot(aSegment);
+ slot != nullptr;
+ slot = gr_slot_next_in_segment(slot), gIndex++)
+ {
+ uint32_t before =
+ gr_cinfo_base(gr_seg_cinfo(aSegment, gr_slot_before(slot)));
+ uint32_t after =
+ gr_cinfo_base(gr_seg_cinfo(aSegment, gr_slot_after(slot)));
+ gids[gIndex] = gr_slot_gid(slot);
+ xLocs[gIndex] = gr_slot_origin_X(slot);
+ yLocs[gIndex] = gr_slot_origin_Y(slot);
+
+ // if this glyph has a "before" character index that precedes the
+ // current cluster's char index, we need to merge preceding
+ // clusters until it gets included
+ while (before < clusters[cIndex].baseChar && cIndex > 0) {
+ clusters[cIndex-1].nChars += clusters[cIndex].nChars;
+ clusters[cIndex-1].nGlyphs += clusters[cIndex].nGlyphs;
+ --cIndex;
+ }
+
+ // if there's a gap between the current cluster's base character and
+ // this glyph's, extend the cluster to include the intervening chars
+ if (gr_slot_can_insert_before(slot) && clusters[cIndex].nChars &&
+ before >= clusters[cIndex].baseChar + clusters[cIndex].nChars)
+ {
+ NS_ASSERTION(cIndex < aLength - 1, "cIndex at end of word");
+ Cluster& c = clusters[cIndex + 1];
+ c.baseChar = clusters[cIndex].baseChar + clusters[cIndex].nChars;
+ c.nChars = before - c.baseChar;
+ c.baseGlyph = gIndex;
+ c.nGlyphs = 0;
+ ++cIndex;
+ }
+
+ // increment cluster's glyph count to include current slot
+ NS_ASSERTION(cIndex < aLength, "cIndex beyond word length");
+ ++clusters[cIndex].nGlyphs;
+
+ // bump |after| index if it falls in the middle of a surrogate pair
+ if (NS_IS_HIGH_SURROGATE(aText[after]) && after < aLength - 1 &&
+ NS_IS_LOW_SURROGATE(aText[after + 1])) {
+ after++;
+ }
+ // extend cluster if necessary to reach the glyph's "after" index
+ if (clusters[cIndex].baseChar + clusters[cIndex].nChars < after + 1) {
+ clusters[cIndex].nChars = after + 1 - clusters[cIndex].baseChar;
+ }
+ }
+
+ bool roundX, roundY;
+ GetRoundOffsetsToPixels(aDrawTarget, &roundX, &roundY);
+
+ gfxShapedText::CompressedGlyph *charGlyphs =
+ aShapedText->GetCharacterGlyphs() + aOffset;
+
+ // now put glyphs into the textrun, one cluster at a time
+ for (uint32_t i = 0; i <= cIndex; ++i) {
+ const Cluster& c = clusters[i];
+
+ float adv; // total advance of the cluster
+ if (rtl) {
+ if (i == 0) {
+ adv = gr_seg_advance_X(aSegment) - xLocs[c.baseGlyph];
+ } else {
+ adv = xLocs[clusters[i-1].baseGlyph] - xLocs[c.baseGlyph];
+ }
+ } else {
+ if (i == cIndex) {
+ adv = gr_seg_advance_X(aSegment) - xLocs[c.baseGlyph];
+ } else {
+ adv = xLocs[clusters[i+1].baseGlyph] - xLocs[c.baseGlyph];
+ }
+ }
+
+ // Check for default-ignorable char that didn't get filtered, combined,
+ // etc by the shaping process, and skip it.
+ uint32_t offs = c.baseChar;
+ NS_ASSERTION(offs < aLength, "unexpected offset");
+ if (c.nGlyphs == 1 && c.nChars == 1 &&
+ aShapedText->FilterIfIgnorable(aOffset + offs, aText[offs])) {
+ continue;
+ }
+
+ uint32_t appAdvance = roundX ? NSToIntRound(adv) * dev2appUnits :
+ NSToIntRound(adv * dev2appUnits);
+ if (c.nGlyphs == 1 &&
+ gfxShapedText::CompressedGlyph::IsSimpleGlyphID(gids[c.baseGlyph]) &&
+ gfxShapedText::CompressedGlyph::IsSimpleAdvance(appAdvance) &&
+ charGlyphs[offs].IsClusterStart() &&
+ yLocs[c.baseGlyph] == 0)
+ {
+ charGlyphs[offs].SetSimpleGlyph(appAdvance, gids[c.baseGlyph]);
+ } else {
+ // not a one-to-one mapping with simple metrics: use DetailedGlyph
+ AutoTArray<gfxShapedText::DetailedGlyph,8> details;
+ float clusterLoc;
+ for (uint32_t j = c.baseGlyph; j < c.baseGlyph + c.nGlyphs; ++j) {
+ gfxShapedText::DetailedGlyph* d = details.AppendElement();
+ d->mGlyphID = gids[j];
+ d->mYOffset = roundY ? NSToIntRound(-yLocs[j]) * dev2appUnits :
+ -yLocs[j] * dev2appUnits;
+ if (j == c.baseGlyph) {
+ d->mXOffset = 0;
+ d->mAdvance = appAdvance;
+ clusterLoc = xLocs[j];
+ } else {
+ float dx = rtl ? (xLocs[j] - clusterLoc) :
+ (xLocs[j] - clusterLoc - adv);
+ d->mXOffset = roundX ? NSToIntRound(dx) * dev2appUnits :
+ dx * dev2appUnits;
+ d->mAdvance = 0;
+ }
+ }
+ gfxShapedText::CompressedGlyph g;
+ g.SetComplex(charGlyphs[offs].IsClusterStart(),
+ true, details.Length());
+ aShapedText->SetGlyphs(aOffset + offs, g, details.Elements());
+ }
+
+ for (uint32_t j = c.baseChar + 1; j < c.baseChar + c.nChars; ++j) {
+ NS_ASSERTION(j < aLength, "unexpected offset");
+ gfxShapedText::CompressedGlyph &g = charGlyphs[j];
+ NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph");
+ g.SetComplex(g.IsClusterStart(), false, 0);
+ }
+ }
+
+ return NS_OK;
+}
+
+#undef SMALL_GLYPH_RUN
+
+// for language tag validation - include list of tags from the IANA registry
+#include "gfxLanguageTagList.cpp"
+
+nsTHashtable<nsUint32HashKey> *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 nsTHashtable<nsUint32HashKey>(ArrayLength(sLanguageTagList));
+ for (const uint32_t *tag = sLanguageTagList; *tag != 0; ++tag) {
+ sLanguageTags->PutEntry(*tag);
+ }
+ }
+
+ // only accept tags known in the IANA registry
+ if (sLanguageTags->GetEntry(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 000000000..e409973cc
--- /dev/null
+++ b/gfx/thebes/gfxGraphiteShaper.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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"
+
+struct gr_face;
+struct gr_font;
+struct gr_segment;
+
+class gfxGraphiteShaper : public gfxFontShaper {
+public:
+ explicit gfxGraphiteShaper(gfxFont *aFont);
+ virtual ~gfxGraphiteShaper();
+
+ virtual bool ShapeText(DrawTarget *aDrawTarget,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxShapedText *aShapedText);
+
+ static void Shutdown();
+
+protected:
+ nsresult SetGlyphsFromSegment(DrawTarget *aDrawTarget,
+ gfxShapedText *aShapedText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ const char16_t *aText,
+ gr_segment *aSegment);
+
+ static float GrGetAdvance(const void* appFontHandle, uint16_t glyphid);
+
+ gr_face *mGrFace; // owned by the font entry; shaper must call
+ // gfxFontEntry::ReleaseGrFace when finished with it
+ gr_font *mGrFont; // owned by the shaper itself
+
+ struct CallbackData {
+ gfxFont* mFont;
+ mozilla::gfx::DrawTarget* mDrawTarget;
+ };
+
+ CallbackData mCallbackData;
+ 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 nsTHashtable<nsUint32HashKey> *sLanguageTags;
+};
+
+#endif /* GFX_GRAPHITESHAPER_H */
diff --git a/gfx/thebes/gfxHarfBuzzShaper.cpp b/gfx/thebes/gfxHarfBuzzShaper.cpp
new file mode 100644
index 000000000..1f472f88d
--- /dev/null
+++ b/gfx/thebes/gfxHarfBuzzShaper.cpp
@@ -0,0 +1,1818 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsUnicodeProperties.h"
+#include "nsUnicodeScriptCodes.h"
+#include "nsUnicodeNormalizer.h"
+
+#include "harfbuzz/hb.h"
+#include "harfbuzz/hb-ot.h"
+
+#if ENABLE_INTL_API // ICU is available: we'll use it for Unicode composition
+ // and decomposition in preference to nsUnicodeNormalizer.
+#include "unicode/unorm.h"
+#include "unicode/utext.h"
+#define MOZ_HB_SHAPER_USE_ICU_NORMALIZATION 1
+static const UNormalizer2 * sNormalizer = nullptr;
+#else
+#undef MOZ_HB_SHAPER_USE_ICU_NORMALIZATION
+#endif
+
+#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),
+ mHBFace(aFont->GetFontEntry()->GetHBFace()),
+ mHBFont(nullptr),
+ mKernTable(nullptr),
+ mHmtxTable(nullptr),
+ mVmtxTable(nullptr),
+ mVORGTable(nullptr),
+ mLocaTable(nullptr),
+ mGlyfTable(nullptr),
+ mCmapTable(nullptr),
+ mCmapFormat(-1),
+ mSubtableOffset(0),
+ mUVSTableOffset(0),
+ mNumLongHMetrics(0),
+ mNumLongVMetrics(0),
+ mUseFontGetGlyph(aFont->ProvidesGetGlyph()),
+ mUseFontGlyphWidths(false),
+ mInitialized(false),
+ mVerticalInitialized(false),
+ mLoadedLocaGlyf(false),
+ mLocaLongOffsets(false)
+{
+}
+
+gfxHarfBuzzShaper::~gfxHarfBuzzShaper()
+{
+ if (mCmapTable) {
+ hb_blob_destroy(mCmapTable);
+ }
+ if (mHmtxTable) {
+ hb_blob_destroy(mHmtxTable);
+ }
+ if (mKernTable) {
+ hb_blob_destroy(mKernTable);
+ }
+ if (mVmtxTable) {
+ hb_blob_destroy(mVmtxTable);
+ }
+ if (mVORGTable) {
+ hb_blob_destroy(mVORGTable);
+ }
+ if (mLocaTable) {
+ hb_blob_destroy(mLocaTable);
+ }
+ if (mGlyfTable) {
+ hb_blob_destroy(mGlyfTable);
+ }
+ if (mHBFont) {
+ hb_font_destroy(mHBFont);
+ }
+ if (mHBFace) {
+ hb_face_destroy(mHBFace);
+ }
+}
+
+#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(mFont->GetFontEntry()->HasCmapTable(),
+ "we cannot be using this font!");
+
+ NS_ASSERTION(mCmapTable && (mCmapFormat > 0) && (mSubtableOffset > 0),
+ "cmap data not correctly set up, expect disaster");
+
+ const uint8_t* data =
+ (const uint8_t*)hb_blob_get_data(mCmapTable, nullptr);
+
+ switch (mCmapFormat) {
+ case 4:
+ gid = unicode < UNICODE_BMP_LIMIT ?
+ gfxFontUtils::MapCharToGlyphFormat4(data + mSubtableOffset,
+ unicode) : 0;
+ break;
+ case 10:
+ gid = gfxFontUtils::MapCharToGlyphFormat10(data + mSubtableOffset,
+ unicode);
+ break;
+ case 12:
+ gid = gfxFontUtils::MapCharToGlyphFormat12(data + mSubtableOffset,
+ unicode);
+ break;
+ default:
+ NS_WARNING("unsupported cmap format, glyphs will be missing");
+ break;
+ }
+ }
+
+ if (!gid) {
+ // if there's no glyph for &nbsp;, just use the space glyph instead
+ if (unicode == 0xA0) {
+ gid = mFont->GetSpaceGlyph();
+ }
+ }
+
+ 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");
+
+ const uint8_t* data =
+ (const uint8_t*)hb_blob_get_data(mCmapTable, nullptr);
+
+ 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,
+ compat);
+ }
+ break;
+ case 10:
+ return gfxFontUtils::MapCharToGlyphFormat10(data + mSubtableOffset,
+ compat);
+ break;
+ case 12:
+ return gfxFontUtils::MapCharToGlyphFormat12(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.
+static hb_codepoint_t
+GetVerticalPresentationForm(hb_codepoint_t unicode)
+{
+ 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(&unicode,
+ 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 = 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 = 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) const
+{
+ if (!mVmtxTable) {
+ // Must be a "vertical" font that doesn't actually have vertical metrics;
+ // use a fixed advance.
+ return FloatToFixed(mFont->GetMetrics(gfxFont::eVertical).aveCharWidth);
+ }
+
+ 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);
+ gfxFont *gfxfont = fcd->mShaper->GetFont();
+ if (gfxfont->ProvidesGlyphWidths()) {
+ return gfxfont->GetGlyphWidth(*fcd->mDrawTarget, glyph);
+ }
+ return fcd->mShaper->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.
+ return fcd->mShaper->GetGlyphVAdvance(glyph);
+}
+
+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 * 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.
+ }
+ }
+
+ // 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);
+ *aY = -FloatToFixed(GetFont()->GetAdjustedSize() * a / (a - d));
+ return;
+ }
+ }
+
+ NS_NOTREACHED("we shouldn't be here!");
+ *aY = -FloatToFixed(GetFont()->GetAdjustedSize() / 2);
+}
+
+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",
+ NS_ConvertUTF16toUTF8(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",
+ NS_ConvertUTF16toUTF8(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 GetMirroredChar(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(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(GetCombiningClass(aCh));
+}
+
+// Hebrew presentation forms with dagesh, for characters 0x05D0..0x05EA;
+// note that some letters do not have a dagesh presForm encoded
+static const char16_t sDageshForms[0x05EA - 0x05D0 + 1] = {
+ 0xFB30, // ALEF
+ 0xFB31, // BET
+ 0xFB32, // GIMEL
+ 0xFB33, // DALET
+ 0xFB34, // HE
+ 0xFB35, // VAV
+ 0xFB36, // ZAYIN
+ 0, // HET
+ 0xFB38, // TET
+ 0xFB39, // YOD
+ 0xFB3A, // FINAL KAF
+ 0xFB3B, // KAF
+ 0xFB3C, // LAMED
+ 0, // FINAL MEM
+ 0xFB3E, // MEM
+ 0, // FINAL NUN
+ 0xFB40, // NUN
+ 0xFB41, // SAMEKH
+ 0, // AYIN
+ 0xFB43, // FINAL PE
+ 0xFB44, // PE
+ 0, // FINAL TSADI
+ 0xFB46, // TSADI
+ 0xFB47, // QOF
+ 0xFB48, // RESH
+ 0xFB49, // SHIN
+ 0xFB4A // TAV
+};
+
+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)
+{
+#if MOZ_HB_SHAPER_USE_ICU_NORMALIZATION
+
+ if (sNormalizer) {
+ UChar32 ch = unorm2_composePair(sNormalizer, a, b);
+ if (ch >= 0) {
+ *ab = ch;
+ return true;
+ }
+ }
+
+#else // no ICU available, use the old nsUnicodeNormalizer
+
+ if (nsUnicodeNormalizer::Compose(a, b, ab)) {
+ return true;
+ }
+
+#endif
+
+ 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
+
+#if MOZ_HB_SHAPER_USE_ICU_NORMALIZATION
+
+ if (!sNormalizer) {
+ return false;
+ }
+
+ // Canonical decompositions are never more than two characters,
+ // or a maximum of 4 utf-16 code units.
+ const unsigned MAX_DECOMP_LENGTH = 4;
+
+ UErrorCode error = U_ZERO_ERROR;
+ UChar decomp[MAX_DECOMP_LENGTH];
+ int32_t len = unorm2_getRawDecomposition(sNormalizer, ab, decomp,
+ MAX_DECOMP_LENGTH, &error);
+ if (U_FAILURE(error) || len < 0) {
+ return false;
+ }
+
+ UText text = UTEXT_INITIALIZER;
+ utext_openUChars(&text, decomp, len, &error);
+ NS_ASSERTION(U_SUCCESS(error), "UText failure?");
+
+ UChar32 ch = UTEXT_NEXT32(&text);
+ if (ch != U_SENTINEL) {
+ *a = ch;
+ }
+ ch = UTEXT_NEXT32(&text);
+ if (ch != U_SENTINEL) {
+ *b = ch;
+ }
+ utext_close(&text);
+
+ return *b != 0 || *a != ab;
+
+#else // no ICU available, use the old nsUnicodeNormalizer
+
+ return nsUnicodeNormalizer::DecomposeNonRecursively(ab, a, b);
+
+#endif
+}
+
+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_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;
+
+ mUseFontGlyphWidths = mFont->ProvidesGlyphWidths();
+
+ 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);
+
+ 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);
+
+#if MOZ_HB_SHAPER_USE_ICU_NORMALIZATION
+ UErrorCode error = U_ZERO_ERROR;
+ sNormalizer = unorm2_getNFCInstance(&error);
+ NS_ASSERTION(U_SUCCESS(error), "failed to get ICU normalizer");
+#endif
+ }
+
+ 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);
+ bool symbol;
+ mCmapFormat = gfxFontUtils::
+ FindPreferredSubtable(data, len,
+ &mSubtableOffset, &mUVSTableOffset,
+ &symbol);
+ 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;
+ }
+ }
+
+ mHBFont = hb_font_create(mHBFace);
+ hb_font_set_funcs(mHBFont, sHBFontFuncs, &mCallbackData, nullptr);
+ hb_font_set_ppem(mHBFont, mFont->GetAdjustedSize(), mFont->GetAdjustedSize());
+ uint32_t scale = FloatToFixed(mFont->GetAdjustedSize()); // 16.16 fixed-point
+ hb_font_set_scale(mHBFont, scale, scale);
+
+ return true;
+}
+
+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;
+}
+
+bool
+gfxHarfBuzzShaper::InitializeVertical()
+{
+ // We only try this once. If we don't have a mHmtxTable after that,
+ // this font can't handle vertical shaping, so return false.
+ if (mVerticalInitialized) {
+ return mHmtxTable != nullptr;
+ }
+ mVerticalInitialized = true;
+
+ if (!mHmtxTable) {
+ if (!LoadHmtxTable()) {
+ return false;
+ }
+ }
+
+ // 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;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool
+gfxHarfBuzzShaper::ShapeText(DrawTarget *aDrawTarget,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxShapedText *aShapedText)
+{
+ // some font back-ends require this in order to get proper hinted metrics
+ if (!mFont->SetupCairoFont(aDrawTarget)) {
+ return false;
+ }
+
+ mCallbackData.mDrawTarget = aDrawTarget;
+ mUseVerticalPresentationForms = false;
+
+ if (!Initialize()) {
+ return false;
+ }
+
+ if (aVertical) {
+ if (!InitializeVertical()) {
+ return false;
+ }
+ 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);
+
+ bool isRightToLeft = aShapedText->IsRightToLeft();
+ hb_buffer_t *buffer = hb_buffer_create();
+ hb_buffer_set_unicode_funcs(buffer, sHBUnicodeFuncs);
+
+ hb_buffer_set_direction(buffer,
+ aVertical ? HB_DIRECTION_TTB :
+ (isRightToLeft ? HB_DIRECTION_RTL :
+ HB_DIRECTION_LTR));
+ hb_script_t scriptTag;
+ if (aShapedText->GetFlags() & gfxTextRunFactory::TEXT_USE_MATH_SCRIPT) {
+ scriptTag = sMathScript;
+ } else {
+ scriptTag = GetHBScriptUsedForShaping(aScript);
+ }
+ hb_buffer_set_script(buffer, 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 (style->explicitLanguage) {
+ nsCString langString;
+ style->language->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(buffer, language);
+
+ uint32_t length = aLength;
+ hb_buffer_add_utf16(buffer,
+ reinterpret_cast<const uint16_t*>(aText),
+ length, 0, length);
+
+ hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
+
+ hb_shape(mHBFont, buffer, features.Elements(), features.Length());
+
+ if (isRightToLeft) {
+ hb_buffer_reverse(buffer);
+ }
+
+ nsresult rv = SetGlyphsFromRun(aDrawTarget, aShapedText, aOffset, aLength,
+ aText, buffer, aVertical);
+
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "failed to store glyphs into gfxShapedWord");
+ hb_buffer_destroy(buffer);
+
+ 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(DrawTarget *aDrawTarget,
+ gfxShapedText *aShapedText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ const char16_t *aText,
+ hb_buffer_t *aBuffer,
+ bool aVertical)
+{
+ uint32_t numGlyphs;
+ const hb_glyph_info_t *ginfo = hb_buffer_get_glyph_infos(aBuffer, &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) {
+ GetRoundOffsetsToPixels(aDrawTarget, &roundB, &roundI);
+ } else {
+ GetRoundOffsetsToPixels(aDrawTarget, &roundI, &roundB);
+ }
+
+ int32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit();
+ gfxShapedText::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(aBuffer, 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) {
+ 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 &&
+ gfxTextRun::CompressedGlyph::IsSimpleGlyphID(ginfo[glyphStart].codepoint) &&
+ gfxTextRun::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.
+ // For vertical orientation, we add a "base offset" to compensate
+ // for the positioning within the cluster being based on horizontal
+ // glyph origin/offset.
+ hb_position_t baseIOffset, baseBOffset;
+ if (aVertical) {
+ baseIOffset = 2 * (i_offset - i_advance);
+ baseBOffset = GetGlyphHAdvance(ginfo[glyphStart].codepoint);
+ }
+ while (1) {
+ gfxTextRun::DetailedGlyph* details =
+ detailedGlyphs.AppendElement();
+ details->mGlyphID = ginfo[glyphStart].codepoint;
+
+ details->mXOffset = iOffset;
+ details->mAdvance = advance;
+
+ details->mYOffset = 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 = baseIOffset - posInfo[glyphStart].y_offset;
+ i_advance = posInfo[glyphStart].y_advance;
+ b_offset = baseBOffset - 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);
+ }
+ }
+
+ gfxShapedText::CompressedGlyph g;
+ g.SetComplex(charGlyphs[baseCharIndex].IsClusterStart(),
+ true, detailedGlyphs.Length());
+ aShapedText->SetGlyphs(aOffset + baseCharIndex,
+ g, 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)) {
+ gfxShapedText::CompressedGlyph &g = charGlyphs[baseCharIndex];
+ NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph");
+ g.SetComplex(g.IsClusterStart(), false, 0);
+ }
+
+ glyphStart = glyphEnd;
+ charStart = charEnd;
+ }
+
+ return NS_OK;
+}
diff --git a/gfx/thebes/gfxHarfBuzzShaper.h b/gfx/thebes/gfxHarfBuzzShaper.h
new file mode 100644
index 000000000..70d912cc0
--- /dev/null
+++ b/gfx/thebes/gfxHarfBuzzShaper.h
@@ -0,0 +1,190 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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;
+ mozilla::gfx::DrawTarget* mDrawTarget;
+ };
+
+ bool Initialize();
+ virtual bool ShapeText(DrawTarget *aDrawTarget,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxShapedText *aShapedText);
+
+ // 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;
+
+ hb_position_t GetGlyphVAdvance(hb_codepoint_t glyph) const;
+
+ 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;
+ }
+
+protected:
+ nsresult SetGlyphsFromRun(DrawTarget *aDrawTarget,
+ gfxShapedText *aShapedText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ const char16_t *aText,
+ hb_buffer_t *aBuffer,
+ bool aVertical);
+
+ // retrieve glyph positions, applying advance adjustments and attachments
+ // returns results in appUnits
+ nscoord GetGlyphPositions(gfxContext *aContext,
+ hb_buffer_t *aBuffer,
+ nsTArray<nsPoint>& aPositions,
+ uint32_t aAppUnitsPerDevUnit);
+
+ bool InitializeVertical();
+ bool LoadHmtxTable();
+
+ struct Glyf { // we only need the bounding-box at the beginning
+ // of the glyph record, not the actual outline data
+ AutoSwap_PRInt16 numberOfContours;
+ AutoSwap_PRInt16 xMin;
+ AutoSwap_PRInt16 yMin;
+ AutoSwap_PRInt16 xMax;
+ AutoSwap_PRInt16 yMax;
+ };
+
+ const Glyf *FindGlyf(hb_codepoint_t aGlyph, bool *aEmptyGlyf) const;
+
+ // harfbuzz face object: we acquire a reference from the font entry
+ // on shaper creation, and release it in our destructor
+ hb_face_t *mHBFace;
+
+ // size-specific font object, owned by the gfxHarfBuzzShaper
+ hb_font_t *mHBFont;
+
+ 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;
+
+ // Whether the font implements GetGlyph, or we should read tables
+ // directly
+ bool mUseFontGetGlyph;
+ // 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 000000000..fe236892b
--- /dev/null
+++ b/gfx/thebes/gfxImageSurface.cpp
@@ -0,0 +1,379 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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),
+ 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 moz_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;
+ }
+
+ CopyForStride(mData, data->GetData(), size, mStride, data->Stride());
+
+ 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;
+ }
+
+ CopyForStride(data->GetData(), mData, size, data->Stride(), 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 000000000..dea580f5e
--- /dev/null
+++ b/gfx/thebes/gfxImageSurface.h
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 000000000..c6b71591e
--- /dev/null
+++ b/gfx/thebes/gfxLanguageTagList.cpp
@@ -0,0 +1,7876 @@
+/* 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 000000000..a217fb309
--- /dev/null
+++ b/gfx/thebes/gfxLineSegment.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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(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) * (mEnd.x - mStart.x ) -
+ (aOther.mEnd.x - aOther.mStart.x ) * (mEnd.y - mStart.y);
+
+ // 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) * (mStart.y - aOther.mStart.y) -
+ (aOther.mEnd.y - aOther.mStart.y) * (mStart.x - aOther.mStart.x);
+
+ gfxFloat bnumerator = (mEnd.x - mStart.x) * (mStart.y - aOther.mStart.y) -
+ (mEnd.y - mStart.y) * (mStart.x - aOther.mStart.x);
+
+ 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 000000000..f512c689f
--- /dev/null
+++ b/gfx/thebes/gfxMacFont.cpp
@@ -0,0 +1,475 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxCoreTextShaper.h"
+#include <algorithm>
+#include "gfxPlatformMac.h"
+#include "gfxContext.h"
+#include "gfxFontUtils.h"
+#include "gfxMacPlatformFontList.h"
+#include "gfxFontConstants.h"
+#include "gfxTextRun.h"
+
+#include "cairo-quartz.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+gfxMacFont::gfxMacFont(MacOSFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
+ bool aNeedsBold)
+ : gfxFont(aFontEntry, aFontStyle),
+ mCGFont(nullptr),
+ mCTFont(nullptr),
+ mFontFace(nullptr)
+{
+ mApplySyntheticBold = aNeedsBold;
+
+ mCGFont = aFontEntry->GetFontRef();
+ if (!mCGFont) {
+ mIsValid = false;
+ return;
+ }
+
+ // InitMetrics will handle the sizeAdjust factor and set mAdjustedSize
+ InitMetrics();
+ if (!mIsValid) {
+ return;
+ }
+
+ mFontFace = cairo_quartz_font_face_create_for_cgfont(mCGFont);
+
+ cairo_status_t cairoerr = cairo_font_face_status(mFontFace);
+ if (cairoerr != CAIRO_STATUS_SUCCESS) {
+ mIsValid = false;
+#ifdef DEBUG
+ char warnBuf[1024];
+ SprintfLiteral(warnBuf,
+ "Failed to create Cairo font face: %s status: %d",
+ NS_ConvertUTF16toUTF8(GetName()).get(), cairoerr);
+ NS_WARNING(warnBuf);
+#endif
+ return;
+ }
+
+ cairo_matrix_t sizeMatrix, ctm;
+ cairo_matrix_init_identity(&ctm);
+ cairo_matrix_init_scale(&sizeMatrix, mAdjustedSize, mAdjustedSize);
+
+ // synthetic oblique by skewing via the font matrix
+ bool needsOblique = mFontEntry != nullptr &&
+ mFontEntry->IsUpright() &&
+ mStyle.style != NS_FONT_STYLE_NORMAL &&
+ mStyle.allowSyntheticStyle;
+
+ if (needsOblique) {
+ cairo_matrix_t style;
+ cairo_matrix_init(&style,
+ 1, //xx
+ 0, //yx
+ -1 * OBLIQUE_SKEW_FACTOR, //xy
+ 1, //yy
+ 0, //x0
+ 0); //y0
+ cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style);
+ }
+
+ cairo_font_options_t *fontOptions = cairo_font_options_create();
+
+ // turn off font anti-aliasing based on user pref setting
+ if (mAdjustedSize <=
+ (gfxFloat)gfxPlatformMac::GetPlatform()->GetAntiAliasingThreshold()) {
+ cairo_font_options_set_antialias(fontOptions, CAIRO_ANTIALIAS_NONE);
+ mAntialiasOption = kAntialiasNone;
+ } else if (mStyle.useGrayscaleAntialiasing) {
+ cairo_font_options_set_antialias(fontOptions, CAIRO_ANTIALIAS_GRAY);
+ mAntialiasOption = kAntialiasGrayscale;
+ }
+
+ mScaledFont = cairo_scaled_font_create(mFontFace, &sizeMatrix, &ctm,
+ fontOptions);
+ cairo_font_options_destroy(fontOptions);
+
+ cairoerr = cairo_scaled_font_status(mScaledFont);
+ if (cairoerr != CAIRO_STATUS_SUCCESS) {
+ mIsValid = false;
+#ifdef DEBUG
+ char warnBuf[1024];
+ SprintfLiteral(warnBuf, "Failed to create scaled font: %s status: %d",
+ NS_ConvertUTF16toUTF8(GetName()).get(), cairoerr);
+ NS_WARNING(warnBuf);
+#endif
+ }
+}
+
+gfxMacFont::~gfxMacFont()
+{
+ if (mCTFont) {
+ ::CFRelease(mCTFont);
+ }
+ if (mScaledFont) {
+ cairo_scaled_font_destroy(mScaledFont);
+ }
+ if (mFontFace) {
+ cairo_font_face_destroy(mFontFace);
+ }
+}
+
+bool
+gfxMacFont::ShapeText(DrawTarget *aDrawTarget,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ 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.
+ if (static_cast<MacOSFontEntry*>(GetFontEntry())->RequiresAATLayout() &&
+ !aVertical) {
+ if (!mCoreTextShaper) {
+ mCoreTextShaper = MakeUnique<gfxCoreTextShaper>(this);
+ }
+ if (mCoreTextShaper->ShapeText(aDrawTarget, aText, aOffset, aLength,
+ aScript, aVertical, aShapedText)) {
+ PostShapingFixup(aDrawTarget, aText, aOffset,
+ aLength, aVertical, aShapedText);
+ return true;
+ }
+ }
+
+ return gfxFont::ShapeText(aDrawTarget, aText, aOffset, aLength, aScript,
+ aVertical, aShapedText);
+}
+
+bool
+gfxMacFont::SetupCairoFont(DrawTarget* aDrawTarget)
+{
+ if (cairo_scaled_font_status(mScaledFont) != CAIRO_STATUS_SUCCESS) {
+ // Don't cairo_set_scaled_font as that would propagate the error to
+ // the cairo_t, precluding any further drawing.
+ return false;
+ }
+ cairo_set_scaled_font(gfxFont::RefCairo(aDrawTarget), mScaledFont);
+ return true;
+}
+
+gfxFont::RunMetrics
+gfxMacFont::Measure(const gfxTextRun *aTextRun,
+ uint32_t aStart, uint32_t aEnd,
+ BoundingBoxType aBoundingBoxType,
+ DrawTarget *aRefDrawTarget,
+ Spacing *aSpacing,
+ uint16_t 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)
+ 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;
+ }
+ ::CFRelease(headData);
+ }
+ 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)",
+ NS_ConvertUTF16toUTF8(mFontEntry->Name()).get());
+ NS_WARNING(warnBuf);
+#endif
+ return;
+ }
+
+ mAdjustedSize = std::max(mStyle.size, 1.0);
+ 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;
+ }
+
+ if (mStyle.sizeAdjust > 0.0 && mStyle.size > 0.0 &&
+ mMetrics.xHeight > 0.0) {
+ // apply font-size-adjust, and recalculate metrics
+ gfxFloat aspect = mMetrics.xHeight / mStyle.size;
+ 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;
+ }
+ if (mMetrics.xHeight == 0.0) {
+ mMetrics.xHeight = ::CGFontGetXHeight(mCGFont) * cgConvFactor;
+ }
+ }
+
+ // 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
+
+ CFDataRef cmap =
+ ::CGFontCopyTableForTag(mCGFont, TRUETYPE_TAG('c','m','a','p'));
+
+ uint32_t glyphID;
+ 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;
+ }
+ }
+ if (IsSyntheticBold()) {
+ mMetrics.aveCharWidth += GetSyntheticBoldOffset();
+ mMetrics.maxAdvance += GetSyntheticBoldOffset();
+ }
+
+ mMetrics.spaceWidth = GetCharWidth(cmap, ' ', &glyphID, cgConvFactor);
+ if (glyphID == 0) {
+ // no space glyph?!
+ mMetrics.spaceWidth = mMetrics.aveCharWidth;
+ }
+ mSpaceGlyph = glyphID;
+
+ mMetrics.zeroOrAveCharWidth = GetCharWidth(cmap, '0', &glyphID,
+ cgConvFactor);
+ if (glyphID == 0) {
+ mMetrics.zeroOrAveCharWidth = mMetrics.aveCharWidth;
+ }
+
+ if (cmap) {
+ ::CFRelease(cmap);
+ }
+
+ CalculateDerivedMetrics(mMetrics);
+
+ SanitizeMetrics(&mMetrics, mFontEntry->mIsBadUnderlineFont);
+
+#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(DrawTarget& aDrawTarget, uint16_t aGID)
+{
+ if (!mCTFont) {
+ mCTFont = ::CTFontCreateWithGraphicsFont(mCGFont, mAdjustedSize,
+ nullptr, nullptr);
+ 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, kCTFontDefaultOrientation, &aGID,
+ &advance, 1);
+ return advance.width * 0x10000;
+}
+
+// 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()
+{
+ 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);
+
+ ::CFRelease(ctFont);
+
+ mIsValid = true;
+}
+
+already_AddRefed<ScaledFont>
+gfxMacFont::GetScaledFont(DrawTarget *aTarget)
+{
+ if (!mAzureScaledFont) {
+ NativeFont nativeFont;
+ nativeFont.mType = NativeFontType::MAC_FONT_FACE;
+ nativeFont.mFont = GetCGFontRef();
+ mAzureScaledFont = mozilla::gfx::Factory::CreateScaledFontWithCairo(nativeFont, GetAdjustedSize(), mScaledFont);
+ }
+
+ RefPtr<ScaledFont> scaledFont(mAzureScaledFont);
+ return scaledFont.forget();
+}
+
+already_AddRefed<mozilla::gfx::GlyphRenderingOptions>
+gfxMacFont::GetGlyphRenderingOptions(const TextRunDrawParams* aRunParams)
+{
+ if (aRunParams) {
+ return mozilla::gfx::Factory::CreateCGGlyphRenderingOptions(aRunParams->fontSmoothingBGColor);
+ }
+ return nullptr;
+}
+
+void
+gfxMacFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const
+{
+ gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+ // mCGFont is shared with the font entry, so not counted here;
+ // and we don't have APIs to measure the cairo mFontFace object
+}
+
+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 000000000..d12cc717b
--- /dev/null
+++ b/gfx/thebes/gfxMacFont.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "cairo.h"
+#include <ApplicationServices/ApplicationServices.h>
+
+class MacOSFontEntry;
+
+class gfxMacFont : public gfxFont
+{
+public:
+ gfxMacFont(MacOSFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
+ bool aNeedsBold);
+
+ virtual ~gfxMacFont();
+
+ CGFontRef GetCGFontRef() const { return mCGFont; }
+
+ /* overrides for the pure virtual methods in gfxFont */
+ virtual uint32_t GetSpaceGlyph() override {
+ return mSpaceGlyph;
+ }
+
+ virtual bool SetupCairoFont(DrawTarget* aDrawTarget) override;
+
+ /* override Measure to add padding for antialiasing */
+ virtual RunMetrics Measure(const gfxTextRun *aTextRun,
+ uint32_t aStart, uint32_t aEnd,
+ BoundingBoxType aBoundingBoxType,
+ DrawTarget *aDrawTargetForTightBoundingBox,
+ Spacing *aSpacing,
+ uint16_t 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.
+ virtual bool ProvidesGlyphWidths() const override {
+ return mFontEntry->HasFontTable(TRUETYPE_TAG('s','b','i','x'));
+ }
+
+ virtual int32_t GetGlyphWidth(DrawTarget& aDrawTarget,
+ uint16_t aGID) override;
+
+ virtual already_AddRefed<mozilla::gfx::ScaledFont>
+ GetScaledFont(mozilla::gfx::DrawTarget *aTarget) override;
+
+ virtual already_AddRefed<mozilla::gfx::GlyphRenderingOptions>
+ GetGlyphRenderingOptions(const TextRunDrawParams* aRunParams = nullptr) override;
+
+ virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const override;
+ virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const override;
+
+ virtual FontType GetType() const override { return FONT_TYPE_MAC; }
+
+protected:
+ virtual const Metrics& GetHorizontalMetrics() override {
+ return mMetrics;
+ }
+
+ // override to prefer CoreText shaping with fonts that depend on AAT
+ virtual bool ShapeText(DrawTarget *aDrawTarget,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ 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 weak reference to the CoreGraphics font: this is owned by the
+ // MacOSFontEntry, it is not retained or released by gfxMacFont
+ CGFontRef mCGFont;
+
+ // a Core Text font reference, created only if we're using CT to measure
+ // glyph widths; otherwise null.
+ CTFontRef mCTFont;
+
+ cairo_font_face_t *mFontFace;
+
+ mozilla::UniquePtr<gfxFontShaper> mCoreTextShaper;
+
+ Metrics mMetrics;
+ uint32_t mSpaceGlyph;
+};
+
+#endif /* GFX_MACFONT_H */
diff --git a/gfx/thebes/gfxMacPlatformFontList.h b/gfx/thebes/gfxMacPlatformFontList.h
new file mode 100644
index 000000000..0ab062dca
--- /dev/null
+++ b/gfx/thebes/gfxMacPlatformFontList.h
@@ -0,0 +1,182 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/MemoryReporting.h"
+#include "nsDataHashtable.h"
+#include "nsRefPtrHashtable.h"
+
+#include "gfxPlatformFontList.h"
+#include "gfxPlatform.h"
+#include "gfxPlatformMac.h"
+
+#include "nsUnicharUtils.h"
+#include "nsTArray.h"
+#include "mozilla/LookAndFeel.h"
+
+class gfxMacPlatformFontList;
+
+// a single member of a font family (i.e. a single face, such as Times Italic)
+class MacOSFontEntry : public gfxFontEntry
+{
+public:
+ friend class gfxMacPlatformFontList;
+
+ MacOSFontEntry(const nsAString& aPostscriptName, int32_t aWeight,
+ bool aIsStandardFace = false,
+ double aSizeHint = 0.0);
+
+ // for use with data fonts
+ MacOSFontEntry(const nsAString& aPostscriptName, CGFontRef aFontRef,
+ uint16_t aWeight, uint16_t aStretch, uint8_t aStyle,
+ bool aIsDataUserFont, bool aIsLocal);
+
+ virtual ~MacOSFontEntry() {
+ ::CGFontRelease(mFontRef);
+ }
+
+ virtual CGFontRef GetFontRef();
+
+ // override gfxFontEntry table access function to bypass table cache,
+ // use CGFontRef API to get direct access to system font data
+ virtual hb_blob_t *GetFontTable(uint32_t aTag) override;
+
+ virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const override;
+
+ nsresult ReadCMAP(FontInfoData *aFontInfoData = nullptr) override;
+
+ bool RequiresAATLayout() const { return mRequiresAAT; }
+
+ bool IsCFF();
+
+protected:
+ virtual gfxFont* CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold) override;
+
+ virtual 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;
+ nsTHashtable<nsUint32HashKey> mAvailableTables;
+};
+
+class gfxMacPlatformFontList : public gfxPlatformFontList {
+public:
+ static gfxMacPlatformFontList* PlatformFontList() {
+ return static_cast<gfxMacPlatformFontList*>(sPlatformFontList);
+ }
+
+ static int32_t AppleWeightToCSSWeight(int32_t aAppleWeight);
+
+ bool GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName) override;
+
+ gfxFontEntry* LookupLocalFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle) override;
+
+ gfxFontEntry* MakePlatformFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t* aFontData,
+ uint32_t aLength) override;
+
+ bool FindAndAddFamilies(const nsAString& aFamily,
+ nsTArray<gfxFontFamily*>* aOutput,
+ gfxFontStyle* aStyle = nullptr,
+ gfxFloat aDevToCssSize = 1.0) override;
+
+ // lookup the system font for a particular system font type and set
+ // the name and style characteristics
+ void LookupSystemFont(mozilla::LookAndFeel::FontID aSystemFontID,
+ nsAString& aSystemFontName,
+ gfxFontStyle &aFontStyle,
+ float aDevPixPerCSSPixel);
+
+protected:
+ virtual gfxFontFamily*
+ GetDefaultFontForPlatform(const gfxFontStyle* aStyle) override;
+
+private:
+ friend class gfxPlatformMac;
+
+ gfxMacPlatformFontList();
+ virtual ~gfxMacPlatformFontList();
+
+ // initialize font lists
+ virtual nsresult InitFontListForPlatform() override;
+
+ // special case font faces treated as font families (set via prefs)
+ void InitSingleFaceList();
+
+ // initialize system fonts
+ void InitSystemFontNames();
+
+ // helper function to lookup in both hidden system fonts and normal fonts
+ gfxFontFamily* FindSystemFontFamily(const nsAString& aFamily);
+
+ 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(const uint32_t aCh,
+ Script aRunScript,
+ const gfxFontStyle* aMatchStyle,
+ gfxFontFamily** aMatchedFamily) override;
+
+ bool UsesSystemFallback() override { return true; }
+
+ already_AddRefed<FontInfoData> CreateFontInfoData() override;
+
+ // Add the specified family to mSystemFontFamilies or 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);
+
+#ifdef MOZ_BUNDLED_FONTS
+ void ActivateBundledFonts();
+#endif
+
+ enum {
+ kATSGenerationInitial = -1
+ };
+
+ // default font for use with system-wide font fallback
+ CTFontRef mDefaultFont;
+
+ // hidden system fonts used within UI elements, there may be a whole set
+ // for different locales (e.g. .Helvetica Neue UI, .SF NS Text)
+ FontFamilyTable mSystemFontFamilies;
+
+ // 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;
+ nsString mSystemTextFontFamilyName;
+ nsString mSystemDisplayFontFamilyName; // only used on OSX 10.11
+};
+
+#endif /* gfxMacPlatformFontList_H_ */
diff --git a/gfx/thebes/gfxMacPlatformFontList.mm b/gfx/thebes/gfxMacPlatformFontList.mm
new file mode 100644
index 000000000..bf958a90b
--- /dev/null
+++ b/gfx/thebes/gfxMacPlatformFontList.mm
@@ -0,0 +1,1451 @@
+/* -*- Mode: ObjC; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * ***** 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 "gfxPlatformMac.h"
+#include "gfxMacPlatformFontList.h"
+#include "gfxMacFont.h"
+#include "gfxUserFontSet.h"
+#include "harfbuzz/hb.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsTArray.h"
+
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsISimpleEnumerator.h"
+#include "nsCharTraits.h"
+#include "nsCocoaFeatures.h"
+#include "nsCocoaUtils.h"
+#include "gfxFontConstants.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/gfx/2D.h"
+
+#include <unistd.h>
+#include <time.h>
+#include <dlfcn.h>
+
+using namespace mozilla;
+
+// 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& aDist)
+{
+ aDist.SetLength([aSrc length]);
+ [aSrc getCharacters:reinterpret_cast<unichar*>(aDist.BeginWriting())];
+}
+
+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) {
+ return NS_OK;
+ }
+
+ RefPtr<gfxCharacterMap> charmap;
+ nsresult rv;
+ bool symbolFont = false; // currently ignored
+
+ if (aFontInfoData && (charmap = GetCMAPFromFontInfo(aFontInfoData,
+ mUVSOffset,
+ symbolFont))) {
+ rv = NS_OK;
+ } else {
+ uint32_t kCMAP = TRUETYPE_TAG('c','m','a','p');
+ charmap = new gfxCharacterMap();
+ AutoTable cmapTable(this, kCMAP);
+
+ if (cmapTable) {
+ bool unicodeFont = false; // currently ignored
+ uint32_t cmapLen;
+ const uint8_t* cmapData =
+ reinterpret_cast<const uint8_t*>(hb_blob_get_data(cmapTable,
+ &cmapLen));
+ rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen,
+ *charmap, mUVSOffset,
+ unicodeFont, symbolFont);
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv) && !HasGraphiteTables()) {
+ // We 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 and/or
+ // opentype layout tables
+ bool hasAATLayout = HasFontTable(TRUETYPE_TAG('m','o','r','x')) ||
+ HasFontTable(TRUETYPE_TAG('m','o','r','t'));
+ bool hasGSUB = HasFontTable(TRUETYPE_TAG('G','S','U','B'));
+ bool hasGPOS = HasFontTable(TRUETYPE_TAG('G','P','O','S'));
+ if (hasAATLayout && !(hasGSUB || hasGPOS)) {
+ mRequiresAAT = true; // prefer CoreText if font has no OTL tables
+ }
+
+ 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)) {
+ 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.
+ // Blacklist 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);
+ }
+ }
+
+ mHasCmapTable = NS_SUCCEEDED(rv);
+ if (mHasCmapTable) {
+ gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList();
+ mCharacterMap = pfl->FindCharMap(charmap);
+ } else {
+ // if error occurred, initialize to null cmap
+ mCharacterMap = new gfxCharacterMap();
+ }
+
+ LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %d hash: %8.8x%s\n",
+ NS_ConvertUTF16toUTF8(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",
+ NS_ConvertUTF16toUTF8(mName).get());
+ charmap->Dump(prefix, eGfxLog_cmapdata);
+ }
+
+ return rv;
+}
+
+gfxFont*
+MacOSFontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold)
+{
+ return new gfxMacFont(this, aFontStyle, aNeedsBold);
+}
+
+bool
+MacOSFontEntry::IsCFF()
+{
+ if (!mIsCFFInitialized) {
+ mIsCFFInitialized = true;
+ mIsCFF = HasFontTable(TRUETYPE_TAG('C','F','F',' '));
+ }
+
+ return mIsCFF;
+}
+
+MacOSFontEntry::MacOSFontEntry(const nsAString& aPostscriptName,
+ int32_t aWeight,
+ bool aIsStandardFace,
+ double aSizeHint)
+ : gfxFontEntry(aPostscriptName, aIsStandardFace),
+ mFontRef(NULL),
+ mSizeHint(aSizeHint),
+ mFontRefInitialized(false),
+ mRequiresAAT(false),
+ mIsCFF(false),
+ mIsCFFInitialized(false)
+{
+ mWeight = aWeight;
+}
+
+MacOSFontEntry::MacOSFontEntry(const nsAString& aPostscriptName,
+ CGFontRef aFontRef,
+ uint16_t aWeight, uint16_t aStretch,
+ uint8_t aStyle,
+ bool aIsDataUserFont,
+ bool aIsLocalUserFont)
+ : gfxFontEntry(aPostscriptName, false),
+ mFontRef(NULL),
+ mSizeHint(0.0),
+ mFontRefInitialized(false),
+ mRequiresAAT(false),
+ mIsCFF(false),
+ mIsCFFInitialized(false)
+{
+ mFontRef = aFontRef;
+ mFontRefInitialized = true;
+ ::CFRetain(mFontRef);
+
+ mWeight = aWeight;
+ mStretch = aStretch;
+ mFixedPitch = false; // xxx - do we need this for downloaded fonts?
+ mStyle = aStyle;
+
+ NS_ASSERTION(!(aIsDataUserFont && aIsLocalUserFont),
+ "userfont is either a data font or a local font");
+ mIsDataUserFont = aIsDataUserFont;
+ mIsLocalUserFont = aIsLocalUserFont;
+}
+
+CGFontRef
+MacOSFontEntry::GetFontRef()
+{
+ if (!mFontRefInitialized) {
+ mFontRefInitialized = true;
+ NSString *psname = GetNSStringForString(mName);
+ mFontRef = ::CGFontCreateWithFontName(CFStringRef(psname));
+ if (!mFontRef) {
+ // 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) {
+ mFontRef = CTFontCopyGraphicsFont((CTFontRef)font, nullptr);
+ }
+ }
+ }
+ return mFontRef;
+}
+
+// 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)
+{
+ CGFontRef fontRef = GetFontRef();
+ 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;
+
+ CGFontRef fontRef = GetFontRef();
+ if (!fontRef) {
+ return false;
+ }
+ 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);
+ }
+ ::CFRelease(tags);
+ }
+
+ return mAvailableTables.GetEntry(aTableTag);
+}
+
+void
+MacOSFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const
+{
+ aSizes->mFontListSize += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
+
+/* gfxMacFontFamily */
+#pragma mark-
+
+class gfxMacFontFamily : public gfxFontFamily
+{
+public:
+ explicit gfxMacFontFamily(nsAString& aName, double aSizeHint) :
+ gfxFontFamily(aName),
+ mSizeHint(aSizeHint)
+ {}
+
+ virtual ~gfxMacFontFamily() {}
+
+ virtual void LocalizedName(nsAString& aLocalizedName);
+
+ virtual void FindStyleVariations(FontInfoData *aFontInfoData = nullptr);
+
+protected:
+ double mSizeHint;
+};
+
+void
+gfxMacFontFamily::LocalizedName(nsAString& aLocalizedName)
+{
+ nsAutoreleasePool localPool;
+
+ if (!HasOtherFamilyNames()) {
+ aLocalizedName = mName;
+ return;
+ }
+
+ NSString *family = GetNSStringForString(mName);
+ NSString *localized = [sFontManager
+ localizedNameForFamily:family
+ face:nil];
+
+ if (localized) {
+ GetStringForNSString(localized, 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::FindStyleVariations(FontInfoData *aFontInfoData)
+{
+ if (mHasStyles)
+ return;
+
+ nsAutoreleasePool localPool;
+
+ NSString *family = GetNSStringForString(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 = GetWeightOverride(postscriptFontName);
+ if (cssWeight) {
+ // scale down and clamp, to get a value from 1..9
+ cssWeight = ((cssWeight + 50) / 100);
+ cssWeight = std::max(1, std::min(cssWeight, 9));
+ } else {
+ cssWeight =
+ gfxMacPlatformFontList::AppleWeightToCSSWeight(appKitWeight);
+ }
+ 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(postscriptFontName, cssWeight, isStandardFace, mSizeHint);
+ if (!fontEntry) {
+ break;
+ }
+
+ // set additional properties based on the traits reported by Cocoa
+ if (macTraits & (NSCondensedFontMask | NSNarrowFontMask | NSCompressedFontMask)) {
+ fontEntry->mStretch = NS_FONT_STRETCH_CONDENSED;
+ } else if (macTraits & NSExpandedFontMask) {
+ fontEntry->mStretch = NS_FONT_STRETCH_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->mStyle = NS_FONT_STYLE_ITALIC;
+ }
+ if (macTraits & NSFixedPitchFontMask) {
+ fontEntry->mFixedPitch = true;
+ }
+
+ if (LOG_FONTLIST_ENABLED()) {
+ LOG_FONTLIST(("(fontlist) added (%s) to family (%s)"
+ " with style: %s weight: %d stretch: %d"
+ " (apple-weight: %d macTraits: %8.8x)",
+ NS_ConvertUTF16toUTF8(fontEntry->Name()).get(),
+ NS_ConvertUTF16toUTF8(Name()).get(),
+ fontEntry->IsItalic() ? "italic" : "normal",
+ cssWeight, fontEntry->Stretch(),
+ appKitWeight, macTraits));
+ }
+
+ // insert into font entry array of family
+ AddFontEntry(fontEntry);
+ }
+
+ SortAvailableFonts();
+ SetHasStyles(true);
+
+ if (mIsBadUnderlineFamily) {
+ SetBadUnderlineFonts();
+ }
+}
+
+/* gfxSingleFaceMacFontFamily */
+#pragma mark-
+
+class gfxSingleFaceMacFontFamily : public gfxFontFamily
+{
+public:
+ explicit gfxSingleFaceMacFontFamily(nsAString& aName) :
+ gfxFontFamily(aName)
+ {
+ mFaceNamesInitialized = true; // omit from face name lists
+ }
+
+ virtual ~gfxSingleFaceMacFontFamily() {}
+
+ virtual void LocalizedName(nsAString& aLocalizedName);
+
+ virtual void ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList);
+};
+
+void
+gfxSingleFaceMacFontFamily::LocalizedName(nsAString& aLocalizedName)
+{
+ nsAutoreleasePool localPool;
+
+ if (!HasOtherFamilyNames()) {
+ aLocalizedName = mName;
+ return;
+ }
+
+ gfxFontEntry *fe = mAvailableFonts[0];
+ NSFont *font = [NSFont fontWithName:GetNSStringForString(fe->Name())
+ size:0.0];
+ if (font) {
+ NSString *localized = [font displayName];
+ if (localized) {
+ GetStringForNSString(localized, aLocalizedName);
+ return;
+ }
+ }
+
+ // failed to get localized name, just use the canonical one
+ aLocalizedName = mName;
+}
+
+void
+gfxSingleFaceMacFontFamily::ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList)
+{
+ 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)
+{
+#ifdef MOZ_BUNDLED_FONTS
+ ActivateBundledFonts();
+#endif
+
+ ::CFNotificationCenterAddObserver(::CFNotificationCenterGetLocalCenter(),
+ this,
+ RegisteredFontsChangedNotificationCallback,
+ kCTFontManagerRegisteredFontsChangedNotification,
+ 0,
+ CFNotificationSuspensionBehaviorDeliverImmediately);
+
+ // cache this in a static variable so that MacOSFontFamily objects
+ // don't have to repeatedly look it up
+ sFontManager = [NSFontManager sharedFontManager];
+}
+
+gfxMacPlatformFontList::~gfxMacPlatformFontList()
+{
+ if (mDefaultFont) {
+ ::CFRelease(mDefaultFont);
+ }
+}
+
+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) {
+ return;
+ }
+
+ bool hiddenSystemFont = [family hasPrefix:@"."];
+
+ FontFamilyTable& table =
+ hiddenSystemFont ? mSystemFontFamilies : mFontFamilies;
+
+ nsAutoString familyName;
+ nsCocoaUtils::GetStringForNSString(family, familyName);
+
+ double sizeHint = 0.0;
+ if (hiddenSystemFont && mUseSizeSensitiveSystemFont &&
+ mSystemDisplayFontFamilyName.Equals(familyName)) {
+ sizeHint = 128.0;
+ }
+
+ nsAutoString key;
+ ToLowerCase(familyName, key);
+
+ RefPtr<gfxFontFamily> familyEntry = new gfxMacFontFamily(familyName, sizeHint);
+ table.Put(key, familyEntry);
+
+ // check the bad underline blacklist
+ if (mBadUnderlineFamilyNames.Contains(key)) {
+ familyEntry->SetBadUnderlineFamily();
+ }
+}
+
+nsresult
+gfxMacPlatformFontList::InitFontListForPlatform()
+{
+ nsAutoreleasePool localPool;
+
+ Telemetry::AutoTimer<Telemetry::MAC_INITFONTLIST_TOTAL> timer;
+
+ // reset system font list
+ mSystemFontFamilies.Clear();
+
+ // iterate over available families
+
+ InitSystemFontNames();
+
+ CFArrayRef familyNames = CTFontManagerCopyAvailableFontFamilyNames();
+
+ for (NSString* familyName in (NSArray*)familyNames) {
+ AddFamily((CFStringRef)familyName);
+ }
+
+ CFRelease(familyNames);
+
+ 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::InitSingleFaceList()
+{
+ AutoTArray<nsString, 10> singleFaceFonts;
+ gfxFontUtils::GetPrefsFontList("font.single-face-list", singleFaceFonts);
+
+ for (const auto& singleFaceFamily : singleFaceFonts) {
+ LOG_FONTLIST(("(fontlist-singleface) face name: %s\n",
+ NS_ConvertUTF16toUTF8(singleFaceFamily).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
+ nsAutoString familyName(singleFaceFamily);
+ 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.
+ nsAutoString key(Substring(familyName, colon + 1));
+ ToLowerCase(key);
+ gfxFontFamily* family = mFontFamilies.GetWeak(key);
+ if (!family) {
+ continue;
+ }
+ family->FindStyleVariations();
+
+ // Truncate the entry from prefs at the colon, so now it is just the
+ // desired single-face-family name.
+ familyName.Truncate(colon);
+
+ // Look through the family's faces to see if this one is present.
+ const gfxFontEntry* fe = nullptr;
+ for (const auto& face : family->GetFontList()) {
+ if (face->Name().Equals(familyName)) {
+ fe = face;
+ break;
+ }
+ }
+ if (!fe) {
+ continue;
+ }
+
+ // We found the correct face, so create the single-face family record.
+ GenerateFontListKey(familyName, key);
+ LOG_FONTLIST(("(fontlist-singleface) family name: %s, key: %s\n",
+ NS_ConvertUTF16toUTF8(familyName).get(),
+ NS_ConvertUTF16toUTF8(key).get()));
+
+ // add only if doesn't exist already
+ if (!mFontFamilies.GetWeak(key)) {
+ RefPtr<gfxFontFamily> familyEntry =
+ new gfxSingleFaceMacFontFamily(familyName);
+ // 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->mWeight, true,
+ static_cast<const MacOSFontEntry*>(fe)->
+ mSizeHint);
+ familyEntry->AddFontEntry(fontEntry);
+ familyEntry->SetHasStyles(true);
+ mFontFamilies.Put(key, familyEntry);
+ LOG_FONTLIST(("(fontlist-singleface) added new family\n",
+ NS_ConvertUTF16toUTF8(familyName).get(),
+ NS_ConvertUTF16toUTF8(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)
+{
+ NSFont* f = [NSFont fontWithName: [[aFont fontDescriptor] postscriptName]
+ size: 0.0];
+ return [f familyName];
+}
+
+// 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()
+{
+ // system font under 10.11 are two distinct families for text/display sizes
+ if (nsCocoaFeatures::OnElCapitanOrLater()) {
+ mUseSizeSensitiveSystemFont = true;
+ }
+
+ // text font family
+ NSFont* sys = [NSFont systemFontOfSize: 0.0];
+ NSString* textFamilyName = GetRealFamilyName(sys);
+ nsAutoString familyName;
+ nsCocoaUtils::GetStringForNSString(textFamilyName, familyName);
+ mSystemTextFontFamilyName = familyName;
+
+ // display font family, if on OSX 10.11
+ if (mUseSizeSensitiveSystemFont) {
+ NSFont* displaySys = [NSFont systemFontOfSize: 128.0];
+ NSString* displayFamilyName = GetRealFamilyName(displaySys);
+ nsCocoaUtils::GetStringForNSString(displayFamilyName, familyName);
+ mSystemDisplayFontFamilyName = familyName;
+
+#if DEBUG
+ // confirm that the optical size switch is at 20.0
+ NS_ASSERTION([textFamilyName compare:displayFamilyName] != NSOrderedSame,
+ "system text/display fonts are the same!");
+ NSString* fam19 = GetRealFamilyName([NSFont systemFontOfSize:
+ (kTextDisplayCrossover - 1.0)]);
+ NSString* fam20 = GetRealFamilyName([NSFont systemFontOfSize:
+ kTextDisplayCrossover]);
+ NS_ASSERTION(fam19 && fam20 && [fam19 compare:fam20] != NSOrderedSame,
+ "system text/display font size switch point is not as expected!");
+#endif
+ }
+
+#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 nsAString& aFamily)
+{
+ nsAutoString key;
+ GenerateFontListKey(aFamily, key);
+
+ gfxFontFamily* familyEntry;
+
+ // lookup in hidden system family name list
+ if ((familyEntry = mSystemFontFamilies.GetWeak(key))) {
+ return CheckFamily(familyEntry);
+ }
+
+ // lookup in user-exposed family name list
+ if ((familyEntry = mFontFamilies.GetWeak(key))) {
+ return CheckFamily(familyEntry);
+ }
+
+ return nullptr;
+}
+
+bool
+gfxMacPlatformFontList::GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName)
+{
+ gfxFontFamily *family = FindFamily(aFontName);
+ if (family) {
+ family->LocalizedName(aFamilyName);
+ return true;
+ }
+
+ return false;
+}
+
+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);
+
+ // xxx - should be carefully pruning the list of fonts, not rebuilding it from scratch
+ fl->UpdateFontList();
+
+ // modify a preference that will trigger reflow everywhere
+ fl->ForceGlobalReflow();
+}
+
+gfxFontEntry*
+gfxMacPlatformFontList::PlatformGlobalFontFallback(const uint32_t aCh,
+ Script aRunScript,
+ const gfxFontStyle* aMatchStyle,
+ gfxFontFamily** 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;
+ CTFontRef fallback;
+ bool cantUseFallbackFont = false;
+
+ if (!mDefaultFont) {
+ mDefaultFont = ::CTFontCreateWithName(CFSTR("LucidaGrande"), 12.f,
+ NULL);
+ }
+
+ fallback = ::CTFontCreateForString(mDefaultFont, str,
+ ::CFRangeMake(0, length));
+
+ if (fallback) {
+ CFStringRef familyNameRef = ::CTFontCopyFamilyName(fallback);
+ ::CFRelease(fallback);
+
+ if (familyNameRef &&
+ ::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;
+ nsDependentString familyNameString(reinterpret_cast<char16_t*>(buffer.Elements()), familyNameLen);
+
+ bool needsBold; // ignored in the system fallback case
+
+ gfxFontFamily *family = FindFamily(familyNameString);
+ if (family) {
+ fontEntry = family->FindFontForStyle(*aMatchStyle, needsBold);
+ if (fontEntry) {
+ if (fontEntry->HasCharacter(aCh)) {
+ *aMatchedFamily = family;
+ } else {
+ fontEntry = nullptr;
+ cantUseFallbackFont = true;
+ }
+ }
+ }
+ }
+
+ if (familyNameRef) {
+ ::CFRelease(familyNameRef);
+ }
+ }
+
+ if (cantUseFallbackFont) {
+ Telemetry::Accumulate(Telemetry::BAD_FALLBACK_FONT, cantUseFallbackFont);
+ }
+
+ ::CFRelease(str);
+
+ return fontEntry;
+}
+
+gfxFontFamily*
+gfxMacPlatformFontList::GetDefaultFontForPlatform(const gfxFontStyle* aStyle)
+{
+ nsAutoreleasePool localPool;
+
+ NSString *defaultFamily = [[NSFont userFontOfSize:aStyle->size] familyName];
+ nsAutoString familyName;
+
+ GetStringForNSString(defaultFamily, familyName);
+ return FindFamily(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(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle)
+{
+ nsAutoreleasePool localPool;
+
+ NSString *faceName = GetNSStringForString(aFontName);
+ MacOSFontEntry *newFontEntry;
+
+ // lookup face based on postscript or full name
+ CGFontRef fontRef = ::CGFontCreateWithFontName(CFStringRef(faceName));
+ if (!fontRef) {
+ return nullptr;
+ }
+
+ NS_ASSERTION(aWeight >= 100 && aWeight <= 900,
+ "bogus font weight value!");
+
+ newFontEntry =
+ new MacOSFontEntry(aFontName, fontRef, aWeight, aStretch, aStyle,
+ false, true);
+ ::CFRelease(fontRef);
+
+ return newFontEntry;
+}
+
+static void ReleaseData(void *info, const void *data, size_t size)
+{
+ free((void*)data);
+}
+
+gfxFontEntry*
+gfxMacPlatformFontList::MakePlatformFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t* aFontData,
+ uint32_t aLength)
+{
+ NS_ASSERTION(aFontData, "MakePlatformFont called with null data");
+
+ NS_ASSERTION(aWeight >= 100 && aWeight <= 900, "bogus font weight value!");
+
+ // create the font entry
+ nsAutoString uniqueName;
+
+ nsresult rv = gfxFontUtils::MakeUniqueUserFontName(uniqueName);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ CGDataProviderRef provider =
+ ::CGDataProviderCreateWithData(nullptr, aFontData, aLength,
+ &ReleaseData);
+ CGFontRef fontRef = ::CGFontCreateWithDataProvider(provider);
+ ::CGDataProviderRelease(provider);
+
+ if (!fontRef) {
+ return nullptr;
+ }
+
+ auto newFontEntry =
+ MakeUnique<MacOSFontEntry>(uniqueName, fontRef, aWeight, aStretch,
+ aStyle, true, false);
+ ::CFRelease(fontRef);
+
+ // if succeeded and font cmap is good, return the new font
+ if (newFontEntry->mIsValid && NS_SUCCEEDED(newFontEntry->ReadCMAP())) {
+ return newFontEntry.release();
+ }
+
+ // if something is funky about this font, delete immediately
+
+#if DEBUG
+ NS_WARNING("downloaded font not loaded properly");
+#endif
+
+ return nullptr;
+}
+
+// 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::FindAndAddFamilies(const nsAString& aFamily,
+ nsTArray<gfxFontFamily*>* aOutput,
+ gfxFontStyle* aStyle,
+ gfxFloat aDevToCssSize)
+{
+ // search for special system font name, -apple-system
+ if (aFamily.EqualsLiteral(kSystemFont_system)) {
+ if (mUseSizeSensitiveSystemFont &&
+ aStyle && (aStyle->size * aDevToCssSize) >= kTextDisplayCrossover) {
+ aOutput->AppendElement(FindSystemFontFamily(mSystemDisplayFontFamilyName));
+ return true;
+ }
+ aOutput->AppendElement(FindSystemFontFamily(mSystemTextFontFamilyName));
+ return true;
+ }
+
+ return gfxPlatformFontList::FindAndAddFamilies(aFamily, aOutput, aStyle,
+ aDevToCssSize);
+}
+
+void
+gfxMacPlatformFontList::LookupSystemFont(LookAndFeel::FontID aSystemFontID,
+ nsAString& aSystemFontName,
+ gfxFontStyle &aFontStyle,
+ float aDevPixPerCSSPixel)
+{
+ // code moved here from widget/cocoa/nsLookAndFeel.mm
+ NSFont *font = nullptr;
+ char* systemFontName = nullptr;
+ switch (aSystemFontID) {
+ case LookAndFeel::eFont_MessageBox:
+ case LookAndFeel::eFont_StatusBar:
+ case LookAndFeel::eFont_List:
+ case LookAndFeel::eFont_Field:
+ case LookAndFeel::eFont_Button:
+ case LookAndFeel::eFont_Widget:
+ font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
+ systemFontName = (char*) kSystemFont_system;
+ break;
+
+ case LookAndFeel::eFont_SmallCaption:
+ font = [NSFont boldSystemFontOfSize:[NSFont smallSystemFontSize]];
+ systemFontName = (char*) kSystemFont_system;
+ break;
+
+ case LookAndFeel::eFont_Icon: // used in urlbar; tried labelFont, but too small
+ case LookAndFeel::eFont_Workspace:
+ case LookAndFeel::eFont_Desktop:
+ case LookAndFeel::eFont_Info:
+ font = [NSFont controlContentFontOfSize:0.0];
+ systemFontName = (char*) kSystemFont_system;
+ break;
+
+ case LookAndFeel::eFont_PullDownMenu:
+ font = [NSFont menuBarFontOfSize:0.0];
+ systemFontName = (char*) kSystemFont_system;
+ break;
+
+ case LookAndFeel::eFont_Tooltips:
+ font = [NSFont toolTipsFontOfSize:0.0];
+ systemFontName = (char*) kSystemFont_system;
+ break;
+
+ case LookAndFeel::eFont_Caption:
+ case LookAndFeel::eFont_Menu:
+ case LookAndFeel::eFont_Dialog:
+ 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) ? NS_FONT_STYLE_ITALIC : NS_FONT_STYLE_NORMAL;
+ aFontStyle.weight =
+ (traits & NSFontBoldTrait) ? NS_FONT_WEIGHT_BOLD : NS_FONT_WEIGHT_NORMAL;
+ aFontStyle.stretch =
+ (traits & NSFontExpandedTrait) ?
+ NS_FONT_STRETCH_EXPANDED : (traits & NSFontCondensedTrait) ?
+ NS_FONT_STRETCH_CONDENSED : NS_FONT_STRETCH_NORMAL;
+ // convert size from css pixels to device pixels
+ aFontStyle.size = [font pointSize] * aDevPixPerCSSPixel;
+ aFontStyle.systemFont = true;
+}
+
+// used to load system-wide font info on off-main thread
+class MacFontInfo : public FontInfoData {
+public:
+ MacFontInfo(bool aLoadOtherNames,
+ bool aLoadFaceNames,
+ bool aLoadCmaps) :
+ FontInfoData(aLoadOtherNames, aLoadFaceNames, aLoadCmaps)
+ {}
+
+ virtual ~MacFontInfo() {}
+
+ virtual void Load() {
+ nsAutoreleasePool localPool;
+ FontInfoData::Load();
+ }
+
+ // loads font data for all members of a given family
+ virtual void LoadFontFamilyData(const nsAString& aFamilyName);
+};
+
+void
+MacFontInfo::LoadFontFamilyData(const nsAString& aFamilyName)
+{
+ // family name ==> CTFontDescriptor
+ NSString *famName = GetNSStringForString(aFamilyName);
+ CFStringRef family = CFStringRef(famName);
+
+ CFMutableDictionaryRef attr =
+ CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, family);
+ CTFontDescriptorRef fd = CTFontDescriptorCreateWithAttributes(attr);
+ CFRelease(attr);
+ CFArrayRef matchingFonts =
+ CTFontDescriptorCreateMatchingFontDescriptors(fd, NULL);
+ CFRelease(fd);
+ if (!matchingFonts) {
+ return;
+ }
+
+ nsTArray<nsString> 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;
+ }
+ CTFontRef fontRef = CTFontCreateWithFontDescriptor(faceDesc,
+ 0.0, nullptr);
+ if (!fontRef) {
+ NS_WARNING("failed to create a CTFontRef");
+ continue;
+ }
+
+ if (mLoadCmaps) {
+ // face name
+ 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;
+ nsAutoString fontName(reinterpret_cast<char16_t*>(buffer.Elements()),
+ len);
+
+ // load the cmap data
+ FontFaceData fontData;
+ 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;
+ bool unicodeFont = false; // ignored
+ bool symbolFont = false;
+ nsresult rv;
+
+ rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, offset,
+ unicodeFont, symbolFont);
+ if (NS_SUCCEEDED(rv)) {
+ fontData.mCharacterMap = charmap;
+ fontData.mUVSOffset = offset;
+ fontData.mSymbolFont = symbolFont;
+ mLoadStats.cmaps++;
+ }
+ CFRelease(cmapTable);
+ }
+
+ mFontFaceData.Put(fontName, fontData);
+ CFRelease(faceName);
+ }
+
+ if (mLoadOtherNames && hasOtherFamilyNames) {
+ CFDataRef nameTable = CTFontCopyTable(fontRef, kCTFontTableName,
+ kCTFontTableOptionNoOptions);
+
+ if (nameTable) {
+ const char *nameData = (const char*)CFDataGetBytePtr(nameTable);
+ uint32_t nameLen = CFDataGetLength(nameTable);
+ gfxFontFamily::ReadOtherFamilyNamesForFace(aFamilyName,
+ nameData, nameLen,
+ otherFamilyNames,
+ false);
+ hasOtherFamilyNames = otherFamilyNames.Length() != 0;
+ CFRelease(nameTable);
+ }
+ }
+
+ CFRelease(fontRef);
+ }
+ CFRelease(matchingFonts);
+
+ // if found other names, insert them in the hash table
+ if (otherFamilyNames.Length() != 0) {
+ mOtherFamilyNames.Put(aFamilyName, otherFamilyNames);
+ mLoadStats.othernames += otherFamilyNames.Length();
+ }
+}
+
+already_AddRefed<FontInfoData>
+gfxMacPlatformFontList::CreateFontInfoData()
+{
+ bool loadCmaps = !UsesSystemFallback() ||
+ gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback();
+
+ RefPtr<MacFontInfo> fi =
+ new MacFontInfo(true, NeedFullnamePostscriptNames(), loadCmaps);
+ return fi.forget();
+}
+
+#ifdef MOZ_BUNDLED_FONTS
+
+void
+gfxMacPlatformFontList::ActivateBundledFonts()
+{
+ nsCOMPtr<nsIFile> localDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ if (NS_FAILED(localDir->Append(NS_LITERAL_STRING("fonts")))) {
+ return;
+ }
+ bool isDir;
+ if (NS_FAILED(localDir->IsDirectory(&isDir)) || !isDir) {
+ return;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> e;
+ rv = localDir->GetDirectoryEntries(getter_AddRefs(e));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ bool hasMore;
+ while (NS_SUCCEEDED(e->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> entry;
+ if (NS_FAILED(e->GetNext(getter_AddRefs(entry)))) {
+ break;
+ }
+ nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
+ if (!file) {
+ continue;
+ }
+ nsCString path;
+ if (NS_FAILED(file->GetNativePath(path))) {
+ continue;
+ }
+ CFURLRef fontURL =
+ ::CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
+ (uint8_t*)path.get(),
+ path.Length(),
+ false);
+ if (fontURL) {
+ CFErrorRef error = nullptr;
+ ::CTFontManagerRegisterFontsForURL(fontURL,
+ kCTFontManagerScopeProcess,
+ &error);
+ ::CFRelease(fontURL);
+ }
+ }
+}
+
+#endif
diff --git a/gfx/thebes/gfxMathTable.cpp b/gfx/thebes/gfxMathTable.cpp
new file mode 100644
index 000000000..f7047c747
--- /dev/null
+++ b/gfx/thebes/gfxMathTable.cpp
@@ -0,0 +1,210 @@
+/* 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 FixedToFloat(f) ((f) * (1.0 / 65536.0))
+
+using namespace mozilla;
+
+gfxMathTable::gfxMathTable(hb_face_t *aFace, gfxFloat aSize)
+{
+ 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 000000000..bb22ff8df
--- /dev/null
+++ b/gfx/thebes/gfxMathTable.h
@@ -0,0 +1,155 @@
+/* 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.cpp b/gfx/thebes/gfxMatrix.cpp
new file mode 100644
index 000000000..8fcce4ce9
--- /dev/null
+++ b/gfx/thebes/gfxMatrix.cpp
@@ -0,0 +1,189 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxMatrix.h"
+#include "cairo.h"
+#include "mozilla/gfx/Tools.h"
+#include "mozilla/gfx/Matrix.h" // for Matrix4x4
+
+#define CAIRO_MATRIX(x) reinterpret_cast<cairo_matrix_t*>((x))
+#define CONST_CAIRO_MATRIX(x) reinterpret_cast<const cairo_matrix_t*>((x))
+
+const gfxMatrix&
+gfxMatrix::Reset()
+{
+ cairo_matrix_init_identity(CAIRO_MATRIX(this));
+ return *this;
+}
+
+bool
+gfxMatrix::Invert()
+{
+ return cairo_matrix_invert(CAIRO_MATRIX(this)) == CAIRO_STATUS_SUCCESS;
+}
+
+gfxMatrix&
+gfxMatrix::Scale(gfxFloat x, gfxFloat y)
+{
+ cairo_matrix_scale(CAIRO_MATRIX(this), x, y);
+ return *this;
+}
+
+gfxMatrix&
+gfxMatrix::Translate(const gfxPoint& pt)
+{
+ cairo_matrix_translate(CAIRO_MATRIX(this), pt.x, pt.y);
+ return *this;
+}
+
+gfxMatrix&
+gfxMatrix::Rotate(gfxFloat radians)
+{
+ cairo_matrix_rotate(CAIRO_MATRIX(this), radians);
+ return *this;
+}
+
+const gfxMatrix&
+gfxMatrix::operator *= (const gfxMatrix& m)
+{
+ cairo_matrix_multiply(CAIRO_MATRIX(this), CAIRO_MATRIX(this), CONST_CAIRO_MATRIX(&m));
+ return *this;
+}
+
+gfxMatrix&
+gfxMatrix::PreMultiply(const gfxMatrix& m)
+{
+ cairo_matrix_multiply(CAIRO_MATRIX(this), CONST_CAIRO_MATRIX(&m), CAIRO_MATRIX(this));
+ return *this;
+}
+
+/* static */ gfxMatrix
+gfxMatrix::Rotation(gfxFloat aAngle)
+{
+ gfxMatrix newMatrix;
+
+ gfxFloat s = sin(aAngle);
+ gfxFloat c = cos(aAngle);
+
+ newMatrix._11 = c;
+ newMatrix._12 = s;
+ newMatrix._21 = -s;
+ newMatrix._22 = c;
+
+ return newMatrix;
+}
+
+gfxPoint
+gfxMatrix::Transform(const gfxPoint& point) const
+{
+ gfxPoint ret = point;
+ cairo_matrix_transform_point(CONST_CAIRO_MATRIX(this), &ret.x, &ret.y);
+ return ret;
+}
+
+gfxSize
+gfxMatrix::Transform(const gfxSize& size) const
+{
+ gfxSize ret = size;
+ cairo_matrix_transform_distance(CONST_CAIRO_MATRIX(this), &ret.width, &ret.height);
+ return ret;
+}
+
+gfxRect
+gfxMatrix::Transform(const gfxRect& rect) const
+{
+ return gfxRect(Transform(rect.TopLeft()), Transform(rect.Size()));
+}
+
+gfxRect
+gfxMatrix::TransformBounds(const gfxRect& rect) const
+{
+ /* Code taken from cairo-matrix.c, _cairo_matrix_transform_bounding_box isn't public */
+ int i;
+ double quad_x[4], quad_y[4];
+ double min_x, max_x;
+ double min_y, max_y;
+
+ quad_x[0] = rect.X();
+ quad_y[0] = rect.Y();
+ cairo_matrix_transform_point (CONST_CAIRO_MATRIX(this), &quad_x[0], &quad_y[0]);
+
+ quad_x[1] = rect.XMost();
+ quad_y[1] = rect.Y();
+ cairo_matrix_transform_point (CONST_CAIRO_MATRIX(this), &quad_x[1], &quad_y[1]);
+
+ quad_x[2] = rect.X();
+ quad_y[2] = rect.YMost();
+ cairo_matrix_transform_point (CONST_CAIRO_MATRIX(this), &quad_x[2], &quad_y[2]);
+
+ quad_x[3] = rect.XMost();
+ quad_y[3] = rect.YMost();
+ cairo_matrix_transform_point (CONST_CAIRO_MATRIX(this), &quad_x[3], &quad_y[3]);
+
+ min_x = max_x = quad_x[0];
+ min_y = max_y = quad_y[0];
+
+ for (i = 1; i < 4; i++) {
+ if (quad_x[i] < min_x)
+ min_x = quad_x[i];
+ if (quad_x[i] > max_x)
+ max_x = quad_x[i];
+
+ if (quad_y[i] < min_y)
+ min_y = quad_y[i];
+ if (quad_y[i] > max_y)
+ max_y = quad_y[i];
+ }
+
+ return gfxRect(min_x, min_y, max_x - min_x, max_y - min_y);
+}
+
+
+static void NudgeToInteger(double *aVal)
+{
+ float f = float(*aVal);
+ mozilla::gfx::NudgeToInteger(&f);
+ *aVal = f;
+}
+
+gfxMatrix&
+gfxMatrix::NudgeToIntegers(void)
+{
+ NudgeToInteger(&_11);
+ NudgeToInteger(&_21);
+ NudgeToInteger(&_12);
+ NudgeToInteger(&_22);
+ NudgeToInteger(&_31);
+ NudgeToInteger(&_32);
+ return *this;
+}
+
+mozilla::gfx::Matrix4x4
+gfxMatrix::operator *(const mozilla::gfx::Matrix4x4& aMatrix) const
+{
+ Matrix4x4 resultMatrix;
+
+ resultMatrix._11 = _11 * aMatrix._11 + _12 * aMatrix._21;
+ resultMatrix._12 = _11 * aMatrix._12 + _12 * aMatrix._22;
+ resultMatrix._13 = _11 * aMatrix._13 + _12 * aMatrix._23;
+ resultMatrix._14 = _11 * aMatrix._14 + _12 * aMatrix._24;
+
+ resultMatrix._21 = _21 * aMatrix._11 + _22 * aMatrix._21;
+ resultMatrix._22 = _21 * aMatrix._12 + _22 * aMatrix._22;
+ resultMatrix._23 = _21 * aMatrix._13 + _22 * aMatrix._23;
+ resultMatrix._24 = _21 * aMatrix._14 + _22 * aMatrix._24;
+
+ resultMatrix._31 = aMatrix._31;
+ resultMatrix._32 = aMatrix._32;
+ resultMatrix._33 = aMatrix._33;
+ resultMatrix._34 = aMatrix._34;
+
+ resultMatrix._41 = _31 * aMatrix._11 + _32 * aMatrix._21 + aMatrix._41;
+ resultMatrix._42 = _31 * aMatrix._12 + _32 * aMatrix._22 + aMatrix._42;
+ resultMatrix._43 = _31 * aMatrix._13 + _32 * aMatrix._23 + aMatrix._43;
+ resultMatrix._44 = _31 * aMatrix._14 + _32 * aMatrix._24 + aMatrix._44;
+
+ return resultMatrix;
+}
diff --git a/gfx/thebes/gfxMatrix.h b/gfx/thebes/gfxMatrix.h
new file mode 100644
index 000000000..9282a22db
--- /dev/null
+++ b/gfx/thebes/gfxMatrix.h
@@ -0,0 +1,310 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxPoint.h"
+#include "gfxTypes.h"
+#include "gfxRect.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/gfx/MatrixFwd.h"
+
+// XX - I don't think this class should use gfxFloat at all,
+// but should use 'double' and be called gfxDoubleMatrix;
+// we can then typedef that to gfxMatrix where we typedef
+// double to be gfxFloat.
+
+/**
+ * A matrix that represents an affine transformation. Projective
+ * transformations are not supported. This matrix looks like:
+ *
+ * / a b 0 \
+ * | c d 0 |
+ * \ tx ty 1 /
+ *
+ * So, transforming a point (x, y) results in:
+ *
+ * / a b 0 \ / a * x + c * y + tx \ T
+ * (x y 1) * | c d 0 | = | b * x + d * y + ty |
+ * \ tx ty 1 / \ 1 /
+ *
+ */
+class gfxMatrix {
+public:
+ double _11; double _12;
+ double _21; double _22;
+ double _31; double _32;
+
+ /**
+ * Initializes this matrix as the identity matrix.
+ */
+ gfxMatrix() { Reset(); }
+
+ /**
+ * Initializes the matrix from individual components. See the class
+ * description for the layout of the matrix.
+ */
+ gfxMatrix(gfxFloat a, gfxFloat b, gfxFloat c, gfxFloat d, gfxFloat tx, gfxFloat ty) :
+ _11(a), _12(b),
+ _21(c), _22(d),
+ _31(tx), _32(ty) { }
+
+ MOZ_ALWAYS_INLINE gfxMatrix Copy() const {
+ return gfxMatrix(*this);
+ }
+
+ friend std::ostream& operator<<(std::ostream& stream, const gfxMatrix& m) {
+ if (m.IsIdentity()) {
+ return stream << "[identity]";
+ }
+
+ return stream << "["
+ << m._11 << " " << m._12
+ << m._21 << " " << m._22
+ << m._31 << " " << m._32
+ << "]";
+ }
+
+ /**
+ * Post-multiplies m onto the matrix.
+ */
+ const gfxMatrix& operator *= (const gfxMatrix& m);
+
+ /**
+ * Multiplies *this with m and returns the result.
+ */
+ gfxMatrix operator * (const gfxMatrix& m) const {
+ return gfxMatrix(*this) *= m;
+ }
+
+ /**
+ * Multiplies *this with aMatrix and returns the result.
+ */
+ mozilla::gfx::Matrix4x4 operator * (const mozilla::gfx::Matrix4x4& aMatrix) const;
+
+ /* Returns true if the other matrix is fuzzy-equal to this matrix.
+ * Note that this isn't a cheap comparison!
+ */
+ bool operator==(const gfxMatrix& other) const
+ {
+ return FuzzyEqual(_11, other._11) && FuzzyEqual(_12, other._12) &&
+ FuzzyEqual(_21, other._21) && FuzzyEqual(_22, other._22) &&
+ FuzzyEqual(_31, other._31) && FuzzyEqual(_32, other._32);
+ }
+
+ bool operator!=(const gfxMatrix& other) const
+ {
+ return !(*this == other);
+ }
+
+ // matrix operations
+ /**
+ * Resets this matrix to the identity matrix.
+ */
+ const gfxMatrix& Reset();
+
+ bool IsIdentity() const {
+ return _11 == 1.0 && _12 == 0.0 &&
+ _21 == 0.0 && _22 == 1.0 &&
+ _31 == 0.0 && _32 == 0.0;
+ }
+
+ /**
+ * Inverts this matrix, if possible. Otherwise, the matrix is left
+ * unchanged.
+ *
+ * XXX should this do something with the return value of
+ * cairo_matrix_invert?
+ */
+ bool Invert();
+
+ /**
+ * Check if matrix is singular (no inverse exists).
+ */
+ bool IsSingular() const {
+ // if the determinant (ad - bc) is zero it's singular
+ return (_11 * _22) == (_12 * _21);
+ }
+
+ /**
+ * Scales this matrix. The scale is pre-multiplied onto this matrix,
+ * i.e. the scaling takes place before the other transformations.
+ */
+ gfxMatrix& Scale(gfxFloat x, gfxFloat y);
+
+ /**
+ * Translates this matrix. The translation is pre-multiplied onto this matrix,
+ * i.e. the translation takes place before the other transformations.
+ */
+ gfxMatrix& Translate(const gfxPoint& pt);
+
+ gfxMatrix& Translate(gfxFloat x, gfxFloat y) {
+ return Translate(gfxPoint(x, y));
+ }
+
+ /**
+ * Rotates this matrix. The rotation is pre-multiplied onto this matrix,
+ * i.e. the translation takes place after the other transformations.
+ *
+ * @param radians Angle in radians.
+ */
+ gfxMatrix& Rotate(gfxFloat radians);
+
+ /**
+ * Multiplies the current matrix with m.
+ * This is a pre-multiplication, i.e. the transformations of m are
+ * applied _before_ the existing transformations.
+ */
+ gfxMatrix& PreMultiply(const gfxMatrix& m);
+
+ static gfxMatrix Translation(gfxFloat aX, gfxFloat aY)
+ {
+ return gfxMatrix(1.0, 0.0, 0.0, 1.0, aX, aY);
+ }
+
+ static gfxMatrix Translation(gfxPoint aPoint)
+ {
+ return Translation(aPoint.x, aPoint.y);
+ }
+
+ static gfxMatrix Rotation(gfxFloat aAngle);
+
+ static gfxMatrix Scaling(gfxFloat aX, gfxFloat aY)
+ {
+ return gfxMatrix(aX, 0.0, 0.0, aY, 0.0, 0.0);
+ }
+
+ /**
+ * Transforms a point according to this matrix.
+ */
+ gfxPoint Transform(const gfxPoint& point) const;
+
+
+ /**
+ * Transform a distance according to this matrix. This does not apply
+ * any translation components.
+ */
+ gfxSize Transform(const gfxSize& size) const;
+
+ /**
+ * Transforms both the point and distance according to this matrix.
+ */
+ gfxRect Transform(const gfxRect& rect) const;
+
+ gfxRect TransformBounds(const gfxRect& rect) const;
+
+ /**
+ * Returns the translation component of this matrix.
+ */
+ gfxPoint GetTranslation() const {
+ return gfxPoint(_31, _32);
+ }
+
+ /**
+ * Returns true if the matrix is anything other than a straight
+ * translation by integers.
+ */
+ bool HasNonIntegerTranslation() const {
+ return HasNonTranslation() ||
+ !FuzzyEqual(_31, floor(_31 + 0.5)) ||
+ !FuzzyEqual(_32, floor(_32 + 0.5));
+ }
+
+ /**
+ * Returns true if the matrix has any transform other
+ * than a straight translation
+ */
+ bool HasNonTranslation() const {
+ return !FuzzyEqual(_11, 1.0) || !FuzzyEqual(_22, 1.0) ||
+ !FuzzyEqual(_21, 0.0) || !FuzzyEqual(_12, 0.0);
+ }
+
+ /**
+ * Returns true if the matrix only has an integer translation.
+ */
+ bool HasOnlyIntegerTranslation() const {
+ return !HasNonIntegerTranslation();
+ }
+
+ /**
+ * Returns true if the matrix has any transform other
+ * than a translation or a -1 y scale (y axis flip)
+ */
+ bool HasNonTranslationOrFlip() const {
+ return !FuzzyEqual(_11, 1.0) ||
+ (!FuzzyEqual(_22, 1.0) && !FuzzyEqual(_22, -1.0)) ||
+ !FuzzyEqual(_21, 0.0) || !FuzzyEqual(_12, 0.0);
+ }
+
+ /**
+ * Returns true if the matrix has any transform other
+ * than a translation or scale; this is, if there is
+ * no rotation.
+ */
+ bool HasNonAxisAlignedTransform() const {
+ return !FuzzyEqual(_21, 0.0) || !FuzzyEqual(_12, 0.0);
+ }
+
+ /**
+ * Computes the determinant of this matrix.
+ */
+ double Determinant() const {
+ return _11*_22 - _12*_21;
+ }
+
+ /* Computes the scale factors of this matrix; that is,
+ * the amounts each basis vector is scaled by.
+ * The xMajor parameter indicates if the larger scale is
+ * to be assumed to be in the X direction or not.
+ */
+ gfxSize ScaleFactors(bool xMajor) const {
+ double det = Determinant();
+
+ if (det == 0.0)
+ return gfxSize(0.0, 0.0);
+
+ gfxSize sz = xMajor ? gfxSize(1.0, 0.0) : gfxSize(0.0, 1.0);
+ sz = Transform(sz);
+
+ double major = sqrt(sz.width * sz.width + sz.height * sz.height);
+ double minor = 0.0;
+
+ // ignore mirroring
+ if (det < 0.0)
+ det = - det;
+
+ if (major)
+ minor = det / major;
+
+ if (xMajor)
+ return gfxSize(major, minor);
+
+ return gfxSize(minor, major);
+ }
+
+ /**
+ * Snap matrix components that are close to integers
+ * to integers. In particular, components that are integral when
+ * converted to single precision are set to those integers.
+ */
+ gfxMatrix& NudgeToIntegers(void);
+
+ /**
+ * Returns true if matrix is multiple of 90 degrees rotation with flipping,
+ * scaling and translation.
+ */
+ bool PreservesAxisAlignedRectangles() const {
+ return ((FuzzyEqual(_11, 0.0) && FuzzyEqual(_22, 0.0))
+ || (FuzzyEqual(_21, 0.0) && FuzzyEqual(_12, 0.0)));
+ }
+
+private:
+ static bool FuzzyEqual(gfxFloat aV1, gfxFloat aV2) {
+ return fabs(aV2 - aV1) < 1e-6;
+ }
+};
+
+#endif /* GFX_MATRIX_H */
diff --git a/gfx/thebes/gfxPattern.cpp b/gfx/thebes/gfxPattern.cpp
new file mode 100644
index 000000000..d937b992f
--- /dev/null
+++ b/gfx/thebes/gfxPattern.cpp
@@ -0,0 +1,218 @@
+/* -*- 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 "gfxASurface.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 Color& aColor)
+ : mExtend(ExtendMode::CLAMP)
+{
+ mGfxPattern.InitColorPattern(ToDeviceColor(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);
+}
+
+// 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 Color& c)
+{
+ if (mGfxPattern.GetPattern()->GetType() != PatternType::LINEAR_GRADIENT &&
+ mGfxPattern.GetPattern()->GetType() != PatternType::RADIAL_GRADIENT) {
+ return;
+ }
+
+ mStops = nullptr;
+
+ GradientStop stop;
+ stop.offset = offset;
+ stop.color = ToDeviceColor(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,
+ Matrix *aOriginalUserToDevice)
+{
+ Matrix patternToUser = mPatternToUserSpace;
+
+ if (aOriginalUserToDevice &&
+ *aOriginalUserToDevice != 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;
+ }
+ 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(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 gfx::SamplingFilter::GOOD;
+ }
+ return static_cast<const SurfacePattern*>(mGfxPattern.GetPattern())->mSamplingFilter;
+}
+
+bool
+gfxPattern::GetSolidColor(Color& aColorOut)
+{
+ if (mGfxPattern.GetPattern()->GetType() == PatternType::COLOR) {
+ aColorOut = static_cast<ColorPattern*>(mGfxPattern.GetPattern())->mColor;
+ return true;
+ }
+
+ return false;
+}
+
+int
+gfxPattern::CairoStatus()
+{
+ return CAIRO_STATUS_SUCCESS;
+}
diff --git a/gfx/thebes/gfxPattern.h b/gfx/thebes/gfxPattern.h
new file mode 100644
index 000000000..a16d1ad39
--- /dev/null
+++ b/gfx/thebes/gfxPattern.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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::Color& aColor);
+ // linear
+ 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(mozilla::gfx::SourceSurface *aSurface,
+ const mozilla::gfx::Matrix &aPatternToUserSpace);
+
+ void AddColorStop(gfxFloat offset, const mozilla::gfx::Color& 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,
+ mozilla::gfx::Matrix *aOriginalUserToDevice = nullptr);
+ bool IsOpaque();
+
+ // clamp, repeat, reflect
+ void SetExtend(mozilla::gfx::ExtendMode aExtend);
+
+ int CairoStatus();
+
+ void SetSamplingFilter(mozilla::gfx::SamplingFilter aSamplingFilter);
+ mozilla::gfx::SamplingFilter SamplingFilter() const;
+
+ /* returns TRUE if it succeeded */
+ bool GetSolidColor(mozilla::gfx::Color& aColorOut);
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~gfxPattern() {}
+
+ 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 000000000..2e4ec990f
--- /dev/null
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -0,0 +1,2599 @@
+/* -*- 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/layers/CompositorBridgeChild.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/ImageBridgeChild.h"
+#include "mozilla/layers/ISurfaceAllocator.h" // for GfxMemoryImageReporter
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "mozilla/gfx/GraphicsMessages.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Unused.h"
+
+#include "mozilla/Logging.h"
+#include "mozilla/Services.h"
+
+#include "gfxCrashReporterUtils.h"
+#include "gfxPlatform.h"
+#include "gfxPrefs.h"
+#include "gfxEnv.h"
+#include "gfxTextRun.h"
+#include "gfxConfig.h"
+#include "MediaPrefs.h"
+
+#ifdef XP_WIN
+#include <process.h>
+#define getpid _getpid
+#else
+#include <unistd.h>
+#endif
+
+#include "nsXULAppAPI.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+
+#if defined(XP_WIN)
+#include "gfxWindowsPlatform.h"
+#elif defined(XP_MACOSX)
+#include "gfxPlatformMac.h"
+#include "gfxQuartzSurface.h"
+#elif defined(MOZ_WIDGET_GTK)
+#include "gfxPlatformGtk.h"
+#elif defined(ANDROID)
+#include "gfxAndroidPlatform.h"
+#endif
+
+#ifdef XP_WIN
+#include "mozilla/WindowsVersion.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 "nsUnicodeRange.h"
+#include "nsServiceManagerUtils.h"
+#include "nsTArray.h"
+#include "nsILocaleService.h"
+#include "nsIObserverService.h"
+#include "nsIScreenManager.h"
+#include "FrameMetrics.h"
+#include "MainThreadUtils.h"
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#endif
+
+#include "nsWeakReference.h"
+
+#include "cairo.h"
+#include "qcms.h"
+
+#include "imgITools.h"
+
+#include "plstr.h"
+#include "nsCRT.h"
+#include "GLContext.h"
+#include "GLContextProvider.h"
+#include "mozilla/gfx/Logging.h"
+
+#if defined(MOZ_WIDGET_GTK)
+#include "gfxPlatformGtk.h" // xxx - for UseFcFontList
+#endif
+
+#ifdef MOZ_WIDGET_ANDROID
+#include "TexturePoolOGL.h"
+#endif
+
+#ifdef USE_SKIA
+# ifdef __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wshadow"
+# endif
+# include "skia/include/core/SkGraphics.h"
+# ifdef USE_SKIA_GPU
+# include "skia/include/gpu/GrContext.h"
+# include "skia/include/gpu/gl/GrGLInterface.h"
+# include "SkiaGLGlue.h"
+# endif
+# ifdef MOZ_ENABLE_FREETYPE
+# include "skia/include/ports/SkTypeface_cairo.h"
+# endif
+# ifdef __GNUC__
+# pragma GCC diagnostic pop // -Wshadow
+# endif
+static const uint32_t kDefaultGlyphCacheSize = -1;
+
+#endif
+
+#if !defined(USE_SKIA) || !defined(USE_SKIA_GPU)
+class mozilla::gl::SkiaGLGlue : public GenericAtomicRefCounted {
+};
+#endif
+
+#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 "gfxVR.h"
+#include "VRManagerChild.h"
+#include "mozilla/gfx/GPUParent.h"
+#include "prsystem.h"
+
+namespace mozilla {
+namespace layers {
+void ShutdownTileCache();
+} // namespace layers
+} // namespace mozilla
+
+using namespace mozilla;
+using namespace mozilla::layers;
+using namespace mozilla::gl;
+using namespace mozilla::gfx;
+
+gfxPlatform *gPlatform = nullptr;
+static bool gEverInitialized = false;
+
+static Mutex* gGfxPlatformPrefsLock = nullptr;
+
+// These two may point to the same profile
+static qcms_profile *gCMSOutputProfile = nullptr;
+static qcms_profile *gCMSsRGBProfile = nullptr;
+
+static qcms_transform *gCMSRGBTransform = nullptr;
+static qcms_transform *gCMSInverseRGBTransform = nullptr;
+static qcms_transform *gCMSRGBATransform = nullptr;
+
+static bool gCMSInitialized = false;
+static eCMSMode gCMSMode = eCMSMode_Off;
+
+static void ShutdownCMS();
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/SourceSurfaceCairo.h"
+using namespace mozilla::gfx;
+
+/* Class to listen for pref changes so that chrome code can dynamically
+ force sRGB as an output profile. See Bug #452125. */
+class SRGBOverrideObserver final : public nsIObserver,
+ public nsSupportsWeakReference
+{
+ ~SRGBOverrideObserver() {}
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+};
+
+/// 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(const char* aKey);
+ virtual void Log(const std::string& aString) override;
+ virtual void CrashAction(LogReason aReason) override;
+ virtual bool UpdateStringsVector(const std::string& aString) override;
+
+ virtual LoggingRecord LoggingRecordCopy() override;
+
+ void SetCircularBufferSize(uint32_t aCapacity);
+
+private:
+ // Helper for the Log()
+ void UpdateCrashReport();
+
+private:
+ LoggingRecord mBuffer;
+ nsCString mCrashCriticalKey;
+ uint32_t mMaxCapacity;
+ int32_t mIndex;
+ Mutex mMutex;
+};
+
+CrashStatsLogForwarder::CrashStatsLogForwarder(const char* 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());
+
+ bool ignored;
+ double tStamp = (TimeStamp::NowLoRes()-TimeStamp::ProcessCreation(ignored)).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 (LoggingRecord::iterator it = mBuffer.begin(); it != mBuffer.end(); ++it) {
+ message << logAnnotation << Get<0>(*it) << "]" << Get<1>(*it) << " (t=" << Get<2>(*it) << ") ";
+ }
+
+#ifdef MOZ_CRASHREPORTER
+ nsCString reportString(message.str().c_str());
+ nsresult annotated = CrashReporter::AnnotateCrashReport(mCrashCriticalKey, reportString);
+#else
+ nsresult annotated = NS_ERROR_NOT_IMPLEMENTED;
+#endif
+ if (annotated != NS_OK) {
+ printf("Crash Annotation %s: %s",
+ mCrashCriticalKey.get(), message.str().c_str());
+ }
+}
+
+class LogForwarderEvent : public Runnable
+{
+ virtual ~LogForwarderEvent() {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ explicit LogForwarderEvent(const nsCString& aMessage) : 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;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED0(LogForwarderEvent, Runnable);
+
+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() {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ explicit CrashTelemetryEvent(uint32_t aReason) : mReason(aReason) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ Telemetry::Accumulate(Telemetry::GFX_CRASH, mReason);
+ return NS_OK;
+ }
+
+protected:
+ uint32_t mReason;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED0(CrashTelemetryEvent, Runnable);
+
+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::GfxDevCrashTelemetry();
+#else
+ // Release builds use telemetry by default, but will crash instead
+ // if this environment variable is present.
+ static bool useTelemetry = !gfxEnv::GfxDevCrashMozCrash();
+#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");
+ }
+}
+
+NS_IMPL_ISUPPORTS(SRGBOverrideObserver, nsIObserver, nsISupportsWeakReference)
+
+#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"
+
+#define BIDI_NUMERAL_PREF "bidi.numeral"
+
+#define GFX_PREF_CMS_FORCE_SRGB "gfx.color_management.force_srgb"
+
+NS_IMETHODIMP
+SRGBOverrideObserver::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t* someData)
+{
+ NS_ASSERTION(NS_strcmp(someData,
+ (u"" GFX_PREF_CMS_FORCE_SRGB)) == 0,
+ "Restarting CMS on wrong pref!");
+ ShutdownCMS();
+ // Update current cms profile.
+ gfxPlatform::CreateCMSOutputProfile();
+ return NS_OK;
+}
+
+static const char* kObservedPrefs[] = {
+ "gfx.downloadable_fonts.",
+ "gfx.font_rendering.",
+ BIDI_NUMERAL_PREF,
+ nullptr
+};
+
+class FontPrefsObserver final : public nsIObserver
+{
+ ~FontPrefsObserver() {}
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+};
+
+NS_IMPL_ISUPPORTS(FontPrefsObserver, nsIObserver)
+
+NS_IMETHODIMP
+FontPrefsObserver::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *someData)
+{
+ if (!someData) {
+ NS_ERROR("font pref observer code broken");
+ return NS_ERROR_UNEXPECTED;
+ }
+ NS_ASSERTION(gfxPlatform::GetPlatform(), "the singleton instance has gone");
+ gfxPlatform::GetPlatform()->FontsPrefsChanged(NS_ConvertUTF16toUTF8(someData).get());
+
+ return NS_OK;
+}
+
+class MemoryPressureObserver final : public nsIObserver
+{
+ ~MemoryPressureObserver() {}
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+};
+
+NS_IMPL_ISUPPORTS(MemoryPressureObserver, nsIObserver)
+
+NS_IMETHODIMP
+MemoryPressureObserver::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *someData)
+{
+ NS_ASSERTION(strcmp(aTopic, "memory-pressure") == 0, "unexpected event topic");
+ Factory::PurgeAllCaches();
+ gfxGradientCache::PurgeAllCaches();
+
+ gfxPlatform::PurgeSkiaFontCache();
+ gfxPlatform::GetPlatform()->PurgeSkiaGPUCache();
+ return NS_OK;
+}
+
+gfxPlatform::gfxPlatform()
+ : mAzureCanvasBackendCollector(this, &gfxPlatform::GetAzureBackendInfo)
+ , mApzSupportCollector(this, &gfxPlatform::GetApzSupportInfo)
+ , mTilesInfoCollector(this, &gfxPlatform::GetTilesSupportInfo)
+ , mCompositorBackend(layers::LayersBackend::LAYERS_NONE)
+ , mScreenDepth(0)
+ , mDeviceCounter(0)
+{
+ mAllowDownloadableFonts = UNINITIALIZED_VALUE;
+ mFallbackUsesCmaps = UNINITIALIZED_VALUE;
+
+ mWordCacheCharLimit = UNINITIALIZED_VALUE;
+ mWordCacheMaxEntries = UNINITIALIZED_VALUE;
+ mGraphiteShapingEnabled = UNINITIALIZED_VALUE;
+ mOpenTypeSVGEnabled = UNINITIALIZED_VALUE;
+ mBidiNumeralOption = UNINITIALIZED_VALUE;
+
+ mSkiaGlue = nullptr;
+
+ uint32_t canvasMask = BackendTypeBit(BackendType::CAIRO);
+ uint32_t contentMask = BackendTypeBit(BackendType::CAIRO);
+#ifdef USE_SKIA
+ canvasMask |= BackendTypeBit(BackendType::SKIA);
+ contentMask |= BackendTypeBit(BackendType::SKIA);
+#endif
+ InitBackendPrefs(canvasMask, BackendType::CAIRO,
+ contentMask, BackendType::CAIRO);
+
+ mTotalSystemMemory = PR_GetPhysicalMemorySize();
+
+ VRManager::ManagerInit();
+}
+
+gfxPlatform*
+gfxPlatform::GetPlatform()
+{
+ if (!gPlatform) {
+ Init();
+ }
+ return gPlatform;
+}
+
+bool
+gfxPlatform::Initialized()
+{
+ return !!gPlatform;
+}
+
+void RecordingPrefChanged(const char *aPrefName, void *aClosure)
+{
+ if (Preferences::GetBool("gfx.2d.recording", false)) {
+ nsAutoCString fileName;
+ nsAdoptingString prefFileName = Preferences::GetString("gfx.2d.recordingfile");
+
+ if (prefFileName) {
+ fileName.Append(NS_ConvertUTF16toUTF8(prefFileName));
+ } else {
+ nsCOMPtr<nsIFile> tmpFile;
+ if (NS_FAILED(NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile)))) {
+ return;
+ }
+ fileName.AppendPrintf("moz2drec_%i_%i.aer", XRE_GetProcessType(), getpid());
+
+ nsresult rv = tmpFile->AppendNative(fileName);
+ if (NS_FAILED(rv))
+ return;
+
+ rv = tmpFile->GetNativePath(fileName);
+ if (NS_FAILED(rv))
+ return;
+ }
+
+ gPlatform->mRecorder = Factory::CreateEventRecorderForFile(fileName.BeginReading());
+ printf_stderr("Recording to %s\n", fileName.get());
+ Factory::SetGlobalEventRecorder(gPlatform->mRecorder);
+ } else {
+ Factory::SetGlobalEventRecorder(nullptr);
+ }
+}
+
+#if defined(USE_SKIA)
+static uint32_t GetSkiaGlyphCacheSize()
+{
+ // Only increase font cache size on non-android to save memory.
+#if !defined(MOZ_WIDGET_ANDROID)
+ // 10mb as the default 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 proces
+ uint32_t cacheSize = 10 * 1024 * 1024;
+ if (mozilla::BrowserTabsRemoteAutostart()) {
+ return XRE_IsContentProcess() ? cacheSize : kDefaultGlyphCacheSize;
+ }
+
+ return cacheSize;
+#else
+ return kDefaultGlyphCacheSize;
+#endif // MOZ_WIDGET_ANDROID
+}
+#endif
+
+void
+gfxPlatform::Init()
+{
+ MOZ_RELEASE_ASSERT(!XRE_IsGPUProcess(), "GFX: Not allowed in GPU process.");
+ MOZ_RELEASE_ASSERT(NS_IsMainThread(), "GFX: Not in main thread.");
+
+ if (gEverInitialized) {
+ NS_RUNTIMEABORT("Already started???");
+ }
+ gEverInitialized = true;
+
+ // Initialize the preferences by creating the singleton.
+ gfxPrefs::GetSingleton();
+ MediaPrefs::GetSingleton();
+ gfxVars::Initialize();
+
+ gfxConfig::Init();
+
+ if (XRE_IsParentProcess()) {
+ GPUProcessManager::Initialize();
+
+ if (Preferences::GetBool("media.wmf.skip-blacklist")) {
+ gfxVars::SetPDMWMFDisableD3D11Dlls(nsCString());
+ gfxVars::SetPDMWMFDisableD3D9Dlls(nsCString());
+ } else {
+ gfxVars::SetPDMWMFDisableD3D11Dlls(Preferences::GetCString("media.wmf.disable-d3d11-for-dlls"));
+ gfxVars::SetPDMWMFDisableD3D9Dlls(Preferences::GetCString("media.wmf.disable-d3d9-for-dlls"));
+ }
+ }
+
+ // 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",
+ gfxPrefs::Direct2DDisabled(),
+ gfxPrefs::Direct2DForceEnabled());
+ // Layers prefs
+ forcedPrefs.AppendPrintf("-L%d%d%d%d",
+ gfxPrefs::LayersAMDSwitchableGfxEnabled(),
+ gfxPrefs::LayersAccelerationDisabledDoNotUseDirectly(),
+ gfxPrefs::LayersAccelerationForceEnabledDoNotUseDirectly(),
+ gfxPrefs::LayersD3D11ForceWARP());
+ // WebGL prefs
+ forcedPrefs.AppendPrintf("-W%d%d%d%d%d%d%d%d",
+ gfxPrefs::WebGLANGLEForceD3D11(),
+ gfxPrefs::WebGLANGLEForceWARP(),
+ gfxPrefs::WebGLDisabled(),
+ gfxPrefs::WebGLDisableANGLE(),
+ gfxPrefs::WebGLDXGLEnabled(),
+ gfxPrefs::WebGLForceEnabled(),
+ gfxPrefs::WebGLForceLayersReadback(),
+ gfxPrefs::WebGLForceMSAA());
+ // Prefs that don't fit into any of the other sections
+ forcedPrefs.AppendPrintf("-T%d%d%d%d) ",
+ gfxPrefs::AndroidRGB16Force(),
+ gfxPrefs::CanvasAzureAccelerated(),
+ gfxPrefs::DisableGralloc(),
+ gfxPrefs::ForceShmemTiles());
+ ScopedGfxFeatureReporter::AppNote(forcedPrefs);
+ }
+
+ InitMoz2DLogging();
+
+ gGfxPlatformPrefsLock = new Mutex("gfxPlatform::gGfxPlatformPrefsLock");
+
+ /* 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 = services::GetGfxInfo();
+
+#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->InitAcceleration();
+
+ if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
+ GPUProcessManager* gpu = GPUProcessManager::Get();
+ gpu->LaunchGPUProcess();
+ }
+
+ if (XRE_IsParentProcess()) {
+ if (gfxPlatform::ForceSoftwareVsync()) {
+ gPlatform->mVsyncSource = (gPlatform)->gfxPlatform::CreateHardwareVsyncSource();
+ } else {
+ gPlatform->mVsyncSource = gPlatform->CreateHardwareVsyncSource();
+ }
+ }
+
+#ifdef USE_SKIA
+ SkGraphics::Init();
+# ifdef MOZ_ENABLE_FREETYPE
+ SkInitCairoFT(gPlatform->FontHintingEnabled());
+# endif
+#endif
+
+#ifdef MOZ_GL_DEBUG
+ GLContext::StaticInit();
+#endif
+
+ InitLayersIPC();
+
+ gPlatform->PopulateScreenInfo();
+ gPlatform->ComputeTileSize();
+
+ nsresult rv;
+
+ bool usePlatformFontList = true;
+#if defined(MOZ_WIDGET_GTK)
+ usePlatformFontList = gfxPlatformGtk::UseFcFontList();
+#endif
+
+ if (usePlatformFontList) {
+ rv = gfxPlatformFontList::Init();
+ if (NS_FAILED(rv)) {
+ NS_RUNTIMEABORT("Could not initialize gfxPlatformFontList");
+ }
+ }
+
+ gPlatform->mScreenReferenceSurface =
+ gPlatform->CreateOffscreenSurface(IntSize(1, 1),
+ SurfaceFormat::A8R8G8B8_UINT32);
+ if (!gPlatform->mScreenReferenceSurface) {
+ NS_RUNTIMEABORT("Could not initialize mScreenReferenceSurface");
+ }
+
+ gPlatform->mScreenReferenceDrawTarget =
+ gPlatform->CreateOffscreenContentDrawTarget(IntSize(1, 1),
+ SurfaceFormat::B8G8R8A8);
+ if (!gPlatform->mScreenReferenceDrawTarget ||
+ !gPlatform->mScreenReferenceDrawTarget->IsValid()) {
+ NS_RUNTIMEABORT("Could not initialize mScreenReferenceDrawTarget");
+ }
+
+ rv = gfxFontCache::Init();
+ if (NS_FAILED(rv)) {
+ NS_RUNTIMEABORT("Could not initialize gfxFontCache");
+ }
+
+ /* Create and register our CMS Override observer. */
+ gPlatform->mSRGBOverrideObserver = new SRGBOverrideObserver();
+ Preferences::AddWeakObserver(gPlatform->mSRGBOverrideObserver, GFX_PREF_CMS_FORCE_SRGB);
+
+ gPlatform->mFontPrefsObserver = new FontPrefsObserver();
+ Preferences::AddStrongObservers(gPlatform->mFontPrefsObserver, kObservedPrefs);
+
+ GLContext::PlatformStartup();
+
+#ifdef MOZ_WIDGET_ANDROID
+ // Texture pool init
+ TexturePoolOGL::Init();
+#endif
+
+ Preferences::RegisterCallbackAndCall(RecordingPrefChanged, "gfx.2d.recording", nullptr);
+
+ CreateCMSOutputProfile();
+
+ // Listen to memory pressure event so we can purge DrawTarget caches
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ gPlatform->mMemoryPressureObserver = new MemoryPressureObserver();
+ obs->AddObserver(gPlatform->mMemoryPressureObserver, "memory-pressure", false);
+ }
+
+ // Request the imgITools service, implicitly initializing ImageLib.
+ nsCOMPtr<imgITools> imgTools = do_GetService("@mozilla.org/image/tools;1");
+ if (!imgTools) {
+ NS_RUNTIMEABORT("Could not initialize ImageLib");
+ }
+
+ RegisterStrongMemoryReporter(new GfxMemoryImageReporter());
+
+#ifdef USE_SKIA
+ uint32_t skiaCacheSize = GetSkiaGlyphCacheSize();
+ if (skiaCacheSize != kDefaultGlyphCacheSize) {
+ SkGraphics::SetFontCacheLimit(skiaCacheSize);
+ }
+#endif
+
+ InitNullMetadata();
+ InitOpenGLConfig();
+
+ if (obs) {
+ obs->NotifyObservers(nullptr, "gfx-features-ready", nullptr);
+ }
+}
+
+/* static */ void
+gfxPlatform::InitMoz2DLogging()
+{
+ auto fwd = new CrashStatsLogForwarder("GraphicsCriticalError");
+ fwd->SetCircularBufferSize(gfxPrefs::GfxLoggingCrashLength());
+
+ mozilla::gfx::Config cfg;
+ cfg.mLogForwarder = fwd;
+ cfg.mMaxTextureSize = gfxPrefs::MaxTextureSize();
+ cfg.mMaxAllocSize = gfxPrefs::MaxAllocSize();
+
+ gfx::Factory::Init(cfg);
+}
+
+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();
+ gfxFontGroup::Shutdown();
+ gfxGradientCache::Shutdown();
+ gfxAlphaBoxBlur::ShutdownBlurCache();
+ gfxGraphiteShaper::Shutdown();
+ gfxPlatformFontList::Shutdown();
+ ShutdownTileCache();
+
+ // Free the various non-null transforms and loaded profiles
+ ShutdownCMS();
+
+ /* Unregister our CMS Override callback. */
+ NS_ASSERTION(gPlatform->mSRGBOverrideObserver, "mSRGBOverrideObserver has alreay gone");
+ Preferences::RemoveObserver(gPlatform->mSRGBOverrideObserver, GFX_PREF_CMS_FORCE_SRGB);
+ gPlatform->mSRGBOverrideObserver = nullptr;
+
+ NS_ASSERTION(gPlatform->mFontPrefsObserver, "mFontPrefsObserver has alreay gone");
+ Preferences::RemoveObservers(gPlatform->mFontPrefsObserver, kObservedPrefs);
+ gPlatform->mFontPrefsObserver = nullptr;
+
+ NS_ASSERTION(gPlatform->mMemoryPressureObserver, "mMemoryPressureObserver has already gone");
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(gPlatform->mMemoryPressureObserver, "memory-pressure");
+ }
+
+ gPlatform->mMemoryPressureObserver = nullptr;
+ gPlatform->mSkiaGlue = nullptr;
+
+ if (XRE_IsParentProcess()) {
+ gPlatform->mVsyncSource->Shutdown();
+ }
+
+ gPlatform->mVsyncSource = nullptr;
+
+#ifdef MOZ_WIDGET_ANDROID
+ // Shut down the texture pool
+ TexturePoolOGL::Shutdown();
+#endif
+
+ // 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();
+ }
+
+ gfx::Factory::ShutDown();
+
+ delete gGfxPlatformPrefsLock;
+
+ gfxVars::Shutdown();
+ gfxPrefs::DestroySingleton();
+ gfxFont::DestroySingletons();
+
+ gfxConfig::Shutdown();
+
+ gPlatform->WillShutdown();
+
+ delete gPlatform;
+ gPlatform = nullptr;
+}
+
+/* static */ void
+gfxPlatform::InitLayersIPC()
+{
+ if (sLayersIPCIsUp) {
+ return;
+ }
+ sLayersIPCIsUp = true;
+
+ if (XRE_IsParentProcess())
+ {
+ layers::CompositorThreadHolder::Start();
+ }
+}
+
+/* static */ void
+gfxPlatform::ShutdownLayersIPC()
+{
+ if (!sLayersIPCIsUp) {
+ return;
+ }
+ sLayersIPCIsUp = false;
+
+ if (XRE_IsContentProcess()) {
+ gfx::VRManagerChild::ShutDown();
+ // cf bug 1215265.
+ if (gfxPrefs::ChildProcessShutdown()) {
+ layers::CompositorBridgeChild::ShutDown();
+ layers::ImageBridgeChild::ShutDown();
+ }
+ } else if (XRE_IsParentProcess()) {
+ gfx::VRManagerChild::ShutDown();
+ layers::CompositorBridgeChild::ShutDown();
+ layers::ImageBridgeChild::ShutDown();
+
+ // This has to happen after shutting down the child protocols.
+ layers::CompositorThreadHolder::Shutdown();
+ } 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;
+}
+
+gfxPlatform::~gfxPlatform()
+{
+ // 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
+#ifdef USE_SKIA
+ // must do Skia cleanup before Cairo cleanup, because Skia may be referencing
+ // Cairo objects e.g. through SkCairoFTTypeface
+ SkGraphics::PurgeFontCache();
+#endif
+
+#if MOZ_TREE_CAIRO
+ cairo_debug_reset_static_data();
+#endif
+#endif
+}
+
+/* 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;
+};
+
+void SourceBufferDestroy(void *srcSurfUD)
+{
+ delete static_cast<SourceSurfaceUserData*>(srcSurfUD);
+}
+
+UserDataKey kThebesSurface;
+
+struct DependentSourceSurfaceUserData
+{
+ RefPtr<gfxASurface> mSurface;
+};
+
+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(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.
+ SourceSurfaceUserData *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.
+ DependentSourceSurfaceUserData *srcSurfUD = new DependentSourceSurfaceUserData;
+ srcSurfUD->mSurface = aSurface;
+ result->AddUserData(&kThebesSurface, srcSurfUD, SourceSurfaceDestroyed);
+
+ return result.forget();
+}
+
+already_AddRefed<ScaledFont>
+gfxPlatform::GetScaledFontForFont(DrawTarget* aTarget, gfxFont *aFont)
+{
+ NativeFont nativeFont;
+ nativeFont.mType = NativeFontType::CAIRO_FONT_FACE;
+ nativeFont.mFont = aFont->GetCairoScaledFont();
+ return Factory::CreateScaledFontForNativeFont(nativeFont,
+ aFont->GetAdjustedSize());
+}
+
+void
+gfxPlatform::ComputeTileSize()
+{
+ // The tile size should be picked in the parent processes
+ // and sent to the child processes over IPDL GetTileSize.
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ int32_t w = gfxPrefs::LayersTileWidth();
+ int32_t h = gfxPrefs::LayersTileHeight();
+
+ if (gfxPrefs::LayersTilesAdjust()) {
+ gfx::IntSize screenSize = GetScreenSize();
+ if (screenSize.width > 0) {
+ // Choose a size so that there are between 2 and 4 tiles per screen width.
+ // FIXME: we should probably make sure this is within the max texture size,
+ // but I think everything should at least support 1024
+ w = h = clamped(int32_t(RoundUpPow2(screenSize.width)) / 4, 256, 1024);
+ }
+ }
+
+ // Don't allow changing the tile size after we've set it.
+ // Right now the code assumes that the tile size doesn't change.
+ MOZ_ASSERT(gfxVars::TileSize().width == -1 &&
+ gfxVars::TileSize().height == -1);
+
+ gfxVars::SetTileSize(IntSize(w, h));
+}
+
+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);
+
+ int left, top;
+ screen->GetRect(&left, &top, &mScreenSize.width, &mScreenSize.height);
+}
+
+bool
+gfxPlatform::SupportsAzureContentForDrawTarget(DrawTarget* aTarget)
+{
+ if (!aTarget || !aTarget->IsValid()) {
+ return false;
+ }
+
+#ifdef USE_SKIA_GPU
+ // Skia content rendering doesn't support GPU acceleration, so we can't
+ // use the same backend if the current backend is accelerated.
+ if ((aTarget->GetType() == DrawTargetType::HARDWARE_RASTER)
+ && (aTarget->GetBackendType() == BackendType::SKIA))
+ {
+ return false;
+ }
+#endif
+
+ return SupportsAzureContentForType(aTarget->GetBackendType());
+}
+
+bool gfxPlatform::AllowOpenGLCanvas()
+{
+ // For now, only allow Skia+OpenGL, unless it's blocked.
+ // Allow acceleration on Skia if the preference is set, unless it's blocked
+ // as long as we have the accelerated layers
+
+ // The compositor backend is only set correctly in the parent process,
+ // so we let content process always assume correct compositor backend.
+ // The callers have to do the right thing.
+ bool correctBackend = !XRE_IsParentProcess() ||
+ ((mCompositorBackend == LayersBackend::LAYERS_OPENGL) &&
+ (GetContentBackendFor(mCompositorBackend) == BackendType::SKIA));
+
+ if (gfxPrefs::CanvasAzureAccelerated() && correctBackend) {
+ nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
+ int32_t status;
+ nsCString discardFailureId;
+ return !gfxInfo ||
+ (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION,
+ discardFailureId,
+ &status)) &&
+ status == nsIGfxInfo::FEATURE_STATUS_OK);
+ }
+ return false;
+}
+
+void
+gfxPlatform::InitializeSkiaCacheLimits()
+{
+ if (AllowOpenGLCanvas()) {
+#ifdef USE_SKIA_GPU
+ bool usingDynamicCache = gfxPrefs::CanvasSkiaGLDynamicCache();
+ int cacheItemLimit = gfxPrefs::CanvasSkiaGLCacheItems();
+ uint64_t cacheSizeLimit = std::max(gfxPrefs::CanvasSkiaGLCacheSize(), (int32_t)0);
+
+ // Prefs are in megabytes, but we want the sizes in bytes
+ cacheSizeLimit *= 1024*1024;
+
+ if (usingDynamicCache) {
+ if (mTotalSystemMemory < 512*1024*1024) {
+ // We need a very minimal cache on anything smaller than 512mb.
+ // Note the large jump as we cross 512mb (from 2mb to 32mb).
+ cacheSizeLimit = 2*1024*1024;
+ } else if (mTotalSystemMemory > 0) {
+ cacheSizeLimit = mTotalSystemMemory / 16;
+ }
+ }
+
+ // Ensure cache size doesn't overflow on 32-bit platforms.
+ cacheSizeLimit = std::min(cacheSizeLimit, (uint64_t)SIZE_MAX);
+
+ #ifdef DEBUG
+ printf_stderr("Determined SkiaGL cache limits: Size %" PRIu64 ", Items: %i\n", cacheSizeLimit, cacheItemLimit);
+ #endif
+
+ mSkiaGlue->GetGrContext()->setResourceCacheLimits(cacheItemLimit, (size_t)cacheSizeLimit);
+#endif
+ }
+}
+
+SkiaGLGlue*
+gfxPlatform::GetSkiaGLGlue()
+{
+#ifdef USE_SKIA_GPU
+ // Check the accelerated Canvas is enabled for the first time,
+ // because the callers should check it before using.
+ if (!mSkiaGlue && !AllowOpenGLCanvas()) {
+ return nullptr;
+ }
+
+ if (!mSkiaGlue) {
+ /* Dummy context. We always draw into a FBO.
+ *
+ * FIXME: This should be stored in TLS or something, since there needs to be one for each thread using it. As it
+ * stands, this only works on the main thread.
+ */
+ RefPtr<GLContext> glContext;
+ nsCString discardFailureId;
+ glContext = GLContextProvider::CreateHeadless(CreateContextFlags::REQUIRE_COMPAT_PROFILE |
+ CreateContextFlags::ALLOW_OFFLINE_RENDERER,
+ &discardFailureId);
+ if (!glContext) {
+ printf_stderr("Failed to create GLContext for SkiaGL!\n");
+ return nullptr;
+ }
+ mSkiaGlue = new SkiaGLGlue(glContext);
+ MOZ_ASSERT(mSkiaGlue->GetGrContext(), "No GrContext");
+ InitializeSkiaCacheLimits();
+ }
+#endif
+
+ return mSkiaGlue;
+}
+
+void
+gfxPlatform::PurgeSkiaFontCache()
+{
+#ifdef USE_SKIA
+ if (gfxPlatform::GetPlatform()->GetDefaultContentBackend() == BackendType::SKIA) {
+ SkGraphics::PurgeFontCache();
+ }
+#endif
+}
+
+void
+gfxPlatform::PurgeSkiaGPUCache()
+{
+#ifdef USE_SKIA_GPU
+ if (!mSkiaGlue)
+ return;
+
+ mSkiaGlue->GetGrContext()->freeGpuResources();
+ // GrContext::flush() doesn't call glFlush. Call it here.
+ mSkiaGlue->GetGLContext()->MakeCurrent();
+ mSkiaGlue->GetGLContext()->fFlush();
+#endif
+}
+
+bool
+gfxPlatform::HasEnoughTotalSystemMemoryForSkiaGL()
+{
+ return true;
+}
+
+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);
+ } else {
+ return Factory::CreateDrawTarget(aBackend, aSize, aFormat);
+ }
+}
+
+already_AddRefed<DrawTarget>
+gfxPlatform::CreateOffscreenCanvasDrawTarget(const IntSize& aSize, SurfaceFormat aFormat)
+{
+ NS_ASSERTION(mPreferredCanvasBackend != BackendType::NONE, "No backend.");
+ 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)
+{
+ NS_ASSERTION(mPreferredCanvasBackend != BackendType::NONE, "No backend.");
+ return CreateDrawTargetForBackend(mContentBackend, aSize, aFormat);
+}
+
+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 {
+ dt = Factory::CreateDrawTarget(BackendType::SKIA, aSize, aFormat);
+ }
+
+ return dt.forget();
+}
+
+/* static */ already_AddRefed<DrawTarget>
+gfxPlatform::CreateDrawTargetForData(unsigned char* aData, const IntSize& aSize, int32_t aStride, SurfaceFormat aFormat)
+{
+ BackendType backendType = gfxVars::ContentBackend();
+ NS_ASSERTION(backendType != BackendType::NONE, "No backend.");
+
+ if (!Factory::DoesBackendSupportDataDrawtarget(backendType)) {
+ backendType = BackendType::CAIRO;
+ }
+
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(backendType,
+ aData, aSize,
+ aStride, aFormat);
+
+ 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(nsIAtom *aLangGroup,
+ const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts)
+{
+ gfxPlatformFontList::PlatformFontList()->GetFontList(aLangGroup,
+ aGenericFamily,
+ aListOfFonts);
+ return NS_OK;
+}
+
+nsresult
+gfxPlatform::UpdateFontList()
+{
+ gfxPlatformFontList::PlatformFontList()->UpdateFontList();
+ return NS_OK;
+}
+
+nsresult
+gfxPlatform::GetStandardFamilyName(const nsAString& aFontName,
+ nsAString& aFamilyName)
+{
+ gfxPlatformFontList::PlatformFontList()->GetStandardFamilyName(aFontName,
+ aFamilyName);
+ return NS_OK;
+}
+
+bool
+gfxPlatform::DownloadableFontsEnabled()
+{
+ if (mAllowDownloadableFonts == UNINITIALIZED_VALUE) {
+ mAllowDownloadableFonts =
+ Preferences::GetBool(GFX_DOWNLOADABLE_FONTS_ENABLED, false);
+ }
+
+ return mAllowDownloadableFonts;
+}
+
+bool
+gfxPlatform::UseCmapsDuringSystemFallback()
+{
+ if (mFallbackUsesCmaps == UNINITIALIZED_VALUE) {
+ mFallbackUsesCmaps =
+ Preferences::GetBool(GFX_PREF_FALLBACK_USE_CMAPS, false);
+ }
+
+ return mFallbackUsesCmaps;
+}
+
+bool
+gfxPlatform::OpenTypeSVGEnabled()
+{
+ if (mOpenTypeSVGEnabled == UNINITIALIZED_VALUE) {
+ mOpenTypeSVGEnabled =
+ Preferences::GetBool(GFX_PREF_OPENTYPE_SVG, false);
+ }
+
+ return mOpenTypeSVGEnabled > 0;
+}
+
+uint32_t
+gfxPlatform::WordCacheCharLimit()
+{
+ if (mWordCacheCharLimit == UNINITIALIZED_VALUE) {
+ mWordCacheCharLimit =
+ Preferences::GetInt(GFX_PREF_WORD_CACHE_CHARLIMIT, 32);
+ if (mWordCacheCharLimit < 0) {
+ mWordCacheCharLimit = 32;
+ }
+ }
+
+ return uint32_t(mWordCacheCharLimit);
+}
+
+uint32_t
+gfxPlatform::WordCacheMaxEntries()
+{
+ if (mWordCacheMaxEntries == UNINITIALIZED_VALUE) {
+ mWordCacheMaxEntries =
+ Preferences::GetInt(GFX_PREF_WORD_CACHE_MAXENTRIES, 10000);
+ if (mWordCacheMaxEntries < 0) {
+ mWordCacheMaxEntries = 10000;
+ }
+ }
+
+ return uint32_t(mWordCacheMaxEntries);
+}
+
+bool
+gfxPlatform::UseGraphiteShaping()
+{
+ if (mGraphiteShapingEnabled == UNINITIALIZED_VALUE) {
+ mGraphiteShapingEnabled =
+ Preferences::GetBool(GFX_PREF_GRAPHITE_SHAPING, false);
+ }
+
+ return mGraphiteShapingEnabled;
+}
+
+gfxFontEntry*
+gfxPlatform::LookupLocalFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle)
+{
+ return gfxPlatformFontList::PlatformFontList()->LookupLocalFont(aFontName,
+ aWeight,
+ aStretch,
+ aStyle);
+}
+
+gfxFontEntry*
+gfxPlatform::MakePlatformFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t* aFontData,
+ uint32_t aLength)
+{
+ return gfxPlatformFontList::PlatformFontList()->MakePlatformFont(aFontName,
+ aWeight,
+ aStretch,
+ aStyle,
+ aFontData,
+ aLength);
+}
+
+mozilla::layers::DiagnosticTypes
+gfxPlatform::GetLayerDiagnosticTypes()
+{
+ mozilla::layers::DiagnosticTypes type = DiagnosticTypes::NO_DIAGNOSTIC;
+ if (gfxPrefs::DrawLayerBorders()) {
+ type |= mozilla::layers::DiagnosticTypes::LAYER_BORDERS;
+ }
+ if (gfxPrefs::DrawTileBorders()) {
+ type |= mozilla::layers::DiagnosticTypes::TILE_BORDERS;
+ }
+ if (gfxPrefs::DrawBigImageBorders()) {
+ type |= mozilla::layers::DiagnosticTypes::BIGIMAGE_BORDERS;
+ }
+ if (gfxPrefs::FlashLayerBorders()) {
+ type |= mozilla::layers::DiagnosticTypes::FLASH_BORDERS;
+ }
+ return type;
+}
+
+void
+gfxPlatform::InitBackendPrefs(uint32_t aCanvasBitmask, BackendType aCanvasDefault,
+ uint32_t aContentBitmask, BackendType aContentDefault)
+{
+ mPreferredCanvasBackend = GetCanvasBackendPref(aCanvasBitmask);
+ if (mPreferredCanvasBackend == BackendType::NONE) {
+ mPreferredCanvasBackend = aCanvasDefault;
+ }
+
+ 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(aCanvasBitmask &
+ ~(BackendTypeBit(mPreferredCanvasBackend) | BackendTypeBit(BackendType::DIRECT2D)));
+ } else {
+ mFallbackCanvasBackend =
+ GetCanvasBackendPref(aCanvasBitmask & ~BackendTypeBit(mPreferredCanvasBackend));
+ }
+
+ mContentBackendBitmask = aContentBitmask;
+ mContentBackend = GetContentBackendPref(mContentBackendBitmask);
+ if (mContentBackend == BackendType::NONE) {
+ mContentBackend = aContentDefault;
+ // 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(aContentDefault);
+ }
+
+ if (XRE_IsParentProcess()) {
+ gfxVars::SetContentBackend(mContentBackend);
+ }
+}
+
+/* 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;
+ nsCString 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();
+}
+
+eCMSMode
+gfxPlatform::GetCMSMode()
+{
+ if (!gCMSInitialized) {
+ int32_t mode = gfxPrefs::CMSMode();
+ if (mode >= 0 && mode < eCMSMode_AllCount) {
+ gCMSMode = static_cast<eCMSMode>(mode);
+ }
+
+ bool enableV4 = gfxPrefs::CMSEnableV4();
+ if (enableV4) {
+ qcms_enable_iccv4();
+ }
+ gCMSInitialized = true;
+ }
+ return gCMSMode;
+}
+
+int
+gfxPlatform::GetRenderingIntent()
+{
+ // gfxPrefs.h 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 = gfxPrefs::CMSRenderingIntent();
+ if ((pIntent < QCMS_INTENT_MIN) || (pIntent > QCMS_INTENT_MAX)) {
+ /* If the pref is out of range, use embedded profile. */
+ pIntent = -1;
+ }
+ return pIntent;
+}
+
+void
+gfxPlatform::TransformPixel(const Color& in, Color& out, 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);
+ out = Color::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);
+ out = Color::UnusualFromARGB(packed);
+#endif
+ }
+
+ else if (&out != &in)
+ out = in;
+}
+
+void
+gfxPlatform::GetPlatformCMSOutputProfile(void *&mem, size_t &size)
+{
+ mem = nullptr;
+ size = 0;
+}
+
+void
+gfxPlatform::GetCMSOutputProfileData(void *&mem, size_t &size)
+{
+ nsAdoptingCString fname = Preferences::GetCString("gfx.color_management.display_profile");
+ if (!fname.IsEmpty()) {
+ qcms_data_from_path(fname, &mem, &size);
+ }
+ else {
+ gfxPlatform::GetPlatform()->GetPlatformCMSOutputProfile(mem, size);
+ }
+}
+
+void
+gfxPlatform::CreateCMSOutputProfile()
+{
+ if (!gCMSOutputProfile) {
+ /* 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 (Preferences::GetBool(GFX_PREF_CMS_FORCE_SRGB, false)) {
+ gCMSOutputProfile = GetCMSsRGBProfile();
+ }
+
+ if (!gCMSOutputProfile) {
+ void* mem = nullptr;
+ size_t size = 0;
+
+ GetCMSOutputProfileData(mem, size);
+ if ((mem != nullptr) && (size > 0)) {
+ gCMSOutputProfile = qcms_profile_from_memory(mem, size);
+ free(mem);
+ }
+ }
+
+ /* 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 != GetCMSsRGBProfile(),
+ "Builtin sRGB profile tagged as bogus!!!");
+ qcms_profile_release(gCMSOutputProfile);
+ gCMSOutputProfile = nullptr;
+ }
+
+ if (!gCMSOutputProfile) {
+ gCMSOutputProfile = GetCMSsRGBProfile();
+ }
+ /* Precache the LUT16 Interpolations for the output profile. See
+ bug 444661 for details. */
+ qcms_profile_precache_output_transform(gCMSOutputProfile);
+ }
+}
+
+qcms_profile *
+gfxPlatform::GetCMSOutputProfile()
+{
+ return gCMSOutputProfile;
+}
+
+qcms_profile *
+gfxPlatform::GetCMSsRGBProfile()
+{
+ if (!gCMSsRGBProfile) {
+
+ /* Create the profile using qcms. */
+ gCMSsRGBProfile = qcms_profile_sRGB();
+ }
+ return gCMSsRGBProfile;
+}
+
+qcms_transform *
+gfxPlatform::GetCMSRGBTransform()
+{
+ if (!gCMSRGBTransform) {
+ qcms_profile *inProfile, *outProfile;
+ outProfile = GetCMSOutputProfile();
+ inProfile = GetCMSsRGBProfile();
+
+ if (!inProfile || !outProfile)
+ return nullptr;
+
+ gCMSRGBTransform = qcms_transform_create(inProfile, QCMS_DATA_RGB_8,
+ outProfile, QCMS_DATA_RGB_8,
+ QCMS_INTENT_PERCEPTUAL);
+ }
+
+ return gCMSRGBTransform;
+}
+
+qcms_transform *
+gfxPlatform::GetCMSInverseRGBTransform()
+{
+ if (!gCMSInverseRGBTransform) {
+ qcms_profile *inProfile, *outProfile;
+ inProfile = GetCMSOutputProfile();
+ outProfile = GetCMSsRGBProfile();
+
+ if (!inProfile || !outProfile)
+ return nullptr;
+
+ gCMSInverseRGBTransform = qcms_transform_create(inProfile, QCMS_DATA_RGB_8,
+ outProfile, QCMS_DATA_RGB_8,
+ QCMS_INTENT_PERCEPTUAL);
+ }
+
+ return gCMSInverseRGBTransform;
+}
+
+qcms_transform *
+gfxPlatform::GetCMSRGBATransform()
+{
+ if (!gCMSRGBATransform) {
+ qcms_profile *inProfile, *outProfile;
+ outProfile = GetCMSOutputProfile();
+ inProfile = GetCMSsRGBProfile();
+
+ if (!inProfile || !outProfile)
+ return nullptr;
+
+ gCMSRGBATransform = qcms_transform_create(inProfile, QCMS_DATA_RGBA_8,
+ outProfile, QCMS_DATA_RGBA_8,
+ QCMS_INTENT_PERCEPTUAL);
+ }
+
+ return gCMSRGBATransform;
+}
+
+/* Shuts down various transforms and profiles for CMS. */
+static void 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 (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 = eCMSMode_Off;
+ gCMSInitialized = false;
+}
+
+// default SetupClusterBoundaries, based on Unicode properties;
+// platform subclasses may override if they wish
+void
+gfxPlatform::SetupClusterBoundaries(gfxTextRun *aTextRun, const char16_t *aString)
+{
+ if (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_IS_8BIT) {
+ // 8-bit text doesn't have clusters.
+ // XXX is this true in all languages???
+ // behdad: don't think so. Czech for example IIRC has a
+ // 'ch' grapheme.
+ // jfkthame: but that's not expected to behave as a grapheme cluster
+ // for selection/editing/etc.
+ return;
+ }
+
+ aTextRun->SetupClusterBoundaries(0, aString, aTextRun->GetLength());
+}
+
+int32_t
+gfxPlatform::GetBidiNumeralOption()
+{
+ if (mBidiNumeralOption == UNINITIALIZED_VALUE) {
+ mBidiNumeralOption = Preferences::GetInt(BIDI_NUMERAL_PREF, 0);
+ }
+ return mBidiNumeralOption;
+}
+
+/* static */ void
+gfxPlatform::FlushFontAndWordCaches()
+{
+ gfxFontCache *fontCache = gfxFontCache::GetCache();
+ if (fontCache) {
+ fontCache->AgeAllGenerations();
+ fontCache->FlushShapedWordCaches();
+ }
+
+ gfxPlatform::PurgeSkiaFontCache();
+}
+
+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_FALLBACK_USE_CMAPS, aPref)) {
+ mFallbackUsesCmaps = UNINITIALIZED_VALUE;
+ } else if (!strcmp(GFX_PREF_WORD_CACHE_CHARLIMIT, aPref)) {
+ mWordCacheCharLimit = UNINITIALIZED_VALUE;
+ FlushFontAndWordCaches();
+ } else if (!strcmp(GFX_PREF_WORD_CACHE_MAXENTRIES, aPref)) {
+ mWordCacheMaxEntries = UNINITIALIZED_VALUE;
+ FlushFontAndWordCaches();
+ } else if (!strcmp(GFX_PREF_GRAPHITE_SHAPING, aPref)) {
+ mGraphiteShapingEnabled = UNINITIALIZED_VALUE;
+ FlushFontAndWordCaches();
+ } else if (!strcmp(BIDI_NUMERAL_PREF, aPref)) {
+ mBidiNumeralOption = UNINITIALIZED_VALUE;
+ } else if (!strcmp(GFX_PREF_OPENTYPE_SVG, aPref)) {
+ mOpenTypeSVGEnabled = UNINITIALIZED_VALUE;
+ gfxFontCache::GetCache()->AgeAllGenerations();
+ }
+}
+
+
+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;
+}
+
+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:
+ NS_NOTREACHED("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:
+ NS_NOTREACHED("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:
+ NS_NOTREACHED("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 bool sBufferRotationCheckPref = true;
+
+static mozilla::Atomic<bool> sLayersAccelerationPrefsInitialized(false);
+
+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::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");
+
+ gfxPrefs::GetSingleton();
+
+ if (XRE_IsParentProcess()) {
+ gfxVars::SetBrowserTabsRemoteAutostart(BrowserTabsRemoteAutostart());
+ gfxVars::SetOffscreenFormat(GetOffscreenFormat());
+ gfxVars::SetRequiresAcceleratedGLContextForCompositorOGL(
+ RequiresAcceleratedGLContextForCompositorOGL());
+ }
+
+ nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
+ nsCString discardFailureId;
+ int32_t status;
+
+ if (Preferences::GetBool("media.hardware-video-decoding.enabled", false) &&
+#ifdef XP_WIN
+ Preferences::GetBool("media.windows-media-foundation.use-dxva", true) &&
+#endif
+ NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ discardFailureId, &status))) {
+ if (status == nsIGfxInfo::FEATURE_STATUS_OK || gfxPrefs::HardwareVideoDecodingForceEnabled()) {
+ sLayersSupportsHardwareVideoDecoding = true;
+ }
+ }
+
+ sLayersAccelerationPrefsInitialized = true;
+
+ if (XRE_IsParentProcess()) {
+ Preferences::RegisterCallbackAndCall(VideoDecodingFailedChangedCallback,
+ "media.hardware-video-decoding.failed",
+ nullptr,
+ Preferences::ExactMatch);
+ InitGPUProcessPrefs();
+ }
+}
+
+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 (!gfxPrefs::GPUProcessDevEnabled() && !gfxPrefs::GPUProcessDevForceEnabled()) {
+ return;
+ }
+
+ FeatureState& gpuProc = gfxConfig::GetFeature(Feature::GPU_PROCESS);
+
+ gpuProc.SetDefaultFromPref(
+ gfxPrefs::GetGPUProcessDevEnabledPrefName(),
+ true,
+ gfxPrefs::GetGPUProcessDevEnabledPrefDefault());
+
+ if (gfxPrefs::GPUProcessDevForceEnabled()) {
+ gpuProc.UserForceEnable("User force-enabled via pref");
+ }
+
+ // 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.ForceDisable(
+ FeatureStatus::Unavailable,
+ "Multi-process mode is not enabled",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_NO_E10S"));
+ return;
+ }
+ if (InSafeMode()) {
+ gpuProc.ForceDisable(
+ FeatureStatus::Blocked,
+ "Safe-mode is enabled",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_SAFE_MODE"));
+ return;
+ }
+ if (gfxPrefs::LayerScopeEnabled()) {
+ gpuProc.ForceDisable(
+ FeatureStatus::Blocked,
+ "LayerScope does not work in the GPU process",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_LAYERSCOPE"));
+ return;
+ }
+}
+
+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 (gfxPrefs::LayersAccelerationDisabledDoNotUseDirectly()) {
+ feature.UserDisable("Disabled by pref",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_COMP_PREF"));
+ } else if (acceleratedEnv && *acceleratedEnv == '0') {
+ feature.UserDisable("Disabled by envvar",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_COMP_ENV"));
+ }
+ } else {
+ if (acceleratedEnv && *acceleratedEnv == '1') {
+ feature.UserEnable("Enabled by envvar");
+ }
+ }
+
+ // This has specific meaning elsewhere, so we always record it.
+ if (gfxPrefs::LayersAccelerationForceEnabledDoNotUseDirectly()) {
+ feature.UserForceEnable("Force-enabled by pref");
+ }
+
+ // Safe mode trumps everything.
+ if (InSafeMode()) {
+ feature.ForceDisable(FeatureStatus::Blocked, "Acceleration blocked by safe-mode",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_COMP_SAFEMODE"));
+ }
+}
+
+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
+}
+
+bool
+gfxPlatform::BufferRotationEnabled()
+{
+ MutexAutoLock autoLock(*gGfxPlatformPrefsLock);
+
+ return sBufferRotationCheckPref && gfxPrefs::BufferRotationEnabled();
+}
+
+void
+gfxPlatform::DisableBufferRotation()
+{
+ MutexAutoLock autoLock(*gGfxPlatformPrefsLock);
+
+ sBufferRotationCheckPref = false;
+}
+
+already_AddRefed<ScaledFont>
+gfxPlatform::GetScaledFontForFontWithCairoSkia(DrawTarget* aTarget, gfxFont* aFont)
+{
+ NativeFont nativeFont;
+ if (aTarget->GetBackendType() == BackendType::CAIRO || aTarget->GetBackendType() == BackendType::SKIA) {
+ nativeFont.mType = NativeFontType::CAIRO_FONT_FACE;
+ nativeFont.mFont = aFont->GetCairoScaledFont();
+ return Factory::CreateScaledFontForNativeFont(nativeFont, aFont->GetAdjustedSize());
+ }
+
+ return nullptr;
+}
+
+/* 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() ||
+ !gfxPrefs::LayersOffMainThreadCompositionForceDisabled();
+#if defined(MOZ_WIDGET_GTK)
+ // Linux users who chose OpenGL are being grandfathered in to OMTC
+ result |= gfxPrefs::LayersAccelerationForceEnabledDoNotUseDirectly();
+
+#endif
+ firstTime = false;
+ }
+
+ return result;
+}
+
+/***
+ * 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::CreateHardwareVsyncSource()
+{
+ RefPtr<mozilla::gfx::VsyncSource> softwareVsync = new SoftwareVsyncSource();
+ return softwareVsync.forget();
+}
+
+/* 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.
+ return gfxPrefs::LayoutFrameRate() == 0;
+}
+
+/* static */ bool
+gfxPlatform::ForceSoftwareVsync()
+{
+ return gfxPrefs::LayoutFrameRate() > 0;
+}
+
+/* static */ int
+gfxPlatform::GetSoftwareVsyncRate()
+{
+ int preferenceRate = gfxPrefs::LayoutFrameRate();
+ if (preferenceRate <= 0) {
+ return gfxPlatform::GetDefaultFrameRate();
+ }
+ return preferenceRate;
+}
+
+/* static */ int
+gfxPlatform::GetDefaultFrameRate()
+{
+ return 60;
+}
+
+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);
+ }
+}
+
+void
+gfxPlatform::GetTilesSupportInfo(mozilla::widget::InfoObject& aObj)
+{
+ if (!gfxPrefs::LayersTilesEnabled()) {
+ return;
+ }
+
+ IntSize tileSize = gfxVars::TileSize();
+ aObj.DefineProperty("TileHeight", tileSize.height);
+ aObj.DefineProperty("TileWidth", tileSize.width);
+}
+
+/*static*/ bool
+gfxPlatform::AsyncPanZoomEnabled()
+{
+#if !defined(MOZ_B2G) && !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_WIDGET_UIKIT)
+ // For XUL applications (everything but B2G on mobile and desktop, and
+ // 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
+ return gfxPrefs::AsyncPanZoomEnabledDoNotUseDirectly();
+#endif
+}
+
+/*static*/ bool
+gfxPlatform::PerfWarnings()
+{
+ return gfxPrefs::PerfWarnings();
+}
+
+void
+gfxPlatform::GetAcceleratedCompositorBackends(nsTArray<LayersBackend>& aBackends)
+{
+ if (gfxConfig::IsEnabled(Feature::OPENGL_COMPOSITING)) {
+ aBackends.AppendElement(LayersBackend::LAYERS_OPENGL);
+ }
+ else {
+ static int tell_me_once = 0;
+ if (!tell_me_once) {
+ NS_WARNING("OpenGL-accelerated layers are not supported on this system");
+ tell_me_once = 1;
+ }
+#ifdef MOZ_WIDGET_ANDROID
+ NS_RUNTIMEABORT("OpenGL-accelerated layers are a hard requirement on this platform. "
+ "Cannot continue without support for them");
+#endif
+ }
+}
+
+void
+gfxPlatform::GetCompositorBackends(bool useAcceleration, nsTArray<mozilla::layers::LayersBackend>& aBackends)
+{
+ if (useAcceleration) {
+ GetAcceleratedCompositorBackends(aBackends);
+ }
+ aBackends.AppendElement(LayersBackend::LAYERS_BASIC);
+}
+
+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;
+
+ // Notify that we created a compositor, so telemetry can update.
+ NS_DispatchToMainThread(NS_NewRunnableFunction([] {
+ if (nsCOMPtr<nsIObserverService> obsvc = services::GetObserverService()) {
+ obsvc->NotifyObservers(nullptr, "compositor:created", nullptr);
+ }
+ }));
+}
+
+void
+gfxPlatform::FetchAndImportContentDeviceData()
+{
+ MOZ_ASSERT(XRE_IsContentProcess());
+
+ 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.
+ GPUProcessManager::Get()->EnsureGPUReady();
+
+ 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::SupportsApzDragInput() const
+{
+ return gfxPrefs::APZDragEnabled();
+}
+
+void
+gfxPlatform::BumpDeviceCounter()
+{
+ mDeviceCounter++;
+}
+
+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",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_OPENGL_NEED_HWCOMP"));
+ return;
+ }
+
+ #ifdef XP_WIN
+ openGLFeature.SetDefaultFromPref(
+ gfxPrefs::GetLayersPreferOpenGLPrefName(),
+ true,
+ gfxPrefs::GetLayersPreferOpenGLPrefDefault());
+ #else
+ openGLFeature.EnableByDefault();
+ #endif
+
+ // When layers acceleration is force-enabled, enable it even for blacklisted
+ // devices.
+ if (gfxPrefs::LayersAccelerationForceEnabledDoNotUseDirectly()) {
+ openGLFeature.UserForceEnable("Force-enabled by pref");
+ return;
+ }
+
+ nsCString message;
+ nsCString failureId;
+ if (!IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_OPENGL_LAYERS, &message, failureId)) {
+ openGLFeature.Disable(FeatureStatus::Blacklisted, message.get(), failureId);
+ }
+}
+
+bool
+gfxPlatform::IsGfxInfoStatusOkay(int32_t aFeature, nsCString* aOutMessage, nsCString& aFailureId)
+{
+ nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
+ 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 000000000..68bb99ea4
--- /dev/null
+++ b/gfx/thebes/gfxPlatform.h
@@ -0,0 +1,846 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/Logging.h"
+#include "mozilla/gfx/Types.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsUnicodeScriptCodes.h"
+
+#include "gfxTypes.h"
+#include "gfxFontFamilyList.h"
+#include "gfxBlur.h"
+#include "gfxSkipChars.h"
+#include "nsRect.h"
+
+#include "qcms.h"
+
+#include "mozilla/RefPtr.h"
+#include "GfxInfoCollector.h"
+
+#include "mozilla/layers/CompositorTypes.h"
+
+class gfxASurface;
+class gfxFont;
+class gfxFontGroup;
+struct gfxFontStyle;
+class gfxUserFontSet;
+class gfxFontEntry;
+class gfxPlatformFontList;
+class gfxTextRun;
+class nsIURI;
+class nsIAtom;
+class nsIObserver;
+class SRGBOverrideObserver;
+class gfxTextPerfMetrics;
+
+namespace mozilla {
+namespace gl {
+class SkiaGLGlue;
+} // namespace gl
+namespace gfx {
+class DrawTarget;
+class SourceSurface;
+class DataSourceSurface;
+class ScaledFont;
+class DrawEventRecorder;
+class VsyncSource;
+class ContentDeviceData;
+class GPUDeviceData;
+class FeatureState;
+
+inline uint32_t
+BackendTypeBit(BackendType b)
+{
+ return 1 << uint8_t(b);
+}
+
+} // namespace gfx
+} // namespace mozilla
+
+#define MOZ_PERFORMANCE_WARNING(module, ...) \
+ do { \
+ if (gfxPlatform::PerfWarnings()) { \
+ printf_stderr("[" module "] " __VA_ARGS__); \
+ } \
+ } while (0)
+
+enum eCMSMode {
+ eCMSMode_Off = 0, // No color management
+ eCMSMode_All = 1, // Color manage everything
+ eCMSMode_TaggedOnly = 2, // Color manage tagged Images Only
+ eCMSMode_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
+};
+
+// 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::NONE:
+ return "none";
+ case mozilla::gfx::BackendType::BACKEND_LAST:
+ return "invalid";
+ }
+ MOZ_CRASH("Incomplete switch");
+}
+
+enum class DeviceResetReason
+{
+ OK = 0,
+ HUNG,
+ REMOVED,
+ RESET,
+ DRIVER_ERROR,
+ INVALID_CALL,
+ OUT_OF_MEMORY,
+ FORCED_RESET,
+ UNKNOWN,
+ D3D9_RESET
+};
+
+enum class ForcedDeviceResetReason
+{
+ OPENSHAREDHANDLE = 0,
+ COMPOSITOR_UPDATED,
+};
+
+class gfxPlatform {
+ friend class SRGBOverrideObserver;
+
+public:
+ typedef mozilla::gfx::Color Color;
+ typedef mozilla::gfx::DataSourceSurface DataSourceSurface;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::IntSize IntSize;
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+ typedef mozilla::unicode::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();
+
+ static void InitLayersIPC();
+ static void ShutdownLayersIPC();
+
+ /**
+ * Initialize ScrollMetadata statics. Does not depend on gfxPlatform.
+ */
+ static void InitNullMetadata();
+
+ static void InitMoz2DLogging();
+
+ /**
+ * 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
+ * PluginInstanceChild (where we can't call gfxPlatform::GetPlatform()
+ * because the prefs service can only be accessed from 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(mozilla::gfx::DrawTarget *aTarget,
+ gfxASurface *aSurface,
+ bool aIsPlugin = false);
+
+ static void ClearSourceSurfaceForSurface(gfxASurface *aSurface);
+
+ static already_AddRefed<DataSourceSurface>
+ GetWrappedDataSourceSurface(gfxASurface *aSurface);
+
+ virtual already_AddRefed<mozilla::gfx::ScaledFont>
+ GetScaledFontForFont(mozilla::gfx::DrawTarget* aTarget, gfxFont *aFont);
+
+ already_AddRefed<DrawTarget>
+ CreateOffscreenContentDrawTarget(const mozilla::gfx::IntSize& aSize, mozilla::gfx::SurfaceFormat aFormat);
+
+ 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);
+
+ /**
+ * Returns true if rendering to data surfaces produces the same results as
+ * rendering to offscreen surfaces on this platform, making it safe to
+ * render content to data surfaces. This is generally false on platforms
+ * which use different backends for each type of DrawTarget.
+ */
+ virtual bool CanRenderContentToDataSurface() const {
+ return 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;
+ }
+
+ /// This function also lets us know if the current preferences/platform
+ /// combination allows for both accelerated and not accelerated canvas
+ /// implementations. If it does, and other relevant preferences are
+ /// asking for it, we will examine the commands in the first few seconds
+ /// of the canvas usage, and potentially change to accelerated or
+ /// non-accelerated canvas.
+ bool AllowOpenGLCanvas();
+ virtual void InitializeSkiaCacheLimits();
+
+ static bool AsyncPanZoomEnabled();
+
+ virtual void GetAzureBackendInfo(mozilla::widget::InfoObject &aObj) {
+ aObj.DefineProperty("AzureCanvasBackend", GetBackendName(mPreferredCanvasBackend));
+ aObj.DefineProperty("AzureCanvasAccelerated", AllowOpenGLCanvas());
+ aObj.DefineProperty("AzureFallbackCanvasBackend", GetBackendName(mFallbackCanvasBackend));
+ aObj.DefineProperty("AzureContentBackend", GetBackendName(mContentBackend));
+ }
+ void GetApzSupportInfo(mozilla::widget::InfoObject& aObj);
+ void GetTilesSupportInfo(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() {
+ return mContentBackend;
+ }
+
+ // 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;
+ }
+
+ mozilla::gfx::BackendType GetPreferredCanvasBackend() {
+ return mPreferredCanvasBackend;
+ }
+ mozilla::gfx::BackendType GetFallbackCanvasBackend() {
+ return mFallbackCanvasBackend;
+ }
+ /*
+ * Font bits
+ */
+
+ virtual void SetupClusterBoundaries(gfxTextRun *aTextRun, const char16_t *aString);
+
+ /**
+ * 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(nsIAtom *aLangGroup,
+ const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts);
+
+ /**
+ * Rebuilds the any cached system font lists
+ */
+ virtual nsresult UpdateFontList();
+
+ /**
+ * 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 gfxPlatformFontList *CreatePlatformFontList() {
+ NS_NOTREACHED("oops, this platform doesn't have a gfxPlatformFontList implementation");
+ return nullptr;
+ }
+
+ /**
+ * 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.
+ */
+ virtual nsresult GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName);
+
+ /**
+ * Create the appropriate platform font group
+ */
+ virtual gfxFontGroup*
+ CreateFontGroup(const mozilla::FontFamilyList& aFontFamilyList,
+ const gfxFontStyle *aStyle,
+ gfxTextPerfMetrics* aTextPerf,
+ gfxUserFontSet *aUserFontSet,
+ gfxFloat aDevToCssSize) = 0;
+
+ /**
+ * 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.
+ */
+ virtual gfxFontEntry* LookupLocalFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle);
+
+ /**
+ * 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.
+ */
+ virtual gfxFontEntry* MakePlatformFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ 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; in particular,
+ * B2G always has FontHintingEnabled FALSE, but RequiresLinearZoom
+ * is only true for the browser process, not Gaia or other apps.
+ *
+ * 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 or not (if unclear, returns true)
+ virtual bool IsFontFormatSupported(nsIURI *aFontURI, uint32_t aFormatFlags) { return false; }
+
+ 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*/, uint32_t /*aNextCh*/,
+ Script /*aRunScript*/,
+ nsTArray<const char*>& /*aFontList*/)
+ {
+ // platform-specific override, by default do nothing
+ }
+
+ // Are we in safe mode?
+ static bool InSafeMode();
+
+ static bool OffMainThreadCompositingEnabled();
+
+ void UpdateCanUseHardwareVideoDecoding();
+
+ // Returns a prioritized list of all available compositor backends.
+ void GetCompositorBackends(bool useAcceleration, nsTArray<mozilla::layers::LayersBackend>& aBackends);
+
+ /**
+ * Is it possible to use buffer rotation. Note that these
+ * check the preference, but also allow for the override to
+ * disable it using DisableBufferRotation.
+ */
+ static bool BufferRotationEnabled();
+ static void DisableBufferRotation();
+
+ /**
+ * Are we going to try color management?
+ */
+ static eCMSMode GetCMSMode();
+
+ /**
+ * 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.
+ *
+ * Sets 'out' to 'in' if transform is nullptr.
+ */
+ static void TransformPixel(const Color& in, Color& out, qcms_transform *transform);
+
+ /**
+ * Return the output device ICC profile.
+ */
+ static qcms_profile* GetCMSOutputProfile();
+
+ /**
+ * Return the sRGB ICC profile.
+ */
+ static qcms_profile* GetCMSsRGBProfile();
+
+ /**
+ * Return sRGB -> output device transform.
+ */
+ static qcms_transform* GetCMSRGBTransform();
+
+ /**
+ * Return output -> sRGB device transform.
+ */
+ static qcms_transform* GetCMSInverseRGBTransform();
+
+ /**
+ * Return sRGBA -> output device transform.
+ */
+ static qcms_transform* GetCMSRGBATransform();
+
+ virtual void FontsPrefsChanged(const char *aPref);
+
+ int32_t GetBidiNumeralOption();
+
+ static void
+ FlushFontAndWordCaches();
+
+ /**
+ * Returns a 1x1 surface that can be used to create graphics contexts
+ * for measuring text etc as if they will be rendered to the screen
+ */
+ gfxASurface* ScreenReferenceSurface() { return mScreenReferenceSurface; }
+
+ /**
+ * 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.
+ */
+ mozilla::gfx::DrawTarget* ScreenReferenceDrawTarget() { return mScreenReferenceDrawTarget; }
+
+ 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; }
+
+ /**
+ * Return the layer debugging options to use browser-wide.
+ */
+ mozilla::layers::DiagnosticTypes GetLayerDiagnosticTypes();
+
+ static mozilla::gfx::IntRect FrameCounterBounds() {
+ int bits = 16;
+ int sizeOfBit = 3;
+ return mozilla::gfx::IntRect(0, 0, bits * sizeOfBit, sizeOfBit);
+ }
+
+ mozilla::gl::SkiaGLGlue* GetSkiaGLGlue();
+ void PurgeSkiaGPUCache();
+ static void PurgeSkiaFontCache();
+
+ virtual bool IsInGonkEmulator() const { return false; }
+
+ static bool UsesOffMainThreadCompositing();
+
+ bool HasEnoughTotalSystemMemoryForSkiaGL();
+
+ /**
+ * Get the hardware vsync source for each platform.
+ * Should only exist and be valid on the parent process
+ */
+ virtual mozilla::gfx::VsyncSource* GetHardwareVsync() {
+ MOZ_ASSERT(mVsyncSource != nullptr);
+ MOZ_ASSERT(XRE_IsParentProcess());
+ return mVsyncSource;
+ }
+
+ /**
+ * 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 the software vsync rate to use.
+ */
+ static int GetSoftwareVsyncRate();
+
+ /**
+ * Returns whether or not a custom vsync rate is set.
+ */
+ static bool ForceSoftwareVsync();
+
+ /**
+ * Returns the default frame rate for the refresh driver / software vsync.
+ */
+ static int GetDefaultFrameRate();
+
+ /**
+ * Used to test which input types are handled via APZ.
+ */
+ virtual bool SupportsApzWheelInput() const {
+ return false;
+ }
+ virtual bool SupportsApzTouchInput() const {
+ return false;
+ }
+ bool SupportsApzDragInput() const;
+
+ virtual void FlushContentDrawing() {}
+
+ // 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 gfxPrefs::PerfWarnings().
+ * Extracted into a function to avoid including gfxPrefs.h from this file.
+ */
+ static bool PerfWarnings();
+
+ 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;
+ }
+
+ uint64_t GetDeviceCounter() const {
+ return mDeviceCounter;
+ }
+
+ /**
+ * 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; }
+
+ /**
+ * 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);
+
+protected:
+ gfxPlatform();
+ virtual ~gfxPlatform();
+
+ virtual void InitAcceleration();
+
+ /**
+ * Called immediately before deleting the gfxPlatform object.
+ */
+ virtual void WillShutdown();
+
+ /**
+ * Initialized hardware vsync based on each platform.
+ */
+ virtual already_AddRefed<mozilla::gfx::VsyncSource> CreateHardwareVsyncSource();
+
+ // Returns whether or not layers should be accelerated by default on this platform.
+ virtual bool AccelerateLayersByDefault();
+
+ // Returns a prioritized list of available compositor backends for acceleration.
+ virtual void GetAcceleratedCompositorBackends(nsTArray<mozilla::layers::LayersBackend>& aBackends);
+
+ /**
+ * 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(uint32_t aCanvasBitmask, mozilla::gfx::BackendType aCanvasDefault,
+ uint32_t aContentBitmask, mozilla::gfx::BackendType aContentDefault);
+
+ /**
+ * 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);
+
+ /**
+ * 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);
+
+ static already_AddRefed<mozilla::gfx::ScaledFont>
+ GetScaledFontForFontWithCairoSkia(mozilla::gfx::DrawTarget* aTarget, gfxFont* aFont);
+
+ virtual bool CanUseHardwareVideoDecoding();
+
+ int8_t mAllowDownloadableFonts;
+ int8_t mGraphiteShapingEnabled;
+ int8_t mOpenTypeSVGEnabled;
+
+ int8_t mBidiNumeralOption;
+
+ // whether to always search font cmaps globally
+ // when doing system font fallback
+ int8_t mFallbackUsesCmaps;
+
+ // max character limit for words in word cache
+ int32_t mWordCacheCharLimit;
+
+ // max number of entries in word cache
+ int32_t mWordCacheMaxEntries;
+
+ uint64_t mTotalSystemMemory;
+
+ // Hardware vsync source. Only valid on parent process
+ RefPtr<mozilla::gfx::VsyncSource> mVsyncSource;
+
+ RefPtr<mozilla::gfx::DrawTarget> mScreenReferenceDrawTarget;
+
+private:
+ /**
+ * Start up Thebes.
+ */
+ static void Init();
+
+ static void InitOpenGLConfig();
+ static void CreateCMSOutputProfile();
+
+ static void GetCMSOutputProfileData(void *&mem, size_t &size);
+
+ friend void RecordingPrefChanged(const char *aPrefName, void *aClosure);
+
+ virtual void GetPlatformCMSOutputProfile(void *&mem, size_t &size);
+
+ /**
+ * Calling this function will compute and set the ideal tile size for the
+ * platform. This will only have an effect in the parent process; child processes
+ * should be updated via SetTileSize to match the value computed in the parent.
+ */
+ void ComputeTileSize();
+
+ /**
+ * This uses nsIScreenManager to determine the screen size and color depth
+ */
+ void PopulateScreenInfo();
+
+ void InitCompositorAccelerationPrefs();
+ void InitGPUProcessPrefs();
+
+ RefPtr<gfxASurface> mScreenReferenceSurface;
+ nsCOMPtr<nsIObserver> mSRGBOverrideObserver;
+ nsCOMPtr<nsIObserver> mFontPrefsObserver;
+ nsCOMPtr<nsIObserver> 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;
+ // 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> mTilesInfoCollector;
+
+ RefPtr<mozilla::gfx::DrawEventRecorder> mRecorder;
+ RefPtr<mozilla::gl::SkiaGLGlue> mSkiaGlue;
+
+ // 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;
+
+ // Generation number for devices that ClientLayerManagers might depend on.
+ uint64_t mDeviceCounter;
+
+ // An instance of gfxSkipChars which is empty. It is used as the
+ // basis for error-case iterators.
+ const gfxSkipChars kEmptySkipChars;
+};
+
+#endif /* GFX_PLATFORM_H */
diff --git a/gfx/thebes/gfxPlatformFontList.cpp b/gfx/thebes/gfxPlatformFontList.cpp
new file mode 100644
index 000000000..01394db14
--- /dev/null
+++ b/gfx/thebes/gfxPlatformFontList.cpp
@@ -0,0 +1,1678 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxPlatformFontList.h"
+#include "gfxTextRun.h"
+#include "gfxUserFontSet.h"
+
+#include "nsCRT.h"
+#include "nsGkAtoms.h"
+#include "nsILocaleService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsUnicodeRange.h"
+#include "nsUnicodeProperties.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/gfx/2D.h"
+
+#include <locale.h>
+
+using namespace mozilla;
+
+#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, 0x06FF, { TRUETYPE_TAG('a','r','a','b'), 0, 0 } },
+ { 0x0700, 0x074F, { TRUETYPE_TAG('s','y','r','c'), 0, 0 } },
+ { 0x0750, 0x077F, { TRUETYPE_TAG('a','r','a','b'), 0, 0 } },
+ { 0x08A0, 0x08FF, { TRUETYPE_TAG('a','r','a','b'), 0, 0 } },
+ { 0x0900, 0x097F, { TRUETYPE_TAG('d','e','v','2'),
+ TRUETYPE_TAG('d','e','v','a'), 0 } },
+ { 0x0980, 0x09FF, { TRUETYPE_TAG('b','n','g','2'),
+ TRUETYPE_TAG('b','e','n','g'), 0 } },
+ { 0x0A00, 0x0A7F, { TRUETYPE_TAG('g','u','r','2'),
+ TRUETYPE_TAG('g','u','r','u'), 0 } },
+ { 0x0A80, 0x0AFF, { TRUETYPE_TAG('g','j','r','2'),
+ TRUETYPE_TAG('g','u','j','r'), 0 } },
+ { 0x0B00, 0x0B7F, { TRUETYPE_TAG('o','r','y','2'),
+ TRUETYPE_TAG('o','r','y','a'), 0 } },
+ { 0x0B80, 0x0BFF, { TRUETYPE_TAG('t','m','l','2'),
+ TRUETYPE_TAG('t','a','m','l'), 0 } },
+ { 0x0C00, 0x0C7F, { TRUETYPE_TAG('t','e','l','2'),
+ TRUETYPE_TAG('t','e','l','u'), 0 } },
+ { 0x0C80, 0x0CFF, { TRUETYPE_TAG('k','n','d','2'),
+ TRUETYPE_TAG('k','n','d','a'), 0 } },
+ { 0x0D00, 0x0D7F, { TRUETYPE_TAG('m','l','m','2'),
+ TRUETYPE_TAG('m','l','y','m'), 0 } },
+ { 0x0D80, 0x0DFF, { TRUETYPE_TAG('s','i','n','h'), 0, 0 } },
+ { 0x0E80, 0x0EFF, { TRUETYPE_TAG('l','a','o',' '), 0, 0 } },
+ { 0x0F00, 0x0FFF, { TRUETYPE_TAG('t','i','b','t'), 0, 0 } },
+ { 0x1000, 0x109f, { TRUETYPE_TAG('m','y','m','r'),
+ TRUETYPE_TAG('m','y','m','2'), 0 } },
+ { 0x1780, 0x17ff, { TRUETYPE_TAG('k','h','m','r'), 0, 0 } },
+ // Khmer Symbols (19e0..19ff) don't seem to need any special shaping
+ { 0xaa60, 0xaa7f, { 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 } } // terminator
+};
+
+// prefs for the font info loader
+#define FONT_LOADER_FAMILIES_PER_SLICE_PREF "gfx.font_loader.families_per_slice"
+#define FONT_LOADER_DELAY_PREF "gfx.font_loader.delay"
+#define FONT_LOADER_INTERVAL_PREF "gfx.font_loader.interval"
+
+static const char* kObservedPrefs[] = {
+ "font.",
+ "font.name-list.",
+ "intl.accept_languages", // hmmmm...
+ nullptr
+};
+
+static const char kFontSystemWhitelistPref[] = "font.system.whitelist";
+
+// 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() {}
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+};
+
+static gfxFontListPrefObserver* gFontListPrefObserver = nullptr;
+
+NS_IMPL_ISUPPORTS(gfxFontListPrefObserver, nsIObserver)
+
+NS_IMETHODIMP
+gfxFontListPrefObserver::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ NS_ASSERTION(!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID), "invalid topic");
+ // 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();
+ gfxFontCache::GetCache()->AgeAllGenerations();
+ 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;
+
+ 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.");
+ }
+
+ return NS_OK;
+}
+
+gfxPlatformFontList::gfxPlatformFontList(bool aNeedFullnamePostscriptNames)
+ : mFontFamilies(64), mOtherFamilyNames(16),
+ mBadUnderlineFamilyNames(8), mSharedCmaps(8),
+ mStartIndex(0), mIncrement(1), mNumFamilies(0), mFontlistInitCount(0),
+ mFontFamilyWhitelistActive(false)
+{
+ mOtherFamilyNamesInitialized = false;
+
+ if (aNeedFullnamePostscriptNames) {
+ mExtraNames = MakeUnique<ExtraNames>();
+ }
+ mFaceNameListsInitialized = false;
+
+ LoadBadUnderlineList();
+
+ // pref changes notification setup
+ NS_ASSERTION(!gFontListPrefObserver,
+ "There has been font list pref observer already");
+ gFontListPrefObserver = new gfxFontListPrefObserver();
+ NS_ADDREF(gFontListPrefObserver);
+ Preferences::AddStrongObservers(gFontListPrefObserver, kObservedPrefs);
+
+ Preferences::RegisterCallback(FontWhitelistPrefChanged,
+ kFontSystemWhitelistPref);
+
+ RegisterStrongMemoryReporter(new MemoryReporter());
+}
+
+gfxPlatformFontList::~gfxPlatformFontList()
+{
+ mSharedCmaps.Clear();
+ ClearLangGroupPrefFonts();
+ NS_ASSERTION(gFontListPrefObserver, "There is no font list pref observer");
+ Preferences::RemoveObservers(gFontListPrefObserver, kObservedPrefs);
+ Preferences::UnregisterCallback(FontWhitelistPrefChanged,
+ kFontSystemWhitelistPref);
+ NS_RELEASE(gFontListPrefObserver);
+}
+
+// number of CSS generic font families
+const uint32_t kNumGenerics = 5;
+
+void
+gfxPlatformFontList::ApplyWhitelist()
+{
+ nsTArray<nsString> list;
+ gfxFontUtils::GetPrefsFontList(kFontSystemWhitelistPref, list);
+ uint32_t numFonts = list.Length();
+ mFontFamilyWhitelistActive = (numFonts > 0);
+ if (!mFontFamilyWhitelistActive) {
+ return;
+ }
+ nsTHashtable<nsStringHashKey> familyNamesWhitelist;
+ for (uint32_t i = 0; i < numFonts; i++) {
+ nsString key;
+ ToLowerCase(list[i], key);
+ familyNamesWhitelist.PutEntry(key);
+ }
+ for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
+ // Don't continue if we only have one font left.
+ if (mFontFamilies.Count() == 1) {
+ break;
+ }
+ nsString fontFamilyName(iter.Key());
+ ToLowerCase(fontFamilyName);
+ if (!familyNamesWhitelist.Contains(fontFamilyName)) {
+ iter.Remove();
+ }
+ }
+}
+
+nsresult
+gfxPlatformFontList::InitFontList()
+{
+ mFontlistInitCount++;
+
+ if (LOG_FONTINIT_ENABLED()) {
+ LOG_FONTINIT(("(fontinit) system fontlist initialization\n"));
+ }
+
+ // rebuilding fontlist so clear out font/word caches
+ gfxFontCache *fontCache = gfxFontCache::GetCache();
+ if (fontCache) {
+ fontCache->AgeAllGenerations();
+ fontCache->FlushShapedWordCaches();
+ }
+
+ gfxPlatform::PurgeSkiaFontCache();
+
+ mFontFamilies.Clear();
+ mOtherFamilyNames.Clear();
+ mOtherFamilyNamesInitialized = false;
+ if (mExtraNames) {
+ mExtraNames->mFullnames.Clear();
+ mExtraNames->mPostscriptNames.Clear();
+ }
+ mFaceNameListsInitialized = false;
+ ClearLangGroupPrefFonts();
+ mReplacementCharFallbackFamily = nullptr;
+ CancelLoader();
+
+ // initialize ranges of characters for which system-wide font search should be skipped
+ mCodepointsWithNoFonts.reset();
+ mCodepointsWithNoFonts.SetRange(0,0x1f); // C0 controls
+ mCodepointsWithNoFonts.SetRange(0x7f,0x9f); // C1 controls
+
+ sPlatformFontList = this;
+
+ nsresult rv = InitFontListForPlatform();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ ApplyWhitelist();
+ return NS_OK;
+}
+
+void
+gfxPlatformFontList::GenerateFontListKey(const nsAString& aKeyName, nsAString& aResult)
+{
+ aResult = aKeyName;
+ ToLowerCase(aResult);
+}
+
+#define OTHERNAMES_TIMEOUT 200
+
+void
+gfxPlatformFontList::InitOtherFamilyNames()
+{
+ if (mOtherFamilyNamesInitialized) {
+ return;
+ }
+
+ TimeStamp start = TimeStamp::Now();
+ bool timedOut = false;
+
+ for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<gfxFontFamily>& family = iter.Data();
+ family->ReadOtherFamilyNames(this);
+ TimeDuration elapsed = TimeStamp::Now() - start;
+ if (elapsed.ToMilliseconds() > OTHERNAMES_TIMEOUT) {
+ timedOut = true;
+ break;
+ }
+ }
+
+ if (!timedOut) {
+ mOtherFamilyNamesInitialized = true;
+ }
+ 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" : "")));
+ }
+}
+
+// time limit for loading facename lists (ms)
+#define NAMELIST_TIMEOUT 200
+
+gfxFontEntry*
+gfxPlatformFontList::SearchFamiliesForFaceName(const nsAString& 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 (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
+ nsStringHashKey::KeyType key = iter.Key();
+ RefPtr<gfxFontFamily>& family = iter.Data();
+
+ // 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 nsAString& 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 nsAString& 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<nsTHashtable<nsStringHashKey>>(2);
+ }
+ mFaceNamesMissed->PutEntry(aFaceName);
+ }
+ }
+
+ return lookup;
+}
+
+void
+gfxPlatformFontList::PreloadNamesList()
+{
+ AutoTArray<nsString, 10> preloadFonts;
+ gfxFontUtils::GetPrefsFontList("font.preload-names-list", preloadFonts);
+
+ uint32_t numFonts = preloadFonts.Length();
+ for (uint32_t i = 0; i < numFonts; i++) {
+ nsAutoString key;
+ GenerateFontListKey(preloadFonts[i], key);
+
+ // only search canonical names!
+ gfxFontFamily *familyEntry = mFontFamilies.GetWeak(key);
+ if (familyEntry) {
+ familyEntry->ReadOtherFamilyNames(this);
+ }
+ }
+
+}
+
+void
+gfxPlatformFontList::LoadBadUnderlineList()
+{
+ AutoTArray<nsString, 10> blacklist;
+ gfxFontUtils::GetPrefsFontList("font.blacklist.underline_offset", blacklist);
+ uint32_t numFonts = blacklist.Length();
+ for (uint32_t i = 0; i < numFonts; i++) {
+ nsAutoString key;
+ GenerateFontListKey(blacklist[i], key);
+ mBadUnderlineFamilyNames.PutEntry(key);
+ }
+}
+
+void
+gfxPlatformFontList::UpdateFontList()
+{
+ InitFontList();
+ RebuildLocalFonts();
+}
+
+void
+gfxPlatformFontList::GetFontList(nsIAtom *aLangGroup,
+ const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts)
+{
+ for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<gfxFontFamily>& family = iter.Data();
+ // use the first variation for now. This data should be the same
+ // for all the variations and should probably be moved up to
+ // the Family
+ gfxFontStyle style;
+ style.language = aLangGroup;
+ bool needsBold;
+ RefPtr<gfxFontEntry> fontEntry = family->FindFontForStyle(style, needsBold);
+ NS_ASSERTION(fontEntry, "couldn't find any font entry in family");
+ if (!fontEntry) {
+ continue;
+ }
+
+ /* skip symbol fonts */
+ if (fontEntry->IsSymbolFont()) {
+ continue;
+ }
+
+ if (fontEntry->SupportsLangGroup(aLangGroup) &&
+ fontEntry->MatchesGenericFamily(aGenericFamily)) {
+ nsAutoString localizedFamilyName;
+ family->LocalizedName(localizedFamilyName);
+ aListOfFonts.AppendElement(localizedFamilyName);
+ }
+ }
+
+ aListOfFonts.Sort();
+ aListOfFonts.Compact();
+}
+
+void
+gfxPlatformFontList::GetFontFamilyList(nsTArray<RefPtr<gfxFontFamily> >& aFamilyArray)
+{
+ for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<gfxFontFamily>& family = iter.Data();
+ aFamilyArray.AppendElement(family);
+ }
+}
+
+gfxFontEntry*
+gfxPlatformFontList::SystemFindFontForChar(uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript,
+ const gfxFontStyle* aStyle)
+ {
+ gfxFontEntry* fontEntry = nullptr;
+
+ // is codepoint with no matching font? return null immediately
+ if (mCodepointsWithNoFonts.test(aCh)) {
+ return nullptr;
+ }
+
+ // 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 && mReplacementCharFallbackFamily) {
+ bool needsBold; // ignored in the system fallback case
+
+ fontEntry =
+ mReplacementCharFallbackFamily->FindFontForStyle(*aStyle,
+ needsBold);
+
+ // 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;
+ }
+ }
+
+ TimeStamp start = TimeStamp::Now();
+
+ // search commonly available fonts
+ bool common = true;
+ gfxFontFamily *fallbackFamily = nullptr;
+ fontEntry = CommonFontFallback(aCh, aNextCh, aRunScript, aStyle,
+ &fallbackFamily);
+
+ // if didn't find a font, do system-wide fallback (except for specials)
+ uint32_t cmapCount = 0;
+ if (!fontEntry) {
+ common = false;
+ fontEntry = GlobalFontFallback(aCh, aRunScript, aStyle, cmapCount,
+ &fallbackFamily);
+ }
+ TimeDuration elapsed = TimeStamp::Now() - start;
+
+ LogModule* log = gfxPlatform::GetLog(eGfxLog_textrun);
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Warning))) {
+ uint32_t unicodeRange = FindCharUnicodeRange(aCh);
+ Script script = mozilla::unicode::GetScriptCode(aCh);
+ MOZ_LOG(log, LogLevel::Warning,\
+ ("(textrun-systemfallback-%s) char: u+%6.6x "
+ "unicode-range: %d script: %d match: [%s]"
+ " time: %dus cmaps: %d\n",
+ (common ? "common" : "global"), aCh,
+ unicodeRange, script,
+ (fontEntry ? NS_ConvertUTF16toUTF8(fontEntry->Name()).get() :
+ "<none>"),
+ int32_t(elapsed.ToMicroseconds()),
+ cmapCount));
+ }
+
+ // no match? add to set of non-matching codepoints
+ if (!fontEntry) {
+ mCodepointsWithNoFonts.set(aCh);
+ } else if (aCh == 0xFFFD && fontEntry && fallbackFamily) {
+ mReplacementCharFallbackFamily = 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 fontEntry;
+}
+
+#define NUM_FALLBACK_FONTS 8
+
+gfxFontEntry*
+gfxPlatformFontList::CommonFontFallback(uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript,
+ const gfxFontStyle* aMatchStyle,
+ gfxFontFamily** aMatchedFamily)
+{
+ AutoTArray<const char*,NUM_FALLBACK_FONTS> defaultFallbacks;
+ uint32_t i, numFallbacks;
+
+ gfxPlatform::GetPlatform()->GetCommonFallbackFonts(aCh, aNextCh,
+ aRunScript,
+ defaultFallbacks);
+ numFallbacks = defaultFallbacks.Length();
+ for (i = 0; i < numFallbacks; i++) {
+ nsAutoString familyName;
+ const char *fallbackFamily = defaultFallbacks[i];
+
+ familyName.AppendASCII(fallbackFamily);
+ gfxFontFamily *fallback = FindFamilyByCanonicalName(familyName);
+ if (!fallback)
+ continue;
+
+ gfxFontEntry *fontEntry;
+ bool needsBold; // ignored in the system fallback case
+
+ // use first font in list that supports a given character
+ fontEntry = fallback->FindFontForStyle(*aMatchStyle, needsBold);
+ if (fontEntry && fontEntry->HasCharacter(aCh)) {
+ *aMatchedFamily = fallback;
+ return fontEntry;
+ }
+ }
+
+ return nullptr;
+}
+
+gfxFontEntry*
+gfxPlatformFontList::GlobalFontFallback(const uint32_t aCh,
+ Script aRunScript,
+ const gfxFontStyle* aMatchStyle,
+ uint32_t& aCmapCount,
+ gfxFontFamily** aMatchedFamily)
+{
+ bool useCmaps = IsFontFamilyWhitelistActive() ||
+ gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback();
+ if (!useCmaps) {
+ // Allow platform-specific fallback code to try and find a usable font
+ gfxFontEntry* fe =
+ PlatformGlobalFontFallback(aCh, aRunScript, aMatchStyle,
+ aMatchedFamily);
+ if (fe) {
+ return fe;
+ }
+ }
+
+ // otherwise, try to find it among local fonts
+ GlobalFontMatch data(aCh, aRunScript, aMatchStyle);
+
+ // iterate over all font families to find a font that support the character
+ for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<gfxFontFamily>& family = iter.Data();
+ // evaluate all fonts in this family for a match
+ family->FindFontForChar(&data);
+ }
+
+ aCmapCount = data.mCmapsTested;
+ *aMatchedFamily = data.mMatchedFamily;
+
+ return data.mBestMatch;
+}
+
+gfxFontFamily*
+gfxPlatformFontList::CheckFamily(gfxFontFamily *aFamily)
+{
+ if (aFamily && !aFamily->HasStyles()) {
+ aFamily->FindStyleVariations();
+ aFamily->CheckForSimpleFamily();
+ }
+
+ if (aFamily && aFamily->GetFontList().Length() == 0) {
+ // failed to load any faces for this family, so discard it
+ nsAutoString key;
+ GenerateFontListKey(aFamily->Name(), key);
+ mFontFamilies.Remove(key);
+ return nullptr;
+ }
+
+ return aFamily;
+}
+
+bool
+gfxPlatformFontList::FindAndAddFamilies(const nsAString& aFamily,
+ nsTArray<gfxFontFamily*>* aOutput,
+ gfxFontStyle* aStyle,
+ gfxFloat aDevToCssSize)
+{
+ nsAutoString key;
+ GenerateFontListKey(aFamily, key);
+
+ NS_ASSERTION(mFontFamilies.Count() != 0, "system font list was not initialized correctly");
+
+ // lookup in canonical (i.e. English) family name list
+ gfxFontFamily *familyEntry = mFontFamilies.GetWeak(key);
+
+ // if not found, lookup in other family names list (mostly localized names)
+ if (!familyEntry) {
+ familyEntry = mOtherFamilyNames.GetWeak(key);
+ }
+
+ // 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();
+ familyEntry = mOtherFamilyNames.GetWeak(key);
+ if (!familyEntry && !mOtherFamilyNamesInitialized) {
+ // localized family names load timed out, add name to list of
+ // names to check after localized names are loaded
+ if (!mOtherNamesMissed) {
+ mOtherNamesMissed = MakeUnique<nsTHashtable<nsStringHashKey>>(2);
+ }
+ mOtherNamesMissed->PutEntry(key);
+ }
+ }
+
+ familyEntry = CheckFamily(familyEntry);
+ if (familyEntry) {
+ aOutput->AppendElement(familyEntry);
+ return true;
+ }
+
+ return false;
+}
+
+gfxFontEntry*
+gfxPlatformFontList::FindFontForFamily(const nsAString& aFamily, const gfxFontStyle* aStyle, bool& aNeedsBold)
+{
+ gfxFontFamily *familyEntry = FindFamily(aFamily);
+
+ aNeedsBold = false;
+
+ if (familyEntry)
+ return familyEntry->FindFontForStyle(*aStyle, aNeedsBold);
+
+ return nullptr;
+}
+
+void
+gfxPlatformFontList::AddOtherFamilyName(gfxFontFamily *aFamilyEntry, nsAString& aOtherFamilyName)
+{
+ nsAutoString key;
+ GenerateFontListKey(aOtherFamilyName, key);
+
+ if (!mOtherFamilyNames.GetWeak(key)) {
+ mOtherFamilyNames.Put(key, aFamilyEntry);
+ LOG_FONTLIST(("(fontlist-otherfamily) canonical family: %s, "
+ "other family: %s\n",
+ NS_ConvertUTF16toUTF8(aFamilyEntry->Name()).get(),
+ NS_ConvertUTF16toUTF8(aOtherFamilyName).get()));
+ if (mBadUnderlineFamilyNames.Contains(key))
+ aFamilyEntry->SetBadUnderlineFamily();
+ }
+}
+
+void
+gfxPlatformFontList::AddFullname(gfxFontEntry *aFontEntry, nsAString& aFullname)
+{
+ if (!mExtraNames->mFullnames.GetWeak(aFullname)) {
+ mExtraNames->mFullnames.Put(aFullname, aFontEntry);
+ LOG_FONTLIST(("(fontlist-fullname) name: %s, fullname: %s\n",
+ NS_ConvertUTF16toUTF8(aFontEntry->Name()).get(),
+ NS_ConvertUTF16toUTF8(aFullname).get()));
+ }
+}
+
+void
+gfxPlatformFontList::AddPostscriptName(gfxFontEntry *aFontEntry, nsAString& aPostscriptName)
+{
+ if (!mExtraNames->mPostscriptNames.GetWeak(aPostscriptName)) {
+ mExtraNames->mPostscriptNames.Put(aPostscriptName, aFontEntry);
+ LOG_FONTLIST(("(fontlist-postscript) name: %s, psname: %s\n",
+ NS_ConvertUTF16toUTF8(aFontEntry->Name()).get(),
+ NS_ConvertUTF16toUTF8(aPostscriptName).get()));
+ }
+}
+
+bool
+gfxPlatformFontList::GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName)
+{
+ aFamilyName.Truncate();
+ gfxFontFamily *ff = FindFamily(aFontName);
+ if (!ff) {
+ return false;
+ }
+ aFamilyName.Assign(ff->Name());
+ return true;
+}
+
+gfxCharacterMap*
+gfxPlatformFontList::FindCharMap(gfxCharacterMap *aCmap)
+{
+ aCmap->CalcHash();
+ gfxCharacterMap *cmap = AddCmap(aCmap);
+ cmap->mShared = true;
+ return cmap;
+}
+
+// add a cmap to the shared cmap set
+gfxCharacterMap*
+gfxPlatformFontList::AddCmap(const gfxCharacterMap* aCharMap)
+{
+ CharMapHashKey *found =
+ mSharedCmaps.PutEntry(const_cast<gfxCharacterMap*>(aCharMap));
+ return found->GetKey();
+}
+
+// remove the cmap from the shared cmap set
+void
+gfxPlatformFontList::RemoveCmap(const gfxCharacterMap* aCharMap)
+{
+ // skip lookups during teardown
+ if (mSharedCmaps.Count() == 0) {
+ return;
+ }
+
+ // cmap needs to match the entry *and* be the same ptr before removing
+ CharMapHashKey *found =
+ mSharedCmaps.GetEntry(const_cast<gfxCharacterMap*>(aCharMap));
+ if (found && found->GetKey() == aCharMap) {
+ mSharedCmaps.RemoveEntry(const_cast<gfxCharacterMap*>(aCharMap));
+ }
+}
+
+void
+gfxPlatformFontList::ResolveGenericFontNames(
+ FontFamilyType aGenericType,
+ eFontPrefLang aPrefLang,
+ nsTArray<RefPtr<gfxFontFamily>>* aGenericFamilies)
+{
+ const char* langGroupStr = GetPrefLangName(aPrefLang);
+ const char* generic = GetGenericName(aGenericType);
+
+ if (!generic) {
+ return;
+ }
+
+ AutoTArray<nsString,4> genericFamilies;
+
+ // load family for "font.name.generic.lang"
+ nsAutoCString prefFontName("font.name.");
+ prefFontName.Append(generic);
+ prefFontName.Append('.');
+ prefFontName.Append(langGroupStr);
+ gfxFontUtils::AppendPrefsFontList(prefFontName.get(), genericFamilies);
+
+ // load fonts for "font.name-list.generic.lang"
+ nsAutoCString prefFontListName("font.name-list.");
+ prefFontListName.Append(generic);
+ prefFontListName.Append('.');
+ prefFontListName.Append(langGroupStr);
+ gfxFontUtils::AppendPrefsFontList(prefFontListName.get(), genericFamilies);
+
+ nsIAtom* langGroup = GetLangGroupForPrefLang(aPrefLang);
+ NS_ASSERTION(langGroup, "null lang group for pref lang");
+
+ // lookup and add platform fonts uniquely
+ for (const nsString& genericFamily : genericFamilies) {
+ gfxFontStyle style;
+ style.language = langGroup;
+ style.systemFont = false;
+ AutoTArray<gfxFontFamily*,10> families;
+ FindAndAddFamilies(genericFamily, &families, &style);
+ for (gfxFontFamily* f : families) {
+ if (!aGenericFamilies->Contains(f)) {
+ aGenericFamilies->AppendElement(f);
+ }
+ }
+ }
+
+#if 0 // dump out generic mappings
+ printf("%s ===> ", prefFontName.get());
+ for (uint32_t k = 0; k < aGenericFamilies->Length(); k++) {
+ if (k > 0) printf(", ");
+ printf("%s", NS_ConvertUTF16toUTF8(aGenericFamilies[k]->Name()).get());
+ }
+ printf("\n");
+#endif
+}
+
+nsTArray<RefPtr<gfxFontFamily>>*
+gfxPlatformFontList::GetPrefFontsLangGroup(mozilla::FontFamilyType aGenericType,
+ eFontPrefLang aPrefLang)
+{
+ // treat -moz-fixed as monospace
+ if (aGenericType == eFamily_moz_fixed) {
+ aGenericType = eFamily_monospace;
+ }
+
+ PrefFontList* prefFonts =
+ mLangGroupPrefFonts[aPrefLang][aGenericType].get();
+ if (MOZ_UNLIKELY(!prefFonts)) {
+ prefFonts = new PrefFontList;
+ ResolveGenericFontNames(aGenericType, aPrefLang, prefFonts);
+ mLangGroupPrefFonts[aPrefLang][aGenericType].reset(prefFonts);
+ }
+ return prefFonts;
+}
+
+void
+gfxPlatformFontList::AddGenericFonts(mozilla::FontFamilyType aGenericType,
+ nsIAtom* aLanguage,
+ nsTArray<gfxFontFamily*>& aFamilyList)
+{
+ // map lang ==> langGroup
+ nsIAtom* langGroup = GetLangGroup(aLanguage);
+
+ // langGroup ==> prefLang
+ eFontPrefLang prefLang = GetFontPrefLangFor(langGroup);
+
+ // lookup pref fonts
+ nsTArray<RefPtr<gfxFontFamily>>* prefFonts =
+ GetPrefFontsLangGroup(aGenericType, prefLang);
+
+ if (!prefFonts->IsEmpty()) {
+ aFamilyList.AppendElements(*prefFonts);
+ }
+}
+
+static nsIAtom* PrefLangToLangGroups(uint32_t aIndex)
+{
+ // static array here avoids static constructor
+ static nsIAtom* 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 (!PL_strcasecmp(gPrefLangNames[i], aLang)) {
+ return eFontPrefLang(i);
+ }
+ }
+ return eFontPrefLang_Others;
+}
+
+eFontPrefLang
+gfxPlatformFontList::GetFontPrefLangFor(nsIAtom *aLang)
+{
+ if (!aLang)
+ return eFontPrefLang_Others;
+ nsAutoCString lang;
+ aLang->ToUTF8String(lang);
+ return GetFontPrefLangFor(lang.get());
+}
+
+nsIAtom*
+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(uint8_t aUnicodeRange)
+{
+ switch (aUnicodeRange) {
+ case kRangeSetLatin: return eFontPrefLang_Western;
+ case kRangeCyrillic: return eFontPrefLang_Cyrillic;
+ case kRangeGreek: return eFontPrefLang_Greek;
+ case kRangeHebrew: return eFontPrefLang_Hebrew;
+ case kRangeArabic: return eFontPrefLang_Arabic;
+ case kRangeThai: return eFontPrefLang_Thai;
+ case kRangeKorean: return eFontPrefLang_Korean;
+ case kRangeJapanese: return eFontPrefLang_Japanese;
+ case kRangeSChinese: return eFontPrefLang_ChineseCN;
+ case kRangeTChinese: return eFontPrefLang_ChineseTW;
+ case kRangeDevanagari: return eFontPrefLang_Devanagari;
+ case kRangeTamil: return eFontPrefLang_Tamil;
+ case kRangeArmenian: return eFontPrefLang_Armenian;
+ case kRangeBengali: return eFontPrefLang_Bengali;
+ case kRangeCanadian: return eFontPrefLang_Canadian;
+ case kRangeEthiopic: return eFontPrefLang_Ethiopic;
+ case kRangeGeorgian: return eFontPrefLang_Georgian;
+ case kRangeGujarati: return eFontPrefLang_Gujarati;
+ case kRangeGurmukhi: return eFontPrefLang_Gurmukhi;
+ case kRangeKhmer: return eFontPrefLang_Khmer;
+ case kRangeMalayalam: return eFontPrefLang_Malayalam;
+ case kRangeOriya: return eFontPrefLang_Oriya;
+ case kRangeTelugu: return eFontPrefLang_Telugu;
+ case kRangeKannada: return eFontPrefLang_Kannada;
+ case kRangeSinhala: return eFontPrefLang_Sinhala;
+ case kRangeTibetan: return eFontPrefLang_Tibetan;
+ case kRangeSetCJK: return eFontPrefLang_CJKSet;
+ 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)
+{
+ 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
+ nsAdoptingCString list = Preferences::GetLocalizedCString("intl.accept_languages");
+ if (!list.IsEmpty()) {
+ const char kComma = ',';
+ const char *p, *p_end;
+ list.BeginReading(p);
+ list.EndReading(p_end);
+ while (p < p_end) {
+ while (nsCRT::IsAsciiSpace(*p)) {
+ if (++p == p_end)
+ break;
+ }
+ if (p == p_end)
+ break;
+ const char *start = p;
+ while (++p != p_end && *p != kComma)
+ /* nothing */ ;
+ nsAutoCString lang(Substring(start, p));
+ lang.CompressWhitespace(false, true);
+ eFontPrefLang fpl = gfxPlatformFontList::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;
+ }
+ p++;
+ }
+ }
+
+ do { // to allow 'break' to abort this block if a call fails
+ nsresult rv;
+ nsCOMPtr<nsILocaleService> ls =
+ do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ break;
+
+ nsCOMPtr<nsILocale> appLocale;
+ rv = ls->GetApplicationLocale(getter_AddRefs(appLocale));
+ if (NS_FAILED(rv))
+ break;
+
+ nsString localeStr;
+ rv = appLocale->
+ GetCategory(NS_LITERAL_STRING(NSILOCALE_MESSAGE), localeStr);
+ if (NS_FAILED(rv))
+ break;
+
+ const nsAString& lang = Substring(localeStr, 0, 2);
+ if (lang.EqualsLiteral("ja")) {
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Japanese);
+ } else if (lang.EqualsLiteral("zh")) {
+ const nsAString& region = Substring(localeStr, 3, 2);
+ if (region.EqualsLiteral("CN")) {
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseCN);
+ } else if (region.EqualsLiteral("TW")) {
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseTW);
+ } else if (region.EqualsLiteral("HK")) {
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseHK);
+ }
+ } else if (lang.EqualsLiteral("ko")) {
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Korean);
+ }
+ } while (0);
+
+ // last resort... (the order is same as old gfx.)
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Japanese);
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_Korean);
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseCN);
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseHK);
+ AppendPrefLang(tempPrefLangs, tempLen, eFontPrefLang_ChineseTW);
+
+ // copy into the cached array
+ uint32_t j;
+ for (j = 0; j < tempLen; j++) {
+ mCJKPrefLangs.AppendElement(tempPrefLangs[j]);
+ }
+ }
+
+ // append in cached CJK langs
+ uint32_t i, numCJKlangs = mCJKPrefLangs.Length();
+
+ for (i = 0; i < numCJKlangs; i++) {
+ AppendPrefLang(aPrefLangs, aLen, (eFontPrefLang) (mCJKPrefLangs[i]));
+ }
+
+}
+
+void
+gfxPlatformFontList::AppendPrefLang(eFontPrefLang aPrefLangs[], uint32_t& aLen, eFontPrefLang aAddLang)
+{
+ if (aLen >= kMaxLenPrefLangList) return;
+
+ // make sure
+ uint32_t i = 0;
+ while (i < aLen && aPrefLangs[i] != aAddLang) {
+ i++;
+ }
+
+ if (i == aLen) {
+ aPrefLangs[aLen] = aAddLang;
+ aLen++;
+ }
+}
+
+mozilla::FontFamilyType
+gfxPlatformFontList::GetDefaultGeneric(eFontPrefLang aLang)
+{
+ // initialize lang group pref font defaults (i.e. serif/sans-serif)
+ if (MOZ_UNLIKELY(mDefaultGenericsLangGroup.IsEmpty())) {
+ mDefaultGenericsLangGroup.AppendElements(ArrayLength(gPrefLangNames));
+ for (uint32_t i = 0; i < ArrayLength(gPrefLangNames); i++) {
+ nsAutoCString prefDefaultFontType("font.default.");
+ prefDefaultFontType.Append(GetPrefLangName(eFontPrefLang(i)));
+ nsAdoptingCString serifOrSans =
+ Preferences::GetCString(prefDefaultFontType.get());
+ if (serifOrSans.EqualsLiteral("sans-serif")) {
+ mDefaultGenericsLangGroup[i] = eFamily_sans_serif;
+ } else {
+ mDefaultGenericsLangGroup[i] = eFamily_serif;
+ }
+ }
+ }
+
+ if (uint32_t(aLang) < ArrayLength(gPrefLangNames)) {
+ return mDefaultGenericsLangGroup[uint32_t(aLang)];
+ }
+ return eFamily_serif;
+}
+
+
+gfxFontFamily*
+gfxPlatformFontList::GetDefaultFont(const gfxFontStyle* aStyle)
+{
+ gfxFontFamily* family = GetDefaultFontForPlatform(aStyle);
+ if (family) {
+ 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 listed in
+ // mFontFamilies.
+ return mFontFamilies.Iter().Data();
+}
+
+void
+gfxPlatformFontList::GetFontFamilyNames(nsTArray<nsString>& aFontFamilyNames)
+{
+ for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<gfxFontFamily>& family = iter.Data();
+ aFontFamilyNames.AppendElement(family->Name());
+ }
+}
+
+nsILanguageAtomService*
+gfxPlatformFontList::GetLangService()
+{
+ if (!mLangService) {
+ mLangService = do_GetService(NS_LANGUAGEATOMSERVICE_CONTRACTID);
+ }
+ NS_ASSERTION(mLangService, "no language service!");
+ return mLangService;
+}
+
+nsIAtom*
+gfxPlatformFontList::GetLangGroup(nsIAtom* aLanguage)
+{
+ // map lang ==> langGroup
+ nsIAtom *langGroup = nullptr;
+ if (aLanguage) {
+ nsresult rv;
+ nsILanguageAtomService* langService = GetLangService();
+ langGroup = langService->GetLanguageGroup(aLanguage, &rv);
+ }
+ if (!langGroup) {
+ langGroup = nsGkAtoms::Unicode;
+ }
+ return langGroup;
+}
+
+/* static */ const char*
+gfxPlatformFontList::GetGenericName(FontFamilyType aGenericType)
+{
+ static const char kGeneric_serif[] = "serif";
+ static const char kGeneric_sans_serif[] = "sans-serif";
+ static const char kGeneric_monospace[] = "monospace";
+ static const char kGeneric_cursive[] = "cursive";
+ static const char kGeneric_fantasy[] = "fantasy";
+
+ // type should be standard generic type at this point
+ NS_ASSERTION(aGenericType >= eFamily_serif &&
+ aGenericType <= eFamily_fantasy,
+ "standard generic font family type required");
+
+ // map generic type to string
+ const char *generic = nullptr;
+ switch (aGenericType) {
+ case eFamily_serif:
+ generic = kGeneric_serif;
+ break;
+ case eFamily_sans_serif:
+ generic = kGeneric_sans_serif;
+ break;
+ case eFamily_monospace:
+ generic = kGeneric_monospace;
+ break;
+ case eFamily_cursive:
+ generic = kGeneric_cursive;
+ break;
+ case eFamily_fantasy:
+ generic = kGeneric_fantasy;
+ break;
+ default:
+ break;
+ }
+
+ return generic;
+}
+
+// mapping of moz lang groups ==> default lang
+struct MozLangGroupData {
+ nsIAtom* 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
+gfxPlatformFontList::TryLangForGroup(const nsACString& aOSLang,
+ nsIAtom* aLangGroup,
+ nsACString& aFcLang)
+{
+ // 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".
+ // nsILanguageAtomService takes languages in the form language-subtag,
+ // where subtag may be a territory. fontconfig and nsILanguageAtomService
+ // 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;
+ }
+
+ nsILanguageAtomService* langService = GetLangService();
+ nsIAtom *atom = langService->LookupLanguage(aFcLang);
+ return atom == aLangGroup;
+}
+
+void
+gfxPlatformFontList::GetSampleLangForGroup(nsIAtom* aLanguage,
+ nsACString& aLangStr,
+ bool aCheckEnvironment)
+{
+ 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.
+ if (aCheckEnvironment) {
+ 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))
+ return;
+
+ if (*pos == '\0')
+ break;
+
+ languages = pos + 1;
+ }
+ }
+ }
+ const char *ctype = setlocale(LC_CTYPE, nullptr);
+ if (ctype &&
+ TryLangForGroup(nsDependentCString(ctype), aLanguage, aLangStr)) {
+ return;
+ }
+ }
+
+ if (mozLangGroup->defaultLang) {
+ aLangStr.Assign(mozLangGroup->defaultLang);
+ } else {
+ aLangStr.Truncate();
+ }
+}
+
+void
+gfxPlatformFontList::InitLoader()
+{
+ GetFontFamilyNames(mFontInfo->mFontFamiliesToLoad);
+ mStartIndex = 0;
+ mNumFamilies = mFontInfo->mFontFamiliesToLoad.Length();
+ memset(&(mFontInfo->mLoadStats), 0, sizeof(mFontInfo->mLoadStats));
+}
+
+#define FONT_LOADER_MAX_TIMESLICE 100 // max time for one pass through RunLoader = 100ms
+
+bool
+gfxPlatformFontList::LoadFontInfo()
+{
+ TimeStamp start = TimeStamp::Now();
+ uint32_t i, endIndex = mNumFamilies;
+ bool loadCmaps = !UsesSystemFallback() ||
+ gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback();
+
+ // for each font family, load in various font info
+ for (i = mStartIndex; i < endIndex; i++) {
+ nsAutoString key;
+ gfxFontFamily *familyEntry;
+ GenerateFontListKey(mFontInfo->mFontFamiliesToLoad[i], key);
+
+ // lookup in canonical (i.e. English) family name list
+ if (!(familyEntry = mFontFamilies.GetWeak(key))) {
+ 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
+ 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;
+ mFaceNameListsInitialized = true;
+ }
+
+ return done;
+}
+
+void
+gfxPlatformFontList::CleanupLoader()
+{
+ mFontFamiliesToLoad.Clear();
+ mNumFamilies = 0;
+ bool rebuilt = false, forceReflow = false;
+
+ // if had missed face names that are now available, force reflow all
+ if (mFaceNamesMissed) {
+ for (auto it = mFaceNamesMissed->Iter(); !it.Done(); it.Next()) {
+ if (FindFaceName(it.Get()->GetKey())) {
+ rebuilt = true;
+ RebuildLocalFonts();
+ break;
+ }
+ }
+ mFaceNamesMissed = nullptr;
+ }
+
+ if (mOtherNamesMissed) {
+ for (auto it = mOtherNamesMissed->Iter(); !it.Done(); it.Next()) {
+ if (FindFamily(it.Get()->GetKey())) {
+ forceReflow = true;
+ ForceGlobalReflow();
+ break;
+ }
+ }
+ 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::GetPrefsAndStartLoader()
+{
+ mIncrement =
+ std::max(1u, Preferences::GetUint(FONT_LOADER_FAMILIES_PER_SLICE_PREF));
+
+ uint32_t delay =
+ std::max(1u, Preferences::GetUint(FONT_LOADER_DELAY_PREF));
+ uint32_t interval =
+ std::max(1u, Preferences::GetUint(FONT_LOADER_INTERVAL_PREF));
+
+ StartLoader(delay, interval);
+}
+
+void
+gfxPlatformFontList::ForceGlobalReflow()
+{
+ // modify a preference that will trigger reflow everywhere
+ static const char kPrefName[] = "font.internaluseonly.changed";
+ bool fontInternalChange = Preferences::GetBool(kPrefName, false);
+ Preferences::SetBool(kPrefName, !fontInternalChange);
+}
+
+void
+gfxPlatformFontList::RebuildLocalFonts()
+{
+ for (auto it = mUserFontSetList.Iter(); !it.Done(); it.Next()) {
+ it.Get()->GetKey()->RebuildLocalRules();
+ }
+}
+
+void
+gfxPlatformFontList::ClearLangGroupPrefFonts()
+{
+ for (uint32_t i = eFontPrefLang_First;
+ i < eFontPrefLang_First + eFontPrefLang_Count; i++) {
+ auto& prefFontsLangGroup = mLangGroupPrefFonts[i];
+ for (uint32_t j = eFamily_generic_first;
+ j < eFamily_generic_first + eFamily_generic_count; j++) {
+ prefFontsLangGroup[j] = nullptr;
+ }
+ }
+}
+
+// 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)
+{
+ size_t n = aTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = aTable.ConstIter(); !iter.Done(); iter.Next()) {
+ // 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.
+ n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ }
+ return n;
+}
+
+/*static*/ size_t
+gfxPlatformFontList::SizeOfFontEntryTableExcludingThis(
+ const FontEntryTable& aTable,
+ MallocSizeOf aMallocSizeOf)
+{
+ size_t n = aTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = aTable.ConstIter(); !iter.Done(); iter.Next()) {
+ // The font itself is counted by its owning family; here we only care
+ // about the names stored in the hashtable keys.
+ n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ }
+ return n;
+}
+
+void
+gfxPlatformFontList::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const
+{
+ aSizes->mFontListSize +=
+ mFontFamilies.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = mFontFamilies.ConstIter(); !iter.Done(); iter.Next()) {
+ aSizes->mFontListSize +=
+ iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ iter.Data()->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 (uint32_t j = eFamily_generic_first;
+ j < eFamily_generic_first + eFamily_generic_count; j++) {
+ PrefFontList* pf = prefFontsLangGroup[j].get();
+ if (pf) {
+ aSizes->mFontListSize +=
+ pf->ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+ }
+ }
+
+ aSizes->mFontListSize +=
+ mCodepointsWithNoFonts.SizeOfExcludingThis(aMallocSizeOf);
+ aSizes->mFontListSize +=
+ mFontFamiliesToLoad.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ aSizes->mFontListSize +=
+ mBadUnderlineFamilyNames.SizeOfExcludingThis(aMallocSizeOf);
+
+ aSizes->mFontListSize +=
+ mSharedCmaps.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = mSharedCmaps.ConstIter(); !iter.Done(); iter.Next()) {
+ aSizes->mCharMapsSize +=
+ iter.Get()->GetKey()->SizeOfIncludingThis(aMallocSizeOf);
+ }
+}
+
+void
+gfxPlatformFontList::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const
+{
+ aSizes->mFontListSize += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
+
+bool
+gfxPlatformFontList::IsFontFamilyWhitelistActive()
+{
+ return mFontFamilyWhitelistActive;
+}
+
+#undef LOG
+#undef LOG_ENABLED
diff --git a/gfx/thebes/gfxPlatformFontList.h b/gfx/thebes/gfxPlatformFontList.h
new file mode 100644
index 000000000..c16994d8c
--- /dev/null
+++ b/gfx/thebes/gfxPlatformFontList.h
@@ -0,0 +1,471 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "nsDataHashtable.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTHashtable.h"
+
+#include "gfxFontUtils.h"
+#include "gfxFontInfoLoader.h"
+#include "gfxFont.h"
+#include "gfxFontConstants.h"
+#include "gfxPlatform.h"
+#include "gfxFontFamilyList.h"
+
+#include "nsIMemoryReporter.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/RangedArray.h"
+#include "nsILanguageAtomService.h"
+
+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);
+ }
+ ~CharMapHashKey()
+ {
+ MOZ_COUNT_DTOR(CharMapHashKey);
+ }
+
+ gfxCharacterMap* GetKey() const { return mCharMap; }
+
+ bool KeyEquals(const gfxCharacterMap *aCharMap) const {
+ NS_ASSERTION(!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:
+ gfxCharacterMap *mCharMap;
+};
+
+// 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
+};
+
+class gfxUserFontSet;
+
+class gfxPlatformFontList : public gfxFontInfoLoader
+{
+public:
+ typedef mozilla::unicode::Script Script;
+
+ static gfxPlatformFontList* PlatformFontList() {
+ return sPlatformFontList;
+ }
+
+ static nsresult Init() {
+ NS_ASSERTION(!sPlatformFontList, "What's this doing here?");
+ gfxPlatform::GetPlatform()->CreatePlatformFontList();
+ if (!sPlatformFontList) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+ }
+
+ static void Shutdown() {
+ delete sPlatformFontList;
+ sPlatformFontList = nullptr;
+ }
+
+ virtual ~gfxPlatformFontList();
+
+ // initialize font lists
+ nsresult InitFontList();
+
+ virtual void GetFontList(nsIAtom *aLangGroup,
+ const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts);
+
+ void UpdateFontList();
+
+ virtual void ClearLangGroupPrefFonts();
+
+ virtual void GetFontFamilyList(nsTArray<RefPtr<gfxFontFamily> >& aFamilyArray);
+
+ gfxFontEntry*
+ SystemFindFontForChar(uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript,
+ const gfxFontStyle* aStyle);
+
+ // 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.
+ virtual bool
+ FindAndAddFamilies(const nsAString& aFamily,
+ nsTArray<gfxFontFamily*>* aOutput,
+ gfxFontStyle* aStyle = nullptr,
+ gfxFloat aDevToCssSize = 1.0);
+
+ gfxFontEntry* FindFontForFamily(const nsAString& aFamily, const gfxFontStyle* aStyle, bool& aNeedsBold);
+
+ // name lookup table methods
+
+ void AddOtherFamilyName(gfxFontFamily *aFamilyEntry, nsAString& aOtherFamilyName);
+
+ void AddFullname(gfxFontEntry *aFontEntry, nsAString& aFullname);
+
+ void AddPostscriptName(gfxFontEntry *aFontEntry, nsAString& aPostscriptName);
+
+ bool NeedFullnamePostscriptNames() { return mExtraNames != nullptr; }
+
+ // pure virtual functions, to be provided by concrete subclasses
+
+ // get the system default font family
+ gfxFontFamily* GetDefaultFont(const gfxFontStyle* aStyle);
+
+ // look up a font by name on the host platform
+ virtual gfxFontEntry* LookupLocalFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle) = 0;
+
+ // create a new platform font from downloaded data (@font-face)
+ // this method is responsible to ensure aFontData is free()'d
+ virtual gfxFontEntry* MakePlatformFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ 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 nsAString& aFontName, nsAString& aFamilyName);
+
+ virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const;
+ virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const;
+
+ // search for existing cmap that matches the input
+ // return the input if no match is found
+ gfxCharacterMap* FindCharMap(gfxCharacterMap *aCmap);
+
+ // add a cmap to the shared cmap set
+ gfxCharacterMap* AddCmap(const gfxCharacterMap *aCharMap);
+
+ // remove the cmap from the shared cmap set
+ void RemoveCmap(const gfxCharacterMap *aCharMap);
+
+ // keep track of userfont sets to notify when global fontlist changes occur
+ void AddUserFontSet(gfxUserFontSet *aUserFontSet) {
+ mUserFontSetList.PutEntry(aUserFontSet);
+ }
+
+ void RemoveUserFontSet(gfxUserFontSet *aUserFontSet) {
+ mUserFontSetList.RemoveEntry(aUserFontSet);
+ }
+
+ static const gfxFontEntry::ScriptRange sComplexScriptRanges[];
+
+ void GetFontlistInitInfo(uint32_t& aNumInits, uint32_t& aLoaderState) {
+ aNumInits = mFontlistInitCount;
+ aLoaderState = (uint32_t) mState;
+ }
+
+ virtual void
+ AddGenericFonts(mozilla::FontFamilyType aGenericType,
+ nsIAtom* aLanguage,
+ nsTArray<gfxFontFamily*>& aFamilyList);
+
+ nsTArray<RefPtr<gfxFontFamily>>*
+ GetPrefFontsLangGroup(mozilla::FontFamilyType aGenericType,
+ eFontPrefLang aPrefLang);
+
+ // 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(nsIAtom *aLang);
+
+ // convert an enum constant to a lang group atom
+ static nsIAtom* 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 Unicode range (based on char code) to a font language for Preferences
+ static eFontPrefLang GetFontPrefLangFor(uint8_t aUnicodeRange);
+
+ // 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::FontFamilyType
+ GetDefaultGeneric(eFontPrefLang aLang);
+
+ // map lang group ==> lang string
+ void GetSampleLangForGroup(nsIAtom* aLanguage, nsACString& aLangStr,
+ bool aCheckEnvironment = true);
+
+ // Returns true if the font family whitelist is not empty.
+ bool IsFontFamilyWhitelistActive();
+
+ static void FontWhitelistPrefChanged(const char *aPref, void *aClosure) {
+ gfxPlatformFontList::PlatformFontList()->UpdateFontList();
+ }
+
+protected:
+ class MemoryReporter final : public nsIMemoryReporter
+ {
+ ~MemoryReporter() {}
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+ };
+
+ explicit gfxPlatformFontList(bool aNeedFullnamePostscriptNames = true);
+
+ static gfxPlatformFontList *sPlatformFontList;
+
+ // Convenience method to return the first matching family (if any) as found
+ // by FindAndAddFamilies().
+ gfxFontFamily*
+ FindFamily(const nsAString& aFamily, gfxFontStyle* aStyle = nullptr,
+ gfxFloat aDevToCssSize = 1.0)
+ {
+ AutoTArray<gfxFontFamily*,1> families;
+ return FindAndAddFamilies(aFamily, &families, aStyle, aDevToCssSize)
+ ? families[0] : nullptr;
+ }
+
+ // Lookup family name in global family list without substitutions or
+ // localized family name lookup. Used for common font fallback families.
+ gfxFontFamily* FindFamilyByCanonicalName(const nsAString& aFamily) {
+ nsAutoString 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
+ gfxFontEntry* CommonFontFallback(uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript,
+ const gfxFontStyle* aMatchStyle,
+ gfxFontFamily** aMatchedFamily);
+
+ // Search fonts system-wide for a given character, null if not found.
+ gfxFontEntry* GlobalFontFallback(const uint32_t aCh,
+ Script aRunScript,
+ const gfxFontStyle* aMatchStyle,
+ uint32_t& aCmapCount,
+ gfxFontFamily** aMatchedFamily);
+
+ // 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(const uint32_t aCh,
+ Script aRunScript,
+ const gfxFontStyle* aMatchStyle,
+ gfxFontFamily** 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);
+
+ // verifies that a family contains a non-zero font count
+ gfxFontFamily* CheckFamily(gfxFontFamily *aFamily);
+
+ // initialize localized family names
+ void InitOtherFamilyNames();
+
+ // 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 nsAString& aFaceName);
+
+ // helper method for finding fullname/postscript names in facename lists
+ gfxFontEntry* FindFaceName(const nsAString& aFaceName);
+
+ // look up a font by name, for cases where platform font list
+ // maintains explicit mappings of fullname/psname ==> font
+ virtual gfxFontEntry* LookupInFaceNameLists(const nsAString& aFontName);
+
+ // commonly used fonts for which the name table should be loaded at startup
+ virtual void PreloadNamesList();
+
+ // load the bad underline blacklist from pref.
+ void LoadBadUnderlineList();
+
+ void GenerateFontListKey(const nsAString& aKeyName, nsAString& aResult);
+
+ virtual void GetFontFamilyNames(nsTArray<nsString>& aFontFamilyNames);
+
+ nsILanguageAtomService* GetLangService();
+
+ // helper function to map lang to lang group
+ nsIAtom* GetLangGroup(nsIAtom* aLanguage);
+
+ // helper method for finding an appropriate lang string
+ bool TryLangForGroup(const nsACString& aOSLang, nsIAtom* aLangGroup,
+ nsACString& aLang);
+
+ static const char* GetGenericName(mozilla::FontFamilyType aGenericType);
+
+ // gfxFontInfoLoader overrides, used to load in font cmaps
+ virtual void InitLoader();
+ virtual bool LoadFontInfo();
+ virtual void CleanupLoader();
+
+ // read the loader initialization prefs, and start it
+ void GetPrefsAndStartLoader();
+
+ // for font list changes that affect all documents
+ void ForceGlobalReflow();
+
+ void RebuildLocalFonts();
+
+ void
+ ResolveGenericFontNames(mozilla::FontFamilyType aGenericType,
+ eFontPrefLang aPrefLang,
+ nsTArray<RefPtr<gfxFontFamily>>* aGenericFamilies);
+
+ virtual nsresult InitFontListForPlatform() = 0;
+
+ void ApplyWhitelist();
+
+ typedef nsRefPtrHashtable<nsStringHashKey, gfxFontFamily> FontFamilyTable;
+ typedef nsRefPtrHashtable<nsStringHashKey, 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 gfxFontFamily*
+ GetDefaultFontForPlatform(const gfxFontStyle* aStyle) = 0;
+
+ // canonical family name ==> family entry (unique, one name per family entry)
+ FontFamilyTable mFontFamilies;
+
+ // 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;
+
+ // flag set after InitOtherFamilyNames is called upon first name lookup miss
+ bool mOtherFamilyNamesInitialized;
+
+ // flag set after fullname and Postcript name lists are populated
+ bool mFaceNameListsInitialized;
+
+ struct ExtraNames {
+ ExtraNames() : mFullnames(64), mPostscriptNames(64) {}
+
+ // fullname ==> font entry (unique, one name per font entry)
+ FontEntryTable mFullnames;
+ // Postscript name ==> font entry (unique, one name per font entry)
+ FontEntryTable mPostscriptNames;
+ };
+ mozilla::UniquePtr<ExtraNames> mExtraNames;
+
+ // face names missed when face name loading takes a long time
+ mozilla::UniquePtr<nsTHashtable<nsStringHashKey> > mFaceNamesMissed;
+
+ // localized family names missed when face name loading takes a long time
+ mozilla::UniquePtr<nsTHashtable<nsStringHashKey> > mOtherNamesMissed;
+
+ typedef nsTArray<RefPtr<gfxFontFamily>> PrefFontList;
+ typedef mozilla::RangedArray<mozilla::UniquePtr<PrefFontList>,
+ mozilla::eFamily_generic_first,
+ mozilla::eFamily_generic_count> PrefFontsForLangGroup;
+ mozilla::RangedArray<PrefFontsForLangGroup,
+ eFontPrefLang_First,
+ eFontPrefLang_Count> mLangGroupPrefFonts;
+
+ // when system-wide font lookup fails for a character, cache it to skip future searches
+ gfxSparseBitSet mCodepointsWithNoFonts;
+
+ // the family to use for U+FFFD fallback, to avoid expensive search every time
+ // on pages with lots of problems
+ RefPtr<gfxFontFamily> mReplacementCharFallbackFamily;
+
+ nsTHashtable<nsStringHashKey> mBadUnderlineFamilyNames;
+
+ // character map data shared across families
+ // contains weak ptrs to cmaps shared by font entry objects
+ nsTHashtable<CharMapHashKey> mSharedCmaps;
+
+ // data used as part of the font cmap loading process
+ nsTArray<RefPtr<gfxFontFamily> > mFontFamiliesToLoad;
+ uint32_t mStartIndex;
+ uint32_t mIncrement;
+ uint32_t mNumFamilies;
+
+ // xxx - info for diagnosing no default font aborts
+ // see bugs 636957, 1070983, 1189129
+ uint32_t mFontlistInitCount; // num times InitFontList called
+
+ nsTHashtable<nsPtrHashKey<gfxUserFontSet> > mUserFontSetList;
+
+ nsCOMPtr<nsILanguageAtomService> mLangService;
+ nsTArray<uint32_t> mCJKPrefLangs;
+ nsTArray<mozilla::FontFamilyType> mDefaultGenericsLangGroup;
+
+ bool mFontFamilyWhitelistActive;
+};
+
+#endif /* GFXPLATFORMFONTLIST_H_ */
diff --git a/gfx/thebes/gfxPlatformGtk.cpp b/gfx/thebes/gfxPlatformGtk.cpp
new file mode 100644
index 000000000..9d7f512f2
--- /dev/null
+++ b/gfx/thebes/gfxPlatformGtk.cpp
@@ -0,0 +1,906 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "prenv.h"
+
+#include "nsUnicharUtils.h"
+#include "nsUnicodeProperties.h"
+#include "gfx2DGlue.h"
+#include "gfxFcPlatformFontList.h"
+#include "gfxFontconfigUtils.h"
+#include "gfxFontconfigFonts.h"
+#include "gfxConfig.h"
+#include "gfxContext.h"
+#include "gfxUserFontSet.h"
+#include "gfxUtils.h"
+#include "gfxFT2FontBase.h"
+#include "gfxPrefs.h"
+#include "VsyncSource.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Monitor.h"
+#include "base/task.h"
+#include "base/thread.h"
+#include "base/message_loop.h"
+#include "mozilla/gfx/Logging.h"
+
+#include "mozilla/gfx/2D.h"
+
+#include "cairo.h"
+#include <gtk/gtk.h>
+
+#include "gfxImageSurface.h"
+#ifdef MOZ_X11
+#include <gdk/gdkx.h>
+#include "gfxXlibSurface.h"
+#include "cairo-xlib.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/X11Util.h"
+
+#ifdef GL_PROVIDER_GLX
+#include "GLContextProvider.h"
+#include "GLContextGLX.h"
+#include "GLXLibrary.h"
+#endif
+
+/* 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 */
+
+#include <fontconfig/fontconfig.h>
+
+#include "nsMathUtils.h"
+
+#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;
+
+gfxFontconfigUtils *gfxPlatformGtk::sFontconfigUtils = nullptr;
+
+#if (MOZ_WIDGET_GTK == 2)
+static cairo_user_data_key_t cairo_gdk_drawable_key;
+#endif
+
+bool gfxPlatformGtk::sUseFcFontList = false;
+
+gfxPlatformGtk::gfxPlatformGtk()
+{
+ gtk_init(nullptr, nullptr);
+
+ sUseFcFontList = mozilla::Preferences::GetBool("gfx.font_rendering.fontconfig.fontlist.enabled");
+ if (!sUseFcFontList && !sFontconfigUtils) {
+ sFontconfigUtils = gfxFontconfigUtils::GetFontconfigUtils();
+ }
+
+ mMaxGenericSubstitutions = UNINITIALIZED_VALUE;
+
+#ifdef MOZ_X11
+ if (XRE_IsParentProcess()) {
+ if (GDK_IS_X11_DISPLAY(gdk_display_get_default()) &&
+ mozilla::Preferences::GetBool("gfx.xrender.enabled"))
+ {
+ gfxVars::SetUseXRender(true);
+ }
+ }
+#endif
+
+ uint32_t canvasMask = BackendTypeBit(BackendType::CAIRO);
+ uint32_t contentMask = BackendTypeBit(BackendType::CAIRO);
+#ifdef USE_SKIA
+ canvasMask |= BackendTypeBit(BackendType::SKIA);
+ contentMask |= BackendTypeBit(BackendType::SKIA);
+#endif
+ InitBackendPrefs(canvasMask, BackendType::CAIRO,
+ contentMask, BackendType::CAIRO);
+
+#ifdef MOZ_X11
+ if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+ mCompositorDisplay = XOpenDisplay(nullptr);
+ MOZ_ASSERT(mCompositorDisplay, "Failed to create compositor display!");
+ } else {
+ mCompositorDisplay = nullptr;
+ }
+#endif // MOZ_X11
+}
+
+gfxPlatformGtk::~gfxPlatformGtk()
+{
+ if (!sUseFcFontList) {
+ gfxFontconfigUtils::Shutdown();
+ sFontconfigUtils = nullptr;
+ gfxPangoFontGroup::Shutdown();
+ }
+
+#ifdef MOZ_X11
+ if (mCompositorDisplay) {
+ XCloseDisplay(mCompositorDisplay);
+ }
+#endif // MOZ_X11
+}
+
+void
+gfxPlatformGtk::FlushContentDrawing()
+{
+ if (gfxVars::UseXRender()) {
+ XFlush(DefaultXDisplay());
+ }
+}
+
+already_AddRefed<gfxASurface>
+gfxPlatformGtk::CreateOffscreenSurface(const IntSize& aSize,
+ gfxImageFormat aFormat)
+{
+ if (!Factory::AllowedSurfaceSize(aSize)) {
+ return nullptr;
+ }
+
+ RefPtr<gfxASurface> newSurface;
+ bool needsClear = true;
+#ifdef MOZ_X11
+ // 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) {
+ // When forcing PaintedLayers to use image surfaces for content,
+ // force creation of gfxImageSurface surfaces.
+ if (gfxVars::UseXRender() && !UseImageOffscreenSurfaces()) {
+ Screen *screen = gdk_x11_screen_get_xscreen(gdkScreen);
+ XRenderPictFormat* xrenderFormat =
+ gfxXlibSurface::FindRenderFormat(DisplayOfScreen(screen),
+ aFormat);
+
+ if (xrenderFormat) {
+ newSurface = gfxXlibSurface::Create(screen, xrenderFormat,
+ aSize);
+ }
+ } else {
+ // We're not going to use XRender, so we don't need to
+ // search for a render format
+ newSurface = new gfxImageSurface(aSize, aFormat);
+ // The gfxImageSurface ctor zeroes this for us, no need to
+ // waste time clearing again
+ needsClear = false;
+ }
+ }
+#endif
+
+ 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(nsIAtom *aLangGroup,
+ const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts)
+{
+ if (sUseFcFontList) {
+ gfxPlatformFontList::PlatformFontList()->GetFontList(aLangGroup,
+ aGenericFamily,
+ aListOfFonts);
+ return NS_OK;
+ }
+
+ return sFontconfigUtils->GetFontList(aLangGroup,
+ aGenericFamily,
+ aListOfFonts);
+}
+
+nsresult
+gfxPlatformGtk::UpdateFontList()
+{
+ if (sUseFcFontList) {
+ gfxPlatformFontList::PlatformFontList()->UpdateFontList();
+ return NS_OK;
+ }
+
+ return sFontconfigUtils->UpdateFontList();
+}
+
+// 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 kFontEmojiOneMozilla[] = "EmojiOne Mozilla";
+static const char kFontFreeSans[] = "FreeSans";
+static const char kFontFreeSerif[] = "FreeSerif";
+static const char kFontTakaoPGothic[] = "TakaoPGothic";
+static const char kFontDroidSansFallback[] = "Droid Sans Fallback";
+static const char kFontWenQuanYiMicroHei[] = "WenQuanYi Micro Hei";
+static const char kFontNanumGothic[] = "NanumGothic";
+
+void
+gfxPlatformGtk::GetCommonFallbackFonts(uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript,
+ nsTArray<const char*>& aFontList)
+{
+ if (aNextCh == 0xfe0fu) {
+ // if char is followed by VS16, try for a color emoji glyph
+ aFontList.AppendElement(kFontEmojiOneMozilla);
+ }
+
+ aFontList.AppendElement(kFontDejaVuSerif);
+ aFontList.AppendElement(kFontFreeSerif);
+ aFontList.AppendElement(kFontDejaVuSans);
+ aFontList.AppendElement(kFontFreeSans);
+
+ if (!IS_IN_BMP(aCh)) {
+ uint32_t p = aCh >> 16;
+ if (p == 1) { // try color emoji font, unless VS15 (text style) present
+ if (aNextCh != 0xfe0fu && aNextCh != 0xfe0eu) {
+ aFontList.AppendElement(kFontEmojiOneMozilla);
+ }
+ }
+ }
+
+ // 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);
+ }
+}
+
+gfxPlatformFontList*
+gfxPlatformGtk::CreatePlatformFontList()
+{
+ gfxPlatformFontList* list = new gfxFcPlatformFontList();
+ if (NS_SUCCEEDED(list->InitFontList())) {
+ return list;
+ }
+ gfxPlatformFontList::Shutdown();
+ return nullptr;
+}
+
+nsresult
+gfxPlatformGtk::GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName)
+{
+ if (sUseFcFontList) {
+ gfxPlatformFontList::PlatformFontList()->
+ GetStandardFamilyName(aFontName, aFamilyName);
+ return NS_OK;
+ }
+
+ return sFontconfigUtils->GetStandardFamilyName(aFontName, aFamilyName);
+}
+
+gfxFontGroup *
+gfxPlatformGtk::CreateFontGroup(const FontFamilyList& aFontFamilyList,
+ const gfxFontStyle* aStyle,
+ gfxTextPerfMetrics* aTextPerf,
+ gfxUserFontSet* aUserFontSet,
+ gfxFloat aDevToCssSize)
+{
+ if (sUseFcFontList) {
+ return new gfxFontGroup(aFontFamilyList, aStyle, aTextPerf,
+ aUserFontSet, aDevToCssSize);
+ }
+
+ return new gfxPangoFontGroup(aFontFamilyList, aStyle,
+ aUserFontSet, aDevToCssSize);
+}
+
+gfxFontEntry*
+gfxPlatformGtk::LookupLocalFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle)
+{
+ if (sUseFcFontList) {
+ gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
+ return pfl->LookupLocalFont(aFontName, aWeight, aStretch,
+ aStyle);
+ }
+
+ return gfxPangoFontGroup::NewFontEntry(aFontName, aWeight,
+ aStretch, aStyle);
+}
+
+gfxFontEntry*
+gfxPlatformGtk::MakePlatformFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t* aFontData,
+ uint32_t aLength)
+{
+ if (sUseFcFontList) {
+ gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
+ return pfl->MakePlatformFont(aFontName, aWeight, aStretch,
+ aStyle, aFontData, aLength);
+ }
+
+ // passing ownership of the font data to the new font entry
+ return gfxPangoFontGroup::NewFontEntry(aFontName, aWeight,
+ aStretch, aStyle,
+ aFontData, aLength);
+}
+
+bool
+gfxPlatformGtk::IsFontFormatSupported(nsIURI *aFontURI, uint32_t aFormatFlags)
+{
+ // check for strange format flags
+ NS_ASSERTION(!(aFormatFlags & gfxUserFontSet::FLAG_FORMAT_NOT_USED),
+ "strange font format hint set");
+
+ // accept supported formats
+ // Pango doesn't apply features from AAT TrueType extensions.
+ // Assume that if this is the only SFNT format specified,
+ // then AAT extensions are required for complex script support.
+ if (aFormatFlags & gfxUserFontSet::FLAG_FORMATS_COMMON) {
+ return true;
+ }
+
+ // reject all other formats, known and unknown
+ if (aFormatFlags != 0) {
+ return false;
+ }
+
+ // no format hint set, need to look at data
+ return true;
+}
+
+static int32_t sDPI = 0;
+
+int32_t
+gfxPlatformGtk::GetDPI()
+{
+ if (!sDPI) {
+ // Make sure init is run so we have a resolution
+ GdkScreen *screen = gdk_screen_get_default();
+ gtk_settings_get_for_screen(screen);
+ sDPI = int32_t(round(gdk_screen_get_resolution(screen)));
+ if (sDPI <= 0) {
+ // Fall back to something sane
+ sDPI = 96;
+ }
+ }
+ return sDPI;
+}
+
+double
+gfxPlatformGtk::GetDPIScale()
+{
+ // 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.
+ int32_t dpi = GetDPI();
+ if (dpi < 144) {
+ return 1.0;
+ } else if (dpi < 168) {
+ return 1.5;
+ } else {
+ return round(dpi/96.0);
+ }
+}
+
+bool
+gfxPlatformGtk::UseImageOffscreenSurfaces()
+{
+ return GetDefaultContentBackend() != mozilla::gfx::BackendType::CAIRO ||
+ gfxPrefs::UseImageOffscreenSurfaces();
+}
+
+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)) {
+ gfxPlatform::FontsPrefsChanged(aPref);
+ return;
+ }
+
+ mMaxGenericSubstitutions = UNINITIALIZED_VALUE;
+ if (sUseFcFontList) {
+ gfxFcPlatformFontList* pfl = gfxFcPlatformFontList::PlatformFontList();
+ pfl->ClearGenericMappings();
+ FlushFontAndWordCaches();
+ }
+}
+
+uint32_t gfxPlatformGtk::MaxGenericSubstitions()
+{
+ if (mMaxGenericSubstitutions == UNINITIALIZED_VALUE) {
+ mMaxGenericSubstitutions =
+ Preferences::GetInt(GFX_PREF_MAX_GENERIC_SUBSTITUTIONS, 3);
+ if (mMaxGenericSubstitutions < 0) {
+ mMaxGenericSubstitutions = 3;
+ }
+ }
+
+ return uint32_t(mMaxGenericSubstitutions);
+}
+
+void
+gfxPlatformGtk::GetPlatformCMSOutputProfile(void *&mem, size_t &size)
+{
+ mem = nullptr;
+ size = 0;
+
+#ifdef MOZ_X11
+ GdkDisplay *display = gdk_display_get_default();
+ if (!GDK_IS_X11_DISPLAY(display))
+ return;
+
+ const char EDID1_ATOM_NAME[] = "XFree86_DDC_EDID1_RAWDATA";
+ const char ICC_PROFILE_ATOM_NAME[] = "_ICC_PROFILE";
+
+ Atom edidAtom, iccAtom;
+ 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;
+
+ Window root = gdk_x11_get_default_root_xwindow();
+
+ Atom retAtom;
+ int retFormat;
+ unsigned long retLength, retAfter;
+ unsigned char *retProperty ;
+
+ iccAtom = XInternAtom(dpy, ICC_PROFILE_ATOM_NAME, TRUE);
+ if (iccAtom) {
+ // read once to get size, once for the data
+ if (Success == XGetWindowProperty(dpy, root, iccAtom,
+ 0, INT_MAX /* length */,
+ False, AnyPropertyType,
+ &retAtom, &retFormat, &retLength,
+ &retAfter, &retProperty)) {
+
+ if (retLength > 0) {
+ void *buffer = malloc(retLength);
+ if (buffer) {
+ memcpy(buffer, retProperty, retLength);
+ mem = buffer;
+ size = retLength;
+ }
+ }
+
+ XFree(retProperty);
+ if (size > 0) {
+#ifdef DEBUG_tor
+ fprintf(stderr,
+ "ICM profile read from %s successfully\n",
+ ICC_PROFILE_ATOM_NAME);
+#endif
+ return;
+ }
+ }
+ }
+
+ edidAtom = XInternAtom(dpy, EDID1_ATOM_NAME, TRUE);
+ if (edidAtom) {
+ if (Success == XGetWindowProperty(dpy, root, edidAtom, 0, 32,
+ False, AnyPropertyType,
+ &retAtom, &retFormat, &retLength,
+ &retAfter, &retProperty)) {
+ double gamma;
+ qcms_CIE_xyY whitePoint;
+ qcms_CIE_xyYTRIPLE primaries;
+
+ if (retLength != 128) {
+#ifdef DEBUG_tor
+ fprintf(stderr, "Short EDID data\n");
+#endif
+ return;
+ }
+
+ // Format documented in "VESA E-EDID Implementation Guide"
+
+ gamma = (100 + retProperty[0x17]) / 100.0;
+ 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;
+
+ 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);
+
+#ifdef DEBUG_tor
+ fprintf(stderr, "EDID gamma: %f\n", gamma);
+ fprintf(stderr, "EDID whitepoint: %f %f %f\n",
+ whitePoint.x, whitePoint.y, whitePoint.Y);
+ fprintf(stderr, "EDID primaries: [%f %f %f] [%f %f %f] [%f %f %f]\n",
+ primaries.Red.x, primaries.Red.y, primaries.Red.Y,
+ primaries.Green.x, primaries.Green.y, primaries.Green.Y,
+ primaries.Blue.x, primaries.Blue.y, primaries.Blue.Y);
+#endif
+
+ qcms_data_create_rgb_with_gamma(whitePoint, primaries, gamma, &mem, &size);
+
+#ifdef DEBUG_tor
+ if (size > 0) {
+ fprintf(stderr,
+ "ICM profile read from %s successfully\n",
+ EDID1_ATOM_NAME);
+ }
+#endif
+ }
+ }
+#endif
+}
+
+
+#if (MOZ_WIDGET_GTK == 2)
+void
+gfxPlatformGtk::SetGdkDrawable(cairo_surface_t *target,
+ GdkDrawable *drawable)
+{
+ if (cairo_surface_status(target))
+ return;
+
+ g_object_ref(drawable);
+
+ cairo_surface_set_user_data (target,
+ &cairo_gdk_drawable_key,
+ drawable,
+ g_object_unref);
+}
+
+GdkDrawable *
+gfxPlatformGtk::GetGdkDrawable(cairo_surface_t *target)
+{
+ if (cairo_surface_status(target))
+ return nullptr;
+
+ GdkDrawable *result;
+
+ result = (GdkDrawable*) cairo_surface_get_user_data (target,
+ &cairo_gdk_drawable_key);
+ if (result)
+ return result;
+
+#ifdef MOZ_X11
+ if (cairo_surface_get_type(target) != CAIRO_SURFACE_TYPE_XLIB)
+ return nullptr;
+
+ // try looking it up in gdk's table
+ result = (GdkDrawable*) gdk_xid_table_lookup(cairo_xlib_surface_get_drawable(target));
+ if (result) {
+ SetGdkDrawable(target, result);
+ return result;
+ }
+#endif
+
+ return nullptr;
+}
+#endif
+
+already_AddRefed<ScaledFont>
+gfxPlatformGtk::GetScaledFontForFont(DrawTarget* aTarget, gfxFont *aFont)
+{
+ switch (aTarget->GetBackendType()) {
+ case BackendType::CAIRO:
+ case BackendType::SKIA:
+ if (aFont->GetType() == gfxFont::FONT_TYPE_FONTCONFIG) {
+ gfxFontconfigFontBase* fcFont = static_cast<gfxFontconfigFontBase*>(aFont);
+ return Factory::CreateScaledFontForFontconfigFont(
+ fcFont->GetCairoScaledFont(),
+ fcFont->GetPattern(),
+ fcFont->GetAdjustedSize());
+ }
+ MOZ_FALLTHROUGH;
+ default:
+ return GetScaledFontForFontWithCairoSkia(aTarget, aFont);
+ }
+}
+
+#ifdef GL_PROVIDER_GLX
+
+class GLXVsyncSource final : public VsyncSource
+{
+public:
+ GLXVsyncSource()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mGlobalDisplay = new GLXDisplay();
+ }
+
+ virtual ~GLXVsyncSource()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ virtual Display& GetGlobalDisplay() override
+ {
+ return *mGlobalDisplay;
+ }
+
+ class GLXDisplay final : public VsyncSource::Display
+ {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GLXDisplay)
+
+ public:
+ GLXDisplay() : mGLContext(nullptr)
+ , mXDisplay(nullptr)
+ , mSetupLock("GLXVsyncSetupLock")
+ , mVsyncThread("GLXVsyncThread")
+ , mVsyncTask(nullptr)
+ , mVsyncEnabledLock("GLXVsyncEnabledLock")
+ , mVsyncEnabled(false)
+ {
+ }
+
+ // 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(this, &GLXDisplay::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;
+ if (!gl::GLContextGLX::FindFBConfigForWindow(mXDisplay, screen, root,
+ &cfgs, &config, &visid)) {
+ lock.NotifyAll();
+ return;
+ }
+
+ mGLContext = gl::GLContextGLX::CreateGLContext(
+ gl::CreateContextFlags::NONE,
+ gl::SurfaceCaps::Any(),
+ nullptr,
+ false,
+ mXDisplay,
+ root,
+ config,
+ false);
+
+ if (!mGLContext) {
+ lock.NotifyAll();
+ return;
+ }
+
+ mGLContext->MakeCurrent();
+
+ // Test that SGI_video_sync lets us get the counter.
+ unsigned int syncCounter = 0;
+ if (gl::sGLXLibrary.xGetVideoSync(&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(this, &GLXDisplay::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(this, &GLXDisplay::Cleanup);
+ mVsyncThread.message_loop()->PostTask(shutdownTask.forget());
+
+ // Stop, waiting for the cleanup task to finish execution.
+ mVsyncThread.Stop();
+ }
+
+ private:
+ virtual ~GLXDisplay()
+ {
+ }
+
+ void RunVsync()
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ mGLContext->MakeCurrent();
+
+ unsigned int syncCounter = 0;
+ gl::sGLXLibrary.xGetVideoSync(&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.xWaitVideoSync(2, 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) {
+ PlatformThread::Sleep(remaining);
+ }
+ }
+
+ lastVsync = TimeStamp::Now();
+ NotifyVsync(lastVsync);
+ }
+ }
+
+ void Cleanup() {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ mGLContext = nullptr;
+ XCloseDisplay(mXDisplay);
+ }
+
+ // Owned by the vsync thread.
+ RefPtr<gl::GLContextGLX> mGLContext;
+ _XDisplay* mXDisplay;
+ Monitor mSetupLock;
+ base::Thread mVsyncThread;
+ RefPtr<Runnable> mVsyncTask;
+ Monitor mVsyncEnabledLock;
+ bool mVsyncEnabled;
+ };
+private:
+ // We need a refcounted VsyncSource::Display to use chromium IPC runnables.
+ RefPtr<GLXDisplay> mGlobalDisplay;
+};
+
+already_AddRefed<gfx::VsyncSource>
+gfxPlatformGtk::CreateHardwareVsyncSource()
+{
+ // Only use GLX vsync when the OpenGL compositor is being used.
+ // The extra cost of initializing a GLX context while blocking the main
+ // thread is not worth it when using basic composition.
+ if (gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) {
+ if (gl::sGLXLibrary.SupportsVideoSync()) {
+ RefPtr<VsyncSource> vsyncSource = new GLXVsyncSource();
+ VsyncSource::Display& display = vsyncSource->GetGlobalDisplay();
+ if (!static_cast<GLXVsyncSource::GLXDisplay&>(display).Setup()) {
+ NS_WARNING("Failed to setup GLContext, falling back to software vsync.");
+ return gfxPlatform::CreateHardwareVsyncSource();
+ }
+ return vsyncSource.forget();
+ }
+ NS_WARNING("SGI_video_sync unsupported. Falling back to software vsync.");
+ }
+ return gfxPlatform::CreateHardwareVsyncSource();
+}
+
+bool
+gfxPlatformGtk::SupportsApzTouchInput() const
+{
+ int value = gfxPrefs::TouchEventsEnabled();
+ return value == 1 || value == 2;
+}
+
+#endif
diff --git a/gfx/thebes/gfxPlatformGtk.h b/gfx/thebes/gfxPlatformGtk.h
new file mode 100644
index 000000000..982390d18
--- /dev/null
+++ b/gfx/thebes/gfxPlatformGtk.h
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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"
+
+#if (MOZ_WIDGET_GTK == 2)
+extern "C" {
+ typedef struct _GdkDrawable GdkDrawable;
+}
+#endif
+
+#ifdef MOZ_X11
+struct _XDisplay;
+typedef struct _XDisplay Display;
+#endif // MOZ_X11
+
+class gfxFontconfigUtils;
+
+class gfxPlatformGtk : public gfxPlatform {
+public:
+ gfxPlatformGtk();
+ virtual ~gfxPlatformGtk();
+
+ static gfxPlatformGtk *GetPlatform() {
+ return (gfxPlatformGtk*) gfxPlatform::GetPlatform();
+ }
+
+ virtual already_AddRefed<gfxASurface>
+ CreateOffscreenSurface(const IntSize& aSize,
+ gfxImageFormat aFormat) override;
+
+ virtual already_AddRefed<mozilla::gfx::ScaledFont>
+ GetScaledFontForFont(mozilla::gfx::DrawTarget* aTarget, gfxFont *aFont) override;
+
+ virtual nsresult GetFontList(nsIAtom *aLangGroup,
+ const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts) override;
+
+ virtual nsresult UpdateFontList() override;
+
+ virtual void
+ GetCommonFallbackFonts(uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript,
+ nsTArray<const char*>& aFontList) override;
+
+ virtual gfxPlatformFontList* CreatePlatformFontList() override;
+
+ virtual nsresult GetStandardFamilyName(const nsAString& aFontName,
+ nsAString& aFamilyName) override;
+
+ gfxFontGroup*
+ CreateFontGroup(const mozilla::FontFamilyList& aFontFamilyList,
+ const gfxFontStyle *aStyle,
+ gfxTextPerfMetrics* aTextPerf,
+ gfxUserFontSet *aUserFontSet,
+ gfxFloat aDevToCssSize) override;
+
+ /**
+ * Look up a local platform font using the full font face name (needed to
+ * support @font-face src local() )
+ */
+ virtual gfxFontEntry* LookupLocalFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle) override;
+
+ /**
+ * Activate a platform font (needed to support @font-face src url() )
+ *
+ */
+ virtual gfxFontEntry* MakePlatformFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t* aFontData,
+ uint32_t aLength) override;
+
+ /**
+ * Check whether format is supported on a platform or not (if unclear,
+ * returns true).
+ */
+ virtual bool IsFontFormatSupported(nsIURI *aFontURI,
+ uint32_t aFormatFlags) override;
+
+ /**
+ * Calls XFlush if xrender is enabled.
+ */
+ virtual void FlushContentDrawing() override;
+
+#if (MOZ_WIDGET_GTK == 2)
+ static void SetGdkDrawable(cairo_surface_t *target,
+ GdkDrawable *drawable);
+ static GdkDrawable *GetGdkDrawable(cairo_surface_t *target);
+#endif
+
+ static int32_t GetDPI();
+ static double GetDPIScale();
+
+#ifdef MOZ_X11
+ virtual void GetAzureBackendInfo(mozilla::widget::InfoObject &aObj) override {
+ gfxPlatform::GetAzureBackendInfo(aObj);
+ aObj.DefineProperty("CairoUseXRender", mozilla::gfx::gfxVars::UseXRender());
+ }
+#endif
+
+ static bool UseFcFontList() { return sUseFcFontList; }
+
+ bool UseImageOffscreenSurfaces();
+
+ virtual gfxImageFormat GetOffscreenFormat() override;
+
+ bool SupportsApzWheelInput() const override {
+ return true;
+ }
+
+ bool SupportsApzTouchInput() const override;
+
+ void FontsPrefsChanged(const char *aPref) override;
+
+ // maximum number of fonts to substitute for a generic
+ uint32_t MaxGenericSubstitions();
+
+ bool SupportsPluginDirectBitmapDrawing() override {
+ return true;
+ }
+
+ bool AccelerateLayersByDefault() override {
+#ifdef NIGHTLY_BUILD
+ // Only enable the GL compositor on Nightly for now until we have
+ // sufficient data for blocklisting.
+ return true;
+#endif
+ return false;
+ }
+
+#ifdef GL_PROVIDER_GLX
+ already_AddRefed<mozilla::gfx::VsyncSource> CreateHardwareVsyncSource() override;
+#endif
+
+#ifdef MOZ_X11
+ Display* GetCompositorDisplay() {
+ return mCompositorDisplay;
+ }
+#endif // MOZ_X11
+
+protected:
+ static gfxFontconfigUtils *sFontconfigUtils;
+
+ int8_t mMaxGenericSubstitutions;
+
+private:
+ virtual void GetPlatformCMSOutputProfile(void *&mem,
+ size_t &size) override;
+
+#ifdef MOZ_X11
+ Display* mCompositorDisplay;
+#endif
+
+ // xxx - this will be removed once the new fontconfig platform font list
+ // replaces gfxPangoFontGroup
+ static bool sUseFcFontList;
+};
+
+#endif /* GFX_PLATFORM_GTK_H */
diff --git a/gfx/thebes/gfxPlatformMac.cpp b/gfx/thebes/gfxPlatformMac.cpp
new file mode 100644
index 000000000..3216f0f07
--- /dev/null
+++ b/gfx/thebes/gfxPlatformMac.cpp
@@ -0,0 +1,620 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/gfx/2D.h"
+#include "mozilla/gfx/MacIOSurface.h"
+
+#include "gfxMacPlatformFontList.h"
+#include "gfxMacFont.h"
+#include "gfxCoreTextShaper.h"
+#include "gfxTextRun.h"
+#include "gfxUserFontSet.h"
+
+#include "nsTArray.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/VsyncDispatcher.h"
+#include "qcms.h"
+#include "gfx2DGlue.h"
+
+#include <dlfcn.h>
+#include <CoreVideo/CoreVideo.h>
+
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "VsyncSource.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+// cribbed from CTFontManager.h
+enum {
+ kAutoActivationDisabled = 1
+};
+typedef uint32_t AutoActivationSetting;
+
+// bug 567552 - disable auto-activation of fonts
+
+static void
+DisableFontActivation()
+{
+ // 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");
+ return;
+ }
+
+ // if possible, fetch CTFontManagerSetAutoActivationSetting
+ void (*CTFontManagerSetAutoActivationSettingPtr)
+ (CFStringRef, AutoActivationSetting);
+ CTFontManagerSetAutoActivationSettingPtr =
+ (void (*)(CFStringRef, AutoActivationSetting))
+ dlsym(RTLD_DEFAULT, "CTFontManagerSetAutoActivationSetting");
+
+ // bug 567552 - disable auto-activation of fonts
+ if (CTFontManagerSetAutoActivationSettingPtr) {
+ CTFontManagerSetAutoActivationSettingPtr(mainBundleID,
+ kAutoActivationDisabled);
+ }
+}
+
+gfxPlatformMac::gfxPlatformMac()
+{
+ DisableFontActivation();
+ mFontAntiAliasingThreshold = ReadAntiAliasingThreshold();
+
+ uint32_t canvasMask = BackendTypeBit(BackendType::SKIA);
+ uint32_t contentMask = BackendTypeBit(BackendType::SKIA);
+ InitBackendPrefs(canvasMask, BackendType::SKIA,
+ contentMask, BackendType::SKIA);
+
+ // XXX: Bug 1036682 - we run out of fds on Mac when using tiled layers because
+ // with 256x256 tiles we can easily hit the soft limit of 800 when using double
+ // buffered tiles in e10s, so let's bump the soft limit to the hard limit for the OS
+ // up to a new cap of OPEN_MAX.
+ struct rlimit limits;
+ if (getrlimit(RLIMIT_NOFILE, &limits) == 0) {
+ limits.rlim_cur = std::min(rlim_t(OPEN_MAX), limits.rlim_max);
+ if (setrlimit(RLIMIT_NOFILE, &limits) != 0) {
+ NS_WARNING("Unable to bump RLIMIT_NOFILE to the maximum number on this OS");
+ }
+ }
+
+ MacIOSurfaceLib::LoadLibrary();
+}
+
+gfxPlatformMac::~gfxPlatformMac()
+{
+ gfxCoreTextShaper::Shutdown();
+}
+
+gfxPlatformFontList*
+gfxPlatformMac::CreatePlatformFontList()
+{
+ gfxPlatformFontList* list = new gfxMacPlatformFontList();
+ if (NS_SUCCEEDED(list->InitFontList())) {
+ return list;
+ }
+ gfxPlatformFontList::Shutdown();
+ return nullptr;
+}
+
+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();
+}
+
+already_AddRefed<ScaledFont>
+gfxPlatformMac::GetScaledFontForFont(DrawTarget* aTarget, gfxFont *aFont)
+{
+ gfxMacFont *font = static_cast<gfxMacFont*>(aFont);
+ return font->GetScaledFont(aTarget);
+}
+
+gfxFontGroup *
+gfxPlatformMac::CreateFontGroup(const FontFamilyList& aFontFamilyList,
+ const gfxFontStyle *aStyle,
+ gfxTextPerfMetrics* aTextPerf,
+ gfxUserFontSet *aUserFontSet,
+ gfxFloat aDevToCssSize)
+{
+ return new gfxFontGroup(aFontFamilyList, aStyle, aTextPerf,
+ aUserFontSet, aDevToCssSize);
+}
+
+bool
+gfxPlatformMac::IsFontFormatSupported(nsIURI *aFontURI, uint32_t aFormatFlags)
+{
+ // check for strange format flags
+ NS_ASSERTION(!(aFormatFlags & gfxUserFontSet::FLAG_FORMAT_NOT_USED),
+ "strange font format hint set");
+
+ // accept supported formats
+ if (aFormatFlags & (gfxUserFontSet::FLAG_FORMATS_COMMON |
+ gfxUserFontSet::FLAG_FORMAT_TRUETYPE_AAT)) {
+ return true;
+ }
+
+ // reject all other formats, known and unknown
+ if (aFormatFlags != 0) {
+ return false;
+ }
+
+ // no format hint set, need to look at data
+ return true;
+}
+
+static const char kFontArialUnicodeMS[] = "Arial Unicode MS";
+static const char kFontAppleBraille[] = "Apple Braille";
+static const char kFontAppleColorEmoji[] = "Apple Color Emoji";
+static const char kFontAppleSymbols[] = "Apple Symbols";
+static const char kFontDevanagariSangamMN[] = "Devanagari Sangam MN";
+static const char kFontEuphemiaUCAS[] = "Euphemia UCAS";
+static const char kFontGeneva[] = "Geneva";
+static const char kFontGeezaPro[] = "Geeza Pro";
+static const char kFontGujaratiSangamMN[] = "Gujarati Sangam MN";
+static const char kFontGurmukhiMN[] = "Gurmukhi MN";
+static const char kFontHiraginoKakuGothic[] = "Hiragino Kaku Gothic ProN";
+static const char kFontHiraginoSansGB[] = "Hiragino Sans GB";
+static const char kFontKefa[] = "Kefa";
+static const char kFontKhmerMN[] = "Khmer MN";
+static const char kFontLaoMN[] = "Lao MN";
+static const char kFontLucidaGrande[] = "Lucida Grande";
+static const char kFontMenlo[] = "Menlo";
+static const char kFontMicrosoftTaiLe[] = "Microsoft Tai Le";
+static const char kFontMingLiUExtB[] = "MingLiU-ExtB";
+static const char kFontMyanmarMN[] = "Myanmar MN";
+static const char kFontPlantagenetCherokee[] = "Plantagenet Cherokee";
+static const char kFontSimSunExtB[] = "SimSun-ExtB";
+static const char kFontSongtiSC[] = "Songti SC";
+static const char kFontSTHeiti[] = "STHeiti";
+static const char kFontSTIXGeneral[] = "STIXGeneral";
+static const char kFontTamilMN[] = "Tamil MN";
+
+void
+gfxPlatformMac::GetCommonFallbackFonts(uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript,
+ nsTArray<const char*>& aFontList)
+{
+ if (aNextCh == 0xfe0f) {
+ aFontList.AppendElement(kFontAppleColorEmoji);
+ }
+
+ aFontList.AppendElement(kFontLucidaGrande);
+
+ if (!IS_IN_BMP(aCh)) {
+ uint32_t p = aCh >> 16;
+ uint32_t b = aCh >> 8;
+ if (p == 1) {
+ if (b >= 0x1f0 && b < 0x1f7) {
+ aFontList.AppendElement(kFontAppleColorEmoji);
+ } else {
+ aFontList.AppendElement(kFontAppleSymbols);
+ aFontList.AppendElement(kFontSTIXGeneral);
+ aFontList.AppendElement(kFontGeneva);
+ }
+ } else if (p == 2) {
+ // OSX installations with MS Office may have these fonts
+ aFontList.AppendElement(kFontMingLiUExtB);
+ aFontList.AppendElement(kFontSimSunExtB);
+ }
+ } else {
+ uint32_t b = (aCh >> 8) & 0xff;
+
+ switch (b) {
+ case 0x03:
+ case 0x05:
+ aFontList.AppendElement(kFontGeneva);
+ break;
+ case 0x07:
+ aFontList.AppendElement(kFontGeezaPro);
+ break;
+ case 0x09:
+ aFontList.AppendElement(kFontDevanagariSangamMN);
+ break;
+ case 0x0a:
+ aFontList.AppendElement(kFontGurmukhiMN);
+ aFontList.AppendElement(kFontGujaratiSangamMN);
+ break;
+ case 0x0b:
+ aFontList.AppendElement(kFontTamilMN);
+ break;
+ case 0x0e:
+ aFontList.AppendElement(kFontLaoMN);
+ break;
+ case 0x0f:
+ aFontList.AppendElement(kFontSongtiSC);
+ break;
+ case 0x10:
+ aFontList.AppendElement(kFontMenlo);
+ aFontList.AppendElement(kFontMyanmarMN);
+ break;
+ case 0x13: // Cherokee
+ aFontList.AppendElement(kFontPlantagenetCherokee);
+ aFontList.AppendElement(kFontKefa);
+ break;
+ case 0x14: // Unified Canadian Aboriginal Syllabics
+ case 0x15:
+ case 0x16:
+ aFontList.AppendElement(kFontEuphemiaUCAS);
+ aFontList.AppendElement(kFontGeneva);
+ break;
+ case 0x18: // Mongolian, UCAS
+ aFontList.AppendElement(kFontSTHeiti);
+ aFontList.AppendElement(kFontEuphemiaUCAS);
+ break;
+ case 0x19: // Khmer
+ aFontList.AppendElement(kFontKhmerMN);
+ aFontList.AppendElement(kFontMicrosoftTaiLe);
+ break;
+ case 0x1d:
+ case 0x1e:
+ aFontList.AppendElement(kFontGeneva);
+ 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 0x2e:
+ aFontList.AppendElement(kFontHiraginoKakuGothic);
+ aFontList.AppendElement(kFontAppleSymbols);
+ aFontList.AppendElement(kFontMenlo);
+ aFontList.AppendElement(kFontSTIXGeneral);
+ aFontList.AppendElement(kFontGeneva);
+ aFontList.AppendElement(kFontAppleColorEmoji);
+ break;
+ case 0x2c:
+ aFontList.AppendElement(kFontGeneva);
+ break;
+ case 0x2d:
+ aFontList.AppendElement(kFontKefa);
+ aFontList.AppendElement(kFontGeneva);
+ break;
+ case 0x28: // Braille
+ aFontList.AppendElement(kFontAppleBraille);
+ break;
+ case 0x31:
+ aFontList.AppendElement(kFontHiraginoSansGB);
+ break;
+ case 0x4d:
+ aFontList.AppendElement(kFontAppleSymbols);
+ break;
+ case 0xa0: // Yi
+ case 0xa1:
+ case 0xa2:
+ case 0xa3:
+ case 0xa4:
+ aFontList.AppendElement(kFontSTHeiti);
+ break;
+ case 0xa6:
+ case 0xa7:
+ aFontList.AppendElement(kFontGeneva);
+ aFontList.AppendElement(kFontAppleSymbols);
+ break;
+ case 0xab:
+ aFontList.AppendElement(kFontKefa);
+ break;
+ case 0xfc:
+ case 0xff:
+ aFontList.AppendElement(kFontAppleSymbols);
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Arial Unicode MS has lots of glyphs for obscure, use it as a last resort
+ aFontList.AppendElement(kFontArialUnicodeMS);
+}
+
+/*static*/ void
+gfxPlatformMac::LookupSystemFont(mozilla::LookAndFeel::FontID aSystemFontID,
+ nsAString& aSystemFontName,
+ gfxFontStyle& aFontStyle,
+ float aDevPixPerCSSPixel)
+{
+ gfxMacPlatformFontList* pfl = gfxMacPlatformFontList::PlatformFontList();
+ return pfl->LookupSystemFont(aSystemFontID, aSystemFontName, aFontStyle,
+ aDevPixPerCSSPixel);
+}
+
+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()
+ {
+ }
+
+ virtual Display& GetGlobalDisplay() override
+ {
+ return mGlobalDisplay;
+ }
+
+ class OSXDisplay final : public VsyncSource::Display
+ {
+ public:
+ OSXDisplay()
+ : mDisplayLink(nullptr)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ }
+
+ ~OSXDisplay()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ static void RetryEnableVsync(nsITimer* aTimer, void* aOsxDisplay)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ OSXDisplay* osxDisplay = static_cast<OSXDisplay*>(aOsxDisplay);
+ MOZ_ASSERT(osxDisplay);
+ osxDisplay->EnableVsync();
+ }
+
+ virtual void EnableVsync() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (IsVsyncEnabled()) {
+ return;
+ }
+
+ // 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.
+ if (CVDisplayLinkCreateWithActiveCGDisplays(&mDisplayLink) != kCVReturnSuccess) {
+ NS_WARNING("Could not create a display link with all active displays. Retrying");
+ CVDisplayLinkRelease(mDisplayLink);
+ mDisplayLink = 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->InitWithFuncCallback(RetryEnableVsync, this, delay, nsITimer::TYPE_ONE_SHOT);
+ return;
+ }
+
+ if (CVDisplayLinkSetOutputCallback(mDisplayLink, &VsyncCallback, this) != kCVReturnSuccess) {
+ NS_WARNING("Could not set displaylink output callback");
+ CVDisplayLinkRelease(mDisplayLink);
+ mDisplayLink = nullptr;
+ return;
+ }
+
+ mPreviousTimestamp = TimeStamp::Now();
+ if (CVDisplayLinkStart(mDisplayLink) != kCVReturnSuccess) {
+ NS_WARNING("Could not activate the display link");
+ CVDisplayLinkRelease(mDisplayLink);
+ mDisplayLink = nullptr;
+ }
+
+ CVTime vsyncRate = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(mDisplayLink);
+ 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);
+ }
+ }
+
+ virtual void DisableVsync() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!IsVsyncEnabled()) {
+ return;
+ }
+
+ // Release the display link
+ if (mDisplayLink) {
+ CVDisplayLinkRelease(mDisplayLink);
+ mDisplayLink = nullptr;
+ }
+ }
+
+ virtual bool IsVsyncEnabled() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mDisplayLink != nullptr;
+ }
+
+ virtual TimeDuration GetVsyncRate() override
+ {
+ return mVsyncRate;
+ }
+
+ virtual 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:
+ // Manages the display link render thread
+ CVDisplayLinkRef mDisplayLink;
+ RefPtr<nsITimer> mTimer;
+ TimeDuration mVsyncRate;
+ }; // OSXDisplay
+
+private:
+ virtual ~OSXVsyncSource()
+ {
+ }
+
+ OSXDisplay mGlobalDisplay;
+}; // 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::OSXDisplay* display = (OSXVsyncSource::OSXDisplay*) aDisplayLinkContext;
+ int64_t nextVsyncTimestamp = aOutputTime->hostTime;
+
+ mozilla::TimeStamp nextVsync = mozilla::TimeStamp::FromSystemTime(nextVsyncTimestamp);
+ mozilla::TimeStamp previousVsync = display->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
+ // OSXDisplay::mPreviousTimestamp
+ previousVsync = now;
+ }
+
+ display->mPreviousTimestamp = nextVsync;
+
+ display->NotifyVsync(previousVsync);
+ return kCVReturnSuccess;
+}
+
+already_AddRefed<mozilla::gfx::VsyncSource>
+gfxPlatformMac::CreateHardwareVsyncSource()
+{
+ RefPtr<VsyncSource> osxVsyncSource = new OSXVsyncSource();
+ VsyncSource::Display& primaryDisplay = osxVsyncSource->GetGlobalDisplay();
+ primaryDisplay.EnableVsync();
+ if (!primaryDisplay.IsVsyncEnabled()) {
+ NS_WARNING("OS X Vsync source not enabled. Falling back to software vsync.");
+ return gfxPlatform::CreateHardwareVsyncSource();
+ }
+
+ primaryDisplay.DisableVsync();
+ return osxVsyncSource.forget();
+}
+
+void
+gfxPlatformMac::GetPlatformCMSOutputProfile(void* &mem, size_t &size)
+{
+ mem = nullptr;
+ size = 0;
+
+ CGColorSpaceRef cspace = ::CGDisplayCopyColorSpace(::CGMainDisplayID());
+ if (!cspace) {
+ cspace = ::CGColorSpaceCreateDeviceRGB();
+ }
+ if (!cspace) {
+ return;
+ }
+
+ CFDataRef iccp = ::CGColorSpaceCopyICCProfile(cspace);
+
+ ::CFRelease(cspace);
+
+ if (!iccp) {
+ return;
+ }
+
+ // copy to external buffer
+ size = static_cast<size_t>(::CFDataGetLength(iccp));
+ if (size > 0) {
+ void *data = malloc(size);
+ if (data) {
+ memcpy(data, ::CFDataGetBytePtr(iccp), size);
+ mem = data;
+ } else {
+ size = 0;
+ }
+ }
+
+ ::CFRelease(iccp);
+}
diff --git a/gfx/thebes/gfxPlatformMac.h b/gfx/thebes/gfxPlatformMac.h
new file mode 100644
index 000000000..0807614f6
--- /dev/null
+++ b/gfx/thebes/gfxPlatformMac.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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();
+
+ static gfxPlatformMac *GetPlatform() {
+ return (gfxPlatformMac*) gfxPlatform::GetPlatform();
+ }
+
+ virtual already_AddRefed<gfxASurface>
+ CreateOffscreenSurface(const IntSize& aSize,
+ gfxImageFormat aFormat) override;
+
+ already_AddRefed<mozilla::gfx::ScaledFont>
+ GetScaledFontForFont(mozilla::gfx::DrawTarget* aTarget, gfxFont *aFont) override;
+
+ gfxFontGroup*
+ CreateFontGroup(const mozilla::FontFamilyList& aFontFamilyList,
+ const gfxFontStyle *aStyle,
+ gfxTextPerfMetrics* aTextPerf,
+ gfxUserFontSet *aUserFontSet,
+ gfxFloat aDevToCssSize) override;
+
+ virtual gfxPlatformFontList* CreatePlatformFontList() override;
+
+ bool IsFontFormatSupported(nsIURI *aFontURI, uint32_t aFormatFlags) override;
+
+ virtual void GetCommonFallbackFonts(uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript,
+ 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,
+ nsAString& aSystemFontName,
+ gfxFontStyle &aFontStyle,
+ float aDevPixPerCSSPixel);
+
+ virtual bool CanRenderContentToDataSurface() const override {
+ return true;
+ }
+
+ virtual 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;
+ }
+
+ virtual already_AddRefed<mozilla::gfx::VsyncSource> CreateHardwareVsyncSource() override;
+
+ // lower threshold on font anti-aliasing
+ uint32_t GetAntiAliasingThreshold() { return mFontAntiAliasingThreshold; }
+
+protected:
+ bool AccelerateLayersByDefault() override;
+
+private:
+ virtual void GetPlatformCMSOutputProfile(void* &mem, size_t &size) override;
+
+ // read in the pref value for the lower threshold on font anti-aliasing
+ static uint32_t ReadAntiAliasingThreshold();
+
+ uint32_t mFontAntiAliasingThreshold;
+};
+
+#endif /* GFX_PLATFORM_MAC_H */
diff --git a/gfx/thebes/gfxPoint.h b/gfx/thebes/gfxPoint.h
new file mode 100644
index 000000000..5883244e3
--- /dev/null
+++ b/gfx/thebes/gfxPoint.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "nsMathUtils.h"
+#include "mozilla/gfx/BaseSize.h"
+#include "mozilla/gfx/BasePoint.h"
+#include "mozilla/gfx/Matrix.h"
+#include "nsSize.h"
+#include "nsPoint.h"
+
+#include "gfxTypes.h"
+
+struct gfxSize : public mozilla::gfx::BaseSize<gfxFloat, gfxSize> {
+ typedef mozilla::gfx::BaseSize<gfxFloat, gfxSize> Super;
+
+ gfxSize() : Super() {}
+ gfxSize(gfxFloat aWidth, gfxFloat aHeight) : Super(aWidth, aHeight) {}
+ MOZ_IMPLICIT gfxSize(const mozilla::gfx::IntSize& aSize) : Super(aSize.width, aSize.height) {}
+};
+
+struct gfxPoint : public mozilla::gfx::BasePoint<gfxFloat, gfxPoint> {
+ typedef mozilla::gfx::BasePoint<gfxFloat, gfxPoint> Super;
+
+ gfxPoint() : Super() {}
+ gfxPoint(gfxFloat aX, gfxFloat aY) : Super(aX, aY) {}
+ MOZ_IMPLICIT gfxPoint(const nsIntPoint& aPoint) : Super(aPoint.x, aPoint.y) {}
+
+ bool WithinEpsilonOf(const gfxPoint& aPoint, gfxFloat aEpsilon) {
+ return fabs(aPoint.x - x) < aEpsilon && fabs(aPoint.y - y) < aEpsilon;
+ }
+
+ void Transform(const mozilla::gfx::Matrix4x4 &aMatrix)
+ {
+ // Transform this point with aMatrix
+ double px = x;
+ double py = y;
+
+ x = px * aMatrix._11 + py * aMatrix._21 + aMatrix._41;
+ y = px * aMatrix._12 + py * aMatrix._22 + aMatrix._42;
+
+ double w = px * aMatrix._14 + py * aMatrix._24 + aMatrix._44;
+ x /= w;
+ y /= w;
+ }
+};
+
+inline gfxPoint
+operator*(const gfxPoint& aPoint, const gfxSize& aSize)
+{
+ return gfxPoint(aPoint.x * aSize.width, aPoint.y * aSize.height);
+}
+
+inline gfxPoint
+operator/(const gfxPoint& aPoint, const gfxSize& aSize)
+{
+ return gfxPoint(aPoint.x / aSize.width, aPoint.y / aSize.height);
+}
+
+inline gfxSize
+operator/(gfxFloat aValue, const gfxSize& aSize)
+{
+ return gfxSize(aValue / aSize.width, aValue / aSize.height);
+}
+
+#endif /* GFX_POINT_H */
diff --git a/gfx/thebes/gfxPrefs.cpp b/gfx/thebes/gfxPrefs.cpp
new file mode 100644
index 000000000..72bc58d78
--- /dev/null
+++ b/gfx/thebes/gfxPrefs.cpp
@@ -0,0 +1,267 @@
+/* -*- 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 "gfxPrefs.h"
+
+#include "MainThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Unused.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/GPUChild.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+
+using namespace mozilla;
+
+nsTArray<gfxPrefs::Pref*>* gfxPrefs::sGfxPrefList = nullptr;
+gfxPrefs* gfxPrefs::sInstance = nullptr;
+bool gfxPrefs::sInstanceHasBeenDestroyed = false;
+
+void
+gfxPrefs::DestroySingleton()
+{
+ if (sInstance) {
+ delete sInstance;
+ sInstance = nullptr;
+ sInstanceHasBeenDestroyed = true;
+ }
+ MOZ_ASSERT(!SingletonExists());
+}
+
+bool
+gfxPrefs::SingletonExists()
+{
+ return sInstance != nullptr;
+}
+
+gfxPrefs::gfxPrefs()
+{
+ // UI, content, and plugin processes use XPCOM and should have prefs
+ // ready by the time we initialize gfxPrefs.
+ MOZ_ASSERT_IF(XRE_IsContentProcess() ||
+ XRE_IsParentProcess() ||
+ XRE_GetProcessType() == GeckoProcessType_Plugin,
+ Preferences::IsServiceAvailable());
+
+ gfxPrefs::AssertMainThread();
+}
+
+void
+gfxPrefs::Init()
+{
+ // Set up Moz2D prefs.
+ mPrefGfxLoggingLevel.SetChangeCallback([]() -> void {
+ mozilla::gfx::LoggingPrefs::sGfxLogLevel = GetSingleton().mPrefGfxLoggingLevel.GetLiveValue();
+ });
+}
+
+gfxPrefs::~gfxPrefs()
+{
+ gfxPrefs::AssertMainThread();
+ mPrefGfxLoggingLevel.SetChangeCallback(nullptr);
+ delete sGfxPrefList;
+ sGfxPrefList = nullptr;
+}
+
+void gfxPrefs::AssertMainThread()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "this code must be run on the main thread");
+}
+
+void
+gfxPrefs::Pref::OnChange()
+{
+ if (auto gpm = gfx::GPUProcessManager::Get()) {
+ if (gfx::GPUChild* gpu = gpm->GetGPUChild()) {
+ GfxPrefValue value;
+ GetLiveValue(&value);
+ Unused << gpu->SendUpdatePref(gfx::GfxPrefSetting(mIndex, value));
+ }
+ }
+ FireChangeCallback();
+}
+
+void
+gfxPrefs::Pref::FireChangeCallback()
+{
+ if (mChangeCallback) {
+ mChangeCallback();
+ }
+}
+
+void
+gfxPrefs::Pref::SetChangeCallback(ChangeCallback aCallback)
+{
+ mChangeCallback = aCallback;
+
+ if (!IsParentProcess() && IsPrefsServiceAvailable()) {
+ // If we're in the parent process, we watch prefs by default so we can
+ // send changes over to the GPU process. Otherwise, we need to add or
+ // remove a watch for the pref now.
+ if (aCallback) {
+ WatchChanges(Name(), this);
+ } else {
+ UnwatchChanges(Name(), this);
+ }
+ }
+
+ // Fire the callback once to make initialization easier for the caller.
+ FireChangeCallback();
+}
+
+// On lightweight processes such as for GMP and GPU, XPCOM is not initialized,
+// and therefore we don't have access to Preferences. When XPCOM is not
+// available we rely on manual synchronization of gfxPrefs values over IPC.
+/* static */ bool
+gfxPrefs::IsPrefsServiceAvailable()
+{
+ return Preferences::IsServiceAvailable();
+}
+
+/* static */ bool
+gfxPrefs::IsParentProcess()
+{
+ return XRE_IsParentProcess();
+}
+
+void gfxPrefs::PrefAddVarCache(bool* aVariable,
+ const char* aPref,
+ bool aDefault)
+{
+ MOZ_ASSERT(IsPrefsServiceAvailable());
+ Preferences::AddBoolVarCache(aVariable, aPref, aDefault);
+}
+
+void gfxPrefs::PrefAddVarCache(int32_t* aVariable,
+ const char* aPref,
+ int32_t aDefault)
+{
+ MOZ_ASSERT(IsPrefsServiceAvailable());
+ Preferences::AddIntVarCache(aVariable, aPref, aDefault);
+}
+
+void gfxPrefs::PrefAddVarCache(uint32_t* aVariable,
+ const char* aPref,
+ uint32_t aDefault)
+{
+ MOZ_ASSERT(IsPrefsServiceAvailable());
+ Preferences::AddUintVarCache(aVariable, aPref, aDefault);
+}
+
+void gfxPrefs::PrefAddVarCache(float* aVariable,
+ const char* aPref,
+ float aDefault)
+{
+ MOZ_ASSERT(IsPrefsServiceAvailable());
+ Preferences::AddFloatVarCache(aVariable, aPref, aDefault);
+}
+
+bool gfxPrefs::PrefGet(const char* aPref, bool aDefault)
+{
+ MOZ_ASSERT(IsPrefsServiceAvailable());
+ return Preferences::GetBool(aPref, aDefault);
+}
+
+int32_t gfxPrefs::PrefGet(const char* aPref, int32_t aDefault)
+{
+ MOZ_ASSERT(IsPrefsServiceAvailable());
+ return Preferences::GetInt(aPref, aDefault);
+}
+
+uint32_t gfxPrefs::PrefGet(const char* aPref, uint32_t aDefault)
+{
+ MOZ_ASSERT(IsPrefsServiceAvailable());
+ return Preferences::GetUint(aPref, aDefault);
+}
+
+float gfxPrefs::PrefGet(const char* aPref, float aDefault)
+{
+ MOZ_ASSERT(IsPrefsServiceAvailable());
+ return Preferences::GetFloat(aPref, aDefault);
+}
+
+void gfxPrefs::PrefSet(const char* aPref, bool aValue)
+{
+ MOZ_ASSERT(IsPrefsServiceAvailable());
+ Preferences::SetBool(aPref, aValue);
+}
+
+void gfxPrefs::PrefSet(const char* aPref, int32_t aValue)
+{
+ MOZ_ASSERT(IsPrefsServiceAvailable());
+ Preferences::SetInt(aPref, aValue);
+}
+
+void gfxPrefs::PrefSet(const char* aPref, uint32_t aValue)
+{
+ MOZ_ASSERT(IsPrefsServiceAvailable());
+ Preferences::SetUint(aPref, aValue);
+}
+
+void gfxPrefs::PrefSet(const char* aPref, float aValue)
+{
+ MOZ_ASSERT(IsPrefsServiceAvailable());
+ Preferences::SetFloat(aPref, aValue);
+}
+
+static void
+OnGfxPrefChanged(const char* aPrefname, void* aClosure)
+{
+ reinterpret_cast<gfxPrefs::Pref*>(aClosure)->OnChange();
+}
+
+void gfxPrefs::WatchChanges(const char* aPrefname, Pref* aPref)
+{
+ MOZ_ASSERT(IsPrefsServiceAvailable());
+ Preferences::RegisterCallback(OnGfxPrefChanged, aPrefname, aPref, Preferences::ExactMatch);
+}
+
+void gfxPrefs::UnwatchChanges(const char* aPrefname, Pref* aPref)
+{
+ // The Preferences service can go offline before gfxPrefs is destroyed.
+ if (IsPrefsServiceAvailable()) {
+ Preferences::UnregisterCallback(OnGfxPrefChanged, aPrefname, aPref, Preferences::ExactMatch);
+ }
+}
+
+void gfxPrefs::CopyPrefValue(const bool* aValue, GfxPrefValue* aOutValue)
+{
+ *aOutValue = *aValue;
+}
+
+void gfxPrefs::CopyPrefValue(const int32_t* aValue, GfxPrefValue* aOutValue)
+{
+ *aOutValue = *aValue;
+}
+
+void gfxPrefs::CopyPrefValue(const uint32_t* aValue, GfxPrefValue* aOutValue)
+{
+ *aOutValue = *aValue;
+}
+
+void gfxPrefs::CopyPrefValue(const float* aValue, GfxPrefValue* aOutValue)
+{
+ *aOutValue = *aValue;
+}
+
+void gfxPrefs::CopyPrefValue(const GfxPrefValue* aValue, bool* aOutValue)
+{
+ *aOutValue = aValue->get_bool();
+}
+
+void gfxPrefs::CopyPrefValue(const GfxPrefValue* aValue, int32_t* aOutValue)
+{
+ *aOutValue = aValue->get_int32_t();
+}
+
+void gfxPrefs::CopyPrefValue(const GfxPrefValue* aValue, uint32_t* aOutValue)
+{
+ *aOutValue = aValue->get_uint32_t();
+}
+
+void gfxPrefs::CopyPrefValue(const GfxPrefValue* aValue, float* aOutValue)
+{
+ *aOutValue = aValue->get_float();
+}
diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h
new file mode 100644
index 000000000..e0eb70de3
--- /dev/null
+++ b/gfx/thebes/gfxPrefs.h
@@ -0,0 +1,682 @@
+/* -*- 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_PREFS_H
+#define GFX_PREFS_H
+
+#include <cmath> // for M_PI
+#include <stdint.h>
+#include "mozilla/Assertions.h"
+#include "mozilla/Function.h"
+#include "mozilla/gfx/LoggingConstants.h"
+#include "nsTArray.h"
+
+// First time gfxPrefs::GetSingleton() needs to be called on the main thread,
+// before any of the methods accessing the values are used, but after
+// the Preferences system has been initialized.
+
+// The static methods to access the preference value are safe to call
+// from any thread after that first call.
+
+// To register a preference, you need to add a line in this file using
+// the DECL_GFX_PREF macro.
+//
+// Update argument controls whether we read the preference value and save it
+// or connect with a callback. See UpdatePolicy enum below.
+// Pref is the string with the preference name.
+// Name argument is the name of the static function to create.
+// Type is the type of the preference - bool, int32_t, uint32_t.
+// Default is the default value for the preference.
+//
+// For example this line in the .h:
+// DECL_GFX_PREF(Once,"layers.dump",LayersDump,bool,false);
+// means that you can call
+// bool var = gfxPrefs::LayersDump();
+// from any thread, but that you will only get the preference value of
+// "layers.dump" as it was set at the start of the session (subject to
+// note 2 below). If the value was not set, the default would be false.
+//
+// In another example, this line in the .h:
+// DECL_GFX_PREF(Live,"gl.msaa-level",MSAALevel,uint32_t,2);
+// means that every time you call
+// uint32_t var = gfxPrefs::MSAALevel();
+// from any thread, you will get the most up to date preference value of
+// "gl.msaa-level". If the value is not set, the default would be 2.
+
+// Note 1: Changing a preference from Live to Once is now as simple
+// as changing the Update argument. If your code worked before, it will
+// keep working, and behave as if the user never changes the preference.
+// Things are a bit more complicated and perhaps even dangerous when
+// going from Once to Live, or indeed setting a preference to be Live
+// in the first place, so be careful. You need to be ready for the
+// values changing mid execution, and if you're using those preferences
+// in any setup and initialization, you may need to do extra work.
+
+// Note 2: Prefs can be set by using the corresponding Set method. For
+// example, if the accessor is Foo() then calling SetFoo(...) will update
+// the preference and also change the return value of subsequent Foo() calls.
+// This is true even for 'Once' prefs which otherwise do not change if the
+// pref is updated after initialization. Changing gfxPrefs values in content
+// processes will not affect the result in other processes. Changing gfxPrefs
+// values in the GPU process is not supported at all.
+
+#define DECL_GFX_PREF(Update, Prefname, Name, Type, Default) \
+public: \
+static Type Name() { MOZ_ASSERT(SingletonExists()); return GetSingleton().mPref##Name.mValue; } \
+static void Set##Name(Type aVal) { MOZ_ASSERT(SingletonExists()); \
+ GetSingleton().mPref##Name.Set(UpdatePolicy::Update, Get##Name##PrefName(), aVal); } \
+static const char* Get##Name##PrefName() { return Prefname; } \
+static Type Get##Name##PrefDefault() { return Default; } \
+private: \
+PrefTemplate<UpdatePolicy::Update, Type, Get##Name##PrefDefault, Get##Name##PrefName> mPref##Name
+
+namespace mozilla {
+namespace gfx {
+class GfxPrefValue; // defined in PGPU.ipdl
+} // namespace gfx
+} // namespace mozilla
+
+class gfxPrefs;
+class gfxPrefs final
+{
+ typedef mozilla::gfx::GfxPrefValue GfxPrefValue;
+
+private:
+ // Enums for the update policy.
+ enum class UpdatePolicy {
+ Skip, // Set the value to default, skip any Preferences calls
+ Once, // Evaluate the preference once, unchanged during the session
+ Live // Evaluate the preference and set callback so it stays current/live
+ };
+
+public:
+ class Pref
+ {
+ public:
+ Pref() : mChangeCallback(nullptr)
+ {
+ mIndex = sGfxPrefList->Length();
+ sGfxPrefList->AppendElement(this);
+ }
+
+ size_t Index() const { return mIndex; }
+ void OnChange();
+
+ typedef void (*ChangeCallback)();
+ void SetChangeCallback(ChangeCallback aCallback);
+
+ virtual const char* Name() const = 0;
+
+ // Returns true if the value is default, false if changed.
+ virtual bool HasDefaultValue() const = 0;
+
+ // Returns the pref value as a discriminated union.
+ virtual void GetLiveValue(GfxPrefValue* aOutValue) const = 0;
+
+ // Returns the pref value as a discriminated union.
+ virtual void GetCachedValue(GfxPrefValue* aOutValue) const = 0;
+
+ // Change the cached value. GfxPrefValue must be a compatible type.
+ virtual void SetCachedValue(const GfxPrefValue& aOutValue) = 0;
+
+ protected:
+ void FireChangeCallback();
+
+ private:
+ size_t mIndex;
+ ChangeCallback mChangeCallback;
+ };
+
+ static const nsTArray<Pref*>& all() {
+ return *sGfxPrefList;
+ }
+
+private:
+ // We split out a base class to reduce the number of virtual function
+ // instantiations that we do, which saves code size.
+ template<class T>
+ class TypedPref : public Pref
+ {
+ public:
+ explicit TypedPref(T aValue)
+ : mValue(aValue)
+ {}
+
+ void GetCachedValue(GfxPrefValue* aOutValue) const override {
+ CopyPrefValue(&mValue, aOutValue);
+ }
+ void SetCachedValue(const GfxPrefValue& aOutValue) override {
+ // This is only used in non-XPCOM processes.
+ MOZ_ASSERT(!IsPrefsServiceAvailable());
+
+ T newValue;
+ CopyPrefValue(&aOutValue, &newValue);
+
+ if (mValue != newValue) {
+ mValue = newValue;
+ FireChangeCallback();
+ }
+ }
+
+ protected:
+ T GetLiveValueByName(const char* aPrefName) const {
+ if (IsPrefsServiceAvailable()) {
+ return PrefGet(aPrefName, mValue);
+ }
+ return mValue;
+ }
+
+ public:
+ T mValue;
+ };
+
+ // Since we cannot use const char*, use a function that returns it.
+ template <UpdatePolicy Update, class T, T Default(void), const char* Prefname(void)>
+ class PrefTemplate final : public TypedPref<T>
+ {
+ typedef TypedPref<T> BaseClass;
+ public:
+ PrefTemplate()
+ : BaseClass(Default())
+ {
+ // If not using the Preferences service, values are synced over IPC, so
+ // there's no need to register us as a Preferences observer.
+ if (IsPrefsServiceAvailable()) {
+ Register(Update, Prefname());
+ }
+ // By default we only watch changes in the parent process, to communicate
+ // changes to the GPU process.
+ if (IsParentProcess() && Update == UpdatePolicy::Live) {
+ WatchChanges(Prefname(), this);
+ }
+ }
+ ~PrefTemplate() {
+ if (IsParentProcess() && Update == UpdatePolicy::Live) {
+ UnwatchChanges(Prefname(), this);
+ }
+ }
+ void Register(UpdatePolicy aUpdate, const char* aPreference)
+ {
+ AssertMainThread();
+ switch (aUpdate) {
+ case UpdatePolicy::Skip:
+ break;
+ case UpdatePolicy::Once:
+ this->mValue = PrefGet(aPreference, this->mValue);
+ break;
+ case UpdatePolicy::Live:
+ PrefAddVarCache(&this->mValue, aPreference, this->mValue);
+ break;
+ default:
+ MOZ_CRASH("Incomplete switch");
+ }
+ }
+ void Set(UpdatePolicy aUpdate, const char* aPref, T aValue)
+ {
+ AssertMainThread();
+ PrefSet(aPref, aValue);
+ switch (aUpdate) {
+ case UpdatePolicy::Skip:
+ case UpdatePolicy::Live:
+ break;
+ case UpdatePolicy::Once:
+ this->mValue = PrefGet(aPref, this->mValue);
+ break;
+ default:
+ MOZ_CRASH("Incomplete switch");
+ }
+ }
+ const char *Name() const override {
+ return Prefname();
+ }
+ void GetLiveValue(GfxPrefValue* aOutValue) const override {
+ T value = GetLiveValue();
+ CopyPrefValue(&value, aOutValue);
+ }
+ // When using the Preferences service, the change callback can be triggered
+ // *before* our cached value is updated, so we expose a method to grab the
+ // true live value.
+ T GetLiveValue() const {
+ return BaseClass::GetLiveValueByName(Prefname());
+ }
+ bool HasDefaultValue() const override {
+ return this->mValue == Default();
+ }
+ };
+
+ // This is where DECL_GFX_PREF for each of the preferences should go.
+ // We will keep these in an alphabetical order to make it easier to see if
+ // a method accessing a pref already exists. Just add yours in the list.
+
+ // The apz prefs are explained in AsyncPanZoomController.cpp
+ DECL_GFX_PREF(Live, "apz.allow_checkerboarding", APZAllowCheckerboarding, bool, true);
+ DECL_GFX_PREF(Live, "apz.allow_immediate_handoff", APZAllowImmediateHandoff, bool, true);
+ DECL_GFX_PREF(Live, "apz.allow_zooming", APZAllowZooming, bool, false);
+ DECL_GFX_PREF(Live, "apz.axis_lock.breakout_angle", APZAxisBreakoutAngle, float, float(M_PI / 8.0) /* 22.5 degrees */);
+ DECL_GFX_PREF(Live, "apz.axis_lock.breakout_threshold", APZAxisBreakoutThreshold, float, 1.0f / 32.0f);
+ DECL_GFX_PREF(Live, "apz.axis_lock.direct_pan_angle", APZAllowedDirectPanAngle, float, float(M_PI / 3.0) /* 60 degrees */);
+ DECL_GFX_PREF(Live, "apz.axis_lock.lock_angle", APZAxisLockAngle, float, float(M_PI / 6.0) /* 30 degrees */);
+ DECL_GFX_PREF(Live, "apz.axis_lock.mode", APZAxisLockMode, int32_t, 0);
+ DECL_GFX_PREF(Live, "apz.content_response_timeout", APZContentResponseTimeout, int32_t, 400);
+ DECL_GFX_PREF(Live, "apz.danger_zone_x", APZDangerZoneX, int32_t, 50);
+ DECL_GFX_PREF(Live, "apz.danger_zone_y", APZDangerZoneY, int32_t, 100);
+ DECL_GFX_PREF(Live, "apz.disable_for_scroll_linked_effects", APZDisableForScrollLinkedEffects, bool, false);
+ DECL_GFX_PREF(Live, "apz.displayport_expiry_ms", APZDisplayPortExpiryTime, uint32_t, 15000);
+ DECL_GFX_PREF(Live, "apz.drag.enabled", APZDragEnabled, bool, false);
+ DECL_GFX_PREF(Live, "apz.enlarge_displayport_when_clipped", APZEnlargeDisplayPortWhenClipped, bool, false);
+ DECL_GFX_PREF(Live, "apz.fling_accel_base_mult", APZFlingAccelBaseMultiplier, float, 1.0f);
+ DECL_GFX_PREF(Live, "apz.fling_accel_interval_ms", APZFlingAccelInterval, int32_t, 500);
+ DECL_GFX_PREF(Live, "apz.fling_accel_supplemental_mult", APZFlingAccelSupplementalMultiplier, float, 1.0f);
+ DECL_GFX_PREF(Live, "apz.fling_accel_min_velocity", APZFlingAccelMinVelocity, float, 1.5f);
+ DECL_GFX_PREF(Once, "apz.fling_curve_function_x1", APZCurveFunctionX1, float, 0.0f);
+ DECL_GFX_PREF(Once, "apz.fling_curve_function_x2", APZCurveFunctionX2, float, 1.0f);
+ DECL_GFX_PREF(Once, "apz.fling_curve_function_y1", APZCurveFunctionY1, float, 0.0f);
+ DECL_GFX_PREF(Once, "apz.fling_curve_function_y2", APZCurveFunctionY2, float, 1.0f);
+ DECL_GFX_PREF(Live, "apz.fling_curve_threshold_inches_per_ms", APZCurveThreshold, float, -1.0f);
+ DECL_GFX_PREF(Live, "apz.fling_friction", APZFlingFriction, float, 0.002f);
+ DECL_GFX_PREF(Live, "apz.fling_min_velocity_threshold", APZFlingMinVelocityThreshold, float, 0.5f);
+ DECL_GFX_PREF(Live, "apz.fling_stop_on_tap_threshold", APZFlingStopOnTapThreshold, float, 0.05f);
+ DECL_GFX_PREF(Live, "apz.fling_stopped_threshold", APZFlingStoppedThreshold, float, 0.01f);
+ DECL_GFX_PREF(Live, "apz.highlight_checkerboarded_areas", APZHighlightCheckerboardedAreas, bool, false);
+ DECL_GFX_PREF(Live, "apz.max_velocity_inches_per_ms", APZMaxVelocity, float, -1.0f);
+ DECL_GFX_PREF(Once, "apz.max_velocity_queue_size", APZMaxVelocityQueueSize, uint32_t, 5);
+ DECL_GFX_PREF(Live, "apz.min_skate_speed", APZMinSkateSpeed, float, 1.0f);
+ DECL_GFX_PREF(Live, "apz.minimap.enabled", APZMinimap, bool, false);
+ DECL_GFX_PREF(Live, "apz.minimap.visibility.enabled", APZMinimapVisibilityEnabled, bool, false);
+ DECL_GFX_PREF(Live, "apz.overscroll.enabled", APZOverscrollEnabled, bool, false);
+ DECL_GFX_PREF(Live, "apz.overscroll.min_pan_distance_ratio", APZMinPanDistanceRatio, float, 1.0f);
+ DECL_GFX_PREF(Live, "apz.overscroll.spring_friction", APZOverscrollSpringFriction, float, 0.015f);
+ DECL_GFX_PREF(Live, "apz.overscroll.spring_stiffness", APZOverscrollSpringStiffness, float, 0.001f);
+ DECL_GFX_PREF(Live, "apz.overscroll.stop_distance_threshold", APZOverscrollStopDistanceThreshold, float, 5.0f);
+ DECL_GFX_PREF(Live, "apz.overscroll.stop_velocity_threshold", APZOverscrollStopVelocityThreshold, float, 0.01f);
+ DECL_GFX_PREF(Live, "apz.overscroll.stretch_factor", APZOverscrollStretchFactor, float, 0.5f);
+ DECL_GFX_PREF(Live, "apz.paint_skipping.enabled", APZPaintSkipping, bool, true);
+ DECL_GFX_PREF(Live, "apz.peek_messages.enabled", APZPeekMessages, bool, true);
+ DECL_GFX_PREF(Live, "apz.printtree", APZPrintTree, bool, false);
+ DECL_GFX_PREF(Live, "apz.record_checkerboarding", APZRecordCheckerboarding, bool, false);
+ DECL_GFX_PREF(Live, "apz.test.fails_with_native_injection", APZTestFailsWithNativeInjection, bool, false);
+ DECL_GFX_PREF(Live, "apz.test.logging_enabled", APZTestLoggingEnabled, bool, false);
+ DECL_GFX_PREF(Live, "apz.touch_move_tolerance", APZTouchMoveTolerance, float, 0.0);
+ DECL_GFX_PREF(Live, "apz.touch_start_tolerance", APZTouchStartTolerance, float, 1.0f/4.5f);
+ DECL_GFX_PREF(Live, "apz.velocity_bias", APZVelocityBias, float, 0.0f);
+ DECL_GFX_PREF(Live, "apz.velocity_relevance_time_ms", APZVelocityRelevanceTime, uint32_t, 150);
+ DECL_GFX_PREF(Live, "apz.x_skate_highmem_adjust", APZXSkateHighMemAdjust, float, 0.0f);
+ DECL_GFX_PREF(Live, "apz.x_skate_size_multiplier", APZXSkateSizeMultiplier, float, 1.5f);
+ DECL_GFX_PREF(Live, "apz.x_stationary_size_multiplier", APZXStationarySizeMultiplier, float, 3.0f);
+ DECL_GFX_PREF(Live, "apz.y_skate_highmem_adjust", APZYSkateHighMemAdjust, float, 0.0f);
+ DECL_GFX_PREF(Live, "apz.y_skate_size_multiplier", APZYSkateSizeMultiplier, float, 2.5f);
+ DECL_GFX_PREF(Live, "apz.y_stationary_size_multiplier", APZYStationarySizeMultiplier, float, 3.5f);
+ DECL_GFX_PREF(Live, "apz.zoom_animation_duration_ms", APZZoomAnimationDuration, int32_t, 250);
+ DECL_GFX_PREF(Live, "apz.scale_repaint_delay_ms", APZScaleRepaintDelay, int32_t, 500);
+
+ DECL_GFX_PREF(Live, "browser.ui.zoom.force-user-scalable", ForceUserScalable, bool, false);
+ DECL_GFX_PREF(Live, "browser.viewport.desktopWidth", DesktopViewportWidth, int32_t, 980);
+
+ DECL_GFX_PREF(Live, "dom.ipc.plugins.asyncdrawing.enabled", PluginAsyncDrawingEnabled, bool, false);
+ DECL_GFX_PREF(Live, "dom.meta-viewport.enabled", MetaViewportEnabled, bool, false);
+ DECL_GFX_PREF(Once, "dom.vr.enabled", VREnabled, bool, false);
+ DECL_GFX_PREF(Once, "dom.vr.oculus.enabled", VROculusEnabled, bool, true);
+ DECL_GFX_PREF(Once, "dom.vr.openvr.enabled", VROpenVREnabled, bool, false);
+ DECL_GFX_PREF(Once, "dom.vr.osvr.enabled", VROSVREnabled, bool, false);
+ DECL_GFX_PREF(Live, "dom.vr.poseprediction.enabled", VRPosePredictionEnabled, bool, false);
+ DECL_GFX_PREF(Live, "dom.w3c_pointer_events.enabled", PointerEventsEnabled, bool, false);
+ DECL_GFX_PREF(Live, "dom.w3c_touch_events.enabled", TouchEventsEnabled, int32_t, 0);
+
+ DECL_GFX_PREF(Live, "general.smoothScroll", SmoothScrollEnabled, bool, true);
+ DECL_GFX_PREF(Live, "general.smoothScroll.currentVelocityWeighting",
+ SmoothScrollCurrentVelocityWeighting, float, 0.25);
+ DECL_GFX_PREF(Live, "general.smoothScroll.durationToIntervalRatio",
+ SmoothScrollDurationToIntervalRatio, int32_t, 200);
+ DECL_GFX_PREF(Live, "general.smoothScroll.mouseWheel", WheelSmoothScrollEnabled, bool, true);
+ DECL_GFX_PREF(Live, "general.smoothScroll.mouseWheel.durationMaxMS",
+ WheelSmoothScrollMaxDurationMs, int32_t, 400);
+ DECL_GFX_PREF(Live, "general.smoothScroll.mouseWheel.durationMinMS",
+ WheelSmoothScrollMinDurationMs, int32_t, 200);
+ DECL_GFX_PREF(Live, "general.smoothScroll.pages", PageSmoothScrollEnabled, bool, true);
+ DECL_GFX_PREF(Live, "general.smoothScroll.pages.durationMaxMS",
+ PageSmoothScrollMaxDurationMs, int32_t, 150);
+ DECL_GFX_PREF(Live, "general.smoothScroll.pages.durationMinMS",
+ PageSmoothScrollMinDurationMs, int32_t, 150);
+ DECL_GFX_PREF(Live, "general.smoothScroll.pixels", PixelSmoothScrollEnabled, bool, true);
+ DECL_GFX_PREF(Live, "general.smoothScroll.pixels.durationMaxMS",
+ PixelSmoothScrollMaxDurationMs, int32_t, 150);
+ DECL_GFX_PREF(Live, "general.smoothScroll.pixels.durationMinMS",
+ PixelSmoothScrollMinDurationMs, int32_t, 150);
+ DECL_GFX_PREF(Live, "general.smoothScroll.stopDecelerationWeighting",
+ SmoothScrollStopDecelerationWeighting, float, 0.4f);
+
+ DECL_GFX_PREF(Once, "gfx.android.rgb16.force", AndroidRGB16Force, bool, false);
+#if defined(ANDROID)
+ DECL_GFX_PREF(Once, "gfx.apitrace.enabled", UseApitrace, bool, false);
+#endif
+#if defined(RELEASE_OR_BETA)
+ // "Skip" means this is locked to the default value in beta and release.
+ DECL_GFX_PREF(Skip, "gfx.blocklist.all", BlocklistAll, int32_t, 0);
+#else
+ DECL_GFX_PREF(Once, "gfx.blocklist.all", BlocklistAll, int32_t, 0);
+#endif
+ DECL_GFX_PREF(Live, "gfx.canvas.auto_accelerate.min_calls", CanvasAutoAccelerateMinCalls, int32_t, 4);
+ DECL_GFX_PREF(Live, "gfx.canvas.auto_accelerate.min_frames", CanvasAutoAccelerateMinFrames, int32_t, 30);
+ DECL_GFX_PREF(Live, "gfx.canvas.auto_accelerate.min_seconds", CanvasAutoAccelerateMinSeconds, float, 5.0f);
+ DECL_GFX_PREF(Live, "gfx.canvas.azure.accelerated", CanvasAzureAccelerated, bool, false);
+ // 0x7fff is the maximum supported xlib surface size and is more than enough for canvases.
+ DECL_GFX_PREF(Live, "gfx.canvas.max-size", MaxCanvasSize, int32_t, 0x7fff);
+ DECL_GFX_PREF(Once, "gfx.canvas.skiagl.cache-items", CanvasSkiaGLCacheItems, int32_t, 256);
+ DECL_GFX_PREF(Once, "gfx.canvas.skiagl.cache-size", CanvasSkiaGLCacheSize, int32_t, 96);
+ DECL_GFX_PREF(Once, "gfx.canvas.skiagl.dynamic-cache", CanvasSkiaGLDynamicCache, bool, false);
+
+ DECL_GFX_PREF(Live, "gfx.color_management.enablev4", CMSEnableV4, bool, false);
+ DECL_GFX_PREF(Live, "gfx.color_management.mode", CMSMode, int32_t,-1);
+ // The zero default here should match QCMS_INTENT_DEFAULT from qcms.h
+ DECL_GFX_PREF(Live, "gfx.color_management.rendering_intent", CMSRenderingIntent, int32_t, 0);
+
+ DECL_GFX_PREF(Once, "gfx.device-reset.limit", DeviceResetLimitCount, int32_t, 10);
+ DECL_GFX_PREF(Once, "gfx.device-reset.threshold-ms", DeviceResetThresholdMilliseconds, int32_t, -1);
+
+ DECL_GFX_PREF(Once, "gfx.direct2d.disabled", Direct2DDisabled, bool, false);
+ DECL_GFX_PREF(Once, "gfx.direct2d.force-enabled", Direct2DForceEnabled, bool, false);
+ DECL_GFX_PREF(Live, "gfx.direct3d11.reuse-decoder-device", Direct3D11ReuseDecoderDevice, int32_t, -1);
+ DECL_GFX_PREF(Live, "gfx.draw-color-bars", CompositorDrawColorBars, bool, false);
+ DECL_GFX_PREF(Once, "gfx.e10s.hide-plugins-for-scroll", HidePluginsForScroll, bool, true);
+ DECL_GFX_PREF(Live, "gfx.gralloc.fence-with-readpixels", GrallocFenceWithReadPixels, bool, false);
+ DECL_GFX_PREF(Live, "gfx.layerscope.enabled", LayerScopeEnabled, bool, false);
+ DECL_GFX_PREF(Live, "gfx.layerscope.port", LayerScopePort, int32_t, 23456);
+ // Note that "gfx.logging.level" is defined in Logging.h.
+ DECL_GFX_PREF(Live, "gfx.logging.level", GfxLoggingLevel, int32_t, mozilla::gfx::LOG_DEFAULT);
+ DECL_GFX_PREF(Once, "gfx.logging.crash.length", GfxLoggingCrashLength, uint32_t, 16);
+ DECL_GFX_PREF(Live, "gfx.logging.painted-pixel-count.enabled",GfxLoggingPaintedPixelCountEnabled, bool, false);
+ // The maximums here are quite conservative, we can tighten them if problems show up.
+ DECL_GFX_PREF(Once, "gfx.logging.texture-usage.enabled", GfxLoggingTextureUsageEnabled, bool, false);
+ DECL_GFX_PREF(Once, "gfx.logging.peak-texture-usage.enabled",GfxLoggingPeakTextureUsageEnabled, bool, false);
+ DECL_GFX_PREF(Once, "gfx.max-alloc-size", MaxAllocSize, int32_t, (int32_t)500000000);
+ DECL_GFX_PREF(Once, "gfx.max-texture-size", MaxTextureSize, int32_t, (int32_t)32767);
+ DECL_GFX_PREF(Live, "gfx.partialpresent.force", PartialPresent, int32_t, 0);
+ DECL_GFX_PREF(Live, "gfx.perf-warnings.enabled", PerfWarnings, bool, false);
+ DECL_GFX_PREF(Live, "gfx.SurfaceTexture.detach.enabled", SurfaceTextureDetachEnabled, bool, true);
+ DECL_GFX_PREF(Live, "gfx.testing.device-reset", DeviceResetForTesting, int32_t, 0);
+ DECL_GFX_PREF(Live, "gfx.testing.device-fail", DeviceFailForTesting, bool, false);
+ DECL_GFX_PREF(Once, "gfx.text.disable-aa", DisableAllTextAA, bool, false);
+ DECL_GFX_PREF(Live, "gfx.ycbcr.accurate-conversion", YCbCrAccurateConversion, bool, false);
+
+ DECL_GFX_PREF(Live, "gfx.content.use-native-pushlayer", UseNativePushLayer, bool, false);
+ DECL_GFX_PREF(Live, "gfx.content.always-paint", AlwaysPaint, bool, false);
+
+ // Disable surface sharing due to issues with compatible FBConfigs on
+ // NVIDIA drivers as described in bug 1193015.
+ DECL_GFX_PREF(Live, "gfx.use-glx-texture-from-pixmap", UseGLXTextureFromPixmap, bool, false);
+
+ DECL_GFX_PREF(Once, "gfx.use-iosurface-textures", UseIOSurfaceTextures, bool, false);
+
+ // These times should be in milliseconds
+ DECL_GFX_PREF(Once, "gfx.touch.resample.delay-threshold", TouchResampleVsyncDelayThreshold, int32_t, 20);
+ DECL_GFX_PREF(Once, "gfx.touch.resample.max-predict", TouchResampleMaxPredict, int32_t, 8);
+ DECL_GFX_PREF(Once, "gfx.touch.resample.min-delta", TouchResampleMinDelta, int32_t, 2);
+ DECL_GFX_PREF(Once, "gfx.touch.resample.old-touch-threshold",TouchResampleOldTouchThreshold, int32_t, 17);
+ DECL_GFX_PREF(Once, "gfx.touch.resample.vsync-adjust", TouchVsyncSampleAdjust, int32_t, 5);
+
+ DECL_GFX_PREF(Live, "gfx.vsync.collect-scroll-transforms", CollectScrollTransforms, bool, false);
+ // On b2g, in really bad cases, I've seen up to 80 ms delays between touch events and the main thread
+ // processing them. So 80 ms / 16 = 5 vsync events. Double it up just to be on the safe side, so 10.
+ DECL_GFX_PREF(Once, "gfx.vsync.compositor.unobserve-count", CompositorUnobserveCount, int32_t, 10);
+ // Use vsync events generated by hardware
+ DECL_GFX_PREF(Once, "gfx.work-around-driver-bugs", WorkAroundDriverBugs, bool, true);
+ DECL_GFX_PREF(Once, "gfx.screen-mirroring.enabled", ScreenMirroringEnabled, bool, false);
+
+ DECL_GFX_PREF(Live, "gl.ignore-dx-interop2-blacklist", IgnoreDXInterop2Blacklist, bool, false);
+ DECL_GFX_PREF(Live, "gl.msaa-level", MSAALevel, uint32_t, 2);
+#if defined(XP_MACOSX)
+ DECL_GFX_PREF(Live, "gl.multithreaded", GLMultithreaded, bool, false);
+#endif
+ DECL_GFX_PREF(Live, "gl.require-hardware", RequireHardwareGL, bool, false);
+
+ DECL_GFX_PREF(Once, "image.cache.size", ImageCacheSize, int32_t, 5*1024*1024);
+ DECL_GFX_PREF(Once, "image.cache.timeweight", ImageCacheTimeWeight, int32_t, 500);
+ DECL_GFX_PREF(Live, "image.decode-immediately.enabled", ImageDecodeImmediatelyEnabled, bool, false);
+ DECL_GFX_PREF(Live, "image.downscale-during-decode.enabled", ImageDownscaleDuringDecodeEnabled, bool, true);
+ DECL_GFX_PREF(Live, "image.infer-src-animation.threshold-ms", ImageInferSrcAnimationThresholdMS, uint32_t, 2000);
+ DECL_GFX_PREF(Once, "image.mem.decode_bytes_at_a_time", ImageMemDecodeBytesAtATime, uint32_t, 200000);
+ DECL_GFX_PREF(Live, "image.mem.discardable", ImageMemDiscardable, bool, false);
+ DECL_GFX_PREF(Once, "image.mem.surfacecache.discard_factor", ImageMemSurfaceCacheDiscardFactor, uint32_t, 1);
+ DECL_GFX_PREF(Once, "image.mem.surfacecache.max_size_kb", ImageMemSurfaceCacheMaxSizeKB, uint32_t, 100 * 1024);
+ DECL_GFX_PREF(Once, "image.mem.surfacecache.min_expiration_ms", ImageMemSurfaceCacheMinExpirationMS, uint32_t, 60*1000);
+ DECL_GFX_PREF(Once, "image.mem.surfacecache.size_factor", ImageMemSurfaceCacheSizeFactor, uint32_t, 64);
+ DECL_GFX_PREF(Live, "image.mozsamplesize.enabled", ImageMozSampleSizeEnabled, bool, false);
+ DECL_GFX_PREF(Once, "image.multithreaded_decoding.limit", ImageMTDecodingLimit, int32_t, -1);
+
+ DECL_GFX_PREF(Once, "layers.acceleration.disabled", LayersAccelerationDisabledDoNotUseDirectly, bool, false);
+ DECL_GFX_PREF(Live, "layers.acceleration.draw-fps", LayersDrawFPS, bool, false);
+ DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.print-histogram", FPSPrintHistogram, bool, false);
+ DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.write-to-file", WriteFPSToFile, bool, false);
+ DECL_GFX_PREF(Once, "layers.acceleration.force-enabled", LayersAccelerationForceEnabledDoNotUseDirectly, bool, false);
+ DECL_GFX_PREF(Once, "layers.allow-d3d9-fallback", LayersAllowD3D9Fallback, bool, false);
+ DECL_GFX_PREF(Once, "layers.amd-switchable-gfx.enabled", LayersAMDSwitchableGfxEnabled, bool, false);
+ DECL_GFX_PREF(Once, "layers.async-pan-zoom.enabled", AsyncPanZoomEnabledDoNotUseDirectly, bool, true);
+ DECL_GFX_PREF(Once, "layers.async-pan-zoom.separate-event-thread", AsyncPanZoomSeparateEventThread, bool, false);
+ DECL_GFX_PREF(Live, "layers.bench.enabled", LayersBenchEnabled, bool, false);
+ DECL_GFX_PREF(Once, "layers.bufferrotation.enabled", BufferRotationEnabled, bool, true);
+ DECL_GFX_PREF(Live, "layers.child-process-shutdown", ChildProcessShutdown, bool, true);
+#ifdef MOZ_GFX_OPTIMIZE_MOBILE
+ // If MOZ_GFX_OPTIMIZE_MOBILE is defined, we force component alpha off
+ // and ignore the preference.
+ DECL_GFX_PREF(Skip, "layers.componentalpha.enabled", ComponentAlphaEnabled, bool, false);
+#else
+ // If MOZ_GFX_OPTIMIZE_MOBILE is not defined, we actually take the
+ // preference value, defaulting to true.
+ DECL_GFX_PREF(Once, "layers.componentalpha.enabled", ComponentAlphaEnabled, bool, true);
+#endif
+ DECL_GFX_PREF(Live, "layers.composer2d.enabled", Composer2DCompositionEnabled, bool, false);
+ DECL_GFX_PREF(Once, "layers.d3d11.force-warp", LayersD3D11ForceWARP, bool, false);
+ DECL_GFX_PREF(Live, "layers.deaa.enabled", LayersDEAAEnabled, bool, false);
+ DECL_GFX_PREF(Live, "layers.draw-bigimage-borders", DrawBigImageBorders, bool, false);
+ DECL_GFX_PREF(Live, "layers.draw-borders", DrawLayerBorders, bool, false);
+ DECL_GFX_PREF(Live, "layers.draw-tile-borders", DrawTileBorders, bool, false);
+ DECL_GFX_PREF(Live, "layers.draw-layer-info", DrawLayerInfo, bool, false);
+ DECL_GFX_PREF(Live, "layers.dump", LayersDump, bool, false);
+ DECL_GFX_PREF(Live, "layers.dump-texture", LayersDumpTexture, bool, false);
+#ifdef MOZ_DUMP_PAINTING
+ DECL_GFX_PREF(Live, "layers.dump-client-layers", DumpClientLayers, bool, false);
+ DECL_GFX_PREF(Live, "layers.dump-decision", LayersDumpDecision, bool, false);
+ DECL_GFX_PREF(Live, "layers.dump-host-layers", DumpHostLayers, bool, false);
+#endif
+
+ // 0 is "no change" for contrast, positive values increase it, negative values
+ // decrease it until we hit mid gray at -1 contrast, after that it gets weird.
+ DECL_GFX_PREF(Live, "layers.effect.contrast", LayersEffectContrast, float, 0.0f);
+ DECL_GFX_PREF(Live, "layers.effect.grayscale", LayersEffectGrayscale, bool, false);
+ DECL_GFX_PREF(Live, "layers.effect.invert", LayersEffectInvert, bool, false);
+ DECL_GFX_PREF(Once, "layers.enable-tiles", LayersTilesEnabled, bool, false);
+ DECL_GFX_PREF(Live, "layers.flash-borders", FlashLayerBorders, bool, false);
+ DECL_GFX_PREF(Once, "layers.force-shmem-tiles", ForceShmemTiles, bool, false);
+ DECL_GFX_PREF(Live, "layers.frame-counter", DrawFrameCounter, bool, false);
+ DECL_GFX_PREF(Once, "layers.gpu-process.dev.enabled", GPUProcessDevEnabled, bool, false);
+ DECL_GFX_PREF(Once, "layers.gpu-process.dev.force-enabled", GPUProcessDevForceEnabled, bool, false);
+ DECL_GFX_PREF(Once, "layers.gpu-process.dev.timeout_ms", GPUProcessDevTimeoutMs, int32_t, 5000);
+ DECL_GFX_PREF(Live, "layers.gpu-process.dev.max_restarts", GPUProcessDevMaxRestarts, int32_t, 0);
+ DECL_GFX_PREF(Once, "layers.gralloc.disable", DisableGralloc, bool, false);
+ DECL_GFX_PREF(Live, "layers.low-precision-buffer", UseLowPrecisionBuffer, bool, false);
+ DECL_GFX_PREF(Live, "layers.low-precision-opacity", LowPrecisionOpacity, float, 1.0f);
+ DECL_GFX_PREF(Live, "layers.low-precision-resolution", LowPrecisionResolution, float, 0.25f);
+ DECL_GFX_PREF(Live, "layers.max-active", MaxActiveLayers, int32_t, -1);
+ DECL_GFX_PREF(Once, "layers.offmainthreadcomposition.force-disabled", LayersOffMainThreadCompositionForceDisabled, bool, false);
+ DECL_GFX_PREF(Live, "layers.offmainthreadcomposition.frame-rate", LayersCompositionFrameRate, int32_t,-1);
+ DECL_GFX_PREF(Live, "layers.orientation.sync.timeout", OrientationSyncMillis, uint32_t, (uint32_t)0);
+ DECL_GFX_PREF(Once, "layers.overzealous-gralloc-unlocking", OverzealousGrallocUnlocking, bool, false);
+ DECL_GFX_PREF(Once, "layers.prefer-d3d9", LayersPreferD3D9, bool, false);
+ DECL_GFX_PREF(Once, "layers.prefer-opengl", LayersPreferOpenGL, bool, false);
+ DECL_GFX_PREF(Live, "layers.progressive-paint", ProgressivePaint, bool, false);
+ DECL_GFX_PREF(Live, "layers.shared-buffer-provider.enabled", PersistentBufferProviderSharedEnabled, bool, false);
+ DECL_GFX_PREF(Live, "layers.single-tile.enabled", LayersSingleTileEnabled, bool, true);
+ DECL_GFX_PREF(Once, "layers.stereo-video.enabled", StereoVideoEnabled, bool, false);
+
+ // We allow for configurable and rectangular tile size to avoid wasting memory on devices whose
+ // screen size does not align nicely to the default tile size. Although layers can be any size,
+ // they are often the same size as the screen, especially for width.
+ DECL_GFX_PREF(Once, "layers.tile-width", LayersTileWidth, int32_t, 256);
+ DECL_GFX_PREF(Once, "layers.tile-height", LayersTileHeight, int32_t, 256);
+ DECL_GFX_PREF(Once, "layers.tile-initial-pool-size", LayersTileInitialPoolSize, uint32_t, (uint32_t)50);
+ DECL_GFX_PREF(Once, "layers.tile-pool-unused-size", LayersTilePoolUnusedSize, uint32_t, (uint32_t)10);
+ DECL_GFX_PREF(Once, "layers.tile-pool-shrink-timeout", LayersTilePoolShrinkTimeout, uint32_t, (uint32_t)50);
+ DECL_GFX_PREF(Once, "layers.tile-pool-clear-timeout", LayersTilePoolClearTimeout, uint32_t, (uint32_t)5000);
+ DECL_GFX_PREF(Once, "layers.tiles.adjust", LayersTilesAdjust, bool, true);
+ DECL_GFX_PREF(Once, "layers.tiles.edge-padding", TileEdgePaddingEnabled, bool, true);
+ DECL_GFX_PREF(Live, "layers.tiles.fade-in.enabled", LayerTileFadeInEnabled, bool, false);
+ DECL_GFX_PREF(Live, "layers.tiles.fade-in.duration-ms", LayerTileFadeInDuration, uint32_t, 250);
+ DECL_GFX_PREF(Live, "layers.transaction.warning-ms", LayerTransactionWarning, uint32_t, 200);
+ DECL_GFX_PREF(Once, "layers.uniformity-info", UniformityInfo, bool, false);
+ DECL_GFX_PREF(Once, "layers.use-image-offscreen-surfaces", UseImageOffscreenSurfaces, bool, true);
+ DECL_GFX_PREF(Live, "layers.draw-mask-debug", DrawMaskLayer, bool, false);
+
+ DECL_GFX_PREF(Live, "layout.css.scroll-behavior.damping-ratio", ScrollBehaviorDampingRatio, float, 1.0f);
+ DECL_GFX_PREF(Live, "layout.css.scroll-behavior.enabled", ScrollBehaviorEnabled, bool, true);
+ DECL_GFX_PREF(Live, "layout.css.scroll-behavior.spring-constant", ScrollBehaviorSpringConstant, float, 250.0f);
+ DECL_GFX_PREF(Live, "layout.css.scroll-snap.prediction-max-velocity", ScrollSnapPredictionMaxVelocity, int32_t, 2000);
+ DECL_GFX_PREF(Live, "layout.css.scroll-snap.prediction-sensitivity", ScrollSnapPredictionSensitivity, float, 0.750f);
+ DECL_GFX_PREF(Live, "layout.css.scroll-snap.proximity-threshold", ScrollSnapProximityThreshold, int32_t, 200);
+ DECL_GFX_PREF(Live, "layout.css.touch_action.enabled", TouchActionEnabled, bool, false);
+ DECL_GFX_PREF(Live, "layout.display-list.dump", LayoutDumpDisplayList, bool, false);
+ DECL_GFX_PREF(Live, "layout.display-list.dump-content", LayoutDumpDisplayListContent, bool, false);
+ DECL_GFX_PREF(Live, "layout.event-regions.enabled", LayoutEventRegionsEnabledDoNotUseDirectly, bool, false);
+ DECL_GFX_PREF(Once, "layout.frame_rate", LayoutFrameRate, int32_t, -1);
+ DECL_GFX_PREF(Once, "layout.paint_rects_separately", LayoutPaintRectsSeparately, bool, true);
+
+ // This and code dependent on it should be removed once containerless scrolling looks stable.
+ DECL_GFX_PREF(Once, "layout.scroll.root-frame-containers", LayoutUseContainersForRootFrames, bool, true);
+
+ DECL_GFX_PREF(Once, "media.hardware-video-decoding.force-enabled",
+ HardwareVideoDecodingForceEnabled, bool, false);
+#ifdef XP_WIN
+ DECL_GFX_PREF(Live, "media.windows-media-foundation.allow-d3d11-dxva", PDMWMFAllowD3D11, bool, true);
+ DECL_GFX_PREF(Live, "media.windows-media-foundation.max-dxva-videos", PDMWMFMaxDXVAVideos, uint32_t, 8);
+ DECL_GFX_PREF(Live, "media.wmf.low-latency.enabled", PDMWMFLowLatencyEnabled, bool, false);
+ DECL_GFX_PREF(Live, "media.wmf.skip-blacklist", PDMWMFSkipBlacklist, bool, false);
+#endif
+
+ // These affect how line scrolls from wheel events will be accelerated.
+ DECL_GFX_PREF(Live, "mousewheel.acceleration.factor", MouseWheelAccelerationFactor, int32_t, -1);
+ DECL_GFX_PREF(Live, "mousewheel.acceleration.start", MouseWheelAccelerationStart, int32_t, -1);
+
+ // This affects whether events will be routed through APZ or not.
+ DECL_GFX_PREF(Live, "mousewheel.system_scroll_override_on_root_content.enabled",
+ MouseWheelHasRootScrollDeltaOverride, bool, false);
+ DECL_GFX_PREF(Live, "mousewheel.system_scroll_override_on_root_content.horizontal.factor",
+ MouseWheelRootScrollHorizontalFactor, int32_t, 0);
+ DECL_GFX_PREF(Live, "mousewheel.system_scroll_override_on_root_content.vertical.factor",
+ MouseWheelRootScrollVerticalFactor, int32_t, 0);
+ DECL_GFX_PREF(Live, "mousewheel.transaction.ignoremovedelay",MouseWheelIgnoreMoveDelayMs, int32_t, (int32_t)100);
+ DECL_GFX_PREF(Live, "mousewheel.transaction.timeout", MouseWheelTransactionTimeoutMs, int32_t, (int32_t)1500);
+
+ DECL_GFX_PREF(Live, "nglayout.debug.widget_update_flashing", WidgetUpdateFlashing, bool, false);
+
+ DECL_GFX_PREF(Live, "test.events.async.enabled", TestEventsAsyncEnabled, bool, false);
+ DECL_GFX_PREF(Live, "test.mousescroll", MouseScrollTestingEnabled, bool, false);
+
+ DECL_GFX_PREF(Live, "ui.click_hold_context_menus.delay", UiClickHoldContextMenusDelay, int32_t, 500);
+
+ // WebGL (for pref access from Worker threads)
+ DECL_GFX_PREF(Live, "webgl.all-angle-options", WebGLAllANGLEOptions, bool, false);
+ DECL_GFX_PREF(Live, "webgl.angle.force-d3d11", WebGLANGLEForceD3D11, bool, false);
+ DECL_GFX_PREF(Live, "webgl.angle.try-d3d11", WebGLANGLETryD3D11, bool, false);
+ DECL_GFX_PREF(Live, "webgl.angle.force-warp", WebGLANGLEForceWARP, bool, false);
+ DECL_GFX_PREF(Live, "webgl.bypass-shader-validation", WebGLBypassShaderValidator, bool, true);
+ DECL_GFX_PREF(Live, "webgl.can-lose-context-in-foreground", WebGLCanLoseContextInForeground, bool, true);
+ DECL_GFX_PREF(Live, "webgl.default-no-alpha", WebGLDefaultNoAlpha, bool, false);
+ DECL_GFX_PREF(Live, "webgl.disable-angle", WebGLDisableANGLE, bool, false);
+ DECL_GFX_PREF(Live, "webgl.disable-wgl", WebGLDisableWGL, bool, false);
+ DECL_GFX_PREF(Live, "webgl.disable-extensions", WebGLDisableExtensions, bool, false);
+ DECL_GFX_PREF(Live, "webgl.dxgl.enabled", WebGLDXGLEnabled, bool, false);
+ DECL_GFX_PREF(Live, "webgl.dxgl.needs-finish", WebGLDXGLNeedsFinish, bool, false);
+
+ DECL_GFX_PREF(Live, "webgl.disable-fail-if-major-performance-caveat",
+ WebGLDisableFailIfMajorPerformanceCaveat, bool, false);
+ DECL_GFX_PREF(Live, "webgl.disable-DOM-blit-uploads",
+ WebGLDisableDOMBlitUploads, bool, false);
+
+ DECL_GFX_PREF(Live, "webgl.disabled", WebGLDisabled, bool, false);
+
+ DECL_GFX_PREF(Live, "webgl.enable-draft-extensions", WebGLDraftExtensionsEnabled, bool, false);
+ DECL_GFX_PREF(Live, "webgl.enable-privileged-extensions", WebGLPrivilegedExtensionsEnabled, bool, false);
+ DECL_GFX_PREF(Live, "webgl.enable-webgl2", WebGL2Enabled, bool, true);
+ DECL_GFX_PREF(Live, "webgl.force-enabled", WebGLForceEnabled, bool, false);
+ DECL_GFX_PREF(Once, "webgl.force-layers-readback", WebGLForceLayersReadback, bool, false);
+ DECL_GFX_PREF(Live, "webgl.lose-context-on-memory-pressure", WebGLLoseContextOnMemoryPressure, bool, false);
+ DECL_GFX_PREF(Live, "webgl.max-warnings-per-context", WebGLMaxWarningsPerContext, uint32_t, 32);
+ DECL_GFX_PREF(Live, "webgl.min_capability_mode", WebGLMinCapabilityMode, bool, false);
+ DECL_GFX_PREF(Live, "webgl.msaa-force", WebGLForceMSAA, bool, false);
+ DECL_GFX_PREF(Live, "webgl.prefer-16bpp", WebGLPrefer16bpp, bool, false);
+ DECL_GFX_PREF(Live, "webgl.restore-context-when-visible", WebGLRestoreWhenVisible, bool, true);
+ DECL_GFX_PREF(Live, "webgl.allow-immediate-queries", WebGLImmediateQueries, bool, false);
+ DECL_GFX_PREF(Live, "webgl.allow-fb-invalidation", WebGLFBInvalidation, bool, false);
+
+ DECL_GFX_PREF(Live, "webgl.webgl2-compat-mode", WebGL2CompatMode, bool, false);
+
+ // WARNING:
+ // Please make sure that you've added your new preference to the list above in alphabetical order.
+ // Please do not just append it to the end of the list.
+
+public:
+ // Manage the singleton:
+ static gfxPrefs& GetSingleton()
+ {
+ MOZ_ASSERT(!sInstanceHasBeenDestroyed, "Should never recreate a gfxPrefs instance!");
+ if (!sInstance) {
+ sGfxPrefList = new nsTArray<Pref*>();
+ sInstance = new gfxPrefs;
+ sInstance->Init();
+ }
+ MOZ_ASSERT(SingletonExists());
+ return *sInstance;
+ }
+ static void DestroySingleton();
+ static bool SingletonExists();
+
+private:
+ static gfxPrefs* sInstance;
+ static bool sInstanceHasBeenDestroyed;
+ static nsTArray<Pref*>* sGfxPrefList;
+
+private:
+ // The constructor cannot access GetSingleton(), since sInstance (necessarily)
+ // has not been assigned yet. Follow-up initialization that needs GetSingleton()
+ // must be added to Init().
+ void Init();
+
+ static bool IsPrefsServiceAvailable();
+ static bool IsParentProcess();
+ // Creating these to avoid having to include Preferences.h in the .h
+ static void PrefAddVarCache(bool*, const char*, bool);
+ static void PrefAddVarCache(int32_t*, const char*, int32_t);
+ static void PrefAddVarCache(uint32_t*, const char*, uint32_t);
+ static void PrefAddVarCache(float*, const char*, float);
+ static bool PrefGet(const char*, bool);
+ static int32_t PrefGet(const char*, int32_t);
+ static uint32_t PrefGet(const char*, uint32_t);
+ static float PrefGet(const char*, float);
+ static void PrefSet(const char* aPref, bool aValue);
+ static void PrefSet(const char* aPref, int32_t aValue);
+ static void PrefSet(const char* aPref, uint32_t aValue);
+ static void PrefSet(const char* aPref, float aValue);
+ static void WatchChanges(const char* aPrefname, Pref* aPref);
+ static void UnwatchChanges(const char* aPrefname, Pref* aPref);
+ // Creating these to avoid having to include PGPU.h in the .h
+ static void CopyPrefValue(const bool* aValue, GfxPrefValue* aOutValue);
+ static void CopyPrefValue(const int32_t* aValue, GfxPrefValue* aOutValue);
+ static void CopyPrefValue(const uint32_t* aValue, GfxPrefValue* aOutValue);
+ static void CopyPrefValue(const float* aValue, GfxPrefValue* aOutValue);
+ static void CopyPrefValue(const GfxPrefValue* aValue, bool* aOutValue);
+ static void CopyPrefValue(const GfxPrefValue* aValue, int32_t* aOutValue);
+ static void CopyPrefValue(const GfxPrefValue* aValue, uint32_t* aOutValue);
+ static void CopyPrefValue(const GfxPrefValue* aValue, float* aOutValue);
+
+ static void AssertMainThread();
+
+ gfxPrefs();
+ ~gfxPrefs();
+ gfxPrefs(const gfxPrefs&) = delete;
+ gfxPrefs& operator=(const gfxPrefs&) = delete;
+};
+
+#undef DECL_GFX_PREF /* Don't need it outside of this file */
+
+#endif /* GFX_PREFS_H */
diff --git a/gfx/thebes/gfxQuad.h b/gfx/thebes/gfxQuad.h
new file mode 100644
index 000000000..8dd7a26e1
--- /dev/null
+++ b/gfx/thebes/gfxQuad.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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, min_x);
+ max_x = std::max(mPoints[i].x, max_x);
+ min_y = std::min(mPoints[i].y, min_y);
+ max_y = std::max(mPoints[i].y, 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 000000000..f14b71d77
--- /dev/null
+++ b/gfx/thebes/gfxQuartzNativeDrawing.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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->IsDualDrawTarget() || dt->IsTiledDrawTarget() ||
+ dt->GetBackendType() != BackendType::SKIA) {
+ // We need a DrawTarget that we can get a CGContextRef from:
+ Matrix transform = dt->GetTransform();
+
+ mNativeRect = transform.TransformBounds(mNativeRect);
+ mNativeRect.RoundOut();
+ // Quartz theme drawing often adjusts drawing rects, so make
+ // sure our surface is big enough for that.
+ mNativeRect.Inflate(5);
+ if (mNativeRect.IsEmpty()) {
+ return nullptr;
+ }
+
+ mTempDrawTarget =
+ Factory::CreateDrawTarget(BackendType::SKIA,
+ IntSize::Truncate(mNativeRect.width, mNativeRect.height),
+ SurfaceFormat::B8G8R8A8);
+
+ if (mTempDrawTarget) {
+ transform.PostTranslate(-mNativeRect.x, -mNativeRect.y);
+ mTempDrawTarget->SetTransform(transform);
+ }
+ dt = mTempDrawTarget;
+ }
+ if (dt) {
+ MOZ_ASSERT(dt->GetBackendType() == BackendType::SKIA);
+ mCGContext = mBorrowedContext.Init(dt);
+ MOZ_ASSERT(mCGContext);
+ }
+ 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));
+ }
+}
diff --git a/gfx/thebes/gfxQuartzNativeDrawing.h b/gfx/thebes/gfxQuartzNativeDrawing.h
new file mode 100644
index 000000000..736f9ce83
--- /dev/null
+++ b/gfx/thebes/gfxQuartzNativeDrawing.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 000000000..99553e0c0
--- /dev/null
+++ b/gfx/thebes/gfxQuartzSurface.cpp
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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);
+}
+
+gfxQuartzSurface::gfxQuartzSurface(unsigned char *data,
+ const mozilla::gfx::IntSize& aSize,
+ long stride,
+ gfxImageFormat format)
+ : mCGContext(nullptr), mSize(aSize.width, aSize.height)
+{
+ if (!mozilla::gfx::Factory::CheckSurfaceSize(aSize))
+ MakeInvalid();
+
+ cairo_format_t cformat = GfxFormatToCairoFormat(format);
+ cairo_surface_t *surf = cairo_quartz_surface_create_for_data
+ (data, cformat, aSize.width, aSize.height, stride);
+
+ mCGContext = cairo_quartz_surface_get_cg_context (surf);
+
+ CGContextRetain(mCGContext);
+
+ Init(surf);
+ if (mSurfaceValid) {
+ RecordMemoryUsed(mSize.height * stride + sizeof(gfxQuartzSurface));
+ }
+}
+
+already_AddRefed<gfxASurface>
+gfxQuartzSurface::CreateSimilarSurface(gfxContentType aType,
+ const mozilla::gfx::IntSize& aSize)
+{
+ cairo_surface_t *surface =
+ cairo_quartz_surface_create_cg_layer(mSurface, (cairo_content_t)aType,
+ aSize.width, aSize.height);
+ if (cairo_surface_status(surface)) {
+ cairo_surface_destroy(surface);
+ return nullptr;
+ }
+
+ RefPtr<gfxASurface> result = Wrap(surface, aSize);
+ cairo_surface_destroy(surface);
+ return result.forget();
+}
+
+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 000000000..50e2bfb2c
--- /dev/null
+++ b/gfx/thebes/gfxQuartzSurface.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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);
+ gfxQuartzSurface(unsigned char *data, const mozilla::gfx::IntSize& size, long stride, gfxImageFormat format);
+
+ virtual ~gfxQuartzSurface();
+
+ virtual already_AddRefed<gfxASurface> CreateSimilarSurface(gfxContentType aType,
+ const mozilla::gfx::IntSize& aSize);
+
+ virtual const mozilla::gfx::IntSize GetSize() const { return mozilla::gfx::IntSize(mSize.width, mSize.height); }
+
+ 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 000000000..e25974974
--- /dev/null
+++ b/gfx/thebes/gfxQuaternion.h
@@ -0,0 +1,93 @@
+/* -*- 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) {
+ 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;
+ }
+
+ mozilla::gfx::Matrix4x4 ToMatrix() {
+ 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.cpp b/gfx/thebes/gfxRect.cpp
new file mode 100644
index 000000000..a418f394e
--- /dev/null
+++ b/gfx/thebes/gfxRect.cpp
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxRect.h"
+
+#include "nsMathUtils.h"
+
+#include "mozilla/gfx/Matrix.h"
+
+#include "gfxQuad.h"
+
+gfxQuad
+gfxRect::TransformToQuad(const mozilla::gfx::Matrix4x4 &aMatrix) const
+{
+ gfxPoint points[4];
+
+ points[0] = TopLeft();
+ points[1] = TopRight();
+ points[2] = BottomRight();
+ points[3] = BottomLeft();
+
+ points[0].Transform(aMatrix);
+ points[1].Transform(aMatrix);
+ points[2].Transform(aMatrix);
+ points[3].Transform(aMatrix);
+
+ // Could this ever result in lines that intersect? I don't think so.
+ return gfxQuad(points[0], points[1], points[2], points[3]);
+}
+
+static bool
+WithinEpsilonOfInteger(gfxFloat aX, gfxFloat aEpsilon)
+{
+ return fabs(NS_round(aX) - aX) <= fabs(aEpsilon);
+}
+
+bool
+gfxRect::WithinEpsilonOfIntegerPixels(gfxFloat aEpsilon) const
+{
+ NS_ASSERTION(-0.5 < aEpsilon && aEpsilon < 0.5, "Nonsense epsilon value");
+ return (WithinEpsilonOfInteger(x, aEpsilon) &&
+ WithinEpsilonOfInteger(y, aEpsilon) &&
+ WithinEpsilonOfInteger(width, aEpsilon) &&
+ WithinEpsilonOfInteger(height, aEpsilon));
+}
+
+/* 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
+ */
+
+#define CAIRO_COORD_MAX (16777215.0)
+#define CAIRO_COORD_MIN (-16777216.0)
+
+void
+gfxRect::Condition()
+{
+ // if either x or y is way out of bounds;
+ // note that we don't handle negative w/h here
+ if (x > CAIRO_COORD_MAX) {
+ x = CAIRO_COORD_MAX;
+ width = 0.0;
+ }
+
+ if (y > CAIRO_COORD_MAX) {
+ y = CAIRO_COORD_MAX;
+ height = 0.0;
+ }
+
+ if (x < CAIRO_COORD_MIN) {
+ width += x - CAIRO_COORD_MIN;
+ if (width < 0.0)
+ width = 0.0;
+ x = CAIRO_COORD_MIN;
+ }
+
+ if (y < CAIRO_COORD_MIN) {
+ height += y - CAIRO_COORD_MIN;
+ if (height < 0.0)
+ height = 0.0;
+ y = CAIRO_COORD_MIN;
+ }
+
+ if (x + width > CAIRO_COORD_MAX) {
+ width = CAIRO_COORD_MAX - x;
+ }
+
+ if (y + height > CAIRO_COORD_MAX) {
+ height = CAIRO_COORD_MAX - y;
+ }
+}
+
diff --git a/gfx/thebes/gfxRect.h b/gfx/thebes/gfxRect.h
new file mode 100644
index 000000000..56d5c43e5
--- /dev/null
+++ b/gfx/thebes/gfxRect.h
@@ -0,0 +1,148 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxTypes.h"
+#include "gfxPoint.h"
+#include "nsDebug.h"
+#include "nsRect.h"
+#include "mozilla/gfx/BaseMargin.h"
+#include "mozilla/gfx/BaseRect.h"
+#include "mozilla/gfx/MatrixFwd.h"
+#include "mozilla/Assertions.h"
+
+struct gfxQuad;
+
+struct gfxMargin : public mozilla::gfx::BaseMargin<gfxFloat, gfxMargin> {
+ typedef mozilla::gfx::BaseMargin<gfxFloat, gfxMargin> Super;
+
+ // Constructors
+ gfxMargin() : Super() {}
+ gfxMargin(const gfxMargin& aMargin) : Super(aMargin) {}
+ gfxMargin(gfxFloat aTop, gfxFloat aRight, gfxFloat aBottom, gfxFloat aLeft)
+ : Super(aTop, aRight, aBottom, aLeft) {}
+};
+
+namespace mozilla {
+ namespace css {
+ enum Corner {
+ // this order is important!
+ eCornerTopLeft = 0,
+ eCornerTopRight = 1,
+ eCornerBottomRight = 2,
+ eCornerBottomLeft = 3,
+ eNumCorners = 4
+ };
+ } // namespace css
+} // namespace mozilla
+#define NS_CORNER_TOP_LEFT mozilla::css::eCornerTopLeft
+#define NS_CORNER_TOP_RIGHT mozilla::css::eCornerTopRight
+#define NS_CORNER_BOTTOM_RIGHT mozilla::css::eCornerBottomRight
+#define NS_CORNER_BOTTOM_LEFT mozilla::css::eCornerBottomLeft
+#define NS_NUM_CORNERS mozilla::css::eNumCorners
+
+#define NS_FOR_CSS_CORNERS(var_) \
+ for (mozilla::css::Corner var_ = NS_CORNER_TOP_LEFT; \
+ var_ <= NS_CORNER_BOTTOM_LEFT; \
+ var_++)
+
+static inline mozilla::css::Corner operator++(mozilla::css::Corner& corner, int) {
+ NS_PRECONDITION(corner >= NS_CORNER_TOP_LEFT &&
+ corner < NS_NUM_CORNERS, "Out of range corner");
+ corner = mozilla::css::Corner(corner + 1);
+ return corner;
+}
+
+struct gfxRect :
+ public mozilla::gfx::BaseRect<gfxFloat, gfxRect, gfxPoint, gfxSize, gfxMargin> {
+ typedef mozilla::gfx::BaseRect<gfxFloat, gfxRect, gfxPoint, gfxSize, gfxMargin> Super;
+
+ gfxRect() : Super() {}
+ gfxRect(const gfxPoint& aPos, const gfxSize& aSize) :
+ Super(aPos, aSize) {}
+ gfxRect(gfxFloat aX, gfxFloat aY, gfxFloat aWidth, gfxFloat aHeight) :
+ Super(aX, aY, aWidth, aHeight) {}
+
+ /**
+ * Return true if all components of this rect are within
+ * aEpsilon of integer coordinates, defined as
+ * |round(coord) - coord| <= |aEpsilon|
+ * for x,y,width,height.
+ */
+ bool WithinEpsilonOfIntegerPixels(gfxFloat aEpsilon) const;
+
+ gfxPoint AtCorner(mozilla::css::Corner corner) const {
+ switch (corner) {
+ case NS_CORNER_TOP_LEFT: return TopLeft();
+ case NS_CORNER_TOP_RIGHT: return TopRight();
+ case NS_CORNER_BOTTOM_RIGHT: return BottomRight();
+ case NS_CORNER_BOTTOM_LEFT: return BottomLeft();
+ default:
+ NS_ERROR("Invalid corner!");
+ break;
+ }
+ return gfxPoint(0.0, 0.0);
+ }
+
+ gfxPoint CCWCorner(mozilla::Side side) const {
+ switch (side) {
+ case NS_SIDE_TOP: return TopLeft();
+ case NS_SIDE_RIGHT: return TopRight();
+ case NS_SIDE_BOTTOM: return BottomRight();
+ case NS_SIDE_LEFT: return BottomLeft();
+ }
+ MOZ_CRASH("Incomplete switch");
+ }
+
+ gfxPoint CWCorner(mozilla::Side side) const {
+ switch (side) {
+ case NS_SIDE_TOP: return TopRight();
+ case NS_SIDE_RIGHT: return BottomRight();
+ case NS_SIDE_BOTTOM: return BottomLeft();
+ case NS_SIDE_LEFT: return TopLeft();
+ }
+ MOZ_CRASH("Incomplete switch");
+ }
+
+ /* 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.
+ */
+ void Condition();
+
+ void Scale(gfxFloat k) {
+ NS_ASSERTION(k >= 0.0, "Invalid (negative) scale factor");
+ x *= k;
+ y *= k;
+ width *= k;
+ height *= k;
+ }
+
+ void Scale(gfxFloat sx, gfxFloat sy) {
+ NS_ASSERTION(sx >= 0.0, "Invalid (negative) scale factor");
+ NS_ASSERTION(sy >= 0.0, "Invalid (negative) scale factor");
+ x *= sx;
+ y *= sy;
+ width *= sx;
+ height *= sy;
+ }
+
+ void ScaleInverse(gfxFloat k) {
+ NS_ASSERTION(k > 0.0, "Invalid (negative) scale factor");
+ x /= k;
+ y /= k;
+ width /= k;
+ height /= k;
+ }
+
+ /*
+ * Transform this rectangle with aMatrix, resulting in a gfxQuad.
+ */
+ gfxQuad TransformToQuad(const mozilla::gfx::Matrix4x4 &aMatrix) const;
+};
+
+#endif /* GFX_RECT_H */
diff --git a/gfx/thebes/gfxSVGGlyphs.cpp b/gfx/thebes/gfxSVGGlyphs.cpp
new file mode 100644
index 000000000..a7615eca8
--- /dev/null
+++ b/gfx/thebes/gfxSVGGlyphs.cpp
@@ -0,0 +1,463 @@
+/* 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/SVGContextPaint.h"
+#include "nsError.h"
+#include "nsIDOMDocument.h"
+#include "nsString.h"
+#include "nsIDocument.h"
+#include "nsICategoryManager.h"
+#include "nsIDocumentLoaderFactory.h"
+#include "nsIContentViewer.h"
+#include "nsIStreamListener.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIPresShell.h"
+#include "nsNetUtil.h"
+#include "nsNullPrincipal.h"
+#include "nsIInputStream.h"
+#include "nsStringStream.h"
+#include "nsStreamUtils.h"
+#include "nsIPrincipal.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/LoadInfo.h"
+#include "nsSVGUtils.h"
+#include "nsHostObjectProtocolHandler.h"
+#include "nsContentUtils.h"
+#include "gfxFont.h"
+#include "nsSMILAnimationController.h"
+#include "gfxContext.h"
+#include "harfbuzz/hb.h"
+#include "mozilla/dom/ImageTracker.h"
+
+#define SVG_CONTENT_TYPE NS_LITERAL_CSTRING("image/svg+xml")
+#define UTF8_CHARSET NS_LITERAL_CSTRING("utf-8")
+
+using namespace mozilla;
+
+typedef mozilla::dom::Element Element;
+
+/* static */ const Color SimpleTextContextPaint::sZero = Color();
+
+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;
+ }
+
+ gfxSVGGlyphsDocument *result = mGlyphDocs.Get(entry->mDocOffset);
+
+ if (!result) {
+ 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) {
+ result = new gfxSVGGlyphsDocument(data + mHeader->mDocIndexOffset + entry->mDocOffset,
+ entry->mDocLength, this);
+ mGlyphDocs.Put(entry->mDocOffset, result);
+ }
+ }
+
+ return result;
+}
+
+nsresult
+gfxSVGGlyphsDocument::SetupPresentation()
+{
+ nsCOMPtr<nsICategoryManager> catMan = do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
+ nsXPIDLCString contractId;
+ nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", "image/svg+xml", getter_Copies(contractId));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory = do_GetService(contractId);
+ NS_ASSERTION(docLoaderFactory, "Couldn't get DocumentLoaderFactory");
+
+ nsCOMPtr<nsIContentViewer> viewer;
+ rv = docLoaderFactory->CreateInstanceForDocument(nullptr, mDocument, nullptr, getter_AddRefs(viewer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = viewer->Init(nullptr, gfx::IntRect(0, 0, 1000, 1000));
+ if (NS_SUCCEEDED(rv)) {
+ rv = viewer->Open(nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIPresShell> presShell;
+ rv = viewer->GetPresShell(getter_AddRefs(presShell));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsPresContext* presContext = presShell->GetPresContext();
+ presContext->SetIsGlyph(true);
+
+ if (!presShell->DidInitialize()) {
+ nsRect rect = presContext->GetVisibleArea();
+ rv = presShell->Initialize(rect.width, rect.height);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mDocument->FlushPendingNotifications(Flush_Layout);
+
+ nsSMILAnimationController* controller = mDocument->GetAnimationController();
+ if (controller) {
+ controller->Resume(nsSMILTimeContainer::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
+ */
+bool
+gfxSVGGlyphs::RenderGlyph(gfxContext *aContext, uint32_t aGlyphId,
+ SVGContextPaint* aContextPaint)
+{
+ gfxContextAutoSaveRestore aContextRestorer(aContext);
+
+ Element *glyph = mGlyphIdMap.Get(aGlyphId);
+ NS_ASSERTION(glyph, "No glyph element. Should check with HasSVGGlyph() first!");
+
+ AutoSetRestoreSVGContextPaint autoSetRestore(aContextPaint, glyph->OwnerDoc());
+
+ return nsSVGUtils::PaintSVGGlyph(glyph, aContext);
+}
+
+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 nsSVGUtils::GetSVGGlyphExtents(glyph, aSVGToAppSpace, aResult);
+}
+
+Element *
+gfxSVGGlyphs::GetGlyphElement(uint32_t aGlyphId)
+{
+ Element *elem;
+
+ if (!mGlyphIdMap.Get(aGlyphId, &elem)) {
+ elem = nullptr;
+ if (gfxSVGGlyphsDocument *set = FindOrCreateGlyphsDocument(aGlyphId)) {
+ elem = set->GetGlyphElement(aGlyphId);
+ }
+ mGlyphIdMap.Put(aGlyphId, elem);
+ }
+
+ 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 (auto iter = mGlyphDocs.ConstIter(); !iter.Done(); iter.Next()) {
+ result += iter.Data()->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)
+{
+ 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),
+ 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, 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);
+
+ nsCOMPtr<nsIURI> uri;
+ nsHostObjectProtocolHandler::GenerateURIString(NS_LITERAL_CSTRING(FONTTABLEURI_SCHEME),
+ nullptr,
+ mSVGGlyphsDocumentURI);
+
+ rv = NS_NewURI(getter_AddRefs(uri), mSVGGlyphsDocumentURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> principal = nsNullPrincipal::Create();
+
+ nsCOMPtr<nsIDOMDocument> domDoc;
+ rv = NS_NewDOMDocument(getter_AddRefs(domDoc),
+ EmptyString(), // aNamespaceURI
+ EmptyString(), // aQualifiedName
+ nullptr, // aDoctype
+ uri, uri, principal,
+ false, // aLoadedAsData
+ nullptr, // aEventObject
+ DocumentFlavorSVG);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDocument> document(do_QueryInterface(domDoc));
+ if (!document) {
+ return NS_ERROR_FAILURE;
+ }
+
+ 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->SetReadyStateInternal(nsIDocument::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, nullptr /* aContext */);
+ if (NS_FAILED(rv)) {
+ channel->Cancel(rv);
+ }
+
+ nsresult status;
+ channel->GetStatus(&status);
+ if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(status)) {
+ rv = listener->OnDataAvailable(channel, nullptr /* aContext */, stream, 0, aBufLen);
+ if (NS_FAILED(rv)) {
+ channel->Cancel(rv);
+ }
+ channel->GetStatus(&status);
+ }
+
+ rv = listener->OnStopRequest(channel, nullptr /* aContext */, 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, NS_LITERAL_STRING("glyph")) ||
+ 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.Put(id, aGlyphElement);
+}
+
+size_t
+gfxSVGGlyphsDocument::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ return aMallocSizeOf(this)
+ + mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf)
+ + mSVGGlyphsDocumentURI.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+
+}
diff --git a/gfx/thebes/gfxSVGGlyphs.h b/gfx/thebes/gfxSVGGlyphs.h
new file mode 100644
index 000000000..8ebebb44b
--- /dev/null
+++ b/gfx/thebes/gfxSVGGlyphs.h
@@ -0,0 +1,242 @@
+/* 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 "nsRefreshDriver.h"
+
+class nsIDocument;
+class nsIContentViewer;
+class nsIPresShell;
+class gfxSVGGlyphs;
+
+namespace mozilla {
+class SVGContextPaint;
+namespace dom {
+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();
+
+ virtual 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;
+ nsCOMPtr<nsIDocument> mDocument;
+ nsCOMPtr<nsIContentViewer> mViewer;
+ nsCOMPtr<nsIPresShell> mPresShell;
+
+ nsBaseHashtable<nsUint32HashKey, Element*, Element*> mGlyphIdMap;
+
+ nsCString mSVGGlyphsDocumentURI;
+};
+
+/**
+ * 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|.
+ */
+ bool 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;
+
+private:
+ Element *GetGlyphElement(uint32_t aGlyphId);
+
+ nsClassHashtable<nsUint32HashKey, gfxSVGGlyphsDocument> mGlyphDocs;
+ nsBaseHashtable<nsUint32HashKey, Element*, Element*> mGlyphIdMap;
+
+ hb_blob_t *mSVGData;
+ gfxFontEntry *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::Color 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) {
+ 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) {
+ if (mStrokePattern) {
+ mStrokePattern->SetMatrix(aCTM * mStrokeMatrix);
+ }
+ RefPtr<gfxPattern> strokePattern = mStrokePattern;
+ return strokePattern.forget();
+ }
+
+ float GetFillOpacity() const {
+ return mFillPattern ? 1.0f : 0.0f;
+ }
+
+ float GetStrokeOpacity() const {
+ 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 000000000..b9426ee6f
--- /dev/null
+++ b/gfx/thebes/gfxScriptItemizer.cpp
@@ -0,0 +1,229 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsUnicodeProperties.h"
+#include "nsCharTraits.h"
+#include "harfbuzz/hb.h"
+
+#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
+SameScript(Script runScript, Script currCharScript)
+{
+ return runScript <= Script::INHERITED ||
+ currCharScript <= Script::INHERITED ||
+ currCharScript == 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;
+
+ 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 = 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 = mozilla::unicode::GetMirroredChar(ch);
+ if (endPairChar != ch) {
+ push(endPairChar, scriptCode);
+ }
+ } else if (gc == HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION &&
+ HasMirroredChar(ch))
+ {
+ while (STACK_IS_NOT_EMPTY() && TOP().endPairChar != ch) {
+ pop();
+ }
+
+ if (STACK_IS_NOT_EMPTY()) {
+ sc = TOP().scriptCode;
+ }
+ }
+ }
+
+ if (SameScript(scriptCode, sc)) {
+ if (scriptCode <= Script::INHERITED &&
+ sc > Script::INHERITED)
+ {
+ scriptCode = sc;
+ fixup(scriptCode);
+ }
+
+ /*
+ * if this character is a close paired character,
+ * pop the matching open character from the stack
+ */
+ if (gc == HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION &&
+ HasMirroredChar(ch)) {
+ pop();
+ }
+ } else {
+ /*
+ * reset scriptLimit in case it was advanced during reading a
+ * multiple-code-unit character
+ */
+ scriptLimit = startOfChar;
+
+ break;
+ }
+ }
+
+ aRunStart = scriptStart;
+ aRunLimit = scriptLimit;
+ aRunScript = scriptCode;
+
+ return true;
+}
diff --git a/gfx/thebes/gfxScriptItemizer.h b/gfx/thebes/gfxScriptItemizer.h
new file mode 100644
index 000000000..8089738dc
--- /dev/null
+++ b/gfx/thebes/gfxScriptItemizer.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsUnicodeScriptCodes.h"
+
+#define PAREN_STACK_DEPTH 32
+
+class gfxScriptItemizer
+{
+public:
+ typedef mozilla::unicode::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 000000000..9a954f981
--- /dev/null
+++ b/gfx/thebes/gfxSharedImageSurface.h
@@ -0,0 +1,24 @@
+// vim:set ts=4 sts=4 sw=4 et cin:
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 000000000..d0fad53e2
--- /dev/null
+++ b/gfx/thebes/gfxSkipChars.cpp
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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"
+
+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 000000000..352a16090
--- /dev/null
+++ b/gfx/thebes/gfxSkipChars.h
@@ -0,0 +1,308 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "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.SwapElements(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 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)
+ : mSkipChars(aIterator.mSkipChars),
+ mOriginalStringOffset(aIterator.mOriginalStringOffset),
+ mSkippedStringOffset(aIterator.mSkippedStringOffset),
+ mCurrentRangeIndex(aIterator.mCurrentRangeIndex),
+ mOriginalStringToSkipCharsOffset(aIterator.mOriginalStringToSkipCharsOffset)
+ { }
+
+ /**
+ * The empty constructor creates an object that is useless until it is assigned.
+ */
+ gfxSkipCharsIterator()
+ : mSkipChars(nullptr)
+ { }
+
+ /**
+ * Return true if this iterator is properly initialized and usable.
+ */
+ bool IsInitialized()
+ {
+ 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 000000000..6718eed01
--- /dev/null
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -0,0 +1,3214 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 et sw=4 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 "gfxPlatformFontList.h"
+#include "gfxUserFontSet.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/Sprintf.h"
+#include "nsGkAtoms.h"
+#include "nsILanguageAtomService.h"
+#include "nsServiceManagerUtils.h"
+
+#include "gfxContext.h"
+#include "gfxFontConstants.h"
+#include "gfxFontMissingGlyphs.h"
+#include "gfxScriptItemizer.h"
+#include "nsUnicodeProperties.h"
+#include "nsUnicodeRange.h"
+#include "nsStyleConsts.h"
+#include "mozilla/Likely.h"
+#include "gfx2DGlue.h"
+#include "mozilla/gfx/Logging.h" // for gfxCriticalError
+#include "mozilla/UniquePtr.h"
+
+#if defined(MOZ_WIDGET_GTK)
+#include "gfxPlatformGtk.h" // xxx - for UseFcFontList
+#endif
+
+#ifdef XP_WIN
+#include "gfxWindowsPlatform.h"
+#endif
+
+#include "cairo.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+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
+
+bool
+gfxTextRun::GlyphRunIterator::NextRun() {
+ if (mNextIndex >= mTextRun->mGlyphRuns.Length())
+ return false;
+ mGlyphRun = &mTextRun->mGlyphRuns[mNextIndex];
+ if (mGlyphRun->mCharacterOffset >= mEndOffset)
+ return false;
+
+ mStringStart = std::max(mStartOffset, mGlyphRun->mCharacterOffset);
+ uint32_t last = mNextIndex + 1 < mTextRun->mGlyphRuns.Length()
+ ? mTextRun->mGlyphRuns[mNextIndex + 1].mCharacterOffset : mTextRun->GetLength();
+ mStringEnd = std::min(mEndOffset, last);
+
+ ++mNextIndex;
+ return true;
+}
+
+#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
+
+static bool
+NeedsGlyphExtents(gfxTextRun *aTextRun)
+{
+ if (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX)
+ return true;
+ uint32_t numRuns;
+ const gfxTextRun::GlyphRun *glyphRuns = aTextRun->GetGlyphRuns(&numRuns);
+ for (uint32_t i = 0; i < numRuns; ++i) {
+ if (glyphRuns[i].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, uint32_t aFlags)
+{
+ void *storage = AllocateStorageForTextRun(sizeof(gfxTextRun), aLength);
+ if (!storage) {
+ return nullptr;
+ }
+
+ RefPtr<gfxTextRun> result = new (storage) gfxTextRun(aParams, aLength,
+ aFontGroup, aFlags);
+ return result.forget();
+}
+
+gfxTextRun::gfxTextRun(const gfxTextRunFactory::Parameters *aParams,
+ uint32_t aLength, gfxFontGroup *aFontGroup, uint32_t aFlags)
+ : gfxShapedText(aLength, aFlags, aParams->mAppUnitsPerDevUnit)
+ , mUserData(aParams->mUserData)
+ , mFontGroup(aFontGroup)
+ , mReleasedFontGroup(false)
+ , mShapingState(eShapingState_Normal)
+{
+ NS_ASSERTION(mAppUnitsPerDevUnit > 0, "Invalid app unit scale");
+ MOZ_COUNT_CTOR(gfxTextRun);
+ 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
+
+ mSkipDrawing = mFontGroup->ShouldSkipDrawing();
+}
+
+gfxTextRun::~gfxTextRun()
+{
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+ AccountStorageForTextRun(this, -1);
+#endif
+#ifdef DEBUG
+ // Make it easy to detect a dead text run
+ mFlags = 0xFFFFFFFF;
+#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);
+ }
+
+ MOZ_COUNT_DTOR(gfxTextRun);
+}
+
+void
+gfxTextRun::ReleaseFontGroup()
+{
+ NS_ASSERTION(!mReleasedFontGroup, "doubly released!");
+ 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,
+ 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 & gfxTextRunFactory::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,
+ 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,
+ 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, PropertyProvider *aProvider,
+ Range aSpacingRange,
+ nsTArray<PropertyProvider::Spacing>*
+ aSpacing) const
+{
+ if (!aProvider || !(mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING))
+ return false;
+ if (!aSpacing->AppendElements(aRange.Length()))
+ 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() + aSpacingRange.end - aRange.start, 0,
+ sizeof(gfxFont::Spacing) * (aRange.end - aSpacingRange.end));
+ return true;
+}
+
+void
+gfxTextRun::ShrinkToLigatureBoundaries(Range* aRange) const
+{
+ if (aRange->start >= aRange->end)
+ return;
+
+ const CompressedGlyph *charGlyphs = mCharacterGlyphs;
+
+ while (aRange->start < aRange->end &&
+ !charGlyphs[aRange->start].IsLigatureGroupStart()) {
+ ++aRange->start;
+ }
+ if (aRange->end < GetLength()) {
+ while (aRange->end > aRange->start &&
+ !charGlyphs[aRange->end].IsLigatureGroupStart()) {
+ --aRange->end;
+ }
+ }
+}
+
+void
+gfxTextRun::DrawGlyphs(gfxFont *aFont, Range aRange, gfxPoint *aPt,
+ PropertyProvider *aProvider, Range aSpacingRange,
+ TextRunDrawParams& aParams, uint16_t 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,
+ gfxPoint *aPt, PropertyProvider *aProvider,
+ TextRunDrawParams& aParams,
+ uint16_t 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);
+ }
+
+ {
+ // 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);
+
+ aParams.context->Save();
+ aParams.context->Clip(clipRect);
+ }
+
+ gfxPoint pt;
+ if (aParams.isVerticalRun) {
+ pt = gfxPoint(aPt->x, aPt->y - aParams.direction * data.mPartAdvance);
+ } else {
+ pt = gfxPoint(aPt->x - aParams.direction * data.mPartAdvance, aPt->y);
+ }
+
+ DrawGlyphs(aFont, data.mRange, &pt,
+ aProvider, aRange, aParams, aOrientation);
+ aParams.context->Restore();
+
+ if (aParams.isVerticalRun) {
+ aPt->y += aParams.direction * data.mPartWidth;
+ } else {
+ aPt->x += aParams.direction * data.mPartWidth;
+ }
+}
+
+// Returns true if a glyph run is using a font with synthetic bolding enabled,
+// or 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(const gfxTextRun *aRun, gfxTextRun::Range aRange)
+{
+ gfxTextRun::GlyphRunIterator iter(aRun, aRange);
+ while (iter.NextRun()) {
+ gfxFont *font = iter.GetGlyphRun()->mFont;
+ if (font) {
+ if (font->IsSyntheticBold()) {
+ return true;
+ }
+ gfxFontEntry* fe = font->GetFontEntry();
+ if (fe->TryGetSVGData(font) || 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;
+}
+
+// Returns true if color is neither opaque nor transparent (i.e. alpha is not 0
+// or 1), and false otherwise. If true, aCurrentColorOut is set on output.
+static bool
+HasNonOpaqueNonTransparentColor(gfxContext *aContext, Color& aCurrentColorOut)
+{
+ if (aContext->GetDeviceColor(aCurrentColorOut)) {
+ if (0.f < aCurrentColorOut.a && aCurrentColorOut.a < 1.f) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// helper class for double-buffering drawing with non-opaque color
+struct BufferAlphaColor {
+ explicit BufferAlphaColor(gfxContext *aContext)
+ : mContext(aContext)
+ {
+
+ }
+
+ ~BufferAlphaColor() {}
+
+ void PushSolidColor(const gfxRect& aBounds, const Color& aAlphaColor, uint32_t appsPerDevUnit)
+ {
+ mContext->Save();
+ mContext->NewPath();
+ mContext->Rectangle(gfxRect(aBounds.X() / appsPerDevUnit,
+ aBounds.Y() / appsPerDevUnit,
+ aBounds.Width() / appsPerDevUnit,
+ aBounds.Height() / appsPerDevUnit), true);
+ mContext->Clip();
+ mContext->SetColor(Color(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(Range aRange, gfxPoint 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 = mSkipDrawing;
+ if (aParams.drawMode & DrawMode::GLYPH_FILL) {
+ Color currentColor;
+ if (aParams.context->GetDeviceColor(currentColor) &&
+ currentColor.a == 0) {
+ 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);
+ Color currentColor;
+ bool needToRestore = false;
+
+ if (aParams.drawMode & DrawMode::GLYPH_FILL &&
+ HasNonOpaqueNonTransparentColor(aParams.context, currentColor) &&
+ HasSyntheticBoldOrColor(this, aRange)) {
+ needToRestore = true;
+ // measure text, use the bounding box
+ gfxTextRun::Metrics metrics = MeasureText(
+ aRange, gfxFont::LOOSE_INK_EXTENTS,
+ aParams.context->GetDrawTarget(), aParams.provider);
+ metrics.mBoundingBox.MoveBy(aPt);
+ syntheticBoldBuffer.PushSolidColor(metrics.mBoundingBox, currentColor,
+ GetAppUnitsPerDevUnit());
+ }
+
+ // 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.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.fontSmoothingBGColor =
+ aParams.context->GetFontSmoothingBackgroundColor();
+
+ GlyphRunIterator iter(this, aRange);
+ gfxFloat advance = 0.0;
+
+ while (iter.NextRun()) {
+ gfxFont *font = iter.GetGlyphRun()->mFont;
+ uint32_t start = iter.GetStringStart();
+ uint32_t end = iter.GetStringEnd();
+ Range ligatureRange(start, end);
+ ShrinkToLigatureBoundaries(&ligatureRange);
+
+ bool drawPartial = (aParams.drawMode & DrawMode::GLYPH_FILL) ||
+ (aParams.drawMode == DrawMode::GLYPH_PATH &&
+ aParams.callbacks);
+ gfxPoint origPt = aPt;
+
+ if (drawPartial) {
+ DrawPartialLigature(font, Range(start, ligatureRange.start),
+ &aPt, aParams.provider, params,
+ iter.GetGlyphRun()->mOrientation);
+ }
+
+ DrawGlyphs(font, ligatureRange, &aPt,
+ aParams.provider, ligatureRange, params,
+ iter.GetGlyphRun()->mOrientation);
+
+ if (drawPartial) {
+ DrawPartialLigature(font, Range(ligatureRange.end, end),
+ &aPt, aParams.provider, params,
+ iter.GetGlyphRun()->mOrientation);
+ }
+
+ if (params.isVerticalRun) {
+ advance += (aPt.y - origPt.y) * params.direction;
+ } else {
+ advance += (aPt.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, gfxPoint aPt,
+ Range aRange, 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();
+
+ gfxFloat& inlineCoord = params.isVertical ? aPt.y : aPt.x;
+ gfxFloat direction = params.direction;
+
+ GlyphRunIterator iter(this, aRange);
+ while (iter.NextRun()) {
+ gfxFont* font = iter.GetGlyphRun()->mFont;
+ uint32_t start = iter.GetStringStart();
+ uint32_t end = iter.GetStringEnd();
+ Range ligatureRange(start, end);
+ ShrinkToLigatureBoundaries(&ligatureRange);
+
+ 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);
+
+ inlineCoord += direction * ComputePartialLigatureWidth(
+ Range(ligatureRange.end, end), aProvider);
+ }
+}
+
+void
+gfxTextRun::AccumulateMetricsForRun(gfxFont *aFont, Range aRange,
+ gfxFont::BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget,
+ PropertyProvider *aProvider,
+ Range aSpacingRange,
+ uint16_t 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,
+ PropertyProvider *aProvider, uint16_t 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.x = bboxLeft;
+ metrics.mBoundingBox.width = bboxRight - bboxLeft;
+
+ // mBoundingBox is now relative to the left baseline origin for the entire
+ // ligature. Shift it left.
+ metrics.mBoundingBox.x -=
+ 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,
+ PropertyProvider *aProvider) const
+{
+ NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range");
+
+ Metrics accumulatedMetrics;
+ GlyphRunIterator iter(this, aRange);
+ while (iter.NextRun()) {
+ gfxFont *font = iter.GetGlyphRun()->mFont;
+ uint32_t start = iter.GetStringStart();
+ uint32_t end = iter.GetStringEnd();
+ Range ligatureRange(start, end);
+ ShrinkToLigatureBoundaries(&ligatureRange);
+
+ AccumulatePartialLigatureMetrics(
+ font, Range(start, ligatureRange.start),
+ aBoundingBoxType, aRefDrawTarget, aProvider,
+ iter.GetGlyphRun()->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.GetGlyphRun()->mOrientation, &accumulatedMetrics);
+
+ AccumulatePartialLigatureMetrics(
+ font, Range(ligatureRange.end, end),
+ aBoundingBoxType, aRefDrawTarget, aProvider,
+ iter.GetGlyphRun()->mOrientation, &accumulatedMetrics);
+ }
+
+ return accumulatedMetrics;
+}
+
+#define MEASUREMENT_BUFFER_SIZE 100
+
+uint32_t
+gfxTextRun::BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength,
+ bool aLineBreakBefore, gfxFloat aWidth,
+ PropertyProvider *aProvider,
+ SuppressBreak aSuppressBreak,
+ gfxFloat *aTrimWhitespace,
+ bool aWhitespaceCanHang,
+ Metrics *aMetrics,
+ gfxFont::BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget,
+ bool *aUsedHyphenation,
+ uint32_t *aLastBreak,
+ bool aCanWordWrap,
+ 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 = aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING) != 0;
+ if (haveSpacing) {
+ GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer);
+ }
+ bool hyphenBuffer[MEASUREMENT_BUFFER_SIZE];
+ bool haveHyphenation = aProvider &&
+ (aProvider->GetHyphensOption() == NS_STYLE_HYPHENS_AUTO ||
+ (aProvider->GetHyphensOption() == NS_STYLE_HYPHENS_MANUAL &&
+ (mFlags & gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS) != 0));
+ if (haveHyphenation) {
+ aProvider->GetHyphenationBreaks(bufferRange, hyphenBuffer);
+ }
+
+ 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;
+ bool aborted = false;
+ uint32_t end = aStart + aMaxLength;
+ bool lastBreakUsedHyphenation = false;
+
+ Range ligatureRange(aStart, end);
+ ShrinkToLigatureBoundaries(&ligatureRange);
+
+ uint32_t i;
+ for (i = aStart; i < end; ++i) {
+ if (i >= bufferRange.end) {
+ // Fetch more spacing and hyphenation data
+ bufferRange.start = i;
+ bufferRange.end = std::min(aStart + aMaxLength,
+ i + MEASUREMENT_BUFFER_SIZE);
+ if (haveSpacing) {
+ GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer);
+ }
+ if (haveHyphenation) {
+ aProvider->GetHyphenationBreaks(bufferRange, hyphenBuffer);
+ }
+ }
+
+ // 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;
+ bool atHyphenationBreak = !atNaturalBreak &&
+ haveHyphenation && hyphenBuffer[i - bufferRange.start];
+ bool atBreak = atNaturalBreak || atHyphenationBreak;
+ bool wordWrapping =
+ aCanWordWrap && mCharacterGlyphs[i].IsClusterStart() &&
+ *aBreakPriority <= gfxBreakPriority::eWordWrapBreak;
+
+ if (atBreak || wordWrapping) {
+ 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 ? gfxBreakPriority::eNormalBreak
+ : gfxBreakPriority::eWordWrapBreak;
+ }
+
+ width += advance;
+ advance = 0;
+ if (width - trimmableAdvance > aWidth) {
+ // No more text fits. Abort
+ aborted = true;
+ break;
+ }
+ }
+ }
+
+ 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 (aTrimWhitespace || aWhitespaceCanHang) {
+ 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;
+ bool usedHyphenation = false;
+ if (width - trimmableAdvance <= aWidth) {
+ charsFit = aMaxLength;
+ } else if (lastBreak >= 0) {
+ charsFit = lastBreak - aStart;
+ trimmableChars = lastBreakTrimmableChars;
+ trimmableAdvance = lastBreakTrimmableAdvance;
+ usedHyphenation = lastBreakUsedHyphenation;
+ } else {
+ charsFit = aMaxLength;
+ }
+
+ if (aMetrics) {
+ auto fitEnd = aStart + charsFit;
+ // Initially, measure everything, so that our bounding box includes
+ // any trimmable or hanging whitespace.
+ *aMetrics = MeasureText(Range(aStart, fitEnd),
+ aBoundingBoxType, aRefDrawTarget,
+ aProvider);
+ if (aTrimWhitespace || aWhitespaceCanHang) {
+ // Measure trailing whitespace that is to be trimmed/hung.
+ Metrics trimOrHangMetrics =
+ MeasureText(Range(fitEnd - trimmableChars, fitEnd),
+ aBoundingBoxType, aRefDrawTarget,
+ aProvider);
+ if (aTrimWhitespace) {
+ aMetrics->mAdvanceWidth -= trimOrHangMetrics.mAdvanceWidth;
+ } else if (aMetrics->mAdvanceWidth > aWidth) {
+ // Restrict width of hanging whitespace so it doesn't overflow.
+ aMetrics->mAdvanceWidth =
+ std::max(aWidth, aMetrics->mAdvanceWidth -
+ trimOrHangMetrics.mAdvanceWidth);
+ }
+ }
+ }
+ if (aTrimWhitespace) {
+ *aTrimWhitespace = trimmableAdvance;
+ }
+ if (aUsedHyphenation) {
+ *aUsedHyphenation = usedHyphenation;
+ }
+ if (aLastBreak && charsFit == aMaxLength) {
+ if (lastBreak < 0) {
+ *aLastBreak = UINT32_MAX;
+ } else {
+ *aLastBreak = lastBreak - aStart;
+ }
+ }
+
+ return charsFit;
+}
+
+gfxFloat
+gfxTextRun::GetAdvanceWidth(Range aRange, PropertyProvider *aProvider,
+ PropertyProvider::Spacing* aSpacing) const
+{
+ NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range");
+
+ Range ligatureRange = aRange;
+ ShrinkToLigatureBoundaries(&ligatureRange);
+
+ gfxFloat result =
+ ComputePartialLigatureWidth(Range(aRange.start, ligatureRange.start),
+ aProvider) +
+ ComputePartialLigatureWidth(Range(ligatureRange.end, aRange.end),
+ aProvider);
+
+ 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 & gfxTextRunFactory::TEXT_ENABLE_SPACING)) {
+ uint32_t i;
+ AutoTArray<PropertyProvider::Spacing,200> spacingBuffer;
+ if (spacingBuffer.AppendElements(aRange.Length())) {
+ 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);
+}
+
+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;
+}
+
+uint32_t
+gfxTextRun::FindFirstGlyphRunContaining(uint32_t aOffset) const
+{
+ NS_ASSERTION(aOffset <= GetLength(), "Bad offset looking for glyphrun");
+ NS_ASSERTION(GetLength() == 0 || mGlyphRuns.Length() > 0,
+ "non-empty text but no glyph runs present!");
+ if (aOffset == GetLength())
+ return mGlyphRuns.Length();
+ uint32_t start = 0;
+ uint32_t end = mGlyphRuns.Length();
+ while (end - start > 1) {
+ uint32_t mid = (start + end)/2;
+ if (mGlyphRuns[mid].mCharacterOffset <= aOffset) {
+ start = mid;
+ } else {
+ end = mid;
+ }
+ }
+ NS_ASSERTION(mGlyphRuns[start].mCharacterOffset <= aOffset,
+ "Hmm, something went wrong, aOffset should have been found");
+ return start;
+}
+
+nsresult
+gfxTextRun::AddGlyphRun(gfxFont *aFont, uint8_t aMatchType,
+ uint32_t aUTF16Offset, bool aForceNewRun,
+ uint16_t aOrientation)
+{
+ NS_ASSERTION(aFont, "adding glyph run for null font!");
+ NS_ASSERTION(aOrientation != gfxTextRunFactory::TEXT_ORIENT_VERTICAL_MIXED,
+ "mixed orientation should have been resolved");
+ if (!aFont) {
+ return NS_OK;
+ }
+ uint32_t numGlyphRuns = mGlyphRuns.Length();
+ if (!aForceNewRun && numGlyphRuns > 0) {
+ GlyphRun *lastGlyphRun = &mGlyphRuns[numGlyphRuns - 1];
+
+ NS_ASSERTION(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->mFont == aFont &&
+ lastGlyphRun->mMatchType == aMatchType &&
+ lastGlyphRun->mOrientation == aOrientation)
+ {
+ return NS_OK;
+ }
+
+ // 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].mFont == aFont &&
+ mGlyphRuns[numGlyphRuns - 2].mMatchType == aMatchType &&
+ mGlyphRuns[numGlyphRuns - 2].mOrientation == aOrientation)
+ {
+ mGlyphRuns.TruncateLength(numGlyphRuns - 1);
+ return NS_OK;
+ }
+
+ lastGlyphRun->mFont = aFont;
+ lastGlyphRun->mMatchType = aMatchType;
+ lastGlyphRun->mOrientation = aOrientation;
+ return NS_OK;
+ }
+ }
+
+ NS_ASSERTION(aForceNewRun || numGlyphRuns > 0 || aUTF16Offset == 0,
+ "First run doesn't cover the first character (and run not forced)?");
+
+ GlyphRun *glyphRun = mGlyphRuns.AppendElement();
+ if (!glyphRun)
+ return NS_ERROR_OUT_OF_MEMORY;
+ glyphRun->mFont = aFont;
+ glyphRun->mCharacterOffset = aUTF16Offset;
+ glyphRun->mMatchType = aMatchType;
+ glyphRun->mOrientation = aOrientation;
+ return NS_OK;
+}
+
+void
+gfxTextRun::SortGlyphRuns()
+{
+ if (mGlyphRuns.Length() <= 1)
+ return;
+
+ nsTArray<GlyphRun> runs(mGlyphRuns);
+ GlyphRunOffsetComparator comp;
+ runs.Sort(comp);
+
+ // Now copy back, coalescing adjacent glyph runs that have the same font
+ mGlyphRuns.Clear();
+ uint32_t i, count = runs.Length();
+ for (i = 0; i < count; ++i) {
+ // a GlyphRun with the same font and orientation as the previous can
+ // just be skipped; the last GlyphRun will cover its character range.
+ if (i == 0 || runs[i].mFont != runs[i - 1].mFont ||
+ runs[i].mOrientation != runs[i - 1].mOrientation) {
+ mGlyphRuns.AppendElement(runs[i]);
+ // If two fonts have the same character offset, Sort() will have
+ // randomized the order.
+ NS_ASSERTION(i == 0 ||
+ runs[i].mCharacterOffset !=
+ runs[i - 1].mCharacterOffset,
+ "Two fonts for the same run, glyph indices may not match the font");
+ }
+ }
+}
+
+// Note that SanitizeGlyphRuns scans all glyph runs in the textrun;
+// therefore we only call it once, at the end of textrun construction,
+// NOT incrementally as each glyph run is added (bug 680402).
+void
+gfxTextRun::SanitizeGlyphRuns()
+{
+ if (mGlyphRuns.Length() <= 1)
+ return;
+
+ // 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)
+ int32_t i, lastRunIndex = mGlyphRuns.Length() - 1;
+ const CompressedGlyph *charGlyphs = mCharacterGlyphs;
+ for (i = lastRunIndex; i >= 0; --i) {
+ GlyphRun& run = mGlyphRuns[i];
+ while (charGlyphs[run.mCharacterOffset].IsLigatureContinuation() &&
+ run.mCharacterOffset < GetLength()) {
+ run.mCharacterOffset++;
+ }
+ // if the run has become empty, eliminate it
+ if ((i < lastRunIndex &&
+ run.mCharacterOffset >= mGlyphRuns[i+1].mCharacterOffset) ||
+ (i == lastRunIndex && run.mCharacterOffset == GetLength())) {
+ mGlyphRuns.RemoveElementAt(i);
+ --lastRunIndex;
+ }
+ }
+}
+
+uint32_t
+gfxTextRun::CountMissingGlyphs() const
+{
+ uint32_t i;
+ uint32_t count = 0;
+ for (i = 0; i < GetLength(); ++i) {
+ if (mCharacterGlyphs[i].IsMissing()) {
+ ++count;
+ }
+ }
+ return count;
+}
+
+void
+gfxTextRun::CopyGlyphDataFrom(gfxShapedWord *aShapedWord, uint32_t aOffset)
+{
+ uint32_t wordLen = aShapedWord->GetLength();
+ NS_ASSERTION(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()) {
+ charGlyphs[aOffset] = g;
+ } else {
+ const DetailedGlyph *details =
+ g.GetGlyphCount() > 0 ?
+ aShapedWord->GetDetailedGlyphs(i) : nullptr;
+ SetGlyphs(aOffset, g, details);
+ }
+ }
+ } else {
+ memcpy(charGlyphs + aOffset, wordGlyphs,
+ wordLen * sizeof(CompressedGlyph));
+ }
+}
+
+void
+gfxTextRun::CopyGlyphDataFrom(gfxTextRun *aSource, Range aRange, uint32_t aDest)
+{
+ NS_ASSERTION(aRange.end <= aSource->GetLength(),
+ "Source substring out of range");
+ NS_ASSERTION(aDest + aRange.Length() <= GetLength(),
+ "Destination substring out of range");
+
+ if (aSource->mSkipDrawing) {
+ mSkipDrawing = 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) {
+ DetailedGlyph *dst = AllocateDetailedGlyphs(i + aDest, count);
+ if (dst) {
+ DetailedGlyph *src =
+ aSource->GetDetailedGlyphs(i + aRange.start);
+ if (src) {
+ ::memcpy(dst, src, count * sizeof(DetailedGlyph));
+ } else {
+ g.SetMissing(0);
+ }
+ } else {
+ g.SetMissing(0);
+ }
+ }
+ }
+ dstGlyphs[i] = g;
+ }
+
+ // Copy glyph runs
+ GlyphRunIterator iter(aSource, aRange);
+#ifdef DEBUG
+ const GlyphRun *prevRun = nullptr;
+#endif
+ while (iter.NextRun()) {
+ gfxFont *font = iter.GetGlyphRun()->mFont;
+ NS_ASSERTION(!prevRun || prevRun->mFont != iter.GetGlyphRun()->mFont ||
+ prevRun->mMatchType != iter.GetGlyphRun()->mMatchType ||
+ prevRun->mOrientation != iter.GetGlyphRun()->mOrientation,
+ "Glyphruns not coalesced?");
+#ifdef DEBUG
+ prevRun = iter.GetGlyphRun();
+ uint32_t end = iter.GetStringEnd();
+#endif
+ uint32_t start = iter.GetStringStart();
+
+ // 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");
+
+ nsresult rv = AddGlyphRun(font, iter.GetGlyphRun()->mMatchType,
+ start - aRange.start + aDest, false,
+ iter.GetGlyphRun()->mOrientation);
+ if (NS_FAILED(rv))
+ return;
+ }
+}
+
+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, uint16_t aOrientation)
+{
+ if (SetSpaceGlyphIfSimple(aFont, aCharIndex, ' ', aOrientation)) {
+ return;
+ }
+
+ aFont->InitWordCache();
+ static const uint8_t space = ' ';
+ uint32_t flags = gfxTextRunFactory::TEXT_IS_8BIT |
+ gfxTextRunFactory::TEXT_IS_ASCII |
+ gfxTextRunFactory::TEXT_IS_PERSISTENT |
+ aOrientation;
+ bool vertical =
+ (GetFlags() & gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT) != 0;
+ gfxShapedWord* sw = aFont->GetShapedWord(aDrawTarget,
+ &space, 1,
+ gfxShapedWord::HashMix(0, ' '),
+ Script::LATIN,
+ vertical,
+ mAppUnitsPerDevUnit,
+ flags,
+ nullptr);
+ if (sw) {
+ AddGlyphRun(aFont, gfxTextRange::kFontGroup, aCharIndex, false,
+ aOrientation);
+ CopyGlyphDataFrom(sw, aCharIndex);
+ }
+}
+
+bool
+gfxTextRun::SetSpaceGlyphIfSimple(gfxFont* aFont, uint32_t aCharIndex,
+ char16_t aSpaceChar, uint16_t aOrientation)
+{
+ uint32_t spaceGlyph = aFont->GetSpaceGlyph();
+ if (!spaceGlyph || !CompressedGlyph::IsSimpleGlyphID(spaceGlyph)) {
+ return false;
+ }
+
+ gfxFont::Orientation fontOrientation =
+ (aOrientation & gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT) ?
+ gfxFont::eVertical : gfxFont::eHorizontal;
+ uint32_t spaceWidthAppUnits =
+ NS_lroundf(aFont->GetMetrics(fontOrientation).spaceWidth *
+ mAppUnitsPerDevUnit);
+ if (!CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits)) {
+ return false;
+ }
+
+ AddGlyphRun(aFont, gfxTextRange::kFontGroup, aCharIndex, false,
+ aOrientation);
+ CompressedGlyph g;
+ g.SetSimpleGlyph(spaceWidthAppUnits, spaceGlyph);
+ if (aSpaceChar == ' ') {
+ g.SetIsSpace();
+ }
+ GetCharacterGlyphs()[aCharIndex] = g;
+ return true;
+}
+
+void
+gfxTextRun::FetchGlyphExtents(DrawTarget* aRefDrawTarget)
+{
+ bool needsGlyphExtents = NeedsGlyphExtents(this);
+ if (!needsGlyphExtents && !mDetailedGlyphs)
+ return;
+
+ uint32_t i, runCount = mGlyphRuns.Length();
+ CompressedGlyph *charGlyphs = mCharacterGlyphs;
+ for (i = 0; i < runCount; ++i) {
+ const GlyphRun& run = mGlyphRuns[i];
+ gfxFont *font = run.mFont;
+ if (MOZ_UNLIKELY(font->GetStyle()->size == 0) ||
+ MOZ_UNLIKELY(font->GetStyle()->sizeAdjust == 0.0f)) {
+ continue;
+ }
+
+ uint32_t start = run.mCharacterOffset;
+ uint32_t end = i + 1 < runCount ?
+ mGlyphRuns[i + 1].mCharacterOffset : GetLength();
+ bool fontIsSetup = false;
+ uint32_t j;
+ gfxGlyphExtents *extents = font->GetOrCreateGlyphExtents(mAppUnitsPerDevUnit);
+
+ for (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->IsGlyphKnown(glyphIndex)) {
+ if (!fontIsSetup) {
+ if (!font->SetupCairoFont(aRefDrawTarget)) {
+ NS_WARNING("failed to set up font for glyph extents");
+ break;
+ }
+ fontIsSetup = true;
+ }
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+ ++gGlyphExtentsSetupEagerSimple;
+#endif
+ font->SetupGlyphExtents(aRefDrawTarget,
+ glyphIndex, false, extents);
+ }
+ }
+ } 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->IsGlyphKnownWithTightExtents(glyphIndex)) {
+ if (!fontIsSetup) {
+ if (!font->SetupCairoFont(aRefDrawTarget)) {
+ NS_WARNING("failed to set up font for glyph extents");
+ break;
+ }
+ fontIsSetup = true;
+ }
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+ ++gGlyphExtentsSetupEagerTight;
+#endif
+ font->SetupGlyphExtents(aRefDrawTarget,
+ glyphIndex, true, extents);
+ }
+ }
+ }
+ }
+ }
+}
+
+
+size_t
+gfxTextRun::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf)
+{
+ // The second arg is how much gfxTextRun::AllocateStorage would have
+ // allocated.
+ 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
+void
+gfxTextRun::Dump(FILE* aOutput) {
+ if (!aOutput) {
+ aOutput = stdout;
+ }
+
+ uint32_t i;
+ fputc('[', aOutput);
+ for (i = 0; i < mGlyphRuns.Length(); ++i) {
+ if (i > 0) {
+ fputc(',', aOutput);
+ }
+ gfxFont* font = mGlyphRuns[i].mFont;
+ const gfxFontStyle* style = font->GetStyle();
+ NS_ConvertUTF16toUTF8 fontName(font->GetName());
+ nsAutoCString lang;
+ style->language->ToUTF8String(lang);
+ fprintf(aOutput, "%d: %s %f/%d/%d/%s", mGlyphRuns[i].mCharacterOffset,
+ fontName.get(), style->size,
+ style->weight, style->style, lang.get());
+ }
+ fputc(']', aOutput);
+}
+#endif
+
+gfxFontGroup::gfxFontGroup(const FontFamilyList& aFontFamilyList,
+ const gfxFontStyle *aStyle,
+ gfxTextPerfMetrics* aTextPerf,
+ gfxUserFontSet *aUserFontSet,
+ gfxFloat aDevToCssSize)
+ : mFamilyList(aFontFamilyList)
+ , mStyle(*aStyle)
+ , mUnderlineOffset(UNDERLINE_OFFSET_NOT_SET)
+ , mHyphenWidth(-1)
+ , mDevToCssSize(aDevToCssSize)
+ , mUserFontSet(aUserFontSet)
+ , mTextPerf(aTextPerf)
+ , mLastPrefLang(eFontPrefLang_Western)
+ , mPageLang(gfxPlatformFontList::GetFontPrefLangFor(aStyle->language))
+ , mLastPrefFirstFont(false)
+ , mSkipDrawing(false)
+ , mSkipUpdateUserFonts(false)
+{
+ // 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()
+{
+}
+
+void
+gfxFontGroup::BuildFontList()
+{
+ bool enumerateFonts = true;
+
+#if defined(MOZ_WIDGET_GTK)
+ // xxx - eliminate this once gfxPangoFontGroup is no longer needed
+ enumerateFonts = gfxPlatformGtk::UseFcFontList();
+#endif
+ if (!enumerateFonts) {
+ return;
+ }
+
+ // initialize fonts in the font family list
+ AutoTArray<gfxFontFamily*,10> fonts;
+ gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList();
+
+ // lookup fonts in the fontlist
+ for (const FontFamilyName& name : mFamilyList.GetFontlist()) {
+ if (name.IsNamed()) {
+ AddPlatformFont(name.mName, fonts);
+ } else {
+ pfl->AddGenericFonts(name.mType, mStyle.language, fonts);
+ if (mTextPerf) {
+ mTextPerf->current.genericLookups++;
+ }
+ }
+ }
+
+ // if necessary, append default generic onto the end
+ if (mFamilyList.GetDefaultFontType() != eFamily_none &&
+ !mFamilyList.HasDefaultGeneric()) {
+ pfl->AddGenericFonts(mFamilyList.GetDefaultFontType(),
+ mStyle.language, fonts);
+ if (mTextPerf) {
+ mTextPerf->current.genericLookups++;
+ }
+ }
+
+ // build the fontlist from the specified families
+ for (gfxFontFamily* fontFamily : fonts) {
+ AddFamilyToFontList(fontFamily);
+ }
+}
+
+void
+gfxFontGroup::AddPlatformFont(const nsAString& aName,
+ nsTArray<gfxFontFamily*>& 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.
+ gfxFontFamily* family = mUserFontSet->LookupFamily(aName);
+ if (family) {
+ aFamilyList.AppendElement(family);
+ return;
+ }
+ }
+
+ // Not known in the user font set ==> check system fonts
+ gfxPlatformFontList::PlatformFontList()
+ ->FindAndAddFamilies(aName, &aFamilyList, &mStyle, mDevToCssSize);
+}
+
+void
+gfxFontGroup::AddFamilyToFontList(gfxFontFamily* aFamily)
+{
+ NS_ASSERTION(aFamily, "trying to add a null font family to fontlist");
+ AutoTArray<gfxFontEntry*,4> fontEntryList;
+ bool needsBold;
+ aFamily->FindAllFontsForStyle(mStyle, fontEntryList, needsBold);
+ // add these to the fontlist
+ for (gfxFontEntry* fe : fontEntryList) {
+ if (!HasFont(fe)) {
+ FamilyFace ff(aFamily, fe, needsBold);
+ 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();
+ }
+}
+
+bool
+gfxFontGroup::HasFont(const gfxFontEntry *aFontEntry)
+{
+ uint32_t count = mFonts.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ if (mFonts[i].FontEntry() == aFontEntry) {
+ return true;
+ }
+ }
+ return false;
+}
+
+gfxFont*
+gfxFontGroup::GetFontAt(int32_t i, uint32_t aCh)
+{
+ 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 = mFonts[i].FontEntry();
+ gfxCharacterMap* unicodeRangeMap = nullptr;
+ if (fe->mIsUserFontContainer) {
+ gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe);
+ if (ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED &&
+ ufe->CharacterInUnicodeRange(aCh) &&
+ !FontLoadingForFamily(ff.Family(), aCh)) {
+ ufe->Load();
+ ff.CheckState(mSkipDrawing);
+ }
+ fe = ufe->GetPlatformFontEntry();
+ if (!fe) {
+ return nullptr;
+ }
+ unicodeRangeMap = ufe->GetUnicodeRangeMap();
+ }
+ font = fe->FindOrMakeFont(&mStyle, mFonts[i].NeedsBold(),
+ unicodeRangeMap);
+ if (!font || !font->Valid()) {
+ ff.SetInvalid();
+ return nullptr;
+ }
+ mFonts[i].SetFont(font);
+ }
+ return font.get();
+}
+
+void
+gfxFontGroup::FamilyFace::CheckState(bool& aSkipDrawing)
+{
+ gfxFontEntry* fe = FontEntry();
+ if (fe->mIsUserFontContainer) {
+ gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe);
+ gfxUserFontEntry::UserFontLoadState state = ufe->LoadState();
+ switch (state) {
+ case gfxUserFontEntry::STATUS_LOADING:
+ SetLoading(true);
+ break;
+ case gfxUserFontEntry::STATUS_FAILED:
+ SetInvalid();
+ // fall-thru to the default case
+ MOZ_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;
+}
+
+bool
+gfxFontGroup::FontLoadingForFamily(gfxFontFamily* aFamily, uint32_t aCh) const
+{
+ uint32_t count = mFonts.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ const FamilyFace& ff = mFonts[i];
+ if (ff.IsLoading() && ff.Family() == aFamily) {
+ const gfxUserFontEntry* ufe =
+ static_cast<gfxUserFontEntry*>(ff.FontEntry());
+ if (ufe->CharacterInUnicodeRange(aCh)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+gfxFont*
+gfxFontGroup::GetDefaultFont()
+{
+ if (mDefaultFont) {
+ return mDefaultFont.get();
+ }
+
+ bool needsBold;
+ gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList();
+ gfxFontFamily *defaultFamily = pfl->GetDefaultFont(&mStyle);
+ NS_ASSERTION(defaultFamily,
+ "invalid default font returned by GetDefaultFont");
+
+ if (defaultFamily) {
+ gfxFontEntry *fe = defaultFamily->FindFontForStyle(mStyle,
+ needsBold);
+ if (fe) {
+ mDefaultFont = fe->FindOrMakeFont(&mStyle, needsBold);
+ }
+ }
+
+ uint32_t numInits, loaderState;
+ pfl->GetFontlistInitInfo(numInits, loaderState);
+ NS_ASSERTION(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)
+ 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,
+ needsBold);
+ if (fe) {
+ mDefaultFont = fe->FindOrMakeFont(&mStyle, needsBold);
+ if (mDefaultFont) {
+ break;
+ }
+ }
+ }
+ }
+
+ 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
+ nsAutoString familiesString;
+ mFamilyList.ToString(familiesString);
+ SprintfLiteral(msg, "unable to find a usable font (%.220s)",
+ NS_ConvertUTF16toUTF8(familiesString).get());
+ NS_RUNTIMEABORT(msg);
+ }
+
+ return mDefaultFont.get();
+}
+
+gfxFont*
+gfxFontGroup::GetFirstValidFont(uint32_t aCh)
+{
+ uint32_t count = mFonts.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ FamilyFace& ff = mFonts[i];
+ if (ff.IsInvalid()) {
+ continue;
+ }
+
+ // already have a font?
+ gfxFont* font = ff.Font();
+ if (font) {
+ return font;
+ }
+
+ // Need to build a font, loading userfont if not loaded. In
+ // cases where unicode range might apply, use the character
+ // provided.
+ if (ff.IsUserFontContainer()) {
+ gfxUserFontEntry* ufe =
+ static_cast<gfxUserFontEntry*>(mFonts[i].FontEntry());
+ bool inRange = ufe->CharacterInUnicodeRange(aCh);
+ if (ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED &&
+ inRange && !FontLoadingForFamily(ff.Family(), aCh)) {
+ ufe->Load();
+ ff.CheckState(mSkipDrawing);
+ }
+ if (ufe->LoadState() != gfxUserFontEntry::STATUS_LOADED ||
+ !inRange) {
+ continue;
+ }
+ }
+
+ font = GetFontAt(i, aCh);
+ if (font) {
+ return font;
+ }
+ }
+ return GetDefaultFont();
+}
+
+gfxFont *
+gfxFontGroup::GetFirstMathFont()
+{
+ uint32_t count = mFonts.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ gfxFont* font = GetFontAt(i);
+ if (font && font->TryGetMathTable()) {
+ return font;
+ }
+ }
+ return nullptr;
+}
+
+gfxFontGroup *
+gfxFontGroup::Copy(const gfxFontStyle *aStyle)
+{
+ gfxFontGroup *fg =
+ new gfxFontGroup(mFamilyList, aStyle, mTextPerf,
+ mUserFontSet, mDevToCssSize);
+ return fg;
+}
+
+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;
+ }
+ return (((ch & 0xFF00) == 0x2000 /* Unicode control character */ &&
+ (ch == 0x200B/*ZWSP*/ || ch == 0x2028/*LSEP*/ || ch == 0x2029/*PSEP*/)) ||
+ IsBidiControl(ch));
+}
+
+already_AddRefed<gfxTextRun>
+gfxFontGroup::MakeEmptyTextRun(const Parameters *aParams, uint32_t aFlags)
+{
+ aFlags |= TEXT_IS_8BIT | TEXT_IS_ASCII | TEXT_IS_PERSISTENT;
+ return gfxTextRun::Create(aParams, 0, this, aFlags);
+}
+
+already_AddRefed<gfxTextRun>
+gfxFontGroup::MakeSpaceTextRun(const Parameters *aParams, uint32_t aFlags)
+{
+ aFlags |= TEXT_IS_8BIT | TEXT_IS_ASCII | TEXT_IS_PERSISTENT;
+
+ RefPtr<gfxTextRun> textRun =
+ gfxTextRun::Create(aParams, 1, this, aFlags);
+ if (!textRun) {
+ return nullptr;
+ }
+
+ uint16_t orientation = aFlags & TEXT_ORIENT_MASK;
+ if (orientation == TEXT_ORIENT_VERTICAL_MIXED) {
+ orientation = TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
+ }
+
+ gfxFont *font = GetFirstValidFont();
+ if (MOZ_UNLIKELY(GetStyle()->size == 0) ||
+ MOZ_UNLIKELY(GetStyle()->sizeAdjust == 0.0f)) {
+ // 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, gfxTextRange::kFontGroup, 0, false,
+ orientation);
+ }
+ 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.
+ uint8_t 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();
+}
+
+already_AddRefed<gfxTextRun>
+gfxFontGroup::MakeBlankTextRun(uint32_t aLength,
+ const Parameters *aParams, uint32_t aFlags)
+{
+ RefPtr<gfxTextRun> textRun =
+ gfxTextRun::Create(aParams, aLength, this, aFlags);
+ if (!textRun) {
+ return nullptr;
+ }
+
+ uint16_t orientation = aFlags & TEXT_ORIENT_MASK;
+ if (orientation == TEXT_ORIENT_VERTICAL_MIXED) {
+ orientation = TEXT_ORIENT_VERTICAL_UPRIGHT;
+ }
+ textRun->AddGlyphRun(GetFirstValidFont(), gfxTextRange::kFontGroup, 0, false,
+ orientation);
+ return textRun.forget();
+}
+
+already_AddRefed<gfxTextRun>
+gfxFontGroup::MakeHyphenTextRun(DrawTarget* aDrawTarget,
+ 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;
+ gfxFont *font = GetFirstValidFont(uint32_t(hyphen));
+ if (font->HasCharacter(hyphen)) {
+ return MakeTextRun(&hyphen, 1, aDrawTarget, aAppUnitsPerDevUnit,
+ gfxFontGroup::TEXT_IS_PERSISTENT, nullptr);
+ }
+
+ static const uint8_t dash = '-';
+ return MakeTextRun(&dash, 1, aDrawTarget, aAppUnitsPerDevUnit,
+ gfxFontGroup::TEXT_IS_PERSISTENT, nullptr);
+}
+
+gfxFloat
+gfxFontGroup::GetHyphenWidth(gfxTextRun::PropertyProvider *aProvider)
+{
+ if (mHyphenWidth < 0) {
+ RefPtr<DrawTarget> dt(aProvider->GetDrawTarget());
+ if (dt) {
+ RefPtr<gfxTextRun>
+ hyphRun(MakeHyphenTextRun(dt,
+ aProvider->GetAppUnitsPerDevUnit()));
+ mHyphenWidth = hyphRun.get() ? hyphRun->GetAdvanceWidth() : 0;
+ }
+ }
+ return mHyphenWidth;
+}
+
+already_AddRefed<gfxTextRun>
+gfxFontGroup::MakeTextRun(const uint8_t *aString, uint32_t aLength,
+ const Parameters *aParams, uint32_t aFlags,
+ gfxMissingFontRecorder *aMFR)
+{
+ if (aLength == 0) {
+ return MakeEmptyTextRun(aParams, aFlags);
+ }
+ if (aLength == 1 && aString[0] == ' ') {
+ return MakeSpaceTextRun(aParams, aFlags);
+ }
+
+ aFlags |= TEXT_IS_8BIT;
+
+ if (MOZ_UNLIKELY(GetStyle()->size == 0) ||
+ MOZ_UNLIKELY(GetStyle()->sizeAdjust == 0.0f)) {
+ // 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(aLength, aParams, aFlags);
+ }
+
+ RefPtr<gfxTextRun> textRun = gfxTextRun::Create(aParams, aLength, this,
+ aFlags);
+ if (!textRun) {
+ return nullptr;
+ }
+
+ InitTextRun(aParams->mDrawTarget, textRun.get(), aString, aLength, aMFR);
+
+ textRun->FetchGlyphExtents(aParams->mDrawTarget);
+
+ return textRun.forget();
+}
+
+already_AddRefed<gfxTextRun>
+gfxFontGroup::MakeTextRun(const char16_t *aString, uint32_t aLength,
+ const Parameters *aParams, uint32_t aFlags,
+ gfxMissingFontRecorder *aMFR)
+{
+ if (aLength == 0) {
+ return MakeEmptyTextRun(aParams, aFlags);
+ }
+ if (aLength == 1 && aString[0] == ' ') {
+ return MakeSpaceTextRun(aParams, aFlags);
+ }
+ if (MOZ_UNLIKELY(GetStyle()->size == 0) ||
+ MOZ_UNLIKELY(GetStyle()->sizeAdjust == 0.0f)) {
+ return MakeBlankTextRun(aLength, aParams, aFlags);
+ }
+
+ RefPtr<gfxTextRun> textRun = gfxTextRun::Create(aParams, aLength, this,
+ aFlags);
+ if (!textRun) {
+ return nullptr;
+ }
+
+ InitTextRun(aParams->mDrawTarget, textRun.get(), aString, aLength, aMFR);
+
+ textRun->FetchGlyphExtents(aParams->mDrawTarget);
+
+ return textRun.forget();
+}
+
+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
+ int32_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() & gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR) != 0;
+ 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 (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;
+ mStyle.language->ToUTF8String(lang);
+ nsAutoString families;
+ mFamilyList.ToString(families);
+ nsAutoCString str((const char*)aString, aLength);
+ MOZ_LOG(log, LogLevel::Warning,\
+ ("(%s) fontgroup: [%s] default: %s lang: %s script: %d "
+ "len %d weight: %d width: %d style: %s size: %6.2f %d-byte "
+ "TEXTRUN [%s] ENDTEXTRUN\n",
+ (mStyle.systemFont ? "textrunui" : "textrun"),
+ NS_ConvertUTF16toUTF8(families).get(),
+ (mFamilyList.GetDefaultFontType() == eFamily_serif ?
+ "serif" :
+ (mFamilyList.GetDefaultFontType() == eFamily_sans_serif ?
+ "sans-serif" : "none")),
+ lang.get(), Script::LATIN, aLength,
+ uint32_t(mStyle.weight), uint32_t(mStyle.stretch),
+ (mStyle.style & NS_FONT_STYLE_ITALIC ? "italic" :
+ (mStyle.style & NS_FONT_STYLE_OBLIQUE ? "oblique" :
+ "normal")),
+ 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;
+ mStyle.language->ToUTF8String(lang);
+ nsAutoString families;
+ mFamilyList.ToString(families);
+ uint32_t runLen = runLimit - runStart;
+ MOZ_LOG(log, LogLevel::Warning,\
+ ("(%s) fontgroup: [%s] default: %s lang: %s script: %d "
+ "len %d weight: %d width: %d style: %s size: %6.2f "
+ "%d-byte TEXTRUN [%s] ENDTEXTRUN\n",
+ (mStyle.systemFont ? "textrunui" : "textrun"),
+ NS_ConvertUTF16toUTF8(families).get(),
+ (mFamilyList.GetDefaultFontType() == eFamily_serif ?
+ "serif" :
+ (mFamilyList.GetDefaultFontType() == eFamily_sans_serif ?
+ "sans-serif" : "none")),
+ lang.get(), runScript, runLen,
+ uint32_t(mStyle.weight), uint32_t(mStyle.stretch),
+ (mStyle.style & NS_FONT_STYLE_ITALIC ? "italic" :
+ (mStyle.style & NS_FONT_STYLE_OBLIQUE ? "oblique" :
+ "normal")),
+ 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();
+
+ aTextRun->SortGlyphRuns();
+}
+
+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 (!mSkipUpdateUserFonts && mUserFontSet &&
+ mCurrGeneration != mUserFontSet->GetGeneration()) {
+ UpdateUserFonts();
+ }
+
+ gfxFont *mainFont = GetFirstValidFont();
+
+ uint32_t runStart = 0;
+ AutoTArray<gfxTextRange,3> fontRanges;
+ ComputeRanges(fontRanges, aString, aLength, aRunScript,
+ aTextRun->GetFlags() & gfxTextRunFactory::TEXT_ORIENT_MASK);
+ uint32_t numRanges = fontRanges.Length();
+ bool missingChars = false;
+
+ for (uint32_t r = 0; r < numRanges; r++) {
+ const gfxTextRange& range = fontRanges[r];
+ uint32_t matchedLength = range.Length();
+ gfxFont *matchedFont = range.font;
+ bool vertical =
+ range.orientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
+ // 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);
+ if (!matchedFont->SplitAndInitTextRun(aDrawTarget, aTextRun,
+ aString + runStart,
+ aOffset + runStart,
+ matchedLength,
+ aRunScript,
+ vertical)) {
+ // 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);
+ if (!subSuperFont->SplitAndInitTextRun(aDrawTarget, aTextRun,
+ aString + runStart,
+ aOffset + runStart,
+ matchedLength,
+ aRunScript,
+ vertical)) {
+ // glyph layout failed! treat as missing glyphs
+ matchedFont = nullptr;
+ }
+ } else if (mStyle.variantCaps != NS_FONT_VARIANT_CAPS_NORMAL &&
+ !matchedFont->SupportsVariantCaps(aRunScript,
+ mStyle.variantCaps,
+ petiteToSmallCaps,
+ syntheticLower,
+ syntheticUpper))
+ {
+ // fallback for small-caps variant glyphs
+ if (!matchedFont->InitFakeSmallCapsRun(aDrawTarget, aTextRun,
+ aString + runStart,
+ aOffset + runStart,
+ matchedLength,
+ range.matchType,
+ range.orientation,
+ aRunScript,
+ 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);
+ if (!matchedFont->SplitAndInitTextRun(aDrawTarget, aTextRun,
+ aString + runStart,
+ aOffset + runStart,
+ matchedLength,
+ aRunScript,
+ vertical)) {
+ // glyph layout failed! treat as missing glyphs
+ matchedFont = nullptr;
+ }
+ }
+ } else {
+ aTextRun->AddGlyphRun(mainFont, gfxTextRange::kFontGroup,
+ aOffset + runStart, (matchedLength > 0),
+ range.orientation);
+ }
+
+ 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 (sizeof(T) == sizeof(char16_t)) {
+ if (NS_IS_HIGH_SURROGATE(ch) &&
+ index + 1 < aLength &&
+ NS_IS_LOW_SURROGATE(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;
+ detailedGlyph.mXOffset = detailedGlyph.mYOffset = 0;
+ gfxShapedText::CompressedGlyph g;
+ g.SetComplex(true, true, 1);
+ aTextRun->SetGlyphs(aOffset + index,
+ g, &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, uint32_t aFlags,
+ LazyReferenceDrawTargetGetter& aRefDrawTargetGetter)
+{
+ MOZ_ASSERT(!(aFlags & ~TEXT_ORIENT_MASK),
+ "flags here should only be used to specify orientation");
+ if (mCachedEllipsisTextRun &&
+ (mCachedEllipsisTextRun->GetFlags() & 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.
+ gfxFont* firstFont = GetFirstValidFont(uint32_t(kEllipsisChar[0]));
+ 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.get(), ellipsis.Length(), &params,
+ aFlags | TEXT_IS_PERSISTENT, 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,
+ Script aRunScript)
+{
+ GlobalFontMatch data(aCh, aRunScript, &mStyle);
+ aFamily->SearchAllFontsForChar(&data);
+ gfxFontEntry* fe = data.mBestMatch;
+ if (!fe) {
+ return nullptr;
+ }
+
+ bool needsBold = mStyle.weight >= 600 && !fe->IsBold() &&
+ mStyle.allowSyntheticWeight;
+ RefPtr<gfxFont> font = fe->FindOrMakeFont(&mStyle, needsBold);
+ return font.forget();
+}
+
+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];
+ if (!ff.IsUserFontContainer() &&
+ !ff.FontEntry()->IsUserFont() &&
+ ff.Family() &&
+ ff.Family()->IsBadUnderlineFamily()) {
+ RefPtr<gfxFont> font = GetFontAt(i);
+ if (!font) {
+ continue;
+ }
+ gfxFloat bad = font->GetMetrics(gfxFont::eHorizontal).
+ underlineOffset;
+ gfxFloat first =
+ GetFirstValidFont()->GetMetrics(gfxFont::eHorizontal).
+ underlineOffset;
+ mUnderlineOffset = std::min(first, bad);
+ return mUnderlineOffset;
+ }
+ }
+
+ // no bad underline fonts, use the first valid font's metric
+ mUnderlineOffset = GetFirstValidFont()->
+ GetMetrics(gfxFont::eHorizontal).underlineOffset;
+ }
+
+ return mUnderlineOffset;
+}
+
+already_AddRefed<gfxFont>
+gfxFontGroup::FindFontForChar(uint32_t aCh, uint32_t aPrevCh, uint32_t aNextCh,
+ Script aRunScript, gfxFont *aPrevMatchedFont,
+ uint8_t *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) &&
+ aPrevMatchedFont->HasCharacter(aCh)) {
+ RefPtr<gfxFont> ret = aPrevMatchedFont;
+ return ret.forget();
+ }
+
+ // Special cases for NNBSP (as used in Mongolian):
+ const uint32_t NARROW_NO_BREAK_SPACE = 0x202f;
+ 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)) {
+ RefPtr<gfxFont> ret = aPrevMatchedFont;
+ return ret.forget();
+ }
+ }
+
+ // To optimize common cases, try the first font in the font-group
+ // before going into the more detailed checks below
+ uint32_t nextIndex = 0;
+ bool isJoinControl = gfxFontUtils::IsJoinControl(aCh);
+ bool wasJoinCauser = gfxFontUtils::IsJoinCauser(aPrevCh);
+ bool isVarSelector = gfxFontUtils::IsVarSelector(aCh);
+
+ if (!isJoinControl && !wasJoinCauser && !isVarSelector) {
+ RefPtr<gfxFont> firstFont = GetFontAt(0, aCh);
+ if (firstFont) {
+ if (firstFont->HasCharacter(aCh)) {
+ *aMatchType = gfxTextRange::kFontGroup;
+ return firstFont.forget();
+ }
+
+ RefPtr<gfxFont> font;
+ if (mFonts[0].CheckForFallbackFaces()) {
+ font = FindFallbackFaceForChar(mFonts[0].Family(), aCh,
+ aRunScript);
+ } 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.
+ gfxFontEntry* fe = firstFont->GetFontEntry();
+ if (!fe->IsUpright() ||
+ fe->Weight() != NS_FONT_WEIGHT_NORMAL ||
+ fe->Stretch() != NS_FONT_STRETCH_NORMAL) {
+ // 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).
+ font = FindFallbackFaceForChar(mFonts[0].Family(), aCh,
+ aRunScript);
+ }
+ }
+ if (font) {
+ *aMatchType = gfxTextRange::kFontGroup;
+ return font.forget();
+ }
+ }
+
+ // 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) {
+ RefPtr<gfxFont> ret = aPrevMatchedFont;
+ return ret.forget();
+ }
+
+ // 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)) {
+ RefPtr<gfxFont> ret = aPrevMatchedFont;
+ return ret.forget();
+ }
+ }
+ }
+
+ // if this character is a variation selector,
+ // use the previous font regardless of whether it supports VS or not.
+ // otherwise the text run will be divided.
+ if (isVarSelector) {
+ if (aPrevMatchedFont) {
+ RefPtr<gfxFont> ret = aPrevMatchedFont;
+ return ret.forget();
+ }
+ // VS alone. it's meaningless to search different fonts
+ return nullptr;
+ }
+
+ // 1. check remaining fonts in the font group
+ uint32_t fontListLength = mFonts.Length();
+ for (uint32_t i = nextIndex; i < fontListLength; i++) {
+ FamilyFace& ff = mFonts[i];
+ if (ff.IsInvalid() || ff.IsLoading()) {
+ continue;
+ }
+
+ // if available, use already made gfxFont and check for character
+ RefPtr<gfxFont> font = ff.Font();
+ if (font) {
+ if (font->HasCharacter(aCh)) {
+ return font.forget();
+ }
+ continue;
+ }
+
+ // don't have a gfxFont yet, test before building
+ gfxFontEntry *fe = ff.FontEntry();
+ if (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 but only if no other font in similar
+ // range within family is loading
+ if (ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED &&
+ !FontLoadingForFamily(ff.Family(), aCh)) {
+ ufe->Load();
+ ff.CheckState(mSkipDrawing);
+ }
+ gfxFontEntry* pfe = ufe->GetPlatformFontEntry();
+ if (pfe && pfe->HasCharacter(aCh)) {
+ font = GetFontAt(i, aCh);
+ if (font) {
+ *aMatchType = gfxTextRange::kFontGroup;
+ return font.forget();
+ }
+ }
+ } else if (fe->HasCharacter(aCh)) {
+ // for normal platform fonts, after checking the cmap
+ // build the font via GetFontAt
+ font = GetFontAt(i, aCh);
+ if (font) {
+ *aMatchType = gfxTextRange::kFontGroup;
+ return font.forget();
+ }
+ }
+
+ // check other family faces if needed
+ if (ff.CheckForFallbackFaces()) {
+ NS_ASSERTION(i == 0 ? true :
+ !mFonts[i-1].CheckForFallbackFaces() ||
+ !mFonts[i-1].Family()->Name().Equals(ff.Family()->Name()),
+ "should only do fallback once per font family");
+ font = FindFallbackFaceForChar(ff.Family(), aCh, aRunScript);
+ if (font) {
+ *aMatchType = gfxTextRange::kFontGroup;
+ 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).
+ fe = ff.FontEntry();
+ if (!fe->mIsUserFontContainer && !fe->IsUserFont() &&
+ (!fe->IsUpright() ||
+ fe->Weight() != NS_FONT_WEIGHT_NORMAL ||
+ fe->Stretch() != NS_FONT_STRETCH_NORMAL)) {
+ font = FindFallbackFaceForChar(ff.Family(), aCh, aRunScript);
+ if (font) {
+ *aMatchType = gfxTextRange::kFontGroup;
+ return font.forget();
+ }
+ }
+ }
+ }
+
+ if (fontListLength == 0) {
+ RefPtr<gfxFont> defaultFont = GetDefaultFont();
+ if (defaultFont->HasCharacter(aCh)) {
+ *aMatchType = gfxTextRange::kFontGroup;
+ return defaultFont.forget();
+ }
+ }
+
+ // if character is in Private Use Area, don't do matching against pref or system fonts
+ if ((aCh >= 0xE000 && aCh <= 0xF8FF) || (aCh >= 0xF0000 && aCh <= 0x10FFFD))
+ return nullptr;
+
+ // 2. search pref fonts
+ RefPtr<gfxFont> font = WhichPrefFontSupportsChar(aCh);
+ if (font) {
+ *aMatchType = gfxTextRange::kPrefsFallback;
+ return font.forget();
+ }
+
+ // 3. use fallback fonts
+ // -- before searching for something else check the font used for the previous character
+ if (aPrevMatchedFont && aPrevMatchedFont->HasCharacter(aCh)) {
+ *aMatchType = gfxTextRange::kSystemFallback;
+ RefPtr<gfxFont> ret = aPrevMatchedFont;
+ return ret.forget();
+ }
+
+ // for known "space" characters, don't do a full system-fallback search;
+ // we'll synthesize appropriate-width spaces instead of missing-glyph boxes
+ if (GetGeneralCategory(aCh) ==
+ HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR &&
+ GetFirstValidFont()->SynthesizeSpaceWidth(aCh) >= 0.0)
+ {
+ return nullptr;
+ }
+
+ // -- otherwise look for other stuff
+ *aMatchType = gfxTextRange::kSystemFallback;
+ font = WhichSystemFontSupportsChar(aCh, aNextCh, aRunScript);
+ return font.forget();
+}
+
+template<typename T>
+void gfxFontGroup::ComputeRanges(nsTArray<gfxTextRange>& aRanges,
+ const T *aString, uint32_t aLength,
+ Script aRunScript, uint16_t 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 (sizeof(T) == sizeof(char16_t)) {
+ if (aLength > 1 && NS_IS_HIGH_SURROGATE(nextCh) &&
+ NS_IS_LOW_SURROGATE(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)
+ gfxFont *prevFont = GetFirstValidFont();
+
+ // if we use the initial value of prevFont, we treat this as a match from
+ // the font group; fixes bug 978313
+ uint8_t matchType = gfxTextRange::kFontGroup;
+
+ 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 (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_HIGH_SURROGATE(nextCh) &&
+ NS_IS_LOW_SURROGATE(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;
+ }
+
+ if (ch == 0xa0) {
+ ch = ' ';
+ }
+
+ // find the font for this char
+ RefPtr<gfxFont> font =
+ FindFontForChar(ch, prevCh, nextCh, aRunScript, prevFont,
+ &matchType);
+
+#ifndef RELEASE_OR_BETA
+ if (MOZ_UNLIKELY(mTextPerf)) {
+ if (matchType == gfxTextRange::kPrefsFallback) {
+ mTextPerf->current.fallbackPrefs++;
+ } else if (matchType == gfxTextRange::kSystemFallback) {
+ mTextPerf->current.fallbackSystem++;
+ }
+ }
+#endif
+
+ prevCh = ch;
+
+ uint16_t orient = aOrientation;
+ if (aOrientation == gfxTextRunFactory::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_Tr:
+ case VERTICAL_ORIENTATION_Tu:
+ orient = TEXT_ORIENT_VERTICAL_UPRIGHT;
+ break;
+ case VERTICAL_ORIENTATION_R:
+ orient = TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
+ break;
+ }
+ }
+
+ if (lastRangeIndex == -1) {
+ // first char ==> make a new range
+ aRanges.AppendElement(gfxTextRange(0, 1, font, matchType, orient));
+ lastRangeIndex++;
+ prevFont = font;
+ } else {
+ // if font or orientation has changed, make a new range...
+ // unless ch is a variation selector (bug 1248248)
+ gfxTextRange& prevRange = aRanges[lastRangeIndex];
+ if (prevRange.font != font || prevRange.matchType != matchType ||
+ (prevRange.orientation != orient && !IsClusterExtender(ch))) {
+ // close out the previous range
+ prevRange.end = origI;
+ aRanges.AppendElement(gfxTextRange(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 = font;
+ }
+ }
+ }
+ }
+
+ 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;
+ mStyle.language->ToUTF8String(lang);
+ nsAutoString families;
+ mFamilyList.ToString(families);
+
+ // collect the font matched for each range
+ nsAutoCString fontMatches;
+ for (size_t i = 0, i_end = aRanges.Length(); i < i_end; i++) {
+ const gfxTextRange& r = aRanges[i];
+ fontMatches.AppendPrintf(" [%u:%u] %.200s (%s)", r.start, r.end,
+ (r.font.get() ?
+ NS_ConvertUTF16toUTF8(r.font->GetName()).get() : "<null>"),
+ (r.matchType == gfxTextRange::kFontGroup ?
+ "list" :
+ (r.matchType == gfxTextRange::kPrefsFallback) ?
+ "prefs" : "sys"));
+ }
+ MOZ_LOG(log, LogLevel::Debug,\
+ ("(%s-fontmatching) fontgroup: [%s] default: %s lang: %s script: %d"
+ "%s\n",
+ (mStyle.systemFont ? "textrunui" : "textrun"),
+ NS_ConvertUTF16toUTF8(families).get(),
+ (mFamilyList.GetDefaultFontType() == eFamily_serif ?
+ "serif" :
+ (mFamilyList.GetDefaultFontType() == eFamily_sans_serif ?
+ "sans-serif" : "none")),
+ lang.get(), 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();
+}
+
+// note: gfxPangoFontGroup overrides UpdateUserFonts, such that
+// BuildFontList is never used
+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)
+{
+ RefPtr<gfxFont> font;
+
+ // get the pref font list if it hasn't been set up already
+ uint32_t unicodeRange = FindCharUnicodeRange(aCh);
+ gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
+ eFontPrefLang charLang = pfl->GetFontPrefLangFor(unicodeRange);
+
+ // 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)) {
+ font = mLastPrefFont;
+ return font.forget();
+ }
+
+ // 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];
+ mozilla::FontFamilyType defaultGeneric =
+ pfl->GetDefaultGeneric(currentLang);
+ nsTArray<RefPtr<gfxFontFamily>>* families =
+ pfl->GetPrefFontsLangGroup(defaultGeneric, 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
+ gfxFontFamily *family = (*families)[j];
+ if (!family) 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)) {
+ font = mLastPrefFont;
+ return font.forget();
+ }
+
+ bool needsBold;
+ gfxFontEntry *fe = family->FindFontForStyle(mStyle, needsBold);
+ // if ch in cmap, create and return a gfxFont
+ if (fe && fe->HasCharacter(aCh)) {
+ RefPtr<gfxFont> prefFont = fe->FindOrMakeFont(&mStyle, needsBold);
+ if (!prefFont) continue;
+ 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)
+{
+ gfxFontEntry *fe =
+ gfxPlatformFontList::PlatformFontList()->
+ SystemFindFontForChar(aCh, aNextCh, aRunScript, &mStyle);
+ if (fe) {
+ bool wantBold = mStyle.ComputeWeight() >= 6;
+ RefPtr<gfxFont> font =
+ fe->FindOrMakeFont(&mStyle, wantBold && !fe->IsBold());
+ return font.forget();
+ }
+
+ return nullptr;
+}
+
+/*static*/ void
+gfxFontGroup::Shutdown()
+{
+ NS_IF_RELEASE(gLangService);
+}
+
+nsILanguageAtomService* gfxFontGroup::gLangService = nullptr;
+
+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 000000000..0e44456ae
--- /dev/null
+++ b/gfx/thebes/gfxTextRun.h
@@ -0,0 +1,1229 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 et sw=4 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 "gfxTypes.h"
+#include "nsString.h"
+#include "gfxPoint.h"
+#include "gfxFont.h"
+#include "gfxFontConstants.h"
+#include "nsTArray.h"
+#include "gfxSkipChars.h"
+#include "gfxPlatform.h"
+#include "mozilla/MemoryReporting.h"
+#include "DrawMode.h"
+#include "harfbuzz/hb.h"
+#include "nsUnicodeScriptCodes.h"
+#include "nsColor.h"
+
+#ifdef DEBUG
+#include <stdio.h>
+#endif
+
+class gfxContext;
+class gfxFontGroup;
+class gfxUserFontEntry;
+class gfxUserFontSet;
+class nsIAtom;
+class nsILanguageAtomService;
+class gfxMissingFontRecorder;
+
+namespace mozilla {
+class SVGContextPaint;
+};
+
+/**
+ * Callback for Draw() to use when drawing text with mode
+ * DrawMode::GLYPH_PATH.
+ */
+struct 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();
+ }
+
+ // 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);
+
+ /**
+ * 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, bool *aBreakBefore) = 0;
+
+ // Returns the provider's hyphenation setting, so callers can decide
+ // whether it is necessary to call GetHyphenationBreaks.
+ // Result is an NS_STYLE_HYPHENS_* value.
+ virtual int8_t GetHyphensOption() = 0;
+
+ // Returns the extra width that will be consumed by a hyphen. This should
+ // be constant for a given textrun.
+ virtual gfxFloat GetHyphenWidth() = 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) = 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() = 0;
+
+ // Return the appUnitsPerDevUnit value to be used when measuring.
+ // Only called if the hyphen width is requested.
+ virtual uint32_t GetAppUnitsPerDevUnit() = 0;
+ };
+
+ struct DrawParams
+ {
+ gfxContext* context;
+ DrawMode drawMode = DrawMode::GLYPH_FILL;
+ nscolor textStrokeColor = 0;
+ gfxPattern* textStrokePattern = nullptr;
+ const mozilla::gfx::StrokeOptions *strokeOpts = nullptr;
+ const mozilla::gfx::DrawOptions *drawOpts = nullptr;
+ PropertyProvider* provider = nullptr;
+ // If non-null, the advance width of the substring is set.
+ gfxFloat* advanceWidth = nullptr;
+ mozilla::SVGContextPaint* contextPaint = nullptr;
+ gfxTextRunDrawCallbacks* callbacks = nullptr;
+ 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(Range aRange, gfxPoint 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, gfxPoint aPt,
+ Range aRange, 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,
+ PropertyProvider* aProvider) const;
+
+ Metrics MeasureText(gfxFont::BoundingBoxType aBoundingBoxType,
+ DrawTarget* aDrawTargetForTightBoundingBox,
+ PropertyProvider* aProvider = nullptr) const {
+ return MeasureText(Range(this), aBoundingBoxType,
+ aDrawTargetForTightBoundingBox, aProvider);
+ }
+
+ /**
+ * 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, PropertyProvider *aProvider,
+ PropertyProvider::Spacing*
+ aSpacing = nullptr) const;
+
+ gfxFloat GetAdvanceWidth() const {
+ return GetAdvanceWidth(Range(this), nullptr);
+ }
+
+ /**
+ * 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
+ };
+
+ /**
+ * Finds the longest substring that will fit into the given width.
+ * Uses GetHyphenationBreaks and GetSpacing from aBreakProvider.
+ * Guarantees the following:
+ * -- 0 <= result <= aMaxLength
+ * -- result is the maximal value of N such that either
+ * N < aMaxLength && line break at N && GetAdvanceWidth(aStart, N) <= aWidth
+ * OR N < aMaxLength && hyphen break at N && GetAdvanceWidth(aStart, N) + GetHyphenWidth() <= aWidth
+ * OR N == aMaxLength && GetAdvanceWidth(aStart, N) <= aWidth
+ * where GetAdvanceWidth assumes the effect of
+ * SetLineBreaks(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(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 aTrimWhitespace if non-null, then we allow a trailing run of
+ * spaces to be trimmed; the width of the space(s) will not be included in
+ * the measured string width for comparison with the limit aWidth, and
+ * trimmed spaces will not be included in returned metrics. The width
+ * of the trimmed spaces will be returned in aTrimWhitespace.
+ * Trimmed spaces are still counted in the "characters fit" result.
+ * @param aMetrics if non-null, 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 aDrawTargetForTightBoundingbox a reference DrawTarget to get the
+ * tight bounding box, if requested
+ * @param aUsedHyphenation if non-null, records if we selected a hyphenation break
+ * @param aLastBreak if non-null and result is aMaxLength, we set this to
+ * the maximal N such that
+ * N < aMaxLength && line break at N && GetAdvanceWidth(aStart, N) <= aWidth
+ * OR N < aMaxLength && hyphen break at N && GetAdvanceWidth(aStart, N) + GetHyphenWidth() <= aWidth
+ * or UINT32_MAX if no such N exists, where GetAdvanceWidth assumes
+ * the effect of
+ * SetLineBreaks(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,
+ PropertyProvider *aProvider,
+ SuppressBreak aSuppressBreak,
+ gfxFloat *aTrimWhitespace,
+ bool aHangWhitespace,
+ Metrics *aMetrics,
+ gfxFont::BoundingBoxType aBoundingBoxType,
+ DrawTarget* aDrawTargetForTightBoundingBox,
+ bool *aUsedHyphenation,
+ uint32_t *aLastBreak,
+ bool aCanWordWrap,
+ gfxBreakPriority *aBreakPriority);
+
+ // Utility getters
+
+ void *GetUserData() const { return mUserData; }
+ void SetUserData(void *aUserData) { mUserData = aUserData; }
+
+ void SetFlagBits(uint32_t aFlags) {
+ NS_ASSERTION(!(aFlags & ~gfxTextRunFactory::SETTABLE_FLAGS),
+ "Only user flags should be mutable");
+ mFlags |= aFlags;
+ }
+ void ClearFlagBits(uint32_t aFlags) {
+ NS_ASSERTION(!(aFlags & ~gfxTextRunFactory::SETTABLE_FLAGS),
+ "Only user flags should be mutable");
+ mFlags &= ~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,
+ uint32_t aFlags);
+
+ // The text is divided into GlyphRuns as necessary
+ struct GlyphRun {
+ RefPtr<gfxFont> mFont; // never null
+ uint32_t mCharacterOffset; // into original UTF16 string
+ uint8_t mMatchType;
+ uint16_t mOrientation; // gfxTextRunFactory::TEXT_ORIENT_* value
+ };
+
+ class GlyphRunIterator {
+ public:
+ GlyphRunIterator(const gfxTextRun *aTextRun, Range aRange)
+ : mTextRun(aTextRun)
+ , mStartOffset(aRange.start)
+ , mEndOffset(aRange.end) {
+ mNextIndex = mTextRun->FindFirstGlyphRunContaining(aRange.start);
+ }
+ bool NextRun();
+ const GlyphRun *GetGlyphRun() const { return mGlyphRun; }
+ uint32_t GetStringStart() const { return mStringStart; }
+ uint32_t GetStringEnd() const { return mStringEnd; }
+ private:
+ const gfxTextRun *mTextRun;
+ MOZ_INIT_OUTSIDE_CTOR const GlyphRun *mGlyphRun;
+ MOZ_INIT_OUTSIDE_CTOR uint32_t mStringStart;
+ MOZ_INIT_OUTSIDE_CTOR uint32_t mStringEnd;
+ uint32_t mNextIndex;
+ uint32_t mStartOffset;
+ uint32_t mEndOffset;
+ };
+
+ 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;
+ }
+ };
+
+ friend class GlyphRunIterator;
+ friend class FontSelector;
+
+ // 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.
+ */
+ nsresult AddGlyphRun(gfxFont *aFont, uint8_t aMatchType,
+ uint32_t aStartCharIndex, bool aForceNewRun,
+ uint16_t aOrientation);
+ void ResetGlyphRuns() { mGlyphRuns.Clear(); }
+ void SortGlyphRuns();
+ 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, uint16_t 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, uint16_t 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();
+ }
+
+ /**
+ * 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);
+
+ uint32_t CountMissingGlyphs() const;
+ const GlyphRun* GetGlyphRuns(uint32_t* aNumGlyphRuns) const {
+ *aNumGlyphRuns = mGlyphRuns.Length();
+ return mGlyphRuns.Elements();
+ }
+ // Returns the index of the GlyphRun containing the given offset.
+ // Returns mGlyphRuns.Length() when aOffset is mCharacterCount.
+ uint32_t 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;
+
+ // Get the size, if it hasn't already been gotten, marking as it goes.
+ size_t MaybeSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
+ if (mFlags & gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED) {
+ return 0;
+ }
+ mFlags |= gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED;
+ return SizeOfIncludingThis(aMallocSizeOf);
+ }
+ void ResetSizeOfAccountingFlags() {
+ mFlags &= ~gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED;
+ }
+
+ // 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 {
+ 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
+ void Dump(FILE* aOutput);
+#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, uint32_t aFlags);
+
+ /**
+ * 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, 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,
+ PropertyProvider *aProvider) const;
+ gfxFloat ComputePartialLigatureWidth(Range aPartRange,
+ PropertyProvider *aProvider) const;
+ void DrawPartialLigature(gfxFont *aFont, Range aRange,
+ gfxPoint *aPt, PropertyProvider *aProvider,
+ TextRunDrawParams& aParams,
+ uint16_t 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.
+ void ShrinkToLigatureBoundaries(Range* aRange) const;
+ // result in appunits
+ gfxFloat GetPartialLigatureWidth(Range aRange,
+ PropertyProvider *aProvider) const;
+ void AccumulatePartialLigatureMetrics(gfxFont *aFont, Range aRange,
+ gfxFont::BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget,
+ PropertyProvider *aProvider,
+ uint16_t aOrientation,
+ Metrics *aMetrics) const;
+
+ // **** measurement helper ****
+ void AccumulateMetricsForRun(gfxFont *aFont, Range aRange,
+ gfxFont::BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget,
+ PropertyProvider *aProvider,
+ Range aSpacingRange,
+ uint16_t aOrientation,
+ Metrics *aMetrics) const;
+
+ // **** drawing helper ****
+ void DrawGlyphs(gfxFont *aFont, Range aRange, gfxPoint *aPt,
+ PropertyProvider *aProvider, Range aSpacingRange,
+ TextRunDrawParams& aParams, uint16_t aOrientation) const;
+
+ // XXX this should be changed to a GlyphRun plus a maybe-null GlyphRun*,
+ // for smaller size especially in the super-common one-glyphrun case
+ AutoTArray<GlyphRun,1> mGlyphRuns;
+
+ void *mUserData;
+ gfxFontGroup *mFontGroup; // addrefed on creation, but our reference
+ // may be released by ReleaseFontGroup()
+ gfxSkipChars mSkipChars;
+
+ bool mSkipDrawing; // true if the font group we used had a user font
+ // download that's in progress, so we should hide text
+ // until the download completes (or timeout fires)
+ bool mReleasedFontGroup; // we already called NS_RELEASE on
+ // mFontGroup, so don't do it again
+
+ // shaping state for handling variant fallback features
+ // such as subscript/superscript variant glyphs
+ ShapingState mShapingState;
+};
+
+class gfxFontGroup : public gfxTextRunFactory {
+public:
+ typedef mozilla::unicode::Script Script;
+
+ static void Shutdown(); // platform must call this to release the languageAtomService
+
+ gfxFontGroup(const mozilla::FontFamilyList& aFontFamilyList,
+ const gfxFontStyle* aStyle,
+ gfxTextPerfMetrics* aTextPerf,
+ gfxUserFontSet* aUserFontSet,
+ gfxFloat aDevToCssSize);
+
+ virtual ~gfxFontGroup();
+
+ // Returns first valid font in the fontlist or default font.
+ // Initiates userfont loads if userfont not loaded
+ virtual gfxFont* GetFirstValidFont(uint32_t aCh = 0x20);
+
+ // 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.
+ gfxFont *GetFirstMathFont();
+
+ const gfxFontStyle *GetStyle() const { return &mStyle; }
+
+ virtual gfxFontGroup *Copy(const gfxFontStyle *aStyle);
+
+ /**
+ * 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.
+ */
+ virtual already_AddRefed<gfxTextRun>
+ MakeTextRun(const char16_t *aString, uint32_t aLength,
+ const Parameters *aParams, uint32_t aFlags,
+ gfxMissingFontRecorder *aMFR);
+ /**
+ * 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.
+ */
+ virtual already_AddRefed<gfxTextRun>
+ MakeTextRun(const uint8_t *aString, uint32_t aLength,
+ const Parameters *aParams, uint32_t aFlags,
+ 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,
+ uint32_t aFlags,
+ gfxMissingFontRecorder *aMFR)
+ {
+ gfxTextRunFactory::Parameters params = {
+ aRefDrawTarget, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevUnit
+ };
+ return MakeTextRun(aString, aLength, &params, aFlags, aMFR);
+ }
+
+ /**
+ * Get the (possibly-cached) width of the hyphen character.
+ * The aCtx and aAppUnitsPerDevUnit parameters will be used only if
+ * needed to initialize the cached hyphen width; otherwise they are
+ * ignored.
+ */
+ gfxFloat GetHyphenWidth(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, 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.
+ enum { UNDERLINE_OFFSET_NOT_SET = INT16_MAX };
+ virtual gfxFloat GetUnderlineOffset();
+
+ virtual already_AddRefed<gfxFont>
+ FindFontForChar(uint32_t ch, uint32_t prevCh, uint32_t aNextCh,
+ Script aRunScript, gfxFont *aPrevMatchedFont,
+ uint8_t *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() { 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.
+ virtual 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, uint32_t aFlags,
+ LazyReferenceDrawTargetGetter& aRefDrawTargetGetter);
+
+protected:
+ // search through pref fonts for a character, return nullptr if no matching pref font
+ already_AddRefed<gfxFont> WhichPrefFontSupportsChar(uint32_t aCh);
+
+ already_AddRefed<gfxFont>
+ WhichSystemFontSupportsChar(uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript);
+
+ template<typename T>
+ void ComputeRanges(nsTArray<gfxTextRange>& mRanges,
+ const T *aString, uint32_t aLength,
+ Script aRunScript, uint16_t aOrientation);
+
+ class FamilyFace {
+ public:
+ FamilyFace() : mFamily(nullptr), mFontEntry(nullptr),
+ mNeedsBold(false), mFontCreated(false),
+ mLoading(false), mInvalid(false),
+ mCheckForFallbackFaces(false)
+ { }
+
+ FamilyFace(gfxFontFamily* aFamily, gfxFont* aFont)
+ : mFamily(aFamily), mNeedsBold(false), mFontCreated(true),
+ mLoading(false), mInvalid(false), mCheckForFallbackFaces(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");
+ mFont = aFont;
+ NS_ADDREF(aFont);
+ }
+
+ FamilyFace(gfxFontFamily* aFamily, gfxFontEntry* aFontEntry,
+ bool aNeedsBold)
+ : mFamily(aFamily), mNeedsBold(aNeedsBold), mFontCreated(false),
+ mLoading(false), mInvalid(false), mCheckForFallbackFaces(false)
+ {
+ 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");
+ mFontEntry = aFontEntry;
+ NS_ADDREF(aFontEntry);
+ }
+
+ FamilyFace(const FamilyFace& aOtherFamilyFace)
+ : mFamily(aOtherFamilyFace.mFamily),
+ mNeedsBold(aOtherFamilyFace.mNeedsBold),
+ mFontCreated(aOtherFamilyFace.mFontCreated),
+ mLoading(aOtherFamilyFace.mLoading),
+ mInvalid(aOtherFamilyFace.mInvalid),
+ mCheckForFallbackFaces(aOtherFamilyFace.mCheckForFallbackFaces)
+ {
+ if (mFontCreated) {
+ mFont = aOtherFamilyFace.mFont;
+ NS_ADDREF(mFont);
+ } else {
+ mFontEntry = aOtherFamilyFace.mFontEntry;
+ NS_IF_ADDREF(mFontEntry);
+ }
+ }
+
+ ~FamilyFace()
+ {
+ if (mFontCreated) {
+ NS_RELEASE(mFont);
+ } else {
+ NS_IF_RELEASE(mFontEntry);
+ }
+ }
+
+ FamilyFace& operator=(const FamilyFace& aOther)
+ {
+ if (mFontCreated) {
+ NS_RELEASE(mFont);
+ } else {
+ NS_IF_RELEASE(mFontEntry);
+ }
+
+ mFamily = aOther.mFamily;
+ mNeedsBold = aOther.mNeedsBold;
+ mFontCreated = aOther.mFontCreated;
+ mLoading = aOther.mLoading;
+ mInvalid = aOther.mInvalid;
+
+ if (mFontCreated) {
+ mFont = aOther.mFont;
+ NS_ADDREF(mFont);
+ } else {
+ mFontEntry = aOther.mFontEntry;
+ NS_IF_ADDREF(mFontEntry);
+ }
+
+ return *this;
+ }
+
+ gfxFontFamily* Family() const { return mFamily.get(); }
+ gfxFont* Font() const {
+ return mFontCreated ? mFont : nullptr;
+ }
+
+ gfxFontEntry* FontEntry() const {
+ return mFontCreated ? mFont->GetFontEntry() : mFontEntry;
+ }
+
+ bool NeedsBold() const { return mNeedsBold; }
+ bool IsUserFontContainer() const {
+ return FontEntry()->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; }
+
+ void SetFont(gfxFont* aFont)
+ {
+ NS_ASSERTION(aFont, "font pointer must not be null");
+ NS_ADDREF(aFont);
+ if (mFontCreated) {
+ NS_RELEASE(mFont);
+ } else {
+ NS_IF_RELEASE(mFontEntry);
+ }
+ mFont = aFont;
+ mFontCreated = true;
+ mLoading = false;
+ }
+
+ bool EqualsUserFont(const gfxUserFontEntry* aUserFont) const;
+
+ private:
+ RefPtr<gfxFontFamily> mFamily;
+ // either a font or a font entry exists
+ union {
+ gfxFont* mFont;
+ gfxFontEntry* mFontEntry;
+ };
+ bool mNeedsBold : 1;
+ bool mFontCreated : 1;
+ bool mLoading : 1;
+ bool mInvalid : 1;
+ bool mCheckForFallbackFaces : 1;
+ };
+
+ // List of font families, either named or generic.
+ // Generic names map to system pref fonts based on language.
+ mozilla::FontFamilyList 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;
+
+ 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
+ RefPtr<gfxFontFamily> 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)
+
+ // xxx - gfxPangoFontGroup skips UpdateUserFonts
+ bool mSkipUpdateUserFonts;
+
+ /**
+ * 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, uint32_t aFlags);
+
+ already_AddRefed<gfxTextRun>
+ MakeSpaceTextRun(const Parameters *aParams, uint32_t aFlags);
+
+ already_AddRefed<gfxTextRun>
+ MakeBlankTextRun(uint32_t aLength, const Parameters *aParams,
+ uint32_t aFlags);
+
+ // Initialize the list of fonts
+ void BuildFontList();
+
+ // Get the font at index i within the fontlist.
+ // Will initiate userfont load if not already loaded.
+ // May return null if userfont not loaded or if font invalid
+ virtual gfxFont* GetFontAt(int32_t i, uint32_t aCh = 0x20);
+
+ // Whether there's a font loading for a given family in the fontlist
+ // for a given character
+ bool FontLoadingForFamily(gfxFontFamily* aFamily, uint32_t aCh) const;
+
+ // will always return a font or force a shutdown
+ 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(gfxFontFamily* aFamily, uint32_t aCh,
+ Script aRunScript);
+
+ // helper methods for looking up fonts
+
+ // lookup and add a font with a given name (i.e. *not* a generic!)
+ void AddPlatformFont(const nsAString& aName,
+ nsTArray<gfxFontFamily*>& aFamilyList);
+
+ // do style selection and add entries to list
+ void AddFamilyToFontList(gfxFontFamily* aFamily);
+
+ static nsILanguageAtomService* gLangService;
+};
+
+// 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::unicode::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::unicode::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 000000000..976da1fef
--- /dev/null
+++ b/gfx/thebes/gfxTypes.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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>
+
+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
+};
+
+#endif /* GFX_TYPES_H */
diff --git a/gfx/thebes/gfxUserFontSet.cpp b/gfx/thebes/gfxUserFontSet.cpp
new file mode 100644
index 000000000..23c26d9fe
--- /dev/null
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -0,0 +1,1439 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "nsContentPolicyUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsNetUtil.h"
+#include "nsIJARChannel.h"
+#include "nsIProtocolHandler.h"
+#include "nsIPrincipal.h"
+#include "nsIZipReader.h"
+#include "gfxFontConstants.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/gfx/2D.h"
+#include "gfxPlatformFontList.h"
+
+#include "opentype-sanitiser.h"
+#include "ots-memory-stream.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 uint64_t sFontSetGeneration = 0;
+
+// 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.
+class ExpandingMemoryStream : public ots::OTSStream {
+public:
+ ExpandingMemoryStream(size_t initial, size_t limit)
+ : mLength(initial), mLimit(limit), mOff(0) {
+ mPtr = moz_xmalloc(mLength);
+ }
+
+ ~ExpandingMemoryStream() {
+ free(mPtr);
+ }
+
+ // 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.
+ void* forget() {
+ void* p = moz_xrealloc(mPtr, mOff);
+ mPtr = nullptr;
+ return p;
+ }
+
+ bool WriteRaw(const void* data, size_t length) {
+ 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 = moz_xrealloc(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) {
+ if (position < 0) {
+ return false;
+ }
+ if (static_cast<size_t>(position) > mLength) {
+ return false;
+ }
+ mOff = position;
+ return true;
+ }
+
+ off_t Tell() const {
+ return mOff;
+ }
+
+private:
+ void* mPtr;
+ size_t mLength;
+ const size_t mLimit;
+ off_t mOff;
+};
+
+gfxUserFontEntry::gfxUserFontEntry(gfxUserFontSet* aFontSet,
+ const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
+ uint32_t aWeight,
+ int32_t aStretch,
+ uint8_t aStyle,
+ const nsTArray<gfxFontFeature>& aFeatureSettings,
+ uint32_t aLanguageOverride,
+ gfxSparseBitSet* aUnicodeRanges,
+ uint8_t aFontDisplay)
+ : gfxFontEntry(NS_LITERAL_STRING("userfont")),
+ mUserFontLoadState(STATUS_NOT_LOADED),
+ mFontDataLoadingState(NOT_LOADING),
+ mUnsupportedFormat(false),
+ mFontDisplay(aFontDisplay),
+ mLoader(nullptr),
+ mFontSet(aFontSet)
+{
+ MOZ_ASSERT(aWeight != 0,
+ "aWeight must not be 0; use NS_FONT_WEIGHT_NORMAL instead");
+ mIsUserFontContainer = true;
+ mSrcList = aFontFaceSrcList;
+ mSrcIndex = 0;
+ mWeight = aWeight;
+ mStretch = aStretch;
+ mStyle = aStyle;
+ mFeatureSettings.AppendElements(aFeatureSettings);
+ mLanguageOverride = aLanguageOverride;
+
+ if (aUnicodeRanges) {
+ mCharacterMap = new gfxCharacterMap(*aUnicodeRanges);
+ }
+}
+
+gfxUserFontEntry::~gfxUserFontEntry()
+{
+}
+
+bool
+gfxUserFontEntry::Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
+ uint32_t aWeight,
+ int32_t aStretch,
+ uint8_t aStyle,
+ const nsTArray<gfxFontFeature>& aFeatureSettings,
+ uint32_t aLanguageOverride,
+ gfxSparseBitSet* aUnicodeRanges,
+ uint8_t aFontDisplay)
+{
+ return mWeight == aWeight &&
+ mStretch == aStretch &&
+ mStyle == aStyle &&
+ mFeatureSettings == aFeatureSettings &&
+ mLanguageOverride == aLanguageOverride &&
+ mSrcList == aFontFaceSrcList &&
+ mFontDisplay == aFontDisplay &&
+ ((!aUnicodeRanges && !mCharacterMap) ||
+ (aUnicodeRanges && mCharacterMap && mCharacterMap->Equals(aUnicodeRanges)));
+}
+
+gfxFont*
+gfxUserFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle, bool aNeedsBold)
+{
+ NS_NOTREACHED("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 gfxOTSContext : public ots::OTSContext {
+public:
+ explicit gfxOTSContext(gfxUserFontEntry* aUserFontEntry)
+ : mUserFontEntry(aUserFontEntry) {}
+
+ virtual ots::TableAction GetTableAction(uint32_t aTag) override {
+ // Preserve Graphite, color glyph and SVG tables
+ if (
+#ifdef RELEASE_OR_BETA // For Beta/Release, also allow OT Layout tables through
+ // unchecked, and rely on harfbuzz to handle them safely.
+ aTag == TRUETYPE_TAG('G', 'D', 'E', 'F') ||
+ aTag == TRUETYPE_TAG('G', 'P', 'O', 'S') ||
+ aTag == TRUETYPE_TAG('G', 'S', 'U', 'B') ||
+#endif
+ aTag == TRUETYPE_TAG('S', 'i', 'l', 'f') ||
+ aTag == TRUETYPE_TAG('S', 'i', 'l', 'l') ||
+ aTag == TRUETYPE_TAG('G', 'l', 'o', 'c') ||
+ aTag == TRUETYPE_TAG('G', 'l', 'a', 't') ||
+ aTag == TRUETYPE_TAG('F', 'e', 'a', 't') ||
+ aTag == TRUETYPE_TAG('S', 'V', 'G', ' ') ||
+ aTag == TRUETYPE_TAG('C', 'O', 'L', 'R') ||
+ aTag == TRUETYPE_TAG('C', 'P', 'A', 'L')) {
+ return ots::TABLE_ACTION_PASSTHRU;
+ }
+ return ots::TABLE_ACTION_DEFAULT;
+ }
+
+ virtual void Message(int level, const char* format,
+ ...) MSGFUNC_FMT_ATTR override {
+ va_list va;
+ va_start(va, format);
+
+ nsCString msg;
+ msg.AppendPrintf(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.Contains(msg)) {
+ return;
+ }
+ mWarningsIssued.PutEntry(msg);
+ }
+
+ mUserFontEntry->mFontSet->LogMessage(mUserFontEntry, msg.get());
+ }
+
+private:
+ gfxUserFontEntry* mUserFontEntry;
+ nsTHashtable<nsCStringHashKey> mWarningsIssued;
+};
+
+// 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& aSaneLength,
+ gfxUserFontType aFontType)
+{
+ if (aFontType == GFX_USERFONT_UNKNOWN) {
+ aSaneLength = 0;
+ return nullptr;
+ }
+
+ uint32_t lengthHint = aLength;
+ if (aFontType == GFX_USERFONT_WOFF) {
+ lengthHint *= 2;
+ } else if (aFontType == GFX_USERFONT_WOFF2) {
+ lengthHint *= 3;
+ }
+
+ // limit output/expansion to 256MB
+ ExpandingMemoryStream output(lengthHint, 1024 * 1024 * 256);
+
+ gfxOTSContext otsContext(this);
+ if (!otsContext.Process(&output, aData, aLength)) {
+ // Failed to decode/sanitize the font, so discard it.
+ aSaneLength = 0;
+ return nullptr;
+ }
+
+ aSaneLength = output.Tell();
+ return static_cast<const uint8_t*>(output.forget());
+}
+
+void
+gfxUserFontEntry::StoreUserFontData(gfxFontEntry* aFontEntry,
+ bool aPrivate,
+ const nsAString& 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 = mSrcIndex;
+ const gfxFontFaceSrc& src = mSrcList[mSrcIndex];
+ 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->mFormat = src.mFormatFlags;
+ userFontData->mRealName = aOriginalName;
+ if (aMetadata) {
+ userFontData->mMetadata.SwapElements(*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.
+}
+
+void
+gfxUserFontEntry::GetFamilyNameAndURIForLogging(nsACString& aFamilyName,
+ nsACString& aURI)
+{
+ aFamilyName.Assign(NS_ConvertUTF16toUTF8(mFamilyName));
+
+ aURI.Truncate();
+ if (mSrcIndex == mSrcList.Length()) {
+ aURI.AppendLiteral("(end of source list)");
+ } else {
+ if (mSrcList[mSrcIndex].mURI) {
+ mSrcList[mSrcIndex].mURI->GetSpec(aURI);
+ } 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()
+{
+ uint32_t numSrc = mSrcList.Length();
+
+ NS_ASSERTION(mSrcIndex < numSrc,
+ "already at the end of the src list for user font");
+ NS_ASSERTION((mUserFontLoadState == STATUS_NOT_LOADED ||
+ 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
+ mSrcIndex++;
+ }
+
+ // load each src entry in turn, until a local face is found
+ // or a download begins successfully
+ while (mSrcIndex < numSrc) {
+ gfxFontFaceSrc& currSrc = mSrcList[mSrcIndex];
+
+ // 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 = pfl && pfl->IsFontFamilyWhitelistActive() ?
+ nullptr :
+ gfxPlatform::GetPlatform()->LookupLocalFont(currSrc.mLocalName,
+ mWeight,
+ mStretch,
+ mStyle);
+ nsTArray<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",
+ mFontSet, mSrcIndex,
+ NS_ConvertUTF16toUTF8(currSrc.mLocalName).get(),
+ NS_ConvertUTF16toUTF8(mFamilyName).get(),
+ uint32_t(mFontSet->mGeneration)));
+ fe->mFeatureSettings.AppendElements(mFeatureSettings);
+ fe->mLanguageOverride = mLanguageOverride;
+ fe->mFamilyName = mFamilyName;
+ // 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, false, nsString(), 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",
+ mFontSet, mSrcIndex,
+ NS_ConvertUTF16toUTF8(currSrc.mLocalName).get(),
+ NS_ConvertUTF16toUTF8(mFamilyName).get()));
+ }
+ }
+
+ // src url ==> start the load process
+ else if (currSrc.mSourceType == gfxFontFaceSrc::eSourceType_URL) {
+ if (gfxPlatform::GetPlatform()->IsFontFormatSupported(currSrc.mURI,
+ currSrc.mFormatFlags)) {
+
+ nsIPrincipal* principal = nullptr;
+ bool bypassCache;
+ nsresult rv = mFontSet->CheckFontLoad(&currSrc, &principal,
+ &bypassCache);
+
+ if (NS_SUCCEEDED(rv) && principal != nullptr) {
+ if (!bypassCache) {
+ // see if we have an existing entry for this source
+ gfxFontEntry* fe = gfxUserFontSet::
+ UserFontCache::GetFont(currSrc.mURI,
+ principal,
+ this,
+ mFontSet->GetPrivateBrowsing());
+ if (fe) {
+ mPlatformFontEntry = fe;
+ SetLoadState(STATUS_LOADED);
+ if (LOG_ENABLED()) {
+ LOG(("userfonts (%p) [src %d] "
+ "loaded uri from cache: (%s) for (%s)\n",
+ mFontSet, mSrcIndex,
+ currSrc.mURI->GetSpecOrDefault().get(),
+ NS_ConvertUTF16toUTF8(mFamilyName).get()));
+ }
+ return;
+ }
+ }
+
+ // record the principal returned by CheckFontLoad,
+ // for use when creating a channel
+ // and when caching the loaded entry
+ mPrincipal = principal;
+
+ bool loadDoesntSpin = false;
+ rv = NS_URIChainHasFlags(currSrc.mURI,
+ nsIProtocolHandler::URI_SYNC_LOAD_IS_OK,
+ &loadDoesntSpin);
+
+ if (NS_SUCCEEDED(rv) && loadDoesntSpin) {
+ uint8_t* buffer = nullptr;
+ uint32_t bufferLength = 0;
+
+ // sync load font immediately
+ rv = mFontSet->SyncLoadFontData(this, &currSrc, buffer,
+ bufferLength);
+
+ if (NS_SUCCEEDED(rv) &&
+ LoadPlatformFont(buffer, bufferLength)) {
+ SetLoadState(STATUS_LOADED);
+ Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE,
+ currSrc.mSourceType + 1);
+ return;
+ } else {
+ mFontSet->LogMessage(this,
+ "font load failed",
+ nsIScriptError::errorFlag,
+ rv);
+ }
+
+ } else {
+ // otherwise load font async
+ rv = mFontSet->StartLoad(this, &currSrc);
+ bool loadOK = NS_SUCCEEDED(rv);
+
+ if (loadOK) {
+ if (LOG_ENABLED()) {
+ LOG(("userfonts (%p) [src %d] loading uri: (%s) for (%s)\n",
+ mFontSet, mSrcIndex,
+ currSrc.mURI->GetSpecOrDefault().get(),
+ NS_ConvertUTF16toUTF8(mFamilyName).get()));
+ }
+ return;
+ } else {
+ mFontSet->LogMessage(this,
+ "download failed",
+ nsIScriptError::errorFlag,
+ rv);
+ }
+ }
+ } else {
+ mFontSet->LogMessage(this, "download not allowed",
+ 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 && LoadPlatformFont(buffer, bufferLength)) {
+ // LoadPlatformFont 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 {
+ mFontSet->LogMessage(this,
+ "font load failed",
+ nsIScriptError::errorFlag);
+ }
+ }
+
+ mSrcIndex++;
+ }
+
+ if (mUnsupportedFormat) {
+ mFontSet->LogMessage(this, "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",
+ mFontSet, NS_ConvertUTF16toUTF8(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::LoadPlatformFont(const uint8_t* aFontData, uint32_t& aLength)
+{
+ NS_ASSERTION((mUserFontLoadState == STATUS_NOT_LOADED ||
+ mUserFontLoadState == STATUS_LOADING) &&
+ mFontDataLoadingState < LOADING_FAILED,
+ "attempting to load a font that has either completed or failed");
+
+ gfxFontEntry* fe = nullptr;
+
+ gfxUserFontType fontType =
+ gfxFontUtils::DetermineFontDataType(aFontData, aLength);
+ Telemetry::Accumulate(Telemetry::WEBFONT_FONTTYPE, uint32_t(fontType));
+
+ // Unwrap/decompress/sanitize or otherwise munge the downloaded data
+ // to make a usable sfnt structure.
+
+ // 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 nsIDOMFontFace API.
+ nsAutoString originalFullName;
+
+ // Call the OTS sanitizer; this will also decode WOFF to sfnt
+ // if necessary. The original data in aFontData is left unchanged.
+ uint32_t saneLen;
+ uint32_t fontCompressionRatio = 0;
+ size_t computedSize = 0;
+ const uint8_t* saneData =
+ SanitizeOpenTypeData(aFontData, aLength, saneLen, fontType);
+ if (!saneData) {
+ mFontSet->LogMessage(this, "rejected by sanitizer");
+ } else {
+ // Check whether saneData 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(saneData, saneLen) !=
+ GFX_USERFONT_OPENTYPE) {
+ mFontSet->LogMessage(this, "not a supported OpenType format");
+ free((void*)saneData);
+ saneData = nullptr;
+ }
+ }
+ if (saneData) {
+ if (saneLen) {
+ fontCompressionRatio = uint32_t(100.0 * aLength / saneLen + 0.5);
+ if (fontType == GFX_USERFONT_WOFF ||
+ fontType == GFX_USERFONT_WOFF2) {
+ Telemetry::Accumulate(fontType == 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(saneData, saneLen,
+ 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(saneData);
+
+ // Here ownership of saneData is passed to the platform,
+ // which will delete it when no longer required
+ fe = gfxPlatform::GetPlatform()->MakePlatformFont(mName,
+ mWeight,
+ mStretch,
+ mStyle,
+ saneData,
+ saneLen);
+ if (!fe) {
+ mFontSet->LogMessage(this, "not usable by platform");
+ }
+ }
+
+ if (fe) {
+ fe->mComputedSizeOfUserFont = computedSize;
+
+ // Save a copy of the metadata block (if present) for nsIDOMFontFace
+ // 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 (fontType == GFX_USERFONT_WOFF) {
+ CopyWOFFMetadata<WOFFHeader>(aFontData, aLength,
+ &metadata, &metaOrigLen);
+ compression = gfxUserFontData::kZlibCompression;
+ } else if (fontType == GFX_USERFONT_WOFF2) {
+ CopyWOFFMetadata<WOFF2Header>(aFontData, aLength,
+ &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->mLanguageOverride = mLanguageOverride;
+ fe->mFamilyName = mFamilyName;
+ StoreUserFontData(fe, mFontSet->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",
+ mFontSet, mSrcIndex,
+ mSrcList[mSrcIndex].mURI->GetSpecOrDefault().get(),
+ NS_ConvertUTF16toUTF8(mFamilyName).get(),
+ this, uint32_t(mFontSet->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",
+ mFontSet, mSrcIndex,
+ mSrcList[mSrcIndex].mURI->GetSpecOrDefault().get(),
+ NS_ConvertUTF16toUTF8(mFamilyName).get()));
+ }
+ }
+
+ // The downloaded data can now be discarded; the font entry is using the
+ // sanitized copy
+ free((void*)aFontData);
+
+ return fe != nullptr;
+}
+
+void
+gfxUserFontEntry::Load()
+{
+ if (mUserFontLoadState == STATUS_NOT_LOADED) {
+ LoadNextSrc();
+ }
+}
+
+void
+gfxUserFontEntry::IncrementGeneration()
+{
+ nsTArray<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().
+bool
+gfxUserFontEntry::FontDataDownloadComplete(const uint8_t* aFontData,
+ uint32_t aLength,
+ nsresult aDownloadStatus)
+{
+ // 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) {
+ bool loaded = LoadPlatformFont(aFontData, aLength);
+ aFontData = nullptr;
+
+ if (loaded) {
+ IncrementGeneration();
+ return true;
+ }
+
+ } else {
+ // download failed
+ mFontSet->LogMessage(this,
+ (mFontDataLoadingState != LOADING_TIMED_OUT ?
+ "download failed" : "download timed out"),
+ nsIScriptError::errorFlag,
+ aDownloadStatus);
+ }
+
+ if (aFontData) {
+ free((void*)aFontData);
+ }
+
+ // error occurred, load next src if load not yet timed out
+ if (mFontDataLoadingState != LOADING_TIMED_OUT) {
+ 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();
+ return true;
+}
+
+void
+gfxUserFontEntry::GetUserFontSets(nsTArray<gfxUserFontSet*>& aResult)
+{
+ aResult.Clear();
+ aResult.AppendElement(mFontSet);
+}
+
+gfxUserFontSet::gfxUserFontSet()
+ : mFontFamilies(4),
+ mLocalRulesUsed(false),
+ mRebuildLocalRules(false),
+ mDownloadCount(0),
+ mDownloadSize(0)
+{
+ IncrementGeneration(true);
+ gfxPlatformFontList* fp = gfxPlatformFontList::PlatformFontList();
+ if (fp) {
+ fp->AddUserFontSet(this);
+ }
+}
+
+gfxUserFontSet::~gfxUserFontSet()
+{
+ gfxPlatformFontList* fp = gfxPlatformFontList::PlatformFontList();
+ if (fp) {
+ fp->RemoveUserFontSet(this);
+ }
+}
+
+already_AddRefed<gfxUserFontEntry>
+gfxUserFontSet::FindOrCreateUserFontEntry(
+ const nsAString& aFamilyName,
+ const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
+ uint32_t aWeight,
+ int32_t aStretch,
+ uint8_t aStyle,
+ const nsTArray<gfxFontFeature>& aFeatureSettings,
+ uint32_t aLanguageOverride,
+ gfxSparseBitSet* aUnicodeRanges,
+ uint8_t aFontDisplay)
+{
+ 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.
+ gfxUserFontFamily* family = LookupFamily(aFamilyName);
+ if (family) {
+ entry = FindExistingUserFontEntry(family, aFontFaceSrcList, aWeight,
+ aStretch, aStyle,
+ aFeatureSettings, aLanguageOverride,
+ aUnicodeRanges, aFontDisplay);
+ }
+
+ if (!entry) {
+ entry = CreateUserFontEntry(aFontFaceSrcList, aWeight, aStretch,
+ aStyle, aFeatureSettings,
+ aLanguageOverride, aUnicodeRanges,
+ aFontDisplay);
+ entry->mFamilyName = aFamilyName;
+ }
+
+ return entry.forget();
+}
+
+already_AddRefed<gfxUserFontEntry>
+gfxUserFontSet::CreateUserFontEntry(
+ const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
+ uint32_t aWeight,
+ int32_t aStretch,
+ uint8_t aStyle,
+ const nsTArray<gfxFontFeature>& aFeatureSettings,
+ uint32_t aLanguageOverride,
+ gfxSparseBitSet* aUnicodeRanges,
+ uint8_t aFontDisplay)
+{
+
+ RefPtr<gfxUserFontEntry> userFontEntry =
+ new gfxUserFontEntry(this, aFontFaceSrcList, aWeight,
+ aStretch, aStyle, aFeatureSettings,
+ aLanguageOverride, aUnicodeRanges, aFontDisplay);
+ return userFontEntry.forget();
+}
+
+gfxUserFontEntry*
+gfxUserFontSet::FindExistingUserFontEntry(
+ gfxUserFontFamily* aFamily,
+ const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
+ uint32_t aWeight,
+ int32_t aStretch,
+ uint8_t aStyle,
+ const nsTArray<gfxFontFeature>& aFeatureSettings,
+ uint32_t aLanguageOverride,
+ gfxSparseBitSet* aUnicodeRanges,
+ uint8_t aFontDisplay)
+{
+ MOZ_ASSERT(aWeight != 0,
+ "aWeight must not be 0; use NS_FONT_WEIGHT_NORMAL instead");
+
+ nsTArray<RefPtr<gfxFontEntry>>& fontList = aFamily->GetFontList();
+
+ for (size_t i = 0, count = fontList.Length(); i < count; i++) {
+ if (!fontList[i]->mIsUserFontContainer) {
+ continue;
+ }
+
+ gfxUserFontEntry* existingUserFontEntry =
+ static_cast<gfxUserFontEntry*>(fontList[i].get());
+ if (!existingUserFontEntry->Matches(aFontFaceSrcList,
+ aWeight, aStretch, aStyle,
+ aFeatureSettings, aLanguageOverride,
+ aUnicodeRanges, aFontDisplay)) {
+ continue;
+ }
+
+ return existingUserFontEntry;
+ }
+
+ return nullptr;
+}
+
+void
+gfxUserFontSet::AddUserFontEntry(const nsAString& aFamilyName,
+ gfxUserFontEntry* aUserFontEntry)
+{
+ gfxUserFontFamily* family = GetFamily(aFamilyName);
+ family->AddFontEntry(aUserFontEntry);
+
+ if (LOG_ENABLED()) {
+ LOG(("userfonts (%p) added to \"%s\" (%p) style: %s weight: %d "
+ "stretch: %d display: %d",
+ this, NS_ConvertUTF16toUTF8(aFamilyName).get(), aUserFontEntry,
+ (aUserFontEntry->IsItalic() ? "italic" :
+ (aUserFontEntry->IsOblique() ? "oblique" : "normal")),
+ aUserFontEntry->Weight(), aUserFontEntry->Stretch(),
+ aUserFontEntry->GetFontDisplay()));
+ }
+}
+
+gfxUserFontEntry*
+gfxUserFontSet::FindUserFontEntryAndLoad(gfxFontFamily* aFamily,
+ const gfxFontStyle& aFontStyle,
+ bool& aNeedsBold,
+ bool& aWaitForUserFont)
+{
+ aWaitForUserFont = false;
+ gfxFontEntry* fe = aFamily->FindFontForStyle(aFontStyle, aNeedsBold);
+ NS_ASSERTION(!fe || fe->mIsUserFontContainer,
+ "should only have userfont entries in userfont families");
+ if (!fe) {
+ return nullptr;
+ }
+
+ gfxUserFontEntry* userFontEntry = static_cast<gfxUserFontEntry*>(fe);
+
+ // start the load if it hasn't been loaded
+ userFontEntry->Load();
+ if (userFontEntry->GetPlatformFontEntry()) {
+ return userFontEntry;
+ }
+
+ aWaitForUserFont = userFontEntry->WaitForUserFont();
+ return nullptr;
+}
+
+void
+gfxUserFontSet::IncrementGeneration(bool aIsRebuild)
+{
+ // add one, increment again if zero
+ ++sFontSetGeneration;
+ if (sFontSetGeneration == 0)
+ ++sFontSetGeneration;
+ mGeneration = sFontSetGeneration;
+ if (aIsRebuild) {
+ mRebuildGeneration = mGeneration;
+ }
+}
+
+void
+gfxUserFontSet::RebuildLocalRules()
+{
+ if (mLocalRulesUsed) {
+ mRebuildLocalRules = true;
+ DoRebuildUserFontSet();
+ }
+}
+
+gfxUserFontFamily*
+gfxUserFontSet::LookupFamily(const nsAString& aFamilyName) const
+{
+ nsAutoString key(aFamilyName);
+ ToLowerCase(key);
+
+ return mFontFamilies.GetWeak(key);
+}
+
+bool
+gfxUserFontSet::ContainsUserFontSetFonts(const FontFamilyList& aFontList) const
+{
+ for (const FontFamilyName& name : aFontList.GetFontlist()) {
+ if (name.mType != eFamily_named &&
+ name.mType != eFamily_named_quoted) {
+ continue;
+ }
+ if (LookupFamily(name.mName)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+gfxUserFontFamily*
+gfxUserFontSet::GetFamily(const nsAString& aFamilyName)
+{
+ nsAutoString key(aFamilyName);
+ ToLowerCase(key);
+
+ gfxUserFontFamily* family = mFontFamilies.GetWeak(key);
+ if (!family) {
+ family = new gfxUserFontFamily(aFamilyName);
+ mFontFamilies.Put(key, family);
+ }
+ return family;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// 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()) {
+ if (!i.Get()->IsPersistent()) {
+ 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 {
+ NS_NOTREACHED("unexpected topic");
+ }
+
+ return NS_OK;
+}
+
+static bool
+IgnorePrincipal(nsIURI* aURI)
+{
+ nsresult rv;
+ bool inherits = false;
+ rv = NS_URIChainHasFlags(aURI,
+ nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
+ &inherits);
+ return NS_SUCCEEDED(rv) && inherits;
+}
+
+bool
+gfxUserFontSet::UserFontCache::Entry::KeyEquals(const KeyTypePointer aKey) const
+{
+ const gfxFontEntry* fe = aKey->mFontEntry;
+ // CRC32 checking mode
+ if (mLength || aKey->mLength) {
+ if (aKey->mLength != mLength ||
+ aKey->mCRC32 != mCRC32) {
+ return false;
+ }
+ } else {
+ bool result;
+ if (NS_FAILED(mURI->Equals(aKey->mURI, &result)) || !result) {
+ 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 (NS_FAILED(mPrincipal->Equals(aKey->mPrincipal, &result)) ||
+ !result) {
+ return false;
+ }
+ }
+
+ if (mPrivate != aKey->mPrivate) {
+ return false;
+ }
+ }
+
+ if (mFontEntry->mStyle != fe->mStyle ||
+ mFontEntry->mWeight != fe->mWeight ||
+ mFontEntry->mStretch != fe->mStretch ||
+ mFontEntry->mFeatureSettings != fe->mFeatureSettings ||
+ mFontEntry->mLanguageOverride != fe->mLanguageOverride ||
+ mFontEntry->mFamilyName != fe->mFamilyName) {
+ return false;
+ }
+
+ return true;
+}
+
+void
+gfxUserFontSet::UserFontCache::CacheFont(gfxFontEntry* aFontEntry,
+ EntryPersistence aPersistence)
+{
+ 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());
+ }
+
+ if (data->mLength) {
+ MOZ_ASSERT(aPersistence == kPersistent);
+ MOZ_ASSERT(!data->mPrivate);
+ sUserFonts->PutEntry(Key(data->mCRC32, data->mLength, aFontEntry,
+ data->mPrivate, aPersistence));
+ } else {
+ MOZ_ASSERT(aPersistence == kDiscardable);
+ // 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.
+ nsIPrincipal* principal;
+ if (IgnorePrincipal(data->mURI)) {
+ principal = nullptr;
+ } else {
+ principal = data->mPrincipal;
+ }
+ sUserFonts->PutEntry(Key(data->mURI, principal, aFontEntry,
+ data->mPrivate, aPersistence));
+ }
+
+#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(nsIURI* aSrcURI,
+ nsIPrincipal* aPrincipal,
+ gfxUserFontEntry* aUserFontEntry,
+ bool aPrivate)
+{
+ if (!sUserFonts ||
+ Preferences::GetBool("gfx.downloadable_fonts.disable_cache")) {
+ 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 (!aUserFontEntry->mFontSet->IsFontLoadAllowed(aSrcURI, aPrincipal)) {
+ return nullptr;
+ }
+
+ // Ignore principal when looking up a data: URI.
+ nsIPrincipal* principal;
+ if (IgnorePrincipal(aSrcURI)) {
+ principal = nullptr;
+ } else {
+ principal = aPrincipal;
+ }
+
+ Entry* entry = sUserFonts->GetEntry(Key(aSrcURI, principal, aUserFontEntry,
+ aPrivate));
+ if (entry) {
+ return entry->GetFontEntry();
+ }
+
+ // The channel is never openend; to be conservative we use the most
+ // restrictive security flag: SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS.
+ nsCOMPtr<nsIChannel> chan;
+ if (NS_FAILED(NS_NewChannel(getter_AddRefs(chan),
+ aSrcURI,
+ aPrincipal,
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS,
+ nsIContentPolicy::TYPE_FONT))) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIJARChannel> jarchan = do_QueryInterface(chan);
+ if (!jarchan) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIZipEntry> zipentry;
+ if (NS_FAILED(jarchan->GetZipEntry(getter_AddRefs(zipentry)))) {
+ return nullptr;
+ }
+
+ uint32_t crc32, length;
+ zipentry->GetCRC32(&crc32);
+ zipentry->GetRealSize(&length);
+
+ entry = sUserFonts->GetEntry(Key(crc32, length, aUserFontEntry, aPrivate));
+ if (entry) {
+ return entry->GetFontEntry();
+ }
+
+ return nullptr;
+}
+
+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 {
+ NS_ConvertUTF16toUTF8 familyName(mFontEntry->mFamilyName);
+ path.AppendPrintf("family=%s", familyName.get());
+ if (mURI) {
+ nsCString spec = mURI->GetSpecOrDefault();
+ spec.ReplaceChar('/', '\\');
+ // Some fonts are loaded using horrendously-long data: URIs;
+ // truncate those before reporting them.
+ bool isData;
+ if (NS_SUCCEEDED(mURI->SchemeIs("data", &isData)) && isData &&
+ spec.Length() > 255) {
+ spec.Truncate(252);
+ spec.Append("...");
+ }
+ path.AppendPrintf(", url=%s", spec.get());
+ }
+ if (mPrincipal) {
+ nsCOMPtr<nsIURI> uri;
+ mPrincipal->GetURI(getter_AddRefs(uri));
+ if (uri) {
+ nsCString spec = uri->GetSpecOrDefault();
+ 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(
+ EmptyCString(), path,
+ nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES,
+ mFontEntry->ComputedSizeOfExcludingThis(UserFontsMallocSizeOf),
+ NS_LITERAL_CSTRING("Memory used by @font-face resource."),
+ 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->GetURI(getter_AddRefs(principalURI));
+ if (NS_SUCCEEDED(rv)) {
+ principalURI->GetSpec(principalURISpec);
+ }
+
+ nsCOMPtr<nsIURI> domainURI;
+ mPrincipal->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,
+ nsURIHashKey::HashKey(mURI),
+ NS_ConvertUTF16toUTF8(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 000000000..896c6951e
--- /dev/null
+++ b/gfx/thebes/gfxUserFontSet.h
@@ -0,0 +1,716 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxFont.h"
+#include "gfxFontFamilyList.h"
+#include "nsRefPtrHashtable.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptError.h"
+#include "nsURIHashKey.h"
+#include "mozilla/net/ReferrerPolicy.h"
+#include "gfxFontConstants.h"
+
+class nsFontFaceLoader;
+
+//#define DEBUG_USERFONT_CACHE
+
+class gfxFontFaceBufferSource
+{
+ NS_INLINE_DECL_REFCOUNTING(gfxFontFaceBufferSource)
+public:
+ virtual void TakeBuffer(uint8_t*& aBuffer, uint32_t& aLength) = 0;
+
+protected:
+ virtual ~gfxFontFaceBufferSource() {}
+};
+
+// 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;
+
+ // format hint flags, union of all possible formats
+ // (e.g. TrueType, EOT, SVG, etc.)
+ // see FLAG_FORMAT_* enum values below
+ uint32_t mFormatFlags;
+
+ nsString mLocalName; // full font name if local
+ nsCOMPtr<nsIURI> mURI; // uri if url
+ nsCOMPtr<nsIURI> mReferrer; // referrer url if url
+ mozilla::net::ReferrerPolicy mReferrerPolicy;
+ nsCOMPtr<nsIPrincipal> mOriginPrincipal; // principal if url
+
+ RefPtr<gfxFontFaceBufferSource> mBuffer;
+};
+
+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: {
+ bool equals;
+ return a.mUseOriginPrincipal == b.mUseOriginPrincipal &&
+ a.mFormatFlags == b.mFormatFlags &&
+ NS_SUCCEEDED(a.mURI->Equals(b.mURI, &equals)) && equals &&
+ NS_SUCCEEDED(a.mReferrer->Equals(b.mReferrer, &equals)) &&
+ equals &&
+ a.mReferrerPolicy == b.mReferrerPolicy &&
+ a.mOriginPrincipal->Equals(b.mOriginPrincipal);
+ }
+ 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), mFormat(0), mMetaOrigLen(0),
+ mCRC32(0), mLength(0), mCompression(kUnknownCompression),
+ mPrivate(false), mIsBuffer(false)
+ { }
+ virtual ~gfxUserFontData() { }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ nsTArray<uint8_t> mMetadata; // woff metadata block (compressed), if any
+ nsCOMPtr<nsIURI> mURI; // URI of the source, if it was url()
+ nsCOMPtr<nsIPrincipal> mPrincipal; // principal for the download, if url()
+ nsString mLocalName; // font name used for the source, if local()
+ nsString mRealName; // original fullname from the font resource
+ uint32_t mSrcIndex; // index in the rule's source list
+ uint32_t mFormat; // format hint for the source used, if any
+ uint32_t mMetaOrigLen; // length needed to decompress metadata
+ uint32_t mCRC32; // Checksum
+ uint32_t mLength; // Font length
+ 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 nsAString& aName)
+ : gfxFontFamily(aName) { }
+
+ virtual ~gfxUserFontFamily() { }
+
+ // add the given font entry to the end of the family's list
+ void AddFontEntry(gfxFontEntry* aFontEntry) {
+ // 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
+ nsString thisName = Name();
+ nsString entryName = aFontEntry->mFamilyName;
+ ToLowerCase(thisName);
+ ToLowerCase(entryName);
+ MOZ_ASSERT(thisName.Equals(entryName));
+#endif
+ }
+ ResetCharacterMap();
+ }
+
+ // Remove all font entries from the family
+ void DetachFontEntries() {
+ mAvailableFonts.Clear();
+ }
+};
+
+class gfxUserFontEntry;
+class gfxOTSContext;
+
+class gfxUserFontSet {
+ friend class gfxUserFontEntry;
+ friend class gfxOTSContext;
+
+public:
+
+ NS_INLINE_DECL_REFCOUNTING(gfxUserFontSet)
+
+ gfxUserFontSet();
+
+ enum {
+ // no flags ==> no hint set
+ // unknown ==> unknown format hint set
+ FLAG_FORMAT_UNKNOWN = 1,
+ FLAG_FORMAT_OPENTYPE = 1 << 1,
+ FLAG_FORMAT_TRUETYPE = 1 << 2,
+ FLAG_FORMAT_TRUETYPE_AAT = 1 << 3,
+ FLAG_FORMAT_EOT = 1 << 4,
+ FLAG_FORMAT_SVG = 1 << 5,
+ FLAG_FORMAT_WOFF = 1 << 6,
+ FLAG_FORMAT_WOFF2 = 1 << 7,
+
+ // the common formats that we support everywhere
+ FLAG_FORMATS_COMMON = FLAG_FORMAT_OPENTYPE |
+ FLAG_FORMAT_TRUETYPE |
+ FLAG_FORMAT_WOFF |
+ FLAG_FORMAT_WOFF2,
+
+ // mask of all unused bits, update when adding new formats
+ FLAG_FORMAT_NOT_USED = ~((1 << 8)-1)
+ };
+
+
+ // creates a font face without adding it to a particular family
+ // weight - [100, 900] (multiples of 100)
+ // stretch = [NS_FONT_STRETCH_ULTRA_CONDENSED, NS_FONT_STRETCH_ULTRA_EXPANDED]
+ // italic style = constants in gfxFontConstants.h, e.g. NS_FONT_STYLE_NORMAL
+ // language override = result of calling gfxFontStyle::ParseFontLanguageOverride
+ // TODO: support for unicode ranges not yet implemented
+ virtual already_AddRefed<gfxUserFontEntry> CreateUserFontEntry(
+ const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
+ uint32_t aWeight,
+ int32_t aStretch,
+ uint8_t aStyle,
+ const nsTArray<gfxFontFeature>& aFeatureSettings,
+ uint32_t aLanguageOverride,
+ gfxSparseBitSet* aUnicodeRanges,
+ uint8_t aFontDisplay) = 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(
+ const nsAString& aFamilyName,
+ const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
+ uint32_t aWeight,
+ int32_t aStretch,
+ uint8_t aStyle,
+ const nsTArray<gfxFontFeature>& aFeatureSettings,
+ uint32_t aLanguageOverride,
+ gfxSparseBitSet* aUnicodeRanges,
+ uint8_t aFontDisplay);
+
+ // add in a font face for which we have the gfxUserFontEntry already
+ void AddUserFontEntry(const nsAString& aFamilyName,
+ gfxUserFontEntry* aUserFontEntry);
+
+ // Whether there is a face with this family name
+ bool HasFamily(const nsAString& aFamilyName) const
+ {
+ return LookupFamily(aFamilyName) != nullptr;
+ }
+
+ // Look up and return the gfxUserFontFamily in mFontFamilies with
+ // the given name
+ gfxUserFontFamily* LookupFamily(const nsAString& aName) const;
+
+ // Look up names in a fontlist and return true if any are in the set
+ bool ContainsUserFontSetFonts(const mozilla::FontFamilyList& aFontList) const;
+
+ // Lookup a font entry for a given style, returns null if not loaded.
+ // aFamily must be a family returned by our LookupFamily method.
+ // (only used by gfxPangoFontGroup for now)
+ gfxUserFontEntry* FindUserFontEntryAndLoad(gfxFontFamily* aFamily,
+ const gfxFontStyle& aFontStyle,
+ bool& aNeedsBold,
+ bool& aWaitForUserFont);
+
+ // check whether the given source is allowed to be loaded;
+ // returns the Principal (for use in the key when caching the loaded font),
+ // and whether the load should bypass the cache (force-reload).
+ virtual nsresult CheckFontLoad(const gfxFontFaceSrc* aFontFaceSrc,
+ nsIPrincipal** aPrincipal,
+ bool* aBypassCache) = 0;
+
+ // check whether content policies allow the given URI to load.
+ virtual bool IsFontLoadAllowed(nsIURI* aFontLocation,
+ nsIPrincipal* aPrincipal) = 0;
+
+ // initialize the process that loads external font data, which upon
+ // completion will call FontDataDownloadComplete method
+ virtual nsresult StartLoad(gfxUserFontEntry* aUserFontEntry,
+ const gfxFontFaceSrc* aFontFaceSrc) = 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();
+
+ class UserFontCache {
+ public:
+ // Flag passed when caching a font entry, to specify whether the entry
+ // should persist in the cache or be discardable.
+ typedef enum {
+ kDiscardable,
+ kPersistent
+ } EntryPersistence;
+
+ // 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.
+ // If aPersistence is Persistent, the entry will remain in the cache
+ // across cacheservice:empty-cache notifications. This is used for
+ // "preloaded hidden fonts" on FxOS.
+ static void CacheFont(gfxFontEntry* aFontEntry,
+ EntryPersistence aPersistence = kDiscardable);
+
+ // 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(nsIURI* aSrcURI,
+ nsIPrincipal* aPrincipal,
+ gfxUserFontEntry* aUserFontEntry,
+ bool aPrivate);
+
+ // Clear everything so that we don't leak URIs and Principals.
+ static void Shutdown();
+
+ // Memory-reporting support.
+ class MemoryReporter final : public nsIMemoryReporter
+ {
+ private:
+ ~MemoryReporter() { }
+
+ 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() {}
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ Flusher() {}
+ };
+
+ // 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 {
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIPrincipal> 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;
+ uint32_t mCRC32;
+ uint32_t mLength;
+ bool mPrivate;
+ EntryPersistence mPersistence;
+
+ Key(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ gfxFontEntry* aFontEntry, bool aPrivate,
+ EntryPersistence aPersistence = kDiscardable)
+ : mURI(aURI),
+ mPrincipal(aPrincipal),
+ mFontEntry(aFontEntry),
+ mCRC32(0),
+ mLength(0),
+ mPrivate(aPrivate),
+ mPersistence(aPersistence)
+ { }
+
+ Key(uint32_t aCRC32, uint32_t aLength,
+ gfxFontEntry* aFontEntry, bool aPrivate,
+ EntryPersistence aPersistence = kDiscardable)
+ : mURI(nullptr),
+ mPrincipal(nullptr),
+ mFontEntry(aFontEntry),
+ mCRC32(aCRC32),
+ mLength(aLength),
+ mPrivate(aPrivate),
+ mPersistence(aPersistence)
+ { }
+ };
+
+ class Entry : public PLDHashEntryHdr {
+ public:
+ typedef const Key& KeyType;
+ typedef const Key* KeyTypePointer;
+
+ explicit Entry(KeyTypePointer aKey)
+ : mURI(aKey->mURI),
+ mPrincipal(aKey->mPrincipal),
+ mCRC32(aKey->mCRC32),
+ mLength(aKey->mLength),
+ mFontEntry(aKey->mFontEntry),
+ mPrivate(aKey->mPrivate),
+ mPersistence(aKey->mPersistence)
+ { }
+
+ Entry(const Entry& aOther)
+ : mURI(aOther.mURI),
+ mPrincipal(aOther.mPrincipal),
+ mCRC32(aOther.mCRC32),
+ mLength(aOther.mLength),
+ mFontEntry(aOther.mFontEntry),
+ mPrivate(aOther.mPrivate),
+ mPersistence(aOther.mPersistence)
+ { }
+
+ ~Entry() { }
+
+ bool KeyEquals(const KeyTypePointer aKey) const;
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+
+ static PLDHashNumber HashKey(const KeyTypePointer aKey) {
+ if (aKey->mLength) {
+ return aKey->mCRC32;
+ }
+ uint32_t principalHash = 0;
+ if (aKey->mPrincipal) {
+ aKey->mPrincipal->GetHashValue(&principalHash);
+ }
+ return mozilla::HashGeneric(principalHash + int(aKey->mPrivate),
+ nsURIHashKey::HashKey(aKey->mURI),
+ HashFeatures(aKey->mFontEntry->mFeatureSettings),
+ mozilla::HashString(aKey->mFontEntry->mFamilyName),
+ (aKey->mFontEntry->mStyle |
+ (aKey->mFontEntry->mWeight << 2) |
+ (aKey->mFontEntry->mStretch << 11) ) ^
+ aKey->mFontEntry->mLanguageOverride);
+ }
+
+ enum { ALLOW_MEMMOVE = false };
+
+ gfxFontEntry* GetFontEntry() const { return mFontEntry; }
+
+ bool IsPersistent() const { return mPersistence == kPersistent; }
+ 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));
+ }
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIPrincipal> mPrincipal; // or nullptr for data: URLs
+
+ uint32_t mCRC32;
+ uint32_t mLength;
+
+ // 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;
+
+ // Whether this entry should survive cache-flushing.
+ EntryPersistence mPersistence;
+ };
+
+ 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;
+
+ // 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,
+ 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,
+ uint32_t aWeight,
+ int32_t aStretch,
+ uint8_t aStyle,
+ const nsTArray<gfxFontFeature>& aFeatureSettings,
+ uint32_t aLanguageOverride,
+ gfxSparseBitSet* aUnicodeRanges,
+ uint8_t aFontDisplay);
+
+ // creates a new gfxUserFontFamily in mFontFamilies, or returns an existing
+ // family if there is one
+ gfxUserFontFamily* GetFamily(const nsAString& aFamilyName);
+
+ // font families defined by @font-face rules
+ nsRefPtrHashtable<nsStringHashKey, 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 gfxUserFontSet;
+ friend class nsUserFontSet;
+ friend class nsFontFaceLoader;
+ friend class gfxOTSContext;
+
+public:
+ enum UserFontLoadState {
+ STATUS_NOT_LOADED = 0,
+ STATUS_LOADING,
+ STATUS_LOADED,
+ STATUS_FAILED
+ };
+
+ gfxUserFontEntry(gfxUserFontSet* aFontSet,
+ const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
+ uint32_t aWeight,
+ int32_t aStretch,
+ uint8_t aStyle,
+ const nsTArray<gfxFontFeature>& aFeatureSettings,
+ uint32_t aLanguageOverride,
+ gfxSparseBitSet* aUnicodeRanges,
+ uint8_t aFontDisplay);
+
+ virtual ~gfxUserFontEntry();
+
+ // Return whether the entry matches the given list of attributes
+ bool Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
+ uint32_t aWeight,
+ int32_t aStretch,
+ uint8_t aStyle,
+ const nsTArray<gfxFontFeature>& aFeatureSettings,
+ uint32_t aLanguageOverride,
+ gfxSparseBitSet* aUnicodeRanges,
+ uint8_t aFontDisplay);
+
+ virtual gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle,
+ bool aNeedsBold);
+
+ gfxFontEntry* GetPlatformFontEntry() const { return mPlatformFontEntry; }
+
+ // is the font loading or loaded, or did it fail?
+ UserFontLoadState LoadState() const { return mUserFontLoadState; }
+
+ // whether to wait before using fallback font or not
+ bool WaitForUserFont() const {
+ return mUserFontLoadState == STATUS_LOADING &&
+ mFontDataLoadingState < LOADING_SLOWLY;
+ }
+
+ // for userfonts, cmap is used to store the unicode range data
+ // no cmap ==> all codepoints permitted
+ bool CharacterInUnicodeRange(uint32_t ch) const {
+ if (mCharacterMap) {
+ return mCharacterMap->test(ch);
+ }
+ return true;
+ }
+
+ gfxCharacterMap* GetUnicodeRangeMap() const {
+ return mCharacterMap.get();
+ }
+
+ uint8_t 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) { mLoader = aLoader; }
+ nsFontFaceLoader* GetLoader() { return mLoader; }
+ nsIPrincipal* GetPrincipal() { return mPrincipal; }
+ uint32_t GetSrcIndex() { return mSrcIndex; }
+ void GetFamilyNameAndURIForLogging(nsACString& aFamilyName,
+ nsACString& aURI);
+
+#ifdef DEBUG
+ gfxUserFontSet* GetUserFontSet() const { return mFontSet; }
+#endif
+
+protected:
+ const uint8_t* SanitizeOpenTypeData(const uint8_t* aData,
+ uint32_t aLength,
+ uint32_t& aSaneLength,
+ gfxUserFontType aFontType);
+
+ // attempt to load the next resource in the src list.
+ void LoadNextSrc();
+
+ // 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
+ // returns true if platform font creation sucessful (or local()
+ // reference was next in line)
+ // Ownership of aFontData is passed in here; the font set must
+ // ensure that it is eventually deleted with free().
+ bool FontDataDownloadComplete(const uint8_t* aFontData, uint32_t aLength,
+ nsresult aDownloadStatus);
+
+ // 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 LoadPlatformFont(const uint8_t* aFontData, uint32_t& aLength);
+
+ // store metadata and src details for current src into aFontEntry
+ void StoreUserFontData(gfxFontEntry* aFontEntry,
+ bool aPrivate,
+ const nsAString& 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 mFontSet, the
+ // owner of this user font entry.
+ virtual void GetUserFontSets(nsTArray<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 mUnsupportedFormat;
+ uint8_t mFontDisplay; // timing of userfont fallback
+
+ RefPtr<gfxFontEntry> mPlatformFontEntry;
+ nsTArray<gfxFontFaceSrc> mSrcList;
+ uint32_t mSrcIndex; // index of loading src item
+ // 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
+ gfxUserFontSet* mFontSet; // font-set which owns this userfont entry
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+};
+
+
+#endif /* GFX_USER_FONT_SET_H */
diff --git a/gfx/thebes/gfxUtils.cpp b/gfx/thebes/gfxUtils.cpp
new file mode 100644
index 000000000..313372ebc
--- /dev/null
+++ b/gfx/thebes/gfxUtils.cpp
@@ -0,0 +1,1547 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "imgIEncoder.h"
+#include "libyuv.h"
+#include "mozilla/Base64.h"
+#include "mozilla/dom/ImageEncoder.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/Vector.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIClipboardHelper.h"
+#include "nsIFile.h"
+#include "nsIGfxInfo.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsRegion.h"
+#include "nsServiceManagerUtils.h"
+#include "GeckoProfiler.h"
+#include "ImageContainer.h"
+#include "ImageRegion.h"
+#include "gfx2DGlue.h"
+#include "gfxPrefs.h"
+
+#ifdef XP_WIN
+#include "gfxWindowsPlatform.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::image;
+using namespace mozilla::layers;
+using namespace mozilla::gfx;
+
+#include "DeprecatedPremultiplyTables.h"
+
+#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 uint8_t PremultiplyValue(uint8_t a, uint8_t v) {
+ return gfxUtils::sPremultiplyTable[a*256+v];
+}
+
+static uint8_t UnpremultiplyValue(uint8_t a, uint8_t v) {
+ return gfxUtils::sUnpremultiplyTable[a*256+v];
+}
+
+static void
+PremultiplyData(const uint8_t* srcData,
+ size_t srcStride, // row-to-row stride in bytes
+ uint8_t* destData,
+ size_t destStride, // row-to-row stride in bytes
+ size_t pixelWidth,
+ size_t rowCount)
+{
+ MOZ_ASSERT(srcData && destData);
+
+ for (size_t y = 0; y < rowCount; ++y) {
+ const uint8_t* src = srcData + y * srcStride;
+ uint8_t* dest = destData + y * destStride;
+
+ for (size_t x = 0; x < pixelWidth; ++x) {
+#ifdef IS_LITTLE_ENDIAN
+ uint8_t b = *src++;
+ uint8_t g = *src++;
+ uint8_t r = *src++;
+ uint8_t a = *src++;
+
+ *dest++ = PremultiplyValue(a, b);
+ *dest++ = PremultiplyValue(a, g);
+ *dest++ = PremultiplyValue(a, r);
+ *dest++ = a;
+#else
+ uint8_t a = *src++;
+ uint8_t r = *src++;
+ uint8_t g = *src++;
+ uint8_t b = *src++;
+
+ *dest++ = a;
+ *dest++ = PremultiplyValue(a, r);
+ *dest++ = PremultiplyValue(a, g);
+ *dest++ = PremultiplyValue(a, b);
+#endif
+ }
+ }
+}
+static void
+UnpremultiplyData(const uint8_t* srcData,
+ size_t srcStride, // row-to-row stride in bytes
+ uint8_t* destData,
+ size_t destStride, // row-to-row stride in bytes
+ size_t pixelWidth,
+ size_t rowCount)
+{
+ MOZ_ASSERT(srcData && destData);
+
+ for (size_t y = 0; y < rowCount; ++y) {
+ const uint8_t* src = srcData + y * srcStride;
+ uint8_t* dest = destData + y * destStride;
+
+ for (size_t x = 0; x < pixelWidth; ++x) {
+#ifdef IS_LITTLE_ENDIAN
+ uint8_t b = *src++;
+ uint8_t g = *src++;
+ uint8_t r = *src++;
+ uint8_t a = *src++;
+
+ *dest++ = UnpremultiplyValue(a, b);
+ *dest++ = UnpremultiplyValue(a, g);
+ *dest++ = UnpremultiplyValue(a, r);
+ *dest++ = a;
+#else
+ uint8_t a = *src++;
+ uint8_t r = *src++;
+ uint8_t g = *src++;
+ uint8_t b = *src++;
+
+ *dest++ = a;
+ *dest++ = UnpremultiplyValue(a, r);
+ *dest++ = UnpremultiplyValue(a, g);
+ *dest++ = UnpremultiplyValue(a, b);
+#endif
+ }
+ }
+}
+
+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->GetFormat() != SurfaceFormat::B8G8R8A8 ||
+ destSurf->GetFormat() != SurfaceFormat::B8G8R8A8)
+ {
+ MOZ_ASSERT(false, "Only operate on BGRA8 surfs.");
+ return false;
+ }
+
+ if (srcSurf->GetSize().width != destSurf->GetSize().width ||
+ srcSurf->GetSize().height != destSurf->GetSize().height)
+ {
+ 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,
+ destMap.mData, destMap.mStride,
+ srcSurf->GetSize().width,
+ srcSurf->GetSize().height);
+
+ 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,
+ destMap.mData, destMap.mStride,
+ srcSurf->GetSize().width,
+ srcSurf->GetSize().height);
+
+ 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);
+
+ if (srcSurf->GetFormat() != SurfaceFormat::B8G8R8A8) {
+ MOZ_ASSERT(false, "Only operate on BGRA8.");
+ return false;
+ }
+
+ // 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,
+ destMap.mData, destMap.mStride,
+ srcSurf->GetSize().width,
+ srcSurf->GetSize().height);
+
+ 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,
+ destMap.mData, destMap.mStride,
+ srcSurf->GetSize().width,
+ srcSurf->GetSize().height);
+
+ 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!");
+ libyuv::ABGRToARGB(aData, aLength, aData, aLength, 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)
+{
+ PROFILER_LABEL("gfxUtils", "CreateSamplingRestricedDrawable",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ DrawTarget* destDrawTarget = aContext->GetDrawTarget();
+ if (destDrawTarget->GetBackendType() == BackendType::DIRECT2D1_1) {
+ 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;
+ }
+
+ RefPtr<gfxContext> tmpCtx = gfxContext::CreateOrNull(target);
+ MOZ_ASSERT(tmpCtx); // already checked the target above
+
+ 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)
+{
+ gfxSize scaleFactor = aContext->CurrentMatrix().ScaleFactors(true);
+ gfxMatrix scaleMatrix = gfxMatrix::Scaling(scaleFactor.width, scaleFactor.height);
+ const float fuzzFactor = 0.01;
+
+ // If we aren't scaling or translating, don't go down this path
+ if ((FuzzyEqual(scaleFactor.width, 1.0, fuzzFactor) &&
+ FuzzyEqual(scaleFactor.width, 1.0, 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 = ToMatrix(scaleMatrix).TransformBounds(ToRect(needed));
+ scaledNeededRect.RoundOut();
+ if (scaledNeededRect.IsEmpty()) {
+ return false;
+ }
+
+ Rect scaledImageRect = ToMatrix(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;
+ }
+
+ RefPtr<gfxContext> tmpCtx = gfxContext::CreateOrNull(scaledDT);
+ MOZ_ASSERT(tmpCtx); // already checked the target above
+
+ scaledDT->SetTransform(ToMatrix(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 = ToMatrix(aContext->CurrentMatrix());
+ DrawTarget* destDrawTarget = aContext->GetDrawTarget();
+
+ // The translation still is in scaled units
+ withoutScale.PreScale(1.0 / scaleFactor.width, 1.0 / scaleFactor.height);
+ aContext->SetMatrix(ThebesMatrix(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)
+{
+ PROFILER_LABEL("gfxUtils", "DrawPixelSnapped",
+ js::ProfileEntry::Category::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);
+ 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*/ gfxFloat
+gfxUtils::ClampToScaleFactor(gfxFloat aVal)
+{
+ // Arbitary scale factor limitation. We can increase this
+ // for better scaling performance at the cost of worse
+ // quality.
+ static const gfxFloat 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;
+ }
+
+ gfxFloat power = log(aVal)/log(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);
+ } else if (inverse) {
+ power = floor(power);
+ } else {
+ power = ceil(power);
+ }
+
+ gfxFloat scale = pow(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);
+}
+
+/* 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->GetType() != SurfaceType::DATA) {
+ // 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::Color&
+gfxUtils::GetColorForFrameNumber(uint64_t aFrameNumber)
+{
+ static bool initialized = false;
+ static gfx::Color colors[sNumFrameColors];
+
+ if (!initialized) {
+ uint32_t i = 0;
+ colors[i++] = gfx::Color::FromABGR(0xffff0000);
+ colors[i++] = gfx::Color::FromABGR(0xffcc00ff);
+ colors[i++] = gfx::Color::FromABGR(0xff0066cc);
+ colors[i++] = gfx::Color::FromABGR(0xff00ff00);
+ colors[i++] = gfx::Color::FromABGR(0xff33ffff);
+ colors[i++] = gfx::Color::FromABGR(0xffff0099);
+ colors[i++] = gfx::Color::FromABGR(0xff0000ff);
+ colors[i++] = gfx::Color::FromABGR(0xff999999);
+ MOZ_ASSERT(i == sNumFrameColors);
+ initialized = true;
+ }
+
+ return colors[aFrameNumber % sNumFrameColors];
+}
+
+static nsresult
+EncodeSourceSurfaceInternal(SourceSurface* aSurface,
+ const nsACString& aMimeType,
+ const nsAString& aOutputOptions,
+ gfxUtils::BinaryOrData aBinaryOrData,
+ FILE* aFile,
+ nsCString* aStrOut)
+{
+ MOZ_ASSERT(aBinaryOrData == gfxUtils::eDataURIEncode || aFile || aStrOut,
+ "Copying binary encoding to clipboard not currently supported");
+
+ const IntSize size = aSurface->GetSize();
+ if (size.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ const Size floatSize(size.width, size.height);
+
+ 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;
+ }
+
+ nsAutoCString encoderCID(
+ NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type=") + aMimeType);
+ nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get());
+ if (!encoder) {
+#ifdef DEBUG
+ int32_t w = std::min(size.width, 8);
+ int32_t h = std::min(size.height, 8);
+ printf("Could not create encoder. Top-left %dx%d pixels contain:\n", w, h);
+ for (int32_t y = 0; y < h; ++y) {
+ for (int32_t x = 0; x < w; ++x) {
+ printf("%x ", reinterpret_cast<uint32_t*>(map.mData)[y*map.mStride+x]);
+ }
+ }
+#endif
+ dataSurface->Unmap();
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = encoder->InitFromData(map.mData,
+ BufferSizeFromStrideAndHeight(map.mStride, size.height),
+ size.width,
+ size.height,
+ map.mStride,
+ imgIEncoder::INPUT_FORMAT_HOSTARGB,
+ aOutputOptions);
+ dataSurface->Unmap();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> imgStream;
+ CallQueryInterface(encoder.get(), getter_AddRefs(imgStream));
+ if (!imgStream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint64_t bufSize64;
+ rv = imgStream->Available(&bufSize64);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(bufSize64 < UINT32_MAX - 16, NS_ERROR_FAILURE);
+
+ uint32_t bufSize = (uint32_t)bufSize64;
+
+ // ...leave a little extra room so we can call read again and make sure we
+ // got everything. 16 bytes for better padding (maybe)
+ bufSize += 16;
+ uint32_t imgSize = 0;
+ Vector<char> imgData;
+ if (!imgData.initCapacity(bufSize)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ uint32_t numReadThisTime = 0;
+ while ((rv = imgStream->Read(imgData.begin() + imgSize,
+ bufSize - imgSize,
+ &numReadThisTime)) == NS_OK && numReadThisTime > 0)
+ {
+ // Update the length of the vector without overwriting the new data.
+ if (!imgData.growByUninitialized(numReadThisTime)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ imgSize += numReadThisTime;
+ if (imgSize == bufSize) {
+ // need a bigger buffer, just double
+ bufSize *= 2;
+ if (!imgData.resizeUninitialized(bufSize)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(!imgData.empty(), NS_ERROR_FAILURE);
+
+ if (aBinaryOrData == gfxUtils::eBinaryEncode) {
+ if (aFile) {
+ fwrite(imgData.begin(), 1, imgSize, aFile);
+ }
+ return NS_OK;
+ }
+
+ // base 64, result will be null-terminated
+ nsCString encodedImg;
+ rv = Base64Encode(Substring(imgData.begin(), imgSize), encodedImg);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString string("data:");
+ string.Append(aMimeType);
+ string.Append(";base64,");
+ string.Append(encodedImg);
+
+ if (aFile) {
+#ifdef ANDROID
+ if (aFile == stdout || aFile == stderr) {
+ // ADB logcat cuts off long strings so we will break it down
+ const char* cStr = string.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", string.BeginReading());
+ } else if (aStrOut) {
+ *aStrOut = string;
+ } else {
+ nsCOMPtr<nsIClipboardHelper> clipboard(do_GetService("@mozilla.org/widget/clipboardhelper;1", &rv));
+ if (clipboard) {
+ clipboard->CopyString(NS_ConvertASCIItoUTF16(string));
+ }
+ }
+ return NS_OK;
+}
+
+static nsCString
+EncodeSourceSurfaceAsPNGURI(SourceSurface* aSurface)
+{
+ nsCString string;
+ EncodeSourceSurfaceInternal(aSurface, NS_LITERAL_CSTRING("image/png"),
+ EmptyString(), gfxUtils::eDataURIEncode,
+ nullptr, &string);
+ return string;
+}
+
+/* static */ nsresult
+gfxUtils::EncodeSourceSurface(SourceSurface* aSurface,
+ const nsACString& aMimeType,
+ const nsAString& aOutputOptions,
+ BinaryOrData aBinaryOrData,
+ FILE* aFile)
+{
+ return EncodeSourceSurfaceInternal(aSurface, aMimeType, aOutputOptions,
+ aBinaryOrData, aFile, nullptr);
+}
+
+/* From Rec601:
+[R] [1.1643835616438356, 0.0, 1.5960267857142858] [ Y - 16]
+[G] = [1.1643835616438358, -0.3917622900949137, -0.8129676472377708] x [Cb - 128]
+[B] [1.1643835616438356, 2.017232142857143, 8.862867620416422e-17] [Cr - 128]
+
+For [0,1] instead of [0,255], and to 5 places:
+[R] [1.16438, 0.00000, 1.59603] [ Y - 0.06275]
+[G] = [1.16438, -0.39176, -0.81297] x [Cb - 0.50196]
+[B] [1.16438, 2.01723, 0.00000] [Cr - 0.50196]
+
+From Rec709:
+[R] [1.1643835616438356, 4.2781193979771426e-17, 1.7927410714285714] [ Y - 16]
+[G] = [1.1643835616438358, -0.21324861427372963, -0.532909328559444] x [Cb - 128]
+[B] [1.1643835616438356, 2.1124017857142854, 0.0] [Cr - 128]
+
+For [0,1] instead of [0,255], and to 5 places:
+[R] [1.16438, 0.00000, 1.79274] [ Y - 0.06275]
+[G] = [1.16438, -0.21325, -0.53291] x [Cb - 0.50196]
+[B] [1.16438, 2.11240, 0.00000] [Cr - 0.50196]
+*/
+
+/* static */ float*
+gfxUtils::Get4x3YuvColorMatrix(YUVColorSpace aYUVColorSpace)
+{
+ static const float yuv_to_rgb_rec601[12] = { 1.16438f, 0.0f, 1.59603f, 0.0f,
+ 1.16438f, -0.39176f, -0.81297f, 0.0f,
+ 1.16438f, 2.01723f, 0.0f, 0.0f,
+ };
+
+ static const float yuv_to_rgb_rec709[12] = { 1.16438f, 0.0f, 1.79274f, 0.0f,
+ 1.16438f, -0.21325f, -0.53291f, 0.0f,
+ 1.16438f, 2.11240f, 0.0f, 0.0f,
+ };
+
+ if (aYUVColorSpace == YUVColorSpace::BT709) {
+ return const_cast<float*>(yuv_to_rgb_rec709);
+ } else {
+ return const_cast<float*>(yuv_to_rgb_rec601);
+ }
+}
+
+/* static */ float*
+gfxUtils::Get3x3YuvColorMatrix(YUVColorSpace aYUVColorSpace)
+{
+ static const float yuv_to_rgb_rec601[9] = {
+ 1.16438f, 1.16438f, 1.16438f, 0.0f, -0.39176f, 2.01723f, 1.59603f, -0.81297f, 0.0f,
+ };
+ static const float yuv_to_rgb_rec709[9] = {
+ 1.16438f, 1.16438f, 1.16438f, 0.0f, -0.21325f, 2.11240f, 1.79274f, -0.53291f, 0.0f,
+ };
+
+ if (aYUVColorSpace == YUVColorSpace::BT709) {
+ return const_cast<float*>(yuv_to_rgb_rec709);
+ } else {
+ return const_cast<float*>(yuv_to_rgb_rec601);
+ }
+}
+
+/* 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, NS_LITERAL_CSTRING("image/png"),
+ EmptyString(), 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::WriteAsPNG(nsIPresShell* aShell, const char* aFile)
+{
+ int32_t width = 1000, height = 1000;
+ nsRect r(0, 0, aShell->GetPresContext()->DevPixelsToAppUnits(width),
+ aShell->GetPresContext()->DevPixelsToAppUnits(height));
+
+ RefPtr<mozilla::gfx::DrawTarget> dt = gfxPlatform::GetPlatform()->
+ CreateOffscreenContentDrawTarget(IntSize(width, height),
+ SurfaceFormat::B8G8R8A8);
+ NS_ENSURE_TRUE(dt && dt->IsValid(), /*void*/);
+
+ RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt);
+ MOZ_ASSERT(context); // already checked the draw target above
+ aShell->RenderDocument(r, 0, NS_RGB(255, 255, 0), context);
+ WriteAsPNG(dt.get(), aFile);
+}
+
+/* static */ void
+gfxUtils::DumpAsDataURI(SourceSurface* aSurface, FILE* aFile)
+{
+ EncodeSourceSurface(aSurface, NS_LITERAL_CSTRING("image/png"),
+ EmptyString(), 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)
+{
+ int32_t dataSize = aSourceSurface->GetSize().height * aSourceSurface->Stride();
+ auto compressedData = MakeUnique<char[]>(LZ4::maxCompressedSize(dataSize));
+ if (compressedData) {
+ int nDataSize = LZ4::compress((char*)aSourceSurface->GetData(),
+ dataSize,
+ compressedData.get());
+ if (nDataSize > 0) {
+ nsCString encodedImg;
+ nsresult rv = Base64Encode(Substring(compressedData.get(), nDataSize), encodedImg);
+ if (rv == NS_OK) {
+ nsCString string("");
+ string.AppendPrintf("data:image/lz4bgra;base64,%i,%i,%i,",
+ aSourceSurface->GetSize().width,
+ aSourceSurface->Stride(),
+ aSourceSurface->GetSize().height);
+ string.Append(encodedImg);
+ return string;
+ }
+ }
+ }
+ return nsCString("");
+}
+
+/* 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, NS_LITERAL_CSTRING("image/png"),
+ EmptyString(), 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 */ nsresult
+gfxUtils::GetInputStream(gfx::DataSourceSurface* aSurface,
+ bool aIsAlphaPremultiplied,
+ const char* aMimeType,
+ const char16_t* 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);
+}
+
+class GetFeatureStatusRunnable final : public dom::workers::WorkerMainThreadRunnable
+{
+public:
+ GetFeatureStatusRunnable(dom::workers::WorkerPrivate* workerPrivate,
+ const nsCOMPtr<nsIGfxInfo>& gfxInfo,
+ int32_t feature,
+ nsACString& failureId,
+ int32_t* status)
+ : WorkerMainThreadRunnable(workerPrivate,
+ NS_LITERAL_CSTRING("GFX :: GetFeatureStatus"))
+ , 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:
+ ~GetFeatureStatusRunnable() {}
+
+private:
+ nsCOMPtr<nsIGfxInfo> mGfxInfo;
+ int32_t mFeature;
+ int32_t* mStatus;
+ nsACString& mFailureId;
+ nsresult mNSResult;
+};
+
+/* static */ nsresult
+gfxUtils::ThreadSafeGetFeatureStatus(const nsCOMPtr<nsIGfxInfo>& gfxInfo,
+ int32_t feature, nsACString& failureId,
+ int32_t* status)
+{
+ if (!NS_IsMainThread()) {
+ dom::workers::WorkerPrivate* workerPrivate =
+ dom::workers::GetCurrentThreadWorkerPrivate();
+
+ RefPtr<GetFeatureStatusRunnable> runnable =
+ new GetFeatureStatusRunnable(workerPrivate, gfxInfo, feature, failureId,
+ status);
+
+ ErrorResult rv;
+ runnable->Dispatch(rv);
+ if (rv.Failed()) {
+ // XXXbz This is totally broken, since we're supposed to just abort
+ // everything up the callstack but the callers basically eat the
+ // exception. Ah, well.
+ return rv.StealNSResult();
+ }
+
+ return runnable->GetNSResult();
+ }
+
+ return gfxInfo->GetFeatureStatus(feature, failureId, status);
+}
+
+/* static */ bool
+gfxUtils::IsFeatureBlacklisted(nsCOMPtr<nsIGfxInfo> gfxInfo, int32_t feature,
+ nsACString* const out_blacklistId)
+{
+ if (!gfxInfo) {
+ gfxInfo = services::GetGfxInfo();
+ }
+
+ int32_t status;
+ if (!NS_SUCCEEDED(gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, feature,
+ *out_blacklistId, &status)))
+ {
+ out_blacklistId->AssignLiteral("");
+ return true;
+ }
+
+ return status != nsIGfxInfo::FEATURE_STATUS_OK;
+}
+
+/* static */ bool
+gfxUtils::DumpDisplayList() {
+ return gfxPrefs::LayoutDumpDisplayList() ||
+ (gfxPrefs::LayoutDumpDisplayListContent() && XRE_IsContentProcess());
+}
+
+FILE *gfxUtils::sDumpPaintFile = stderr;
+
+namespace mozilla {
+namespace gfx {
+
+Color ToDeviceColor(Color 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() == eCMSMode_All) {
+ qcms_transform *transform = gfxPlatform::GetCMSRGBTransform();
+ if (transform) {
+ gfxPlatform::TransformPixel(aColor, aColor, transform);
+ // Use the original alpha to avoid unnecessary float->byte->float
+ // conversion errors
+ }
+ }
+ return aColor;
+}
+
+Color ToDeviceColor(nscolor aColor)
+{
+ return ToDeviceColor(Color::FromABGR(aColor));
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/thebes/gfxUtils.h b/gfx/thebes/gfxUtils.h
new file mode 100644
index 000000000..7a4679fb9
--- /dev/null
+++ b/gfx/thebes/gfxUtils.h
@@ -0,0 +1,325 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxTypes.h"
+#include "ImageTypes.h"
+#include "imgIContainer.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsColor.h"
+#include "nsPrintfCString.h"
+#include "nsRegionFwd.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/CheckedInt.h"
+
+class gfxASurface;
+class gfxDrawable;
+class nsIInputStream;
+class nsIGfxInfo;
+class nsIPresShell;
+
+namespace mozilla {
+namespace layers {
+struct PlanarYCbCrData;
+} // namespace layers
+namespace image {
+class ImageRegion;
+} // namespace image
+} // namespace mozilla
+
+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::SourceSurface SourceSurface;
+ typedef mozilla::gfx::SurfaceFormat SurfaceFormat;
+ typedef mozilla::image::ImageRegion ImageRegion;
+ typedef mozilla::YUVColorSpace YUVColorSpace;
+
+ /*
+ * 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);
+
+ /**
+ * 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);
+
+ /**
+ * Return the smallest power of kScaleResolution (2) greater than or equal to
+ * aVal.
+ */
+ static gfxFloat ClampToScaleFactor(gfxFloat aVal);
+
+ /**
+ * Clears surface to aColor (which defaults to transparent black).
+ */
+ static void ClearThebesSurface(gfxASurface* aSurface);
+
+ /**
+ * Get array of yuv to rgb conversion matrix.
+ */
+ static float* Get4x3YuvColorMatrix(YUVColorSpace aYUVColorSpace);
+
+ static float* Get3x3YuvColorMatrix(YUVColorSpace aYUVColorSpace);
+
+ /**
+ * 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);
+
+ static const uint8_t sUnpremultiplyTable[256*256];
+ static const uint8_t sPremultiplyTable[256*256];
+
+ /**
+ * 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::Color& GetColorForFrameNumber(uint64_t aFrameNumber);
+ static const uint32_t sNumFrameColors;
+
+
+ enum BinaryOrData {
+ eBinaryEncode,
+ eDataURIEncode
+ };
+
+ /**
+ * Encodes the given surface to PNG/JPEG/BMP/etc. using imgIEncoder.
+ *
+ * @param aMimeType The MIME-type of 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 sane 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, otherwise
+ * it is copied to the clipboard.
+ *
+ * TODO: Copying to the clipboard as a binary file is not currently
+ * supported.
+ */
+ static nsresult
+ EncodeSourceSurface(SourceSurface* aSurface,
+ const nsACString& aMimeType,
+ const nsAString& aOutputOptions,
+ BinaryOrData aBinaryOrData,
+ FILE* aFile);
+
+ /**
+ * 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);
+ static void WriteAsPNG(nsIPresShell* aShell, 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 nsresult GetInputStream(DataSourceSurface* aSurface,
+ bool aIsAlphaPremultiplied,
+ const char* aMimeType,
+ const char16_t* aEncoderOptions,
+ nsIInputStream** outStream);
+
+ static nsresult ThreadSafeGetFeatureStatus(const nsCOMPtr<nsIGfxInfo>& gfxInfo,
+ int32_t feature,
+ nsACString& failureId,
+ int32_t* status);
+
+ // Can pass `nullptr` for gfxInfo.
+ // If FAILED(ThreadSafeGetFeatureStatus), out_blacklistId will be empty.
+ static bool IsFeatureBlacklisted(nsCOMPtr<nsIGfxInfo> gfxInfo, int32_t feature,
+ nsACString* const out_blacklistId);
+
+ /**
+ * 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 {
+namespace gfx {
+
+/**
+ * If the CMS mode is eCMSMode_All, these functions transform the passed
+ * color to a device color using the transform returened 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).
+ */
+Color ToDeviceColor(Color aColor);
+Color ToDeviceColor(nscolor aColor);
+
+/**
+ * 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 000000000..bd2f78d21
--- /dev/null
+++ b/gfx/thebes/gfxWindowsNativeDrawing.cpp
@@ -0,0 +1,320 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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->CurrentMatrix();
+ 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 = gfxSize(1.0, 1.0);
+
+ // 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(true);
+
+ mWorldTransform.eM11 = (FLOAT) mScale.width;
+ mWorldTransform.eM12 = 0.0f;
+ mWorldTransform.eM21 = 0.0f;
+ mWorldTransform.eM22 = (FLOAT) mScale.height;
+ mWorldTransform.eDx = 0.0f;
+ mWorldTransform.eDy = 0.0f;
+
+ // See comment above about "+1"
+ mTempSurfaceSize =
+ IntSize((int32_t) ceil(mNativeRect.Width() * mScale.width + 1),
+ (int32_t) ceil(mNativeRect.Height() * mScale.height + 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.width, 1.0 / mScale.height);
+ SamplingFilter filter = (mNativeDrawFlags & DO_NEAREST_NEIGHBOR_FILTERING)
+ ? SamplingFilter::LINEAR
+ : SamplingFilter::GOOD;
+ SurfacePattern pat(source, ExtendMode::CLAMP, m, filter);
+ 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 000000000..25589fc5b
--- /dev/null
+++ b/gfx/thebes/gfxWindowsNativeDrawing.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 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,
+
+ /* If we have to do transforms with cairo, should we use nearest-neighbour filtering? */
+ DO_NEAREST_NEIGHBOR_FILTERING = 1 << 3,
+ DO_BILINEAR_FILTERING = 0 << 3
+ };
+
+ /* 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 |
+ DO_BILINEAR_FILTERING);
+
+ /* 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:
+
+ RefPtr<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;
+ gfxSize 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 100755
index 000000000..be1780797
--- /dev/null
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -0,0 +1,2139 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 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 "gfxWindowsPlatform.h"
+
+#include "cairo.h"
+#include "mozilla/ArrayUtils.h"
+
+#include "gfxImageSurface.h"
+#include "gfxWindowsSurface.h"
+
+#include "nsUnicharUtils.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsIGfxInfo.h"
+#include "nsServiceManagerUtils.h"
+#include "nsTArray.h"
+#include "mozilla/Telemetry.h"
+#include "GeckoProfiler.h"
+
+#include "nsIWindowsRegKey.h"
+#include "nsIFile.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/CompositorThread.h"
+#include "DeviceManagerD3D9.h"
+#include "mozilla/layers/ReadbackManagerD3D11.h"
+
+#include "WinUtils.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 "nsMemory.h"
+
+#include <d3d11.h>
+
+#include "nsIMemoryReporter.h"
+#include <winternl.h>
+#include "d3dkmtQueryStatistics.h"
+
+#include "base/thread.h"
+#include "gfxPrefs.h"
+#include "gfxConfig.h"
+#include "VsyncSource.h"
+#include "DriverCrashGuard.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "D3D11Checks.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+using namespace mozilla::image;
+
+IDWriteRenderingParams* GetDwriteRenderingParams(bool aGDI)
+{
+ gfxWindowsPlatform::TextRenderingMode mode = aGDI ?
+ gfxWindowsPlatform::TEXT_RENDERING_GDI_CLASSIC :
+ gfxWindowsPlatform::TEXT_RENDERING_NORMAL;
+ return gfxWindowsPlatform::GetPlatform()->GetRenderingParams(mode);
+}
+
+DCFromDrawTarget::DCFromDrawTarget(DrawTarget& aDrawTarget)
+{
+ mDC = nullptr;
+ if (aDrawTarget.GetBackendType() == BackendType::CAIRO) {
+ cairo_t* ctx = static_cast<cairo_t*>
+ (aDrawTarget.GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT));
+ if (ctx) {
+ cairo_surface_t* surf = cairo_get_group_target(ctx);
+ if (surf) {
+ cairo_surface_type_t surfaceType = cairo_surface_get_type(surf);
+ if (surfaceType == CAIRO_SURFACE_TYPE_WIN32 ||
+ surfaceType == CAIRO_SURFACE_TYPE_WIN32_PRINTING) {
+ mDC = cairo_win32_surface_get_dc(surf);
+ mNeedsRelease = false;
+ SaveDC(mDC);
+ cairo_scaled_font_t* scaled = cairo_get_scaled_font(ctx);
+ cairo_win32_scaled_font_select_font(scaled, mDC);
+ }
+ }
+ }
+ }
+
+ if (!mDC) {
+ // Get the whole screen DC:
+ mDC = GetDC(nullptr);
+ SetGraphicsMode(mDC, GM_ADVANCED);
+ mNeedsRelease = true;
+ }
+}
+
+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)
+
+#define GFX_USE_CLEARTYPE_ALWAYS "gfx.font_rendering.cleartype.always_use_for_content"
+#define GFX_DOWNLOADABLE_FONTS_USE_CLEARTYPE "gfx.font_rendering.cleartype.use_for_downloadable_fonts"
+
+#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"
+
+class GPUAdapterReporter final : public nsIMemoryReporter
+{
+ // Callers must Release the DXGIAdapter after use or risk mem-leak
+ static bool GetDXGIAdapter(IDXGIAdapter **aDXGIAdapter)
+ {
+ ID3D11Device *d3d11Device;
+ IDXGIDevice *dxgiDevice;
+ bool result = false;
+
+ if ((d3d11Device = mozilla::gfx::Factory::GetDirect3D11Device())) {
+ if (d3d11Device->QueryInterface(__uuidof(IDXGIDevice), (void **)&dxgiDevice) == S_OK) {
+ result = (dxgiDevice->GetAdapter(aDXGIAdapter) == S_OK);
+ dxgiDevice->Release();
+ }
+ }
+
+ 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;
+
+ // GPU memory reporting is not available before Windows 7
+ if (!IsWin7OrLater())
+ return NS_OK;
+
+ 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)
+{
+ mUseClearTypeForDownloadableFonts = UNINITIALIZED_VALUE;
+ mUseClearTypeAlways = UNINITIALIZED_VALUE;
+
+ /*
+ * Initialize COM
+ */
+ CoInitialize(nullptr);
+
+ RegisterStrongMemoryReporter(new GfxD2DVramReporter());
+ RegisterStrongMemoryReporter(new GPUAdapterReporter());
+ RegisterStrongMemoryReporter(new D3DSharedTexturesReporter());
+}
+
+gfxWindowsPlatform::~gfxWindowsPlatform()
+{
+ mozilla::gfx::Factory::D2DCleanup();
+
+ DeviceManagerD3D9::Shutdown();
+ DeviceManagerDx::Shutdown();
+
+ /*
+ * Uninitialize COM
+ */
+ CoUninitialize();
+}
+
+static void
+UpdateANGLEConfig()
+{
+ if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
+ gfxConfig::Disable(Feature::D3D11_HW_ANGLE, FeatureStatus::Disabled, "D3D11 compositing is disabled");
+ }
+}
+
+void
+gfxWindowsPlatform::InitAcceleration()
+{
+ gfxPlatform::InitAcceleration();
+
+ // 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);
+ mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_9_3);
+
+ DeviceManagerDx::Init();
+ DeviceManagerD3D9::Init();
+
+ InitializeConfig();
+ InitializeDevices();
+ UpdateANGLEConfig();
+ UpdateRenderMode();
+
+ // If we have Skia and we didn't init dwrite already, do it now.
+ if (!mDWriteFactory && GetDefaultContentBackend() == BackendType::SKIA) {
+ InitDWriteSupport();
+ }
+
+ // CanUseHardwareVideoDecoding depends on DeviceManagerDx state,
+ // so update the cached value now.
+ UpdateCanUseHardwareVideoDecoding();
+}
+
+bool
+gfxWindowsPlatform::CanUseHardwareVideoDecoding()
+{
+ DeviceManagerDx* dm = DeviceManagerDx::Get();
+ if (!dm) {
+ return false;
+ }
+ if (!gfxPrefs::LayersPreferD3D9() && !dm->TextureSharingWorks()) {
+ return false;
+ }
+ return !dm->IsWARP() && gfxPlatform::CanUseHardwareVideoDecoding();
+}
+
+bool
+gfxWindowsPlatform::InitDWriteSupport()
+{
+ if (!IsVistaOrLater()) {
+ return false;
+ }
+
+ // DWrite is only supported on Windows 7 with the platform update and higher.
+ // We check this by seeing if D2D1 support is available.
+ if (!Factory::SupportsD2D1()) {
+ return false;
+ }
+
+ mozilla::ScopedGfxFeatureReporter reporter("DWrite");
+ decltype(DWriteCreateFactory)* createDWriteFactory = (decltype(DWriteCreateFactory)*)
+ GetProcAddress(LoadLibraryW(L"dwrite.dll"), "DWriteCreateFactory");
+ if (!createDWriteFactory) {
+ return false;
+ }
+
+ // I need a direct pointer to be able to cast to IUnknown**, I also need to
+ // remember to release this because the nsRefPtr will AddRef it.
+ RefPtr<IDWriteFactory> factory;
+ HRESULT hr = createDWriteFactory(
+ DWRITE_FACTORY_TYPE_SHARED,
+ __uuidof(IDWriteFactory),
+ (IUnknown **)((IDWriteFactory **)getter_AddRefs(factory)));
+ if (FAILED(hr) || !factory) {
+ return false;
+ }
+
+ mDWriteFactory = factory;
+ Factory::SetDWriteFactory(mDWriteFactory);
+
+ SetupClearTypeParams();
+ 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();
+
+ if (XRE_IsContentProcess()) {
+ // Fetch updated device parameters.
+ FetchAndImportContentDeviceData();
+ UpdateANGLEConfig();
+ }
+
+ InitializeDevices();
+ UpdateANGLEConfig();
+ BumpDeviceCounter();
+ return true;
+}
+
+void
+gfxWindowsPlatform::UpdateBackendPrefs()
+{
+ uint32_t canvasMask = BackendTypeBit(BackendType::CAIRO);
+ uint32_t contentMask = BackendTypeBit(BackendType::CAIRO);
+ BackendType defaultBackend = BackendType::CAIRO;
+ if (gfxConfig::IsEnabled(Feature::DIRECT2D) && Factory::GetD2D1Device()) {
+ contentMask |= BackendTypeBit(BackendType::DIRECT2D1_1);
+ canvasMask |= BackendTypeBit(BackendType::DIRECT2D1_1);
+ defaultBackend = BackendType::DIRECT2D1_1;
+ } else {
+ canvasMask |= BackendTypeBit(BackendType::SKIA);
+ }
+ contentMask |= BackendTypeBit(BackendType::SKIA);
+ InitBackendPrefs(canvasMask, defaultBackend, contentMask, defaultBackend);
+}
+
+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())
+ << ", D3D11 status:" << FeatureStatusToString(gfxConfig::GetValue(Feature::D3D11_COMPOSITING))
+ << ", D2D1 device:" << hexa(Factory::GetD2D1Device())
+ << ", 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");
+ }
+ }
+}
+
+mozilla::gfx::BackendType
+gfxWindowsPlatform::GetContentBackendFor(mozilla::layers::LayersBackend aLayers)
+{
+ mozilla::gfx::BackendType defaultBackend = gfxPlatform::GetDefaultContentBackend();
+ if (aLayers == LayersBackend::LAYERS_D3D11) {
+ return defaultBackend;
+ }
+
+ if (defaultBackend == BackendType::DIRECT2D1_1) {
+ // We can't have D2D without D3D11 layers, so fallback to Cairo.
+ return BackendType::CAIRO;
+ }
+
+ // Otherwise we have some non-accelerated backend and that's ok.
+ return defaultBackend;
+}
+
+gfxPlatformFontList*
+gfxWindowsPlatform::CreatePlatformFontList()
+{
+ gfxPlatformFontList *pfl;
+
+ // bug 630201 - older pre-RTM versions of Direct2D/DirectWrite cause odd
+ // crashers so blacklist them altogether
+ if (IsNotWin7PreRTM() && GetDWriteFactory()) {
+ pfl = new gfxDWriteFontList();
+ if (NS_SUCCEEDED(pfl->InitFontList())) {
+ return pfl;
+ }
+ // 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.
+ gfxPlatformFontList::Shutdown();
+ DisableD2D(FeatureStatus::Failed, "Failed to initialize fonts",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_FONT_FAIL"));
+ }
+
+ pfl = new gfxGDIFontList();
+
+ if (NS_SUCCEEDED(pfl->InitFontList())) {
+ return pfl;
+ }
+
+ gfxPlatformFontList::Shutdown();
+ return nullptr;
+}
+
+// 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 (mRenderMode == RENDER_GDI || mRenderMode == RENDER_DIRECT2D)
+ surf = new gfxWindowsSurface(aSize, aFormat);
+#endif
+
+ if (!surf || surf->CairoStatus()) {
+ surf = new gfxImageSurface(aSize, aFormat);
+ }
+
+ return surf.forget();
+}
+
+already_AddRefed<ScaledFont>
+gfxWindowsPlatform::GetScaledFontForFont(DrawTarget* aTarget, gfxFont *aFont)
+{
+ if (aFont->GetType() == gfxFont::FONT_TYPE_DWRITE) {
+ gfxDWriteFont *font = static_cast<gfxDWriteFont*>(aFont);
+
+ NativeFont nativeFont;
+ nativeFont.mType = NativeFontType::DWRITE_FONT_FACE;
+ nativeFont.mFont = font->GetFontFace();
+
+ if (aTarget->GetBackendType() == BackendType::CAIRO) {
+ return Factory::CreateScaledFontWithCairo(nativeFont,
+ font->GetAdjustedSize(),
+ font->GetCairoScaledFont());
+ }
+
+ return Factory::CreateScaledFontForNativeFont(nativeFont,
+ font->GetAdjustedSize());
+ }
+
+ NS_ASSERTION(aFont->GetType() == gfxFont::FONT_TYPE_GDI,
+ "Fonts on windows should be GDI or DWrite!");
+
+ NativeFont nativeFont;
+ nativeFont.mType = NativeFontType::GDI_FONT_FACE;
+ LOGFONT lf;
+ GetObject(static_cast<gfxGDIFont*>(aFont)->GetHFONT(), sizeof(LOGFONT), &lf);
+ nativeFont.mFont = &lf;
+
+ if (aTarget->GetBackendType() == BackendType::CAIRO) {
+ return Factory::CreateScaledFontWithCairo(nativeFont,
+ aFont->GetAdjustedSize(),
+ aFont->GetCairoScaledFont());
+ }
+
+ return Factory::CreateScaledFontForNativeFont(nativeFont, aFont->GetAdjustedSize());
+}
+
+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 kFontEmojiOneMozilla[] = "EmojiOne Mozilla";
+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 kFontUtsaah[] = "Utsaah";
+static const char kFontYuGothic[] = "Yu Gothic";
+
+void
+gfxWindowsPlatform::GetCommonFallbackFonts(uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript,
+ nsTArray<const char*>& aFontList)
+{
+ if (aNextCh == 0xfe0fu) {
+ aFontList.AppendElement(kFontSegoeUIEmoji);
+ aFontList.AppendElement(kFontEmojiOneMozilla);
+ }
+
+ // 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
+ if (aNextCh == 0xfe0eu) {
+ aFontList.AppendElement(kFontSegoeUISymbol);
+ aFontList.AppendElement(kFontSegoeUIEmoji);
+ aFontList.AppendElement(kFontEmojiOneMozilla);
+ } else {
+ if (aNextCh != 0xfe0fu) {
+ aFontList.AppendElement(kFontSegoeUIEmoji);
+ aFontList.AppendElement(kFontEmojiOneMozilla);
+ }
+ 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);
+}
+
+gfxFontGroup *
+gfxWindowsPlatform::CreateFontGroup(const FontFamilyList& aFontFamilyList,
+ const gfxFontStyle *aStyle,
+ gfxTextPerfMetrics* aTextPerf,
+ gfxUserFontSet *aUserFontSet,
+ gfxFloat aDevToCssSize)
+{
+ return new gfxFontGroup(aFontFamilyList, aStyle, aTextPerf,
+ aUserFontSet, aDevToCssSize);
+}
+
+bool
+gfxWindowsPlatform::IsFontFormatSupported(nsIURI *aFontURI, uint32_t aFormatFlags)
+{
+ // check for strange format flags
+ NS_ASSERTION(!(aFormatFlags & gfxUserFontSet::FLAG_FORMAT_NOT_USED),
+ "strange font format hint set");
+
+ // accept supported formats
+ if (aFormatFlags & gfxUserFontSet::FLAG_FORMATS_COMMON) {
+ return true;
+ }
+
+ // reject all other formats, known and unknown
+ if (aFormatFlags != 0) {
+ return false;
+ }
+
+ // no format hint set, need to look at data
+ return true;
+}
+
+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()
+{
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS);
+
+ DeviceResetReason resetReason = DeviceResetReason::OK;
+ if (!DidRenderingDeviceReset(&resetReason)) {
+ return;
+ }
+
+ gfxCriticalNote << "(gfxWindowsPlatform) Detected device reset: " << (int)resetReason;
+
+ // Trigger an ::OnPaint for each window.
+ ::EnumThreadWindows(GetCurrentThreadId(),
+ InvalidateWindowForDeviceReset,
+ 0);
+
+ gfxCriticalNote << "(gfxWindowsPlatform) Finished device reset.";
+}
+
+void
+gfxWindowsPlatform::GetPlatformCMSOutputProfile(void* &mem, size_t &mem_size)
+{
+ WCHAR str[MAX_PATH];
+ DWORD size = MAX_PATH;
+ BOOL res;
+
+ mem = nullptr;
+ mem_size = 0;
+
+ HDC dc = GetDC(nullptr);
+ if (!dc)
+ return;
+
+ MOZ_SEH_TRY {
+ res = GetICMProfileW(dc, &size, (LPWSTR)&str);
+ } MOZ_SEH_EXCEPT(GetExceptionCode() == EXCEPTION_ILLEGAL_INSTRUCTION) {
+ res = FALSE;
+ }
+
+ ReleaseDC(nullptr, dc);
+ if (!res)
+ return;
+
+#ifdef _WIN32
+ qcms_data_from_unicode_path(str, &mem, &mem_size);
+
+#ifdef DEBUG_tor
+ if (mem_size > 0)
+ fprintf(stderr,
+ "ICM profile read from %s successfully\n",
+ NS_ConvertUTF16toUTF8(str).get());
+#endif // DEBUG_tor
+#endif // _WIN32
+}
+
+bool
+gfxWindowsPlatform::UseClearTypeForDownloadableFonts()
+{
+ if (mUseClearTypeForDownloadableFonts == UNINITIALIZED_VALUE) {
+ mUseClearTypeForDownloadableFonts = Preferences::GetBool(GFX_DOWNLOADABLE_FONTS_USE_CLEARTYPE, true);
+ }
+
+ return mUseClearTypeForDownloadableFonts;
+}
+
+bool
+gfxWindowsPlatform::UseClearTypeAlways()
+{
+ if (mUseClearTypeAlways == UNINITIALIZED_VALUE) {
+ mUseClearTypeAlways = Preferences::GetBool(GFX_USE_CLEARTYPE_ALWAYS, false);
+ }
+
+ return mUseClearTypeAlways;
+}
+
+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 ||
+ !versionInfo.AppendElements(uint32_t(versInfoSize)))
+ {
+ return;
+ }
+
+ 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, "%u.%u.%u.%u", vers[0], vers[1], vers[2], vers[3]);
+ aVersion.Assign(NS_ConvertUTF8toUTF16(buf));
+}
+
+void
+gfxWindowsPlatform::GetCleartypeParams(nsTArray<ClearTypeParameterInfo>& aParams)
+{
+ HKEY hKey, subKey;
+ DWORD i, rv, size, type;
+ WCHAR displayName[256], subkeyName[256];
+
+ aParams.Clear();
+
+ // construct subkeys based on HKLM subkeys, assume they are same for HKCU
+ rv = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
+ L"Software\\Microsoft\\Avalon.Graphics",
+ 0, KEY_READ, &hKey);
+
+ if (rv != ERROR_SUCCESS) {
+ return;
+ }
+
+ // enumerate over subkeys
+ for (i = 0, rv = ERROR_SUCCESS; rv != ERROR_NO_MORE_ITEMS; i++) {
+ size = ArrayLength(displayName);
+ rv = RegEnumKeyExW(hKey, i, displayName, &size,
+ nullptr, nullptr, nullptr, nullptr);
+ if (rv != ERROR_SUCCESS) {
+ continue;
+ }
+
+ ClearTypeParameterInfo ctinfo;
+ ctinfo.displayName.Assign(displayName);
+
+ DWORD subrv, value;
+ bool foundData = false;
+
+ swprintf_s(subkeyName, ArrayLength(subkeyName),
+ L"Software\\Microsoft\\Avalon.Graphics\\%s", displayName);
+
+ // subkey for gamma, pixel structure
+ subrv = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
+ subkeyName, 0, KEY_QUERY_VALUE, &subKey);
+
+ if (subrv == ERROR_SUCCESS) {
+ size = sizeof(value);
+ subrv = RegQueryValueExW(subKey, L"GammaLevel", nullptr, &type,
+ (LPBYTE)&value, &size);
+ if (subrv == ERROR_SUCCESS && type == REG_DWORD) {
+ foundData = true;
+ ctinfo.gamma = value;
+ }
+
+ size = sizeof(value);
+ subrv = RegQueryValueExW(subKey, L"PixelStructure", nullptr, &type,
+ (LPBYTE)&value, &size);
+ if (subrv == ERROR_SUCCESS && type == REG_DWORD) {
+ foundData = true;
+ ctinfo.pixelStructure = value;
+ }
+
+ RegCloseKey(subKey);
+ }
+
+ // subkey for cleartype level, enhanced contrast
+ subrv = RegOpenKeyExW(HKEY_CURRENT_USER,
+ subkeyName, 0, KEY_QUERY_VALUE, &subKey);
+
+ if (subrv == ERROR_SUCCESS) {
+ size = sizeof(value);
+ subrv = RegQueryValueExW(subKey, L"ClearTypeLevel", nullptr, &type,
+ (LPBYTE)&value, &size);
+ if (subrv == ERROR_SUCCESS && type == REG_DWORD) {
+ foundData = true;
+ ctinfo.clearTypeLevel = value;
+ }
+
+ size = sizeof(value);
+ subrv = RegQueryValueExW(subKey, L"EnhancedContrastLevel",
+ nullptr, &type, (LPBYTE)&value, &size);
+ if (subrv == ERROR_SUCCESS && type == REG_DWORD) {
+ foundData = true;
+ ctinfo.enhancedContrast = value;
+ }
+
+ RegCloseKey(subKey);
+ }
+
+ if (foundData) {
+ aParams.AppendElement(ctinfo);
+ }
+ }
+
+ RegCloseKey(hKey);
+}
+
+void
+gfxWindowsPlatform::FontsPrefsChanged(const char *aPref)
+{
+ bool clearTextFontCaches = true;
+
+ gfxPlatform::FontsPrefsChanged(aPref);
+
+ if (!aPref) {
+ mUseClearTypeForDownloadableFonts = UNINITIALIZED_VALUE;
+ mUseClearTypeAlways = UNINITIALIZED_VALUE;
+ } else if (!strcmp(GFX_DOWNLOADABLE_FONTS_USE_CLEARTYPE, aPref)) {
+ mUseClearTypeForDownloadableFonts = UNINITIALIZED_VALUE;
+ } else if (!strcmp(GFX_USE_CLEARTYPE_ALWAYS, aPref)) {
+ mUseClearTypeAlways = UNINITIALIZED_VALUE;
+ } else if (!strncmp(GFX_CLEARTYPE_PARAMS, aPref, strlen(GFX_CLEARTYPE_PARAMS))) {
+ SetupClearTypeParams();
+ } else {
+ clearTextFontCaches = false;
+ }
+
+ if (clearTextFontCaches) {
+ gfxFontCache *fc = gfxFontCache::GetCache();
+ if (fc) {
+ fc->Flush();
+ }
+ }
+}
+
+#define DISPLAY1_REGISTRY_KEY \
+ HKEY_CURRENT_USER, L"Software\\Microsoft\\Avalon.Graphics\\DISPLAY1"
+
+#define ENHANCED_CONTRAST_VALUE_NAME L"EnhancedContrastLevel"
+
+void
+gfxWindowsPlatform::SetupClearTypeParams()
+{
+ if (GetDWriteFactory()) {
+ // any missing prefs will default to invalid (-1) and be ignored;
+ // out-of-range values will also be ignored
+ FLOAT gamma = -1.0;
+ FLOAT contrast = -1.0;
+ FLOAT level = -1.0;
+ int geometry = -1;
+ int mode = -1;
+ int32_t value;
+ if (NS_SUCCEEDED(Preferences::GetInt(GFX_CLEARTYPE_PARAMS_GAMMA, &value))) {
+ if (value >= 1000 && value <= 2200) {
+ gamma = FLOAT(value / 1000.0);
+ }
+ }
+
+ if (NS_SUCCEEDED(Preferences::GetInt(GFX_CLEARTYPE_PARAMS_CONTRAST, &value))) {
+ if (value >= 0 && value <= 1000) {
+ contrast = FLOAT(value / 100.0);
+ }
+ }
+
+ if (NS_SUCCEEDED(Preferences::GetInt(GFX_CLEARTYPE_PARAMS_LEVEL, &value))) {
+ if (value >= 0 && value <= 100) {
+ level = FLOAT(value / 100.0);
+ }
+ }
+
+ if (NS_SUCCEEDED(Preferences::GetInt(GFX_CLEARTYPE_PARAMS_STRUCTURE, &value))) {
+ if (value >= 0 && value <= 2) {
+ geometry = value;
+ }
+ }
+
+ if (NS_SUCCEEDED(Preferences::GetInt(GFX_CLEARTYPE_PARAMS_MODE, &value))) {
+ if (value >= 0 && value <= 5) {
+ mode = value;
+ }
+ }
+
+ cairo_dwrite_set_cleartype_params(gamma, contrast, level, geometry, mode);
+
+ switch (mode) {
+ case DWRITE_RENDERING_MODE_ALIASED:
+ case DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC:
+ mMeasuringMode = DWRITE_MEASURING_MODE_GDI_CLASSIC;
+ break;
+ case DWRITE_RENDERING_MODE_CLEARTYPE_GDI_NATURAL:
+ mMeasuringMode = DWRITE_MEASURING_MODE_GDI_NATURAL;
+ break;
+ default:
+ mMeasuringMode = DWRITE_MEASURING_MODE_NATURAL;
+ break;
+ }
+
+ RefPtr<IDWriteRenderingParams> defaultRenderingParams;
+ GetDWriteFactory()->CreateRenderingParams(getter_AddRefs(defaultRenderingParams));
+ // For EnhancedContrast, we override the default if the user has not set it
+ // in the registry (by using the ClearType Tuner).
+ if (contrast < 0.0 || contrast > 10.0) {
+ 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) {
+ contrast = defaultRenderingParams->GetEnhancedContrast();
+ }
+ RegCloseKey(hKey);
+ }
+
+ if (contrast < 0.0 || contrast > 10.0) {
+ contrast = 1.0;
+ }
+ }
+
+ if (GetDefaultContentBackend() == BackendType::SKIA) {
+ // Skia doesn't support a contrast value outside of 0-1, so default to 1.0
+ if (contrast < 0.0 || contrast > 1.0) {
+ NS_WARNING("Custom dwrite contrast not supported in Skia. Defaulting to 1.0.");
+ contrast = 1.0;
+ }
+ }
+
+ // For parameters that have not been explicitly set,
+ // we copy values from default params (or our overridden value for contrast)
+ if (gamma < 1.0 || gamma > 2.2) {
+ gamma = defaultRenderingParams->GetGamma();
+ }
+
+ if (level < 0.0 || level > 1.0) {
+ level = defaultRenderingParams->GetClearTypeLevel();
+ }
+
+ DWRITE_PIXEL_GEOMETRY dwriteGeometry =
+ static_cast<DWRITE_PIXEL_GEOMETRY>(geometry);
+ DWRITE_RENDERING_MODE renderMode =
+ static_cast<DWRITE_RENDERING_MODE>(mode);
+
+ if (dwriteGeometry < DWRITE_PIXEL_GEOMETRY_FLAT ||
+ dwriteGeometry > DWRITE_PIXEL_GEOMETRY_BGR) {
+ dwriteGeometry = defaultRenderingParams->GetPixelGeometry();
+ }
+
+ if (renderMode < DWRITE_RENDERING_MODE_DEFAULT ||
+ renderMode > DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC) {
+ renderMode = defaultRenderingParams->GetRenderingMode();
+ }
+
+ mRenderingParams[TEXT_RENDERING_NO_CLEARTYPE] = defaultRenderingParams;
+
+ HRESULT hr = GetDWriteFactory()->CreateCustomRenderingParams(
+ gamma, contrast, level, dwriteGeometry, renderMode,
+ getter_AddRefs(mRenderingParams[TEXT_RENDERING_NORMAL]));
+ if (FAILED(hr) || !mRenderingParams[TEXT_RENDERING_NORMAL]) {
+ mRenderingParams[TEXT_RENDERING_NORMAL] = defaultRenderingParams;
+ }
+
+ hr = GetDWriteFactory()->CreateCustomRenderingParams(
+ gamma, contrast, level,
+ dwriteGeometry, DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC,
+ getter_AddRefs(mRenderingParams[TEXT_RENDERING_GDI_CLASSIC]));
+ if (FAILED(hr) || !mRenderingParams[TEXT_RENDERING_GDI_CLASSIC]) {
+ mRenderingParams[TEXT_RENDERING_GDI_CLASSIC] =
+ defaultRenderingParams;
+ }
+ }
+}
+
+ReadbackManagerD3D11*
+gfxWindowsPlatform::GetReadbackManager()
+{
+ if (!mD3D11ReadbackManager) {
+ mD3D11ReadbackManager = new ReadbackManagerD3D11();
+ }
+
+ return mD3D11ReadbackManager;
+}
+
+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",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D3D11_DISABLED"));
+ return;
+ }
+
+ d3d11ANGLE.EnableByDefault();
+
+ nsCString message;
+ nsCString failureId;
+ if (!gfxPlatform::IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE, &message,
+ failureId)) {
+ d3d11ANGLE.Disable(FeatureStatus::Blacklisted, 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.
+ InitializeD3D9Config();
+ InitializeD3D11Config();
+ InitializeANGLEConfig();
+ InitializeD2DConfig();
+ } else {
+ FetchAndImportContentDeviceData();
+ InitializeANGLEConfig();
+ }
+}
+
+void
+gfxWindowsPlatform::InitializeD3D9Config()
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ FeatureState& d3d9 = gfxConfig::GetFeature(Feature::D3D9_COMPOSITING);
+
+ if (!gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) {
+ d3d9.DisableByDefault(FeatureStatus::Unavailable, "Hardware compositing is disabled",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D3D9_NEED_HWCOMP"));
+ return;
+ }
+
+ if (!IsVistaOrLater()) {
+ d3d9.EnableByDefault();
+ } else {
+ d3d9.SetDefaultFromPref(
+ gfxPrefs::GetLayersAllowD3D9FallbackPrefName(),
+ true,
+ gfxPrefs::GetLayersAllowD3D9FallbackPrefDefault());
+
+ if (!d3d9.IsEnabled() && gfxPrefs::LayersPreferD3D9()) {
+ d3d9.UserEnable("Direct3D9 enabled via layers.prefer-d3d9");
+ }
+ }
+
+ nsCString message;
+ nsCString failureId;
+ if (!gfxPlatform::IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, &message,
+ failureId)) {
+ d3d9.Disable(FeatureStatus::Blacklisted, message.get(), failureId);
+ }
+
+ if (gfxConfig::IsForcedOnByUser(Feature::HW_COMPOSITING)) {
+ d3d9.UserForceEnable("Hardware compositing is force-enabled");
+ }
+}
+
+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",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D3D11_NEED_HWCOMP"));
+ return;
+ }
+
+ d3d11.EnableByDefault();
+
+ if (!IsWin8OrLater() &&
+ !DeviceManagerDx::Get()->CheckRemotePresentSupport()) {
+ nsCOMPtr<nsIGfxInfo> gfxInfo;
+ gfxInfo = services::GetGfxInfo();
+ nsAutoString adaptorId;
+ gfxInfo->GetAdapterDeviceID(adaptorId);
+ // Blacklist 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")) {
+ d3d11.Disable(FeatureStatus::Blacklisted, "Blacklisted, see bug 1351349",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_BUG_1351349"));
+ }
+ }
+
+ // If the user prefers D3D9, act as though they disabled D3D11.
+ if (gfxPrefs::LayersPreferD3D9()) {
+ d3d11.UserDisable("Disabled due to user preference for Direct3D 9",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D3D11_PREF"));
+ return;
+ }
+
+ nsCString message;
+ nsCString failureId;
+ if (!gfxPlatform::IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, &message, failureId)) {
+ d3d11.Disable(FeatureStatus::Blacklisted, message.get(), failureId);
+ }
+
+ // Check if the user really, really wants WARP.
+ if (gfxPrefs::LayersD3D11ForceWARP()) {
+ // Force D3D11 on even if we disabled it.
+ d3d11.UserForceEnable("User force-enabled WARP");
+ }
+}
+
+/* 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::InitializeDevices()
+{
+ MOZ_ASSERT(!InSafeMode());
+
+ 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 (InitGPUProcessSupport()) {
+ 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 blacklisted for D3D11 layers. This first attempt will try to create
+ // a hardware accelerated device. If this creation fails or the hardware is
+ // blacklisted, then this function will abort if WARP is disabled, causing us
+ // to fallback to D3D9 or 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",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D2D_D3D11_COMP"));
+ return;
+ }
+ if (!IsVistaOrLater()) {
+ d2d1.DisableByDefault(FeatureStatus::Unavailable, "Direct2D is not available on Windows XP",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D2D_XP"));
+ return;
+ }
+
+ d2d1.SetDefaultFromPref(
+ gfxPrefs::GetDirect2DDisabledPrefName(),
+ false,
+ gfxPrefs::GetDirect2DDisabledPrefDefault());
+
+ nsCString message;
+ nsCString failureId;
+ if (!gfxPlatform::IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_DIRECT2D, &message, failureId)) {
+ d2d1.Disable(FeatureStatus::Blacklisted, message.get(), failureId);
+ }
+
+ if (!d2d1.IsEnabled() && gfxPrefs::Direct2DForceEnabled()) {
+ 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",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D2D_WARP_BLOCK"));
+ }
+
+ // 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",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D2D_FACTORY"));
+ return;
+ }
+
+ if (!dm->GetContentDevice()) {
+ d2d1.SetFailed(FeatureStatus::Failed, "Failed to acquire a Direct3D 11 content device",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D2D_DEVICE"));
+ return;
+ }
+
+ if (!dm->TextureSharingWorks()) {
+ d2d1.SetFailed(FeatureStatus::Failed, "Direct3D11 device does not support texture sharing",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D2D_TXT_SHARING"));
+ return;
+ }
+
+ // Using Direct2D depends on DWrite support.
+ if (!mDWriteFactory && !InitDWriteSupport()) {
+ d2d1.SetFailed(FeatureStatus::Failed, "Failed to initialize DirectWrite support",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D2D_DWRITE"));
+ 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",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D2D_CREATE_FAILED"));
+ return;
+ }
+
+ MOZ_ASSERT(d2d1.IsEnabled());
+ d2d1_1.SetSuccessful();
+}
+
+bool
+gfxWindowsPlatform::InitGPUProcessSupport()
+{
+ FeatureState& gpuProc = gfxConfig::GetFeature(Feature::GPU_PROCESS);
+
+ if (!gpuProc.IsEnabled()) {
+ return false;
+ }
+
+ if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
+ // Don't use the GPU process if not using D3D11.
+ gpuProc.Disable(
+ FeatureStatus::Unavailable,
+ "Not using GPU Process since D3D11 is unavailable",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_NO_D3D11"));
+ } else if (!IsWin7SP1OrLater()) {
+ // For Windows XP, we simply don't care enough to support this
+ // configuration. On Windows Vista and 7 Pre-SP1, DXGI 1.2 is not
+ // available and remote presentation for D3D11 will not work. Rather
+ // than take a regression and use D3D9, we revert back to in-process
+ // rendering.
+ gpuProc.Disable(
+ FeatureStatus::Unavailable,
+ "Windows XP, Vista, and 7 Pre-SP1 cannot use the GPU process",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_OLD_WINDOWS"));
+ } 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",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_PLATFORM_UPDATE"));
+ } 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.
+ return gpuProc.IsEnabled();
+}
+
+bool
+gfxWindowsPlatform::DwmCompositionEnabled()
+{
+ if (!IsVistaOrLater()) {
+ return false;
+ }
+
+ MOZ_ASSERT(WinUtils::dwmIsCompositionEnabledPtr);
+ BOOL dwmEnabled = false;
+
+ if (FAILED(WinUtils::dwmIsCompositionEnabledPtr(&dwmEnabled))) {
+ return false;
+ }
+
+ return dwmEnabled;
+}
+
+class D3DVsyncSource final : public VsyncSource
+{
+public:
+
+ class D3DVsyncDisplay final : public VsyncSource::Display
+ {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(D3DVsyncDisplay)
+ public:
+ D3DVsyncDisplay()
+ : mPrevVsync(TimeStamp::Now())
+ , mVsyncEnabledLock("D3DVsyncEnabledLock")
+ , mVsyncEnabled(false)
+ {
+ mVsyncThread = new base::Thread("WindowsVsyncThread");
+ MOZ_RELEASE_ASSERT(mVsyncThread->Start(), "GFX: Could not start Windows vsync thread");
+ SetVsyncRate();
+ }
+
+ 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 = WinUtils::dwmGetCompositionTimingInfoPtr(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
+ MonitorAutoLock lock(mVsyncEnabledLock);
+ if (mVsyncEnabled) {
+ return;
+ }
+ mVsyncEnabled = true;
+ }
+
+ mVsyncThread->message_loop()->PostTask(
+ NewRunnableMethod(this, &D3DVsyncDisplay::VBlankLoop));
+ }
+
+ virtual void DisableVsync() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mVsyncThread->IsRunning());
+ MonitorAutoLock lock(mVsyncEnabledLock);
+ if (!mVsyncEnabled) {
+ return;
+ }
+ mVsyncEnabled = false;
+ }
+
+ virtual bool IsVsyncEnabled() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MonitorAutoLock lock(mVsyncEnabledLock);
+ 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(this, &D3DVsyncDisplay::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 = WinUtils::dwmGetCompositionTimingInfoPtr(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
+ MonitorAutoLock lock(mVsyncEnabledLock);
+ 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());
+ Display::NotifyVsync(vsync);
+
+ // 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;
+ }
+
+ // Using WaitForVBlank, the whole system dies because WaitForVBlank
+ // only works if it's run on the same thread as the Present();
+ HRESULT hr = WinUtils::dwmFlushProcPtr();
+ 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
+ }
+
+ private:
+ virtual ~D3DVsyncDisplay()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ bool IsInVsyncThread()
+ {
+ return mVsyncThread->thread_id() == PlatformThread::CurrentId();
+ }
+
+ TimeStamp mPrevVsync;
+ Monitor mVsyncEnabledLock;
+ base::Thread* mVsyncThread;
+ TimeDuration mVsyncRate;
+ bool mVsyncEnabled;
+ }; // end d3dvsyncdisplay
+
+ D3DVsyncSource()
+ {
+ mPrimaryDisplay = new D3DVsyncDisplay();
+ }
+
+ virtual Display& GetGlobalDisplay() override
+ {
+ return *mPrimaryDisplay;
+ }
+
+private:
+ virtual ~D3DVsyncSource()
+ {
+ }
+ RefPtr<D3DVsyncDisplay> mPrimaryDisplay;
+}; // end D3DVsyncSource
+
+already_AddRefed<mozilla::gfx::VsyncSource>
+gfxWindowsPlatform::CreateHardwareVsyncSource()
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread(), "GFX: Not in main thread.");
+ if (!WinUtils::dwmIsCompositionEnabledPtr) {
+ NS_WARNING("Dwm composition not available, falling back to software vsync");
+ return gfxPlatform::CreateHardwareVsyncSource();
+ }
+
+ BOOL dwmEnabled = false;
+ WinUtils::dwmIsCompositionEnabledPtr(&dwmEnabled);
+ if (!dwmEnabled) {
+ NS_WARNING("DWM not enabled, falling back to software vsync");
+ return gfxPlatform::CreateHardwareVsyncSource();
+ }
+
+ RefPtr<VsyncSource> d3dVsyncSource = new D3DVsyncSource();
+ return d3dVsyncSource.forget();
+}
+
+bool
+gfxWindowsPlatform::SupportsApzTouchInput() const
+{
+ int value = gfxPrefs::TouchEventsEnabled();
+ return value == 1 || value == 2;
+}
+
+void
+gfxWindowsPlatform::GetAcceleratedCompositorBackends(nsTArray<LayersBackend>& aBackends)
+{
+ if (gfxConfig::IsEnabled(Feature::OPENGL_COMPOSITING) && gfxPrefs::LayersPreferOpenGL()) {
+ aBackends.AppendElement(LayersBackend::LAYERS_OPENGL);
+ }
+
+ if (gfxConfig::IsEnabled(Feature::D3D9_COMPOSITING) && gfxPrefs::LayersPreferD3D9()) {
+ aBackends.AppendElement(LayersBackend::LAYERS_D3D9);
+ }
+
+ if (gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
+ aBackends.AppendElement(LayersBackend::LAYERS_D3D11);
+ }
+
+ if (gfxConfig::IsEnabled(Feature::D3D9_COMPOSITING) && !gfxPrefs::LayersPreferD3D9()) {
+ aBackends.AppendElement(LayersBackend::LAYERS_D3D9);
+ }
+}
+
+void
+gfxWindowsPlatform::ImportGPUDeviceData(const mozilla::gfx::GPUDeviceData& aData)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ gfxPlatform::ImportGPUDeviceData(aData);
+
+ gfxConfig::ImportChange(Feature::D3D11_COMPOSITING, aData.d3d11Compositing());
+ gfxConfig::ImportChange(Feature::D3D9_COMPOSITING, aData.d3d9Compositing());
+
+ DeviceManagerDx* dm = DeviceManagerDx::Get();
+ if (gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
+ dm->ImportDeviceInfo(aData.gpuDevice().get_D3D11DeviceStatus());
+ } 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",
+ NS_LITERAL_CSTRING("FEATURE_FAILURE_D2D_D3D11_COMP"));
+ }
+ }
+
+ // 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::D3D9_COMPOSITING, prefs.d3d9Compositing());
+ gfxConfig::Inherit(Feature::DIRECT2D, prefs.useD2D1());
+
+ if (gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
+ DeviceManagerDx* dm = DeviceManagerDx::Get();
+ dm->ImportDeviceInfo(aData.d3d11());
+ }
+}
+
+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().d3d9Compositing() = gfxConfig::GetValue(Feature::D3D9_COMPOSITING);
+ aOut->prefs().useD2D1() = gfxConfig::GetValue(Feature::DIRECT2D);
+
+ if (d3d11.IsEnabled()) {
+ DeviceManagerDx* dm = DeviceManagerDx::Get();
+ dm->ExportDeviceInfo(&aOut->d3d11());
+ }
+}
+
+bool
+gfxWindowsPlatform::SupportsPluginDirectDXGIDrawing()
+{
+ DeviceManagerDx* dm = DeviceManagerDx::Get();
+ if (!dm->GetContentDevice() || !dm->TextureSharingWorks()) {
+ return false;
+ }
+ return true;
+}
diff --git a/gfx/thebes/gfxWindowsPlatform.h b/gfx/thebes/gfxWindowsPlatform.h
new file mode 100644
index 000000000..8db7cf575
--- /dev/null
+++ b/gfx/thebes/gfxWindowsPlatform.h
@@ -0,0 +1,281 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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
+
+
+/**
+ * XXX to get CAIRO_HAS_D2D_SURFACE, CAIRO_HAS_DWRITE_FONT
+ * and cairo_win32_scaled_font_select_font
+ */
+#include "cairo-win32.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 "nsDataHashtable.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 layers {
+class DeviceManagerD3D9;
+class ReadbackManagerD3D11;
+}
+}
+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 DCFromDrawTarget final
+{
+public:
+ DCFromDrawTarget(mozilla::gfx::DrawTarget& aDrawTarget);
+
+ ~DCFromDrawTarget() {
+ if (mNeedsRelease) {
+ ReleaseDC(nullptr, mDC);
+ } else {
+ RestoreDC(mDC, -1);
+ }
+ }
+
+ operator HDC () {
+ return mDC;
+ }
+
+private:
+ HDC mDC;
+ bool mNeedsRelease;
+};
+
+// 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 : 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();
+ }
+
+ virtual gfxPlatformFontList* CreatePlatformFontList() override;
+
+ virtual already_AddRefed<gfxASurface>
+ CreateOffscreenSurface(const IntSize& aSize,
+ gfxImageFormat aFormat) override;
+
+ virtual already_AddRefed<mozilla::gfx::ScaledFont>
+ GetScaledFontForFont(mozilla::gfx::DrawTarget* aTarget, gfxFont *aFont) 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);
+
+ virtual void GetCommonFallbackFonts(uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript,
+ nsTArray<const char*>& aFontList) override;
+
+ gfxFontGroup*
+ CreateFontGroup(const mozilla::FontFamilyList& aFontFamilyList,
+ const gfxFontStyle *aStyle,
+ gfxTextPerfMetrics* aTextPerf,
+ gfxUserFontSet *aUserFontSet,
+ gfxFloat aDevToCssSize) override;
+
+ virtual bool CanUseHardwareVideoDecoding() override;
+
+ /**
+ * Check whether format is supported on a platform or not (if unclear, returns true)
+ */
+ virtual bool IsFontFormatSupported(nsIURI *aFontURI, uint32_t aFormatFlags) override;
+
+ virtual void CompositorUpdated() override;
+
+ bool DidRenderingDeviceReset(DeviceResetReason* aResetReason = nullptr) override;
+ void SchedulePaintIfDeviceReset() override;
+
+ mozilla::gfx::BackendType GetContentBackendFor(mozilla::layers::LayersBackend aLayers) override;
+
+ // ClearType is not always enabled even when available (e.g. Windows XP)
+ // if either of these prefs are enabled and apply, use ClearType rendering
+ bool UseClearTypeForDownloadableFonts();
+ bool UseClearTypeAlways();
+
+ static void GetDLLVersion(char16ptr_t aDLLPath, nsAString& aVersion);
+
+ // returns ClearType tuning information for each display
+ static void GetCleartypeParams(nsTArray<ClearTypeParameterInfo>& aParams);
+
+ virtual void FontsPrefsChanged(const char *aPref) override;
+
+ void SetupClearTypeParams();
+
+ IDWriteFactory *GetDWriteFactory() { return mDWriteFactory; }
+ inline bool DWriteEnabled() { return !!mDWriteFactory; }
+ inline DWRITE_MEASURING_MODE DWriteMeasuringMode() { return mMeasuringMode; }
+
+ IDWriteRenderingParams *GetRenderingParams(TextRenderingMode aRenderMode)
+ { return mRenderingParams[aRenderMode]; }
+
+public:
+ bool DwmCompositionEnabled();
+
+ mozilla::layers::ReadbackManagerD3D11* GetReadbackManager();
+
+ static bool IsOptimus();
+
+ bool SupportsApzWheelInput() const override {
+ return true;
+ }
+ bool SupportsApzTouchInput() const override;
+
+ // Recreate devices as needed for a device reset. Returns true if a device
+ // reset occurred.
+ bool HandleDeviceReset();
+ void UpdateBackendPrefs();
+
+ virtual already_AddRefed<mozilla::gfx::VsyncSource> CreateHardwareVsyncSource() override;
+ static mozilla::Atomic<size_t> sD3D11SharedTextures;
+ static mozilla::Atomic<size_t> sD3D9SharedTextures;
+
+ bool SupportsPluginDirectBitmapDrawing() override {
+ return true;
+ }
+ bool SupportsPluginDirectDXGIDrawing();
+
+ static void RecordContentDeviceFailure(mozilla::gfx::TelemetryDeviceCode aDevice);
+
+protected:
+ bool AccelerateLayersByDefault() override {
+ return true;
+ }
+ void GetAcceleratedCompositorBackends(nsTArray<mozilla::layers::LayersBackend>& aBackends) override;
+ virtual void GetPlatformCMSOutputProfile(void* &mem, size_t &size) override;
+
+ void ImportGPUDeviceData(const mozilla::gfx::GPUDeviceData& aData) override;
+ void ImportContentDeviceData(const mozilla::gfx::ContentDeviceData& aData) override;
+ void BuildContentDeviceData(mozilla::gfx::ContentDeviceData* aOut) override;
+
+protected:
+ RenderMode mRenderMode;
+
+ int8_t mUseClearTypeForDownloadableFonts;
+ int8_t mUseClearTypeAlways;
+
+private:
+ void Init();
+ void InitAcceleration() override;
+
+ void InitializeDevices();
+ void InitializeD3D11();
+ void InitializeD2D();
+ bool InitDWriteSupport();
+ bool InitGPUProcessSupport();
+
+ void DisableD2D(mozilla::gfx::FeatureStatus aStatus, const char* aMessage,
+ const nsACString& aFailureId);
+
+ void InitializeConfig();
+ void InitializeD3D9Config();
+ void InitializeD3D11Config();
+ void InitializeD2DConfig();
+ void InitializeDirectDrawConfig();
+
+ RefPtr<IDWriteFactory> mDWriteFactory;
+ RefPtr<IDWriteRenderingParams> mRenderingParams[TEXT_RENDERING_COUNT];
+ DWRITE_MEASURING_MODE mMeasuringMode;
+
+ RefPtr<mozilla::layers::ReadbackManagerD3D11> mD3D11ReadbackManager;
+
+ nsTArray<D3D_FEATURE_LEVEL> mFeatureLevels;
+};
+
+#endif /* GFX_WINDOWS_PLATFORM_H */
diff --git a/gfx/thebes/gfxWindowsSurface.cpp b/gfx/thebes/gfxWindowsSurface.cpp
new file mode 100644
index 000000000..e2d90883b
--- /dev/null
+++ b/gfx/thebes/gfxWindowsSurface.cpp
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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);
+}
+
+gfxWindowsSurface::gfxWindowsSurface(IDirect3DSurface9 *surface, uint32_t flags) :
+ mOwnsDC(false), mDC(0), mWnd(nullptr)
+{
+ cairo_surface_t *surf = cairo_win32_surface_create_with_d3dsurface9(surface);
+ Init(surf);
+}
+
+
+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_alpha(mDC));
+ } else {
+ Init(cairo_win32_surface_create(mDC));
+ }
+}
+
+already_AddRefed<gfxASurface>
+gfxWindowsSurface::CreateSimilarSurface(gfxContentType aContent,
+ const mozilla::gfx::IntSize& aSize)
+{
+ if (!mSurface || !mSurfaceValid) {
+ return nullptr;
+ }
+
+ cairo_surface_t *surface;
+ if (GetContentType() == gfxContentType::COLOR_ALPHA) {
+ // When creating a similar surface to a transparent surface, ensure
+ // the new surface uses a DIB. cairo_surface_create_similar won't
+ // use a DIB for a gfxContentType::COLOR surface if this surface doesn't
+ // have a DIB (e.g. if we're a transparent window surface). But
+ // we need a DIB to perform well if the new surface is composited into
+ // a surface that's the result of create_similar(gfxContentType::COLOR_ALPHA)
+ // (e.g. a backbuffer for the window) --- that new surface *would*
+ // have a DIB.
+ gfxImageFormat gformat =
+ gfxPlatform::GetPlatform()->OptimalFormatForContent(aContent);
+ cairo_format_t cformat = GfxFormatToCairoFormat(gformat);
+ surface = cairo_win32_surface_create_with_dib(cformat, aSize.width,
+ aSize.height);
+ } else {
+ surface =
+ cairo_surface_create_similar(mSurface, (cairo_content_t)(int)aContent,
+ aSize.width, aSize.height);
+ }
+
+ if (cairo_surface_status(surface)) {
+ cairo_surface_destroy(surface);
+ return nullptr;
+ }
+
+ RefPtr<gfxASurface> result = Wrap(surface, aSize);
+ cairo_surface_destroy(surface);
+ return result.forget();
+}
+
+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!");
+
+ return mozilla::gfx::IntSize(cairo_win32_surface_get_width(mSurface),
+ cairo_win32_surface_get_height(mSurface));
+}
diff --git a/gfx/thebes/gfxWindowsSurface.h b/gfx/thebes/gfxWindowsSurface.h
new file mode 100644
index 000000000..594357a26
--- /dev/null
+++ b/gfx/thebes/gfxWindowsSurface.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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)
+ };
+
+ gfxWindowsSurface(HDC dc, uint32_t flags = 0);
+
+ // Create from a shared d3d9surface
+ gfxWindowsSurface(IDirect3DSurface9 *surface, uint32_t flags = 0);
+
+ // Create a DIB surface
+ gfxWindowsSurface(const mozilla::gfx::IntSize& size,
+ gfxImageFormat imageFormat = mozilla::gfx::SurfaceFormat::X8R8G8B8_UINT32);
+
+ gfxWindowsSurface(cairo_surface_t *csurf);
+
+ virtual already_AddRefed<gfxASurface> CreateSimilarSurface(gfxContentType aType,
+ const mozilla::gfx::IntSize& aSize);
+
+ 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/gfxXlibNativeRenderer.cpp b/gfx/thebes/gfxXlibNativeRenderer.cpp
new file mode 100644
index 000000000..19213b4e7
--- /dev/null
+++ b/gfx/thebes/gfxXlibNativeRenderer.cpp
@@ -0,0 +1,620 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxXlibNativeRenderer.h"
+
+#include "gfxXlibSurface.h"
+#include "gfxImageSurface.h"
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+#include "gfxAlphaRecovery.h"
+#include "cairo-xlib.h"
+#include "cairo-xlib-xrender.h"
+#include "mozilla/gfx/BorrowedContext.h"
+#include "mozilla/gfx/HelpersCairo.h"
+#include "gfx2DGlue.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+#if 0
+#include <stdio.h>
+#define NATIVE_DRAWING_NOTE(m) fprintf(stderr, m)
+#else
+#define NATIVE_DRAWING_NOTE(m) do {} while (0)
+#endif
+
+/* We have four basic strategies available:
+
+ 1) 'direct': If the target is an xlib surface, and other conditions are met,
+ we can pass the underlying drawable directly to the callback.
+
+ 2) 'simple': If the drawing is opaque, or we can draw to a surface with an
+ alpha channel, then we can create a temporary xlib surface, pass its
+ underlying drawable to the callback, and composite the result using
+ cairo.
+
+ 3) 'copy-background': If the drawing is not opaque but the target is
+ opaque, and we can draw to a surface with format such that pixel
+ conversion to and from the target format is exact, we can create a
+ temporary xlib surface, copy the background from the target, pass the
+ underlying drawable to the callback, and copy back to the target.
+
+ This strategy is not used if the pixel format conversion is not exact,
+ because that would mean that drawing intended to be very transparent
+ messes with other content.
+
+ The strategy is prefered over simple for non-opaque drawing and opaque
+ targets on the same screen as compositing without alpha is a simpler
+ operation.
+
+ 4) 'alpha-extraction': create a temporary xlib surface, fill with black,
+ pass its underlying drawable to the callback, copy the results to a
+ cairo image surface, repeat with a white background, update the on-black
+ image alpha values by comparing the two images, then paint the on-black
+ image using cairo.
+
+ Sure would be nice to have an X extension or GL to do this for us on the
+ server...
+*/
+
+static cairo_bool_t
+_convert_coord_to_int (double coord, int32_t *v)
+{
+ *v = (int32_t)coord;
+ /* XXX allow some tolerance here? */
+ return *v == coord;
+}
+
+static bool
+_get_rectangular_clip (cairo_t *cr,
+ const IntRect& bounds,
+ bool *need_clip,
+ IntRect *rectangles, int max_rectangles,
+ int *num_rectangles)
+{
+ cairo_rectangle_list_t *cliplist;
+ cairo_rectangle_t *clips;
+ int i;
+ bool retval = true;
+
+ cliplist = cairo_copy_clip_rectangle_list (cr);
+ if (cliplist->status != CAIRO_STATUS_SUCCESS) {
+ retval = false;
+ NATIVE_DRAWING_NOTE("FALLBACK: non-rectangular clip");
+ goto FINISH;
+ }
+
+ /* the clip is always in surface backend coordinates (i.e. native backend coords) */
+ clips = cliplist->rectangles;
+
+ for (i = 0; i < cliplist->num_rectangles; ++i) {
+
+ IntRect rect;
+ if (!_convert_coord_to_int (clips[i].x, &rect.x) ||
+ !_convert_coord_to_int (clips[i].y, &rect.y) ||
+ !_convert_coord_to_int (clips[i].width, &rect.width) ||
+ !_convert_coord_to_int (clips[i].height, &rect.height))
+ {
+ retval = false;
+ NATIVE_DRAWING_NOTE("FALLBACK: non-integer clip");
+ goto FINISH;
+ }
+
+ if (rect.IsEqualInterior(bounds)) {
+ /* the bounds are entirely inside the clip region so we don't need to clip. */
+ *need_clip = false;
+ goto FINISH;
+ }
+
+ NS_ASSERTION(bounds.Contains(rect),
+ "Was expecting to be clipped to bounds");
+
+ if (i >= max_rectangles) {
+ retval = false;
+ NATIVE_DRAWING_NOTE("FALLBACK: unsupported clip rectangle count");
+ goto FINISH;
+ }
+
+ rectangles[i] = rect;
+ }
+
+ *need_clip = true;
+ *num_rectangles = cliplist->num_rectangles;
+
+FINISH:
+ cairo_rectangle_list_destroy (cliplist);
+
+ return retval;
+}
+
+#define MAX_STATIC_CLIP_RECTANGLES 50
+
+/**
+ * Try the direct path.
+ * @return True if we took the direct path
+ */
+bool
+gfxXlibNativeRenderer::DrawDirect(DrawTarget* aDT, IntSize size,
+ uint32_t flags,
+ Screen *screen, Visual *visual)
+{
+ // We need to actually borrow the context because we want to read out the
+ // clip rectangles.
+ BorrowedCairoContext borrowed(aDT);
+ if (!borrowed.mCairo) {
+ return false;
+ }
+
+ bool direct = DrawCairo(borrowed.mCairo, size, flags, screen, visual);
+ borrowed.Finish();
+
+ return direct;
+}
+
+bool
+gfxXlibNativeRenderer::DrawCairo(cairo_t* cr, IntSize size,
+ uint32_t flags,
+ Screen *screen, Visual *visual)
+{
+ /* Check that the target surface is an xlib surface. */
+ cairo_surface_t *target = cairo_get_group_target (cr);
+ if (cairo_surface_get_type (target) != CAIRO_SURFACE_TYPE_XLIB) {
+ NATIVE_DRAWING_NOTE("FALLBACK: non-X surface");
+ return false;
+ }
+
+ cairo_matrix_t matrix;
+ cairo_get_matrix (cr, &matrix);
+ double device_offset_x, device_offset_y;
+ cairo_surface_get_device_offset (target, &device_offset_x, &device_offset_y);
+
+ /* Draw() checked that the matrix contained only a very-close-to-integer
+ translation. Here (and in several other places and thebes) device
+ offsets are assumed to be integer. */
+ NS_ASSERTION(int32_t(device_offset_x) == device_offset_x &&
+ int32_t(device_offset_y) == device_offset_y,
+ "Expected integer device offsets");
+ IntPoint offset(NS_lroundf(matrix.x0 + device_offset_x),
+ NS_lroundf(matrix.y0 + device_offset_y));
+
+ int max_rectangles = 0;
+ if (flags & DRAW_SUPPORTS_CLIP_RECT) {
+ max_rectangles = 1;
+ }
+ if (flags & DRAW_SUPPORTS_CLIP_LIST) {
+ max_rectangles = MAX_STATIC_CLIP_RECTANGLES;
+ }
+
+ /* The client won't draw outside the surface so consider this when
+ analysing clip rectangles. */
+ IntRect bounds(offset, size);
+ bounds.IntersectRect(bounds,
+ IntRect(0, 0,
+ cairo_xlib_surface_get_width(target),
+ cairo_xlib_surface_get_height(target)));
+
+ bool needs_clip = true;
+ IntRect rectangles[MAX_STATIC_CLIP_RECTANGLES];
+ int rect_count = 0;
+
+ /* Check that the clip is rectangular and aligned on unit boundaries. */
+ /* Temporarily set the matrix for _get_rectangular_clip. It's basically
+ the identity matrix, but we must adjust for the fact that our
+ offset-rect is in device coordinates. */
+ cairo_identity_matrix (cr);
+ cairo_translate (cr, -device_offset_x, -device_offset_y);
+ bool have_rectangular_clip =
+ _get_rectangular_clip (cr, bounds, &needs_clip,
+ rectangles, max_rectangles, &rect_count);
+ cairo_set_matrix (cr, &matrix);
+ if (!have_rectangular_clip)
+ return false;
+
+ /* Stop now if everything is clipped out */
+ if (needs_clip && rect_count == 0)
+ return true;
+
+ /* Check that the screen is supported.
+ Visuals belong to screens, so, if alternate visuals are not supported,
+ then alternate screens cannot be supported. */
+ bool supports_alternate_visual =
+ (flags & DRAW_SUPPORTS_ALTERNATE_VISUAL) != 0;
+ bool supports_alternate_screen = supports_alternate_visual &&
+ (flags & DRAW_SUPPORTS_ALTERNATE_SCREEN);
+ if (!supports_alternate_screen &&
+ cairo_xlib_surface_get_screen (target) != screen) {
+ NATIVE_DRAWING_NOTE("FALLBACK: non-default screen");
+ return false;
+ }
+
+ /* Check that there is a visual */
+ Visual *target_visual = cairo_xlib_surface_get_visual (target);
+ if (!target_visual) {
+ NATIVE_DRAWING_NOTE("FALLBACK: no Visual for surface");
+ return false;
+ }
+ /* Check that the visual is supported */
+ if (!supports_alternate_visual && target_visual != visual) {
+ // Only the format of the visual is important (not the GLX properties)
+ // for Xlib or XRender drawing.
+ XRenderPictFormat *target_format =
+ cairo_xlib_surface_get_xrender_format (target);
+ if (!target_format ||
+ (target_format !=
+ XRenderFindVisualFormat (DisplayOfScreen(screen), visual))) {
+ NATIVE_DRAWING_NOTE("FALLBACK: unsupported Visual");
+ return false;
+ }
+ }
+
+ /* we're good to go! */
+ NATIVE_DRAWING_NOTE("TAKING FAST PATH\n");
+ cairo_surface_flush (target);
+ nsresult rv = DrawWithXlib(target,
+ offset, rectangles,
+ needs_clip ? rect_count : 0);
+ if (NS_SUCCEEDED(rv)) {
+ cairo_surface_mark_dirty (target);
+ return true;
+ }
+ return false;
+}
+
+static bool
+VisualHasAlpha(Screen *screen, Visual *visual) {
+ // There may be some other visuals format with alpha but usually this is
+ // the only one we care about.
+ return visual->c_class == TrueColor &&
+ visual->bits_per_rgb == 8 &&
+ visual->red_mask == 0xff0000 &&
+ visual->green_mask == 0xff00 &&
+ visual->blue_mask == 0xff &&
+ gfxXlibSurface::DepthOfVisual(screen, visual) == 32;
+}
+
+// Returns whether pixel conversion between visual and format is exact (in
+// both directions).
+static bool
+FormatConversionIsExact(Screen *screen, Visual *visual, XRenderPictFormat *format) {
+ if (!format ||
+ visual->c_class != TrueColor ||
+ format->type != PictTypeDirect ||
+ gfxXlibSurface::DepthOfVisual(screen, visual) != format->depth)
+ return false;
+
+ XRenderPictFormat *visualFormat =
+ XRenderFindVisualFormat(DisplayOfScreen(screen), visual);
+
+ if (visualFormat->type != PictTypeDirect )
+ return false;
+
+ const XRenderDirectFormat& a = visualFormat->direct;
+ const XRenderDirectFormat& b = format->direct;
+ return a.redMask == b.redMask &&
+ a.greenMask == b.greenMask &&
+ a.blueMask == b.blueMask;
+}
+
+// The 3 non-direct strategies described above.
+// The surface format and strategy are inter-dependent.
+enum DrawingMethod {
+ eSimple,
+ eCopyBackground,
+ eAlphaExtraction
+};
+
+static cairo_surface_t*
+CreateTempXlibSurface (cairo_surface_t* cairoTarget,
+ DrawTarget* drawTarget,
+ IntSize size,
+ bool canDrawOverBackground,
+ uint32_t flags, Screen *screen, Visual *visual,
+ DrawingMethod *method)
+{
+ NS_ASSERTION(cairoTarget || drawTarget, "Must have some type");
+
+ bool drawIsOpaque = (flags & gfxXlibNativeRenderer::DRAW_IS_OPAQUE) != 0;
+ bool supportsAlternateVisual =
+ (flags & gfxXlibNativeRenderer::DRAW_SUPPORTS_ALTERNATE_VISUAL) != 0;
+ bool supportsAlternateScreen = supportsAlternateVisual &&
+ (flags & gfxXlibNativeRenderer::DRAW_SUPPORTS_ALTERNATE_SCREEN);
+
+ cairo_surface_type_t cairoTargetType =
+ cairoTarget ? cairo_surface_get_type (cairoTarget) : (cairo_surface_type_t)0xFF;
+
+ Screen *target_screen = cairoTargetType == CAIRO_SURFACE_TYPE_XLIB ?
+ cairo_xlib_surface_get_screen (cairoTarget) : screen;
+
+ // When the background has an alpha channel, we need to draw with an alpha
+ // channel anyway, so there is no need to copy the background. If
+ // doCopyBackground is set here, we'll also need to check below that the
+ // background can copied without any loss in format conversions.
+ bool doCopyBackground = !drawIsOpaque && canDrawOverBackground &&
+ cairoTarget && cairo_surface_get_content (cairoTarget) == CAIRO_CONTENT_COLOR;
+
+ if (supportsAlternateScreen && screen != target_screen && drawIsOpaque) {
+ // Prefer a visual on the target screen.
+ // (If !drawIsOpaque, we'll need doCopyBackground or an alpha channel.)
+ visual = DefaultVisualOfScreen(target_screen);
+ screen = target_screen;
+
+ } else if (doCopyBackground || (supportsAlternateVisual && drawIsOpaque)) {
+ // Analyse the pixel formats either to check whether we can
+ // doCopyBackground or to see if we can find a better visual for
+ // opaque drawing.
+ Visual *target_visual = nullptr;
+ XRenderPictFormat *target_format = nullptr;
+ if (cairoTargetType == CAIRO_SURFACE_TYPE_XLIB) {
+ target_visual = cairo_xlib_surface_get_visual (cairoTarget);
+ target_format = cairo_xlib_surface_get_xrender_format (cairoTarget);
+ } else if (cairoTargetType == CAIRO_SURFACE_TYPE_IMAGE || drawTarget) {
+ gfxImageFormat imageFormat =
+ drawTarget ? SurfaceFormatToImageFormat(drawTarget->GetFormat()) :
+ CairoFormatToGfxFormat(cairo_image_surface_get_format(cairoTarget));
+ target_visual = gfxXlibSurface::FindVisual(screen, imageFormat);
+ Display *dpy = DisplayOfScreen(screen);
+ if (target_visual) {
+ target_format = XRenderFindVisualFormat(dpy, target_visual);
+ } else {
+ target_format =
+ gfxXlibSurface::FindRenderFormat(dpy, imageFormat);
+ }
+ }
+
+ if (supportsAlternateVisual &&
+ (supportsAlternateScreen || screen == target_screen)) {
+ if (target_visual) {
+ visual = target_visual;
+ screen = target_screen;
+ }
+ }
+ // Could try harder to match formats across screens for background
+ // copying when !supportsAlternateScreen, if we cared. Preferably
+ // we'll find a visual below with an alpha channel anyway; if so, the
+ // background won't need to be copied.
+
+ if (doCopyBackground && visual != target_visual &&
+ !FormatConversionIsExact(screen, visual, target_format)) {
+ doCopyBackground = false;
+ }
+ }
+
+ if (supportsAlternateVisual && !drawIsOpaque &&
+ (screen != target_screen ||
+ !(doCopyBackground || VisualHasAlpha(screen, visual)))) {
+ // Try to find a visual with an alpha channel.
+ Screen *visualScreen =
+ supportsAlternateScreen ? target_screen : screen;
+ Visual *argbVisual =
+ gfxXlibSurface::FindVisual(visualScreen,
+ SurfaceFormat::A8R8G8B8_UINT32);
+ if (argbVisual) {
+ visual = argbVisual;
+ screen = visualScreen;
+ } else if (!doCopyBackground &&
+ gfxXlibSurface::DepthOfVisual(screen, visual) != 24) {
+ // Will need to do alpha extraction; prefer a 24-bit visual.
+ // No advantage in using the target screen.
+ Visual *rgb24Visual =
+ gfxXlibSurface::FindVisual(screen,
+ SurfaceFormat::X8R8G8B8_UINT32);
+ if (rgb24Visual) {
+ visual = rgb24Visual;
+ }
+ }
+ }
+
+ Drawable drawable =
+ (screen == target_screen && cairoTargetType == CAIRO_SURFACE_TYPE_XLIB) ?
+ cairo_xlib_surface_get_drawable (cairoTarget) : RootWindowOfScreen(screen);
+
+ cairo_surface_t *surface =
+ gfxXlibSurface::CreateCairoSurface(screen, visual,
+ IntSize(size.width, size.height),
+ drawable);
+ if (!surface) {
+ return nullptr;
+ }
+
+ if (drawIsOpaque ||
+ cairo_surface_get_content(surface) == CAIRO_CONTENT_COLOR_ALPHA) {
+ NATIVE_DRAWING_NOTE(drawIsOpaque ?
+ ", SIMPLE OPAQUE\n" : ", SIMPLE WITH ALPHA");
+ *method = eSimple;
+ } else if (doCopyBackground) {
+ NATIVE_DRAWING_NOTE(", COPY BACKGROUND\n");
+ *method = eCopyBackground;
+ } else {
+ NATIVE_DRAWING_NOTE(", SLOW ALPHA EXTRACTION\n");
+ *method = eAlphaExtraction;
+ }
+
+ return surface;
+}
+
+bool
+gfxXlibNativeRenderer::DrawOntoTempSurface(cairo_surface_t *tempXlibSurface,
+ IntPoint offset)
+{
+ cairo_surface_flush(tempXlibSurface);
+ /* no clipping is needed because the callback can't draw outside the native
+ surface anyway */
+ nsresult rv = DrawWithXlib(tempXlibSurface, offset, nullptr, 0);
+ cairo_surface_mark_dirty(tempXlibSurface);
+ return NS_SUCCEEDED(rv);
+}
+
+static already_AddRefed<gfxImageSurface>
+CopyXlibSurfaceToImage(cairo_surface_t *tempXlibSurface,
+ IntSize size,
+ gfxImageFormat format)
+{
+ RefPtr<gfxImageSurface> result = new gfxImageSurface(size, format);
+
+ cairo_t* copyCtx = cairo_create(result->CairoSurface());
+ cairo_set_source_surface(copyCtx, tempXlibSurface, 0, 0);
+ cairo_set_operator(copyCtx, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(copyCtx);
+ cairo_destroy(copyCtx);
+
+ return result.forget();
+}
+
+void
+gfxXlibNativeRenderer::Draw(gfxContext* ctx, IntSize size,
+ uint32_t flags, Screen *screen, Visual *visual)
+{
+ gfxMatrix matrix = ctx->CurrentMatrix();
+
+ // We can only draw direct or onto a copied background if pixels align and
+ // native drawing is compatible with the current operator. (The matrix is
+ // actually also pixel-exact for flips and right-angle rotations, which
+ // would permit copying the background but not drawing direct.)
+ bool matrixIsIntegerTranslation = !matrix.HasNonIntegerTranslation();
+ bool canDrawOverBackground = matrixIsIntegerTranslation &&
+ ctx->CurrentOp() == CompositionOp::OP_OVER;
+
+ // The padding of 0.5 for non-pixel-exact transformations used here is
+ // the same as what _cairo_pattern_analyze_filter uses.
+ const gfxFloat filterRadius = 0.5;
+ gfxRect affectedRect(0.0, 0.0, size.width, size.height);
+ if (!matrixIsIntegerTranslation) {
+ // The filter footprint means that the affected rectangle is a
+ // little larger than the drawingRect;
+ affectedRect.Inflate(filterRadius);
+
+ NATIVE_DRAWING_NOTE("FALLBACK: matrix not integer translation");
+ } else if (!canDrawOverBackground) {
+ NATIVE_DRAWING_NOTE("FALLBACK: unsupported operator");
+ }
+
+ DrawTarget* drawTarget = ctx->GetDrawTarget();
+ if (!drawTarget) {
+ gfxCriticalError() << "gfxContext without a DrawTarget";
+ return;
+ }
+
+ // Clipping to the region affected by drawing allows us to consider only
+ // the portions of the clip region that will be affected by drawing.
+ gfxRect clipExtents;
+ {
+ gfxContextAutoSaveRestore autoSR(ctx);
+ ctx->Clip(affectedRect);
+
+ clipExtents = ctx->GetClipExtents();
+ if (clipExtents.IsEmpty()) {
+ return; // nothing to do
+ }
+ if (canDrawOverBackground &&
+ DrawDirect(drawTarget, size, flags, screen, visual)) {
+ return;
+ }
+ }
+
+ IntRect drawingRect(IntPoint(0, 0), size);
+ // Drawing need only be performed within the clip extents
+ // (and padding for the filter).
+ if (!matrixIsIntegerTranslation) {
+ // The source surface may need to be a little larger than the clip
+ // extents due to the filter footprint.
+ clipExtents.Inflate(filterRadius);
+ }
+ clipExtents.RoundOut();
+
+ IntRect intExtents(int32_t(clipExtents.X()),
+ int32_t(clipExtents.Y()),
+ int32_t(clipExtents.Width()),
+ int32_t(clipExtents.Height()));
+ drawingRect.IntersectRect(drawingRect, intExtents);
+
+ gfxPoint offset(drawingRect.x, drawingRect.y);
+
+ DrawingMethod method;
+ Matrix dtTransform = drawTarget->GetTransform();
+ gfxPoint deviceTranslation = gfxPoint(dtTransform._31, dtTransform._32);
+ cairo_t* cairo = static_cast<cairo_t*>
+ (drawTarget->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT));
+ cairo_surface_t* cairoTarget = cairo ? cairo_get_group_target(cairo) : nullptr;
+ cairo_surface_t* tempXlibSurface =
+ CreateTempXlibSurface(cairoTarget, drawTarget, size,
+ canDrawOverBackground, flags, screen, visual,
+ &method);
+ if (!tempXlibSurface) {
+ return;
+ }
+
+ bool drawIsOpaque = (flags & DRAW_IS_OPAQUE) != 0;
+ if (!drawIsOpaque) {
+ cairo_t* tmpCtx = cairo_create(tempXlibSurface);
+ if (method == eCopyBackground) {
+ NS_ASSERTION(cairoTarget, "eCopyBackground only used when there's a cairoTarget");
+ cairo_set_operator(tmpCtx, CAIRO_OPERATOR_SOURCE);
+ gfxPoint pt = -(offset + deviceTranslation);
+ cairo_set_source_surface(tmpCtx, cairoTarget, pt.x, pt.y);
+ // The copy from the tempXlibSurface to the target context should
+ // use operator SOURCE, but that would need a mask to bound the
+ // operation. Here we only copy opaque backgrounds so operator
+ // OVER will behave like SOURCE masked by the surface.
+ NS_ASSERTION(cairo_surface_get_content(tempXlibSurface) == CAIRO_CONTENT_COLOR,
+ "Don't copy background with a transparent surface");
+ } else {
+ cairo_set_operator(tmpCtx, CAIRO_OPERATOR_CLEAR);
+ }
+ cairo_paint(tmpCtx);
+ cairo_destroy(tmpCtx);
+ }
+
+ if (!DrawOntoTempSurface(tempXlibSurface, -drawingRect.TopLeft())) {
+ cairo_surface_destroy(tempXlibSurface);
+ return;
+ }
+
+ SurfaceFormat moz2DFormat =
+ cairo_surface_get_content(tempXlibSurface) == CAIRO_CONTENT_COLOR ?
+ SurfaceFormat::B8G8R8A8 : SurfaceFormat::B8G8R8X8;
+ if (method != eAlphaExtraction) {
+ RefPtr<SourceSurface> sourceSurface =
+ Factory::CreateSourceSurfaceForCairoSurface(tempXlibSurface, size, moz2DFormat);
+ if (sourceSurface) {
+ drawTarget->DrawSurface(sourceSurface,
+ Rect(offset.x, offset.y, size.width, size.height),
+ Rect(0, 0, size.width, size.height));
+ }
+ cairo_surface_destroy(tempXlibSurface);
+ return;
+ }
+
+ RefPtr<gfxImageSurface> blackImage =
+ CopyXlibSurfaceToImage(tempXlibSurface, size, SurfaceFormat::A8R8G8B8_UINT32);
+
+ cairo_t* tmpCtx = cairo_create(tempXlibSurface);
+ cairo_set_source_rgba(tmpCtx, 1.0, 1.0, 1.0, 1.0);
+ cairo_set_operator(tmpCtx, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(tmpCtx);
+ cairo_destroy(tmpCtx);
+ DrawOntoTempSurface(tempXlibSurface, -drawingRect.TopLeft());
+ RefPtr<gfxImageSurface> whiteImage =
+ CopyXlibSurfaceToImage(tempXlibSurface, size, SurfaceFormat::X8R8G8B8_UINT32);
+
+ if (blackImage->CairoStatus() == CAIRO_STATUS_SUCCESS &&
+ whiteImage->CairoStatus() == CAIRO_STATUS_SUCCESS) {
+ if (!gfxAlphaRecovery::RecoverAlpha(blackImage, whiteImage)) {
+ cairo_surface_destroy(tempXlibSurface);
+ return;
+ }
+
+ gfxASurface* paintSurface = blackImage;
+ RefPtr<SourceSurface> sourceSurface =
+ Factory::CreateSourceSurfaceForCairoSurface(paintSurface->CairoSurface(),
+ size, moz2DFormat);
+ if (sourceSurface) {
+ drawTarget->DrawSurface(sourceSurface,
+ Rect(offset.x, offset.y, size.width, size.height),
+ Rect(0, 0, size.width, size.height));
+ }
+ }
+ cairo_surface_destroy(tempXlibSurface);
+}
diff --git a/gfx/thebes/gfxXlibNativeRenderer.h b/gfx/thebes/gfxXlibNativeRenderer.h
new file mode 100644
index 000000000..85af130ca
--- /dev/null
+++ b/gfx/thebes/gfxXlibNativeRenderer.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 GFXXLIBNATIVERENDER_H_
+#define GFXXLIBNATIVERENDER_H_
+
+#include "nsPoint.h"
+#include "nsRect.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/gfx/Point.h"
+#include <X11/Xlib.h>
+
+namespace mozilla {
+namespace gfx {
+ class DrawTarget;
+}
+}
+
+class gfxASurface;
+class gfxContext;
+typedef struct _cairo cairo_t;
+typedef struct _cairo_surface cairo_surface_t;
+
+/**
+ * This class lets us take code that draws into an X drawable and lets us
+ * use it to draw into any Thebes context. The user should subclass this class,
+ * override DrawWithXib, and then call Draw(). The drawing will be subjected
+ * to all Thebes transformations, clipping etc.
+ */
+class gfxXlibNativeRenderer {
+public:
+ /**
+ * Perform the native drawing.
+ * @param surface the cairo_surface_t for drawing. Must be a cairo_xlib_surface_t.
+ * The extents of this surface do not necessarily cover the
+ * entire rectangle with size provided to Draw().
+ * @param offset draw at this offset into the given drawable
+ * @param clipRects an array of rectangles; clip to the union.
+ * Any rectangles provided will be contained by the
+ * rectangle with size provided to Draw and by the
+ * surface extents.
+ * @param numClipRects the number of rects in the array, or zero if
+ * no clipping is required.
+ */
+ virtual nsresult DrawWithXlib(cairo_surface_t* surface,
+ mozilla::gfx::IntPoint offset,
+ mozilla::gfx::IntRect* clipRects,
+ uint32_t numClipRects) = 0;
+
+ enum {
+ // If set, then Draw() is opaque, i.e., every pixel in the intersection
+ // of the clipRect and (offset.x,offset.y,bounds.width,bounds.height)
+ // will be set and there is no dependence on what the existing pixels
+ // in the drawable are set to.
+ DRAW_IS_OPAQUE = 0x01,
+ // If set, then numClipRects can be zero or one
+ DRAW_SUPPORTS_CLIP_RECT = 0x04,
+ // If set, then numClipRects can be any value. If neither this
+ // nor CLIP_RECT are set, then numClipRects will be zero
+ DRAW_SUPPORTS_CLIP_LIST = 0x08,
+ // If set, then the surface in the callback may have any visual;
+ // otherwise the pixels will have the same format as the visual
+ // passed to 'Draw'.
+ DRAW_SUPPORTS_ALTERNATE_VISUAL = 0x10,
+ // If set, then the Screen 'screen' in the callback can be different
+ // from the default Screen of the display passed to 'Draw' and can be
+ // on a different display.
+ DRAW_SUPPORTS_ALTERNATE_SCREEN = 0x20
+ };
+
+ /**
+ * @param flags see above
+ * @param size the size of the rectangle being drawn;
+ * the caller guarantees that drawing will not extend beyond the rectangle
+ * (0,0,size.width,size.height).
+ * @param screen a Screen to use for the drawing if ctx doesn't have one.
+ * @param visual a Visual to use for the drawing if ctx doesn't have one.
+ * @param result if non-null, we will try to capture a copy of the
+ * rendered image into a surface similar to the surface of ctx; if
+ * successful, a pointer to the new gfxASurface is stored in *resultSurface,
+ * otherwise *resultSurface is set to nullptr.
+ */
+ void Draw(gfxContext* ctx, mozilla::gfx::IntSize size,
+ uint32_t flags, Screen *screen, Visual *visual);
+
+private:
+ bool DrawDirect(mozilla::gfx::DrawTarget* aDT, mozilla::gfx::IntSize bounds,
+ uint32_t flags, Screen *screen, Visual *visual);
+
+ bool DrawCairo(cairo_t* cr, mozilla::gfx::IntSize size,
+ uint32_t flags, Screen *screen, Visual *visual);
+
+ void DrawFallback(mozilla::gfx::DrawTarget* dt, gfxContext* ctx,
+ gfxASurface* aSurface, mozilla::gfx::IntSize& size,
+ mozilla::gfx::IntRect& drawingRect, bool canDrawOverBackground,
+ uint32_t flags, Screen* screen, Visual* visual);
+
+ bool DrawOntoTempSurface(cairo_surface_t *tempXlibSurface,
+ mozilla::gfx::IntPoint offset);
+
+};
+
+#endif /*GFXXLIBNATIVERENDER_H_*/
diff --git a/gfx/thebes/gfxXlibSurface.cpp b/gfx/thebes/gfxXlibSurface.cpp
new file mode 100644
index 000000000..12891c454
--- /dev/null
+++ b/gfx/thebes/gfxXlibSurface.cpp
@@ -0,0 +1,614 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "cairo-xlib-xrender.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
+
+#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(dpy), mDrawable(drawable)
+#if defined(GL_PROVIDER_GLX)
+ , mGLXPixmap(X11None)
+#endif
+{
+ 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)
+ : mPixmapTaken(false), mDisplay(dpy), mDrawable(drawable)
+#if defined(GL_PROVIDER_GLX)
+ , mGLXPixmap(X11None)
+#endif
+{
+ 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(Screen *screen, Drawable drawable, XRenderPictFormat *format,
+ const gfx::IntSize& size)
+ : mPixmapTaken(false), mDisplay(DisplayOfScreen(screen)),
+ mDrawable(drawable)
+#if defined(GL_PROVIDER_GLX)
+ , mGLXPixmap(X11None)
+#endif
+{
+ NS_ASSERTION(Factory::CheckSurfaceSize(size, XLIB_IMAGE_SIDE_SIZE_LIMIT),
+ "Bad Size");
+
+ cairo_surface_t *surf =
+ cairo_xlib_surface_create_with_xrender_format(mDisplay, drawable,
+ screen, format,
+ size.width, size.height);
+ Init(surf);
+}
+
+gfxXlibSurface::gfxXlibSurface(cairo_surface_t *csurf)
+ : mPixmapTaken(false)
+#if defined(GL_PROVIDER_GLX)
+ , mGLXPixmap(X11None)
+#endif
+{
+ NS_PRECONDITION(cairo_surface_status(csurf) == 0,
+ "Not expecting an error surface");
+
+ mDrawable = cairo_xlib_surface_get_drawable(csurf);
+ mDisplay = cairo_xlib_surface_get_display(csurf);
+
+ Init(csurf, true);
+}
+
+gfxXlibSurface::~gfxXlibSurface()
+{
+ // gfxASurface's destructor calls RecordMemoryFreed().
+ if (mPixmapTaken) {
+#if defined(GL_PROVIDER_GLX)
+ if (mGLXPixmap) {
+ gl::sGLXLibrary.DestroyPixmap(mDisplay, mGLXPixmap);
+ }
+#endif
+ 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)
+{
+ Drawable drawable =
+ CreatePixmap(screen, size, DepthOfVisual(screen, visual),
+ relatedDrawable);
+ if (!drawable)
+ return nullptr;
+
+ RefPtr<gfxXlibSurface> result =
+ new gfxXlibSurface(DisplayOfScreen(screen), drawable, visual, size);
+ result->TakePixmap();
+
+ if (result->CairoStatus() != 0)
+ return nullptr;
+
+ return result.forget();
+}
+
+/* static */
+already_AddRefed<gfxXlibSurface>
+gfxXlibSurface::Create(Screen *screen, XRenderPictFormat *format,
+ const gfx::IntSize& size, Drawable relatedDrawable)
+{
+ Drawable drawable =
+ CreatePixmap(screen, size, format->depth, relatedDrawable);
+ if (!drawable)
+ return nullptr;
+
+ RefPtr<gfxXlibSurface> result =
+ new gfxXlibSurface(screen, drawable, format, size);
+ result->TakePixmap();
+
+ if (result->CairoStatus() != 0)
+ return nullptr;
+
+ return result.forget();
+}
+
+static bool GetForce24bppPref()
+{
+ return Preferences::GetBool("mozilla.widget.force-24bpp", false);
+}
+
+already_AddRefed<gfxASurface>
+gfxXlibSurface::CreateSimilarSurface(gfxContentType aContent,
+ const gfx::IntSize& aSize)
+{
+ if (!mSurface || !mSurfaceValid) {
+ return nullptr;
+ }
+
+ if (aContent == gfxContentType::COLOR) {
+ // cairo_surface_create_similar will use a matching visual if it can.
+ // However, systems with 16-bit or indexed default visuals may benefit
+ // from rendering with 24-bit formats.
+ static bool force24bpp = GetForce24bppPref();
+ if (force24bpp
+ && cairo_xlib_surface_get_depth(CairoSurface()) != 24) {
+ XRenderPictFormat* format =
+ XRenderFindStandardFormat(mDisplay, PictStandardRGB24);
+ if (format) {
+ // Cairo only performs simple self-copies as desired if it
+ // knows that this is a Pixmap surface. It only knows that
+ // surfaces are pixmap surfaces if it creates the Pixmap
+ // itself, so we use cairo_surface_create_similar with a
+ // temporary reference surface to indicate the format.
+ Screen* screen = cairo_xlib_surface_get_screen(CairoSurface());
+ RefPtr<gfxXlibSurface> depth24reference =
+ gfxXlibSurface::Create(screen, format,
+ gfx::IntSize(1, 1), mDrawable);
+ if (depth24reference)
+ return depth24reference->
+ gfxASurface::CreateSimilarSurface(aContent, aSize);
+ }
+ }
+ }
+
+ return gfxASurface::CreateSimilarSurface(aContent, aSize);
+}
+
+void
+gfxXlibSurface::Finish()
+{
+#if defined(GL_PROVIDER_GLX)
+ if (mPixmapTaken && mGLXPixmap) {
+ gl::sGLXLibrary.DestroyPixmap(mDisplay, mGLXPixmap);
+ mGLXPixmap = X11None;
+ }
+#endif
+ 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,
+ XRenderPictFormat* format,
+ Visual* visual, Colormap* colormap,
+ Visual** visualForColormap);
+
+private:
+ struct ColormapEntry {
+ XRenderPictFormat* mFormat;
+ // The Screen is needed here because colormaps (and their visuals) may
+ // only be used on one Screen, but XRenderPictFormats are not unique
+ // to any 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, XRenderPictFormat* aFormat,
+ 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
+ || (aFormat
+ && aFormat == XRenderFindVisualFormat(display, 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);
+ // Only the format and screen need to match. (The visual may differ.)
+ // If there is no format (e.g. no RENDER extension) then just compare
+ // the visual.
+ if ((aFormat && entry.mFormat == aFormat && entry.mScreen == aScreen)
+ || 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->mFormat = aFormat;
+ 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)
+{
+ XRenderPictFormat* format =
+ cairo_xlib_surface_get_xrender_format(aXlibSurface);
+ Screen* screen = cairo_xlib_surface_get_screen(aXlibSurface);
+ Visual* visual = cairo_xlib_surface_get_visual(aXlibSurface);
+
+ return DisplayTable::GetColormapAndVisual(screen, format, 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;
+}
+
+/* static */
+XRenderPictFormat*
+gfxXlibSurface::FindRenderFormat(Display *dpy, gfxImageFormat format)
+{
+ switch (format) {
+ case gfx::SurfaceFormat::A8R8G8B8_UINT32:
+ return XRenderFindStandardFormat (dpy, PictStandardARGB32);
+ case gfx::SurfaceFormat::X8R8G8B8_UINT32:
+ return XRenderFindStandardFormat (dpy, PictStandardRGB24);
+ case gfx::SurfaceFormat::R5G6B5_UINT16: {
+ // PictStandardRGB16_565 is not standard Xrender format
+ // we should try to find related visual
+ // and find xrender format by visual
+ Visual *visual = FindVisual(DefaultScreenOfDisplay(dpy), format);
+ if (!visual)
+ return nullptr;
+ return XRenderFindVisualFormat(dpy, visual);
+ }
+ case gfx::SurfaceFormat::A8:
+ return XRenderFindStandardFormat (dpy, PictStandardA8);
+ default:
+ break;
+ }
+
+ return nullptr;
+}
+
+Screen*
+gfxXlibSurface::XScreen()
+{
+ return cairo_xlib_surface_get_screen(CairoSurface());
+}
+
+XRenderPictFormat*
+gfxXlibSurface::XRenderFormat()
+{
+ return cairo_xlib_surface_get_xrender_format(CairoSurface());
+}
+
+#if defined(GL_PROVIDER_GLX)
+GLXPixmap
+gfxXlibSurface::GetGLXPixmap()
+{
+ if (!mGLXPixmap) {
+#ifdef DEBUG
+ // cairo_surface_has_show_text_glyphs is used solely for the
+ // side-effect of setting the error on surface if
+ // cairo_surface_finish() has been called.
+ cairo_surface_has_show_text_glyphs(CairoSurface());
+ NS_ASSERTION(CairoStatus() != CAIRO_STATUS_SURFACE_FINISHED,
+ "GetGLXPixmap called after surface finished");
+#endif
+ mGLXPixmap = gl::sGLXLibrary.CreatePixmap(this);
+ }
+ return mGLXPixmap;
+}
+
+void
+gfxXlibSurface::BindGLXPixmap(GLXPixmap aPixmap)
+{
+ MOZ_ASSERT(!mGLXPixmap, "A GLXPixmap is already bound!");
+ mGLXPixmap = aPixmap;
+}
+
+#endif
diff --git a/gfx/thebes/gfxXlibSurface.h b/gfx/thebes/gfxXlibSurface.h
new file mode 100644
index 000000000..499bc5e96
--- /dev/null
+++ b/gfx/thebes/gfxXlibSurface.h
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/extensions/Xrender.h>
+#include <X11/Xlib.h>
+#include "X11UndefineNone.h"
+
+#if defined(GL_PROVIDER_GLX)
+#include "GLXLibrary.h"
+#endif
+
+#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);
+
+ // construct a wrapper around the specified drawable with dpy/format,
+ // and known width/height.
+ gfxXlibSurface(Screen *screen, Drawable drawable, XRenderPictFormat *format,
+ 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 cairo_surface_t *
+ CreateCairoSurface(Screen *screen, Visual *visual, const mozilla::gfx::IntSize& size,
+ Drawable relatedDrawable = X11None);
+ static already_AddRefed<gfxXlibSurface>
+ Create(Screen* screen, XRenderPictFormat *format, const mozilla::gfx::IntSize& size,
+ Drawable relatedDrawable = X11None);
+
+ virtual ~gfxXlibSurface();
+
+ virtual already_AddRefed<gfxASurface>
+ CreateSimilarSurface(gfxContentType aType,
+ const mozilla::gfx::IntSize& aSize) override;
+ virtual void Finish() override;
+
+ virtual const mozilla::gfx::IntSize GetSize() const override;
+
+ Display* XDisplay() { return mDisplay; }
+ Screen* XScreen();
+ Drawable XDrawable() { return mDrawable; }
+ XRenderPictFormat* XRenderFormat();
+
+ static int DepthOfVisual(const Screen* screen, const Visual* visual);
+ static Visual* FindVisual(Screen* screen, gfxImageFormat format);
+ static XRenderPictFormat *FindRenderFormat(Display *dpy, 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);
+
+#if defined(GL_PROVIDER_GLX)
+ GLXPixmap GetGLXPixmap();
+ // Binds a GLXPixmap backed by this context's surface.
+ // Primarily for use in sharing surfaces.
+ void BindGLXPixmap(GLXPixmap aPixmap);
+#endif
+
+ // 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) >= 60700000 ||
+ VendorRelease(mDisplay) < 10699000;
+ }
+
+protected:
+ // if TakePixmap() has been called on this
+ bool mPixmapTaken;
+
+ Display *mDisplay;
+ Drawable mDrawable;
+
+ const mozilla::gfx::IntSize DoSizeQuery();
+
+#if defined(GL_PROVIDER_GLX)
+ GLXPixmap mGLXPixmap;
+#endif
+};
+
+#endif /* GFX_XLIBSURFACE_H */
diff --git a/gfx/thebes/moz.build b/gfx/thebes/moz.build
new file mode 100644
index 000000000..227b2b875
--- /dev/null
+++ b/gfx/thebes/moz.build
@@ -0,0 +1,273 @@
+# -*- 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/.
+
+EXPORTS += [
+ 'ContextStateTracker.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',
+ 'gfxFontFamilyList.h',
+ 'gfxFontFeatures.h',
+ 'gfxFontInfoLoader.h',
+ 'gfxFontPrefLangList.h',
+ 'gfxFontTest.h',
+ 'gfxFontUtils.h',
+ 'gfxGradientCache.h',
+ 'gfxImageSurface.h',
+ 'gfxLineSegment.h',
+ 'gfxMathTable.h',
+ 'gfxMatrix.h',
+ 'gfxPattern.h',
+ 'gfxPlatform.h',
+ 'gfxPoint.h',
+ 'gfxPrefs.h',
+ 'gfxQuad.h',
+ 'gfxQuaternion.h',
+ 'gfxRect.h',
+ 'gfxSharedImageSurface.h',
+ 'gfxSkipChars.h',
+ 'gfxSVGGlyphs.h',
+ 'gfxTextRun.h',
+ 'gfxTypes.h',
+ 'gfxUserFontSet.h',
+ 'gfxUtils.h',
+ 'RoundedRect.h',
+ 'SoftwareVsyncSource.h',
+ 'VsyncSource.h',
+]
+
+EXPORTS.mozilla.gfx += [
+ 'D3D11Checks.h',
+ 'DeviceManagerDx.h',
+ 'PrintTarget.h',
+ 'PrintTargetThebes.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 += [
+ 'gfxPlatformMac.h',
+ 'gfxQuartzNativeDrawing.h',
+ 'gfxQuartzSurface.h',
+ ]
+ EXPORTS.mozilla.gfx += [
+ 'PrintTargetCG.h',
+ ]
+ SOURCES += [
+ 'gfxCoreTextShaper.cpp',
+ 'gfxMacFont.cpp',
+ 'gfxPlatformMac.cpp',
+ 'gfxQuartzNativeDrawing.cpp',
+ 'gfxQuartzSurface.cpp',
+ 'PrintTargetCG.cpp',
+ ]
+elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ EXPORTS += [
+ 'gfxFontconfigFonts.h',
+ 'gfxFT2FontBase.h',
+ 'gfxGdkNativeRenderer.h',
+ 'gfxPlatformGtk.h',
+ ]
+ EXPORTS.mozilla.gfx += [
+ 'PrintTargetPDF.h',
+ 'PrintTargetPS.h',
+ ]
+ SOURCES += [
+ 'gfxFcPlatformFontList.cpp',
+ 'gfxFontconfigFonts.cpp',
+ 'gfxFontconfigUtils.cpp',
+ 'gfxFT2FontBase.cpp',
+ 'gfxFT2Utils.cpp',
+ 'gfxGdkNativeRenderer.cpp',
+ 'gfxPlatformGtk.cpp',
+ 'PrintTargetPDF.cpp',
+ 'PrintTargetPS.cpp',
+ ]
+
+ if CONFIG['MOZ_X11']:
+ EXPORTS += [
+ 'gfxXlibNativeRenderer.h',
+ 'gfxXlibSurface.h',
+ ]
+ SOURCES += [
+ 'gfxXlibNativeRenderer.cpp',
+ 'gfxXlibSurface.cpp',
+ ]
+
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ EXPORTS += [
+ 'gfxDWriteFonts.h',
+ 'gfxGDIFont.h',
+ 'gfxGDIFontList.h',
+ 'gfxPlatformFontList.h',
+ 'gfxWindowsNativeDrawing.h',
+ 'gfxWindowsPlatform.h',
+ 'gfxWindowsSurface.h',
+ ]
+ EXPORTS.mozilla.gfx += [
+ 'PrintTargetPDF.h',
+ 'PrintTargetWindows.h',
+ ]
+ SOURCES += [
+ 'gfxGDIFont.cpp',
+ 'gfxGDIFontList.cpp',
+ 'gfxWindowsNativeDrawing.cpp',
+ 'gfxWindowsPlatform.cpp',
+ 'gfxWindowsSurface.cpp',
+ 'PrintTargetPDF.cpp',
+ 'PrintTargetWindows.cpp',
+ ]
+ if CONFIG['MOZ_ENABLE_DWRITE_FONT']:
+ UNIFIED_SOURCES += [
+ 'gfxDWriteFontList.cpp',
+ ]
+ SOURCES += [
+ 'gfxDWriteCommon.cpp',
+ 'gfxDWriteFonts.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 += [
+ 'ContextStateTracker.cpp',
+ # 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',
+ # gfxPlatform.cpp includes mac system header conflicting with point/size
+ 'gfxPlatform.cpp',
+ 'gfxPrefs.cpp',
+ 'PrintTarget.cpp',
+ 'PrintTargetThebes.cpp',
+]
+
+UNIFIED_SOURCES += [
+ 'CJKCompatSVS.cpp',
+ 'gfxAlphaRecovery.cpp',
+ 'gfxBaseSharedMemorySurface.cpp',
+ 'gfxBlur.cpp',
+ 'gfxContext.cpp',
+ 'gfxFont.cpp',
+ 'gfxFontEntry.cpp',
+ 'gfxFontFeatures.cpp',
+ 'gfxFontInfoLoader.cpp',
+ 'gfxFontMissingGlyphs.cpp',
+ 'gfxFontTest.cpp',
+ 'gfxFontUtils.cpp',
+ 'gfxGlyphExtents.cpp',
+ 'gfxGradientCache.cpp',
+ 'gfxGraphiteShaper.cpp',
+ 'gfxHarfBuzzShaper.cpp',
+ 'gfxImageSurface.cpp',
+ 'gfxMathTable.cpp',
+ 'gfxMatrix.cpp',
+ 'gfxPattern.cpp',
+ 'gfxPlatformFontList.cpp',
+ 'gfxRect.cpp',
+ 'gfxScriptItemizer.cpp',
+ 'gfxSkipChars.cpp',
+ 'gfxSVGGlyphs.cpp',
+ 'gfxTextRun.cpp',
+ 'gfxUserFontSet.cpp',
+ 'gfxUtils.cpp',
+ 'nsUnicodeRange.cpp',
+ 'SoftwareVsyncSource.cpp',
+ 'VsyncSource.cpp',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ UNIFIED_SOURCES += [
+ 'gfxMacPlatformFontList.mm',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ UNIFIED_SOURCES += [
+ 'D3D11Checks.cpp',
+ 'DeviceManagerDx.cpp',
+ ]
+
+# We prefer to use ICU for normalization functions, but currently it is only
+# available if we're building with the Intl API enabled:
+if CONFIG['ENABLE_INTL_API']:
+ USE_LIBS += [
+ 'icu',
+ ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+GENERATED_FILES = [
+ 'DeprecatedPremultiplyTables.h',
+]
+GENERATED_FILES['DeprecatedPremultiplyTables.h'].script = 'genTables.py:generate'
+
+LOCAL_INCLUDES += [
+ '/dom/workers',
+ '/dom/xml',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('android', 'gtk2', 'gtk3'):
+ DEFINES['MOZ_ENABLE_FREETYPE'] = True
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ for var in ('MOZ_ENABLE_D3D9_LAYER', 'MOZ_ENABLE_D3D10_LAYER'):
+ if CONFIG[var]:
+ DEFINES[var] = True
+
+CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
+CXXFLAGS += CONFIG['TK_CFLAGS']
+CFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
+CFLAGS += CONFIG['TK_CFLAGS']
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('android'):
+ CXXFLAGS += CONFIG['CAIRO_FT_CFLAGS']
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'):
+ CXXFLAGS += CONFIG['MOZ_PANGO_CFLAGS']
+
+LOCAL_INCLUDES += CONFIG['SKIA_INCLUDES']
+LOCAL_INCLUDES += ['/media/libyuv/include']
+
+DEFINES['GRAPHITE2_STATIC'] = True
+
+if CONFIG['CLANG_CXX']:
+ # Suppress warnings from Skia header files.
+ SOURCES['gfxPlatform.cpp'].flags += ['-Wno-implicit-fallthrough']
diff --git a/gfx/thebes/nsUnicodeRange.cpp b/gfx/thebes/nsUnicodeRange.cpp
new file mode 100644
index 000000000..af53f21fa
--- /dev/null
+++ b/gfx/thebes/nsUnicodeRange.cpp
@@ -0,0 +1,419 @@
+/* -*- 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 "nsUnicodeRange.h"
+
+/**********************************************************************
+ * Unicode subranges as defined in unicode 3.0
+ * x-western -> latin
+ * 0000 - 036f
+ * 1e00 - 1eff
+ * 2000 - 206f (general punctuation)
+ * 20a0 - 20cf (currency symbols)
+ * 2100 - 214f (letterlike symbols)
+ * 2150 - 218f (Number Forms)
+ * el -> greek
+ * 0370 - 03ff
+ * 1f00 - 1fff
+ * x-cyrillic -> cyrillic
+ * 0400 - 04ff
+ * he -> hebrew
+ * 0590 - 05ff
+ * ar -> arabic
+ * 0600 - 06ff
+ * fb50 - fdff (arabic presentation forms)
+ * fe70 - feff (arabic presentation forms b)
+ * th - thai
+ * 0e00 - 0e7f
+ * ko -> korean
+ * ac00 - d7af (hangul Syllables)
+ * 1100 - 11ff (jamo)
+ * 3130 - 318f (hangul compatibility jamo)
+ * ja
+ * 3040 - 309f (hiragana)
+ * 30a0 - 30ff (katakana)
+ * zh-CN
+ * zh-TW
+ *
+ * CJK
+ * 3100 - 312f (bopomofo)
+ * 31a0 - 31bf (bopomofo extended)
+ * 3000 - 303f (CJK Symbols and Punctuation)
+ * 2e80 - 2eff (CJK radicals supplement)
+ * 2f00 - 2fdf (Kangxi Radicals)
+ * 2ff0 - 2fff (Ideographic Description Characters)
+ * 3190 - 319f (kanbun)
+ * 3200 - 32ff (Enclosed CJK letters and Months)
+ * 3300 - 33ff (CJK compatibility)
+ * 3400 - 4dbf (CJK Unified Ideographs Extension A)
+ * 4e00 - 9faf (CJK Unified Ideographs)
+ * f900 - fa5f (CJK Compatibility Ideographs)
+ * fe30 - fe4f (CJK compatibility Forms)
+ * ff00 - ffef (halfwidth and fullwidth forms)
+ *
+ * Armenian
+ * 0530 - 058f
+ * Sriac
+ * 0700 - 074f
+ * Thaana
+ * 0780 - 07bf
+ * Devanagari
+ * 0900 - 097f
+ * Bengali
+ * 0980 - 09ff
+ * Gurmukhi
+ * 0a00 - 0a7f
+ * Gujarati
+ * 0a80 - 0aff
+ * Oriya
+ * 0b00 - 0b7f
+ * Tamil
+ * 0b80 - 0bff
+ * Telugu
+ * 0c00 - 0c7f
+ * Kannada
+ * 0c80 - 0cff
+ * Malayalam
+ * 0d00 - 0d7f
+ * Sinhala
+ * 0d80 - 0def
+ * Lao
+ * 0e80 - 0eff
+ * Tibetan
+ * 0f00 - 0fbf
+ * Myanmar
+ * 1000 - 109f
+ * Georgian
+ * 10a0 - 10ff
+ * Ethiopic
+ * 1200 - 137f
+ * Cherokee
+ * 13a0 - 13ff
+ * Canadian Aboriginal Syllabics
+ * 1400 - 167f
+ * Ogham
+ * 1680 - 169f
+ * Runic
+ * 16a0 - 16ff
+ * Khmer
+ * 1780 - 17ff
+ * Mongolian
+ * 1800 - 18af
+ * Misc - superscripts and subscripts
+ * 2070 - 209f
+ * Misc - Combining Diacritical Marks for Symbols
+ * 20d0 - 20ff
+ * Misc - Arrows
+ * 2190 - 21ff
+ * Misc - Mathematical Operators
+ * 2200 - 22ff
+ * Misc - Miscellaneous Technical
+ * 2300 - 23ff
+ * Misc - Control picture
+ * 2400 - 243f
+ * Misc - Optical character recognition
+ * 2440 - 2450
+ * Misc - Enclose Alphanumerics
+ * 2460 - 24ff
+ * Misc - Box Drawing
+ * 2500 - 257f
+ * Misc - Block Elements
+ * 2580 - 259f
+ * Misc - Geometric Shapes
+ * 25a0 - 25ff
+ * Misc - Miscellaneous Symbols
+ * 2600 - 267f
+ * Misc - Dingbats
+ * 2700 - 27bf
+ * Misc - Braille Patterns
+ * 2800 - 28ff
+ * Yi Syllables
+ * a000 - a48f
+ * Yi radicals
+ * a490 - a4cf
+ * Alphabetic Presentation Forms
+ * fb00 - fb4f
+ * Misc - Combining half Marks
+ * fe20 - fe2f
+ * Misc - small form variants
+ * fe50 - fe6f
+ * Misc - Specials
+ * fff0 - ffff
+ *********************************************************************/
+
+
+
+#define NUM_OF_SUBTABLES 10
+#define SUBTABLE_SIZE 16
+
+static const uint8_t gUnicodeSubrangeTable[NUM_OF_SUBTABLES][SUBTABLE_SIZE] =
+{
+ { // table for X---
+ kRangeTableBase+1, //u0xxx
+ kRangeTableBase+2, //u1xxx
+ kRangeTableBase+3, //u2xxx
+ kRangeSetCJK, //u3xxx
+ kRangeSetCJK, //u4xxx
+ kRangeSetCJK, //u5xxx
+ kRangeSetCJK, //u6xxx
+ kRangeSetCJK, //u7xxx
+ kRangeSetCJK, //u8xxx
+ kRangeSetCJK, //u9xxx
+ kRangeTableBase+4, //uaxxx
+ kRangeKorean, //ubxxx
+ kRangeKorean, //ucxxx
+ kRangeTableBase+5, //udxxx
+ kRangePrivate, //uexxx
+ kRangeTableBase+6 //ufxxx
+ },
+ { //table for 0X--
+ kRangeSetLatin, //u00xx
+ kRangeSetLatin, //u01xx
+ kRangeSetLatin, //u02xx
+ kRangeGreek, //u03xx XXX 0300-036f is in fact kRangeCombiningDiacriticalMarks
+ kRangeCyrillic, //u04xx
+ kRangeTableBase+7, //u05xx, includes Cyrillic supplement, Hebrew, and Armenian
+ kRangeArabic, //u06xx
+ kRangeTertiaryTable, //u07xx
+ kRangeUnassigned, //u08xx
+ kRangeTertiaryTable, //u09xx
+ kRangeTertiaryTable, //u0axx
+ kRangeTertiaryTable, //u0bxx
+ kRangeTertiaryTable, //u0cxx
+ kRangeTertiaryTable, //u0dxx
+ kRangeTertiaryTable, //u0exx
+ kRangeTibetan //u0fxx
+ },
+ { //table for 1x--
+ kRangeTertiaryTable, //u10xx
+ kRangeKorean, //u11xx
+ kRangeEthiopic, //u12xx
+ kRangeTertiaryTable, //u13xx
+ kRangeCanadian, //u14xx
+ kRangeCanadian, //u15xx
+ kRangeTertiaryTable, //u16xx
+ kRangeKhmer, //u17xx
+ kRangeMongolian, //u18xx
+ kRangeUnassigned, //u19xx
+ kRangeUnassigned, //u1axx
+ kRangeUnassigned, //u1bxx
+ kRangeUnassigned, //u1cxx
+ kRangeUnassigned, //u1dxx
+ kRangeSetLatin, //u1exx
+ kRangeGreek //u1fxx
+ },
+ { //table for 2x--
+ kRangeSetLatin, //u20xx
+ kRangeSetLatin, //u21xx
+ kRangeMathOperators, //u22xx
+ kRangeMiscTechnical, //u23xx
+ kRangeControlOpticalEnclose, //u24xx
+ kRangeBoxBlockGeometrics, //u25xx
+ kRangeMiscSymbols, //u26xx
+ kRangeDingbats, //u27xx
+ kRangeBraillePattern, //u28xx
+ kRangeUnassigned, //u29xx
+ kRangeUnassigned, //u2axx
+ kRangeUnassigned, //u2bxx
+ kRangeUnassigned, //u2cxx
+ kRangeUnassigned, //u2dxx
+ kRangeSetCJK, //u2exx
+ kRangeSetCJK //u2fxx
+ },
+ { //table for ax--
+ kRangeYi, //ua0xx
+ kRangeYi, //ua1xx
+ kRangeYi, //ua2xx
+ kRangeYi, //ua3xx
+ kRangeYi, //ua4xx
+ kRangeUnassigned, //ua5xx
+ kRangeUnassigned, //ua6xx
+ kRangeUnassigned, //ua7xx
+ kRangeUnassigned, //ua8xx
+ kRangeUnassigned, //ua9xx
+ kRangeUnassigned, //uaaxx
+ kRangeUnassigned, //uabxx
+ kRangeKorean, //uacxx
+ kRangeKorean, //uadxx
+ kRangeKorean, //uaexx
+ kRangeKorean //uafxx
+ },
+ { //table for dx--
+ kRangeKorean, //ud0xx
+ kRangeKorean, //ud1xx
+ kRangeKorean, //ud2xx
+ kRangeKorean, //ud3xx
+ kRangeKorean, //ud4xx
+ kRangeKorean, //ud5xx
+ kRangeKorean, //ud6xx
+ kRangeKorean, //ud7xx
+ kRangeSurrogate, //ud8xx
+ kRangeSurrogate, //ud9xx
+ kRangeSurrogate, //udaxx
+ kRangeSurrogate, //udbxx
+ kRangeSurrogate, //udcxx
+ kRangeSurrogate, //uddxx
+ kRangeSurrogate, //udexx
+ kRangeSurrogate //udfxx
+ },
+ { // table for fx--
+ kRangePrivate, //uf0xx
+ kRangePrivate, //uf1xx
+ kRangePrivate, //uf2xx
+ kRangePrivate, //uf3xx
+ kRangePrivate, //uf4xx
+ kRangePrivate, //uf5xx
+ kRangePrivate, //uf6xx
+ kRangePrivate, //uf7xx
+ kRangePrivate, //uf8xx
+ kRangeSetCJK, //uf9xx
+ kRangeSetCJK, //ufaxx
+ kRangeArabic, //ufbxx, includes alphabic presentation form
+ kRangeArabic, //ufcxx
+ kRangeArabic, //ufdxx
+ kRangeTableBase+8, //ufexx
+ kRangeTableBase+9 //uffxx, halfwidth and fullwidth forms, includes Specials
+ },
+ { //table for 0x0500 - 0x05ff
+ kRangeCyrillic, //u050x
+ kRangeCyrillic, //u051x
+ kRangeCyrillic, //u052x
+ kRangeArmenian, //u053x
+ kRangeArmenian, //u054x
+ kRangeArmenian, //u055x
+ kRangeArmenian, //u056x
+ kRangeArmenian, //u057x
+ kRangeArmenian, //u058x
+ kRangeHebrew, //u059x
+ kRangeHebrew, //u05ax
+ kRangeHebrew, //u05bx
+ kRangeHebrew, //u05cx
+ kRangeHebrew, //u05dx
+ kRangeHebrew, //u05ex
+ kRangeHebrew //u05fx
+ },
+ { //table for 0xfe00 - 0xfeff
+ kRangeSetCJK, //ufe0x
+ kRangeSetCJK, //ufe1x
+ kRangeSetCJK, //ufe2x
+ kRangeSetCJK, //ufe3x
+ kRangeSetCJK, //ufe4x
+ kRangeSetCJK, //ufe5x
+ kRangeSetCJK, //ufe6x
+ kRangeArabic, //ufe7x
+ kRangeArabic, //ufe8x
+ kRangeArabic, //ufe9x
+ kRangeArabic, //ufeax
+ kRangeArabic, //ufebx
+ kRangeArabic, //ufecx
+ kRangeArabic, //ufedx
+ kRangeArabic, //ufeex
+ kRangeArabic //ufefx
+ },
+ { //table for 0xff00 - 0xffff
+ kRangeSetCJK, //uff0x, fullwidth latin
+ kRangeSetCJK, //uff1x, fullwidth latin
+ kRangeSetCJK, //uff2x, fullwidth latin
+ kRangeSetCJK, //uff3x, fullwidth latin
+ kRangeSetCJK, //uff4x, fullwidth latin
+ kRangeSetCJK, //uff5x, fullwidth latin
+ kRangeSetCJK, //uff6x, halfwidth katakana
+ kRangeSetCJK, //uff7x, halfwidth katakana
+ kRangeSetCJK, //uff8x, halfwidth katakana
+ kRangeSetCJK, //uff9x, halfwidth katakana
+ kRangeSetCJK, //uffax, halfwidth hangul jamo
+ kRangeSetCJK, //uffbx, halfwidth hangul jamo
+ kRangeSetCJK, //uffcx, halfwidth hangul jamo
+ kRangeSetCJK, //uffdx, halfwidth hangul jamo
+ kRangeSetCJK, //uffex, fullwidth symbols
+ kRangeSpecials, //ufffx, Specials
+ },
+};
+
+// Most scripts between U+0700 and U+16FF are assigned a chunk of 128 (0x80)
+// code points so that the number of entries in the tertiary range
+// table for that range is obtained by dividing (0x1700 - 0x0700) by 128.
+// Exceptions: Ethiopic, Tibetan, Hangul Jamo and Canadian aboriginal
+// syllabaries take multiple chunks and Ogham and Runic share a single chunk.
+#define TERTIARY_TABLE_SIZE ((0x1700 - 0x0700) / 0x80)
+
+static const uint8_t gUnicodeTertiaryRangeTable[TERTIARY_TABLE_SIZE] =
+{ //table for 0x0700 - 0x1600
+ kRangeSyriac, //u070x
+ kRangeThaana, //u078x
+ kRangeUnassigned, //u080x place holder(resolved in the 2ndary tab.)
+ kRangeUnassigned, //u088x place holder(resolved in the 2ndary tab.)
+ kRangeDevanagari, //u090x
+ kRangeBengali, //u098x
+ kRangeGurmukhi, //u0a0x
+ kRangeGujarati, //u0a8x
+ kRangeOriya, //u0b0x
+ kRangeTamil, //u0b8x
+ kRangeTelugu, //u0c0x
+ kRangeKannada, //u0c8x
+ kRangeMalayalam, //u0d0x
+ kRangeSinhala, //u0d8x
+ kRangeThai, //u0e0x
+ kRangeLao, //u0e8x
+ kRangeTibetan, //u0f0x place holder(resolved in the 2ndary tab.)
+ kRangeTibetan, //u0f8x place holder(resolved in the 2ndary tab.)
+ kRangeMyanmar, //u100x
+ kRangeGeorgian, //u108x
+ kRangeKorean, //u110x place holder(resolved in the 2ndary tab.)
+ kRangeKorean, //u118x place holder(resolved in the 2ndary tab.)
+ kRangeEthiopic, //u120x place holder(resolved in the 2ndary tab.)
+ kRangeEthiopic, //u128x place holder(resolved in the 2ndary tab.)
+ kRangeEthiopic, //u130x
+ kRangeCherokee, //u138x
+ kRangeCanadian, //u140x place holder(resolved in the 2ndary tab.)
+ kRangeCanadian, //u148x place holder(resolved in the 2ndary tab.)
+ kRangeCanadian, //u150x place holder(resolved in the 2ndary tab.)
+ kRangeCanadian, //u158x place holder(resolved in the 2ndary tab.)
+ kRangeCanadian, //u160x
+ kRangeOghamRunic //u168x this contains two scripts, Ogham & Runic
+};
+
+// A two level index is almost enough for locating a range, with the
+// exception of u03xx and u05xx. Since we don't really care about range for
+// combining diacritical marks in our font application, they are
+// not discriminated further. But future adoption of this module for other use
+// should be aware of this limitation. The implementation can be extended if
+// there is such a need.
+// For Indic, Southeast Asian scripts and some other scripts between
+// U+0700 and U+16FF, it's extended to the third level.
+uint32_t FindCharUnicodeRange(uint32_t ch)
+{
+ uint32_t range;
+
+ // aggregate ranges for non-BMP codepoints
+ if (ch > 0xFFFF) {
+ uint32_t p = (ch >> 16);
+ if (p == 1) {
+ return kRangeSMP;
+ } else if (p == 2) {
+ return kRangeSetCJK;
+ }
+ return kRangeHigherPlanes;
+ }
+
+ // lookup explicit range for BMP codepoints
+ // first general range
+ range = gUnicodeSubrangeTable[0][ch >> 12];
+
+ // if general range is good enough, return that
+ if (range < kRangeTableBase)
+ // we try to get a specific range
+ return range;
+
+ // otherwise, use subrange tables
+ range = gUnicodeSubrangeTable[range - kRangeTableBase][(ch & 0x0f00) >> 8];
+ if (range < kRangeTableBase)
+ return range;
+ if (range < kRangeTertiaryTable)
+ return gUnicodeSubrangeTable[range - kRangeTableBase][(ch & 0x00f0) >> 4];
+
+ // Yet another table to look at : U+0700 - U+16FF : 128 code point blocks
+ return gUnicodeTertiaryRangeTable[(ch - 0x0700) >> 7];
+}
diff --git a/gfx/thebes/nsUnicodeRange.h b/gfx/thebes/nsUnicodeRange.h
new file mode 100644
index 000000000..292a6a9ab
--- /dev/null
+++ b/gfx/thebes/nsUnicodeRange.h
@@ -0,0 +1,91 @@
+/* -*- 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 NS_UNICODERANGE_H
+#define NS_UNICODERANGE_H
+
+#include <stdint.h>
+
+// The following constants define unicode subranges
+// values below kRangeNum must be continuous so that we can map to
+// lang group directly.
+// all ranges we care about should be defined under 32, that allows
+// us to store range using bits of a uint32_t
+
+// frequently used range definitions
+const uint8_t kRangeCyrillic = 0;
+const uint8_t kRangeGreek = 1;
+const uint8_t kRangeHebrew = 2;
+const uint8_t kRangeArabic = 3;
+const uint8_t kRangeThai = 4;
+const uint8_t kRangeKorean = 5;
+const uint8_t kRangeJapanese = 6;
+const uint8_t kRangeSChinese = 7;
+const uint8_t kRangeTChinese = 8;
+const uint8_t kRangeDevanagari = 9;
+const uint8_t kRangeTamil = 10;
+const uint8_t kRangeArmenian = 11;
+const uint8_t kRangeBengali = 12;
+const uint8_t kRangeCanadian = 13;
+const uint8_t kRangeEthiopic = 14;
+const uint8_t kRangeGeorgian = 15;
+const uint8_t kRangeGujarati = 16;
+const uint8_t kRangeGurmukhi = 17;
+const uint8_t kRangeKhmer = 18;
+const uint8_t kRangeMalayalam = 19;
+const uint8_t kRangeOriya = 20;
+const uint8_t kRangeTelugu = 21;
+const uint8_t kRangeKannada = 22;
+const uint8_t kRangeSinhala = 23;
+const uint8_t kRangeTibetan = 24;
+
+const uint8_t kRangeSpecificItemNum = 25;
+
+//range/rangeSet grow to this place 25-29
+
+const uint8_t kRangeSetStart = 30; // range set definition starts from here
+const uint8_t kRangeSetLatin = 30;
+const uint8_t kRangeSetCJK = 31;
+const uint8_t kRangeSetEnd = 31; // range set definition ends here, this
+ // and smaller ranges are used as bit
+ // mask, don't increase this value.
+
+// less frequently used range definition
+const uint8_t kRangeSurrogate = 32;
+const uint8_t kRangePrivate = 33;
+const uint8_t kRangeMisc = 34;
+const uint8_t kRangeUnassigned = 35;
+const uint8_t kRangeSyriac = 36;
+const uint8_t kRangeThaana = 37;
+const uint8_t kRangeLao = 38;
+const uint8_t kRangeMyanmar = 39;
+const uint8_t kRangeCherokee = 40;
+const uint8_t kRangeOghamRunic = 41;
+const uint8_t kRangeMongolian = 42;
+const uint8_t kRangeMathOperators = 43;
+const uint8_t kRangeMiscTechnical = 44;
+const uint8_t kRangeControlOpticalEnclose = 45;
+const uint8_t kRangeBoxBlockGeometrics = 46;
+const uint8_t kRangeMiscSymbols = 47;
+const uint8_t kRangeDingbats = 48;
+const uint8_t kRangeBraillePattern = 49;
+const uint8_t kRangeYi = 50;
+const uint8_t kRangeCombiningDiacriticalMarks = 51;
+const uint8_t kRangeSpecials = 52;
+
+// aggregate ranges for non-BMP codepoints (u+2xxxx are all CJK)
+const uint8_t kRangeSMP = 53; // u+1xxxx
+const uint8_t kRangeHigherPlanes = 54; // u+3xxxx and above
+
+const uint8_t kRangeTableBase = 128; //values over 127 are reserved for internal use only
+const uint8_t kRangeTertiaryTable = 145; // leave room for 16 subtable
+ // indices (kRangeTableBase + 1 ..
+ // kRangeTableBase + 16)
+
+
+
+uint32_t FindCharUnicodeRange(uint32_t ch);
+
+#endif