From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- gfx/thebes/CJKCompatSVS.cpp | 1039 ++++ gfx/thebes/ContextStateTracker.cpp | 137 + gfx/thebes/ContextStateTracker.h | 84 + gfx/thebes/D3D11Checks.cpp | 412 ++ gfx/thebes/D3D11Checks.h | 30 + gfx/thebes/DeviceManagerDx.cpp | 878 ++++ gfx/thebes/DeviceManagerDx.h | 154 + gfx/thebes/DrawMode.h | 26 + gfx/thebes/PrintTarget.cpp | 159 + gfx/thebes/PrintTarget.h | 160 + gfx/thebes/PrintTargetCG.cpp | 120 + gfx/thebes/PrintTargetCG.h | 42 + gfx/thebes/PrintTargetPDF.cpp | 85 + gfx/thebes/PrintTargetPDF.h | 41 + gfx/thebes/PrintTargetPS.cpp | 110 + gfx/thebes/PrintTargetPS.h | 54 + gfx/thebes/PrintTargetRecording.cpp | 116 + gfx/thebes/PrintTargetRecording.h | 43 + gfx/thebes/PrintTargetThebes.cpp | 126 + gfx/thebes/PrintTargetThebes.h | 57 + gfx/thebes/PrintTargetWindows.cpp | 112 + gfx/thebes/PrintTargetWindows.h | 43 + gfx/thebes/RoundedRect.h | 43 + gfx/thebes/SoftwareVsyncSource.cpp | 150 + gfx/thebes/SoftwareVsyncSource.h | 62 + gfx/thebes/VsyncSource.cpp | 153 + gfx/thebes/VsyncSource.h | 84 + gfx/thebes/cairo-xlib-utils.h | 119 + gfx/thebes/d3dkmtQueryStatistics.h | 168 + gfx/thebes/genLanguageTagList.pl | 86 + gfx/thebes/genTables.py | 22 + gfx/thebes/gencjkcisvs.py | 77 + gfx/thebes/gfx2DGlue.h | 131 + gfx/thebes/gfxASurface.cpp | 620 +++ gfx/thebes/gfxASurface.h | 204 + gfx/thebes/gfxAlphaRecovery.cpp | 53 + gfx/thebes/gfxAlphaRecovery.h | 111 + gfx/thebes/gfxAlphaRecoverySSE2.cpp | 235 + gfx/thebes/gfxAndroidPlatform.cpp | 338 ++ gfx/thebes/gfxAndroidPlatform.h | 85 + gfx/thebes/gfxBaseSharedMemorySurface.cpp | 11 + gfx/thebes/gfxBaseSharedMemorySurface.h | 199 + gfx/thebes/gfxBlur.cpp | 1088 ++++ gfx/thebes/gfxBlur.h | 194 + gfx/thebes/gfxColor.h | 83 + gfx/thebes/gfxContext.cpp | 1257 +++++ gfx/thebes/gfxContext.h | 694 +++ gfx/thebes/gfxCoreTextShaper.cpp | 800 +++ gfx/thebes/gfxCoreTextShaper.h | 71 + gfx/thebes/gfxDWriteCommon.cpp | 182 + gfx/thebes/gfxDWriteCommon.h | 153 + gfx/thebes/gfxDWriteFontList.cpp | 1838 +++++++ gfx/thebes/gfxDWriteFontList.h | 445 ++ gfx/thebes/gfxDWriteFonts.cpp | 709 +++ gfx/thebes/gfxDWriteFonts.h | 107 + gfx/thebes/gfxDrawable.cpp | 254 + gfx/thebes/gfxDrawable.h | 182 + gfx/thebes/gfxEnv.h | 123 + gfx/thebes/gfxFT2FontBase.cpp | 217 + gfx/thebes/gfxFT2FontBase.h | 45 + gfx/thebes/gfxFT2FontList.cpp | 1608 ++++++ gfx/thebes/gfxFT2FontList.h | 200 + gfx/thebes/gfxFT2Fonts.cpp | 227 + gfx/thebes/gfxFT2Fonts.h | 84 + gfx/thebes/gfxFT2Utils.cpp | 378 ++ gfx/thebes/gfxFT2Utils.h | 94 + gfx/thebes/gfxFailure.h | 25 + gfx/thebes/gfxFcPlatformFontList.cpp | 1866 +++++++ gfx/thebes/gfxFcPlatformFontList.h | 330 ++ gfx/thebes/gfxFont.cpp | 4028 +++++++++++++++ gfx/thebes/gfxFont.h | 2223 ++++++++ gfx/thebes/gfxFontConstants.h | 237 + gfx/thebes/gfxFontEntry.cpp | 1831 +++++++ gfx/thebes/gfxFontEntry.h | 777 +++ gfx/thebes/gfxFontFamilyList.h | 364 ++ gfx/thebes/gfxFontFeatures.cpp | 81 + gfx/thebes/gfxFontFeatures.h | 126 + gfx/thebes/gfxFontInfoLoader.cpp | 281 + gfx/thebes/gfxFontInfoLoader.h | 258 + gfx/thebes/gfxFontMissingGlyphs.cpp | 288 ++ gfx/thebes/gfxFontMissingGlyphs.h | 55 + gfx/thebes/gfxFontPrefLangList.h | 36 + gfx/thebes/gfxFontTest.cpp | 8 + gfx/thebes/gfxFontTest.h | 84 + gfx/thebes/gfxFontUtils.cpp | 1809 +++++++ gfx/thebes/gfxFontUtils.h | 1027 ++++ gfx/thebes/gfxFontconfigFonts.cpp | 2262 +++++++++ gfx/thebes/gfxFontconfigFonts.h | 124 + gfx/thebes/gfxFontconfigUtils.cpp | 1100 ++++ gfx/thebes/gfxFontconfigUtils.h | 330 ++ gfx/thebes/gfxGDIFont.cpp | 564 +++ gfx/thebes/gfxGDIFont.h | 108 + gfx/thebes/gfxGDIFontList.cpp | 1195 +++++ gfx/thebes/gfxGDIFontList.h | 354 ++ gfx/thebes/gfxGdkNativeRenderer.cpp | 69 + gfx/thebes/gfxGdkNativeRenderer.h | 88 + gfx/thebes/gfxGlyphExtents.cpp | 154 + gfx/thebes/gfxGlyphExtents.h | 151 + gfx/thebes/gfxGradientCache.cpp | 239 + gfx/thebes/gfxGradientCache.h | 36 + gfx/thebes/gfxGraphiteShaper.cpp | 438 ++ gfx/thebes/gfxGraphiteShaper.h | 59 + gfx/thebes/gfxHarfBuzzShaper.cpp | 1818 +++++++ gfx/thebes/gfxHarfBuzzShaper.h | 190 + gfx/thebes/gfxImageSurface.cpp | 379 ++ gfx/thebes/gfxImageSurface.h | 188 + gfx/thebes/gfxLanguageTagList.cpp | 7876 +++++++++++++++++++++++++++++ gfx/thebes/gfxLineSegment.h | 77 + gfx/thebes/gfxMacFont.cpp | 475 ++ gfx/thebes/gfxMacFont.h | 102 + gfx/thebes/gfxMacPlatformFontList.h | 182 + gfx/thebes/gfxMacPlatformFontList.mm | 1451 ++++++ gfx/thebes/gfxMathTable.cpp | 210 + gfx/thebes/gfxMathTable.h | 155 + gfx/thebes/gfxMatrix.cpp | 189 + gfx/thebes/gfxMatrix.h | 310 ++ gfx/thebes/gfxPattern.cpp | 218 + gfx/thebes/gfxPattern.h | 77 + gfx/thebes/gfxPlatform.cpp | 2599 ++++++++++ gfx/thebes/gfxPlatform.h | 846 ++++ gfx/thebes/gfxPlatformFontList.cpp | 1678 ++++++ gfx/thebes/gfxPlatformFontList.h | 471 ++ gfx/thebes/gfxPlatformGtk.cpp | 906 ++++ gfx/thebes/gfxPlatformGtk.h | 172 + gfx/thebes/gfxPlatformMac.cpp | 620 +++ gfx/thebes/gfxPlatformMac.h | 96 + gfx/thebes/gfxPoint.h | 70 + gfx/thebes/gfxPrefs.cpp | 267 + gfx/thebes/gfxPrefs.h | 682 +++ gfx/thebes/gfxQuad.h | 51 + gfx/thebes/gfxQuartzNativeDrawing.cpp | 74 + gfx/thebes/gfxQuartzNativeDrawing.h | 71 + gfx/thebes/gfxQuartzSurface.cpp | 137 + gfx/thebes/gfxQuartzSurface.h | 43 + gfx/thebes/gfxQuaternion.h | 93 + gfx/thebes/gfxRect.cpp | 96 + gfx/thebes/gfxRect.h | 148 + gfx/thebes/gfxSVGGlyphs.cpp | 463 ++ gfx/thebes/gfxSVGGlyphs.h | 242 + gfx/thebes/gfxScriptItemizer.cpp | 229 + gfx/thebes/gfxScriptItemizer.h | 102 + gfx/thebes/gfxSharedImageSurface.h | 24 + gfx/thebes/gfxSkipChars.cpp | 153 + gfx/thebes/gfxSkipChars.h | 308 ++ gfx/thebes/gfxTextRun.cpp | 3214 ++++++++++++ gfx/thebes/gfxTextRun.h | 1229 +++++ gfx/thebes/gfxTypes.h | 82 + gfx/thebes/gfxUserFontSet.cpp | 1439 ++++++ gfx/thebes/gfxUserFontSet.h | 716 +++ gfx/thebes/gfxUtils.cpp | 1547 ++++++ gfx/thebes/gfxUtils.h | 325 ++ gfx/thebes/gfxWindowsNativeDrawing.cpp | 320 ++ gfx/thebes/gfxWindowsNativeDrawing.h | 116 + gfx/thebes/gfxWindowsPlatform.cpp | 2139 ++++++++ gfx/thebes/gfxWindowsPlatform.h | 281 + gfx/thebes/gfxWindowsSurface.cpp | 169 + gfx/thebes/gfxWindowsSurface.h | 61 + gfx/thebes/gfxXlibNativeRenderer.cpp | 620 +++ gfx/thebes/gfxXlibNativeRenderer.h | 105 + gfx/thebes/gfxXlibSurface.cpp | 614 +++ gfx/thebes/gfxXlibSurface.h | 122 + gfx/thebes/moz.build | 273 + gfx/thebes/nsUnicodeRange.cpp | 419 ++ gfx/thebes/nsUnicodeRange.h | 91 + 164 files changed, 79437 insertions(+) create mode 100644 gfx/thebes/CJKCompatSVS.cpp create mode 100644 gfx/thebes/ContextStateTracker.cpp create mode 100644 gfx/thebes/ContextStateTracker.h create mode 100644 gfx/thebes/D3D11Checks.cpp create mode 100644 gfx/thebes/D3D11Checks.h create mode 100644 gfx/thebes/DeviceManagerDx.cpp create mode 100644 gfx/thebes/DeviceManagerDx.h create mode 100644 gfx/thebes/DrawMode.h create mode 100644 gfx/thebes/PrintTarget.cpp create mode 100644 gfx/thebes/PrintTarget.h create mode 100644 gfx/thebes/PrintTargetCG.cpp create mode 100644 gfx/thebes/PrintTargetCG.h create mode 100644 gfx/thebes/PrintTargetPDF.cpp create mode 100644 gfx/thebes/PrintTargetPDF.h create mode 100644 gfx/thebes/PrintTargetPS.cpp create mode 100644 gfx/thebes/PrintTargetPS.h create mode 100644 gfx/thebes/PrintTargetRecording.cpp create mode 100644 gfx/thebes/PrintTargetRecording.h create mode 100644 gfx/thebes/PrintTargetThebes.cpp create mode 100644 gfx/thebes/PrintTargetThebes.h create mode 100644 gfx/thebes/PrintTargetWindows.cpp create mode 100644 gfx/thebes/PrintTargetWindows.h create mode 100644 gfx/thebes/RoundedRect.h create mode 100644 gfx/thebes/SoftwareVsyncSource.cpp create mode 100644 gfx/thebes/SoftwareVsyncSource.h create mode 100644 gfx/thebes/VsyncSource.cpp create mode 100644 gfx/thebes/VsyncSource.h create mode 100644 gfx/thebes/cairo-xlib-utils.h create mode 100644 gfx/thebes/d3dkmtQueryStatistics.h create mode 100644 gfx/thebes/genLanguageTagList.pl create mode 100644 gfx/thebes/genTables.py create mode 100644 gfx/thebes/gencjkcisvs.py create mode 100644 gfx/thebes/gfx2DGlue.h create mode 100644 gfx/thebes/gfxASurface.cpp create mode 100644 gfx/thebes/gfxASurface.h create mode 100644 gfx/thebes/gfxAlphaRecovery.cpp create mode 100644 gfx/thebes/gfxAlphaRecovery.h create mode 100644 gfx/thebes/gfxAlphaRecoverySSE2.cpp create mode 100644 gfx/thebes/gfxAndroidPlatform.cpp create mode 100644 gfx/thebes/gfxAndroidPlatform.h create mode 100644 gfx/thebes/gfxBaseSharedMemorySurface.cpp create mode 100644 gfx/thebes/gfxBaseSharedMemorySurface.h create mode 100644 gfx/thebes/gfxBlur.cpp create mode 100644 gfx/thebes/gfxBlur.h create mode 100644 gfx/thebes/gfxColor.h create mode 100644 gfx/thebes/gfxContext.cpp create mode 100644 gfx/thebes/gfxContext.h create mode 100644 gfx/thebes/gfxCoreTextShaper.cpp create mode 100644 gfx/thebes/gfxCoreTextShaper.h create mode 100644 gfx/thebes/gfxDWriteCommon.cpp create mode 100644 gfx/thebes/gfxDWriteCommon.h create mode 100644 gfx/thebes/gfxDWriteFontList.cpp create mode 100644 gfx/thebes/gfxDWriteFontList.h create mode 100644 gfx/thebes/gfxDWriteFonts.cpp create mode 100644 gfx/thebes/gfxDWriteFonts.h create mode 100644 gfx/thebes/gfxDrawable.cpp create mode 100644 gfx/thebes/gfxDrawable.h create mode 100644 gfx/thebes/gfxEnv.h create mode 100644 gfx/thebes/gfxFT2FontBase.cpp create mode 100644 gfx/thebes/gfxFT2FontBase.h create mode 100644 gfx/thebes/gfxFT2FontList.cpp create mode 100644 gfx/thebes/gfxFT2FontList.h create mode 100644 gfx/thebes/gfxFT2Fonts.cpp create mode 100644 gfx/thebes/gfxFT2Fonts.h create mode 100644 gfx/thebes/gfxFT2Utils.cpp create mode 100644 gfx/thebes/gfxFT2Utils.h create mode 100644 gfx/thebes/gfxFailure.h create mode 100644 gfx/thebes/gfxFcPlatformFontList.cpp create mode 100644 gfx/thebes/gfxFcPlatformFontList.h create mode 100644 gfx/thebes/gfxFont.cpp create mode 100644 gfx/thebes/gfxFont.h create mode 100644 gfx/thebes/gfxFontConstants.h create mode 100644 gfx/thebes/gfxFontEntry.cpp create mode 100644 gfx/thebes/gfxFontEntry.h create mode 100644 gfx/thebes/gfxFontFamilyList.h create mode 100644 gfx/thebes/gfxFontFeatures.cpp create mode 100644 gfx/thebes/gfxFontFeatures.h create mode 100644 gfx/thebes/gfxFontInfoLoader.cpp create mode 100644 gfx/thebes/gfxFontInfoLoader.h create mode 100644 gfx/thebes/gfxFontMissingGlyphs.cpp create mode 100644 gfx/thebes/gfxFontMissingGlyphs.h create mode 100644 gfx/thebes/gfxFontPrefLangList.h create mode 100644 gfx/thebes/gfxFontTest.cpp create mode 100644 gfx/thebes/gfxFontTest.h create mode 100644 gfx/thebes/gfxFontUtils.cpp create mode 100644 gfx/thebes/gfxFontUtils.h create mode 100644 gfx/thebes/gfxFontconfigFonts.cpp create mode 100644 gfx/thebes/gfxFontconfigFonts.h create mode 100644 gfx/thebes/gfxFontconfigUtils.cpp create mode 100644 gfx/thebes/gfxFontconfigUtils.h create mode 100644 gfx/thebes/gfxGDIFont.cpp create mode 100644 gfx/thebes/gfxGDIFont.h create mode 100644 gfx/thebes/gfxGDIFontList.cpp create mode 100644 gfx/thebes/gfxGDIFontList.h create mode 100644 gfx/thebes/gfxGdkNativeRenderer.cpp create mode 100644 gfx/thebes/gfxGdkNativeRenderer.h create mode 100644 gfx/thebes/gfxGlyphExtents.cpp create mode 100644 gfx/thebes/gfxGlyphExtents.h create mode 100644 gfx/thebes/gfxGradientCache.cpp create mode 100644 gfx/thebes/gfxGradientCache.h create mode 100644 gfx/thebes/gfxGraphiteShaper.cpp create mode 100644 gfx/thebes/gfxGraphiteShaper.h create mode 100644 gfx/thebes/gfxHarfBuzzShaper.cpp create mode 100644 gfx/thebes/gfxHarfBuzzShaper.h create mode 100644 gfx/thebes/gfxImageSurface.cpp create mode 100644 gfx/thebes/gfxImageSurface.h create mode 100644 gfx/thebes/gfxLanguageTagList.cpp create mode 100644 gfx/thebes/gfxLineSegment.h create mode 100644 gfx/thebes/gfxMacFont.cpp create mode 100644 gfx/thebes/gfxMacFont.h create mode 100644 gfx/thebes/gfxMacPlatformFontList.h create mode 100644 gfx/thebes/gfxMacPlatformFontList.mm create mode 100644 gfx/thebes/gfxMathTable.cpp create mode 100644 gfx/thebes/gfxMathTable.h create mode 100644 gfx/thebes/gfxMatrix.cpp create mode 100644 gfx/thebes/gfxMatrix.h create mode 100644 gfx/thebes/gfxPattern.cpp create mode 100644 gfx/thebes/gfxPattern.h create mode 100644 gfx/thebes/gfxPlatform.cpp create mode 100644 gfx/thebes/gfxPlatform.h create mode 100644 gfx/thebes/gfxPlatformFontList.cpp create mode 100644 gfx/thebes/gfxPlatformFontList.h create mode 100644 gfx/thebes/gfxPlatformGtk.cpp create mode 100644 gfx/thebes/gfxPlatformGtk.h create mode 100644 gfx/thebes/gfxPlatformMac.cpp create mode 100644 gfx/thebes/gfxPlatformMac.h create mode 100644 gfx/thebes/gfxPoint.h create mode 100644 gfx/thebes/gfxPrefs.cpp create mode 100644 gfx/thebes/gfxPrefs.h create mode 100644 gfx/thebes/gfxQuad.h create mode 100644 gfx/thebes/gfxQuartzNativeDrawing.cpp create mode 100644 gfx/thebes/gfxQuartzNativeDrawing.h create mode 100644 gfx/thebes/gfxQuartzSurface.cpp create mode 100644 gfx/thebes/gfxQuartzSurface.h create mode 100644 gfx/thebes/gfxQuaternion.h create mode 100644 gfx/thebes/gfxRect.cpp create mode 100644 gfx/thebes/gfxRect.h create mode 100644 gfx/thebes/gfxSVGGlyphs.cpp create mode 100644 gfx/thebes/gfxSVGGlyphs.h create mode 100644 gfx/thebes/gfxScriptItemizer.cpp create mode 100644 gfx/thebes/gfxScriptItemizer.h create mode 100644 gfx/thebes/gfxSharedImageSurface.h create mode 100644 gfx/thebes/gfxSkipChars.cpp create mode 100644 gfx/thebes/gfxSkipChars.h create mode 100644 gfx/thebes/gfxTextRun.cpp create mode 100644 gfx/thebes/gfxTextRun.h create mode 100644 gfx/thebes/gfxTypes.h create mode 100644 gfx/thebes/gfxUserFontSet.cpp create mode 100644 gfx/thebes/gfxUserFontSet.h create mode 100644 gfx/thebes/gfxUtils.cpp create mode 100644 gfx/thebes/gfxUtils.h create mode 100644 gfx/thebes/gfxWindowsNativeDrawing.cpp create mode 100644 gfx/thebes/gfxWindowsNativeDrawing.h create mode 100755 gfx/thebes/gfxWindowsPlatform.cpp create mode 100644 gfx/thebes/gfxWindowsPlatform.h create mode 100644 gfx/thebes/gfxWindowsSurface.cpp create mode 100644 gfx/thebes/gfxWindowsSurface.h create mode 100644 gfx/thebes/gfxXlibNativeRenderer.cpp create mode 100644 gfx/thebes/gfxXlibNativeRenderer.h create mode 100644 gfx/thebes/gfxXlibSurface.cpp create mode 100644 gfx/thebes/gfxXlibSurface.h create mode 100644 gfx/thebes/moz.build create mode 100644 gfx/thebes/nsUnicodeRange.cpp create mode 100644 gfx/thebes/nsUnicodeRange.h (limited to 'gfx/thebes') 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 + +#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 + +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 mCompletedSections; + nsTArray 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 +#include +#include +#include + +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 deviceContext; + aDevice->GetImmediateContext(getter_AddRefs(deviceContext)); + int backbufferWidth = 32; int backbufferHeight = 32; + RefPtr offscreenTexture; + RefPtr 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 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 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& 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 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 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 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 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 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 sharedResource; + RefPtr 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 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 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 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 dxgiDevice; + HRESULT hr = device->QueryInterface(__uuidof(IDXGIDevice), getter_AddRefs(dxgiDevice)); + if (FAILED(hr)) { + return false; + } + + RefPtr 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 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 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 +#include + +namespace mozilla { +namespace gfx { + +using namespace mozilla::widget; + +StaticAutoPtr 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 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 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& 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 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 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 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& 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 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 adapter; + if (!mDeviceStatus->isWARP()) { + adapter = GetDXGIAdapter(); + if (!adapter) { + gfxCriticalNote << "Could not get a DXGI adapter"; + return FeatureStatus::Unavailable; + } + } + + HRESULT hr; + RefPtr 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 ok = ContentAdapterIsParentAdapter(device); + MOZ_ASSERT(ok); + } + + { + MutexAutoLock lock(mDeviceLock); + mContentDevice = device; + } + mContentDevice->SetExceptionMode(0); + + RefPtr multi; + hr = mContentDevice->QueryInterface(__uuidof(ID3D10Multithread), getter_AddRefs(multi)); + if (SUCCEEDED(hr) && multi) { + multi->SetMultithreadProtected(TRUE); + } + return FeatureStatus::Available; +} + +RefPtr +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 multi; + mDecoderDevice->QueryInterface(__uuidof(ID3D10Multithread), getter_AddRefs(multi)); + if (multi) { + multi->SetMultithreadProtected(TRUE); + } + } + + if (mDecoderDevice) { + RefPtr dev = mDecoderDevice; + return dev.forget(); + } + } + + if (!sD3D11CreateDeviceFn) { + // We should just be on Windows Vista or XP in this case. + return nullptr; + } + + RefPtr adapter = GetDXGIAdapter(); + if (!adapter) { + return nullptr; + } + + HRESULT hr; + RefPtr 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 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& 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 +DeviceManagerDx::GetCompositorDevice() +{ + MutexAutoLock lock(mDeviceLock); + return mCompositorDevice; +} + +RefPtr +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 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 +#include + +#include + +// This header is available in the June 2010 SDK and in the Win8 SDK +#include +// 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(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 GetCompositorDevice(); + RefPtr GetContentDevice(); + RefPtr 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& aOutDevice); + + void CreateWARPCompositorDevice(); + + mozilla::gfx::FeatureStatus CreateContentDevice(); + + bool CreateDevice(IDXGIAdapter* aAdapter, + D3D_DRIVER_TYPE aDriverType, + UINT aFlags, + HRESULT& aResOut, + RefPtr& 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 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 mFeatureLevels; + RefPtr mAdapter; + RefPtr mCompositorDevice; + RefPtr mContentDevice; + RefPtr mDecoderDevice; + bool mCompositorDeviceSupportsVideo; + + Maybe mDeviceStatus; + + nsModuleHandle mDirectDrawDLL; + RefPtr mDirectDraw; + + Maybe 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 +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 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 +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 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 +PrintTarget::CreateRecordingDrawTarget(DrawEventRecorder* aRecorder, + DrawTarget* aDrawTarget) +{ + MOZ_ASSERT(aRecorder); + MOZ_ASSERT(aDrawTarget); + + RefPtr 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 + 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 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 + CreateRecordingDrawTarget(DrawEventRecorder* aRecorder, + DrawTarget* aDrawTarget); + + cairo_surface_t* mCairoSurface; + RefPtr 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::CreateOrNull(const IntSize& aSize, gfxImageFormat aFormat) +{ + if (!Factory::CheckSurfaceSize(aSize)) { + return nullptr; + } + + unsigned int width = static_cast(aSize.width); + unsigned int height = static_cast(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 target = new PrintTargetCG(surface, aSize); + + return target.forget(); +} + +/* static */ already_AddRefed +PrintTargetCG::CreateOrNull(CGContextRef aContext, const IntSize& aSize) +{ + if (!Factory::CheckSurfaceSize(aSize)) { + return nullptr; + } + + unsigned int width = static_cast(aSize.width); + unsigned int height = static_cast(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 target = new PrintTargetCG(surface, aSize); + + return target.forget(); +} + +static size_t +PutBytesNull(void* info, const void* buffer, size_t count) +{ + return count; +} + +already_AddRefed +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 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 +#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 + CreateOrNull(const IntSize& aSize, gfxImageFormat aFormat); + + static already_AddRefed + CreateOrNull(CGContextRef aContext, const IntSize& aSize); + + virtual already_AddRefed + 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 out = reinterpret_cast(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::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 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 + 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 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 out = reinterpret_cast(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::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 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 + 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 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::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 target = + new PrintTargetRecording(surface, aSize); + + return target.forget(); +} + +already_AddRefed +PrintTargetRecording::MakeDrawTarget(const IntSize& aSize, + DrawEventRecorder* aRecorder) +{ + MOZ_ASSERT(aRecorder, "A DrawEventRecorder is required"); + + if (!aRecorder) { + return nullptr; + } + + RefPtr dt = PrintTarget::MakeDrawTarget(aSize, nullptr); + if (dt) { + dt = CreateRecordingDrawTarget(aRecorder, dt); + if (!dt || !dt->IsValid()) { + return nullptr; + } + } + + return dt.forget(); +} + +already_AddRefed +PrintTargetRecording::CreateRecordingDrawTarget(DrawEventRecorder* aRecorder, + DrawTarget* aDrawTarget) +{ + MOZ_ASSERT(aRecorder); + MOZ_ASSERT(aDrawTarget); + + RefPtr 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 + CreateOrNull(const IntSize& aSize); + + virtual already_AddRefed + MakeDrawTarget(const IntSize& aSize, + DrawEventRecorder* aRecorder = nullptr) override; + +private: + PrintTargetRecording(cairo_surface_t* aCairoSurface, + const IntSize& aSize); + + already_AddRefed + 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::CreateOrNull(gfxASurface* aSurface) +{ + MOZ_ASSERT(aSurface); + + if (!aSurface || aSurface->CairoStatus()) { + return nullptr; + } + + RefPtr target = new PrintTargetThebes(aSurface); + + return target.forget(); +} + +PrintTargetThebes::PrintTargetThebes(gfxASurface* aSurface) + : PrintTarget(nullptr, aSurface->GetSize()) + , mGfxSurface(aSurface) +{ +} + +already_AddRefed +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 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 +PrintTargetThebes::GetReferenceDrawTarget(DrawEventRecorder* aRecorder) +{ + if (!mRefDT) { + RefPtr 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 + 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 + MakeDrawTarget(const IntSize& aSize, + DrawEventRecorder* aRecorder = nullptr) override; + + virtual already_AddRefed GetReferenceDrawTarget(DrawEventRecorder* aRecorder) final; + +private: + + // Only created via CreateOrNull + explicit PrintTargetThebes(gfxASurface* aSurface); + + RefPtr 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::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 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 + +namespace mozilla { +namespace gfx { + +/** + * Windows printing target. + */ +class PrintTargetWindows final : public PrintTarget +{ +public: + static already_AddRefed + 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(this, + &SoftwareDisplay::NotifyVsync, + nextVsync); + + RefPtr 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 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 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 +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 +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 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> mCompositorVsyncDispatchers; + RefPtr mRefreshTimerVsyncDispatcher; + }; + + void AddCompositorVsyncDispatcher(CompositorVsyncDispatcher* aCompositorVsyncDispatcher); + void RemoveCompositorVsyncDispatcher(CompositorVsyncDispatcher* aCompositorVsyncDispatcher); + + RefPtr 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 + +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 ", 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 + +#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 + +#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 +#include + +#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::Wrap (cairo_surface_t *csurf, const IntSize& aSize) +{ + RefPtr 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(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::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 result = Wrap(surface, aSize); + cairo_surface_destroy(surface); + return result.forget(); +} + +already_AddRefed +gfxASurface::CopyToARGB32ImageSurface() +{ + if (!mSurface || !mSurfaceValid) { + return nullptr; + } + + const IntSize size = GetSize(); + RefPtr imgSurface = + new gfxImageSurface(size, SurfaceFormat::A8R8G8B8_UINT32); + + RefPtr dt = gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(imgSurface, IntSize(size.width, size.height)); + RefPtr 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 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 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(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 +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 +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 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 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 GetAsImageSurface(); + + /** + * Creates a new ARGB32 image surface with the same contents as this surface. + * Returns null on error. + */ + already_AddRefed 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 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(blackData); + const uint32_t* whitePixel = reinterpret_cast(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 + +// 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(blackData), + *reinterpret_cast(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(blackData), + *reinterpret_cast(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 + // for alpha recovery, and want a SIMD fast-path. The + // rect /needs/ to be redrawn, but it might not be + // properly aligned for SIMD. So we want to find a rect 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 + // 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 , 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 + // left and up by 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 +{ +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 CountingAllocatorBase::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 +gfxAndroidPlatform::CreateOffscreenSurface(const IntSize& aSize, + gfxImageFormat aFormat) +{ + if (!Factory::AllowedSurfaceSize(aSize)) { + return nullptr; + } + + RefPtr 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 ls = + do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + break; + } + nsCOMPtr 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& 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* 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 +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 +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 + CreateOffscreenSurface(const IntSize& aSize, + gfxImageFormat aFormat) override; + + virtual gfxImageFormat GetOffscreenFormat() override { return mOffscreenFormat; } + + already_AddRefed + GetScaledFontForFont(mozilla::gfx::DrawTarget* aTarget, gfxFont *aFont) override; + + // to support IPC font list (sharing between chrome and content) + void GetSystemFontList(InfallibleTArray* 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& 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 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 + (aShmem.get() + aShmem.Size() - sizeof(SharedImageInfo)); +} + +extern const cairo_user_data_key_t SHM_KEY; + +template +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 + static already_AddRefed + Create(ShmemAllocator* aAllocator, + const mozilla::gfx::IntSize& aSize, + gfxImageFormat aFormat, + SharedMemory::SharedMemoryType aShmType = SharedMemory::TYPE_BASIC) + { + return Create(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 + 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 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 + static already_AddRefed + CreateUnsafe(ShmemAllocator* aAllocator, + const mozilla::gfx::IntSize& aSize, + gfxImageFormat aFormat, + SharedMemory::SharedMemoryType aShmType = SharedMemory::TYPE_BASIC) + { + return Create(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(), 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 + static already_AddRefed + 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 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 dirtyRect; + if (aDirtyRect) { + dirtyRect = MakeUnique(Float(aDirtyRect->x), + Float(aDirtyRect->y), + Float(aDirtyRect->width), + Float(aDirtyRect->height)); + } + UniquePtr skipRect; + if (aSkipRect) { + skipRect = MakeUnique(Float(aSkipRect->x), + Float(aSkipRect->y), + Float(aSkipRect->width), + Float(aSkipRect->height)); + } + + mBlur = MakeUnique(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(blurDataSize); + if (!mData) { + return nullptr; + } + memset(mData.get(), 0, blurDataSize); + + RefPtr 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 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 +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 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 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 +{ + public: + BlurCache() + : nsExpirationTracker(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 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 +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 roundedRect = + MakePathForRoundedRect(*blurDT, Rect(minRect), *aCornerRadii); + blurDT->Fill(roundedRect, black); + } else { + blurDT->FillRect(Rect(minRect), black); + } + + IntPoint topLeft; + RefPtr 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 +CreateBoxShadow(DrawTarget& aDestDT, SourceSurface* aBlurMask, const Color& aShadowColor) +{ + IntSize blurredSize = aBlurMask->GetSize(); + RefPtr 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 +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 blur = cached->mBlur; + return blur.forget(); + } + + RefPtr blurMask = + CreateBlurMask(minSize, aCornerRadii, aBlurRadius, aExtendDestBy, aSlice, + destDT); + + if (!blurMask) { + return nullptr; + } + + RefPtr 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 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 +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 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 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 +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 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 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 minMask = DoBlur(minDrawTarget, &topLeft); + if (!minMask) { + return nullptr; + } + + // Fill in with the color we actually wanted + RefPtr 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 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 + 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 + 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 mContext; + + /** + * The temporary alpha surface. + */ + mozilla::UniquePtr mData; + + /** + * The object that actually does the blurring for us. + */ + mozilla::UniquePtr 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 + +#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 + +#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::CreateOrNull(DrawTarget* aTarget, + const mozilla::gfx::Point& aDeviceOffset) +{ + if (!aTarget || !aTarget->IsValid()) { + gfxCriticalNote << "Invalid target in gfxContext::CreateOrNull " << hexa(aTarget); + return nullptr; + } + + RefPtr result = new gfxContext(aTarget, aDeviceOffset); + return result.forget(); +} + +/* static */ already_AddRefed +gfxContext::CreatePreservingTransformOrNull(DrawTarget* aTarget) +{ + if (!aTarget || !aTarget->IsValid()) { + gfxCriticalNote << "Invalid target in gfxContext::CreatePreservingTransformOrNull " << hexa(aTarget); + return nullptr; + } + + Matrix transform = aTarget->GetTransform(); + RefPtr 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 gfxContext::GetPath() +{ + EnsurePath(); + RefPtr 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& 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 +gfxContext::GetPattern() +{ + RefPtr 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 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(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 mask = CurrentState().mBlendMask; + Matrix maskTransform = CurrentState().mBlendMaskTransform; + + RefPtr 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 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 = 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 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 + 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 + 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 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 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& 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 pattern; + RefPtr sourceSurfCairo; + RefPtr sourceSurface; + mozilla::gfx::Point sourceSurfaceDeviceOffset; + Matrix surfTransform; + Matrix transform; + struct PushedClip { + RefPtr path; + Rect rect; + Matrix transform; + }; + nsTArray pushedClips; + nsTArray dashPattern; + StrokeOptions strokeOptions; + RefPtr 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 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 mPathBuilder; + RefPtr mPath; + Matrix mTransform; + nsTArray mStateStack; + + AzureState &CurrentState() { return mStateStack[mStateStack.Length() - 1]; } + const AzureState &CurrentState() const { return mStateStack[mStateStack.Length() - 1]; } + + RefPtr 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 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 mColorPattern; + mozilla::AlignedStorage2 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 + +#include + +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(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 glyphsArray; + UniquePtr positionsArray; + UniquePtr 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(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(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(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 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 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 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 kDefaultFeatures[] = { + { kSmartSwashType, kLineInitialSwashesOffSelector }, + { kSmartSwashType, kLineFinalSwashesOffSelector } + }; + sDefaultFeaturesDescriptor = + CreateFontFeaturesDescriptor(kDefaultFeatures, + ArrayLength(kDefaultFeatures)); + } + return sDefaultFeaturesDescriptor; +} + +CTFontDescriptorRef +gfxCoreTextShaper::GetDisableLigaturesDescriptor() +{ + if (sDisableLigaturesDescriptor == nullptr) { + const std::pair kDisableLigatures[] = { + { kSmartSwashType, kLineInitialSwashesOffSelector }, + { kSmartSwashType, kLineFinalSwashesOffSelector }, + { kLigaturesType, kCommonLigaturesOffSelector } + }; + sDisableLigaturesDescriptor = + CreateFontFeaturesDescriptor(kDisableLigatures, + ArrayLength(kDisableLigatures)); + } + return sDisableLigaturesDescriptor; +} + +CTFontDescriptorRef +gfxCoreTextShaper::GetIndicFeaturesDescriptor() +{ + if (sIndicFeaturesDescriptor == nullptr) { + const std::pair kIndicFeatures[] = { + { kSmartSwashType, kLineFinalSwashesOffSelector } + }; + sIndicFeaturesDescriptor = + CreateFontFeaturesDescriptor(kIndicFeatures, + ArrayLength(kIndicFeatures)); + } + return sIndicFeaturesDescriptor; +} + +CTFontDescriptorRef +gfxCoreTextShaper::GetIndicDisableLigaturesDescriptor() +{ + if (sIndicDisableLigaturesDescriptor == nullptr) { + const std::pair kIndicDisableLigatures[] = { + { kSmartSwashType, kLineFinalSwashesOffSelector }, + { kLigaturesType, kCommonLigaturesOffSelector } + }; + sIndicDisableLigaturesDescriptor = + CreateFontFeaturesDescriptor(kIndicDisableLigatures, + ArrayLength(kIndicDisableLigatures)); + } + return sIndicDisableLigaturesDescriptor; +} + +CTFontRef +gfxCoreTextShaper::CreateCTFontWithFeatures(CGFloat aSize, + CTFontDescriptorRef aDescriptor) +{ + gfxMacFont *f = static_cast(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 + +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 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 + +#include "mozilla/Atomics.h" +#include "mozilla/gfx/Logging.h" + +static mozilla::Atomic sNextFontFileKey; +static std::unordered_map 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 *aData, + uint64_t aFontFileKey); + ~gfxDWriteFontFileStream(); + + // IUnknown interface + IFACEMETHOD(QueryInterface)(IID const& iid, OUT void** ppObject) + { + if (iid == __uuidof(IDWriteFontFileStream)) { + *ppObject = static_cast(this); + return S_OK; + } + else if (iid == __uuidof(IUnknown)) { + *ppObject = static_cast(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 mData; + nsAutoRefCnt mRefCnt; + uint64_t mFontFileKey; +}; + +gfxDWriteFontFileStream::gfxDWriteFontFileStream(FallibleTArray *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(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& 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 ffsRef = new gfxDWriteFontFileStream(&aFontData, fontFileKey); + sFontFileStreams[fontFileKey] = ffsRef; + + RefPtr 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 +#include + +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 *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(this); + return S_OK; + } else if (iid == __uuidof(IUnknown)) { + *ppObject = static_cast(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& 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 names; + hr = aFont->GetFaceNames(getter_AddRefs(names)); + if (FAILED(hr)) { + return hr; + } + + BOOL exists; + AutoTArray 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 infostrings; + hr = aFont->GetInformationalStrings(aWhichName, getter_AddRefs(infostrings), &exists); + if (FAILED(hr) || !exists) { + return E_FAIL; + } + + AutoTArray 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 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 ls = do_GetService(NS_LOCALESERVICE_CONTRACTID, + &rv); + nsCOMPtr 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 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 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 &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 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 mFontFace; + void *mContext; +}; + +static void +DestroyBlobFunc(void* aUserData) +{ + FontTableRec *ftr = static_cast(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(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 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(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 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 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(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(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 newFontData; + + rv = gfxFontUtils::RenameFont(uniqueName, aFontData, aLength, &newFontData); + free((void*)aFontData); + + if (NS_FAILED(rv)) { + return nullptr; + } + + RefPtr fontFileStream; + RefPtr 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 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 >& 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(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 family; + aCollection->GetFontFamily(i, getter_AddRefs(family)); + + RefPtr 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 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 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 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* 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 family; + + // clean out previous value + aFamilyName.Truncate(); + + hr = aFont->GetFontFamily(getter_AddRefs(family)); + if (FAILED(hr)) { + return hr; + } + + RefPtr 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 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 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 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 (aCh); + str[1] = 0; + strLen = 1; + } else { + str[0] = static_cast (H_SURROGATE(aCh)); + str[1] = static_cast (L_SURROGATE(aCh)); + str[2] = 0; + strLen = 2; + } + + // set up layout + RefPtr 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 mSystemFonts; +#ifdef MOZ_BUNDLED_FONTS + RefPtr mBundledFonts; +#endif +}; + +void +DirectWriteFontInfo::LoadFontFamilyData(const nsAString& aFamilyName) +{ + // lookup the family + AutoTArray 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 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 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 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 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 +gfxDWriteFontList::CreateFontInfoData() +{ + bool loadCmaps = !UsesSystemFallback() || + gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback(); + + RefPtr 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 mFactory; + + nsCOMPtr mFontDir; + nsCOMPtr mEntries; + nsCOMPtr 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 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 +gfxDWriteFontList::CreateBundledFontsCollection(IDWriteFactory* aFactory) +{ + nsCOMPtr 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 loader = new BundledFontLoader(); + if (FAILED(aFactory->RegisterFontCollectionLoader(loader))) { + return nullptr; + } + + const void *key = localDir.get(); + RefPtr 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 + + +/** + * 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 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(100, weight); + weight = std::min(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& 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 mFont; + RefPtr mFontFile; + + // For custom fonts, we hold a reference to the IDWriteFontFileStream for + // for the IDWriteFontFile, so that the data is available. + RefPtr mFontFileStream; + + // font face corresponding to the mFont/mFontFile *without* any DWrite + // style simulations applied + RefPtr 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 mSystemFonts; + nsString mFamilyName; +}; + + + +class gfxDWriteFontList : public gfxPlatformFontList { +public: + gfxDWriteFontList(); + + static gfxDWriteFontList* PlatformFontList() { + return static_cast(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* 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 + CreateBundledFontsCollection(IDWriteFactory* aFactory); +#endif + + /** + * Fonts listed in the registry as substitutes but for which no actual + * font family is found. + */ + nsTArray mNonExistingFonts; + + /** + * Table of font substitutes, we grab this from the registry to get + * alternative font names. + */ + FontFamilyTable mFontSubstitutes; + + virtual already_AddRefed CreateFontInfoData(); + + gfxFloat mForceGDIClassicMaxFontSize; + + // whether to use GDI font table access routines + bool mGDIFontTableAccess; + RefPtr mGDIInterop; + + RefPtr mFallbackRenderer; + RefPtr mFallbackFormat; + + RefPtr mSystemFonts; +#ifdef MOZ_BUNDLED_FONTS + RefPtr 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 +#include "gfxDWriteFontList.h" +#include "gfxContext.h" +#include "gfxTextRun.h" +#include + +#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(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(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 font = fe->FindOrMakeFont(&style, needsBold); + gfxDWriteFont *dwFont = static_cast(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(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 + (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(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(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(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(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(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>(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 +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(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 +gfxDWriteFont::GetScaledFont(mozilla::gfx::DrawTarget *aTarget) +{ + bool wantCairo = aTarget->GetBackendType() == BackendType::CAIRO; + if (mAzureScaledFont && mAzureScaledFontIsCairo == wantCairo) { + RefPtr 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(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(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 + +#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 + 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 + 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 mFontFace; + cairo_font_face_t *mCairoFontFace; + + Metrics *mMetrics; + + // cache of glyph widths in 16.16 fixed-point pixels + mozilla::UniquePtr> 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 +gfxCallbackDrawable::MakeSurfaceDrawable(const SamplingFilter aSamplingFilter) +{ + SurfaceFormat format = + gfxPlatform::GetPlatform()->Optimal2DFormatForContent(gfxContentType::COLOR_ALPHA); + RefPtr dt = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(mSize, + format); + if (!dt || !dt->IsValid()) + return nullptr; + + RefPtr 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 surface = dt->Snapshot(); + if (surface) { + RefPtr 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 mDrawable; +}; + +already_AddRefed +gfxPatternDrawable::MakeCallbackDrawable() +{ + RefPtr callback = + new DrawingCallbackFromDrawable(this); + RefPtr 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 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 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 + MakeSurfaceDrawable(mozilla::gfx::SamplingFilter aSamplingFilter = + mozilla::gfx::SamplingFilter::LINEAR); + + RefPtr mCallback; + RefPtr 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 MakeCallbackDrawable(); + + RefPtr 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 + (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 + (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 +#include +#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 +#include +#include + +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 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(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(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(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 (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 charmap = new gfxCharacterMap(); + + AutoTArray 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& 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( + 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* aFontList, + Visibility aVisibility) +{ + for (int i = 0, n = mAvailableFonts.Length(); i < n; ++i) { + const FT2FontEntry *fe = + static_cast(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(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 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(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(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(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(key)); + } + + static bool HashMatchEntry(const PLDHashEntryHdr *aHdr, const void *key) + { + const FNCMapEntry* entry = + static_cast(aHdr); + return entry->mFilename.Equals(reinterpret_cast(key)); + } + + static void MoveEntry(PLDHashTable *table, const PLDHashEntryHdr *aFrom, + PLDHashEntryHdr *aTo) + { + FNCMapEntry* to = static_cast(aTo); + const FNCMapEntry* from = static_cast(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 obs = services::GetObserverService(); + if (obs) { + mObserver = new WillShutdownObserver(this); + obs->AddObserver(mObserver, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false); + } +} + +gfxFT2FontList::~gfxFT2FontList() +{ + if (mObserver) { + nsCOMPtr 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 + (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, ×tamp, &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 cachedModifiedTimeBuf; + uint32_t longSize; + if (cache && + NS_SUCCEEDED(cache->GetBuffer(JAR_LAST_MODIFED_TIME, + &cachedModifiedTimeBuf, + &longSize)) && + longSize == sizeof(int64_t)) + { + nsCOMPtr 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 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 fe = + CreateNamedFontEntry(aFace, aEntryName.get(), aIndex); + + auto& fontFamilies = + (aVisibility == FT2FontFamily::kHidden) ? mHiddenFontFamilies : + mFontFamilies; + + if (fe) { + NS_ConvertUTF8toUTF16 name(aFace->family_name); + BuildKeyNameFromFontName(name); + RefPtr 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, ×tamp, &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(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& 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 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& family = iter.Data(); + FinalizeFamilyMemberList(key, family, /* aSortFaces */ false); + } + for (auto iter = mHiddenFontFamilies.Iter(); !iter.Done(); iter.Next()) { + nsStringHashKey::KeyType key = iter.Key(); + RefPtr& 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(); + } + 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 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 dirSvc = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID); + if (dirSvc) { + nsCOMPtr 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 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& family = iter.Data(); + FinalizeFamilyMemberList(key, family, /* aSortFaces */ true); + } + for (auto iter = mHiddenFontFamilies.Iter(); !iter.Done(); iter.Next()) { + nsStringHashKey::KeyType key = iter.Key(); + RefPtr& 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 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* retValue) +{ + for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { + auto family = static_cast(iter.Data().get()); + family->AddFacesToFontList(retValue, FT2FontFamily::kVisible); + } + for (auto iter = mHiddenFontFamilies.Iter(); !iter.Done(); iter.Next()) { + auto family = static_cast(iter.Data().get()); + family->AddFacesToFontList(retValue, FT2FontFamily::kHidden); + } +} + +static void +LoadSkipSpaceLookupCheck(nsTHashtable& aSkipSpaceLookupCheck) +{ + AutoTArray 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& 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(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( + 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(); + 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& 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& 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 >& 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(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 >& aFamilyArray) +{ + for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { + RefPtr& family = iter.Data(); + aFamilyArray.AppendElement(family); + } + for (auto iter = mHiddenFontFamilies.Iter(); !iter.Done(); iter.Next()) { + RefPtr& 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& 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* 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* retValue); + + static gfxFT2FontList* PlatformFontList() { + return static_cast(gfxPlatformFontList::PlatformFontList()); + } + + virtual void GetFontFamilyList(nsTArray >& 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 mSkipSpaceLookupCheckFamilies; + +private: + FontFamilyTable mHiddenFontFamilies; + + mozilla::UniquePtr mFontNameCache; + int64_t mJarModifiedTime; + nsCOMPtr 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 +#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 CharGlyphMapEntryType; + typedef nsTHashtable 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 + +#ifdef HAVE_FONTCONFIG_FCFREETYPE_H +#include +#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(FT_Get_Sfnt_Table(mFace, ft_sfnt_head)); + if (head) { + gfxFloat emUnit = head->Units_Per_EM; + yScale = emHeight / emUnit; + } + } + + TT_OS2 *os2 = + static_cast(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 + (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 + (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 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 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 + +#ifdef MOZ_WIDGET_GTK +#include +#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(aStr); +} + +static const char* +ToCharPtr(const FcChar8 *aStr) +{ + return reinterpret_cast(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 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(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 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(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 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 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& 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 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& 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(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 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 + (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& aListOfFonts, nsIAtom *aLangGroup) +{ + aListOfFonts.Clear(); + + nsAutoRef pat(FcPatternCreate()); + if (!pat) { + return; + } + + nsAutoRef 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 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& 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* 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 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 sentinelSubst(FcPatternCreate()); + FcPatternAddString(sentinelSubst, FC_FAMILY, kSentinelName); + FcConfigSubstitute(nullptr, sentinelSubst, FcMatchPattern); + FcPatternGetString(sentinelSubst, FC_FAMILY, 0, &sentinelFirstFamily); + + // substitutions for font, -moz-sentinel pattern + nsAutoRef 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 pat(FcPatternCreate()); + if (!pat) { + return true; + } + + nsAutoRef 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 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 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 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& 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 font = fe->FindOrMakeFont(&style, false); + if (!font) { + return nullptr; + } + + gfxFT2FontBase* ft2Font = reinterpret_cast(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 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 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 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(aThis); + FcConfig* current = FcConfigGetCurrent(); + if (current != pfl->GetLastConfig()) { + pfl->UpdateFontList(); + pfl->ForceGlobalReflow(); + } +} + +#ifdef MOZ_BUNDLED_FONTS +void +gfxFcPlatformFontList::ActivateBundledFonts() +{ + if (!mBundledFontsInitialized) { + mBundledFontsInitialized = true; + nsCOMPtr 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 +#include "ft2build.h" +#include FT_FREETYPE_H +#include FT_TRUETYPE_TABLES_H +#include +#include + +#include "gfxFontconfigUtils.h" // xxx - only for nsAutoRefTraits, etc. + +template <> +class nsAutoRefTraits : public nsPointerRefTraits +{ +public: + static void Release(FcObjectSet *ptr) { FcObjectSetDestroy(ptr); } +}; + +template <> +class nsAutoRefTraits : public nsPointerRefTraits +{ +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(aData); + delete aUserFontDataRef; + } + +private: + RefPtr 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& 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 mFontPattern; + + // user font data, when needed + RefPtr 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& aFontEntryList, + bool& aNeedsSyntheticBold) override; + +protected: + virtual ~gfxFontconfigFontFamily() { } + + nsTArray > 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(sPlatformFontList); + } + + // initialize font lists + virtual nsresult InitFontListForPlatform() override; + + void GetFontList(nsIAtom *aLangGroup, + const nsACString& aGenericFamily, + nsTArray& 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* 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& 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, + FcPattern*> mLocalNames; + + // caching generic/lang ==> font family list + nsClassHashtable 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> mFcSubstituteCache; + + nsCOMPtr mCheckFontUpdatesTimer; + nsCountedRef 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 +#include +#include + +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(FONT_TIMEOUT_SECONDS * 1000, + "gfxFontCache") +{ + nsCOMPtr 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 +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 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(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& altValue, + nsTArray& aFontFeatures) +{ + uint32_t numAlternates = altValue.Length(); + for (uint32_t i = 0; i < numAlternates; i++) { + const gfxAlternateValue& av = altValue.ElementAt(i); + AutoTArray values; + + // map ==> + 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& 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& 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 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 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(); + } + + 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(this); + } + gfxHarfBuzzShaper* shaper = + static_cast(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& + 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& + 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 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 *gfxFont::sScriptTagToCode = nullptr; +nsTHashtable *gfxFont::sDefaultFeatures = nullptr; + +static inline bool +HasSubstitution(uint32_t *aBitVector, Script aScript) { + return (aBitVector[static_cast(aScript) >> 5] + & (1 << (static_cast(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(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(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(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(s) >> 5; + uint32_t bit = static_cast(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(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(this); + } + gfxHarfBuzzShaper* shaper = + static_cast(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& 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& 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 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(pat)->mMatrix; + } else if (pat->GetType() == PatternType::RADIAL_GRADIENT) { + mat = &static_cast(pat)->mMatrix; + } else if (pat->GetType() == PatternType::SURFACE) { + mat = &static_cast(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(pat)->mMatrix; + } else if (pat->GetType() == PatternType::RADIAL_GRADIENT) { + mat = &static_cast(pat)->mMatrix; + } else if (pat->GetType() == PatternType::SURFACE) { + mat = &static_cast(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 = + 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::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::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 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 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 layerGlyphs; + AutoTArray 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 +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 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(this); + } + ok = mGraphiteShaper->ShapeText(aDrawTarget, aText, aOffset, aLength, + aScript, aVertical, aShapedText); + } + } + + if (!ok) { + if (!mHarfBuzzShaper) { + mHarfBuzzShaper = MakeUnique(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 +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 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 +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 +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 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 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 charsToMergeArray; + AutoTArray 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 tempRun( + gfxTextRun::Create(¶ms, 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 mergedRun( + gfxTextRun::Create(¶ms, 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(aText), + aLength); + return InitFakeSmallCapsRun(aDrawTarget, aTextRun, static_cast(unicodeString.get()), + aOffset, aLength, aMatchType, aOrientation, + aScript, aSyntheticLower, aSyntheticUpper); +} + +already_AddRefed +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::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(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 + (aDT->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT)); + if (refCairo) { + return refCairo; + } + } + + refCairo = static_cast(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 + (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(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(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(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 + (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 + (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(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>); + } + 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(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 +#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 +#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 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 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 alternateValues; + + // -- object used to look these up once the font is matched + RefPtr 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(&size) == + *reinterpret_cast(&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(&sizeAdjust) == + *reinterpret_cast(&other.sizeAdjust)) && + (featureSettings == other.featureSettings) && + (languageOverride == other.languageOverride) && + (alternateValues == other.alternateValues) && + (featureValueLookup == other.featureValueLookup); + } + + static void ParseFontFeatureSettings(const nsString& aFeatureString, + nsTArray& 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 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 { +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 + 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 mFonts; + + static void WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache); + nsCOMPtr 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(¤t, 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& 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::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 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 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::index_type mLastUsed; + }; + + mozilla::UniquePtr 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(mCharGlyphsStorage + GetLength()); + } + + const char16_t* TextUnicode() const { + NS_ASSERTION(!TextIs8Bit(), "invalid use of TextUnicode()"); + return reinterpret_cast(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(&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(&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 + 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 + 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 + 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 + 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>(); + } + } + + // 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 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 + 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 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 + 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 + 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 *sScriptTagToCode; + static nsTHashtable *sDefaultFeatures; + + RefPtr 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(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(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 mShapedWord; + }; + + mozilla::UniquePtr > 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> mGlyphExtentsArray; + mozilla::UniquePtr>> + 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 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 mHarfBuzzShaper; + mozilla::UniquePtr mGraphiteShaper; + + // if a userfont with unicode-range specified, contains map of *possible* + // ranges supported by font + RefPtr mUnicodeRangeMap; + + RefPtr mAzureScaledFont; + + // For vertical metrics, created on demand. + mozilla::UniquePtr mVerticalMetrics; + + // Table used for MathML layout. + mozilla::UniquePtr 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 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 scaledFont; + RefPtr 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 + +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 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 +gfxFontEntry::FindOrMakeFont(const gfxFontStyle *aStyle, + bool aNeedsBold, + gfxCharacterMap* aUnicodeRangeMap) +{ + // the font entry name is the psname, not the family name + RefPtr 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(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(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&& 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(mTableData.Elements()); + } + uint32_t GetTableLength() const { return mTableData.Length(); } + + // Tell this FontTableBlobData to remove the HashEntry when this is + // destroyed. + void ManageHashEntry(nsTHashtable *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 mTableData; + + // The blob destroy function needs to know the owning hashtable + // and the hashtable key, so that it can remove the entry. + nsTHashtable *mHashtable; + uint32_t mHashKey; + + // not implemented + FontTableBlobData(const FontTableBlobData&); +}; + +hb_blob_t * +gfxFontEntry::FontTableHashEntry:: +ShareTableAndGetBlob(nsTArray&& aTable, + nsTHashtable *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(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>(8); + } + + FontTableHashEntry *entry = mFontTableCache->GetEntry(aTag); + if (!entry) { + return false; + } + + *aBlob = entry->GetBlob(); + return true; +} + +hb_blob_t * +gfxFontEntry::ShareFontTableAndGetBlob(uint32_t aTag, + nsTArray* 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>(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 +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 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(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(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(const_cast(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(const_cast(aAppFaceHandle)); + void *data; + if (fontEntry->mGrTableMap->Get(aTableBuffer, &data)) { + fontEntry->mGrTableMap->Remove(aTableBuffer); + hb_blob_destroy(static_cast(data)); + } +} + +gr_face* +gfxFontEntry::GetGrFace() +{ + if (!mGrFaceInitialized) { + gr_face_ops faceOps = { + sizeof(gr_face_ops), + GrGetTable, + GrReleaseTable + }; + mGrTableMap = new nsDataHashtable,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(s))) + +bool +gfxFontEntry::SupportsOpenTypeFeature(Script aScript, uint32_t aFeatureTag) +{ + if (!mSupportedFeatures) { + mSupportedFeatures = MakeUnique>(); + } + + // 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>(); + } + + 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>(); + } + + // 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& aLayerGlyphs, + nsTArray& 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& a, const RefPtr& b) const { + return a->mStandardFace == b->mStandardFace; + } + bool LessThan(const RefPtr& a, const RefPtr& 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 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& 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(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& aOtherFamilyNames, + bool useFullName) +{ + const gfxFontUtils::NameHeader *nameHeader = + reinterpret_cast(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(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 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 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 +#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& layerGlyphs, + nsTArray& 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 + 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* 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 mCharacterMap; + uint32_t mUVSOffset; + mozilla::UniquePtr mUVSData; + mozilla::UniquePtr mUserFontData; + mozilla::UniquePtr mSVGGlyphs; + // list of gfxFonts that are using SVG glyphs + nsTArray mFontsUsingSVGGlyphs; + nsTArray mFeatureSettings; + mozilla::UniquePtr> mSupportedFeatures; + mozilla::UniquePtr> 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& aBuffer) { + NS_NOTREACHED("forgot to override either GetFontTable or CopyFontTable?"); + return NS_ERROR_FAILURE; + } + + // lookup the cmap in cached font data + virtual already_AddRefed + 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,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&& aTable, + nsTHashtable *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 > 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 mBestMatch; // current best match + RefPtr 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 >& GetFontList() { return mAvailableFonts; } + + void AddFontEntry(RefPtr 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& 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& 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 > 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& 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& 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 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& 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& 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& aSelectors) + : name(aName), featureSelectors(aSelectors) + {} + nsString name; + nsTArray featureSelectors; + }; + + struct FeatureValues { + uint32_t alternate; + nsTArray valuelist; + }; + + // returns true if found, false otherwise + bool + GetFontFeatureValuesFor(const nsAString& aFamily, + uint32_t aVariantProperty, + const nsAString& aName, + nsTArray& aValues); + void + AddFontFeatureValues(const nsAString& aFamily, + const nsTArray& 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 mValues; + }; + + nsTHashtable 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 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 mFontInfo; + RefPtr 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 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(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 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 obs = GetObserverService(); + if (obs) { + mObserver = new ShutdownObserver(this); + obs->AddObserver(mObserver, "quit-application", false); + } +} + +void +gfxFontInfoLoader::RemoveShutdownObserver() +{ + if (mObserver) { + nsCOMPtr 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 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 + 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 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& aOtherFamilyNames) + { + return mOtherFamilyNames.Get(aFamilyName, &aOtherFamilyNames); + } + + nsTArray mFontFamiliesToLoad; + + // currently non-issue but beware, + // this is also set during cleanup after finishing + mozilla::Atomic 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 mFontFaceData; + + // canonical family name ==> array of localized family names + nsDataHashtable > 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 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(aThis); + loader->LoadFontInfoTimerFire(); + } + + static void DelayedStartCallback(nsITimer *aTimer, void *aThis) { + gfxFontInfoLoader *loader = static_cast(aThis); + loader->StartLoader(0, loader->GetInterval()); + } + + void LoadFontInfoTimerFire(); + + void AddShutdownObserver(); + void RemoveShutdownObserver(); + + nsCOMPtr mTimer; + nsCOMPtr mObserver; + nsCOMPtr mFontLoaderThread; + uint32_t mInterval; + TimerState mState; + + // after async font loader completes, data is stored here + RefPtr 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 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 = 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(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(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 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(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(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(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(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(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(skipCode - 1, + endCount)); + } + if (skipCode < endCount) { + aCharacterMap.SetRange(std::max(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& 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(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(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(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(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(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(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(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 + (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& 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& 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& 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 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(&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(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(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(aKey); + const TableDirEntry* entry = static_cast(aItem); + return tag - int32_t(entry->tag); +} + +/* static */ +TableDirEntry* +gfxFontUtils::FindTableDirEntry(const void* aFontData, uint32_t aTableTag) +{ + const SFNTHeader* header = + reinterpret_cast(aFontData); + const TableDirEntry* dir = + reinterpret_cast(header + 1); + return static_cast + (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(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 *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(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(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(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(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(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 (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(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(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(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& 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 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(aName.BeginWriting()), + strLen); +#else + memcpy(aName.BeginWriting(), aNameData, strLen * 2); +#endif + return true; + } + + nsCOMPtr 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& aNames) +{ + NS_ASSERTION(aDataLen != 0, "null name table"); + + if (!aDataLen) { + return NS_ERROR_FAILURE; + } + + // -- name table data + const NameHeader *nameHeader = reinterpret_cast(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(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(hb_blob_get_data(aCOLR, &colrLength)); + unsigned int cpalLength; + const CPALHeaderVersion0* cpal = + reinterpret_cast(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( + reinterpret_cast(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( + reinterpret_cast(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(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(aCOLR) + + uint32_t(aCOLR->offsetBaseGlyphRecord); + // BaseGlyphRecord is sorted by glyphId + return reinterpret_cast( + 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& aGlyphs, + nsTArray& aColors) +{ + unsigned int blobLength; + const COLRHeader* colr = + reinterpret_cast(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( + 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( + reinterpret_cast(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( + reinterpret_cast(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(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 + +/* Bug 341128 - w32api defines min/max which causes problems with */ +#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); + } + } + } + + 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(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(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(*aBitset.mBlocks[i]); + continue; + } + // else set existing block to the union of both + uint32_t *dst = reinterpret_cast(mBlocks[i]->mBits); + const uint32_t *src = + reinterpret_cast(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> 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(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& 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 + // 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 *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& 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& aFontList); + + // for a given font list pref name, append list of font names + static void AppendPrefsFontList(const char *aPrefName, + nsTArray& aFontList); + + // for a given font list pref name, initialize a list of font names + static void GetPrefsFontList(const char *aPrefName, + nsTArray& 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 &aGlyphs, + nsTArray &aColors); + +protected: + friend struct MacCharsetMappingComparator; + + static nsresult + ReadNames(const char *aNameData, uint32_t aDataLen, uint32_t aNameID, + int32_t aLangID, int32_t aPlatformID, nsTArray& 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 +#include +#include "mozilla/gfx/HelpersCairo.h" + +#include +#include + +#include FT_TRUETYPE_TABLES_H + +#ifdef MOZ_WIDGET_GTK +#include +#endif + +#include + +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 >& GetPatterns() + { + return mPatterns; + } + + static gfxFcFontEntry *LookupFontEntry(cairo_font_face_t *aFace) + { + return static_cast + (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,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& aBuffer) override; + + void MaybeReleaseFTFace(); + +private: + cairo_font_face_t *mFontFace; + FT_Face mFTFace; + bool mFTFaceInitialized; +}; + +nsresult +gfxSystemFcFontEntry::CopyFontTable(uint32_t aTableTag, + nsTArray& 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 >& 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(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 + (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 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(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 + GetOrMakeFont(FcPattern *aRequestedPattern, FcPattern *aFontPattern, + const gfxFontStyle *aFontStyle); + + // return a cloned font resized and offset to simulate sub/superscript glyphs + virtual already_AddRefed + GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel) override; + +protected: + virtual already_AddRefed MakeScaledFont(gfxFontStyle *aFontStyle, + gfxFloat aFontScale); + virtual already_AddRefed 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 SortPreferredFonts(bool& aWaitForUserFont); + nsReturnRef SortFallbackFonts(); + + struct FontEntry { + explicit FontEntry(FcPattern *aPattern) : mPattern(aPattern) {} + nsCountedRef mPattern; + RefPtr 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 mSortPattern; + // Fonts from @font-face rules + RefPtr mUserFontSet; + // A (trimmed) list of font patterns and fonts that is built up as + // required. + nsTArray 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 mFcFontSet; + // The set of characters supported by the fonts in mFonts. + nsAutoRef 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 >* +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 + (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 + (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 + (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 +gfxFcFontSet::SortPreferredFonts(bool &aWaitForUserFont) +{ + aWaitForUserFont = false; + + gfxFontconfigUtils *utils = gfxFontconfigUtils::GetFontconfigUtils(); + if (!utils) + return nsReturnRef(); + + // 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 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 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 existingFamilies(32); + FcChar8 *family; + for (int v = 0; + FcPatternGetString(mSortPattern, + FC_FAMILY, v, &family) == FcResultMatch; ++v) { + const nsTArray< nsCountedRef > *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 >& 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 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 +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(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 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& 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& 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 *list = static_cast*>(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(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 +gfxPangoFontGroup::MakeFontSet(PangoLanguage *aLang, gfxFloat aSizeAdjustFactor, + nsAutoRef *aMatchPattern) +{ + const char *lang = pango_language_to_string(aLang); + + RefPtr langGroup; + if (aLang != mPangoLanguage) { + // Set up langGroup for Mozilla's font prefs. + langGroup = NS_Atomize(lang); + } + + AutoTArray 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 pattern + (gfxFontconfigUtils::NewPattern(fcFamilyList, mStyle, lang)); + + PrepareSortPattern(pattern, mStyle.size, aSizeAdjustFactor, mStyle.printerFont); + + RefPtr 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 fontSet = + MakeFontSet(aLang, mSizeAdjustFactor); + mFontSets.AppendElement(FontSetByLangEntry(aLang, fontSet)); + + return fontSet; +} + +already_AddRefed +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(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(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(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(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(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 +gfxFcFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel) +{ + gfxFontStyle style(*GetStyle()); + style.AdjustForSubSuperscript(aAppUnitsPerDevPixel); + return MakeScaledFont(&style, style.size / GetStyle()->size); +} + +already_AddRefed +gfxFcFont::MakeScaledFont(gfxFontStyle *aFontStyle, gfxFloat aScaleFactor) +{ + gfxFcFontEntry* fe = static_cast(GetFontEntry()); + RefPtr 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 +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 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 >& 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 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::GetOrMakeFont(FcPattern *aRequestedPattern, FcPattern *aFontPattern, + const gfxFontStyle *aFontStyle) +{ + nsAutoRef 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 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 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 retval(static_cast(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 pattern; + RefPtr 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 + +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 + 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 mFontSet; + }; + // There is only one of entry in this array unless characters from scripts + // of other languages are measured. + AutoTArray 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 + MakeFontSet(PangoLanguage *aLang, gfxFloat aSizeAdjustFactor, + nsAutoRef *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& 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 +#include + +#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 +gfxFontconfigUtils::NewPattern(const nsTArray& aFamilies, + const gfxFontStyle& aFontStyle, + const char *aLang) +{ + static const char* sFontconfigGenerics[] = + { "sans-serif", "serif", "monospace", "fantasy", "cursive" }; + + nsAutoRef pattern(FcPatternCreate()); + if (!pattern) + return nsReturnRef(); + + 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& aListOfFonts) +{ + aListOfFonts.Clear(); + + nsTArray 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& 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 + // 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 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 >& +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 >& +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 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 >& +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 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 + + +template <> +class nsAutoRefTraits : public nsPointerRefTraits +{ +public: + static void Release(FcPattern *ptr) { FcPatternDestroy(ptr); } + static void AddRef(FcPattern *ptr) { FcPatternReference(ptr); } +}; + +template <> +class nsAutoRefTraits : public nsPointerRefTraits +{ +public: + static void Release(FcFontSet *ptr) { FcFontSetDestroy(ptr); } +}; + +template <> +class nsAutoRefTraits : public nsPointerRefTraits +{ +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& aListOfFonts); + + nsresult UpdateFontList(); + + nsresult GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName); + + const nsTArray< nsCountedRef >& + GetFontsForFamily(const FcChar8 *aFamilyName); + + const nsTArray< nsCountedRef >& + 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 >& + 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(aCharPtr); + } + static const FcChar8 *ToFcChar8(const nsCString& aCString) + { + return ToFcChar8(aCString.get()); + } + static const char *ToCString(const FcChar8 *aChar8Ptr) + { + return reinterpret_cast(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 + NewPattern(const nsTArray& 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 >& GetFonts() { + return mFonts; + } + private: + nsTArray< nsCountedRef > 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 >& GetFonts() { + return mFonts; + } + + // Don't memmove the AutoTArray. + enum { ALLOW_MEMMOVE = false }; + private: + // There is usually only one font, but sometimes more. + AutoTArray,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 > mFonts; + }; + + static gfxFontconfigUtils* sUtils; + + bool IsExistingFamily(const nsCString& aFamilyName); + + nsresult GetFontListInternal(nsTArray& 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 mFontsByFamily; + nsTHashtable mFontsByFullname; + // mLangSupportTable contains an entry for each language that has been + // looked up through GetLangSupportEntry, even when the language is not + // supported. + nsTHashtable mLangSupportTable; + const nsTArray< nsCountedRef > 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 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 +#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(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(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(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(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(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>(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>(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 > mGlyphIDs; + SCRIPT_CACHE mScriptCache; + + // cache of glyph widths in 16.16 fixed-point pixels + mozilla::UniquePtr > 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 + +#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 + +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 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 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 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& 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 tempFont = FindOrMakeFont(&fakeStyle, false); + if (!tempFont || !tempFont->Valid()) + return false; + gfxGDIFont *font = static_cast(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(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(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(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(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(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(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 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 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(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(aFontData + uint32_t(dir->offset)); + CmapEncodingRecord *encRec = + reinterpret_cast(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 newFontData; + + rv = gfxFontUtils::RenameFont(uniqueName, aFontData, aLength, &newFontData); + + if (NS_FAILED(rv)) + return nullptr; + + DWORD numFonts = 0; + + uint8_t *fontData = reinterpret_cast (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* 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 mOtherFamilyNames; + GDIFontInfo& mFontInfo; + nsString mPreviousFontName; +}; + +int CALLBACK GDIFontInfo::EnumerateFontsForFamily( + const ENUMLOGFONTEXW *lpelfe, + const NEWTEXTMETRICEXW *nmetrics, + DWORD fontType, LPARAM data) +{ + EnumerateFontsForFamilyData *famData = + reinterpret_cast(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 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 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 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(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 +gfxGDIFontList::CreateFontInfoData() +{ + bool loadCmaps = !UsesSystemFallback() || + gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback(); + + RefPtr fi = + new GDIFontInfo(true, NeedFullnamePostscriptNames(), loadCmaps); + + return fi.forget(); +} + +#ifdef MOZ_BUNDLED_FONTS + +void +gfxGDIFontList::ActivateBundledFonts() +{ + nsCOMPtr 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 e; + rv = localDir->GetDirectoryEntries(getter_AddRefs(e)); + if (NS_FAILED(rv)) { + return; + } + + bool hasMore; + while (NS_SUCCEEDED(e->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr entry; + if (NS_FAILED(e->GetNext(getter_AddRefs(entry)))) { + break; + } + nsCOMPtr 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 + +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& 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(sPlatformFontList); + } + + // initialize font lists + virtual nsresult InitFontListForPlatform() override; + + bool FindAndAddFamilies(const nsAString& aFamily, + nsTArray* 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 CreateFontInfoData(); + +#ifdef MOZ_BUNDLED_FONTS + void ActivateBundledFonts(); +#endif + + FontFamilyTable mFontSubstitutes; + nsTArray 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 +#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 +#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(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(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(newBlock); + } else { + newBlock = reinterpret_cast(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(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 mBlocks; + }; + + GlyphWidths mContainedGlyphWidths; + nsTHashtable 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 + +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 mStops; + ExtendMode mExtend; + BackendType mBackendType; + + GradientCacheKey(const nsTArray& 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 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 +{ + public: + GradientCache() + : nsExpirationTracker(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& 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 mHashEntries; +}; + +static GradientCache* gGradientCache = nullptr; + +GradientStops * +gfxGradientCache::GetGradientStops(const DrawTarget *aDT, nsTArray& 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 +gfxGradientCache::GetOrCreateGradientStops(const DrawTarget *aDT, nsTArray& aStops, ExtendMode aExtend) +{ + if (aDT->IsRecording()) { + return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), aExtend); + } + + RefPtr 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& aStops, + gfx::ExtendMode aExtend); + + static already_AddRefed + GetOrCreateGradientStops(const gfx::DrawTarget *aDT, + nsTArray& 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(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(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 clusters; + AutoTArray gids; + AutoTArray xLocs; + AutoTArray 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 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 *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(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 *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 + +#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  , 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(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(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(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(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(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(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(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(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(hb_blob_get_data(mVORGTable, nullptr)); + + const VORGrec *lo = reinterpret_cast(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 + (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 + (&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(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(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(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(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(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(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 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(aSubtable); + + const KernPair *lo = reinterpret_cast(hdr + 1); + const KernPair *hi = lo + uint16_t(hdr->nPairs); + const KernPair *limit = hi; + + if (reinterpret_cast(aSubtable) + aSubtableLen < + reinterpret_cast(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(aSubtable); + const char* subtableEnd = base + aSubtableLen; + + const KernHeaderVersion1Fmt2* h = + reinterpret_cast(aSubtable); + uint32_t offset = h->array; + + const KernClassTableHdr* leftClassTable = + reinterpret_cast(base + + uint16_t(h->leftOffsetTable)); + if (reinterpret_cast(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(leftClassTable) + + sizeof(KernClassTableHdr) + + aFirstGlyph * sizeof(uint16_t) >= subtableEnd) { + return 0; + } + offset = uint16_t(leftClassTable->offsets[aFirstGlyph]); + } + } + + const KernClassTableHdr* rightClassTable = + reinterpret_cast(base + + uint16_t(h->rightOffsetTable)); + if (reinterpret_cast(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(rightClassTable) + + sizeof(KernClassTableHdr) + + aSecondGlyph * sizeof(uint16_t) >= subtableEnd) { + return 0; + } + offset += uint16_t(rightClassTable->offsets[aSecondGlyph]); + } + } + + const AutoSwap_PRInt16* pval = + reinterpret_cast(base + offset); + if (reinterpret_cast(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(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(hdr + 1); + const uint8_t* leftClass = + reinterpret_cast(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 , 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(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 + (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(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 + (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(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* features = static_cast*> (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 + (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 + (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 + (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(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 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(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 detailedGlyphs; + + uint32_t wordLength = aLength; + static const int32_t NO_GLYPH = -1; + AutoTArray 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(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& 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 + +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 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 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 +gfxImageSurface::CopyToB8G8R8A8DataSourceSurface() +{ + RefPtr dataSurface = + Factory::CreateDataSourceSurface(IntSize(GetSize().width, GetSize().height), + SurfaceFormat::B8G8R8A8); + if (dataSurface) { + CopyTo(dataSurface); + } + return dataSurface.forget(); +} + +already_AddRefed +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 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::GetAsImageSurface() +{ + RefPtr 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 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 GetSubimage(const gfxRect& aRect); + + virtual already_AddRefed 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 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 +#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(GetFontEntry())->RequiresAATLayout() && + !aVertical) { + if (!mCoreTextShaper) { + mCoreTextShaper = MakeUnique(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(::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(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(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 +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(mAzureScaledFont); + return scaledFont.forget(); +} + +already_AddRefed +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 + +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 + GetScaledFont(mozilla::gfx::DrawTarget *aTarget) override; + + virtual already_AddRefed + 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 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 + +#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 mAvailableTables; +}; + +class gfxMacPlatformFontList : public gfxPlatformFontList { +public: + static gfxMacPlatformFontList* PlatformFontList() { + return static_cast(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* 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 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 + * Masayuki Nakano + * John Daggett + * Jonathan Kew + * + * 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 + +#import + +#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 +#include +#include + +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(aDist.BeginWriting())]; +} + +static NSString* GetNSStringForString(const nsAString& aSrc) +{ + return [NSString stringWithCharacters:reinterpret_cast(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 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(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(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 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 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 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 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(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(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 buffer; + CFIndex familyNameLen = ::CFStringGetLength(familyNameRef); + buffer.SetLength(familyNameLen+1); + ::CFStringGetCharacters(familyNameRef, ::CFRangeMake(0, familyNameLen), + buffer.Elements()); + buffer[familyNameLen] = 0; + nsDependentString familyNameString(reinterpret_cast(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(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* 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 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 buffer; + CFIndex len = CFStringGetLength(faceName); + buffer.SetLength(len+1); + CFStringGetCharacters(faceName, ::CFRangeMake(0, len), + buffer.Elements()); + buffer[len] = 0; + nsAutoString fontName(reinterpret_cast(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 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 +gfxMacPlatformFontList::CreateFontInfoData() +{ + bool loadCmaps = !UsesSystemFallback() || + gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback(); + + RefPtr fi = + new MacFontInfo(true, NeedFullnamePostscriptNames(), loadCmaps); + return fi.forget(); +} + +#ifdef MOZ_BUNDLED_FONTS + +void +gfxMacPlatformFontList::ActivateBundledFonts() +{ + nsCOMPtr 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 e; + rv = localDir->GetDirectoryEntries(getter_AddRefs(e)); + if (NS_FAILED(rv)) { + return; + } + + bool hasMore; + while (NS_SUCCEEDED(e->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr entry; + if (NS_FAILED(e->GetNext(getter_AddRefs(entry)))) { + break; + } + nsCOMPtr 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(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((x)) +#define CONST_CAIRO_MATRIX(x) reinterpret_cast((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 + +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(mGfxPattern.GetPattern()); + surfacePattern->mMatrix = patternToUser; + surfacePattern->mExtendMode = mExtend; + break; + } + case PatternType::LINEAR_GRADIENT: { + LinearGradientPattern* linearGradientPattern = static_cast(mGfxPattern.GetPattern()); + linearGradientPattern->mMatrix = patternToUser; + linearGradientPattern->mStops = mStops; + break; + } + case PatternType::RADIAL_GRADIENT: { + RadialGradientPattern* radialGradientPattern = static_cast(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(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(mGfxPattern.GetPattern())->mSamplingFilter = filter; +} + +SamplingFilter +gfxPattern::SamplingFilter() const +{ + if (mGfxPattern.GetPattern()->GetType() != PatternType::SURFACE) { + return gfx::SamplingFilter::GOOD; + } + return static_cast(mGfxPattern.GetPattern())->mSamplingFilter; +} + +bool +gfxPattern::GetSolidColor(Color& aColorOut) +{ + if (mGfxPattern.GetPattern()->GetType() == PatternType::COLOR) { + aColorOut = static_cast(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 mSourceSurface; + mozilla::gfx::Matrix mPatternToUserSpace; + RefPtr mStops; + nsTArray 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 +#define getpid _getpid +#else +#include +#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(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(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 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 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 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 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 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 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 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 +gfxPlatform::CreateDrawTargetForSurface(gfxASurface *aSurface, const IntSize& aSize) +{ + SurfaceFormat format = aSurface->GetSurfaceFormat(); + RefPtr 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 mSrcSurface; + BackendType mBackendType; +}; + +void SourceBufferDestroy(void *srcSurfUD) +{ + delete static_cast(srcSurfUD); +} + +UserDataKey kThebesSurface; + +struct DependentSourceSurfaceUserData +{ + RefPtr mSurface; +}; + +void SourceSurfaceDestroyed(void *aData) +{ + delete static_cast(aData); +} + +void +gfxPlatform::ClearSourceSurfaceForSurface(gfxASurface *aSurface) +{ + aSurface->SetData(&kSourceSurface, nullptr, nullptr); +} + +/* static */ already_AddRefed +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(userData); + + if (surf->mSrcSurface->IsValid() && surf->mBackendType == aTarget->GetBackendType()) { + RefPtr 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 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 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(srcBuffer.get())->GetSurface() == + aSurface->CairoSurface()) || + (srcBuffer->GetType() == SurfaceType::CAIRO_IMAGE && + static_cast(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 +gfxPlatform::GetWrappedDataSourceSurface(gfxASurface* aSurface) +{ + RefPtr image = aSurface->GetAsImageSurface(); + if (!image) { + return nullptr; + } + RefPtr 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 +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 manager = do_GetService("@mozilla.org/gfx/screenmanager;1"); + MOZ_ASSERT(manager, "failed to get nsIScreenManager"); + + nsCOMPtr 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 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; + 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 +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 surf = CreateOffscreenSurface(aSize, SurfaceFormatToImageFormat(aFormat)); + if (!surf || surf->CairoStatus()) { + return nullptr; + } + + return CreateDrawTargetForSurface(surf, aSize); + } else { + return Factory::CreateDrawTarget(aBackend, aSize, aFormat); + } +} + +already_AddRefed +gfxPlatform::CreateOffscreenCanvasDrawTarget(const IntSize& aSize, SurfaceFormat aFormat) +{ + NS_ASSERTION(mPreferredCanvasBackend != BackendType::NONE, "No backend."); + RefPtr 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 +gfxPlatform::CreateOffscreenContentDrawTarget(const IntSize& aSize, SurfaceFormat aFormat) +{ + NS_ASSERTION(mPreferredCanvasBackend != BackendType::NONE, "No backend."); + return CreateDrawTargetForBackend(mContentBackend, aSize, aFormat); +} + +already_AddRefed +gfxPlatform::CreateSimilarSoftwareDrawTarget(DrawTarget* aDT, + const IntSize& aSize, + SurfaceFormat aFormat) +{ + RefPtr 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 +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 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& 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 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 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(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 sLayersSupportsHardwareVideoDecoding(false); +static bool sLayersHardwareVideoDecodingFailed = false; +static bool sBufferRotationCheckPref = true; + +static mozilla::Atomic 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 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 +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 +gfxPlatform::CreateHardwareVsyncSource() +{ + RefPtr 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& 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& 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 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 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 + 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 + 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 + GetSourceSurfaceForSurface(mozilla::gfx::DrawTarget *aTarget, + gfxASurface *aSurface, + bool aIsPlugin = false); + + static void ClearSourceSurfaceForSurface(gfxASurface *aSurface); + + static already_AddRefed + GetWrappedDataSourceSurface(gfxASurface *aSurface); + + virtual already_AddRefed + GetScaledFontForFont(mozilla::gfx::DrawTarget* aTarget, gfxFont *aFont); + + already_AddRefed + CreateOffscreenContentDrawTarget(const mozilla::gfx::IntSize& aSize, mozilla::gfx::SurfaceFormat aFormat); + + already_AddRefed + CreateOffscreenCanvasDrawTarget(const mozilla::gfx::IntSize& aSize, mozilla::gfx::SurfaceFormat aFormat); + + already_AddRefed + CreateSimilarSoftwareDrawTarget(DrawTarget* aDT, const IntSize &aSize, mozilla::gfx::SurfaceFormat aFormat); + + static already_AddRefed + 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& 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& /*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& 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 + 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 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& 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 + 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 mVsyncSource; + + RefPtr 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 mScreenReferenceSurface; + nsCOMPtr mSRGBOverrideObserver; + nsCOMPtr mFontPrefsObserver; + nsCOMPtr 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 mAzureCanvasBackendCollector; + mozilla::widget::GfxInfoCollector mApzSupportCollector; + mozilla::widget::GfxInfoCollector mTilesInfoCollector; + + RefPtr mRecorder; + RefPtr 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 + +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(); + } + 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 list; + gfxFontUtils::GetPrefsFontList(kFontSystemWhitelistPref, list); + uint32_t numFonts = list.Length(); + mFontFamilyWhitelistActive = (numFonts > 0); + if (!mFontFamilyWhitelistActive) { + return; + } + nsTHashtable 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& 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& 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>(2); + } + mFaceNamesMissed->PutEntry(aFaceName); + } + } + + return lookup; +} + +void +gfxPlatformFontList::PreloadNamesList() +{ + AutoTArray 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 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& aListOfFonts) +{ + for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { + RefPtr& 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 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 >& aFamilyArray) +{ + for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { + RefPtr& 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() : + ""), + 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 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& 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* 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>(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(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(aCharMap)); + if (found && found->GetKey() == aCharMap) { + mSharedCmaps.RemoveEntry(const_cast(aCharMap)); + } +} + +void +gfxPlatformFontList::ResolveGenericFontNames( + FontFamilyType aGenericType, + eFontPrefLang aPrefLang, + nsTArray>* aGenericFamilies) +{ + const char* langGroupStr = GetPrefLangName(aPrefLang); + const char* generic = GetGenericName(aGenericType); + + if (!generic) { + return; + } + + AutoTArray 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 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>* +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& aFamilyList) +{ + // map lang ==> langGroup + nsIAtom* langGroup = GetLangGroup(aLanguage); + + // langGroup ==> prefLang + eFontPrefLang prefLang = GetFontPrefLangFor(langGroup); + + // lookup pref fonts + nsTArray>* 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 ls = + do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + break; + + nsCOMPtr 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& aFontFamilyNames) +{ + for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { + RefPtr& 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(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& aListOfFonts); + + void UpdateFontList(); + + virtual void ClearLangGroupPrefFonts(); + + virtual void GetFontFamilyList(nsTArray >& 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* 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& aFamilyList); + + nsTArray>* + 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 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& 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>* aGenericFamilies); + + virtual nsresult InitFontListForPlatform() = 0; + + void ApplyWhitelist(); + + typedef nsRefPtrHashtable FontFamilyTable; + typedef nsRefPtrHashtable 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 mExtraNames; + + // face names missed when face name loading takes a long time + mozilla::UniquePtr > mFaceNamesMissed; + + // localized family names missed when face name loading takes a long time + mozilla::UniquePtr > mOtherNamesMissed; + + typedef nsTArray> PrefFontList; + typedef mozilla::RangedArray, + mozilla::eFamily_generic_first, + mozilla::eFamily_generic_count> PrefFontsForLangGroup; + mozilla::RangedArray 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 mReplacementCharFallbackFamily; + + nsTHashtable mBadUnderlineFamilyNames; + + // character map data shared across families + // contains weak ptrs to cmaps shared by font entry objects + nsTHashtable mSharedCmaps; + + // data used as part of the font cmap loading process + nsTArray > 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 > mUserFontSetList; + + nsCOMPtr mLangService; + nsTArray mCJKPrefLangs; + nsTArray 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 + +#include "gfxImageSurface.h" +#ifdef MOZ_X11 +#include +#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 + +#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 +gfxPlatformGtk::CreateOffscreenSurface(const IntSize& aSize, + gfxImageFormat aFormat) +{ + if (!Factory::AllowedSurfaceSize(aSize)) { + return nullptr; + } + + RefPtr 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& 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& 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 +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(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 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 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 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 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 mGLContext; + _XDisplay* mXDisplay; + Monitor mSetupLock; + base::Thread mVsyncThread; + RefPtr mVsyncTask; + Monitor mVsyncEnabledLock; + bool mVsyncEnabled; + }; +private: + // We need a refcounted VsyncSource::Display to use chromium IPC runnables. + RefPtr mGlobalDisplay; +}; + +already_AddRefed +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 = new GLXVsyncSource(); + VsyncSource::Display& display = vsyncSource->GetGlobalDisplay(); + if (!static_cast(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 + CreateOffscreenSurface(const IntSize& aSize, + gfxImageFormat aFormat) override; + + virtual already_AddRefed + GetScaledFontForFont(mozilla::gfx::DrawTarget* aTarget, gfxFont *aFont) override; + + virtual nsresult GetFontList(nsIAtom *aLangGroup, + const nsACString& aGenericFamily, + nsTArray& aListOfFonts) override; + + virtual nsresult UpdateFontList() override; + + virtual void + GetCommonFallbackFonts(uint32_t aCh, uint32_t aNextCh, + Script aRunScript, + nsTArray& 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 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 +#include + +#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 +gfxPlatformMac::CreateOffscreenSurface(const IntSize& aSize, + gfxImageFormat aFormat) +{ + if (!Factory::AllowedSurfaceSize(aSize)) { + return nullptr; + } + + RefPtr newSurface = + new gfxQuartzSurface(aSize, aFormat); + return newSurface.forget(); +} + +already_AddRefed +gfxPlatformMac::GetScaledFontForFont(DrawTarget* aTarget, gfxFont *aFont) +{ + gfxMacFont *font = static_cast(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& 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(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 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 +gfxPlatformMac::CreateHardwareVsyncSource() +{ + RefPtr 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(::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 + CreateOffscreenSurface(const IntSize& aSize, + gfxImageFormat aFormat) override; + + already_AddRefed + 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& 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 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 { + typedef mozilla::gfx::BaseSize 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 { + typedef mozilla::gfx::BasePoint 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::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(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 // for M_PI +#include +#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 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& 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 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 + class PrefTemplate final : public TypedPref + { + typedef TypedPref 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(); + 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* 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 + +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 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 mDrawTarget; + RefPtr 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(mSize.width); + unsigned int height = static_cast(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(mSize.width); + unsigned int height = static_cast(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 +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 result = Wrap(surface, aSize); + cairo_surface_destroy(surface); + return result.forget(); +} + +already_AddRefed gfxQuartzSurface::GetAsImageSurface() +{ + cairo_surface_t *surface = cairo_quartz_surface_get_image(mSurface); + if (!surface || cairo_surface_status(surface)) + return nullptr; + + RefPtr 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(); +} + +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 + +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 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 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 + +struct gfxQuaternion : public mozilla::gfx::BasePoint4D { + typedef mozilla::gfx::BasePoint4D 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 { + typedef mozilla::gfx::BaseMargin 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 { + typedef mozilla::gfx::BaseRect 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(svgData); + mDocIndex = nullptr; + + if (sizeof(Header) <= length && uint16_t(mHeader->mVersion) == 0 && + uint64_t(mHeader->mDocIndexOffset) + 2 <= length) { + const DocIndex* docIndex = reinterpret_cast + (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 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 docLoaderFactory = do_GetService(contractId); + NS_ASSERTION(docLoaderFactory, "Couldn't get DocumentLoaderFactory"); + + nsCOMPtr 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 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 &aResult) +{ + nsCOMPtr stream; + nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), + reinterpret_cast(aBuffer), + aBufLen, NS_ASSIGNMENT_DEPEND); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 stream; + nsresult rv = CreateBufferedStream(aBuffer, aBufLen, stream); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr uri; + nsHostObjectProtocolHandler::GenerateURIString(NS_LITERAL_CSTRING(FONTTABLEURI_SCHEME), + nullptr, + mSVGGlyphsDocumentURI); + + rv = NS_NewURI(getter_AddRefs(uri), mSVGGlyphsDocumentURI); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr principal = nsNullPrincipal::Create(); + + nsCOMPtr 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 document(do_QueryInterface(domDoc)); + if (!document) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr 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 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 mDocument; + nsCOMPtr mViewer; + nsCOMPtr mPresShell; + + nsBaseHashtable 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 mGlyphDocs; + nsBaseHashtable 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 GetFillPattern(const DrawTarget* aDrawTarget, + float aOpacity, + const gfxMatrix& aCTM) { + if (mFillPattern) { + mFillPattern->SetMatrix(aCTM * mFillMatrix); + } + RefPtr fillPattern = mFillPattern; + return fillPattern.forget(); + } + + already_AddRefed GetStrokePattern(const DrawTarget* aDrawTarget, + float aOpacity, + const gfxMatrix& aCTM) { + if (mStrokePattern) { + mStrokePattern->SetMatrix(aCTM * mStrokeMatrix); + } + RefPtr 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 mFillPattern; + RefPtr 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 +#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 +{ + typedef gfxBaseSharedMemorySurface Super; + friend class gfxBaseSharedMemorySurface; +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& 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& 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 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(storage) + aSize, 0, + aLength * sizeof(CompressedGlyph)); + + return storage; +} + +already_AddRefed +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 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(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* + 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 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 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 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(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 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 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,

+ // &%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(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 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& 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 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 font = ff.Font(); + if (!font) { + gfxFontEntry* fe = mFonts[i].FontEntry(); + gfxCharacterMap* unicodeRangeMap = nullptr; + if (fe->mIsUserFontContainer) { + gfxUserFontEntry* ufe = static_cast(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(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(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,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(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 +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 +gfxFontGroup::MakeSpaceTextRun(const Parameters *aParams, uint32_t aFlags) +{ + aFlags |= TEXT_IS_8BIT | TEXT_IS_ASCII | TEXT_IS_PERSISTENT; + + RefPtr 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 (bug 970891), + // find one that does. + uint8_t matchType; + RefPtr 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 +gfxFontGroup::MakeBlankTextRun(uint32_t aLength, + const Parameters *aParams, uint32_t aFlags) +{ + RefPtr 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 +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 dt(aProvider->GetDrawTarget()); + if (dt) { + RefPtr + hyphRun(MakeHyphenTextRun(dt, + aProvider->GetAppUnitsPerDevUnit())); + mHyphenWidth = hyphRun.get() ? hyphRun->GetAdvanceWidth() : 0; + } + } + return mHyphenWidth; +} + +already_AddRefed +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 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 +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 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 +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 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(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(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 +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 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 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 refDT = aRefDrawTargetGetter.GetRefDrawTarget(); + Parameters params = { + refDT, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevPixel + }; + mCachedEllipsisTextRun = + MakeTextRun(ellipsis.get(), ellipsis.Length(), ¶ms, + 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 +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 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 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 +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 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 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 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 firstFont = GetFontAt(0, aCh); + if (firstFont) { + if (firstFont->HasCharacter(aCh)) { + *aMatchType = gfxTextRange::kFontGroup; + return firstFont.forget(); + } + + RefPtr 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 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 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 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 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(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 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 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 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 +void gfxFontGroup::ComputeRanges(nsTArray& 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 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() : ""), + (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 +gfxFontGroup::WhichPrefFontSupportsChar(uint32_t aCh) +{ + RefPtr 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>* 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 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 +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 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(Script::NUM_SCRIPT_CODES), + "how did we set the bit for an invalid script code?"); + uint32_t tag = GetScriptTagForCode(static_cast